1 2 3 4 5 | bpy.context.object # Active object (last object selected) bpy.context.selected_objects # All selected objects context.scene.objects # All objects in current scene bpy.data.objects # All objects bpy.data.meshes # All meshes |
check existing bool modifiers individually to see if they change the dimensions or vert count of the depsgraph object, removing bools with no effect.
Would be useful for removing unnecessary bool mods on slices
This could potentially be slow in a dense lookup, so it’s best as a util operator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | # Iterate through modifiers for mod in context.object.modifiers: # Store the current view state of the modifier show_viewport = bool(mod.show_viewport) # Get the dimensions with the modifier enabled and disabled dims = [] for show in [True, False]: mod.show_viewport = show context.view_layer.update() dims.append(context.object.dimensions[:]) # Restore the view state of the modifier mod.show_viewport = show_viewport # If the dimensions changed, the modifier stays if any(a != b for a, b in zip(dims[0], dims[1])): continue # Get the vertex count with the modifier enabled and disabled verts = [] for show in [True, False]: mod.show_viewport = show context.view_layer.update() deps = context.evaluated_depsgraph_get() eva = context.object.evaluated_get(deps) mesh = eva.to_mesh() bm = bmesh.new() bm.from_mesh(mesh) verts.append(len(bm.verts)) # Restore the view state of the modifier mod.show_viewport = show_viewport # If the vertex count changed, the modifier stays if verts[0] != verts[1]: continue # Otherwise, remove the modifier context.object.modifiers.remove(mod) |
how do i store contents of bpy.context.scene.cursor.location as values not references?
1 2 | cursor = bpy.context.scene.cursor.location[:] bpy.context.scene.cursor.location = cursor |
[:] is a slicing operation, it gives you a new array with the values of the one you do it on
Syntax: [start : end : step]
Step is optional and start and end are the start and end of the array of you leave them blank like [:]
for objects and meshes and such you can use obj.copy()
If you ever come across a situation where neither [:] nor .copy() work, there’s always
1 2 3 | import copy a = something b = copy.deepcopy(a) |
Panel popup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import bpy from .. ui . main_panel import SOURCEOPS_PT_MainPanel class SOURCEOPS_OT_CallPanel(bpy.types.Operator, SOURCEOPS_PT_MainPanel): bl_idname = "sourceops.call_panel" bl_options = {'REGISTER'} bl_label = "SourceOps Panel" bl_description = "Display the SourceOps panel" def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, width=300) def execute(self, context): return {'FINISHED'} |
Copy physical name to logical name:
1 2 | for obj in bpy.context.selected_objects: obj.data.name = obj.name |
Drivers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # create empty driver driver = empty.driver_add('rotation_euler', 2).driver driver.expression = "(frame - frame_start) * (2 * pi) / (1 + frame_end - frame_start) * 2" # create frame start variable frame_start = driver.variables.new() frame_start.name = "frame_start" frame_start.targets[0].id_type = 'SCENE' frame_start.targets[0].id = context.scene frame_start.targets[0].data_path = "frame_start" # create frame end variable frame_end = driver.variables.new() frame_end.name = "frame_end" frame_end.targets[0].id_type = 'SCENE' frame_end.targets[0].id = context.scene frame_end.targets[0].data_path = "frame_end" |
Blink all objects’ latest Bevel modifiers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | bl_info = { "name" : "ModalTimerTest", "author" : "c0", "description" : "", "blender" : (2, 80, 0), "version" : (0, 0, 1), "location" : "", "warning" : "", "category" : "Generic" } import bpy class ModalTimerOperator(bpy.types.Operator): """Operator which runs its self from a timer""" bl_idname = "wm.modal_timer_operator" bl_label = "Modal Timer Operator" _timer = None def modal(self, context, event): if event.type in {'RIGHTMOUSE', 'ESC'}: self.cancel(context) return {'CANCELLED'} if event.type == 'TIMER': # assuming we are in 3D view... for obj in bpy.context.scene.objects: for mod in reversed(obj.modifiers): #print ("obj " + str(obj.name) + " " + str(obj.type)) #print ("mod " + str(mod.name) + " " + str(mod.type)) #print ("\n") if mod.type == "BEVEL": mod.show_viewport = not mod.show_viewport break return {'PASS_THROUGH'} def execute(self, context): wm = context.window_manager self._timer = wm.event_timer_add(3, window=context.window) wm.modal_handler_add(self) return {'RUNNING_MODAL'} def cancel(self, context): wm = context.window_manager wm.event_timer_remove(self._timer) def register(): bpy.utils.register_class(ModalTimerOperator) def unregister(): bpy.utils.unregister_class(ModalTimerOperator) if __name__ == "__main__": register() # test call bpy.ops.wm.modal_timer_operator() |
In Blender 2.8 the class name must match the following convention:
UPPER_CASE_{SEPARATOR}_mixed_case
Where the {SEPARATOR} is two letters denoting the class belonging to a certain type (from which type the class is inherited):
HT – Header
MT – Menu
OT – Operator
PT – Panel
UL – UI list
The class identifier “bl_idname” must match the class name. Examples of valid class names and identifiers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | class MYADDON_OT_my_operator(bpy.types.Operator): bl_idname = 'myaddon.my_operator' ... class MYADDON_MT_my_menu(bpy.types.Menu): bl_idname = 'MYADDON_MT_my_menu' ... class MYADDON_HT_my_header(bpy.types.Header): bl_idname = 'MYADDON_HT_my_header' ... class MYADDON_PT_my_panel(bpy.types.Panel): bl_idname = 'MYADDON_PT_my_panel' ... class MYADDON_OT_my_operator(bpy.types.Operator): bl_idname = 'myaddon.my_operator' ... class MYADDON_MT_my_menu(bpy.types.Menu): bl_idname = 'MYADDON_MT_my_menu' ... class MYADDON_HT_my_header(bpy.types.Header): bl_idname = 'MYADDON_HT_my_header' ... class MYADDON_PT_my_panel(bpy.types.Panel): bl_idname = 'MYADDON_PT_my_panel' ... |
If class name or identifier doesn’t meet the conventions, Blender signals with error: ‘…’ doesn’t contain ‘PT’ with prefix & suffix
1 | new_list = [expression(i) for i in old_list if filter(i)] |
is equal to
1 2 3 4 | new_list = [] for i in old_list: if filter(i): new_list.append(expressions(i)) |
which means
1 | self.bound_box = hops_math.coords_to_bounds([v.co for v in self.sources[0].data.vertices if v.select]) |
is equal to:
1 2 3 4 | self.bound_box = [] for v in self.sources[0].data.vertices: if v.select: self.bound_box.append(hops_math.coords_to_bounds(v.co)) |
1 2 3 4 5 6 | g_scene_name = '' def store_scene(context): global g_scene_name g_scene_name = context.scene.name scene = bpy.data.scenes[g_scene_name] |
Draws two cubes. Move, rotate, scale (not applied).
1 2 | for obj in bpy.context.selected_objects: print(obj) |
<bpy_struct, Object(“Cube”)>
<bpy_struct, Object(“Cube.001”)
1 2 | for obj in bpy.context.selected_objects: print(obj.bound_box) |
<bpy_float[8], Object.bound_box>
<bpy_float[8], Object.bound_box>
1 2 | for obj in bpy.context.selected_objects: print(obj.bound_box[0]) |
<bpy_float[3], Object.bound_box>
<bpy_float[3], Object.bound_box>
You can out a for loop in the for loop for going through the bound box corners, then print the 3 floats that a corner consists of with an f string
1 2 3 4 5 6 7 8 9 10 11 12 | for obj in bpy.context.selected_objects: for corner in obj.bound_box: print(f"({', '.join(str(n) for n in corner)})") (-1.0, -1.0, -1.0) (-1.0, -1.0, 1.0) (-1.0, 1.0, 1.0) (-1.0, 1.0, -1.0) (1.0, -1.0, -1.0) (1.0, -1.0, 1.0) (1.0, 1.0, 1.0) (1.0, 1.0, -1.0) |
Or like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from mathutils import Vector obj = bpy.context.active_object bbox = [Vector(v) for v in obj.bound_box] print(bbox) [Vector((-1.0, -1.0, -1.0)), Vector((-1.0, -1.0, 1.0)), Vector((-1.0, 1.0, 1.0)), Vector((-1.0, 1.0, -1.0)), Vector((1.0, -1.0, -1.0)), Vector((1.0, -1.0, 1.0)), Vector((1.0, 1.0, 1.0)), Vector((1.0, 1.0, -1.0))] |
F string is a format operator
You can put any variable encased into {} between ” ” so python would use it as a string:
f "{int} str {int}"
Here we use F string curly braces {} to evaluate an expression instead of using a single variable:
print(f"({', '.join(str(n) for n in corner)})")
https://docs.blender.org/api/current/bpy.ops.html
https://docs.blender.org/api/current/bpy.types.Space.html https://docs.blender.org/manual/en/latest/interface/controls/buttons/menus.html
https://docs.blender.org/api/current/bpy.types.Panel.html
[!] https://docs.blender.org/api/current/bpy.types.Operator.html
Here is an example how to create a custom Panel in Blender Python:
Here is an example how to create a custom Menu in Blender Python:
Pop-up Menus
Pop-up Menus like the [Q] menu for HardOps are just your normal average bpy.types.Menu
menu. They are simply floating because they are not part of a Header or Panel which makes them look like pop-up menus.
Menus are part of the user interface. Menus are dependent on a Space area of the screen (in this case View_3D). Typically a menu option calls a Menu (sub menu) or Operator (action). Operators can call their own floating Menu like Hard Ops does.
To see all Menus try this in Scripting tab console:
1 | bpy.types.VIEW3D_M [TAB] |
To see all Panels try this in Scripting tab console:
1 | bpy.types.VIEW3D_P [TAB] |
HT – Header
MT – Menu
OT – Operator
PT – Panel
UL – UI list
Tip: Use [CTRL-C] to copy the call from a menu option to the clipboard and learn the code.
For example, to call the Unwrap submenu:
1 | bpy.ops.wm.call_menu(name="VIEW3D_MT_uv_map") |
The ‘custom Menu video’ just before the above video contains the following syntax:
1 2 | # call another menu layout.operator("wm.call_menu", text="Unwrap").name = "VIEW3D_MT_uv_map" |
Use Merge + Split Edges and Faces to create vertices at intersects. Extremely useful when switching from non-destructive workflow to destructive (subdivision modelling) workflow. For when Knife Cut doesn’t cut it.
“bool extract”
BC starts out as normal rectangle cut.
But it doesn’t cut into the main object at all.
There is no diff bool added to the main object.
Then, we grab all the difference bools that are on the main object (negative space; the holes).
We copy them onto the extract cutter cube.
We change each diff bool on the cutter cube into intersect bools.
We now have a ‘negative’ of the main object; we have captured ‘the holes’.
We can now use this cutter as a cookie cutter to make the same holes as on the original object on other objects.
Problem: This type of extraction only works on 100% parametric models where all holes were cut using modifiers.
Solution:
“make extract”
BC starts out as a normal rectangle cut where we draw a cube ‘into’ the object (extrude, no offset).
However, this time BC uses “make” mode (not to be confused with “join” mode).
This cutter is simply a separate object.
Note: We have to recess the cube below the surface slightly (booleans limitation bypass)
We can now substract the original object from the make cube to capture the negative space, the holes.
The ‘make cube’ cutter has 1 difference boolean (with main object). There are no slices to be transferred.
We can ‘Apply’ this bool if we want stop being dependent on the modifier stack of the main object.
Same as before, we can now use this cutter as a cookie cutter to make the same holes as on the original object on other
objects.
Advantage: “make extract” also captures non-parametric holes e.g. [Edit Mode] subdivision modelling holes.
Source file: make extract.blend
1 2 3 4 5 6 7 8 | bpy.ops.mesh.bevel(offset=0.1, segments=5, vertex_only=True) bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') # start - remove netting faces from selection bpy.ops.mesh.hide(unselected=True) bpy.ops.mesh.select_face_by_sides(number=4, type='GREATER', extend=False) bpy.ops.mesh.select_all(action='INVERT') bpy.ops.mesh.reveal(select=False) # end - remove netting faces from selection |