===== Blender ===== ==== cushion ==== source : https://www.youtube.com/watch?v=MLUnfE4FcOY * Unordered List Itemcube * s z * loop cuts hor 30 * loop cut vert 1 * physics -> cloth -> change preset to silk * collision -> quality 4 * enable self collision * field weights -> gravity 0 * add a "force field" (make sure its placed in the center of the cube) * strength 150 * start animation and stop when enough * apply the cloth modifier * remove the force field ==== dae cleaner ==== import bpy def decimate(): for obj in bpy.data.objects: if obj.type == 'MESH': bpy.context.scene.objects.active = obj bpy.ops.object.modifier_add(type="DECIMATE") mod_name = bpy.context.object.modifiers[-1].name bpy.context.object.modifiers[mod_name].decimate_type = 'DISSOLVE' bpy.ops.object.modifier_apply(apply_as = 'DATA', modifier=mod_name) def recenter_origin(): bpy.context.scene.cursor_location = (0.0,0.0,0.0) for obj in bpy.data.objects: if obj.type == 'MESH': obj.select = True #bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS') bpy.ops.object.origin_set(type='ORIGIN_CURSOR') def set_layers(): for obj in bpy.data.objects: if obj.type == 'MESH': bpy.context.scene.objects.active = obj if obj.parent.name == "group_0": if obj.name[0:5] == "group": bpy.context.object.layers[1] = True elif obj.name[0:8] == "instance": bpy.context.object.layers[2] = True if obj.parent.name[0:8] == "instance": bpy.context.object.layers[3] = True if (obj.parent.name[0:5] == "group") and (obj.parent.name != "group_0"): bpy.context.object.layers[4] = True bpy.context.object.layers[0] = False def unparent_all(): bpy.ops.object.select_all(action='DESELECT') for obj in bpy.data.objects: bpy.context.scene.objects.active = obj if bpy.context.active_object.parent != None: obj.select = True bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') def select_all(status=True): #bpy.ops.object.select_all(action='SELECT') #bpy.ops.object.select_all(action='DESELECT') for obj in bpy.data.objects: obj.select = status def hide_all(status=True): for obj in bpy.data.objects: bpy.context.scene.objects.active = obj bpy.context.object.hide = status def remove_duplicates(): unparent_all() bpy.ops.object.select_all(action='DESELECT') datas = {} for obj in bpy.data.objects: if obj.type == 'MESH': bpy.context.scene.objects.active = obj actual = bpy.context.scene.objects.active if actual.data.name not in datas: datas[actual.data.name] = [obj.name] else: datas[actual.data.name].append(obj.name) #print(datas) for values in datas: length = len(datas[values]) if length > 1: print(values, length, sep=':') to_delete = sorted(datas[values])[1:length] for value in to_delete: bpy.data.objects[value].select = True bpy.ops.object.delete() #if length == 6: # print(datas[values]) #print(values, datas[values][0].split('_')[1], sep=" : ") #print(to_delete) print("== Decimation Start ==") print(bpy.data.scenes['Scene'].statistics()) remove_duplicates() decimate() recenter_origin() print("== Decimation End ==") print(bpy.data.scenes['Scene'].statistics()) ==== shortcuts ==== === Camera === ^ Shortcut ^ Function ^ Notes ^ |ctrl+alt+0|align camera to view || === Interface === http://www.blendertips.com/hotkeys.html ^ Shortcut ^ Function ^ Notes ^ |ctrl + up/dow arrows|show / hide full screen panel|| |keypad 1/3/7|change view|| === Beziers === ^ Shortcut ^ Function ^ Notes ^ |v|set handle type|| |alt+c|close|| === Edit mode === ^ Shortcut ^ Function ^ Notes ^ |a|select all / deselect all|| |b|box select|| |bb|brush select|| |g|grab (translate)|| |r|rotate|| |s|scale|| |x/y/z|using axis|| |shift + x/y/z|use 2 axis except the one selected (ex : shift + z = use x+y)|| |xx/yy/zz|use local axis|| |ctrl + l|select linked elements|| |p|separate items|| |j|join item|| |h|hide|| |alt + h|show all|| |f|create a face|| === Utils === ^ Shortcut ^ Function ^ Notes ^ |ctrl + t|tracking mode to align camera to an object|| |alt + g|clear coordinates|| |shift + c|3D Cursor to origin|| ==== Blender gmaps ==== === sources === * https://youtu.be/X6Q7dbtXVZQ * https://www.youtube.com/watch?v=XUkMY8Sp_AM === installation === == renderdoc == * install renderdoc v1.10 : https://renderdoc.org/builds * create a shortcut to chrome using "target" : C:\Windows\System32\cmd.exe /c "SET RENDERDOC_HOOK_EGL=0 && START "" ^"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe^" --disable-gpu-sandbox --gpu-startup-dialog" == renderdoc blender addon == * install blender addon https://github.com/eliemichel/MapsModelsImporter === meshlab === * install meshlab https://www.meshlab.net/=download === procedure === == capture == * run renderdoc * go to "file/inject into process" * run chrome using the shortcut and see the alert * get the alert "process id" and use "refresh" in renderdoc to see the process and then push "inject" button (bottom right) * click on the "ok" button on the chrome alert * chrome should display numbers on the top left * go to google maps and set to satellite and 3d * explore the place with zooming and turn around the buildings * go to renderdoc and use "capture after" 5 seconds * go to chrome and move the 3d view and then it will hang shortly (it has been captured) * go to renderdoc to see the capture * right click and save the rdc file * use import rdc file inside blender == optimizing mesh == * remove unuseful items * select all the items and join them (ctrl+j) * "edit mode" and "merge by distance" == optimize uv map == * duplicate the item and disable the first one * remove the materials using this script : import bpy C = bpy.context for i in range(0,len(C.object.material_slots)): C.object.active_material_index = 1 bpy.ops.object.material_slot_remove() bpy.ops.object.mode_set(mode = 'EDIT') bpy.ops.mesh.select_all(action = 'SELECT') bpy.ops.object.material_slot_assign() bpy.ops.object.mode_set(mode = 'OBJECT') * export selection as obj * go to meshlab * "file/import mesh" * select mesh * go to "filters / texture / parametrization : trivial per-triangle" * set "texture dimension" to 8192 and click "apply" * go to "render / show uv tex map" to see the result * "file / export mesh" * uncheck "vert / normal" and "face / color" so keep "wedge / texcoord" * go back to blender * delete the object * import obj == bake the new texture == * select the imported mesh * go to "shader editor" * add a new material * add an "image texture" node (do not connect it) * create a new texture with 8192px size * change renderer to "cycles" (set device to "cpu") * go to "bake" tab and select "bake type" as "diffuse" * under "influence", disable "direct" and "indirect" * check "selected to active" * "ray distance" to 0.1 * "output margin" 1px * select image texture node in shader editor * select disbled mesh and then select imported mesh (using ctrl to set the last one as active) * click "bake" * wait * connect the image node * rename the new texture and export it from the image editor ==== blender utils ==== === blender 2.8 get names === import bpy def getNames(): selection_names = bpy.context.selected_objects for i in selection_names: print(i.name) getNames() === blender 2.8 custom join === import bpy def customJoin(): # convert to single user bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True, material=False, animation=False) # get selection selected_objects = bpy.context.selected_objects # do the job for o in selected_objects: if o.type != 'EMPTY': # set active bpy.context.view_layer.objects.active = o # convert to mesh if o.type in ['CURVE', 'FONT']: bpy.ops.object.convert(target='MESH') # apply modifiers for mod in o.modifiers: bpy.ops.object.modifier_apply(modifier = mod.name) # get the object's name object_name = o.name # get group if object_name in o.vertex_groups: group = o.vertex_groups[object_name] else: group = o.vertex_groups.new(name = object_name) # get vertices verts = [] for vert in o.data.vertices: verts.append(vert.index) # assign vertices to group group.add(verts, 1.0, 'REPLACE') # join to a single object bpy.ops.object.join() customJoin() === 2.79 groups === import bpy def giveGroup(groupname=""): selected = bpy.context.selected_objects for item in selected: bpy.context.scene.objects.active = item bpy.ops.object.group_link(group='veranda') #giveGroup("veranda") def giveMaterial(materialname=""): selected = bpy.context.selected_objects for item in selected: bpy.context.scene.objects.active = item bpy.context.object.active_material.name = materialname giveMaterial("vitre") === addon template === import bpy bl_info = { "name": "my test addon", "author": "jojo", "version": (1,0), "blender": (2,83,0), "category": "Object", "location": "Operator Search", "description": "More monkeys!!", "warning" : "", "doc_url": "", "tracker_url": "" } class MESH_OT_monkey_grid(bpy.types.Operator): """Let's spread some joy""" bl_idname = "mesh.monkey_grid" bl_label = "Monkey Grid" bl_options = {'REGISTER', 'UNDO'} count_x: bpy.props.IntProperty( name="X", description="Number of monkeys in the x direction", default=3, min=1,soft_max=10 ) count_y: bpy.props.IntProperty( name="Y", description="Number of monkeys in the y direction", default=2, min=1,soft_max=10 ) size: bpy.props.FloatProperty( name="Size", description="Size of each monkey", min=0,max=1 ) @classmethod def poll(cls, context): if context.area.type == 'VIEW_3D': return True return False def execute(self, context): for idx in range(self.count_x * self.count_y): x = idx % self.count_x y = idx // self.count_x bpy.ops.mesh.primitive_monkey_add( size=self.size, location=(x, y, 1)) return {'FINISHED'} class VIEW3D_PT_monkey_grid(bpy.types.Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "Monkeys" bl_label = "Grid" def draw(self, context): self.layout.operator('mesh.monkey_grid', text='Default grid', icon='MONKEY') props = self.layout.operator('mesh.monkey_grid', text='Big grid', icon='MONKEY') props.count_x = 10 props.count_y = 10 props.size = 0.8 def register(): bpy.utils.register_class(MESH_OT_monkey_grid) bpy.utils.register_class(VIEW3D_PT_monkey_grid) def unregister(): bpy.utils.unregister_class(MESH_OT_monkey_grid) bpy.utils.unregister_class(VIEW3D_PT_monkey_grid) === Simple button === source : https://blenderartists.org/t/calling-a-function-from-a-button/616752/7 import bpy class SimpleOperator(bpy.types.Operator): """Tooltip""" bl_idname = "object.simple_operator" bl_label = "Simple Object Operator" @classmethod def poll(cls, context): return context.active_object is not None def execute(self, context): self.report({'INFO'}, "Button clicked!") return {'FINISHED'} def draw_func(self, context): layout = self.layout layout.operator("object.simple_operator") def register(): bpy.utils.register_class(SimpleOperator) bpy.types.VIEW3D_HT_header.prepend(draw_func) def unregister(): bpy.utils.unregister_class(SimpleOperator) bpy.types.VIEW3D_HT_header.remove(draw_func) if __name__ == "__main__": register() bl_info = { "name": "Exploded Bake", "category": "Render", } import bpy class ExplodedBake(bpy.types.Panel): """Creates a Panel in the Object properties window""" bl_label = "Exploded Bake" bl_idname = "OBJECT_PT_exploded_bake" # follow Blender convention for id names bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "object" def draw(self, context): layout = self.layout obj = context.object row = layout.row() row.prop(obj, "name") row = layout.row() row.operator("button.explode") class buttonExplode(bpy.types.Operator): bl_idname = "button.explode" # translates to C-name BUTTON_OT_explode bl_label = "Button text" def execute(self, context): #self.report({'INFO'}, "Hello world!") print("hello") return {'FINISHED'} # (un-)register entire module, so you don't need to add every class here... def register(): bpy.utils.register_module(__name__) def unregister(): bpy.utils.unregister_module(__name__) if __name__ == "__main__": register() import bpy class SEBMAS_PROP_move_origins(bpy.types.PropertyGroup): def update_function(self, context): if self.move_origin_toggle: bpy.context.scene.tool_settings.use_transform_data_origin = True bpy.context.scene.tool_settings.snap_elements = {'VERTEX'} else: bpy.context.scene.tool_settings.use_transform_data_origin = False bpy.context.scene.tool_settings.snap_elements = {'INCREMENT'} return bpy.types.WindowManager.move_origin_toggle = bpy.props.BoolProperty( default = False, update = update_function) class SEBMAS_PT_tools_move_origin(bpy.types.Panel): bl_category = "Tool" bl_context = ".objectmode" # dot on purpose (access from topbar) bl_label = "SebMas" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' def draw(self, context): self.layout.prop(context.window_manager, 'move_origin_toggle', text="Move origin", toggle=True, icon='MONKEY') classes = ( SEBMAS_PT_tools_move_origin, ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) if __name__ == "__main__": register() ==== bounding box ==== Select an object and then : import bpy; ob = bpy.data.scenes.active.objects.active; ob.getData(mesh=True).verts.extend(ob.getBoundBox(0)) the object needs to dont have any “transformation” ==== inventory ==== import bpy import csv def displayLine(title, qty, price): print(title+ " : "+ str(qty)+ "("+ str(price* qty)+ ")") def cherche(): # get selection selected_objects = bpy.context.selected_objects inventory = { "eclisse-de-liaison-40-52" : { "price":4.75, "qty":0 }, "corniere-murale-2000mm": { "price":33.82, "qty":0 }, # solives "solive-omega 65-1000mm": { "price":17, "qty":0 }, "solive-omega 65-2000mm": { "price":34, "qty":0 }, "solive-omega 65-2500mm": { "price":42.44, "qty":0 }, "solive-omega 65-3000mm": { "price":51.03, "qty":0 }, # lambourdes "lambourde-omega 35-100mm": { "price":1.018, "qty":0 }, "lambourde-omega 35-150mm": { "price":1.52, "qty":0 }, "lambourde-omega 35-2000mm": { "price":20.37, "qty":0 }, "lambourde-omega 35-3000mm": { "price":30.52, "qty":0 }, "longueur-en-metres-lambourde-a-decouper": { "price":10.18, "qty":0 }, # profil de descente "profil-de-descente-55-20-100mm": { "price":0.57, "qty":0 }, "longueur-en-metres-profil-de-descente": { "price":5.78, "qty":0 }, # plots "plot": { "price":2.4, "qty":0 }, # lames (7 par m² donc a 90€ le m² ça fait 12.85 la lame) "lame-terrasse-1000mm": { "price":12.85, "qty":0 }, # scotch "bande-adhesive-epdm-100mm": { "price":0.12, "qty":0 }, "bande-adhesive-epdm-150mm": { "price":0.18, "qty":0 }, "bande-adhesive-epdm-1000mm": { "price":1.21, "qty":0 }, "bande-adhesive-epdm-2000mm": { "price":2.42, "qty":0 }, "longueur-en-metres-bande-adhesive": { "price": 1.21, "qty":0 } } # do the job for o in selected_objects: if o.type != 'EMPTY': # get the object's name object_name = o.name for item, details in inventory.items(): if object_name.__contains__(item): details["qty"] += 1 # longueur bande adhesive if object_name.__contains__("bande-adhesive-epdm-100mm"): inventory["longueur-en-metres-bande-adhesive"]["qty"] += 0.1 if object_name.__contains__("bande-adhesive-epdm-150mm"): inventory["longueur-en-metres-bande-adhesive"]["qty"] += 0.15 if object_name.__contains__("bande-adhesive-epdm-1000mm"): inventory["longueur-en-metres-bande-adhesive"]["qty"] += 1 if object_name.__contains__("bande-adhesive-epdm-2000mm"): inventory["longueur-en-metres-bande-adhesive"]["qty"] += 2 # longueur lambourde a decouper if object_name.__contains__("lambourde-omega 35-100mm"): inventory["longueur-en-metres-lambourde-a-decouper"]["qty"] += 0.1 if object_name.__contains__("lambourde-omega 35-150mm"): inventory["longueur-en-metres-lambourde-a-decouper"]["qty"] += 0.15 # longueur profil de descente if object_name.__contains__("profil-de-descente-55-20-100mm"): inventory["longueur-en-metres-profil-de-descente"]["qty"] += 0.1 return inventory def montre(inventory): total_price = 0 for item, value in inventory.items(): if not item.__contains__("bande-adhesive-epdm")\ and not item.__contains__("lambourde-omega 35-100mm")\ and not item.__contains__("lambourde-omega 35-150mm")\ and not item.__contains__("profil-de-descente-55-20-100mm"): if value["qty"] > 0: print(item) print("\t\tqty:\t"+ str(round(value["qty"], 2))) print("\t\tp/u:\t"+ str(value["price"])+ " ht") price = value["qty"]*value["price"] total_price += price print("\ttotal price:\t"+ str(round(price, 2))+ " ht\n") print("----------------------------------------") print("total price: "+ str(round((total_price* 1.206), 2))+ " ttc") def writeCSV(inventory): total_price = 0 rows = [['designation', 'qty', 'pu', 'total']] for item, value in inventory.items(): if not item.__contains__("bande-adhesive-epdm")\ and not item.__contains__("lambourde-omega 35-100mm")\ and not item.__contains__("lambourde-omega 35-150mm")\ and not item.__contains__("profil-de-descente-55-20-100mm"): if value["qty"] > 0: price = value["qty"]*value["price"] rows.append([item, round(value["qty"], 2), value["price"], round(price, 2)]) with open(bpy.path.abspath("//")+'\\prix_terrasse.csv', 'w', newline='') as file: writer = csv.writer(file, delimiter='|') writer.writerows(rows) print("========================================") bpy.ops.object.select_all(action='DESELECT') # get collection col = bpy.data.collections["terrassteel-droit"] # get all children collections recursively for subcol in col.children_recursive: for obj in col.all_objects: obj.select_set(True) inventory = cherche() montre(inventory) writeCSV(inventory) print("========================================") ==== script as module ==== import bpy my_module = bpy.data.texts["Text"].as_module() my_module.toto() ==== delete collection ==== # source : https://blender.stackexchange.com/a/173894 import bpy #from bpy import context name = "Collection 1" remove_collection_objects = True #coll = context.collection # coll = bpy.data.collections.get(name) if coll: if remove_collection_objects: obs = [o for o in coll.objects if o.users == 1] while obs: bpy.data.objects.remove(obs.pop()) bpy.data.collections.remove(coll) ==== duplicate collection linked ==== import bpy def duplicate_collection_by_name(collection_name): original_collection = bpy.data.collections.get(collection_name) if original_collection is None: print(f"Collection '{collection_name}' does not exist.") return new_collection = original_collection.copy() new_collection.name = f"{collection_name}_duplicate" bpy.context.scene.collection.children.link(new_collection) ==== duplicate collection ==== import bpy def duplicate_collection_by_name(collection_name): original_collection = bpy.data.collections.get(collection_name) if original_collection is None: print(f"Collection '{collection_name}' does not exist.") return new_collection = bpy.data.collections.new(f"{collection_name}_duplicate") bpy.context.scene.collection.children.link(new_collection) for obj in original_collection.objects: new_object = obj.copy() new_object.data = obj.data.copy() new_collection.objects.link(new_object) ==== delete empty collection ==== import bpy def delete_empty_collections(): collections = bpy.data.collections for collection in collections: if len(collection.objects) == 0: bpy.data.collections.remove(collection, do_unlink=True) ==== delete collection and its content ==== import bpy def delete_collection_by_name(collection_name): collection = bpy.data.collections.get(collection_name) if collection is not None: bpy.data.collections.remove(collection, do_unlink=True) else: print(f"Collection '{collection_name}' does not exist.") {{tag>blender}}