diff options
375 files changed, 27400 insertions, 14896 deletions
@@ -15,3 +15,6 @@ __pycache__/ Thumbs.db ehthumbs.db Desktop.ini + +# external addons +cycles/ diff --git a/add_advanced_objects_menu/__init__.py b/add_advanced_objects_menu/__init__.py index 42a33445..32bbbd7f 100644 --- a/add_advanced_objects_menu/__init__.py +++ b/add_advanced_objects_menu/__init__.py @@ -92,8 +92,8 @@ from bpy.props import ( # Define the "Scenes" menu -class INFO_MT_scene_elements_add(Menu): - bl_idname = "INFO_MT_scene_elements" +class VIEW3D_MT_scene_elements_add(Menu): + bl_idname = "VIEW3D_MT_scene_elements" bl_label = "Test Scenes" def draw(self, context): @@ -108,8 +108,8 @@ class INFO_MT_scene_elements_add(Menu): # Define the "Lights" menu -class INFO_MT_mesh_lamps_add(Menu): - bl_idname = "INFO_MT_scene_lamps" +class VIEW3D_MT_mesh_lights_add(Menu): + bl_idname = "VIEW3D_MT_scene_lights" bl_label = "Lighting Sets" def draw(self, context): @@ -122,8 +122,8 @@ class INFO_MT_mesh_lamps_add(Menu): # Define the "Chains" menu -class INFO_MT_mesh_chain_add(Menu): - bl_idname = "INFO_MT_mesh_chain" +class VIEW3D_MT_mesh_chain_add(Menu): + bl_idname = "VIEW3D_MT_mesh_chain" bl_label = "Chains" def draw(self, context): @@ -134,15 +134,15 @@ class INFO_MT_mesh_chain_add(Menu): # Define the "Array" Menu -class INFO_MT_array_mods_add(Menu): - bl_idname = "INFO_MT_array_mods" +class VIEW3D_MT_array_mods_add(Menu): + bl_idname = "VIEW3D_MT_array_mods" bl_label = "Array Mods" def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - layout.menu("INFO_MT_mesh_chain", icon="LINKED") + layout.menu("VIEW3D_MT_mesh_chain", icon="LINKED") layout.operator("objects.circle_array_operator", text="Circle Array", icon="MOD_ARRAY") @@ -153,8 +153,8 @@ class INFO_MT_array_mods_add(Menu): # Define the "Blocks" Menu -class INFO_MT_quick_blocks_add(Menu): - bl_idname = "INFO_MT_quick_tools" +class VIEW3D_MT_quick_blocks_add(Menu): + bl_idname = "VIEW3D_MT_quick_tools" bl_label = "Block Tools" def draw(self, context): @@ -171,8 +171,8 @@ class INFO_MT_quick_blocks_add(Menu): # Define the "Phsysics Tools" Menu -class INFO_MT_Physics_tools_add(Menu): - bl_idname = "INFO_MT_physics_tools" +class VIEW3D_MT_Physics_tools_add(Menu): + bl_idname = "VIEW3D_MT_physics_tools" bl_label = "Physics Tools" def draw(self, context): @@ -190,12 +190,12 @@ def menu(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' self.layout.separator() - self.layout.menu("INFO_MT_scene_elements", icon="SCENE_DATA") - self.layout.menu("INFO_MT_scene_lamps", icon="LAMP_SPOT") + self.layout.menu("VIEW3D_MT_scene_elements", icon="SCENE_DATA") + self.layout.menu("VIEW3D_MT_scene_lights", icon="LIGHT_SPOT") self.layout.separator() - self.layout.menu("INFO_MT_array_mods", icon="MOD_ARRAY") - self.layout.menu("INFO_MT_quick_tools", icon="MOD_BUILD") - self.layout.menu("INFO_MT_physics_tools", icon="PHYSICS") + self.layout.menu("VIEW3D_MT_array_mods", icon="MOD_ARRAY") + self.layout.menu("VIEW3D_MT_quick_tools", icon="MOD_BUILD") + self.layout.menu("VIEW3D_MT_physics_tools", icon="PHYSICS") # Addons Preferences @@ -524,7 +524,7 @@ def register(): ) # Add "Extras" menu to the "Add" menu - bpy.types.INFO_MT_add.append(menu) + bpy.types.VIEW3D_MT_add.append(menu) try: bpy.types.VIEW3D_MT_AddMenu.append(menu) except: @@ -533,7 +533,7 @@ def register(): def unregister(): # Remove "Extras" menu from the "Add" menu. - bpy.types.INFO_MT_add.remove(menu) + bpy.types.VIEW3D_MT_add.remove(menu) try: bpy.types.VIEW3D_MT_AddMenu.remove(menu) except: diff --git a/add_advanced_objects_menu/add_light_template.py b/add_advanced_objects_menu/add_light_template.py index 9e2c139f..5b4fbc86 100644 --- a/add_advanced_objects_menu/add_light_template.py +++ b/add_advanced_objects_menu/add_light_template.py @@ -5,10 +5,10 @@ from bpy.types import Operator from bpy.props import BoolProperty -def add_lamps(self, context): +def add_lights(self, context): if self.bKeyLight: - keyLight = bpy.data.lamps.new(name="Key_Light", type="SPOT") + keyLight = bpy.data.lights.new(name="Key_Light", type="SPOT") ob = bpy.data.objects.new("Key_Light", keyLight) constraint = ob.constraints.new(type='COPY_LOCATION') constraint.use_offset = True @@ -23,7 +23,7 @@ def add_lamps(self, context): ob.rotation_euler[2] = -0.785398 if self.bFillLight: - fillLight = bpy.data.lamps.new(name="Fill_Light", type="SPOT") + fillLight = bpy.data.lights.new(name="Fill_Light", type="SPOT") ob = bpy.data.objects.new("Fill_Light", fillLight) constraint = ob.constraints.new(type='COPY_LOCATION') constraint.use_offset = True @@ -39,7 +39,7 @@ def add_lamps(self, context): ob.data.energy = 0.3 if self.bBackLight: - backLight = bpy.data.lamps.new(name="Back_Light", type="SPOT") + backLight = bpy.data.lights.new(name="Back_Light", type="SPOT") ob = bpy.data.objects.new("Back_Light", backLight) constraint = ob.constraints.new(type='COPY_LOCATION') constraint.use_offset = True @@ -121,7 +121,7 @@ class OBJECT_OT_add_light_template(Operator): self.target = context.active_object self.camera = context.scene.camera - add_lamps(self, context) + add_lights(self, context) except Exception as e: self.report({'WARNING'}, diff --git a/add_advanced_objects_menu/arrange_on_curve.py b/add_advanced_objects_menu/arrange_on_curve.py index c292e341..bfed37a7 100644 --- a/add_advanced_objects_menu/arrange_on_curve.py +++ b/add_advanced_objects_menu/arrange_on_curve.py @@ -59,7 +59,7 @@ class PanelDupliCurve(Panel): elif adv_obj.arrange_c_select_type == 'G': layout.column(align=True).prop_search( adv_obj, "arrange_c_obj_arranjar", - bpy.data, "groups" + bpy.data, "collections" ) if context.object.type == 'CURVE': layout.operator("object.arranjar_numa_curva", text="Arrange Objects") @@ -238,7 +238,7 @@ class DupliCurve(Operator): elif adv_obj.arrange_c_select_type == 'O': G_Objeto = bpy.data.objects[adv_obj.arrange_c_obj_arranjar], elif adv_obj.arrange_c_select_type == 'G': - G_Objeto = bpy.data.groups[adv_obj.arrange_c_obj_arranjar].objects + G_Objeto = bpy.data.collections[adv_obj.arrange_c_obj_arranjar].objects yawMatrix = mathutils.Matrix.Rotation(self.Yaw, 4, 'X') pitchMatrix = mathutils.Matrix.Rotation(self.Pitch, 4, 'Y') diff --git a/add_advanced_objects_menu/copy2.py b/add_advanced_objects_menu/copy2.py index 489f6dee..9a94ac1d 100644 --- a/add_advanced_objects_menu/copy2.py +++ b/add_advanced_objects_menu/copy2.py @@ -168,7 +168,7 @@ def copy_to_from(scene, to_obj, from_obj, copymode, axes, edgescale, scale): if copymode == 'E': # don't pass edgescalling to object types that cannot be scaled - if from_obj.type in ["CAMERA", "LAMP", "EMPTY", "ARMATURE", "SPEAKER", "META"]: + if from_obj.type in ["CAMERA", "LIGHT", "EMPTY", "ARMATURE", "SPEAKER", "META"]: edgescale = False edge_copy(scene, to_obj, from_obj, axes, edgescale, scale) diff --git a/add_advanced_objects_menu/mesh_easylattice.py b/add_advanced_objects_menu/mesh_easylattice.py index cdb165bd..d2e608e9 100644 --- a/add_advanced_objects_menu/mesh_easylattice.py +++ b/add_advanced_objects_menu/mesh_easylattice.py @@ -63,7 +63,7 @@ def createLattice(context, obj, props): # the rotation comes from the combined obj world # matrix which was converted to euler pairs ob.rotation_euler = buildRot_World(obj) - ob.show_x_ray = True + ob.show_in_front = True # Link object to scene scn = context.scene @@ -102,7 +102,7 @@ def createVertexGroup(obj): if obj.mode == "EDIT": bpy.ops.object.editmode_toggle() - group = obj.vertex_groups.new("easy_lattice_group") + group = obj.vertex_groups.new(name="easy_lattice_group") for vert in vertices: if vert.select is True: diff --git a/add_advanced_objects_menu/pixelate_3d.py b/add_advanced_objects_menu/pixelate_3d.py index 3a9fc886..567e882a 100644 --- a/add_advanced_objects_menu/pixelate_3d.py +++ b/add_advanced_objects_menu/pixelate_3d.py @@ -28,7 +28,7 @@ def pix(self, obj): mes.transform(obj.matrix_world) dup = bpy.data.objects.new('dup', mes) sce.objects.link(dup) - dup.dupli_type = 'VERTS' + dup.instance_type = 'VERTS' sce.objects.active = dup bpy.ops.object.mode_set() ver = mes.vertices diff --git a/add_advanced_objects_menu/random_box_structure.py b/add_advanced_objects_menu/random_box_structure.py index fa4b6497..ead19bd1 100644 --- a/add_advanced_objects_menu/random_box_structure.py +++ b/add_advanced_objects_menu/random_box_structure.py @@ -94,8 +94,8 @@ class makestructure(Operator): rsdchange = self.rsd oblst = [] uvyes = 0 - bpy.ops.group.create(name='Cubagrouper') - bpy.ops.group.objects_remove() + bpy.ops.collection.create(name='Cubagrouper') + bpy.ops.collection.objects_remove() for ob in bpy.context.selected_objects: oblst.append(ob) @@ -107,7 +107,7 @@ class makestructure(Operator): uvyes = 1 else: uvyes = 0 - bpy.ops.object.group_link(group='Cubagrouper') + bpy.ops.object.collection_link(group='Cubagrouper') dim = obj.dimensions rot = obj.rotation_euler if self.uf is True: @@ -155,7 +155,7 @@ class makestructure(Operator): ) bpy.ops.object.mode_set(mode='OBJECT') select = bpy.context.object # This is used to keep something selected for poll() - bpy.ops.object.group_link(group='Cubagrouper') + bpy.ops.object.collection_link(group='Cubagrouper') rsdchange += 3 bpy.ops.object.select_grouped(type='GROUP') bpy.ops.transform.rotate( @@ -180,7 +180,7 @@ class makestructure(Operator): if uvyes == 1: bpy.ops.object.join_uvs() - bpy.ops.group.objects_remove() + bpy.ops.collection.objects_remove() bpy.context.scene.objects.active = select if self.dc is True: diff --git a/add_advanced_objects_menu/rope_alpha.py b/add_advanced_objects_menu/rope_alpha.py index 2815c7c6..f0bd5dd7 100644 --- a/add_advanced_objects_menu/rope_alpha.py +++ b/add_advanced_objects_menu/rope_alpha.py @@ -622,7 +622,7 @@ class BallRope(Operator): bpy.ops.rigidbody.objects_add(type='ACTIVE') bpy.context.object.name = "CubeLink" if n != 0: - bpy.context.object.draw_type = 'WIRE' + bpy.context.object.display_type = 'WIRE' bpy.context.object.hide_render = True n += 1 bpy.context.object.scale.z = (longitud * 2) / (segmentos * 2) - separation diff --git a/add_advanced_objects_menu/scene_objects_bi.py b/add_advanced_objects_menu/scene_objects_bi.py index f189bb11..94bda4bc 100644 --- a/add_advanced_objects_menu/scene_objects_bi.py +++ b/add_advanced_objects_menu/scene_objects_bi.py @@ -43,11 +43,11 @@ class add_BI_scene(Operator): ) cam = bpy.context.active_object.data cam.lens = 35 - cam.draw_size = 0.1 + cam.display_size = 0.1 bpy.ops.view3d.viewnumpad(type='CAMERA') # add point lamp - bpy.ops.object.lamp_add( + bpy.ops.object.light_add( type="POINT", location=(4.07625, 1.00545, 5.90386), rotation=(0.650328, 0.055217, 1.866391) ) @@ -59,7 +59,7 @@ class add_BI_scene(Operator): lamp1.use_sphere = True # add point lamp2 - bpy.ops.object.lamp_add( + bpy.ops.object.light_add( type="POINT", location=(-0.57101, -4.24586, 5.53674), rotation=(1.571, 0, 0.785) ) diff --git a/add_advanced_objects_menu/scene_objects_cycles.py b/add_advanced_objects_menu/scene_objects_cycles.py index 85e85867..7158b02f 100644 --- a/add_advanced_objects_menu/scene_objects_cycles.py +++ b/add_advanced_objects_menu/scene_objects_cycles.py @@ -43,11 +43,11 @@ class add_cycles_scene(Operator): ) cam = bpy.context.active_object.data cam.lens = 35 - cam.draw_size = 0.1 + cam.display_size = 0.1 bpy.ops.view3d.viewnumpad(type='CAMERA') # add point lamp - bpy.ops.object.lamp_add( + bpy.ops.object.light_add( type="POINT", location=(4.07625, 1.00545, 5.90386), rotation=(0.650328, 0.055217, 1.866391) ) @@ -59,7 +59,7 @@ class add_cycles_scene(Operator): lamp1.use_sphere = True # add point lamp2 - bpy.ops.object.lamp_add( + bpy.ops.object.light_add( type="POINT", location=(-0.57101, -4.24586, 5.53674), rotation=(1.571, 0, 0.785) ) diff --git a/add_advanced_objects_menu/scene_texture_render.py b/add_advanced_objects_menu/scene_texture_render.py index 02d6490b..0f9ccc2b 100644 --- a/add_advanced_objects_menu/scene_texture_render.py +++ b/add_advanced_objects_menu/scene_texture_render.py @@ -43,7 +43,7 @@ class add_texture_scene(Operator): ) cam = bpy.context.active_object.data cam.lens = 35 - cam.draw_size = 0.1 + cam.display_size = 0.1 # add plane bpy.ops.mesh.primitive_plane_add(enter_editmode=True, location=(0, 0, 0)) diff --git a/add_advanced_objects_menu/trilighting.py b/add_advanced_objects_menu/trilighting.py index e0068e66..c163a894 100644 --- a/add_advanced_objects_menu/trilighting.py +++ b/add_advanced_objects_menu/trilighting.py @@ -173,7 +173,7 @@ class TriLighting(Operator): backx = obj_position.x + self.distance * singleback_vector.x backy = obj_position.y + self.distance * singleback_vector.y - backData = bpy.data.lamps.new(name="TriLamp-Back", type=self.secondarytype) + backData = bpy.data.lights.new(name="TriLamp-Back", type=self.secondarytype) backData.energy = backEnergy backLamp = bpy.data.objects.new(name="TriLamp-Back", object_data=backData) @@ -196,7 +196,7 @@ class TriLighting(Operator): rightx = obj_position.x + self.distance * singleright_vector.x righty = obj_position.y + self.distance * singleright_vector.y - rightData = bpy.data.lamps.new(name="TriLamp-Fill", type=self.secondarytype) + rightData = bpy.data.lights.new(name="TriLamp-Fill", type=self.secondarytype) rightData.energy = fillEnergy rightLamp = bpy.data.objects.new(name="TriLamp-Fill", object_data=rightData) scene.objects.link(rightLamp) @@ -215,7 +215,7 @@ class TriLighting(Operator): leftx = obj_position.x + self.distance * singleleft_vector.x lefty = obj_position.y + self.distance * singleleft_vector.y - leftData = bpy.data.lamps.new(name="TriLamp-Key", type=self.primarytype) + leftData = bpy.data.lights.new(name="TriLamp-Key", type=self.primarytype) leftData.energy = keyEnergy leftLamp = bpy.data.objects.new(name="TriLamp-Key", object_data=leftData) diff --git a/add_advanced_objects_panels/drop_to_ground.py b/add_advanced_objects_panels/drop_to_ground.py index e6facbcf..8c66fb46 100644 --- a/add_advanced_objects_panels/drop_to_ground.py +++ b/add_advanced_objects_panels/drop_to_ground.py @@ -87,13 +87,13 @@ def get_lowest_world_co(context, ob, mat_parent=None): if ob.type == 'MESH': return get_lowest_world_co_from_mesh(ob) - elif ob.type == 'EMPTY' and ob.dupli_type == 'GROUP': - if not ob.dupli_group: + elif ob.type == 'EMPTY' and ob.instance_type == 'COLLECTION': + if not ob.instance_collection: return None else: lowest_co = None - for ob_l in ob.dupli_group.objects: + for ob_l in ob.instance_collection.objects: if ob_l.type == 'MESH': lowest_ob_l = get_lowest_world_co_from_mesh(ob_l, ob.matrix_world) if not lowest_co: diff --git a/add_advanced_objects_panels/object_laplace_lightning.py b/add_advanced_objects_panels/object_laplace_lightning.py index fa50afd3..15e6730f 100644 --- a/add_advanced_objects_panels/object_laplace_lightning.py +++ b/add_advanced_objects_panels/object_laplace_lightning.py @@ -1087,7 +1087,7 @@ def setupObjects(): bpy.context.scene.objects.link(oOB) gOB = bpy.data.objects.new('ELground', None) - gOB.empty_draw_type = 'ARROWS' + gOB.empty_display_type = 'ARROWS' bpy.context.scene.objects.link(gOB) cME = makeMeshCube(1) diff --git a/add_advanced_objects_panels/unfold_transition.py b/add_advanced_objects_panels/unfold_transition.py index 42d8c3ba..f832c21d 100644 --- a/add_advanced_objects_panels/unfold_transition.py +++ b/add_advanced_objects_panels/unfold_transition.py @@ -129,8 +129,8 @@ class Set_Up_Fold(Operator): scn.objects.link(rig) scn.objects.active = rig bpy.ops.object.mode_set(mode="EDIT") - arm.draw_type = "WIRE" - rig.show_x_ray = True + arm.display_type = "WIRE" + rig.show_in_front = True mod = obj.modifiers.new("UnFold", "ARMATURE") mod.show_in_editmode = True mod.object = rig @@ -154,7 +154,7 @@ class Set_Up_Fold(Operator): b.select = False b.layers = vis b.parent = root - vg = obj.vertex_groups.new(b.name) + vg = obj.vertex_groups.new(name=b.name) vg.add(f.vertices, 1, "ADD") bpy.ops.object.mode_set() diff --git a/add_curve_extra_objects/__init__.py b/add_curve_extra_objects/__init__.py index 42ae2d44..2622401b 100644 --- a/add_curve_extra_objects/__init__.py +++ b/add_curve_extra_objects/__init__.py @@ -23,8 +23,8 @@ bl_info = { "name": "Extra Objects", "author": "Multiple Authors", - "version": (0, 1, 2), - "blender": (2, 76, 0), + "version": (0, 1, 3), + "blender": (2, 80, 0), "location": "View3D > Add > Curve > Extra Objects", "description": "Add extra curve object types", "warning": "", @@ -140,10 +140,10 @@ class CurveExtraObjectsAddonPreferences(AddonPreferences): "curve_type": ['POLY', 'NURBS'], "spiral_direction": ['COUNTER_CLOCKWISE', 'CLOCKWISE'] } - update_spiral_presets_msg = StringProperty( + update_spiral_presets_msg : StringProperty( default="Nothing to do" ) - update_spiral_presets = BoolProperty( + update_spiral_presets : BoolProperty( name="Update Old Presets", description="Update presets to reflect data changes", default=False, @@ -155,12 +155,12 @@ class CurveExtraObjectsAddonPreferences(AddonPreferences): fixdic=spiral_fixdic ) ) - show_menu_list = BoolProperty( + show_menu_list : BoolProperty( name="Menu List", description="Show/Hide the Add Menu items", default=False ) - show_panel_list = BoolProperty( + show_panel_list : BoolProperty( name="Panels List", description="Show/Hide the Panel items", default=False @@ -236,7 +236,10 @@ class INFO_MT_curve_knots_add(Menu): layout.operator("curve.torus_knot_plus", text="Torus Knot Plus") layout.operator("curve.celtic_links", text="Celtic Links") - layout.operator("mesh.add_braid", text="Braid Knot") + layout.operator("curve.add_braid", text="Braid Knot") + layout.operator("object.add_spirofit_spline", icon="FORCE_MAGNETIC") + layout.operator("object.add_bounce_spline", icon="FORCE_HARMONIC") + layout.operator("object.add_catenary_curve", icon="FORCE_CURVE") # Define "Extras" menus @@ -247,55 +250,72 @@ def menu_func(self, context): layout = self.layout - layout.operator_menu_enum("mesh.curveaceous_galore", "ProfileType", - icon='CURVE_DATA') - layout.operator_menu_enum("curve.spirals", "spiral_type", - icon='CURVE_DATA') + layout.operator_menu_enum("curve.curveaceous_galore", "ProfileType", icon='CURVE_DATA') + layout.operator_menu_enum("curve.spirals", "spiral_type", icon='CURVE_DATA') layout.separator() layout.menu(INFO_MT_curve_knots_add.bl_idname, text="Knots", icon='CURVE_DATA') layout.separator() - layout.operator("curve.curlycurve", text="Curly Curve", - icon='CURVE_DATA') - layout.menu("OBJECT_MT_bevel_taper_curve_menu", text="Bevel/Taper", - icon='CURVE_DATA') + layout.operator("curve.curlycurve", text="Curly Curve", icon='CURVE_DATA') + #layout.menu(VIEW3D_MT_bevel_taper_curve_menu, text="Bevel/Taper", icon='CURVE_DATA') def menu_surface(self, context): self.layout.separator() if context.mode == 'EDIT_SURFACE': - self.layout.operator("curve.smooth_x_times", - text="Special Smooth", icon="MOD_CURVE") + self.layout.operator("curve.smooth_x_times", text="Special Smooth", icon="MOD_CURVE") elif context.mode == 'OBJECT': - self.layout.operator("object.add_surface_wedge", text="Wedge", - icon="SURFACE_DATA") - self.layout.operator("object.add_surface_cone", text="Cone", - icon="SURFACE_DATA") - self.layout.operator("object.add_surface_star", text="Star", - icon="SURFACE_DATA") - self.layout.operator("object.add_surface_plane", text="Plane", - icon="SURFACE_DATA") + self.layout.operator("object.add_surface_wedge", text="Wedge", icon="SURFACE_DATA") + self.layout.operator("object.add_surface_cone", text="Cone", icon="SURFACE_DATA") + self.layout.operator("object.add_surface_star", text="Star", icon="SURFACE_DATA") + self.layout.operator("object.add_surface_plane", text="Plane", icon="SURFACE_DATA") +# Register +classes = [ + CurveExtraObjectsAddonPreferences, + INFO_MT_curve_knots_add +] def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + add_curve_simple.register() - bpy.utils.register_module(__name__) - + add_curve_spirals.register() + add_curve_aceous_galore.register() + add_curve_torus_knots.register() + add_curve_braid.register() + add_curve_celtic_links.register() + add_curve_curly.register() + add_curve_spirofit_bouncespline.register() + add_surface_plane_cone.register() + # Add "Extras" menu to the "Add Curve" menu - bpy.types.INFO_MT_curve_add.append(menu_func) + bpy.types.VIEW3D_MT_curve_add.append(menu_func) # Add "Extras" menu to the "Add Surface" menu - bpy.types.INFO_MT_surface_add.append(menu_surface) + bpy.types.VIEW3D_MT_surface_add.append(menu_surface) def unregister(): - add_curve_simple.unregister() # Remove "Extras" menu from the "Add Curve" menu. - bpy.types.INFO_MT_curve_add.remove(menu_func) + bpy.types.VIEW3D_MT_curve_add.remove(menu_func) # Remove "Extras" menu from the "Add Surface" menu. - bpy.types.INFO_MT_surface_add.remove(menu_surface) - - bpy.utils.unregister_module(__name__) - + bpy.types.VIEW3D_MT_surface_add.remove(menu_surface) + + add_surface_plane_cone.unregister() + add_curve_spirofit_bouncespline.unregister() + add_curve_curly.unregister() + add_curve_celtic_links.unregister() + add_curve_braid.unregister() + add_curve_torus_knots.unregister() + add_curve_aceous_galore.unregister() + add_curve_spirals.unregister() + add_curve_simple.unregister() + + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) if __name__ == "__main__": register() diff --git a/add_curve_extra_objects/add_curve_aceous_galore.py b/add_curve_extra_objects/add_curve_aceous_galore.py index d823cbb5..246e0551 100644 --- a/add_curve_extra_objects/add_curve_aceous_galore.py +++ b/add_curve_extra_objects/add_curve_aceous_galore.py @@ -20,8 +20,8 @@ bl_info = { "name": "Curveaceous Galore!", "author": "Jimmy Hazevoet, testscreenings", - "version": (0, 2, 1), - "blender": (2, 59), + "version": (0, 2, 2), + "blender": (2, 80), "location": "View3D > Add > Curve", "description": "Adds many different types of Curves", "warning": "", @@ -38,7 +38,7 @@ from bpy.props import ( FloatProperty, IntProperty, ) -from mathutils import Matrix +from mathutils import Matrix, Vector from bpy.types import Operator from math import ( sin, cos, pi @@ -78,7 +78,7 @@ def randnum(low=0.0, high=1.0, seed=0): # ------------------------------------------------------------ # Make some noise: -def vTurbNoise(x, y, z, iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0): +def vTurbNoise(x, y, z, iScale=0.25, Size=1.0, Depth=6, Hard=False, Basis=0, Seed=0): """ vTurbNoise((x,y,z), iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0 ) @@ -93,7 +93,7 @@ def vTurbNoise(x, y, z, iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0) (type=float) Depth - number of noise values added. (type=int) - Hard - noise hardness: 0 - soft noise; 1 - hard noise + Hard - noise hardness: True - soft noise; False - hard noise (type=int) basis - type of noise used for turbulence (type=int) @@ -106,8 +106,9 @@ def vTurbNoise(x, y, z, iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0) rand = randnum(-100, 100, Seed) if Basis is 9: Basis = 14 - vTurb = Noise.turbulence_vector((x / Size + rand, y / Size + rand, z / Size + rand), - Depth, Hard, Basis) + vec = Vector((x / Size + rand, y / Size + rand, z / Size + rand)) + vTurb = Noise.turbulence_vector(vec, Depth, Hard) + #mathutils.noise.turbulence_vector(position, octaves, hard, noise_basis='PERLIN_ORIGINAL', amplitude_scale=0.5, frequency_scale=2.0) tx = vTurb[0] * iScale ty = vTurb[1] * iScale tz = vTurb[2] * iScale @@ -533,7 +534,7 @@ def SplatCurve(sides=24, scale=1.0, seed=0, basis=0, radius=1.0): i = 0 while i < sides: t = i * step - turb = vTurbNoise(t, t, t, 1.0, scale, 6, 0, basis, seed) + turb = vTurbNoise(t, t, t, 1.0, scale, 6, False, basis, seed) turb = turb[2] * 0.5 + 0.5 x = sin(t * pi) * radius * turb y = cos(t * pi) * radius * turb @@ -684,7 +685,7 @@ def NoiseCurve(type=0, number=100, length=2.0, size=0.5, # noise circle while i < number: t = i * step - v = vTurbNoise(t, t, t, 1.0, size, octaves, 0, basis, seed) + v = vTurbNoise(t, t, t, 1.0, size, octaves, False, basis, seed) x = sin(t * pi) + (v[0] * scale[0]) y = cos(t * pi) + (v[1] * scale[1]) z = v[2] * scale[2] @@ -694,7 +695,7 @@ def NoiseCurve(type=0, number=100, length=2.0, size=0.5, # noise knot / ball while i < number: t = i * step - v = vTurbNoise(t, t, t, 1.0, 1.0, octaves, 0, basis, seed) + v = vTurbNoise(t, t, t, 1.0, 1.0, octaves, False, basis, seed) x = v[0] * scale[0] * size y = v[1] * scale[1] * size z = v[2] * scale[2] * size @@ -704,7 +705,7 @@ def NoiseCurve(type=0, number=100, length=2.0, size=0.5, # noise linear while i < number: t = i * step - v = vTurbNoise(t, t, t, 1.0, size, octaves, 0, basis, seed) + v = vTurbNoise(t, t, t, 1.0, size, octaves, False, basis, seed) x = t + v[0] * scale[0] y = v[1] * scale[1] z = v[2] * scale[2] @@ -727,24 +728,24 @@ def align_matrix(context): else: rot = Matrix() - align_matrix = loc * rot + align_matrix = loc @ rot return align_matrix # ------------------------------------------------------------ # Curve creation functions, sets bezierhandles to auto -def setBezierHandles(obj, mode='AUTOMATIC'): +def setBezierHandles(obj, mode='AUTO'): scene = bpy.context.scene if obj.type != 'CURVE': return - scene.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT', toggle=True) - bpy.ops.curve.select_all(action='SELECT') - bpy.ops.curve.handle_type_set(type=mode) - bpy.ops.object.mode_set(mode='OBJECT', toggle=True) - + #scene.objects.active = obj + #bpy.ops.object.mode_set(mode='EDIT', toggle=True) + #bpy.ops.curve.select_all(action='SELECT') + #obj.select_set(action='SELECT') + #bpy.ops.curve.handle_type_set(type=mode) + #bpy.ops.object.mode_set(mode='OBJECT', toggle=True) # get array of vertcoordinates according to splinetype def vertsToPoints(Verts, splineType): @@ -772,7 +773,7 @@ def vertsToPoints(Verts, splineType): # create new CurveObject from vertarray and splineType def createCurve(context, vertArray, self, align_matrix): - scene = context.scene + scene = bpy.context.scene # output splineType 'POLY' 'NURBS' 'BEZIER' splineType = self.outputType @@ -788,6 +789,9 @@ def createCurve(context, vertArray, self, align_matrix): if splineType == 'BEZIER': newSpline.bezier_points.add(int(len(vertArray) * 0.33)) newSpline.bezier_points.foreach_set('co', vertArray) + for point in newSpline.bezier_points: + point.handle_right_type = self.handleType + point.handle_left_type = self.handleType else: newSpline.points.add(int(len(vertArray) * 0.25 - 1)) newSpline.points.foreach_set('co', vertArray) @@ -801,14 +805,15 @@ def createCurve(context, vertArray, self, align_matrix): # create object with newCurve new_obj = bpy.data.objects.new(name, newCurve) - scene.objects.link(new_obj) - new_obj.select = True - scene.objects.active = new_obj + scene.collection.objects.link(new_obj) + new_obj.select_set(True) + #scene.objects.active = new_obj new_obj.matrix_world = align_matrix # set bezierhandles - if splineType == 'BEZIER': - setBezierHandles(new_obj, self.handleType) + #if splineType == 'BEZIER': + #bpy.ops.curve.handle_type_set(type='AUTO') + #setBezierHandles(new_obj, self.handleType) return @@ -817,7 +822,7 @@ def createCurve(context, vertArray, self, align_matrix): # Main Function def main(context, self, align_matrix): # deselect all objects - bpy.ops.object.select_all(action='DESELECT') + #bpy.ops.object.select_all(action='DESELECT') # options proType = self.ProfileType @@ -930,7 +935,7 @@ def main(context, self, align_matrix): class Curveaceous_galore(Operator): - bl_idname = "mesh.curveaceous_galore" + bl_idname = "curve.curveaceous_galore" bl_label = "Curve Profiles" bl_description = "Construct many types of curves" bl_options = {'REGISTER', 'UNDO', 'PRESET'} @@ -939,7 +944,7 @@ class Curveaceous_galore(Operator): align_matrix = None # general properties - ProfileType = EnumProperty( + ProfileType : EnumProperty( name="Type", description="Form of Curve to create", items=[ @@ -956,7 +961,7 @@ class Curveaceous_galore(Operator): ('Splat', "Splat", "Splat"), ('Star', "Star", "Star")] ) - outputType = EnumProperty( + outputType : EnumProperty( name="Output splines", description="Type of splines to output", items=[ @@ -965,7 +970,7 @@ class Curveaceous_galore(Operator): ('BEZIER', "Bezier", "Bezier Spline type")] ) # Curve Options - shape = EnumProperty( + shape : EnumProperty( name="2D / 3D", description="2D or 3D Curve", items=[ @@ -973,135 +978,135 @@ class Curveaceous_galore(Operator): ('3D', "3D", "3D") ] ) - use_cyclic_u = BoolProperty( + use_cyclic_u : BoolProperty( name="Cyclic", default=True, description="make curve closed" ) - endp_u = BoolProperty( + endp_u : BoolProperty( name="Use endpoint u", default=True, description="stretch to endpoints" ) - order_u = IntProperty( + order_u : IntProperty( name="Order u", default=4, min=2, soft_min=2, max=6, soft_max=6, description="Order of nurbs spline" ) - handleType = EnumProperty( + handleType : EnumProperty( name="Handle type", - default='AUTOMATIC', + default='AUTO', description="Bezier handles type", items=[ ('VECTOR', "Vector", "Vector type Bezier handles"), - ('AUTOMATIC', "Auto", "Automatic type Bezier handles")] + ('AUTO', "Auto", "Automatic type Bezier handles")] ) # ProfileCurve properties - ProfileCurveType = IntProperty( + ProfileCurveType : IntProperty( name="Type", min=1, max=5, default=1, description="Type of Curve's Profile" ) - ProfileCurvevar1 = FloatProperty( + ProfileCurvevar1 : FloatProperty( name="Variable 1", default=0.25, description="Variable 1 of Curve's Profile" ) - ProfileCurvevar2 = FloatProperty( + ProfileCurvevar2 : FloatProperty( name="Variable 2", default=0.25, description="Variable 2 of Curve's Profile" ) # Arrow, Rectangle, MiscCurve properties - MiscCurveType = IntProperty( + MiscCurveType : IntProperty( name="Type", min=0, max=3, default=0, description="Type of Curve" ) - MiscCurvevar1 = FloatProperty( + MiscCurvevar1 : FloatProperty( name="Variable 1", default=1.0, description="Variable 1 of Curve" ) - MiscCurvevar2 = FloatProperty( + MiscCurvevar2 : FloatProperty( name="Variable 2", default=0.5, description="Variable 2 of Curve" ) - MiscCurvevar3 = FloatProperty( + MiscCurvevar3 : FloatProperty( name="Variable 3", default=0.1, min=0, description="Variable 3 of Curve" ) # Common properties - innerRadius = FloatProperty( + innerRadius : FloatProperty( name="Inner radius", default=0.5, min=0, description="Inner radius" ) - middleRadius = FloatProperty( + middleRadius : FloatProperty( name="Middle radius", default=0.95, min=0, description="Middle radius" ) - outerRadius = FloatProperty( + outerRadius : FloatProperty( name="Outer radius", default=1.0, min=0, description="Outer radius" ) # Flower properties - petals = IntProperty( + petals : IntProperty( name="Petals", default=8, min=2, description="Number of petals" ) - petalWidth = FloatProperty( + petalWidth : FloatProperty( name="Petal width", default=2.0, min=0.01, description="Petal width" ) # Star properties - starPoints = IntProperty( + starPoints : IntProperty( name="Star points", default=8, min=2, description="Number of star points" ) - starTwist = FloatProperty( + starTwist : FloatProperty( name="Twist", default=0.0, description="Twist" ) # Arc properties - arcSides = IntProperty( + arcSides : IntProperty( name="Arc sides", default=6, min=1, description="Sides of arc" ) - startAngle = FloatProperty( + startAngle : FloatProperty( name="Start angle", default=0.0, description="Start angle" ) - endAngle = FloatProperty( + endAngle : FloatProperty( name="End angle", default=90.0, description="End angle" ) - arcType = IntProperty( + arcType : IntProperty( name="Arc type", default=3, min=1, @@ -1109,13 +1114,13 @@ class Curveaceous_galore(Operator): description="Sides of arc" ) # Cogwheel properties - teeth = IntProperty( + teeth : IntProperty( name="Teeth", default=8, min=2, description="number of teeth" ) - bevel = FloatProperty( + bevel : FloatProperty( name="Bevel", default=0.5, min=0, @@ -1123,32 +1128,32 @@ class Curveaceous_galore(Operator): description="Bevel" ) # Nsided property - Nsides = IntProperty( + Nsides : IntProperty( name="Sides", default=8, min=3, description="Number of sides" ) # Splat properties - splatSides = IntProperty( + splatSides : IntProperty( name="Splat sides", default=24, min=3, description="Splat sides" ) - splatScale = FloatProperty( + splatScale : FloatProperty( name="Splat scale", default=1.0, min=0.0001, description="Splat scale" ) - seed = IntProperty( + seed : IntProperty( name="Seed", default=0, min=0, description="Seed" ) - basis = IntProperty( + basis : IntProperty( name="Basis", default=0, min=0, @@ -1156,134 +1161,134 @@ class Curveaceous_galore(Operator): description="Basis" ) # Helix properties - helixPoints = IntProperty( + helixPoints : IntProperty( name="Resolution", default=100, min=3, description="Resolution" ) - helixHeight = FloatProperty( + helixHeight : FloatProperty( name="Height", default=2.0, min=0, description="Helix height" ) - helixStart = FloatProperty( + helixStart : FloatProperty( name="Start angle", default=0.0, description="Helix start angle" ) - helixEnd = FloatProperty( + helixEnd : FloatProperty( name="Endangle", default=360.0, description="Helix end angle" ) - helixWidth = FloatProperty( + helixWidth : FloatProperty( name="Width", default=1.0, description="Helix width" ) - helix_a = FloatProperty( + helix_a : FloatProperty( name="Variable 1", default=0.0, description="Helix Variable 1" ) - helix_b = FloatProperty( + helix_b : FloatProperty( name="Variable 2", default=0.0, description="Helix Variable 2" ) # Cycloid properties - cycloPoints = IntProperty( + cycloPoints : IntProperty( name="Resolution", default=100, min=3, soft_min=3, description="Resolution" ) - cycloType = IntProperty( + cycloType : IntProperty( name="Type", default=1, min=0, max=2, description="Type: Cycloid , Hypocycloid / Hypotrochoid , Epicycloid / Epitrochoid" ) - cyclo_a = FloatProperty( + cyclo_a : FloatProperty( name="R", default=1.0, min=0.01, description="Cycloid: R radius a" ) - cyclo_b = FloatProperty( + cyclo_b : FloatProperty( name="r", default=0.25, min=0.01, description="Cycloid: r radius b" ) - cyclo_d = FloatProperty( + cyclo_d : FloatProperty( name="d", default=0.25, description="Cycloid: d distance" ) # Noise properties - noiseType = IntProperty( + noiseType : IntProperty( name="Type", default=0, min=0, max=2, description="Noise curve type: Linear, Circular or Knot" ) - noisePoints = IntProperty( + noisePoints : IntProperty( name="Resolution", default=100, min=3, description="Resolution" ) - noiseLength = FloatProperty( + noiseLength : FloatProperty( name="Length", default=2.0, min=0.01, description="Curve Length" ) - noiseSize = FloatProperty( + noiseSize : FloatProperty( name="Noise size", default=1.0, min=0.0001, description="Noise size" ) - noiseScaleX = FloatProperty( + noiseScaleX : FloatProperty( name="Noise x", default=1.0, min=0.0001, description="Noise x" ) - noiseScaleY = FloatProperty( + noiseScaleY : FloatProperty( name="Noise y", default=1.0, min=0.0001, description="Noise y" ) - noiseScaleZ = FloatProperty( + noiseScaleZ : FloatProperty( name="Noise z", default=1.0, min=0.0001, description="Noise z" ) - noiseOctaves = IntProperty( + noiseOctaves : IntProperty( name="Octaves", default=2, min=1, max=16, description="Basis" ) - noiseBasis = IntProperty( + noiseBasis : IntProperty( name="Basis", default=0, min=0, max=9, description="Basis" ) - noiseSeed = IntProperty( + noiseSeed : IntProperty( name="Seed", default=1, min=0, @@ -1460,3 +1465,21 @@ class Curveaceous_galore(Operator): self.execute(context) return {'FINISHED'} + +# Register +classes = [ + Curveaceous_galore +] + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + +def unregister(): + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + +if __name__ == "__main__": + register()
\ No newline at end of file diff --git a/add_curve_extra_objects/add_curve_braid.py b/add_curve_extra_objects/add_curve_braid.py index 90b41e5f..fcaaf15e 100644 --- a/add_curve_extra_objects/add_curve_braid.py +++ b/add_curve_extra_objects/add_curve_braid.py @@ -4,8 +4,8 @@ bl_info = { "name": "New Braid", "author": "Jared Forsyth <github.com/jaredly>", - "version": (1, 0, 2), - "blender": (2, 6, 0), + "version": (1, 0, 3), + "blender": (2, 80, 0), "location": "View3D > Add > Mesh > New Braid", "description": "Adds a new Braid", "warning": "", @@ -134,13 +134,13 @@ def star_pts(r=1, ir=None, points=5, center=(0, 0)): def defaultCircle(w=.6): circle = nurbs_circle('braid_circle', w, w) - circle.hide = True + circle.hide_select = True return circle def defaultStar(): star = poly_lines('star', 'staz', [tuple(star_pts(points=5, r=.5, ir=.05))], type='NURBS') - star.hide = True + star.hide_select = True return star @@ -151,46 +151,46 @@ def awesome_braid(strands=3, sides=5, bevel='braid_circle', pointy=False, **kwds class Braid(Operator): - bl_idname = "mesh.add_braid" + bl_idname = "curve.add_braid" bl_label = "New Braid" bl_description = ("Construct a new Braid\n" "Creates two objects - the hidden one is used as the Bevel control") bl_options = {'REGISTER', 'UNDO', 'PRESET'} - strands = IntProperty( + strands : IntProperty( name="Strands", description="Number of Strands", min=2, max=100, default=3 ) - sides = IntProperty( + sides : IntProperty( name="Sides", description="Number of Knot sides", min=2, max=100, default=5 ) - radius = FloatProperty( + radius : FloatProperty( name="Radius", description="Increase / decrease the diameter in X,Y axis", default=1 ) - thickness = FloatProperty( + thickness : FloatProperty( name="Thickness", description="The ratio between inner and outside diameters", default=.3 ) - strandsize = FloatProperty( + strandsize : FloatProperty( name="Bevel Depth", description="Individual strand diameter (similar to Curve's Bevel depth)", default=.3, min=.01, max=10 ) - width = FloatProperty( + width : FloatProperty( name="Width", description="Stretch the Braids along the Z axis", default=.2 ) - resolution = IntProperty( + resolution : IntProperty( name="Bevel Resolution", description="Resolution of the Created curve\n" "Increasing this value, will produce heavy geometry", @@ -198,7 +198,7 @@ class Braid(Operator): max=100, soft_max=24, default=2 ) - pointy = BoolProperty( + pointy : BoolProperty( name="Pointy", description="Switch between round and sharp corners", default=False @@ -209,7 +209,7 @@ class Braid(Operator): box = layout.box() col = box.column(align=True) - col.label("Settings:") + col.label(text="Settings:") col.prop(self, "strands") col.prop(self, "sides") @@ -223,13 +223,13 @@ class Braid(Operator): box = layout.box() col = box.column(align=True) - col.label("Geometry Options:") + col.label(text="Geometry Options:") col.prop(self, "strandsize") col.prop(self, "resolution") def execute(self, context): circle = defaultCircle(self.strandsize) - context.scene.objects.link(circle) + context.scene.collection.objects.link(circle) braid = awesome_braid( self.strands, self.sides, bevel=circle.name, @@ -239,12 +239,12 @@ class Braid(Operator): mz=self.width, resolution=self.resolution ) - base = context.scene.objects.link(braid) + base = context.scene.collection.objects.link(braid) for ob in context.scene.objects: - ob.select = False - base.select = True - context.scene.objects.active = braid + ob.select_set(False) + #base.select_set(True) + #context.scene.objects.active = braid return {'FINISHED'} diff --git a/add_curve_extra_objects/add_curve_celtic_links.py b/add_curve_extra_objects/add_curve_celtic_links.py index f4313c85..c5b581fa 100644 --- a/add_curve_extra_objects/add_curve_celtic_links.py +++ b/add_curve_extra_objects/add_curve_celtic_links.py @@ -26,8 +26,8 @@ bl_info = { "name": "Celtic Knot", "description": "", "author": "Adam Newgas", - "version": (0, 1, 2), - "blender": (2, 74, 0), + "version": (0, 1, 3), + "blender": (2, 80, 0), "location": "View3D > Add > Curve", "warning": "", "wiki_url": "https://github.com/BorisTheBrave/celtic-knot/wiki", @@ -53,13 +53,13 @@ class CelticKnotOperator(Operator): bl_description = "Select a low poly Mesh Object to cover with Knitted Links" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - weave_up = FloatProperty( + weave_up : FloatProperty( name="Weave Up", description="Distance to shift curve upwards over knots", subtype="DISTANCE", unit="LENGTH" ) - weave_down = FloatProperty( + weave_down : FloatProperty( name="Weave Down", description="Distance to shift curve downward under knots", subtype="DISTANCE", @@ -69,7 +69,7 @@ class CelticKnotOperator(Operator): ('ALIGNED', "Aligned", "Points at a fixed crossing angle"), ('AUTO', "Auto", "Automatic control points") ] - handle_type = EnumProperty( + handle_type : EnumProperty( items=handle_types, name="Handle Type", description="Controls what type the bezier control points use", @@ -78,7 +78,7 @@ class CelticKnotOperator(Operator): handle_type_map = {"AUTO": "AUTOMATIC", "ALIGNED": "ALIGNED"} - crossing_angle = FloatProperty( + crossing_angle : FloatProperty( name="Crossing Angle", description="Aligned only: the angle between curves in a knot", default=pi / 4, @@ -86,14 +86,14 @@ class CelticKnotOperator(Operator): subtype="ANGLE", unit="ROTATION" ) - crossing_strength = FloatProperty( + crossing_strength : FloatProperty( name="Crossing Strength", description="Aligned only: strength of bezier control points", soft_min=0, subtype="DISTANCE", unit="LENGTH" ) - geo_bDepth = FloatProperty( + geo_bDepth : FloatProperty( name="Bevel Depth", default=0.04, min=0, soft_min=0, @@ -211,7 +211,7 @@ class CelticKnotOperator(Operator): forward = loop.vert.index == v if not first: - current_spline.bezier_points.add() + current_spline.bezier_points.add(1) first = False midpoint = midpoints[loop.edge.index] normal = loop.calc_normal() + prev_loop.calc_normal() @@ -267,7 +267,7 @@ class CelticKnotOperator(Operator): curve_obj.data.bevel_depth = self.geo_bDepth except: pass - context.scene.objects.active = orig_obj + #context.scene.objects.active = orig_obj return {'FINISHED'} diff --git a/add_curve_extra_objects/add_curve_curly.py b/add_curve_extra_objects/add_curve_curly.py index 779689a9..8cd7d8b9 100644 --- a/add_curve_extra_objects/add_curve_curly.py +++ b/add_curve_extra_objects/add_curve_curly.py @@ -5,8 +5,8 @@ bl_info = { "name": "Curly Curves", "author": "Cmomoney", - "version": (1, 1, 8), - "blender": (2, 69, 0), + "version": (1, 1, 9), + "blender": (2, 80, 0), "location": "View3D > Add > Curve > Curly Curve", "description": "Adds a new Curly Curve", "warning": "", @@ -405,20 +405,20 @@ class add_curlycurve(Operator, AddObjectHelper): bl_idname = "curve.curlycurve" bl_label = "Add Curly Curve" bl_description = "Create a Curly Curve" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'REGISTER', 'UNDO', 'PRESET'} - types = IntProperty( + types : IntProperty( name="Type", description="Type of curly curve", default=1, min=1, max=10 ) - scale_x = FloatProperty( + scale_x : FloatProperty( name="Scale X", description="Scale on X axis", default=1.0 ) - scale_y = FloatProperty( + scale_y : FloatProperty( name="Scale Y", description="Scale on Y axis", default=1.0 @@ -434,11 +434,11 @@ class add_curlycurve(Operator, AddObjectHelper): col.prop(self, "rotation") col = layout.column() - col.label("Curve:") + col.label(text = "Curve:") col.prop(self, "types") col = layout.column(align=True) - col.label("Resize:") + col.label(text = "Resize:") col.prop(self, "scale_x") col.prop(self, "scale_y") @@ -479,12 +479,12 @@ def add_curlycurve_button(self, context): def register(): bpy.utils.register_class(add_curlycurve) - bpy.types.INFO_MT_curve_add.append(add_curlycurve_button) + #bpy.types.INFO_MT_curve_add.append(add_curlycurve_button) def unregister(): bpy.utils.unregister_class(add_curlycurve) - bpy.types.INFO_MT_curve_add.remove(add_curlycurve_button) + #bpy.types.INFO_MT_curve_add.remove(add_curlycurve_button) if __name__ == "__main__": diff --git a/add_curve_extra_objects/add_curve_simple.py b/add_curve_extra_objects/add_curve_simple.py index 15dd3e1a..0d4b9224 100644 --- a/add_curve_extra_objects/add_curve_simple.py +++ b/add_curve_extra_objects/add_curve_simple.py @@ -19,8 +19,8 @@ bl_info = { "name": "Simple Curve", "author": "Spivak Vladimir (http://cwolf3d.korostyshev.net)", - "version": (1, 5, 3), - "blender": (2, 6, 9), + "version": (1, 5, 4), + "blender": (2, 80, 0), "location": "View3D > Add > Curve", "description": "Adds Simple Curve", "warning": "", @@ -388,7 +388,7 @@ def align_matrix(context, location): rot = context.space_data.region_3d.view_matrix.to_3x3().inverted().to_4x4() else: rot = Matrix() - align_matrix = loc * rot + align_matrix = loc @ rot return align_matrix @@ -397,19 +397,30 @@ def align_matrix(context, location): # Main Function def main(context, self, align_matrix): - # deselect all objects - bpy.ops.object.select_all(action='DESELECT') - # create object - name = self.Simple_Type # Type as name - - # create curve scene = bpy.context.scene - newCurve = bpy.data.curves.new(name, type='CURVE') # curvedatablock - newSpline = newCurve.splines.new('BEZIER') # spline + + if bpy.context.mode == 'EDIT_CURVE': + newCurve = context.active_object.data + newSpline = newCurve.splines.new('BEZIER') # spline + else: + name = self.Simple_Type # Type as name + # create curve + + newCurve = bpy.data.curves.new(name, type='CURVE') # curvedatablock + newSpline = newCurve.splines.new('BEZIER') # spline + + # set curveOptions + newCurve.dimensions = self.shape + + # create object with newCurve + SimpleCurve = bpy.data.objects.new(name, newCurve) # object + scene.collection.objects.link(SimpleCurve) # place in active scene + SimpleCurve.select_set(True) + SimpleCurve.matrix_world = align_matrix # apply matrix + SimpleCurve.rotation_euler = self.Simple_rotation_euler - # set curveOptions - newCurve.dimensions = self.shape + newSpline.use_endpoint_u = True sides = abs(int((self.Simple_endangle - self.Simple_startangle) / 90)) @@ -525,14 +536,6 @@ def main(context, self, align_matrix): newSpline.bezier_points.add(int(len(vertArray) * 0.333333333)) newSpline.bezier_points.foreach_set('co', vertArray) - # create object with newCurve - SimpleCurve = bpy.data.objects.new(name, newCurve) # object - scene.objects.link(SimpleCurve) # place in active scene - SimpleCurve.select = True # set as selected - scene.objects.active = SimpleCurve # set as active - SimpleCurve.matrix_world = align_matrix # apply matrix - SimpleCurve.rotation_euler = self.Simple_rotation_euler - all_points = [p for p in newSpline.bezier_points] d = 2 * 0.27606262 n = 0 @@ -761,45 +764,73 @@ def main(context, self, align_matrix): all_points[int(n / 2) - 1].handle_right_type = 'VECTOR' all_points[int(n / 2)].handle_left_type = 'VECTOR' - SimpleCurve.s_curve.Simple = True - SimpleCurve.s_curve.Simple_Change = False - SimpleCurve.s_curve.Simple_Type = self.Simple_Type - SimpleCurve.s_curve.Simple_startlocation = self.Simple_startlocation - SimpleCurve.s_curve.Simple_endlocation = self.Simple_endlocation - SimpleCurve.s_curve.Simple_a = self.Simple_a - SimpleCurve.s_curve.Simple_b = self.Simple_b - SimpleCurve.s_curve.Simple_h = self.Simple_h - SimpleCurve.s_curve.Simple_angle = self.Simple_angle - SimpleCurve.s_curve.Simple_startangle = self.Simple_startangle - SimpleCurve.s_curve.Simple_endangle = self.Simple_endangle - SimpleCurve.s_curve.Simple_rotation_euler = self.Simple_rotation_euler - SimpleCurve.s_curve.Simple_sides = self.Simple_sides - SimpleCurve.s_curve.Simple_radius = self.Simple_radius - SimpleCurve.s_curve.Simple_center = self.Simple_center - SimpleCurve.s_curve.Simple_width = self.Simple_width - SimpleCurve.s_curve.Simple_length = self.Simple_length - SimpleCurve.s_curve.Simple_rounded = self.Simple_rounded - - bpy.ops.object.mode_set(mode='EDIT', toggle=True) - bpy.ops.curve.select_all(action='SELECT') - bpy.ops.object.mode_set(mode='OBJECT', toggle=True) - return +# ### MENU append ### +def Simple_curve_edit_menu(self, context): + bl_label = 'Simple edit' + + self.layout.operator("curve.bezier_points_fillet", text="Fillet") + self.layout.operator("curve.bezier_spline_divide", text="Divide") + self.layout.separator() -# ------------------------------------------------------------ -# Delete simple curve +def menu(self, context): + oper1 = self.layout.operator(Simple.bl_idname, text="Angle", icon="MOD_CURVE") + oper1.Simple_Change = False + oper1.Simple_Type = "Angle" -def SimpleDelete(name): - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') + oper2 = self.layout.operator(Simple.bl_idname, text="Arc", icon="MOD_CURVE") + oper2.Simple_Change = False + oper2.Simple_Type = "Arc" - bpy.context.scene.objects.active = bpy.data.objects[name] - bpy.ops.object.delete() + oper3 = self.layout.operator(Simple.bl_idname, text="Circle", icon="MOD_CURVE") + oper3.Simple_Change = False + oper3.Simple_Type = "Circle" - return + oper4 = self.layout.operator(Simple.bl_idname, text="Distance", icon="MOD_CURVE") + oper4.Simple_Change = False + oper4.Simple_Type = "Distance" + + oper5 = self.layout.operator(Simple.bl_idname, text="Ellipse", icon="MOD_CURVE") + oper5.Simple_Change = False + oper5.Simple_Type = "Ellipse" + oper6 = self.layout.operator(Simple.bl_idname, text="Line", icon="MOD_CURVE") + oper6.Simple_Change = False + oper6.Simple_Type = "Line" + oper7 = self.layout.operator(Simple.bl_idname, text="Point", icon="MOD_CURVE") + oper7.Simple_Change = False + oper7.Simple_Type = "Point" + + oper8 = self.layout.operator(Simple.bl_idname, text="Polygon", icon="MOD_CURVE") + oper8.Simple_Change = False + oper8.Simple_Type = "Polygon" + + oper9 = self.layout.operator(Simple.bl_idname, text="Polygon ab", icon="MOD_CURVE") + oper9.Simple_Change = False + oper9.Simple_Type = "Polygon_ab" + + oper10 = self.layout.operator(Simple.bl_idname, text="Rectangle", icon="MOD_CURVE") + oper10.Simple_Change = False + oper10.Simple_Type = "Rectangle" + + oper11 = self.layout.operator(Simple.bl_idname, text="Rhomb", icon="MOD_CURVE") + oper11.Simple_Change = False + oper11.Simple_Type = "Rhomb" + + oper12 = self.layout.operator(Simple.bl_idname, text="Sector", icon="MOD_CURVE") + oper12.Simple_Change = False + oper12.Simple_Type = "Sector" + + oper13 = self.layout.operator(Simple.bl_idname, text="Segment", icon="MOD_CURVE") + oper13.Simple_Change = False + oper13.Simple_Type = "Segment" + + oper14 = self.layout.operator(Simple.bl_idname, text="Trapezoid", icon="MOD_CURVE") + oper14.Simple_Change = False + oper14.Simple_Type = "Trapezoid" + # ------------------------------------------------------------ # Simple operator @@ -810,20 +841,20 @@ class Simple(Operator): bl_options = {'REGISTER', 'UNDO'} # align_matrix for the invoke - align_matrix = Matrix() + align_matrix : Matrix() # change properties - Simple = BoolProperty( + Simple : BoolProperty( name="Simple", default=True, description="Simple Curve" ) - Simple_Change = BoolProperty( + Simple_Change : BoolProperty( name="Change", default=False, description="Change Simple Curve" ) - Simple_Delete = StringProperty( + Simple_Delete : StringProperty( name="Delete", description="Delete Simple Curve" ) @@ -843,84 +874,84 @@ class Simple(Operator): ('Polygon_ab', "Polygon ab", "Construct a Polygon ab"), ('Trapezoid', "Trapezoid", "Construct a Trapezoid") ] - Simple_Type = EnumProperty( + Simple_Type : EnumProperty( name="Type", description="Form of Curve to create", items=Types ) # Line properties - Simple_startlocation = FloatVectorProperty( + Simple_startlocation : FloatVectorProperty( name="", description="Start location", default=(0.0, 0.0, 0.0), subtype='TRANSLATION' ) - Simple_endlocation = FloatVectorProperty( + Simple_endlocation : FloatVectorProperty( name="", description="End location", default=(2.0, 2.0, 2.0), subtype='TRANSLATION' ) - Simple_rotation_euler = FloatVectorProperty( + Simple_rotation_euler : FloatVectorProperty( name="", description="Rotation", default=(0.0, 0.0, 0.0), subtype='EULER' ) # Trapezoid properties - Simple_a = FloatProperty( + Simple_a : FloatProperty( name="Side a", default=2.0, min=0.0, soft_min=0.0, unit='LENGTH', description="a side Value" ) - Simple_b = FloatProperty( + Simple_b : FloatProperty( name="Side b", default=1.0, min=0.0, soft_min=0.0, unit='LENGTH', description="b side Value" ) - Simple_h = FloatProperty( + Simple_h : FloatProperty( name="Height", default=1.0, unit='LENGTH', description="Height of the Trapezoid - distance between a and b" ) - Simple_angle = FloatProperty( + Simple_angle : FloatProperty( name="Angle", default=45.0, description="Angle" ) - Simple_startangle = FloatProperty( + Simple_startangle : FloatProperty( name="Start angle", default=0.0, min=-360.0, soft_min=-360.0, max=360.0, soft_max=360.0, description="Start angle" ) - Simple_endangle = FloatProperty( + Simple_endangle : FloatProperty( name="End angle", default=45.0, min=-360.0, soft_min=-360.0, max=360.0, soft_max=360.0, description="End angle" ) - Simple_sides = IntProperty( + Simple_sides : IntProperty( name="Sides", default=3, min=0, soft_min=0, description="Sides" ) - Simple_radius = FloatProperty( + Simple_radius : FloatProperty( name="Radius", default=1.0, min=0.0, soft_min=0.0, unit='LENGTH', description="Radius" ) - Simple_center = BoolProperty( + Simple_center : BoolProperty( name="Length center", default=True, description="Length center" @@ -928,27 +959,27 @@ class Simple(Operator): Angle_types = [('Degrees', "Degrees", "Use Degrees"), ('Radians', "Radians", "Use Radians")] - Simple_degrees_or_radians = EnumProperty( + Simple_degrees_or_radians : EnumProperty( name="Degrees or radians", description="Degrees or radians", items=Angle_types ) # Rectangle properties - Simple_width = FloatProperty( + Simple_width : FloatProperty( name="Width", default=2.0, min=0.0, soft_min=0, unit='LENGTH', description="Width" ) - Simple_length = FloatProperty( + Simple_length : FloatProperty( name="Length", default=2.0, min=0.0, soft_min=0.0, unit='LENGTH', description="Length" ) - Simple_rounded = FloatProperty( + Simple_rounded : FloatProperty( name="Rounded", default=0.0, min=0.0, soft_min=0.0, @@ -959,7 +990,7 @@ class Simple(Operator): shapeItems = [ ('2D', "2D", "2D shape Curve"), ('3D', "3D", "3D shape Curve")] - shape = EnumProperty( + shape : EnumProperty( name="2D / 3D", items=shapeItems, description="2D or 3D Curve" @@ -1140,10 +1171,10 @@ class Simple(Operator): row = layout.row() row.prop(self, "shape", expand=True) box = layout.box() - box.label("Location:") + box.label(text="Location:") box.prop(self, "Simple_startlocation") box = layout.box() - box.label("Rotation:") + box.label(text="Rotation:") box.prop(self, "Simple_rotation_euler") if l != 0 or s != 0: @@ -1151,49 +1182,22 @@ class Simple(Operator): box.label(text="Statistics:", icon="INFO") if l != 0: l_str = str(round(l, 4)) - box.label("Length: " + l_str) + box.label(text="Length: " + l_str) if s != 0: s_str = str(round(s, 4)) - box.label("Area: " + s_str) + box.label(text="Area: " + s_str) @classmethod def poll(cls, context): return context.scene is not None def execute(self, context): - if self.Simple_Change: - SimpleDelete(self.Simple_Delete) - - # go to object mode - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') - - # turn off undo - undo = bpy.context.user_preferences.edit.use_global_undo - bpy.context.user_preferences.edit.use_global_undo = False - # main function self.align_matrix = align_matrix(context, self.Simple_startlocation) main(context, self, self.align_matrix) - # restore pre operator undo state - bpy.context.user_preferences.edit.use_global_undo = undo - - return {'FINISHED'} - - def invoke(self, context, event): - # store creation_matrix - if self.Simple_Change: - bpy.context.scene.cursor_location = self.Simple_startlocation - else: - self.Simple_startlocation = bpy.context.scene.cursor_location - - self.align_matrix = align_matrix(context, self.Simple_startlocation) - self.execute(context) - return {'FINISHED'} - # ------------------------------------------------------------ # Fillet @@ -1201,9 +1205,9 @@ class BezierPointsFillet(Operator): bl_idname = "curve.bezier_points_fillet" bl_label = "Bezier points Fillet" bl_description = "Bezier points Fillet" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'REGISTER', 'UNDO', 'PRESET'} - Fillet_radius = FloatProperty( + Fillet_radius : FloatProperty( name="Radius", default=0.25, unit='LENGTH', @@ -1211,7 +1215,7 @@ class BezierPointsFillet(Operator): ) Types = [('Round', "Round", "Round"), ('Chamfer', "Chamfer", "Chamfer")] - Fillet_Type = EnumProperty( + Fillet_Type : EnumProperty( name="Type", description="Fillet type", items=Types @@ -1230,15 +1234,6 @@ class BezierPointsFillet(Operator): return context.scene is not None def execute(self, context): - # go to object mode - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') - - # turn off undo - undo = bpy.context.user_preferences.edit.use_global_undo - bpy.context.user_preferences.edit.use_global_undo = False - # main function spline = bpy.context.object.data.splines.active selected = [p for p in spline.bezier_points if p.select_control_point] @@ -1321,17 +1316,8 @@ class BezierPointsFillet(Operator): bpy.ops.curve.select_all(action='SELECT') bpy.ops.curve.spline_type_set(type='BEZIER') - # restore pre operator undo state - bpy.context.user_preferences.edit.use_global_undo = undo - return {'FINISHED'} - def invoke(self, context, event): - self.execute(context) - - return {'FINISHED'} - - def subdivide_cubic_bezier(p1, p2, p3, p4, t): p12 = (p2 - p1) * t + p1 p23 = (p3 - p2) * t + p2 @@ -1352,9 +1338,9 @@ class BezierDivide(Operator): bl_options = {'REGISTER', 'UNDO'} # align_matrix for the invoke - align_matrix = Matrix() + align_matrix : Matrix() - Bezier_t = FloatProperty( + Bezier_t : FloatProperty( name="t (0% - 100%)", default=50.0, min=0.0, soft_min=0.0, @@ -1367,15 +1353,6 @@ class BezierDivide(Operator): return context.scene is not None def execute(self, context): - # go to object mode - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') - - # turn off undo - undo = bpy.context.user_preferences.edit.use_global_undo - bpy.context.user_preferences.edit.use_global_undo = False - # main function spline = bpy.context.object.data.splines.active selected_all = [p for p in spline.bezier_points if p.select_control_point] @@ -1414,376 +1391,30 @@ class BezierDivide(Operator): selected_all[2].handle_right = h[3] selected_all[0].handle_left = h[4] - # restore pre operator undo state - bpy.context.user_preferences.edit.use_global_undo = undo - return {'FINISHED'} - def invoke(self, context, event): - self.execute(context) - - return {'FINISHED'} - - -# ------------------------------------------------------------ -# Simple change panel - -class SimplePanel(Panel): - bl_idname = "VIEW3D_PT_simple_curve" - bl_label = "Simple Curve" - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_options = {'DEFAULT_CLOSED'} - bl_category = "Tools" - - @classmethod - def poll(cls, context): - if not context.active_object: - pass - elif context.object.s_curve.Simple is True: - return (context.object) - - def draw(self, context): - if context.object.s_curve.Simple is True: - layout = self.layout - obj = context.object - row = layout.row() - - simple_change = row.operator("curve.simple", text="Change", - icon="OUTLINER_DATA_CURVE") - simple_change.Simple_Change = True - simple_change.Simple_Delete = obj.name - simple_change.Simple_Type = obj.s_curve.Simple_Type - simple_change.Simple_startlocation = obj.location - simple_change.Simple_endlocation = obj.s_curve.Simple_endlocation - - simple_change.Simple_a = obj.s_curve.Simple_a - simple_change.Simple_b = obj.s_curve.Simple_b - simple_change.Simple_h = obj.s_curve.Simple_h - - simple_change.Simple_angle = obj.s_curve.Simple_angle - simple_change.Simple_startangle = obj.s_curve.Simple_startangle - simple_change.Simple_endangle = obj.s_curve.Simple_endangle - simple_change.Simple_rotation_euler = obj.rotation_euler - - simple_change.Simple_sides = obj.s_curve.Simple_sides - simple_change.Simple_radius = obj.s_curve.Simple_radius - simple_change.Simple_center = obj.s_curve.Simple_center - simple_change.Simple_width = obj.s_curve.Simple_width - simple_change.Simple_length = obj.s_curve.Simple_length - simple_change.Simple_rounded = obj.s_curve.Simple_rounded - - -# ------------------------------------------------------------ -# Fillet tools panel - -class SimpleEdit(Operator): - bl_idname = "object._simple_edit" - bl_label = "Create Curves" - bl_description = "Subdivide and Fillet Curves" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - vertex = [] - nselected = [] - n = 0 - obj = context.active_object - if obj is not None: - if obj.type == 'CURVE': - for i in obj.data.splines: - for j in i.bezier_points: - n += 1 - if j.select_control_point: - nselected.append(n) - vertex.append(obj.matrix_world * j.co) - - if len(vertex) > 0 and n > 2: - return (context.active_object) - if len(vertex) == 2 and abs(nselected[0] - nselected[1]) == 1: - return (context.active_object) - - selected = 0 - for obj in context.selected_objects: - if obj.type == 'CURVE': - selected += 1 - - if selected >= 2: - return (context.selected_objects) - - def draw(self, context): - vertex = [] - selected = [] - n = 0 - obj = context.active_object - if obj is not None: - if obj.type == 'CURVE': - for i in obj.data.splines: - for j in i.bezier_points: - n += 1 - if j.select_control_point: - selected.append(n) - vertex.append(obj.matrix_world * j.co) - - if len(vertex) > 0 and n > 2: - layout = self.layout - row = layout.row() - row.operator("curve.bezier_points_fillet", text="Fillet") - - if len(vertex) == 2 and abs(selected[0] - selected[1]) == 1: - layout = self.layout - row = layout.row() - row.operator("curve.bezier_spline_divide", text="Divide") - - -# ------------------------------------------------------------ -# location update - -def StartLocationUpdate(self, context): - - bpy.context.scene.cursor_location = self.Simple_startlocation - return - - -# ------------------------------------------------------------ -# Add properties to objects - -class SimpleVariables(PropertyGroup): - - Simple = BoolProperty() - Simple_Change = BoolProperty() - - # general properties - Types = [('Point', "Point", "Construct a Point"), - ('Line', "Line", "Construct a Line"), - ('Distance', "Distance", "Construct a two point Distance"), - ('Angle', "Angle", "Construct an Angle"), - ('Circle', "Circle", "Construct a Circle"), - ('Ellipse', "Ellipse", "Construct an Ellipse"), - ('Arc', "Arc", "Construct an Arc"), - ('Sector', "Sector", "Construct a Sector"), - ('Segment', "Segment", "Construct a Segment"), - ('Rectangle', "Rectangle", "Construct a Rectangle"), - ('Rhomb', "Rhomb", "Construct a Rhomb"), - ('Polygon', "Polygon", "Construct a Polygon"), - ('Polygon_ab', "Polygon ab", "Construct a Polygon ab"), - ('Trapezoid', "Trapezoid", "Construct a Trapezoid") - ] - Simple_Type = EnumProperty( - name="Type", - description="Form of Curve to create", - items=Types - ) - # Line properties - Simple_startlocation = FloatVectorProperty( - name="Start location", - description="Start location", - default=(0.0, 0.0, 0.0), - subtype='TRANSLATION', - update=StartLocationUpdate - ) - Simple_endlocation = FloatVectorProperty( - name="End location", - description="End location", - default=(2.0, 2.0, 2.0), - subtype='TRANSLATION' - ) - Simple_rotation_euler = FloatVectorProperty( - name="Rotation", - description="Rotation", - default=(0.0, 0.0, 0.0), - subtype='EULER' - ) - # Trapezoid properties - Simple_a = FloatProperty( - name="Side a", - default=2.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="a side Value" - ) - Simple_b = FloatProperty( - name="Side b", - default=1.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="b side Value" - ) - Simple_h = FloatProperty( - name="Height", - default=1.0, - unit='LENGTH', - description="Height of the Trapezoid - distance between a and b" - ) - Simple_angle = FloatProperty( - name="Angle", - default=45.0, - description="Angle" - ) - Simple_startangle = FloatProperty( - name="Start angle", - default=0.0, - min=-360.0, soft_min=-360.0, - max=360.0, soft_max=360.0, - description="Start angle" - ) - Simple_endangle = FloatProperty( - name="End angle", - default=45.0, - min=-360.0, soft_min=-360.0, - max=360.0, soft_max=360.0, - description="End angle" - ) - Simple_sides = IntProperty( - name="Sides", - default=3, - min=3, soft_min=3, - description="Number of Sides" - ) - Simple_radius = FloatProperty( - name="Radius", - default=1.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="Radius" - ) - Simple_center = BoolProperty( - name="Length center", - default=True, - description="Length center" - ) - # Rectangle properties - Simple_width = FloatProperty( - name="Width", - default=2.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="Width" - ) - Simple_length = FloatProperty( - name="Length", - default=2.0, - min=0.0, soft_min=0.0, - unit='LENGTH', - description="Length" - ) - Simple_rounded = FloatProperty( - name="Rounded", - default=0.0, - unit='LENGTH', - description="Rounded corners" - ) - - -class INFO_MT_simple_menu(Menu): - bl_idname = "INFO_MT_simple_menu" - bl_label = "2D Objects" - - def draw(self, context): - self.layout.operator_context = 'INVOKE_REGION_WIN' - - oper1 = self.layout.operator(Simple.bl_idname, text="Angle", icon="MOD_CURVE") - oper1.Simple_Change = False - oper1.Simple_Type = "Angle" - - oper2 = self.layout.operator(Simple.bl_idname, text="Arc", icon="MOD_CURVE") - oper2.Simple_Change = False - oper2.Simple_Type = "Arc" - - oper3 = self.layout.operator(Simple.bl_idname, text="Circle", icon="MOD_CURVE") - oper3.Simple_Change = False - oper3.Simple_Type = "Circle" - - oper4 = self.layout.operator(Simple.bl_idname, text="Distance", icon="MOD_CURVE") - oper4.Simple_Change = False - oper4.Simple_Type = "Distance" - - oper5 = self.layout.operator(Simple.bl_idname, text="Ellipse", icon="MOD_CURVE") - oper5.Simple_Change = False - oper5.Simple_Type = "Ellipse" - - oper6 = self.layout.operator(Simple.bl_idname, text="Line", icon="MOD_CURVE") - oper6.Simple_Change = False - oper6.Simple_Type = "Line" - - oper7 = self.layout.operator(Simple.bl_idname, text="Point", icon="MOD_CURVE") - oper7.Simple_Change = False - oper7.Simple_Type = "Point" - - oper8 = self.layout.operator(Simple.bl_idname, text="Polygon", icon="MOD_CURVE") - oper8.Simple_Change = False - oper8.Simple_Type = "Polygon" - - oper9 = self.layout.operator(Simple.bl_idname, text="Polygon ab", icon="MOD_CURVE") - oper9.Simple_Change = False - oper9.Simple_Type = "Polygon_ab" - - oper10 = self.layout.operator(Simple.bl_idname, text="Rectangle", icon="MOD_CURVE") - oper10.Simple_Change = False - oper10.Simple_Type = "Rectangle" - - oper11 = self.layout.operator(Simple.bl_idname, text="Rhomb", icon="MOD_CURVE") - oper11.Simple_Change = False - oper11.Simple_Type = "Rhomb" - - oper12 = self.layout.operator(Simple.bl_idname, text="Sector", icon="MOD_CURVE") - oper12.Simple_Change = False - oper12.Simple_Type = "Sector" - - oper13 = self.layout.operator(Simple.bl_idname, text="Segment", icon="MOD_CURVE") - oper13.Simple_Change = False - oper13.Simple_Type = "Segment" - - oper14 = self.layout.operator(Simple.bl_idname, text="Trapezoid", icon="MOD_CURVE") - oper14.Simple_Change = False - oper14.Simple_Type = "Trapezoid" - - # Register - -def Simple_button(self, context): - layout = self.layout - layout.separator() - self.layout.menu("INFO_MT_simple_menu", icon="MOD_CURVE") - -class VIEW3D_MT_simple_edit_curve_menu(bpy.types.Menu): - bl_label = 'Simple edit' - - def draw(self, context): - self.layout.operator("curve.bezier_points_fillet", text="Fillet") - self.layout.operator("curve.bezier_spline_divide", text="Divide") - -def Simple_curve_edit_menu(self, context): - self.layout.menu('VIEW3D_MT_simple_edit_curve_menu') - self.layout.separator() +classes = [ + Simple, + BezierDivide, + BezierPointsFillet +] def register(): - bpy.utils.register_class(Simple) - bpy.utils.register_class(BezierPointsFillet) - bpy.utils.register_class(BezierDivide) - bpy.utils.register_class(SimplePanel) - bpy.utils.register_class(SimpleEdit) - bpy.utils.register_class(INFO_MT_simple_menu) - bpy.utils.register_class(SimpleVariables) - - bpy.types.INFO_MT_curve_add.append(Simple_button) - bpy.types.VIEW3D_MT_edit_curve_specials.prepend(Simple_curve_edit_menu) - - bpy.types.Object.s_curve = PointerProperty(type=SimpleVariables) + from bpy.utils import register_class + for cls in classes: + register_class(cls) + bpy.types.VIEW3D_MT_curve_add.append(menu) + bpy.types.VIEW3D_MT_edit_curve_specials.prepend(Simple_curve_edit_menu) def unregister(): - bpy.utils.unregister_class(Simple) - bpy.utils.unregister_class(BezierPointsFillet) - bpy.utils.unregister_class(BezierDivide) - bpy.utils.unregister_class(SimplePanel) - bpy.utils.unregister_class(SimpleEdit) - bpy.utils.unregister_class(INFO_MT_simple_menu) - bpy.utils.unregister_class(SimpleVariables) - - bpy.types.INFO_MT_curve_add.remove(Simple_button) - del bpy.types.Object.s_curve + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + bpy.types.VIEW3D_MT_curve_add.remove(menu) + bpy.types.VIEW3D_MT_edit_curve_specials.remove(Simple_curve_edit_menu) if __name__ == "__main__": register() diff --git a/add_curve_extra_objects/add_curve_spirals.py b/add_curve_extra_objects/add_curve_spirals.py index 9abfebd8..c8c11789 100644 --- a/add_curve_extra_objects/add_curve_spirals.py +++ b/add_curve_extra_objects/add_curve_spirals.py @@ -5,8 +5,8 @@ bl_info = { "name": "Spirals", "description": "Make spirals", "author": "Alejandro Omar Chocano Vasquez", - "version": (1, 2, 1), - "blender": (2, 62, 0), + "version": (1, 2, 2), + "blender": (2, 80, 0), "location": "View3D > Add > Curve", "warning": "", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.4/Py/" @@ -204,9 +204,9 @@ class CURVE_OT_spirals(Operator): bl_idname = "curve.spirals" bl_label = "Curve Spirals" bl_description = "Create different types of spirals" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'REGISTER', 'UNDO', 'PRESET'} - spiral_type = EnumProperty( + spiral_type : EnumProperty( items=[('ARCH', "Archemedian", "Archemedian"), ("LOG", "Logarithmic", "Logarithmic"), ("SPHERE", "Spheric", "Spheric"), @@ -215,14 +215,14 @@ class CURVE_OT_spirals(Operator): name="Spiral Type", description="Type of spiral to add" ) - curve_type = EnumProperty( + curve_type : EnumProperty( items=[('POLY', "Poly", "PolyLine"), ("NURBS", "NURBS", "NURBS")], default='POLY', name="Curve Type", description="Type of spline to use" ) - spiral_direction = EnumProperty( + spiral_direction : EnumProperty( items=[('COUNTER_CLOCKWISE', "Counter Clockwise", "Wind in a counter clockwise direction"), ("CLOCKWISE", "Clockwise", @@ -231,62 +231,62 @@ class CURVE_OT_spirals(Operator): name="Spiral Direction", description="Direction of winding" ) - turns = IntProperty( + turns : IntProperty( default=1, min=1, max=1000, description="Length of Spiral in 360 deg" ) - steps = IntProperty( + steps : IntProperty( default=24, min=2, max=1000, description="Number of Vertices per turn" ) - radius = FloatProperty( + radius : FloatProperty( default=1.00, min=0.00, max=100.00, description="Radius for first turn" ) - dif_z = FloatProperty( + dif_z : FloatProperty( default=0, min=-10.00, max=100.00, description="Increase in Z axis per turn" ) # needed for 1 and 2 spiral_type # Archemedian variables - dif_radius = FloatProperty( + dif_radius : FloatProperty( default=0.00, min=-50.00, max=50.00, description="Radius increment in each turn" ) # step between turns(one turn equals 360 deg) # Log variables - B_force = FloatProperty( + B_force : FloatProperty( default=1.00, min=0.00, max=30.00, description="Factor of exponent" ) # Torus variables - inner_radius = FloatProperty( + inner_radius : FloatProperty( default=0.20, min=0.00, max=100, description="Inner Radius of Torus" ) - dif_inner_radius = FloatProperty( + dif_inner_radius : FloatProperty( default=0, min=-10, max=100, description="Increase of inner Radius per Cycle" ) - dif_radius = FloatProperty( + dif_radius : FloatProperty( default=0, min=-10, max=100, description="Increase of Torus Radius per Cycle" ) - cycles = FloatProperty( + cycles : FloatProperty( default=1, min=0.00, max=1000, description="Number of Cycles" ) - curves_number = IntProperty( + curves_number : IntProperty( default=1, min=1, max=400, description="Number of curves of spiral" @@ -300,13 +300,13 @@ class CURVE_OT_spirals(Operator): layout = self.layout col = layout.column_flow(align=True) - col.label("Presets:") + col.label(text="Presets:") row = col.row(align=True) row.menu("OBJECT_MT_spiral_curve_presets", text=bpy.types.OBJECT_MT_spiral_curve_presets.bl_label) - row.operator("curve_extras.spiral_presets", text="", icon='ZOOMIN') - op = row.operator("curve_extras.spiral_presets", text="", icon='ZOOMOUT') + row.operator("curve_extras.spiral_presets", text="") + op = row.operator("curve_extras.spiral_presets", text="") op.remove_active = True layout.prop(self, "spiral_type") @@ -320,25 +320,25 @@ class CURVE_OT_spirals(Operator): box = layout.box() if self.spiral_type == 'ARCH': - box.label("Archemedian Settings:") + box.label(text="Archemedian Settings:") col = box.column(align=True) col.prop(self, "dif_radius", text="Radius Growth") col.prop(self, "radius", text="Radius") col.prop(self, "dif_z", text="Height") if self.spiral_type == 'LOG': - box.label("Logarithmic Settings:") + box.label(text="Logarithmic Settings:") col = box.column(align=True) col.prop(self, "radius", text="Radius") col.prop(self, "B_force", text="Expansion Force") col.prop(self, "dif_z", text="Height") if self.spiral_type == 'SPHERE': - box.label("Spheric Settings:") + box.label(text="Spheric Settings:") box.prop(self, "radius", text="Radius") if self.spiral_type == 'TORUS': - box.label("Torus Settings:") + box.label(text="Torus Settings:") col = box.column(align=True) col.prop(self, "cycles", text="Number of Cycles") @@ -412,13 +412,22 @@ class OBJECT_MT_spiral_curve_presets(Menu): draw = bpy.types.Menu.draw_preset -def register(): - bpy.utils.register_module(__name__) +# Register +classes = [ + CURVE_OT_spirals, + CURVE_EXTRAS_OT_spirals_presets, + OBJECT_MT_spiral_curve_presets +] +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) def unregister(): - bpy.utils.unregister_module(__name__) - + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) if __name__ == "__main__": register() diff --git a/add_curve_extra_objects/add_curve_spirofit_bouncespline.py b/add_curve_extra_objects/add_curve_spirofit_bouncespline.py index 382b2d6b..f2dd0fb5 100644 --- a/add_curve_extra_objects/add_curve_spirofit_bouncespline.py +++ b/add_curve_extra_objects/add_curve_spirofit_bouncespline.py @@ -20,8 +20,8 @@ bl_info = { "name": "SpiroFit, BounceSpline and Catenary", "author": "Antonio Osprite, Liero, Atom, Jimmy Hazevoet", - "version": (0, 2, 1), - "blender": (2, 78, 0), + "version": (0, 2, 2), + "blender": (2, 80, 0), "location": "Toolshelf > Create Tab", "description": "SpiroFit, BounceSpline and Catenary adds " "splines to selected mesh or objects", @@ -123,19 +123,19 @@ class SpiroFitSpline(Operator): bl_description = "Wrap selected mesh in a spiral" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - map_method = EnumProperty( + map_method : EnumProperty( name="Mapping", default='RAYCAST', description="Mapping method", items=[('RAYCAST', 'Ray cast', 'Ray casting'), ('CLOSESTPOINT', 'Closest point', 'Closest point on mesh')] ) - direction = BoolProperty( + direction : BoolProperty( name="Direction", description="Spire direction", default=False ) - spire_resolution = IntProperty( + spire_resolution : IntProperty( name="Spire Resolution", default=8, min=3, @@ -143,7 +143,7 @@ class SpiroFitSpline(Operator): soft_max=128, description="Number of steps for one turn" ) - spires = IntProperty( + spires : IntProperty( name="Spires", default=4, min=1, @@ -151,72 +151,72 @@ class SpiroFitSpline(Operator): soft_max=128, description="Number of turns" ) - offset = FloatProperty( + offset : FloatProperty( name="Offset", default=0.0, precision=3, description="Use normal direction to offset spline" ) - waves = IntProperty( + waves : IntProperty( name="Wave", default=0, min=0, description="Wave amount" ) - wave_iscale = FloatProperty( + wave_iscale : FloatProperty( name="Wave Intensity", default=0.0, min=0.0, precision=3, description="Wave intensity scale" ) - rndm_spire = FloatProperty( + rndm_spire : FloatProperty( name="Randomise", default=0.0, min=0.0, precision=3, description="Randomise spire" ) - spline_name = StringProperty( + spline_name : StringProperty( name="Name", default="SpiroFit" ) - spline_type = EnumProperty( + spline_type : EnumProperty( name="Spline", default='BEZIER', description="Spline type", items=[('POLY', 'Poly', 'Poly spline'), ('BEZIER', 'Bezier', 'Bezier spline')] ) - resolution_u = IntProperty( + resolution_u : IntProperty( name="Resolution U", default=12, min=0, max=64, description="Curve resolution u" ) - bevel = FloatProperty( + bevel : FloatProperty( name="Bevel Radius", default=0.0, min=0.0, precision=3, description="Bevel depth" ) - bevel_res = IntProperty( + bevel_res : IntProperty( name="Bevel Resolution", default=0, min=0, max=32, description="Bevel resolution" ) - extrude = FloatProperty( + extrude : FloatProperty( name="Extrude", default=0.0, min=0.0, precision=3, description="Extrude amount" ) - twist_mode = EnumProperty( + twist_mode : EnumProperty( name="Twisting", default='MINIMUM', description="Twist method, type of tilt calculation", @@ -224,48 +224,48 @@ class SpiroFitSpline(Operator): ('MINIMUM', "Minimum", 'Minimum'), ('TANGENT', "Tangent", 'Tangent')] ) - twist_smooth = FloatProperty( + twist_smooth : FloatProperty( name="Smooth", default=0.0, min=0.0, precision=3, description="Twist smoothing amount for tangents" ) - tilt = FloatProperty( + tilt : FloatProperty( name="Tilt", default=0.0, precision=3, description="Spline handle tilt" ) - random_radius = FloatProperty( + random_radius : FloatProperty( name="Randomise", default=0.0, min=0.0, precision=3, description="Randomise radius of spline controlpoints" ) - x_ray = BoolProperty( + x_ray : BoolProperty( name="X-Ray", default=False, description="X-Ray - make the object draw in front of others" ) - random_seed = IntProperty( + random_seed : IntProperty( name="Random Seed", default=1, min=0, description="Random seed number" ) - origin_to_start = BoolProperty( + origin_to_start : BoolProperty( name="Origin at Start", description="Set origin at first point of spline", default=False ) - refresh = BoolProperty( + refresh : BoolProperty( name="Refresh", description="Refresh spline", default=False ) - auto_refresh = BoolProperty( + auto_refresh : BoolProperty( name="Auto", description="Auto refresh spline", default=True @@ -323,10 +323,10 @@ class SpiroFitSpline(Operator): "Active Object is not a Mesh. Operation Cancelled") return {'CANCELLED'} - undo = context.user_preferences.edit.use_global_undo - context.user_preferences.edit.use_global_undo = False + #undo = context.user_preferences.edit.use_global_undo + #context.user_preferences.edit.use_global_undo = False - bpy.ops.object.select_all(action='DESELECT') + #bpy.ops.object.select_all(action='DESELECT') r.seed(self.random_seed) @@ -364,7 +364,7 @@ class SpiroFitSpline(Operator): if self.auto_refresh is False: self.refresh = False - context.user_preferences.edit.use_global_undo = undo + #context.user_preferences.edit.use_global_undo = undo return {'FINISHED'} @@ -426,7 +426,7 @@ class BounceSpline(Operator): bl_description = "Fill selected mesh with a spline" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - bounce_number = IntProperty( + bounce_number : IntProperty( name="Bounces", default=1000, min=1, @@ -434,71 +434,71 @@ class BounceSpline(Operator): soft_max=10000, description="Number of bounces" ) - ang_noise = FloatProperty( + ang_noise : FloatProperty( name="Angular Noise", default=0.25, min=0.0, precision=3, description="Add some noise to ray direction" ) - offset = FloatProperty( + offset : FloatProperty( name="Offset", default=0.0, precision=3, description="Use normal direction to offset spline" ) - extra = IntProperty( + extra : IntProperty( name="Extra", default=50, min=0, max=1000, description="Number of extra tries if it fails to hit mesh" ) - active_face = BoolProperty( + active_face : BoolProperty( name="Active Face", default=False, description="Starts from active face or a random one" ) - spline_name = StringProperty( + spline_name : StringProperty( name="Name", default="BounceSpline" ) - spline_type = EnumProperty( + spline_type : EnumProperty( name="Spline", default='BEZIER', description="Spline type", items=[('POLY', "Poly", "Poly spline"), ('BEZIER', "Bezier", "Bezier spline")] ) - resolution_u = IntProperty( + resolution_u : IntProperty( name="Resolution U", default=12, min=0, max=64, description="Curve resolution u" ) - bevel = FloatProperty( + bevel : FloatProperty( name="Bevel Radius", default=0.0, min=0.0, precision=3, description="Bevel depth" ) - bevel_res = IntProperty( + bevel_res : IntProperty( name="Bevel Resolution", default=0, min=0, max=32, description="Bevel resolution" ) - extrude = FloatProperty( + extrude : FloatProperty( name="Extrude", default=0.0, min=0.0, precision=3, description="Extrude amount" ) - twist_mode = EnumProperty( + twist_mode : EnumProperty( name="Twisting", default='MINIMUM', description="Twist method, type of tilt calculation", @@ -506,48 +506,48 @@ class BounceSpline(Operator): ('MINIMUM', "Minimum", 'Minimum'), ('TANGENT', "Tangent", 'Tangent')] ) - twist_smooth = FloatProperty( + twist_smooth : FloatProperty( name="Smooth", default=0.0, min=0.0, precision=3, description="Twist smoothing amount for tangents" ) - tilt = FloatProperty( + tilt : FloatProperty( name="Tilt", default=0.0, precision=3, description="Spline handle tilt" ) - random_radius = FloatProperty( + random_radius : FloatProperty( name="Randomise", default=0.0, min=0.0, precision=3, description="Randomise radius of spline controlpoints" ) - x_ray = BoolProperty( + x_ray : BoolProperty( name="X-Ray", default=False, description="X-Ray - make the object draw in front of others" ) - random_seed = IntProperty( + random_seed : IntProperty( name="Random Seed", default=1, min=0, description="Random seed number" ) - origin_to_start = BoolProperty( + origin_to_start : BoolProperty( name="Origin at Start", description="Set origin at first point of spline", default=False ) - refresh = BoolProperty( + refresh : BoolProperty( name="Refresh", description="Refresh spline", default=False ) - auto_refresh = BoolProperty( + auto_refresh : BoolProperty( name="Auto", description="Auto refresh spline", default=True @@ -572,7 +572,7 @@ class BounceSpline(Operator): col.prop(self, "spline_name") col.separator() col.prop(self, "bounce_number") - row = col.row(align=True).split(0.9, align=True) + row = col.row(align=True).split(factor=0.9, align=True) row.prop(self, "ang_noise") row.prop(self, "active_face", toggle=True, text="", icon="SNAP_FACE") col.prop(self, "offset") @@ -598,10 +598,10 @@ class BounceSpline(Operator): if obj.type != 'MESH': return {'CANCELLED'} - undo = context.user_preferences.edit.use_global_undo - context.user_preferences.edit.use_global_undo = False + #undo = context.user_preferences.edit.use_global_undo + #context.user_preferences.edit.use_global_undo = False - bpy.ops.object.select_all(action='DESELECT') + #bpy.ops.object.select_all(action='DESELECT') r.seed(self.random_seed) @@ -636,7 +636,7 @@ class BounceSpline(Operator): if self.auto_refresh is False: self.refresh = False - context.user_preferences.edit.use_global_undo = undo + #context.user_preferences.edit.use_global_undo = undo return {'FINISHED'} @@ -675,14 +675,14 @@ class CatenaryCurve(Operator): bl_description = "Hang a curve between two selected objects" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - steps = IntProperty( + steps : IntProperty( name="Steps", description="Resolution of the curve", default=24, min=2, max=1024, ) - var_a = FloatProperty( + var_a : FloatProperty( name="a", description="Catenary variable a", precision=3, @@ -690,46 +690,46 @@ class CatenaryCurve(Operator): min=0.01, max=100.0 ) - spline_name = StringProperty( + spline_name : StringProperty( name="Name", default="Catenary" ) - spline_type = EnumProperty( + spline_type : EnumProperty( name="Spline", default='BEZIER', description="Spline type", items=[('POLY', "Poly", "Poly spline"), ('BEZIER', "Bezier", "Bezier spline")] ) - resolution_u = IntProperty( + resolution_u : IntProperty( name="Resolution U", default=12, min=0, max=64, description="Curve resolution u" ) - bevel = FloatProperty( + bevel : FloatProperty( name="Bevel Radius", default=0.0, min=0.0, precision=3, description="Bevel depth" ) - bevel_res = IntProperty( + bevel_res : IntProperty( name="Bevel Resolution", default=0, min=0, max=32, description="Bevel resolution" ) - extrude = FloatProperty( + extrude : FloatProperty( name="Extrude", default=0.0, min=0.0, precision=3, description="Extrude amount" ) - twist_mode = EnumProperty( + twist_mode : EnumProperty( name="Twisting", default='MINIMUM', description="Twist method, type of tilt calculation", @@ -737,48 +737,48 @@ class CatenaryCurve(Operator): ('MINIMUM', "Minimum", "Minimum"), ('TANGENT', "Tangent", "Tangent")] ) - twist_smooth = FloatProperty( + twist_smooth : FloatProperty( name="Smooth", default=0.0, min=0.0, precision=3, description="Twist smoothing amount for tangents" ) - tilt = FloatProperty( + tilt : FloatProperty( name="Tilt", default=0.0, precision=3, description="Spline handle tilt" ) - random_radius = FloatProperty( + random_radius : FloatProperty( name="Randomise", default=0.0, min=0.0, precision=3, description="Randomise radius of spline controlpoints" ) - x_ray = BoolProperty( + x_ray : BoolProperty( name="X-Ray", default=False, description="X-Ray - make the object draw in front of others" ) - random_seed = IntProperty( + random_seed : IntProperty( name="Random Seed", default=1, min=0, description="Random seed number" ) - origin_to_start = BoolProperty( + origin_to_start : BoolProperty( name="Origin at Start", description="Set origin at first point of spline", default=False ) - refresh = BoolProperty( + refresh : BoolProperty( name="Refresh", description="Refresh spline", default=False ) - auto_refresh = BoolProperty( + auto_refresh : BoolProperty( name="Auto", description="Auto refresh spline", default=True @@ -839,10 +839,10 @@ class CatenaryCurve(Operator): "Catenary could not be completed. Operation Cancelled") return {'CANCELLED'} - bpy.ops.object.select_all(action='DESELECT') + #bpy.ops.object.select_all(action='DESELECT') - undo = context.user_preferences.edit.use_global_undo - context.user_preferences.edit.use_global_undo = False + #undo = context.user_preferences.edit.use_global_undo + #context.user_preferences.edit.use_global_undo = False r.seed(self.random_seed) @@ -876,7 +876,7 @@ class CatenaryCurve(Operator): if self.auto_refresh is False: self.refresh = False - context.user_preferences.edit.use_global_undo = undo + #context.user_preferences.edit.use_global_undo = undo return {'FINISHED'} @@ -920,7 +920,7 @@ def add_curve_object( for i in range(len(verts)): spline.points[i].co = verts[i][0], verts[i][1], verts[i][2], 1 - bpy.context.scene.objects.link(cur) + bpy.context.scene.collection.objects.link(cur) cur.data.use_uv_as_generated = True cur.data.resolution_u = resolution_u cur.data.fill_mode = 'FULL' @@ -930,8 +930,8 @@ def add_curve_object( cur.data.twist_mode = twist_mode cur.data.twist_smooth = twist_smooth cur.matrix_world = matrix - bpy.context.scene.objects.active = cur - cur.select = True + #bpy.context.scene.objects.active = cur + cur.select_set(True) if x_ray is True: cur.show_x_ray = x_ray return @@ -984,7 +984,7 @@ class SplinePanel(Panel): bl_context = "objectmode" bl_region_type = "TOOLS" bl_label = "Spline" - bl_category = "Create" + #bl_category = "Create" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): diff --git a/add_curve_extra_objects/add_curve_torus_knots.py b/add_curve_extra_objects/add_curve_torus_knots.py index 4401112d..d138508e 100644 --- a/add_curve_extra_objects/add_curve_torus_knots.py +++ b/add_curve_extra_objects/add_curve_torus_knots.py @@ -20,8 +20,8 @@ bl_info = { "name": "Torus Knots", "author": "Marius Giurgi (DolphinDream), testscreenings", - "version": (0, 2), - "blender": (2, 76, 0), + "version": (0, 3), + "blender": (2, 80, 0), "location": "View3D > Add > Curve", "description": "Adds many types of (torus) knots", "warning": "", @@ -160,22 +160,22 @@ def align_matrix(self, context): else: rot = Matrix() - align_matrix = userLoc * loc * rot * userRot + align_matrix = userLoc @ loc @ rot @ userRot return align_matrix # ------------------------------------------------------------------------------ # Set curve BEZIER handles to auto -def setBezierHandles(obj, mode='AUTOMATIC'): +def setBezierHandles(obj, mode='AUTO'): scene = bpy.context.scene if obj.type != 'CURVE': return - scene.objects.active = obj - bpy.ops.object.mode_set(mode='EDIT', toggle=True) - bpy.ops.curve.select_all(action='SELECT') - bpy.ops.curve.handle_type_set(type=mode) - bpy.ops.object.mode_set(mode='OBJECT', toggle=True) + #scene.objects.active = obj + #bpy.ops.object.mode_set(mode='EDIT', toggle=True) + #bpy.ops.curve.select_all(action='SELECT') + #bpy.ops.curve.handle_type_set(type=mode) + #bpy.ops.object.mode_set(mode='OBJECT', toggle=True) # ------------------------------------------------------------------------------ @@ -237,6 +237,9 @@ def create_torus_knot(self, context): if splineType == 'BEZIER': spline.bezier_points.add(int(len(vertArray) * 1.0 / 3 - 1)) spline.bezier_points.foreach_set('co', vertArray) + for point in spline.bezier_points: + point.handle_right_type = self.handleType + point.handle_left_type = self.handleType else: spline.points.add(int(len(vertArray) * 1.0 / 4 - 1)) spline.points.foreach_set('co', vertArray) @@ -265,14 +268,14 @@ def create_torus_knot(self, context): # set object in the scene scene = bpy.context.scene - scene.objects.link(new_obj) # place in active scene - new_obj.select = True # set as selected - scene.objects.active = new_obj # set as active + scene.collection.objects.link(new_obj) # place in active scene + new_obj.select_set(True) # set as selected + #scene.objects.active = new_obj # set as active new_obj.matrix_world = self.align_matrix # apply matrix # set BEZIER handles - if splineType == 'BEZIER': - setBezierHandles(new_obj, self.handleType) + #if splineType == 'BEZIER': + # setBezierHandles(new_obj, self.handleType) return @@ -355,123 +358,123 @@ class torus_knot_plus(Operator, AddObjectHelper): align_matrix = None # GENERAL options - options_plus = BoolProperty( + options_plus : BoolProperty( name="Extra Options", default=False, description="Show more options (the plus part)", ) - absolute_location = BoolProperty( + absolute_location : BoolProperty( name="Absolute Location", default=False, description="Set absolute location instead of relative to 3D cursor", ) # COLOR options - use_colors = BoolProperty( + use_colors : BoolProperty( name="Use Colors", default=False, description="Show torus links in colors", ) - colorSet = EnumProperty( + colorSet : EnumProperty( name="Color Set", items=(('1', "RGBish", "RGBsish ordered colors"), ('2', "Rainbow", "Rainbow ordered colors")), ) - random_colors = BoolProperty( + random_colors : BoolProperty( name="Randomize Colors", default=False, description="Randomize link colors", ) - saturation = FloatProperty( + saturation : FloatProperty( name="Saturation", default=0.75, min=0.0, max=1.0, description="Color saturation", ) # SURFACE Options - geo_surface = BoolProperty( + geo_surface : BoolProperty( name="Surface", default=True, description="Create surface", ) - geo_bDepth = FloatProperty( + geo_bDepth : FloatProperty( name="Bevel Depth", default=0.04, min=0, soft_min=0, description="Bevel Depth", ) - geo_bRes = IntProperty( + geo_bRes : IntProperty( name="Bevel Resolution", default=2, min=0, soft_min=0, max=5, soft_max=5, description="Bevel Resolution" ) - geo_extrude = FloatProperty( + geo_extrude : FloatProperty( name="Extrude", default=0.0, min=0, soft_min=0, description="Amount of curve extrusion" ) - geo_offset = FloatProperty( + geo_offset : FloatProperty( name="Offset", default=0.0, min=0, soft_min=0, description="Offset the surface relative to the curve" ) # TORUS KNOT Options - torus_p = IntProperty( + torus_p : IntProperty( name="p", default=2, min=1, soft_min=1, description="Number of Revolutions around the torus hole before closing the knot" ) - torus_q = IntProperty( + torus_q : IntProperty( name="q", default=3, min=1, soft_min=1, description="Number of Spins through the torus hole before closing the knot" ) - flip_p = BoolProperty( + flip_p : BoolProperty( name="Flip p", default=False, description="Flip Revolution direction" ) - flip_q = BoolProperty( + flip_q : BoolProperty( name="Flip q", default=False, description="Flip Spin direction" ) - multiple_links = BoolProperty( + multiple_links : BoolProperty( name="Multiple Links", default=True, description="Generate all links or just one link when q and q are not co-primes" ) - torus_u = IntProperty( + torus_u : IntProperty( name="Rev. Multiplier", default=1, min=1, soft_min=1, description="Revolutions Multiplier" ) - torus_v = IntProperty( + torus_v : IntProperty( name="Spin Multiplier", default=1, min=1, soft_min=1, description="Spin multiplier" ) - torus_rP = FloatProperty( + torus_rP : FloatProperty( name="Revolution Phase", default=0.0, min=0.0, soft_min=0.0, description="Phase revolutions by this radian amount" ) - torus_sP = FloatProperty( + torus_sP : FloatProperty( name="Spin Phase", default=0.0, min=0.0, soft_min=0.0, description="Phase spins by this radian amount" ) # TORUS DIMENSIONS options - mode = EnumProperty( + mode : EnumProperty( name="Torus Dimensions", items=(("MAJOR_MINOR", "Major/Minor", "Use the Major/Minor radii for torus dimensions."), @@ -479,7 +482,7 @@ class torus_knot_plus(Operator, AddObjectHelper): "Use the Exterior/Interior radii for torus dimensions.")), update=mode_update_callback, ) - torus_R = FloatProperty( + torus_R : FloatProperty( name="Major Radius", min=0.00, max=100.0, default=1.0, @@ -487,7 +490,7 @@ class torus_knot_plus(Operator, AddObjectHelper): unit='LENGTH', description="Radius from the torus origin to the center of the cross section" ) - torus_r = FloatProperty( + torus_r : FloatProperty( name="Minor Radius", min=0.00, max=100.0, default=.25, @@ -495,7 +498,7 @@ class torus_knot_plus(Operator, AddObjectHelper): unit='LENGTH', description="Radius of the torus' cross section" ) - torus_iR = FloatProperty( + torus_iR : FloatProperty( name="Interior Radius", min=0.00, max=100.0, default=.75, @@ -503,7 +506,7 @@ class torus_knot_plus(Operator, AddObjectHelper): unit='LENGTH', description="Interior radius of the torus (closest to the torus center)" ) - torus_eR = FloatProperty( + torus_eR : FloatProperty( name="Exterior Radius", min=0.00, max=100.0, default=1.25, @@ -511,26 +514,26 @@ class torus_knot_plus(Operator, AddObjectHelper): unit='LENGTH', description="Exterior radius of the torus (farthest from the torus center)" ) - torus_s = FloatProperty( + torus_s : FloatProperty( name="Scale", min=0.01, max=100.0, default=1.00, description="Scale factor to multiply the radii" ) - torus_h = FloatProperty( + torus_h : FloatProperty( name="Height", default=1.0, min=0.0, max=100.0, description="Scale along the local Z axis" ) # CURVE options - torus_res = IntProperty( + torus_res : IntProperty( name="Curve Resolution", default=100, min=3, soft_min=3, description="Number of control vertices in the curve" ) - segment_res = IntProperty( + segment_res : IntProperty( name="Segment Resolution", default=12, min=1, soft_min=1, @@ -540,7 +543,7 @@ class torus_knot_plus(Operator, AddObjectHelper): ('POLY', "Poly", "Poly type"), ('NURBS', "Nurbs", "Nurbs type"), ('BEZIER', "Bezier", "Bezier type")] - outputType = EnumProperty( + outputType : EnumProperty( name="Output splines", default='BEZIER', description="Type of splines to output", @@ -548,15 +551,15 @@ class torus_knot_plus(Operator, AddObjectHelper): ) bezierHandles = [ ('VECTOR', "Vector", "Bezier Handles type - Vector"), - ('AUTOMATIC', "Auto", "Bezier Handles type - Automatic"), + ('AUTO', "Auto", "Bezier Handles type - Automatic"), ] - handleType = EnumProperty( + handleType : EnumProperty( name="Handle type", - default='AUTOMATIC', + default='AUTO', items=bezierHandles, description="Bezier handle type", ) - adaptive_resolution = BoolProperty( + adaptive_resolution : BoolProperty( name="Adaptive Resolution", default=False, description="Auto adjust curve resolution based on TK length", @@ -573,12 +576,12 @@ class torus_knot_plus(Operator, AddObjectHelper): col.label(text="Torus Knot Parameters:") box = layout.box() - split = box.split(percentage=0.85, align=True) + split = box.split(factor=0.85, align=True) split.prop(self, "torus_p", text="Revolutions") split.prop(self, "flip_p", toggle=True, text="", icon='ARROW_LEFTRIGHT') - split = box.split(percentage=0.85, align=True) + split = box.split(factor=0.85, align=True) split.prop(self, "torus_q", text="Spins") split.prop(self, "flip_q", toggle=True, text="", icon='ARROW_LEFTRIGHT') @@ -709,14 +712,14 @@ class torus_knot_plus(Operator, AddObjectHelper): self.align_matrix = align_matrix(self, context) # turn off undo - undo = bpy.context.user_preferences.edit.use_global_undo - bpy.context.user_preferences.edit.use_global_undo = False + #undo = bpy.context.user_preferences.edit.use_global_undo + #bpy.context.user_preferences.edit.use_global_undo = False # create the curve create_torus_knot(self, context) # restore pre operator undo state - bpy.context.user_preferences.edit.use_global_undo = undo + #bpy.context.user_preferences.edit.use_global_undo = undo return {'FINISHED'} @@ -724,3 +727,21 @@ class torus_knot_plus(Operator, AddObjectHelper): self.execute(context) return {'FINISHED'} + +# Register +classes = [ + torus_knot_plus +] + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + +def unregister(): + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + +if __name__ == "__main__": + register()
\ No newline at end of file diff --git a/add_curve_extra_objects/add_surface_plane_cone.py b/add_curve_extra_objects/add_surface_plane_cone.py index ad5e1eb9..4f34f654 100644 --- a/add_curve_extra_objects/add_surface_plane_cone.py +++ b/add_curve_extra_objects/add_surface_plane_cone.py @@ -4,8 +4,8 @@ bl_info = { "name": "Surface: Plane / Cone/ Star / Wedge", "description": "Create a NURBS surface plane", "author": "Folkert de Vries", - "version": (1, 0, 1), - "blender": (2, 5, 9), + "version": (1, 0, 2), + "blender": (2, 80, 0), "location": "View3D > Add > Surface", "warning": "", "wiki_url": "", @@ -31,7 +31,7 @@ from bpy.types import Operator # generic class for inheritance class MakeSurfaceHelpers: # get input for size and resolution - size = FloatProperty( + size : FloatProperty( name="Size", description="Size of the object", default=1.0, @@ -39,14 +39,14 @@ class MakeSurfaceHelpers: max=100.0, unit="LENGTH", ) - res_u = IntProperty( + res_u : IntProperty( name="Resolution U", description="Surface resolution in u direction", default=1, min=1, max=500, ) - res_v = IntProperty( + res_v : IntProperty( name="Resolution V", description="Surface resolution in v direction", default=1, @@ -71,7 +71,7 @@ class MakeSurfaceWedge(Operator, MakeSurfaceHelpers): bl_idname = "object.add_surface_wedge" bl_label = "Add Surface Wedge" bl_description = "Construct a Surface Wedge" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'REGISTER', 'UNDO', 'PRESET'} def execute(self, context): # variables @@ -137,7 +137,7 @@ class MakeSurfaceCone(Operator, MakeSurfaceHelpers): bl_idname = "object.add_surface_cone" bl_label = "Add Surface Cone" bl_description = "Construct a Surface Cone" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'REGISTER', 'UNDO', 'PRESET'} def execute(self, context): size = self.size @@ -283,7 +283,7 @@ class MakeSurfacePlane(Operator, MakeSurfaceHelpers): bl_idname = "object.add_surface_plane" bl_label = "Add Surface Plane" bl_description = "Construct a Surface Plane" - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'REGISTER', 'UNDO', 'PRESET'} def execute(self, context): size = self.size @@ -347,15 +347,15 @@ class MakeSurfacePlane(Operator, MakeSurfaceHelpers): class SmoothXtimes(Operator): bl_idname = "curve.smooth_x_times" bl_label = "Smooth X Times" - bl_space_type = "VIEW_3D" - bl_options = {'REGISTER', 'UNDO'} + #bl_space_type = "VIEW_3D" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} # use of this class: # lets you smooth till a thousand times. this is normally difficult, because # you have to press w, click, press w, click etc. # get values - times = IntProperty( + times : IntProperty( name="Smooth x Times", min=1, max=1000, @@ -375,24 +375,25 @@ class SmoothXtimes(Operator): return{'FINISHED'} +# Register +classes = [ + #MakeSurfaceHelpers, + MakeSurfacePlane, + MakeSurfaceCone, + MakeSurfaceStar, + MakeSurfaceWedge, + SmoothXtimes +] def register(): - bpy.utils.register_class(MakeSurfaceHelpers) - bpy.utils.register_class(MakeSurfacePlane) - bpy.utils.register_class(MakeSurfaceCone) - bpy.utils.register_class(MakeSurfaceStar) - bpy.utils.register_class(MakeSurfaceWedge) - bpy.utils.register_class(SmoothXtimes) - + from bpy.utils import register_class + for cls in classes: + register_class(cls) def unregister(): - bpy.utils.unregister_class(MakeSurfaceHelpers) - bpy.utils.unregister_class(MakeSurfacePlane) - bpy.utils.unregister_class(MakeSurfaceCone) - bpy.utils.unregister_class(MakeSurfaceStar) - bpy.utils.unregister_class(MakeSurfaceWedge) - bpy.utils.unregister_class(SmoothXtimes) - + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) if __name__ == "__main__": register() diff --git a/add_curve_extra_objects/beveltaper_curve.py b/add_curve_extra_objects/beveltaper_curve.py index f91eb8b3..016a0bc4 100644 --- a/add_curve_extra_objects/beveltaper_curve.py +++ b/add_curve_extra_objects/beveltaper_curve.py @@ -20,8 +20,8 @@ bl_info = { "name": "Bevel/Taper Curve", "author": "Cmomoney", - "version": (1, 1), - "blender": (2, 69, 0), + "version": (1, 2), + "blender": (2, 80, 0), "location": "View3D > Object > Bevel/Taper", "description": "Adds bevel and/or taper curve to active curve", "warning": "", @@ -238,38 +238,38 @@ class add_tapercurve(Operator): bl_label = "Add Curve as Taper" bl_description = ("Add taper curve to Active Curve\n" "Needs an existing Active Curve") - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'REGISTER', 'UNDO', 'PRESET'} - scale_ends1 = FloatProperty( + scale_ends1 : FloatProperty( name="End Width Left", description="Adjust left end taper", default=0.0, min=0.0 ) - scale_ends2 = FloatProperty( + scale_ends2 : FloatProperty( name="End Width Right", description="Adjust right end taper", default=0.0, min=0.0 ) - scale_mid = FloatProperty( + scale_mid : FloatProperty( name="Center Width", description="Adjust taper at center", default=1.0, min=0.0 ) - link1 = BoolProperty( + link1 : BoolProperty( name="Link Ends", description="Link the End Width Left / Right settings\n" "End Width Left will be editable ", default=True ) - link2 = BoolProperty( + link2 : BoolProperty( name="Link Ends / Center", description="Link the End Widths with the Center Width", default=False ) - diff = FloatProperty( + diff : FloatProperty( name="Difference", default=1, description="Difference between ends and center while linked" @@ -325,25 +325,25 @@ class add_bevelcurve(Operator, AddObjectHelper): bl_label = "Add Curve as Bevel" bl_description = ("Add bevel curve to Active Curve\n" "Needs an existing Active Curve") - bl_options = {'REGISTER', 'UNDO'} + bl_options = {'REGISTER', 'UNDO', 'PRESET'} - types = IntProperty( + types : IntProperty( name="Type", description="Type of bevel curve", default=1, min=1, max=5 ) - scale_x = FloatProperty( + scale_x : FloatProperty( name="Scale X", description="Scale on X axis", default=1.0 ) - scale_y = FloatProperty( + scale_y : FloatProperty( name="Scale Y", description="Scale on Y axis", default=1.0 ) - link = BoolProperty( + link : BoolProperty( name="Link XY", description="Link the Scale on X/Y axis", default=True @@ -364,7 +364,7 @@ class add_bevelcurve(Operator, AddObjectHelper): col.prop(self, "rotation") col = layout.column(align=True) - col.label("Settings:") + col.label(text = "Settings:") col.prop(self, "types") split = layout.split(percentage=0.95, align=True) @@ -394,7 +394,7 @@ class add_bevelcurve(Operator, AddObjectHelper): class Bevel_Taper_Curve_Menu(Menu): bl_label = "Bevel/Taper" - bl_idname = "OBJECT_MT_bevel_taper_curve_menu" + bl_idname = "VIEW3D_MT_bevel_taper_curve_menu" def draw(self, context): layout = self.layout @@ -405,16 +405,16 @@ class Bevel_Taper_Curve_Menu(Menu): def menu_funcs(self, context): if bpy.context.scene.objects.active.type == "CURVE": - self.layout.menu("OBJECT_MT_bevel_taper_curve_menu") + self.layout.menu("VIEW3D_MT_bevel_taper_curve_menu") def register(): - bpy.utils.register_module(__name__) + #bpy.utils.register_module(__name__) bpy.types.VIEW3D_MT_object.append(menu_funcs) def unregister(): - bpy.utils.unregister_module(__name__) + #bpy.utils.unregister_module(__name__) bpy.types.VIEW3D_MT_object.remove(menu_funcs) diff --git a/add_curve_ivygen.py b/add_curve_ivygen.py index 89b6abf0..efb55f98 100644 --- a/add_curve_ivygen.py +++ b/add_curve_ivygen.py @@ -21,9 +21,9 @@ bl_info = { "name": "IvyGen", "author": "testscreenings, PKHG, TrumanBlending", - "version": (0, 1, 4), - "blender": (2, 59, 0), - "location": "View3D > Tool Shelf > Create > Ivy Generator", + "version": (0, 1, 5), + "blender": (2, 80, 0), + "location": "View3D > Properties Panel > Ivy Generator", "description": "Adds generated ivy to a mesh object starting " "at the 3D cursor", "warning": "", @@ -35,29 +35,30 @@ bl_info = { import bpy from bpy.types import ( - Operator, - Panel, - PropertyGroup, - ) + Operator, + Panel, + PropertyGroup, +) from bpy.props import ( - BoolProperty, - FloatProperty, - IntProperty, - PointerProperty, - ) + BoolProperty, + FloatProperty, + IntProperty, + PointerProperty, +) +from mathutils.bvhtree import BVHTree from mathutils import ( - Vector, - Matrix, - ) + Vector, + Matrix, +) from collections import deque from math import ( - pow, cos, - pi, atan2, - ) + pow, cos, + pi, atan2, +) from random import ( - random as rand_val, - seed as rand_seed, - ) + random as rand_val, + seed as rand_seed, +) import time @@ -193,7 +194,7 @@ def createIvyGeometry(IVY, growLeaves): # Add the object and link to scene newCurve = bpy.data.objects.new("IVY_Curve", curve) - bpy.context.scene.objects.link(newCurve) + bpy.context.collection.objects.link(newCurve) if growLeaves: faceList = [[4 * i + l for l in range(4)] for i in @@ -204,9 +205,9 @@ def createIvyGeometry(IVY, growLeaves): me.from_pydata(vertList, [], faceList) me.update(calc_edges=True) ob = bpy.data.objects.new('IvyLeaf', me) - bpy.context.scene.objects.link(ob) + bpy.context.collection.objects.link(ob) - me.uv_textures.new("Leaves") + me.uv_layers.new(name="Leaves") # Set the uv texture coords # TODO, this is non-functional, default uvs are ok? @@ -292,7 +293,7 @@ class Ivy: tmpRoot.ivyNodes.append(tmpIvy) self.ivyRoots.append(tmpRoot) - def grow(self, ob): + def grow(self, ob, bvhtree): # Determine the local sizes # local_ivySize = self.ivySize # * radius # local_maxFloatLength = self.maxFloatLength # * radius @@ -319,8 +320,8 @@ class Ivy: randomVector.normalize() # Calculate the adhesion vector - adhesionVector = adhesion(prevIvy.pos, ob, - self.maxAdhesionDistance) + adhesionVector = adhesion( + prevIvy.pos, bvhtree, self.maxAdhesionDistance) # Calculate the growing vector growVector = self.ivySize * (primaryVector * self.primaryWeight + @@ -337,7 +338,7 @@ class Ivy: newPos = prevIvy.pos + growVector + gravityVector # Check for collisions with the object - climbing = collision(ob, prevIvy.pos, newPos) + climbing, newPos = collision(bvhtree, prevIvy.pos, newPos) # Update the growing vector for any collisions growVector = newPos - prevIvy.pos - gravityVector @@ -396,17 +397,13 @@ class Ivy: return -def adhesion(loc, ob, max_l): - # Get transfor vector and transformed loc - tran_mat = ob.matrix_world.inverted() - tran_loc = tran_mat * loc - +def adhesion(loc, bvhtree, max_l): # Compute the adhesion vector by finding the nearest point - nearest_result = ob.closest_point_on_mesh(tran_loc, max_l) + nearest_location, *_ = bvhtree.find_nearest(loc, max_l) adhesion_vector = Vector((0.0, 0.0, 0.0)) - if nearest_result[0]: + if nearest_location is not None: # Compute the distance to the nearest point - adhesion_vector = ob.matrix_world * nearest_result[1] - loc + adhesion_vector = nearest_location - loc distance = adhesion_vector.length # If it's less than the maximum allowed and not 0, continue if distance: @@ -417,32 +414,38 @@ def adhesion(loc, ob, max_l): return adhesion_vector -def collision(ob, pos, new_pos): +def collision(bvhtree, pos, new_pos): # Check for collision with the object climbing = False - # Transform vecs - tran_mat = ob.matrix_world.inverted() - tran_pos = tran_mat * pos - tran_new_pos = tran_mat * new_pos - tran_dir = tran_new_pos - tran_pos + corrected_new_pos = new_pos + direction = new_pos - pos - ray_result = ob.ray_cast(tran_pos, tran_dir, tran_dir.length) + hit_location, hit_normal, *_ = bvhtree.ray_cast(pos, direction, direction.length) # If there's a collision we need to check it - if ray_result[0]: + if hit_location is not None: # Check whether the collision is going into the object - if tran_dir.dot(ray_result[2]) < 0.0: - # Find projection of the point onto the plane - p0 = tran_new_pos - (tran_new_pos - - ray_result[1]).project(ray_result[2]) - # Reflect in the plane - tran_new_pos += 2 * (p0 - tran_new_pos) - new_pos *= 0 - new_pos += ob.matrix_world * tran_new_pos + if direction.dot(hit_normal) < 0.0: + reflected_direction = (new_pos - hit_location).reflect(hit_normal) + + corrected_new_pos = hit_location + reflected_direction climbing = True - return climbing + + return climbing, corrected_new_pos +def bvhtree_from_object(ob): + import bmesh + bm = bmesh.new() + + mesh = ob.to_mesh(bpy.context.depsgraph, True) + bm.from_mesh(mesh) + bm.transform(ob.matrix_world) + + bvhtree = BVHTree.FromBMesh(bm) + bpy.data.meshes.remove(mesh) + return bvhtree + def check_mesh_faces(ob): me = ob.data if len(me.polygons) > 0: @@ -457,16 +460,16 @@ class IvyGen(Operator): bl_description = "Generate Ivy on an Mesh Object" bl_options = {'REGISTER', 'UNDO'} - updateIvy = BoolProperty( - name="Update Ivy", - description="Update the Ivy location based on the cursor and Panel settings", - default=False - ) - defaultIvy = BoolProperty( - name="Default Ivy", - options={"HIDDEN", "SKIP_SAVE"}, - default=False - ) + updateIvy: BoolProperty( + name="Update Ivy", + description="Update the Ivy location based on the cursor and Panel settings", + default=False + ) + defaultIvy: BoolProperty( + name="Default Ivy", + options={"HIDDEN", "SKIP_SAVE"}, + default=False + ) @classmethod def poll(self, context): @@ -510,6 +513,7 @@ class IvyGen(Operator): # Get the selected object ob = context.active_object + bvhtree = bvhtree_from_object(ob) # Check if the mesh has at least one polygon since some functions # are expecting them in the object's data (see T51753) @@ -562,7 +566,7 @@ class IvyGen(Operator): (IVY.maxLength < maxLength) and (not checkTime or (time.time() - t < maxTime))): # Grow the ivy for this iteration - IVY.grow(ob) + IVY.grow(ob, bvhtree) # Print the proportion of ivy growth to console if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10: @@ -596,9 +600,8 @@ class CURVE_PT_IvyGenPanel(Panel): bl_label = "Ivy Generator" bl_idname = "CURVE_PT_IvyGenPanel" bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_category = "Create" - bl_context = 'objectmode' + bl_region_type = "UI" + bl_category = "View" bl_options = {"DEFAULT_CLOSED"} def draw(self, context): @@ -615,26 +618,26 @@ class CURVE_PT_IvyGenPanel(Panel): prop_def.updateIvy = True col = layout.column(align=True) - col.label("Generation Settings:") + col.label(text="Generation Settings:") col.prop(wm.ivy_gen_props, "randomSeed") col.prop(wm.ivy_gen_props, "maxTime") col = layout.column(align=True) - col.label("Size Settings:") + col.label(text="Size Settings:") col.prop(wm.ivy_gen_props, "maxIvyLength") col.prop(wm.ivy_gen_props, "ivySize") col.prop(wm.ivy_gen_props, "maxFloatLength") col.prop(wm.ivy_gen_props, "maxAdhesionDistance") col = layout.column(align=True) - col.label("Weight Settings:") + col.label(text="Weight Settings:") col.prop(wm.ivy_gen_props, "primaryWeight") col.prop(wm.ivy_gen_props, "randomWeight") col.prop(wm.ivy_gen_props, "gravityWeight") col.prop(wm.ivy_gen_props, "adhesionWeight") col = layout.column(align=True) - col.label("Branch Settings:") + col.label(text="Branch Settings:") col.prop(wm.ivy_gen_props, "branchingProbability") col.prop(wm.ivy_gen_props, "ivyBranchSize") @@ -643,124 +646,124 @@ class CURVE_PT_IvyGenPanel(Panel): if wm.ivy_gen_props.growLeaves: col = layout.column(align=True) - col.label("Leaf Settings:") + col.label(text="Leaf Settings:") col.prop(wm.ivy_gen_props, "ivyLeafSize") col.prop(wm.ivy_gen_props, "leafProbability") class IvyGenProperties(PropertyGroup): - maxIvyLength = FloatProperty( - name="Max Ivy Length", - description="Maximum ivy length in Blender Units", - default=1.0, - min=0.0, - soft_max=3.0, - subtype='DISTANCE', - unit='LENGTH' - ) - primaryWeight = FloatProperty( - name="Primary Weight", - description="Weighting given to the current direction", - default=0.5, - min=0.0, - soft_max=1.0 - ) - randomWeight = FloatProperty( - name="Random Weight", - description="Weighting given to the random direction", - default=0.2, - min=0.0, - soft_max=1.0 - ) - gravityWeight = FloatProperty( - name="Gravity Weight", - description="Weighting given to the gravity direction", - default=1.0, - min=0.0, - soft_max=1.0 - ) - adhesionWeight = FloatProperty( - name="Adhesion Weight", - description="Weighting given to the adhesion direction", - default=0.1, - min=0.0, - soft_max=1.0 - ) - branchingProbability = FloatProperty( - name="Branching Probability", - description="Probability of a new branch forming", - default=0.05, - min=0.0, - soft_max=1.0 - ) - leafProbability = FloatProperty( - name="Leaf Probability", - description="Probability of a leaf forming", - default=0.35, - min=0.0, - soft_max=1.0 - ) - ivySize = FloatProperty( - name="Ivy Size", - description="The length of an ivy segment in Blender" - " Units", - default=0.02, - min=0.0, - soft_max=1.0, - precision=3 - ) - ivyLeafSize = FloatProperty( - name="Ivy Leaf Size", - description="The size of the ivy leaves", - default=0.02, - min=0.0, - soft_max=0.5, - precision=3 - ) - ivyBranchSize = FloatProperty( - name="Ivy Branch Size", - description="The size of the ivy branches", - default=0.001, - min=0.0, - soft_max=0.1, - precision=4 - ) - maxFloatLength = FloatProperty( - name="Max Float Length", - description="The maximum distance that a branch " - "can live while floating", - default=0.5, - min=0.0, - soft_max=1.0 - ) - maxAdhesionDistance = FloatProperty( - name="Max Adhesion Length", - description="The maximum distance that a branch " - "will feel the effects of adhesion", - default=1.0, - min=0.0, - soft_max=2.0, - precision=2 - ) - randomSeed = IntProperty( - name="Random Seed", - description="The seed governing random generation", - default=0, - min=0 - ) - maxTime = FloatProperty( - name="Maximum Time", - description="The maximum time to run the generation for " - "in seconds generation (0.0 = Disabled)", - default=0.0, - min=0.0, - soft_max=10 - ) - growLeaves = BoolProperty( - name="Grow Leaves", - description="Grow leaves or not", - default=True - ) + maxIvyLength: FloatProperty( + name="Max Ivy Length", + description="Maximum ivy length in Blender Units", + default=1.0, + min=0.0, + soft_max=3.0, + subtype='DISTANCE', + unit='LENGTH' + ) + primaryWeight: FloatProperty( + name="Primary Weight", + description="Weighting given to the current direction", + default=0.5, + min=0.0, + soft_max=1.0 + ) + randomWeight: FloatProperty( + name="Random Weight", + description="Weighting given to the random direction", + default=0.2, + min=0.0, + soft_max=1.0 + ) + gravityWeight: FloatProperty( + name="Gravity Weight", + description="Weighting given to the gravity direction", + default=1.0, + min=0.0, + soft_max=1.0 + ) + adhesionWeight: FloatProperty( + name="Adhesion Weight", + description="Weighting given to the adhesion direction", + default=0.1, + min=0.0, + soft_max=1.0 + ) + branchingProbability: FloatProperty( + name="Branching Probability", + description="Probability of a new branch forming", + default=0.05, + min=0.0, + soft_max=1.0 + ) + leafProbability: FloatProperty( + name="Leaf Probability", + description="Probability of a leaf forming", + default=0.35, + min=0.0, + soft_max=1.0 + ) + ivySize: FloatProperty( + name="Ivy Size", + description="The length of an ivy segment in Blender" + " Units", + default=0.02, + min=0.0, + soft_max=1.0, + precision=3 + ) + ivyLeafSize: FloatProperty( + name="Ivy Leaf Size", + description="The size of the ivy leaves", + default=0.02, + min=0.0, + soft_max=0.5, + precision=3 + ) + ivyBranchSize: FloatProperty( + name="Ivy Branch Size", + description="The size of the ivy branches", + default=0.001, + min=0.0, + soft_max=0.1, + precision=4 + ) + maxFloatLength: FloatProperty( + name="Max Float Length", + description="The maximum distance that a branch " + "can live while floating", + default=0.5, + min=0.0, + soft_max=1.0 + ) + maxAdhesionDistance: FloatProperty( + name="Max Adhesion Length", + description="The maximum distance that a branch " + "will feel the effects of adhesion", + default=1.0, + min=0.0, + soft_max=2.0, + precision=2 + ) + randomSeed: IntProperty( + name="Random Seed", + description="The seed governing random generation", + default=0, + min=0 + ) + maxTime: FloatProperty( + name="Maximum Time", + description="The maximum time to run the generation for " + "in seconds generation (0.0 = Disabled)", + default=0.0, + min=0.0, + soft_max=10 + ) + growLeaves: BoolProperty( + name="Grow Leaves", + description="Grow leaves or not", + default=True + ) classes = ( @@ -775,8 +778,8 @@ def register(): bpy.utils.register_class(cls) bpy.types.WindowManager.ivy_gen_props = PointerProperty( - type=IvyGenProperties - ) + type=IvyGenProperties + ) def unregister(): diff --git a/add_curve_sapling/__init__.py b/add_curve_sapling/__init__.py index f49823b5..a657264a 100644 --- a/add_curve_sapling/__init__.py +++ b/add_curve_sapling/__init__.py @@ -19,9 +19,9 @@ bl_info = { "name": "Sapling Tree Gen", - "author": "Andrew Hale (TrumanBlending), Aaron Buchler", - "version": (0, 3, 3), - "blender": (2, 77, 0), + "author": "Andrew Hale (TrumanBlending), Aaron Buchler, CansecoGPC", + "version": (0, 3, 4), + "blender": (2, 80, 0), "location": "View3D > Add > Curve", "description": ("Adds a parametric tree. The method is presented by " "Jason Weber & Joseph Penn in their paper 'Creation and Rendering of " @@ -110,14 +110,7 @@ def getPresetpath(): """Support user defined scripts directory Find the first occurrence of add_curve_sapling/presets in possible script paths and return it as preset path""" - # presetpath = "" - # for p in bpy.utils.script_paths(): - # presetpath = os.path.join(p, 'addons', 'add_curve_sapling_3', 'presets') - # if os.path.exists(presetpath): - # break - # return presetpath - - # why not just do this + script_file = os.path.realpath(__file__) directory = os.path.dirname(script_file) directory = os.path.join(directory, "presets") @@ -145,7 +138,7 @@ class ExportData(Operator): bl_idname = 'sapling.exportdata' bl_label = 'Export Preset' - data = StringProperty() + data: StringProperty() def execute(self, context): # Unpack some data from the input @@ -196,7 +189,7 @@ class ImportData(Operator): bl_idname = "sapling.importdata" bl_label = "Import Preset" - filename = StringProperty() + filename: StringProperty() def execute(self, context): # Make sure the operator knows about the global variables @@ -276,49 +269,49 @@ class AddTree(Operator): def no_update_tree(self, context): self.do_update = False - do_update = BoolProperty( + do_update: BoolProperty( name='Do Update', default=True, options={'HIDDEN'} ) - chooseSet = EnumProperty( + chooseSet: EnumProperty( name='Settings', description='Choose the settings to modify', items=settings, default='0', update=no_update_tree ) - bevel = BoolProperty( + bevel: BoolProperty( name='Bevel', description='Whether the curve is beveled', default=False, update=update_tree ) - prune = BoolProperty( + prune: BoolProperty( name='Prune', description='Whether the tree is pruned', default=False, update=update_tree ) - showLeaves = BoolProperty( + showLeaves: BoolProperty( name='Show Leaves', description='Whether the leaves are shown', default=False, update=update_tree ) - useArm = BoolProperty( + useArm: BoolProperty( name='Use Armature', description='Whether the armature is generated', default=False, update=update_tree ) - seed = IntProperty( + seed: IntProperty( name='Random Seed', description='The seed of the random number generator', default=0, update=update_tree ) - handleType = IntProperty( + handleType: IntProperty( name='Handle Type', description='The type of curve handles', min=0, max=1, default=0, update=update_tree ) - levels = IntProperty( + levels: IntProperty( name='Levels', description='Number of recursive branches (Levels)', min=1, @@ -326,14 +319,14 @@ class AddTree(Operator): soft_max=4, default=3, update=update_tree ) - length = FloatVectorProperty( + length: FloatVectorProperty( name='Length', description='The relative lengths of each branch level (nLength)', min=0.000001, default=[1, 0.3, 0.6, 0.45], size=4, update=update_tree ) - lengthV = FloatVectorProperty( + lengthV: FloatVectorProperty( name='Length Variation', description='The relative length variations of each level (nLengthV)', min=0.0, @@ -341,52 +334,52 @@ class AddTree(Operator): default=[0, 0, 0, 0], size=4, update=update_tree ) - taperCrown = FloatProperty( + taperCrown: FloatProperty( name='Taper Crown', description='Shorten trunk splits toward outside of tree', min=0.0, soft_max=1.0, default=0, update=update_tree ) - branches = IntVectorProperty( + branches: IntVectorProperty( name='Branches', description='The number of branches grown at each level (nBranches)', min=0, default=[50, 30, 10, 10], size=4, update=update_tree ) - curveRes = IntVectorProperty( + curveRes: IntVectorProperty( name='Curve Resolution', description='The number of segments on each branch (nCurveRes)', min=1, default=[3, 5, 3, 1], size=4, update=update_tree ) - curve = FloatVectorProperty( + curve: FloatVectorProperty( name='Curvature', description='The angle of the end of the branch (nCurve)', default=[0, -40, -40, 0], size=4, update=update_tree ) - curveV = FloatVectorProperty( + curveV: FloatVectorProperty( name='Curvature Variation', description='Variation of the curvature (nCurveV)', default=[20, 50, 75, 0], size=4, update=update_tree ) - curveBack = FloatVectorProperty( + curveBack: FloatVectorProperty( name='Back Curvature', description='Curvature for the second half of a branch (nCurveBack)', default=[0, 0, 0, 0], size=4, update=update_tree ) - baseSplits = IntProperty( + baseSplits: IntProperty( name='Base Splits', description='Number of trunk splits at its base (nBaseSplits)', min=0, default=0, update=update_tree ) - segSplits = FloatVectorProperty( + segSplits: FloatVectorProperty( name='Segment Splits', description='Number of splits per segment (nSegSplits)', min=0, @@ -394,45 +387,45 @@ class AddTree(Operator): default=[0, 0, 0, 0], size=4, update=update_tree ) - splitByLen = BoolProperty( + splitByLen: BoolProperty( name='Split relative to length', description='Split proportional to branch length', default=False, update=update_tree ) - rMode = EnumProperty( + rMode: EnumProperty( name="", # "Branching Mode" description='Branching and Rotation Mode', items=branchmodes, default="rotate", update=update_tree ) - splitAngle = FloatVectorProperty( + splitAngle: FloatVectorProperty( name='Split Angle', description='Angle of branch splitting (nSplitAngle)', default=[0, 0, 0, 0], size=4, update=update_tree ) - splitAngleV = FloatVectorProperty( + splitAngleV: FloatVectorProperty( name='Split Angle Variation', description='Variation in the split angle (nSplitAngleV)', default=[0, 0, 0, 0], size=4, update=update_tree ) - scale = FloatProperty( + scale: FloatProperty( name='Scale', description='The tree scale (Scale)', min=0.0, default=13.0, update=update_tree) - scaleV = FloatProperty(name='Scale Variation', + scaleV: FloatProperty(name='Scale Variation', description='The variation in the tree scale (ScaleV)', default=3.0, update=update_tree ) - attractUp = FloatVectorProperty( + attractUp: FloatVectorProperty( name='Vertical Attraction', description='Branch upward attraction', default=[0, 0, 0, 0], size=4, update=update_tree ) - attractOut = FloatVectorProperty( + attractOut: FloatVectorProperty( name='Outward Attraction', description='Branch outward attraction', default=[0, 0, 0, 0], @@ -440,19 +433,19 @@ class AddTree(Operator): max=1.0, size=4, update=update_tree ) - shape = EnumProperty( + shape: EnumProperty( name='Shape', description='The overall shape of the tree (Shape)', items=shapeList3, default='7', update=update_tree ) - shapeS = EnumProperty( + shapeS: EnumProperty( name='Secondary Branches Shape', description='The shape of secondary splits', items=shapeList4, default='4', update=update_tree ) - customShape = FloatVectorProperty( + customShape: FloatVectorProperty( name='Custom Shape', description='custom shape branch length at (Base, Middle, Middle Position, Top)', size=4, @@ -460,76 +453,76 @@ class AddTree(Operator): max=1, default=[.5, 1.0, .3, .5], update=update_tree ) - branchDist = FloatProperty( + branchDist: FloatProperty( name='Branch Distribution', description='Adjust branch spacing to put more branches at the top or bottom of the tree', min=0.1, soft_max=10, default=1.0, update=update_tree ) - nrings = IntProperty( + nrings: IntProperty( name='Branch Rings', description='grow branches in rings', min=0, default=0, update=update_tree ) - baseSize = FloatProperty( + baseSize: FloatProperty( name='Trunk Height', description='Fraction of tree height with no branches (Base Size)', min=0.0, max=1.0, default=0.4, update=update_tree ) - baseSize_s = FloatProperty( + baseSize_s: FloatProperty( name='Secondary Base Size', description='Factor to decrease base size for each level', min=0.0, max=1.0, default=0.25, update=update_tree ) - splitHeight = FloatProperty( + splitHeight: FloatProperty( name='Split Height', description='Fraction of tree height with no splits', min=0.0, max=1.0, default=0.2, update=update_tree ) - splitBias = FloatProperty( + splitBias: FloatProperty( name='splitBias', description='Put more splits at the top or bottom of the tree', soft_min=-2.0, soft_max=2.0, default=0.0, update=update_tree ) - ratio = FloatProperty( + ratio: FloatProperty( name='Ratio', description='Base radius size (Ratio)', min=0.0, default=0.015, update=update_tree ) - minRadius = FloatProperty( + minRadius: FloatProperty( name='Minimum Radius', description='Minimum branch Radius', min=0.0, default=0.0, update=update_tree ) - closeTip = BoolProperty( + closeTip: BoolProperty( name='Close Tip', description='Set radius at branch tips to zero', default=False, update=update_tree ) - rootFlare = FloatProperty( + rootFlare: FloatProperty( name='Root Flare', description='Root radius factor', min=1.0, default=1.0, update=update_tree ) - autoTaper = BoolProperty( + autoTaper: BoolProperty( name='Auto Taper', description='Calculate taper automatically based on branch lengths', default=True, update=update_tree ) - taper = FloatVectorProperty( + taper: FloatVectorProperty( name='Taper', description='The fraction of tapering on each branch (nTaper)', min=0.0, @@ -537,7 +530,7 @@ class AddTree(Operator): default=[1, 1, 1, 1], size=4, update=update_tree ) - radiusTweak = FloatVectorProperty( + radiusTweak: FloatVectorProperty( name='Tweak Radius', description='multiply radius by this factor', min=0.0, @@ -545,164 +538,164 @@ class AddTree(Operator): default=[1, 1, 1, 1], size=4, update=update_tree ) - ratioPower = FloatProperty( + ratioPower: FloatProperty( name='Branch Radius Ratio', description=('Power which defines the radius of a branch compared to ' 'the radius of the branch it grew from (RatioPower)'), min=0.0, default=1.2, update=update_tree ) - downAngle = FloatVectorProperty( + downAngle: FloatVectorProperty( name='Down Angle', description=('The angle between a new branch and the one it grew ' 'from (nDownAngle)'), default=[90, 60, 45, 45], size=4, update=update_tree ) - downAngleV = FloatVectorProperty( + downAngleV: FloatVectorProperty( name='Down Angle Variation', description="Angle to decrease Down Angle by towards end of parent branch " "(negative values add random variation)", default=[0, -50, 10, 10], size=4, update=update_tree ) - useOldDownAngle = BoolProperty( + useOldDownAngle: BoolProperty( name='Use old down angle variation', default=False, update=update_tree ) - useParentAngle = BoolProperty( + useParentAngle: BoolProperty( name='Use parent angle', description='(first level) Rotate branch to match parent branch', default=True, update=update_tree ) - rotate = FloatVectorProperty( + rotate: FloatVectorProperty( name='Rotate Angle', description="The angle of a new branch around the one it grew from " "(negative values rotate opposite from the previous)", default=[137.5, 137.5, 137.5, 137.5], size=4, update=update_tree ) - rotateV = FloatVectorProperty( + rotateV: FloatVectorProperty( name='Rotate Angle Variation', description='Variation in the rotate angle (nRotateV)', default=[0, 0, 0, 0], size=4, update=update_tree ) - scale0 = FloatProperty( + scale0: FloatProperty( name='Radius Scale', description='The scale of the trunk radius (0Scale)', min=0.0, default=1.0, update=update_tree ) - scaleV0 = FloatProperty( + scaleV0: FloatProperty( name='Radius Scale Variation', description='Variation in the radius scale (0ScaleV)', min=0.0, max=1.0, default=0.2, update=update_tree ) - pruneWidth = FloatProperty( + pruneWidth: FloatProperty( name='Prune Width', description='The width of the envelope (PruneWidth)', min=0.0, default=0.4, update=update_tree ) - pruneBase = FloatProperty( + pruneBase: FloatProperty( name='Prune Base Height', description='The height of the base of the envelope, bound by trunk height', min=0.0, max=1.0, default=0.3, update=update_tree ) - pruneWidthPeak = FloatProperty( + pruneWidthPeak: FloatProperty( name='Prune Width Peak', description=("Fraction of envelope height where the maximum width " "occurs (PruneWidthPeak)"), min=0.0, default=0.6, update=update_tree ) - prunePowerHigh = FloatProperty( + prunePowerHigh: FloatProperty( name='Prune Power High', description=('Power which determines the shape of the upper portion ' 'of the envelope (PrunePowerHigh)'), default=0.5, update=update_tree ) - prunePowerLow = FloatProperty( + prunePowerLow: FloatProperty( name='Prune Power Low', description=('Power which determines the shape of the lower portion ' 'of the envelope (PrunePowerLow)'), default=0.001, update=update_tree ) - pruneRatio = FloatProperty( + pruneRatio: FloatProperty( name='Prune Ratio', description='Proportion of pruned length (PruneRatio)', min=0.0, max=1.0, default=1.0, update=update_tree ) - leaves = IntProperty( + leaves: IntProperty( name='Leaves', description="Maximum number of leaves per branch (negative values grow " "leaves from branch tip (palmate compound leaves))", default=25, update=update_tree ) - leafDownAngle = FloatProperty( + leafDownAngle: FloatProperty( name='Leaf Down Angle', description='The angle between a new leaf and the branch it grew from', default=45, update=update_leaves ) - leafDownAngleV = FloatProperty( + leafDownAngleV: FloatProperty( name='Leaf Down Angle Variation', description="Angle to decrease Down Angle by towards end of parent branch " "(negative values add random variation)", default=10, update=update_tree ) - leafRotate = FloatProperty( + leafRotate: FloatProperty( name='Leaf Rotate Angle', description="The angle of a new leaf around the one it grew from " "(negative values rotate opposite from previous)", default=137.5, update=update_tree ) - leafRotateV = FloatProperty( + leafRotateV: FloatProperty( name='Leaf Rotate Angle Variation', description='Variation in the rotate angle', default=0.0, update=update_leaves ) - leafScale = FloatProperty( + leafScale: FloatProperty( name='Leaf Scale', description='The scaling applied to the whole leaf (LeafScale)', min=0.0, default=0.17, update=update_leaves ) - leafScaleX = FloatProperty( + leafScaleX: FloatProperty( name='Leaf Scale X', description=('The scaling applied to the x direction of the leaf ' '(LeafScaleX)'), min=0.0, default=1.0, update=update_leaves ) - leafScaleT = FloatProperty( + leafScaleT: FloatProperty( name='Leaf Scale Taper', description='scale leaves toward the tip or base of the patent branch', min=-1.0, max=1.0, default=0.0, update=update_leaves ) - leafScaleV = FloatProperty( + leafScaleV: FloatProperty( name='Leaf Scale Variation', description='randomize leaf scale', min=0.0, max=1.0, default=0.0, update=update_leaves ) - leafShape = EnumProperty( + leafShape: EnumProperty( name='Leaf Shape', description='The shape of the leaves', items=(('hex', 'Hexagonal', '0'), ('rect', 'Rectangular', '1'), ('dFace', 'DupliFaces', '2'), ('dVert', 'DupliVerts', '3')), default='hex', update=update_leaves ) - leafDupliObj = EnumProperty( + leafDupliObj: EnumProperty( name='Leaf Object', description='Object to use for leaf instancing if Leaf Shape is DupliFaces or DupliVerts', items=objectList, @@ -717,64 +710,64 @@ class AddTree(Operator): default=0.0, update=update_leaves ) """ - leafangle = FloatProperty( + leafangle: FloatProperty( name='Leaf Angle', description='Leaf vertical attraction', default=0.0, update=update_leaves ) - horzLeaves = BoolProperty( + horzLeaves: BoolProperty( name='Horizontal leaves', description='Leaves face upwards', default=True, update=update_leaves ) - leafDist = EnumProperty( + leafDist: EnumProperty( name='Leaf Distribution', description='The way leaves are distributed on branches', items=shapeList4, default='6', update=update_tree ) - bevelRes = IntProperty( + bevelRes: IntProperty( name='Bevel Resolution', description='The bevel resolution of the curves', min=0, max=32, default=0, update=update_tree ) - resU = IntProperty( + resU: IntProperty( name='Curve Resolution', description='The resolution along the curves', min=1, default=4, update=update_tree ) - handleType = EnumProperty( + handleType: EnumProperty( name='Handle Type', description='The type of handles used in the spline', items=handleList, default='0', update=update_tree ) - armAnim = BoolProperty( + armAnim: BoolProperty( name='Armature Animation', description='Whether animation is added to the armature', default=False, update=update_tree ) - previewArm = BoolProperty( + previewArm: BoolProperty( name='Fast Preview', description='Disable armature modifier, hide tree, and set bone display to wire, for fast playback', # Disable skin modifier and hide tree and armature, for fast playback default=False, update=update_tree ) - leafAnim = BoolProperty( + leafAnim: BoolProperty( name='Leaf Animation', description='Whether animation is added to the leaves', default=False, update=update_tree ) - frameRate = FloatProperty( + frameRate: FloatProperty( name='Animation Speed', description=('Adjust speed of animation, relative to scene frame rate'), min=0.001, default=1, update=update_tree ) - loopFrames = IntProperty( + loopFrames: IntProperty( name='Loop Frames', description='Number of frames to make the animation loop for, zero is disabled', min=0, @@ -792,66 +785,66 @@ class AddTree(Operator): default=0.0, update=update_tree ) """ - wind = FloatProperty( + wind: FloatProperty( name='Overall Wind Strength', description='The intensity of the wind to apply to the armature', default=1.0, update=update_tree ) - gust = FloatProperty( + gust: FloatProperty( name='Wind Gust Strength', description='The amount of directional movement, (from the positive Y direction)', default=1.0, update=update_tree ) - gustF = FloatProperty( + gustF: FloatProperty( name='Wind Gust Fequency', description='The Frequency of directional movement', default=0.075, update=update_tree ) - af1 = FloatProperty( + af1: FloatProperty( name='Amplitude', description='Multiplier for noise amplitude', default=1.0, update=update_tree ) - af2 = FloatProperty( + af2: FloatProperty( name='Frequency', description='Multiplier for noise fequency', default=1.0, update=update_tree ) - af3 = FloatProperty( + af3: FloatProperty( name='Randomness', description='Random offset in noise', default=4.0, update=update_tree ) - makeMesh = BoolProperty( + makeMesh: BoolProperty( name='Make Mesh', description='Convert curves to mesh, uses skin modifier, enables armature simplification', default=False, update=update_tree ) - armLevels = IntProperty( + armLevels: IntProperty( name='Armature Levels', description='Number of branching levels to make bones for, 0 is all levels', min=0, default=2, update=update_tree ) - boneStep = IntVectorProperty( + boneStep: IntVectorProperty( name='Bone Length', description='Number of stem segments per bone', min=1, default=[1, 1, 1, 1], size=4, update=update_tree ) - presetName = StringProperty( + presetName: StringProperty( name='Preset Name', description='The name of the preset to be saved', default='', subtype='FILE_NAME', update=no_update_tree ) - limitImport = BoolProperty( + limitImport: BoolProperty( name='Limit Import', description='Limited imported tree to 2 levels & no leaves for speed', default=True, update=no_update_tree ) - overwrite = BoolProperty( + overwrite: BoolProperty( name='Overwrite', description='When checked, overwrite existing preset files when saving', default=False, update=no_update_tree @@ -881,7 +874,7 @@ class AddTree(Operator): if self.chooseSet == '0': box = layout.box() - box.label("Geometry:") + box.label(text="Geometry:") box.prop(self, 'bevel') row = box.row() @@ -900,7 +893,7 @@ class AddTree(Operator): box.prop(self, 'nrings') box.prop(self, 'seed') - box.label("Tree Scale:") + box.label(text="Tree Scale:") row = box.row() row.prop(self, 'scale') row.prop(self, 'scaleV') @@ -927,7 +920,7 @@ class AddTree(Operator): # Send the data dict and the file name to the exporter row.operator('sapling.exportdata').data = repr([repr(data), self.presetName, self.overwrite]) row = box.row() - row.label(" ") + row.label(text=" ") row.prop(self, 'overwrite') row = box.row() row.menu('SAPLING_MT_preset', text='Load Preset') @@ -935,7 +928,7 @@ class AddTree(Operator): elif self.chooseSet == '1': box = layout.box() - box.label("Branch Radius:") + box.label(text="Branch Radius:") row = box.row() row.prop(self, 'bevel') @@ -961,7 +954,7 @@ class AddTree(Operator): elif self.chooseSet == '2': box = layout.box() - box.label("Branch Splitting:") + box.label(text="Branch Splitting:") box.prop(self, 'levels') box.prop(self, 'baseSplits') row = box.row() @@ -984,14 +977,14 @@ class AddTree(Operator): col.prop(self, 'splitAngleV') col.prop(self, 'rotateV') - col.label("Branching Mode:") + col.label(text="Branching Mode:") col.prop(self, 'rMode') box.column().prop(self, 'curveRes') elif self.chooseSet == '3': box = layout.box() - box.label("Branch Growth:") + box.label(text="Branch Growth:") box.prop(self, 'taperCrown') @@ -1014,7 +1007,7 @@ class AddTree(Operator): elif self.chooseSet == '4': box = layout.box() - box.label("Prune:") + box.label(text="Prune:") box.prop(self, 'prune') box.prop(self, 'pruneRatio') row = box.row() @@ -1028,14 +1021,14 @@ class AddTree(Operator): elif self.chooseSet == '5': box = layout.box() - box.label("Leaves:") + box.label(text="Leaves:") box.prop(self, 'showLeaves') box.prop(self, 'leafShape') box.prop(self, 'leafDupliObj') box.prop(self, 'leaves') box.prop(self, 'leafDist') - box.label("") + box.label(text="") row = box.row() row.prop(self, 'leafDownAngle') row.prop(self, 'leafDownAngleV') @@ -1043,7 +1036,7 @@ class AddTree(Operator): row = box.row() row.prop(self, 'leafRotate') row.prop(self, 'leafRotateV') - box.label("") + box.label(text="") row = box.row() row.prop(self, 'leafScale') @@ -1056,22 +1049,22 @@ class AddTree(Operator): box.prop(self, 'horzLeaves') box.prop(self, 'leafangle') - # box.label(" ") + # box.label(text=" ") # box.prop(self, 'bend') elif self.chooseSet == '6': box = layout.box() - box.label("Armature:") + box.label(text="Armature:") row = box.row() row.prop(self, 'useArm') box.prop(self, 'makeMesh') - box.label("Armature Simplification:") + box.label(text="Armature Simplification:") box.prop(self, 'armLevels') box.prop(self, 'boneStep') elif self.chooseSet == '7': box = layout.box() - box.label("Finalize All Other Settings First!") + box.label(text="Finalize All Other Settings First!") box.prop(self, 'armAnim') box.prop(self, 'leafAnim') box.prop(self, 'previewArm') @@ -1082,13 +1075,13 @@ class AddTree(Operator): # row.prop(self, 'windSpeed') # row.prop(self, 'windGust') - box.label('Wind Settings:') + box.label(text='Wind Settings:') box.prop(self, 'wind') row = box.row() row.prop(self, 'gust') row.prop(self, 'gustF') - box.label('Leaf Wind Settings:') + box.label(text='Leaf Wind Settings:') box.prop(self, 'af1') box.prop(self, 'af2') box.prop(self, 'af3') @@ -1097,7 +1090,7 @@ class AddTree(Operator): # Ensure the use of the global variables global settings, useSet start_time = time.time() - # bpy.ops.ImportData.filename = "quaking_aspen" + # If we need to set the properties from a preset then do it here if useSet: for a, b in settings.items(): @@ -1115,24 +1108,32 @@ class AddTree(Operator): return {'FINISHED'} def invoke(self, context, event): - bpy.ops.sapling.importdata(filename="quaking_aspen.py") + bpy.ops.sapling.importdata(filename="callistemon.py") return self.execute(context) def menu_func(self, context): self.layout.operator(AddTree.bl_idname, text="Sapling Tree Gen", icon='CURVE_DATA') +classes = ( + AddTree, + PresetMenu, + ImportData, + ExportData, +) def register(): - bpy.utils.register_module(__name__) - - bpy.types.INFO_MT_curve_add.append(menu_func) + from bpy.utils import register_class + for cls in classes: + register_class(cls) + bpy.types.VIEW3D_MT_curve_add.append(menu_func) def unregister(): - bpy.utils.unregister_module(__name__) - - bpy.types.INFO_MT_curve_add.remove(menu_func) + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + bpy.types.VIEW3D_MT_curve_add.remove(menu_func) if __name__ == "__main__": diff --git a/add_curve_sapling/utils.py b/add_curve_sapling/utils.py index 87ae57fe..66ee91f6 100644 --- a/add_curve_sapling/utils.py +++ b/add_curve_sapling/utils.py @@ -19,6 +19,7 @@ import bpy + import time import copy @@ -489,7 +490,7 @@ def growSpline(n, stem, numSplit, splitAng, splitAngV, splineList, end_co = stem.p.co.copy() # Add the new point and adjust its coords, handles and radius - newSpline.bezier_points.add() + newSpline.bezier_points.add(1) newPoint = newSpline.bezier_points[-1] (newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (end_co + dirVec, hType, hType) newPoint.radius = ( @@ -561,7 +562,7 @@ def growSpline(n, stem, numSplit, splitAng, splitAngV, splineList, # Get the end point position end_co = stem.p.co.copy() - stem.spline.bezier_points.add() + stem.spline.bezier_points.add(1) newPoint = stem.spline.bezier_points[-1] (newPoint.co, newPoint.handle_left_type, newPoint.handle_right_type) = (end_co + dirVec, hType, hType) newPoint.radius = stem.radS * (1 - (stem.seg + 1) / stem.segMax) + \ @@ -730,19 +731,19 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi leafAnim, loopFrames, previewArm, armLevels, makeMesh, boneStep): arm = bpy.data.armatures.new('tree') armOb = bpy.data.objects.new('treeArm', arm) - bpy.context.scene.objects.link(armOb) + bpy.context.scene.collection.objects.link(armOb) # Create a new action to store all animation newAction = bpy.data.actions.new(name='windAction') armOb.animation_data_create() armOb.animation_data.action = newAction - arm.draw_type = 'STICK' + arm.display_type = 'STICK' arm.use_deform_delay = True # Add the armature modifier to the curve armMod = treeOb.modifiers.new('windSway', 'ARMATURE') if previewArm: armMod.show_viewport = False - arm.draw_type = 'WIRE' - treeOb.hide = True + arm.display_type = 'WIRE' + treeOb.hide_viewport = True armMod.use_apply_on_spline = True armMod.object = armOb armMod.use_bone_envelopes = True @@ -753,15 +754,16 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi armMod.object = armOb armMod.use_bone_envelopes = False armMod.use_vertex_groups = True + # Make sure all objects are deselected (may not be required?) for ob in bpy.data.objects: - ob.select = False + ob.select_set(state=False) fps = bpy.context.scene.render.fps animSpeed = (24 / fps) * frameRate # Set the armature as active and go to edit mode to add bones - bpy.context.scene.objects.active = armOb + bpy.context.view_layer.objects.active = armOb bpy.ops.object.mode_set(mode='EDIT') # For all the splines in the curve we need to add bones at each bezier point for i, parBone in enumerate(splineToBone): @@ -821,9 +823,9 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi # If there are leaves then we need a new vertex group so they will attach to the bone if not leafAnim: if (len(levelCount) > 1) and (i >= levelCount[-2]) and leafObj: - leafObj.vertex_groups.new(boneName) + leafObj.vertex_groups.new(name=boneName) elif (len(levelCount) == 1) and leafObj: - leafObj.vertex_groups.new(boneName) + leafObj.vertex_groups.new(name=boneName) """ # If this is first point of the spline then it must be parented to the level above it if n == 0: @@ -873,10 +875,10 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi # Add new fcurves for each sway as well as the modifiers swayX = armOb.animation_data.action.fcurves.new( - 'pose.bones["' + boneName + '"].rotation_euler', 0 + 'pose.bones["' + boneName + '"].rotation_euler', index=0 ) swayY = armOb.animation_data.action.fcurves.new( - 'pose.bones["' + boneName + '"].rotation_euler', 2 + 'pose.bones["' + boneName + '"].rotation_euler', index=2 ) swayXMod1 = swayX.modifiers.new(type='FNGENERATOR') swayXMod2 = swayX.modifiers.new(type='FNGENERATOR') @@ -955,14 +957,14 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi # Add new fcurves for each sway as well as the modifiers swayX = armOb.animation_data.action.fcurves.new( - 'pose.bones["' + bname + '"].rotation_euler', 0 + 'pose.bones["' + bname + '"].rotation_euler', index=0 ) swayY = armOb.animation_data.action.fcurves.new( - 'pose.bones["' + bname + '"].rotation_euler', 2 + 'pose.bones["' + bname + '"].rotation_euler', index=2 ) # Add keyframe so noise works - swayX.keyframe_points.add() - swayY.keyframe_points.add() + swayX.keyframe_points.add(1) + swayY.keyframe_points.add(1) swayX.keyframe_points[0].co = (0, 0) swayY.keyframe_points[0].co = (0, 0) @@ -997,7 +999,7 @@ def create_armature(armAnim, leafP, cu, frameRate, leafMesh, leafObj, leafVertSi ) for group in vertexGroups: - leafObj.vertex_groups.new(group) + leafObj.vertex_groups.new(name=group) leafObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD') # Now we need the rotation mode to be 'XYZ' to ensure correct rotation @@ -1257,7 +1259,7 @@ def fabricate_stems(addsplinetobone, addstem, baseSize, branches, childP, cu, cu def perform_pruning(baseSize, baseSplits, childP, cu, currentMax, currentMin, currentScale, curve, - curveBack, curveRes, deleteSpline, forceSprout, handles, n, oldMax, orginalSplineToBone, + curveBack, curveRes, deleteSpline, forceSprout, handles, n, oldMax, originalSplineToBone, originalCo, originalCurv, originalCurvV, originalHandleL, originalHandleR, originalLength, originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio, pruneWidth, pruneBase, pruneWidthPeak, randState, ratio, scaleVal, segSplits, splineToBone, splitAngle, splitAngleV, @@ -1291,7 +1293,7 @@ def perform_pruning(baseSize, baseSplits, childP, cu, currentMax, currentMin, cu st.seg = originalSeg st.p = newPoint newPoint.radius = st.radS - splineToBone = orginalSplineToBone + splineToBone = originalSplineToBone # Initialise the spline list for those contained in the current level of branching splineList = [st] @@ -1594,12 +1596,12 @@ def addTree(props): handles = 'VECTOR' for ob in bpy.data.objects: - ob.select = False + ob.select_set(state=False) # Initialise the tree object and curve and adjust the settings cu = bpy.data.curves.new('tree', 'CURVE') treeOb = bpy.data.objects.new('tree', cu) - bpy.context.scene.objects.link(treeOb) + bpy.context.scene.collection.objects.link(treeOb) # treeOb.location=bpy.context.scene.cursor_location attractUp @@ -1621,14 +1623,14 @@ def addTree(props): enCu = bpy.data.curves.new('envelope', 'CURVE') enOb = bpy.data.objects.new('envelope', enCu) enOb.parent = treeOb - bpy.context.scene.objects.link(enOb) + bpy.context.scene.collection.objects.link(enOb) newSpline = enCu.splines.new('BEZIER') newPoint = newSpline.bezier_points[-1] newPoint.co = Vector((0, 0, scaleVal)) (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle) # Set the coordinates by varying the z value, envelope will be aligned to the x-axis for c in range(enNum): - newSpline.bezier_points.add() + newSpline.bezier_points.add(1) newPoint = newSpline.bezier_points[-1] ratioVal = (c + 1) / (enNum) zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal @@ -1646,7 +1648,7 @@ def addTree(props): (newPoint.handle_right_type, newPoint.handle_left_type) = (enHandle, enHandle) # Create a second envelope but this time on the y-axis for c in range(enNum): - newSpline.bezier_points.add() + newSpline.bezier_points.add(1) newPoint = newSpline.bezier_points[-1] ratioVal = (c + 1) / (enNum) zVal = scaleVal - scaleVal * (1 - pruneBase) * ratioVal @@ -1721,14 +1723,14 @@ def addTree(props): currentScale = 1.0 oldMax = 1.0 deleteSpline = False - orginalSplineToBone = copy.copy(splineToBone) + originalSplineToBone = copy.copy(splineToBone) forceSprout = False # Now do the iterative pruning, this uses a binary search and halts once the difference # between upper and lower bounds of the search are less than 0.005 ratio, splineToBone = perform_pruning( baseSize, baseSplits, childP, cu, currentMax, currentMin, currentScale, curve, curveBack, curveRes, deleteSpline, forceSprout, - handles, n, oldMax, orginalSplineToBone, originalCo, originalCurv, + handles, n, oldMax, originalSplineToBone, originalCo, originalCurv, originalCurvV, originalHandleL, originalHandleR, originalLength, originalSeg, prune, prunePowerHigh, prunePowerLow, pruneRatio, pruneWidth, pruneBase, pruneWidthPeak, randState, ratio, scaleVal, @@ -1787,7 +1789,7 @@ def addTree(props): # edges are currently added by validating the mesh which isn't great leafMesh = bpy.data.meshes.new('leaves') leafObj = bpy.data.objects.new('leaves', leafMesh) - bpy.context.scene.objects.link(leafObj) + bpy.context.scene.collection.objects.link(leafObj) leafObj.parent = treeOb leafMesh.from_pydata(leafVerts, (), leafFaces) @@ -1797,17 +1799,17 @@ def addTree(props): # enable duplication if leafShape == 'dFace': - leafObj.dupli_type = "FACES" - leafObj.use_dupli_faces_scale = True - leafObj.dupli_faces_scale = 10.0 + leafObj.instance_type = "FACES" + leafObj.use_instance_faces_scale = True + leafObj.instance_faces_scale = 10.0 try: if leafDupliObj not in "NONE": bpy.data.objects[leafDupliObj].parent = leafObj except KeyError: pass elif leafShape == 'dVert': - leafObj.dupli_type = "VERTS" - leafObj.use_dupli_vertices_rotation = True + leafObj.instance_type = "VERTS" + leafObj.use_instance_vertices_rotation = True try: if leafDupliObj not in "NONE": bpy.data.objects[leafDupliObj].parent = leafObj @@ -1816,7 +1818,7 @@ def addTree(props): # add leaf UVs if leafShape == 'rect': - leafMesh.uv_textures.new("leafUV") + leafMesh.uv_layers.new(name='leafUV') uvlayer = leafMesh.uv_layers.active.data u1 = .5 * (1 - leafScaleX) @@ -1829,7 +1831,7 @@ def addTree(props): uvlayer[i * 4 + 3].uv = Vector((u1, 0)) elif leafShape == 'hex': - leafMesh.uv_textures.new("leafUV") + leafMesh.uv_layers.new(name='leafUV') uvlayer = leafMesh.uv_layers.active.data u1 = .5 * (1 - leafScaleX) @@ -1877,7 +1879,7 @@ def addTree(props): treeMesh = bpy.data.meshes.new('treemesh') treeObj = bpy.data.objects.new('treemesh', treeMesh) - bpy.context.scene.objects.link(treeObj) + bpy.context.scene.collection.objects.link(treeObj) treeVerts = [] treeEdges = [] @@ -1988,15 +1990,15 @@ def addTree(props): treeMesh.from_pydata(treeVerts, treeEdges, ()) for group in vertexGroups: - treeObj.vertex_groups.new(group) + treeObj.vertex_groups.new(name=group) treeObj.vertex_groups[group].add(vertexGroups[group], 1.0, 'ADD') # add armature if useArm: armMod = treeObj.modifiers.new('windSway', 'ARMATURE') if previewArm: - bpy.data.objects['treeArm'].hide = True - bpy.data.armatures['tree'].draw_type = 'STICK' + bpy.data.objects['treeArm'].hide_viewport = True + bpy.data.armatures['tree'].display_type = 'STICK' armMod.object = bpy.data.objects['treeArm'] armMod.use_bone_envelopes = False armMod.use_vertex_groups = True diff --git a/add_mesh_BoltFactory/Boltfactory.py b/add_mesh_BoltFactory/Boltfactory.py index 25d90706..0b405f3c 100644 --- a/add_mesh_BoltFactory/Boltfactory.py +++ b/add_mesh_BoltFactory/Boltfactory.py @@ -330,3 +330,29 @@ class add_mesh_bolt(Operator, AddObjectHelper): self.execute(context) return {'FINISHED'} + +# Register: +def menu_func_bolt(self, context): + self.layout.operator( + add_mesh_bolt.bl_idname, + text="Bolt", + icon="MOD_SCREW") + +classes = ( + add_mesh_bolt, +) + + + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + bpy.types.VIEW3D_MT_mesh_add.append(menu_func_bolt) + + +def unregister(): + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + bpy.types.VIEW3D_MT_mesh_add.remove(menu_func_bolt)
\ No newline at end of file diff --git a/add_mesh_BoltFactory/__init__.py b/add_mesh_BoltFactory/__init__.py index 46d250de..383ddc36 100644 --- a/add_mesh_BoltFactory/__init__.py +++ b/add_mesh_BoltFactory/__init__.py @@ -19,8 +19,8 @@ bl_info = { "name": "BoltFactory", "author": "Aaron Keith", - "version": (0, 3, 4), - "blender": (2, 78, 0), + "version": (0, 4, 0), + "blender": (2, 80, 0), "location": "View3D > Add > Mesh", "description": "Add a bolt or nut", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" @@ -42,22 +42,15 @@ import bpy # ### REGISTER ### -def add_mesh_bolt_button(self, context): - self.layout.operator(Boltfactory.add_mesh_bolt.bl_idname, text="Bolt", icon="MOD_SCREW") def register(): - bpy.utils.register_module(__name__) - - bpy.types.INFO_MT_mesh_add.append(add_mesh_bolt_button) - # bpy.types.VIEW3D_PT_tools_objectmode.prepend(add_mesh_bolt_button) # just for testing + Boltfactory.register() + def unregister(): - bpy.utils.unregister_module(__name__) - - bpy.types.INFO_MT_mesh_add.remove(add_mesh_bolt_button) - # bpy.types.VIEW3D_PT_tools_objectmode.remove(add_mesh_bolt_button) # just for testing + Boltfactory.unregister() if __name__ == "__main__": diff --git a/add_mesh_BoltFactory/createMesh.py b/add_mesh_BoltFactory/createMesh.py index c59c9939..71f22bf7 100644 --- a/add_mesh_BoltFactory/createMesh.py +++ b/add_mesh_BoltFactory/createMesh.py @@ -169,7 +169,7 @@ def Get_Phillips_Bit_Height(Bit_Dia): # Returns a list of verts rotated by the given matrix. Used by SpinDup def Rot_Mesh(verts, matrix): from mathutils import Vector - return [(matrix * Vector(v))[:] for v in verts] + return [(matrix @ Vector(v))[:] for v in verts] # Returns a list of faces that has there index incremented by offset diff --git a/add_mesh_extra_objects/__init__.py b/add_mesh_extra_objects/__init__.py index ecc023df..1197738c 100644 --- a/add_mesh_extra_objects/__init__.py +++ b/add_mesh_extra_objects/__init__.py @@ -107,9 +107,9 @@ from bpy.props import ( ) -class INFO_MT_mesh_vert_add(Menu): +class VIEW3D_MT_mesh_vert_add(Menu): # Define the "Single Vert" menu - bl_idname = "INFO_MT_mesh_vert_add" + bl_idname = "VIEW3D_MT_mesh_vert_add" bl_label = "Single Vert" def draw(self, context): @@ -126,9 +126,9 @@ class INFO_MT_mesh_vert_add(Menu): text="Object Origin Mirrored") -class INFO_MT_mesh_gears_add(Menu): +class VIEW3D_MT_mesh_gears_add(Menu): # Define the "Gears" menu - bl_idname = "INFO_MT_mesh_gears_add" + bl_idname = "VIEW3D_MT_mesh_gears_add" bl_label = "Gears" def draw(self, context): @@ -140,9 +140,9 @@ class INFO_MT_mesh_gears_add(Menu): text="Worm") -class INFO_MT_mesh_diamonds_add(Menu): +class VIEW3D_MT_mesh_diamonds_add(Menu): # Define the "Diamonds" menu - bl_idname = "INFO_MT_mesh_diamonds_add" + bl_idname = "VIEW3D_MT_mesh_diamonds_add" bl_label = "Diamonds" def draw(self, context): @@ -156,9 +156,9 @@ class INFO_MT_mesh_diamonds_add(Menu): text="Gem") -class INFO_MT_mesh_math_add(Menu): +class VIEW3D_MT_mesh_math_add(Menu): # Define the "Math Function" menu - bl_idname = "INFO_MT_mesh_math_add" + bl_idname = "VIEW3D_MT_mesh_math_add" bl_label = "Math Functions" def draw(self, context): @@ -172,29 +172,29 @@ class INFO_MT_mesh_math_add(Menu): self.layout.operator("mesh.make_triangle", icon="MESH_DATA") -class INFO_MT_mesh_mech(Menu): +class VIEW3D_MT_mesh_mech(Menu): # Define the "Math Function" menu - bl_idname = "INFO_MT_mesh_mech_add" + bl_idname = "VIEW3D_MT_mesh_mech_add" bl_label = "Mechanical" def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - layout.menu("INFO_MT_mesh_pipe_joints_add", + layout.menu("VIEW3D_MT_mesh_pipe_joints_add", text="Pipe Joints", icon="SNAP_PEEL_OBJECT") - layout.menu("INFO_MT_mesh_gears_add", + layout.menu("VIEW3D_MT_mesh_gears_add", text="Gears", icon="SCRIPTWIN") -class INFO_MT_mesh_extras_add(Menu): +class VIEW3D_MT_mesh_extras_add(Menu): # Define the "Extra Objects" menu - bl_idname = "INFO_MT_mesh_extras_add" + bl_idname = "VIEW3D_MT_mesh_extras_add" bl_label = "Extras" def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - layout.menu("INFO_MT_mesh_diamonds_add", text="Diamonds", + layout.menu("VIEW3D_MT_mesh_diamonds_add", text="Diamonds", icon="PMARKER_SEL") layout.separator() layout.operator("mesh.add_beam", @@ -214,9 +214,9 @@ class INFO_MT_mesh_extras_add(Menu): text="Menger Sponge") -class INFO_MT_mesh_torus_add(Menu): +class VIEW3D_MT_mesh_torus_add(Menu): # Define the "Torus Objects" menu - bl_idname = "INFO_MT_mesh_torus_add" + bl_idname = "VIEW3D_MT_mesh_torus_add" bl_label = "Torus Objects" def draw(self, context): @@ -230,9 +230,9 @@ class INFO_MT_mesh_torus_add(Menu): text="Torus Knot") -class INFO_MT_mesh_pipe_joints_add(Menu): +class VIEW3D_MT_mesh_pipe_joints_add(Menu): # Define the "Pipe Joints" menu - bl_idname = "INFO_MT_mesh_pipe_joints_add" + bl_idname = "VIEW3D_MT_mesh_pipe_joints_add" bl_label = "Pipe Joints" def draw(self, context): @@ -355,15 +355,15 @@ def menu_func(self, context): lay_out.operator_context = 'INVOKE_REGION_WIN' lay_out.separator() - lay_out.menu("INFO_MT_mesh_vert_add", + lay_out.menu("VIEW3D_MT_mesh_vert_add", text="Single Vert", icon="LAYER_ACTIVE") lay_out.operator("mesh.primitive_round_cube_add", text="Round Cube", icon="MOD_SUBSURF") - lay_out.menu("INFO_MT_mesh_math_add", + lay_out.menu("VIEW3D_MT_mesh_math_add", text="Math Function", icon="PACKAGE") - lay_out.menu("INFO_MT_mesh_mech_add", + lay_out.menu("VIEW3D_MT_mesh_mech_add", text="Mechanical", icon="SCRIPTWIN") - lay_out.menu("INFO_MT_mesh_torus_add", + lay_out.menu("VIEW3D_MT_mesh_torus_add", text="Torus Objects", icon="MESH_TORUS") lay_out.separator() lay_out.operator("mesh.generate_geodesic_dome", @@ -371,7 +371,7 @@ def menu_func(self, context): lay_out.operator("discombobulate.ops", text="Discombobulator", icon="RETOPO") lay_out.separator() - lay_out.menu("INFO_MT_mesh_extras_add", + lay_out.menu("VIEW3D_MT_mesh_extras_add", text="Extras", icon="MESH_DATA") lay_out.separator() lay_out.operator("object.parent_to_empty", @@ -402,12 +402,12 @@ def register(): ) # Add "Extras" menu to the "Add Mesh" menu - bpy.types.INFO_MT_mesh_add.append(menu_func) + bpy.types.VIEW3D_MT_mesh_add.append(menu_func) def unregister(): # Remove "Extras" menu from the "Add Mesh" menu. - bpy.types.INFO_MT_mesh_add.remove(menu_func) + bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) del bpy.types.Scene.discomb del bpy.types.Scene.error_message diff --git a/add_mesh_extra_objects/add_empty_as_parent.py b/add_mesh_extra_objects/add_empty_as_parent.py index bcaecf64..a6ae69e6 100644 --- a/add_mesh_extra_objects/add_empty_as_parent.py +++ b/add_mesh_extra_objects/add_empty_as_parent.py @@ -78,18 +78,18 @@ class P2E(Operator): bpy.ops.object.add(type='EMPTY', location=loc) context.object.name = self.nombre context.object.show_name = True - context.object.show_x_ray = True + context.object.show_in_front = True if self.grupo: - bpy.ops.group.create(name=self.nombre) - bpy.ops.group.objects_add_active() + bpy.ops.collection.create(name=self.nombre) + bpy.ops.collection.objects_add_active() for o in objs: o.select = True if not o.parent: bpy.ops.object.parent_set(type='OBJECT') if self.grupo: - bpy.ops.group.objects_add_active() + bpy.ops.collection.objects_add_active() o.select = False for o in objs: if self.renom: diff --git a/add_mesh_extra_objects/add_mesh_gears.py b/add_mesh_extra_objects/add_mesh_gears.py index b042a6d5..6a7bafd9 100644 --- a/add_mesh_extra_objects/add_mesh_gears.py +++ b/add_mesh_extra_objects/add_mesh_gears.py @@ -666,10 +666,10 @@ class AddGear(Operator): # XXX, supporting adding in editmode is move involved if obj.mode != 'EDIT': # Create vertex groups from stored vertices. - tipGroup = obj.vertex_groups.new('Tips') + tipGroup = obj.vertex_groups.new(name='Tips') tipGroup.add(verts_tip, 1.0, 'ADD') - valleyGroup = obj.vertex_groups.new('Valleys') + valleyGroup = obj.vertex_groups.new(name='Valleys') valleyGroup.add(verts_valley, 1.0, 'ADD') return {'FINISHED'} @@ -790,7 +790,7 @@ class AddWormGear(Operator): # XXX, supporting adding in editmode is move involved if obj.mode != 'EDIT': # Create vertex groups from stored vertices. - tipGroup = obj.vertex_groups.new('Tips') + tipGroup = obj.vertex_groups.new(name='Tips') tipGroup.add(verts_tip, 1.0, 'ADD') valleyGroup = obj.vertex_groups.new('Valleys') diff --git a/ant_landscape/__init__.py b/ant_landscape/__init__.py index 8fbfb79d..49432e81 100644 --- a/ant_landscape/__init__.py +++ b/ant_landscape/__init__.py @@ -938,14 +938,14 @@ class AntLandscapePropertiesGroup(bpy.types.PropertyGroup): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_mesh_add.append(menu_func_landscape) + bpy.types.VIEW3D_MT_mesh_add.append(menu_func_landscape) bpy.types.Object.ant_landscape = PointerProperty(type=AntLandscapePropertiesGroup, name="ANT_Landscape", description="Landscape properties") bpy.types.VIEW3D_MT_paint_weight.append(menu_func_eroder) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_mesh_add.remove(menu_func_landscape) + bpy.types.VIEW3D_MT_mesh_add.remove(menu_func_landscape) bpy.types.VIEW3D_MT_paint_weight.remove(menu_func_eroder) diff --git a/ant_landscape/ant_functions.py b/ant_landscape/ant_functions.py index bee655c0..ca81ce6d 100644 --- a/ant_landscape/ant_functions.py +++ b/ant_landscape/ant_functions.py @@ -953,43 +953,43 @@ class Eroder(bpy.types.Operator): try: vg=ob.vertex_groups["rainmap"] except: - vg=ob.vertex_groups.new("rainmap") + vg=ob.vertex_groups.new(name="rainmap") try: vgscree=ob.vertex_groups["scree"] except: - vgscree=ob.vertex_groups.new("scree") + vgscree=ob.vertex_groups.new(name="scree") try: vgavalanced=ob.vertex_groups["avalanced"] except: - vgavalanced=ob.vertex_groups.new("avalanced") + vgavalanced=ob.vertex_groups.new(name="avalanced") try: vgw=ob.vertex_groups["water"] except: - vgw=ob.vertex_groups.new("water") + vgw=ob.vertex_groups.new(name="water") try: vgscour=ob.vertex_groups["scour"] except: - vgscour=ob.vertex_groups.new("scour") + vgscour=ob.vertex_groups.new(name="scour") try: vgdeposit=ob.vertex_groups["deposit"] except: - vgdeposit=ob.vertex_groups.new("deposit") + vgdeposit=ob.vertex_groups.new(name="deposit") try: vgflowrate=ob.vertex_groups["flowrate"] except: - vgflowrate=ob.vertex_groups.new("flowrate") + vgflowrate=ob.vertex_groups.new(name="flowrate") try: vgsediment=ob.vertex_groups["sediment"] except: - vgsediment=ob.vertex_groups.new("sediment") + vgsediment=ob.vertex_groups.new(name="sediment") try: vgsedimentpct=ob.vertex_groups["sedimentpct"] except: - vgsedimentpct=ob.vertex_groups.new("sedimentpct") + vgsedimentpct=ob.vertex_groups.new(name="sedimentpct") try: vgcapacity=ob.vertex_groups["capacity"] except: - vgcapacity=ob.vertex_groups.new("capacity") + vgcapacity=ob.vertex_groups.new(name="capacity") g = Grid.fromBlenderMesh(me, vg, self.Ef) diff --git a/archimesh/__init__.py b/archimesh/__init__.py index cb22e157..07049ff2 100644 --- a/archimesh/__init__.py +++ b/archimesh/__init__.py @@ -53,7 +53,7 @@ if "bpy" in locals(): importlib.reload(achm_kitchen_maker) importlib.reload(achm_shelves_maker) importlib.reload(achm_books_maker) - importlib.reload(achm_lamp_maker) + importlib.reload(achm_light_maker) importlib.reload(achm_curtain_maker) importlib.reload(achm_venetian_maker) importlib.reload(achm_main_panel) @@ -66,7 +66,7 @@ else: from . import achm_venetian_maker from . import achm_door_maker from . import achm_kitchen_maker - from . import achm_lamp_maker + from . import achm_light_maker from . import achm_main_panel from . import achm_roof_maker from . import achm_room_maker @@ -92,7 +92,7 @@ from bpy.types import ( AddonPreferences, Menu, Scene, - INFO_MT_mesh_add, + VIEW3D_MT_mesh_add, WindowManager, ) @@ -102,13 +102,13 @@ from bpy.types import ( class AchmInfoMtMeshDecorationAdd(Menu): - bl_idname = "INFO_MT_mesh_decoration_add" + bl_idname = "VIEW3D_MT_mesh_decoration_add" bl_label = "Decoration assets" # noinspection PyUnusedLocal def draw(self, context): self.layout.operator("mesh.archimesh_books", text="Add Books") - self.layout.operator("mesh.archimesh_lamp", text="Add Lamp") + self.layout.operator("mesh.archimesh_light", text="Add Lamp") self.layout.operator("mesh.archimesh_roller", text="Add Roller curtains") self.layout.operator("mesh.archimesh_venetian", text="Add Venetian blind") self.layout.operator("mesh.archimesh_japan", text="Add Japanese curtains") @@ -119,7 +119,7 @@ class AchmInfoMtMeshDecorationAdd(Menu): class AchmInfoMtMeshCustomMenuAdd(Menu): - bl_idname = "INFO_MT_mesh_custom_menu_add" + bl_idname = "VIEW3D_MT_mesh_custom_menu_add" bl_label = "Archimesh" # noinspection PyUnusedLocal @@ -134,7 +134,7 @@ class AchmInfoMtMeshCustomMenuAdd(Menu): self.layout.operator("mesh.archimesh_column", text="Add Column") self.layout.operator("mesh.archimesh_stairs", text="Add Stairs") self.layout.operator("mesh.archimesh_roof", text="Add Roof") - self.layout.menu("INFO_MT_mesh_decoration_add", text="Decoration props", icon="GROUP") + self.layout.menu("VIEW3D_MT_mesh_decoration_add", text="Decoration props", icon="GROUP") # -------------------------------------------------------------- # Register all operators and panels @@ -189,7 +189,7 @@ class Archi_Pref(AddonPreferences): # Define menu # noinspection PyUnusedLocal def AchmMenu_func(self, context): - self.layout.menu("INFO_MT_mesh_custom_menu_add", icon="GROUP") + self.layout.menu("VIEW3D_MT_mesh_custom_menu_add", icon="GROUP") def register(): @@ -210,7 +210,7 @@ def register(): bpy.utils.register_class(achm_kitchen_maker.AchmExportInventory) bpy.utils.register_class(achm_shelves_maker.AchmShelves) bpy.utils.register_class(achm_books_maker.AchmBooks) - bpy.utils.register_class(achm_lamp_maker.AchmLamp) + bpy.utils.register_class(achm_light_maker.AchmLamp) bpy.utils.register_class(achm_curtain_maker.AchmRoller) bpy.utils.register_class(achm_curtain_maker.AchmJapan) bpy.utils.register_class(achm_venetian_maker.AchmVenetian) @@ -222,7 +222,7 @@ def register(): bpy.utils.register_class(achm_window_panel.AchmWinPanel) bpy.utils.register_class(achm_window_panel.AchmWindowEditPanel) bpy.utils.register_class(Archi_Pref) - INFO_MT_mesh_add.append(AchmMenu_func) + VIEW3D_MT_mesh_add.append(AchmMenu_func) update_panel(None, bpy.context) # Define properties Scene.archimesh_select_only = BoolProperty( @@ -321,7 +321,7 @@ def unregister(): bpy.utils.unregister_class(achm_kitchen_maker.AchmExportInventory) bpy.utils.unregister_class(achm_shelves_maker.AchmShelves) bpy.utils.unregister_class(achm_books_maker.AchmBooks) - bpy.utils.unregister_class(achm_lamp_maker.AchmLamp) + bpy.utils.unregister_class(achm_light_maker.AchmLamp) bpy.utils.unregister_class(achm_curtain_maker.AchmRoller) bpy.utils.unregister_class(achm_curtain_maker.AchmJapan) bpy.utils.unregister_class(achm_venetian_maker.AchmVenetian) @@ -333,7 +333,7 @@ def unregister(): bpy.utils.unregister_class(achm_window_panel.AchmWinPanel) bpy.utils.unregister_class(achm_window_panel.AchmWindowEditPanel) bpy.utils.unregister_class(Archi_Pref) - INFO_MT_mesh_add.remove(AchmMenu_func) + VIEW3D_MT_mesh_add.remove(AchmMenu_func) # Remove properties del Scene.archimesh_select_only diff --git a/archimesh/achm_door_maker.py b/archimesh/achm_door_maker.py index 6d048bfd..4ef99373 100644 --- a/archimesh/achm_door_maker.py +++ b/archimesh/achm_door_maker.py @@ -233,7 +233,7 @@ def shape_children(mainobject, update=False): myctrl.location.x = 0 myctrl.location.y = -((mp.frame_thick * 3) / 2) myctrl.location.z = -gap - myctrl.draw_type = 'BOUNDS' + myctrl.display_type = 'BOUNDS' myctrl.hide = False myctrl.hide_render = True if bpy.context.scene.render.engine == 'CYCLES': @@ -256,7 +256,7 @@ def shape_children(mainobject, update=False): myctrlbase.location.x = 0 myctrlbase.location.y = -0.15 - (mp.frame_thick / 3) myctrlbase.location.z = -0.10 - myctrlbase.draw_type = 'BOUNDS' + myctrlbase.display_type = 'BOUNDS' myctrlbase.hide = False myctrlbase.hide_render = True if bpy.context.scene.render.engine == 'CYCLES': diff --git a/archimesh/achm_lamp_maker.py b/archimesh/achm_lamp_maker.py index a6b15435..67fbfc1e 100644 --- a/archimesh/achm_lamp_maker.py +++ b/archimesh/achm_lamp_maker.py @@ -130,7 +130,7 @@ def set_preset(self): # Lamps # ------------------------------------------------------------------ class AchmLamp(Operator): - bl_idname = "mesh.archimesh_lamp" + bl_idname = "mesh.archimesh_light" bl_label = "Lamp" bl_description = "Lamp Generator" bl_category = 'Archimesh' @@ -375,7 +375,7 @@ class AchmLamp(Operator): self.oldpreset = self.preset # Create lamp - create_lamp_mesh(self) + create_light_mesh(self) return {'FINISHED'} else: self.report({'WARNING'}, "Archimesh: Option only valid in Object mode") @@ -386,13 +386,13 @@ class AchmLamp(Operator): # Generate mesh data # All custom values are passed using self container (self.myvariable) # ------------------------------------------------------------------------------ -def create_lamp_mesh(self): +def create_light_mesh(self): # deactivate others for o in bpy.data.objects: if o.select is True: o.select = False bpy.ops.object.select_all(False) - generate_lamp(self) + generate_light(self) return @@ -401,13 +401,13 @@ def create_lamp_mesh(self): # Generate lamps # All custom values are passed using self container (self.myvariable) # ------------------------------------------------------------------------------ -def generate_lamp(self): +def generate_light(self): location = bpy.context.scene.cursor_location myloc = copy(location) # copy location to keep 3D cursor position # --------------------- # Lamp base # --------------------- - mydata = create_lamp_base("Lamp_base", self.base_height, + mydata = create_light_base("Lamp_base", self.base_height, myloc.x, myloc.y, myloc.z, self.base_segments, self.base_rings, [self.br01, self.br02, self.br03, self.br04, self.br05, self.br06, @@ -429,7 +429,7 @@ def generate_lamp(self): # --------------------- # Lampholder # --------------------- - myholder = create_lampholder("Lampholder", self.holder, + myholder = create_lightholder("Lampholder", self.holder, myloc.x, myloc.y, myloc.z, self.crt_mat) # refine @@ -444,7 +444,7 @@ def generate_lamp(self): # --------------------- # Lamp strings # --------------------- - mystrings = create_lampholder_strings("Lampstrings", self.holder, + mystrings = create_lightholder_strings("Lampstrings", self.holder, myloc.x, myloc.y, myloc.z, self.tr02, self.top_height, @@ -460,7 +460,7 @@ def generate_lamp(self): # --------------------- # Lampshade # --------------------- - mytop = create_lampshade("Lampshade", self.top_height, + mytop = create_lightshade("Lampshade", self.top_height, myloc.x, myloc.y, myloc.z, self.top_segments, self.tr01, self.tr02, @@ -517,7 +517,7 @@ def generate_lamp(self): # mat: Flag for creating materials # objcol: Color # ------------------------------------------------------------------------------ -def create_lamp_base(objname, height, px, py, pz, segments, rings, radios, ratios, subdivide, mat, objcol): +def create_light_base(objname, height, px, py, pz, segments, rings, radios, ratios, subdivide, mat, objcol): # Calculate heights h = height / (rings - 1) listheight = [] @@ -562,7 +562,7 @@ def create_lamp_base(objname, height, px, py, pz, segments, rings, radios, ratio # pZ: position Z axis # mat: Flag for creating materials # ------------------------------------------------------------------------------ -def create_lampholder(objname, height, px, py, pz, mat): +def create_lightholder(objname, height, px, py, pz, mat): mydata = create_cylinder_data(16, [0, height, height + 0.005, height + 0.008, height + 0.05], [0.005, 0.005, 0.010, 0.018, 0.018], False, False, False, 0, False) @@ -600,7 +600,7 @@ def create_lampholder(objname, height, px, py, pz, mat): # shadeh: height of lampshader # mat: Flag for creating materials # ------------------------------------------------------------------------------ -def create_lampholder_strings(objname, height, px, py, pz, radio, shadeh, mat): +def create_lightholder_strings(objname, height, px, py, pz, radio, shadeh, mat): mydata = create_cylinder_data(32, [height + 0.005, height + 0.005, height + 0.006, height + 0.006], [0.018, 0.025, 0.025, 0.018], False, False, False, 0, False) @@ -652,7 +652,7 @@ def create_lampholder_strings(objname, height, px, py, pz, radio, shadeh, mat): # opacity: opacity factor # mat: Flag for creating materials # ------------------------------------------------------------------------------ -def create_lampshade(objname, height, px, py, pz, segments, radio1, radio2, pleats, pleatsize, opacity, mat): +def create_lightshade(objname, height, px, py, pz, segments, radio1, radio2, pleats, pleatsize, opacity, mat): gap = 0.002 radios = [radio1 - gap, radio1 - gap, radio1, radio2, radio2 - gap, radio2 - gap] heights = [gap * 2, 0, 0, height, height, height - (gap * 2)] diff --git a/archimesh/achm_main_panel.py b/archimesh/achm_main_panel.py index 70312ac3..eb3d7748 100644 --- a/archimesh/achm_main_panel.py +++ b/archimesh/achm_main_panel.py @@ -462,10 +462,10 @@ class ArchimeshMainPanel(Panel): # Prop Buttons # ------------------------------ box = layout.box() - box.label("Props", icon='LAMP_DATA') + box.label("Props", icon='LIGHT_DATA') row = box.row() row.operator("mesh.archimesh_books") - row.operator("mesh.archimesh_lamp") + row.operator("mesh.archimesh_light") row = box.row() row.operator("mesh.archimesh_venetian") row.operator("mesh.archimesh_roller") diff --git a/archimesh/achm_window_maker.py b/archimesh/achm_window_maker.py index b7eb8235..1e43c792 100644 --- a/archimesh/achm_window_maker.py +++ b/archimesh/achm_window_maker.py @@ -216,7 +216,7 @@ def shape_mesh_and_create_children(mainobject, tmp_mesh, update=False): myctrl.location.x = 0 myctrl.location.y = -mp.depth * 3 / 2 myctrl.location.z = 0 - myctrl.draw_type = 'BOUNDS' + myctrl.display_type = 'BOUNDS' myctrl.hide = False myctrl.hide_render = True if bpy.context.scene.render.engine == 'CYCLES': diff --git a/archimesh/achm_window_panel.py b/archimesh/achm_window_panel.py index dbeed892..16c9209b 100644 --- a/archimesh/achm_window_panel.py +++ b/archimesh/achm_window_panel.py @@ -394,7 +394,7 @@ def do_ctrl_box(myobject): myctrl.location.x = 0 myctrl.location.y = 0 myctrl.location.z = 0 - myctrl.draw_type = 'WIRE' + myctrl.display_type = 'WIRE' myctrl.hide = False myctrl.hide_render = True if bpy.context.scene.render.engine == 'CYCLES': diff --git a/archipack/__init__.py b/archipack/__init__.py index 0f5d3a86..6c30d218 100644 --- a/archipack/__init__.py +++ b/archipack/__init__.py @@ -31,8 +31,8 @@ bl_info = { 'author': 's-leger', 'license': 'GPL', 'deps': '', - 'version': (1, 2, 8), - 'blender': (2, 7, 8), + 'version': (1, 2, 81), + 'blender': (2, 80, 0), 'location': 'View3D > Tools > Create > Archipack', 'warning': '', 'wiki_url': 'https://github.com/s-leger/archipack/wiki', @@ -46,7 +46,6 @@ import os if "bpy" in locals(): import importlib as imp - imp.reload(archipack_progressbar) imp.reload(archipack_material) imp.reload(archipack_snap) imp.reload(archipack_manipulator) @@ -65,7 +64,6 @@ if "bpy" in locals(): # print("archipack: reload ready") else: - from . import archipack_progressbar from . import archipack_material from . import archipack_snap from . import archipack_manipulator @@ -81,8 +79,7 @@ else: from . import archipack_truss from . import archipack_floor from . import archipack_rendering - - # print("archipack: ready") + print("archipack: ready") # noinspection PyUnresolvedReferences import bpy @@ -105,82 +102,110 @@ icons_collection = {} # Addon preferences # ---------------------------------------------------- -def update_panel(self, context): - try: - bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools) - bpy.utils.unregister_class(TOOLS_PT_Archipack_Create) - except: - pass - prefs = context.user_preferences.addons[__name__].preferences - TOOLS_PT_Archipack_Tools.bl_category = prefs.tools_category - bpy.utils.register_class(TOOLS_PT_Archipack_Tools) - TOOLS_PT_Archipack_Create.bl_category = prefs.create_category - bpy.utils.register_class(TOOLS_PT_Archipack_Create) - class Archipack_Pref(AddonPreferences): bl_idname = __name__ - tools_category = StringProperty( - name="Tools", - description="Choose a name for the category of the Tools panel", - default="Tools", - update=update_panel - ) - create_category = StringProperty( - name="Create", - description="Choose a name for the category of the Create panel", - default="Create", - update=update_panel - ) - create_submenu = BoolProperty( + create_submenu : BoolProperty( name="Use Sub-menu", description="Put Achipack's object into a sub menu (shift+a)", default=True ) - max_style_draw_tool = BoolProperty( + max_style_draw_tool : BoolProperty( name="Draw a wall use 3dsmax style", description="Reverse clic / release & drag cycle for Draw a wall", default=True ) # Arrow sizes (world units) - arrow_size = FloatProperty( + arrow_size : FloatProperty( name="Arrow", description="Manipulators arrow size (blender units)", default=0.05 ) # Handle area size (pixels) - handle_size = IntProperty( + handle_size : IntProperty( name="Handle", description="Manipulators handle sensitive area size (pixels)", min=2, default=10 ) - matlib_path = StringProperty( + constant_handle_size: BoolProperty( + name="Constant handle size", + description="When checked, handle size on scree remains constant (handle size pixels)", + default=False + ) + text_size: IntProperty( + name="Manipulators", + description="Manipulator font size (pixels)", + min=2, + default=14 + ) + handle_colour_selected: FloatVectorProperty( + name="Selected handle colour", + description="Handle color when selected", + subtype='COLOR_GAMMA', + default=(0.0, 0.0, 0.7, 1.0), + size=4, + min=0, max=1 + ) + handle_colour_inactive: FloatVectorProperty( + name="Inactive handle colour", + description="Handle color when disabled", + subtype='COLOR_GAMMA', + default=(0.3, 0.3, 0.3, 1.0), + size=4, + min=0, max=1 + ) + handle_colour_normal: FloatVectorProperty( + name="Handle colour normal", + description="Base handle color when not selected", + subtype='COLOR_GAMMA', + default=(1.0, 1.0, 1.0, 1.0), + size=4, + min=0, max=1 + ) + handle_colour_hover: FloatVectorProperty( + name="Handle colour hover", + description="Handle color when mouse hover", + subtype='COLOR_GAMMA', + default=(1.0, 1.0, 0.0, 1.0), + size=4, + min=0, max=1 + ) + handle_colour_active: FloatVectorProperty( + name="Handle colour active", + description="Handle colour when moving", + subtype='COLOR_GAMMA', + default=(1.0, 0.0, 0.0, 1.0), + size=4, + min=0, max=1 + ) + matlib_path : StringProperty( name="Folder path", description="absolute path to material library folder", - default="" + default="", + subtype="DIR_PATH" ) # Font sizes and basic colour scheme - feedback_size_main = IntProperty( + feedback_size_main : IntProperty( name="Main", description="Main title font size (pixels)", min=2, default=16 ) - feedback_size_title = IntProperty( + feedback_size_title : IntProperty( name="Title", description="Tool name font size (pixels)", min=2, default=14 ) - feedback_size_shortcut = IntProperty( + feedback_size_shortcut : IntProperty( name="Shortcut", description="Shortcuts font size (pixels)", min=2, default=11 ) - feedback_shortcut_area = FloatVectorProperty( + feedback_shortcut_area : FloatVectorProperty( name="Background Shortcut", description="Shortcut area background color", subtype='COLOR_GAMMA', @@ -188,7 +213,7 @@ class Archipack_Pref(AddonPreferences): size=4, min=0, max=1 ) - feedback_title_area = FloatVectorProperty( + feedback_title_area : FloatVectorProperty( name="Background Main", description="Title area background color", subtype='COLOR_GAMMA', @@ -196,7 +221,7 @@ class Archipack_Pref(AddonPreferences): size=4, min=0, max=1 ) - feedback_colour_main = FloatVectorProperty( + feedback_colour_main : FloatVectorProperty( name="Font Main", description="Title color", subtype='COLOR_GAMMA', @@ -204,7 +229,7 @@ class Archipack_Pref(AddonPreferences): size=4, min=0, max=1 ) - feedback_colour_key = FloatVectorProperty( + feedback_colour_key : FloatVectorProperty( name="Font Shortcut key", description="KEY label color", subtype='COLOR_GAMMA', @@ -212,7 +237,7 @@ class Archipack_Pref(AddonPreferences): size=4, min=0, max=1 ) - feedback_colour_shortcut = FloatVectorProperty( + feedback_colour_shortcut : FloatVectorProperty( name="Font Shortcut hint", description="Shortcuts text color", subtype='COLOR_GAMMA', @@ -226,21 +251,22 @@ class Archipack_Pref(AddonPreferences): box = layout.box() row = box.row() col = row.column() - col.label(text="Tab Category:") - col.prop(self, "tools_category") - col.prop(self, "create_category") - col.prop(self, "create_submenu") - box = layout.box() - box.label("Features") - box.prop(self, "max_style_draw_tool") + col.label(text="Setup actions") + col.prop(self, "matlib_path", text="Material library") + box.label(text="Render presets Thumbnails") + box.operator("archipack.render_thumbs", icon='ERROR') box = layout.box() row = box.row() col = row.column() - col.label(text="Material library:") - col.prop(self, "matlib_path") + col.label(text="Add menu:") + col.prop(self, "create_submenu") + + box = layout.box() + box.label(text="Features") + box.prop(self, "max_style_draw_tool") box = layout.box() row = box.row() - split = row.split(percentage=0.5) + split = row.split(factor=0.5) col = split.column() col.label(text="Colors:") row = col.row(align=True) @@ -253,6 +279,16 @@ class Archipack_Pref(AddonPreferences): row.prop(self, "feedback_colour_key") row = col.row(align=True) row.prop(self, "feedback_colour_shortcut") + row = col.row(align=True) + row.prop(self, "handle_colour_normal") + row = col.row(align=True) + row.prop(self, "handle_colour_hover") + row = col.row(align=True) + row.prop(self, "handle_colour_active") + row = col.row(align=True) + row.prop(self, "handle_colour_selected") + row = col.row(align=True) + row.prop(self, "handle_colour_inactive") col = split.column() col.label(text="Font size:") col.prop(self, "feedback_size_main") @@ -261,46 +297,19 @@ class Archipack_Pref(AddonPreferences): col.label(text="Manipulators:") col.prop(self, "arrow_size") col.prop(self, "handle_size") - # layout.operator("archipack.render_thumbs") + col.prop(self, "text_size") + col.prop(self, "constant_handle_size") # ---------------------------------------------------- -# Archipack panels +# Archipack panel # ---------------------------------------------------- - -class TOOLS_PT_Archipack_Tools(Panel): - bl_label = "Archipack Tools" - bl_idname = "TOOLS_PT_Archipack_Tools" - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_category = "Tools" - bl_context = "objectmode" - - @classmethod - def poll(self, context): - return True - - def draw(self, context): - wm = context.window_manager - layout = self.layout - box = layout.box() - box.label("Auto boolean") - box.operator("archipack.auto_boolean", text="AutoBoolean", icon='AUTO').mode = 'HYBRID' - box = layout.box() - box.label("Rendering") - box.prop(wm.archipack, 'render_type', text="") - box.operator("archipack.render", icon='RENDER_STILL') - box = layout.box() - box.label("Render presets Thumbnails") - box.operator("archipack.render_thumbs", icon='ERROR') - - class TOOLS_PT_Archipack_Create(Panel): - bl_label = "Add Archipack" + bl_label = "Archipack" bl_idname = "TOOLS_PT_Archipack_Create" bl_space_type = "VIEW_3D" bl_region_type = "TOOLS" - bl_category = "Create" + # bl_category = "Create" bl_context = "objectmode" @classmethod @@ -312,9 +321,11 @@ class TOOLS_PT_Archipack_Create(Panel): icons = icons_collection["main"] layout = self.layout + box = layout.box() + box.operator("archipack.auto_boolean", text="Boolean", icon='AUTO').mode = 'HYBRID' row = layout.row(align=True) box = row.box() - box.label("Objects") + box.label(text="Create") row = box.row(align=True) row.operator("archipack.window_preset_menu", text="Window", @@ -454,25 +465,6 @@ def menu_func(self, context): draw_menu(self, context) -# ---------------------------------------------------- -# Datablock to store global addon variables -# ---------------------------------------------------- - - -class archipack_data(PropertyGroup): - render_type = EnumProperty( - items=( - ('1', "Draw over", "Draw over last rendered image"), - ('2', "OpenGL", ""), - ('3', "Animation OpenGL", ""), - ('4', "Image", "Render image and draw over"), - ('5', "Animation", "Draw on each frame") - ), - name="Render type", - description="Render method" - ) - - def register(): global icons_collection icons = previews.new() @@ -481,8 +473,6 @@ def register(): name, ext = os.path.splitext(icon) icons.load(name, os.path.join(icons_dir, icon), 'IMAGE') icons_collection["main"] = icons - - archipack_progressbar.register() archipack_material.register() archipack_snap.register() archipack_manipulator.register() @@ -498,24 +488,18 @@ def register(): archipack_truss.register() archipack_floor.register() archipack_rendering.register() - - bpy.utils.register_class(archipack_data) - WindowManager.archipack = PointerProperty(type=archipack_data) bpy.utils.register_class(Archipack_Pref) - update_panel(None, bpy.context) + bpy.utils.register_class(TOOLS_PT_Archipack_Create) bpy.utils.register_class(ARCHIPACK_MT_create) - bpy.types.INFO_MT_mesh_add.append(menu_func) + bpy.types.VIEW3D_MT_mesh_add.append(menu_func) def unregister(): global icons_collection - bpy.types.INFO_MT_mesh_add.remove(menu_func) + bpy.types.VIEW3D_MT_mesh_add.remove(menu_func) bpy.utils.unregister_class(ARCHIPACK_MT_create) - - bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools) bpy.utils.unregister_class(TOOLS_PT_Archipack_Create) bpy.utils.unregister_class(Archipack_Pref) - archipack_progressbar.unregister() archipack_material.unregister() archipack_snap.unregister() archipack_manipulator.unregister() @@ -532,9 +516,6 @@ def unregister(): archipack_floor.unregister() archipack_rendering.unregister() - bpy.utils.unregister_class(archipack_data) - del WindowManager.archipack - for icons in icons_collection.values(): previews.remove(icons) icons_collection.clear() diff --git a/archipack/archipack_2d.py b/archipack/archipack_2d.py index 0078ae3c..e286e730 100644 --- a/archipack/archipack_2d.py +++ b/archipack/archipack_2d.py @@ -253,10 +253,10 @@ class Line(Projection): t: param t of intersection on current line """ c = line.cross_z - d = self.v * c + d = self.v.dot(c) if d == 0: return False, 0, 0 - t = (c * (line.p - self.p)) / d + t = c.dot(line.p - self.p) / d return True, self.lerp(t), t def intersect_ext(self, line): @@ -264,13 +264,13 @@ class Line(Projection): same as intersect, but return param t on both lines """ c = line.cross_z - d = self.v * c + d = self.v.dot(c) if d == 0: return False, 0, 0, 0 dp = line.p - self.p c2 = self.cross_z - u = (c * dp) / d - v = (c2 * dp) / d + u = c.dot(dp) / d + v = c2.dot(dp) / d return u > 0 and v > 0 and u < 1 and v < 1, self.lerp(u), u, v def point_sur_segment(self, pt): @@ -284,7 +284,7 @@ class Line(Projection): if dl == 0: return dp.length < 0.00001, 0, 0 d = (self.v.x * dp.y - self.v.y * dp.x) / dl - t = (self.v * dp) / (dl * dl) + t = self.v.dot(dp) / (dl * dl) return t > 0 and t < 1, d, t def steps(self, len): @@ -328,7 +328,7 @@ class Line(Projection): self.v = Matrix([ [ca, -sa], [sa, ca] - ]) * self.v + ]) @ self.v return self def scale(self, length): @@ -354,8 +354,8 @@ class Line(Projection): x, y, z = p spline.points[i].co = (x, y, 0, 1) curve_obj = bpy.data.objects.new('LINE', curve) - context.scene.objects.link(curve_obj) - curve_obj.select = True + context.scene.collection.objects.link(curve_obj) + curve_obj.select_set(state=True) def make_offset(self, offset, last=None): """ @@ -399,13 +399,13 @@ class Line(Projection): # intersect line / line # 1 line -> 2 line c = line.cross_z - d = last.v * c + d = last.v.dot(c) if d == 0: return line v = line.p - last.p - t = (c * v) / d + t = c.dot(v) / d c2 = last.cross_z - u = (c2 * v) / d + u = c2.dot(v) / d # intersect past this segment end # or before last segment start # print("u:%s t:%s" % (u, t)) @@ -431,9 +431,9 @@ class Circle(Projection): def intersect(self, line): v = line.p - self.c - A = line.v * line.v - B = 2 * v * line.v - C = v * v - self.r2 + A = line.v.dot(line.v) + B = 2 * v.dot(line.v) + C = v.dot(v) - self.r2 d = B * B - 4 * A * C if A <= 0.0000001 or d < 0: # dosent intersect, find closest point of line @@ -549,7 +549,7 @@ class Arc(Circle): u = self.p0 - self.p1 v = p0 - self.p1 scale, rM = self.scale_rot_matrix(u, v) - self.c = self.p1 + rM * (self.c - self.p1) + self.c = self.p1 + rM @ (self.c - self.p1) self.r *= scale self.r2 = self.r * self.r dp = p0 - self.c @@ -566,7 +566,7 @@ class Arc(Circle): v = p1 - p0 scale, rM = self.scale_rot_matrix(u, v) - self.c = p0 + rM * (self.c - p0) + self.c = p0 + rM @ (self.c - p0) self.r *= scale self.r2 = self.r * self.r dp = p0 - self.c @@ -730,7 +730,7 @@ class Arc(Circle): [sa, ca] ]) p0 = self.p0 - self.c = p0 + rM * (self.c - p0) + self.c = p0 + rM @ (self.c - p0) dp = p0 - self.c self.a0 = atan2(dp.y, dp.x) return self @@ -842,8 +842,8 @@ class Arc(Circle): x, y = p spline.points[i].co = (x, y, 0, 1) curve_obj = bpy.data.objects.new('ARC', curve) - context.scene.objects.link(curve_obj) - curve_obj.select = True + context.scene.collection.objects.link(curve_obj) + curve_obj.select_set(state=True) class Line3d(Line): diff --git a/archipack/archipack_autoboolean.py b/archipack/archipack_autoboolean.py index c7e60305..8e4df558 100644 --- a/archipack/archipack_autoboolean.py +++ b/archipack/archipack_autoboolean.py @@ -28,9 +28,9 @@ import bpy from bpy.types import Operator from bpy.props import EnumProperty from mathutils import Vector +from . archipack_object import ArchipackCollectionManager - -class ArchipackBoolManager(): +class ArchipackBoolManager(ArchipackCollectionManager): """ Handle three methods for booleans - interactive: one modifier for each hole right on wall @@ -38,12 +38,11 @@ class ArchipackBoolManager(): - mixed: merge holes with boolean and use result on wall may be slow, but is robust """ - def __init__(self, mode, solver_mode='CARVE'): + def __init__(self, mode): """ mode in 'ROBUST', 'INTERACTIVE', 'HYBRID' """ self.mode = mode - self.solver_mode = solver_mode # internal variables self.itM = None self.min_x = 0 @@ -69,7 +68,7 @@ class ArchipackBoolManager(): self.min_z + 0.5 * (self.max_z - self.min_z))) def _contains(self, pt): - p = self.itM * pt + p = self.itM @ pt return (p.x >= self.min_x and p.x <= self.max_x and p.y >= self.min_y and p.y <= self.max_y and p.z >= self.min_z and p.z <= self.max_z) @@ -105,10 +104,10 @@ class ArchipackBoolManager(): hole.lock_location = (True, True, True) hole.lock_rotation = (True, True, True) hole.lock_scale = (True, True, True) - hole.draw_type = 'WIRE' + hole.display_type = 'WIRE' hole.hide_render = True hole.hide_select = True - hole.select = True + hole.select_set(state=True) hole.cycles_visibility.camera = False hole.cycles_visibility.diffuse = False hole.cycles_visibility.glossy = False @@ -135,7 +134,7 @@ class ArchipackBoolManager(): if d is not None: if (self.itM is not None and ( self._contains(o.location) or - self._contains(o.matrix_world * Vector((0, 0, 0.5 * d.z)))) + self._contains(o.matrix_world @ Vector((0, 0, 0.5 * d.z)))) ): if self.mode != 'ROBUST': hole = d.interactive_hole(context, o) @@ -172,7 +171,7 @@ class ArchipackBoolManager(): sort hole from center to borders by distance from center may improve nested booleans """ - center = wall.matrix_world * self.center + center = wall.matrix_world @ self.center holes = [(o, (o.matrix_world.translation - center).length) for o in holes] self.quicksort(holes) return [o[0] for o in holes] @@ -181,17 +180,12 @@ class ArchipackBoolManager(): # print("difference %s" % (hole.name)) m = basis.modifiers.new('AutoBoolean', 'BOOLEAN') m.operation = 'DIFFERENCE' - if solver is None: - m.solver = self.solver_mode - else: - m.solver = solver m.object = hole def union(self, basis, hole): # print("union %s" % (hole.name)) m = basis.modifiers.new('AutoMerge', 'BOOLEAN') m.operation = 'UNION' - m.solver = self.solver_mode m.object = hole def remove_modif_and_object(self, context, o, to_delete): @@ -202,7 +196,7 @@ class ArchipackBoolManager(): m.object = None o.modifiers.remove(m) if h is not None: - context.scene.objects.unlink(h) + self.unlink_object_from_scene(h) bpy.data.objects.remove(h, do_unlink=True) # Mixed @@ -210,7 +204,7 @@ class ArchipackBoolManager(): # print("create_merge_basis") h = bpy.data.meshes.new("AutoBoolean") hole_obj = bpy.data.objects.new("AutoBoolean", h) - context.scene.objects.link(hole_obj) + self.link_object_to_scene(context, hole_obj) hole_obj['archipack_hybridhole'] = True if wall.parent is not None: hole_obj.parent = wall.parent @@ -254,7 +248,6 @@ class ArchipackBoolManager(): m = wall.modifiers.get("AutoMixedBoolean") if m is None: m = wall.modifiers.new('AutoMixedBoolean', 'BOOLEAN') - m.solver = self.solver_mode m.operation = 'DIFFERENCE' if m.object is None: @@ -306,7 +299,7 @@ class ArchipackBoolManager(): to_delete.append([m, h]) # remove modifier and holes not found in new list self.remove_modif_and_object(context, hole_obj, to_delete) - context.scene.objects.unlink(hole_obj) + self.unlink_object_from_scene(hole_obj) bpy.data.objects.remove(hole_obj, do_unlink=True) to_delete = [] @@ -388,12 +381,12 @@ class ArchipackBoolManager(): # to support "revival" of applied modifiers m = wall.modifiers.get("Wall") if m is None: - wall.select = True - context.scene.objects.active = wall + wall.select_set(state=True) + context.view_layer.objects.active = wall wall.data.archipack_wall2[0].update(context) bpy.ops.object.select_all(action='DESELECT') - context.scene.objects.active = None + context.view_layer.objects.active = None childs = [] holes = [] # get wall bounds to find what's inside @@ -429,19 +422,19 @@ class ArchipackBoolManager(): # parenting childs to wall reference point if wall.parent is None: x, y, z = wall.bound_box[0] - context.scene.cursor_location = wall.matrix_world * Vector((x, y, z)) + context.scene.cursor_location = wall.matrix_world @ Vector((x, y, z)) # fix issue #9 - context.scene.objects.active = wall + context.view_layer.objects.active = wall bpy.ops.archipack.reference_point() else: - wall.parent.select = True - context.scene.objects.active = wall.parent + wall.parent.select_set(state=True) + context.view_layer.objects.active = wall.parent - wall.select = True + wall.select_set(state=True) for o in childs: if 'archipack_robusthole' in o: o.hide_select = False - o.select = True + o.select_set(state=True) bpy.ops.archipack.parent_to_reference() @@ -499,7 +492,6 @@ class ArchipackBoolManager(): if m is None: m = wall.modifiers.new('AutoMixedBoolean', 'BOOLEAN') m.operation = 'DIFFERENCE' - m.solver = self.solver_mode if m.object is None: hole_obj = self.create_merge_basis(context, wall) @@ -513,21 +505,21 @@ class ArchipackBoolManager(): # parenting childs to wall reference point if wall.parent is None: x, y, z = wall.bound_box[0] - context.scene.cursor_location = wall.matrix_world * Vector((x, y, z)) + context.scene.cursor_location = wall.matrix_world @ Vector((x, y, z)) # fix issue #9 - context.scene.objects.active = wall + context.view_layer.objects.active = wall bpy.ops.archipack.reference_point() else: - context.scene.objects.active = wall.parent + context.view_layer.objects.active = wall.parent if hole_obj is not None: - hole_obj.select = True + hole_obj.select_set(state=True) - wall.select = True - o.select = True + wall.select_set(state=True) + o.select_set(state=True) bpy.ops.archipack.parent_to_reference() - wall.select = True - context.scene.objects.active = wall + wall.select_set(state=True) + context.view_layer.objects.active = wall if "archipack_wall2" in wall.data: d = wall.data.archipack_wall2[0] g = d.get_generator() @@ -545,7 +537,7 @@ class ARCHIPACK_OT_single_boolean(Operator): bl_description = "Add single boolean for doors and windows" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - mode = EnumProperty( + mode : EnumProperty( name="Mode", items=( ('INTERACTIVE', 'INTERACTIVE', 'Interactive, fast but may fail', 0), @@ -554,14 +546,6 @@ class ARCHIPACK_OT_single_boolean(Operator): ), default='HYBRID' ) - solver_mode = EnumProperty( - name="Solver", - items=( - ('CARVE', 'CARVE', 'Slow but robust (could be slow in hybrid mode with many holes)', 0), - ('BMESH', 'BMESH', 'Fast but more prone to errors', 1) - ), - default='BMESH' - ) """ Wall must be active object window or door must be selected @@ -583,14 +567,14 @@ class ARCHIPACK_OT_single_boolean(Operator): def execute(self, context): if context.mode == "OBJECT": wall = context.active_object - manager = ArchipackBoolManager(mode=self.mode, solver_mode=self.solver_mode) + manager = ArchipackBoolManager(mode=self.mode) for o in context.selected_objects: if o != wall: manager.singleboolean(context, wall, o) + o.select_set(state=False) break - o.select = False - wall.select = True - context.scene.objects.active = wall + wall.select_set(state=True) + context.view_layer.objects.active = wall return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -603,7 +587,7 @@ class ARCHIPACK_OT_auto_boolean(Operator): bl_description = "Automatic boolean for doors and windows" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - mode = EnumProperty( + mode : EnumProperty( name="Mode", items=( ('INTERACTIVE', 'INTERACTIVE', 'Interactive, fast but may fail', 0), @@ -612,39 +596,30 @@ class ARCHIPACK_OT_auto_boolean(Operator): ), default='HYBRID' ) - solver_mode = EnumProperty( - name="Solver", - items=( - ('CARVE', 'CARVE', 'Slow but robust (could be slow in hybrid mode with many holes)', 0), - ('BMESH', 'BMESH', 'Fast but more prone to errors', 1) - ), - default='BMESH' - ) def draw(self, context): layout = self.layout row = layout.row() row.prop(self, 'mode') - row.prop(self, 'solver_mode') def execute(self, context): if context.mode == "OBJECT": - manager = ArchipackBoolManager(mode=self.mode, solver_mode=self.solver_mode) - active = context.scene.objects.active + manager = ArchipackBoolManager(mode=self.mode) + active = context.view_layer.objects.active walls = [wall for wall in context.selected_objects if not manager.filter_wall(wall)] bpy.ops.object.select_all(action='DESELECT') for wall in walls: manager.autoboolean(context, wall) bpy.ops.object.select_all(action='DESELECT') - wall.select = True - context.scene.objects.active = wall + wall.select_set(state=True) + context.view_layer.objects.active = wall if wall.data is not None and 'archipack_wall2' in wall.data: bpy.ops.archipack.wall2_manipulate('EXEC_DEFAULT') # reselect walls bpy.ops.object.select_all(action='DESELECT') for wall in walls: - wall.select = True - context.scene.objects.active = active + wall.select_set(state=True) + context.view_layer.objects.active = active return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -668,13 +643,13 @@ class ARCHIPACK_OT_generate_hole(Operator): self.report({'WARNING'}, "Archipack: active object must be a door, a window or a roof") return {'CANCELLED'} bpy.ops.object.select_all(action='DESELECT') - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o hole = manager._generate_hole(context, o) manager.prepare_hole(hole) - hole.select = False - o.select = True - context.scene.objects.active = o + hole.select_set(state=False) + o.select_set(state=True) + context.view_layer.objects.active = o return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") diff --git a/archipack/archipack_cutter.py b/archipack/archipack_cutter.py index 2ce37d4c..3b9f424d 100644 --- a/archipack/archipack_cutter.py +++ b/archipack/archipack_cutter.py @@ -160,7 +160,7 @@ class CutterGenerator(): """ move shape fromTM into toTM coordsys """ - dp = (toTM.inverted() * fromTM.translation).to_2d() + dp = (toTM.inverted() @ fromTM.translation).to_2d() da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d()) ca = cos(da) sa = sin(da) @@ -169,7 +169,7 @@ class CutterGenerator(): [sa, ca] ]) for s in self.segs: - tp = (rM * s.p0) - s.p0 + dp + tp = (rM @ s.p0) - s.p0 + dp s.rotate(da) s.translate(tp) @@ -519,7 +519,7 @@ class CutAbleGenerator(): f.calc_center_median().to_2d(), segs=segs)] if len(f_geom) > 0: - bmesh.ops.delete(bm, geom=f_geom, context=5) + bmesh.ops.delete(bm, geom=f_geom, context='FACES') def cut_boundary(self, bm, cutable, offset={'DEFAULT': 0}): o_keys = offset.keys() @@ -545,7 +545,7 @@ class CutAbleGenerator(): f_geom = [f for f in bm.faces if not cutable.inside(f.calc_center_median().to_2d())] if len(f_geom) > 0: - bmesh.ops.delete(bm, geom=f_geom, context=5) + bmesh.ops.delete(bm, geom=f_geom, context='FACES') def update_hole(self, context): @@ -562,14 +562,14 @@ class ArchipackCutterPart(): Childs MUST define -type EnumProperty """ - length = FloatProperty( + length : FloatProperty( name="Length", min=0.01, max=1000.0, default=2.0, update=update_hole ) - a0 = FloatProperty( + a0 : FloatProperty( name="Angle", min=-2 * pi, max=2 * pi, @@ -577,7 +577,7 @@ class ArchipackCutterPart(): subtype='ANGLE', unit='ROTATION', update=update_hole ) - offset = FloatProperty( + offset : FloatProperty( name="Offset", min=0, default=0, @@ -617,28 +617,28 @@ def update_manipulators(self, context): class ArchipackCutter(): - n_parts = IntProperty( + n_parts : IntProperty( name="Parts", min=1, default=1, update=update_manipulators ) - z = FloatProperty( + z : FloatProperty( name="dumb z", description="Dumb z for manipulator placeholder", default=0.01, options={'SKIP_SAVE'} ) - user_defined_path = StringProperty( + user_defined_path : StringProperty( name="User defined", update=update_path ) - user_defined_resolution = IntProperty( + user_defined_resolution : IntProperty( name="Resolution", min=1, max=128, default=12, update=update_path ) - operation = EnumProperty( + operation : EnumProperty( items=( ('DIFFERENCE', 'Difference', 'Cut inside part', 0), ('INTERSECTION', 'Intersection', 'Keep inside part', 1) @@ -646,20 +646,17 @@ class ArchipackCutter(): default='DIFFERENCE', update=update_operation ) - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update_manipulators ) # UI layout related - parts_expand = BoolProperty( + parts_expand : BoolProperty( default=False ) - closed = BoolProperty( - description="keep closed to be wall snap manipulator compatible", - options={'SKIP_SAVE'}, - default=True - ) + + closed = True def draw(self, layout, context): box = layout.box() @@ -731,18 +728,18 @@ class ArchipackCutter(): # since this can lower points count by a resolution factor # use normalized to handle non linear t if resolution == 0: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: v = (p1.co - p0.co).normalized() d1 = (p0.handle_right - p0.co).normalized() d2 = (p1.co - p1.handle_left).normalized() if d1 == v and d2 == v: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: - seg = interpolate_bezier(wM * p0.co, - wM * p0.handle_right, - wM * p1.handle_left, - wM * p1.co, + seg = interpolate_bezier(wM @ p0.co, + wM @ p0.handle_right, + wM @ p1.handle_left, + wM @ p1.co, resolution + 1) for i in range(resolution): pts.append(seg[i].to_3d()) @@ -768,7 +765,7 @@ class ArchipackCutter(): def from_spline(self, context, wM, resolution, spline): pts = [] if spline.type == 'POLY': - pts = [wM * p.co.to_3d() for p in spline.points] + pts = [wM @ p.co.to_3d() for p in spline.points] if spline.use_cyclic_u: pts.append(pts[0]) elif spline.type == 'BEZIER': @@ -783,21 +780,16 @@ class ArchipackCutter(): self.interpolate_bezier(pts, wM, p0, p1, resolution) pts.append(pts[0]) else: - pts.append(wM * points[-1].co) + pts.append(wM @ points[-1].co) if self.is_cw(pts) == (self.operation == 'INTERSECTION'): pts = list(reversed(pts)) - pt = wM.inverted() * pts[0] + pt = wM.inverted() @ pts[0] # pretranslate o = self.find_in_selection(context, self.auto_update) - o.matrix_world = wM * Matrix([ - [1, 0, 0, pt.x], - [0, 1, 0, pt.y], - [0, 0, 1, pt.z], - [0, 0, 0, 1] - ]) + o.matrix_world = wM @ Matrix.Translation(pt) self.auto_update = False self.from_points(pts) self.auto_update = True @@ -854,7 +846,8 @@ class ArchipackCutter(): self.update_parent(context, o) def update_path(self, context): - user_def_path = context.scene.objects.get(self.user_defined_path) + + user_def_path = context.scene.objects.get(self.user_defined_path.strip()) if user_def_path is not None and user_def_path.type == 'CURVE': self.from_spline(context, user_def_path.matrix_world, @@ -912,7 +905,7 @@ class ArchipackCutter(): def manipulable_setup(self, context): self.manipulable_disable(context) - o = context.active_object + o = context.object n_parts = self.n_parts + 1 diff --git a/archipack/archipack_door.py b/archipack/archipack_door.py index 3e85cf31..03e3d5e1 100644 --- a/archipack/archipack_door.py +++ b/archipack/archipack_door.py @@ -40,7 +40,7 @@ from .panel import Panel as DoorPanel from .archipack_handle import create_handle, door_handle_horizontal_01 from .archipack_manipulator import Manipulable from .archipack_preset import ArchipackPreset, PresetMenuOperator -from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool +from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool, ArchipackCollectionManager from .archipack_gl import FeedbackPanel from .archipack_keymaps import Keymaps @@ -60,83 +60,83 @@ def update_childs(self, context): class archipack_door_panel(ArchipackObject, PropertyGroup): - x = FloatProperty( + x : FloatProperty( name='Width', min=0.25, default=100.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='Width' ) - y = FloatProperty( + y : FloatProperty( name='Depth', min=0.001, default=0.02, precision=2, unit='LENGTH', subtype='DISTANCE', description='depth' ) - z = FloatProperty( + z : FloatProperty( name='Height', min=0.1, default=2.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='height' ) - direction = IntProperty( + direction : IntProperty( name="Direction", min=0, max=1, description="open direction" ) - model = IntProperty( + model : IntProperty( name="Model", min=0, max=3, default=0, description="Model" ) - chanfer = FloatProperty( + chanfer : FloatProperty( name='Bevel', min=0.001, default=0.005, precision=3, unit='LENGTH', subtype='DISTANCE', description='chanfer' ) - panel_spacing = FloatProperty( + panel_spacing : FloatProperty( name='Spacing', min=0.001, default=0.1, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance between panels' ) - panel_bottom = FloatProperty( + panel_bottom : FloatProperty( name='Bottom', min=0.0, default=0.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from bottom' ) - panel_border = FloatProperty( + panel_border : FloatProperty( name='Border', min=0.001, default=0.2, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from border' ) - panels_x = IntProperty( + panels_x : IntProperty( name="# h", min=1, max=50, default=1, description="panels h" ) - panels_y = IntProperty( + panels_y : IntProperty( name="# v", min=1, max=50, default=1, description="panels v" ) - panels_distrib = EnumProperty( + panels_distrib : EnumProperty( name='distribution', items=( ('REGULAR', 'Regular', '', 0), @@ -144,7 +144,7 @@ class archipack_door_panel(ArchipackObject, PropertyGroup): ), default='REGULAR' ) - handle = EnumProperty( + handle : EnumProperty( name='Shape', items=( ('NONE', 'No handle', '', 0), @@ -559,7 +559,7 @@ class archipack_door_panel(ArchipackObject, PropertyGroup): def remove_handle(self, context, o): handle = self.find_handle(o) if handle is not None: - context.scene.objects.unlink(handle) + self.unlink_object_from_scene(handle) bpy.data.objects.remove(handle, do_unlink=True) def update(self, context): @@ -584,7 +584,7 @@ class ARCHIPACK_PT_door_panel(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' # bl_context = 'object' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -600,88 +600,88 @@ class ARCHIPACK_PT_door_panel(Panel): # ------------------------------------------------------------------ -class ARCHIPACK_OT_door_panel(Operator): +class ARCHIPACK_OT_door_panel(ArchipackCollectionManager, Operator): bl_idname = "archipack.door_panel" bl_label = "Door model 1" bl_description = "Door model 1" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - x = FloatProperty( + x : FloatProperty( name='Width', min=0.1, default=0.80, precision=2, unit='LENGTH', subtype='DISTANCE', description='Width' ) - z = FloatProperty( + z : FloatProperty( name='Height', min=0.1, default=2.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='height' ) - y = FloatProperty( + y : FloatProperty( name='Depth', min=0.001, default=0.02, precision=2, unit='LENGTH', subtype='DISTANCE', description='Depth' ) - direction = IntProperty( + direction : IntProperty( name="Direction", min=0, max=1, description="open direction" ) - model = IntProperty( + model : IntProperty( name="Model", min=0, max=3, description="panel type" ) - chanfer = FloatProperty( + chanfer : FloatProperty( name='Bevel', min=0.001, default=0.005, precision=3, unit='LENGTH', subtype='DISTANCE', description='chanfer' ) - panel_spacing = FloatProperty( + panel_spacing : FloatProperty( name='Spacing', min=0.001, default=0.1, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance between panels' ) - panel_bottom = FloatProperty( + panel_bottom : FloatProperty( name='Bottom', min=0.0, default=0.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from bottom' ) - panel_border = FloatProperty( + panel_border : FloatProperty( name='Border', min=0.001, default=0.2, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from border' ) - panels_x = IntProperty( + panels_x : IntProperty( name="# h", min=1, max=50, default=1, description="panels h" ) - panels_y = IntProperty( + panels_y : IntProperty( name="# v", min=1, max=50, default=1, description="panels v" ) - panels_distrib = EnumProperty( + panels_distrib : EnumProperty( name='Distribution', items=( ('REGULAR', 'Regular', '', 0), @@ -689,7 +689,7 @@ class ARCHIPACK_OT_door_panel(Operator): ), default='REGULAR' ) - handle = EnumProperty( + handle : EnumProperty( name='Shape', items=( ('NONE', 'No handle', '', 0), @@ -697,14 +697,14 @@ class ARCHIPACK_OT_door_panel(Operator): ), default='BOTH' ) - material = StringProperty( + material : StringProperty( default="" ) def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def create(self, context): """ @@ -727,7 +727,7 @@ class ARCHIPACK_OT_door_panel(Operator): d.panels_x = self.panels_x d.panels_y = self.panels_y d.handle = self.handle - context.scene.objects.link(o) + self.link_object_to_scene(context, o) o.lock_location[0] = True o.lock_location[1] = True o.lock_location[2] = True @@ -736,8 +736,8 @@ class ARCHIPACK_OT_door_panel(Operator): o.lock_scale[0] = True o.lock_scale[1] = True o.lock_scale[2] = True - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o m = o.archipack_material.add() m.category = "door" m.material = self.material @@ -751,8 +751,8 @@ class ARCHIPACK_OT_door_panel(Operator): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -769,14 +769,14 @@ class ARCHIPACK_OT_select_parent(Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def execute(self, context): if context.mode == "OBJECT": if context.active_object is not None and context.active_object.parent is not None: bpy.ops.object.select_all(action="DESELECT") - context.active_object.parent.select = True - context.scene.objects.active = context.active_object.parent + context.active_object.parent.select_set(state=True) + context.view_layer.objects.active = context.active_object.parent return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -789,117 +789,117 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): parent parametric object create/remove/update her own childs """ - x = FloatProperty( + x : FloatProperty( name='Width', min=0.25, default=100.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='Width', update=update, ) - y = FloatProperty( + y : FloatProperty( name='Depth', min=0.1, default=0.20, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='Depth', update=update, ) - z = FloatProperty( + z : FloatProperty( name='Height', min=0.1, default=2.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='height', update=update, ) - frame_x = FloatProperty( + frame_x : FloatProperty( name='Width', min=0, default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='frame width', update=update, ) - frame_y = FloatProperty( + frame_y : FloatProperty( name='Depth', default=0.03, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='frame depth', update=update, ) - direction = IntProperty( + direction : IntProperty( name="Direction", min=0, max=1, description="open direction", update=update, ) - door_y = FloatProperty( + door_y : FloatProperty( name='Depth', min=0.001, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='depth', update=update, ) - door_offset = FloatProperty( + door_offset : FloatProperty( name='Offset', min=0, default=0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='offset', update=update, ) - model = IntProperty( + model : IntProperty( name="Model", min=0, max=3, default=0, description="Model", update=update, ) - n_panels = IntProperty( + n_panels : IntProperty( name="Panels", min=1, max=2, default=1, description="number of panels", update=update ) - chanfer = FloatProperty( + chanfer : FloatProperty( name='Bevel', min=0.001, default=0.005, precision=3, step=0.01, unit='LENGTH', subtype='DISTANCE', description='chanfer', update=update_childs, ) - panel_spacing = FloatProperty( + panel_spacing : FloatProperty( name='Spacing', min=0.001, default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='distance between panels', update=update_childs, ) - panel_bottom = FloatProperty( + panel_bottom : FloatProperty( name='Bottom', min=0.0, default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='distance from bottom', update=update_childs, ) - panel_border = FloatProperty( + panel_border : FloatProperty( name='Border', min=0.001, default=0.2, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='distance from border', update=update_childs, ) - panels_x = IntProperty( + panels_x : IntProperty( name="# h", min=1, max=50, default=1, description="panels h", update=update_childs, ) - panels_y = IntProperty( + panels_y : IntProperty( name="# v", min=1, max=50, default=1, description="panels v", update=update_childs, ) - panels_distrib = EnumProperty( + panels_distrib : EnumProperty( name='Distribution', items=( ('REGULAR', 'Regular', '', 0), @@ -907,7 +907,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): ), default='REGULAR', update=update_childs, ) - handle = EnumProperty( + handle : EnumProperty( name='Handle', items=( ('NONE', 'No handle', '', 0), @@ -915,19 +915,19 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): ), default='BOTH', update=update_childs, ) - hole_margin = FloatProperty( + hole_margin : FloatProperty( name='Hole margin', min=0.0, default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='how much hole surround wall' ) - flip = BoolProperty( + flip : BoolProperty( default=False, update=update, description='flip outside and outside material of hole' ) - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update @@ -1032,13 +1032,13 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): if archipack_door_panel.filter(child): self.remove_handle(context, child) to_remove -= 1 - context.scene.objects.unlink(child) + self.unlink_object_from_scene(child) bpy.data.objects.remove(child, do_unlink=True) def remove_handle(self, context, o): handle = self.find_handle(o) if handle is not None: - context.scene.objects.unlink(handle) + self.unlink_object_from_scene(handle) bpy.data.objects.remove(handle, do_unlink=True) def create_childs(self, context, o): @@ -1112,7 +1112,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): id = c_names.index(c.data.name) except: self.remove_handle(context, c) - context.scene.objects.unlink(c) + self.unlink_object_from_scene(c) bpy.data.objects.remove(c, do_unlink=True) # children ordering may not be the same, so get the right l_childs order @@ -1130,7 +1130,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): for i, child in enumerate(childs): if order[i] < 0: p = bpy.data.objects.new("DoorPanel", child.data) - context.scene.objects.link(p) + self.link_object_to_scene(context, p) p.lock_location[0] = True p.lock_location[1] = True p.lock_location[2] = True @@ -1158,7 +1158,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): # MaterialUtils.add_handle_materials(h) h.location = handle.location.copy() elif h is not None: - context.scene.objects.unlink(h) + self.unlink_object_from_scene(h) bpy.data.objects.remove(h, do_unlink=True) def _synch_hole(self, context, linked, hole): @@ -1166,7 +1166,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): if l_hole is None: l_hole = bpy.data.objects.new("hole", hole.data) l_hole['archipack_hole'] = True - context.scene.objects.link(l_hole) + self.link_object_to_scene(context, l_hole) l_hole.parent = linked l_hole.matrix_world = linked.matrix_world.copy() l_hole.location = hole.location.copy() @@ -1178,8 +1178,8 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): synch childs nodes of linked objects """ bpy.ops.object.select_all(action='DESELECT') - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o childs = self.get_childs_panels(context, o) hole = self.find_hole(o) bpy.ops.object.select_linked(type='OBDATA') @@ -1253,8 +1253,8 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): child.matrix_world = o.matrix_world.copy() else: child = childs[child_n - 1] - child.select = True - context.scene.objects.active = child + child.select_set(state=True) + context.view_layer.objects.active = child props = archipack_door_panel.datablock(child) if props is not None: props.x = x @@ -1314,7 +1314,7 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): if hole_obj is None: m = bpy.data.meshes.new("hole") hole_obj = bpy.data.objects.new("hole", m) - context.scene.objects.link(hole_obj) + self.link_object_to_scene(context, hole_obj) hole_obj['archipack_hole'] = True hole_obj.parent = o hole_obj.matrix_world = o.matrix_world.copy() @@ -1339,19 +1339,19 @@ class archipack_door(ArchipackObject, Manipulable, PropertyGroup): m = bpy.data.meshes.new("hole") o = bpy.data.objects.new("hole", m) o['archipack_robusthole'] = True - context.scene.objects.link(o) + self.link_object_to_scene(context, o) v = Vector((0, 0, 0)) offset = Vector((0, -0.001, 0)) size = Vector((self.x + 2 * self.frame_x, self.z + self.frame_x + 0.001, self.y)) verts = hole.vertices(16, offset, v, v, size, v, 0, 0, shape_z=None, path_type='RECTANGLE') - verts = [tM * Vector(v) for v in verts] + verts = [tM @ Vector(v) for v in verts] faces = hole.faces(16, path_type='RECTANGLE') matids = hole.mat(16, 0, 1, path_type='RECTANGLE') uvs = hole.uv(16, v, v, size, v, 0, 0, 0, 0, path_type='RECTANGLE') bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return o @@ -1360,7 +1360,7 @@ class ARCHIPACK_PT_door(Panel): bl_label = "Door" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -1371,7 +1371,7 @@ class ARCHIPACK_PT_door(Panel): if not archipack_door.filter(o): return layout = self.layout - layout.operator('archipack.door_manipulate', icon='HAND') + layout.operator('archipack.door_manipulate', icon='VIEW_PAN') props = archipack_door.datablock(o) row = layout.row(align=True) row.operator('archipack.door', text="Refresh", icon='FILE_REFRESH').mode = 'REFRESH' @@ -1382,8 +1382,8 @@ class ARCHIPACK_PT_door(Panel): # box.label(text="Styles") row = box.row(align=True) row.operator("archipack.door_preset_menu", text=bpy.types.ARCHIPACK_OT_door_preset_menu.bl_label) - row.operator("archipack.door_preset", text="", icon='ZOOMIN') - row.operator("archipack.door_preset", text="", icon='ZOOMOUT').remove_active = True + row.operator("archipack.door_preset", text="", icon='ADD') + row.operator("archipack.door_preset", text="", icon='REMOVE').remove_active = True row = layout.row() box = row.box() box.label(text="Size") @@ -1434,82 +1434,82 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): bl_description = "Door" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - x = FloatProperty( + x : FloatProperty( name='width', min=0.1, default=0.80, precision=2, unit='LENGTH', subtype='DISTANCE', description='Width' ) - y = FloatProperty( + y : FloatProperty( name='depth', min=0.1, default=0.20, precision=2, unit='LENGTH', subtype='DISTANCE', description='Depth' ) - z = FloatProperty( + z : FloatProperty( name='height', min=0.1, default=2.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='height' ) - direction = IntProperty( + direction : IntProperty( name="direction", min=0, max=1, description="open direction" ) - n_panels = IntProperty( + n_panels : IntProperty( name="panels", min=1, max=2, default=1, description="number of panels" ) - chanfer = FloatProperty( + chanfer : FloatProperty( name='chanfer', min=0.001, default=0.005, precision=3, unit='LENGTH', subtype='DISTANCE', description='chanfer' ) - panel_spacing = FloatProperty( + panel_spacing : FloatProperty( name='spacing', min=0.001, default=0.1, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance between panels' ) - panel_bottom = FloatProperty( + panel_bottom : FloatProperty( name='bottom', default=0.0, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from bottom' ) - panel_border = FloatProperty( + panel_border : FloatProperty( name='border', min=0.001, default=0.2, precision=2, unit='LENGTH', subtype='DISTANCE', description='distance from border' ) - panels_x = IntProperty( + panels_x : IntProperty( name="panels h", min=1, max=50, default=1, description="panels h" ) - panels_y = IntProperty( + panels_y : IntProperty( name="panels v", min=1, max=50, default=1, description="panels v" ) - panels_distrib = EnumProperty( + panels_distrib : EnumProperty( name='distribution', items=( ('REGULAR', 'Regular', '', 0), @@ -1517,7 +1517,7 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): ), default='REGULAR' ) - handle = EnumProperty( + handle : EnumProperty( name='Shape', items=( ('NONE', 'No handle', '', 0), @@ -1525,7 +1525,7 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): ), default='BOTH' ) - mode = EnumProperty( + mode : EnumProperty( items=( ('CREATE', 'Create', '', 0), ('DELETE', 'Delete', '', 1), @@ -1556,13 +1556,13 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): d.panels_x = self.panels_x d.panels_y = self.panels_y d.handle = self.handle - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o self.add_material(o) self.load_preset(d) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return o def delete(self, context): @@ -1571,16 +1571,16 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): bpy.ops.archipack.disable_manipulate() for child in o.children: if 'archipack_hole' in child: - context.scene.objects.unlink(child) + self.unlink_object_from_scene(child) bpy.data.objects.remove(child, do_unlink=True) elif child.data is not None and 'archipack_door_panel' in child.data: for handle in child.children: if 'archipack_handle' in handle: - context.scene.objects.unlink(handle) + self.unlink_object_from_scene(handle) bpy.data.objects.remove(handle, do_unlink=True) - context.scene.objects.unlink(child) + self.unlink_object_from_scene(child) bpy.data.objects.remove(child, do_unlink=True) - context.scene.objects.unlink(o) + self.unlink_object_from_scene(o) bpy.data.objects.remove(o, do_unlink=True) def update(self, context): @@ -1593,8 +1593,8 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): if linked != o: archipack_door.datablock(linked).update(context) bpy.ops.object.select_all(action="DESELECT") - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o def unique(self, context): act = context.active_object @@ -1602,12 +1602,12 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): bpy.ops.object.select_all(action="DESELECT") for o in sel: if archipack_door.filter(o): - o.select = True + o.select_set(state=True) for child in o.children: if 'archipack_hole' in child or (child.data is not None and 'archipack_door_panel' in child.data): child.hide_select = False - child.select = True + child.select_set(state=True) if len(context.selected_objects) > 0: bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True, material=False, texture=False, animation=False) @@ -1615,9 +1615,9 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): if 'archipack_hole' in child: child.hide_select = True bpy.ops.object.select_all(action="DESELECT") - context.scene.objects.active = act + context.view_layer.objects.active = act for o in sel: - o.select = True + o.select_set(state=True) def execute(self, context): if context.mode == "OBJECT": @@ -1625,8 +1625,8 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): bpy.ops.object.select_all(action="DESELECT") o = self.create(context) o.location = bpy.context.scene.cursor_location - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() elif self.mode == 'DELETE': self.delete(context) @@ -1640,14 +1640,14 @@ class ARCHIPACK_OT_door(ArchipackCreateTool, Operator): return {'CANCELLED'} -class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator): +class ARCHIPACK_OT_door_draw(ArchipackDrawTool, Operator): bl_idname = "archipack.door_draw" bl_label = "Draw Doors" bl_description = "Draw Doors over walls" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - filepath = StringProperty(default="") + filepath : StringProperty(default="") feedback = None stack = [] object_name = "" @@ -1659,7 +1659,7 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def draw_callback(self, _self, context): self.feedback.draw(context) @@ -1670,32 +1670,32 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator): if archipack_door.filter(o): - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o if event.shift: bpy.ops.archipack.door(mode="UNIQUE") new_w = o.copy() new_w.data = o.data - context.scene.objects.link(new_w) + self.link_object_to_scene(context, new_w) # instance subs for child in o.children: if "archipack_hole" not in child: new_c = child.copy() new_c.data = child.data new_c.parent = new_w - context.scene.objects.link(new_c) + self.link_object_to_scene(context, new_c) # dup handle if any for c in child.children: new_h = c.copy() new_h.data = c.data new_h.parent = new_c - context.scene.objects.link(new_h) + self.link_object_to_scene(context, new_h) o = new_w - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o else: bpy.ops.archipack.door(auto_manipulate=False, filepath=self.filepath) @@ -1704,13 +1704,13 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator): self.object_name = o.name bpy.ops.archipack.generate_hole('INVOKE_DEFAULT') - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o def modal(self, context, event): context.area.tag_redraw() - o = context.scene.objects.get(self.object_name) + o = context.scene.objects.get(self.object_name.strip()) if o is None: return {'FINISHED'} @@ -1722,14 +1722,14 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator): # hide hole from raycast if hole is not None: - o.hide = True - hole.hide = True + o.hide_viewport = True + hole.hide_viewport = True - res, tM, wall, y = self.mouse_hover_wall(context, event) + res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event) if hole is not None: - o.hide = False - hole.hide = False + o.hide_viewport = False + hole.hide_viewport = False if res and d is not None: o.matrix_world = tM @@ -1749,14 +1749,14 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator): if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: if wall is not None: - context.scene.objects.active = wall - wall.select = True + context.view_layer.objects.active = wall + wall.select_set(state=True) if bpy.ops.archipack.single_boolean.poll(): bpy.ops.archipack.single_boolean() - wall.select = False + wall.select_set(state=False) # o must be a door here if d is not None: - context.scene.objects.active = o + context.view_layer.objects.active = o self.stack.append(o) self.add_object(context, event) context.active_object.matrix_world = tM @@ -1771,9 +1771,9 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator): ): if len(self.stack) > 0: last = self.stack.pop() - context.scene.objects.active = last + context.view_layer.objects.active = last bpy.ops.archipack.door(mode="DELETE") - context.scene.objects.active = o + context.view_layer.objects.active = o return {'RUNNING_MODAL'} if event.value == 'RELEASE': @@ -1797,11 +1797,11 @@ class ARCHIPACK_OT_door_draw(ArchpackDrawTool, Operator): # invoke with alt pressed will use current object as basis for linked copy if self.filepath == '' and archipack_door.filter(context.active_object): o = context.active_object - context.scene.objects.active = None + context.view_layer.objects.active = None bpy.ops.object.select_all(action="DESELECT") if o is not None: - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.add_object(context, event) self.feedback = FeedbackPanel() self.feedback.instructions(context, "Draw a door", "Click & Drag over a wall", [ diff --git a/archipack/archipack_fence.py b/archipack/archipack_fence.py index c2989be2..5f987779 100644 --- a/archipack/archipack_fence.py +++ b/archipack/archipack_fence.py @@ -268,7 +268,7 @@ class FenceGenerator(): co.z *= self.user_defined_post_scale.z if 'Slope' in g: co.z += co.y * slope - verts.append(tM * co) + verts.append(tM @ co) matids += self.user_defined_mat faces += [tuple([i + f for i in p.vertices]) for p in m.polygons] uvs += self.user_defined_uvs @@ -341,7 +341,7 @@ class FenceGenerator(): if s < n_sections: v1 = subs[s + 1][0].v.normalized() dir = (v0 + v1).normalized() - scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1)))) + scale = 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1))))) for p in profile: x, y = n.p + scale * p.x * dir z = zl + p.y + altitude @@ -556,7 +556,7 @@ class FenceGenerator(): if s < n_sections: v1 = sections[s + 1][0].v.normalized() dir = (v0 + v1).normalized() - scale = min(10, 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1))))) + scale = min(10, 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1) ))))) for p in profile: # x, y = n.p + scale * (x_offset + p.x) * dir x, y = n.p + scale * p.x * dir @@ -673,7 +673,7 @@ materials_enum = ( class archipack_fence_material(PropertyGroup): - index = EnumProperty( + index : EnumProperty( items=materials_enum, default='0', update=update @@ -684,7 +684,7 @@ class archipack_fence_material(PropertyGroup): find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: props = archipack_fence.datablock(o) if props: @@ -700,7 +700,7 @@ class archipack_fence_material(PropertyGroup): class archipack_fence_part(PropertyGroup): - type = EnumProperty( + type : EnumProperty( items=( ('S_FENCE', 'Straight fence', '', 0), ('C_FENCE', 'Curved fence', '', 1), @@ -708,21 +708,21 @@ class archipack_fence_part(PropertyGroup): default='S_FENCE', update=update_type ) - length = FloatProperty( + length : FloatProperty( name="Length", min=0.01, default=2.0, unit='LENGTH', subtype='DISTANCE', update=update ) - radius = FloatProperty( + radius : FloatProperty( name="Radius", min=0.01, default=0.7, unit='LENGTH', subtype='DISTANCE', update=update ) - da = FloatProperty( + da : FloatProperty( name="Angle", min=-pi, max=pi, @@ -730,7 +730,7 @@ class archipack_fence_part(PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - a0 = FloatProperty( + a0 : FloatProperty( name="Start angle", min=-2 * pi, max=2 * pi, @@ -738,20 +738,20 @@ class archipack_fence_part(PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - dz = FloatProperty( + dz : FloatProperty( name="delta z", default=0, unit='LENGTH', subtype='DISTANCE' ) - manipulators = CollectionProperty(type=archipack_manipulator) + manipulators : CollectionProperty(type=archipack_manipulator) def find_datablock_in_selection(self, context): """ find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: props = archipack_fence.datablock(o) if props is not None: @@ -783,43 +783,43 @@ class archipack_fence_part(PropertyGroup): class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): - parts = CollectionProperty(type=archipack_fence_part) - user_defined_path = StringProperty( + parts : CollectionProperty(type=archipack_fence_part) + user_defined_path : StringProperty( name="User defined", update=update_path ) - user_defined_spline = IntProperty( + user_defined_spline : IntProperty( name="Spline index", min=0, default=0, update=update_path ) - user_defined_resolution = IntProperty( + user_defined_resolution : IntProperty( name="Resolution", min=1, max=128, default=12, update=update_path ) - n_parts = IntProperty( + n_parts : IntProperty( name="Parts", min=1, default=1, update=update_manipulators ) - x_offset = FloatProperty( + x_offset : FloatProperty( name="Offset", default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - radius = FloatProperty( + radius : FloatProperty( name="Radius", min=0.01, default=0.7, unit='LENGTH', subtype='DISTANCE', update=update ) - da = FloatProperty( + da : FloatProperty( name="Angle", min=-pi, max=pi, @@ -827,7 +827,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - angle_limit = FloatProperty( + angle_limit : FloatProperty( name="Angle", min=0, max=2 * pi, @@ -835,7 +835,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update_manipulators ) - shape = EnumProperty( + shape : EnumProperty( items=( ('RECTANGLE', 'Straight', '', 0), ('CIRCLE', 'Curved ', '', 1) @@ -843,106 +843,106 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): default='RECTANGLE', update=update ) - post = BoolProperty( + post : BoolProperty( name='Enable', default=True, update=update ) - post_spacing = FloatProperty( + post_spacing : FloatProperty( name="Spacing", min=0.1, default=1.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_x = FloatProperty( + post_x : FloatProperty( name="Width", min=0.001, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_y = FloatProperty( + post_y : FloatProperty( name="Length", min=0.001, max=1000, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_z = FloatProperty( + post_z : FloatProperty( name="Height", min=0.001, default=1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_alt = FloatProperty( + post_alt : FloatProperty( name="Altitude", default=0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - user_defined_post_enable = BoolProperty( + user_defined_post_enable : BoolProperty( name="User", update=update, default=True ) - user_defined_post = StringProperty( + user_defined_post : StringProperty( name="User defined", update=update ) - idmat_post = EnumProperty( + idmat_post : EnumProperty( name="Post", items=materials_enum, default='1', update=update ) - subs = BoolProperty( + subs : BoolProperty( name='Enable', default=False, update=update ) - subs_spacing = FloatProperty( + subs_spacing : FloatProperty( name="Spacing", min=0.05, default=0.10, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_x = FloatProperty( + subs_x : FloatProperty( name="Width", min=0.001, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_y = FloatProperty( + subs_y : FloatProperty( name="Length", min=0.001, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_z = FloatProperty( + subs_z : FloatProperty( name="Height", min=0.001, default=1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_alt = FloatProperty( + subs_alt : FloatProperty( name="Altitude", default=0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_offset_x = FloatProperty( + subs_offset_x : FloatProperty( name="Offset", default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_bottom = EnumProperty( + subs_bottom : EnumProperty( name="Bottom", items=( ('STEP', 'Follow step', '', 0), @@ -951,78 +951,78 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): default='STEP', update=update ) - user_defined_subs_enable = BoolProperty( + user_defined_subs_enable : BoolProperty( name="User", update=update, default=True ) - user_defined_subs = StringProperty( + user_defined_subs : StringProperty( name="User defined", update=update ) - idmat_subs = EnumProperty( + idmat_subs : EnumProperty( name="Subs", items=materials_enum, default='1', update=update ) - panel = BoolProperty( + panel : BoolProperty( name='Enable', default=True, update=update ) - panel_alt = FloatProperty( + panel_alt : FloatProperty( name="Altitude", default=0.25, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - panel_x = FloatProperty( + panel_x : FloatProperty( name="Width", min=0.001, default=0.01, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - panel_z = FloatProperty( + panel_z : FloatProperty( name="Height", min=0.001, default=0.6, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - panel_dist = FloatProperty( + panel_dist : FloatProperty( name="Spacing", min=0.001, default=0.05, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - panel_offset_x = FloatProperty( + panel_offset_x : FloatProperty( name="Offset", default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - idmat_panel = EnumProperty( + idmat_panel : EnumProperty( name="Panels", items=materials_enum, default='2', update=update ) - rail = BoolProperty( + rail : BoolProperty( name="Enable", update=update, default=False ) - rail_n = IntProperty( + rail_n : IntProperty( name="#", default=1, min=0, max=31, update=update ) - rail_x = FloatVectorProperty( + rail_x : FloatVectorProperty( name="Width", default=[ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, @@ -1036,7 +1036,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', update=update ) - rail_z = FloatVectorProperty( + rail_z : FloatVectorProperty( name="Height", default=[ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, @@ -1050,7 +1050,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', update=update ) - rail_offset = FloatVectorProperty( + rail_offset : FloatVectorProperty( name="Offset", default=[ 0, 0, 0, 0, 0, 0, 0, 0, @@ -1063,7 +1063,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', update=update ) - rail_alt = FloatVectorProperty( + rail_alt : FloatVectorProperty( name="Altitude", default=[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -1076,43 +1076,43 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', update=update ) - rail_mat = CollectionProperty(type=archipack_fence_material) + rail_mat : CollectionProperty(type=archipack_fence_material) - handrail = BoolProperty( + handrail : BoolProperty( name="Enable", update=update, default=True ) - handrail_offset = FloatProperty( + handrail_offset : FloatProperty( name="Offset", default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_alt = FloatProperty( + handrail_alt : FloatProperty( name="Altitude", default=1.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_extend = FloatProperty( + handrail_extend : FloatProperty( name="Extend", min=0, default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_slice = BoolProperty( + handrail_slice : BoolProperty( name='Slice', default=True, update=update ) - handrail_slice_right = BoolProperty( + handrail_slice_right : BoolProperty( name='Slice', default=True, update=update ) - handrail_profil = EnumProperty( + handrail_profil : EnumProperty( name="Profil", items=( ('SQUARE', 'Square', '', 0), @@ -1122,28 +1122,28 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): default='SQUARE', update=update ) - handrail_x = FloatProperty( + handrail_x : FloatProperty( name="Width", min=0.001, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_y = FloatProperty( + handrail_y : FloatProperty( name="Height", min=0.001, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_radius = FloatProperty( + handrail_radius : FloatProperty( name="Radius", min=0.001, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - idmat_handrail = EnumProperty( + idmat_handrail : EnumProperty( name="Handrail", items=materials_enum, default='0', @@ -1151,25 +1151,25 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): ) # UI layout related - parts_expand = BoolProperty( + parts_expand : BoolProperty( default=False ) - rail_expand = BoolProperty( + rail_expand : BoolProperty( default=False ) - idmats_expand = BoolProperty( + idmats_expand : BoolProperty( default=False ) - handrail_expand = BoolProperty( + handrail_expand : BoolProperty( default=False ) - post_expand = BoolProperty( + post_expand : BoolProperty( default=False ) - panel_expand = BoolProperty( + panel_expand : BoolProperty( default=False ) - subs_expand = BoolProperty( + subs_expand : BoolProperty( default=False ) @@ -1178,7 +1178,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): # .auto_update = False # bulk changes # .auto_update = True - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update_manipulators @@ -1226,7 +1226,6 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): # add parts for i in range(len(self.parts), self.n_parts): self.parts.add() - self.setup_manipulators() def interpolate_bezier(self, pts, wM, p0, p1, resolution): @@ -1234,18 +1233,18 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): # since this can lower points count by a resolution factor # use normalized to handle non linear t if resolution == 0: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: v = (p1.co - p0.co).normalized() d1 = (p0.handle_right - p0.co).normalized() d2 = (p1.co - p1.handle_left).normalized() if d1 == v and d2 == v: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: - seg = interpolate_bezier(wM * p0.co, - wM * p0.handle_right, - wM * p1.handle_left, - wM * p1.co, + seg = interpolate_bezier(wM @ p0.co, + wM @ p0.handle_right, + wM @ p1.handle_left, + wM @ p1.co, resolution + 1) for i in range(resolution): pts.append(seg[i].to_3d()) @@ -1264,7 +1263,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): pts = [] if spline.type == 'POLY': pt = spline.points[0].co - pts = [wM * p.co.to_3d() for p in spline.points] + pts = [wM @ p.co.to_3d() for p in spline.points] if spline.use_cyclic_u: pts.append(pts[0]) elif spline.type == 'BEZIER': @@ -1280,7 +1279,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): self.interpolate_bezier(pts, wM, p0, p1, resolution) pts.append(pts[0]) else: - pts.append(wM * points[-1].co) + pts.append(wM @ points[-1].co) auto_update = self.auto_update self.auto_update = False @@ -1305,15 +1304,10 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): self.auto_update = auto_update - o.matrix_world = tM * Matrix([ - [1, 0, 0, pt.x], - [0, 1, 0, pt.y], - [0, 0, 1, pt.z], - [0, 0, 0, 1] - ]) + o.matrix_world = tM @ Matrix.Translation(pt) def update_path(self, context): - path = context.scene.objects.get(self.user_defined_path) + path = context.scene.objects.get(self.user_defined_path.strip()) if path is not None and path.type == 'CURVE': splines = path.data.splines if len(splines) > self.user_defined_spline: @@ -1335,7 +1329,6 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): return g def update(self, context, manipulable_refresh=False): - o = self.find_in_selection(context, self.auto_update) if o is None: @@ -1359,7 +1352,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): if self.user_defined_post_enable: # user defined posts - user_def_post = context.scene.objects.get(self.user_defined_post) + user_def_post = context.scene.objects.get(self.user_defined_post.strip()) if user_def_post is not None and user_def_post.type == 'MESH': g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z) @@ -1373,7 +1366,7 @@ class archipack_fence(ArchipackObject, Manipulable, PropertyGroup): # user defined subs if self.user_defined_subs_enable: - user_def_subs = context.scene.objects.get(self.user_defined_subs) + user_def_subs = context.scene.objects.get(self.user_defined_subs.strip()) if user_def_subs is not None and user_def_subs.type == 'MESH': g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z) @@ -1465,7 +1458,7 @@ class ARCHIPACK_PT_fence(Panel): bl_label = "Fence" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -1478,13 +1471,13 @@ class ARCHIPACK_PT_fence(Panel): scene = context.scene layout = self.layout row = layout.row(align=True) - row.operator('archipack.fence_manipulate', icon='HAND') + row.operator('archipack.fence_manipulate', icon='VIEW_PAN') box = layout.box() # box.label(text="Styles") row = box.row(align=True) row.operator("archipack.fence_preset_menu", text=bpy.types.ARCHIPACK_OT_fence_preset_menu.bl_label) - row.operator("archipack.fence_preset", text="", icon='ZOOMIN') - row.operator("archipack.fence_preset", text="", icon='ZOOMOUT').remove_active = True + row.operator("archipack.fence_preset", text="", icon='ADD') + row.operator("archipack.fence_preset", text="", icon='REMOVE').remove_active = True box = layout.box() row = box.row(align=True) row.operator("archipack.fence_curve_update", text="", icon='FILE_REFRESH') @@ -1619,15 +1612,18 @@ class ARCHIPACK_OT_fence(ArchipackCreateTool, Operator): bl_options = {'REGISTER', 'UNDO'} def create(self, context): + m = bpy.data.meshes.new("Fence") o = bpy.data.objects.new("Fence", m) d = m.archipack_fence.add() # make manipulators selectable + d.manipulable_selectable = True - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o self.load_preset(d) + self.add_material(o) return o @@ -1635,9 +1631,9 @@ class ARCHIPACK_OT_fence(ArchipackCreateTool, Operator): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) - o.location = bpy.context.scene.cursor_location - o.select = True - context.scene.objects.active = o + o.location = context.scene.cursor_location + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: @@ -1663,7 +1659,7 @@ class ARCHIPACK_OT_fence_curve_update(Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def execute(self, context): if context.mode == "OBJECT": @@ -1689,7 +1685,7 @@ class ARCHIPACK_OT_fence_from_curve(ArchipackCreateTool, Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def create(self, context): o = None @@ -1709,8 +1705,8 @@ class ARCHIPACK_OT_fence_from_curve(ArchipackCreateTool, Operator): bpy.ops.object.select_all(action="DESELECT") o = self.create(context) if o is not None: - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o # self.manipulate() return {'FINISHED'} else: diff --git a/archipack/archipack_floor.py b/archipack/archipack_floor.py index ed52fd8f..82c1f000 100644 --- a/archipack/archipack_floor.py +++ b/archipack/archipack_floor.py @@ -225,7 +225,7 @@ class FloorGenerator(CutAblePolygon, CutAbleGenerator): # rotate seg seg.rotate(a) # rotate delta from rotation center to segment start - dp = rM * (seg.p0 - p0) + dp = rM @ (seg.p0 - p0) seg.translate(dp) def translate(self, idx_from, dp): @@ -305,7 +305,7 @@ class FloorGenerator(CutAblePolygon, CutAbleGenerator): use_dissolve_boundaries=False, verts=bm.verts, edges=bm.edges, - delimit=1) + delimit={'MATERIAL'}) bm.verts.ensure_lookup_table() @@ -333,7 +333,7 @@ class FloorGenerator(CutAblePolygon, CutAbleGenerator): bmesh.ops.bevel(bm, geom=geom, offset=d.bevel_amount, - offset_type=0, + offset_type='OFFSET', segments=1, # d.bevel_res profile=0.5, vertex_only=False, @@ -366,7 +366,7 @@ class FloorGenerator(CutAblePolygon, CutAbleGenerator): use_dissolve_boundaries=False, verts=bm.verts, edges=bm.edges, - delimit=1) + delimit={'MATERIAL'}) bm.verts.ensure_lookup_table() @@ -925,7 +925,7 @@ class archipack_floor_part(PropertyGroup): A single manipulable polyline like segment polyline like segment line or arc based """ - type = EnumProperty( + type : EnumProperty( items=( ('S_SEG', 'Straight', '', 0), ('C_SEG', 'Curved', '', 1), @@ -933,19 +933,19 @@ class archipack_floor_part(PropertyGroup): default='S_SEG', update=update_type ) - length = FloatProperty( + length : FloatProperty( name="Length", min=0.01, default=2.0, update=update ) - radius = FloatProperty( + radius : FloatProperty( name="Radius", min=0.5, default=0.7, update=update ) - da = FloatProperty( + da : FloatProperty( name="Angle", min=-pi, max=pi, @@ -953,7 +953,7 @@ class archipack_floor_part(PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - a0 = FloatProperty( + a0 : FloatProperty( name="Start angle", min=-2 * pi, max=2 * pi, @@ -961,21 +961,21 @@ class archipack_floor_part(PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - offset = FloatProperty( + offset : FloatProperty( name="Offset", description="Side offset of segment", default=0, unit='LENGTH', subtype='DISTANCE', update=update ) - manipulators = CollectionProperty(type=archipack_manipulator) + manipulators : CollectionProperty(type=archipack_manipulator) def find_in_selection(self, context): """ find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: props = archipack_floor.datablock(o) if props: @@ -1002,35 +1002,35 @@ class archipack_floor_part(PropertyGroup): class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): - n_parts = IntProperty( + n_parts : IntProperty( name="Parts", min=1, default=1, update=update_manipulators ) - parts = CollectionProperty(type=archipack_floor_part) - user_defined_path = StringProperty( + parts : CollectionProperty(type=archipack_floor_part) + user_defined_path : StringProperty( name="User defined", update=update_path ) - user_defined_resolution = IntProperty( + user_defined_resolution : IntProperty( name="Resolution", min=1, max=128, default=12, update=update_path ) - closed = BoolProperty( + closed : BoolProperty( default=True, name="Close", options={'SKIP_SAVE'}, update=update_manipulators ) # UI layout related - parts_expand = BoolProperty( + parts_expand : BoolProperty( options={'SKIP_SAVE'}, default=False ) - pattern = EnumProperty( + pattern : EnumProperty( name='Floor Pattern', items=(("boards", "Boards", ""), ("square_parquet", "Square Parquet", ""), @@ -1044,7 +1044,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): default="boards", update=update ) - spacing = FloatProperty( + spacing : FloatProperty( name='Spacing', description='The amount of space between boards or tiles in both directions', unit='LENGTH', subtype='DISTANCE', @@ -1053,7 +1053,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): precision=2, update=update ) - thickness = FloatProperty( + thickness : FloatProperty( name='Thickness', description='Thickness', unit='LENGTH', subtype='DISTANCE', @@ -1062,13 +1062,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): precision=2, update=update ) - vary_thickness = BoolProperty( + vary_thickness : BoolProperty( name='Random Thickness', description='Vary thickness', default=False, update=update ) - thickness_variance = FloatProperty( + thickness_variance : FloatProperty( name='Variance', description='How much vary by', min=0, max=100, @@ -1078,7 +1078,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): update=update ) - board_width = FloatProperty( + board_width : FloatProperty( name='Width', description='The width', unit='LENGTH', subtype='DISTANCE', @@ -1087,13 +1087,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): precision=2, update=update ) - vary_width = BoolProperty( + vary_width : BoolProperty( name='Random Width', description='Vary width', default=False, update=update ) - width_variance = FloatProperty( + width_variance : FloatProperty( name='Variance', description='How much vary by', subtype='PERCENTAGE', @@ -1101,7 +1101,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): precision=2, update=update ) - width_spacing = FloatProperty( + width_spacing : FloatProperty( name='Width Spacing', description='The amount of space between boards in the width direction', unit='LENGTH', subtype='DISTANCE', @@ -1111,7 +1111,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): update=update ) - board_length = FloatProperty( + board_length : FloatProperty( name='Length', description='The length of the boards', unit='LENGTH', subtype='DISTANCE', @@ -1120,7 +1120,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): default=2, update=update ) - short_board_length = FloatProperty( + short_board_length : FloatProperty( name='Length', description='The length of the boards', unit='LENGTH', subtype='DISTANCE', @@ -1129,27 +1129,27 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): default=2, update=update ) - vary_length = BoolProperty( + vary_length : BoolProperty( name='Random Length', description='Vary board length', default=False, update=update ) - length_variance = FloatProperty( + length_variance : FloatProperty( name='Variance', description='How much board length can vary by', subtype='PERCENTAGE', min=1, max=100, default=50, precision=2, update=update ) - max_boards = IntProperty( + max_boards : IntProperty( name='Max Boards', description='Max number of boards in one row', min=1, default=20, update=update ) - length_spacing = FloatProperty( + length_spacing : FloatProperty( name='Length Spacing', description='The amount of space between boards in the length direction', unit='LENGTH', subtype='DISTANCE', @@ -1160,7 +1160,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): ) # parquet specific - boards_in_group = IntProperty( + boards_in_group : IntProperty( name='Boards in Group', description='Number of boards in a group', min=1, default=4, @@ -1168,7 +1168,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): ) # tile specific - tile_width = FloatProperty( + tile_width : FloatProperty( name='Width', description='Width of the tiles', unit='LENGTH', subtype='DISTANCE', @@ -1177,7 +1177,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): precision=2, update=update ) - tile_length = FloatProperty( + tile_length : FloatProperty( name='Length', description='Length of the tiles', unit='LENGTH', subtype='DISTANCE', @@ -1188,13 +1188,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): ) # grout - add_grout = BoolProperty( + add_grout : BoolProperty( name='Add Grout', description='Add grout', default=False, update=update ) - mortar_depth = FloatProperty( + mortar_depth : FloatProperty( name='Depth', description='The depth of the mortar from the surface of the tile', unit='LENGTH', subtype='DISTANCE', @@ -1206,19 +1206,19 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): ) # regular tile - random_offset = BoolProperty( + random_offset : BoolProperty( name='Random Offset', description='Random amount of offset for each row of tiles', update=update, default=False ) - offset = FloatProperty( + offset : FloatProperty( name='Offset', description='How much to offset each row of tiles', min=0, max=100, default=0, precision=2, update=update ) - offset_variance = FloatProperty( + offset_variance : FloatProperty( name='Variance', description='How much to vary the offset each row of tiles', min=0.001, max=100, default=50, @@ -1227,13 +1227,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): ) # bevel - bevel = BoolProperty( + bevel : BoolProperty( name='Bevel', update=update, default=False, description='Bevel upper faces' ) - bevel_amount = FloatProperty( + bevel_amount : FloatProperty( name='Bevel', description='Bevel amount', unit='LENGTH', subtype='DISTANCE', @@ -1241,29 +1241,29 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): precision=2, step=0.05, update=update ) - solidify = BoolProperty( + solidify : BoolProperty( name="Solidify", default=True, update=update ) - vary_materials = BoolProperty( + vary_materials : BoolProperty( name="Random Material", default=True, description="Vary Material indexes", update=update) - matid = IntProperty( + matid : IntProperty( name="#variations", min=1, max=10, default=7, description="Material index maxi", update=update) - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update_manipulators ) - z = FloatProperty( + z : FloatProperty( name="dumb z", description="Dumb z for manipulator placeholder", default=0.01, @@ -1324,18 +1324,18 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): # since this can lower points count by a resolution factor # use normalized to handle non linear t if resolution == 0: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: v = (p1.co - p0.co).normalized() d1 = (p0.handle_right - p0.co).normalized() d2 = (p1.co - p1.handle_left).normalized() if d1 == v and d2 == v: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: - seg = interpolate_bezier(wM * p0.co, - wM * p0.handle_right, - wM * p1.handle_left, - wM * p1.co, + seg = interpolate_bezier(wM @ p0.co, + wM @ p0.handle_right, + wM @ p1.handle_left, + wM @ p1.co, resolution + 1) for i in range(resolution): pts.append(seg[i].to_3d()) @@ -1343,7 +1343,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): def from_spline(self, context, wM, resolution, spline): pts = [] if spline.type == 'POLY': - pts = [wM * p.co.to_3d() for p in spline.points] + pts = [wM @ p.co.to_3d() for p in spline.points] if spline.use_cyclic_u: pts.append(pts[0]) elif spline.type == 'BEZIER': @@ -1358,18 +1358,13 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): self.interpolate_bezier(pts, wM, p0, p1, resolution) pts.append(pts[0]) else: - pts.append(wM * points[-1].co) + pts.append(wM @ points[-1].co) - pt = wM.inverted() * pts[0] + pt = wM.inverted() @ pts[0] # pretranslate o = self.find_in_selection(context, self.auto_update) - o.matrix_world = wM * Matrix([ - [1, 0, 0, pt.x], - [0, 1, 0, pt.y], - [0, 0, 1, pt.z], - [0, 0, 0, 1] - ]) + o.matrix_world = wM @ Matrix.Translation(pt) self.from_points(pts) def from_points(self, pts): @@ -1405,7 +1400,7 @@ class archipack_floor(ArchipackObject, Manipulable, PropertyGroup): self.auto_update = True def update_path(self, context): - user_def_path = context.scene.objects.get(self.user_defined_path) + user_def_path = context.scene.objects.get(self.user_defined_path.strip()) if user_def_path is not None and user_def_path.type == 'CURVE': self.from_spline( context, @@ -1565,8 +1560,8 @@ def update_operation(self, context): class archipack_floor_cutter_segment(ArchipackCutterPart, PropertyGroup): - manipulators = CollectionProperty(type=archipack_manipulator) - type = EnumProperty( + manipulators : CollectionProperty(type=archipack_manipulator) + type : EnumProperty( name="Type", items=( ('DEFAULT', 'Side', 'Side with rake', 0), @@ -1581,7 +1576,7 @@ class archipack_floor_cutter_segment(ArchipackCutterPart, PropertyGroup): ) def find_in_selection(self, context): - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: d = archipack_floor_cutter.datablock(o) if d: @@ -1599,7 +1594,7 @@ class archipack_floor_cutter_segment(ArchipackCutterPart, PropertyGroup): class archipack_floor_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup): - parts = CollectionProperty(type=archipack_floor_cutter_segment) + parts : CollectionProperty(type=archipack_floor_cutter_segment) def update_points(self, context, o, pts, update_parent=False): """ @@ -1615,11 +1610,11 @@ class archipack_floor_cutter(ArchipackCutter, ArchipackObject, Manipulable, Prop d = archipack_floor.datablock(o.parent) if d is not None: - o.parent.select = True - context.scene.objects.active = o.parent + o.parent.select_set(state=True) + context.view_layer.objects.active = o.parent d.update(context) - o.parent.select = False - context.scene.objects.active = o + o.parent.select_set(state=False) + context.view_layer.objects.active = o # ------------------------------------------------------------------ @@ -1632,7 +1627,7 @@ class ARCHIPACK_PT_floor(Panel): bl_label = "Flooring" bl_space_type = "VIEW_3D" bl_region_type = "UI" - bl_category = "Archipack" + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -1648,7 +1643,7 @@ class ARCHIPACK_PT_floor(Panel): # retrieve datablock of your object props = archipack_floor.datablock(o) # manipulate - layout.operator("archipack.floor_manipulate", icon="HAND") + layout.operator("archipack.floor_manipulate", icon="VIEW_PAN") layout.separator() box = layout.box() row = box.row(align=True) @@ -1658,10 +1653,10 @@ class ARCHIPACK_PT_floor(Panel): text=bpy.types.ARCHIPACK_OT_floor_preset_menu.bl_label) row.operator("archipack.floor_preset", text="", - icon='ZOOMIN') + icon='ADD') row.operator("archipack.floor_preset", text="", - icon='ZOOMOUT').remove_active = True + icon='REMOVE').remove_active = True box = layout.box() box.operator('archipack.floor_cutter').parent = o.name @@ -1759,7 +1754,7 @@ class ARCHIPACK_PT_floor_cutter(Panel): bl_label = "Floor Cutter" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -1772,7 +1767,7 @@ class ARCHIPACK_PT_floor_cutter(Panel): layout = self.layout scene = context.scene box = layout.box() - box.operator('archipack.floor_cutter_manipulate', icon='HAND') + box.operator('archipack.floor_cutter_manipulate', icon='VIEW_PAN') box.prop(prop, 'operation', text="") box = layout.box() box.label(text="From curve") @@ -1824,9 +1819,9 @@ class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator): p.a0 = angle_90 p.length = x d.n_parts = 4 - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o self.load_preset(d) self.add_material(o) return o @@ -1837,8 +1832,8 @@ class ARCHIPACK_OT_floor(ArchipackCreateTool, Operator): o = self.create(context) o.location = context.scene.cursor_location # activate manipulators at creation time - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: @@ -1864,7 +1859,7 @@ class ARCHIPACK_OT_floor_from_curve(ArchipackCreateTool, Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def create(self, context): curve = context.active_object @@ -1903,7 +1898,7 @@ class ARCHIPACK_OT_floor_from_wall(ArchipackCreateTool, Operator): wall = context.active_object wd = wall.data.archipack_wall2[0] bpy.ops.archipack.floor(auto_manipulate=False, filepath=self.filepath) - o = context.scene.objects.active + o = context.view_layer.objects.active d = archipack_floor.datablock(o) d.auto_update = False d.closed = True @@ -1931,8 +1926,8 @@ class ARCHIPACK_OT_floor_from_wall(ArchipackCreateTool, Operator): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o if self.auto_manipulate: bpy.ops.archipack.floor_manipulate('INVOKE_DEFAULT') return {'FINISHED'} @@ -1948,13 +1943,13 @@ class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator): bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - parent = StringProperty("") + parent : StringProperty("") def create(self, context): m = bpy.data.meshes.new("Floor Cutter") o = bpy.data.objects.new("Floor Cutter", m) d = m.archipack_floor_cutter.add() - parent = context.scene.objects.get(self.parent) + parent = context.scene.objects.get(self.parent.strip()) if parent is not None: o.parent = parent bbox = parent.bound_box @@ -1963,7 +1958,7 @@ class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator): x1, y1, z = bbox[6] x = 0.2 * (x1 - x0) y = 0.2 * (y1 - y0) - o.matrix_world = parent.matrix_world * Matrix([ + o.matrix_world = parent.matrix_world @ Matrix([ [1, 0, 0, -3 * x], [0, 1, 0, 0], [0, 0, 1, 0], @@ -1986,9 +1981,9 @@ class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator): o.location = context.scene.cursor_location # make manipulators selectable d.manipulable_selectable = True - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o # self.add_material(o) self.load_preset(d) update_operation(d, context) @@ -2001,8 +1996,8 @@ class ARCHIPACK_OT_floor_cutter(ArchipackCreateTool, Operator): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: diff --git a/archipack/archipack_gl.py b/archipack/archipack_gl.py index a85a3080..578098f9 100644 --- a/archipack/archipack_gl.py +++ b/archipack/archipack_gl.py @@ -24,10 +24,12 @@ # Author: Stephen Leger (s-leger) # # ---------------------------------------------------------- - import bgl import blf +import gpu +from gpu_extras.batch import batch_for_shader import bpy +import numpy as np from math import sin, cos, atan2, pi from mathutils import Vector, Matrix from bpy_extras import view3d_utils, object_utils @@ -44,6 +46,7 @@ class DefaultColorScheme: default to this when not found in addon prefs Colors are FloatVectorProperty of size 4 and type COLOR_GAMMA """ + text_size = 14 feedback_size_main = 16 feedback_size_title = 14 feedback_size_shortcut = 11 @@ -52,6 +55,167 @@ class DefaultColorScheme: feedback_colour_shortcut = (0.51, 0.51, 0.51, 1.0) feedback_shortcut_area = (0, 0.4, 0.6, 0.2) feedback_title_area = (0, 0.4, 0.6, 0.5) + handle_colour_normal = (1.0, 1.0, 1.0, 1.0) + handle_colour_hover = (1.0, 1.0, 0.0, 1.0) + handle_colour_active = (1.0, 0.0, 0.0, 1.0) + handle_colour_selected = (0.0, 0.0, 0.7, 1.0) + handle_colour_inactive = (0.3, 0.3, 0.3, 1.0) + + +def get_prefs(context): + global __name__ + try: + addon_name = __name__.split('.')[0] + prefs = context.user_preferences.addons[addon_name].preferences + except: + prefs = DefaultColorScheme + pass + return prefs + + +g_poly = None +g_image = None +g_line = None + + +line_vertSrc = ''' +uniform mat4 ModelViewProjectionMatrix; + +in vec2 pos; + +void main() +{ + gl_Position = ModelViewProjectionMatrix * vec4(pos, 0.0, 1.0); +} + +''' + +line_fragSrc = ''' +uniform vec4 color; + +out vec4 fragColor; + +void main() +{ + fragColor = color; +} +''' + +""" +# TESTS +_format = gpu.types.GPUVertFormat() +_pos_id = _format.attr_add( + id="pos", + comp_type="F32", + len=2, + fetch_mode="FLOAT") +coords = [(0,0), (200,100)] +vbo = gpu.types.GPUVertBuf(len=len(coords), format=_format) +""" + + +class GPU_Line: + def __init__(self): + # never call this in -b mode + if bpy.app.background: + return + self._format = gpu.types.GPUVertFormat() + self._pos_id = self._format.attr_add( + id="pos", + comp_type="F32", + len=2, + fetch_mode="FLOAT") + self.shader = gpu.types.GPUShader(line_vertSrc, line_fragSrc) + self.unif_color = self.shader.uniform_from_name("color") + self.color = np.array([0.0, 0.0, 0.0, 1.0], 'f') + + def batch_line_strip_create(self, coords): + vbo = gpu.types.GPUVertBuf(len=len(coords), format=self._format) + vbo.attr_fill(id=self._pos_id, data=coords) + batch_lines = gpu.types.GPUBatch(type="LINE_STRIP", buf=vbo) + batch_lines.program_set(self.shader) + return batch_lines + + def draw(self, color, list_verts_co): + if list_verts_co: + batch = self.batch_line_strip_create(list_verts_co) + self.color[0:4] = color[0:4] + self.shader.uniform_vector_float(self.unif_color, self.color, 4) + batch.draw() + del batch + + +class GPU_Poly: + + def __init__(self): + if bpy.app.background: + return + self._format = gpu.types.GPUVertFormat() + self._pos_id = self._format.attr_add( + id="pos", + comp_type="F32", + len=2, + fetch_mode="FLOAT") + self.shader = gpu.types.GPUShader(line_vertSrc, line_fragSrc) + self.unif_color = self.shader.uniform_from_name("color") + self.color = np.array([0.0, 0.0, 0.0, 0.5], 'f') + + def batch_create(self, coords): + vbo = gpu.types.GPUVertBuf(len=len(coords), format=self._format) + vbo.attr_fill(id=self._pos_id, data=coords) + batch = gpu.types.GPUBatch(type="TRI_FAN", buf=vbo) + # batch.program_set(self.shader) + # batch = batch_for_shader(self.shader, 'TRI_FAN', {"position": coords}) + return batch + + def draw(self, color, list_verts_co): + if list_verts_co: + self.shader.bind() + batch = self.batch_create(list_verts_co) + self.color[0:4] = color[0:4] + self.shader.uniform_vector_float(self.unif_color, self.color, 4) + batch.draw(self.shader) + + +class GPU_Image: + uvs = [(0, 0), (1, 0), (0, 1), (1, 1)] + indices = [(0, 1, 2), (2, 1, 3)] + + def __init__(self): + if bpy.app.background: + return + self._format = gpu.types.GPUVertFormat() + self._pos_id = self._format.attr_add( + id="pos", + comp_type="F32", + len=2, + fetch_mode="FLOAT") + self.shader = gpu.shader.from_builtin('2D_IMAGE') + + def batch_create(self, coords): + batch = batch_for_shader(self.shader, 'TRIS', + {"pos": coords, + "texCoord": self.uvs}, + indices=self.indices) + return batch + + def draw(self, texture_id, list_verts_co): + if list_verts_co: + batch = self.batch_create(list_verts_co) + + # in case someone disabled it before + bgl.glEnable(bgl.GL_TEXTURE_2D) + + # bind texture to image unit 0 + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, texture_id) + + self.shader.bind() + # tell shader to use the image that is bound to image unit 0 + self.shader.uniform_int("image", 0) + batch.draw(self.shader) + + bgl.glDisable(bgl.GL_TEXTURE_2D) class Gl(): @@ -61,9 +225,22 @@ class Gl(): 3 to convert pos from 3d 2 to keep pos as 2d absolute screen position """ + def __init__(self, - d=3, - colour=(0.0, 0.0, 0.0, 1.0)): + d=3, + colour=(0.0, 0.0, 0.0, 1.0)): + + global g_poly, g_image, g_line + + if g_poly is None: + g_poly = GPU_Poly() + + if g_image is None: + g_image = GPU_Image() + + if g_line is None: + g_line = GPU_Line() + # nth dimensions of input coords 3=word coords 2=pixel screen coords self.d = d self.pos_2d = Vector((0, 0)) @@ -77,13 +254,13 @@ class Gl(): """ coord given in local input coordsys """ if self.d == 2: - return coord + return Vector(coord) if render: return self.get_render_location(context, coord) region = context.region rv3d = context.region_data loc = view3d_utils.location_3d_to_region_2d(region, rv3d, coord, self.pos_2d) - return loc + return Vector(loc) def get_render_location(self, context, coord): scene = context.scene @@ -92,42 +269,32 @@ class Gl(): render_scale = scene.render.resolution_percentage / 100 render_size = (int(scene.render.resolution_x * render_scale), int(scene.render.resolution_y * render_scale)) - return [round(co_2d.x * render_size[0]), round(co_2d.y * render_size[1])] - - def _end(self): - - # print("_end") - bgl.glEnd() - bgl.glPopAttrib() - bgl.glLineWidth(1) - bgl.glDisable(bgl.GL_BLEND) - bgl.glColor4f(0.0, 0.0, 0.0, 1.0) - # print("_end %s" % (type(self).__name__)) + return Vector(round(co_2d.x * render_size[0]), round(co_2d.y * render_size[1])) class GlText(Gl): def __init__(self, - d=3, - label="", - value=None, - precision=2, - unit_mode='AUTO', - unit_type='SIZE', - dimension=1, - angle=0, - font_size=12, - colour=(1, 1, 1, 1), - z_axis=Vector((0, 0, 1))): + d=3, + label="", + value=None, + precision=4, + unit_mode='AUTO', + unit_type='SIZE', + dimension=1, + angle=0, + font_size=None, + colour=(1, 1, 1, 1), + z_axis=Vector((0, 0, 1))): """ d: [2|3] coords type: 2 for coords in screen pixels, 3 for 3d world location label : string label value : float value (will add unit according following settings) precision : integer rounding for values dimension : [1 - 3] nth dimension of unit (single, square, cubic) - unit_mode : ['AUTO','METER','CENTIMETER','MILIMETER','FEET','INCH','RADIANS','DEGREE'] + unit_mode : ['ADAPTIVE','METER','CENTIMETER','MILIMETER','FEET','INCH','RADIANS','DEGREE'] unit type to use to postfix values - auto use scene units setup + ADAPTIVE use scene units setup unit_type : ['SIZE','ANGLE'] unit type to add to value angle : angle to rotate text @@ -142,13 +309,17 @@ class GlText(Gl): self.dimension = dimension self.unit_type = unit_type self.unit_mode = unit_mode - - self.font_size = font_size + if font_size is None: + prefs = get_prefs(bpy.context) + self.font_size = prefs.text_size + else: + self.font_size = font_size self.angle = angle Gl.__init__(self, d) self.colour_inactive = colour # store text with units self._text = "" + self.cbuff = bgl.Buffer(bgl.GL_FLOAT, 4) def text_size(self, context): """ @@ -177,20 +348,25 @@ class GlText(Gl): def add_units(self, context): if self.value is None: return "" + system = context.scene.unit_settings.system if self.unit_type == 'ANGLE': scale = 1 + mode = 'ADAPTIVE' else: scale = context.scene.unit_settings.scale_length + mode = self.unit_mode + if mode == 'AUTO': + mode = context.scene.unit_settings.length_unit.upper() val = self.value * scale - mode = self.unit_mode - if mode == 'AUTO': + + if mode == 'ADAPTIVE': if self.unit_type == 'ANGLE': mode = context.scene.unit_settings.system_rotation else: - if context.scene.unit_settings.system == "IMPERIAL": + if system == "IMPERIAL": if round(val * (3.2808399 ** self.dimension), 2) >= 1.0: - mode = 'FEET' + mode = 'FOOT' else: mode = 'INCH' elif context.scene.unit_settings.system == "METRIC": @@ -201,7 +377,10 @@ class GlText(Gl): mode = 'CENTIMETER' else: mode = 'MILIMETER' + + # TODO: support for separate units (through 2.8 api) # convert values + unit = "" if mode == 'METER': unit = "m" elif mode == 'CENTIMETER': @@ -210,26 +389,38 @@ class GlText(Gl): elif mode == 'MILIMETER': val *= (1000 ** self.dimension) unit = 'mm' + elif mode in {'FOOT', 'FEET'}: + val *= (3.2808399 ** self.dimension) + unit = "ft" elif mode == 'INCH': val *= (39.3700787 ** self.dimension) unit = "in" - elif mode == 'FEET': - val *= (3.2808399 ** self.dimension) - unit = "ft" elif mode == 'RADIANS': unit = "" elif mode == 'DEGREES': val = self.value / pi * 180 unit = "°" - else: - unit = "" - if self.dimension == 2: - unit += "\u00b2" # Superscript two - elif self.dimension == 3: - unit += "\u00b3" # Superscript three - - fmt = "%1." + str(self.precision) + "f " + unit - return fmt % val + if system == 'IMPERIAL': + if self.dimension == 2: + unit = "sq " + unit + elif self.dimension == 3: + unit = "cu " + unit + elif system == 'METRIC': + if self.dimension == 2: + unit += "\u00b2" # Superscript two + elif self.dimension == 3: + unit += "\u00b3" # Superscript three + + fmt = "%1." + str(self.precision) + "f" + # remove trailing zeros + res = fmt % val + while res[-1] == '0': + res = res[:-1] + + if res[-1] == ".": + res = res + '0' + + return "{} {}".format(res, unit) def set_pos(self, context, value, pos_3d, direction, angle=0, normal=Vector((0, 0, 1))): self.up_axis = direction.normalized() @@ -243,66 +434,68 @@ class GlText(Gl): # print("draw_text %s %s" % (self.text, type(self).__name__)) self.render = render - x, y = self.position_2d_from_coord(context, self.pts[0], render) + p = self.position_2d_from_coord(context, self.pts[0], render) + # dirty fast assignment dpi, font_id = context.user_preferences.system.dpi, 0 - bgl.glColor4f(*self.colour) + + # self.cbuff[0:4] = self.colour + + # bgl.glEnableClientState(bgl.GL_COLOR_ARRAY) + # bgl.glColorPointer(4, bgl.GL_FLOAT, 0, self.cbuff) + blf.color(0, *self.colour) if self.angle != 0: blf.enable(font_id, blf.ROTATION) blf.rotation(font_id, self.angle) blf.size(font_id, self.font_size, dpi) - blf.position(font_id, x, y, 0) + blf.position(font_id, p.x, p.y, 0) blf.draw(font_id, self.text) if self.angle != 0: blf.disable(font_id, blf.ROTATION) + # bgl.glDisableClientState(bgl.GL_COLOR_ARRAY) + class GlBaseLine(Gl): def __init__(self, - d=3, - width=1, - style=bgl.GL_LINE, - closed=False): + d=3, + width=1, + style=bgl.GL_LINE, + closed=False, + n_pts=2): Gl.__init__(self, d) # default line width self.width = width # default line style self.style = style # allow closed lines - self.closed = False + self.closed = closed + + self.n_pts = n_pts def draw(self, context, render=False): """ render flag when rendering """ - - # print("draw_line %s" % (type(self).__name__)) - bgl.glPushAttrib(bgl.GL_ENABLE_BIT) - if self.style == bgl.GL_LINE_STIPPLE: - bgl.glLineStipple(1, 0x9999) - bgl.glEnable(self.style) + self.render = render bgl.glEnable(bgl.GL_BLEND) - if render: - # enable anti-alias on lines - bgl.glEnable(bgl.GL_LINE_SMOOTH) - bgl.glColor4f(*self.colour) bgl.glLineWidth(self.width) + list_verts_co = [ + tuple(self.position_2d_from_coord(context, pt, render)[0:2]) + for i, pt in enumerate(self.pts)] if self.closed: - bgl.glBegin(bgl.GL_LINE_LOOP) - else: - bgl.glBegin(bgl.GL_LINE_STRIP) - - for pt in self.pts: - x, y = self.position_2d_from_coord(context, pt, render) - bgl.glVertex2f(x, y) - self._end() + list_verts_co.append(list_verts_co[0]) + g_line.draw(self.colour, list_verts_co) + bgl.glLineWidth(1.0) + bgl.glDisable(bgl.GL_BLEND) class GlLine(GlBaseLine): """ 2d/3d Line """ + def __init__(self, d=3, p=None, v=None, p0=None, p1=None, z_axis=None): """ d=3 use 3d coords, d=2 use 2d pixels coords @@ -322,13 +515,13 @@ class GlLine(GlBaseLine): self.p = Vector(p0) self.v = Vector(p1) - self.p else: - self.p = Vector((0, 0, 0)) - self.v = Vector((0, 0, 0)) + self.p = Vector() + self.v = Vector() if z_axis is not None: self.z_axis = z_axis else: self.z_axis = Vector((0, 0, 1)) - GlBaseLine.__init__(self, d) + GlBaseLine.__init__(self, d, n_pts=2) @property def p0(self): @@ -391,7 +584,7 @@ class GlLine(GlBaseLine): def sized_normal(self, t, size): """ GlLine perpendicular on plane defined by z_axis and of given size - positioned at t in current line + positionned at t in current line lie on the right side p1 |--x @@ -425,7 +618,7 @@ class GlLine(GlBaseLine): v2d = self.v.to_2d() dl = v2d.length d = (self.v.x * dp.y - self.v.y * dp.x) / dl - t = (v2d * dp) / (dl * dl) + t = v2d.dot(dp) / (dl * dl) return t > 0 and t < 1, d, t @property @@ -436,10 +629,10 @@ class GlLine(GlBaseLine): class GlCircle(GlBaseLine): def __init__(self, - d=3, - radius=0, - center=Vector((0, 0, 0)), - z_axis=Vector((0, 0, 1))): + d=3, + radius=0, + center=Vector((0, 0, 0)), + z_axis=Vector((0, 0, 1))): self.r = radius self.c = center @@ -460,18 +653,19 @@ class GlCircle(GlBaseLine): self.z_axis = z self.a0 = 0 self.da = 2 * pi - GlBaseLine.__init__(self, d) + GlBaseLine.__init__(self, d, n_pts=60) def lerp(self, t): """ Linear interpolation """ a = self.a0 + t * self.da - return self.c + self.rM * Vector((self.r * cos(a), self.r * sin(a), 0)) + return self.c + self.rM @ Vector((self.r * cos(a), self.r * sin(a), 0)) @property def pts(self): n_pts = max(1, int(round(abs(self.da) / pi * 30, 0))) + self.n_pts = n_pts t_step = 1 / n_pts return [self.lerp(i * t_step) for i in range(n_pts + 1)] @@ -479,12 +673,12 @@ class GlCircle(GlBaseLine): class GlArc(GlCircle): def __init__(self, - d=3, - radius=0, - center=Vector((0, 0, 0)), - z_axis=Vector((0, 0, 1)), - a0=0, - da=0): + d=3, + radius=0, + center=Vector((0, 0, 0)), + z_axis=Vector((0, 0, 1)), + a0=0, + da=0): """ a0 and da arguments are in radians a0 = 0 on the x+ axis side @@ -526,8 +720,8 @@ class GlArc(GlCircle): ca = cos(a) sa = sin(a) n = GlLine(d=self.d, z_axis=self.z_axis) - n.p = self.c + self.rM * Vector((self.r * ca, self.r * sa, 0)) - n.v = self.rM * Vector((length * sa, -length * ca, 0)) + n.p = self.c + self.rM @ Vector((self.r * ca, self.r * sa, 0)) + n.v = self.rM @ Vector((length * sa, -length * ca, 0)) if self.da > 0: n.v = -n.v return n @@ -541,22 +735,23 @@ class GlArc(GlCircle): else: radius = self.r - offset return GlArc(d=self.d, - radius=radius, - center=self.c, - a0=self.a0, - da=self.da, - z_axis=self.z_axis) + radius=radius, + center=self.c, + a0=self.a0, + da=self.da, + z_axis=self.z_axis) class GlPolygon(Gl): def __init__(self, - colour=(0.0, 0.0, 0.0, 1.0), - d=3): - + colour=(0.3, 0.3, 0.3, 1.0), + d=3, n_pts=60): self.pts_3d = [] Gl.__init__(self, d, colour) + self.n_pts = min(n_pts, 60) + def set_pos(self, pts_3d): self.pts_3d = pts_3d @@ -568,52 +763,53 @@ class GlPolygon(Gl): """ render flag when rendering """ + # return - # print("draw_polygon") self.render = render - bgl.glPushAttrib(bgl.GL_ENABLE_BIT) - bgl.glEnable(bgl.GL_BLEND) - if render: - # enable anti-alias on polygons - bgl.glEnable(bgl.GL_POLYGON_SMOOTH) - bgl.glColor4f(*self.colour) - bgl.glBegin(bgl.GL_POLYGON) + if self.n_pts == 0: + return + + pts = self.pts - for pt in self.pts: - x, y = self.position_2d_from_coord(context, pt, render) - bgl.glVertex2f(x, y) - self._end() + g_vertices = [ + tuple(self.position_2d_from_coord(context, pt, render)[0:2]) + for i, pt in enumerate(pts)] + bgl.glEnable(bgl.GL_BLEND) + g_poly.draw(self.colour, g_vertices) + bgl.glDisable(bgl.GL_BLEND) class GlRect(GlPolygon): def __init__(self, - colour=(0.0, 0.0, 0.0, 1.0), - d=2): - GlPolygon.__init__(self, colour, d) + colour=(0.0, 0.0, 0.0, 1.0), + d=2): + GlPolygon.__init__(self, colour, d, n_pts=4) - def draw(self, context, render=False): + @property + def pts(self): + return self.pts_2d - self.render = render - bgl.glPushAttrib(bgl.GL_ENABLE_BIT) - bgl.glEnable(bgl.GL_BLEND) - if render: - # enable anti-alias on polygons - bgl.glEnable(bgl.GL_POLYGON_SMOOTH) - bgl.glColor4f(*self.colour) - p0 = self.pts[0] - p1 = self.pts[1] - bgl.glRectf(p0.x, p0.y, p1.x, p1.y) - self._end() + def draw(self, context, render=False): + pts = [ + self.position_2d_from_coord(context, pt, render) + for pt in self.pts_3d + ] + x0, y0 = pts[0] + x1, y1 = pts[1] + self.pts_2d = [Vector((x, y)) for x, y in [(x0, y0), (x0, y1), (x1, y1), (x1, y0)]] + GlPolygon.draw(self, context, render) class GlImage(Gl): def __init__(self, - d=2, - image=None): + d=2, + image=None): + # GImage bindcode[0] self.image = image self.colour_inactive = (1, 1, 1, 1) Gl.__init__(self, d) self.pts_2d = [Vector((0, 0)), Vector((10, 10))] + self.n_pts = 4 def set_pos(self, pts): self.pts_2d = pts @@ -625,38 +821,17 @@ class GlImage(Gl): def draw(self, context, render=False): if self.image is None: return - bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + p0 = self.pts[0] p1 = self.pts[1] - bgl.glEnable(bgl.GL_BLEND) - bgl.glColor4f(*self.colour) - bgl.glRectf(p0.x, p0.y, p1.x, p1.y) - self.image.gl_load() - bgl.glEnable(bgl.GL_BLEND) - bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.image.bindcode[0]) - bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST) - bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST) - bgl.glEnable(bgl.GL_TEXTURE_2D) - bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA) - # bgl.glColor4f(1, 1, 1, 1) - bgl.glBegin(bgl.GL_QUADS) - bgl.glTexCoord2d(0, 0) - bgl.glVertex2d(p0.x, p0.y) - bgl.glTexCoord2d(0, 1) - bgl.glVertex2d(p0.x, p1.y) - bgl.glTexCoord2d(1, 1) - bgl.glVertex2d(p1.x, p1.y) - bgl.glTexCoord2d(1, 0) - bgl.glVertex2d(p1.x, p0.y) - bgl.glEnd() - self.image.gl_free() - bgl.glDisable(bgl.GL_TEXTURE_2D) + coords = [(p0.x, p0.y), (p1.x, p0.y), (p0.x, p1.y), (p1.x, p1.y)] + g_image.draw(self.image.bindcode, coords) class GlPolyline(GlBaseLine): - def __init__(self, colour, d=3): + def __init__(self, colour, d=3, n_pts=60): self.pts_3d = [] - GlBaseLine.__init__(self, d) + GlBaseLine.__init__(self, d, n_pts=n_pts) self.colour_inactive = colour def set_pos(self, pts_3d): @@ -670,17 +845,25 @@ class GlPolyline(GlBaseLine): class GlHandle(GlPolygon): - def __init__(self, sensor_size, size, draggable=False, selectable=False, d=3): + def __init__(self, sensor_size, size, draggable=False, selectable=False, d=3, n_pts=4): """ sensor_size : 2d size in pixels of sensor area size : 3d size of handle """ - GlPolygon.__init__(self, d=d) - self.colour_active = (1.0, 0.0, 0.0, 1.0) - self.colour_hover = (1.0, 1.0, 0.0, 1.0) - self.colour_normal = (1.0, 1.0, 1.0, 1.0) - self.colour_selected = (0.0, 0.0, 0.7, 1.0) + GlPolygon.__init__(self, d=d, n_pts=n_pts) + prefs = get_prefs(bpy.context) + self.colour_active = prefs.handle_colour_active + self.colour_hover = prefs.handle_colour_hover + self.colour_normal = prefs.handle_colour_normal + self.colour_selected = prefs.handle_colour_selected + self.colour_inactive = prefs.handle_colour_inactive + # variable symbol size in world coords self.size = size + # constant symbol size in pixels + self.symbol_size = sensor_size + # scale factor for constant symbol pixel size + self.scale = 1 + # sensor size in pixels self.sensor_width = sensor_size self.sensor_height = sensor_size self.pos_3d = Vector((0, 0, 0)) @@ -692,11 +875,38 @@ class GlHandle(GlPolygon): self.selectable = selectable self.selected = False + def scale_factor(self, context, pts): + """ + Compute screen scale factor for symbol + given 2 points in symbol direction (eg start and end of arrow) + """ + prefs = get_prefs(context) + if not prefs.constant_handle_size: + return 1 + + p2d = [] + for pt in pts: + p2d.append(self.position_2d_from_coord(context, pt)) + sx = [p.x for p in p2d] + sy = [p.y for p in p2d] + x = max(sx) - min(sx) + y = max(sy) - min(sy) + s = max(x, y) + if s > 0: + fac = self.symbol_size / s + else: + fac = 1 + return fac + def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))): self.up_axis = direction.normalized() self.c_axis = self.up_axis.cross(normal) self.pos_3d = pos_3d self.pos_2d = self.position_2d_from_coord(context, self.sensor_center) + x = self.up_axis * 0.5 * self.size + y = self.c_axis * 0.5 * self.size + pts = [self.sensor_center + v for v in [-x, x, -y, y]] + self.scale = self.scale_factor(context, pts) def check_hover(self, pos_2d): if self.draggable: @@ -737,7 +947,7 @@ class GlHandle(GlPolygon): class SquareHandle(GlHandle): def __init__(self, sensor_size, size, draggable=False, selectable=False): - GlHandle.__init__(self, sensor_size, size, draggable, selectable) + GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=4) @property def pts(self): @@ -747,15 +957,15 @@ class SquareHandle(GlHandle): scale = 1 else: scale = 0.5 - x = n * self.size * scale - y = c * self.size * scale + x = n * self.scale * self.size * scale + y = c * self.scale * self.size * scale return [self.pos_3d - x - y, self.pos_3d + x - y, self.pos_3d + x + y, self.pos_3d - x + y] class TriHandle(GlHandle): def __init__(self, sensor_size, size, draggable=False, selectable=False): - GlHandle.__init__(self, sensor_size, size, draggable, selectable) + GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=3) @property def pts(self): @@ -767,11 +977,110 @@ class TriHandle(GlHandle): scale = 1 # else: # scale = 0.5 - x = n * self.size * 4 * scale - y = c * self.size * scale + x = n * self.scale * self.size * 4 * scale + y = c * self.scale * self.size * scale return [self.pos_3d - x + y, self.pos_3d - x - y, self.pos_3d] +class CruxHandle(GlHandle): + + def __init__(self, sensor_size, size, draggable=True, selectable=False): + GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=0) + self.branch_0 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4) + self.branch_1 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4) + + def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))): + self.pos_3d = pos_3d + self.pos_2d = self.position_2d_from_coord(context, self.sensor_center) + o = self.pos_3d + d = 0.5 * self.size + x = direction.normalized() + y = x.cross(normal) + + sx = x * 0.5 * self.size + sy = y * 0.5 * self.size + pts = [o + v for v in [-sx, sx, -sy, sy]] + self.scale = self.scale_factor(context, pts) + + c = self.scale * d / 1.4242 + w = self.scale * self.size + s = w - c + + xs = x * s + xw = x * w + ys = y * s + yw = y * w + p0 = o + xs + yw + p1 = o + xw + ys + p2 = o - xs - yw + p3 = o - xw - ys + p4 = o - xs + yw + p5 = o + xw - ys + p6 = o + xs - yw + p7 = o - xw + ys + + self.branch_0.set_pos([p0, p1, p2, p3]) + self.branch_1.set_pos([p4, p5, p6, p7]) + + @property + def pts(self): + return [self.pos_3d] + + def draw(self, context, render=False): + self.render = render + self.branch_0.colour_inactive = self.colour + self.branch_1.colour_inactive = self.colour + self.branch_0.draw(context) + self.branch_1.draw(context) + + +class PlusHandle(GlHandle): + + def __init__(self, sensor_size, size, draggable=True, selectable=False): + GlHandle.__init__(self, sensor_size, size, draggable, selectable, n_pts=0) + self.branch_0 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4) + self.branch_1 = GlPolygon((1, 1, 1, 1), d=3, n_pts=4) + + def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))): + self.pos_3d = pos_3d + self.pos_2d = self.position_2d_from_coord(context, self.sensor_center) + o = self.pos_3d + x = direction.normalized() + y = x.cross(normal) + sx = x * 0.5 * self.size + sy = y * 0.5 * self.size + pts = [o + v for v in [-sx, sx, -sy, sy]] + self.scale = self.scale_factor(context, pts) + w = self.scale * self.size + s = self.scale * 0.25 * w + + xs = x * s + xw = x * w + ys = y * s + yw = y * w + p0 = o - xw + ys + p1 = o + xw + ys + p2 = o + xw - ys + p3 = o - xw - ys + p4 = o - xs + yw + p5 = o + xs + yw + p6 = o + xs - yw + p7 = o - xs - yw + self.branch_0.set_pos([p0, p1, p2, p3]) + self.branch_1.set_pos([p4, p5, p6, p7]) + + @property + def pts(self): + return [self.pos_3d] + + def draw(self, context, render=False): + self.render = render + self.branch_0.colour_inactive = self.colour + self.branch_1.colour_inactive = self.colour + self.branch_0.draw(context) + self.branch_1.draw(context) + + class EditableText(GlText, GlHandle): def __init__(self, sensor_size, size, draggable=False, selectable=False): GlHandle.__init__(self, sensor_size, size, draggable, selectable) @@ -783,10 +1092,10 @@ class EditableText(GlText, GlHandle): self.pos_3d = pos_3d self.value = value self._text = self.add_units(context) - x, y = self.text_size(context) + ts = self.text_size(context) self.pos_2d = self.position_2d_from_coord(context, pos_3d) - self.pos_2d.x += 0.5 * x - self.sensor_width, self.sensor_height = 0.5 * x, y + self.pos_2d.x += 0.5 * ts.x + self.sensor_width, self.sensor_height = 0.5 * ts.x, ts.y @property def sensor_center(self): @@ -796,10 +1105,10 @@ class EditableText(GlText, GlHandle): class ThumbHandle(GlHandle): def __init__(self, size_2d, label, image=None, draggable=False, selectable=False, d=2): - GlHandle.__init__(self, size_2d, size_2d, draggable, selectable, d) + GlHandle.__init__(self, size_2d, size_2d, draggable, selectable, d, n_pts=4) self.image = GlImage(image=image) self.label = GlText(d=2, label=label.replace("_", " ").capitalize()) - self.frame = GlPolyline((1, 1, 1, 1), d=2) + self.frame = GlPolyline((1, 1, 1, 1), d=2, n_pts=4) self.frame.closed = True self.size_2d = size_2d self.sensor_width = 0.5 * size_2d.x @@ -830,7 +1139,7 @@ class ThumbHandle(GlHandle): def draw(self, context, render=False): self.render = render self.image.colour_inactive = self.colour - GlHandle.draw(self, context, render=False) + # GlHandle.draw(self, context, render=False) self.image.draw(context, render=False) self.label.draw(context, render=False) self.frame.draw(context, render=False) @@ -849,10 +1158,9 @@ class Screen(): y_max = h - self.margin x_min = self.margin x_max = w - self.margin - if (system.use_region_overlap and - system.window_draw_method in {'TRIPLE_BUFFER', 'AUTOMATIC'}): + if system.use_region_overlap: + # system.window_draw_method in {'TRIPLE_BUFFER', 'ADAPTIVEMATIC'}): area = context.area - for r in area.regions: if r.type == 'TOOLS': x_min += r.width @@ -866,29 +1174,30 @@ class FeedbackPanel(): Feed-back panel inspired by np_station """ + def __init__(self, title='Archipack'): - prefs = self.get_prefs(bpy.context) + prefs = get_prefs(bpy.context) self.main_title = GlText(d=2, - label=title + " : ", - font_size=prefs.feedback_size_main, - colour=prefs.feedback_colour_main - ) + label=title + " : ", + font_size=prefs.feedback_size_main, + colour=prefs.feedback_colour_main + ) self.title = GlText(d=2, - font_size=prefs.feedback_size_title, - colour=prefs.feedback_colour_main - ) + font_size=prefs.feedback_size_title, + colour=prefs.feedback_colour_main + ) self.spacing = Vector(( 0.5 * prefs.feedback_size_shortcut, 0.5 * prefs.feedback_size_shortcut)) self.margin = 50 self.explanation = GlText(d=2, - font_size=prefs.feedback_size_shortcut, - colour=prefs.feedback_colour_main - ) - self.shortcut_area = GlPolygon(colour=prefs.feedback_shortcut_area, d=2) - self.title_area = GlPolygon(colour=prefs.feedback_title_area, d=2) + font_size=prefs.feedback_size_shortcut, + colour=prefs.feedback_colour_main + ) + self.shortcut_area = GlPolygon(colour=prefs.feedback_shortcut_area, d=2, n_pts=4) + self.title_area = GlPolygon(colour=prefs.feedback_title_area, d=2, n_pts=4) self.shortcuts = [] self.on = False self.show_title = True @@ -903,22 +1212,11 @@ class FeedbackPanel(): def enable(self): self.on = True - def get_prefs(self, context): - global __name__ - try: - # retrieve addon name from imports - addon_name = __name__.split('.')[0] - prefs = context.user_preferences.addons[addon_name].preferences - except: - prefs = DefaultColorScheme - pass - return prefs - def instructions(self, context, title, explanation, shortcuts): """ position from bottom to top """ - prefs = self.get_prefs(context) + prefs = get_prefs(context) self.explanation.label = explanation self.title.label = title @@ -927,16 +1225,17 @@ class FeedbackPanel(): for key, label in shortcuts: key = GlText(d=2, label=key, - font_size=prefs.feedback_size_shortcut, - colour=prefs.feedback_colour_key) + font_size=prefs.feedback_size_shortcut, + colour=prefs.feedback_colour_key) label = GlText(d=2, label=' : ' + label, - font_size=prefs.feedback_size_shortcut, - colour=prefs.feedback_colour_shortcut) + font_size=prefs.feedback_size_shortcut, + colour=prefs.feedback_colour_shortcut) ks = key.text_size(context) ls = label.text_size(context) self.shortcuts.append([key, ks, label, ls]) def draw(self, context, render=False): + if self.on: """ draw from bottom to top @@ -992,7 +1291,7 @@ class FeedbackPanel(): (x_max, self.margin), (x_max, pos.y), (x_min, pos.y) - ] + ] # small space between shortcut area and main title bar if n_shortcuts > 0: @@ -1003,7 +1302,7 @@ class FeedbackPanel(): (x_max, pos.y), (x_max, pos.y + main_title_size.y + 2 * self.spacing.y), (x_min, pos.y + main_title_size.y + 2 * self.spacing.y) - ] + ] pos.y += self.spacing.y title_size = self.title.text_size(context) @@ -1047,12 +1346,13 @@ class GlCursorFence(): """ Cursor crossing Fence """ - def __init__(self, width=1, colour=(1.0, 1.0, 1.0, 0.5), style=bgl.GL_LINE_STIPPLE): - self.line_x = GlLine(d=2) + + def __init__(self, width=1, colour=(1.0, 1.0, 1.0, 0.5), style=2852): + self.line_x = GlLine(d=2, n_pts=2) self.line_x.style = style self.line_x.width = width self.line_x.colour_inactive = colour - self.line_y = GlLine(d=2) + self.line_y = GlLine(d=2, n_pts=2) self.line_y.style = style self.line_y.width = width self.line_y.colour_inactive = colour @@ -1061,7 +1361,8 @@ class GlCursorFence(): def set_location(self, context, location): w = context.region.width h = context.region.height - x, y = location + p = Vector(location) + x, y = p.x, p.y self.line_x.p = Vector((0, y)) self.line_x.v = Vector((w, 0)) self.line_y.p = Vector((x, 0)) @@ -1081,27 +1382,29 @@ class GlCursorFence(): class GlCursorArea(): def __init__(self, - width=1, - bordercolour=(1.0, 1.0, 1.0, 0.5), - areacolour=(0.5, 0.5, 0.5, 0.08), - style=bgl.GL_LINE_STIPPLE): + width=1, + bordercolour=(1.0, 1.0, 1.0, 0.5), + areacolour=(0.5, 0.5, 0.5, 0.08), + style=2852): - self.border = GlPolyline(bordercolour, d=2) + self.border = GlPolyline(bordercolour, d=2, n_pts=4) self.border.style = style self.border.width = width self.border.closed = True - self.area = GlPolygon(areacolour, d=2) + self.area = GlPolygon(areacolour, d=2, n_pts=4) self.min = Vector((0, 0)) self.max = Vector((0, 0)) self.on = False def in_area(self, pt): return (self.min.x <= pt.x and self.max.x >= pt.x and - self.min.y <= pt.y and self.max.y >= pt.y) + self.min.y <= pt.y and self.max.y >= pt.y) def set_location(self, context, p0, p1): - x0, y0 = p0 - x1, y1 = p1 + p = Vector(p0) + x0, y0 = p.x, p.y + p = Vector(p1) + x1, y1 = p.x, p.y if x0 > x1: x1, x0 = x0, x1 if y0 > y1: diff --git a/archipack/archipack_handle.py b/archipack/archipack_handle.py index fcdb570e..27da6b0b 100644 --- a/archipack/archipack_handle.py +++ b/archipack/archipack_handle.py @@ -26,23 +26,24 @@ # ---------------------------------------------------------- import bpy +from .archipack_object import ArchipackCollectionManager def create_handle(context, parent, mesh): old = context.active_object handle = bpy.data.objects.new("Handle", mesh) handle['archipack_handle'] = True - context.scene.objects.link(handle) + ArchipackCollectionManager.link_object_to_scene(context, handle) modif = handle.modifiers.new('Subsurf', 'SUBSURF') modif.render_levels = 4 modif.levels = 1 handle.parent = parent handle.matrix_world = parent.matrix_world.copy() - context.scene.objects.active = handle + context.view_layer.objects.active = handle m = handle.archipack_material.add() m.category = 'handle' m.material = 'DEFAULT' - context.scene.objects.active = old + context.view_layer.objects.active = old return handle diff --git a/archipack/archipack_keymaps.py b/archipack/archipack_keymaps.py index c4c8176e..537833ee 100644 --- a/archipack/archipack_keymaps.py +++ b/archipack/archipack_keymaps.py @@ -24,6 +24,7 @@ # Author: Stephen Leger (s-leger) # # ---------------------------------------------------------- +import bpy class Keymaps: @@ -51,7 +52,9 @@ class Keymaps: """ # provide abstration between user and addon # with different select mouse side - mouse_right = context.user_preferences.inputs.select_mouse + wm = context.window_manager + keyconfig = wm.keyconfigs.active + mouse_right = getattr(keyconfig.preferences, "select_mouse", "LEFT") if mouse_right == 'LEFT': mouse_left = 'RIGHT' mouse_right_side = 'Left' @@ -79,6 +82,10 @@ class Keymaps: event: simple event signature to compare like : if event == keymap.undo.event: """ + # Headless mode fails without this check + if bpy.app.background: + return {'type': None, 'event':(False, False, False, False, None, None)} + ev = context.window_manager.keyconfigs.user.keymaps[keyconfig].keymap_items[keymap_item] key = ev.type if ev.ctrl: @@ -89,6 +96,7 @@ class Keymaps: key += '+SHIFT' return {'type': key, 'name': ev.name, 'event': (ev.alt, ev.ctrl, ev.shift, ev.type, ev.value)} + def dump_keys(self, context, filename="c:\\tmp\\keymap.txt"): """ Utility for developers : diff --git a/archipack/archipack_manipulator.py b/archipack/archipack_manipulator.py index 4e916240..be6c2ef2 100644 --- a/archipack/archipack_manipulator.py +++ b/archipack/archipack_manipulator.py @@ -30,7 +30,13 @@ from mathutils import Vector, Matrix from mathutils.geometry import intersect_line_plane, intersect_point_line, intersect_line_sphere from bpy_extras import view3d_utils from bpy.types import PropertyGroup, Operator -from bpy.props import FloatVectorProperty, StringProperty, CollectionProperty, BoolProperty +from bpy.props import ( + FloatVectorProperty, + StringProperty, + CollectionProperty, + BoolProperty +) + from bpy.app.handlers import persistent from .archipack_snap import snap_point from .archipack_keymaps import Keymaps @@ -67,14 +73,16 @@ from .archipack_gl import ( # (manips[key].manipulable.manip_stack) # Must investigate for a way to handle unselect after drag done. + +import logging +logger = logging.getLogger("archipack") + + """ - @TODO: - Last modal running wins. - Manipulateurs without snap and thus not running own modal, - may loose events events caught by select mode of last - manipulable enabled + Change object location when moving 1 point + When False, change data.origin instead """ - +USE_MOVE_OBJECT = True # Arrow sizes (world units) arrow_size = 0.05 # Handle area size (pixels) @@ -445,7 +453,7 @@ class Manipulator(): ret = False self.keyboard_cancel(context, event) pass - context.area.header_text_set("") + context.area.header_text_set(None) self.keyboard_input_active = False self.feedback.disable() return ret @@ -469,7 +477,7 @@ class Manipulator(): view_vector_mouse = view3d_utils.region_2d_to_vector_3d(region, rv3d, self.mouse_pos) ray_origin_mouse = view3d_utils.region_2d_to_origin_3d(region, rv3d, self.mouse_pos) pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse, - self.origin, rM * self.manipulator.normal, False) + self.origin, rM @ self.manipulator.normal, False) # fix issue with parallel plane if pt is None: pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse, @@ -495,18 +503,19 @@ class Manipulator(): """ try: if self.get_value(data, attr, index) != value: + o = self.o # switch context so unselected object may be manipulable too - old = context.active_object - state = self.o.select - self.o.select = True - context.scene.objects.active = self.o + old = context.object + state = o.select_get() + o.select_set(state=True) + context.view_layer.objects.active = o if index > -1: getattr(data, attr)[index] = value else: setattr(data, attr, value) - self.o.select = state - old.select = True - context.scene.objects.active = old + o.select_set(state=state) + old.select_set(state=True) + context.view_layer.objects.active = old except: pass @@ -516,11 +525,7 @@ class Manipulator(): tM Matrix source vec Vector translation """ - return tM * Matrix([ - [1, 0, 0, vec.x], - [0, 1, 0, vec.y], - [0, 0, 1, vec.z], - [0, 0, 0, 1]]) + return tM @ Matrix.Translation(vec) def _move(self, o, axis, value): if axis == 'x': @@ -538,15 +543,15 @@ class Manipulator(): """ old = context.active_object bpy.ops.object.select_all(action='DESELECT') - self.o.select = True - context.scene.objects.active = self.o + self.o.select_set(state=True) + context.view_layer.objects.active = self.o bpy.ops.object.select_linked(type='OBDATA') for o in context.selected_objects: if o != self.o: self._move(o, axis, value) bpy.ops.object.select_all(action='DESELECT') - old.select = True - context.scene.objects.active = old + old.select_set(state=True) + context.view_layer.objects.active = old def move(self, context, axis, value): """ @@ -555,68 +560,6 @@ class Manipulator(): self._move(self.o, axis, value) -# OUT OF ORDER -class SnapPointManipulator(Manipulator): - """ - np_station based snap manipulator - dosent update anything by itself. - NOTE : currently out of order - and disabled in __init__ - """ - def __init__(self, context, o, datablock, manipulator, handle_size, snap_callback=None): - - raise NotImplementedError - - self.handle = SquareHandle(handle_size, 1.2 * arrow_size, draggable=True) - Manipulator.__init__(self, context, o, datablock, manipulator, snap_callback) - - def check_hover(self): - self.handle.check_hover(self.mouse_pos) - - def mouse_press(self, context, event): - if self.handle.hover: - self.handle.hover = False - self.handle.active = True - self.o.select = True - # takeloc = self.o.matrix_world * self.manipulator.p0 - # print("Invoke sp_point_move %s" % (takeloc)) - # @TODO: - # implement and add draw and callbacks - # snap_point(takeloc, draw, callback) - return True - return False - - def mouse_release(self, context, event): - self.check_hover() - self.handle.active = False - # False to callback manipulable_release - return False - - def update(self, context, event): - # NOTE: - # dosent set anything internally - return - - def mouse_move(self, context, event): - """ - - """ - self.mouse_position(event) - if self.handle.active: - # self.handle.active = np_snap.is_running - # self.update(context) - # True here to callback manipulable_manipulate - return True - else: - self.check_hover() - return False - - def draw_callback(self, _self, context, render=False): - left, right, side, normal = self.manipulator.get_pts(self.o.matrix_world) - self.handle.set_pos(context, left, Vector((1, 0, 0)), normal=normal) - self.handle.draw(context, render) - - # Generic snap tool for line based archipack objects (fence, wall, maybe stair too) gl_pts3d = [] @@ -682,7 +625,7 @@ class WallSnapManipulator(Manipulator): ]) self.feedback.enable() self.handle.hover = False - self.o.select = True + self.o.select_set(state=True) takeloc, right, side, dz = self.manipulator.get_pts(self.o.matrix_world) dx = (right - takeloc).normalized() dy = dz.cross(dx) @@ -712,20 +655,20 @@ class WallSnapManipulator(Manipulator): np station callback on moving, place, or cancel """ global gl_pts3d + logger.debug("WallSnapManipulator.sp_callback") if state == 'SUCCESS': - - self.o.select = True + o = self.o + o.select_set(state=True) + context.view_layer.objects.active = o # apply changes to wall d = self.datablock - d.auto_update = False - g = d.get_generator() # rotation relative to object - rM = self.o.matrix_world.inverted().to_3x3() - delta = (rM * sp.delta).to_2d() - # x_axis = (rM * Vector((1, 0, 0))).to_2d() + rM = o.matrix_world.inverted().to_3x3() + delta =rM @ sp.delta + # x_axis = (rM @ Vector((1, 0, 0))).to_2d() # update generator idx = 0 @@ -734,7 +677,7 @@ class WallSnapManipulator(Manipulator): if selected: # new location in object space - pt = g.segs[idx].lerp(0) + delta + pt = g.segs[idx].lerp(0) + delta.to_2d() # move last point of segment before current if idx > 0: @@ -747,6 +690,7 @@ class WallSnapManipulator(Manipulator): # update properties from generator idx = 0 + d.auto_update = False for p0, p1, selected in gl_pts3d: if selected: @@ -759,7 +703,7 @@ class WallSnapManipulator(Manipulator): if idx > 1: part.a0 = w.delta_angle(g.segs[idx - 2]) else: - part.a0 = w.straight(1, 0).angle + part.a0 = w.a0 if "C_" in part.type: part.radius = w.r @@ -773,10 +717,14 @@ class WallSnapManipulator(Manipulator): if idx > 0: part.a0 = w.delta_angle(g.segs[idx - 1]) else: - part.a0 = w.straight(1, 0).angle + part.a0 = w.a0 # move object when point 0 - self.o.location += sp.delta - self.o.matrix_world.translation += sp.delta + if USE_MOVE_OBJECT: + d.move_object(o, o.matrix_world.translation + sp.delta) + # self.o.location += sp.delta + # self.o.matrix_world.translation += sp.delta + else: + d.origin += sp.delta if "C_" in part.type: part.radius = w.r @@ -790,15 +738,20 @@ class WallSnapManipulator(Manipulator): idx += 1 self.mouse_release(context, event) + if hasattr(d, "relocate_childs"): + d.relocate_childs(context, o, g) d.auto_update = True + d.update(context) if state == 'CANCEL': self.mouse_release(context, event) + logger.debug("WallSnapManipulator.sp_callback done") return def sp_draw(self, sp, context): # draw wall placeholders + logger.debug("WallSnapManipulator.sp_draw") global gl_pts3d @@ -842,6 +795,7 @@ class WallSnapManipulator(Manipulator): self.label.set_pos(context, self.line.length, self.line.lerp(0.5), self.line.v, normal=Vector((0, 0, 1))) self.line.draw(context, render=False) self.label.draw(context, render=False) + logger.debug("WallSnapManipulator.sp_draw done") def mouse_move(self, context, event): self.mouse_position(event) @@ -1002,7 +956,6 @@ class SizeManipulator(Manipulator): ]) gl_pts3d = [left, right] snap_point(takemat=takemat, - draw=self.sp_draw, callback=self.sp_callback, constraint_axis=(True, False, False)) self.handle_right.active = True @@ -1085,32 +1038,31 @@ class SizeManipulator(Manipulator): self.label.draw(context, render) self.feedback.draw(context, render) - def sp_draw(self, sp, context): + def sp_callback(self, context, event, state, sp): + logger.debug("SizeManipulator.sp_callback") global gl_pts3d - if self.o is None: - return + p0 = gl_pts3d[0].copy() p1 = gl_pts3d[1].copy() - p1 += sp.delta - self.sp_update(context, p0, p1) - return - def sp_callback(self, context, event, state, sp): + if state != 'CANCEL': + p1 += sp.delta - if state == 'SUCCESS': - self.sp_draw(sp, context) - self.mouse_release(context, event) + length = (p0 - p1).length - if state == 'CANCEL': - p0 = gl_pts3d[0].copy() - p1 = gl_pts3d[1].copy() - self.sp_update(context, p0, p1) - self.mouse_release(context, event) + if state != 'CANCEL' and event.alt: + if event.shift: + length = round(length, 2) + else: + length = round(length, 1) - def sp_update(self, context, p0, p1): - length = (p0 - p1).length self.set_value(context, self.datablock, self.manipulator.prop1_name, length) + if state != 'RUNNING': + self.mouse_release(context, event) + + logger.debug("SizeManipulator.sp_callback done") + class SizeLocationManipulator(SizeManipulator): """ @@ -1190,7 +1142,7 @@ class SizeLocationManipulator(SizeManipulator): self.mouse_release(context, event) # must move back to original location itM = self.o.matrix_world.inverted() - dl = self.get_value(itM * self.original_location, self.manipulator.prop2_name) + dl = self.get_value(itM @ self.original_location, self.manipulator.prop2_name) self.move(context, self.manipulator.prop2_name, dl) self.set_value(context, self.datablock, self.manipulator.prop1_name, self.original_size) @@ -1263,7 +1215,6 @@ class SnapSizeLocationManipulator(SizeLocationManipulator): ]) gl_pts3d = [left, right] snap_point(takemat=takemat, - draw=self.sp_draw, callback=self.sp_callback, constraint_axis=(True, False, False)) @@ -1290,7 +1241,6 @@ class SnapSizeLocationManipulator(SizeLocationManipulator): ]) gl_pts3d = [left, right] snap_point(takemat=takemat, - draw=self.sp_draw, callback=self.sp_callback, constraint_axis=(True, False, False)) self.handle_left.active = True @@ -1305,50 +1255,50 @@ class SnapSizeLocationManipulator(SizeLocationManipulator): return False - def sp_draw(self, sp, context): + def sp_callback(self, context, event, state, sp): + logger.debug("SnapSizeLocationManipulator.sp_callback") global gl_pts3d - if self.o is None: - return p0 = gl_pts3d[0].copy() p1 = gl_pts3d[1].copy() - if self.handle_right.active: - p1 += sp.delta - else: - p0 += sp.delta - self.sp_update(context, p0, p1) - - # snapping child objects may require base object update - # eg manipulating windows requiring wall update - if self.snap_callback is not None: - snap_helper = context.active_object - self.snap_callback(context, o=self.o, manipulator=self) - context.scene.objects.active = snap_helper - - return - - def sp_callback(self, context, event, state, sp): - if state == 'SUCCESS': - self.sp_draw(sp, context) - self.mouse_release(context, event) - - if state == 'CANCEL': - p0 = gl_pts3d[0].copy() - p1 = gl_pts3d[1].copy() - self.sp_update(context, p0, p1) - self.mouse_release(context, event) + if state != 'CANCEL': + if self.handle_right.active: + p1 += sp.delta + else: + p0 += sp.delta - def sp_update(self, context, p0, p1): l0 = self.get_value(self.datablock, self.manipulator.prop1_name) length = (p0 - p1).length + + if state != 'CANCEL' and event.alt: + if event.shift: + length = round(length, 2) + else: + length = round(length, 1) + dp = length - l0 + if self.handle_left.active: dp = -dp dl = 0.5 * dp + + # snap_helper = context.object self.move(context, self.manipulator.prop2_name, dl) self.set_value(context, self.datablock, self.manipulator.prop1_name, length) self.move_linked(context, self.manipulator.prop2_name, dl) + # snapping child objects may require base object update + # eg manipulating windows requiring wall update + if self.snap_callback is not None: + snap_helper = context.active_object + self.snap_callback(context, o=self.o, manipulator=self) + snap_helper.select_set(state=True) + + if state != 'RUNNING': + self.mouse_release(context, event) + + logger.debug("SnapSizeLocationManipulator.sp_callback done") + class DeltaLocationManipulator(SizeManipulator): """ @@ -1388,7 +1338,6 @@ class DeltaLocationManipulator(SizeManipulator): ]) gl_pts3d = [p0] snap_point(takemat=takemat, - draw=self.sp_draw, callback=self.sp_callback, constraint_axis=( self.manipulator.prop1_name == 'x', @@ -1413,40 +1362,37 @@ class DeltaLocationManipulator(SizeManipulator): self.check_hover() return False - def sp_draw(self, sp, context): - global gl_pts3d - if self.o is None: - return - p0 = gl_pts3d[0].copy() - p1 = p0 + sp.delta - itM = self.o.matrix_world.inverted() - dl = self.get_value(itM * p1, self.manipulator.prop1_name) - self.move(context, self.manipulator.prop1_name, dl) - - # snapping child objects may require base object update - # eg manipulating windows requiring wall update - if self.snap_callback is not None: - snap_helper = context.active_object - self.snap_callback(context, o=self.o, manipulator=self) - context.scene.objects.active = snap_helper - - return - def sp_callback(self, context, event, state, sp): - - if state == 'SUCCESS': - self.sp_draw(sp, context) - self.mouse_release(context, event) + logger.debug("DeltaLocationManipulator.sp_callback") if state == 'CANCEL': self.cancel(context, event) + else: + global gl_pts3d + p0 = gl_pts3d[0].copy() + p1 = p0 + sp.delta + itM = self.o.matrix_world.inverted() + dl = self.get_value(itM @ p1, self.manipulator.prop1_name) + self.move(context, self.manipulator.prop1_name, dl) + + # snapping child objects may require base object update + # eg manipulating windows requiring wall update + if self.snap_callback is not None: + snap_helper = context.active_object + self.snap_callback(context, o=self.o, manipulator=self) + snap_helper.select_set(state=True) + + if state == 'SUCCESS': + self.mouse_release(context, event) + + logger.debug("DeltaLocationManipulator.sp_callback done") def cancel(self, context, event): if self.active: self.mouse_release(context, event) # must move back to original location itM = self.o.matrix_world.inverted() - dl = self.get_value(itM * self.original_location, self.manipulator.prop1_name) + dl = self.get_value(itM @ self.original_location, self.manipulator.prop1_name) self.move(context, self.manipulator.prop1_name, dl) def draw_callback(self, _self, context, render=False): @@ -1963,22 +1909,22 @@ class archipack_manipulator(PropertyGroup): p0, p1, p2 3d Vectors as base points to represent manipulators on screen normal Vector normal of plane on with draw manipulator """ - type_key = StringProperty(default='SIZE') + type_key : StringProperty(default='SIZE') # How 3d points are stored in manipulators ? # SIZE = 2 absolute positioned and a scaling vector # RADIUS = 1 absolute positioned (center) and 2 relatives (sides) # POLYGON = 2 absolute positioned and a relative vector (for rect polygons) - pts_mode = StringProperty(default='SIZE') - prop1_name = StringProperty() - prop2_name = StringProperty() - p0 = FloatVectorProperty(subtype='XYZ') - p1 = FloatVectorProperty(subtype='XYZ') - p2 = FloatVectorProperty(subtype='XYZ') + pts_mode : StringProperty(default='SIZE') + prop1_name : StringProperty() + prop2_name : StringProperty() + p0 : FloatVectorProperty(subtype='XYZ') + p1 : FloatVectorProperty(subtype='XYZ') + p2 : FloatVectorProperty(subtype='XYZ') # allow orientation of manipulators by default on xy plane, # but may be used to constrain heights on local object space - normal = FloatVectorProperty(subtype='XYZ', default=(0, 0, 1)) + normal : FloatVectorProperty(subtype='XYZ', default=(0, 0, 1)) def set_pts(self, pts, normal=None): """ @@ -1999,9 +1945,9 @@ class archipack_manipulator(PropertyGroup): """ rM = tM.to_3x3() if self.pts_mode in ['SIZE', 'POLYGON']: - return tM * self.p0, tM * self.p1, self.p2, rM * self.normal + return tM @ self.p0, tM @ self.p1, self.p2, rM @ self.normal else: - return tM * self.p0, rM * self.p1, rM * self.p2, rM * self.normal + return tM @ self.p0, rM @ self.p1, rM @ self.p2, rM @ self.normal def get_prefs(self, context): global __name__ @@ -2053,7 +1999,7 @@ class ARCHIPACK_OT_manipulate(Operator): bl_description = "Manipulate" bl_options = {'REGISTER', 'UNDO'} - object_name = StringProperty(default="") + object_name : StringProperty(default="") @classmethod def poll(self, context): @@ -2123,38 +2069,39 @@ class Manipulable(): Beware : prevent crash calling manipulable_disable() before changing manipulated data structure """ - manipulators = CollectionProperty( + manipulators : CollectionProperty( type=archipack_manipulator, # options={'SKIP_SAVE'}, # options={'HIDDEN'}, description="store 3d points to draw gl manipulators" ) - manipulable_refresh = BoolProperty( + + # TODO: make simple instance vars + manipulable_refresh : BoolProperty( default=False, options={'SKIP_SAVE'}, description="Flag enable to rebuild manipulators when data model change" ) - manipulate_mode = BoolProperty( + manipulate_mode : BoolProperty( default=False, options={'SKIP_SAVE'}, description="Flag manipulation state so we are able to toggle" ) - select_mode = BoolProperty( + select_mode : BoolProperty( default=False, options={'SKIP_SAVE'}, description="Flag select state so we are able to toggle" ) - manipulable_selectable = BoolProperty( + manipulable_selectable : BoolProperty( default=False, options={'SKIP_SAVE'}, description="Flag make manipulators selectable" ) - keymap = None - # selectable manipulators - manipulable_area = GlCursorArea() - manipulable_start_point = Vector((0, 0)) - manipulable_end_point = Vector((0, 0)) + keymap = None + manipulable_area = None + manipulable_start_point = None + manipulable_end_point = None manipulable_draw_handler = None def setup_manipulators(self): @@ -2171,6 +2118,13 @@ class Manipulable(): """ disable gl draw handlers """ + + if self.keymap is None: + self.keymap = Keymaps(context) + self.manipulable_area = GlCursorArea() + self.manipulable_start_point = Vector((0, 0)) + self.manipulable_end_point = Vector((0, 0)) + o = context.active_object if o is not None: self.manipulable_exit_selectmode(context) @@ -2259,6 +2213,7 @@ class Manipulable(): as it provide all needed functionality out of the box """ + # setup again when manipulators type change if self.manipulable_refresh: # print("manipulable_refresh") diff --git a/archipack/archipack_material.py b/archipack/archipack_material.py index ddb44009..cbec80cd 100644 --- a/archipack/archipack_material.py +++ b/archipack/archipack_material.py @@ -181,7 +181,7 @@ class MatlibsManager(): def apply(self, context, slot_index, name, link=False): o = context.active_object - o.select = True + o.select_set(state=True) # material with same name exist in scene mat = self.from_data(name) @@ -225,6 +225,8 @@ class MaterialSetManager(): self.objects = {} # hold reference of dynamic enumerator self.enums = {} + self.default_enum = [('DEFAULT', 'Default', '', 0)] + def get_filename(self, object_type): @@ -262,9 +264,9 @@ class MaterialSetManager(): material_sets = {} # create file object, and set open mode - if os.path.exists(filename): - try: - f = open(filename, 'r') + + try: + with open(filename, 'r') as f: lines = f.readlines() for line in lines: @@ -272,17 +274,15 @@ class MaterialSetManager(): if str(s_key) not in material_sets.keys(): material_sets[s_key] = [] material_sets[s_key].append(mat_name.strip()) - except: - print("Archipack: An error occurred while loading {}".format(filename)) - pass - finally: - f.close() + except: + print("Archipack: material preset for {} not found".format(object_type)) + pass - s_keys = material_sets.keys() - for s_key in s_keys: - self.register_set(object_type, s_key, material_sets[s_key]) + s_keys = material_sets.keys() + for s_key in s_keys: + self.register_set(object_type, s_key, material_sets[s_key]) - self.make_enum(object_type, s_keys) + self.make_enum(object_type, s_keys) def save(self, object_type): # always save in user prefs @@ -330,17 +330,15 @@ class MaterialSetManager(): if object_type not in self.objects.keys(): self.load(object_type) if object_type not in self.objects.keys(): - print("Archipack: Unknown object type {}".format(object_type)) + # print("Archipack: Unknown object type {}".format(object_type)) return None if set_name not in self.objects[object_type].keys(): - print("Archipack: set {} not found".format(set_name)) + # print("Archipack: set {} not found".format(set_name)) return None return self.objects[object_type][set_name] def make_enum(self, object_type, s_keys): - if len(s_keys) < 1: - self.enums[object_type] = [('DEFAULT', 'Default', '', 0)] - else: + if len(s_keys) > 0: self.enums[object_type] = [(s.upper(), s.capitalize(), '', i) for i, s in enumerate(s_keys)] def get_enum(self, object_type): @@ -351,7 +349,10 @@ class MaterialSetManager(): if object_type not in self.objects.keys(): self.objects[object_type] = {} - return self.enums[object_type] + if object_type in self.enums: + return self.enums[object_type] + + return self.default_enum def material_enum(self, context): @@ -367,12 +368,12 @@ def update(self, context): class archipack_material(PropertyGroup): - category = StringProperty( + category : StringProperty( name="Category", description="Archipack object name", default="" ) - material = EnumProperty( + material : EnumProperty( name="Material", description="Material Set name", items=material_enum, @@ -411,17 +412,17 @@ class archipack_material(PropertyGroup): mats = setman.get_materials(self.category, self.material) - if mats is None: + if mats is None or len(mats) < 1: return False for ob in sel: - context.scene.objects.active = ob + context.view_layer.objects.active = ob for slot_index, mat_name in enumerate(mats): if slot_index >= len(ob.material_slots): bpy.ops.object.material_slot_add() self.apply_material(context, slot_index, mat_name) - context.scene.objects.active = o + context.view_layer.objects.active = o return True @@ -431,7 +432,7 @@ class ARCHIPACK_PT_material(Panel): bl_label = "Archipack Material" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - # bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -442,8 +443,8 @@ class ARCHIPACK_PT_material(Panel): props = context.active_object.archipack_material[0] row = layout.row(align=True) row.prop(props, 'material', text="") - row.operator('archipack.material_add', icon="ZOOMIN", text="") - row.operator('archipack.material_remove', icon="ZOOMOUT", text="") + row.operator('archipack.material_add', icon="ADD", text="") + row.operator('archipack.material_remove', icon="REMOVE", text="") class ARCHIPACK_OT_material(Operator): @@ -452,12 +453,12 @@ class ARCHIPACK_OT_material(Operator): bl_description = "Add archipack material" bl_options = {'REGISTER', 'UNDO'} - category = StringProperty( + category : StringProperty( name="Category", description="Archipack object name", default="" ) - material = StringProperty( + material : StringProperty( name="Material", description="Material Set name", default="" @@ -484,13 +485,11 @@ class ARCHIPACK_OT_material(Operator): res = False pass - if res: - # print("ARCHIPACK_OT_material.apply {} {}".format(self.category, self.material)) - return {'FINISHED'} - else: + if not res: print("Archipack: unable to add material {} for {}".format(self.material, self.category)) - self.report({'WARNING'}, 'Material {} for {} not found'.format(self.material, self.category)) - return {'CANCELLED'} + # self.report({'WARNING'}, 'Material {} for {} not found'.format(self.material, self.category)) + + return {'FINISHED'} class ARCHIPACK_OT_material_add(Operator): @@ -499,7 +498,7 @@ class ARCHIPACK_OT_material_add(Operator): bl_description = "Add a set of archipack material" bl_options = {'REGISTER', 'UNDO'} - material = StringProperty( + material : StringProperty( name="Material", description="Material Set name", default="" @@ -603,5 +602,5 @@ def unregister(): bpy.utils.unregister_class(ARCHIPACK_OT_material_add) bpy.utils.unregister_class(ARCHIPACK_OT_material_remove) bpy.utils.unregister_class(ARCHIPACK_OT_material_library) - bpy.utils.unregister_class(archipack_material) del Object.archipack_material + bpy.utils.unregister_class(archipack_material) diff --git a/archipack/archipack_object.py b/archipack/archipack_object.py index b479ea30..8c908214 100644 --- a/archipack/archipack_object.py +++ b/archipack/archipack_object.py @@ -38,7 +38,23 @@ from bpy_extras.view3d_utils import ( ) -class ArchipackObject(): +class ArchipackCollectionManager(): + + @staticmethod + def link_object_to_scene(context, o): + coll_main = context.scene.collection.children.get("Archipack") + if coll_main is None: + coll_main = bpy.data.collections.new(name="Archipack") + context.scene.collection.children.link(coll_main) + coll_main.objects.link(o) + + @staticmethod + def unlink_object_from_scene(o): + for coll in o.users_collection: + coll.objects.unlink(o) + + +class ArchipackObject(ArchipackCollectionManager): """ Shared property of archipack's objects PropertyGroup provide basic support for copy to selected @@ -100,6 +116,7 @@ class ArchipackObject(): selected = context.selected_objects[:] for o in selected: + if self.__class__.datablock(o) == self: self.previously_selected = selected self.previously_active = active @@ -113,27 +130,41 @@ class ArchipackObject(): try: for o in self.previously_selected: - o.select = True + o.select_set(state=True) except: pass if self.previously_active is not None: - self.previously_active.select = True - context.scene.objects.active = self.previously_active + self.previously_active.select_set(state=True) + context.view_layer.objects.active = self.previously_active self.previously_selected = None self.previously_active = None + def move_object(self, o, p): + """ + When firstpoint is moving we must move object according + p is new x, y location in world coordsys + """ + p = Vector((p.x, p.y, o.matrix_world.translation.z)) + # p is in o coordsys + if o.parent: + o.location = p @ o.parent.matrix_world.inverted() + o.matrix_world.translation = p + else: + o.location = p + o.matrix_world.translation = p + -class ArchipackCreateTool(): +class ArchipackCreateTool(ArchipackCollectionManager): """ Shared property of archipack's create tool Operator """ - auto_manipulate = BoolProperty( + auto_manipulate : BoolProperty( name="Auto manipulate", description="Enable object's manipulators after create", options={'SKIP_SAVE'}, default=True ) - filepath = StringProperty( + filepath : StringProperty( options={'SKIP_SAVE'}, name="Preset", description="Full filename of python preset to load at create time", @@ -164,7 +195,10 @@ class ArchipackCreateTool(): if fallback: # fallback to load preset on background process try: - exec(compile(open(self.filepath).read(), self.filepath, 'exec')) + with open(self.filepath) as f: + lines = f.read() + cmp = compile(lines, self.filepath, 'exec') + exec(cmp) except: print("Archipack unable to load preset file : %s" % (self.filepath)) pass @@ -194,7 +228,7 @@ class ArchipackCreateTool(): pass -class ArchpackDrawTool(): +class ArchipackDrawTool(ArchipackCollectionManager): """ Draw tools """ @@ -225,8 +259,9 @@ class ArchpackDrawTool(): view_vector_mouse = region_2d_to_vector_3d(region, rv3d, co2d) ray_origin_mouse = region_2d_to_origin_3d(region, rv3d, co2d) res, pos, normal, face_index, object, matrix_world = context.scene.ray_cast( - ray_origin_mouse, - view_vector_mouse) + view_layer=context.view_layer, + origin=ray_origin_mouse, + direction=view_vector_mouse) return res, pos, normal, face_index, object, matrix_world def mouse_hover_wall(self, context, event): @@ -245,5 +280,5 @@ class ArchpackDrawTool(): [x.y, y.y, z.y, pt.y], [x.z, y.z, z.z, o.matrix_world.translation.z], [0, 0, 0, 1] - ]), o, y - return False, Matrix(), None, Vector() + ]), o, d.width, y, 0 # d.z_offset + return False, Matrix(), None, 0, Vector(), 0 diff --git a/archipack/archipack_preset.py b/archipack/archipack_preset.py index dcaa7970..8f1468b2 100644 --- a/archipack/archipack_preset.py +++ b/archipack/archipack_preset.py @@ -170,7 +170,9 @@ class PresetMenuItem(): def __init__(self, thumbsize, preset, image=None): name = bpy.path.display_name_from_filepath(preset) self.preset = preset - self.handle = ThumbHandle(thumbsize, name, image, draggable=True) + self.image = image + self.image.gl_load() + self.handle = ThumbHandle(thumbsize, name, self.image, draggable=True) self.enable = True def filter(self, keywords): @@ -179,6 +181,11 @@ class PresetMenuItem(): return False return True + def cleanup(self): + if self.image: + self.image.gl_free() + # bpy.data.images.remove(self.image) + def set_pos(self, context, pos): self.handle.set_pos(context, pos) @@ -223,7 +230,6 @@ class PresetMenu(): self.border = GlPolyline((0.7, 0.7, 0.7, 1), d=2) self.keywords = SeekBox() self.keywords.colour_normal = (1, 1, 1, 1) - self.border.closed = True self.set_pos(context) @@ -268,12 +274,15 @@ class PresetMenu(): return file_list def clearImages(self): + for item in self.menuItems: + item.cleanup() for image in bpy.data.images: if image.filepath_raw in self.imageList: # image.user_clear() bpy.data.images.remove(image, do_unlink=True) self.imageList.clear() + def make_menuitem(self, filepath): """ @TODO: @@ -377,7 +386,7 @@ class PresetMenu(): class PresetMenuOperator(): - preset_operator = StringProperty( + preset_operator : StringProperty( options={'SKIP_SAVE'}, default="script.execute_preset" ) @@ -414,7 +423,11 @@ class PresetMenuOperator(): if self.preset_operator == 'script.execute_preset': # call from preset menu # ensure right active_object class - d = getattr(bpy.types, self.preset_subdir).datablock(context.active_object) + o = context.active_object + if o.data and self.preset_subdir in o.data: + d = getattr(o.data, self.preset_subdir)[0] + elif self.preset_subdir in o: + d = getattr(o, self.preset_subdir)[0] if d is not None: d.auto_update = False # print("Archipack execute_preset loading auto_update:%s" % d.auto_update) @@ -525,17 +538,22 @@ class ArchipackPreset(AddPresetBase): def background_render(self, context, cls, preset): generator = os.path.dirname(os.path.realpath(__file__)) + os.path.sep + "archipack_thumbs.py" + addon_name = __name__.split('.')[0] + matlib_path = context.user_preferences.addons[addon_name].preferences.matlib_path # Run external instance of blender like the original thumbnail generator. cmd = [ bpy.app.binary_path, "--background", + "--factory-startup", "-noaudio", + # "--addons", addon_name, "--python", generator, "--", + "addon:" + addon_name, + "matlib:" + matlib_path, "cls:" + cls, "preset:" + preset - ] - # print(repr(cmd)) + ] subprocess.Popen(cmd) @@ -555,7 +573,7 @@ class ArchipackPreset(AddPresetBase): preset = os.path.join(target_path, filename) + ".py" cls = self.preset_subdir[10:] - print("post cb cls:%s preset:%s" % (cls, preset)) + # print("post cb cls:%s preset:%s" % (cls, preset)) self.background_render(context, cls, preset) return diff --git a/archipack/archipack_reference_point.py b/archipack/archipack_reference_point.py index 2f58c088..0b0c0348 100644 --- a/archipack/archipack_reference_point.py +++ b/archipack/archipack_reference_point.py @@ -34,6 +34,7 @@ from bpy.props import ( ) from mathutils import Vector from .bmesh_utils import BmeshEdit as bmed +from .archipack_object import ArchipackCollectionManager def update(self, context): @@ -41,22 +42,22 @@ def update(self, context): class archipack_reference_point(PropertyGroup): - location_2d = FloatVectorProperty( + location_2d : FloatVectorProperty( subtype='XYZ', name="position 2d", default=Vector((0, 0, 0)) ) - location_3d = FloatVectorProperty( + location_3d : FloatVectorProperty( subtype='XYZ', name="position 3d", default=Vector((0, 0, 0)) ) - symbol_scale = FloatProperty( + symbol_scale : FloatProperty( name="Screen scale", default=1, min=0.01, update=update) - symbol_type = EnumProperty( + symbol_type : EnumProperty( name="Symbol type", default='WALL', items=( @@ -177,7 +178,7 @@ class ARCHIPACK_PT_reference_point(Panel): bl_label = "Reference point" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -199,19 +200,19 @@ class ARCHIPACK_PT_reference_point(Panel): layout.operator('archipack.apply_holes') -class ARCHIPACK_OT_reference_point(Operator): +class ARCHIPACK_OT_reference_point(ArchipackCollectionManager, Operator): """Add reference point""" bl_idname = "archipack.reference_point" bl_label = "Reference point" bl_description = "Add reference point" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - location_3d = FloatVectorProperty( + location_3d : FloatVectorProperty( subtype='XYZ', name="position 3d", default=Vector((0, 0, 0)) ) - symbol_type = EnumProperty( + symbol_type : EnumProperty( name="Symbol type", default='WALL', items=( @@ -226,7 +227,7 @@ class ARCHIPACK_OT_reference_point(Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def create(self, context): x, y, z = context.scene.cursor_location @@ -234,21 +235,21 @@ class ARCHIPACK_OT_reference_point(Operator): m = bpy.data.meshes.new(name="Reference") o = bpy.data.objects.new("Reference", m) o.location = Vector((x, y, 0)) - context.scene.objects.link(o) + self.link_object_to_scene(context, o) d = o.archipack_reference_point.add() d.location_2d = Vector((x, y, 0)) d.location_3d = self.location_3d d.symbol_type = self.symbol_type - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o d.update(context) return o def execute(self, context): if context.mode == "OBJECT": o = self.create(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -298,7 +299,7 @@ class ARCHIPACK_OT_apply_holes(Operator): ctx['modifier'] = mod try: bpy.ops.object.modifier_apply(ctx, apply_as='DATA', - modifier=ctx['modifier'].name) + modifier=mod.name) except: pass @@ -327,12 +328,12 @@ class ARCHIPACK_OT_apply_holes(Operator): bpy.ops.object.select_all(action="DESELECT") for r in to_remove: r.hide_select = False - r.select = True - context.scene.objects.active = r + r.select_set(state=True) + context.view_layer.objects.active = r bpy.ops.object.delete(use_global=False) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return {'FINISHED'} else: @@ -410,9 +411,9 @@ class ARCHIPACK_OT_move_2d_reference_to_cursor(Operator): bpy.ops.object.select_all(action="DESELECT") bpy.ops.archipack.reference_point(location_3d=props.location_3d) for child in o.children: - child.select = True + child.select_set(state=True) bpy.ops.archipack.parent_to_reference() - context.scene.objects.unlink(o) + self.unlink_object_from_scene(o) return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -441,7 +442,7 @@ class ARCHIPACK_OT_parent_to_reference(Operator): # print("parent_to_reference parenting:%s objects" % (len(sel))) for child in sel: rs = child.matrix_world.to_3x3().to_4x4() - loc = itM * child.matrix_world.translation + loc = itM @ child.matrix_world.translation child.parent = None child.matrix_parent_inverse.identity() child.location = Vector((0, 0, 0)) diff --git a/archipack/archipack_rendering.py b/archipack/archipack_rendering.py index 8ea06ef9..533afffc 100644 --- a/archipack/archipack_rendering.py +++ b/archipack/archipack_rendering.py @@ -31,15 +31,14 @@ import bpy # noinspection PyUnresolvedReferences import bgl from shutil import copyfile -from os import path, remove, listdir -from sys import exc_info +from os import path, listdir import subprocess # noinspection PyUnresolvedReferences import bpy_extras.image_utils as img_utils # noinspection PyUnresolvedReferences from math import ceil from bpy.types import Operator -from bl_ui import properties_render +# from bl_ui import properties_render class ARCHIPACK_OT_render_thumbs(Operator): @@ -51,8 +50,8 @@ class ARCHIPACK_OT_render_thumbs(Operator): @classmethod def poll(cls, context): # Ensure CYCLES engine is available - return context.scene.archipack_progress < 0 and \ - 'CYCLES' in properties_render.RENDER_PT_render.COMPAT_ENGINES + # hasattr(context.scene, 'cycles') + return context.scene def background_render(self, context, cls, preset): generator = path.dirname(path.realpath(__file__)) + path.sep + "archipack_thumbs.py" @@ -64,6 +63,7 @@ class ARCHIPACK_OT_render_thumbs(Operator): "--background", "--factory-startup", "-noaudio", + # "--addons", addon_name, "--python", generator, "--", "addon:" + addon_name, @@ -71,6 +71,9 @@ class ARCHIPACK_OT_render_thumbs(Operator): "cls:" + cls, "preset:" + preset ] + + # print(" ".join(cmd)) + popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) for stdout_line in iter(popen.stdout.readline, ""): yield stdout_line @@ -158,12 +161,16 @@ class ARCHIPACK_OT_render_thumbs(Operator): for i, preset in enumerate(file_list): dir, file = preset cls = dir[10:] - context.scene.archipack_progress = (100 * i / ttl) + # context.scene.archipack_progress = (100 * i / ttl) + log_all = False for l in self.background_render(context, cls, file + ".py"): if "[log]" in l: print(l[5:].strip()) - # elif not "Fra:1" in l: - # print(l.strip()) + elif "blender.crash" in l: + print("Unexpected error") + log_all = True + if log_all: + print(l.strip()) def invoke(self, context, event): addon_name = __name__.split('.')[0] @@ -174,504 +181,16 @@ class ARCHIPACK_OT_render_thumbs(Operator): return context.window_manager.invoke_confirm(self, event) def execute(self, context): - context.scene.archipack_progress_text = 'Generating thumbs' - context.scene.archipack_progress = 0 + # context.scene.archipack_progress_text = 'Generating thumbs' + # context.scene.archipack_progress = 0 self.rebuild_thumbs(context) - context.scene.archipack_progress = -1 + # context.scene.archipack_progress = -1 return {'FINISHED'} -# ------------------------------------------------------------- -# Defines button for render -# -# ------------------------------------------------------------- -class ARCHIPACK_OT_render(Operator): - bl_idname = "archipack.render" - bl_label = "Render" - bl_category = 'Archipack' - bl_description = "Create a render image with measures. Use UV/Image editor to view image generated" - bl_category = 'Archipack' - - # -------------------------------------------------------------------- - # Get the final render image and return as image object - # - # return None if no render available - # -------------------------------------------------------------------- - - def get_render_image(self, outpath): - saved = False - # noinspection PyBroadException - try: - # noinspection PyBroadException - try: - result = bpy.data.images['Render Result'] - if result.has_data is False: - # this save produce to fill data image - result.save_render(outpath) - saved = True - except: - print("No render image found") - return None - - # Save and reload - if saved is False: - result.save_render(outpath) - - img = img_utils.load_image(outpath) - - return img - except: - print("Unexpected render image error") - return None - - # ------------------------------------- - # Save image to file - # ------------------------------------- - - def save_image(self, filepath, myimage): - # noinspection PyBroadException - try: - - # Save old info - settings = bpy.context.scene.render.image_settings - myformat = settings.file_format - mode = settings.color_mode - depth = settings.color_depth - - # Apply new info and save - settings.file_format = 'PNG' - settings.color_mode = "RGBA" - settings.color_depth = '8' - myimage.save_render(filepath) - print("Archipack: Image " + filepath + " saved") - - # Restore old info - settings.file_format = myformat - settings.color_mode = mode - settings.color_depth = depth - except: - print("Unexpected error:" + str(exc_info())) - self.report({'ERROR'}, "Archipack: Unable to save render image") - return - - # ------------------------------------------------------------- - # Render image main entry point - # - # ------------------------------------------------------------- - - def render_main(self, context, objlist, animation=False): - # noinspection PyBroadException,PyBroadException - # Save old info - scene = context.scene - render = scene.render - settings = render.image_settings - depth = settings.color_depth - settings.color_depth = '8' - # noinspection PyBroadException - try: - - # Get visible layers - layers = [] - for x in range(0, 20): - if scene.layers[x] is True: - layers.extend([x]) - - # -------------------- - # Get resolution - # -------------------- - render_scale = render.resolution_percentage / 100 - - width = int(render.resolution_x * render_scale) - height = int(render.resolution_y * render_scale) - # --------------------------------------- - # Get output path - # --------------------------------------- - temp_path = path.realpath(bpy.app.tempdir) - if len(temp_path) > 0: - outpath = path.join(temp_path, "archipack_tmp_render.png") - else: - self.report({'ERROR'}, - "Archipack: Unable to save temporary render image. Define a valid temp path") - settings.color_depth = depth - return False - - # Get Render Image - img = self.get_render_image(outpath) - if img is None: - self.report({'ERROR'}, - "Archipack: Unable to save temporary render image. Define a valid temp path") - settings.color_depth = depth - return False - - # ----------------------------- - # Calculate rows and columns - # ----------------------------- - tile_x = 240 - tile_y = 216 - row_num = ceil(height / tile_y) - col_num = ceil(width / tile_x) - print("Archipack: Image divided in " + str(row_num) + "x" + str(col_num) + " tiles") - - # pixels out of visible area - cut4 = (col_num * tile_x * 4) - width * 4 # pixels aout of drawing area - totpixel4 = width * height * 4 # total pixels RGBA - - viewport_info = bgl.Buffer(bgl.GL_INT, 4) - bgl.glGetIntegerv(bgl.GL_VIEWPORT, viewport_info) - - # Load image on memory - img.gl_load(0, bgl.GL_NEAREST, bgl.GL_NEAREST) - - # 2.77 API change - if bpy.app.version >= (2, 77, 0): - tex = img.bindcode[0] - else: - tex = img.bindcode - - # -------------------------------------------- - # Create output image (to apply texture) - # -------------------------------------------- - if "archipack_output" in bpy.data.images: - out_img = bpy.data.images["archipack_output"] - if out_img is not None: - # out_img.user_clear() - bpy.data.images.remove(out_img, do_unlink=True) - - out = bpy.data.images.new("archipack_output", width, height) - tmp_pixels = [1] * totpixel4 - - # -------------------------------- - # Loop for all tiles - # -------------------------------- - for row in range(0, row_num): - for col in range(0, col_num): - buffer = bgl.Buffer(bgl.GL_FLOAT, width * height * 4) - bgl.glDisable(bgl.GL_SCISSOR_TEST) # if remove this line, get blender screenshot not image - bgl.glViewport(0, 0, tile_x, tile_y) - - bgl.glMatrixMode(bgl.GL_PROJECTION) - bgl.glLoadIdentity() - - # defines ortographic view for single tile - x1 = tile_x * col - y1 = tile_y * row - bgl.gluOrtho2D(x1, x1 + tile_x, y1, y1 + tile_y) - - # Clear - bgl.glClearColor(0.0, 0.0, 0.0, 0.0) - bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT) - - bgl.glEnable(bgl.GL_TEXTURE_2D) - bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex) - - # defines drawing area - bgl.glBegin(bgl.GL_QUADS) - - bgl.glColor3f(1.0, 1.0, 1.0) - bgl.glTexCoord2f(0.0, 0.0) - bgl.glVertex2f(0.0, 0.0) - - bgl.glTexCoord2f(1.0, 0.0) - bgl.glVertex2f(width, 0.0) - - bgl.glTexCoord2f(1.0, 1.0) - bgl.glVertex2f(width, height) - - bgl.glTexCoord2f(0.0, 1.0) - bgl.glVertex2f(0.0, height) - - bgl.glEnd() - - # ----------------------------- - # Loop to draw all lines - # ----------------------------- - for o, d in objlist: - if o.hide is False: - # verify visible layer - for x in range(0, 20): - if o.layers[x] is True: - if x in layers: - context.scene.objects.active = o - # print("%s: %s" % (o.name, d.manip_stack)) - manipulators = d.manip_stack - if manipulators is not None: - for m in manipulators: - if m is not None: - m.draw_callback(m, context, render=True) - break - - # ----------------------------- - # Loop to draw all debug - # ----------------------------- - """ - if scene.archipack_debug is True: - selobj = bpy.context.selected_objects - for myobj in selobj: - if scene.archipack_debug_vertices is True: - draw_vertices(context, myobj, None, None) - if scene.archipack_debug_faces is True or scene.archipack_debug_normals is True: - draw_faces(context, myobj, None, None) - """ - """ - if scene.archipack_rf is True: - bgl.glColor3f(1.0, 1.0, 1.0) - rfcolor = scene.archipack_rf_color - rfborder = scene.archipack_rf_border - rfline = scene.archipack_rf_line - - bgl.glLineWidth(rfline) - bgl.glColor4f(rfcolor[0], rfcolor[1], rfcolor[2], rfcolor[3]) - - x1 = rfborder - x2 = width - rfborder - y1 = int(ceil(rfborder / (width / height))) - y2 = height - y1 - draw_rectangle((x1, y1), (x2, y2)) - """ - # -------------------------------- - # copy pixels to temporary area - # -------------------------------- - bgl.glFinish() - bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_FLOAT, buffer) # read image data - for y in range(0, tile_y): - # final image pixels position - p1 = (y * width * 4) + (row * tile_y * width * 4) + (col * tile_x * 4) - p2 = p1 + (tile_x * 4) - # buffer pixels position - b1 = y * width * 4 - b2 = b1 + (tile_x * 4) - - if p1 < totpixel4: # avoid pixel row out of area - if col == col_num - 1: # avoid pixel columns out of area - p2 -= cut4 - b2 -= cut4 - - tmp_pixels[p1:p2] = buffer[b1:b2] - - # ----------------------- - # Copy temporary to final - # ----------------------- - out.pixels = tmp_pixels[:] # Assign image data - img.gl_free() # free opengl image memory - - # delete image - # img.user_clear() - bpy.data.images.remove(img, do_unlink=True) - # remove temp file - remove(outpath) - # reset - bgl.glEnable(bgl.GL_SCISSOR_TEST) - # ----------------------- - # restore opengl defaults - # ----------------------- - bgl.glLineWidth(1) - bgl.glDisable(bgl.GL_BLEND) - bgl.glColor4f(0.0, 0.0, 0.0, 1.0) - # Saves image - if out is not None: - # and (scene.archipack_render is True or animation is True): - ren_path = bpy.context.scene.render.filepath - filename = "ap_frame" - if len(ren_path) > 0: - if ren_path.endswith(path.sep): - initpath = path.realpath(ren_path) + path.sep - else: - (initpath, filename) = path.split(ren_path) - - ftxt = "%04d" % scene.frame_current - outpath = path.realpath(path.join(initpath, filename + ftxt + ".png")) - - self.save_image(outpath, out) - - settings.color_depth = depth - return True - - except: - settings.color_depth = depth - print("Unexpected error:" + str(exc_info())) - self.report( - {'ERROR'}, - "Archipack: Unable to create render image. Be sure the output render path is correct" - ) - return False - - def get_objlist(self, context): - """ - Get objects with gl manipulators - """ - objlist = [] - for o in context.scene.objects: - if o.data is not None: - d = None - if 'archipack_window' in o.data: - d = o.data.archipack_window[0] - elif 'archipack_door' in o.data: - d = o.data.archipack_door[0] - elif 'archipack_wall2' in o.data: - d = o.data.archipack_wall2[0] - elif 'archipack_stair' in o.data: - d = o.data.archipack_stair[0] - elif 'archipack_fence' in o.data: - d = o.data.archipack_fence[0] - elif 'archipack_floor' in o.data: - d = o.data.archipack_floor[0] - elif 'archipack_roof' in o.data: - d = o.data.archipack_roof[0] - if d is not None: - objlist.append((o, d)) - return objlist - - def draw_gl(self, context): - objlist = self.get_objlist(context) - for o, d in objlist: - context.scene.objects.active = o - d.manipulable_disable(context) - d.manipulable_invoke(context) - return objlist - - def hide_gl(self, context, objlist): - for o, d in objlist: - context.scene.objects.active = o - d.manipulable_disable(context) - - # ------------------------------ - # Execute button action - # ------------------------------ - # noinspection PyMethodMayBeStatic,PyUnusedLocal - def execute(self, context): - scene = context.scene - wm = context.window_manager - msg = "New image created with measures. Open it in UV/image editor" - camera_msg = "Unable to render. No camera found" - - # ----------------------------- - # Check camera - # ----------------------------- - if scene.camera is None: - self.report({'ERROR'}, camera_msg) - return {'FINISHED'} - - objlist = self.draw_gl(context) - - # ----------------------------- - # Use current rendered image - # ----------------------------- - if wm.archipack.render_type == "1": - # noinspection PyBroadException - try: - result = bpy.data.images['Render Result'] - if result.has_data is False: - bpy.ops.render.render() - except: - bpy.ops.render.render() - - print("Archipack: Using current render image on buffer") - if self.render_main(context, objlist) is True: - self.report({'INFO'}, msg) - - # ----------------------------- - # OpenGL image - # ----------------------------- - elif wm.archipack.render_type == "2": - self.set_camera_view() - self.set_only_render(True) - - print("Archipack: Rendering opengl image") - bpy.ops.render.opengl() - if self.render_main(context, objlist) is True: - self.report({'INFO'}, msg) - - self.set_only_render(False) - - # ----------------------------- - # OpenGL Animation - # ----------------------------- - elif wm.archipack.render_type == "3": - oldframe = scene.frame_current - self.set_camera_view() - self.set_only_render(True) - flag = False - # loop frames - for frm in range(scene.frame_start, scene.frame_end + 1): - scene.frame_set(frm) - print("Archipack: Rendering opengl frame %04d" % frm) - bpy.ops.render.opengl() - flag = self.render_main(context, objlist, True) - if flag is False: - break - - self.set_only_render(False) - scene.frame_current = oldframe - if flag is True: - self.report({'INFO'}, msg) - - # ----------------------------- - # Image - # ----------------------------- - elif wm.archipack.render_type == "4": - print("Archipack: Rendering image") - bpy.ops.render.render() - if self.render_main(context, objlist) is True: - self.report({'INFO'}, msg) - - # ----------------------------- - # Animation - # ----------------------------- - elif wm.archipack.render_type == "5": - oldframe = scene.frame_current - flag = False - # loop frames - for frm in range(scene.frame_start, scene.frame_end + 1): - scene.frame_set(frm) - print("Archipack: Rendering frame %04d" % frm) - bpy.ops.render.render() - flag = self.render_main(context, objlist, True) - if flag is False: - break - - scene.frame_current = oldframe - if flag is True: - self.report({'INFO'}, msg) - - self.hide_gl(context, objlist) - - return {'FINISHED'} - - # --------------------- - # Set cameraView - # --------------------- - # noinspection PyMethodMayBeStatic - def set_camera_view(self): - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - area.spaces[0].region_3d.view_perspective = 'CAMERA' - - # ------------------------------------- - # Set only render status - # ------------------------------------- - # noinspection PyMethodMayBeStatic - def set_only_render(self, status): - screen = bpy.context.screen - - v3d = False - s = None - # get spaceview_3d in current screen - for a in screen.areas: - if a.type == 'VIEW_3D': - for s in a.spaces: - if s.type == 'VIEW_3D': - v3d = s - break - - if v3d is not False: - s.show_only_render = status - - def register(): - bpy.utils.register_class(ARCHIPACK_OT_render) bpy.utils.register_class(ARCHIPACK_OT_render_thumbs) def unregister(): - bpy.utils.unregister_class(ARCHIPACK_OT_render) bpy.utils.unregister_class(ARCHIPACK_OT_render_thumbs) diff --git a/archipack/archipack_roof.py b/archipack/archipack_roof.py index 024dccb7..02bb01e8 100644 --- a/archipack/archipack_roof.py +++ b/archipack/archipack_roof.py @@ -1564,7 +1564,7 @@ class RoofGenerator(CutAbleGenerator): use_dissolve_boundaries=False, verts=bm.verts, edges=bm.edges, - delimit=1) + delimit={'MATERIAL'}) geom = bm.faces[:] verts = bm.verts[:] @@ -1595,7 +1595,7 @@ class RoofGenerator(CutAbleGenerator): BEVEL_AMT_PERCENT }; """ - offset_type = 3 + offset_type = 'PERCENT' if d.tile_offset > 0: offset = - d.tile_offset / 100 @@ -1694,8 +1694,8 @@ class RoofGenerator(CutAbleGenerator): step = 100 / ttl - if d.quick_edit: - context.scene.archipack_progress_text = "Build tiles:" + # if d.quick_edit: + # context.scene.archipack_progress_text = "Build tiles:" for i, pan in enumerate(self.pans): @@ -1742,8 +1742,8 @@ class RoofGenerator(CutAbleGenerator): progress = step * i + substep * k # print("progress %s" % (progress)) - if d.quick_edit: - context.scene.archipack_progress = progress + # if d.quick_edit: + # context.scene.archipack_progress = progress y = k * dy @@ -1759,7 +1759,7 @@ class RoofGenerator(CutAbleGenerator): for j in range(nx): x = x0 + j * dx - lM = tM * Matrix([ + lM = tM @ Matrix([ [sx, 0, 0, x], [0, sy, 0, -y], [0, 0, sz, 0], @@ -1768,7 +1768,7 @@ class RoofGenerator(CutAbleGenerator): v = len(verts) - verts.extend([lM * p for p in t_pts]) + verts.extend([lM @ p for p in t_pts]) faces.extend([tuple(i + v for i in f) for f in t_faces]) mid = randint(idmat, idmat + rand) t_mats = [mid for i in range(n_faces)] @@ -1847,7 +1847,7 @@ class RoofGenerator(CutAbleGenerator): segs[-1].p1 = p f_geom = [f for f in bm.faces if not pan.inside(f.calc_center_median().to_2d(), segs)] if len(f_geom) > 0: - bmesh.ops.delete(bm, geom=f_geom, context=5) + bmesh.ops.delete(bm, geom=f_geom, context="FACES") self.cut_holes(bm, pan) @@ -1856,7 +1856,7 @@ class RoofGenerator(CutAbleGenerator): use_dissolve_boundaries=False, verts=bm.verts[:], edges=bm.edges[:], - delimit=1) + delimit={'MATERIAL'}) if d.tile_bevel: geom = bm.verts[:] @@ -1881,8 +1881,8 @@ class RoofGenerator(CutAbleGenerator): bmed.bmesh_join(context, o, [bm], normal_update=True) bpy.ops.object.mode_set(mode='OBJECT') - if d.quick_edit: - context.scene.archipack_progress = -1 + # if d.quick_edit: + # context.scene.archipack_progress = -1 def _bargeboard(self, s, i, boundary, pan, width, height, altitude, offset, idmat, @@ -2774,7 +2774,7 @@ class RoofGenerator(CutAbleGenerator): use_dissolve_boundaries=False, verts=bm.verts, edges=bm.edges, - delimit=1) + delimit={'MATERIAL'}) geom = bm.faces[:] verts = bm.verts[:] @@ -3040,7 +3040,7 @@ class RoofGenerator(CutAbleGenerator): t_verts[i].z += dz * t_verts[i].y for k in range(n_x): - lM = tM * Matrix([ + lM = tM @ Matrix([ [1, 0, 0, x0 + k * dx], [0, -1, 0, 0], [0, 0, 1, 0], @@ -3048,7 +3048,7 @@ class RoofGenerator(CutAbleGenerator): ]) f = len(verts) - verts.extend([lM * p for p in t_verts]) + verts.extend([lM @ p for p in t_verts]) faces.extend([tuple(i + f for i in p) for p in t_faces]) matids.extend(t_idmats) uvs.extend(t_uvs) @@ -3132,14 +3132,14 @@ class RoofGenerator(CutAbleGenerator): t_verts[i].z -= t_verts[i].y * (pan.slope - d.tile_size_z / d.tile_size_y) for k in range(n_obj): - lM = tM * Matrix([ + lM = tM @ Matrix([ [1, 0, 0, x0 + k * dx], [0, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ]) v = len(verts) - verts.extend([lM * p for p in t_verts]) + verts.extend([lM @ p for p in t_verts]) faces.extend([tuple(i + v for i in f) for f in t_faces]) matids.extend(t_idmats) uvs.extend(t_uvs) @@ -3243,19 +3243,19 @@ class RoofGenerator(CutAbleGenerator): pts = [p.to_3d() for p in pts] if hole_obj is None: - context.scene.objects.active = o.parent + context.view_layer.objects.active = o.parent bpy.ops.archipack.roof_cutter(parent=d.t_parent, auto_manipulate=False) hole_obj = context.active_object else: - context.scene.objects.active = hole_obj + context.view_layer.objects.active = hole_obj - hole_obj.select = True + hole_obj.select_set(state=True) if d.parts[0].a0 < 0: y = -d.t_dist_y else: y = d.t_dist_y - hole_obj.matrix_world = o.matrix_world * Matrix([ + hole_obj.matrix_world = o.matrix_world @ Matrix([ [1, 0, 0, 0], [0, 1, 0, y], [0, 0, 1, 0], @@ -3265,15 +3265,15 @@ class RoofGenerator(CutAbleGenerator): hd = archipack_roof_cutter.datablock(hole_obj) hd.boundary = o.name hd.update_points(context, hole_obj, pts, update_parent=update_parent) - hole_obj.select = False + hole_obj.select_set(state=False) - context.scene.objects.active = o + context.view_layer.objects.active = o def change_coordsys(self, fromTM, toTM): """ move shape fromTM into toTM coordsys """ - dp = (toTM.inverted() * fromTM.translation).to_2d() + dp = (toTM.inverted() @ fromTM.translation).to_2d() da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d()) ca = cos(da) sa = sin(da) @@ -3282,7 +3282,7 @@ class RoofGenerator(CutAbleGenerator): [sa, ca] ]) for s in self.segs: - tp = (rM * s.p0) - s.p0 + dp + tp = (rM @ s.p0) - s.p0 + dp s.rotate(da) s.translate(tp) @@ -3357,9 +3357,9 @@ class RoofGenerator(CutAbleGenerator): wall_t[widx].append((0, z, 0)) old = context.active_object - old_sel = wall.select - wall.select = True - context.scene.objects.active = wall + old_sel = wall.select_get() + wall.select_set(state=True) + context.view_layer.objects.active = wall wd.auto_update = False # setup splits count and first split to 0 @@ -3412,8 +3412,8 @@ class RoofGenerator(CutAbleGenerator): for s in wg.segs: s.as_curve(context) """ - wall.select = old_sel - context.scene.objects.active = old + wall.select_set(state=old_sel) + context.view_layer.objects.active = old def boundary(self, context, o): """ @@ -3494,14 +3494,14 @@ def update_components(self, context): class ArchipackSegment(): - length = FloatProperty( + length : FloatProperty( name="Length", min=0.01, max=1000.0, default=4.0, update=update ) - a0 = FloatProperty( + a0 : FloatProperty( name="Angle", min=-2 * pi, max=2 * pi, @@ -3509,17 +3509,17 @@ class ArchipackSegment(): subtype='ANGLE', unit='ROTATION', update=update_cutter ) - manipulators = CollectionProperty(type=archipack_manipulator) + manipulators : CollectionProperty(type=archipack_manipulator) class ArchipackLines(): - n_parts = IntProperty( + n_parts : IntProperty( name="Parts", min=1, default=1, update=update_manipulators ) # UI layout related - parts_expand = BoolProperty( + parts_expand : BoolProperty( default=False ) @@ -3580,37 +3580,37 @@ class ArchipackLines(): class archipack_roof_segment(ArchipackSegment, PropertyGroup): - bound_idx = IntProperty( + bound_idx : IntProperty( name="Link to", default=0, min=0, update=update_manipulators ) - width_left = FloatProperty( + width_left : FloatProperty( name="L Width", min=0.01, default=3.0, update=update_cutter ) - width_right = FloatProperty( + width_right : FloatProperty( name="R Width", min=0.01, default=3.0, update=update_cutter ) - slope_left = FloatProperty( + slope_left : FloatProperty( name="L slope", min=0.0, default=0.3, update=update_cutter ) - slope_right = FloatProperty( + slope_right : FloatProperty( name="R slope", min=0.0, default=0.3, update=update_cutter ) - auto_left = EnumProperty( + auto_left : EnumProperty( description="Left mode", name="Left", items=( @@ -3622,7 +3622,7 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup): default="AUTO", update=update_manipulators ) - auto_right = EnumProperty( + auto_right : EnumProperty( description="Right mode", name="Right", items=( @@ -3634,19 +3634,19 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup): default="AUTO", update=update_manipulators ) - triangular_end = BoolProperty( + triangular_end : BoolProperty( name="Triangular end", default=False, update=update ) - take_precedence = BoolProperty( + take_precedence : BoolProperty( name="Take precedence", description="On T segment take width precedence", default=False, update=update ) - constraint_type = EnumProperty( + constraint_type : EnumProperty( items=( ('HORIZONTAL', 'Horizontal', '', 0), ('SLOPE', 'Slope', '', 1) @@ -3655,7 +3655,7 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup): update=update_manipulators ) - enforce_part = EnumProperty( + enforce_part : EnumProperty( name="Enforce part", items=( ('AUTO', 'Auto', '', 0), @@ -3671,7 +3671,7 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup): find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: d = archipack_roof.datablock(o) if d: @@ -3687,7 +3687,7 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup): if self.constraint_type == 'SLOPE': box.prop(self, "enforce_part", text="") else: - box.label("Part 1:") + box.label(text="Part 1:") box.prop(self, "length") box.prop(self, "a0") @@ -3720,63 +3720,63 @@ class archipack_roof_segment(ArchipackSegment, PropertyGroup): class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup): - parts = CollectionProperty(type=archipack_roof_segment) - z = FloatProperty( + parts : CollectionProperty(type=archipack_roof_segment) + z : FloatProperty( name="Altitude", default=3, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update_childs ) - slope_left = FloatProperty( + slope_left : FloatProperty( name="L slope", default=0.5, precision=2, step=1, update=update_childs ) - slope_right = FloatProperty( + slope_right : FloatProperty( name="R slope", default=0.5, precision=2, step=1, update=update_childs ) - width_left = FloatProperty( + width_left : FloatProperty( name="L width", default=3, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update_cutter ) - width_right = FloatProperty( + width_right : FloatProperty( name="R width", default=3, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update_cutter ) - draft = BoolProperty( + draft : BoolProperty( options={'SKIP_SAVE'}, name="Draft mode", default=False, update=update_manipulators ) - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update_manipulators ) - quick_edit = BoolProperty( + quick_edit : BoolProperty( options={'SKIP_SAVE'}, name="Quick Edit", - default=True + default=False ) - tile_enable = BoolProperty( + tile_enable : BoolProperty( name="Enable", default=True, update=update_components ) - tile_solidify = BoolProperty( + tile_solidify : BoolProperty( name="Solidify", default=True, update=update_components ) - tile_height = FloatProperty( + tile_height : FloatProperty( name="Height", description="Amount for solidify", min=0, @@ -3784,12 +3784,12 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_bevel = BoolProperty( + tile_bevel : BoolProperty( name="Bevel", default=False, update=update_components ) - tile_bevel_amt = FloatProperty( + tile_bevel_amt : FloatProperty( name="Amount", description="Amount for bevel", min=0, @@ -3797,19 +3797,19 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_bevel_segs = IntProperty( + tile_bevel_segs : IntProperty( name="Segs", description="Bevel Segs", min=1, default=2, update=update_components ) - tile_alternate = BoolProperty( + tile_alternate : BoolProperty( name="Alternate", default=False, update=update_components ) - tile_offset = FloatProperty( + tile_offset : FloatProperty( name="Offset", description="Offset from start", min=0, @@ -3817,14 +3817,14 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup subtype="PERCENTAGE", update=update_components ) - tile_altitude = FloatProperty( + tile_altitude : FloatProperty( name="Altitude", description="Altitude from roof", default=0.1, unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_size_x = FloatProperty( + tile_size_x : FloatProperty( name="Width", description="Size of tiles on x axis", min=0.01, @@ -3832,7 +3832,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_size_y = FloatProperty( + tile_size_y : FloatProperty( name="Length", description="Size of tiles on y axis", min=0.01, @@ -3840,7 +3840,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_size_z = FloatProperty( + tile_size_z : FloatProperty( name="Thickness", description="Size of tiles on z axis", min=0.0, @@ -3848,7 +3848,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_space_x = FloatProperty( + tile_space_x : FloatProperty( name="Width", description="Space between tiles on x axis", min=0.01, @@ -3856,7 +3856,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_space_y = FloatProperty( + tile_space_y : FloatProperty( name="Length", description="Space between tiles on y axis", min=0.01, @@ -3864,25 +3864,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_fit_x = BoolProperty( + tile_fit_x : BoolProperty( name="Fit x", description="Fit roof on x axis", default=True, update=update_components ) - tile_fit_y = BoolProperty( + tile_fit_y : BoolProperty( name="Fit y", description="Fit roof on y axis", default=True, update=update_components ) - tile_expand = BoolProperty( + tile_expand : BoolProperty( options={'SKIP_SAVE'}, name="Tiles", description="Expand tiles panel", default=False ) - tile_model = EnumProperty( + tile_model : EnumProperty( name="Model", items=( ('BRAAS1', 'Braas 1', '', 0), @@ -3899,14 +3899,14 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup default="BRAAS2", update=update_components ) - tile_side = FloatProperty( + tile_side : FloatProperty( name="Side", description="Space on side", default=0, unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_couloir = FloatProperty( + tile_couloir : FloatProperty( name="Valley", description="Space between tiles on valley", min=0, @@ -3914,7 +3914,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - tile_border = FloatProperty( + tile_border : FloatProperty( name="Bottom", description="Tiles offset from bottom", default=0, @@ -3922,25 +3922,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup update=update_components ) - gutter_expand = BoolProperty( + gutter_expand : BoolProperty( options={'SKIP_SAVE'}, name="Gutter", description="Expand gutter panel", default=False ) - gutter_enable = BoolProperty( + gutter_enable : BoolProperty( name="Enable", default=True, update=update_components ) - gutter_alt = FloatProperty( + gutter_alt : FloatProperty( name="Altitude", description="altitude", default=0, unit='LENGTH', subtype='DISTANCE', update=update_components ) - gutter_width = FloatProperty( + gutter_width : FloatProperty( name="Width", description="Width", min=0.01, @@ -3948,7 +3948,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - gutter_dist = FloatProperty( + gutter_dist : FloatProperty( name="Spacing", description="Spacing", min=0, @@ -3956,7 +3956,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - gutter_boudin = FloatProperty( + gutter_boudin : FloatProperty( name="Small width", description="Small width", min=0, @@ -3964,25 +3964,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - gutter_segs = IntProperty( + gutter_segs : IntProperty( default=6, min=1, name="Segs", update=update_components ) - beam_expand = BoolProperty( + beam_expand : BoolProperty( options={'SKIP_SAVE'}, name="Beam", description="Expand beam panel", default=False ) - beam_enable = BoolProperty( + beam_enable : BoolProperty( name="Ridge pole", default=True, update=update_components ) - beam_width = FloatProperty( + beam_width : FloatProperty( name="Width", description="Width", min=0.01, @@ -3990,7 +3990,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - beam_height = FloatProperty( + beam_height : FloatProperty( name="Height", description="Height", min=0.01, @@ -3998,26 +3998,26 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - beam_offset = FloatProperty( + beam_offset : FloatProperty( name="Offset", description="Distance from roof border", default=0.02, unit='LENGTH', subtype='DISTANCE', update=update_components ) - beam_alt = FloatProperty( + beam_alt : FloatProperty( name="Altitude", description="Altitude from roof", default=-0.15, unit='LENGTH', subtype='DISTANCE', update=update_components ) - beam_sec_enable = BoolProperty( + beam_sec_enable : BoolProperty( name="Hip rafter", default=True, update=update_components ) - beam_sec_width = FloatProperty( + beam_sec_width : FloatProperty( name="Width", description="Width", min=0.01, @@ -4025,7 +4025,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - beam_sec_height = FloatProperty( + beam_sec_height : FloatProperty( name="Height", description="Height", min=0.01, @@ -4033,19 +4033,19 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - beam_sec_alt = FloatProperty( + beam_sec_alt : FloatProperty( name="Altitude", description="Distance from roof", default=-0.1, unit='LENGTH', subtype='DISTANCE', update=update_components ) - rafter_enable = BoolProperty( + rafter_enable : BoolProperty( name="Rafter", default=True, update=update_components ) - rafter_width = FloatProperty( + rafter_width : FloatProperty( name="Width", description="Width", min=0.01, @@ -4053,7 +4053,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - rafter_height = FloatProperty( + rafter_height : FloatProperty( name="Height", description="Height", min=0.01, @@ -4061,7 +4061,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - rafter_spacing = FloatProperty( + rafter_spacing : FloatProperty( name="Spacing", description="Spacing", min=0.1, @@ -4069,7 +4069,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - rafter_start = FloatProperty( + rafter_start : FloatProperty( name="Offset", description="Spacing from roof border", min=0, @@ -4077,7 +4077,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - rafter_alt = FloatProperty( + rafter_alt : FloatProperty( name="Altitude", description="Altitude from roof", max=-0.0001, @@ -4086,25 +4086,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup update=update_components ) - hip_enable = BoolProperty( + hip_enable : BoolProperty( name="Enable", default=True, update=update_components ) - hip_expand = BoolProperty( + hip_expand : BoolProperty( options={'SKIP_SAVE'}, name="Hips", description="Expand hips panel", default=False ) - hip_alt = FloatProperty( + hip_alt : FloatProperty( name="Altitude", description="Hip altitude from roof", default=0.1, unit='LENGTH', subtype='DISTANCE', update=update_components ) - hip_space_x = FloatProperty( + hip_space_x : FloatProperty( name="Spacing", description="Space between hips", min=0.01, @@ -4112,7 +4112,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - hip_size_x = FloatProperty( + hip_size_x : FloatProperty( name="Length", description="Length of hip", min=0.01, @@ -4120,7 +4120,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - hip_size_y = FloatProperty( + hip_size_y : FloatProperty( name="Width", description="Width of hip", min=0.01, @@ -4128,7 +4128,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - hip_size_z = FloatProperty( + hip_size_z : FloatProperty( name="Height", description="Height of hip", min=0.0, @@ -4136,7 +4136,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - hip_model = EnumProperty( + hip_model : EnumProperty( name="Model", items=( ('ROUND', 'Round', '', 0), @@ -4146,32 +4146,32 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup default="ROUND", update=update_components ) - valley_altitude = FloatProperty( + valley_altitude : FloatProperty( name="Altitude", description="Valley altitude from roof", default=0.1, unit='LENGTH', subtype='DISTANCE', update=update_components ) - valley_enable = BoolProperty( + valley_enable : BoolProperty( name="Valley", default=True, update=update_components ) - fascia_enable = BoolProperty( + fascia_enable : BoolProperty( name="Enable", description="Enable Fascia", default=True, update=update_components ) - fascia_expand = BoolProperty( + fascia_expand : BoolProperty( options={'SKIP_SAVE'}, name="Fascia", description="Expand fascia panel", default=False ) - fascia_height = FloatProperty( + fascia_height : FloatProperty( name="Height", description="Height", min=0.01, @@ -4179,7 +4179,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - fascia_width = FloatProperty( + fascia_width : FloatProperty( name="Width", description="Width", min=0.01, @@ -4187,14 +4187,14 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - fascia_offset = FloatProperty( + fascia_offset : FloatProperty( name="Offset", description="Offset from roof border", default=0, unit='LENGTH', subtype='DISTANCE', update=update_components ) - fascia_altitude = FloatProperty( + fascia_altitude : FloatProperty( name="Altitude", description="Fascia altitude from roof", default=0.1, @@ -4202,19 +4202,19 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup update=update_components ) - bargeboard_enable = BoolProperty( + bargeboard_enable : BoolProperty( name="Enable", description="Enable Bargeboard", default=True, update=update_components ) - bargeboard_expand = BoolProperty( + bargeboard_expand : BoolProperty( options={'SKIP_SAVE'}, name="Bargeboard", description="Expand Bargeboard panel", default=False ) - bargeboard_height = FloatProperty( + bargeboard_height : FloatProperty( name="Height", description="Height", min=0.01, @@ -4222,7 +4222,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - bargeboard_width = FloatProperty( + bargeboard_width : FloatProperty( name="Width", description="Width", min=0.01, @@ -4230,14 +4230,14 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup unit='LENGTH', subtype='DISTANCE', update=update_components ) - bargeboard_offset = FloatProperty( + bargeboard_offset : FloatProperty( name="Offset", description="Offset from roof border", default=0.001, unit='LENGTH', subtype='DISTANCE', update=update_components ) - bargeboard_altitude = FloatProperty( + bargeboard_altitude : FloatProperty( name="Altitude", description="Fascia altitude from roof", default=0.1, @@ -4245,25 +4245,25 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup update=update_components ) - t_parent = StringProperty( + t_parent : StringProperty( name="Parent", default="", update=update_parent ) - t_part = IntProperty( + t_part : IntProperty( name="Part", description="Parent part index", default=0, min=0, update=update_cutter ) - t_dist_x = FloatProperty( + t_dist_x : FloatProperty( name="Dist x", description="Location on axis ", default=0, update=update_cutter ) - t_dist_y = FloatProperty( + t_dist_y : FloatProperty( name="Dist y", description="Lateral distance from axis", min=0.0001, @@ -4271,21 +4271,21 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup update=update_cutter ) - hole_offset_left = FloatProperty( + hole_offset_left : FloatProperty( name="Left", description="Left distance from border", min=0, default=0, update=update_cutter ) - hole_offset_right = FloatProperty( + hole_offset_right : FloatProperty( name="Right", description="Right distance from border", min=0, default=0, update=update_cutter ) - hole_offset_front = FloatProperty( + hole_offset_front : FloatProperty( name="Front", description="Front distance from border", default=0, @@ -4387,7 +4387,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup bm.free() def find_parent(self, context): - o = context.scene.objects.get(self.t_parent) + o = context.scene.objects.get(self.t_parent.strip()) return o, archipack_roof.datablock(o) def intersection_angle(self, t_slope, t_width, p_slope, angle): @@ -4420,7 +4420,7 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup t = (d.t_dist_x / seg.length) x, y, z = seg.lerp(t).to_3d() dy = -seg.v.normalized() - child.matrix_world = o.matrix_world * Matrix([ + child.matrix_world = o.matrix_world @ Matrix([ [dy.x, -dy.y, 0, x], [dy.y, dy.x, 0, y], [0, 0, 1, z], @@ -4438,13 +4438,13 @@ class archipack_roof(ArchipackLines, ArchipackObject, Manipulable, PropertyGroup d = archipack_roof.datablock(child) if d is not None and d.t_parent == o.name: # print("upate_childs(%s)" % (child.name)) - child.select = True - context.scene.objects.active = child + child.select_set(state=True) + context.view_layer.objects.active = child # regenerate hole d.update(context, update_hole=True, update_parent=False) - child.select = False - o.select = True - context.scene.objects.active = o + child.select_set(state=False) + o.select_set(state=True) + context.view_layer.objects.active = o def update(self, context, @@ -4726,8 +4726,8 @@ def update_operation(self, context): class archipack_roof_cutter_segment(ArchipackCutterPart, PropertyGroup): - manipulators = CollectionProperty(type=archipack_manipulator) - type = EnumProperty( + manipulators : CollectionProperty(type=archipack_manipulator) + type : EnumProperty( name="Type", items=( ('SIDE', 'Side', 'Side with bargeboard', 0), @@ -4742,7 +4742,7 @@ class archipack_roof_cutter_segment(ArchipackCutterPart, PropertyGroup): ) def find_in_selection(self, context): - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: d = archipack_roof_cutter.datablock(o) if d: @@ -4754,8 +4754,8 @@ class archipack_roof_cutter_segment(ArchipackCutterPart, PropertyGroup): class archipack_roof_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup): # boundary - parts = CollectionProperty(type=archipack_roof_cutter_segment) - boundary = StringProperty( + parts : CollectionProperty(type=archipack_roof_cutter_segment) + boundary : StringProperty( default="", name="Boundary", description="Boundary of t child to cut parent" @@ -4778,11 +4778,11 @@ class archipack_roof_cutter(ArchipackCutter, ArchipackObject, Manipulable, Prope d = archipack_roof.datablock(o.parent) if d is not None: - o.parent.select = True - context.scene.objects.active = o.parent + o.parent.select_set(state=True) + context.view_layer.objects.active = o.parent d.update(context, update_childs=False, update_hole=False) - o.parent.select = False - context.scene.objects.active = o + o.parent.select_set(state=False) + context.view_layer.objects.active = o # print("update_parent") @@ -4791,7 +4791,7 @@ class ARCHIPACK_PT_roof_cutter(Panel): bl_label = "Roof Cutter" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -4808,7 +4808,7 @@ class ARCHIPACK_PT_roof_cutter(Panel): box.label(text="Auto Cutter:") box.label(text=prop.boundary) else: - box.operator('archipack.roof_cutter_manipulate', icon='HAND') + box.operator('archipack.roof_cutter_manipulate', icon='VIEW_PAN') box.prop(prop, 'operation', text="") box = layout.box() box.label(text="From curve") @@ -4828,7 +4828,7 @@ class ARCHIPACK_PT_roof(Panel): bl_label = "Roof" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -4842,13 +4842,13 @@ class ARCHIPACK_PT_roof(Panel): scene = context.scene layout = self.layout row = layout.row(align=True) - row.operator('archipack.roof_manipulate', icon='HAND') + row.operator('archipack.roof_manipulate', icon='VIEW_PAN') box = layout.box() row = box.row(align=True) row.operator("archipack.roof_preset_menu", text=bpy.types.ARCHIPACK_OT_roof_preset_menu.bl_label) - row.operator("archipack.roof_preset", text="", icon='ZOOMIN') - row.operator("archipack.roof_preset", text="", icon='ZOOMOUT').remove_active = True + row.operator("archipack.roof_preset", text="", icon='ADD') + row.operator("archipack.roof_preset", text="", icon='REMOVE').remove_active = True box = layout.box() box.prop_search(prop, "t_parent", scene, "objects", text="Parent", icon='OBJECT_DATA') layout.operator('archipack.roof_cutter').parent = o.name @@ -5029,9 +5029,9 @@ class ARCHIPACK_OT_roof(ArchipackCreateTool, Operator): d = m.archipack_roof.add() # make manipulators selectable d.manipulable_selectable = True - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o self.add_material(o) # disable progress bar when @@ -5050,8 +5050,8 @@ class ARCHIPACK_OT_roof(ArchipackCreateTool, Operator): bpy.ops.object.select_all(action="DESELECT") o = self.create(context) o.location = context.scene.cursor_location - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: @@ -5066,13 +5066,13 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator): bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - parent = StringProperty("") + parent : StringProperty("") def create(self, context): m = bpy.data.meshes.new("Roof Cutter") o = bpy.data.objects.new("Roof Cutter", m) d = m.archipack_roof_cutter.add() - parent = context.scene.objects.get(self.parent) + parent = context.scene.objects.get(self.parent.strip()) if parent is not None: o.parent = parent bbox = parent.bound_box @@ -5081,7 +5081,7 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator): x1, y1, z = bbox[6] x = 0.2 * (x1 - x0) y = 0.2 * (y1 - y0) - o.matrix_world = parent.matrix_world * Matrix([ + o.matrix_world = parent.matrix_world @ Matrix([ [1, 0, 0, -3 * x], [0, 1, 0, 0], [0, 0, 1, 0], @@ -5104,9 +5104,9 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator): o.location = context.scene.cursor_location # make manipulators selectable d.manipulable_selectable = True - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o self.add_material(o) self.load_preset(d) update_operation(d, context) @@ -5119,8 +5119,8 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: @@ -5133,14 +5133,14 @@ class ARCHIPACK_OT_roof_cutter(ArchipackCreateTool, Operator): # ------------------------------------------------------------------ -class ARCHIPACK_OT_roof_from_curve(Operator): +class ARCHIPACK_OT_roof_from_curve(ArchipackCreateTool, Operator): bl_idname = "archipack.roof_from_curve" bl_label = "Roof curve" bl_description = "Create a roof from a curve" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - auto_manipulate = BoolProperty(default=True) + auto_manipulate : BoolProperty(default=True) @classmethod def poll(self, context): @@ -5149,7 +5149,7 @@ class ARCHIPACK_OT_roof_from_curve(Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def create(self, context): curve = context.active_object @@ -5159,9 +5159,9 @@ class ARCHIPACK_OT_roof_from_curve(Operator): # make manipulators selectable d.manipulable_selectable = True d.user_defined_path = curve.name - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o d.update_path(context) spline = curve.data.splines[0] @@ -5172,14 +5172,14 @@ class ARCHIPACK_OT_roof_from_curve(Operator): else: pt = Vector((0, 0, 0)) # pretranslate - o.matrix_world = curve.matrix_world * Matrix([ + o.matrix_world = curve.matrix_world @ Matrix([ [1, 0, 0, pt.x], [0, 1, 0, pt.y], [0, 0, 1, pt.z], [0, 0, 0, 1] ]) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return o # ----------------------------------------------------- @@ -5250,7 +5250,7 @@ class ArchipackThrottleHandler(): def start_timer(self, context): self.start = time.time() - self._timer = context.window_manager.event_timer_add(self.delay, context.window) + self._timer = context.window_manager.event_timer_add(self.delay, window=context.window) def stop_timer(self, context): if self._timer is not None: @@ -5300,7 +5300,7 @@ class ARCHIPACK_OT_roof_throttle_update(Operator): bl_idname = "archipack.roof_throttle_update" bl_label = "Update childs with a delay" - name = StringProperty() + name : StringProperty() def kill_handler(self, context, name): if name in throttle_handlers.keys(): @@ -5318,19 +5318,19 @@ class ARCHIPACK_OT_roof_throttle_update(Operator): if self.name in throttle_handlers.keys(): if throttle_handlers[self.name].modal(context, event): act = context.active_object - o = context.scene.objects.get(self.name) + o = context.scene.objects.get(self.name.strip()) # print("delay update of %s" % (self.name)) if o is not None: - selected = o.select - o.select = True - context.scene.objects.active = o + selected = o.select_get() + o.select_set(state=True) + context.view_layer.objects.active = o d = o.data.archipack_roof[0] d.update(context, force_update=True, update_parent=False) # skip_parent_update=self.skip_parent_update) - o.select = selected - context.scene.objects.active = act + o.select_set(state=selected) + context.view_layer.objects.active = act del throttle_handlers[self.name] return {'FINISHED'} else: diff --git a/archipack/archipack_slab.py b/archipack/archipack_slab.py index a6cc9bf6..38150094 100644 --- a/archipack/archipack_slab.py +++ b/archipack/archipack_slab.py @@ -220,7 +220,7 @@ class SlabGenerator(CutAblePolygon, CutAbleGenerator): # rotate seg seg.rotate(a) # rotate delta from rotation center to segment start - dp = rM * (seg.p0 - p0) + dp = rM @ (seg.p0 - p0) seg.translate(dp) def translate(self, idx_from, dp): @@ -282,7 +282,7 @@ class SlabGenerator(CutAblePolygon, CutAbleGenerator): use_dissolve_boundaries=False, verts=bm.verts, edges=bm.edges, - delimit=1) + delimit={'MATERIAL'}) bm.to_mesh(o.data) bm.free() @@ -319,7 +319,7 @@ materials_enum = ( class archipack_slab_material(PropertyGroup): - index = EnumProperty( + index : EnumProperty( items=materials_enum, default='4', update=update @@ -330,7 +330,7 @@ class archipack_slab_material(PropertyGroup): find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: props = archipack_slab.datablock(o) if props: @@ -349,12 +349,12 @@ class archipack_slab_child(PropertyGroup): """ Store child fences to be able to sync """ - child_name = StringProperty() - idx = IntProperty() + child_name : StringProperty() + idx : IntProperty() def get_child(self, context): d = None - child = context.scene.objects.get(self.child_name) + child = context.scene.objects.get(self.child_name.strip()) if child is not None and child.data is not None: if 'archipack_fence' in child.data: d = child.data.archipack_fence[0] @@ -439,7 +439,7 @@ class ArchipackSegment(): @TODO: share this base class with stair, wall, fence, slab """ - type = EnumProperty( + type : EnumProperty( items=( ('S_SEG', 'Straight', '', 0), ('C_SEG', 'Curved', '', 1), @@ -447,19 +447,19 @@ class ArchipackSegment(): default='S_SEG', update=update_type ) - length = FloatProperty( + length : FloatProperty( name="Length", min=0.01, default=2.0, update=update ) - radius = FloatProperty( + radius : FloatProperty( name="Radius", min=0.5, default=0.7, update=update ) - da = FloatProperty( + da : FloatProperty( name="Angle", min=-pi, max=pi, @@ -467,7 +467,7 @@ class ArchipackSegment(): subtype='ANGLE', unit='ROTATION', update=update ) - a0 = FloatProperty( + a0 : FloatProperty( name="Start angle", min=-2 * pi, max=2 * pi, @@ -475,21 +475,21 @@ class ArchipackSegment(): subtype='ANGLE', unit='ROTATION', update=update ) - offset = FloatProperty( + offset : FloatProperty( name="Offset", description="Add to current segment offset", default=0, unit='LENGTH', subtype='DISTANCE', update=update ) - linked_idx = IntProperty(default=-1) + linked_idx : IntProperty(default=-1) # @TODO: # flag to handle wall's x_offset # when set add wall offset value to segment offset # pay attention at allowing per wall segment offset - manipulators = CollectionProperty(type=archipack_manipulator) + manipulators : CollectionProperty(type=archipack_manipulator) def find_in_selection(self, context): raise NotImplementedError @@ -531,7 +531,7 @@ class archipack_slab_part(ArchipackSegment, PropertyGroup): find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: props = archipack_slab.datablock(o) if props: @@ -543,38 +543,38 @@ class archipack_slab_part(ArchipackSegment, PropertyGroup): class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): # boundary - n_parts = IntProperty( + n_parts : IntProperty( name="Parts", min=1, default=1, update=update_manipulators ) - parts = CollectionProperty(type=archipack_slab_part) - closed = BoolProperty( + parts : CollectionProperty(type=archipack_slab_part) + closed : BoolProperty( default=True, name="Close", options={'SKIP_SAVE'}, update=update_manipulators ) # UI layout related - parts_expand = BoolProperty( + parts_expand : BoolProperty( options={'SKIP_SAVE'}, default=False ) - x_offset = FloatProperty( + x_offset : FloatProperty( name="Offset", min=-1000, max=1000, default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - z = FloatProperty( + z : FloatProperty( name="Thickness", default=0.3, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - auto_synch = BoolProperty( + auto_synch : BoolProperty( name="Auto-Synch", description="Keep wall in synch when editing", default=True, @@ -584,13 +584,13 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): # Global slab offset # will only affect slab parts sharing a wall - childs = CollectionProperty(type=archipack_slab_child) + childs : CollectionProperty(type=archipack_slab_child) # Flag to prevent mesh update while making bulk changes over variables # use : # .auto_update = False # bulk changes # .auto_update = True - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update_manipulators @@ -696,17 +696,17 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): o = context.active_object bpy.ops.archipack.fence(auto_manipulate=False) c = context.active_object - c.select = True + c.select_set(state=True) c.data.archipack_fence[0].n_parts = 3 - c.select = False + c.select_set(state=False) # link to o c.location = Vector((0, 0, 0)) c.parent = o c.location = g.segs[where + 1].p0.to_3d() self.add_child(c.name, where + 1) # c.matrix_world.translation = g.segs[where].p1.to_3d() - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.relocate_childs(context, o, g) def add_part(self, context, length): @@ -736,7 +736,7 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): dmax = 0.2 for c in o.children: if (c.data and 'archipack_fence' in c.data): - pt = (itM * c.matrix_world.translation).to_2d() + pt = (itM @ c.matrix_world.translation).to_2d() for idx, seg in enumerate(g.segs): # may be optimized with a bound check res, d, t = seg.point_sur_segment(pt) @@ -874,14 +874,14 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): else: d.parts[i].length = max(0.01, seg.length) - wall.select = True - context.scene.objects.active = wall + wall.select_set(state=True) + context.view_layer.objects.active = wall d.auto_update = True - wall.select = False + wall.select_set(state=False) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o wall.matrix_world = o.matrix_world.copy() @@ -897,11 +897,11 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): ca = cos(a) if d is not None: - c.select = True + c.select_set(state=True) # auto_update need object to be active to # setup manipulators on the right object - context.scene.objects.active = c + context.view_layer.objects.active = c d.auto_update = False for i, part in enumerate(d.parts): @@ -915,11 +915,11 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): part.radius = self.parts[i + child.idx].radius d.parts[0].a0 = pi / 2 d.auto_update = True - c.select = False + c.select_set(state=False) - context.scene.objects.active = o + context.view_layer.objects.active = o # preTranslate - c.matrix_world = tM * Matrix([ + c.matrix_world = tM @ Matrix([ [sa, ca, 0, x], [-ca, sa, 0, y], [0, 0, 1, 0], @@ -1034,18 +1034,18 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): # since this can lower points count by a resolution factor # use normalized to handle non linear t if resolution == 0: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: v = (p1.co - p0.co).normalized() d1 = (p0.handle_right - p0.co).normalized() d2 = (p1.co - p1.handle_left).normalized() if d1 == v and d2 == v: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: - seg = interpolate_bezier(wM * p0.co, - wM * p0.handle_right, - wM * p1.handle_left, - wM * p1.co, + seg = interpolate_bezier(wM @ p0.co, + wM @ p0.handle_right, + wM @ p1.handle_left, + wM @ p1.co, resolution + 1) for i in range(resolution): pts.append(seg[i].to_3d()) @@ -1053,7 +1053,7 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): def from_spline(self, wM, resolution, spline): pts = [] if spline.type == 'POLY': - pts = [wM * p.co.to_3d() for p in spline.points] + pts = [wM @ p.co.to_3d() for p in spline.points] if spline.use_cyclic_u: pts.append(pts[0]) elif spline.type == 'BEZIER': @@ -1068,7 +1068,7 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): self.interpolate_bezier(pts, wM, p0, p1, resolution) pts.append(pts[0]) else: - pts.append(wM * points[-1].co) + pts.append(wM @ points[-1].co) self.from_points(pts, spline.use_cyclic_u) @@ -1147,8 +1147,8 @@ class archipack_slab(ArchipackObject, Manipulable, PropertyGroup): # relocate before cutting segs self.relocate_childs(context, o, g) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o g.cut(context, o) @@ -1244,8 +1244,8 @@ def update_operation(self, context): class archipack_slab_cutter_segment(ArchipackCutterPart, PropertyGroup): - manipulators = CollectionProperty(type=archipack_manipulator) - type = EnumProperty( + manipulators : CollectionProperty(type=archipack_manipulator) + type : EnumProperty( name="Type", items=( ('DEFAULT', 'Side', 'Side with rake', 0), @@ -1260,7 +1260,7 @@ class archipack_slab_cutter_segment(ArchipackCutterPart, PropertyGroup): ) def find_in_selection(self, context): - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: d = archipack_slab_cutter.datablock(o) if d: @@ -1278,7 +1278,7 @@ class archipack_slab_cutter_segment(ArchipackCutterPart, PropertyGroup): class archipack_slab_cutter(ArchipackCutter, ArchipackObject, Manipulable, PropertyGroup): - parts = CollectionProperty(type=archipack_slab_cutter_segment) + parts : CollectionProperty(type=archipack_slab_cutter_segment) def update_points(self, context, o, pts, update_parent=False): self.auto_update = False @@ -1291,11 +1291,11 @@ class archipack_slab_cutter(ArchipackCutter, ArchipackObject, Manipulable, Prope d = archipack_slab.datablock(o.parent) if d is not None: - o.parent.select = True - context.scene.objects.active = o.parent + o.parent.select_set(state=True) + context.view_layer.objects.active = o.parent d.update(context) - o.parent.select = False - context.scene.objects.active = o + o.parent.select_set(state=False) + context.view_layer.objects.active = o class ARCHIPACK_PT_slab(Panel): @@ -1305,7 +1305,7 @@ class ARCHIPACK_PT_slab(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' # bl_context = 'object' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -1317,7 +1317,7 @@ class ARCHIPACK_PT_slab(Panel): if prop is None: return layout = self.layout - layout.operator('archipack.slab_manipulate', icon='HAND') + layout.operator('archipack.slab_manipulate', icon='VIEW_PAN') box = layout.box() box.operator('archipack.slab_cutter').parent = o.name box = layout.box() @@ -1341,7 +1341,7 @@ class ARCHIPACK_PT_slab_cutter(Panel): bl_label = "Slab Cutter" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -1354,7 +1354,7 @@ class ARCHIPACK_PT_slab_cutter(Panel): layout = self.layout scene = context.scene box = layout.box() - box.operator('archipack.slab_cutter_manipulate', icon='HAND') + box.operator('archipack.slab_cutter_manipulate', icon='VIEW_PAN') box.prop(prop, 'operation', text="") box = layout.box() box.label(text="From curve") @@ -1380,7 +1380,7 @@ class ARCHIPACK_OT_slab_insert(Operator): bl_description = "Insert part" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - index = IntProperty(default=0) + index : IntProperty(default=0) def execute(self, context): if context.mode == "OBJECT": @@ -1400,7 +1400,7 @@ class ARCHIPACK_OT_slab_balcony(Operator): bl_description = "Insert part" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - index = IntProperty(default=0) + index : IntProperty(default=0) def execute(self, context): if context.mode == "OBJECT": @@ -1420,7 +1420,7 @@ class ARCHIPACK_OT_slab_remove(Operator): bl_description = "Remove part" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - index = IntProperty(default=0) + index : IntProperty(default=0) def execute(self, context): if context.mode == "OBJECT": @@ -1452,9 +1452,9 @@ class ARCHIPACK_OT_slab(ArchipackCreateTool, Operator): d = m.archipack_slab.add() # make manipulators selectable d.manipulable_selectable = True - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o self.load_preset(d) self.add_material(o) return o @@ -1467,8 +1467,8 @@ class ARCHIPACK_OT_slab(ArchipackCreateTool, Operator): bpy.ops.object.select_all(action="DESELECT") o = self.create(context) o.location = bpy.context.scene.cursor_location - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: @@ -1483,7 +1483,7 @@ class ARCHIPACK_OT_slab_from_curve(Operator): bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - auto_manipulate = BoolProperty(default=True) + auto_manipulate : BoolProperty(default=True) @classmethod def poll(self, context): @@ -1496,12 +1496,12 @@ class ARCHIPACK_OT_slab_from_curve(Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def create(self, context): curve = context.active_object bpy.ops.archipack.slab(auto_manipulate=self.auto_manipulate) - o = context.scene.objects.active + o = context.view_layer.objects.active d = archipack_slab.datablock(o) spline = curve.data.splines[0] d.from_spline(curve.matrix_world, 12, spline) @@ -1512,12 +1512,7 @@ class ARCHIPACK_OT_slab_from_curve(Operator): else: pt = Vector((0, 0, 0)) # pretranslate - o.matrix_world = curve.matrix_world * Matrix([ - [1, 0, 0, pt.x], - [0, 1, 0, pt.y], - [0, 0, 1, pt.z], - [0, 0, 0, 1] - ]) + o.matrix_world = curve.matrix_world @ Matrix.Translation(pt) return o # ----------------------------------------------------- @@ -1540,8 +1535,8 @@ class ARCHIPACK_OT_slab_from_wall(Operator): bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - auto_manipulate = BoolProperty(default=True) - ceiling = BoolProperty(default=False) + auto_manipulate : BoolProperty(default=True) + ceiling : BoolProperty(default=False) @classmethod def poll(self, context): @@ -1552,7 +1547,7 @@ class ARCHIPACK_OT_slab_from_wall(Operator): wall = context.active_object wd = wall.data.archipack_wall2[0] bpy.ops.archipack.slab(auto_manipulate=False) - o = context.scene.objects.active + o = context.view_layer.objects.active d = archipack_slab.datablock(o) d.auto_update = False d.closed = True @@ -1576,24 +1571,24 @@ class ARCHIPACK_OT_slab_from_wall(Operator): [0, 1, 0, 0], [0, 0, 1, wd.z + d.z], [0, 0, 0, 1], - ]) * wall.matrix_world + ]) @ wall.matrix_world else: o.matrix_world = wall.matrix_world.copy() bpy.ops.object.select_all(action='DESELECT') # parenting childs to wall reference point if wall.parent is None: x, y, z = wall.bound_box[0] - context.scene.cursor_location = wall.matrix_world * Vector((x, y, z)) + context.scene.cursor_location = wall.matrix_world @ Vector((x, y, z)) # fix issue #9 - context.scene.objects.active = wall + context.view_layer.objects.active = wall bpy.ops.archipack.reference_point() else: - wall.parent.select = True - context.scene.objects.active = wall.parent - wall.select = True - o.select = True + wall.parent.select_set(state=True) + context.view_layer.objects.active = wall.parent + wall.select_set(state=True) + o.select_set(state=True) bpy.ops.archipack.parent_to_reference() - wall.parent.select = False + wall.parent.select_set(state=False) return o @@ -1604,8 +1599,8 @@ class ARCHIPACK_OT_slab_from_wall(Operator): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o if self.auto_manipulate: bpy.ops.archipack.slab_manipulate('INVOKE_DEFAULT') return {'FINISHED'} @@ -1621,13 +1616,13 @@ class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator): bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - parent = StringProperty("") + parent : StringProperty("") def create(self, context): m = bpy.data.meshes.new("Slab Cutter") o = bpy.data.objects.new("Slab Cutter", m) d = m.archipack_slab_cutter.add() - parent = context.scene.objects.get(self.parent) + parent = context.scene.objects.get(self.parent.strip()) if parent is not None: o.parent = parent bbox = parent.bound_box @@ -1636,7 +1631,7 @@ class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator): x1, y1, z = bbox[6] x = 0.2 * (x1 - x0) y = 0.2 * (y1 - y0) - o.matrix_world = parent.matrix_world * Matrix([ + o.matrix_world = parent.matrix_world @ Matrix([ [1, 0, 0, -3 * x], [0, 1, 0, 0], [0, 0, 1, 0], @@ -1659,9 +1654,9 @@ class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator): o.location = context.scene.cursor_location # make manipulators selectable d.manipulable_selectable = True - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o # self.add_material(o) self.load_preset(d) update_operation(d, context) @@ -1674,8 +1669,8 @@ class ARCHIPACK_OT_slab_cutter(ArchipackCreateTool, Operator): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: diff --git a/archipack/archipack_snap.py b/archipack/archipack_snap.py index 92e09595..92d6ae24 100644 --- a/archipack/archipack_snap.py +++ b/archipack/archipack_snap.py @@ -43,7 +43,7 @@ sp.delta action_callback(context, event, state, sp) - state in {'SUCCESS', 'CANCEL'} + state in {'RUNNING', 'SUCCESS', 'CANCEL'} sp.takeloc sp.placeloc sp.delta @@ -64,6 +64,9 @@ import bpy from bpy.types import Operator from mathutils import Vector, Matrix +import logging + +logger = logging.getLogger("archipack") def dumb_callback(context, event, state, sp): @@ -81,8 +84,8 @@ class SnapStore: callback = None draw = None helper = None - takeloc = Vector((0, 0, 0)) - placeloc = Vector((0, 0, 0)) + takeloc = Vector() + placeloc = Vector() constraint_axis = (True, True, False) helper_matrix = Matrix() transform_orientation = 'GLOBAL' @@ -93,20 +96,20 @@ class SnapStore: act = None sel = [] use_snap = False - snap_element = None + snap_elements = None snap_target = None pivot_point = None trans_orientation = None def snap_point(takeloc=None, - draw=dumb_draw, - callback=dumb_callback, - takemat=None, - constraint_axis=(True, True, False), - transform_orientation='GLOBAL', - mode='OBJECT', - release_confirm=True): + draw=None, + callback=dumb_callback, + takemat=None, + constraint_axis=(True, True, False), + transform_orientation='GLOBAL', + mode='OBJECT', + release_confirm=True): """ Invoke op from outside world in a convenient importable function @@ -126,16 +129,19 @@ def snap_point(takeloc=None, SnapStore.callback = callback SnapStore.constraint_axis = constraint_axis SnapStore.release_confirm = release_confirm + if takemat is not None: SnapStore.helper_matrix = takemat - takeloc = takemat.translation + takeloc = takemat.translation.copy() transform_orientation = 'LOCAL' elif takeloc is not None: - SnapStore.helper_matrix = Matrix().Translation(takeloc) + SnapStore.helper_matrix = Matrix.Translation(takeloc) else: raise ValueError("ArchipackSnap: Either takeloc or takemat must be defined") + SnapStore.takeloc = takeloc - SnapStore.placeloc = takeloc + SnapStore.placeloc = takeloc.copy() + SnapStore.transform_orientation = transform_orientation # @NOTE: unused mode var to switch between OBJECT and EDIT mode @@ -160,53 +166,56 @@ class ArchipackSnapBase(): - takeloc - placeloc """ + def __init__(self): self._draw_handler = None def init(self, context, event): # Store context data - if SnapStore.instances_running < 1: - SnapStore.sel = [o for o in context.selected_objects] - SnapStore.act = context.active_object - bpy.ops.object.select_all(action="DESELECT") - SnapStore.use_snap = context.tool_settings.use_snap - SnapStore.snap_element = context.tool_settings.snap_element - SnapStore.snap_target = context.tool_settings.snap_target - SnapStore.pivot_point = context.space_data.pivot_point - SnapStore.trans_orientation = context.space_data.transform_orientation + # if SnapStore.instances_running < 1: + SnapStore.sel = context.selected_objects[:] + SnapStore.act = context.object + bpy.ops.object.select_all(action="DESELECT") + ts = context.tool_settings + SnapStore.use_snap = ts.use_snap + SnapStore.snap_elements = ts.snap_elements + SnapStore.snap_target = ts.snap_target + SnapStore.pivot_point = ts.transform_pivot_point + SnapStore.trans_orientation = context.scene.transform_orientation self.create_helper(context) - SnapStore.instances_running += 1 - # print("ArchipackSnapBase init: %s" % (SnapStore.instances_running)) - self.set_transform_orientation(context) - args = (self, context) - self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(SnapStore.draw, args, 'WINDOW', 'POST_PIXEL') + # Use a timer to broadcast a TIMER event while transform.translate is running + self._timer = context.window_manager.event_timer_add(0.1, window=context.window) + + if SnapStore.draw is not None: + args = (self, context) + self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(SnapStore.draw, args, 'WINDOW', 'POST_PIXEL') + + def remove_timer(self, context): + if self._timer is not None: + context.window_manager.event_timer_remove(self._timer) def exit(self, context): - bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, 'WINDOW') - # trick to allow launch 2nd instance - # via callback, preserve context as it - SnapStore.instances_running -= 1 - # print("ArchipackSnapBase exit: %s" % (SnapStore.instances_running)) - if SnapStore.instances_running > 0: - return - self.destroy_helper(context) + self.remove_timer(context) + + if self._draw_handler is not None: + bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, 'WINDOW') + # Restore original context - context.tool_settings.use_snap = SnapStore.use_snap - context.tool_settings.snap_element = SnapStore.snap_element - context.tool_settings.snap_target = SnapStore.snap_target - context.space_data.pivot_point = SnapStore.pivot_point - context.space_data.transform_orientation = SnapStore.trans_orientation + if hasattr(context, "tool_settings"): + ts = context.tool_settings + ts.use_snap = SnapStore.use_snap + ts.snap_elements = SnapStore.snap_elements + ts.snap_target = SnapStore.snap_target + ts.transform_pivot_point = SnapStore.pivot_point + context.scene.transform_orientation = SnapStore.trans_orientation for o in SnapStore.sel: - o.select = True + o.select_set(state=True) if SnapStore.act is not None: - context.scene.objects.active = SnapStore.act - - def set_transform_orientation(self, context): - """ - Allow local constraint orientation to be set - """ - context.space_data.transform_orientation = SnapStore.transform_orientation + SnapStore.act.select_set(state=True) + context.view_layer.objects.active = SnapStore.act + self.destroy_helper(context) + logger.debug("Snap.exit %s", context.object.name) def create_helper(self, context): """ @@ -217,23 +226,25 @@ class ArchipackSnapBase(): Do target helper be linked to scene in order to work ? """ - - helper_idx = bpy.data.objects.find('Archipack_snap_helper') - if helper_idx > -1: - helper = bpy.data.objects[helper_idx] - if context.scene.objects.find('Archipack_snap_helper') < 0: - context.scene.objects.link(helper) + helper = bpy.data.objects.get('Archipack_snap_helper') + if helper is not None: + print("helper found") + if context.scene.objects.get('Archipack_snap_helper') is None: + print("link helper") + # self.link_object_to_scene(context, helper) + context.scene.collection.objects.link(helper) else: - bpy.ops.object.add(type='MESH') - helper = context.active_object - helper.name = 'Archipack_snap_helper' + print("create helper") + m = bpy.data.meshes.new("Archipack_snap_helper") + m.vertices.add(count=1) + helper = bpy.data.objects.new("Archipack_snap_helper", m) + context.scene.collection.objects.link(helper) helper.use_fake_user = True helper.data.use_fake_user = True - # hide snap helper - # helper.hide = True + helper.matrix_world = SnapStore.helper_matrix - helper.select = True - context.scene.objects.active = helper + helper.select_set(state=True) + context.view_layer.objects.active = helper SnapStore.helper = helper def destroy_helper(self, context): @@ -242,7 +253,8 @@ class ArchipackSnapBase(): currently only support OBJECT mode """ if SnapStore.helper is not None: - context.scene.objects.unlink(SnapStore.helper) + # @TODO: Fix this + # self.unlink_object_from_scene(context, SnapStore.helper) SnapStore.helper = None @property @@ -266,35 +278,61 @@ class ArchipackSnapBase(): class ARCHIPACK_OT_snap(ArchipackSnapBase, Operator): bl_idname = 'archipack.snap' bl_label = 'Archipack snap' - bl_options = {'UNDO'} + bl_options = {'INTERNAL'} # , 'UNDO' def modal(self, context, event): - # print("Snap.modal event %s %s" % (event.type, event.value)) + + if SnapStore.helper is not None: + logger.debug("Snap.modal event %s %s location:%s", + event.type, + event.value, + SnapStore.helper.location) + context.area.tag_redraw() - # NOTE: this part only run after transform LEFTMOUSE RELEASE - # or with ESC and RIGHTMOUSE - if event.type not in {'ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE'}: - # print("Snap.modal skip unknown event %s %s" % (event.type, event.value)) - # self.report({'WARNING'}, "ARCHIPACK_OT_snap unknown event") - return{'PASS_THROUGH'} + + if event.type in ('TIMER', 'NOTHING'): + SnapStore.callback(context, event, 'RUNNING', self) + return {'PASS_THROUGH'} + + if event.type not in ('ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'): + return {'PASS_THROUGH'} + if event.type in ('ESC', 'RIGHTMOUSE'): SnapStore.callback(context, event, 'CANCEL', self) else: SnapStore.placeloc = SnapStore.helper.location - SnapStore.callback(context, event, 'SUCCESS', self) + # on tt modal exit with right click, the delta is 0 so exit + if self.delta.length == 0: + SnapStore.callback(context, event, 'CANCEL', self) + else: + SnapStore.callback(context, event, 'SUCCESS', self) + self.exit(context) # self.report({'INFO'}, "ARCHIPACK_OT_snap exit") - return{'FINISHED'} + return {'FINISHED'} def invoke(self, context, event): if context.area.type == 'VIEW_3D': - # print("Snap.invoke event %s %s" % (event.type, event.value)) + + if event.type in ('ESC', 'RIGHTMOUSE'): + return {'FINISHED'} + self.init(context, event) + + logger.debug("Snap.invoke event %s %s location:%s act:%s", + event.type, + event.value, + SnapStore.helper.location, context.object.name) + context.window_manager.modal_handler_add(self) + bpy.ops.transform.translate('INVOKE_DEFAULT', - constraint_axis=SnapStore.constraint_axis, - constraint_orientation=SnapStore.transform_orientation, - release_confirm=SnapStore.release_confirm) + constraint_axis=SnapStore.constraint_axis, + constraint_orientation=SnapStore.transform_orientation, + release_confirm=SnapStore.release_confirm) + + logger.debug("Snap.invoke transform.translate done") + return {'RUNNING_MODAL'} else: self.report({'WARNING'}, "View3D not found, cannot run operator") diff --git a/archipack/archipack_stair.py b/archipack/archipack_stair.py index 6e3d6212..c0d75317 100644 --- a/archipack/archipack_stair.py +++ b/archipack/archipack_stair.py @@ -234,16 +234,16 @@ class Stair(): def project_uv(self, rM, uvs, verts, indexes, up_axis='Z'): if up_axis == 'Z': - uvs.append([(rM * Vector(verts[i])).to_2d() for i in indexes]) + uvs.append([(rM @ Vector(verts[i])).to_2d() for i in indexes]) elif up_axis == 'Y': - uvs.append([(x, z) for x, y, z in [(rM * Vector(verts[i])) for i in indexes]]) + uvs.append([(x, z) for x, y, z in [(rM @ Vector(verts[i])) for i in indexes]]) else: - uvs.append([(y, z) for x, y, z in [(rM * Vector(verts[i])) for i in indexes]]) + uvs.append([(y, z) for x, y, z in [(rM @ Vector(verts[i])) for i in indexes]]) def get_proj_matrix(self, part, t, nose_y): # a matrix to project verts # into uv space for horizontal parts of this step - # so uv = (rM * vertex).to_2d() + # so uv = (rM @ vertex).to_2d() tl = t - nose_y / self.get_length("LEFT") tr = t - nose_y / self.get_length("RIGHT") t2, part, dz, shape = self.get_part(tl, "LEFT") @@ -264,7 +264,7 @@ class Stair(): # a matrix to project verts # into uv space for horizontal parts of this step - # so uv = (rM * vertex).to_2d() + # so uv = (rM @ vertex).to_2d() rM = self.get_proj_matrix(self, t, nose_y) if self.z_mode == 'LINEAR': @@ -1024,7 +1024,7 @@ class StairGenerator(): co.z += z1 if 'Slope' in g: co.z += co.y * slope - verts.append(tM * co) + verts.append(tM @ co) matids += self.user_defined_mat faces += [tuple([i + f for i in p.vertices]) for p in m.polygons] uvs += self.user_defined_uvs @@ -1106,7 +1106,7 @@ class StairGenerator(): if s < n_sections: v1 = subs[s + 1][0].v.normalized() dir = (v0 + v1).normalized() - scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1)))) + scale = 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1))))) for p in profile: x, y = n.p + scale * p.x * dir z = zl + p.y + altitude @@ -1463,7 +1463,7 @@ class StairGenerator(): if s < n_sections: v1 = cur_sect[s + 1][0].v.normalized() dir = (v0 + v1).normalized() - scale = 1 / cos(0.5 * acos(min(1, max(-1, v0 * v1)))) + scale = 1 / cos(0.5 * acos(min(1, max(-1, v0.dot(v1))))) for p in profile: x, y = n.p + scale * p.x * dir z = zl + p.y + z_offset @@ -1546,7 +1546,7 @@ materials_enum = ( class archipack_stair_material(PropertyGroup): - index = EnumProperty( + index : EnumProperty( items=materials_enum, default='4', update=update @@ -1557,7 +1557,7 @@ class archipack_stair_material(PropertyGroup): find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: props = archipack_stair.datablock(o) if props: @@ -1573,7 +1573,7 @@ class archipack_stair_material(PropertyGroup): class archipack_stair_part(PropertyGroup): - type = EnumProperty( + type : EnumProperty( items=( ('S_STAIR', 'Straight stair', '', 0), ('C_STAIR', 'Curved stair', '', 1), @@ -1585,21 +1585,21 @@ class archipack_stair_part(PropertyGroup): default='S_STAIR', update=update_manipulators ) - length = FloatProperty( + length : FloatProperty( name="Length", min=0.01, default=2.0, unit='LENGTH', subtype='DISTANCE', update=update ) - radius = FloatProperty( + radius : FloatProperty( name="Radius", min=0.01, default=0.7, unit='LENGTH', subtype='DISTANCE', update=update ) - da = FloatProperty( + da : FloatProperty( name="Angle", min=-pi, max=pi, @@ -1607,7 +1607,7 @@ class archipack_stair_part(PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - left_shape = EnumProperty( + left_shape : EnumProperty( items=( ('RECTANGLE', 'Straight', '', 0), ('CIRCLE', 'Curved ', '', 1) @@ -1615,7 +1615,7 @@ class archipack_stair_part(PropertyGroup): default='RECTANGLE', update=update ) - right_shape = EnumProperty( + right_shape : EnumProperty( items=( ('RECTANGLE', 'Straight', '', 0), ('CIRCLE', 'Curved ', '', 1) @@ -1623,14 +1623,14 @@ class archipack_stair_part(PropertyGroup): default='RECTANGLE', update=update ) - manipulators = CollectionProperty(type=archipack_manipulator) + manipulators : CollectionProperty(type=archipack_manipulator) def find_datablock_in_selection(self, context): """ find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: props = archipack_stair.datablock(o) if props: @@ -1670,69 +1670,69 @@ class archipack_stair_part(PropertyGroup): class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): - parts = CollectionProperty(type=archipack_stair_part) - n_parts = IntProperty( + parts : CollectionProperty(type=archipack_stair_part) + n_parts : IntProperty( name="Parts", min=1, max=32, default=1, update=update_manipulators ) - step_depth = FloatProperty( + step_depth : FloatProperty( name="Going", min=0.2, default=0.25, unit='LENGTH', subtype='DISTANCE', update=update ) - width = FloatProperty( + width : FloatProperty( name="Width", min=0.01, default=1.2, unit='LENGTH', subtype='DISTANCE', update=update ) - height = FloatProperty( + height : FloatProperty( name="Height", min=0.1, default=2.4, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - nose_y = FloatProperty( + nose_y : FloatProperty( name="Depth", min=0.0, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - x_offset = FloatProperty( + x_offset : FloatProperty( name="Offset", default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - nose_z = FloatProperty( + nose_z : FloatProperty( name="Height", min=0.001, default=0.03, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - bottom_z = FloatProperty( + bottom_z : FloatProperty( name="Thickness", min=0.001, default=0.03, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - radius = FloatProperty( + radius : FloatProperty( name="Radius", min=0.5, default=0.7, unit='LENGTH', subtype='DISTANCE', update=update ) - da = FloatProperty( + da : FloatProperty( name="Angle", min=-pi, max=pi, @@ -1740,7 +1740,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - total_angle = FloatProperty( + total_angle : FloatProperty( name="Angle", min=-50 * pi, max=50 * pi, @@ -1748,7 +1748,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - steps_type = EnumProperty( + steps_type : EnumProperty( name="Steps", items=( ('CLOSED', 'Closed', '', 0), @@ -1758,7 +1758,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): default='CLOSED', update=update ) - nose_type = EnumProperty( + nose_type : EnumProperty( name="Nosing", items=( ('STRAIGHT', 'Straight', '', 0), @@ -1767,7 +1767,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): default='STRAIGHT', update=update ) - left_shape = EnumProperty( + left_shape : EnumProperty( items=( ('RECTANGLE', 'Straight', '', 0), ('CIRCLE', 'Curved ', '', 1) @@ -1775,7 +1775,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): default='RECTANGLE', update=update ) - right_shape = EnumProperty( + right_shape : EnumProperty( items=( ('RECTANGLE', 'Straight', '', 0), ('CIRCLE', 'Curved ', '', 1) @@ -1783,7 +1783,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): default='RECTANGLE', update=update ) - z_mode = EnumProperty( + z_mode : EnumProperty( name="Interp z", items=( ('STANDARD', 'Standard', '', 0), @@ -1793,7 +1793,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): default='STANDARD', update=update ) - presets = EnumProperty( + presets : EnumProperty( items=( ('STAIR_I', 'I stair', '', 0), ('STAIR_L', 'L stair', '', 1), @@ -1803,131 +1803,131 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): ), default='STAIR_I', update=update_preset ) - left_post = BoolProperty( + left_post : BoolProperty( name='left', default=True, update=update ) - right_post = BoolProperty( + right_post : BoolProperty( name='right', default=True, update=update ) - post_spacing = FloatProperty( + post_spacing : FloatProperty( name="Spacing", min=0.1, default=1.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_x = FloatProperty( + post_x : FloatProperty( name="Width", min=0.001, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_y = FloatProperty( + post_y : FloatProperty( name="Length", min=0.001, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_z = FloatProperty( + post_z : FloatProperty( name="Height", min=0.001, default=1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_alt = FloatProperty( + post_alt : FloatProperty( name="Altitude", min=-100, default=0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_offset_x = FloatProperty( + post_offset_x : FloatProperty( name="Offset", min=-100.0, max=100, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - post_corners = BoolProperty( + post_corners : BoolProperty( name="Only on edges", update=update, default=False ) - user_defined_post_enable = BoolProperty( + user_defined_post_enable : BoolProperty( name="User", update=update, default=True ) - user_defined_post = StringProperty( + user_defined_post : StringProperty( name="User defined", update=update ) - idmat_post = EnumProperty( + idmat_post : EnumProperty( name="Post", items=materials_enum, default='4', update=update ) - left_subs = BoolProperty( + left_subs : BoolProperty( name='left', default=False, update=update ) - right_subs = BoolProperty( + right_subs : BoolProperty( name='right', default=False, update=update ) - subs_spacing = FloatProperty( + subs_spacing : FloatProperty( name="Spacing", min=0.05, default=0.10, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_x = FloatProperty( + subs_x : FloatProperty( name="Width", min=0.001, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_y = FloatProperty( + subs_y : FloatProperty( name="Length", min=0.001, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_z = FloatProperty( + subs_z : FloatProperty( name="Height", min=0.001, default=1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_alt = FloatProperty( + subs_alt : FloatProperty( name="Altitude", min=-100, default=0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_offset_x = FloatProperty( + subs_offset_x : FloatProperty( name="Offset", min=-100.0, max=100, default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - subs_bottom = EnumProperty( + subs_bottom : EnumProperty( name="Bottom", items=( ('STEP', 'Follow step', '', 0), @@ -1936,88 +1936,88 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): default='STEP', update=update ) - user_defined_subs_enable = BoolProperty( + user_defined_subs_enable : BoolProperty( name="User", update=update, default=True ) - user_defined_subs = StringProperty( + user_defined_subs : StringProperty( name="User defined", update=update ) - idmat_subs = EnumProperty( + idmat_subs : EnumProperty( name="Subs", items=materials_enum, default='4', update=update ) - left_panel = BoolProperty( + left_panel : BoolProperty( name='left', default=True, update=update ) - right_panel = BoolProperty( + right_panel : BoolProperty( name='right', default=True, update=update ) - panel_alt = FloatProperty( + panel_alt : FloatProperty( name="Altitude", default=0.25, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - panel_x = FloatProperty( + panel_x : FloatProperty( name="Width", min=0.001, default=0.01, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - panel_z = FloatProperty( + panel_z : FloatProperty( name="Height", min=0.001, default=0.6, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - panel_dist = FloatProperty( + panel_dist : FloatProperty( name="Spacing", min=0.001, default=0.05, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - panel_offset_x = FloatProperty( + panel_offset_x : FloatProperty( name="Offset", default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - idmat_panel = EnumProperty( + idmat_panel : EnumProperty( name="Panels", items=materials_enum, default='5', update=update ) - left_rail = BoolProperty( + left_rail : BoolProperty( name="left", update=update, default=False ) - right_rail = BoolProperty( + right_rail : BoolProperty( name="right", update=update, default=False ) - rail_n = IntProperty( + rail_n : IntProperty( name="#", default=1, min=0, max=31, update=update ) - rail_x = FloatVectorProperty( + rail_x : FloatVectorProperty( name="Width", default=[ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, @@ -2031,7 +2031,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', update=update ) - rail_z = FloatVectorProperty( + rail_z : FloatVectorProperty( name="Height", default=[ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, @@ -2045,7 +2045,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', update=update ) - rail_offset = FloatVectorProperty( + rail_offset : FloatVectorProperty( name="Offset", default=[ 0, 0, 0, 0, 0, 0, 0, 0, @@ -2058,7 +2058,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', update=update ) - rail_alt = FloatVectorProperty( + rail_alt : FloatVectorProperty( name="Altitude", default=[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -2071,47 +2071,47 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): unit='LENGTH', update=update ) - rail_mat = CollectionProperty(type=archipack_stair_material) + rail_mat : CollectionProperty(type=archipack_stair_material) - left_handrail = BoolProperty( + left_handrail : BoolProperty( name="left", update=update, default=True ) - right_handrail = BoolProperty( + right_handrail : BoolProperty( name="right", update=update, default=True ) - handrail_offset = FloatProperty( + handrail_offset : FloatProperty( name="Offset", default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_alt = FloatProperty( + handrail_alt : FloatProperty( name="Altitude", default=1.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_extend = FloatProperty( + handrail_extend : FloatProperty( name="Extend", default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_slice_left = BoolProperty( + handrail_slice_left : BoolProperty( name='Slice', default=True, update=update ) - handrail_slice_right = BoolProperty( + handrail_slice_right : BoolProperty( name='Slice', default=True, update=update ) - handrail_profil = EnumProperty( + handrail_profil : EnumProperty( name="Profil", items=( ('SQUARE', 'Square', '', 0), @@ -2121,21 +2121,21 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): default='SQUARE', update=update ) - handrail_x = FloatProperty( + handrail_x : FloatProperty( name="Width", min=0.001, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_y = FloatProperty( + handrail_y : FloatProperty( name="Height", min=0.001, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - handrail_radius = FloatProperty( + handrail_radius : FloatProperty( name="Radius", min=0.001, default=0.02, precision=2, step=1, @@ -2143,85 +2143,85 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): update=update ) - left_string = BoolProperty( + left_string : BoolProperty( name="left", update=update, default=False ) - right_string = BoolProperty( + right_string : BoolProperty( name="right", update=update, default=False ) - string_x = FloatProperty( + string_x : FloatProperty( name="Width", min=-100.0, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - string_z = FloatProperty( + string_z : FloatProperty( name="Height", default=0.3, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - string_offset = FloatProperty( + string_offset : FloatProperty( name="Offset", default=0.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - string_alt = FloatProperty( + string_alt : FloatProperty( name="Altitude", default=-0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - idmat_bottom = EnumProperty( + idmat_bottom : EnumProperty( name="Bottom", items=materials_enum, default='1', update=update ) - idmat_raise = EnumProperty( + idmat_raise : EnumProperty( name="Raise", items=materials_enum, default='1', update=update ) - idmat_step_front = EnumProperty( + idmat_step_front : EnumProperty( name="Step front", items=materials_enum, default='3', update=update ) - idmat_top = EnumProperty( + idmat_top : EnumProperty( name="Top", items=materials_enum, default='3', update=update ) - idmat_side = EnumProperty( + idmat_side : EnumProperty( name="Side", items=materials_enum, default='1', update=update ) - idmat_step_side = EnumProperty( + idmat_step_side : EnumProperty( name="Step Side", items=materials_enum, default='3', update=update ) - idmat_handrail = EnumProperty( + idmat_handrail : EnumProperty( name="Handrail", items=materials_enum, default='3', update=update ) - idmat_string = EnumProperty( + idmat_string : EnumProperty( name="String", items=materials_enum, default='3', @@ -2229,35 +2229,35 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): ) # UI layout related - parts_expand = BoolProperty( + parts_expand : BoolProperty( default=False ) - steps_expand = BoolProperty( + steps_expand : BoolProperty( default=False ) - rail_expand = BoolProperty( + rail_expand : BoolProperty( default=False ) - idmats_expand = BoolProperty( + idmats_expand : BoolProperty( default=False ) - handrail_expand = BoolProperty( + handrail_expand : BoolProperty( default=False ) - string_expand = BoolProperty( + string_expand : BoolProperty( default=False ) - post_expand = BoolProperty( + post_expand : BoolProperty( default=False ) - panel_expand = BoolProperty( + panel_expand : BoolProperty( default=False ) - subs_expand = BoolProperty( + subs_expand : BoolProperty( default=False ) - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update_manipulators @@ -2389,7 +2389,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): if self.user_defined_post_enable: # user defined posts - user_def_post = context.scene.objects.get(self.user_defined_post) + user_def_post = context.scene.objects.get(self.user_defined_post.strip()) if user_def_post is not None and user_def_post.type == 'MESH': g.setup_user_defined_post(user_def_post, self.post_x, self.post_y, self.post_z) @@ -2408,7 +2408,7 @@ class archipack_stair(ArchipackObject, Manipulable, PropertyGroup): # user defined subs if self.user_defined_subs_enable: - user_def_subs = context.scene.objects.get(self.user_defined_subs) + user_def_subs = context.scene.objects.get(self.user_defined_subs.strip()) if user_def_subs is not None and user_def_subs.type == 'MESH': g.setup_user_defined_post(user_def_subs, self.subs_x, self.subs_y, self.subs_z) @@ -2539,7 +2539,7 @@ class ARCHIPACK_PT_stair(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' # bl_context = 'object' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -2552,7 +2552,7 @@ class ARCHIPACK_PT_stair(Panel): scene = context.scene layout = self.layout row = layout.row(align=True) - row.operator('archipack.stair_manipulate', icon='HAND') + row.operator('archipack.stair_manipulate', icon='VIEW_PAN') row = layout.row(align=True) row.prop(prop, 'presets', text="") box = layout.box() @@ -2560,8 +2560,8 @@ class ARCHIPACK_PT_stair(Panel): row = box.row(align=True) # row.menu("ARCHIPACK_MT_stair_preset", text=bpy.types.ARCHIPACK_MT_stair_preset.bl_label) row.operator("archipack.stair_preset_menu", text=bpy.types.ARCHIPACK_OT_stair_preset_menu.bl_label) - row.operator("archipack.stair_preset", text="", icon='ZOOMIN') - row.operator("archipack.stair_preset", text="", icon='ZOOMOUT').remove_active = True + row.operator("archipack.stair_preset", text="", icon='ADD') + row.operator("archipack.stair_preset", text="", icon='REMOVE').remove_active = True box = layout.box() box.prop(prop, 'width') box.prop(prop, 'height') @@ -2753,9 +2753,9 @@ class ARCHIPACK_OT_stair(ArchipackCreateTool, Operator): m = bpy.data.meshes.new("Stair") o = bpy.data.objects.new("Stair", m) d = m.archipack_stair.add() - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o self.load_preset(d) self.add_material(o) m.auto_smooth_angle = 0.20944 @@ -2769,8 +2769,8 @@ class ARCHIPACK_OT_stair(ArchipackCreateTool, Operator): bpy.ops.object.select_all(action="DESELECT") o = self.create(context) o.location = context.scene.cursor_location - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: diff --git a/archipack/archipack_thumbs.py b/archipack/archipack_thumbs.py index 8f652ab2..4ae4d020 100644 --- a/archipack/archipack_thumbs.py +++ b/archipack/archipack_thumbs.py @@ -33,38 +33,30 @@ def log(s): print("[log]" + s) -def generateThumb(context, cls, preset): - log("### RENDER THUMB ############################") - log("Start generating: " + cls) +def create_lamp(context, loc): + bpy.ops.object.light_add( + type='POINT', + radius=1, + view_align=False, + location=loc) + lamp = context.active_object + lamp.data.use_nodes = True + tree = lamp.data.node_tree + return tree, tree.nodes, lamp.data - # engine settings - context.scene.render.engine = 'CYCLES' - render = context.scene.cycles - render.progressive = 'PATH' - render.samples = 24 - try: - render.use_square_samples = True - except: - pass - render.preview_samples = 24 - render.aa_samples = 24 - render.transparent_max_bounces = 8 - render.transparent_min_bounces = 8 - render.transmission_bounces = 8 - render.max_bounces = 8 - render.min_bounces = 6 - render.caustics_refractive = False - render.caustics_reflective = False - render.use_transparent_shadows = True - render.diffuse_bounces = 1 - render.glossy_bounces = 4 - bpy.ops.object.select_all(action="SELECT") - bpy.ops.object.delete() - # create object, loading preset - getattr(bpy.ops.archipack, cls)('INVOKE_DEFAULT', filepath=preset, auto_manipulate=False) - o = context.active_object - size = o.dimensions +def create_camera(context, loc, rot): + bpy.ops.object.camera_add( + view_align=True, + enter_editmode=False, + location=loc, + rotation=rot) + cam = context.active_object + context.scene.camera = cam + return cam + + +def get_center(o): x, y, z = o.bound_box[0] min_x = x min_y = y @@ -73,93 +65,131 @@ def generateThumb(context, cls, preset): max_x = x max_y = y max_z = z - center = Vector(( + return Vector(( min_x + 0.5 * (max_x - min_x), min_y + 0.5 * (max_y - min_y), min_z + 0.5 * (max_z - min_z))) - # oposite / tan (0.5 * fov) where fov is 49.134 deg + +def apply_simple_material(o, name, color): + m = bpy.data.materials.new(name) + m.use_nodes = True + m.node_tree.nodes[1].inputs[0].default_value = color + o.data.materials.append(m) + + +# /home/stephen/blender-28-git/build_linux_full/bin/blender --background --factory-startup -noaudio --python /home/stephen/blender-28-git/build_linux_full/bin/2.80/scripts/addons/archipack/archipack_thumbs.py -- addon:archipack matlib:/medias/stephen/DATA/lib/ cls:roof preset:/home/stephen/.config/blender/2.80/scripts/presets/archipack_roof/square.py + + +def generateThumb(context, cls, preset, engine): + log("### RENDER THUMB ############################") + + # Cleanup scene + for o in context.scene.objects: + o.select_set(state=True) + + bpy.ops.object.delete() + + log("Start generating: %s" % cls) + + # setup render + + context.scene.render.engine = engine + + if engine == 'CYCLES': + cycles = context.scene.cycles + cycles.progressive = 'PATH' + cycles.samples = 24 + try: + cycles.use_square_samples = True + except: + pass + cycles.preview_samples = 24 + cycles.aa_samples = 24 + cycles.transparent_max_bounces = 8 + cycles.transparent_min_bounces = 8 + cycles.transmission_bounces = 8 + cycles.max_bounces = 8 + cycles.min_bounces = 6 + cycles.caustics_refractive = False + cycles.caustics_reflective = False + cycles.use_transparent_shadows = True + cycles.diffuse_bounces = 1 + cycles.glossy_bounces = 4 + + elif engine == 'BLENDER_EEVEE': + eevee = context.scene.eevee + eevee.use_gtao = True + eevee.use_ssr = True + eevee.use_soft_shadows = True + eevee.taa_render_samples = 64 + else: + raise RuntimeError("Unsupported render engine %s" % engine) + + render = context.scene.render + + # engine settings + render.resolution_x = 150 + render.resolution_y = 100 + render.filepath = preset[:-3] + ".png" + + # create object, loading preset + getattr(bpy.ops.archipack, cls)('INVOKE_DEFAULT', filepath=preset, auto_manipulate=False) + o = context.active_object + size = o.dimensions + center = get_center(o) + + # opposite / tan (0.5 * fov) where fov is 49.134 deg dist = max(size) / 0.32 - loc = center + dist * Vector((0.5, -1, 0.5)).normalized() + loc = center + dist * Vector((-0.5, -1, 0.5)).normalized() log("Prepare camera") - bpy.ops.object.camera_add(view_align=True, - enter_editmode=False, - location=loc, - rotation=(1.150952, 0.0, 0.462509)) - cam = context.active_object + cam = create_camera(context, loc, (1.150952, 0.0, -0.462509)) cam.data.lens = 50 - cam.select = True - context.scene.camera = cam - bpy.ops.object.select_all(action="DESELECT") - o.select = True + for ob in context.scene.objects: + ob.select_set(state=False) + + o.select_set(state=True) + bpy.ops.view3d.camera_to_view_selected() + cam.data.lens = 45 log("Prepare scene") # add plane bpy.ops.mesh.primitive_plane_add( - radius=1000, + size=1000, view_align=False, enter_editmode=False, location=(0, 0, 0) - ) + ) + p = context.active_object - m = bpy.data.materials.new("Plane") - m.use_nodes = True - m.node_tree.nodes[1].inputs[0].default_value = (1, 1, 1, 1) - p.data.materials.append(m) + apply_simple_material(p, "Plane", (1, 1, 1, 1)) # add 3 lights - bpy.ops.object.lamp_add( - type='POINT', - radius=1, - view_align=False, - location=(3.69736, -7, 6.0)) - l = context.active_object - l.data.use_nodes = True - tree = l.data.node_tree - nodes = l.data.node_tree.nodes + tree, nodes, lamp = create_lamp(context, (3.69736, -7, 6.0)) + lamp.energy = 50 emit = nodes["Emission"] emit.inputs[1].default_value = 2000.0 - bpy.ops.object.lamp_add( - type='POINT', - radius=1, - view_align=False, - location=(9.414563179016113, 5.446230888366699, 5.903861999511719)) - l = context.active_object - l.data.use_nodes = True - tree = l.data.node_tree - nodes = l.data.node_tree.nodes + tree, nodes, lamp = create_lamp(context, (9.414563179016113, 5.446230888366699, 5.903861999511719)) emit = nodes["Emission"] falloff = nodes.new(type="ShaderNodeLightFalloff") falloff.inputs[0].default_value = 5 tree.links.new(falloff.outputs[2], emit.inputs[1]) - bpy.ops.object.lamp_add( - type='POINT', - radius=1, - view_align=False, - location=(-7.847615718841553, 1.03135085105896, 5.903861999511719)) - l = context.active_object - l.data.use_nodes = True - tree = l.data.node_tree - nodes = l.data.node_tree.nodes + tree, nodes, lamp = create_lamp(context, (-7.847615718841553, 1.03135085105896, 5.903861999511719)) emit = nodes["Emission"] falloff = nodes.new(type="ShaderNodeLightFalloff") falloff.inputs[0].default_value = 5 tree.links.new(falloff.outputs[2], emit.inputs[1]) # Set output filename. - render = context.scene.render - render.filepath = preset[:-3] + ".png" render.use_file_extension = True render.use_overwrite = True render.use_compositing = False render.use_sequencer = False - render.resolution_x = 150 - render.resolution_y = 100 render.resolution_percentage = 100 # render.image_settings.file_format = 'PNG' # render.image_settings.color_mode = 'RGBA' @@ -175,8 +205,9 @@ def generateThumb(context, cls, preset): if __name__ == "__main__": - preset = "" + preset = "" + engine = 'BLENDER_EEVEE' #'CYCLES' for arg in sys.argv: if arg.startswith("cls:"): cls = arg[4:] @@ -186,9 +217,14 @@ if __name__ == "__main__": matlib = arg[7:] if arg.startswith("addon:"): module = arg[6:] + if arg.startswith("engine:"): + engine = arg[7:] try: + # log("### ENABLE %s ADDON ############################" % module) bpy.ops.wm.addon_enable(module=module) + # log("### MATLIB PATH ############################") bpy.context.user_preferences.addons[module].preferences.matlib_path = matlib except: raise RuntimeError("module name not found") - generateThumb(bpy.context, cls, preset) + # log("### GENERATE ############################") + generateThumb(bpy.context, cls, preset, engine) diff --git a/archipack/archipack_truss.py b/archipack/archipack_truss.py index b8056daa..2931717a 100644 --- a/archipack/archipack_truss.py +++ b/archipack/archipack_truss.py @@ -43,7 +43,7 @@ def update(self, context): class archipack_truss(ArchipackObject, Manipulable, PropertyGroup): - truss_type = EnumProperty( + truss_type : EnumProperty( name="Type", items=( ('1', 'Prolyte E20', 'Prolyte E20', 0), @@ -57,40 +57,40 @@ class archipack_truss(ArchipackObject, Manipulable, PropertyGroup): default='2', update=update ) - z = FloatProperty( + z : FloatProperty( name="Height", default=2.0, min=0.01, unit='LENGTH', subtype='DISTANCE', update=update ) - segs = IntProperty( + segs : IntProperty( name="Segs", default=6, min=3, update=update ) - master_segs = IntProperty( + master_segs : IntProperty( name="Master Segs", default=1, min=1, update=update ) - master_count = IntProperty( + master_count : IntProperty( name="Masters", default=3, min=2, update=update ) - entre_axe = FloatProperty( + entre_axe : FloatProperty( name="Distance", default=0.239, min=0.001, unit='LENGTH', subtype='DISTANCE', update=update ) - master_radius = FloatProperty( + master_radius : FloatProperty( name="Radius", default=0.02415, min=0.0001, unit='LENGTH', subtype='DISTANCE', update=update ) - slaves_radius = FloatProperty( + slaves_radius : FloatProperty( name="Subs radius", default=0.01, min=0.0001, unit='LENGTH', subtype='DISTANCE', @@ -101,7 +101,7 @@ class archipack_truss(ArchipackObject, Manipulable, PropertyGroup): # .auto_update = False # bulk changes # .auto_update = True - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update @@ -127,10 +127,10 @@ class archipack_truss(ArchipackObject, Manipulable, PropertyGroup): if not add: for seg in range(segs): - verts.append(tM * tMb * tmpverts[seg]) + verts.append(tM @ tMb @ tmpverts[seg]) for seg in range(segs): - verts.append(tM * tMt * tmpverts[seg]) + verts.append(tM @ tMt @ tmpverts[seg]) for seg in range(segs - 1): f = cv + seg @@ -280,7 +280,7 @@ class ARCHIPACK_PT_truss(Panel): bl_label = "Truss" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -292,7 +292,7 @@ class ARCHIPACK_PT_truss(Panel): return layout = self.layout row = layout.row(align=True) - row.operator('archipack.truss_manipulate', icon='HAND') + row.operator('archipack.truss_manipulate', icon='VIEW_PAN') box = layout.box() box.prop(prop, 'truss_type') box.prop(prop, 'z') @@ -318,9 +318,9 @@ class ARCHIPACK_OT_truss(ArchipackCreateTool, Operator): d = m.archipack_truss.add() # make manipulators selectable # d.manipulable_selectable = True - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o self.load_preset(d) self.add_material(o) m.auto_smooth_angle = 1.15 @@ -334,8 +334,8 @@ class ARCHIPACK_OT_truss(ArchipackCreateTool, Operator): bpy.ops.object.select_all(action="DESELECT") o = self.create(context) o.location = bpy.context.scene.cursor_location - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: diff --git a/archipack/archipack_wall2.py b/archipack/archipack_wall2.py index 5f464a02..750cd313 100644 --- a/archipack/archipack_wall2.py +++ b/archipack/archipack_wall2.py @@ -45,11 +45,14 @@ from .archipack_manipulator import ( GlPolygon, GlPolyline, GlLine, GlText, FeedbackPanel ) -from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool +from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool from .archipack_2d import Line, Arc from .archipack_snap import snap_point from .archipack_keymaps import Keymaps +import logging +logger = logging.getLogger("archipack") + class Wall(): def __init__(self, wall_z, z, t, flip): @@ -323,7 +326,7 @@ class WallGenerator(): for i in range(idx_from + 1, len(self.segs)): seg = self.segs[i] seg.rotate(a) - dp = rM * (seg.p0 - p0) + dp = rM @ (seg.p0 - p0) seg.translate(dp) def translate(self, idx_from, dp): @@ -338,7 +341,7 @@ class WallGenerator(): """ move shape fromTM into toTM coordsys """ - dp = (toTM.inverted() * fromTM.translation).to_2d() + dp = (toTM.inverted() @ fromTM.translation).to_2d() da = toTM.row[1].to_2d().angle_signed(fromTM.row[1].to_2d()) ca = cos(da) sa = sin(da) @@ -347,7 +350,7 @@ class WallGenerator(): [sa, ca] ]) for s in self.segs: - tp = (rM * s.p0) - s.p0 + dp + tp = (rM @ s.p0) - s.p0 + dp s.rotate(da) s.translate(tp) @@ -419,7 +422,7 @@ def update_t_part(self, context): if o is not None: # w is parent wall - w = context.scene.objects.get(self.t_part) + w = context.scene.objects.get(self.t_part.strip()) wd = archipack_wall2.datablock(w) if wd is not None: @@ -446,27 +449,27 @@ def update_t_part(self, context): if o.parent is None: # create a reference point and make it active x, y, z = w.bound_box[0] - context.scene.cursor_location = w.matrix_world * Vector((x, y, z)) + context.scene.cursor_location = w.matrix_world @ Vector((x, y, z)) # fix issue #9 - context.scene.objects.active = o + context.view_layer.objects.active = o bpy.ops.archipack.reference_point() - o.select = True + o.select_set(state=True) else: - context.scene.objects.active = o.parent - w.select = True + context.view_layer.objects.active = o.parent + w.select_set(state=True) else: # w has parent if o.parent is not w.parent: link_to_parent = True - context.scene.objects.active = w.parent - o.select = True + context.view_layer.objects.active = w.parent + o.select_set(state=True) if o.parent is not None: # store o.parent to delete it to_delete = o.parent for c in o.parent.children: if c is not o: c.hide_select = False - c.select = True + c.select_set(state=True) parent = context.active_object @@ -487,11 +490,11 @@ def update_t_part(self, context): ]) # dir in absolute world coordsys - dir = orM * og.segs[0].straight(1, 0).v + dir = orM @ og.segs[0].straight(1, 0).v # pt in w coordsys pos = otM.translation - pt = (wtM.inverted() * pos).to_2d() + pt = (wtM.inverted() @ pos).to_2d() for wall_idx, wall in enumerate(wg.segs): res, dist, t = wall.point_sur_segment(pt) @@ -504,8 +507,8 @@ def update_t_part(self, context): # rotation here is wrong when w has not parent while o has parent if res and t > 0 and t < 1 and abs(dist) < dmax: - x = wrM * wall.straight(1, t).v - y = wrM * wall.normal(t).v.normalized() + x = wrM @ wall.straight(1, t).v + y = wrM @ wall.normal(t).v.normalized() self.parts[0].a0 = dir.angle_signed(x) o.matrix_world = Matrix([ [x.x, -y.x, 0, pos.x], @@ -531,8 +534,8 @@ def update_t_part(self, context): # delete unneeded reference point if to_delete is not None: bpy.ops.object.select_all(action="DESELECT") - to_delete.select = True - context.scene.objects.active = to_delete + to_delete.select_set(state=True) + context.view_layer.objects.active = to_delete if bpy.ops.object.delete.poll(): bpy.ops.object.delete(use_global=False) @@ -624,7 +627,7 @@ def update_type(self, context): class archipack_wall2_part(PropertyGroup): - type = EnumProperty( + type : EnumProperty( items=( ('S_WALL', 'Straight', '', 0), ('C_WALL', 'Curved', '', 1) @@ -632,21 +635,21 @@ class archipack_wall2_part(PropertyGroup): default='S_WALL', update=update_type ) - length = FloatProperty( + length : FloatProperty( name="Length", min=0.01, default=2.0, unit='LENGTH', subtype='DISTANCE', update=update ) - radius = FloatProperty( + radius : FloatProperty( name="Radius", min=0.5, default=0.7, unit='LENGTH', subtype='DISTANCE', update=update ) - a0 = FloatProperty( + a0 : FloatProperty( name="Start angle", min=-pi, max=pi, @@ -654,7 +657,7 @@ class archipack_wall2_part(PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - da = FloatProperty( + da : FloatProperty( name="Angle", min=-pi, max=pi, @@ -662,7 +665,7 @@ class archipack_wall2_part(PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - z = FloatVectorProperty( + z : FloatVectorProperty( name="Height", default=[ 0, 0, 0, 0, 0, 0, 0, 0, @@ -673,7 +676,7 @@ class archipack_wall2_part(PropertyGroup): size=31, update=update ) - t = FloatVectorProperty( + t : FloatVectorProperty( name="Position", min=0, max=1, @@ -686,24 +689,24 @@ class archipack_wall2_part(PropertyGroup): size=31, update=update ) - splits = IntProperty( + splits : IntProperty( name="Splits", default=1, min=1, max=31, get=get_splits, set=set_splits ) - n_splits = IntProperty( + n_splits : IntProperty( name="Splits", default=1, min=1, max=31, update=update ) - auto_update = BoolProperty(default=True) - manipulators = CollectionProperty(type=archipack_manipulator) + auto_update : BoolProperty(default=True) + manipulators : CollectionProperty(type=archipack_manipulator) # ui related - expand = BoolProperty(default=False) + expand : BoolProperty(default=False) def _set_t(self, splits): t = 1 / splits @@ -715,7 +718,7 @@ class archipack_wall2_part(PropertyGroup): find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: props = archipack_wall2.datablock(o) if props: @@ -766,15 +769,15 @@ class archipack_wall2_part(PropertyGroup): class archipack_wall2_child(PropertyGroup): # Size Loc # Delta Loc - manipulators = CollectionProperty(type=archipack_manipulator) - child_name = StringProperty() - wall_idx = IntProperty() - pos = FloatVectorProperty(subtype='XYZ') - flip = BoolProperty(default=False) + manipulators : CollectionProperty(type=archipack_manipulator) + child_name : StringProperty() + wall_idx : IntProperty() + pos : FloatVectorProperty(subtype='XYZ') + flip : BoolProperty(default=False) def get_child(self, context): d = None - child = context.scene.objects.get(self.child_name) + child = context.scene.objects.get(self.child_name.strip()) if child is not None and child.data is not None: cd = child.data if 'archipack_window' in cd: @@ -785,14 +788,14 @@ class archipack_wall2_child(PropertyGroup): class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): - parts = CollectionProperty(type=archipack_wall2_part) - n_parts = IntProperty( + parts : CollectionProperty(type=archipack_wall2_part) + n_parts : IntProperty( name="Parts", min=1, max=1024, default=1, update=update_manipulators ) - step_angle = FloatProperty( + step_angle : FloatProperty( description="Curved parts segmentation", name="Step angle", min=1 / 180 * pi, @@ -801,34 +804,34 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - width = FloatProperty( + width : FloatProperty( name="Width", min=0.01, default=0.2, unit='LENGTH', subtype='DISTANCE', update=update ) - z = FloatProperty( + z : FloatProperty( name='Height', min=0.1, default=2.7, precision=2, unit='LENGTH', subtype='DISTANCE', description='height', update=update, ) - x_offset = FloatProperty( + x_offset : FloatProperty( name="Offset", min=-1, max=1, default=-1, precision=2, step=1, update=update ) - radius = FloatProperty( + radius : FloatProperty( name="Radius", min=0.5, default=0.7, unit='LENGTH', subtype='DISTANCE', update=update ) - da = FloatProperty( + da : FloatProperty( name="Angle", min=-pi, max=pi, @@ -836,32 +839,32 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): subtype='ANGLE', unit='ROTATION', update=update ) - flip = BoolProperty( + flip : BoolProperty( name="Flip", default=False, update=update_childs ) - closed = BoolProperty( + closed : BoolProperty( default=False, name="Close", update=update_manipulators ) - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update_manipulators ) - realtime = BoolProperty( + realtime : BoolProperty( options={'SKIP_SAVE'}, default=True, name="Real Time", description="Relocate childs in realtime" ) # dumb manipulators to show sizes between childs - childs_manipulators = CollectionProperty(type=archipack_manipulator) + childs_manipulators : CollectionProperty(type=archipack_manipulator) # store to manipulate windows and doors - childs = CollectionProperty(type=archipack_wall2_child) - t_part = StringProperty( + childs : CollectionProperty(type=archipack_wall2_child) + t_part : StringProperty( name="Parent wall", description="This part will follow parent when set", default="", @@ -1014,18 +1017,18 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): def interpolate_bezier(self, pts, wM, p0, p1, resolution): if resolution == 0: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: v = (p1.co - p0.co).normalized() d1 = (p0.handle_right - p0.co).normalized() d2 = (p1.co - p1.handle_left).normalized() if d1 == v and d2 == v: - pts.append(wM * p0.co.to_3d()) + pts.append(wM @ p0.co.to_3d()) else: - seg = interpolate_bezier(wM * p0.co, - wM * p0.handle_right, - wM * p1.handle_left, - wM * p1.co, + seg = interpolate_bezier(wM @ p0.co, + wM @ p0.handle_right, + wM @ p1.handle_left, + wM @ p1.co, resolution + 1) for i in range(resolution): pts.append(seg[i].to_3d()) @@ -1041,7 +1044,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): def from_spline(self, wM, resolution, spline): pts = [] if spline.type == 'POLY': - pts = [wM * p.co.to_3d() for p in spline.points] + pts = [wM @ p.co.to_3d() for p in spline.points] if spline.use_cyclic_u: pts.append(pts[0]) elif spline.type == 'BEZIER': @@ -1056,7 +1059,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): self.interpolate_bezier(pts, wM, p0, p1, resolution) pts.append(pts[0]) else: - pts.append(wM * points[-1].co) + pts.append(wM @ points[-1].co) if self.is_cw(pts): pts = list(reversed(pts)) @@ -1134,7 +1137,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): if not self.closed: dp = pts[0] - pts[-1] # pre-translate as dp is in local coordsys - o.matrix_world = o.matrix_world * Matrix([ + o.matrix_world = o.matrix_world @ Matrix([ [1, 0, 0, dp.x], [0, 1, 0, dp.y], [0, 0, 1, 0], @@ -1326,7 +1329,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): # pt in w coordsys pos = ctM.translation - pt = (witM * pos).to_2d() + pt = (witM @ pos).to_2d() for wall_idx, wall in enumerate(g.segs): # may be optimized with a bound check @@ -1337,7 +1340,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): # p0 if res and t > 0 and t < 1 and abs(dist) < dmax: # dir in world coordsys - dir = wrM * wall.sized_normal(t, 1).v + dir = wrM @ wall.sized_normal(t, 1).v wall_with_childs[wall_idx] = 1 m = self.childs_manipulators.add() m.type_key = 'DUMB_SIZE' @@ -1345,14 +1348,14 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): if "archipack_window" in cd: flip = self.flip else: - dir_y = crM * Vector((0, -1)) + dir_y = crM @ Vector((0, -1)) # let door orient where user want flip = (dir_y - dir).length > 0.5 # store z in wall space relocate.append(( child.name, wall_idx, - (t * wall.length, dist, (witM * pos).z), + (t * wall.length, dist, (witM @ pos).z), flip, t)) break @@ -1392,19 +1395,19 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): if d is not None: # print("change flip:%s width:%s" % (d.flip != child.flip, d.y != self.width)) if d.y != self.width or d.flip != child.flip: - c.select = True + c.select_set(state=True) d.auto_update = False d.flip = child.flip d.y = self.width d.auto_update = True - c.select = False + c.select_set(state=False) x, y = n.p - (0.5 * w * n.v) else: x, y = n.p - (child.pos.y * n.v) - context.scene.objects.active = o + context.view_layer.objects.active = o # preTranslate - c.matrix_world = tM * Matrix([ + c.matrix_world = tM @ Matrix([ [rx, -ry, 0, x], [ry, rx, 0, y], [0, 0, 1, child.pos.z], @@ -1445,7 +1448,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): # child is either a window or a door wall_has_childs = True dt = 0.5 * d.x / wall.length - pt = (itM * c.matrix_world.translation).to_2d() + pt = (itM @ c.matrix_world.translation).to_2d() res, y, t = wall.point_sur_segment(pt) child.pos = (wall.length * t, y, child.pos.z) p1 = wall.lerp(t - dt) @@ -1503,7 +1506,7 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): if o.parent is not None: for c in o.parent.children: if (archipack_wall2.datablock(c) == self): - context.scene.objects.active = c + context.view_layer.objects.active = c found = True break if found: @@ -1522,12 +1525,12 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): if o.parent is None: return g = self.get_generator() - itM = o.matrix_world.inverted() * o.parent.matrix_world + itM = o.matrix_world.inverted() @ o.parent.matrix_world for child in self.childs: c, d = child.get_child(context) if d is not None: wall = g.segs[child.wall_idx] - pt = (itM * c.location).to_2d() + pt = (itM @ c.location).to_2d() res, d, t = wall.point_sur_segment(pt) child.pos = (t * wall.length, d, child.pos.z) # update childs manipulators @@ -1631,14 +1634,14 @@ class archipack_wall2(ArchipackObject, Manipulable, PropertyGroup): tM = o.matrix_world up = Vector((0, 0, 1)) for seg in g.segs: - p = tM * seg.p0.to_3d() + p = tM @ seg.p0.to_3d() p.z = 0.01 # prevent self intersect - o.hide = True + o.hide_viewport = True res, pos, normal, face_index, r, matrix_world = context.scene.ray_cast( p, up) - o.hide = False + o.hide_viewport = False # print("res:%s" % res) if res and r.data is not None and "archipack_roof" in r.data: return r, r.data.archipack_roof[0] @@ -1659,7 +1662,7 @@ class ARCHIPACK_OT_wall2_throttle_update(Operator): bl_idname = "archipack.wall2_throttle_update" bl_label = "Update childs with a delay" - name = StringProperty() + name : StringProperty() def modal(self, context, event): global update_timer_updating @@ -1667,12 +1670,12 @@ class ARCHIPACK_OT_wall2_throttle_update(Operator): # cant rely on TIMER event as another timer may run if time.time() - throttle_start > throttle_delay: update_timer_updating = True - o = context.scene.objects.get(self.name) + o = context.scene.objects.get(self.name.strip()) if o is not None: m = o.modifiers.get("AutoBoolean") if m is not None: - o.hide = False - # o.draw_type = 'TEXTURED' + o.hide_viewport = False + # o.display_type = 'TEXTURED' # m.show_viewport = True return self.cancel(context) @@ -1709,7 +1712,7 @@ class ARCHIPACK_PT_wall2(Panel): bl_label = "Wall" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' def draw(self, context): prop = archipack_wall2.datablock(context.object) @@ -1717,7 +1720,7 @@ class ARCHIPACK_PT_wall2(Panel): return layout = self.layout row = layout.row(align=True) - row.operator("archipack.wall2_manipulate", icon='HAND') + row.operator("archipack.wall2_manipulate", icon='VIEW_PAN') # row = layout.row(align=True) # row.prop(prop, 'realtime') box = layout.box() @@ -1765,11 +1768,11 @@ class ARCHIPACK_OT_wall2(ArchipackCreateTool, Operator): o = bpy.data.objects.new("Wall", m) d = m.archipack_wall2.add() d.manipulable_selectable = True - context.scene.objects.link(o) - o.select = True + self.link_object_to_scene(context, o) + o.select_set(state=True) # around 12 degree m.auto_smooth_angle = 0.20944 - context.scene.objects.active = o + context.view_layer.objects.active = o self.load_preset(d) self.add_material(o) return o @@ -1779,8 +1782,8 @@ class ARCHIPACK_OT_wall2(ArchipackCreateTool, Operator): bpy.ops.object.select_all(action="DESELECT") o = self.create(context) o.location = bpy.context.scene.cursor_location - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() return {'FINISHED'} else: @@ -1795,7 +1798,7 @@ class ARCHIPACK_OT_wall2_from_curve(Operator): bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - auto_manipulate = BoolProperty(default=True) + auto_manipulate : BoolProperty(default=True) @classmethod def poll(self, context): @@ -1805,7 +1808,7 @@ class ARCHIPACK_OT_wall2_from_curve(Operator): curve = context.active_object for spline in curve.data.splines: bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate) - o = context.scene.objects.active + o = context.view_layer.objects.active d = archipack_wall2.datablock(o) d.from_spline(curve.matrix_world, 12, spline) if spline.type == 'POLY': @@ -1815,7 +1818,7 @@ class ARCHIPACK_OT_wall2_from_curve(Operator): else: pt = Vector((0, 0, 0)) # pretranslate - o.matrix_world = curve.matrix_world * Matrix([ + o.matrix_world = curve.matrix_world @ Matrix([ [1, 0, 0, pt.x], [0, 1, 0, pt.y], [0, 0, 1, pt.z], @@ -1831,8 +1834,8 @@ class ARCHIPACK_OT_wall2_from_curve(Operator): bpy.ops.object.select_all(action="DESELECT") o = self.create(context) if o is not None: - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -1846,7 +1849,7 @@ class ARCHIPACK_OT_wall2_from_slab(Operator): bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - auto_manipulate = BoolProperty(default=True) + auto_manipulate : BoolProperty(default=True) @classmethod def poll(self, context): @@ -1857,7 +1860,7 @@ class ARCHIPACK_OT_wall2_from_slab(Operator): slab = context.active_object wd = slab.data.archipack_slab[0] bpy.ops.archipack.wall2(auto_manipulate=self.auto_manipulate) - o = context.scene.objects.active + o = context.view_layer.objects.active d = archipack_wall2.datablock(o) d.auto_update = False d.parts.clear() @@ -1873,8 +1876,8 @@ class ARCHIPACK_OT_wall2_from_slab(Operator): p.radius = part.radius p.da = part.da p.a0 = part.a0 - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o d.auto_update = True # pretranslate o.matrix_world = slab.matrix_world.copy() @@ -1883,17 +1886,17 @@ class ARCHIPACK_OT_wall2_from_slab(Operator): # parenting childs to wall reference point if o.parent is None: x, y, z = o.bound_box[0] - context.scene.cursor_location = o.matrix_world * Vector((x, y, z)) + context.scene.cursor_location = o.matrix_world @ Vector((x, y, z)) # fix issue #9 - context.scene.objects.active = o + context.view_layer.objects.active = o bpy.ops.archipack.reference_point() else: - o.parent.select = True - context.scene.objects.active = o.parent - o.select = True - slab.select = True + o.parent.select_set(state=True) + context.view_layer.objects.active = o.parent + o.select_set(state=True) + slab.select_set(state=True) bpy.ops.archipack.parent_to_reference() - o.parent.select = False + o.parent.select_set(state=False) return o # ----------------------------------------------------- @@ -1903,8 +1906,8 @@ class ARCHIPACK_OT_wall2_from_slab(Operator): if context.mode == "OBJECT": bpy.ops.object.select_all(action="DESELECT") o = self.create(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") @@ -1918,7 +1921,7 @@ class ARCHIPACK_OT_wall2_fit_roof(Operator): bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - inside = BoolProperty(default=False) + inside : BoolProperty(default=False) @classmethod def poll(self, context): @@ -1939,12 +1942,11 @@ class ARCHIPACK_OT_wall2_fit_roof(Operator): # ------------------------------------------------------------------ -class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): +class ARCHIPACK_OT_wall2_draw(ArchipackDrawTool, Operator): bl_idname = "archipack.wall2_draw" bl_label = "Draw a Wall" - bl_description = "Draw a Wall" + bl_description = "Create a wall by drawing its baseline in 3D view" bl_category = 'Archipack' - bl_options = {'REGISTER', 'UNDO'} o = None state = 'RUNNING' @@ -1993,7 +1995,7 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): self.line.draw(context) def sp_callback(self, context, event, state, sp): - # print("sp_callback event %s %s state:%s" % (event.type, event.value, state)) + logger.debug("ARCHIPACK_OT_wall2_draw.sp_callback event %s %s state:%s", event.type, event.value, state) if state == 'SUCCESS': @@ -2004,19 +2006,21 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): takeloc = sp.takeloc delta = sp.delta - old = context.active_object + old = context.object if self.o is None: bpy.ops.archipack.wall2(auto_manipulate=False) - o = context.active_object + o = context.object o.location = takeloc self.o = o d = archipack_wall2.datablock(o) + part = d.parts[0] part.length = delta.length else: o = self.o - o.select = True - context.scene.objects.active = o + # select and make active + o.select_set(state=True) + context.view_layer.objects.active = o d = archipack_wall2.datablock(o) # Check for end close to start and close when applicable dp = sp.placeloc - o.location @@ -2031,7 +2035,7 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): rM = o.matrix_world.inverted().to_3x3() g = d.get_generator() w = g.segs[-2] - dp = rM * delta + dp = rM @ delta da = atan2(dp.y, dp.x) - w.straight(1).angle a0 = part.a0 + da if a0 > pi: @@ -2039,8 +2043,10 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): if a0 < -pi: a0 += 2 * pi part.a0 = a0 + d.update(context) - context.scene.objects.active = old + old.select_set(state=True) + context.view_layer.objects.active = old self.flag_next = True context.area.tag_redraw() # print("feedback.on:%s" % self.feedback.on) @@ -2051,7 +2057,7 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): # print("sp_init event %s %s %s" % (event.type, event.value, state)) if state == 'SUCCESS': # point placed, check if a wall was under mouse - res, tM, wall, y = self.mouse_hover_wall(context, event) + res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event) if res: d = archipack_wall2.datablock(wall) if event.ctrl: @@ -2078,8 +2084,8 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): so reverse order here when needed """ d = archipack_wall2.datablock(self.o) - g = d.get_generator() - pts = [seg.p0.to_3d() for seg in g.segs] + g = d.get_generator(axis=False) + pts = [seg.p0 for seg in g.segs] if d.closed: pts.append(pts[0]) @@ -2094,46 +2100,70 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): def modal(self, context, event): context.area.tag_redraw() - # print("modal event %s %s" % (event.type, event.value)) - if event.type == 'NONE': + if event.type in {'NONE', 'TIMER', 'TIMER_REPORT', 'EVT_TWEAK_L', 'WINDOW_DEACTIVATE'}: return {'PASS_THROUGH'} - if self.state == 'STARTING': - takeloc = self.mouse_to_plane(context, event) - # wait for takeloc being visible when button is over horizon - rv3d = context.region_data - viewinv = rv3d.view_matrix.inverted() + if self.keymap.check(event, self.keymap.delete): + self.feedback.disable() + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + self.o = None + return {'FINISHED', 'PASS_THROUGH'} - if (takeloc * viewinv).z < 0 or not rv3d.is_perspective: - # print("STARTING") + if self.state == 'STARTING' and event.type not in {'ESC', 'RIGHTMOUSE'}: + # wait for takeloc being visible when button is over horizon + takeloc = self.mouse_to_plane(context, event) + if takeloc is not None: + logger.debug("ARCHIPACK_OT_wall2_draw.modal(STARTING) location:%s", takeloc) snap_point(takeloc=takeloc, - callback=self.sp_init, - constraint_axis=(True, True, False), - release_confirm=True) + callback=self.sp_init, + constraint_axis=(True, True, False), + release_confirm=True) return {'RUNNING_MODAL'} elif self.state == 'RUNNING': # print("RUNNING") + logger.debug("ARCHIPACK_OT_wall2_draw.modal(RUNNING) location:%s", self.takeloc) self.state = 'CREATE' snap_point(takeloc=self.takeloc, - draw=self.sp_draw, - takemat=self.takemat, - transform_orientation=context.space_data.transform_orientation, - callback=self.sp_callback, - constraint_axis=(True, True, False), - release_confirm=self.max_style_draw_tool) + draw=self.sp_draw, + takemat=self.takemat, + transform_orientation=context.scene.transform_orientation, + callback=self.sp_callback, + constraint_axis=(True, True, False), + release_confirm=self.max_style_draw_tool) return {'RUNNING_MODAL'} + elif self.state != 'CANCEL' and event.type in {'C', 'c'}: + + logger.debug("ARCHIPACK_OT_wall2_draw.modal(%s) C pressed", self.state) + self.feedback.disable() + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + + o = self.o + # select and make active + o.select_set(state=True) + context.view_layer.objects.active = o + + d = archipack_wall2.datablock(o) + d.closed = True + + if bpy.ops.archipack.manipulate.poll(): + bpy.ops.archipack.manipulate('INVOKE_DEFAULT') + + return {'FINISHED'} + elif self.state != 'CANCEL' and event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: # print('LEFTMOUSE %s' % (event.value)) self.feedback.instructions(context, "Draw a wall", "Click & Drag to add a segment", [ + ('ENTER', 'Add part'), + ('BACK_SPACE', 'Remove part'), ('CTRL', 'Snap'), + ('C', 'Close wall and exit'), ('MMBTN', 'Constraint to axis'), ('X Y', 'Constraint to axis'), - ('BACK_SPACE', 'Remove part'), ('RIGHTCLICK or ESC', 'exit') - ]) + ]) # press with max mode release with blender mode if self.max_style_draw_tool: @@ -2146,74 +2176,85 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): if self.flag_next: self.flag_next = False o = self.o - o.select = True - context.scene.objects.active = o + + # select and make active + o.select_set(state=True) + context.view_layer.objects.active = o + d = archipack_wall2.datablock(o) g = d.get_generator() p0 = g.segs[-2].p0 p1 = g.segs[-2].p1 dp = p1 - p0 - takemat = o.matrix_world * Matrix([ + takemat = o.matrix_world @ Matrix([ [dp.x, dp.y, 0, p1.x], [dp.y, -dp.x, 0, p1.y], [0, 0, 1, 0], [0, 0, 0, 1] ]) - takeloc = o.matrix_world * p1.to_3d() - o.select = False + takeloc = o.matrix_world @ p1.to_3d() + o.select_set(state=False) else: - takeloc = self.mouse_to_plane(context, event) takemat = None + takeloc = self.mouse_to_plane(context, event) - snap_point(takeloc=takeloc, - takemat=takemat, - draw=self.sp_draw, - callback=self.sp_callback, - constraint_axis=(True, True, False), - release_confirm=self.max_style_draw_tool) + if takeloc is not None: + logger.debug("ARCHIPACK_OT_wall2_draw.modal(CREATE) location:%s", takeloc) + + snap_point(takeloc=takeloc, + takemat=takemat, + draw=self.sp_draw, + callback=self.sp_callback, + constraint_axis=(True, True, False), + release_confirm=self.max_style_draw_tool) return {'RUNNING_MODAL'} if self.keymap.check(event, self.keymap.undo) or ( event.type in {'BACK_SPACE'} and event.value == 'RELEASE' - ): + ): if self.o is not None: o = self.o - o.select = True - context.scene.objects.active = o + + # select and make active + o.select_set(state=True) + context.view_layer.objects.active = o d = archipack_wall2.datablock(o) if d.n_parts > 1: d.n_parts -= 1 return {'RUNNING_MODAL'} if self.state == 'CANCEL' or (event.type in {'ESC', 'RIGHTMOUSE'} and - event.value == 'RELEASE'): + event.value == 'RELEASE'): self.feedback.disable() bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') - + logger.debug("ARCHIPACK_OT_wall2_draw.modal(CANCEL) %s", event.type) if self.o is None: - context.scene.objects.active = self.act for o in self.sel: - o.select = True + o.select_set(state=True) + # select and make active + self.act.select_set(state=True) + context.view_layer.objects.active = self.act + else: - self.o.select = True - context.scene.objects.active = self.o + o = self.o + o.select_set(state=True) + context.view_layer.objects.active = o # remove last segment with blender mode - d = archipack_wall2.datablock(self.o) + d = archipack_wall2.datablock(o) if not self.max_style_draw_tool: if not d.closed and d.n_parts > 1: d.n_parts -= 1 - - self.o.select = True - context.scene.objects.active = self.o + o.select_set(state=True) + context.view_layer.objects.active = o # make T child if self.parent is not None: d.t_part = self.parent - if bpy.ops.archipack.wall2_manipulate.poll(): - bpy.ops.archipack.wall2_manipulate('INVOKE_DEFAULT') + if bpy.ops.archipack.manipulate.poll(): + bpy.ops.archipack.manipulate('INVOKE_DEFAULT') return {'FINISHED'} @@ -2236,11 +2277,11 @@ class ARCHIPACK_OT_wall2_draw(ArchpackDrawTool, Operator): ('X Y', 'Constraint to axis'), ('SHIFT+CTRL+TAB', 'Switch snap mode'), ('RIGHTCLICK or ESC', 'exit without change') - ]) + ]) self.feedback.enable() args = (self, context) - self.sel = [o for o in context.selected_objects] + self.sel = context.selected_objects[:] self.act = context.active_object bpy.ops.object.select_all(action="DESELECT") @@ -2265,7 +2306,7 @@ class ARCHIPACK_OT_wall2_insert(Operator): bl_description = "Insert part" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - index = IntProperty(default=0) + index : IntProperty(default=0) def execute(self, context): if context.mode == "OBJECT": @@ -2286,7 +2327,7 @@ class ARCHIPACK_OT_wall2_remove(Operator): bl_description = "Remove part" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - index = IntProperty(default=0) + index : IntProperty(default=0) def execute(self, context): if context.mode == "OBJECT": @@ -2352,8 +2393,8 @@ class ARCHIPACK_OT_wall2_manipulate(Operator): d.setup_childs(o, g) d.update_childs(context, o, g) d.update(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return {'FINISHED'} diff --git a/archipack/archipack_window.py b/archipack/archipack_window.py index 54d057e7..f79005e4 100644 --- a/archipack/archipack_window.py +++ b/archipack/archipack_window.py @@ -41,7 +41,7 @@ from .archipack_handle import create_handle, window_handle_vertical_01, window_h from .archipack_manipulator import Manipulable from .archipack_preset import ArchipackPreset, PresetMenuOperator from .archipack_gl import FeedbackPanel -from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchpackDrawTool +from .archipack_object import ArchipackObject, ArchipackCreateTool, ArchipackDrawTool, ArchipackCollectionManager from .archipack_keymaps import Keymaps @@ -71,7 +71,7 @@ def get_cols(self): class archipack_window_panelrow(PropertyGroup): - width = FloatVectorProperty( + width : FloatVectorProperty( name="Width", min=0.5, max=100.0, @@ -84,7 +84,7 @@ class archipack_window_panelrow(PropertyGroup): size=31, update=update ) - fixed = BoolVectorProperty( + fixed : BoolVectorProperty( name="Fixed", default=[ False, False, False, False, False, False, False, False, @@ -95,7 +95,7 @@ class archipack_window_panelrow(PropertyGroup): size=32, update=update ) - cols = IntProperty( + cols : IntProperty( name="Panels", description="number of panels getter and setter, to avoid infinite recursion", min=1, @@ -103,7 +103,7 @@ class archipack_window_panelrow(PropertyGroup): default=2, get=get_cols, set=set_cols ) - n_cols = IntProperty( + n_cols : IntProperty( name="Panels", description="store number of panels, internal use only to avoid infinite recursion", min=1, @@ -111,14 +111,14 @@ class archipack_window_panelrow(PropertyGroup): default=2, update=update ) - height = FloatProperty( + height : FloatProperty( name="Height", min=0.1, default=1.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', update=update ) - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, name="auto_update", description="disable auto update to avoid infinite recursion", @@ -156,7 +156,7 @@ class archipack_window_panelrow(PropertyGroup): find witch selected object this instance belongs to provide support for "copy to selected" """ - selected = [o for o in context.selected_objects] + selected = context.selected_objects[:] for o in selected: props = archipack_window.datablock(o) if props: @@ -188,19 +188,19 @@ class archipack_window_panelrow(PropertyGroup): class archipack_window_panel(ArchipackObject, PropertyGroup): - center = FloatVectorProperty( + center : FloatVectorProperty( subtype='XYZ' ) - origin = FloatVectorProperty( + origin : FloatVectorProperty( subtype='XYZ' ) - size = FloatVectorProperty( + size : FloatVectorProperty( subtype='XYZ' ) - radius = FloatVectorProperty( + radius : FloatVectorProperty( subtype='XYZ' ) - angle_y = FloatProperty( + angle_y : FloatProperty( name='angle', unit='ROTATION', subtype='ANGLE', @@ -208,27 +208,27 @@ class archipack_window_panel(ArchipackObject, PropertyGroup): default=0, precision=2, description='angle' ) - frame_y = FloatProperty( + frame_y : FloatProperty( name='Depth', min=0, default=0.06, precision=2, unit='LENGTH', subtype='DISTANCE', description='frame depth' ) - frame_x = FloatProperty( + frame_x : FloatProperty( name='Width', min=0, default=0.06, precision=2, unit='LENGTH', subtype='DISTANCE', description='frame width' ) - curve_steps = IntProperty( + curve_steps : IntProperty( name="curve steps", min=1, max=128, default=1 ) - shape = EnumProperty( + shape : EnumProperty( name='Shape', items=( ('RECTANGLE', 'Rectangle', '', 0), @@ -239,19 +239,19 @@ class archipack_window_panel(ArchipackObject, PropertyGroup): ), default='RECTANGLE' ) - pivot = FloatProperty( + pivot : FloatProperty( name='pivot', min=-1, max=1, default=-1, precision=2, description='pivot' ) - side_material = IntProperty( + side_material : IntProperty( name="side material", min=0, max=2, default=0 ) - handle = EnumProperty( + handle : EnumProperty( name='Shape', items=( ('NONE', 'No handle', '', 0), @@ -260,24 +260,24 @@ class archipack_window_panel(ArchipackObject, PropertyGroup): ), default='NONE' ) - handle_model = IntProperty( + handle_model : IntProperty( name="handle model", default=1, min=1, max=2 ) - handle_altitude = FloatProperty( + handle_altitude : FloatProperty( name='handle altitude', min=0, default=0.2, precision=2, unit='LENGTH', subtype='DISTANCE', description='handle altitude' ) - fixed = BoolProperty( + fixed : BoolProperty( name="Fixed", default=False ) - enable_glass = BoolProperty( + enable_glass : BoolProperty( name="Enable glass", default=True ) @@ -404,7 +404,7 @@ class archipack_window_panel(ArchipackObject, PropertyGroup): def remove_handle(self, context, o): handle = self.find_handle(o) if handle is not None: - context.scene.objects.unlink(handle) + self.unlink_object_from_scene(handle) bpy.data.objects.remove(handle, do_unlink=True) def update(self, context): @@ -425,28 +425,28 @@ class archipack_window_panel(ArchipackObject, PropertyGroup): class archipack_window(ArchipackObject, Manipulable, PropertyGroup): - x = FloatProperty( + x : FloatProperty( name='Width', min=0.25, default=100.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='Width', update=update ) - y = FloatProperty( + y : FloatProperty( name='Depth', min=0.1, default=0.20, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='Depth', update=update, ) - z = FloatProperty( + z : FloatProperty( name='Height', min=0.1, default=1.2, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='height', update=update, ) - angle_y = FloatProperty( + angle_y : FloatProperty( name='Angle', unit='ROTATION', subtype='ANGLE', @@ -454,179 +454,179 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): default=0, precision=2, description='angle', update=update, ) - radius = FloatProperty( + radius : FloatProperty( name='Radius', min=0.1, default=2.5, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='radius', update=update, ) - elipsis_b = FloatProperty( + elipsis_b : FloatProperty( name='Ellipsis', min=0.1, default=0.5, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='ellipsis vertical size', update=update, ) - altitude = FloatProperty( + altitude : FloatProperty( name='Altitude', default=1.0, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='altitude', update=update, ) - offset = FloatProperty( + offset : FloatProperty( name='Offset', default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='offset', update=update, ) - frame_y = FloatProperty( + frame_y : FloatProperty( name='Depth', min=0, default=0.06, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='frame depth', update=update, ) - frame_x = FloatProperty( + frame_x : FloatProperty( name='Width', min=0, default=0.06, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='frame width', update=update, ) - out_frame = BoolProperty( + out_frame : BoolProperty( name="Out frame", default=False, update=update, ) - out_frame_y = FloatProperty( + out_frame_y : FloatProperty( name='Side depth', min=0.001, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='frame side depth', update=update, ) - out_frame_y2 = FloatProperty( + out_frame_y2 : FloatProperty( name='Front depth', min=0.001, default=0.02, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='frame front depth', update=update, ) - out_frame_x = FloatProperty( + out_frame_x : FloatProperty( name='Front Width', min=0.0, default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='frame width set to 0 disable front frame', update=update, ) - out_frame_offset = FloatProperty( + out_frame_offset : FloatProperty( name='Offset', min=0.0, default=0.0, precision=3, step=0.1, unit='LENGTH', subtype='DISTANCE', description='frame offset', update=update, ) - out_tablet_enable = BoolProperty( + out_tablet_enable : BoolProperty( name="Out tablet", default=True, update=update, ) - out_tablet_x = FloatProperty( + out_tablet_x : FloatProperty( name='Width', min=0.0, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='tablet width', update=update, ) - out_tablet_y = FloatProperty( + out_tablet_y : FloatProperty( name='Depth', min=0.001, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='tablet depth', update=update, ) - out_tablet_z = FloatProperty( + out_tablet_z : FloatProperty( name='Height', min=0.001, default=0.03, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='tablet height', update=update, ) - in_tablet_enable = BoolProperty( + in_tablet_enable : BoolProperty( name="In tablet", default=True, update=update, ) - in_tablet_x = FloatProperty( + in_tablet_x : FloatProperty( name='Width', min=0.0, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='tablet width', update=update, ) - in_tablet_y = FloatProperty( + in_tablet_y : FloatProperty( name='Depth', min=0.001, default=0.04, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='tablet depth', update=update, ) - in_tablet_z = FloatProperty( + in_tablet_z : FloatProperty( name='Height', min=0.001, default=0.03, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='tablet height', update=update, ) - blind_enable = BoolProperty( + blind_enable : BoolProperty( name="Blind", default=False, update=update, ) - blind_y = FloatProperty( + blind_y : FloatProperty( name='Depth', min=0.001, default=0.002, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='Store depth', update=update, ) - blind_z = FloatProperty( + blind_z : FloatProperty( name='Height', min=0.001, default=0.03, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='Store height', update=update, ) - blind_open = FloatProperty( + blind_open : FloatProperty( name='Open', min=0.0, max=100, default=80, precision=1, subtype='PERCENTAGE', description='Store open', update=update, ) - rows = CollectionProperty(type=archipack_window_panelrow) - n_rows = IntProperty( + rows : CollectionProperty(type=archipack_window_panelrow) + n_rows : IntProperty( name="Number of rows", min=1, max=32, default=1, update=update, ) - curve_steps = IntProperty( + curve_steps : IntProperty( name="Steps", min=6, max=128, default=16, update=update, ) - hole_outside_mat = IntProperty( + hole_outside_mat : IntProperty( name="Outside", min=0, max=128, default=0, update=update, ) - hole_inside_mat = IntProperty( + hole_inside_mat : IntProperty( name="Inside", min=0, max=128, default=1, update=update, ) - window_shape = EnumProperty( + window_shape : EnumProperty( name='Shape', items=( ('RECTANGLE', 'Rectangle', '', 0), @@ -637,7 +637,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): ), default='RECTANGLE', update=update, ) - window_type = EnumProperty( + window_type : EnumProperty( name='Type', items=( ('FLAT', 'Flat window', '', 0), @@ -645,59 +645,59 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): ), default='FLAT', update=update, ) - enable_glass = BoolProperty( + enable_glass : BoolProperty( name="Enable glass", default=True, update=update ) - warning = BoolProperty( + warning : BoolProperty( options={'SKIP_SAVE'}, name="Warning", default=False ) - handle_enable = BoolProperty( + handle_enable : BoolProperty( name='Handle', default=True, update=update_childs, ) - handle_altitude = FloatProperty( + handle_altitude : FloatProperty( name="Altitude", min=0, default=1.4, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='handle altitude', update=update_childs, ) - hole_margin = FloatProperty( + hole_margin : FloatProperty( name='Hole margin', min=0.0, default=0.1, precision=2, step=1, unit='LENGTH', subtype='DISTANCE', description='how much hole surround wall' ) - flip = BoolProperty( + flip : BoolProperty( default=False, update=update, description='flip outside and outside material of hole' ) # layout related - display_detail = BoolProperty( + display_detail : BoolProperty( options={'SKIP_SAVE'}, default=False ) - display_panels = BoolProperty( + display_panels : BoolProperty( options={'SKIP_SAVE'}, default=True ) - display_materials = BoolProperty( + display_materials : BoolProperty( options={'SKIP_SAVE'}, default=True ) - auto_update = BoolProperty( + auto_update : BoolProperty( options={'SKIP_SAVE'}, default=True, update=update ) - portal = BoolProperty( + portal : BoolProperty( default=False, name="Portal", description="Generate a portal", @@ -993,7 +993,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): def find_portal(self, o): for child in o.children: - if child.type == 'LAMP': + if child.type == 'LIGHT': return child return None @@ -1002,7 +1002,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): lamp = self.find_portal(o) if self.portal: if lamp is None: - bpy.ops.object.lamp_add(type='AREA') + bpy.ops.object.light_add(type='AREA') lamp = context.active_object lamp.name = "Portal" lamp.parent = o @@ -1019,15 +1019,15 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): [0, 1, 0, 0.5 * self.z + self.altitude], [0, 0, 0, 1] ]) - lamp.matrix_world = o.matrix_world * tM + lamp.matrix_world = o.matrix_world @ tM elif lamp is not None: d = lamp.data - context.scene.objects.unlink(lamp) + self.unlink_object_from_scene(lamp) bpy.data.objects.remove(lamp) - bpy.data.lamps.remove(d) + bpy.data.lights.remove(d) - context.scene.objects.active = o + context.view_layer.objects.active = o def setup_manipulators(self): if len(self.manipulators) == 4: @@ -1054,13 +1054,13 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): if archipack_window_panel.filter(child): to_remove -= 1 self.remove_handle(context, child) - context.scene.objects.unlink(child) + self.unlink_object_from_scene(child) bpy.data.objects.remove(child, do_unlink=True) def remove_handle(self, context, o): handle = self.find_handle(o) if handle is not None: - context.scene.objects.unlink(handle) + self.unlink_object_from_scene(handle) bpy.data.objects.remove(handle, do_unlink=True) def update_rows(self, context, o): @@ -1127,7 +1127,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): id = c_names.index(c.data.name) except: self.remove_handle(context, c) - context.scene.objects.unlink(c) + self.unlink_object_from_scene(c) bpy.data.objects.remove(c, do_unlink=True) # children ordering may not be the same, so get the right l_childs order @@ -1145,7 +1145,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): for i, child in enumerate(childs): if order[i] < 0: p = bpy.data.objects.new("Panel", child.data) - context.scene.objects.link(p) + self.link_object_to_scene(context, p) p.lock_location[0] = True p.lock_location[1] = True p.lock_location[2] = True @@ -1169,20 +1169,20 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): h = create_handle(context, p, handle.data) h.location = handle.location.copy() elif h is not None: - context.scene.objects.unlink(h) + self.unlink_object_from_scene(h) bpy.data.objects.remove(h, do_unlink=True) p.location = child.location.copy() # restore context - context.scene.objects.active = o + context.view_layer.objects.active = o def _synch_hole(self, context, linked, hole): l_hole = self.find_hole(linked) if l_hole is None: l_hole = bpy.data.objects.new("hole", hole.data) l_hole['archipack_hole'] = True - context.scene.objects.link(l_hole) + self.link_object_to_scene(context, l_hole) for mat in hole.data.materials: l_hole.data.materials.append(mat) l_hole.parent = linked @@ -1196,8 +1196,8 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): synch childs nodes of linked objects """ bpy.ops.object.select_all(action='DESELECT') - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o childs = self.get_childs_panels(context, o) hole = self.find_hole(o) bpy.ops.object.select_linked(type='OBDATA') @@ -1296,8 +1296,8 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): else: child = childs[child_n - 1] - child.select = True - context.scene.objects.active = child + child.select_set(state=True) + context.view_layer.objects.active = child props = archipack_window_panel.datablock(child) if props is not None: props.origin = Vector((origin[panel].x, offset.y, 0)) @@ -1442,7 +1442,7 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): if hole_obj is None: m = bpy.data.meshes.new("hole") hole_obj = bpy.data.objects.new("hole", m) - context.scene.objects.link(hole_obj) + self.link_object_to_scene(context, hole_obj) hole_obj['archipack_hole'] = True hole_obj.parent = o hole_obj.matrix_world = o.matrix_world.copy() @@ -1496,11 +1496,11 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): m = bpy.data.meshes.new("hole") o = bpy.data.objects.new("hole", m) o['archipack_robusthole'] = True - context.scene.objects.link(o) + self.link_object_to_scene(context, o) verts = hole.vertices(self.curve_steps, Vector((0, self.altitude, 0)), center, origin, size, radius, self.angle_y, 0, shape_z=shape_z, path_type=self.shape) - verts = [tM * Vector(v) for v in verts] + verts = [tM @ Vector(v) for v in verts] faces = hole.faces(self.curve_steps, path_type=self.shape) @@ -1511,8 +1511,8 @@ class archipack_window(ArchipackObject, Manipulable, PropertyGroup): bmed.buildmesh(context, o, verts, faces, matids=matids, uvs=uvs) # MaterialUtils.add_wall2_materials(o) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return o @@ -1522,13 +1522,13 @@ class ARCHIPACK_PT_window(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' # bl_context = 'object' - bl_category = 'ArchiPack' + bl_category = 'Archipack' # layout related - display_detail = BoolProperty( + display_detail : BoolProperty( default=False ) - display_panels = BoolProperty( + display_panels : BoolProperty( default=True ) @@ -1542,7 +1542,7 @@ class ARCHIPACK_PT_window(Panel): if prop is None: return layout = self.layout - layout.operator('archipack.window_manipulate', icon='HAND') + layout.operator('archipack.window_manipulate', icon='VIEW_PAN') row = layout.row(align=True) row.operator('archipack.window', text="Refresh", icon='FILE_REFRESH').mode = 'REFRESH' if o.data.users > 1: @@ -1552,8 +1552,8 @@ class ARCHIPACK_PT_window(Panel): # box.label(text="Styles") row = box.row(align=True) row.operator("archipack.window_preset_menu", text=bpy.types.ARCHIPACK_OT_window_preset_menu.bl_label) - row.operator("archipack.window_preset", text="", icon='ZOOMIN') - row.operator("archipack.window_preset", text="", icon='ZOOMOUT').remove_active = True + row.operator("archipack.window_preset", text="", icon='ADD') + row.operator("archipack.window_preset", text="", icon='REMOVE').remove_active = True box = layout.box() box.prop(prop, 'window_type') box.prop(prop, 'x') @@ -1587,7 +1587,7 @@ class ARCHIPACK_PT_window(Panel): box = layout.box() box.prop(prop, 'enable_glass') box = layout.box() - box.label("Frame") + box.label(text="Frame") box.prop(prop, 'frame_x') box.prop(prop, 'frame_y') if prop.window_shape != 'CIRCLE': @@ -1654,11 +1654,11 @@ class ARCHIPACK_PT_window(Panel): row.prop(prop, "display_materials", icon="TRIA_RIGHT", icon_only=True, text="Materials", emboss=False) if prop.display_materials: box = layout.box() - box.label("Hole") + box.label(text="Hole") box.prop(prop, 'hole_inside_mat') box.prop(prop, 'hole_outside_mat') - layout.prop(prop, 'portal', icon="LAMP_AREA") + layout.prop(prop, 'portal', icon="LIGHT_AREA") class ARCHIPACK_PT_window_panel(Panel): @@ -1666,7 +1666,7 @@ class ARCHIPACK_PT_window_panel(Panel): bl_label = "Window panel" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'ArchiPack' + bl_category = 'Archipack' @classmethod def poll(cls, context): @@ -1688,31 +1688,31 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): bl_description = "Window" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - x = FloatProperty( + x : FloatProperty( name='width', min=0.1, max=10000, default=2.0, precision=2, description='Width' ) - y = FloatProperty( + y : FloatProperty( name='depth', min=0.1, max=10000, default=0.20, precision=2, description='Depth' ) - z = FloatProperty( + z : FloatProperty( name='height', min=0.1, max=10000, default=1.2, precision=2, description='height' ) - altitude = FloatProperty( + altitude : FloatProperty( name='altitude', min=0.0, max=10000, default=1.0, precision=2, description='altitude' ) - mode = EnumProperty( + mode : EnumProperty( items=( ('CREATE', 'Create', '', 0), ('DELETE', 'Delete', '', 1), @@ -1721,12 +1721,12 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): ), default='CREATE' ) - # auto_manipulate = BoolProperty(default=True) + # auto_manipulate : BoolProperty(default=True) def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def create(self, context): m = bpy.data.meshes.new("Window") @@ -1736,14 +1736,14 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): d.y = self.y d.z = self.z d.altitude = self.altitude - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o self.add_material(o) self.load_preset(d) # select frame - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return o def delete(self, context): @@ -1751,22 +1751,22 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): if archipack_window.filter(o): bpy.ops.archipack.disable_manipulate() for child in o.children: - if child.type == 'LAMP': + if child.type == 'LIGHT': d = child.data - context.scene.objects.unlink(child) + self.unlink_object_from_scene(child) bpy.data.objects.remove(child) - bpy.data.lamps.remove(d) + bpy.data.lights.remove(d) elif 'archipack_hole' in child: - context.scene.objects.unlink(child) + self.unlink_object_from_scene(child) bpy.data.objects.remove(child, do_unlink=True) elif child.data is not None and 'archipack_window_panel' in child.data: for handle in child.children: if 'archipack_handle' in handle: - context.scene.objects.unlink(handle) + self.unlink_object_from_scene(handle) bpy.data.objects.remove(handle, do_unlink=True) - context.scene.objects.unlink(child) + self.unlink_object_from_scene(child) bpy.data.objects.remove(child, do_unlink=True) - context.scene.objects.unlink(o) + self.unlink_object_from_scene(o) bpy.data.objects.remove(o, do_unlink=True) def update(self, context): @@ -1780,22 +1780,22 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): archipack_window.datablock(linked).update(context) bpy.ops.object.select_all(action="DESELECT") - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o def unique(self, context): act = context.active_object - sel = [o for o in context.selected_objects] + sel = context.selected_objects[:] bpy.ops.object.select_all(action="DESELECT") for o in sel: if archipack_window.filter(o): - o.select = True + o.select_set(state=True) for child in o.children: if 'archipack_hole' in child or ( child.data is not None and 'archipack_window_panel' in child.data): child.hide_select = False - child.select = True + child.select_set(state=True) if len(context.selected_objects) > 0: bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True, material=False, texture=False, animation=False) @@ -1803,9 +1803,9 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): if 'archipack_hole' in child: child.hide_select = True bpy.ops.object.select_all(action="DESELECT") - context.scene.objects.active = act + context.view_layer.objects.active = act for o in sel: - o.select = True + o.select_set(state=True) # ----------------------------------------------------- # Execute @@ -1816,8 +1816,8 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): bpy.ops.object.select_all(action="DESELECT") o = self.create(context) o.location = bpy.context.scene.cursor_location - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.manipulate() elif self.mode == 'DELETE': self.delete(context) @@ -1831,14 +1831,14 @@ class ARCHIPACK_OT_window(ArchipackCreateTool, Operator): return {'CANCELLED'} -class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): +class ARCHIPACK_OT_window_draw(ArchipackDrawTool, Operator): bl_idname = "archipack.window_draw" bl_label = "Draw Windows" bl_description = "Draw Windows over walls" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - filepath = StringProperty(default="") + filepath : StringProperty(default="") feedback = None stack = [] object_name = "" @@ -1850,7 +1850,7 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def draw_callback(self, _self, context): self.feedback.draw(context) @@ -1862,8 +1862,8 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): if archipack_window.filter(o): - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o if event.shift: bpy.ops.archipack.window(mode="UNIQUE") @@ -1871,23 +1871,23 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): # instance subs new_w = o.copy() new_w.data = o.data - context.scene.objects.link(new_w) + self.link_object_to_scene(context, new_w) for child in o.children: if "archipack_hole" not in child: new_c = child.copy() new_c.data = child.data new_c.parent = new_w - context.scene.objects.link(new_c) + self.link_object_to_scene(context, new_c) # dup handle if any for c in child.children: new_h = c.copy() new_h.data = c.data new_h.parent = new_c - context.scene.objects.link(new_h) + self.link_object_to_scene(context, new_h) o = new_w - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o else: bpy.ops.archipack.window(auto_manipulate=False, filepath=self.filepath) @@ -1896,13 +1896,13 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): self.object_name = o.name bpy.ops.archipack.generate_hole('INVOKE_DEFAULT') - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o def modal(self, context, event): context.area.tag_redraw() - o = context.scene.objects.get(self.object_name) + o = context.scene.objects.get(self.object_name.strip()) if o is None: return {'FINISHED'} @@ -1914,14 +1914,14 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): # hide hole from raycast if hole is not None: - o.hide = True - hole.hide = True + o.hide_viewport = True + hole.hide_viewport = True - res, tM, wall, y = self.mouse_hover_wall(context, event) + res, tM, wall, width, y, z_offset = self.mouse_hover_wall(context, event) if hole is not None: - o.hide = False - hole.hide = False + o.hide_viewport = False + hole.hide_viewport = False if res and d is not None: o.matrix_world = tM @@ -1939,14 +1939,14 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER', 'SPACE'}: if wall is not None: - context.scene.objects.active = wall - wall.select = True + context.view_layer.objects.active = wall + wall.select_set(state=True) if bpy.ops.archipack.single_boolean.poll(): bpy.ops.archipack.single_boolean() - wall.select = False + wall.select_set(state=False) # o must be a window here if d is not None: - context.scene.objects.active = o + context.view_layer.objects.active = o self.stack.append(o) self.add_object(context, event) context.active_object.matrix_world = tM @@ -1960,9 +1960,9 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): ): if len(self.stack) > 0: last = self.stack.pop() - context.scene.objects.active = last + context.view_layer.objects.active = last bpy.ops.archipack.window(mode="DELETE") - context.scene.objects.active = o + context.view_layer.objects.active = o return {'RUNNING_MODAL'} if event.value == 'RELEASE': @@ -1986,11 +1986,11 @@ class ARCHIPACK_OT_window_draw(ArchpackDrawTool, Operator): # invoke with shift pressed will use current object as basis for linked copy if self.filepath == '' and archipack_window.filter(context.active_object): o = context.active_object - context.scene.objects.active = None + context.view_layer.objects.active = None bpy.ops.object.select_all(action="DESELECT") if o is not None: - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o self.add_object(context, event) self.feedback = FeedbackPanel() self.feedback.instructions(context, "Draw a window", "Click & Drag over a wall", [ @@ -2035,25 +2035,25 @@ class ARCHIPACK_OT_window_portals(Operator): # ------------------------------------------------------------------ -class ARCHIPACK_OT_window_panel(Operator): +class ARCHIPACK_OT_window_panel(ArchipackCollectionManager, Operator): bl_idname = "archipack.window_panel" bl_label = "Window panel" bl_description = "Window panel" bl_category = 'Archipack' bl_options = {'REGISTER', 'UNDO'} - center = FloatVectorProperty( + center : FloatVectorProperty( subtype='XYZ' ) - origin = FloatVectorProperty( + origin : FloatVectorProperty( subtype='XYZ' ) - size = FloatVectorProperty( + size : FloatVectorProperty( subtype='XYZ' ) - radius = FloatVectorProperty( + radius : FloatVectorProperty( subtype='XYZ' ) - angle_y = FloatProperty( + angle_y : FloatProperty( name='angle', unit='ROTATION', subtype='ANGLE', @@ -2061,25 +2061,25 @@ class ARCHIPACK_OT_window_panel(Operator): default=0, precision=2, description='angle' ) - frame_y = FloatProperty( + frame_y : FloatProperty( name='Depth', min=0, max=100, default=0.06, precision=2, description='frame depth' ) - frame_x = FloatProperty( + frame_x : FloatProperty( name='Width', min=0, max=100, default=0.06, precision=2, description='frame width' ) - curve_steps = IntProperty( + curve_steps : IntProperty( name="curve steps", min=1, max=128, default=16 ) - shape = EnumProperty( + shape : EnumProperty( name='Shape', items=( ('RECTANGLE', 'Rectangle', '', 0), @@ -2090,19 +2090,19 @@ class ARCHIPACK_OT_window_panel(Operator): ), default='RECTANGLE' ) - pivot = FloatProperty( + pivot : FloatProperty( name='pivot', min=-1, max=1, default=-1, precision=2, description='pivot' ) - side_material = IntProperty( + side_material : IntProperty( name="side material", min=0, max=2, default=0 ) - handle = EnumProperty( + handle : EnumProperty( name='Handle', items=( ('NONE', 'No handle', '', 0), @@ -2111,27 +2111,27 @@ class ARCHIPACK_OT_window_panel(Operator): ), default='NONE' ) - handle_model = IntProperty( + handle_model : IntProperty( name="handle model", default=1, min=1, max=2 ) - handle_altitude = FloatProperty( + handle_altitude : FloatProperty( name='handle altitude', min=0, max=1000, default=0.2, precision=2, description='handle altitude' ) - fixed = BoolProperty( + fixed : BoolProperty( name="Fixed", default=False ) - material = StringProperty( + material : StringProperty( name="material", default="" ) - enable_glass = BoolProperty( + enable_glass : BoolProperty( name="Enable glass", default=True ) @@ -2139,7 +2139,7 @@ class ARCHIPACK_OT_window_panel(Operator): def draw(self, context): layout = self.layout row = layout.row() - row.label("Use Properties panel (N) to define parms", icon='INFO') + row.label(text="Use Properties panel (N) to define parms", icon='INFO') def create(self, context): m = bpy.data.meshes.new("Window Panel") @@ -2161,9 +2161,9 @@ class ARCHIPACK_OT_window_panel(Operator): d.handle_model = self.handle_model d.handle_altitude = self.handle_altitude d.enable_glass = self.enable_glass - context.scene.objects.link(o) - o.select = True - context.scene.objects.active = o + self.link_object_to_scene(context, o) + o.select_set(state=True) + context.view_layer.objects.active = o m = o.archipack_material.add() m.category = "window" m.material = self.material @@ -2180,8 +2180,8 @@ class ARCHIPACK_OT_window_panel(Operator): def execute(self, context): if context.mode == "OBJECT": o = self.create(context) - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o return {'FINISHED'} else: self.report({'WARNING'}, "Archipack: Option only valid in Object mode") diff --git a/archipack/bmesh_utils.py b/archipack/bmesh_utils.py index 3f402d1d..54805f7b 100644 --- a/archipack/bmesh_utils.py +++ b/archipack/bmesh_utils.py @@ -34,8 +34,8 @@ class BmeshEdit(): """ private, start bmesh editing of active object """ - o.select = True - context.scene.objects.active = o + o.select_set(state=True) + context.view_layer.objects.active = o bpy.ops.object.mode_set(mode='EDIT') bm = bmesh.from_edit_mesh(o.data) bm.verts.ensure_lookup_table() @@ -212,7 +212,7 @@ class BmeshEdit(): @staticmethod def bevel(context, o, offset, - offset_type=0, + offset_type='OFFSET', segments=1, profile=0.5, vertex_only=False, diff --git a/blender_id/CHANGELOG.md b/blender_id/CHANGELOG.md index cf62a6e1..efda8c80 100644 --- a/blender_id/CHANGELOG.md +++ b/blender_id/CHANGELOG.md @@ -1,5 +1,18 @@ # Blender ID Add-on Changelog +# Version 2.0 (in development) + +- Require Blender 2.80+. +- API change: `blender_id.get_subclient_user_id()` now returns `''` instead of `None` when the user + is not logged in. +- Log which Blender ID instance is communicated with. + + +# Version 1.5 (released 2018-07-03) + +- Support Blender 2.80. + + ## Version 1.4.1 (released 2017-12-15) - Improved error reporting when validating a token fails due to diff --git a/blender_id/README.md b/blender_id/README.md index 936e6e3e..8f73fbc7 100644 --- a/blender_id/README.md +++ b/blender_id/README.md @@ -13,6 +13,9 @@ Blender ID add-on version 1.2.0 removed some workarounds necessary for Blender 2.77a. As such, versions 1.1.x are the last versions compatible with Blender 2.77a, and 1.2.0 and newer require at least Blender 2.78. +Blender ID add-on version 2.0 is the first to support and require Blender 2.80+. + + Building & Bundling ------------------- diff --git a/blender_id/__init__.py b/blender_id/__init__.py index 73371945..e5d94715 100644 --- a/blender_id/__init__.py +++ b/blender_id/__init__.py @@ -14,15 +14,17 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # +# Copyright (C) 2014-2018 Blender Foundation +# # ##### END GPL LICENSE BLOCK ##### # <pep8 compliant> bl_info = { 'name': 'Blender ID authentication', - 'author': 'Francesco Siddi, Inês Almeida and Sybren A. Stüvel', - 'version': (1, 4, 1), - 'blender': (2, 77, 0), + 'author': 'Sybren A. Stüvel, Francesco Siddi, and Inês Almeida', + 'version': (1, 9, 9), + 'blender': (2, 80, 0), 'location': 'Add-on preferences', 'description': 'Stores your Blender ID credentials for usage with other add-ons', @@ -117,11 +119,11 @@ def get_subclient_user_id(subclient_id: str) -> str: Requires that the user has been authenticated at the subclient using a call to create_subclient_token(...) - :returns: the subclient-local user ID, or None if not logged in. + :returns: the subclient-local user ID, or the empty string if not logged in. """ if not BlenderIdProfile.user_id: - return None + return '' return BlenderIdProfile.subclients[subclient_id]['subclient_user_id'] @@ -159,9 +161,9 @@ def token_expires() -> typing.Optional[datetime.datetime]: # Try parsing as different formats. A new Blender ID is coming, # which may change the format in which timestamps are sent. formats = [ - '%Y-%m-%dT%H:%M:%SZ', # ISO 8601 with Z-suffix - '%Y-%m-%dT%H:%M:%S.%fZ', # ISO 8601 with fractional seconds and Z-suffix - '%a, %d %b %Y %H:%M:%S GMT', # RFC 1123, used by old Blender ID + '%Y-%m-%dT%H:%M:%SZ', # ISO 8601 with Z-suffix + '%Y-%m-%dT%H:%M:%S.%fZ', # ISO 8601 with fractional seconds and Z-suffix + '%a, %d %b %Y %H:%M:%S GMT', # RFC 1123, used by old Blender ID ] for fmt in formats: try: @@ -177,22 +179,22 @@ def token_expires() -> typing.Optional[datetime.datetime]: class BlenderIdPreferences(AddonPreferences): bl_idname = __name__ - error_message = StringProperty( + error_message: StringProperty( name='Error Message', default='', options={'HIDDEN', 'SKIP_SAVE'} ) - ok_message = StringProperty( + ok_message: StringProperty( name='Message', default='', options={'HIDDEN', 'SKIP_SAVE'} ) - blender_id_username = StringProperty( + blender_id_username: StringProperty( name='E-mail address', default='', options={'HIDDEN', 'SKIP_SAVE'} ) - blender_id_password = StringProperty( + blender_id_password: StringProperty( name='Password', default='', options={'HIDDEN', 'SKIP_SAVE'}, @@ -209,10 +211,10 @@ class BlenderIdPreferences(AddonPreferences): if self.error_message: sub = layout.row() sub.alert = True # labels don't display in red :( - sub.label(self.error_message, icon='ERROR') + sub.label(text=self.error_message, icon='ERROR') if self.ok_message: sub = layout.row() - sub.label(self.ok_message, icon='FILE_TICK') + sub.label(text=self.ok_message, icon='FILE_TICK') active_profile = get_active_profile() if active_profile: @@ -238,15 +240,17 @@ class BlenderIdPreferences(AddonPreferences): exp_str = 'within seconds' if time_left.days < 14: - layout.label('You are logged in as %s.' % active_profile.username, + layout.label(text='You are logged in as %s.' % active_profile.username, icon='WORLD_DATA') layout.label(text='Your token will expire %s. Please log out and log in again ' - 'to refresh it.' % exp_str, icon='PREVIEW_RANGE') + 'to refresh it.' % exp_str, icon='PREVIEW_RANGE') else: - layout.label('You are logged in as %s. Your authentication token expires %s.' - % (active_profile.username, exp_str), icon='WORLD_DATA') + layout.label( + text='You are logged in as %s. Your authentication token expires %s.' + % (active_profile.username, exp_str), + icon='WORLD_DATA') - row = layout.row().split(0.8) + row = layout.row().split(factor=0.8) row.operator('blender_id.logout') row.operator('blender_id.validate') else: @@ -343,14 +347,20 @@ def register(): profiles.register() BlenderIdProfile.read_json() - bpy.utils.register_module(__name__) + bpy.utils.register_class(BlenderIdLogin) + bpy.utils.register_class(BlenderIdLogout) + bpy.utils.register_class(BlenderIdPreferences) + bpy.utils.register_class(BlenderIdValidate) preferences = bpy.context.user_preferences.addons[__name__].preferences preferences.reset_messages() def unregister(): - bpy.utils.unregister_module(__name__) + bpy.utils.unregister_class(BlenderIdLogin) + bpy.utils.unregister_class(BlenderIdLogout) + bpy.utils.unregister_class(BlenderIdPreferences) + bpy.utils.unregister_class(BlenderIdValidate) if __name__ == '__main__': diff --git a/blender_id/communication.py b/blender_id/communication.py index 5572b637..9960c338 100644 --- a/blender_id/communication.py +++ b/blender_id/communication.py @@ -55,7 +55,12 @@ def blender_id_endpoint(endpoint_path=None): import os import urllib.parse - base_url = os.environ.get('BLENDER_ID_ENDPOINT', 'https://www.blender.org/id/') + base_url = os.environ.get('BLENDER_ID_ENDPOINT') + if base_url: + log.warning('Using overridden Blender ID url %s', base_url) + else: + base_url = 'https://www.blender.org/id/' + log.info('Using standard Blender ID url %s', base_url) # urljoin() is None-safe for the 2nd parameter. return urllib.parse.urljoin(base_url, endpoint_path) diff --git a/bone_selection_sets.py b/bone_selection_sets.py index 686eb808..e91d5557 100644 --- a/bone_selection_sets.py +++ b/bone_selection_sets.py @@ -20,7 +20,7 @@ bl_info = { "name": "Bone Selection Sets", "author": "Inês Almeida, Sybren A. Stüvel, Antony Riakiotakis, Dan Eicher", "version": (2, 1, 1), - "blender": (2, 75, 0), + "blender": (2, 80, 0), "location": "Properties > Object Data (Armature) > Selection Sets", "description": "List of Bone sets for easy selection while animating", "warning": "", @@ -41,6 +41,7 @@ from bpy.props import ( StringProperty, IntProperty, EnumProperty, + BoolProperty, CollectionProperty, ) @@ -48,14 +49,15 @@ from bpy.props import ( # Data Structure ############################################################## # Note: bones are stored by name, this means that if the bone is renamed, -# there can be problems. However, bone renaming is unlikely during animation +# there can be problems. However, bone renaming is unlikely during animation. class SelectionEntry(PropertyGroup): - name = StringProperty(name="Bone Name") + name: StringProperty(name="Bone Name") class SelectionSet(PropertyGroup): - name = StringProperty(name="Set Name") - bone_ids = CollectionProperty(type=SelectionEntry) + name: StringProperty(name="Set Name") + bone_ids: CollectionProperty(type=SelectionEntry) + is_selected: BoolProperty(name="Is Selected") # UI Panel w/ UIList ########################################################## @@ -103,8 +105,8 @@ class POSE_PT_selection_sets(Panel): # add/remove/specials UI list Menu col = row.column(align=True) - col.operator("pose.selection_set_add", icon='ZOOMIN', text="") - col.operator("pose.selection_set_remove", icon='ZOOMOUT', text="") + col.operator("pose.selection_set_add", icon='ADD', text="") + col.operator("pose.selection_set_remove", icon='REMOVE', text="") col.menu("POSE_MT_selection_sets_specials", icon='DOWNARROW_HLT', text="") # move up/down arrows @@ -126,8 +128,11 @@ class POSE_PT_selection_sets(Panel): class POSE_UL_selection_set(UIList): - def draw_item(self, context, layout, data, set, icon, active_data, active_propname, index): - layout.prop(set, "name", text="", icon='GROUP_BONE', emboss=False) + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + sel_set = item + layout.prop(item, "name", text="", icon='GROUP_BONE', emboss=False) + if self.layout_type in ('DEFAULT', 'COMPACT'): + layout.prop(item, "is_selected", text="") class POSE_MT_selection_set_create(Menu): @@ -139,7 +144,7 @@ class POSE_MT_selection_set_create(Menu): text="New Selection Set") -class POSE_MT_selection_sets(Menu): +class POSE_MT_selection_sets_select(Menu): bl_label = 'Select Selection Set' @classmethod @@ -157,6 +162,7 @@ class POSE_MT_selection_sets(Menu): # Operators ################################################################### class PluginOperator(Operator): + """Operator only available for objects of type armature in pose mode.""" @classmethod def poll(cls, context): return (context.object and @@ -165,6 +171,7 @@ class PluginOperator(Operator): class NeedSelSetPluginOperator(PluginOperator): + """Operator only available if the armature has a selected selection set.""" @classmethod def poll(cls, context): if not super().poll(context): @@ -187,8 +194,8 @@ class POSE_OT_selection_set_delete_all(PluginOperator): class POSE_OT_selection_set_remove_bones(PluginOperator): bl_idname = "pose.selection_set_remove_bones" - bl_label = "Remove Bones from Sets" - bl_description = "Removes the Active Bones from All Sets" + bl_label = "Remove Selected Bones from All Sets" + bl_description = "Removes the Selected Bones from All Sets" bl_options = {'UNDO', 'REGISTER'} def execute(self, context): @@ -210,14 +217,15 @@ class POSE_OT_selection_set_move(NeedSelSetPluginOperator): bl_description = "Move the active Selection Set up/down the list of sets" bl_options = {'UNDO', 'REGISTER'} - direction = EnumProperty( + direction: EnumProperty( name="Move Direction", description="Direction to move the active Selection Set: UP (default) or DOWN", items=[ ('UP', "Up", "", -1), ('DOWN', "Down", "", 1), ], - default='UP' + default='UP', + options={'HIDDEN'}, ) @classmethod @@ -334,10 +342,12 @@ class POSE_OT_selection_set_select(NeedSelSetPluginOperator): bl_description = "Add Selection Set bones to current selection" bl_options = {'UNDO', 'REGISTER'} - selection_set_index = IntProperty( + selection_set_index: IntProperty( name='Selection Set Index', default=-1, - description='Which Selection Set to select; -1 uses the active Selection Set') + description='Which Selection Set to select; -1 uses the active Selection Set', + options={'HIDDEN'}, + ) def execute(self, context): arm = context.object @@ -386,20 +396,20 @@ class POSE_OT_selection_set_add_and_assign(PluginOperator): class POSE_OT_selection_set_copy(NeedSelSetPluginOperator): bl_idname = "pose.selection_set_copy" - bl_label = "Copy Selection Set to Clipboard" - bl_description = "Converts the Selection Set to JSON and places it on the clipboard" + bl_label = "Copy Selection Set(s)" + bl_description = "Copies the selected Selection Set(s) to the clipboard" bl_options = {'UNDO', 'REGISTER'} def execute(self, context): context.window_manager.clipboard = to_json(context) - self.report({'INFO'}, 'Copied Selection Set to Clipboard') + self.report({'INFO'}, 'Copied Selection Set(s) to Clipboard') return {'FINISHED'} class POSE_OT_selection_set_paste(PluginOperator): bl_idname = "pose.selection_set_paste" - bl_label = "Paste Selection Set from Clipboard" - bl_description = "Adds a new Selection Set from copied JSON on the clipboard" + bl_label = "Paste Selection Set(s)" + bl_description = "Adds new Selection Set(s) from the Clipboard" bl_options = {'UNDO', 'REGISTER'} def execute(self, context): @@ -416,67 +426,51 @@ class POSE_OT_selection_set_paste(PluginOperator): return {'FINISHED'} -# Registry #################################################################### - -classes = ( - POSE_MT_selection_set_create, - POSE_MT_selection_sets_specials, - POSE_MT_selection_sets, - POSE_PT_selection_sets, - POSE_UL_selection_set, - SelectionEntry, - SelectionSet, - POSE_OT_selection_set_delete_all, - POSE_OT_selection_set_remove_bones, - POSE_OT_selection_set_move, - POSE_OT_selection_set_add, - POSE_OT_selection_set_remove, - POSE_OT_selection_set_assign, - POSE_OT_selection_set_unassign, - POSE_OT_selection_set_select, - POSE_OT_selection_set_deselect, - POSE_OT_selection_set_add_and_assign, - POSE_OT_selection_set_copy, - POSE_OT_selection_set_paste, -) - +# Helper Functions ############################################################ -def add_sss_button(self, context): - self.layout.menu('POSE_MT_selection_sets') +def menu_func_select_selection_set(self, context): + self.layout.menu('POSE_MT_selection_sets_select', text="Bone Selection Set") def to_json(context) -> str: - """Convert the active bone selection set of the current rig to JSON.""" + """Convert the selected Selection Sets of the current rig to JSON. + + Selected Sets are the active_selection_set determined by the UIList + plus any with the is_selected checkbox on.""" import json arm = context.object active_idx = arm.active_selection_set - sel_set = arm.selection_sets[active_idx] - return json.dumps({ - 'name': sel_set.name, - 'bones': [bone_id.name for bone_id in sel_set.bone_ids] - }) + json_obj = {} + for idx, sel_set in enumerate(context.object.selection_sets): + if idx == active_idx or sel_set.is_selected: + bones = [bone_id.name for bone_id in sel_set.bone_ids] + json_obj[sel_set.name] = bones + + return json.dumps(json_obj) def from_json(context, as_json: str): - """Add the single bone selection set from JSON to the current rig.""" + """Add the selection sets (one or more) from JSON to the current rig.""" import json - sel_set = json.loads(as_json) - - sel_sets = context.object.selection_sets - new_sel_set = sel_sets.add() - new_sel_set.name = uniqify(sel_set['name'], sel_sets.keys()) + json_obj = json.loads(as_json) + arm_sel_sets = context.object.selection_sets - for bone_name in sel_set['bones']: - bone_id = new_sel_set.bone_ids.add() - bone_id.name = bone_name + for name, bones in json_obj.items(): + new_sel_set = arm_sel_sets.add() + new_sel_set.name = uniqify(name, arm_sel_sets.keys()) + for bone_name in bones: + bone_id = new_sel_set.bone_ids.add() + bone_id.name = bone_name def uniqify(name: str, other_names: list) -> str: """Return a unique name with .xxx suffix if necessary. + Example usage: + >>> uniqify('hey', ['there']) 'hey' >>> uniqify('hey', ['hey.001', 'hey.005']) @@ -514,7 +508,32 @@ def uniqify(name: str, other_names: list) -> str: return "{}.{:03d}".format(name, min_index) -# store keymaps here to access after registration +# Registry #################################################################### + +classes = ( + POSE_MT_selection_set_create, + POSE_MT_selection_sets_specials, + POSE_MT_selection_sets_select, + POSE_PT_selection_sets, + POSE_UL_selection_set, + SelectionEntry, + SelectionSet, + POSE_OT_selection_set_delete_all, + POSE_OT_selection_set_remove_bones, + POSE_OT_selection_set_move, + POSE_OT_selection_set_add, + POSE_OT_selection_set_remove, + POSE_OT_selection_set_assign, + POSE_OT_selection_set_unassign, + POSE_OT_selection_set_select, + POSE_OT_selection_set_deselect, + POSE_OT_selection_set_add_and_assign, + POSE_OT_selection_set_copy, + POSE_OT_selection_set_paste, +) + + +# Store keymaps here to access after registration. addon_keymaps = [] @@ -522,6 +541,7 @@ def register(): for cls in classes: bpy.utils.register_class(cls) + # Add properties. bpy.types.Object.selection_sets = CollectionProperty( type=SelectionSet, name="Selection Sets", @@ -533,28 +553,34 @@ def register(): default=0 ) + # Add shortcuts to the keymap. wm = bpy.context.window_manager km = wm.keyconfigs.addon.keymaps.new(name='Pose') - kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS', alt=True, shift=True) - kmi.properties.name = 'POSE_MT_selection_sets' + kmi.properties.name = 'POSE_MT_selection_sets_select' addon_keymaps.append((km, kmi)) - bpy.types.VIEW3D_MT_select_pose.append(add_sss_button) + # Add entries to menus. + bpy.types.VIEW3D_MT_select_pose.append(menu_func_select_selection_set) def unregister(): for cls in classes: bpy.utils.unregister_class(cls) + # Clear properties. del bpy.types.Object.selection_sets del bpy.types.Object.active_selection_set - # handle the keymap + # Clear shortcuts from the keymap. for km, kmi in addon_keymaps: km.keymap_items.remove(kmi) addon_keymaps.clear() + # Clear entries from menus. + bpy.types.VIEW3D_MT_select_pose.remove(menu_func_select_selection_set) + + if __name__ == "__main__": import doctest diff --git a/btrace/bTrace.py b/btrace/bTrace.py index 82d93f9d..8df06475 100644 --- a/btrace/bTrace.py +++ b/btrace/bTrace.py @@ -115,9 +115,9 @@ class OBJECT_OT_objectconnect(Operator): if curve_handle == 'AUTOMATIC': # hackish because of naming conflict in api curve_handle = 'AUTO' # Check if Btrace group exists, if not create - bgroup = bpy.data.groups.keys() + bgroup = bpy.data.collections.keys() if 'Btrace' not in bgroup: - bpy.ops.group.create(name="Btrace") + bpy.ops.collections.create(name="Btrace") # check if noise if Btrace.connect_noise: bpy.ops.object.btfcnoise() @@ -181,7 +181,7 @@ class OBJECT_OT_objectconnect(Operator): if Btrace.animate: # Add Curve Grow it? bpy.ops.curve.btgrow() - bpy.ops.object.group_link(group="Btrace") # add to Btrace group + bpy.ops.object.collection_link(group="Btrace") # add to Btrace group if Btrace.animate: bpy.ops.curve.btgrow() # Add grow curve @@ -245,9 +245,9 @@ class OBJECT_OT_particletrace(Operator): curve_handle = 'FREE' # Check if Btrace group exists, if not create - bgroup = bpy.data.groups.keys() + bgroup = bpy.data.collections.keys() if 'Btrace' not in bgroup: - bpy.ops.group.create(name="Btrace") + bpy.ops.collection.create(name="Btrace") if Btrace.curve_join: tracer = curvetracer('Tracer', 'Splines') @@ -271,7 +271,7 @@ class OBJECT_OT_particletrace(Operator): for curveobject in curvelist: curveobject.select = True bpy.context.scene.objects.active = curveobject - bpy.ops.object.group_link(group="Btrace") + bpy.ops.object.collection_link(group="Btrace") # Materials trace_mats = addtracemat(curveobject.data) if not trace_mats and check_materials is True: @@ -721,9 +721,9 @@ class OBJECT_OT_meshfollow(Operator): # Run methods # Check if Btrace group exists, if not create - bgroup = bpy.data.groups.keys() + bgroup = bpy.data.collections.keys() if 'Btrace' not in bgroup: - bpy.ops.group.create(name="Btrace") + bpy.ops.collection.create(name="Btrace") Btrace = bpy.context.window_manager.curve_tracer sel = getsel_option() # Get selection @@ -743,7 +743,7 @@ class OBJECT_OT_meshfollow(Operator): if curveobject.type == 'CURVE': curveobject.select = True context.scene.objects.active = curveobject - bpy.ops.object.group_link(group="Btrace") + bpy.ops.object.collection_link(group="Btrace") # Materials trace_mats = addtracemat(curveobject.data) if not trace_mats and check_materials is True: diff --git a/camera_dolly_crane_rigs.py b/camera_dolly_crane_rigs.py index c55b19e8..5f482ac4 100644 --- a/camera_dolly_crane_rigs.py +++ b/camera_dolly_crane_rigs.py @@ -21,7 +21,7 @@ bl_info = { "name": "Add Camera Rigs", "author": "Wayne Dixon, Kris Wittig", "version": (1, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "View3D > Add > Camera > Dolly or Crane Rig", "description": "Adds a Camera Rig with UI", "warning": "Enable Auto Run Python Scripts in User Preferences > File", @@ -47,20 +47,21 @@ def create_widget(self, name): obj_name = "WDGT_" + name scene = bpy.context.scene - # Check if it already exists - if obj_name in scene.objects: - return None - else: - mesh = bpy.data.meshes.new(obj_name) - obj = bpy.data.objects.new(obj_name, mesh) - scene.objects.link(obj) + mesh = bpy.data.meshes.new(obj_name) + obj = bpy.data.objects.new(obj_name, mesh) - # this will put the Widget objects out of the way on layer 19 - WDGT_layers = (False, False, False, False, False, False, False, False, False, True, - False, False, False, False, False, False, False, False, False, False) - obj.layers = WDGT_layers + # create a new collection for the wigets + collection_name = "camera_widgets" + c = bpy.data.collections.get(collection_name) + if c is not None: + c.objects.link(obj) + else: + c = bpy.data.collections.new(collection_name) + # link the collection + scene.collection.children.link(c) + c.objects.link(obj) - return obj + return obj def create_root_widget(self, name): @@ -320,6 +321,7 @@ class MakeCameraActive(Operator): # Define function to add marker to timeline and bind camera # ========================================================================= def markerBind(): + view_layer = bpy.context.view_layer ob = bpy.context.active_object # rig object active_cam = ob.children[0] # camera object @@ -329,11 +331,11 @@ def markerBind(): bpy.ops.marker.add() bpy.ops.marker.rename(name="cam_" + str(bpy.context.scene.frame_current)) # select rig camera - bpy.context.scene.objects.active = active_cam + view_layer.objects.active = active_cam # bind marker to selected camera bpy.ops.marker.camera_bind() # switch selected object back to the rig - bpy.context.scene.objects.active = ob + view_layer.objects.active = ob # switch back to 3d view bpy.context.area.type = 'VIEW_3D' @@ -358,6 +360,7 @@ class AddMarkerBind(Operator): # Define the function to add an Empty as DOF object # ========================================================================= def add_DOF_Empty(): + view_layer = bpy.context.view_layer smode = bpy.context.mode rig = bpy.context.active_object bone = rig.data.bones['AIM_child'] @@ -383,7 +386,7 @@ def add_DOF_Empty(): # make this new empty the dof_object cam.dof_object = obj # reselect the rig - bpy.context.scene.objects.active = rig + view_layer.objects.active = rig obj.select = False rig.select = True @@ -409,6 +412,8 @@ class AddDofEmpty(Operator): # Define the function to build the Dolly Rig # ========================================================================= def build_dolly_rig(context): + view_layer = bpy.context.view_layer + # Define some useful variables: boneLayer = (False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, @@ -498,7 +503,7 @@ def build_dolly_rig(context): prop["soft_max"] = prop["max"] = 1.0 # Add Driver to Lock/Unlock Camera from Aim Target - rig = bpy.context.scene.objects.active + rig = view_layer.objects.active pose_bone = bpy.data.objects[rig.name].pose.bones['CTRL'] constraint = pose_bone.constraints["Track To"] @@ -533,7 +538,7 @@ def build_dolly_rig(context): cam.data.name = "Dolly_Camera.000" cam_data_name = bpy.context.object.data.name - bpy.data.cameras[cam_data_name].draw_size = 1.0 + bpy.data.cameras[cam_data_name].display_size = 1.0 cam.rotation_euler[0] = 1.5708 # rotate the camera 90 degrees in x cam.location = (0.0, -2.0, 0.0) # move the camera to the correct position cam.parent = rig @@ -558,9 +563,9 @@ def build_dolly_rig(context): bpy.context.object.hide_select = False # make the rig the active object before finishing - bpy.context.scene.objects.active = rig - cam.select = False - rig.select = True + view_layer.objects.active = rig + cam.select_set(False) + rig.select_set(True) return rig @@ -576,6 +581,7 @@ def build_crane_rig(context): False, False, False, False, False, False, False, False) # Add the new armature object: + view_layer = bpy.context.view_layer bpy.ops.object.armature_add() rig = context.active_object @@ -631,9 +637,9 @@ def build_crane_rig(context): ctrlAimChild.parent = ctrlAim # change display to BBone: it just looks nicer - bpy.context.object.data.draw_type = 'BBONE' + bpy.context.object.data.display_type = 'BBONE' # change display to wire for object - bpy.context.object.draw_type = 'WIRE' + bpy.context.object.display_type = 'WIRE' # jump into pose mode and change bones to euler bpy.ops.object.mode_set(mode='POSE') @@ -683,7 +689,7 @@ def build_crane_rig(context): prop["soft_max"] = prop["max"] = 1.0 # Add Driver to Lock/Unlock Camera from Aim Target - rig = bpy.context.scene.objects.active + rig = view_layer.objects.active pose_bone = bpy.data.objects[rig.name].pose.bones['CTRL'] constraint = pose_bone.constraints["Track To"] @@ -718,7 +724,7 @@ def build_crane_rig(context): cam.data.name = "Crane_Camera.000" cam_data_name = bpy.context.object.data.name - bpy.data.cameras[cam_data_name].draw_size = 1.0 + bpy.data.cameras[cam_data_name].display_size = 1.0 cam.rotation_euler[0] = 1.5708 # rotate the camera 90 degrees in x cam.location = (0.0, -2.0, 0.0) # move the camera to the correct position cam.parent = rig @@ -743,9 +749,9 @@ def build_crane_rig(context): bpy.context.object.hide_select = False # make the rig the active object before finishing - bpy.context.scene.objects.active = rig - cam.select = False - rig.select = True + view_layer.objects.active = rig + cam.select_set(False) + rig.select_set(True) return rig @@ -801,7 +807,7 @@ class DollyCameraUI(Panel): text="Make Active Camera", icon='CAMERA_DATA') col.prop(context.active_object, - 'show_x_ray', toggle=False, text='X Ray') + 'show_in_front', toggle=False, text='X Ray') col.prop(cam, "show_limits") col.prop(cam, "show_safe_areas") col.prop(cam, "show_passepartout") @@ -865,7 +871,7 @@ class CraneCameraUI(Panel): col.operator( "scene.make_camera_active", text="Make Active Camera", icon='CAMERA_DATA') col.prop( - context.active_object, 'show_x_ray', toggle=False, text='X Ray') + context.active_object, 'show_in_front', toggle=False, text='X Ray') col.prop(cam, "show_limits") col.prop(cam, "show_safe_areas") col.prop(cam, "show_passepartout") @@ -966,7 +972,7 @@ def register(): bpy.utils.register_class(MakeCameraActive) bpy.utils.register_class(AddMarkerBind) bpy.utils.register_class(AddDofEmpty) - bpy.types.INFO_MT_camera_add.append(add_dolly_crane_buttons) + bpy.types.VIEW3D_MT_camera_add.append(add_dolly_crane_buttons) def unregister(): @@ -977,7 +983,7 @@ def unregister(): bpy.utils.unregister_class(MakeCameraActive) bpy.utils.unregister_class(AddMarkerBind) bpy.utils.unregister_class(AddDofEmpty) - bpy.types.INFO_MT_camera_add.remove(add_dolly_crane_buttons) + bpy.types.VIEW3D_MT_camera_add.remove(add_dolly_crane_buttons) if __name__ == "__main__": diff --git a/camera_turnaround.py b/camera_turnaround.py index 6a6e710f..547c2834 100644 --- a/camera_turnaround.py +++ b/camera_turnaround.py @@ -96,7 +96,7 @@ class RunAction(Operator): context.user_preferences.edit.keyframe_new_interpolation_type = 'LINEAR' # create first frame myempty.rotation_euler = (0, 0, 0) - myempty.empty_draw_size = 0.1 + myempty.empty_display_size = 0.1 context.scene.frame_set(scene.frame_start) myempty.keyframe_insert(data_path='rotation_euler', frame=scene.frame_start) diff --git a/curve_simplify.py b/curve_simplify.py index b053a4b9..4a390feb 100644 --- a/curve_simplify.py +++ b/curve_simplify.py @@ -20,7 +20,7 @@ bl_info = { "name": "Simplify Curves", "author": "testscreenings", "version": (1, 0, 3), - "blender": (2, 75, 0), + "blender": (2, 80, 0), "location": "View3D > Add > Curve > Simplify Curves", "description": "Simplifies 3D Curve objects and animation F-Curves", "warning": "", @@ -35,16 +35,16 @@ This script simplifies Curve objects and animation F-Curves. import bpy from bpy.props import ( - BoolProperty, - EnumProperty, - FloatProperty, - IntProperty, - ) + BoolProperty, + EnumProperty, + FloatProperty, + IntProperty, +) from mathutils import Vector from math import ( - sin, - pow, - ) + sin, + pow, +) from bpy.types import Operator @@ -307,10 +307,11 @@ def main(context, obj, options, curve_dimension): # create new object and put into scene newCurve = bpy.data.objects.new("Simple_" + obj.name, curve) - scene.objects.link(newCurve) - newCurve.select = True + coll = context.view_layer.active_layer_collection.collection + coll.objects.link(newCurve) + newCurve.select_set(True) - scene.objects.active = newCurve + context.view_layer.objects.active = newCurve newCurve.matrix_world = obj.matrix_world # set bezierhandles to auto @@ -325,7 +326,7 @@ def getFcurveData(obj): for fc in obj.animation_data.action.fcurves: if fc.select: fcVerts = [vcVert.co.to_3d() - for vcVert in fc.keyframe_points.values()] + for vcVert in fc.keyframe_points.values()] fcurves.append(fcVerts) return fcurves @@ -396,38 +397,38 @@ class GRAPH_OT_simplify(Operator): opModes = [ ('DISTANCE', 'Distance', 'Distance-based simplification (Poly)'), ('CURVATURE', 'Curvature', 'Curvature-based simplification (RDP)')] - mode = EnumProperty( + mode: EnumProperty( name="Mode", description="Choose algorithm to use", items=opModes ) - k_thresh = FloatProperty( + k_thresh: FloatProperty( name="k", min=0, soft_min=0, default=0, precision=3, description="Threshold" ) - pointsNr = IntProperty( + pointsNr: IntProperty( name="n", min=5, soft_min=5, max=16, soft_max=9, default=5, description="Degree of curve to get averaged curvatures" ) - error = FloatProperty( + error: FloatProperty( name="Error", description="Maximum allowed distance error", min=0.0, soft_min=0.0, default=0, precision=3 ) - degreeOut = IntProperty( + degreeOut: IntProperty( name="Degree", min=3, soft_min=3, max=7, soft_max=7, default=5, description="Degree of new curve" ) - dis_error = FloatProperty( + dis_error: FloatProperty( name="Distance error", description="Maximum allowed distance error in Blender Units", min=0, soft_min=0, @@ -490,7 +491,7 @@ class CURVE_OT_simplify(Operator): ('DISTANCE', 'Distance', 'Distance-based simplification (Poly)'), ('CURVATURE', 'Curvature', 'Curvature-based simplification (RDP)') ] - mode = EnumProperty( + mode: EnumProperty( name="Mode", description="Choose algorithm to use", items=opModes @@ -501,42 +502,44 @@ class CURVE_OT_simplify(Operator): ('BEZIER', 'Bezier', 'BEZIER'), ('POLY', 'Poly', 'POLY') ] - output = EnumProperty( + output: EnumProperty( name="Output splines", description="Type of splines to output", items=SplineTypes ) - k_thresh = FloatProperty( + k_thresh: FloatProperty( name="k", min=0, soft_min=0, default=0, precision=3, description="Threshold" ) - pointsNr = IntProperty(name="n", + pointsNr: IntProperty( + name="n", min=5, soft_min=5, max=9, soft_max=9, default=5, description="Degree of curve to get averaged curvatures" ) - error = FloatProperty( + error: FloatProperty( name="Error", description="Maximum allowed distance error in Blender Units", min=0, soft_min=0, default=0.0, precision=3 ) - degreeOut = IntProperty(name="Degree", + degreeOut: IntProperty( + name="Degree", min=3, soft_min=3, max=7, soft_max=7, default=5, description="Degree of new curve" ) - dis_error = FloatProperty( + dis_error: FloatProperty( name="Distance error", description="Maximum allowed distance error in Blender Units", min=0, soft_min=0, default=0.0 ) - keepShort = BoolProperty( + keepShort: BoolProperty( name="Keep short splines", description="Keep short splines (less than 7 points)", default=True @@ -546,7 +549,7 @@ class CURVE_OT_simplify(Operator): layout = self.layout col = layout.column() - col.label("Distance Error:") + col.label(text="Distance Error:") col.prop(self, "error", expand=True) col.prop(self, "output", text="Output", icon="OUTLINER_OB_CURVE") if self.output == "NURBS": @@ -591,21 +594,30 @@ class CURVE_OT_simplify(Operator): # Register +classes = [ + GRAPH_OT_simplify, + CURVE_OT_simplify, +] + def register(): - bpy.utils.register_module(__name__) + from bpy.utils import register_class + for cls in classes: + register_class(cls) bpy.types.GRAPH_MT_channel.append(menu_func) bpy.types.DOPESHEET_MT_channel.append(menu_func) - bpy.types.INFO_MT_curve_add.append(menu) + bpy.types.VIEW3D_MT_curve_add.append(menu) def unregister(): + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + bpy.types.GRAPH_MT_channel.remove(menu_func) bpy.types.DOPESHEET_MT_channel.remove(menu_func) - bpy.types.INFO_MT_curve_add.remove(menu) - - bpy.utils.unregister_module(__name__) + bpy.types.VIEW3D_MT_curve_add.remove(menu) if __name__ == "__main__": diff --git a/depsgraph_debug.py b/depsgraph_debug.py index 438d4885..76f25290 100644 --- a/depsgraph_debug.py +++ b/depsgraph_debug.py @@ -27,7 +27,7 @@ bl_info = { "name": "Dependency Graph Debug", "author": "Sergey Sharybin", "version": (0, 1), - "blender": (2, 79, 0), + "blender": (2, 80, 0), "description": "Various dependency graph debugging tools", "warning": "", "wiki_url": "", @@ -48,12 +48,12 @@ def _get_depsgraph(context): # Save data from depsgraph to a specified file. class SCENE_OT_depsgraph_save_common: - filepath = StringProperty( - name="File Path", - description="Filepath used for saving the file", - maxlen=1024, - subtype='FILE_PATH', - ) + filepath: StringProperty( + name="File Path", + description="Filepath used for saving the file", + maxlen=1024, + subtype='FILE_PATH', + ) def _getExtension(self, context): return "" @@ -86,8 +86,10 @@ class SCENE_OT_depsgraph_save_common: pass -class SCENE_OT_depsgraph_relations_graphviz(Operator, - SCENE_OT_depsgraph_save_common): +class SCENE_OT_depsgraph_relations_graphviz( + Operator, + SCENE_OT_depsgraph_save_common, +): bl_idname = "scene.depsgraph_relations_graphviz" bl_label = "Save Depsgraph" bl_description = "Save current scene's dependency graph to a graphviz file" @@ -96,13 +98,16 @@ class SCENE_OT_depsgraph_relations_graphviz(Operator, return ".dot" def performSave(self, context, depsgraph): + import os basename, extension = os.path.splitext(self.filepath) - depsgraph.debug_relations_graphviz(self.filepath, absename + ".png") + depsgraph.debug_relations_graphviz(os.path.join(self.filepath, basename + ".dot")) return True -class SCENE_OT_depsgraph_stats_gnuplot(Operator, - SCENE_OT_depsgraph_save_common): +class SCENE_OT_depsgraph_stats_gnuplot( + Operator, + SCENE_OT_depsgraph_save_common, +): bl_idname = "scene.depsgraph_stats_gnuplot" bl_label = "Save Depsgraph Stats" bl_description = "Save current scene's evaluaiton stats to gnuplot file" diff --git a/development_api_navigator.py b/development_api_navigator.py index 5e0276cb..deae8443 100644 --- a/development_api_navigator.py +++ b/development_api_navigator.py @@ -514,7 +514,7 @@ class OBJECT_PT_api_navigator(ApiNavigator, Panel): elif iterable == 'b': box = self.layout.box() row = box.row() - row.label(text="Item Values", icon="OOPS") + row.label(text="Item Values", icon='OUTLINER') box = box.box() col = box.column(align=True) collection = list(current_module) diff --git a/development_icon_get.py b/development_icon_get.py index a7740c31..e32c58ef 100644 --- a/development_icon_get.py +++ b/development_icon_get.py @@ -23,9 +23,9 @@ bl_info = { "name": "Icon Viewer", "description": "Click an icon to copy its name to the clipboard", "author": "roaoao", - "version": (1, 3, 2), - "blender": (2, 75, 0), - "location": "Spacebar > Icon Viewer, Text Editor > Properties", + "version": (1, 4, 0), + "blender": (2, 80, 0), + "location": "Search Menu > Icon Viewer, Text Editor > Properties", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6" "/Py/Scripts/Development/Display_All_Icons", "category": "Development" @@ -33,6 +33,10 @@ bl_info = { import bpy import math +from bpy.props import ( + BoolProperty, + StringProperty, +) DPI = 72 POPUP_PADDING = 10 @@ -88,6 +92,9 @@ class Icons: not pr.show_brush_icons and "BRUSH_" in icon and \ icon != 'BRUSH_DATA' or \ not pr.show_matcap_icons and "MATCAP_" in icon or \ + not pr.show_event_icons and ( + "EVENT_" in icon or "MOUSE_" in icon + ) or \ not pr.show_colorset_icons and "COLORSET_" in icon: continue self._filtered_icons.append(icon) @@ -109,8 +116,8 @@ class Icons: else: filtered_icons = self.filtered_icons - column = layout.column(True) - row = column.row(True) + column = layout.column(align=True) + row = column.row(align=True) row.alignment = 'CENTER' selected_icon = self.selected_icon if self.is_popup else \ @@ -118,7 +125,7 @@ class Icons: col_idx = 0 for i, icon in enumerate(filtered_icons): p = row.operator( - IV_OT_icon_select.bl_idname, "", + IV_OT_icon_select.bl_idname, text="", icon=icon, emboss=icon == selected_icon) p.icon = icon p.force_copy_on_select = not self.is_popup @@ -129,16 +136,15 @@ class Icons: break col_idx = 0 if i < len(filtered_icons) - 1: - row = column.row(True) + row = column.row(align=True) row.alignment = 'CENTER' if col_idx != 0 and not icons and i >= num_cols: - sub = row.row(True) - sub.scale_x = num_cols - col_idx - sub.label("", icon='BLANK1') + for _ in range(num_cols - col_idx): + row.label(text="", icon='BLANK1') if not filtered_icons: - row.label("No icons were found") + row.label(text="No icons were found") class IV_Preferences(bpy.types.AddonPreferences): @@ -154,47 +160,51 @@ class IV_Preferences(bpy.types.AddonPreferences): def set_panel_filter(self, value): self.panel_icons.filter = value - panel_filter = bpy.props.StringProperty( + panel_filter: StringProperty( description="Filter", default="", get=lambda s: s.panel_icons.filter, set=set_panel_filter, options={'TEXTEDIT_UPDATE'}) - show_panel_icons = bpy.props.BoolProperty( + show_panel_icons: BoolProperty( name="Show Icons", description="Show icons", default=True) - show_history = bpy.props.BoolProperty( + show_history: BoolProperty( name="Show History", description="Show history", default=True) - show_brush_icons = bpy.props.BoolProperty( + show_brush_icons: BoolProperty( name="Show Brush Icons", description="Show brush icons", default=True, update=update_icons) - show_matcap_icons = bpy.props.BoolProperty( + show_matcap_icons: BoolProperty( name="Show Matcap Icons", description="Show matcap icons", default=True, update=update_icons) - show_colorset_icons = bpy.props.BoolProperty( + show_event_icons: BoolProperty( + name="Show Event Icons", + description="Show event icons", default=True, + update=update_icons) + show_colorset_icons: BoolProperty( name="Show Colorset Icons", description="Show colorset icons", default=True, update=update_icons) - copy_on_select = bpy.props.BoolProperty( + copy_on_select: BoolProperty( name="Copy Icon On Click", description="Copy icon on click", default=True) - close_on_select = bpy.props.BoolProperty( + close_on_select: BoolProperty( name="Close Popup On Click", description=( "Close the popup on click.\n" "Not supported by some windows (User Preferences, Render)" - ), + ), default=False) - auto_focus_filter = bpy.props.BoolProperty( + auto_focus_filter: BoolProperty( name="Auto Focus Input Field", description="Auto focus input field", default=True) - show_panel = bpy.props.BoolProperty( + show_panel: BoolProperty( name="Show Panel", description="Show the panel in the Text Editor", default=True) - show_header = bpy.props.BoolProperty( + show_header: BoolProperty( name="Show Header", description="Show the header in the Python Console", default=True) @@ -207,29 +217,30 @@ class IV_Preferences(bpy.types.AddonPreferences): row = layout.row() - col = row.column(True) - col.label("Icons:") + col = row.column(align=True) + col.label(text="Icons:") col.prop(self, "show_matcap_icons") col.prop(self, "show_brush_icons") col.prop(self, "show_colorset_icons") + col.prop(self, "show_event_icons") col.separator() col.prop(self, "show_history") - col = row.column(True) - col.label("Popup:") + col = row.column(align=True) + col.label(text="Popup:") col.prop(self, "auto_focus_filter") col.prop(self, "copy_on_select") if self.copy_on_select: col.prop(self, "close_on_select") - col = row.column(True) - col.label("Panel:") + col = row.column(align=True) + col.label(text="Panel:") col.prop(self, "show_panel") if self.show_panel: col.prop(self, "show_panel_icons") col.separator() - col.label("Header:") + col.label(text="Header:") col.prop(self, "show_header") @@ -253,12 +264,13 @@ class IV_PT_icons(bpy.types.Panel): def draw(self, context): pr = prefs() - row = self.layout.row(True) + row = self.layout.row(align=True) if pr.show_panel_icons: - row.prop(pr, "panel_filter", "", icon='VIEWZOOM') + row.prop(pr, "panel_filter", text="", icon='VIEWZOOM') else: row.operator(IV_OT_icons_show.bl_idname) - row.operator(IV_OT_panel_menu_call.bl_idname, "", icon='COLLAPSEMENU') + row.operator( + IV_OT_panel_menu_call.bl_idname, text="", icon='COLLAPSEMENU') _, y0 = context.region.view2d.region_to_view(0, 0) _, y1 = context.region.view2d.region_to_view(0, 10) @@ -271,11 +283,11 @@ class IV_PT_icons(bpy.types.Panel): col = None if HISTORY and pr.show_history: - col = self.layout.column(True) + col = self.layout.column(align=True) pr.panel_icons.draw(col.box(), num_cols, HISTORY) if pr.show_panel_icons: - col = col or self.layout.column(True) + col = col or self.layout.column(align=True) pr.panel_icons.draw(col.box(), num_cols) @classmethod @@ -313,9 +325,10 @@ class IV_OT_panel_menu_call(bpy.types.Operator): layout.prop(pr, "show_matcap_icons") layout.prop(pr, "show_brush_icons") layout.prop(pr, "show_colorset_icons") + layout.prop(pr, "show_event_icons") def execute(self, context): - context.window_manager.popup_menu(self.menu, "Icon Viewer") + context.window_manager.popup_menu(self.menu, title="Icon Viewer") return {'FINISHED'} @@ -325,8 +338,8 @@ class IV_OT_icon_select(bpy.types.Operator): bl_description = "Select the icon" bl_options = {'INTERNAL'} - icon = bpy.props.StringProperty() - force_copy_on_select = bpy.props.BoolProperty() + icon: StringProperty() + force_copy_on_select: BoolProperty() def execute(self, context): pr = prefs() @@ -362,17 +375,17 @@ class IV_OT_icons_show(bpy.types.Operator): if IV_OT_icons_show.instance: IV_OT_icons_show.instance.auto_focusable = False - filter_auto_focus = bpy.props.StringProperty( + filter_auto_focus: StringProperty( description="Filter", get=lambda s: prefs().popup_icons.filter, set=set_filter, options={'TEXTEDIT_UPDATE', 'SKIP_SAVE'}) - filter = bpy.props.StringProperty( + filter: StringProperty( description="Filter", get=lambda s: prefs().popup_icons.filter, set=set_filter, options={'TEXTEDIT_UPDATE'}) - selected_icon = bpy.props.StringProperty( + selected_icon: StringProperty( description="Selected Icon", get=lambda s: prefs().popup_icons.selected_icon, set=set_selected_icon) @@ -383,36 +396,38 @@ class IV_OT_icons_show(bpy.types.Operator): def draw_header(self, layout): pr = prefs() header = layout.box() - header = header.split(0.75) if self.selected_icon else header.row() - row = header.row(True) - row.prop(pr, "show_matcap_icons", "", icon='SMOOTH') - row.prop(pr, "show_brush_icons", "", icon='BRUSH_DATA') - row.prop(pr, "show_colorset_icons", "", icon='COLOR') + header = header.split(factor=0.75) if self.selected_icon else \ + header.row() + row = header.row(align=True) + row.prop(pr, "show_matcap_icons", text="", icon='SHADING_RENDERED') + row.prop(pr, "show_brush_icons", text="", icon='BRUSH_DATA') + row.prop(pr, "show_colorset_icons", text="", icon='COLOR') + row.prop(pr, "show_event_icons", text="", icon='HAND') row.separator() row.prop( - pr, "copy_on_select", "", - icon='BORDER_RECT', toggle=True) + pr, "copy_on_select", text="", + icon='COPYDOWN', toggle=True) if pr.copy_on_select: - sub = row.row(True) + sub = row.row(align=True) if bpy.context.window.screen.name == "temp": sub.alert = True sub.prop( - pr, "close_on_select", "", + pr, "close_on_select", text="", icon='RESTRICT_SELECT_OFF', toggle=True) row.prop( - pr, "auto_focus_filter", "", + pr, "auto_focus_filter", text="", icon='OUTLINER_DATA_FONT', toggle=True) row.separator() if self.auto_focusable and pr.auto_focus_filter: - row.prop(self, "filter_auto_focus", "", icon='VIEWZOOM') + row.prop(self, "filter_auto_focus", text="", icon='VIEWZOOM') else: - row.prop(self, "filter", "", icon='VIEWZOOM') + row.prop(self, "filter", text="", icon='VIEWZOOM') if self.selected_icon: row = header.row() - row.prop(self, "selected_icon", "", icon=self.selected_icon) + row.prop(self, "selected_icon", text="", icon=self.selected_icon) def draw(self, context): pr = prefs() @@ -425,7 +440,7 @@ class IV_OT_icons_show(bpy.types.Operator): self.get_num_cols(len(pr.popup_icons.filtered_icons)), history_num_cols) - subcol = col.column(True) + subcol = col.column(align=True) if HISTORY and pr.show_history: pr.popup_icons.draw(subcol.box(), history_num_cols, HISTORY) @@ -468,18 +483,31 @@ class IV_OT_icons_show(bpy.types.Operator): ui_scale() * (num_cols * ICON_SIZE + POPUP_PADDING), context.window.width - WIN_PADDING) - return context.window_manager.invoke_props_dialog(self, self.width) + return context.window_manager.invoke_props_dialog( + self, width=self.width) + + +classes = ( + IV_PT_icons, + IV_HT_icons, + IV_OT_panel_menu_call, + IV_OT_icon_select, + IV_OT_icons_show, + IV_Preferences, +) def register(): if bpy.app.background: return - bpy.utils.register_module(__name__) + for cls in classes: + bpy.utils.register_class(cls) def unregister(): if bpy.app.background: return - bpy.utils.unregister_module(__name__) + for cls in classes: + bpy.utils.unregister_class(cls) diff --git a/development_iskeyfree.py b/development_iskeyfree.py index 332e11d4..5d5600f4 100644 --- a/development_iskeyfree.py +++ b/development_iskeyfree.py @@ -146,9 +146,9 @@ class MyChecker(): allkeys = [ "LEFTMOUSE", "MIDDLEMOUSE", "RIGHTMOUSE", "BUTTON4MOUSE", "BUTTON5MOUSE", "BUTTON6MOUSE", "BUTTON7MOUSE", - "ACTIONMOUSE", "SELECTMOUSE", "MOUSEMOVE", "INBETWEEN_MOUSEMOVE", "TRACKPADPAN", "TRACKPADZOOM", + "MOUSEMOVE", "INBETWEEN_MOUSEMOVE", "TRACKPADPAN", "TRACKPADZOOM", "MOUSEROTATE", "WHEELUPMOUSE", "WHEELDOWNMOUSE", "WHEELINMOUSE", "WHEELOUTMOUSE", "EVT_TWEAK_L", - "EVT_TWEAK_M", "EVT_TWEAK_R", "EVT_TWEAK_A", "EVT_TWEAK_S", "A", "B", "C", "D", "E", "F", "G", "H", + "EVT_TWEAK_M", "EVT_TWEAK_R", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE", "LEFT_CTRL", "LEFT_ALT", "LEFT_SHIFT", @@ -309,8 +309,6 @@ class IskeyFreeProperties(PropertyGroup): ("BUTTON5MOUSE", "BUTTON5MOUSE", ""), ("BUTTON6MOUSE", "BUTTON6MOUSE", ""), ("BUTTON7MOUSE", "BUTTON7MOUSE", ""), - ("ACTIONMOUSE", "ACTIONMOUSE", ""), - ("SELECTMOUSE", "SELECTMOUSE", ""), ("MOUSEMOVE", "MOUSEMOVE", ""), ("INBETWEEN_MOUSEMOVE", "INBETWEEN_MOUSEMOVE", ""), ("TRACKPADPAN", "TRACKPADPAN", ""), @@ -323,8 +321,6 @@ class IskeyFreeProperties(PropertyGroup): ("EVT_TWEAK_L", "EVT_TWEAK_L", ""), ("EVT_TWEAK_M", "EVT_TWEAK_M", ""), ("EVT_TWEAK_R", "EVT_TWEAK_R", ""), - ("EVT_TWEAK_A", "EVT_TWEAK_A", ""), - ("EVT_TWEAK_S", "EVT_TWEAK_S", ""), ("A", "A", ""), ("B", "B", ""), ("C", "C", ""), diff --git a/game_engine_publishing.py b/game_engine_publishing.py deleted file mode 100644 index 495b0123..00000000 --- a/game_engine_publishing.py +++ /dev/null @@ -1,576 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import bpy -import os -import tempfile -import shutil -import tarfile -import time -import stat - - -bl_info = { - "name": "Game Engine Publishing", - "author": "Mitchell Stokes (Moguri), Oren Titane (Genome36)", - "version": (0, 1, 0), - "blender": (2, 75, 0), - "location": "Render Properties > Publishing Info", - "description": "Publish .blend file as game engine runtime, manage versions and platforms", - "warning": "", - "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Game_Engine/Publishing", - "category": "Game Engine", -} - - -def WriteRuntime(player_path, output_path, asset_paths, copy_python, overwrite_lib, copy_dlls, make_archive, report=print): - import struct - - player_path = bpy.path.abspath(player_path) - ext = os.path.splitext(player_path)[-1].lower() - output_path = bpy.path.abspath(output_path) - output_dir = os.path.dirname(output_path) - if not os.path.exists(output_dir): - os.makedirs(output_dir) - - python_dir = os.path.join(os.path.dirname(player_path), - bpy.app.version_string.split()[0], - "python", - "lib") - - # Check the paths - if not os.path.isfile(player_path) and not(os.path.exists(player_path) and player_path.endswith('.app')): - report({'ERROR'}, "The player could not be found! Runtime not saved") - return - - # Check if we're bundling a .app - if player_path.lower().endswith('.app'): - # Python doesn't need to be copied for OS X since it's already inside blenderplayer.app - copy_python = False - - output_path = bpy.path.ensure_ext(output_path, '.app') - - if os.path.exists(output_path): - shutil.rmtree(output_path) - - shutil.copytree(player_path, output_path) - bpy.ops.wm.save_as_mainfile(filepath=os.path.join(output_path, 'Contents', 'Resources', 'game.blend'), - relative_remap=False, - compress=False, - copy=True, - ) - else: - # Enforce "exe" extension on Windows - if player_path.lower().endswith('.exe'): - output_path = bpy.path.ensure_ext(output_path, '.exe') - - # Get the player's binary and the offset for the blend - with open(player_path, "rb") as file: - player_d = file.read() - offset = file.tell() - - # Create a tmp blend file (Blenderplayer doesn't like compressed blends) - tempdir = tempfile.mkdtemp() - blend_path = os.path.join(tempdir, bpy.path.clean_name(output_path)) - bpy.ops.wm.save_as_mainfile(filepath=blend_path, - relative_remap=False, - compress=False, - copy=True, - ) - - # Get the blend data - with open(blend_path, "rb") as blend_file: - blend_d = blend_file.read() - - # Get rid of the tmp blend, we're done with it - os.remove(blend_path) - os.rmdir(tempdir) - - # Create a new file for the bundled runtime - with open(output_path, "wb") as output: - # Write the player and blend data to the new runtime - print("Writing runtime...", end=" ", flush=True) - output.write(player_d) - output.write(blend_d) - - # Store the offset (an int is 4 bytes, so we split it up into 4 bytes and save it) - output.write(struct.pack('BBBB', (offset >> 24) & 0xFF, - (offset >> 16) & 0xFF, - (offset >> 8) & 0xFF, - (offset >> 0) & 0xFF)) - - # Stuff for the runtime - output.write(b'BRUNTIME') - - print("done", flush=True) - - # Make sure the runtime is executable - os.chmod(output_path, 0o755) - - # Copy bundled Python - blender_dir = os.path.dirname(player_path) - - if copy_python: - print("Copying Python files...", end=" ", flush=True) - py_folder = os.path.join(bpy.app.version_string.split()[0], "python", "lib") - dst = os.path.join(output_dir, py_folder) - src = python_dir - - if os.path.exists(dst) and overwrite_lib: - shutil.rmtree(dst) - - if not os.path.exists(dst): - shutil.copytree(src, dst, ignore=lambda dir, contents: [i for i in contents if i == '__pycache__']) - print("done", flush=True) - else: - print("used existing Python folder", flush=True) - - # And DLLs if we're doing a Windows runtime) - if copy_dlls and ext == ".exe": - print("Copying DLLs...", end=" ", flush=True) - for file in [i for i in os.listdir(blender_dir) if i.lower().endswith('.dll')]: - src = os.path.join(blender_dir, file) - dst = os.path.join(output_dir, file) - shutil.copy2(src, dst) - - print("done", flush=True) - - # Copy assets - for ap in asset_paths: - src = bpy.path.abspath(ap.name) - dst = os.path.join(output_dir, ap.name[2:] if ap.name.startswith('//') else ap.name) - - if os.path.exists(src): - if os.path.isdir(src): - if ap.overwrite and os.path.exists(dst): - shutil.rmtree(dst) - elif not os.path.exists(dst): - shutil.copytree(src, dst) - else: - if ap.overwrite or not os.path.exists(dst): - shutil.copy2(src, dst) - else: - report({'ERROR'}, "Could not find asset path: '%s'" % src) - - # Make archive - if make_archive: - print("Making archive...", end=" ", flush=True) - - arctype = '' - if player_path.lower().endswith('.exe'): - arctype = 'zip' - elif player_path.lower().endswith('.app'): - arctype = 'zip' - else: # Linux - arctype = 'gztar' - - basedir = os.path.normpath(os.path.join(os.path.dirname(output_path), '..')) - afilename = os.path.join(basedir, os.path.basename(output_dir)) - - if arctype == 'gztar': - # Create the tarball ourselves instead of using shutil.make_archive - # so we can handle permission bits. - - # The runtimename needs to use forward slashes as a path separator - # since this is what tarinfo.name is using. - runtimename = os.path.relpath(output_path, basedir).replace('\\', '/') - - def _set_ex_perm(tarinfo): - if tarinfo.name == runtimename: - tarinfo.mode = 0o755 - return tarinfo - - with tarfile.open(afilename + '.tar.gz', 'w:gz') as tf: - tf.add(output_dir, os.path.relpath(output_dir, basedir), filter=_set_ex_perm) - elif arctype == 'zip': - shutil.make_archive(afilename, 'zip', output_dir) - else: - report({'ERROR'}, "Unknown archive type %s for runtime %s\n" % (arctype, player_path)) - - print("done", flush=True) - - -class PublishAllPlatforms(bpy.types.Operator): - bl_idname = "wm.publish_platforms" - bl_label = "Exports a runtime for each listed platform" - - def execute(self, context): - ps = context.scene.ge_publish_settings - - if ps.publish_default_platform: - print("Publishing default platform") - blender_bin_path = bpy.app.binary_path - blender_bin_dir = os.path.dirname(blender_bin_path) - ext = os.path.splitext(blender_bin_path)[-1].lower() - WriteRuntime(os.path.join(blender_bin_dir, 'blenderplayer' + ext), - os.path.join(ps.output_path, 'default', ps.runtime_name), - ps.asset_paths, - True, - True, - True, - ps.make_archive, - self.report - ) - else: - print("Skipping default platform") - - for platform in ps.platforms: - if platform.publish: - print("Publishing", platform.name) - WriteRuntime(platform.player_path, - os.path.join(ps.output_path, platform.name, ps.runtime_name), - ps.asset_paths, - True, - True, - True, - ps.make_archive, - self.report - ) - else: - print("Skipping", platform.name) - - return {'FINISHED'} - - -class RENDER_UL_assets(bpy.types.UIList): - bl_label = "Asset Paths Listing" - - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - layout.prop(item, "name", text="", emboss=False) - - -class RENDER_UL_platforms(bpy.types.UIList): - bl_label = "Platforms Listing" - - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - row = layout.row() - row.label(item.name) - row.prop(item, "publish", text="") - - -class RENDER_PT_publish(bpy.types.Panel): - bl_label = "Publishing Info" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - - @classmethod - def poll(cls, context): - scene = context.scene - return scene and (scene.render.engine == "BLENDER_GAME") - - def draw(self, context): - ps = context.scene.ge_publish_settings - layout = self.layout - - # config - layout.prop(ps, 'output_path') - layout.prop(ps, 'runtime_name') - layout.prop(ps, 'lib_path') - layout.prop(ps, 'make_archive') - - layout.separator() - - # assets list - layout.label("Asset Paths") - - # UI_UL_list - row = layout.row() - row.template_list("RENDER_UL_assets", "assets_list", ps, 'asset_paths', ps, 'asset_paths_active') - - # operators - col = row.column(align=True) - col.operator(PublishAddAssetPath.bl_idname, icon='ZOOMIN', text="") - col.operator(PublishRemoveAssetPath.bl_idname, icon='ZOOMOUT', text="") - - # indexing - if len(ps.asset_paths) > ps.asset_paths_active >= 0: - ap = ps.asset_paths[ps.asset_paths_active] - row = layout.row() - row.prop(ap, 'overwrite') - - layout.separator() - - # publishing list - row = layout.row(align=True) - row.label("Platforms") - row.prop(ps, 'publish_default_platform') - - # UI_UL_list - row = layout.row() - row.template_list("RENDER_UL_platforms", "platforms_list", ps, 'platforms', ps, 'platforms_active') - - # operators - col = row.column(align=True) - col.operator(PublishAddPlatform.bl_idname, icon='ZOOMIN', text="") - col.operator(PublishRemovePlatform.bl_idname, icon='ZOOMOUT', text="") - col.menu("PUBLISH_MT_platform_specials", icon='DOWNARROW_HLT', text="") - - # indexing - if len(ps.platforms) > ps.platforms_active >= 0: - platform = ps.platforms[ps.platforms_active] - layout.prop(platform, 'name') - layout.prop(platform, 'player_path') - - layout.operator(PublishAllPlatforms.bl_idname, 'Publish Platforms') - - -class PublishAutoPlatforms(bpy.types.Operator): - bl_idname = "scene.publish_auto_platforms" - bl_label = "Auto Add Platforms" - - def execute(self, context): - ps = context.scene.ge_publish_settings - - # verify lib folder - lib_path = bpy.path.abspath(ps.lib_path) - if not os.path.exists(lib_path): - self.report({'ERROR'}, "Could not add platforms, lib folder (%s) does not exist" % lib_path) - return {'CANCELLED'} - - for lib in [i for i in os.listdir(lib_path) if os.path.isdir(os.path.join(lib_path, i))]: - print("Found folder:", lib) - player_found = False - for root, dirs, files in os.walk(os.path.join(lib_path, lib)): - if "__MACOSX" in root: - continue - - for f in dirs + files: - if f.startswith("blenderplayer.app") or f.startswith("blenderplayer"): - a = ps.platforms.add() - if lib.startswith('blender-'): - # Clean up names for packages from blender.org - # example: blender-2.71-RC2-OSX_10.6-x86_64.zip => OSX_10.6-x86_64.zip - # We're pretty consistent on naming, so this should hold up. - a.name = '-'.join(lib.split('-')[3 if 'rc' in lib.lower() else 2:]) - else: - a.name = lib - a.player_path = bpy.path.relpath(os.path.join(root, f)) - player_found = True - break - - if player_found: - break - - return {'FINISHED'} - -# TODO This operator takes a long time to run, which is bad for UX. Could this instead be done as some sort of -# modal dialog? This could also allow users to select which platforms to download and give a better progress -# indicator. -class PublishDownloadPlatforms(bpy.types.Operator): - bl_idname = "scene.publish_download_platforms" - bl_label = "Download Platforms" - - def execute(self, context): - import html.parser - import urllib.request - - remote_platforms = [] - - ps = context.scene.ge_publish_settings - - # create lib folder if not already available - lib_path = bpy.path.abspath(ps.lib_path) - if not os.path.exists(lib_path): - os.makedirs(lib_path) - - print("Retrieving list of platforms from blender.org...", end=" ", flush=True) - - class AnchorParser(html.parser.HTMLParser): - def handle_starttag(self, tag, attrs): - if tag == 'a': - for key, value in attrs: - if key == 'href' and value.startswith('blender'): - remote_platforms.append(value) - - url = 'http://download.blender.org/release/Blender' + bpy.app.version_string.split()[0] - parser = AnchorParser() - data = urllib.request.urlopen(url).read() - parser.feed(str(data)) - - print("done", flush=True) - - print("Downloading files (this will take a while depending on your internet connection speed).", flush=True) - for i in remote_platforms: - src = '/'.join((url, i)) - dst = os.path.join(lib_path, i) - - dst_dir = '.'.join([i for i in dst.split('.') if i not in {'zip', 'tar', 'bz2'}]) - if not os.path.exists(dst) and not os.path.exists(dst.split('.')[0]): - print("Downloading " + src + "...", end=" ", flush=True) - urllib.request.urlretrieve(src, dst) - print("done", flush=True) - else: - print("Reusing existing file: " + dst, flush=True) - - print("Unpacking " + dst + "...", end=" ", flush=True) - if os.path.exists(dst_dir): - shutil.rmtree(dst_dir) - shutil.unpack_archive(dst, dst_dir) - print("done", flush=True) - - print("Creating platform from libs...", flush=True) - bpy.ops.scene.publish_auto_platforms() - return {'FINISHED'} - - -class PublishAddPlatform(bpy.types.Operator): - bl_idname = "scene.publish_add_platform" - bl_label = "Add Publish Platform" - - def execute(self, context): - a = context.scene.ge_publish_settings.platforms.add() - a.name = a.name - return {'FINISHED'} - - -class PublishRemovePlatform(bpy.types.Operator): - bl_idname = "scene.publish_remove_platform" - bl_label = "Remove Publish Platform" - - def execute(self, context): - ps = context.scene.ge_publish_settings - if ps.platforms_active < len(ps.platforms): - ps.platforms.remove(ps.platforms_active) - return {'FINISHED'} - return {'CANCELLED'} - - -# TODO maybe this should display a file browser? -class PublishAddAssetPath(bpy.types.Operator): - bl_idname = "scene.publish_add_assetpath" - bl_label = "Add Asset Path" - - def execute(self, context): - a = context.scene.ge_publish_settings.asset_paths.add() - a.name = a.name - return {'FINISHED'} - - -class PublishRemoveAssetPath(bpy.types.Operator): - bl_idname = "scene.publish_remove_assetpath" - bl_label = "Remove Asset Path" - - def execute(self, context): - ps = context.scene.ge_publish_settings - if ps.asset_paths_active < len(ps.asset_paths): - ps.asset_paths.remove(ps.asset_paths_active) - return {'FINISHED'} - return {'CANCELLED'} - - -class PUBLISH_MT_platform_specials(bpy.types.Menu): - bl_label = "Platform Specials" - - def draw(self, context): - layout = self.layout - layout.operator(PublishAutoPlatforms.bl_idname) - layout.operator(PublishDownloadPlatforms.bl_idname) - - -class PlatformSettings(bpy.types.PropertyGroup): - name = bpy.props.StringProperty( - name = "Platform Name", - description = "The name of the platform", - default = "Platform", - ) - - player_path = bpy.props.StringProperty( - name = "Player Path", - description = "The path to the Blenderplayer to use for this platform", - default = "//lib/platform/blenderplayer", - subtype = 'FILE_PATH', - ) - - publish = bpy.props.BoolProperty( - name = "Publish", - description = "Whether or not to publish to this platform", - default = True, - ) - - -class AssetPath(bpy.types.PropertyGroup): - # TODO This needs a way to be a FILE_PATH or a DIR_PATH - name = bpy.props.StringProperty( - name = "Asset Path", - description = "Path to the asset to be copied", - default = "//src", - subtype = 'FILE_PATH', - ) - - overwrite = bpy.props.BoolProperty( - name = "Overwrite Asset", - description = "Overwrite the asset if it already exists in the destination folder", - default = True, - ) - - -class PublishSettings(bpy.types.PropertyGroup): - output_path = bpy.props.StringProperty( - name = "Publish Output", - description = "Where to publish the game", - default = "//bin/", - subtype = 'DIR_PATH', - ) - - runtime_name = bpy.props.StringProperty( - name = "Runtime name", - description = "The filename for the created runtime", - default = "game", - ) - - lib_path = bpy.props.StringProperty( - name = "Library Path", - description = "Directory to search for platforms", - default = "//lib/", - subtype = 'DIR_PATH', - ) - - publish_default_platform = bpy.props.BoolProperty( - name = "Publish Default Platform", - description = "Whether or not to publish the default platform (the Blender install running this addon) when publishing platforms", - default = True, - ) - - - platforms = bpy.props.CollectionProperty(type=PlatformSettings, name="Platforms") - platforms_active = bpy.props.IntProperty() - - asset_paths = bpy.props.CollectionProperty(type=AssetPath, name="Asset Paths") - asset_paths_active = bpy.props.IntProperty() - - make_archive = bpy.props.BoolProperty( - name = "Make Archive", - description = "Create a zip archive of the published game", - default = True, - ) - - -def register(): - bpy.utils.register_module(__name__) - - bpy.types.Scene.ge_publish_settings = bpy.props.PointerProperty(type=PublishSettings) - - -def unregister(): - bpy.utils.unregister_module(__name__) - del bpy.types.Scene.ge_publish_settings - - -if __name__ == "__main__": - register() diff --git a/game_engine_save_as_runtime.py b/game_engine_save_as_runtime.py deleted file mode 100644 index 25e47d94..00000000 --- a/game_engine_save_as_runtime.py +++ /dev/null @@ -1,258 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -bl_info = { - "name": "Save As Game Engine Runtime", - "author": "Mitchell Stokes (Moguri)", - "version": (0, 3, 1), - "blender": (2, 61, 0), - "location": "File > Export", - "description": "Bundle a .blend file with the Blenderplayer", - "warning": "", - "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Game_Engine/Save_As_Runtime", - "category": "Game Engine", -} - -import bpy -import os -import sys -import shutil -import tempfile - - -def CopyPythonLibs(dst, overwrite_lib, report=print): - import platform - - # use python module to find pytohn's libpath - src = os.path.dirname(platform.__file__) - - # dst points to lib/, but src points to current python's library path, eg: - # '/usr/lib/python3.2' vs '/usr/lib' - # append python's library dir name to destination, so only python's - # libraries would be copied - if os.name == 'posix': - dst = os.path.join(dst, os.path.basename(src)) - - if os.path.exists(src): - write = False - if os.path.exists(dst): - if overwrite_lib: - shutil.rmtree(dst) - write = True - else: - write = True - if write: - shutil.copytree(src, dst, ignore=lambda dir, contents: [i for i in contents if i == '__pycache__']) - else: - report({'WARNING'}, "Python not found in %r, skipping pythn copy" % src) - - -def WriteAppleRuntime(player_path, output_path, copy_python, overwrite_lib): - # Enforce the extension - if not output_path.endswith('.app'): - output_path += '.app' - - # Use the system's cp command to preserve some meta-data - os.system('cp -R "%s" "%s"' % (player_path, output_path)) - - bpy.ops.wm.save_as_mainfile(filepath=os.path.join(output_path, "Contents/Resources/game.blend"), - relative_remap=False, - compress=False, - copy=True, - ) - - # Python doesn't need to be copied for OS X since it's already inside blenderplayer.app - - -def WriteRuntime(player_path, output_path, copy_python, overwrite_lib, copy_dlls, report=print): - import struct - - # Check the paths - if not os.path.isfile(player_path) and not(os.path.exists(player_path) and player_path.endswith('.app')): - report({'ERROR'}, "The player could not be found! Runtime not saved") - return - - # Check if we're bundling a .app - if player_path.endswith('.app'): - WriteAppleRuntime(player_path, output_path, copy_python, overwrite_lib) - return - - # Enforce "exe" extension on Windows - if player_path.endswith('.exe') and not output_path.endswith('.exe'): - output_path += '.exe' - - # Get the player's binary and the offset for the blend - file = open(player_path, 'rb') - player_d = file.read() - offset = file.tell() - file.close() - - # Create a tmp blend file (Blenderplayer doesn't like compressed blends) - tempdir = tempfile.mkdtemp() - blend_path = os.path.join(tempdir, bpy.path.clean_name(output_path)) - bpy.ops.wm.save_as_mainfile(filepath=blend_path, - relative_remap=False, - compress=False, - copy=True, - ) - - # Get the blend data - blend_file = open(blend_path, 'rb') - blend_d = blend_file.read() - blend_file.close() - - # Get rid of the tmp blend, we're done with it - os.remove(blend_path) - os.rmdir(tempdir) - - # Create a new file for the bundled runtime - output = open(output_path, 'wb') - - # Write the player and blend data to the new runtime - print("Writing runtime...", end=" ") - output.write(player_d) - output.write(blend_d) - - # Store the offset (an int is 4 bytes, so we split it up into 4 bytes and save it) - output.write(struct.pack('B', (offset>>24)&0xFF)) - output.write(struct.pack('B', (offset>>16)&0xFF)) - output.write(struct.pack('B', (offset>>8)&0xFF)) - output.write(struct.pack('B', (offset>>0)&0xFF)) - - # Stuff for the runtime - output.write(b'BRUNTIME') - output.close() - - print("done") - - # Make the runtime executable on Linux - if os.name == 'posix': - os.chmod(output_path, 0o755) - - # Copy bundled Python - blender_dir = os.path.dirname(bpy.app.binary_path) - runtime_dir = os.path.dirname(output_path) - - if copy_python: - print("Copying Python files...", end=" ") - py_folder = os.path.join(bpy.app.version_string.split()[0], "python", "lib") - dst = os.path.join(runtime_dir, py_folder) - CopyPythonLibs(dst, overwrite_lib, report) - print("done") - - # And DLLs - if copy_dlls: - print("Copying DLLs...", end=" ") - for file in [i for i in os.listdir(blender_dir) if i.lower().endswith('.dll')]: - src = os.path.join(blender_dir, file) - dst = os.path.join(runtime_dir, file) - shutil.copy2(src, dst) - - print("done") - -from bpy.props import * - - -class SaveAsRuntime(bpy.types.Operator): - bl_idname = "wm.save_as_runtime" - bl_label = "Save As Game Engine Runtime" - bl_options = {'REGISTER'} - - if sys.platform == 'darwin': - # XXX, this line looks suspicious, could be done better? - blender_bin_dir = '/' + os.path.join(*bpy.app.binary_path.split('/')[0:-4]) - ext = '.app' - else: - blender_bin_path = bpy.app.binary_path - blender_bin_dir = os.path.dirname(blender_bin_path) - ext = os.path.splitext(blender_bin_path)[-1].lower() - - default_player_path = os.path.join(blender_bin_dir, 'blenderplayer' + ext) - player_path = StringProperty( - name="Player Path", - description="The path to the player to use", - default=default_player_path, - subtype='FILE_PATH', - ) - filepath = StringProperty( - subtype='FILE_PATH', - ) - copy_python = BoolProperty( - name="Copy Python", - description="Copy bundle Python with the runtime", - default=True, - ) - overwrite_lib = BoolProperty( - name="Overwrite 'lib' folder", - description="Overwrites the lib folder (if one exists) with the bundled Python lib folder", - default=False, - ) - - # Only Windows has dlls to copy - if ext == '.exe': - copy_dlls = BoolProperty( - name="Copy DLLs", - description="Copy all needed DLLs with the runtime", - default=True, - ) - else: - copy_dlls = False - - def execute(self, context): - import time - start_time = time.clock() - print("Saving runtime to %r" % self.filepath) - WriteRuntime(self.player_path, - self.filepath, - self.copy_python, - self.overwrite_lib, - self.copy_dlls, - self.report, - ) - print("Finished in %.4fs" % (time.clock()-start_time)) - return {'FINISHED'} - - def invoke(self, context, event): - if not self.filepath: - ext = '.app' if sys.platform == 'darwin' else os.path.splitext(bpy.app.binary_path)[-1] - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ext) - - wm = context.window_manager - wm.fileselect_add(self) - return {'RUNNING_MODAL'} - - -def menu_func(self, context): - self.layout.operator(SaveAsRuntime.bl_idname) - - -def register(): - bpy.utils.register_module(__name__) - - bpy.types.INFO_MT_file_export.append(menu_func) - - -def unregister(): - bpy.utils.unregister_module(__name__) - - bpy.types.INFO_MT_file_export.remove(menu_func) - - -if __name__ == "__main__": - register() diff --git a/io_anim_acclaim/__init__.py b/io_anim_acclaim/__init__.py index 620c1fc4..7cada817 100644 --- a/io_anim_acclaim/__init__.py +++ b/io_anim_acclaim/__init__.py @@ -189,7 +189,7 @@ class StructureBuilder(DataStructure): self.armature = self.object.data self.object.name = self.name self.armature.name = self.name - self.armature.draw_type = 'STICK' + self.armature.display_type = 'STICK' self.object['source_file_path'] = self.file_path self.object['source_scale'] = self.user_def_scale self.object['MhxArmature'] = 'Daz' @@ -530,16 +530,16 @@ def menu_func_me(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_func_s) - bpy.types.INFO_MT_file_import.append(menu_func_mi) - bpy.types.INFO_MT_file_export.append(menu_func_me) + bpy.types.TOPBAR_MT_file_import.append(menu_func_s) + bpy.types.TOPBAR_MT_file_import.append(menu_func_mi) + bpy.types.TOPBAR_MT_file_export.append(menu_func_me) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_func_s) - bpy.types.INFO_MT_file_import.remove(menu_func_mi) - bpy.types.INFO_MT_file_export.remove(menu_func_me) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_s) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_mi) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_me) if __name__ == "__main__": diff --git a/io_anim_bvh/__init__.py b/io_anim_bvh/__init__.py index 4a4983ff..dd9c2037 100644 --- a/io_anim_bvh/__init__.py +++ b/io_anim_bvh/__init__.py @@ -22,7 +22,7 @@ bl_info = { "name": "BioVision Motion Capture (BVH) format", "author": "Campbell Barton", "version": (1, 0, 0), - "blender": (2, 74, 0), + "blender": (2, 80, 0), "location": "File > Import-Export", "description": "Import-Export BVH from armature objects", "warning": "", @@ -52,24 +52,22 @@ from bpy.props import ( from bpy_extras.io_utils import ( ImportHelper, ExportHelper, - orientation_helper_factory, + orientation_helper, axis_conversion, ) -ImportBVHOrientationHelper = orientation_helper_factory("ImportBVHOrientationHelper", axis_forward='-Z', axis_up='Y') - - -class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper): +@orientation_helper(axis_forward='-Z', axis_up='Y') +class ImportBVH(bpy.types.Operator, ImportHelper): """Load a BVH motion capture file""" bl_idname = "import_anim.bvh" bl_label = "Import BVH" bl_options = {'REGISTER', 'UNDO'} filename_ext = ".bvh" - filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.bvh", options={'HIDDEN'}) - target = EnumProperty( + target: EnumProperty( items=( ('ARMATURE', "Armature", ""), ('OBJECT', "Object", ""), @@ -78,20 +76,19 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper): description="Import target type", default='ARMATURE', ) - - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", description="Scale the BVH by this value", min=0.0001, max=1000000.0, soft_min=0.001, soft_max=100.0, default=1.0, ) - frame_start = IntProperty( + frame_start: IntProperty( name="Start Frame", description="Starting frame for the animation", default=1, ) - use_fps_scale = BoolProperty( + use_fps_scale: BoolProperty( name="Scale FPS", description=( "Scale the framerate from the BVH to the current scenes, " @@ -99,25 +96,25 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper): ), default=False, ) - update_scene_fps = BoolProperty( + update_scene_fps: BoolProperty( name="Update Scene FPS", description=( "Set the scene framerate to that of the BVH file (note that this " "nullifies the 'Scale FPS' option, as the scale will be 1:1)" ), - default=False + default=False, ) - update_scene_duration = BoolProperty( + update_scene_duration: BoolProperty( name="Update Scene Duration", description="Extend the scene's duration to the BVH duration (never shortens the scene)", default=False, ) - use_cyclic = BoolProperty( + use_cyclic: BoolProperty( name="Loop", description="Loop the animation playback", default=False, ) - rotate_mode = EnumProperty( + rotate_mode: EnumProperty( name="Rotation", description="Rotation conversion", items=( @@ -143,7 +140,6 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper): "filter_glob", ) ) - global_matrix = axis_conversion( from_forward=self.axis_forward, from_up=self.axis_up, @@ -161,29 +157,29 @@ class ExportBVH(bpy.types.Operator, ExportHelper): bl_label = "Export BVH" filename_ext = ".bvh" - filter_glob = StringProperty( + filter_glob: StringProperty( default="*.bvh", options={'HIDDEN'}, ) - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", description="Scale the BVH by this value", min=0.0001, max=1000000.0, soft_min=0.001, soft_max=100.0, default=1.0, ) - frame_start = IntProperty( + frame_start: IntProperty( name="Start Frame", description="Starting frame to export", default=0, ) - frame_end = IntProperty( + frame_end: IntProperty( name="End Frame", description="End frame to export", default=0, ) - rotate_mode = EnumProperty( + rotate_mode: EnumProperty( name="Rotation", description="Rotation conversion", items=( @@ -198,7 +194,7 @@ class ExportBVH(bpy.types.Operator, ExportHelper): ), default='NATIVE', ) - root_transform_only = BoolProperty( + root_transform_only: BoolProperty( name="Root Translation Only", description="Only write out translation channels for the root bone", default=False, @@ -220,7 +216,14 @@ class ExportBVH(bpy.types.Operator, ExportHelper): self.frame_start = context.scene.frame_start self.frame_end = context.scene.frame_end - keywords = self.as_keywords(ignore=("check_existing", "filter_glob")) + keywords = self.as_keywords( + ignore=( + "axis_forward", + "axis_up", + "check_existing", + "filter_glob", + ) + ) from . import export_bvh return export_bvh.save(context, **keywords) @@ -234,19 +237,25 @@ def menu_func_export(self, context): self.layout.operator(ExportBVH.bl_idname, text="Motion Capture (.bvh)") +classes = ( + ImportBVH, + ExportBVH +) + def register(): - bpy.utils.register_module(__name__) + for cls in classes: + bpy.utils.register_class(cls) - bpy.types.INFO_MT_file_import.append(menu_func_import) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): - bpy.utils.unregister_module(__name__) - - bpy.types.INFO_MT_file_import.remove(menu_func_import) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + for cls in classes: + bpy.utils.unregister_class(cls) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) if __name__ == "__main__": register() diff --git a/io_anim_bvh/export_bvh.py b/io_anim_bvh/export_bvh.py index 6a0c210f..a8b589df 100644 --- a/io_anim_bvh/export_bvh.py +++ b/io_anim_bvh/export_bvh.py @@ -255,12 +255,12 @@ def write_armature( itrans = Matrix.Translation(-dbone.rest_bone.head_local) if dbone.parent: - mat_final = dbone.parent.rest_arm_mat * dbone.parent.pose_imat * dbone.pose_mat * dbone.rest_arm_imat - mat_final = itrans * mat_final * trans + mat_final = dbone.parent.rest_arm_mat @ dbone.parent.pose_imat @ dbone.pose_mat @ dbone.rest_arm_imat + mat_final = itrans @ mat_final @ trans loc = mat_final.to_translation() + (dbone.rest_bone.head_local - dbone.parent.rest_bone.head_local) else: - mat_final = dbone.pose_mat * dbone.rest_arm_imat - mat_final = itrans * mat_final * trans + mat_final = dbone.pose_mat @ dbone.rest_arm_imat + mat_final = itrans @ mat_final @ trans loc = mat_final.to_translation() + dbone.rest_bone.head # keep eulers compatible, no jumping on interpolation. diff --git a/io_anim_bvh/import_bvh.py b/io_anim_bvh/import_bvh.py index 1f3c93fe..f609ca6c 100644 --- a/io_anim_bvh/import_bvh.py +++ b/io_anim_bvh/import_bvh.py @@ -348,19 +348,19 @@ def bvh_node_dict2objects(context, bvh_name, bvh_nodes, rotate_mode='NATIVE', fr scene = context.scene for obj in scene.objects: - obj.select = False + obj.select_set(False) objects = [] def add_ob(name): obj = bpy.data.objects.new(name, None) - scene.objects.link(obj) + context.collection.objects.link(obj) objects.append(obj) - obj.select = True + obj.select_set(True) # nicer drawing. - obj.empty_draw_type = 'CUBE' - obj.empty_draw_size = 0.1 + obj.empty_display_type = 'CUBE' + obj.empty_display_size = 0.1 return obj @@ -422,15 +422,15 @@ def bvh_node_dict2armature( # Add the new armature, scene = context.scene for obj in scene.objects: - obj.select = False + obj.select_set(False) arm_data = bpy.data.armatures.new(bvh_name) arm_ob = bpy.data.objects.new(bvh_name, arm_data) - scene.objects.link(arm_ob) + context.collection.objects.link(arm_ob) - arm_ob.select = True - scene.objects.active = arm_ob + arm_ob.select_set(True) + context.view_layer.objects.active = arm_ob bpy.ops.object.mode_set(mode='OBJECT', toggle=False) bpy.ops.object.mode_set(mode='EDIT', toggle=False) @@ -580,7 +580,7 @@ def bvh_node_dict2armature( bone_translate_matrix = Matrix.Translation( Vector(bvh_loc) - bvh_node.rest_head_local) - location[frame_i] = (bone_rest_matrix_inv * + location[frame_i] = (bone_rest_matrix_inv @ bone_translate_matrix).to_translation() # For each location x, y, z. @@ -617,8 +617,8 @@ def bvh_node_dict2armature( euler = Euler(bvh_rot, bvh_node.rot_order_str[::-1]) bone_rotation_matrix = euler.to_matrix().to_4x4() bone_rotation_matrix = ( - bone_rest_matrix_inv * - bone_rotation_matrix * + bone_rest_matrix_inv @ + bone_rotation_matrix @ bone_rest_matrix ) diff --git a/io_anim_c3d/__init__.py b/io_anim_c3d/__init__.py index 2c9cd0d6..e14fcaec 100644 --- a/io_anim_c3d/__init__.py +++ b/io_anim_c3d/__init__.py @@ -272,7 +272,7 @@ class C3DImporter(bpy.types.Operator): unames[name] = o.name bpy.ops.transform.resize(value=empty_size) o.show_name = self.properties.show_names - o.show_x_ray = self.properties.x_ray + o.show_in_front = self.properties.x_ray for name in unames.values(): bpy.context.scene.objects[name].select = True return unames @@ -291,7 +291,7 @@ class C3DImporter(bpy.types.Operator): arm = bpy.context.active_object arm.name = os.path.basename(self.properties.filepath) arm.data.show_names = self.properties.show_names - arm.show_x_ray = self.properties.x_ray + arm.show_in_front = self.properties.x_ray for idx, ml in enumerate(ms.markerLabels): name = self.properties.prefix + ml bpy.ops.armature.select_all(action='DESELECT') @@ -354,12 +354,12 @@ def menu_func(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_func) + bpy.types.TOPBAR_MT_file_import.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_func) + bpy.types.TOPBAR_MT_file_import.remove(menu_func) if __name__ == "__main__": diff --git a/io_anim_camera.py b/io_anim_camera.py index 20cb5395..b382a32d 100644 --- a/io_anim_camera.py +++ b/io_anim_camera.py @@ -22,7 +22,7 @@ bl_info = { "name": "Export Camera Animation", "author": "Campbell Barton", "version": (0, 1), - "blender": (2, 57, 0), + "blender": (2, 80, 0), "location": "File > Export > Cameras & Markers (.py)", "description": "Export Cameras & Markers (.py)", "warning": "", @@ -45,7 +45,7 @@ def write_cameras(context, filepath, frame_start, frame_end, only_selected=False 'dof_distance', 'clip_start', 'clip_end', - 'draw_size', + 'display_size', ) obj_attrs = ( @@ -59,7 +59,7 @@ def write_cameras(context, filepath, frame_start, frame_end, only_selected=False cameras = [] for obj in scene.objects: - if only_selected and not obj.select: + if only_selected and not obj.select_get(): continue if obj.type != 'CAMERA': continue @@ -84,7 +84,7 @@ def write_cameras(context, filepath, frame_start, frame_end, only_selected=False for attr in obj_attrs: fw("obj.%s = %s\n" % (attr, repr(getattr(obj, attr)))) - fw("scene.objects.link(obj)\n") + fw("bpy.context.collection.objects.link(obj)\n") fw("cameras[%r] = obj\n" % obj.name) fw("\n") @@ -134,15 +134,15 @@ class CameraExporter(bpy.types.Operator, ExportHelper): bl_label = "Export Camera & Markers" filename_ext = ".py" - filter_glob = StringProperty(default="*.py", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.py", options={'HIDDEN'}) - frame_start = IntProperty(name="Start Frame", + frame_start: IntProperty(name="Start Frame", description="Start frame for export", default=1, min=1, max=300000) - frame_end = IntProperty(name="End Frame", + frame_end: IntProperty(name="End Frame", description="End frame for export", default=250, min=1, max=300000) - only_selected = BoolProperty(name="Only Selected", + only_selected: BoolProperty(name="Only Selected", default=True) def execute(self, context): @@ -165,15 +165,13 @@ def menu_export(self, context): def register(): - bpy.utils.register_module(__name__) - - bpy.types.INFO_MT_file_export.append(menu_export) + bpy.utils.register_class(CameraExporter) + bpy.types.TOPBAR_MT_file_export.append(menu_export) def unregister(): - bpy.utils.unregister_module(__name__) - - bpy.types.INFO_MT_file_export.remove(menu_export) + bpy.utils.unregister_class(CameraExporter) + bpy.types.TOPBAR_MT_file_export.remove(menu_export) if __name__ == "__main__": diff --git a/io_anim_nuke_chan/__init__.py b/io_anim_nuke_chan/__init__.py index b3102329..9266c439 100644 --- a/io_anim_nuke_chan/__init__.py +++ b/io_anim_nuke_chan/__init__.py @@ -143,15 +143,15 @@ def menu_func_export(self, context): def register(): bpy.utils.register_class(ImportChan) bpy.utils.register_class(ExportChan) - bpy.types.INFO_MT_file_import.append(menu_func_import) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): bpy.utils.unregister_class(ImportChan) bpy.utils.unregister_class(ExportChan) - bpy.types.INFO_MT_file_import.remove(menu_func_import) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) if __name__ == "__main__": diff --git a/io_blend_utils/__init__.py b/io_blend_utils/__init__.py index d44d4754..9d102102 100644 --- a/io_blend_utils/__init__.py +++ b/io_blend_utils/__init__.py @@ -135,14 +135,14 @@ def register(): for cls in classes: bpy.utils.register_class(cls) - bpy.types.INFO_MT_file_external_data.append(menu_func) + bpy.types.TOPBAR_MT_file_external_data.append(menu_func) def unregister(): for cls in classes: bpy.utils.unregister_class(cls) - bpy.types.INFO_MT_file_external_data.remove(menu_func) + bpy.types.TOPBAR_MT_file_external_data.remove(menu_func) if __name__ == "__main__": diff --git a/io_blend_utils/blend/blendfile_path_walker.py b/io_blend_utils/blend/blendfile_path_walker.py index df07235e..ef499604 100644 --- a/io_blend_utils/blend/blendfile_path_walker.py +++ b/io_blend_utils/blend/blendfile_path_walker.py @@ -726,7 +726,7 @@ class ExpandID: del dup_group yield block.get_pointer(b'proxy') - yield block.get_pointer(b'proxy_group') + yield block.get_pointer(b'proxy_collection') if USE_ALEMBIC_BRANCH: if has_dup_group: diff --git a/io_blend_utils/blender_bam-unpacked.whl/bam/blend/blendfile_path_walker.py b/io_blend_utils/blender_bam-unpacked.whl/bam/blend/blendfile_path_walker.py index df07235e..ef499604 100644 --- a/io_blend_utils/blender_bam-unpacked.whl/bam/blend/blendfile_path_walker.py +++ b/io_blend_utils/blender_bam-unpacked.whl/bam/blend/blendfile_path_walker.py @@ -726,7 +726,7 @@ class ExpandID: del dup_group yield block.get_pointer(b'proxy') - yield block.get_pointer(b'proxy_group') + yield block.get_pointer(b'proxy_collection') if USE_ALEMBIC_BRANCH: if has_dup_group: diff --git a/io_coat3D/__init__.py b/io_coat3D/__init__.py index b5539ede..7c16e22a 100644 --- a/io_coat3D/__init__.py +++ b/io_coat3D/__init__.py @@ -19,8 +19,8 @@ bl_info = { "name": "3D-Coat Applink", "author": "Kalle-Samuli Riihikoski (haikalle)", - "version": (3, 5, 22), - "blender": (2, 59, 0), + "version": (5, 0, 00), + "blender": (2, 80, 0), "location": "Scene > 3D-Coat Applink", "description": "Transfer data between 3D-Coat/Blender", "warning": "", @@ -35,10 +35,16 @@ if "bpy" in locals(): importlib.reload(coat) importlib.reload(tex) else: - from . import coat from . import tex +from io_coat3D import tex +import os +import ntpath +import re + +import time import bpy +import subprocess from bpy.types import PropertyGroup from bpy.props import ( BoolProperty, @@ -49,244 +55,1257 @@ from bpy.props import ( ) +bpy.coat3D = dict() +bpy.coat3D['active_coat'] = '' +bpy.coat3D['status'] = 0 + +def update_exe_path(): + if (bpy.context.scene.coat3D.coat3D_exe != ''): + importfile = bpy.context.scene.coat3D.exchangedir + importfile += ('%scoat3D_exe.txt' % (os.sep)) + file = open(importfile, "w") + file.write("%s" % (bpy.context.scene.coat3D.coat3D_exe)) + file.close() + +def folder_size(path): + + tosi = True + while tosi: + list_of_files = [] + for file in os.listdir(path): + list_of_files.append(path + os.sep + file) + + if len(list_of_files) >= 400: + oldest_file = min(list_of_files, key=os.path.getctime) + os.remove(os.path.abspath(oldest_file)) + else: + tosi = False + +def set_exchange_folder(): + platform = os.sys.platform + coat3D = bpy.context.scene.coat3D + Blender_export = "" + + if(platform == 'win32'): + exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV48' + os.sep +'Exchange' + if not(os.path.isdir(exchange)): + exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV4' + os.sep +'Exchange' + if not (os.path.isdir(exchange)): + exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV3' + os.sep + 'Exchange' + + else: + exchange = os.path.expanduser("~") + os.sep + '3D-CoatV4' + os.sep + 'Exchange' + if not(os.path.isdir(exchange)): + exchange = os.path.expanduser("~") + os.sep + '3D-CoatV3' + os.sep + 'Exchange' + if(not(os.path.isdir(exchange))): + exchange = coat3D.exchangedir + + if(os.path.isdir(exchange)): + bpy.coat3D['status'] = 1 + if(platform == 'win32'): + exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt' + applink_folder = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + if(not(os.path.isdir(applink_folder))): + os.makedirs(applink_folder) + else: + exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt' + applink_folder = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + if(not(os.path.isdir(applink_folder))): + os.makedirs(applink_folder) + file = open(exchange_path, "w") + file.write("%s"%(coat3D.exchangedir)) + file.close() + + else: + if(platform == 'win32'): + exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt' + else: + exchange_path = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt' + if(os.path.isfile(exchange_path)): + ex_path ='' + + ex_pathh = open(exchange_path) + for line in ex_pathh: + ex_path = line + break + ex_pathh.close() + + if(os.path.isdir(ex_path) and ex_path.rfind('Exchange') >= 0): + exchange = ex_path + bpy.coat3D['status'] = 1 + else: + bpy.coat3D['status'] = 0 + else: + bpy.coat3D['status'] = 0 + if(bpy.coat3D['status'] == 1): + Blender_folder = ("%s%sBlender"%(exchange,os.sep)) + Blender_export = Blender_folder + path3b_now = exchange + path3b_now += ('last_saved_3b_file.txt') + Blender_export += ('%sexport.txt'%(os.sep)) + + if(not(os.path.isdir(Blender_folder))): + os.makedirs(Blender_folder) + Blender_folder = os.path.join(Blender_folder,"run.txt") + file = open(Blender_folder, "w") + file.close() + return exchange + +def set_working_folders(): + platform = os.sys.platform + coat3D = bpy.context.scene.coat3D + if(platform == 'win32'): + folder_objects = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'ApplinkObjects' + if(not(os.path.isdir(folder_objects))): + os.makedirs(folder_objects) + else: + folder_objects = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'ApplinkObjects' + if(not(os.path.isdir(folder_objects))): + os.makedirs(folder_objects) + + return folder_objects + +def make_texture_list(texturefolder): + texturefolder += ('%stextures.txt'%(os.sep)) + texturelist = [] + + if (os.path.isfile(texturefolder)): + texturefile = open(texturefolder) + index = 0 + for line in texturefile: + if line != '' and index == 0: + line = line.rstrip('\n') + objekti = line + index += 1 + elif index == 1: + line = line.rstrip('\n') + material = line + index += 1 + elif index == 2: + line = line.rstrip('\n') + type = line + index += 1 + elif index == 3: + line = line.rstrip('\n') + address = line + texturelist.append([objekti,material,type,address]) + index = 0 + texturefile.close() + return texturelist + + +''' +#Updating objects MESH part ( Mesh, Vertex Groups, Vertex Colors ) +''' + +def updatemesh(objekti, proxy): + + + #TO DO VERTEX GROUPS, gives an error with this code. + + if(objekti.vertex_groups.keys() != []): + bpy.ops.object.select_all(action='DESELECT') + proxy.select_set(True) + objekti.select_set(True) + bpy.ops.object.vertex_group_copy_to_selected() + bpy.ops.object.select_all(action='DESELECT') + + # UV Set Copy + + proxy.select_set(True) + objekti.select_set(True) + + if len(objekti.data.uv_layers) > 1: + obj_uv_index = objekti.data.uv_layers.active_index + index = 0 + for uv_layer in objekti.data.uv_layers: + if (uv_layer != objekti.data.uv_layers[0]): + proxy.data.uv_layers.new(name=uv_layer.name) + proxy.data.uv_layers.active_index = index + objekti.data.uv_layers.active_index = index + bpy.ops.object.join_uvs() + index += 1 + proxy.data.uv_layers.active_index = obj_uv_index + + bpy.ops.object.select_all(action='DESELECT') + + #Mesh Copy + + proxy.select_set(True) + obj_data = objekti.data.id_data + objekti.data = proxy.data.id_data + objekti.data.id_data.name = obj_data.name + if (bpy.data.meshes[obj_data.name].users == 0): + bpy.data.meshes.remove(obj_data) + +def running(): + n=0# number of instances of the program running + prog=[line.split() for line in subprocess.check_output("tasklist").splitlines()] + [prog.pop(e) for e in [0,1,2]] #useless + for task in prog: + if str(task[0]) == "b'3D-CoatDX64C.exe'" or str(task[0]) == "b'3D-CoatGL64C.exe'": + n+=1 + break + if n>0: + return True + else: + return False + +class SCENE_OT_folder(bpy.types.Operator): + bl_idname = "update_exchange_folder.pilgway_3d_coat" + bl_label = "Export your custom property" + bl_description = "Export your custom property" + bl_options = {'UNDO'} + + def invoke(self, context, event): + coat3D = bpy.context.scene.coat3D + + if(os.path.isdir(coat3D.exchangedir)): + coat3D.exchange_found = True + else: + coat3D.exchange_found = False + + return {'FINISHED'} + +class SCENE_OT_opencoat(bpy.types.Operator): + bl_idname = "open_3dcoat.pilgway_3d_coat" + bl_label = "Export your custom property" + bl_description = "Export your custom property" + bl_options = {'UNDO'} + + def invoke(self, context, event): + + update_exe_path() + + exefile = bpy.context.scene.coat3D.exchangedir + exefile += ('%scoat3D_exe.txt' % (os.sep)) + exe_path = '' + if (os.path.isfile(exefile)): + + ex_pathh = open(exefile) + for line in ex_pathh: + exe_path = line + break + ex_pathh.close() + + coat3D = bpy.context.selected_objects[0].coat3D.applink_3b_path + platform = os.sys.platform + prog_path = os.environ['PROGRAMFILES'] + if (platform == 'win32'): + + active_3dcoat = exe_path + + if running() == False: + print('tulele tanne') + os.popen('"' + active_3dcoat + '" ' + coat3D) + else: + importfile = bpy.context.scene.coat3D.exchangedir + importfile += ('%simport.txt' % (os.sep)) + file = open(importfile, "w") + file.write("%s" % (coat3D)) + file.write("\n%s" % (coat3D)) + file.write("\n[3B]") + file.close() + + ''' + If not Windows Os it will only write import.txt. Auto run 3d-coat.exe is disabled. + ''' + + else: + importfile = bpy.context.scene.coat3D.exchangedir + importfile += ('%simport.txt' % (os.sep)) + file = open(importfile, "w") + file.write("%s" % (coat3D)) + file.write("\n%s" % (coat3D)) + file.write("\n[3B]") + file.close() + + + + return {'FINISHED'} + +class SCENE_OT_export(bpy.types.Operator): + bl_idname = "export_applink.pilgway_3d_coat" + bl_label = "Export your custom property" + bl_description = "Export your custom property" + bl_options = {'UNDO'} + + def invoke(self, context, event): + + update_exe_path() + + for mesh in bpy.data.meshes: + if (mesh.users == 0 and mesh.coat3D.name == '3DC'): + bpy.data.meshes.remove(mesh) + + for material in bpy.data.materials: + if (material.users == 1 and material.coat3D.name == '3DC'): + bpy.data.materials.remove(material) + + export_ok = False + coat3D = bpy.context.scene.coat3D + + if (bpy.context.selected_objects == []): + return {'FINISHED'} + else: + for objec in bpy.context.selected_objects: + if objec.type == 'MESH': + export_ok = True + if (export_ok == False): + return {'FINISHED'} + + activeobj = bpy.context.active_object.name + checkname = '' + coa = bpy.context.active_object.coat3D + coat3D.exchangedir = set_exchange_folder() + + if (not os.path.isdir(coat3D.exchangedir)): + coat3D.exchange_found = False + return {'FINISHED'} + + + folder_objects = set_working_folders() + folder_size(folder_objects) + + importfile = coat3D.exchangedir + texturefile = coat3D.exchangedir + importfile += ('%simport.txt'%(os.sep)) + texturefile += ('%stextures.txt'%(os.sep)) + + looking = True + object_index = 0 + + while(looking == True): + checkname = folder_objects + os.sep + "3DC" + checkname = ("%s%.3d.fbx"%(checkname,object_index)) + if(os.path.isfile(checkname)): + object_index += 1 + else: + looking = False + coa.applink_name = ("%s%.2d"%(activeobj,object_index)) + coa.applink_address = checkname + + matindex = 0 + + for objekti in bpy.context.selected_objects: + if(objekti.material_slots.keys() == []): + newmat = bpy.data.materials.new('Material') + newmat.use_nodes = True + objekti.data.materials.append(newmat) + matindex += 1 + new_name = objekti.data.name + name_boxs = new_name.split('.') + if(len(name_boxs)>1): + objekti.name = name_boxs[0] + name_boxs[1] + nimi = name_boxs[0] + name_boxs[1] + nimiNum = int(name_boxs[1]) + looking = False + lyytyi = False + while(looking == False): + for all_ob in bpy.data.meshes: + numero = ("%.3d"%(nimiNum)) + nimi2 = name_boxs[0] + numero + if(all_ob.name == nimi2): + lyytyi = True + break + else: + lyytyi = False + + if(lyytyi == True): + nimiNum += 1 + else: + looking = True + objekti.data.name = nimi2 + objekti.name = nimi2 + + + + else: + objekti.name = name_boxs[0] + objekti.data.name = name_boxs[0] + objekti.coat3D.applink_name = objekti.data.name + + for objekti in bpy.context.selected_objects: + objekti.coat3D.applink_scale = objekti.scale + + bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') + if(len(bpy.context.selected_objects) > 1 and coat3D.type != 'vox'): + bpy.ops.object.transforms_to_deltas(mode='ROT') + + + bpy.ops.export_scene.fbx(filepath=coa.applink_address, use_selection=True, use_mesh_modifiers=coat3D.exportmod, axis_forward='X', axis_up='Y') + + file = open(importfile, "w") + file.write("%s"%(checkname)) + file.write("\n%s"%(checkname)) + file.write("\n[%s]"%(coat3D.type)) + file.close() + group_index = -1.0 + for idx, objekti in enumerate(bpy.context.selected_objects): + + if(len(bpy.context.selected_objects) == 1): + objekti.coat3D.applink_onlyone = True + objekti.coat3D.type = coat3D.type + objekti.coat3D.applink_mesh = True + objekti.coat3D.applink_address = coa.applink_address + objekti.coat3D.obj_mat = '' + objekti.coat3D.applink_firsttime = True + objekti.coat3D.objecttime = str(os.path.getmtime(objekti.coat3D.applink_address)) + objekti.data.coat3D.name = '3DC' + + if(coat3D.type != 'vox'): + if(objekti.material_slots.keys() != []): + for material in objekti.material_slots: + if material.material.use_nodes == True: + for node in material.material.node_tree.nodes: + if(node.name.startswith('3DC_') == True): + material.material.node_tree.nodes.remove(node) + + return {'FINISHED'} + +class SCENE_OT_import(bpy.types.Operator): + bl_idname = "import_applink.pilgway_3d_coat" + bl_label = "import your custom property" + bl_description = "import your custom property" + bl_options = {'UNDO'} + + def invoke(self, context, event): + + update_exe_path() + + for node_group in bpy.data.node_groups: + if(node_group.users == 0): + bpy.data.node_groups.remove(node_group) + + for mesh in bpy.data.meshes: + if(mesh.users == 0 and mesh.coat3D.name == '3DC'): + bpy.data.meshes.remove(mesh) + + for material in bpy.data.materials: + img_list = [] + if (material.users == 1 and material.coat3D.name == '3DC'): + if material.use_nodes == True: + for node in material.node_tree.nodes: + if node.type == 'TEX_IMAGE' and node.name.startswith('3DC'): + img_list.append(node.image) + if img_list != []: + for del_img in img_list: + bpy.data.images.remove(del_img) + + bpy.data.materials.remove(material) + + coat3D = bpy.context.scene.coat3D + coat = bpy.coat3D + coat3D.exchangedir = set_exchange_folder() + + + texturelist = make_texture_list(coat3D.exchangedir) + for texturepath in texturelist: + for image in bpy.data.images: + if(image.filepath == texturepath[3] and image.users == 0): + bpy.data.images.remove(image) + + + kokeilu = coat3D.exchangedir[:-10] + Blender_folder = ("%s%sExchange%sBlender"%(kokeilu,os.sep,os.sep)) + Blender_export = Blender_folder + path3b_now = coat3D.exchangedir + path3b_now += ('last_saved_3b_file.txt') + Blender_export += ('%sexport.txt'%(os.sep)) + new_applink_address = 'False' + new_object = False + + if(os.path.isfile(Blender_export)): + obj_pathh = open(Blender_export) + new_object = True + for line in obj_pathh: + new_applink_address = line + break + obj_pathh.close() + + for scene_objects in bpy.context.collection.all_objects: + if(scene_objects.type == 'MESH'): + if(scene_objects.coat3D.applink_address == new_applink_address): + new_object = False + + exportfile = coat3D.exchangedir + exportfile += ('%sBlender' % (os.sep)) + exportfile += ('%sexport.txt' % (os.sep)) + if (os.path.isfile(exportfile)): + os.remove(exportfile) + if(new_object == False): + + ''' + #Blender -> 3DC -> Blender workflow + #First check if objects needs to be imported, if imported it will then delete extra mat and objs. + ''' + + old_materials = bpy.data.materials.keys() + old_objects = bpy.data.objects.keys() + cache_base = bpy.data.objects.keys() + + object_list = [] + import_list = [] + import_type = [] + + for objekti in bpy.data.objects: + if objekti.type == 'MESH': + obj_coat = objekti.coat3D + if(obj_coat.applink_mesh == True): + object_list.append(objekti.name) + + if(os.path.isfile(obj_coat.applink_address)): + if (obj_coat.objecttime != str(os.path.getmtime(obj_coat.applink_address))): + obj_coat.dime = objekti.dimensions + obj_coat.import_mesh = True + obj_coat.objecttime = str(os.path.getmtime(obj_coat.applink_address)) + if(obj_coat.applink_address not in import_list): + import_list.append(obj_coat.applink_address) + import_type.append(coat3D.type) + + if(import_list or coat3D.importmesh): + print('import_list:', import_list) + for idx, list in enumerate(import_list): + bpy.ops.import_scene.fbx(filepath=list, global_scale = 1,axis_forward='X',use_custom_normals=False) + cache_objects = bpy.data.objects.keys() + cache_objects = [i for i in cache_objects if i not in cache_base] + for cache_object in cache_objects: + bpy.data.objects[cache_object].coat3D.type = import_type[idx] + bpy.data.objects[cache_object].coat3D.applink_address = list + cache_base.append(cache_object) + + + bpy.ops.object.select_all(action='DESELECT') + + new_materials = bpy.data.materials.keys() + new_objects = bpy.data.objects.keys() + new_images = bpy.data.images.keys() + + + diff_mat = [i for i in new_materials if i not in old_materials] + diff_objects = [i for i in new_objects if i not in old_objects] + + for mark_mesh in diff_objects: + bpy.data.objects[mark_mesh].data.coat3D.name = '3DC' + for c_index in diff_mat: + bpy.data.materials.remove(bpy.data.materials[c_index]) + '''The main Applink Object Loop''' + + remove_path = True + for oname in object_list: + objekti = bpy.data.objects[oname] + if(objekti.coat3D.applink_mesh == True): + exportfile = coat3D.exchangedir + path3b_n = coat3D.exchangedir + path3b_n += ('%slast_saved_3b_file.txt' % (os.sep)) + if(objekti.coat3D.import_mesh and coat3D.importmesh == True): + objekti.coat3D.import_mesh = False + objekti.select_set(True) + + use_smooth = objekti.data.polygons[0].use_smooth + + new_name = objekti.data.name + name_boxs = new_name.split('.') + found_obj = False + + '''Changes objects mesh into proxy mesh''' + print('ONAME:',oname) + if(objekti.coat3D.type): + for proxy_objects in diff_objects: + print('tryis to found: ',proxy_objects) + if (proxy_objects.startswith(objekti.coat3D.applink_name + '.')): + obj_proxy = bpy.data.objects[proxy_objects] + obj_proxy.coat3D.delete_proxy_mesh = True + found_obj = True + + mat_list = [] + if (objekti.material_slots): + act_mat = objekti.active_material + for obj_mat in objekti.material_slots: + mat_list.append(obj_mat.material) + + if(found_obj == True): + exportfile = coat3D.exchangedir + path3b_n = coat3D.exchangedir + path3b_n += ('%slast_saved_3b_file.txt' % (os.sep)) + exportfile += ('%sBlender' % (os.sep)) + exportfile += ('%sexport.txt'%(os.sep)) + if(os.path.isfile(exportfile)): + export_file = open(exportfile) + for line in export_file: + if line.rfind('.3b'): + coat['active_coat'] = line + export_file.close() + os.remove(exportfile) + if(os.path.isfile(path3b_n)): + export_file = open(path3b_n) + for line in export_file: + objekti.coat3D.applink_3b_path = line + export_file.close() + coat3D.remove_path = True + + + + bpy.ops.object.select_all(action='DESELECT') + obj_proxy.select_set(True) + + bpy.ops.object.select_all(action='TOGGLE') + + if objekti.coat3D.applink_firsttime == True and objekti.coat3D.type == 'vox': + objekti.select_set(True) + + objekti.rotation_euler[0] = 1.5708 + objekti.rotation_euler[2] = 1.5708 + bpy.ops.object.transforms_to_deltas(mode='ROT') + objekti.scale = (0.01, 0.01, 0.01) + bpy.ops.object.transforms_to_deltas(mode='SCALE') + objekti.coat3D.applink_firsttime = False + objekti.select_set(False) + + elif objekti.coat3D.applink_firsttime == True: + objekti.scale = (objekti.scale[0]/objekti.coat3D.applink_scale[0],objekti.scale[1]/objekti.coat3D.applink_scale[1],objekti.scale[2]/objekti.coat3D.applink_scale[2]) + bpy.ops.object.transforms_to_deltas(mode='SCALE') + if(objekti.coat3D.applink_onlyone == False): + objekti.rotation_euler = (0,0,0) + objekti.scale = (0.01,0.01,0.01) + objekti.coat3D.applink_firsttime = False + + if(coat3D.importlevel): + obj_proxy.select = True + obj_proxy.modifiers.new(name='temp',type='MULTIRES') + objekti.select = True + bpy.ops.object.multires_reshape(modifier=multires_name) + bpy.ops.object.select_all(action='TOGGLE') + multires_on = False + else: + updatemesh(objekti,obj_proxy) + + #tärkee että saadaan oikein käännettyä objekt + + objekti.select_set(True) + bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN') + + objekti.data.materials.pop() + for mat in mat_list: + objekti.data.materials.append(mat) + + if (use_smooth): + for data_mesh in objekti.data.polygons: + data_mesh.use_smooth = True + else: + for data_mesh in objekti.data.polygons: + data_mesh.use_smooth = False + + bpy.ops.object.select_all(action='DESELECT') + + if(coat3D.importmesh and not(os.path.isfile(objekti.coat3D.applink_address))): + coat3D.importmesh = False + + objekti.select_set(True) + if(coat3D.importtextures): + is_new = False + tex.matlab(objekti,mat_list,texturelist,is_new) + objekti.select_set(False) + else: + print('JAAKO TAHAN KOHTAAN 2') + mat_list = [] + if (objekti.material_slots): + for obj_mat in objekti.material_slots: + mat_list.append(obj_mat.material) + + if (coat3D.importtextures): + is_new = False + tex.matlab(objekti,mat_list,texturelist, is_new) + objekti.select_set(False) + + if(coat3D.remove_path == True): + os.remove(path3b_n) + coat3D.remove_path = False + + bpy.ops.object.select_all(action='DESELECT') + if(import_list): + for del_obj in diff_objects: + print('diff_objects', diff_objects) + + if(bpy.context.collection.all_objects[del_obj].coat3D.type == 'vox' and bpy.context.collection.all_objects[del_obj].coat3D.delete_proxy_mesh == False): + bpy.context.collection.all_objects[del_obj].select_set(True) + objekti = bpy.context.collection.all_objects[del_obj] + objekti.rotation_euler[2] = 1.5708 + bpy.ops.object.transforms_to_deltas(mode='ROT') + # objekti.rotation_euler = (0, 0, 0) + objekti.scale = (0.02, 0.02, 0.02) + bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') + + objekti.data.coat3D.name = '3DC' + + objekti.coat3D.objecttime = str(os.path.getmtime(objekti.coat3D.applink_address)) + objekti.coat3D.applink_name = objekti.name + objekti.coat3D.applink_mesh = True + objekti.coat3D.import_mesh = False + bpy.ops.object.transforms_to_deltas(mode='SCALE') + objekti.coat3D.applink_firsttime = False + bpy.context.collection.all_objects[del_obj].select_set(False) + + else: + bpy.context.collection.all_objects[del_obj].select_set(True) + bpy.ops.object.delete() + + else: + + ''' + 3DC -> Blender workflow + ''' + + for old_obj in bpy.context.collection.objects: + old_obj.coat3D.applink_old = True + + coat3D = bpy.context.scene.coat3D + scene = context.scene + Blender_folder = ("%s%sBlender"%(coat3D.exchangedir,os.sep)) + Blender_export = Blender_folder + path3b_now = coat3D.exchangedir + os.sep + path3b_now += ('last_saved_3b_file.txt') + Blender_export += ('%sexport.txt'%(os.sep)) + mat_list = [] + osoite_3b = '' + if (os.path.isfile(path3b_now)): + path3b_fil = open(path3b_now) + for lin in path3b_fil: + osoite_3b = lin + path3b_fil.close() + head, tail = os.path.split(osoite_3b) + just_3b_name = tail + os.remove(path3b_now) + + old_materials = bpy.data.materials.keys() + old_objects = bpy.data.objects.keys() + + bpy.ops.import_scene.fbx(filepath=new_applink_address, global_scale = 0.001, use_manual_orientation=True, axis_forward='X', axis_up='Y') + + new_materials = bpy.data.materials.keys() + new_objects = bpy.data.objects.keys() + + diff_mat = [i for i in new_materials if i not in old_materials] + diff_objects = [i for i in new_objects if i not in old_objects] + + for mark_mesh in diff_mat: + bpy.data.materials[mark_mesh].coat3D.name = '3DC' + bpy.data.materials[mark_mesh].use_fake_user = True + laskuri = 0 + index = 0 + for c_index in diff_objects: + bpy.data.objects[c_index].data.coat3D.name = '3DC' + bpy.data.objects[c_index].material_slots[0].material = bpy.data.materials[diff_mat[laskuri]] + laskuri += 1 + + bpy.ops.object.select_all(action='DESELECT') + for new_obj in bpy.context.collection.objects: + + if(new_obj.coat3D.applink_old == False): + new_obj.select_set(True) + #bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN') + #new_obj.rotation_euler = (0, 0, 0) + new_obj.scale = (0.03, 0.03, 0.03) + new_obj.coat3D.applink_firsttime = False + new_obj.select_set(False) + new_obj.coat3D.applink_address = new_applink_address + new_obj.coat3D.applink_mesh = True + new_obj.coat3D.objecttime = str(os.path.getmtime(new_obj.coat3D.applink_address)) + + new_obj.coat3D.applink_name = new_obj.material_slots[0].material.name + index = index + 1 + + new_obj.coat3D.applink_export = True + if(osoite_3b != ''): + new_obj.coat3D.applink_3b_path = osoite_3b + new_obj.coat3D.applink_3b_just_name = just_3b_name + + mat_list.append(new_obj.material_slots[0].material) + is_new = True + tex.matlab(new_obj, mat_list, texturelist, is_new) + mat_list.pop() + + for new_obj in bpy.context.collection.objects: + if(new_obj.coat3D.applink_old == False): + new_obj.coat3D.applink_old = True + + bpy.ops.object.select_all(action='SELECT') + bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') + bpy.ops.object.select_all(action='DESELECT') + + kokeilu = coat3D.exchangedir[:-10] + Blender_folder2 = ("%s%sExchange%sBlender" % (kokeilu, os.sep, os.sep)) + Blender_folder2 += ('%sexport.txt' % (os.sep)) + + if (os.path.isfile(Blender_export)): + os.remove(Blender_export) + if (os.path.isfile(Blender_folder2)): + os.remove(Blender_folder2) + for material in bpy.data.materials: + if material.use_nodes == True: + for node in material.node_tree.nodes: + if (node.name).startswith('3DC'): + node.location = node.location + + return {'FINISHED'} + +from bpy import * +from mathutils import Vector, Matrix + +class SCENE_PT_Main(bpy.types.Panel): + bl_label = "3D-Coat Applink" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = 'View' + + @classmethod + def poll(cls, context): + if bpy.context.mode == 'OBJECT': + return True + else: + return False + + def draw(self, context): + layout = self.layout + coat = bpy.coat3D + coat3D = bpy.context.scene.coat3D + if(bpy.context.active_object): + coa = bpy.context.active_object.coat3D + if(coat['status'] == 0): + row = layout.row() + row.label(text="Applink didn't find your 3d-Coat/Excahnge folder.") + row = layout.row() + row.label("Please select it before using Applink.") + row = layout.row() + row.prop(coat3D,"exchangedir",text="") + row = layout.row() + row.operator("update_exchange_folder.pilgway_3d_coat", text="Apply folder") + + else: + + #Here you add your GUI + row = layout.row() + row.prop(coat3D,"type",text = "") + flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True) + + col = flow.column() + + col.operator("export_applink.pilgway_3d_coat", text="Transfer") + col = flow.column() + col.operator("import_applink.pilgway_3d_coat", text="Update") + + + + +class ObjectButtonsPanel(): + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "object" + +class SCENE_PT_Settings(ObjectButtonsPanel,bpy.types.Panel): + bl_label = "3D-Coat Applink Settings" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + def draw(self, context): + pass + +class SCENE_PT_Settings_Update(ObjectButtonsPanel, bpy.types.Panel): + bl_label = "Update" + bl_parent_id = "SCENE_PT_Settings" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = False + coat3D = bpy.context.scene.coat3D + + rd = context.scene.render + + layout.active = True + + flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True) + + col = flow.column() + col.prop(coat3D, "importmesh", text="Update Mesh/UV") + col = flow.column() + col.prop(coat3D, "createnodes", text="Create Extra Nodes") + col = flow.column() + col.prop(coat3D, "importtextures", text="Update Textures") + col = flow.column() + col.prop(coat3D, "creategroup", text="Group Nodes") + col = flow.column() + col.prop(coat3D, "exportmod", text="Export with modifiers") + +class SCENE_PT_Settings_Folders(ObjectButtonsPanel, bpy.types.Panel): + bl_label = "Folders" + bl_parent_id = "SCENE_PT_Settings" + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_OPENGL'} + + def draw(self, context): + layout = self.layout + layout.use_property_split = False + coat3D = bpy.context.scene.coat3D + + rd = context.scene.render + + layout.active = True + + flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True) + + col = flow.column() + col.prop(coat3D, "exchangedir", text="Exchange folder") + + col = flow.column() + col.prop(coat3D, "coat3D_exe", text="3D-Coat.exe") + +# 3D-Coat Dynamic Menu +class VIEW3D_MT_Coat_Dynamic_Menu(bpy.types.Menu): + bl_label = "3D-Coat Applink Menu" + + def draw(self, context): + layout = self.layout + + layout.operator_context = 'INVOKE_REGION_WIN' + + ob = context + if ob.mode == 'OBJECT': + if(len(context.selected_objects) > 0): + layout.operator("import_applink.pilgway_3d_coat", text="Update Scene") + layout.separator() + + layout.operator("export_applink.pilgway_3d_coat", text="Copy selected object(s) into 3D-Coat") + layout.separator() + if(context.selected_objects[0].coat3D.applink_3b_path != ''): + layout.operator("open_3dcoat.pilgway_3d_coat", text="Open .3b file" +context.selected_objects[0].coat3D.applink_3b_just_name) + layout.separator() + + else: + layout.operator("import_applink.pilgway_3d_coat", text="Update Scene") + layout.separator() + + + +class ObjectCoat3D(PropertyGroup): + + obj_mat: StringProperty( + name="Object_Path", + default='' + ) + applink_address: StringProperty( + name="Object_Applink_address" + ) + applink_3b_path: StringProperty( + name="Object_3B_Path" + ) + applink_name: StringProperty( + name="Applink object name" + ) + applink_3b_just_name: StringProperty( + name="Applink object name" + ) + applink_firsttime: BoolProperty( + name="FirstTime", + description="FirstTime", + default=True + ) + delete_proxy_mesh: BoolProperty( + name="FirstTime", + description="FirstTime", + default=False + ) + applink_onlyone: BoolProperty( + name="FirstTime", + description="FirstTime", + default=False + ) + type: StringProperty( + name="type", + description="shows type", + default='' + ) + import_mesh: BoolProperty( + name="ImportMesh", + description="ImportMesh", + default=False + ) + applink_mesh: BoolProperty( + name="ImportMesh", + description="ImportMesh", + default=False + ) + applink_old: BoolProperty( + name="OldObject", + description="Old Object", + default=False + ) + applink_export: BoolProperty( + name="FirstTime", + description="Object is from 3d-ocat", + default=False + ) + objecttime: StringProperty( + name="ObjectTime", + subtype="FILE_PATH" + ) + path3b: StringProperty( + name="3B Path", + subtype="FILE_PATH" + ) + dime: FloatVectorProperty( + name="dime", + description="Dimension" + ) + applink_scale: FloatVectorProperty( + name="Scale", + description="Scale" + ) +class MaterialCoat3D(PropertyGroup): + Nodegroup: StringProperty( + name="NodeGroup", + ) + +class SceneCoat3D(PropertyGroup): + defaultfolder: StringProperty( + name="FilePath", + subtype="DIR_PATH", + ) + coat3D_exe: StringProperty( + name="FilePath", + subtype="FILE_PATH", + ) + cursor_loc: FloatVectorProperty( + name="Cursor_loc", + description="location" + ) + exchangedir: StringProperty( + name="FilePath", + subtype="DIR_PATH" + ) + exchangefolder: StringProperty( + name="FilePath", + subtype="DIR_PATH" + ) + wasactive: StringProperty( + name="Pass active object", + ) + import_box: BoolProperty( + name="Import window", + description="Allows to skip import dialog", + default=True + ) + remove_path: BoolProperty( + name="Import window", + description="Allows to skip import dialog", + default=False + ) + exchange_found: BoolProperty( + name="Exchange Found", + description="Alert if Exchange folder is not found", + default=True + ) + export_box: BoolProperty( + name="Export window", + description="Allows to skip export dialog", + default=True + ) + export_color: BoolProperty( + name="Export color", + description="Export color texture", + default=True + ) + export_spec: BoolProperty( + name="Export specular", + description="Export specular texture", + default=True + ) + export_normal: BoolProperty( + name="Export Normal", + description="Export normal texture", + default=True + ) + export_disp: BoolProperty( + name="Export Displacement", + description="Export displacement texture", + default=True + ) + export_position: BoolProperty( + name="Export Source Position", + description="Export source position", + default=True + ) + export_zero_layer: BoolProperty( + name="Export from Layer 0", + description="Export mesh from Layer 0", + default=True + ) + export_coarse: BoolProperty( + name="Export Coarse", + description="Export Coarse", + default=True + ) + exportfile: BoolProperty( + name="No Import File", + description="Add Modifiers and export", + default=False + ) + importmod: BoolProperty( + name="Remove Modifiers", + description="Import and add modifiers", + default=False + ) + exportmod: BoolProperty( + name="Modifiers", + description="Export modifiers", + default=False + ) + export_pos: BoolProperty( + name="Remember Position", + description="Remember position", + default=True + ) + importtextures: BoolProperty( + name="Bring Textures", + description="Import Textures", + default=True + ) + createnodes: BoolProperty( + name="Bring Textures", + description="Import Textures", + default=True + ) + creategroup: BoolProperty( + name="Bring Textures", + description="Import Textures", + default=True + ) + importlevel: BoolProperty( + name="Multires. Level", + description="Bring Specific Multires Level", + default=False + ) + exportover: BoolProperty( + name="Export Obj", + description="Import Textures", + default=False + ) + importmesh: BoolProperty( + name="Mesh", + description="Import Mesh", + default=False + ) + + # copy location + + loca: FloatVectorProperty( + name="location", + description="Location", + subtype="XYZ", + default=(0.0, 0.0, 0.0) + ) + rota: FloatVectorProperty( + name="location", + description="Location", + subtype="EULER", + default=(0.0, 0.0, 0.0) + ) + scal: FloatVectorProperty( + name="location", + description="Location", + subtype="XYZ", + default=(0.0, 0.0, 0.0) + ) + dime: FloatVectorProperty( + name="dimension", + description="Dimension", + subtype="XYZ", + default=(0.0, 0.0, 0.0) + ) + type: EnumProperty( + name="Export Type", + description="Different Export Types", + items=(("ppp", "Per-Pixel Painting", ""), + ("mv", "Microvertex Painting", ""), + ("ptex", "Ptex Painting", ""), + ("uv", "UV-Mapping", ""), + ("ref", "Reference Mesh", ""), + ("retopo", "Retopo mesh as new layer", ""), + ("vox", "Mesh As Voxel Object", ""), + ("alpha", "Mesh As New Pen Alpha", ""), + ("prim", "Mesh As Voxel Primitive", ""), + ("curv", "Mesh As a Curve Profile", ""), + ("autopo", "Mesh For Auto-retopology", ""), + ), + default="ppp" + ) +class MeshCoat3D(PropertyGroup): + applink_address: StringProperty( + name="ApplinkAddress", + subtype="APPLINK_ADDRESS", + ) +class MaterialCoat3D(PropertyGroup): + name: StringProperty( + name="ApplinkAddress", + subtype="APPLINK_ADDRESS", + ) + +classes = ( + #ObjectButtonsPanel, + SCENE_PT_Main, + SCENE_PT_Settings, + SCENE_PT_Settings_Update, + SCENE_PT_Settings_Folders, + SCENE_OT_folder, + SCENE_OT_opencoat, + SCENE_OT_export, + SCENE_OT_import, + VIEW3D_MT_Coat_Dynamic_Menu, + ObjectCoat3D, + SceneCoat3D, + MeshCoat3D, + MaterialCoat3D, + ) + def register(): bpy.coat3D = dict() bpy.coat3D['active_coat'] = '' - bpy.coat3D['status'] = 0 + bpy.coat3D['status'] = 1 bpy.coat3D['kuva'] = 1 - class ObjectCoat3D(PropertyGroup): - objpath = StringProperty( - name="Object_Path" - ) - applink_name = StringProperty( - name="Object_Applink_name" - ) - coatpath = StringProperty( - name="Coat_Path" - ) - objectdir = StringProperty( - name="ObjectPath", - subtype="FILE_PATH" - ) - objecttime = StringProperty( - name="ObjectTime", - subtype="FILE_PATH" - ) - texturefolder = StringProperty( - name="Texture folder:", - subtype="DIR_PATH" - ) - path3b = StringProperty( - name="3B Path", - subtype="FILE_PATH" - ) - export_on = BoolProperty( - name="Export_On", - description="Add Modifiers and export", - default=False - ) - dime = FloatVectorProperty( - name="dime", - description="Dimension" - ) - loc = FloatVectorProperty( - name="Location", - description="Location" - ) - rot = FloatVectorProperty( - name="Rotation", - description="Rotation", - subtype='EULER' - ) - sca = FloatVectorProperty( - name="Scale", - description="Scale" - ) - - class SceneCoat3D(PropertyGroup): - defaultfolder = StringProperty( - name="FilePath", - subtype="DIR_PATH", - ) - cursor_loc = FloatVectorProperty( - name="Cursor_loc", - description="location" - ) - exchangedir = StringProperty( - name="FilePath", - subtype="DIR_PATH" - ) - exchangefolder = StringProperty( - name="FilePath", - subtype="DIR_PATH" - ) - wasactive = StringProperty( - name="Pass active object", - ) - import_box = BoolProperty( - name="Import window", - description="Allows to skip import dialog", - default=True - ) - exchange_found = BoolProperty( - name="Exchange Found", - description="Alert if Exchange folder is not found", - default=True - ) - export_box = BoolProperty( - name="Export window", - description="Allows to skip export dialog", - default=True - ) - export_color = BoolProperty( - name="Export color", - description="Export color texture", - default=True - ) - export_spec = BoolProperty( - name="Export specular", - description="Export specular texture", - default=True - ) - export_normal = BoolProperty( - name="Export Normal", - description="Export normal texture", - default=True - ) - export_disp = BoolProperty( - name="Export Displacement", - description="Export displacement texture", - default=True - ) - export_position = BoolProperty( - name="Export Source Position", - description="Export source position", - default=True - ) - export_zero_layer = BoolProperty( - name="Export from Layer 0", - description="Export mesh from Layer 0", - default=True - ) - export_coarse = BoolProperty( - name="Export Coarse", - description="Export Coarse", - default=True - ) - exportfile = BoolProperty( - name="No Import File", - description="Add Modifiers and export", - default=False - ) - importmod = BoolProperty( - name="Remove Modifiers", - description="Import and add modifiers", - default=False - ) - exportmod = BoolProperty( - name="Modifiers", - description="Export modifiers", - default=False - ) - export_pos = BoolProperty( - name="Remember Position", - description="Remember position", - default=True - ) - importtextures = BoolProperty( - name="Bring Textures", - description="Import Textures", - default=True - ) - importlevel = BoolProperty( - name="Multires. Level", - description="Bring Specific Multires Level", - default=False - ) - exportover = BoolProperty( - name="Export Obj", - description="Import Textures", - default=False - ) - importmesh = BoolProperty( - name="Mesh", - description="Import Mesh", - default=True - ) - - # copy location - cursor = FloatVectorProperty( - name="Cursor", - description="Location", - subtype="XYZ", - default=(0.0, 0.0, 0.0) - ) - loca = FloatVectorProperty( - name="location", - description="Location", - subtype="XYZ", - default=(0.0, 0.0, 0.0) - ) - rota = FloatVectorProperty( - name="location", - description="Location", - subtype="EULER", - default=(0.0, 0.0, 0.0) - ) - scal = FloatVectorProperty( - name="location", - description="Location", - subtype="XYZ", - default=(0.0, 0.0, 0.0) - ) - dime = FloatVectorProperty( - name="dimension", - description="Dimension", - subtype="XYZ", - default=(0.0, 0.0, 0.0) - ) - type = EnumProperty( - name="Export Type", - description="Different Export Types", - items=(("ppp", "Per-Pixel Painting", ""), - ("mv", "Microvertex Painting", ""), - ("ptex", "Ptex Painting", ""), - ("uv", "UV-Mapping", ""), - ("ref", "Reference Mesh", ""), - ("retopo", "Retopo mesh as new layer", ""), - ("vox", "Mesh As Voxel Object", ""), - ("alpha", "Mesh As New Pen Alpha", ""), - ("prim", "Mesh As Voxel Primitive", ""), - ("curv", "Mesh As a Curve Profile", ""), - ("autopo", "Mesh For Auto-retopology", ""), - ), - default="ppp" - ) - - bpy.utils.register_module(__name__) - - bpy.types.Object.coat3D = PointerProperty( - name="Applink Variables", - type=ObjectCoat3D, - description="Applink variables" - ) - bpy.types.Scene.coat3D = PointerProperty( - name="Applink Variables", - type=SceneCoat3D, - description="Applink variables" - ) + from bpy.utils import register_class + for cls in classes: + register_class(cls) + + bpy.types.Object.coat3D = PointerProperty(type=ObjectCoat3D) + bpy.types.Scene.coat3D = PointerProperty(type=SceneCoat3D) + bpy.types.Mesh.coat3D = PointerProperty(type=MeshCoat3D) + bpy.types.Material.coat3D = PointerProperty(type=MaterialCoat3D) + kc = bpy.context.window_manager.keyconfigs.addon + + if kc: + km = kc.keymaps.new(name="Object Mode") + kmi = km.keymap_items.new('wm.call_menu', 'Q', 'PRESS', shift=True) + kmi.properties.name = "VIEW3D_MT_Coat_Dynamic_Menu" def unregister(): + import bpy + from bpy.utils import unregister_class del bpy.types.Object.coat3D del bpy.types.Scene.coat3D + del bpy.types.Mesh.coat3D del bpy.coat3D - bpy.utils.unregister_module(__name__) - + kc = bpy.context.window_manager.keyconfigs.addon + if kc: + km = kc.keymaps.get('Object Mode') + for kmi in km.keymap_items: + if kmi.idname == 'wm.call_menu': + if kmi.properties.name == "VIEW3D_MT_Coat_Dynamic_Menu": + km.keymap_items.remove(kmi) -if __name__ == "__main__": - register() + for cls in reversed(classes): + unregister_class(cls) diff --git a/io_coat3D/coat.py b/io_coat3D/coat.py deleted file mode 100644 index fc3cb747..00000000 --- a/io_coat3D/coat.py +++ /dev/null @@ -1,626 +0,0 @@ -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -import bpy -from bpy.props import * -from io_coat3D import tex -import os - - -bpy.coat3D = dict() -bpy.coat3D['active_coat'] = '' -bpy.coat3D['status'] = 0 -def set_exchange_folder(): - platform = os.sys.platform - coat3D = bpy.context.scene.coat3D - Blender_export = "" - - if(platform == 'win32'): - exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV4' + os.sep +'Exchange' - if not(os.path.isdir(exchange)): - exchange = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3D-CoatV3' + os.sep +'Exchange' - else: - exchange = os.path.expanduser("~") + os.sep + '3D-CoatV4' + os.sep + 'Exchange' - if not(os.path.isdir(exchange)): - exchange = os.path.expanduser("~") + os.sep + '3D-CoatV3' + os.sep + 'Exchange' - if(not(os.path.isdir(exchange))): - exchange = coat3D.exchangedir - - if(os.path.isdir(exchange)): - bpy.coat3D['status'] = 1 - if(platform == 'win32'): - exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt' - applink_folder = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' - if(not(os.path.isdir(applink_folder))): - os.makedirs(applink_folder) - else: - exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt' - applink_folder = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' - if(not(os.path.isdir(applink_folder))): - os.makedirs(applink_folder) - file = open(exchange_path, "w") - file.write("%s"%(coat3D.exchangedir)) - file.close() - - else: - if(platform == 'win32'): - exchange_path = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt' - else: - exchange_path = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Exchange_folder.txt' - if(os.path.isfile(exchange_path)): - ex_path ='' - - ex_pathh = open(exchange_path) - for line in ex_pathh: - ex_path = line - break - ex_pathh.close() - - if(os.path.isdir(ex_path) and ex_path.rfind('Exchange') >= 0): - exchange = ex_path - bpy.coat3D['status'] = 1 - else: - bpy.coat3D['status'] = 0 - else: - bpy.coat3D['status'] = 0 - if(bpy.coat3D['status'] == 1): - Blender_folder = ("%s%sBlender"%(exchange,os.sep)) - Blender_export = Blender_folder - path3b_now = exchange - path3b_now += ('last_saved_3b_file.txt') - Blender_export += ('%sexport.txt'%(os.sep)) - - if(not(os.path.isdir(Blender_folder))): - os.makedirs(Blender_folder) - Blender_folder = os.path.join(Blender_folder,"run.txt") - file = open(Blender_folder, "w") - file.close() - return exchange - -def set_working_folders(): - platform = os.sys.platform - coat3D = bpy.context.scene.coat3D - if(platform == 'win32'): - folder_objects = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Objects' - folder_textures = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Textures' + os.sep - if(not(os.path.isdir(folder_objects))): - os.makedirs(folder_objects) - if(not(os.path.isdir(folder_textures))): - os.makedirs(folder_textures) - else: - folder_objects = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Objects' - folder_textures = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Textures' + os.sep - if(not(os.path.isdir(folder_objects))): - os.makedirs(folder_objects) - if(not(os.path.isdir(folder_textures))): - os.makedirs(folder_textures) - - - return folder_objects,folder_textures - -class ObjectButtonsPanel(): - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = "object" - -class SCENE_PT_Main(ObjectButtonsPanel,bpy.types.Panel): - bl_label = "3D-Coat Applink" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - scene = context.scene - me = context.scene.objects - mat_list = [] - import_no = 0 - coat = bpy.coat3D - coat3D = bpy.context.scene.coat3D - if(bpy.context.scene.objects.active): - coa = bpy.context.scene.objects.active.coat3D - - - if(bpy.coat3D['status'] == 0 and not(os.path.isdir(coat3D.exchangedir))): - bpy.coat3D['active_coat'] = set_exchange_folder() - row = layout.row() - row.label(text="Applink didn't find your 3d-Coat/Excahnge folder.") - row = layout.row() - row.label("Please select it before using Applink.") - row = layout.row() - row.prop(coat3D,"exchangedir",text="") - - else: - - - #Here you add your GUI - row = layout.row() - row.prop(coat3D,"type",text = "") - row = layout.row() - colL = row.column() - colR = row.column() - - colR.operator("export_applink.pilgway_3d_coat", text="Transfer") - - colL.operator("import_applink.pilgway_3d_coat", text="Update") - - - - - - - - - - -class SCENE_OT_export(bpy.types.Operator): - bl_idname = "export_applink.pilgway_3d_coat" - bl_label = "Export your custom property" - bl_description = "Export your custom property" - bl_options = {'UNDO'} - - def invoke(self, context, event): - checkname = '' - coat3D = bpy.context.scene.coat3D - scene = context.scene - activeobj = bpy.context.active_object.name - obj = scene.objects[activeobj] - coa = bpy.context.scene.objects.active.coat3D - coat3D.exchangedir = set_exchange_folder() - export_ok = False - - folder_objects,folder_textures = set_working_folders() - - if(coat3D.exchange_found == False): - return {'FINISHED'} - - if(bpy.context.selected_objects == []): - return {'FINISHED'} - else: - for objec in bpy.context.selected_objects: - if objec.type == 'MESH': - export_ok = True - if(export_ok == False): - return {'FINISHED'} - - importfile = coat3D.exchangedir - texturefile = coat3D.exchangedir - importfile += ('%simport.txt'%(os.sep)) - texturefile += ('%stextures.txt'%(os.sep)) - - looking = True - object_index = 0 - if(coa.applink_name and os.path.isfile(coa.applink_name)): - checkname = coa.applink_name - - else: - while(looking == True): - checkname = folder_objects + os.sep + activeobj - checkname = ("%s%.2d.obj"%(checkname,object_index)) - if(os.path.isfile(checkname)): - object_index += 1 - else: - looking = False - coa.applink_name = checkname - - - coat3D.cursor_loc = obj.location - coat3D.cursor_orginal = bpy.context.scene.cursor_location - - - - coa.loc = obj.location - coa.rot = obj.rotation_euler - coa.sca = obj.scale - coa.dime = obj.dimensions - - obj.location = (0,0,0) - obj.rotation_euler = (0,0,0) - bpy.ops.object.transform_apply(scale=True) - - bpy.ops.export_scene.obj(filepath=coa.applink_name,use_selection=True, - use_mesh_modifiers=False,use_blen_objects=True, use_materials = True, - keep_vertex_order = True,axis_forward='-Z',axis_up='Y') - - obj.location = coa.loc - obj.rotation_euler = coa.rot - - - bpy.context.scene.cursor_location = coat3D.cursor_loc - bpy.context.scene.cursor_location = coat3D.cursor_orginal - - file = open(importfile, "w") - file.write("%s"%(checkname)) - file.write("\n%s"%(checkname)) - file.write("\n[%s]"%(coat3D.type)) - file.write("\n[TexOutput:%s]"%(folder_textures)) - file.close() - - coa.objecttime = str(os.path.getmtime(coa.applink_name)) - - - - return {'FINISHED'} - -class SCENE_OT_import(bpy.types.Operator): - bl_idname = "import_applink.pilgway_3d_coat" - bl_label = "import your custom property" - bl_description = "import your custom property" - bl_options = {'UNDO'} - - def invoke(self, context, event): - scene = context.scene - coat3D = bpy.context.scene.coat3D - coat = bpy.coat3D - test = bpy.context.selected_objects - act_first = bpy.context.scene.objects.active - bpy.context.scene.game_settings.material_mode = 'GLSL' - coat3D.exchangedir = set_exchange_folder() - - folder_objects,folder_textures = set_working_folders() - - Blender_folder = ("%s%sBlender"%(coat3D.exchangedir,os.sep)) - Blender_export = Blender_folder - path3b_now = coat3D.exchangedir - path3b_now += ('last_saved_3b_file.txt') - Blender_export += ('%sexport.txt'%(os.sep)) - new_applink_name = 'False' - new_object = False - if(os.path.isfile(Blender_export)): - obj_pathh = open(Blender_export) - new_object = True - for line in obj_pathh: - new_applink_name = line - break - obj_pathh.close() - - for scene_objects in bpy.context.scene.objects: - if(scene_objects.type == 'MESH'): - if(scene_objects.coat3D.applink_name == new_applink_name): - new_object = False - - for act_name in test: - coa = act_name.coat3D - path_object = coa.applink_name - if act_name.type == 'MESH' and os.path.isfile(path_object): - multires_on = False - activeobj = act_name.name - mat_list = [] - scene.objects[activeobj].select = True - objekti = scene.objects[activeobj] - coat3D.loca = objekti.location - coat3D.rota = objekti.rotation_euler - coat3D.dime = objekti.scale - - - - #See if there is multres modifier. - for modifiers in objekti.modifiers: - if modifiers.type == 'MULTIRES' and (modifiers.total_levels > 0): - if(not(coat3D.importlevel)): - bpy.ops.object.multires_external_pack() - multires = coat3D.exchangedir - multires += ('%stemp.btx'%(os.sep)) - bpy.ops.object.multires_external_save(filepath=multires) - #bpy.ops.object.multires_external_pack() - multires_on = True - multires_name = modifiers.name - break - - exportfile = coat3D.exchangedir - path3b_n = coat3D.exchangedir - path3b_n += ('last_saved_3b_file.txt') - exportfile += ('%sexport.txt'%(os.sep)) - if(os.path.isfile(exportfile)): - export_file = open(exportfile) - for line in export_file: - if line.rfind('.3b'): - objekti.coat3D.coatpath = line - coat['active_coat'] = line - export_file.close() - os.remove(exportfile) - - if(len(objekti.material_slots) == 0): - delete_material = False - else: - delete_material = True - - - if(not(objekti.active_material) and objekti.material_slots): - act_mat_index = objekti.active_material_index - materials_old = bpy.data.materials.keys() - bpy.ops.material.new() - materials_new = bpy.data.materials.keys() - new_ma = list(set(materials_new).difference(set(materials_old))) - new_mat = new_ma[0] - ki = bpy.data.materials[new_mat] - objekti.material_slots[act_mat_index].material = ki - - - - if(os.path.isfile(path_object) and (coa.objecttime != str(os.path.getmtime(path_object)))): - - if(objekti.material_slots): - act_mat_index = objekti.active_material_index - for obj_mat in objekti.material_slots: - mat_list.append(obj_mat.material) - - coa.dime = objekti.dimensions - coa.objecttime = str(os.path.getmtime(path_object)) - mtl = coa.applink_name - mtl = mtl.replace('.obj','.mtl') - if(os.path.isfile(mtl)): - os.remove(mtl) - - bpy.ops.import_scene.obj(filepath=path_object,axis_forward='-Z',axis_up='Y',use_image_search=False) - obj_proxy = scene.objects[0] - bpy.ops.object.select_all(action='TOGGLE') - obj_proxy.select = True - - - bpy.ops.object.transform_apply(rotation=True) - proxy_mat = obj_proxy.material_slots[0].material - if(delete_material): - while(list(obj_proxy.data.materials) != []): - proxy_mat = obj_proxy.material_slots[0].material - obj_proxy.data.materials.pop(0,1) - proxy_mat.user_clear() - bpy.data.materials.remove(proxy_mat) - bpy.ops.object.select_all(action='TOGGLE') - - if(coat3D.importlevel): - obj_proxy.select = True - obj_proxy.modifiers.new(name='temp',type='MULTIRES') - objekti.select = True - bpy.ops.object.multires_reshape(modifier=multires_name) - bpy.ops.object.select_all(action='TOGGLE') - multires_on = False - else: - - scene.objects.active = obj_proxy - - obj_data = objekti.data.id_data - objekti.data = obj_proxy.data.id_data - if(bpy.data.meshes[obj_data.name].users == 0): - objekti.data.id_data.name = obj_data.name - bpy.data.meshes.remove(obj_data) - - - obj_proxy.select = True - bpy.ops.object.delete() - objekti.select = True - objekti.scale = coat3D.dime - - bpy.context.scene.objects.active = objekti - - if(os.path.isfile(path3b_n)): - path3b_fil = open(path3b_n) - for lin in path3b_fil: - objekti.coat3D.path3b = lin - path3b_fil.close() - os.remove(path3b_n) - - if(coat3D.importmesh and not(os.path.isfile(path_object))): - coat3D.importmesh = False - - if(mat_list and coat3D.importmesh): - for mat_one in mat_list: - objekti.data.materials.append(mat_one) - objekti.active_material_index = act_mat_index - - if(mat_list): - for obj_mate in objekti.material_slots: - if(hasattr(obj_mate.material,'texture_slots')): - for tex_slot in obj_mate.material.texture_slots: - if(hasattr(tex_slot,'texture')): - if(tex_slot.texture.type == 'IMAGE'): - if tex_slot.texture.image is not None: - tex_slot.texture.image.reload() - - - if(coat3D.importtextures): - export = '' - tex.gettex(mat_list,objekti,scene,export) - - if(multires_on): - temp_file = coat3D.exchangedir - temp_file += ('%stemp2.btx'%(os.sep)) - if(objekti.modifiers[multires_name].levels == 0): - objekti.modifiers[multires_name].levels = 1 - bpy.ops.object.multires_external_save(filepath=temp_file) - objekti.modifiers[multires_name].filepath = multires - objekti.modifiers[multires_name].levels = 0 - - else: - bpy.ops.object.multires_external_save(filepath=temp_file) - objekti.modifiers[multires_name].filepath = multires - #bpy.ops.object.multires_external_pack() - bpy.ops.object.shade_smooth() - - - for act_name in test: - act_name.select = True - bpy.context.scene.objects.active = act_first - - if(new_object == True): - coat3D = bpy.context.scene.coat3D - scene = context.scene - - Blender_folder = ("%s%sBlender"%(coat3D.exchangedir,os.sep)) - Blender_export = Blender_folder - path3b_now = coat3D.exchangedir - path3b_now += ('last_saved_3b_file.txt') - Blender_export += ('%sexport.txt'%(os.sep)) - - - mat_list = [] - obj_path ='' - - - export = new_applink_name - mod_time = os.path.getmtime(new_applink_name) - mtl_list = new_applink_name.replace('.obj','.mtl') - if(os.path.isfile(mtl_list)): - os.remove(mtl_list) - - bpy.ops.import_scene.obj(filepath=new_applink_name,axis_forward='-Z',axis_up='Y') - bpy.ops.object.transform_apply(rotation=True) - new_obj = scene.objects[0] - new_obj.coat3D.applink_name = obj_path - scene.objects[0].coat3D.applink_name = export #objectdir muutettava - - os.remove(Blender_export) - - bpy.context.scene.objects.active = new_obj - - bpy.ops.object.shade_smooth() - - Blender_tex = ("%s%stextures.txt"%(coat3D.exchangedir,os.sep)) - mat_list.append(new_obj.material_slots[0].material) - tex.gettex(mat_list, new_obj, scene,export) - - return {'FINISHED'} - - - -from bpy import * -from mathutils import Vector, Matrix - -# 3D-Coat Dynamic Menu -class VIEW3D_MT_Coat_Dynamic_Menu(bpy.types.Menu): - bl_label = "3D-Coat Applink Menu" - - def draw(self, context): - layout = self.layout - settings = context.tool_settings - layout.operator_context = 'INVOKE_REGION_WIN' - coat3D = bpy.context.scene.coat3D - Blender_folder = ("%s%sBlender"%(coat3D.exchangedir,os.sep)) - Blender_export = Blender_folder - Blender_export += ('%sexport.txt'%(os.sep)) - - ob = context - if ob.mode == 'OBJECT': - if(bpy.context.selected_objects): - for ind_obj in bpy.context.selected_objects: - if(ind_obj.type == 'MESH'): - layout.active = True - break - layout.active = False - - if(layout.active == True): - - layout.operator("import_applink.pilgway_3d_coat", text="Import") - layout.separator() - - layout.operator("export_applink.pilgway_3d_coat", text="Export") - layout.separator() - - layout.menu("VIEW3D_MT_ImportMenu") - layout.separator() - - layout.menu("VIEW3D_MT_ExportMenu") - layout.separator() - - layout.menu("VIEW3D_MT_ExtraMenu") - layout.separator() - - if(len(bpy.context.selected_objects) == 1): - if(os.path.isfile(bpy.context.selected_objects[0].coat3D.path3b)): - layout.operator("import_applink.pilgway_3d_coat_3b", text="Load 3b") - layout.separator() - - if(os.path.isfile(Blender_export)): - - layout.operator("import3b_applink.pilgway_3d_coat", text="Bring from 3D-Coat") - layout.separator() - else: - if(os.path.isfile(Blender_export)): - layout.active = True - - layout.operator("import3b_applink.pilgway_3d_coat", text="Bring from 3D-Coat") - layout.separator() - else: - if(os.path.isfile(Blender_export)): - - - layout.operator("import3b_applink.pilgway_3d_coat", text="Bring from 3D-Coat") - layout.separator() - -class VIEW3D_MT_ImportMenu(bpy.types.Menu): - bl_label = "Import Settings" - - def draw(self, context): - layout = self.layout - coat3D = bpy.context.scene.coat3D - settings = context.tool_settings - layout.operator_context = 'INVOKE_REGION_WIN' - layout.prop(coat3D,"importmesh") - layout.prop(coat3D,"importmod") - layout.prop(coat3D,"smooth_on") - layout.prop(coat3D,"importtextures") - -class VIEW3D_MT_ExportMenu(bpy.types.Menu): - bl_label = "Export Settings" - - def draw(self, context): - layout = self.layout - coat3D = bpy.context.scene.coat3D - settings = context.tool_settings - layout.operator_context = 'INVOKE_REGION_WIN' - layout.prop(coat3D,"exportover") - if(coat3D.exportover): - layout.prop(coat3D,"exportmod") - -class VIEW3D_MT_ExtraMenu(bpy.types.Menu): - bl_label = "Extra" - - def draw(self, context): - layout = self.layout - coat3D = bpy.context.scene.coat3D - settings = context.tool_settings - layout.operator_context = 'INVOKE_REGION_WIN' - - layout.operator("import_applink.pilgway_3d_deltex",text="Delete all Textures") - layout.separator() - -def register(): - bpy.utils.register_module(__name__) - - kc = bpy.context.window_manager.keyconfigs.addon - if kc: - km = kc.keymaps.new(name='3D View', space_type='VIEW_3D') - kmi = km.keymap_items.new('wm.call_menu2', 'Q', 'PRESS') - kmi.properties.name = "VIEW3D_MT_Coat_Dynamic_Menu" - -def unregister(): - bpy.utils.unregister_module(__name__) - - kc = bpy.context.window_manager.keyconfigs.addon - if kc: - km = kc.keymapskeymaps['3D View'] - for kmi in km.keymap_items: - if kmi.idname == '': - if kmi.properties.name == "VIEW3D_MT_Coat_Dynamic_Menu": - km.keymap_items.remove(kmi) - break - - -if __name__ == "__main__": - register() diff --git a/io_coat3D/tex.py b/io_coat3D/tex.py index f532177c..7af4d68e 100644 --- a/io_coat3D/tex.py +++ b/io_coat3D/tex.py @@ -19,8 +19,9 @@ import bpy import os - +import re def find_index(objekti): + luku = 0 for tex in objekti.active_material.texture_slots: if(not(hasattr(tex,'texture'))): @@ -28,308 +29,371 @@ def find_index(objekti): luku = luku +1 return luku -def gettex(mat_list, objekti, scene,export): +def RemoveFbxNodes(objekti): + Node_Tree = objekti.active_material.node_tree + for node in Node_Tree.nodes: + if node.type != 'OUTPUT_MATERIAL': + Node_Tree.nodes.remove(node) + else: + output = node + output.location = 340,400 + Prin_mat = Node_Tree.nodes.new(type="ShaderNodeBsdfPrincipled") + Prin_mat.location = 13, 375 + + Node_Tree.links.new(Prin_mat.outputs[0], output.inputs[0]) + +def readtexturefolder(objekti, mat_list, texturelist, is_new): #read textures from texture file + + create_nodes = False + for index_mat in objekti.material_slots: + + texcoat = {} + texcoat['color'] = [] + texcoat['ao'] = [] + texcoat['rough'] = [] + texcoat['metalness'] = [] + texcoat['nmap'] = [] + texcoat['disp'] = [] + texcoat['emissive'] = [] + texcoat['emissive_power'] = [] + texcoat['displacement'] = [] + + + for texture_info in texturelist: + if texture_info[0] == index_mat.name: + if texture_info[2] == 'color' or texture_info[2] == 'diffuse': + texcoat['color'].append(texture_info[3]) + create_nodes = True + if texture_info[2] == 'metalness' or texture_info[2] == 'specular' or texture_info[2] == 'reflection': + texcoat['metalness'].append(texture_info[3]) + create_nodes = True + if texture_info[2] == 'rough' or texture_info[2] == 'roughness': + texcoat['rough'].append(texture_info[3]) + create_nodes = True + if texture_info[2] == 'nmap' or texture_info[2] == 'normalmap' or texture_info[2] == 'normal_map': + texcoat['nmap'].append(texture_info[3]) + create_nodes = True + if texture_info[2] == 'emissive': + texcoat['emissive'].append(texture_info[3]) + create_nodes = True + if texture_info[2] == 'emissive_power': + texcoat['emissive_power'].append(texture_info[3]) + create_nodes = True + if texture_info[2] == 'ao': + texcoat['ao'].append(texture_info[3]) + create_nodes = True + if texture_info[2].startswith('displacement'): + texcoat['displacement'].append(texture_info[3]) + create_nodes = True + if(create_nodes): + coat3D = bpy.context.scene.coat3D + path3b_n = coat3D.exchangedir + path3b_n += ('%slast_saved_3b_file.txt' % (os.sep)) + + if (os.path.isfile(path3b_n)): + export_file = open(path3b_n) + for line in export_file: + objekti.coat3D.applink_3b_path = line + export_file.close() + coat3D.remove_path = True + createnodes(index_mat, texcoat) + +def checkmaterial(mat_list, objekti): #check how many materials object has + mat_list = [] + + for obj_mate in objekti.material_slots: + if(obj_mate.material.use_nodes == False): + obj_mate.material.use_nodes = True + +def createnodes(active_mat,texcoat): # Cretes new nodes and link textures into them + bring_color = True # Meaning of these is to check if we can only update textures or do we need to create new nodes + bring_metalness = True + bring_roughness = True + bring_normal = True + bring_displacement = True + bring_AO = True coat3D = bpy.context.scene.coat3D - coa = objekti.coat3D + coatMat = active_mat.material + + if(coatMat.use_nodes == False): + coatMat.use_nodes = True + act_material = coatMat.node_tree + main_material = coatMat.node_tree + applink_group_node = False + #ensimmaiseksi kaydaan kaikki image nodet lapi ja tarkistetaan onko nimi 3DC alkunen jos on niin reload + + for node in coatMat.node_tree.nodes: + if (node.type == 'OUTPUT_MATERIAL'): + out_mat = node + break - if(bpy.context.scene.render.engine == 'VRAY_RENDER' or bpy.context.scene.render.engine == 'VRAY_RENDER_PREVIEW'): - vray = True - else: - vray = False - - take_color = 0 - take_spec = 0 - take_normal = 0 - take_disp = 0 - - bring_color = 1 - bring_spec = 1 - bring_normal = 1 - bring_disp = 1 - - texcoat = {} - texcoat['color'] = [] - texcoat['specular'] = [] - texcoat['nmap'] = [] - texcoat['disp'] = [] - texu = [] - - if(export): - objekti.coat3D.objpath = export - nimi = os.path.split(export)[1] - osoite = os.path.dirname(export) + os.sep #pitaa ehka muuttaa - for mate in objekti.material_slots: - for tex_slot in mate.material.texture_slots: - if(hasattr(tex_slot,'texture')): - if(tex_slot.texture.type == 'IMAGE'): - if tex_slot.texture.image is not None: - tex_slot.texture.image.reload() + for node in act_material.nodes: + if(node.name == '3DC_Applink' and node.type == 'GROUP'): + applink_group_node = True + act_material = node.node_tree + group_tree = node.node_tree + applink_tree = node + break + + print('TeXture UPDATE happens') + for node in act_material.nodes: + if(node.type == 'TEX_IMAGE'): + if(node.name == '3DC_color'): + bring_color = False + node.image.reload() + elif(node.name == '3DC_metalness'): + bring_metalness = False + node.image.reload() + elif(node.name == '3DC_roughness'): + bring_roughness = False + node.image.reload() + elif(node.name == '3DC_normal'): + bring_normal = False + node.image.reload() + elif(node.name == '3DC_displacement'): + bring_displacement = False + node.image.reload() + elif (node.name == '3DC_AO'): + bring_AO = False + node.image.reload() + + #seuraavaksi lahdemme rakentamaan node tree. Lahdetaan Material Outputista rakentaa + + if(applink_group_node == False and coat3D.creategroup): + group_tree = bpy.data.node_groups.new( type="ShaderNodeTree", name="3DC_Applink") + group_tree.outputs.new("NodeSocketColor", "Color") + group_tree.outputs.new("NodeSocketColor", "Metallic") + group_tree.outputs.new("NodeSocketColor", "Roughness") + group_tree.outputs.new("NodeSocketVector", "Normal map") + group_tree.outputs.new("NodeSocketColor", "Displacement") + group_tree.outputs.new("NodeSocketColor", "Emissive") + group_tree.outputs.new("NodeSocketColor", "Emissive Power") + group_tree.outputs.new("NodeSocketColor", "AO") + applink_tree = act_material.nodes.new('ShaderNodeGroup') + applink_tree.name = '3DC_Applink' + applink_tree.node_tree = group_tree + applink_tree.location = -400, 300 + act_material = group_tree + notegroup = act_material.nodes.new('NodeGroupOutput') + notegroup.location = 220, -260 else: - if(os.sys.platform == 'win32'): - osoite = os.path.expanduser("~") + os.sep + 'Documents' + os.sep + '3DC2Blender' + os.sep + 'Textures' + os.sep + index = 0 + for node in coatMat.node_tree.nodes: + if (node.type == 'GROUP' and node.name =='3DC_Applink'): + for in_node in node.node_tree.nodes: + if(in_node.type == 'GROUP_OUTPUT'): + notegroup = in_node + index = 1 + break + if(index == 1): + break + + if(out_mat.inputs['Surface'].is_linked == True): + main_mat = out_mat.inputs['Surface'].links[0].from_node + if(main_mat.inputs.find('Base Color') == -1): + input_color = main_mat.inputs.find('Color') else: - osoite = os.path.expanduser("~") + os.sep + '3DC2Blender' + os.sep + 'Textures' + os.sep - ki = os.path.split(coa.applink_name)[1] - ko = os.path.splitext(ki)[0] - just_nimi = ko + '_' - just_nimi_len = len(just_nimi) - print('terve:' + coa.applink_name) - - if(len(objekti.material_slots) != 0): - for obj_tex in objekti.active_material.texture_slots: - if(hasattr(obj_tex,'texture')): - if(obj_tex.texture.type == 'IMAGE'): - if(obj_tex.use_map_color_diffuse): - bring_color = 0; - if(obj_tex.use_map_specular): - bring_spec = 0; - if(obj_tex.use_map_normal): - bring_normal = 0; - if(obj_tex.use_map_displacement): - bring_disp = 0; - - files = os.listdir(osoite) - for i in files: - tui = i[:just_nimi_len] - if(tui == just_nimi): - texu.append(i) - - for yy in texu: - minimi = (yy.rfind('_'))+1 - maksimi = (yy.rfind('.')) - tex_name = yy[minimi:maksimi] - koko = '' - koko += osoite - koko += yy - texcoat[tex_name].append(koko) - - if((texcoat['color'] or texcoat['nmap'] or texcoat['disp'] or texcoat['specular']) and (len(objekti.material_slots)) == 0): - materials_old = bpy.data.materials.keys() - bpy.ops.material.new() - materials_new = bpy.data.materials.keys() - new_ma = list(set(materials_new).difference(set(materials_old))) - new_mat = new_ma[0] - ki = bpy.data.materials[new_mat] - objekti.data.materials.append(ki) - - if(bring_color == 1 and texcoat['color']): - index = find_index(objekti) - tex = bpy.ops.Texture - objekti.active_material.texture_slots.create(index) - total_mat = len(objekti.active_material.texture_slots.items()) - useold = '' - - for seekco in bpy.data.textures: - if((seekco.name[:5] == 'Color') and (seekco.users_material == ())): - useold = seekco - - - if(useold == ''): - - textures_old = bpy.data.textures.keys() - bpy.data.textures.new('Color',type='IMAGE') - textures_new = bpy.data.textures.keys() - name_te = list(set(textures_new).difference(set(textures_old))) - name_tex = name_te[0] - - bpy.ops.image.new(name=name_tex) - bpy.data.images[name_tex].filepath = texcoat['color'][0] - bpy.data.images[name_tex].source = 'FILE' - - objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex] - objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex] - - if(objekti.data.uv_textures.active): - objekti.active_material.texture_slots[index].texture_coords = 'UV' - objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name - - objekti.active_material.texture_slots[index].texture.image.reload() - - - elif(useold != ''): - - objekti.active_material.texture_slots[index].texture = useold - objekti.active_material.texture_slots[index].texture.image = bpy.data.images[useold.name] - objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['color'][0] - if(objekti.data.uv_textures.active): - objekti.active_material.texture_slots[index].texture_coords = 'UV' - objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name - - - if(bring_normal == 1 and texcoat['nmap']): - index = find_index(objekti) - tex = bpy.ops.Texture - objekti.active_material.texture_slots.create(index) - total_mat = len(objekti.active_material.texture_slots.items()) - useold = '' - - for seekco in bpy.data.textures: - if((seekco.name[:6] == 'Normal') and (seekco.users_material == ())): - useold = seekco - - if(useold == ''): - - textures_old = bpy.data.textures.keys() - bpy.data.textures.new('Normal',type='IMAGE') - textures_new = bpy.data.textures.keys() - name_te = list(set(textures_new).difference(set(textures_old))) - name_tex = name_te[0] - - bpy.ops.image.new(name=name_tex) - bpy.data.images[name_tex].filepath = texcoat['nmap'][0] - bpy.data.images[name_tex].source = 'FILE' - - objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex] - objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex] - - if(objekti.data.uv_textures.active): - objekti.active_material.texture_slots[index].texture_coords = 'UV' - objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name - - objekti.active_material.texture_slots[index].use_map_color_diffuse = False - objekti.active_material.texture_slots[index].use_map_normal = True - - objekti.active_material.texture_slots[index].texture.image.reload() - if(vray): - bpy.data.textures[name_tex].vray_slot.BRDFBump.map_type = 'TANGENT' - + input_color = main_mat.inputs.find('Base Color') + + ''' COLOR ''' + + if(bring_color == True and texcoat['color'] != []): + print('Color:', texcoat['color'][0]) + node = act_material.nodes.new('ShaderNodeTexImage') + node.name = '3DC_color' + node.label = 'Color' + if (texcoat['color']): + node.image = bpy.data.images.load(texcoat['color'][0]) + + if(coat3D.createnodes): + curvenode = act_material.nodes.new('ShaderNodeRGBCurve') + curvenode.name = '3DC_RGBCurve' + huenode = act_material.nodes.new('ShaderNodeHueSaturation') + huenode.name = '3DC_HueSaturation' + + act_material.links.new(curvenode.outputs[0], huenode.inputs[4]) + act_material.links.new(node.outputs[0], curvenode.inputs[1]) + if(coat3D.creategroup): + act_material.links.new(huenode.outputs[0], notegroup.inputs[0]) + if(main_mat.type != 'MIX_SHADER'): + main_material.links.new(applink_tree.outputs[0],main_mat.inputs[input_color]) + else: + location = main_mat.location + applink_tree.location = main_mat.location[0], main_mat.location[1] + 200 + else: + act_material.links.new(huenode.outputs[0], main_mat.inputs[input_color]) + node.location = -990, 530 + curvenode.location = -660, 480 + huenode.location = -337, 335 else: - bpy.data.textures[name_tex].use_normal_map = True - objekti.active_material.texture_slots[index].normal_map_space = 'TANGENT' - objekti.active_material.texture_slots[index].normal_factor = 1 - - - - elif(useold != ''): - - objekti.active_material.texture_slots[index].texture = useold - objekti.active_material.texture_slots[index].texture.image = bpy.data.images[useold.name] - objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['nmap'][0] - if(objekti.data.uv_textures.active): - objekti.active_material.texture_slots[index].texture_coords = 'UV' - objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name - objekti.active_material.texture_slots[index].use_map_color_diffuse = False - objekti.active_material.texture_slots[index].use_map_normal = True - objekti.active_material.texture_slots[index].normal_factor = 1 - - - if(bring_spec == 1 and texcoat['specular']): - - index = find_index(objekti) - - objekti.active_material.texture_slots.create(index) - useold = '' - - for seekco in bpy.data.textures: - if((seekco.name[:8] == 'Specular') and (seekco.users_material == ())): - useold = seekco - - if(useold == ''): - - textures_old = bpy.data.textures.keys() - bpy.data.textures.new('Specular',type='IMAGE') - textures_new = bpy.data.textures.keys() - name_te = list(set(textures_new).difference(set(textures_old))) - name_tex = name_te[0] - - bpy.ops.image.new(name=name_tex) - bpy.data.images[name_tex].filepath = texcoat['specular'][0] - bpy.data.images[name_tex].source = 'FILE' + if (coat3D.creategroup): + node.location = -400, 400 + act_material.links.new(node.outputs[0], notegroup.inputs[len(notegroup.inputs)-1]) + if (input_color != -1): + main_material.links.new(applink_tree.outputs[len(applink_tree.outputs)-1], main_mat.inputs[input_color]) + + else: + node.location = -400,400 + if (input_color != -1): + act_material.links.new(node.outputs[0], main_mat.inputs[input_color]) + + ''' METALNESS ''' + + if(bring_metalness == True and texcoat['metalness'] != []): + node = act_material.nodes.new('ShaderNodeTexImage') + node.name='3DC_metalness' + node.label = 'Metalness' + input_color = main_mat.inputs.find('Metallic') + if(texcoat['metalness']): + node.image = bpy.data.images.load(texcoat['metalness'][0]) + node.color_space = 'NONE' + if (coat3D.createnodes): + curvenode = act_material.nodes.new('ShaderNodeRGBCurve') + curvenode.name = '3DC_RGBCurve' + huenode = act_material.nodes.new('ShaderNodeHueSaturation') + huenode.name = '3DC_HueSaturation' + + act_material.links.new(curvenode.outputs[0], huenode.inputs[4]) + act_material.links.new(node.outputs[0], curvenode.inputs[1]) + + if (coat3D.creategroup): + act_material.links.new(huenode.outputs[0], notegroup.inputs[1]) + if (main_mat.type == 'BSDF_PRINCIPLED'): + main_material.links.new(applink_tree.outputs[1], main_mat.inputs[input_color]) + else: + act_material.links.new(huenode.outputs[0], main_mat.inputs[input_color]) + + node.location = -994, 119 + curvenode.location = -668, 113 + huenode.location = -345, 118 - objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex] - objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex] - - if(objekti.data.uv_textures.active): - objekti.active_material.texture_slots[index].texture_coords = 'UV' - objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name - - objekti.active_material.texture_slots[index].use_map_color_diffuse = False - objekti.active_material.texture_slots[index].use_map_specular = True - - objekti.active_material.texture_slots[index].texture.image.reload() - - - elif(useold != ''): - - objekti.active_material.texture_slots[index].texture = useold - objekti.active_material.texture_slots[index].texture.image = bpy.data.images[useold.name] - objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['specular'][0] - if(objekti.data.uv_textures.active): - objekti.active_material.texture_slots[index].texture_coords = 'UV' - objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name - objekti.active_material.texture_slots[index].use_map_color_diffuse = False - objekti.active_material.texture_slots[index].use_map_specular = True - - if(bring_disp == 1 and texcoat['disp']): - - index = find_index(objekti) - - - objekti.active_material.texture_slots.create(index) - useold = '' - - for seekco in bpy.data.textures: - if((seekco.name[:12] == 'Displacement') and (seekco.users_material == ())): - useold = seekco - - if useold == "": - - textures_old = bpy.data.textures.keys() - bpy.data.textures.new('Displacement',type='IMAGE') - textures_new = bpy.data.textures.keys() - name_te = list(set(textures_new).difference(set(textures_old))) - name_tex = name_te[0] - - bpy.ops.image.new(name=name_tex) - bpy.data.images[name_tex].filepath = texcoat['disp'][0] - bpy.data.images[name_tex].source = 'FILE' - - objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex] - objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex] + else: + if (coat3D.creategroup): + node.location = -830, 160 + act_material.links.new(node.outputs[0], notegroup.inputs[len(notegroup.inputs)-1]) + if (input_color != -1): + main_material.links.new(applink_tree.outputs[len(applink_tree.outputs)-1], main_mat.inputs[input_color]) + else: + node.location = -830, 160 + if (input_color != -1): + act_material.links.new(node.outputs[0], main_mat.inputs[input_color]) + + ''' ROUGHNESS ''' + + if(bring_roughness == True and texcoat['rough'] != []): + node = act_material.nodes.new('ShaderNodeTexImage') + node.name='3DC_roughness' + node.label = 'Roughness' + input_color = main_mat.inputs.find('Roughness') + if(texcoat['rough']): + node.image = bpy.data.images.load(texcoat['rough'][0]) + node.color_space = 'NONE' + + if (coat3D.createnodes): + curvenode = act_material.nodes.new('ShaderNodeRGBCurve') + curvenode.name = '3DC_RGBCurve' + huenode = act_material.nodes.new('ShaderNodeHueSaturation') + huenode.name = '3DC_HueSaturation' + + act_material.links.new(curvenode.outputs[0], huenode.inputs[4]) + act_material.links.new(node.outputs[0], curvenode.inputs[1]) + + if (coat3D.creategroup): + act_material.links.new(huenode.outputs[0], notegroup.inputs[2]) + if(main_mat.type == 'BSDF_PRINCIPLED'): + main_material.links.new(applink_tree.outputs[2], main_mat.inputs[input_color]) + else: + act_material.links.new(huenode.outputs[0], main_mat.inputs[input_color]) + + node.location = -1000, -276 + curvenode.location = -670, -245 + huenode.location = -340, -100 - if(objekti.data.uv_textures.active): - objekti.active_material.texture_slots[index].texture_coords = 'UV' - objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name + else: + if (coat3D.creategroup): + node.location = -550, 0 + act_material.links.new(node.outputs[0],notegroup.inputs[len(notegroup.inputs)-1]) + if (input_color != -1): + main_material.links.new(applink_tree.outputs[len(applink_tree.outputs)-1], main_mat.inputs[input_color]) + + else: + node.location = -550, 0 + if (input_color != -1): + act_material.links.new(node.outputs[0], main_mat.inputs[input_color]) + + ''' NORMAL MAP''' + + if(bring_normal == True and texcoat['nmap'] != []): + node = act_material.nodes.new('ShaderNodeTexImage') + normal_node = act_material.nodes.new('ShaderNodeNormalMap') + + node.location = -600,-670 + normal_node.location = -300,-300 + + node.name='3DC_normal' + node.label = 'Normal Map' + normal_node.name='3DC_normalnode' + if(texcoat['nmap']): + node.image = bpy.data.images.load(texcoat['nmap'][0]) + node.color_space = 'NONE' + input_color = main_mat.inputs.find('Normal') + act_material.links.new(node.outputs[0], normal_node.inputs[1]) + act_material.links.new(normal_node.outputs[0], main_mat.inputs[input_color]) + if (coat3D.creategroup): + act_material.links.new(normal_node.outputs[0], notegroup.inputs[3]) + if(main_mat.inputs[input_color].name == 'Normal'): + main_material.links.new(applink_tree.outputs[3], main_mat.inputs[input_color]) + + ''' DISPLACEMENT ''' + + if (bring_displacement == True and texcoat['displacement'] != []): + node = act_material.nodes.new('ShaderNodeTexImage') + node.name = '3DC_displacement' + node.label = 'Displacement' + # input_color = main_mat.inputs.find('Roughness') Blender 2.8 Does not support Displacement yet. + if (texcoat['displacement']): + node.image = bpy.data.images.load(texcoat['displacement'][0]) + node.color_space = 'NONE' + + if (coat3D.createnodes): + ''' + curvenode = act_material.nodes.new('ShaderNodeRGBCurve') + curvenode.name = '3DC_RGBCurve' + huenode = act_material.nodes.new('ShaderNodeHueSaturation') + huenode.name = '3DC_HueSaturation' + + act_material.links.new(curvenode.outputs[0], huenode.inputs[4]) + act_material.links.new(node.outputs[0], curvenode.inputs[1]) + ''' + + if (coat3D.creategroup): + act_material.links.new(node.outputs[0], notegroup.inputs[4]) + + #if (main_mat.type == 'BSDF_PRINCIPLED'): + #main_material.links.new(applink_tree.outputs[2], main_mat.inputs[input_color]) + #else: + #act_material.links.new(huenode.outputs[0], main_mat.inputs[input_color]) + + node.location = -276, -579 - objekti.active_material.texture_slots[index].use_map_color_diffuse = False - objekti.active_material.texture_slots[index].use_map_displacement = True + else: + if (coat3D.creategroup): + node.location = -550, 0 + act_material.links.new(node.outputs[0], notegroup.inputs[len(notegroup.inputs) - 1]) - objekti.active_material.texture_slots[index].texture.image.reload() - elif(useold != ''): - objekti.active_material.texture_slots[index].texture = useold - objekti.active_material.texture_slots[index].texture.image = bpy.data.images[useold.name] - objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['disp'][0] - if(objekti.data.uv_textures.active): - objekti.active_material.texture_slots[index].texture_coords = 'UV' - objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name - objekti.active_material.texture_slots[index].use_map_color_diffuse = False - objekti.active_material.texture_slots[index].use_map_displacement = True - if(vray): - objekti.active_material.texture_slots[index].texture.use_interpolation = False - objekti.active_material.texture_slots[index].displacement_factor = 0.05 +def matlab(objekti,mat_list,texturelist,is_new): + ''' FBX Materials: remove all nodes and create princibles node''' + if(is_new): + RemoveFbxNodes(objekti) - else: - disp_modi = '' - for seek_modi in objekti.modifiers: - if(seek_modi.type == 'DISPLACE'): - disp_modi = seek_modi - break - if(disp_modi): - disp_modi.texture = objekti.active_material.texture_slots[index].texture - if(objekti.data.uv_textures.active): - disp_modi.texture_coords = 'UV' - disp_modi.uv_layer = objekti.data.uv_textures.active.name - else: - objekti.modifiers.new('Displace',type='DISPLACE') - objekti.modifiers['Displace'].texture = objekti.active_material.texture_slots[index].texture - if(objekti.data.uv_textures.active): - objekti.modifiers['Displace'].texture_coords = 'UV' - objekti.modifiers['Displace'].uv_layer = objekti.data.uv_textures.active.name + '''Main Loop for Texture Update''' + #checkmaterial(mat_list, objekti) + readtexturefolder(objekti,mat_list,texturelist,is_new) return('FINISHED') diff --git a/io_convert_image_to_mesh_img/__init__.py b/io_convert_image_to_mesh_img/__init__.py index 1e5bdddf..a884fb90 100644 --- a/io_convert_image_to_mesh_img/__init__.py +++ b/io_convert_image_to_mesh_img/__init__.py @@ -51,12 +51,12 @@ def menu_import(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_import) + bpy.types.TOPBAR_MT_file_import.append(menu_import) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_import) + bpy.types.TOPBAR_MT_file_import.remove(menu_import) if __name__ == '__main__': diff --git a/io_curve_svg/__init__.py b/io_curve_svg/__init__.py index 188e9189..c8a9988d 100644 --- a/io_curve_svg/__init__.py +++ b/io_curve_svg/__init__.py @@ -21,8 +21,7 @@ bl_info = { "name": "Scalable Vector Graphics (SVG) 1.1 format", "author": "JM Soler, Sergey Sharybin", - "version": (1, 0, 0), - "blender": (2, 57, 0), + "blender": (2, 80, 0), "location": "File > Import > Scalable Vector Graphics (.svg)", "description": "Import SVG as curves", "warning": "", @@ -53,13 +52,12 @@ class ImportSVG(bpy.types.Operator, ImportHelper): bl_options = {'UNDO'} filename_ext = ".svg" - filter_glob = StringProperty(default="*.svg", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.svg", options={'HIDDEN'}) def execute(self, context): from . import import_svg - return import_svg.load(self, context, - **self.as_keywords(ignore=("filter_glob",))) + return import_svg.load(self, context, filepath=self.filepath) def menu_func_import(self, context): @@ -68,15 +66,15 @@ def menu_func_import(self, context): def register(): - bpy.utils.register_module(__name__) + bpy.utils.register_class(ImportSVG) - bpy.types.INFO_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) def unregister(): - bpy.utils.unregister_module(__name__) + bpy.utils.unregister_class(ImportSVG) - bpy.types.INFO_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) # NOTES # - blender version is hardcoded diff --git a/io_curve_svg/import_svg.py b/io_curve_svg/import_svg.py index 6b45d91d..c7302711 100644 --- a/io_curve_svg/import_svg.py +++ b/io_curve_svg/import_svg.py @@ -122,14 +122,15 @@ def SVGParseFloat(s, i=0): return token, i -def SVGCreateCurve(): +def SVGCreateCurve(context): """ Create new curve object to hold splines in """ cu = bpy.data.curves.new("Curve", 'CURVE') obj = bpy.data.objects.new("Curve", cu) - bpy.context.scene.objects.link(obj) + + context['collection'].objects.link(obj) return obj @@ -217,8 +218,8 @@ def SVGMatrixFromNode(node, context): m = Matrix.Translation(Vector((x, y, 0.0))) if has_user_coordinate: if rect[0] != 0 and rect[1] != 0: - m = m * Matrix.Scale(w / rect[0], 4, Vector((1.0, 0.0, 0.0))) - m = m * Matrix.Scale(h / rect[1], 4, Vector((0.0, 1.0, 0.0))) + m = m @ Matrix.Scale(w / rect[0], 4, Vector((1.0, 0.0, 0.0))) + m = m @ Matrix.Scale(h / rect[1], 4, Vector((0.0, 1.0, 0.0))) if node.getAttribute('viewBox'): viewBox = node.getAttribute('viewBox').replace(',', ' ').split() @@ -241,11 +242,11 @@ def SVGMatrixFromNode(node, context): tx = (w - vw * scale) / 2 ty = (h - vh * scale) / 2 - m = m * Matrix.Translation(Vector((tx, ty, 0.0))) + m = m @ Matrix.Translation(Vector((tx, ty, 0.0))) - m = m * Matrix.Translation(Vector((-vx, -vy, 0.0))) - m = m * Matrix.Scale(scale, 4, Vector((1.0, 0.0, 0.0))) - m = m * Matrix.Scale(scale, 4, Vector((0.0, 1.0, 0.0))) + m = m @ Matrix.Translation(Vector((-vx, -vy, 0.0))) + m = m @ Matrix.Scale(scale, 4, Vector((1.0, 0.0, 0.0))) + m = m @ Matrix.Scale(scale, 4, Vector((0.0, 1.0, 0.0))) return m @@ -267,7 +268,7 @@ def SVGParseTransform(transform): if proc is None: raise Exception('Unknown trasnform function: ' + func) - m = m * proc(params) + m = m @ proc(params) return m @@ -308,7 +309,6 @@ def SVGGetMaterial(color, context): mat = bpy.data.materials.new(name='SVGMat') mat.diffuse_color = diffuse_color - mat.diffuse_intensity = 1.0 materials[color] = mat @@ -354,8 +354,8 @@ def SVGTransformScale(params): m = Matrix() - m = m * Matrix.Scale(sx, 4, Vector((1.0, 0.0, 0.0))) - m = m * Matrix.Scale(sy, 4, Vector((0.0, 1.0, 0.0))) + m = m @ Matrix.Scale(sx, 4, Vector((1.0, 0.0, 0.0))) + m = m @ Matrix.Scale(sy, 4, Vector((0.0, 1.0, 0.0))) return m @@ -399,7 +399,7 @@ def SVGTransformRotate(params): tm = Matrix.Translation(Vector((cx, cy, 0.0))) rm = Matrix.Rotation(ang, 4, Vector((0.0, 0.0, 1.0))) - return tm * rm * tm.inverted() + return tm @ rm @ tm.inverted() SVGTransforms = {'translate': SVGTransformTranslate, 'scale': SVGTransformScale, @@ -1038,7 +1038,7 @@ class SVGGeometry: """ self._context['transform'].append(matrix) - self._context['matrix'] = self._context['matrix'] * matrix + self._context['matrix'] = self._context['matrix'] @ matrix def _popMatrix(self): """ @@ -1046,7 +1046,7 @@ class SVGGeometry: """ matrix = self._context['transform'].pop() - self._context['matrix'] = self._context['matrix'] * matrix.inverted() + self._context['matrix'] = self._context['matrix'] @ matrix.inverted() def _pushStyle(self, style): """ @@ -1071,7 +1071,7 @@ class SVGGeometry: v = Vector((point[0], point[1], 0.0)) - return self._context['matrix'] * v + return self._context['matrix'] @ v def getNodeMatrix(self): """ @@ -1219,7 +1219,7 @@ class SVGGeometryPATH(SVGGeometry): Create real geometries """ - ob = SVGCreateCurve() + ob = SVGCreateCurve(self._context) cu = ob.data if self._node.getAttribute('id'): @@ -1255,7 +1255,7 @@ class SVGGeometryPATH(SVGGeometry): act_spline = cu.splines[-1] act_spline.use_cyclic_u = spline['closed'] else: - act_spline.bezier_points.add() + act_spline.bezier_points.add(1) bezt = act_spline.bezier_points[-1] bezt.co = co @@ -1399,7 +1399,7 @@ class SVGGeometryRECT(SVGGeometry): co = self._transformCoord(coord) if not firstTime: - spline.bezier_points.add() + spline.bezier_points.add(1) bezt = spline.bezier_points[-1] bezt.co = co @@ -1450,7 +1450,7 @@ class SVGGeometryRECT(SVGGeometry): radius = (rx, ry) # Geometry creation - ob = SVGCreateCurve() + ob = SVGCreateCurve(self._context) cu = ob.data if self._styles['useFill']: @@ -1560,7 +1560,7 @@ class SVGGeometryELLIPSE(SVGGeometry): return # Create circle - ob = SVGCreateCurve() + ob = SVGCreateCurve(self._context) cu = ob.data if self._node.getAttribute('id'): @@ -1599,7 +1599,7 @@ class SVGGeometryELLIPSE(SVGGeometry): spline = cu.splines[-1] spline.use_cyclic_u = True else: - spline.bezier_points.add() + spline.bezier_points.add(1) bezt = spline.bezier_points[-1] bezt.co = co @@ -1677,7 +1677,7 @@ class SVGGeometryLINE(SVGGeometry): y2 = SVGParseCoord(self._y2, crect[1]) # Create cline - ob = SVGCreateCurve() + ob = SVGCreateCurve(self._context) cu = ob.data coords = [(x1, y1), (x2, y2)] @@ -1691,7 +1691,7 @@ class SVGGeometryLINE(SVGGeometry): spline = cu.splines[-1] spline.use_cyclic_u = True else: - spline.bezier_points.add() + spline.bezier_points.add(1) bezt = spline.bezier_points[-1] bezt.co = co @@ -1748,7 +1748,7 @@ class SVGGeometryPOLY(SVGGeometry): Create real geometries """ - ob = SVGCreateCurve() + ob = SVGCreateCurve(self._context) cu = ob.data if self._closed and self._styles['useFill']: @@ -1767,7 +1767,7 @@ class SVGGeometryPOLY(SVGGeometry): spline = cu.splines[-1] spline.use_cyclic_u = self._closed else: - spline.bezier_points.add() + spline.bezier_points.add(1) bezt = spline.bezier_points[-1] bezt.co = co @@ -1819,7 +1819,7 @@ class SVGGeometrySVG(SVGGeometryContainer): if self._node.getAttribute('inkscape:version'): raw_height = self._node.getAttribute('height') document_height = SVGParseCoord(raw_height, 1.0) - matrix = matrix * Matrix.Translation([0.0, -document_height , 0.0]) + matrix = matrix @ matrix.Translation([0.0, -document_height , 0.0]) self._pushMatrix(matrix) self._pushRect(rect) @@ -1845,16 +1845,22 @@ class SVGLoader(SVGGeometryContainer): return None - def __init__(self, filepath, do_colormanage): + def __init__(self, context, filepath, do_colormanage): """ Initialize SVG loader """ + import os + + svg_name = os.path.basename(filepath) + scene = context.scene + collection = bpy.data.collections.new(name=svg_name) + scene.collection.children.link(collection) node = xml.dom.minidom.parse(filepath) m = Matrix() - m = m * Matrix.Scale(1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((1.0, 0.0, 0.0))) - m = m * Matrix.Scale(-1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((0.0, 1.0, 0.0))) + m = m @ Matrix.Scale(1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((1.0, 0.0, 0.0))) + m = m @ Matrix.Scale(-1.0 / 90.0 * 0.3048 / 12.0, 4, Vector((0.0, 1.0, 0.0))) rect = (0, 0) @@ -1866,7 +1872,8 @@ class SVGLoader(SVGGeometryContainer): 'materials': {}, 'styles': [None], 'style': None, - 'do_colormanage': do_colormanage} + 'do_colormanage': do_colormanage, + 'collection': collection} super().__init__(node, self._context) @@ -1903,7 +1910,7 @@ def parseAbstractNode(node, context): return None -def load_svg(filepath, do_colormanage): +def load_svg(context, filepath, do_colormanage): """ Load specified SVG file """ @@ -1911,7 +1918,7 @@ def load_svg(filepath, do_colormanage): if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') - loader = SVGLoader(filepath, do_colormanage) + loader = SVGLoader(context, filepath, do_colormanage) loader.parse() loader.createGeom(False) @@ -1922,7 +1929,7 @@ def load(operator, context, filepath=""): # non SVG files can give useful messages. do_colormanage = context.scene.display_settings.display_device != 'NONE' try: - load_svg(filepath, do_colormanage) + load_svg(context, filepath, do_colormanage) except (xml.parsers.expat.ExpatError, UnicodeEncodeError) as e: import traceback traceback.print_exc() diff --git a/io_export_after_effects.py b/io_export_after_effects.py index bb7ec73c..d682497d 100644 --- a/io_export_after_effects.py +++ b/io_export_after_effects.py @@ -110,7 +110,7 @@ def get_selected(context): # not ready yet. is_plane(object) returns False in all cases. This is temporary solids.append([ob, convert_name(ob.name)]) - elif ob.type == 'LAMP': + elif ob.type == 'LIGHT': lights.append([ob, ob.data.type + convert_name(ob.name)]) # Type of lamp added to name else: @@ -768,12 +768,12 @@ def menu_func(self, context): def register(): bpy.utils.register_class(ExportJsx) - bpy.types.INFO_MT_file_export.append(menu_func) + bpy.types.TOPBAR_MT_file_export.append(menu_func) def unregister(): bpy.utils.unregister_class(ExportJsx) - bpy.types.INFO_MT_file_export.remove(menu_func) + bpy.types.TOPBAR_MT_file_export.remove(menu_func) if __name__ == "__main__": register() diff --git a/io_export_dxf/__init__.py b/io_export_dxf/__init__.py index 254ffdae..41fd79e6 100644 --- a/io_export_dxf/__init__.py +++ b/io_export_dxf/__init__.py @@ -45,7 +45,7 @@ classes = ( ) def register(): - bpy.types.INFO_MT_file_export.append(menu_func) + bpy.types.TOPBAR_MT_file_export.append(menu_func) from bpy.utils import register_class for cls in classes: @@ -53,7 +53,7 @@ def register(): def unregister(): - bpy.types.INFO_MT_file_export.remove(menu_func) + bpy.types.TOPBAR_MT_file_export.remove(menu_func) from bpy.utils import unregister_class for cls in reversed(classes): diff --git a/io_export_dxf/export_dxf.py b/io_export_dxf/export_dxf.py index df0dcddc..934ae90e 100644 --- a/io_export_dxf/export_dxf.py +++ b/io_export_dxf/export_dxf.py @@ -27,7 +27,7 @@ if DEBUG: from .model.migiusModel import MigiusDXFLibDrawing -SUPPORTED_TYPES = ('MESH')#,'CURVE','EMPTY','TEXT','CAMERA','LAMP') +SUPPORTED_TYPES = ('MESH')#,'CURVE','EMPTY','TEXT','CAMERA','LIGHT') def exportDXF(context, filePath, settings): """ @@ -277,8 +277,8 @@ def _exportItem(ctx, o, mw, drawing, settings): elif (o.type == 'CAMERA') and settings['camera_as']: from .primitive_exporters.camera_exporter import CameraDXFExporter e = CameraDXFExporter(settings) - elif (o.type == 'LAMP') and settings['lamp_as']: - from .primitive_exporters.lamp_exporter import LampDXFExporter + elif (o.type == 'LIGHT') and settings['light_as']: + from .primitive_exporters.light_exporter import LampDXFExporter e = LampDXFExporter(settings) return e.export(ctx, drawing, o, mx, mx_n, color=ecolor, layer=elayer, lineType=eltype) diff --git a/io_export_dxf/operator.py b/io_export_dxf/operator.py index e419608f..7e2dd7a7 100644 --- a/io_export_dxf/operator.py +++ b/io_export_dxf/operator.py @@ -160,10 +160,10 @@ class DXFExporter(bpy.types.Operator): # ('VIEW', 'VIEW', ''), # ('POINT', 'POINT', '') # ) -# lamp_asItems = ( +# light_asItems = ( # ('NO', 'Do not export', ''), # ('..BLOCK', '..BLOCK', ''), -# ('..A_LAMP', '..A_LAMP', ''), +# ('..A_LIGHT', '..A_LIGHT', ''), # ('POINT', 'POINT', '') # ) # --------- CONTROL PROPERTIES -------------------------------------------- @@ -207,9 +207,9 @@ class DXFExporter(bpy.types.Operator): # camera_as = EnumProperty( name="Export camera As:", default='NO', # description="Select representation of a camera", # items=camera_asItems) -# lamp_as = EnumProperty( name="Export lamp As:", default='NO', +# light_as = EnumProperty( name="Export lamp As:", default='NO', # description="Select representation of a lamp", -# items=lamp_asItems) +# items=light_asItems) # ---------------------------------------------------------- entitylayer_from = EnumProperty(name="Entity Layer", default="obj.data.name", description="Entity LAYER assigned to?", @@ -265,7 +265,7 @@ class DXFExporter(bpy.types.Operator): # 'group_as' : self._checkNO(self.group_as), # 'proxy_as' : self._checkNO(self.proxy_as), # 'camera_as' : self._checkNO(self.camera_as), -# 'lamp_as' : self._checkNO(self.lamp_as), +# 'light_as' : self._checkNO(self.light_as), 'entitylayer_from' : self.entitylayer_from, 'entitycolor_from' : self.entitycolor_from, diff --git a/io_export_dxf/primitive_exporters/lamp_exporter.py b/io_export_dxf/primitive_exporters/lamp_exporter.py index c67eb032..849f0984 100644 --- a/io_export_dxf/primitive_exporters/lamp_exporter.py +++ b/io_export_dxf/primitive_exporters/lamp_exporter.py @@ -13,7 +13,7 @@ def exportLamp(ob, mx, mw, **common): [p] = toGlobalOrigin([p]) entities = [] - c = lamp_as_list[GUI_A['lamp_as'].val] + c = light_as_list[GUI_A['light_as'].val] if c=="POINT": # export as POINT dxfPOINT = DXF.Point(points=[p],**common) entities.append(dxfPOINT) diff --git a/io_export_paper_model.py b/io_export_paper_model.py index 38444b37..0906eb33 100644 --- a/io_export_paper_model.py +++ b/io_export_paper_model.py @@ -2238,8 +2238,8 @@ class ExportPaperModel(bpy.types.Operator): row = layout.row(align=True) row.menu("VIEW3D_MT_paper_model_presets", text=bpy.types.VIEW3D_MT_paper_model_presets.bl_label) - row.operator("export_mesh.paper_model_preset_add", text="", icon='ZOOMIN') - row.operator("export_mesh.paper_model_preset_add", text="", icon='ZOOMOUT').remove_active = True + row.operator("export_mesh.paper_model_preset_add", text="", icon='ADD') + row.operator("export_mesh.paper_model_preset_add", text="", icon='REMOVE').remove_active = True # a little hack: this prints out something like "Scale: 1: 72" layout.prop(self.properties, "scale", text="Scale: 1") @@ -2582,12 +2582,12 @@ def register(): bpy.types.Mesh.paper_island_index = bpy.props.IntProperty( name="Island List Index", default=-1, min=-1, max=100, options={'SKIP_SAVE'}) - bpy.types.INFO_MT_file_export.append(menu_func) + bpy.types.TOPBAR_MT_file_export.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_export.remove(menu_func) + bpy.types.TOPBAR_MT_file_export.remove(menu_func) if display_islands.handle: bpy.types.SpaceView3D.draw_handler_remove(display_islands.handle, 'WINDOW') display_islands.handle = None diff --git a/io_export_pc2.py b/io_export_pc2.py index 752df5c4..7384e21e 100644 --- a/io_export_pc2.py +++ b/io_export_pc2.py @@ -19,8 +19,8 @@ bl_info = { "name": "Export Pointcache Format(.pc2)", "author": "Florian Meyer (tstscr)", - "version": (1, 1, 1), - "blender": (2, 71, 0), + "version": (1, 1, 2), + "blender": (2, 80, 0), "location": "File > Export > Pointcache (.pc2)", "description": "Export mesh Pointcache data (.pc2)", "warning": "", @@ -50,9 +50,11 @@ import time import math import struct + def get_sampled_frames(start, end, sampling): return [math.modf(start + x * sampling) for x in range(int((end - start) / sampling) + 1)] + def do_export(context, props, filepath): mat_x90 = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X') ob = context.active_object @@ -61,13 +63,13 @@ def do_export(context, props, filepath): end = props.range_end sampling = float(props.sampling) apply_modifiers = props.apply_modifiers - me = ob.to_mesh(sc, apply_modifiers, 'PREVIEW') + me = ob.to_mesh(context.depsgraph, apply_modifiers) vertCount = len(me.vertices) sampletimes = get_sampled_frames(start, end, sampling) sampleCount = len(sampletimes) # Create the header - headerFormat='<12siiffi' + headerFormat = '<12siiffi' headerStr = struct.pack(headerFormat, b'POINTCACHE2\0', 1, vertCount, start, sampling, sampleCount) @@ -75,8 +77,9 @@ def do_export(context, props, filepath): file.write(headerStr) for frame in sampletimes: - sc.frame_set(int(frame[1]), frame[0]) # stupid modf() gives decimal part first! - me = ob.to_mesh(sc, apply_modifiers, 'PREVIEW') + # stupid modf() gives decimal part first! + sc.frame_set(int(frame[1]), subframe=frame[0]) + me = ob.to_mesh(context.depsgraph, apply_modifiers) if len(me.vertices) != vertCount: bpy.data.meshes.remove(me, do_unlink=True) @@ -97,19 +100,18 @@ def do_export(context, props, filepath): for v in me.vertices: thisVertex = struct.pack('<fff', float(v.co[0]), - float(v.co[1]), - float(v.co[2])) + float(v.co[1]), + float(v.co[2])) file.write(thisVertex) bpy.data.meshes.remove(me, do_unlink=True) - file.flush() file.close() return True -###### EXPORT OPERATOR ####### +# EXPORT OPERATOR class Export_pc2(bpy.types.Operator, ExportHelper): """Export the active Object as a .pc2 Pointcache file""" bl_idname = "export_shape.pc2" @@ -117,50 +119,51 @@ class Export_pc2(bpy.types.Operator, ExportHelper): filename_ext = ".pc2" - rot_x90 = BoolProperty(name="Convert to Y-up", - description="Rotate 90 degrees around X to convert to y-up", - default=True, - ) - world_space = BoolProperty(name="Export into Worldspace", - description="Transform the Vertexcoordinates into Worldspace", - default=False, - ) - apply_modifiers = BoolProperty(name="Apply Modifiers", - description="Applies the Modifiers", - default=True, - ) - range_start = IntProperty(name='Start Frame', - description='First frame to use for Export', - default=1, - ) - range_end = IntProperty(name='End Frame', - description='Last frame to use for Export', - default=250, - ) - sampling = EnumProperty(name='Sampling', - description='Sampling --> frames per sample (0.1 yields 10 samples per frame)', - items=(('0.01', '0.01', ''), - ('0.05', '0.05', ''), - ('0.1', '0.1', ''), - ('0.2', '0.2', ''), - ('0.25', '0.25', ''), - ('0.5', '0.5', ''), - ('1', '1', ''), - ('2', '2', ''), - ('3', '3', ''), - ('4', '4', ''), - ('5', '5', ''), - ('10', '10', ''), - ), - default='1', - ) + rot_x90: BoolProperty( + name="Convert to Y-up", + description="Rotate 90 degrees around X to convert to y-up", + default=True,) + world_space: BoolProperty( + name="Export into Worldspace", + description="Transform the Vertexcoordinates into Worldspace", + default=False,) + apply_modifiers: BoolProperty( + name="Apply Modifiers", + description="Applies the Modifiers", + default=True,) + range_start: IntProperty( + name='Start Frame', + description='First frame to use for Export', + default=1,) + range_end: IntProperty( + name='End Frame', + description='Last frame to use for Export', + default=250,) + sampling: EnumProperty( + name='Sampling', + description='Sampling --> frames per sample (0.1 yields 10 samples per frame)', + items=(('0.01', '0.01', ''), + ('0.05', '0.05', ''), + ('0.1', '0.1', ''), + ('0.2', '0.2', ''), + ('0.25', '0.25', ''), + ('0.5', '0.5', ''), + ('1', '1', ''), + ('2', '2', ''), + ('3', '3', ''), + ('4', '4', ''), + ('5', '5', ''), + ('10', '10', ''), + ), + default='1', + ) @classmethod def poll(cls, context): obj = context.active_object return ( - obj is not None and - obj.type in {'MESH', 'CURVE', 'SURFACE', 'FONT'} + obj is not None + and obj.type in {'MESH', 'CURVE', 'SURFACE', 'FONT'} ) def execute(self, context): @@ -173,7 +176,8 @@ class Export_pc2(bpy.types.Operator, ExportHelper): exported = do_export(context, props, filepath) if exported: - print('finished export in %s seconds' %((time.time() - start_time))) + print('finished export in %s seconds' % + ((time.time() - start_time))) print(filepath) return {'FINISHED'} @@ -183,7 +187,7 @@ class Export_pc2(bpy.types.Operator, ExportHelper): if True: # File selector - wm.fileselect_add(self) # will run self.execute() + wm.fileselect_add(self) # will run self.execute() return {'RUNNING_MODAL'} elif True: # search the enum @@ -196,23 +200,29 @@ class Export_pc2(bpy.types.Operator, ExportHelper): return self.execute(context) -### REGISTER ### - -def menu_func(self, context): +def menu_func_export_button(self, context): self.layout.operator(Export_pc2.bl_idname, text="Pointcache (.pc2)") +classes = [ + Export_pc2, +] + + def register(): - bpy.utils.register_module(__name__) + for cls in classes: + bpy.utils.register_class(cls) + + bpy.types.TOPBAR_MT_file_export.append(menu_func_export_button) + #bpy.types.VIEW3D_PT_tools_objectmode.prepend(menu_func_export_button) - bpy.types.INFO_MT_file_export.append(menu_func) - #bpy.types.VIEW3D_PT_tools_objectmode.prepend(menu_func) def unregister(): - bpy.utils.unregister_module(__name__) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export_button) + #bpy.types.VIEW3D_PT_tools_objectmode.remove(menu_func_export_button) + for cls in classes: + bpy.utils.unregister_class(cls) - bpy.types.INFO_MT_file_export.remove(menu_func) - #bpy.types.VIEW3D_PT_tools_objectmode.remove(menu_func) if __name__ == "__main__": register() diff --git a/io_export_unreal_psk_psa.py b/io_export_unreal_psk_psa.py index 6179470c..2f0a9fe2 100644 --- a/io_export_unreal_psk_psa.py +++ b/io_export_unreal_psk_psa.py @@ -1201,8 +1201,8 @@ def parse_mesh(mesh, psk): # does with the mesh Y coordinates. this is otherwise known as MAGIC-2 uv[1] = 1.0 - uv[1] - # clamp UV coords if udk_option_clamp_uv is True - if bpy.context.scene.udk_option_clamp_uv: + # clamp UV coords if udk_option_clight_uv is True + if bpy.context.scene.udk_option_clight_uv: if (uv[0] > 1): uv[0] = 1 if (uv[0] < 0): @@ -2170,7 +2170,7 @@ def rebuildmesh(obj): # vertices weight groups for vgroup in vertGroups: - group = obmesh.vertex_groups.new(vgroup) + group = obmesh.vertex_groups.new(name=vgroup) for v in vertGroups[vgroup]: group.add([v[0]], v[1], 'ADD') # group.add(array[vertex id],weight,add) bpy.context.scene.objects.link(obmesh) @@ -2422,7 +2422,7 @@ class Panel_UDKExport(Panel): object_name = context.active_object.name row10 = layout.row() row10.prop(context.scene, "udk_option_smoothing_groups") - row10.prop(context.scene, "udk_option_clamp_uv") + row10.prop(context.scene, "udk_option_clight_uv") row10.prop(context.scene, "udk_option_verbose") row = layout.row() @@ -2792,7 +2792,7 @@ class ExportUDKAnimData(Operator): scene = context.scene layout.prop(scene, "udk_option_smoothing_groups") - layout.prop(scene, "udk_option_clamp_uv") + layout.prop(scene, "udk_option_clight_uv") layout.prop(scene, "udk_option_verbose") layout.prop(scene, "udk_option_filename_src") layout.prop(scene, "udk_option_export") @@ -2865,7 +2865,7 @@ class PskAddonPreferences(AddonPreferences): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_export.append(menu_func) + bpy.types.TOPBAR_MT_file_export.append(menu_func) update_panel(None, bpy.context) # Added by [MGVS] @@ -2888,7 +2888,7 @@ def register(): description="Boolean for exporting psa format (Animation Data)", default=True ) - bpy.types.Scene.udk_option_clamp_uv = BoolProperty( + bpy.types.Scene.udk_option_clight_uv = BoolProperty( name="Clamp UV", description="True is to limit Clamp UV co-ordinates to [0-1]. False is unrestricted (x,y)", default=False @@ -2972,12 +2972,12 @@ def register(): def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_export.remove(menu_func) + bpy.types.TOPBAR_MT_file_export.remove(menu_func) del bpy.types.Scene.udk_option_filename_src del bpy.types.Scene.udk_option_export_psk del bpy.types.Scene.udk_option_export_psa - del bpy.types.Scene.udk_option_clamp_uv + del bpy.types.Scene.udk_option_clight_uv del bpy.types.Scene.udk_copy_merge del bpy.types.Scene.udk_option_export del bpy.types.Scene.udk_option_verbose diff --git a/io_import_dxf/__init__.py b/io_import_dxf/__init__.py index d768d624..46e84fd5 100644 --- a/io_import_dxf/__init__.py +++ b/io_import_dxf/__init__.py @@ -559,12 +559,12 @@ def menu_func(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_func) + bpy.types.TOPBAR_MT_file_import.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_func) + bpy.types.TOPBAR_MT_file_import.remove(menu_func) if __name__ == "__main__": diff --git a/io_import_dxf/dxfimport/do.py b/io_import_dxf/dxfimport/do.py index 7ac4c392..8db6b854 100644 --- a/io_import_dxf/dxfimport/do.py +++ b/io_import_dxf/dxfimport/do.py @@ -738,7 +738,7 @@ class Do: if self.import_light: type_map = ["NONE", "SUN", "POINT", "SPOT"] layer = self.dwg.layers[en.layer] - lamp = bpy.data.lamps.new(en.name, type_map[en.light_type]) + lamp = bpy.data.lights.new(en.name, type_map[en.light_type]) if en.color != 256: aci = en.color else: @@ -989,13 +989,13 @@ class Do: bbox = self._object_bbox(objects + inserts, block_scene, name, True) for i in inserts: - sub_group = i.dupli_group + sub_group = i.instance_collection block_scene.objects.unlink(i) block_group.objects.unlink(i) i_empty = bpy.data.objects.new(i.name, None) i_empty.matrix_basis = i.matrix_basis - i_empty.dupli_type = "GROUP" - i_empty.dupli_group = sub_group + i_empty.instance_type = "COLLECTION" + i_empty.instance_collection = sub_group block_group.objects.link(i_empty) block_scene.objects.link(i_empty) @@ -1005,9 +1005,9 @@ class Do: bpy.context.screen.scene = scene o = bbox.copy() - # o.empty_draw_size = 0.3 - o.dupli_type = "GROUP" - o.dupli_group = block_group + # o.empty_display_size = 0.3 + o.instance_type = "COLLECTION" + o.instance_collection = block_group group.objects.link(o) if invisible is not None: o.hide = invisible @@ -1021,7 +1021,7 @@ class Do: """ entity: DXF entity name: String; not used but required to be consistent with the methods being called from _call_type() - group: Blender group of type (bpy_types.group) being set if called from block() + group: Blender group of type (bpy_types.Collection) being set if called from block() invisible: boolean to control visibility; being set if called from block() """ aunits = self.dwg.header.get('$AUNITS', 0) @@ -1066,7 +1066,7 @@ class Do: entity.col_count, entity.row_count) o = bpy.data.objects.new(entity.name, dm) instance.parent = o - o.dupli_type = "VERTS" + o.instance_type = "VERTS" # insert transformations rot = radians(entity.rotation) if aunits == 0 else entity.rotation @@ -1342,11 +1342,11 @@ class Do: name: name of group (String) Finds group by name or creates it if it does not exist. """ - groups = bpy.data.groups + groups = bpy.data.collections if name in groups.keys(): group = groups[name] else: - group = bpy.data.groups.new(name) + group = bpy.data.collections.new(name) return group def _call_object_types(self, TYPE, entities, group, name, scene, separated=False): @@ -1466,9 +1466,9 @@ class Do: scene.objects.link(o) self._nest_block(o, blockname, blgroup, scene) - o.dupli_type = "FACES" - o.use_dupli_faces_scale = True - o.dupli_faces_scale = f + o.instance_type = "FACES" + o.use_instance_faces_scale = True + o.instance_faces_scale = f def _nest_block(self, parent, name, blgroup, scene): b = self.dwg.blocks[name] diff --git a/io_import_gimp_image_to_scene.py b/io_import_gimp_image_to_scene.py index 6ff1d66f..94d496e3 100644 --- a/io_import_gimp_image_to_scene.py +++ b/io_import_gimp_image_to_scene.py @@ -619,7 +619,7 @@ class GIMPImageToScene(bpy.types.Operator): if self.OpacityMode == 'COMPO' and self.SetupCompo == False: box.label('Tip: Enable Node Compositing', icon='INFO') box.prop(self, 'AlphaMode', icon='IMAGE_RGB_ALPHA') - box.prop(self, 'ShadelessMats', icon='SOLID') + box.prop(self, 'ShadelessMats', icon='SHADING_SOLID') box.prop(self, 'LayerOffset') box.prop(self, 'LayerScale') @@ -680,13 +680,13 @@ def menu_func(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_func) + bpy.types.TOPBAR_MT_file_import.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_func) + bpy.types.TOPBAR_MT_file_import.remove(menu_func) if __name__ == "__main__": diff --git a/io_import_images_as_planes.py b/io_import_images_as_planes.py index a4aa98f2..88cee32a 100644 --- a/io_import_images_as_planes.py +++ b/io_import_images_as_planes.py @@ -21,14 +21,15 @@ bl_info = { "name": "Import Images as Planes", "author": "Florian Meyer (tstscr), mont29, matali, Ted Schundler (SpkyElctrc)", - "version": (3, 1, 1), - "blender": (2, 78, 0), + "version": (3, 2, 1), + "blender": (2, 80, 0), "location": "File > Import > Images as Planes or Add > Mesh > Images as Planes", "description": "Imports images and creates planes with the appropriate aspect ratio. " "The images are mapped to the planes.", "warning": "", "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/Add_Mesh/Planes_from_Images", + "support": 'OFFICIAL', "category": "Import-Export", } @@ -179,7 +180,6 @@ def load_images(filenames, directory, force_reload=False, frame_start=1, find_se frames = image.frame_duration elif frames > 1: # Not movie, but multiple frames -> image sequence - image.use_animation = True image.source = 'SEQUENCE' yield ImageSpec(image, size, frame_start, offset - 1, frames) @@ -200,11 +200,10 @@ def offset_planes(planes, gap, axis): prior = planes[0] offset = Vector() for current in planes[1:]: - - local_offset = abs((prior.dimensions + current.dimensions) * axis) / 2.0 + gap + local_offset = abs((prior.dimensions + current.dimensions).dot(axis)) / 2.0 + gap offset += local_offset * axis - current.location = current.matrix_world * offset + current.location = current.matrix_world @ offset prior = current @@ -242,7 +241,7 @@ def compute_camera_size(context, center, fill_mode, aspect): def center_in_camera(scene, camera, obj, axis=(1, 1)): - """Center object along specified axiis of the camera""" + """Center object along specified axis of the camera""" camera_matrix_col = camera.matrix_world.col location = obj.location @@ -250,11 +249,11 @@ def center_in_camera(scene, camera, obj, axis=(1, 1)): delta = camera_matrix_col[3].xyz - location # How far off center we are along the camera's local X - camera_x_mag = delta * camera_matrix_col[0].xyz * axis[0] + camera_x_mag = delta.dot(camera_matrix_col[0].xyz) * axis[0] # How far off center we are along the camera's local Y - camera_y_mag = delta * camera_matrix_col[1].xyz * axis[1] + camera_y_mag = delta.dot(camera_matrix_col[1].xyz) * axis[1] - # Now offet only along camera local axiis + # Now offset only along camera local axis offset = camera_matrix_col[0].xyz * camera_x_mag + \ camera_matrix_col[1].xyz * camera_y_mag @@ -262,7 +261,7 @@ def center_in_camera(scene, camera, obj, axis=(1, 1)): # ----------------------------------------------------------------------------- -# Cycles utils +# Cycles/Eevee utils def get_input_nodes(node, links): """Get nodes that are a inputs to the given node""" @@ -327,7 +326,7 @@ def clean_node_tree(node_tree): def get_shadeless_node(dest_node_tree): - """Return a "shadless" cycles node, creating a node group if nonexistent""" + """Return a "shadless" cycles/eevee node, creating a node group if nonexistent""" try: node_tree = bpy.data.node_groups['IAP_SHADELESS'] @@ -474,7 +473,7 @@ def find_plane_corner(object_name, x, y, axis, camera=None, *args, **kwargs): v = plane.dimensions.copy() v.x *= x / scale.x v.y *= y / scale.y - v = plane.matrix_world * v + v = plane.matrix_world @ v camera_vertex = world_to_camera_view( bpy.context.scene, camera, v) @@ -614,22 +613,22 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): # ---------------------- # File dialog properties - files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'}) + files: CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'}) - directory = StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'}) + directory: StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'}) - filter_image = BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'}) - filter_movie = BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'}) - filter_folder = BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'}) + filter_image: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'}) + filter_movie: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'}) + filter_folder: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'}) # ---------------------- # Properties - Importing - force_reload = BoolProperty( + force_reload: BoolProperty( name="Force Reload", default=False, description="Force reloading of the image if already opened elsewhere in Blender" ) - image_sequence = BoolProperty( + image_sequence: BoolProperty( name="Animate Image Sequences", default=False, description="Import sequentially numbered images as an animated " "image sequence instead of separate planes" @@ -646,7 +645,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): 'Z-': Vector(( 0, 0, -1)), } - offset = BoolProperty(name="Offset Planes", default=True, description="Offset Planes From Each Other") + offset: BoolProperty(name="Offset Planes", default=True, description="Offset Planes From Each Other") OFFSET_MODES = ( ('X+', "X+", "Side by Side to the Left"), @@ -656,12 +655,12 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): ('Y-', "Y-", "Side by Side, Upward"), ('Z-', "Z-", "Stacked Below"), ) - offset_axis = EnumProperty( + offset_axis: EnumProperty( name="Orientation", default='X+', items=OFFSET_MODES, description="How planes are oriented relative to each others' local axis" ) - offset_amount = FloatProperty( + offset_amount: FloatProperty( name="Offset", soft_min=0, default=0.1, description="Space between planes", subtype='DISTANCE', unit='LENGTH' ) @@ -676,14 +675,14 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): ('CAM', "Face Camera", "Facing Camera"), ('CAM_AX', "Main Axis", "Facing the Camera's dominant axis"), ) - align_axis = EnumProperty( + align_axis: EnumProperty( name="Align", default='CAM_AX', items=AXIS_MODES, description="How to align the planes" ) # prev_align_axis is used only by update_size_model - prev_align_axis = EnumProperty( + prev_align_axis: EnumProperty( items=AXIS_MODES + (('NONE', '', ''),), default='NONE', options={'HIDDEN', 'SKIP_SAVE'}) - align_track = BoolProperty( + align_track: BoolProperty( name="Track Camera", default=False, description="Always face the camera" ) @@ -707,7 +706,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): ('DPI', "Dpi", "Use definition of the image as dots per inch"), ('DPBU', "Dots/BU", "Use definition of the image as dots per Blender Unit"), ) - size_mode = EnumProperty( + size_mode: EnumProperty( name="Size Mode", default='ABSOLUTE', items=SIZE_MODES, update=update_size_mode, description="How the size of the plane is computed") @@ -716,13 +715,13 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): ('FILL', "Fill", "Fill camera frame, spilling outside the frame"), ('FIT', "Fit", "Fit entire image within the camera frame"), ) - fill_mode = EnumProperty(name="Scale", default='FILL', items=FILL_MODES, + fill_mode: EnumProperty(name="Scale", default='FILL', items=FILL_MODES, description="How large in the camera frame is the plane") - height = FloatProperty(name="Height", description="Height of the created plane", + height: FloatProperty(name="Height", description="Height of the created plane", default=1.0, min=0.001, soft_min=0.001, subtype='DISTANCE', unit='LENGTH') - factor = FloatProperty(name="Definition", min=1.0, default=600.0, + factor: FloatProperty(name="Definition", min=1.0, default=600.0, description="Number of pixels per inch or Blender Unit") # ------------------------------ @@ -732,40 +731,37 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): ('SHADELESS', "Shadeless", "Only visible to camera and reflections."), ('EMISSION', "Emit", "Emission Shader"), ) - shader = EnumProperty(name="Shader", items=SHADERS, default='DIFFUSE', description="Node shader to use") + shader: EnumProperty(name="Shader", items=SHADERS, default='DIFFUSE', description="Node shader to use") - emit_strength = FloatProperty( + emit_strength: FloatProperty( name="Strength", min=0.0, default=1.0, soft_max=10.0, step=100, description="Brightness of Emission Texture") - overwrite_material = BoolProperty( + overwrite_material: BoolProperty( name="Overwrite Material", default=True, description="Overwrite existing Material (based on material name)") - compositing_nodes = BoolProperty( + compositing_nodes: BoolProperty( name="Setup Corner Pin", default=False, description="Build Compositor Nodes to reference this image " "without re-rendering") # ------------------ # Properties - Image - use_transparency = BoolProperty( + use_transparency: BoolProperty( name="Use Alpha", default=True, description="Use alphachannel for transparency") t = bpy.types.Image.bl_rna.properties["alpha_mode"] alpha_mode_items = tuple((e.identifier, e.name, e.description) for e in t.enum_items) - alpha_mode = EnumProperty( + alpha_mode: EnumProperty( name=t.name, items=alpha_mode_items, default=t.default, description=t.description) - t = bpy.types.Image.bl_rna.properties["use_fields"] - use_fields = BoolProperty(name=t.name, default=False, description=t.description) - t = bpy.types.ImageUser.bl_rna.properties["use_auto_refresh"] - use_auto_refresh = BoolProperty(name=t.name, default=True, description=t.description) + use_auto_refresh: BoolProperty(name=t.name, default=True, description=t.description) - relative = BoolProperty(name="Relative Paths", default=True, description="Use relative file paths") + relative: BoolProperty(name="Relative Paths", default=True, description="Use relative file paths") # ------- # Draw UI @@ -798,7 +794,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): box.prop(self, "emit_strength") engine = context.scene.render.engine - if engine not in ('CYCLES', 'BLENDER_RENDER'): + if engine not in ('CYCLES', 'BLENDER_EEVEE', 'BLENDER_OPENGL'): box.label(text="%s is not supported" % engine, icon='ERROR') box.prop(self, "overwrite_material") @@ -809,7 +805,6 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): sub = row.row() sub.active = self.use_transparency sub.prop(self, "alpha_mode", text="") - box.prop(self, "use_fields") box.prop(self, "use_auto_refresh") def draw_spatial_config(self, context): @@ -837,7 +832,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): else: box.prop(self, "factor") - box.label(text="Orientation:", icon='MANIPUL') + box.label(text="Orientation:") row = box.row() row.enabled = 'CAM' not in self.size_mode row.prop(self, "align_axis") @@ -857,9 +852,13 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): # Core functionality def invoke(self, context, event): engine = context.scene.render.engine - if engine not in ('CYCLES', 'BLENDER_RENDER', 'BLENDER_GAME'): - # Use default blender texture, but acknowledge things may not work - self.report({'WARNING'}, "Cannot generate materials for unknown %s render engine" % engine) + if engine not in {'CYCLES', 'BLENDER_EEVEE'}: + if engine not in {'BLENDER_OPENGL'}: + self.report({'ERROR'}, "Cannot generate materials for unknown %s render engine" % engine) + return {'CANCELLED'} + else: + self.report({'WARNING'}, + "Generating Cycles/EEVEE compatible material, but won't be visible with %s engine" % engine) # Open file browser context.window_manager.fileselect_add(self) @@ -910,7 +909,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): # setup new selection for plane in planes: - plane.select = True + plane.select_set(True) # all done! self.report({'INFO'}, "Added {} Image Plane(s)".format(len(planes))) @@ -923,22 +922,14 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): # Configure material engine = context.scene.render.engine - if engine == 'CYCLES': + if engine in {'CYCLES', 'BLENDER_EEVEE', 'BLENDER_OPENGL'}: material = self.create_cycles_material(context, img_spec) - else: - tex = self.create_image_textures(context, img_spec) - material = self.create_material_for_texture(tex) - - # Game Engine Material Settings - material.game_settings.use_backface_culling = False - material.game_settings.alpha_blend = 'ALPHA' # Create and position plane object plane = self.create_image_plane(context, material.name, img_spec) # Assign Material plane.data.materials.append(material) - plane.data.uv_textures[0].data[0].image = img_spec.image # If applicable, setup Corner Pin node if self.compositing_nodes: @@ -949,7 +940,6 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): def apply_image_options(self, image): image.use_alpha = self.use_transparency image.alpha_mode = self.alpha_mode - image.use_fields = self.use_fields if self.relative: try: # can't always find the relative path (between drive letters on windows) @@ -971,48 +961,6 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): texture.extension = 'CLIP' # Default of "Repeat" can cause artifacts - # ------------------------------------------------------------------------- - # Blender Internal Material - def create_image_textures(self, context, img_spec): - image = img_spec.image - fn_full = os.path.normpath(bpy.path.abspath(image.filepath)) - - # look for texture referencing this file - for texture in bpy.data.textures: - if texture.type == 'IMAGE': - tex_img = texture.image - if (tex_img is not None) and (tex_img.library is None): - fn_tex_full = os.path.normpath(bpy.path.abspath(tex_img.filepath)) - if fn_full == fn_tex_full: - if self.overwrite_material: - self.apply_texture_options(texture, img_spec) - return texture - - # if no texture is found: create one - name_compat = bpy.path.display_name_from_filepath(image.filepath) - texture = bpy.data.textures.new(name=name_compat, type='IMAGE') - texture.image = image - self.apply_texture_options(texture, img_spec) - return texture - - def create_material_for_texture(self, texture): - # look for material with the needed texture - for material in bpy.data.materials: - slot = material.texture_slots[0] - if slot and slot.texture == texture: - if self.overwrite_material: - self.apply_material_options(material, slot) - return material - - # if no material found: create one - name_compat = bpy.path.display_name_from_filepath(texture.image.filepath) - material = bpy.data.materials.new(name=name_compat) - slot = material.texture_slots.add() - slot.texture = texture - slot.texture_coords = 'UV' - self.apply_material_options(material, slot) - return material - def apply_material_options(self, material, slot): shader = self.shader @@ -1034,7 +982,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): material.emit = self.emit_strength if shader == 'EMISSION' else 0.0 # ------------------------------------------------------------------------- - # Cycles + # Cycles/Eevee def create_cycles_texnode(self, context, node_tree, img_spec): tex_image = node_tree.nodes.new('ShaderNodeTexImage') tex_image.image = img_spec.image @@ -1092,14 +1040,13 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): # Create new mesh bpy.ops.mesh.primitive_plane_add('INVOKE_REGION_WIN') - plane = context.scene.objects.active + plane = context.active_object # Why does mesh.primitive_plane_add leave the object in edit mode??? if plane.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT') plane.dimensions = width, height, 0.0 plane.data.name = plane.name = name bpy.ops.object.transform_apply(scale=True) - plane.data.uv_textures.new() # If sizing for camera, also insert into the camera's field of view if self.size_mode == 'CAMERA': @@ -1148,7 +1095,7 @@ class IMPORT_IMAGE_OT_to_plane(Operator, AddObjectHelper): camera = context.scene.camera if (camera): # Find the axis that best corresponds to the camera's view direction - axis = camera.matrix_world * \ + axis = camera.matrix_world @ \ Vector((0, 0, 1)) - camera.matrix_world.col[3].xyz # pick the axis with the greatest magnitude mag = max(map(abs, axis)) @@ -1213,16 +1160,16 @@ def register(): for cls in classes: bpy.utils.register_class(cls) - bpy.types.INFO_MT_file_import.append(import_images_button) - bpy.types.INFO_MT_mesh_add.append(import_images_button) + bpy.types.TOPBAR_MT_file_import.append(import_images_button) + bpy.types.VIEW3D_MT_image_add.append(import_images_button) bpy.app.handlers.load_post.append(register_driver) register_driver() def unregister(): - bpy.types.INFO_MT_file_import.remove(import_images_button) - bpy.types.INFO_MT_mesh_add.remove(import_images_button) + bpy.types.TOPBAR_MT_file_import.remove(import_images_button) + bpy.types.VIEW3D_MT_image_add.remove(import_images_button) # This will only exist if drivers are active if check_drivers in bpy.app.handlers.scene_update_post: diff --git a/io_import_scene_lwo.py b/io_import_scene_lwo.py index 3cfe207c..be5f55aa 100644 --- a/io_import_scene_lwo.py +++ b/io_import_scene_lwo.py @@ -1251,13 +1251,13 @@ def menu_func(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_func) + bpy.types.TOPBAR_MT_file_import.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_func) + bpy.types.TOPBAR_MT_file_import.remove(menu_func) if __name__ == "__main__": register() diff --git a/io_import_scene_unreal_psa_psk.py b/io_import_scene_unreal_psa_psk.py index 5fb0c8f6..d6896f6d 100644 --- a/io_import_scene_unreal_psa_psk.py +++ b/io_import_scene_unreal_psa_psk.py @@ -673,7 +673,7 @@ def pskimport(infile,importmesh,importbone,bDebugLogPSK,importmultiuvtextures): for bone in ob_new.data.bones: #print("names:", bone.name, ":", dir(bone)) #print("names:", bone.name) - group = obmesh.vertex_groups.new(bone.name) + group = obmesh.vertex_groups.new(name=bone.name) for vgroup in obmesh.vertex_groups: #print(vgroup.name, ":", vgroup.index) @@ -1275,11 +1275,11 @@ def menu_func(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_func) + bpy.types.TOPBAR_MT_file_import.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_func) + bpy.types.TOPBAR_MT_file_import.remove(menu_func) if __name__ == "__main__": register() diff --git a/io_mesh_pdb/__init__.py b/io_mesh_pdb/__init__.py index 1355d73b..4e731cf5 100644 --- a/io_mesh_pdb/__init__.py +++ b/io_mesh_pdb/__init__.py @@ -82,7 +82,7 @@ class ImportPDB(Operator, ImportHelper): use_camera = BoolProperty( name="Camera", default=False, description="Do you need a camera?") - use_lamp = BoolProperty( + use_light = BoolProperty( name="Lamp", default=False, description = "Do you need a lamp?") ball = EnumProperty( @@ -162,7 +162,7 @@ class ImportPDB(Operator, ImportHelper): layout = self.layout row = layout.row() row.prop(self, "use_camera") - row.prop(self, "use_lamp") + row.prop(self, "use_light") row = layout.row() row.prop(self, "use_center") # Balls @@ -256,7 +256,7 @@ class ImportPDB(Operator, ImportHelper): self.sticks_radius, self.use_center, self.use_camera, - self.use_lamp, + self.use_light, filepath_pdb) return {'FINISHED'} @@ -301,13 +301,13 @@ def menu_func_export(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_func_import) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_func_import) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) if __name__ == "__main__": diff --git a/io_mesh_pdb/import_pdb.py b/io_mesh_pdb/import_pdb.py index a7067692..bf243899 100644 --- a/io_mesh_pdb/import_pdb.py +++ b/io_mesh_pdb/import_pdb.py @@ -536,7 +536,7 @@ def build_stick(radius, length, sectors): # Function, which puts a camera and light source into the 3D scene def camera_light_source(use_camera, - use_lamp, + use_light, object_center_vec, object_size): @@ -592,27 +592,27 @@ def camera_light_source(use_camera, release_confirm=False) # Here a lamp is put into the scene, if chosen. - if use_lamp == True: + if use_light == True: # This is the distance from the object measured in terms of % # of the camera distance. It is set onto 50% (1/2) distance. - lamp_dl = sqrt(object_size) * 15 * 0.5 + light_dl = sqrt(object_size) * 15 * 0.5 # This is a factor to which extend the lamp shall go to the right # (from the camera point of view). - lamp_dy_right = lamp_dl * (3.0/4.0) + light_dy_right = light_dl * (3.0/4.0) # Create x, y and z for the lamp. - object_lamp_vec = Vector((lamp_dl,lamp_dy_right,lamp_dl)) - lamp_xyz_vec = object_center_vec + object_lamp_vec + object_light_vec = Vector((light_dl,light_dy_right,light_dl)) + light_xyz_vec = object_center_vec + object_light_vec # Create the lamp current_layers=bpy.context.scene.layers - lamp_data = bpy.data.lamps.new(name="A_lamp", type="POINT") - lamp_data.distance = 500.0 - lamp_data.energy = 3.0 - lamp_data.shadow_method = 'RAY_SHADOW' - lamp = bpy.data.objects.new("A_lamp", lamp_data) - lamp.location = lamp_xyz_vec + light_data = bpy.data.lights.new(name="A_light", type="POINT") + light_data.distance = 500.0 + light_data.energy = 3.0 + light_data.shadow_method = 'RAY_SHADOW' + lamp = bpy.data.objects.new("A_light", light_data) + lamp.location = light_xyz_vec lamp.layers = current_layers bpy.context.scene.objects.link(lamp) @@ -685,7 +685,7 @@ def draw_atoms_one_type(draw_all_atoms_type, ball.name = "Ball_"+atom[0] ball.active_material = atom[1] ball.parent = new_atom_mesh - new_atom_mesh.dupli_type = 'VERTS' + new_atom_mesh.instance_type = 'VERTS' # The object is back translated to 'object_center_vec'. new_atom_mesh.location = object_center_vec @@ -858,7 +858,7 @@ def draw_sticks_dupliverts(all_atoms, # Parenting the mesh to the cylinder. stick_cylinder.parent = new_mesh stick_cups.parent = new_mesh - new_mesh.dupli_type = 'FACES' + new_mesh.instance_type = 'FACES' new_mesh.location = center atom_object_list.append(new_mesh) @@ -1145,7 +1145,7 @@ def import_pdb(Ball_type, Stick_diameter, put_to_center, use_camera, - use_lamp, + use_light, filepath_pdb): @@ -1376,7 +1376,7 @@ def import_pdb(Ball_type, # CAMERA and LIGHT SOURCES camera_light_source(use_camera, - use_lamp, + use_light, object_center_vec, object_size) diff --git a/io_mesh_ply/__init__.py b/io_mesh_ply/__init__.py index 6764dcb4..30051796 100644 --- a/io_mesh_ply/__init__.py +++ b/io_mesh_ply/__init__.py @@ -22,7 +22,7 @@ bl_info = { "name": "Stanford PLY format", "author": "Bruce Merry, Campbell Barton", "version": (1, 0, 0), - "blender": (2, 74, 0), + "blender": (2, 80, 0), "location": "File > Import-Export", "description": "Import-Export PLY mesh data with UV's and vertex colors", "warning": "", @@ -56,29 +56,26 @@ from bpy.props import ( from bpy_extras.io_utils import ( ImportHelper, ExportHelper, - orientation_helper_factory, axis_conversion, + orientation_helper ) -IOPLYOrientationHelper = orientation_helper_factory("IOPLYOrientationHelper", axis_forward='Y', axis_up='Z') - - class ImportPLY(bpy.types.Operator, ImportHelper): """Load a PLY geometry file""" bl_idname = "import_mesh.ply" bl_label = "Import PLY" bl_options = {'UNDO'} - files = CollectionProperty(name="File Path", + files: CollectionProperty(name="File Path", description="File path used for importing " "the PLY file", type=bpy.types.OperatorFileListElement) - directory = StringProperty() + directory: StringProperty() filename_ext = ".ply" - filter_glob = StringProperty(default="*.ply", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.ply", options={'HIDDEN'}) def execute(self, context): paths = [os.path.join(self.directory, name.name) @@ -94,21 +91,22 @@ class ImportPLY(bpy.types.Operator, ImportHelper): return {'FINISHED'} -class ExportPLY(bpy.types.Operator, ExportHelper, IOPLYOrientationHelper): +@orientation_helper(axis_forward='Y', axis_up='Z') +class ExportPLY(bpy.types.Operator, ExportHelper): """Export a single object as a Stanford PLY with normals, """ \ """colors and texture coordinates""" bl_idname = "export_mesh.ply" bl_label = "Export PLY" filename_ext = ".ply" - filter_glob = StringProperty(default="*.ply", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.ply", options={'HIDDEN'}) - use_mesh_modifiers = BoolProperty( + use_mesh_modifiers: BoolProperty( name="Apply Modifiers", description="Apply Modifiers to the exported mesh", default=True, ) - use_normals = BoolProperty( + use_normals: BoolProperty( name="Normals", description="Export Normals for smooth and " "hard shaded faces " @@ -116,18 +114,18 @@ class ExportPLY(bpy.types.Operator, ExportHelper, IOPLYOrientationHelper): "as individual faces)", default=True, ) - use_uv_coords = BoolProperty( + use_uv_coords: BoolProperty( name="UVs", description="Export the active UV layer", default=True, ) - use_colors = BoolProperty( + use_colors: BoolProperty( name="Vertex Colors", description="Export the active vertex color layer", default=True, ) - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", min=0.01, max=1000.0, default=1.0, @@ -150,7 +148,7 @@ class ExportPLY(bpy.types.Operator, ExportHelper, IOPLYOrientationHelper): )) global_matrix = axis_conversion(to_forward=self.axis_forward, to_up=self.axis_up, - ).to_4x4() * Matrix.Scale(self.global_scale, 4) + ).to_4x4() @ Matrix.Scale(self.global_scale, 4) keywords["global_matrix"] = global_matrix filepath = self.filepath @@ -191,16 +189,16 @@ def register(): for cls in classes: bpy.utils.register_class(cls) - bpy.types.INFO_MT_file_import.append(menu_func_import) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): for cls in classes: bpy.utils.unregister_class(cls) - bpy.types.INFO_MT_file_import.remove(menu_func_import) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) if __name__ == "__main__": register() diff --git a/io_mesh_ply/export_ply.py b/io_mesh_ply/export_ply.py index 8de5d674..b50b6544 100644 --- a/io_mesh_ply/export_ply.py +++ b/io_mesh_ply/export_ply.py @@ -44,12 +44,12 @@ def save_mesh(filepath, file = open(filepath, "w", encoding="utf8", newline="\n") fw = file.write - # Be sure tessface & co are available! - if not mesh.tessfaces and mesh.polygons: - mesh.calc_tessface() + # Be sure tessellated loop trianlges are available! + if not mesh.loop_triangles and mesh.polygons: + mesh.calc_loop_triangles() - has_uv = bool(mesh.tessface_uv_textures) - has_vcol = bool(mesh.tessface_vertex_colors) + has_uv = bool(mesh.uv_layers) + has_vcol = bool(mesh.vertex_colors) if not has_uv: use_uv_coords = False @@ -62,7 +62,7 @@ def save_mesh(filepath, has_vcol = False if has_uv: - active_uv_layer = mesh.tessface_uv_textures.active + active_uv_layer = mesh.uv_layers.active if not active_uv_layer: use_uv_coords = False has_uv = False @@ -70,7 +70,7 @@ def save_mesh(filepath, active_uv_layer = active_uv_layer.data if has_vcol: - active_col_layer = mesh.tessface_vertex_colors.active + active_col_layer = mesh.vertex_colors.active if not active_col_layer: use_colors = False has_vcol = False @@ -84,9 +84,9 @@ def save_mesh(filepath, ply_verts = [] # list of dictionaries # vdict = {} # (index, normal, uv) -> new index vdict = [{} for i in range(len(mesh_verts))] - ply_faces = [[] for f in range(len(mesh.tessfaces))] + ply_faces = [[] for f in range(len(mesh.loop_triangles))] vert_count = 0 - for i, f in enumerate(mesh.tessfaces): + for i, f in enumerate(mesh.loop_triangles): smooth = not use_normals or f.use_smooth if not smooth: @@ -94,16 +94,12 @@ def save_mesh(filepath, normal_key = rvec3d(normal) if has_uv: - uv = active_uv_layer[i] - uv = uv.uv1, uv.uv2, uv.uv3, uv.uv4 + uv = [active_uv_layer[l].uv[:] for l in f.loops] if has_vcol: - col = active_col_layer[i] - col = col.color1[:], col.color2[:], col.color3[:], col.color4[:] - - f_verts = f.vertices + col = [active_col_layer[l].color[:] for l in f.loops] pf = ply_faces[i] - for j, vidx in enumerate(f_verts): + for j, vidx in enumerate(f.vertices): v = mesh_verts[vidx] if smooth: @@ -119,6 +115,7 @@ def save_mesh(filepath, color = (int(color[0] * 255.0), int(color[1] * 255.0), int(color[2] * 255.0), + 255 ) key = normal_key, uvcoord_key, color @@ -157,7 +154,7 @@ def save_mesh(filepath, "property uchar blue\n" "property uchar alpha\n") - fw("element face %d\n" % len(mesh.tessfaces)) + fw("element face %d\n" % len(mesh.loop_triangles)) fw("property list uchar uint vertex_indices\n") fw("end_header\n") @@ -193,7 +190,6 @@ def save(operator, global_matrix=None ): - scene = context.scene obj = context.active_object if global_matrix is None: @@ -204,14 +200,15 @@ def save(operator, bpy.ops.object.mode_set(mode='OBJECT') if use_mesh_modifiers and obj.modifiers: - mesh = obj.to_mesh(scene, True, 'PREVIEW') + mesh = obj.to_mesh(context.depsgraph, True) + else: mesh = obj.data.copy() if not mesh: raise Exception("Error, could not get mesh data from active object") - mesh.transform(global_matrix * obj.matrix_world) + mesh.transform(global_matrix @ obj.matrix_world) if use_normals: mesh.calc_normals() @@ -221,7 +218,6 @@ def save(operator, use_colors=use_colors, ) - if use_mesh_modifiers: - bpy.data.meshes.remove(mesh) + bpy.data.meshes.remove(mesh) return ret diff --git a/io_mesh_ply/import_ply.py b/io_mesh_ply/import_ply.py index 86fc2e1b..5ec24f8a 100644 --- a/io_mesh_ply/import_ply.py +++ b/io_mesh_ply/import_ply.py @@ -176,6 +176,7 @@ def read(filepath): else: texture = tokens[2] continue + elif tokens[0] == b'obj_info': continue elif tokens[0] == b'format': @@ -222,9 +223,9 @@ import bpy def load_ply_mesh(filepath, ply_name): from bpy_extras.io_utils import unpack_face_list - # from bpy_extras.image_utils import load_image # UNUSED obj_spec, obj, texture = read(filepath) + # XXX28: use texture if obj is None: print('Invalid file') return @@ -262,9 +263,9 @@ def load_ply_mesh(filepath, ply_name): def add_face(vertices, indices, uvindices, colindices): mesh_faces.append(indices) if uvindices: - mesh_uvs.append([(vertices[index][uvindices[0]], vertices[index][uvindices[1]]) for index in indices]) + mesh_uvs.extend([(vertices[index][uvindices[0]], vertices[index][uvindices[1]]) for index in indices]) if colindices: - mesh_colors.append([(vertices[index][colindices[0]] * colmultiply[0], + mesh_colors.extend([(vertices[index][colindices[0]] * colmultiply[0], vertices[index][colindices[1]] * colmultiply[1], vertices[index][colindices[2]] * colmultiply[2], vertices[index][colindices[3]] * colmultiply[3], @@ -316,41 +317,45 @@ def load_ply_mesh(filepath, ply_name): mesh.edges.foreach_set("vertices", [a for e in obj[b'edge'] for a in (e[eindex1], e[eindex2])]) if mesh_faces: - mesh.tessfaces.add(len(mesh_faces)) - mesh.tessfaces.foreach_set("vertices_raw", unpack_face_list(mesh_faces)) - - if uvindices or colindices: - if uvindices: - uvlay = mesh.tessface_uv_textures.new() - if colindices: - vcol_lay = mesh.tessface_vertex_colors.new() - - if uvindices: - for i, f in enumerate(uvlay.data): - ply_uv = mesh_uvs[i] - for j, uv in enumerate(f.uv): - uv[0], uv[1] = ply_uv[j] - - if colindices: - for i, f in enumerate(vcol_lay.data): - # XXX, colors dont come in right, needs further investigation. - ply_col = mesh_colors[i] - if len(ply_col) == 4: - f_col = f.color1, f.color2, f.color3, f.color4 - else: - f_col = f.color1, f.color2, f.color3 + loops_vert_idx = [] + faces_loop_start = [] + faces_loop_total = [] + lidx = 0 + for f in mesh_faces: + nbr_vidx = len(f) + loops_vert_idx.extend(f) + faces_loop_start.append(lidx) + faces_loop_total.append(nbr_vidx) + lidx += nbr_vidx + + mesh.loops.add(len(loops_vert_idx)) + mesh.polygons.add(len(mesh_faces)) + + mesh.loops.foreach_set("vertex_index", loops_vert_idx) + mesh.polygons.foreach_set("loop_start", faces_loop_start) + mesh.polygons.foreach_set("loop_total", faces_loop_total) + + if uvindices: + uv_layer = mesh.uv_layers.new() + for i, uv in enumerate(uv_layer.data): + uv.uv = mesh_uvs[i] - for j, col in enumerate(f_col): - col[0] = ply_col[j][0] - col[1] = ply_col[j][1] - col[2] = ply_col[j][2] - col[3] = ply_col[j][3] + if colindices: + vcol_lay = mesh.vertex_colors.new() + + for i, col in enumerate(vcol_lay.data): + col.color[0] = mesh_colors[i][0] + col.color[1] = mesh_colors[i][1] + col.color[2] = mesh_colors[i][2] + col.color[3] = mesh_colors[i][3] - mesh.validate() mesh.update() + mesh.validate() if texture and uvindices: - + pass + # XXX28: add support for using texture. + ''' import os import sys from bpy_extras.image_utils import load_image @@ -375,6 +380,7 @@ def load_ply_mesh(filepath, ply_name): mesh.materials.append(material) for face in mesh.uv_textures[0].data: face.image = image + ''' return mesh @@ -389,12 +395,10 @@ def load_ply(filepath): if not mesh: return {'CANCELLED'} - scn = bpy.context.scene - obj = bpy.data.objects.new(ply_name, mesh) - scn.objects.link(obj) - scn.objects.active = obj - obj.select = True + bpy.context.collection.objects.link(obj) + bpy.context.view_layer.objects.active = obj + obj.select_set(True) print('\nSuccessfully imported %r in %.3f sec' % (filepath, time.time() - t)) return {'FINISHED'} diff --git a/io_mesh_raw/__init__.py b/io_mesh_raw/__init__.py index 69640932..5ad4c6f5 100644 --- a/io_mesh_raw/__init__.py +++ b/io_mesh_raw/__init__.py @@ -107,15 +107,15 @@ def menu_export(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_import) - bpy.types.INFO_MT_file_export.append(menu_export) + bpy.types.TOPBAR_MT_file_import.append(menu_import) + bpy.types.TOPBAR_MT_file_export.append(menu_export) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_import) - bpy.types.INFO_MT_file_export.remove(menu_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_export) if __name__ == "__main__": register() diff --git a/io_mesh_stl/__init__.py b/io_mesh_stl/__init__.py index fc8bced2..b1807d2a 100644 --- a/io_mesh_stl/__init__.py +++ b/io_mesh_stl/__init__.py @@ -21,8 +21,8 @@ bl_info = { "name": "STL format", "author": "Guillaume Bouchard (Guillaum)", - "version": (1, 1, 2), - "blender": (2, 74, 0), + "version": (1, 1, 3), + "blender": (2, 80, 0), "location": "File > Import-Export > Stl", "description": "Import-Export STL files", "warning": "", @@ -67,7 +67,7 @@ from bpy.props import ( from bpy_extras.io_utils import ( ImportHelper, ExportHelper, - orientation_helper_factory, + orientation_helper, axis_conversion, ) from bpy.types import ( @@ -76,10 +76,8 @@ from bpy.types import ( ) -IOSTLOrientationHelper = orientation_helper_factory("IOSTLOrientationHelper", axis_forward='Y', axis_up='Z') - - -class ImportSTL(Operator, ImportHelper, IOSTLOrientationHelper): +@orientation_helper(axis_forward='Y', axis_up='Z') +class ImportSTL(Operator, ImportHelper): """Load STL triangle mesh data""" bl_idname = "import_mesh.stl" bl_label = "Import STL" @@ -87,32 +85,32 @@ class ImportSTL(Operator, ImportHelper, IOSTLOrientationHelper): filename_ext = ".stl" - filter_glob = StringProperty( + filter_glob: StringProperty( default="*.stl", options={'HIDDEN'}, ) - files = CollectionProperty( + files: CollectionProperty( name="File Path", type=OperatorFileListElement, ) - directory = StringProperty( + directory: StringProperty( subtype='DIR_PATH', ) - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", soft_min=0.001, soft_max=1000.0, min=1e-6, max=1e6, default=1.0, ) - use_scene_unit = BoolProperty( + use_scene_unit: BoolProperty( name="Scene Unit", description="Apply current scene's unit (as defined by unit scale) to imported data", default=False, ) - use_facet_normal = BoolProperty( + use_facet_normal: BoolProperty( name="Facet Normals", description="Use (import) facet normals (note that this will still give flat shading)", default=False, @@ -135,7 +133,7 @@ class ImportSTL(Operator, ImportHelper, IOSTLOrientationHelper): global_matrix = axis_conversion(from_forward=self.axis_forward, from_up=self.axis_up, - ).to_4x4() * Matrix.Scale(global_scale, 4) + ).to_4x4() @ Matrix.Scale(global_scale, 4) if not paths: paths.append(self.filepath) @@ -155,41 +153,42 @@ class ImportSTL(Operator, ImportHelper, IOSTLOrientationHelper): return {'FINISHED'} -class ExportSTL(Operator, ExportHelper, IOSTLOrientationHelper): +@orientation_helper(axis_forward='Y', axis_up='Z') +class ExportSTL(Operator, ExportHelper): """Save STL triangle mesh data from the active object""" bl_idname = "export_mesh.stl" bl_label = "Export STL" filename_ext = ".stl" - filter_glob = StringProperty(default="*.stl", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.stl", options={'HIDDEN'}) - use_selection = BoolProperty( + use_selection: BoolProperty( name="Selection Only", description="Export selected objects only", default=False, ) - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", min=0.01, max=1000.0, default=1.0, ) - use_scene_unit = BoolProperty( + use_scene_unit: BoolProperty( name="Scene Unit", description="Apply current scene's unit (as defined by unit scale) to exported data", default=False, ) - ascii = BoolProperty( + ascii: BoolProperty( name="Ascii", description="Save the file in ASCII file format", default=False, ) - use_mesh_modifiers = BoolProperty( + use_mesh_modifiers: BoolProperty( name="Apply Modifiers", description="Apply the modifiers before saving", default=True, ) - batch_mode = EnumProperty( + batch_mode: EnumProperty( name="Batch Mode", items=(('OFF', "Off", "All data in one file"), ('OBJECT', "Object", "Each object as a file"), @@ -228,7 +227,7 @@ class ExportSTL(Operator, ExportHelper, IOSTLOrientationHelper): global_matrix = axis_conversion(to_forward=self.axis_forward, to_up=self.axis_up, - ).to_4x4() * Matrix.Scale(global_scale, 4) + ).to_4x4() @ Matrix.Scale(global_scale, 4) if self.batch_mode == 'OFF': faces = itertools.chain.from_iterable( @@ -252,22 +251,28 @@ def menu_import(self, context): def menu_export(self, context): - default_path = os.path.splitext(bpy.data.filepath)[0] + ".stl" self.layout.operator(ExportSTL.bl_idname, text="Stl (.stl)") +classes = ( + ImportSTL, + ExportSTL +) + def register(): - bpy.utils.register_module(__name__) + for cls in classes: + bpy.utils.register_class(cls) - bpy.types.INFO_MT_file_import.append(menu_import) - bpy.types.INFO_MT_file_export.append(menu_export) + bpy.types.TOPBAR_MT_file_import.append(menu_import) + bpy.types.TOPBAR_MT_file_export.append(menu_export) def unregister(): - bpy.utils.unregister_module(__name__) + for cls in classes: + bpy.utils.unregister_class(cls) - bpy.types.INFO_MT_file_import.remove(menu_import) - bpy.types.INFO_MT_file_export.remove(menu_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_export) if __name__ == "__main__": diff --git a/io_mesh_stl/blender_utils.py b/io_mesh_stl/blender_utils.py index 864335ab..fcc4889a 100644 --- a/io_mesh_stl/blender_utils.py +++ b/io_mesh_stl/blender_utils.py @@ -57,15 +57,13 @@ def create_and_link_mesh(name, faces, face_nors, points, global_matrix): mesh.update() - scene = bpy.context.scene - obj = bpy.data.objects.new(name, mesh) - scene.objects.link(obj) - scene.objects.active = obj - obj.select = True + bpy.context.collection.objects.link(obj) + bpy.context.view_layer.objects.active = obj + obj.select_set(True) -def faces_from_mesh(ob, global_matrix, use_mesh_modifiers=False, triangulate=True): +def faces_from_mesh(ob, global_matrix, use_mesh_modifiers=False): """ From an object, return a generator over a list of faces. @@ -84,34 +82,19 @@ def faces_from_mesh(ob, global_matrix, use_mesh_modifiers=False, triangulate=Tru # get the modifiers try: - mesh = ob.to_mesh(bpy.context.scene, use_mesh_modifiers, "PREVIEW") + mesh = ob.to_mesh(bpy.context.depsgraph, use_mesh_modifiers) except RuntimeError: - raise StopIteration + return - mat = global_matrix * ob.matrix_world + mat = global_matrix @ ob.matrix_world mesh.transform(mat) if mat.is_negative: mesh.flip_normals() - mesh.calc_tessface() - - if triangulate: - # From a list of faces, return the face triangulated if needed. - def iter_face_index(): - for face in mesh.tessfaces: - vertices = face.vertices[:] - if len(vertices) == 4: - yield vertices[0], vertices[1], vertices[2] - yield vertices[2], vertices[3], vertices[0] - else: - yield vertices - else: - def iter_face_index(): - for face in mesh.tessfaces: - yield face.vertices[:] + mesh.calc_loop_triangles() vertices = mesh.vertices - for indexes in iter_face_index(): - yield [vertices[index].co.copy() for index in indexes] + for tri in mesh.loop_triangles: + yield [vertices[index].co.copy() for index in tri.vertices] bpy.data.meshes.remove(mesh) diff --git a/io_mesh_uv_layout/__init__.py b/io_mesh_uv_layout/__init__.py index 30dff949..ad20a7a1 100644 --- a/io_mesh_uv_layout/__init__.py +++ b/io_mesh_uv_layout/__init__.py @@ -22,7 +22,7 @@ bl_info = { "name": "UV Layout", "author": "Campbell Barton, Matt Ebb", "version": (1, 1, 1), - "blender": (2, 75, 0), + "blender": (2, 80, 0), "location": "Image-Window > UVs > Export UV Layout", "description": "Export the UV layout as a 2D graphic", "warning": "", @@ -43,15 +43,16 @@ if "bpy" in locals(): if "export_uv_svg" in locals(): importlib.reload(export_uv_svg) +import os import bpy from bpy.props import ( - StringProperty, - BoolProperty, - EnumProperty, - IntVectorProperty, - FloatProperty, - ) + StringProperty, + BoolProperty, + EnumProperty, + IntVectorProperty, + FloatProperty, +) class ExportUVLayout(bpy.types.Operator): @@ -61,172 +62,171 @@ class ExportUVLayout(bpy.types.Operator): bl_label = "Export UV Layout" bl_options = {'REGISTER', 'UNDO'} - filepath = StringProperty( - subtype='FILE_PATH', - ) - check_existing = BoolProperty( - name="Check Existing", - description="Check and warn on overwriting existing files", - default=True, - options={'HIDDEN'}, - ) - export_all = BoolProperty( - name="All UVs", - description="Export all UVs in this mesh (not just visible ones)", - default=False, - ) - modified = BoolProperty( - name="Modified", - description="Exports UVs from the modified mesh", - default=False, - ) - mode = EnumProperty( - items=(('SVG', "Scalable Vector Graphic (.svg)", - "Export the UV layout to a vector SVG file"), - ('EPS', "Encapsulate PostScript (.eps)", - "Export the UV layout to a vector EPS file"), - ('PNG', "PNG Image (.png)", - "Export the UV layout to a bitmap image"), - ), - name="Format", - description="File format to export the UV layout to", - default='PNG', - ) - size = IntVectorProperty( - size=2, - default=(1024, 1024), - min=8, max=32768, - description="Dimensions of the exported file", - ) - opacity = FloatProperty( - name="Fill Opacity", - min=0.0, max=1.0, - default=0.25, - description="Set amount of opacity for exported UV layout" - ) - tessellated = BoolProperty( - name="Tessellated UVs", - description="Export tessellated UVs instead of polygons ones", - default=False, - options={'HIDDEN'}, # As not working currently :/ - ) + filepath: StringProperty( + subtype='FILE_PATH', + ) + export_all: BoolProperty( + name="All UVs", + description="Export all UVs in this mesh (not just visible ones)", + default=False, + ) + modified: BoolProperty( + name="Modified", + description="Exports UVs from the modified mesh", + default=False, + ) + mode: EnumProperty( + items=( + ('SVG', "Scalable Vector Graphic (.svg)", + "Export the UV layout to a vector SVG file"), + ('EPS', "Encapsulate PostScript (.eps)", + "Export the UV layout to a vector EPS file"), + ('PNG', "PNG Image (.png)", + "Export the UV layout to a bitmap image"), + ), + name="Format", + description="File format to export the UV layout to", + default='PNG', + ) + size: IntVectorProperty( + size=2, + default=(1024, 1024), + min=8, max=32768, + description="Dimensions of the exported file", + ) + opacity: FloatProperty( + name="Fill Opacity", + min=0.0, max=1.0, + default=0.25, + description="Set amount of opacity for exported UV layout", + ) @classmethod def poll(cls, context): obj = context.active_object - return (obj and obj.type == 'MESH' and obj.data.uv_textures) + return obj is not None and obj.type == 'MESH' and obj.data.uv_layers - def _space_image(self, context): - space_data = context.space_data - if isinstance(space_data, bpy.types.SpaceImageEditor): - return space_data - else: - return None - - def _image_size(self, context, default_width=1024, default_height=1024): - # fallback if not in image context. - image_width, image_height = default_width, default_height + def invoke(self, context, event): + self.size = self.get_image_size(context) + self.filepath = self.get_default_file_name(context) + "." + self.mode.lower() + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} - space_data = self._space_image(context) - if space_data: - image = space_data.image - if image: - width, height = tuple(context.space_data.image.size) - # in case no data is found. - if width and height: - image_width, image_height = width, height + def get_default_file_name(self, context): + AMOUNT = 3 + objects = list(self.iter_objects_to_export(context)) + name = " ".join(sorted([obj.name for obj in objects[:AMOUNT]])) + if len(objects) > AMOUNT: + name += " and more" + return name - return image_width, image_height + def check(self, context): + if any(self.filepath.endswith(ext) for ext in (".png", ".eps", ".svg")): + self.filepath = self.filepath[:-4] - def _face_uv_iter(self, context, mesh, tessellated): - uv_layer = mesh.uv_layers.active.data - polys = mesh.polygons - - if not self.export_all: - uv_tex = mesh.uv_textures.active.data - local_image = Ellipsis - - if context.tool_settings.show_uv_local_view: - space_data = self._space_image(context) - if space_data: - local_image = space_data.image - - for i, p in enumerate(polys): - # context checks - if polys[i].select and local_image in {Ellipsis, - uv_tex[i].image}: - start = p.loop_start - end = start + p.loop_total - uvs = tuple((uv.uv[0], uv.uv[1]) - for uv in uv_layer[start:end]) - - # just write what we see. - yield (i, uvs) - else: - # all, simple - for i, p in enumerate(polys): - start = p.loop_start - end = start + p.loop_total - uvs = tuple((uv.uv[0], uv.uv[1]) for uv in uv_layer[start:end]) - yield (i, uvs) + ext = "." + self.mode.lower() + self.filepath = bpy.path.ensure_ext(self.filepath, ext) + return True def execute(self, context): - obj = context.active_object is_editmode = (obj.mode == 'EDIT') if is_editmode: bpy.ops.object.mode_set(mode='OBJECT', toggle=False) - mode = self.mode - filepath = self.filepath - filepath = bpy.path.ensure_ext(filepath, "." + mode.lower()) - file = open(filepath, "w") - fw = file.write - - if mode == 'EPS': - from . import export_uv_eps - func = export_uv_eps.write - elif mode == 'PNG': - from . import export_uv_png - func = export_uv_png.write - elif mode == 'SVG': - from . import export_uv_svg - func = export_uv_svg.write + filepath = bpy.path.ensure_ext(filepath, "." + self.mode.lower()) + meshes = list(self.iter_meshes_to_export(context)) + polygon_data = list(self.iter_polygon_data_to_draw(context, meshes)) + different_colors = set(color for _, color in polygon_data) if self.modified: - mesh = obj.to_mesh(context.scene, True, 'PREVIEW') - else: - mesh = obj.data + self.free_meshes(meshes) - func(fw, mesh, self.size[0], self.size[1], self.opacity, - lambda: self._face_uv_iter(context, mesh, self.tessellated)) - - if self.modified: - bpy.data.meshes.remove(mesh) + export = self.get_exporter() + export(filepath, polygon_data, different_colors, self.size[0], self.size[1], self.opacity) if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False) - file.close() - return {'FINISHED'} - def check(self, context): - filepath = bpy.path.ensure_ext(self.filepath, "." + self.mode.lower()) - if filepath != self.filepath: - self.filepath = filepath - return True - else: - return False + def iter_meshes_to_export(self, context): + for obj in self.iter_objects_to_export(context): + if self.modified: + yield obj.to_mesh(context.depsgraph, apply_modifiers=True) + else: + yield obj.data + + @staticmethod + def iter_objects_to_export(context): + for obj in {*context.selected_objects, context.active_object}: + if obj.type != 'MESH': + continue + mesh = obj.data + if mesh.uv_layers.active is None: + continue + yield obj - def invoke(self, context, event): - import os - self.size = self._image_size(context) - self.filepath = os.path.splitext(bpy.data.filepath)[0] - wm = context.window_manager - wm.fileselect_add(self) - return {'RUNNING_MODAL'} + @staticmethod + def free_meshes(meshes): + for mesh in meshes: + bpy.data.meshes.remove(mesh) + + @staticmethod + def currently_image_image_editor(context): + return isinstance(context.space_data, bpy.types.SpaceImageEditor) + + def get_currently_opened_image(self, context): + if not self.currently_image_image_editor(context): + return None + return context.space_data.image + + def get_image_size(self, context): + # fallback if not in image context + image_width = self.size[0] + image_height = self.size[1] + + # get size of "active" image if some exist + image = self.get_currently_opened_image(context) + if image is not None: + width, height = image.size + if width and height: + image_width = width + image_height = height + + return image_width, image_height + + def iter_polygon_data_to_draw(self, context, meshes): + for mesh in meshes: + uv_layer = mesh.uv_layers.active.data + for polygon in mesh.polygons: + if self.export_all or polygon.select: + start = polygon.loop_start + end = start + polygon.loop_total + uvs = tuple(tuple(uv.uv) for uv in uv_layer[start:end]) + yield (uvs, self.get_polygon_color(mesh, polygon)) + + @staticmethod + def get_polygon_color(mesh, polygon, default=(0.8, 0.8, 0.8)): + if polygon.material_index < len(mesh.materials): + material = mesh.materials[polygon.material_index] + if material is not None: + return tuple(material.diffuse_color) + return default + + def get_exporter(self): + if self.mode == 'PNG': + from . import export_uv_png + return export_uv_png.export + elif self.mode == 'EPS': + from . import export_uv_eps + return export_uv_eps.export + elif self.mode == 'SVG': + from . import export_uv_svg + return export_uv_svg.export + else: + assert False def menu_func(self, context): @@ -234,13 +234,14 @@ def menu_func(self, context): def register(): - bpy.utils.register_module(__name__) + bpy.utils.register_class(ExportUVLayout) bpy.types.IMAGE_MT_uvs.append(menu_func) def unregister(): - bpy.utils.unregister_module(__name__) + bpy.utils.unregister_class(ExportUVLayout) bpy.types.IMAGE_MT_uvs.remove(menu_func) + if __name__ == "__main__": register() diff --git a/io_mesh_uv_layout/export_uv_eps.py b/io_mesh_uv_layout/export_uv_eps.py index a15dc266..3280cefa 100644 --- a/io_mesh_uv_layout/export_uv_eps.py +++ b/io_mesh_uv_layout/export_uv_eps.py @@ -21,66 +21,72 @@ import bpy -def write(fw, mesh, image_width, image_height, opacity, face_iter_func): - fw("%!PS-Adobe-3.0 EPSF-3.0\n") - fw("%%%%Creator: Blender %s\n" % bpy.app.version_string) - fw("%%Pages: 1\n") - fw("%%Orientation: Portrait\n") - fw("%%%%BoundingBox: 0 0 %d %d\n" % (image_width, image_height)) - fw("%%%%HiResBoundingBox: 0.0 0.0 %.4f %.4f\n" % - (image_width, image_height)) - fw("%%EndComments\n") - fw("%%Page: 1 1\n") - fw("0 0 translate\n") - fw("1.0 1.0 scale\n") - fw("0 0 0 setrgbcolor\n") - fw("[] 0 setdash\n") - fw("1 setlinewidth\n") - fw("1 setlinejoin\n") - fw("1 setlinecap\n") - - polys = mesh.polygons +def export(filepath, face_data, colors, width, height, opacity): + with open(filepath, "w") as file: + for text in get_file_parts(face_data, colors, width, height, opacity): + file.write(text) +def get_file_parts(face_data, colors, width, height, opacity): + yield from header(width, height) if opacity > 0.0: - for i, mat in enumerate(mesh.materials if mesh.materials else [None]): - fw("/DRAW_%d {" % i) - fw("gsave\n") - if mat: - color = tuple((1.0 - ((1.0 - c) * opacity)) - for c in mat.diffuse_color) - else: - color = 1.0, 1.0, 1.0 - fw("%.3g %.3g %.3g setrgbcolor\n" % color) - fw("fill\n") - fw("grestore\n") - fw("0 setgray\n") - fw("} def\n") + name_by_color = {} + yield from prepare_colors(colors, name_by_color) + yield from draw_colored_polygons(face_data, name_by_color, width, height) + yield from draw_lines(face_data, width, height) + yield from footer() + + +def header(width, height): + yield "%!PS-Adobe-3.0 EPSF-3.0\n" + yield f"%%Creator: Blender {bpy.app.version_string}\n" + yield "%%Pages: 1\n" + yield "%%Orientation: Portrait\n" + yield f"%%BoundingBox: 0 0 {width} {height}\n" + yield f"%%HiResBoundingBox: 0.0 0.0 {width:.4f} {height:.4f}\n" + yield "%%EndComments\n" + yield "%%Page: 1 1\n" + yield "0 0 translate\n" + yield "1.0 1.0 scale\n" + yield "0 0 0 setrgbcolor\n" + yield "[] 0 setdash\n" + yield "1 setlinewidth\n" + yield "1 setlinejoin\n" + yield "1 setlinecap\n" + +def prepare_colors(colors, out_name_by_color): + for i, color in enumerate(colors): + name = f"COLOR_{i}" + yield "/%s {" % name + out_name_by_color[color] = name - # fill - for i, uvs in face_iter_func(): - fw("newpath\n") - for j, uv in enumerate(uvs): - uv_scale = (uv[0] * image_width, uv[1] * image_height) - if j == 0: - fw("%.5f %.5f moveto\n" % uv_scale) - else: - fw("%.5f %.5f lineto\n" % uv_scale) + yield "gsave\n" + yield "%.3g %.3g %.3g setrgbcolor\n" % color + yield "fill\n" + yield "grestore\n" + yield "0 setgray\n" + yield "} def\n" - fw("closepath\n") - fw("DRAW_%d\n" % polys[i].material_index) +def draw_colored_polygons(face_data, name_by_color, width, height): + for uvs, color in face_data: + yield from draw_polygon_path(uvs, width, height) + yield "closepath\n" + yield "%s\n" % name_by_color[color] - # stroke only - for i, uvs in face_iter_func(): - fw("newpath\n") - for j, uv in enumerate(uvs): - uv_scale = (uv[0] * image_width, uv[1] * image_height) - if j == 0: - fw("%.5f %.5f moveto\n" % uv_scale) - else: - fw("%.5f %.5f lineto\n" % uv_scale) +def draw_lines(face_data, width, height): + for uvs, _ in face_data: + yield from draw_polygon_path(uvs, width, height) + yield "closepath\n" + yield "stroke\n" - fw("closepath\n") - fw("stroke\n") +def draw_polygon_path(uvs, width, height): + yield "newpath\n" + for j, uv in enumerate(uvs): + uv_scale = (uv[0] * width, uv[1] * height) + if j == 0: + yield "%.5f %.5f moveto\n" % uv_scale + else: + yield "%.5f %.5f lineto\n" % uv_scale - fw("showpage\n") - fw("%%EOF\n") +def footer(): + yield "showpage\n" + yield "%%EOF\n"
\ No newline at end of file diff --git a/io_mesh_uv_layout/export_uv_png.py b/io_mesh_uv_layout/export_uv_png.py index b556c982..8aa61151 100644 --- a/io_mesh_uv_layout/export_uv_png.py +++ b/io_mesh_uv_layout/export_uv_png.py @@ -19,133 +19,96 @@ # <pep8-80 compliant> import bpy - - -def write(fw, mesh_source, image_width, image_height, opacity, face_iter_func): - filepath = fw.__self__.name - fw.__self__.close() - - material_solids = [bpy.data.materials.new("uv_temp_solid") - for i in range(max(1, len(mesh_source.materials)))] - - material_wire = bpy.data.materials.new("uv_temp_wire") - - scene = bpy.data.scenes.new("uv_temp") - mesh = bpy.data.meshes.new("uv_temp") - for mat_solid in material_solids: - mesh.materials.append(mat_solid) - - polys_source = mesh_source.polygons - - # get unique UV's in case there are many overlapping - # which slow down filling. - face_hash = {(uvs, polys_source[i].material_index) - for i, uvs in face_iter_func()} - - # now set the faces coords and locations - # build mesh data - mesh_new_vertices = [] - mesh_new_materials = [] - mesh_new_polys_startloop = [] - mesh_new_polys_totloop = [] - mesh_new_loops_vertices = [] - - current_vert = 0 - - for uvs, mat_idx in face_hash: - num_verts = len(uvs) - dummy = (0.0,) * num_verts - for uv in uvs: - mesh_new_vertices += (uv[0], uv[1], 0.0) - mesh_new_polys_startloop.append(current_vert) - mesh_new_polys_totloop.append(num_verts) - mesh_new_loops_vertices += range(current_vert, - current_vert + num_verts) - mesh_new_materials.append(mat_idx) - current_vert += num_verts - - mesh.vertices.add(current_vert) - mesh.loops.add(current_vert) - mesh.polygons.add(len(mesh_new_polys_startloop)) - - mesh.vertices.foreach_set("co", mesh_new_vertices) - mesh.loops.foreach_set("vertex_index", mesh_new_loops_vertices) - mesh.polygons.foreach_set("loop_start", mesh_new_polys_startloop) - mesh.polygons.foreach_set("loop_total", mesh_new_polys_totloop) - mesh.polygons.foreach_set("material_index", mesh_new_materials) - - mesh.update(calc_edges=True) - - obj_solid = bpy.data.objects.new("uv_temp_solid", mesh) - obj_wire = bpy.data.objects.new("uv_temp_wire", mesh) - base_solid = scene.objects.link(obj_solid) - base_wire = scene.objects.link(obj_wire) - base_solid.layers[0] = True - base_wire.layers[0] = True - - # place behind the wire - obj_solid.location = 0, 0, -1 - - obj_wire.material_slots[0].link = 'OBJECT' - obj_wire.material_slots[0].material = material_wire - - # setup the camera - cam = bpy.data.cameras.new("uv_temp") - cam.type = 'ORTHO' - cam.ortho_scale = 1.0 - obj_cam = bpy.data.objects.new("uv_temp_cam", cam) - obj_cam.location = 0.5, 0.5, 1.0 - scene.objects.link(obj_cam) - scene.camera = obj_cam - - # setup materials - for i, mat_solid in enumerate(material_solids): - if mesh_source.materials and mesh_source.materials[i]: - mat_solid.diffuse_color = mesh_source.materials[i].diffuse_color - - mat_solid.use_shadeless = True - mat_solid.use_transparency = True - mat_solid.alpha = opacity - - material_wire.type = 'WIRE' - material_wire.use_shadeless = True - material_wire.diffuse_color = 0, 0, 0 - material_wire.use_transparency = True - - # scene render settings - scene.render.use_raytrace = False - scene.render.alpha_mode = 'TRANSPARENT' - scene.render.image_settings.color_mode = 'RGBA' - - scene.render.resolution_x = image_width - scene.render.resolution_y = image_height - scene.render.resolution_percentage = 100 - - if image_width > image_height: - scene.render.pixel_aspect_y = image_width / image_height - elif image_width < image_height: - scene.render.pixel_aspect_x = image_height / image_width - - scene.frame_start = 1 - scene.frame_end = 1 - - scene.render.image_settings.file_format = 'PNG' - scene.render.filepath = filepath - - scene.update() - - data_context = {"blend_data": bpy.context.blend_data, "scene": scene} - bpy.ops.render.render(data_context, write_still=True) - - # cleanup - bpy.data.scenes.remove(scene, do_unlink=True) - bpy.data.objects.remove(obj_cam, do_unlink=True) - bpy.data.objects.remove(obj_solid, do_unlink=True) - bpy.data.objects.remove(obj_wire, do_unlink=True) - - bpy.data.cameras.remove(cam, do_unlink=True) - bpy.data.meshes.remove(mesh, do_unlink=True) - - bpy.data.materials.remove(material_wire, do_unlink=True) - for mat_solid in material_solids: - bpy.data.materials.remove(mat_solid, do_unlink=True) +import gpu +import bgl +from mathutils import Vector, Matrix +from mathutils.geometry import tessellate_polygon +from gpu_extras.batch import batch_for_shader + +def export(filepath, face_data, colors, width, height, opacity): + offscreen = gpu.types.GPUOffScreen(width, height) + offscreen.bind() + + try: + bgl.glClear(bgl.GL_COLOR_BUFFER_BIT) + draw_image(face_data, opacity) + + pixel_data = get_pixel_data_from_current_back_buffer(width, height) + save_pixels(filepath, pixel_data, width, height) + finally: + offscreen.unbind() + offscreen.free() + +def draw_image(face_data, opacity): + bgl.glLineWidth(1) + bgl.glEnable(bgl.GL_BLEND) + bgl.glEnable(bgl.GL_LINE_SMOOTH) + bgl.glHint(bgl.GL_LINE_SMOOTH_HINT, bgl.GL_NICEST) + + with gpu.matrix.push_pop(): + gpu.matrix.load_matrix(get_normalize_uvs_matrix()) + gpu.matrix.load_projection_matrix(Matrix.Identity(4)) + + draw_background_colors(face_data, opacity) + draw_lines(face_data) + + bgl.glDisable(bgl.GL_BLEND) + bgl.glDisable(bgl.GL_LINE_SMOOTH) + +def get_normalize_uvs_matrix(): + '''matrix maps x and y coordinates from [0, 1] to [-1, 1]''' + matrix = Matrix.Identity(4) + matrix.col[3][0] = -1 + matrix.col[3][1] = -1 + matrix[0][0] = 2 + matrix[1][1] = 2 + return matrix + +def draw_background_colors(face_data, opacity): + coords = [uv for uvs, _ in face_data for uv in uvs] + colors = [(*color, opacity) for uvs, color in face_data for _ in range(len(uvs))] + + indices = [] + offset = 0 + for uvs, _ in face_data: + triangles = tessellate_uvs(uvs) + indices.extend([index + offset for index in triangle] for triangle in triangles) + offset += len(uvs) + + shader = gpu.shader.from_builtin('2D_FLAT_COLOR') + batch = batch_for_shader(shader, 'TRIS', + {"pos" : coords, + "color" : colors}, + indices=indices) + batch.draw(shader) + +def tessellate_uvs(uvs): + return tessellate_polygon([[Vector(uv) for uv in uvs]]) + +def draw_lines(face_data): + coords = [] + for uvs, _ in face_data: + for i in range(len(uvs)): + start = uvs[i] + end = uvs[(i+1) % len(uvs)] + coords.append((start[0], start[1])) + coords.append((end[0], end[1])) + + shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') + batch = batch_for_shader(shader, 'LINES', {"pos" : coords}) + shader.bind() + shader.uniform_float("color", (0, 0, 0, 1)) + batch.draw(shader) + +def get_pixel_data_from_current_back_buffer(width, height): + buffer = bgl.Buffer(bgl.GL_BYTE, width * height * 4) + bgl.glReadBuffer(bgl.GL_BACK) + bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer) + return buffer + +def save_pixels(filepath, pixel_data, width, height): + image = bpy.data.images.new("temp", width, height, alpha=True) + image.filepath = filepath + image.pixels = [v / 255 for v in pixel_data] + image.save() + bpy.data.images.remove(image) diff --git a/io_mesh_uv_layout/export_uv_svg.py b/io_mesh_uv_layout/export_uv_svg.py index 764f0d34..d00f9402 100644 --- a/io_mesh_uv_layout/export_uv_svg.py +++ b/io_mesh_uv_layout/export_uv_svg.py @@ -19,50 +19,46 @@ # <pep8-80 compliant> import bpy +from os.path import basename +from xml.sax.saxutils import escape +def export(filepath, face_data, colors, width, height, opacity): + with open(filepath, "w") as file: + for text in get_file_parts(face_data, colors, width, height, opacity): + file.write(text) -def write(fw, mesh, image_width, image_height, opacity, face_iter_func): - # for making an XML compatible string - from xml.sax.saxutils import escape - from os.path import basename +def get_file_parts(face_data, colors, width, height, opacity): + yield from header(width, height) + yield from draw_polygons(face_data, width, height, opacity) + yield from footer() - fw('<?xml version="1.0" standalone="no"?>\n') - fw('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \n') - fw(' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n') - fw('<svg width="%d" height="%d" viewBox="0 0 %d %d"\n' % - (image_width, image_height, image_width, image_height)) - fw(' xmlns="http://www.w3.org/2000/svg" version="1.1">\n') - desc = ("%r, %s, (Blender %s)" % - (basename(bpy.data.filepath), mesh.name, bpy.app.version_string)) - fw('<desc>%s</desc>\n' % escape(desc)) +def header(width, height): + yield '<?xml version="1.0" standalone="no"?>\n' + yield '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \n' + yield ' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' + yield f'<svg width="{width}" height="{height}" viewBox="0 0 {width} {height}"\n' + yield ' xmlns="http://www.w3.org/2000/svg" version="1.1">\n' + desc = f"{basename(bpy.data.filepath)}, (Blender {bpy.app.version_string})" + yield f'<desc>{escape(desc)}</desc>\n' - # svg colors - fill_settings = [] - fill_default = 'fill="grey"' - for mat in mesh.materials if mesh.materials else [None]: - if mat: - fill_settings.append('fill="rgb(%d, %d, %d)"' % - tuple(int(c * 255) - for c in mat.diffuse_color)) - else: - fill_settings.append(fill_default) +def draw_polygons(face_data, width, height, opacity): + for uvs, color in face_data: + fill = f'fill="{get_color_string(color)}"' - polys = mesh.polygons - for i, uvs in face_iter_func(): - try: # rare cases material index is invalid. - fill = fill_settings[polys[i].material_index] - except IndexError: - fill = fill_default + yield '<polygon stroke="black" stroke-width="1"' + yield f' {fill} fill-opacity="{opacity:.2g}"' - fw('<polygon stroke="black" stroke-width="1"') - if opacity > 0.0: - fw(' %s fill-opacity="%.2g"' % (fill, opacity)) + yield ' points="' - fw(' points="') - - for j, uv in enumerate(uvs): + for uv in uvs: x, y = uv[0], 1.0 - uv[1] - fw('%.3f,%.3f ' % (x * image_width, y * image_height)) - fw('" />\n') - fw('\n') - fw('</svg>\n') + yield f'{x*width:.3f},{y*height:.3f} ' + yield '" />\n' + +def get_color_string(color): + r, g, b = color + return f"rgb({round(r*255)}, {round(g*255)}, {round(b*255)})" + +def footer(): + yield '\n' + yield '</svg>\n'
\ No newline at end of file diff --git a/io_online_sketchfab/__init__.py b/io_online_sketchfab/__init__.py index ba73bd19..2205f497 100644 --- a/io_online_sketchfab/__init__.py +++ b/io_online_sketchfab/__init__.py @@ -247,7 +247,7 @@ class ExportSketchfab(Operator): with open(SKETCHFAB_EXPORT_DATA_FILE, 'w') as s: json.dump({ "models": props.models, - "lamps": props.lamps, + "lights": props.lights, }, s) subprocess.check_call([ @@ -311,7 +311,7 @@ class VIEW3D_PT_sketchfab(Panel): layout.label("Export:") col = layout.box().column(align=True) col.prop(props, "models") - col.prop(props, "lamps") + col.prop(props, "lights") layout.label("Model info:") col = layout.box().column(align=True) diff --git a/io_online_sketchfab/pack_for_export.py b/io_online_sketchfab/pack_for_export.py index 587c94c2..972735c8 100644 --- a/io_online_sketchfab/pack_for_export.py +++ b/io_online_sketchfab/pack_for_export.py @@ -87,12 +87,12 @@ def prepare_assets(export_settings): images.add(image) if ((export_settings['models'] == 'SELECTION' and ob.type == 'MESH') or - (export_settings['lamps'] == 'SELECTION' and ob.type == 'LAMP')): + (export_settings['lamps'] == 'SELECTION' and ob.type == 'LIGHT')): if not ob.select and not ob.hide: ob.hide = True hidden.add(ob) - elif export_settings['lamps'] == 'NONE' and ob.type == 'LAMP': + elif export_settings['lamps'] == 'NONE' and ob.type == 'LIGHT': if not ob.hide: ob.hide = True hidden.add(ob) diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index 9236c07e..4daa4c29 100644 --- a/io_scene_3ds/__init__.py +++ b/io_scene_3ds/__init__.py @@ -50,15 +50,13 @@ from bpy.props import ( from bpy_extras.io_utils import ( ImportHelper, ExportHelper, - orientation_helper_factory, + orientation_helper, axis_conversion, ) -IO3DSOrientationHelper = orientation_helper_factory("IO3DSOrientationHelper", axis_forward='Y', axis_up='Z') - - -class Import3DS(bpy.types.Operator, ImportHelper, IO3DSOrientationHelper): +@orientation_helper(axis_forward='Y', axis_up='Z') +class Import3DS(bpy.types.Operator, ImportHelper): """Import from 3DS file format (.3ds)""" bl_idname = "import_scene.autodesk_3ds" bl_label = 'Import 3DS' @@ -104,7 +102,8 @@ class Import3DS(bpy.types.Operator, ImportHelper, IO3DSOrientationHelper): return import_3ds.load(self, context, **keywords) -class Export3DS(bpy.types.Operator, ExportHelper, IO3DSOrientationHelper): +@orientation_helper(axis_forward='Y', axis_up='Z') +class Export3DS(bpy.types.Operator, ExportHelper): """Export to 3DS file format (.3ds)""" bl_idname = "export_scene.autodesk_3ds" bl_label = 'Export 3DS' @@ -149,15 +148,15 @@ def menu_func_import(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_func_import) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_func_import) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) # NOTES: # why add 1 extra vertex? and remove it when done? - diff --git a/io_scene_3ds/import_3ds.py b/io_scene_3ds/import_3ds.py index b9de42d5..28d7b25f 100644 --- a/io_scene_3ds/import_3ds.py +++ b/io_scene_3ds/import_3ds.py @@ -83,27 +83,27 @@ MAT_24BIT_COLOR = 0x0011 # color defined as 3 bytes #>------ sub defines of OBJECT OBJECT_MESH = 0x4100 # This lets us know that we are reading a new object -OBJECT_LAMP = 0x4600 # This lets un know we are reading a light object -OBJECT_LAMP_SPOT = 0x4610 # The light is a spotloght. -OBJECT_LAMP_OFF = 0x4620 # The light off. -OBJECT_LAMP_ATTENUATE = 0x4625 -OBJECT_LAMP_RAYSHADE = 0x4627 -OBJECT_LAMP_SHADOWED = 0x4630 -OBJECT_LAMP_LOCAL_SHADOW = 0x4640 -OBJECT_LAMP_LOCAL_SHADOW2 = 0x4641 -OBJECT_LAMP_SEE_CONE = 0x4650 -OBJECT_LAMP_SPOT_RECTANGULAR = 0x4651 -OBJECT_LAMP_SPOT_OVERSHOOT = 0x4652 -OBJECT_LAMP_SPOT_PROJECTOR = 0x4653 -OBJECT_LAMP_EXCLUDE = 0x4654 -OBJECT_LAMP_RANGE = 0x4655 -OBJECT_LAMP_ROLL = 0x4656 -OBJECT_LAMP_SPOT_ASPECT = 0x4657 -OBJECT_LAMP_RAY_BIAS = 0x4658 -OBJECT_LAMP_INNER_RANGE = 0x4659 -OBJECT_LAMP_OUTER_RANGE = 0x465A -OBJECT_LAMP_MULTIPLIER = 0x465B -OBJECT_LAMP_AMBIENT_LIGHT = 0x4680 +OBJECT_LIGHT = 0x4600 # This lets un know we are reading a light object +OBJECT_LIGHT_SPOT = 0x4610 # The light is a spotloght. +OBJECT_LIGHT_OFF = 0x4620 # The light off. +OBJECT_LIGHT_ATTENUATE = 0x4625 +OBJECT_LIGHT_RAYSHADE = 0x4627 +OBJECT_LIGHT_SHADOWED = 0x4630 +OBJECT_LIGHT_LOCAL_SHADOW = 0x4640 +OBJECT_LIGHT_LOCAL_SHADOW2 = 0x4641 +OBJECT_LIGHT_SEE_CONE = 0x4650 +OBJECT_LIGHT_SPOT_RECTANGULAR = 0x4651 +OBJECT_LIGHT_SPOT_OVERSHOOT = 0x4652 +OBJECT_LIGHT_SPOT_PROJECTOR = 0x4653 +OBJECT_LIGHT_EXCLUDE = 0x4654 +OBJECT_LIGHT_RANGE = 0x4655 +OBJECT_LIGHT_ROLL = 0x4656 +OBJECT_LIGHT_SPOT_ASPECT = 0x4657 +OBJECT_LIGHT_RAY_BIAS = 0x4658 +OBJECT_LIGHT_INNER_RANGE = 0x4659 +OBJECT_LIGHT_OUTER_RANGE = 0x465A +OBJECT_LIGHT_MULTIPLIER = 0x465B +OBJECT_LIGHT_AMBIENT_LIGHT = 0x4680 OBJECT_CAMERA = 0x4700 # This lets un know we are reading a camera object @@ -596,7 +596,7 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): new_chunk.bytes_read += temp_chunk.bytes_read - elif new_chunk.ID == OBJECT_LAMP: # Basic lamp support. + elif new_chunk.ID == OBJECT_LIGHT: # Basic lamp support. temp_data = file.read(STRUCT_SIZE_3FLOAT) @@ -604,7 +604,7 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH): new_chunk.bytes_read += STRUCT_SIZE_3FLOAT # no lamp in dict that would be confusing - contextLamp[1] = bpy.data.lamps.new("Lamp", 'POINT') + contextLamp[1] = bpy.data.lights.new("Lamp", 'POINT') contextLamp[0] = ob = bpy.data.objects.new("Lamp", contextLamp[1]) SCN.objects.link(ob) @@ -949,8 +949,8 @@ def load_3ds(filepath, axis_min = [1000000000] * 3 axis_max = [-1000000000] * 3 - global_clamp_size = IMPORT_CONSTRAIN_BOUNDS - if global_clamp_size != 0.0: + global_clight_size = IMPORT_CONSTRAIN_BOUNDS + if global_clight_size != 0.0: # Get all object bounds for ob in importedObjects: for v in ob.bound_box: @@ -966,7 +966,7 @@ def load_3ds(filepath, axis_max[2] - axis_min[2]) scale = 1.0 - while global_clamp_size < max_axis * scale: + while global_clight_size < max_axis * scale: scale = scale / 10.0 scale_mat = mathutils.Matrix.Scale(scale, 4) diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index fddeda6c..d415305d 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -21,8 +21,8 @@ bl_info = { "name": "FBX format", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier", - "version": (3, 10, 0), - "blender": (2, 79, 1), + "version": (4, 13, 1), + "blender": (2, 80, 0), "location": "File > Import-Export", "description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions", "warning": "", @@ -52,27 +52,25 @@ from bpy.props import ( from bpy_extras.io_utils import ( ImportHelper, ExportHelper, - orientation_helper_factory, + orientation_helper, path_reference_mode, axis_conversion, ) -IOFBXOrientationHelper = orientation_helper_factory("IOFBXOrientationHelper", axis_forward='-Z', axis_up='Y') - - -class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): +@orientation_helper(axis_forward='-Z', axis_up='Y') +class ImportFBX(bpy.types.Operator, ImportHelper): """Load a FBX file""" bl_idname = "import_scene.fbx" bl_label = "Import FBX" bl_options = {'UNDO', 'PRESET'} - directory = StringProperty() + directory: StringProperty() filename_ext = ".fbx" - filter_glob = StringProperty(default="*.fbx", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'}) - ui_tab = EnumProperty( + ui_tab: EnumProperty( items=(('MAIN', "Main", "Main basic settings"), ('ARMATURE', "Armatures", "Armature-related settings"), ), @@ -80,17 +78,17 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): description="Import options categories", ) - use_manual_orientation = BoolProperty( + use_manual_orientation: BoolProperty( name="Manual Orientation", description="Specify orientation and scale, instead of using embedded data in FBX file", default=False, ) - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", min=0.001, max=1000.0, default=1.0, ) - bake_space_transform = BoolProperty( + bake_space_transform: BoolProperty( name="!EXPERIMENTAL! Apply Transform", description="Bake space transform into object data, avoids getting unwanted rotations to objects when " "target space is not aligned with Blender's space " @@ -98,69 +96,69 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): default=False, ) - use_custom_normals = BoolProperty( + use_custom_normals: BoolProperty( name="Import Normals", description="Import custom normals, if available (otherwise Blender will recompute them)", default=True, ) - use_image_search = BoolProperty( + use_image_search: BoolProperty( name="Image Search", description="Search subdirs for any associated images (WARNING: may be slow)", default=True, ) - use_alpha_decals = BoolProperty( + use_alpha_decals: BoolProperty( name="Alpha Decals", description="Treat materials with alpha as decals (no shadow casting)", default=False, ) - decal_offset = FloatProperty( + decal_offset: FloatProperty( name="Decal Offset", description="Displace geometry of alpha meshes", min=0.0, max=1.0, default=0.0, ) - use_anim = BoolProperty( + use_anim: BoolProperty( name="Import Animation", description="Import FBX animation", default=True, ) - anim_offset = FloatProperty( + anim_offset: FloatProperty( name="Animation Offset", description="Offset to apply to animation during import, in frames", default=1.0, ) - use_custom_props = BoolProperty( + use_custom_props: BoolProperty( name="Import User Properties", description="Import user properties as custom properties", default=True, ) - use_custom_props_enum_as_string = BoolProperty( + use_custom_props_enum_as_string: BoolProperty( name="Import Enums As Strings", description="Store enumeration values as strings", default=True, ) - ignore_leaf_bones = BoolProperty( + ignore_leaf_bones: BoolProperty( name="Ignore Leaf Bones", description="Ignore the last bone at the end of each chain (used to mark the length of the previous bone)", default=False, ) - force_connect_children = BoolProperty( + force_connect_children: BoolProperty( name="Force Connect Children", description="Force connection of children bones to their parent, even if their computed head/tail " "positions do not match (can be useful with pure-joints-type armatures)", default=False, ) - automatic_bone_orientation = BoolProperty( + automatic_bone_orientation: BoolProperty( name="Automatic Bone Orientation", description="Try to align the major bone axis with the bone children", default=False, ) - primary_bone_axis = EnumProperty( + primary_bone_axis: EnumProperty( name="Primary Bone Axis", items=(('X', "X Axis", ""), ('Y', "Y Axis", ""), @@ -171,7 +169,7 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): ), default='Y', ) - secondary_bone_axis = EnumProperty( + secondary_bone_axis: EnumProperty( name="Secondary Bone Axis", items=(('X', "X Axis", ""), ('Y', "Y Axis", ""), @@ -183,7 +181,7 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): default='X', ) - use_prepost_rot = BoolProperty( + use_prepost_rot: BoolProperty( name="Use Pre/Post Rotation", description="Use pre/post rotation from FBX transform (you may have to disable that in some cases)", default=True, @@ -228,25 +226,25 @@ class ImportFBX(bpy.types.Operator, ImportHelper, IOFBXOrientationHelper): def execute(self, context): keywords = self.as_keywords(ignore=("filter_glob", "directory", "ui_tab")) - keywords["use_cycles"] = (context.scene.render.engine == 'CYCLES') from . import import_fbx return import_fbx.load(self, context, **keywords) -class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): +@orientation_helper(axis_forward='-Z', axis_up='Y') +class ExportFBX(bpy.types.Operator, ExportHelper): """Write a FBX file""" bl_idname = "export_scene.fbx" bl_label = "Export FBX" bl_options = {'UNDO', 'PRESET'} filename_ext = ".fbx" - filter_glob = StringProperty(default="*.fbx", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'}) # List of operator properties, the attributes will be assigned # to the class instance from the operator settings before calling. - ui_tab = EnumProperty( + ui_tab: EnumProperty( items=(('MAIN', "Main", "Main basic settings"), ('GEOMETRY', "Geometries", "Geometry-related settings"), ('ARMATURE', "Armatures", "Armature-related settings"), @@ -256,24 +254,29 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): description="Export options categories", ) - use_selection = BoolProperty( + use_selection: BoolProperty( name="Selected Objects", - description="Export selected objects on visible layers", + description="Export selected and visible objects only", + default=False, + ) + use_active_collection: BoolProperty( + name="Active Collection", + description="Export only objects from the active collection (and its children)", default=False, ) - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", description="Scale all data (Some importers do not support scaled armatures!)", min=0.001, max=1000.0, soft_min=0.01, soft_max=1000.0, default=1.0, ) - apply_unit_scale = BoolProperty( + apply_unit_scale: BoolProperty( name="Apply Unit", description="Take into account current Blender units settings (if unset, raw Blender Units values are used as-is)", default=True, ) - apply_scale_options = EnumProperty( + apply_scale_options: EnumProperty( items=(('FBX_SCALE_NONE', "All Local", "Apply custom scaling and units scaling to each object transformation, FBX scale remains at 1.0"), ('FBX_SCALE_UNITS', "FBX Units Scale", @@ -288,7 +291,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): "(Blender uses FBX scale to detect units on import, " "but many other applications do not handle the same way)", ) - bake_space_transform = BoolProperty( + bake_space_transform: BoolProperty( name="!EXPERIMENTAL! Apply Transform", description="Bake space transform into object data, avoids getting unwanted rotations to objects when " "target space is not aligned with Blender's space " @@ -296,32 +299,32 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): default=False, ) - object_types = EnumProperty( + object_types: EnumProperty( name="Object Types", options={'ENUM_FLAG'}, items=(('EMPTY', "Empty", ""), ('CAMERA', "Camera", ""), - ('LAMP', "Lamp", ""), + ('LIGHT', "Lamp", ""), ('ARMATURE', "Armature", "WARNING: not supported in dupli/group instances"), ('MESH', "Mesh", ""), ('OTHER', "Other", "Other geometry types, like curve, metaball, etc. (converted to meshes)"), ), description="Which kind of object to export", - default={'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH', 'OTHER'}, + default={'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'}, ) - use_mesh_modifiers = BoolProperty( + use_mesh_modifiers: BoolProperty( name="Apply Modifiers", description="Apply modifiers to mesh objects (except Armature ones) - " "WARNING: prevents exporting shape keys", default=True, ) - use_mesh_modifiers_render = BoolProperty( + use_mesh_modifiers_render: BoolProperty( name="Use Modifiers Render Setting", description="Use render settings when applying modifiers to mesh objects", default=True, ) - mesh_smooth_type = EnumProperty( + mesh_smooth_type: EnumProperty( name="Smoothing", items=(('OFF', "Normals Only", "Export only normals instead of writing edge or face smoothing data"), ('FACE', "Face", "Write face smoothing"), @@ -331,29 +334,29 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): "(prefer 'Normals Only' option if your target importer understand split normals)", default='OFF', ) - use_mesh_edges = BoolProperty( + use_mesh_edges: BoolProperty( name="Loose Edges", description="Export loose edges (as two-vertices polygons)", default=False, ) - use_tspace = BoolProperty( + use_tspace: BoolProperty( name="Tangent Space", description="Add binormal and tangent vectors, together with normal they form the tangent space " "(will only work correctly with tris/quads only meshes!)", default=False, ) - use_custom_props = BoolProperty( + use_custom_props: BoolProperty( name="Custom Properties", description="Export custom properties", default=False, ) - add_leaf_bones = BoolProperty( + add_leaf_bones: BoolProperty( name="Add Leaf Bones", description="Append a final bone to the end of each chain to specify last bone length " "(use this when you intend to edit the armature from exported data)", default=True # False for commit! ) - primary_bone_axis = EnumProperty( + primary_bone_axis: EnumProperty( name="Primary Bone Axis", items=(('X', "X Axis", ""), ('Y', "Y Axis", ""), @@ -364,7 +367,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): ), default='Y', ) - secondary_bone_axis = EnumProperty( + secondary_bone_axis: EnumProperty( name="Secondary Bone Axis", items=(('X', "X Axis", ""), ('Y', "Y Axis", ""), @@ -375,12 +378,12 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): ), default='X', ) - use_armature_deform_only = BoolProperty( + use_armature_deform_only: BoolProperty( name="Only Deform Bones", description="Only write deforming bones (and non-deforming ones when they have deforming children)", default=False, ) - armature_nodetype = EnumProperty( + armature_nodetype: EnumProperty( name="Armature FBXNode Type", items=(('NULL', "Null", "'Null' FBX node, similar to Blender's Empty (default)"), ('ROOT', "Root", "'Root' FBX node, supposed to be the root of chains of bones..."), @@ -391,68 +394,75 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): "perfectly in Blender...)", default='NULL', ) - bake_anim = BoolProperty( + bake_anim: BoolProperty( name="Baked Animation", description="Export baked keyframe animation", default=True, ) - bake_anim_use_all_bones = BoolProperty( + bake_anim_use_all_bones: BoolProperty( name="Key All Bones", description="Force exporting at least one key of animation for all bones " "(needed with some target applications, like UE4)", default=True, ) - bake_anim_use_nla_strips = BoolProperty( + bake_anim_use_nla_strips: BoolProperty( name="NLA Strips", description="Export each non-muted NLA strip as a separated FBX's AnimStack, if any, " "instead of global scene animation", default=True, ) - bake_anim_use_all_actions = BoolProperty( + bake_anim_use_all_actions: BoolProperty( name="All Actions", description="Export each action as a separated FBX's AnimStack, instead of global scene animation " "(note that animated objects will get all actions compatible with them, " "others will get no animation at all)", default=True, ) - bake_anim_force_startend_keying = BoolProperty( + bake_anim_force_startend_keying: BoolProperty( name="Force Start/End Keying", description="Always add a keyframe at start and end of actions for animated channels", default=True, ) - bake_anim_step = FloatProperty( + bake_anim_step: FloatProperty( name="Sampling Rate", description="How often to evaluate animated values (in frames)", min=0.01, max=100.0, soft_min=0.1, soft_max=10.0, default=1.0, ) - bake_anim_simplify_factor = FloatProperty( + bake_anim_simplify_factor: FloatProperty( name="Simplify", description="How much to simplify baked values (0.0 to disable, the higher the more simplified)", min=0.0, max=100.0, # No simplification to up to 10% of current magnitude tolerance. soft_min=0.0, soft_max=10.0, default=1.0, # default: min slope: 0.005, max frame step: 10. ) - path_mode = path_reference_mode - embed_textures = BoolProperty( + path_mode: path_reference_mode + embed_textures: BoolProperty( name="Embed Textures", description="Embed textures in FBX binary file (only for \"Copy\" path mode!)", default=False, ) - batch_mode = EnumProperty( + batch_mode: EnumProperty( name="Batch Mode", items=(('OFF', "Off", "Active scene to file"), ('SCENE', "Scene", "Each scene as a file"), - ('GROUP', "Group", "Each group as a file"), + ('COLLECTION', "Collection", + "Each collection (data-block ones) as a file, does not include content of children collections"), + ('SCENE_COLLECTION', "Scene Collections", + "Each collection (including master, non-data-block ones) of each scene as a file, " + "including content from children collections"), + ('ACTIVE_SCENE_COLLECTION', "Active Scene Collections", + "Each collection (including master, non-data-block one) of the active scene as a file, " + "including content from children collections"), ), ) - use_batch_own_dir = BoolProperty( + use_batch_own_dir: BoolProperty( name="Batch Own Dir", description="Create a dir for each exported file", default=True, ) - use_metadata = BoolProperty( + use_metadata: BoolProperty( name="Use Metadata", default=True, options={'HIDDEN'}, @@ -464,6 +474,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper, IOFBXOrientationHelper): layout.prop(self, "ui_tab", expand=True) if self.ui_tab == 'MAIN': layout.prop(self, "use_selection") + layout.prop(self, "use_active_collection") col = layout.column(align=True) row = col.row(align=True) @@ -559,13 +570,13 @@ def register(): for cls in classes: bpy.utils.register_class(cls) - bpy.types.INFO_MT_file_import.append(menu_func_import) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): - bpy.types.INFO_MT_file_import.remove(menu_func_import) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) for cls in classes: bpy.utils.unregister_class(cls) diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index ed1109e7..6f6005cb 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -27,7 +27,6 @@ import math import os import time -from collections import OrderedDict from itertools import zip_longest, chain if "bpy" in locals(): @@ -41,6 +40,7 @@ if "bpy" in locals(): import bpy import bpy_extras +from bpy_extras import node_shader_utils from mathutils import Vector, Matrix from . import encode_bin, data_types, fbx_utils @@ -75,6 +75,7 @@ from .fbx_utils import ( get_blender_bindpose_key, get_blender_armature_skin_key, get_blender_bone_cluster_key, get_blender_anim_id_base, get_blender_anim_stack_key, get_blender_anim_layer_key, get_blender_anim_curve_node_key, get_blender_anim_curve_key, + get_blender_nodetexture_key, # FBX element data. elem_empty, elem_data_single_bool, elem_data_single_int16, elem_data_single_int32, elem_data_single_int64, @@ -110,7 +111,7 @@ convert_rad_to_deg_iter = units_convertor_iter("radian", "degree") # TODO: check all those "default" values, they should match Blender's default as much as possible, I guess? def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict() + props = {} if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"GlobalSettings", b"", props, nbr_users, [False]) @@ -118,91 +119,91 @@ def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr def fbx_template_def_model(scene, settings, override_defaults=None, nbr_users=0): gscale = settings.global_scale - props = OrderedDict(( - # Name, Value, Type, Animatable - (b"QuaternionInterpolate", (0, "p_enum", False)), # 0 = no quat interpolation. - (b"RotationOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"RotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"ScalingOffset", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"ScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"TranslationActive", (False, "p_bool", False)), - (b"TranslationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"TranslationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"TranslationMinX", (False, "p_bool", False)), - (b"TranslationMinY", (False, "p_bool", False)), - (b"TranslationMinZ", (False, "p_bool", False)), - (b"TranslationMaxX", (False, "p_bool", False)), - (b"TranslationMaxY", (False, "p_bool", False)), - (b"TranslationMaxZ", (False, "p_bool", False)), - (b"RotationOrder", (0, "p_enum", False)), # we always use 'XYZ' order. - (b"RotationSpaceForLimitOnly", (False, "p_bool", False)), - (b"RotationStiffnessX", (0.0, "p_double", False)), - (b"RotationStiffnessY", (0.0, "p_double", False)), - (b"RotationStiffnessZ", (0.0, "p_double", False)), - (b"AxisLen", (10.0, "p_double", False)), - (b"PreRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"PostRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"RotationActive", (False, "p_bool", False)), - (b"RotationMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"RotationMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"RotationMinX", (False, "p_bool", False)), - (b"RotationMinY", (False, "p_bool", False)), - (b"RotationMinZ", (False, "p_bool", False)), - (b"RotationMaxX", (False, "p_bool", False)), - (b"RotationMaxY", (False, "p_bool", False)), - (b"RotationMaxZ", (False, "p_bool", False)), - (b"InheritType", (0, "p_enum", False)), # RrSs - (b"ScalingActive", (False, "p_bool", False)), - (b"ScalingMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"ScalingMax", ((1.0, 1.0, 1.0), "p_vector_3d", False)), - (b"ScalingMinX", (False, "p_bool", False)), - (b"ScalingMinY", (False, "p_bool", False)), - (b"ScalingMinZ", (False, "p_bool", False)), - (b"ScalingMaxX", (False, "p_bool", False)), - (b"ScalingMaxY", (False, "p_bool", False)), - (b"ScalingMaxZ", (False, "p_bool", False)), - (b"GeometricTranslation", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"GeometricRotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"GeometricScaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)), - (b"MinDampRangeX", (0.0, "p_double", False)), - (b"MinDampRangeY", (0.0, "p_double", False)), - (b"MinDampRangeZ", (0.0, "p_double", False)), - (b"MaxDampRangeX", (0.0, "p_double", False)), - (b"MaxDampRangeY", (0.0, "p_double", False)), - (b"MaxDampRangeZ", (0.0, "p_double", False)), - (b"MinDampStrengthX", (0.0, "p_double", False)), - (b"MinDampStrengthY", (0.0, "p_double", False)), - (b"MinDampStrengthZ", (0.0, "p_double", False)), - (b"MaxDampStrengthX", (0.0, "p_double", False)), - (b"MaxDampStrengthY", (0.0, "p_double", False)), - (b"MaxDampStrengthZ", (0.0, "p_double", False)), - (b"PreferedAngleX", (0.0, "p_double", False)), - (b"PreferedAngleY", (0.0, "p_double", False)), - (b"PreferedAngleZ", (0.0, "p_double", False)), - (b"LookAtProperty", (None, "p_object", False)), - (b"UpVectorProperty", (None, "p_object", False)), - (b"Show", (True, "p_bool", False)), - (b"NegativePercentShapeSupport", (True, "p_bool", False)), - (b"DefaultAttributeIndex", (-1, "p_integer", False)), - (b"Freeze", (False, "p_bool", False)), - (b"LODBox", (False, "p_bool", False)), - (b"Lcl Translation", ((0.0, 0.0, 0.0), "p_lcl_translation", True)), - (b"Lcl Rotation", ((0.0, 0.0, 0.0), "p_lcl_rotation", True)), - (b"Lcl Scaling", ((1.0, 1.0, 1.0), "p_lcl_scaling", True)), - (b"Visibility", (1.0, "p_visibility", True)), - (b"Visibility Inheritance", (1, "p_visibility_inheritance", False)), - )) + props = { + # Name, Value, Type, Animatable + b"QuaternionInterpolate": (0, "p_enum", False), # 0 = no quat interpolation. + b"RotationOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"RotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"ScalingOffset": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"ScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"TranslationActive": (False, "p_bool", False), + b"TranslationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"TranslationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"TranslationMinX": (False, "p_bool", False), + b"TranslationMinY": (False, "p_bool", False), + b"TranslationMinZ": (False, "p_bool", False), + b"TranslationMaxX": (False, "p_bool", False), + b"TranslationMaxY": (False, "p_bool", False), + b"TranslationMaxZ": (False, "p_bool", False), + b"RotationOrder": (0, "p_enum", False), # we always use 'XYZ' order. + b"RotationSpaceForLimitOnly": (False, "p_bool", False), + b"RotationStiffnessX": (0.0, "p_double", False), + b"RotationStiffnessY": (0.0, "p_double", False), + b"RotationStiffnessZ": (0.0, "p_double", False), + b"AxisLen": (10.0, "p_double", False), + b"PreRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"PostRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"RotationActive": (False, "p_bool", False), + b"RotationMin": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"RotationMax": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"RotationMinX": (False, "p_bool", False), + b"RotationMinY": (False, "p_bool", False), + b"RotationMinZ": (False, "p_bool", False), + b"RotationMaxX": (False, "p_bool", False), + b"RotationMaxY": (False, "p_bool", False), + b"RotationMaxZ": (False, "p_bool", False), + b"InheritType": (0, "p_enum", False), # RrSs + b"ScalingActive": (False, "p_bool", False), + b"ScalingMin": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"ScalingMax": ((1.0, 1.0, 1.0), "p_vector_3d", False), + b"ScalingMinX": (False, "p_bool", False), + b"ScalingMinY": (False, "p_bool", False), + b"ScalingMinZ": (False, "p_bool", False), + b"ScalingMaxX": (False, "p_bool", False), + b"ScalingMaxY": (False, "p_bool", False), + b"ScalingMaxZ": (False, "p_bool", False), + b"GeometricTranslation": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"GeometricRotation": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"GeometricScaling": ((1.0, 1.0, 1.0), "p_vector_3d", False), + b"MinDampRangeX": (0.0, "p_double", False), + b"MinDampRangeY": (0.0, "p_double", False), + b"MinDampRangeZ": (0.0, "p_double", False), + b"MaxDampRangeX": (0.0, "p_double", False), + b"MaxDampRangeY": (0.0, "p_double", False), + b"MaxDampRangeZ": (0.0, "p_double", False), + b"MinDampStrengthX": (0.0, "p_double", False), + b"MinDampStrengthY": (0.0, "p_double", False), + b"MinDampStrengthZ": (0.0, "p_double", False), + b"MaxDampStrengthX": (0.0, "p_double", False), + b"MaxDampStrengthY": (0.0, "p_double", False), + b"MaxDampStrengthZ": (0.0, "p_double", False), + b"PreferedAngleX": (0.0, "p_double", False), + b"PreferedAngleY": (0.0, "p_double", False), + b"PreferedAngleZ": (0.0, "p_double", False), + b"LookAtProperty": (None, "p_object", False), + b"UpVectorProperty": (None, "p_object", False), + b"Show": (True, "p_bool", False), + b"NegativePercentShapeSupport": (True, "p_bool", False), + b"DefaultAttributeIndex": (-1, "p_integer", False), + b"Freeze": (False, "p_bool", False), + b"LODBox": (False, "p_bool", False), + b"Lcl Translation": ((0.0, 0.0, 0.0), "p_lcl_translation", True), + b"Lcl Rotation": ((0.0, 0.0, 0.0), "p_lcl_rotation", True), + b"Lcl Scaling": ((1.0, 1.0, 1.0), "p_lcl_scaling", True), + b"Visibility": (1.0, "p_visibility", True), + b"Visibility Inheritance": (1, "p_visibility_inheritance", False), + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"Model", b"FbxNode", props, nbr_users, [False]) def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict(( - (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)), - (b"Size", (100.0, "p_double", False)), - (b"Look", (1, "p_enum", False)), # Cross (0 is None, i.e. invisible?). - )) + props = { + b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False), + b"Size": (100.0, "p_double", False), + b"Look": (1, "p_enum", False), # Cross (0 is None, i.e. invisible?). + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"NodeAttribute", b"FbxNull", props, nbr_users, [False]) @@ -210,17 +211,17 @@ def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0): def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0): gscale = settings.global_scale - props = OrderedDict(( - (b"LightType", (0, "p_enum", False)), # Point light. - (b"CastLight", (True, "p_bool", False)), - (b"Color", ((1.0, 1.0, 1.0), "p_color", True)), - (b"Intensity", (100.0, "p_number", True)), # Times 100 compared to Blender values... - (b"DecayType", (2, "p_enum", False)), # Quadratic. - (b"DecayStart", (30.0 * gscale, "p_double", False)), - (b"CastShadows", (True, "p_bool", False)), - (b"ShadowColor", ((0.0, 0.0, 0.0), "p_color", True)), - (b"AreaLightShape", (0, "p_enum", False)), # Rectangle. - )) + props = { + b"LightType": (0, "p_enum", False), # Point light. + b"CastLight": (True, "p_bool", False), + b"Color": ((1.0, 1.0, 1.0), "p_color", True), + b"Intensity": (100.0, "p_number", True), # Times 100 compared to Blender values... + b"DecayType": (2, "p_enum", False), # Quadratic. + b"DecayStart": (30.0 * gscale, "p_double", False), + b"CastShadows": (True, "p_bool", False), + b"ShadowColor": ((0.0, 0.0, 0.0), "p_color", True), + b"AreaLightShape": (0, "p_enum", False), # Rectangle. + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"NodeAttribute", b"FbxLight", props, nbr_users, [False]) @@ -228,137 +229,137 @@ def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0) def fbx_template_def_camera(scene, settings, override_defaults=None, nbr_users=0): r = scene.render - props = OrderedDict(( - (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)), - (b"Position", ((0.0, 0.0, -50.0), "p_vector", True)), - (b"UpVector", ((0.0, 1.0, 0.0), "p_vector", True)), - (b"InterestPosition", ((0.0, 0.0, 0.0), "p_vector", True)), - (b"Roll", (0.0, "p_roll", True)), - (b"OpticalCenterX", (0.0, "p_opticalcenterx", True)), - (b"OpticalCenterY", (0.0, "p_opticalcentery", True)), - (b"BackgroundColor", ((0.63, 0.63, 0.63), "p_color", True)), - (b"TurnTable", (0.0, "p_number", True)), - (b"DisplayTurnTableIcon", (False, "p_bool", False)), - (b"UseMotionBlur", (False, "p_bool", False)), - (b"UseRealTimeMotionBlur", (True, "p_bool", False)), - (b"Motion Blur Intensity", (1.0, "p_number", True)), - (b"AspectRatioMode", (0, "p_enum", False)), # WindowSize. - (b"AspectWidth", (320.0, "p_double", False)), - (b"AspectHeight", (200.0, "p_double", False)), - (b"PixelAspectRatio", (1.0, "p_double", False)), - (b"FilmOffsetX", (0.0, "p_number", True)), - (b"FilmOffsetY", (0.0, "p_number", True)), - (b"FilmWidth", (0.816, "p_double", False)), - (b"FilmHeight", (0.612, "p_double", False)), - (b"FilmAspectRatio", (1.3333333333333333, "p_double", False)), - (b"FilmSqueezeRatio", (1.0, "p_double", False)), - (b"FilmFormatIndex", (0, "p_enum", False)), # Assuming this is ApertureFormat, 0 = custom. - (b"PreScale", (1.0, "p_number", True)), - (b"FilmTranslateX", (0.0, "p_number", True)), - (b"FilmTranslateY", (0.0, "p_number", True)), - (b"FilmRollPivotX", (0.0, "p_number", True)), - (b"FilmRollPivotY", (0.0, "p_number", True)), - (b"FilmRollValue", (0.0, "p_number", True)), - (b"FilmRollOrder", (0, "p_enum", False)), # 0 = rotate first (default). - (b"ApertureMode", (2, "p_enum", False)), # 2 = Vertical. - (b"GateFit", (0, "p_enum", False)), # 0 = no resolution gate fit. - (b"FieldOfView", (25.114999771118164, "p_fov", True)), - (b"FieldOfViewX", (40.0, "p_fov_x", True)), - (b"FieldOfViewY", (40.0, "p_fov_y", True)), - (b"FocalLength", (34.89327621672628, "p_number", True)), - (b"CameraFormat", (0, "p_enum", False)), # Custom camera format. - (b"UseFrameColor", (False, "p_bool", False)), - (b"FrameColor", ((0.3, 0.3, 0.3), "p_color_rgb", False)), - (b"ShowName", (True, "p_bool", False)), - (b"ShowInfoOnMoving", (True, "p_bool", False)), - (b"ShowGrid", (True, "p_bool", False)), - (b"ShowOpticalCenter", (False, "p_bool", False)), - (b"ShowAzimut", (True, "p_bool", False)), - (b"ShowTimeCode", (False, "p_bool", False)), - (b"ShowAudio", (False, "p_bool", False)), - (b"AudioColor", ((0.0, 1.0, 0.0), "p_vector_3d", False)), # Yep, vector3d, not corlorgb… :cry: - (b"NearPlane", (10.0, "p_double", False)), - (b"FarPlane", (4000.0, "p_double", False)), - (b"AutoComputeClipPanes", (False, "p_bool", False)), - (b"ViewCameraToLookAt", (True, "p_bool", False)), - (b"ViewFrustumNearFarPlane", (False, "p_bool", False)), - (b"ViewFrustumBackPlaneMode", (2, "p_enum", False)), # 2 = show back plane if texture added. - (b"BackPlaneDistance", (4000.0, "p_number", True)), - (b"BackPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera. - (b"ViewFrustumFrontPlaneMode", (2, "p_enum", False)), # 2 = show front plane if texture added. - (b"FrontPlaneDistance", (10.0, "p_number", True)), - (b"FrontPlaneDistanceMode", (1, "p_enum", False)), # 1 = relative to camera. - (b"LockMode", (False, "p_bool", False)), - (b"LockInterestNavigation", (False, "p_bool", False)), + props = { + b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False), + b"Position": ((0.0, 0.0, -50.0), "p_vector", True), + b"UpVector": ((0.0, 1.0, 0.0), "p_vector", True), + b"InterestPosition": ((0.0, 0.0, 0.0), "p_vector", True), + b"Roll": (0.0, "p_roll", True), + b"OpticalCenterX": (0.0, "p_opticalcenterx", True), + b"OpticalCenterY": (0.0, "p_opticalcentery", True), + b"BackgroundColor": ((0.63, 0.63, 0.63), "p_color", True), + b"TurnTable": (0.0, "p_number", True), + b"DisplayTurnTableIcon": (False, "p_bool", False), + b"UseMotionBlur": (False, "p_bool", False), + b"UseRealTimeMotionBlur": (True, "p_bool", False), + b"Motion Blur Intensity": (1.0, "p_number", True), + b"AspectRatioMode": (0, "p_enum", False), # WindowSize. + b"AspectWidth": (320.0, "p_double", False), + b"AspectHeight": (200.0, "p_double", False), + b"PixelAspectRatio": (1.0, "p_double", False), + b"FilmOffsetX": (0.0, "p_number", True), + b"FilmOffsetY": (0.0, "p_number", True), + b"FilmWidth": (0.816, "p_double", False), + b"FilmHeight": (0.612, "p_double", False), + b"FilmAspectRatio": (1.3333333333333333, "p_double", False), + b"FilmSqueezeRatio": (1.0, "p_double", False), + b"FilmFormatIndex": (0, "p_enum", False), # Assuming this is ApertureFormat, 0 = custom. + b"PreScale": (1.0, "p_number", True), + b"FilmTranslateX": (0.0, "p_number", True), + b"FilmTranslateY": (0.0, "p_number", True), + b"FilmRollPivotX": (0.0, "p_number", True), + b"FilmRollPivotY": (0.0, "p_number", True), + b"FilmRollValue": (0.0, "p_number", True), + b"FilmRollOrder": (0, "p_enum", False), # 0 = rotate first (default). + b"ApertureMode": (2, "p_enum", False), # 2 = Vertical. + b"GateFit": (0, "p_enum", False), # 0 = no resolution gate fit. + b"FieldOfView": (25.114999771118164, "p_fov", True), + b"FieldOfViewX": (40.0, "p_fov_x", True), + b"FieldOfViewY": (40.0, "p_fov_y", True), + b"FocalLength": (34.89327621672628, "p_number", True), + b"CameraFormat": (0, "p_enum", False), # Custom camera format. + b"UseFrameColor": (False, "p_bool", False), + b"FrameColor": ((0.3, 0.3, 0.3), "p_color_rgb", False), + b"ShowName": (True, "p_bool", False), + b"ShowInfoOnMoving": (True, "p_bool", False), + b"ShowGrid": (True, "p_bool", False), + b"ShowOpticalCenter": (False, "p_bool", False), + b"ShowAzimut": (True, "p_bool", False), + b"ShowTimeCode": (False, "p_bool", False), + b"ShowAudio": (False, "p_bool", False), + b"AudioColor": ((0.0, 1.0, 0.0), "p_vector_3d", False), # Yep, vector3d, not corlorgb… :cry: + b"NearPlane": (10.0, "p_double", False), + b"FarPlane": (4000.0, "p_double", False), + b"AutoComputeClipPanes": (False, "p_bool", False), + b"ViewCameraToLookAt": (True, "p_bool", False), + b"ViewFrustumNearFarPlane": (False, "p_bool", False), + b"ViewFrustumBackPlaneMode": (2, "p_enum", False), # 2 = show back plane if texture added. + b"BackPlaneDistance": (4000.0, "p_number", True), + b"BackPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera. + b"ViewFrustumFrontPlaneMode": (2, "p_enum", False), # 2 = show front plane if texture added. + b"FrontPlaneDistance": (10.0, "p_number", True), + b"FrontPlaneDistanceMode": (1, "p_enum", False), # 1 = relative to camera. + b"LockMode": (False, "p_bool", False), + b"LockInterestNavigation": (False, "p_bool", False), # BackPlate... properties **arggggg!** - (b"FitImage", (False, "p_bool", False)), - (b"Crop", (False, "p_bool", False)), - (b"Center", (True, "p_bool", False)), - (b"KeepRatio", (True, "p_bool", False)), + b"FitImage": (False, "p_bool", False), + b"Crop": (False, "p_bool", False), + b"Center": (True, "p_bool", False), + b"KeepRatio": (True, "p_bool", False), # End of BackPlate... - (b"BackgroundAlphaTreshold", (0.5, "p_double", False)), - (b"ShowBackplate", (True, "p_bool", False)), - (b"BackPlaneOffsetX", (0.0, "p_number", True)), - (b"BackPlaneOffsetY", (0.0, "p_number", True)), - (b"BackPlaneRotation", (0.0, "p_number", True)), - (b"BackPlaneScaleX", (1.0, "p_number", True)), - (b"BackPlaneScaleY", (1.0, "p_number", True)), - (b"Background Texture", (None, "p_object", False)), - (b"FrontPlateFitImage", (True, "p_bool", False)), - (b"FrontPlateCrop", (False, "p_bool", False)), - (b"FrontPlateCenter", (True, "p_bool", False)), - (b"FrontPlateKeepRatio", (True, "p_bool", False)), - (b"Foreground Opacity", (1.0, "p_double", False)), - (b"ShowFrontplate", (True, "p_bool", False)), - (b"FrontPlaneOffsetX", (0.0, "p_number", True)), - (b"FrontPlaneOffsetY", (0.0, "p_number", True)), - (b"FrontPlaneRotation", (0.0, "p_number", True)), - (b"FrontPlaneScaleX", (1.0, "p_number", True)), - (b"FrontPlaneScaleY", (1.0, "p_number", True)), - (b"Foreground Texture", (None, "p_object", False)), - (b"DisplaySafeArea", (False, "p_bool", False)), - (b"DisplaySafeAreaOnRender", (False, "p_bool", False)), - (b"SafeAreaDisplayStyle", (1, "p_enum", False)), # 1 = rounded corners. - (b"SafeAreaAspectRatio", (1.3333333333333333, "p_double", False)), - (b"Use2DMagnifierZoom", (False, "p_bool", False)), - (b"2D Magnifier Zoom", (100.0, "p_number", True)), - (b"2D Magnifier X", (50.0, "p_number", True)), - (b"2D Magnifier Y", (50.0, "p_number", True)), - (b"CameraProjectionType", (0, "p_enum", False)), # 0 = perspective, 1 = orthogonal. - (b"OrthoZoom", (1.0, "p_double", False)), - (b"UseRealTimeDOFAndAA", (False, "p_bool", False)), - (b"UseDepthOfField", (False, "p_bool", False)), - (b"FocusSource", (0, "p_enum", False)), # 0 = camera interest, 1 = distance from camera interest. - (b"FocusAngle", (3.5, "p_double", False)), # ??? - (b"FocusDistance", (200.0, "p_double", False)), - (b"UseAntialiasing", (False, "p_bool", False)), - (b"AntialiasingIntensity", (0.77777, "p_double", False)), - (b"AntialiasingMethod", (0, "p_enum", False)), # 0 = oversampling, 1 = hardware. - (b"UseAccumulationBuffer", (False, "p_bool", False)), - (b"FrameSamplingCount", (7, "p_integer", False)), - (b"FrameSamplingType", (1, "p_enum", False)), # 0 = uniform, 1 = stochastic. - )) + b"BackgroundAlphaTreshold": (0.5, "p_double", False), + b"ShowBackplate": (True, "p_bool", False), + b"BackPlaneOffsetX": (0.0, "p_number", True), + b"BackPlaneOffsetY": (0.0, "p_number", True), + b"BackPlaneRotation": (0.0, "p_number", True), + b"BackPlaneScaleX": (1.0, "p_number", True), + b"BackPlaneScaleY": (1.0, "p_number", True), + b"Background Texture": (None, "p_object", False), + b"FrontPlateFitImage": (True, "p_bool", False), + b"FrontPlateCrop": (False, "p_bool", False), + b"FrontPlateCenter": (True, "p_bool", False), + b"FrontPlateKeepRatio": (True, "p_bool", False), + b"Foreground Opacity": (1.0, "p_double", False), + b"ShowFrontplate": (True, "p_bool", False), + b"FrontPlaneOffsetX": (0.0, "p_number", True), + b"FrontPlaneOffsetY": (0.0, "p_number", True), + b"FrontPlaneRotation": (0.0, "p_number", True), + b"FrontPlaneScaleX": (1.0, "p_number", True), + b"FrontPlaneScaleY": (1.0, "p_number", True), + b"Foreground Texture": (None, "p_object", False), + b"DisplaySafeArea": (False, "p_bool", False), + b"DisplaySafeAreaOnRender": (False, "p_bool", False), + b"SafeAreaDisplayStyle": (1, "p_enum", False), # 1 = rounded corners. + b"SafeAreaAspectRatio": (1.3333333333333333, "p_double", False), + b"Use2DMagnifierZoom": (False, "p_bool", False), + b"2D Magnifier Zoom": (100.0, "p_number", True), + b"2D Magnifier X": (50.0, "p_number", True), + b"2D Magnifier Y": (50.0, "p_number", True), + b"CameraProjectionType": (0, "p_enum", False), # 0 = perspective, 1 = orthogonal. + b"OrthoZoom": (1.0, "p_double", False), + b"UseRealTimeDOFAndAA": (False, "p_bool", False), + b"UseDepthOfField": (False, "p_bool", False), + b"FocusSource": (0, "p_enum", False), # 0 = camera interest, 1 = distance from camera interest. + b"FocusAngle": (3.5, "p_double", False), # ??? + b"FocusDistance": (200.0, "p_double", False), + b"UseAntialiasing": (False, "p_bool", False), + b"AntialiasingIntensity": (0.77777, "p_double", False), + b"AntialiasingMethod": (0, "p_enum", False), # 0 = oversampling, 1 = hardware. + b"UseAccumulationBuffer": (False, "p_bool", False), + b"FrameSamplingCount": (7, "p_integer", False), + b"FrameSamplingType": (1, "p_enum", False), # 0 = uniform, 1 = stochastic. + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"NodeAttribute", b"FbxCamera", props, nbr_users, [False]) def fbx_template_def_bone(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict() + props = {} if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"NodeAttribute", b"LimbNode", props, nbr_users, [False]) def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict(( - (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)), - (b"BBoxMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"BBoxMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"Primary Visibility", (True, "p_bool", False)), - (b"Casts Shadows", (True, "p_bool", False)), - (b"Receive Shadows", (True, "p_bool", False)), - )) + props = { + b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False), + b"BBoxMin": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"BBoxMax": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"Primary Visibility": (True, "p_bool", False), + b"Casts Shadows": (True, "p_bool", False), + b"Receive Shadows": (True, "p_bool", False), + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"Geometry", b"FbxMesh", props, nbr_users, [False]) @@ -366,37 +367,37 @@ def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users=0): # WIP... - props = OrderedDict(( - (b"ShadingModel", ("Phong", "p_string", False)), - (b"MultiLayer", (False, "p_bool", False)), + props = { + b"ShadingModel": ("Phong", "p_string", False), + b"MultiLayer": (False, "p_bool", False), # Lambert-specific. - (b"EmissiveColor", ((0.0, 0.0, 0.0), "p_color", True)), - (b"EmissiveFactor", (1.0, "p_number", True)), - (b"AmbientColor", ((0.2, 0.2, 0.2), "p_color", True)), - (b"AmbientFactor", (1.0, "p_number", True)), - (b"DiffuseColor", ((0.8, 0.8, 0.8), "p_color", True)), - (b"DiffuseFactor", (1.0, "p_number", True)), - (b"TransparentColor", ((0.0, 0.0, 0.0), "p_color", True)), - (b"TransparencyFactor", (0.0, "p_number", True)), - (b"Opacity", (1.0, "p_number", True)), - (b"NormalMap", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"Bump", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"BumpFactor", (1.0, "p_double", False)), - (b"DisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)), - (b"DisplacementFactor", (1.0, "p_double", False)), - (b"VectorDisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)), - (b"VectorDisplacementFactor", (1.0, "p_double", False)), + b"EmissiveColor": ((0.0, 0.0, 0.0), "p_color", True), + b"EmissiveFactor": (1.0, "p_number", True), + b"AmbientColor": ((0.2, 0.2, 0.2), "p_color", True), + b"AmbientFactor": (1.0, "p_number", True), + b"DiffuseColor": ((0.8, 0.8, 0.8), "p_color", True), + b"DiffuseFactor": (1.0, "p_number", True), + b"TransparentColor": ((0.0, 0.0, 0.0), "p_color", True), + b"TransparencyFactor": (0.0, "p_number", True), + b"Opacity": (1.0, "p_number", True), + b"NormalMap": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"BumpFactor": (1.0, "p_double", False), + b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False), + b"DisplacementFactor": (1.0, "p_double", False), + b"VectorDisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb", False), + b"VectorDisplacementFactor": (1.0, "p_double", False), # Phong-specific. - (b"SpecularColor", ((0.2, 0.2, 0.2), "p_color", True)), - (b"SpecularFactor", (1.0, "p_number", True)), + b"SpecularColor": ((0.2, 0.2, 0.2), "p_color", True), + b"SpecularFactor": (1.0, "p_number", True), # Not sure about the name, importer uses this (but ShininessExponent for tex prop name!) # And in fbx exported by sdk, you have one in template, the other in actual material!!! :/ # For now, using both. - (b"Shininess", (20.0, "p_number", True)), - (b"ShininessExponent", (20.0, "p_number", True)), - (b"ReflectionColor", ((0.0, 0.0, 0.0), "p_color", True)), - (b"ReflectionFactor", (1.0, "p_number", True)), - )) + b"Shininess": (20.0, "p_number", True), + b"ShininessExponent": (20.0, "p_number", True), + b"ReflectionColor": ((0.0, 0.0, 0.0), "p_color", True), + b"ReflectionFactor": (1.0, "p_number", True), + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"Material", b"FbxSurfacePhong", props, nbr_users, [False]) @@ -405,26 +406,26 @@ def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_users=0): # WIP... # XXX Not sure about all names! - props = OrderedDict(( - (b"TextureTypeUse", (0, "p_enum", False)), # Standard. - (b"AlphaSource", (2, "p_enum", False)), # Black (i.e. texture's alpha), XXX name guessed!. - (b"Texture alpha", (1.0, "p_double", False)), - (b"PremultiplyAlpha", (True, "p_bool", False)), - (b"CurrentTextureBlendMode", (1, "p_enum", False)), # Additive... - (b"CurrentMappingType", (0, "p_enum", False)), # UV. - (b"UVSet", ("default", "p_string", False)), # UVMap name. - (b"WrapModeU", (0, "p_enum", False)), # Repeat. - (b"WrapModeV", (0, "p_enum", False)), # Repeat. - (b"UVSwap", (False, "p_bool", False)), - (b"Translation", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"Rotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"Scaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)), - (b"TextureRotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)), - (b"TextureScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)), + props = { + b"TextureTypeUse": (0, "p_enum", False), # Standard. + b"AlphaSource": (2, "p_enum", False), # Black (i.e. texture's alpha), XXX name guessed!. + b"Texture alpha": (1.0, "p_double", False), + b"PremultiplyAlpha": (True, "p_bool", False), + b"CurrentTextureBlendMode": (1, "p_enum", False), # Additive... + b"CurrentMappingType": (0, "p_enum", False), # UV. + b"UVSet": ("default", "p_string", False), # UVMap name. + b"WrapModeU": (0, "p_enum", False), # Repeat. + b"WrapModeV": (0, "p_enum", False), # Repeat. + b"UVSwap": (False, "p_bool", False), + b"Translation": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"Rotation": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"Scaling": ((1.0, 1.0, 1.0), "p_vector_3d", False), + b"TextureRotationPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False), + b"TextureScalingPivot": ((0.0, 0.0, 0.0), "p_vector_3d", False), # Not sure about those two... - (b"UseMaterial", (False, "p_bool", False)), - (b"UseMipMap", (False, "p_bool", False)), - )) + b"UseMaterial": (False, "p_bool", False), + b"UseMipMap": (False, "p_bool", False), + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"Texture", b"FbxFileTexture", props, nbr_users, [False]) @@ -432,86 +433,86 @@ def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_u def fbx_template_def_video(scene, settings, override_defaults=None, nbr_users=0): # WIP... - props = OrderedDict(( + props = { # All pictures. - (b"Width", (0, "p_integer", False)), - (b"Height", (0, "p_integer", False)), - (b"Path", ("", "p_string_url", False)), - (b"AccessMode", (0, "p_enum", False)), # Disk (0=Disk, 1=Mem, 2=DiskAsync). + b"Width": (0, "p_integer", False), + b"Height": (0, "p_integer", False), + b"Path": ("", "p_string_url", False), + b"AccessMode": (0, "p_enum", False), # Disk (0=Disk, 1=Mem, 2=DiskAsync). # All videos. - (b"StartFrame", (0, "p_integer", False)), - (b"StopFrame", (0, "p_integer", False)), - (b"Offset", (0, "p_timestamp", False)), - (b"PlaySpeed", (0.0, "p_double", False)), - (b"FreeRunning", (False, "p_bool", False)), - (b"Loop", (False, "p_bool", False)), - (b"InterlaceMode", (0, "p_enum", False)), # None, i.e. progressive. + b"StartFrame": (0, "p_integer", False), + b"StopFrame": (0, "p_integer", False), + b"Offset": (0, "p_timestamp", False), + b"PlaySpeed": (0.0, "p_double", False), + b"FreeRunning": (False, "p_bool", False), + b"Loop": (False, "p_bool", False), + b"InterlaceMode": (0, "p_enum", False), # None, i.e. progressive. # Image sequences. - (b"ImageSequence", (False, "p_bool", False)), - (b"ImageSequenceOffset", (0, "p_integer", False)), - (b"FrameRate", (0.0, "p_double", False)), - (b"LastFrame", (0, "p_integer", False)), - )) + b"ImageSequence": (False, "p_bool", False), + b"ImageSequenceOffset": (0, "p_integer", False), + b"FrameRate": (0.0, "p_double", False), + b"LastFrame": (0, "p_integer", False), + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"Video", b"FbxVideo", props, nbr_users, [False]) def fbx_template_def_pose(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict() + props = {} if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"Pose", b"", props, nbr_users, [False]) def fbx_template_def_deformer(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict() + props = {} if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"Deformer", b"", props, nbr_users, [False]) def fbx_template_def_animstack(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict(( - (b"Description", ("", "p_string", False)), - (b"LocalStart", (0, "p_timestamp", False)), - (b"LocalStop", (0, "p_timestamp", False)), - (b"ReferenceStart", (0, "p_timestamp", False)), - (b"ReferenceStop", (0, "p_timestamp", False)), - )) + props = { + b"Description": ("", "p_string", False), + b"LocalStart": (0, "p_timestamp", False), + b"LocalStop": (0, "p_timestamp", False), + b"ReferenceStart": (0, "p_timestamp", False), + b"ReferenceStop": (0, "p_timestamp", False), + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users, [False]) def fbx_template_def_animlayer(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict(( - (b"Weight", (100.0, "p_number", True)), - (b"Mute", (False, "p_bool", False)), - (b"Solo", (False, "p_bool", False)), - (b"Lock", (False, "p_bool", False)), - (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)), - (b"BlendMode", (0, "p_enum", False)), - (b"RotationAccumulationMode", (0, "p_enum", False)), - (b"ScaleAccumulationMode", (0, "p_enum", False)), - (b"BlendModeBypass", (0, "p_ulonglong", False)), - )) + props = { + b"Weight": (100.0, "p_number", True), + b"Mute": (False, "p_bool", False), + b"Solo": (False, "p_bool", False), + b"Lock": (False, "p_bool", False), + b"Color": ((0.8, 0.8, 0.8), "p_color_rgb", False), + b"BlendMode": (0, "p_enum", False), + b"RotationAccumulationMode": (0, "p_enum", False), + b"ScaleAccumulationMode": (0, "p_enum", False), + b"BlendModeBypass": (0, "p_ulonglong", False), + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users, [False]) def fbx_template_def_animcurvenode(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict(( - (FBX_ANIM_PROPSGROUP_NAME.encode(), (None, "p_compound", False)), - )) + props = { + FBX_ANIM_PROPSGROUP_NAME.encode(): (None, "p_compound", False), + } if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"AnimationCurveNode", b"FbxAnimCurveNode", props, nbr_users, [False]) def fbx_template_def_animcurve(scene, settings, override_defaults=None, nbr_users=0): - props = OrderedDict() + props = {} if override_defaults is not None: props.update(override_defaults) return FBXTemplate(b"AnimationCurve", b"", props, nbr_users, [False]) @@ -581,13 +582,13 @@ def fbx_data_empty_elements(root, empty, scene_data): # No custom properties, already saved with object (Model). -def fbx_data_lamp_elements(root, lamp, scene_data): +def fbx_data_light_elements(root, lamp, scene_data): """ Write the Lamp data block. """ gscale = scene_data.settings.global_scale - lamp_key = scene_data.data_lamps[lamp] + light_key = scene_data.data_lights[lamp] do_light = True decay_type = FBX_LIGHT_DECAY_TYPES['CONSTANT'] do_shadow = False @@ -595,11 +596,11 @@ def fbx_data_lamp_elements(root, lamp, scene_data): if lamp.type not in {'HEMI'}: if lamp.type not in {'SUN', 'AREA'}: decay_type = FBX_LIGHT_DECAY_TYPES[lamp.falloff_type] - do_light = (not lamp.use_only_shadow) and (lamp.use_specular or lamp.use_diffuse) - do_shadow = lamp.shadow_method not in {'NOSHADOW'} + do_light = True + do_shadow = lamp.use_shadow shadow_color = lamp.shadow_color - light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(lamp_key)) + light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(light_key)) light.add_string(fbx_name_class(lamp.name.encode(), b"NodeAttribute")) light.add_string(b"Light") @@ -639,8 +640,8 @@ def fbx_data_camera_elements(root, cam_obj, scene_data): # Real data now, good old camera! # Object transform info. loc, rot, scale, matrix, matrix_rot = cam_obj.fbx_object_tx(scene_data) - up = matrix_rot * Vector((0.0, 1.0, 0.0)) - to = matrix_rot * Vector((0.0, 0.0, -1.0)) + up = matrix_rot @ Vector((0.0, 1.0, 0.0)) + to = matrix_rot @ Vector((0.0, 0.0, -1.0)) # Render settings. # TODO We could export much more... render = scene_data.scene.render @@ -1035,7 +1036,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) uv_names = [uvlayer.name for uvlayer in me.uv_layers] for name in uv_names: - me.calc_tangents(name) + me.calc_tangents(uvmap=name) for idx, uvlayer in enumerate(me.uv_layers): name = uvlayer.name # Loop bitangents (aka binormals). @@ -1121,38 +1122,39 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): del _uvtuples_gen # Face's materials. - me_fbxmats_idx = scene_data.mesh_mat_indices.get(me) - if me_fbxmats_idx is not None: - me_blmats = me.materials - if me_fbxmats_idx and me_blmats: - lay_mat = elem_data_single_int32(geom, b"LayerElementMaterial", 0) - elem_data_single_int32(lay_mat, b"Version", FBX_GEOMETRY_MATERIAL_VERSION) - elem_data_single_string(lay_mat, b"Name", b"") - nbr_mats = len(me_fbxmats_idx) + me_fbxmaterials_idx = scene_data.mesh_material_indices.get(me) + if me_fbxmaterials_idx is not None: + me_blmaterials = me.materials + if me_fbxmaterials_idx and me_blmaterials: + lay_ma = elem_data_single_int32(geom, b"LayerElementMaterial", 0) + elem_data_single_int32(lay_ma, b"Version", FBX_GEOMETRY_MATERIAL_VERSION) + elem_data_single_string(lay_ma, b"Name", b"") + nbr_mats = len(me_fbxmaterials_idx) if nbr_mats > 1: t_pm = array.array(data_types.ARRAY_INT32, (0,)) * len(me.polygons) me.polygons.foreach_get("material_index", t_pm) # We have to validate mat indices, and map them to FBX indices. # Note a mat might not be in me_fbxmats_idx (e.g. node mats are ignored). - blmats_to_fbxmats_idxs = [me_fbxmats_idx[m] for m in me_blmats if m in me_fbxmats_idx] - mat_idx_limit = len(blmats_to_fbxmats_idxs) - def_mat = blmats_to_fbxmats_idxs[0] - _gen = (blmats_to_fbxmats_idxs[m] if m < mat_idx_limit else def_mat for m in t_pm) + blmaterials_to_fbxmaterials_idxs = [me_fbxmaterials_idx[m] + for m in me_blmaterials if m in me_fbxmaterials_idx] + ma_idx_limit = len(blmaterials_to_fbxmaterials_idxs) + def_ma = blmaterials_to_fbxmaterials_idxs[0] + _gen = (blmaterials_to_fbxmaterials_idxs[m] if m < ma_idx_limit else def_ma for m in t_pm) t_pm = array.array(data_types.ARRAY_INT32, _gen) - elem_data_single_string(lay_mat, b"MappingInformationType", b"ByPolygon") + elem_data_single_string(lay_ma, b"MappingInformationType", b"ByPolygon") # XXX Logically, should be "Direct" reference type, since we do not have any index array, and have one # value per polygon... # But looks like FBX expects it to be IndexToDirect here (maybe because materials are already # indices??? *sigh*). - elem_data_single_string(lay_mat, b"ReferenceInformationType", b"IndexToDirect") - elem_data_single_int32_array(lay_mat, b"Materials", t_pm) + elem_data_single_string(lay_ma, b"ReferenceInformationType", b"IndexToDirect") + elem_data_single_int32_array(lay_ma, b"Materials", t_pm) del t_pm else: - elem_data_single_string(lay_mat, b"MappingInformationType", b"AllSame") - elem_data_single_string(lay_mat, b"ReferenceInformationType", b"IndexToDirect") - elem_data_single_int32_array(lay_mat, b"Materials", [0]) + elem_data_single_string(lay_ma, b"MappingInformationType", b"AllSame") + elem_data_single_string(lay_ma, b"ReferenceInformationType", b"IndexToDirect") + elem_data_single_int32_array(lay_ma, b"Materials", [0]) # And the "layer TOC"... @@ -1181,10 +1183,10 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): lay_uv = elem_empty(layer, b"LayerElement") elem_data_single_string(lay_uv, b"Type", b"LayerElementUV") elem_data_single_int32(lay_uv, b"TypedIndex", 0) - if me_fbxmats_idx is not None: - lay_mat = elem_empty(layer, b"LayerElement") - elem_data_single_string(lay_mat, b"Type", b"LayerElementMaterial") - elem_data_single_int32(lay_mat, b"TypedIndex", 0) + if me_fbxmaterials_idx is not None: + lay_ma = elem_empty(layer, b"LayerElement") + elem_data_single_string(lay_ma, b"Type", b"LayerElementMaterial") + elem_data_single_int32(lay_ma, b"TypedIndex", 0) # Add other uv and/or vcol layers... for vcolidx, uvidx, tspaceidx in zip_longest(range(1, vcolnumber), range(1, uvnumber), range(1, tspacenumber), @@ -1214,76 +1216,70 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): done_meshes.add(me_key) -def check_skip_material(mat): - """Simple helper to check whether we actually support exporting that material or not""" - return mat.type not in {'SURFACE'} - - -def fbx_data_material_elements(root, mat, scene_data): +def fbx_data_material_elements(root, ma, scene_data): """ Write the Material data block. """ + ambient_color = (0.0, 0.0, 0.0) if scene_data.data_world: - ambient_color = next(iter(scene_data.data_world.keys())).ambient_color + ambient_color = next(iter(scene_data.data_world.keys())).color - mat_key, _objs = scene_data.data_materials[mat] - skip_mat = check_skip_material(mat) - node_mat = mat.use_nodes - mat_type = b"Phong" - # Approximation... - if not skip_mat and not node_mat and mat.specular_shader not in {'COOKTORR', 'PHONG', 'BLINN'}: - mat_type = b"Lambert" + ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True) + ma_key, _objs = scene_data.data_materials[ma] + ma_type = b"Phong" - fbx_mat = elem_data_single_int64(root, b"Material", get_fbx_uuid_from_key(mat_key)) - fbx_mat.add_string(fbx_name_class(mat.name.encode(), b"Material")) - fbx_mat.add_string(b"") + fbx_ma = elem_data_single_int64(root, b"Material", get_fbx_uuid_from_key(ma_key)) + fbx_ma.add_string(fbx_name_class(ma.name.encode(), b"Material")) + fbx_ma.add_string(b"") - elem_data_single_int32(fbx_mat, b"Version", FBX_MATERIAL_VERSION) + elem_data_single_int32(fbx_ma, b"Version", FBX_MATERIAL_VERSION) # those are not yet properties, it seems... - elem_data_single_string(fbx_mat, b"ShadingModel", mat_type) - elem_data_single_int32(fbx_mat, b"MultiLayer", 0) # Should be bool... + elem_data_single_string(fbx_ma, b"ShadingModel", ma_type) + elem_data_single_int32(fbx_ma, b"MultiLayer", 0) # Should be bool... tmpl = elem_props_template_init(scene_data.templates, b"Material") - props = elem_properties(fbx_mat) - - if not skip_mat: - elem_props_template_set(tmpl, props, "p_string", b"ShadingModel", mat_type.decode()) - elem_props_template_set(tmpl, props, "p_color", b"DiffuseColor", mat.diffuse_color) - elem_props_template_set(tmpl, props, "p_number", b"DiffuseFactor", mat.diffuse_intensity) - if not node_mat: - elem_props_template_set(tmpl, props, "p_color", b"EmissiveColor", mat.diffuse_color) - elem_props_template_set(tmpl, props, "p_number", b"EmissiveFactor", mat.emit) - elem_props_template_set(tmpl, props, "p_color", b"AmbientColor", ambient_color) - elem_props_template_set(tmpl, props, "p_number", b"AmbientFactor", mat.ambient) - elem_props_template_set(tmpl, props, "p_color", b"TransparentColor", - mat.diffuse_color if mat.use_transparency else (1.0, 1.0, 1.0)) - elem_props_template_set(tmpl, props, "p_number", b"TransparencyFactor", - 1.0 - mat.alpha if mat.use_transparency else 0.0) - elem_props_template_set(tmpl, props, "p_number", b"Opacity", mat.alpha if mat.use_transparency else 1.0) - elem_props_template_set(tmpl, props, "p_vector_3d", b"NormalMap", (0.0, 0.0, 0.0)) - # Not sure about those... - """ - b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"), - b"BumpFactor": (1.0, "p_double"), - b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"), - b"DisplacementFactor": (0.0, "p_double"), - """ - if mat_type == b"Phong": - elem_props_template_set(tmpl, props, "p_color", b"SpecularColor", mat.specular_color) - elem_props_template_set(tmpl, props, "p_number", b"SpecularFactor", mat.specular_intensity / 2.0) - # See Material template about those two! - elem_props_template_set(tmpl, props, "p_number", b"Shininess", (mat.specular_hardness - 1.0) / 5.10) - elem_props_template_set(tmpl, props, "p_number", b"ShininessExponent", (mat.specular_hardness - 1.0) / 5.10) - elem_props_template_set(tmpl, props, "p_color", b"ReflectionColor", mat.mirror_color) - elem_props_template_set(tmpl, props, "p_number", b"ReflectionFactor", - mat.raytrace_mirror.reflect_factor if mat.raytrace_mirror.use else 0.0) + props = elem_properties(fbx_ma) + + elem_props_template_set(tmpl, props, "p_string", b"ShadingModel", ma_type.decode()) + elem_props_template_set(tmpl, props, "p_color", b"DiffuseColor", ma_wrap.base_color) + # Not in Principled BSDF, so assuming always 1 + elem_props_template_set(tmpl, props, "p_number", b"DiffuseFactor", 1.0) + # Not in Principled BSDF, so assuming always 0 + elem_props_template_set(tmpl, props, "p_color", b"EmissiveColor", ma_wrap.base_color) + elem_props_template_set(tmpl, props, "p_number", b"EmissiveFactor", 0.0) + # Not in Principled BSDF, so assuming always 0 + elem_props_template_set(tmpl, props, "p_color", b"AmbientColor", ambient_color) + elem_props_template_set(tmpl, props, "p_number", b"AmbientFactor", 0.0) + elem_props_template_set(tmpl, props, "p_color", b"TransparentColor", ma_wrap.base_color) + elem_props_template_set(tmpl, props, "p_number", b"TransparencyFactor", ma_wrap.transmission) + elem_props_template_set(tmpl, props, "p_number", b"Opacity", 1.0 - ma_wrap.transmission) + elem_props_template_set(tmpl, props, "p_vector_3d", b"NormalMap", (0.0, 0.0, 0.0)) + # Not sure about those... + """ + b"Bump": ((0.0, 0.0, 0.0), "p_vector_3d"), + b"BumpFactor": (1.0, "p_double"), + b"DisplacementColor": ((0.0, 0.0, 0.0), "p_color_rgb"), + b"DisplacementFactor": (0.0, "p_double"), + """ + # TODO: use specular tint? + elem_props_template_set(tmpl, props, "p_color", b"SpecularColor", ma_wrap.base_color) + elem_props_template_set(tmpl, props, "p_number", b"SpecularFactor", ma_wrap.specular / 2.0) + # See Material template about those two! + # XXX Totally empirical conversion, trying to adapt it + # (from 0.0 - 100.0 FBX shininess range to 1.0 - 0.0 Principled BSDF range)... + shininess = (1.0 - ma_wrap.roughness) * 10 + shininess *= shininess + elem_props_template_set(tmpl, props, "p_number", b"Shininess", shininess) + elem_props_template_set(tmpl, props, "p_number", b"ShininessExponent", shininess) + elem_props_template_set(tmpl, props, "p_color", b"ReflectionColor", ma_wrap.base_color) + elem_props_template_set(tmpl, props, "p_number", b"ReflectionFactor", ma_wrap.metallic) elem_props_template_finalize(tmpl, props) # Custom properties. if scene_data.settings.use_custom_props: - fbx_data_element_custom_properties(props, mat) + fbx_data_element_custom_properties(props, ma) def _gen_vid_path(img, scene_data): @@ -1294,7 +1290,7 @@ def _gen_vid_path(img, scene_data): return fname_abs, fname_rel -def fbx_data_texture_file_elements(root, tex, scene_data): +def fbx_data_texture_file_elements(root, blender_tex_key, scene_data): """ Write the (file) Texture data block. """ @@ -1302,45 +1298,50 @@ def fbx_data_texture_file_elements(root, tex, scene_data): # Textures do not seem to use properties as much as they could. # For now assuming most logical and simple stuff. - tex_key, _mats = scene_data.data_textures[tex] - img = tex.texture.image + ma, sock_name = blender_tex_key + ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True) + tex_key, _fbx_prop = scene_data.data_textures[blender_tex_key] + tex = getattr(ma_wrap, sock_name) + img = tex.image fname_abs, fname_rel = _gen_vid_path(img, scene_data) fbx_tex = elem_data_single_int64(root, b"Texture", get_fbx_uuid_from_key(tex_key)) - fbx_tex.add_string(fbx_name_class(tex.name.encode(), b"Texture")) + fbx_tex.add_string(fbx_name_class(sock_name.encode(), b"Texture")) fbx_tex.add_string(b"") elem_data_single_string(fbx_tex, b"Type", b"TextureVideoClip") elem_data_single_int32(fbx_tex, b"Version", FBX_TEXTURE_VERSION) - elem_data_single_string(fbx_tex, b"TextureName", fbx_name_class(tex.name.encode(), b"Texture")) + elem_data_single_string(fbx_tex, b"TextureName", fbx_name_class(sock_name.encode(), b"Texture")) elem_data_single_string(fbx_tex, b"Media", fbx_name_class(img.name.encode(), b"Video")) elem_data_single_string_unicode(fbx_tex, b"FileName", fname_abs) elem_data_single_string_unicode(fbx_tex, b"RelativeFilename", fname_rel) alpha_source = 0 # None if img.use_alpha: - if tex.texture.use_calculate_alpha: - alpha_source = 1 # RGBIntensity as alpha. - else: - alpha_source = 2 # Black, i.e. alpha channel. + # ~ if tex.texture.use_calculate_alpha: + # ~ alpha_source = 1 # RGBIntensity as alpha. + # ~ else: + # ~ alpha_source = 2 # Black, i.e. alpha channel. + alpha_source = 2 # Black, i.e. alpha channel. # BlendMode not useful for now, only affects layered textures afaics. mapping = 0 # UV. uvset = None - if tex.texture_coords in {'ORCO'}: # XXX Others? - if tex.mapping in {'FLAT'}: + if tex.texcoords == 'ORCO': # XXX Others? + if tex.projection == 'FLAT': mapping = 1 # Planar - elif tex.mapping in {'CUBE'}: + elif tex.projection == 'CUBE': mapping = 4 # Box - elif tex.mapping in {'TUBE'}: + elif tex.projection == 'TUBE': mapping = 3 # Cylindrical - elif tex.mapping in {'SPHERE'}: + elif tex.projection == 'SPHERE': mapping = 2 # Spherical - elif tex.texture_coords in {'UV'}: + elif tex.texcoords == 'UV': mapping = 0 # UV # Yuck, UVs are linked by mere names it seems... :/ - uvset = tex.uv_layer + # XXX TODO how to get that now??? + # uvset = tex.uv_layer wrap_mode = 1 # Clamp - if tex.texture.extension in {'REPEAT'}: + if tex.extension == 'REPEAT': wrap_mode = 0 # Repeat tmpl = elem_props_template_init(scene_data.templates, b"TextureFile") @@ -1353,16 +1354,15 @@ def fbx_data_texture_file_elements(root, tex, scene_data): elem_props_template_set(tmpl, props, "p_string", b"UVSet", uvset) elem_props_template_set(tmpl, props, "p_enum", b"WrapModeU", wrap_mode) elem_props_template_set(tmpl, props, "p_enum", b"WrapModeV", wrap_mode) - elem_props_template_set(tmpl, props, "p_vector_3d", b"Translation", tex.offset) - elem_props_template_set(tmpl, props, "p_vector_3d", b"Scaling", tex.scale) + elem_props_template_set(tmpl, props, "p_vector_3d", b"Translation", tex.translation) + elem_props_template_set(tmpl, props, "p_vector_3d", b"Rotation", (-r for r in tex.rotation)) + elem_props_template_set(tmpl, props, "p_vector_3d", b"Scaling", (((1.0 / s) if s != 0.0 else 1.0) for s in tex.scale)) # UseMaterial should always be ON imho. elem_props_template_set(tmpl, props, "p_bool", b"UseMaterial", True) - elem_props_template_set(tmpl, props, "p_bool", b"UseMipMap", tex.texture.use_mipmap) + elem_props_template_set(tmpl, props, "p_bool", b"UseMipMap", False) elem_props_template_finalize(tmpl, props) - # Custom properties. - if scene_data.settings.use_custom_props: - fbx_data_element_custom_properties(props, tex.texture) + # No custom properties, since that's not a data-block anymore. def fbx_data_video_elements(root, vid, scene_data): @@ -1472,7 +1472,7 @@ def fbx_data_armature_elements(root, arm_obj, scene_data): bo_vg_idx = {bo_obj.bdata.name: ob.vertex_groups[bo_obj.bdata.name].index for bo_obj in clusters.keys() if bo_obj.bdata.name in ob.vertex_groups} valid_idxs = set(bo_vg_idx.values()) - vgroups = {vg.index: OrderedDict() for vg in ob.vertex_groups} + vgroups = {vg.index: {} for vg in ob.vertex_groups} verts_vgroups = (sorted(((vg.group, vg.weight) for vg in v.groups if vg.weight and vg.group in valid_idxs), key=lambda e: e[1], reverse=True) for v in me.vertices) @@ -1507,7 +1507,7 @@ def fbx_data_armature_elements(root, arm_obj, scene_data): # http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return- # by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/ elem_data_single_float64_array(fbx_clstr, b"Transform", - matrix4_to_array(mat_world_bones[bo_obj].inverted_safe() * mat_world_obj)) + matrix4_to_array(mat_world_bones[bo_obj].inverted_safe() @ mat_world_obj)) elem_data_single_float64_array(fbx_clstr, b"TransformLink", matrix4_to_array(mat_world_bones[bo_obj])) elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix4_to_array(mat_world_arm)) @@ -1580,7 +1580,7 @@ def fbx_data_object_elements(root, ob_obj, scene_data): obj_type = b"Null" elif (ob_obj.type in BLENDER_OBJECT_TYPES_MESHLIKE): obj_type = b"Mesh" - elif (ob_obj.type == 'LAMP'): + elif (ob_obj.type == 'LIGHT'): obj_type = b"Light" elif (ob_obj.type == 'CAMERA'): obj_type = b"Camera" @@ -1727,46 +1727,30 @@ def fbx_data_animation_elements(root, scene_data): # ##### Top-level FBX data container. ##### -def fbx_mat_properties_from_texture(tex): - """ - Returns a set of FBX metarial properties that are affected by the given texture. - Quite obviously, this is a fuzzy and far-from-perfect mapping! Amounts of influence are completely lost, e.g. - Note tex is actually expected to be a texture slot. - """ - # Mapping Blender -> FBX (blend_use_name, blend_fact_name, fbx_name). - blend_to_fbx = ( - # Lambert & Phong... - ("diffuse", "diffuse", b"DiffuseFactor"), - ("color_diffuse", "diffuse_color", b"DiffuseColor"), - ("alpha", "alpha", b"TransparencyFactor"), - ("diffuse", "diffuse", b"TransparentColor"), # Uses diffuse color in Blender! - ("emit", "emit", b"EmissiveFactor"), - ("diffuse", "diffuse", b"EmissiveColor"), # Uses diffuse color in Blender! - ("ambient", "ambient", b"AmbientFactor"), - # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore... - ("normal", "normal", b"NormalMap"), - # Note: unsure about those... :/ - # ("", "", b"Bump"), - # ("", "", b"BumpFactor"), - # ("", "", b"DisplacementColor"), - # ("", "", b"DisplacementFactor"), - # Phong only. - ("specular", "specular", b"SpecularFactor"), - ("color_spec", "specular_color", b"SpecularColor"), - # See Material template about those two! - ("hardness", "hardness", b"Shininess"), - ("hardness", "hardness", b"ShininessExponent"), - ("mirror", "mirror", b"ReflectionColor"), - ("raymir", "raymir", b"ReflectionFactor"), - ) - - tex_fbx_props = set() - for use_map_name, name_factor, fbx_prop_name in blend_to_fbx: - # Always export enabled textures, even if they have a null influence... - if getattr(tex, "use_map_" + use_map_name): - tex_fbx_props.add(fbx_prop_name) - - return tex_fbx_props +# Mapping Blender -> FBX (principled_socket_name, fbx_name). +PRINCIPLED_TEXTURE_SOCKETS_TO_FBX = ( + # ("diffuse", "diffuse", b"DiffuseFactor"), + ("base_color_texture", b"DiffuseColor"), + ("transmission_texture", b"TransparencyFactor"), + # ("base_color_texture", b"TransparentColor"), # Uses diffuse color in Blender! + # ("emit", "emit", b"EmissiveFactor"), + # ("diffuse", "diffuse", b"EmissiveColor"), # Uses diffuse color in Blender! + # ("ambient", "ambient", b"AmbientFactor"), + # ("", "", b"AmbientColor"), # World stuff in Blender, for now ignore... + ("normalmap_texture", b"NormalMap"), + # Note: unsure about those... :/ + # ("", "", b"Bump"), + # ("", "", b"BumpFactor"), + # ("", "", b"DisplacementColor"), + # ("", "", b"DisplacementFactor"), + ("specular_texture", b"SpecularFactor"), + # ("base_color", b"SpecularColor"), # TODO: use tint? + # See Material template about those two! + ("roughness_texture", b"Shininess"), + ("roughness_texture", b"ShininessExponent"), + # ("mirror", "mirror", b"ReflectionColor"), + ("metallic_texture", b"ReflectionFactor"), +) def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes, @@ -1781,7 +1765,7 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes, data_empties[arm_obj] = get_blender_empty_key(arm_obj.bdata) arm_data = arm_obj.bdata.data - bones = OrderedDict() + bones = {} for bo in arm_obj.bones: if settings.use_armature_deform_only: if bo.bdata.use_deform: @@ -1795,7 +1779,7 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes, else: bones[bo] = True - bones = OrderedDict((bo, None) for bo, use in bones.items() if use) + bones = {bo: None for bo, use in bones.items() if use} if not bones: return @@ -1823,8 +1807,8 @@ def fbx_skeleton_from_armature(scene, settings, arm_obj, objects, data_meshes, # Note: bindpose have no relations at all (no connections), so no need for any preprocess for them. # Create skin & clusters relations (note skins are connected to geometry, *not* model!). _key, me, _free = data_meshes[ob_obj] - clusters = OrderedDict((bo, get_blender_bone_cluster_key(arm_obj.bdata, me, bo.bdata)) for bo in bones) - data_deformers_skin.setdefault(arm_obj, OrderedDict())[me] = (get_blender_armature_skin_key(arm_obj.bdata, me), + clusters = {bo: get_blender_bone_cluster_key(arm_obj.bdata, me, bo.bdata) for bo in bones} + data_deformers_skin.setdefault(arm_obj, {})[me] = (get_blender_armature_skin_key(arm_obj.bdata, me), ob_obj, clusters) # We don't want a regular parent relationship for those in FBX... @@ -1859,9 +1843,9 @@ def fbx_generate_leaf_bones(settings, data_bones): bone_length = (parent.bdata.tail_local - parent.bdata.head_local).length matrix = Matrix.Translation((0, bone_length, 0)) if settings.bone_correction_matrix_inv: - matrix = settings.bone_correction_matrix_inv * matrix + matrix = settings.bone_correction_matrix_inv @ matrix if settings.bone_correction_matrix: - matrix = matrix * settings.bone_correction_matrix + matrix = matrix @ settings.bone_correction_matrix leaf_bones.append((node_name, parent_uuid, node_uuid, attr_uuid, matrix, hide, size)) return leaf_bones @@ -1874,6 +1858,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No bake_step = scene_data.settings.bake_anim_step simplify_fac = scene_data.settings.bake_anim_simplify_factor scene = scene_data.scene + depsgraph = scene_data.depsgraph force_keying = scene_data.settings.bake_anim_use_all_bones force_sek = scene_data.settings.bake_anim_force_startend_keying @@ -1884,16 +1869,14 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No continue if ob_obj.type == 'ARMATURE': objects |= {bo_obj for bo_obj in ob_obj.bones if bo_obj in scene_data.objects} - ob_obj.dupli_list_create(scene, 'RENDER') - for dp_obj in ob_obj.dupli_list: + for dp_obj in ob_obj.dupli_list_gen(depsgraph): if dp_obj in scene_data.objects: objects.add(dp_obj) - ob_obj.dupli_list_clear() else: objects = scene_data.objects back_currframe = scene.frame_current - animdata_ob = OrderedDict() + animdata_ob = {} p_rots = {} for ob_obj in objects: @@ -1909,8 +1892,8 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No p_rots[ob_obj] = rot force_key = (simplify_fac == 0.0) + animdata_shapes = {} - animdata_shapes = OrderedDict() for me, (me_key, _shapes_key, shapes) in scene_data.data_deformers_shape.items(): # Ignore absolute shape keys for now! if not me.shape_keys.use_relative: @@ -1921,7 +1904,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No acnode.add_group(me_key, shape.name, shape.name, (shape.name,)) animdata_shapes[channel_key] = (acnode, me, shape) - animdata_cameras = OrderedDict() + animdata_cameras = {} for cam_obj, cam_key in scene_data.data_cameras.items(): cam = cam_obj.bdata.data acnode = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCAL', force_key, force_sek, (cam.lens,)) @@ -1930,10 +1913,10 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No currframe = f_start while currframe <= f_end: real_currframe = currframe - f_start if start_zero else currframe - scene.frame_set(int(currframe), currframe - int(currframe)) + scene.frame_set(int(currframe), subframe=currframe - int(currframe)) - for ob_obj in animdata_ob: - ob_obj.dupli_list_create(scene, 'RENDER') + for dp_obj in ob_obj.dupli_list_gen(depsgraph): + pass # Merely updating dupli matrix of ObjectWrapper... for ob_obj, (anim_loc, anim_rot, anim_scale) in animdata_ob.items(): # We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!). p_rot = p_rots.get(ob_obj, None) @@ -1942,17 +1925,15 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No anim_loc.add_keyframe(real_currframe, loc) anim_rot.add_keyframe(real_currframe, tuple(convert_rad_to_deg_iter(rot))) anim_scale.add_keyframe(real_currframe, scale) - for ob_obj in objects: - ob_obj.dupli_list_clear() for anim_shape, me, shape in animdata_shapes.values(): anim_shape.add_keyframe(real_currframe, (shape.value * 100.0,)) for anim_camera, camera in animdata_cameras.values(): anim_camera.add_keyframe(real_currframe, (camera.lens,)) currframe += bake_step - scene.frame_set(back_currframe, 0.0) + scene.frame_set(back_currframe, subframe=0.0) - animations = OrderedDict() + animations = {} # And now, produce final data (usable by FBX export code) # Objects-like loc/rot/scale... @@ -1962,34 +1943,28 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No if not anim: continue for obj_key, group_key, group, fbx_group, fbx_gname in anim.get_final_data(scene, ref_id, force_keep): - anim_data = animations.get(obj_key) - if anim_data is None: - anim_data = animations[obj_key] = ("dummy_unused_key", OrderedDict()) + anim_data = animations.setdefault(obj_key, ("dummy_unused_key", {})) anim_data[1][fbx_group] = (group_key, group, fbx_gname) # And meshes' shape keys. for channel_key, (anim_shape, me, shape) in animdata_shapes.items(): - final_keys = OrderedDict() + final_keys = {} anim_shape.simplify(simplify_fac, bake_step, force_keep) if not anim_shape: continue for elem_key, group_key, group, fbx_group, fbx_gname in anim_shape.get_final_data(scene, ref_id, force_keep): - anim_data = animations.get(elem_key) - if anim_data is None: - anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict()) - anim_data[1][fbx_group] = (group_key, group, fbx_gname) + anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {})) + anim_data[1][fbx_group] = (group_key, group, fbx_gname) # And cameras' lens keys. for cam_key, (anim_camera, camera) in animdata_cameras.items(): - final_keys = OrderedDict() + final_keys = {} anim_camera.simplify(simplify_fac, bake_step, force_keep) if not anim_camera: continue for elem_key, group_key, group, fbx_group, fbx_gname in anim_camera.get_final_data(scene, ref_id, force_keep): - anim_data = animations.get(elem_key) - if anim_data is None: - anim_data = animations[elem_key] = ("dummy_unused_key", OrderedDict()) - anim_data[1][fbx_group] = (group_key, group, fbx_gname) + anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {})) + anim_data[1][fbx_group] = (group_key, group, fbx_gname) astack_key = get_blender_anim_stack_key(scene, ref_id) alayer_key = get_blender_anim_layer_key(scene, ref_id) @@ -2055,7 +2030,7 @@ def fbx_animations(scene_data): add_anim(animations, animated, fbx_animations_do(scene_data, strip, strip.frame_start, strip.frame_end, True, force_keep=True)) strip.mute = True - scene.frame_set(scene.frame_current, 0.0) + scene.frame_set(scene.frame_current, subframe=0.0) for strip in strips: strip.mute = False @@ -2082,14 +2057,14 @@ def fbx_animations(scene_data): 'location', 'rotation_quaternion', 'rotation_axis_angle', 'rotation_euler', 'rotation_mode', 'scale', 'delta_location', 'delta_rotation_euler', 'delta_rotation_quaternion', 'delta_scale', 'lock_location', 'lock_rotation', 'lock_rotation_w', 'lock_rotations_4d', 'lock_scale', - 'tag', 'layers', 'select', 'track_axis', 'up_axis', 'active_material', 'active_material_index', - 'matrix_parent_inverse', 'empty_draw_type', 'empty_draw_size', 'empty_image_offset', 'pass_index', - 'color', 'hide', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset', - 'use_extra_recalc_object', 'use_extra_recalc_data', 'dupli_type', 'use_dupli_frames_speed', - 'use_dupli_vertices_rotation', 'use_dupli_faces_scale', 'dupli_faces_scale', 'dupli_group', - 'dupli_frames_start', 'dupli_frames_end', 'dupli_frames_on', 'dupli_frames_off', - 'draw_type', 'show_bounds', 'draw_bounds_type', 'show_name', 'show_axis', 'show_texture_space', - 'show_wire', 'show_all_edges', 'show_transparent', 'show_x_ray', + 'tag', 'track_axis', 'up_axis', 'active_material', 'active_material_index', + 'matrix_parent_inverse', 'empty_display_type', 'empty_display_size', 'empty_image_offset', 'pass_index', + 'color', 'hide_viewport', 'hide_select', 'hide_render', 'use_slow_parent', 'slow_parent_offset', + 'instance_type', 'use_instance_frames_speed', + 'use_instance_vertices_rotation', 'use_instance_faces_scale', 'instance_faces_scale', + 'instance_frames_start', 'instance_frames_end', 'instance_frames_on', 'instance_frames_off', + 'display_type', 'show_bounds', 'display_bounds_type', 'show_name', 'show_axis', 'show_texture_space', + 'show_wire', 'show_all_edges', 'show_transparent', 'show_in_front', 'show_only_shape_key', 'use_shape_key_edit_mode', 'active_shape_key_index', ) for p in props: @@ -2134,7 +2109,7 @@ def fbx_animations(scene_data): pbo.matrix_basis = mat.copy() ob.animation_data.action = org_act restore_object(ob, ob_copy) - scene.frame_set(scene.frame_current, 0.0) + scene.frame_set(scene.frame_current, subframe=0.0) if pbones_matrices is not ...: for pbo, mat in zip(ob.pose.bones, pbones_matrices): @@ -2142,19 +2117,19 @@ def fbx_animations(scene_data): ob.animation_data.action = org_act bpy.data.objects.remove(ob_copy) - scene.frame_set(scene.frame_current, 0.0) + scene.frame_set(scene.frame_current, subframe=0.0) # Global (containing everything) animstack, only if not exporting NLA strips and/or all actions. if not scene_data.settings.bake_anim_use_nla_strips and not scene_data.settings.bake_anim_use_all_actions: add_anim(animations, animated, fbx_animations_do(scene_data, None, scene.frame_start, scene.frame_end, False)) # Be sure to update all matrices back to org state! - scene.frame_set(scene.frame_current, 0.0) + scene.frame_set(scene.frame_current, subframe=0.0) return animations, animated, frame_start, frame_end -def fbx_data_from_scene(scene, settings): +def fbx_data_from_scene(scene, depsgraph, settings): """ Do some pre-processing over scene's data... """ @@ -2169,34 +2144,32 @@ def fbx_data_from_scene(scene, settings): # This is rather simple for now, maybe we could end generating templates with most-used values # instead of default ones? - objects = OrderedDict() # Because we do not have any ordered set... + objects = {} # Because we do not have any ordered set... for ob in settings.context_objects: if ob.type not in objtypes: continue ob_obj = ObjectWrapper(ob) objects[ob_obj] = None # Duplis... - ob_obj.dupli_list_create(scene, 'RENDER') - for dp_obj in ob_obj.dupli_list: + for dp_obj in ob_obj.dupli_list_gen(depsgraph): if dp_obj.type not in dp_objtypes: continue objects[dp_obj] = None - ob_obj.dupli_list_clear() perfmon.step("FBX export prepare: Wrapping Data (lamps, cameras, empties)...") - data_lamps = OrderedDict((ob_obj.bdata.data, get_blenderID_key(ob_obj.bdata.data)) - for ob_obj in objects if ob_obj.type == 'LAMP') + data_lights = {ob_obj.bdata.data: get_blenderID_key(ob_obj.bdata.data) + for ob_obj in objects if ob_obj.type == 'LIGHT'} # Unfortunately, FBX camera data contains object-level data (like position, orientation, etc.)... - data_cameras = OrderedDict((ob_obj, get_blenderID_key(ob_obj.bdata.data)) - for ob_obj in objects if ob_obj.type == 'CAMERA') + data_cameras = {ob_obj: get_blenderID_key(ob_obj.bdata.data) + for ob_obj in objects if ob_obj.type == 'CAMERA'} # Yep! Contains nothing, but needed! - data_empties = OrderedDict((ob_obj, get_blender_empty_key(ob_obj.bdata)) - for ob_obj in objects if ob_obj.type == 'EMPTY') + data_empties = {ob_obj: get_blender_empty_key(ob_obj.bdata) + for ob_obj in objects if ob_obj.type == 'EMPTY'} perfmon.step("FBX export prepare: Wrapping Meshes...") - data_meshes = OrderedDict() + data_meshes = {} for ob_obj in objects: if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE: continue @@ -2221,21 +2194,22 @@ def fbx_data_from_scene(scene, settings): # No need to create a new mesh in this case, if no modifier is active! for mod in ob.modifiers: # For meshes, when armature export is enabled, disable Armature modifiers here! + # XXX Temp hacks here since currently we only have access to a viewport depsgraph... if mod.type == 'ARMATURE' and 'ARMATURE' in settings.object_types: - tmp_mods.append((mod, mod.show_render)) + tmp_mods.append((mod, mod.show_render, mod.show_viewport)) mod.show_render = False - if mod.show_render: + mod.show_viewport = False + if mod.show_render or mod.show_viewport: use_org_data = False if not use_org_data: tmp_me = ob.to_mesh( - scene, - apply_modifiers=settings.use_mesh_modifiers, - settings='RENDER' if settings.use_mesh_modifiers_render else 'PREVIEW', - ) + depsgraph, + apply_modifiers=settings.use_mesh_modifiers) data_meshes[ob_obj] = (get_blenderID_key(tmp_me), tmp_me, True) # Re-enable temporary disabled modifiers. - for mod, show_render in tmp_mods: + for mod, show_render, show_viewport in tmp_mods: mod.show_render = show_render + mod.show_viewport = show_viewport if use_org_data: data_meshes[ob_obj] = (get_blenderID_key(ob.data), ob.data, False) @@ -2246,7 +2220,7 @@ def fbx_data_from_scene(scene, settings): perfmon.step("FBX export prepare: Wrapping ShapeKeys...") # ShapeKeys. - data_deformers_shape = OrderedDict() + data_deformers_shape = {} geom_mat_co = settings.global_matrix if settings.bake_space_transform else None for me_key, me, _free in data_meshes.values(): if not (me.shape_keys and len(me.shape_keys.key_blocks) > 1): # We do not want basis-only relative skeys... @@ -2284,13 +2258,13 @@ def fbx_data_from_scene(scene, settings): continue channel_key, geom_key = get_blender_mesh_shape_channel_key(me, shape) data = (channel_key, geom_key, shape_verts_co, shape_verts_idx) - data_deformers_shape.setdefault(me, (me_key, shapes_key, OrderedDict()))[2][shape] = data + data_deformers_shape.setdefault(me, (me_key, shapes_key, {}))[2][shape] = data perfmon.step("FBX export prepare: Wrapping Armatures...") # Armatures! - data_deformers_skin = OrderedDict() - data_bones = OrderedDict() + data_deformers_skin = {} + data_bones = {} arm_parents = set() for ob_obj in tuple(objects): if not (ob_obj.is_object and ob_obj.type in {'ARMATURE'}): @@ -2307,71 +2281,51 @@ def fbx_data_from_scene(scene, settings): # Some world settings are embedded in FBX materials... if scene.world: - data_world = OrderedDict(((scene.world, get_blenderID_key(scene.world)),)) + data_world = {scene.world: get_blenderID_key(scene.world)} else: - data_world = OrderedDict() + data_world = {} perfmon.step("FBX export prepare: Wrapping Materials...") - # TODO: Check all the mat stuff works even when mats are linked to Objects + # TODO: Check all the material stuff works even when they are linked to Objects # (we can then have the same mesh used with different materials...). # *Should* work, as FBX always links its materials to Models (i.e. objects). # XXX However, material indices would probably break... - data_materials = OrderedDict() + data_materials = {} for ob_obj in objects: # If obj is not a valid object for materials, wrapper will just return an empty tuple... - for mat_s in ob_obj.material_slots: - mat = mat_s.material - if mat is None: + for ma_s in ob_obj.material_slots: + ma = ma_s.material + if ma is None: continue # Empty slots! # Note theoretically, FBX supports any kind of materials, even GLSL shaders etc. # However, I doubt anything else than Lambert/Phong is really portable! - # We support any kind of 'surface' shader though, better to have some kind of default Lambert than nothing. - # Note we want to keep a 'dummy' empty mat even when we can't really support it, see T41396. - mat_data = data_materials.get(mat) - if mat_data is not None: - mat_data[1].append(ob_obj) - else: - data_materials[mat] = (get_blenderID_key(mat), [ob_obj]) + # Note we want to keep a 'dummy' empty material even when we can't really support it, see T41396. + ma_data = data_materials.setdefault(ma, (get_blenderID_key(ma), [])) + ma_data[1].append(ob_obj) perfmon.step("FBX export prepare: Wrapping Textures...") # Note FBX textures also hold their mapping info. # TODO: Support layers? - data_textures = OrderedDict() + data_textures = {} # FbxVideo also used to store static images... - data_videos = OrderedDict() + data_videos = {} # For now, do not use world textures, don't think they can be linked to anything FBX wise... - for mat in data_materials.keys(): - if check_skip_material(mat): - continue - for tex, use_tex in zip(mat.texture_slots, mat.use_textures): - if tex is None or tex.texture is None or not use_tex: - continue - # For now, only consider image textures. - # Note FBX does has support for procedural, but this is not portable at all (opaque blob), - # so not useful for us. - # TODO I think ENVIRONMENT_MAP should be usable in FBX as well, but for now let it aside. - # if tex.texture.type not in {'IMAGE', 'ENVIRONMENT_MAP'}: - if tex.texture.type not in {'IMAGE'}: - continue - img = tex.texture.image - if img is None: + for ma in data_materials.keys(): + # Note: with nodal shaders, we'll could be generating much more textures, but that's kind of unavoidable, + # given that textures actually do not exist anymore in material context in Blender... + ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=True) + for sock_name, fbx_name in PRINCIPLED_TEXTURE_SOCKETS_TO_FBX: + tex = getattr(ma_wrap, sock_name) + if tex is None or tex.image is None: continue - # Find out whether we can actually use this texture for this material, in FBX context. - tex_fbx_props = fbx_mat_properties_from_texture(tex) - if not tex_fbx_props: - continue - tex_data = data_textures.get(tex) - if tex_data is not None: - tex_data[1][mat] = tex_fbx_props - else: - data_textures[tex] = (get_blenderID_key(tex), OrderedDict(((mat, tex_fbx_props),))) - vid_data = data_videos.get(img) - if vid_data is not None: - vid_data[1].append(tex) - else: - data_videos[img] = (get_blenderID_key(img), [tex]) + blender_tex_key = (ma, sock_name) + data_textures[blender_tex_key] = (get_blender_nodetexture_key(*blender_tex_key), fbx_name) + + img = tex.image + vid_data = data_videos.setdefault(img, (get_blenderID_key(img), [])) + vid_data[1].append(blender_tex_key) perfmon.step("FBX export prepare: Wrapping Animations...") @@ -2385,8 +2339,8 @@ def fbx_data_from_scene(scene, settings): # Kind of hack, we need a temp scene_data for object's space handling to bake animations... tmp_scdata = FBXExportData( None, None, None, - settings, scene, objects, None, None, 0.0, 0.0, - data_empties, data_lamps, data_cameras, data_meshes, None, + settings, scene, depsgraph, objects, None, None, 0.0, 0.0, + data_empties, data_lights, data_cameras, data_meshes, None, data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape, data_world, data_materials, data_textures, data_videos, ) @@ -2396,14 +2350,14 @@ def fbx_data_from_scene(scene, settings): perfmon.step("FBX export prepare: Generating templates...") - templates = OrderedDict() + templates = {} templates[b"GlobalSettings"] = fbx_template_def_globalsettings(scene, settings, nbr_users=1) if data_empties: templates[b"Null"] = fbx_template_def_null(scene, settings, nbr_users=len(data_empties)) - if data_lamps: - templates[b"Light"] = fbx_template_def_light(scene, settings, nbr_users=len(data_lamps)) + if data_lights: + templates[b"Light"] = fbx_template_def_light(scene, settings, nbr_users=len(data_lights)) if data_cameras: templates[b"Camera"] = fbx_template_def_camera(scene, settings, nbr_users=len(data_cameras)) @@ -2501,9 +2455,9 @@ def fbx_data_from_scene(scene, settings): bo_data_key = data_bones[ob_obj] connections.append((b"OO", get_fbx_uuid_from_key(bo_data_key), ob_obj.fbx_uuid, None)) else: - if ob_obj.type == 'LAMP': - lamp_key = data_lamps[ob_obj.bdata.data] - connections.append((b"OO", get_fbx_uuid_from_key(lamp_key), ob_obj.fbx_uuid, None)) + if ob_obj.type == 'LIGHT': + light_key = data_lights[ob_obj.bdata.data] + connections.append((b"OO", get_fbx_uuid_from_key(light_key), ob_obj.fbx_uuid, None)) elif ob_obj.type == 'CAMERA': cam_key = data_cameras[ob_obj] connections.append((b"OO", get_fbx_uuid_from_key(cam_key), ob_obj.fbx_uuid, None)) @@ -2543,35 +2497,33 @@ def fbx_data_from_scene(scene, settings): connections.append((b"OO", bo_obj.fbx_uuid, get_fbx_uuid_from_key(clstr_key), None)) # Materials - mesh_mat_indices = OrderedDict() + mesh_material_indices = {} _objs_indices = {} - for mat, (mat_key, ob_objs) in data_materials.items(): + for ma, (ma_key, ob_objs) in data_materials.items(): for ob_obj in ob_objs: - connections.append((b"OO", get_fbx_uuid_from_key(mat_key), ob_obj.fbx_uuid, None)) - # Get index of this mat for this object (or dupliobject). - # Mat indices for mesh faces are determined by their order in 'mat to ob' connections. - # Only mats for meshes currently... - # Note in case of dupliobjects a same me/mat idx will be generated several times... + connections.append((b"OO", get_fbx_uuid_from_key(ma_key), ob_obj.fbx_uuid, None)) + # Get index of this material for this object (or dupliobject). + # Material indices for mesh faces are determined by their order in 'ma to ob' connections. + # Only materials for meshes currently... + # Note in case of dupliobjects a same me/ma idx will be generated several times... # Should not be an issue in practice, and it's needed in case we export duplis but not the original! if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE: continue _mesh_key, me, _free = data_meshes[ob_obj] idx = _objs_indices[ob_obj] = _objs_indices.get(ob_obj, -1) + 1 - mesh_mat_indices.setdefault(me, OrderedDict())[mat] = idx + mesh_material_indices.setdefault(me, {})[ma] = idx del _objs_indices # Textures - for tex, (tex_key, mats) in data_textures.items(): - for mat, fbx_mat_props in mats.items(): - mat_key, _ob_objs = data_materials[mat] - for fbx_prop in fbx_mat_props: - # texture -> material properties - connections.append((b"OP", get_fbx_uuid_from_key(tex_key), get_fbx_uuid_from_key(mat_key), fbx_prop)) + for (ma, sock_name), (tex_key, fbx_prop) in data_textures.items(): + ma_key, _ob_objs = data_materials[ma] + # texture -> material properties + connections.append((b"OP", get_fbx_uuid_from_key(tex_key), get_fbx_uuid_from_key(ma_key), fbx_prop)) # Images - for vid, (vid_key, texs) in data_videos.items(): - for tex in texs: - tex_key, _texs = data_textures[tex] + for vid, (vid_key, blender_tex_keys) in data_videos.items(): + for blender_tex_key in blender_tex_keys: + tex_key, _fbx_prop = data_textures[blender_tex_key] connections.append((b"OO", get_fbx_uuid_from_key(vid_key), get_fbx_uuid_from_key(tex_key), None)) # Animations @@ -2603,8 +2555,8 @@ def fbx_data_from_scene(scene, settings): return FBXExportData( templates, templates_users, connections, - settings, scene, objects, animations, animated, frame_start, frame_end, - data_empties, data_lamps, data_cameras, data_meshes, mesh_mat_indices, + settings, scene, depsgraph, objects, animations, animated, frame_start, frame_end, + data_empties, data_lights, data_cameras, data_meshes, mesh_material_indices, data_bones, data_leaf_bones, data_deformers_skin, data_deformers_shape, data_world, data_materials, data_textures, data_videos, ) @@ -2813,10 +2765,10 @@ def fbx_objects_elements(root, scene_data): for empty in scene_data.data_empties: fbx_data_empty_elements(objects, empty, scene_data) - perfmon.step("FBX export fetch lamps (%d)..." % len(scene_data.data_lamps)) + perfmon.step("FBX export fetch lamps (%d)..." % len(scene_data.data_lights)) - for lamp in scene_data.data_lamps: - fbx_data_lamp_elements(objects, lamp, scene_data) + for lamp in scene_data.data_lights: + fbx_data_light_elements(objects, lamp, scene_data) perfmon.step("FBX export fetch cameras (%d)..." % len(scene_data.data_cameras)) @@ -2837,12 +2789,10 @@ def fbx_objects_elements(root, scene_data): if ob_obj.is_dupli: continue fbx_data_object_elements(objects, ob_obj, scene_data) - ob_obj.dupli_list_create(scene_data.scene, 'RENDER') - for dp_obj in ob_obj.dupli_list: + for dp_obj in ob_obj.dupli_list_gen(scene_data.depsgraph): if dp_obj not in scene_data.objects: continue fbx_data_object_elements(objects, dp_obj, scene_data) - ob_obj.dupli_list_clear() perfmon.step("FBX export fetch remaining...") @@ -2854,11 +2804,11 @@ def fbx_objects_elements(root, scene_data): if scene_data.data_leaf_bones: fbx_data_leaf_bone_elements(objects, scene_data) - for mat in scene_data.data_materials: - fbx_data_material_elements(objects, mat, scene_data) + for ma in scene_data.data_materials: + fbx_data_material_elements(objects, ma, scene_data) - for tex in scene_data.data_textures: - fbx_data_texture_file_elements(objects, tex, scene_data) + for blender_tex_key in scene_data.data_textures: + fbx_data_texture_file_elements(objects, blender_tex_key, scene_data) for vid in scene_data.data_videos: fbx_data_video_elements(objects, vid, scene_data) @@ -2907,7 +2857,7 @@ def fbx_takes_elements(root, scene_data): # ##### "Main" functions. ##### # This func can be called with just the filepath -def save_single(operator, scene, filepath="", +def save_single(operator, scene, depsgraph, filepath="", global_matrix=Matrix(), apply_unit_scale=False, global_scale=1.0, @@ -2945,7 +2895,7 @@ def save_single(operator, scene, filepath="", ObjectWrapper.cache_clear() if object_types is None: - object_types = {'EMPTY', 'CAMERA', 'LAMP', 'ARMATURE', 'MESH', 'OTHER'} + object_types = {'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'} if 'OTHER' in object_types: object_types |= BLENDER_OTHER_OBJECT_TYPES @@ -2953,12 +2903,12 @@ def save_single(operator, scene, filepath="", # Default Blender unit is equivalent to meter, while FBX one is centimeter... unit_scale = units_blender_to_fbx_factor(scene) if apply_unit_scale else 100.0 if apply_scale_options == 'FBX_SCALE_NONE': - global_matrix = Matrix.Scale(unit_scale * global_scale, 4) * global_matrix + global_matrix = Matrix.Scale(unit_scale * global_scale, 4) @ global_matrix unit_scale = 1.0 elif apply_scale_options == 'FBX_SCALE_UNITS': - global_matrix = Matrix.Scale(global_scale, 4) * global_matrix + global_matrix = Matrix.Scale(global_scale, 4) @ global_matrix elif apply_scale_options == 'FBX_SCALE_CUSTOM': - global_matrix = Matrix.Scale(unit_scale, 4) * global_matrix + global_matrix = Matrix.Scale(unit_scale, 4) @ global_matrix unit_scale = global_scale else: # if apply_scale_options == 'FBX_SCALE_ALL': unit_scale = global_scale * unit_scale @@ -3014,7 +2964,7 @@ def save_single(operator, scene, filepath="", start_time = time.process_time() # Generate some data about exported scene... - scene_data = fbx_data_from_scene(scene, settings) + scene_data = fbx_data_from_scene(scene, depsgraph, settings) root = elem_empty(None, b"") # Root element has no id, as it is not saved per se! @@ -3058,7 +3008,6 @@ def save_single(operator, scene, filepath="", def defaults_unity3d(): return { # These options seem to produce the same result as the old Ascii exporter in Unity3D: - "version": 'BIN7400', "axis_up": 'Y', "axis_forward": '-Z', "global_matrix": Matrix.Rotation(-math.pi / 2.0, 4, 'X'), @@ -3097,18 +3046,19 @@ def defaults_unity3d(): def save(operator, context, filepath="", use_selection=False, + use_active_collection=False, batch_mode='OFF', use_batch_own_dir=False, **kwargs ): """ - This is a wrapper around save_single, which handles multi-scenes (or groups) cases, when batch-exporting a whole - .blend file. + This is a wrapper around save_single, which handles multi-scenes (or collections) cases, when batch-exporting + a whole .blend file. """ - ret = None + ret = {'FINISHED'} - active_object = context.scene.objects.active + active_object = context.view_layer.objects.active org_mode = None if active_object and active_object.mode != 'OBJECT' and bpy.ops.object.mode_set.poll(): @@ -3117,37 +3067,59 @@ def save(operator, context, if batch_mode == 'OFF': kwargs_mod = kwargs.copy() - if use_selection: - kwargs_mod["context_objects"] = context.selected_objects + if use_active_collection: + if use_selection: + ctx_objects = tuple(obj + for obj in context.view_layer.active_layer_collection.collection.all_objects + if obj.select_get()) + else: + ctx_objects = context.view_layer.active_layer_collection.collection.all_objects else: - kwargs_mod["context_objects"] = context.scene.objects + if use_selection: + ctx_objects = context.selected_objects + else: + ctx_objects = context.view_layer.objects + kwargs_mod["context_objects"] = ctx_objects - ret = save_single(operator, context.scene, filepath, **kwargs_mod) + ret = save_single(operator, context.scene, context.depsgraph, filepath, **kwargs_mod) else: + # XXX We need a way to generate a depsgraph for inactive view_layers first... + # XXX Also, what to do in case of batch-exporting scenes, when there is more than one view layer? + # Scenes have no concept of 'active' view layer, that's on window level... fbxpath = filepath prefix = os.path.basename(fbxpath) if prefix: fbxpath = os.path.dirname(fbxpath) - if batch_mode == 'GROUP': - data_seq = tuple(grp for grp in bpy.data.groups if grp.objects) + if batch_mode == 'COLLECTION': + data_seq = tuple((coll, coll.name, 'objects') for coll in bpy.data.collections if coll.objects) + elif batch_mode in {'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}: + scenes = [context.scene] if batch_mode == 'ACTIVE_SCENE_COLLECTION' else bpy.data.scenes + data_seq = [] + for scene in scenes: + if not scene.objects: + continue + # Needed to avoid having tens of 'Master Collection' entries. + todo_collections = [(scene.collection, "_".join((scene.name, scene.collection.name)))] + while todo_collections: + coll, coll_name = todo_collections.pop() + todo_collections.extend(((c, c.name) for c in coll.children if c.all_objects)) + data_seq.append((coll, coll_name, 'all_objects')) else: - data_seq = bpy.data.scenes + data_seq = tuple((scene, scene.name, 'objects') for scene in bpy.data.scenes if scene.objects) # call this function within a loop with BATCH_ENABLE == False - # no scene switching done at the moment. - # orig_sce = context.scene new_fbxpath = fbxpath # own dir option modifies, we need to keep an original - for data in data_seq: # scene or group - newname = "_".join((prefix, bpy.path.clean_name(data.name))) if prefix else bpy.path.clean_name(data.name) + for data, data_name, data_obj_propname in data_seq: # scene or collection + newname = "_".join((prefix, bpy.path.clean_name(data_name))) if prefix else bpy.path.clean_name(data_name) if use_batch_own_dir: new_fbxpath = os.path.join(fbxpath, newname) - # path may already exist - # TODO - might exist but be a file. unlikely but should probably account for it. - + # path may already exist... and be a file. + while os.path.isfile(new_fbxpath): + new_fbxpath = "_".join((new_fbxpath, "dir")) if not os.path.exists(new_fbxpath): os.makedirs(new_fbxpath) @@ -3155,18 +3127,14 @@ def save(operator, context, print('\nBatch exporting %s as...\n\t%r' % (data, filepath)) - if batch_mode == 'GROUP': # group - # group, so objects update properly, add a dummy scene. + if batch_mode in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}: + # Collection, so that objects update properly, add a dummy scene. scene = bpy.data.scenes.new(name="FBX_Temp") - scene.layers = [True] * 20 - # bpy.data.scenes.active = scene # XXX, cant switch src_scenes = {} # Count how much each 'source' scenes are used. - for ob_base in data.objects: - for src_sce in ob_base.users_scene: - if src_sce not in src_scenes: - src_scenes[src_sce] = 0 - src_scenes[src_sce] += 1 - scene.objects.link(ob_base) + for obj in getattr(data, data_obj_propname): + for src_sce in obj.users_scene: + src_scenes[src_sce] = src_scenes.setdefault(src_sce, 0) + 1 + scene.collection.objects.link(obj) # Find the 'most used' source scene, and use its unit settings. This is somewhat weak, but should work # fine in most cases, and avoids stupid issues like T41931. @@ -3186,20 +3154,17 @@ def save(operator, context, scene = data kwargs_batch = kwargs.copy() - kwargs_batch["context_objects"] = data.objects + kwargs_batch["context_objects"] = getattr(data, data_obj_propname) - save_single(operator, scene, filepath, **kwargs_batch) + save_single(operator, scene, scene.view_layers[0].depsgraph, filepath, **kwargs_batch) - if batch_mode == 'GROUP': - # remove temp group scene + if batch_mode in {'COLLECTION', 'SCENE_COLLECTION', 'ACTIVE_SCENE_COLLECTION'}: + # Remove temp collection scene. bpy.data.scenes.remove(scene) - # no active scene changing! - # bpy.data.scenes.active = orig_sce - - ret = {'FINISHED'} # so the script wont run after we have batch exported. - - if active_object and org_mode and bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode=org_mode) + if active_object and org_mode: + context.view_layer.objects.active = active_object + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode=org_mode) return ret diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py index 0ed26b6a..19f32800 100644 --- a/io_scene_fbx/fbx_utils.py +++ b/io_scene_fbx/fbx_utils.py @@ -24,13 +24,13 @@ import math import time -from collections import namedtuple, OrderedDict +from collections import namedtuple from collections.abc import Iterable from itertools import zip_longest, chain import bpy import bpy_extras -from bpy.types import Object, Bone, PoseBone, DupliObject +from bpy.types import Object, Bone, PoseBone, DepsgraphObjectInstance from mathutils import Vector, Matrix from . import encode_bin, data_types @@ -71,7 +71,7 @@ FBX_ANIM_PROPSGROUP_NAME = "d" FBX_KTIME = 46186158000 # This is the number of "ktimes" in one second (yep, precision over the nanosecond...) -MAT_CONVERT_LAMP = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y. +MAT_CONVERT_LIGHT = Matrix.Rotation(math.pi / 2.0, 4, 'X') # Blender is -Z, FBX is -Y. MAT_CONVERT_CAMERA = Matrix.Rotation(math.pi / 2.0, 4, 'Y') # Blender is -Z, FBX is +X. # XXX I can't get this working :( # MAT_CONVERT_BONE = Matrix.Rotation(math.pi / 2.0, 4, 'Z') # Blender is +Y, FBX is -X. @@ -271,14 +271,14 @@ def similar_values_iter(v1, v2, e=1e-6): def vcos_transformed_gen(raw_cos, m=None): # Note: we could most likely get much better performances with numpy, but will leave this as TODO for now. gen = zip(*(iter(raw_cos),) * 3) - return gen if m is None else (m * Vector(v) for v in gen) + return gen if m is None else (m @ Vector(v) for v in gen) def nors_transformed_gen(raw_nors, m=None): # Great, now normals are also expected 4D! # XXX Back to 3D normals for now! # gen = zip(*(iter(raw_nors),) * 3 + (_infinite_gen(1.0),)) gen = zip(*(iter(raw_nors),) * 3) - return gen if m is None else (m * Vector(v) for v in gen) + return gen if m is None else (m @ Vector(v) for v in gen) # ##### UIDs code. ##### @@ -324,7 +324,7 @@ def _key_to_uuid(uuids, key): def get_fbx_uuid_from_key(key): """ - Return an UUID for given key, which is assumed hasable. + Return an UUID for given key, which is assumed to be hashable. """ uuid = _keys_to_uuids.get(key, None) if uuid is None: @@ -431,6 +431,10 @@ def get_blender_anim_curve_key(scene, ref_id, obj_key, fbx_prop_name, fbx_prop_i fbx_prop_item_name, "AnimCurve")) +def get_blender_nodetexture_key(ma, socket_names): + return "|".join((get_blenderID_key(ma), *socket_names)) + + # ##### Element generators. ##### # Note: elem may be None, in this case the element is not added to any parent. @@ -614,12 +618,12 @@ def elem_props_template_init(templates, template_type): """ Init a writing template of given type, for *one* element's properties. """ - ret = OrderedDict() + ret = {} tmpl = templates.get(template_type) if tmpl is not None: written = tmpl.written[0] props = tmpl.properties - ret = OrderedDict((name, [val, ptype, anim, written]) for name, (val, ptype, anim) in props.items()) + ret = {name: [val, ptype, anim, written] for name, (val, ptype, anim) in props.items()} return ret @@ -671,14 +675,11 @@ def fbx_templates_generate(root, fbx_templates): # for Lights, Cameras, LibNodes, etc.). ref_templates = {(tmpl.type_name, tmpl.prop_type_name): tmpl for tmpl in fbx_templates.values()} - templates = OrderedDict() + templates = {} for type_name, prop_type_name, properties, nbr_users, _written in fbx_templates.values(): - tmpl = templates.get(type_name) - if tmpl is None: - templates[type_name] = [OrderedDict(((prop_type_name, (properties, nbr_users)),)), nbr_users] - else: - tmpl[0][prop_type_name] = (properties, nbr_users) - tmpl[1] += nbr_users + tmpl = templates.setdefault(type_name, [{}, 0]) + tmpl[0][prop_type_name] = (properties, nbr_users) + tmpl[1] += nbr_users for type_name, (subprops, nbr_users) in templates.items(): template = elem_data_single_string(root, b"ObjectType", type_name) @@ -844,7 +845,7 @@ class AnimationCurveNodeWrapper: for elem_key, fbx_group, fbx_gname, fbx_props in \ zip(self.elem_keys, self.fbx_group, self.fbx_gname, self.fbx_props): group_key = get_blender_anim_curve_node_key(scene, ref_id, elem_key, fbx_group) - group = OrderedDict() + group = {} for c, def_val, fbx_item in zip(curves, self.default_values, fbx_props): fbx_item = FBX_ANIM_PROPSGROUP_NAME + "|" + fbx_item curve_key = get_blender_anim_curve_key(scene, ref_id, elem_key, fbx_group, fbx_item) @@ -856,7 +857,7 @@ class AnimationCurveNodeWrapper: # ##### FBX objects generators. ##### -# FBX Model-like data (i.e. Blender objects, dupliobjects and bones) are wrapped in ObjectWrapper. +# FBX Model-like data (i.e. Blender objects, depsgraph instances and bones) are wrapped in ObjectWrapper. # This allows us to have a (nearly) same code FBX-wise for all those types. # The wrapper tries to stay as small as possible, by mostly using callbacks (property(get...)) # to actual Blender data it contains. @@ -870,9 +871,13 @@ class MetaObjectWrapper(type): dup_mat = None if isinstance(bdata, Object): key = get_blenderID_key(bdata) - elif isinstance(bdata, DupliObject): - key = "|".join((get_blenderID_key((bdata.id_data, bdata.object)), cls._get_dup_num_id(bdata))) - dup_mat = bdata.matrix.copy() + elif isinstance(bdata, DepsgraphObjectInstance): + if bdata.is_instance: + key = "|".join((get_blenderID_key((bdata.parent.original, bdata.instance_object.original)), + cls._get_dup_num_id(bdata))) + dup_mat = bdata.matrix_world.copy() + else: + key = get_blenderID_key(bdata.object.original) else: # isinstance(bdata, (Bone, PoseBone)): if isinstance(bdata, PoseBone): bdata = armature.data.bones[bdata.name] @@ -883,9 +888,9 @@ class MetaObjectWrapper(type): cache = cls._cache = {} instance = cache.get(key) if instance is not None: - # Duplis hack: since duplis are not persistent in Blender (we have to re-create them to get updated + # Duplis hack: since dupli instances are not persistent in Blender (we have to re-create them to get updated # info like matrix...), we *always* need to reset that matrix when calling ObjectWrapper() (all - # other data is supposed valid during whole cache live, so we can skip resetting it). + # other data is supposed valid during whole cache live span, so we can skip resetting it). instance._dupli_matrix = dup_mat return instance @@ -902,7 +907,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): This class provides a same common interface for all (FBX-wise) object-like elements: * Blender Object * Blender Bone and PoseBone - * Blender DupliObject + * Blender DepsgraphObjectInstance (for dulis). Note since a same Blender object might be 'mapped' to several FBX models (esp. with duplis), we need to use a key to identify each. """ @@ -918,24 +923,42 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): @staticmethod def _get_dup_num_id(bdata): - return ".".join(str(i) for i in bdata.persistent_id if i != 2147483647) + INVALID_IDS = {2147483647, 0} + pids = tuple(bdata.persistent_id) + idx_valid = 0 + prev_i = ... + for idx, i in enumerate(pids[::-1]): + if i not in INVALID_IDS or (idx == len(pids) and i == 0 and prev_i != 0): + idx_valid = len(pids) - idx + break + prev_i = i + return ".".join(str(i) for i in pids[:idx_valid]) def __init__(self, bdata, armature=None): """ - bdata might be an Object, DupliObject, Bone or PoseBone. + bdata might be an Object (deprecated), DepsgraphObjectInstance, Bone or PoseBone. If Bone or PoseBone, armature Object must be provided. """ - if isinstance(bdata, Object): + # Note: DepsgraphObjectInstance are purely runtime data, they become invalid as soon as we step to the next item! + # Hence we have to immediately copy *all* needed data... + if isinstance(bdata, Object): # DEPRECATED self._tag = 'OB' self.name = get_blenderID_name(bdata) self.bdata = bdata self._ref = None - elif isinstance(bdata, DupliObject): - self._tag = 'DP' - self.name = "|".join((get_blenderID_name((bdata.id_data, bdata.object)), - "Dupli", self._get_dup_num_id(bdata))) - self.bdata = bdata.object - self._ref = bdata.id_data + elif isinstance(bdata, DepsgraphObjectInstance): + if bdata.is_instance: + # Note that dupli instance matrix is set by meta-class initialization. + self._tag = 'DP' + self.name = "|".join((get_blenderID_name((bdata.parent.original, bdata.instance_object.original)), + "Dupli", self._get_dup_num_id(bdata))) + self.bdata = bdata.instance_object.original + self._ref = bdata.parent.original + else: + self._tag = 'OB' + self.name = get_blenderID_name(bdata) + self.bdata = bdata.object.original + self._ref = None else: # isinstance(bdata, (Bone, PoseBone)): if isinstance(bdata, PoseBone): bdata = armature.data.bones[bdata.name] @@ -951,13 +974,17 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): def __hash__(self): return hash(self.key) + def __repr__(self): + return self.key + # #### Common to all _tag values. def get_fbx_uuid(self): return get_fbx_uuid_from_key(self.key) fbx_uuid = property(get_fbx_uuid) + # XXX Not sure how much that’s useful now... :/ def get_hide(self): - return self.bdata.hide + return self.bdata.hide_viewport if self._tag in {'OB', 'DP'} else self.bdata.hide hide = property(get_hide) def get_parent(self): @@ -974,7 +1001,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): # Mere object parenting. return ObjectWrapper(self.bdata.parent) elif self._tag == 'DP': - return ObjectWrapper(self.bdata.parent or self._ref) + return ObjectWrapper(self._ref) else: # self._tag == 'BO' return ObjectWrapper(self.bdata.parent, self._ref) or ObjectWrapper(self._ref) parent = property(get_parent) @@ -983,12 +1010,12 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): if self._tag == 'OB': return self.bdata.matrix_local.copy() elif self._tag == 'DP': - return self._ref.matrix_world.inverted_safe() * self._dupli_matrix + return self._ref.matrix_world.inverted_safe() @ self._dupli_matrix else: # 'BO', current pose # PoseBone.matrix is in armature space, bring in back in real local one! par = self.bdata.parent par_mat_inv = self._ref.pose.bones[par.name].matrix.inverted_safe() if par else Matrix() - return par_mat_inv * self._ref.pose.bones[self.bdata.name].matrix + return par_mat_inv @ self._ref.pose.bones[self.bdata.name].matrix matrix_local = property(get_matrix_local) def get_matrix_global(self): @@ -997,7 +1024,7 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): elif self._tag == 'DP': return self._dupli_matrix else: # 'BO', current pose - return self._ref.matrix_world * self._ref.pose.bones[self.bdata.name].matrix + return self._ref.matrix_world @ self._ref.pose.bones[self.bdata.name].matrix matrix_global = property(get_matrix_global) def get_matrix_rest_local(self): @@ -1005,14 +1032,14 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): # Bone.matrix_local is in armature space, bring in back in real local one! par = self.bdata.parent par_mat_inv = par.matrix_local.inverted_safe() if par else Matrix() - return par_mat_inv * self.bdata.matrix_local + return par_mat_inv @ self.bdata.matrix_local else: return self.matrix_local.copy() matrix_rest_local = property(get_matrix_rest_local) def get_matrix_rest_global(self): if self._tag == 'BO': - return self._ref.matrix_world * self.bdata.matrix_local + return self._ref.matrix_world @ self.bdata.matrix_local else: return self.matrix_global.copy() matrix_rest_global = property(get_matrix_rest_global) @@ -1065,41 +1092,41 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): if self._tag == 'BO': # If we have a bone parent we need to undo the parent correction. if not is_global and scene_data.settings.bone_correction_matrix_inv and parent and parent.is_bone: - matrix = scene_data.settings.bone_correction_matrix_inv * matrix + matrix = scene_data.settings.bone_correction_matrix_inv @ matrix # Apply the bone correction. if scene_data.settings.bone_correction_matrix: - matrix = matrix * scene_data.settings.bone_correction_matrix - elif self.bdata.type == 'LAMP': - matrix = matrix * MAT_CONVERT_LAMP + matrix = matrix @ scene_data.settings.bone_correction_matrix + elif self.bdata.type == 'LIGHT': + matrix = matrix @ MAT_CONVERT_LIGHT elif self.bdata.type == 'CAMERA': - matrix = matrix * MAT_CONVERT_CAMERA + matrix = matrix @ MAT_CONVERT_CAMERA if self._tag in {'DP', 'OB'} and parent: if parent._tag == 'BO': # In bone parent case, we get transformation in **bone tip** space (sigh). # Have to bring it back into bone root, which is FBX expected value. - matrix = Matrix.Translation((0, (parent.bdata.tail - parent.bdata.head).length, 0)) * matrix + matrix = Matrix.Translation((0, (parent.bdata.tail - parent.bdata.head).length, 0)) @ matrix # Our matrix is in local space, time to bring it in its final desired space. if parent: if is_global: # Move matrix to global Blender space. - matrix = (parent.matrix_rest_global if rest else parent.matrix_global) * matrix + matrix = (parent.matrix_rest_global if rest else parent.matrix_global) @ matrix elif parent.use_bake_space_transform(scene_data): # Blender's and FBX's local space of parent may differ if we use bake_space_transform... # Apply parent's *Blender* local space... - matrix = (parent.matrix_rest_local if rest else parent.matrix_local) * matrix + matrix = (parent.matrix_rest_local if rest else parent.matrix_local) @ matrix # ...and move it back into parent's *FBX* local space. par_mat = parent.fbx_object_matrix(scene_data, rest=rest, local_space=True) - matrix = par_mat.inverted_safe() * matrix + matrix = par_mat.inverted_safe() @ matrix if self.use_bake_space_transform(scene_data): # If we bake the transforms we need to post-multiply inverse global transform. # This means that the global transform will not apply to children of this transform. - matrix = matrix * scene_data.settings.global_matrix_inv + matrix = matrix @ scene_data.settings.global_matrix_inv if is_global: # In any case, pre-multiply the global matrix to get it in FBX global space! - matrix = scene_data.settings.global_matrix * matrix + matrix = scene_data.settings.global_matrix @ matrix return matrix @@ -1164,19 +1191,11 @@ class ObjectWrapper(metaclass=MetaObjectWrapper): return True # #### Duplis... - def dupli_list_create(self, scene, settings='PREVIEW'): - if self._tag == 'OB' and self.bdata.is_duplicator: - self.bdata.dupli_list_create(scene, settings) - - def dupli_list_clear(self): - if self._tag == 'OB'and self.bdata.is_duplicator: - self.bdata.dupli_list_clear() - - def get_dupli_list(self): - if self._tag == 'OB'and self.bdata.is_duplicator: - return (ObjectWrapper(dup) for dup in self.bdata.dupli_list) + def dupli_list_gen(self, depsgraph): + if self._tag == 'OB' and self.bdata.is_instancer: + return (ObjectWrapper(dup) for dup in depsgraph.object_instances + if dup.parent and ObjectWrapper(dup.parent.original) == self) return () - dupli_list = property(get_dupli_list) def fbx_name_class(name, cls): @@ -1213,8 +1232,8 @@ FBXExportSettings = namedtuple("FBXExportSettings", ( # * animations. FBXExportData = namedtuple("FBXExportData", ( "templates", "templates_users", "connections", - "settings", "scene", "objects", "animations", "animated", "frame_start", "frame_end", - "data_empties", "data_lamps", "data_cameras", "data_meshes", "mesh_mat_indices", + "settings", "scene", "depsgraph", "objects", "animations", "animated", "frame_start", "frame_end", + "data_empties", "data_lights", "data_cameras", "data_meshes", "mesh_material_indices", "data_bones", "data_leaf_bones", "data_deformers_skin", "data_deformers_shape", "data_world", "data_materials", "data_textures", "data_videos", )) @@ -1223,11 +1242,11 @@ FBXExportData = namedtuple("FBXExportData", ( FBXImportSettings = namedtuple("FBXImportSettings", ( "report", "to_axes", "global_matrix", "global_scale", "bake_space_transform", "global_matrix_inv", "global_matrix_inv_transposed", - "use_custom_normals", "use_cycles", "use_image_search", + "use_custom_normals", "use_image_search", "use_alpha_decals", "decal_offset", "use_anim", "anim_offset", "use_custom_props", "use_custom_props_enum_as_string", - "cycles_material_wrap_map", "image_cache", + "nodal_material_wrap_map", "image_cache", "ignore_leaf_bones", "force_connect_children", "automatic_bone_orientation", "bone_correction_matrix", "use_prepost_rot", )) diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index addb8ae6..f35a6387 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -60,7 +60,7 @@ fbx_elem_nil = None convert_deg_to_rad_iter = units_convertor_iter("degree", "radian") MAT_CONVERT_BONE = fbx_utils.MAT_CONVERT_BONE.inverted() -MAT_CONVERT_LAMP = fbx_utils.MAT_CONVERT_LAMP.inverted() +MAT_CONVERT_LIGHT = fbx_utils.MAT_CONVERT_LIGHT.inverted() MAT_CONVERT_CAMERA = fbx_utils.MAT_CONVERT_CAMERA.inverted() @@ -369,7 +369,7 @@ def blen_read_custom_properties(fbx_obj, blen_obj, settings): def blen_read_object_transform_do(transform_data): # This is a nightmare. FBX SDK uses Maya way to compute the transformation matrix of a node - utterly simple: # - # WorldTransform = ParentWorldTransform * T * Roff * Rp * Rpre * R * Rpost * Rp-1 * Soff * Sp * S * Sp-1 + # WorldTransform = ParentWorldTransform @ T @ Roff @ Rp @ Rpre @ R @ Rpost @ Rp-1 @ Soff @ Sp @ S @ Sp-1 # # Where all those terms are 4 x 4 matrices that contain: # WorldTransform: Transformation matrix of the node in global space. @@ -389,7 +389,7 @@ def blen_read_object_transform_do(transform_data): # But it was still too simple, and FBX notion of compatibility is... quite specific. So we also have to # support 3DSMax way: # - # WorldTransform = ParentWorldTransform * T * R * S * OT * OR * OS + # WorldTransform = ParentWorldTransform @ T @ R @ S @ OT @ OR @ OS # # Where all those terms are 4 x 4 matrices that contain: # WorldTransform: Transformation matrix of the node in global space @@ -414,7 +414,7 @@ def blen_read_object_transform_do(transform_data): # rotation to_rot = lambda rot, rot_ord: Euler(convert_deg_to_rad_iter(rot), rot_ord).to_matrix().to_4x4() - lcl_rot = to_rot(transform_data.rot, transform_data.rot_ord) * transform_data.rot_alt_mat + lcl_rot = to_rot(transform_data.rot, transform_data.rot_ord) @ transform_data.rot_alt_mat pre_rot = to_rot(transform_data.pre_rot, transform_data.rot_ord) pst_rot = to_rot(transform_data.pst_rot, transform_data.rot_ord) geom_rot = to_rot(transform_data.geom_rot, transform_data.rot_ord) @@ -431,21 +431,21 @@ def blen_read_object_transform_do(transform_data): geom_scale[0][0], geom_scale[1][1], geom_scale[2][2] = transform_data.geom_sca base_mat = ( - lcl_translation * - rot_ofs * - rot_piv * - pre_rot * - lcl_rot * - pst_rot * - rot_piv.inverted_safe() * - sca_ofs * - sca_piv * - lcl_scale * + lcl_translation @ + rot_ofs @ + rot_piv @ + pre_rot @ + lcl_rot @ + pst_rot @ + rot_piv.inverted_safe() @ + sca_ofs @ + sca_piv @ + lcl_scale @ sca_piv.inverted_safe() ) - geom_mat = geom_loc * geom_rot * geom_scale + geom_mat = geom_loc @ geom_rot @ geom_scale # We return mat without 'geometric transforms' too, because it is to be used for children, sigh... - return (base_mat * geom_mat, base_mat, geom_mat) + return (base_mat @ geom_mat, base_mat, geom_mat) # XXX This might be weak, now that we can add vgroups from both bones and shapes, name collisions become @@ -457,7 +457,7 @@ def add_vgroup_to_objects(vg_indices, vg_weights, vg_name, objects): # We replace/override here... vg = obj.vertex_groups.get(vg_name) if vg is None: - vg = obj.vertex_groups.new(vg_name) + vg = obj.vertex_groups.new(name=vg_name) for i, w in zip(vg_indices, vg_weights): vg.add((i,), w, 'REPLACE') @@ -601,7 +601,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): else: # Euler props[1] = (bl_obj.path_from_id("rotation_euler"), 3, grpname or "Euler Rotation") - blen_curves = [action.fcurves.new(prop, channel, grpname) + blen_curves = [action.fcurves.new(prop, index=channel, action_group=grpname) for prop, nbr_channels, grpname in props for channel in range(nbr_channels)] if isinstance(item, Material): @@ -613,7 +613,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): value[channel] = v for fc, v in zip(blen_curves, value): - fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR' + fc.keyframe_points.insert(frame, v, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR' elif isinstance(item, ShapeKey): for frame, values in blen_read_animations_curves_iter(fbx_curves, anim_offset, 0, fps): @@ -624,7 +624,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): value = v / 100.0 for fc, v in zip(blen_curves, (value,)): - fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR' + fc.keyframe_points.insert(frame, v, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR' elif isinstance(item, Camera): for frame, values in blen_read_animations_curves_iter(fbx_curves, anim_offset, 0, fps): @@ -635,7 +635,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): value = v for fc, v in zip(blen_curves, (value,)): - fc.keyframe_points.insert(frame, v, {'NEEDED', 'FAST'}).interpolation = 'LINEAR' + fc.keyframe_points.insert(frame, v, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR' else: # Object or PoseBone: if item.is_bone: @@ -661,19 +661,19 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): # compensate for changes in the local matrix during processing if item.anim_compensation_matrix: - mat = mat * item.anim_compensation_matrix + mat = mat @ item.anim_compensation_matrix # apply pre- and post matrix # post-matrix will contain any correction for lights, camera and bone orientation # pre-matrix will contain any correction for a parent's correction matrix or the global matrix if item.pre_matrix: - mat = item.pre_matrix * mat + mat = item.pre_matrix @ mat if item.post_matrix: - mat = mat * item.post_matrix + mat = mat @ item.post_matrix # And now, remove that rest pose matrix from current mat (also in parent space). if restmat_inv: - mat = restmat_inv * mat + mat = restmat_inv @ mat # Now we have a virtual matrix of transform from AnimCurves, we can insert keyframes! loc, rot, sca = mat.decompose() @@ -686,7 +686,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset): rot = rot.to_euler(rot_mode, rot_prev) rot_prev = rot for fc, value in zip(blen_curves, chain(loc, rot, sca)): - fc.keyframe_points.insert(frame, value, {'NEEDED', 'FAST'}).interpolation = 'LINEAR' + fc.keyframe_points.insert(frame, value, options={'NEEDED', 'FAST'}).interpolation = 'LINEAR' # Since we inserted our keyframes in 'FAST' mode, we have to update the fcurves now. for fc in blen_curves: @@ -1006,8 +1006,7 @@ def blen_read_geom_layer_uv(fbx_obj, mesh): fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, b'UV')) fbx_layer_index = elem_prop_first(elem_find_first(fbx_layer, b'UVIndex')) - uv_tex = mesh.uv_textures.new(name=fbx_layer_name) - uv_lay = mesh.uv_layers[-1] + uv_lay = mesh.uv_layers.new(name=fbx_layer_name) blen_data = uv_lay.data # some valid files omit this data @@ -1087,7 +1086,6 @@ def blen_read_geom_layer_smooth(fbx_obj, mesh): ) # We only set sharp edges here, not face smoothing itself... mesh.use_auto_smooth = True - mesh.show_edge_sharp = True return False elif fbx_layer_mapping == b'ByPolygon': blen_data = mesh.polygons @@ -1165,7 +1163,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings): if geom_mat_co is not None: def _vcos_transformed_gen(raw_cos, m=None): # Note: we could most likely get much better performances with numpy, but will leave this as TODO for now. - return chain(*(m * Vector(v) for v in zip(*(iter(raw_cos),) * 3))) + return chain(*(m @ Vector(v) for v in zip(*(iter(raw_cos),) * 3))) fbx_verts = array.array(fbx_verts.typecode, _vcos_transformed_gen(fbx_verts, geom_mat_co)) if fbx_verts is None: @@ -1242,7 +1240,7 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings): ok_normals = blen_read_geom_layer_normal(fbx_obj, mesh) else: def nortrans(v): - return geom_mat_no * Vector(v) + return geom_mat_no @ Vector(v) ok_normals = blen_read_geom_layer_normal(fbx_obj, mesh, nortrans) mesh.validate(clean_customdata=False) # *Very* important to not remove lnors here! @@ -1257,7 +1255,6 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings): mesh.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3))) mesh.use_auto_smooth = True - mesh.show_edge_sharp = True else: mesh.calc_normals() @@ -1319,9 +1316,12 @@ def blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene): # Material def blen_read_material(fbx_tmpl, fbx_obj, settings): + from bpy_extras import node_shader_utils + from math import sqrt + elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Material') - cycles_material_wrap_map = settings.cycles_material_wrap_map + nodal_material_wrap_map = settings.nodal_material_wrap_map ma = bpy.data.materials.new(name=elem_name_utf8) const_color_white = 1.0, 1.0, 1.0 @@ -1329,43 +1329,23 @@ def blen_read_material(fbx_tmpl, fbx_obj, settings): fbx_props = (elem_find_first(fbx_obj, b'Properties70'), elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil)) - ma_diff = elem_props_get_color_rgb(fbx_props, b'DiffuseColor', const_color_white) - ma_spec = elem_props_get_color_rgb(fbx_props, b'SpecularColor', const_color_white) - ma_alpha = elem_props_get_number(fbx_props, b'Opacity', 1.0) - ma_spec_intensity = ma.specular_intensity = elem_props_get_number(fbx_props, b'SpecularFactor', 0.25) * 2.0 - ma_spec_hardness = elem_props_get_number(fbx_props, b'Shininess', 9.6) - ma_refl_factor = elem_props_get_number(fbx_props, b'ReflectionFactor', 0.0) - ma_refl_color = elem_props_get_color_rgb(fbx_props, b'ReflectionColor', const_color_white) - - if settings.use_cycles: - from modules import cycles_shader_compat - # viewport color - ma.diffuse_color = ma_diff - - ma_wrap = cycles_shader_compat.CyclesShaderWrapper(ma) - ma_wrap.diffuse_color_set(ma_diff) - ma_wrap.specular_color_set([c * ma_spec_intensity for c in ma_spec]) - ma_wrap.hardness_value_set(((ma_spec_hardness + 3.0) / 5.0) - 0.65) - ma_wrap.alpha_value_set(ma_alpha) - ma_wrap.reflect_factor_set(ma_refl_factor) - ma_wrap.reflect_color_set(ma_refl_color) - - cycles_material_wrap_map[ma] = ma_wrap - else: - # TODO, number BumpFactor isnt used yet - ma.diffuse_color = ma_diff - ma.specular_color = ma_spec - ma.alpha = ma_alpha - if ma_alpha < 1.0: - ma.use_transparency = True - ma.transparency_method = 'RAYTRACE' - ma.specular_intensity = ma_spec_intensity - ma.specular_hardness = ma_spec_hardness * 5.10 + 1.0 - - if ma_refl_factor != 0.0: - ma.raytrace_mirror.use = True - ma.raytrace_mirror.reflect_factor = ma_refl_factor - ma.mirror_color = ma_refl_color + ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=False, use_nodes=True) + ma_wrap.base_color = elem_props_get_color_rgb(fbx_props, b'DiffuseColor', const_color_white) + # No specular color in Principled BSDF shader, assumed to be either white or take some tint from diffuse one... + # TODO: add way to handle tint option (guesstimate from spec color + intensity...)? + ma_wrap.specular = elem_props_get_number(fbx_props, b'SpecularFactor', 0.25) * 2.0 + # XXX Totally empirical conversion, trying to adapt it + # (from 1.0 - 0.0 Principled BSDF range to 0.0 - 100.0 FBX shininess range)... + fbx_shininess = elem_props_get_number(fbx_props, b'Shininess', 20.0) + ma_wrap.roughness = 1.0 - (sqrt(fbx_shininess) / 10.0) + ma_wrap.transmission = 1.0 - elem_props_get_number(fbx_props, b'Opacity', 1.0) + ma_wrap.metallic = elem_props_get_number(fbx_props, b'ReflectionFactor', 0.0) + # We have no metallic (a.k.a. reflection) color... + # elem_props_get_color_rgb(fbx_props, b'ReflectionColor', const_color_white) + # (x / 7.142) is only a guess, cycles usable range is (0.0 -> 0.5) + ma_wrap.normalmap_strength = elem_props_get_number(fbx_props, b'BumpFactor', 2.5) / 7.142 + + nodal_material_wrap_map[ma] = ma_wrap if settings.use_custom_props: blen_read_custom_properties(fbx_obj, ma, settings) @@ -1479,7 +1459,7 @@ def blen_read_light(fbx_tmpl, fbx_obj, global_scale): 1: 'SUN', 2: 'SPOT'}.get(elem_props_get_enum(fbx_props, b'LightType', 0), 'POINT') - lamp = bpy.data.lamps.new(name=elem_name_utf8, type=light_type) + lamp = bpy.data.lights.new(name=elem_name_utf8, type=light_type) if light_type == 'SPOT': spot_size = elem_props_get_number(fbx_props, b'OuterAngle', None) @@ -1494,11 +1474,14 @@ def blen_read_light(fbx_tmpl, fbx_obj, global_scale): spot_blend = elem_props_get_number(fbx_props, b'HotSpot', 45.0) lamp.spot_blend = 1.0 - (spot_blend / spot_size) - # TODO, cycles + # TODO, cycles nodes??? lamp.color = elem_props_get_color_rgb(fbx_props, b'Color', (1.0, 1.0, 1.0)) lamp.energy = elem_props_get_number(fbx_props, b'Intensity', 100.0) / 100.0 lamp.distance = elem_props_get_number(fbx_props, b'DecayStart', 25.0) * global_scale - lamp.shadow_method = ('RAY_SHADOW' if elem_props_get_bool(fbx_props, b'CastShadow', True) else 'NOSHADOW') + lamp.use_shadow = elem_props_get_bool(fbx_props, b'CastShadow', True) + if hasattr(lamp, "cycles"): + lamp.cycles.cast_shadow = lamp.use_shadow + # Keeping this for now, but this is not used nor exposed anymore afaik... lamp.shadow_color = elem_props_get_color_rgb(fbx_props, b'ShadowColor', (0.0, 0.0, 0.0)) return lamp @@ -1612,7 +1595,7 @@ class FbxImportHelperNode: self.pre_matrix = settings.global_matrix if parent_correction_inv: - self.pre_matrix = parent_correction_inv * (self.pre_matrix if self.pre_matrix else Matrix()) + self.pre_matrix = parent_correction_inv @ (self.pre_matrix if self.pre_matrix else Matrix()) correction_matrix = None @@ -1700,12 +1683,12 @@ class FbxImportHelperNode: if self.fbx_type == b'Camera': correction_matrix = MAT_CONVERT_CAMERA elif self.fbx_type == b'Light': - correction_matrix = MAT_CONVERT_LAMP + correction_matrix = MAT_CONVERT_LIGHT self.post_matrix = correction_matrix if self.do_bake_transform(settings): - self.post_matrix = settings.global_matrix_inv * (self.post_matrix if self.post_matrix else Matrix()) + self.post_matrix = settings.global_matrix_inv @ (self.post_matrix if self.post_matrix else Matrix()) # process children correction_matrix_inv = correction_matrix.inverted_safe() if correction_matrix else None @@ -1782,29 +1765,29 @@ class FbxImportHelperNode: def get_world_matrix_as_parent(self): matrix = self.parent.get_world_matrix_as_parent() if self.parent else Matrix() if self.matrix_as_parent: - matrix = matrix * self.matrix_as_parent + matrix = matrix @ self.matrix_as_parent return matrix def get_world_matrix(self): matrix = self.parent.get_world_matrix_as_parent() if self.parent else Matrix() if self.matrix: - matrix = matrix * self.matrix + matrix = matrix @ self.matrix return matrix def get_matrix(self): matrix = self.matrix if self.matrix else Matrix() if self.pre_matrix: - matrix = self.pre_matrix * matrix + matrix = self.pre_matrix @ matrix if self.post_matrix: - matrix = matrix * self.post_matrix + matrix = matrix @ self.post_matrix return matrix def get_bind_matrix(self): matrix = self.bind_matrix if self.bind_matrix else Matrix() if self.pre_matrix: - matrix = self.pre_matrix * matrix + matrix = self.pre_matrix @ matrix if self.post_matrix: - matrix = matrix * self.post_matrix + matrix = matrix @ self.post_matrix return matrix def make_bind_pose_local(self, parent_matrix=None): @@ -1812,13 +1795,13 @@ class FbxImportHelperNode: parent_matrix = Matrix() if self.bind_matrix: - bind_matrix = parent_matrix.inverted_safe() * self.bind_matrix + bind_matrix = parent_matrix.inverted_safe() @ self.bind_matrix else: bind_matrix = self.matrix.copy() if self.matrix else None self.bind_matrix = bind_matrix if bind_matrix: - parent_matrix = parent_matrix * bind_matrix + parent_matrix = parent_matrix @ bind_matrix for child in self.children: child.make_bind_pose_local(parent_matrix) @@ -1838,8 +1821,8 @@ class FbxImportHelperNode: child.collect_skeleton_meshes(meshes) for m in meshes: old_matrix = m.matrix - m.matrix = armature_matrix_inv * m.get_world_matrix() - m.anim_compensation_matrix = old_matrix.inverted_safe() * m.matrix + m.matrix = armature_matrix_inv @ m.get_world_matrix() + m.anim_compensation_matrix = old_matrix.inverted_safe() @ m.matrix m.is_global_animation = True m.parent = self self.meshes = meshes @@ -1914,7 +1897,7 @@ class FbxImportHelperNode: bone.tail = bone_tail # And rotate/move it to its final "rest pose". - bone_matrix = parent_matrix * self.get_bind_matrix().normalized() + bone_matrix = parent_matrix @ self.get_bind_matrix().normalized() bone.matrix = bone_matrix @@ -1927,7 +1910,7 @@ class FbxImportHelperNode: if child.is_leaf and force_connect_children: # Arggggggggggggggggg! We do not want to create this bone, but we need its 'virtual head' location # to orient current one!!! - child_head = (bone_matrix * child.get_bind_matrix().normalized()).translation + child_head = (bone_matrix @ child.get_bind_matrix().normalized()).translation child_connect(bone, None, child_head, connect_ctx) elif child.is_bone and not child.ignore: child_bone = child.build_skeleton(arm, bone_matrix, bone_size, @@ -1958,7 +1941,7 @@ class FbxImportHelperNode: # Misc Attributes obj.color[0:3] = elem_props_get_color_rgb(fbx_props, b'Color', (0.8, 0.8, 0.8)) - obj.hide = not bool(elem_props_get_visibility(fbx_props, b'Visibility', 1.0)) + obj.hide_viewport = not bool(elem_props_get_visibility(fbx_props, b'Visibility', 1.0)) obj.matrix_basis = self.get_matrix() @@ -1967,12 +1950,12 @@ class FbxImportHelperNode: return obj - def build_skeleton_children(self, fbx_tmpl, settings, scene): + def build_skeleton_children(self, fbx_tmpl, settings, scene, view_layer): if self.is_bone: for child in self.children: if child.ignore: continue - child.build_skeleton_children(fbx_tmpl, settings, scene) + child.build_skeleton_children(fbx_tmpl, settings, scene, view_layer) return None else: # child is not a bone @@ -1984,11 +1967,11 @@ class FbxImportHelperNode: for child in self.children: if child.ignore: continue - child.build_skeleton_children(fbx_tmpl, settings, scene) + child.build_skeleton_children(fbx_tmpl, settings, scene, view_layer) # instance in scene - obj_base = scene.objects.link(obj) - obj_base.select = True + view_layer.active_layer_collection.collection.objects.link(obj) + obj.select_set(True) return obj @@ -2007,7 +1990,7 @@ class FbxImportHelperNode: # Blender attaches to the end of a bone, while FBX attaches to the start. # bone_child_matrix corrects for that. if child.pre_matrix: - child.pre_matrix = self.bone_child_matrix * child.pre_matrix + child.pre_matrix = self.bone_child_matrix @ child.pre_matrix else: child.pre_matrix = self.bone_child_matrix @@ -2027,7 +2010,7 @@ class FbxImportHelperNode: def set_pose_matrix(self, arm): pose_bone = arm.bl_obj.pose.bones[self.bl_bone] - pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() * self.get_matrix() + pose_bone.matrix_basis = self.get_bind_matrix().inverted_safe() @ self.get_matrix() for child in self.children: if child.ignore: @@ -2094,7 +2077,7 @@ class FbxImportHelperNode: if child.is_bone and not child.ignore: child.set_bone_weights() - def build_hierarchy(self, fbx_tmpl, settings, scene): + def build_hierarchy(self, fbx_tmpl, settings, scene, view_layer): if self.is_armature: # create when linking since we need object data elem_name_utf8 = self.fbx_name @@ -2114,15 +2097,15 @@ class FbxImportHelperNode: blen_read_custom_properties(self.fbx_elem, arm, settings) # instance in scene - obj_base = scene.objects.link(arm) - obj_base.select = True + view_layer.active_layer_collection.collection.objects.link(arm) + arm.select_set(True) # Add bones: # Switch to Edit mode. - scene.objects.active = arm - is_hidden = arm.hide - arm.hide = False # Can't switch to Edit mode hidden objects... + view_layer.objects.active = arm + is_hidden = arm.hide_viewport + arm.hide_viewport = False # Can't switch to Edit mode hidden objects... bpy.ops.object.mode_set(mode='EDIT') for child in self.children: @@ -2133,7 +2116,7 @@ class FbxImportHelperNode: bpy.ops.object.mode_set(mode='OBJECT') - arm.hide = is_hidden + arm.hide_viewport = is_hidden # Set pose matrix for child in self.children: @@ -2146,7 +2129,7 @@ class FbxImportHelperNode: for child in self.children: if child.ignore: continue - child_obj = child.build_skeleton_children(fbx_tmpl, settings, scene) + child_obj = child.build_skeleton_children(fbx_tmpl, settings, scene, view_layer) return arm elif self.fbx_elem and not self.is_bone: @@ -2154,16 +2137,16 @@ class FbxImportHelperNode: # walk through children for child in self.children: - child.build_hierarchy(fbx_tmpl, settings, scene) + child.build_hierarchy(fbx_tmpl, settings, scene, view_layer) # instance in scene - obj_base = scene.objects.link(obj) - obj_base.select = True + view_layer.active_layer_collection.collection.objects.link(obj) + obj.select_set(True) return obj else: for child in self.children: - child.build_hierarchy(fbx_tmpl, settings, scene) + child.build_hierarchy(fbx_tmpl, settings, scene, view_layer) return None @@ -2192,16 +2175,16 @@ class FbxImportHelperNode: # which we obviously cannot do in Blender. :/ if amat is None: amat = self.bind_matrix - amat = settings.global_matrix * (Matrix() if amat is None else amat) + amat = settings.global_matrix @ (Matrix() if amat is None else amat) if self.matrix_geom: - amat = amat * self.matrix_geom - mmat = settings.global_matrix * mmat + amat = amat @ self.matrix_geom + mmat = settings.global_matrix @ mmat if mesh.matrix_geom: - mmat = mmat * mesh.matrix_geom + mmat = mmat @ mesh.matrix_geom # Now that we have armature and mesh in there (global) bind 'state' (matrix), # we can compute inverse parenting matrix of the mesh. - me_obj.matrix_parent_inverse = amat.inverted_safe() * mmat * me_obj.matrix_basis.inverted_safe() + me_obj.matrix_parent_inverse = amat.inverted_safe() @ mmat @ me_obj.matrix_basis.inverted_safe() mod = mesh.bl_obj.modifiers.new(arm.name, 'ARMATURE') mod.object = arm @@ -2249,7 +2232,6 @@ def load(operator, context, filepath="", global_scale=1.0, bake_space_transform=False, use_custom_normals=True, - use_cycles=True, use_image_search=False, use_alpha_decals=False, decal_offset=0.0, @@ -2311,10 +2293,8 @@ def load(operator, context, filepath="", basedir = os.path.dirname(filepath) - cycles_material_wrap_map = {} + nodal_material_wrap_map = {} image_cache = {} - if not use_cycles: - texture_cache = {} # Tables: (FBX_byte_id -> [FBX_data, None or Blender_datablock]) fbx_table_nodes = {} @@ -2325,6 +2305,7 @@ def load(operator, context, filepath="", material_decals = None scene = context.scene + view_layer = context.view_layer # #### Get some info from GlobalSettings. @@ -2350,7 +2331,7 @@ def load(operator, context, filepath="", elem_props_get_integer(fbx_settings_props, b'CoordAxisSign', 1)) axis_key = (axis_up, axis_forward, axis_coord) axis_up, axis_forward = {v: k for k, v in RIGHT_HAND_AXES.items()}.get(axis_key, ('Z', 'Y')) - global_matrix = (Matrix.Scale(global_scale, 4) * + global_matrix = (Matrix.Scale(global_scale, 4) @ axis_conversion(from_forward=axis_forward, from_up=axis_up).to_4x4()) # To cancel out unwanted rotation/scale on nodes. @@ -2381,11 +2362,11 @@ def load(operator, context, filepath="", settings = FBXImportSettings( operator.report, (axis_up, axis_forward), global_matrix, global_scale, bake_space_transform, global_matrix_inv, global_matrix_inv_transposed, - use_custom_normals, use_cycles, use_image_search, + use_custom_normals, use_image_search, use_alpha_decals, decal_offset, use_anim, anim_offset, use_custom_props, use_custom_props_enum_as_string, - cycles_material_wrap_map, image_cache, + nodal_material_wrap_map, image_cache, ignore_leaf_bones, force_connect_children, automatic_bone_orientation, bone_correction_matrix, use_prepost_rot, ) @@ -2682,7 +2663,7 @@ def load(operator, context, filepath="", armature_matrix = tx_arm if tx_bone: - mesh_matrix = tx_bone * mesh_matrix + mesh_matrix = tx_bone @ mesh_matrix helper_node.bind_matrix = tx_bone # overwrite the bind matrix # Get the meshes driven by this cluster: (Shouldn't that be only one?) @@ -2725,7 +2706,7 @@ def load(operator, context, filepath="", root_helper.find_correction_matrix(settings) # build the Object/Armature/Bone hierarchy - root_helper.build_hierarchy(fbx_tmpl, settings, scene) + root_helper.build_hierarchy(fbx_tmpl, settings, scene, view_layer) # Link the Object/Armature/Bone hierarchy root_helper.link_hierarchy(fbx_tmpl, settings, scene) @@ -2857,8 +2838,7 @@ def load(operator, context, filepath="", continue mat = fbx_item[1] items.append((mat, lnk_prop)) - if settings.use_cycles: - print("WARNING! Importing material's animation is not supported for Cycles materials...") + print("WARNING! Importing material's animation is not supported for Nodal materials...") for al_uuid, al_ctype in fbx_connection_map.get(acn_uuid, ()): if al_ctype.props[0] != b'OO': continue @@ -2919,17 +2899,17 @@ def load(operator, context, filepath="", # So we have to be careful not to re-add endlessly the same material to a mesh! # This can easily happen with 'baked' dupliobjects, see T44386. # TODO: add an option to link materials to objects in Blender instead? - done_mats = set() + done_materials = set() for (fbx_lnk, fbx_lnk_item, fbx_lnk_type) in connection_filter_forward(fbx_uuid, b'Model'): # link materials fbx_lnk_uuid = elem_uuid(fbx_lnk) for (fbx_lnk_material, material, fbx_lnk_material_type) in connection_filter_reverse(fbx_lnk_uuid, b'Material'): - if material not in done_mats: + if material not in done_materials: mesh.materials.append(material) - done_mats.add(material) + done_materials.add(material) - # We have to validate mesh polygons' mat_idx, see T41015! + # We have to validate mesh polygons' ma_idx, see T41015! # Some FBX seem to have an extra 'default' material which is not defined in FBX file. if mesh.validate_material_indices(): print("WARNING: mesh '%s' had invalid material indices, those were reset to first material" % mesh.name) @@ -2943,51 +2923,36 @@ def load(operator, context, filepath="", fbx_tmpl = fbx_template_get((b'Material', b'KFbxSurfacePhong')) # b'KFbxSurfaceLambert' - # textures that use this material - def texture_bumpfac_get(fbx_obj): - assert(fbx_obj.id == b'Material') - fbx_props = (elem_find_first(fbx_obj, b'Properties70'), - elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil)) - # (x / 7.142) is only a guess, cycles usable range is (0.0 -> 0.5) - return elem_props_get_number(fbx_props, b'BumpFactor', 2.5) / 7.142 - - def texture_mapping_get(fbx_obj): + def texture_mapping_set(fbx_obj, node_texture): assert(fbx_obj.id == b'Texture') fbx_props = (elem_find_first(fbx_obj, b'Properties70'), elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil)) - return (elem_props_get_vector_3d(fbx_props, b'Translation', (0.0, 0.0, 0.0)), - elem_props_get_vector_3d(fbx_props, b'Rotation', (0.0, 0.0, 0.0)), - elem_props_get_vector_3d(fbx_props, b'Scaling', (1.0, 1.0, 1.0)), - (bool(elem_props_get_enum(fbx_props, b'WrapModeU', 0)), - bool(elem_props_get_enum(fbx_props, b'WrapModeV', 0)))) - - if not use_cycles: - # Simple function to make a new mtex and set defaults - def material_mtex_new(material, image, tex_map): - tex = texture_cache.get(image) - if tex is None: - tex = bpy.data.textures.new(name=image.name, type='IMAGE') - tex.image = image - texture_cache[image] = tex - - # copy custom properties from image object to texture - for key, value in image.items(): - tex[key] = value - - # delete custom properties on the image object - for key in image.keys(): - del image[key] - - mtex = material.texture_slots.add() - mtex.texture = tex - mtex.texture_coords = 'UV' - mtex.use_map_color_diffuse = False - - # No rotation here... - mtex.offset[:] = tex_map[0] - mtex.scale[:] = tex_map[2] - return mtex + loc = elem_props_get_vector_3d(fbx_props, b'Translation', (0.0, 0.0, 0.0)) + rot = tuple(-r for r in elem_props_get_vector_3d(fbx_props, b'Rotation', (0.0, 0.0, 0.0))) + scale = tuple(((1.0 / s) if s != 0.0 else 1.0) + for s in elem_props_get_vector_3d(fbx_props, b'Scaling', (1.0, 1.0, 1.0))) + clamp_uv = (bool(elem_props_get_enum(fbx_props, b'WrapModeU', 0)), + bool(elem_props_get_enum(fbx_props, b'WrapModeV', 0))) + + if (loc == (0.0, 0.0, 0.0) and + rot == (0.0, 0.0, 0.0) and + scale == (1.0, 1.0, 1.0) and + clamp_uv == (False, False)): + return + + node_texture.translation = loc + node_texture.rotation = rot + node_texture.scale = scale + + # awkward conversion UV clamping to min/max + node_texture.min = (0.0, 0.0, 0.0) + node_texture.max = (1.0, 1.0, 1.0) + node_texture.use_min = node_texture.use_max = clamp_uv[0] or clamp_uv[1] + if clamp_uv[0] != clamp_uv[1]: + # use bool as index + node_texture.min[not clamp[0]] = -1e9 + node_texture.max[not clamp[0]] = 1e9 for fbx_uuid, fbx_item in fbx_table_nodes.items(): fbx_obj, blen_data = fbx_item @@ -2999,112 +2964,44 @@ def load(operator, context, filepath="", image, fbx_lnk_type) in connection_filter_reverse(fbx_uuid, b'Texture'): - if use_cycles: - if fbx_lnk_type.props[0] == b'OP': - lnk_type = fbx_lnk_type.props[3] - - ma_wrap = cycles_material_wrap_map[material] - - # tx/rot/scale - tex_map = texture_mapping_get(fbx_lnk) - if (tex_map[0] == (0.0, 0.0, 0.0) and - tex_map[1] == (0.0, 0.0, 0.0) and - tex_map[2] == (1.0, 1.0, 1.0) and - tex_map[3] == (False, False)): - use_mapping = False - else: - use_mapping = True - tex_map_kw = { - "translation": tex_map[0], - "rotation": [-i for i in tex_map[1]], - "scale": [((1.0 / i) if i != 0.0 else 1.0) for i in tex_map[2]], - "clamp": tex_map[3], - } - - if lnk_type in {b'DiffuseColor', b'3dsMax|maps|texmap_diffuse'}: - ma_wrap.diffuse_image_set(image) - if use_mapping: - ma_wrap.diffuse_mapping_set(**tex_map_kw) - elif lnk_type == b'SpecularColor': - ma_wrap.specular_image_set(image) - if use_mapping: - ma_wrap.specular_mapping_set(**tex_map_kw) - elif lnk_type in {b'ReflectionColor', b'3dsMax|maps|texmap_reflection'}: - ma_wrap.reflect_image_set(image) - if use_mapping: - ma_wrap.reflect_mapping_set(**tex_map_kw) - elif lnk_type == b'TransparentColor': # alpha - ma_wrap.alpha_image_set(image) - if use_mapping: - ma_wrap.alpha_mapping_set(**tex_map_kw) - if use_alpha_decals: - material_decals.add(material) - elif lnk_type == b'DiffuseFactor': - pass # TODO - elif lnk_type == b'ShininessExponent': - ma_wrap.hardness_image_set(image) - if use_mapping: - ma_wrap.hardness_mapping_set(**tex_map_kw) - # XXX, applications abuse bump! - elif lnk_type in {b'NormalMap', b'Bump', b'3dsMax|maps|texmap_bump'}: - ma_wrap.normal_image_set(image) - ma_wrap.normal_factor_set(texture_bumpfac_get(fbx_obj)) - if use_mapping: - ma_wrap.normal_mapping_set(**tex_map_kw) - """ - elif lnk_type == b'Bump': - ma_wrap.bump_image_set(image) - ma_wrap.bump_factor_set(texture_bumpfac_get(fbx_obj)) - if use_mapping: - ma_wrap.bump_mapping_set(**tex_map_kw) - """ - else: - print("WARNING: material link %r ignored" % lnk_type) - - material_images.setdefault(material, {})[lnk_type] = (image, tex_map) - else: - if fbx_lnk_type.props[0] == b'OP': - lnk_type = fbx_lnk_type.props[3] - - # tx/rot/scale (rot is ignored here!). - tex_map = texture_mapping_get(fbx_lnk) - - mtex = material_mtex_new(material, image, tex_map) - - if lnk_type in {b'DiffuseColor', b'3dsMax|maps|texmap_diffuse'}: - mtex.use_map_color_diffuse = True - mtex.blend_type = 'MULTIPLY' - elif lnk_type == b'SpecularColor': - mtex.use_map_color_spec = True - mtex.blend_type = 'MULTIPLY' - elif lnk_type in {b'ReflectionColor', b'3dsMax|maps|texmap_reflection'}: - mtex.use_map_raymir = True - elif lnk_type == b'TransparentColor': # alpha - material.use_transparency = True - material.transparency_method = 'RAYTRACE' - material.alpha = 0.0 - mtex.use_map_alpha = True - mtex.alpha_factor = 1.0 - if use_alpha_decals: - material_decals.add(material) - elif lnk_type == b'DiffuseFactor': - mtex.use_map_diffuse = True - elif lnk_type == b'ShininessExponent': - mtex.use_map_hardness = True - # XXX, applications abuse bump! - elif lnk_type in {b'NormalMap', b'Bump', b'3dsMax|maps|texmap_bump'}: - mtex.texture.use_normal_map = True # not ideal! - mtex.use_map_normal = True - mtex.normal_factor = texture_bumpfac_get(fbx_obj) - """ - elif lnk_type == b'Bump': - mtex.use_map_normal = True - mtex.normal_factor = texture_bumpfac_get(fbx_obj) - """ - else: - print("WARNING: material link %r ignored" % lnk_type) + if fbx_lnk_type.props[0] == b'OP': + lnk_type = fbx_lnk_type.props[3] + + ma_wrap = nodal_material_wrap_map[material] + + if lnk_type in {b'DiffuseColor', b'3dsMax|maps|texmap_diffuse'}: + ma_wrap.base_color_texture.image = image + texture_mapping_set(fbx_lnk, ma_wrap.base_color_texture) + elif lnk_type in {b'SpecularColor', b'SpecularFactor'}: + # Intensity actually, not color... + ma_wrap.specular_texture.image = image + texture_mapping_set(fbx_lnk, ma_wrap.specular_texture) + elif lnk_type in {b'ReflectionColor', b'ReflectionFactor', b'3dsMax|maps|texmap_reflection'}: + # Intensity actually, not color... + ma_wrap.metallic_texture.image = image + texture_mapping_set(fbx_lnk, ma_wrap.metallic_texture) + elif lnk_type in {b'TransparentColor', b'TransparentFactor'}: + # Transparency... sort of... + ma_wrap.transmission_texture.image = image + texture_mapping_set(fbx_lnk, ma_wrap.transmission_texture) + if use_alpha_decals: + material_decals.add(material) + elif lnk_type == b'ShininessExponent': + # That is probably reversed compared to expected results? TODO... + ma_wrap.roughness_texture.image = image + texture_mapping_set(fbx_lnk, ma_wrap.roughness_texture) + # XXX, applications abuse bump! + elif lnk_type in {b'NormalMap', b'Bump', b'3dsMax|maps|texmap_bump'}: + ma_wrap.normalmap_texture.image = image + texture_mapping_set(fbx_lnk, ma_wrap.normalmap_texture) + """ + elif lnk_type == b'Bump': + # TODO displacement... + """ + else: + print("WARNING: material link %r ignored" % lnk_type) - material_images.setdefault(material, {})[lnk_type] = (image, tex_map) + material_images.setdefault(material, {})[lnk_type] = image # Check if the diffuse image has an alpha channel, # if so, use the alpha channel. @@ -3115,30 +3012,21 @@ def load(operator, context, filepath="", if fbx_obj.id != b'Material': continue material = fbx_table_nodes.get(fbx_uuid, (None, None))[1] - image, tex_map = material_images.get(material, {}).get(b'DiffuseColor', (None, None)) + image = material_images.get(material, {}).get(b'DiffuseColor', None) # do we have alpha? if image and image.depth == 32: if use_alpha_decals: material_decals.add(material) - if use_cycles: - ma_wrap = cycles_material_wrap_map[material] - if ma_wrap.node_bsdf_alpha.mute: - ma_wrap.alpha_image_set_from_diffuse() - else: - if not any((True for mtex in material.texture_slots if mtex and mtex.use_map_alpha)): - mtex = material_mtex_new(material, image, tex_map) + ma_wrap = nodal_material_wrap_map[material] + ma_wrap.transmission_texture.use_alpha = True + ma_wrap.transmission_texture.copy_from(ma_wrap.base_color_texture) - material.use_transparency = True - material.transparency_method = 'RAYTRACE' - material.alpha = 0.0 - mtex.use_map_alpha = True - mtex.alpha_factor = 1.0 - - # propagate mapping from diffuse to all other channels which have none defined. - if use_cycles: - ma_wrap = cycles_material_wrap_map[material] - ma_wrap.mapping_set_from_diffuse() + # Propagate mapping from diffuse to all other channels which have none defined. + # XXX Commenting for now, I do not really understand the logic here, why should diffuse mapping + # be applied to all others if not defined for them??? + # ~ ma_wrap = nodal_material_wrap_map[material] + # ~ ma_wrap.mapping_set_from_diffuse() _(); del _ @@ -3161,14 +3049,8 @@ def load(operator, context, filepath="", v.co += v.normal * decal_offset break - if use_cycles: - for obj in (obj for obj in bpy.data.objects if obj.data == mesh): - obj.cycles_visibility.shadow = False - else: - for material in mesh.materials: - if material in material_decals: - # receive but dont cast shadows - material.use_raytrace = False + for obj in (obj for obj in bpy.data.objects if obj.data == mesh): + obj.cycles_visibility.shadow = False _(); del _ perfmon.level_down() diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py new file mode 100755 index 00000000..0318919a --- /dev/null +++ b/io_scene_gltf2/__init__.py @@ -0,0 +1,520 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +import os +import bpy +from bpy_extras.io_utils import ImportHelper, ExportHelper +from bpy.types import Operator, AddonPreferences + +from .io.com.gltf2_io_debug import Log + +from bpy.props import (CollectionProperty, + StringProperty, + BoolProperty, + EnumProperty, + FloatProperty, + IntProperty) + +# +# Globals +# + +bl_info = { + 'name': 'glTF 2.0 format', + 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann Moritz Becher, Benjamin Schmithüsen', + "version": (0, 0, 1), + 'blender': (2, 80, 0), + 'location': 'File > Import-Export', + 'description': 'Import-Export as glTF 2.0', + 'warning': '', + 'wiki_url': "https://github.com/KhronosGroup/glTF-Blender-IO", + 'tracker_url': "https://github.com/KhronosGroup/glTF-Blender-IO/issues/", + 'support': 'OFFICIAL', + 'category': 'Import-Export'} + + +# +# Functions / Classes. +# + + +class ExportGLTF2_Base: + + # TODO: refactor to avoid boilerplate + + bl_options = {'UNDO', 'PRESET'} + + export_format: EnumProperty( + name='Format', + items=(('GLB', 'glTF Binary (.glb)', + 'Exports a single file, with all data packed in binary form. ' + 'Most efficient and portable, but more difficult to edit later'), + ('GLTF_EMBEDDED', 'glTF Embedded (.gltf)', + 'Exports a single file, with all data packed in JSON. ' + 'Less efficient than binary, but easier to edit later'), + ('GLTF_SEPARATE', 'glTF Separate (.gltf + .bin + textures)', + 'Exports multiple files, with separate JSON, binary and texture data. ' + 'Easiest to edit later')), + description=( + 'Output format and embedding options. Binary is most efficient, ' + 'but JSON (embedded or separate) may be easier to edit later' + ), + default='GLB' + ) + + ui_tab: EnumProperty( + items=(('GENERAL', "General", "General settings"), + ('MESHES', "Meshes", "Mesh settings"), + ('OBJECTS', "Objects", "Object settings"), + ('MATERIALS', "Materials", "Material settings"), + ('ANIMATION', "Animation", "Animation settings")), + name="ui_tab", + description="Export setting categories", + ) + + export_copyright: StringProperty( + name='Copyright', + description='Legal rights and conditions for the model', + default='' + ) + + export_texcoords: BoolProperty( + name='UVs', + description='Export UVs (texture coordinates) with meshes', + default=True + ) + + export_normals: BoolProperty( + name='Normals', + description='Export vertex normals with meshes', + default=True + ) + + export_tangents: BoolProperty( + name='Tangents', + description='Export vertex tangents with meshes', + default=False + ) + + export_materials: BoolProperty( + name='Materials', + description='Export materials', + default=True + ) + + export_colors: BoolProperty( + name='Vertex Colors', + description='Export vertex colors with meshes', + default=True + ) + + export_cameras: BoolProperty( + name='Cameras', + description='Export cameras', + default=False + ) + + export_selected: BoolProperty( + name='Selected Objects', + description='Export selected objects only', + default=False + ) + + # export_layers: BoolProperty( + # name='All layers', + # description='Export all layers, rather than just the first', + # default=True + # ) + + export_extras: BoolProperty( + name='Custom Properties', + description='Export custom properties as glTF extras', + default=False + ) + + export_yup: BoolProperty( + name='+Y Up', + description='Export using glTF convention, +Y up', + default=True + ) + + export_apply: BoolProperty( + name='Apply Modifiers', + description='Apply modifiers to mesh objects', + default=False + ) + + export_animations: BoolProperty( + name='Animations', + description='Exports active actions and NLA tracks as glTF animations', + default=True + ) + + export_frame_range: BoolProperty( + name='Limit to Playback Range', + description='Clips animations to selected playback range', + default=True + ) + + export_frame_step: IntProperty( + name='Sampling Rate', + description='How often to evaluate animated values (in frames)', + default=1, + min=1, + max=120 + ) + + export_move_keyframes: BoolProperty( + name='Keyframes Start at 0', + description='Keyframes start at 0, instead of 1', + default=True + ) + + export_force_sampling: BoolProperty( + name='Always Sample Animations', + description='Apply sampling to all animations', + default=False + ) + + export_current_frame: BoolProperty( + name='Use Current Frame', + description='Export the scene in the current animation frame', + default=True + ) + + export_skins: BoolProperty( + name='Skinning', + description='Export skinning (armature) data', + default=True + ) + + export_bake_skins: BoolProperty( + name='Bake Skinning Constraints', + description='Apply skinning constraints to armatures', + default=False + ) + + export_all_influences: BoolProperty( + name='Include All Bone Influences', + description='Allow >4 joint vertex influences. Models may appear incorrectly in many viewers', + default=False + ) + + export_morph: BoolProperty( + name='Shape Keys', + description='Export shape keys (morph targets)', + default=True + ) + + export_morph_normal: BoolProperty( + name='Shape Key Normals', + description='Export vertex normals with shape keys (morph targets)', + default=True + ) + + export_morph_tangent: BoolProperty( + name='Shape Key Tangents', + description='Export vertex tangents with shape keys (morph targets)', + default=False + ) + + export_lights: BoolProperty( + name='Punctual Lights', + description='Export directional, point, and spot lights. ' + 'Uses "KHR_lights_punctual" glTF extension', + default=False + ) + + export_texture_transform: BoolProperty( + name='Texture Transforms', + description='Export texture or UV position, rotation, and scale. ' + 'Uses "KHR_texture_transform" glTF extension', + default=False + ) + + export_displacement: BoolProperty( + name='Displacement Textures (EXPERIMENTAL)', + description='EXPERIMENTAL: Export displacement textures. ' + 'Uses incomplete "KHR_materials_displacement" glTF extension', + default=False + ) + + will_save_settings: BoolProperty(default=False) + + # Custom scene property for saving settings + scene_key = "glTF2ExportSettings" + + # + + def invoke(self, context, event): + settings = context.scene.get(self.scene_key) + self.will_save_settings = False + if settings: + try: + for (k, v) in settings.items(): + setattr(self, k, v) + self.will_save_settings = True + + except (AttributeError, TypeError): + self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings") + del context.scene[self.scene_key] + + return ExportHelper.invoke(self, context, event) + + def save_settings(self, context): + # find all export_ props + all_props = self.properties + export_props = {x: getattr(self, x) for x in dir(all_props) + if x.startswith("export_") and all_props.get(x) is not None} + + context.scene[self.scene_key] = export_props + + def execute(self, context): + import datetime + from .blender.exp import gltf2_blender_export + + if self.will_save_settings: + self.save_settings(context) + + if self.export_format == 'GLB': + self.filename_ext = '.glb' + else: + self.filename_ext = '.gltf' + + # All custom export settings are stored in this container. + export_settings = {} + + export_settings['timestamp'] = datetime.datetime.now() + + export_settings['gltf_filepath'] = bpy.path.ensure_ext(self.filepath, self.filename_ext) + export_settings['gltf_filedirectory'] = os.path.dirname(export_settings['gltf_filepath']) + '/' + + export_settings['gltf_format'] = self.export_format + export_settings['gltf_copyright'] = self.export_copyright + export_settings['gltf_texcoords'] = self.export_texcoords + export_settings['gltf_normals'] = self.export_normals + export_settings['gltf_tangents'] = self.export_tangents and self.export_normals + export_settings['gltf_materials'] = self.export_materials + export_settings['gltf_colors'] = self.export_colors + export_settings['gltf_cameras'] = self.export_cameras + export_settings['gltf_selected'] = self.export_selected + export_settings['gltf_layers'] = True #self.export_layers + export_settings['gltf_extras'] = self.export_extras + export_settings['gltf_yup'] = self.export_yup + export_settings['gltf_apply'] = self.export_apply + export_settings['gltf_animations'] = self.export_animations + if self.export_animations: + export_settings['gltf_current_frame'] = False + export_settings['gltf_frame_range'] = self.export_frame_range + export_settings['gltf_move_keyframes'] = self.export_move_keyframes + export_settings['gltf_force_sampling'] = self.export_force_sampling + else: + export_settings['gltf_current_frame'] = self.export_current_frame + export_settings['gltf_frame_range'] = False + export_settings['gltf_move_keyframes'] = False + export_settings['gltf_force_sampling'] = False + export_settings['gltf_skins'] = self.export_skins + if self.export_skins: + export_settings['gltf_bake_skins'] = self.export_bake_skins + export_settings['gltf_all_vertex_influences'] = self.export_all_influences + else: + export_settings['gltf_bake_skins'] = False + export_settings['gltf_all_vertex_influences'] = False + export_settings['gltf_frame_step'] = self.export_frame_step + export_settings['gltf_morph'] = self.export_morph + if self.export_morph: + export_settings['gltf_morph_normal'] = self.export_morph_normal + else: + export_settings['gltf_morph_normal'] = False + if self.export_morph and self.export_morph_normal: + export_settings['gltf_morph_tangent'] = self.export_morph_tangent + else: + export_settings['gltf_morph_tangent'] = False + + export_settings['gltf_lights'] = self.export_lights + export_settings['gltf_texture_transform'] = self.export_texture_transform + export_settings['gltf_displacement'] = self.export_displacement + + export_settings['gltf_binary'] = bytearray() + export_settings['gltf_binaryfilename'] = os.path.splitext(os.path.basename(self.filepath))[0] + '.bin' + + return gltf2_blender_export.save(context, export_settings) + + def draw(self, context): + self.layout.prop(self, 'ui_tab', expand=True) + if self.ui_tab == 'GENERAL': + self.draw_general_settings() + elif self.ui_tab == 'MESHES': + self.draw_mesh_settings() + elif self.ui_tab == 'OBJECTS': + self.draw_object_settings() + elif self.ui_tab == 'MATERIALS': + self.draw_material_settings() + elif self.ui_tab == 'ANIMATION': + self.draw_animation_settings() + + def draw_general_settings(self): + col = self.layout.box().column() + col.prop(self, 'export_format') + col.prop(self, 'export_selected') + col.prop(self, 'export_apply') + col.prop(self, 'export_yup') + col.prop(self, 'export_extras') + col.prop(self, 'export_copyright') + + def draw_mesh_settings(self): + col = self.layout.box().column() + col.prop(self, 'export_texcoords') + col.prop(self, 'export_normals') + if self.export_normals: + col.prop(self, 'export_tangents') + col.prop(self, 'export_colors') + + def draw_object_settings(self): + col = self.layout.box().column() + col.prop(self, 'export_cameras') + col.prop(self, 'export_lights') + + def draw_material_settings(self): + col = self.layout.box().column() + col.prop(self, 'export_materials') + col.prop(self, 'export_texture_transform') + + def draw_animation_settings(self): + col = self.layout.box().column() + col.prop(self, 'export_animations') + if self.export_animations: + col.prop(self, 'export_frame_range') + col.prop(self, 'export_frame_step') + col.prop(self, 'export_move_keyframes') + col.prop(self, 'export_force_sampling') + else: + col.prop(self, 'export_current_frame') + col.prop(self, 'export_skins') + if self.export_skins: + col.prop(self, 'export_bake_skins') + col.prop(self, 'export_all_influences') + col.prop(self, 'export_morph') + if self.export_morph: + col.prop(self, 'export_morph_normal') + if self.export_morph_normal: + col.prop(self, 'export_morph_tangent') + + +class ExportGLTF2(bpy.types.Operator, ExportGLTF2_Base, ExportHelper): + """Export scene as glTF 2.0 file""" + bl_idname = 'export_scene.gltf' + bl_label = 'glTF 2.0 (.glb/.gltf)' + + filename_ext = '' + + filter_glob: StringProperty(default='*.glb;*.gltf', options={'HIDDEN'}) + + +def menu_func_export(self, context): + self.layout.operator(ExportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)') + + +class ImportGLTF2(Operator, ImportHelper): + bl_idname = 'import_scene.gltf' + bl_label = 'glTF 2.0 (.glb/.gltf)' + + filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'}) + + loglevel: EnumProperty( + items=Log.get_levels(), + name="Log Level", + description="Set level of log to display", + default=Log.default()) + + import_pack_images: BoolProperty( + name='Pack images', + description='Pack all images into .blend file', + default=True + ) + + import_shading: EnumProperty( + name="Shading", + items=(("NORMALS", "Use Normal Data", ""), + ("FLAT", "Flat Shading", ""), + ("SMOOTH", "Smooth Shading", "")), + description="How normals are computed during import", + default="NORMALS") + + def draw(self, context): + layout = self.layout + + layout.prop(self, 'loglevel') + layout.prop(self, 'import_pack_images') + layout.prop(self, 'import_shading') + + def execute(self, context): + return self.import_gltf2(context) + + def import_gltf2(self, context): + from .io.imp.gltf2_io_gltf import glTFImporter + from .blender.imp.gltf2_blender_gltf import BlenderGlTF + + import_settings = self.as_keywords() + + self.gltf_importer = glTFImporter(self.filepath, import_settings) + success, txt = self.gltf_importer.read() + if not success: + self.report({'ERROR'}, txt) + return {'CANCELLED'} + success, txt = self.gltf_importer.checks() + if not success: + self.report({'ERROR'}, txt) + return {'CANCELLED'} + self.gltf_importer.log.critical("Data are loaded, start creating Blender stuff") + BlenderGlTF.create(self.gltf_importer) + self.gltf_importer.log.critical("glTF import is now finished") + self.gltf_importer.log.removeHandler(self.gltf_importer.log_handler) + + return {'FINISHED'} + + +def menu_func_import(self, context): + self.layout.operator(ImportGLTF2.bl_idname, text=ImportGLTF2.bl_label) + + +classes = ( + ExportGLTF2, + ImportGLTF2 +) + + +def register(): + for c in classes: + bpy.utils.register_class(c) + # bpy.utils.register_module(__name__) + + # add to the export / import menu + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + + +def unregister(): + for c in classes: + bpy.utils.unregister_class(c) + # bpy.utils.unregister_module(__name__) + + # remove from the export / import menu + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_conversion.py b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py new file mode 100755 index 00000000..95fa292d --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py @@ -0,0 +1,42 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from mathutils import Matrix, Quaternion + +def matrix_gltf_to_blender(mat_input): + """Matrix from glTF format to Blender format.""" + mat = Matrix([mat_input[0:4], mat_input[4:8], mat_input[8:12], mat_input[12:16]]) + mat.transpose() + return mat + +def loc_gltf_to_blender(loc): + """Location.""" + return loc + +def scale_gltf_to_blender(scale): + """Scaling.""" + return scale + +def quaternion_gltf_to_blender(q): + """Quaternion from glTF to Blender.""" + return Quaternion([q[3], q[0], q[1], q[2]]) + +def scale_to_matrix(scale): + """Scale to matrix.""" + mat = Matrix() + for i in range(3): + mat[i][i] = scale[i] + + return mat + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_data_path.py b/io_scene_gltf2/blender/com/gltf2_blender_data_path.py new file mode 100755 index 00000000..c5ce4025 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_data_path.py @@ -0,0 +1,28 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def get_target_property_name(data_path: str) -> str: + """Retrieve target property.""" + return data_path.rsplit('.', 1)[-1] + + +def get_target_object_path(data_path: str) -> str: + """Retrieve target object data path without property""" + path_split = data_path.rsplit('.', 1) + self_targeting = len(path_split) < 2 + if self_targeting: + return "" + return path_split[0] + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_image.py b/io_scene_gltf2/blender/com/gltf2_blender_image.py new file mode 100755 index 00000000..7564070d --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_image.py @@ -0,0 +1,32 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +from ...io.com.gltf2_io_image import create_img_from_pixels + + +def create_img_from_blender_image(blender_image): + """ + Create a new image object using the given blender image. + + Returns the created image object. + """ + if blender_image is None: + return None + + return create_img_from_pixels(blender_image.size[0], blender_image.size[1], blender_image.pixels[:]) + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_image_util.py b/io_scene_gltf2/blender/com/gltf2_blender_image_util.py new file mode 100755 index 00000000..e2563a52 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_image_util.py @@ -0,0 +1,121 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import shutil +import bpy +import zlib +import struct +from io_scene_gltf2.blender.exp import gltf2_blender_get + + +def create_image_file(context, blender_image, dst_path, file_format): + """Create JPEG or PNG file from a given Blender image.""" + # Check, if source image exists e.g. does not exist if image is packed. + file_exists = 1 + try: + src_path = bpy.path.abspath(blender_image.filepath, library=blender_image.library) + file = open(src_path) + except IOError: + file_exists = 0 + else: + file.close() + + if file_exists == 0: + # Image does not exist on disk ... + blender_image.filepath = dst_path + # ... so save it. + blender_image.save() + + elif file_format == blender_image.file_format: + # Copy source image to destination, keeping original format. + + src_path = bpy.path.abspath(blender_image.filepath, library=blender_image.library) + + # Required for comapre. + src_path = src_path.replace('\\', '/') + dst_path = dst_path.replace('\\', '/') + + # Check that source and destination path are not the same using os.path.abspath + # because bpy.path.abspath seems to not always return an absolute path + if os.path.abspath(dst_path) != os.path.abspath(src_path): + shutil.copyfile(src_path, dst_path) + + else: + # Render a new image to destination, converting to target format. + + # TODO: Reusing the existing scene means settings like exposure are applied on export, + # which we don't want, but I'm not sure how to create a new Scene object through the + # Python API. See: https://github.com/KhronosGroup/glTF-Blender-Exporter/issues/184. + + tmp_file_format = context.scene.render.image_settings.file_format + tmp_color_depth = context.scene.render.image_settings.color_depth + + context.scene.render.image_settings.file_format = file_format + context.scene.render.image_settings.color_depth = '8' + blender_image.save_render(dst_path, context.scene) + + context.scene.render.image_settings.file_format = tmp_file_format + context.scene.render.image_settings.color_depth = tmp_color_depth + + +def create_image_data(context, export_settings, blender_image, file_format): + """Create JPEG or PNG byte array from a given Blender image.""" + if blender_image is None: + return None + + if file_format == 'PNG': + return _create_png_data(blender_image) + else: + return _create_jpg_data(context, export_settings, blender_image) + + +def _create_jpg_data(context, export_settings, blender_image): + """Create a JPEG byte array from a given Blender image.""" + uri = gltf2_blender_get.get_image_uri(export_settings, blender_image) + path = export_settings['gltf_filedirectory'] + uri + + create_image_file(context, blender_image, path, 'JPEG') + + jpg_data = open(path, 'rb').read() + os.remove(path) + + return jpg_data + + +def _create_png_data(blender_image): + """Create a PNG byte array from a given Blender image.""" + width, height = blender_image.size + + buf = bytearray([int(channel * 255.0) for channel in blender_image.pixels]) + + # + # Taken from 'blender-thumbnailer.py' in Blender. + # + + # reverse the vertical line order and add null bytes at the start + width_byte_4 = width * 4 + raw_data = b"".join( + b'\x00' + buf[span:span + width_byte_4] for span in range((height - 1) * width * 4, -1, - width_byte_4)) + + def png_pack(png_tag, data): + chunk_head = png_tag + data + return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head)) + + return b"".join([ + b'\x89PNG\r\n\x1a\n', + png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)), + png_pack(b'IDAT', zlib.compress(raw_data, 9)), + png_pack(b'IEND', b'')]) + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_json.py b/io_scene_gltf2/blender/com/gltf2_blender_json.py new file mode 100755 index 00000000..fbf833c1 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_json.py @@ -0,0 +1,38 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import bpy + + +class BlenderJSONEncoder(json.JSONEncoder): + """Blender JSON Encoder.""" + + def default(self, obj): + if isinstance(obj, bpy.types.ID): + return dict( + name=obj.name, + type=obj.__class__.__name__ + ) + return super(BlenderJSONEncoder, self).default(obj) + + +def is_json_convertible(data): + """Test, if a data set can be expressed as JSON.""" + try: + json.dumps(data, cls=BlenderJSONEncoder) + return True + except: + return False + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py new file mode 100755 index 00000000..05f35954 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_material_helpers.py @@ -0,0 +1,59 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def get_output_node(node_tree): + """Retrive output node.""" + output = [node for node in node_tree.nodes if node.type == 'OUTPUT_MATERIAL'][0] + return output + + +def get_output_surface_input(node_tree): + """Retrieve surface input of output node.""" + output_node = get_output_node(node_tree) + return output_node.inputs['Surface'] + + +def get_diffuse_texture(node_tree): + """Retrieve diffuse texture node.""" + for node in node_tree.nodes: + print(node.name) + if node.label == 'BASE COLOR': + return node + + return None + + +def get_preoutput_node_output(node_tree): + """Retrieve node just before output node.""" + output_node = get_output_node(node_tree) + preoutput_node = output_node.inputs['Surface'].links[0].from_node + + # Pre output node is Principled BSDF or any BSDF => BSDF + if 'BSDF' in preoutput_node.type: + return preoutput_node.outputs['BSDF'] + elif 'SHADER' in preoutput_node.type: + return preoutput_node.outputs['Shader'] + else: + print(preoutput_node.type) + + +def get_base_color_node(node_tree): + """Returns the last node of the diffuse block.""" + for node in node_tree.nodes: + if node.label == 'BASE COLOR': + return node + + return None + diff --git a/io_scene_gltf2/blender/com/gltf2_blender_math.py b/io_scene_gltf2/blender/com/gltf2_blender_math.py new file mode 100755 index 00000000..dd15ce2f --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_math.py @@ -0,0 +1,172 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import typing +import math +from mathutils import Matrix, Vector, Quaternion, Euler + +from io_scene_gltf2.blender.com.gltf2_blender_data_path import get_target_property_name + + +def multiply(a, b): + """Multiplication.""" + return a @ b + + +def list_to_mathutils(values: typing.List[float], data_path: str) -> typing.Union[Vector, Quaternion, Euler]: + """Transform a list to blender py object.""" + target = get_target_property_name(data_path) + + if target == 'delta_location': + return Vector(values) # TODO Should be Vector(values) - Vector(something)? + elif target == 'delta_rotation_euler': + return Euler(values).to_quaternion() # TODO Should be multiply(Euler(values).to_quaternion(), something)? + elif target == 'location': + return Vector(values) + elif target == 'rotation_axis_angle': + angle = values[0] + axis = values[1:] + return Quaternion(axis, math.radians(angle)) + elif target == 'rotation_euler': + return Euler(values).to_quaternion() + elif target == 'rotation_quaternion': + return Quaternion(values) + elif target == 'scale': + return Vector(values) + elif target == 'value': + return values + + return values + + +def mathutils_to_gltf(x: typing.Union[Vector, Quaternion]) -> typing.List[float]: + """Transform a py object to glTF list.""" + if isinstance(x, Vector): + return list(x) + if isinstance(x, Quaternion): + # Blender has w-first quaternion notation + return [x[1], x[2], x[3], x[0]] + else: + return list(x) + + +def to_yup() -> Matrix: + """Transform to Yup.""" + return Matrix( + ((1.0, 0.0, 0.0, 0.0), + (0.0, 0.0, 1.0, 0.0), + (0.0, -1.0, 0.0, 0.0), + (0.0, 0.0, 0.0, 1.0)) + ) + + +to_zup = to_yup + + +def swizzle_yup(v: typing.Union[Vector, Quaternion], data_path: str) -> typing.Union[Vector, Quaternion]: + """Manage Yup.""" + target = get_target_property_name(data_path) + swizzle_func = { + "delta_location": swizzle_yup_location, + "delta_rotation_euler": swizzle_yup_rotation, + "location": swizzle_yup_location, + "rotation_axis_angle": swizzle_yup_rotation, + "rotation_euler": swizzle_yup_rotation, + "rotation_quaternion": swizzle_yup_rotation, + "scale": swizzle_yup_scale, + "value": swizzle_yup_value + }.get(target) + + if swizzle_func is None: + raise RuntimeError("Cannot transform values at {}".format(data_path)) + + return swizzle_func(v) + + +def swizzle_yup_location(loc: Vector) -> Vector: + """Manage Yup location.""" + return Vector((loc[0], loc[2], -loc[1])) + + +def swizzle_yup_rotation(rot: Quaternion) -> Quaternion: + """Manage Yup rotation.""" + return Quaternion((rot[0], rot[1], rot[3], -rot[2])) + + +def swizzle_yup_scale(scale: Vector) -> Vector: + """Manage Yup scale.""" + return Vector((scale[0], scale[2], scale[1])) + + +def swizzle_yup_value(value: typing.Any) -> typing.Any: + """Manage Yup value.""" + return value + + +def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Matrix = Matrix.Identity(4)) -> typing \ + .Union[Vector, Quaternion]: + """Manage transformations.""" + target = get_target_property_name(data_path) + transform_func = { + "delta_location": transform_location, + "delta_rotation_euler": transform_rotation, + "location": transform_location, + "rotation_axis_angle": transform_rotation, + "rotation_euler": transform_rotation, + "rotation_quaternion": transform_rotation, + "scale": transform_scale, + "value": transform_value + }.get(target) + + if transform_func is None: + raise RuntimeError("Cannot transform values at {}".format(data_path)) + + return transform_func(v, transform) + + +def transform_location(location: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector: + """Transform location.""" + m = Matrix.Translation(location) + m = multiply(transform, m) + return m.to_translation() + + +def transform_rotation(rotation: Quaternion, transform: Matrix = Matrix.Identity(4)) -> Quaternion: + """Transform rotation.""" + m = rotation.to_matrix().to_4x4() + m = multiply(transform, m) + return m.to_quaternion() + + +def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector: + """Transform scale.""" + m = Matrix.Identity(4) + m[0][0] = scale.x + m[1][1] = scale.y + m[2][2] = scale.z + m = multiply(transform, m) + + return m.to_scale() + + +def transform_value(value: Vector, _: Matrix = Matrix.Identity(4)) -> Vector: + """Transform value.""" + return value + + +def round_if_near(value: float, target: float) -> float: + """If value is very close to target, round to target.""" + return value if abs(value - target) > 2.0e-6 else target + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_animate.py b/io_scene_gltf2/blender/exp/gltf2_blender_animate.py new file mode 100755 index 00000000..e4b11487 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_animate.py @@ -0,0 +1,638 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +import bpy +from . import gltf2_blender_export_keys +from . import gltf2_blender_extract +from mathutils import Matrix, Quaternion, Euler + + +# +# Globals +# + +JOINT_NODE = 'JOINT' + +NEEDS_CONVERSION = 'CONVERSION_NEEDED' +CUBIC_INTERPOLATION = 'CUBICSPLINE' +LINEAR_INTERPOLATION = 'LINEAR' +STEP_INTERPOLATION = 'STEP' +BEZIER_INTERPOLATION = 'BEZIER' +CONSTANT_INTERPOLATION = 'CONSTANT' + + +# +# Functions +# + +def animate_get_interpolation(export_settings, blender_fcurve_list): + """ + Retrieve the glTF interpolation, depending on a fcurve list. + + Blender allows mixing and more variations of interpolations. + In such a case, a conversion is needed. + """ + if export_settings[gltf2_blender_export_keys.FORCE_SAMPLING]: + return NEEDS_CONVERSION + + # + + interpolation = None + + keyframe_count = None + + for blender_fcurve in blender_fcurve_list: + if blender_fcurve is None: + continue + + # + + current_keyframe_count = len(blender_fcurve.keyframe_points) + + if keyframe_count is None: + keyframe_count = current_keyframe_count + + if current_keyframe_count > 0 > blender_fcurve.keyframe_points[0].co[0]: + return NEEDS_CONVERSION + + if keyframe_count != current_keyframe_count: + return NEEDS_CONVERSION + + # + + for blender_keyframe in blender_fcurve.keyframe_points: + is_bezier = blender_keyframe.interpolation == BEZIER_INTERPOLATION + is_linear = blender_keyframe.interpolation == LINEAR_INTERPOLATION + is_constant = blender_keyframe.interpolation == CONSTANT_INTERPOLATION + + if interpolation is None: + if is_bezier: + interpolation = CUBIC_INTERPOLATION + elif is_linear: + interpolation = LINEAR_INTERPOLATION + elif is_constant: + interpolation = STEP_INTERPOLATION + else: + interpolation = NEEDS_CONVERSION + return interpolation + else: + if is_bezier and interpolation != CUBIC_INTERPOLATION: + interpolation = NEEDS_CONVERSION + return interpolation + elif is_linear and interpolation != LINEAR_INTERPOLATION: + interpolation = NEEDS_CONVERSION + return interpolation + elif is_constant and interpolation != STEP_INTERPOLATION: + interpolation = NEEDS_CONVERSION + return interpolation + elif not is_bezier and not is_linear and not is_constant: + interpolation = NEEDS_CONVERSION + return interpolation + + if interpolation is None: + interpolation = NEEDS_CONVERSION + + return interpolation + + +def animate_convert_rotation_axis_angle(axis_angle): + """Convert an axis angle to a quaternion rotation.""" + q = Quaternion((axis_angle[1], axis_angle[2], axis_angle[3]), axis_angle[0]) + + return [q.x, q.y, q.z, q.w] + + +def animate_convert_rotation_euler(euler, rotation_mode): + """Convert an euler angle to a quaternion rotation.""" + rotation = Euler((euler[0], euler[1], euler[2]), rotation_mode).to_quaternion() + + return [rotation.x, rotation.y, rotation.z, rotation.w] + + +def animate_convert_keys(key_list): + """Convert Blender key frames to glTF time keys depending on the applied frames per second.""" + times = [] + + for key in key_list: + times.append(key / bpy.context.scene.render.fps) + + return times + + +def animate_gather_keys(export_settings, fcurve_list, interpolation): + """ + Merge and sort several key frames to one set. + + If an interpolation conversion is needed, the sample key frames are created as well. + """ + keys = [] + + frame_start = bpy.context.scene.frame_start + frame_end = bpy.context.scene.frame_end + + if interpolation == NEEDS_CONVERSION: + start = None + end = None + + for blender_fcurve in fcurve_list: + if blender_fcurve is None: + continue + + if start is None: + start = blender_fcurve.range()[0] + else: + start = min(start, blender_fcurve.range()[0]) + + if end is None: + end = blender_fcurve.range()[1] + else: + end = max(end, blender_fcurve.range()[1]) + + # + + add_epsilon_keyframe = False + for blender_keyframe in blender_fcurve.keyframe_points: + if add_epsilon_keyframe: + key = blender_keyframe.co[0] - 0.001 + + if key not in keys: + keys.append(key) + + add_epsilon_keyframe = False + + if blender_keyframe.interpolation == CONSTANT_INTERPOLATION: + add_epsilon_keyframe = True + + if add_epsilon_keyframe: + key = end - 0.001 + + if key not in keys: + keys.append(key) + + key = start + while key <= end: + if not export_settings[gltf2_blender_export_keys.FRAME_RANGE] or (frame_start <= key <= frame_end): + keys.append(key) + key += export_settings[gltf2_blender_export_keys.FRAME_STEP] + + keys.sort() + + else: + for blender_fcurve in fcurve_list: + if blender_fcurve is None: + continue + + for blender_keyframe in blender_fcurve.keyframe_points: + key = blender_keyframe.co[0] + if not export_settings[gltf2_blender_export_keys.FRAME_RANGE] or (frame_start <= key <= frame_end): + if key not in keys: + keys.append(key) + + keys.sort() + + return keys + + +def animate_location(export_settings, location, interpolation, node_type, node_name, action_name, matrix_correction, + matrix_basis): + """Calculate/gather the key value pairs for location transformations.""" + joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name] + if not joint_cache.get(node_name): + joint_cache[node_name] = {} + + keys = animate_gather_keys(export_settings, location, interpolation) + + times = animate_convert_keys(keys) + + result = {} + result_in_tangent = {} + result_out_tangent = {} + + keyframe_index = 0 + for timeIndex, time in enumerate(times): + translation = [0.0, 0.0, 0.0] + in_tangent = [0.0, 0.0, 0.0] + out_tangent = [0.0, 0.0, 0.0] + + if node_type == JOINT_NODE: + if joint_cache[node_name].get(keys[keyframe_index]): + translation, tmp_rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]] + else: + bpy.context.scene.frame_set(keys[keyframe_index]) + + matrix = matrix_correction * matrix_basis + + translation, tmp_rotation, tmp_scale = matrix.decompose() + + joint_cache[node_name][keys[keyframe_index]] = [translation, tmp_rotation, tmp_scale] + else: + channel_index = 0 + for blender_fcurve in location: + + if blender_fcurve is not None: + + if interpolation == CUBIC_INTERPOLATION: + blender_key_frame = blender_fcurve.keyframe_points[keyframe_index] + + translation[channel_index] = blender_key_frame.co[1] + + if timeIndex == 0: + in_tangent_value = 0.0 + else: + factor = 3.0 / (time - times[timeIndex - 1]) + in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor + + if timeIndex == len(times) - 1: + out_tangent_value = 0.0 + else: + factor = 3.0 / (times[timeIndex + 1] - time) + out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor + + in_tangent[channel_index] = in_tangent_value + out_tangent[channel_index] = out_tangent_value + else: + value = blender_fcurve.evaluate(keys[keyframe_index]) + + translation[channel_index] = value + + channel_index += 1 + + # handle parent inverse + matrix = Matrix.Translation(translation) + matrix = matrix_correction * matrix + translation = matrix.to_translation() + + translation = gltf2_blender_extract.convert_swizzle_location(translation, export_settings) + in_tangent = gltf2_blender_extract.convert_swizzle_location(in_tangent, export_settings) + out_tangent = gltf2_blender_extract.convert_swizzle_location(out_tangent, export_settings) + + result[time] = translation + result_in_tangent[time] = in_tangent + result_out_tangent[time] = out_tangent + + keyframe_index += 1 + + return result, result_in_tangent, result_out_tangent + + +def animate_rotation_axis_angle(export_settings, rotation_axis_angle, interpolation, node_type, node_name, action_name, + matrix_correction, matrix_basis): + """Calculate/gather the key value pairs for axis angle transformations.""" + joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name] + if not joint_cache.get(node_name): + joint_cache[node_name] = {} + + keys = animate_gather_keys(export_settings, rotation_axis_angle, interpolation) + + times = animate_convert_keys(keys) + + result = {} + + keyframe_index = 0 + for time in times: + axis_angle_rotation = [1.0, 0.0, 0.0, 0.0] + + if node_type == JOINT_NODE: + if joint_cache[node_name].get(keys[keyframe_index]): + tmp_location, rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]] + else: + bpy.context.scene.frame_set(keys[keyframe_index]) + + matrix = matrix_correction * matrix_basis + + tmp_location, rotation, tmp_scale = matrix.decompose() + + joint_cache[node_name][keys[keyframe_index]] = [tmp_location, rotation, tmp_scale] + else: + channel_index = 0 + for blender_fcurve in rotation_axis_angle: + if blender_fcurve is not None: + value = blender_fcurve.evaluate(keys[keyframe_index]) + + axis_angle_rotation[channel_index] = value + + channel_index += 1 + + rotation = animate_convert_rotation_axis_angle(axis_angle_rotation) + + # handle parent inverse + rotation = Quaternion((rotation[3], rotation[0], rotation[1], rotation[2])) + matrix = rotation.to_matrix().to_4x4() + matrix = matrix_correction * matrix + rotation = matrix.to_quaternion() + + # Bring back to internal Quaternion notation. + rotation = gltf2_blender_extract.convert_swizzle_rotation( + [rotation[0], rotation[1], rotation[2], rotation[3]], export_settings) + + # Bring back to glTF Quaternion notation. + rotation = [rotation[1], rotation[2], rotation[3], rotation[0]] + + result[time] = rotation + + keyframe_index += 1 + + return result + + +def animate_rotation_euler(export_settings, rotation_euler, rotation_mode, interpolation, node_type, node_name, + action_name, matrix_correction, matrix_basis): + """Calculate/gather the key value pairs for euler angle transformations.""" + joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name] + if not joint_cache.get(node_name): + joint_cache[node_name] = {} + + keys = animate_gather_keys(export_settings, rotation_euler, interpolation) + + times = animate_convert_keys(keys) + + result = {} + + keyframe_index = 0 + for time in times: + euler_rotation = [0.0, 0.0, 0.0] + + if node_type == JOINT_NODE: + if joint_cache[node_name].get(keys[keyframe_index]): + tmp_location, rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]] + else: + bpy.context.scene.frame_set(keys[keyframe_index]) + + matrix = matrix_correction * matrix_basis + + tmp_location, rotation, tmp_scale = matrix.decompose() + + joint_cache[node_name][keys[keyframe_index]] = [tmp_location, rotation, tmp_scale] + else: + channel_index = 0 + for blender_fcurve in rotation_euler: + if blender_fcurve is not None: + value = blender_fcurve.evaluate(keys[keyframe_index]) + + euler_rotation[channel_index] = value + + channel_index += 1 + + rotation = animate_convert_rotation_euler(euler_rotation, rotation_mode) + + # handle parent inverse + rotation = Quaternion((rotation[3], rotation[0], rotation[1], rotation[2])) + matrix = rotation.to_matrix().to_4x4() + matrix = matrix_correction * matrix + rotation = matrix.to_quaternion() + + # Bring back to internal Quaternion notation. + rotation = gltf2_blender_extract.convert_swizzle_rotation( + [rotation[0], rotation[1], rotation[2], rotation[3]], export_settings) + + # Bring back to glTF Quaternion notation. + rotation = [rotation[1], rotation[2], rotation[3], rotation[0]] + + result[time] = rotation + + keyframe_index += 1 + + return result + + +def animate_rotation_quaternion(export_settings, rotation_quaternion, interpolation, node_type, node_name, action_name, + matrix_correction, matrix_basis): + """Calculate/gather the key value pairs for quaternion transformations.""" + joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name] + if not joint_cache.get(node_name): + joint_cache[node_name] = {} + + keys = animate_gather_keys(export_settings, rotation_quaternion, interpolation) + + times = animate_convert_keys(keys) + + result = {} + result_in_tangent = {} + result_out_tangent = {} + + keyframe_index = 0 + for timeIndex, time in enumerate(times): + rotation = [1.0, 0.0, 0.0, 0.0] + in_tangent = [1.0, 0.0, 0.0, 0.0] + out_tangent = [1.0, 0.0, 0.0, 0.0] + + if node_type == JOINT_NODE: + if joint_cache[node_name].get(keys[keyframe_index]): + tmp_location, rotation, tmp_scale = joint_cache[node_name][keys[keyframe_index]] + else: + bpy.context.scene.frame_set(keys[keyframe_index]) + + matrix = matrix_correction * matrix_basis + + tmp_location, rotation, tmp_scale = matrix.decompose() + + joint_cache[node_name][keys[keyframe_index]] = [tmp_location, rotation, tmp_scale] + else: + channel_index = 0 + for blender_fcurve in rotation_quaternion: + + if blender_fcurve is not None: + if interpolation == CUBIC_INTERPOLATION: + blender_key_frame = blender_fcurve.keyframe_points[keyframe_index] + + rotation[channel_index] = blender_key_frame.co[1] + + if timeIndex == 0: + in_tangent_value = 0.0 + else: + factor = 3.0 / (time - times[timeIndex - 1]) + in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor + + if timeIndex == len(times) - 1: + out_tangent_value = 0.0 + else: + factor = 3.0 / (times[timeIndex + 1] - time) + out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor + + in_tangent[channel_index] = in_tangent_value + out_tangent[channel_index] = out_tangent_value + else: + value = blender_fcurve.evaluate(keys[keyframe_index]) + + rotation[channel_index] = value + + channel_index += 1 + + rotation = Quaternion((rotation[0], rotation[1], rotation[2], rotation[3])) + in_tangent = gltf2_blender_extract.convert_swizzle_rotation(in_tangent, export_settings) + out_tangent = gltf2_blender_extract.convert_swizzle_rotation(out_tangent, export_settings) + + # handle parent inverse + matrix = rotation.to_matrix().to_4x4() + matrix = matrix_correction * matrix + rotation = matrix.to_quaternion() + + # Bring back to internal Quaternion notation. + rotation = gltf2_blender_extract.convert_swizzle_rotation( + [rotation[0], rotation[1], rotation[2], rotation[3]], export_settings) + + # Bring to glTF Quaternion notation. + rotation = [rotation[1], rotation[2], rotation[3], rotation[0]] + in_tangent = [in_tangent[1], in_tangent[2], in_tangent[3], in_tangent[0]] + out_tangent = [out_tangent[1], out_tangent[2], out_tangent[3], out_tangent[0]] + + result[time] = rotation + result_in_tangent[time] = in_tangent + result_out_tangent[time] = out_tangent + + keyframe_index += 1 + + return result, result_in_tangent, result_out_tangent + + +def animate_scale(export_settings, scale, interpolation, node_type, node_name, action_name, matrix_correction, + matrix_basis): + """Calculate/gather the key value pairs for scale transformations.""" + joint_cache = export_settings[gltf2_blender_export_keys.JOINT_CACHE][action_name] + if not joint_cache.get(node_name): + joint_cache[node_name] = {} + + keys = animate_gather_keys(export_settings, scale, interpolation) + + times = animate_convert_keys(keys) + + result = {} + result_in_tangent = {} + result_out_tangent = {} + + keyframe_index = 0 + for timeIndex, time in enumerate(times): + scale_data = [1.0, 1.0, 1.0] + in_tangent = [0.0, 0.0, 0.0] + out_tangent = [0.0, 0.0, 0.0] + + if node_type == JOINT_NODE: + if joint_cache[node_name].get(keys[keyframe_index]): + tmp_location, tmp_rotation, scale_data = joint_cache[node_name][keys[keyframe_index]] + else: + bpy.context.scene.frame_set(keys[keyframe_index]) + + matrix = matrix_correction * matrix_basis + + tmp_location, tmp_rotation, scale_data = matrix.decompose() + + joint_cache[node_name][keys[keyframe_index]] = [tmp_location, tmp_rotation, scale_data] + else: + channel_index = 0 + for blender_fcurve in scale: + + if blender_fcurve is not None: + if interpolation == CUBIC_INTERPOLATION: + blender_key_frame = blender_fcurve.keyframe_points[keyframe_index] + + scale_data[channel_index] = blender_key_frame.co[1] + + if timeIndex == 0: + in_tangent_value = 0.0 + else: + factor = 3.0 / (time - times[timeIndex - 1]) + in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor + + if timeIndex == len(times) - 1: + out_tangent_value = 0.0 + else: + factor = 3.0 / (times[timeIndex + 1] - time) + out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor + + in_tangent[channel_index] = in_tangent_value + out_tangent[channel_index] = out_tangent_value + else: + value = blender_fcurve.evaluate(keys[keyframe_index]) + + scale_data[channel_index] = value + + channel_index += 1 + + scale_data = gltf2_blender_extract.convert_swizzle_scale(scale_data, export_settings) + in_tangent = gltf2_blender_extract.convert_swizzle_scale(in_tangent, export_settings) + out_tangent = gltf2_blender_extract.convert_swizzle_scale(out_tangent, export_settings) + + # handle parent inverse + matrix = Matrix() + matrix[0][0] = scale_data.x + matrix[1][1] = scale_data.y + matrix[2][2] = scale_data.z + matrix = matrix_correction * matrix + scale_data = matrix.to_scale() + + result[time] = scale_data + result_in_tangent[time] = in_tangent + result_out_tangent[time] = out_tangent + + keyframe_index += 1 + + return result, result_in_tangent, result_out_tangent + + +def animate_value(export_settings, value_parameter, interpolation, + node_type, node_name, matrix_correction, matrix_basis): + """Calculate/gather the key value pairs for scalar anaimations.""" + keys = animate_gather_keys(export_settings, value_parameter, interpolation) + + times = animate_convert_keys(keys) + + result = {} + result_in_tangent = {} + result_out_tangent = {} + + keyframe_index = 0 + for timeIndex, time in enumerate(times): + value_data = [] + in_tangent = [] + out_tangent = [] + + for blender_fcurve in value_parameter: + + if blender_fcurve is not None: + if interpolation == CUBIC_INTERPOLATION: + blender_key_frame = blender_fcurve.keyframe_points[keyframe_index] + + value_data.append(blender_key_frame.co[1]) + + if timeIndex == 0: + in_tangent_value = 0.0 + else: + factor = 3.0 / (time - times[timeIndex - 1]) + in_tangent_value = (blender_key_frame.co[1] - blender_key_frame.handle_left[1]) * factor + + if timeIndex == len(times) - 1: + out_tangent_value = 0.0 + else: + factor = 3.0 / (times[timeIndex + 1] - time) + out_tangent_value = (blender_key_frame.handle_right[1] - blender_key_frame.co[1]) * factor + + in_tangent.append(in_tangent_value) + out_tangent.append(out_tangent_value) + else: + value = blender_fcurve.evaluate(keys[keyframe_index]) + + value_data.append(value) + + result[time] = value_data + result_in_tangent[time] = in_tangent + result_out_tangent[time] = out_tangent + + keyframe_index += 1 + + return result, result_in_tangent, result_out_tangent + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export.py b/io_scene_gltf2/blender/exp/gltf2_blender_export.py new file mode 100755 index 00000000..418453c2 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_export.py @@ -0,0 +1,128 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import sys +import traceback + +from io_scene_gltf2.blender.com import gltf2_blender_json +from io_scene_gltf2.blender.exp import gltf2_blender_export_keys +from io_scene_gltf2.blender.exp import gltf2_blender_gather +from io_scene_gltf2.blender.exp.gltf2_blender_gltf2_exporter import GlTF2Exporter +from io_scene_gltf2.io.com.gltf2_io_debug import print_console, print_newline +from io_scene_gltf2.io.exp import gltf2_io_export + + +def save(context, export_settings): + """Start the glTF 2.0 export and saves to content either to a .gltf or .glb file.""" + if bpy.context.active_object is not None: + bpy.ops.object.mode_set(mode='OBJECT') + + __notify_start(context) + json, buffer = __export(export_settings) + __write_file(json, buffer, export_settings) + __notify_end(context) + return {'FINISHED'} + + +def __export(export_settings): + export_settings['gltf_channelcache'] = dict() + exporter = GlTF2Exporter(__get_copyright(export_settings)) + __add_root_objects(exporter, export_settings) + buffer = __create_buffer(exporter, export_settings) + exporter.finalize_images(export_settings[gltf2_blender_export_keys.FILE_DIRECTORY]) + json = __fix_json(exporter.glTF.to_dict()) + + return json, buffer + + +def __get_copyright(export_settings): + if export_settings[gltf2_blender_export_keys.COPYRIGHT]: + return export_settings[gltf2_blender_export_keys.COPYRIGHT] + return None + + +def __add_root_objects(exporter, export_settings): + scenes, animations = gltf2_blender_gather.gather_gltf2(export_settings) + for scene in scenes: + exporter.add_scene(scene) + for animation in animations: + exporter.add_animation(animation) + + +def __create_buffer(exporter, export_settings): + buffer = bytes() + if export_settings[gltf2_blender_export_keys.FORMAT] == 'GLB': + buffer = exporter.finalize_buffer(export_settings[gltf2_blender_export_keys.FILE_DIRECTORY], is_glb=True) + else: + if export_settings[gltf2_blender_export_keys.FORMAT] == 'GLTF_EMBEDDED': + exporter.finalize_buffer(export_settings[gltf2_blender_export_keys.FILE_DIRECTORY]) + else: + exporter.finalize_buffer(export_settings[gltf2_blender_export_keys.FILE_DIRECTORY], + export_settings[gltf2_blender_export_keys.BINARY_FILENAME]) + + return buffer + + +def __fix_json(obj): + # TODO: move to custom JSON encoder + fixed = obj + if isinstance(obj, dict): + fixed = {} + for key, value in obj.items(): + if value is None: + continue + elif isinstance(value, list) and len(value) == 0: + continue + fixed[key] = __fix_json(value) + elif isinstance(obj, list): + fixed = [] + for value in obj: + fixed.append(__fix_json(value)) + elif isinstance(obj, float): + # force floats to int, if they are integers (prevent INTEGER_WRITTEN_AS_FLOAT validator warnings) + if int(obj) == obj: + return int(obj) + return fixed + + +def __write_file(json, buffer, export_settings): + try: + gltf2_io_export.save_gltf( + json, + export_settings, + gltf2_blender_json.BlenderJSONEncoder, + buffer) + except AssertionError as e: + _, _, tb = sys.exc_info() + traceback.print_tb(tb) # Fixed format + tb_info = traceback.extract_tb(tb) + for tbi in tb_info: + filename, line, func, text = tbi + print_console('ERROR', 'An error occurred on line {} in statement {}'.format(line, text)) + print_console('ERROR', str(e)) + raise e + + +def __notify_start(context): + print_console('INFO', 'Starting glTF 2.0 export') + context.window_manager.progress_begin(0, 100) + context.window_manager.progress_update(0) + + +def __notify_end(context): + print_console('INFO', 'Finished glTF 2.0 export') + context.window_manager.progress_end() + print_newline() + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py new file mode 100755 index 00000000..ca9ca139 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py @@ -0,0 +1,63 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FILTERED_VERTEX_GROUPS = 'filtered_vertex_groups' +FILTERED_MESHES = 'filtered_meshes' +FILTERED_IMAGES = 'filtered_images' +FILTERED_IMAGES_USE_ALPHA = 'filtered_images_use_alpha' +FILTERED_MERGED_IMAGES = 'filtered_merged_images' +FILTERED_TEXTURES = 'filtered_textures' +FILTERED_MATERIALS = 'filtered_materials' +FILTERED_LIGHTS = 'filtered_lights' +TEMPORARY_MESHES = 'temporary_meshes' +FILTERED_OBJECTS = 'filtered_objects' +FILTERED_CAMERAS = 'filtered_cameras' + +APPLY = 'gltf_apply' +LAYERS = 'gltf_layers' +SELECTED = 'gltf_selected' +SKINS = 'gltf_skins' +DISPLACEMENT = 'gltf_displacement' +FORCE_SAMPLING = 'gltf_force_sampling' +FRAME_RANGE = 'gltf_frame_range' +FRAME_STEP = 'gltf_frame_step' +JOINT_CACHE = 'gltf_joint_cache' +COPYRIGHT = 'gltf_copyright' +FORMAT = 'gltf_format' +FILE_DIRECTORY = 'gltf_filedirectory' +BINARY_FILENAME = 'gltf_binaryfilename' +YUP = 'gltf_yup' +MORPH = 'gltf_morph' +BAKE_SKINS = 'gltf_bake_skins' +TEX_COORDS = 'gltf_texcoords' +COLORS = 'gltf_colors' +NORMALS = 'gltf_normals' +TANGENTS = 'gltf_tangents' +MORPH_TANGENT = 'gltf_morph_tangent' +MORPH_NORMAL = 'gltf_morph_normal' +MOVE_KEYFRAMES = 'gltf_move_keyframes' +MATERIALS = 'gltf_materials' +EXTRAS = 'gltf_extras' +CAMERAS = 'gltf_cameras' +LIGHTS = 'gltf_lights' +ANIMATIONS = 'gltf_animations' +EMBED_IMAGES = 'gltf_embed_images' +BINARY = 'gltf_binary' +EMBED_BUFFERS = 'gltf_embed_buffers' +TEXTURE_TRANSFORM = 'gltf_texture_transform' +USE_NO_COLOR = 'gltf_use_no_color' + +METALLIC_ROUGHNESS_IMAGE = "metallic_roughness_image" +GROUP_INDEX = 'group_index' + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py new file mode 100755 index 00000000..87c9d426 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py @@ -0,0 +1,1116 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +from mathutils import Vector, Quaternion +from mathutils.geometry import tessellate_polygon + +from . import gltf2_blender_export_keys +from ...io.com.gltf2_io_debug import print_console +from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins + +# +# Globals +# + +INDICES_ID = 'indices' +MATERIAL_ID = 'material' +ATTRIBUTES_ID = 'attributes' + +COLOR_PREFIX = 'COLOR_' +MORPH_TANGENT_PREFIX = 'MORPH_TANGENT_' +MORPH_NORMAL_PREFIX = 'MORPH_NORMAL_' +MORPH_POSITION_PREFIX = 'MORPH_POSITION_' +TEXCOORD_PREFIX = 'TEXCOORD_' +WEIGHTS_PREFIX = 'WEIGHTS_' +JOINTS_PREFIX = 'JOINTS_' + +TANGENT_ATTRIBUTE = 'TANGENT' +NORMAL_ATTRIBUTE = 'NORMAL' +POSITION_ATTRIBUTE = 'POSITION' + +GLTF_MAX_COLORS = 2 + + +# +# Classes +# + +class ShapeKey: + def __init__(self, shape_key, vertex_normals, polygon_normals): + self.shape_key = shape_key + self.vertex_normals = vertex_normals + self.polygon_normals = polygon_normals + + +# +# Functions +# + +def convert_swizzle_location(loc, export_settings): + """Convert a location from Blender coordinate system to glTF coordinate system.""" + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((loc[0], loc[2], -loc[1])) + else: + return Vector((loc[0], loc[1], loc[2])) + + +def convert_swizzle_tangent(tan, export_settings): + """Convert a tangent from Blender coordinate system to glTF coordinate system.""" + if tan[0] == 0.0 and tan[1] == 0.0 and tan[2] == 0.0: + print_console('WARNING', 'Tangent has zero length.') + + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((tan[0], tan[2], -tan[1], 1.0)) + else: + return Vector((tan[0], tan[1], tan[2], 1.0)) + + +def convert_swizzle_rotation(rot, export_settings): + """ + Convert a quaternion rotation from Blender coordinate system to glTF coordinate system. + + 'w' is still at first position. + """ + if export_settings[gltf2_blender_export_keys.YUP]: + return Quaternion((rot[0], rot[1], rot[3], -rot[2])) + else: + return Quaternion((rot[0], rot[1], rot[2], rot[3])) + + +def convert_swizzle_scale(scale, export_settings): + """Convert a scale from Blender coordinate system to glTF coordinate system.""" + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((scale[0], scale[2], scale[1])) + else: + return Vector((scale[0], scale[1], scale[2])) + + +def decompose_transition(matrix, context, export_settings): + translation, rotation, scale = matrix.decompose() + """Decompose a matrix depending if it is associated to a joint or node.""" + if context == 'NODE': + translation = convert_swizzle_location(translation, export_settings) + rotation = convert_swizzle_rotation(rotation, export_settings) + scale = convert_swizzle_scale(scale, export_settings) + + # Put w at the end. + rotation = Quaternion((rotation[1], rotation[2], rotation[3], rotation[0])) + + return translation, rotation, scale + + +def color_srgb_to_scene_linear(c): + """ + Convert from sRGB to scene linear color space. + + Source: Cycles addon implementation, node_color.h. + """ + if c < 0.04045: + return 0.0 if c < 0.0 else c * (1.0 / 12.92) + else: + return pow((c + 0.055) * (1.0 / 1.055), 2.4) + + +def extract_primitive_floor(a, indices, use_tangents): + """Shift indices, that the first one starts with 0. It is assumed, that the indices are packed.""" + attributes = { + POSITION_ATTRIBUTE: [], + NORMAL_ATTRIBUTE: [] + } + + if use_tangents: + attributes[TANGENT_ATTRIBUTE] = [] + + result_primitive = { + MATERIAL_ID: a[MATERIAL_ID], + INDICES_ID: [], + ATTRIBUTES_ID: attributes + } + + source_attributes = a[ATTRIBUTES_ID] + + # + + tex_coord_index = 0 + process_tex_coord = True + while process_tex_coord: + tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) + + if source_attributes.get(tex_coord_id) is not None: + attributes[tex_coord_id] = [] + tex_coord_index += 1 + else: + process_tex_coord = False + + tex_coord_max = tex_coord_index + + # + + color_index = 0 + process_color = True + while process_color: + color_id = COLOR_PREFIX + str(color_index) + + if source_attributes.get(color_id) is not None: + attributes[color_id] = [] + color_index += 1 + else: + process_color = False + + color_max = color_index + + # + + bone_index = 0 + process_bone = True + while process_bone: + joint_id = JOINTS_PREFIX + str(bone_index) + weight_id = WEIGHTS_PREFIX + str(bone_index) + + if source_attributes.get(joint_id) is not None: + attributes[joint_id] = [] + attributes[weight_id] = [] + bone_index += 1 + else: + process_bone = False + + bone_max = bone_index + + # + + morph_index = 0 + process_morph = True + while process_morph: + morph_position_id = MORPH_POSITION_PREFIX + str(morph_index) + morph_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) + morph_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) + + if source_attributes.get(morph_position_id) is not None: + attributes[morph_position_id] = [] + attributes[morph_normal_id] = [] + if use_tangents: + attributes[morph_tangent_id] = [] + morph_index += 1 + else: + process_morph = False + + morph_max = morph_index + + # + + min_index = min(indices) + max_index = max(indices) + + for old_index in indices: + result_primitive[INDICES_ID].append(old_index - min_index) + + for old_index in range(min_index, max_index + 1): + for vi in range(0, 3): + attributes[POSITION_ATTRIBUTE].append(source_attributes[POSITION_ATTRIBUTE][old_index * 3 + vi]) + attributes[NORMAL_ATTRIBUTE].append(source_attributes[NORMAL_ATTRIBUTE][old_index * 3 + vi]) + + if use_tangents: + for vi in range(0, 4): + attributes[TANGENT_ATTRIBUTE].append(source_attributes[TANGENT_ATTRIBUTE][old_index * 4 + vi]) + + for tex_coord_index in range(0, tex_coord_max): + tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) + for vi in range(0, 2): + attributes[tex_coord_id].append(source_attributes[tex_coord_id][old_index * 2 + vi]) + + for color_index in range(0, color_max): + color_id = COLOR_PREFIX + str(color_index) + for vi in range(0, 4): + attributes[color_id].append(source_attributes[color_id][old_index * 4 + vi]) + + for bone_index in range(0, bone_max): + joint_id = JOINTS_PREFIX + str(bone_index) + weight_id = WEIGHTS_PREFIX + str(bone_index) + for vi in range(0, 4): + attributes[joint_id].append(source_attributes[joint_id][old_index * 4 + vi]) + attributes[weight_id].append(source_attributes[weight_id][old_index * 4 + vi]) + + for morph_index in range(0, morph_max): + morph_position_id = MORPH_POSITION_PREFIX + str(morph_index) + morph_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) + morph_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) + for vi in range(0, 3): + attributes[morph_position_id].append(source_attributes[morph_position_id][old_index * 3 + vi]) + attributes[morph_normal_id].append(source_attributes[morph_normal_id][old_index * 3 + vi]) + if use_tangents: + for vi in range(0, 4): + attributes[morph_tangent_id].append(source_attributes[morph_tangent_id][old_index * 4 + vi]) + + return result_primitive + + +def extract_primitive_pack(a, indices, use_tangents): + """Pack indices, that the first one starts with 0. Current indices can have gaps.""" + attributes = { + POSITION_ATTRIBUTE: [], + NORMAL_ATTRIBUTE: [] + } + + if use_tangents: + attributes[TANGENT_ATTRIBUTE] = [] + + result_primitive = { + MATERIAL_ID: a[MATERIAL_ID], + INDICES_ID: [], + ATTRIBUTES_ID: attributes + } + + source_attributes = a[ATTRIBUTES_ID] + + # + + tex_coord_index = 0 + process_tex_coord = True + while process_tex_coord: + tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) + + if source_attributes.get(tex_coord_id) is not None: + attributes[tex_coord_id] = [] + tex_coord_index += 1 + else: + process_tex_coord = False + + tex_coord_max = tex_coord_index + + # + + color_index = 0 + process_color = True + while process_color: + color_id = COLOR_PREFIX + str(color_index) + + if source_attributes.get(color_id) is not None: + attributes[color_id] = [] + color_index += 1 + else: + process_color = False + + color_max = color_index + + # + + bone_index = 0 + process_bone = True + while process_bone: + joint_id = JOINTS_PREFIX + str(bone_index) + weight_id = WEIGHTS_PREFIX + str(bone_index) + + if source_attributes.get(joint_id) is not None: + attributes[joint_id] = [] + attributes[weight_id] = [] + bone_index += 1 + else: + process_bone = False + + bone_max = bone_index + + # + + morph_index = 0 + process_morph = True + while process_morph: + morph_position_id = MORPH_POSITION_PREFIX + str(morph_index) + morph_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) + morph_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) + + if source_attributes.get(morph_position_id) is not None: + attributes[morph_position_id] = [] + attributes[morph_normal_id] = [] + if use_tangents: + attributes[morph_tangent_id] = [] + morph_index += 1 + else: + process_morph = False + + morph_max = morph_index + + # + + old_to_new_indices = {} + new_to_old_indices = {} + + new_index = 0 + for old_index in indices: + if old_to_new_indices.get(old_index) is None: + old_to_new_indices[old_index] = new_index + new_to_old_indices[new_index] = old_index + new_index += 1 + + result_primitive[INDICES_ID].append(old_to_new_indices[old_index]) + + end_new_index = new_index + + for new_index in range(0, end_new_index): + old_index = new_to_old_indices[new_index] + + for vi in range(0, 3): + attributes[POSITION_ATTRIBUTE].append(source_attributes[POSITION_ATTRIBUTE][old_index * 3 + vi]) + attributes[NORMAL_ATTRIBUTE].append(source_attributes[NORMAL_ATTRIBUTE][old_index * 3 + vi]) + + if use_tangents: + for vi in range(0, 4): + attributes[TANGENT_ATTRIBUTE].append(source_attributes[TANGENT_ATTRIBUTE][old_index * 4 + vi]) + + for tex_coord_index in range(0, tex_coord_max): + tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) + for vi in range(0, 2): + attributes[tex_coord_id].append(source_attributes[tex_coord_id][old_index * 2 + vi]) + + for color_index in range(0, color_max): + color_id = COLOR_PREFIX + str(color_index) + for vi in range(0, 4): + attributes[color_id].append(source_attributes[color_id][old_index * 4 + vi]) + + for bone_index in range(0, bone_max): + joint_id = JOINTS_PREFIX + str(bone_index) + weight_id = WEIGHTS_PREFIX + str(bone_index) + for vi in range(0, 4): + attributes[joint_id].append(source_attributes[joint_id][old_index * 4 + vi]) + attributes[weight_id].append(source_attributes[weight_id][old_index * 4 + vi]) + + for morph_index in range(0, morph_max): + morph_position_id = MORPH_POSITION_PREFIX + str(morph_index) + morph_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) + morph_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) + for vi in range(0, 3): + attributes[morph_position_id].append(source_attributes[morph_position_id][old_index * 3 + vi]) + attributes[morph_normal_id].append(source_attributes[morph_normal_id][old_index * 3 + vi]) + if use_tangents: + for vi in range(0, 4): + attributes[morph_tangent_id].append(source_attributes[morph_tangent_id][old_index * 4 + vi]) + + return result_primitive + + +def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, export_settings): + """ + Extract primitives from a mesh. Polygons are triangulated and sorted by material. + + Furthermore, primitives are split up, if the indices range is exceeded. + Finally, triangles are also split up/duplicated, if face normals are used instead of vertex normals. + """ + print_console('INFO', 'Extracting primitive') + + use_tangents = False + if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0: + try: + blender_mesh.calc_tangents() + use_tangents = True + except Exception: + print_console('WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.') + + # + + material_map = {} + + # + # Gathering position, normal and tex_coords. + # + no_material_attributes = { + POSITION_ATTRIBUTE: [], + NORMAL_ATTRIBUTE: [] + } + + if use_tangents: + no_material_attributes[TANGENT_ATTRIBUTE] = [] + + # + # Directory of materials with its primitive. + # + no_material_primitives = { + MATERIAL_ID: '', + INDICES_ID: [], + ATTRIBUTES_ID: no_material_attributes + } + + material_name_to_primitives = {'': no_material_primitives} + + # + + vertex_index_to_new_indices = {} + + material_map[''] = vertex_index_to_new_indices + + # + # Create primitive for each material. + # + for blender_material in blender_mesh.materials: + if blender_material is None: + continue + + attributes = { + POSITION_ATTRIBUTE: [], + NORMAL_ATTRIBUTE: [] + } + + if use_tangents: + attributes[TANGENT_ATTRIBUTE] = [] + + primitive = { + MATERIAL_ID: blender_material.name, + INDICES_ID: [], + ATTRIBUTES_ID: attributes + } + + material_name_to_primitives[blender_material.name] = primitive + + # + + vertex_index_to_new_indices = {} + + material_map[blender_material.name] = vertex_index_to_new_indices + + tex_coord_max = 0 + if blender_mesh.uv_layers.active: + tex_coord_max = len(blender_mesh.uv_layers) + + # + + vertex_colors = {} + + color_index = 0 + for vertex_color in blender_mesh.vertex_colors: + vertex_color_name = COLOR_PREFIX + str(color_index) + vertex_colors[vertex_color_name] = vertex_color + + color_index += 1 + if color_index >= GLTF_MAX_COLORS: + break + color_max = color_index + + # + + bone_max = 0 + for blender_polygon in blender_mesh.polygons: + for loop_index in blender_polygon.loop_indices: + vertex_index = blender_mesh.loops[loop_index].vertex_index + bones_count = len(blender_mesh.vertices[vertex_index].groups) + if bones_count > 0: + if bones_count % 4 == 0: + bones_count -= 1 + bone_max = max(bone_max, bones_count // 4 + 1) + + # + + morph_max = 0 + + blender_shape_keys = [] + + if blender_mesh.shape_keys is not None: + morph_max = len(blender_mesh.shape_keys.key_blocks) - 1 + + for blender_shape_key in blender_mesh.shape_keys.key_blocks: + if blender_shape_key != blender_shape_key.relative_key: + blender_shape_keys.append(ShapeKey( + blender_shape_key, + blender_shape_key.normals_vertex_get(), # calculate vertex normals for this shape key + blender_shape_key.normals_polygon_get())) # calculate polygon normals for this shape key + + # + # Convert polygon to primitive indices and eliminate invalid ones. Assign to material. + # + for blender_polygon in blender_mesh.polygons: + export_color = True + + # + + if blender_polygon.material_index < 0 or blender_polygon.material_index >= len(blender_mesh.materials) or \ + blender_mesh.materials[blender_polygon.material_index] is None: + primitive = material_name_to_primitives[''] + vertex_index_to_new_indices = material_map[''] + else: + primitive = material_name_to_primitives[blender_mesh.materials[blender_polygon.material_index].name] + vertex_index_to_new_indices = material_map[blender_mesh.materials[blender_polygon.material_index].name] + # + + attributes = primitive[ATTRIBUTES_ID] + + face_normal = blender_polygon.normal + face_tangent = Vector((0.0, 0.0, 0.0)) + face_bitangent = Vector((0.0, 0.0, 0.0)) + if use_tangents: + for loop_index in blender_polygon.loop_indices: + temp_vertex = blender_mesh.loops[loop_index] + face_tangent += temp_vertex.tangent + face_bitangent += temp_vertex.bitangent + + face_tangent.normalize() + face_bitangent.normalize() + + # + + indices = primitive[INDICES_ID] + + loop_index_list = [] + + if len(blender_polygon.loop_indices) == 3: + loop_index_list.extend(blender_polygon.loop_indices) + elif len(blender_polygon.loop_indices) > 3: + # Triangulation of polygon. Using internal function, as non-convex polygons could exist. + polyline = [] + + for loop_index in blender_polygon.loop_indices: + vertex_index = blender_mesh.loops[loop_index].vertex_index + v = blender_mesh.vertices[vertex_index].co + polyline.append(Vector((v[0], v[1], v[2]))) + + triangles = tessellate_polygon((polyline,)) + + for triangle in triangles: + loop_index_list.append(blender_polygon.loop_indices[triangle[0]]) + loop_index_list.append(blender_polygon.loop_indices[triangle[2]]) + loop_index_list.append(blender_polygon.loop_indices[triangle[1]]) + else: + continue + + for loop_index in loop_index_list: + vertex_index = blender_mesh.loops[loop_index].vertex_index + + if vertex_index_to_new_indices.get(vertex_index) is None: + vertex_index_to_new_indices[vertex_index] = [] + + # + + v = None + n = None + t = None + b = None + uvs = [] + colors = [] + joints = [] + weights = [] + + target_positions = [] + target_normals = [] + target_tangents = [] + + vertex = blender_mesh.vertices[vertex_index] + + v = convert_swizzle_location(vertex.co, export_settings) + if blender_polygon.use_smooth: + n = convert_swizzle_location(vertex.normal, export_settings) + if use_tangents: + t = convert_swizzle_tangent(blender_mesh.loops[loop_index].tangent, export_settings) + b = convert_swizzle_location(blender_mesh.loops[loop_index].bitangent, export_settings) + else: + n = convert_swizzle_location(face_normal, export_settings) + if use_tangents: + t = convert_swizzle_tangent(face_tangent, export_settings) + b = convert_swizzle_location(face_bitangent, export_settings) + + if use_tangents: + tv = Vector((t[0], t[1], t[2])) + bv = Vector((b[0], b[1], b[2])) + nv = Vector((n[0], n[1], n[2])) + + if (nv.cross(tv)).dot(bv) < 0.0: + t[3] = -1.0 + + if blender_mesh.uv_layers.active: + for tex_coord_index in range(0, tex_coord_max): + uv = blender_mesh.uv_layers[tex_coord_index].data[loop_index].uv + uvs.append([uv.x, 1.0 - uv.y]) + + # + + if color_max > 0 and export_color: + for color_index in range(0, color_max): + color_name = COLOR_PREFIX + str(color_index) + color = vertex_colors[color_name].data[loop_index].color + colors.append([ + color_srgb_to_scene_linear(color[0]), + color_srgb_to_scene_linear(color[1]), + color_srgb_to_scene_linear(color[2]), + 1.0 + ]) + + # + + bone_count = 0 + + if blender_vertex_groups is not None and vertex.groups is not None and len(vertex.groups) > 0 and export_settings[gltf2_blender_export_keys.SKINS]: + joint = [] + weight = [] + for group_element in vertex.groups: + + if len(joint) == 4: + bone_count += 1 + joints.append(joint) + weights.append(weight) + joint = [] + weight = [] + + # + + vertex_group_index = group_element.group + vertex_group_name = blender_vertex_groups[vertex_group_index].name + + # + + joint_index = 0 + + if modifiers is not None: + modifiers_dict = {m.type: m for m in modifiers} + if "ARMATURE" in modifiers_dict: + armature = modifiers_dict["ARMATURE"].object + skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings) + for index, j in enumerate(skin.joints): + if j.name == vertex_group_name: + joint_index = index + + joint_weight = group_element.weight + + # + joint.append(joint_index) + weight.append(joint_weight) + + if len(joint) > 0: + bone_count += 1 + + for fill in range(0, 4 - len(joint)): + joint.append(0) + weight.append(0.0) + + joints.append(joint) + weights.append(weight) + + for fill in range(0, bone_max - bone_count): + joints.append([0, 0, 0, 0]) + weights.append([0.0, 0.0, 0.0, 0.0]) + + # + + if morph_max > 0 and export_settings[gltf2_blender_export_keys.MORPH]: + for morph_index in range(0, morph_max): + blender_shape_key = blender_shape_keys[morph_index] + + v_morph = convert_swizzle_location(blender_shape_key.shape_key.data[vertex_index].co, + export_settings) + + # Store delta. + v_morph -= v + + target_positions.append(v_morph) + + # + + n_morph = None + + if blender_polygon.use_smooth: + temp_normals = blender_shape_key.vertex_normals + n_morph = (temp_normals[vertex_index * 3 + 0], temp_normals[vertex_index * 3 + 1], + temp_normals[vertex_index * 3 + 2]) + else: + temp_normals = blender_shape_key.polygon_normals + n_morph = ( + temp_normals[blender_polygon.index * 3 + 0], temp_normals[blender_polygon.index * 3 + 1], + temp_normals[blender_polygon.index * 3 + 2]) + + n_morph = convert_swizzle_location(n_morph, export_settings) + + # Store delta. + n_morph -= n + + target_normals.append(n_morph) + + # + + if use_tangents: + rotation = n_morph.rotation_difference(n) + + t_morph = Vector((t[0], t[1], t[2])) + + t_morph.rotate(rotation) + + target_tangents.append(t_morph) + + # + # + + create = True + + for current_new_index in vertex_index_to_new_indices[vertex_index]: + found = True + + for i in range(0, 3): + if attributes[POSITION_ATTRIBUTE][current_new_index * 3 + i] != v[i]: + found = False + break + + if attributes[NORMAL_ATTRIBUTE][current_new_index * 3 + i] != n[i]: + found = False + break + + if use_tangents: + for i in range(0, 4): + if attributes[TANGENT_ATTRIBUTE][current_new_index * 4 + i] != t[i]: + found = False + break + + if not found: + continue + + for tex_coord_index in range(0, tex_coord_max): + uv = uvs[tex_coord_index] + + tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) + for i in range(0, 2): + if attributes[tex_coord_id][current_new_index * 2 + i] != uv[i]: + found = False + break + + if export_color: + for color_index in range(0, color_max): + color = colors[color_index] + + color_id = COLOR_PREFIX + str(color_index) + for i in range(0, 3): + # Alpha is always 1.0 - see above. + current_color = attributes[color_id][current_new_index * 4 + i] + if color_srgb_to_scene_linear(current_color) != color[i]: + found = False + break + + if export_settings[gltf2_blender_export_keys.SKINS]: + for bone_index in range(0, bone_max): + joint = joints[bone_index] + weight = weights[bone_index] + + joint_id = JOINTS_PREFIX + str(bone_index) + weight_id = WEIGHTS_PREFIX + str(bone_index) + for i in range(0, 4): + if attributes[joint_id][current_new_index * 4 + i] != joint[i]: + found = False + break + if attributes[weight_id][current_new_index * 4 + i] != weight[i]: + found = False + break + + if export_settings[gltf2_blender_export_keys.MORPH]: + for morph_index in range(0, morph_max): + target_position = target_positions[morph_index] + target_normal = target_normals[morph_index] + if use_tangents: + target_tangent = target_tangents[morph_index] + + target_position_id = MORPH_POSITION_PREFIX + str(morph_index) + target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) + target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) + for i in range(0, 3): + if attributes[target_position_id][current_new_index * 3 + i] != target_position[i]: + found = False + break + if attributes[target_normal_id][current_new_index * 3 + i] != target_normal[i]: + found = False + break + if use_tangents: + if attributes[target_tangent_id][current_new_index * 3 + i] != target_tangent[i]: + found = False + break + + if found: + indices.append(current_new_index) + + create = False + break + + if not create: + continue + + new_index = 0 + + if primitive.get('max_index') is not None: + new_index = primitive['max_index'] + 1 + + primitive['max_index'] = new_index + + vertex_index_to_new_indices[vertex_index].append(new_index) + + # + # + + indices.append(new_index) + + # + + attributes[POSITION_ATTRIBUTE].extend(v) + attributes[NORMAL_ATTRIBUTE].extend(n) + if use_tangents: + attributes[TANGENT_ATTRIBUTE].extend(t) + + if blender_mesh.uv_layers.active: + for tex_coord_index in range(0, tex_coord_max): + tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) + + if attributes.get(tex_coord_id) is None: + attributes[tex_coord_id] = [] + + attributes[tex_coord_id].extend(uvs[tex_coord_index]) + + if export_color: + for color_index in range(0, color_max): + color_id = COLOR_PREFIX + str(color_index) + + if attributes.get(color_id) is None: + attributes[color_id] = [] + + attributes[color_id].extend(colors[color_index]) + + if export_settings[gltf2_blender_export_keys.SKINS]: + for bone_index in range(0, bone_max): + joint_id = JOINTS_PREFIX + str(bone_index) + + if attributes.get(joint_id) is None: + attributes[joint_id] = [] + + attributes[joint_id].extend(joints[bone_index]) + + weight_id = WEIGHTS_PREFIX + str(bone_index) + + if attributes.get(weight_id) is None: + attributes[weight_id] = [] + + attributes[weight_id].extend(weights[bone_index]) + + if export_settings[gltf2_blender_export_keys.MORPH]: + for morph_index in range(0, morph_max): + target_position_id = MORPH_POSITION_PREFIX + str(morph_index) + + if attributes.get(target_position_id) is None: + attributes[target_position_id] = [] + + attributes[target_position_id].extend(target_positions[morph_index]) + + target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) + + if attributes.get(target_normal_id) is None: + attributes[target_normal_id] = [] + + attributes[target_normal_id].extend(target_normals[morph_index]) + + if use_tangents: + target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) + + if attributes.get(target_tangent_id) is None: + attributes[target_tangent_id] = [] + + attributes[target_tangent_id].extend(target_tangents[morph_index]) + + # + # Add primitive plus split them if needed. + # + + result_primitives = [] + + for material_name, primitive in material_name_to_primitives.items(): + export_color = True + + # + + indices = primitive[INDICES_ID] + + if len(indices) == 0: + continue + + position = primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE] + normal = primitive[ATTRIBUTES_ID][NORMAL_ATTRIBUTE] + if use_tangents: + tangent = primitive[ATTRIBUTES_ID][TANGENT_ATTRIBUTE] + tex_coords = [] + for tex_coord_index in range(0, tex_coord_max): + tex_coords.append(primitive[ATTRIBUTES_ID][TEXCOORD_PREFIX + str(tex_coord_index)]) + colors = [] + if export_color: + for color_index in range(0, color_max): + tex_coords.append(primitive[ATTRIBUTES_ID][COLOR_PREFIX + str(color_index)]) + joints = [] + weights = [] + if export_settings[gltf2_blender_export_keys.SKINS]: + for bone_index in range(0, bone_max): + joints.append(primitive[ATTRIBUTES_ID][JOINTS_PREFIX + str(bone_index)]) + weights.append(primitive[ATTRIBUTES_ID][WEIGHTS_PREFIX + str(bone_index)]) + + target_positions = [] + target_normals = [] + target_tangents = [] + if export_settings[gltf2_blender_export_keys.MORPH]: + for morph_index in range(0, morph_max): + target_positions.append(primitive[ATTRIBUTES_ID][MORPH_POSITION_PREFIX + str(morph_index)]) + target_normals.append(primitive[ATTRIBUTES_ID][MORPH_NORMAL_PREFIX + str(morph_index)]) + if use_tangents: + target_tangents.append(primitive[ATTRIBUTES_ID][MORPH_TANGENT_PREFIX + str(morph_index)]) + + # + + count = len(indices) + + if count == 0: + continue + + max_index = max(indices) + + # + + range_indices = 65536 + + # + + if max_index >= range_indices: + # + # Splitting result_primitives. + # + + # At start, all indices are pending. + pending_attributes = { + POSITION_ATTRIBUTE: [], + NORMAL_ATTRIBUTE: [] + } + + if use_tangents: + pending_attributes[TANGENT_ATTRIBUTE] = [] + + pending_primitive = { + MATERIAL_ID: material_name, + INDICES_ID: [], + ATTRIBUTES_ID: pending_attributes + } + + pending_primitive[INDICES_ID].extend(indices) + + pending_attributes[POSITION_ATTRIBUTE].extend(position) + pending_attributes[NORMAL_ATTRIBUTE].extend(normal) + if use_tangents: + pending_attributes[TANGENT_ATTRIBUTE].extend(tangent) + tex_coord_index = 0 + for tex_coord in tex_coords: + pending_attributes[TEXCOORD_PREFIX + str(tex_coord_index)] = tex_coord + tex_coord_index += 1 + if export_color: + color_index = 0 + for color in colors: + pending_attributes[COLOR_PREFIX + str(color_index)] = color + color_index += 1 + if export_settings[gltf2_blender_export_keys.SKINS]: + joint_index = 0 + for joint in joints: + pending_attributes[JOINTS_PREFIX + str(joint_index)] = joint + joint_index += 1 + weight_index = 0 + for weight in weights: + pending_attributes[WEIGHTS_PREFIX + str(weight_index)] = weight + weight_index += 1 + if export_settings[gltf2_blender_export_keys.MORPH]: + morph_index = 0 + for target_position in target_positions: + pending_attributes[MORPH_POSITION_PREFIX + str(morph_index)] = target_position + morph_index += 1 + morph_index = 0 + for target_normal in target_normals: + pending_attributes[MORPH_NORMAL_PREFIX + str(morph_index)] = target_normal + morph_index += 1 + if use_tangents: + morph_index = 0 + for target_tangent in target_tangents: + pending_attributes[MORPH_TANGENT_PREFIX + str(morph_index)] = target_tangent + morph_index += 1 + + pending_indices = pending_primitive[INDICES_ID] + + # Continue until all are processed. + while len(pending_indices) > 0: + + process_indices = pending_primitive[INDICES_ID] + max_index = max(process_indices) + + pending_indices = [] + + # + # + + all_local_indices = [] + + for i in range(0, (max_index // range_indices) + 1): + all_local_indices.append([]) + + # + # + + # For all faces ... + for face_index in range(0, len(process_indices), 3): + + written = False + + face_min_index = min(process_indices[face_index + 0], process_indices[face_index + 1], + process_indices[face_index + 2]) + face_max_index = max(process_indices[face_index + 0], process_indices[face_index + 1], + process_indices[face_index + 2]) + + # ... check if it can be but in a range of maximum indices. + for i in range(0, (max_index // range_indices) + 1): + offset = i * range_indices + + # Yes, so store the primitive with its indices. + if face_min_index >= offset and face_max_index < offset + range_indices: + all_local_indices[i].extend( + [process_indices[face_index + 0], process_indices[face_index + 1], + process_indices[face_index + 2]]) + + written = True + break + + # If not written, the triangle face has indices from different ranges. + if not written: + pending_indices.extend([process_indices[face_index + 0], process_indices[face_index + 1], + process_indices[face_index + 2]]) + + # Only add result_primitives, which do have indices in it. + for local_indices in all_local_indices: + if len(local_indices) > 0: + current_primitive = extract_primitive_floor(pending_primitive, local_indices, use_tangents) + + result_primitives.append(current_primitive) + + print_console('DEBUG', 'Adding primitive with splitting. Indices: ' + str( + len(current_primitive[INDICES_ID])) + ' Vertices: ' + str( + len(current_primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3)) + + # Process primitive faces having indices in several ranges. + if len(pending_indices) > 0: + pending_primitive = extract_primitive_pack(pending_primitive, pending_indices, use_tangents) + + print_console('DEBUG', 'Creating temporary primitive for splitting') + + else: + # + # No splitting needed. + # + result_primitives.append(primitive) + + print_console('DEBUG', 'Adding primitive without splitting. Indices: ' + str( + len(primitive[INDICES_ID])) + ' Vertices: ' + str( + len(primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3)) + + print_console('INFO', 'Primitives created: ' + str(len(result_primitives))) + + return result_primitives + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_filter.py b/io_scene_gltf2/blender/exp/gltf2_blender_filter.py new file mode 100755 index 00000000..6a4e18f9 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_filter.py @@ -0,0 +1,455 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +import bpy +from . import gltf2_blender_export_keys +from . import gltf2_blender_get +from ...io.com.gltf2_io_debug import print_console +from ..com.gltf2_blender_image import create_img_from_blender_image +from ...io.com import gltf2_io_image + +# +# Globals +# + +PREVIEW = 'PREVIEW' +GLOSSINESS = 'glTF Specular Glossiness' +ROUGHNESS = 'glTF Metallic Roughness' + + +# +# Functions +# + +def filter_merge_image(export_settings, blender_image): + metallic_channel = gltf2_blender_get.get_image_material_usage_to_socket(blender_image, "Metallic") + roughness_channel = gltf2_blender_get.get_image_material_usage_to_socket(blender_image, "Roughness") + + if metallic_channel < 0 and roughness_channel < 0: + return False + + output = export_settings[gltf2_blender_export_keys.METALLIC_ROUGHNESS_IMAGE] + if export_settings.get(export_keys.METALLIC_ROUGHNESS_IMAGE) is None: + width = blender_image.image.size[0] + height = blender_image.image.size[1] + output = gltf2_io_image.create_img(width, height, r=1.0, g=1.0, b=1.0, a=1.0) + + source = create_img_from_blender_image(blender_image.image) + + if metallic_channel >= 0: + gltf2_io_image.copy_img_channel(output, dst_channel=2, src_image=source, src_channel=metallic_channel) + output.name = blender_image.image.name + output.name + if roughness_channel >= 0: + gltf2_io_image.copy_img_channel(output, dst_channel=1, src_image=source, src_channel=roughness_channel) + if metallic_channel < 0: + output.name = output.name + blender_image.image.name + return True + + +def filter_used_materials(): + """Gather and return all unfiltered, valid Blender materials.""" + materials = [] + + for blender_material in bpy.data.materials: + if blender_material.node_tree and blender_material.use_nodes: + for currentNode in blender_material.node_tree.nodes: + if isinstance(currentNode, bpy.types.ShaderNodeGroup): + if currentNode.node_tree.name.startswith(ROUGHNESS): + materials.append(blender_material) + elif currentNode.node_tree.name.startswith(GLOSSINESS): + materials.append(blender_material) + elif isinstance(currentNode, bpy.types.ShaderNodeBsdfPrincipled): + materials.append(blender_material) + else: + materials.append(blender_material) + + return materials + + +def filter_apply(export_settings): + """ + Gathers and filters the objects and assets to export. + + Also filters out invalid, deleted and not exportable elements. + """ + filtered_objects = [] + implicit_filtered_objects = [] + + for blender_object in bpy.data.objects: + + if blender_object.users == 0: + continue + + if export_settings[gltf2_blender_export_keys.SELECTED] and blender_object.select_get() is False: + continue + + if not export_settings[gltf2_blender_export_keys.LAYERS] and not blender_object.layers[0]: + continue + + filtered_objects.append(blender_object) + + if export_settings[gltf2_blender_export_keys.SELECTED] or not export_settings[gltf2_blender_export_keys.LAYERS]: + current_parent = blender_object.parent + while current_parent: + if current_parent not in implicit_filtered_objects: + implicit_filtered_objects.append(current_parent) + + current_parent = current_parent.parent + + export_settings[gltf2_blender_export_keys.FILTERED_OBJECTS] = filtered_objects + + # Meshes + + filtered_meshes = {} + filtered_vertex_groups = {} + temporary_meshes = [] + + for blender_mesh in bpy.data.meshes: + + if blender_mesh.users == 0: + continue + + current_blender_mesh = blender_mesh + + current_blender_object = None + + skip = True + + for blender_object in filtered_objects: + + current_blender_object = blender_object + + if current_blender_object.type != 'MESH': + continue + + if current_blender_object.data == current_blender_mesh: + + skip = False + + use_auto_smooth = current_blender_mesh.use_auto_smooth + + if use_auto_smooth: + + if current_blender_mesh.shape_keys is None: + current_blender_object = current_blender_object.copy() + else: + use_auto_smooth = False + + print_console('WARNING', + 'Auto smooth and shape keys cannot be exported in parallel. ' + 'Falling back to non auto smooth.') + + if export_settings[gltf2_blender_export_keys.APPLY] or use_auto_smooth: + # TODO: maybe add to new exporter + if not export_settings[gltf2_blender_export_keys.APPLY]: + current_blender_object.modifiers.clear() + + if use_auto_smooth: + blender_modifier = current_blender_object.modifiers.new('Temporary_Auto_Smooth', 'EDGE_SPLIT') + + blender_modifier.split_angle = current_blender_mesh.auto_smooth_angle + blender_modifier.use_edge_angle = not current_blender_mesh.has_custom_normals + + current_blender_mesh = current_blender_object.to_mesh(bpy.context.scene, True, PREVIEW) + temporary_meshes.append(current_blender_mesh) + + break + + if skip: + continue + + filtered_meshes[blender_mesh.name] = current_blender_mesh + filtered_vertex_groups[blender_mesh.name] = current_blender_object.vertex_groups + + # Curves + + for blender_curve in bpy.data.curves: + + if blender_curve.users == 0: + continue + + current_blender_curve = blender_curve + + current_blender_mesh = None + + current_blender_object = None + + skip = True + + for blender_object in filtered_objects: + + current_blender_object = blender_object + + if current_blender_object.type not in ('CURVE', 'FONT'): + continue + + if current_blender_object.data == current_blender_curve: + + skip = False + + current_blender_object = current_blender_object.copy() + + if not export_settings[gltf2_blender_export_keys.APPLY]: + current_blender_object.modifiers.clear() + + current_blender_mesh = current_blender_object.to_mesh(bpy.context.scene, True, PREVIEW) + temporary_meshes.append(current_blender_mesh) + + break + + if skip: + continue + + filtered_meshes[blender_curve.name] = current_blender_mesh + filtered_vertex_groups[blender_curve.name] = current_blender_object.vertex_groups + + # + + export_settings[gltf2_blender_export_keys.FILTERED_MESHES] = filtered_meshes + export_settings[gltf2_blender_export_keys.FILTERED_VERTEX_GROUPS] = filtered_vertex_groups + export_settings[gltf2_blender_export_keys.TEMPORARY_MESHES] = temporary_meshes + + # + + filtered_materials = [] + + for blender_material in filter_used_materials(): + + if blender_material.users == 0: + continue + + for mesh_name, blender_mesh in filtered_meshes.items(): + for compare_blender_material in blender_mesh.materials: + if compare_blender_material == blender_material and blender_material not in filtered_materials: + filtered_materials.append(blender_material) + + # + + for blender_object in filtered_objects: + if blender_object.material_slots: + for blender_material_slot in blender_object.material_slots: + if blender_material_slot.link == 'DATA': + continue + + if blender_material_slot.material not in filtered_materials: + filtered_materials.append(blender_material_slot.material) + + export_settings[gltf2_blender_export_keys.FILTERED_MATERIALS] = filtered_materials + + # + + filtered_textures = [] + filtered_merged_textures = [] + + temp_filtered_texture_names = [] + + for blender_material in filtered_materials: + if blender_material.node_tree and blender_material.use_nodes: + + per_material_textures = [] + + for blender_node in blender_material.node_tree.nodes: + + if is_valid_node(blender_node) and blender_node not in filtered_textures: + add_node = False + add_merged_node = False + for blender_socket in blender_node.outputs: + if blender_socket.is_linked: + for blender_link in blender_socket.links: + if isinstance(blender_link.to_node, bpy.types.ShaderNodeGroup): + is_roughness = blender_link.to_node.node_tree.name.startswith(ROUGHNESS) + is_glossiness = blender_link.to_node.node_tree.name.startswith(GLOSSINESS) + if is_roughness or is_glossiness: + add_node = True + break + elif isinstance(blender_link.to_node, bpy.types.ShaderNodeBsdfPrincipled): + add_node = True + break + elif isinstance(blender_link.to_node, bpy.types.ShaderNodeNormalMap): + add_node = True + break + elif isinstance(blender_link.to_node, bpy.types.ShaderNodeSeparateRGB): + add_merged_node = True + break + + if add_node or add_merged_node: + break + + if add_node: + filtered_textures.append(blender_node) + # TODO: Add displacement texture, as not stored in node tree. + + if add_merged_node: + if len(per_material_textures) == 0: + filtered_merged_textures.append(per_material_textures) + + per_material_textures.append(blender_node) + + else: + + for blender_texture_slot in blender_material.texture_slots: + + if is_valid_texture_slot(blender_texture_slot) and \ + blender_texture_slot not in filtered_textures and \ + blender_texture_slot.name not in temp_filtered_texture_names: + accept = False + + if blender_texture_slot.use_map_color_diffuse: + accept = True + + if blender_texture_slot.use_map_ambient: + accept = True + if blender_texture_slot.use_map_emit: + accept = True + if blender_texture_slot.use_map_normal: + accept = True + + if export_settings[gltf2_blender_export_keys.DISPLACEMENT]: + if blender_texture_slot.use_map_displacement: + accept = True + + if accept: + filtered_textures.append(blender_texture_slot) + temp_filtered_texture_names.append(blender_texture_slot.name) + + export_settings[gltf2_blender_export_keys.FILTERED_TEXTURES] = filtered_textures + + # + + filtered_images = [] + filtered_merged_images = [] + filtered_images_use_alpha = {} + + for blender_texture in filtered_textures: + + if isinstance(blender_texture, bpy.types.ShaderNodeTexImage): + if is_valid_image(blender_texture.image) and blender_texture.image not in filtered_images: + filtered_images.append(blender_texture.image) + alpha_socket = blender_texture.outputs.get('Alpha') + if alpha_socket is not None and alpha_socket.is_linked: + filtered_images_use_alpha[blender_texture.image.name] = True + + else: + if is_valid_image(blender_texture.texture.image) and blender_texture.texture.image not in filtered_images: + filtered_images.append(blender_texture.texture.image) + if blender_texture.use_map_alpha: + filtered_images_use_alpha[blender_texture.texture.image.name] = True + + # + + for per_material_textures in filtered_merged_textures: + + export_settings[gltf2_blender_export_keys.METALLIC_ROUGHNESS_IMAGE] = None + + for blender_texture in per_material_textures: + + if isinstance(blender_texture, bpy.types.ShaderNodeTexImage): + if is_valid_image(blender_texture.image) and blender_texture.image not in filtered_images: + filter_merge_image(export_settings, blender_texture) + + img = export_settings.get(export_keys.METALLIC_ROUGHNESS_IMAGE) + if img is not None: + filtered_merged_images.append(img) + export_settings[gltf2_blender_export_keys.FILTERED_TEXTURES].append(img) + + export_settings[gltf2_blender_export_keys.FILTERED_MERGED_IMAGES] = filtered_merged_images + export_settings[gltf2_blender_export_keys.FILTERED_IMAGES] = filtered_images + export_settings[gltf2_blender_export_keys.FILTERED_IMAGES_USE_ALPHA] = filtered_images_use_alpha + + # + + filtered_cameras = [] + + for blender_camera in bpy.data.cameras: + + if blender_camera.users == 0: + continue + + if export_settings[gltf2_blender_export_keys.SELECTED]: + if blender_camera not in filtered_objects: + continue + + filtered_cameras.append(blender_camera) + + export_settings[gltf2_blender_export_keys.FILTERED_CAMERAS] = filtered_cameras + + # + # + + filtered_lights = [] + + for blender_light in bpy.data.lamps: + + if blender_light.users == 0: + continue + + if export_settings[gltf2_blender_export_keys.SELECTED]: + if blender_light not in filtered_objects: + continue + + if blender_light.type == 'HEMI': + continue + + filtered_lights.append(blender_light) + + export_settings[gltf2_blender_export_keys.FILTERED_LIGHTS] = filtered_lights + + # + # + + for implicit_object in implicit_filtered_objects: + if implicit_object not in filtered_objects: + filtered_objects.append(implicit_object) + + # + # + # + + group_index = {} + + if export_settings[gltf2_blender_export_keys.SKINS]: + for blender_object in filtered_objects: + if blender_object.type != 'ARMATURE' or len(blender_object.pose.bones) == 0: + continue + for blender_bone in blender_object.pose.bones: + group_index[blender_bone.name] = len(group_index) + + export_settings[gltf2_blender_export_keys.GROUP_INDEX] = group_index + + +def is_valid_node(blender_node): + return isinstance(blender_node, bpy.types.ShaderNodeTexImage) and is_valid_image(blender_node.image) + + +def is_valid_image(image): + return image is not None and \ + image.users != 0 and \ + image.size[0] > 0 and \ + image.size[1] > 0 + + +def is_valid_texture_slot(blender_texture_slot): + return blender_texture_slot is not None and \ + blender_texture_slot.texture and \ + blender_texture_slot.texture.users != 0 and \ + blender_texture_slot.texture.type == 'IMAGE' and \ + blender_texture_slot.texture.image is not None and \ + blender_texture_slot.texture.image.users != 0 and \ + blender_texture_slot.texture.image.size[0] > 0 and \ + blender_texture_slot.texture.image.size[1] > 0 + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py new file mode 100755 index 00000000..217930e1 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py @@ -0,0 +1,69 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy + +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_nodes +from io_scene_gltf2.blender.exp import gltf2_blender_gather_animations +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.blender.exp import gltf2_blender_generate_extras +from io_scene_gltf2.blender.exp import gltf2_blender_export_keys + + +def gather_gltf2(export_settings): + """ + Gather glTF properties from the current state of blender. + + :return: list of scene graphs to be added to the glTF export + """ + scenes = [] + animations = [] # unfortunately animations in gltf2 are just as 'root' as scenes. + for blender_scene in bpy.data.scenes: + scenes.append(__gather_scene(blender_scene, export_settings)) + animations += __gather_animations(blender_scene, export_settings) + + return scenes, animations + + +@cached +def __gather_scene(blender_scene, export_settings): + scene = gltf2_io.Scene( + extensions=None, + extras=__gather_extras(blender_scene, export_settings), + name=blender_scene.name, + nodes=[] + ) + + for blender_object in blender_scene.objects: + if blender_object.parent is None: + node = gltf2_blender_gather_nodes.gather_node(blender_object, export_settings) + if node is not None: + scene.nodes.append(node) + + return scene + + +def __gather_animations(blender_scene, export_settings): + animations = [] + for blender_object in blender_scene.objects: + animations += gltf2_blender_gather_animations.gather_animations(blender_object, export_settings) + return animations + + +def __gather_extras(blender_object, export_settings): + if export_settings[gltf2_blender_export_keys.EXTRAS]: + return gltf2_blender_generate_extras.generate_extras(blender_object) + return None + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py new file mode 100755 index 00000000..2e4ac1d7 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py @@ -0,0 +1,84 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import bpy +import typing +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.blender.exp import gltf2_blender_gather_nodes +from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints + + +@cached +def gather_animation_channel_target(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> gltf2_io.AnimationChannelTarget: + return gltf2_io.AnimationChannelTarget( + extensions=__gather_extensions(channels, blender_object, export_settings), + extras=__gather_extras(channels, blender_object, export_settings), + node=__gather_node(channels, blender_object, export_settings), + path=__gather_path(channels, blender_object, export_settings) + ) + + +def __gather_extensions(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> typing.Any: + return None + + +def __gather_extras(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> typing.Any: + return None + + +def __gather_node(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> gltf2_io.Node: + if blender_object.type == "ARMATURE": + # TODO: get joint from fcurve data_path and gather_joint + blender_bone = blender_object.path_resolve(channels[0].data_path.rsplit('.', 1)[0]) + if isinstance(blender_bone, bpy.types.PoseBone): + return gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings) + + return gltf2_blender_gather_nodes.gather_node(blender_object, export_settings) + + +def __gather_path(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> str: + target = channels[0].data_path.split('.')[-1] + path = { + "delta_location": "translation", + "delta_rotation_euler": "rotation", + "location": "translation", + "rotation_axis_angle": "rotation", + "rotation_euler": "rotation", + "rotation_quaternion": "rotation", + "scale": "scale", + "value": "weights" + }.get(target) + + if target is None: + raise RuntimeError("Cannot export an animation with {} target".format(target)) + + return path + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py new file mode 100755 index 00000000..808c970d --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py @@ -0,0 +1,131 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import typing + +from ..com.gltf2_blender_data_path import get_target_object_path, get_target_property_name +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.io.com import gltf2_io_debug +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_samplers +from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_channel_target + + +@cached +def gather_animation_channels(blender_action: bpy.types.Action, + blender_object: bpy.types.Object, + export_settings + ) -> typing.List[gltf2_io.AnimationChannel]: + channels = [] + + for channel_group in __get_channel_groups(blender_action, blender_object): + channel = __gather_animation_channel(channel_group, blender_object, export_settings) + if channel is not None: + channels.append(channel) + + return channels + + +def __gather_animation_channel(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> typing.Union[gltf2_io.AnimationChannel, None]: + if not __filter_animation_channel(channels, blender_object, export_settings): + return None + + return gltf2_io.AnimationChannel( + extensions=__gather_extensions(channels, blender_object, export_settings), + extras=__gather_extras(channels, blender_object, export_settings), + sampler=__gather_sampler(channels, blender_object, export_settings), + target=__gather_target(channels, blender_object, export_settings) + ) + + +def __filter_animation_channel(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> bool: + return True + + +def __gather_extensions(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> typing.Any: + return None + + +def __gather_extras(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> typing.Any: + return None + + +def __gather_sampler(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> gltf2_io.AnimationSampler: + return gltf2_blender_gather_animation_samplers.gather_animation_sampler( + channels, + blender_object, + export_settings + ) + + +def __gather_target(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> gltf2_io.AnimationChannelTarget: + return gltf2_blender_gather_animation_channel_target.gather_animation_channel_target( + channels, blender_object, export_settings) + + +def __get_channel_groups(blender_action: bpy.types.Action, blender_object: bpy.types.Object): + targets = {} + for fcurve in blender_action.fcurves: + target_property = get_target_property_name(fcurve.data_path) + object_path = get_target_object_path(fcurve.data_path) + + # find the object affected by this action + if not object_path: + target = blender_object + else: + try: + target = blender_object.path_resolve(object_path) + except ValueError: + # if the object is a mesh and the action target path can not be resolved, we know that this is a morph + # animation. + if blender_object.type == "MESH": + # if you need the specific shape key for some reason, this is it: + # shape_key = blender_object.data.shape_keys.path_resolve(object_path) + target = blender_object.data.shape_keys + else: + gltf2_io_debug.print_console("WARNING", "Can not export animations with target {}".format(object_path)) + continue + + # group channels by target object and affected property of the target + target_properties = targets.get(target, {}) + channels = target_properties.get(target_property, []) + channels.append(fcurve) + target_properties[target_property] = channels + targets[target] = target_properties + + groups = [] + for p in targets.values(): + groups += list(p.values()) + + return map(tuple, groups) + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py new file mode 100755 index 00000000..6ef7fb0b --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py @@ -0,0 +1,200 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import mathutils +import typing + +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.blender.com import gltf2_blender_math +from . import gltf2_blender_export_keys +from io_scene_gltf2.io.com import gltf2_io_debug + + +class Keyframe: + def __init__(self, channels: typing.Tuple[bpy.types.FCurve], time: float): + self.seconds = time / bpy.context.scene.render.fps + self.__target = channels[0].data_path.split('.')[-1] + self.__indices = [c.array_index for c in channels] + + # Data holders for virtual properties + self.__value = None + self.__in_tangent = None + self.__out_tangent = None + + def __get_target_len(self): + length = { + "delta_location": 3, + "delta_rotation_euler": 3, + "location": 3, + "rotation_axis_angle": 4, + "rotation_euler": 3, + "rotation_quaternion": 4, + "scale": 3, + "value": 1 + }.get(self.__target) + + if length is None: + raise RuntimeError("Unknown target type {}".format(self.__target)) + + return length + + def __set_indexed(self, value): + # 'value' targets don't use keyframe.array_index + if self.__target == "value": + return value + # Sometimes blender animations only reference a subset of components of a data target. Keyframe should always + # contain a complete Vector/ Quaternion --> use the array_index value of the keyframe to set components in such + # structures + result = [0.0] * self.__get_target_len() + for i, v in zip(self.__indices, value): + result[i] = v + result = gltf2_blender_math.list_to_mathutils(result, self.__target) + return result + + @property + def value(self) -> typing.Union[mathutils.Vector, mathutils.Euler, mathutils.Quaternion, typing.List[float]]: + return self.__value + + @value.setter + def value(self, value: typing.List[float]): + self.__value = self.__set_indexed(value) + + @property + def in_tangent(self) -> typing.Union[mathutils.Vector, mathutils.Euler, mathutils.Quaternion, typing.List[float]]: + return self.__in_tangent + + @in_tangent.setter + def in_tangent(self, value: typing.List[float]): + self.__in_tangent = self.__set_indexed(value) + + @property + def out_tangent(self) -> typing.Union[mathutils.Vector, mathutils.Euler, mathutils.Quaternion, typing.List[float]]: + return self.__in_tangent + + @out_tangent.setter + def out_tangent(self, value: typing.List[float]): + self.__out_tangent = self.__set_indexed(value) + + +# cache for performance reasons +@cached +def gather_keyframes(channels: typing.Tuple[bpy.types.FCurve], export_settings) \ + -> typing.List[Keyframe]: + """Convert the blender action groups' fcurves to keyframes for use in glTF.""" + # Find the start and end of the whole action group + ranges = [channel.range() for channel in channels] + + start = min([channel.range()[0] for channel in channels]) + end = max([channel.range()[1] for channel in channels]) + + keyframes = [] + if needs_baking(channels, export_settings): + # Bake the animation, by evaluating it at a high frequency + # TODO: maybe baking can also be done with FCurve.convert_to_samples + time = start + # TODO: make user controllable + step = 1.0 / bpy.context.scene.render.fps + while time <= end: + key = Keyframe(channels, time) + key.value = [c.evaluate(time) for c in channels] + keyframes.append(key) + time += step + else: + # Just use the keyframes as they are specified in blender + times = [keyframe.co[0] for keyframe in channels[0].keyframe_points] + for i, time in enumerate(times): + key = Keyframe(channels, time) + # key.value = [c.keyframe_points[i].co[0] for c in action_group.channels] + key.value = [c.evaluate(time) for c in channels] + + # compute tangents for cubic spline interpolation + if channels[0].keyframe_points[0].interpolation == "BEZIER": + # Construct the in tangent + if time == times[0]: + # start in-tangent has zero length + key.in_tangent = [0.0 for _ in channels] + else: + # otherwise construct an in tangent from the keyframes control points + + key.in_tangent = [ + 3.0 * (c.keyframe_points[i].co[1] - c.keyframe_points[i].handle_left[1] + ) / (time - times[i - 1]) + for c in channels + ] + # Construct the out tangent + if time == times[-1]: + # end out-tangent has zero length + key.out_tangent = [0.0 for _ in channels] + else: + # otherwise construct an out tangent from the keyframes control points + key.out_tangent = [ + 3.0 * (c.keyframe_points[i].handle_right[1] - c.keyframe_points[i].co[1] + ) / (times[i + 1] - time) + for c in channels + ] + keyframes.append(key) + + return keyframes + + +def needs_baking(channels: typing.Tuple[bpy.types.FCurve], + export_settings + ) -> bool: + """ + Check if baking is needed. + + Some blender animations need to be baked as they can not directly be expressed in glTF. + """ + def all_equal(lst): + return lst[1:] == lst[:-1] + + + if export_settings[gltf2_blender_export_keys.FORCE_SAMPLING]: + return True + + interpolation = channels[0].keyframe_points[0].interpolation + if interpolation not in ["BEZIER", "LINEAR", "CONSTANT"]: + gltf2_io_debug.print_console("WARNING", + "Baking animation because of an unsupported interpolation method: {}".format( + interpolation) + ) + return True + + if any(any(k.interpolation != interpolation for k in c.keyframe_points) for c in channels): + # There are different interpolation methods in one action group + gltf2_io_debug.print_console("WARNING", + "Baking animation because there are different " + "interpolation methods in one channel" + ) + return True + + if not all_equal([len(c.keyframe_points) for c in channels]): + gltf2_io_debug.print_console("WARNING", + "Baking animation because the number of keyframes is not " + "equal for all channel tracks") + return True + + if len(channels[0].keyframe_points) <= 1: + # we need to bake to 'STEP', as at least two keyframes are required to interpolate + return True + + if not all(all_equal(key_times) for key_times in zip([[k.co[0] for k in c.keyframe_points] for c in channels])): + # The channels have differently located keyframes + gltf2_io_debug.print_console("WARNING", + "Baking animation because of differently located keyframes in one channel") + return True + + return False + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py new file mode 100755 index 00000000..6846128d --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py @@ -0,0 +1,169 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import bpy +import mathutils +import typing +import math + +from . import gltf2_blender_export_keys +from mathutils import Matrix +from io_scene_gltf2.blender.com.gltf2_blender_data_path import get_target_property_name, get_target_object_path +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.io.exp import gltf2_io_binary_data +from io_scene_gltf2.io.com import gltf2_io_constants +from io_scene_gltf2.blender.com import gltf2_blender_math +from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_sampler_keyframes + + +@cached +def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> gltf2_io.AnimationSampler: + return gltf2_io.AnimationSampler( + extensions=__gather_extensions(channels, blender_object, export_settings), + extras=__gather_extras(channels, blender_object, export_settings), + input=__gather_input(channels, blender_object, export_settings), + interpolation=__gather_interpolation(channels, blender_object, export_settings), + output=__gather_output(channels, blender_object, export_settings) + ) + + +def __gather_extensions(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> typing.Any: + return None + + +def __gather_extras(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> typing.Any: + return None + + +def __gather_input(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> gltf2_io.Accessor: + """Gather the key time codes.""" + keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(channels, export_settings) + times = [k.seconds for k in keyframes] + + return gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData.from_list(times, gltf2_io_constants.ComponentType.Float), + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(times), + extensions=None, + extras=None, + max=[max(times)], + min=[min(times)], + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Scalar + ) + + +def __gather_interpolation(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> str: + if gltf2_blender_gather_animation_sampler_keyframes.needs_baking(channels, export_settings): + return 'STEP' + + blender_keyframe = channels[0].keyframe_points[0] + + # Select the interpolation method. Any unsupported method will fallback to STEP + return { + "BEZIER": "CUBICSPLINE", + "LINEAR": "LINEAR", + "CONSTANT": "STEP" + }[blender_keyframe.interpolation] + + +def __gather_output(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + export_settings + ) -> gltf2_io.Accessor: + """Gather the data of the keyframes.""" + keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(channels, export_settings) + + target_datapath = channels[0].data_path + + transform = blender_object.matrix_parent_inverse + + isYup = export_settings[gltf2_blender_export_keys.YUP] + + if blender_object.type == "ARMATURE": + bone = blender_object.path_resolve(get_target_object_path(target_datapath)) + if isinstance(bone, bpy.types.PoseBone): + if bone.parent is not None: + parent_transform = bone.parent.bone.matrix_local + transform = gltf2_blender_math.multiply(transform, parent_transform.inverted()) + # if not isYup: + # transform = gltf2_blender_math.multiply(transform, gltf2_blender_math.to_zup()) + else: + # only apply the y-up conversion to root bones, as child bones already are in the y-up space + if isYup: + transform = gltf2_blender_math.multiply(transform, gltf2_blender_math.to_yup()) + local_transform = bone.bone.matrix_local + transform = gltf2_blender_math.multiply(transform, local_transform) + + values = [] + for keyframe in keyframes: + # Transform the data and extract + value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform) + if isYup and not blender_object.type == "ARMATURE": + value = gltf2_blender_math.swizzle_yup(value, target_datapath) + keyframe_value = gltf2_blender_math.mathutils_to_gltf(value) + if keyframe.in_tangent is not None: + in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform) + if isYup and not blender_object.type == "ARMATURE": + in_tangent = gltf2_blender_math.swizzle_yup(in_tangent, target_datapath) + keyframe_value = gltf2_blender_math.mathutils_to_gltf(in_tangent) + keyframe_value + if keyframe.out_tangent is not None: + out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform) + if isYup and not blender_object.type == "ARMATURE": + out_tangent = gltf2_blender_math.swizzle_yup(out_tangent, target_datapath) + keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf(out_tangent) + values += keyframe_value + + component_type = gltf2_io_constants.ComponentType.Float + if get_target_property_name(target_datapath) == "value": + # channels with 'weight' targets must have scalar accessors + data_type = gltf2_io_constants.DataType.Scalar + else: + data_type = gltf2_io_constants.DataType.vec_type_from_num(len(keyframes[0].value)) + + return gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData.from_list(values, component_type), + byte_offset=None, + component_type=component_type, + count=len(values) // gltf2_io_constants.DataType.num_elements(data_type), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=data_type + ) + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py new file mode 100755 index 00000000..bfbc03ed --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py @@ -0,0 +1,169 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import typing + +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_channels + + +def gather_animations(blender_object: bpy.types.Object, export_settings) -> typing.List[gltf2_io.Animation]: + """ + Gather all animations which contribute to the objects property. + + :param blender_object: The blender object which is animated + :param export_settings: + :return: A list of glTF2 animations + """ + animations = [] + + # Collect all 'actions' affecting this object. There is a direct mapping between blender actions and glTF animations + blender_actions = __get_blender_actions(blender_object) + + # Export all collected actions. + for blender_action in blender_actions: + animation = __gather_animation(blender_action, blender_object, export_settings) + if animation is not None: + animations.append(animation) + + return animations + + +def __gather_animation(blender_action: bpy.types.Action, + blender_object: bpy.types.Object, + export_settings + ) -> typing.Optional[gltf2_io.Animation]: + if not __filter_animation(blender_action, blender_object, export_settings): + return None + + animation = gltf2_io.Animation( + channels=__gather_channels(blender_action, blender_object, export_settings), + extensions=__gather_extensions(blender_action, blender_object, export_settings), + extras=__gather_extras(blender_action, blender_object, export_settings), + name=__gather_name(blender_action, blender_object, export_settings), + samplers=__gather_samplers(blender_action, blender_object, export_settings) + ) + + # To allow reuse of samplers in one animation, + __link_samplers(animation, export_settings) + + if not animation.channels: + return None + + return animation + + +def __filter_animation(blender_action: bpy.types.Action, + blender_object: bpy.types.Object, + export_settings + ) -> bool: + if blender_action.users == 0: + return False + + return True + + +def __gather_channels(blender_action: bpy.types.Action, + blender_object: bpy.types.Object, + export_settings + ) -> typing.List[gltf2_io.AnimationChannel]: + return gltf2_blender_gather_animation_channels.gather_animation_channels( + blender_action, blender_object, export_settings) + + +def __gather_extensions(blender_action: bpy.types.Action, + blender_object: bpy.types.Object, + export_settings + ) -> typing.Any: + return None + + +def __gather_extras(blender_action: bpy.types.Action, + blender_object: bpy.types.Object, + export_settings + ) -> typing.Any: + return None + + +def __gather_name(blender_action: bpy.types.Action, + blender_object: bpy.types.Object, + export_settings + ) -> typing.Optional[str]: + return blender_action.name + + +def __gather_samplers(blender_action: bpy.types.Action, + blender_object: bpy.types.Object, + export_settings + ) -> typing.List[gltf2_io.AnimationSampler]: + # We need to gather the samplers after gathering all channels --> populate this list in __link_samplers + return [] + + +def __link_samplers(animation: gltf2_io.Animation, export_settings): + """ + Move animation samplers to their own list and store their indices at their previous locations. + + After gathering, samplers are stored in the channels properties of the animation and need to be moved + to their own list while storing an index into this list at the position where they previously were. + This behaviour is similar to that of the glTFExporter that traverses all nodes + :param animation: + :param export_settings: + :return: + """ + # TODO: move this to some util module and update gltf2 exporter also + T = typing.TypeVar('T') + + def __append_unique_and_get_index(l: typing.List[T], item: T): + if item in l: + return l.index(item) + else: + index = len(l) + l.append(item) + return index + + for i, channel in enumerate(animation.channels): + animation.channels[i].sampler = __append_unique_and_get_index(animation.samplers, channel.sampler) + + +def __get_blender_actions(blender_object: bpy.types.Object + ) -> typing.List[bpy.types.Action]: + blender_actions = [] + + if blender_object.animation_data is not None: + # Collect active action. + if blender_object.animation_data.action is not None: + blender_actions.append(blender_object.animation_data.action) + + # Collect associated strips from NLA tracks. + for track in blender_object.animation_data.nla_tracks: + # Multi-strip tracks do not export correctly yet (they need to be baked), + # so skip them for now and only write single-strip tracks. + if track.strips is None or len(track.strips) != 1: + continue + for strip in track.strips: + blender_actions.append(strip.action) + + if blender_object.type == "MESH" \ + and blender_object.data is not None \ + and blender_object.data.shape_keys is not None \ + and blender_object.data.shape_keys.animation_data is not None: + blender_actions.append(blender_object.data.shape_keys.animation_data.action) + + # Remove duplicate actions. + blender_actions = list(set(blender_actions)) + + return blender_actions + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py new file mode 100755 index 00000000..5b00a98b --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py @@ -0,0 +1,60 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools + + +def cached(func): + """ + Decorate the cache gather functions results. + + The gather function is only executed if its result isn't in the cache yet + :param func: the function to be decorated. It will have a static __cache member afterwards + :return: + """ + @functools.wraps(func) + def wrapper_cached(*args, **kwargs): + assert len(args) >= 2 and 0 <= len(kwargs) <= 1, "Wrong signature for cached function" + cache_key_args = args + # make a shallow copy of the keyword arguments so that 'export_settings' can be removed + cache_key_kwargs = dict(kwargs) + if kwargs.get("export_settings"): + export_settings = kwargs["export_settings"] + # 'export_settings' should not be cached + del cache_key_kwargs["export_settings"] + else: + export_settings = args[-1] + cache_key_args = args[:-1] + + # we make a tuple from the function arguments so that they can be used as a key to the cache + cache_key = tuple(cache_key_args + tuple(cache_key_kwargs.values())) + + # invalidate cache if export settings have changed + if not hasattr(func, "__export_settings") or export_settings != func.__export_settings: + func.__cache = {} + func.__export_settings = export_settings + # use or fill cache + if cache_key in func.__cache: + return func.__cache[cache_key] + else: + result = func(*args) + func.__cache[cache_key] = result + return result + return wrapper_cached + + +# TODO: replace "cached" with "unique" in all cases where the caching is functional and not only for performance reasons +call_or_fetch = cached +unique = cached + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py new file mode 100755 index 00000000..9640a7ce --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py @@ -0,0 +1,122 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import gltf2_blender_export_keys +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.io.com import gltf2_io + +import bpy +import math + + +@cached +def gather_camera(blender_object, export_settings): + if not __filter_camera(blender_object, export_settings): + return None + + return gltf2_io.Camera( + extensions=__gather_extensions(blender_object, export_settings), + extras=__gather_extras(blender_object, export_settings), + name=__gather_name(blender_object, export_settings), + orthographic=__gather_orthographic(blender_object, export_settings), + perspective=__gather_perspective(blender_object, export_settings), + type=__gather_type(blender_object, export_settings) + ) + + +def __filter_camera(blender_object, export_settings): + if blender_object.type != 'CAMERA': + return False + if not __gather_type(blender_object, export_settings): + return False + + return True + + +def __gather_extensions(blender_object, export_settings): + return None + + +def __gather_extras(blender_object, export_settings): + return None + + +def __gather_name(blender_object, export_settings): + return blender_object.data.name + + +def __gather_orthographic(blender_object, export_settings): + if __gather_type(blender_object, export_settings) == "orthographic": + orthographic = gltf2_io.CameraOrthographic( + extensions=None, + extras=None, + xmag=None, + ymag=None, + zfar=None, + znear=None + ) + blender_camera = blender_object.data + + orthographic.xmag = blender_camera.ortho_scale + orthographic.ymag = blender_camera.ortho_scale + + orthographic.znear = blender_camera.clip_start + orthographic.zfar = blender_camera.clip_end + + return orthographic + return None + + +def __gather_perspective(blender_object, export_settings): + if __gather_type(blender_object, export_settings) == "perspective": + perspective = gltf2_io.CameraPerspective( + aspect_ratio=None, + extensions=None, + extras=None, + yfov=None, + zfar=None, + znear=None + ) + blender_camera = blender_object.data + + width = bpy.context.scene.render.pixel_aspect_x * bpy.context.scene.render.resolution_x + height = bpy.context.scene.render.pixel_aspect_y * bpy.context.scene.render.resolution_y + perspective.aspectRatio = width / height + + if width >= height: + if blender_camera.sensor_fit != 'VERTICAL': + perspective.yfov = 2.0 * math.atan(math.tan(blender_camera.angle * 0.5) / perspective.aspectRatio) + else: + perspective.yfov = blender_camera.angle + else: + if blender_camera.sensor_fit != 'HORIZONTAL': + perspective.yfov = blender_camera.angle + else: + perspective.yfov = 2.0 * math.atan(math.tan(blender_camera.angle * 0.5) / perspective.aspectRatio) + + perspective.znear = blender_camera.clip_start + perspective.zfar = blender_camera.clip_end + + return perspective + return None + + +def __gather_type(blender_object, export_settings): + camera = blender_object.data + if camera.type == 'PERSP': + return "perspective" + elif camera.type == 'ORTHO': + return "orthographic" + return None + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py new file mode 100755 index 00000000..b6131a59 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py @@ -0,0 +1,177 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import typing +import os +import numpy as np + +from . import gltf2_blender_export_keys +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree +from io_scene_gltf2.io.exp import gltf2_io_binary_data +from io_scene_gltf2.io.exp import gltf2_io_image_data + + +def gather_image( + blender_shader_sockets_or_texture_slots: typing.Union[typing.Tuple[bpy.types.NodeSocket], + typing.Tuple[bpy.types.Texture]], + export_settings): + if not __filter_image(blender_shader_sockets_or_texture_slots, export_settings): + return None + image = gltf2_io.Image( + buffer_view=__gather_buffer_view(blender_shader_sockets_or_texture_slots, export_settings), + extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings), + extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings), + mime_type=__gather_mime_type(blender_shader_sockets_or_texture_slots, export_settings), + name=__gather_name(blender_shader_sockets_or_texture_slots, export_settings), + uri=__gather_uri(blender_shader_sockets_or_texture_slots, export_settings) + ) + return image + + +def __filter_image(sockets_or_slots, export_settings): + if not sockets_or_slots: + return False + return True + + +def __gather_buffer_view(sockets_or_slots, export_settings): + if export_settings[gltf2_blender_export_keys.FORMAT] != 'GLTF_SEPARATE': + image = __get_image_data(sockets_or_slots, export_settings) + return gltf2_io_binary_data.BinaryData( + data=image.to_image_data(__gather_mime_type(sockets_or_slots, export_settings))) + return None + + +def __gather_extensions(sockets_or_slots, export_settings): + return None + + +def __gather_extras(sockets_or_slots, export_settings): + return None + + +def __gather_mime_type(sockets_or_slots, export_settings): + return 'image/png' + # return 'image/jpeg' + + +def __gather_name(sockets_or_slots, export_settings): + if __is_socket(sockets_or_slots): + node = __get_tex_from_socket(sockets_or_slots[0]) + if node is not None: + return node.shader_node.image.name + elif isinstance(sockets_or_slots[0], bpy.types.MaterialTextureSlot): + return sockets_or_slots[0].name + return None + + +def __gather_uri(sockets_or_slots, export_settings): + if export_settings[gltf2_blender_export_keys.FORMAT] == 'GLTF_SEPARATE': + # as usual we just store the data in place instead of already resolving the references + return __get_image_data(sockets_or_slots, export_settings) + return None + + +def __is_socket(sockets_or_slots): + return isinstance(sockets_or_slots[0], bpy.types.NodeSocket) + + +def __is_slot(sockets_or_slots): + return isinstance(sockets_or_slots[0], bpy.types.MaterialTextureSlot) + + +def __get_image_data(sockets_or_slots, export_settings): + # For shared ressources, such as images, we just store the portion of data that is needed in the glTF property + # in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary + # ressources. + def split_pixels_by_channels(image: bpy.types.Image, export_settings) -> typing.List[typing.List[float]]: + channelcache = export_settings['gltf_channelcache'] + if image.name in channelcache: + return channelcache[image.name] + + pixels = np.array(image.pixels) + pixels = pixels.reshape((pixels.shape[0] // image.channels, image.channels)) + channels = np.split(pixels, pixels.shape[1], axis=1) + + channelcache[image.name] = channels + + return channels + + if __is_socket(sockets_or_slots): + results = [__get_tex_from_socket(socket) for socket in sockets_or_slots] + image = None + for result, socket in zip(results, sockets_or_slots): + # rudimentarily try follow the node tree to find the correct image data. + channel = None + for elem in result.path: + if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateRGB): + channel = { + 'R': 0, + 'G': 1, + 'B': 2 + }[elem.from_socket.name] + + if channel is not None: + pixels = [split_pixels_by_channels(result.shader_node.image, export_settings)[channel]] + else: + pixels = split_pixels_by_channels(result.shader_node.image, export_settings) + channel = 0 + + file_name = os.path.splitext(result.shader_node.image.name)[0] + + image_data = gltf2_io_image_data.ImageData( + file_name, + result.shader_node.image.filepath, + result.shader_node.image.size[0], + result.shader_node.image.size[1], + channel, + pixels) + + if image is None: + image = image_data + else: + image.add_to_image(channel, image_data) + + return image + elif __is_slot(sockets_or_slots): + texture = __get_tex_from_slot(sockets_or_slots[0]) + pixels = split_pixels_by_channels(texture.image, export_settings) + + image_data = gltf2_io_image_data.ImageData( + texture.name, + texture.image.filepath, + texture.image.size[0], + texture.image.size[1], + 0, + pixels) + return image_data + else: + # Texture slots + raise NotImplementedError() + + +def __get_tex_from_socket(blender_shader_socket: bpy.types.NodeSocket): + result = gltf2_blender_search_node_tree.from_socket( + blender_shader_socket, + gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage)) + if not result: + return None + return result[0] + + +def __get_tex_from_slot(blender_texture_slot): + return blender_texture_slot.texture + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py new file mode 100755 index 00000000..38e47031 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py @@ -0,0 +1,80 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mathutils + +from . import gltf2_blender_export_keys +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.io.com import gltf2_io_debug +from io_scene_gltf2.blender.exp import gltf2_blender_extract +from io_scene_gltf2.blender.com import gltf2_blender_math + + +@cached +def gather_joint(blender_bone, export_settings): + """ + Generate a glTF2 node from a blender bone, as joints in glTF2 are simply nodes. + + :param blender_bone: a blender PoseBone + :param export_settings: the settings for this export + :return: a glTF2 node (acting as a joint) + """ + axis_basis_change = mathutils.Matrix.Identity(4) + if export_settings[gltf2_blender_export_keys.YUP]: + axis_basis_change = mathutils.Matrix( + ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0))) + + # extract bone transform + if blender_bone.parent is None: + correction_matrix_local = gltf2_blender_math.multiply(axis_basis_change, blender_bone.bone.matrix_local) + else: + correction_matrix_local = gltf2_blender_math.multiply( + blender_bone.parent.bone.matrix_local.inverted(), blender_bone.bone.matrix_local) + matrix_basis = blender_bone.matrix_basis + if export_settings[gltf2_blender_export_keys.BAKE_SKINS]: + gltf2_io_debug.print_console("WARNING", "glTF bake skins not supported") + # matrix_basis = blender_object.convert_space(blender_bone, blender_bone.matrix, from_space='POSE', + # to_space='LOCAL') + trans, rot, sca = gltf2_blender_extract.decompose_transition( + gltf2_blender_math.multiply(correction_matrix_local, matrix_basis), 'JOINT', export_settings) + translation, rotation, scale = (None, None, None) + if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0: + translation = [trans[0], trans[1], trans[2]] + if rot[0] != 0.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 1.0: + rotation = [rot[0], rot[1], rot[2], rot[3]] + if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0: + scale = [sca[0], sca[1], sca[2]] + + # traverse into children + children = [] + for bone in blender_bone.children: + children.append(gather_joint(bone, export_settings)) + + # finally add to the joints array containing all the joints in the hierarchy + return gltf2_io.Node( + camera=None, + children=children, + extensions=None, + extras=None, + matrix=None, + mesh=None, + name=blender_bone.name, + rotation=rotation, + scale=scale, + skin=None, + translation=translation, + weights=None + ) + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py new file mode 100644 index 00000000..b2ab3953 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_light_spots.py @@ -0,0 +1,45 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional +from io_scene_gltf2.io.com import gltf2_io_lights_punctual + + +def gather_light_spot(blender_lamp, export_settings) -> Optional[gltf2_io_lights_punctual.LightSpot]: + + if not __filter_light_spot(blender_lamp, export_settings): + return None + + spot = gltf2_io_lights_punctual.LightSpot( + inner_cone_angle=__gather_inner_cone_angle(blender_lamp, export_settings), + outer_cone_angle=__gather_outer_cone_angle(blender_lamp, export_settings) + ) + return spot + + +def __filter_light_spot(blender_lamp, _) -> bool: + if blender_lamp.type != "SPOT": + return False + + return True + + +def __gather_inner_cone_angle(blender_lamp, _) -> Optional[float]: + angle = blender_lamp.spot_size * 0.5 + return angle - angle * blender_lamp.spot_blend + + +def __gather_outer_cone_angle(blender_lamp, _) -> Optional[float]: + return blender_lamp.spot_size * 0.5 + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py new file mode 100644 index 00000000..108656ea --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py @@ -0,0 +1,132 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import math +from typing import Optional, List, Dict, Any + +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached + +from io_scene_gltf2.io.com import gltf2_io_lights_punctual +from io_scene_gltf2.io.com import gltf2_io_debug + +from io_scene_gltf2.blender.exp import gltf2_blender_gather_light_spots +from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree + + +@cached +def gather_lights_punctual(blender_lamp, export_settings) -> Optional[Dict[str, Any]]: + if not __filter_lights_punctual(blender_lamp, export_settings): + return None + + light = gltf2_io_lights_punctual.Light( + color=__gather_color(blender_lamp, export_settings), + intensity=__gather_intensity(blender_lamp, export_settings), + spot=__gather_spot(blender_lamp, export_settings), + type=__gather_type(blender_lamp, export_settings), + range=__gather_range(blender_lamp, export_settings), + name=__gather_name(blender_lamp, export_settings), + extensions=__gather_extensions(blender_lamp, export_settings), + extras=__gather_extras(blender_lamp, export_settings) + ) + + return light.to_dict() + + +def __filter_lights_punctual(blender_lamp, export_settings) -> bool: + if blender_lamp.type in ["HEMI", "AREA"]: + gltf2_io_debug.print_console("WARNING", "Unsupported light source {}".format(blender_lamp.type)) + return False + + return True + + +def __gather_color(blender_lamp, export_settings) -> Optional[List[float]]: + emission_node = __get_cycles_emission_node(blender_lamp) + if emission_node is not None: + return emission_node.inputs["Color"].default_value + + return list(blender_lamp.color) + + +def __gather_intensity(blender_lamp, _) -> Optional[float]: + emission_node = __get_cycles_emission_node(blender_lamp) + if emission_node is not None: + if blender_lamp.type != 'SUN': + # When using cycles, the strength should be influenced by a LightFalloff node + result = gltf2_blender_search_node_tree.from_socket( + emission_node.get("Strength"), + gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeLightFalloff) + ) + if result: + quadratic_falloff_node = result[0].shader_node + emission_strength = quadratic_falloff_node.inputs["Strength"].default_value / (math.pi * 4.0) + else: + gltf2_io_debug.print_console('WARNING', + 'No quadratic light falloff node attached to emission strength property') + emission_strength = blender_lamp.energy + else: + emission_strength = emission_node.inputs["Strength"].default_value + return emission_strength + + return blender_lamp.energy + + +def __gather_spot(blender_lamp, export_settings) -> Optional[gltf2_io_lights_punctual.LightSpot]: + if blender_lamp.type == "SPOT": + return gltf2_blender_gather_light_spots.gather_light_spot(blender_lamp, export_settings) + return None + + +def __gather_type(blender_lamp, _) -> str: + return { + "POINT": "point", + "SUN": "directional", + "SPOT": "spot" + }[blender_lamp.type] + + +def __gather_range(blender_lamp, export_settings) -> Optional[float]: + # TODO: calculate range from + # https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual#range-property + return None + + +def __gather_name(blender_lamp, export_settings) -> Optional[str]: + return blender_lamp.name + + +def __gather_extensions(blender_lamp, export_settings) -> Optional[dict]: + return None + + +def __gather_extras(blender_lamp, export_settings) -> Optional[Any]: + return None + + +def __get_cycles_emission_node(blender_lamp) -> Optional[bpy.types.ShaderNodeEmission]: + if blender_lamp.use_nodes and blender_lamp.node_tree: + for currentNode in blender_lamp.node_tree.nodes: + if isinstance(currentNode, bpy.types.ShaderNodeOutputLamp): + if not currentNode.is_active_output: + continue + result = gltf2_blender_search_node_tree.from_socket( + currentNode.inputs.get("Surface"), + gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeEmission) + ) + if not result: + continue + return result[0].shader_node + return None + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_normal_texture_info_class.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_normal_texture_info_class.py new file mode 100755 index 00000000..3d78a478 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_normal_texture_info_class.py @@ -0,0 +1,131 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import typing +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture +from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree +from io_scene_gltf2.blender.exp import gltf2_blender_export_keys +from io_scene_gltf2.blender.exp import gltf2_blender_get +from io_scene_gltf2.io.com.gltf2_io_extensions import Extension + + +@cached +def gather_material_normal_texture_info_class(blender_shader_sockets_or_texture_slots: typing.Union[ + typing.Tuple[bpy.types.NodeSocket], typing.Tuple[bpy.types.Texture]], + export_settings): + if not __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings): + return None + + texture_info = gltf2_io.MaterialNormalTextureInfoClass( + extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings), + extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings), + scale=__gather_scale(blender_shader_sockets_or_texture_slots, export_settings), + index=__gather_index(blender_shader_sockets_or_texture_slots, export_settings), + tex_coord=__gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings) + ) + + return texture_info + + +def __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings): + if not blender_shader_sockets_or_texture_slots: + return False + if not all([elem is not None for elem in blender_shader_sockets_or_texture_slots]): + return False + if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket): + if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets_or_texture_slots]): + # sockets do not lead to a texture --> discard + return False + return True + + +def __gather_extensions(blender_shader_sockets_or_texture_slots, export_settings): + if not export_settings[gltf2_blender_export_keys.TEXTURE_TRANSFORM]: + return None + + normal_map_node = blender_shader_sockets_or_texture_slots[0].links[0].from_node + if not isinstance(normal_map_node, bpy.types.ShaderNodeNormalMap): + return None + + texture_socket = normal_map_node.inputs["Color"] + if len(texture_socket.links) == 0: + return None + + texture_node = texture_socket.links[0].from_node + texture_transform = gltf2_blender_get.get_texture_transform_from_texture_node(texture_node) + + extension = Extension("KHR_texture_transform", texture_transform) + return {"KHR_texture_transform": extension} + + +def __gather_extras(blender_shader_sockets_or_texture_slots, export_settings): + return None + + +def __gather_scale(blender_shader_sockets_or_texture_slots, export_settings): + return None + + +def __gather_index(blender_shader_sockets_or_texture_slots, export_settings): + # We just put the actual shader into the 'index' member + return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets_or_texture_slots, export_settings) + + +def __gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings): + if __is_socket(blender_shader_sockets_or_texture_slots): + blender_shader_node = __get_tex_from_socket(blender_shader_sockets_or_texture_slots[0]).shader_node + if len(blender_shader_node.inputs['Vector'].links) == 0: + return 0 + + input_node = blender_shader_node.inputs['Vector'].links[0].from_node + + if isinstance(input_node, bpy.types.ShaderNodeMapping): + + if len(input_node.inputs['Vector'].links) == 0: + return 0 + + input_node = input_node.inputs['Vector'].links[0].from_node + + if not isinstance(input_node, bpy.types.ShaderNodeUVMap): + return 0 + + if input_node.uv_map == '': + return 0 + + # Try to gather map index. + for blender_mesh in bpy.data.meshes: + texCoordIndex = blender_mesh.uv_layers.find(input_node.uv_map) + if texCoordIndex >= 0: + return texCoordIndex + + return 0 + else: + raise NotImplementedError() + + +def __is_socket(sockets_or_slots): + return isinstance(sockets_or_slots[0], bpy.types.NodeSocket) + + +def __get_tex_from_socket(socket): + result = gltf2_blender_search_node_tree.from_socket( + socket, + gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage)) + if not result: + return None + return result[0] + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_occlusion_texture_info_class.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_occlusion_texture_info_class.py new file mode 100755 index 00000000..c9c70e42 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_material_occlusion_texture_info_class.py @@ -0,0 +1,113 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import typing +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture +from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree + + +@cached +def gather_material_occlusion_texture_info_class(blender_shader_sockets_or_texture_slots: typing.Union[ + typing.Tuple[bpy.types.NodeSocket], typing.Tuple[bpy.types.Texture]], + export_settings): + if not __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings): + return None + + texture_info = gltf2_io.MaterialOcclusionTextureInfoClass( + extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings), + extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings), + strength=__gather_scale(blender_shader_sockets_or_texture_slots, export_settings), + index=__gather_index(blender_shader_sockets_or_texture_slots, export_settings), + tex_coord=__gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings) + ) + + return texture_info + + +def __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings): + if not blender_shader_sockets_or_texture_slots: + return False + if not all([elem is not None for elem in blender_shader_sockets_or_texture_slots]): + return False + if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket): + if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets_or_texture_slots]): + # sockets do not lead to a texture --> discard + return False + return True + + +def __gather_extensions(blender_shader_sockets_or_texture_slots, export_settings): + return None + + +def __gather_extras(blender_shader_sockets_or_texture_slots, export_settings): + return None + + +def __gather_scale(blender_shader_sockets_or_texture_slots, export_settings): + return None + + +def __gather_index(blender_shader_sockets_or_texture_slots, export_settings): + # We just put the actual shader into the 'index' member + return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets_or_texture_slots, export_settings) + + +def __gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings): + if __is_socket(blender_shader_sockets_or_texture_slots): + blender_shader_node = __get_tex_from_socket(blender_shader_sockets_or_texture_slots[0]).shader_node + if len(blender_shader_node.inputs['Vector'].links) == 0: + return 0 + + input_node = blender_shader_node.inputs['Vector'].links[0].from_node + + if isinstance(input_node, bpy.types.ShaderNodeMapping): + + if len(input_node.inputs['Vector'].links) == 0: + return 0 + + input_node = input_node.inputs['Vector'].links[0].from_node + + if not isinstance(input_node, bpy.types.ShaderNodeUVMap): + return 0 + + if input_node.uv_map == '': + return 0 + + # Try to gather map index. + for blender_mesh in bpy.data.meshes: + texCoordIndex = blender_mesh.uv_layers.find(input_node.uv_map) + if texCoordIndex >= 0: + return texCoordIndex + + return 0 + else: + raise NotImplementedError() + + +def __is_socket(sockets_or_slots): + return isinstance(sockets_or_slots[0], bpy.types.NodeSocket) + + +def __get_tex_from_socket(socket): + result = gltf2_blender_search_node_tree.from_socket( + socket, + gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage)) + if not result: + return None + return result[0] + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py new file mode 100755 index 00000000..427f07ce --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -0,0 +1,154 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy + +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info +from io_scene_gltf2.blender.exp import gltf2_blender_gather_material_normal_texture_info_class +from io_scene_gltf2.blender.exp import gltf2_blender_gather_material_occlusion_texture_info_class + +from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_pbr_metallic_roughness +from io_scene_gltf2.blender.exp import gltf2_blender_generate_extras +from io_scene_gltf2.blender.exp import gltf2_blender_get + + +@cached +def gather_material(blender_material, export_settings): + """ + Gather the material used by the blender primitive. + + :param blender_material: the blender material used in the glTF primitive + :param export_settings: + :return: a glTF material + """ + if not __filter_material(blender_material, export_settings): + return None + + material = gltf2_io.Material( + alpha_cutoff=__gather_alpha_cutoff(blender_material, export_settings), + alpha_mode=__gather_alpha_mode(blender_material, export_settings), + double_sided=__gather_double_sided(blender_material, export_settings), + emissive_factor=__gather_emissive_factor(blender_material, export_settings), + emissive_texture=__gather_emissive_texture(blender_material, export_settings), + extensions=__gather_extensions(blender_material, export_settings), + extras=__gather_extras(blender_material, export_settings), + name=__gather_name(blender_material, export_settings), + normal_texture=__gather_normal_texture(blender_material, export_settings), + occlusion_texture=__gather_occlusion_texture(blender_material, export_settings), + pbr_metallic_roughness=__gather_pbr_metallic_roughness(blender_material, export_settings) + ) + + return material + # material = blender_primitive['material'] + # + # if get_material_requires_texcoords(glTF, material) and not export_settings['gltf_texcoords']: + # material = -1 + # + # if get_material_requires_normals(glTF, material) and not export_settings['gltf_normals']: + # material = -1 + # + # # Meshes/primitives without material are allowed. + # if material >= 0: + # primitive.material = material + # else: + # print_console('WARNING', 'Material ' + internal_primitive[ + # 'material'] + ' not found. Please assign glTF 2.0 material or enable Blinn-Phong material in export.') + + +def __filter_material(blender_material, export_settings): + # if not blender_material.use_nodes: + # return False + # if not blender_material.node_tree: + # return False + return True + + +def __gather_alpha_cutoff(blender_material, export_settings): + if blender_material.blend_method == 'CLIP': + return blender_material.alpha_threshold + return None + + +def __gather_alpha_mode(blender_material, export_settings): + if blender_material.blend_method == 'CLIP': + return 'MASK' + elif blender_material.blend_method == 'BLEND': + return 'BLEND' + return None + + +def __gather_double_sided(blender_material, export_settings): + return None + + +def __gather_emissive_factor(blender_material, export_settings): + emissive_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Emissive") + if emissive_socket is None: + emissive_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "EmissiveFactor") + if isinstance(emissive_socket, bpy.types.NodeSocket) and not emissive_socket.is_linked: + return list(emissive_socket.default_value)[0:3] + return None + + +def __gather_emissive_texture(blender_material, export_settings): + emissive = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Emissive") + if emissive is None: + emissive = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Emissive") + return gltf2_blender_gather_texture_info.gather_texture_info((emissive,), export_settings) + + +def __gather_extensions(blender_material, export_settings): + extensions = {} + + + # TODO specular glossiness extension + + return extensions if extensions else None + + +def __gather_extras(blender_material, export_settings): + if export_settings['gltf_extras']: + return gltf2_blender_generate_extras.generate_extras(blender_material) + return None + + +def __gather_name(blender_material, export_settings): + return blender_material.name + + +def __gather_normal_texture(blender_material, export_settings): + normal = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Normal") + if normal is None: + normal = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Normal") + return gltf2_blender_gather_material_normal_texture_info_class.gather_material_normal_texture_info_class( + (normal,), + export_settings) + + +def __gather_occlusion_texture(blender_material, export_settings): + occlusion = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Occlusion") + if occlusion is None: + occlusion = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "Occlusion") + return gltf2_blender_gather_material_occlusion_texture_info_class.gather_material_occlusion_texture_info_class( + (occlusion,), + export_settings) + + +def __gather_pbr_metallic_roughness(blender_material, export_settings): + return gltf2_blender_gather_materials_pbr_metallic_roughness.gather_material_pbr_metallic_roughness( + blender_material, + export_settings) + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py new file mode 100755 index 00000000..2a6315bf --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials_pbr_metallic_roughness.py @@ -0,0 +1,103 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy + +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info +from io_scene_gltf2.blender.exp import gltf2_blender_get +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached + + +@cached +def gather_material_pbr_metallic_roughness(blender_material, export_settings): + if not __filter_pbr_material(blender_material, export_settings): + return None + + material = gltf2_io.MaterialPBRMetallicRoughness( + base_color_factor=__gather_base_color_factor(blender_material, export_settings), + base_color_texture=__gather_base_color_texture(blender_material, export_settings), + extensions=__gather_extensions(blender_material, export_settings), + extras=__gather_extras(blender_material, export_settings), + metallic_factor=__gather_metallic_factor(blender_material, export_settings), + metallic_roughness_texture=__gather_metallic_roughness_texture(blender_material, export_settings), + roughness_factor=__gather_roughness_factor(blender_material, export_settings) + ) + + return material + + +def __filter_pbr_material(blender_material, export_settings): + return True + + +def __gather_base_color_factor(blender_material, export_settings): + base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Base Color") + if base_color_socket is None: + base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "BaseColor") + if base_color_socket is None: + base_color_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "BaseColorFactor") + if isinstance(base_color_socket, bpy.types.NodeSocket) and not base_color_socket.is_linked: + return list(base_color_socket.default_value) + return None + +def __gather_base_color_texture(blender_material, export_settings): + base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Base Color") + if base_color_socket is None: + base_color_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "BaseColor") + if base_color_socket is None: + base_color_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "BaseColor") + return gltf2_blender_gather_texture_info.gather_texture_info((base_color_socket,), export_settings) + + +def __gather_extensions(blender_material, export_settings): + return None + + +def __gather_extras(blender_material, export_settings): + return None + + +def __gather_metallic_factor(blender_material, export_settings): + metallic_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Metallic") + if metallic_socket is None: + metallic_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "MetallicFactor") + if isinstance(metallic_socket, bpy.types.NodeSocket) and not metallic_socket.is_linked: + return metallic_socket.default_value + return None + + +def __gather_metallic_roughness_texture(blender_material, export_settings): + metallic_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Metallic") + roughness_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Roughness") + + if metallic_socket is None and roughness_socket is None: + metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "MetallicRoughness") + if metallic_roughness is None: + metallic_roughness = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "MetallicRoughness") + texture_input = (metallic_roughness,) + else: + texture_input = (metallic_socket, roughness_socket) + + return gltf2_blender_gather_texture_info.gather_texture_info(texture_input, export_settings) + + +def __gather_roughness_factor(blender_material, export_settings): + roughness_socket = gltf2_blender_get.get_socket_or_texture_slot(blender_material, "Roughness") + if roughness_socket is None: + roughness_socket = gltf2_blender_get.get_socket_or_texture_slot_old(blender_material, "RoughnessFactor") + if isinstance(roughness_socket, bpy.types.NodeSocket) and not roughness_socket.is_linked: + return roughness_socket.default_value + return None + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py new file mode 100755 index 00000000..f32eb733 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py @@ -0,0 +1,94 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from typing import Optional, Dict, List, Any +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitives +from io_scene_gltf2.blender.exp import gltf2_blender_generate_extras + + +@cached +def gather_mesh(blender_mesh: bpy.types.Mesh, + vertex_groups: Optional[bpy.types.VertexGroups], + modifiers: Optional[bpy.types.ObjectModifiers], + skip_filter: bool, + export_settings + ) -> Optional[gltf2_io.Mesh]: + if not skip_filter and not __filter_mesh(blender_mesh, vertex_groups, modifiers, export_settings): + return None + + mesh = gltf2_io.Mesh( + extensions=__gather_extensions(blender_mesh, vertex_groups, modifiers, export_settings), + extras=__gather_extras(blender_mesh, vertex_groups, modifiers, export_settings), + name=__gather_name(blender_mesh, vertex_groups, modifiers, export_settings), + primitives=__gather_primitives(blender_mesh, vertex_groups, modifiers, export_settings), + weights=__gather_weights(blender_mesh, vertex_groups, modifiers, export_settings) + ) + + return mesh + + +def __filter_mesh(blender_mesh: bpy.types.Mesh, + vertex_groups: Optional[bpy.types.VertexGroups], + modifiers: Optional[bpy.types.ObjectModifiers], + export_settings + ) -> bool: + if blender_mesh.users == 0: + return False + return True + + +def __gather_extensions(blender_mesh: bpy.types.Mesh, + vertex_groups: Optional[bpy.types.VertexGroups], + modifiers: Optional[bpy.types.ObjectModifiers], + export_settings + ) -> Any: + return None + + +def __gather_extras(blender_mesh: bpy.types.Mesh, + vertex_groups: Optional[bpy.types.VertexGroups], + modifiers: Optional[bpy.types.ObjectModifiers], + export_settings + ) -> Optional[Dict[Any, Any]]: + if export_settings['gltf_extras']: + return gltf2_blender_generate_extras.generate_extras(blender_mesh) + return None + + +def __gather_name(blender_mesh: bpy.types.Mesh, + vertex_groups: Optional[bpy.types.VertexGroups], + modifiers: Optional[bpy.types.ObjectModifiers], + export_settings + ) -> str: + return blender_mesh.name + + +def __gather_primitives(blender_mesh: bpy.types.Mesh, + vertex_groups: Optional[bpy.types.VertexGroups], + modifiers: Optional[bpy.types.ObjectModifiers], + export_settings + ) -> List[gltf2_io.MeshPrimitive]: + return gltf2_blender_gather_primitives.gather_primitives(blender_mesh, vertex_groups, modifiers, export_settings) + + +def __gather_weights(blender_mesh: bpy.types.Mesh, + vertex_groups: Optional[bpy.types.VertexGroups], + modifiers: Optional[bpy.types.ObjectModifiers], + export_settings + ) -> Optional[List[float]]: + return None + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py new file mode 100755 index 00000000..f6533c14 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py @@ -0,0 +1,240 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import bpy +from mathutils import Quaternion + +from . import gltf2_blender_export_keys +from io_scene_gltf2.blender.com import gltf2_blender_math +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins +from io_scene_gltf2.blender.exp import gltf2_blender_gather_cameras +from io_scene_gltf2.blender.exp import gltf2_blender_gather_mesh +from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints +from io_scene_gltf2.blender.exp import gltf2_blender_extract +from io_scene_gltf2.blender.exp import gltf2_blender_gather_lights +from io_scene_gltf2.blender.exp import gltf2_blender_generate_extras +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.io.com import gltf2_io_extensions + + +@cached +def gather_node(blender_object, export_settings): + if not __filter_node(blender_object, export_settings): + return None + + node = gltf2_io.Node( + camera=__gather_camera(blender_object, export_settings), + children=__gather_children(blender_object, export_settings), + extensions=__gather_extensions(blender_object, export_settings), + extras=__gather_extras(blender_object, export_settings), + matrix=__gather_matrix(blender_object, export_settings), + mesh=__gather_mesh(blender_object, export_settings), + name=__gather_name(blender_object, export_settings), + rotation=None, + scale=None, + skin=__gather_skin(blender_object, export_settings), + translation=None, + weights=__gather_weights(blender_object, export_settings) + ) + node.translation, node.rotation, node.scale = __gather_trans_rot_scale(blender_object, export_settings) + + if export_settings[gltf2_blender_export_keys.YUP]: + if blender_object.type == 'LIGHT' and export_settings[gltf2_blender_export_keys.LIGHTS]: + correction_node = __get_correction_node(blender_object, export_settings) + correction_node.extensions = {"KHR_lights_punctual": node.extensions["KHR_lights_punctual"]} + del node.extensions["KHR_lights_punctual"] + node.children.append(correction_node) + if blender_object.type == 'CAMERA' and export_settings[gltf2_blender_export_keys.CAMERAS]: + correction_node = __get_correction_node(blender_object, export_settings) + correction_node.camera = node.camera + node.children.append(correction_node) + node.camera = None + + return node + + +def __filter_node(blender_object, export_settings): + if blender_object.users == 0: + return False + if export_settings[gltf2_blender_export_keys.SELECTED] and blender_object.select_get() is False: + return False + if not export_settings[gltf2_blender_export_keys.LAYERS] and not blender_object.layers[0]: + return False + if blender_object.instance_collection is not None and not blender_object.instance_collection.layers[0]: + return False + + return True + + +def __gather_camera(blender_object, export_settings): + return gltf2_blender_gather_cameras.gather_camera(blender_object, export_settings) + + +def __gather_children(blender_object, export_settings): + children = [] + # standard children + for child_object in blender_object.children: + node = gather_node(child_object, export_settings) + if node is not None: + children.append(node) + # blender dupli objects + if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection: + for dupli_object in blender_object.instance_collection.objects: + node = gather_node(dupli_object, export_settings) + if node is not None: + children.append(node) + + # blender bones + if blender_object.type == "ARMATURE": + for blender_bone in blender_object.pose.bones: + if not blender_bone.parent: + children.append(gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings)) + + return children + + +def __gather_extensions(blender_object, export_settings): + extensions = {} + + if export_settings["gltf_lights"] and (blender_object.type == "LAMP" or blender_object.type == "LIGHT"): + blender_lamp = blender_object.data + light = gltf2_blender_gather_lights.gather_lights_punctual( + blender_lamp, + export_settings + ) + if light is not None: + light_extension = gltf2_io_extensions.ChildOfRootExtension( + name="KHR_lights_punctual", + path=["lights"], + extension=light + ) + extensions["KHR_lights_punctual"] = gltf2_io_extensions.Extension( + name="KHR_lights_punctual", + extension={ + "light": light_extension + } + ) + + return extensions if extensions else None + + +def __gather_extras(blender_object, export_settings): + if export_settings['gltf_extras']: + return gltf2_blender_generate_extras.generate_extras(blender_object) + return None + + +def __gather_matrix(blender_object, export_settings): + # return blender_object.matrix_local + return [] + + +def __gather_mesh(blender_object, export_settings): + if blender_object.type != "MESH": + return None + + # If not using vertex group, they are irrelevant for caching --> ensure that they do not trigger a cache miss + vertex_groups = blender_object.vertex_groups + modifiers = blender_object.modifiers + if len(vertex_groups) == 0: + vertex_groups = None + if len(modifiers) == 0: + modifiers = None + + if export_settings[gltf2_blender_export_keys.APPLY]: + auto_smooth = blender_object.data.use_auto_smooth + if auto_smooth: + blender_object = blender_object.copy() + edge_split = blender_object.modifiers.new('Temporary_Auto_Smooth', 'EDGE_SPLIT') + edge_split.split_angle = blender_object.data.auto_smooth_angle + edge_split.use_edge_angle = not blender_object.data.has_custom_normals + + blender_mesh = blender_object.to_mesh(bpy.context.depsgraph, True) + skip_filter = True + + if auto_smooth: + bpy.data.objects.remove(blender_object) + else: + blender_mesh = blender_object.data + skip_filter = False + + result = gltf2_blender_gather_mesh.gather_mesh(blender_mesh, vertex_groups, modifiers, skip_filter, export_settings) + + if export_settings[gltf2_blender_export_keys.APPLY]: + bpy.data.meshes.remove(blender_mesh) + + return result + + +def __gather_name(blender_object, export_settings): + if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection: + return "Duplication_Offset_" + blender_object.name + return blender_object.name + + +def __gather_trans_rot_scale(blender_object, export_settings): + trans, rot, sca = gltf2_blender_extract.decompose_transition(blender_object.matrix_local, 'NODE', export_settings) + if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection: + trans = -gltf2_blender_extract.convert_swizzle_location( + blender_object.instance_collection.instance_offset, export_settings) + translation, rotation, scale = (None, None, None) + trans[0], trans[1], trans[2] = gltf2_blender_math.round_if_near(trans[0], 0.0), gltf2_blender_math.round_if_near(trans[1], 0.0), \ + gltf2_blender_math.round_if_near(trans[2], 0.0) + rot[0], rot[1], rot[2], rot[3] = gltf2_blender_math.round_if_near(rot[0], 0.0), gltf2_blender_math.round_if_near(rot[1], 0.0), \ + gltf2_blender_math.round_if_near(rot[2], 0.0), gltf2_blender_math.round_if_near(rot[3], 1.0) + sca[0], sca[1], sca[2] = gltf2_blender_math.round_if_near(sca[0], 1.0), gltf2_blender_math.round_if_near(sca[1], 1.0), \ + gltf2_blender_math.round_if_near(sca[2], 1.0) + if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0: + translation = [trans[0], trans[1], trans[2]] + if rot[0] != 0.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 1.0: + rotation = [rot[0], rot[1], rot[2], rot[3]] + if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0: + scale = [sca[0], sca[1], sca[2]] + return translation, rotation, scale + + +def __gather_skin(blender_object, export_settings): + modifiers = {m.type: m for m in blender_object.modifiers} + + if "ARMATURE" in modifiers: + # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures + return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, export_settings) + + +def __gather_weights(blender_object, export_settings): + return None + + +def __get_correction_node(blender_object, export_settings): + correction_quaternion = gltf2_blender_extract.convert_swizzle_rotation( + Quaternion((1.0, 0.0, 0.0), math.radians(-90.0)), export_settings) + correction_quaternion = [correction_quaternion[1], correction_quaternion[2], + correction_quaternion[3], correction_quaternion[0]] + return gltf2_io.Node( + camera=None, + children=None, + extensions=None, + extras=None, + matrix=None, + mesh=None, + name=blender_object.name + '_Orientation', + rotation=correction_quaternion, + scale=None, + skin=None, + translation=None, + weights=None + ) + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py new file mode 100755 index 00000000..76a1f0e4 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py @@ -0,0 +1,217 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import gltf2_blender_export_keys +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.io.com import gltf2_io_constants +from io_scene_gltf2.io.com import gltf2_io_debug +from io_scene_gltf2.io.exp import gltf2_io_binary_data +from io_scene_gltf2.blender.exp import gltf2_blender_utils + + +def gather_primitive_attributes(blender_primitive, export_settings): + """ + Gathers the attributes, such as POSITION, NORMAL, TANGENT from a blender primitive. + + :return: a dictionary of attributes + """ + attributes = {} + attributes.update(__gather_position(blender_primitive, export_settings)) + attributes.update(__gather_normal(blender_primitive, export_settings)) + attributes.update(__gather_tangent(blender_primitive, export_settings)) + attributes.update(__gather_texcoord(blender_primitive, export_settings)) + attributes.update(__gather_colors(blender_primitive, export_settings)) + attributes.update(__gather_skins(blender_primitive, export_settings)) + return attributes + + +def __gather_position(blender_primitive, export_settings): + position = blender_primitive["attributes"]["POSITION"] + componentType = gltf2_io_constants.ComponentType.Float + return { + "POSITION": gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData.from_list(position, componentType), + byte_offset=None, + component_type=componentType, + count=len(position) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec3), + extensions=None, + extras=None, + max=gltf2_blender_utils.max_components(position, gltf2_io_constants.DataType.Vec3), + min=gltf2_blender_utils.min_components(position, gltf2_io_constants.DataType.Vec3), + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec3 + ) + } + + +def __gather_normal(blender_primitive, export_settings): + if export_settings[gltf2_blender_export_keys.NORMALS]: + normal = blender_primitive["attributes"]['NORMAL'] + return { + "NORMAL": gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData.from_list(normal, gltf2_io_constants.ComponentType.Float), + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(normal) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec3), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec3 + ) + } + return {} + + +def __gather_tangent(blender_primitive, export_settings): + if export_settings[gltf2_blender_export_keys.TANGENTS]: + if blender_primitive["attributes"].get('TANGENT') is not None: + tangent = blender_primitive["attributes"]['TANGENT'] + return { + "TANGENT": gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData.from_list( + tangent, gltf2_io_constants.ComponentType.Float), + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(tangent) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec4), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec4 + ) + } + + return {} + + +def __gather_texcoord(blender_primitive, export_settings): + attributes = {} + if export_settings[gltf2_blender_export_keys.TEX_COORDS]: + tex_coord_index = 0 + tex_coord_id = 'TEXCOORD_' + str(tex_coord_index) + while blender_primitive["attributes"].get(tex_coord_id) is not None: + tex_coord = blender_primitive["attributes"][tex_coord_id] + attributes[tex_coord_id] = gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData.from_list( + tex_coord, gltf2_io_constants.ComponentType.Float), + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(tex_coord) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec2), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec2 + ) + tex_coord_index += 1 + tex_coord_id = 'TEXCOORD_' + str(tex_coord_index) + return attributes + + +def __gather_colors(blender_primitive, export_settings): + attributes = {} + if export_settings[gltf2_blender_export_keys.COLORS]: + color_index = 0 + color_id = 'COLOR_' + str(color_index) + while blender_primitive["attributes"].get(color_id) is not None: + internal_color = blender_primitive["attributes"][color_id] + attributes[color_id] = gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData.from_list( + internal_color, gltf2_io_constants.ComponentType.Float), + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(internal_color) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec4), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec4 + ) + color_index += 1 + color_id = 'COLOR_' + str(color_index) + return attributes + + +def __gather_skins(blender_primitive, export_settings): + attributes = {} + if export_settings[gltf2_blender_export_keys.SKINS]: + bone_index = 0 + joint_id = 'JOINTS_' + str(bone_index) + weight_id = 'WEIGHTS_' + str(bone_index) + while blender_primitive["attributes"].get(joint_id) and blender_primitive["attributes"].get(weight_id): + if bone_index >= 4: + gltf2_io_debug.print_console("WARNING", "There are more than 4 joint vertex influences." + "Consider to apply blenders Limit Total function.") + if not export_settings['gltf_all_vertex_influences']: + break + + # joints + internal_joint = blender_primitive["attributes"][joint_id] + joint = gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData.from_list( + internal_joint, gltf2_io_constants.ComponentType.UnsignedShort), + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.UnsignedShort, + count=len(internal_joint) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Vec4), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec4 + ) + attributes[joint_id] = joint + + # weights + internal_weight = blender_primitive["attributes"][weight_id] + weight = gltf2_io.Accessor( + buffer_view=gltf2_io_binary_data.BinaryData.from_list( + internal_weight, gltf2_io_constants.ComponentType.Float), + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(internal_weight) // gltf2_io_constants.DataType.num_elements( + gltf2_io_constants.DataType.Vec4), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec4 + ) + attributes[weight_id] = weight + + bone_index += 1 + joint_id = 'JOINTS_' + str(bone_index) + weight_id = 'WEIGHTS_' + str(bone_index) + return attributes + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py new file mode 100755 index 00000000..4fa025b2 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py @@ -0,0 +1,197 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from typing import List, Optional + +from .gltf2_blender_export_keys import NORMALS, MORPH_NORMAL, TANGENTS, MORPH_TANGENT, MORPH + +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.blender.exp import gltf2_blender_extract +from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitive_attributes +from io_scene_gltf2.blender.exp import gltf2_blender_utils +from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials + +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.io.exp import gltf2_io_binary_data +from io_scene_gltf2.io.com import gltf2_io_constants +from io_scene_gltf2.io.com.gltf2_io_debug import print_console + + +@cached +def gather_primitives( + blender_mesh: bpy.types.Mesh, + vertex_groups: Optional[bpy.types.VertexGroups], + modifiers: Optional[bpy.types.ObjectModifiers], + export_settings + ) -> List[gltf2_io.MeshPrimitive]: + """ + Extract the mesh primitives from a blender object + + :return: a list of glTF2 primitives + """ + primitives = [] + blender_primitives = gltf2_blender_extract.extract_primitives( + None, blender_mesh, vertex_groups, modifiers, export_settings) + + for internal_primitive in blender_primitives: + + primitive = gltf2_io.MeshPrimitive( + attributes=__gather_attributes(internal_primitive, blender_mesh, modifiers, export_settings), + extensions=None, + extras=None, + indices=__gather_indices(internal_primitive, blender_mesh, modifiers, export_settings), + material=__gather_materials(internal_primitive, blender_mesh, modifiers, export_settings), + mode=None, + targets=__gather_targets(internal_primitive, blender_mesh, modifiers, export_settings) + ) + primitives.append(primitive) + + return primitives + + +def __gather_materials(blender_primitive, blender_mesh, modifiers, export_settings): + if not blender_primitive['material']: + # TODO: fix 'extract_promitives' so that the value of 'material' is None and not empty string + return None + material = bpy.data.materials[blender_primitive['material']] + return gltf2_blender_gather_materials.gather_material(material, export_settings) + + +def __gather_indices(blender_primitive, blender_mesh, modifiers, export_settings): + indices = blender_primitive['indices'] + + max_index = max(indices) + if max_index < (1 << 8): + component_type = gltf2_io_constants.ComponentType.UnsignedByte + elif max_index < (1 << 16): + component_type = gltf2_io_constants.ComponentType.UnsignedShort + elif max_index < (1 << 32): + component_type = gltf2_io_constants.ComponentType.UnsignedInt + else: + print_console('ERROR', 'Invalid max_index: ' + str(max_index)) + return None + + element_type = gltf2_io_constants.DataType.Scalar + binary_data = gltf2_io_binary_data.BinaryData.from_list(indices, component_type) + return gltf2_io.Accessor( + buffer_view=binary_data, + byte_offset=None, + component_type=component_type, + count=len(indices) // gltf2_io_constants.DataType.num_elements(element_type), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=element_type + ) + + +def __gather_attributes(blender_primitive, blender_mesh, modifiers, export_settings): + return gltf2_blender_gather_primitive_attributes.gather_primitive_attributes(blender_primitive, export_settings) + + +def __gather_targets(blender_primitive, blender_mesh, modifiers, export_settings): + if export_settings[MORPH]: + targets = [] + if blender_mesh.shape_keys is not None: + morph_index = 0 + for blender_shape_key in blender_mesh.shape_keys.key_blocks: + if blender_shape_key != blender_shape_key.relative_key: + + target_position_id = 'MORPH_POSITION_' + str(morph_index) + target_normal_id = 'MORPH_NORMAL_' + str(morph_index) + target_tangent_id = 'MORPH_TANGENT_' + str(morph_index) + + if blender_primitive["attributes"].get(target_position_id): + target = {} + internal_target_position = blender_primitive["attributes"][target_position_id] + binary_data = gltf2_io_binary_data.BinaryData.from_list( + internal_target_position, + gltf2_io_constants.ComponentType.Float + ) + target["POSITION"] = gltf2_io.Accessor( + buffer_view=binary_data, + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(internal_target_position) // gltf2_io_constants.DataType.num_elements( + gltf2_io_constants.DataType.Vec3), + extensions=None, + extras=None, + max=gltf2_blender_utils.max_components( + internal_target_position, gltf2_io_constants.DataType.Vec3), + min=gltf2_blender_utils.min_components( + internal_target_position, gltf2_io_constants.DataType.Vec3), + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec3 + ) + + if export_settings[NORMALS] \ + and export_settings[MORPH_NORMAL] \ + and blender_primitive["attributes"].get(target_normal_id): + + internal_target_normal = blender_primitive["attributes"][target_normal_id] + binary_data = gltf2_io_binary_data.BinaryData.from_list( + internal_target_normal, + gltf2_io_constants.ComponentType.Float, + ) + target['NORMAL'] = gltf2_io.Accessor( + buffer_view=binary_data, + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(internal_target_normal) // gltf2_io_constants.DataType.num_elements( + gltf2_io_constants.DataType.Vec3), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec3 + ) + + if export_settings[TANGENTS] \ + and export_settings[MORPH_TANGENT] \ + and blender_primitive["attributes"].get(target_tangent_id): + internal_target_tangent = blender_primitive["attributes"][target_tangent_id] + binary_data = gltf2_io_binary_data.BinaryData.from_list( + internal_target_tangent, + gltf2_io_constants.ComponentType.Float, + ) + target['TANGENT'] = gltf2_io.Accessor( + buffer_view=binary_data, + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(internal_target_tangent) // gltf2_io_constants.DataType.num_elements( + gltf2_io_constants.DataType.Vec3), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Vec3 + ) + targets.append(target) + morph_index += 1 + return targets + return None + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_sampler.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_sampler.py new file mode 100755 index 00000000..840c98f4 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_sampler.py @@ -0,0 +1,98 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached + + +@cached +def gather_sampler(blender_shader_node: bpy.types.Node, export_settings): + if not __filter_sampler(blender_shader_node, export_settings): + return None + + return gltf2_io.Sampler( + extensions=__gather_extensions(blender_shader_node, export_settings), + extras=__gather_extras(blender_shader_node, export_settings), + mag_filter=__gather_mag_filter(blender_shader_node, export_settings), + min_filter=__gather_min_filter(blender_shader_node, export_settings), + name=__gather_name(blender_shader_node, export_settings), + wrap_s=__gather_wrap_s(blender_shader_node, export_settings), + wrap_t=__gather_wrap_t(blender_shader_node, export_settings) + ) + + +def __filter_sampler(blender_shader_node, export_settings): + if not blender_shader_node.interpolation == 'Closest' and not blender_shader_node.extension == 'CLIP': + return False + return True + + +def __gather_extensions(blender_shader_node, export_settings): + return None + + +def __gather_extras(blender_shader_node, export_settings): + return None + + +def __gather_mag_filter(blender_shader_node, export_settings): + if blender_shader_node.interpolation == 'Closest': + return 9728 # NEAREST + return 9729 # LINEAR + + +def __gather_min_filter(blender_shader_node, export_settings): + if blender_shader_node.interpolation == 'Closest': + return 9984 # NEAREST_MIPMAP_NEAREST + return 9986 # NEAREST_MIPMAP_LINEAR + + +def __gather_name(blender_shader_node, export_settings): + return None + + +def __gather_wrap_s(blender_shader_node, export_settings): + if blender_shader_node.extension == 'CLIP': + return 33071 + return None + + +def __gather_wrap_t(blender_shader_node, export_settings): + if blender_shader_node.extension == 'CLIP': + return 33071 + return None + + +@cached +def gather_sampler_from_texture_slot(blender_texture: bpy.types.TextureSlot, export_settings): + magFilter = 9729 + wrap = 10497 + if blender_texture.texture.extension == 'CLIP': + wrap = 33071 + + minFilter = 9986 + if magFilter == 9728: + minFilter = 9984 + + return gltf2_io.Sampler( + extensions=None, + extras=None, + mag_filter=magFilter, + min_filter=minFilter, + name=None, + wrap_s=wrap, + wrap_t=wrap + ) + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py new file mode 100755 index 00000000..84703414 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py @@ -0,0 +1,150 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mathutils +from . import gltf2_blender_export_keys +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.io.exp import gltf2_io_binary_data +from io_scene_gltf2.io.com import gltf2_io_constants +from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints +from io_scene_gltf2.blender.com import gltf2_blender_math + + +@cached +def gather_skin(blender_object, export_settings): + """ + Gather armatures, bones etc into a glTF2 skin object. + + :param blender_object: the object which may contain a skin + :param export_settings: + :return: a glTF2 skin object + """ + if not __filter_skin(blender_object, export_settings): + return None + + return gltf2_io.Skin( + extensions=__gather_extensions(blender_object, export_settings), + extras=__gather_extras(blender_object, export_settings), + inverse_bind_matrices=__gather_inverse_bind_matrices(blender_object, export_settings), + joints=__gather_joints(blender_object, export_settings), + name=__gather_name(blender_object, export_settings), + skeleton=__gather_skeleton(blender_object, export_settings) + ) + + +def __filter_skin(blender_object, export_settings): + if not export_settings[gltf2_blender_export_keys.SKINS]: + return False + if blender_object.type != 'ARMATURE' or len(blender_object.pose.bones) == 0: + return False + + return True + + +def __gather_extensions(blender_object, export_settings): + return None + + +def __gather_extras(blender_object, export_settings): + return None + + +def __gather_inverse_bind_matrices(blender_object, export_settings): + inverse_matrices = [] + + axis_basis_change = mathutils.Matrix.Identity(4) + if export_settings[gltf2_blender_export_keys.YUP]: + axis_basis_change = mathutils.Matrix( + ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0))) + + # # artificial torso, as needed by glTF + # inverse_bind_matrix = blender_object.matrix_world.inverted() * axis_basis_change.inverted() + # for column in range(0, 4): + # for row in range(0, 4): + # inverse_matrices.append(inverse_bind_matrix[row][column]) + + # + for blender_bone in blender_object.pose.bones: + inverse_bind_matrix = gltf2_blender_math.multiply(axis_basis_change, blender_bone.bone.matrix_local) + bind_shape_matrix = gltf2_blender_math.multiply(gltf2_blender_math.multiply( + axis_basis_change, blender_object.matrix_world.inverted()), axis_basis_change.inverted()) + + inverse_bind_matrix = gltf2_blender_math.multiply(inverse_bind_matrix.inverted(), bind_shape_matrix) + for column in range(0, 4): + for row in range(0, 4): + inverse_matrices.append(inverse_bind_matrix[row][column]) + + binary_data = gltf2_io_binary_data.BinaryData.from_list(inverse_matrices, gltf2_io_constants.ComponentType.Float) + return gltf2_io.Accessor( + buffer_view=binary_data, + byte_offset=None, + component_type=gltf2_io_constants.ComponentType.Float, + count=len(inverse_matrices) // gltf2_io_constants.DataType.num_elements(gltf2_io_constants.DataType.Mat4), + extensions=None, + extras=None, + max=None, + min=None, + name=None, + normalized=None, + sparse=None, + type=gltf2_io_constants.DataType.Mat4 + ) + + +def __gather_joints(blender_object, export_settings): + # # the skeletal hierarchy groups below a 'root' joint + # # TODO: add transform? + # torso = gltf2_io.Node( + # camera=None, + # children=[], + # extensions={}, + # extras=None, + # matrix=[], + # mesh=None, + # name="Skeleton_" + blender_object.name, + # rotation=None, + # scale=None, + # skin=None, + # translation=None, + # weights=None + # ) + + root_joints = [] + # build the hierarchy of nodes out of the bones + for blender_bone in blender_object.pose.bones: + if not blender_bone.parent: + root_joints.append(gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings)) + + # joints is a flat list containing all nodes belonging to the skin + joints = [] + + def __collect_joints(node): + joints.append(node) + for child in node.children: + __collect_joints(child) + for joint in root_joints: + __collect_joints(joint) + + return joints + + +def __gather_name(blender_object, export_settings): + return blender_object.name + + +def __gather_skeleton(blender_object, export_settings): + # In the future support the result of https://github.com/KhronosGroup/glTF/pull/1195 + return None # gltf2_blender_gather_nodes.gather_node(blender_object, export_settings) + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py new file mode 100755 index 00000000..93db33f9 --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture.py @@ -0,0 +1,100 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing +import bpy +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached + +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_sampler +from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree +from io_scene_gltf2.blender.exp import gltf2_blender_gather_image +from io_scene_gltf2.io.com import gltf2_io_debug + + +@cached +def gather_texture( + blender_shader_sockets_or_texture_slots: typing.Union[ + typing.Tuple[bpy.types.NodeSocket], typing.Tuple[typing.Any]], + export_settings): + """ + Gather texture sampling information and image channels from a blender shader textu re attached to a shader socket. + + :param blender_shader_sockets: The sockets of the material which should contribute to the texture + :param export_settings: configuration of the export + :return: a glTF 2.0 texture with sampler and source embedded (will be converted to references by the exporter) + """ + # TODO: extend to texture slots + if not __filter_texture(blender_shader_sockets_or_texture_slots, export_settings): + return None + + return gltf2_io.Texture( + extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings), + extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings), + name=__gather_name(blender_shader_sockets_or_texture_slots, export_settings), + sampler=__gather_sampler(blender_shader_sockets_or_texture_slots, export_settings), + source=__gather_source(blender_shader_sockets_or_texture_slots, export_settings) + ) + + +def __filter_texture(blender_shader_sockets_or_texture_slots, export_settings): + return True + + +def __gather_extensions(blender_shader_sockets, export_settings): + return None + + +def __gather_extras(blender_shader_sockets, export_settings): + return None + + +def __gather_name(blender_shader_sockets, export_settings): + return None + + +def __gather_sampler(blender_shader_sockets_or_texture_slots, export_settings): + if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket): + shader_nodes = [__get_tex_from_socket(socket).shader_node for socket in blender_shader_sockets_or_texture_slots] + if len(shader_nodes) > 1: + gltf2_io_debug.print_console("WARNING", + "More than one shader node tex image used for a texture. " + "The resulting glTF sampler will behave like the first shader node tex image.") + return gltf2_blender_gather_sampler.gather_sampler( + shader_nodes[0], + export_settings) + elif isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.MaterialTextureSlot): + return gltf2_blender_gather_sampler.gather_sampler_from_texture_slot( + blender_shader_sockets_or_texture_slots[0], + export_settings + ) + else: + # TODO: implement texture slot sampler + raise NotImplementedError() + + +def __gather_source(blender_shader_sockets_or_texture_slots, export_settings): + return gltf2_blender_gather_image.gather_image(blender_shader_sockets_or_texture_slots, export_settings) + +# Helpers + + +def __get_tex_from_socket(socket): + result = gltf2_blender_search_node_tree.from_socket( + socket, + gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage)) + if not result: + return None + return result[0] + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py new file mode 100755 index 00000000..600bf81e --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py @@ -0,0 +1,119 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import typing +from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture +from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree +from io_scene_gltf2.blender.exp import gltf2_blender_export_keys +from io_scene_gltf2.blender.exp import gltf2_blender_get +from io_scene_gltf2.io.com.gltf2_io_extensions import Extension + + +@cached +def gather_texture_info(blender_shader_sockets_or_texture_slots: typing.Union[ + typing.Tuple[bpy.types.NodeSocket], typing.Tuple[bpy.types.Texture]], + export_settings): + if not __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings): + return None + + texture_info = gltf2_io.TextureInfo( + extensions=__gather_extensions(blender_shader_sockets_or_texture_slots, export_settings), + extras=__gather_extras(blender_shader_sockets_or_texture_slots, export_settings), + index=__gather_index(blender_shader_sockets_or_texture_slots, export_settings), + tex_coord=__gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings) + ) + + return texture_info + + +def __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settings): + if not blender_shader_sockets_or_texture_slots: + return False + if not all([elem is not None for elem in blender_shader_sockets_or_texture_slots]): + return False + if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket): + if any([__get_tex_from_socket(socket) is None for socket in blender_shader_sockets_or_texture_slots]): + # sockets do not lead to a texture --> discard + return False + return True + + +def __gather_extensions(blender_shader_sockets_or_texture_slots, export_settings): + if not export_settings[gltf2_blender_export_keys.TEXTURE_TRANSFORM]: + return None + + texture_node = blender_shader_sockets_or_texture_slots[0].links[0].from_node + texture_transform = gltf2_blender_get.get_texture_transform_from_texture_node(texture_node) + if texture_transform is None: + return None + + extension = Extension("KHR_texture_transform", texture_transform) + return {"KHR_texture_transform": extension} + + +def __gather_extras(blender_shader_sockets_or_texture_slots, export_settings): + return None + + +def __gather_index(blender_shader_sockets_or_texture_slots, export_settings): + # We just put the actual shader into the 'index' member + return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets_or_texture_slots, export_settings) + + +def __gather_tex_coord(blender_shader_sockets_or_texture_slots, export_settings): + if isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.NodeSocket): + blender_shader_node = __get_tex_from_socket(blender_shader_sockets_or_texture_slots[0]).shader_node + if len(blender_shader_node.inputs['Vector'].links) == 0: + return 0 + + input_node = blender_shader_node.inputs['Vector'].links[0].from_node + + if isinstance(input_node, bpy.types.ShaderNodeMapping): + + if len(input_node.inputs['Vector'].links) == 0: + return 0 + + input_node = input_node.inputs['Vector'].links[0].from_node + + if not isinstance(input_node, bpy.types.ShaderNodeUVMap): + return 0 + + if input_node.uv_map == '': + return 0 + + # Try to gather map index. + for blender_mesh in bpy.data.meshes: + texCoordIndex = blender_mesh.uv_layers.find(input_node.uv_map) + if texCoordIndex >= 0: + return texCoordIndex + + return 0 + elif isinstance(blender_shader_sockets_or_texture_slots[0], bpy.types.MaterialTextureSlot): + # TODO: implement for texture slots + return 0 + else: + raise NotImplementedError() + + +def __get_tex_from_socket(socket): + result = gltf2_blender_search_node_tree.from_socket( + socket, + gltf2_blender_search_node_tree.FilterByType(bpy.types.ShaderNodeTexImage)) + if not result: + return None + return result[0] + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_generate_extras.py b/io_scene_gltf2/blender/exp/gltf2_blender_generate_extras.py new file mode 100755 index 00000000..c26c494a --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_generate_extras.py @@ -0,0 +1,64 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import bpy +from io_scene_gltf2.blender.com import gltf2_blender_json + + +def generate_extras(blender_element): + """Filter and create a custom property, which is stored in the glTF extra field.""" + if not blender_element: + return None + + extras = {} + + # Custom properties, which are in most cases present and should not be exported. + black_list = ['cycles', 'cycles_visibility', 'cycles_curves', '_RNA_UI'] + + count = 0 + for custom_property in blender_element.keys(): + if custom_property in black_list: + continue + + value = blender_element[custom_property] + + add_value = False + + if isinstance(value, bpy.types.ID): + add_value = True + + if isinstance(value, str): + add_value = True + + if isinstance(value, (int, float)): + add_value = True + + if hasattr(value, "to_list"): + value = value.to_list() + add_value = True + + if hasattr(value, "to_dict"): + value = value.to_dict() + add_value = gltf2_blender_json.is_json_convertible(value) + + if add_value: + extras[custom_property] = value + count += 1 + + if count == 0: + return None + + return extras + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_get.py b/io_scene_gltf2/blender/exp/gltf2_blender_get.py new file mode 100755 index 00000000..7801190b --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_get.py @@ -0,0 +1,423 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +import bpy + +from . import gltf2_blender_export_keys +from ...io.exp import gltf2_io_get +from io_scene_gltf2.io.com import gltf2_io_debug +# +# Globals +# + +# +# Functions +# + + +def get_animation_target(action_group: bpy.types.ActionGroup): + return action_group.channels[0].data_path.split('.')[-1] + + +def get_socket_or_texture_slot(blender_material: bpy.types.Material, name: str): + """ + For a given material input name, retrieve the corresponding node tree socket or blender render texture slot. + + :param blender_material: a blender material for which to get the socket/slot + :param name: the name of the socket/slot + :return: either a blender NodeSocket, if the material is a node tree or a blender Texture otherwise + """ + if blender_material.node_tree and blender_material.use_nodes: + #i = [input for input in blender_material.node_tree.inputs] + #o = [output for output in blender_material.node_tree.outputs] + if name == "Emissive": + type = bpy.types.ShaderNodeEmission + name = "Color" + else: + type = bpy.types.ShaderNodeBsdfPrincipled + nodes = [n for n in blender_material.node_tree.nodes if isinstance(n, type)] + inputs = sum([[input for input in node.inputs if input.name == name] for node in nodes], []) + if inputs: + return inputs[0] + + + + return None + + +def get_socket_or_texture_slot_old(blender_material: bpy.types.Material, name: str): + """ + For a given material input name, retrieve the corresponding node tree socket in the special glTF Metallic Roughness nodes (which might be deprecated?). + + :param blender_material: a blender material for which to get the socket/slot + :param name: the name of the socket/slot + :return: either a blender NodeSocket, if the material is a node tree or a blender Texture otherwise + """ + if blender_material.node_tree and blender_material.use_nodes: + nodes = [n for n in blender_material.node_tree.nodes if \ + isinstance(n, bpy.types.ShaderNodeGroup) and \ + n.node_tree.name.startswith('glTF Metallic Roughness')] + inputs = sum([[input for input in node.inputs if input.name == name] for node in nodes], []) + if inputs: + return inputs[0] + + return None + + +def find_shader_image_from_shader_socket(shader_socket, max_hops=10): + """Find any ShaderNodeTexImage in the path from the socket.""" + if shader_socket is None: + return None + + if max_hops <= 0: + return None + + for link in shader_socket.links: + if isinstance(link.from_node, bpy.types.ShaderNodeTexImage): + return link.from_node + + for socket in link.from_node.inputs.values(): + image = find_shader_image_from_shader_socket(shader_socket=socket, max_hops=max_hops - 1) + if image is not None: + return image + + return None + + +def get_shader_add_to_shader_node(shader_node): + + if shader_node is None: + return None + + if len(shader_node.outputs['BSDF'].links) == 0: + return None + + to_node = shader_node.outputs['BSDF'].links[0].to_node + + if not isinstance(to_node, bpy.types.ShaderNodeAddShader): + return None + + return to_node + +# + + +def get_shader_emission_from_shader_add(shader_add): + + if shader_add is None: + return None + + if not isinstance(shader_add, bpy.types.ShaderNodeAddShader): + return None + + from_node = None + + for input in shader_add.inputs: + + if len(input.links) == 0: + continue + + from_node = input.links[0].from_node + + if isinstance(from_node, bpy.types.ShaderNodeEmission): + break + + return from_node + + +def get_shader_mapping_from_shader_image(shader_image): + + if shader_image is None: + return None + + if not isinstance(shader_image, bpy.types.ShaderNodeTexImage): + return None + + if shader_image.inputs.get('Vector') is None: + return None + + if len(shader_image.inputs['Vector'].links) == 0: + return None + + from_node = shader_image.inputs['Vector'].links[0].from_node + + # + + if not isinstance(from_node, bpy.types.ShaderNodeMapping): + return None + + return from_node + + +def get_image_material_usage_to_socket(shader_image, socket_name): + if shader_image is None: + return -1 + + if not isinstance(shader_image, bpy.types.ShaderNodeTexImage): + return -2 + + if shader_image.outputs.get('Color') is None: + return -3 + + if len(shader_image.outputs.get('Color').links) == 0: + return -4 + + for img_link in shader_image.outputs.get('Color').links: + separate_rgb = img_link.to_node + + if not isinstance(separate_rgb, bpy.types.ShaderNodeSeparateRGB): + continue + + for i, channel in enumerate("RGB"): + if separate_rgb.outputs.get(channel) is None: + continue + for link in separate_rgb.outputs.get(channel).links: + if socket_name == link.to_socket.name: + return i + + return -6 + + +def get_emission_node_from_lamp_output_node(lamp_node): + if lamp_node is None: + return None + + if not isinstance(lamp_node, bpy.types.ShaderNodeOutputLamp): + return None + + if lamp_node.inputs.get('Surface') is None: + return None + + if len(lamp_node.inputs.get('Surface').links) == 0: + return None + + from_node = lamp_node.inputs.get('Surface').links[0].from_node + if isinstance(from_node, bpy.types.ShaderNodeEmission): + return from_node + + return None + + +def get_ligth_falloff_node_from_emission_node(emission_node, type): + if emission_node is None: + return None + + if not isinstance(emission_node, bpy.types.ShaderNodeEmission): + return None + + if emission_node.inputs.get('Strength') is None: + return None + + if len(emission_node.inputs.get('Strength').links) == 0: + return None + + from_node = emission_node.inputs.get('Strength').links[0].from_node + if not isinstance(from_node, bpy.types.ShaderNodeLightFalloff): + return None + + if from_node.outputs.get(type) is None: + return None + + if len(from_node.outputs.get(type).links) == 0: + return None + + if emission_node != from_node.outputs.get(type).links[0].to_node: + return None + + return from_node + + +def get_shader_image_from_shader_node(name, shader_node): + + if shader_node is None: + return None + + if not isinstance(shader_node, bpy.types.ShaderNodeGroup) and \ + not isinstance(shader_node, bpy.types.ShaderNodeBsdfPrincipled) and \ + not isinstance(shader_node, bpy.types.ShaderNodeEmission): + return None + + if shader_node.inputs.get(name) is None: + return None + + if len(shader_node.inputs[name].links) == 0: + return None + + from_node = shader_node.inputs[name].links[0].from_node + + # + + if isinstance(from_node, bpy.types.ShaderNodeNormalMap): + + name = 'Color' + + if len(from_node.inputs[name].links) == 0: + return None + + from_node = from_node.inputs[name].links[0].from_node + + # + + if not isinstance(from_node, bpy.types.ShaderNodeTexImage): + return None + + return from_node + + +def get_texture_index_from_shader_node(export_settings, glTF, name, shader_node): + """Return the texture index in the glTF array.""" + from_node = get_shader_image_from_shader_node(name, shader_node) + + if from_node is None: + return -1 + + # + + if from_node.image is None or from_node.image.size[0] == 0 or from_node.image.size[1] == 0: + return -1 + + return gltf2_io_get.get_texture_index(glTF, from_node.image.name) + + +def get_texture_index_from_export_settings(export_settings, name): + """Return the texture index in the glTF array.""" + + +def get_texcoord_index_from_shader_node(glTF, name, shader_node): + """Return the texture coordinate index, if assigned and used.""" + from_node = get_shader_image_from_shader_node(name, shader_node) + + if from_node is None: + return 0 + + # + + if len(from_node.inputs['Vector'].links) == 0: + return 0 + + input_node = from_node.inputs['Vector'].links[0].from_node + + # + + if isinstance(input_node, bpy.types.ShaderNodeMapping): + + if len(input_node.inputs['Vector'].links) == 0: + return 0 + + input_node = input_node.inputs['Vector'].links[0].from_node + + # + + if not isinstance(input_node, bpy.types.ShaderNodeUVMap): + return 0 + + if input_node.uv_map == '': + return 0 + + # + + # Try to gather map index. + for blender_mesh in bpy.data.meshes: + texCoordIndex = blender_mesh.uv_layers.find(input_node.uv_map) + if texCoordIndex >= 0: + return texCoordIndex + + return 0 + + +def get_texture_transform_from_texture_node(texture_node): + if not isinstance(texture_node, bpy.types.ShaderNodeTexImage): + return None + + mapping_socket = texture_node.inputs["Vector"] + if len(mapping_socket.links) == 0: + return None + + mapping_node = mapping_socket.links[0].from_node + if not isinstance(mapping_node, bpy.types.ShaderNodeMapping): + return None + + texture_transform = {} + if mapping_node.vector_type == 'TEXTURE': + texture_transform["offset"] = [-mapping_node.translation[0], -mapping_node.translation[1]] + texture_transform["rotation"] = -mapping_node.rotation[2] + texture_transform["scale"] = [1.0 / mapping_node.scale[0], 1.0 / mapping_node.scale[1]] + elif mapping_node.vector_type == 'POINT': + texture_transform["offset"] = [mapping_node.translation[0], mapping_node.translation[1]] + texture_transform["rotation"] = mapping_node.rotation[2] + texture_transform["scale"] = [mapping_node.scale[0], mapping_node.scale[1]] + + if all([component == 0 for component in texture_transform["offset"]]): + del(texture_transform["offset"]) + if all([component == 1 for component in texture_transform["scale"]]): + del(texture_transform["scale"]) + if texture_transform["rotation"] == 0: + del(texture_transform["rotation"]) + + return texture_transform + + +def get_image_uri(export_settings, blender_image): + """Return the final URI depending on a file path.""" + file_format = get_image_format(export_settings, blender_image) + extension = '.jpg' if file_format == 'JPEG' else '.png' + + return gltf2_io_get.get_image_name(blender_image.name) + extension + + +def get_image_format(export_settings, blender_image): + """ + Return the final output format of the given image. + + Only PNG and JPEG are supported as outputs - all other formats must be converted. + """ + if blender_image.file_format in ['PNG', 'JPEG']: + return blender_image.file_format + + use_alpha = export_settings[gltf2_blender_export_keys.FILTERED_IMAGES_USE_ALPHA].get(blender_image.name) + + return 'PNG' if use_alpha else 'JPEG' + + +def get_node(data_path): + """Return Blender node on a given Blender data path.""" + if data_path is None: + return None + + index = data_path.find("[\"") + if (index == -1): + return None + + node_name = data_path[(index + 2):] + + index = node_name.find("\"") + if (index == -1): + return None + + return node_name[:(index)] + + +def get_data_path(data_path): + """Return Blender data path.""" + index = data_path.rfind('.') + + if index == -1: + return data_path + + return data_path[(index + 1):] + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py new file mode 100755 index 00000000..65efafef --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py @@ -0,0 +1,301 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional, List, Dict + +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.io.com import gltf2_io_extensions +from io_scene_gltf2.io.exp import gltf2_io_binary_data +from io_scene_gltf2.io.exp import gltf2_io_image_data +from io_scene_gltf2.io.exp import gltf2_io_buffer + +import bpy +import os +from shutil import copyfile + +class GlTF2Exporter: + """ + The glTF exporter flattens a scene graph to a glTF serializable format. + + Any child properties are replaced with references where necessary + """ + + def __init__(self, copyright=None): + self.__finalized = False + + asset = gltf2_io.Asset( + copyright=copyright, + extensions=None, + extras=None, + generator='Khronos Blender glTF 2.0 I/O', + min_version=None, + version='2.0') + + self.__gltf = gltf2_io.Gltf( + accessors=[], + animations=[], + asset=asset, + buffers=[], + buffer_views=[], + cameras=[], + extensions={}, + extensions_required=[], + extensions_used=[], + extras=None, + images=[], + materials=[], + meshes=[], + nodes=[], + samplers=[], + scene=-1, + scenes=[], + skins=[], + textures=[] + ) + + self.__buffer = gltf2_io_buffer.Buffer() + self.__images = [] + + # mapping of all glTFChildOfRootProperty types to their corresponding root level arrays + self.__childOfRootPropertyTypeLookup = { + gltf2_io.Accessor: self.__gltf.accessors, + gltf2_io.Animation: self.__gltf.animations, + gltf2_io.Buffer: self.__gltf.buffers, + gltf2_io.BufferView: self.__gltf.buffer_views, + gltf2_io.Camera: self.__gltf.cameras, + gltf2_io.Image: self.__gltf.images, + gltf2_io.Material: self.__gltf.materials, + gltf2_io.Mesh: self.__gltf.meshes, + gltf2_io.Node: self.__gltf.nodes, + gltf2_io.Sampler: self.__gltf.samplers, + gltf2_io.Scene: self.__gltf.scenes, + gltf2_io.Skin: self.__gltf.skins, + gltf2_io.Texture: self.__gltf.textures + } + + self.__propertyTypeLookup = [ + gltf2_io.AccessorSparseIndices, + gltf2_io.AccessorSparse, + gltf2_io.AccessorSparseValues, + gltf2_io.AnimationChannel, + gltf2_io.AnimationChannelTarget, + gltf2_io.AnimationSampler, + gltf2_io.Asset, + gltf2_io.CameraOrthographic, + gltf2_io.CameraPerspective, + gltf2_io.MeshPrimitive, + gltf2_io.TextureInfo, + gltf2_io.MaterialPBRMetallicRoughness, + gltf2_io.MaterialNormalTextureInfoClass, + gltf2_io.MaterialOcclusionTextureInfoClass + ] + + @property + def glTF(self): + if not self.__finalized: + raise RuntimeError("glTF requested, but buffers are not finalized yet") + return self.__gltf + + def finalize_buffer(self, output_path=None, buffer_name=None, is_glb=False): + """Finalize the glTF and write buffers.""" + if self.__finalized: + raise RuntimeError("Tried to finalize buffers for finalized glTF file") + + if is_glb: + uri = None + elif output_path and buffer_name: + with open(output_path + buffer_name, 'wb') as f: + f.write(self.__buffer.to_bytes()) + uri = buffer_name + else: + uri = self.__buffer.to_embed_string() + + buffer = gltf2_io.Buffer( + byte_length=self.__buffer.byte_length, + extensions=None, + extras=None, + name=None, + uri=uri + ) + self.__gltf.buffers.append(buffer) + + self.__finalized = True + + if is_glb: + return self.__buffer.to_bytes() + + def finalize_images(self, output_path): + """ + Write all images. + + Due to a current limitation the output_path must be the same as that of the glTF file + :param output_path: + :return: + """ + for image in self.__images: + dst_path = output_path + image.name + image.get_extension() + src_path = bpy.path.abspath(image.filepath) + if os.path.isfile(src_path): + # Source file exists. + if os.path.abspath(dst_path) != os.path.abspath(src_path): + # Only copy, if source and destination are not the same. + copyfile(src_path, dst_path) + else: + # Source file does not exist e.g. it is packed or has been generated. + with open(dst_path, 'wb') as f: + f.write(image.to_png_data()) + + def add_scene(self, scene: gltf2_io.Scene, active: bool = True): + """ + Add a scene to the glTF. + + The scene should be built up with the generated glTF classes + :param scene: gltf2_io.Scene type. Root node of the scene graph + :param active: If true, sets the glTD.scene index to the added scene + :return: nothing + """ + if self.__finalized: + raise RuntimeError("Tried to add scene to finalized glTF file") + + # for node in scene.nodes: + # self.__traverse(node) + scene_num = self.__traverse(scene) + if active: + self.__gltf.scene = scene_num + + def add_animation(self, animation: gltf2_io.Animation): + """ + Add an animation to the glTF. + + :param animation: glTF animation, with python style references (names) + :return: nothing + """ + if self.__finalized: + raise RuntimeError("Tried to add animation to finalized glTF file") + + self.__traverse(animation) + + def __to_reference(self, property): + """ + Append a child of root property to its respective list and return a reference into said list. + + If the property is not child of root, the property itself is returned. + :param property: A property type object that should be converted to a reference + :return: a reference or the object itself if it is not child or root + """ + gltf_list = self.__childOfRootPropertyTypeLookup.get(type(property), None) + if gltf_list is None: + # The object is not of a child of root --> don't convert to reference + return property + + return self.__append_unique_and_get_index(gltf_list, property) + + @staticmethod + def __append_unique_and_get_index(target: list, obj): + if obj in target: + return target.index(obj) + else: + index = len(target) + target.append(obj) + return index + + def __add_image(self, image: gltf2_io_image_data.ImageData): + self.__images.append(image) + # TODO: we need to know the image url at this point already --> maybe add all options to the constructor of the + # exporter + # TODO: allow embedding of images (base64) + return image.name + ".png" + + @classmethod + def __get_key_path(cls, d: dict, keypath: List[str], default=[]): + """Create if necessary and get the element at key path from a dict""" + key = keypath.pop(0) + + if len(keypath) == 0: + v = d.get(key, default) + d[key] = v + return v + + d_key = d.get(key, {}) + d[key] = d_key + return cls.__get_key_path(d[key], keypath, default) + + def __traverse(self, node): + """ + Recursively traverse a scene graph consisting of gltf compatible elements. + + The tree is traversed downwards until a primitive is reached. Then any ChildOfRoot property + is stored in the according list in the glTF and replaced with a index reference in the upper level. + """ + def __traverse_property(node): + for member_name in [a for a in dir(node) if not a.startswith('__') and not callable(getattr(node, a))]: + new_value = self.__traverse(getattr(node, member_name)) + setattr(node, member_name, new_value) # usually this is the same as before + + # # TODO: maybe with extensions hooks we can find a more elegant solution + # if member_name == "extensions" and new_value is not None: + # for extension_name in new_value.keys(): + # self.__append_unique_and_get_index(self.__gltf.extensions_used, extension_name) + # self.__append_unique_and_get_index(self.__gltf.extensions_required, extension_name) + return node + + # traverse nodes of a child of root property type and add them to the glTF root + if type(node) in self.__childOfRootPropertyTypeLookup: + node = __traverse_property(node) + idx = self.__to_reference(node) + # child of root properties are only present at root level --> replace with index in upper level + return idx + + # traverse lists, such as children and replace them with indices + if isinstance(node, list): + for i in range(len(node)): + node[i] = self.__traverse(node[i]) + return node + + if isinstance(node, dict): + for key in node.keys(): + node[key] = self.__traverse(node[key]) + return node + + # traverse into any other property + if type(node) in self.__propertyTypeLookup: + return __traverse_property(node) + + # binary data needs to be moved to a buffer and referenced with a buffer view + if isinstance(node, gltf2_io_binary_data.BinaryData): + buffer_view = self.__buffer.add_and_get_view(node) + return self.__to_reference(buffer_view) + + # image data needs to be saved to file + if isinstance(node, gltf2_io_image_data.ImageData): + return self.__add_image(node) + + # extensions + if isinstance(node, gltf2_io_extensions.Extension): + extension = self.__traverse(node.extension) + self.__append_unique_and_get_index(self.__gltf.extensions_used, node.name) + self.__append_unique_and_get_index(self.__gltf.extensions_required, node.name) + + # extensions that lie in the root of the glTF. + # They need to be converted to a reference at place of occurrence + if isinstance(node, gltf2_io_extensions.ChildOfRootExtension): + root_extension_list = self.__get_key_path(self.__gltf.extensions, [node.name] + node.path) + idx = self.__append_unique_and_get_index(root_extension_list, extension) + return idx + + return extension + + # do nothing for any type that does not match a glTF schema (primitives) + return node + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py new file mode 100755 index 00000000..92b63c7d --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_search_node_tree.py @@ -0,0 +1,98 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +import bpy +import typing + + +class Filter: + """Base class for all node tree filter operations.""" + + def __init__(self): + pass + + def __call__(self, shader_node): + return True + + +class FilterByName(Filter): + """ + Filter the material node tree by name. + + example usage: + find_from_socket(start_socket, ShaderNodeFilterByName("Normal")) + """ + + def __init__(self, name): + self.name = name + super(FilterByName, self).__init__() + + def __call__(self, shader_node): + return shader_node.name == self.name + + +class FilterByType(Filter): + """Filter the material node tree by type.""" + + def __init__(self, type): + self.type = type + super(FilterByType, self).__init__() + + def __call__(self, shader_node): + return isinstance(shader_node, self.type) + + +class NodeTreeSearchResult: + def __init__(self, shader_node: bpy.types.Node, path: typing.List[bpy.types.NodeLink]): + self.shader_node = shader_node + self.path = path + + +# TODO: cache these searches +def from_socket(start_socket: bpy.types.NodeSocket, + shader_node_filter: typing.Union[Filter, typing.Callable]) -> typing.List[NodeTreeSearchResult]: + """ + Find shader nodes where the filter expression is true. + + :param start_socket: the beginning of the traversal + :param shader_node_filter: should be a function(x: shader_node) -> bool + :return: a list of shader nodes for which filter is true + """ + # hide implementation (especially the search path + def __search_from_socket(start_socket: bpy.types.NodeSocket, + shader_node_filter: typing.Union[Filter, typing.Callable], + search_path: typing.List[bpy.types.NodeLink]) -> typing.List[NodeTreeSearchResult]: + results = [] + + for link in start_socket.links: + # follow the link to a shader node + linked_node = link.from_node + # add the link to the current path + search_path.append(link) + # check if the node matches the filter + if shader_node_filter(linked_node): + results.append(NodeTreeSearchResult(linked_node, search_path)) + # traverse into inputs of the node + for input_socket in linked_node.inputs: + results += __search_from_socket(input_socket, shader_node_filter, search_path) + + return results + + return __search_from_socket(start_socket, shader_node_filter, []) + + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_search_scene.py b/io_scene_gltf2/blender/exp/gltf2_blender_search_scene.py new file mode 100755 index 00000000..0fa7db6e --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_search_scene.py @@ -0,0 +1,89 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import typing + + +class Filter: + """Base class for all node tree filter operations.""" + + def __call__(self, obj: bpy.types.Object): + return True + + +class ByName(Filter): + """ + Filter the objects by name. + + example usage: + find_objects(FilterByName("Cube")) + """ + + def __init__(self, name): + self.name = name + + def __call__(self, obj: bpy.types.Object): + return obj.name == self.name + + +class ByDataType(Filter): + """Filter the scene objects by their data type.""" + + def __init__(self, data_type: str): + self.type = data_type + + def __call__(self, obj: bpy.types.Object): + return obj.type == self.type + + +class ByDataInstance(Filter): + """Filter the scene objects by a specific ID instance.""" + + def __init__(self, data_instance: bpy.types.ID): + self.data = data_instance + + def __call__(self, obj: bpy.types.Object): + return self.data == obj.data + + +def find_objects(object_filter: typing.Union[Filter, typing.Callable]): + """ + Find objects in the scene where the filter expression is true. + + :param object_filter: should be a function(x: object) -> bool + :return: a list of shader nodes for which filter is true + """ + results = [] + for obj in bpy.context.scene.objects: + if object_filter(obj): + results.append(obj) + return results + + +def find_objects_from(obj: bpy.types.Object, object_filter: typing.Union[Filter, typing.Callable]): + """ + Search for objects matching a filter function below a specified object. + + :param obj: the starting point of the search + :param object_filter: a function(x: object) -> bool + :return: a list of objects which passed the filter + """ + results = [] + if object_filter(obj): + results.append(obj) + for child in obj.children: + results += find_objects_from(child, object_filter) + return results + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_utils.py b/io_scene_gltf2/blender/exp/gltf2_blender_utils.py new file mode 100755 index 00000000..c3e0d6ee --- /dev/null +++ b/io_scene_gltf2/blender/exp/gltf2_blender_utils.py @@ -0,0 +1,68 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +from io_scene_gltf2.io.com import gltf2_io_constants + + +# TODO: we could apply functional programming to these problems (currently we only have a single use case) + +def split_list_by_data_type(l: list, data_type: gltf2_io_constants.DataType): + """ + Split a flat list of components by their data type. + + E.g.: A list [0,1,2,3,4,5] of data type Vec3 would be split to [[0,1,2], [3,4,5]] + :param l: the flat list + :param data_type: the data type of the list + :return: a list of lists, where each element list contains the components of the data type + """ + if not (len(l) % gltf2_io_constants.DataType.num_elements(data_type) == 0): + raise ValueError("List length does not match specified data type") + num_elements = gltf2_io_constants.DataType.num_elements(data_type) + return [l[i:i + num_elements] for i in range(0, len(l), num_elements)] + + +def max_components(l: list, data_type: gltf2_io_constants.DataType) -> list: + """ + Find the maximum components in a flat list. + + This is required, for example, for the glTF2.0 accessor min and max properties + :param l: the flat list of components + :param data_type: the data type of the list (determines the length of the result) + :return: a list with length num_elements(data_type) containing the maximum per component along the list + """ + components_lists = split_list_by_data_type(l, data_type) + result = [-math.inf] * gltf2_io_constants.DataType.num_elements(data_type) + for components in components_lists: + for i, c in enumerate(components): + result[i] = max(result[i], c) + return result + + +def min_components(l: list, data_type: gltf2_io_constants.DataType) -> list: + """ + Find the minimum components in a flat list. + + This is required, for example, for the glTF2.0 accessor min and max properties + :param l: the flat list of components + :param data_type: the data type of the list (determines the length of the result) + :return: a list with length num_elements(data_type) containing the minimum per component along the list + """ + components_lists = split_list_by_data_type(l, data_type) + result = [math.inf] * gltf2_io_constants.DataType.num_elements(data_type) + for components in components_lists: + for i, c in enumerate(components): + result[i] = min(result[i], c) + return result + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py new file mode 100755 index 00000000..1854f45d --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_KHR_materials_pbrSpecularGlossiness.py @@ -0,0 +1,327 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from .gltf2_blender_texture import BlenderTextureInfo + + +class BlenderKHR_materials_pbrSpecularGlossiness(): + """Blender KHR_materials_pbrSpecularGlossiness extension.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, pbrSG, mat_name, vertex_color): + """KHR_materials_pbrSpecularGlossiness creation.""" + engine = bpy.context.scene.render.engine + if engine in ['CYCLES', 'BLENDER_EEVEE']: + BlenderKHR_materials_pbrSpecularGlossiness.create_nodetree(gltf, pbrSG, mat_name, vertex_color) + + @staticmethod + def create_nodetree(gltf, pbrSG, mat_name, vertex_color): + """Node tree creation.""" + material = bpy.data.materials[mat_name] + material.use_nodes = True + node_tree = material.node_tree + + # delete all nodes except output + for node in list(node_tree.nodes): + if not node.type == 'OUTPUT_MATERIAL': + node_tree.nodes.remove(node) + + output_node = node_tree.nodes[0] + output_node.location = 1000, 0 + + # create PBR node + diffuse = node_tree.nodes.new('ShaderNodeBsdfDiffuse') + diffuse.location = 0, 0 + glossy = node_tree.nodes.new('ShaderNodeBsdfGlossy') + glossy.location = 0, 100 + mix = node_tree.nodes.new('ShaderNodeMixShader') + mix.location = 500, 0 + + glossy.inputs[1].default_value = 1 - pbrSG['glossinessFactor'] + + if pbrSG['diffuse_type'] == gltf.SIMPLE: + if not vertex_color: + # change input values + diffuse.inputs[0].default_value = pbrSG['diffuseFactor'] + + else: + # Create attribute node to get COLOR_0 data + attribute_node = node_tree.nodes.new('ShaderNodeAttribute') + attribute_node.attribute_name = 'COLOR_0' + attribute_node.location = -500, 0 + + # links + node_tree.links.new(diffuse.inputs[0], attribute_node.outputs[0]) + + elif pbrSG['diffuse_type'] == gltf.TEXTURE_FACTOR: + + # TODO alpha ? + if vertex_color: + # TODO tree locations + # Create attribute / separate / math nodes + attribute_node = node_tree.nodes.new('ShaderNodeAttribute') + attribute_node.attribute_name = 'COLOR_0' + + separate_vertex_color = node_tree.nodes.new('ShaderNodeSeparateRGB') + math_vc_R = node_tree.nodes.new('ShaderNodeMath') + math_vc_R.operation = 'MULTIPLY' + + math_vc_G = node_tree.nodes.new('ShaderNodeMath') + math_vc_G.operation = 'MULTIPLY' + + math_vc_B = node_tree.nodes.new('ShaderNodeMath') + math_vc_B.operation = 'MULTIPLY' + + BlenderTextureInfo.create(gltf, pbrSG['diffuseTexture']['index']) + + # create UV Map / Mapping / Texture nodes / separate & math and combine + text_node = node_tree.nodes.new('ShaderNodeTexImage') + text_node.image = \ + bpy.data.images[ + gltf.data.images[gltf.data.textures[pbrSG['diffuseTexture']['index']].source].blender_image_name + ] + text_node.location = -1000, 500 + + combine = node_tree.nodes.new('ShaderNodeCombineRGB') + combine.location = -250, 500 + + math_R = node_tree.nodes.new('ShaderNodeMath') + math_R.location = -500, 750 + math_R.operation = 'MULTIPLY' + math_R.inputs[1].default_value = pbrSG['diffuseFactor'][0] + + math_G = node_tree.nodes.new('ShaderNodeMath') + math_G.location = -500, 500 + math_G.operation = 'MULTIPLY' + math_G.inputs[1].default_value = pbrSG['diffuseFactor'][1] + + math_B = node_tree.nodes.new('ShaderNodeMath') + math_B.location = -500, 250 + math_B.operation = 'MULTIPLY' + math_B.inputs[1].default_value = pbrSG['diffuseFactor'][2] + + separate = node_tree.nodes.new('ShaderNodeSeparateRGB') + separate.location = -750, 500 + + mapping = node_tree.nodes.new('ShaderNodeMapping') + mapping.location = -1500, 500 + + uvmap = node_tree.nodes.new('ShaderNodeUVMap') + uvmap.location = -2000, 500 + if 'texCoord' in pbrSG['diffuseTexture'].keys(): + uvmap["gltf2_texcoord"] = pbrSG['diffuseTexture']['texCoord'] # Set custom flag to retrieve TexCoord + else: + uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here? + # UV Map will be set after object/UVMap creation + + # Create links + if vertex_color: + node_tree.links.new(separate_vertex_color.inputs[0], attribute_node.outputs[0]) + node_tree.links.new(math_vc_R.inputs[1], separate_vertex_color.outputs[0]) + node_tree.links.new(math_vc_G.inputs[1], separate_vertex_color.outputs[1]) + node_tree.links.new(math_vc_B.inputs[1], separate_vertex_color.outputs[2]) + node_tree.links.new(math_vc_R.inputs[0], math_R.outputs[0]) + node_tree.links.new(math_vc_G.inputs[0], math_G.outputs[0]) + node_tree.links.new(math_vc_B.inputs[0], math_B.outputs[0]) + node_tree.links.new(combine.inputs[0], math_vc_R.outputs[0]) + node_tree.links.new(combine.inputs[1], math_vc_G.outputs[0]) + node_tree.links.new(combine.inputs[2], math_vc_B.outputs[0]) + + else: + node_tree.links.new(combine.inputs[0], math_R.outputs[0]) + node_tree.links.new(combine.inputs[1], math_G.outputs[0]) + node_tree.links.new(combine.inputs[2], math_B.outputs[0]) + + # Common for both mode (non vertex color / vertex color) + node_tree.links.new(math_R.inputs[0], separate.outputs[0]) + node_tree.links.new(math_G.inputs[0], separate.outputs[1]) + node_tree.links.new(math_B.inputs[0], separate.outputs[2]) + + node_tree.links.new(mapping.inputs[0], uvmap.outputs[0]) + node_tree.links.new(text_node.inputs[0], mapping.outputs[0]) + node_tree.links.new(separate.inputs[0], text_node.outputs[0]) + + node_tree.links.new(diffuse.inputs[0], combine.outputs[0]) + + elif pbrSG['diffuse_type'] == gltf.TEXTURE: + + BlenderTextureInfo.create(gltf, pbrSG['diffuseTexture']['index']) + + # TODO alpha ? + if vertex_color: + # Create attribute / separate / math nodes + attribute_node = node_tree.nodes.new('ShaderNodeAttribute') + attribute_node.attribute_name = 'COLOR_0' + attribute_node.location = -2000, 250 + + separate_vertex_color = node_tree.nodes.new('ShaderNodeSeparateRGB') + separate_vertex_color.location = -1500, 250 + + math_vc_R = node_tree.nodes.new('ShaderNodeMath') + math_vc_R.operation = 'MULTIPLY' + math_vc_R.location = -1000, 750 + + math_vc_G = node_tree.nodes.new('ShaderNodeMath') + math_vc_G.operation = 'MULTIPLY' + math_vc_G.location = -1000, 500 + + math_vc_B = node_tree.nodes.new('ShaderNodeMath') + math_vc_B.operation = 'MULTIPLY' + math_vc_B.location = -1000, 250 + + combine = node_tree.nodes.new('ShaderNodeCombineRGB') + combine.location = -500, 500 + + separate = node_tree.nodes.new('ShaderNodeSeparateRGB') + separate.location = -1500, 500 + + # create UV Map / Mapping / Texture nodes / separate & math and combine + text_node = node_tree.nodes.new('ShaderNodeTexImage') + text_node.image = bpy.data.images[ + gltf.data.images[gltf.data.textures[pbrSG['diffuseTexture']['index']].source].blender_image_name + ] + if vertex_color: + text_node.location = -2000, 500 + else: + text_node.location = -500, 500 + + mapping = node_tree.nodes.new('ShaderNodeMapping') + if vertex_color: + mapping.location = -2500, 500 + else: + mapping.location = -1500, 500 + + uvmap = node_tree.nodes.new('ShaderNodeUVMap') + if vertex_color: + uvmap.location = -3000, 500 + else: + uvmap.location = -2000, 500 + if 'texCoord' in pbrSG['diffuseTexture'].keys(): + uvmap["gltf2_texcoord"] = pbrSG['diffuseTexture']['texCoord'] # Set custom flag to retrieve TexCoord + else: + uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here? + # UV Map will be set after object/UVMap creation + + # Create links + if vertex_color: + node_tree.links.new(separate_vertex_color.inputs[0], attribute_node.outputs[0]) + + node_tree.links.new(math_vc_R.inputs[1], separate_vertex_color.outputs[0]) + node_tree.links.new(math_vc_G.inputs[1], separate_vertex_color.outputs[1]) + node_tree.links.new(math_vc_B.inputs[1], separate_vertex_color.outputs[2]) + + node_tree.links.new(combine.inputs[0], math_vc_R.outputs[0]) + node_tree.links.new(combine.inputs[1], math_vc_G.outputs[0]) + node_tree.links.new(combine.inputs[2], math_vc_B.outputs[0]) + + node_tree.links.new(separate.inputs[0], text_node.outputs[0]) + + node_tree.links.new(diffuse.inputs[0], combine.outputs[0]) + + node_tree.links.new(math_vc_R.inputs[0], separate.outputs[0]) + node_tree.links.new(math_vc_G.inputs[0], separate.outputs[1]) + node_tree.links.new(math_vc_B.inputs[0], separate.outputs[2]) + + else: + node_tree.links.new(diffuse.inputs[0], text_node.outputs[0]) + + # Common for both mode (non vertex color / vertex color) + + node_tree.links.new(mapping.inputs[0], uvmap.outputs[0]) + node_tree.links.new(text_node.inputs[0], mapping.outputs[0]) + + if pbrSG['specgloss_type'] == gltf.SIMPLE: + + combine = node_tree.nodes.new('ShaderNodeCombineRGB') + combine.inputs[0].default_value = pbrSG['specularFactor'][0] + combine.inputs[1].default_value = pbrSG['specularFactor'][1] + combine.inputs[2].default_value = pbrSG['specularFactor'][2] + + # links + node_tree.links.new(glossy.inputs[0], combine.outputs[0]) + + elif pbrSG['specgloss_type'] == gltf.TEXTURE: + BlenderTextureInfo.create(gltf, pbrSG['specularGlossinessTexture']['index']) + spec_text = node_tree.nodes.new('ShaderNodeTexImage') + spec_text.image = bpy.data.images[ + gltf.data.images[ + gltf.data.textures[pbrSG['specularGlossinessTexture']['index']].source + ].blender_image_name + ] + spec_text.color_space = 'NONE' + spec_text.location = -500, 0 + + spec_mapping = node_tree.nodes.new('ShaderNodeMapping') + spec_mapping.location = -1000, 0 + + spec_uvmap = node_tree.nodes.new('ShaderNodeUVMap') + spec_uvmap.location = -1500, 0 + if 'texCoord' in pbrSG['specularGlossinessTexture'].keys(): + # Set custom flag to retrieve TexCoord + spec_uvmap["gltf2_texcoord"] = pbrSG['specularGlossinessTexture']['texCoord'] + else: + spec_uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here? + + # links + node_tree.links.new(glossy.inputs[0], spec_text.outputs[0]) + node_tree.links.new(mix.inputs[0], spec_text.outputs[1]) + + node_tree.links.new(spec_mapping.inputs[0], spec_uvmap.outputs[0]) + node_tree.links.new(spec_text.inputs[0], spec_mapping.outputs[0]) + + elif pbrSG['specgloss_type'] == gltf.TEXTURE_FACTOR: + + BlenderTextureInfo.create(gltf, pbrSG['specularGlossinessTexture']['index']) + + spec_text = node_tree.nodes.new('ShaderNodeTexImage') + spec_text.image = bpy.data.images[gltf.data.images[ + gltf.data.textures[pbrSG['specularGlossinessTexture']['index']].source + ].blender_image_name] + spec_text.color_space = 'NONE' + spec_text.location = -1000, 0 + + spec_math = node_tree.nodes.new('ShaderNodeMath') + spec_math.operation = 'MULTIPLY' + spec_math.inputs[0].default_value = pbrSG['glossinessFactor'] + spec_math.location = -250, 100 + + spec_mapping = node_tree.nodes.new('ShaderNodeMapping') + spec_mapping.location = -1000, 0 + + spec_uvmap = node_tree.nodes.new('ShaderNodeUVMap') + spec_uvmap.location = -1500, 0 + if 'texCoord' in pbrSG['specularGlossinessTexture'].keys(): + # Set custom flag to retrieve TexCoord + spec_uvmap["gltf2_texcoord"] = pbrSG['specularGlossinessTexture']['texCoord'] + else: + spec_uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here? + + # links + + node_tree.links.new(spec_math.inputs[1], spec_text.outputs[0]) + node_tree.links.new(mix.inputs[0], spec_text.outputs[1]) + node_tree.links.new(glossy.inputs[1], spec_math.outputs[0]) + node_tree.links.new(glossy.inputs[0], spec_text.outputs[0]) + + node_tree.links.new(spec_mapping.inputs[0], spec_uvmap.outputs[0]) + node_tree.links.new(spec_text.inputs[0], spec_mapping.outputs[0]) + + # link node to output + node_tree.links.new(mix.inputs[2], diffuse.outputs[0]) + node_tree.links.new(mix.inputs[1], glossy.outputs[0]) + node_tree.links.new(output_node.inputs[0], mix.outputs[0]) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation.py new file mode 100755 index 00000000..4180672a --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation.py @@ -0,0 +1,35 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .gltf2_blender_animation_bone import BlenderBoneAnim +from .gltf2_blender_animation_node import BlenderNodeAnim + + +class BlenderAnimation(): + """Dispatch Animation to bone or object animation.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def anim(gltf, anim_idx, node_idx): + """Dispatch Animation to bone or object.""" + if gltf.data.nodes[node_idx].is_joint: + BlenderBoneAnim.anim(gltf, anim_idx, node_idx) + else: + BlenderNodeAnim.anim(gltf, anim_idx, node_idx) + + if gltf.data.nodes[node_idx].children: + for child in gltf.data.nodes[node_idx].children: + BlenderAnimation.anim(gltf, anim_idx, child) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py new file mode 100755 index 00000000..c0884966 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py @@ -0,0 +1,194 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from mathutils import Matrix + +from ..com.gltf2_blender_conversion import loc_gltf_to_blender, quaternion_gltf_to_blender, scale_to_matrix +from ...io.imp.gltf2_io_binary import BinaryData + + +class BlenderBoneAnim(): + """Blender Bone Animation.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def set_interpolation(interpolation, kf): + """Set interpolation.""" + if interpolation == "LINEAR": + kf.interpolation = 'LINEAR' + elif interpolation == "STEP": + kf.interpolation = 'CONSTANT' + elif interpolation == "CUBICSPLINE": + kf.interpolation = 'BEZIER' + else: + kf.interpolation = 'BEZIER' + + @staticmethod + def parse_translation_channel(gltf, node, obj, bone, channel, animation): + """Manage Location animation.""" + fps = bpy.context.scene.render.fps + blender_path = "location" + + keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) + values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) + inv_bind_matrix = node.blender_bone_matrix.to_quaternion().to_matrix().to_4x4().inverted() \ + @ Matrix.Translation(node.blender_bone_matrix.to_translation()).inverted() + + for idx, key in enumerate(keys): + if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": + # TODO manage tangent? + translation_keyframe = loc_gltf_to_blender(values[idx * 3 + 1]) + else: + translation_keyframe = loc_gltf_to_blender(values[idx]) + if not node.parent: + parent_mat = Matrix() + else: + if not gltf.data.nodes[node.parent].is_joint: + parent_mat = Matrix() + else: + parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix + + # Pose is in object (armature) space and it's value if the offset from the bind pose + # (which is also in object space) + # Scale is not taken into account + final_trans = (parent_mat @ Matrix.Translation(translation_keyframe)).to_translation() + bone.location = inv_bind_matrix @ final_trans + bone.keyframe_insert(blender_path, frame=key[0] * fps, group="location") + + for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "location"]: + for kf in fcurve.keyframe_points: + BlenderBoneAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) + + @staticmethod + def parse_rotation_channel(gltf, node, obj, bone, channel, animation): + """Manage rotation animation.""" + # Note: some operations lead to issue with quaternions. Converting to matrix and then back to quaternions breaks + # quaternion continuity + # (see antipodal quaternions). Blender interpolates between two antipodal quaternions, which causes glitches in + # animation. + # Converting to euler and then back to quaternion is a dirty fix preventing this issue in animation, until a + # better solution is found + # This fix is skipped when parent matrix is identity + fps = bpy.context.scene.render.fps + blender_path = "rotation_quaternion" + + keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) + values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) + bind_rotation = node.blender_bone_matrix.to_quaternion() + + for idx, key in enumerate(keys): + if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": + # TODO manage tangent? + quat_keyframe = quaternion_gltf_to_blender(values[idx * 3 + 1]) + else: + quat_keyframe = quaternion_gltf_to_blender(values[idx]) + if not node.parent: + bone.rotation_quaternion = bind_rotation.inverted() @ quat_keyframe + else: + if not gltf.data.nodes[node.parent].is_joint: + parent_mat = Matrix() + else: + parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix + + if parent_mat != parent_mat.inverted(): + final_rot = (parent_mat @ quat_keyframe.to_matrix().to_4x4()).to_quaternion() + bone.rotation_quaternion = bind_rotation.rotation_difference(final_rot).to_euler().to_quaternion() + else: + bone.rotation_quaternion = \ + bind_rotation.rotation_difference(quat_keyframe).to_euler().to_quaternion() + + bone.keyframe_insert(blender_path, frame=key[0] * fps, group='rotation') + + for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "rotation"]: + for kf in fcurve.keyframe_points: + BlenderBoneAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) + + @staticmethod + def parse_scale_channel(gltf, node, obj, bone, channel, animation): + """Manage scaling animation.""" + fps = bpy.context.scene.render.fps + blender_path = "scale" + + keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) + values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) + bind_scale = scale_to_matrix(node.blender_bone_matrix.to_scale()) + + for idx, key in enumerate(keys): + if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": + # TODO manage tangent? + scale_mat = scale_to_matrix(loc_gltf_to_blender(values[idx * 3 + 1])) + else: + scale_mat = scale_to_matrix(loc_gltf_to_blender(values[idx])) + if not node.parent: + bone.scale = (bind_scale.inverted() @ scale_mat).to_scale() + else: + if not gltf.data.nodes[node.parent].is_joint: + parent_mat = Matrix() + else: + parent_mat = gltf.data.nodes[node.parent].blender_bone_matrix + + bone.scale = ( + bind_scale.inverted() @ scale_to_matrix(parent_mat.to_scale()) @ scale_mat + ).to_scale() + + bone.keyframe_insert(blender_path, frame=key[0] * fps, group='scale') + + for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "scale"]: + for kf in fcurve.keyframe_points: + BlenderBoneAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) + + @staticmethod + def anim(gltf, anim_idx, node_idx): + """Manage animation.""" + node = gltf.data.nodes[node_idx] + obj = bpy.data.objects[gltf.data.skins[node.skin_id].blender_armature_name] + bone = obj.pose.bones[node.blender_bone_name] + + if anim_idx not in node.animations.keys(): + return + + animation = gltf.data.animations[anim_idx] + + if animation.name: + name = animation.name + "_" + obj.name + else: + name = "Animation_" + str(anim_idx) + "_" + obj.name + if name not in bpy.data.actions: + action = bpy.data.actions.new(name) + else: + action = bpy.data.actions[name] + # Check if this action has some users. + # If no user (only 1 indeed), that means that this action must be deleted + # (is an action from a deleted object) + if action.users == 1: + bpy.data.actions.remove(action) + action = bpy.data.actions.new(name) + if not obj.animation_data: + obj.animation_data_create() + obj.animation_data.action = bpy.data.actions[action.name] + + for channel_idx in node.animations[anim_idx]: + channel = animation.channels[channel_idx] + + if channel.target.path == "translation": + BlenderBoneAnim.parse_translation_channel(gltf, node, obj, bone, channel, animation) + + elif channel.target.path == "rotation": + BlenderBoneAnim.parse_rotation_channel(gltf, node, obj, bone, channel, animation) + + elif channel.target.path == "scale": + BlenderBoneAnim.parse_scale_channel(gltf, node, obj, bone, channel, animation) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py new file mode 100755 index 00000000..c053f84a --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py @@ -0,0 +1,142 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from mathutils import Vector + +from ..com.gltf2_blender_conversion import loc_gltf_to_blender, quaternion_gltf_to_blender, scale_gltf_to_blender +from ...io.imp.gltf2_io_binary import BinaryData + + +class BlenderNodeAnim(): + """Blender Object Animation.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def set_interpolation(interpolation, kf): + """Manage interpolation.""" + if interpolation == "LINEAR": + kf.interpolation = 'LINEAR' + elif interpolation == "STEP": + kf.interpolation = 'CONSTANT' + elif interpolation == "CUBICSPLINE": + kf.interpolation = 'BEZIER' + else: + kf.interpolation = 'BEZIER' + + @staticmethod + def anim(gltf, anim_idx, node_idx): + """Manage animation.""" + node = gltf.data.nodes[node_idx] + obj = bpy.data.objects[node.blender_object] + fps = bpy.context.scene.render.fps + + if anim_idx not in node.animations.keys(): + return + + animation = gltf.data.animations[anim_idx] + + if animation.name: + name = animation.name + "_" + obj.name + else: + name = "Animation_" + str(anim_idx) + "_" + obj.name + action = bpy.data.actions.new(name) + # Check if this action has some users. + # If no user (only 1 indeed), that means that this action must be deleted + # (is an action from a deleted object) + if action.users == 1: + bpy.data.actions.remove(action) + action = bpy.data.actions.new(name) + if not obj.animation_data: + obj.animation_data_create() + obj.animation_data.action = bpy.data.actions[action.name] + + for channel_idx in node.animations[anim_idx]: + channel = animation.channels[channel_idx] + + keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input) + values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output) + + if channel.target.path in ['translation', 'rotation', 'scale']: + + # There is an animation on object + # We can't remove Yup2Zup oject + gltf.animation_object = True + + if channel.target.path == "translation": + blender_path = "location" + for idx, key in enumerate(keys): + if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": + # TODO manage tangent? + obj.location = Vector(loc_gltf_to_blender(list(values[idx * 3 + 1]))) + else: + obj.location = Vector(loc_gltf_to_blender(list(values[idx]))) + obj.keyframe_insert(blender_path, frame=key[0] * fps, group='location') + + # Setting interpolation + for fcurve in [curve for curve in obj.animation_data.action.fcurves + if curve.group.name == "location"]: + for kf in fcurve.keyframe_points: + BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) + + elif channel.target.path == "rotation": + blender_path = "rotation_quaternion" + for idx, key in enumerate(keys): + if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": + # TODO manage tangent? + obj.rotation_quaternion = quaternion_gltf_to_blender(values[idx * 3 + 1]) + else: + obj.rotation_quaternion = quaternion_gltf_to_blender(values[idx]) + obj.keyframe_insert(blender_path, frame=key[0] * fps, group='rotation') + + # Setting interpolation + for fcurve in [curve for curve in obj.animation_data.action.fcurves + if curve.group.name == "rotation"]: + for kf in fcurve.keyframe_points: + BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) + + elif channel.target.path == "scale": + blender_path = "scale" + for idx, key in enumerate(keys): + # TODO manage tangent? + if animation.samplers[channel.sampler].interpolation == "CUBICSPLINE": + obj.scale = Vector(scale_gltf_to_blender(list(values[idx * 3 + 1]))) + else: + obj.scale = Vector(scale_gltf_to_blender(list(values[idx]))) + obj.keyframe_insert(blender_path, frame=key[0] * fps, group='scale') + + # Setting interpolation + for fcurve in [curve for curve in obj.animation_data.action.fcurves if curve.group.name == "scale"]: + for kf in fcurve.keyframe_points: + BlenderNodeAnim.set_interpolation(animation.samplers[channel.sampler].interpolation, kf) + + elif channel.target.path == 'weights': + + # retrieve number of targets + nb_targets = 0 + for prim in gltf.data.meshes[gltf.data.nodes[node_idx].mesh].primitives: + if prim.targets: + if len(prim.targets) > nb_targets: + nb_targets = len(prim.targets) + + for idx, key in enumerate(keys): + for sk in range(nb_targets): + obj.data.shape_keys.key_blocks[sk + 1].value = values[idx * nb_targets + sk][0] + obj.data.shape_keys.key_blocks[sk + 1].keyframe_insert( + "value", + frame=key[0] * fps, + group='ShapeKeys' + ) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_camera.py b/io_scene_gltf2/blender/imp/gltf2_blender_camera.py new file mode 100755 index 00000000..b5a10ac5 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_camera.py @@ -0,0 +1,47 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy + + +class BlenderCamera(): + """Blender Camera.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, camera_id): + """Camera creation.""" + pycamera = gltf.data.cameras[camera_id] + + if not pycamera.name: + pycamera.name = "Camera" + + cam = bpy.data.cameras.new(pycamera.name) + + # Blender create a perspective camera by default + if pycamera.type == "orthographic": + cam.type = "ORTHO" + + # TODO: lot's of work for camera here... + if hasattr(pycamera, "znear"): + cam.clip_start = pycamera.znear + + if hasattr(pycamera, "zfar"): + cam.clip_end = pycamera.zfar + + obj = bpy.data.objects.new(pycamera.name, cam) + bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) + return obj + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py new file mode 100755 index 00000000..1f708b40 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py @@ -0,0 +1,225 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from .gltf2_blender_scene import BlenderScene +from ...io.com.gltf2_io_trs import TRS + + +class BlenderGlTF(): + """Main glTF import class.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf): + """Create glTF main method.""" + bpy.context.scene.render.engine = 'BLENDER_EEVEE' + BlenderGlTF.pre_compute(gltf) + + for scene_idx, scene in enumerate(gltf.data.scenes): + BlenderScene.create(gltf, scene_idx) + + # Armature correction + # Try to detect bone chains, and set bone lengths + # To detect if a bone is in a chain, we try to detect if a bone head is aligned + # with parent_bone : + # Parent bone defined a line (between head & tail) + # Bone head defined a point + # Calcul of distance between point and line + # If < threshold --> In a chain + # Based on an idea of @Menithal, but added alignement detection to avoid some bad cases + + threshold = 0.001 + for armobj in [obj for obj in bpy.data.objects if obj.type == "ARMATURE"]: + bpy.context.view_layer.objects.active = armobj + armature = armobj.data + bpy.ops.object.mode_set(mode="EDIT") + for bone in armature.edit_bones: + if bone.parent is None: + continue + + parent = bone.parent + + # case where 2 bones are aligned (not in chain, same head) + if (bone.head - parent.head).length < threshold: + continue + + u = (parent.tail - parent.head).normalized() + point = bone.head + distance = ((point - parent.head).cross(u)).length / u.length + if distance < threshold: + save_parent_direction = (parent.tail - parent.head).normalized().copy() + save_parent_tail = parent.tail.copy() + parent.tail = bone.head + + # case where 2 bones are aligned (not in chain, same head) + # bone is no more is same direction + if (parent.tail - parent.head).normalized().dot(save_parent_direction) < 0.9: + parent.tail = save_parent_tail + + bpy.ops.object.mode_set(mode="OBJECT") + + @staticmethod + def pre_compute(gltf): + """Pre compute, just before creation.""" + # default scene used + gltf.blender_scene = None + + # Check if there is animation on object + # Init is to False, and will be set to True during creation + gltf.animation_object = False + + # Blender material + if gltf.data.materials: + for material in gltf.data.materials: + material.blender_material = {} + + if material.pbr_metallic_roughness: + # Init + material.pbr_metallic_roughness.color_type = gltf.SIMPLE + material.pbr_metallic_roughness.vertex_color = False + material.pbr_metallic_roughness.metallic_type = gltf.SIMPLE + + if material.pbr_metallic_roughness.base_color_texture: + material.pbr_metallic_roughness.color_type = gltf.TEXTURE + + if material.pbr_metallic_roughness.metallic_roughness_texture: + material.pbr_metallic_roughness.metallic_type = gltf.TEXTURE + + if material.pbr_metallic_roughness.base_color_factor: + if material.pbr_metallic_roughness.color_type == gltf.TEXTURE and \ + material.pbr_metallic_roughness.base_color_factor != [1.0, 1.0, 1.0, 1.0]: + material.pbr_metallic_roughness.color_type = gltf.TEXTURE_FACTOR + else: + material.pbr_metallic_roughness.base_color_factor = [1.0, 1.0, 1.0, 1.0] + + if material.pbr_metallic_roughness.metallic_factor is not None: + if material.pbr_metallic_roughness.metallic_type == gltf.TEXTURE \ + and material.pbr_metallic_roughness.metallic_factor != 1.0: + material.pbr_metallic_roughness.metallic_type = gltf.TEXTURE_FACTOR + else: + material.pbr_metallic_roughness.metallic_factor = 1.0 + + if material.pbr_metallic_roughness.roughness_factor is not None: + if material.pbr_metallic_roughness.metallic_type == gltf.TEXTURE \ + and material.pbr_metallic_roughness.roughness_factor != 1.0: + material.pbr_metallic_roughness.metallic_type = gltf.TEXTURE_FACTOR + else: + material.pbr_metallic_roughness.roughness_factor = 1.0 + + # pre compute material for KHR_materials_pbrSpecularGlossiness + if material.extensions is not None \ + and 'KHR_materials_pbrSpecularGlossiness' in material.extensions.keys(): + # Init + material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] = gltf.SIMPLE + material.extensions['KHR_materials_pbrSpecularGlossiness']['vertex_color'] = False + material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] = gltf.SIMPLE + + if 'diffuseTexture' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys(): + material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] = gltf.TEXTURE + + if 'diffuseFactor' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys(): + if material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] == gltf.TEXTURE \ + and material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuseFactor'] != \ + [1.0, 1.0, 1.0, 1.0]: + material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuse_type'] = \ + gltf.TEXTURE_FACTOR + else: + material.extensions['KHR_materials_pbrSpecularGlossiness']['diffuseFactor'] = \ + [1.0, 1.0, 1.0, 1.0] + + if 'specularGlossinessTexture' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys(): + material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] = gltf.TEXTURE + + if 'specularFactor' in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys(): + if material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] == \ + gltf.TEXTURE \ + and material.extensions['KHR_materials_pbrSpecularGlossiness']['specularFactor'] != \ + [1.0, 1.0, 1.0]: + material.extensions['KHR_materials_pbrSpecularGlossiness']['specgloss_type'] = \ + gltf.TEXTURE_FACTOR + else: + material.extensions['KHR_materials_pbrSpecularGlossiness']['specularFactor'] = [1.0, 1.0, 1.0] + + if 'glossinessFactor' not in material.extensions['KHR_materials_pbrSpecularGlossiness'].keys(): + material.extensions['KHR_materials_pbrSpecularGlossiness']['glossinessFactor'] = 1.0 + + if gltf.data.nodes is None: + # Something is wrong in file, there is no nodes + return + + for node_idx, node in enumerate(gltf.data.nodes): + + # skin management + if node.skin is not None and node.mesh is not None: + if not hasattr(gltf.data.skins[node.skin], "node_ids"): + gltf.data.skins[node.skin].node_ids = [] + + gltf.data.skins[node.skin].node_ids.append(node_idx) + + # transform management + if node.matrix: + node.transform = node.matrix + continue + + # No matrix, but TRS + mat = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] # init + + if node.scale: + mat = TRS.scale_to_matrix(node.scale) + + if node.rotation: + q_mat = TRS.quaternion_to_matrix(node.rotation) + mat = TRS.matrix_multiply(q_mat, mat) + + if node.translation: + loc_mat = TRS.translation_to_matrix(node.translation) + mat = TRS.matrix_multiply(loc_mat, mat) + + node.transform = mat + + # joint management + for node_idx, node in enumerate(gltf.data.nodes): + is_joint, skin_idx = gltf.is_node_joint(node_idx) + if is_joint: + node.is_joint = True + node.skin_id = skin_idx + else: + node.is_joint = False + + if gltf.data.skins: + for skin_id, skin in enumerate(gltf.data.skins): + # init blender values + skin.blender_armature_name = None + # if skin.skeleton and skin.skeleton not in skin.joints: + # gltf.data.nodes[skin.skeleton].is_joint = True + # gltf.data.nodes[skin.skeleton].skin_id = skin_id + + # Dispatch animation + if gltf.data.animations: + for node_idx, node in enumerate(gltf.data.nodes): + node.animations = {} + + for anim_idx, anim in enumerate(gltf.data.animations): + for channel_idx, channel in enumerate(anim.channels): + if anim_idx not in gltf.data.nodes[channel.target.node].animations.keys(): + gltf.data.nodes[channel.target.node].animations[anim_idx] = [] + gltf.data.nodes[channel.target.node].animations[anim_idx].append(channel_idx) + + # Meshes + if gltf.data.meshes: + for mesh in gltf.data.meshes: + mesh.blender_name = None + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_image.py b/io_scene_gltf2/blender/imp/gltf2_blender_image.py new file mode 100755 index 00000000..ca1eb626 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_image.py @@ -0,0 +1,101 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import os +import tempfile +from os.path import dirname, join, isfile, basename + +from ...io.imp.gltf2_io_binary import BinaryData + + +# Note that Image is not a glTF2.0 object +class BlenderImage(): + """Manage Image.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def get_image_path(gltf, img_idx): + """Return image path.""" + pyimage = gltf.data.images[img_idx] + + image_name = "Image_" + str(img_idx) + + if pyimage.uri: + sep = ';base64,' + if pyimage.uri[:5] == 'data:': + idx = pyimage.uri.find(sep) + if idx != -1: + return False, None, None + + if isfile(join(dirname(gltf.filename), pyimage.uri)): + return True, join(dirname(gltf.filename), pyimage.uri), basename(join(dirname(gltf.filename), pyimage.uri)) + else: + pyimage.gltf.log.error("Missing file (index " + str(img_idx) + "): " + pyimage.uri) + return False, None, None + + if pyimage.buffer_view is None: + return False, None, None + + return False, None, None + + @staticmethod + def create(gltf, img_idx): + """Image creation.""" + img = gltf.data.images[img_idx] + + img.blender_image_name = None + + if gltf.import_settings['import_pack_images'] is False: + + # Images are not packed (if image is a real file) + real, path, img_name = BlenderImage.get_image_path(gltf, img_idx) + + if real is True: + + # Check if image is already loaded + for img_ in bpy.data.images: + if img_.filepath == path: + # Already loaded, not needed to reload it + img.blender_image_name = img_.name + return + + blender_image = bpy.data.images.load(path) + blender_image.name = img_name + img.blender_image_name = blender_image.name + return + + # Check if the file is already loaded (packed file) + file_creation_needed = True + for img_ in bpy.data.images: + if hasattr(img_, "gltf_index") and img_['gltf_index'] == img_idx: + file_creation_needed = False + img.blender_image_name = img_.name + break + + if file_creation_needed is True: + # Create a temp image, pack, and delete image + tmp_image = tempfile.NamedTemporaryFile(delete=False) + img_data, img_name = BinaryData.get_image_data(gltf, img_idx) + tmp_image.write(img_data) + tmp_image.close() + + blender_image = bpy.data.images.load(tmp_image.name) + blender_image.pack() + blender_image.name = img_name + img.blender_image_name = blender_image.name + blender_image['gltf_index'] = img_idx + os.remove(tmp_image.name) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_map_emissive.py b/io_scene_gltf2/blender/imp/gltf2_blender_map_emissive.py new file mode 100755 index 00000000..c3d0cb22 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_map_emissive.py @@ -0,0 +1,110 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from .gltf2_blender_texture import BlenderTextureInfo +from ..com.gltf2_blender_material_helpers import get_preoutput_node_output + + +class BlenderEmissiveMap(): + """Blender Emissive Map.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, material_idx, vertex_color): + """Create emissive map.""" + engine = bpy.context.scene.render.engine + if engine in ['CYCLES', 'BLENDER_EEVEE']: + BlenderEmissiveMap.create_nodetree(gltf, material_idx, vertex_color) + + def create_nodetree(gltf, material_idx, vertex_color): + """Create node tree.""" + pymaterial = gltf.data.materials[material_idx] + + material = bpy.data.materials[pymaterial.blender_material[vertex_color]] + node_tree = material.node_tree + + BlenderTextureInfo.create(gltf, pymaterial.emissive_texture.index) + + # check if there is some emssive_factor on material + if pymaterial.emissive_factor is None: + pymaterial.emissive_factor = [1.0, 1.0, 1.0] + + # retrieve principled node and output node + principled = get_preoutput_node_output(node_tree) + output = [node for node in node_tree.nodes if node.type == 'OUTPUT_MATERIAL'][0] + + # add nodes + emit = node_tree.nodes.new('ShaderNodeEmission') + emit.location = 0, 1000 + if pymaterial.emissive_factor != [1.0, 1.0, 1.0]: + separate = node_tree.nodes.new('ShaderNodeSeparateRGB') + separate.location = -750, 1000 + combine = node_tree.nodes.new('ShaderNodeCombineRGB') + combine.location = -250, 1000 + mapping = node_tree.nodes.new('ShaderNodeMapping') + mapping.location = -1500, 1000 + uvmap = node_tree.nodes.new('ShaderNodeUVMap') + uvmap.location = -2000, 1000 + if pymaterial.emissive_texture.tex_coord is not None: + uvmap["gltf2_texcoord"] = pymaterial.emissive_texture.tex_coord # Set custom flag to retrieve TexCoord + else: + uvmap["gltf2_texcoord"] = 0 # TODO: set in precompute instead of here? + + text = node_tree.nodes.new('ShaderNodeTexImage') + text.image = bpy.data.images[gltf.data.images[ + gltf.data.textures[pymaterial.emissive_texture.index].source + ].blender_image_name] + text.label = 'EMISSIVE' + text.location = -1000, 1000 + add = node_tree.nodes.new('ShaderNodeAddShader') + add.location = 500, 500 + + if pymaterial.emissive_factor != [1.0, 1.0, 1.0]: + math_R = node_tree.nodes.new('ShaderNodeMath') + math_R.location = -500, 1500 + math_R.operation = 'MULTIPLY' + math_R.inputs[1].default_value = pymaterial.emissive_factor[0] + + math_G = node_tree.nodes.new('ShaderNodeMath') + math_G.location = -500, 1250 + math_G.operation = 'MULTIPLY' + math_G.inputs[1].default_value = pymaterial.emissive_factor[1] + + math_B = node_tree.nodes.new('ShaderNodeMath') + math_B.location = -500, 1000 + math_B.operation = 'MULTIPLY' + math_B.inputs[1].default_value = pymaterial.emissive_factor[2] + + # create links + node_tree.links.new(mapping.inputs[0], uvmap.outputs[0]) + node_tree.links.new(text.inputs[0], mapping.outputs[0]) + if pymaterial.emissive_factor != [1.0, 1.0, 1.0]: + node_tree.links.new(separate.inputs[0], text.outputs[0]) + node_tree.links.new(math_R.inputs[0], separate.outputs[0]) + node_tree.links.new(math_G.inputs[0], separate.outputs[1]) + node_tree.links.new(math_B.inputs[0], separate.outputs[2]) + node_tree.links.new(combine.inputs[0], math_R.outputs[0]) + node_tree.links.new(combine.inputs[1], math_G.outputs[0]) + node_tree.links.new(combine.inputs[2], math_B.outputs[0]) + node_tree.links.new(emit.inputs[0], combine.outputs[0]) + else: + node_tree.links.new(emit.inputs[0], text.outputs[0]) + + # following links will modify PBR node tree + node_tree.links.new(add.inputs[0], emit.outputs[0]) + node_tree.links.new(add.inputs[1], principled) + node_tree.links.new(output.inputs[0], add.outputs[0]) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_map_normal.py b/io_scene_gltf2/blender/imp/gltf2_blender_map_normal.py new file mode 100755 index 00000000..0f7bab81 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_map_normal.py @@ -0,0 +1,89 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from .gltf2_blender_texture import BlenderTextureInfo + + +class BlenderNormalMap(): + """Blender Normal map.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, material_idx, vertex_color): + """Creation of Normal map.""" + engine = bpy.context.scene.render.engine + if engine in ['CYCLES', 'BLENDER_EEVEE']: + BlenderNormalMap.create_nodetree(gltf, material_idx, vertex_color) + + def create_nodetree(gltf, material_idx, vertex_color): + """Creation of Nodetree.""" + pymaterial = gltf.data.materials[material_idx] + + material = bpy.data.materials[pymaterial.blender_material[vertex_color]] + node_tree = material.node_tree + + BlenderTextureInfo.create(gltf, pymaterial.normal_texture.index) + + # retrieve principled node and output node + principled = None + diffuse = None + glossy = None + if len([node for node in node_tree.nodes if node.type == "BSDF_PRINCIPLED"]) != 0: + principled = [node for node in node_tree.nodes if node.type == "BSDF_PRINCIPLED"][0] + else: + # No principled, we are probably coming from extension + diffuse = [node for node in node_tree.nodes if node.type == "BSDF_DIFFUSE"][0] + glossy = [node for node in node_tree.nodes if node.type == "BSDF_GLOSSY"][0] + + # add nodes + mapping = node_tree.nodes.new('ShaderNodeMapping') + mapping.location = -1000, -500 + uvmap = node_tree.nodes.new('ShaderNodeUVMap') + uvmap.location = -1500, -500 + if pymaterial.normal_texture.tex_coord is not None: + uvmap["gltf2_texcoord"] = pymaterial.normal_texture.tex_coord # Set custom flag to retrieve TexCoord + else: + uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here + + text = node_tree.nodes.new('ShaderNodeTexImage') + text.image = bpy.data.images[gltf.data.images[ + gltf.data.textures[pymaterial.normal_texture.index].source + ].blender_image_name] + text.label = 'NORMALMAP' + text.color_space = 'NONE' + text.location = -500, -500 + + normalmap_node = node_tree.nodes.new('ShaderNodeNormalMap') + normalmap_node.location = -250, -500 + if pymaterial.normal_texture.tex_coord is not None: + # Set custom flag to retrieve TexCoord + normalmap_node["gltf2_texcoord"] = pymaterial.normal_texture.tex_coord + else: + normalmap_node["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here + + # create links + node_tree.links.new(mapping.inputs[0], uvmap.outputs[0]) + node_tree.links.new(text.inputs[0], mapping.outputs[0]) + node_tree.links.new(normalmap_node.inputs[1], text.outputs[0]) + + # following links will modify PBR node tree + if principled: + node_tree.links.new(principled.inputs[17], normalmap_node.outputs[0]) + if diffuse: + node_tree.links.new(diffuse.inputs[2], normalmap_node.outputs[0]) + if glossy: + node_tree.links.new(glossy.inputs[2], normalmap_node.outputs[0]) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py b/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py new file mode 100755 index 00000000..70e1e54a --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_map_occlusion.py @@ -0,0 +1,41 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from .gltf2_blender_texture import BlenderTextureInfo + + +class BlenderOcclusionMap(): + """Blender Occlusion map.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, material_idx, vertex_color): + """Occlusion map creation.""" + engine = bpy.context.scene.render.engine + if engine in ['CYCLES', 'BLENDER_EEVEE']: + BlenderOcclusionMap.create_nodetree(gltf, material_idx, vertex_color) + + def create_nodetree(gltf, material_idx, vertex_color): + """Nodetree creation.""" + pymaterial = gltf.data.materials[material_idx] + + BlenderTextureInfo.create(gltf, pymaterial.occlusion_texture.index) + + # Pack texture, but doesn't use it for now. Occlusion is calculated from Cycles. + bpy.data.images[gltf.data.images[gltf.data.textures[ + pymaterial.occlusion_texture.index + ].source].blender_image_name].use_fake_user = True + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_material.py b/io_scene_gltf2/blender/imp/gltf2_blender_material.py new file mode 100755 index 00000000..c910b7f8 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_material.py @@ -0,0 +1,156 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from .gltf2_blender_pbrMetallicRoughness import BlenderPbr +from .gltf2_blender_KHR_materials_pbrSpecularGlossiness import BlenderKHR_materials_pbrSpecularGlossiness +from .gltf2_blender_map_emissive import BlenderEmissiveMap +from .gltf2_blender_map_normal import BlenderNormalMap +from .gltf2_blender_map_occlusion import BlenderOcclusionMap +from ..com.gltf2_blender_material_helpers import get_output_surface_input +from ..com.gltf2_blender_material_helpers import get_preoutput_node_output +from ..com.gltf2_blender_material_helpers import get_base_color_node +from ...io.com.gltf2_io import MaterialPBRMetallicRoughness + + +class BlenderMaterial(): + """Blender Material.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, material_idx, vertex_color): + """Material creation.""" + pymaterial = gltf.data.materials[material_idx] + + if vertex_color is None: + if pymaterial.name is not None: + name = pymaterial.name + else: + name = "Material_" + str(material_idx) + else: + if pymaterial.name is not None: + name = pymaterial.name + "_" + vertex_color + else: + name = "Material_" + str(material_idx) + "_" + vertex_color + + mat = bpy.data.materials.new(name) + pymaterial.blender_material[vertex_color] = mat.name + + if pymaterial.extensions is not None and 'KHR_materials_pbrSpecularGlossiness' in pymaterial.extensions.keys(): + BlenderKHR_materials_pbrSpecularGlossiness.create( + gltf, pymaterial.extensions['KHR_materials_pbrSpecularGlossiness'], mat.name, vertex_color + ) + else: + # create pbr material + if pymaterial.pbr_metallic_roughness is None: + # If no pbr material is set, we need to apply all default of pbr + pbr = {} + pbr["baseColorFactor"] = [1.0, 1.0, 1.0, 1.0] + pbr["metallicFactor"] = 1.0 + pbr["roughnessFactor"] = 1.0 + pymaterial.pbr_metallic_roughness = MaterialPBRMetallicRoughness.from_dict(pbr) + pymaterial.pbr_metallic_roughness.color_type = gltf.SIMPLE + pymaterial.pbr_metallic_roughness.metallic_type = gltf.SIMPLE + + BlenderPbr.create(gltf, pymaterial.pbr_metallic_roughness, mat.name, vertex_color) + + # add emission map if needed + if pymaterial.emissive_texture is not None: + BlenderEmissiveMap.create(gltf, material_idx, vertex_color) + + # add normal map if needed + if pymaterial.normal_texture is not None: + BlenderNormalMap.create(gltf, material_idx, vertex_color) + + # add occlusion map if needed + # will be pack, but not used + if pymaterial.occlusion_texture is not None: + BlenderOcclusionMap.create(gltf, material_idx, vertex_color) + + if pymaterial.alpha_mode is not None and pymaterial.alpha_mode != 'OPAQUE': + BlenderMaterial.blender_alpha(gltf, material_idx, vertex_color) + + @staticmethod + def set_uvmap(gltf, material_idx, prim, obj, vertex_color): + """Set UV Map.""" + pymaterial = gltf.data.materials[material_idx] + + node_tree = bpy.data.materials[pymaterial.blender_material[vertex_color]].node_tree + uvmap_nodes = [node for node in node_tree.nodes if node.type in ['UVMAP', 'NORMAL_MAP']] + for uvmap_node in uvmap_nodes: + if uvmap_node["gltf2_texcoord"] in prim.blender_texcoord.keys(): + uvmap_node.uv_map = prim.blender_texcoord[uvmap_node["gltf2_texcoord"]] + + @staticmethod + def blender_alpha(gltf, material_idx, vertex_color): + """Set alpha.""" + pymaterial = gltf.data.materials[material_idx] + material = bpy.data.materials[pymaterial.blender_material[vertex_color]] + + node_tree = material.node_tree + # Add nodes for basic transparency + # Add mix shader between output and Principled BSDF + trans = node_tree.nodes.new('ShaderNodeBsdfTransparent') + trans.location = 750, -500 + mix = node_tree.nodes.new('ShaderNodeMixShader') + mix.location = 1000, 0 + + output_surface_input = get_output_surface_input(node_tree) + preoutput_node_output = get_preoutput_node_output(node_tree) + + link = output_surface_input.links[0] + node_tree.links.remove(link) + + # PBR => Mix input 1 + node_tree.links.new(preoutput_node_output, mix.inputs[1]) + + # Trans => Mix input 2 + node_tree.links.new(trans.outputs['BSDF'], mix.inputs[2]) + + # Mix => Output + node_tree.links.new(mix.outputs['Shader'], output_surface_input) + + # alpha blend factor + add = node_tree.nodes.new('ShaderNodeMath') + add.operation = 'ADD' + add.location = 750, -250 + + diffuse_factor = 1.0 + if pymaterial.extensions is not None and 'KHR_materials_pbrSpecularGlossiness' in pymaterial.extensions: + diffuse_factor = pymaterial.extensions['KHR_materials_pbrSpecularGlossiness']['diffuseFactor'][3] + elif pymaterial.pbr_metallic_roughness: + diffuse_factor = pymaterial.pbr_metallic_roughness.base_color_factor[3] + + add.inputs[0].default_value = abs(1.0 - diffuse_factor) + add.inputs[1].default_value = 0.0 + node_tree.links.new(add.outputs['Value'], mix.inputs[0]) + + # Take diffuse texture alpha into account if any + diffuse_texture = get_base_color_node(node_tree) + if diffuse_texture: + inverter = node_tree.nodes.new('ShaderNodeInvert') + inverter.location = 250, -250 + inverter.inputs[1].default_value = (1.0, 1.0, 1.0, 1.0) + node_tree.links.new(diffuse_texture.outputs['Alpha'], inverter.inputs[0]) + + mult = node_tree.nodes.new('ShaderNodeMath') + mult.operation = 'MULTIPLY' if pymaterial.alpha_mode == 'BLEND' else 'GREATER_THAN' + mult.location = 500, -250 + alpha_cutoff = 1.0 if pymaterial.alpha_mode == 'BLEND' else \ + 1.0 - pymaterial.alpha_cutoff if pymaterial.alpha_cutoff is not None else 0.5 + mult.inputs[1].default_value = alpha_cutoff + node_tree.links.new(inverter.outputs['Color'], mult.inputs[0]) + node_tree.links.new(mult.outputs['Value'], add.inputs[0]) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py new file mode 100755 index 00000000..2d346638 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py @@ -0,0 +1,175 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +import bmesh + +from .gltf2_blender_primitive import BlenderPrimitive +from ...io.imp.gltf2_io_binary import BinaryData +from ..com.gltf2_blender_conversion import loc_gltf_to_blender + + +class BlenderMesh(): + """Blender Mesh.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, mesh_idx, node_idx, parent): + """Mesh creation.""" + pymesh = gltf.data.meshes[mesh_idx] + + # Geometry + if pymesh.name: + mesh_name = pymesh.name + else: + mesh_name = "Mesh_" + str(mesh_idx) + + mesh = bpy.data.meshes.new(mesh_name) + verts = [] + edges = [] + faces = [] + for prim in pymesh.primitives: + verts, edges, faces = BlenderPrimitive.create(gltf, prim, verts, edges, faces) + + mesh.from_pydata(verts, edges, faces) + mesh.validate() + + pymesh.blender_name = mesh.name + + return mesh + + @staticmethod + def set_mesh(gltf, pymesh, mesh, obj): + """Set all data after mesh creation.""" + # Normals + offset = 0 + custom_normals = [[0.0, 0.0, 0.0]] * len(mesh.vertices) + + if gltf.import_settings['import_shading'] == "NORMALS": + mesh.create_normals_split() + + for prim in pymesh.primitives: + offset = BlenderPrimitive.set_normals(gltf, prim, mesh, offset, custom_normals) + + mesh.update() + + # manage UV + offset = 0 + for prim in pymesh.primitives: + offset = BlenderPrimitive.set_UV(gltf, prim, obj, mesh, offset) + + mesh.update() + + # Normals, now that every update is done + if gltf.import_settings['import_shading'] == "NORMALS": + mesh.normals_split_custom_set_from_vertices(custom_normals) + mesh.use_auto_smooth = True + + # Object and UV are now created, we can set UVMap into material + for prim in pymesh.primitives: + vertex_color = None + if 'COLOR_0' in prim.attributes.keys(): + vertex_color = 'COLOR_0' + BlenderPrimitive.set_UV_in_mat(gltf, prim, obj, vertex_color) + + # Assign materials to mesh + offset = 0 + cpt_index_mat = 0 + bm = bmesh.new() + bm.from_mesh(obj.data) + bm.faces.ensure_lookup_table() + for prim in pymesh.primitives: + offset, cpt_index_mat = BlenderPrimitive.assign_material(gltf, prim, obj, bm, offset, cpt_index_mat) + + bm.to_mesh(obj.data) + bm.free() + + # Create shapekeys if needed + max_shape_to_create = 0 + for prim in pymesh.primitives: + if prim.targets: + if len(prim.targets) > max_shape_to_create: + max_shape_to_create = len(prim.targets) + + # Create basis shape key + if max_shape_to_create > 0: + obj.shape_key_add(name="Basis") + + for i in range(max_shape_to_create): + + obj.shape_key_add(name="target_" + str(i)) + + offset_idx = 0 + for prim in pymesh.primitives: + if prim.targets is None: + continue + if i >= len(prim.targets): + continue + + bm = bmesh.new() + bm.from_mesh(mesh) + + shape_layer = bm.verts.layers.shape[i + 1] + + pos = BinaryData.get_data_from_accessor(gltf, prim.targets[i]['POSITION']) + + for vert in bm.verts: + if vert.index not in range(offset_idx, offset_idx + prim.vertices_length): + continue + + shape = vert[shape_layer] + + co = loc_gltf_to_blender(list(pos[vert.index - offset_idx])) + shape.x = obj.data.vertices[vert.index].co.x + co[0] + shape.y = obj.data.vertices[vert.index].co.y + co[1] + shape.z = obj.data.vertices[vert.index].co.z + co[2] + + bm.to_mesh(obj.data) + bm.free() + offset_idx += prim.vertices_length + + # set default weights for shape keys, and names + if pymesh.weights is not None: + for i in range(max_shape_to_create): + if i < len(pymesh.weights): + obj.data.shape_keys.key_blocks[i + 1].value = pymesh.weights[i] + if gltf.data.accessors[pymesh.primitives[0].targets[i]['POSITION']].name is not None: + obj.data.shape_keys.key_blocks[i + 1].name = \ + gltf.data.accessors[pymesh.primitives[0].targets[i]['POSITION']].name + + # Apply vertex color. + vertex_color = None + offset = 0 + for prim in pymesh.primitives: + if 'COLOR_0' in prim.attributes.keys(): + # Create vertex color, once only per object + if vertex_color is None: + vertex_color = obj.data.vertex_colors.new(name="COLOR_0") + + color_data = BinaryData.get_data_from_accessor(gltf, prim.attributes['COLOR_0']) + + for poly in mesh.polygons: + for loop_idx in range(poly.loop_start, poly.loop_start + poly.loop_total): + vert_idx = mesh.loops[loop_idx].vertex_index + if vert_idx in range(offset, offset + prim.vertices_length): + cpt_idx = vert_idx - offset + # check dimension, and add alpha if needed + if len(color_data[cpt_idx]) == 3: + vertex_color_data = color_data[cpt_idx] + (1.0,) + else: + vertex_color_data = color_data[cpt_idx] + vertex_color.data[loop_idx].color = vertex_color_data + offset = offset + prim.vertices_length + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_node.py new file mode 100755 index 00000000..a82f1db7 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_node.py @@ -0,0 +1,184 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from .gltf2_blender_mesh import BlenderMesh +from .gltf2_blender_camera import BlenderCamera +from .gltf2_blender_skin import BlenderSkin +from ..com.gltf2_blender_conversion import scale_to_matrix, matrix_gltf_to_blender + + +class BlenderNode(): + """Blender Node.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, node_idx, parent): + """Node creation.""" + pynode = gltf.data.nodes[node_idx] + + # Blender attributes initialization + pynode.blender_object = "" + pynode.parent = parent + + if pynode.mesh is not None: + + if gltf.data.meshes[pynode.mesh].blender_name is not None: + # Mesh is already created, only create instance + mesh = bpy.data.meshes[gltf.data.meshes[pynode.mesh].blender_name] + else: + if pynode.name: + gltf.log.info("Blender create Mesh node " + pynode.name) + else: + gltf.log.info("Blender create Mesh node") + + mesh = BlenderMesh.create(gltf, pynode.mesh, node_idx, parent) + + if pynode.name: + name = pynode.name + else: + # Take mesh name if exist + if gltf.data.meshes[pynode.mesh].name: + name = gltf.data.meshes[pynode.mesh].name + else: + name = "Object_" + str(node_idx) + + obj = bpy.data.objects.new(name, mesh) + obj.rotation_mode = 'QUATERNION' + bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) + + # Transforms apply only if this mesh is not skinned + # See implementation node of gltf2 specification + if not (pynode.mesh and pynode.skin is not None): + BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent) + pynode.blender_object = obj.name + BlenderNode.set_parent(gltf, pynode, obj, parent) + + BlenderMesh.set_mesh(gltf, gltf.data.meshes[pynode.mesh], mesh, obj) + + if pynode.children: + for child_idx in pynode.children: + BlenderNode.create(gltf, child_idx, node_idx) + + return + + if pynode.camera is not None: + if pynode.name: + gltf.log.info("Blender create Camera node " + pynode.name) + else: + gltf.log.info("Blender create Camera node") + obj = BlenderCamera.create(gltf, pynode.camera) + BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent) # TODO default rotation of cameras ? + pynode.blender_object = obj.name + BlenderNode.set_parent(gltf, pynode, obj, parent) + + return + + if pynode.is_joint: + if pynode.name: + gltf.log.info("Blender create Bone node " + pynode.name) + else: + gltf.log.info("Blender create Bone node") + # Check if corresponding armature is already created, create it if needed + if gltf.data.skins[pynode.skin_id].blender_armature_name is None: + BlenderSkin.create_armature(gltf, pynode.skin_id, parent) + + BlenderSkin.create_bone(gltf, pynode.skin_id, node_idx, parent) + + if pynode.children: + for child_idx in pynode.children: + BlenderNode.create(gltf, child_idx, node_idx) + + return + + # No mesh, no camera. For now, create empty #TODO + + if pynode.name: + gltf.log.info("Blender create Empty node " + pynode.name) + obj = bpy.data.objects.new(pynode.name, None) + else: + gltf.log.info("Blender create Empty node") + obj = bpy.data.objects.new("Node", None) + obj.rotation_mode = 'QUATERNION' + bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) + BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent) + pynode.blender_object = obj.name + BlenderNode.set_parent(gltf, pynode, obj, parent) + + if pynode.children: + for child_idx in pynode.children: + BlenderNode.create(gltf, child_idx, node_idx) + + @staticmethod + def set_parent(gltf, pynode, obj, parent): + """Set parent.""" + if parent is None: + return + + for node_idx, node in enumerate(gltf.data.nodes): + if node_idx == parent: + if node.is_joint is True: + bpy.ops.object.select_all(action='DESELECT') + bpy.data.objects[node.blender_armature_name].select_set(True) + bpy.context.view_layer.objects.active = bpy.data.objects[node.blender_armature_name] + + bpy.ops.object.mode_set(mode='EDIT') + bpy.data.objects[node.blender_armature_name].data.edit_bones.active = \ + bpy.data.objects[node.blender_armature_name].data.edit_bones[node.blender_bone_name] + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(True) + bpy.data.objects[node.blender_armature_name].select_set(True) + bpy.context.view_layer.objects.active = bpy.data.objects[node.blender_armature_name] + bpy.context.scene.update() + bpy.ops.object.parent_set(type='BONE_RELATIVE', keep_transform=True) + # From world transform to local (-armature transform -bone transform) + bone_trans = bpy.data.objects[node.blender_armature_name] \ + .pose.bones[node.blender_bone_name].matrix.to_translation().copy() + bone_rot = bpy.data.objects[node.blender_armature_name] \ + .pose.bones[node.blender_bone_name].matrix.to_quaternion().copy() + bone_scale_mat = scale_to_matrix(node.blender_bone_matrix.to_scale()) + obj.location = bone_scale_mat @ obj.location + obj.location = bone_rot @ obj.location + obj.location += bone_trans + obj.location = bpy.data.objects[node.blender_armature_name].matrix_world.to_quaternion() \ + @ obj.location + obj.rotation_quaternion = obj.rotation_quaternion \ + @ bpy.data.objects[node.blender_armature_name].matrix_world.to_quaternion() + obj.scale = bone_scale_mat @ obj.scale + + return + if node.blender_object: + obj.parent = bpy.data.objects[node.blender_object] + return + + gltf.log.error("ERROR, parent not found") + + @staticmethod + def set_transforms(gltf, node_idx, pynode, obj, parent): + """Set transforms.""" + if parent is None: + obj.matrix_world = matrix_gltf_to_blender(pynode.transform) + return + + for idx, node in enumerate(gltf.data.nodes): + if idx == parent: + if node.is_joint is True: + obj.matrix_world = matrix_gltf_to_blender(pynode.transform) + return + else: + obj.matrix_world = matrix_gltf_to_blender(pynode.transform) + return + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py b/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py new file mode 100755 index 00000000..a86923ff --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_pbrMetallicRoughness.py @@ -0,0 +1,284 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from .gltf2_blender_texture import BlenderTextureInfo + + +class BlenderPbr(): + """Blender Pbr.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + def create(gltf, pypbr, mat_name, vertex_color): + """Pbr creation.""" + engine = bpy.context.scene.render.engine + if engine in ['CYCLES', 'BLENDER_EEVEE']: + BlenderPbr.create_nodetree(gltf, pypbr, mat_name, vertex_color) + + def create_nodetree(gltf, pypbr, mat_name, vertex_color): + """Nodetree creation.""" + material = bpy.data.materials[mat_name] + material.use_nodes = True + node_tree = material.node_tree + + # If there is no diffuse texture, but only a color, wihtout + # vertex_color, we set this color in viewport color + if pypbr.color_type == gltf.SIMPLE and not vertex_color: + material.diffuse_color = pypbr.base_color_factor[:3] + + # delete all nodes except output + for node in list(node_tree.nodes): + if not node.type == 'OUTPUT_MATERIAL': + node_tree.nodes.remove(node) + + output_node = node_tree.nodes[0] + output_node.location = 1250, 0 + + # create PBR node + principled = node_tree.nodes.new('ShaderNodeBsdfPrincipled') + principled.location = 0, 0 + + if pypbr.color_type == gltf.SIMPLE: + + if not vertex_color: + + # change input values + principled.inputs[0].default_value = pypbr.base_color_factor + # TODO : currently set metallic & specular in same way + principled.inputs[5].default_value = pypbr.metallic_factor + principled.inputs[7].default_value = pypbr.roughness_factor + + else: + # Create attribute node to get COLOR_0 data + attribute_node = node_tree.nodes.new('ShaderNodeAttribute') + attribute_node.attribute_name = 'COLOR_0' + attribute_node.location = -500, 0 + + # TODO : currently set metallic & specular in same way + principled.inputs[5].default_value = pypbr.metallic_factor + principled.inputs[7].default_value = pypbr.roughness_factor + + # links + rgb_node = node_tree.nodes.new('ShaderNodeMixRGB') + rgb_node.blend_type = 'MULTIPLY' + rgb_node.inputs['Fac'].default_value = 1.0 + rgb_node.inputs['Color1'].default_value = pypbr.base_color_factor + node_tree.links.new(rgb_node.inputs['Color2'], attribute_node.outputs[0]) + node_tree.links.new(principled.inputs[0], rgb_node.outputs[0]) + + elif pypbr.color_type == gltf.TEXTURE_FACTOR: + + # TODO alpha ? + if vertex_color: + # TODO tree locations + # Create attribute / separate / math nodes + attribute_node = node_tree.nodes.new('ShaderNodeAttribute') + attribute_node.attribute_name = 'COLOR_0' + + vc_mult_node = node_tree.nodes.new('ShaderNodeMixRGB') + vc_mult_node.blend_type = 'MULTIPLY' + vc_mult_node.inputs['Fac'].default_value = 0.5 + + BlenderTextureInfo.create(gltf, pypbr.base_color_texture.index) + + # create UV Map / Mapping / Texture nodes / separate & math and combine + text_node = node_tree.nodes.new('ShaderNodeTexImage') + text_node.image = bpy.data.images[gltf.data.images[ + gltf.data.textures[pypbr.base_color_texture.index].source + ].blender_image_name] + text_node.label = 'BASE COLOR' + text_node.location = -1000, 500 + + mult_node = node_tree.nodes.new('ShaderNodeMixRGB') + mult_node.blend_type = 'MULTIPLY' + mult_node.inputs['Fac'].default_value = 0.5 + mult_node.inputs['Color2'].default_value = [ + pypbr.base_color_factor[0], + pypbr.base_color_factor[1], + pypbr.base_color_factor[2], + pypbr.base_color_factor[3], + ] + + mapping = node_tree.nodes.new('ShaderNodeMapping') + mapping.location = -1500, 500 + + uvmap = node_tree.nodes.new('ShaderNodeUVMap') + uvmap.location = -2000, 500 + if pypbr.base_color_texture.tex_coord is not None: + uvmap["gltf2_texcoord"] = pypbr.base_color_texture.tex_coord # Set custom flag to retrieve TexCoord + else: + uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here + # UV Map will be set after object/UVMap creation + + # Create links + if vertex_color: + node_tree.links.new(vc_mult_node.inputs[2], attribute_node.outputs[0]) + node_tree.links.new(vc_mult_node.inputs[1], mult_node.outputs[0]) + node_tree.links.new(principled.inputs[0], vc_mult_node.outputs[0]) + + else: + node_tree.links.new(principled.inputs[0], mult_node.outputs[0]) + + # Common for both mode (non vertex color / vertex color) + node_tree.links.new(mapping.inputs[0], uvmap.outputs[0]) + node_tree.links.new(text_node.inputs[0], mapping.outputs[0]) + node_tree.links.new(mult_node.inputs[1], text_node.outputs[0]) + + elif pypbr.color_type == gltf.TEXTURE: + + BlenderTextureInfo.create(gltf, pypbr.base_color_texture.index) + + # TODO alpha ? + if vertex_color: + # Create attribute / separate / math nodes + attribute_node = node_tree.nodes.new('ShaderNodeAttribute') + attribute_node.attribute_name = 'COLOR_0' + attribute_node.location = -2000, 250 + + vc_mult_node = node_tree.nodes.new('ShaderNodeMixRGB') + vc_mult_node.blend_type = 'MULTIPLY' + vc_mult_node.inputs['Fac'].default_value = 0.5 + + # create UV Map / Mapping / Texture nodes / separate & math and combine + text_node = node_tree.nodes.new('ShaderNodeTexImage') + text_node.image = bpy.data.images[gltf.data.images[ + gltf.data.textures[pypbr.base_color_texture.index].source + ].blender_image_name] + text_node.label = 'BASE COLOR' + if vertex_color: + text_node.location = -2000, 500 + else: + text_node.location = -500, 500 + + mapping = node_tree.nodes.new('ShaderNodeMapping') + if vertex_color: + mapping.location = -2500, 500 + else: + mapping.location = -1500, 500 + + uvmap = node_tree.nodes.new('ShaderNodeUVMap') + if vertex_color: + uvmap.location = -3000, 500 + else: + uvmap.location = -2000, 500 + if pypbr.base_color_texture.tex_coord is not None: + uvmap["gltf2_texcoord"] = pypbr.base_color_texture.tex_coord # Set custom flag to retrieve TexCoord + else: + uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here + # UV Map will be set after object/UVMap creation + + # Create links + if vertex_color: + node_tree.links.new(vc_mult_node.inputs[2], attribute_node.outputs[0]) + node_tree.links.new(vc_mult_node.inputs[1], text_node.outputs[0]) + node_tree.links.new(principled.inputs[0], vc_mult_node.outputs[0]) + + else: + node_tree.links.new(principled.inputs[0], text_node.outputs[0]) + + # Common for both mode (non vertex color / vertex color) + + node_tree.links.new(mapping.inputs[0], uvmap.outputs[0]) + node_tree.links.new(text_node.inputs[0], mapping.outputs[0]) + + # Says metallic, but it means metallic & Roughness values + if pypbr.metallic_type == gltf.SIMPLE: + principled.inputs[4].default_value = pypbr.metallic_factor + principled.inputs[7].default_value = pypbr.roughness_factor + + elif pypbr.metallic_type == gltf.TEXTURE: + BlenderTextureInfo.create(gltf, pypbr.metallic_roughness_texture.index) + metallic_text = node_tree.nodes.new('ShaderNodeTexImage') + metallic_text.image = bpy.data.images[gltf.data.images[ + gltf.data.textures[pypbr.metallic_roughness_texture.index].source + ].blender_image_name] + metallic_text.color_space = 'NONE' + metallic_text.label = 'METALLIC ROUGHNESS' + metallic_text.location = -500, 0 + + metallic_separate = node_tree.nodes.new('ShaderNodeSeparateRGB') + metallic_separate.location = -250, 0 + + metallic_mapping = node_tree.nodes.new('ShaderNodeMapping') + metallic_mapping.location = -1000, 0 + + metallic_uvmap = node_tree.nodes.new('ShaderNodeUVMap') + metallic_uvmap.location = -1500, 0 + if pypbr.metallic_roughness_texture.tex_coord is not None: + # Set custom flag to retrieve TexCoord + metallic_uvmap["gltf2_texcoord"] = pypbr.metallic_roughness_texture.tex_coord + else: + metallic_uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here + + # links + node_tree.links.new(metallic_separate.inputs[0], metallic_text.outputs[0]) + node_tree.links.new(principled.inputs[4], metallic_separate.outputs[2]) # metallic + node_tree.links.new(principled.inputs[7], metallic_separate.outputs[1]) # Roughness + + node_tree.links.new(metallic_mapping.inputs[0], metallic_uvmap.outputs[0]) + node_tree.links.new(metallic_text.inputs[0], metallic_mapping.outputs[0]) + + elif pypbr.metallic_type == gltf.TEXTURE_FACTOR: + + BlenderTextureInfo.create(gltf, pypbr.metallic_roughness_texture.index) + metallic_text = node_tree.nodes.new('ShaderNodeTexImage') + metallic_text.image = bpy.data.images[gltf.data.images[ + gltf.data.textures[pypbr.metallic_roughness_texture.index].source + ].blender_image_name] + metallic_text.color_space = 'NONE' + metallic_text.label = 'METALLIC ROUGHNESS' + metallic_text.location = -1000, 0 + + metallic_separate = node_tree.nodes.new('ShaderNodeSeparateRGB') + metallic_separate.location = -500, 0 + + metallic_math = node_tree.nodes.new('ShaderNodeMath') + metallic_math.operation = 'MULTIPLY' + metallic_math.inputs[1].default_value = pypbr.metallic_factor + metallic_math.location = -250, 100 + + roughness_math = node_tree.nodes.new('ShaderNodeMath') + roughness_math.operation = 'MULTIPLY' + roughness_math.inputs[1].default_value = pypbr.roughness_factor + roughness_math.location = -250, -100 + + metallic_mapping = node_tree.nodes.new('ShaderNodeMapping') + metallic_mapping.location = -1000, 0 + + metallic_uvmap = node_tree.nodes.new('ShaderNodeUVMap') + metallic_uvmap.location = -1500, 0 + if pypbr.metallic_roughness_texture.tex_coord is not None: + # Set custom flag to retrieve TexCoord + metallic_uvmap["gltf2_texcoord"] = pypbr.metallic_roughness_texture.tex_coord + else: + metallic_uvmap["gltf2_texcoord"] = 0 # TODO set in pre_compute instead of here + + # links + node_tree.links.new(metallic_separate.inputs[0], metallic_text.outputs[0]) + + # metallic + node_tree.links.new(metallic_math.inputs[0], metallic_separate.outputs[2]) + node_tree.links.new(principled.inputs[4], metallic_math.outputs[0]) + + # roughness + node_tree.links.new(roughness_math.inputs[0], metallic_separate.outputs[1]) + node_tree.links.new(principled.inputs[7], roughness_math.outputs[0]) + + node_tree.links.new(metallic_mapping.inputs[0], metallic_uvmap.outputs[0]) + node_tree.links.new(metallic_text.inputs[0], metallic_mapping.outputs[0]) + + # link node to output + node_tree.links.new(output_node.inputs[0], principled.outputs[0]) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py b/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py new file mode 100755 index 00000000..59e13391 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py @@ -0,0 +1,181 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from mathutils import Vector + +from .gltf2_blender_material import BlenderMaterial +from ..com.gltf2_blender_conversion import loc_gltf_to_blender +from ...io.imp.gltf2_io_binary import BinaryData + + +class BlenderPrimitive(): + """Blender Primitive.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, pyprimitive, verts, edges, faces): + """Primitive creation.""" + pyprimitive.blender_texcoord = {} + + # TODO mode of primitive 4 for now. + current_length = len(verts) + pos = BinaryData.get_data_from_accessor(gltf, pyprimitive.attributes['POSITION']) + if pyprimitive.indices is not None: + indices = BinaryData.get_data_from_accessor(gltf, pyprimitive.indices) + else: + indices = [] + indices_ = range(0, len(pos)) + for i in indices_: + indices.append((i,)) + + prim_verts = [loc_gltf_to_blender(vert) for vert in pos] + pyprimitive.vertices_length = len(prim_verts) + verts.extend(prim_verts) + prim_faces = [] + for i in range(0, len(indices), 3): + vals = indices[i:i + 3] + new_vals = [] + for y in vals: + new_vals.append(y[0] + current_length) + prim_faces.append(tuple(new_vals)) + faces.extend(prim_faces) + pyprimitive.faces_length = len(prim_faces) + + # manage material of primitive + if pyprimitive.material is not None: + + vertex_color = None + if 'COLOR_0' in pyprimitive.attributes.keys(): + vertex_color = 'COLOR_0' + + # Create Blender material if needed + if vertex_color is None: + if None not in gltf.data.materials[pyprimitive.material].blender_material.keys(): + BlenderMaterial.create(gltf, pyprimitive.material, vertex_color) + else: + if vertex_color not in gltf.data.materials[pyprimitive.material].blender_material.keys(): + BlenderMaterial.create(gltf, pyprimitive.material, vertex_color) + + + return verts, edges, faces + + def set_normals(gltf, pyprimitive, mesh, offset, custom_normals): + """Set Normal.""" + if 'NORMAL' in pyprimitive.attributes.keys(): + normal_data = BinaryData.get_data_from_accessor(gltf, pyprimitive.attributes['NORMAL']) + for poly in mesh.polygons: + if gltf.import_settings['import_shading'] == "NORMALS": + calc_norm_vertices = [] + for loop_idx in range(poly.loop_start, poly.loop_start + poly.loop_total): + vert_idx = mesh.loops[loop_idx].vertex_index + if vert_idx in range(offset, offset + pyprimitive.vertices_length): + cpt_vert = vert_idx - offset + mesh.vertices[vert_idx].normal = normal_data[cpt_vert] + custom_normals[vert_idx] = list(normal_data[cpt_vert]) + calc_norm_vertices.append(vert_idx) + + if len(calc_norm_vertices) == 3: + # Calcul normal + vert0 = mesh.vertices[calc_norm_vertices[0]].co + vert1 = mesh.vertices[calc_norm_vertices[1]].co + vert2 = mesh.vertices[calc_norm_vertices[2]].co + calc_normal = (vert1 - vert0).cross(vert2 - vert0).normalized() + + # Compare normal to vertex normal + for i in calc_norm_vertices: + cpt_vert = vert_idx - offset + vec = Vector( + (normal_data[cpt_vert][0], normal_data[cpt_vert][1], normal_data[cpt_vert][2]) + ) + if not calc_normal.dot(vec) > 0.9999999: + poly.use_smooth = True + break + elif gltf.import_settings['import_shading'] == "FLAT": + poly.use_smooth = False + elif gltf.import_settings['import_shading'] == "SMOOTH": + poly.use_smooth = True + else: + pass # Should not happend + + offset = offset + pyprimitive.vertices_length + return offset + + def set_UV(gltf, pyprimitive, obj, mesh, offset): + """Set UV Map.""" + for texcoord in [attr for attr in pyprimitive.attributes.keys() if attr[:9] == "TEXCOORD_"]: + if texcoord not in mesh.uv_layers: + mesh.uv_layers.new(name=texcoord) + pyprimitive.blender_texcoord[int(texcoord[9:])] = texcoord + + texcoord_data = BinaryData.get_data_from_accessor(gltf, pyprimitive.attributes[texcoord]) + for poly in mesh.polygons: + for loop_idx in range(poly.loop_start, poly.loop_start + poly.loop_total): + vert_idx = mesh.loops[loop_idx].vertex_index + if vert_idx in range(offset, offset + pyprimitive.vertices_length): + obj.data.uv_layers[texcoord].data[loop_idx].uv = \ + Vector((texcoord_data[vert_idx - offset][0], 1 - texcoord_data[vert_idx - offset][1])) + + offset = offset + pyprimitive.vertices_length + return offset + + def set_UV_in_mat(gltf, pyprimitive, obj, vertex_color): + """After nodetree creation, set UVMap in nodes.""" + if pyprimitive.material is None: + return + if gltf.data.materials[pyprimitive.material].extensions \ + and "KHR_materials_pbrSpecularGlossiness" in \ + gltf.data.materials[pyprimitive.material].extensions.keys(): + if pyprimitive.material is not None \ + and gltf.data.materials[pyprimitive.material].extensions[ + 'KHR_materials_pbrSpecularGlossiness' + ]['diffuse_type'] in [gltf.TEXTURE, gltf.TEXTURE_FACTOR]: + BlenderMaterial.set_uvmap(gltf, pyprimitive.material, pyprimitive, obj, vertex_color) + else: + if pyprimitive.material is not None \ + and gltf.data.materials[pyprimitive.material].extensions[ + 'KHR_materials_pbrSpecularGlossiness' + ]['specgloss_type'] in [gltf.TEXTURE, gltf.TEXTURE_FACTOR]: + BlenderMaterial.set_uvmap(gltf, pyprimitive.material, pyprimitive, obj, vertex_color) + + else: + if pyprimitive.material is not None \ + and gltf.data.materials[pyprimitive.material].pbr_metallic_roughness.color_type in \ + [gltf.TEXTURE, gltf.TEXTURE_FACTOR]: + BlenderMaterial.set_uvmap(gltf, pyprimitive.material, pyprimitive, obj, vertex_color) + else: + if pyprimitive.material is not None \ + and gltf.data.materials[pyprimitive.material].pbr_metallic_roughness.metallic_type in \ + [gltf.TEXTURE, gltf.TEXTURE_FACTOR]: + BlenderMaterial.set_uvmap(gltf, pyprimitive.material, pyprimitive, obj, vertex_color) + + def assign_material(gltf, pyprimitive, obj, bm, offset, cpt_index_mat): + """Assign material to faces of primitives.""" + if pyprimitive.material is not None: + + vertex_color = None + if 'COLOR_0' in pyprimitive.attributes.keys(): + vertex_color = 'COLOR_0' + + obj.data.materials.append(bpy.data.materials[gltf.data.materials[pyprimitive.material].blender_material[vertex_color]]) + for vert in bm.verts: + if vert.index in range(offset, offset + pyprimitive.vertices_length): + for loop in vert.link_loops: + face = loop.face.index + bm.faces[face].material_index = cpt_index_mat + cpt_index_mat += 1 + offset = offset + pyprimitive.vertices_length + return offset, cpt_index_mat + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_scene.py b/io_scene_gltf2/blender/imp/gltf2_blender_scene.py new file mode 100755 index 00000000..6c2f14cb --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_scene.py @@ -0,0 +1,98 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import bpy +from math import sqrt +from mathutils import Quaternion +from .gltf2_blender_node import BlenderNode +from .gltf2_blender_skin import BlenderSkin +from .gltf2_blender_animation import BlenderAnimation + + +class BlenderScene(): + """Blender Scene.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, scene_idx): + """Scene creation.""" + pyscene = gltf.data.scenes[scene_idx] + + # Create a new scene only if not already exists in .blend file + # TODO : put in current scene instead ? + if pyscene.name not in [scene.name for scene in bpy.data.scenes]: + # TODO: There is a bug in 2.8 alpha that break CLEAR_KEEP_TRANSFORM + # if we are creating a new scene + scene = bpy.context.scene + scene.render.engine = "BLENDER_EEVEE" + + gltf.blender_scene = scene.name + else: + gltf.blender_scene = pyscene.name + + # Switch to newly created main scene + bpy.context.window.scene = bpy.data.scenes[gltf.blender_scene] + + # Create Yup2Zup empty + obj_rotation = bpy.data.objects.new("Yup2Zup", None) + obj_rotation.rotation_mode = 'QUATERNION' + obj_rotation.rotation_quaternion = Quaternion((sqrt(2) / 2, sqrt(2) / 2, 0.0, 0.0)) + + bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj_rotation) + + if pyscene.nodes is not None: + for node_idx in pyscene.nodes: + BlenderNode.create(gltf, node_idx, None) # None => No parent + + # Now that all mesh / bones are created, create vertex groups on mesh + if gltf.data.skins: + for skin_id, skin in enumerate(gltf.data.skins): + if hasattr(skin, "node_ids"): + BlenderSkin.create_vertex_groups(gltf, skin_id) + + for skin_id, skin in enumerate(gltf.data.skins): + if hasattr(skin, "node_ids"): + BlenderSkin.assign_vertex_groups(gltf, skin_id) + + for skin_id, skin in enumerate(gltf.data.skins): + if hasattr(skin, "node_ids"): + BlenderSkin.create_armature_modifiers(gltf, skin_id) + + if gltf.data.animations: + for anim_idx, anim in enumerate(gltf.data.animations): + if pyscene.nodes is not None: + for node_idx in pyscene.nodes: + BlenderAnimation.anim(gltf, anim_idx, node_idx) + + # Parent root node to rotation object + if pyscene.nodes is not None: + for node_idx in pyscene.nodes: + bpy.data.objects[gltf.data.nodes[node_idx].blender_object].parent = obj_rotation + + if gltf.animation_object is False: + + + for node_idx in pyscene.nodes: + for obj_ in bpy.context.scene.objects: + obj_.select_set(False) + bpy.data.objects[gltf.data.nodes[node_idx].blender_object].select_set(True) + bpy.context.view_layer.objects.active = bpy.data.objects[gltf.data.nodes[node_idx].blender_object] + + bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') + + # remove object + bpy.context.scene.collection.objects.unlink(obj_rotation) + bpy.data.objects.remove(obj_rotation) + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_skin.py b/io_scene_gltf2/blender/imp/gltf2_blender_skin.py new file mode 100755 index 00000000..db0e50f9 --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_skin.py @@ -0,0 +1,209 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import bpy +from mathutils import Vector, Matrix +from ..com.gltf2_blender_conversion import matrix_gltf_to_blender, scale_to_matrix +from ...io.imp.gltf2_io_binary import BinaryData + + +class BlenderSkin(): + """Blender Skinning / Armature.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create_armature(gltf, skin_id, parent): + """Armature creation.""" + pyskin = gltf.data.skins[skin_id] + + if pyskin.name is not None: + name = pyskin.name + else: + name = "Armature_" + str(skin_id) + + armature = bpy.data.armatures.new(name) + obj = bpy.data.objects.new(name, armature) + bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) + pyskin.blender_armature_name = obj.name + if parent is not None: + obj.parent = bpy.data.objects[gltf.data.nodes[parent].blender_object] + + @staticmethod + def set_bone_transforms(gltf, skin_id, bone, node_id, parent): + """Set bone transformations.""" + pyskin = gltf.data.skins[skin_id] + pynode = gltf.data.nodes[node_id] + + obj = bpy.data.objects[pyskin.blender_armature_name] + + # Set bone bind_pose by inverting bindpose matrix + if node_id in pyskin.joints: + index_in_skel = pyskin.joints.index(node_id) + inverse_bind_matrices = BinaryData.get_data_from_accessor(gltf, pyskin.inverse_bind_matrices) + # Needed to keep scale in matrix, as bone.matrix seems to drop it + if index_in_skel < len(inverse_bind_matrices): + pynode.blender_bone_matrix = matrix_gltf_to_blender( + inverse_bind_matrices[index_in_skel] + ).inverted() + bone.matrix = pynode.blender_bone_matrix + else: + gltf.log.error("Error with inverseBindMatrix for skin " + pyskin) + else: + print('No invBindMatrix for bone ' + str(node_id)) + pynode.blender_bone_matrix = Matrix() + + # Parent the bone + if parent is not None and hasattr(gltf.data.nodes[parent], "blender_bone_name"): + bone.parent = obj.data.edit_bones[gltf.data.nodes[parent].blender_bone_name] # TODO if in another scene + + # Switch to Pose mode + bpy.ops.object.mode_set(mode="POSE") + obj.data.pose_position = 'POSE' + + # Set posebone location/rotation/scale (in armature space) + # location is actual bone location minus it's original (bind) location + bind_location = Matrix.Translation(pynode.blender_bone_matrix.to_translation()) + bind_rotation = pynode.blender_bone_matrix.to_quaternion() + bind_scale = scale_to_matrix(pynode.blender_bone_matrix.to_scale()) + + location, rotation, scale = matrix_gltf_to_blender(pynode.transform).decompose() + if parent is not None and hasattr(gltf.data.nodes[parent], "blender_bone_matrix"): + parent_mat = gltf.data.nodes[parent].blender_bone_matrix + + # Get armature space location (bindpose + pose) + # Then, remove original bind location from armspace location, and bind rotation + final_location = (bind_location.inverted() @ parent_mat @ Matrix.Translation(location)).to_translation() + obj.pose.bones[pynode.blender_bone_name].location = \ + bind_rotation.inverted().to_matrix().to_4x4() @ final_location + + # Do the same for rotation + obj.pose.bones[pynode.blender_bone_name].rotation_quaternion = \ + (bind_rotation.to_matrix().to_4x4().inverted() @ parent_mat @ + rotation.to_matrix().to_4x4()).to_quaternion() + obj.pose.bones[pynode.blender_bone_name].scale = \ + (bind_scale.inverted() @ parent_mat @ scale_to_matrix(scale)).to_scale() + + else: + obj.pose.bones[pynode.blender_bone_name].location = bind_location.inverted() @ location + obj.pose.bones[pynode.blender_bone_name].rotation_quaternion = bind_rotation.inverted() @ rotation + obj.pose.bones[pynode.blender_bone_name].scale = bind_scale.inverted() @ scale + + @staticmethod + def create_bone(gltf, skin_id, node_id, parent): + """Bone creation.""" + pyskin = gltf.data.skins[skin_id] + pynode = gltf.data.nodes[node_id] + + scene = bpy.data.scenes[gltf.blender_scene] + obj = bpy.data.objects[pyskin.blender_armature_name] + + bpy.context.window.scene = scene + bpy.context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode="EDIT") + + if pynode.name: + name = pynode.name + else: + name = "Bone_" + str(node_id) + + bone = obj.data.edit_bones.new(name) + pynode.blender_bone_name = bone.name + pynode.blender_armature_name = pyskin.blender_armature_name + bone.tail = Vector((0.0, 1.0, 0.0)) # Needed to keep bone alive + + # set bind and pose transforms + BlenderSkin.set_bone_transforms(gltf, skin_id, bone, node_id, parent) + bpy.ops.object.mode_set(mode="OBJECT") + + @staticmethod + def create_vertex_groups(gltf, skin_id): + """Vertex Group creation.""" + pyskin = gltf.data.skins[skin_id] + for node_id in pyskin.node_ids: + obj = bpy.data.objects[gltf.data.nodes[node_id].blender_object] + for bone in pyskin.joints: + obj.vertex_groups.new(name=gltf.data.nodes[bone].blender_bone_name) + + @staticmethod + def assign_vertex_groups(gltf, skin_id): + """Assign vertex groups to vertices.""" + pyskin = gltf.data.skins[skin_id] + for node_id in pyskin.node_ids: + node = gltf.data.nodes[node_id] + obj = bpy.data.objects[node.blender_object] + + offset = 0 + for prim in gltf.data.meshes[node.mesh].primitives: + idx_already_done = {} + + if 'JOINTS_0' in prim.attributes.keys() and 'WEIGHTS_0' in prim.attributes.keys(): + joint_ = BinaryData.get_data_from_accessor(gltf, prim.attributes['JOINTS_0']) + weight_ = BinaryData.get_data_from_accessor(gltf, prim.attributes['WEIGHTS_0']) + + for poly in obj.data.polygons: + for loop_idx in range(poly.loop_start, poly.loop_start + poly.loop_total): + vert_idx = obj.data.loops[loop_idx].vertex_index + + if vert_idx in idx_already_done.keys(): + continue + idx_already_done[vert_idx] = True + + if vert_idx in range(offset, offset + prim.vertices_length): + + tab_index = vert_idx - offset + cpt = 0 + for joint_idx in joint_[tab_index]: + weight_val = weight_[tab_index][cpt] + if weight_val != 0.0: # It can be a problem to assign weights of 0 + # for bone index 0, if there is always 4 indices in joint_ + # tuple + group = obj.vertex_groups[gltf.data.nodes[ + pyskin.joints[joint_idx] + ].blender_bone_name] + group.add([vert_idx], weight_val, 'REPLACE') + cpt += 1 + else: + gltf.log.error("No Skinning ?????") # TODO + + offset = offset + prim.vertices_length + + @staticmethod + def create_armature_modifiers(gltf, skin_id): + """Create Armature modifier.""" + pyskin = gltf.data.skins[skin_id] + + if pyskin.blender_armature_name is None: + # TODO seems something is wrong + # For example, some joints are in skin 0, and are in another skin too + # Not sure this is glTF compliant, will check it + return + + for node_id in pyskin.node_ids: + node = gltf.data.nodes[node_id] + obj = bpy.data.objects[node.blender_object] + + for obj_sel in bpy.context.scene.objects: + obj_sel.select_set(False) + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + + # bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') + # Reparent skinned mesh to it's armature to avoid breaking + # skinning with interleaved transforms + obj.parent = bpy.data.objects[pyskin.blender_armature_name] + arma = obj.modifiers.new(name="Armature", type="ARMATURE") + arma.object = bpy.data.objects[pyskin.blender_armature_name] + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_texture.py b/io_scene_gltf2/blender/imp/gltf2_blender_texture.py new file mode 100755 index 00000000..c8983d9c --- /dev/null +++ b/io_scene_gltf2/blender/imp/gltf2_blender_texture.py @@ -0,0 +1,39 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .gltf2_blender_image import BlenderImage + + +class BlenderTextureInfo(): + """Blender Texture info.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, pytextureinfo_idx): + """Create Texture info.""" + BlenderTexture.create(gltf, pytextureinfo_idx) + + +class BlenderTexture(): + """Blender Texture.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def create(gltf, pytexture_idx): + """Create texture.""" + pytexture = gltf.data.textures[pytexture_idx] + BlenderImage.create(gltf, pytexture.source) + diff --git a/io_scene_gltf2/io/__init__.py b/io_scene_gltf2/io/__init__.py new file mode 100755 index 00000000..10973240 --- /dev/null +++ b/io_scene_gltf2/io/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .imp import * + diff --git a/io_scene_gltf2/io/com/gltf2_io.py b/io_scene_gltf2/io/com/gltf2_io.py new file mode 100755 index 00000000..1332adf6 --- /dev/null +++ b/io_scene_gltf2/io/com/gltf2_io.py @@ -0,0 +1,1200 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# NOTE: Generated from latest glTF 2.0 JSON Scheme specs using quicktype (https://github.com/quicktype/quicktype) +# command used: +# quicktype --src glTF.schema.json --src-lang schema -t gltf --lang python --python-version 3.5 + +# TODO: add __slots__ to all classes by extending the generator + +# TODO: REMOVE traceback import +import sys +import traceback + +from io_scene_gltf2.io.com import gltf2_io_debug + + +def from_int(x): + assert isinstance(x, int) and not isinstance(x, bool) + return x + + +def from_none(x): + assert x is None + return x + + +def from_union(fs, x): + tracebacks = [] + for f in fs: + try: + return f(x) + except AssertionError: + _, _, tb = sys.exc_info() + tracebacks.append(tb) + for tb in tracebacks: + traceback.print_tb(tb) # Fixed format + tb_info = traceback.extract_tb(tb) + for tbi in tb_info: + filename, line, func, text = tbi + gltf2_io_debug.print_console('ERROR', 'An error occurred on line {} in statement {}'.format(line, text)) + assert False + + +def from_dict(f, x): + assert isinstance(x, dict) + return {k: f(v) for (k, v) in x.items()} + + +def to_class(c, x): + assert isinstance(x, c) + return x.to_dict() + + +def from_list(f, x): + assert isinstance(x, list) + return [f(y) for y in x] + + +def from_float(x): + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) + + +def from_str(x): + assert isinstance(x, str) + return x + + +def from_bool(x): + assert isinstance(x, bool) + return x + + +def to_float(x): + assert isinstance(x, float) + return x + + +class AccessorSparseIndices: + """Index array of size `count` that points to those accessor attributes that deviate from + their initialization value. Indices must strictly increase. + + Indices of those attributes that deviate from their initialization value. + """ + + def __init__(self, buffer_view, byte_offset, component_type, extensions, extras): + self.buffer_view = buffer_view + self.byte_offset = byte_offset + self.component_type = component_type + self.extensions = extensions + self.extras = extras + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + buffer_view = from_int(obj.get("bufferView")) + byte_offset = from_union([from_int, from_none], obj.get("byteOffset")) + component_type = from_int(obj.get("componentType")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + return AccessorSparseIndices(buffer_view, byte_offset, component_type, extensions, extras) + + def to_dict(self): + result = {} + result["bufferView"] = from_int(self.buffer_view) + result["byteOffset"] = from_union([from_int, from_none], self.byte_offset) + result["componentType"] = from_int(self.component_type) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + return result + + +class AccessorSparseValues: + """Array of size `count` times number of components, storing the displaced accessor + attributes pointed by `indices`. Substituted values must have the same `componentType` + and number of components as the base accessor. + + Array of size `accessor.sparse.count` times number of components storing the displaced + accessor attributes pointed by `accessor.sparse.indices`. + """ + + def __init__(self, buffer_view, byte_offset, extensions, extras): + self.buffer_view = buffer_view + self.byte_offset = byte_offset + self.extensions = extensions + self.extras = extras + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + buffer_view = from_int(obj.get("bufferView")) + byte_offset = from_union([from_int, from_none], obj.get("byteOffset")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + return AccessorSparseValues(buffer_view, byte_offset, extensions, extras) + + def to_dict(self): + result = {} + result["bufferView"] = from_int(self.buffer_view) + result["byteOffset"] = from_union([from_int, from_none], self.byte_offset) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + return result + + +class AccessorSparse: + """Sparse storage of attributes that deviate from their initialization value.""" + + def __init__(self, count, extensions, extras, indices, values): + self.count = count + self.extensions = extensions + self.extras = extras + self.indices = indices + self.values = values + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + count = from_int(obj.get("count")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + indices = AccessorSparseIndices.from_dict(obj.get("indices")) + values = AccessorSparseValues.from_dict(obj.get("values")) + return AccessorSparse(count, extensions, extras, indices, values) + + def to_dict(self): + result = {} + result["count"] = from_int(self.count) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["indices"] = to_class(AccessorSparseIndices, self.indices) + result["values"] = to_class(AccessorSparseValues, self.values) + return result + + +class Accessor: + """A typed view into a bufferView. A bufferView contains raw binary data. An accessor + provides a typed view into a bufferView or a subset of a bufferView similar to how + WebGL's `vertexAttribPointer()` defines an attribute in a buffer. + """ + + def __init__(self, buffer_view, byte_offset, component_type, count, extensions, extras, max, min, name, normalized, + sparse, type): + self.buffer_view = buffer_view + self.byte_offset = byte_offset + self.component_type = component_type + self.count = count + self.extensions = extensions + self.extras = extras + self.max = max + self.min = min + self.name = name + self.normalized = normalized + self.sparse = sparse + self.type = type + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + buffer_view = from_union([from_int, from_none], obj.get("bufferView")) + byte_offset = from_union([from_int, from_none], obj.get("byteOffset")) + component_type = from_int(obj.get("componentType")) + count = from_int(obj.get("count")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + max = from_union([lambda x: from_list(from_float, x), from_none], obj.get("max")) + min = from_union([lambda x: from_list(from_float, x), from_none], obj.get("min")) + name = from_union([from_str, from_none], obj.get("name")) + normalized = from_union([from_bool, from_none], obj.get("normalized")) + sparse = from_union([AccessorSparse.from_dict, from_none], obj.get("sparse")) + type = from_str(obj.get("type")) + return Accessor(buffer_view, byte_offset, component_type, count, extensions, extras, max, min, name, normalized, + sparse, type) + + def to_dict(self): + result = {} + result["bufferView"] = from_union([from_int, from_none], self.buffer_view) + result["byteOffset"] = from_union([from_int, from_none], self.byte_offset) + result["componentType"] = from_int(self.component_type) + result["count"] = from_int(self.count) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["max"] = from_union([lambda x: from_list(to_float, x), from_none], self.max) + result["min"] = from_union([lambda x: from_list(to_float, x), from_none], self.min) + result["name"] = from_union([from_str, from_none], self.name) + result["normalized"] = from_union([from_bool, from_none], self.normalized) + result["sparse"] = from_union([lambda x: to_class(AccessorSparse, x), from_none], self.sparse) + result["type"] = from_str(self.type) + return result + + +class AnimationChannelTarget: + """The index of the node and TRS property to target. + + The index of the node and TRS property that an animation channel targets. + """ + + def __init__(self, extensions, extras, node, path): + self.extensions = extensions + self.extras = extras + self.node = node + self.path = path + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + node = from_union([from_int, from_none], obj.get("node")) + path = from_str(obj.get("path")) + return AnimationChannelTarget(extensions, extras, node, path) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["node"] = from_union([from_int, from_none], self.node) + result["path"] = from_str(self.path) + return result + + +class AnimationChannel: + """Targets an animation's sampler at a node's property.""" + + def __init__(self, extensions, extras, sampler, target): + self.extensions = extensions + self.extras = extras + self.sampler = sampler + self.target = target + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + sampler = from_int(obj.get("sampler")) + target = AnimationChannelTarget.from_dict(obj.get("target")) + return AnimationChannel(extensions, extras, sampler, target) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["sampler"] = from_int(self.sampler) + result["target"] = to_class(AnimationChannelTarget, self.target) + return result + + +class AnimationSampler: + """Combines input and output accessors with an interpolation algorithm to define a keyframe + graph (but not its target). + """ + + def __init__(self, extensions, extras, input, interpolation, output): + self.extensions = extensions + self.extras = extras + self.input = input + self.interpolation = interpolation + self.output = output + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + input = from_int(obj.get("input")) + interpolation = from_union([from_str, from_none], obj.get("interpolation")) + output = from_int(obj.get("output")) + return AnimationSampler(extensions, extras, input, interpolation, output) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["input"] = from_int(self.input) + result["interpolation"] = from_union([from_str, from_none], self.interpolation) + result["output"] = from_int(self.output) + return result + + +class Animation: + """A keyframe animation.""" + + def __init__(self, channels, extensions, extras, name, samplers): + self.channels = channels + self.extensions = extensions + self.extras = extras + self.name = name + self.samplers = samplers + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + channels = from_list(AnimationChannel.from_dict, obj.get("channels")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + name = from_union([from_str, from_none], obj.get("name")) + samplers = from_list(AnimationSampler.from_dict, obj.get("samplers")) + return Animation(channels, extensions, extras, name, samplers) + + def to_dict(self): + result = {} + result["channels"] = from_list(lambda x: to_class(AnimationChannel, x), self.channels) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["name"] = from_union([from_str, from_none], self.name) + result["samplers"] = from_list(lambda x: to_class(AnimationSampler, x), self.samplers) + return result + + +class Asset: + """Metadata about the glTF asset.""" + + def __init__(self, copyright, extensions, extras, generator, min_version, version): + self.copyright = copyright + self.extensions = extensions + self.extras = extras + self.generator = generator + self.min_version = min_version + self.version = version + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + copyright = from_union([from_str, from_none], obj.get("copyright")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + generator = from_union([from_str, from_none], obj.get("generator")) + min_version = from_union([from_str, from_none], obj.get("minVersion")) + version = from_str(obj.get("version")) + return Asset(copyright, extensions, extras, generator, min_version, version) + + def to_dict(self): + result = {} + result["copyright"] = from_union([from_str, from_none], self.copyright) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["generator"] = from_union([from_str, from_none], self.generator) + result["minVersion"] = from_union([from_str, from_none], self.min_version) + result["version"] = from_str(self.version) + return result + + +class BufferView: + """A view into a buffer generally representing a subset of the buffer.""" + + def __init__(self, buffer, byte_length, byte_offset, byte_stride, extensions, extras, name, target): + self.buffer = buffer + self.byte_length = byte_length + self.byte_offset = byte_offset + self.byte_stride = byte_stride + self.extensions = extensions + self.extras = extras + self.name = name + self.target = target + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + buffer = from_int(obj.get("buffer")) + byte_length = from_int(obj.get("byteLength")) + byte_offset = from_union([from_int, from_none], obj.get("byteOffset")) + byte_stride = from_union([from_int, from_none], obj.get("byteStride")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + name = from_union([from_str, from_none], obj.get("name")) + target = from_union([from_int, from_none], obj.get("target")) + return BufferView(buffer, byte_length, byte_offset, byte_stride, extensions, extras, name, target) + + def to_dict(self): + result = {} + result["buffer"] = from_int(self.buffer) + result["byteLength"] = from_int(self.byte_length) + result["byteOffset"] = from_union([from_int, from_none], self.byte_offset) + result["byteStride"] = from_union([from_int, from_none], self.byte_stride) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["name"] = from_union([from_str, from_none], self.name) + result["target"] = from_union([from_int, from_none], self.target) + return result + + +class Buffer: + """A buffer points to binary geometry, animation, or skins.""" + + def __init__(self, byte_length, extensions, extras, name, uri): + self.byte_length = byte_length + self.extensions = extensions + self.extras = extras + self.name = name + self.uri = uri + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + byte_length = from_int(obj.get("byteLength")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + name = from_union([from_str, from_none], obj.get("name")) + uri = from_union([from_str, from_none], obj.get("uri")) + return Buffer(byte_length, extensions, extras, name, uri) + + def to_dict(self): + result = {} + result["byteLength"] = from_int(self.byte_length) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["name"] = from_union([from_str, from_none], self.name) + result["uri"] = from_union([from_str, from_none], self.uri) + return result + + +class CameraOrthographic: + """An orthographic camera containing properties to create an orthographic projection matrix.""" + + def __init__(self, extensions, extras, xmag, ymag, zfar, znear): + self.extensions = extensions + self.extras = extras + self.xmag = xmag + self.ymag = ymag + self.zfar = zfar + self.znear = znear + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + xmag = from_float(obj.get("xmag")) + ymag = from_float(obj.get("ymag")) + zfar = from_float(obj.get("zfar")) + znear = from_float(obj.get("znear")) + return CameraOrthographic(extensions, extras, xmag, ymag, zfar, znear) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["xmag"] = to_float(self.xmag) + result["ymag"] = to_float(self.ymag) + result["zfar"] = to_float(self.zfar) + result["znear"] = to_float(self.znear) + return result + + +class CameraPerspective: + """A perspective camera containing properties to create a perspective projection matrix.""" + + def __init__(self, aspect_ratio, extensions, extras, yfov, zfar, znear): + self.aspect_ratio = aspect_ratio + self.extensions = extensions + self.extras = extras + self.yfov = yfov + self.zfar = zfar + self.znear = znear + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + aspect_ratio = from_union([from_float, from_none], obj.get("aspectRatio")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + yfov = from_float(obj.get("yfov")) + zfar = from_union([from_float, from_none], obj.get("zfar")) + znear = from_float(obj.get("znear")) + return CameraPerspective(aspect_ratio, extensions, extras, yfov, zfar, znear) + + def to_dict(self): + result = {} + result["aspectRatio"] = from_union([to_float, from_none], self.aspect_ratio) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["yfov"] = to_float(self.yfov) + result["zfar"] = from_union([to_float, from_none], self.zfar) + result["znear"] = to_float(self.znear) + return result + + +class Camera: + """A camera's projection. A node can reference a camera to apply a transform to place the + camera in the scene. + """ + + def __init__(self, extensions, extras, name, orthographic, perspective, type): + self.extensions = extensions + self.extras = extras + self.name = name + self.orthographic = orthographic + self.perspective = perspective + self.type = type + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + name = from_union([from_str, from_none], obj.get("name")) + orthographic = from_union([CameraOrthographic.from_dict, from_none], obj.get("orthographic")) + perspective = from_union([CameraPerspective.from_dict, from_none], obj.get("perspective")) + type = from_str(obj.get("type")) + return Camera(extensions, extras, name, orthographic, perspective, type) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["name"] = from_union([from_str, from_none], self.name) + result["orthographic"] = from_union([lambda x: to_class(CameraOrthographic, x), from_none], self.orthographic) + result["perspective"] = from_union([lambda x: to_class(CameraPerspective, x), from_none], self.perspective) + result["type"] = from_str(self.type) + return result + + +class Image: + """Image data used to create a texture. Image can be referenced by URI or `bufferView` + index. `mimeType` is required in the latter case. + """ + + def __init__(self, buffer_view, extensions, extras, mime_type, name, uri): + self.buffer_view = buffer_view + self.extensions = extensions + self.extras = extras + self.mime_type = mime_type + self.name = name + self.uri = uri + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + buffer_view = from_union([from_int, from_none], obj.get("bufferView")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + mime_type = from_union([from_str, from_none], obj.get("mimeType")) + name = from_union([from_str, from_none], obj.get("name")) + uri = from_union([from_str, from_none], obj.get("uri")) + return Image(buffer_view, extensions, extras, mime_type, name, uri) + + def to_dict(self): + result = {} + result["bufferView"] = from_union([from_int, from_none], self.buffer_view) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["mimeType"] = from_union([from_str, from_none], self.mime_type) + result["name"] = from_union([from_str, from_none], self.name) + result["uri"] = from_union([from_str, from_none], self.uri) + return result + + +class TextureInfo: + """The emissive map texture. + + The base color texture. + + The metallic-roughness texture. + + Reference to a texture. + """ + + def __init__(self, extensions, extras, index, tex_coord): + self.extensions = extensions + self.extras = extras + self.index = index + self.tex_coord = tex_coord + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + index = from_int(obj.get("index")) + tex_coord = from_union([from_int, from_none], obj.get("texCoord")) + return TextureInfo(extensions, extras, index, tex_coord) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["index"] = from_int(self.index) + result["texCoord"] = from_union([from_int, from_none], self.tex_coord) + return result + + +class MaterialNormalTextureInfoClass: + """The normal map texture. + + Reference to a texture. + """ + + def __init__(self, extensions, extras, index, scale, tex_coord): + self.extensions = extensions + self.extras = extras + self.index = index + self.scale = scale + self.tex_coord = tex_coord + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + index = from_int(obj.get("index")) + scale = from_union([from_float, from_none], obj.get("scale")) + tex_coord = from_union([from_int, from_none], obj.get("texCoord")) + return MaterialNormalTextureInfoClass(extensions, extras, index, scale, tex_coord) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["index"] = from_int(self.index) + result["scale"] = from_union([to_float, from_none], self.scale) + result["texCoord"] = from_union([from_int, from_none], self.tex_coord) + return result + + +class MaterialOcclusionTextureInfoClass: + """The occlusion map texture. + + Reference to a texture. + """ + + def __init__(self, extensions, extras, index, strength, tex_coord): + self.extensions = extensions + self.extras = extras + self.index = index + self.strength = strength + self.tex_coord = tex_coord + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + index = from_int(obj.get("index")) + strength = from_union([from_float, from_none], obj.get("strength")) + tex_coord = from_union([from_int, from_none], obj.get("texCoord")) + return MaterialOcclusionTextureInfoClass(extensions, extras, index, strength, tex_coord) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["index"] = from_int(self.index) + result["strength"] = from_union([to_float, from_none], self.strength) + result["texCoord"] = from_union([from_int, from_none], self.tex_coord) + return result + + +class MaterialPBRMetallicRoughness: + """A set of parameter values that are used to define the metallic-roughness material model + from Physically-Based Rendering (PBR) methodology. When not specified, all the default + values of `pbrMetallicRoughness` apply. + + A set of parameter values that are used to define the metallic-roughness material model + from Physically-Based Rendering (PBR) methodology. + """ + + def __init__(self, base_color_factor, base_color_texture, extensions, extras, metallic_factor, + metallic_roughness_texture, roughness_factor): + self.base_color_factor = base_color_factor + self.base_color_texture = base_color_texture + self.extensions = extensions + self.extras = extras + self.metallic_factor = metallic_factor + self.metallic_roughness_texture = metallic_roughness_texture + self.roughness_factor = roughness_factor + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + base_color_factor = from_union([lambda x: from_list(from_float, x), from_none], obj.get("baseColorFactor")) + base_color_texture = from_union([TextureInfo.from_dict, from_none], obj.get("baseColorTexture")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + metallic_factor = from_union([from_float, from_none], obj.get("metallicFactor")) + metallic_roughness_texture = from_union([TextureInfo.from_dict, from_none], obj.get("metallicRoughnessTexture")) + roughness_factor = from_union([from_float, from_none], obj.get("roughnessFactor")) + return MaterialPBRMetallicRoughness(base_color_factor, base_color_texture, extensions, extras, metallic_factor, + metallic_roughness_texture, roughness_factor) + + def to_dict(self): + result = {} + result["baseColorFactor"] = from_union([lambda x: from_list(to_float, x), from_none], self.base_color_factor) + result["baseColorTexture"] = from_union([lambda x: to_class(TextureInfo, x), from_none], + self.base_color_texture) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["metallicFactor"] = from_union([to_float, from_none], self.metallic_factor) + result["metallicRoughnessTexture"] = from_union([lambda x: to_class(TextureInfo, x), from_none], + self.metallic_roughness_texture) + result["roughnessFactor"] = from_union([to_float, from_none], self.roughness_factor) + return result + + +class Material: + """The material appearance of a primitive.""" + + def __init__(self, alpha_cutoff, alpha_mode, double_sided, emissive_factor, emissive_texture, extensions, extras, + name, normal_texture, occlusion_texture, pbr_metallic_roughness): + self.alpha_cutoff = alpha_cutoff + self.alpha_mode = alpha_mode + self.double_sided = double_sided + self.emissive_factor = emissive_factor + self.emissive_texture = emissive_texture + self.extensions = extensions + self.extras = extras + self.name = name + self.normal_texture = normal_texture + self.occlusion_texture = occlusion_texture + self.pbr_metallic_roughness = pbr_metallic_roughness + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + alpha_cutoff = from_union([from_float, from_none], obj.get("alphaCutoff")) + alpha_mode = from_union([from_str, from_none], obj.get("alphaMode")) + double_sided = from_union([from_bool, from_none], obj.get("doubleSided")) + emissive_factor = from_union([lambda x: from_list(from_float, x), from_none], obj.get("emissiveFactor")) + emissive_texture = from_union([TextureInfo.from_dict, from_none], obj.get("emissiveTexture")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + name = from_union([from_str, from_none], obj.get("name")) + normal_texture = from_union([MaterialNormalTextureInfoClass.from_dict, from_none], obj.get("normalTexture")) + occlusion_texture = from_union([MaterialOcclusionTextureInfoClass.from_dict, from_none], + obj.get("occlusionTexture")) + pbr_metallic_roughness = from_union([MaterialPBRMetallicRoughness.from_dict, from_none], + obj.get("pbrMetallicRoughness")) + return Material(alpha_cutoff, alpha_mode, double_sided, emissive_factor, emissive_texture, extensions, extras, + name, normal_texture, occlusion_texture, pbr_metallic_roughness) + + def to_dict(self): + result = {} + result["alphaCutoff"] = from_union([to_float, from_none], self.alpha_cutoff) + result["alphaMode"] = from_union([from_str, from_none], self.alpha_mode) + result["doubleSided"] = from_union([from_bool, from_none], self.double_sided) + result["emissiveFactor"] = from_union([lambda x: from_list(to_float, x), from_none], self.emissive_factor) + result["emissiveTexture"] = from_union([lambda x: to_class(TextureInfo, x), from_none], self.emissive_texture) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["name"] = from_union([from_str, from_none], self.name) + result["normalTexture"] = from_union([lambda x: to_class(MaterialNormalTextureInfoClass, x), from_none], + self.normal_texture) + result["occlusionTexture"] = from_union([lambda x: to_class(MaterialOcclusionTextureInfoClass, x), from_none], + self.occlusion_texture) + result["pbrMetallicRoughness"] = from_union([lambda x: to_class(MaterialPBRMetallicRoughness, x), from_none], + self.pbr_metallic_roughness) + return result + + +class MeshPrimitive: + """Geometry to be rendered with the given material.""" + + def __init__(self, attributes, extensions, extras, indices, material, mode, targets): + self.attributes = attributes + self.extensions = extensions + self.extras = extras + self.indices = indices + self.material = material + self.mode = mode + self.targets = targets + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + attributes = from_dict(from_int, obj.get("attributes")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + indices = from_union([from_int, from_none], obj.get("indices")) + material = from_union([from_int, from_none], obj.get("material")) + mode = from_union([from_int, from_none], obj.get("mode")) + targets = from_union([lambda x: from_list(lambda x: from_dict(from_int, x), x), from_none], obj.get("targets")) + return MeshPrimitive(attributes, extensions, extras, indices, material, mode, targets) + + def to_dict(self): + result = {} + result["attributes"] = from_dict(from_int, self.attributes) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["indices"] = from_union([from_int, from_none], self.indices) + result["material"] = from_union([from_int, from_none], self.material) + result["mode"] = from_union([from_int, from_none], self.mode) + result["targets"] = from_union([lambda x: from_list(lambda x: from_dict(from_int, x), x), from_none], + self.targets) + return result + + +class Mesh: + """A set of primitives to be rendered. A node can contain one mesh. A node's transform + places the mesh in the scene. + """ + + def __init__(self, extensions, extras, name, primitives, weights): + self.extensions = extensions + self.extras = extras + self.name = name + self.primitives = primitives + self.weights = weights + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + name = from_union([from_str, from_none], obj.get("name")) + primitives = from_list(MeshPrimitive.from_dict, obj.get("primitives")) + weights = from_union([lambda x: from_list(from_float, x), from_none], obj.get("weights")) + return Mesh(extensions, extras, name, primitives, weights) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["name"] = from_union([from_str, from_none], self.name) + result["primitives"] = from_list(lambda x: to_class(MeshPrimitive, x), self.primitives) + result["weights"] = from_union([lambda x: from_list(to_float, x), from_none], self.weights) + return result + + +class Node: + """A node in the node hierarchy. When the node contains `skin`, all `mesh.primitives` must + contain `JOINTS_0` and `WEIGHTS_0` attributes. A node can have either a `matrix` or any + combination of `translation`/`rotation`/`scale` (TRS) properties. TRS properties are + converted to matrices and postmultiplied in the `T * R * S` order to compose the + transformation matrix; first the scale is applied to the vertices, then the rotation, and + then the translation. If none are provided, the transform is the identity. When a node is + targeted for animation (referenced by an animation.channel.target), only TRS properties + may be present; `matrix` will not be present. + """ + + def __init__(self, camera, children, extensions, extras, matrix, mesh, name, rotation, scale, skin, translation, + weights): + self.camera = camera + self.children = children + self.extensions = extensions + self.extras = extras + self.matrix = matrix + self.mesh = mesh + self.name = name + self.rotation = rotation + self.scale = scale + self.skin = skin + self.translation = translation + self.weights = weights + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + camera = from_union([from_int, from_none], obj.get("camera")) + children = from_union([lambda x: from_list(from_int, x), from_none], obj.get("children")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + matrix = from_union([lambda x: from_list(from_float, x), from_none], obj.get("matrix")) + mesh = from_union([from_int, from_none], obj.get("mesh")) + name = from_union([from_str, from_none], obj.get("name")) + rotation = from_union([lambda x: from_list(from_float, x), from_none], obj.get("rotation")) + scale = from_union([lambda x: from_list(from_float, x), from_none], obj.get("scale")) + skin = from_union([from_int, from_none], obj.get("skin")) + translation = from_union([lambda x: from_list(from_float, x), from_none], obj.get("translation")) + weights = from_union([lambda x: from_list(from_float, x), from_none], obj.get("weights")) + return Node(camera, children, extensions, extras, matrix, mesh, name, rotation, scale, skin, translation, + weights) + + def to_dict(self): + result = {} + result["camera"] = from_union([from_int, from_none], self.camera) + result["children"] = from_union([lambda x: from_list(from_int, x), from_none], self.children) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["matrix"] = from_union([lambda x: from_list(to_float, x), from_none], self.matrix) + result["mesh"] = from_union([from_int, from_none], self.mesh) + result["name"] = from_union([from_str, from_none], self.name) + result["rotation"] = from_union([lambda x: from_list(to_float, x), from_none], self.rotation) + result["scale"] = from_union([lambda x: from_list(to_float, x), from_none], self.scale) + result["skin"] = from_union([from_int, from_none], self.skin) + result["translation"] = from_union([lambda x: from_list(to_float, x), from_none], self.translation) + result["weights"] = from_union([lambda x: from_list(to_float, x), from_none], self.weights) + return result + + +class Sampler: + """Texture sampler properties for filtering and wrapping modes.""" + + def __init__(self, extensions, extras, mag_filter, min_filter, name, wrap_s, wrap_t): + self.extensions = extensions + self.extras = extras + self.mag_filter = mag_filter + self.min_filter = min_filter + self.name = name + self.wrap_s = wrap_s + self.wrap_t = wrap_t + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + mag_filter = from_union([from_int, from_none], obj.get("magFilter")) + min_filter = from_union([from_int, from_none], obj.get("minFilter")) + name = from_union([from_str, from_none], obj.get("name")) + wrap_s = from_union([from_int, from_none], obj.get("wrapS")) + wrap_t = from_union([from_int, from_none], obj.get("wrapT")) + return Sampler(extensions, extras, mag_filter, min_filter, name, wrap_s, wrap_t) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["magFilter"] = from_union([from_int, from_none], self.mag_filter) + result["minFilter"] = from_union([from_int, from_none], self.min_filter) + result["name"] = from_union([from_str, from_none], self.name) + result["wrapS"] = from_union([from_int, from_none], self.wrap_s) + result["wrapT"] = from_union([from_int, from_none], self.wrap_t) + return result + + +class Scene: + """The root nodes of a scene.""" + + def __init__(self, extensions, extras, name, nodes): + self.extensions = extensions + self.extras = extras + self.name = name + self.nodes = nodes + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + name = from_union([from_str, from_none], obj.get("name")) + nodes = from_union([lambda x: from_list(from_int, x), from_none], obj.get("nodes")) + return Scene(extensions, extras, name, nodes) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["name"] = from_union([from_str, from_none], self.name) + result["nodes"] = from_union([lambda x: from_list(from_int, x), from_none], self.nodes) + return result + + +class Skin: + """Joints and matrices defining a skin.""" + + def __init__(self, extensions, extras, inverse_bind_matrices, joints, name, skeleton): + self.extensions = extensions + self.extras = extras + self.inverse_bind_matrices = inverse_bind_matrices + self.joints = joints + self.name = name + self.skeleton = skeleton + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + inverse_bind_matrices = from_union([from_int, from_none], obj.get("inverseBindMatrices")) + joints = from_list(from_int, obj.get("joints")) + name = from_union([from_str, from_none], obj.get("name")) + skeleton = from_union([from_int, from_none], obj.get("skeleton")) + return Skin(extensions, extras, inverse_bind_matrices, joints, name, skeleton) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["inverseBindMatrices"] = from_union([from_int, from_none], self.inverse_bind_matrices) + result["joints"] = from_list(from_int, self.joints) + result["name"] = from_union([from_str, from_none], self.name) + result["skeleton"] = from_union([from_int, from_none], self.skeleton) + return result + + +class Texture: + """A texture and its sampler.""" + + def __init__(self, extensions, extras, name, sampler, source): + self.extensions = extensions + self.extras = extras + self.name = name + self.sampler = sampler + self.source = source + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + name = from_union([from_str, from_none], obj.get("name")) + sampler = from_union([from_int, from_none], obj.get("sampler")) + source = from_int(obj.get("source")) + return Texture(extensions, extras, name, sampler, source) + + def to_dict(self): + result = {} + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + result["name"] = from_union([from_str, from_none], self.name) + result["sampler"] = from_union([from_int, from_none], self.sampler) + result["source"] = from_int(self.source) + return result + + +class Gltf: + """The root object for a glTF asset.""" + + def __init__(self, accessors, animations, asset, buffers, buffer_views, cameras, extensions, extensions_required, + extensions_used, extras, images, materials, meshes, nodes, samplers, scene, scenes, skins, textures): + self.accessors = accessors + self.animations = animations + self.asset = asset + self.buffers = buffers + self.buffer_views = buffer_views + self.cameras = cameras + self.extensions = extensions + self.extensions_required = extensions_required + self.extensions_used = extensions_used + self.extras = extras + self.images = images + self.materials = materials + self.meshes = meshes + self.nodes = nodes + self.samplers = samplers + self.scene = scene + self.scenes = scenes + self.skins = skins + self.textures = textures + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + accessors = from_union([lambda x: from_list(Accessor.from_dict, x), from_none], obj.get("accessors")) + animations = from_union([lambda x: from_list(Animation.from_dict, x), from_none], obj.get("animations")) + asset = Asset.from_dict(obj.get("asset")) + buffers = from_union([lambda x: from_list(Buffer.from_dict, x), from_none], obj.get("buffers")) + buffer_views = from_union([lambda x: from_list(BufferView.from_dict, x), from_none], obj.get("bufferViews")) + cameras = from_union([lambda x: from_list(Camera.from_dict, x), from_none], obj.get("cameras")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extensions_required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("extensionsRequired")) + extensions_used = from_union([lambda x: from_list(from_str, x), from_none], obj.get("extensionsUsed")) + extras = obj.get("extras") + images = from_union([lambda x: from_list(Image.from_dict, x), from_none], obj.get("images")) + materials = from_union([lambda x: from_list(Material.from_dict, x), from_none], obj.get("materials")) + meshes = from_union([lambda x: from_list(Mesh.from_dict, x), from_none], obj.get("meshes")) + nodes = from_union([lambda x: from_list(Node.from_dict, x), from_none], obj.get("nodes")) + samplers = from_union([lambda x: from_list(Sampler.from_dict, x), from_none], obj.get("samplers")) + scene = from_union([from_int, from_none], obj.get("scene")) + scenes = from_union([lambda x: from_list(Scene.from_dict, x), from_none], obj.get("scenes")) + skins = from_union([lambda x: from_list(Skin.from_dict, x), from_none], obj.get("skins")) + textures = from_union([lambda x: from_list(Texture.from_dict, x), from_none], obj.get("textures")) + return Gltf(accessors, animations, asset, buffers, buffer_views, cameras, extensions, extensions_required, + extensions_used, extras, images, materials, meshes, nodes, samplers, scene, scenes, skins, textures) + + def to_dict(self): + result = {} + result["accessors"] = from_union([lambda x: from_list(lambda x: to_class(Accessor, x), x), from_none], + self.accessors) + result["animations"] = from_union([lambda x: from_list(lambda x: to_class(Animation, x), x), from_none], + self.animations) + result["asset"] = to_class(Asset, self.asset) + result["buffers"] = from_union([lambda x: from_list(lambda x: to_class(Buffer, x), x), from_none], self.buffers) + result["bufferViews"] = from_union([lambda x: from_list(lambda x: to_class(BufferView, x), x), from_none], + self.buffer_views) + result["cameras"] = from_union([lambda x: from_list(lambda x: to_class(Camera, x), x), from_none], self.cameras) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extensionsRequired"] = from_union([lambda x: from_list(from_str, x), from_none], + self.extensions_required) + result["extensionsUsed"] = from_union([lambda x: from_list(from_str, x), from_none], self.extensions_used) + result["extras"] = self.extras + result["images"] = from_union([lambda x: from_list(lambda x: to_class(Image, x), x), from_none], self.images) + result["materials"] = from_union([lambda x: from_list(lambda x: to_class(Material, x), x), from_none], + self.materials) + result["meshes"] = from_union([lambda x: from_list(lambda x: to_class(Mesh, x), x), from_none], self.meshes) + result["nodes"] = from_union([lambda x: from_list(lambda x: to_class(Node, x), x), from_none], self.nodes) + result["samplers"] = from_union([lambda x: from_list(lambda x: to_class(Sampler, x), x), from_none], + self.samplers) + result["scene"] = from_union([from_int, from_none], self.scene) + result["scenes"] = from_union([lambda x: from_list(lambda x: to_class(Scene, x), x), from_none], self.scenes) + result["skins"] = from_union([lambda x: from_list(lambda x: to_class(Skin, x), x), from_none], self.skins) + result["textures"] = from_union([lambda x: from_list(lambda x: to_class(Texture, x), x), from_none], + self.textures) + return result + + +def gltf_from_dict(s): + return Gltf.from_dict(s) + + +def gltf_to_dict(x): + return to_class(Gltf, x) + diff --git a/io_scene_gltf2/io/com/gltf2_io_constants.py b/io_scene_gltf2/io/com/gltf2_io_constants.py new file mode 100755 index 00000000..c97908cd --- /dev/null +++ b/io_scene_gltf2/io/com/gltf2_io_constants.py @@ -0,0 +1,132 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from enum import IntEnum + + +class ComponentType(IntEnum): + Byte = 5120 + UnsignedByte = 5121 + Short = 5122 + UnsignedShort = 5123 + UnsignedInt = 5125 + Float = 5126 + + @classmethod + def to_type_code(cls, component_type): + return { + ComponentType.Byte: 'b', + ComponentType.UnsignedByte: 'B', + ComponentType.Short: 'h', + ComponentType.UnsignedShort: 'H', + ComponentType.UnsignedInt: 'I', + ComponentType.Float: 'f' + }[component_type] + + @classmethod + def from_legacy_define(cls, type_define): + return { + GLTF_COMPONENT_TYPE_BYTE: ComponentType.Byte, + GLTF_COMPONENT_TYPE_UNSIGNED_BYTE: ComponentType.UnsignedByte, + GLTF_COMPONENT_TYPE_SHORT: ComponentType.Short, + GLTF_COMPONENT_TYPE_UNSIGNED_SHORT: ComponentType.UnsignedShort, + GLTF_COMPONENT_TYPE_UNSIGNED_INT: ComponentType.UnsignedInt, + GLTF_COMPONENT_TYPE_FLOAT: ComponentType.Float + }[type_define] + + @classmethod + def get_size(cls, component_type): + return { + ComponentType.Byte: 1, + ComponentType.UnsignedByte: 1, + ComponentType.Short: 2, + ComponentType.UnsignedShort: 2, + ComponentType.UnsignedInt: 4, + ComponentType.Float: 4 + }[component_type] + + +class DataType: + Scalar = "SCALAR" + Vec2 = "VEC2" + Vec3 = "VEC3" + Vec4 = "VEC4" + Mat2 = "MAT2" + Mat3 = "MAT3" + Mat4 = "MAT4" + + def __new__(cls, *args, **kwargs): + raise RuntimeError("{} should not be instantiated".format(cls.__name__)) + + @classmethod + def num_elements(cls, data_type): + return { + DataType.Scalar: 1, + DataType.Vec2: 2, + DataType.Vec3: 3, + DataType.Vec4: 4, + DataType.Mat2: 4, + DataType.Mat3: 9, + DataType.Mat4: 16 + }[data_type] + + @classmethod + def vec_type_from_num(cls, num_elems): + if not (0 < num_elems < 5): + raise ValueError("No vector type with {} elements".format(num_elems)) + return { + 1: DataType.Scalar, + 2: DataType.Vec2, + 3: DataType.Vec3, + 4: DataType.Vec4 + }[num_elems] + + @classmethod + def mat_type_from_num(cls, num_elems): + if not (4 <= num_elems <= 16): + raise ValueError("No matrix type with {} elements".format(num_elems)) + return { + 4: DataType.Mat2, + 9: DataType.Mat3, + 16: DataType.Mat4 + }[num_elems] + + +################# +# LEGACY DEFINES + +GLTF_VERSION = "2.0" + +# +# Component Types +# +GLTF_COMPONENT_TYPE_BYTE = "BYTE" +GLTF_COMPONENT_TYPE_UNSIGNED_BYTE = "UNSIGNED_BYTE" +GLTF_COMPONENT_TYPE_SHORT = "SHORT" +GLTF_COMPONENT_TYPE_UNSIGNED_SHORT = "UNSIGNED_SHORT" +GLTF_COMPONENT_TYPE_UNSIGNED_INT = "UNSIGNED_INT" +GLTF_COMPONENT_TYPE_FLOAT = "FLOAT" + + +# +# Data types +# +GLTF_DATA_TYPE_SCALAR = "SCALAR" +GLTF_DATA_TYPE_VEC2 = "VEC2" +GLTF_DATA_TYPE_VEC3 = "VEC3" +GLTF_DATA_TYPE_VEC4 = "VEC4" +GLTF_DATA_TYPE_MAT2 = "MAT2" +GLTF_DATA_TYPE_MAT3 = "MAT3" +GLTF_DATA_TYPE_MAT4 = "MAT4" + diff --git a/io_scene_gltf2/io/com/gltf2_io_debug.py b/io_scene_gltf2/io/com/gltf2_io_debug.py new file mode 100755 index 00000000..b9098eba --- /dev/null +++ b/io_scene_gltf2/io/com/gltf2_io_debug.py @@ -0,0 +1,143 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +import time +import logging + +# +# Globals +# + +OUTPUT_LEVELS = ['ERROR', 'WARNING', 'INFO', 'PROFILE', 'DEBUG', 'VERBOSE'] + +g_current_output_level = 'DEBUG' +g_profile_started = False +g_profile_start = 0.0 +g_profile_end = 0.0 +g_profile_delta = 0.0 + +# +# Functions +# + + +def set_output_level(level): + """Set an output debug level.""" + global g_current_output_level + + if OUTPUT_LEVELS.index(level) < 0: + return + + g_current_output_level = level + + +def print_console(level, output): + """Print to Blender console with a given header and output.""" + global OUTPUT_LEVELS + global g_current_output_level + + if OUTPUT_LEVELS.index(level) > OUTPUT_LEVELS.index(g_current_output_level): + return + + print(get_timestamp() + " | " + level + ': ' + output) + + +def print_newline(): + """Print a new line to Blender console.""" + print() + + +def get_timestamp(): + current_time = time.gmtime() + return time.strftime("%H:%M:%S", current_time) + + +def print_timestamp(label=None): + """Print a timestamp to Blender console.""" + output = 'Timestamp: ' + get_timestamp() + + if label is not None: + output = output + ' (' + label + ')' + + print_console('PROFILE', output) + + +def profile_start(): + """Start profiling by storing the current time.""" + global g_profile_start + global g_profile_started + + if g_profile_started: + print_console('ERROR', 'Profiling already started') + return + + g_profile_started = True + + g_profile_start = time.time() + + +def profile_end(label=None): + """Stop profiling and printing out the delta time since profile start.""" + global g_profile_end + global g_profile_delta + global g_profile_started + + if not g_profile_started: + print_console('ERROR', 'Profiling not started') + return + + g_profile_started = False + + g_profile_end = time.time() + g_profile_delta = g_profile_end - g_profile_start + + output = 'Delta time: ' + str(g_profile_delta) + + if label is not None: + output = output + ' (' + label + ')' + + print_console('PROFILE', output) + + +# TODO: need to have a unique system for logging importer/exporter +# TODO: this logger is used for importer, but in io and in blender part, but is written here in a _io_ file +class Log: + def __init__(self, loglevel): + self.logger = logging.getLogger('glTFImporter') + self.hdlr = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') + self.hdlr.setFormatter(formatter) + self.logger.addHandler(self.hdlr) + self.logger.setLevel(int(loglevel)) + + @staticmethod + def get_levels(): + levels = [ + (str(logging.CRITICAL), "Critical", "", logging.CRITICAL), + (str(logging.ERROR), "Error", "", logging.ERROR), + (str(logging.WARNING), "Warning", "", logging.WARNING), + (str(logging.INFO), "Info", "", logging.INFO), + (str(logging.NOTSET), "NotSet", "", logging.NOTSET) + ] + + return levels + + @staticmethod + def default(): + return str(logging.ERROR) + diff --git a/io_scene_gltf2/io/com/gltf2_io_extensions.py b/io_scene_gltf2/io/com/gltf2_io_extensions.py new file mode 100644 index 00000000..2422d205 --- /dev/null +++ b/io_scene_gltf2/io/com/gltf2_io_extensions.py @@ -0,0 +1,38 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import List, Dict, Any + + +class Extension: + """Container for extensions. Allows to specify requiredness""" + def __init__(self, name: str, extension: Dict[str, Any], required: bool = True): + self.name = name + self.extension = extension + self.required = required + + +class ChildOfRootExtension(Extension): + """Container object for extensions that should be appended to the root extensions""" + def __init__(self, path: List[str], name: str, extension: Dict[str, Any], required: bool = True): + """ + Wrap a local extension entity into an object that will later be inserted into a root extension and converted + to a reference. + :param path: The path of the extension object in the root extension. E.g. ['lights'] for + KHR_lights_punctual. Must be a path to a list in the extensions dict. + :param extension: The data that should be placed into the extension list + """ + self.path = path + super().__init__(name, extension, required) + diff --git a/io_scene_gltf2/io/com/gltf2_io_functional.py b/io_scene_gltf2/io/com/gltf2_io_functional.py new file mode 100755 index 00000000..eb65112f --- /dev/null +++ b/io_scene_gltf2/io/com/gltf2_io_functional.py @@ -0,0 +1,41 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing + + +def chunks(lst: typing.Sequence[typing.Any], n: int) -> typing.List[typing.Any]: + """ + Generator that yields successive n sized chunks of the list l + :param lst: the list to be split + :param n: the length of the chunks + :return: a sublist of at most length n + """ + result = [] + for i in range(0, len(lst), n): + result.append(lst[i:i + n]) + return result + + +def unzip(*args: typing.Iterable[typing.Any]) -> typing.Iterable[typing.Iterable[typing.Any]]: + """ + Unzip the list. Inverse of the builtin zip + :param args: a list of lists or multiple list arguments + :return: a list of unzipped lists + """ + if len(args) == 1: + args = args[0] + + return zip(*args) + diff --git a/io_scene_gltf2/io/com/gltf2_io_image.py b/io_scene_gltf2/io/com/gltf2_io_image.py new file mode 100755 index 00000000..af86daeb --- /dev/null +++ b/io_scene_gltf2/io/com/gltf2_io_image.py @@ -0,0 +1,154 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +import struct +import zlib + + +class Image: + """ + Image object class to represent a 4-channel RGBA image. + + Pixel values are expected to be floating point in the range of [0.0 to 1.0] + """ + + def __init__(self, width, height, pixels): + self.width = width + self.height = height + self.channels = 4 + self.pixels = pixels + self.name = "" + self.file_format = "PNG" + + def to_png_data(self): + buf = bytearray([int(channel * 255.0) for channel in self.pixels]) + + # + # Taken from 'blender-thumbnailer.py' in Blender. + # + + # reverse the vertical line order and add null bytes at the start + width_byte_4 = self.width * 4 + raw_data = b"".join( + b'\x00' + buf[span:span + width_byte_4] for span in range( + (self.height - 1) * self.width * 4, -1, - width_byte_4)) + + def png_pack(png_tag, data): + chunk_head = png_tag + data + return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head)) + + return b"".join([ + b'\x89PNG\r\n\x1a\n', + png_pack(b'IHDR', struct.pack("!2I5B", self.width, self.height, 8, 6, 0, 0, 0)), + png_pack(b'IDAT', zlib.compress(raw_data, 9)), + png_pack(b'IEND', b'')]) + + def to_image_data(self, mime_type): + if mime_type == 'image/png': + return self.to_png_data() + raise ValueError("Unsupported image file type {}".format(mime_type)) + + def save_png(self, dst_path): + data = self.to_png_data() + with open(dst_path, 'wb') as f: + f.write(data) + + +def create_img(width, height, r=0.0, g=0.0, b=0.0, a=1.0): + """ + Create a new image object with 4 channels and initialize it with the given default values. + + (if no arguments are given, these default to R=0, G=0, B=0, A=1.0) + Return the created image object. + """ + return Image(width, height, [r, g, b, a] * (width * height)) + + +def create_img_from_pixels(width, height, pixels): + """ + Create a new image object with 4 channels and initialize it using the given array of pixel data. + + Return the created image object. + """ + if pixels is None or len(pixels) != width * height * 4: + return None + + return Image(width, height, pixels) + + +def copy_img_channel(dst_image, dst_channel, src_image, src_channel): + """ + Copy a single channel (identified by src_channel) from src_image to dst_image (overwriting dst_channel). + + src_image and dst_image are expected to be image objects created using create_img. + Return True on success, False otherwise. + """ + if dst_image is None or src_image is None: + return False + + if dst_channel < 0 or dst_channel >= dst_image.channels or src_channel < 0 or src_channel >= src_image.channels: + return False + + if src_image.width != dst_image.width or \ + src_image.height != dst_image.height or \ + src_image.channels != dst_image.channels: + return False + + for i in range(0, len(dst_image.pixels), dst_image.channels): + dst_image.pixels[i + dst_channel] = src_image.pixels[i + src_channel] + + return True + + +def test_save_img(image, path): + """ + Save the given image to a PNG file (specified by path). + + Return True on success, False otherwise. + """ + if image is None or image.channels != 4: + return False + + width = image.width + height = image.height + + buf = bytearray([int(channel * 255.0) for channel in image.pixels]) + + # + # Taken from 'blender-thumbnailer.py' in Blender. + # + + # reverse the vertical line order and add null bytes at the start + width_byte_4 = width * 4 + raw_data = b"".join( + b'\x00' + buf[span:span + width_byte_4] for span in range((height - 1) * width * 4, -1, - width_byte_4)) + + def png_pack(png_tag, data): + chunk_head = png_tag + data + return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head)) + + data = b"".join([ + b'\x89PNG\r\n\x1a\n', + png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)), + png_pack(b'IDAT', zlib.compress(raw_data, 9)), + png_pack(b'IEND', b'')]) + + with open(path, 'wb') as f: + f.write(data) + return True + diff --git a/io_scene_gltf2/io/com/gltf2_io_lights_punctual.py b/io_scene_gltf2/io/com/gltf2_io_lights_punctual.py new file mode 100644 index 00000000..3dc8704c --- /dev/null +++ b/io_scene_gltf2/io/com/gltf2_io_lights_punctual.py @@ -0,0 +1,76 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from io_scene_gltf2.io.com.gltf2_io import * + + +class LightSpot: + """light/spot""" + def __init__(self, inner_cone_angle, outer_cone_angle): + self.inner_cone_angle = inner_cone_angle + self.outer_cone_angle = outer_cone_angle + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + inner_cone_angle = from_union([from_float, from_none], obj.get("innerConeAngle")) + outer_cone_angle = from_union([from_float, from_none], obj.get("outerConeAngle")) + return LightSpot(inner_cone_angle, outer_cone_angle) + + def to_dict(self): + result = {} + result["innerConeAngle"] = from_union([from_float, from_none], self.inner_cone_angle) + result["outerConeAngle"] = from_union([from_float, from_none], self.outer_cone_angle) + return result + + +class Light: + """defines a set of lights for use with glTF 2.0. Lights define light sources within a scene""" + def __init__(self, color, intensity, spot, type, range, name, extensions, extras): + self.color = color + self.intensity = intensity + self.spot = spot + self.type = type + self.range = range + self.name = name + self.extensions = extensions + self.extras = extras + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + color = from_union([lambda x: from_list(from_float, x), from_none], obj.get("color")) + intensity = from_union([from_float, from_none], obj.get("intensity")) + spot = LightSpot.from_dict(obj.get("spot")) + type = from_str(obj.get("type")) + range = from_union([from_float, from_none], obj.get("range")) + name = from_union([from_str, from_none], obj.get("name")) + extensions = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + obj.get("extensions")) + extras = obj.get("extras") + return Light(color, intensity, spot, type, range, name, extensions, extras) + + def to_dict(self): + result = {} + result["color"] = from_union([lambda x: from_list(to_float, x), from_none], self.color) + result["intensity"] = from_union([from_float, from_none], self.intensity) + result["spot"] = from_union([lambda x: to_class(LightSpot, x), from_none], self.spot) + result["type"] = from_str(self.type) + result["range"] = from_union([from_float, from_none], self.range) + result["name"] = from_union([from_str, from_none], self.name) + result["extensions"] = from_union([lambda x: from_dict(lambda x: from_dict(lambda x: x, x), x), from_none], + self.extensions) + result["extras"] = self.extras + return result + diff --git a/io_scene_gltf2/io/com/gltf2_io_trs.py b/io_scene_gltf2/io/com/gltf2_io_trs.py new file mode 100755 index 00000000..59f30830 --- /dev/null +++ b/io_scene_gltf2/io/com/gltf2_io_trs.py @@ -0,0 +1,68 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class TRS: + + def __new__(cls, *args, **kwargs): + raise RuntimeError("{} should not be instantiated".format(cls.__name__)) + + @staticmethod + def scale_to_matrix(scale): + # column major ! + return [scale[0], 0, 0, 0, + 0, scale[1], 0, 0, + 0, 0, scale[2], 0, + 0, 0, 0, 1] + + @staticmethod + def quaternion_to_matrix(q): + x, y, z, w = q + # TODO : is q normalized ? --> if not, multiply by 1/(w*w + x*x + y*y + z*z) + # column major ! + return [ + 1 - 2 * y * y - 2 * z * z, 2 * x * y + 2 * w * z, 2 * x * z - 2 * w * y, 0, + 2 * x * y - 2 * w * z, 1 - 2 * x * x - 2 * z * z, 2 * y * z + 2 * w * x, 0, + 2 * x * z + 2 * y * w, 2 * y * z - 2 * w * x, 1 - 2 * x * x - 2 * y * y, 0, + 0, 0, 0, 1] + + @staticmethod + def matrix_multiply(m, n): + # column major ! + + return [ + m[0] * n[0] + m[4] * n[1] + m[8] * n[2] + m[12] * n[3], + m[1] * n[0] + m[5] * n[1] + m[9] * n[2] + m[13] * n[3], + m[2] * n[0] + m[6] * n[1] + m[10] * n[2] + m[14] * n[3], + m[3] * n[0] + m[7] * n[1] + m[11] * n[2] + m[15] * n[3], + m[0] * n[4] + m[4] * n[5] + m[8] * n[6] + m[12] * n[7], + m[1] * n[4] + m[5] * n[5] + m[9] * n[6] + m[13] * n[7], + m[2] * n[4] + m[6] * n[5] + m[10] * n[6] + m[14] * n[7], + m[3] * n[4] + m[7] * n[5] + m[11] * n[6] + m[15] * n[7], + m[0] * n[8] + m[4] * n[9] + m[8] * n[10] + m[12] * n[11], + m[1] * n[8] + m[5] * n[9] + m[9] * n[10] + m[13] * n[11], + m[2] * n[8] + m[6] * n[9] + m[10] * n[10] + m[14] * n[11], + m[3] * n[8] + m[7] * n[9] + m[11] * n[10] + m[15] * n[11], + m[0] * n[12] + m[4] * n[13] + m[8] * n[14] + m[12] * n[15], + m[1] * n[12] + m[5] * n[13] + m[9] * n[14] + m[13] * n[15], + m[2] * n[12] + m[6] * n[13] + m[10] * n[14] + m[14] * n[15], + m[3] * n[12] + m[7] * n[13] + m[11] * n[14] + m[15] * n[15], + ] + + @staticmethod + def translation_to_matrix(translation): + # column major ! + return [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, + translation[0], translation[1], translation[2], 1.0] + diff --git a/io_scene_gltf2/io/exp/gltf2_io_binary_data.py b/io_scene_gltf2/io/exp/gltf2_io_binary_data.py new file mode 100755 index 00000000..42f6d5d7 --- /dev/null +++ b/io_scene_gltf2/io/exp/gltf2_io_binary_data.py @@ -0,0 +1,36 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing +import array +from io_scene_gltf2.io.com import gltf2_io_constants + + +class BinaryData: + """Store for gltf binary data that can later be stored in a buffer.""" + + def __init__(self, data: bytes): + if not isinstance(data, bytes): + raise TypeError("Data is not a bytes array") + self.data = data + + @classmethod + def from_list(cls, lst: typing.List[typing.Any], gltf_component_type: gltf2_io_constants.ComponentType): + format_char = gltf2_io_constants.ComponentType.to_type_code(gltf_component_type) + return BinaryData(array.array(format_char, lst).tobytes()) + + @property + def byte_length(self): + return len(self.data) + diff --git a/io_scene_gltf2/io/exp/gltf2_io_buffer.py b/io_scene_gltf2/io/exp/gltf2_io_buffer.py new file mode 100755 index 00000000..694be11e --- /dev/null +++ b/io_scene_gltf2/io/exp/gltf2_io_buffer.py @@ -0,0 +1,61 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 + +from io_scene_gltf2.io.com import gltf2_io +from io_scene_gltf2.io.exp import gltf2_io_binary_data + + +class Buffer: + """Class representing binary data for use in a glTF file as 'buffer' property.""" + + def __init__(self, buffer_index=0): + self.__data = b"" + self.__buffer_index = buffer_index + + def add_and_get_view(self, binary_data: gltf2_io_binary_data.BinaryData) -> gltf2_io.BufferView: + """Add binary data to the buffer. Return a glTF BufferView.""" + offset = len(self.__data) + self.__data += binary_data.data + + # offsets should be a multiple of 4 --> therefore add padding if necessary + padding = (4 - (binary_data.byte_length % 4)) % 4 + self.__data += b"\x00" * padding + + buffer_view = gltf2_io.BufferView( + buffer=self.__buffer_index, + byte_length=binary_data.byte_length, + byte_offset=offset, + byte_stride=None, + extensions=None, + extras=None, + name=None, + target=None + ) + return buffer_view + + @property + def byte_length(self): + return len(self.__data) + + def to_bytes(self): + return self.__data + + def to_embed_string(self): + return 'data:application/octet-stream;base64,' + base64.b64encode(self.__data).decode('ascii') + + def clear(self): + self.__data = b"" + diff --git a/io_scene_gltf2/io/exp/gltf2_io_export.py b/io_scene_gltf2/io/exp/gltf2_io_export.py new file mode 100755 index 00000000..286d4e75 --- /dev/null +++ b/io_scene_gltf2/io/exp/gltf2_io_export.py @@ -0,0 +1,97 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +import json +import struct + +# +# Globals +# + +# +# Functions +# + + +def save_gltf(glTF, export_settings, encoder, glb_buffer): + indent = None + separators = separators = (',', ':') + + if export_settings['gltf_format'] != 'GLB': + indent = 4 + # The comma is typically followed by a newline, so no trailing whitespace is needed on it. + separators = separators = (',', ' : ') + + glTF_encoded = json.dumps(glTF, indent=indent, separators=separators, sort_keys=True, cls=encoder, allow_nan=False) + + # + + if export_settings['gltf_format'] != 'GLB': + file = open(export_settings['gltf_filepath'], "w", encoding="utf8", newline="\n") + file.write(glTF_encoded) + file.write("\n") + file.close() + + binary = export_settings['gltf_binary'] + if len(binary) > 0 and not export_settings['gltf_embed_buffers']: + file = open(export_settings['gltf_filedirectory'] + export_settings['gltf_binaryfilename'], "wb") + file.write(binary) + file.close() + + else: + file = open(export_settings['gltf_filepath'], "wb") + + glTF_data = glTF_encoded.encode() + binary = glb_buffer + + length_gtlf = len(glTF_data) + spaces_gltf = (4 - (length_gtlf & 3)) & 3 + length_gtlf += spaces_gltf + + length_bin = len(binary) + zeros_bin = (4 - (length_bin & 3)) & 3 + length_bin += zeros_bin + + length = 12 + 8 + length_gtlf + if length_bin > 0: + length += 8 + length_bin + + # Header (Version 2) + file.write('glTF'.encode()) + file.write(struct.pack("I", 2)) + file.write(struct.pack("I", length)) + + # Chunk 0 (JSON) + file.write(struct.pack("I", length_gtlf)) + file.write('JSON'.encode()) + file.write(glTF_data) + for i in range(0, spaces_gltf): + file.write(' '.encode()) + + # Chunk 1 (BIN) + if length_bin > 0: + file.write(struct.pack("I", length_bin)) + file.write('BIN\0'.encode()) + file.write(binary) + for i in range(0, zeros_bin): + file.write('\0'.encode()) + + file.close() + + return True + diff --git a/io_scene_gltf2/io/exp/gltf2_io_get.py b/io_scene_gltf2/io/exp/gltf2_io_get.py new file mode 100755 index 00000000..35c65615 --- /dev/null +++ b/io_scene_gltf2/io/exp/gltf2_io_get.py @@ -0,0 +1,316 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Imports +# + +import os + +# +# Globals +# + +# +# Functions +# + + +def get_material_requires_texcoords(glTF, index): + """Query function, if a material "needs" texture coordinates. This is the case, if a texture is present and used.""" + if glTF.materials is None: + return False + + materials = glTF.materials + + if index < 0 or index >= len(materials): + return False + + material = materials[index] + + # General + + if material.emissive_texture is not None: + return True + + if material.normal_texture is not None: + return True + + if material.occlusion_texture is not None: + return True + + # Metallic roughness + + if material.pbr_metallic_roughness is not None and \ + material.pbr_metallic_roughness.base_color_texture is not None: + return True + + if material.pbr_metallic_roughness is not None and \ + material.pbr_metallic_roughness.metallic_roughness_texture is not None: + return True + + return False + + +def get_material_requires_normals(glTF, index): + """ + Query function, if a material "needs" normals. This is the case, if a texture is present and used. + + At point of writing, same function as for texture coordinates. + """ + return get_material_requires_texcoords(glTF, index) + + +def get_material_index(glTF, name): + """Return the material index in the glTF array.""" + if name is None: + return -1 + + if glTF.materials is None: + return -1 + + index = 0 + for material in glTF.materials: + if material.name == name: + return index + + index += 1 + + return -1 + + +def get_mesh_index(glTF, name): + """Return the mesh index in the glTF array.""" + if glTF.meshes is None: + return -1 + + index = 0 + for mesh in glTF.meshes: + if mesh.name == name: + return index + + index += 1 + + return -1 + + +def get_skin_index(glTF, name, index_offset): + """Return the skin index in the glTF array.""" + if glTF.skins is None: + return -1 + + skeleton = get_node_index(glTF, name) + + index = 0 + for skin in glTF.skins: + if skin.skeleton == skeleton: + return index + index_offset + + index += 1 + + return -1 + + +def get_camera_index(glTF, name): + """Return the camera index in the glTF array.""" + if glTF.cameras is None: + return -1 + + index = 0 + for camera in glTF.cameras: + if camera.name == name: + return index + + index += 1 + + return -1 + + +def get_light_index(glTF, name): + """Return the light index in the glTF array.""" + if glTF.extensions is None: + return -1 + + extensions = glTF.extensions + + if extensions.get('KHR_lights_punctual') is None: + return -1 + + khr_lights_punctual = extensions['KHR_lights_punctual'] + + if khr_lights_punctual.get('lights') is None: + return -1 + + lights = khr_lights_punctual['lights'] + + index = 0 + for light in lights: + if light['name'] == name: + return index + + index += 1 + + return -1 + + +def get_node_index(glTF, name): + """Return the node index in the glTF array.""" + if glTF.nodes is None: + return -1 + + index = 0 + for node in glTF.nodes: + if node.name == name: + return index + + index += 1 + + return -1 + + +def get_scene_index(glTF, name): + """Return the scene index in the glTF array.""" + if glTF.scenes is None: + return -1 + + index = 0 + for scene in glTF.scenes: + if scene.name == name: + return index + + index += 1 + + return -1 + + +def get_texture_index(glTF, filename): + """Return the texture index in the glTF array by a given file path.""" + if glTF.textures is None: + return -1 + + image_index = get_image_index(glTF, filename) + + if image_index == -1: + return -1 + + for texture_index, texture in enumerate(glTF.textures): + if image_index == texture.source: + return texture_index + + return -1 + + +def get_image_index(glTF, filename): + """Return the image index in the glTF array.""" + if glTF.images is None: + return -1 + + image_name = get_image_name(filename) + + for index, current_image in enumerate(glTF.images): + if image_name == current_image.name: + return index + + return -1 + + +def get_image_name(filename): + """Return user-facing, extension-agnostic name for image.""" + return os.path.splitext(filename)[0] + + +def get_scalar(default_value, init_value=0.0): + """Return scalar with a given default/fallback value.""" + return_value = init_value + + if default_value is None: + return return_value + + return_value = default_value + + return return_value + + +def get_vec2(default_value, init_value=[0.0, 0.0]): + """Return vec2 with a given default/fallback value.""" + return_value = init_value + + if default_value is None or len(default_value) < 2: + return return_value + + index = 0 + for number in default_value: + return_value[index] = number + + index += 1 + if index == 2: + return return_value + + return return_value + + +def get_vec3(default_value, init_value=[0.0, 0.0, 0.0]): + """Return vec3 with a given default/fallback value.""" + return_value = init_value + + if default_value is None or len(default_value) < 3: + return return_value + + index = 0 + for number in default_value: + return_value[index] = number + + index += 1 + if index == 3: + return return_value + + return return_value + + +def get_vec4(default_value, init_value=[0.0, 0.0, 0.0, 1.0]): + """Return vec4 with a given default/fallback value.""" + return_value = init_value + + if default_value is None or len(default_value) < 4: + return return_value + + index = 0 + for number in default_value: + return_value[index] = number + + index += 1 + if index == 4: + return return_value + + return return_value + + +def get_index(elements, name): + """Return index of a glTF element by a given name.""" + if elements is None or name is None: + return -1 + + index = 0 + for element in elements: + if isinstance(element, dict): + if element.get('name') == name: + return index + else: + if element.name == name: + return index + + index += 1 + + return -1 + diff --git a/io_scene_gltf2/io/exp/gltf2_io_image_data.py b/io_scene_gltf2/io/exp/gltf2_io_image_data.py new file mode 100755 index 00000000..92bdd09f --- /dev/null +++ b/io_scene_gltf2/io/exp/gltf2_io_image_data.py @@ -0,0 +1,136 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing +import struct +import re +import zlib +import numpy as np + +class ImageData: + """Contains channels of an image with raw pixel data.""" + # TODO: refactor to only operate on numpy arrays + # FUTURE_WORK: as a method to allow the node graph to be better supported, we could model some of + # the node graph elements with numpy functions + + def __init__(self, name: str, filepath: str, width: int, height: int, offset: int, channels: typing.Optional[typing.List[np.ndarray]] = []): + if width <= 0 or height <= 0: + raise ValueError("Image data can not have zero width or height") + if offset + len(channels) > 4: + raise ValueError("Image data can not have more than 4 channels") + self.channels = [None, None, None, None] + channels_length = len(channels) + for index in range(offset, offset + channels_length): + self.channels[index] = channels[index - offset] + self.name = name + self.filepath = filepath + self.width = width + self.height = height + + def add_to_image(self, channel, image_data): + if self.width != image_data.width or self.height != image_data.height: + raise ValueError("Image dimensions do not match") + if channel < 0 or channel > 3: + raise ValueError("Can't append image: channels out of bounds") + if len(image_data.channels) != 4: + raise ValueError("Can't append image: incomplete image") + + if self.name != image_data.name: + self.name += image_data.name + self.filepath = "" + + # Replace channel. + self.channels[channel] = image_data.channels[channel] + + @property + def r(self): + if len(self.channels) <= 0: + return None + return self.channels[0] + + @property + def g(self): + if len(self.channels) <= 1: + return None + return self.channels[1] + + @property + def b(self): + if len(self.channels) <= 2: + return None + return self.channels[2] + + @property + def a(self): + if len(self.channels) <= 3: + return None + return self.channels[3] + + def get_extension(self): + allowed_extensions = ['.png', '.jpg', '.jpeg'] + fallback_extension = allowed_extensions[0] + + matches = re.findall(r'\.\w+$', self.filepath) + extension = matches[0] if len(matches) > 0 else fallback_extension + return extension if extension.lower() in allowed_extensions else fallback_extension + + def to_image_data(self, mime_type: str) -> bytes: + if mime_type == 'image/png': + return self.to_png_data() + raise ValueError("Unsupported image file type {}".format(mime_type)) + + def to_png_data(self) -> bytes: + channels = self.channels + + # if there is no data, create a single pixel image + if not channels: + channels = np.ones((1, 1)) + # fill all channels of the png + for _ in range(4 - len(channels)): + channels.append(np.ones_like(channels[0])) + else: + template_index = None + for index in range(0, 4): + if channels[index] is not None: + template_index = index + break + for index in range(0, 4): + if channels[index] is None: + channels[index] = np.ones_like(channels[template_index]) + + image = np.concatenate(channels, axis=1) + image = image.flatten() + image = (image * 255.0).astype(np.uint8) + buf = image.tobytes() + + # + # Taken from 'blender-thumbnailer.py' in Blender. + # + + # reverse the vertical line order and add null bytes at the start + width_byte_4 = self.width * 4 + raw_data = b"".join( + b'\x00' + buf[span:span + width_byte_4] for span in range( + (self.height - 1) * self.width * 4, -1, - width_byte_4)) + + def png_pack(png_tag, data): + chunk_head = png_tag + data + return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head)) + + return b"".join([ + b'\x89PNG\r\n\x1a\n', + png_pack(b'IHDR', struct.pack("!2I5B", self.width, self.height, 8, 6, 0, 0, 0)), + png_pack(b'IDAT', zlib.compress(raw_data, 9)), + png_pack(b'IEND', b'')]) + diff --git a/io_scene_gltf2/io/imp/__init__.py b/io_scene_gltf2/io/imp/__init__.py new file mode 100755 index 00000000..d3c53771 --- /dev/null +++ b/io_scene_gltf2/io/imp/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""IO imp package.""" + diff --git a/io_scene_gltf2/io/imp/gltf2_io_binary.py b/io_scene_gltf2/io/imp/gltf2_io_binary.py new file mode 100755 index 00000000..5f51d95d --- /dev/null +++ b/io_scene_gltf2/io/imp/gltf2_io_binary.py @@ -0,0 +1,178 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import struct +import base64 +from os.path import dirname, join, isfile, basename + + +class BinaryData(): + """Binary reader.""" + def __new__(cls, *args, **kwargs): + raise RuntimeError("%s should not be instantiated" % cls) + + @staticmethod + def get_binary_from_accessor(gltf, accessor_idx): + """Get binary from accessor.""" + accessor = gltf.data.accessors[accessor_idx] + bufferView = gltf.data.buffer_views[accessor.buffer_view] # TODO initialize with 0 when not present! + if bufferView.buffer in gltf.buffers.keys(): + buffer = gltf.buffers[bufferView.buffer] + else: + # load buffer + gltf.load_buffer(bufferView.buffer) + buffer = gltf.buffers[bufferView.buffer] + + accessor_offset = accessor.byte_offset + bufferview_offset = bufferView.byte_offset + + if accessor_offset is None: + accessor_offset = 0 + if bufferview_offset is None: + bufferview_offset = 0 + + return buffer[accessor_offset + bufferview_offset:accessor_offset + bufferview_offset + bufferView.byte_length] + + @staticmethod + def get_data_from_accessor(gltf, accessor_idx): + """Get data from accessor.""" + accessor = gltf.data.accessors[accessor_idx] + + bufferView = gltf.data.buffer_views[accessor.buffer_view] # TODO initialize with 0 when not present! + buffer_data = BinaryData.get_binary_from_accessor(gltf, accessor_idx) + + fmt_char = gltf.fmt_char_dict[accessor.component_type] + component_nb = gltf.component_nb_dict[accessor.type] + fmt = '<' + (fmt_char * component_nb) + stride_ = struct.calcsize(fmt) + # TODO data alignment stuff + + if bufferView.byte_stride: + stride = bufferView.byte_stride + else: + stride = stride_ + + data = [] + offset = 0 + while len(data) < accessor.count: + element = struct.unpack_from(fmt, buffer_data, offset) + data.append(element) + offset += stride + + if accessor.sparse: + sparse_indices_data = BinaryData.get_data_from_sparse(gltf, accessor.sparse, "indices") + sparse_values_values = BinaryData.get_data_from_sparse( + gltf, + accessor.sparse, + "values", + accessor.type, + accessor.component_type + ) + + # apply sparse + for cpt_idx, idx in enumerate(sparse_indices_data): + data[idx[0]] = sparse_values_values[cpt_idx] + + # Normalization + if accessor.normalized: + for idx, tuple in enumerate(data): + new_tuple = () + for i in tuple: + new_tuple += (float(i),) + data[idx] = new_tuple + + return data + + @staticmethod + def get_data_from_sparse(gltf, sparse, type_, type_val=None, comp_type=None): + """Get data from sparse.""" + if type_ == "indices": + bufferView = gltf.data.buffer_views[sparse.indices.buffer_view] + offset = sparse.indices.byte_offset + component_nb = gltf.component_nb_dict['SCALAR'] + fmt_char = gltf.fmt_char_dict[sparse.indices.component_type] + elif type_ == "values": + bufferView = gltf.data.buffer_views[sparse.values.buffer_view] + offset = sparse.values.byte_offset + component_nb = gltf.component_nb_dict[type_val] + fmt_char = gltf.fmt_char_dict[comp_type] + + if bufferView.buffer in gltf.buffers.keys(): + buffer = gltf.buffers[bufferView.buffer] + else: + # load buffer + gltf.load_buffer(bufferView.buffer) + buffer = gltf.buffers[bufferView.buffer] + + bin_data = buffer[bufferView.byte_offset + offset:bufferView.byte_offset + offset + bufferView.byte_length] + + fmt = '<' + (fmt_char * component_nb) + stride_ = struct.calcsize(fmt) + # TODO data alignment stuff ? + + if bufferView.byte_stride: + stride = bufferView.byte_stride + else: + stride = stride_ + + data = [] + offset = 0 + while len(data) < sparse.count: + element = struct.unpack_from(fmt, bin_data, offset) + data.append(element) + offset += stride + + return data + + @staticmethod + def get_image_data(gltf, img_idx): + """Get data from image.""" + pyimage = gltf.data.images[img_idx] + + image_name = "Image_" + str(img_idx) + + if pyimage.uri: + sep = ';base64,' + if pyimage.uri[:5] == 'data:': + idx = pyimage.uri.find(sep) + if idx != -1: + data = pyimage.uri[idx + len(sep):] + return base64.b64decode(data), image_name + + if isfile(join(dirname(gltf.filename), pyimage.uri)): + with open(join(dirname(gltf.filename), pyimage.uri), 'rb') as f_: + return f_.read(), basename(join(dirname(gltf.filename), pyimage.uri)) + else: + pyimage.gltf.log.error("Missing file (index " + str(img_idx) + "): " + pyimage.uri) + return None, None + + if pyimage.buffer_view is None: + return None, None + + bufferView = gltf.data.buffer_views[pyimage.buffer_view] + + if bufferView.buffer in gltf.buffers.keys(): + buffer = gltf.buffers[bufferView.buffer] + else: + # load buffer + gltf.load_buffer(bufferView.buffer) + buffer = gltf.buffers[bufferView.buffer] + + bufferview_offset = bufferView.byte_offset + + if bufferview_offset is None: + bufferview_offset = 0 + + return buffer[bufferview_offset:bufferview_offset + bufferView.byte_length], image_name + diff --git a/io_scene_gltf2/io/imp/gltf2_io_gltf.py b/io_scene_gltf2/io/imp/gltf2_io_gltf.py new file mode 100755 index 00000000..1c9e67a2 --- /dev/null +++ b/io_scene_gltf2/io/imp/gltf2_io_gltf.py @@ -0,0 +1,199 @@ +# Copyright 2018 The glTF-Blender-IO authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ..com.gltf2_io import gltf_from_dict +from ..com.gltf2_io_debug import Log +import logging +import json +import struct +import base64 +from os.path import dirname, join, getsize, isfile + + +class glTFImporter(): + """glTF Importer class.""" + + def __init__(self, filename, import_settings): + """initialization.""" + self.filename = filename + self.import_settings = import_settings + self.buffers = {} + + if 'loglevel' not in self.import_settings.keys(): + self.import_settings['loglevel'] = logging.ERROR + + log = Log(import_settings['loglevel']) + self.log = log.logger + self.log_handler = log.hdlr + + self.SIMPLE = 1 + self.TEXTURE = 2 + self.TEXTURE_FACTOR = 3 + + # TODO: move to a com place? + self.extensions_managed = [ + 'KHR_materials_pbrSpecularGlossiness' + ] + + # TODO : merge with io_constants + self.fmt_char_dict = {} + self.fmt_char_dict[5120] = 'b' # Byte + self.fmt_char_dict[5121] = 'B' # Unsigned Byte + self.fmt_char_dict[5122] = 'h' # Short + self.fmt_char_dict[5123] = 'H' # Unsigned Short + self.fmt_char_dict[5125] = 'I' # Unsigned Int + self.fmt_char_dict[5126] = 'f' # Float + + self.component_nb_dict = {} + self.component_nb_dict['SCALAR'] = 1 + self.component_nb_dict['VEC2'] = 2 + self.component_nb_dict['VEC3'] = 3 + self.component_nb_dict['VEC4'] = 4 + self.component_nb_dict['MAT2'] = 4 + self.component_nb_dict['MAT3'] = 9 + self.component_nb_dict['MAT4'] = 16 + + @staticmethod + def bad_json_value(val): + """Bad Json value.""" + raise ValueError('Json contains some unauthorized values') + + def checks(self): + """Some checks.""" + if self.data.asset.version != "2.0": + return False, "glTF version must be 2" + + if self.data.extensions_required is not None: + for extension in self.data.extensions_required: + if extension not in self.data.extensions_used: + return False, "Extension required must be in Extension Used too" + if extension not in self.extensions_managed: + return False, "Extension " + extension + " is not available on this addon version" + + if self.data.extensions_used is not None: + for extension in self.data.extensions_used: + if extension not in self.extensions_managed: + # Non blocking error #TODO log + pass + + return True, None + + def load_glb(self): + """Load binary glb.""" + header = struct.unpack_from('<4sII', self.content) + self.format = header[0] + self.version = header[1] + self.file_size = header[2] + + if self.format != b'glTF': + return False, "This file is not a glTF/glb file" + + if self.version != 2: + return False, "glTF version doesn't match to 2" + + if self.file_size != getsize(self.filename): + return False, "File size doesn't match" + + offset = 12 # header size = 12 + + # TODO check json type for chunk 0, and BIN type for next ones + + # json + type, len_, str_json, offset = self.load_chunk(offset) + if len_ != len(str_json): + return False, "Length of json part doesn't match" + try: + json_ = json.loads(str_json.decode('utf-8'), parse_constant=glTFImporter.bad_json_value) + self.data = gltf_from_dict(json_) + except ValueError as e: + return False, e.args[0] + + # binary data + chunk_cpt = 0 + while offset < len(self.content): + type, len_, data, offset = self.load_chunk(offset) + if len_ != len(data): + return False, "Length of bin buffer " + str(chunk_cpt) + " doesn't match" + + self.buffers[chunk_cpt] = data + chunk_cpt += 1 + + self.content = None + return True, None + + def load_chunk(self, offset): + """Load chunk.""" + chunk_header = struct.unpack_from('<I4s', self.content, offset) + data_length = chunk_header[0] + data_type = chunk_header[1] + data = self.content[offset + 8: offset + 8 + data_length] + + return data_type, data_length, data, offset + 8 + data_length + + def read(self): + """Read file.""" + # Check this is a file + if not isfile(self.filename): + return False, "Please select a file" + + # Check if file is gltf or glb + with open(self.filename, 'rb') as f: + self.content = f.read() + + self.is_glb_format = self.content[:4] == b'glTF' + + # glTF file + if not self.is_glb_format: + self.content = None + with open(self.filename, 'r') as f: + content = f.read() + try: + self.data = gltf_from_dict(json.loads(content, parse_constant=glTFImporter.bad_json_value)) + return True, None + except ValueError as e: + return False, e.args[0] + + # glb file + else: + # Parsing glb file + success, txt = self.load_glb() + return success, txt + + def is_node_joint(self, node_idx): + """Check if node is a joint.""" + if not self.data.skins: # if no skin in gltf file + return False, None + + for skin_idx, skin in enumerate(self.data.skins): + if node_idx in skin.joints: + return True, skin_idx + + return False, None + + def load_buffer(self, buffer_idx): + """Load buffer.""" + buffer = self.data.buffers[buffer_idx] + + if buffer.uri: + sep = ';base64,' + if buffer.uri[:5] == 'data:': + idx = buffer.uri.find(sep) + if idx != -1: + data = buffer.uri[idx + len(sep):] + self.buffers[buffer_idx] = base64.b64decode(data) + return + + with open(join(dirname(self.filename), buffer.uri), 'rb') as f_: + self.buffers[buffer_idx] = f_.read() + diff --git a/io_scene_ms3d/__init__.py b/io_scene_ms3d/__init__.py index 794aae6b..805b1d83 100644 --- a/io_scene_ms3d/__init__.py +++ b/io_scene_ms3d/__init__.py @@ -64,8 +64,8 @@ from bpy.utils import ( unregister_module, ) from bpy.types import ( - INFO_MT_file_export, - INFO_MT_file_import, + TOPBAR_MT_file_export, + TOPBAR_MT_file_import, ) @@ -82,16 +82,16 @@ def register(): ms3d_ui.register() register_module(__name__) - INFO_MT_file_export.append(Ms3dExportOperator.menu_func) - INFO_MT_file_import.append(Ms3dImportOperator.menu_func) + TOPBAR_MT_file_export.append(Ms3dExportOperator.menu_func) + TOPBAR_MT_file_import.append(Ms3dImportOperator.menu_func) def unregister(): ms3d_ui.unregister() unregister_module(__name__) - INFO_MT_file_export.remove(Ms3dExportOperator.menu_func) - INFO_MT_file_import.remove(Ms3dImportOperator.menu_func) + TOPBAR_MT_file_export.remove(Ms3dExportOperator.menu_func) + TOPBAR_MT_file_import.remove(Ms3dImportOperator.menu_func) ############################################################################### diff --git a/io_scene_ms3d/ms3d_import.py b/io_scene_ms3d/ms3d_import.py index a2205556..d5ef0015 100644 --- a/io_scene_ms3d/ms3d_import.py +++ b/io_scene_ms3d/ms3d_import.py @@ -234,7 +234,7 @@ class Ms3dImporter(): blender_scene = blender_context.scene - blender_group = blender_context.blend_data.groups.new( + blender_group = blender_context.blend_data.collections.new( FORMAT_GROUP.format(ms3d_model.name)) blender_empty_object = blender_context.blend_data.objects.new( FORMAT_EMPTY_OBJECT.format(ms3d_model.name), None) @@ -690,14 +690,14 @@ class Ms3dImporter(): blender_armature = blender_context.blend_data.armatures.new( ms3d_armature_name) blender_armature.ms3d.name = ms3d_model.name - blender_armature.draw_type = 'STICK' + blender_armature.display_type = 'STICK' blender_armature.show_axes = True blender_armature.use_auto_ik = True blender_armature_object = blender_context.blend_data.objects.new( ms3d_armature_object_name, blender_armature) blender_scene.objects.link(blender_armature_object) #blender_armature_object.location = blender_scene.cursor_location - blender_armature_object.show_x_ray = True + blender_armature_object.show_in_front = True ########################## # create new modifier @@ -771,7 +771,7 @@ class Ms3dImporter(): in ms3d_to_blender_vertex_groups.items(): ms3d_name = ms3d_model.joints[ms3d_bone_id].name blender_vertex_group = blender_mesh_object.vertex_groups.new( - ms3d_name) + name=ms3d_name) for blender_vertex_id_weight in blender_vertex_index_weight_list: blender_vertex_index = blender_vertex_id_weight[0] blender_vertex_weight = blender_vertex_id_weight[1] diff --git a/io_scene_ms3d/ms3d_ui.py b/io_scene_ms3d/ms3d_ui.py index e7b1dfc4..c483df8a 100644 --- a/io_scene_ms3d/ms3d_ui.py +++ b/io_scene_ms3d/ms3d_ui.py @@ -210,7 +210,7 @@ class Ms3dUi: return ms3d_value ########################################################################### - ICON_OPTIONS = 'LAMP' + ICON_OPTIONS = 'LIGHT' ICON_OBJECT = 'WORLD' ICON_PROCESSING = 'OBJECT_DATAMODE' ICON_MODIFIER = 'MODIFIER' diff --git a/io_scene_obj/__init__.py b/io_scene_obj/__init__.py index 3bebdecd..bb8c3e2c 100644 --- a/io_scene_obj/__init__.py +++ b/io_scene_obj/__init__.py @@ -21,8 +21,8 @@ bl_info = { "name": "Wavefront OBJ format", "author": "Campbell Barton, Bastien Montagne", - "version": (2, 3, 7), - "blender": (2, 78, 0), + "version": (3, 5, 2), + "blender": (2, 80, 0), "location": "File > Import-Export", "description": "Import-Export OBJ, Import OBJ mesh, UV's, materials and textures", "warning": "", @@ -48,70 +48,68 @@ from bpy.props import ( from bpy_extras.io_utils import ( ImportHelper, ExportHelper, - orientation_helper_factory, + orientation_helper, path_reference_mode, axis_conversion, ) -IOOBJOrientationHelper = orientation_helper_factory("IOOBJOrientationHelper", axis_forward='-Z', axis_up='Y') - - -class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper): +@orientation_helper(axis_forward='-Z', axis_up='Y') +class ImportOBJ(bpy.types.Operator, ImportHelper): """Load a Wavefront OBJ File""" bl_idname = "import_scene.obj" bl_label = "Import OBJ" bl_options = {'PRESET', 'UNDO'} filename_ext = ".obj" - filter_glob = StringProperty( + filter_glob: StringProperty( default="*.obj;*.mtl", options={'HIDDEN'}, ) - use_edges = BoolProperty( + use_edges: BoolProperty( name="Lines", description="Import lines and faces with 2 verts as edge", default=True, ) - use_smooth_groups = BoolProperty( + use_smooth_groups: BoolProperty( name="Smooth Groups", description="Surround smooth groups by sharp edges", default=True, ) - use_split_objects = BoolProperty( + use_split_objects: BoolProperty( name="Object", description="Import OBJ Objects into Blender Objects", default=True, ) - use_split_groups = BoolProperty( + use_split_groups: BoolProperty( name="Group", description="Import OBJ Groups into Blender Objects", default=True, ) - use_groups_as_vgroups = BoolProperty( + use_groups_as_vgroups: BoolProperty( name="Poly Groups", description="Import OBJ groups as vertex groups", default=False, ) - use_image_search = BoolProperty( + use_image_search: BoolProperty( name="Image Search", description="Search subdirs for any associated images " "(Warning, may be slow)", default=True, ) - split_mode = EnumProperty( + split_mode: EnumProperty( name="Split", items=(('ON', "Split", "Split geometry, omits unused verts"), ('OFF', "Keep Vert Order", "Keep vertex order from file"), ), ) - global_clamp_size = FloatProperty( + global_clight_size: FloatProperty( name="Clamp Size", description="Clamp bounds under this value (zero to disable)", min=0.0, max=1000.0, @@ -139,7 +137,6 @@ class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper): from_up=self.axis_up, ).to_4x4() keywords["global_matrix"] = global_matrix - keywords["use_cycles"] = (context.scene.render.engine == 'CYCLES') if bpy.data.is_saved and context.user_preferences.filepaths.use_relative_paths: import os @@ -166,15 +163,16 @@ class ImportOBJ(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper): else: row.prop(self, "use_groups_as_vgroups") - row = layout.split(percentage=0.67) - row.prop(self, "global_clamp_size") + row = layout.split(factor=0.67) + row.prop(self, "global_clight_size") layout.prop(self, "axis_forward") layout.prop(self, "axis_up") layout.prop(self, "use_image_search") -class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper): +@orientation_helper(axis_forward='-Z', axis_up='Y') +class ExportOBJ(bpy.types.Operator, ExportHelper): """Save a Wavefront OBJ File""" bl_idname = "export_scene.obj" @@ -182,113 +180,113 @@ class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper): bl_options = {'PRESET'} filename_ext = ".obj" - filter_glob = StringProperty( + filter_glob: StringProperty( default="*.obj;*.mtl", options={'HIDDEN'}, ) # context group - use_selection = BoolProperty( + use_selection: BoolProperty( name="Selection Only", description="Export selected objects only", default=False, ) - use_animation = BoolProperty( + use_animation: BoolProperty( name="Animation", description="Write out an OBJ for each frame", default=False, ) # object group - use_mesh_modifiers = BoolProperty( + use_mesh_modifiers: BoolProperty( name="Apply Modifiers", description="Apply modifiers", default=True, ) - use_mesh_modifiers_render = BoolProperty( + use_mesh_modifiers_render: BoolProperty( name="Use Modifiers Render Settings", description="Use render settings when applying modifiers to mesh objects", default=False, ) # extra data group - use_edges = BoolProperty( + use_edges: BoolProperty( name="Include Edges", description="", default=True, ) - use_smooth_groups = BoolProperty( + use_smooth_groups: BoolProperty( name="Smooth Groups", description="Write sharp edges as smooth groups", default=False, ) - use_smooth_groups_bitflags = BoolProperty( + use_smooth_groups_bitflags: BoolProperty( name="Bitflag Smooth Groups", description="Same as 'Smooth Groups', but generate smooth groups IDs as bitflags " "(produces at most 32 different smooth groups, usually much less)", default=False, ) - use_normals = BoolProperty( + use_normals: BoolProperty( name="Write Normals", description="Export one normal per vertex and per face, to represent flat faces and sharp edges", default=True, ) - use_uvs = BoolProperty( + use_uvs: BoolProperty( name="Include UVs", description="Write out the active UV coordinates", default=True, ) - use_materials = BoolProperty( + use_materials: BoolProperty( name="Write Materials", description="Write out the MTL file", default=True, ) - use_triangles = BoolProperty( + use_triangles: BoolProperty( name="Triangulate Faces", description="Convert all faces to triangles", default=False, ) - use_nurbs = BoolProperty( + use_nurbs: BoolProperty( name="Write Nurbs", description="Write nurbs curves as OBJ nurbs rather than " "converting to geometry", default=False, ) - use_vertex_groups = BoolProperty( + use_vertex_groups: BoolProperty( name="Polygroups", description="", default=False, ) # grouping group - use_blen_objects = BoolProperty( + use_blen_objects: BoolProperty( name="Objects as OBJ Objects", description="", default=True, ) - group_by_object = BoolProperty( + group_by_object: BoolProperty( name="Objects as OBJ Groups ", description="", default=False, ) - group_by_material = BoolProperty( + group_by_material: BoolProperty( name="Material Groups", description="", default=False, ) - keep_vertex_order = BoolProperty( + keep_vertex_order: BoolProperty( name="Keep Vertex Order", description="", default=False, ) - global_scale = FloatProperty( + global_scale: FloatProperty( name="Scale", min=0.01, max=1000.0, default=1.0, ) - path_mode = path_reference_mode + path_mode: path_reference_mode check_extension = True @@ -303,7 +301,7 @@ class ExportOBJ(bpy.types.Operator, ExportHelper, IOOBJOrientationHelper): "filter_glob", )) - global_matrix = (Matrix.Scale(self.global_scale, 4) * + global_matrix = (Matrix.Scale(self.global_scale, 4) @ axis_conversion(to_forward=self.axis_forward, to_up=self.axis_up, ).to_4x4()) @@ -330,13 +328,13 @@ def register(): for cls in classes: bpy.utils.register_class(cls) - bpy.types.INFO_MT_file_import.append(menu_func_import) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): - bpy.types.INFO_MT_file_import.remove(menu_func_import) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) for cls in classes: bpy.utils.unregister_class(cls) diff --git a/io_scene_obj/export_obj.py b/io_scene_obj/export_obj.py index 05ff4db2..0e905141 100644 --- a/io_scene_obj/export_obj.py +++ b/io_scene_obj/export_obj.py @@ -21,8 +21,8 @@ import os import bpy -import mathutils -import bpy_extras.io_utils +from mathutils import Matrix, Vector, Color +from bpy_extras import io_utils, node_shader_utils from bpy_extras.wm_utils.progress_report import ( ProgressReport, @@ -47,13 +47,8 @@ def mesh_triangulate(me): def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict): - from mathutils import Color, Vector - world = scene.world - if world: - world_amb = world.ambient_color - else: - world_amb = Color((0.0, 0.0, 0.0)) + world_amb = Color((0.8, 0.8, 0.8)) source_dir = os.path.dirname(bpy.data.filepath) dest_dir = os.path.dirname(filepath) @@ -69,133 +64,100 @@ def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict): # Write material/image combinations we have used. # Using mtl_dict.values() directly gives un-predictable order. - for mtl_mat_name, mat, face_img in mtl_dict_values: + for mtl_mat_name, mat in mtl_dict_values: # Get the Blender data for the material and the image. # Having an image named None will make a bug, dont do it :) fw('\nnewmtl %s\n' % mtl_mat_name) # Define a new material: matname_imgname - if mat: - use_mirror = mat.raytrace_mirror.use and mat.raytrace_mirror.reflect_factor != 0.0 + mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mat) if mat else None - # convert from blenders spec to 0 - 1000 range. - if mat.specular_shader == 'WARDISO': - tspec = (0.4 - mat.specular_slope) / 0.0004 - else: - tspec = (mat.specular_hardness - 1) / 0.51 - fw('Ns %.6f\n' % tspec) - del tspec + if mat_wrap: + use_mirror = mat_wrap.metallic != 0.0 + use_transparency = mat_wrap.transmission != 0.0 + + # XXX Totally empirical conversion, trying to adapt it + # (from 1.0 - 0.0 Principled BSDF range to 0.0 - 900.0 OBJ specular exponent range)... + spec = (1.0 - mat_wrap.roughness) * 30 + spec *= spec + fw('Ns %.6f\n' % spec) # Ambient if use_mirror: - fw('Ka %.6f %.6f %.6f\n' % (mat.raytrace_mirror.reflect_factor * mat.mirror_color)[:]) + fw('Ka %.6f %.6f %.6f\n' % (mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic)) else: - fw('Ka %.6f %.6f %.6f\n' % (mat.ambient, mat.ambient, mat.ambient)) # Do not use world color! - fw('Kd %.6f %.6f %.6f\n' % (mat.diffuse_intensity * mat.diffuse_color)[:]) # Diffuse - fw('Ks %.6f %.6f %.6f\n' % (mat.specular_intensity * mat.specular_color)[:]) # Specular + fw('Ka %.6f %.6f %.6f\n' % (1.0, 1.0, 1.0)) + fw('Kd %.6f %.6f %.6f\n' % mat_wrap.base_color[:3]) # Diffuse + # XXX TODO Find a way to handle tint and diffuse color, in a consistent way with import... + fw('Ks %.6f %.6f %.6f\n' % (mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)) # Specular # Emission, not in original MTL standard but seems pretty common, see T45766. - # XXX Blender has no color emission, it's using diffuse color instead... - fw('Ke %.6f %.6f %.6f\n' % (mat.emit * mat.diffuse_color)[:]) - if hasattr(mat, "raytrace_transparency") and hasattr(mat.raytrace_transparency, "ior"): - fw('Ni %.6f\n' % mat.raytrace_transparency.ior) # Refraction index - else: - fw('Ni %.6f\n' % 1.0) - fw('d %.6f\n' % mat.alpha) # Alpha (obj uses 'd' for dissolve) + # XXX Not supported by current Principled-based shader. + fw('Ke 0.0 0.0 0.0\n') + fw('Ni %.6f\n' % mat_wrap.ior) # Refraction index + fw('d %.6f\n' % (1.0 - mat_wrap.transmission)) # Alpha (obj uses 'd' for dissolve) # See http://en.wikipedia.org/wiki/Wavefront_.obj_file for whole list of values... # Note that mapping is rather fuzzy sometimes, trying to do our best here. - if mat.use_shadeless: - fw('illum 0\n') # ignore lighting - elif mat.specular_intensity == 0: + if mat_wrap.specular == 0: fw('illum 1\n') # no specular. elif use_mirror: - if mat.use_transparency and mat.transparency_method == 'RAYTRACE': - if mat.raytrace_mirror.fresnel != 0.0: - fw('illum 7\n') # Reflection, Transparency, Ray trace and Fresnel - else: - fw('illum 6\n') # Reflection, Transparency, Ray trace - elif mat.raytrace_mirror.fresnel != 0.0: - fw('illum 5\n') # Reflection, Ray trace and Fresnel + if use_transparency: + fw('illum 6\n') # Reflection, Transparency, Ray trace else: fw('illum 3\n') # Reflection and Ray trace - elif mat.use_transparency and mat.transparency_method == 'RAYTRACE': + elif use_transparency: fw('illum 9\n') # 'Glass' transparency and no Ray trace reflection... fuzzy matching, but... else: fw('illum 2\n') # light normally - else: - # Write a dummy material here? - fw('Ns 0\n') - fw('Ka %.6f %.6f %.6f\n' % world_amb[:]) # Ambient, uses mirror color, - fw('Kd 0.8 0.8 0.8\n') - fw('Ks 0.8 0.8 0.8\n') - fw('d 1\n') # No alpha - fw('illum 2\n') # light normally - - # Write images! - if face_img: # We have an image on the face! - filepath = face_img.filepath - if filepath: # may be '' for generated images - # write relative image path - filepath = bpy_extras.io_utils.path_reference(filepath, source_dir, dest_dir, - path_mode, "", copy_set, face_img.library) - fw('map_Kd %s\n' % filepath) # Diffuse mapping image - del filepath - else: - # so we write the materials image. - face_img = None - - if mat: # No face image. if we havea material search for MTex image. - image_map = {} - # backwards so topmost are highest priority - for mtex in reversed(mat.texture_slots): - if mtex and mtex.texture and mtex.texture.type == 'IMAGE': - image = mtex.texture.image - if image: - # texface overrides others - if (mtex.use_map_color_diffuse and (face_img is None) and - (mtex.use_map_warp is False) and (mtex.texture_coords != 'REFLECTION')): - image_map["map_Kd"] = (mtex, image) - if mtex.use_map_ambient: - image_map["map_Ka"] = (mtex, image) - # this is the Spec intensity channel but Ks stands for specular Color - ''' - if mtex.use_map_specular: - image_map["map_Ks"] = (mtex, image) - ''' - if mtex.use_map_color_spec: # specular color - image_map["map_Ks"] = (mtex, image) - if mtex.use_map_hardness: # specular hardness/glossiness - image_map["map_Ns"] = (mtex, image) - if mtex.use_map_alpha: - image_map["map_d"] = (mtex, image) - if mtex.use_map_translucency: - image_map["map_Tr"] = (mtex, image) - if mtex.use_map_normal: - image_map["map_Bump"] = (mtex, image) - if mtex.use_map_displacement: - image_map["disp"] = (mtex, image) - if mtex.use_map_color_diffuse and (mtex.texture_coords == 'REFLECTION'): - image_map["refl"] = (mtex, image) - if mtex.use_map_emit: - image_map["map_Ke"] = (mtex, image) - - for key, (mtex, image) in sorted(image_map.items()): - filepath = bpy_extras.io_utils.path_reference(image.filepath, source_dir, dest_dir, - path_mode, "", copy_set, image.library) + #### And now, the image textures... + image_map = { + "map_Kd": "base_color_texture", + "map_Ka": None, # ambient... + "map_Ks": "specular_texture", + "map_Ns": "roughness_texture", + "map_d": "transmission_texture", + "map_Tr": None, # transmission roughness? + "map_Bump": "normalmap_texture", + "disp": None, # displacement... + "refl": "metallic_texture", + "map_Ke": None # emission... + } + + for key, mat_wrap_key in sorted(image_map.items()): + if mat_wrap_key is None: + continue + tex_wrap = getattr(mat_wrap, mat_wrap_key, None) + if tex_wrap is None: + continue + image = tex_wrap.image + if image is None: + continue + + filepath = io_utils.path_reference(image.filepath, source_dir, dest_dir, + path_mode, "", copy_set, image.library) options = [] if key == "map_Bump": - if mtex.normal_factor != 1.0: - options.append('-bm %.6f' % mtex.normal_factor) - if mtex.offset != Vector((0.0, 0.0, 0.0)): - options.append('-o %.6f %.6f %.6f' % mtex.offset[:]) - if mtex.scale != Vector((1.0, 1.0, 1.0)): - options.append('-s %.6f %.6f %.6f' % mtex.scale[:]) + if mat_wrap.normalmap_strengh != 1.0: + options.append('-bm %.6f' % mat_wrap.normalmap_strengh) + if tex_wrap.translation != Vector((0.0, 0.0, 0.0)): + options.append('-o %.6f %.6f %.6f' % tex_wrap.translation[:]) + if tex_wrap.scale != Vector((1.0, 1.0, 1.0)): + options.append('-s %.6f %.6f %.6f' % tex_wrap.scale[:]) if options: fw('%s %s %s\n' % (key, " ".join(options), repr(filepath)[1:-1])) else: fw('%s %s\n' % (key, repr(filepath)[1:-1])) + else: + # Write a dummy material here? + fw('Ns 500\n') + fw('Ka 0.8 0.8 0.8\n') + fw('Kd 0.8 0.8 0.8\n') + fw('Ks 0.8 0.8 0.8\n') + fw('d 1\n') # No alpha + fw('illum 2\n') # light normally + def test_nurbs_compat(ob): if ob.type != 'CURVE': @@ -236,7 +198,7 @@ def write_nurb(fw, ob, ob_mat): do_endpoints = (do_closed == 0) and nu.use_endpoint_u for pt in nu.points: - fw('v %.6f %.6f %.6f\n' % (ob_mat * pt.co.to_3d())[:]) + fw('v %.6f %.6f %.6f\n' % (ob_mat @ pt.co.to_3d())[:]) pt_num += 1 tot_verts += pt_num @@ -274,7 +236,7 @@ def write_nurb(fw, ob, ob_mat): return tot_verts -def write_file(filepath, objects, scene, +def write_file(filepath, objects, depsgraph, scene, EXPORT_TRI=False, EXPORT_EDGES=False, EXPORT_SMOOTH_GROUPS=False, @@ -301,7 +263,7 @@ def write_file(filepath, objects, scene, write( 'c:\\test\\foobar.obj', Blender.Object.GetSelected() ) # Using default options. """ if EXPORT_GLOBAL_MATRIX is None: - EXPORT_GLOBAL_MATRIX = mathutils.Matrix() + EXPORT_GLOBAL_MATRIX = Matrix() def veckey3d(v): return round(v.x, 4), round(v.y, 4), round(v.z, 4) @@ -362,13 +324,13 @@ def write_file(filepath, objects, scene, subprogress1.enter_substeps(len(objects)) for i, ob_main in enumerate(objects): # ignore dupli children - if ob_main.parent and ob_main.parent.dupli_type in {'VERTS', 'FACES'}: + if ob_main.parent and ob_main.parent.instance_type in {'VERTS', 'FACES'}: # XXX subprogress1.step("Ignoring %s, dupli child..." % ob_main.name) continue obs = [(ob_main, ob_main.matrix_world)] - if ob_main.dupli_type != 'NONE': + if ob_main.instance_type != 'NONE': # XXX print('creating dupli_list on', ob_main.name) ob_main.dupli_list_create(scene) @@ -385,14 +347,13 @@ def write_file(filepath, objects, scene, # Nurbs curve support if EXPORT_CURVE_AS_NURBS and test_nurbs_compat(ob): - ob_mat = EXPORT_GLOBAL_MATRIX * ob_mat + ob_mat = EXPORT_GLOBAL_MATRIX @ ob_mat totverts += write_nurb(fw, ob, ob_mat) continue # END NURBS try: - me = ob.to_mesh(scene, EXPORT_APPLY_MODIFIERS, calc_tessface=False, - settings='RENDER' if EXPORT_APPLY_MODIFIERS_RENDER else 'PREVIEW') + me = ob.to_mesh(depsgraph, EXPORT_APPLY_MODIFIERS) except RuntimeError: me = None @@ -404,15 +365,14 @@ def write_file(filepath, objects, scene, # _must_ do this first since it re-allocs arrays mesh_triangulate(me) - me.transform(EXPORT_GLOBAL_MATRIX * ob_mat) + me.transform(EXPORT_GLOBAL_MATRIX @ ob_mat) # If negative scaling, we have to invert the normals... if ob_mat.determinant() < 0.0: me.flip_normals() if EXPORT_UV: - faceuv = len(me.uv_textures) > 0 + faceuv = len(me.uv_layers) > 0 if faceuv: - uv_texture = me.uv_textures.active.data[:] uv_layer = me.uv_layers.active.data[:] else: faceuv = False @@ -421,7 +381,6 @@ def write_file(filepath, objects, scene, # Make our own list so it can be sorted to reduce context switching face_index_pairs = [(face, index) for index, face in enumerate(me.polygons)] - # faces = [ f for f in me.tessfaces ] if EXPORT_EDGES: edges = me.edges @@ -459,16 +418,7 @@ def write_file(filepath, objects, scene, if EXPORT_KEEP_VERT_ORDER: pass else: - if faceuv: - if smooth_groups: - sort_func = lambda a: (a[0].material_index, - hash(uv_texture[a[1]].image), - smooth_groups[a[1]] if a[0].use_smooth else False) - else: - sort_func = lambda a: (a[0].material_index, - hash(uv_texture[a[1]].image), - a[0].use_smooth) - elif len(materials) > 1: + if len(materials) > 1: if smooth_groups: sort_func = lambda a: (a[0].material_index, smooth_groups[a[1]] if a[0].use_smooth else False) @@ -562,9 +512,6 @@ def write_file(filepath, objects, scene, else: loops_to_normals = [] - if not faceuv: - f_image = None - subprogress2.step() # XXX @@ -584,15 +531,8 @@ def write_file(filepath, objects, scene, f_smooth = smooth_groups[f_index] f_mat = min(f.material_index, len(materials) - 1) - if faceuv: - tface = uv_texture[f_index] - f_image = tface.image - # MAKE KEY - if faceuv and f_image: # Object is always true. - key = material_names[f_mat], f_image.name - else: - key = material_names[f_mat], None # No image, use None instead. + key = material_names[f_mat], None # No image, use None instead. # Write the vertex group if EXPORT_POLYGROUPS: @@ -638,7 +578,7 @@ def write_file(filepath, objects, scene, i += 1 tmp_ext = "_%3d" % i mtl_name += tmp_ext - mat_data = mtl_dict[key] = mtl_name, materials[f_mat], f_image + mat_data = mtl_dict[key] = mtl_name, materials[f_mat] mtl_rev_dict[mtl_name] = key if EXPORT_GROUP_BY_MAT: @@ -704,7 +644,7 @@ def write_file(filepath, objects, scene, # clean up bpy.data.meshes.remove(me) - if ob_main.dupli_type != 'NONE': + if ob_main.instance_type != 'NONE': ob_main.dupli_list_clear() subprogress1.leave_substeps("Finished writing geometry of '%s'." % ob_main.name) @@ -717,7 +657,7 @@ def write_file(filepath, objects, scene, write_mtl(scene, mtlfilepath, EXPORT_PATH_MODE, copy_set, mtl_dict) # copy all collected files. - bpy_extras.io_utils.path_reference_copy(copy_set) + io_utils.path_reference_copy(copy_set) def _write(context, filepath, @@ -746,6 +686,7 @@ def _write(context, filepath, base_name, ext = os.path.splitext(filepath) context_name = [base_name, '', '', ext] # Base name, scene name, frame number, extension + depsgraph = context.depsgraph scene = context.scene # Exit edit mode before exporting, so current object states are exported properly. @@ -766,7 +707,7 @@ def _write(context, filepath, if EXPORT_ANIMATION: # Add frame to the filepath. context_name[2] = '_%.6d' % frame - scene.frame_set(frame, 0.0) + scene.frame_set(frame, subframe=0.0) if EXPORT_SEL_ONLY: objects = context.selected_objects else: @@ -777,7 +718,7 @@ def _write(context, filepath, # erm... bit of a problem here, this can overwrite files when exporting frames. not too bad. # EXPORT THE FILE. progress.enter_substeps(1) - write_file(full_path, objects, scene, + write_file(full_path, objects, depsgraph, scene, EXPORT_TRI, EXPORT_EDGES, EXPORT_SMOOTH_GROUPS, @@ -799,7 +740,7 @@ def _write(context, filepath, ) progress.leave_substeps() - scene.frame_set(orig_frame, 0.0) + scene.frame_set(orig_frame, subframe=0.0) progress.leave_substeps() diff --git a/io_scene_obj/import_obj.py b/io_scene_obj/import_obj.py index 6e26a4fc..3185e5c2 100644 --- a/io_scene_obj/import_obj.py +++ b/io_scene_obj/import_obj.py @@ -87,21 +87,24 @@ def obj_image_load(context_imagepath_map, line, DIR, recursive, relpath): def create_materials(filepath, relpath, - material_libs, unique_materials, unique_material_images, - use_image_search, use_cycles, float_func): + material_libs, unique_materials, + use_image_search, float_func): """ Create all the used materials in this obj, assign colors and images to the materials from all referenced material libs """ + from math import sqrt + from bpy_extras import node_shader_utils + DIR = os.path.dirname(filepath) context_material_vars = set() # Don't load the same image multiple times context_imagepath_map = {} - cycles_material_wrap_map = {} + nodal_material_wrap_map = {} - def load_material_image(blender_material, mat_wrap, use_cycles, context_material_name, img_data, line, type): + def load_material_image(blender_material, mat_wrap, context_material_name, img_data, line, type): """ Set textures defined in .mtl file. """ @@ -120,105 +123,47 @@ def create_materials(filepath, relpath, # Absolute path - c:\.. etc would work here image = obj_image_load(context_imagepath_map, line, DIR, use_image_search, relpath) - texture = bpy.data.textures.new(name=type, type='IMAGE') - if image is not None: - texture.image = image - map_offset = map_options.get(b'-o') map_scale = map_options.get(b'-s') + def _generic_tex_set(nodetex, image, texcoords, translation, scale): + nodetex.image = image + nodetex.texcoords = texcoords + if translation is not None: + nodetex.translation = translation + if scale is not None: + nodetex.scale = scale + # Adds textures for materials (rendering) if type == 'Kd': - if use_cycles: - mat_wrap.diffuse_image_set(image) - mat_wrap.diffuse_mapping_set(coords='UV', translation=map_offset, scale=map_scale) - - mtex = blender_material.texture_slots.add() - mtex.texture = texture - mtex.texture_coords = 'UV' - mtex.use_map_color_diffuse = True - - # adds textures to faces (Textured/Alt-Z mode) - # Only apply the diffuse texture to the face if the image has not been set with the inline usemat func. - unique_material_images[context_material_name] = image # set the texface image + _generic_tex_set(mat_wrap.base_color_texture, image, 'UV', map_offset, map_scale) elif type == 'Ka': - if use_cycles: - # XXX Not supported? - print("WARNING, currently unsupported ambient texture, skipped.") - - mtex = blender_material.texture_slots.add() - mtex.use_map_color_diffuse = False - mtex.texture = texture - mtex.texture_coords = 'UV' - mtex.use_map_ambient = True + # XXX Not supported? + print("WARNING, currently unsupported ambient texture, skipped.") elif type == 'Ks': - if use_cycles: - mat_wrap.specular_image_set(image) - mat_wrap.specular_mapping_set(coords='UV', translation=map_offset, scale=map_scale) - - mtex = blender_material.texture_slots.add() - mtex.use_map_color_diffuse = False - mtex.texture = texture - mtex.texture_coords = 'UV' - mtex.use_map_color_spec = True + _generic_tex_set(mat_wrap.specular_texture, image, 'UV', map_offset, map_scale) elif type == 'Ke': - if use_cycles: - # XXX Not supported? - print("WARNING, currently unsupported emit texture, skipped.") - - mtex = blender_material.texture_slots.add() - mtex.use_map_color_diffuse = False - mtex.texture = texture - mtex.texture_coords = 'UV' - mtex.use_map_emit = True + # XXX Not supported? + print("WARNING, currently unsupported emit texture, skipped.") elif type == 'Bump': bump_mult = map_options.get(b'-bm') bump_mult = float(bump_mult[0]) if (bump_mult and len(bump_mult[0]) > 1) else 1.0 + mat_wrap.normalmap_strength_set(bump_mult) - if use_cycles: - mat_wrap.normal_image_set(image) - mat_wrap.normal_mapping_set(coords='UV', translation=map_offset, scale=map_scale) - if bump_mult: - mat_wrap.normal_factor_set(bump_mult) - - mtex = blender_material.texture_slots.add() - mtex.use_map_color_diffuse = False - mtex.texture = texture - mtex.texture_coords = 'UV' - mtex.use_map_normal = True - if bump_mult: - mtex.normal_factor = bump_mult + _generic_tex_set(mat_wrap.normalmap_texture, image, 'UV', map_offset, map_scale) elif type == 'D': - if use_cycles: - mat_wrap.alpha_image_set(image) - mat_wrap.alpha_mapping_set(coords='UV', translation=map_offset, scale=map_scale) - - mtex = blender_material.texture_slots.add() - mtex.use_map_color_diffuse = False - mtex.texture = texture - mtex.texture_coords = 'UV' - mtex.use_map_alpha = True - blender_material.use_transparency = True - blender_material.transparency_method = 'Z_TRANSPARENCY' - if "alpha" not in context_material_vars: - blender_material.alpha = 0.0 - # Todo, unset diffuse material alpha if it has an alpha channel + _generic_tex_set(mat_wrap.transmission_texture, image, 'UV', map_offset, map_scale) elif type == 'disp': - if use_cycles: - mat_wrap.bump_image_set(image) - mat_wrap.bump_mapping_set(coords='UV', translation=map_offset, scale=map_scale) - - mtex = blender_material.texture_slots.add() - mtex.use_map_color_diffuse = False - mtex.texture = texture - mtex.texture_coords = 'UV' - mtex.use_map_displacement = True + # XXX Not supported? + print("WARNING, currently unsupported displacement texture, skipped.") + # ~ mat_wrap.bump_image_set(image) + # ~ mat_wrap.bump_mapping_set(coords='UV', translation=map_offset, scale=map_scale) elif type == 'refl': map_type = map_options.get(b'-type') @@ -226,54 +171,25 @@ def create_materials(filepath, relpath, print("WARNING, unsupported reflection type '%s', defaulting to 'sphere'" "" % ' '.join(i.decode() for i in map_type)) - if use_cycles: - mat_wrap.diffuse_image_set(image, projection='SPHERE') - mat_wrap.diffuse_mapping_set(coords='Reflection', translation=map_offset, scale=map_scale) + _generic_tex_set(mat_wrap.base_color_texture, image, 'Reflection', map_offset, map_scale) + mat_wrap.base_color_texture.projection = 'SPHERE' - mtex = blender_material.texture_slots.add() - mtex.use_map_color_diffuse = False - mtex.texture = texture - mtex.texture_coords = 'REFLECTION' - mtex.use_map_color_diffuse = True - mtex.mapping = 'SPHERE' else: raise Exception("invalid type %r" % type) - if map_offset: - mtex.offset.x = float(map_offset[0]) - if len(map_offset) >= 2: - mtex.offset.y = float(map_offset[1]) - if len(map_offset) >= 3: - mtex.offset.z = float(map_offset[2]) - if map_scale: - mtex.scale.x = float(map_scale[0]) - if len(map_scale) >= 2: - mtex.scale.y = float(map_scale[1]) - if len(map_scale) >= 3: - mtex.scale.z = float(map_scale[2]) - - # Add an MTL with the same name as the obj if no MTLs are spesified. + # Try to find a MTL with the same name as the OBJ if no MTLs are specified. temp_mtl = os.path.splitext((os.path.basename(filepath)))[0] + ".mtl" - if os.path.exists(os.path.join(DIR, temp_mtl)): material_libs.add(temp_mtl) del temp_mtl # Create new materials for name in unique_materials: # .keys() - if name is not None: - ma = unique_materials[name] = bpy.data.materials.new(name.decode('utf-8', "replace")) - unique_material_images[name] = None # assign None to all material images to start with, add to later. - if use_cycles: - from modules import cycles_shader_compat - ma_wrap = cycles_shader_compat.CyclesShaderWrapper(ma) - cycles_material_wrap_map[ma] = ma_wrap - - - # XXX Why was this needed? Cannot find any good reason, and adds stupid empty matslot in case we do not separate - # mesh (see T44947). - #~ unique_materials[None] = None - #~ unique_material_images[None] = None + ma_name = "Default OBJ" if name is None else name.decode('utf-8', "replace") + ma = unique_materials[name] = bpy.data.materials.new(ma_name) + ma_wrap = node_shader_utils.PrincipledBSDFWrapper(ma, is_readonly=False) + nodal_material_wrap_map[ma] = ma_wrap + ma_wrap.use_nodes = True for libname in sorted(material_libs): # print(libname) @@ -281,13 +197,13 @@ def create_materials(filepath, relpath, if not os.path.exists(mtlpath): print("\tMaterial not found MTL: %r" % mtlpath) else: - do_ambient = True + # Note: with modern Principled BSDF shader, things like ambient, raytrace or fresnel are always 'ON' + # (i.e. automatically controlled by other parameters). do_highlight = False do_reflection = False do_transparency = False do_glass = False - do_fresnel = False - do_raytrace = False + spec_colors = [0.0, 0.0, 0.0] emit_colors = [0.0, 0.0, 0.0] # print('\t\tloading mtl: %e' % mtlpath) @@ -305,133 +221,113 @@ def create_materials(filepath, relpath, if line_id == b'newmtl': # Finalize previous mat, if any. if context_material: + if "specular" in context_material_vars: + # XXX This is highly approximated, not sure whether we can do better... + # TODO: Find a way to guesstimate best value from diffuse color... + # IDEA: Use standard deviation of both spec and diff colors (i.e. how far away they are + # from some grey), and apply the the proportion between those two as tint factor? + spec = sum(spec_colors) / 3.0 + # ~ spec_var = math.sqrt(sum((c - spec) ** 2 for c in spec_color) / 3.0) + # ~ diff = sum(context_mat_wrap.base_color) / 3.0 + # ~ diff_var = math.sqrt(sum((c - diff) ** 2 for c in context_mat_wrap.base_color) / 3.0) + # ~ tint = min(1.0, spec_var / diff_var) + context_mat_wrap.specular = spec + context_mat_wrap.specular_tint = 0.0 + if "roughness" not in context_material_vars: + context_mat_wrap.roughness = 0.0 + + emit_value = sum(emit_colors) / 3.0 if emit_value > 1e-6: - if use_cycles: - print("WARNING, currently unsupported emit value, skipped.") + print("WARNING, emit value unsupported by Principled BSDF shader, skipped.") # We have to adapt it to diffuse color too... emit_value /= sum(context_material.diffuse_color) / 3.0 - context_material.emit = emit_value - - if not do_ambient: - context_material.ambient = 0.0 + # ~ context_material.emit = emit_value + # FIXME, how else to use this? if do_highlight: - if use_cycles: - context_mat_wrap.hardness_value_set(1.0) - # FIXME, how else to use this? - context_material.specular_intensity = 1.0 + if "specular" not in context_material_vars: + context_mat_wrap.specular = 1.0 + if "roughness" not in context_material_vars: + context_mat_wrap.roughness = 0.0 else: - if use_cycles: - context_mat_wrap.hardness_value_set(0.0) + if "specular" not in context_material_vars: + context_mat_wrap.specular = 0.0 + if "roughness" not in context_material_vars: + context_mat_wrap.roughness = 1.0 if do_reflection: - if use_cycles: - context_mat_wrap.reflect_factor_set(1.0) - context_material.raytrace_mirror.use = True - context_material.raytrace_mirror.reflect_factor = 1.0 + if "metallic" not in context_material_vars: + context_mat_wrap.metallic = 1.0 if do_transparency: - context_material.use_transparency = True - context_material.transparency_method = 'RAYTRACE' if do_raytrace else 'Z_TRANSPARENCY' - if "alpha" not in context_material_vars: - if use_cycles: - context_mat_wrap.alpha_value_set(0.0) - context_material.alpha = 0.0 + if "ior" not in context_material_vars: + context_mat_wrap.ior = 1.0 + if "transmission" not in context_material_vars: + context_mat_wrap.transmission = 1.0 + # EEVEE only + context_material.blend_method = 'BLEND' if do_glass: - if use_cycles: - print("WARNING, currently unsupported glass material, skipped.") if "ior" not in context_material_vars: - context_material.raytrace_transparency.ior = 1.5 - - if do_fresnel: - if use_cycles: - print("WARNING, currently unsupported fresnel option, skipped.") - context_material.raytrace_mirror.fresnel = 1.0 # could be any value for 'ON' - - """ - if do_raytrace: - context_material.use_raytrace = True - else: - context_material.use_raytrace = False - """ - # XXX, this is not following the OBJ spec, but this was - # written when raytracing wasnt default, annoying to disable for blender users. - context_material.use_raytrace = True + context_mat_wrap.ior = 1.5 context_material_name = line_value(line_split) context_material = unique_materials.get(context_material_name) - if use_cycles and context_material is not None: - context_mat_wrap = cycles_material_wrap_map[context_material] + if context_material is not None: + context_mat_wrap = nodal_material_wrap_map[context_material] context_material_vars.clear() + spec_colors = [0.0, 0.0, 0.0] emit_colors[:] = [0.0, 0.0, 0.0] - do_ambient = True do_highlight = False do_reflection = False do_transparency = False do_glass = False - do_fresnel = False - do_raytrace = False elif context_material: # we need to make a material to assign properties to it. if line_id == b'ka': - col = (float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])) - if use_cycles: - context_mat_wrap.reflect_color_set(col) - context_material.mirror_color = col - # This is highly approximated, but let's try to stick as close from exporter as possible... :/ - context_material.ambient = sum(context_material.mirror_color) / 3 + refl = (float_func(line_split[1]) + float_func(line_split[2]) + float_func(line_split[3])) / 3.0 + context_mat_wrap.metallic = refl + context_material_vars.add("metallic") elif line_id == b'kd': col = (float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])) - if use_cycles: - context_mat_wrap.diffuse_color_set(col) - context_material.diffuse_color = col - context_material.diffuse_intensity = 1.0 + context_mat_wrap.base_color = col elif line_id == b'ks': - col = (float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])) - if use_cycles: - context_mat_wrap.specular_color_set(col) - context_mat_wrap.hardness_value_set(1.0) - context_material.specular_color = col - context_material.specular_intensity = 1.0 + spec_colors[:] = [ + float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])] + context_material_vars.add("specular") elif line_id == b'ke': # We cannot set context_material.emit right now, we need final diffuse color as well for this. + # XXX Unsupported currently emit_colors[:] = [ float_func(line_split[1]), float_func(line_split[2]), float_func(line_split[3])] elif line_id == b'ns': - if use_cycles: - context_mat_wrap.hardness_value_set(((float_func(line_split[1]) + 3.0) / 50.0) - 0.65) - context_material.specular_hardness = int((float_func(line_split[1]) * 0.51) + 1) - elif line_id == b'ni': # Refraction index (between 1 and 3). - if use_cycles: - print("WARNING, currently unsupported glass material, skipped.") - context_material.raytrace_transparency.ior = max(1, min(float_func(line_split[1]), 3)) + # XXX Totally empirical conversion, trying to adapt it + # (from 0.0 - 900.0 OBJ specular exponent range to 1.0 - 0.0 Principled BSDF range)... + context_mat_wrap.roughness = 1.0 - (sqrt(float_func(line_split[1])) / 30) + context_material_vars.add("roughness") + elif line_id == b'ni': # Refraction index (between 0.001 and 10). + context_mat_wrap.ior = float_func(line_split[1]) context_material_vars.add("ior") elif line_id == b'd': # dissolve (transparency) - if use_cycles: - context_mat_wrap.alpha_value_set(float_func(line_split[1])) - context_material.alpha = float_func(line_split[1]) - context_material.use_transparency = True - context_material.transparency_method = 'Z_TRANSPARENCY' - context_material_vars.add("alpha") + context_mat_wrap.transmission = 1.0 - float_func(line_split[1]) + context_material_vars.add("transmission") elif line_id == b'tr': # translucency - if use_cycles: - print("WARNING, currently unsupported translucency option, skipped.") - context_material.translucency = float_func(line_split[1]) + print("WARNING, currently unsupported 'tr' translucency option, skipped.") elif line_id == b'tf': # rgb, filter color, blender has no support for this. - pass + print("WARNING, currently unsupported 'tf' filter color option, skipped.") elif line_id == b'illum': illum = get_int(line_split[1]) # inline comments are from the spec, v4.2 if illum == 0: # Color on and Ambient off - do_ambient = False + print("WARNING, Principled BSDF shader does not support illumination 0 mode " + "(colors with no ambient), skipped.") elif illum == 1: # Color on and Ambient on pass @@ -441,32 +337,25 @@ def create_materials(filepath, relpath, elif illum == 3: # Reflection on and Ray trace on do_reflection = True - do_raytrace = True elif illum == 4: # Transparency: Glass on # Reflection: Ray trace on do_transparency = True do_reflection = True do_glass = True - do_raytrace = True elif illum == 5: # Reflection: Fresnel on and Ray trace on do_reflection = True - do_fresnel = True - do_raytrace = True elif illum == 6: # Transparency: Refraction on # Reflection: Fresnel off and Ray trace on do_transparency = True do_reflection = True - do_raytrace = True elif illum == 7: # Transparency: Refraction on # Reflection: Fresnel on and Ray trace on do_transparency = True do_reflection = True - do_fresnel = True - do_raytrace = True elif illum == 8: # Reflection on and Ray trace off do_reflection = True @@ -474,58 +363,58 @@ def create_materials(filepath, relpath, # Transparency: Glass on # Reflection: Ray trace off do_transparency = True - do_reflection = True + do_reflection = False do_glass = True elif illum == 10: # Casts shadows onto invisible surfaces - - # blender can't do this + print("WARNING, Principled BSDF shader does not support illumination 10 mode " + "(cast shadows on invisible surfaces), skipped.") pass elif line_id == b'map_ka': img_data = line.split()[1:] if img_data: - load_material_image(context_material, context_mat_wrap, use_cycles, + load_material_image(context_material, context_mat_wrap, context_material_name, img_data, line, 'Ka') elif line_id == b'map_ks': img_data = line.split()[1:] if img_data: - load_material_image(context_material, context_mat_wrap, use_cycles, + load_material_image(context_material, context_mat_wrap, context_material_name, img_data, line, 'Ks') elif line_id == b'map_kd': img_data = line.split()[1:] if img_data: - load_material_image(context_material, context_mat_wrap, use_cycles, + load_material_image(context_material, context_mat_wrap, context_material_name, img_data, line, 'Kd') elif line_id == b'map_ke': img_data = line.split()[1:] if img_data: - load_material_image(context_material, context_mat_wrap, use_cycles, + load_material_image(context_material, context_mat_wrap, context_material_name, img_data, line, 'Ke') elif line_id in {b'map_bump', b'bump'}: # 'bump' is incorrect but some files use it. img_data = line.split()[1:] if img_data: - load_material_image(context_material, context_mat_wrap, use_cycles, + load_material_image(context_material, context_mat_wrap, context_material_name, img_data, line, 'Bump') elif line_id in {b'map_d', b'map_tr'}: # Alpha map - Dissolve img_data = line.split()[1:] if img_data: - load_material_image(context_material, context_mat_wrap, use_cycles, + load_material_image(context_material, context_mat_wrap, context_material_name, img_data, line, 'D') elif line_id in {b'map_disp', b'disp'}: # displacementmap img_data = line.split()[1:] if img_data: - load_material_image(context_material, context_mat_wrap, use_cycles, + load_material_image(context_material, context_mat_wrap, context_material_name, img_data, line, 'disp') elif line_id in {b'map_refl', b'refl'}: # reflectionmap img_data = line.split()[1:] if img_data: - load_material_image(context_material, context_mat_wrap, use_cycles, + load_material_image(context_material, context_mat_wrap, context_material_name, img_data, line, 'refl') else: - print("\t%r:%r (ignored)" % (filepath, line)) + print("WARNING: %r:%r (ignored)" % (filepath, line)) mtl.close() @@ -538,8 +427,8 @@ def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP): filename = os.path.splitext((os.path.basename(filepath)))[0] if not SPLIT_OB_OR_GROUP or not faces: - use_verts_nor = any((False if f[1] is ... else True) for f in faces) - use_verts_tex = any((False if f[2] is ... else True) for f in faces) + use_verts_nor = any(f[1] for f in faces) + use_verts_tex = any(f[2] for f in faces) # use the filename for the object name since we aren't chopping up the mesh. return [(verts_loc, faces, unique_materials, filename, use_verts_nor, use_verts_tex)] @@ -556,7 +445,15 @@ def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP): oldkey = -1 # initialize to a value that will never match the key for face in faces: - key = face[5] + (face_vert_loc_indices, + face_vert_nor_indices, + face_vert_tex_indices, + context_material, + context_smooth_group, + context_object, + face_invalid_blenpoly, + ) = face + key = context_object if oldkey != key: # Check the key has changed. @@ -564,27 +461,25 @@ def split_mesh(verts_loc, faces, unique_materials, filepath, SPLIT_OB_OR_GROUP): use_verts_nor, use_verts_tex) = face_split_dict.setdefault(key, ([], [], {}, {}, [], [])) oldkey = key - face_vert_loc_indices = face[0] - if not use_verts_nor and face[1] is not ...: + if not use_verts_nor and face_vert_nor_indices: use_verts_nor.append(True) - if not use_verts_tex and face[2] is not ...: + if not use_verts_tex and face_vert_tex_indices: use_verts_tex.append(True) # Remap verts to new vert list and add where needed - for enum, i in enumerate(face_vert_loc_indices): - map_index = vert_remap.get(i) + for loop_idx, vert_idx in enumerate(face_vert_loc_indices): + map_index = vert_remap.get(vert_idx) if map_index is None: map_index = len(verts_split) - vert_remap[i] = map_index # set the new remapped index so we only add once and can reference next time. - verts_split.append(verts_loc[i]) # add the vert to the local verts + vert_remap[vert_idx] = map_index # set the new remapped index so we only add once and can reference next time. + verts_split.append(verts_loc[vert_idx]) # add the vert to the local verts - face_vert_loc_indices[enum] = map_index # remap to the local index + face_vert_loc_indices[loop_idx] = map_index # remap to the local index - matname = face[3] - if matname and matname not in unique_materials_split: - unique_materials_split[matname] = unique_materials[matname] + if context_material not in unique_materials_split: + unique_materials_split[context_material] = unique_materials[context_material] faces_split.append(face) @@ -601,7 +496,6 @@ def create_mesh(new_objects, verts_tex, faces, unique_materials, - unique_material_images, unique_smooth_groups, vertex_groups, dataname, @@ -664,7 +558,7 @@ def create_mesh(new_objects, # ignore triangles with invalid indices if len(face_vert_loc_indices) > 3: from bpy_extras.mesh_utils import ngon_tessellate - ngon_face_indices = ngon_tessellate(verts_loc, face_vert_loc_indices) + ngon_face_indices = ngon_tessellate(verts_loc, face_vert_loc_indices, debug_print=bpy.app.debug) faces.extend([([face_vert_loc_indices[ngon[0]], face_vert_loc_indices[ngon[1]], face_vert_loc_indices[ngon[2]], @@ -734,68 +628,37 @@ def create_mesh(new_objects, # verts_loc is a list of (x, y, z) tuples me.vertices.foreach_set("co", unpack_list(verts_loc)) - loops_vert_idx = [] + loops_vert_idx = tuple(vidx for (face_vert_loc_indices, _, _, _, _, _, _) in faces for vidx in face_vert_loc_indices) faces_loop_start = [] - faces_loop_total = [] lidx = 0 for f in faces: - vidx = f[0] - nbr_vidx = len(vidx) - loops_vert_idx.extend(vidx) + face_vert_loc_indices = f[0] + nbr_vidx = len(face_vert_loc_indices) faces_loop_start.append(lidx) - faces_loop_total.append(nbr_vidx) lidx += nbr_vidx + faces_loop_total = tuple(len(face_vert_loc_indices) for (face_vert_loc_indices, _, _, _, _, _, _) in faces) me.loops.foreach_set("vertex_index", loops_vert_idx) me.polygons.foreach_set("loop_start", faces_loop_start) me.polygons.foreach_set("loop_total", faces_loop_total) + faces_ma_index = tuple(material_mapping[context_material] for (_, _, _, context_material, _, _, _) in faces) + me.polygons.foreach_set("material_index", faces_ma_index) + + faces_use_smooth = tuple(bool(context_smooth_group) for (_, _, _, _, context_smooth_group, _, _) in faces) + me.polygons.foreach_set("use_smooth", faces_use_smooth) + if verts_nor and me.loops: # Note: we store 'temp' normals in loops, since validate() may alter final mesh, # we can only set custom lnors *after* calling it. me.create_normals_split() + loops_nor = tuple(no for (_, face_vert_nor_indices, _, _, _, _, _) in faces for face_noidx in face_vert_nor_indices for no in verts_nor[face_noidx]) + me.loops.foreach_set("normal", loops_nor) if verts_tex and me.polygons: - me.uv_textures.new() - - context_material_old = -1 # avoid a dict lookup - mat = 0 # rare case it may be un-initialized. - - for i, (face, blen_poly) in enumerate(zip(faces, me.polygons)): - if len(face[0]) < 3: - raise Exception("bad face") # Shall not happen, we got rid of those earlier! - - (face_vert_loc_indices, - face_vert_nor_indices, - face_vert_tex_indices, - context_material, - context_smooth_group, - context_object, - face_invalid_blenpoly, - ) = face - - if context_smooth_group: - blen_poly.use_smooth = True - - if context_material: - if context_material_old is not context_material: - mat = material_mapping[context_material] - context_material_old = context_material - blen_poly.material_index = mat - - if verts_nor and face_vert_nor_indices: - for face_noidx, lidx in zip(face_vert_nor_indices, blen_poly.loop_indices): - me.loops[lidx].normal[:] = verts_nor[0 if (face_noidx is ...) else face_noidx] - - if verts_tex and face_vert_tex_indices: - if context_material: - image = unique_material_images[context_material] - if image: # Can be none if the material dosnt have an image. - me.uv_textures[0].data[i].image = image - - blen_uvs = me.uv_layers[0] - for face_uvidx, lidx in zip(face_vert_tex_indices, blen_poly.loop_indices): - blen_uvs.data[lidx].uv = verts_tex[0 if (face_uvidx is ...) else face_uvidx] + me.uv_layers.new() + loops_uv = tuple(uv for (_, _, face_vert_tex_indices, _, _, _, _) in faces for face_uvidx in face_vert_tex_indices for uv in verts_tex[face_uvidx]) + me.uv_layers[0].data.foreach_set("uv", loops_uv) use_edges = use_edges and bool(edges) if use_edges: @@ -829,7 +692,6 @@ def create_mesh(new_objects, for e in me.edges: if e.key in sharp_edges: e.use_edge_sharp = True - me.show_edge_sharp = True if verts_nor: clnors = array.array('f', [0.0] * (len(me.loops) * 3)) @@ -840,7 +702,6 @@ def create_mesh(new_objects, me.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3))) me.use_auto_smooth = True - me.show_edge_sharp = True ob = bpy.data.objects.new(me.name, me) new_objects.append(ob) @@ -849,7 +710,7 @@ def create_mesh(new_objects, # content of the vertex_groups. If the user selects to NOT have vertex groups saved then # the following test will never run for group_name, group_indices in vertex_groups.items(): - group = ob.vertex_groups.new(group_name.decode('utf-8', "replace")) + group = ob.vertex_groups.new(name=group_name.decode('utf-8', "replace")) group.add(group_indices, 1.0, 'REPLACE') @@ -966,14 +827,13 @@ def get_int(svalue): def load(context, filepath, *, - global_clamp_size=0.0, + global_clight_size=0.0, use_smooth_groups=True, use_edges=True, use_split_objects=True, use_split_groups=True, use_image_search=True, use_groups_as_vgroups=False, - use_cycles=True, relpath=None, global_matrix=None ): @@ -1041,8 +901,8 @@ def load(context, context_parm = b'' # used by nurbs too but could be used elsewhere # Until we can use sets + use_default_material = False unique_materials = {} - unique_material_images = {} unique_smooth_groups = {} # unique_obects= {} - no use for this variable since the objects are stored in the face. @@ -1057,15 +917,19 @@ def load(context, face_vert_nor_indices = None face_vert_tex_indices = None face_vert_nor_valid = face_vert_tex_valid = False + verts_loc_len = verts_nor_len = verts_tex_len = 0 face_items_usage = set() face_invalid_blenpoly = None prev_vidx = None face = None vec = [] + quick_vert_failures = 0 + skip_quick_vert = False + progress.enter_substeps(3, "Parsing OBJ file...") with open(filepath, 'rb') as f: - for line in f: # .readlines(): + for line in f: line_split = line.split() if not line_split: @@ -1073,17 +937,39 @@ def load(context, line_start = line_split[0] # we compare with this a _lot_ - if line_start == b'v' or context_multi_line == b'v': - context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'v', verts_loc, vec, 3) - - elif line_start == b'vn' or context_multi_line == b'vn': - context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'vn', verts_nor, vec, 3) + # Handling vertex data are pretty similar, factorize that. + # Also, most OBJ files store all those on a single line, so try fast parsing for that first, + # and only fallback to full multi-line parsing when needed, this gives significant speed-up + # (~40% on affected code). + if line_start == b'v': + vdata, vdata_len, do_quick_vert = (verts_loc, 3, not skip_quick_vert) + elif line_start == b'vn': + vdata, vdata_len, do_quick_vert = (verts_nor, 3, not skip_quick_vert) + elif line_start == b'vt': + vdata, vdata_len, do_quick_vert = verts_tex, 2, not skip_quick_vert + elif context_multi_line == b'v': + vdata, vdata_len, do_quick_vert = verts_loc, 3, False + elif context_multi_line == b'vn': + vdata, vdata_len, do_quick_vert = verts_nor, 3, False + elif context_multi_line == b'vt': + vdata, vdata_len, do_quick_vert = verts_tex, 2, False + else: + vdata_len = 0 + + if vdata_len: + if do_quick_vert: + try: + vdata.append(tuple(map(float_func, line_split[1:vdata_len + 1]))) + except: + do_quick_vert = False + # In case we get too many failures on quick parsing, force fallback to full multi-line one. + # Exception handling can become costly... + quick_vert_failures += 1 + if quick_vert_failures > 10000: + skip_quick_vert = True + if not do_quick_vert: + context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'v', vdata, vec, vdata_len) - elif line_start == b'vt' or context_multi_line == b'vt': - context_multi_line = handle_vec(line_start, context_multi_line, line_split, b'vt', verts_tex, vec, 2) - - # Handle faces lines (as faces) and the second+ lines of fa multiline face here - # use 'f' not 'f ' because some objs (very rare have 'fo ' for faces) elif line_start == b'f' or context_multi_line == b'f': if not context_multi_line: line_split = line_split[1:] @@ -1093,14 +979,19 @@ def load(context, _1, _2, _3, face_invalid_blenpoly) = face faces.append(face) face_items_usage.clear() + verts_loc_len = len(verts_loc) + verts_nor_len = len(verts_nor) + verts_tex_len = len(verts_tex) + if context_material is None: + use_default_material = True # Else, use face_vert_loc_indices and face_vert_tex_indices previously defined and used the obj_face context_multi_line = b'f' if strip_slash(line_split) else b'' for v in line_split: obj_vert = v.split(b'/') - idx = int(obj_vert[0]) - 1 - vert_loc_index = (idx + len(verts_loc) + 1) if (idx < 0) else idx + idx = int(obj_vert[0]) # Note that we assume here we cannot get OBJ invalid 0 index... + vert_loc_index = (idx + verts_loc_len) if (idx < 1) else idx - 1 # Add the vertex to the current group # *warning*, this wont work for files that have groups defined around verts if use_groups_as_vgroups and context_vgroup: @@ -1118,18 +1009,18 @@ def load(context, # formatting for faces with normals and textures is # loc_index/tex_index/nor_index if len(obj_vert) > 1 and obj_vert[1] and obj_vert[1] != b'0': - idx = int(obj_vert[1]) - 1 - face_vert_tex_indices.append((idx + len(verts_tex) + 1) if (idx < 0) else idx) + idx = int(obj_vert[1]) + face_vert_tex_indices.append((idx + verts_tex_len) if (idx < 1) else idx - 1) face_vert_tex_valid = True else: - face_vert_tex_indices.append(...) + face_vert_tex_indices.append(0) if len(obj_vert) > 2 and obj_vert[2] and obj_vert[2] != b'0': - idx = int(obj_vert[2]) - 1 - face_vert_nor_indices.append((idx + len(verts_nor) + 1) if (idx < 0) else idx) + idx = int(obj_vert[2]) + face_vert_nor_indices.append((idx + verts_nor_len) if (idx < 1) else idx - 1) face_vert_nor_valid = True else: - face_vert_nor_indices.append(...) + face_vert_nor_indices.append(0) if not context_multi_line: # Clear nor/tex indices in case we had none defined for this face. @@ -1263,8 +1154,10 @@ def load(context, progress.step("Done, loading materials and images...") + if use_default_material: + unique_materials[None] = None create_materials(filepath, relpath, material_libs, unique_materials, - unique_material_images, use_image_search, use_cycles, float_func) + use_image_search, float_func) progress.step("Done, building geometries (verts:%i faces:%i materials: %i smoothgroups:%i) ..." % (len(verts_loc), len(faces), len(unique_materials), len(unique_smooth_groups))) @@ -1290,7 +1183,6 @@ def load(context, verts_tex if use_vtex else [], faces_split, unique_materials_split, - unique_material_images, unique_smooth_groups, vertex_groups, dataname, @@ -1300,10 +1192,13 @@ def load(context, for context_nurbs in nurbs: create_nurbs(context_nurbs, verts_loc, new_objects) + view_layer = context.view_layer + collection = view_layer.active_layer_collection.collection + # Create new obj for obj in new_objects: - base = scene.objects.link(obj) - base.select = True + collection.objects.link(obj) + obj.select_set(True) # we could apply this anywhere before scaling. obj.matrix_world = global_matrix @@ -1313,7 +1208,7 @@ def load(context, axis_min = [1000000000] * 3 axis_max = [-1000000000] * 3 - if global_clamp_size: + if global_clight_size: # Get all object bounds for ob in new_objects: for v in ob.bound_box: @@ -1327,7 +1222,7 @@ def load(context, max_axis = max(axis_max[0] - axis_min[0], axis_max[1] - axis_min[1], axis_max[2] - axis_min[2]) scale = 1.0 - while global_clamp_size < max_axis * scale: + while global_clight_size < max_axis * scale: scale = scale / 10.0 for obj in new_objects: diff --git a/io_scene_vrml2/__init__.py b/io_scene_vrml2/__init__.py index d6c0da2f..4a420f70 100644 --- a/io_scene_vrml2/__init__.py +++ b/io_scene_vrml2/__init__.py @@ -47,16 +47,14 @@ from bpy.props import ( ) from bpy_extras.io_utils import ( ExportHelper, - orientation_helper_factory, + orientation_helper, path_reference_mode, axis_conversion, ) -ExportVRMLOrientationHelper = orientation_helper_factory("ExportVRMLOrientationHelper", axis_forward='Z', axis_up='Y') - - -class ExportVRML(bpy.types.Operator, ExportHelper, ExportVRMLOrientationHelper): +@orientation_helper(axis_forward='Z', axis_up='Y') +class ExportVRML(bpy.types.Operator, ExportHelper): """Export mesh objects as a VRML2, colors and texture coordinates""" bl_idname = "export_scene.vrml2" bl_label = "Export VRML2" @@ -151,13 +149,13 @@ def menu_func_export(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) if __name__ == "__main__": register() diff --git a/io_scene_vrml2/export_vrml2.py b/io_scene_vrml2/export_vrml2.py index 06f79fc1..22114582 100644 --- a/io_scene_vrml2/export_vrml2.py +++ b/io_scene_vrml2/export_vrml2.py @@ -163,7 +163,7 @@ def save_object(fw, global_matrix, if is_editmode: bpy.ops.object.editmode_toggle() - me = obj.to_mesh(scene, True, 'PREVIEW', calc_tessface=False) + me = obj.to_mesh(scene, True, 'PREVIEW') bm = bmesh.new() bm.from_mesh(me) diff --git a/io_scene_x/__init__.py b/io_scene_x/__init__.py index 001dca61..404d9dc2 100644 --- a/io_scene_x/__init__.py +++ b/io_scene_x/__init__.py @@ -176,13 +176,13 @@ def menu_func(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_export.append(menu_func) + bpy.types.TOPBAR_MT_file_export.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_export.remove(menu_func) + bpy.types.TOPBAR_MT_file_export.remove(menu_func) if __name__ == "__main__": diff --git a/io_scene_x3d/__init__.py b/io_scene_x3d/__init__.py index d5c555a1..06a9a122 100644 --- a/io_scene_x3d/__init__.py +++ b/io_scene_x3d/__init__.py @@ -48,16 +48,14 @@ from bpy.props import ( from bpy_extras.io_utils import ( ImportHelper, ExportHelper, - orientation_helper_factory, + orientation_helper, axis_conversion, path_reference_mode, ) -IOX3DOrientationHelper = orientation_helper_factory("IOX3DOrientationHelper", axis_forward='Z', axis_up='Y') - - -class ImportX3D(bpy.types.Operator, ImportHelper, IOX3DOrientationHelper): +@orientation_helper(axis_forward='Z', axis_up='Y') +class ImportX3D(bpy.types.Operator, ImportHelper): """Import an X3D or VRML2 file""" bl_idname = "import_scene.x3d" bl_label = "Import X3D/VRML2" @@ -81,7 +79,8 @@ class ImportX3D(bpy.types.Operator, ImportHelper, IOX3DOrientationHelper): return import_x3d.load(context, **keywords) -class ExportX3D(bpy.types.Operator, ExportHelper, IOX3DOrientationHelper): +@orientation_helper(axis_forward='Z', axis_up='Y') +class ExportX3D(bpy.types.Operator, ExportHelper): """Export selection to Extensible 3D file (.x3d)""" bl_idname = "export_scene.x3d" bl_label = 'Export X3D' @@ -172,15 +171,15 @@ def menu_func_export(self, context): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_file_import.append(menu_func_import) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): bpy.utils.unregister_module(__name__) - bpy.types.INFO_MT_file_import.remove(menu_func_import) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) # NOTES # - blender version is hardcoded diff --git a/io_scene_x3d/export_x3d.py b/io_scene_x3d/export_x3d.py index c2ebfca7..d6c4a293 100644 --- a/io_scene_x3d/export_x3d.py +++ b/io_scene_x3d/export_x3d.py @@ -48,7 +48,7 @@ H3D_CAMERA_FOLLOW = 'CAMERA_FOLLOW_TRANSFORM' H3D_VIEW_MATRIX = 'view_matrix' -def clamp_color(col): +def clight_color(col): return tuple([max(min(c, 1.0), 0.0) for c in col]) @@ -164,14 +164,14 @@ def h3d_shader_glsl_frag_patch(filepath, scene, global_vars, frag_uniform_var_ma lines.append("%s\n" % v) lines.append("// h3d custom vars end\n") lines.append("\n") - elif l.lstrip().startswith("lamp_visibility_other("): + elif l.lstrip().startswith("light_visibility_other("): w = l.split(', ') last_transform = w[1] + "_transform" # XXX - HACK!!! w[1] = '(view_matrix * %s_transform * vec4(%s.x, %s.y, %s.z, 1.0)).xyz' % (w[1], w[1], w[1], w[1]) l = ", ".join(w) - elif l.lstrip().startswith("lamp_visibility_sun_hemi("): + elif l.lstrip().startswith("light_visibility_sun_hemi("): w = l.split(', ') - w[0] = w[0][len("lamp_visibility_sun_hemi(") + 1:] + w[0] = w[0][len("light_visibility_sun_hemi(") + 1:] if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]): w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0] @@ -179,10 +179,10 @@ def h3d_shader_glsl_frag_patch(filepath, scene, global_vars, frag_uniform_var_ma w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * -%s)' % (last_transform, last_transform, last_transform, w[0])) - l = "\tlamp_visibility_sun_hemi(" + ", ".join(w) - elif l.lstrip().startswith("lamp_visibility_spot_circle("): + l = "\tlight_visibility_sun_hemi(" + ", ".join(w) + elif l.lstrip().startswith("light_visibility_spot_circle("): w = l.split(', ') - w[0] = w[0][len("lamp_visibility_spot_circle(") + 1:] + w[0] = w[0][len("light_visibility_spot_circle(") + 1:] if not h3d_is_object_view(scene, frag_uniform_var_map[w[0]]): w[0] = '(mat3(normalize(view_matrix[0].xyz), normalize(view_matrix[1].xyz), normalize(view_matrix[2].xyz)) * -%s)' % w[0] @@ -190,7 +190,7 @@ def h3d_shader_glsl_frag_patch(filepath, scene, global_vars, frag_uniform_var_ma w[0] = ('(mat3(normalize((view_matrix*%s)[0].xyz), normalize((view_matrix*%s)[1].xyz), normalize((view_matrix*%s)[2].xyz)) * %s)' % (last_transform, last_transform, last_transform, w[0])) - l = "\tlamp_visibility_spot_circle(" + ", ".join(w) + l = "\tlight_visibility_spot_circle(" + ", ".join(w) lines.append(l) @@ -241,7 +241,7 @@ def export(file, # since objects of different types will always have # different decorated names. uuid_cache_object = {} # object - uuid_cache_lamp = {} # 'LA_' + object.name + uuid_cache_light = {} # 'LA_' + object.name uuid_cache_view = {} # object, different namespace uuid_cache_mesh = {} # mesh uuid_cache_material = {} # material @@ -261,7 +261,7 @@ def export(file, # prevent uuid collisions. uuid_cache = {} uuid_cache_object = uuid_cache # object - uuid_cache_lamp = uuid_cache # 'LA_' + object.name + uuid_cache_light = uuid_cache # 'LA_' + object.name uuid_cache_view = uuid_cache # object, different namespace uuid_cache_mesh = uuid_cache # mesh uuid_cache_material = uuid_cache # material @@ -370,16 +370,16 @@ def export(file, ident_step = ident + (' ' * (-len(ident) + \ fw('%s<Fog ' % ident))) fw('fogType="%s"\n' % ('LINEAR' if (mtype == 'LINEAR') else 'EXPONENTIAL')) - fw(ident_step + 'color="%.3f %.3f %.3f"\n' % clamp_color(world.horizon_color)) + fw(ident_step + 'color="%.3f %.3f %.3f"\n' % clight_color(world.horizon_color)) fw(ident_step + 'visibilityRange="%.3f"\n' % mparam.depth) fw(ident_step + '/>\n') else: return - def writeNavigationInfo(ident, scene, has_lamp): + def writeNavigationInfo(ident, scene, has_light): ident_step = ident + (' ' * (-len(ident) + \ fw('%s<NavigationInfo ' % ident))) - fw('headlight="%s"\n' % bool_as_str(not has_lamp)) + fw('headlight="%s"\n' % bool_as_str(not has_light)) fw(ident_step + 'visibilityLimit="0.0"\n') fw(ident_step + 'type=\'"EXAMINE", "ANY"\'\n') fw(ident_step + 'avatarSize="0.25, 1.75, 0.75"\n') @@ -411,8 +411,8 @@ def export(file, return ident def writeSpotLight(ident, obj, matrix, lamp, world): - # note, lamp_id is not re-used - lamp_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_")) + # note, light_id is not re-used + light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) if world: ambi = world.ambient_color @@ -435,11 +435,11 @@ def export(file, # radius = lamp.dist*math.cos(beamWidth) ident_step = ident + (' ' * (-len(ident) + \ fw('%s<SpotLight ' % ident))) - fw('DEF=%s\n' % lamp_id) + fw('DEF=%s\n' % light_id) fw(ident_step + 'radius="%.4f"\n' % radius) fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity) fw(ident_step + 'intensity="%.4f"\n' % intensity) - fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(lamp.color)) + fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clight_color(lamp.color)) fw(ident_step + 'beamWidth="%.4f"\n' % beamWidth) fw(ident_step + 'cutOffAngle="%.4f"\n' % cutOffAngle) fw(ident_step + 'direction="%.4f %.4f %.4f"\n' % orientation) @@ -447,8 +447,8 @@ def export(file, fw(ident_step + '/>\n') def writeDirectionalLight(ident, obj, matrix, lamp, world): - # note, lamp_id is not re-used - lamp_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_")) + # note, light_id is not re-used + light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) if world: ambi = world.ambient_color @@ -464,16 +464,16 @@ def export(file, ident_step = ident + (' ' * (-len(ident) + \ fw('%s<DirectionalLight ' % ident))) - fw('DEF=%s\n' % lamp_id) + fw('DEF=%s\n' % light_id) fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity) - fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(lamp.color)) + fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clight_color(lamp.color)) fw(ident_step + 'intensity="%.4f"\n' % intensity) fw(ident_step + 'direction="%.4f %.4f %.4f"\n' % orientation) fw(ident_step + '/>\n') def writePointLight(ident, obj, matrix, lamp, world): - # note, lamp_id is not re-used - lamp_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_")) + # note, light_id is not re-used + light_id = quoteattr(unique_name(obj, LA_ + obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) if world: ambi = world.ambient_color @@ -488,9 +488,9 @@ def export(file, ident_step = ident + (' ' * (-len(ident) + \ fw('%s<PointLight ' % ident))) - fw('DEF=%s\n' % lamp_id) + fw('DEF=%s\n' % light_id) fw(ident_step + 'ambientIntensity="%.4f"\n' % amb_intensity) - fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clamp_color(lamp.color)) + fw(ident_step + 'color="%.4f %.4f %.4f"\n' % clight_color(lamp.color)) fw(ident_step + 'intensity="%.4f"\n' % intensity) fw(ident_step + 'radius="%.4f" \n' % lamp.distance) @@ -1002,9 +1002,9 @@ def export(file, ident_step = ident + (' ' * (-len(ident) + \ fw('%s<Material ' % ident))) fw('DEF=%s\n' % material_id) - fw(ident_step + 'diffuseColor="%.3f %.3f %.3f"\n' % clamp_color(diffuseColor)) - fw(ident_step + 'specularColor="%.3f %.3f %.3f"\n' % clamp_color(specColor)) - fw(ident_step + 'emissiveColor="%.3f %.3f %.3f"\n' % clamp_color(emitColor)) + fw(ident_step + 'diffuseColor="%.3f %.3f %.3f"\n' % clight_color(diffuseColor)) + fw(ident_step + 'specularColor="%.3f %.3f %.3f"\n' % clight_color(specColor)) + fw(ident_step + 'emissiveColor="%.3f %.3f %.3f"\n' % clight_color(emitColor)) fw(ident_step + 'ambientIntensity="%.3f"\n' % ambient) fw(ident_step + 'shininess="%.3f"\n' % shininess) fw(ident_step + 'transparency="%s"\n' % transp) @@ -1035,12 +1035,12 @@ def export(file, #~ GPU_DATA_4F 5 #~ GPU_DATA_4UB 8 #~ GPU_DATA_9F 6 - #~ GPU_DYNAMIC_LAMP_DYNCO 7 - #~ GPU_DYNAMIC_LAMP_DYNCOL 11 - #~ GPU_DYNAMIC_LAMP_DYNENERGY 10 - #~ GPU_DYNAMIC_LAMP_DYNIMAT 8 - #~ GPU_DYNAMIC_LAMP_DYNPERSMAT 9 - #~ GPU_DYNAMIC_LAMP_DYNVEC 6 + #~ GPU_DYNAMIC_LIGHT_DYNCO 7 + #~ GPU_DYNAMIC_LIGHT_DYNCOL 11 + #~ GPU_DYNAMIC_LIGHT_DYNENERGY 10 + #~ GPU_DYNAMIC_LIGHT_DYNIMAT 8 + #~ GPU_DYNAMIC_LIGHT_DYNPERSMAT 9 + #~ GPU_DYNAMIC_LIGHT_DYNVEC 6 #~ GPU_DYNAMIC_OBJECT_COLOR 5 #~ GPU_DYNAMIC_OBJECT_IMAT 4 #~ GPU_DYNAMIC_OBJECT_MAT 2 @@ -1134,45 +1134,45 @@ def export(file, writeImageTexture(ident + '\t', uniform['image']) fw('%s</field>\n' % ident) - elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNCO: - lamp_obj = uniform['lamp'] - frag_uniform_var_map[uniform['varname']] = lamp_obj + elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNCO: + light_obj = uniform['lamp'] + frag_uniform_var_map[uniform['varname']] = light_obj if uniform['datatype'] == gpu.GPU_DATA_3F: # should always be true! - lamp_obj_id = quoteattr(unique_name(lamp_obj, LA_ + lamp_obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_")) - lamp_obj_transform_id = quoteattr(unique_name(lamp_obj, lamp_obj.name, uuid_cache_object, clean_func=clean_def, sep="_")) + light_obj_id = quoteattr(unique_name(light_obj, LA_ + light_obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) + light_obj_transform_id = quoteattr(unique_name(light_obj, light_obj.name, uuid_cache_object, clean_func=clean_def, sep="_")) - value = '%.6f %.6f %.6f' % (global_matrix * lamp_obj.matrix_world).to_translation()[:] - field_descr = " <!--- Lamp DynCo '%s' -->" % lamp_obj.name + value = '%.6f %.6f %.6f' % (global_matrix * light_obj.matrix_world).to_translation()[:] + field_descr = " <!--- Lamp DynCo '%s' -->" % light_obj.name fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr)) # ------------------------------------------------------ # shader-patch - field_descr = " <!--- Lamp DynCo '%s' (shader patch) -->" % lamp_obj.name + field_descr = " <!--- Lamp DynCo '%s' (shader patch) -->" % light_obj.name fw('%s<field name="%s_transform" type="SFMatrix4f" accessType="inputOutput" />%s\n' % (ident, uniform['varname'], field_descr)) # transform frag_vars.append("uniform mat4 %s_transform;" % uniform['varname']) h3d_material_route.append( '<ROUTE fromNode=%s fromField="accumulatedForward" toNode=%s toField="%s_transform" />%s' % - (suffix_quoted_str(lamp_obj_transform_id, _TRANSFORM), material_id, uniform['varname'], field_descr)) + (suffix_quoted_str(light_obj_transform_id, _TRANSFORM), material_id, uniform['varname'], field_descr)) h3d_material_route.append( '<ROUTE fromNode=%s fromField="location" toNode=%s toField="%s" /> %s' % - (lamp_obj_id, material_id, uniform['varname'], field_descr)) + (light_obj_id, material_id, uniform['varname'], field_descr)) # ------------------------------------------------------ else: assert(0) - elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNCOL: + elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNCOL: # odd we have both 3, 4 types. - lamp_obj = uniform['lamp'] - frag_uniform_var_map[uniform['varname']] = lamp_obj + light_obj = uniform['lamp'] + frag_uniform_var_map[uniform['varname']] = light_obj - lamp = lamp_obj.data + lamp = light_obj.data value = '%.6f %.6f %.6f' % (lamp.color * lamp.energy)[:] - field_descr = " <!--- Lamp DynColor '%s' -->" % lamp_obj.name + field_descr = " <!--- Lamp DynColor '%s' -->" % light_obj.name if uniform['datatype'] == gpu.GPU_DATA_3F: fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr)) elif uniform['datatype'] == gpu.GPU_DATA_4F: @@ -1180,26 +1180,26 @@ def export(file, else: assert(0) - elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNENERGY: + elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNENERGY: # not used ? assert(0) - elif uniform['type'] == gpu.GPU_DYNAMIC_LAMP_DYNVEC: - lamp_obj = uniform['lamp'] - frag_uniform_var_map[uniform['varname']] = lamp_obj + elif uniform['type'] == gpu.GPU_DYNAMIC_LIGHT_DYNVEC: + light_obj = uniform['lamp'] + frag_uniform_var_map[uniform['varname']] = light_obj if uniform['datatype'] == gpu.GPU_DATA_3F: - lamp_obj = uniform['lamp'] - value = '%.6f %.6f %.6f' % ((global_matrix * lamp_obj.matrix_world).to_quaternion() * mathutils.Vector((0.0, 0.0, 1.0))).normalized()[:] - field_descr = " <!--- Lamp DynDirection '%s' -->" % lamp_obj.name + light_obj = uniform['lamp'] + value = '%.6f %.6f %.6f' % ((global_matrix * light_obj.matrix_world).to_quaternion() * mathutils.Vector((0.0, 0.0, 1.0))).normalized()[:] + field_descr = " <!--- Lamp DynDirection '%s' -->" % light_obj.name fw('%s<field name="%s" type="SFVec3f" accessType="inputOutput" value="%s" />%s\n' % (ident, uniform['varname'], value, field_descr)) # route so we can have the lamp update the view - if h3d_is_object_view(scene, lamp_obj): - lamp_id = quoteattr(unique_name(lamp_obj, LA_ + lamp_obj.name, uuid_cache_lamp, clean_func=clean_def, sep="_")) + if h3d_is_object_view(scene, light_obj): + light_id = quoteattr(unique_name(light_obj, LA_ + light_obj.name, uuid_cache_light, clean_func=clean_def, sep="_")) h3d_material_route.append( '<ROUTE fromNode=%s fromField="direction" toNode=%s toField="%s" />%s' % - (lamp_id, material_id, uniform['varname'], field_descr)) + (light_id, material_id, uniform['varname'], field_descr)) else: assert(0) @@ -1330,9 +1330,9 @@ def export(file, blending = world.use_sky_blend, world.use_sky_paper, world.use_sky_real - grd_triple = clamp_color(world.horizon_color) - sky_triple = clamp_color(world.zenith_color) - mix_triple = clamp_color((grd_triple[i] + sky_triple[i]) / 2.0 for i in range(3)) + grd_triple = clight_color(world.horizon_color) + sky_triple = clight_color(world.zenith_color) + mix_triple = clight_color((grd_triple[i] + sky_triple[i]) / 2.0 for i in range(3)) ident_step = ident + (' ' * (-len(ident) + \ fw('%s<Background ' % ident))) @@ -1469,7 +1469,7 @@ def export(file, if do_remove: bpy.data.meshes.remove(me) - elif obj_type == 'LAMP': + elif obj_type == 'LIGHT': data = obj.data datatype = data.type if datatype == 'POINT': @@ -1521,7 +1521,7 @@ def export(file, ident = '' ident = writeHeader(ident) - writeNavigationInfo(ident, scene, any(obj.type == 'LAMP' for obj in objects)) + writeNavigationInfo(ident, scene, any(obj.type == 'LIGHT' for obj in objects)) writeBackground(ident, world) writeFog(ident, world) diff --git a/io_scene_x3d/import_x3d.py b/io_scene_x3d/import_x3d.py index 83dcc83d..d2592ecd 100644 --- a/io_scene_x3d/import_x3d.py +++ b/io_scene_x3d/import_x3d.py @@ -2790,9 +2790,9 @@ def appearance_LoadTexture(tex_node, ancestry, node): if bpyima: # Loading can still fail repeat_s = tex_node.getFieldAsBool('repeatS', True, ancestry) - bpyima.use_clamp_x = not repeat_s + bpyima.use_clight_x = not repeat_s repeat_t = tex_node.getFieldAsBool('repeatT', True, ancestry) - bpyima.use_clamp_y = not repeat_t + bpyima.use_clight_y = not repeat_t # Update the desc-based cache if desc: @@ -3148,7 +3148,7 @@ def importLamp_PointLight(node, ancestry): # is_on = node.getFieldAsBool('on', True, ancestry) # TODO radius = node.getFieldAsFloat('radius', 100.0, ancestry) - bpylamp = bpy.data.lamps.new(vrmlname, 'POINT') + bpylamp = bpy.data.lights.new(vrmlname, 'POINT') bpylamp.energy = intensity bpylamp.distance = radius bpylamp.color = color @@ -3169,7 +3169,7 @@ def importLamp_DirectionalLight(node, ancestry): intensity = node.getFieldAsFloat('intensity', 1.0, ancestry) # max is documented to be 1.0 but some files have higher. # is_on = node.getFieldAsBool('on', True, ancestry) # TODO - bpylamp = bpy.data.lamps.new(vrmlname, 'SUN') + bpylamp = bpy.data.lights.new(vrmlname, 'SUN') bpylamp.energy = intensity bpylamp.color = color @@ -3197,7 +3197,7 @@ def importLamp_SpotLight(node, ancestry): # is_on = node.getFieldAsBool('on', True, ancestry) # TODO radius = node.getFieldAsFloat('radius', 100.0, ancestry) - bpylamp = bpy.data.lamps.new(vrmlname, 'SPOT') + bpylamp = bpy.data.lights.new(vrmlname, 'SPOT') bpylamp.energy = intensity bpylamp.distance = radius bpylamp.color = color @@ -3271,8 +3271,8 @@ def importTransform(bpyscene, node, ancestry, global_matrix): bpyob.matrix_world = getFinalMatrix(node, None, ancestry, global_matrix) # so they are not too annoying - bpyob.empty_draw_type = 'PLAIN_AXES' - bpyob.empty_draw_size = 0.2 + bpyob.empty_display_type = 'PLAIN_AXES' + bpyob.empty_display_size = 0.2 #def importTimeSensor(node): diff --git a/io_shape_mdd/__init__.py b/io_shape_mdd/__init__.py index 99623241..b6f0e734 100644 --- a/io_shape_mdd/__init__.py +++ b/io_shape_mdd/__init__.py @@ -22,7 +22,7 @@ bl_info = { "name": "NewTek MDD format", "author": "Bill L.Nieuwendorp", "version": (1, 0, 1), - "blender": (2, 57, 0), + "blender": (2, 80, 0), "location": "File > Import-Export", "description": "Import-Export MDD as mesh shape keys", "warning": "", @@ -57,17 +57,17 @@ class ImportMDD(bpy.types.Operator, ImportHelper): filename_ext = ".mdd" - filter_glob = StringProperty( + filter_glob: StringProperty( default="*.mdd", options={'HIDDEN'}, ) - frame_start = IntProperty( + frame_start: IntProperty( name="Start Frame", description="Start frame for inserting animation", min=-300000, max=300000, default=0, ) - frame_step = IntProperty( + frame_step: IntProperty( name="Step", min=1, max=1000, default=1, @@ -97,7 +97,7 @@ class ExportMDD(bpy.types.Operator, ExportHelper): bl_label = "Export MDD" filename_ext = ".mdd" - filter_glob = StringProperty(default="*.mdd", options={'HIDDEN'}) + filter_glob: StringProperty(default="*.mdd", options={'HIDDEN'}) # get first scene to get min and max properties for frames, fps @@ -108,25 +108,25 @@ class ExportMDD(bpy.types.Operator, ExportHelper): # List of operator properties, the attributes will be assigned # to the class instance from the operator settings before calling. - fps = FloatProperty( + fps: FloatProperty( name="Frames Per Second", description="Number of frames/second", min=minfps, max=maxfps, default=25.0, ) - frame_start = IntProperty( + frame_start: IntProperty( name="Start Frame", description="Start frame for baking", min=minframe, max=maxframe, default=1, ) - frame_end = IntProperty( + frame_end: IntProperty( name="End Frame", description="End frame for baking", min=minframe, max=maxframe, default=250, ) - use_rest_frame = BoolProperty( + use_rest_frame: BoolProperty( name="Rest Frame", description="Write the rest state at the first frame", default=False, @@ -164,18 +164,25 @@ def menu_func_export(self, context): ) +classes = ( + ImportMDD, + ExportMDD +) + def register(): - bpy.utils.register_module(__name__) + for cls in classes: + bpy.utils.register_class(cls) - bpy.types.INFO_MT_file_import.append(menu_func_import) - bpy.types.INFO_MT_file_export.append(menu_func_export) + bpy.types.TOPBAR_MT_file_import.append(menu_func_import) + bpy.types.TOPBAR_MT_file_export.append(menu_func_export) def unregister(): - bpy.utils.unregister_module(__name__) + for cls in classes: + bpy.utils.unregister_class(cls) - bpy.types.INFO_MT_file_import.remove(menu_func_import) - bpy.types.INFO_MT_file_export.remove(menu_func_export) + bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) + bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) if __name__ == "__main__": register() diff --git a/io_shape_mdd/export_mdd.py b/io_shape_mdd/export_mdd.py index 04ca8238..48e34f29 100644 --- a/io_shape_mdd/export_mdd.py +++ b/io_shape_mdd/export_mdd.py @@ -67,7 +67,7 @@ def save(context, filepath="", frame_start=1, frame_end=300, fps=25.0, use_rest_ orig_frame = scene.frame_current scene.frame_set(frame_start) - me = obj.to_mesh(scene, True, 'PREVIEW') + me = obj.to_mesh(context.depsgraph, True) #Flip y and z ''' @@ -95,21 +95,21 @@ def save(context, filepath="", frame_start=1, frame_end=300, fps=25.0, use_rest_ if use_rest_frame: check_vertcount(me, numverts) - me.transform(mat_flip * obj.matrix_world) + me.transform(mat_flip @ obj.matrix_world) f.write(pack(">%df" % (numverts * 3), *[axis for v in me.vertices for axis in v.co])) - bpy.data.meshes.remove(me, do_unlink=True) + bpy.data.meshes.remove(me) for frame in range(frame_start, frame_end + 1): # in order to start at desired frame scene.frame_set(frame) - me = obj.to_mesh(scene, True, 'PREVIEW') + me = obj.to_mesh(context.depsgraph, True) check_vertcount(me, numverts) - me.transform(mat_flip * obj.matrix_world) + me.transform(mat_flip @ obj.matrix_world) # Write the vertex data f.write(pack(">%df" % (numverts * 3), *[axis for v in me.vertices for axis in v.co])) - bpy.data.meshes.remove(me, do_unlink=True) + bpy.data.meshes.remove(me) f.close() diff --git a/light_field_tools/light_field_tools.py b/light_field_tools/light_field_tools.py index 82d86ba4..5ab2092f 100644 --- a/light_field_tools/light_field_tools.py +++ b/light_field_tools/light_field_tools.py @@ -189,7 +189,7 @@ class OBJECT_OT_create_lightfield_rig(Operator): cam.data.angle = scene.lightfield.angle # display options of the camera - cam.data.draw_size = 0.15 + cam.data.display_size = 0.15 cam.data.lens_unit = 'FOV' # handler parent @@ -228,7 +228,7 @@ class OBJECT_OT_create_lightfield_rig(Operator): def createSpot(self, index, textured=False): scene = bpy.context.scene - bpy.ops.object.lamp_add( + bpy.ops.object.light_add( type='SPOT') spot = bpy.context.active_object diff --git a/materials_utils/__init__.py b/materials_utils/__init__.py index 5b5cda6f..94975e6b 100644 --- a/materials_utils/__init__.py +++ b/materials_utils/__init__.py @@ -2506,7 +2506,7 @@ def check_mat_name_unique(name_id="Material_new"): def included_object_types(objects): # Pass the bpy.data.objects.type to avoid needless assigning/removing # included - type that can have materials - included = ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META'] + included = ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META', 'GPENCIL'] obj = objects return bool(obj and obj in included) diff --git a/measureit/__init__.py b/measureit/__init__.py index a030fe73..2d248d9c 100644 --- a/measureit/__init__.py +++ b/measureit/__init__.py @@ -29,8 +29,8 @@ bl_info = { "name": "MeasureIt", "author": "Antonio Vazquez (antonioya)", "location": "View3D > Tools Panel /Properties panel", - "version": (1, 7, 1), - "blender": (2, 79, 0), + "version": (1, 8, 0), + "blender": (2, 80, 0), "description": "Tools for measuring objects.", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/" "Py/Scripts/3D_interaction/Measureit", @@ -74,10 +74,10 @@ from bpy.props import ( # Define Panel classes for updating panels = ( - measureit_main.MeasureitEditPanel, - measureit_main.MeasureitMainPanel, - measureit_main.MeasureitConfPanel, - measureit_main.MeasureitRenderPanel, + measureit_main.MEASUREIT_PT_Edit, + measureit_main.MEASUREIT_PT_Main, + measureit_main.MEASUREIT_PT_Conf, + measureit_main.MEASUREIT_PT_Render, ) @@ -120,29 +120,35 @@ class Measure_Pref(AddonPreferences): # Define menu # noinspection PyUnusedLocal +classes = ( + measureit_main.MEASUREIT_OT_RunHintDisplay, + measureit_main.MEASUREIT_OT_AddSegment, + measureit_main.MEASUREIT_OT_AddArea, + measureit_main.MEASUREIT_OT_AddSegmentOrto, + measureit_main.MEASUREIT_OT_AddAngle, + measureit_main.MEASUREIT_OT_AddArc, + measureit_main.MEASUREIT_OT_AddLabel, + measureit_main.MEASUREIT_OT_AddNote, + measureit_main.MEASUREIT_OT_AddLink, + measureit_main.MEASUREIT_OT_AddOrigin, + measureit_main.MEASUREIT_OT_DeleteSegment, + measureit_main.MEASUREIT_OT_DeleteAllSegment, + measureit_main.MEASUREIT_OT_DeleteAllSum, + measureit_main.MEASUREIT_OT_RenderSegment, + measureit_main.MEASUREIT_OT_ExpandAllSegment, + measureit_main.MEASUREIT_OT_CollapseAllSegment, + measureit_main.MEASUREIT_PT_Main, + measureit_main.MEASUREIT_PT_Edit, + measureit_main.MEASUREIT_PT_Conf, + measureit_main.MEASUREIT_PT_Render, +# Measure_Pref, +) + def register(): - bpy.utils.register_class(measureit_main.RunHintDisplayButton) - bpy.utils.register_class(measureit_main.AddSegmentButton) - bpy.utils.register_class(measureit_main.AddAreaButton) - bpy.utils.register_class(measureit_main.AddSegmentOrtoButton) - bpy.utils.register_class(measureit_main.AddAngleButton) - bpy.utils.register_class(measureit_main.AddArcButton) - bpy.utils.register_class(measureit_main.AddLabelButton) - bpy.utils.register_class(measureit_main.AddNoteButton) - bpy.utils.register_class(measureit_main.AddLinkButton) - bpy.utils.register_class(measureit_main.AddOriginButton) - bpy.utils.register_class(measureit_main.DeleteSegmentButton) - bpy.utils.register_class(measureit_main.DeleteAllSegmentButton) - bpy.utils.register_class(measureit_main.DeleteAllSumButton) - bpy.utils.register_class(measureit_main.MeasureitEditPanel) - bpy.utils.register_class(measureit_main.MeasureitMainPanel) - bpy.utils.register_class(measureit_main.MeasureitConfPanel) - bpy.utils.register_class(measureit_main.MeasureitRenderPanel) - bpy.utils.register_class(measureit_main.RenderSegmentButton) - bpy.utils.register_class(measureit_main.ExpandAllSegmentButton) - bpy.utils.register_class(measureit_main.CollapseAllSegmentButton) - bpy.utils.register_class(Measure_Pref) - update_panel(None, bpy.context) + from bpy.utils import register_class + for cls in classes: + register_class(cls) + # Define properties Scene.measureit_default_color = FloatVectorProperty( name="Default color", @@ -244,11 +250,8 @@ def register(): description="Save an image with measures over" " render image", default=False) - Scene.measureit_render_type = EnumProperty(items=(('1', "*Current", "Use current render"), - ('2', "OpenGL", ""), - ('3', "Animation OpenGL", ""), - ('4', "Image", ""), - ('5', "Animation", "")), + Scene.measureit_render_type = EnumProperty(items=(('1', "Frame", "Render current frame"), + ('2', "Animation", "")), name="Render type", description="Type of render image") Scene.measureit_sum = EnumProperty(items=(('99', "-", "Select a group for sum"), @@ -420,27 +423,9 @@ def register(): def unregister(): - bpy.utils.unregister_class(measureit_main.RunHintDisplayButton) - bpy.utils.unregister_class(measureit_main.AddSegmentButton) - bpy.utils.unregister_class(measureit_main.AddAreaButton) - bpy.utils.unregister_class(measureit_main.AddSegmentOrtoButton) - bpy.utils.unregister_class(measureit_main.AddAngleButton) - bpy.utils.unregister_class(measureit_main.AddArcButton) - bpy.utils.unregister_class(measureit_main.AddLabelButton) - bpy.utils.unregister_class(measureit_main.AddNoteButton) - bpy.utils.unregister_class(measureit_main.AddLinkButton) - bpy.utils.unregister_class(measureit_main.AddOriginButton) - bpy.utils.unregister_class(measureit_main.DeleteSegmentButton) - bpy.utils.unregister_class(measureit_main.DeleteAllSegmentButton) - bpy.utils.unregister_class(measureit_main.DeleteAllSumButton) - bpy.utils.unregister_class(measureit_main.MeasureitEditPanel) - bpy.utils.unregister_class(measureit_main.MeasureitMainPanel) - bpy.utils.unregister_class(measureit_main.MeasureitConfPanel) - bpy.utils.unregister_class(measureit_main.MeasureitRenderPanel) - bpy.utils.unregister_class(measureit_main.RenderSegmentButton) - bpy.utils.unregister_class(measureit_main.ExpandAllSegmentButton) - bpy.utils.unregister_class(measureit_main.CollapseAllSegmentButton) - bpy.utils.unregister_class(Measure_Pref) + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) # Remove properties del Scene.measureit_default_color @@ -501,7 +486,7 @@ def unregister(): del Scene.measureit_font_align # remove OpenGL data - measureit_main.RunHintDisplayButton.handle_remove(measureit_main.RunHintDisplayButton, bpy.context) + measureit_main.MEASUREIT_OT_RunHintDisplay.handle_remove(measureit_main.MEASUREIT_OT_RunHintDisplay, bpy.context) wm = bpy.context.window_manager p = 'measureit_run_opengl' if p in wm: diff --git a/measureit/measureit_geometry.py b/measureit/measureit_geometry.py index e0b036d6..6340a890 100644 --- a/measureit/measureit_geometry.py +++ b/measureit/measureit_geometry.py @@ -26,8 +26,6 @@ # noinspection PyUnresolvedReferences import bpy # noinspection PyUnresolvedReferences -import bgl -# noinspection PyUnresolvedReferences import blf from blf import ROTATION from math import fabs, degrees, radians, sqrt, cos, sin, pi @@ -36,13 +34,16 @@ from bmesh import from_edit_mesh from bpy_extras import view3d_utils, mesh_utils import bpy_extras.object_utils as object_utils from sys import exc_info +# GPU +import bgl +import gpu +from gpu_extras.batch import batch_for_shader +shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') # ------------------------------------------------------------- # Draw segments # -# rgb: Color -# fsize: Font size # ------------------------------------------------------------- # noinspection PyUnresolvedReferences,PyUnboundLocalVariable def draw_segments(context, myobj, op, region, rv3d): @@ -60,6 +61,7 @@ def draw_segments(context, myobj, op, region, rv3d): ovrline = scene.measureit_ovr_width units = scene.measureit_units fang = get_angle_in_rad(scene.measureit_font_rotation) + # -------------------- # Scene Scale # -------------------- @@ -72,6 +74,7 @@ def draw_segments(context, myobj, op, region, rv3d): draw_text(myobj, pos_2d, tx_scale, scene.measureit_scale_color, scene.measureit_scale_font, text_rot=fang) + # -------------------- # Loop # -------------------- @@ -96,9 +99,10 @@ def draw_segments(context, myobj, op, region, rv3d): # noinspection PyBroadException try: if ovr is False: - rgb = ms.glcolor + rgba = ms.glcolor else: - rgb = ovrcolor + rgba = ovrcolor + # ---------------------- # Segment or Label # ---------------------- @@ -108,6 +112,7 @@ def draw_segments(context, myobj, op, region, rv3d): if ms.glpointa <= len(obverts) and ms.glpointb <= len(obverts): a_p1 = get_point(obverts[ms.glpointa].co, myobj) b_p1 = get_point(obverts[ms.glpointb].co, myobj) + # ---------------------- # Segment or Label # ---------------------- @@ -326,8 +331,6 @@ def draw_segments(context, myobj, op, region, rv3d): else: bgl.glLineWidth(ovrline) - bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) - # ------------------------------------ # Text (distance) # ------------------------------------ @@ -366,7 +369,7 @@ def draw_segments(context, myobj, op, region, rv3d): if scene.measureit_gl_show_n is True and ms.glnames is True: msg += ms.gltxt if scene.measureit_gl_show_d is True or scene.measureit_gl_show_n is True: - draw_text(myobj, txtpoint2d, msg, rgb, fsize, faln, fang) + draw_text(myobj, txtpoint2d, msg, rgba, fsize, faln, fang) # ------------------------------ # if axis loc, show a indicator @@ -381,7 +384,7 @@ def draw_segments(context, myobj, op, region, rv3d): if ms.glocz is True: txt += "Z" txt += "]" - draw_text(myobj, txtpoint2d, txt, rgb, fsize - 1, text_rot=fang) + draw_text(myobj, txtpoint2d, txt, rgba, fsize - 1, text_rot=fang) except: pass @@ -445,7 +448,7 @@ def draw_segments(context, myobj, op, region, rv3d): tmp_point = get_2d_point(region, rv3d, gap3d) if tmp_point is not None: txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty - draw_text(myobj, txtpoint2d, msg, rgb, fsize, faln, fang) + draw_text(myobj, txtpoint2d, msg, rgba, fsize, faln, fang) # Radius if scene.measureit_gl_show_d is True and ms.gldist is True and \ ms.glarc_rad is True: @@ -461,7 +464,7 @@ def draw_segments(context, myobj, op, region, rv3d): tmp_point = get_2d_point(region, rv3d, gap3d) if tmp_point is not None: txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty - draw_text(myobj, txtpoint2d, tx_dist, rgb, fsize, faln, fang) + draw_text(myobj, txtpoint2d, tx_dist, rgba, fsize, faln, fang) except: pass # ------------------------------------ @@ -475,31 +478,30 @@ def draw_segments(context, myobj, op, region, rv3d): tmp_point = get_2d_point(region, rv3d, gap3d) if tmp_point is not None: txtpoint2d = tmp_point[0] + ms.glfontx, tmp_point[1] + ms.glfonty - draw_text(myobj, txtpoint2d, tx_dist, rgb, fsize, faln, fang) + draw_text(myobj, txtpoint2d, tx_dist, rgba, fsize, faln, fang) # ------------------------------------ # Draw lines # ------------------------------------ bgl.glEnable(bgl.GL_BLEND) - bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) if ms.gltype == 1: # Segment - draw_line(screen_point_ap1, screen_point_v11) - draw_line(screen_point_bp1, screen_point_v22) - draw_arrow(screen_point_v1, screen_point_v2, a_size, a_type, b_type) + draw_line(screen_point_ap1, screen_point_v11, rgba) + draw_line(screen_point_bp1, screen_point_v22, rgba) + draw_arrow(screen_point_v1, screen_point_v2, rgba, a_size, a_type, b_type) if ms.gltype == 12 or ms.gltype == 13 or ms.gltype == 14: # Segment to origin - draw_line(screen_point_ap1, screen_point_v11) - draw_line(screen_point_bp1, screen_point_v22) - draw_arrow(screen_point_v1, screen_point_v2, a_size, a_type, b_type) + draw_line(screen_point_ap1, screen_point_v11, rgba) + draw_line(screen_point_bp1, screen_point_v22, rgba) + draw_arrow(screen_point_v1, screen_point_v2, rgba, a_size, a_type, b_type) if ms.gltype == 2: # Label - draw_line(screen_point_v11a, screen_point_v11b) - draw_arrow(screen_point_ap1, screen_point_v11, a_size, a_type, b_type) + draw_line(screen_point_v11a, screen_point_v11b, rgba) + draw_arrow(screen_point_ap1, screen_point_v11, rgba, a_size, a_type, b_type) if ms.gltype == 3 or ms.gltype == 4 or ms.gltype == 5 or ms.gltype == 8 \ or ms.gltype == 6 or ms.gltype == 7: # Origin and Links - draw_arrow(screen_point_ap1, screen_point_bp1, a_size, a_type, b_type) + draw_arrow(screen_point_ap1, screen_point_bp1, rgba, a_size, a_type, b_type) if ms.gltype == 9: # Angle dist, distloc = distance(an_p1, an_p2) @@ -512,16 +514,16 @@ def draw_segments(context, myobj, op, region, rv3d): screen_point_an_p2 = get_2d_point(region, rv3d, an_p2) screen_point_an_p3 = get_2d_point(region, rv3d, mp2) - draw_line(screen_point_an_p1, screen_point_an_p2) - draw_line(screen_point_an_p2, screen_point_an_p3) - draw_line(screen_point_an_p1, screen_point_an_p3) + draw_line(screen_point_an_p1, screen_point_an_p2, rgba) + draw_line(screen_point_an_p2, screen_point_an_p3, rgba) + draw_line(screen_point_an_p1, screen_point_an_p3, rgba) if ms.gltype == 11: # arc # draw line from center of arc second point c = Vector(a_p1) if ms.glarc_rad is True: if ms.glarc_extrad is False: - draw_arrow(screen_point_ap1, screen_point_bp1, a_size, a_type, b_type) + draw_arrow(screen_point_ap1, screen_point_bp1, rgba, a_size, a_type, b_type) else: vne = Vector((b_p1[0] - a_p1[0], b_p1[1] - a_p1[1], @@ -530,7 +532,7 @@ def draw_segments(context, myobj, op, region, rv3d): vie = vne * ms.glspace pe = (b_p1[0] + vie[0], b_p1[1] + vie[1], b_p1[2] + vie[2]) screen_point_pe = get_2d_point(region, rv3d, pe) - draw_arrow(screen_point_ap1, screen_point_pe, a_size, a_type, b_type) + draw_arrow(screen_point_ap1, screen_point_pe, rgba, a_size, a_type, b_type) # create arc around the centerpoint # rotation matrix around normal vector at center point @@ -558,7 +560,7 @@ def draw_segments(context, myobj, op, region, rv3d): p_02b = None # draw the arc for i in range(int(n_step)): - p2 = mat_trans2 * mat_rot1 * mat_trans1 * p1 + p2 = mat_trans2 @ mat_rot1 @ mat_trans1 @ p1 p1_ = (p1[0] + vi[0], p1[1] + vi[1], p1[2] + vi[2]) # First Point if i == 0: @@ -577,11 +579,11 @@ def draw_segments(context, myobj, op, region, rv3d): screen_point_p1 = get_2d_point(region, rv3d, p1_) screen_point_p2 = get_2d_point(region, rv3d, p2_) if i == 0: - draw_arrow(screen_point_p1, screen_point_p2, ms.glarc_s, ms.glarc_a, "99") + draw_arrow(screen_point_p1, screen_point_p2, rgba, ms.glarc_s, ms.glarc_a, "99") elif i == int(n_step) - 1: - draw_arrow(screen_point_p1, screen_point_p2, ms.glarc_s, "99", ms.glarc_b) + draw_arrow(screen_point_p1, screen_point_p2, rgba, ms.glarc_s, "99", ms.glarc_b) else: - draw_line(screen_point_p1, screen_point_p2) + draw_line(screen_point_p1, screen_point_p2, rgba) p1 = p2.copy() @@ -597,8 +599,8 @@ def draw_segments(context, myobj, op, region, rv3d): screen_point_p2a = get_2d_point(region, rv3d, p_02a) screen_point_p2b = get_2d_point(region, rv3d, p_02b) - draw_line(screen_point_p1a, screen_point_p1b) - draw_line(screen_point_p2a, screen_point_p2b) + draw_line(screen_point_p1a, screen_point_p1b, rgba) + draw_line(screen_point_p2a, screen_point_p2b, rgba) if ms.gltype == 20: # Area obverts = get_mesh_vertices(myobj) @@ -608,7 +610,7 @@ def draw_segments(context, myobj, op, region, rv3d): for v in face.measureit_index: myvertices.append(v.glidx) - area = get_area_and_paint(myvertices, myobj, obverts, region, rv3d) + area = get_area_and_paint(myvertices, myobj, obverts, region, rv3d, rgba) tot += area # Draw Area number over first face if len(ms.measureit_faces) > 0: @@ -657,7 +659,7 @@ def draw_segments(context, myobj, op, region, rv3d): # Get polygon area and paint area # # ------------------------------------------ -def get_area_and_paint(myvertices, myobj, obverts, region, rv3d): +def get_area_and_paint(myvertices, myobj, obverts, region, rv3d, rgba): mymesh = myobj.data totarea = 0 if len(myvertices) > 3: @@ -680,9 +682,11 @@ def get_area_and_paint(myvertices, myobj, obverts, region, rv3d): screen_point_p1 = get_2d_point(region, rv3d, p1) screen_point_p2 = get_2d_point(region, rv3d, p2) screen_point_p3 = get_2d_point(region, rv3d, p3) - draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3) + + draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3, rgba) # Area + area = get_triangle_area(p1, p2, p3) totarea += area @@ -695,7 +699,7 @@ def get_area_and_paint(myvertices, myobj, obverts, region, rv3d): screen_point_p1 = get_2d_point(region, rv3d, p1) screen_point_p2 = get_2d_point(region, rv3d, p2) screen_point_p3 = get_2d_point(region, rv3d, p3) - draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3) + draw_triangle(screen_point_p1, screen_point_p2, screen_point_p3, rgba) # Area area = get_triangle_area(p1, p2, p3) @@ -798,7 +802,7 @@ def get_group_sum(myobj, tag): # Create OpenGL text # # ------------------------------------------------------------- -def draw_text(myobj, pos2d, display_text, rgb, fsize, align='L', text_rot=0.0): +def draw_text(myobj, pos2d, display_text, rgba, fsize, align='L', text_rot=0.0): if pos2d is None: return @@ -843,7 +847,7 @@ def draw_text(myobj, pos2d, display_text, rgb, fsize, align='L', text_rot=0.0): new_y = y_pos + (mheight * idx) # Draw blf.position(font_id, newx, new_y, 0) - bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) + blf.color(font_id, rgba[0], rgba[1], rgba[2], rgba[3]) blf.draw(font_id, " " + line) # sub line idx -= 1 @@ -861,14 +865,16 @@ def draw_text(myobj, pos2d, display_text, rgb, fsize, align='L', text_rot=0.0): # Draw an OpenGL line # # ------------------------------------------------------------- -def draw_line(v1, v2): +def draw_line(v1, v2, rgba): + coords = [(v1[0], v1[1]), (v2[0], v2[1])] + batch = batch_for_shader(shader, 'LINES', {"pos": coords}) + # noinspection PyBroadException try: if v1 is not None and v2 is not None: - bgl.glBegin(bgl.GL_LINES) - bgl.glVertex2f(*v1) - bgl.glVertex2f(*v2) - bgl.glEnd() + shader.bind() + shader.uniform_float("color", rgba) + batch.draw(shader) except: pass @@ -877,15 +883,16 @@ def draw_line(v1, v2): # Draw an OpenGL triangle # # ------------------------------------------------------------- -def draw_triangle(v1, v2, v3): +def draw_triangle(v1, v2, v3, rgba): + coords = [(v1[0], v1[1]), (v2[0], v2[1]), (v3[0], v3[1])] + batch = batch_for_shader(shader, 'TRIS', {"pos": coords}) + # noinspection PyBroadException try: if v1 is not None and v2 is not None and v3 is not None: - bgl.glBegin(bgl.GL_TRIANGLES) - bgl.glVertex2f(*v1) - bgl.glVertex2f(*v2) - bgl.glVertex2f(*v3) - bgl.glEnd() + shader.bind() + shader.uniform_float("color", rgba) + batch.draw(shader) except: pass @@ -894,7 +901,7 @@ def draw_triangle(v1, v2, v3): # Draw an Arrow # # ------------------------------------------------------------- -def draw_arrow(v1, v2, size=20, a_typ="1", b_typ="1"): +def draw_arrow(v1, v2, rgba, size=20, a_typ="1", b_typ="1"): if v1 is None or v2 is None: return @@ -938,19 +945,19 @@ def draw_arrow(v1, v2, size=20, a_typ="1", b_typ="1"): # Triangle o Lines if a_typ == "1" or a_typ == "3": - draw_line(v1, v1a) - draw_line(v1, v1b) + draw_line(v1, v1a, rgba) + draw_line(v1, v1b, rgba) if b_typ == "1" or b_typ == "3": - draw_line(v2, v2a) - draw_line(v2, v2b) + draw_line(v2, v2a, rgba) + draw_line(v2, v2b, rgba) if a_typ == "2": - draw_triangle(v1, v1a, v1b) + draw_triangle(v1, v1a, v1b, rgba) if b_typ == "2": - draw_triangle(v2, v2a, v2b) + draw_triangle(v2, v2a, v2b, rgba) - draw_line(v1, v2) + draw_line(v1, v2, rgba) # ------------------------------------------------------------- @@ -958,16 +965,16 @@ def draw_arrow(v1, v2, size=20, a_typ="1", b_typ="1"): # # v1, v2 are corners (bottom left / top right) # ------------------------------------------------------------- -def draw_rectangle(v1, v2): +def draw_rectangle(v1, v2, rgba): # noinspection PyBroadException try: if v1 is not None and v2 is not None: v1b = (v2[0], v1[1]) v2b = (v1[0], v2[1]) - draw_line(v1, v1b) - draw_line(v1b, v2) - draw_line(v2, v2b) - draw_line(v2b, v1) + draw_line(v1, v1b, rgba) + draw_line(v1b, v2, rgba) + draw_line(v2, v2b, rgba) + draw_line(v2b, v1, rgba) except: pass @@ -996,7 +1003,7 @@ def format_point(mypoint, pr): # noinspection PyUnresolvedReferences,PyUnboundLocalVariable,PyUnusedLocal def draw_object(context, myobj, region, rv3d): scene = bpy.context.scene - rgb = scene.measureit_debug_obj_color + rgba = scene.measureit_debug_obj_color fsize = scene.measureit_debug_font precision = scene.measureit_debug_precision # -------------------- @@ -1010,8 +1017,6 @@ def draw_object(context, myobj, region, rv3d): if objs[o].select is False: continue a_p1 = Vector(get_location(objs[o])) - # colour - bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) # Text txt = '' if scene.measureit_debug_objects is True: @@ -1020,7 +1025,7 @@ def draw_object(context, myobj, region, rv3d): txt += format_point(a_p1, precision) # converting to screen coordinates txtpoint2d = get_2d_point(region, rv3d, a_p1) - draw_text(myobj, txtpoint2d, txt, rgb, fsize) + draw_text(myobj, txtpoint2d, txt, rgba, fsize) return @@ -1035,7 +1040,7 @@ def draw_vertices(context, myobj, region, rv3d): return scene = bpy.context.scene - rgb = scene.measureit_debug_vert_color + rgba = scene.measureit_debug_vert_color fsize = scene.measureit_debug_font precision = scene.measureit_debug_precision # -------------------- @@ -1044,7 +1049,7 @@ def draw_vertices(context, myobj, region, rv3d): if scene.measureit_debug_vert_loc_toggle == '1': co_mult = lambda c: c else: # if global, convert local c to global - co_mult = lambda c: myobj.matrix_world * c + co_mult = lambda c: myobj.matrix_world @ c if myobj.mode == 'EDIT': bm = from_edit_mesh(myobj.data) @@ -1060,8 +1065,6 @@ def draw_vertices(context, myobj, region, rv3d): # noinspection PyBroadException # try: a_p1 = get_point(v.co, myobj) - # colour - bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) # converting to screen coordinates txtpoint2d = get_2d_point(region, rv3d, a_p1) # Text @@ -1070,7 +1073,7 @@ def draw_vertices(context, myobj, region, rv3d): txt += str(v.index) if scene.measureit_debug_vert_loc is True: txt += format_point(co_mult(v.co), precision) - draw_text(myobj, txtpoint2d, txt, rgb, fsize) + draw_text(myobj, txtpoint2d, txt, rgba, fsize) # except: # print("Unexpected error:" + str(exc_info())) # pass @@ -1089,7 +1092,7 @@ def draw_edges(context, myobj, region, rv3d): return scene = bpy.context.scene - rgb = scene.measureit_debug_edge_color + rgba = scene.measureit_debug_edge_color fsize = scene.measureit_debug_font precision = scene.measureit_debug_precision # -------------------- @@ -1115,11 +1118,9 @@ def draw_edges(context, myobj, region, rv3d): continue a_mp = midf(e, obverts) a_p1 = get_point(a_mp, myobj) - # colour - bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) # converting to screen coordinates txtpoint2d = get_2d_point(region, rv3d, a_p1) - draw_text(myobj, txtpoint2d, str(e.index), rgb, fsize) + draw_text(myobj, txtpoint2d, str(e.index), rgba, fsize) return @@ -1134,8 +1135,8 @@ def draw_faces(context, myobj, region, rv3d): return scene = bpy.context.scene - rgb = scene.measureit_debug_face_color - rgb2 = scene.measureit_debug_norm_color + rgba = scene.measureit_debug_face_color + rgba2 = scene.measureit_debug_norm_color fsize = scene.measureit_debug_font ln = scene.measureit_debug_normal_size th = scene.measureit_debug_width @@ -1166,21 +1167,19 @@ def draw_faces(context, myobj, region, rv3d): a_p1 = get_point(f.center, myobj) a_p2 = (a_p1[0] + normal[0] * ln, a_p1[1] + normal[1] * ln, a_p1[2] + normal[2] * ln) - # colour + line setup + # line setup bgl.glEnable(bgl.GL_BLEND) bgl.glLineWidth(th) - bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) # converting to screen coordinates txtpoint2d = get_2d_point(region, rv3d, a_p1) point2 = get_2d_point(region, rv3d, a_p2) # Text if scene.measureit_debug_faces is True: - draw_text(myobj, txtpoint2d, str(f.index), rgb, fsize) + draw_text(myobj, txtpoint2d, str(f.index), rgba, fsize) # Draw Normal if scene.measureit_debug_normals is True: bgl.glEnable(bgl.GL_BLEND) - bgl.glColor4f(rgb2[0], rgb2[1], rgb2[2], rgb2[3]) - draw_arrow(txtpoint2d, point2, 10, "99", "1") + draw_arrow(txtpoint2d, point2, rgba, 10, "99", "1") if len(obverts) > 2 and scene.measureit_debug_normal_details is True: if myobj.mode == 'EDIT': @@ -1200,11 +1199,11 @@ def draw_faces(context, myobj, region, rv3d): b2d = get_2d_point(region, rv3d, a_p2) c2d = get_2d_point(region, rv3d, a_p3) # draw vectors - draw_arrow(a2d, b2d, 10, "99", "1") - draw_arrow(b2d, c2d, 10, "99", "1") + draw_arrow(a2d, b2d, rgba, 10, "99", "1") + draw_arrow(b2d, c2d, rgba, 10, "99", "1") # Normal vector data txt = format_point(normal, precision) - draw_text(myobj, point2, txt, rgb2, fsize) + draw_text(myobj, point2, txt, rgba2, fsize) except: print("Unexpected error:" + str(exc_info())) @@ -1271,7 +1270,7 @@ def get_point(v1, mainobject): # Using World Matrix vt = Vector((v1[0], v1[1], v1[2], 1)) m4 = mainobject.matrix_world - vt2 = m4 * vt + vt2 = m4 @ vt v2 = [vt2[0], vt2[1], vt2[2]] return v2 diff --git a/measureit/measureit_main.py b/measureit/measureit_main.py index 2ea41da1..aa5ded6d 100644 --- a/measureit/measureit_main.py +++ b/measureit/measureit_main.py @@ -46,7 +46,7 @@ from .measureit_render import * # noinspection PyUnusedLocal @persistent def load_handler(dummy): - RunHintDisplayButton.handle_remove(None, bpy.context) + MEASUREIT_OT_RunHintDisplay.handle_remove(None, bpy.context) # ------------------------------------------------------ @@ -88,8 +88,7 @@ bpy.app.handlers.save_pre.append(save_handler) # Define property group class for measureit faces index # ------------------------------------------------------------------ class MeasureitIndex(PropertyGroup): - glidx = IntProperty(name="index", - description="vertex index") + glidx: IntProperty(name="index", description="vertex index") # Register @@ -100,10 +99,9 @@ bpy.utils.register_class(MeasureitIndex) # Define property group class for measureit faces # ------------------------------------------------------------------ class MeasureitFaces(PropertyGroup): - glface = IntProperty(name="glface", - description="Face number") + glface: IntProperty(name="glface", description="Face number") # Array of index - measureit_index = CollectionProperty(type=MeasureitIndex) + measureit_index: CollectionProperty(type=MeasureitIndex) # Register @@ -114,86 +112,86 @@ bpy.utils.register_class(MeasureitFaces) # Define property group class for measureit data # ------------------------------------------------------------------ class MeasureitProperties(PropertyGroup): - gltype = IntProperty(name="gltype", - description="Measure type (1-Segment, 2-Label, etc..)", default=1) - glpointa = IntProperty(name="glpointa", - description="Hidden property for opengl") - glpointb = IntProperty(name="glpointb", - description="Hidden property for opengl") - glpointc = IntProperty(name="glpointc", - description="Hidden property for opengl") - glcolor = FloatVectorProperty(name="glcolor", + gltype: IntProperty(name="gltype", + description="Measure type (1-Segment, 2-Label, etc..)", default=1) + glpointa: IntProperty(name="glpointa", + description="Hidden property for opengl") + glpointb: IntProperty(name="glpointb", + description="Hidden property for opengl") + glpointc: IntProperty(name="glpointc", + description="Hidden property for opengl") + glcolor: FloatVectorProperty(name="glcolor", description="Color for the measure", default=(0.173, 0.545, 1.0, 1.0), min=0.1, max=1, subtype='COLOR', size=4) - glview = BoolProperty(name="glview", + glview: BoolProperty(name="glview", description="Measure visible/hide", default=True) - glspace = FloatProperty(name='glspace', min=-100, max=100, default=0.1, + glspace: FloatProperty(name='glspace', min=-100, max=100, default=0.1, precision=3, description='Distance to display measure') - glwidth = IntProperty(name='glwidth', min=1, max=10, default=1, + glwidth: IntProperty(name='glwidth', min=1, max=10, default=1, description='line width') - glfree = BoolProperty(name="glfree", + glfree: BoolProperty(name="glfree", description="This measure is free and can be deleted", default=False) - gltxt = StringProperty(name="gltxt", maxlen=256, + gltxt: StringProperty(name="gltxt", maxlen=256, description="Short description (use | for line break)") - gladvance = BoolProperty(name="gladvance", + gladvance: BoolProperty(name="gladvance", description="Advanced options as line width or position", default=False) - gldefault = BoolProperty(name="gldefault", + gldefault: BoolProperty(name="gldefault", description="Display measure in position calculated by default", default=True) - glnormalx = FloatProperty(name="glnormalx", + glnormalx: FloatProperty(name="glnormalx", description="Change orientation in X axis", default=1, min=-1, max=1, precision=2) - glnormaly = FloatProperty(name="glnormaly", + glnormaly: FloatProperty(name="glnormaly", description="Change orientation in Y axis", default=0, min=-1, max=1, precision=2) - glnormalz = FloatProperty(name="glnormalz", + glnormalz: FloatProperty(name="glnormalz", description="Change orientation in Z axis", default=0, min=-1, max=1, precision=2) - glfont_size = IntProperty(name="Text Size", + glfont_size: IntProperty(name="Text Size", description="Text size", default=14, min=6, max=150) - glfont_align = EnumProperty(items=(('L', "Left Align", ""), + glfont_align: EnumProperty(items=(('L', "Left Align", ""), ('C', "Center Align", ""), ('R', "Right Align", "")), name="Align Font", description="Set Font Alignment") - glfont_rotat = IntProperty(name='Rotate', min=0, max=360, default=0, + glfont_rotat: IntProperty(name='Rotate', min=0, max=360, default=0, description="Text rotation in degrees") - gllink = StringProperty(name="gllink", + gllink: StringProperty(name="gllink", description="linked object for linked measures") - glocwarning = BoolProperty(name="glocwarning", + glocwarning: BoolProperty(name="glocwarning", description="Display a warning if some axis is not used in distance", default=True) - glocx = BoolProperty(name="glocx", + glocx: BoolProperty(name="glocx", description="Include changes in X axis for calculating the distance", default=True) - glocy = BoolProperty(name="glocy", + glocy: BoolProperty(name="glocy", description="Include changes in Y axis for calculating the distance", default=True) - glocz = BoolProperty(name="glocz", + glocz: BoolProperty(name="glocz", description="Include changes in Z axis for calculating the distance", default=True) - glfontx = IntProperty(name="glfontx", + glfontx: IntProperty(name="glfontx", description="Change font position in X axis", default=0, min=-3000, max=3000) - glfonty = IntProperty(name="glfonty", + glfonty: IntProperty(name="glfonty", description="Change font position in Y axis", default=0, min=-3000, max=3000) - gldist = BoolProperty(name="gldist", + gldist: BoolProperty(name="gldist", description="Display distance for this measure", default=True) - glnames = BoolProperty(name="glnames", + glnames: BoolProperty(name="glnames", description="Display text for this measure", default=True) - gltot = EnumProperty(items=(('99', "-", "Select a group for sum"), + gltot: EnumProperty(items=(('99', "-", "Select a group for sum"), ('0', "A", ""), ('1', "B", ""), ('2', "C", ""), @@ -222,74 +220,74 @@ class MeasureitProperties(PropertyGroup): ('25', "Z", "")), name="Sum in Group", description="Add segment length in selected group") - glorto = EnumProperty(items=(('99', "None", ""), + glorto: EnumProperty(items=(('99', "None", ""), ('0', "A", "Point A must use selected point B location"), ('1', "B", "Point B must use selected point A location")), name="Orthogonal", description="Display point selected as orthogonal (select axis to copy)") - glorto_x = BoolProperty(name="ox", + glorto_x: BoolProperty(name="ox", description="Copy X location", default=False) - glorto_y = BoolProperty(name="oy", + glorto_y: BoolProperty(name="oy", description="Copy Y location", default=False) - glorto_z = BoolProperty(name="oz", + glorto_z: BoolProperty(name="oz", description="Copy Z location", default=False) - glarrow_a = EnumProperty(items=(('99', "--", "No arrow"), + glarrow_a: EnumProperty(items=(('99', "--", "No arrow"), ('1', "Line", "The point of the arrow are lines"), ('2', "Triangle", "The point of the arrow is triangle"), ('3', "TShape", "The point of the arrow is a T")), name="A end", description="Add arrows to point A") - glarrow_b = EnumProperty(items=(('99', "--", "No arrow"), + glarrow_b: EnumProperty(items=(('99', "--", "No arrow"), ('1', "Line", "The point of the arrow are lines"), ('2', "Triangle", "The point of the arrow is triangle"), ('3', "TShape", "The point of the arrow is a T")), name="B end", description="Add arrows to point B") - glarrow_s = IntProperty(name="Size", + glarrow_s: IntProperty(name="Size", description="Arrow size", default=15, min=6, max=500) - glarc_full = BoolProperty(name="arcfull", + glarc_full: BoolProperty(name="arcfull", description="Create full circunference", default=False) - glarc_extrad = BoolProperty(name="arcextrad", + glarc_extrad: BoolProperty(name="arcextrad", description="Adapt radio length to arc line", default=True) - glarc_rad = BoolProperty(name="arc rad", + glarc_rad: BoolProperty(name="arc rad", description="Show arc radius", default=True) - glarc_len = BoolProperty(name="arc len", + glarc_len: BoolProperty(name="arc len", description="Show arc length", default=True) - glarc_ang = BoolProperty(name="arc ang", + glarc_ang: BoolProperty(name="arc ang", description="Show arc angle", default=True) - glarc_a = EnumProperty(items=(('99', "--", "No arrow"), + glarc_a: EnumProperty(items=(('99', "--", "No arrow"), ('1', "Line", "The point of the arrow are lines"), ('2', "Triangle", "The point of the arrow is triangle"), ('3', "TShape", "The point of the arrow is a T")), name="Ar end", description="Add arrows to point A") - glarc_b = EnumProperty(items=(('99', "--", "No arrow"), + glarc_b: EnumProperty(items=(('99', "--", "No arrow"), ('1', "Line", "The point of the arrow are lines"), ('2', "Triangle", "The point of the arrow is triangle"), ('3', "TShape", "The point of the arrow is a T")), name="Br end", description="Add arrows to point B") - glarc_s = IntProperty(name="Size", + glarc_s: IntProperty(name="Size", description="Arrow size", default=15, min=6, max=500) - glarc_txradio = StringProperty(name="txradio", + glarc_txradio: StringProperty(name="txradio", description="Text for radius", default="r=") - glarc_txlen = StringProperty(name="txlen", + glarc_txlen: StringProperty(name="txlen", description="Text for length", default="L=") - glarc_txang = StringProperty(name="txang", + glarc_txang: StringProperty(name="txang", description="Text for angle", default="A=") - glcolorarea = FloatVectorProperty(name="glcolorarea", + glcolorarea: FloatVectorProperty(name="glcolorarea", description="Color for the measure of area", default=(0.1, 0.1, 0.1, 1.0), min=0.1, @@ -298,7 +296,7 @@ class MeasureitProperties(PropertyGroup): size=4) # Array of faces - measureit_faces = CollectionProperty(type=MeasureitFaces) + measureit_faces: CollectionProperty(type=MeasureitFaces) # Register @@ -310,10 +308,10 @@ bpy.utils.register_class(MeasureitProperties) # Measureit # ------------------------------------------------------------------ class MeasureContainer(PropertyGroup): - measureit_num = IntProperty(name='Number of measures', min=0, max=1000, default=0, + measureit_num: IntProperty(name='Number of measures', min=0, max=1000, default=0, description='Number total of measureit elements') # Array of segments - measureit_segments = CollectionProperty(type=MeasureitProperties) + measureit_segments: CollectionProperty(type=MeasureitProperties) bpy.utils.register_class(MeasureContainer) @@ -324,12 +322,13 @@ Object.MeasureGenerator = CollectionProperty(type=MeasureContainer) # Define UI class # Measureit # ------------------------------------------------------------------ -class MeasureitEditPanel(Panel): - bl_idname = "measureit.editpanel" - bl_label = "Measureit" +class MEASUREIT_PT_Edit(Panel): + bl_idname = "MEASUREIT_PT_Edit" + bl_label = "Items" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' - bl_category = 'MeasureIt' + bl_category= 'View' + bl_parent_id = 'measureit_main_panel' # ----------------------------------------------------- # Verify if visible @@ -359,12 +358,12 @@ class MeasureitEditPanel(Panel): if 'MeasureGenerator' in context.object: box = layout.box() row = box.row() - row.label(context.object.name) + row.label(text=context.object.name) row = box.row() row.prop(scene, 'measureit_gl_precision', text="Precision") row.prop(scene, 'measureit_units') row = box.row() - row.prop(scene, 'measureit_gl_show_d', text="Distances", toggle=True, icon="ALIGN") + row.prop(scene, 'measureit_gl_show_d', text="Distances", toggle=True, icon="ALIGN_CENTER") row.prop(scene, 'measureit_gl_show_n', text="Texts", toggle=True, icon="FONT_DATA") row = box.row() row.prop(scene, 'measureit_hide_units', text="Hide measurement unit") @@ -372,7 +371,7 @@ class MeasureitEditPanel(Panel): row = box.row() row.prop(scene, 'measureit_scale', text="Scale") if scene.measureit_scale is True: - split = row.split(percentage=0.25, align=False) + split = row.split(factor=0.25, align=False) split.prop(scene, 'measureit_scale_color', text="") split.prop(scene, 'measureit_scale_factor', text="1") row = box.row() @@ -389,7 +388,7 @@ class MeasureitEditPanel(Panel): row = box.row() row.prop(scene, 'measureit_ovr', text="Override") if scene.measureit_ovr is True: - split = row.split(percentage=0.25, align=False) + split = row.split(factor=0.25, align=False) split.prop(scene, 'measureit_ovr_color', text="") split.prop(scene, 'measureit_ovr_width', text="Width") row = box.row() @@ -405,15 +404,15 @@ class MeasureitEditPanel(Panel): # ----------------- if mp.measureit_num > 0: box = layout.box() - row = box.row(True) - row.operator("measureit.expandallsegmentbutton", text="Expand all", icon="ZOOMIN") - row.operator("measureit.collapseallsegmentbutton", text="Collapse all", icon="ZOOMOUT") + row = box.row(align=True) + row.operator("measureit.expandallsegment", text="Expand all", icon="ZOOM_IN") + row.operator("measureit.collapseallsegment", text="Collapse all", icon="ZOOM_OUT") for idx in range(mp.measureit_num): if mp.measureit_segments[idx].glfree is False: add_item(box, idx, mp.measureit_segments[idx]) row = box.row() - row.operator("measureit.deleteallsegmentbutton", text="Delete all", icon="X") + row.operator("measureit.deleteallsegment", text="Delete all", icon="X") # ----------------- # Sum loop segments # ----------------- @@ -476,25 +475,25 @@ class MeasureitEditPanel(Panel): if ac[idx] is True: final += tot[idx] tx_dist = format_distance(fmt, units, tot[idx]) - row = box.row(True) - row.label("Group " + tx[idx] + ":") - row.label(" ") - row.label(tx_dist) + row = box.row(align=True) + row.label(text="Group " + tx[idx] + ":") + row.label(text=" ") + row.label(text=tx_dist) # Grand total - row = box.row(True) - row.label("") - row.label(" ") - row.label("-" * 20) + row = box.row(align=True) + row.label(text="") + row.label(text=" ") + row.label(text="-" * 20) tx_dist = format_distance(fmt, units, final) - row = box.row(True) - row.label("") - row.label(" ") - row.label(tx_dist) + row = box.row(align=True) + row.label(text="") + row.label(text=" ") + row.label(text=tx_dist) # delete all row = box.row() - row.operator("measureit.deleteallsumbutton", text="Delete all", icon="X") + row.operator("measureit.deleteallsum", text="Delete all", icon="X") # ----------------------------------------------------- @@ -502,32 +501,32 @@ class MeasureitEditPanel(Panel): # ----------------------------------------------------- def add_item(box, idx, segment): scene = bpy.context.scene - row = box.row(True) + row = box.row(align=True) if segment.glview is True: icon = "VISIBLE_IPO_ON" else: icon = "VISIBLE_IPO_OFF" row.prop(segment, 'glview', text="", toggle=True, icon=icon) - row.prop(segment, 'gladvance', text="", toggle=True, icon="SCRIPTWIN") + row.prop(segment, 'gladvance', text="", toggle=True, icon="PREFERENCES") if segment.gltype == 20: # Area special - split = row.split(percentage=0.15, align=True) + split = row.split(factor=0.15, align=True) split.prop(segment, 'glcolorarea', text="") - split = split.split(percentage=0.20, align=True) + split = split.split(factor=0.20, align=True) split.prop(segment, 'glcolor', text="") else: - split = row.split(percentage=0.25, align=True) + split = row.split(factor=0.25, align=True) split.prop(segment, 'glcolor', text="") split.prop(segment, 'gltxt', text="") - op = row.operator("measureit.deletesegmentbutton", text="", icon="X") + op = row.operator("measureit.deletesegment", text="", icon="X") op.tag = idx # saves internal data if segment.gladvance is True: - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glfont_size', text="Font") row.prop(segment, 'glfont_align', text="") if segment.glfont_align == 'L': row.prop(segment, 'glfont_rotat', text="Rotate") - row = box.row(True) + row = box.row(align=True) if segment.gltype != 9 and segment.gltype != 10 and segment.gltype != 20: row.prop(segment, 'glspace', text="Distance") row.prop(segment, 'glfontx', text="X") @@ -535,16 +534,16 @@ def add_item(box, idx, segment): # Arrows if segment.gltype != 9 and segment.gltype != 10 and segment.gltype != 20: - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glarrow_a', text="") row.prop(segment, 'glarrow_b', text="") if segment.glarrow_a != '99' or segment.glarrow_b != '99': row.prop(segment, 'glarrow_s', text="Size") if segment.gltype != 2 and segment.gltype != 10: - row = box.row(True) + row = box.row(align=True) if scene.measureit_gl_show_d is True and segment.gltype != 9: - row.prop(segment, 'gldist', text="Distance", toggle=True, icon="ALIGN") + row.prop(segment, 'gldist', text="Distance", toggle=True, icon="ALIGN_CENTER") if scene.measureit_gl_show_n is True: row.prop(segment, 'glnames', text="Text", toggle=True, icon="FONT_DATA") # sum distances @@ -552,11 +551,11 @@ def add_item(box, idx, segment): row.prop(segment, 'gltot', text="Sum") if segment.gltype != 9 and segment.gltype != 10 and segment.gltype != 20: - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glwidth', text="Line") row.prop(segment, 'gldefault', text="Automatic position") if segment.gldefault is False: - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glnormalx', text="X") row.prop(segment, 'glnormaly', text="Y") row.prop(segment, 'glnormalz', text="Z") @@ -565,7 +564,7 @@ def add_item(box, idx, segment): if segment.gltype != 2 and segment.gltype != 9 and segment.gltype != 10 \ and segment.gltype != 11 and segment.gltype != 12 and segment.gltype != 13 \ and segment.gltype != 14 and segment.gltype != 20: - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glocx', text="X", toggle=True) row.prop(segment, 'glocy', text="Y", toggle=True) row.prop(segment, 'glocz', text="Z", toggle=True) @@ -577,28 +576,28 @@ def add_item(box, idx, segment): # ortogonal (only segments) if segment.gltype == 1: if segment.glorto != "99": - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glorto_x', text="X", toggle=True) row.prop(segment, 'glorto_y', text="Y", toggle=True) row.prop(segment, 'glorto_z', text="Z", toggle=True) # Arc special if segment.gltype == 11: - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glarc_rad', text="Radius") row.prop(segment, 'glarc_len', text="Length") row.prop(segment, 'glarc_ang', text="Angle") - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glarc_txradio', text="") row.prop(segment, 'glarc_txlen', text="") row.prop(segment, 'glarc_txang', text="") - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glarc_full', text="Full Circle") if segment.glarc_rad is True: row.prop(segment, 'glarc_extrad', text="Adapt radio") - row = box.row(True) + row = box.row(align=True) row.prop(segment, 'glarc_a', text="") row.prop(segment, 'glarc_b', text="") if segment.glarc_a != '99' or segment.glarc_b != '99': @@ -608,12 +607,12 @@ def add_item(box, idx, segment): # ------------------------------------------------------------------ # Define panel class for main functions. # ------------------------------------------------------------------ -class MeasureitMainPanel(Panel): +class MEASUREIT_PT_Main(Panel): bl_idname = "measureit_main_panel" bl_label = "MeasureIt Tools" bl_space_type = 'VIEW_3D' - bl_region_type = "TOOLS" - bl_category = 'Measureit' + bl_region_type = 'UI' + bl_category= 'View' # ------------------------------ # Draw UI @@ -637,45 +636,45 @@ class MeasureitMainPanel(Panel): icon = "PAUSE" txt = 'Hide' - row.operator("measureit.runopenglbutton", text=txt, icon=icon) + row.operator("measureit.runopengl", text=txt, icon=icon) row.prop(scene, "measureit_gl_ghost", text="", icon='GHOST_ENABLED') # Tools box = layout.box() - box.label("Add Measures") + box.label(text="Add Measures") row = box.row() - row.operator("measureit.addsegmentbutton", text="Segment", icon="ALIGN") + row.operator("measureit.addsegment", text="Segment") row.prop(scene, "measureit_sum", text="Sum") # To origin row = box.row() - op = row.operator("measureit.addsegmentortobutton", text="X", icon="ALIGN") + op = row.operator("measureit.addsegmentorto", text="X") op.tag = 0 # saves internal data - op = row.operator("measureit.addsegmentortobutton", text="Y", icon="ALIGN") + op = row.operator("measureit.addsegmentorto", text="Y") op.tag = 1 # saves internal data - op = row.operator("measureit.addsegmentortobutton", text="Z", icon="ALIGN") + op = row.operator("measureit.addsegmentorto", text="Z") op.tag = 2 # saves internal data row = box.row() - row.operator("measureit.addanglebutton", text="Angle", icon="LINCURVE") - row.operator("measureit.addarcbutton", text="Arc", icon="MAN_ROT") + row.operator("measureit.addangle", text="Angle", icon="LINCURVE") + row.operator("measureit.addarc", text="Arc") row = box.row() - row.operator("measureit.addlabelbutton", text="Label", icon="FONT_DATA") - row.operator("measureit.addnotebutton", text="Annotation", icon="NEW") + row.operator("measureit.addlabel", text="Label", icon="FONT_DATA") + row.operator("measureit.addnote", text="Annotation") row = box.row() - row.operator("measureit.addlinkbutton", text="Link", icon="ROTATECENTER") - row.operator("measureit.addoriginbutton", text="Origin", icon="CURSOR") + row.operator("measureit.addlink", text="Link") + row.operator("measureit.addorigin", text="Origin") row = box.row() - row.operator("measureit.addareabutton", text="Area", icon="MESH_GRID") + row.operator("measureit.addarea", text="Area", icon="MESH_GRID") # ------------------------------ # Debug data # ------------------------------ box = layout.box() - row = box.row(False) + row = box.row(align=False) if scene.measureit_debug is False: row.prop(scene, "measureit_debug", icon="TRIA_RIGHT", text="Mesh Debug", emboss=False) @@ -684,43 +683,43 @@ class MeasureitMainPanel(Panel): text="Mesh Debug", emboss=False) row = box.row() - split = row.split(percentage=0.10, align=True) + split = row.split(factor=0.10, align=True) split.prop(scene, 'measureit_debug_obj_color', text="") split.prop(scene, "measureit_debug_objects", icon="OBJECT_DATA") split.prop(scene, "measureit_debug_object_loc", icon="EMPTY_DATA") row = box.row() - split = row.split(percentage=0.10, align=True) + split = row.split(factor=0.10, align=True) split.prop(scene, 'measureit_debug_vert_color', text="") - split.prop(scene, "measureit_debug_vertices", icon="LOOPSEL") + split.prop(scene, "measureit_debug_vertices", icon="VERTEXSEL") split.prop(scene, "measureit_debug_vert_loc", icon="EMPTY_DATA") if scene.measureit_debug_vert_loc is True: split.prop(scene, 'measureit_debug_vert_loc_toggle', text="") row = box.row() - split = row.split(percentage=0.10, align=True) + split = row.split(factor=0.10, align=True) split.prop(scene, 'measureit_debug_edge_color', text="") - split = split.split(percentage=0.5, align=True) + split = split.split(factor=0.5, align=True) split.prop(scene, "measureit_debug_edges", icon="EDGESEL") row = box.row() - split = row.split(percentage=0.10, align=True) + split = row.split(factor=0.10, align=True) split.prop(scene, 'measureit_debug_face_color', text="") - split = split.split(percentage=0.5, align=True) + split = split.split(factor=0.5, align=True) split.prop(scene, "measureit_debug_faces", icon="FACESEL") row = box.row() - split = row.split(percentage=0.10, align=True) + split = row.split(factor=0.10, align=True) split.prop(scene, 'measureit_debug_norm_color', text="") if scene.measureit_debug_normals is False: - split = split.split(percentage=0.50, align=True) - split.prop(scene, "measureit_debug_normals", icon="MAN_TRANS") + split = split.split(factor=0.50, align=True) + split.prop(scene, "measureit_debug_normals", icon="OBJECT_ORIGIN") else: - split = split.split(percentage=0.5, align=True) - split.prop(scene, "measureit_debug_normals", icon="MAN_TRANS") + split = split.split(factor=0.5, align=True) + split.prop(scene, "measureit_debug_normals", icon="OBJECT_ORIGIN") split.prop(scene, "measureit_debug_normal_size") row = box.row() - split = row.split(percentage=0.10, align=True) + split = row.split(factor=0.10, align=True) split.separator() split.prop(scene, "measureit_debug_normal_details") split.prop(scene, 'measureit_debug_width', text="Thickness") @@ -734,12 +733,13 @@ class MeasureitMainPanel(Panel): # ------------------------------------------------------------------ # Define panel class for conf functions. # ------------------------------------------------------------------ -class MeasureitConfPanel(Panel): +class MEASUREIT_PT_Conf(Panel): bl_idname = "measureit_conf_panel" - bl_label = "MeasureIt Configuration" + bl_label = "Configuration" bl_space_type = 'VIEW_3D' - bl_region_type = "TOOLS" - bl_category = 'Measureit' + bl_region_type = 'UI' + bl_category= 'View' + bl_parent_id = 'measureit_main_panel' bl_options = {'DEFAULT_CLOSED'} # ------------------------------ @@ -752,21 +752,21 @@ class MeasureitConfPanel(Panel): # Configuration data box = layout.box() row = box.row() - split = row.split(percentage=0.2, align=True) - split.label("Text") - split = split.split(percentage=0.2, align=True) + split = row.split(factor=0.2, align=True) + split.label(text="Text") + split = split.split(factor=0.2, align=True) split.prop(scene, "measureit_default_color", text="") split.prop(scene, "measureit_gl_txt", text="") - row = box.row(True) + row = box.row(align=True) row.prop(scene, "measureit_hint_space") row.prop(scene, "measureit_font_align", text="") # Arrow - row = box.row(True) + row = box.row(align=True) row.prop(scene, "measureit_glarrow_a", text="") row.prop(scene, "measureit_glarrow_b", text="") if scene.measureit_glarrow_a != '99' or scene.measureit_glarrow_b != '99': row.prop(scene, "measureit_glarrow_s", text="Size") - row = box.row(True) + row = box.row(align=True) row.prop(scene, "measureit_font_size") if scene.measureit_font_align == 'L': row.prop(scene, "measureit_font_rotation", text="Rotate") @@ -775,12 +775,13 @@ class MeasureitConfPanel(Panel): # ------------------------------------------------------------------ # Define panel class for render functions. # ------------------------------------------------------------------ -class MeasureitRenderPanel(Panel): +class MEASUREIT_PT_Render(Panel): bl_idname = "measureit_render_panel" - bl_label = "MeasureIt Render" + bl_label = "Render" bl_space_type = 'VIEW_3D' - bl_region_type = "TOOLS" - bl_category = 'Measureit' + bl_region_type = 'UI' + bl_category= 'View' + bl_parent_id = 'measureit_main_panel' bl_options = {'DEFAULT_CLOSED'} # ------------------------------ @@ -795,7 +796,7 @@ class MeasureitRenderPanel(Panel): row = box.row() row.prop(scene, "measureit_render_type") row = box.row() - row.operator("measureit.rendersegmentbutton", icon='SCRIPT') + row.operator("measureit.rendersegment", icon='SCRIPT') row = box.row() row.prop(scene, "measureit_render", text="Save render image") row = box.row() @@ -811,11 +812,10 @@ class MeasureitRenderPanel(Panel): # Defines button that adds a measure segment # # ------------------------------------------------------------- -class AddSegmentButton(Operator): - bl_idname = "measureit.addsegmentbutton" +class MEASUREIT_OT_AddSegment(Operator): + bl_idname = "measureit.addsegment" bl_label = "Add" bl_description = "(EDITMODE only) Add a new measure segment between 2 vertices (select 2 vertices or more)" - bl_category = 'Measureit' # ------------------------------ # Poll @@ -900,11 +900,10 @@ class AddSegmentButton(Operator): # Defines button that adds an area measure # # ------------------------------------------------------------- -class AddAreaButton(Operator): - bl_idname = "measureit.addareabutton" +class MEASUREIT_OT_AddArea(Operator): + bl_idname = "measureit.addarea" bl_label = "Area" bl_description = "(EDITMODE only) Add a new measure for area (select 1 o more faces)" - bl_category = 'Measureit' # ------------------------------ # Poll @@ -988,13 +987,12 @@ class AddAreaButton(Operator): # Defines button that adds a measure segment to x/y/z origin # # ------------------------------------------------------------- -class AddSegmentOrtoButton(Operator): - bl_idname = "measureit.addsegmentortobutton" +class MEASUREIT_OT_AddSegmentOrto(Operator): + bl_idname = "measureit.addsegmentorto" bl_label = "Add" bl_description = "(EDITMODE only) Add a new measure segment from vertex to object origin for one " \ "axis (select 1 vertex)" - bl_category = 'Measureit' - tag = IntProperty() + tag: IntProperty() # ------------------------------ # Poll @@ -1079,11 +1077,10 @@ class AddSegmentOrtoButton(Operator): # Defines button that adds an angle measure # # ------------------------------------------------------------- -class AddAngleButton(Operator): - bl_idname = "measureit.addanglebutton" +class MEASUREIT_OT_AddAngle(Operator): + bl_idname = "measureit.addangle" bl_label = "Angle" bl_description = "(EDITMODE only) Add a new angle measure (select 3 vertices, 2nd is angle vertex)" - bl_category = 'Measureit' # ------------------------------ # Poll @@ -1160,12 +1157,11 @@ class AddAngleButton(Operator): # Defines button that adds an arc measure # # ------------------------------------------------------------- -class AddArcButton(Operator): - bl_idname = "measureit.addarcbutton" +class MEASUREIT_OT_AddArc(Operator): + bl_idname = "measureit.addarc" bl_label = "Angle" bl_description = "(EDITMODE only) Add a new arc measure (select 3 vertices of the arc," \ " vertices 1st and 3rd are arc extremes)" - bl_category = 'Measureit' # ------------------------------ # Poll @@ -1245,11 +1241,10 @@ class AddArcButton(Operator): # Defines button that adds a label segment # # ------------------------------------------------------------- -class AddLabelButton(Operator): - bl_idname = "measureit.addlabelbutton" +class MEASUREIT_OT_AddLabel(Operator): + bl_idname = "measureit.addlabel" bl_label = "Add" bl_description = "(EDITMODE only) Add a new measure label (select 1 vertex)" - bl_category = 'Measureit' # ------------------------------ # Poll @@ -1328,12 +1323,11 @@ class AddLabelButton(Operator): # Defines button that adds a link # # ------------------------------------------------------------- -class AddLinkButton(Operator): - bl_idname = "measureit.addlinkbutton" +class MEASUREIT_OT_AddLink(Operator): + bl_idname = "measureit.addlink" bl_label = "Add" bl_description = "(OBJECT mode only) Add a new measure between objects (select 2 " \ "objects and optionally 1 or 2 vertices)" - bl_category = 'Measureit' # ------------------------------ # Poll @@ -1344,7 +1338,7 @@ class AddLinkButton(Operator): if o is None: return False else: - if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LAMP": + if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LIGHT": if bpy.context.mode == 'OBJECT': return True else: @@ -1475,11 +1469,10 @@ class AddLinkButton(Operator): # Defines button that adds an origin segment # # ------------------------------------------------------------- -class AddOriginButton(Operator): - bl_idname = "measureit.addoriginbutton" +class MEASUREIT_OT_AddOrigin(Operator): + bl_idname = "measureit.addorigin" bl_label = "Add" bl_description = "(OBJECT mode only) Add a new measure to origin (select object and optionally 1 vertex)" - bl_category = 'Measureit' # ------------------------------ # Poll @@ -1490,7 +1483,7 @@ class AddOriginButton(Operator): if o is None: return False else: - if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LAMP": + if o.type == "MESH" or o.type == "EMPTY" or o.type == "CAMERA" or o.type == "LIGHT": if bpy.context.mode == 'OBJECT': return True else: @@ -1574,12 +1567,11 @@ class AddOriginButton(Operator): # Defines button that deletes a measure segment # # ------------------------------------------------------------- -class DeleteSegmentButton(Operator): - bl_idname = "measureit.deletesegmentbutton" +class MEASUREIT_OT_DeleteSegment(Operator): + bl_idname = "measureit.deletesegment" bl_label = "Delete" bl_description = "Delete a measure" - bl_category = 'Measureit' - tag = IntProperty() + tag: IntProperty() # ------------------------------ # Execute button action @@ -1608,12 +1600,11 @@ class DeleteSegmentButton(Operator): # Defines button that deletes all measure segments # # ------------------------------------------------------------- -class DeleteAllSegmentButton(Operator): - bl_idname = "measureit.deleteallsegmentbutton" +class MEASUREIT_OT_DeleteAllSegment(Operator): + bl_idname = "measureit.deleteallsegment" bl_label = "Delete" bl_description = "Delete all measures (it cannot be undone)" - bl_category = 'Measureit' - tag = IntProperty() + tag: IntProperty() # ------------------------------ # Execute button action @@ -1643,12 +1634,11 @@ class DeleteAllSegmentButton(Operator): # Defines button that deletes all measure segment sums # # ------------------------------------------------------------- -class DeleteAllSumButton(Operator): - bl_idname = "measureit.deleteallsumbutton" +class MEASUREIT_OT_DeleteAllSum(Operator): + bl_idname = "measureit.deleteallsum" bl_label = "Delete" bl_description = "Delete all sum groups" - bl_category = 'Measureit' - tag = IntProperty() + tag: IntProperty() # ------------------------------ # Execute button action @@ -1669,12 +1659,11 @@ class DeleteAllSumButton(Operator): # Defines button that expands all measure segments # # ------------------------------------------------------------- -class ExpandAllSegmentButton(Operator): - bl_idname = "measureit.expandallsegmentbutton" +class MEASUREIT_OT_ExpandAllSegment(Operator): + bl_idname = "measureit.expandallsegment" bl_label = "Expand" bl_description = "Expand all measure properties" - bl_category = 'Measureit' - tag = IntProperty() + tag: IntProperty() # ------------------------------ # Execute button action @@ -1700,12 +1689,11 @@ class ExpandAllSegmentButton(Operator): # Defines button that collapses all measure segments # # ------------------------------------------------------------- -class CollapseAllSegmentButton(Operator): - bl_idname = "measureit.collapseallsegmentbutton" +class MEASUREIT_OT_CollapseAllSegment(Operator): + bl_idname = "measureit.collapseallsegment" bl_label = "Collapse" bl_description = "Collapses all measure properties" - bl_category = 'Measureit' - tag = IntProperty() + tag: IntProperty() # ------------------------------ # Execute button action @@ -1731,12 +1719,11 @@ class CollapseAllSegmentButton(Operator): # Defines button for render option # # ------------------------------------------------------------- -class RenderSegmentButton(Operator): - bl_idname = "measureit.rendersegmentbutton" +class MEASUREIT_OT_RenderSegment(Operator): + bl_idname = "measureit.rendersegment" bl_label = "Render" bl_description = "Create a render image with measures. Use UV/Image editor to view image generated" - bl_category = 'Measureit' - tag = IntProperty() + tag: IntProperty() # ------------------------------ # Execute button action @@ -1753,75 +1740,22 @@ class RenderSegmentButton(Operator): self.report({'ERROR'}, camera_msg) return {'FINISHED'} # ----------------------------- - # Use default render + # Frame render # ----------------------------- if scene.measureit_render_type == "1": # noinspection PyBroadException - try: - result = bpy.data.images['Render Result'] - bpy.ops.render.render() - except: - bpy.ops.render.render() - print("MeasureIt: Using current render image on buffer") if render_main(self, context) is True: self.report({'INFO'}, msg) - - # ----------------------------- - # OpenGL image - # ----------------------------- - if scene.measureit_render_type == "2": - self.set_camera_view() - self.set_only_render(True) - - print("MeasureIt: Rendering opengl image") - bpy.ops.render.opengl() - if render_main(self, context) is True: - self.report({'INFO'}, msg) - - self.set_only_render(False) - - # ----------------------------- - # OpenGL Animation - # ----------------------------- - if scene.measureit_render_type == "3": - oldframe = scene.frame_current - self.set_camera_view() - self.set_only_render(True) - flag = False - # loop frames - for frm in range(scene.frame_start, scene.frame_end + 1): - scene.frame_set(frm) - print("MeasureIt: Rendering opengl frame %04d" % frm) - bpy.ops.render.opengl() - flag = render_main(self, context, True) - if flag is False: - break - - self.set_only_render(False) - scene.frame_current = oldframe - if flag is True: - self.report({'INFO'}, msg) - - # ----------------------------- - # Image - # ----------------------------- - if scene.measureit_render_type == "4": - print("MeasureIt: Rendering image") - bpy.ops.render.render() - if render_main(self, context) is True: - self.report({'INFO'}, msg) - # ----------------------------- # Animation # ----------------------------- - if scene.measureit_render_type == "5": + if scene.measureit_render_type == "2": oldframe = scene.frame_current flag = False # loop frames for frm in range(scene.frame_start, scene.frame_end + 1): scene.frame_set(frm) print("MeasureIt: Rendering frame %04d" % frm) - bpy.ops.render.render() flag = render_main(self, context, True) if flag is False: break @@ -1866,12 +1800,11 @@ class RenderSegmentButton(Operator): # Defines a new note # # ------------------------------------------------------------- -class AddNoteButton(Operator): - bl_idname = "measureit.addnotebutton" +class MEASUREIT_OT_AddNote(Operator): + bl_idname = "measureit.addnote" bl_label = "Note" bl_description = "(OBJECT mode only) Add a new annotation" - bl_category = 'Measureit' - tag = IntProperty() + tag: IntProperty() # ------------------------------ # Poll @@ -1892,7 +1825,7 @@ class AddNoteButton(Operator): bpy.ops.object.empty_add(type='PLAIN_AXES') myempty = bpy.data.objects[bpy.context.active_object.name] myempty.location = bpy.context.scene.cursor_location - myempty.empty_draw_size = 0.01 + myempty.empty_display_size = 0.01 myempty.name = "Annotation" # Add properties scene = context.scene @@ -1936,11 +1869,10 @@ class AddNoteButton(Operator): # Defines button that enables/disables the tip display # # ------------------------------------------------------------- -class RunHintDisplayButton(Operator): - bl_idname = "measureit.runopenglbutton" +class MEASUREIT_OT_RunHintDisplay(Operator): + bl_idname = "measureit.runopengl" bl_label = "Display hint data manager" bl_description = "Main control for enabling or disabling the display of measurements in the viewport" - bl_category = 'Measureit' _handle = None # keep function handler @@ -1949,8 +1881,8 @@ class RunHintDisplayButton(Operator): # ---------------------------------- @staticmethod def handle_add(self, context): - if RunHintDisplayButton._handle is None: - RunHintDisplayButton._handle = SpaceView3D.draw_handler_add(draw_callback_px, (self, context), + if MEASUREIT_OT_RunHintDisplay._handle is None: + MEASUREIT_OT_RunHintDisplay._handle = SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.measureit_run_opengl = True @@ -1961,9 +1893,9 @@ class RunHintDisplayButton(Operator): # noinspection PyUnusedLocal @staticmethod def handle_remove(self, context): - if RunHintDisplayButton._handle is not None: - SpaceView3D.draw_handler_remove(RunHintDisplayButton._handle, 'WINDOW') - RunHintDisplayButton._handle = None + if MEASUREIT_OT_RunHintDisplay._handle is not None: + SpaceView3D.draw_handler_remove(MEASUREIT_OT_RunHintDisplay._handle, 'WINDOW') + MEASUREIT_OT_RunHintDisplay._handle = None context.window_manager.measureit_run_opengl = False # ------------------------------ @@ -2011,22 +1943,9 @@ def draw_main(context): rv3d = context.space_data.region_quadviews[i] scene = bpy.context.scene - local_view = context.area.spaces.active.local_view is not None - layers = [] - if local_view is False: - # Get visible layers - if bpy.context.space_data.lock_camera_and_layers is True: - for x in range(20): - if bpy.context.scene.layers[x] is True: - layers.append(x) - else: - # Lock disabled, use view dependent visible layers - for x in range(20): - if bpy.context.space_data.layers[x] is True: - layers.append(x) # Display selected or all - if scene.measureit_gl_ghost is False or local_view is True: + if scene.measureit_gl_ghost is False: objlist = context.selected_objects else: objlist = context.scene.objects @@ -2037,20 +1956,11 @@ def draw_main(context): # Generate all OpenGL calls for measures # --------------------------------------- for myobj in objlist: - if myobj.hide is False: + if myobj.visible_get() is True: if 'MeasureGenerator' in myobj: - if local_view is False: - # verify visible layer - for x in range(20): - if myobj.layers[x] is True and x in layers: - op = myobj.MeasureGenerator[0] - draw_segments(context, myobj, op, region, rv3d) - break - else: - # Layer check not needed here, selected objects are not - # added to context.selected_objects if in disabled layers - op = myobj.MeasureGenerator[0] - draw_segments(context, myobj, op, region, rv3d) + op = myobj.MeasureGenerator[0] + draw_segments(context, myobj, op, region, rv3d) + # --------------------------------------- # Generate all OpenGL calls for debug # --------------------------------------- @@ -2075,7 +1985,6 @@ def draw_main(context): # ----------------------- bgl.glLineWidth(1) bgl.glDisable(bgl.GL_BLEND) - bgl.glColor4f(0.0, 0.0, 0.0, 1.0) # ------------------------------------------------------------- @@ -2118,7 +2027,7 @@ def get_selected_vertex(myobject): # meshes # -------------------- oldobj = bpy.context.object - bpy.context.scene.objects.active = myobject + bpy.context.view_layer.objects.active = myobject flag = False if myobject.mode != 'EDIT': bpy.ops.object.mode_set(mode='EDIT') @@ -2133,7 +2042,7 @@ def get_selected_vertex(myobject): if flag is True: bpy.ops.object.editmode_toggle() # Back context object - bpy.context.scene.objects.active = oldobj + bpy.context.view_layer.objects.active = oldobj # if select all vertices, then use origin if tv == len(mylist): @@ -2154,7 +2063,7 @@ def get_selected_vertex_history(myobject): # meshes # -------------------- oldobj = bpy.context.object - bpy.context.scene.objects.active = myobject + bpy.context.view_layer.objects.active = myobject flag = False if myobject.mode != 'EDIT': bpy.ops.object.mode_set(mode='EDIT') @@ -2167,7 +2076,7 @@ def get_selected_vertex_history(myobject): if flag is True: bpy.ops.object.editmode_toggle() # Back context object - bpy.context.scene.objects.active = oldobj + bpy.context.view_layer.objects.active = oldobj return mylist @@ -2184,7 +2093,7 @@ def get_smart_selected(myobject): # meshes # -------------------- oldobj = bpy.context.object - bpy.context.scene.objects.active = myobject + bpy.context.view_layer.objects.active = myobject flag = False if myobject.mode != 'EDIT': bpy.ops.object.mode_set(mode='EDIT') @@ -2199,7 +2108,7 @@ def get_smart_selected(myobject): if flag is True: bpy.ops.object.editmode_toggle() # Back context object - bpy.context.scene.objects.active = oldobj + bpy.context.view_layer.objects.active = oldobj return mylist @@ -2216,7 +2125,7 @@ def get_selected_faces(myobject): # meshes # -------------------- oldobj = bpy.context.object - bpy.context.scene.objects.active = myobject + bpy.context.view_layer.objects.active = myobject flag = False if myobject.mode != 'EDIT': bpy.ops.object.mode_set(mode='EDIT') @@ -2234,6 +2143,6 @@ def get_selected_faces(myobject): if flag is True: bpy.ops.object.editmode_toggle() # Back context object - bpy.context.scene.objects.active = oldobj + bpy.context.view_layer.objects.active = oldobj return mylist diff --git a/measureit/measureit_render.py b/measureit/measureit_render.py index d5eb6430..dd7826a4 100644 --- a/measureit/measureit_render.py +++ b/measureit/measureit_render.py @@ -25,7 +25,7 @@ # ---------------------------------------------------------- # noinspection PyUnresolvedReferences import bpy -# noinspection PyUnresolvedReferences +import gpu import bgl # noinspection PyUnresolvedReferences import blf @@ -46,264 +46,111 @@ from .measureit_geometry import * # # ------------------------------------------------------------- def render_main(self, context, animation=False): - # noinspection PyBroadException,PyBroadException # Save old info settings = bpy.context.scene.render.image_settings depth = settings.color_depth settings.color_depth = '8' - # noinspection PyBroadException - try: - # Get visible layers - layers = [] - scene = context.scene - for x in range(20): - if scene.layers[x] is True: - layers.append(x) - - # Get object list - objlist = context.scene.objects - # -------------------- - # Get resolution - # -------------------- - scene = bpy.context.scene - render_scale = scene.render.resolution_percentage / 100 - width = int(scene.render.resolution_x * render_scale) - height = int(scene.render.resolution_y * render_scale) - # --------------------------------------- - # Get output path - # --------------------------------------- - temp_path = path.realpath(bpy.app.tempdir) - if len(temp_path) > 0: - outpath = path.join(temp_path, "measureit_tmp_render.png") - else: - self.report({'ERROR'}, - "MeasureIt: Unable to save temporary render image. Define a valid temp path") - settings.color_depth = depth - return False - - # Get Render Image - img = get_render_image(outpath) - if img is None: - self.report({'ERROR'}, - "MeasureIt: Unable to save temporary render image. Define a valid temp path") - settings.color_depth = depth - return False + # Get object list + scene = context.scene + objlist = context.scene.objects + # -------------------- + # Get resolution + # -------------------- + render_scale = scene.render.resolution_percentage / 100 + width = int(scene.render.resolution_x * render_scale) + height = int(scene.render.resolution_y * render_scale) + + # -------------------------------------- + # Loop to draw all lines in Offsecreen + # -------------------------------------- + offscreen = gpu.types.GPUOffScreen(width, height) + view_matrix = Matrix([ + [2 / width, 0, 0, -1], + [0, 2 / height, 0, -1], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + + with offscreen.bind(): + bgl.glClear(bgl.GL_COLOR_BUFFER_BIT) + gpu.matrix.reset() + gpu.matrix.load_matrix(view_matrix) + gpu.matrix.load_projection_matrix(Matrix.Identity(4)) # ----------------------------- - # Calculate rows and columns + # Loop to draw all objects # ----------------------------- - tile_x = 240 - tile_y = 216 - row_num = ceil(height / tile_y) - col_num = ceil(width / tile_x) - print("MeasureIt: Image divided in " + str(row_num) + "x" + str(col_num) + " tiles") - - # pixels out of visible area - cut4 = (col_num * tile_x * 4) - width * 4 # pixels aout of drawing area - totpixel4 = width * height * 4 # total pixels RGBA - - viewport_info = bgl.Buffer(bgl.GL_INT, 4) - bgl.glGetIntegerv(bgl.GL_VIEWPORT, viewport_info) - - # Load image on memory - img.gl_load(0, bgl.GL_NEAREST, bgl.GL_NEAREST) - tex = img.bindcode[0] - - # -------------------------------------------- - # Create output image (to apply texture) - # -------------------------------------------- - if "measureit_output" in bpy.data.images: - out_img = bpy.data.images["measureit_output"] - if out_img is not None: - bpy.data.images.remove(out_img) - - out = bpy.data.images.new("measureit_output", width, height) - tmp_pixels = [1] * totpixel4 - - # -------------------------------- - # Loop for all tiles - # -------------------------------- - for row in range(row_num): - for col in range(col_num): - buffer = bgl.Buffer(bgl.GL_FLOAT, width * height * 4) - bgl.glDisable(bgl.GL_SCISSOR_TEST) # if remove this line, get blender screenshot not image - bgl.glViewport(0, 0, tile_x, tile_y) - - bgl.glMatrixMode(bgl.GL_PROJECTION) - bgl.glLoadIdentity() - - # defines ortographic view for single tile - x1 = tile_x * col - y1 = tile_y * row - bgl.gluOrtho2D(x1, x1 + tile_x, y1, y1 + tile_y) - - # Clear - bgl.glClearColor(0.0, 0.0, 0.0, 0.0) - bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT) - - bgl.glEnable(bgl.GL_TEXTURE_2D) - bgl.glBindTexture(bgl.GL_TEXTURE_2D, tex) - - # defines drawing area - bgl.glBegin(bgl.GL_QUADS) - - bgl.glColor3f(1.0, 1.0, 1.0) - bgl.glTexCoord2f(0.0, 0.0) - bgl.glVertex2f(0.0, 0.0) - - bgl.glTexCoord2f(1.0, 0.0) - bgl.glVertex2f(width, 0.0) - - bgl.glTexCoord2f(1.0, 1.0) - bgl.glVertex2f(width, height) - - bgl.glTexCoord2f(0.0, 1.0) - bgl.glVertex2f(0.0, height) - - bgl.glEnd() - - # ----------------------------- - # Loop to draw all lines - # ----------------------------- - for myobj in objlist: - if myobj.hide is False: - if 'MeasureGenerator' in myobj: - # verify visible layer - for x in range(20): - if myobj.layers[x] is True: - if x in layers: - op = myobj.MeasureGenerator[0] - draw_segments(context, myobj, op, None, None) - break - - # ----------------------------- - # Loop to draw all debug - # ----------------------------- - if scene.measureit_debug is True: - selobj = bpy.context.selected_objects - for myobj in selobj: - if scene.measureit_debug_objects is True: - draw_object(context, myobj, None, None) - elif scene.measureit_debug_object_loc is True: - draw_object(context, myobj, None, None) - if scene.measureit_debug_vertices is True: - draw_vertices(context, myobj, None, None) - elif scene.measureit_debug_vert_loc is True: - draw_vertices(context, myobj, None, None) - if scene.measureit_debug_edges is True: - draw_edges(context, myobj, None, None) - if scene.measureit_debug_faces is True or scene.measureit_debug_normals is True: - draw_faces(context, myobj, None, None) - - if scene.measureit_rf is True: - bgl.glColor3f(1.0, 1.0, 1.0) - rfcolor = scene.measureit_rf_color - rfborder = scene.measureit_rf_border - rfline = scene.measureit_rf_line - - bgl.glLineWidth(rfline) - bgl.glColor4f(rfcolor[0], rfcolor[1], rfcolor[2], rfcolor[3]) - - x1 = rfborder - x2 = width - rfborder - y1 = int(ceil(rfborder / (width / height))) - y2 = height - y1 - draw_rectangle((x1, y1), (x2, y2)) - - # -------------------------------- - # copy pixels to temporary area - # -------------------------------- - bgl.glFinish() - bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_FLOAT, buffer) # read image data - for y in range(tile_y): - # final image pixels position - p1 = (y * width * 4) + (row * tile_y * width * 4) + (col * tile_x * 4) - p2 = p1 + (tile_x * 4) - # buffer pixels position - b1 = y * width * 4 - b2 = b1 + (tile_x * 4) - - if p1 < totpixel4: # avoid pixel row out of area - if col == col_num - 1: # avoid pixel columns out of area - p2 -= cut4 - b2 -= cut4 - - tmp_pixels[p1:p2] = buffer[b1:b2] - - # ----------------------- - # Copy temporary to final - # ----------------------- - out.pixels = tmp_pixels[:] # Assign image data - img.gl_free() # free opengl image memory - - # delete image - bpy.data.images.remove(img) - # remove temp file - remove(outpath) - # reset - bgl.glEnable(bgl.GL_SCISSOR_TEST) - # ----------------------- - # restore opengl defaults - # ----------------------- - bgl.glLineWidth(1) - bgl.glDisable(bgl.GL_BLEND) - bgl.glColor4f(0.0, 0.0, 0.0, 1.0) - # Saves image - if out is not None and (scene.measureit_render is True or animation is True): - ren_path = bpy.context.scene.render.filepath - filename = "mit_frame" - if len(ren_path) > 0: - if ren_path.endswith(path.sep): - initpath = path.realpath(ren_path) + path.sep - else: - (initpath, filename) = path.split(ren_path) - - ftxt = "%04d" % scene.frame_current - outpath = path.realpath(path.join(initpath, filename + ftxt + ".png")) - - save_image(self, outpath, out) - - settings.color_depth = depth - return True - - except: - settings.color_depth = depth - print("Unexpected error:" + str(exc_info())) - self.report({'ERROR'}, "MeasureIt: Unable to create render image. Be sure the output render path is correct") - return False - - -# -------------------------------------------------------------------- -# Get the final render image and return as image object -# -# return None if no render available -# -------------------------------------------------------------------- -def get_render_image(outpath): - saved = False - # noinspection PyBroadException - try: - # noinspection PyBroadException - try: - result = bpy.data.images['Render Result'] - if result.has_data is False: - # this save produce to fill data image - result.save_render(outpath) - saved = True - except: - print("No render image found") - return None - - # Save and reload - if saved is False: - result.save_render(outpath) - - img = img_utils.load_image(outpath) - - return img - except: - print("Unexpected render image error") - return None + for myobj in objlist: + if myobj.visible_get() is True: + if 'MeasureGenerator' in myobj: + op = myobj.MeasureGenerator[0] + draw_segments(context, myobj, op, None, None) + # ----------------------------- + # Loop to draw all debug + # ----------------------------- + if scene.measureit_debug is True: + selobj = bpy.context.selected_objects + for myobj in selobj: + if scene.measureit_debug_objects is True: + draw_object(context, myobj, None, None) + elif scene.measureit_debug_object_loc is True: + draw_object(context, myobj, None, None) + if scene.measureit_debug_vertices is True: + draw_vertices(context, myobj, None, None) + elif scene.measureit_debug_vert_loc is True: + draw_vertices(context, myobj, None, None) + if scene.measureit_debug_edges is True: + draw_edges(context, myobj, None, None) + if scene.measureit_debug_faces is True or scene.measureit_debug_normals is True: + draw_faces(context, myobj, None, None) + # ----------------------------- + # Draw a rectangle frame + # ----------------------------- + if scene.measureit_rf is True: + rfcolor = scene.measureit_rf_color + rfborder = scene.measureit_rf_border + rfline = scene.measureit_rf_line + + bgl.glLineWidth(rfline) + x1 = rfborder + x2 = width - rfborder + y1 = int(ceil(rfborder / (width / height))) + y2 = height - y1 + draw_rectangle((x1, y1), (x2, y2), rfcolor) + + buffer = bgl.Buffer(bgl.GL_BYTE, width * height * 4) + bgl.glReadBuffer(bgl.GL_COLOR_ATTACHMENT0) + bgl.glReadPixels(0, 0, width, height, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer) + + offscreen.free() + + # ----------------------------- + # Create image + # ----------------------------- + image_name = "measureit_output" + if not image_name in bpy.data.images: + bpy.data.images.new(image_name, width, height) + + image = bpy.data.images[image_name] + image.scale(width, height) + image.pixels = [v / 255 for v in buffer] + + # Saves image + if image is not None and (scene.measureit_render is True or animation is True): + ren_path = bpy.context.scene.render.filepath + filename = "mit_frame" + if len(ren_path) > 0: + if ren_path.endswith(path.sep): + initpath = path.realpath(ren_path) + path.sep + else: + (initpath, filename) = path.split(ren_path) + + ftxt = "%04d" % scene.frame_current + outpath = path.realpath(path.join(initpath, filename + ftxt + ".png")) + save_image(self, outpath, image) + + # restore default value + settings.color_depth = depth # ------------------------------------- diff --git a/mesh_carver.py b/mesh_carver.py index 74624abd..7d453ad8 100644 --- a/mesh_carver.py +++ b/mesh_carver.py @@ -1618,7 +1618,7 @@ def update_bevel(context): mod.limit_method = 'WEIGHT' mod.width = 0.01 mod.profile = 0.699099 - mod.use_clamp_overlap = False + mod.use_clight_overlap = False mod.segments = 3 mod.loop_slide = False @@ -1676,7 +1676,7 @@ def CreateBevel(context, CurrentObject): mod.limit_method = 'WEIGHT' mod.width = 0.01 mod.profile = 0.699099 - mod.use_clamp_overlap = False + mod.use_clight_overlap = False mod.segments = 3 mod.loop_slide = False @@ -1707,7 +1707,7 @@ def Picking(context, event): if obj.type == 'MESH': yield (obj, obj.matrix_world.copy()) - if obj.dupli_type != 'NONE': + if obj.instance_type != 'NONE': obj.dupli_list_create(scene) for dob in obj.dupli_list: obj_dupli = dob.object @@ -1964,12 +1964,12 @@ def duplicateObject(self): ob_new.rotation_quaternion = qRot ob_new.rotation_mode = 'XYZ' - if (ob_new.draw_type == "WIRE") and (self.BrushSolidify is False): + if (ob_new.display_type == "WIRE") and (self.BrushSolidify is False): ob_new.hide = True if self.BrushSolidify: - ob_new.draw_type = "SOLID" - ob_new.show_x_ray = False + ob_new.display_type = "SOLID" + ob_new.show_in_front = False for o in bpy.context.selected_objects: UndoAdd(self, "DUPLICATE", o) @@ -2070,20 +2070,20 @@ def boolean_operation(bool_type="DIFFERENCE"): ) BoolMod.object = bpy.context.selected_objects[sel_index] BoolMod.operation = bool_type - bpy.context.selected_objects[sel_index].draw_type = 'WIRE' + bpy.context.selected_objects[sel_index].display_type = 'WIRE' def Rebool(context, self): LastObj = context.active_object Brush = context.selected_objects[0] - Brush.draw_type = "WIRE" + Brush.display_type = "WIRE" obj = context.selected_objects[1] bpy.ops.object.select_all(action='TOGGLE') context.scene.objects.active = obj - obj.draw_type = "SOLID" + obj.display_type = "SOLID" obj.select = True bpy.ops.object.duplicate_move( OBJECT_OT_duplicate={ @@ -2124,7 +2124,7 @@ def Rebool(context, self): mb.show_viewport = False if self.ObjectBrush or self.ProfileBrush: - LastObjectCreated.show_x_ray = False + LastObjectCreated.show_in_front = False try: bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_SOLIDIFY") except: @@ -2177,7 +2177,7 @@ def createMeshFromData(self): scn.objects.active = ob ob.select = True ob.location = Vector((10000.0, 0.0, 0.0)) - ob.draw_type = "WIRE" + ob.display_type = "WIRE" self.SolidifyPossible = True else: @@ -2390,7 +2390,7 @@ class Carver(bpy.types.Operator): self.ProfileBrush.select = True context.scene.objects.active = self.ProfileBrush # Set xRay - self.ProfileBrush.show_x_ray = True + self.ProfileBrush.show_in_front = True bpy.ops.object.modifier_add(type='SOLIDIFY') context.object.modifiers["Solidify"].name = "CT_SOLIDIFY" @@ -2407,8 +2407,8 @@ class Carver(bpy.types.Operator): self.ObjectBrush.scale = self.InitBrushScale self.ObjectBrush.rotation_quaternion = self.InitBrushQRotation self.ObjectBrush.rotation_euler = self.InitBrushERotation - self.ObjectBrush.draw_type = self.ObjectBrush_DT - self.ObjectBrush.show_x_ray = self.XRay + self.ObjectBrush.display_type = self.ObjectBrush_DT + self.ObjectBrush.show_in_front = self.XRay # Remove solidify modifier Selection_Save(self) @@ -2430,7 +2430,7 @@ class Carver(bpy.types.Operator): self.ObjectBrush.select = True context.scene.objects.active = self.ObjectBrush # Set xRay - self.ObjectBrush.show_x_ray = True + self.ObjectBrush.show_in_front = True bpy.ops.object.modifier_add(type='SOLIDIFY') context.object.modifiers["Solidify"].name = "CT_SOLIDIFY" context.object.modifiers["CT_SOLIDIFY"].thickness = 0.1 @@ -2529,12 +2529,12 @@ class Carver(bpy.types.Operator): self.ObjectBrush.select = True context.scene.objects.active = self.ObjectBrush # Active le xray - self.ObjectBrush.show_x_ray = True + self.ObjectBrush.show_in_front = True else: self.ProfileBrush.select = True context.scene.objects.active = self.ProfileBrush # Active le xray - self.ProfileBrush.show_x_ray = True + self.ProfileBrush.show_in_front = True bpy.ops.object.modifier_add(type='SOLIDIFY') context.object.modifiers["Solidify"].name = "CT_SOLIDIFY" @@ -2773,8 +2773,8 @@ class Carver(bpy.types.Operator): self.ObjectBrush.scale = self.InitBrushScale self.ObjectBrush.rotation_quaternion = self.InitBrushQRotation self.ObjectBrush.rotation_euler = self.InitBrushERotation - self.ObjectBrush.draw_type = self.ObjectBrush_DT - self.ObjectBrush.show_x_ray = self.XRay + self.ObjectBrush.display_type = self.ObjectBrush_DT + self.ObjectBrush.show_in_front = self.XRay # remove solidify Selection_Save(self) @@ -2844,8 +2844,8 @@ class Carver(bpy.types.Operator): self.ObjectBrush.scale = self.InitBrushScale self.ObjectBrush.rotation_quaternion = self.InitBrushQRotation self.ObjectBrush.rotation_euler = self.InitBrushERotation - self.ObjectBrush.draw_type = self.ObjectBrush_DT - self.ObjectBrush.show_x_ray = self.XRay + self.ObjectBrush.display_type = self.ObjectBrush_DT + self.ObjectBrush.show_in_front = self.XRay # Remove solidify modifier Selection_Save(self) @@ -2884,7 +2884,7 @@ class Carver(bpy.types.Operator): traceback.print_exc() context.window.cursor_modal_set("DEFAULT") - context.area.header_text_set("") + context.area.header_text_set(None) bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') self.report({'WARNING'}, @@ -3089,8 +3089,8 @@ class Carver(bpy.types.Operator): self.InitBrushScale = self.ObjectBrush.scale.copy() self.InitBrushQRotation = self.ObjectBrush.rotation_quaternion.copy() self.InitBrushERotation = self.ObjectBrush.rotation_euler.copy() - self.ObjectBrush_DT = self.ObjectBrush.draw_type - self.XRay = self.ObjectBrush.show_x_ray + self.ObjectBrush_DT = self.ObjectBrush.display_type + self.XRay = self.ObjectBrush.show_in_front # Test if flat object z = self.ObjectBrush.data.vertices[0].co.z ErrorMarge = 0.01 @@ -3363,7 +3363,7 @@ class Carver(bpy.types.Operator): bpy.ops.object.delete(use_global=False) else: if self.ObjectMode: - self.ObjectBrush.draw_type = self.ObjectBrush_DT + self.ObjectBrush.display_type = self.ObjectBrush_DT if len(context.selected_objects) > 0: bpy.ops.object.select_all(action='TOGGLE') @@ -3392,9 +3392,9 @@ class Carver(bpy.types.Operator): # If object has children, set "Wire" draw type if self.ObjectBrush is not None: if len(self.ObjectBrush.children) > 0: - self.ObjectBrush.draw_type = "WIRE" + self.ObjectBrush.display_type = "WIRE" if self.ProfileMode: - self.ProfileBrush.draw_type = "WIRE" + self.ProfileBrush.display_type = "WIRE" if bLocalView: bpy.ops.view3d.localview() diff --git a/mesh_custom_normals_tools.py b/mesh_custom_normals_tools.py deleted file mode 100644 index 462b7609..00000000 --- a/mesh_custom_normals_tools.py +++ /dev/null @@ -1,90 +0,0 @@ -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -bl_info = { - "name": "Custom Normals Tools", - "author": "Bastien Montagne (mont29)", - "version": (0, 0, 1), - "blender": (2, 75, 0), - "location": "3DView > Tools", - "description": "Various tools/helpers for custom normals", - "warning": "", - "support": 'OFFICIAL', - "category": "Mesh", -} - - -import bpy - - -class MESH_OT_flip_custom_normals(bpy.types.Operator): - """Flip active mesh's normals, including custom ones (only in Object mode)""" - bl_idname = "mesh.flip_custom_normals" - bl_label = "Flip Custom Normals" - bl_options = {'UNDO'} - - @classmethod - def poll(cls, context): - return context.object and context.object.type == 'MESH' and context.object.mode == 'OBJECT' - - def execute(self, context): - me = context.object.data - - if me.has_custom_normals: - me.calc_normals_split() - clnors = [0.0] * 3 * len(me.loops) - me.loops.foreach_get("normal", clnors) - - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.mesh.flip_normals() - bpy.ops.object.mode_set(mode='OBJECT') - - me = context.object.data - if me.has_custom_normals: - clnors[:] = list(zip(*[(-n for n in clnors)] * 3)) - # We also have to take in account that the winding was reverted... - for p in me.polygons: - ls = p.loop_start + 1 - le = ls + p.loop_total - 1 - clnors[ls:le] = reversed(clnors[ls:le]) - me.normals_split_custom_set(clnors) - - context.scene.update() - return {'FINISHED'} - - -def flip_custom_normals_draw_func(self, context): - if isinstance(self, bpy.types.Panel): - self.layout.label("Custom Normal Tools:") - self.layout.operator(MESH_OT_flip_custom_normals.bl_idname) - - -def register(): - bpy.utils.register_module(__name__) - bpy.types.VIEW3D_PT_tools_object.append(flip_custom_normals_draw_func) - - -def unregister(): - bpy.types.VIEW3D_PT_tools_object.remove(flip_custom_normals_draw_func) - bpy.utils.unregister_module(__name__) - - -if __name__ == "__main__": - register() diff --git a/mesh_extra_tools/face_inset_fillet.py b/mesh_extra_tools/face_inset_fillet.py index 2f46115d..eb3e34c2 100644 --- a/mesh_extra_tools/face_inset_fillet.py +++ b/mesh_extra_tools/face_inset_fillet.py @@ -62,7 +62,7 @@ def face_inset_fillet(bme, face_index_list, inset_amount, distance, bme.faces.ensure_lookup_table() # loops through the faces... f = bme.faces[faceindex] - f.select_set(0) + f.select_set(False) list_del.append(f) f.normal_update() vertex_index_list = [v.index for v in f.verts] diff --git a/mesh_extra_tools/mesh_extrude_and_reshape.py b/mesh_extra_tools/mesh_extrude_and_reshape.py index f4eef683..a7ca0fdd 100644 --- a/mesh_extra_tools/mesh_extrude_and_reshape.py +++ b/mesh_extra_tools/mesh_extrude_and_reshape.py @@ -304,7 +304,7 @@ class Extrude_and_Reshape(Operator): nf = bmesh.utils.face_split(f, v1, v2) # sp_faces2.update({f, nf[0]}) - bmesh.update_edit_mesh(self.mesh, tessface=True, destructive=True) + bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True) return {'FINISHED'} if self.cancel: return {'FINISHED'} @@ -345,7 +345,7 @@ class Extrude_and_Reshape(Operator): dfaces = bmesh.ops.dissolve_edges( self.bm, edges=geom, use_verts=True, use_face_split=False ) - bmesh.update_edit_mesh(self.mesh, tessface=True, destructive=True) + bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True) bpy.ops.transform.translate( 'INVOKE_DEFAULT', constraint_axis=(False, False, True), constraint_orientation='NORMAL', release_confirm=True diff --git a/mesh_extra_tools/mesh_fastloop.py b/mesh_extra_tools/mesh_fastloop.py index 61e9b0d2..0bfcc405 100644 --- a/mesh_extra_tools/mesh_fastloop.py +++ b/mesh_extra_tools/mesh_fastloop.py @@ -79,7 +79,7 @@ class OBJECT_OT_FastLoop(Operator): def modal(self, context, event): if event.type == 'ESC': - context.area.header_text_set("") + context.area.header_text_set(None) return {'CANCELLED'} elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE': diff --git a/mesh_extra_tools/mesh_filletplus.py b/mesh_extra_tools/mesh_filletplus.py index 442adb54..f3c9cbdf 100644 --- a/mesh_extra_tools/mesh_filletplus.py +++ b/mesh_extra_tools/mesh_filletplus.py @@ -201,7 +201,7 @@ def fillets(list_0, startv, vertlist, face, adj, n, out, flip, radius): bm.edges.index_update() bm.faces.index_update() - me.update(calc_edges=True, calc_tessface=True) + me.update(calc_edges=True, calc_loop_triangles=True) bmesh.ops.recalc_face_normals(bm, faces=bm.faces) except Exception as e: diff --git a/mesh_extra_tools/mesh_offset_edges.py b/mesh_extra_tools/mesh_offset_edges.py index 19eccd3b..e92c5430 100644 --- a/mesh_extra_tools/mesh_offset_edges.py +++ b/mesh_extra_tools/mesh_offset_edges.py @@ -786,7 +786,7 @@ class OffsetEdges(Operator): def restore_original_and_free(self, context): self.caches_valid = False # Make caches invalid - context.area.header_text_set("") + context.area.header_text_set(None) me = context.edit_object.data bpy.ops.object.mode_set(mode="OBJECT") @@ -794,7 +794,7 @@ class OffsetEdges(Operator): bpy.ops.object.mode_set(mode="EDIT") self._bm_orig.free() - context.area.header_text_set("") + context.area.header_text_set(None) def invoke(self, context, event): # In edit mode diff --git a/mesh_extra_tools/mesh_vertex_chamfer.py b/mesh_extra_tools/mesh_vertex_chamfer.py index 62383c38..a80ba270 100644 --- a/mesh_extra_tools/mesh_vertex_chamfer.py +++ b/mesh_extra_tools/mesh_vertex_chamfer.py @@ -144,7 +144,7 @@ class VertexChamfer(Operator): else: v.co += displace * v.normal - me.calc_tessface() + me.calc_loop_triangles() return {'FINISHED'} @@ -22,8 +22,8 @@ bl_info = { "name": "F2", "author": "Bart Crouch, Alexander Nedovizin, Paul Kotelevets " "(concept design)", - "version": (1, 7, 2), - "blender": (2, 70, 0), + "version": (1, 7, 3), + "blender": (2, 80, 0), "location": "Editmode > F", "warning": "", "description": "Extends the 'Make Edge/Face' functionality", @@ -40,31 +40,6 @@ import mathutils from bpy_extras import view3d_utils -# returns a custom data layer of the UV map, or None -def get_uv_layer(ob, bm, mat_index): - uv = None - uv_layer = None - if not ob.material_slots: - me = ob.data - if me.uv_textures: - uv = me.uv_textures.active.name - else: - mat = ob.material_slots[mat_index].material - if mat is not None: - slot = mat.texture_slots[mat.active_texture_index] - if slot and slot.uv_layer: - uv = slot.uv_layer - else: - for tex_slot in mat.texture_slots: - if tex_slot and tex_slot.uv_layer: - uv = tex_slot.uv_layer - break - if uv: - uv_layer = bm.loops.layers.uv.get(uv) - - return(uv_layer) - - # create a face from a single selected edge def quad_from_edge(bm, edge_sel, context, event): ob = context.active_object @@ -87,7 +62,7 @@ def quad_from_edge(bm, edge_sel, context, event): min_dist = False for edge in edges: vert = [vert for vert in edge.verts if not vert.select][0] - world_pos = ob.matrix_world * vert.co.copy() + world_pos = ob.matrix_world @ vert.co.copy() screen_pos = view3d_utils.location_3d_to_region_2d(region, region_3d, world_pos) dist = (mouse_pos - screen_pos).length @@ -177,8 +152,7 @@ def quad_from_edge(bm, edge_sel, context, event): if __name__ != '__main__': addon_prefs = context.user_preferences.addons[__name__].preferences if addon_prefs.adjustuv: - uv_layer = get_uv_layer(ob, bm, mat_index) - if uv_layer: + for (key, uv_layer) in bm.loops.layers.uv.items(): uv_ori = {} for vert in [v1, v2, v3, v4]: for loop in vert.link_loops: @@ -214,7 +188,7 @@ def quad_from_vertex(bm, vert_sel, context, event): mid_other = (other_verts[0].co.copy() + other_verts[1].co.copy()) \ / 2 new_pos = 2 * (mid_other - vert_sel.co.copy()) + vert_sel.co.copy() - world_pos = ob.matrix_world * new_pos + world_pos = ob.matrix_world @ new_pos screen_pos = view3d_utils.location_3d_to_region_2d(region, region_3d, world_pos) dist = (mouse_pos - screen_pos).length @@ -277,8 +251,7 @@ def quad_from_vertex(bm, vert_sel, context, event): if __name__ != '__main__': addon_prefs = context.user_preferences.addons[__name__].preferences if addon_prefs.adjustuv: - uv_layer = get_uv_layer(ob, bm, mat_index) - if uv_layer: + for (key, uv_layer) in bm.loops.layers.uv.items(): uv_others = {} uv_sel = None uv_new = None @@ -317,11 +290,11 @@ def quad_from_vertex(bm, vert_sel, context, event): # autograb preference in addons panel class F2AddonPreferences(bpy.types.AddonPreferences): bl_idname = __name__ - adjustuv = bpy.props.BoolProperty( + adjustuv: bpy.props.BoolProperty( name = "Adjust UV", description = "Automatically update UV unwrapping", default = True) - autograb = bpy.props.BoolProperty( + autograb: bpy.props.BoolProperty( name = "Auto Grab", description = "Automatically puts a newly created vertex in grab mode", default = False) diff --git a/mesh_looptools.py b/mesh_looptools.py index 62417f21..25c63f9e 100644 --- a/mesh_looptools.py +++ b/mesh_looptools.py @@ -20,7 +20,7 @@ bl_info = { "name": "LoopTools", "author": "Bart Crouch", "version": (4, 6, 7), - "blender": (2, 72, 2), + "blender": (2, 80, 0), "location": "View3D > Toolbar and View3D > Specials (W-key)", "warning": "", "description": "Mesh modelling toolkit. Several tools to aid modelling", @@ -110,10 +110,11 @@ loops, derived, mapping): del looptools_cache[tool] # prepare values to be saved to cache input = [v.index for v in bm.verts if v.select and not v.hide] - modifiers = [mod.name for mod in object.modifiers if mod.show_viewport and - mod.type == 'MIRROR'] + modifiers = [mod.name for mod in object.modifiers if mod.show_viewport + and mod.type == 'MIRROR'] # update cache - looptools_cache[tool] = {"input": input, "object": object.name, + looptools_cache[tool] = { + "input": input, "object": object.name, "input_method": input_method, "boundaries": boundaries, "single_loops": single_loops, "loops": loops, "derived": derived, "mapping": mapping, "modifiers": modifiers} @@ -276,7 +277,7 @@ def calculate_plane(bm_mod, loop, method="best_fit", object=False): vec2 = mathutils.Vector((1.0, 1.0, 1.0)) for i in range(itermax): vec = vec2 - vec2 = mat * vec + vec2 = mat @ vec if vec2.length != 0: vec2 /= vec2.length if vec2 == vec: @@ -425,9 +426,9 @@ def face_edgekeys(face): # calculate input loops -def get_connected_input(object, bm, scene, input): +def get_connected_input(object, bm, input): # get mesh with modifiers applied - derived, bm_mod = get_derived_bmesh(object, bm, scene) + derived, bm_mod = get_derived_bmesh(object, bm) # calculate selected loops edge_keys = [edgekey(edge) for edge in bm_mod.edges if edge.select and not edge.hide] @@ -514,7 +515,7 @@ def get_connected_selections(edge_keys): # get the derived mesh data, if there is a mirror modifier -def get_derived_bmesh(object, bm, scene): +def get_derived_bmesh(object, bm): # check for mirror modifiers if 'MIRROR' in [mod.type for mod in object.modifiers if mod.show_viewport]: derived = True @@ -525,7 +526,7 @@ def get_derived_bmesh(object, bm, scene): mod.show_viewport = False # get derived mesh bm_mod = bmesh.new() - mesh_mod = object.to_mesh(scene, True, 'PREVIEW') + mesh_mod = object.to_mesh(bpy.context.depsgraph, True) bm_mod.from_mesh(mesh_mod) bpy.context.blend_data.meshes.remove(mesh_mod) # re-enable other modifiers @@ -831,7 +832,7 @@ def terminate(global_undo): # update editmesh cached data obj = bpy.context.active_object if obj.mode == 'EDIT': - bmesh.update_edit_mesh(obj.data, tessface=True, destructive=True) + bmesh.update_edit_mesh(obj.data, loop_triangles=True, destructive=True) bpy.context.user_preferences.edit.use_global_undo = global_undo @@ -1043,11 +1044,11 @@ def bridge_calculate_lines(bm, loops, mode, twist, reverse): itermax = 500 iter = 0 vec = mathutils.Vector((1.0, 1.0, 1.0)) - vec2 = (mat * vec) / (mat * vec).length + vec2 = (mat @ vec) / (mat @ vec).length while vec != vec2 and iter < itermax: iter += 1 vec = vec2 - vec2 = mat * vec + vec2 = mat @ vec if vec2.length != 0: vec2 /= vec2.length if vec2.length == 0: @@ -1079,7 +1080,7 @@ def bridge_calculate_lines(bm, loops, mode, twist, reverse): # match start vertex of loop1 with loop2 target_vector = bm.verts[loop2[0]].co - center2 - dif_angles = [[(rotation_matrix * (bm.verts[vertex].co - center1) + dif_angles = [[(rotation_matrix @ (bm.verts[vertex].co - center1) ).angle(target_vector, 0), False, i] for i, vertex in enumerate(loop1)] dif_angles.sort() @@ -1161,7 +1162,7 @@ def bridge_calculate_lines(bm, loops, mode, twist, reverse): shifting = False break to_last, to_first = [ - (rotation_matrix * (bm.verts[loop1[-1]].co - center1)).angle( + (rotation_matrix @ (bm.verts[loop1[-1]].co - center1)).angle( (bm.verts[loop2[i]].co - center2), 0) for i in [-1, 0] ] if to_first < to_last: @@ -1802,7 +1803,7 @@ def circle_calculate_best_fit(locs_2d): jmat2.invert() except: pass - dx0, dy0, dr = jmat2 * k2 + dx0, dy0, dr = jmat2 @ k2 x0 += dx0 y0 += dy0 r += dr @@ -1980,9 +1981,9 @@ def circle_flatten_singles(bm_mod, com, p, q, normal, single_loop): # calculate input loops -def circle_get_input(object, bm, scene): +def circle_get_input(object, bm): # get mesh with modifiers applied - derived, bm_mod = get_derived_bmesh(object, bm, scene) + derived, bm_mod = get_derived_bmesh(object, bm) # create list of edge-keys based on selection state faces = False @@ -2317,9 +2318,9 @@ def curve_cut_boundaries(bm_mod, loops): # calculate input loops -def curve_get_input(object, bm, boundaries, scene): +def curve_get_input(object, bm, boundaries): # get mesh with modifiers applied - derived, bm_mod = get_derived_bmesh(object, bm, scene) + derived, bm_mod = get_derived_bmesh(object, bm) # vertices that still need a loop to run through it verts_unsorted = [ @@ -2610,7 +2611,7 @@ def gstretch_align_pairs(ls_pairs, object, bm_mod, method): else: relative_distance = relative_lengths[i] - loc1 = object.matrix_world * bm_mod.verts[v_index].co + loc1 = object.matrix_world @ bm_mod.verts[v_index].co loc2, stroke_lengths_cache = gstretch_eval_stroke(stroke, relative_distance, stroke_lengths_cache) total_distance += (loc2 - loc1).length @@ -2683,7 +2684,7 @@ def gstretch_calculate_verts(loop, stroke, object, bm_mod, method): relative_distance = relative_lengths[i] loc, stroke_lengths_cache = gstretch_eval_stroke(stroke, relative_distance, stroke_lengths_cache) - loc = matrix_inverse * loc + loc = matrix_inverse @ loc move.append([v_index, loc]) return(move) @@ -2828,8 +2829,8 @@ def gstretch_eval_stroke(stroke, distance, stroke_lengths_cache=False): def gstretch_get_fake_strokes(object, bm_mod, loops): strokes = [] for loop in loops: - p1 = object.matrix_world * bm_mod.verts[loop[0][0]].co - p2 = object.matrix_world * bm_mod.verts[loop[0][-1]].co + p1 = object.matrix_world @ bm_mod.verts[loop[0][0]].co + p2 = object.matrix_world @ bm_mod.verts[loop[0][-1]].co strokes.append(gstretch_fake_stroke([p1, p2])) return(strokes) @@ -2866,7 +2867,7 @@ def gstretch_match_loops_strokes(loops, strokes, object, bm_mod): for v_index in loop[0]: center += bm_mod.verts[v_index].co center /= len(loop[0]) - center = object.matrix_world * center + center = object.matrix_world @ center loop_centers.append([center, loop]) # calculate stroke centers @@ -3189,14 +3190,14 @@ class Bridge(Operator): bl_description = "Bridge two, or loft several, loops of vertices" bl_options = {'REGISTER', 'UNDO'} - cubic_strength = FloatProperty( + cubic_strength: FloatProperty( name="Strength", description="Higher strength results in more fluid curves", default=1.0, soft_min=-3.0, soft_max=3.0 ) - interpolation = EnumProperty( + interpolation: EnumProperty( name="Interpolation mode", items=(('cubic', "Cubic", "Gives curved results"), ('linear', "Linear", "Basic, fast, straight interpolation")), @@ -3204,18 +3205,18 @@ class Bridge(Operator): "segments", default='cubic' ) - loft = BoolProperty( + loft: BoolProperty( name="Loft", description="Loft multiple loops, instead of considering them as " "a multi-input for bridging", default=False ) - loft_loop = BoolProperty( + loft_loop: BoolProperty( name="Loop", description="Connect the first and the last loop with each other", default=False ) - min_width = IntProperty( + min_width: IntProperty( name="Minimum width", description="Segments with an edge smaller than this are merged " "(compared to base edge)", @@ -3224,32 +3225,32 @@ class Bridge(Operator): max=100, subtype='PERCENTAGE' ) - mode = EnumProperty( + mode: EnumProperty( name="Mode", items=(('basic', "Basic", "Fast algorithm"), ('shortest', "Shortest edge", "Slower algorithm with better vertex matching")), description="Algorithm used for bridging", default='shortest' ) - remove_faces = BoolProperty( + remove_faces: BoolProperty( name="Remove faces", description="Remove faces that are internal after bridging", default=True ) - reverse = BoolProperty( + reverse: BoolProperty( name="Reverse", description="Manually override the direction in which the loops " "are bridged. Only use if the tool gives the wrong result", default=False ) - segments = IntProperty( + segments: IntProperty( name="Segments", description="Number of segments used to bridge the gap (0=automatic)", default=1, min=0, soft_max=20 ) - twist = IntProperty( + twist: IntProperty( name="Twist", description="Twist what vertices are connected to each other", default=0 @@ -3367,7 +3368,7 @@ class Bridge(Operator): if self.remove_faces and old_selected_faces: bridge_remove_internal_faces(bm, old_selected_faces) # make sure normals are facing outside - bmesh.update_edit_mesh(object.data, tessface=False, + bmesh.update_edit_mesh(object.data, loop_triangles=False, destructive=True) bpy.ops.mesh.normals_make_consistent() @@ -3384,24 +3385,24 @@ class Circle(Operator): bl_description = "Move selected vertices into a circle shape" bl_options = {'REGISTER', 'UNDO'} - custom_radius = BoolProperty( + custom_radius: BoolProperty( name="Radius", description="Force a custom radius", default=False ) - fit = EnumProperty( + fit: EnumProperty( name="Method", items=(("best", "Best fit", "Non-linear least squares"), ("inside", "Fit inside", "Only move vertices towards the center")), description="Method used for fitting a circle to the vertices", default='best' ) - flatten = BoolProperty( + flatten: BoolProperty( name="Flatten", description="Flatten the circle, instead of projecting it on the mesh", default=True ) - influence = FloatProperty( + influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -3410,28 +3411,28 @@ class Circle(Operator): precision=1, subtype='PERCENTAGE' ) - lock_x = BoolProperty( + lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False ) - lock_y = BoolProperty( + lock_y: BoolProperty( name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - lock_z = BoolProperty(name="Lock Z", + lock_z: BoolProperty(name="Lock Z", description="Lock editing of the z-coordinate", default=False ) - radius = FloatProperty( + radius: FloatProperty( name="Radius", description="Custom radius for circle", default=1.0, min=0.0, soft_max=1000.0 ) - regular = BoolProperty( + regular: BoolProperty( name="Regular", description="Distribute vertices at constant distances along the circle", default=True @@ -3487,11 +3488,11 @@ class Circle(Operator): cached, single_loops, loops, derived, mapping = cache_read("Circle", object, bm, False, False) if cached: - derived, bm_mod = get_derived_bmesh(object, bm, context.scene) + derived, bm_mod = get_derived_bmesh(object, bm) else: # find loops derived, bm_mod, single_vertices, single_loops, loops = \ - circle_get_input(object, bm, context.scene) + circle_get_input(object, bm) mapping = get_mapping(derived, bm, bm_mod, single_vertices, False, loops) single_loops, loops = circle_check_loops(single_loops, loops, @@ -3557,12 +3558,12 @@ class Curve(Operator): bl_description = "Turn a loop into a smooth curve" bl_options = {'REGISTER', 'UNDO'} - boundaries = BoolProperty( + boundaries: BoolProperty( name="Boundaries", description="Limit the tool to work within the boundaries of the selected vertices", default=False ) - influence = FloatProperty( + influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -3571,34 +3572,34 @@ class Curve(Operator): precision=1, subtype='PERCENTAGE' ) - interpolation = EnumProperty( + interpolation: EnumProperty( name="Interpolation", items=(("cubic", "Cubic", "Natural cubic spline, smooth results"), ("linear", "Linear", "Simple and fast linear algorithm")), description="Algorithm used for interpolation", default='cubic' ) - lock_x = BoolProperty( + lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False ) - lock_y = BoolProperty( + lock_y: BoolProperty( name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - lock_z = BoolProperty( + lock_z: BoolProperty( name="Lock Z", description="Lock editing of the z-coordinate", default=False ) - regular = BoolProperty( + regular: BoolProperty( name="Regular", description="Distribute vertices at constant distances along the curve", default=True ) - restriction = EnumProperty( + restriction: EnumProperty( name="Restriction", items=(("none", "None", "No restrictions on vertex movement"), ("extrude", "Extrude only", "Only allow extrusions (no indentations)"), @@ -3651,11 +3652,10 @@ class Curve(Operator): cached, single_loops, loops, derived, mapping = cache_read("Curve", object, bm, False, self.boundaries) if cached: - derived, bm_mod = get_derived_bmesh(object, bm, context.scene) + derived, bm_mod = get_derived_bmesh(object, bm) else: # find loops - derived, bm_mod, loops = curve_get_input(object, bm, - self.boundaries, context.scene) + derived, bm_mod, loops = curve_get_input(object, bm, self.boundaries) mapping = get_mapping(derived, bm, bm_mod, False, True, loops) loops = check_loops(loops, mapping, bm_mod) verts_selected = [ @@ -3702,7 +3702,7 @@ class Flatten(Operator): bl_description = "Flatten vertices on a best-fitting plane" bl_options = {'REGISTER', 'UNDO'} - influence = FloatProperty( + influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -3711,21 +3711,21 @@ class Flatten(Operator): precision=1, subtype='PERCENTAGE' ) - lock_x = BoolProperty( + lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False ) - lock_y = BoolProperty( + lock_y: BoolProperty( name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - lock_z = BoolProperty(name="Lock Z", + lock_z: BoolProperty(name="Lock Z", description="Lock editing of the z-coordinate", default=False ) - plane = EnumProperty( + plane: EnumProperty( name="Plane", items=(("best_fit", "Best fit", "Calculate a best fitting plane"), ("normal", "Normal", "Derive plane from averaging vertex normals"), @@ -3733,7 +3733,7 @@ class Flatten(Operator): description="Plane on which vertices are flattened", default='best_fit' ) - restriction = EnumProperty( + restriction: EnumProperty( name="Restriction", items=(("none", "None", "No restrictions on vertex movement"), ("bounding_box", "Bounding box", "Vertices are restricted to " @@ -3841,7 +3841,7 @@ class GStretch(Operator): bl_description = "Stretch selected vertices to Grease Pencil stroke" bl_options = {'REGISTER', 'UNDO'} - conversion = EnumProperty( + conversion: EnumProperty( name="Conversion", items=(("distance", "Distance", "Set the distance between vertices " "of the converted grease pencil stroke"), @@ -3856,7 +3856,7 @@ class GStretch(Operator): "use this simplification method", default='limit_vertices' ) - conversion_distance = FloatProperty( + conversion_distance: FloatProperty( name="Distance", description="Absolute distance between vertices along the converted " "grease pencil stroke", @@ -3865,7 +3865,7 @@ class GStretch(Operator): soft_min=0.01, soft_max=100 ) - conversion_max = IntProperty( + conversion_max: IntProperty( name="Max Vertices", description="Maximum number of vertices grease pencil strokes will " "have, when they are converted to geomtery", @@ -3874,7 +3874,7 @@ class GStretch(Operator): soft_max=500, update=gstretch_update_min ) - conversion_min = IntProperty( + conversion_min: IntProperty( name="Min Vertices", description="Minimum number of vertices grease pencil strokes will " "have, when they are converted to geomtery", @@ -3883,7 +3883,7 @@ class GStretch(Operator): soft_max=500, update=gstretch_update_max ) - conversion_vertices = IntProperty( + conversion_vertices: IntProperty( name="Vertices", description="Number of vertices grease pencil strokes will " "have, when they are converted to geometry. If strokes have less " @@ -3892,13 +3892,13 @@ class GStretch(Operator): min=3, soft_max=500 ) - delete_strokes = BoolProperty( + delete_strokes: BoolProperty( name="Delete strokes", description="Remove Grease Pencil strokes if they have been used " "for Gstretch. WARNING: DOES NOT SUPPORT UNDO", default=False ) - influence = FloatProperty( + influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -3907,22 +3907,22 @@ class GStretch(Operator): precision=1, subtype='PERCENTAGE' ) - lock_x = BoolProperty( + lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False ) - lock_y = BoolProperty( + lock_y: BoolProperty( name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - lock_z = BoolProperty( + lock_z: BoolProperty( name="Lock Z", description="Lock editing of the z-coordinate", default=False ) - method = EnumProperty( + method: EnumProperty( name="Method", items=(("project", "Project", "Project vertices onto the stroke, " "using vertex normals and connected edges"), @@ -4010,13 +4010,12 @@ class GStretch(Operator): bm_mod.faces.ensure_lookup_table() strokes = gstretch_get_fake_strokes(object, bm_mod, loops) if not straightening: - derived, bm_mod = get_derived_bmesh(object, bm, context.scene) + derived, bm_mod = get_derived_bmesh(object, bm) else: # get loops and strokes if get_grease_pencil(object, context): # find loops - derived, bm_mod, loops = get_connected_input(object, bm, - context.scene, input='selected') + derived, bm_mod, loops = get_connected_input(object, bm, input='selected') mapping = get_mapping(derived, bm, bm_mod, False, False, loops) loops = check_loops(loops, mapping, bm_mod) # get strokes @@ -4086,7 +4085,7 @@ class GStretch(Operator): lock = [self.lock_x, self.lock_y, self.lock_z] else: lock = False - bmesh.update_edit_mesh(object.data, tessface=True, destructive=True) + bmesh.update_edit_mesh(object.data, loop_triangles=True, destructive=True) move_verts(object, bm, mapping, move, lock, self.influence) # cleaning up @@ -4104,7 +4103,7 @@ class Relax(Operator): bl_description = "Relax the loop, so it is smoother" bl_options = {'REGISTER', 'UNDO'} - input = EnumProperty( + input: EnumProperty( name="Input", items=(("all", "Parallel (all)", "Also use non-selected " "parallel loops as input"), @@ -4112,14 +4111,14 @@ class Relax(Operator): description="Loops that are relaxed", default='selected' ) - interpolation = EnumProperty( + interpolation: EnumProperty( name="Interpolation", items=(("cubic", "Cubic", "Natural cubic spline, smooth results"), ("linear", "Linear", "Simple and fast linear algorithm")), description="Algorithm used for interpolation", default='cubic' ) - iterations = EnumProperty( + iterations: EnumProperty( name="Iterations", items=(("1", "1", "One"), ("3", "3", "Three"), @@ -4129,7 +4128,7 @@ class Relax(Operator): description="Number of times the loop is relaxed", default="1" ) - regular = BoolProperty( + regular: BoolProperty( name="Regular", description="Distribute vertices at constant distances along the loop", default=True @@ -4162,11 +4161,10 @@ class Relax(Operator): cached, single_loops, loops, derived, mapping = cache_read("Relax", object, bm, self.input, False) if cached: - derived, bm_mod = get_derived_bmesh(object, bm, context.scene) + derived, bm_mod = get_derived_bmesh(object, bm) else: # find loops - derived, bm_mod, loops = get_connected_input(object, bm, - context.scene, self.input) + derived, bm_mod, loops = get_connected_input(object, bm, self.input) mapping = get_mapping(derived, bm, bm_mod, False, False, loops) loops = check_loops(loops, mapping, bm_mod) knots, points = relax_calculate_knots(loops) @@ -4203,7 +4201,7 @@ class Space(Operator): bl_description = "Space the vertices in a regular distrubtion on the loop" bl_options = {'REGISTER', 'UNDO'} - influence = FloatProperty( + influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -4212,7 +4210,7 @@ class Space(Operator): precision=1, subtype='PERCENTAGE' ) - input = EnumProperty( + input: EnumProperty( name="Input", items=(("all", "Parallel (all)", "Also use non-selected " "parallel loops as input"), @@ -4220,24 +4218,24 @@ class Space(Operator): description="Loops that are spaced", default='selected' ) - interpolation = EnumProperty( + interpolation: EnumProperty( name="Interpolation", items=(("cubic", "Cubic", "Natural cubic spline, smooth results"), ("linear", "Linear", "Vertices are projected on existing edges")), description="Algorithm used for interpolation", default='cubic' ) - lock_x = BoolProperty( + lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False ) - lock_y = BoolProperty( + lock_y: BoolProperty( name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - lock_z = BoolProperty( + lock_z: BoolProperty( name="Lock Z", description="Lock editing of the z-coordinate", default=False @@ -4285,11 +4283,10 @@ class Space(Operator): cached, single_loops, loops, derived, mapping = cache_read("Space", object, bm, self.input, False) if cached: - derived, bm_mod = get_derived_bmesh(object, bm, context.scene) + derived, bm_mod = get_derived_bmesh(object, bm) else: # find loops - derived, bm_mod, loops = get_connected_input(object, bm, - context.scene, self.input) + derived, bm_mod, loops = get_connected_input(object, bm, self.input) mapping = get_mapping(derived, bm, bm_mod, False, False, loops) loops = check_loops(loops, mapping, bm_mod) @@ -4347,8 +4344,8 @@ class VIEW3D_MT_edit_mesh_looptools(Menu): # panel containing all tools class VIEW3D_PT_tools_looptools(Panel): bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' - bl_category = 'Tools' + bl_region_type = 'UI' + bl_category = 'View' bl_context = "mesh_edit" bl_label = "LoopTools" bl_options = {'DEFAULT_CLOSED'} @@ -4359,7 +4356,7 @@ class VIEW3D_PT_tools_looptools(Panel): lt = context.window_manager.looptools # bridge - first line - split = col.split(percentage=0.15, align=True) + split = col.split(factor=0.15, align=True) if lt.display_bridge: split.prop(lt, "display_bridge", text="", icon='DOWNARROW_HLT') else: @@ -4395,7 +4392,7 @@ class VIEW3D_PT_tools_looptools(Panel): row.prop(lt, "bridge_reverse") # circle - first line - split = col.split(percentage=0.15, align=True) + split = col.split(factor=0.15, align=True) if lt.display_circle: split.prop(lt, "display_circle", text="", icon='DOWNARROW_HLT') else: @@ -4433,7 +4430,7 @@ class VIEW3D_PT_tools_looptools(Panel): col_move.prop(lt, "circle_influence") # curve - first line - split = col.split(percentage=0.15, align=True) + split = col.split(factor=0.15, align=True) if lt.display_curve: split.prop(lt, "display_curve", text="", icon='DOWNARROW_HLT') else: @@ -4465,7 +4462,7 @@ class VIEW3D_PT_tools_looptools(Panel): col_move.prop(lt, "curve_influence") # flatten - first line - split = col.split(percentage=0.15, align=True) + split = col.split(factor=0.15, align=True) if lt.display_flatten: split.prop(lt, "display_flatten", text="", icon='DOWNARROW_HLT') else: @@ -4495,7 +4492,7 @@ class VIEW3D_PT_tools_looptools(Panel): col_move.prop(lt, "flatten_influence") # gstretch - first line - split = col.split(percentage=0.15, align=True) + split = col.split(factor=0.15, align=True) if lt.display_gstretch: split.prop(lt, "display_gstretch", text="", icon='DOWNARROW_HLT') else: @@ -4536,7 +4533,7 @@ class VIEW3D_PT_tools_looptools(Panel): box.operator("remove.gp", text="Delete GP Strokes") # loft - first line - split = col.split(percentage=0.15, align=True) + split = col.split(factor=0.15, align=True) if lt.display_loft: split.prop(lt, "display_loft", text="", icon='DOWNARROW_HLT') else: @@ -4573,7 +4570,7 @@ class VIEW3D_PT_tools_looptools(Panel): row.prop(lt, "bridge_reverse") # relax - first line - split = col.split(percentage=0.15, align=True) + split = col.split(factor=0.15, align=True) if lt.display_relax: split.prop(lt, "display_relax", text="", icon='DOWNARROW_HLT') else: @@ -4588,7 +4585,7 @@ class VIEW3D_PT_tools_looptools(Panel): box.prop(lt, "relax_regular") # space - first line - split = col.split(percentage=0.15, align=True) + split = col.split(factor=0.15, align=True) if lt.display_space: split.prop(lt, "display_space", text="", icon='DOWNARROW_HLT') else: @@ -4625,74 +4622,74 @@ class LoopToolsProps(PropertyGroup): bpy.context.window_manager.looptools """ # general display properties - display_bridge = BoolProperty( + display_bridge: BoolProperty( name="Bridge settings", description="Display settings of the Bridge tool", default=False ) - display_circle = BoolProperty( + display_circle: BoolProperty( name="Circle settings", description="Display settings of the Circle tool", default=False ) - display_curve = BoolProperty( + display_curve: BoolProperty( name="Curve settings", description="Display settings of the Curve tool", default=False ) - display_flatten = BoolProperty( + display_flatten: BoolProperty( name="Flatten settings", description="Display settings of the Flatten tool", default=False ) - display_gstretch = BoolProperty( + display_gstretch: BoolProperty( name="Gstretch settings", description="Display settings of the Gstretch tool", default=False ) - display_loft = BoolProperty( + display_loft: BoolProperty( name="Loft settings", description="Display settings of the Loft tool", default=False ) - display_relax = BoolProperty( + display_relax: BoolProperty( name="Relax settings", description="Display settings of the Relax tool", default=False ) - display_space = BoolProperty( + display_space: BoolProperty( name="Space settings", description="Display settings of the Space tool", default=False ) # bridge properties - bridge_cubic_strength = FloatProperty( + bridge_cubic_strength: FloatProperty( name="Strength", description="Higher strength results in more fluid curves", default=1.0, soft_min=-3.0, soft_max=3.0 ) - bridge_interpolation = EnumProperty( + bridge_interpolation: EnumProperty( name="Interpolation mode", items=(('cubic', "Cubic", "Gives curved results"), ('linear', "Linear", "Basic, fast, straight interpolation")), description="Interpolation mode: algorithm used when creating segments", default='cubic' ) - bridge_loft = BoolProperty( + bridge_loft: BoolProperty( name="Loft", description="Loft multiple loops, instead of considering them as " "a multi-input for bridging", default=False ) - bridge_loft_loop = BoolProperty( + bridge_loft_loop: BoolProperty( name="Loop", description="Connect the first and the last loop with each other", default=False ) - bridge_min_width = IntProperty( + bridge_min_width: IntProperty( name="Minimum width", description="Segments with an edge smaller than this are merged " "(compared to base edge)", @@ -4701,7 +4698,7 @@ class LoopToolsProps(PropertyGroup): max=100, subtype='PERCENTAGE' ) - bridge_mode = EnumProperty( + bridge_mode: EnumProperty( name="Mode", items=(('basic', "Basic", "Fast algorithm"), ('shortest', "Shortest edge", "Slower algorithm with " @@ -4709,49 +4706,49 @@ class LoopToolsProps(PropertyGroup): description="Algorithm used for bridging", default='shortest' ) - bridge_remove_faces = BoolProperty( + bridge_remove_faces: BoolProperty( name="Remove faces", description="Remove faces that are internal after bridging", default=True ) - bridge_reverse = BoolProperty( + bridge_reverse: BoolProperty( name="Reverse", description="Manually override the direction in which the loops " "are bridged. Only use if the tool gives the wrong result", default=False ) - bridge_segments = IntProperty( + bridge_segments: IntProperty( name="Segments", description="Number of segments used to bridge the gap (0=automatic)", default=1, min=0, soft_max=20 ) - bridge_twist = IntProperty( + bridge_twist: IntProperty( name="Twist", description="Twist what vertices are connected to each other", default=0 ) # circle properties - circle_custom_radius = BoolProperty( + circle_custom_radius: BoolProperty( name="Radius", description="Force a custom radius", default=False ) - circle_fit = EnumProperty( + circle_fit: EnumProperty( name="Method", items=(("best", "Best fit", "Non-linear least squares"), ("inside", "Fit inside", "Only move vertices towards the center")), description="Method used for fitting a circle to the vertices", default='best' ) - circle_flatten = BoolProperty( + circle_flatten: BoolProperty( name="Flatten", description="Flatten the circle, instead of projecting it on the mesh", default=True ) - circle_influence = FloatProperty( + circle_influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -4760,41 +4757,41 @@ class LoopToolsProps(PropertyGroup): precision=1, subtype='PERCENTAGE' ) - circle_lock_x = BoolProperty( + circle_lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False ) - circle_lock_y = BoolProperty( + circle_lock_y: BoolProperty( name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - circle_lock_z = BoolProperty( + circle_lock_z: BoolProperty( name="Lock Z", description="Lock editing of the z-coordinate", default=False ) - circle_radius = FloatProperty( + circle_radius: FloatProperty( name="Radius", description="Custom radius for circle", default=1.0, min=0.0, soft_max=1000.0 ) - circle_regular = BoolProperty( + circle_regular: BoolProperty( name="Regular", description="Distribute vertices at constant distances along the circle", default=True ) # curve properties - curve_boundaries = BoolProperty( + curve_boundaries: BoolProperty( name="Boundaries", description="Limit the tool to work within the boundaries of the " "selected vertices", default=False ) - curve_influence = FloatProperty( + curve_influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -4803,34 +4800,34 @@ class LoopToolsProps(PropertyGroup): precision=1, subtype='PERCENTAGE' ) - curve_interpolation = EnumProperty( + curve_interpolation: EnumProperty( name="Interpolation", items=(("cubic", "Cubic", "Natural cubic spline, smooth results"), ("linear", "Linear", "Simple and fast linear algorithm")), description="Algorithm used for interpolation", default='cubic' ) - curve_lock_x = BoolProperty( + curve_lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False ) - curve_lock_y = BoolProperty( + curve_lock_y: BoolProperty( name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - curve_lock_z = BoolProperty( + curve_lock_z: BoolProperty( name="Lock Z", description="Lock editing of the z-coordinate", default=False ) - curve_regular = BoolProperty( + curve_regular: BoolProperty( name="Regular", description="Distribute vertices at constant distances along the curve", default=True ) - curve_restriction = EnumProperty( + curve_restriction: EnumProperty( name="Restriction", items=(("none", "None", "No restrictions on vertex movement"), ("extrude", "Extrude only", "Only allow extrusions (no indentations)"), @@ -4840,7 +4837,7 @@ class LoopToolsProps(PropertyGroup): ) # flatten properties - flatten_influence = FloatProperty( + flatten_influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -4849,20 +4846,20 @@ class LoopToolsProps(PropertyGroup): precision=1, subtype='PERCENTAGE' ) - flatten_lock_x = BoolProperty( + flatten_lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False) - flatten_lock_y = BoolProperty(name="Lock Y", + flatten_lock_y: BoolProperty(name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - flatten_lock_z = BoolProperty( + flatten_lock_z: BoolProperty( name="Lock Z", description="Lock editing of the z-coordinate", default=False ) - flatten_plane = EnumProperty( + flatten_plane: EnumProperty( name="Plane", items=(("best_fit", "Best fit", "Calculate a best fitting plane"), ("normal", "Normal", "Derive plane from averaging vertex " @@ -4872,7 +4869,7 @@ class LoopToolsProps(PropertyGroup): description="Plane on which vertices are flattened", default='best_fit' ) - flatten_restriction = EnumProperty( + flatten_restriction: EnumProperty( name="Restriction", items=(("none", "None", "No restrictions on vertex movement"), ("bounding_box", "Bounding box", "Vertices are restricted to " @@ -4882,7 +4879,7 @@ class LoopToolsProps(PropertyGroup): ) # gstretch properties - gstretch_conversion = EnumProperty( + gstretch_conversion: EnumProperty( name="Conversion", items=(("distance", "Distance", "Set the distance between vertices " "of the converted grease pencil stroke"), @@ -4897,7 +4894,7 @@ class LoopToolsProps(PropertyGroup): "use this simplification method", default='limit_vertices' ) - gstretch_conversion_distance = FloatProperty( + gstretch_conversion_distance: FloatProperty( name="Distance", description="Absolute distance between vertices along the converted " "grease pencil stroke", @@ -4906,7 +4903,7 @@ class LoopToolsProps(PropertyGroup): soft_min=0.01, soft_max=100 ) - gstretch_conversion_max = IntProperty( + gstretch_conversion_max: IntProperty( name="Max Vertices", description="Maximum number of vertices grease pencil strokes will " "have, when they are converted to geomtery", @@ -4915,7 +4912,7 @@ class LoopToolsProps(PropertyGroup): soft_max=500, update=gstretch_update_min ) - gstretch_conversion_min = IntProperty( + gstretch_conversion_min: IntProperty( name="Min Vertices", description="Minimum number of vertices grease pencil strokes will " "have, when they are converted to geomtery", @@ -4924,7 +4921,7 @@ class LoopToolsProps(PropertyGroup): soft_max=500, update=gstretch_update_max ) - gstretch_conversion_vertices = IntProperty( + gstretch_conversion_vertices: IntProperty( name="Vertices", description="Number of vertices grease pencil strokes will " "have, when they are converted to geometry. If strokes have less " @@ -4933,13 +4930,13 @@ class LoopToolsProps(PropertyGroup): min=3, soft_max=500 ) - gstretch_delete_strokes = BoolProperty( + gstretch_delete_strokes: BoolProperty( name="Delete strokes", description="Remove Grease Pencil strokes if they have been used " "for Gstretch. WARNING: DOES NOT SUPPORT UNDO", default=False ) - gstretch_influence = FloatProperty( + gstretch_influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -4948,22 +4945,22 @@ class LoopToolsProps(PropertyGroup): precision=1, subtype='PERCENTAGE' ) - gstretch_lock_x = BoolProperty( + gstretch_lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False ) - gstretch_lock_y = BoolProperty( + gstretch_lock_y: BoolProperty( name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - gstretch_lock_z = BoolProperty( + gstretch_lock_z: BoolProperty( name="Lock Z", description="Lock editing of the z-coordinate", default=False ) - gstretch_method = EnumProperty( + gstretch_method: EnumProperty( name="Method", items=(("project", "Project", "Project vertices onto the stroke, " "using vertex normals and connected edges"), @@ -4977,21 +4974,21 @@ class LoopToolsProps(PropertyGroup): ) # relax properties - relax_input = EnumProperty(name="Input", + relax_input: EnumProperty(name="Input", items=(("all", "Parallel (all)", "Also use non-selected " "parallel loops as input"), ("selected", "Selection", "Only use selected vertices as input")), description="Loops that are relaxed", default='selected' ) - relax_interpolation = EnumProperty( + relax_interpolation: EnumProperty( name="Interpolation", items=(("cubic", "Cubic", "Natural cubic spline, smooth results"), ("linear", "Linear", "Simple and fast linear algorithm")), description="Algorithm used for interpolation", default='cubic' ) - relax_iterations = EnumProperty(name="Iterations", + relax_iterations: EnumProperty(name="Iterations", items=(("1", "1", "One"), ("3", "3", "Three"), ("5", "5", "Five"), @@ -5000,14 +4997,14 @@ class LoopToolsProps(PropertyGroup): description="Number of times the loop is relaxed", default="1" ) - relax_regular = BoolProperty( + relax_regular: BoolProperty( name="Regular", description="Distribute vertices at constant distances along the loop", default=True ) # space properties - space_influence = FloatProperty( + space_influence: FloatProperty( name="Influence", description="Force of the tool", default=100.0, @@ -5016,7 +5013,7 @@ class LoopToolsProps(PropertyGroup): precision=1, subtype='PERCENTAGE' ) - space_input = EnumProperty( + space_input: EnumProperty( name="Input", items=(("all", "Parallel (all)", "Also use non-selected " "parallel loops as input"), @@ -5024,24 +5021,24 @@ class LoopToolsProps(PropertyGroup): description="Loops that are spaced", default='selected' ) - space_interpolation = EnumProperty( + space_interpolation: EnumProperty( name="Interpolation", items=(("cubic", "Cubic", "Natural cubic spline, smooth results"), ("linear", "Linear", "Vertices are projected on existing edges")), description="Algorithm used for interpolation", default='cubic' ) - space_lock_x = BoolProperty( + space_lock_x: BoolProperty( name="Lock X", description="Lock editing of the x-coordinate", default=False ) - space_lock_y = BoolProperty( + space_lock_y: BoolProperty( name="Lock Y", description="Lock editing of the y-coordinate", default=False ) - space_lock_z = BoolProperty( + space_lock_z: BoolProperty( name="Lock Z", description="Lock editing of the z-coordinate", default=False @@ -5083,7 +5080,7 @@ class LoopPreferences(AddonPreferences): # when defining this in a submodule of a python package. bl_idname = __name__ - category = StringProperty( + category: StringProperty( name="Tab Category", description="Choose a name for the category of the panel", default="Tools", @@ -5127,12 +5124,13 @@ def register(): # unregistering and removing menus def unregister(): - for cls in classes: + for cls in reversed(classes): bpy.utils.unregister_class(cls) bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func) try: del bpy.types.WindowManager.looptools - except: + except Exception as e: + print('unregister fail:\n', e) pass diff --git a/mesh_snap_utilities_line.py b/mesh_snap_utilities_line.py deleted file mode 100644 index 8f2f2a94..00000000 --- a/mesh_snap_utilities_line.py +++ /dev/null @@ -1,1191 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# ##### END GPL LICENSE BLOCK ##### - -# Contact for more information about the Addon: -# Email: germano.costa@ig.com.br -# Twitter: wii_mano @mano_wii - - -bl_info = { - "name": "Snap Utilities Line", - "author": "Germano Cavalcante", - "version": (5, 7, 6), - "blender": (2, 75, 0), - "location": "View3D > TOOLS > Snap Utilities > snap utilities", - "description": "Extends Blender Snap controls", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/Snap_Utils_Line", - "category": "Mesh"} - -import bpy -import bgl -import bmesh -from mathutils import Vector -from mathutils.geometry import ( - intersect_point_line, - intersect_line_line, - intersect_line_plane, - intersect_ray_tri - ) -from bpy.types import ( - Operator, - Panel, - AddonPreferences, - ) -from bpy.props import ( - BoolProperty, - FloatProperty, - FloatVectorProperty, - StringProperty, - ) - -##DEBUG = False -##if DEBUG: -## from .snap_framebuffer_debug import screenTexture -## from .snap_context import mesh_drawing - - -def get_units_info(scale, unit_system, separate_units): - if unit_system == 'METRIC': - scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'), - (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m')) - elif unit_system == 'IMPERIAL': - scale_steps = ((5280, 'mi'), (1, '\''), - (1 / 12, '"'), (1 / 12000, 'thou')) - scale /= 0.3048 # BU to feet - else: - scale_steps = ((1, ' BU'),) - separate_units = False - - return (scale, scale_steps, separate_units) - - -def convert_distance(val, units_info, precision=5): - scale, scale_steps, separate_units = units_info - sval = val * scale - idx = 0 - while idx < len(scale_steps) - 1: - if sval >= scale_steps[idx][0]: - break - idx += 1 - factor, suffix = scale_steps[idx] - sval /= factor - if not separate_units or idx == len(scale_steps) - 1: - dval = str(round(sval, precision)) + suffix - else: - ival = int(sval) - dval = str(round(ival, precision)) + suffix - fval = sval - ival - idx += 1 - while idx < len(scale_steps): - fval *= scale_steps[idx - 1][0] / scale_steps[idx][0] - if fval >= 1: - dval += ' ' \ - + ("%.1f" % fval) \ - + scale_steps[idx][1] - break - idx += 1 - - return dval - - -def location_3d_to_region_2d(region, rv3d, coord): - prj = rv3d.perspective_matrix * Vector((coord[0], coord[1], coord[2], 1.0)) - width_half = region.width / 2.0 - height_half = region.height / 2.0 - return Vector((width_half + width_half * (prj.x / prj.w), - height_half + height_half * (prj.y / prj.w), - prj.z / prj.w - )) - - -def out_Location(rv3d, region, orig, vector): - view_matrix = rv3d.view_matrix - v1 = Vector((int(view_matrix[0][0] * 1.5), int(view_matrix[0][1] * 1.5), int(view_matrix[0][2] * 1.5))) - v2 = Vector((int(view_matrix[1][0] * 1.5), int(view_matrix[1][1] * 1.5), int(view_matrix[1][2] * 1.5))) - - hit = intersect_ray_tri(Vector((1, 0, 0)), Vector((0, 1, 0)), Vector(), (vector), (orig), False) - if hit is None: - hit = intersect_ray_tri(v1, v2, Vector(), (vector), (orig), False) - if hit is None: - hit = intersect_ray_tri(v1, v2, Vector(), (-vector), (orig), False) - if hit is None: - hit = Vector() - return hit - - -def get_closest_edge(bm, point, dist): - r_edge = None - for edge in bm.edges: - v1 = edge.verts[0].co - v2 = edge.verts[1].co - # Test the BVH (AABB) first - for i in range(3): - if v1[i] <= v2[i]: - isect = v1[i] - dist <= point[i] <= v2[i] + dist - else: - isect = v2[i] - dist <= point[i] <= v1[i] + dist - - if not isect: - break - else: - ret = intersect_point_line(point, v1, v2) - - if ret[1] < 0.0: - tmp = v1 - elif ret[1] > 1.0: - tmp = v2 - else: - tmp = ret[0] - - new_dist = (point - tmp).length - if new_dist <= dist: - dist = new_dist - r_edge = edge - - return r_edge - - -class SnapCache(): - bvert = None - vco = None - - bedge = None - v0 = None - v1 = None - vmid = None - vperp = None - v2d0 = None - v2d1 = None - v2dmid = None - v2dperp = None - - bm_geom_selected = None - - -def snap_utilities( - sctx, obj, - cache, context, obj_matrix_world, - bm, mcursor, - constrain = None, - previous_vert = None, - increment = 0.0): - - rv3d = context.region_data - region = context.region - scene = context.scene - is_increment = False - r_loc = None - r_type = None - r_len = 0.0 - bm_geom = None - - if cache.bm_geom_selected: - try: - cache.bm_geom_selected.select = False - except ReferenceError as e: - print(e) - - snp_obj, loc, elem = sctx.snap_get(mcursor) - view_vector, orig = sctx.last_ray - - if not snp_obj: - is_increment = True - if constrain: - end = orig + view_vector - t_loc = intersect_line_line(constrain[0], constrain[1], orig, end) - if t_loc is None: - t_loc = constrain - r_loc = t_loc[0] - else: - r_type = 'OUT' - r_loc = out_Location(rv3d, region, orig, view_vector) - - elif snp_obj.data[0] != obj: #OUT - r_loc = loc - - if constrain: - is_increment = False - r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0] - if not r_loc: - r_loc = out_Location(rv3d, region, orig, view_vector) - elif len(elem) == 1: - is_increment = False - r_type = 'VERT' - elif len(elem) == 2: - is_increment = True - r_type = 'EDGE' - else: - is_increment = True - r_type = 'FACE' - - elif len(elem) == 1: - r_type = 'VERT' - bm_geom = bm.verts[elem[0]] - - if cache.bvert != bm_geom: - cache.bvert = bm_geom - cache.vco = loc - #cache.v2d = location_3d_to_region_2d(region, rv3d, cache.vco) - - if constrain: - r_loc = intersect_point_line(cache.vco, constrain[0], constrain[1])[0] - else: - r_loc = cache.vco - - elif len(elem) == 2: - v1 = bm.verts[elem[0]] - v2 = bm.verts[elem[1]] - bm_geom = bm.edges.get([v1, v2]) - - if cache.bedge != bm_geom: - cache.bedge = bm_geom - cache.v0 = obj_matrix_world * v1.co - cache.v1 = obj_matrix_world * v2.co - cache.vmid = 0.5 * (cache.v0 + cache.v1) - cache.v2d0 = location_3d_to_region_2d(region, rv3d, cache.v0) - cache.v2d1 = location_3d_to_region_2d(region, rv3d, cache.v1) - cache.v2dmid = location_3d_to_region_2d(region, rv3d, cache.vmid) - - if previous_vert and previous_vert not in {v1, v2}: - pvert_co = obj_matrix_world * previous_vert.co - perp_point = intersect_point_line(pvert_co, cache.v0, cache.v1) - cache.vperp = perp_point[0] - #factor = point_perpendicular[1] - cache.v2dperp = location_3d_to_region_2d(region, rv3d, perp_point[0]) - - #else: cache.v2dperp = None - - if constrain: - t_loc = intersect_line_line(constrain[0], constrain[1], cache.v0, cache.v1) - if t_loc is None: - is_increment = True - end = orig + view_vector - t_loc = intersect_line_line(constrain[0], constrain[1], orig, end) - r_loc = t_loc[0] - - elif cache.v2dperp and\ - abs(cache.v2dperp[0] - mcursor[0]) < 10 and abs(cache.v2dperp[1] - mcursor[1]) < 10: - r_type = 'PERPENDICULAR' - r_loc = cache.vperp - - elif abs(cache.v2dmid[0] - mcursor[0]) < 10 and abs(cache.v2dmid[1] - mcursor[1]) < 10: - r_type = 'CENTER' - r_loc = cache.vmid - - else: - if increment and previous_vert in cache.bedge.verts: - is_increment = True - - r_type = 'EDGE' - r_loc = loc - - elif len(elem) == 3: - is_increment = True - r_type = 'FACE' - tri = [ - bm.verts[elem[0]], - bm.verts[elem[1]], - bm.verts[elem[2]], - ] - - faces = set(tri[0].link_faces).intersection(tri[1].link_faces, tri[2].link_faces) - if len(faces) == 1: - bm_geom = faces.pop() - else: - i = -2 - edge = None - while not edge and i != 1: - edge = bm.edges.get([tri[i], tri[i + 1]]) - i += 1 - if edge: - for l in edge.link_loops: - if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]: - bm_geom = l.face - break - else: # This should never happen!!!! - raise - bm_geom = faces.pop() - - r_loc = loc - - if constrain: - is_increment = False - r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0] - - if previous_vert: - pv_co = obj_matrix_world * previous_vert.co - vec = r_loc - pv_co - if is_increment and increment: - r_len = round((1 / increment) * vec.length) * increment - r_loc = r_len * vec.normalized() + pv_co - else: - r_len = vec.length - - if bm_geom: - bm_geom.select = True - - cache.bm_geom_selected = bm_geom - - return r_loc, r_type, bm_geom, r_len - - -def get_loose_linked_edges(bmvert): - linked = [e for e in bmvert.link_edges if not e.link_faces] - for e in linked: - linked += [le for v in e.verts if not v.link_faces for le in v.link_edges if le not in linked] - return linked - - -def draw_line(self, obj, bm, bm_geom, location): - split_faces = set() - - drawing_is_dirt = False - update_edit_mesh = False - tessface = False - - if bm_geom is None: - vert = bm.verts.new(location) - self.list_verts.append(vert) - - elif isinstance(bm_geom, bmesh.types.BMVert): - if (bm_geom.co - location).length_squared < .001: - if self.list_verts == [] or self.list_verts[-1] != bm_geom: - self.list_verts.append(bm_geom) - else: - vert = bm.verts.new(location) - self.list_verts.append(vert) - drawing_is_dirt = True - - elif isinstance(bm_geom, bmesh.types.BMEdge): - self.list_edges.append(bm_geom) - ret = intersect_point_line(location, bm_geom.verts[0].co, bm_geom.verts[1].co) - - if (ret[0] - location).length_squared < .001: - if ret[1] == 0.0: - vert = bm_geom.verts[0] - elif ret[1] == 1.0: - vert = bm_geom.verts[1] - else: - edge, vert = bmesh.utils.edge_split(bm_geom, bm_geom.verts[0], ret[1]) - drawing_is_dirt = True - self.list_verts.append(vert) - # self.list_edges.append(edge) - - else: # constrain point is near - vert = bm.verts.new(location) - self.list_verts.append(vert) - drawing_is_dirt = True - - elif isinstance(bm_geom, bmesh.types.BMFace): - split_faces.add(bm_geom) - vert = bm.verts.new(location) - self.list_verts.append(vert) - drawing_is_dirt = True - - # draw, split and create face - if len(self.list_verts) >= 2: - v1, v2 = self.list_verts[-2:] - # v2_link_verts = [x for y in [a.verts for a in v2.link_edges] for x in y if x != v2] - edge = bm.edges.get([v1, v2]) - if edge: - self.list_edges.append(edge) - - else: # if v1 not in v2_link_verts: - if not v2.link_edges: - edge = bm.edges.new([v1, v2]) - self.list_edges.append(edge) - drawing_is_dirt = True - else: # split face - v1_link_faces = v1.link_faces - v2_link_faces = v2.link_faces - if v1_link_faces and v2_link_faces: - split_faces.update(set(v1_link_faces).intersection(v2_link_faces)) - - else: - if v1_link_faces: - faces = v1_link_faces - co2 = v2.co.copy() - else: - faces = v2_link_faces - co2 = v1.co.copy() - - for face in faces: - if bmesh.geometry.intersect_face_point(face, co2): - co = co2 - face.calc_center_median() - if co.dot(face.normal) < 0.001: - split_faces.add(face) - - if split_faces: - edge = bm.edges.new([v1, v2]) - self.list_edges.append(edge) - ed_list = get_loose_linked_edges(v2) - for face in split_faces: - facesp = bmesh.utils.face_split_edgenet(face, ed_list) - del split_faces - update_edit_mesh = True - tessface = True - else: - if self.intersect: - facesp = bmesh.ops.connect_vert_pair(bm, verts=[v1, v2], verts_exclude=bm.verts) - # print(facesp) - if not self.intersect or not facesp['edges']: - edge = bm.edges.new([v1, v2]) - self.list_edges.append(edge) - drawing_is_dirt = True - else: - for edge in facesp['edges']: - self.list_edges.append(edge) - update_edit_mesh = True - tessface = True - - # create face - if self.create_face: - ed_list = set(self.list_edges) - for edge in v2.link_edges: - for vert in edge.verts: - if vert != v2 and vert in self.list_verts: - ed_list.add(edge) - break - else: - continue - # Inner loop had a break, break the outer - break - - ed_list.update(get_loose_linked_edges(v2)) - - bmesh.ops.edgenet_fill(bm, edges=list(ed_list)) - update_edit_mesh = True - tessface = True - # print('face created') - if update_edit_mesh: - bmesh.update_edit_mesh(obj.data, tessface = tessface) - self.sctx.update_drawn_snap_object(self.snap_obj) - #bm.verts.index_update() - elif drawing_is_dirt: - self.obj.update_from_editmode() - self.sctx.update_drawn_snap_object(self.snap_obj) - - return [obj.matrix_world * v.co for v in self.list_verts] - - -class NavigationKeys: - def __init__(self, context): - # TO DO: - # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View' - self._rotate = set() - self._move = set() - self._zoom = set() - for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items: - if key.idname == 'view3d.rotate': - #self.keys_rotate[key.id]={'Alt': key.alt, 'Ctrl': key.ctrl, 'Shift':key.shift, 'Type':key.type, 'Value':key.value} - self._rotate.add((key.alt, key.ctrl, key.shift, key.type, key.value)) - if key.idname == 'view3d.move': - self._move.add((key.alt, key.ctrl, key.shift, key.type, key.value)) - if key.idname == 'view3d.zoom': - if key.type == 'WHEELINMOUSE': - self._zoom.add((key.alt, key.ctrl, key.shift, 'WHEELUPMOUSE', key.value, key.properties.delta)) - elif key.type == 'WHEELOUTMOUSE': - self._zoom.add((key.alt, key.ctrl, key.shift, 'WHEELDOWNMOUSE', key.value, key.properties.delta)) - else: - self._zoom.add((key.alt, key.ctrl, key.shift, key.type, key.value, key.properties.delta)) - - -class CharMap: - ascii = { - ".", ",", "-", "+", "1", "2", "3", - "4", "5", "6", "7", "8", "9", "0", - "c", "m", "d", "k", "h", "a", - " ", "/", "*", "'", "\"" - # "=" - } - type = { - 'BACK_SPACE', 'DEL', - 'LEFT_ARROW', 'RIGHT_ARROW' - } - - @staticmethod - def modal(self, context, event): - c = event.ascii - if c: - if c == ",": - c = "." - self.length_entered = self.length_entered[:self.line_pos] + c + self.length_entered[self.line_pos:] - self.line_pos += 1 - if self.length_entered: - if event.type == 'BACK_SPACE': - self.length_entered = self.length_entered[:self.line_pos - 1] + self.length_entered[self.line_pos:] - self.line_pos -= 1 - - elif event.type == 'DEL': - self.length_entered = self.length_entered[:self.line_pos] + self.length_entered[self.line_pos + 1:] - - elif event.type == 'LEFT_ARROW': - self.line_pos = (self.line_pos - 1) % (len(self.length_entered) + 1) - - elif event.type == 'RIGHT_ARROW': - self.line_pos = (self.line_pos + 1) % (len(self.length_entered) + 1) - - -class SnapUtilitiesLine(Operator): - bl_idname = "mesh.snap_utilities_line" - bl_label = "Line Tool" - bl_description = "Draw edges. Connect them to split faces" - bl_options = {'REGISTER', 'UNDO'} - - constrain_keys = { - 'X': Vector((1, 0, 0)), - 'Y': Vector((0, 1, 0)), - 'Z': Vector((0, 0, 1)), - 'RIGHT_SHIFT': 'shift', - 'LEFT_SHIFT': 'shift', - } - - @classmethod - def poll(cls, context): - preferences = context.user_preferences.addons[__name__].preferences - return (context.mode in {'EDIT_MESH', 'OBJECT'} and - preferences.create_new_obj or - (context.object is not None and - context.object.type == 'MESH')) - - - def draw_callback_px(self, context): - # draw 3d point OpenGL in the 3D View - bgl.glEnable(bgl.GL_BLEND) - bgl.glDisable(bgl.GL_DEPTH_TEST) - # bgl.glPushMatrix() - # bgl.glMultMatrixf(self.obj_glmatrix) - -## if DEBUG: -## mesh_drawing._store_current_shader_state(mesh_drawing.PreviousGLState) -## self.screen.Draw(self.sctx._texture) -## mesh_drawing._restore_shader_state(mesh_drawing.PreviousGLState) - - if self.vector_constrain: - vc = self.vector_constrain - if hasattr(self, 'preloc') and self.type in {'VERT', 'FACE'}: - bgl.glColor4f(1.0, 1.0, 1.0, 0.5) - bgl.glPointSize(5) - bgl.glBegin(bgl.GL_POINTS) - bgl.glVertex3f(*self.preloc) - bgl.glEnd() - if vc[2] == 'X': - Color4f = (self.axis_x_color + (1.0,)) - elif vc[2] == 'Y': - Color4f = (self.axis_y_color + (1.0,)) - elif vc[2] == 'Z': - Color4f = (self.axis_z_color + (1.0,)) - else: - Color4f = self.constrain_shift_color - else: - if self.type == 'OUT': - Color4f = self.out_color - elif self.type == 'FACE': - Color4f = self.face_color - elif self.type == 'EDGE': - Color4f = self.edge_color - elif self.type == 'VERT': - Color4f = self.vert_color - elif self.type == 'CENTER': - Color4f = self.center_color - elif self.type == 'PERPENDICULAR': - Color4f = self.perpendicular_color - else: # self.type == None - Color4f = self.out_color - - bgl.glColor4f(*Color4f) - bgl.glPointSize(10) - bgl.glBegin(bgl.GL_POINTS) - bgl.glVertex3f(*self.location) - bgl.glEnd() - - # draw 3d line OpenGL in the 3D View - bgl.glEnable(bgl.GL_DEPTH_TEST) - bgl.glDepthRange(0, 0.9999) - bgl.glColor4f(1.0, 0.8, 0.0, 1.0) - bgl.glLineWidth(2) - bgl.glEnable(bgl.GL_LINE_STIPPLE) - bgl.glBegin(bgl.GL_LINE_STRIP) - for vert_co in self.list_verts_co: - bgl.glVertex3f(*vert_co) - bgl.glVertex3f(*self.location) - bgl.glEnd() - - # restore opengl defaults - # bgl.glPopMatrix() - bgl.glDepthRange(0, 1) - bgl.glPointSize(1) - bgl.glLineWidth(1) - bgl.glDisable(bgl.GL_BLEND) - bgl.glDisable(bgl.GL_LINE_STIPPLE) - bgl.glColor4f(0.0, 0.0, 0.0, 1.0) - - - def modal_navigation(self, context, event): - evkey = (event.alt, event.ctrl, event.shift, event.type, event.value) - if evkey in self.navigation_keys._rotate: - bpy.ops.view3d.rotate('INVOKE_DEFAULT') - return True - elif evkey in self.navigation_keys._move: - if event.shift and self.vector_constrain and \ - self.vector_constrain[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}: - self.vector_constrain = None - bpy.ops.view3d.move('INVOKE_DEFAULT') - return True - else: - for key in self.navigation_keys._zoom: - if evkey == key[0:5]: - if True: # TODO: Use Zoom to mouse position - v3d = context.space_data - dist_range = (v3d.clip_start, v3d.clip_end) - rv3d = context.region_data - if (key[5] < 0 and rv3d.view_distance < dist_range[1]) or\ - (key[5] > 0 and rv3d.view_distance > dist_range[0]): - rv3d.view_location += key[5] * (self.location - rv3d.view_location) / 6 - rv3d.view_distance -= key[5] * rv3d.view_distance / 6 - else: - bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta = key[5]) - return True - #break - - return False - - - def modal(self, context, event): - if self.modal_navigation(context, event): - return {'RUNNING_MODAL'} - - context.area.tag_redraw() - - if event.ctrl and event.type == 'Z' and event.value == 'PRESS': - bpy.ops.ed.undo() - self.vector_constrain = None - self.list_verts_co = [] - self.list_verts = [] - self.list_edges = [] - self.obj = bpy.context.active_object - self.obj_matrix = self.obj.matrix_world.copy() - self.bm = bmesh.from_edit_mesh(self.obj.data) - self.sctx.update_drawn_snap_object(self.snap_obj) - return {'RUNNING_MODAL'} - - if event.type == 'MOUSEMOVE' or self.bool_update: - if self.rv3d.view_matrix != self.rotMat: - self.rotMat = self.rv3d.view_matrix.copy() - self.bool_update = True - self.cache.bedge = None - else: - self.bool_update = False - - mval = Vector((event.mouse_region_x, event.mouse_region_y)) - - self.location, self.type, self.geom, self.len = snap_utilities( - self.sctx, self.obj, self.cache, context, self.obj_matrix, - self.bm, mval, - constrain = self.vector_constrain, - previous_vert = (self.list_verts[-1] if self.list_verts else None), - increment = self.incremental - ) - if self.snap_to_grid and self.type == 'OUT': - loc = self.location / self.rd - self.location = Vector((round(loc.x), - round(loc.y), - round(loc.z))) * self.rd - - if self.keyf8 and self.list_verts_co: - lloc = self.list_verts_co[-1] - view_vec, orig = self.sctx.last_ray - location = intersect_point_line(lloc, orig, (orig + view_vec)) - vec = (location[0] - lloc) - ax, ay, az = abs(vec.x), abs(vec.y), abs(vec.z) - vec.x = ax > ay > az or ax > az > ay - vec.y = ay > ax > az or ay > az > ax - vec.z = az > ay > ax or az > ax > ay - if vec == Vector(): - self.vector_constrain = None - else: - vc = lloc + vec - try: - if vc != self.vector_constrain[1]: - type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift' - self.vector_constrain = [lloc, vc, type] - except: - type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift' - self.vector_constrain = [lloc, vc, type] - - if event.value == 'PRESS': - if self.list_verts_co and (event.ascii in CharMap.ascii or event.type in CharMap.type): - CharMap.modal(self, context, event) - - elif event.type in self.constrain_keys: - self.bool_update = True - if self.vector_constrain and self.vector_constrain[2] == event.type: - self.vector_constrain = () - - else: - if event.shift: - if isinstance(self.geom, bmesh.types.BMEdge): - if self.list_verts: - loc = self.list_verts_co[-1] - self.vector_constrain = (loc, loc + self.geom.verts[1].co - - self.geom.verts[0].co, event.type) - else: - self.vector_constrain = [self.obj_matrix * v.co for - v in self.geom.verts] + [event.type] - else: - if self.list_verts: - loc = self.list_verts_co[-1] - else: - loc = self.location - self.vector_constrain = [loc, loc + self.constrain_keys[event.type]] + [event.type] - - elif event.type == 'LEFTMOUSE': - point = self.obj_matinv * self.location - # with constraint the intersection can be in a different element of the selected one - if self.vector_constrain and self.geom: - geom2 = get_closest_edge(self.bm, point, 0.001) - else: - geom2 = self.geom - - self.vector_constrain = None - self.list_verts_co = draw_line(self, self.obj, self.bm, geom2, point) - bpy.ops.ed.undo_push(message="Undo draw line*") - - elif event.type == 'TAB': - self.keytab = self.keytab is False - if self.keytab: - self.sctx.set_snap_mode(False, False, True) - context.tool_settings.mesh_select_mode = (False, False, True) - else: - self.sctx.set_snap_mode(True, True, True) - context.tool_settings.mesh_select_mode = (True, True, True) - - elif event.type == 'F8': - self.vector_constrain = None - self.keyf8 = self.keyf8 is False - - elif event.value == 'RELEASE': - if event.type in {'RET', 'NUMPAD_ENTER'}: - if self.length_entered != "" and self.list_verts_co: - try: - text_value = bpy.utils.units.to_value(self.unit_system, 'LENGTH', self.length_entered) - vector = (self.location - self.list_verts_co[-1]).normalized() - location = (self.list_verts_co[-1] + (vector * text_value)) - G_location = self.obj_matinv * location - self.list_verts_co = draw_line(self, self.obj, self.bm, self.geom, G_location) - self.length_entered = "" - self.vector_constrain = None - - except: # ValueError: - self.report({'INFO'}, "Operation not supported yet") - - elif event.type in {'RIGHTMOUSE', 'ESC'}: - if self.list_verts_co == [] or event.type == 'ESC': - del self.bm - del self.list_edges - del self.list_verts - del self.list_verts_co - - bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') - context.area.header_text_set("") - self.sctx.free() - - #restore initial state - context.user_preferences.view.use_rotate_around_active = self.use_rotate_around_active - context.tool_settings.mesh_select_mode = self.select_mode - if not self.is_editmode: - bpy.ops.object.editmode_toggle() - - return {'FINISHED'} - else: - self.vector_constrain = None - self.list_edges = [] - self.list_verts = [] - self.list_verts_co = [] - - a = "" - if self.list_verts_co: - if self.length_entered: - pos = self.line_pos - a = 'length: ' + self.length_entered[:pos] + '|' + self.length_entered[pos:] - else: - length = self.len - length = convert_distance(length, self.uinfo) - a = 'length: ' + length - - context.area.header_text_set( - "hit: %.3f %.3f %.3f %s" % (self.location[0], - self.location[1], self.location[2], a) - ) - - return {'RUNNING_MODAL'} - - def invoke(self, context, event): - if context.space_data.type == 'VIEW_3D': - # print('name', __name__, __package__) - preferences = context.user_preferences.addons[__name__].preferences - - #Store the preferences that will be used in modal - self.intersect = preferences.intersect - self.create_face = preferences.create_face - self.outer_verts = preferences.outer_verts - self.snap_to_grid = preferences.increments_grid - - self.out_color = preferences.out_color - self.face_color = preferences.face_color - self.edge_color = preferences.edge_color - self.vert_color = preferences.vert_color - self.center_color = preferences.center_color - self.perpendicular_color = preferences.perpendicular_color - self.constrain_shift_color = preferences.constrain_shift_color - - self.axis_x_color = tuple(context.user_preferences.themes[0].user_interface.axis_x) - self.axis_y_color = tuple(context.user_preferences.themes[0].user_interface.axis_y) - self.axis_z_color = tuple(context.user_preferences.themes[0].user_interface.axis_z) - - if context.mode == 'OBJECT' and \ - (preferences.create_new_obj or context.object is None or context.object.type != 'MESH'): - - mesh = bpy.data.meshes.new("") - obj = bpy.data.objects.new("", mesh) - context.scene.objects.link(obj) - context.scene.objects.active = obj - - #Store current state - self.is_editmode = context.object.data.is_editmode - self.use_rotate_around_active = context.user_preferences.view.use_rotate_around_active - self.select_mode = context.tool_settings.mesh_select_mode[:] - - #Modify the current state - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_all(action='DESELECT') - context.user_preferences.view.use_rotate_around_active = True - context.tool_settings.mesh_select_mode = (True, True, True) - context.space_data.use_occlude_geometry = True - - #Configure the unit of measure - scale = context.scene.unit_settings.scale_length - self.unit_system = context.scene.unit_settings.system - separate_units = context.scene.unit_settings.use_separate - self.uinfo = get_units_info(scale, self.unit_system, separate_units) - - scale /= context.space_data.grid_scale * preferences.relative_scale - self.rd = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(1 / scale)) - - self.incremental = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(preferences.incremental)) - - #Store values from 3d view context - self.rv3d = context.region_data - self.rotMat = self.rv3d.view_matrix.copy() - self.obj = bpy.context.active_object - self.obj_matrix = self.obj.matrix_world.copy() - self.obj_matinv = self.obj_matrix.inverted() - # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4], self.obj_matrix.transposed()) - self.bm = bmesh.from_edit_mesh(self.obj.data) #remove at end - self.cache = SnapCache() - - #init these variables to avoid errors - self.prevloc = Vector() - self.location = Vector() - self.list_verts = [] - self.list_edges = [] - self.list_verts_co = [] - self.bool_update = False - self.vector_constrain = () - self.navigation_keys = NavigationKeys(context) - self.type = 'OUT' - self.len = 0 - self.length_entered = "" - self.line_pos = 0 - self.bm_geom_selected = None - - #Init event variables - self.keytab = False - self.keyf8 = False - - #Init Snap Context - from snap_context import SnapContext - - self.sctx = SnapContext(context.region, context.space_data) - self.sctx.set_pixel_dist(12) - self.sctx.use_clip_planes(True) - - act_base = context.active_base - - if self.outer_verts: - for base in context.visible_bases: - if base != act_base: - self.sctx.add_obj(base.object, base.object.matrix_world) - - self.snap_obj = self.sctx.add_obj(act_base.object, act_base.object.matrix_world) - - self.snap_face = context.space_data.viewport_shade not in {'BOUNDBOX', 'WIREFRAME'} - self.sctx.set_snap_mode(True, True, self.snap_face) - - #modals - self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (context,), 'WINDOW', 'POST_VIEW') - context.window_manager.modal_handler_add(self) - - return {'RUNNING_MODAL'} - else: - self.report({'WARNING'}, "Active space must be a View3d") - return {'CANCELLED'} - - -class PanelSnapUtilities(Panel): - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_category = "Snap Utilities" - bl_label = "Snap Utilities" - - @classmethod - def poll(cls, context): - preferences = context.user_preferences.addons[__name__].preferences - return (context.mode in {'EDIT_MESH', 'OBJECT'} and - preferences.create_new_obj or - (context.object is not None and - context.object.type == 'MESH')) - - def draw(self, context): - layout = self.layout - TheCol = layout.column(align=True) - TheCol.operator("mesh.snap_utilities_line", text="Line", icon="GREASEPENCIL") - - addon_prefs = context.user_preferences.addons[__name__].preferences - expand = addon_prefs.expand_snap_settings - icon = "TRIA_DOWN" if expand else "TRIA_RIGHT" - - box = layout.box() - box.prop(addon_prefs, "expand_snap_settings", icon=icon, - text="Settings:", emboss=False) - if expand: - box.prop(addon_prefs, "outer_verts") - box.prop(addon_prefs, "incremental") - box.prop(addon_prefs, "increments_grid") - if addon_prefs.increments_grid: - box.prop(addon_prefs, "relative_scale") - box.label(text="Line Tool:") - box.prop(addon_prefs, "intersect") - box.prop(addon_prefs, "create_face") - box.prop(addon_prefs, "create_new_obj") - - -# Add-ons Preferences Update Panel - -# Define Panel classes for updating -panels = ( - PanelSnapUtilities, - ) - - -def update_panel(self, context): - message = "Snap Utilities Line: Updating Panel locations has failed" - addon_prefs = context.user_preferences.addons[__name__].preferences - try: - for panel in panels: - if addon_prefs.category != panel.bl_category: - if "bl_rna" in panel.__dict__: - bpy.utils.unregister_class(panel) - - panel.bl_category = addon_prefs.category - bpy.utils.register_class(panel) - - except Exception as e: - print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e)) - pass - - -class SnapAddonPreferences(AddonPreferences): - # this must match the addon name, use '__package__' - # when defining this in a submodule of a python package. - bl_idname = __name__ - - intersect = BoolProperty( - name="Intersect", - description="Intersects created line with the existing edges, " - "even if the lines do not intersect", - default=True - ) - create_new_obj = BoolProperty( - name="Create a new object", - description="If have not a active object, or the active object " - "is not in edit mode, it creates a new object", - default=False - ) - create_face = BoolProperty( - name="Create faces", - description="Create faces defined by enclosed edges", - default=False - ) - outer_verts = BoolProperty( - name="Snap to outer vertices", - description="The vertices of the objects are not activated also snapped", - default=True - ) - expand_snap_settings = BoolProperty( - name="Expand", - description="Expand, to display the settings", - default=False - ) - expand_color_settings = BoolProperty( - name="Color Settings", - description="Expand, to display the color settings", - default=False - ) - increments_grid = BoolProperty( - name="Increments of Grid", - description="Snap to increments of grid", - default=False - ) - category = StringProperty( - name="Category", - description="Choose a name for the category of the panel", - default="Snap Utilities", - update=update_panel - ) - incremental = FloatProperty( - name="Incremental", - description="Snap in defined increments", - default=0, - min=0, - step=1, - precision=3 - ) - relative_scale = FloatProperty( - name="Relative Scale", - description="Value that divides the global scale", - default=1, - min=0, - step=1, - precision=3 - ) - out_color = FloatVectorProperty( - name="OUT", - default=(0.0, 0.0, 0.0, 0.5), - size=4, - subtype="COLOR", - min=0, max=1 - ) - face_color = FloatVectorProperty( - name="FACE", - default=(1.0, 0.8, 0.0, 1.0), - size=4, - subtype="COLOR", - min=0, max=1 - ) - edge_color = FloatVectorProperty( - name="EDGE", - default=(0.0, 0.8, 1.0, 1.0), - size=4, - subtype="COLOR", - min=0, max=1 - ) - vert_color = FloatVectorProperty( - name="VERT", - default=(1.0, 0.5, 0.0, 1.0), - size=4, - subtype="COLOR", - min=0, max=1 - ) - center_color = FloatVectorProperty( - name="CENTER", - default=(1.0, 0.0, 1.0, 1.0), - size=4, - subtype="COLOR", - min=0, max=1 - ) - perpendicular_color = FloatVectorProperty( - name="PERPENDICULAR", - default=(0.1, 0.5, 0.5, 1.0), - size=4, - subtype="COLOR", - min=0, max=1 - ) - constrain_shift_color = FloatVectorProperty( - name="SHIFT CONSTRAIN", - default=(0.8, 0.5, 0.4, 1.0), - size=4, - subtype="COLOR", - min=0, max=1 - ) - - def draw(self, context): - layout = self.layout - icon = "TRIA_DOWN" if self.expand_color_settings else "TRIA_RIGHT" - - box = layout.box() - box.prop(self, "expand_color_settings", icon=icon, toggle=True, emboss=False) - if self.expand_color_settings: - split = box.split() - - col = split.column() - col.prop(self, "out_color") - col.prop(self, "constrain_shift_color") - col = split.column() - col.prop(self, "face_color") - col.prop(self, "center_color") - col = split.column() - col.prop(self, "edge_color") - col.prop(self, "perpendicular_color") - col = split.column() - col.prop(self, "vert_color") - - row = layout.row() - - col = row.column(align=True) - box = col.box() - box.label(text="Snap Items:") - box.prop(self, "incremental") - box.prop(self, "outer_verts") - box.prop(self, "increments_grid") - if self.increments_grid: - box.prop(self, "relative_scale") - else: - box.separator() - box.separator() - - col = row.column(align=True) - box = col.box() - box.label(text="Line Tool:") - box.prop(self, "intersect") - box.prop(self, "create_face") - box.prop(self, "create_new_obj") - box.separator() - box.separator() - - row = layout.row() - col = row.column() - col.label(text="Tab Category:") - col.prop(self, "category", text="") - - -def register(): - bpy.utils.register_class(SnapAddonPreferences) - bpy.utils.register_class(SnapUtilitiesLine) - bpy.utils.register_class(PanelSnapUtilities) - update_panel(None, bpy.context) - - -def unregister(): - bpy.utils.unregister_class(PanelSnapUtilities) - bpy.utils.unregister_class(SnapUtilitiesLine) - bpy.utils.unregister_class(SnapAddonPreferences) - - -if __name__ == "__main__": - __name__ = "mesh_snap_utilities_line" - register() diff --git a/mesh_snap_utilities_line/__init__.py b/mesh_snap_utilities_line/__init__.py new file mode 100644 index 00000000..c6c5f27c --- /dev/null +++ b/mesh_snap_utilities_line/__init__.py @@ -0,0 +1,112 @@ +### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ##### END GPL LICENSE BLOCK ##### + +# Contact for more information about the Addon: +# Email: germano.costa@ig.com.br +# Twitter: wii_mano @mano_wii + +bl_info = { + "name": "Snap_Utilities_Line", + "author": "Germano Cavalcante", + "version": (5, 8, 24), + "blender": (0, 0, 0), + "location": "View3D > TOOLS > Make Line", + "description": "Extends Blender Snap controls", + #"wiki_url" : "http://blenderartists.org/forum/showthread.php?363859-Addon-CAD-Snap-Utilities", + "category": "Mesh"} + +if "bpy" in locals(): + import importlib + importlib.reload(preferences) + importlib.reload(ops_line) + importlib.reload(common_classes) +else: + from . import preferences + from . import ops_line + +import bpy +from bpy.utils.toolsystem import ToolDef + +@ToolDef.from_fn +def tool_make_line(): + import os + def draw_settings(context, layout, tool): + addon_prefs = context.user_preferences.addons["mesh_snap_utilities_line"].preferences + + layout.prop(addon_prefs, "incremental") + layout.prop(addon_prefs, "increments_grid") + if addon_prefs.increments_grid: + layout.prop(addon_prefs, "relative_scale") + layout.prop(addon_prefs, "create_face") + layout.prop(addon_prefs, "outer_verts") + #props = tool.operator_properties("mesh.snap_utilities_line") + #layout.prop(props, "radius") + + icons_dir = os.path.join(os.path.dirname(__file__), "icons") + + return dict( + text="Make Line", + description=( + "Make Lines\n" + "Connect them to split faces" + ), + icon=os.path.join(icons_dir, "ops.mesh.make_line"), +# widget="MESH_GGT_mouse_point", + operator="mesh.make_line", + keymap=( + ("mesh.make_line", dict(type='LEFTMOUSE', value='PRESS'), None), + ), + draw_settings=draw_settings, + ) + + +def register(): + def get_tool_list(space_type, context_mode): + from bl_ui.space_toolsystem_common import ToolSelectPanelHelper + cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type) + return cls._tools[context_mode] + + bpy.utils.register_class(preferences.SnapUtilitiesLinePreferences) + bpy.utils.register_class(common_classes.VIEW3D_OT_rotate_custom_pivot) + bpy.utils.register_class(common_classes.VIEW3D_OT_zoom_custom_target) + bpy.utils.register_class(ops_line.SnapUtilitiesLine) +# bpy.utils.register_class(common_classes.MousePointWidget) +# bpy.utils.register_class(common_classes.MousePointWidgetGroup) + + bpy.utils.register_tool('VIEW_3D', 'EDIT_MESH', tool_make_line) + + # Move tool to after 'Add Cube' + tools = get_tool_list('VIEW_3D', 'EDIT_MESH') + for index, tool in enumerate(tools): + if isinstance(tool, ToolDef) and tool.text == "Add Cube": + break + tools.insert(index + 1, tools.pop(-1)) + +def unregister(): + bpy.utils.unregister_tool('VIEW_3D', 'EDIT_MESH', tool_make_line) + +# bpy.utils.unregister_class(common_classes.MousePointWidgetGroup) +# bpy.utils.unregister_class(common_classes.MousePointWidget) + bpy.utils.unregister_class(ops_line.SnapUtilitiesLine) + bpy.utils.unregister_class(common_classes.VIEW3D_OT_zoom_custom_target) + bpy.utils.unregister_class(common_classes.VIEW3D_OT_rotate_custom_pivot) + bpy.utils.unregister_class(preferences.SnapUtilitiesLinePreferences) + +if __name__ == "__main__": + __name__ = "mesh_snap_utilities_line" + __package__ = "mesh_snap_utilities_line" + register() diff --git a/mesh_snap_utilities_line/common_classes.py b/mesh_snap_utilities_line/common_classes.py new file mode 100644 index 00000000..8227f485 --- /dev/null +++ b/mesh_snap_utilities_line/common_classes.py @@ -0,0 +1,510 @@ +### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +import bgl +import gpu +import numpy as np + +from .common_utilities import snap_utilities + + +class SnapDrawn(): + def __init__(self, out_color, face_color, + edge_color, vert_color, center_color, + perpendicular_color, constrain_shift_color, + axis_x_color, axis_y_color, axis_z_color): + + self.out_color = out_color + self.face_color = face_color + self.edge_color = edge_color + self.vert_color = vert_color + self.center_color = center_color + self.perpendicular_color = perpendicular_color + self.constrain_shift_color = constrain_shift_color + + self.axis_x_color = axis_x_color + self.axis_y_color = axis_y_color + self.axis_z_color = axis_z_color + + self._format_pos = gpu.types.GPUVertFormat() + self._format_pos.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT') + + self._format_pos_and_color = gpu.types.GPUVertFormat() + self._format_pos_and_color.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT') + self._format_pos_and_color.attr_add(id="color", comp_type='F32', len=4, fetch_mode='FLOAT') + + self._program_unif_col = gpu.shader.from_builtin("3D_UNIFORM_COLOR") + self._program_smooth_col = gpu.shader.from_builtin("3D_SMOOTH_COLOR") + + self._batch_point = None + self._batch_circle = None + self._batch_vector = None + + + def batch_line_strip_create(self, coords): + vbo = gpu.types.GPUVertBuf(self._format_pos, len = len(coords)) + vbo.attr_fill(0, data = coords) + batch_lines = gpu.types.GPUBatch(type = "LINE_STRIP", buf = vbo) + return batch_lines + + def batch_lines_smooth_color_create(self, coords, colors): + vbo = gpu.types.GPUVertBuf(self._format_pos_and_color, len = len(coords)) + vbo.attr_fill(0, data = coords) + vbo.attr_fill(1, data = colors) + batch_lines = gpu.types.GPUBatch(type = "LINES", buf = vbo) + return batch_lines + + def batch_triangles_create(self, coords): + vbo = gpu.types.GPUVertBuf(self._format_pos, len = len(coords)) + vbo.attr_fill(0, data = coords) + batch_tris = gpu.types.GPUBatch(type = "TRIS", buf = vbo) + return batch_tris + + def batch_point_get(self): + if self._batch_point is None: + vbo = gpu.types.GPUVertBuf(self._format_pos, len = 1) + vbo.attr_fill(0, ((0.0, 0.0, 0.0),)) + self._batch_point = gpu.types.GPUBatch(type = "POINTS", buf = vbo) + return self._batch_point + + def draw(self, type, location, list_verts_co, vector_constrain, prevloc): + # draw 3d point OpenGL in the 3D View + bgl.glEnable(bgl.GL_BLEND) + gpu.matrix.push() + self._program_unif_col.bind() + + if list_verts_co: + # draw 3d line OpenGL in the 3D View + bgl.glDepthRange(0, 0.9999) + bgl.glLineWidth(3.0) + + batch = self.batch_line_strip_create([v.to_tuple() for v in list_verts_co] + [location.to_tuple()]) + + self._program_unif_col.uniform_float("color", (1.0, 0.8, 0.0, 0.5)) + batch.draw(self._program_unif_col) + del batch + + bgl.glDisable(bgl.GL_DEPTH_TEST) + + point_batch = self.batch_point_get() + if vector_constrain: + if prevloc: + bgl.glPointSize(5) + gpu.matrix.translate(prevloc) + self._program_unif_col.uniform_float("color", (1.0, 1.0, 1.0, 0.5)) + point_batch.draw(self._program_unif_col) + gpu.matrix.translate(-prevloc) + + if vector_constrain[2] == 'X': + Color4f = self.axis_x_color + elif vector_constrain[2] == 'Y': + Color4f = self.axis_y_color + elif vector_constrain[2] == 'Z': + Color4f = self.axis_z_color + else: + Color4f = self.constrain_shift_color + else: + if type == 'OUT': + Color4f = self.out_color + elif type == 'FACE': + Color4f = self.face_color + elif type == 'EDGE': + Color4f = self.edge_color + elif type == 'VERT': + Color4f = self.vert_color + elif type == 'CENTER': + Color4f = self.center_color + elif type == 'PERPENDICULAR': + Color4f = self.perpendicular_color + else: # type == None + Color4f = self.out_color + + bgl.glPointSize(10) + + gpu.matrix.translate(location) + self._program_unif_col.uniform_float("color", Color4f) + point_batch.draw(self._program_unif_col) + + # restore opengl defaults + bgl.glDepthRange(0.0, 1.0) + bgl.glPointSize(1.0) + bgl.glLineWidth(1.0) + bgl.glEnable(bgl.GL_DEPTH_TEST) + bgl.glDisable(bgl.GL_BLEND) + + gpu.matrix.pop() + + def draw_elem(self, snap_obj, bm, elem): + from bmesh.types import( + BMVert, + BMEdge, + BMFace, + ) + # draw 3d point OpenGL in the 3D View + bgl.glEnable(bgl.GL_BLEND) + bgl.glDisable(bgl.GL_DEPTH_TEST) + + with gpu.matrix.push_pop(): + gpu.matrix.multiply_matrix(snap_obj.mat) + + if isinstance(elem, BMVert): + if elem.link_edges: + color = self.vert_color + edges = np.empty((len(elem.link_edges), 2), [("pos", "f4", 3), ("color", "f4", 4)]) + edges["pos"][:, 0] = elem.co + edges["pos"][:, 1] = [e.other_vert(elem).co for e in elem.link_edges] + edges["color"][:, 0] = color + edges["color"][:, 1] = (color[0], color[1], color[2], 0.0) + edges.shape = -1 + + self._program_smooth_col.bind() + bgl.glLineWidth(3.0) + batch = self.batch_lines_smooth_color_create(edges["pos"], edges["color"]) + batch.draw(self._program_smooth_col) + bgl.glLineWidth(1.0) + else: + self._program_unif_col.bind() + + if isinstance(elem, BMEdge): + self._program_unif_col.uniform_float("color", self.edge_color) + + bgl.glLineWidth(3.0) + batch = self.batch_line_strip_create([v.co for v in elem.verts]) + batch.draw(self._program_unif_col) + bgl.glLineWidth(1.0) + + elif isinstance(elem, BMFace): + if len(snap_obj.data) == 2: + face_color = self.face_color[0], self.face_color[1], self.face_color[2], self.face_color[3] * 0.2 + self._program_unif_col.uniform_float("color", face_color) + + tris = snap_obj.data[1].get_loop_tri_co_by_bmface(bm, elem) + tris.shape = (-1, 3) + batch = self.batch_triangles_create(tris) + batch.draw(self._program_unif_col) + + # restore opengl defaults + bgl.glEnable(bgl.GL_DEPTH_TEST) + bgl.glDisable(bgl.GL_BLEND) + + +class SnapNavigation(): + @staticmethod + def debug_key(key): + for member in dir(key): + print(member, getattr(key, member)) + + @staticmethod + def convert_to_flag(shift, ctrl, alt): + return (shift << 0) | (ctrl << 1) | (alt << 2) + + def __init__(self, context, use_ndof): + # TO DO: + # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View' + self.use_ndof = use_ndof and context.user_preferences.inputs.use_ndof + + self._rotate = set() + self._move = set() + self._zoom = set() + + if self.use_ndof: + self._ndof_all = set() + self._ndof_orbit = set() + self._ndof_orbit_zoom = set() + self._ndof_pan = set() + + for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items: + if key.idname == 'view3d.rotate': + self._rotate.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value)) + elif key.idname == 'view3d.move': + self._move.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value)) + elif key.idname == 'view3d.zoom': + if key.type == 'WHEELINMOUSE': + self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELUPMOUSE', key.value, key.properties.delta)) + elif key.type == 'WHEELOUTMOUSE': + self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELDOWNMOUSE', key.value, key.properties.delta)) + else: + self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value, key.properties.delta)) + + elif self.use_ndof: + if key.idname == 'view3d.ndof_all': + self._ndof_all.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type)) + elif key.idname == 'view3d.ndof_orbit': + self._ndof_orbit.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type)) + elif key.idname == 'view3d.ndof_orbit_zoom': + self._ndof_orbit_zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type)) + elif key.idname == 'view3d.ndof_pan': + self._ndof_pan.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type)) + + + def run(self, context, event, snap_location): + evkey = (self.convert_to_flag(event.shift, event.ctrl, event.alt), event.type, event.value) + + if evkey in self._rotate: + if snap_location: + bpy.ops.view3d.rotate_custom_pivot('INVOKE_DEFAULT', pivot=snap_location) + else: + bpy.ops.view3d.rotate('INVOKE_DEFAULT', use_mouse_init=True) + return True + + if evkey in self._move: + #if event.shift and self.vector_constrain and \ + # self.vector_constrain[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}: + # self.vector_constrain = None + bpy.ops.view3d.move('INVOKE_DEFAULT') + return True + + for key in self._zoom: + if evkey == key[0:3]: + if snap_location and key[3]: + bpy.ops.view3d.zoom_custom_target('INVOKE_DEFAULT', delta=key[3], target=snap_location) + else: + bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta=key[3]) + return True + + if self.use_ndof: + ndofkey = evkey[:2] + if ndofkey in self._ndof_all: + bpy.ops.view3d.ndof_all('INVOKE_DEFAULT') + return True + if ndofkey in self._ndof_orbit: + bpy.ops.view3d.ndof_orbit('INVOKE_DEFAULT') + return True + if ndofkey in self._ndof_orbit_zoom: + bpy.ops.view3d.ndof_orbit_zoom('INVOKE_DEFAULT') + return True + if ndofkey in self._ndof_pan: + bpy.ops.view3d.ndof_pan('INVOKE_DEFAULT') + return True + + return False + + +class CharMap: + ascii = { + ".", ",", "-", "+", "1", "2", "3", + "4", "5", "6", "7", "8", "9", "0", + "c", "m", "d", "k", "h", "a", + " ", "/", "*", "'", "\"" + # "=" + } + type = { + 'BACK_SPACE', 'DEL', + 'LEFT_ARROW', 'RIGHT_ARROW' + } + + @staticmethod + def modal(self, context, event): + c = event.ascii + if c: + if c == ",": + c = "." + self.length_entered = self.length_entered[:self.line_pos] + c + self.length_entered[self.line_pos:] + self.line_pos += 1 + if self.length_entered: + if event.type == 'BACK_SPACE': + self.length_entered = self.length_entered[:self.line_pos - 1] + self.length_entered[self.line_pos:] + self.line_pos -= 1 + + elif event.type == 'DEL': + self.length_entered = self.length_entered[:self.line_pos] + self.length_entered[self.line_pos + 1:] + + elif event.type == 'LEFT_ARROW': + self.line_pos = (self.line_pos - 1) % (len(self.length_entered) + 1) + + elif event.type == 'RIGHT_ARROW': + self.line_pos = (self.line_pos + 1) % (len(self.length_entered) + 1) + +g_snap_widget = [None] + +class MousePointWidget(bpy.types.Gizmo): + bl_idname = "VIEW3D_GT_mouse_point" + + __slots__ = ( + "sctx", + "bm", + "draw_cache", + "geom", + "incremental", + "preferences", + "loc", + "snap_obj", + "snap_to_grid", + "type", + ) + + def test_select(self, context, mval): + #print('test_select', mval) + self.snap_obj, prev_loc, self.loc, self.type, self.bm, self.geom, len = snap_utilities( + self.sctx, + None, + mval, + increment=self.incremental + ) + context.area.tag_redraw() + return False + + def draw(self, context): + if self.bm: + self.draw_cache.draw_elem(self.snap_obj, self.bm, self.geom) + self.draw_cache.draw(self.type, self.loc, None, None, None) + + def setup(self): + if not hasattr(self, "sctx"): + global g_snap_widget + g_snap_widget[0] = self + + context = bpy.context + + self.preferences = preferences = context.user_preferences.addons[__package__].preferences + + #Configure the unit of measure + self.snap_to_grid = preferences.increments_grid + self.incremental = bpy.utils.units.to_value( + context.scene.unit_settings.system, 'LENGTH', str(preferences.incremental)) + + self.draw_cache = SnapDrawn( + preferences.out_color, + preferences.face_color, + preferences.edge_color, + preferences.vert_color, + preferences.center_color, + preferences.perpendicular_color, + preferences.constrain_shift_color, + (*context.user_preferences.themes[0].user_interface.axis_x, 1.0), + (*context.user_preferences.themes[0].user_interface.axis_y, 1.0), + (*context.user_preferences.themes[0].user_interface.axis_z, 1.0) + ) + + #Init Snap Context + from .snap_context_l import SnapContext + from mathutils import Vector + + self.sctx = SnapContext(context.region, context.space_data) + self.sctx.set_pixel_dist(12) + self.sctx.use_clip_planes(True) + + if preferences.outer_verts: + for base in context.visible_bases: + self.sctx.add_obj(base.object, base.object.matrix_world) + + self.sctx.set_snap_mode(True, True, True) + self.bm = None + self.type = 'OUT' + self.loc = Vector() + + def __del__(self): + global g_snap_widget + g_snap_widget[0] = None + + +class MousePointWidgetGroup(bpy.types.GizmoGroup): + bl_idname = "MESH_GGT_mouse_point" + bl_label = "Draw Mouse Point" + bl_space_type = 'VIEW_3D' + bl_region_type = 'WINDOW' + bl_options = {'3D'} + + def setup(self, context): + snap_widget = self.gizmos.new(MousePointWidget.bl_idname) + props = snap_widget.target_set_operator("mesh.make_line") + props.wait_for_input = False + + +class VIEW3D_OT_rotate_custom_pivot(bpy.types.Operator): + bl_idname = "view3d.rotate_custom_pivot" + bl_label = "Rotate the view" + bl_options = {'BLOCKING', 'GRAB_CURSOR'} + + pivot: bpy.props.FloatVectorProperty("Pivot", subtype='XYZ') + g_up_axis: bpy.props.FloatVectorProperty("up_axis", default=(0.0, 0.0, 1.0), subtype='XYZ') + sensitivity: bpy.props.FloatProperty("sensitivity", default=0.007) + + def modal(self, context, event): + from mathutils import Matrix + if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}: + dx = self.init_coord[0] - event.mouse_region_x + dy = self.init_coord[1] - event.mouse_region_y + rot_ver = Matrix.Rotation(-dx * self.sensitivity, 3, self.g_up_axis) + rot_hor = Matrix.Rotation(dy * self.sensitivity, 3, self.view_rot[0]) + rot_mat = rot_hor @ rot_ver + view_matrix = self.view_rot @ rot_mat + + pos = self.pos1 @ rot_mat + self.pivot + qua = view_matrix.to_quaternion() + qua.invert() + + self.rv3d.view_location = pos + self.rv3d.view_rotation = qua + + context.area.tag_redraw() + return {'RUNNING_MODAL'} + + return {'FINISHED'} + + def invoke(self, context, event): + self.rv3d = context.region_data + self.init_coord = event.mouse_region_x, event.mouse_region_y + self.pos1 = self.rv3d.view_location - self.pivot + self.view_rot = self.rv3d.view_matrix.to_3x3() + + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + +class VIEW3D_OT_zoom_custom_target(bpy.types.Operator): + bl_idname = "view3d.zoom_custom_target" + bl_label = "Zoom the view" + bl_options = {'BLOCKING', 'GRAB_CURSOR'} + + target: bpy.props.FloatVectorProperty("target", subtype='XYZ') + delta: bpy.props.IntProperty("delta", default=0) + step_factor = 0.333 + + def modal(self, context, event): + if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}: + if not hasattr(self, "init_mouse_region_y"): + self.init_mouse_region_y = event.mouse_region_y + self.heigt_up = context.area.height - self.init_mouse_region_y + self.rv3d.view_location = self.target + + fac = (event.mouse_region_y - self.init_mouse_region_y) / self.heigt_up + ret = 'RUNNING_MODAL' + else: + fac = self.step_factor * self.delta + ret = 'FINISHED' + + self.rv3d.view_location = self.init_loc + (self.target - self.init_loc) * fac + self.rv3d.view_distance = self.init_dist - self.init_dist * fac + + context.area.tag_redraw() + return {ret} + + def invoke(self, context, event): + v3d = context.space_data + dist_range = (v3d.clip_start, v3d.clip_end) + self.rv3d = context.region_data + self.init_dist = self.rv3d.view_distance + if ((self.delta <= 0 and self.init_dist < dist_range[1]) or + (self.delta > 0 and self.init_dist > dist_range[0])): + self.init_loc = self.rv3d.view_location.copy() + + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + return {'FINISHED'} diff --git a/mesh_snap_utilities_line/common_utilities.py b/mesh_snap_utilities_line/common_utilities.py new file mode 100644 index 00000000..2fc0ce6d --- /dev/null +++ b/mesh_snap_utilities_line/common_utilities.py @@ -0,0 +1,269 @@ +### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ##### END GPL LICENSE BLOCK ##### + +#python tip: from-imports don't save memory. +#They execute and cache the entire module just like a regular import. + +import bpy +import bmesh +from .snap_context_l import SnapContext +from mathutils import Vector +from mathutils.geometry import ( + intersect_point_line, + intersect_line_line, + intersect_line_plane, + intersect_ray_tri, + ) + + +def get_units_info(scale, unit_system, separate_units): + if unit_system == 'METRIC': + scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'), + (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m')) + elif unit_system == 'IMPERIAL': + scale_steps = ((5280, 'mi'), (1, '\''), + (1 / 12, '"'), (1 / 12000, 'thou')) + scale /= 0.3048 # BU to feet + else: + scale_steps = ((1, ' BU'),) + separate_units = False + + return (scale, scale_steps, separate_units) + + +def convert_distance(val, units_info, precision=5): + scale, scale_steps, separate_units = units_info + sval = val * scale + idx = 0 + while idx < len(scale_steps) - 1: + if sval >= scale_steps[idx][0]: + break + idx += 1 + factor, suffix = scale_steps[idx] + sval /= factor + if not separate_units or idx == len(scale_steps) - 1: + dval = str(round(sval, precision)) + suffix + else: + ival = int(sval) + dval = str(round(ival, precision)) + suffix + fval = sval - ival + idx += 1 + while idx < len(scale_steps): + fval *= scale_steps[idx - 1][0] / scale_steps[idx][0] + if fval >= 1: + dval += ' ' \ + + ("%.1f" % fval) \ + + scale_steps[idx][1] + break + idx += 1 + + return dval + + +def location_3d_to_region_2d(region, rv3d, coord): + prj = rv3d.perspective_matrix @ Vector((coord[0], coord[1], coord[2], 1.0)) + width_half = region.width / 2.0 + height_half = region.height / 2.0 + return Vector((width_half + width_half * (prj.x / prj.w), + height_half + height_half * (prj.y / prj.w), + prj.z / prj.w + )) + + +def out_Location(rv3d, orig, vector): + view_matrix = rv3d.view_matrix + v1 = (int(view_matrix[0][0]*1.5), int(view_matrix[0][1]*1.5), int(view_matrix[0][2]*1.5)) + v2 = (int(view_matrix[1][0]*1.5), int(view_matrix[1][1]*1.5), int(view_matrix[1][2]*1.5)) + + hit = intersect_ray_tri((1,0,0), (0,1,0), (0,0,0), (vector), (orig), False) + if hit is None: + hit = intersect_ray_tri(v1, v2, (0,0,0), (vector), (orig), False) + if hit is None: + hit = intersect_ray_tri(v1, v2, (0,0,0), (-vector), (orig), False) + if hit is None: + hit = Vector() + return hit + + +def get_snap_bm_geom(sctx, main_snap_obj, mcursor): + + r_snp_obj, r_loc, r_elem, r_elem_co = sctx.snap_get(mcursor, main_snap_obj) + r_view_vector, r_orig = sctx.last_ray + r_bm = None + r_bm_geom = None + + if r_snp_obj is not None: + obj = r_snp_obj.data[0] + + if obj.type == 'MESH' and obj.data.is_editmode: + r_bm = bmesh.from_edit_mesh(obj.data) + if len(r_elem) == 1: + r_bm_geom = r_bm.verts[r_elem[0]] + + elif len(r_elem) == 2: + try: + v1 = r_bm.verts[r_elem[0]] + v2 = r_bm.verts[r_elem[1]] + r_bm_geom = r_bm.edges.get([v1, v2]) + except IndexError: + r_bm.verts.ensure_lookup_table() + + elif len(r_elem) == 3: + tri = [ + r_bm.verts[r_elem[0]], + r_bm.verts[r_elem[1]], + r_bm.verts[r_elem[2]], + ] + + faces = set(tri[0].link_faces).intersection(tri[1].link_faces, tri[2].link_faces) + if len(faces) == 1: + r_bm_geom = faces.pop() + else: + i = -2 + edge = None + while not edge and i != 1: + edge = r_bm.edges.get([tri[i], tri[i + 1]]) + i += 1 + if edge: + for l in edge.link_loops: + if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]: + r_bm_geom = l.face + break + + return r_snp_obj, r_loc, r_elem, r_elem_co, r_view_vector, r_orig, r_bm, r_bm_geom + + +class SnapCache: + snp_obj = None + elem = None + + v0 = None + v1 = None + vmid = None + vperp = None + + v2d0 = None + v2d1 = None + v2dmid = None + v2dperp = None + + is_increment = False + + +def snap_utilities( + sctx, main_snap_obj, + mcursor, + constrain = None, + previous_vert = None, + increment = 0.0): + + snp_obj, loc, elem, elem_co, view_vector, orig, bm, bm_geom = get_snap_bm_geom(sctx, main_snap_obj, mcursor) + + is_increment = False + r_loc = None + r_type = None + r_len = 0.0 + + if not snp_obj: + is_increment = True + if constrain: + end = orig + view_vector + t_loc = intersect_line_line(constrain[0], constrain[1], orig, end) + if t_loc is None: + t_loc = constrain + r_loc = t_loc[0] + else: + r_type = 'OUT' + r_loc = out_Location(sctx.rv3d, orig, view_vector) + + elif len(elem) == 1: + r_type = 'VERT' + if constrain: + r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0] + else: + r_loc = loc + + elif len(elem) == 2: + if SnapCache.snp_obj is not snp_obj or not (elem == SnapCache.elem).all(): + SnapCache.snp_obj = snp_obj + SnapCache.elem = elem + + SnapCache.v0 = elem_co[0] + SnapCache.v1 = elem_co[1] + SnapCache.vmid = 0.5 * (SnapCache.v0 + SnapCache.v1) + SnapCache.v2d0 = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.v0) + SnapCache.v2d1 = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.v1) + SnapCache.v2dmid = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.vmid) + + if previous_vert and (not bm_geom or previous_vert not in bm_geom.verts): + pvert_co = main_snap_obj.mat @ previous_vert.co + perp_point = intersect_point_line(pvert_co, SnapCache.v0, SnapCache.v1) + SnapCache.vperp = perp_point[0] + #factor = point_perpendicular[1] + SnapCache.v2dperp = location_3d_to_region_2d(sctx.region, sctx.rv3d, perp_point[0]) + SnapCache.is_increment = False + else: + SnapCache.is_increment = True + + #else: SnapCache.v2dperp = None + + if constrain: + t_loc = intersect_line_line(constrain[0], constrain[1], SnapCache.v0, SnapCache.v1) + + if t_loc is None: + is_increment = True + end = orig + view_vector + t_loc = intersect_line_line(constrain[0], constrain[1], orig, end) + r_loc = t_loc[0] + + elif SnapCache.v2dperp and\ + abs(SnapCache.v2dperp[0] - mcursor[0]) < 10 and abs(SnapCache.v2dperp[1] - mcursor[1]) < 10: + r_type = 'PERPENDICULAR' + r_loc = SnapCache.vperp + + elif abs(SnapCache.v2dmid[0] - mcursor[0]) < 10 and abs(SnapCache.v2dmid[1] - mcursor[1]) < 10: + r_type = 'CENTER' + r_loc = SnapCache.vmid + + else: + is_increment = SnapCache.is_increment + + r_type = 'EDGE' + r_loc = loc + + elif len(elem) == 3: + r_type = 'FACE' + + if constrain: + is_increment = False + r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0] + else: + is_increment = True + r_loc = loc + + if previous_vert: + pv_co = main_snap_obj.mat @ previous_vert.co + vec = r_loc - pv_co + if is_increment and increment: + r_len = round((1 / increment) * vec.length) * increment + r_loc = r_len * vec.normalized() + pv_co + else: + r_len = vec.length + + return snp_obj, loc, r_loc, r_type, bm, bm_geom, r_len + +snap_utilities.cache = SnapCache diff --git a/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat b/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat Binary files differnew file mode 100644 index 00000000..fa738db9 --- /dev/null +++ b/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat diff --git a/mesh_snap_utilities_line/ops_line.py b/mesh_snap_utilities_line/ops_line.py new file mode 100644 index 00000000..11a40871 --- /dev/null +++ b/mesh_snap_utilities_line/ops_line.py @@ -0,0 +1,553 @@ +### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy, bmesh + +from bpy.props import FloatProperty + +from mathutils import Vector + +from mathutils.geometry import intersect_point_line + +from .common_classes import ( + SnapDrawn, + CharMap, + SnapNavigation, + g_snap_widget, #TODO: remove + ) + +from .common_utilities import ( + get_units_info, + convert_distance, + snap_utilities, + ) + +if not __package__: + __package__ = "mesh_snap_utilities" + + +def get_closest_edge(bm, point, dist): + r_edge = None + for edge in bm.edges: + v1 = edge.verts[0].co + v2 = edge.verts[1].co + # Test the BVH (AABB) first + for i in range(3): + if v1[i] <= v2[i]: + isect = v1[i] - dist <= point[i] <= v2[i] + dist + else: + isect = v2[i] - dist <= point[i] <= v1[i] + dist + + if not isect: + break + else: + ret = intersect_point_line(point, v1, v2) + + if ret[1] < 0.0: + tmp = v1 + elif ret[1] > 1.0: + tmp = v2 + else: + tmp = ret[0] + + new_dist = (point - tmp).length + if new_dist <= dist: + dist = new_dist + r_edge = edge + + return r_edge + + +def get_loose_linked_edges(bmvert): + linked = [e for e in bmvert.link_edges if not e.link_faces] + for e in linked: + linked += [le for v in e.verts if not v.link_faces for le in v.link_edges if le not in linked] + return linked + + +def draw_line(self, bm_geom, location): + obj = self.main_snap_obj.data[0] + bm = self.main_bm + split_faces = set() + + update_edit_mesh = False + + if bm_geom is None: + vert = bm.verts.new(location) + self.list_verts.append(vert) + update_edit_mesh = True + + elif isinstance(bm_geom, bmesh.types.BMVert): + if (bm_geom.co - location).length_squared < .001: + if self.list_verts == [] or self.list_verts[-1] != bm_geom: + self.list_verts.append(bm_geom) + else: + vert = bm.verts.new(location) + self.list_verts.append(vert) + update_edit_mesh = True + + elif isinstance(bm_geom, bmesh.types.BMEdge): + self.list_edges.append(bm_geom) + ret = intersect_point_line(location, bm_geom.verts[0].co, bm_geom.verts[1].co) + + if (ret[0] - location).length_squared < .001: + if ret[1] == 0.0: + vert = bm_geom.verts[0] + elif ret[1] == 1.0: + vert = bm_geom.verts[1] + else: + edge, vert = bmesh.utils.edge_split(bm_geom, bm_geom.verts[0], ret[1]) + update_edit_mesh = True + + self.list_verts.append(vert) + self.geom = vert # hack to highlight in the drawing + # self.list_edges.append(edge) + + else: # constrain point is near + vert = bm.verts.new(location) + self.list_verts.append(vert) + update_edit_mesh = True + + elif isinstance(bm_geom, bmesh.types.BMFace): + split_faces.add(bm_geom) + vert = bm.verts.new(location) + self.list_verts.append(vert) + update_edit_mesh = True + + # draw, split and create face + if len(self.list_verts) >= 2: + v1, v2 = self.list_verts[-2:] + edge = bm.edges.get([v1, v2]) + if edge: + self.list_edges.append(edge) + + else: + if not v2.link_edges: + edge = bm.edges.new([v1, v2]) + self.list_edges.append(edge) + else: # split face + v1_link_faces = v1.link_faces + v2_link_faces = v2.link_faces + if v1_link_faces and v2_link_faces: + split_faces.update(set(v1_link_faces).intersection(v2_link_faces)) + + else: + if v1_link_faces: + faces = v1_link_faces + co2 = v2.co.copy() + else: + faces = v2_link_faces + co2 = v1.co.copy() + + for face in faces: + if bmesh.geometry.intersect_face_point(face, co2): + co = co2 - face.calc_center_median() + if co.dot(face.normal) < 0.001: + split_faces.add(face) + + if split_faces: + edge = bm.edges.new([v1, v2]) + self.list_edges.append(edge) + ed_list = get_loose_linked_edges(v2) + for face in split_faces: + facesp = bmesh.utils.face_split_edgenet(face, ed_list) + del split_faces + else: + if self.intersect: + facesp = bmesh.ops.connect_vert_pair(bm, verts=[v1, v2], verts_exclude=bm.verts) + # print(facesp) + if not self.intersect or not facesp['edges']: + edge = bm.edges.new([v1, v2]) + self.list_edges.append(edge) + else: + for edge in facesp['edges']: + self.list_edges.append(edge) + update_edit_mesh = True + + # create face + if self.create_face: + ed_list = set(self.list_edges) + for edge in v2.link_edges: + for vert in edge.verts: + if vert != v2 and vert in self.list_verts: + ed_list.add(edge) + break + else: + continue + # Inner loop had a break, break the outer + break + + ed_list.update(get_loose_linked_edges(v2)) + + bmesh.ops.edgenet_fill(bm, edges=list(ed_list)) + update_edit_mesh = True + # print('face created') + + if update_edit_mesh: + obj.data.update_gpu_tag() + obj.data.update_tag() + obj.update_from_editmode() + obj.update_tag() + bmesh.update_edit_mesh(obj.data) + self.sctx.tag_update_drawn_snap_object(self.main_snap_obj) + #bm.verts.index_update() + + if not self.wait_for_input: + bpy.ops.ed.undo_push(message="Undo draw line*") + + return [obj.matrix_world @ v.co for v in self.list_verts] + + +class SnapUtilitiesLine(bpy.types.Operator): + """Make Lines. Connect them to split faces""" + bl_idname = "mesh.make_line" + bl_label = "Line Tool" + bl_options = {'REGISTER'} + + wait_for_input: bpy.props.BoolProperty(name="Wait for Input", default=True) + + constrain_keys = { + 'X': Vector((1,0,0)), + 'Y': Vector((0,1,0)), + 'Z': Vector((0,0,1)), + 'RIGHT_SHIFT': 'shift', + 'LEFT_SHIFT': 'shift', + } + + def _exit(self, context): + del self.main_bm #avoids unpredictable crashs + del self.bm + del self.list_edges + del self.list_verts + del self.list_verts_co + + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + context.area.header_text_set(None) + + if not self.snap_widget: + self.sctx.free() + del self.draw_cache + else: + self.sctx = None + self.draw_cache = None + + #Restore initial state + context.tool_settings.mesh_select_mode = self.select_mode + context.space_data.overlay.show_face_center = self.show_face_center + + def modal(self, context, event): + if self.navigation_ops.run(context, event, self.prevloc if self.vector_constrain else self.location): + return {'RUNNING_MODAL'} + + context.area.tag_redraw() + + if event.ctrl and event.type == 'Z' and event.value == 'PRESS': + if self.bm: + self.bm.free() + self.bm = None + if self.main_bm: + self.main_bm.free() + bpy.ops.ed.undo() + self.vector_constrain = None + self.list_verts_co = [] + self.list_verts = [] + self.list_edges = [] + bpy.ops.object.mode_set(mode='EDIT') # just to be sure + self.main_bm = bmesh.from_edit_mesh(self.main_snap_obj.data[0].data) + self.sctx.tag_update_drawn_snap_object(self.main_snap_obj) + return {'RUNNING_MODAL'} + + is_making_lines = bool(self.list_verts_co) + + if event.type == 'MOUSEMOVE' or self.bool_update: + if self.rv3d.view_matrix != self.rotMat: + self.rotMat = self.rv3d.view_matrix.copy() + self.bool_update = True + snap_utilities.cache.snp_obj = None # hack for snap edge elemens update + else: + self.bool_update = False + + mval = Vector((event.mouse_region_x, event.mouse_region_y)) + + self.snap_obj, self.prevloc, self.location, self.type, self.bm, self.geom, self.len = snap_utilities( + self.sctx, + self.main_snap_obj, + mval, + constrain=self.vector_constrain, + previous_vert=(self.list_verts[-1] if self.list_verts else None), + increment=self.incremental + ) + + if self.snap_to_grid and self.type == 'OUT': + loc = self.location / self.rd + self.location = Vector((round(loc.x), + round(loc.y), + round(loc.z))) * self.rd + + if self.keyf8 and is_making_lines: + lloc = self.list_verts_co[-1] + view_vec, orig = self.sctx.last_ray + location = intersect_point_line(lloc, orig, (orig + view_vec)) + vec = (location[0] - lloc) + ax, ay, az = abs(vec.x), abs(vec.y), abs(vec.z) + vec.x = ax > ay > az or ax > az > ay + vec.y = ay > ax > az or ay > az > ax + vec.z = az > ay > ax or az > ax > ay + if vec == Vector(): + self.vector_constrain = None + else: + vc = lloc + vec + try: + if vc != self.vector_constrain[1]: + type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift' + self.vector_constrain = [lloc, vc, type] + except: + type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift' + self.vector_constrain = [lloc, vc, type] + + if event.value == 'PRESS': + if is_making_lines and (event.ascii in CharMap.ascii or event.type in CharMap.type): + CharMap.modal(self, context, event) + + elif event.type in self.constrain_keys: + self.bool_update = True + self.keyf8 = False + + if self.vector_constrain and self.vector_constrain[2] == event.type: + self.vector_constrain = () + + else: + if event.shift: + if isinstance(self.geom, bmesh.types.BMEdge): + if is_making_lines: + loc = self.list_verts_co[-1] + self.vector_constrain = (loc, loc + self.geom.verts[1].co - + self.geom.verts[0].co, event.type) + else: + self.vector_constrain = [self.main_snap_obj.mat @ v.co for + v in self.geom.verts] + [event.type] + else: + if is_making_lines: + loc = self.list_verts_co[-1] + else: + loc = self.location + self.vector_constrain = [loc, loc + self.constrain_keys[event.type], event.type] + + elif event.type == 'LEFTMOUSE': + if not is_making_lines and self.bm: + self.main_snap_obj = self.snap_obj + self.main_bm = self.bm + + mat_inv = self.main_snap_obj.mat.inverted_safe() + point = mat_inv @ self.location + # with constraint the intersection can be in a different element of the selected one + geom2 = self.geom + if geom2: + geom2.select = False + + if self.vector_constrain: + geom2 = get_closest_edge(self.main_bm, point, .001) + + self.vector_constrain = None + self.list_verts_co = draw_line(self, geom2, point) + + elif event.type == 'F8': + self.vector_constrain = None + self.keyf8 = self.keyf8 is False + + elif event.value == 'RELEASE': + if event.type in {'RET', 'NUMPAD_ENTER'}: + if self.length_entered != "" and self.list_verts_co: + try: + text_value = bpy.utils.units.to_value(self.unit_system, 'LENGTH', self.length_entered) + vector = (self.location - self.list_verts_co[-1]).normalized() + location = (self.list_verts_co[-1] + (vector * text_value)) + + mat_inv = self.main_snap_obj.mat.inverted_safe() + self.list_verts_co = draw_line(self, self.geom, mat_inv @ location) + self.length_entered = "" + self.vector_constrain = None + + except: # ValueError: + self.report({'INFO'}, "Operation not supported yet") + + if not self.wait_for_input: + self._exit(context) + return {'FINISHED'} + + elif event.type in {'RIGHTMOUSE', 'ESC'}: + if not self.wait_for_input or not is_making_lines or event.type == 'ESC': + self._exit(context) + return {'FINISHED'} + else: + snap_utilities.cache.snp_obj = None # hack for snap edge elemens update + self.vector_constrain = None + self.list_edges = [] + self.list_verts = [] + self.list_verts_co = [] + + a = "" + if is_making_lines: + if self.length_entered: + pos = self.line_pos + a = 'length: ' + self.length_entered[:pos] + '|' + self.length_entered[pos:] + else: + length = self.len + length = convert_distance(length, self.uinfo) + a = 'length: ' + length + + context.area.header_text_set(text = "hit: %.3f %.3f %.3f %s" % (*self.location, a)) + + if True or is_making_lines: + return {'RUNNING_MODAL'} + + return {'PASS_THROUGH'} + + def draw_callback_px(self): + if self.bm: + self.draw_cache.draw_elem(self.snap_obj, self.bm, self.geom) + self.draw_cache.draw(self.type, self.location, self.list_verts_co, self.vector_constrain, self.prevloc) + + def invoke(self, context, event): + if context.space_data.type == 'VIEW_3D': + # print('name', __name__, __package__) + + #Store current state + self.select_mode = context.tool_settings.mesh_select_mode[:] + self.show_face_center = context.space_data.overlay.show_face_center + + #Modify the current state + bpy.ops.mesh.select_all(action='DESELECT') + context.tool_settings.mesh_select_mode = (True, False, True) + context.space_data.overlay.show_face_center = True + + #Store values from 3d view context + self.rv3d = context.region_data + self.rotMat = self.rv3d.view_matrix.copy() + # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4], self.obj_matrix.transposed()) + + #Init event variables + self.keyf8 = False + self.snap_face = True + + self.snap_widget = g_snap_widget[0] + + if self.snap_widget is not None: + self.draw_cache = self.snap_widget.draw_cache + self.sctx = self.snap_widget.sctx + + preferences = self.snap_widget.preferences + else: + preferences = context.user_preferences.addons[__package__].preferences + + #Init DrawCache + self.draw_cache = SnapDrawn( + preferences.out_color, + preferences.face_color, + preferences.edge_color, + preferences.vert_color, + preferences.center_color, + preferences.perpendicular_color, + preferences.constrain_shift_color, + tuple(context.user_preferences.themes[0].user_interface.axis_x) + (1.0,), + tuple(context.user_preferences.themes[0].user_interface.axis_y) + (1.0,), + tuple(context.user_preferences.themes[0].user_interface.axis_z) + (1.0,) + ) + + #Init Snap Context + from .snap_context_l import SnapContext + + self.sctx = SnapContext(context.region, context.space_data) + self.sctx.set_pixel_dist(12) + self.sctx.use_clip_planes(True) + + if preferences.outer_verts: + for base in context.visible_bases: + self.sctx.add_obj(base.object, base.object.matrix_world) + + self.sctx.set_snap_mode(True, True, self.snap_face) + + #Configure the unit of measure + self.unit_system = context.scene.unit_settings.system + scale = context.scene.unit_settings.scale_length + separate_units = context.scene.unit_settings.use_separate + self.uinfo = get_units_info(scale, self.unit_system, separate_units) + scale /= context.space_data.overlay.grid_scale * preferences.relative_scale + self.rd = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(1 / scale)) + + self.intersect = preferences.intersect + self.create_face = preferences.create_face + self.outer_verts = preferences.outer_verts + self.snap_to_grid = preferences.increments_grid + self.incremental = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(preferences.incremental)) + + if self.snap_widget: + self.geom = self.snap_widget.geom + self.type = self.snap_widget.type + self.location = self.snap_widget.loc + if self.snap_widget.snap_obj: + context.view_layer.objects.active = self.snap_widget.snap_obj.data[0] + else: + #init these variables to avoid errors + self.geom = None + self.type = 'OUT' + self.location = Vector() + + self.prevloc = Vector() + self.list_verts = [] + self.list_edges = [] + self.list_verts_co = [] + self.bool_update = False + self.vector_constrain = () + self.len = 0 + self.length_entered = "" + self.line_pos = 0 + + self.navigation_ops = SnapNavigation(context, True) + + active_object = context.active_object + + #Create a new object + if active_object is None or active_object.type != 'MESH': + mesh = bpy.data.meshes.new("") + active_object = bpy.data.objects.new("", mesh) + context.scene.objects.link(obj) + context.scene.objects.active = active_object + else: + mesh = active_object.data + + self.main_snap_obj = self.snap_obj = self.sctx._get_snap_obj_by_obj(active_object) + self.main_bm = self.bm = bmesh.from_edit_mesh(mesh) #remove at end + + #modals + if not self.wait_for_input: + self.modal(context, event) + + self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (), 'WINDOW', 'POST_VIEW') + context.window_manager.modal_handler_add(self) + + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "Active space must be a View3d") + return {'CANCELLED'} + + +def register(): + bpy.utils.register_class(SnapUtilitiesLine) + +if __name__ == "__main__": + register() diff --git a/mesh_snap_utilities_line/preferences.py b/mesh_snap_utilities_line/preferences.py new file mode 100644 index 00000000..98b89774 --- /dev/null +++ b/mesh_snap_utilities_line/preferences.py @@ -0,0 +1,113 @@ +### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +from bpy.props import ( + EnumProperty, + StringProperty, + BoolProperty, + IntProperty, + FloatVectorProperty, + FloatProperty, + ) + + +class SnapUtilitiesLinePreferences(bpy.types.AddonPreferences): + # this must match the addon name, use '__package__' + # when defining this in a submodule of a python package. + bl_idname = __package__ + + intersect: BoolProperty( + name="Intersect", + description="intersects created line with the existing edges, even if the lines do not intersect", + default=True) + + create_face: BoolProperty( + name="Create faces", + description="Create faces defined by enclosed edges", + default=False) + + outer_verts: BoolProperty( + name="Snap to outer vertices", + description="The vertices of the objects are not activated also snapped", + default=True) + + increments_grid: BoolProperty( + name="Increments of Grid", + description="Snap to increments of grid", + default=False) + + incremental: FloatProperty( + name="Incremental", + description="Snap in defined increments", + default=0, + min=0, + step=1, + precision=3) + + relative_scale: FloatProperty( + name="Relative Scale", + description="Value that divides the global scale", + default=1, + min=0, + step=1, + precision=3) + + out_color: FloatVectorProperty(name="OUT", default=(0.0, 0.0, 0.0, 0.5), size=4, subtype="COLOR", min=0, max=1) + face_color: FloatVectorProperty(name="FACE", default=(1.0, 0.8, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1) + edge_color: FloatVectorProperty(name="EDGE", default=(0.0, 0.8, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1) + vert_color: FloatVectorProperty(name="VERT", default=(1.0, 0.5, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1) + center_color: FloatVectorProperty(name="CENTER", default=(1.0, 0.0, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1) + perpendicular_color: FloatVectorProperty(name="PERPENDICULAR", default=(0.1, 0.5, 0.5, 1.0), size=4, subtype="COLOR", min=0, max=1) + constrain_shift_color: FloatVectorProperty(name="SHIFT CONSTRAIN", default=(0.8, 0.5, 0.4, 1.0), size=4, subtype="COLOR", min=0, max=1) + + def draw(self, context): + layout = self.layout + + layout.label(text="Snap Colors:") + split = layout.split() + + col = split.column() + col.prop(self, "out_color") + col.prop(self, "constrain_shift_color") + col = split.column() + col.prop(self, "face_color") + col = split.column() + col.prop(self, "edge_color") + col = split.column() + col.prop(self, "vert_color") + col = split.column() + col.prop(self, "center_color") + col = split.column() + col.prop(self, "perpendicular_color") + + row = layout.row() + + col = row.column() + #col.label(text="Snap Items:") + col.prop(self, "incremental") + col.prop(self, "increments_grid") + if self.increments_grid: + col.prop(self, "relative_scale") + + col.prop(self, "outer_verts") + row.separator() + + col = row.column() + col.label(text="Line Tool:") + col.prop(self, "intersect") + col.prop(self, "create_face") diff --git a/modules/snap_context/__init__.py b/mesh_snap_utilities_line/snap_context_l/__init__.py index 77e84726..c4b03156 100644 --- a/modules/snap_context/__init__.py +++ b/mesh_snap_utilities_line/snap_context_l/__init__.py @@ -52,6 +52,100 @@ class _SnapObjectData(): self.mat = omat +class _SnapOffscreen(): + bound = None + def __init__(self, width, height): + import ctypes + + self.freed = False + self.is_bound = False + + self.width = width + self.height = height + + self.fbo = bgl.Buffer(bgl.GL_INT, 1) + self.buf_color = bgl.Buffer(bgl.GL_INT, 1) + self.buf_depth = bgl.Buffer(bgl.GL_INT, 1) + + self.cur_fbo = bgl.Buffer(bgl.GL_INT, 1) + self.cur_viewport = bgl.Buffer(bgl.GL_INT, 4) + + bgl.glGenRenderbuffers(1, self.buf_depth) + bgl.glBindRenderbuffer(bgl.GL_RENDERBUFFER, self.buf_depth[0]) + bgl.glRenderbufferStorage(bgl.GL_RENDERBUFFER, bgl.GL_DEPTH_COMPONENT, width, height) + + bgl.glGenTextures(1, self.buf_color) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.buf_color[0]) + NULL = bgl.Buffer(bgl.GL_INT, 1, (ctypes.c_int32 * 1).from_address(0)) + bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_R32UI, width, height, 0, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, NULL) + del NULL + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST) + + bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo) + + bgl.glGenFramebuffers(1, self.fbo) + bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0]) + bgl.glFramebufferRenderbuffer(bgl.GL_FRAMEBUFFER, bgl.GL_DEPTH_ATTACHMENT, bgl.GL_RENDERBUFFER, self.buf_depth[0]) + bgl.glFramebufferTexture(bgl.GL_FRAMEBUFFER, bgl.GL_COLOR_ATTACHMENT0, self.buf_color[0], 0) + + bgl.glDrawBuffers(1, bgl.Buffer(bgl.GL_INT, 1, [bgl.GL_COLOR_ATTACHMENT0])) + + status = bgl.glCheckFramebufferStatus(bgl.GL_FRAMEBUFFER) + if status != bgl.GL_FRAMEBUFFER_COMPLETE: + print("Framebuffer Invalid", status) + + bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0]) + + def bind(self): + if self is not _SnapOffscreen.bound: + if _SnapOffscreen.bound is None: + bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo) + bgl.glGetIntegerv(bgl.GL_VIEWPORT, self.cur_viewport) + + bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0]) + bgl.glViewport(0, 0, self.width, self.height) + _SnapOffscreen.bound = self + + def unbind(self): + if self is _SnapOffscreen.bound: + bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0]) + bgl.glViewport(*self.cur_viewport) + _SnapOffscreen.bound = None + + def clear(self): + is_bound = self is _SnapOffscreen.bound + if not is_bound: + self.bind() + + bgl.glColorMask(bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE) + bgl.glClearColor(0.0, 0.0, 0.0, 0.0) + + bgl.glDepthMask(bgl.GL_TRUE) + bgl.glClearDepth(1.0); + + bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT) + + if not is_bound: + self.unbind() + + def __del__(self): + if not self.freed: + bgl.glDeleteFramebuffers(1, self.fbo) + bgl.glDeleteRenderbuffers(1, self.buf_depth) + bgl.glDeleteTextures(1, self.buf_color) + del self.fbo + del self.buf_color + del self.buf_depth + + del self.cur_fbo + del self.cur_viewport + + def free(self): + self.__del__() + self.freed = True + + class SnapContext(): """ Initializes the snap context with the region and space where the snap objects will be added. @@ -66,8 +160,8 @@ class SnapContext(): """ def __init__(self, region, space): - import gpu - import ctypes + #print('Render:', bgl.glGetString(bgl.GL_RENDERER)) + #print('OpenGL Version:', bgl.glGetString(bgl.GL_VERSION)) self.freed = False self.snap_objects = [] @@ -87,75 +181,74 @@ class SnapContext(): self.set_pixel_dist(12) - self._offscreen = gpu.offscreen.new(self.region.width, self.region.height) - - self._texture = self._offscreen.color_texture - bgl.glBindTexture(bgl.GL_TEXTURE_2D, self._texture) - - NULL = bgl.Buffer(bgl.GL_INT, 1, (ctypes.c_int32 * 1).from_address(0)) - bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_R32UI, self.region.width, self.region.height, 0, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, NULL) - del NULL - - bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST) - bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST) - bgl.glBindTexture(bgl.GL_TEXTURE_2D, 0) + self._offscreen = _SnapOffscreen(self.region.width, self.region.height) self.winsize = Vector((self._offscreen.width, self._offscreen.height)) + self._offscreen.clear() + ## PRIVATE ## def _get_snap_obj_by_index(self, index): - for snap_obj in self.snap_objects[:self.drawn_count]: - data = snap_obj.data[1] - if index < data.first_index + data.get_tot_elems(): - return snap_obj + if index: + for snap_obj in self.snap_objects[:self.drawn_count]: + data = snap_obj.data[1] + if index < data.first_index + data.get_tot_elems(): + return snap_obj return None def _get_nearest_index(self): + r_snap_obj = None + r_value = 0 + loc = [self._dist_px, self._dist_px] d = 1 m = self.threshold - max = 2 * m - 1 - offset = 1 - last_snap_obj = None - r_value = 0 - while m < max: + max_val = 2 * m - 1 + last_value = -1 + find_next_index = self._snap_mode & FACE and self._snap_mode & (VERT | EDGE) + while m < max_val: for i in range(2): while 2 * loc[i] * d < m: value = int(self._snap_buffer[loc[0]][loc[1]]) loc[i] += d - if value >= offset: + if value != last_value: r_value = value - snap_obj = self._get_snap_obj_by_index(r_value) - - if self._snap_mode & FACE and self._snap_mode & (VERT | EDGE) and last_snap_obj != snap_obj: - data = snap_obj.data[1] - offset = data.first_index + data.num_tris - last_snap_obj = snap_obj - continue - return snap_obj, r_value + if find_next_index: + last_value = value + r_snap_obj = self._get_snap_obj_by_index(value) + if (r_snap_obj is None) or value < (r_snap_obj.data[1].first_index + len(r_snap_obj.data[1].tri_verts)): + continue + find_next_index = False + elif (r_snap_obj is None) or\ + (value < r_snap_obj.data[1].first_index) or\ + (value >= (r_snap_obj.data[1].first_index + r_snap_obj.data[1].get_tot_elems())): + r_snap_obj = self._get_snap_obj_by_index(value) + return r_snap_obj, r_value d = -d m += 4 * self._dist_px * d + 1 - return last_snap_obj, r_value + return r_snap_obj, r_value def _get_loc(self, snap_obj, index): index -= snap_obj.data[1].first_index gpu_data = snap_obj.data[1] if gpu_data.draw_tris: - if index < snap_obj.data[1].num_tris: + num_tris = len(snap_obj.data[1].tri_verts) + if index < num_tris: tri_verts = gpu_data.get_tri_verts(index) - tri_co = [snap_obj.mat * Vector(v) for v in gpu_data.get_tri_co(index)] + tri_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_tri_co(index)] nor = (tri_co[1] - tri_co[0]).cross(tri_co[2] - tri_co[0]) - return _Internal.intersect_line_plane(self.last_ray[1], self.last_ray[1] + self.last_ray[0], tri_co[0], nor), tri_verts + return _Internal.intersect_line_plane(self.last_ray[1], self.last_ray[1] + self.last_ray[0], tri_co[0], nor), tri_verts, tri_co - index -= gpu_data.num_tris + index -= num_tris if gpu_data.draw_edges: - if index < snap_obj.data[1].num_edges: + num_edges = len(snap_obj.data[1].edge_verts) + if index < num_edges: edge_verts = gpu_data.get_edge_verts(index) - edge_co = [snap_obj.mat * Vector(v) for v in gpu_data.get_edge_co(index)] + edge_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_edge_co(index)] fac = _Internal.intersect_ray_segment_fac(*edge_co, *self.last_ray) if (self._snap_mode) & VERT and (fac < 0.25 or fac > 0.75): @@ -163,7 +256,7 @@ class SnapContext(): proj_co = _Internal.project_co_v3(self, co) dist = self.mval - proj_co if abs(dist.x) < self._dist_px and abs(dist.y) < self._dist_px: - return co, (edge_verts[0] if fac < 0.5 else edge_verts[1],) + return co, (edge_verts[0] if fac < 0.5 else edge_verts[1],), co if fac <= 0.0: co = edge_co[0] @@ -172,15 +265,16 @@ class SnapContext(): else: co = edge_co[0] + fac * (edge_co[1] - edge_co[0]) - return co, edge_verts + return co, edge_verts, edge_co - index -= gpu_data.num_edges + index -= num_edges if gpu_data.draw_verts: - if index < snap_obj.data[1].num_verts: - return snap_obj.mat * Vector(gpu_data.get_loosevert_co(index)), (gpu_data.get_loosevert_index(index),) + if index < len(snap_obj.data[1].looseverts): + co = snap_obj.mat @ Vector(gpu_data.get_loosevert_co(index)) + return co, (gpu_data.get_loosevert_index(index),), co - return None, None + return None, None, None def _get_snap_obj_by_obj(self, obj): @@ -203,17 +297,27 @@ class SnapContext(): def update_all(self): self.drawn_count = 0 self._offset_cur = 1 + self._offscreen.clear() - bgl.glClearColor(0.0, 0.0, 0.0, 0.0) - bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT) - - def update_drawn_snap_object(self, snap_obj): + def tag_update_drawn_snap_object(self, snap_obj): if len(snap_obj.data) > 1: del snap_obj.data[1:] #self.update_all() # Update on next snap_get call # self.proj_mat = None + def update_drawn_snap_object(self, snap_obj): + if len(snap_obj.data) > 1: + _Internal.gpu_Indices_enable_state() + + from .mesh_drawing import GPU_Indices_Mesh + snap_vert = self._snap_mode & VERT != 0 + snap_edge = self._snap_mode & EDGE != 0 + snap_face = self._snap_mode & FACE != 0 + snap_obj.data[1] = GPU_Indices_Mesh(snap_obj.data[0], snap_face, snap_edge, snap_vert) + + _Internal.gpu_Indices_restore_state() + def use_clip_planes(self, value): _Internal.gpu_Indices_use_clip_planes(self.rv3d, value) @@ -221,7 +325,7 @@ class SnapContext(): self._dist_px = int(dist_px) self._dist_px_sq = self._dist_px ** 2 self.threshold = 2 * self._dist_px + 1 - self._snap_buffer = bgl.Buffer(bgl.GL_FLOAT, (self.threshold, self.threshold)) + self._snap_buffer = bgl.Buffer(bgl.GL_INT, (self.threshold, self.threshold)) def set_snap_mode(self, snap_to_vert, snap_to_edge, snap_to_face): snap_mode = 0 @@ -250,8 +354,8 @@ class SnapContext(): self.last_ray = _Internal.region_2d_to_orig_and_view_vector(self.region, self.rv3d, mval) return self.last_ray - def snap_get(self, mval): - ret = None, None + def snap_get(self, mval, main_snap_obj = None): + ret = None, None, None self.mval[:] = mval snap_vert = self._snap_mode & VERT != 0 snap_edge = self._snap_mode & EDGE != 0 @@ -274,15 +378,20 @@ class SnapContext(): ray_dir, ray_orig = self.get_ray(mval) for i, snap_obj in enumerate(self.snap_objects[self.drawn_count:], self.drawn_count): obj = snap_obj.data[0] - bbmin = Vector(obj.bound_box[0]) - bbmax = Vector(obj.bound_box[6]) + try: + bbmin = Vector(obj.bound_box[0]) + bbmax = Vector(obj.bound_box[6]) + except ReferenceError: + self.snap_objects.remove(snap_obj) + continue if bbmin != bbmax: - MVP = proj_mat * snap_obj.mat + MVP = proj_mat @ snap_obj.mat mat_inv = snap_obj.mat.inverted() - ray_orig_local = mat_inv * ray_orig - ray_dir_local = mat_inv.to_3x3() * ray_dir - in_threshold = _Internal.intersect_boundbox_threshold(self, MVP, ray_orig_local, ray_dir_local, bbmin, bbmax) + ray_orig_local = mat_inv @ ray_orig + ray_dir_local = mat_inv.to_3x3() @ ray_dir + in_threshold = _Internal.intersect_boundbox_threshold( + self, MVP, ray_orig_local, ray_dir_local, bbmin, bbmax) else: proj_co = _Internal.project_co_v3(self, snap_obj.mat.translation) dist = self.mval - proj_co @@ -295,27 +404,38 @@ class SnapContext(): snap_obj.data.append(GPU_Indices_Mesh(obj, snap_face, snap_edge, snap_vert)) snap_obj.data[1].set_draw_mode(snap_face, snap_edge, snap_vert) snap_obj.data[1].set_ModelViewMatrix(snap_obj.mat) - snap_obj.data[1].Draw(self._offset_cur) + + if snap_obj == main_snap_obj: + snap_obj.data[1].Draw(self._offset_cur, -0.0001) + else: + snap_obj.data[1].Draw(self._offset_cur) self._offset_cur += snap_obj.data[1].get_tot_elems() - self.snap_objects[self.drawn_count], self.snap_objects[i] = self.snap_objects[i], self.snap_objects[self.drawn_count] + tmp = self.snap_objects[self.drawn_count] + self.snap_objects[self.drawn_count] = self.snap_objects[i] + self.snap_objects[i] = tmp + self.drawn_count += 1 bgl.glReadBuffer(bgl.GL_COLOR_ATTACHMENT0) bgl.glReadPixels( int(self.mval[0]) - self._dist_px, int(self.mval[1]) - self._dist_px, self.threshold, self.threshold, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, self._snap_buffer) - bgl.glReadBuffer(bgl.GL_BACK) + #bgl.glReadBuffer(bgl.GL_BACK) + #import numpy as np + #a = np.array(self._snap_buffer) + #print(a) snap_obj, index = self._get_nearest_index() - #print(index) + #print("index:", index) if snap_obj: ret = self._get_loc(snap_obj, index) + bgl.glDisable(bgl.GL_DEPTH_TEST) self._offscreen.unbind() _Internal.gpu_Indices_restore_state() - return snap_obj, ret[0], ret[1] + return (snap_obj, *ret) def free(self): self.__del__() diff --git a/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py b/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py new file mode 100644 index 00000000..6ef0b0a0 --- /dev/null +++ b/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py @@ -0,0 +1,412 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ##### END GPL LICENSE BLOCK ##### + + +import bgl +import bmesh +import numpy as np +from mathutils import Matrix +import gpu + +_Hash = {} + +def load_shader(shadername): + from os import path + with open(path.join(path.dirname(__file__), 'shaders', shadername), 'r') as f: + return f.read() + +def get_mesh_vert_co_array(me): + tot_vco = len(me.vertices) + if tot_vco: + verts_co = np.empty(len(me.vertices) * 3, 'f4') + me.vertices.foreach_get("co", verts_co) + verts_co.shape = (-1, 3) + return verts_co + return None + + +def get_bmesh_vert_co_array(bm): + tot_vco = len(bm.verts) + if tot_vco: + return np.array([v.co for v in bm.verts], 'f4') + return None + + +def get_mesh_tri_verts_array(me): + me.calc_loop_triangles() + len_triangles = len(me.loop_triangles) + if len_triangles: + tris = np.empty(len_triangles * 3, 'i4') + me.loop_triangles.foreach_get("vertices", tris) + tris.shape = (-1, 3) + return tris + return None + + +def get_bmesh_tri_verts_array(bm): + l_tri_layer = bm.faces.layers.int.get("l_tri") + if l_tri_layer is None: + l_tri_layer = bm.faces.layers.int.new("l_tri") + + ltris = bm.calc_loop_triangles() + tris = np.empty((len(ltris), 3), 'i4') + i = 0 + last_face = bm.faces[-1] + for ltri in ltris: + face = ltri[0].face + if not face.hide: + tris[i] = ltri[0].vert.index, ltri[1].vert.index, ltri[2].vert.index + if last_face != face: + last_face = face + face[l_tri_layer] = i + i += 1 + if i: + tris.resize((i, 3), refcheck=False) + return tris + return None + + +def get_mesh_edge_verts_array(me): + tot_edges = len(me.edges) + if tot_edges: + edge_verts = np.empty(tot_edges * 2, 'i4') + me.edges.foreach_get("vertices", edge_verts) + edge_verts.shape = tot_edges, 2 + return edge_verts + return None + + +def get_bmesh_edge_verts_array(bm): + bm.edges.ensure_lookup_table() + edges = [[e.verts[0].index, e.verts[1].index] for e in bm.edges if not e.hide] + if edges: + return np.array(edges, 'i4') + return None + + +def get_mesh_loosevert_array(me, edges): + verts = np.arange(len(me.vertices)) + + mask = np.in1d(verts, edges, invert=True) + + verts = verts[mask] + if len(verts): + return verts + return None + + +def get_bmesh_loosevert_array(bm): + looseverts = [v.index for v in bm.verts if not (v.link_edges or v.hide)] + if looseverts: + return np.array(looseverts, 'i4') + return None + + +class _Mesh_Arrays(): + def __init__(self, obj, create_tris, create_edges, create_looseverts): + self.tri_verts = self.edge_verts = self.looseverts = () + if obj.type == 'MESH': + me = obj.data + if me.is_editmode: + bm = bmesh.from_edit_mesh(me) + bm.verts.ensure_lookup_table() + + self.verts_co = get_bmesh_vert_co_array(bm) + + if create_tris: + self.tri_verts = get_bmesh_tri_verts_array(bm) + if create_edges: + self.edge_verts = get_bmesh_edge_verts_array(bm) + if create_looseverts: + self.looseverts = get_bmesh_loosevert_array(bm) + + del bm + else: + import bpy + self.verts_co = get_mesh_vert_co_array(me) + + if create_tris: + self.tri_verts = get_mesh_tri_verts_array(me) + if create_edges: + self.edge_verts = get_mesh_edge_verts_array(me) + if create_looseverts: + edge_verts = self.edge_verts + if edge_verts is None: + edge_verts = get_mesh_edge_verts_array(me) + self.looseverts = get_mesh_loosevert_array(me, edge_verts) + del edge_verts + + + else: #TODO + self.verts_co = np.zeros((1,3), 'f4') + self.looseverts = np.zeros(1, 'i4') + + def __del__(self): + del self.tri_verts, self.edge_verts, self.looseverts + del self.verts_co + + +class GPU_Indices_Mesh(): + __slots__ = ( + "obj", + "draw_tris", + "draw_edges", + "draw_verts", + "batch_tris", + "batch_edges", + "batch_lverts", + "verts_co", + "tri_verts", + "edge_verts", + "looseverts", + "first_index", + "users" + ) + + shader = None + + @classmethod + def end_opengl(cls): + del cls.shader + del cls.P + + del cls + + @staticmethod + def init_opengl(): + cls = GPU_Indices_Mesh + # OpenGL was already initialized, nothing to do here. + if cls.shader is not None: + return + + import atexit + + # Make sure we only registered the callback once. + atexit.unregister(cls.end_opengl) + atexit.register(cls.end_opengl) + + cls.shader = gpu.types.GPUShader( + load_shader("ID_color_vert.glsl"), + load_shader("ID_color_frag.glsl"), + ) + #cls.unif_use_clip_planes = cls.shader.uniform_from_name('use_clip_planes') + #cls.unif_clip_plane = cls.shader.uniform_from_name('clip_plane') + cls.unif_offset = cls.shader.uniform_from_name('offset') + + cls.P = Matrix() + + + @staticmethod + def set_ModelViewMatrix(MV): + gpu.matrix.load_matrix(MV) + + + def __init__(self, obj, draw_tris, draw_edges, draw_verts): + self.obj = obj + + if obj.data in _Hash: + src = _Hash[obj.data] + dst = self + + dst.draw_tris = src.draw_tris + dst.draw_edges = src.draw_edges + dst.draw_verts = src.draw_verts + dst.batch_tris = src.batch_tris + dst.batch_edges = src.batch_edges + dst.batch_lverts = src.batch_lverts + dst.verts_co = src.verts_co + dst.tri_verts = src.tri_verts + dst.edge_verts = src.edge_verts + dst.looseverts = src.looseverts + dst.users = src.users + dst.users.append(self) + + update = obj.type == 'MESH' and obj.data.is_editmode + + else: + _Hash[obj.data] = self + self.users = [self] + update = True; + + if update: + self.draw_tris = draw_tris + self.draw_edges = draw_edges + self.draw_verts = draw_verts + + GPU_Indices_Mesh.init_opengl() + + ## Init Array ## + mesh_arrays = _Mesh_Arrays(obj, draw_tris, draw_edges, draw_verts) + + if mesh_arrays.verts_co is None: + self.draw_tris = False + self.draw_edges = False + self.draw_verts = False + self.tri_verts = None + self.edge_verts = None + self.looseverts = None + return + + ## Create VBO for vertices ## + self.verts_co = mesh_arrays.verts_co + self.tri_verts = mesh_arrays.tri_verts + self.edge_verts = mesh_arrays.edge_verts + self.looseverts = mesh_arrays.looseverts + del mesh_arrays + + format = gpu.types.GPUVertFormat() + format.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT') + + vbo = gpu.types.GPUVertBuf(format, len = len(self.verts_co)) + + vbo.attr_fill(0, data = self.verts_co) + + ## Create Batch for Tris ## + if self.tri_verts is not None: + ebo = gpu.types.GPUIndexBuf(type = "TRIS", seq = self.tri_verts) + self.batch_tris = gpu.types.GPUBatch(type = "TRIS", buf = vbo, elem = ebo) + self.batch_tris.program_set(self.shader) + else: + self.draw_tris = False + self.batch_tris = None + + ## Create Batch for Edges ## + if self.edge_verts is not None: + ebo = gpu.types.GPUIndexBuf(type = "LINES", seq = self.edge_verts) + self.batch_edges = gpu.types.GPUBatch(type = "LINES", buf = vbo, elem = ebo) + self.batch_edges.program_set(self.shader) + else: + self.draw_edges = False + self.batch_edges = None + + ## Create Batch for Loose Verts ## + if self.looseverts is not None: + ebo = gpu.types.GPUIndexBuf(type = "POINTS", seq = self.looseverts) + self.batch_lverts = gpu.types.GPUBatch(type = "POINTS", buf = vbo, elem = ebo) + self.batch_lverts.program_set(self.shader) + else: + self.draw_verts = False + self.batch_lverts = None + + + def get_tot_elems(self): + tot = 0 + if self.draw_tris: + tot += len(self.tri_verts) + + if self.draw_edges: + tot += len(self.edge_verts) + + if self.draw_verts: + tot += len(self.looseverts) + + return tot + + + def set_draw_mode(self, draw_tris, draw_edges, draw_verts): + self.draw_tris = draw_tris and self.tri_verts is not None + self.draw_edges = draw_edges and self.edge_verts is not None + self.draw_verts = draw_verts and self.looseverts is not None + + + def Draw(self, index_offset, depth_offset = -0.00005): + self.first_index = index_offset + if self.draw_tris: + self.shader.uniform_int("offset", (index_offset,)) + self.batch_tris.draw(self.shader) + index_offset += len(self.tri_verts) + bgl.glDepthRange(depth_offset, 1 + depth_offset) + + if self.draw_edges: + self.shader.uniform_int("offset", (index_offset,)) + #bgl.glLineWidth(3.0) + self.batch_edges.draw(self.shader) + #bgl.glLineWidth(1.0) + index_offset += len(self.edge_verts) + + if self.draw_verts: + self.shader.uniform_int("offset", (index_offset,)) + self.batch_lverts.draw(self.shader) + + bgl.glDepthRange(0.0, 1.0) + + + def get_tri_co(self, index): + return self.verts_co[self.tri_verts[index]] + + def get_edge_co(self, index): + return self.verts_co[self.edge_verts[index]] + + def get_loosevert_co(self, index): + return self.verts_co[self.looseverts[index]] + + def get_loop_tri_co_by_bmface(self, bm, bmface): + l_tri_layer = bm.faces.layers.int["l_tri"] + tri = bmface[l_tri_layer] + return self.verts_co[self.tri_verts[tri : tri + len(bmface.verts) - 2]] + + + def get_tri_verts(self, index): + return self.tri_verts[index] + + def get_edge_verts(self, index): + return self.edge_verts[index] + + def get_loosevert_index(self, index): + return self.looseverts[index] + + + def __del__(self): + if len(self.users) == 1: + self.free_gl() + _Hash.pop(obj.data) + + self.user.remove(self) + #print('mesh_del', self.obj.name) + + +def gpu_Indices_enable_state(): + GPU_Indices_Mesh.init_opengl() + gpu.matrix.push() + gpu.matrix.push_projection() + gpu.matrix.load_projection_matrix(GPU_Indices_Mesh.P) + GPU_Indices_Mesh.shader.bind() + + +def gpu_Indices_restore_state(): + gpu.matrix.pop() + gpu.matrix.pop_projection() + + +def gpu_Indices_use_clip_planes(rv3d, value): + pass #TODO + #if rv3d.use_clip_planes: + #planes = bgl.Buffer(bgl.GL_FLOAT, (6, 4), rv3d.clip_planes) + + #_store_current_shader_state(PreviousGLState) + #GPU_Indices_Mesh.init_opengl() + #bgl.glUseProgram(GPU_Indices_Mesh.shader.program) + #bgl.glUniform1i(GPU_Indices_Mesh.unif_use_clip_planes, value) + + #bgl.glUniform4fv(GPU_Indices_Mesh.unif_clip_plane, 4, planes) + + #_restore_shader_state(PreviousGLState) + + +def gpu_Indices_set_ProjectionMatrix(P): + gpu.matrix.load_projection_matrix(P) + GPU_Indices_Mesh.P[:] = P diff --git a/modules/snap_context/resources/primitive_id_frag.glsl b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl index f3f7a124..3e01f7b0 100644 --- a/modules/snap_context/resources/primitive_id_frag.glsl +++ b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl @@ -1,14 +1,15 @@ -#version 120 +uniform int offset; +#ifdef USE_CLIP_PLANES uniform bool use_clip_planes; -varying vec4 clip_distance; +in vec4 clip_distance; +#endif -uniform float offset; - -varying float primitive_id_var; +out uint FragColor; void main() { +#ifdef USE_CLIP_PLANES if (use_clip_planes && ((clip_distance[0] < 0) || (clip_distance[1] < 0) || @@ -17,6 +18,7 @@ void main() { discard; } +#endif - gl_FragColor = vec4(offset + primitive_id_var, 0, 0, 0); + FragColor = uint(gl_PrimitiveID + offset); } diff --git a/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl new file mode 100644 index 00000000..fa0afec6 --- /dev/null +++ b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl @@ -0,0 +1,25 @@ +uniform mat4 ModelViewProjectionMatrix; + +#ifdef USE_CLIP_PLANES +uniform mat4 ModelViewMatrix; +uniform bool use_clip_planes; +uniform vec4 clip_plane[4]; +out vec4 clip_distance; +#endif + +in vec3 pos; + +void main() +{ +#ifdef USE_CLIP_PLANES + if (use_clip_planes) { + vec4 g_pos = ModelViewMatrix * vec4(pos, 1.0); + + for (int i = 0; i != 4; i++) { + clip_distance[i] = dot(clip_plane[i], g_pos); + } + } +#endif + + gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0); +} diff --git a/modules/snap_context/utils_projection.py b/mesh_snap_utilities_line/snap_context_l/utils_projection.py index d3970b46..cc17aa23 100644 --- a/modules/snap_context/utils_projection.py +++ b/mesh_snap_utilities_line/snap_context_l/utils_projection.py @@ -26,8 +26,8 @@ def depth_get(co, ray_start, ray_dir): def region_2d_to_orig_and_view_vector(region, rv3d, coord): - viewinv = rv3d.view_matrix.inverted() - persinv = rv3d.perspective_matrix.inverted() + viewinv = rv3d.view_matrix.inverted_safe() + persinv = rv3d.perspective_matrix.inverted_safe() dx = (2.0 * coord[0] / region.width) - 1.0 dy = (2.0 * coord[1] / region.height) - 1.0 @@ -39,7 +39,7 @@ def region_2d_to_orig_and_view_vector(region, rv3d, coord): w = out.dot(persinv[3].xyz) + persinv[3][3] - view_vector = ((persinv * out) / w) - origin_start + view_vector = ((persinv @ out) / w) - origin_start else: view_vector = -viewinv.col[2].xyz @@ -52,8 +52,11 @@ def region_2d_to_orig_and_view_vector(region, rv3d, coord): def project_co_v3(sctx, co): - proj_co = sctx.proj_mat * co.to_4d() - proj_co.xy /= proj_co.w + proj_co = sctx.proj_mat @ co.to_4d() + try: + proj_co.xy /= proj_co.w + except Exception as e: + print(e) win_half = sctx.winsize * 0.5 proj_co[0] = (proj_co[0] + 1.0) * win_half[0] @@ -210,3 +213,4 @@ def intersect_ray_segment_fac(v0, v1, ray_direction, ray_origin): c = n - t cray = c.cross(ray_direction) return cray.dot(n) / nlen + diff --git a/mesh_tissue/tessellate_numpy.py b/mesh_tissue/tessellate_numpy.py index 57b80b5e..d2361cb4 100644 --- a/mesh_tissue/tessellate_numpy.py +++ b/mesh_tissue/tessellate_numpy.py @@ -401,7 +401,7 @@ def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode, # vertex group if bool_vertex_group: - new_ob.vertex_groups.new("generator_group") + new_ob.vertex_groups.new(name="generator_group") for i in range(len(new_vertex_group_np)): new_ob.vertex_groups["generator_group"].add([i], new_vertex_group_np[i], diff --git a/mesh_tissue/uv_to_mesh.py b/mesh_tissue/uv_to_mesh.py index 7c64b5f5..f502c147 100644 --- a/mesh_tissue/uv_to_mesh.py +++ b/mesh_tissue/uv_to_mesh.py @@ -141,7 +141,7 @@ class uv_to_mesh(Operator): try: for group in ob0.vertex_groups: index = group.index - ob.vertex_groups.new(group.name) + ob.vertex_groups.new(name=group.name) for p in me0.polygons: for vert, loop in zip(p.vertices, p.loop_indices): ob.vertex_groups[index].add([loop], group.weight(vert), "ADD") diff --git a/modules/rna_manual_reference.py b/modules/rna_manual_reference.py index 6f7903ef..1ba5501a 100644 --- a/modules/rna_manual_reference.py +++ b/modules/rna_manual_reference.py @@ -253,7 +253,6 @@ url_manual_mapping = ( ("bpy.types.cyclesrendersettings*", "render/cycles/settings/index.html#bpy-types-cyclesrendersettings"), ("bpy.types.datatransfermodifier*", "modeling/modifiers/modify/data_transfer.html#bpy-types-datatransfermodifier"), ("bpy.types.dynamicpaintmodifier*", "physics/dynamic_paint/index.html#bpy-types-dynamicpaintmodifier"), - ("bpy.types.expressioncontroller*", "game_engine/logic/controllers/types/expression.html#bpy-types-expressioncontroller"), ("bpy.types.ffmpegsettings.audio*", "render/output/video.html#bpy-types-ffmpegsettings-audio"), ("bpy.types.followpathconstraint*", "rigging/constraints/relationship/follow_path.html#bpy-types-followpathconstraint"), ("bpy.types.gaussianblursequence*", "editors/vse/sequencer/strips/effects/blur.html#bpy-types-gaussianblursequence"), @@ -333,7 +332,7 @@ url_manual_mapping = ( ("bpy.ops.sound.bake_animation*", "data_system/scenes/properties.html#bpy-ops-sound-bake-animation"), ("bpy.ops.transform.edge_slide*", "modeling/meshes/editing/edges.html#bpy-ops-transform-edge-slide"), ("bpy.ops.transform.vert_slide*", "modeling/meshes/editing/vertices.html#bpy-ops-transform-vert-slide"), - ("bpy.ops.view3d.select_border*", "editors/3dview/object/selecting/tools.html#bpy-ops-view3d-select-border"), + ("bpy.ops.view3d.select_box*", "editors/3dview/object/selecting/tools.html#bpy-ops-view3d-select-border"), ("bpy.ops.view3d.select_circle*", "editors/3dview/object/selecting/tools.html#bpy-ops-view3d-select-circle"), ("bpy.types.adjustmentsequence*", "editors/vse/sequencer/strips/effects/adjustment.html#bpy-types-adjustmentsequence"), ("bpy.types.alphaundersequence*", "editors/vse/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaundersequence"), @@ -344,8 +343,6 @@ url_manual_mapping = ( ("bpy.types.compositornodemask*", "compositing/types/input/mask.html#bpy-types-compositornodemask"), ("bpy.types.compositornodemath*", "compositing/types/converter/math.html#bpy-types-compositornodemath"), ("bpy.types.compositornodetime*", "compositing/types/input/time.html#bpy-types-compositornodetime"), - ("bpy.types.constraintactuator*", "game_engine/logic/actuators/types/constraint.html#bpy-types-constraintactuator"), - ("bpy.types.editobjectactuator*", "game_engine/logic/actuators/types/edit_object.html#bpy-types-editobjectactuator"), ("bpy.types.fluidfluidsettings*", "physics/fluid/types/fluid_object.html#bpy-types-fluidfluidsettings"), ("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiergenerator"), ("bpy.types.freestylelinestyle*", "render/freestyle/parameter_editor/line_style/index.html#bpy-types-freestylelinestyle"), @@ -355,7 +352,7 @@ url_manual_mapping = ( ("bpy.types.movietrackingtrack*", "editors/movie_clip_editor/tracking/clip/properties/introduction.html#bpy-types-movietrackingtrack"), ("bpy.types.nodeoutputfileslot*", "compositing/types/output/file.html#bpy-types-nodeoutputfileslot"), ("bpy.types.normaleditmodifier*", "modeling/modifiers/modify/normal_edit.html#bpy-types-normaleditmodifier"), - ("bpy.types.object.dupli_group*", "editors/3dview/object/properties/duplication/dupligroup.html#bpy-types-object-dupli-group"), + ("bpy.types.object.instance_collection*", "editors/3dview/object/properties/duplication/dupligroup.html#bpy-types-object-dupli-group"), ("bpy.types.scene.audio_volume*", "data_system/scenes/properties.html#bpy-types-scene-audio-volume"), ("bpy.types.shadernodebsdfhair*", "render/cycles/nodes/types/shaders/hair.html#bpy-types-shadernodebsdfhair"), ("bpy.types.shadernodebsdftoon*", "render/cycles/nodes/types/shaders/toon.html#bpy-types-shadernodebsdftoon"), @@ -372,7 +369,6 @@ url_manual_mapping = ( ("bpy.types.shrinkwrapmodifier*", "modeling/modifiers/deform/shrinkwrap.html#bpy-types-shrinkwrapmodifier"), ("bpy.types.splineikconstraint*", "rigging/constraints/tracking/spline_ik.html#bpy-types-splineikconstraint"), ("bpy.types.texturenodetexture*", "render/blender_render/textures/nodes/types/input/texture.html#bpy-types-texturenodetexture"), - ("bpy.types.visibilityactuator*", "game_engine/logic/actuators/types/visibility.html#bpy-types-visibilityactuator"), ("bpy.ops.anim.keyframe_clear*", "animation/keyframes/editing.html#bpy-ops-anim-keyframe-clear"), ("bpy.ops.curve.primitive*add*", "modeling/curves/primitives.html#bpy-ops-curve-primitive-add"), ("bpy.ops.mesh.duplicate_move*", "modeling/meshes/editing/duplicating/duplicate.html#bpy-ops-mesh-duplicate-move"), @@ -386,7 +382,7 @@ url_manual_mapping = ( ("bpy.types.alphaoversequence*", "editors/vse/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaoversequence"), ("bpy.types.armatureeditbones*", "rigging/armatures/bones/editing/index.html#bpy-types-armatureeditbones"), ("bpy.types.childofconstraint*", "rigging/constraints/relationship/child_of.html#bpy-types-childofconstraint"), - ("bpy.types.clamptoconstraint*", "rigging/constraints/tracking/clamp_to.html#bpy-types-clamptoconstraint"), + ("bpy.types.clamptoconstraint*", "rigging/constraints/tracking/clight_to.html#bpy-types-clamptoconstraint"), ("bpy.types.collisionmodifier*", "physics/collision.html#bpy-types-collisionmodifier"), ("bpy.types.collisionsettings*", "physics/collision.html#bpy-types-collisionsettings"), ("bpy.types.compositornodergb*", "compositing/types/input/rgb.html#bpy-types-compositornodergb"), @@ -398,7 +394,7 @@ url_manual_mapping = ( ("bpy.types.meshcachemodifier*", "modeling/modifiers/modify/mesh_cache.html#bpy-types-meshcachemodifier"), ("bpy.types.movieclipsequence*", "editors/vse/sequencer/strips/clip_mask.html#bpy-types-movieclipsequence"), ("bpy.types.object.dimensions*", "editors/3dview/object/properties/transforms.html#bpy-types-object-dimensions"), - ("bpy.types.object.dupli_type*", "editors/3dview/object/properties/duplication/index.html#bpy-types-object-dupli-type"), + ("bpy.types.object.instance_type*", "editors/3dview/object/properties/duplication/index.html#bpy-types-object-dupli-type"), ("bpy.types.object.track_axis*", "editors/3dview/object/properties/relations/extras.html#bpy-types-object-track-axis"), ("bpy.types.scene.active_clip*", "data_system/scenes/properties.html#bpy-types-scene-active-clip"), ("bpy.types.shadernodecombine*", "render/cycles/nodes/types/converter/combine_separate.html#bpy-types-shadernodecombine"), @@ -428,12 +424,10 @@ url_manual_mapping = ( ("bpy.ops.transform.tosphere*", "modeling/meshes/editing/transform/to_sphere.html#bpy-ops-transform-tosphere"), ("bpy.types.actionconstraint*", "rigging/constraints/relationship/action.html#bpy-types-actionconstraint"), ("bpy.types.addonpreferences*", "preferences/addons.html#bpy-types-addonpreferences"), - ("bpy.types.armatureactuator*", "game_engine/logic/actuators/types/armature.html#bpy-types-armatureactuator"), ("bpy.types.armaturemodifier*", "modeling/modifiers/deform/armature.html#bpy-types-armaturemodifier"), ("bpy.types.decimatemodifier*", "modeling/modifiers/generate/decimate.html#bpy-types-decimatemodifier"), ("bpy.types.displacemodifier*", "modeling/modifiers/deform/displace.html#bpy-types-displacemodifier"), ("bpy.types.displaysafeareas*", "render/blender_render/camera/object_data.html#bpy-types-displaysafeareas"), - ("bpy.types.filter2dactuator*", "game_engine/logic/actuators/types/filter_2d.html#bpy-types-filter2dactuator"), ("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierstepped"), ("bpy.types.freestylelineset*", "render/freestyle/parameter_editor/line_set.html#bpy-types-freestylelineset"), ("bpy.types.material.ambient*", "render/blender_render/materials/properties/shading.html#bpy-types-material-ambient"), @@ -441,12 +435,10 @@ url_manual_mapping = ( ("bpy.types.multicamsequence*", "editors/vse/sequencer/strips/effects/multicam.html#bpy-types-multicamsequence"), ("bpy.types.multiplysequence*", "editors/vse/sequencer/strips/effects/multiply.html#bpy-types-multiplysequence"), ("bpy.types.multiresmodifier*", "modeling/modifiers/generate/multiresolution.html#bpy-types-multiresmodifier"), - ("bpy.types.object.draw_type*", "editors/3dview/object/properties/display.html#bpy-types-object-draw-type"), + ("bpy.types.object.display_type*", "editors/3dview/object/properties/display.html#bpy-types-object-draw-type"), ("bpy.types.object.use_extra*", "editors/3dview/object/properties/relations/extras.html#bpy-types-object-use-extra"), ("bpy.types.overdropsequence*", "editors/vse/sequencer/strips/effects/alpha_over_under_overdrop.html#bpy-types-overdropsequence"), ("bpy.types.particlesettings*", "physics/particles/index.html#bpy-types-particlesettings"), - ("bpy.types.propertyactuator*", "game_engine/logic/actuators/types/property.html#bpy-types-propertyactuator"), - ("bpy.types.pythoncontroller*", "game_engine/logic/controllers/types/python.html#bpy-types-pythoncontroller"), ("bpy.types.scenerenderlayer*", "render/cycles/settings/scene/render_layers/layers.html#bpy-types-scenerenderlayer"), ("bpy.types.sequencemodifier*", "editors/vse/sequencer/properties/modifiers.html#bpy-types-sequencemodifier"), ("bpy.types.shadernodeinvert*", "render/cycles/nodes/types/color/invert.html#bpy-types-shadernodeinvert"), @@ -462,7 +454,6 @@ url_manual_mapping = ( ("bpy.types.spaceimageeditor*", "editors/uv_image/image/index.html#bpy-types-spaceimageeditor"), ("bpy.types.spacelogiceditor*", "editors/logic_editor.html#bpy-types-spacelogiceditor"), ("bpy.types.sphfluidsettings*", "physics/fluid/index.html#bpy-types-sphfluidsettings"), - ("bpy.types.steeringactuator*", "game_engine/logic/actuators/types/steering.html#bpy-types-steeringactuator"), ("bpy.types.subtractsequence*", "editors/vse/sequencer/strips/effects/subtract.html#bpy-types-subtractsequence"), ("bpy.types.texturenodegroup*", "render/blender_render/textures/nodes/types/groups.html#bpy-types-texturenodegroup"), ("bpy.types.texturenodeimage*", "render/blender_render/textures/nodes/types/input/image.html#bpy-types-texturenodeimage"), @@ -481,7 +472,6 @@ url_manual_mapping = ( ("bpy.ops.uv.remove_doubles*", "editors/uv_image/uv/editing/layout.html#bpy-ops-uv-remove-doubles"), ("bpy.types.backgroundimage*", "editors/3dview/properties/background_images.html#bpy-types-backgroundimage"), ("bpy.types.booleanmodifier*", "modeling/modifiers/generate/booleans.html#bpy-types-booleanmodifier"), - ("bpy.types.collisionsensor*", "game_engine/logic/sensors/types/collision.html#bpy-types-collisionsensor"), ("bpy.types.constraint.mute*", "rigging/constraints/interface/header.html#bpy-types-constraint-mute"), ("bpy.types.explodemodifier*", "modeling/modifiers/simulate/explode.html#bpy-types-explodemodifier"), ("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fcurvemodifiers"), @@ -491,7 +481,6 @@ url_manual_mapping = ( ("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierlimits"), ("bpy.types.gpussaosettings*", "editors/3dview/properties/shading.html#bpy-types-gpussaosettings"), ("bpy.types.latticemodifier*", "modeling/modifiers/deform/lattice.html#bpy-types-latticemodifier"), - ("bpy.types.messageactuator*", "game_engine/logic/actuators/types/message.html#bpy-types-messageactuator"), ("bpy.types.musgravetexture*", "render/blender_render/textures/types/procedural/musgrave.html#bpy-types-musgravetexture"), ("bpy.types.object.location*", "editors/3dview/object/properties/transforms.html#bpy-types-object-location"), ("bpy.types.object.rotation*", "editors/3dview/object/properties/transforms.html#bpy-types-object-rotation"), @@ -516,28 +505,18 @@ url_manual_mapping = ( ("bpy.ops.transform.resize*", "editors/3dview/object/editing/transform/basics.html#bpy-ops-transform-resize"), ("bpy.ops.transform.rotate*", "editors/3dview/object/editing/transform/basics.html#bpy-ops-transform-rotate"), ("bpy.ops.view3d.localview*", "editors/3dview/navigate/views.html#bpy-ops-view3d-localview"), - ("bpy.types.actionactuator*", "game_engine/logic/actuators/types/action.html#bpy-types-actionactuator"), - ("bpy.types.actuatorsensor*", "game_engine/logic/sensors/types/actuator.html#bpy-types-actuatorsensor"), - ("bpy.types.armaturesensor*", "game_engine/logic/sensors/types/armature.html#bpy-types-armaturesensor"), - ("bpy.types.cameraactuator*", "game_engine/logic/actuators/types/camera.html#bpy-types-cameraactuator"), ("bpy.types.curvesmodifier*", "editors/vse/sequencer/properties/modifiers.html#bpy-types-curvesmodifier"), ("bpy.types.effectsequence*", "editors/vse/sequencer/properties/filter.html#bpy-types-effectsequence"), ("bpy.types.ffmpegsettings*", "render/output/video.html#bpy-types-ffmpegsettings"), ("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiernoise"), ("bpy.types.gpudofsettings*", "editors/3dview/properties/shading.html#bpy-types-gpudofsettings"), - ("bpy.types.joysticksensor*", "game_engine/logic/sensors/types/joystick.html#bpy-types-joysticksensor"), - ("bpy.types.keyboardsensor*", "game_engine/logic/sensors/types/keyboard.html#bpy-types-keyboardsensor"), ("bpy.types.materialstrand*", "render/blender_render/materials/properties/strands.html#bpy-types-materialstrand"), ("bpy.types.materialvolume*", "render/blender_render/materials/special_effects/volume.html#bpy-types-materialvolume"), ("bpy.types.mirrormodifier*", "modeling/modifiers/generate/mirror.html#bpy-types-mirrormodifier"), ("bpy.types.movieclipproxy*", "editors/movie_clip_editor/properties/proxy.html#bpy-types-movieclipproxy"), ("bpy.types.object.up_axis*", "editors/3dview/object/properties/relations/extras.html#bpy-types-object-up-axis"), - ("bpy.types.objectactuator*", "game_engine/logic/actuators/types/motion.html#bpy-types-objectactuator"), - ("bpy.types.parentactuator*", "game_engine/logic/actuators/types/parent.html#bpy-types-parentactuator"), ("bpy.types.particlesystem*", "physics/particles/index.html#bpy-types-particlesystem"), ("bpy.types.particletarget*", "physics/particles/emitter/physics/keyed.html#bpy-types-particletarget"), - ("bpy.types.propertysensor*", "game_engine/logic/sensors/types/property.html#bpy-types-propertysensor"), - ("bpy.types.randomactuator*", "game_engine/logic/actuators/types/random.html#bpy-types-randomactuator"), ("bpy.types.remeshmodifier*", "modeling/modifiers/generate/remesh.html#bpy-types-remeshmodifier"), ("bpy.types.rendersettings*", "render/index.html#bpy-types-rendersettings"), ("bpy.types.rigidbodyworld*", "physics/rigid_body/world.html#bpy-types-rigidbodyworld"), @@ -571,9 +550,7 @@ url_manual_mapping = ( ("bpy.types.gpufxsettings*", "editors/3dview/properties/shading.html#bpy-types-gpufxsettings"), ("bpy.types.imagesequence*", "editors/vse/sequencer/strips/movie_image.html#bpy-types-imagesequence"), ("bpy.types.marbletexture*", "render/blender_render/textures/types/procedural/marble.html#bpy-types-marbletexture"), - ("bpy.types.messagesensor*", "game_engine/logic/sensors/types/message.html#bpy-types-messagesensor"), ("bpy.types.modifier.show*", "modeling/modifiers/introduction.html#bpy-types-modifier-show"), - ("bpy.types.mouseactuator*", "game_engine/logic/actuators/types/mouse.html#bpy-types-mouseactuator"), ("bpy.types.moviesequence*", "editors/vse/sequencer/strips/movie_image.html#bpy-types-moviesequence"), ("bpy.types.movietracking*", "editors/movie_clip_editor/tracking/index.html#bpy-types-movietracking"), ("bpy.types.object.layers*", "editors/3dview/object/properties/relations/layers.html#bpy-types-object-layers"), @@ -581,18 +558,15 @@ url_manual_mapping = ( ("bpy.types.oceanmodifier*", "modeling/modifiers/simulate/ocean.html#bpy-types-oceanmodifier"), ("bpy.types.particlebrush*", "physics/particles/mode.html#bpy-types-particlebrush"), ("bpy.types.scene.gravity*", "physics/gravity.html#bpy-types-scene-gravity"), - ("bpy.types.sceneactuator*", "game_engine/logic/actuators/types/scene.html#bpy-types-sceneactuator"), ("bpy.types.scenesequence*", "editors/vse/sequencer/strips/scene.html#bpy-types-scenesequence"), ("bpy.types.screwmodifier*", "modeling/modifiers/generate/screw.html#bpy-types-screwmodifier"), ("bpy.types.sequenceproxy*", "editors/vse/sequencer/properties/proxy_timecode.html#bpy-types-sequenceproxy"), ("bpy.types.shadernodergb*", "render/cycles/nodes/types/input/rgb.html#bpy-types-shadernodergb"), ("bpy.types.smokemodifier*", "physics/smoke/index.html#bpy-types-smokemodifier"), - ("bpy.types.soundactuator*", "game_engine/logic/actuators/types/sound.html#bpy-types-soundactuator"), ("bpy.types.soundsequence*", "editors/vse/sequencer/strips/sound.html#bpy-types-soundsequence"), ("bpy.types.spaceoutliner*", "editors/outliner.html#bpy-types-spaceoutliner"), ("bpy.types.spacetimeline*", "editors/timeline.html#bpy-types-spacetimeline"), ("bpy.types.spaceuveditor*", "editors/uv_image/uv/index.html#bpy-types-spaceuveditor"), - ("bpy.types.stateactuator*", "game_engine/logic/actuators/types/state.html#bpy-types-stateactuator"), ("bpy.types.stuccitexture*", "render/blender_render/textures/types/procedural/stucci.html#bpy-types-stuccitexture"), ("bpy.types.windowmanager*", "interface/index.html#bpy-types-windowmanager"), ("bpy.types.worldlighting*", "render/blender_render/world/ambient_occlusion.html#bpy-types-worldlighting"), @@ -600,12 +574,10 @@ url_manual_mapping = ( ("bpy.ops.object.convert*", "editors/3dview/object/editing/introduction.html#bpy-ops-object-convert"), ("bpy.ops.object.speaker*", "render/audio/speaker.html#bpy-ops-object-speaker"), ("bpy.ops.transform.bend*", "modeling/meshes/editing/transform/bend.html#bpy-ops-transform-bend"), - ("bpy.types.alwayssensor*", "game_engine/logic/sensors/types/always.html#bpy-types-alwayssensor"), ("bpy.types.bakesettings*", "render/cycles/baking.html#bpy-types-bakesettings"), ("bpy.types.blendtexture*", "render/blender_render/textures/types/procedural/blend.html#bpy-types-blendtexture"), ("bpy.types.castmodifier*", "modeling/modifiers/deform/cast.html#bpy-types-castmodifier"), ("bpy.types.colormanaged*", "render/post_process/color_management.html#bpy-types-colormanaged"), - ("bpy.types.gameactuator*", "game_engine/logic/actuators/types/game.html#bpy-types-gameactuator"), ("bpy.types.glowsequence*", "editors/vse/sequencer/strips/effects/glow.html#bpy-types-glowsequence"), ("bpy.types.gpencilbrush*", "interface/grease_pencil/drawing/brushes.html#bpy-types-gpencilbrush"), ("bpy.types.greasepencil*", "interface/grease_pencil/index.html#bpy-types-greasepencil"), @@ -623,7 +595,6 @@ url_manual_mapping = ( ("bpy.types.object.scale*", "editors/3dview/object/properties/transforms.html#bpy-types-object-scale"), ("bpy.types.oceantexture*", "render/blender_render/textures/types/procedural/ocean.html#bpy-types-oceantexture"), ("bpy.types.particleedit*", "physics/particles/mode.html#bpy-types-particleedit"), - ("bpy.types.randomsensor*", "game_engine/logic/sensors/types/random.html#bpy-types-randomsensor"), ("bpy.types.scene.camera*", "data_system/scenes/properties.html#bpy-types-scene-camera"), ("bpy.types.sequencecrop*", "editors/vse/sequencer/properties/input.html#bpy-types-sequencecrop"), ("bpy.types.skinmodifier*", "modeling/modifiers/generate/skin.html#bpy-types-skinmodifier"), @@ -641,14 +612,11 @@ url_manual_mapping = ( ("bpy.ops.wm.properties*", "data_system/custom_properties.html#bpy-ops-wm-properties"), ("bpy.types.addsequence*", "editors/vse/sequencer/strips/effects/add.html#bpy-types-addsequence"), ("bpy.types.consoleline*", "editors/python_console.html#bpy-types-consoleline"), - ("bpy.types.delaysensor*", "game_engine/logic/sensors/types/delay.html#bpy-types-delaysensor"), ("bpy.types.meshstatvis*", "modeling/meshes/mesh_analysis.html#bpy-types-meshstatvis"), - ("bpy.types.mousesensor*", "game_engine/logic/sensors/types/mouse.html#bpy-types-mousesensor"), ("bpy.types.nodesetting*", "editors/node_editor/nodes/parts.html#bpy-types-nodesetting"), ("bpy.types.object.lock*", "editors/3dview/object/properties/transforms.html#bpy-types-object-lock"), ("bpy.types.object.show*", "editors/3dview/object/properties/display.html#bpy-types-object-show"), ("bpy.types.particlekey*", "physics/particles/emitter/physics/keyed.html#bpy-types-particlekey"), - ("bpy.types.radarsensor*", "game_engine/logic/sensors/types/radar.html#bpy-types-radarsensor"), ("bpy.types.renderlayer*", "render/post_process/layers.html#bpy-types-renderlayer"), ("bpy.types.spaceview3d*", "editors/3dview/index.html#bpy-types-spaceview3d"), ("bpy.types.uipopupmenu*", "interface/controls/buttons/menus.html#bpy-types-uipopupmenu"), @@ -665,8 +633,6 @@ url_manual_mapping = ( ("bpy.types.bpy_struct*", "data_system/custom_properties.html#bpy-types-bpy-struct"), ("bpy.types.compositor*", "compositing/index.html#bpy-types-compositor"), ("bpy.types.constraint*", "rigging/constraints/index.html#bpy-types-constraint"), - ("bpy.types.controller*", "game_engine/logic/controllers/index.html#bpy-types-controller"), - ("bpy.types.nearsensor*", "game_engine/logic/sensors/types/near.html#bpy-types-nearsensor"), ("bpy.types.nodesocket*", "editors/node_editor/nodes/parts.html#bpy-types-nodesocket"), ("bpy.types.pointcache*", "physics/baking.html#bpy-types-pointcache"), ("bpy.types.renderpass*", "render/blender_render/settings/passes.html#bpy-types-renderpass"), @@ -687,7 +653,6 @@ url_manual_mapping = ( ("bpy.types.nodeframe*", "editors/node_editor/nodes/frame.html#bpy-types-nodeframe"), ("bpy.types.nodegroup*", "editors/node_editor/nodes/groups.html#bpy-types-nodegroup"), ("bpy.types.pointlamp*", "render/blender_render/lighting/lamps/point.html#bpy-types-pointlamp"), - ("bpy.types.raysensor*", "game_engine/logic/sensors/types/ray.html#bpy-types-raysensor"), ("bpy.types.spaceinfo*", "editors/info/index.html#bpy-types-spaceinfo"), ("bpy.types.textcurve*", "modeling/texts/index.html#bpy-types-textcurve"), ("bpy.types.uipiemenu*", "interface/controls/buttons/menus.html#bpy-types-uipiemenu"), @@ -697,14 +662,12 @@ url_manual_mapping = ( ("bpy.ops.mesh.noise*", "modeling/meshes/editing/transform/noise.html#bpy-ops-mesh-noise"), ("bpy.ops.mesh.screw*", "modeling/meshes/editing/duplicating/screw.html#bpy-ops-mesh-screw"), ("bpy.ops.safe_areas*", "render/blender_render/camera/object_data.html#bpy-ops-safe-areas"), - ("bpy.types.actuator*", "game_engine/logic/actuators/index.html#bpy-types-actuator"), ("bpy.types.arealamp*", "render/blender_render/lighting/lamps/area/index.html#bpy-types-arealamp"), ("bpy.types.armature*", "rigging/armatures/index.html#bpy-types-armature"), ("bpy.types.editbone*", "rigging/armatures/bones/editing/index.html#bpy-types-editbone"), ("bpy.types.hemilamp*", "render/blender_render/lighting/lamps/hemi.html#bpy-types-hemilamp"), ("bpy.types.keyframe*", "animation/keyframes/index.html#bpy-types-keyframe"), ("bpy.types.linesets*", "render/freestyle/parameter_editor/line_set.html#bpy-types-linesets"), - ("bpy.types.lodlevel*", "game_engine/settings/object.html#bpy-types-lodlevel"), ("bpy.types.material*", "render/blender_render/materials/index.html#bpy-types-material"), ("bpy.types.metaball*", "modeling/metas/index.html#bpy-types-metaball"), ("bpy.types.modifier*", "modeling/modifiers/index.html#bpy-types-modifier"), @@ -748,7 +711,6 @@ url_manual_mapping = ( ("bpy.types.render*", "render/index.html#bpy-types-render"), ("bpy.types.screen*", "interface/window_system/screens.html#bpy-types-screen"), ("bpy.types.sculpt*", "sculpt_paint/sculpting/index.html#bpy-types-sculpt"), - ("bpy.types.sensor*", "game_engine/logic/sensors/index.html#bpy-types-sensor"), ("bpy.types.shader*", "render/cycles/nodes/types/shaders/index.html#bpy-types-shader"), ("bpy.types.window*", "interface/index.html#bpy-types-window"), ("bpy.ops.buttons*", "interface/index.html#bpy-ops-buttons"), @@ -765,7 +727,7 @@ url_manual_mapping = ( ("bpy.types.addon*", "preferences/addons.html#bpy-types-addon"), ("bpy.types.brush*", "sculpt_paint/brush.html#bpy-types-brush"), ("bpy.types.curve*", "modeling/curves/index.html#bpy-types-curve"), - ("bpy.types.group*", "editors/3dview/object/properties/relations/groups.html#bpy-types-group"), + ("bpy.types.collection*", "editors/3dview/object/properties/relations/groups.html#bpy-types-group"), ("bpy.types.image*", "data_system/files/media/image_formats.html#bpy-types-image"), ("bpy.types.nodes*", "editors/node_editor/nodes/index.html#bpy-types-nodes"), ("bpy.types.panel*", "interface/window_system/tabs_panels.html#bpy-types-panel"), @@ -792,7 +754,7 @@ url_manual_mapping = ( ("bpy.types.area*", "interface/window_system/areas.html#bpy-types-area"), ("bpy.types.boid*", "physics/particles/emitter/physics/boids.html#bpy-types-boid"), ("bpy.types.bone*", "rigging/armatures/bones/index.html#bpy-types-bone"), - ("bpy.types.lamp*", "render/blender_render/lighting/index.html#bpy-types-lamp"), + ("bpy.types.light*", "render/blender_render/lighting/index.html#bpy-types-lamp"), ("bpy.types.mask*", "editors/movie_clip_editor/masking/index.html#bpy-types-mask"), ("bpy.types.menu*", "interface/controls/buttons/menus.html#bpy-types-menu"), ("bpy.types.mesh*", "modeling/meshes/index.html#bpy-types-mesh"), @@ -803,9 +765,8 @@ url_manual_mapping = ( ("bpy.ops.curve*", "modeling/curves/index.html#bpy-ops-curve"), ("bpy.ops.fluid*", "physics/fluid/index.html#bpy-ops-fluid"), ("bpy.ops.graph*", "editors/graph_editor/index.html#bpy-ops-graph"), - ("bpy.ops.group*", "editors/3dview/object/properties/relations/groups.html#bpy-ops-group"), + ("bpy.ops.collection*", "editors/3dview/object/properties/relations/groups.html#bpy-ops-group"), ("bpy.ops.image*", "data_system/files/media/image_formats.html#bpy-ops-image"), - ("bpy.ops.logic*", "game_engine/logic/index.html#bpy-ops-logic"), ("bpy.ops.mball*", "modeling/metas/index.html#bpy-ops-mball"), ("bpy.ops.paint*", "sculpt_paint/index.html#bpy-ops-paint"), ("bpy.ops.scene*", "data_system/scenes/index.html#bpy-ops-scene"), @@ -818,7 +779,7 @@ url_manual_mapping = ( ("bpy.ops.file*", "editors/file_browser/index.html#bpy-ops-file"), ("bpy.ops.font*", "modeling/texts/index.html#bpy-ops-font"), ("bpy.ops.info*", "editors/info/index.html#bpy-ops-info"), - ("bpy.ops.lamp*", "render/blender_render/lighting/index.html#bpy-ops-lamp"), + ("bpy.ops.light*", "render/blender_render/lighting/index.html#bpy-ops-lamp"), ("bpy.ops.mask*", "editors/movie_clip_editor/masking/index.html#bpy-ops-mask"), ("bpy.ops.mesh*", "modeling/meshes/index.html#bpy-ops-mesh"), ("bpy.ops.node*", "editors/node_editor/nodes/index.html#bpy-ops-node"), diff --git a/modules/snap_context/mesh_drawing.py b/modules/snap_context/mesh_drawing.py deleted file mode 100644 index bdfca4d3..00000000 --- a/modules/snap_context/mesh_drawing.py +++ /dev/null @@ -1,518 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# ##### END GPL LICENSE BLOCK ##### - - -import bgl -import bmesh -import numpy as np -from mathutils import Matrix - -from .utils_shader import Shader - - -def load_shader(shadername): - from os import path - with open(path.join(path.dirname(__file__), 'resources', shadername), 'r') as f: - return f.read() - -def gl_buffer_void_as_long(value): - import ctypes - a = (ctypes.c_byte * 1).from_address(value) - return bgl.Buffer(bgl.GL_BYTE, 1, a) - -def get_mesh_vert_co_array(me): - tot_vco = len(me.vertices) - if tot_vco: - verts_co = np.empty(len(me.vertices) * 3, 'f4') - me.vertices.foreach_get("co", verts_co) - verts_co.shape = (-1, 3) - return verts_co - return None - - -def get_bmesh_vert_co_array(bm): - tot_vco = len(bm.verts) - if tot_vco: - return np.array([v.co for v in bm.verts], 'f4') - return None - - -def get_mesh_tri_verts_array(me): - me.calc_tessface() - len_tessfaces = len(me.tessfaces) - if len_tessfaces: - tessfaces = np.empty(len_tessfaces * 4, 'i4') - me.tessfaces.foreach_get("vertices_raw", tessfaces) - tessfaces.shape = (-1, 4) - - quad_indices = tessfaces[:, 3].nonzero()[0] - tris = np.empty(((len_tessfaces + len(quad_indices)), 3), 'i4') - - tris[:len_tessfaces] = tessfaces[:, :3] - tris[len_tessfaces:] = tessfaces[quad_indices][:, (0, 2, 3)] - - del tessfaces - return tris - return None - - -def get_bmesh_tri_verts_array(bm): - ltris = bm.calc_tessface() - tris = [[ltri[0].vert.index, ltri[1].vert.index, ltri[2].vert.index] for ltri in ltris if not ltri[0].face.hide] - if tris: - return np.array(tris, 'i4') - return None - - -def get_mesh_edge_verts_array(me): - tot_edges = len(me.edges) - if tot_edges: - edge_verts = np.empty(tot_edges * 2, 'i4') - me.edges.foreach_get("vertices", edge_verts) - edge_verts.shape = tot_edges, 2 - return edge_verts - return None - - -def get_bmesh_edge_verts_array(bm): - bm.edges.ensure_lookup_table() - edges = [[e.verts[0].index, e.verts[1].index] for e in bm.edges if not e.hide] - if edges: - return np.array(edges, 'i4') - return None - - -def get_mesh_loosevert_array(me, edges): - verts = np.arange(len(me.vertices)) - - mask = np.in1d(verts, edges, invert=True) - - verts = verts[mask] - if len(verts): - return verts - return None - - -def get_bmesh_loosevert_array(bm): - looseverts = [v.index for v in bm.verts if not (v.link_edges or v.hide)] - if looseverts: - return np.array(looseverts, 'i4') - return None - - -class _Mesh_Arrays(): - def __init__(self, obj, create_tris, create_edges, create_looseverts): - self.tri_verts = self.edge_verts = self.looseverts = None - self.tris_co = self.edges_co = self.looseverts_co = None - if obj.type == 'MESH': - me = obj.data - if me.is_editmode: - bm = bmesh.from_edit_mesh(me) - bm.verts.ensure_lookup_table() - - self.verts_co = get_bmesh_vert_co_array(bm) - - if create_tris: - self.tri_verts = get_bmesh_tri_verts_array(bm) - if create_edges: - self.edge_verts = get_bmesh_edge_verts_array(bm) - if create_looseverts: - self.looseverts = get_bmesh_loosevert_array(bm) - else: - self.verts_co = get_mesh_vert_co_array(me) - - if create_tris: - self.tri_verts = get_mesh_tri_verts_array(me) - if create_edges: - self.edge_verts = get_mesh_edge_verts_array(me) - if create_looseverts: - edge_verts = self.edge_verts - if edge_verts is None: - edge_verts = get_mesh_edge_verts_array(me) - self.looseverts = get_mesh_loosevert_array(me, edge_verts) - del edge_verts - - else: #TODO - self.verts_co = np.zeros((1,3), 'f4') - self.looseverts = np.zeros(1, 'i4') - - def __del__(self): - del self.tri_verts, self.edge_verts, self.looseverts - del self.verts_co - - -class GPU_Indices_Mesh(): - shader = None - - @classmethod - def end_opengl(cls): - del cls.shader - del cls._NULL - del cls.P - del cls.MV - del cls.MVP - del cls.vert_index - del cls.tri_co - del cls.edge_co - del cls.vert_co - - del cls - - @classmethod - def init_opengl(cls): - # OpenGL was already initialized, nothing to do here. - if cls.shader is not None: - return - - import atexit - - # Make sure we only registered the callback once. - atexit.unregister(cls.end_opengl) - atexit.register(cls.end_opengl) - - cls.shader = Shader( - load_shader('3D_vert.glsl'), - None, - load_shader('primitive_id_frag.glsl'), - ) - - cls.unif_use_clip_planes = bgl.glGetUniformLocation(cls.shader.program, 'use_clip_planes') - cls.unif_clip_plane = bgl.glGetUniformLocation(cls.shader.program, 'clip_plane') - - cls._NULL = gl_buffer_void_as_long(0) - - cls.unif_MVP = bgl.glGetUniformLocation(cls.shader.program, 'MVP') - cls.unif_MV = bgl.glGetUniformLocation(cls.shader.program, 'MV') - cls.unif_offset = bgl.glGetUniformLocation(cls.shader.program, 'offset') - - cls.attr_pos = bgl.glGetAttribLocation(cls.shader.program, 'pos') - cls.attr_primitive_id = bgl.glGetAttribLocation(cls.shader.program, 'primitive_id') - - cls.P = bgl.Buffer(bgl.GL_FLOAT, (4, 4)) - cls.MV = bgl.Buffer(bgl.GL_FLOAT, (4, 4)) - cls.MVP = bgl.Buffer(bgl.GL_FLOAT, (4, 4)) - - # returns of public API # - cls.vert_index = bgl.Buffer(bgl.GL_INT, 1) - - cls.tri_co = bgl.Buffer(bgl.GL_FLOAT, (3, 3)) - cls.edge_co = bgl.Buffer(bgl.GL_FLOAT, (2, 3)) - cls.vert_co = bgl.Buffer(bgl.GL_FLOAT, 3) - - def __init__(self, obj, draw_tris, draw_edges, draw_verts): - GPU_Indices_Mesh.init_opengl() - - self.obj = obj - self.draw_tris = draw_tris - self.draw_edges = draw_edges - self.draw_verts = draw_verts - - self.vbo = None - self.vbo_tris = None - self.vbo_edges = None - self.vbo_verts = None - - ## Create VAO ## - self.vao = bgl.Buffer(bgl.GL_INT, 1) - bgl.glGenVertexArrays(1, self.vao) - bgl.glBindVertexArray(self.vao[0]) - - ## Init Array ## - mesh_arrays = _Mesh_Arrays(obj, draw_tris, draw_edges, draw_verts) - - ## Create VBO for vertices ## - if mesh_arrays.verts_co is None: - self.draw_tris = False - self.draw_edges = False - self.draw_verts = False - return - - if False: # Blender 2.8 - self.vbo_len = len(mesh_arrays.verts_co) - - self.vbo = bgl.Buffer(bgl.GL_INT, 1) - bgl.glGenBuffers(1, self.vbo) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo[0]) - verts_co = bgl.Buffer(bgl.GL_FLOAT, mesh_arrays.verts_co.shape, mesh_arrays.verts_co) - bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.vbo_len * 12, verts_co, bgl.GL_STATIC_DRAW) - - ## Create VBO for Tris ## - if mesh_arrays.tri_verts is not None: - self.tri_verts = mesh_arrays.tri_verts - self.num_tris = len(self.tri_verts) - - np_tris_co = mesh_arrays.verts_co[mesh_arrays.tri_verts] - np_tris_co = bgl.Buffer(bgl.GL_FLOAT, np_tris_co.shape, np_tris_co) - self.vbo_tris = bgl.Buffer(bgl.GL_INT, 1) - bgl.glGenBuffers(1, self.vbo_tris) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tris[0]) - bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_tris * 36, np_tris_co, bgl.GL_STATIC_DRAW) - del np_tris_co - - tri_indices = np.repeat(np.arange(self.num_tris, dtype = 'f4'), 3) - tri_indices = bgl.Buffer(bgl.GL_FLOAT, tri_indices.shape, tri_indices) - self.vbo_tri_indices = bgl.Buffer(bgl.GL_INT, 1) - bgl.glGenBuffers(1, self.vbo_tri_indices) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tri_indices[0]) - bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_tris * 12, tri_indices, bgl.GL_STATIC_DRAW) - del tri_indices - - else: - self.num_tris = 0 - self.draw_tris = False - - ## Create VBO for Edges ## - if mesh_arrays.edge_verts is not None: - self.edge_verts = mesh_arrays.edge_verts - self.num_edges = len(self.edge_verts) - - np_edges_co = mesh_arrays.verts_co[mesh_arrays.edge_verts] - np_edges_co = bgl.Buffer(bgl.GL_FLOAT, np_edges_co.shape, np_edges_co) - self.vbo_edges = bgl.Buffer(bgl.GL_INT, 1) - bgl.glGenBuffers(1, self.vbo_edges) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edges[0]) - bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_edges * 24, np_edges_co, bgl.GL_STATIC_DRAW) - del np_edges_co - - edge_indices = np.repeat(np.arange(self.num_edges, dtype = 'f4'), 2) - edge_indices = bgl.Buffer(bgl.GL_FLOAT, edge_indices.shape, edge_indices) - self.vbo_edge_indices = bgl.Buffer(bgl.GL_INT, 1) - bgl.glGenBuffers(1, self.vbo_edge_indices) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edge_indices[0]) - bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_edges * 8, edge_indices, bgl.GL_STATIC_DRAW) - del edge_indices - else: - self.num_edges = 0 - self.draw_edges = False - - ## Create EBO for Loose Verts ## - if mesh_arrays.looseverts is not None: - self.looseverts = mesh_arrays.looseverts - self.num_verts = len(mesh_arrays.looseverts) - - np_lverts_co = mesh_arrays.verts_co[mesh_arrays.looseverts] - np_lverts_co = bgl.Buffer(bgl.GL_FLOAT, np_lverts_co.shape, np_lverts_co) - self.vbo_verts = bgl.Buffer(bgl.GL_INT, 1) - bgl.glGenBuffers(1, self.vbo_verts) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_verts[0]) - bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_verts * 12, np_lverts_co, bgl.GL_STATIC_DRAW) - del np_lverts_co - - looseverts_indices = np.arange(self.num_verts, dtype = 'f4') - looseverts_indices = bgl.Buffer(bgl.GL_FLOAT, looseverts_indices.shape, looseverts_indices) - self.vbo_looseverts_indices = bgl.Buffer(bgl.GL_INT, 1) - bgl.glGenBuffers(1, self.vbo_looseverts_indices) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_looseverts_indices[0]) - bgl.glBufferData(bgl.GL_ARRAY_BUFFER, self.num_verts * 4, looseverts_indices, bgl.GL_STATIC_DRAW) - del looseverts_indices - else: - self.num_verts = 0 - self.draw_verts = False - - del mesh_arrays - - bgl.glBindVertexArray(0) - - - def get_tot_elems(self): - tot = 0 - - if self.draw_tris: - tot += self.num_tris - - if self.draw_edges: - tot += self.num_edges - - if self.draw_verts: - tot += self.num_verts - - return tot - - - def set_draw_mode(self, draw_tris, draw_edges, draw_verts): - self.draw_tris = draw_tris and self.vbo_tris - self.draw_edges = draw_edges and self.vbo_edges - self.draw_verts = draw_verts and self.vbo_verts - - - def set_ModelViewMatrix(self, MV): - self.MV[:] = MV[:] - self.MVP[:] = Matrix(self.P) * MV - - - def Draw(self, index_offset): - self.first_index = index_offset - bgl.glUseProgram(self.shader.program) - bgl.glBindVertexArray(self.vao[0]) - - bgl.glUniformMatrix4fv(self.unif_MV, 1, bgl.GL_TRUE, self.MV) - bgl.glUniformMatrix4fv(self.unif_MVP, 1, bgl.GL_TRUE, self.MVP) - - if self.draw_tris: - bgl.glUniform1f(self.unif_offset, float(index_offset)) # bgl has no glUniform1ui :\ - - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tris[0]) - bgl.glEnableVertexAttribArray(self.attr_pos) - bgl.glVertexAttribPointer(self.attr_pos, 3, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL) - - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tri_indices[0]) - bgl.glEnableVertexAttribArray(self.attr_primitive_id) - bgl.glVertexAttribPointer(self.attr_primitive_id, 1, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL) - - bgl.glDrawArrays(bgl.GL_TRIANGLES, 0, self.num_tris * 3) - - index_offset += self.num_tris - bgl.glDepthRange(-0.00005, 0.99995) - - if self.draw_edges: - bgl.glUniform1f(self.unif_offset, float(index_offset)) #TODO: use glUniform1ui - - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edges[0]) - bgl.glVertexAttribPointer(self.attr_pos, 3, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL) - bgl.glEnableVertexAttribArray(self.attr_pos) - - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edge_indices[0]) - bgl.glVertexAttribPointer(self.attr_primitive_id, 1, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL) - bgl.glEnableVertexAttribArray(self.attr_primitive_id) - - bgl.glDrawArrays(bgl.GL_LINES, 0, self.num_edges * 2) - - index_offset += self.num_edges - - if self.draw_verts: - bgl.glUniform1f(self.unif_offset, float(index_offset)) #TODO: use glUniform1ui - - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_verts[0]) - bgl.glVertexAttribPointer(self.attr_pos, 3, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL) - bgl.glEnableVertexAttribArray(self.attr_pos) - - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_looseverts_indices[0]) - bgl.glVertexAttribPointer(self.attr_primitive_id, 1, bgl.GL_FLOAT, bgl.GL_FALSE, 0, self._NULL) - bgl.glEnableVertexAttribArray(self.attr_primitive_id) - - bgl.glDrawArrays(bgl.GL_POINTS, 0, self.num_verts) - - bgl.glDepthRange(0.0, 1.0) - - - def get_tri_co(self, index): - bgl.glBindVertexArray(self.vao[0]) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_tris[0]) - bgl.glGetBufferSubData(bgl.GL_ARRAY_BUFFER, index * 36, 36, self.tri_co) - bgl.glBindVertexArray(0) - return self.tri_co - - - def get_edge_co(self, index): - bgl.glBindVertexArray(self.vao[0]) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_edges[0]) - bgl.glGetBufferSubData(bgl.GL_ARRAY_BUFFER, index * 24, 24, self.edge_co) - bgl.glBindVertexArray(0) - return self.edge_co - - - def get_loosevert_co(self, index): - bgl.glBindVertexArray(self.vao[0]) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, self.vbo_verts[0]) - bgl.glGetBufferSubData(bgl.GL_ARRAY_BUFFER, index * 12, 12, self.vert_co) - bgl.glBindVertexArray(0) - return self.vert_co - - - def get_tri_verts(self, index): - return self.tri_verts[index] - - - def get_edge_verts(self, index): - return self.edge_verts[index] - - - def get_loosevert_index(self, index): - return self.looseverts[index] - - - def __del__(self): - if self.vbo_tris: - bgl.glDeleteBuffers(1, self.vbo_tris) - bgl.glDeleteBuffers(1, self.vbo_tri_indices) - del self.tri_verts - - if self.vbo_edges: - bgl.glDeleteBuffers(1, self.vbo_edges) - bgl.glDeleteBuffers(1, self.vbo_edge_indices) - del self.edge_verts - - if self.vbo_verts: - bgl.glDeleteBuffers(1, self.vbo_verts) - bgl.glDeleteBuffers(1, self.vbo_looseverts_indices) - del self.looseverts - - bgl.glDeleteVertexArrays(1, self.vao) - #print('mesh_del', self.obj.name) - - -class PreviousGLState: - buf = bgl.Buffer(bgl.GL_INT, (4, 1)) - cur_program = buf[0] - cur_vao = buf[1] - cur_vbo = buf[2] - cur_ebo = buf[3] - - -def _store_current_shader_state(cls): - bgl.glGetIntegerv(bgl.GL_CURRENT_PROGRAM, cls.cur_program) - bgl.glGetIntegerv(bgl.GL_VERTEX_ARRAY_BINDING, cls.cur_vao) - bgl.glGetIntegerv(bgl.GL_ARRAY_BUFFER_BINDING, cls.cur_vbo) - bgl.glGetIntegerv(bgl.GL_ELEMENT_ARRAY_BUFFER_BINDING, cls.cur_ebo) - - -def _restore_shader_state(cls): - bgl.glUseProgram(cls.cur_program[0]) - bgl.glBindVertexArray(cls.cur_vao[0]) - bgl.glBindBuffer(bgl.GL_ARRAY_BUFFER, cls.cur_vbo[0]) - bgl.glBindBuffer(bgl.GL_ELEMENT_ARRAY_BUFFER, cls.cur_ebo[0]) - - -def gpu_Indices_enable_state(): - _store_current_shader_state(PreviousGLState) - - GPU_Indices_Mesh.init_opengl() - bgl.glUseProgram(GPU_Indices_Mesh.shader.program) - #bgl.glBindVertexArray(GPU_Indices_Mesh.vao[0]) - - -def gpu_Indices_restore_state(): - bgl.glBindVertexArray(0) - _restore_shader_state(PreviousGLState) - - -def gpu_Indices_use_clip_planes(rv3d, value): - if rv3d.use_clip_planes: - planes = bgl.Buffer(bgl.GL_FLOAT, (6, 4), rv3d.clip_planes) - - _store_current_shader_state(PreviousGLState) - GPU_Indices_Mesh.init_opengl() - bgl.glUseProgram(GPU_Indices_Mesh.shader.program) - bgl.glUniform1i(GPU_Indices_Mesh.unif_use_clip_planes, value) - - bgl.glUniform4fv(GPU_Indices_Mesh.unif_clip_plane, 4, planes) - - _restore_shader_state(PreviousGLState) - - -def gpu_Indices_set_ProjectionMatrix(P): - GPU_Indices_Mesh.P[:] = P diff --git a/modules/snap_context/resources/3D_vert.glsl b/modules/snap_context/resources/3D_vert.glsl deleted file mode 100644 index c97df2bf..00000000 --- a/modules/snap_context/resources/3D_vert.glsl +++ /dev/null @@ -1,26 +0,0 @@ -#version 120 - -uniform bool use_clip_planes; -uniform vec4 clip_plane[4]; -varying vec4 clip_distance; - -uniform mat4 MV; -uniform mat4 MVP; - -attribute vec3 pos; -attribute float primitive_id; -varying float primitive_id_var; - -void main() -{ - if (use_clip_planes) { - vec4 g_pos = MV * vec4(pos, 1.0); - - for (int i = 0; i != 4; i++) { - clip_distance[i] = dot(clip_plane[i], g_pos); - } - } - - primitive_id_var = primitive_id; - gl_Position = MVP * vec4(pos, 1.0); -} diff --git a/modules/snap_context/utils_shader.py b/modules/snap_context/utils_shader.py deleted file mode 100644 index 1758585a..00000000 --- a/modules/snap_context/utils_shader.py +++ /dev/null @@ -1,85 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -# ##### END GPL LICENSE BLOCK ##### - - -import bgl - -def check_shaderError(shader, flag, isProgram, errorMessage): - success = bgl.Buffer(bgl.GL_INT, 1) - - if isProgram: - bgl.glGetProgramiv(shader, flag, success) - else: - bgl.glGetShaderiv(shader, flag, success) - - if success[0] == bgl.GL_FALSE: - import numpy as np - import ctypes - - offset = bgl.Buffer(bgl.GL_INT, 1, (ctypes.c_int32 * 1).from_address(0)) - error = bgl.Buffer(bgl.GL_BYTE, 1024) - if isProgram: - bgl.glGetProgramInfoLog(shader, 1024, offset, error) - print(errorMessage, np.bytes_(error).decode("utf-8")) - else: - bgl.glGetShaderInfoLog(shader, 1024, offset, error) - print(errorMessage, np.bytes_(error).decode("utf-8")) - - del offset - raise #RuntimeError(errorMessage, bgl.glGetShaderInfoLog(shader)) - - -def create_shader(source, shaderType): - shader = bgl.glCreateShader(shaderType) - - if shader == 0: - raise RuntimeError("Error: Shader creation failed!") - - bgl.glShaderSource(shader, source) - bgl.glCompileShader(shader) - - check_shaderError(shader, bgl.GL_COMPILE_STATUS, False, "Error: Shader compilation failed:") - - return shader - - -class Shader(): - def __init__(self, vertexcode, geomcode, fragcode): - self.program = bgl.glCreateProgram() - self.shaders = [] - - if vertexcode: - self.shaders.append(create_shader(vertexcode, bgl.GL_VERTEX_SHADER)) - if geomcode: - self.shaders.append(create_shader(geomcode, bgl.GL_GEOMETRY_SHADER)) - if fragcode: - self.shaders.append(create_shader(fragcode, bgl.GL_FRAGMENT_SHADER)) - - for shad in self.shaders: - bgl.glAttachShader(self.program, shad) - - bgl.glLinkProgram(self.program) - check_shaderError(self.program, bgl.GL_LINK_STATUS, True, "Error: Program linking failed:") - bgl.glValidateProgram(self.program) - check_shaderError(self.program, bgl.GL_VALIDATE_STATUS, True, "Error: Program is invalid:") - - def __del__(self): - for shad in self.shaders: - bgl.glDetachShader(self.program, shad) - bgl.glDeleteShader(shad) - bgl.glDeleteProgram(self.program) - #print('shader_del') diff --git a/netrender/ui.py b/netrender/ui.py index c5c22329..bcd83197 100644 --- a/netrender/ui.py +++ b/netrender/ui.py @@ -35,7 +35,7 @@ ADDRESS_TEST_TIMEOUT = 30 def base_poll(cls, context): rd = context.scene.render - return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES) + return (rd.engine in cls.COMPAT_ENGINES) def init_file(): @@ -96,7 +96,7 @@ class NetRenderButtonsPanel(): @classmethod def poll(cls, context): rd = context.scene.render - return rd.engine == 'NET_RENDER' and rd.use_game_engine == False + return rd.engine == 'NET_RENDER' # Setting panel, use in the scene for now. class RENDER_PT_network_settings(NetRenderButtonsPanel, bpy.types.Panel): diff --git a/node_wrangler.py b/node_wrangler.py index b535a580..d3fd8448 100644 --- a/node_wrangler.py +++ b/node_wrangler.py @@ -20,7 +20,7 @@ bl_info = { "name": "Node Wrangler", "author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer", "version": (3, 35), - "blender": (2, 78, 0), + "blender": (2, 80, 0), "location": "Node Editor Toolbar or Ctrl-Space", "description": "Various tools to enhance and speed up node-based workflow", "warning": "", @@ -115,7 +115,7 @@ shaders_input_nodes_props = ( # Keeping mixed case to avoid having to translate entries when adding new nodes in operators. shaders_output_nodes_props = ( ('ShaderNodeOutputMaterial', 'OUTPUT_MATERIAL', 'Material Output'), - ('ShaderNodeOutputLamp', 'OUTPUT_LAMP', 'Lamp Output'), + ('ShaderNodeOutputLight', 'OUTPUT_LIGHT', 'Light Output'), ('ShaderNodeOutputWorld', 'OUTPUT_WORLD', 'World Output'), ) # (rna_type.identifier, type, rna_type.name) @@ -332,7 +332,7 @@ compo_layout_nodes_props = ( blender_mat_input_nodes_props = ( ('ShaderNodeMaterial', 'MATERIAL', 'Material'), ('ShaderNodeCameraData', 'CAMERA', 'Camera Data'), - ('ShaderNodeLampData', 'LAMP', 'Lamp Data'), + ('ShaderNodeLightData', 'LIGHT', 'Light Data'), ('ShaderNodeValue', 'VALUE', 'Value'), ('ShaderNodeRGB', 'RGB', 'RGB'), ('ShaderNodeTexture', 'TEXTURE', 'Texture'), @@ -547,13 +547,16 @@ draw_color_sets = { } +def is_cycles_or_eevee(context): + return context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'} + + def nice_hotkey_name(punc): # convert the ugly string name into the actual character pairs = ( ('LEFTMOUSE', "LMB"), ('MIDDLEMOUSE', "MMB"), ('RIGHTMOUSE', "RMB"), - ('SELECTMOUSE', "Select"), ('WHEELUPMOUSE', "Wheel Up"), ('WHEELDOWNMOUSE', "Wheel Down"), ('WHEELINMOUSE', "Wheel In"), @@ -1000,39 +1003,39 @@ def get_nodes_links(context): # Principled prefs class NWPrincipledPreferences(bpy.types.PropertyGroup): - base_color = StringProperty( + base_color: StringProperty( name='Base Color', default='diffuse diff albedo base col color', description='Naming Components for Base Color maps') - sss_color = StringProperty( + sss_color: StringProperty( name='Subsurface Color', default='sss subsurface', description='Naming Components for Subsurface Color maps') - metallic = StringProperty( + metallic: StringProperty( name='Metallic', default='metallic metalness metal mtl', description='Naming Components for metallness maps') - specular = StringProperty( + specular: StringProperty( name='Specular', default='specularity specular spec spc', description='Naming Components for Specular maps') - normal = StringProperty( + normal: StringProperty( name='Normal', default='normal nor nrm nrml norm', description='Naming Components for Normal maps') - bump = StringProperty( + bump: StringProperty( name='Bump', default='bump bmp', description='Naming Components for bump maps') - rough = StringProperty( + rough: StringProperty( name='Roughness', default='roughness rough rgh', description='Naming Components for roughness maps') - gloss = StringProperty( + gloss: StringProperty( name='Gloss', default='gloss glossy glossyness', description='Naming Components for glossy maps') - displacement = StringProperty( + displacement: StringProperty( name='Displacement', default='displacement displace disp dsp height heightmap', description='Naming Components for displacement maps') @@ -1041,7 +1044,7 @@ class NWPrincipledPreferences(bpy.types.PropertyGroup): class NWNodeWrangler(bpy.types.AddonPreferences): bl_idname = __name__ - merge_hide = EnumProperty( + merge_hide: EnumProperty( name="Hide Mix nodes", items=( ("ALWAYS", "Always", "Always collapse the new merge nodes"), @@ -1050,7 +1053,7 @@ class NWNodeWrangler(bpy.types.AddonPreferences): ), default='NON_SHADER', description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify whether to collapse them or show the full node with options expanded") - merge_position = EnumProperty( + merge_position: EnumProperty( name="Mix Node Position", items=( ("CENTER", "Center", "Place the Mix node between the two nodes"), @@ -1059,22 +1062,22 @@ class NWNodeWrangler(bpy.types.AddonPreferences): default='CENTER', description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes") - show_hotkey_list = BoolProperty( + show_hotkey_list: BoolProperty( name="Show Hotkey List", default=False, description="Expand this box into a list of all the hotkeys for functions in this addon" ) - hotkey_list_filter = StringProperty( + hotkey_list_filter: StringProperty( name=" Filter by Name", default="", description="Show only hotkeys that have this text in their name" ) - show_principled_lists = BoolProperty( + show_principled_lists: BoolProperty( name="Show Principled naming tags", default=False, description="Expand this box into a list of all naming tags for principled texture setup" ) - principled_tags = bpy.props.PointerProperty(type=NWPrincipledPreferences) + principled_tags: bpy.props.PointerProperty(type=NWPrincipledPreferences) def draw(self, context): layout = self.layout @@ -1113,7 +1116,7 @@ class NWNodeWrangler(bpy.types.AddonPreferences): if self.hotkey_list_filter.lower() in hotkey_name.lower(): row = col.row(align=True) - row.label(hotkey_name) + row.label(text=hotkey_name) keystr = nice_hotkey_name(hotkey[1]) if hotkey[4]: keystr = "Shift " + keystr @@ -1121,7 +1124,7 @@ class NWNodeWrangler(bpy.types.AddonPreferences): keystr = "Alt " + keystr if hotkey[3]: keystr = "Ctrl " + keystr - row.label(keystr) + row.label(text=keystr) @@ -1223,7 +1226,7 @@ class NWLazyConnect(Operator, NWBase): bl_idname = "node.nw_lazy_connect" bl_label = "Lazy Connect" bl_options = {'REGISTER', 'UNDO'} - with_menu = BoolProperty() + with_menu: BoolProperty() def modal(self, context, event): context.area.tag_redraw() @@ -1329,12 +1332,12 @@ class NWDeleteUnused(Operator, NWBase): bl_label = 'Delete Unused Nodes' bl_options = {'REGISTER', 'UNDO'} - delete_muted = BoolProperty(name="Delete Muted", description="Delete (but reconnect, like Ctrl-X) all muted nodes", default=True) - delete_frames = BoolProperty(name="Delete Empty Frames", description="Delete all frames that have no nodes inside them", default=True) + delete_muted: BoolProperty(name="Delete Muted", description="Delete (but reconnect, like Ctrl-X) all muted nodes", default=True) + delete_frames: BoolProperty(name="Delete Empty Frames", description="Delete all frames that have no nodes inside them", default=True) def is_unused_node(self, node): end_types = ['OUTPUT_MATERIAL', 'OUTPUT', 'VIEWER', 'COMPOSITE', \ - 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LAMP', \ + 'SPLITVIEWER', 'OUTPUT_FILE', 'LEVELS', 'OUTPUT_LIGHT', \ 'OUTPUT_WORLD', 'GROUP_INPUT', 'GROUP_OUTPUT', 'FRAME'] if node.type in end_types: return False @@ -1558,8 +1561,8 @@ class NWResetBG(Operator, NWBase): def execute(self, context): context.space_data.backdrop_zoom = 1 - context.space_data.backdrop_x = 0 - context.space_data.backdrop_y = 0 + context.space_data.backdrop_offset[0] = 0 + context.space_data.backdrop_offset[1] = 0 return {'FINISHED'} @@ -1567,9 +1570,10 @@ class NWAddAttrNode(Operator, NWBase): """Add an Attribute node with this name""" bl_idname = 'node.nw_add_attr_node' bl_label = 'Add UV map' - attr_name = StringProperty() bl_options = {'REGISTER', 'UNDO'} + attr_name: StringProperty() + def execute(self, context): bpy.ops.node.add_node('INVOKE_DEFAULT', use_transform=True, type="ShaderNodeAttribute") nodes, links = get_nodes_links(context) @@ -1585,7 +1589,7 @@ class NWEmissionViewer(Operator, NWBase): @classmethod def poll(cls, context): - is_cycles = context.scene.render.engine == 'CYCLES' + is_cycles = is_cycles_or_eevee(context) if nw_check(context): space = context.space_data if space.tree_type == 'ShaderNodeTree' and is_cycles: @@ -1600,13 +1604,13 @@ class NWEmissionViewer(Operator, NWBase): space = context.space_data shader_type = space.shader_type if shader_type == 'OBJECT': - if space.id not in [lamp for lamp in bpy.data.lamps]: # cannot use bpy.data.lamps directly as iterable + if space.id not in [light for light in bpy.data.lights]: # cannot use bpy.data.lights directly as iterable shader_output_type = "OUTPUT_MATERIAL" shader_output_ident = "ShaderNodeOutputMaterial" shader_viewer_ident = "ShaderNodeEmission" else: - shader_output_type = "OUTPUT_LAMP" - shader_output_ident = "ShaderNodeOutputLamp" + shader_output_type = "OUTPUT_LIGHT" + shader_output_ident = "ShaderNodeOutputLight" shader_viewer_ident = "ShaderNodeEmission" elif shader_type == 'WORLD': @@ -1734,9 +1738,19 @@ class NWFrameSelected(Operator, NWBase): bl_label = "Frame Selected" bl_description = "Add a frame node and parent the selected nodes to it" bl_options = {'REGISTER', 'UNDO'} - label_prop = StringProperty(name='Label', default=' ', description='The visual name of the frame node') - color_prop = FloatVectorProperty(name="Color", description="The color of the frame node", default=(0.6, 0.6, 0.6), - min=0, max=1, step=1, precision=3, subtype='COLOR_GAMMA', size=3) + + label_prop: StringProperty( + name='Label', + description='The visual name of the frame node', + default=' ' + ) + color_prop: FloatVectorProperty( + name="Color", + description="The color of the frame node", + default=(0.6, 0.6, 0.6), + min=0, max=1, step=1, precision=3, + subtype='COLOR_GAMMA', size=3 + ) def execute(self, context): nodes, links = get_nodes_links(context) @@ -1795,7 +1809,7 @@ class NWSwitchNodeType(Operator, NWBase): bl_label = "Switch Node Type" bl_options = {'REGISTER', 'UNDO'} - to_type = EnumProperty( + to_type: EnumProperty( name="Switch to type", items=list(shaders_input_nodes_props) + list(shaders_output_nodes_props) + @@ -2009,12 +2023,12 @@ class NWMergeNodes(Operator, NWBase): bl_description = "Merge Selected Nodes" bl_options = {'REGISTER', 'UNDO'} - mode = EnumProperty( + mode: EnumProperty( name="mode", description="All possible blend types and math operations", items=blend_types + [op for op in operations if op not in blend_types], ) - merge_type = EnumProperty( + merge_type: EnumProperty( name="merge type", description="Type of Merge to be used", items=( @@ -2260,11 +2274,11 @@ class NWBatchChangeNodes(Operator, NWBase): bl_description = "Batch Change Blend Type and Math Operation" bl_options = {'REGISTER', 'UNDO'} - blend_type = EnumProperty( + blend_type: EnumProperty( name="Blend Type", items=blend_types + navs, ) - operation = EnumProperty( + operation: EnumProperty( name="Operation", items=operations + navs, ) @@ -2326,7 +2340,7 @@ class NWChangeMixFactor(Operator, NWBase): # option: Change factor. # If option is 1.0 or 0.0 - set to 1.0 or 0.0 # Else - change factor by option value. - option = FloatProperty() + option: FloatProperty() def execute(self, context): nodes, links = get_nodes_links(context) @@ -2465,7 +2479,7 @@ class NWCopyLabel(Operator, NWBase): bl_label = "Copy Label" bl_options = {'REGISTER', 'UNDO'} - option = EnumProperty( + option: EnumProperty( name="option", description="Source of name of label", items=( @@ -2509,7 +2523,7 @@ class NWClearLabel(Operator, NWBase): bl_label = "Clear Label" bl_options = {'REGISTER', 'UNDO'} - option = BoolProperty() + option: BoolProperty() def execute(self, context): nodes, links = get_nodes_links(context) @@ -2531,16 +2545,16 @@ class NWModifyLabels(Operator, NWBase): bl_label = "Modify Labels" bl_options = {'REGISTER', 'UNDO'} - prepend = StringProperty( + prepend: StringProperty( name="Add to Beginning" ) - append = StringProperty( + append: StringProperty( name="Add to End" ) - replace_from = StringProperty( + replace_from: StringProperty( name="Text to Replace" ) - replace_to = StringProperty( + replace_to: StringProperty( name="Replace with" ) @@ -2564,14 +2578,14 @@ class NWAddTextureSetup(Operator, NWBase): bl_description = "Add Texture Node Setup to Selected Shaders" bl_options = {'REGISTER', 'UNDO'} - add_mapping = BoolProperty(name="Add Mapping Nodes", description="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default=True) + add_mapping: BoolProperty(name="Add Mapping Nodes", description="Create coordinate and mapping nodes for the texture (ignored for selected texture nodes)", default=True) @classmethod def poll(cls, context): valid = False if nw_check(context): space = context.space_data - if space.tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES': + if space.tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context): valid = True return valid @@ -2642,26 +2656,28 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper): bl_description = "Add Texture Node Setup for Principled BSDF" bl_options = {'REGISTER', 'UNDO'} - directory = StringProperty( - name='Directory', - subtype='DIR_PATH', - default='', - description='Folder to search in for image files') - files = CollectionProperty( - type=bpy.types.OperatorFileListElement, - options={'HIDDEN', 'SKIP_SAVE'}) + directory: StringProperty( + name='Directory', + subtype='DIR_PATH', + default='', + description='Folder to search in for image files' + ) + files: CollectionProperty( + type=bpy.types.OperatorFileListElement, + options={'HIDDEN', 'SKIP_SAVE'} + ) order = [ "filepath", "files", - ] + ] @classmethod def poll(cls, context): valid = False if nw_check(context): space = context.space_data - if space.tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES': + if space.tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context): valid = True return valid @@ -2755,35 +2771,18 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper): disp_texture.color_space = 'NONE' # Add displacement offset nodes - math_sub = nodes.new(type='ShaderNodeMath') - math_sub.operation = 'SUBTRACT' - math_sub.label = 'Offset' - math_sub.location = active_node.location + Vector((0, -560)) - math_mul = nodes.new(type='ShaderNodeMath') - math_mul.operation = 'MULTIPLY' - math_mul.label = 'Strength' - math_mul.location = math_sub.location + Vector((200, 0)) - link = links.new(math_mul.inputs[0], math_sub.outputs[0]) - link = links.new(math_sub.inputs[0], disp_texture.outputs[0]) - - # Turn on true displacement in the material + disp_node = nodes.new(type='ShaderNodeDisplacement') + disp_node.location = active_node.location + Vector((0, -560)) + link = links.new(disp_node.inputs[0], disp_texture.outputs[0]) + + # TODO Turn on true displacement in the material # Too complicated for now - ''' - # Frame. Does not update immediately - # Seems to need an editor redraw - frame = nodes.new(type='NodeFrame') - frame.label = 'Displacement' - math_sub.parent = frame - math_mul.parent = frame - frame.update() - ''' - - #find output node + # Find output node output_node = [n for n in nodes if n.bl_idname == 'ShaderNodeOutputMaterial'] if output_node: if not output_node[0].inputs[2].is_linked: - link = links.new(output_node[0].inputs[2], math_mul.outputs[0]) + link = links.new(output_node[0].inputs[2], disp_node.outputs[0]) continue @@ -2850,23 +2849,24 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper): # Alignment for i, texture_node in enumerate(texture_nodes): - offset = Vector((-400, (i * -260) + 200)) + offset = Vector((-550, (i * -280) + 200)) texture_node.location = active_node.location + offset if normal_node: # Extra alignment if normal node was added - normal_node.location = normal_node_texture.location + Vector((200, 0)) + normal_node.location = normal_node_texture.location + Vector((300, 0)) if roughness_node: # Alignment of invert node if glossy map - invert_node.location = roughness_node.location + Vector((200, 0)) + invert_node.location = roughness_node.location + Vector((300, 0)) # Add texture input + mapping mapping = nodes.new(type='ShaderNodeMapping') - mapping.location = active_node.location + Vector((-900, 0)) + mapping.location = active_node.location + Vector((-1050, 0)) if len(texture_nodes) > 1: # If more than one texture add reroute node in between reroute = nodes.new(type='NodeReroute') + texture_nodes.append(reroute) tex_coords = Vector((texture_nodes[0].location.x, sum(n.location.y for n in texture_nodes)/len(texture_nodes))) reroute.location = tex_coords + Vector((-50, -120)) for texture_node in texture_nodes: @@ -2880,6 +2880,20 @@ class NWAddPrincipledSetup(Operator, NWBase, ImportHelper): texture_input.location = mapping.location + Vector((-200, 0)) link = links.new(mapping.inputs[0], texture_input.outputs[2]) + # Create frame around tex coords and mapping + frame = nodes.new(type='NodeFrame') + frame.label = 'Mapping' + mapping.parent = frame + texture_input.parent = frame + frame.update() + + # Create frame around texture nodes + frame = nodes.new(type='NodeFrame') + frame.label = 'Textures' + for tnode in texture_nodes: + tnode.parent = frame + frame.update() + # Just to be sure active_node.select = False nodes.update() @@ -2895,7 +2909,7 @@ class NWAddReroutes(Operator, NWBase): bl_description = "Add Reroutes to Outputs" bl_options = {'REGISTER', 'UNDO'} - option = EnumProperty( + option: EnumProperty( name="option", items=[ ('ALL', 'to all', 'Add to all outputs'), @@ -2995,9 +3009,9 @@ class NWLinkActiveToSelected(Operator, NWBase): bl_label = "Link Active Node to Selected" bl_options = {'REGISTER', 'UNDO'} - replace = BoolProperty() - use_node_name = BoolProperty() - use_outputs_names = BoolProperty() + replace: BoolProperty() + use_node_name: BoolProperty() + use_outputs_names: BoolProperty() @classmethod def poll(cls, context): @@ -3075,7 +3089,7 @@ class NWAlignNodes(Operator, NWBase): bl_idname = "node.nw_align_nodes" bl_label = "Align Nodes" bl_options = {'REGISTER', 'UNDO'} - margin = IntProperty(name='Margin', default=50, description='The amount of space between nodes') + margin: IntProperty(name='Margin', default=50, description='The amount of space between nodes') def execute(self, context): nodes, links = get_nodes_links(context) @@ -3145,7 +3159,7 @@ class NWSelectParentChildren(Operator, NWBase): bl_label = "Select Parent or Children" bl_options = {'REGISTER', 'UNDO'} - option = EnumProperty( + option: EnumProperty( name="option", items=( ('PARENT', 'Select Parent', 'Select Parent Frame'), @@ -3228,7 +3242,7 @@ class NWLinkToOutputNode(Operator, NWBase): if not output_node: bpy.ops.node.select_all(action="DESELECT") if tree_type == 'ShaderNodeTree': - if context.scene.render.engine == 'CYCLES': + if is_cycles_or_eevee(context): output_node = nodes.new('ShaderNodeOutputMaterial') else: output_node = nodes.new('ShaderNodeOutput') @@ -3249,7 +3263,7 @@ class NWLinkToOutputNode(Operator, NWBase): break out_input_index = 0 - if tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES': + if tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context): if active.outputs[output_index].name == 'Volume': out_input_index = 1 elif active.outputs[output_index].type != 'SHADER': # connect to displacement if not a shader @@ -3266,8 +3280,8 @@ class NWMakeLink(Operator, NWBase): bl_idname = 'node.nw_make_link' bl_label = 'Make Link' bl_options = {'REGISTER', 'UNDO'} - from_socket = IntProperty() - to_socket = IntProperty() + from_socket: IntProperty() + to_socket: IntProperty() def execute(self, context): nodes, links = get_nodes_links(context) @@ -3287,7 +3301,7 @@ class NWCallInputsMenu(Operator, NWBase): bl_idname = 'node.nw_call_inputs_menu' bl_label = 'Make Link' bl_options = {'REGISTER', 'UNDO'} - from_socket = IntProperty() + from_socket: IntProperty() def execute(self, context): nodes, links = get_nodes_links(context) @@ -3308,9 +3322,17 @@ class NWAddSequence(Operator, ImportHelper): bl_idname = 'node.nw_add_sequence' bl_label = 'Import Image Sequence' bl_options = {'REGISTER', 'UNDO'} - directory = StringProperty(subtype="DIR_PATH") - filename = StringProperty(subtype="FILE_NAME") - files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'}) + + directory: StringProperty( + subtype="DIR_PATH" + ) + filename: StringProperty( + subtype="FILE_NAME" + ) + files: CollectionProperty( + type=bpy.types.OperatorFileListElement, + options={'HIDDEN', 'SKIP_SAVE'} + ) def execute(self, context): nodes, links = get_nodes_links(context) @@ -3402,8 +3424,13 @@ class NWAddMultipleImages(Operator, ImportHelper): bl_idname = 'node.nw_add_multiple_images' bl_label = 'Open Selected Images' bl_options = {'REGISTER', 'UNDO'} - directory = StringProperty(subtype="DIR_PATH") - files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'}) + directory: StringProperty( + subtype="DIR_PATH" + ) + files: CollectionProperty( + type=bpy.types.OperatorFileListElement, + options={'HIDDEN', 'SKIP_SAVE'} + ) def execute(self, context): nodes, links = get_nodes_links(context) @@ -3450,8 +3477,8 @@ class NWViewerFocus(bpy.types.Operator): bl_idname = "node.nw_viewer_focus" bl_label = "Viewer Focus" - x = bpy.props.IntProperty() - y = bpy.props.IntProperty() + x: bpy.props.IntProperty() + y: bpy.props.IntProperty() @classmethod def poll(cls, context): @@ -3505,8 +3532,8 @@ class NWSaveViewer(bpy.types.Operator, ExportHelper): """Save the current viewer node to an image file""" bl_idname = "node.nw_save_viewer" bl_label = "Save This Image" - filepath = StringProperty(subtype="FILE_PATH") - filename_ext = EnumProperty( + filepath: StringProperty(subtype="FILE_PATH") + filename_ext: EnumProperty( name="Format", description="Choose the file format to save to", items=(('.bmp', "PNG", ""), @@ -3684,7 +3711,7 @@ def drawlayout(context, layout, mode='non-panel'): col.menu(NWSwitchNodeTypeMenu.bl_idname, text="Switch Node Type") col.separator() - if tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES': + if tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context): col = layout.column(align=True) col.operator(NWAddTextureSetup.bl_idname, text="Add Texture Setup", icon='NODE_SEL') col.operator(NWAddPrincipledSetup.bl_idname, text="Add Principled Setup", icon='NODE_SEL') @@ -3725,7 +3752,7 @@ def drawlayout(context, layout, mode='non-panel'): col.separator() col = layout.column(align=True) - col.operator(NWAlignNodes.bl_idname, icon='ALIGN') + col.operator(NWAlignNodes.bl_idname, icon='CENTER_ONLY') col.separator() col = layout.column(align=True) @@ -3737,14 +3764,14 @@ class NodeWranglerPanel(Panel, NWBase): bl_idname = "NODE_PT_nw_node_wrangler" bl_space_type = 'NODE_EDITOR' bl_label = "Node Wrangler" - bl_region_type = "TOOLS" + bl_region_type = "UI" bl_category = "Node Wrangler" - prepend = StringProperty( + prepend: StringProperty( name='prepend', ) - append = StringProperty() - remove = StringProperty() + append: StringProperty() + remove: StringProperty() def draw(self, context): self.layout.label(text="(Quick access: Ctrl+Space)") @@ -3769,7 +3796,7 @@ class NWMergeNodesMenu(Menu, NWBase): def draw(self, context): type = context.space_data.tree_type layout = self.layout - if type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES': + if type == 'ShaderNodeTree' and is_cycles_or_eevee(context): layout.menu(NWMergeShadersMenu.bl_idname, text="Use Shaders") layout.menu(NWMergeMixMenu.bl_idname, text="Use Mix Nodes") layout.menu(NWMergeMathMenu.bl_idname, text="Use Math Nodes") @@ -3993,7 +4020,7 @@ class NWVertColMenu(bpy.types.Menu): valid = False if nw_check(context): snode = context.space_data - valid = snode.tree_type == 'ShaderNodeTree' and context.scene.render.engine == 'CYCLES' + valid = snode.tree_type == 'ShaderNodeTree' and is_cycles_or_eevee(context) return valid def draw(self, context): @@ -4028,7 +4055,7 @@ class NWSwitchNodeTypeMenu(Menu, NWBase): layout = self.layout tree = context.space_data.node_tree if tree.type == 'SHADER': - if context.scene.render.engine == 'CYCLES': + if is_cycles_or_eevee(context): layout.menu(NWSwitchShadersInputSubmenu.bl_idname) layout.menu(NWSwitchShadersOutputSubmenu.bl_idname) layout.menu(NWSwitchShadersShaderSubmenu.bl_idname) @@ -4037,7 +4064,7 @@ class NWSwitchNodeTypeMenu(Menu, NWBase): layout.menu(NWSwitchShadersVectorSubmenu.bl_idname) layout.menu(NWSwitchShadersConverterSubmenu.bl_idname) layout.menu(NWSwitchShadersLayoutSubmenu.bl_idname) - if context.scene.render.engine != 'CYCLES': + else: layout.menu(NWSwitchMatInputSubmenu.bl_idname) layout.menu(NWSwitchMatOutputSubmenu.bl_idname) layout.menu(NWSwitchMatColorSubmenu.bl_idname) @@ -4645,7 +4672,7 @@ kmi_defs = ( # Reset Nodes (Back Space) (NWResetNodes.bl_idname, 'BACK_SPACE', 'PRESS', False, False, False, None, "Revert node back to default state, but keep connections"), # MENUS - ('wm.call_menu', 'SPACE', 'PRESS', True, False, False, (('name', NodeWranglerMenu.bl_idname),), "Node Wranger menu"), + ('wm.call_menu', 'SPACE', 'PRESS', True, True, False, (('name', NodeWranglerMenu.bl_idname),), "Node Wranger menu"), ('wm.call_menu', 'SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"), ('wm.call_menu', 'NUMPAD_SLASH', 'PRESS', False, False, False, (('name', NWAddReroutesMenu.bl_idname),), "Add Reroutes menu"), ('wm.call_menu', 'BACK_SLASH', 'PRESS', False, False, False, (('name', NWLinkActiveToSelectedMenu.bl_idname),), "Link active to selected (menu)"), @@ -4781,13 +4808,10 @@ def register(): # menu items bpy.types.NODE_MT_select.append(select_parent_children_buttons) bpy.types.NODE_MT_category_SH_NEW_INPUT.prepend(attr_nodes_menu_func) - bpy.types.NODE_PT_category_SH_NEW_INPUT.prepend(attr_nodes_menu_func) bpy.types.NODE_PT_backdrop.append(bgreset_menu_func) bpy.types.NODE_PT_active_node_generic.append(save_viewer_menu_func) bpy.types.NODE_MT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func) - bpy.types.NODE_PT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func) bpy.types.NODE_MT_category_CMP_INPUT.prepend(multipleimages_menu_func) - bpy.types.NODE_PT_category_CMP_INPUT.prepend(multipleimages_menu_func) bpy.types.NODE_PT_active_node_generic.prepend(reset_nodes_button) bpy.types.NODE_MT_node.prepend(reset_nodes_button) @@ -4809,13 +4833,10 @@ def unregister(): # menuitems bpy.types.NODE_MT_select.remove(select_parent_children_buttons) bpy.types.NODE_MT_category_SH_NEW_INPUT.remove(attr_nodes_menu_func) - bpy.types.NODE_PT_category_SH_NEW_INPUT.remove(attr_nodes_menu_func) bpy.types.NODE_PT_backdrop.remove(bgreset_menu_func) bpy.types.NODE_PT_active_node_generic.remove(save_viewer_menu_func) bpy.types.NODE_MT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func) - bpy.types.NODE_PT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func) bpy.types.NODE_MT_category_CMP_INPUT.remove(multipleimages_menu_func) - bpy.types.NODE_PT_category_CMP_INPUT.remove(multipleimages_menu_func) bpy.types.NODE_PT_active_node_generic.remove(reset_nodes_button) bpy.types.NODE_MT_node.remove(reset_nodes_button) diff --git a/object_boolean_tools.py b/object_boolean_tools.py index 4c7e1bc1..702fbb8e 100644 --- a/object_boolean_tools.py +++ b/object_boolean_tools.py @@ -159,9 +159,9 @@ def Operation(context, _operation): obj.modifiers.remove(mod) """ if useWire: - selObj.draw_type = "WIRE" + selObj.display_type = "WIRE" else: - selObj.draw_type = "BOUNDS" + selObj.display_type = "BOUNDS" cyclesVis.camera = False cyclesVis.diffuse = False @@ -169,7 +169,7 @@ def Operation(context, _operation): cyclesVis.shadow = False cyclesVis.transmission = False if _operation == "SLICE": - # copies dupli_group property(empty), but group property is empty (users_group = None) + # copies instance_collection property(empty), but group property is empty (users_group = None) clone = context.active_object.copy() # clone.select = True context.scene.objects.link(clone) @@ -200,7 +200,7 @@ def Remove(context, thisObj_name, Prop): # if it's the brush object if obj.name == _thisObj_name: cyclesVis = obj.cycles_visibility - obj.draw_type = "TEXTURED" + obj.display_type = "TEXTURED" del obj["BoolToolBrush"] del obj["BoolTool_FTransform"] cyclesVis.camera = True @@ -229,7 +229,7 @@ def Remove(context, thisObj_name, Prop): if (actObj.name in mod.name): Canvas.modifiers.remove(mod) cyclesVis = actObj.cycles_visibility - actObj.draw_type = "TEXTURED" + actObj.display_type = "TEXTURED" del actObj["BoolToolBrush"] del actObj["BoolTool_FTransform"] cyclesVis.camera = True @@ -509,9 +509,9 @@ class BTool_FastTransform(Operator): if isBrush(actObj) and actObj["BoolTool_FTransform"] == "True": EnableThisBrush(bpy.context, "False") if useWire: - actObj.draw_type = "WIRE" + actObj.display_type = "WIRE" else: - actObj.draw_type = "BOUNDS" + actObj.display_type = "BOUNDS" if self.operator == "Translate": bpy.ops.transform.translate('INVOKE_DEFAULT') @@ -523,13 +523,13 @@ class BTool_FastTransform(Operator): if event.type == 'LEFTMOUSE': if isBrush(actObj): EnableThisBrush(bpy.context, "True") - actObj.draw_type = "WIRE" + actObj.display_type = "WIRE" return {'FINISHED'} if event.type in {'RIGHTMOUSE', 'ESC'}: if isBrush(actObj): EnableThisBrush(bpy.context, "True") - actObj.draw_type = "WIRE" + actObj.display_type = "WIRE" return {'CANCELLED'} return {'RUNNING_MODAL'} @@ -914,18 +914,18 @@ class VIEW3D_MT_booltool_menu(Menu): layout = self.layout layout.label("Auto Boolean:") - layout.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text='Difference', icon="ROTACTIVE") - layout.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text='Union', icon="ROTATECOLLECTION") - layout.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text='Intersect', icon="ROTATECENTER") - layout.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text='Slice', icon="ROTATECENTER") - layout.operator(OBJECT_OT_BoolTool_Auto_Subtract.bl_idname, text='Subtract', icon="ROTACTIVE") + layout.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text='Difference', icon='PIVOT_ACTIVE') + layout.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text='Union', icon='PIVOT_INDIVIDUAL') + layout.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text='Intersect', icon='PIVOT_MEDIAN') + layout.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text='Slice', icon='PIVOT_MEDIAN') + layout.operator(OBJECT_OT_BoolTool_Auto_Subtract.bl_idname, text='Subtract', icon='PIVOT_ACTIVE') layout.separator() layout.label("Brush Boolean:") - layout.operator(BTool_Diff.bl_idname, icon="ROTACTIVE") - layout.operator(BTool_Union.bl_idname, icon="ROTATECOLLECTION") - layout.operator(BTool_Inters.bl_idname, icon="ROTATECENTER") - layout.operator(BTool_Slice.bl_idname, icon="ROTATECENTER") + layout.operator(BTool_Diff.bl_idname, icon='PIVOT_ACTIVE') + layout.operator(BTool_Union.bl_idname, icon='PIVOT_INDIVIDUAL') + layout.operator(BTool_Inters.bl_idname, icon='PIVOT_MEDIAN') + layout.operator(BTool_Slice.bl_idname, icon='PIVOT_MEDIAN') if (isCanvas(context.active_object)): layout.separator() @@ -977,16 +977,16 @@ class VIEW3D_PT_booltool_tools(Panel): col.enabled = obs_len > 1 col.label("Auto Boolean:", icon="MODIFIER") col.separator() - col.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text='Difference', icon="ROTACTIVE") - col.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text='Union', icon="ROTATECOLLECTION") - col.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text='Intersect', icon="ROTATECENTER") + col.operator(OBJECT_OT_BoolTool_Auto_Difference.bl_idname, text='Difference', icon='PIVOT_ACTIVE') + col.operator(OBJECT_OT_BoolTool_Auto_Union.bl_idname, text='Union', icon='PIVOT_INDIVIDUAL') + col.operator(OBJECT_OT_BoolTool_Auto_Intersect.bl_idname, text='Intersect', icon='PIVOT_MEDIAN') main.separator() col = main.column(align=True) col.enabled = obs_len == 2 - col.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text='Slice', icon="ROTATECENTER") - col.operator(OBJECT_OT_BoolTool_Auto_Subtract.bl_idname, text='Subtract', icon="ROTACTIVE") + col.operator(OBJECT_OT_BoolTool_Auto_Slice.bl_idname, text='Slice', icon='PIVOT_MEDIAN') + col.operator(OBJECT_OT_BoolTool_Auto_Subtract.bl_idname, text='Subtract', icon='PIVOT_ACTIVE') main.separator() @@ -994,10 +994,10 @@ class VIEW3D_PT_booltool_tools(Panel): col.enabled = obs_len > 1 col.label("Brush Boolean:", icon="MODIFIER") col.separator() - col.operator(BTool_Diff.bl_idname, text="Difference", icon="ROTACTIVE") - col.operator(BTool_Union.bl_idname, text="Union", icon="ROTATECOLLECTION") - col.operator(BTool_Inters.bl_idname, text="Intersect", icon="ROTATECENTER") - col.operator(BTool_Slice.bl_idname, text="Slice", icon="ROTATECENTER") + col.operator(BTool_Diff.bl_idname, text="Difference", icon='PIVOT_ACTIVE') + col.operator(BTool_Union.bl_idname, text="Union", icon='PIVOT_INDIVIDUAL') + col.operator(BTool_Inters.bl_idname, text="Intersect", icon='PIVOT_MEDIAN') + col.operator(BTool_Slice.bl_idname, text="Slice", icon='PIVOT_MEDIAN') main.separator() diff --git a/object_cloud_gen.py b/object_cloud_gen.py index 008763fc..6601b8f1 100644 --- a/object_cloud_gen.py +++ b/object_cloud_gen.py @@ -563,7 +563,7 @@ class GenerateCloud(Operator): # Select all of the left over boxes so people can immediately # press generate again if they want for eachMember in definitionObjects: - eachMember.draw_type = 'SOLID' + eachMember.display_type = 'SOLID' eachMember.select = True eachMember.hide_render = False @@ -576,7 +576,7 @@ class GenerateCloud(Operator): # Create a new object cloudPnts cloudPnts = addNewObject(scene, "CloudPoints", bounds) cloudPnts["CloudMember"] = "CreatedObj" - cloudPnts.draw_type = 'WIRE' + cloudPnts.display_type = 'WIRE' cloudPnts.hide_render = True makeParent(bounds, cloudPnts, scene) @@ -606,7 +606,7 @@ class GenerateCloud(Operator): selectedObjects[0] ) - bounds.draw_type = 'BOUNDS' + bounds.display_type = 'BOUNDS' bounds.hide_render = False # Just add a Definition Property designating this @@ -630,7 +630,7 @@ class GenerateCloud(Operator): for selObj in selectedObjects: selObj["CloudMember"] = "DefinitionObj" selObj.name = "DefinitionObj" - selObj.draw_type = 'WIRE' + selObj.display_type = 'WIRE' selObj.hide_render = True selObj.hide = True makeParent(bounds, selObj, scene) @@ -638,7 +638,7 @@ class GenerateCloud(Operator): # Do the same to the 1. object since it is no longer in list. firstObject["CloudMember"] = "DefinitionObj" firstObject.name = "DefinitionObj" - firstObject.draw_type = 'WIRE' + firstObject.display_type = 'WIRE' firstObject.hide_render = True makeParent(bounds, firstObject, scene) @@ -646,7 +646,7 @@ class GenerateCloud(Operator): # Create a new object cloud. cloud = addNewObject(scene, "CloudMesh", bounds) cloud["CloudMember"] = "CreatedObj" - cloud.draw_type = 'WIRE' + cloud.display_type = 'WIRE' cloud.hide_render = True makeParent(bounds, cloud, scene) @@ -677,7 +677,7 @@ class GenerateCloud(Operator): cloudParticles.settings.frame_end = 0 cloudParticles.settings.emit_from = 'VOLUME' cloudParticles.settings.lifetime = scene.frame_end - cloudParticles.settings.draw_method = 'DOT' + cloudParticles.settings.display_method = 'DOT' cloudParticles.settings.render_type = 'NONE' cloudParticles.settings.distribution = 'RAND' cloudParticles.settings.physics_type = 'NEWTON' @@ -836,7 +836,7 @@ class GenerateCloud(Operator): # Create a new object cloudPnts cloudPnts = addNewObject(scene, "CloudPoints", bounds) cloudPnts["CloudMember"] = "CreatedObj" - cloudPnts.draw_type = 'WIRE' + cloudPnts.display_type = 'WIRE' cloudPnts.hide_render = True makeParent(bounds, cloudPnts, scene) diff --git a/object_edit_linked.py b/object_edit_linked.py index 85359a51..0077fb50 100644 --- a/object_edit_linked.py +++ b/object_edit_linked.py @@ -19,21 +19,24 @@ bl_info = { "name": "Edit Linked Library", - "author": "Jason van Gumster (Fweeb), Bassam Kurdali, Pablo Vazquez", - "version": (0, 8, 1), - "blender": (2, 74, 0), - "location": "View3D > Toolshelf > Edit Linked Library", + "author": "Jason van Gumster (Fweeb), Bassam Kurdali, Pablo Vazquez, Rainer Trummer", + "version": (0, 9, 1), + "blender": (2, 80, 0), + "location": "File > External Data > Edit Linked Library", "description": "Allows editing of objects linked from a .blend library.", "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/Object/Edit_Linked_Library", "category": "Object", } - import bpy -from bpy.app.handlers import persistent +import logging import os +from bpy.app.handlers import persistent + +logger = logging.getLogger('object_edit_linked') + settings = { "original_file": "", "linked_file": "", @@ -42,15 +45,15 @@ settings = { @persistent -def linked_file_check(context): +def linked_file_check(context: bpy.context): if settings["linked_file"] != "": if os.path.samefile(settings["linked_file"], bpy.data.filepath): - print("Editing a linked library.") + logger.info("Editing a linked library.") bpy.ops.object.select_all(action='DESELECT') for ob_name in settings["linked_objects"]: - bpy.data.objects[ob_name].select = True # XXX Assumes selected object is in the active scene + bpy.data.objects[ob_name].select_set(True) # XXX Assumes selected object is in the active scene if len(settings["linked_objects"]) == 1: - bpy.context.scene.objects.active = bpy.data.objects[settings["linked_objects"][0]] + context.view_layer.objects.active = bpy.data.objects[settings["linked_objects"][0]] else: # For some reason, the linked editing session ended # (failed to find a file or opened a different file @@ -59,37 +62,35 @@ def linked_file_check(context): settings["linked_file"] = "" -class EditLinked(bpy.types.Operator): +class OBJECT_OT_EditLinked(bpy.types.Operator): """Edit Linked Library""" bl_idname = "object.edit_linked" bl_label = "Edit Linked Library" - use_autosave = bpy.props.BoolProperty( + use_autosave: bpy.props.BoolProperty( name="Autosave", description="Save the current file before opening the linked library", default=True) - use_instance = bpy.props.BoolProperty( + use_instance: bpy.props.BoolProperty( name="New Blender Instance", description="Open in a new Blender instance", default=False) @classmethod - def poll(cls, context): + def poll(cls, context: bpy.context): return settings["original_file"] == "" and context.active_object is not None and ( - (context.active_object.dupli_group and - context.active_object.dupli_group.library is not None) or + (context.active_object.instance_collection and + context.active_object.instance_collection.library is not None) or (context.active_object.proxy and - context.active_object.proxy.library is not None) or - context.active_object.library is not None) - #return context.active_object is not None + context.active_object.proxy.library is not None) or + context.active_object.library is not None) - def execute(self, context): - #print(bpy.context.active_object.library) + def execute(self, context: bpy.context): target = context.active_object - if target.dupli_group and target.dupli_group.library: - targetpath = target.dupli_group.library.filepath - settings["linked_objects"].extend({ob.name for ob in target.dupli_group.objects}) + if target.instance_collection and target.instance_collection.library: + targetpath = target.instance_collection.library.filepath + settings["linked_objects"].extend({ob.name for ob in target.instance_collection.objects}) elif target.library: targetpath = target.library.filepath settings["linked_objects"].append(target.name) @@ -99,7 +100,7 @@ class EditLinked(bpy.types.Operator): settings["linked_objects"].append(target.name) if targetpath: - print(target.name + " is linked to " + targetpath) + logger.debug(target.name + " is linked to " + targetpath) if self.use_autosave: if not bpy.data.filepath: @@ -116,35 +117,35 @@ class EditLinked(bpy.types.Operator): try: subprocess.Popen([bpy.app.binary_path, settings["linked_file"]]) except: - print("Error on the new Blender instance") + logger.error("Error on the new Blender instance") import traceback - traceback.print_exc() + logger.error(traceback.print_exc()) else: bpy.ops.wm.open_mainfile(filepath=settings["linked_file"]) - print("Opened linked file!") + logger.info("Opened linked file!") else: self.report({'WARNING'}, target.name + " is not linked") - print(target.name + " is not linked") + logger.warning(target.name + " is not linked") return {'FINISHED'} -class ReturnToOriginal(bpy.types.Operator): +class WM_OT_ReturnToOriginal(bpy.types.Operator): """Load the original file""" bl_idname = "wm.return_to_original" bl_label = "Return to Original File" - use_autosave = bpy.props.BoolProperty( + use_autosave: bpy.props.BoolProperty( name="Autosave", description="Save the current file before opening original file", default=True) @classmethod - def poll(cls, context): + def poll(cls, context: bpy.context): return (settings["original_file"] != "") - def execute(self, context): + def execute(self, context: bpy.context): if self.use_autosave: bpy.ops.wm.save_mainfile() @@ -152,25 +153,29 @@ class ReturnToOriginal(bpy.types.Operator): settings["original_file"] = "" settings["linked_objects"] = [] - print("Back to the original!") + logger.info("Back to the original!") return {'FINISHED'} -# UI -# TODO:Add operators to the File menu? -# Hide the entire panel for non-linked objects? -class PanelLinkedEdit(bpy.types.Panel): +class VIEW3D_PT_PanelLinkedEdit(bpy.types.Panel): bl_label = "Edit Linked Library" bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_category = "Relations" - bl_context = "objectmode" + bl_region_type = 'UI' + bl_category = "View" + bl_context = 'objectmode' @classmethod - def poll(cls, context): + def poll(cls, context: bpy.context): return (context.active_object is not None) or (settings["original_file"] != "") - def draw(self, context): + def draw_common(self, scene, layout, props): + props.use_autosave = scene.use_autosave + props.use_instance = scene.use_instance + + layout.prop(scene, "use_autosave") + layout.prop(scene, "use_instance") + + def draw(self, context: bpy.context): layout = self.layout scene = context.scene icon = "OUTLINER_DATA_" + context.active_object.type @@ -180,11 +185,11 @@ class PanelLinkedEdit(bpy.types.Panel): if context.active_object.proxy: target = context.active_object.proxy else: - target = context.active_object.dupli_group + target = context.active_object.instance_collection if settings["original_file"] == "" and ( (target and - target.library is not None) or + target.library is not None) or context.active_object.library is not None): if (target is not None): @@ -193,18 +198,15 @@ class PanelLinkedEdit(bpy.types.Panel): else: props = layout.operator("object.edit_linked", icon="LINK_BLEND", text="Edit Library: %s" % context.active_object.name) - props.use_autosave = scene.use_autosave - props.use_instance = scene.use_instance - layout.prop(scene, "use_autosave") - layout.prop(scene, "use_instance") + self.draw_common(scene, layout, props) if (target is not None): layout.label(text="Path: %s" % - target.library.filepath) + target.library.filepath) else: layout.label(text="Path: %s" % - context.active_object.library.filepath) + context.active_object.library.filepath) elif settings["original_file"] != "": @@ -215,19 +217,17 @@ class PanelLinkedEdit(bpy.types.Panel): layout.separator() - #XXX - This is for nested linked assets... but it only works - # when launching a new Blender instance. Nested links don't - # currently work when using a single instance of Blender. + # XXX - This is for nested linked assets... but it only works + # when launching a new Blender instance. Nested links don't + # currently work when using a single instance of Blender. props = layout.operator("object.edit_linked", - text="Edit Library: %s" % context.active_object.dupli_group.name, + text="Edit Library: %s" % context.active_object.instance_collection.name, icon="LINK_BLEND") - props.use_autosave = scene.use_autosave - props.use_instance = scene.use_instance - layout.prop(scene, "use_autosave") - layout.prop(scene, "use_instance") + + self.draw_common(scene, layout, props) layout.label(text="Path: %s" % - context.active_object.dupli_group.library.filepath) + context.active_object.instance_collection.library.filepath) else: props = layout.operator("wm.return_to_original", icon="LOOP_BACK") @@ -237,31 +237,50 @@ class PanelLinkedEdit(bpy.types.Panel): else: layout.label(text="%s is not linked" % context.active_object.name, - icon=icon) + icon=icon) + + +class TOPBAR_MT_edit_linked_submenu(bpy.types.Menu): + bl_label = 'Edit Linked Library' + bl_idname = 'view3d.TOPBAR_MT_edit_linked_submenu' + + def draw(self, context): + self.layout.separator() + self.layout.operator(OBJECT_OT_EditLinked.bl_idname) + self.layout.operator(WM_OT_ReturnToOriginal.bl_idname) addon_keymaps = [] +classes = ( + OBJECT_OT_EditLinked, + WM_OT_ReturnToOriginal, + VIEW3D_PT_PanelLinkedEdit, + TOPBAR_MT_edit_linked_submenu + ) def register(): - bpy.app.handlers.load_post.append(linked_file_check) - bpy.utils.register_class(EditLinked) - bpy.utils.register_class(ReturnToOriginal) - bpy.utils.register_class(PanelLinkedEdit) + bpy.app.handlers.load_post.append(linked_file_check) + + for c in classes: + bpy.utils.register_class(c) - # Is there a better place to store this properties? bpy.types.Scene.use_autosave = bpy.props.BoolProperty( name="Autosave", description="Save the current file before opening a linked file", default=True) + bpy.types.Scene.use_instance = bpy.props.BoolProperty( name="New Blender Instance", description="Open in a new Blender instance", default=False) + # add the function to the file menu + bpy.types.TOPBAR_MT_file_external_data.append(TOPBAR_MT_edit_linked_submenu.draw) + # Keymapping (deactivated by default; activated when a library object is selected) - kc = bpy.context.window_manager.keyconfigs.addon - if kc: # don't register keymaps from command line + kc = bpy.context.window_manager.keyconfigs.addon + if kc: # don't register keymaps from command line km = kc.keymaps.new(name="3D View", space_type='VIEW_3D') kmi = km.keymap_items.new("object.edit_linked", 'NUMPAD_SLASH', 'PRESS', shift=True) kmi.active = True @@ -272,10 +291,9 @@ def register(): def unregister(): - bpy.utils.unregister_class(EditLinked) - bpy.utils.unregister_class(ReturnToOriginal) - bpy.utils.unregister_class(PanelLinkedEdit) - bpy.app.handlers.load_post.remove(linked_file_check) + + bpy.app.handlers.load_post.remove(linked_file_check) + bpy.types.TOPBAR_MT_file_external_data.remove(TOPBAR_MT_edit_linked_submenu) del bpy.types.Scene.use_autosave del bpy.types.Scene.use_instance @@ -285,6 +303,9 @@ def unregister(): km.keymap_items.remove(kmi) addon_keymaps.clear() + for c in reversed(classes): + bpy.utils.unregister_class(c) + if __name__ == "__main__": register() diff --git a/object_fracture/__init__.py b/object_fracture/__init__.py index 0ac9f403..5dad8801 100644 --- a/object_fracture/__init__.py +++ b/object_fracture/__init__.py @@ -40,8 +40,8 @@ else: import bpy -class INFO_MT_add_fracture_objects(bpy.types.Menu): - bl_idname = "INFO_MT_add_fracture_objects" +class VIEW3D_MT_add_fracture_objects(bpy.types.Menu): + bl_idname = "VIEW3D_MT_add_fracture_objects" bl_label = "Fracture Helper Objects" def draw(self, context): @@ -57,21 +57,21 @@ class INFO_MT_add_fracture_objects(bpy.types.Menu): def menu_func(self, context): - self.layout.menu("INFO_MT_add_fracture_objects") + self.layout.menu("VIEW3D_MT_add_fracture_objects") def register(): bpy.utils.register_module(__name__) # Add the "add fracture objects" menu to the "Add" menu - bpy.types.INFO_MT_add.append(menu_func) + bpy.types.VIEW3D_MT_add.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) # Remove "add fracture objects" menu from the "Add" menu. - bpy.types.INFO_MT_add.remove(menu_func) + bpy.types.VIEW3D_MT_add.remove(menu_func) if __name__ == "__main__": diff --git a/object_fracture/fracture_ops.py b/object_fracture/fracture_ops.py index f5687e3a..d2f88159 100644 --- a/object_fracture/fracture_ops.py +++ b/object_fracture/fracture_ops.py @@ -317,7 +317,7 @@ def fracture_group(context, group): and (len(ob.users_group) == 0 or ob.users_group[0].name != group)): tobesplit.append(ob) - cutters = bpy.data.groups[group].objects + cutters = bpy.data.collections[group].objects # @todo This can be optimized. # Avoid booleans on obs where bbox doesn't intersect. @@ -407,7 +407,7 @@ class FractureGroup(bpy.types.Operator): description="Specify the group used for fracturing") # e = [] -# for i, g in enumerate(bpy.data.groups): +# for i, g in enumerate(bpy.data.collections): # e.append((g.name, g.name, '')) # group = EnumProperty(name='Group (hit F8 to refresh list)', # items=e, @@ -431,7 +431,7 @@ class FractureGroup(bpy.types.Operator): def draw(self, context): layout = self.layout layout.prop(self, "exe") - layout.prop_search(self, "group", bpy.data, "groups") + layout.prop_search(self, "group", bpy.data, "collections") ##################################################################### # Import Functions diff --git a/object_fracture_cell/__init__.py b/object_fracture_cell/__init__.py index ad4cdf39..f845ccf1 100644 --- a/object_fracture_cell/__init__.py +++ b/object_fracture_cell/__init__.py @@ -75,8 +75,8 @@ def main_object(scene, obj, level, **kw): obj.select = False if kw_copy["use_debug_redraw"]: - obj_draw_type_prev = obj.draw_type - obj.draw_type = 'WIRE' + obj_display_type_prev = obj.display_type + obj.display_type = 'WIRE' objects = fracture_cell_setup.cell_fracture_objects(scene, obj, **kw_copy) objects = fracture_cell_setup.cell_fracture_boolean(scene, obj, objects, @@ -163,16 +163,16 @@ def main_object(scene, obj, level, **kw): # group if group_name: - group = bpy.data.groups.get(group_name) + group = bpy.data.collections.get(group_name) if group is None: - group = bpy.data.groups.new(group_name) + group = bpy.data.collections.new(group_name) group_objects = group.objects[:] for obj_cell in objects: if obj_cell not in group_objects: group.objects.link(obj_cell) if kw_copy["use_debug_redraw"]: - obj.draw_type = obj_draw_type_prev + obj.display_type = obj_display_type_prev # testing only! # obj.hide = True diff --git a/object_grease_scatter.py b/object_grease_scatter.py deleted file mode 100644 index 105b40ea..00000000 --- a/object_grease_scatter.py +++ /dev/null @@ -1,401 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# <pep8-80 compliant> - -# Script copyright (C) Campbell Barton - -bl_info = { - "name": "Grease Scatter Objects", - "author": "Campbell Barton", - "version": (0, 1), - "blender": (2, 58, 0), - "location": "3D View, Add Mesh", - "description": "Scatter a group of objects onto the active mesh using " - "the grease pencil lines", - "warning": "", - "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Object/Grease_Scatter", - "support": 'OFFICIAL', - "category": "Object", -} - -from mathutils import Vector, Matrix, Quaternion -from random import uniform, shuffle -import bpy - - -def _main(self, - obj, - group, - DENSITY=1.0, - SCALE=0.6, - RAND_LOC=0.8, - RAND_ALIGN=0.75, - ): - - from math import radians, pi - - # OFS = 0.2 - SEEK = 2.0 # distance for ray to seek - BAD_NORMAL = Vector((0.0, 0.0, -1.0)) - WALL_LIMIT = radians(45.0) - - mats = (Matrix.Rotation(radians(-45), 3, 'X'), - Matrix.Rotation(radians(+45), 3, 'X'), - Matrix.Rotation(radians(-45), 3, 'Y'), - Matrix.Rotation(radians(+45), 3, 'Y'), - Matrix.Rotation(radians(-45), 3, 'Z'), - Matrix.Rotation(radians(+45), 3, 'Z'), - ) - - Z_UP = Vector((0.0, 0.0, 1.0)) - Y_UP = Vector((0.0, 1.0, 0.0)) - - if not group: - self.report({'WARNING'}, "Group '%s' not found" % obj.name) - return - - def debug_edge(v1, v2): - mesh = bpy.data.meshes.new("Retopo") - mesh.from_pydata([v1, v2], [(0.0, 1.0)], []) - - scene = bpy.context.scene - mesh.update() - obj_new = bpy.data.objects.new("Torus", mesh) - scene.objects.link(obj_new) - - ray = obj.ray_cast - closest_point_on_mesh = obj.closest_point_on_mesh - - obj_mat = obj.matrix_world.copy() - obj_mat_inv = obj_mat.inverted() - # obj_quat = obj_mat.to_quaternion() - # obj_quat_inv = obj_mat_inv.to_quaternion() - - DEBUG = False - - def fix_point(p): - ok, hit, no, ind = closest_point_on_mesh(obj_mat_inv * p) - if ok: - if DEBUG: - return [p, no, None] - else: - # print("good", hit, no) - return [hit, no, None] - - # worry! - print("bad!", p, BAD_NORMAL) - - return [p, BAD_NORMAL, None] - - def get_points(stroke): - return [fix_point(point.co) for point in stroke.points] - - def get_splines(gp): - if gp.layers.active: - frame = gp.layers.active.active_frame - return [get_points(stroke) for stroke in frame.strokes] - else: - return [] - - def main(): - scene = bpy.context.scene - obj = bpy.context.object - - gp = None - - if obj: - gp = obj.grease_pencil - - if not gp: - gp = scene.grease_pencil - - if not gp: - self.report({'WARNING'}, "No grease pencil layer found") - return - - splines = get_splines(gp) - - for s in splines: - for pt in s: - p = pt[0] - n = pt[1] - # print(p, n) - if n is BAD_NORMAL: - continue - - # # dont self intersect - best_nor = None - #best_hit = None - best_dist = 10000000.0 - pofs = p + n * 0.01 - - n_seek = n * SEEK - m_alt_1 = Matrix.Rotation(radians(22.5), 3, n) - m_alt_2 = Matrix.Rotation(radians(-22.5), 3, n) - for _m in mats: - for m in (_m, m_alt_1 * _m, m_alt_2 * _m): - pdir = m * n_seek - ok, hit, nor, ind = ray(pofs, pdir, best_dist) - if ok: - best_dist = (pofs - hit).length - best_nor = nor - # best_hit = hit - - if best_nor: - pt[1].length = best_dist - best_nor.negate() - pt[2] = best_nor - - #scene.cursor_location[:] = best_hitnyway - # bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', - # iterations=1) - # debug_edge(p, best_hit) - # p[:] = best_hit - - # Now we need to do scattering. - # first corners - hits = [] - nors = [] - oris = [] - for s in splines: - # point, normal, n_other the closest hit normal - for p, n, n_other in s: - if n is BAD_NORMAL: - continue - if n_other: - # cast vectors twice as long as the distance - # needed just in case. - n_down = (n * -SEEK) - l = n_down.length - n_other.length = l - - vantage = p + n - if DEBUG: - p[:] = vantage - - # We should cast rays between n_down and n_other - #for f in (0.0, 0.2, 0.4, 0.6, 0.8, 1.0): - TOT = int(10 * DENSITY) - #for i in list(range(TOT)): - for i in list(range(TOT))[int(TOT / 1.5):]: # second half - f = i / (TOT - 1) - - # focus on the center - ''' - f -= 0.5 - f = f*f - f += 0.5 - ''' - - ntmp = f * n_down + (1.0 - f) * n_other - # randomize - ntmp.x += uniform(-l, l) * RAND_LOC - ntmp.y += uniform(-l, l) * RAND_LOC - ntmp.z += uniform(-l, l) * RAND_LOC - - ok, hit, hit_no, ind = ray(vantage, ntmp, ntmp.length) - # print(hit, hit_no) - if ok: - if hit_no.angle(Z_UP) < WALL_LIMIT: - hits.append(hit) - nors.append(hit_no) - oris.append(n_other.cross(hit_no)) - #oris.append(n_other) - - if 0: - mesh = bpy.data.meshes.new("ScatterDupliFace") - mesh.from_pydata(hits, [], []) - - scene = bpy.context.scene - mesh.update() - obj_new = bpy.data.objects.new("ScatterPar", mesh) - scene.objects.link(obj_new) - obj_new.layers[:] = obj.layers - - # Now setup dupli-faces - obj_new.dupli_type = 'VERTS' - ob_child = bpy.data.objects["trash"] - ob_child.location = obj_new.location - ob_child.parent = obj_new - else: - - def apply_faces(triples): - # first randomize the faces - shuffle(triples) - - obs = group.objects[:] - tot = len(obs) - tot_div = int(len(triples) / tot) - - for inst_ob in obs: - triple_sub = triples[0:tot_div] - triples[0:tot_div] = [] - - vv = [tuple(v) for f in triple_sub for v in f] - - mesh = bpy.data.meshes.new("ScatterDupliFace") - mesh.from_pydata(vv, [], [(i * 3, i * 3 + 1, i * 3 + 2) - for i in range(len(triple_sub))]) - - scene = bpy.context.scene - mesh.update() - obj_new = bpy.data.objects.new("ScatterPar", mesh) - - scene.objects.link(obj_new) - obj_new.layers[:] = obj.layers - - # Now setup dupli-faces - obj_new.dupli_type = 'FACES' - obj_new.use_dupli_faces_scale = True - obj_new.dupli_faces_scale = 100.0 - - inst_ob.location = 0.0, 0.0, 0.0 - inst_ob.parent = obj_new - - # align the object with worldspace - obj_new.matrix_world = obj_mat - - # BGE settings for testing - ''' - inst_ob.game.physics_type = 'RIGID_BODY' - inst_ob.game.use_collision_bounds = True - inst_ob.game.collision_bounds = 'TRIANGLE_MESH' - inst_ob.game.collision_margin = 0.1 - obj_new.select = True - ''' - - # build faces from vert/normals - tri = (Vector((0.0, 0.0, 0.01)), - Vector((0.0, 0.0, 0.0)), - Vector((0.0, 0.01, 0.01))) - - coords = [] - # face_ind = [] - for i in range(len(hits)): - co = hits[i] - no = nors[i] - ori = oris[i] - quat = no.to_track_quat('X', 'Z') - - # make 2 angles and blend - angle = uniform(-pi, pi) - angle_aligned = -(ori.angle(quat * Y_UP, pi)) - - quat = Quaternion(no, - (angle * (1.0 - RAND_ALIGN)) + - (angle_aligned * RAND_ALIGN) - ).cross(quat) - - f = uniform(0.1, 1.2) * SCALE - - coords.append([co + (quat * (tri[0] * f)), - co + (quat * (tri[1] * f)), - co + (quat * (tri[2] * f)), - ]) - - apply_faces(coords) - - main() - - -from bpy.props import FloatProperty, StringProperty - - -class Scatter(bpy.types.Operator): - """""" - bl_idname = "object.scatter" - bl_label = "Grease Pencil Scatter" - - density = FloatProperty( - name="Density", - description="Multiplier for the density of items", - default=1.0, min=0.01, max=10.0, - ) - scale = FloatProperty( - name="Scale", - description="Size multiplier for duplifaces", - default=1.0, min=0.01, max=10.0, - ) - rand_align = FloatProperty( - name="Random Align", - description="Randomize alignment with the walls", - default=0.75, min=0.0, max=1.0, - ) - rand_loc = FloatProperty( - name="Random Loc", - description="Randomize placement", - default=0.75, min=0.0, max=1.0, - ) - # XXX, should not be a string - TODO, add a way for scritps to select ID's - group = StringProperty( - name="Group", - description=("Group name to use for object placement, " - "defaults to object name when that matches a group")) - - def execute(self, context): - obj = bpy.context.object - group = bpy.data.groups.get(self.group) - - if not group: - self.report({'ERROR'}, "Group %r not found" % self.group) - return {'CANCELLED'} - - _main(self, - obj, - group, - DENSITY=self.density, - SCALE=self.scale, - RAND_LOC=self.rand_loc, - RAND_ALIGN=self.rand_align, - ) - return {'FINISHED'} - - def check(self, context): - if self.group not in bpy.data.groups: - self.group = "" - return True - return False - - def invoke(self, context, event): - - # useful to initialize, take a guess - if not self.group and context.object.name in bpy.data.groups: - self.group = context.object.name - - wm = context.window_manager - wm.invoke_props_dialog(self, width=180) - return {'RUNNING_MODAL'} - - -def menu_func(self, context): - self.layout.operator(Scatter.bl_idname, icon='AUTO') - - -def register(): - bpy.utils.register_class(Scatter) - bpy.types.INFO_MT_mesh_add.append(menu_func) - - -def unregister(): - bpy.utils.unregister_class(Scatter) - bpy.types.INFO_MT_mesh_add.remove(menu_func) - -#if __name__ == "__main__": -# _main() diff --git a/object_print3d_utils/__init__.py b/object_print3d_utils/__init__.py index 5ae1d3d6..6bf2a8a7 100644 --- a/object_print3d_utils/__init__.py +++ b/object_print3d_utils/__init__.py @@ -21,7 +21,7 @@ bl_info = { "name": "3D Print Toolbox", "author": "Campbell Barton", - "blender": (2, 79, 0), + "blender": (2, 80, 0), "location": "3D View > Toolbox", "description": "Utilities for 3D printing", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" @@ -59,101 +59,66 @@ else: class Print3D_Scene_Props(PropertyGroup): - export_format = EnumProperty( - name="Format", - description="Format type to export to", - items=(('STL', "STL", ""), - ('PLY', "PLY", ""), - ('WRL', "VRML2", ""), - ('X3D', "X3D", ""), - ('OBJ', "OBJ", "")), - default='STL', - ) - use_export_texture = BoolProperty( - name="Copy Textures", - description="Copy textures on export to the output path", - default=False, - ) - use_apply_scale = BoolProperty( - name="Apply Scale", - description="Apply scene scale setting on export", - default=False, - ) - export_path = StringProperty( - name="Export Directory", - description="Path to directory where the files are created", - default="//", maxlen=1024, subtype="DIR_PATH", - ) - thickness_min = FloatProperty( - name="Thickness", - description="Minimum thickness", - subtype='DISTANCE', - default=0.001, # 1mm - min=0.0, max=10.0, - ) - threshold_zero = FloatProperty( - name="Threshold", - description="Limit for checking zero area/length", - default=0.0001, - precision=5, - min=0.0, max=0.2, - ) - angle_distort = FloatProperty( - name="Angle", - description="Limit for checking distorted faces", - subtype='ANGLE', - default=math.radians(45.0), - min=0.0, max=math.radians(180.0), - ) - angle_sharp = FloatProperty( - name="Angle", - subtype='ANGLE', - default=math.radians(160.0), - min=0.0, max=math.radians(180.0), - ) - angle_overhang = FloatProperty( - name="Angle", - subtype='ANGLE', - default=math.radians(45.0), - min=0.0, max=math.radians(90.0), - ) - - -# Update panel category name -panels = ( - ui.VIEW3D_PT_Print3D_Object, - ui.VIEW3D_PT_Print3D_Mesh, + export_format: EnumProperty( + name="Format", + description="Format type to export to", + items=( + ('STL', "STL", ""), + ('PLY', "PLY", ""), + ('WRL', "VRML2", ""), + ('X3D', "X3D", ""), + ('OBJ', "OBJ", ""), + ), + default='STL', + ) + use_export_texture: BoolProperty( + name="Copy Textures", + description="Copy textures on export to the output path", + default=False, + ) + use_apply_scale: BoolProperty( + name="Apply Scale", + description="Apply scene scale setting on export", + default=False, + ) + export_path: StringProperty( + name="Export Directory", + description="Path to directory where the files are created", + default="//", maxlen=1024, subtype="DIR_PATH", + ) + thickness_min: FloatProperty( + name="Thickness", + description="Minimum thickness", + subtype='DISTANCE', + default=0.001, # 1mm + min=0.0, max=10.0, + ) + threshold_zero: FloatProperty( + name="Threshold", + description="Limit for checking zero area/length", + default=0.0001, + precision=5, + min=0.0, max=0.2, + ) + angle_distort: FloatProperty( + name="Angle", + description="Limit for checking distorted faces", + subtype='ANGLE', + default=math.radians(45.0), + min=0.0, max=math.radians(180.0), + ) + angle_sharp: FloatProperty( + name="Angle", + subtype='ANGLE', + default=math.radians(160.0), + min=0.0, max=math.radians(180.0), + ) + angle_overhang: FloatProperty( + name="Angle", + subtype='ANGLE', + default=math.radians(45.0), + min=0.0, max=math.radians(90.0), ) - - -def update_panels(self, context): - try: - for panel in panels: - if "bl_rna" in panel.__dict__: - bpy.utils.unregister_class(panel) - - for panel in panels: - panel.bl_category = context.user_preferences.addons[__name__].preferences.category - bpy.utils.register_class(panel) - - except Exception as e: - message = "3D Print Toolbox: Updating Panel locations has failed" - print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e)) - - -class Print3D_Preferences(AddonPreferences): - bl_idname = __name__ - - category = StringProperty( - name="Tab Category", - description="Choose a name for the category of the panel", - default="3D Printing", - update=update_panels, - ) - - def draw(self, context): - layout = self.layout - layout.prop(self, "category") classes = ( @@ -185,8 +150,7 @@ classes = ( operators.MESH_OT_Print3D_Export, Print3D_Scene_Props, - Print3D_Preferences, - ) +) def register(): @@ -195,8 +159,6 @@ def register(): bpy.types.Scene.print_3d = PointerProperty(type=Print3D_Scene_Props) - update_panels(None, bpy.context) - def unregister(): for cls in classes: diff --git a/object_print3d_utils/export.py b/object_print3d_utils/export.py index cbb607d2..00277958 100644 --- a/object_print3d_utils/export.py +++ b/object_print3d_utils/export.py @@ -53,11 +53,12 @@ def image_copy_guess(filepath, objects): def write_mesh(context, info, report_cb): scene = context.scene + layer = context.view_layer unit = scene.unit_settings print_3d = scene.print_3d - obj_base = scene.object_bases.active - obj = obj_base.object + # obj_base = layer.object_bases.active + obj = layer.objects.active export_format = print_3d.export_format global_scale = unit.scale_length if (unit.system != 'NONE' and print_3d.use_apply_scale) else 1.0 @@ -73,13 +74,16 @@ def write_mesh(context, info, report_cb): bpy.ops.object.mode_set(mode='OBJECT', toggle=False) from . import mesh_helpers - obj_base_tmp = mesh_helpers.object_merge(context, context_override["selected_objects"]) - context_override["active_object"] = obj_base_tmp.object - context_override["selected_bases"] = [obj_base_tmp] - context_override["selected_objects"] = [obj_base_tmp.object] + obj_tmp = mesh_helpers.object_merge(context, context_override["selected_objects"]) + context_override["active_object"] = obj_tmp + # context_override["selected_bases"] = [obj_base_tmp] + context_override["selected_objects"] = [obj_tmp] else: + # XXX28 + ''' if obj_base not in context_override["selected_bases"]: context_override["selected_bases"].append(obj_base) + ''' if obj not in context_override["selected_objects"]: context_override["selected_objects"].append(obj) diff --git a/object_print3d_utils/mesh_helpers.py b/object_print3d_utils/mesh_helpers.py index a5d5f74f..56dbfc65 100644 --- a/object_print3d_utils/mesh_helpers.py +++ b/object_print3d_utils/mesh_helpers.py @@ -32,7 +32,7 @@ def bmesh_copy_from_object(obj, transform=True, triangulate=True, apply_modifier if apply_modifiers and obj.modifiers: import bpy - me = obj.to_mesh(bpy.context.scene, True, 'PREVIEW', calc_tessface=False) + me = obj.to_mesh(depsgraph=bpy.context.depsgraph, apply_modifiers=True) bm = bmesh.new() bm.from_mesh(me) bpy.data.meshes.remove(me) @@ -157,19 +157,27 @@ def bmesh_check_thick_object(obj, thickness): # Create a real mesh (lame!) context = bpy.context scene = context.scene + layer = context.view_layer + layer_collection = context.layer_collection or layer.active_layer_collection + scene_collection = layer_collection.collection + me_tmp = bpy.data.meshes.new(name="~temp~") bm.to_mesh(me_tmp) # bm.free() # delay free obj_tmp = bpy.data.objects.new(name=me_tmp.name, object_data=me_tmp) - base = scene.objects.link(obj_tmp) + # base = scene.objects.link(obj_tmp) + scene_collection.objects.link(obj_tmp) # Add new object to local view layer + # XXX28 + ''' v3d = None if context.space_data and context.space_data.type == 'VIEW_3D': v3d = context.space_data if v3d and v3d.local_view: base.layers_from_view(context.space_data) + ''' scene.update() ray_cast = obj_tmp.ray_cast @@ -190,7 +198,7 @@ def bmesh_check_thick_object(obj, thickness): p_b = p - no_end p_dir = p_b - p_a - ok, co, no, index = ray_cast(p_a, p_dir, p_dir.length) + ok, co, no, index = ray_cast(p_a, p_dir, distance=p_dir.length) if ok: # Add the face we hit @@ -203,7 +211,7 @@ def bmesh_check_thick_object(obj, thickness): # finished with bm bm.free() - scene.objects.unlink(obj_tmp) + scene_collection.objects.unlink(obj_tmp) bpy.data.objects.remove(obj_tmp) bpy.data.meshes.remove(me_tmp) @@ -228,17 +236,20 @@ def object_merge(context, objects): seq.remove(seq[i]) scene = context.scene + layer = context.view_layer + layer_collection = context.layer_collection or layer.active_layer_collection + scene_collection = layer_collection.collection # deselect all for obj in scene.objects: - obj.select = False + obj.select_set(False) # add empty object mesh_base = bpy.data.meshes.new(name="~tmp~") obj_base = bpy.data.objects.new(name="~tmp~", object_data=mesh_base) - base_base = scene.objects.link(obj_base) - scene.objects.active = obj_base - obj_base.select = True + scene_collection.objects.link(obj_base) + layer.objects.active = obj_base + obj_base.select_set(True) # loop over all meshes for obj in objects: @@ -246,29 +257,29 @@ def object_merge(context, objects): continue # convert each to a mesh - mesh_new = obj.to_mesh(scene=scene, - apply_modifiers=True, - settings='PREVIEW', - calc_tessface=False) + mesh_new = obj.to_mesh( + depsgraph=context.depsgraph, + apply_modifiers=True, + ) # remove non-active uvs/vcols cd_remove_all_but_active(mesh_new.vertex_colors) - cd_remove_all_but_active(mesh_new.uv_textures) + cd_remove_all_but_active(mesh_new.uv_layers) # join into base mesh obj_new = bpy.data.objects.new(name="~tmp-new~", object_data=mesh_new) - base_new = scene.objects.link(obj_new) + base_new = scene_collection.objects.link(obj_new) obj_new.matrix_world = obj.matrix_world fake_context = context.copy() fake_context["active_object"] = obj_base - fake_context["selected_editable_bases"] = [base_base, base_new] + fake_context["selected_editable_objects"] = [obj_base, obj_new] bpy.ops.object.join(fake_context) del base_new, obj_new # remove object and its mesh, join does this - # scene.objects.unlink(obj_new) + # scene_collection.objects.unlink(obj_new) # bpy.data.objects.remove(obj_new) bpy.data.meshes.remove(mesh_new) @@ -276,7 +287,8 @@ def object_merge(context, objects): scene.update() # return new object - return base_base + return obj_base + def face_is_distorted(ele, angle_distort): diff --git a/object_print3d_utils/operators.py b/object_print3d_utils/operators.py index e7ff4713..ebc1a730 100644 --- a/object_print3d_utils/operators.py +++ b/object_print3d_utils/operators.py @@ -23,15 +23,15 @@ import bpy from bpy.types import Operator from bpy.props import ( - IntProperty, - FloatProperty, - ) + IntProperty, + FloatProperty, +) import bmesh from . import ( - mesh_helpers, - report, - ) + mesh_helpers, + report, +) def clean_float(text): @@ -430,16 +430,16 @@ class MESH_OT_Print3D_Clean_Non_Manifold(Operator): bl_label = "Print3D Clean Non-Manifold and Inverted" bl_options = {'REGISTER', 'UNDO'} - threshold = bpy.props.FloatProperty( - name="threshold", - description="Minimum distance between elements to merge", - default=0.0001, - ) - sides = bpy.props.IntProperty( - name="sides", - description="Number of sides in hole required to fill", - default=4, - ) + threshold: bpy.props.FloatProperty( + name="threshold", + description="Minimum distance between elements to merge", + default=0.0001, + ) + sides: bpy.props.IntProperty( + name="sides", + description="Number of sides in hole required to fill", + default=4, + ) def execute(self, context): self.context = context @@ -600,7 +600,7 @@ class MESH_OT_Print3D_Select_Report(Operator): bl_label = "Print3D Select Report" bl_options = {'INTERNAL'} - index = IntProperty() + index: IntProperty() _type_to_mode = { bmesh.types.BMVert: 'VERT', @@ -659,14 +659,14 @@ class MESH_OT_Print3D_Scale_To_Volume(Operator): bl_label = "Scale to Volume" bl_options = {'REGISTER', 'UNDO'} - volume_init = FloatProperty( - options={'HIDDEN'}, - ) - volume = FloatProperty( - name="Volume", - unit='VOLUME', - min=0.0, max=100000.0, - ) + volume_init: FloatProperty( + options={'HIDDEN'}, + ) + volume: FloatProperty( + name="Volume", + unit='VOLUME', + min=0.0, max=100000.0, + ) def execute(self, context): import math @@ -705,17 +705,17 @@ class MESH_OT_Print3D_Scale_To_Bounds(Operator): bl_label = "Scale to Bounds" bl_options = {'REGISTER', 'UNDO'} - length_init = FloatProperty( - options={'HIDDEN'}, - ) - axis_init = IntProperty( - options={'HIDDEN'}, - ) - length = FloatProperty( - name="Length Limit", - unit='LENGTH', - min=0.0, max=100000.0, - ) + length_init: FloatProperty( + options={'HIDDEN'}, + ) + axis_init: IntProperty( + options={'HIDDEN'}, + ) + length: FloatProperty( + name="Length Limit", + unit='LENGTH', + min=0.0, max=100000.0, + ) def execute(self, context): scale = self.length / self.length_init @@ -731,11 +731,11 @@ class MESH_OT_Print3D_Scale_To_Bounds(Operator): return max(((max(v[i] for v in vecs) - min(v[i] for v in vecs)), i) for i in range(3)) if context.mode == 'EDIT_MESH': - length, axis = calc_length([Vector(v) * obj.matrix_world + length, axis = calc_length([Vector(v) @ obj.matrix_world for obj in [context.edit_object] for v in obj.bound_box]) else: - length, axis = calc_length([Vector(v) * obj.matrix_world + length, axis = calc_length([Vector(v) @ obj.matrix_world for obj in context.selected_editable_objects if obj.type == 'MESH' for v in obj.bound_box]) diff --git a/object_print3d_utils/ui.py b/object_print3d_utils/ui.py index 6e5c3284..9f950c09 100644 --- a/object_print3d_utils/ui.py +++ b/object_print3d_utils/ui.py @@ -29,7 +29,7 @@ from . import report class Print3D_ToolBar: bl_label = "Print3D" bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' + bl_region_type = 'UI' _type_to_icon = { bmesh.types.BMVert: 'VERTEXSEL', @@ -49,7 +49,7 @@ class Print3D_ToolBar: if info: obj = context.edit_object - layout.label("Output:") + layout.label(text="Output:") box = layout.box() col = box.column(align=False) # box.alert = True @@ -61,7 +61,7 @@ class Print3D_ToolBar: icon=Print3D_ToolBar._type_to_icon[bm_type]).index = i layout.operator("mesh.select_non_manifold", text='Non Manifold Extended') else: - col.label(text) + col.label(text=text) def draw(self, context): @@ -73,13 +73,13 @@ class Print3D_ToolBar: # TODO, presets row = layout.row() - row.label("Statistics:") + row.label(text="Statistics:") rowsub = layout.row(align=True) rowsub.operator("mesh.print3d_info_volume", text="Volume") rowsub.operator("mesh.print3d_info_area", text="Area") row = layout.row() - row.label("Checks:") + row.label(text="Checks:") col = layout.column(align=True) col.operator("mesh.print3d_check_solid", text="Solid") col.operator("mesh.print3d_check_intersect", text="Intersections") @@ -102,7 +102,7 @@ class Print3D_ToolBar: col.operator("mesh.print3d_check_all", text="Check All") row = layout.row() - row.label("Cleanup:") + row.label(text="Cleanup:") col = layout.column(align=True) col.operator("mesh.print3d_clean_isolated", text="Isolated") rowsub = col.row(align=True) @@ -114,15 +114,15 @@ class Print3D_ToolBar: # col.operator("mesh.print3d_clean_thin", text="Wall Thickness") row = layout.row() - row.label("Scale To:") + row.label(text="Scale To:") rowsub = layout.row(align=True) rowsub.operator("mesh.print3d_scale_to_volume", text="Volume") rowsub.operator("mesh.print3d_scale_to_bounds", text="Bounds") col = layout.column() rowsub = col.row(align=True) - rowsub.label("Export Path:") - rowsub.prop(print_3d, "use_apply_scale", text="", icon='MAN_SCALE') + rowsub.label(text="Export Path:") + rowsub.prop(print_3d, "use_apply_scale", text="", icon='ORIENTATION_GLOBAL') rowsub.prop(print_3d, "use_export_texture", text="", icon='FILE_IMAGE') rowsub = col.row() rowsub.prop(print_3d, "export_path", text="") diff --git a/object_scatter/__init__.py b/object_scatter/__init__.py new file mode 100644 index 00000000..0d929b44 --- /dev/null +++ b/object_scatter/__init__.py @@ -0,0 +1,41 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +bl_info = { + "name": "Scatter Objects", + "author": "Jacques Lucke", + "version": (0, 1), + "blender": (2, 80, 0), + "location": "3D View", + "description": "Distribute object instances on another object.", + "warning": "", + "wiki_url": "", + "support": 'OFFICIAL', + "category": "Object", +} + +from . import ui +from . import operator + +def register(): + ui.register() + operator.register() + +def unregister(): + ui.unregister() + operator.register() diff --git a/object_scatter/operator.py b/object_scatter/operator.py new file mode 100644 index 00000000..1c0c133a --- /dev/null +++ b/object_scatter/operator.py @@ -0,0 +1,508 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +import gpu +import bgl +import blf +import math +import enum +import random + +from itertools import islice +from mathutils.bvhtree import BVHTree +from mathutils import Vector, Matrix, Euler +from gpu_extras.batch import batch_for_shader + +from bpy_extras.view3d_utils import ( + region_2d_to_vector_3d, + region_2d_to_origin_3d +) + +uniform_color_shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') + + +# Modal Operator +################################################################ + +class ScatterObjects(bpy.types.Operator): + bl_idname = "object.scatter" + bl_label = "Scatter Objects" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return ( + currently_in_3d_view(context) + and context.active_object is not None + and context.active_object.mode == 'OBJECT') + + def invoke(self, context, event): + self.target_object = context.active_object + self.objects_to_scatter = get_selected_non_active_objects(context) + + if self.target_object is None or len(self.objects_to_scatter) == 0: + self.report({'ERROR'}, "Select objects to scatter and a target object.") + return {'CANCELLED'} + + self.base_scale = get_max_object_side_length(self.objects_to_scatter) + + self.targets = [] + self.active_target = None + self.target_cache = {} + + self.enable_draw_callback() + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + def modal(self, context, event): + context.area.tag_redraw() + + if not event_is_in_region(event, context.region) and self.active_target is None: + return {'PASS_THROUGH'} + + if event.type == 'ESC': + return self.finish('CANCELLED') + + if event.type == 'RET' and event.value == 'PRESS': + self.create_scatter_object() + return self.finish('FINISHED') + + event_used = self.handle_non_exit_event(event) + if event_used: + return {'RUNNING_MODAL'} + else: + return {'PASS_THROUGH'} + + def handle_non_exit_event(self, event): + if self.active_target is None: + if event.type == 'LEFTMOUSE' and event.value == 'PRESS': + self.active_target = StrokeTarget() + self.active_target.start_build(self.target_object) + return True + else: + build_state = self.active_target.continue_build(event) + if build_state == BuildState.FINISHED: + self.targets.append(self.active_target) + self.active_target = None + self.remove_target_from_cache(self.active_target) + return True + + return False + + def enable_draw_callback(self): + self._draw_callback_view = bpy.types.SpaceView3D.draw_handler_add(self.draw_view, (), 'WINDOW', 'POST_VIEW') + self._draw_callback_px = bpy.types.SpaceView3D.draw_handler_add(self.draw_px, (), 'WINDOW', 'POST_PIXEL') + + def disable_draw_callback(self): + bpy.types.SpaceView3D.draw_handler_remove(self._draw_callback_view, 'WINDOW') + bpy.types.SpaceView3D.draw_handler_remove(self._draw_callback_px, 'WINDOW') + + def draw_view(self): + for target in self.iter_targets(): + target.draw() + + draw_matrices_batches(list(self.iter_matrix_batches())) + + def draw_px(self): + draw_text((20, 20, 0), "Instances: " + str(len(self.get_all_matrices()))) + + def finish(self, return_value): + self.disable_draw_callback() + bpy.context.area.tag_redraw() + return {return_value} + + def create_scatter_object(self): + matrix_chunks = make_random_chunks( + self.get_all_matrices(), len(self.objects_to_scatter)) + + collection = bpy.data.collections.new("Scatter") + bpy.context.collection.children.link(collection) + + for obj, matrices in zip(self.objects_to_scatter, matrix_chunks): + make_duplicator(collection, obj, matrices) + + def get_all_matrices(self): + settings = self.get_current_settings() + + matrices = [] + for target in self.iter_targets(): + self.ensure_target_is_in_cache(target) + matrices.extend(self.target_cache[target].get_matrices(settings)) + return matrices + + def iter_matrix_batches(self): + settings = self.get_current_settings() + for target in self.iter_targets(): + self.ensure_target_is_in_cache(target) + yield self.target_cache[target].get_batch(settings) + + def iter_targets(self): + yield from self.targets + if self.active_target is not None: + yield self.active_target + + def ensure_target_is_in_cache(self, target): + if target not in self.target_cache: + entry = TargetCacheEntry(target, self.base_scale) + self.target_cache[target] = entry + + def remove_target_from_cache(self, target): + self.target_cache.pop(self.active_target, None) + + def get_current_settings(self): + return bpy.context.scene.scatter_properties.to_settings() + +class TargetCacheEntry: + def __init__(self, target, base_scale): + self.target = target + self.last_used_settings = None + self.base_scale = base_scale + self.settings_changed() + + def get_matrices(self, settings): + self._handle_new_settings(settings) + if self.matrices is None: + self.matrices = self.target.get_matrices(settings) + return self.matrices + + def get_batch(self, settings): + self._handle_new_settings(settings) + if self.gpu_batch is None: + self.gpu_batch = create_batch_for_matrices(self.get_matrices(settings), self.base_scale) + return self.gpu_batch + + def _handle_new_settings(self, settings): + if settings != self.last_used_settings: + self.settings_changed() + self.last_used_settings = settings + + def settings_changed(self): + self.matrices = None + self.gpu_batch = None + + +# Duplicator Creation +###################################################### + +def make_duplicator(target_collection, source_object, matrices): + triangle_scale = 0.1 + + duplicator = triangle_object_from_matrices(source_object.name + " Duplicator", matrices, triangle_scale) + duplicator.instance_type = 'FACES' + duplicator.use_instance_faces_scale = True + duplicator.show_instancer_for_viewport = True + duplicator.show_instancer_for_render = False + duplicator.instance_faces_scale = 1 / triangle_scale + + copy_obj = source_object.copy() + copy_obj.name = source_object.name + " - copy" + copy_obj.hide_viewport = True + copy_obj.hide_render = True + copy_obj.location = (0, 0, 0) + copy_obj.parent = duplicator + + target_collection.objects.link(duplicator) + target_collection.objects.link(copy_obj) + +def triangle_object_from_matrices(name, matrices, triangle_scale): + mesh = triangle_mesh_from_matrices(name, matrices, triangle_scale) + return bpy.data.objects.new(name, mesh) + +def triangle_mesh_from_matrices(name, matrices, triangle_scale): + mesh = bpy.data.meshes.new(name) + vertices, polygons = mesh_data_from_matrices(matrices, triangle_scale) + mesh.from_pydata(vertices, [], polygons) + mesh.update() + mesh.validate() + return mesh + +unit_triangle_vertices = ( + Vector((-3**-0.25, -3**-0.75, 0)), + Vector((3**-0.25, -3**-0.75, 0)), + Vector((0, 2/3**0.75, 0))) + +def mesh_data_from_matrices(matrices, triangle_scale): + vertices = [] + polygons = [] + triangle_vertices = [triangle_scale * v for v in unit_triangle_vertices] + + for i, matrix in enumerate(matrices): + vertices.extend((matrix @ v for v in triangle_vertices)) + polygons.append((i * 3 + 0, i * 3 + 1, i * 3 + 2)) + + return vertices, polygons + + +# Target Provider +################################################# + +class BuildState(enum.Enum): + FINISHED = enum.auto() + ONGOING = enum.auto() + +class TargetProvider: + def start_build(self, target_object): + pass + + def continue_build(self, event): + return BuildState.FINISHED + + def get_matrices(self, scatter_settings): + return [] + + def draw(self): + pass + +class StrokeTarget(TargetProvider): + def start_build(self, target_object): + self.points = [] + self.bvhtree = bvhtree_from_object(target_object) + self.batch = None + + def continue_build(self, event): + if event.type == 'LEFTMOUSE' and event.value == 'RELEASE': + return BuildState.FINISHED + + mouse_pos = (event.mouse_region_x, event.mouse_region_y) + location, *_ = shoot_region_2d_ray(self.bvhtree, mouse_pos) + if location is not None: + self.points.append(location) + self.batch = None + return BuildState.ONGOING + + def draw(self): + if self.batch is None: + self.batch = create_line_strip_batch(self.points) + draw_line_strip_batch(self.batch, color=(1.0, 0.4, 0.1, 1.0), thickness=5) + + def get_matrices(self, scatter_settings): + return scatter_around_stroke(self.points, self.bvhtree, scatter_settings) + +def scatter_around_stroke(stroke_points, bvhtree, settings): + scattered_matrices = [] + for point, local_seed in iter_points_on_stroke_with_seed(stroke_points, settings.density, settings.seed): + matrix = scatter_from_source_point(bvhtree, point, local_seed, settings) + scattered_matrices.append(matrix) + return scattered_matrices + +def iter_points_on_stroke_with_seed(stroke_points, density, seed): + for i, (start, end) in enumerate(iter_pairwise(stroke_points)): + segment_seed = sub_seed(seed, i) + segment_vector = end - start + + segment_length = segment_vector.length + amount = round_random(segment_length * density, segment_seed) + + for j in range(amount): + t = random_uniform(sub_seed(segment_seed, j, 0)) + origin = start + t * segment_vector + yield origin, sub_seed(segment_seed, j, 1) + +def scatter_from_source_point(bvhtree, point, seed, settings): + # Project displaced point on surface + radius = random_uniform(sub_seed(seed, 0)) * settings.radius + offset = random_vector(sub_seed(seed, 2)) * radius + location, normal, *_ = bvhtree.find_nearest(point + offset) + assert location is not None + normal.normalize() + + # Scale + min_scale = settings.scale * (1 - settings.random_scale) + max_scale = settings.scale + scale = random_uniform(sub_seed(seed, 1), min_scale, max_scale) + + # Location + location += normal * settings.normal_offset * scale + + # Rotation + z_rotation = Euler((0, 0, random_uniform(sub_seed(seed, 3), 0, 2 * math.pi))).to_matrix() + normal_rotation = normal.to_track_quat('Z', 'X').to_matrix() + local_rotation = random_euler(sub_seed(seed, 3), settings.rotation).to_matrix() + rotation = local_rotation @ normal_rotation @ z_rotation + + return Matrix.Translation(location) @ rotation.to_4x4() @ scale_matrix(scale) + + +# Drawing +################################################# + +box_vertices = ( + (-1, -1, 1), ( 1, -1, 1), ( 1, 1, 1), (-1, 1, 1), + (-1, -1, -1), ( 1, -1, -1), ( 1, 1, -1), (-1, 1, -1)) + +box_indices = ( + (0, 1, 2), (2, 3, 0), (1, 5, 6), (6, 2, 1), + (7, 6, 5), (5, 4, 7), (4, 0, 3), (3, 7, 4), + (4, 5, 1), (1, 0, 4), (3, 2, 6), (6, 7, 3)) + +box_vertices = tuple(Vector(vertex) * 0.5 for vertex in box_vertices) + +def draw_matrices_batches(batches): + uniform_color_shader.bind() + uniform_color_shader.uniform_float("color", (0.4, 0.4, 1.0, 0.3)) + + bgl.glEnable(bgl.GL_BLEND) + bgl.glDepthMask(bgl.GL_FALSE) + + for batch in batches: + batch.draw(uniform_color_shader) + + bgl.glDisable(bgl.GL_BLEND) + bgl.glDepthMask(bgl.GL_TRUE) + +def create_batch_for_matrices(matrices, base_scale): + coords = [] + indices = [] + + scaled_box_vertices = [base_scale * vertex for vertex in box_vertices] + + for matrix in matrices: + offset = len(coords) + coords.extend((matrix @ vertex for vertex in scaled_box_vertices)) + indices.extend(tuple(index + offset for index in element) for element in box_indices) + + batch = batch_for_shader(uniform_color_shader, 'TRIS', {"pos" : coords}, indices = indices) + return batch + + +def draw_line_strip_batch(batch, color, thickness=1): + bgl.glLineWidth(thickness) + uniform_color_shader.bind() + uniform_color_shader.uniform_float("color", color) + batch.draw(uniform_color_shader) + +def create_line_strip_batch(coords): + return batch_for_shader(uniform_color_shader, 'LINE_STRIP', {"pos" : coords}) + + +def draw_text(location, text, size=15, color=(1, 1, 1, 1)): + font_id = 0 + blf.position(font_id, *location) + blf.size(font_id, size, 72) + blf.draw(font_id, text) + + +# Utilities +######################################################## + +''' +Pythons random functions are designed to be used in cases +when a seed is set once and then many random numbers are +generated. To improve the user experience I want to have +full control over how random seeds propagate through the +functions. This is why I use custom random functions. + +One benefit is that changing the object density does not +generate new random positions for all objects. +''' + +def round_random(value, seed): + probability = value % 1 + if probability < random_uniform(seed): + return math.floor(value) + else: + return math.ceil(value) + +def random_vector(x, min=-1, max=1): + return Vector(( + random_uniform(sub_seed(x, 0), min, max), + random_uniform(sub_seed(x, 1), min, max), + random_uniform(sub_seed(x, 2), min, max))) + +def random_euler(x, factor): + return Euler(tuple(random_vector(x) * factor)) + +def random_uniform(x, min=0, max=1): + return random_int(x) / 2147483648 * (max - min) + min + +def random_int(x): + x = (x<<13) ^ x + return (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff + +def sub_seed(seed, index, index2=0): + return random_int(seed * 3243 + index * 5643 + index2 * 54243) + + +def currently_in_3d_view(context): + return context.space_data.type == 'VIEW_3D' + +def get_selected_non_active_objects(context): + return set(context.selected_objects) - {context.active_object} + +def make_random_chunks(sequence, chunk_amount): + sequence = list(sequence) + random.shuffle(sequence) + return make_chunks(sequence, chunk_amount) + +def make_chunks(sequence, chunk_amount): + length = math.ceil(len(sequence) / chunk_amount) + return [sequence[i:i+length] for i in range(0, len(sequence), length)] + +def iter_pairwise(sequence): + return zip(sequence, islice(sequence, 1, None)) + +def bvhtree_from_object(object): + import bmesh + bm = bmesh.new() + + mesh = object.to_mesh(bpy.context.depsgraph, True) + bm.from_mesh(mesh) + bm.transform(object.matrix_world) + + bvhtree = BVHTree.FromBMesh(bm) + bpy.data.meshes.remove(mesh) + return bvhtree + +def shoot_region_2d_ray(bvhtree, position_2d): + region = bpy.context.region + region_3d = bpy.context.space_data.region_3d + + origin = region_2d_to_origin_3d(region, region_3d, position_2d) + direction = region_2d_to_vector_3d(region, region_3d, position_2d) + + location, normal, index, distance = bvhtree.ray_cast(origin, direction) + return location, normal, index, distance + +def scale_matrix(factor): + m = Matrix.Identity(4) + m[0][0] = factor + m[1][1] = factor + m[2][2] = factor + return m + +def event_is_in_region(event, region): + return (region.x <= event.mouse_x <= region.x + region.width + and region.y <= event.mouse_y <= region.y + region.height) + +def get_max_object_side_length(objects): + return max( + max(obj.dimensions[0] for obj in objects), + max(obj.dimensions[1] for obj in objects), + max(obj.dimensions[2] for obj in objects) + ) + + +# Registration +############################################### + +def register(): + bpy.utils.register_class(ScatterObjects) + +def unregister(): + bpy.utils.unregister_class(ScatterObjects) diff --git a/object_scatter/ui.py b/object_scatter/ui.py new file mode 100644 index 00000000..94d97a9c --- /dev/null +++ b/object_scatter/ui.py @@ -0,0 +1,138 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +import math + +from collections import namedtuple + +from bpy.props import ( + IntProperty, + FloatProperty, + PointerProperty +) + + +ScatterSettings = namedtuple("ScatterSettings", + ["seed", "density", "radius", "scale", "random_scale", + "rotation", "normal_offset"]) + +class ObjectScatterProperties(bpy.types.PropertyGroup): + seed: IntProperty( + name="Seed", + default=0 + ) + + density: FloatProperty( + name="Density", + default=10, + min=0, + soft_max=50 + ) + + radius: FloatProperty( + name="Radius", + default=1, + min=0, + soft_max=5, + subtype='DISTANCE', + unit='LENGTH' + ) + + scale: FloatProperty( + name="Scale", + default=0.3, + min=0.00001, + soft_max=1 + ) + + random_scale_percentage: FloatProperty( + name="Random Scale Percentage", + default=80, + min=0, + max=100, + subtype='PERCENTAGE', + precision=0 + ) + + rotation: FloatProperty( + name="Rotation", + default=0.5, + min=0, + max=math.pi * 2, + soft_max=math.pi / 2, + subtype='ANGLE', + unit='ROTATION' + ) + + normal_offset: FloatProperty( + name="Normal Offset", + default=0, + soft_min=-1.0, + soft_max=1.0 + ) + + def to_settings(self): + return ScatterSettings( + seed=self.seed, + density=self.density, + radius=self.radius, + scale=self.scale, + random_scale=self.random_scale_percentage / 100, + rotation=self.rotation, + normal_offset=self.normal_offset, + ) + + +class ObjectScatterPanel(bpy.types.Panel): + bl_label = "Object Scatter" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = '.objectmode' + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + scatter = context.scene.scatter_properties + + layout.prop(scatter, "density", slider=True) + layout.prop(scatter, "radius", slider=True) + + col = layout.column(align=True) + col.prop(scatter, "scale", slider=True) + col.prop(scatter, "random_scale_percentage", text="Randomness", slider=True) + + layout.prop(scatter, "rotation", slider=True) + layout.prop(scatter, "normal_offset", text="Offset", slider=True) + layout.prop(scatter, "seed") + + +classes = ( + ObjectScatterProperties, + ObjectScatterPanel, +) + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + bpy.types.Scene.scatter_properties = PointerProperty(type=ObjectScatterProperties) + +def unregister(): + for cls in classes: + bpy.utils.unregister_class(cls) + del bpy.types.Scene.scatter_properties diff --git a/oscurart_tools/__init__.py b/oscurart_tools/__init__.py index 420a4990..4b7b5cf7 100644 --- a/oscurart_tools/__init__.py +++ b/oscurart_tools/__init__.py @@ -208,7 +208,7 @@ class OscPanelMesh(Panel): colrow.operator("lattice.mirror_selected", icon="LATTICE_DATA") colrow = col.row(align=1) colrow.label(text="Edit Multimesh") - colrow.prop_search(scene, "multimeshedit", bpy.data, "groups", text="") + colrow.prop_search(scene, "multimeshedit", bpy.data, "collections", text="") colrow = col.row(align=1) colrow.operator("mesh.create_edit_multimesh", icon="IMPORT", text= "StartEdit") colrow.operator("mesh.apply_edit_multimesh", icon="EXPORT", text="FinishEdit") diff --git a/oscurart_tools/oscurart_files.py b/oscurart_tools/oscurart_files.py index 65c248eb..6c2f371d 100644 --- a/oscurart_tools/oscurart_files.py +++ b/oscurart_tools/oscurart_files.py @@ -97,10 +97,10 @@ class reFreshMissingGroups(Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - for group in bpy.data.groups: + for group in bpy.data.collections: if group.library is not None: with bpy.data.libraries.load(group.library.filepath, link=True) as (linked, local): - local.groups = linked.groups + local.collections = linked.collections return {'FINISHED'} diff --git a/oscurart_tools/oscurart_meshes.py b/oscurart_tools/oscurart_meshes.py index 70f4df1c..39257542 100644 --- a/oscurart_tools/oscurart_meshes.py +++ b/oscurart_tools/oscurart_meshes.py @@ -321,8 +321,7 @@ def DefOscObjectToMesh(): MESH = ACTOBJ.to_mesh( scene=bpy.context.scene, apply_modifiers=True, - settings="RENDER", - calc_tessface=True) + settings="RENDER") OBJECT = bpy.data.objects.new(("%s_Freeze") % (ACTOBJ.name), MESH) bpy.context.scene.objects.link(OBJECT) @@ -450,7 +449,7 @@ class ModalIndexOperator(Operator): self.tsize -= 1 elif event.type in {'RIGHTMOUSE', 'ESC', 'TAB'}: bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') - context.area.header_text_set("") + context.area.header_text_set(None) return {'CANCELLED'} return {'PASS_THROUGH'} @@ -601,7 +600,7 @@ def defPasteUvsIsland(self, uvOffset, rotateUv,context): bm = bmesh.from_edit_mesh(bpy.context.object.data) bmesh.ops.reverse_uvs(bm, faces=[f for f in bm.faces if f.select]) bmesh.ops.rotate_uvs(bm, faces=[f for f in bm.faces if f.select]) - #bmesh.update_edit_mesh(bpy.context.object.data, tessface=False, destructive=False) + #bmesh.update_edit_mesh(bpy.context.object.data, loop_triangles=False, destructive=False) @@ -660,7 +659,7 @@ class createEditMultimesh(Operator): global relvert global me global ob - temp = [[ob , [vert.co for vert in ob.data.vertices]]for ob in bpy.data.groups[bpy.context.scene.multimeshedit].objects] + temp = [[ob , [vert.co for vert in ob.data.vertices]]for ob in bpy.data.collections[bpy.context.scene.multimeshedit].objects] vi = 0 pi = 0 relvert = {} diff --git a/oscurart_tools/oscurart_objects.py b/oscurart_tools/oscurart_objects.py index dd3d4624..b3773b7a 100644 --- a/oscurart_tools/oscurart_objects.py +++ b/oscurart_tools/oscurart_objects.py @@ -198,7 +198,7 @@ def CopyObjectGroupsAndLayers(self): scene.object_bases[OBJECT.name].layers[:] = list(GLOBALLAYERS) # REMUEVO DE TODO GRUPO - for GROUP in bpy.data.groups[:]: + for GROUP in bpy.data.collections[:]: if GROUP in OBJECT.users_group[:]: GROUP.objects.unlink(OBJECT) @@ -342,17 +342,17 @@ class SetLayersToOtherScenes (Operator): def DefRenderOnlyInCamera(): # crea grupos - if "INCAMERA" not in bpy.data.groups: - bpy.data.groups.new("INCAMERA") - if "NOTINCAMERA" not in bpy.data.groups: - bpy.data.groups.new("NOTINCAMERA") + if "INCAMERA" not in bpy.data.collections: + bpy.data.collections.new("INCAMERA") + if "NOTINCAMERA" not in bpy.data.collections: + bpy.data.collections.new("NOTINCAMERA") # limpio grupos for ob in bpy.data.objects: - if ob.name in bpy.data.groups["INCAMERA"].objects: - bpy.data.groups["INCAMERA"].objects.unlink(ob) - if ob.name in bpy.data.groups["NOTINCAMERA"].objects: - bpy.data.groups["NOTINCAMERA"].objects.unlink(ob) + if ob.name in bpy.data.collections["INCAMERA"].objects: + bpy.data.collections["INCAMERA"].objects.unlink(ob) + if ob.name in bpy.data.collections["NOTINCAMERA"].objects: + bpy.data.collections["NOTINCAMERA"].objects.unlink(ob) # ordeno grupos for ob in bpy.data.objects: @@ -370,9 +370,9 @@ def DefRenderOnlyInCamera(): else: obs = True if obs: - bpy.data.groups["INCAMERA"].objects.link(ob) + bpy.data.collections["INCAMERA"].objects.link(ob) else: - bpy.data.groups["NOTINCAMERA"].objects.link(ob) + bpy.data.collections["NOTINCAMERA"].objects.link(ob) class RenderOnlyInCamera (Operator): @@ -528,15 +528,15 @@ class oscDuplicateSymmetricalOp (Operator): def DefObjectToGroups(): try: "%s_MSH" % (os.path.basename(bpy.data.filepath).replace(".blend", "")) - scgr = bpy.data.groups["%s_MSH" % (os.path.basename(bpy.data.filepath).replace(".blend", ""))] + scgr = bpy.data.collections["%s_MSH" % (os.path.basename(bpy.data.filepath).replace(".blend", ""))] except: - scgr = bpy.data.groups.new( + scgr = bpy.data.collections.new( "%s_MSH" % (os.path.basename(bpy.data.filepath).replace(".blend", ""))) for ob in bpy.data.objects: if ob.select: if ob.type == "MESH": - gr = bpy.data.groups.new(ob.name) + gr = bpy.data.collections.new(ob.name) gr.objects.link(ob) scgr.objects.link(ob) diff --git a/oscurart_tools/oscurart_overrides.py b/oscurart_tools/oscurart_overrides.py index 5709c5b5..9924fda5 100644 --- a/oscurart_tools/oscurart_overrides.py +++ b/oscurart_tools/oscurart_overrides.py @@ -39,7 +39,7 @@ def DefOscApplyOverrides(self): scene = bpy.context.scene proptolist = list(eval(scene.oscurart.overrides)) for group, material in proptolist: - for object in bpy.data.groups[group].objects: + for object in bpy.data.collections[group].objects: lenslots = len(object.material_slots) if object.type in types: if len(object.data.materials): @@ -133,7 +133,7 @@ class OscCheckOverrides(Operator): MATLIST.append(MATERIAL.name) GROUPLIST = [] - for GROUP in bpy.data.groups[:]: + for GROUP in bpy.data.collections[:]: if GROUP.users > 0: GROUPLIST.append(GROUP.name) @@ -194,7 +194,7 @@ class OscOverridesGUI(Panel): for i, m in enumerate(bpy.context.scene.ovlist): colrow = col.row(align=1) - colrow.prop_search(m, "grooverride", bpy.data, "groups", text="") + colrow.prop_search(m, "grooverride", bpy.data, "collections", text="") colrow.prop_search( m, "matoverride", diff --git a/oscurart_tools/oscurart_render.py b/oscurart_tools/oscurart_render.py index f6e083e9..67dacfb2 100644 --- a/oscurart_tools/oscurart_render.py +++ b/oscurart_tools/oscurart_render.py @@ -55,7 +55,7 @@ def defRenderAll(frametype, scenes): scene.frame_start = FC for group, material in proptolist: - for object in bpy.data.groups[group].objects: + for object in bpy.data.collections[group].objects: lenslots = len(object.material_slots) if object.type in types: if len(object.data.materials): diff --git a/paint_palette.py b/paint_palette.py index cf0e58e9..320163ad 100644 --- a/paint_palette.py +++ b/paint_palette.py @@ -462,12 +462,12 @@ def color_palette_draw(self, context): row = layout.row(align=True) row.menu("PALETTE_MT_menu", text=PALETTE_MT_menu.bl_label) - row.operator("palette.preset_add", text="", icon="ZOOMIN").remove_active = False - row.operator("palette.preset_add", text="", icon="ZOOMOUT").remove_active = True + row.operator("palette.preset_add", text="", icon='ADD').remove_active = False + row.operator("palette.preset_add", text="", icon='REMOVE').remove_active = True col = layout.column(align=True) row = col.row(align=True) - row.operator("palette_props.add_color", icon="ZOOMIN") + row.operator("palette_props.add_color", icon='ADD') row.prop(palette_props, "index") row.operator("palette_props.remove_color", icon="PANEL_CLOSE") diff --git a/pie_menus_official/__init__.py b/pie_menus_official/__init__.py index 513ae2b3..52c8d346 100644 --- a/pie_menus_official/__init__.py +++ b/pie_menus_official/__init__.py @@ -31,7 +31,7 @@ bl_info = { "name": "UI Pie Menu Official", "author": "Antony Riakiotakis, Sebastian Koenig", "version": (1, 1, 5), - "blender": (2, 7, 7), + "blender": (2, 80, 0), "description": "Individual Pie Menu Activation List", "location": "Addons Preferences", "warning": "", @@ -129,42 +129,42 @@ class UIToolsPreferences(AddonPreferences): op = sub.operator('wm.context_toggle', text='', icon=icon, emboss=False) op.data_path = 'addon_prefs.show_expanded_' + mod_name - sub.label('{}: {}'.format(info['category'], info['name'])) + sub.label(text='{}: {}'.format(info['category'], info['name'])) sub = row.row() sub.alignment = 'RIGHT' if info.get('warning'): - sub.label('', icon='ERROR') + sub.label(text='', icon='ERROR') sub.prop(self, 'use_' + mod_name, text='') # The second stage if expand: if info.get('description'): - split = col.row().split(percentage=0.15) - split.label('Description:') - split.label(info['description']) + split = col.row().split(factor=0.15) + split.label(text='Description:') + split.label(text=info['description']) if info.get('location'): - split = col.row().split(percentage=0.15) - split.label('Location:') - split.label(info['location']) + split = col.row().split(factor=0.15) + split.label(text='Location:') + split.label(text=info['location']) """ if info.get('author'): - split = col.row().split(percentage=0.15) - split.label('Author:') - split.label(info['author']) + split = col.row().split(factor=0.15) + split.label(text='Author:') + split.label(text=info['author']) """ if info.get('version'): - split = col.row().split(percentage=0.15) - split.label('Version:') - split.label('.'.join(str(x) for x in info['version']), + split = col.row().split(factor=0.15) + split.label(text='Version:') + split.label(text='.'.join(str(x) for x in info['version']), translate=False) if info.get('warning'): - split = col.row().split(percentage=0.15) - split.label('Warning:') - split.label(' ' + info['warning'], icon='ERROR') + split = col.row().split(factor=0.15) + split.label(text='Warning:') + split.label(text=' ' + info['warning'], icon='ERROR') tot_row = int(bool(info.get('wiki_url'))) if tot_row: - split = col.row().split(percentage=0.15) + split = col.row().split(factor=0.15) split.label(text='Internet:') if info.get('wiki_url'): op = split.operator('wm.url_open', diff --git a/pie_menus_official/pie_manipulator_of.py b/pie_menus_official/pie_manipulator_of.py index 04ced83a..eb475cf7 100644 --- a/pie_menus_official/pie_manipulator_of.py +++ b/pie_menus_official/pie_manipulator_of.py @@ -26,7 +26,7 @@ class VIEW3D_manipulator_set_of(Operator): bl_label = "Set Manipulator" bl_idname = "view3d.manipulator_set" - type = EnumProperty( + type: EnumProperty( name="Type", items=(('TRANSLATE', "Translate", "Use the manipulator for movement transformations"), ('ROTATE', "Rotate", "Use the manipulator for rotation transformations"), @@ -36,7 +36,7 @@ class VIEW3D_manipulator_set_of(Operator): def execute(self, context): # show manipulator if user selects an option - context.space_data.show_manipulator = True + context.space_data.show_gizmo = True context.space_data.transform_manipulators = {self.type} return {'FINISHED'} @@ -50,10 +50,10 @@ class VIEW3D_PIE_manipulator_of(Menu): layout = self.layout pie = layout.menu_pie() - pie.operator("view3d.manipulator_set", icon='MAN_TRANS', text="Translate").type = 'TRANSLATE' - pie.operator("view3d.manipulator_set", icon='MAN_ROT', text="Rotate").type = 'ROTATE' - pie.operator("view3d.manipulator_set", icon='MAN_SCALE', text="Scale").type = 'SCALE' - pie.prop(context.space_data, "show_manipulator") + pie.operator("wm.tool_set_by_name", icon='MAN_TRANS', text="Translate").name = "Move" + pie.operator("wm.tool_set_by_name", icon='MAN_ROT', text="Rotate").name = "Rotate" + pie.operator("wm.tool_set_by_name", icon='MAN_SCALE', text="Scale").name = "Scale" + pie.prop(context.space_data, "show_gizmo") classes = ( diff --git a/pie_menus_official/pie_pivot_of.py b/pie_menus_official/pie_pivot_of.py index 88677b69..ec48d359 100644 --- a/pie_menus_official/pie_pivot_of.py +++ b/pie_menus_official/pie_pivot_of.py @@ -24,9 +24,9 @@ class VIEW3D_PIE_pivot_of(Menu): layout = self.layout pie = layout.menu_pie() - pie.prop(context.space_data, "pivot_point", expand=True) + pie.prop(context.scene.tool_settings, "transform_pivot_point", expand=True) if context.active_object and context.active_object.mode == 'OBJECT': - pie.prop(context.space_data, "use_pivot_point_align", text="Center Points") + pie.prop(context.scene.tool_settings, "use_transform_pivot_point_align", text="Center Points") classes = ( diff --git a/pie_menus_official/pie_view_of.py b/pie_menus_official/pie_view_of.py index 40b79217..1c1f3df2 100644 --- a/pie_menus_official/pie_view_of.py +++ b/pie_menus_official/pie_view_of.py @@ -38,7 +38,7 @@ class VIEW3D_PIE_view_of(Menu): layout = self.layout pie = layout.menu_pie() - pie.operator_enum("VIEW3D_OT_viewnumpad", "type") + pie.operator_enum("VIEW3D_OT_view_axis", "type") pie.operator("wm.call_menu_pie", text="More", icon='PLUS').name = "VIEW3D_PIE_view_more_of" diff --git a/render_copy_settings/__init__.py b/render_copy_settings/__init__.py index 5f7d8781..8c57f68f 100644 --- a/render_copy_settings/__init__.py +++ b/render_copy_settings/__init__.py @@ -19,10 +19,10 @@ # <pep8 compliant> bl_info = { - "name": "Copy Settings", + "name": "Copy Render Settings", "author": "Bastien Montagne", - "version": (0, 1, 7), - "blender": (2, 79, 1), + "version": (1, 0, 0), + "blender": (2, 80, 0), "location": "Render buttons (Properties window)", "description": "Allows to copy a selection of render settings " "from current scene to others.", diff --git a/render_copy_settings/data.py b/render_copy_settings/data.py index d370d7b7..59b91fcc 100644 --- a/render_copy_settings/data.py +++ b/render_copy_settings/data.py @@ -31,34 +31,34 @@ from bpy.props import ( ######################################################################################################################## class RenderCopySettingsDataScene(bpy.types.PropertyGroup): - allowed = BoolProperty(default=True) + allowed: BoolProperty(default=True) class RenderCopySettingsDataSetting(bpy.types.PropertyGroup): - strid = StringProperty(default="") - copy = BoolProperty(default=False) + strid: StringProperty(default="") + copy: BoolProperty(default=False) class RenderCopySettingsData(bpy.types.PropertyGroup): # XXX: The consistency of this collection is delegated to the UI code. # It should only contain one element for each render setting. - affected_settings = CollectionProperty(type=RenderCopySettingsDataSetting, - name="Affected Settings", - description="The list of all available render settings") + affected_settings: CollectionProperty(type=RenderCopySettingsDataSetting, + name="Affected Settings", + description="The list of all available render settings") # XXX Unused, but needed for template_list… - affected_settings_idx = IntProperty() + affected_settings_idx: IntProperty() # XXX: The consistency of this collection is delegated to the UI code. # It should only contain one element for each scene. - allowed_scenes = CollectionProperty(type=RenderCopySettingsDataScene, - name="Allowed Scenes", - description="The list all scenes in the file") + allowed_scenes: CollectionProperty(type=RenderCopySettingsDataScene, + name="Allowed Scenes", + description="The list all scenes in the file") # XXX Unused, but needed for template_list… - allowed_scenes_idx = IntProperty() + allowed_scenes_idx: IntProperty() - filter_scene = StringProperty(name="Filter Scene", - description="Regex to only affect scenes which name matches it", - default="") + filter_scene: StringProperty(name="Filter Scene", + description="Regex to only affect scenes which name matches it", + default="") classes = ( diff --git a/render_copy_settings/operator.py b/render_copy_settings/operator.py index a88e7dc1..5c633f1f 100644 --- a/render_copy_settings/operator.py +++ b/render_copy_settings/operator.py @@ -47,7 +47,7 @@ def collection_property_sort(collection, sortkey, start_idx=0): class RenderCopySettingsOPPrepare(bpy.types.Operator): - """Prepare internal data for render_copy_settings (gathering all existingrender settings, and scenes)""" + """Prepare internal data for render_copy_settings (gathering all existing render settings, and scenes)""" bl_idname = "scene.render_copy_settings_prepare" bl_label = "Render: Copy Settings Prepare" bl_option = {'REGISTER'} @@ -127,9 +127,9 @@ class RenderCopySettingsOPPreset(bpy.types.Operator): # Enable undo… bl_option = {'REGISTER', 'UNDO'} - presets = EnumProperty(items=(p.rna_enum for p in presets.presets), - default=set(), - options={'ENUM_FLAG'}) + presets: EnumProperty(items=(p.rna_enum for p in presets.presets), + default=set(), + options={'ENUM_FLAG'}) @staticmethod def process_elements(settings, elts): diff --git a/render_copy_settings/panel.py b/render_copy_settings/panel.py index 375e1bd3..88f4e940 100644 --- a/render_copy_settings/panel.py +++ b/render_copy_settings/panel.py @@ -28,14 +28,14 @@ class RENDER_UL_copy_settings(bpy.types.UIList): #assert(isinstance(item, (data_types.RenderCopySettingsScene, data_types.RenderCopySettingsDataSetting))) if self.layout_type in {'DEFAULT', 'COMPACT'}: if isinstance(item, data_types.RenderCopySettingsDataSetting): - layout.label(item.name, icon_value=icon) + layout.label(text=item.name, icon_value=icon) layout.prop(item, "copy", text="") else: #elif isinstance(item, data_types.RenderCopySettingsDataScene): layout.prop(item, "allowed", text=item.name, toggle=True) elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' if isinstance(item, data_types.RenderCopySettingsDataSetting): - layout.label(item.name, icon_value=icon) + layout.label(text=item.name, icon_value=icon) layout.prop(item, "copy", text="") else: #elif isinstance(item, data_types.RenderCopySettingsDataScene): layout.prop(item, "allowed", text=item.name, toggle=True) @@ -60,9 +60,9 @@ class RENDER_PT_copy_settings(bpy.types.Panel): if bpy.ops.scene.render_copy_settings_prepare.poll(): bpy.ops.scene.render_copy_settings_prepare() - split = layout.split(0.75) + split = layout.split(factor=0.75) split.template_list("RENDER_UL_copy_settings", "settings", cp_sett, "affected_settings", - cp_sett, "affected_settings_idx", rows=6) + cp_sett, "affected_settings_idx", rows=5) col = split.column() all_set = {sett.strid for sett in cp_sett.affected_settings if sett.copy} @@ -76,7 +76,7 @@ class RENDER_PT_copy_settings(bpy.types.Panel): layout.prop(cp_sett, "filter_scene") if len(cp_sett.allowed_scenes): - layout.label("Affected Scenes:") + layout.label(text="Affected Scenes:") layout.template_list("RENDER_UL_copy_settings", "scenes", cp_sett, "allowed_scenes", # cp_sett, "allowed_scenes_idx", rows=6, type='GRID') cp_sett, "allowed_scenes_idx", rows=6) # XXX Grid is not nice currently... diff --git a/render_copy_settings/presets.py b/render_copy_settings/presets.py index 8bc43afb..0fc53e39 100644 --- a/render_copy_settings/presets.py +++ b/render_copy_settings/presets.py @@ -38,9 +38,6 @@ presets = (CopyPreset("Resolution", CopyPreset("Threads", ("threads", "Render Threads", "The thread mode and number settings"), {"threads_mode", "threads"}), - CopyPreset("Fields", - ("fields", "Render Fields", "The Fields settings"), - {"use_fields", "field_order", "use_fields_still"}), CopyPreset("Stamp", ("stamp", "Render Stamp", "The Stamp toggle"), {"use_stamp"}) diff --git a/render_povray/__init__.py b/render_povray/__init__.py index a8b07887..9d7254ea 100644 --- a/render_povray/__init__.py +++ b/render_povray/__init__.py @@ -2274,11 +2274,11 @@ class PovrayPreferences(AddonPreferences): def register(): bpy.utils.register_module(__name__) - bpy.types.INFO_MT_add.prepend(ui.menu_func_add) - bpy.types.INFO_MT_file_import.append(ui.menu_func_import) + bpy.types.VIEW3D_MT_add.prepend(ui.menu_func_add) + bpy.types.TOPBAR_MT_file_import.append(ui.menu_func_import) bpy.types.TEXT_MT_templates.append(ui.menu_func_templates) bpy.types.RENDER_PT_povray_radiosity.prepend(ui.rad_panel_func) - bpy.types.LAMP_PT_POV_lamp.prepend(ui.lamp_panel_func) + bpy.types.LIGHT_PT_POV_light.prepend(ui.light_panel_func) bpy.types.WORLD_PT_world.prepend(ui.world_panel_func) # was used for parametric objects but made the other addon unreachable on # unregister for other tools to use created a user action call instead @@ -2312,11 +2312,11 @@ def unregister(): #bpy.types.TEXTURE_PT_context_texture.remove(TEXTURE_PT_povray_type) #addon_utils.disable("add_mesh_extra_objects", default_set=False) bpy.types.WORLD_PT_world.remove(ui.world_panel_func) - bpy.types.LAMP_PT_POV_lamp.remove(ui.lamp_panel_func) + bpy.types.LIGHT_PT_POV_light.remove(ui.light_panel_func) bpy.types.RENDER_PT_povray_radiosity.remove(ui.rad_panel_func) bpy.types.TEXT_MT_templates.remove(ui.menu_func_templates) - bpy.types.INFO_MT_file_import.remove(ui.menu_func_import) - bpy.types.INFO_MT_add.remove(ui.menu_func_add) + bpy.types.TOPBAR_MT_file_import.remove(ui.menu_func_import) + bpy.types.VIEW3D_MT_add.remove(ui.menu_func_add) bpy.utils.unregister_module(__name__) diff --git a/render_povray/nodes.py b/render_povray/nodes.py index a4acd138..a8281c8a 100644 --- a/render_povray/nodes.py +++ b/render_povray/nodes.py @@ -52,7 +52,7 @@ class ObjectNodeTree(bpy.types.NodeTree): @classmethod def get_from_context(cls, context): ob = context.active_object - if ob and ob.type not in {'LAMP'}: + if ob and ob.type not in {'LIGHT'}: ma = ob.active_material if ma is not None: nt_name = ma.node_tree @@ -517,7 +517,7 @@ class PovrayColorImageNode(Node, ObjectNodeTree): im=image split = column.split(percentage=0.8,align=True) split.prop_search(self,"image",context.blend_data,"images",text="") - split.operator("pov.imageopen",text="",icon="FILESEL") + split.operator("pov.imageopen",text="",icon="FILEBROWSER") if im is not None: column.prop(im,"source",text="") column.prop(self,"map_type",text="") @@ -535,7 +535,7 @@ class PovrayColorImageNode(Node, ObjectNodeTree): im=image split = column.split(percentage=0.8,align=True) split.prop_search(self,"image",context.blend_data,"images",text="") - split.operator("pov.imageopen",text="",icon="FILESEL") + split.operator("pov.imageopen",text="",icon="FILEBROWSER") if im is not None: column.prop(im,"source",text="") column.prop(self,"map_type",text="") @@ -597,7 +597,7 @@ class PovrayBumpMapNode(Node, ObjectNodeTree): im=image split = column.split(percentage=0.8,align=True) split.prop_search(self,"image",context.blend_data,"images",text="") - split.operator("pov.imageopen",text="",icon="FILESEL") + split.operator("pov.imageopen",text="",icon="FILEBROWSER") if im is not None: column.prop(im,"source",text="") column.prop(self,"map_type",text="") @@ -613,7 +613,7 @@ class PovrayBumpMapNode(Node, ObjectNodeTree): im=image split = column.split(percentage=0.8,align=True) split.prop_search(self,"image",context.blend_data,"images",text="") - split.operator("pov.imageopen",text="",icon="FILESEL") + split.operator("pov.imageopen",text="",icon="FILEBROWSER") if im is not None: column.prop(im,"source",text="") column.prop(self,"map_type",text="") @@ -668,7 +668,7 @@ class PovrayImagePatternNode(Node, ObjectNodeTree): im=image split = column.split(percentage=0.8,align=True) split.prop_search(self,"image",context.blend_data,"images",text="") - split.operator("pov.imageopen",text="",icon="FILESEL") + split.operator("pov.imageopen",text="",icon="FILEBROWSER") if im is not None: column.prop(im,"source",text="") column.prop(self,"map_type",text="") @@ -687,7 +687,7 @@ class PovrayImagePatternNode(Node, ObjectNodeTree): im=image split = column.split(percentage=0.8,align=True) split.prop_search(self,"image",context.blend_data,"images",text="") - split.operator("pov.imageopen",text="",icon="FILESEL") + split.operator("pov.imageopen",text="",icon="FILEBROWSER") if im is not None: column.prop(im,"source",text="") column.prop(self,"map_type",text="") diff --git a/render_povray/primitives.py b/render_povray/primitives.py index d7dfe663..1ae43c1a 100644 --- a/render_povray/primitives.py +++ b/render_povray/primitives.py @@ -1052,7 +1052,7 @@ class POVRAY_OT_rainbow_add(bpy.types.Operator): def execute(self,context): cam = context.scene.camera - bpy.ops.object.lamp_add(type='SPOT', radius=1) + bpy.ops.object.light_add(type='SPOT', radius=1) ob = context.object ob.data.show_cone = False ob.data.spot_blend = 0.5 diff --git a/render_povray/render.py b/render_povray/render.py index 26352b2d..1e27beab 100644 --- a/render_povray/render.py +++ b/render_povray/render.py @@ -2067,7 +2067,7 @@ def write_pov(filename, scene=None, info_callback=None): # XXX I moved all those checks here, as there is no need to compute names # for object we won't export here! - if (ob.type in {'LAMP', 'CAMERA', #'EMPTY', #empties can bear dupligroups + if (ob.type in {'LIGHT', 'CAMERA', #'EMPTY', #empties can bear dupligroups 'META', 'ARMATURE', 'LATTICE'}): continue smokeFlag=False @@ -2111,7 +2111,7 @@ def write_pov(filename, scene=None, info_callback=None): strandShape = 0.0 # Set the number of particles to render count rather than 3d view display pSys.set_resolution(scene, ob, 'RENDER') - steps = pSys.settings.draw_step + steps = pSys.settings.display_step steps = 3 ** steps # or (power of 2 rather than 3) + 1 # Formerly : len(particle.hair_keys) totalNumberOfHairs = ( len(pSys.particles) + len(pSys.child_particles) ) @@ -2163,7 +2163,7 @@ def write_pov(filename, scene=None, info_callback=None): elif step == 0: hDiameter = strandStart else: - hDiameter += (strandEnd-strandStart)/(pSys.settings.draw_step+1) #XXX +1 or not? + hDiameter += (strandEnd-strandStart)/(pSys.settings.display_step+1) #XXX +1 or not? if step == 0 and pSys.settings.use_hair_bspline: # Write three times the first point to compensate pov Bezier handling file.write('<%.6g,%.6g,%.6g>,%.7g,\n' % (co[0], co[1], co[2], abs(hDiameter))) @@ -2266,10 +2266,10 @@ def write_pov(filename, scene=None, info_callback=None): if ob.data: name_orig = "OB" + ob.name dataname_orig = "DATA" + ob.data.name - elif ob.is_duplicator: - if ob.dupli_type == 'GROUP': + elif ob.is_instancer: + if ob.instance_type == 'COLLECTION': name_orig = "OB" + ob.name - dataname_orig = "DATA" + ob.dupli_group.name + dataname_orig = "DATA" + ob.instance_collection.name else: #hoping only dupligroups have several source datablocks ob.dupli_list_create(scene) @@ -2671,7 +2671,7 @@ def write_pov(filename, scene=None, info_callback=None): ############################################else try to export mesh - elif ob.is_duplicator == False: #except duplis which should be instances groups for now but all duplis later + elif ob.is_instancer == False: #except duplis which should be instances groups for now but all duplis later if ob.type == 'EMPTY': tabWrite("\n//dummy sphere to represent Empty location\n") tabWrite("#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n" % povdataname) @@ -2689,7 +2689,7 @@ def write_pov(filename, scene=None, info_callback=None): importance = ob.pov.importance_value if me: me_materials = me.materials - me_faces = me.tessfaces[:] + me_faces = me.loop_triangles[:] #if len(me_faces)==0: #tabWrite("\n//dummy sphere to represent empty mesh location\n") #tabWrite("#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n" % povdataname) @@ -2700,16 +2700,16 @@ def write_pov(filename, scene=None, info_callback=None): tabWrite("#declare %s =sphere {<0, 0, 0>,0 pigment{rgbt 1} no_image no_reflection no_radiosity photons{pass_through collect off} hollow}\n" % povdataname) continue - uv_textures = me.tessface_uv_textures - if len(uv_textures) > 0: - if me.uv_textures.active and uv_textures.active.data: - uv_layer = uv_textures.active.data + uv_layers = me.uv_layers + if len(uv_layers) > 0: + if me.uv_layers.active and uv_layers.active.data: + uv_layer = uv_layers.active.data else: uv_layer = None try: #vcol_layer = me.vertex_colors.active.data - vcol_layer = me.tessface_vertex_colors.active.data + vcol_layer = me.vertex_colors.active.data except AttributeError: vcol_layer = None @@ -2717,9 +2717,6 @@ def write_pov(filename, scene=None, info_callback=None): faces_normals = [f.normal[:] for f in me_faces] verts_normals = [v.normal[:] for v in me.vertices] - # quads incur an extra face - quadCount = sum(1 for f in faces_verts if len(f) == 4) - # Use named declaration to allow reference e.g. for baking. MR file.write("\n") tabWrite("#declare %s =\n" % povdataname) @@ -2775,12 +2772,8 @@ def write_pov(filename, scene=None, info_callback=None): # Generate unique UV's uniqueUVs = {} #n = 0 - for fi, uv in enumerate(uv_layer): - - if len(faces_verts[fi]) == 4: - uvs = uv_layer[fi].uv[0], uv_layer[fi].uv[1], uv_layer[fi].uv[2], uv_layer[fi].uv[3] - else: - uvs = uv_layer[fi].uv[0], uv_layer[fi].uv[1], uv_layer[fi].uv[2] + for f in me.faces: + uvs = [uv_layer[l].uv[:] for l in f.loops] for uv in uvs: uniqueUVs[uv[:]] = [-1] @@ -2811,7 +2804,7 @@ def write_pov(filename, scene=None, info_callback=None): if me.vertex_colors: #Write down vertex colors as a texture for each vertex tabWrite("texture_list {\n") - tabWrite("%d\n" % (((len(me_faces)-quadCount) * 3 )+ quadCount * 4)) # works only with tris and quad mesh for now + tabWrite("%d\n" % (len(me_faces) * 3)) # assumes we have only triangles VcolIdx=0 if comments: file.write("\n //Vertex colors: one simple pigment texture per vertex\n") @@ -2824,12 +2817,7 @@ def write_pov(filename, scene=None, info_callback=None): material = None if material: #and material.use_vertex_color_paint: #Always use vertex color when there is some for now - col = vcol_layer[fi] - - if len(faces_verts[fi]) == 4: - cols = col.color1, col.color2, col.color3, col.color4 - else: - cols = col.color1, col.color2, col.color3 + cols = [vcol_layer[l].color[:] for l in f.loops] for col in cols: key = col[0], col[1], col[2], material_index # Material index! @@ -2857,135 +2845,107 @@ def write_pov(filename, scene=None, info_callback=None): tabWrite("\n}\n") # Face indices tabWrite("\nface_indices {\n") - tabWrite("%d" % (len(me_faces) + quadCount)) # faces count + tabWrite("%d" % (len(me_faces))) # faces count tabStr = tab * tabLevel for fi, f in enumerate(me_faces): fv = faces_verts[fi] material_index = f.material_index - if len(fv) == 4: - indices = (0, 1, 2), (0, 2, 3) - else: - indices = ((0, 1, 2),) if vcol_layer: - col = vcol_layer[fi] - - if len(fv) == 4: - cols = col.color1, col.color2, col.color3, col.color4 - else: - cols = col.color1, col.color2, col.color3 + cols = [vcol_layer[l].color[:] for l in f.loops] if not me_materials or me_materials[material_index] is None: # No materials - for i1, i2, i3 in indices: - if linebreaksinlists: - file.write(",\n") - # vert count - file.write(tabStr + "<%d,%d,%d>" % (fv[i1], fv[i2], fv[i3])) - else: - file.write(", ") - file.write("<%d,%d,%d>" % (fv[i1], fv[i2], fv[i3])) # vert count + if linebreaksinlists: + file.write(",\n") + # vert count + file.write(tabStr + "<%d,%d,%d>" % (fv[0], fv[1], fv[2])) + else: + file.write(", ") + file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2])) # vert count else: material = me_materials[material_index] - for i1, i2, i3 in indices: - if me.vertex_colors: #and material.use_vertex_color_paint: - # Color per vertex - vertex color + if me.vertex_colors: #and material.use_vertex_color_paint: + # Color per vertex - vertex color - col1 = cols[i1] - col2 = cols[i2] - col3 = cols[i3] + col1 = cols[0] + col2 = cols[1] + col3 = cols[2] - ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0] - ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0] - ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0] - else: - # Color per material - flat material color - if material.subsurface_scattering.use: - diffuse_color = [i * j for i, j in zip(material.subsurface_scattering.color[:], material.diffuse_color[:])] - else: - diffuse_color = material.diffuse_color[:] - ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], \ - diffuse_color[2], f.material_index][0] - # ci are zero based index so we'll subtract 1 from them - if linebreaksinlists: - file.write(",\n") - file.write(tabStr + "<%d,%d,%d>, %d,%d,%d" % \ - (fv[i1], fv[i2], fv[i3], ci1-1, ci2-1, ci3-1)) # vert count + ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0] + ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0] + ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0] + else: + # Color per material - flat material color + if material.subsurface_scattering.use: + diffuse_color = [i * j for i, j in zip(material.subsurface_scattering.color[:], material.diffuse_color[:])] else: - file.write(", ") - file.write("<%d,%d,%d>, %d,%d,%d" % \ - (fv[i1], fv[i2], fv[i3], ci1-1, ci2-1, ci3-1)) # vert count + diffuse_color = material.diffuse_color[:] + ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], \ + diffuse_color[2], f.material_index][0] + # ci are zero based index so we'll subtract 1 from them + if linebreaksinlists: + file.write(",\n") + file.write(tabStr + "<%d,%d,%d>, %d,%d,%d" % \ + (fv[0], fv[1], fv[2], ci1-1, ci2-1, ci3-1)) # vert count + else: + file.write(", ") + file.write("<%d,%d,%d>, %d,%d,%d" % \ + (fv[0], fv[1], fv[2], ci1-1, ci2-1, ci3-1)) # vert count file.write("\n") tabWrite("}\n") # normal_indices indices tabWrite("normal_indices {\n") - tabWrite("%d" % (len(me_faces) + quadCount)) # faces count + tabWrite("%d" % (len(me_faces))) # faces count tabStr = tab * tabLevel for fi, fv in enumerate(faces_verts): - if len(fv) == 4: - indices = (0, 1, 2), (0, 2, 3) + if me_faces[fi].use_smooth: + if linebreaksinlists: + file.write(",\n") + file.write(tabStr + "<%d,%d,%d>" %\ + (uniqueNormals[verts_normals[fv[0]]][0],\ + uniqueNormals[verts_normals[fv[1]]][0],\ + uniqueNormals[verts_normals[fv[2]]][0])) # vert count + else: + file.write(", ") + file.write("<%d,%d,%d>" %\ + (uniqueNormals[verts_normals[fv[0]]][0],\ + uniqueNormals[verts_normals[fv[1]]][0],\ + uniqueNormals[verts_normals[fv[2]]][0])) # vert count else: - indices = ((0, 1, 2),) - - for i1, i2, i3 in indices: - if me_faces[fi].use_smooth: - if linebreaksinlists: - file.write(",\n") - file.write(tabStr + "<%d,%d,%d>" %\ - (uniqueNormals[verts_normals[fv[i1]]][0],\ - uniqueNormals[verts_normals[fv[i2]]][0],\ - uniqueNormals[verts_normals[fv[i3]]][0])) # vert count - else: - file.write(", ") - file.write("<%d,%d,%d>" %\ - (uniqueNormals[verts_normals[fv[i1]]][0],\ - uniqueNormals[verts_normals[fv[i2]]][0],\ - uniqueNormals[verts_normals[fv[i3]]][0])) # vert count + idx = uniqueNormals[faces_normals[fi]][0] + if linebreaksinlists: + file.write(",\n") + file.write(tabStr + "<%d,%d,%d>" % (idx, idx, idx)) # vert count else: - idx = uniqueNormals[faces_normals[fi]][0] - if linebreaksinlists: - file.write(",\n") - file.write(tabStr + "<%d,%d,%d>" % (idx, idx, idx)) # vert count - else: - file.write(", ") - file.write("<%d,%d,%d>" % (idx, idx, idx)) # vert count + file.write(", ") + file.write("<%d,%d,%d>" % (idx, idx, idx)) # vert count file.write("\n") tabWrite("}\n") if uv_layer: tabWrite("uv_indices {\n") - tabWrite("%d" % (len(me_faces) + quadCount)) # faces count + tabWrite("%d" % (len(me_faces))) # faces count tabStr = tab * tabLevel - for fi, fv in enumerate(faces_verts): - - if len(fv) == 4: - indices = (0, 1, 2), (0, 2, 3) - else: - indices = ((0, 1, 2),) - - uv = uv_layer[fi] - if len(faces_verts[fi]) == 4: - uvs = uv.uv[0][:], uv.uv[1][:], uv.uv[2][:], uv.uv[3][:] + for f in me_faces: + uvs = [uv_layer[l].uv[:] for l in f.loops] + + if linebreaksinlists: + file.write(",\n") + file.write(tabStr + "<%d,%d,%d>" % ( + uniqueUVs[uvs[0]][0],\ + uniqueUVs[uvs[1]][0],\ + uniqueUVs[uvs[2]][0])) else: - uvs = uv.uv[0][:], uv.uv[1][:], uv.uv[2][:] - - for i1, i2, i3 in indices: - if linebreaksinlists: - file.write(",\n") - file.write(tabStr + "<%d,%d,%d>" % ( - uniqueUVs[uvs[i1]][0],\ - uniqueUVs[uvs[i2]][0],\ - uniqueUVs[uvs[i3]][0])) - else: - file.write(", ") - file.write("<%d,%d,%d>" % ( - uniqueUVs[uvs[i1]][0],\ - uniqueUVs[uvs[i2]][0],\ - uniqueUVs[uvs[i3]][0])) + file.write(", ") + file.write("<%d,%d,%d>" % ( + uniqueUVs[uvs[0]][0],\ + uniqueUVs[uvs[1]][0],\ + uniqueUVs[uvs[2]][0])) file.write("\n") tabWrite("}\n") @@ -3108,140 +3068,111 @@ def write_pov(filename, scene=None, info_callback=None): # Face indices tabWrite("face_indices {\n") - tabWrite("%d" % (len(me_faces) + quadCount)) # faces count + tabWrite("%d" % (len(me_faces))) # faces count tabStr = tab * tabLevel for fi, f in enumerate(me_faces): fv = faces_verts[fi] material_index = f.material_index - if len(fv) == 4: - indices = (0, 1, 2), (0, 2, 3) - else: - indices = ((0, 1, 2),) if vcol_layer: - col = vcol_layer[fi] - - if len(fv) == 4: - cols = col.color1, col.color2, col.color3, col.color4 - else: - cols = col.color1, col.color2, col.color3 + cols = [vcol_layer[l].color[:] for l in f.loops] if not me_materials or me_materials[material_index] is None: # No materials - for i1, i2, i3 in indices: - if linebreaksinlists: - file.write(",\n") - # vert count - file.write(tabStr + "<%d,%d,%d>" % (fv[i1], fv[i2], fv[i3])) - else: - file.write(", ") - file.write("<%d,%d,%d>" % (fv[i1], fv[i2], fv[i3])) # vert count + if linebreaksinlists: + file.write(",\n") + # vert count + file.write(tabStr + "<%d,%d,%d>" % (fv[0], fv[1], fv[2])) + else: + file.write(", ") + file.write("<%d,%d,%d>" % (fv[0], fv[1], fv[2])) # vert count else: material = me_materials[material_index] - for i1, i2, i3 in indices: - ci1 = ci2 = ci3 = f.material_index - if me.vertex_colors: #and material.use_vertex_color_paint: - # Color per vertex - vertex color - - col1 = cols[i1] - col2 = cols[i2] - col3 = cols[i3] - - ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0] - ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0] - ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0] - elif material.pov.material_use_nodes: - ci1 = ci2 = ci3 = 0 + ci1 = ci2 = ci3 = f.material_index + if me.vertex_colors: #and material.use_vertex_color_paint: + # Color per vertex - vertex color + + col1 = cols[0] + col2 = cols[1] + col3 = cols[2] + + ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0] + ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0] + ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0] + elif material.pov.material_use_nodes: + ci1 = ci2 = ci3 = 0 + else: + # Color per material - flat material color + if material.subsurface_scattering.use: + diffuse_color = [i * j for i, j in + zip(material.subsurface_scattering.color[:], + material.diffuse_color[:])] else: - # Color per material - flat material color - if material.subsurface_scattering.use: - diffuse_color = [i * j for i, j in - zip(material.subsurface_scattering.color[:], - material.diffuse_color[:])] - else: - diffuse_color = material.diffuse_color[:] - ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], \ - diffuse_color[2], f.material_index][0] + diffuse_color = material.diffuse_color[:] + ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], \ + diffuse_color[2], f.material_index][0] - if linebreaksinlists: - file.write(",\n") - file.write(tabStr + "<%d,%d,%d>, %d,%d,%d" % \ - (fv[i1], fv[i2], fv[i3], ci1, ci2, ci3)) # vert count - else: - file.write(", ") - file.write("<%d,%d,%d>, %d,%d,%d" % \ - (fv[i1], fv[i2], fv[i3], ci1, ci2, ci3)) # vert count + if linebreaksinlists: + file.write(",\n") + file.write(tabStr + "<%d,%d,%d>, %d,%d,%d" % \ + (fv[0], fv[1], fv[2], ci1, ci2, ci3)) # vert count + else: + file.write(", ") + file.write("<%d,%d,%d>, %d,%d,%d" % \ + (fv[0], fv[1], fv[2], ci1, ci2, ci3)) # vert count file.write("\n") tabWrite("}\n") # normal_indices indices tabWrite("normal_indices {\n") - tabWrite("%d" % (len(me_faces) + quadCount)) # faces count + tabWrite("%d" % (len(me_faces))) # faces count tabStr = tab * tabLevel for fi, fv in enumerate(faces_verts): - - if len(fv) == 4: - indices = (0, 1, 2), (0, 2, 3) + if me_faces[fi].use_smooth: + if linebreaksinlists: + file.write(",\n") + file.write(tabStr + "<%d,%d,%d>" %\ + (uniqueNormals[verts_normals[fv[0]]][0],\ + uniqueNormals[verts_normals[fv[1]]][0],\ + uniqueNormals[verts_normals[fv[2]]][0])) # vert count + else: + file.write(", ") + file.write("<%d,%d,%d>" %\ + (uniqueNormals[verts_normals[fv[0]]][0],\ + uniqueNormals[verts_normals[fv[1]]][0],\ + uniqueNormals[verts_normals[fv[2]]][0])) # vert count else: - indices = ((0, 1, 2),) - - for i1, i2, i3 in indices: - if me_faces[fi].use_smooth: - if linebreaksinlists: - file.write(",\n") - file.write(tabStr + "<%d,%d,%d>" %\ - (uniqueNormals[verts_normals[fv[i1]]][0],\ - uniqueNormals[verts_normals[fv[i2]]][0],\ - uniqueNormals[verts_normals[fv[i3]]][0])) # vert count - else: - file.write(", ") - file.write("<%d,%d,%d>" %\ - (uniqueNormals[verts_normals[fv[i1]]][0],\ - uniqueNormals[verts_normals[fv[i2]]][0],\ - uniqueNormals[verts_normals[fv[i3]]][0])) # vert count + idx = uniqueNormals[faces_normals[fi]][0] + if linebreaksinlists: + file.write(",\n") + file.write(tabStr + "<%d,%d,%d>" % (idx, idx, idx)) # vertcount else: - idx = uniqueNormals[faces_normals[fi]][0] - if linebreaksinlists: - file.write(",\n") - file.write(tabStr + "<%d,%d,%d>" % (idx, idx, idx)) # vertcount - else: - file.write(", ") - file.write("<%d,%d,%d>" % (idx, idx, idx)) # vert count + file.write(", ") + file.write("<%d,%d,%d>" % (idx, idx, idx)) # vert count file.write("\n") tabWrite("}\n") if uv_layer: tabWrite("uv_indices {\n") - tabWrite("%d" % (len(me_faces) + quadCount)) # faces count + tabWrite("%d" % (len(me_faces))) # faces count tabStr = tab * tabLevel - for fi, fv in enumerate(faces_verts): - - if len(fv) == 4: - indices = (0, 1, 2), (0, 2, 3) - else: - indices = ((0, 1, 2),) - - uv = uv_layer[fi] - if len(faces_verts[fi]) == 4: - uvs = uv.uv[0][:], uv.uv[1][:], uv.uv[2][:], uv.uv[3][:] + for f in me_faces: + uvs = [uv_layer[l].uv[:] for l in f.loops] + + if linebreaksinlists: + file.write(",\n") + file.write(tabStr + "<%d,%d,%d>" % ( + uniqueUVs[uvs[0]][0],\ + uniqueUVs[uvs[1]][0],\ + uniqueUVs[uvs[2]][0])) else: - uvs = uv.uv[0][:], uv.uv[1][:], uv.uv[2][:] - - for i1, i2, i3 in indices: - if linebreaksinlists: - file.write(",\n") - file.write(tabStr + "<%d,%d,%d>" % ( - uniqueUVs[uvs[i1]][0],\ - uniqueUVs[uvs[i2]][0],\ - uniqueUVs[uvs[i3]][0])) - else: - file.write(", ") - file.write("<%d,%d,%d>" % ( - uniqueUVs[uvs[i1]][0],\ - uniqueUVs[uvs[i2]][0],\ - uniqueUVs[uvs[i3]][0])) + file.write(", ") + file.write("<%d,%d,%d>" % ( + uniqueUVs[uvs[0]][0],\ + uniqueUVs[uvs[1]][0],\ + uniqueUVs[uvs[2]][0])) file.write("\n") tabWrite("}\n") @@ -3285,7 +3216,7 @@ def write_pov(filename, scene=None, info_callback=None): duplidata_ref = [] for ob in sel: #matrix = global_matrix * ob.matrix_world - if ob.is_duplicator: + if ob.is_instancer: tabWrite("\n//--DupliObjects in %s--\n\n"% ob.name) ob.dupli_list_create(scene) dup = "" @@ -3297,8 +3228,8 @@ def write_pov(filename, scene=None, info_callback=None): for eachduplicate in ob.dupli_list: duplidataname = "OB"+string_strip_hyphen(bpy.path.clean_name(bpy.data.objects[eachduplicate.object.name].data.name)) dup += ("\tobject {\n\t\tDATA%s\n\t\t%s\t}\n" %(string_strip_hyphen(bpy.path.clean_name(bpy.data.objects[eachduplicate.object.name].data.name)), MatrixAsPovString(ob.matrix_world.inverted() * eachduplicate.matrix))) - #add object to a list so that it is not rendered for some dupli_types - if ob.dupli_type not in {'GROUP'} and duplidataname not in duplidata_ref: + #add object to a list so that it is not rendered for some instance_types + if ob.instance_type not in {'COLLECTION'} and duplidataname not in duplidata_ref: duplidata_ref.append(duplidataname) #older key [string_strip_hyphen(bpy.path.clean_name("OB"+ob.name))] dup += "}\n" ob.dupli_list_clear() @@ -3666,11 +3597,11 @@ def write_pov(filename, scene=None, info_callback=None): csg = True sel = renderable_objects(scene) - exportLamps([L for L in sel if (L.type == 'LAMP' and L.pov.object_as != 'RAINBOW')]) + exportLamps([L for L in sel if (L.type == 'LIGHT' and L.pov.object_as != 'RAINBOW')]) if comments: file.write("\n//--Rainbows--\n\n") - exportRainbows([L for L in sel if (L.type == 'LAMP' and L.pov.object_as == 'RAINBOW')]) + exportRainbows([L for L in sel if (L.type == 'LIGHT' and L.pov.object_as == 'RAINBOW')]) if comments: diff --git a/render_povray/ui.py b/render_povray/ui.py index 34123794..1d5a621f 100644 --- a/render_povray/ui.py +++ b/render_povray/ui.py @@ -214,15 +214,12 @@ del properties_data_modifier from bl_ui import properties_material for member in dir(properties_material): subclass = getattr(properties_material, member) - if subclass not in (properties_material.MATERIAL_PT_transp_game, - properties_material.MATERIAL_PT_game_settings, - properties_material.MATERIAL_PT_physics): - try: - #mat=context.material - #if mat and mat.type == "SURFACE" and (engine in cls.COMPAT_ENGINES) and not (mat.pov.material_use_nodes or mat.use_nodes): - subclass.COMPAT_ENGINES.add('POVRAY_RENDER') - except: - pass + try: + #mat=context.material + #if mat and mat.type == "SURFACE" and (engine in cls.COMPAT_ENGINES) and not (mat.pov.material_use_nodes or mat.use_nodes): + subclass.COMPAT_ENGINES.add('POVRAY_RENDER') + except: + pass del properties_material @@ -294,7 +291,7 @@ class RenderButtonsPanel(): @classmethod def poll(cls, context): rd = context.scene.render - return (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES) + return (rd.engine in cls.COMPAT_ENGINES) class ModifierButtonsPanel(): bl_space_type = 'PROPERTIES' @@ -306,7 +303,7 @@ class ModifierButtonsPanel(): def poll(cls, context): mods = context.object.modifiers rd = context.scene.render - return mods and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES) + return mods and (rd.engine in cls.COMPAT_ENGINES) class MaterialButtonsPanel(): bl_space_type = 'PROPERTIES' @@ -318,7 +315,7 @@ class MaterialButtonsPanel(): def poll(cls, context): mat = context.material rd = context.scene.render - return mat and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES) + return mat (rd.engine in cls.COMPAT_ENGINES) class TextureButtonsPanel(): @@ -331,7 +328,7 @@ class TextureButtonsPanel(): def poll(cls, context): tex = context.texture rd = context.scene.render - return tex and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES) + return tex and (rd.engine in cls.COMPAT_ENGINES) # class TextureTypePanel(TextureButtonsPanel): @@ -352,7 +349,7 @@ class ObjectButtonsPanel(): def poll(cls, context): obj = context.object rd = context.scene.render - return obj and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES) + return obj and (rd.engine in cls.COMPAT_ENGINES) class CameraDataButtonsPanel(): bl_space_type = 'PROPERTIES' @@ -364,7 +361,7 @@ class CameraDataButtonsPanel(): def poll(cls, context): cam = context.camera rd = context.scene.render - return cam and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES) + return cam and (rd.engine in cls.COMPAT_ENGINES) class WorldButtonsPanel(): bl_space_type = 'PROPERTIES' @@ -376,7 +373,7 @@ class WorldButtonsPanel(): def poll(cls, context): wld = context.world rd = context.scene.render - return wld and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES) + return wld and (rd.engine in cls.COMPAT_ENGINES) class TextButtonsPanel(): bl_space_type = 'TEXT_EDITOR' @@ -388,7 +385,7 @@ class TextButtonsPanel(): def poll(cls, context): text = context.space_data rd = context.scene.render - return text and (rd.use_game_engine is False) and (rd.engine in cls.COMPAT_ENGINES) + return text and (rd.engine in cls.COMPAT_ENGINES) from bl_ui import properties_data_mesh # These panels are kept @@ -464,25 +461,25 @@ del properties_data_mesh ################################################################################ -# from bl_ui import properties_data_lamp -# for member in dir(properties_data_lamp): - # subclass = getattr(properties_data_lamp, member) +# from bl_ui import properties_data_light +# for member in dir(properties_data_light): + # subclass = getattr(properties_data_light, member) # try: # subclass.COMPAT_ENGINES.add('POVRAY_RENDER') # except: # pass -# del properties_data_lamp -#########################LAMPS################################ +# del properties_data_light +#########################LIGHTS################################ -from bl_ui import properties_data_lamp +from bl_ui import properties_data_light # These panels are kept -properties_data_lamp.DATA_PT_custom_props_lamp.COMPAT_ENGINES.add('POVRAY_RENDER') -properties_data_lamp.DATA_PT_context_lamp.COMPAT_ENGINES.add('POVRAY_RENDER') +properties_data_light.DATA_PT_custom_props_light.COMPAT_ENGINES.add('POVRAY_RENDER') +properties_data_light.DATA_PT_context_light.COMPAT_ENGINES.add('POVRAY_RENDER') ## make some native panels contextual to some object variable ## by recreating custom panels inheriting their properties -class PovLampButtonsPanel(properties_data_lamp.DataButtonsPanel): +class PovLampButtonsPanel(properties_data_light.DataButtonsPanel): COMPAT_ENGINES = {'POVRAY_RENDER'} POV_OBJECT_TYPES = {'RAINBOW'} @@ -499,17 +496,17 @@ class PovLampButtonsPanel(properties_data_lamp.DataButtonsPanel): # Complex py/bpy/rna interactions (with metaclass and all) simply do not allow it to work. # So we simply have to explicitly copy here the interesting bits. ;) -class LAMP_PT_POV_preview(PovLampButtonsPanel, bpy.types.Panel): - bl_label = properties_data_lamp.DATA_PT_preview.bl_label +class LIGHT_PT_POV_preview(PovLampButtonsPanel, bpy.types.Panel): + bl_label = properties_data_light.DATA_PT_preview.bl_label - draw = properties_data_lamp.DATA_PT_preview.draw + draw = properties_data_light.DATA_PT_preview.draw -class LAMP_PT_POV_lamp(PovLampButtonsPanel, bpy.types.Panel): - bl_label = properties_data_lamp.DATA_PT_lamp.bl_label +class LIGHT_PT_POV_light(PovLampButtonsPanel, bpy.types.Panel): + bl_label = properties_data_light.DATA_PT_light.bl_label - draw = properties_data_lamp.DATA_PT_lamp.draw + draw = properties_data_light.DATA_PT_light.draw -class POV_LAMP_MT_presets(bpy.types.Menu): +class POV_LIGHT_MT_presets(bpy.types.Menu): bl_label = "Lamp Presets" preset_subdir = "pov/lamp" preset_operator = "script.execute_preset" @@ -518,19 +515,19 @@ class POV_LAMP_MT_presets(bpy.types.Menu): class AddPresetLamp(AddPresetBase, bpy.types.Operator): '''Add a Lamp Preset''' - bl_idname = "object.lamp_preset_add" + bl_idname = "object.light_preset_add" bl_label = "Add Lamp Preset" - preset_menu = "POV_LAMP_MT_presets" + preset_menu = "POV_LIGHT_MT_presets" # variable used for all preset values preset_defines = [ - "lampdata = bpy.context.object.data" + "lightdata = bpy.context.object.data" ] # properties to store in the preset preset_values = [ - "lampdata.type", - "lampdata.color", + "lightdata.type", + "lightdata.color", ] # where to store the preset @@ -541,68 +538,68 @@ class AddPresetLamp(AddPresetBase, bpy.types.Operator): # Draw into an existing panel -def lamp_panel_func(self, context): +def light_panel_func(self, context): layout = self.layout row = layout.row(align=True) - row.menu(POV_LAMP_MT_presets.__name__, text=POV_LAMP_MT_presets.bl_label) + row.menu(POV_LIGHT_MT_presets.__name__, text=POV_LIGHT_MT_presets.bl_label) row.operator(AddPresetLamp.bl_idname, text="", icon='ZOOMIN') row.operator(AddPresetLamp.bl_idname, text="", icon='ZOOMOUT').remove_active = True classes = ( - POV_LAMP_MT_presets, + POV_LIGHT_MT_presets, AddPresetLamp, ) -class LAMP_PT_POV_sunsky(PovLampButtonsPanel, bpy.types.Panel): - bl_label = properties_data_lamp.DATA_PT_sunsky.bl_label +class LIGHT_PT_POV_sunsky(PovLampButtonsPanel, bpy.types.Panel): + bl_label = properties_data_light.DATA_PT_sunsky.bl_label @classmethod def poll(cls, context): - lamp = context.lamp + lamp = context.light engine = context.scene.render.engine return (lamp and lamp.type == 'SUN') and (engine in cls.COMPAT_ENGINES) - draw = properties_data_lamp.DATA_PT_sunsky.draw + draw = properties_data_light.DATA_PT_sunsky.draw -class LAMP_PT_POV_shadow(PovLampButtonsPanel, bpy.types.Panel): - bl_label = properties_data_lamp.DATA_PT_shadow.bl_label +class LIGHT_PT_POV_shadow(PovLampButtonsPanel, bpy.types.Panel): + bl_label = properties_data_light.DATA_PT_shadow.bl_label - draw = properties_data_lamp.DATA_PT_shadow.draw + draw = properties_data_light.DATA_PT_shadow.draw -class LAMP_PT_POV_area(PovLampButtonsPanel, bpy.types.Panel): - bl_label = properties_data_lamp.DATA_PT_area.bl_label +class LIGHT_PT_POV_area(PovLampButtonsPanel, bpy.types.Panel): + bl_label = properties_data_light.DATA_PT_area.bl_label @classmethod def poll(cls, context): - lamp = context.lamp + lamp = context.light engine = context.scene.render.engine return (lamp and lamp.type == 'AREA') and (engine in cls.COMPAT_ENGINES) - draw = properties_data_lamp.DATA_PT_area.draw + draw = properties_data_light.DATA_PT_area.draw -class LAMP_PT_POV_spot(PovLampButtonsPanel, bpy.types.Panel): - bl_label = properties_data_lamp.DATA_PT_spot.bl_label +class LIGHT_PT_POV_spot(PovLampButtonsPanel, bpy.types.Panel): + bl_label = properties_data_light.DATA_PT_spot.bl_label @classmethod def poll(cls, context): - lamp = context.lamp + lamp = context.light engine = context.scene.render.engine return (lamp and lamp.type == 'SPOT') and (engine in cls.COMPAT_ENGINES) - draw = properties_data_lamp.DATA_PT_spot.draw + draw = properties_data_light.DATA_PT_spot.draw -class LAMP_PT_POV_falloff_curve(PovLampButtonsPanel, bpy.types.Panel): - bl_label = properties_data_lamp.DATA_PT_falloff_curve.bl_label - bl_options = properties_data_lamp.DATA_PT_falloff_curve.bl_options +class LIGHT_PT_POV_falloff_curve(PovLampButtonsPanel, bpy.types.Panel): + bl_label = properties_data_light.DATA_PT_falloff_curve.bl_label + bl_options = properties_data_light.DATA_PT_falloff_curve.bl_options @classmethod def poll(cls, context): - lamp = context.lamp + lamp = context.light engine = context.scene.render.engine return (lamp and lamp.type in {'POINT', 'SPOT'} and lamp.falloff_type == 'CUSTOM_CURVE') and (engine in cls.COMPAT_ENGINES) - draw = properties_data_lamp.DATA_PT_falloff_curve.draw + draw = properties_data_light.DATA_PT_falloff_curve.draw class OBJECT_PT_povray_obj_rainbow(PovLampButtonsPanel, bpy.types.Panel): bl_label = "POV-Ray Rainbow" @@ -644,7 +641,7 @@ class OBJECT_PT_povray_obj_rainbow(PovLampButtonsPanel, bpy.types.Panel): col.prop(obj.pov, "arc_angle") col.prop(obj.pov, "falloff_angle") -del properties_data_lamp +del properties_data_light ############################################################################### class RENDER_PT_povray_export_settings(RenderButtonsPanel, bpy.types.Panel): diff --git a/rigify/__init__.py b/rigify/__init__.py index fa93533b..302fc453 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -22,7 +22,7 @@ bl_info = { "name": "Rigify", "version": (0, 5), "author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello", - "blender": (2, 78, 0), + "blender": (2, 80, 0), "description": "Automatic rigging from building-block components", "location": "Armature properties, Bone properties, View3d tools panel, Armature Add menu", "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/" @@ -44,7 +44,15 @@ import bpy import sys import os from bpy.types import AddonPreferences -from bpy.props import BoolProperty +from bpy.props import ( + BoolProperty, + IntProperty, + EnumProperty, + StringProperty, + FloatVectorProperty, + PointerProperty, + CollectionProperty, +) class RigifyPreferences(AddonPreferences): @@ -118,14 +126,13 @@ class RigifyPreferences(AddonPreferences): register() - legacy_mode = BoolProperty( + legacy_mode: BoolProperty( name='Rigify Legacy Mode', description='Select if you want to use Rigify in legacy mode', default=False, update=update_legacy ) - - show_expanded = BoolProperty() + show_expanded: BoolProperty() def draw(self, context): layout = self.layout @@ -143,71 +150,80 @@ class RigifyPreferences(AddonPreferences): op = sub.operator('wm.context_toggle', text='', icon=icon, emboss=False) op.data_path = 'addon_prefs.show_expanded' - sub.label('{}: {}'.format('Rigify', 'Enable Legacy Mode')) + sub.label(text='{}: {}'.format('Rigify', 'Enable Legacy Mode')) sub = row.row() sub.alignment = 'RIGHT' sub.prop(self, 'legacy_mode') if expand: - split = col.row().split(percentage=0.15) - split.label('Description:') + split = col.row().split(factor=0.15) + split.label(text='Description:') split.label(text='When enabled the add-on will run in legacy mode using the old 2.76b feature set.') row = layout.row() - row.label("End of Rigify Preferences") + row.label(text="End of Rigify Preferences") class RigifyName(bpy.types.PropertyGroup): - name = bpy.props.StringProperty() + name: StringProperty() class RigifyColorSet(bpy.types.PropertyGroup): - name = bpy.props.StringProperty(name="Color Set", default=" ") - active = bpy.props.FloatVectorProperty( - name="object_color", - subtype='COLOR', - default=(1.0, 1.0, 1.0), - min=0.0, max=1.0, - description="color picker" - ) - normal = bpy.props.FloatVectorProperty( - name="object_color", - subtype='COLOR', - default=(1.0, 1.0, 1.0), - min=0.0, max=1.0, - description="color picker" - ) - select = bpy.props.FloatVectorProperty( - name="object_color", - subtype='COLOR', - default=(1.0, 1.0, 1.0), - min=0.0, max=1.0, - description="color picker" - ) - standard_colors_lock = bpy.props.BoolProperty(default=True) + name: StringProperty(name="Color Set", default=" ") + active: FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(1.0, 1.0, 1.0), + min=0.0, max=1.0, + description="color picker" + ) + normal: FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(1.0, 1.0, 1.0), + min=0.0, max=1.0, + description="color picker" + ) + select: FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(1.0, 1.0, 1.0), + min=0.0, max=1.0, + description="color picker" + ) + standard_colors_lock: BoolProperty(default=True) class RigifySelectionColors(bpy.types.PropertyGroup): - select = bpy.props.FloatVectorProperty( - name="object_color", - subtype='COLOR', - default=(0.314, 0.784, 1.0), - min=0.0, max=1.0, - description="color picker" - ) + select: FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(0.314, 0.784, 1.0), + min=0.0, max=1.0, + description="color picker" + ) - active = bpy.props.FloatVectorProperty( - name="object_color", - subtype='COLOR', - default=(0.549, 1.0, 1.0), - min=0.0, max=1.0, - description="color picker" - ) + active: FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(0.549, 1.0, 1.0), + min=0.0, max=1.0, + description="color picker" + ) class RigifyParameters(bpy.types.PropertyGroup): - name = bpy.props.StringProperty() + name: StringProperty() + + +# Remember the initial property set +RIGIFY_PARAMETERS_BASE_DIR = set(dir(RigifyParameters)) + +def clear_rigify_parameters(): + for name in list(dir(RigifyParameters)): + if name not in RIGIFY_PARAMETERS_BASE_DIR: + delattr(RigifyParameters, name) class RigifyArmatureLayer(bpy.types.PropertyGroup): @@ -225,104 +241,123 @@ class RigifyArmatureLayer(bpy.types.PropertyGroup): else: self['group_prop'] = value - name = bpy.props.StringProperty(name="Layer Name", default=" ") - row = bpy.props.IntProperty(name="Layer Row", default=1, min=1, max=32, description='UI row for this layer') - set = bpy.props.BoolProperty(name="Selection Set", default=False, description='Add Selection Set for this layer') - group = bpy.props.IntProperty(name="Bone Group", default=0, min=0, max=32, - get=get_group, set=set_group, description='Assign Bone Group to this layer') + name: StringProperty(name="Layer Name", default=" ") + row: IntProperty(name="Layer Row", default=1, min=1, max=32, description='UI row for this layer') + selset: BoolProperty(name="Selection Set", default=False, description='Add Selection Set for this layer') + group: IntProperty(name="Bone Group", default=0, min=0, max=32, + get=get_group, set=set_group, description='Assign Bone Group to this layer') + ##### REGISTER ##### +classes = ( + RigifyName, + RigifyParameters, + RigifyColorSet, + RigifySelectionColors, + RigifyArmatureLayer, + RigifyPreferences, +) + + def register(): + from bpy.utils import register_class + + # Sub-modules. ui.register() metarig_menu.register() - bpy.utils.register_class(RigifyName) - bpy.utils.register_class(RigifyParameters) - - bpy.utils.register_class(RigifyColorSet) - bpy.utils.register_class(RigifySelectionColors) - bpy.utils.register_class(RigifyArmatureLayer) - bpy.utils.register_class(RigifyPreferences) - bpy.types.Armature.rigify_layers = bpy.props.CollectionProperty(type=RigifyArmatureLayer) - - bpy.types.PoseBone.rigify_type = bpy.props.StringProperty(name="Rigify Type", description="Rig type for this bone") - bpy.types.PoseBone.rigify_parameters = bpy.props.PointerProperty(type=RigifyParameters) - - bpy.types.Armature.rigify_colors = bpy.props.CollectionProperty(type=RigifyColorSet) - - bpy.types.Armature.rigify_selection_colors = bpy.props.PointerProperty(type=RigifySelectionColors) - - bpy.types.Armature.rigify_colors_index = bpy.props.IntProperty(default=-1) - bpy.types.Armature.rigify_colors_lock = bpy.props.BoolProperty(default=True) - bpy.types.Armature.rigify_theme_to_add = bpy.props.EnumProperty(items=(('THEME01', 'THEME01', ''), - ('THEME02', 'THEME02', ''), - ('THEME03', 'THEME03', ''), - ('THEME04', 'THEME04', ''), - ('THEME05', 'THEME05', ''), - ('THEME06', 'THEME06', ''), - ('THEME07', 'THEME07', ''), - ('THEME08', 'THEME08', ''), - ('THEME09', 'THEME09', ''), - ('THEME10', 'THEME10', ''), - ('THEME11', 'THEME11', ''), - ('THEME12', 'THEME12', ''), - ('THEME13', 'THEME13', ''), - ('THEME14', 'THEME14', ''), - ('THEME15', 'THEME15', ''), - ('THEME16', 'THEME16', ''), - ('THEME17', 'THEME17', ''), - ('THEME18', 'THEME18', ''), - ('THEME19', 'THEME19', ''), - ('THEME20', 'THEME20', '') - ), name='Theme') + # Classes. + for cls in classes: + register_class(cls) + + # Properties. + bpy.types.Armature.rigify_layers = CollectionProperty(type=RigifyArmatureLayer) + + bpy.types.PoseBone.rigify_type = StringProperty(name="Rigify Type", description="Rig type for this bone") + bpy.types.PoseBone.rigify_parameters = PointerProperty(type=RigifyParameters) + + bpy.types.Armature.rigify_colors = CollectionProperty(type=RigifyColorSet) + + bpy.types.Armature.rigify_selection_colors = PointerProperty(type=RigifySelectionColors) + + bpy.types.Armature.rigify_colors_index = IntProperty(default=-1) + bpy.types.Armature.rigify_colors_lock = BoolProperty(default=True) + bpy.types.Armature.rigify_theme_to_add = EnumProperty(items=( + ('THEME01', 'THEME01', ''), + ('THEME02', 'THEME02', ''), + ('THEME03', 'THEME03', ''), + ('THEME04', 'THEME04', ''), + ('THEME05', 'THEME05', ''), + ('THEME06', 'THEME06', ''), + ('THEME07', 'THEME07', ''), + ('THEME08', 'THEME08', ''), + ('THEME09', 'THEME09', ''), + ('THEME10', 'THEME10', ''), + ('THEME11', 'THEME11', ''), + ('THEME12', 'THEME12', ''), + ('THEME13', 'THEME13', ''), + ('THEME14', 'THEME14', ''), + ('THEME15', 'THEME15', ''), + ('THEME16', 'THEME16', ''), + ('THEME17', 'THEME17', ''), + ('THEME18', 'THEME18', ''), + ('THEME19', 'THEME19', ''), + ('THEME20', 'THEME20', '') + ), name='Theme') IDStore = bpy.types.WindowManager - IDStore.rigify_collection = bpy.props.EnumProperty(items=rig_lists.col_enum_list, default="All", - name="Rigify Active Collection", - description="The selected rig collection") + IDStore.rigify_collection = EnumProperty(items=rig_lists.col_enum_list, default="All", + name="Rigify Active Collection", + description="The selected rig collection") - IDStore.rigify_types = bpy.props.CollectionProperty(type=RigifyName) - IDStore.rigify_active_type = bpy.props.IntProperty(name="Rigify Active Type", description="The selected rig type") + IDStore.rigify_types = CollectionProperty(type=RigifyName) + IDStore.rigify_active_type = IntProperty(name="Rigify Active Type", description="The selected rig type") - IDStore.rigify_advanced_generation = bpy.props.BoolProperty(name="Advanced Options", - description="Enables/disables advanced options for Rigify rig generation", - default=False) + IDStore.rigify_advanced_generation = BoolProperty(name="Advanced Options", + description="Enables/disables advanced options for Rigify rig generation", + default=False) def update_mode(self, context): if self.rigify_generate_mode == 'new': self.rigify_force_widget_update = False - IDStore.rigify_generate_mode = bpy.props.EnumProperty(name="Rigify Generate Rig Mode", - description="'Generate Rig' mode. In 'overwrite' mode the features of the target rig will be updated as defined by the metarig. In 'new' mode a new rig will be created as defined by the metarig. Current mode", - update=update_mode, - items=(('overwrite', 'overwrite', ''), - ('new', 'new', ''))) - - IDStore.rigify_force_widget_update = bpy.props.BoolProperty(name="Force Widget Update", - description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created", - default=False) - - IDStore.rigify_target_rigs = bpy.props.CollectionProperty(type=RigifyName) - IDStore.rigify_target_rig = bpy.props.StringProperty(name="Rigify Target Rig", - description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created", - default="") - - IDStore.rigify_rig_uis = bpy.props.CollectionProperty(type=RigifyName) - IDStore.rigify_rig_ui = bpy.props.StringProperty(name="Rigify Target Rig UI", - description="Defines the UI to overwrite. It should always be the same as the target rig. If unset, 'rig_ui.py' will be used", - default="") - - IDStore.rigify_rig_basename = bpy.props.StringProperty(name="Rigify Rig Name", - description="Defines the name of the Rig. If unset, in 'new' mode 'rig' will be used, in 'overwrite' mode the target rig name will be used", - default="") - - IDStore.rigify_transfer_only_selected = bpy.props.BoolProperty(name="Transfer Only Selected", description="Transfer selected bones only", default=True) - IDStore.rigify_transfer_start_frame = bpy.props.IntProperty(name="Start Frame", description="First Frame to Transfer", default=0, min= 0) - IDStore.rigify_transfer_end_frame = bpy.props.IntProperty(name="End Frame", description="Last Frame to Transfer", default=0, min= 0) - + IDStore.rigify_generate_mode = EnumProperty(name="Rigify Generate Rig Mode", + description="'Generate Rig' mode. In 'overwrite' mode the features of the target rig will be updated as defined by the metarig. In 'new' mode a new rig will be created as defined by the metarig. Current mode", + update=update_mode, + items=( ('overwrite', 'overwrite', ''), + ('new', 'new', ''))) + + IDStore.rigify_force_widget_update = BoolProperty(name="Force Widget Update", + description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created", + default=False) + + IDStore.rigify_target_rigs = CollectionProperty(type=RigifyName) + IDStore.rigify_target_rig = StringProperty(name="Rigify Target Rig", + description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created", + default="") + + IDStore.rigify_rig_uis = CollectionProperty(type=RigifyName) + IDStore.rigify_rig_ui = StringProperty(name="Rigify Target Rig UI", + description="Defines the UI to overwrite. It should always be the same as the target rig. If unset, 'rig_ui.py' will be used", + default="") + + IDStore.rigify_rig_basename = StringProperty(name="Rigify Rig Name", + description="Defines the name of the Rig. If unset, in 'new' mode 'rig' will be used, in 'overwrite' mode the target rig name will be used", + default="") + + IDStore.rigify_transfer_only_selected = BoolProperty( + name="Transfer Only Selected", + description="Transfer selected bones only", default=True) + IDStore.rigify_transfer_start_frame = IntProperty( + name="Start Frame", + description="First Frame to Transfer", default=0, min= 0) + IDStore.rigify_transfer_end_frame = IntProperty( + name="End Frame", + description="Last Frame to Transfer", default=0, min= 0) + + # Update legacy on restart or reload. if (ui and 'legacy' in str(ui)) or bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode: - # update legacy on restart or reload bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode = True # Add rig parameters @@ -335,6 +370,9 @@ def register(): def unregister(): + from bpy.utils import unregister_class + + # Properties. del bpy.types.PoseBone.rigify_type del bpy.types.PoseBone.rigify_parameters @@ -354,14 +392,12 @@ def unregister(): del IDStore.rigify_transfer_start_frame del IDStore.rigify_transfer_end_frame - bpy.utils.unregister_class(RigifyName) - bpy.utils.unregister_class(RigifyParameters) - - bpy.utils.unregister_class(RigifyColorSet) - bpy.utils.unregister_class(RigifySelectionColors) + # Classes. + for cls in classes: + unregister_class(cls) - bpy.utils.unregister_class(RigifyArmatureLayer) - bpy.utils.unregister_class(RigifyPreferences) + clear_rigify_parameters() + # Sub-modules. metarig_menu.unregister() ui.unregister() diff --git a/rigify/generate.py b/rigify/generate.py index e2492708..c804cb69 100644 --- a/rigify/generate.py +++ b/rigify/generate.py @@ -29,6 +29,7 @@ from .utils import MetarigError, new_bone, get_rig_type from .utils import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name from .utils import RIG_DIR from .utils import create_root_widget +from .utils import ensure_widget_collection from .utils import random_id from .utils import copy_attributes from .utils import gamma_correct @@ -40,7 +41,6 @@ ORG_LAYER = [n == 31 for n in range(0, 32)] # Armature layer that original bone MCH_LAYER = [n == 30 for n in range(0, 32)] # Armature layer that mechanism bones should be moved to. DEF_LAYER = [n == 29 for n in range(0, 32)] # Armature layer that deformation bones should be moved to. ROOT_LAYER = [n == 28 for n in range(0, 32)] # Armature layer that root bone should be moved to. -WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer. class Timer: @@ -72,7 +72,11 @@ def generate_rig(context, metarig): bpy.ops.object.mode_set(mode='OBJECT') scene = context.scene + view_layer = context.view_layer + collection = context.collection + layer_collection = context.layer_collection id_store = context.window_manager + #------------------------------------------ # Create/find the rig object and set it up @@ -96,13 +100,13 @@ def generate_rig(context, metarig): rig_old_name = name name = rig_new_name or name obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) - obj.draw_type = 'WIRE' - scene.objects.link(obj) + obj.display_type = 'WIRE' + collection.objects.link(obj) else: name = rig_new_name or "rig" obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) # in case name 'rig' exists it will be rig.001 - obj.draw_type = 'WIRE' - scene.objects.link(obj) + obj.display_type = 'WIRE' + collection.objects.link(obj) id_store.rigify_target_rig = obj.name obj.data.pose_position = 'POSE' @@ -112,23 +116,17 @@ def generate_rig(context, metarig): obj.animation_data_clear() # Select generated rig object - metarig.select = False - obj.select = True - scene.objects.active = obj + metarig.select_set(False) + obj.select_set(True) + view_layer.objects.active = obj # Remove wgts if force update is set wgts_group_name = "WGTS_" + (rig_old_name or obj.name) if wgts_group_name in scene.objects and id_store.rigify_force_widget_update: bpy.ops.object.select_all(action='DESELECT') - for i, lyr in enumerate(WGT_LAYERS): - if lyr: - context.scene.layers[i] = True for wgt in bpy.data.objects[wgts_group_name].children: - wgt.select = True + wgt.select_set(True) bpy.ops.object.delete(use_global=False) - for i, lyr in enumerate(WGT_LAYERS): - if lyr: - context.scene.layers[i] = False if rig_old_name: bpy.data.objects[wgts_group_name].name = "WGTS_" + obj.name @@ -148,18 +146,18 @@ def generate_rig(context, metarig): # Create temporary duplicates for merging temp_rig_1 = metarig.copy() temp_rig_1.data = metarig.data.copy() - scene.objects.link(temp_rig_1) + collection.objects.link(temp_rig_1) temp_rig_2 = metarig.copy() temp_rig_2.data = obj.data - scene.objects.link(temp_rig_2) + collection.objects.link(temp_rig_2) # Select the temp rigs for merging for objt in scene.objects: - objt.select = False # deselect all objects - temp_rig_1.select = True - temp_rig_2.select = True - scene.objects.active = temp_rig_2 + objt.select_set(False) # deselect all objects + temp_rig_1.select_set(True) + temp_rig_2.select_set(True) + view_layer.objects.active = temp_rig_2 # Merge the temporary rigs bpy.ops.object.join() @@ -169,9 +167,9 @@ def generate_rig(context, metarig): # Select the generated rig for objt in scene.objects: - objt.select = False # deselect all objects - obj.select = True - scene.objects.active = obj + objt.select_set(False) # deselect all objects + obj.select_set(True) + view_layer.objects.active = obj # Copy over bone properties for bone in metarig.data.bones: @@ -303,7 +301,8 @@ def generate_rig(context, metarig): rna_idprop_ui_prop_get(obj.data, "rig_id", create=True) obj.data["rig_id"] = rig_id - t.tick("Create root bone: ") + # Create/find widge collection + widget_collection = ensure_widget_collection(context) # Create Group widget # wgts_group_name = "WGTS" @@ -313,21 +312,14 @@ def generate_rig(context, metarig): bpy.data.objects.remove(bpy.data.objects[wgts_group_name]) mesh = bpy.data.meshes.new(wgts_group_name) wgts_obj = bpy.data.objects.new(wgts_group_name, mesh) - scene.objects.link(wgts_obj) - wgts_obj.layers = WGT_LAYERS + widget_collection.objects.link(wgts_obj) t.tick("Create main WGTS: ") # # if id_store.rigify_generate_mode == 'new': # bpy.ops.object.select_all(action='DESELECT') # for wgt in bpy.data.objects[wgts_group_name].children: - # wgt.select = True - # for i, lyr in enumerate(WGT_LAYERS): - # if lyr: - # context.scene.layers[i] = True + # wgt.select_set(True) # bpy.ops.object.make_single_user(obdata=True) - # for i, lyr in enumerate(WGT_LAYERS): - # if lyr: - # context.scene.layers[i] = False #---------------------------------- try: @@ -343,8 +335,8 @@ def generate_rig(context, metarig): for rig in rigs: # Go into editmode in the rig armature bpy.ops.object.mode_set(mode='OBJECT') - context.scene.objects.active = obj - obj.select = True + context.view_layer.objects.active = obj + obj.select_set(True) bpy.ops.object.mode_set(mode='EDIT') scripts = rig.generate() if scripts is not None: @@ -511,18 +503,7 @@ def generate_rig(context, metarig): create_bone_groups(obj, metarig) # Add rig_ui to logic - skip = False - ctrls = obj.game.controllers - - for c in ctrls: - if 'Python' in c.name and c.text.name == script.name: - skip = True - break - if not skip: - bpy.ops.logic.controller_add(type='PYTHON', object=obj.name) - ctrl = obj.game.controllers[-1] - ctrl.text = bpy.data.texts[script.name] - + create_persistent_rig_ui(obj, script) t.tick("The rest: ") #---------------------------------- @@ -538,6 +519,11 @@ def generate_rig(context, metarig): child.parent_bone = sub_parent child.matrix_world = mat + #---------------------------------- + # Restore active collection + view_layer.active_layer_collection = layer_collection + + def create_selection_sets(obj, metarig): # Check if selection sets addon is installed @@ -547,13 +533,13 @@ def create_selection_sets(obj, metarig): bpy.ops.object.mode_set(mode='POSE') - bpy.context.scene.objects.active = obj - obj.select = True - metarig.select = False + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + metarig.select_set(False) pbones = obj.pose.bones for i, name in enumerate(metarig.data.rigify_layers.keys()): - if name == '' or not metarig.data.rigify_layers[i].set: + if name == '' or not metarig.data.rigify_layers[i].selset: continue bpy.ops.pose.select_all(action='DESELECT') @@ -588,7 +574,7 @@ def create_bone_groups(obj, metarig): g_id = l.group - 1 name = groups[g_id].name if name not in obj.pose.bone_groups.keys(): - bg = obj.pose.bone_groups.new(name) + bg = obj.pose.bone_groups.new(name=name) bg.color_set = 'CUSTOM' bg.colors.normal = gamma_correct(groups[g_id].normal) bg.colors.select = gamma_correct(groups[g_id].select) @@ -607,6 +593,31 @@ def create_bone_groups(obj, metarig): b.bone_group = obj.pose.bone_groups[name] +def create_persistent_rig_ui(obj, script): + """Make sure the ui script always follows the rig around""" + skip = False + driver = None + + for fcurve in obj.animation_data.drivers: + if fcurve.data_path == 'pass_index': + driver = fcurve.driver + for variable in driver.variables: + if variable.name == script.name: + skip = True + break + break + + if not skip: + if not driver: + fcurve = obj.driver_add("pass_index") + driver = fcurve.driver + + variable = driver.variables.new() + variable.name = script.name + variable.targets[0].id_type = 'TEXT' + variable.targets[0].id = script + + def get_bone_rigs(obj, bone_name, halt_on_missing=False): """ Fetch all the rigs specified on a bone. """ diff --git a/rigify/legacy/__init__.py b/rigify/legacy/__init__.py index 6e6f751c..029d877e 100644 --- a/rigify/legacy/__init__.py +++ b/rigify/legacy/__init__.py @@ -45,16 +45,16 @@ import bpy class RigifyName(bpy.types.PropertyGroup): - name = bpy.props.StringProperty() + name: bpy.props.StringProperty() class RigifyParameters(bpy.types.PropertyGroup): - name = bpy.props.StringProperty() + name: bpy.props.StringProperty() class RigifyArmatureLayer(bpy.types.PropertyGroup): - name = bpy.props.StringProperty(name="Layer Name", default=" ") - row = bpy.props.IntProperty(name="Layer Row", default=1, min=1, max=32) + name: bpy.props.StringProperty(name="Layer Name", default=" ") + row: bpy.props.IntProperty(name="Layer Row", default=1, min=1, max=32) ##### REGISTER ##### diff --git a/rigify/legacy/generate.py b/rigify/legacy/generate.py index ec822b9e..476cdbe8 100644 --- a/rigify/legacy/generate.py +++ b/rigify/legacy/generate.py @@ -28,7 +28,7 @@ from rna_prop_ui import rna_idprop_ui_prop_get from .utils import MetarigError, new_bone, get_rig_type from .utils import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name from .utils import RIG_DIR -from .utils import create_root_widget +from .utils import create_root_widget, ensure_widget_collection from .utils import random_id from .utils import copy_attributes from .rig_ui_template import UI_SLIDERS, layers_ui, UI_REGISTER @@ -71,6 +71,9 @@ def generate_rig(context, metarig): bpy.ops.object.mode_set(mode='OBJECT') scene = context.scene + view_layer = context.view_layer + collection = context.collection + layer_collection = context.layer_collection #------------------------------------------ # Create/find the rig object and set it up @@ -88,8 +91,8 @@ def generate_rig(context, metarig): obj = scene.objects[name] except KeyError: obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) - obj.draw_type = 'WIRE' - scene.objects.link(obj) + obj.display_type = 'WIRE' + collection.objects.link(obj) obj.data.pose_position = 'POSE' @@ -98,9 +101,9 @@ def generate_rig(context, metarig): obj.animation_data_clear() # Select generated rig object - metarig.select = False - obj.select = True - scene.objects.active = obj + metarig.select_set(False) + obj.select_set(True) + view_layer.objects.active = obj # Remove all bones from the generated rig armature. bpy.ops.object.mode_set(mode='EDIT') @@ -111,18 +114,18 @@ def generate_rig(context, metarig): # Create temporary duplicates for merging temp_rig_1 = metarig.copy() temp_rig_1.data = metarig.data.copy() - scene.objects.link(temp_rig_1) + collection.objects.link(temp_rig_1) temp_rig_2 = metarig.copy() temp_rig_2.data = obj.data - scene.objects.link(temp_rig_2) + collection.objects.link(temp_rig_2) # Select the temp rigs for merging for objt in scene.objects: - objt.select = False # deselect all objects - temp_rig_1.select = True - temp_rig_2.select = True - scene.objects.active = temp_rig_2 + objt.select_set(False) # deselect all objects + temp_rig_1.select_set(True) + temp_rig_2.select_set(True) + view_layer.objects.active = temp_rig_2 # Merge the temporary rigs bpy.ops.object.join() @@ -132,9 +135,9 @@ def generate_rig(context, metarig): # Select the generated rig for objt in scene.objects: - objt.select = False # deselect all objects - obj.select = True - scene.objects.active = obj + objt.select_set(False) # deselect all objects + obj.select_set(True) + view_layer.objects.active = obj # Copy over bone properties for bone in metarig.data.bones: @@ -262,6 +265,9 @@ def generate_rig(context, metarig): rna_idprop_ui_prop_get(obj.data, "rig_id", create=True) obj.data["rig_id"] = rig_id + # Create/find widget collection + ensure_widget_collection(context) + t.tick("Create root bone: ") #---------------------------------- try: @@ -277,8 +283,8 @@ def generate_rig(context, metarig): for rig in rigs: # Go into editmode in the rig armature bpy.ops.object.mode_set(mode='OBJECT') - context.scene.objects.active = obj - obj.select = True + context.view_layer.objects.active = obj + obj.select_set(True) bpy.ops.object.mode_set(mode='EDIT') scripts = rig.generate() if scripts is not None: @@ -353,7 +359,7 @@ def generate_rig(context, metarig): if obj.data.bones[bone].name.startswith(DEF_PREFIX): obj.data.bones[bone].layers = DEF_LAYER - # Create root bone widget + # Create root bone widget create_root_widget(obj, "root") # Assign shapes to bones @@ -427,6 +433,10 @@ def generate_rig(context, metarig): metarig.data.pose_position = rest_backup obj.data.pose_position = 'POSE' + #---------------------------------- + # Restore active collection + view_layer.active_layer_collection = layer_collection + def get_bone_rigs(obj, bone_name, halt_on_missing=False): """ Fetch all the rigs specified on a bone. diff --git a/rigify/legacy/metarig_menu.py b/rigify/legacy/metarig_menu.py index 4bdf2701..c0c20955 100644 --- a/rigify/legacy/metarig_menu.py +++ b/rigify/legacy/metarig_menu.py @@ -114,7 +114,7 @@ def register(): bpy.utils.register_class(mop) for mf in menu_funcs: - bpy.types.INFO_MT_armature_add.append(mf) + bpy.types.VIEW3D_MT_armature_add.append(mf) def unregister(): @@ -122,4 +122,4 @@ def unregister(): bpy.utils.unregister_class(mop) for mf in menu_funcs: - bpy.types.INFO_MT_armature_add.remove(mf) + bpy.types.VIEW3D_MT_armature_add.remove(mf) diff --git a/rigify/legacy/rig_ui_pitchipoy_template.py b/rigify/legacy/rig_ui_pitchipoy_template.py index 5817f9c5..45e4f83a 100644 --- a/rigify/legacy/rig_ui_pitchipoy_template.py +++ b/rigify/legacy/rig_ui_pitchipoy_template.py @@ -135,11 +135,11 @@ def get_pose_matrix_in_other_space(mat, pose_bone): par_rest = Matrix() # Get matrix in bone's current transform space - smat = rest_inv * (par_rest * (par_inv * mat)) + smat = rest_inv @ (par_rest @ (par_inv @ mat)) # Compensate for non-local location #if not pose_bone.bone.use_local_location: - # loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion() + # loc = smat.to_translation() @ (par_rest.inverted() @ rest).to_quaternion() # smat.translation = loc return smat @@ -166,8 +166,8 @@ def set_pose_translation(pose_bone, mat): else: par_rest = Matrix() - q = (par_rest.inverted() * rest).to_quaternion() - pose_bone.location = q * loc + q = (par_rest.inverted() @ rest).to_quaternion() + pose_bone.location = q @ loc def set_pose_rotation(pose_bone, mat): @@ -283,11 +283,11 @@ def match_pole_target(ik_first, ik_last, pole, match_bone, length): angle = rotation_difference(ik_first.matrix, match_bone.matrix) # Try compensating for the rotation difference in both directions - pv1 = Matrix.Rotation(angle, 4, ikv) * pv + pv1 = Matrix.Rotation(angle, 4, ikv) @ pv set_pole(pv1) ang1 = rotation_difference(ik_first.matrix, match_bone.matrix) - pv2 = Matrix.Rotation(-angle, 4, ikv) * pv + pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv set_pole(pv2) ang2 = rotation_difference(ik_first.matrix, match_bone.matrix) @@ -421,8 +421,8 @@ def fk2ik_leg(obj, fk, ik): match_pose_scale(shin, shini) # Foot position - mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local - footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat + mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local + footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat set_pose_rotation(foot, footmat) set_pose_scale(foot, footmat) bpy.ops.object.mode_set(mode='OBJECT') @@ -439,8 +439,8 @@ def fk2ik_leg(obj, fk, ik): match_pose_scale(shin, shini) # Foot position - mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local - footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat + mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local + footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat set_pose_rotation(foot, footmat) set_pose_scale(foot, footmat) bpy.ops.object.mode_set(mode='OBJECT') @@ -478,8 +478,8 @@ def ik2fk_leg(obj, fk, ik): set_pose_rotation(footroll, Matrix()) # Foot position - mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local - footmat = get_pose_matrix_in_other_space(foot.matrix, footi) * mat + mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local + footmat = get_pose_matrix_in_other_space(foot.matrix, footi) @ mat set_pose_translation(footi, footmat) set_pose_rotation(footi, footmat) set_pose_scale(footi, footmat) @@ -505,8 +505,8 @@ def ik2fk_leg(obj, fk, ik): set_pose_rotation(footroll, Matrix()) # Foot position - mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local - footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) * mat + mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local + footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) @ mat set_pose_translation(footi, footmat) set_pose_rotation(footi, footmat) set_pose_scale(footi, footmat) @@ -528,13 +528,13 @@ class Rigify_Arm_FK2IK(bpy.types.Operator): bl_label = "Rigify Snap FK arm to IK" bl_options = {'UNDO'} - uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name") - farm_fk = bpy.props.StringProperty(name="Forerm FK Name") - hand_fk = bpy.props.StringProperty(name="Hand FK Name") + uarm_fk: bpy.props.StringProperty(name="Upper Arm FK Name") + farm_fk: bpy.props.StringProperty(name="Forerm FK Name") + hand_fk: bpy.props.StringProperty(name="Hand FK Name") - uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name") - farm_ik = bpy.props.StringProperty(name="Forearm IK Name") - hand_ik = bpy.props.StringProperty(name="Hand IK Name") + uarm_ik: bpy.props.StringProperty(name="Upper Arm IK Name") + farm_ik: bpy.props.StringProperty(name="Forearm IK Name") + hand_ik: bpy.props.StringProperty(name="Hand IK Name") @classmethod def poll(cls, context): @@ -557,14 +557,14 @@ class Rigify_Arm_IK2FK(bpy.types.Operator): bl_label = "Rigify Snap IK arm to FK" bl_options = {'UNDO'} - uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name") - farm_fk = bpy.props.StringProperty(name="Forerm FK Name") - hand_fk = bpy.props.StringProperty(name="Hand FK Name") + uarm_fk: bpy.props.StringProperty(name="Upper Arm FK Name") + farm_fk: bpy.props.StringProperty(name="Forerm FK Name") + hand_fk: bpy.props.StringProperty(name="Hand FK Name") - uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name") - farm_ik = bpy.props.StringProperty(name="Forearm IK Name") - hand_ik = bpy.props.StringProperty(name="Hand IK Name") - pole = bpy.props.StringProperty(name="Pole IK Name") + uarm_ik: bpy.props.StringProperty(name="Upper Arm IK Name") + farm_ik: bpy.props.StringProperty(name="Forearm IK Name") + hand_ik: bpy.props.StringProperty(name="Hand IK Name") + pole: bpy.props.StringProperty(name="Pole IK Name") @classmethod def poll(cls, context): @@ -587,15 +587,15 @@ class Rigify_Leg_FK2IK(bpy.types.Operator): bl_label = "Rigify Snap FK leg to IK" bl_options = {'UNDO'} - thigh_fk = bpy.props.StringProperty(name="Thigh FK Name") - shin_fk = bpy.props.StringProperty(name="Shin FK Name") - foot_fk = bpy.props.StringProperty(name="Foot FK Name") - mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name") + thigh_fk: bpy.props.StringProperty(name="Thigh FK Name") + shin_fk: bpy.props.StringProperty(name="Shin FK Name") + foot_fk: bpy.props.StringProperty(name="Foot FK Name") + mfoot_fk: bpy.props.StringProperty(name="MFoot FK Name") - thigh_ik = bpy.props.StringProperty(name="Thigh IK Name") - shin_ik = bpy.props.StringProperty(name="Shin IK Name") - foot_ik = bpy.props.StringProperty(name="Foot IK Name") - mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name") + thigh_ik: bpy.props.StringProperty(name="Thigh IK Name") + shin_ik: bpy.props.StringProperty(name="Shin IK Name") + foot_ik: bpy.props.StringProperty(name="Foot IK Name") + mfoot_ik: bpy.props.StringProperty(name="MFoot IK Name") @classmethod def poll(cls, context): @@ -618,16 +618,16 @@ class Rigify_Leg_IK2FK(bpy.types.Operator): bl_label = "Rigify Snap IK leg to FK" bl_options = {'UNDO'} - thigh_fk = bpy.props.StringProperty(name="Thigh FK Name") - shin_fk = bpy.props.StringProperty(name="Shin FK Name") - mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name") - foot_fk = bpy.props.StringProperty(name="Foot FK Name") - thigh_ik = bpy.props.StringProperty(name="Thigh IK Name") - shin_ik = bpy.props.StringProperty(name="Shin IK Name") - foot_ik = bpy.props.StringProperty(name="Foot IK Name") - footroll = bpy.props.StringProperty(name="Foot Roll Name") - pole = bpy.props.StringProperty(name="Pole IK Name") - mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name") + thigh_fk: bpy.props.StringProperty(name="Thigh FK Name") + shin_fk: bpy.props.StringProperty(name="Shin FK Name") + mfoot_fk: bpy.props.StringProperty(name="MFoot FK Name") + foot_fk: bpy.props.StringProperty(name="Foot FK Name") + thigh_ik: bpy.props.StringProperty(name="Thigh IK Name") + shin_ik: bpy.props.StringProperty(name="Shin IK Name") + foot_ik: bpy.props.StringProperty(name="Foot IK Name") + footroll: bpy.props.StringProperty(name="Foot Roll Name") + pole: bpy.props.StringProperty(name="Pole IK Name") + mfoot_ik: bpy.props.StringProperty(name="MFoot IK Name") @classmethod @@ -653,6 +653,7 @@ class RigUI(bpy.types.Panel): bl_region_type = 'UI' bl_label = "Rig Main Properties" bl_idname = rig_id + "_PT_rig_ui" + bl_category = 'View' @classmethod def poll(self, context): @@ -696,6 +697,7 @@ class RigLayers(bpy.types.Panel): bl_region_type = 'UI' bl_label = "Rig Layers" bl_idname = rig_id + "_PT_rig_layers" + bl_category = 'View' @classmethod def poll(self, context): diff --git a/rigify/legacy/rig_ui_template.py b/rigify/legacy/rig_ui_template.py index 717410da..cab5674f 100644 --- a/rigify/legacy/rig_ui_template.py +++ b/rigify/legacy/rig_ui_template.py @@ -83,11 +83,11 @@ def get_pose_matrix_in_other_space(mat, pose_bone): par_rest = Matrix() # Get matrix in bone's current transform space - smat = rest_inv * (par_rest * (par_inv * mat)) + smat = rest_inv @ (par_rest @ (par_inv @ mat)) # Compensate for non-local location #if not pose_bone.bone.use_local_location: - # loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion() + # loc = smat.to_translation() @ (par_rest.inverted() @ rest).to_quaternion() # smat.translation = loc return smat @@ -114,8 +114,8 @@ def set_pose_translation(pose_bone, mat): else: par_rest = Matrix() - q = (par_rest.inverted() * rest).to_quaternion() - pose_bone.location = q * loc + q = (par_rest.inverted() @ rest).to_quaternion() + pose_bone.location = q @ loc def set_pose_rotation(pose_bone, mat): @@ -219,11 +219,11 @@ def match_pole_target(ik_first, ik_last, pole, match_bone, length): angle = rotation_difference(ik_first.matrix, match_bone.matrix) # Try compensating for the rotation difference in both directions - pv1 = Matrix.Rotation(angle, 4, ikv) * pv + pv1 = Matrix.Rotation(angle, 4, ikv) @ pv set_pole(pv1) ang1 = rotation_difference(ik_first.matrix, match_bone.matrix) - pv2 = Matrix.Rotation(-angle, 4, ikv) * pv + pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv set_pole(pv2) ang2 = rotation_difference(ik_first.matrix, match_bone.matrix) @@ -322,8 +322,8 @@ def fk2ik_leg(obj, fk, ik): match_pose_scale(shin, shini) # Foot position - mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local - footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat + mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local + footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat set_pose_rotation(foot, footmat) set_pose_scale(foot, footmat) bpy.ops.object.mode_set(mode='OBJECT') @@ -353,8 +353,8 @@ def ik2fk_leg(obj, fk, ik): set_pose_rotation(footroll, Matrix()) # Foot position - mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local - footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) * mat + mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local + footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) @ mat set_pose_translation(footi, footmat) set_pose_rotation(footi, footmat) set_pose_scale(footi, footmat) @@ -376,13 +376,13 @@ class Rigify_Arm_FK2IK(bpy.types.Operator): bl_label = "Rigify Snap FK arm to IK" bl_options = {'UNDO'} - uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name") - farm_fk = bpy.props.StringProperty(name="Forerm FK Name") - hand_fk = bpy.props.StringProperty(name="Hand FK Name") + uarm_fk: bpy.props.StringProperty(name="Upper Arm FK Name") + farm_fk: bpy.props.StringProperty(name="Forerm FK Name") + hand_fk: bpy.props.StringProperty(name="Hand FK Name") - uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name") - farm_ik = bpy.props.StringProperty(name="Forearm IK Name") - hand_ik = bpy.props.StringProperty(name="Hand IK Name") + uarm_ik: bpy.props.StringProperty(name="Upper Arm IK Name") + farm_ik: bpy.props.StringProperty(name="Forearm IK Name") + hand_ik: bpy.props.StringProperty(name="Hand IK Name") @classmethod def poll(cls, context): @@ -405,14 +405,14 @@ class Rigify_Arm_IK2FK(bpy.types.Operator): bl_label = "Rigify Snap IK arm to FK" bl_options = {'UNDO'} - uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name") - farm_fk = bpy.props.StringProperty(name="Forerm FK Name") - hand_fk = bpy.props.StringProperty(name="Hand FK Name") + uarm_fk: bpy.props.StringProperty(name="Upper Arm FK Name") + farm_fk: bpy.props.StringProperty(name="Forerm FK Name") + hand_fk: bpy.props.StringProperty(name="Hand FK Name") - uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name") - farm_ik = bpy.props.StringProperty(name="Forearm IK Name") - hand_ik = bpy.props.StringProperty(name="Hand IK Name") - pole = bpy.props.StringProperty(name="Pole IK Name") + uarm_ik: bpy.props.StringProperty(name="Upper Arm IK Name") + farm_ik: bpy.props.StringProperty(name="Forearm IK Name") + hand_ik: bpy.props.StringProperty(name="Hand IK Name") + pole: bpy.props.StringProperty(name="Pole IK Name") @classmethod def poll(cls, context): @@ -435,15 +435,15 @@ class Rigify_Leg_FK2IK(bpy.types.Operator): bl_label = "Rigify Snap FK leg to IK" bl_options = {'UNDO'} - thigh_fk = bpy.props.StringProperty(name="Thigh FK Name") - shin_fk = bpy.props.StringProperty(name="Shin FK Name") - foot_fk = bpy.props.StringProperty(name="Foot FK Name") - mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name") + thigh_fk: bpy.props.StringProperty(name="Thigh FK Name") + shin_fk: bpy.props.StringProperty(name="Shin FK Name") + foot_fk: bpy.props.StringProperty(name="Foot FK Name") + mfoot_fk: bpy.props.StringProperty(name="MFoot FK Name") - thigh_ik = bpy.props.StringProperty(name="Thigh IK Name") - shin_ik = bpy.props.StringProperty(name="Shin IK Name") - foot_ik = bpy.props.StringProperty(name="Foot IK Name") - mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name") + thigh_ik: bpy.props.StringProperty(name="Thigh IK Name") + shin_ik: bpy.props.StringProperty(name="Shin IK Name") + foot_ik: bpy.props.StringProperty(name="Foot IK Name") + mfoot_ik: bpy.props.StringProperty(name="MFoot IK Name") @classmethod def poll(cls, context): @@ -466,16 +466,16 @@ class Rigify_Leg_IK2FK(bpy.types.Operator): bl_label = "Rigify Snap IK leg to FK" bl_options = {'UNDO'} - thigh_fk = bpy.props.StringProperty(name="Thigh FK Name") - shin_fk = bpy.props.StringProperty(name="Shin FK Name") - mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name") + thigh_fk: bpy.props.StringProperty(name="Thigh FK Name") + shin_fk: bpy.props.StringProperty(name="Shin FK Name") + mfoot_fk: bpy.props.StringProperty(name="MFoot FK Name") - thigh_ik = bpy.props.StringProperty(name="Thigh IK Name") - shin_ik = bpy.props.StringProperty(name="Shin IK Name") - foot_ik = bpy.props.StringProperty(name="Foot IK Name") - footroll = bpy.props.StringProperty(name="Foot Roll Name") - pole = bpy.props.StringProperty(name="Pole IK Name") - mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name") + thigh_ik: bpy.props.StringProperty(name="Thigh IK Name") + shin_ik: bpy.props.StringProperty(name="Shin IK Name") + foot_ik: bpy.props.StringProperty(name="Foot IK Name") + footroll: bpy.props.StringProperty(name="Foot Roll Name") + pole: bpy.props.StringProperty(name="Pole IK Name") + mfoot_ik: bpy.props.StringProperty(name="MFoot IK Name") @classmethod def poll(cls, context): @@ -500,6 +500,7 @@ class RigUI(bpy.types.Panel): bl_region_type = 'UI' bl_label = "Rig Main Properties" bl_idname = rig_id + "_PT_rig_ui" + bl_category = 'View' @classmethod def poll(self, context): @@ -543,6 +544,7 @@ class RigLayers(bpy.types.Panel): bl_region_type = 'UI' bl_label = "Rig Layers" bl_idname = rig_id + "_PT_rig_layers" + bl_category = 'View' @classmethod def poll(self, context): diff --git a/rigify/legacy/rigs/biped/limb_common.py b/rigify/legacy/rigs/biped/limb_common.py index 73e9ad34..05e3e59c 100644 --- a/rigify/legacy/rigs/biped/limb_common.py +++ b/rigify/legacy/rigs/biped/limb_common.py @@ -416,11 +416,11 @@ class IKLimb: v1 = flimb_e.tail - ulimb_e.head if 'X' in self.primary_rotation_axis or 'Y' in self.primary_rotation_axis: v2 = v1.cross(flimb_e.x_axis) - if (v2 * flimb_e.z_axis) > 0.0: + if (v2 @ flimb_e.z_axis) > 0.0: v2 *= -1.0 else: v2 = v1.cross(flimb_e.z_axis) - if (v2 * flimb_e.x_axis) < 0.0: + if (v2 @ flimb_e.x_axis) < 0.0: v2 *= -1.0 v2.normalize() v2 *= v1.length diff --git a/rigify/legacy/rigs/pitchipoy/limbs/limb_utils.py b/rigify/legacy/rigs/pitchipoy/limbs/limb_utils.py index b0b62d79..69449bb7 100644 --- a/rigify/legacy/rigs/pitchipoy/limbs/limb_utils.py +++ b/rigify/legacy/rigs/pitchipoy/limbs/limb_utils.py @@ -11,11 +11,11 @@ def orient_bone( cls, eb, axis, scale = 1.0, reverse = False ): setattr(v,axis,scale) if reverse: - tail_vec = v * cls.obj.matrix_world + tail_vec = v @ cls.obj.matrix_world eb.head[:] = eb.tail eb.tail[:] = eb.head + tail_vec else: - tail_vec = v * cls.obj.matrix_world + tail_vec = v @ cls.obj.matrix_world eb.tail[:] = eb.head + tail_vec eb.roll = 0.0 diff --git a/rigify/legacy/rigs/pitchipoy/super_face.py b/rigify/legacy/rigs/pitchipoy/super_face.py index c999ae9a..32097d43 100644 --- a/rigify/legacy/rigs/pitchipoy/super_face.py +++ b/rigify/legacy/rigs/pitchipoy/super_face.py @@ -741,8 +741,10 @@ class Rig: 'DEF-chin.R' : 'lips.R', 'DEF-jaw.R.001' : 'chin.R', 'DEF-brow.T.L.003' : 'nose', + 'DEF-ear.L' : None, 'DEF-ear.L.003' : 'ear.L.004', 'DEF-ear.L.004' : 'ear.L', + 'DEF-ear.R' : None, 'DEF-ear.R.003' : 'ear.R.004', 'DEF-ear.R.004' : 'ear.R', 'DEF-lip.B.L.001' : 'lips.L', @@ -769,8 +771,9 @@ class Rig: pattern = r'^DEF-(\w+\.?\w?\.?\w?)(\.?)(\d*?)(\d?)$' for bone in [ bone for bone in all_bones['deform']['all'] if 'lid' not in bone ]: - if bone in list( def_specials.keys() ): - self.make_constraits('def_tweak', bone, def_specials[bone] ) + if bone in def_specials: + if def_specials[bone] is not None: + self.make_constraits('def_tweak', bone, def_specials[bone] ) else: matches = re.match( pattern, bone ).groups() if len( matches ) > 1 and matches[-1]: diff --git a/rigify/legacy/rigs/pitchipoy/super_torso_turbo.py b/rigify/legacy/rigs/pitchipoy/super_torso_turbo.py index 38d5887b..d9645adb 100644 --- a/rigify/legacy/rigs/pitchipoy/super_torso_turbo.py +++ b/rigify/legacy/rigs/pitchipoy/super_torso_turbo.py @@ -99,11 +99,11 @@ class Rig: setattr(v,axis,scale) if reverse: - tail_vec = v * self.obj.matrix_world + tail_vec = v @ self.obj.matrix_world eb.head[:] = eb.tail eb.tail[:] = eb.head + tail_vec else: - tail_vec = v * self.obj.matrix_world + tail_vec = v @ self.obj.matrix_world eb.tail[:] = eb.head + tail_vec diff --git a/rigify/legacy/rigs/pitchipoy/super_widgets.py b/rigify/legacy/rigs/pitchipoy/super_widgets.py index 72384a7c..f442c590 100644 --- a/rigify/legacy/rigs/pitchipoy/super_widgets.py +++ b/rigify/legacy/rigs/pitchipoy/super_widgets.py @@ -3,7 +3,6 @@ import importlib import importlib from ...utils import create_widget -WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer. MODULE_NAME = "super_widgets" # Windows/Mac blender is weird, so __package__ doesn't work diff --git a/rigify/legacy/ui.py b/rigify/legacy/ui.py index dec5b73c..f04ec335 100644 --- a/rigify/legacy/ui.py +++ b/rigify/legacy/ui.py @@ -124,7 +124,7 @@ class DATA_PT_rigify_layer_names(bpy.types.Panel): col = layout.column(align=True) row = col.row() row.prop(arm, "layers", index=i, text="", toggle=True) - split = row.split(percentage=0.8) + split = row.split(factor=0.8) split.prop(rigify_layer, "name", text="Layer %d" % (i + 1)) split.prop(rigify_layer, "row", text="") @@ -200,9 +200,9 @@ class BONE_PT_rigify_buttons(bpy.types.Panel): class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel): bl_label = "Rigify Dev Tools" - bl_category = 'Tools' bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' + bl_region_type = 'UI' + bl_category = 'View' @classmethod def poll(cls, context): @@ -221,8 +221,8 @@ class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel): r = self.layout.row() r.operator("mesh.rigify_encode_mesh_widget", text="Encode Mesh Widget to Python") -#~ class INFO_MT_armature_metarig_add(bpy.types.Menu): - #~ bl_idname = "INFO_MT_armature_metarig_add" +#~ class VIEW3D_MT_armature_metarig_add(bpy.types.Menu): + #~ bl_idname = "VIEW3D_MT_armature_metarig_add" #~ bl_label = "Meta-Rig" #~ def draw(self, context): @@ -304,11 +304,11 @@ class Sample(bpy.types.Operator): bl_label = "Add a sample metarig for a rig type" bl_options = {'UNDO'} - metarig_type = StringProperty( - name="Type", - description="Name of the rig type to generate a sample of", - maxlen=128, - ) + metarig_type: StringProperty( + name="Type", + description="Name of the rig type to generate a sample of", + maxlen=128, + ) def execute(self, context): if context.mode == 'EDIT_ARMATURE' and self.metarig_type != "": @@ -410,7 +410,7 @@ class EncodeWidget(bpy.types.Operator): return {'FINISHED'} -#menu_func = (lambda self, context: self.layout.menu("INFO_MT_armature_metarig_add", icon='OUTLINER_OB_ARMATURE')) +#menu_func = (lambda self, context: self.layout.menu("VIEW3D_MT_armature_metarig_add", icon='OUTLINER_OB_ARMATURE')) #from bl_ui import space_info # ensure the menu is loaded first @@ -425,7 +425,7 @@ def register(): bpy.utils.register_class(EncodeMetarig) bpy.utils.register_class(EncodeMetarigSample) bpy.utils.register_class(EncodeWidget) - #space_info.INFO_MT_armature_add.append(ui.menu_func) + #space_info.VIEW3D_MT_armature_add.append(ui.menu_func) def unregister(): diff --git a/rigify/legacy/utils.py b/rigify/legacy/utils.py index e3927ac3..f74a7abf 100644 --- a/rigify/legacy/utils.py +++ b/rigify/legacy/utils.py @@ -36,8 +36,6 @@ DEF_PREFIX = "DEF-" # Prefix of deformation bones. WGT_PREFIX = "WGT-" # Prefix for widget objects ROOT_NAME = "root" # Name of the root bone. -WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer. - MODULE_NAME = "rigify" # Windows/Mac blender is weird, so __package__ doesn't work @@ -342,7 +340,7 @@ def obj_to_bone(obj, rig, bone_name): bone = rig.data.bones[bone_name] - mat = rig.matrix_world * bone.matrix_local + mat = rig.matrix_world @ bone.matrix_local obj.location = mat.to_translation() @@ -397,6 +395,7 @@ def create_widget(rig, bone_name, bone_transform_name=None): obj_name = WGT_PREFIX + bone_name scene = bpy.context.scene + collection = bpy.context.collection # Check if it already exists in the scene if obj_name in scene.objects: @@ -416,11 +415,10 @@ def create_widget(rig, bone_name, bone_transform_name=None): # Create mesh object mesh = bpy.data.meshes.new(obj_name) obj = bpy.data.objects.new(obj_name, mesh) - scene.objects.link(obj) + collection.objects.link(obj) # Move object to bone position and set layers obj_to_bone(obj, rig, bone_transform_name) - obj.layers = WGT_LAYERS return obj @@ -606,8 +604,8 @@ def align_bone_roll(obj, bone1, bone2): rot_mat = Matrix.Rotation(angle, 3, axis) # Roll factor - x3 = rot_mat * x1 - dot = x2 * x3 + x3 = rot_mat @ x1 + dot = x2 @ x3 if dot > 1.0: dot = 1.0 elif dot < -1.0: @@ -618,8 +616,8 @@ def align_bone_roll(obj, bone1, bone2): bone1_e.roll = roll # Check if we rolled in the right direction - x3 = rot_mat * bone1_e.x_axis - check = x2 * x3 + x3 = rot_mat @ bone1_e.x_axis + check = x2 @ x3 # If not, reverse if check < 0.9999: @@ -937,3 +935,43 @@ def random_id(length=8): text += random.choice(chars) text += str(hex(int(time.time())))[2:][-tlength:].rjust(tlength, '0')[::-1] return text + + +def find_layer_collection_by_collection(layer_collection, collection): + if collection == layer_collection.collection: + return layer_collection + + # go recursive + for child in layer_collection.children: + layer_collection = find_layer_collection_by_collection(child, collection) + if layer_collection: + return layer_collection + + +def ensure_widget_collection(context): + wgts_collection_name = "Widgets" + + view_layer = context.view_layer + layer_collection = bpy.context.layer_collection + collection = layer_collection.collection + + widget_collection = bpy.data.collections.get(wgts_collection_name) + if not widget_collection: + # ------------------------------------------ + # Create the widget collection + widget_collection = bpy.data.collections.new(wgts_collection_name) + widget_collection.hide_viewport = True + widget_collection.hide_render = True + + widget_layer_collection = None + else: + widget_layer_collection = find_layer_collection_by_collection(view_layer.layer_collection, widget_collection) + + if not widget_layer_collection: + # Add the widget collection to the tree + collection.children.link(widget_collection) + widget_layer_collection = [c for c in layer_collection.children if c.collection == widget_collection][0] + + # Make the widget the active collection for the upcoming added (widget) objects + view_layer.active_layer_collection = widget_layer_collection + return widget_collection diff --git a/rigify/metarig_menu.py b/rigify/metarig_menu.py index 6b12abad..102d366d 100644 --- a/rigify/metarig_menu.py +++ b/rigify/metarig_menu.py @@ -31,7 +31,7 @@ class ArmatureSubMenu(bpy.types.Menu): def draw(self, context): layout = self.layout - layout.label(self.bl_label) + layout.label(text=self.bl_label) for op, name in self.operators: text = capwords(name.replace("_", " ")) + " (Meta-Rig)" layout.operator(op, icon='OUTLINER_OB_ARMATURE', text=text) @@ -159,25 +159,33 @@ for metarig_class in metarig_classes: arm_sub = next((e for e in armature_submenus if e.bl_label == metarig_class + ' (submenu)'), '') arm_sub.operators.append((mop.bl_idname, name,)) + +### Registering ### + + def register(): + from bpy.utils import register_class + for cl in metarig_ops: for mop, name in metarig_ops[cl]: - bpy.utils.register_class(mop) + register_class(mop) for arm_sub in armature_submenus: - bpy.utils.register_class(arm_sub) + register_class(arm_sub) for mf in menu_funcs: - bpy.types.INFO_MT_armature_add.append(mf) + bpy.types.VIEW3D_MT_armature_add.append(mf) def unregister(): + from bpy.utils import unregister_class + for cl in metarig_ops: for mop, name in metarig_ops[cl]: - bpy.utils.unregister_class(mop) + unregister_class(mop) for arm_sub in armature_submenus: - bpy.utils.unregister_class(arm_sub) + unregister_class(arm_sub) for mf in menu_funcs: - bpy.types.INFO_MT_armature_add.remove(mf) + bpy.types.VIEW3D_MT_armature_add.remove(mf) diff --git a/rigify/metarigs/Animals/bird.py b/rigify/metarigs/Animals/bird.py index e31ce7fe..a3331c29 100644 --- a/rigify/metarigs/Animals/bird.py +++ b/rigify/metarigs/Animals/bird.py @@ -48,119 +48,119 @@ def create(obj): arm.rigify_layers[0].name = "Face" arm.rigify_layers[0].row = 1 - arm.rigify_layers[0].set = False + arm.rigify_layers[0].selset = False arm.rigify_layers[0].group = 6 arm.rigify_layers[1].name = "Face (Tweak)" arm.rigify_layers[1].row = 2 - arm.rigify_layers[1].set = False + arm.rigify_layers[1].selset = False arm.rigify_layers[1].group = 4 arm.rigify_layers[2].name = " " arm.rigify_layers[2].row = 1 - arm.rigify_layers[2].set = False + arm.rigify_layers[2].selset = False arm.rigify_layers[2].group = 0 arm.rigify_layers[3].name = "Spine" arm.rigify_layers[3].row = 3 - arm.rigify_layers[3].set = False + arm.rigify_layers[3].selset = False arm.rigify_layers[3].group = 3 arm.rigify_layers[4].name = "Spine (Tweak)" arm.rigify_layers[4].row = 4 - arm.rigify_layers[4].set = False + arm.rigify_layers[4].selset = False arm.rigify_layers[4].group = 4 arm.rigify_layers[5].name = " " arm.rigify_layers[5].row = 1 - arm.rigify_layers[5].set = False + arm.rigify_layers[5].selset = False arm.rigify_layers[5].group = 0 arm.rigify_layers[6].name = " " arm.rigify_layers[6].row = 1 - arm.rigify_layers[6].set = False + arm.rigify_layers[6].selset = False arm.rigify_layers[6].group = 0 arm.rigify_layers[7].name = "Wing.L" arm.rigify_layers[7].row = 6 - arm.rigify_layers[7].set = False + arm.rigify_layers[7].selset = False arm.rigify_layers[7].group = 5 arm.rigify_layers[8].name = "" arm.rigify_layers[8].row = 8 - arm.rigify_layers[8].set = False + arm.rigify_layers[8].selset = False arm.rigify_layers[8].group = 0 arm.rigify_layers[9].name = "Wing.L (Tweak)" arm.rigify_layers[9].row = 7 - arm.rigify_layers[9].set = False + arm.rigify_layers[9].selset = False arm.rigify_layers[9].group = 4 arm.rigify_layers[10].name = "Wing.R" arm.rigify_layers[10].row = 6 - arm.rigify_layers[10].set = False + arm.rigify_layers[10].selset = False arm.rigify_layers[10].group = 5 arm.rigify_layers[11].name = "" arm.rigify_layers[11].row = 8 - arm.rigify_layers[11].set = False + arm.rigify_layers[11].selset = False arm.rigify_layers[11].group = 0 arm.rigify_layers[12].name = "Wing.R (Tweak)" arm.rigify_layers[12].row = 7 - arm.rigify_layers[12].set = False + arm.rigify_layers[12].selset = False arm.rigify_layers[12].group = 4 arm.rigify_layers[13].name = "Leg.L (IK)" arm.rigify_layers[13].row = 10 - arm.rigify_layers[13].set = False + arm.rigify_layers[13].selset = False arm.rigify_layers[13].group = 2 arm.rigify_layers[14].name = "Leg.L (FK)" arm.rigify_layers[14].row = 11 - arm.rigify_layers[14].set = False + arm.rigify_layers[14].selset = False arm.rigify_layers[14].group = 5 arm.rigify_layers[15].name = "Leg.L (Tweak)" arm.rigify_layers[15].row = 12 - arm.rigify_layers[15].set = False + arm.rigify_layers[15].selset = False arm.rigify_layers[15].group = 4 arm.rigify_layers[16].name = " Leg.R (IK)" arm.rigify_layers[16].row = 10 - arm.rigify_layers[16].set = False + arm.rigify_layers[16].selset = False arm.rigify_layers[16].group = 2 arm.rigify_layers[17].name = " Leg.R (FK)" arm.rigify_layers[17].row = 11 - arm.rigify_layers[17].set = False + arm.rigify_layers[17].selset = False arm.rigify_layers[17].group = 5 arm.rigify_layers[18].name = " Leg.R (Tweak)" arm.rigify_layers[18].row = 12 - arm.rigify_layers[18].set = False + arm.rigify_layers[18].selset = False arm.rigify_layers[18].group = 4 arm.rigify_layers[19].name = " " arm.rigify_layers[19].row = 1 - arm.rigify_layers[19].set = False + arm.rigify_layers[19].selset = False arm.rigify_layers[19].group = 0 arm.rigify_layers[20].name = " " arm.rigify_layers[20].row = 1 - arm.rigify_layers[20].set = False + arm.rigify_layers[20].selset = False arm.rigify_layers[20].group = 0 arm.rigify_layers[21].name = "Claws" arm.rigify_layers[21].row = 13 - arm.rigify_layers[21].set = False + arm.rigify_layers[21].selset = False arm.rigify_layers[21].group = 6 arm.rigify_layers[22].name = "Claws (Tweak)" arm.rigify_layers[22].row = 14 - arm.rigify_layers[22].set = False + arm.rigify_layers[22].selset = False arm.rigify_layers[22].group = 4 arm.rigify_layers[23].name = " " arm.rigify_layers[23].row = 1 - arm.rigify_layers[23].set = False + arm.rigify_layers[23].selset = False arm.rigify_layers[23].group = 0 arm.rigify_layers[24].name = "Feathers" arm.rigify_layers[24].row = 8 - arm.rigify_layers[24].set = False + arm.rigify_layers[24].selset = False arm.rigify_layers[24].group = 6 arm.rigify_layers[25].name = " " arm.rigify_layers[25].row = 1 - arm.rigify_layers[25].set = False + arm.rigify_layers[25].selset = False arm.rigify_layers[25].group = 0 arm.rigify_layers[26].name = " " arm.rigify_layers[26].row = 1 - arm.rigify_layers[26].set = False + arm.rigify_layers[26].selset = False arm.rigify_layers[26].group = 0 arm.rigify_layers[27].name = " " arm.rigify_layers[27].row = 1 - arm.rigify_layers[27].set = False + arm.rigify_layers[27].selset = False arm.rigify_layers[27].group = 0 arm.rigify_layers[28].name = "Root" arm.rigify_layers[28].row = 14 - arm.rigify_layers[28].set = False + arm.rigify_layers[28].selset = False arm.rigify_layers[28].group = 1 bones = {} diff --git a/rigify/metarigs/Animals/cat.py b/rigify/metarigs/Animals/cat.py index 836c9794..d3de1192 100644 --- a/rigify/metarigs/Animals/cat.py +++ b/rigify/metarigs/Animals/cat.py @@ -48,119 +48,119 @@ def create(obj): arm.rigify_layers[0].name = "Face" arm.rigify_layers[0].row = 1 - arm.rigify_layers[0].set = False + arm.rigify_layers[0].selset = False arm.rigify_layers[0].group = 5 arm.rigify_layers[1].name = "Face (Primary)" arm.rigify_layers[1].row = 2 - arm.rigify_layers[1].set = False + arm.rigify_layers[1].selset = False arm.rigify_layers[1].group = 2 arm.rigify_layers[2].name = "Face (Secondary)" arm.rigify_layers[2].row = 2 - arm.rigify_layers[2].set = False + arm.rigify_layers[2].selset = False arm.rigify_layers[2].group = 3 arm.rigify_layers[3].name = "Spine" arm.rigify_layers[3].row = 3 - arm.rigify_layers[3].set = False + arm.rigify_layers[3].selset = False arm.rigify_layers[3].group = 3 arm.rigify_layers[4].name = "Spine (Tweak)" arm.rigify_layers[4].row = 4 - arm.rigify_layers[4].set = False + arm.rigify_layers[4].selset = False arm.rigify_layers[4].group = 4 arm.rigify_layers[5].name = "Paws" arm.rigify_layers[5].row = 5 - arm.rigify_layers[5].set = False + arm.rigify_layers[5].selset = False arm.rigify_layers[5].group = 6 arm.rigify_layers[6].name = "Paws (Tweak)" arm.rigify_layers[6].row = 6 - arm.rigify_layers[6].set = False + arm.rigify_layers[6].selset = False arm.rigify_layers[6].group = 4 arm.rigify_layers[7].name = "Arm.L (IK)" arm.rigify_layers[7].row = 7 - arm.rigify_layers[7].set = False + arm.rigify_layers[7].selset = False arm.rigify_layers[7].group = 2 arm.rigify_layers[8].name = "Arm.L (FK)" arm.rigify_layers[8].row = 8 - arm.rigify_layers[8].set = False + arm.rigify_layers[8].selset = False arm.rigify_layers[8].group = 5 arm.rigify_layers[9].name = "Arm,L (Tweak)" arm.rigify_layers[9].row = 9 - arm.rigify_layers[9].set = False + arm.rigify_layers[9].selset = False arm.rigify_layers[9].group = 4 arm.rigify_layers[10].name = "Arm.R (IK)" arm.rigify_layers[10].row = 7 - arm.rigify_layers[10].set = False + arm.rigify_layers[10].selset = False arm.rigify_layers[10].group = 2 arm.rigify_layers[11].name = "Arm.R (FK)" arm.rigify_layers[11].row = 8 - arm.rigify_layers[11].set = False + arm.rigify_layers[11].selset = False arm.rigify_layers[11].group = 5 arm.rigify_layers[12].name = "Arm.R (Tweak)" arm.rigify_layers[12].row = 9 - arm.rigify_layers[12].set = False + arm.rigify_layers[12].selset = False arm.rigify_layers[12].group = 4 arm.rigify_layers[13].name = "Leg.L (IK)" arm.rigify_layers[13].row = 10 - arm.rigify_layers[13].set = False + arm.rigify_layers[13].selset = False arm.rigify_layers[13].group = 2 arm.rigify_layers[14].name = "Leg.L (FK)" arm.rigify_layers[14].row = 11 - arm.rigify_layers[14].set = False + arm.rigify_layers[14].selset = False arm.rigify_layers[14].group = 5 arm.rigify_layers[15].name = "Leg.L (Tweak)" arm.rigify_layers[15].row = 12 - arm.rigify_layers[15].set = False + arm.rigify_layers[15].selset = False arm.rigify_layers[15].group = 4 arm.rigify_layers[16].name = "Leg.R (IK)" arm.rigify_layers[16].row = 10 - arm.rigify_layers[16].set = False + arm.rigify_layers[16].selset = False arm.rigify_layers[16].group = 2 arm.rigify_layers[17].name = "Leg.R (FK)" arm.rigify_layers[17].row = 11 - arm.rigify_layers[17].set = False + arm.rigify_layers[17].selset = False arm.rigify_layers[17].group = 5 arm.rigify_layers[18].name = "Leg.R (Tweak)" arm.rigify_layers[18].row = 12 - arm.rigify_layers[18].set = False + arm.rigify_layers[18].selset = False arm.rigify_layers[18].group = 4 arm.rigify_layers[19].name = "Tail" arm.rigify_layers[19].row = 13 - arm.rigify_layers[19].set = False + arm.rigify_layers[19].selset = False arm.rigify_layers[19].group = 3 arm.rigify_layers[20].name = "Tail (Tweaks)" arm.rigify_layers[20].row = 14 - arm.rigify_layers[20].set = False + arm.rigify_layers[20].selset = False arm.rigify_layers[20].group = 4 arm.rigify_layers[21].name = " " arm.rigify_layers[21].row = 1 - arm.rigify_layers[21].set = False + arm.rigify_layers[21].selset = False arm.rigify_layers[21].group = 0 arm.rigify_layers[22].name = " " arm.rigify_layers[22].row = 1 - arm.rigify_layers[22].set = False + arm.rigify_layers[22].selset = False arm.rigify_layers[22].group = 0 arm.rigify_layers[23].name = " " arm.rigify_layers[23].row = 1 - arm.rigify_layers[23].set = False + arm.rigify_layers[23].selset = False arm.rigify_layers[23].group = 0 arm.rigify_layers[24].name = " " arm.rigify_layers[24].row = 1 - arm.rigify_layers[24].set = False + arm.rigify_layers[24].selset = False arm.rigify_layers[24].group = 0 arm.rigify_layers[25].name = " " arm.rigify_layers[25].row = 1 - arm.rigify_layers[25].set = False + arm.rigify_layers[25].selset = False arm.rigify_layers[25].group = 0 arm.rigify_layers[26].name = " " arm.rigify_layers[26].row = 1 - arm.rigify_layers[26].set = False + arm.rigify_layers[26].selset = False arm.rigify_layers[26].group = 0 arm.rigify_layers[27].name = " " arm.rigify_layers[27].row = 1 - arm.rigify_layers[27].set = False + arm.rigify_layers[27].selset = False arm.rigify_layers[27].group = 0 arm.rigify_layers[28].name = "Root" arm.rigify_layers[28].row = 16 - arm.rigify_layers[28].set = False + arm.rigify_layers[28].selset = False arm.rigify_layers[28].group = 1 bones = {} diff --git a/rigify/metarigs/Animals/horse.py b/rigify/metarigs/Animals/horse.py index 72ed1de2..358a9ceb 100644 --- a/rigify/metarigs/Animals/horse.py +++ b/rigify/metarigs/Animals/horse.py @@ -48,119 +48,119 @@ def create(obj): arm.rigify_layers[0].name = "Face" arm.rigify_layers[0].row = 1 - arm.rigify_layers[0].set = False + arm.rigify_layers[0].selset = False arm.rigify_layers[0].group = 5 arm.rigify_layers[1].name = "Face (Primary)" arm.rigify_layers[1].row = 2 - arm.rigify_layers[1].set = False + arm.rigify_layers[1].selset = False arm.rigify_layers[1].group = 4 arm.rigify_layers[2].name = " " arm.rigify_layers[2].row = 3 - arm.rigify_layers[2].set = False + arm.rigify_layers[2].selset = False arm.rigify_layers[2].group = 0 arm.rigify_layers[3].name = "Spine" arm.rigify_layers[3].row = 4 - arm.rigify_layers[3].set = False + arm.rigify_layers[3].selset = False arm.rigify_layers[3].group = 3 arm.rigify_layers[4].name = "Spine (Tweak)" arm.rigify_layers[4].row = 5 - arm.rigify_layers[4].set = False + arm.rigify_layers[4].selset = False arm.rigify_layers[4].group = 4 arm.rigify_layers[5].name = " " arm.rigify_layers[5].row = 1 - arm.rigify_layers[5].set = False + arm.rigify_layers[5].selset = False arm.rigify_layers[5].group = 0 arm.rigify_layers[6].name = " " arm.rigify_layers[6].row = 1 - arm.rigify_layers[6].set = False + arm.rigify_layers[6].selset = False arm.rigify_layers[6].group = 0 arm.rigify_layers[7].name = "Arm.L (IK)" arm.rigify_layers[7].row = 7 - arm.rigify_layers[7].set = False + arm.rigify_layers[7].selset = False arm.rigify_layers[7].group = 2 arm.rigify_layers[8].name = "Arm.L (FK)" arm.rigify_layers[8].row = 8 - arm.rigify_layers[8].set = False + arm.rigify_layers[8].selset = False arm.rigify_layers[8].group = 5 arm.rigify_layers[9].name = "Arm.L (Tweak)" arm.rigify_layers[9].row = 9 - arm.rigify_layers[9].set = False + arm.rigify_layers[9].selset = False arm.rigify_layers[9].group = 4 arm.rigify_layers[10].name = "Arm.R (IK)" arm.rigify_layers[10].row = 7 - arm.rigify_layers[10].set = False + arm.rigify_layers[10].selset = False arm.rigify_layers[10].group = 2 arm.rigify_layers[11].name = "Arm.R (FK)" arm.rigify_layers[11].row = 8 - arm.rigify_layers[11].set = False + arm.rigify_layers[11].selset = False arm.rigify_layers[11].group = 5 arm.rigify_layers[12].name = "Arm.R (Tweak)" arm.rigify_layers[12].row = 9 - arm.rigify_layers[12].set = False + arm.rigify_layers[12].selset = False arm.rigify_layers[12].group = 4 arm.rigify_layers[13].name = "Leg.L (IK)" arm.rigify_layers[13].row = 10 - arm.rigify_layers[13].set = False + arm.rigify_layers[13].selset = False arm.rigify_layers[13].group = 2 arm.rigify_layers[14].name = "Leg.L (FK)" arm.rigify_layers[14].row = 11 - arm.rigify_layers[14].set = False + arm.rigify_layers[14].selset = False arm.rigify_layers[14].group = 5 arm.rigify_layers[15].name = "Leg.L (Tweak)" arm.rigify_layers[15].row = 12 - arm.rigify_layers[15].set = False + arm.rigify_layers[15].selset = False arm.rigify_layers[15].group = 4 arm.rigify_layers[16].name = "Leg.R (IK)" arm.rigify_layers[16].row = 10 - arm.rigify_layers[16].set = False + arm.rigify_layers[16].selset = False arm.rigify_layers[16].group = 2 arm.rigify_layers[17].name = "Leg.R (FK)" arm.rigify_layers[17].row = 11 - arm.rigify_layers[17].set = False + arm.rigify_layers[17].selset = False arm.rigify_layers[17].group = 5 arm.rigify_layers[18].name = "Leg.R (Tweak)" arm.rigify_layers[18].row = 12 - arm.rigify_layers[18].set = False + arm.rigify_layers[18].selset = False arm.rigify_layers[18].group = 4 arm.rigify_layers[19].name = "Tail" arm.rigify_layers[19].row = 13 - arm.rigify_layers[19].set = False + arm.rigify_layers[19].selset = False arm.rigify_layers[19].group = 6 arm.rigify_layers[20].name = " " arm.rigify_layers[20].row = 1 - arm.rigify_layers[20].set = False + arm.rigify_layers[20].selset = False arm.rigify_layers[20].group = 4 arm.rigify_layers[21].name = "Hair" arm.rigify_layers[21].row = 14 - arm.rigify_layers[21].set = False + arm.rigify_layers[21].selset = False arm.rigify_layers[21].group = 6 arm.rigify_layers[22].name = " " arm.rigify_layers[22].row = 1 - arm.rigify_layers[22].set = False + arm.rigify_layers[22].selset = False arm.rigify_layers[22].group = 0 arm.rigify_layers[23].name = " " arm.rigify_layers[23].row = 1 - arm.rigify_layers[23].set = False + arm.rigify_layers[23].selset = False arm.rigify_layers[23].group = 0 arm.rigify_layers[24].name = " " arm.rigify_layers[24].row = 1 - arm.rigify_layers[24].set = False + arm.rigify_layers[24].selset = False arm.rigify_layers[24].group = 0 arm.rigify_layers[25].name = " " arm.rigify_layers[25].row = 1 - arm.rigify_layers[25].set = False + arm.rigify_layers[25].selset = False arm.rigify_layers[25].group = 0 arm.rigify_layers[26].name = " " arm.rigify_layers[26].row = 1 - arm.rigify_layers[26].set = False + arm.rigify_layers[26].selset = False arm.rigify_layers[26].group = 0 arm.rigify_layers[27].name = " " arm.rigify_layers[27].row = 1 - arm.rigify_layers[27].set = False + arm.rigify_layers[27].selset = False arm.rigify_layers[27].group = 0 arm.rigify_layers[28].name = "Root" arm.rigify_layers[28].row = 14 - arm.rigify_layers[28].set = False + arm.rigify_layers[28].selset = False arm.rigify_layers[28].group = 1 diff --git a/rigify/metarigs/Animals/shark.py b/rigify/metarigs/Animals/shark.py index bcc53df3..1ee1d0d9 100644 --- a/rigify/metarigs/Animals/shark.py +++ b/rigify/metarigs/Animals/shark.py @@ -48,119 +48,119 @@ def create(obj): arm.rigify_layers[0].name = "Face" arm.rigify_layers[0].row = 1 - arm.rigify_layers[0].set = False + arm.rigify_layers[0].selset = False arm.rigify_layers[0].group = 5 arm.rigify_layers[1].name = "Face (Tweak)" arm.rigify_layers[1].row = 2 - arm.rigify_layers[1].set = False + arm.rigify_layers[1].selset = False arm.rigify_layers[1].group = 4 arm.rigify_layers[2].name = " " arm.rigify_layers[2].row = 1 - arm.rigify_layers[2].set = False + arm.rigify_layers[2].selset = False arm.rigify_layers[2].group = 0 arm.rigify_layers[3].name = "Spine" arm.rigify_layers[3].row = 3 - arm.rigify_layers[3].set = False + arm.rigify_layers[3].selset = False arm.rigify_layers[3].group = 3 arm.rigify_layers[4].name = "Spine (Tweak)" arm.rigify_layers[4].row = 4 - arm.rigify_layers[4].set = False + arm.rigify_layers[4].selset = False arm.rigify_layers[4].group = 4 arm.rigify_layers[5].name = "Tail" arm.rigify_layers[5].row = 5 - arm.rigify_layers[5].set = False + arm.rigify_layers[5].selset = False arm.rigify_layers[5].group = 6 arm.rigify_layers[6].name = "Fins.L" arm.rigify_layers[6].row = 6 - arm.rigify_layers[6].set = False + arm.rigify_layers[6].selset = False arm.rigify_layers[6].group = 5 arm.rigify_layers[7].name = "Fins.L (Tweak)" arm.rigify_layers[7].row = 7 - arm.rigify_layers[7].set = False + arm.rigify_layers[7].selset = False arm.rigify_layers[7].group = 4 arm.rigify_layers[8].name = "Fins.R" arm.rigify_layers[8].row = 6 - arm.rigify_layers[8].set = False + arm.rigify_layers[8].selset = False arm.rigify_layers[8].group = 5 arm.rigify_layers[9].name = "Fins.R (Tweak)" arm.rigify_layers[9].row = 7 - arm.rigify_layers[9].set = False + arm.rigify_layers[9].selset = False arm.rigify_layers[9].group = 4 arm.rigify_layers[10].name = "Fins" arm.rigify_layers[10].row = 8 - arm.rigify_layers[10].set = False + arm.rigify_layers[10].selset = False arm.rigify_layers[10].group = 3 arm.rigify_layers[11].name = "Fins (Tweak)" arm.rigify_layers[11].row = 9 - arm.rigify_layers[11].set = False + arm.rigify_layers[11].selset = False arm.rigify_layers[11].group = 4 arm.rigify_layers[12].name = " " arm.rigify_layers[12].row = 1 - arm.rigify_layers[12].set = False + arm.rigify_layers[12].selset = False arm.rigify_layers[12].group = 0 arm.rigify_layers[13].name = " " arm.rigify_layers[13].row = 1 - arm.rigify_layers[13].set = False + arm.rigify_layers[13].selset = False arm.rigify_layers[13].group = 6 arm.rigify_layers[14].name = " " arm.rigify_layers[14].row = 1 - arm.rigify_layers[14].set = False + arm.rigify_layers[14].selset = False arm.rigify_layers[14].group = 0 arm.rigify_layers[15].name = " " arm.rigify_layers[15].row = 1 - arm.rigify_layers[15].set = False + arm.rigify_layers[15].selset = False arm.rigify_layers[15].group = 0 arm.rigify_layers[16].name = " " arm.rigify_layers[16].row = 1 - arm.rigify_layers[16].set = False + arm.rigify_layers[16].selset = False arm.rigify_layers[16].group = 0 arm.rigify_layers[17].name = " " arm.rigify_layers[17].row = 1 - arm.rigify_layers[17].set = False + arm.rigify_layers[17].selset = False arm.rigify_layers[17].group = 0 arm.rigify_layers[18].name = " " arm.rigify_layers[18].row = 1 - arm.rigify_layers[18].set = False + arm.rigify_layers[18].selset = False arm.rigify_layers[18].group = 0 arm.rigify_layers[19].name = " " arm.rigify_layers[19].row = 1 - arm.rigify_layers[19].set = False + arm.rigify_layers[19].selset = False arm.rigify_layers[19].group = 0 arm.rigify_layers[20].name = " " arm.rigify_layers[20].row = 1 - arm.rigify_layers[20].set = False + arm.rigify_layers[20].selset = False arm.rigify_layers[20].group = 0 arm.rigify_layers[21].name = " " arm.rigify_layers[21].row = 1 - arm.rigify_layers[21].set = False + arm.rigify_layers[21].selset = False arm.rigify_layers[21].group = 0 arm.rigify_layers[22].name = " " arm.rigify_layers[22].row = 1 - arm.rigify_layers[22].set = False + arm.rigify_layers[22].selset = False arm.rigify_layers[22].group = 0 arm.rigify_layers[23].name = " " arm.rigify_layers[23].row = 1 - arm.rigify_layers[23].set = False + arm.rigify_layers[23].selset = False arm.rigify_layers[23].group = 0 arm.rigify_layers[24].name = " " arm.rigify_layers[24].row = 1 - arm.rigify_layers[24].set = False + arm.rigify_layers[24].selset = False arm.rigify_layers[24].group = 0 arm.rigify_layers[25].name = " " arm.rigify_layers[25].row = 1 - arm.rigify_layers[25].set = False + arm.rigify_layers[25].selset = False arm.rigify_layers[25].group = 0 arm.rigify_layers[26].name = " " arm.rigify_layers[26].row = 1 - arm.rigify_layers[26].set = False + arm.rigify_layers[26].selset = False arm.rigify_layers[26].group = 0 arm.rigify_layers[27].name = " " arm.rigify_layers[27].row = 1 - arm.rigify_layers[27].set = False + arm.rigify_layers[27].selset = False arm.rigify_layers[27].group = 0 arm.rigify_layers[28].name = "Root" arm.rigify_layers[28].row = 14 - arm.rigify_layers[28].set = False + arm.rigify_layers[28].selset = False arm.rigify_layers[28].group = 1 bones = {} diff --git a/rigify/metarigs/Animals/wolf.py b/rigify/metarigs/Animals/wolf.py index 20aa121c..4cccf085 100644 --- a/rigify/metarigs/Animals/wolf.py +++ b/rigify/metarigs/Animals/wolf.py @@ -48,119 +48,119 @@ def create(obj): arm.rigify_layers[0].name = "Face" arm.rigify_layers[0].row = 1 - arm.rigify_layers[0].set = False + arm.rigify_layers[0].selset = False arm.rigify_layers[0].group = 5 arm.rigify_layers[1].name = "Face (Primary)" arm.rigify_layers[1].row = 2 - arm.rigify_layers[1].set = False + arm.rigify_layers[1].selset = False arm.rigify_layers[1].group = 2 arm.rigify_layers[2].name = "Face (Secondary)" arm.rigify_layers[2].row = 2 - arm.rigify_layers[2].set = False + arm.rigify_layers[2].selset = False arm.rigify_layers[2].group = 3 arm.rigify_layers[3].name = "Spine" arm.rigify_layers[3].row = 3 - arm.rigify_layers[3].set = False + arm.rigify_layers[3].selset = False arm.rigify_layers[3].group = 3 arm.rigify_layers[4].name = "Spine (Tweak)" arm.rigify_layers[4].row = 4 - arm.rigify_layers[4].set = False + arm.rigify_layers[4].selset = False arm.rigify_layers[4].group = 4 arm.rigify_layers[5].name = "Paws" arm.rigify_layers[5].row = 5 - arm.rigify_layers[5].set = False + arm.rigify_layers[5].selset = False arm.rigify_layers[5].group = 6 arm.rigify_layers[6].name = "Paws (Tweak)" arm.rigify_layers[6].row = 6 - arm.rigify_layers[6].set = False + arm.rigify_layers[6].selset = False arm.rigify_layers[6].group = 4 arm.rigify_layers[7].name = "Arm.L (IK)" arm.rigify_layers[7].row = 7 - arm.rigify_layers[7].set = False + arm.rigify_layers[7].selset = False arm.rigify_layers[7].group = 2 arm.rigify_layers[8].name = "Arm.L (FK)" arm.rigify_layers[8].row = 8 - arm.rigify_layers[8].set = False + arm.rigify_layers[8].selset = False arm.rigify_layers[8].group = 5 arm.rigify_layers[9].name = "Arm.L (Tweak)" arm.rigify_layers[9].row = 9 - arm.rigify_layers[9].set = False + arm.rigify_layers[9].selset = False arm.rigify_layers[9].group = 4 arm.rigify_layers[10].name = "Arm.R (IK)" arm.rigify_layers[10].row = 7 - arm.rigify_layers[10].set = False + arm.rigify_layers[10].selset = False arm.rigify_layers[10].group = 2 arm.rigify_layers[11].name = "Arm.R (FK)" arm.rigify_layers[11].row = 8 - arm.rigify_layers[11].set = False + arm.rigify_layers[11].selset = False arm.rigify_layers[11].group = 5 arm.rigify_layers[12].name = "Arm.R (Tweak)" arm.rigify_layers[12].row = 9 - arm.rigify_layers[12].set = False + arm.rigify_layers[12].selset = False arm.rigify_layers[12].group = 4 arm.rigify_layers[13].name = "Leg.L (IK)" arm.rigify_layers[13].row = 10 - arm.rigify_layers[13].set = False + arm.rigify_layers[13].selset = False arm.rigify_layers[13].group = 2 arm.rigify_layers[14].name = "Leg.L (FK)" arm.rigify_layers[14].row = 11 - arm.rigify_layers[14].set = False + arm.rigify_layers[14].selset = False arm.rigify_layers[14].group = 5 arm.rigify_layers[15].name = "Leg.L (Tweak)" arm.rigify_layers[15].row = 12 - arm.rigify_layers[15].set = False + arm.rigify_layers[15].selset = False arm.rigify_layers[15].group = 4 arm.rigify_layers[16].name = "Leg.R (IK)" arm.rigify_layers[16].row = 10 - arm.rigify_layers[16].set = False + arm.rigify_layers[16].selset = False arm.rigify_layers[16].group = 2 arm.rigify_layers[17].name = "Leg.R (FK)" arm.rigify_layers[17].row = 11 - arm.rigify_layers[17].set = False + arm.rigify_layers[17].selset = False arm.rigify_layers[17].group = 5 arm.rigify_layers[18].name = "Leg.R (Tweak)" arm.rigify_layers[18].row = 12 - arm.rigify_layers[18].set = False + arm.rigify_layers[18].selset = False arm.rigify_layers[18].group = 4 arm.rigify_layers[19].name = "Tail" arm.rigify_layers[19].row = 13 - arm.rigify_layers[19].set = False + arm.rigify_layers[19].selset = False arm.rigify_layers[19].group = 6 arm.rigify_layers[20].name = "" arm.rigify_layers[20].row = 1 - arm.rigify_layers[20].set = False + arm.rigify_layers[20].selset = False arm.rigify_layers[20].group = 0 arm.rigify_layers[21].name = "" arm.rigify_layers[21].row = 13 - arm.rigify_layers[21].set = False + arm.rigify_layers[21].selset = False arm.rigify_layers[21].group = 0 arm.rigify_layers[22].name = "" arm.rigify_layers[22].row = 13 - arm.rigify_layers[22].set = False + arm.rigify_layers[22].selset = False arm.rigify_layers[22].group = 0 arm.rigify_layers[23].name = "" arm.rigify_layers[23].row = 1 - arm.rigify_layers[23].set = False + arm.rigify_layers[23].selset = False arm.rigify_layers[23].group = 0 arm.rigify_layers[24].name = "" arm.rigify_layers[24].row = 1 - arm.rigify_layers[24].set = False + arm.rigify_layers[24].selset = False arm.rigify_layers[24].group = 0 arm.rigify_layers[25].name = "" arm.rigify_layers[25].row = 1 - arm.rigify_layers[25].set = False + arm.rigify_layers[25].selset = False arm.rigify_layers[25].group = 0 arm.rigify_layers[26].name = "" arm.rigify_layers[26].row = 1 - arm.rigify_layers[26].set = False + arm.rigify_layers[26].selset = False arm.rigify_layers[26].group = 0 arm.rigify_layers[27].name = "" arm.rigify_layers[27].row = 1 - arm.rigify_layers[27].set = False + arm.rigify_layers[27].selset = False arm.rigify_layers[27].group = 0 arm.rigify_layers[28].name = "Root" arm.rigify_layers[28].row = 14 - arm.rigify_layers[28].set = False + arm.rigify_layers[28].selset = False arm.rigify_layers[28].group = 1 bones = {} diff --git a/rigify/metarigs/Basic/basic_human.py b/rigify/metarigs/Basic/basic_human.py index 5b1a401d..01367a7b 100644 --- a/rigify/metarigs/Basic/basic_human.py +++ b/rigify/metarigs/Basic/basic_human.py @@ -48,119 +48,119 @@ def create(obj): arm.rigify_layers[0].name = " " arm.rigify_layers[0].row = 1 - arm.rigify_layers[0].set = False + arm.rigify_layers[0].selset = False arm.rigify_layers[0].group = 0 arm.rigify_layers[1].name = " " arm.rigify_layers[1].row = 1 - arm.rigify_layers[1].set = False + arm.rigify_layers[1].selset = False arm.rigify_layers[1].group = 0 arm.rigify_layers[2].name = " " arm.rigify_layers[2].row = 1 - arm.rigify_layers[2].set = False + arm.rigify_layers[2].selset = False arm.rigify_layers[2].group = 0 arm.rigify_layers[3].name = "Torso" arm.rigify_layers[3].row = 3 - arm.rigify_layers[3].set = False + arm.rigify_layers[3].selset = False arm.rigify_layers[3].group = 3 arm.rigify_layers[4].name = "Torso (Tweak)" arm.rigify_layers[4].row = 4 - arm.rigify_layers[4].set = False + arm.rigify_layers[4].selset = False arm.rigify_layers[4].group = 4 arm.rigify_layers[5].name = " " arm.rigify_layers[5].row = 1 - arm.rigify_layers[5].set = False + arm.rigify_layers[5].selset = False arm.rigify_layers[5].group = 0 arm.rigify_layers[6].name = " " arm.rigify_layers[6].row = 1 - arm.rigify_layers[6].set = False + arm.rigify_layers[6].selset = False arm.rigify_layers[6].group = 0 arm.rigify_layers[7].name = "Arm.L (IK)" arm.rigify_layers[7].row = 7 - arm.rigify_layers[7].set = False + arm.rigify_layers[7].selset = False arm.rigify_layers[7].group = 2 arm.rigify_layers[8].name = "Arm.L (FK)" arm.rigify_layers[8].row = 8 - arm.rigify_layers[8].set = False + arm.rigify_layers[8].selset = False arm.rigify_layers[8].group = 5 arm.rigify_layers[9].name = "Arm.L (Tweak)" arm.rigify_layers[9].row = 9 - arm.rigify_layers[9].set = False + arm.rigify_layers[9].selset = False arm.rigify_layers[9].group = 4 arm.rigify_layers[10].name = "Arm.R (IK)" arm.rigify_layers[10].row = 7 - arm.rigify_layers[10].set = False + arm.rigify_layers[10].selset = False arm.rigify_layers[10].group = 2 arm.rigify_layers[11].name = "Arm.R (FK)" arm.rigify_layers[11].row = 8 - arm.rigify_layers[11].set = False + arm.rigify_layers[11].selset = False arm.rigify_layers[11].group = 5 arm.rigify_layers[12].name = "Arm.R (Tweak)" arm.rigify_layers[12].row = 9 - arm.rigify_layers[12].set = False + arm.rigify_layers[12].selset = False arm.rigify_layers[12].group = 4 arm.rigify_layers[13].name = "Leg.L (IK)" arm.rigify_layers[13].row = 10 - arm.rigify_layers[13].set = False + arm.rigify_layers[13].selset = False arm.rigify_layers[13].group = 2 arm.rigify_layers[14].name = "Leg.L (FK)" arm.rigify_layers[14].row = 11 - arm.rigify_layers[14].set = False + arm.rigify_layers[14].selset = False arm.rigify_layers[14].group = 5 arm.rigify_layers[15].name = "Leg.L (Tweak)" arm.rigify_layers[15].row = 12 - arm.rigify_layers[15].set = False + arm.rigify_layers[15].selset = False arm.rigify_layers[15].group = 4 arm.rigify_layers[16].name = "Leg.R (IK)" arm.rigify_layers[16].row = 10 - arm.rigify_layers[16].set = False + arm.rigify_layers[16].selset = False arm.rigify_layers[16].group = 2 arm.rigify_layers[17].name = "Leg.R (FK)" arm.rigify_layers[17].row = 11 - arm.rigify_layers[17].set = False + arm.rigify_layers[17].selset = False arm.rigify_layers[17].group = 5 arm.rigify_layers[18].name = "Leg.R (Tweak)" arm.rigify_layers[18].row = 12 - arm.rigify_layers[18].set = False + arm.rigify_layers[18].selset = False arm.rigify_layers[18].group = 4 arm.rigify_layers[19].name = "" arm.rigify_layers[19].row = 1 - arm.rigify_layers[19].set = False + arm.rigify_layers[19].selset = False arm.rigify_layers[19].group = 0 arm.rigify_layers[20].name = "" arm.rigify_layers[20].row = 1 - arm.rigify_layers[20].set = False + arm.rigify_layers[20].selset = False arm.rigify_layers[20].group = 0 arm.rigify_layers[21].name = "" arm.rigify_layers[21].row = 1 - arm.rigify_layers[21].set = False + arm.rigify_layers[21].selset = False arm.rigify_layers[21].group = 0 arm.rigify_layers[22].name = "" arm.rigify_layers[22].row = 1 - arm.rigify_layers[22].set = False + arm.rigify_layers[22].selset = False arm.rigify_layers[22].group = 0 arm.rigify_layers[23].name = "" arm.rigify_layers[23].row = 1 - arm.rigify_layers[23].set = False + arm.rigify_layers[23].selset = False arm.rigify_layers[23].group = 0 arm.rigify_layers[24].name = "" arm.rigify_layers[24].row = 1 - arm.rigify_layers[24].set = False + arm.rigify_layers[24].selset = False arm.rigify_layers[24].group = 0 arm.rigify_layers[25].name = "" arm.rigify_layers[25].row = 1 - arm.rigify_layers[25].set = False + arm.rigify_layers[25].selset = False arm.rigify_layers[25].group = 0 arm.rigify_layers[26].name = "" arm.rigify_layers[26].row = 1 - arm.rigify_layers[26].set = False + arm.rigify_layers[26].selset = False arm.rigify_layers[26].group = 0 arm.rigify_layers[27].name = "" arm.rigify_layers[27].row = 1 - arm.rigify_layers[27].set = False + arm.rigify_layers[27].selset = False arm.rigify_layers[27].group = 0 arm.rigify_layers[28].name = "Root" arm.rigify_layers[28].row = 14 - arm.rigify_layers[28].set = False + arm.rigify_layers[28].selset = False arm.rigify_layers[28].group = 1 bones = {} diff --git a/rigify/metarigs/Basic/basic_quadruped.py b/rigify/metarigs/Basic/basic_quadruped.py index c46743c7..5aa9f657 100644 --- a/rigify/metarigs/Basic/basic_quadruped.py +++ b/rigify/metarigs/Basic/basic_quadruped.py @@ -48,119 +48,119 @@ def create(obj): arm.rigify_layers[0].name = " " arm.rigify_layers[0].row = 1 - arm.rigify_layers[0].set = False + arm.rigify_layers[0].selset = False arm.rigify_layers[0].group = 0 arm.rigify_layers[1].name = " " arm.rigify_layers[1].row = 2 - arm.rigify_layers[1].set = False + arm.rigify_layers[1].selset = False arm.rigify_layers[1].group = 0 arm.rigify_layers[2].name = " " arm.rigify_layers[2].row = 2 - arm.rigify_layers[2].set = False + arm.rigify_layers[2].selset = False arm.rigify_layers[2].group = 0 arm.rigify_layers[3].name = "Spine" arm.rigify_layers[3].row = 3 - arm.rigify_layers[3].set = False + arm.rigify_layers[3].selset = False arm.rigify_layers[3].group = 3 arm.rigify_layers[4].name = "Spine (Tweak)" arm.rigify_layers[4].row = 4 - arm.rigify_layers[4].set = False + arm.rigify_layers[4].selset = False arm.rigify_layers[4].group = 4 arm.rigify_layers[5].name = " " arm.rigify_layers[5].row = 5 - arm.rigify_layers[5].set = False + arm.rigify_layers[5].selset = False arm.rigify_layers[5].group = 0 arm.rigify_layers[6].name = " " arm.rigify_layers[6].row = 6 - arm.rigify_layers[6].set = False + arm.rigify_layers[6].selset = False arm.rigify_layers[6].group = 0 arm.rigify_layers[7].name = "Arm.L (IK)" arm.rigify_layers[7].row = 7 - arm.rigify_layers[7].set = False + arm.rigify_layers[7].selset = False arm.rigify_layers[7].group = 2 arm.rigify_layers[8].name = "Arm.L (FK)" arm.rigify_layers[8].row = 8 - arm.rigify_layers[8].set = False + arm.rigify_layers[8].selset = False arm.rigify_layers[8].group = 5 arm.rigify_layers[9].name = "Arm.L (Tweak)" arm.rigify_layers[9].row = 9 - arm.rigify_layers[9].set = False + arm.rigify_layers[9].selset = False arm.rigify_layers[9].group = 4 arm.rigify_layers[10].name = "Arm.R (IK)" arm.rigify_layers[10].row = 7 - arm.rigify_layers[10].set = False + arm.rigify_layers[10].selset = False arm.rigify_layers[10].group = 2 arm.rigify_layers[11].name = "Arm.R (FK)" arm.rigify_layers[11].row = 8 - arm.rigify_layers[11].set = False + arm.rigify_layers[11].selset = False arm.rigify_layers[11].group = 5 arm.rigify_layers[12].name = "Arm.R (Tweak)" arm.rigify_layers[12].row = 9 - arm.rigify_layers[12].set = False + arm.rigify_layers[12].selset = False arm.rigify_layers[12].group = 4 arm.rigify_layers[13].name = "Leg.L (IK)" arm.rigify_layers[13].row = 10 - arm.rigify_layers[13].set = False + arm.rigify_layers[13].selset = False arm.rigify_layers[13].group = 2 arm.rigify_layers[14].name = "Leg.L (FK)" arm.rigify_layers[14].row = 11 - arm.rigify_layers[14].set = False + arm.rigify_layers[14].selset = False arm.rigify_layers[14].group = 5 arm.rigify_layers[15].name = "Leg.L (Tweak)" arm.rigify_layers[15].row = 12 - arm.rigify_layers[15].set = False + arm.rigify_layers[15].selset = False arm.rigify_layers[15].group = 4 arm.rigify_layers[16].name = "Leg.R (IK)" arm.rigify_layers[16].row = 10 - arm.rigify_layers[16].set = False + arm.rigify_layers[16].selset = False arm.rigify_layers[16].group = 2 arm.rigify_layers[17].name = "Leg.R (FK)" arm.rigify_layers[17].row = 11 - arm.rigify_layers[17].set = False + arm.rigify_layers[17].selset = False arm.rigify_layers[17].group = 5 arm.rigify_layers[18].name = "Leg.R (Tweak)" arm.rigify_layers[18].row = 12 - arm.rigify_layers[18].set = False + arm.rigify_layers[18].selset = False arm.rigify_layers[18].group = 4 arm.rigify_layers[19].name = "Tail" arm.rigify_layers[19].row = 13 - arm.rigify_layers[19].set = False + arm.rigify_layers[19].selset = False arm.rigify_layers[19].group = 6 arm.rigify_layers[20].name = "" arm.rigify_layers[20].row = 1 - arm.rigify_layers[20].set = False + arm.rigify_layers[20].selset = False arm.rigify_layers[20].group = 0 arm.rigify_layers[21].name = "" arm.rigify_layers[21].row = 13 - arm.rigify_layers[21].set = False + arm.rigify_layers[21].selset = False arm.rigify_layers[21].group = 0 arm.rigify_layers[22].name = "" arm.rigify_layers[22].row = 13 - arm.rigify_layers[22].set = False + arm.rigify_layers[22].selset = False arm.rigify_layers[22].group = 0 arm.rigify_layers[23].name = "" arm.rigify_layers[23].row = 1 - arm.rigify_layers[23].set = False + arm.rigify_layers[23].selset = False arm.rigify_layers[23].group = 0 arm.rigify_layers[24].name = "" arm.rigify_layers[24].row = 1 - arm.rigify_layers[24].set = False + arm.rigify_layers[24].selset = False arm.rigify_layers[24].group = 0 arm.rigify_layers[25].name = "" arm.rigify_layers[25].row = 1 - arm.rigify_layers[25].set = False + arm.rigify_layers[25].selset = False arm.rigify_layers[25].group = 0 arm.rigify_layers[26].name = "" arm.rigify_layers[26].row = 1 - arm.rigify_layers[26].set = False + arm.rigify_layers[26].selset = False arm.rigify_layers[26].group = 0 arm.rigify_layers[27].name = "" arm.rigify_layers[27].row = 1 - arm.rigify_layers[27].set = False + arm.rigify_layers[27].selset = False arm.rigify_layers[27].group = 0 arm.rigify_layers[28].name = "Root" arm.rigify_layers[28].row = 14 - arm.rigify_layers[28].set = False + arm.rigify_layers[28].selset = False arm.rigify_layers[28].group = 1 bones = {} diff --git a/rigify/metarigs/human.py b/rigify/metarigs/human.py index a31a107c..bbba8eed 100644 --- a/rigify/metarigs/human.py +++ b/rigify/metarigs/human.py @@ -48,119 +48,119 @@ def create(obj): arm.rigify_layers[0].name = "Face" arm.rigify_layers[0].row = 1 - arm.rigify_layers[0].set = False + arm.rigify_layers[0].selset = False arm.rigify_layers[0].group = 5 arm.rigify_layers[1].name = "Face (Primary)" arm.rigify_layers[1].row = 2 - arm.rigify_layers[1].set = False + arm.rigify_layers[1].selset = False arm.rigify_layers[1].group = 2 arm.rigify_layers[2].name = "Face (Secondary)" arm.rigify_layers[2].row = 2 - arm.rigify_layers[2].set = False + arm.rigify_layers[2].selset = False arm.rigify_layers[2].group = 3 arm.rigify_layers[3].name = "Torso" arm.rigify_layers[3].row = 3 - arm.rigify_layers[3].set = False + arm.rigify_layers[3].selset = False arm.rigify_layers[3].group = 3 arm.rigify_layers[4].name = "Torso (Tweak)" arm.rigify_layers[4].row = 4 - arm.rigify_layers[4].set = False + arm.rigify_layers[4].selset = False arm.rigify_layers[4].group = 4 arm.rigify_layers[5].name = "Fingers" arm.rigify_layers[5].row = 5 - arm.rigify_layers[5].set = False + arm.rigify_layers[5].selset = False arm.rigify_layers[5].group = 6 arm.rigify_layers[6].name = "Fingers (Tweak)" arm.rigify_layers[6].row = 6 - arm.rigify_layers[6].set = False + arm.rigify_layers[6].selset = False arm.rigify_layers[6].group = 4 arm.rigify_layers[7].name = "Arm.L (IK)" arm.rigify_layers[7].row = 7 - arm.rigify_layers[7].set = False + arm.rigify_layers[7].selset = False arm.rigify_layers[7].group = 2 arm.rigify_layers[8].name = "Arm.L (FK)" arm.rigify_layers[8].row = 8 - arm.rigify_layers[8].set = False + arm.rigify_layers[8].selset = False arm.rigify_layers[8].group = 5 arm.rigify_layers[9].name = "Arm.L (Tweak)" arm.rigify_layers[9].row = 9 - arm.rigify_layers[9].set = False + arm.rigify_layers[9].selset = False arm.rigify_layers[9].group = 4 arm.rigify_layers[10].name = "Arm.R (IK)" arm.rigify_layers[10].row = 7 - arm.rigify_layers[10].set = False + arm.rigify_layers[10].selset = False arm.rigify_layers[10].group = 2 arm.rigify_layers[11].name = "Arm.R (FK)" arm.rigify_layers[11].row = 8 - arm.rigify_layers[11].set = False + arm.rigify_layers[11].selset = False arm.rigify_layers[11].group = 5 arm.rigify_layers[12].name = "Arm.R (Tweak)" arm.rigify_layers[12].row = 9 - arm.rigify_layers[12].set = False + arm.rigify_layers[12].selset = False arm.rigify_layers[12].group = 4 arm.rigify_layers[13].name = "Leg.L (IK)" arm.rigify_layers[13].row = 10 - arm.rigify_layers[13].set = False + arm.rigify_layers[13].selset = False arm.rigify_layers[13].group = 2 arm.rigify_layers[14].name = "Leg.L (FK)" arm.rigify_layers[14].row = 11 - arm.rigify_layers[14].set = False + arm.rigify_layers[14].selset = False arm.rigify_layers[14].group = 5 arm.rigify_layers[15].name = "Leg.L (Tweak)" arm.rigify_layers[15].row = 12 - arm.rigify_layers[15].set = False + arm.rigify_layers[15].selset = False arm.rigify_layers[15].group = 4 arm.rigify_layers[16].name = "Leg.R (IK)" arm.rigify_layers[16].row = 10 - arm.rigify_layers[16].set = False + arm.rigify_layers[16].selset = False arm.rigify_layers[16].group = 2 arm.rigify_layers[17].name = "Leg.R (FK)" arm.rigify_layers[17].row = 11 - arm.rigify_layers[17].set = False + arm.rigify_layers[17].selset = False arm.rigify_layers[17].group = 5 arm.rigify_layers[18].name = "Leg.R (Tweak)" arm.rigify_layers[18].row = 12 - arm.rigify_layers[18].set = False + arm.rigify_layers[18].selset = False arm.rigify_layers[18].group = 4 arm.rigify_layers[19].name = "" arm.rigify_layers[19].row = 1 - arm.rigify_layers[19].set = False + arm.rigify_layers[19].selset = False arm.rigify_layers[19].group = 0 arm.rigify_layers[20].name = "" arm.rigify_layers[20].row = 1 - arm.rigify_layers[20].set = False + arm.rigify_layers[20].selset = False arm.rigify_layers[20].group = 0 arm.rigify_layers[21].name = "" arm.rigify_layers[21].row = 1 - arm.rigify_layers[21].set = False + arm.rigify_layers[21].selset = False arm.rigify_layers[21].group = 0 arm.rigify_layers[22].name = "" arm.rigify_layers[22].row = 1 - arm.rigify_layers[22].set = False + arm.rigify_layers[22].selset = False arm.rigify_layers[22].group = 0 arm.rigify_layers[23].name = "" arm.rigify_layers[23].row = 1 - arm.rigify_layers[23].set = False + arm.rigify_layers[23].selset = False arm.rigify_layers[23].group = 0 arm.rigify_layers[24].name = "" arm.rigify_layers[24].row = 1 - arm.rigify_layers[24].set = False + arm.rigify_layers[24].selset = False arm.rigify_layers[24].group = 0 arm.rigify_layers[25].name = "" arm.rigify_layers[25].row = 1 - arm.rigify_layers[25].set = False + arm.rigify_layers[25].selset = False arm.rigify_layers[25].group = 0 arm.rigify_layers[26].name = "" arm.rigify_layers[26].row = 1 - arm.rigify_layers[26].set = False + arm.rigify_layers[26].selset = False arm.rigify_layers[26].group = 0 arm.rigify_layers[27].name = "" arm.rigify_layers[27].row = 1 - arm.rigify_layers[27].set = False + arm.rigify_layers[27].selset = False arm.rigify_layers[27].group = 0 arm.rigify_layers[28].name = "Root" arm.rigify_layers[28].row = 14 - arm.rigify_layers[28].set = False + arm.rigify_layers[28].selset = False arm.rigify_layers[28].group = 1 bones = {} diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py index 3cdda311..aec675e3 100644 --- a/rigify/rig_ui_template.py +++ b/rigify/rig_ui_template.py @@ -20,6 +20,7 @@ UI_SLIDERS = ''' import bpy +from bpy.props import StringProperty from mathutils import Matrix, Vector from math import acos, pi, radians @@ -135,11 +136,11 @@ def get_pose_matrix_in_other_space(mat, pose_bone): par_rest = Matrix() # Get matrix in bone's current transform space - smat = rest_inv * (par_rest * (par_inv * mat)) + smat = rest_inv @ (par_rest @ (par_inv @ mat)) # Compensate for non-local location #if not pose_bone.bone.use_local_location: - # loc = smat.to_translation() * (par_rest.inverted() * rest).to_quaternion() + # loc = smat.to_translation() @ (par_rest.inverted() @ rest).to_quaternion() # smat.translation = loc return smat @@ -166,8 +167,8 @@ def set_pose_translation(pose_bone, mat): else: par_rest = Matrix() - q = (par_rest.inverted() * rest).to_quaternion() - pose_bone.location = q * loc + q = (par_rest.inverted() @ rest).to_quaternion() + pose_bone.location = q @ loc def set_pose_rotation(pose_bone, mat): @@ -283,11 +284,11 @@ def match_pole_target(ik_first, ik_last, pole, match_bone, length): angle = rotation_difference(ik_first.matrix, match_bone.matrix) # Try compensating for the rotation difference in both directions - pv1 = Matrix.Rotation(angle, 4, ikv) * pv + pv1 = Matrix.Rotation(angle, 4, ikv) @ pv set_pole(pv1) ang1 = rotation_difference(ik_first.matrix, match_bone.matrix) - pv2 = Matrix.Rotation(-angle, 4, ikv) * pv + pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv set_pole(pv2) ang2 = rotation_difference(ik_first.matrix, match_bone.matrix) @@ -424,8 +425,8 @@ def fk2ik_leg(obj, fk, ik): match_pose_scale(shin, shini) # Foot position - mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local - footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat + mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local + footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat set_pose_rotation(foot, footmat) set_pose_scale(foot, footmat) bpy.ops.object.mode_set(mode='OBJECT') @@ -442,8 +443,8 @@ def fk2ik_leg(obj, fk, ik): match_pose_scale(shin, shini) # Foot position - mat = mfoot.bone.matrix_local.inverted() * foot.bone.matrix_local - footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) * mat + mat = mfoot.bone.matrix_local.inverted() @ foot.bone.matrix_local + footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot) @ mat set_pose_rotation(foot, footmat) set_pose_scale(foot, footmat) bpy.ops.object.mode_set(mode='OBJECT') @@ -482,8 +483,8 @@ def ik2fk_leg(obj, fk, ik): set_pose_rotation(footroll, Matrix()) # Foot position - mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local - footmat = get_pose_matrix_in_other_space(foot.matrix, footi) * mat + mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local + footmat = get_pose_matrix_in_other_space(foot.matrix, footi) @ mat set_pose_translation(footi, footmat) set_pose_rotation(footi, footmat) set_pose_scale(footi, footmat) @@ -508,8 +509,8 @@ def ik2fk_leg(obj, fk, ik): set_pose_rotation(footroll, Matrix()) # Foot position - mat = mfooti.bone.matrix_local.inverted() * footi.bone.matrix_local - footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) * mat + mat = mfooti.bone.matrix_local.inverted() @ footi.bone.matrix_local + footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi) @ mat set_pose_translation(footi, footmat) set_pose_rotation(footi, footmat) set_pose_scale(footi, footmat) @@ -600,13 +601,13 @@ class Rigify_Arm_FK2IK(bpy.types.Operator): bl_label = "Rigify Snap FK arm to IK" bl_options = {'UNDO'} - uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name") - farm_fk = bpy.props.StringProperty(name="Forerm FK Name") - hand_fk = bpy.props.StringProperty(name="Hand FK Name") + uarm_fk: StringProperty(name="Upper Arm FK Name") + farm_fk: StringProperty(name="Forerm FK Name") + hand_fk: StringProperty(name="Hand FK Name") - uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name") - farm_ik = bpy.props.StringProperty(name="Forearm IK Name") - hand_ik = bpy.props.StringProperty(name="Hand IK Name") + uarm_ik: StringProperty(name="Upper Arm IK Name") + farm_ik: StringProperty(name="Forearm IK Name") + hand_ik: StringProperty(name="Hand IK Name") @classmethod def poll(cls, context): @@ -629,16 +630,16 @@ class Rigify_Arm_IK2FK(bpy.types.Operator): bl_label = "Rigify Snap IK arm to FK" bl_options = {'UNDO'} - uarm_fk = bpy.props.StringProperty(name="Upper Arm FK Name") - farm_fk = bpy.props.StringProperty(name="Forerm FK Name") - hand_fk = bpy.props.StringProperty(name="Hand FK Name") + uarm_fk: StringProperty(name="Upper Arm FK Name") + farm_fk: StringProperty(name="Forerm FK Name") + hand_fk: StringProperty(name="Hand FK Name") - uarm_ik = bpy.props.StringProperty(name="Upper Arm IK Name") - farm_ik = bpy.props.StringProperty(name="Forearm IK Name") - hand_ik = bpy.props.StringProperty(name="Hand IK Name") - pole = bpy.props.StringProperty(name="Pole IK Name") + uarm_ik: StringProperty(name="Upper Arm IK Name") + farm_ik: StringProperty(name="Forearm IK Name") + hand_ik: StringProperty(name="Hand IK Name") + pole : StringProperty(name="Pole IK Name") - main_parent = bpy.props.StringProperty(name="Main Parent", default="") + main_parent: StringProperty(name="Main Parent", default="") @classmethod def poll(cls, context): @@ -661,15 +662,15 @@ class Rigify_Leg_FK2IK(bpy.types.Operator): bl_label = "Rigify Snap FK leg to IK" bl_options = {'UNDO'} - thigh_fk = bpy.props.StringProperty(name="Thigh FK Name") - shin_fk = bpy.props.StringProperty(name="Shin FK Name") - foot_fk = bpy.props.StringProperty(name="Foot FK Name") - mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name") + thigh_fk: StringProperty(name="Thigh FK Name") + shin_fk: StringProperty(name="Shin FK Name") + foot_fk: StringProperty(name="Foot FK Name") + mfoot_fk: StringProperty(name="MFoot FK Name") - thigh_ik = bpy.props.StringProperty(name="Thigh IK Name") - shin_ik = bpy.props.StringProperty(name="Shin IK Name") - foot_ik = bpy.props.StringProperty(name="Foot IK Name") - mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name") + thigh_ik: StringProperty(name="Thigh IK Name") + shin_ik: StringProperty(name="Shin IK Name") + foot_ik: StringProperty(name="Foot IK Name") + mfoot_ik: StringProperty(name="MFoot IK Name") @classmethod def poll(cls, context): @@ -692,18 +693,18 @@ class Rigify_Leg_IK2FK(bpy.types.Operator): bl_label = "Rigify Snap IK leg to FK" bl_options = {'UNDO'} - thigh_fk = bpy.props.StringProperty(name="Thigh FK Name") - shin_fk = bpy.props.StringProperty(name="Shin FK Name") - mfoot_fk = bpy.props.StringProperty(name="MFoot FK Name") - foot_fk = bpy.props.StringProperty(name="Foot FK Name", default="") - thigh_ik = bpy.props.StringProperty(name="Thigh IK Name") - shin_ik = bpy.props.StringProperty(name="Shin IK Name") - foot_ik = bpy.props.StringProperty(name="Foot IK Name") - footroll = bpy.props.StringProperty(name="Foot Roll Name") - pole = bpy.props.StringProperty(name="Pole IK Name") - mfoot_ik = bpy.props.StringProperty(name="MFoot IK Name") + thigh_fk: StringProperty(name="Thigh FK Name") + shin_fk: StringProperty(name="Shin FK Name") + mfoot_fk: StringProperty(name="MFoot FK Name") + foot_fk: StringProperty(name="Foot FK Name", default="") + thigh_ik: StringProperty(name="Thigh IK Name") + shin_ik: StringProperty(name="Shin IK Name") + foot_ik: StringProperty(name="Foot IK Name") + footroll: StringProperty(name="Foot Roll Name") + pole: StringProperty(name="Pole IK Name") + mfoot_ik: StringProperty(name="MFoot IK Name") - main_parent = bpy.props.StringProperty(name="Main Parent", default="") + main_parent: StringProperty(name="Main Parent", default="") @classmethod def poll(cls, context): @@ -726,13 +727,14 @@ class Rigify_Rot2PoleSwitch(bpy.types.Operator): bl_idname = "pose.rigify_rot2pole_" + rig_id bl_label = "Rotation - Pole toggle" bl_description = "Toggles IK chain between rotation and pole target" - bone_name = bpy.props.StringProperty(default='') - limb_type = bpy.props.StringProperty(name="Limb Type") - controls = bpy.props.StringProperty(name="Controls string") - ik_ctrl = bpy.props.StringProperty(name="IK Controls string") - fk_ctrl = bpy.props.StringProperty(name="FK Controls string") - parent = bpy.props.StringProperty(name="Parent name") - pole = bpy.props.StringProperty(name="Pole name") + + bone_name: StringProperty(default='') + limb_type: StringProperty(name="Limb Type") + controls: StringProperty(name="Controls string") + ik_ctrl: StringProperty(name="IK Controls string") + fk_ctrl: StringProperty(name="FK Controls string") + parent: StringProperty(name="Parent name") + pole: StringProperty(name="Pole name") def execute(self, context): rig = context.object @@ -753,6 +755,7 @@ class RigUI(bpy.types.Panel): bl_region_type = 'UI' bl_label = "Rig Main Properties" bl_idname = rig_id + "_PT_rig_ui" + bl_category = 'View' @classmethod def poll(self, context): @@ -796,6 +799,7 @@ class RigLayers(bpy.types.Panel): bl_region_type = 'UI' bl_label = "Rig Layers" bl_idname = rig_id + "_PT_rig_layers" + bl_category = 'View' @classmethod def poll(self, context): @@ -841,23 +845,26 @@ class RigLayers(bpy.types.Panel): UI_REGISTER = ''' +classes = ( + Rigify_Arm_FK2IK, + Rigify_Arm_IK2FK, + Rigify_Leg_FK2IK, + Rigify_Leg_IK2FK, + Rigify_Rot2PoleSwitch, + RigUI, + RigLayers, +) + def register(): - bpy.utils.register_class(Rigify_Arm_FK2IK) - bpy.utils.register_class(Rigify_Arm_IK2FK) - bpy.utils.register_class(Rigify_Leg_FK2IK) - bpy.utils.register_class(Rigify_Leg_IK2FK) - bpy.utils.register_class(Rigify_Rot2PoleSwitch) - bpy.utils.register_class(RigUI) - bpy.utils.register_class(RigLayers) + from bpy.utils import register_class + for cls in classes: + register_class(cls) + def unregister(): - bpy.utils.unregister_class(Rigify_Arm_FK2IK) - bpy.utils.unregister_class(Rigify_Arm_IK2FK) - bpy.utils.unregister_class(Rigify_Leg_FK2IK) - bpy.utils.unregister_class(Rigify_Leg_IK2FK) - bpy.utils.register_class(Rigify_Rot2PoleSwitch) - bpy.utils.unregister_class(RigUI) - bpy.utils.unregister_class(RigLayers) + from bpy.utils import unregister_class + for cls in classes: + unregister_class(cls) register() ''' diff --git a/rigify/rigs/experimental/super_chain.py b/rigify/rigs/experimental/super_chain.py index 408bce3f..2b2c472b 100644 --- a/rigify/rigs/experimental/super_chain.py +++ b/rigify/rigs/experimental/super_chain.py @@ -115,11 +115,11 @@ class Rig: setattr(v,axis,scale) if reverse: - tail_vec = v * self.obj.matrix_world + tail_vec = v @ self.obj.matrix_world eb.head[:] = eb.tail eb.tail[:] = eb.head + tail_vec else: - tail_vec = v * self.obj.matrix_world + tail_vec = v @ self.obj.matrix_world eb.tail[:] = eb.head + tail_vec def create_pivot(self, bones=None, pivot=None): diff --git a/rigify/rigs/faces/super_face.py b/rigify/rigs/faces/super_face.py index 2c4999d5..b899c0b2 100644 --- a/rigify/rigs/faces/super_face.py +++ b/rigify/rigs/faces/super_face.py @@ -758,8 +758,10 @@ class Rig: 'DEF-chin.R' : 'lips.R', 'DEF-jaw.R.001' : 'chin.R', 'DEF-brow.T.L.003' : 'nose', + 'DEF-ear.L' : None, 'DEF-ear.L.003' : 'ear.L.004', 'DEF-ear.L.004' : 'ear.L', + 'DEF-ear.R' : None, 'DEF-ear.R.003' : 'ear.R.004', 'DEF-ear.R.004' : 'ear.R', 'DEF-lip.B.L.001' : 'lips.L', @@ -786,8 +788,9 @@ class Rig: pattern = r'^DEF-(\w+\.?\w?\.?\w?)(\.?)(\d*?)(\d?)$' for bone in [ bone for bone in all_bones['deform']['all'] if 'lid' not in bone ]: - if bone in list( def_specials.keys() ): - self.make_constraits('def_tweak', bone, def_specials[bone] ) + if bone in def_specials: + if def_specials[bone] is not None: + self.make_constraits('def_tweak', bone, def_specials[bone] ) else: matches = re.match( pattern, bone ).groups() if len( matches ) > 1 and matches[-1]: diff --git a/rigify/rigs/limbs/limb_utils.py b/rigify/rigs/limbs/limb_utils.py index 80588516..609da256 100644 --- a/rigify/rigs/limbs/limb_utils.py +++ b/rigify/rigs/limbs/limb_utils.py @@ -11,11 +11,11 @@ def orient_bone( cls, eb, axis, scale = 1.0, reverse = False ): setattr(v,axis,scale) if reverse: - tail_vec = v * cls.obj.matrix_world + tail_vec = v @ cls.obj.matrix_world eb.head[:] = eb.tail eb.tail[:] = eb.head + tail_vec else: - tail_vec = v * cls.obj.matrix_world + tail_vec = v @ cls.obj.matrix_world eb.tail[:] = eb.head + tail_vec eb.roll = 0.0 diff --git a/rigify/rigs/spines/super_spine.py b/rigify/rigs/spines/super_spine.py index 115d0450..6d28de69 100644 --- a/rigify/rigs/spines/super_spine.py +++ b/rigify/rigs/spines/super_spine.py @@ -123,11 +123,11 @@ class Rig: setattr(v, axis, scale) if reverse: - tail_vec = v * self.obj.matrix_world + tail_vec = v @ self.obj.matrix_world eb.head[:] = eb.tail eb.tail[:] = eb.head + tail_vec else: - tail_vec = v * self.obj.matrix_world + tail_vec = v @ self.obj.matrix_world eb.tail[:] = eb.head + tail_vec def create_pivot(self, pivot): diff --git a/rigify/rigs/widgets.py b/rigify/rigs/widgets.py index 8461d82a..aebe7139 100644 --- a/rigify/rigs/widgets.py +++ b/rigify/rigs/widgets.py @@ -4,7 +4,6 @@ import importlib from mathutils import Matrix from ..utils import create_widget -WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer. MODULE_NAME = "super_widgets" # Windows/Mac blender is weird, so __package__ doesn't work diff --git a/rigify/rot_mode.py b/rigify/rot_mode.py index e30e28d1..9abfecea 100644 --- a/rigify/rot_mode.py +++ b/rigify/rot_mode.py @@ -38,8 +38,10 @@ blender.stackexchange.com/questions/40711/how-to-convert-quaternions-keyframes-t # "category": "Animation"} import bpy - -order_list = ['QUATERNION', 'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX'] +from bpy.props import ( + BoolProperty, + EnumProperty, +) class convert(): @@ -219,32 +221,10 @@ class convert(): convert = convert() -# def initSceneProperties(scn): -# -# bpy.types.Scene.order_list = bpy.props.EnumProperty( -# items = [('QUATERNION', 'QUATERNION', 'QUATERNION' ), -# ('XYZ', 'XYZ', 'XYZ' ), -# ('XZY', 'XZY', 'XZY' ), -# ('YXZ', 'YXZ', 'YXZ' ), -# ('YZX', 'YZX', 'YZX' ), -# ('ZXY', 'ZXY', 'ZXY' ), -# ('ZYX', 'ZYX', 'ZYX' ) ], -# name = "Order", -# description = "The target rotation mode") -# -# scn['order_list'] = 0 -# -# return -# -# initSceneProperties(bpy.context.scene) - - -# GUI (Panel) -# class ToolsPanel(bpy.types.Panel): bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' - bl_category = "Tools" + bl_region_type = 'UI' + bl_category = 'View' bl_context = "posemode" bl_label = 'Rigify Quat/Euler Converter' @@ -290,7 +270,7 @@ class CONVERT_OT_quat2eu_current_action(bpy.types.Operator): obj = bpy.context.active_object pose_bones = bpy.context.selected_pose_bones action = obj.animation_data.action - order = order_list[bpy.context.scene['order_list']] + order = bpy.context.scene.order_list id_store = context.window_manager if id_store.rigify_convert_only_selected: @@ -315,7 +295,7 @@ class CONVERT_OT_quat2eu_all_actions(bpy.types.Operator): def execute(op, context): obj = bpy.context.active_object pose_bones = bpy.context.selected_pose_bones - order = order_list[bpy.context.scene['order_list']] + order = bpy.context.scene.order_list id_store = context.window_manager if id_store.rigify_convert_only_selected: @@ -326,9 +306,19 @@ class CONVERT_OT_quat2eu_all_actions(bpy.types.Operator): return {'FINISHED'} +### Registering ### + +classes = ( + ToolsPanel, + CONVERT_OT_quat2eu_current_action, + CONVERT_OT_quat2eu_all_actions, +) + + def register(): - IDStore = bpy.types.WindowManager + from bpy.utils import register_class + # Properties. items = [('QUATERNION', 'QUATERNION', 'QUATERNION'), ('XYZ', 'XYZ', 'XYZ'), ('XZY', 'XZY', 'XZY'), @@ -336,24 +326,27 @@ def register(): ('YZX', 'YZX', 'YZX'), ('ZXY', 'ZXY', 'ZXY'), ('ZYX', 'ZYX', 'ZYX')] + bpy.types.Scene.order_list = EnumProperty( + items=items, name='Convert to', + description="The target rotation mode", default='QUATERNION') - bpy.types.Scene.order_list = bpy.props.EnumProperty(items=items, name='Convert to', - description="The target rotation mode", default='QUATERNION') + IDStore = bpy.types.WindowManager + IDStore.rigify_convert_only_selected = BoolProperty( + name="Convert Only Selected", + description="Convert selected bones only", default=True) - IDStore.rigify_convert_only_selected = bpy.props.BoolProperty( - name="Convert Only Selected", description="Convert selected bones only", default=True) + # Classes. + for cls in classes: + register_class(cls) - bpy.utils.register_class(ToolsPanel) - bpy.utils.register_class(CONVERT_OT_quat2eu_current_action) - bpy.utils.register_class(CONVERT_OT_quat2eu_all_actions) def unregister(): - IDStore = bpy.types.WindowManager + from bpy.utils import unregister_class - bpy.utils.unregister_class(ToolsPanel) - bpy.utils.unregister_class(CONVERT_OT_quat2eu_current_action) - bpy.utils.unregister_class(CONVERT_OT_quat2eu_all_actions) + # Classes. + for cls in classes: + unregister_class(cls) + # Properties. + IDStore = bpy.types.WindowManager del IDStore.rigify_convert_only_selected - -# bpy.utils.register_module(__name__) diff --git a/rigify/ui.py b/rigify/ui.py index 76b1fd23..77398ff0 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -19,7 +19,13 @@ # <pep8 compliant> import bpy -from bpy.props import StringProperty +from bpy.props import ( + BoolProperty, + IntProperty, + EnumProperty, + StringProperty +) + from mathutils import Color from .utils import get_rig_type, MetarigError @@ -89,7 +95,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): if show_update_metarig: layout.label(text="This metarig contains old rig-types that can be automatically upgraded to benefit of rigify's new features.", icon='ERROR') - layout.label(text= "To use it as-is you need to enable legacy mode.",) + layout.label(text="To use it as-is you need to enable legacy mode.",) layout.operator("pose.rigify_upgrade_types", text="Upgrade Metarig") row = layout.row() @@ -111,7 +117,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): row = col.row(align=True) row.prop(id_store, "rigify_generate_mode", expand=True) - main_row = col.row(align=True).split(percentage=0.3) + main_row = col.row(align=True).split(factor=0.3) col1 = main_row.column() col2 = main_row.column() col1.label(text="Rig Name") @@ -208,14 +214,14 @@ class DATA_PT_rigify_layer_names(bpy.types.Panel): return # UI - main_row = layout.row(align=True).split(0.05) + main_row = layout.row(align=True).split(factor=0.05) col1 = main_row.column() col2 = main_row.column() col1.label() for i in range(32): if i == 16 or i == 29: col1.label() - col1.label(str(i+1) + '.') + col1.label(text=str(i+1) + '.') for i, rigify_layer in enumerate(arm.rigify_layers): # note: rigify_layer == arm.rigify_layers[i] @@ -234,8 +240,8 @@ class DATA_PT_rigify_layer_names(bpy.types.Panel): #row.prop(arm, "layers", index=i, text="Layer %d" % (i + 1), toggle=True, icon=icon) row.prop(rigify_layer, "name", text="") row.prop(rigify_layer, "row", text="UI Row") - icon = 'RADIOBUT_ON' if rigify_layer.set else 'RADIOBUT_OFF' - row.prop(rigify_layer, "set", text="", toggle=True, icon=icon) + icon = 'RADIOBUT_ON' if rigify_layer.selset else 'RADIOBUT_OFF' + row.prop(rigify_layer, "selset", text="", toggle=True, icon=icon) row.prop(rigify_layer, "group", text="Bone Group") else: row = col.row(align=True) @@ -247,8 +253,8 @@ class DATA_PT_rigify_layer_names(bpy.types.Panel): row1.prop(rigify_layer, "name", text="") row1.prop(rigify_layer, "row", text="UI Row") row1.enabled = False - icon = 'RADIOBUT_ON' if rigify_layer.set else 'RADIOBUT_OFF' - row.prop(rigify_layer, "set", text="", toggle=True, icon=icon) + icon = 'RADIOBUT_ON' if rigify_layer.selset else 'RADIOBUT_OFF' + row.prop(rigify_layer, "selset", text="", toggle=True, icon=icon) row.prop(rigify_layer, "group", text="Bone Group") if rigify_layer.group == 0: row.label(text='None') @@ -395,28 +401,29 @@ class DATA_OT_rigify_bone_group_add_theme(bpy.types.Operator): bl_label = "Rigify Add Bone Group color set from Theme" bl_options = {"REGISTER", "UNDO"} - theme = bpy.props.EnumProperty(items=(('THEME01', 'THEME01', ''), - ('THEME02', 'THEME02', ''), - ('THEME03', 'THEME03', ''), - ('THEME04', 'THEME04', ''), - ('THEME05', 'THEME05', ''), - ('THEME06', 'THEME06', ''), - ('THEME07', 'THEME07', ''), - ('THEME08', 'THEME08', ''), - ('THEME09', 'THEME09', ''), - ('THEME10', 'THEME10', ''), - ('THEME11', 'THEME11', ''), - ('THEME12', 'THEME12', ''), - ('THEME13', 'THEME13', ''), - ('THEME14', 'THEME14', ''), - ('THEME15', 'THEME15', ''), - ('THEME16', 'THEME16', ''), - ('THEME17', 'THEME17', ''), - ('THEME18', 'THEME18', ''), - ('THEME19', 'THEME19', ''), - ('THEME20', 'THEME20', '') - ), - name='Theme') + theme: EnumProperty(items=( + ('THEME01', 'THEME01', ''), + ('THEME02', 'THEME02', ''), + ('THEME03', 'THEME03', ''), + ('THEME04', 'THEME04', ''), + ('THEME05', 'THEME05', ''), + ('THEME06', 'THEME06', ''), + ('THEME07', 'THEME07', ''), + ('THEME08', 'THEME08', ''), + ('THEME09', 'THEME09', ''), + ('THEME10', 'THEME10', ''), + ('THEME11', 'THEME11', ''), + ('THEME12', 'THEME12', ''), + ('THEME13', 'THEME13', ''), + ('THEME14', 'THEME14', ''), + ('THEME15', 'THEME15', ''), + ('THEME16', 'THEME16', ''), + ('THEME17', 'THEME17', ''), + ('THEME18', 'THEME18', ''), + ('THEME19', 'THEME19', ''), + ('THEME20', 'THEME20', '') + ), + name='Theme') @classmethod def poll(cls, context): @@ -448,7 +455,7 @@ class DATA_OT_rigify_bone_group_remove(bpy.types.Operator): bl_idname = "armature.rigify_bone_group_remove" bl_label = "Rigify Remove Bone Group color set" - idx = bpy.props.IntProperty() + idx: IntProperty() @classmethod def poll(cls, context): @@ -492,9 +499,9 @@ class DATA_OT_rigify_bone_group_remove_all(bpy.types.Operator): class DATA_UL_rigify_bone_groups(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): row = layout.row(align=True) - row = row.split(percentage=0.1) + row = row.split(factor=0.1) row.label(text=str(index+1)) - row = row.split(percentage=0.7) + row = row.split(factor=0.7) row.prop(item, "name", text='', emboss=False) row = row.row(align=True) icon = 'LOCKED' if item.standard_colors_lock else 'UNLOCKED' @@ -547,8 +554,8 @@ class DATA_PT_rigify_bone_groups(bpy.types.Panel): row.template_list("DATA_UL_rigify_bone_groups", "", obj.data, "rigify_colors", obj.data, "rigify_colors_index") col = row.column(align=True) - col.operator("armature.rigify_bone_group_add", icon='ZOOMIN', text="") - col.operator("armature.rigify_bone_group_remove", icon='ZOOMOUT', text="").idx = obj.data.rigify_colors_index + col.operator("armature.rigify_bone_group_add", icon='ZOOM_IN', text="") + col.operator("armature.rigify_bone_group_remove", icon='ZOOM_OUT', text="").idx = obj.data.rigify_colors_index col.menu("DATA_MT_rigify_bone_groups_specials", icon='DOWNARROW_HLT', text="") row = layout.row() row.prop(armature, 'rigify_theme_to_add', text = 'Theme') @@ -626,9 +633,9 @@ class BONE_PT_rigify_buttons(bpy.types.Panel): class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel): bl_label = "Rigify Dev Tools" - bl_category = 'Tools' bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' + bl_region_type = 'UI' + bl_category = 'View' @classmethod def poll(cls, context): @@ -650,10 +657,10 @@ class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel): class VIEW3D_PT_rigify_animation_tools(bpy.types.Panel): bl_label = "Rigify Animation Tools" - bl_category = 'Tools' bl_context = "posemode" bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' + bl_region_type = 'UI' + bl_category = 'View' @classmethod def poll(cls, context): @@ -681,8 +688,8 @@ class VIEW3D_PT_rigify_animation_tools(bpy.types.Panel): row.operator("rigify.transfer_ik_to_fk", text='FK2IK Action', icon='ACTION_TWEAK') row = self.layout.row(align=True) - row.operator("rigify.clear_animation", text="Clear IK Action", icon='CANCEL').type = "IK" - row.operator("rigify.clear_animation", text="Clear FK Action", icon='CANCEL').type = "FK" + row.operator("rigify.clear_animation", text="Clear IK Action", icon='CANCEL').anim_type = "IK" + row.operator("rigify.clear_animation", text="Clear FK Action", icon='CANCEL').anim_type = "FK" row = self.layout.row(align=True) op = row.operator("rigify.rotation_pole", icon='FORCE_HARMONIC', text='Switch to pole') @@ -794,18 +801,17 @@ class SwitchToLegacy(bpy.types.Operator): class Sample(bpy.types.Operator): - """Create a sample metarig to be modified before generating """ \ - """the final rig""" + """Create a sample metarig to be modified before generating the final rig""" bl_idname = "armature.metarig_sample_add" bl_label = "Add a sample metarig for a rig type" bl_options = {'UNDO'} - metarig_type = StringProperty( - name="Type", - description="Name of the rig type to generate a sample of", - maxlen=128, - ) + metarig_type: StringProperty( + name="Type", + description="Name of the rig type to generate a sample of", + maxlen=128, + ) def execute(self, context): if context.mode == 'EDIT_ARMATURE' and self.metarig_type != "": @@ -1074,20 +1080,20 @@ def IktoFk(rig, window='ALL'): break -def clearAnimation(act, type, names): +def clearAnimation(act, anim_type, names): bones = [] for group in names: if names[group]['limb_type'] == 'arm': - if type == 'IK': + if anim_type == 'IK': bones.extend([names[group]['controls'][0], names[group]['controls'][4]]) - elif type == 'FK': + elif anim_type == 'FK': bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3]]) else: - if type == 'IK': + if anim_type == 'IK': bones.extend([names[group]['controls'][0], names[group]['controls'][6], names[group]['controls'][5], names[group]['controls'][4]]) - elif type == 'FK': + elif anim_type == 'FK': bones.extend([names[group]['controls'][1], names[group]['controls'][2], names[group]['controls'][3], names[group]['controls'][4]]) FCurves = [] @@ -1271,7 +1277,8 @@ class OBJECT_OT_ClearAnimation(bpy.types.Operator): bl_idname = "rigify.clear_animation" bl_label = "Clear Animation" bl_description = "Clear Animation For FK or IK Bones" - type = StringProperty() + + anim_type: StringProperty() def execute(self, context): @@ -1286,7 +1293,7 @@ class OBJECT_OT_ClearAnimation(bpy.types.Operator): if not act: return {'FINISHED'} - clearAnimation(act, self.type, names=get_limb_generated_names(rig)) + clearAnimation(act, self.anim_type, names=get_limb_generated_names(rig)) finally: context.user_preferences.edit.use_global_undo = use_global_undo return {'FINISHED'} @@ -1296,11 +1303,12 @@ class OBJECT_OT_Rot2Pole(bpy.types.Operator): bl_idname = "rigify.rotation_pole" bl_label = "Rotation - Pole toggle" bl_description = "Toggles IK chain between rotation and pole target" - bone_name = bpy.props.StringProperty(default='') - window = bpy.props.StringProperty(default='ALL') - toggle = bpy.props.BoolProperty(default=True) - value = bpy.props.BoolProperty(default=True) - bake = bpy.props.BoolProperty(default=True) + + bone_name: StringProperty(default='') + window: StringProperty(default='ALL') + toggle: BoolProperty(default=True) + value: BoolProperty(default=True) + bake: BoolProperty(default=True) def execute(self, context): rig = context.object @@ -1313,73 +1321,60 @@ class OBJECT_OT_Rot2Pole(bpy.types.Operator): return {'FINISHED'} +### Registering ### + + +classes = ( + DATA_OT_rigify_add_bone_groups, + DATA_OT_rigify_use_standard_colors, + DATA_OT_rigify_apply_selection_colors, + DATA_OT_rigify_bone_group_add, + DATA_OT_rigify_bone_group_add_theme, + DATA_OT_rigify_bone_group_remove, + DATA_OT_rigify_bone_group_remove_all, + DATA_UL_rigify_bone_groups, + DATA_MT_rigify_bone_groups_specials, + DATA_PT_rigify_bone_groups, + DATA_PT_rigify_layer_names, + DATA_PT_rigify_buttons, + BONE_PT_rigify_buttons, + VIEW3D_PT_rigify_animation_tools, + VIEW3D_PT_tools_rigify_dev, + LayerInit, + Generate, + UpgradeMetarigTypes, + SwitchToLegacy, + Sample, + EncodeMetarig, + EncodeMetarigSample, + EncodeWidget, + OBJECT_OT_GetFrameRange, + OBJECT_OT_FK2IK, + OBJECT_OT_IK2FK, + OBJECT_OT_TransferFKtoIK, + OBJECT_OT_TransferIKtoFK, + OBJECT_OT_ClearAnimation, + OBJECT_OT_Rot2Pole, +) + + def register(): + from bpy.utils import register_class - bpy.utils.register_class(DATA_OT_rigify_add_bone_groups) - bpy.utils.register_class(DATA_OT_rigify_use_standard_colors) - bpy.utils.register_class(DATA_OT_rigify_apply_selection_colors) - bpy.utils.register_class(DATA_OT_rigify_bone_group_add) - bpy.utils.register_class(DATA_OT_rigify_bone_group_add_theme) - bpy.utils.register_class(DATA_OT_rigify_bone_group_remove) - bpy.utils.register_class(DATA_OT_rigify_bone_group_remove_all) - bpy.utils.register_class(DATA_UL_rigify_bone_groups) - bpy.utils.register_class(DATA_MT_rigify_bone_groups_specials) - bpy.utils.register_class(DATA_PT_rigify_bone_groups) - bpy.utils.register_class(DATA_PT_rigify_layer_names) - bpy.utils.register_class(DATA_PT_rigify_buttons) - bpy.utils.register_class(BONE_PT_rigify_buttons) - bpy.utils.register_class(VIEW3D_PT_rigify_animation_tools) - bpy.utils.register_class(VIEW3D_PT_tools_rigify_dev) - bpy.utils.register_class(LayerInit) - bpy.utils.register_class(Generate) - bpy.utils.register_class(UpgradeMetarigTypes) - bpy.utils.register_class(SwitchToLegacy) - bpy.utils.register_class(Sample) - bpy.utils.register_class(EncodeMetarig) - bpy.utils.register_class(EncodeMetarigSample) - bpy.utils.register_class(EncodeWidget) - bpy.utils.register_class(OBJECT_OT_GetFrameRange) - bpy.utils.register_class(OBJECT_OT_FK2IK) - bpy.utils.register_class(OBJECT_OT_IK2FK) - bpy.utils.register_class(OBJECT_OT_TransferFKtoIK) - bpy.utils.register_class(OBJECT_OT_TransferIKtoFK) - bpy.utils.register_class(OBJECT_OT_ClearAnimation) - bpy.utils.register_class(OBJECT_OT_Rot2Pole) + # Classes. + for cls in classes: + register_class(cls) + # Sub-modules. rot_mode.register() def unregister(): + from bpy.utils import unregister_class - bpy.utils.unregister_class(DATA_OT_rigify_add_bone_groups) - bpy.utils.unregister_class(DATA_OT_rigify_use_standard_colors) - bpy.utils.unregister_class(DATA_OT_rigify_apply_selection_colors) - bpy.utils.unregister_class(DATA_OT_rigify_bone_group_add) - bpy.utils.unregister_class(DATA_OT_rigify_bone_group_add_theme) - bpy.utils.unregister_class(DATA_OT_rigify_bone_group_remove) - bpy.utils.unregister_class(DATA_OT_rigify_bone_group_remove_all) - bpy.utils.unregister_class(DATA_UL_rigify_bone_groups) - bpy.utils.unregister_class(DATA_MT_rigify_bone_groups_specials) - bpy.utils.unregister_class(DATA_PT_rigify_bone_groups) - bpy.utils.unregister_class(DATA_PT_rigify_layer_names) - bpy.utils.unregister_class(DATA_PT_rigify_buttons) - bpy.utils.unregister_class(BONE_PT_rigify_buttons) - bpy.utils.unregister_class(VIEW3D_PT_rigify_animation_tools) - bpy.utils.unregister_class(VIEW3D_PT_tools_rigify_dev) - bpy.utils.unregister_class(LayerInit) - bpy.utils.unregister_class(Generate) - bpy.utils.unregister_class(UpgradeMetarigTypes) - bpy.utils.unregister_class(SwitchToLegacy) - bpy.utils.unregister_class(Sample) - bpy.utils.unregister_class(EncodeMetarig) - bpy.utils.unregister_class(EncodeMetarigSample) - bpy.utils.unregister_class(EncodeWidget) - bpy.utils.unregister_class(OBJECT_OT_GetFrameRange) - bpy.utils.unregister_class(OBJECT_OT_FK2IK) - bpy.utils.unregister_class(OBJECT_OT_IK2FK) - bpy.utils.unregister_class(OBJECT_OT_TransferFKtoIK) - bpy.utils.unregister_class(OBJECT_OT_TransferIKtoFK) - bpy.utils.unregister_class(OBJECT_OT_ClearAnimation) - bpy.utils.unregister_class(OBJECT_OT_Rot2Pole) - + # Sub-modules. rot_mode.unregister() + + # Classes. + for cls in classes: + unregister_class(cls) diff --git a/rigify/utils.py b/rigify/utils.py index 1a711a3d..73b64112 100644 --- a/rigify/utils.py +++ b/rigify/utils.py @@ -38,8 +38,6 @@ DEF_PREFIX = "DEF-" # Prefix of deformation bones. WGT_PREFIX = "WGT-" # Prefix for widget objects ROOT_NAME = "root" # Name of the root bone. -WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer. - MODULE_NAME = "rigify" # Windows/Mac blender is weird, so __package__ doesn't work outdated_types = {"pitchipoy.limbs.super_limb": "limbs.super_limb", @@ -420,7 +418,7 @@ def obj_to_bone(obj, rig, bone_name): bone = rig.data.bones[bone_name] - mat = rig.matrix_world * bone.matrix_local + mat = rig.matrix_world @ bone.matrix_local obj.location = mat.to_translation() @@ -440,6 +438,7 @@ def create_widget(rig, bone_name, bone_transform_name=None): obj_name = WGT_PREFIX + rig.name + '_' + bone_name scene = bpy.context.scene + collection = bpy.context.collection id_store = bpy.context.window_manager # Check if it already exists in the scene @@ -460,14 +459,13 @@ def create_widget(rig, bone_name, bone_transform_name=None): # Create mesh object mesh = bpy.data.meshes.new(obj_name) obj = bpy.data.objects.new(obj_name, mesh) - scene.objects.link(obj) + collection.objects.link(obj) # Move object to bone position and set layers obj_to_bone(obj, rig, bone_transform_name) wgts_group_name = 'WGTS_' + rig.name if wgts_group_name in bpy.data.objects.keys(): obj.parent = bpy.data.objects[wgts_group_name] - obj.layers = WGT_LAYERS return obj @@ -802,8 +800,8 @@ def align_bone_roll(obj, bone1, bone2): rot_mat = Matrix.Rotation(angle, 3, axis) # Roll factor - x3 = rot_mat * x1 - dot = x2 * x3 + x3 = rot_mat @ x1 + dot = x2 @ x3 if dot > 1.0: dot = 1.0 elif dot < -1.0: @@ -814,8 +812,8 @@ def align_bone_roll(obj, bone1, bone2): bone1_e.roll = roll # Check if we rolled in the right direction - x3 = rot_mat * bone1_e.x_axis - check = x2 * x3 + x3 = rot_mat @ bone1_e.x_axis + check = x2 @ x3 # If not, reverse if check < 0.9999: @@ -1026,11 +1024,11 @@ def write_metarig(obj, layers=False, func_name="create", groups=False): for i in range(len(arm.rigify_layers)): name = arm.rigify_layers[i].name row = arm.rigify_layers[i].row - set = arm.rigify_layers[i].set + selset = arm.rigify_layers[i].selset group = arm.rigify_layers[i].group code.append(' arm.rigify_layers[' + str(i) + '].name = "' + name + '"') code.append(' arm.rigify_layers[' + str(i) + '].row = ' + str(row)) - code.append(' arm.rigify_layers[' + str(i) + '].set = ' + str(set)) + code.append(' arm.rigify_layers[' + str(i) + '].selset = ' + str(selset)) code.append(' arm.rigify_layers[' + str(i) + '].group = ' + str(group)) # write parents first @@ -1262,3 +1260,43 @@ def overwrite_prop_animation(rig, bone, prop_name, value, frames): for kp in curve.keyframe_points: if kp.co[0] in frames: kp.co[1] = value + + +def find_layer_collection_by_collection(layer_collection, collection): + if collection == layer_collection.collection: + return layer_collection + + # go recursive + for child in layer_collection.children: + layer_collection = find_layer_collection_by_collection(child, collection) + if layer_collection: + return layer_collection + + +def ensure_widget_collection(context): + wgts_collection_name = "Widgets" + + view_layer = context.view_layer + layer_collection = bpy.context.layer_collection + collection = layer_collection.collection + + widget_collection = bpy.data.collections.get(wgts_collection_name) + if not widget_collection: + # ------------------------------------------ + # Create the widget collection + widget_collection = bpy.data.collections.new(wgts_collection_name) + widget_collection.hide_viewport = True + widget_collection.hide_render = True + + widget_layer_collection = None + else: + widget_layer_collection = find_layer_collection_by_collection(view_layer.layer_collection, widget_collection) + + if not widget_layer_collection: + # Add the widget collection to the tree + collection.children.link(widget_collection) + widget_layer_collection = [c for c in layer_collection.children if c.collection == widget_collection][0] + + # Make the widget the active collection for the upcoming added (widget) objects + view_layer.active_layer_collection = widget_layer_collection + return widget_collection diff --git a/sequencer_kinoraw_tools/ui.py b/sequencer_kinoraw_tools/ui.py index 9e7f2762..221f4f47 100644 --- a/sequencer_kinoraw_tools/ui.py +++ b/sequencer_kinoraw_tools/ui.py @@ -321,7 +321,7 @@ class JumptoCut(Panel): if prefs.kr_mini_ui: row = layout.row(align=True) row.operator("sequencerextra.extrasnap", text="", icon="SNAP_ON").align = 0 - row.operator("sequencerextra.extrasnap", text="", icon="SNAP_SURFACE").align = 1 + row.operator("sequencerextra.extrasnap", text="", icon="NONE").align = 1 row.operator("sequencerextra.extrasnap", text="", icon="SNAP_ON").align = 2 row.separator() diff --git a/space_clip_editor_refine_solution.py b/space_clip_editor_refine_solution.py index a3287b78..2287714b 100644 --- a/space_clip_editor_refine_solution.py +++ b/space_clip_editor_refine_solution.py @@ -18,12 +18,14 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + bl_info = { "name": "Refine tracking solution", "author": "Stephen Leger", "license": "GPL", - "version": (1, 1, 3), - "blender": (2, 7, 8), + "version": (1, 1, 4), + "blender": (2, 80, 0), "location": "Clip Editor > Tools > Solve > Refine Solution", "description": "Refine motion solution by setting track weight according" " to reprojection error", @@ -70,10 +72,9 @@ class OP_Tracking_refine_solution(Operator): marker_position = Vector() for frame in range(start, end): - camera = tracking.reconstruction.cameras.find_frame(frame) + camera = tracking.reconstruction.cameras.find_frame(frame=frame) if camera is not None: - imat = camera.matrix.inverted() - projection_matrix = imat.transposed() + camera_invert = camera.matrix.inverted() else: continue @@ -102,7 +103,7 @@ class OP_Tracking_refine_solution(Operator): else: tw = 1.0 - reprojected_position = track.bundle * projection_matrix + reprojected_position = camera_invert @ track.bundle if reprojected_position.z == 0: track.weight = 0 track.keyframe_insert("weight", frame=frame) @@ -152,11 +153,11 @@ class OP_Tracking_reset_solution(Operator): start = tracking.reconstruction.cameras[0].frame end = tracking.reconstruction.cameras[-1].frame for frame in range(start, end): - camera = tracking.reconstruction.cameras.find_frame(frame) + camera = tracking.reconstruction.cameras.find_frame(frame=frame) if camera is None: continue for track in tracking.tracks: - marker = track.markers.find_frame(frame) + marker = track.markers.find_frame(frame=frame) if marker is None: continue track.weight = 1.0 @@ -191,6 +192,13 @@ class RefineMotionTrackingPanel(Panel): row.operator("tracking.reset_solution") +classes =( + OP_Tracking_refine_solution, + OP_Tracking_reset_solution, + RefineMotionTrackingPanel + ) + + def register(): bpy.types.WindowManager.TrackingTargetError = FloatProperty( name="Target Error", @@ -204,11 +212,13 @@ def register(): default=25, min=1 ) - bpy.utils.register_module(__name__) + for cls in classes: + bpy.utils.register_class(cls) def unregister(): - bpy.utils.unregister_module(__name__) + for cls in reversed(classes): + bpy.utils.unregister_class(cls) del bpy.types.WindowManager.TrackingTargetError del bpy.types.WindowManager.TrackingSmooth diff --git a/space_view3d_3d_navigation.py b/space_view3d_3d_navigation.py index a4c359cc..8ed4bf00 100644 --- a/space_view3d_3d_navigation.py +++ b/space_view3d_3d_navigation.py @@ -252,9 +252,9 @@ class VIEW3D_PT_3dnavigationPanel(Panel): col.operator("view3d.view_selected", text="View to Selected") col = layout.column(align=True) - col.label(text="Cursor:", icon="CURSOR") + col.label(text="Cursor:", icon='PIVOT_CURSOR') row = col.row(align=True) - row.operator("view3d.snap_cursor_to_center", text="Center") + row.operator("view3d.snap_cursor_to_center", text="World Origin") row.operator("view3d.view_center_cursor", text="View") col.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected") @@ -299,8 +299,8 @@ class VIEW3D_PT_pan_navigation1(Panel): row = layout.row() row.label(text="Zoom:") row = layout.row() - row.operator("opr.zoom_in_view1", text="In", icon="ZOOMIN") - row.operator("opr.zoom_out_view1", text="Out", icon="ZOOMOUT") + row.operator("opr.zoom_in_view1", text="In", icon='ADD') + row.operator("opr.zoom_out_view1", text="Out", icon='REMOVE') row = layout.row() row.label(text="Roll:") diff --git a/space_view3d_copy_attributes.py b/space_view3d_copy_attributes.py index 762eb953..7037354c 100644 --- a/space_view3d_copy_attributes.py +++ b/space_view3d_copy_attributes.py @@ -22,7 +22,7 @@ bl_info = { "name": "Copy Attributes Menu", "author": "Bassam Kurdali, Fabian Fricke, Adam Wiseman", "version": (0, 4, 8), - "blender": (2, 63, 0), + "blender": (2, 80, 0), "location": "View3D > Ctrl-C", "description": "Copy Attributes Menu from Blender 2.4", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" @@ -33,13 +33,13 @@ bl_info = { import bpy from mathutils import Matrix from bpy.types import ( - Operator, - Menu, - ) + Operator, + Menu, +) from bpy.props import ( - BoolVectorProperty, - StringProperty, - ) + BoolVectorProperty, + StringProperty, +) # First part of the operator Info message INFO_MESSAGE = "Copy Attributes: " @@ -82,7 +82,7 @@ def genops(copylist, oplist, prefix, poll_func, loopfunc): exec_func = build_exec(loopfunc, op[3]) invoke_func = build_invoke(loopfunc, op[3]) opclass = build_op(prefix + op[0], "Copy " + op[1], op[2], - poll_func, exec_func, invoke_func) + poll_func, exec_func, invoke_func) oplist.append(opclass) @@ -101,22 +101,25 @@ def getmat(bone, active, context, ignoreparent): """Helper function for visual transform copy, gets the active transform in bone space """ - obj_act = context.active_object - data_bone = obj_act.data.bones[bone.name] + obj_bone = bone.id_data + obj_active = active.id_data + data_bone = obj_bone.data.bones[bone.name] # all matrices are in armature space unless commented otherwise - otherloc = active.matrix # final 4x4 mat of target, location. + active_to_selected = obj_bone.matrix_world.inverted() @ obj_active.matrix_world + active_matrix = active_to_selected @ active.matrix + otherloc = active_matrix # final 4x4 mat of target, location. bonemat_local = data_bone.matrix_local.copy() # self rest matrix if data_bone.parent: - parentposemat = obj_act.pose.bones[data_bone.parent.name].matrix.copy() + parentposemat = obj_bone.pose.bones[data_bone.parent.name].matrix.copy() parentbonemat = data_bone.parent.matrix_local.copy() else: parentposemat = parentbonemat = Matrix() if parentbonemat == parentposemat or ignoreparent: - newmat = bonemat_local.inverted() * otherloc + newmat = bonemat_local.inverted() @ otherloc else: - bonemat = parentbonemat.inverted() * bonemat_local + bonemat = parentbonemat.inverted() @ bonemat_local - newmat = bonemat.inverted() * parentposemat.inverted() * otherloc + newmat = bonemat.inverted() @ parentposemat.inverted() @ otherloc return newmat @@ -160,14 +163,16 @@ def pVisLocExec(bone, active, context): def pVisRotExec(bone, active, context): + obj_bone = bone.id_data rotcopy(bone, getmat(bone, active, - context, not context.active_object.data.bones[bone.name].use_inherit_rotation)) + context, not obj_bone.data.bones[bone.name].use_inherit_rotation)) def pVisScaExec(bone, active, context): + obj_bone = bone.id_data bone.scale = getmat(bone, active, context, - not context.active_object.data.bones[bone.name].use_inherit_scale)\ - .to_scale() + not obj_bone.data.bones[bone.name].use_inherit_scale)\ + .to_scale() def pDrwExec(bone, active, context): @@ -207,30 +212,29 @@ def pBBonesExec(bone, active, context): pose_copies = ( - ('pose_loc_loc', "Local Location", - "Copy Location from Active to Selected", pLocLocExec), - ('pose_loc_rot', "Local Rotation", - "Copy Rotation from Active to Selected", pLocRotExec), - ('pose_loc_sca', "Local Scale", - "Copy Scale from Active to Selected", pLocScaExec), - ('pose_vis_loc', "Visual Location", - "Copy Location from Active to Selected", pVisLocExec), - ('pose_vis_rot', "Visual Rotation", - "Copy Rotation from Active to Selected", pVisRotExec), - ('pose_vis_sca', "Visual Scale", - "Copy Scale from Active to Selected", pVisScaExec), - ('pose_drw', "Bone Shape", - "Copy Bone Shape from Active to Selected", pDrwExec), - ('pose_lok', "Protected Transform", - "Copy Protected Transforms from Active to Selected", pLokExec), - ('pose_con', "Bone Constraints", - "Copy Object Constraints from Active to Selected", pConExec), - ('pose_iks', "IK Limits", - "Copy IK Limits from Active to Selected", pIKsExec), - ('bbone_settings', "BBone Settings", - "Copy BBone Settings from Active to Selected", pBBonesExec), - ) - + ('pose_loc_loc', "Local Location", + "Copy Location from Active to Selected", pLocLocExec), + ('pose_loc_rot', "Local Rotation", + "Copy Rotation from Active to Selected", pLocRotExec), + ('pose_loc_sca', "Local Scale", + "Copy Scale from Active to Selected", pLocScaExec), + ('pose_vis_loc', "Visual Location", + "Copy Location from Active to Selected", pVisLocExec), + ('pose_vis_rot', "Visual Rotation", + "Copy Rotation from Active to Selected", pVisRotExec), + ('pose_vis_sca', "Visual Scale", + "Copy Scale from Active to Selected", pVisScaExec), + ('pose_drw', "Bone Shape", + "Copy Bone Shape from Active to Selected", pDrwExec), + ('pose_lok', "Protected Transform", + "Copy Protected Transforms from Active to Selected", pLokExec), + ('pose_con', "Bone Constraints", + "Copy Object Constraints from Active to Selected", pConExec), + ('pose_iks', "IK Limits", + "Copy IK Limits from Active to Selected", pIKsExec), + ('bbone_settings', "BBone Settings", + "Copy BBone Settings from Active to Selected", pBBonesExec), +) @classmethod def pose_poll_func(cls, context): @@ -248,10 +252,10 @@ class CopySelectedPoseConstraints(Operator): bl_idname = "pose.copy_selected_constraints" bl_label = "Copy Selected Constraints" - selection = BoolVectorProperty( - size=32, - options={'SKIP_SAVE'} - ) + selection: BoolVectorProperty( + size=32, + options={'SKIP_SAVE'} + ) poll = pose_poll_func invoke = pose_invoke_func @@ -260,7 +264,7 @@ class CopySelectedPoseConstraints(Operator): layout = self.layout for idx, const in enumerate(context.active_pose_bone.constraints): layout.prop(self, "selection", index=idx, text=const.name, - toggle=True) + toggle=True) def execute(self, context): active = context.active_pose_bone @@ -271,8 +275,8 @@ class CopySelectedPoseConstraints(Operator): if flag: old_constraint = active.constraints[index] new_constraint = bone.constraints.new( - active.constraints[index].type - ) + active.constraints[index].type + ) generic_copy(old_constraint, new_constraint) return {'FINISHED'} @@ -306,9 +310,9 @@ def obLoopExec(self, context, funk): def world_to_basis(active, ob, context): """put world coords of active as basis coords of ob""" - local = ob.parent.matrix_world.inverted() * active.matrix_world - P = ob.matrix_basis * ob.matrix_local.inverted() - mat = P * local + local = ob.parent.matrix_world.inverted() @ active.matrix_world + P = ob.matrix_basis @ ob.matrix_local.inverted() + mat = P @ local return(mat) @@ -355,17 +359,17 @@ def obVisSca(ob, active, context): def obDrw(ob, active, context): - ob.draw_type = active.draw_type + ob.display_type = active.display_type ob.show_axis = active.show_axis ob.show_bounds = active.show_bounds - ob.draw_bounds_type = active.draw_bounds_type + ob.display_bounds_type = active.display_bounds_type ob.show_name = active.show_name ob.show_texture_space = active.show_texture_space ob.show_transparent = active.show_transparent ob.show_wire = active.show_wire - ob.show_x_ray = active.show_x_ray - ob.empty_draw_type = active.empty_draw_type - ob.empty_draw_size = active.empty_draw_size + ob.show_in_front = active.show_in_front + ob.empty_display_type = active.empty_display_type + ob.empty_display_size = active.empty_display_size def obOfs(ob, active, context): @@ -382,11 +386,6 @@ def obCol(ob, active, context): ob.color = active.color -def obMas(ob, active, context): - ob.game.mass = active.game.mass - return('INFO', "Mass copied") - - def obLok(ob, active, context): for index, state in enumerate(active.lock_location): ob.lock_location[index] = state @@ -429,13 +428,13 @@ def obMod(ob, active, context): ob.modifiers.remove(modifier) for old_modifier in active.modifiers.values(): new_modifier = ob.modifiers.new(name=old_modifier.name, - type=old_modifier.type) + type=old_modifier.type) generic_copy(old_modifier, new_modifier) return('INFO', "Modifiers copied") def obGrp(ob, active, context): - for grp in bpy.data.groups: + for grp in bpy.data.collections: if active.name in grp.objects and ob.name not in grp.objects: grp.objects.link(ob) return('INFO', "Groups copied") @@ -483,62 +482,60 @@ def obWei(ob, active, context): for vgs in range(0, len(groups)): if groups[vgs].name == groupName: groups[vgs].add((v.index,), - vgroupIndex_weight[i][1], "REPLACE") + vgroupIndex_weight[i][1], "REPLACE") return('INFO', "Weights copied") object_copies = ( - # ('obj_loc', "Location", - # "Copy Location from Active to Selected", obLoc), - # ('obj_rot', "Rotation", - # "Copy Rotation from Active to Selected", obRot), - # ('obj_sca', "Scale", - # "Copy Scale from Active to Selected", obSca), - ('obj_vis_loc', "Location", - "Copy Location from Active to Selected", obVisLoc), - ('obj_vis_rot', "Rotation", - "Copy Rotation from Active to Selected", obVisRot), - ('obj_vis_sca', "Scale", - "Copy Scale from Active to Selected", obVisSca), - ('obj_drw', "Draw Options", - "Copy Draw Options from Active to Selected", obDrw), - ('obj_ofs', "Time Offset", - "Copy Time Offset from Active to Selected", obOfs), - ('obj_dup', "Dupli", - "Copy Dupli from Active to Selected", obDup), - ('obj_col', "Object Color", - "Copy Object Color from Active to Selected", obCol), - ('obj_mas', "Mass", - "Copy Mass from Active to Selected", obMas), - # ('obj_dmp', "Damping", - # "Copy Damping from Active to Selected"), - # ('obj_all', "All Physical Attributes", - # "Copy Physical Attributes from Active to Selected"), - # ('obj_prp', "Properties", - # "Copy Properties from Active to Selected"), - # ('obj_log', "Logic Bricks", - # "Copy Logic Bricks from Active to Selected"), - ('obj_lok', "Protected Transform", - "Copy Protected Transforms from Active to Selected", obLok), - ('obj_con', "Object Constraints", - "Copy Object Constraints from Active to Selected", obCon), - # ('obj_nla', "NLA Strips", - # "Copy NLA Strips from Active to Selected"), - # ('obj_tex', "Texture Space", - # "Copy Texture Space from Active to Selected", obTex), - # ('obj_sub', "Subsurf Settings", - # "Copy Subsurf Settings from Active to Selected"), - # ('obj_smo', "AutoSmooth", - # "Copy AutoSmooth from Active to Selected"), - ('obj_idx', "Pass Index", - "Copy Pass Index from Active to Selected", obIdx), - ('obj_mod', "Modifiers", - "Copy Modifiers from Active to Selected", obMod), - ('obj_wei', "Vertex Weights", - "Copy vertex weights based on indices", obWei), - ('obj_grp', "Group Links", - "Copy selected into active object's groups", obGrp) - ) + # ('obj_loc', "Location", + # "Copy Location from Active to Selected", obLoc), + # ('obj_rot', "Rotation", + # "Copy Rotation from Active to Selected", obRot), + # ('obj_sca', "Scale", + # "Copy Scale from Active to Selected", obSca), + ('obj_vis_loc', "Location", + "Copy Location from Active to Selected", obVisLoc), + ('obj_vis_rot', "Rotation", + "Copy Rotation from Active to Selected", obVisRot), + ('obj_vis_sca', "Scale", + "Copy Scale from Active to Selected", obVisSca), + ('obj_drw', "Draw Options", + "Copy Draw Options from Active to Selected", obDrw), + ('obj_ofs', "Time Offset", + "Copy Time Offset from Active to Selected", obOfs), + ('obj_dup', "Dupli", + "Copy Dupli from Active to Selected", obDup), + ('obj_col', "Object Color", + "Copy Object Color from Active to Selected", obCol), + # ('obj_dmp', "Damping", + # "Copy Damping from Active to Selected"), + # ('obj_all', "All Physical Attributes", + # "Copy Physical Attributes from Active to Selected"), + # ('obj_prp', "Properties", + # "Copy Properties from Active to Selected"), + # ('obj_log', "Logic Bricks", + # "Copy Logic Bricks from Active to Selected"), + ('obj_lok', "Protected Transform", + "Copy Protected Transforms from Active to Selected", obLok), + ('obj_con', "Object Constraints", + "Copy Object Constraints from Active to Selected", obCon), + # ('obj_nla', "NLA Strips", + # "Copy NLA Strips from Active to Selected"), + # ('obj_tex', "Texture Space", + # "Copy Texture Space from Active to Selected", obTex), + # ('obj_sub', "Subsurf Settings", + # "Copy Subsurf Settings from Active to Selected"), + # ('obj_smo', "AutoSmooth", + # "Copy AutoSmooth from Active to Selected"), + ('obj_idx', "Pass Index", + "Copy Pass Index from Active to Selected", obIdx), + ('obj_mod', "Modifiers", + "Copy Modifiers from Active to Selected", obMod), + ('obj_wei', "Vertex Weights", + "Copy vertex weights based on indices", obWei), + ('obj_grp', "Group Links", + "Copy selected into active object's groups", obGrp) +) @classmethod @@ -557,10 +554,10 @@ class CopySelectedObjectConstraints(Operator): bl_idname = "object.copy_selected_constraints" bl_label = "Copy Selected Constraints" - selection = BoolVectorProperty( - size=32, - options={'SKIP_SAVE'} - ) + selection: BoolVectorProperty( + size=32, + options={'SKIP_SAVE'} + ) poll = object_poll_func invoke = object_invoke_func @@ -569,7 +566,7 @@ class CopySelectedObjectConstraints(Operator): layout = self.layout for idx, const in enumerate(context.active_object.constraints): layout.prop(self, "selection", index=idx, text=const.name, - toggle=True) + toggle=True) def execute(self, context): active = context.active_object @@ -580,8 +577,8 @@ class CopySelectedObjectConstraints(Operator): if flag: old_constraint = active.constraints[index] new_constraint = obj.constraints.new( - active.constraints[index].type - ) + active.constraints[index].type + ) generic_copy(old_constraint, new_constraint) return{'FINISHED'} @@ -591,10 +588,10 @@ class CopySelectedObjectModifiers(Operator): bl_idname = "object.copy_selected_modifiers" bl_label = "Copy Selected Modifiers" - selection = BoolVectorProperty( - size=32, - options={'SKIP_SAVE'} - ) + selection: BoolVectorProperty( + size=32, + options={'SKIP_SAVE'} + ) poll = object_poll_func invoke = object_invoke_func @@ -603,7 +600,7 @@ class CopySelectedObjectModifiers(Operator): layout = self.layout for idx, const in enumerate(context.active_object.modifiers): layout.prop(self, 'selection', index=idx, text=const.name, - toggle=True) + toggle=True) def execute(self, context): active = context.active_object @@ -614,9 +611,9 @@ class CopySelectedObjectModifiers(Operator): if flag: old_modifier = active.modifiers[index] new_modifier = obj.modifiers.new( - type=active.modifiers[index].type, - name=active.modifiers[index].name - ) + type=active.modifiers[index].type, + name=active.modifiers[index].name + ) generic_copy(old_modifier, new_modifier) return{'FINISHED'} @@ -664,23 +661,23 @@ class MESH_MT_CopyFaceSettings(Menu): layout = self.layout op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname, - text="Copy Material") + text="Copy Material") op['layer'] = '' op['mode'] = 'MAT' if mesh.uv_textures.active: op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname, - text="Copy Active UV Image") + text="Copy Active UV Image") op['layer'] = '' op['mode'] = 'IMAGE' op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname, - text="Copy Active UV Coords") + text="Copy Active UV Coords") op['layer'] = '' op['mode'] = 'UV' if mesh.vertex_colors.active: op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname, - text="Copy Active Vertex Colors") + text="Copy Active Vertex Colors") op['layer'] = '' op['mode'] = 'VCOL' @@ -704,7 +701,7 @@ class MESH_MT_CopyImagesFromLayer(Menu): def poll(cls, context): obj = context.active_object return obj and obj.mode == "EDIT_MESH" and len( - obj.data.uv_layers) > 1 + obj.data.uv_layers) > 1 def draw(self, context): mesh = context.active_object.data @@ -718,7 +715,7 @@ class MESH_MT_CopyUVCoordsFromLayer(Menu): def poll(cls, context): obj = context.active_object return obj and obj.mode == "EDIT_MESH" and len( - obj.data.uv_layers) > 1 + obj.data.uv_layers) > 1 def draw(self, context): mesh = context.active_object.data @@ -732,7 +729,7 @@ class MESH_MT_CopyVertexColorsFromLayer(Menu): def poll(cls, context): obj = context.active_object return obj and obj.mode == "EDIT_MESH" and len( - obj.data.vertex_colors) > 1 + obj.data.vertex_colors) > 1 def draw(self, context): mesh = context.active_object.data @@ -759,14 +756,14 @@ class MESH_OT_CopyFaceSettings(Operator): bl_label = "Copy Face Settings" bl_options = {'REGISTER', 'UNDO'} - mode = StringProperty( - name="Mode", - options={"HIDDEN"}, - ) - layer = StringProperty( - name="Layer", - options={"HIDDEN"}, - ) + mode: StringProperty( + name="Mode", + options={"HIDDEN"}, + ) + layer: StringProperty( + name="Layer", + options={"HIDDEN"}, + ) @classmethod def poll(cls, context): @@ -838,8 +835,25 @@ class MESH_OT_CopyFaceSettings(Operator): return(retval) +classes = ( + CopySelectedPoseConstraints, + VIEW3D_MT_posecopypopup, + CopySelectedObjectConstraints, + CopySelectedObjectModifiers, + VIEW3D_MT_copypopup, + MESH_MT_CopyFaceSettings, + MESH_MT_CopyImagesFromLayer, + MESH_MT_CopyUVCoordsFromLayer, + MESH_MT_CopyVertexColorsFromLayer, + MESH_OT_CopyFaceSettings, + *pose_ops, + *object_ops, +) + def register(): - bpy.utils.register_module(__name__) + from bpy.utils import register_class + for cls in classes: + register_class(cls) # mostly to get the keymap working kc = bpy.context.window_manager.keyconfigs.addon @@ -888,7 +902,9 @@ def unregister(): if kmi.properties.name == 'VIEW3D_MT_copypopup': km.keymap_items.remove(kmi) - bpy.utils.unregister_module(__name__) + from bpy.utils import unregister_class + for cls in classes: + unregister_class(cls) if __name__ == "__main__": diff --git a/space_view3d_display_tools/__init__.py b/space_view3d_display_tools/__init__.py index 58699f98..462f417e 100644 --- a/space_view3d_display_tools/__init__.py +++ b/space_view3d_display_tools/__init__.py @@ -80,7 +80,7 @@ class DisplayToolsPanel(Panel): bl_category = "Display" bl_options = {'DEFAULT_CLOSED'} - draw_type_icons = { + display_type_icons = { 'BOUNDS': 'BBOX', 'WIRE': 'WIRE', 'SOLID': 'SOLID', @@ -142,7 +142,7 @@ class DisplayToolsPanel(Panel): col.prop(view, "show_all_objects_origin", toggle=True) col.prop(view, "show_backface_culling", toggle=True) if obj: - col.prop(obj, "show_x_ray", text="X-Ray", toggle=True) + col.prop(obj, "show_in_front", text="X-Ray", toggle=True) if obj and obj_type == 'MESH': col.prop(obj, "show_transparent", text="Transparency", toggle=True) @@ -176,8 +176,8 @@ class DisplayToolsPanel(Panel): if obj: col = layout.column(align=True) col.alignment = 'EXPAND' - col.label(text="Maximum:") - col.prop(obj, "draw_type", text="", icon=self.draw_type_icons[obj.draw_type]) + col.label(text="Display As:") + col.prop(obj, "display_type", text="", icon=self.display_type_icons[obj.display_type]) col = layout.column(align=True) col.alignment = 'CENTER' @@ -394,7 +394,7 @@ class DisplayToolsPanel(Panel): row.prop(display_tools, "UiTabDrop", index=4, text="Selection", icon=icon_active_4) if not SELECT2DROP: - row.operator("view3d.select_border", text="", icon="MESH_PLANE") + row.operator("view3d.select_box", text="", icon="MESH_PLANE") row.operator("view3d.select_circle", text="", icon="MESH_CIRCLE") row.label(text="", icon="BLANK1") else: diff --git a/space_view3d_display_tools/display.py b/space_view3d_display_tools/display.py index d0310ee8..6c1c6796 100644 --- a/space_view3d_display_tools/display.py +++ b/space_view3d_display_tools/display.py @@ -82,10 +82,10 @@ class DisplayDrawChange(Operator, BasePollCheck): if not selection: for obj in bpy.data.objects: - obj.draw_type = self.drawing + obj.display_type = self.drawing else: for obj in selection: - obj.draw_type = self.drawing + obj.display_type = self.drawing except: self.report({'ERROR'}, "Setting Draw Type could not be applied") return {'CANCELLED'} @@ -110,12 +110,12 @@ class DisplayBoundsSwitch(Operator, BasePollCheck): for obj in bpy.data.objects: obj.show_bounds = self.bounds if self.bounds: - obj.draw_bounds_type = scene.BoundingMode + obj.display_bounds_type = scene.BoundingMode else: for obj in selection: obj.show_bounds = self.bounds if self.bounds: - obj.draw_bounds_type = scene.BoundingMode + obj.display_bounds_type = scene.BoundingMode except: self.report({'ERROR'}, "Display/Hide Bounding box overlay failed") return {'CANCELLED'} @@ -164,10 +164,10 @@ class DisplayXRayOn(Operator, BasePollCheck): if not selection: for obj in bpy.data.objects: - obj.show_x_ray = self.xrays + obj.show_in_front = self.xrays else: for obj in selection: - obj.show_x_ray = self.xrays + obj.show_in_front = self.xrays except: self.report({'ERROR'}, "Turn on/off X-ray mode failed") return {'CANCELLED'} diff --git a/space_view3d_display_tools/fast_navigate.py b/space_view3d_display_tools/fast_navigate.py index 269aec2c..5d61138a 100644 --- a/space_view3d_display_tools/fast_navigate.py +++ b/space_view3d_display_tools/fast_navigate.py @@ -43,14 +43,14 @@ def display_particles(mode, dis_particles): for particles in bpy.data.particles: if scene.ShowParticles is False: - particles.draw_method = 'NONE' + particles.display_method = 'NONE' else: if particles.type == 'EMITTER': - particles.draw_method = 'DOT' - particles.draw_percentage = 100 + particles.display_method = 'DOT' + particles.display_percentage = 100 else: - particles.draw_method = 'RENDER' - particles.draw_percentage = dis_particles + particles.display_method = 'RENDER' + particles.display_percentage = dis_particles return dis_particles @@ -149,15 +149,15 @@ class FastNavigate(Operator): self.store_viewport_shade = shade for particle in bpy.data.particles: self.store_init_particles[particle.name] = \ - [particle.draw_method, particle.draw_percentage] + [particle.display_method, particle.display_percentage] else: if not shade: self.store_fail = True else: shade = self.store_viewport_shade or 'SOLID' for particle in bpy.data.particles: - particle.draw_method = self.store_init_particles[particle.name][0] - particle.draw_percentage = self.store_init_particles[particle.name][1] + particle.display_method = self.store_init_particles[particle.name][0] + particle.display_percentage = self.store_init_particles[particle.name][1] except: self.store_fail = True diff --git a/space_view3d_display_tools/select_tools.py b/space_view3d_display_tools/select_tools.py index 95dc4ff2..2eb79dfc 100644 --- a/space_view3d_display_tools/select_tools.py +++ b/space_view3d_display_tools/select_tools.py @@ -65,7 +65,7 @@ class ShowHideObject(Operator): i.hide = True i.select = False - if i.type not in ['CAMERA', 'LAMP']: + if i.type not in ['CAMERA', 'LIGHT']: i.hide_render = True except: continue @@ -103,7 +103,7 @@ class HideAllObjects(Operator): i.hide = True i.select = False - if i.type not in ['CAMERA', 'LAMP']: + if i.type not in ['CAMERA', 'LIGHT']: i.hide_render = True else: obj_name = context.object.name @@ -113,7 +113,7 @@ class HideAllObjects(Operator): i.hide = True i.select = False - if i.type not in ['CAMERA', 'LAMP']: + if i.type not in ['CAMERA', 'LIGHT']: i.hide_render = True return {'FINISHED'} @@ -232,11 +232,11 @@ class OBJECT_OT_HideShowByTypeTemplate(): ('LATTICE', 'Lattice', ''), ('EMPTY', 'Empty', ''), ('CAMERA', 'Camera', ''), - ('LAMP', 'Lamp', ''), + ('LIGHT', 'Lamp', ''), ('ALL', 'All', '')), name="Type", description="Type", - default='LAMP', + default='LIGHT', options={'ANIMATABLE'} ) diff --git a/space_view3d_display_tools/selection_restrictor.py b/space_view3d_display_tools/selection_restrictor.py index feb3b8b8..71561bfe 100644 --- a/space_view3d_display_tools/selection_restrictor.py +++ b/space_view3d_display_tools/selection_restrictor.py @@ -45,7 +45,7 @@ curve = 'OUTLINER_OB_CURVE' arm = 'OUTLINER_OB_ARMATURE' empty = 'OUTLINER_OB_EMPTY' cam = 'OUTLINER_OB_CAMERA' -lamp = 'OUTLINER_OB_LAMP' +lamp = 'OUTLINER_OB_LIGHT' lat = 'OUTLINER_OB_LATTICE' font = 'OUTLINER_OB_FONT' meta = 'OUTLINER_OB_META' @@ -135,10 +135,10 @@ def check_restrictors(dummy): # lamp if bpy.context.scene.get('lamprestrictor') is None: lamprestrictorenabled = True - lamp = 'OUTLINER_OB_LAMP' + lamp = 'OUTLINER_OB_LIGHT' else: lamprestrictorenabled = False - lamp = 'LAMP_DATA' + lamp = 'LIGHT_DATA' # lattice if bpy.context.scene.get('latrestrictor') is None: @@ -408,7 +408,7 @@ class RestrictorCam(Operator): # Restrictor for Lamps class RestrictorLamp(Operator): - bl_idname = "restrictor.lamp" + bl_idname = "restrictor.light" bl_label = "Restrictor Lamps" bl_option = {'REGISTER', 'UNDO'} bl_description = "Lamps selection restrictor" @@ -421,18 +421,18 @@ class RestrictorLamp(Operator): lamprestrictorenabled = True if bpy.context.scene.get('lamprestrictor') is not None: del bpy.context.scene['lamprestrictor'] - lamp = 'OUTLINER_OB_LAMP' + lamp = 'OUTLINER_OB_LIGHT' for ob in bpy.context.scene.objects: - if ob.type == 'LAMP': + if ob.type == 'LIGHT': if ob.get('ignore_restrictors') is None: ob.hide_select = False else: lamprestrictorenabled = False bpy.context.scene['lamprestrictor'] = 1 - lamp = 'LAMP_DATA' + lamp = 'LIGHT_DATA' for ob in bpy.context.scene.objects: - if ob.type == 'LAMP': + if ob.type == 'LIGHT': if ob.get('ignore_restrictors') is None: ob.hide_select = True ob.select = False @@ -638,7 +638,7 @@ class RefreshRestrictors(Operator): 'armrestrictor': ("OUTLINER_OB_ARMATURE", "ARMATURE_DATA", "ARMATURE"), 'emptyrestrictor': ("OUTLINER_OB_EMPTY", "EMPTY_DATA", "EMPTY"), 'camrestrictor': ("OUTLINER_OB_CAMERA", "CAMERA_DATA", "CAMERA"), - 'lamprestrictor': ("OUTLINER_OB_LAMP", "LAMP_DATA", "LAMP"), + 'lamprestrictor': ("OUTLINER_OB_LIGHT", "LIGHT_DATA", "LIGHT"), 'latrestrictor': ("OUTLINER_OB_LATTICE", "LATTICE", "LATTICE"), 'fontrestrictor': ("OUTLINER_OB_FONT", "FONT", "FONT"), 'metarestrictor': ("OUTLINER_OB_META", "META_DATA", "META"), @@ -664,7 +664,7 @@ class RefreshRestrictors(Operator): arm = gl_icon if types == "ARMATURE" else arm empty = gl_icon if types == "EMPTY" else empty cam = gl_icon if types == "CAMERA" else cam - lamp = gl_icon if types == "LAMP" else lamp + lamp = gl_icon if types == "LIGHT" else lamp lat = gl_icon if types == "LATTICE" else lat font = gl_icon if types == "FONT" else font meta = gl_icon if types == "META" else meta @@ -701,7 +701,7 @@ class RestrictorSelection(Menu): layout.operator("restrictor.arm", icon=arm, text="Armature") layout.operator("restrictor.empty", icon=empty, text="Empty") layout.operator("restrictor.cam", icon=cam, text="Camera") - layout.operator("restrictor.lamp", icon=lamp, text="Lamp") + layout.operator("restrictor.light", icon=lamp, text="Lamp") layout.operator("restrictor.lat", icon=lat, text="Lattice") layout.operator("restrictor.font", icon=font, text="Font") layout.operator("restrictor.meta", icon=meta, text="MetaBall") diff --git a/space_view3d_display_tools/useless_tools.py b/space_view3d_display_tools/useless_tools.py index 512fcf2d..22931423 100644 --- a/space_view3d_display_tools/useless_tools.py +++ b/space_view3d_display_tools/useless_tools.py @@ -204,7 +204,7 @@ class UTSubsurfHideSelAll(Operator): objects = bpy.context.selected_objects if self.selected else bpy.data.objects for e in objects: try: - if e.type not in {"LAMP", "CAMERA", "EMPTY"}: + if e.type not in {"LIGHT", "CAMERA", "EMPTY"}: e.modifiers['Subsurf'].show_viewport = self.show except Exception as k: name = getattr(e, "name", "Nameless") @@ -233,7 +233,7 @@ class UTOptimalDisplaySelAll(Operator): objects = bpy.context.selected_objects if self.selected else bpy.data.objects for e in objects: try: - if e.type not in {"LAMP", "CAMERA", "EMPTY"}: + if e.type not in {"LIGHT", "CAMERA", "EMPTY"}: e.modifiers['Subsurf'].show_only_control_edges = self.on except Exception as k: name = getattr(e, "name", "Nameless") diff --git a/space_view3d_math_vis/__init__.py b/space_view3d_math_vis/__init__.py index 50d399f2..0dcb7b7a 100644 --- a/space_view3d_math_vis/__init__.py +++ b/space_view3d_math_vis/__init__.py @@ -22,7 +22,7 @@ bl_info = { "name": "Math Vis (Console)", "author": "Campbell Barton", "version": (0, 2, 1), - "blender": (2, 57, 0), + "blender": (2, 80, 0), "location": "Properties: Scene > Math Vis Console and Python Console: Menu", "description": "Display console defined mathutils variables in the 3D view", "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" @@ -74,7 +74,7 @@ class PanelConsoleVars(Panel): if len(state_props) == 0: box = layout.box() col = box.column(align=True) - col.label("No vars to display") + col.label(text="No vars to display") else: layout.template_list( 'MathVisVarList', @@ -98,7 +98,7 @@ class DeleteVar(Operator): bl_description = "Remove the variable from the Console" bl_options = {'REGISTER'} - key = StringProperty(name="Key") + key: StringProperty(name="Key") def execute(self, context): locals = utils.console_namespace() @@ -114,7 +114,7 @@ class ToggleDisplay(Operator): bl_description = "Change the display state of the var" bl_options = {'REGISTER'} - key = StringProperty(name="Key") + key: StringProperty(name="Key") def execute(self, context): utils.VarStates.toggle_display_state(self.key) @@ -128,7 +128,7 @@ class ToggleLock(Operator): bl_description = "Lock the var from being deleted" bl_options = {'REGISTER'} - key = StringProperty(name="Key") + key: StringProperty(name="Key") def execute(self, context): utils.VarStates.toggle_lock_state(self.key) @@ -177,8 +177,8 @@ def call_console_hook(self, context): class MathVisStateProp(PropertyGroup): - ktype = StringProperty() - state = BoolVectorProperty(default=(False, False), size=2) + ktype: StringProperty() + state: BoolVectorProperty(default=(False, False), size=2) class MathVisVarList(UIList): @@ -219,22 +219,22 @@ class MathVisVarList(UIList): class MathVis(PropertyGroup): - index = IntProperty( + index: IntProperty( name="index" ) - bbox_hide = BoolProperty( + bbox_hide: BoolProperty( name="Hide BBoxes", default=False, description="Hide the bounding boxes rendered for Matrix like items", update=call_console_hook ) - name_hide = BoolProperty( + name_hide: BoolProperty( name="Hide Names", default=False, description="Hide the names of the rendered items", update=call_console_hook ) - bbox_scale = FloatProperty( + bbox_scale: FloatProperty( name="Scale factor", min=0, default=1, description="Resize the Bounding Box and the coordinate " diff --git a/space_view3d_math_vis/draw.py b/space_view3d_math_vis/draw.py index 8a8cfa7a..d4acda2f 100644 --- a/space_view3d_math_vis/draw.py +++ b/space_view3d_math_vis/draw.py @@ -20,6 +20,8 @@ import bpy import blf +import gpu +from gpu_extras.batch import batch_for_shader from . import utils from mathutils import Vector @@ -27,6 +29,8 @@ from mathutils import Vector SpaceView3D = bpy.types.SpaceView3D callback_handle = [] +single_color_shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') +smooth_color_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') def tag_redraw_areas(): context = bpy.context @@ -64,28 +68,16 @@ def callback_disable(): def draw_callback_px(): context = bpy.context - from bgl import glColor3f - font_id = 0 # XXX, need to find out how best to get this. + if context.window_manager.MathVisProp.name_hide: + return + + font_id = 0 blf.size(font_id, 12, 72) data_matrix, data_quat, data_euler, data_vector, data_vector_array = utils.console_math_data() - - name_hide = context.window_manager.MathVisProp.name_hide - - if name_hide: - return - if not data_matrix and not data_quat and not data_euler and not data_vector and not data_vector_array: - - ''' - # draw some text - glColor3f(1.0, 0.0, 0.0) - blf.position(font_id, 180, 10, 0) - blf.draw(font_id, "Python Console has no mathutils definitions") - ''' return - glColor3f(1.0, 1.0, 1.0) region = context.region region3d = context.space_data.region_3d @@ -93,11 +85,10 @@ def draw_callback_px(): region_mid_width = region.width / 2.0 region_mid_height = region.height / 2.0 - # vars for projection perspective_matrix = region3d.perspective_matrix.copy() def draw_text(text, vec, dx=3.0, dy=-4.0): - vec_4d = perspective_matrix * vec.to_4d() + vec_4d = perspective_matrix @ vec.to_4d() if vec_4d.w > 0.0: x = region_mid_width + region_mid_width * (vec_4d.x / vec_4d.w) y = region_mid_height + region_mid_height * (vec_4d.y / vec_4d.w) @@ -105,191 +96,138 @@ def draw_callback_px(): blf.position(font_id, x + dx, y + dy, 0.0) blf.draw(font_id, text) - # points if data_vector: for key, vec in data_vector.items(): draw_text(key, vec) - # lines if data_vector_array: for key, vec in data_vector_array.items(): if vec: draw_text(key, vec[0]) - # matrix if data_matrix: for key, mat in data_matrix.items(): loc = Vector((mat[0][3], mat[1][3], mat[2][3])) draw_text(key, loc, dx=10, dy=-20) - line = 20 + offset_y = 20 if data_quat: loc = context.scene.cursor_location.copy() for key, mat in data_quat.items(): - draw_text(key, loc, dy=-line) - line += 20 + draw_text(key, loc, dy=-offset_y) + offset_y += 20 if data_euler: loc = context.scene.cursor_location.copy() for key, mat in data_euler.items(): - draw_text(key, loc, dy=-line) - line += 20 - + draw_text(key, loc, dy=-offset_y) + offset_y += 20 def draw_callback_view(): - context = bpy.context - - from bgl import ( - glEnable, - glDisable, - glColor3f, - glVertex3f, - glPointSize, - glLineWidth, - glBegin, - glEnd, - glLineStipple, - GL_POINTS, - GL_LINE_STRIP, - GL_LINES, - GL_LINE_STIPPLE - ) + settings = bpy.context.window_manager.MathVisProp + scale = settings.bbox_scale + with_bounding_box = not settings.bbox_hide data_matrix, data_quat, data_euler, data_vector, data_vector_array = utils.console_math_data() - # draw_matrix modifiers - bbox_hide = context.window_manager.MathVisProp.bbox_hide - bbox_scale = context.window_manager.MathVisProp.bbox_scale - - # draw_matrix vars - zero = Vector((0.0, 0.0, 0.0)) - x_p = Vector((bbox_scale, 0.0, 0.0)) - x_n = Vector((-bbox_scale, 0.0, 0.0)) - y_p = Vector((0.0, bbox_scale, 0.0)) - y_n = Vector((0.0, -bbox_scale, 0.0)) - z_p = Vector((0.0, 0.0, bbox_scale)) - z_n = Vector((0.0, 0.0, -bbox_scale)) - bb = [Vector() for i in range(8)] - - def draw_matrix(mat): - zero_tx = mat * zero - - glLineWidth(2.0) - - # x - glColor3f(1.0, 0.2, 0.2) - glBegin(GL_LINES) - glVertex3f(*(zero_tx)) - glVertex3f(*(mat * x_p)) - glEnd() - - glColor3f(0.6, 0.0, 0.0) - glBegin(GL_LINES) - glVertex3f(*(zero_tx)) - glVertex3f(*(mat * x_n)) - glEnd() - - # y - glColor3f(0.2, 1.0, 0.2) - glBegin(GL_LINES) - glVertex3f(*(zero_tx)) - glVertex3f(*(mat * y_p)) - glEnd() - - glColor3f(0.0, 0.6, 0.0) - glBegin(GL_LINES) - glVertex3f(*(zero_tx)) - glVertex3f(*(mat * y_n)) - glEnd() - - # z - glColor3f(0.4, 0.4, 1.0) - glBegin(GL_LINES) - glVertex3f(*(zero_tx)) - glVertex3f(*(mat * z_p)) - glEnd() - - glColor3f(0.0, 0.0, 0.6) - glBegin(GL_LINES) - glVertex3f(*(zero_tx)) - glVertex3f(*(mat * z_n)) - glEnd() - - # bounding box - if bbox_hide: - return - - i = 0 - glColor3f(1.0, 1.0, 1.0) - for x in (-bbox_scale, bbox_scale): - for y in (-bbox_scale, bbox_scale): - for z in (-bbox_scale, bbox_scale): - bb[i][:] = x, y, z - bb[i] = mat * bb[i] - i += 1 - - # strip - glLineWidth(1.0) - glLineStipple(1, 0xAAAA) - glEnable(GL_LINE_STIPPLE) - - glBegin(GL_LINE_STRIP) - for i in 0, 1, 3, 2, 0, 4, 5, 7, 6, 4: - glVertex3f(*bb[i]) - glEnd() - - # not done by the strip - glBegin(GL_LINES) - glVertex3f(*bb[1]) - glVertex3f(*bb[5]) - - glVertex3f(*bb[2]) - glVertex3f(*bb[6]) - - glVertex3f(*bb[3]) - glVertex3f(*bb[7]) - glEnd() - glDisable(GL_LINE_STIPPLE) - - # points if data_vector: - glPointSize(3.0) - glBegin(GL_POINTS) - glColor3f(0.5, 0.5, 1) - for key, vec in data_vector.items(): - glVertex3f(*vec.to_3d()) - glEnd() - glPointSize(1.0) + coords = [tuple(vec.to_3d()) for vec in data_vector.values()] + draw_points(coords) - # lines if data_vector_array: - glColor3f(0.5, 0.5, 1) - glLineWidth(2.0) - for line in data_vector_array.values(): - glBegin(GL_LINE_STRIP) - for vec in line: - glVertex3f(*vec) - glEnd() - glPointSize(1.0) + coords = [tuple(vec.to_3d()) for vec in line] + draw_line(coords) - glLineWidth(1.0) - - # matrix if data_matrix: - for mat in data_matrix.values(): - draw_matrix(mat) + draw_matrices(list(data_matrix.values()), scale, with_bounding_box) - if data_quat: - loc = context.scene.cursor_location.copy() + if data_euler or data_quat: + cursor = bpy.context.scene.cursor_location.copy() + derived_matrices = [] for quat in data_quat.values(): - mat = quat.to_matrix().to_4x4() - mat.translation = loc - draw_matrix(mat) - - if data_euler: - loc = context.scene.cursor_location.copy() + matrix = quat.to_matrix().to_4x4() + matrix.translation = cursor + derived_matrices.append(matrix) for eul in data_euler.values(): - mat = eul.to_matrix().to_4x4() - mat.translation = loc - draw_matrix(mat) + matrix = eul.to_matrix().to_4x4() + matrix.translation = cursor + derived_matrices.append(matrix) + draw_matrices(derived_matrices, scale, with_bounding_box) + + +def draw_points(points): + batch = batch_from_points(points, "POINTS") + single_color_shader.bind() + single_color_shader.uniform_float("color", (0.5, 0.5, 1, 1)) + batch.draw(single_color_shader) + +def draw_line(points): + batch = batch_from_points(points, "LINE_STRIP") + single_color_shader.bind() + single_color_shader.uniform_float("color", (0.5, 0.5, 1, 1)) + batch.draw(single_color_shader) + +def batch_from_points(points, type): + return batch_for_shader(single_color_shader, type, {"pos" : points}) + +def draw_matrices(matrices, scale, with_bounding_box): + x_p = Vector(( scale, 0.0, 0.0)) + x_n = Vector((-scale, 0.0, 0.0)) + y_p = Vector((0.0, scale, 0.0)) + y_n = Vector((0.0, -scale, 0.0)) + z_p = Vector((0.0, 0.0, scale)) + z_n = Vector((0.0, 0.0, -scale)) + + red_dark = (0.2, 0.0, 0.0, 1.0) + red_light = (1.0, 0.2, 0.2, 1.0) + green_dark = (0.0, 0.2, 0.0, 1.0) + green_light = (0.2, 1.0, 0.2, 1.0) + blue_dark = (0.0, 0.0, 0.2, 1.0) + blue_light = (0.4, 0.4, 1.0, 1.0) + + coords = [] + colors = [] + for matrix in matrices: + coords.append(matrix @ x_n) + coords.append(matrix @ x_p) + colors.extend((red_dark, red_light)) + coords.append(matrix @ y_n) + coords.append(matrix @ y_p) + colors.extend((green_dark, green_light)) + coords.append(matrix @ z_n) + coords.append(matrix @ z_p) + colors.extend((blue_dark, blue_light)) + + batch = batch_for_shader(smooth_color_shader, "LINES", { + "pos" : coords, + "color" : colors + }) + batch.draw(smooth_color_shader) + + if with_bounding_box: + draw_bounding_boxes(matrices, scale, (1.0, 1.0, 1.0, 1.0)) + +def draw_bounding_boxes(matrices, scale, color): + boundbox_points = [] + for x in (-scale, scale): + for y in (-scale, scale): + for z in (-scale, scale): + boundbox_points.append(Vector((x, y, z))) + + boundbox_lines = [ + (0, 1), (1, 3), (3, 2), (2, 0), (0, 4), (4, 5), + (5, 7), (7, 6), (6, 4), (1, 5), (2, 6), (3, 7) + ] + + points = [] + for matrix in matrices: + for v1, v2 in boundbox_lines: + points.append(matrix @ boundbox_points[v1]) + points.append(matrix @ boundbox_points[v2]) + + batch = batch_from_points(points, "LINES") + + single_color_shader.bind() + single_color_shader.uniform_float("color", color) + batch.draw(single_color_shader)
\ No newline at end of file diff --git a/space_view3d_pie_menus/__init__.py b/space_view3d_pie_menus/__init__.py index 486d538f..26ac9958 100644 --- a/space_view3d_pie_menus/__init__.py +++ b/space_view3d_pie_menus/__init__.py @@ -21,20 +21,20 @@ import bpy from bpy.props import ( - BoolProperty, - PointerProperty, - ) + BoolProperty, + PointerProperty, +) from bpy.types import ( - PropertyGroup, - AddonPreferences, - ) + PropertyGroup, + AddonPreferences, +) bl_info = { "name": "3D Viewport Pie Menus", "author": "meta-androcto, pitiwazou, chromoly, italic", "version": (1, 1, 8), - "blender": (2, 7, 7), + "blender": (2, 80, 0), "description": "Individual Pie Menu Activation List", "location": "Addons Preferences", "warning": "", @@ -149,32 +149,54 @@ def disable_all_modules(self, context): class PieToolsPreferences(AddonPreferences): bl_idname = __name__ - enable_all = BoolProperty( - name="Enable all", - description="Enable all Pie Modules", - default=False, - update=enable_all_modules - ) - disable_all = BoolProperty( - name="Disable all", - description="Disable all Pie Modules", - default=False, - update=disable_all_modules - ) + enable_all: BoolProperty( + name="Enable all", + description="Enable all Pie Modules", + default=False, + update=enable_all_modules + ) + disable_all: BoolProperty( + name="Disable all", + description="Disable all Pie Modules", + default=False, + update=disable_all_modules + ) + + for mod in sub_modules: + mod_name = mod.__name__.split('.')[-1] + + def gen_update(mod, use_prop_name): + def update(self, context): + if getattr(self, use_prop_name): + if not mod.__addon_enabled__: + register_submodule(mod) + else: + if mod.__addon_enabled__: + unregister_submodule(mod) + return update + + use_prop_name = 'use_' + mod_name + __annotations__[use_prop_name] = BoolProperty( + name=mod.bl_info['name'], + description=mod.bl_info.get('description', ''), + update=gen_update(mod, use_prop_name), + ) + + __annotations__['show_expanded_' + mod_name] = BoolProperty() def draw(self, context): layout = self.layout - split = layout.split(percentage=0.5, align=True) + split = layout.split(factor=0.5, align=True) row = split.row() row.alignment = "LEFT" sub_box = row.box() sub_box.prop(self, "enable_all", emboss=False, icon="VISIBLE_IPO_ON", icon_only=True) - row.label("Enable All") + row.label(text="Enable All") row = split.row() row.alignment = "RIGHT" - row.label("Disable All") + row.label(text="Disable All") sub_box = row.box() sub_box.prop(self, "disable_all", emboss=False, icon="VISIBLE_IPO_OFF", icon_only=True) @@ -195,42 +217,42 @@ class PieToolsPreferences(AddonPreferences): op = sub.operator('wm.context_toggle', text='', icon=icon, emboss=False) op.data_path = 'addon_prefs.show_expanded_' + mod_name - sub.label('{}: {}'.format(info['category'], info['name'])) + sub.label(text='{}: {}'.format(info['category'], info['name'])) sub = row.row() sub.alignment = 'RIGHT' if info.get('warning'): - sub.label('', icon='ERROR') + sub.label(text='', icon='ERROR') sub.prop(self, 'use_' + mod_name, text='') # The second stage if expand: if info.get('description'): - split = col.row().split(percentage=0.15) - split.label('Description:') - split.label(info['description']) + split = col.row().split(factor=0.15) + split.label(text='Description:') + split.label(text=info['description']) if info.get('location'): - split = col.row().split(percentage=0.15) - split.label('Location:') - split.label(info['location']) + split = col.row().split(factor=0.15) + split.label(text='Location:') + split.label(text=info['location']) """ if info.get('author'): - split = col.row().split(percentage=0.15) - split.label('Author:') + split = col.row().split(factor=0.15) + split.label(text='Author:') split.label(info['author']) """ if info.get('version'): - split = col.row().split(percentage=0.15) - split.label('Version:') - split.label('.'.join(str(x) for x in info['version']), + split = col.row().split(factor=0.15) + split.label(text='Version:') + split.label(text='.'.join(str(x) for x in info['version']), translate=False) if info.get('warning'): - split = col.row().split(percentage=0.15) - split.label('Warning:') - split.label(' ' + info['warning'], icon='ERROR') + split = col.row().split(factor=0.15) + split.label(text='Warning:') + split.label(text=' ' + info['warning'], icon='ERROR') tot_row = int(bool(info.get('wiki_url'))) if tot_row: - split = col.row().split(percentage=0.15) + split = col.row().split(factor=0.15) split.label(text='Internet:') if info.get('wiki_url'): op = split.operator('wm.url_open', @@ -259,32 +281,9 @@ class PieToolsPreferences(AddonPreferences): icon="FILE_PARENT") -for mod in sub_modules: - info = mod.bl_info - mod_name = mod.__name__.split('.')[-1] - - def gen_update(mod): - def update(self, context): - if getattr(self, 'use_' + mod.__name__.split('.')[-1]): - if not mod.__addon_enabled__: - register_submodule(mod) - else: - if mod.__addon_enabled__: - unregister_submodule(mod) - return update - - prop = BoolProperty( - name=info['name'], - description=info.get('description', ''), - update=gen_update(mod), - ) - setattr(PieToolsPreferences, 'use_' + mod_name, prop) - prop = BoolProperty() - setattr(PieToolsPreferences, 'show_expanded_' + mod_name, prop) - classes = ( PieToolsPreferences, - ) +) def register(): diff --git a/space_view3d_pie_menus/pie_align_menu.py b/space_view3d_pie_menus/pie_align_menu.py index c8ce2e15..c59a7c97 100644 --- a/space_view3d_pie_menus/pie_align_menu.py +++ b/space_view3d_pie_menus/pie_align_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "V/E/F Align tools", "author": "pitiwazou, meta-androcto", "version": (0, 1, 2), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "Mesh Edit Mode", "warning": "", "wiki_url": "", @@ -68,7 +68,7 @@ class PieAlign(Menu): box = pie.split().box().column() row = box.row(align=True) - row.label("X") + row.label(text="X") align_1 = row.operator("alignxyz.all", text="Neg") align_1.axis = '0' align_1.side = 'NEGATIVE' @@ -77,7 +77,7 @@ class PieAlign(Menu): align_2.side = 'POSITIVE' row = box.row(align=True) - row.label("Y") + row.label(text="Y") align_3 = row.operator("alignxyz.all", text="Neg") align_3.axis = '1' align_3.side = 'NEGATIVE' @@ -86,7 +86,7 @@ class PieAlign(Menu): align_4.side = 'POSITIVE' row = box.row(align=True) - row.label("Z") + row.label(text="Z") align_5 = row.operator("alignxyz.all", text="Neg") align_5.axis = '2' align_5.side = 'NEGATIVE' @@ -102,7 +102,7 @@ class AlignSelectedXYZ(Operator): bl_description = "Align Selected Along the chosen axis" bl_options = {'REGISTER', 'UNDO'} - axis = EnumProperty( + axis: EnumProperty( name="Axis", items=[ ('X', "X", "X Axis"), @@ -147,7 +147,7 @@ class AlignToXYZ0(Operator): bl_description = "Align Active Object To a chosen X, Y or Z equals 0 Location" bl_options = {'REGISTER', 'UNDO'} - axis = EnumProperty( + axis: EnumProperty( name="Axis", items=[ ('0', "X", "X Axis"), @@ -181,7 +181,7 @@ class AlignXYZAll(Operator): bl_description = "Align to a Front or Back along the chosen Axis" bl_options = {'REGISTER', 'UNDO'} - axis = EnumProperty( + axis: EnumProperty( name="Axis", items=[ ('0', "X", "X Axis"), @@ -191,7 +191,7 @@ class AlignXYZAll(Operator): description="Choose an axis for alignment", default='0' ) - side = EnumProperty( + side: EnumProperty( name="Side", items=[ ('POSITIVE', "Front", "Align on the positive chosen axis"), diff --git a/space_view3d_pie_menus/pie_animation_menu.py b/space_view3d_pie_menus/pie_animation_menu.py index d65891f9..774aa8e0 100644 --- a/space_view3d_pie_menus/pie_animation_menu.py +++ b/space_view3d_pie_menus/pie_animation_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Pie menu for Timeline controls", "author": "pitiwazou, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View", "warning": "", "wiki_url": "", diff --git a/space_view3d_pie_menus/pie_apply_transform_menu.py b/space_view3d_pie_menus/pie_apply_transform_menu.py index e22f23b4..a92920db 100644 --- a/space_view3d_pie_menus/pie_apply_transform_menu.py +++ b/space_view3d_pie_menus/pie_apply_transform_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Apply Transform Menu", "author": "pitiwazou, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View", "warning": "", "wiki_url": "", @@ -49,15 +49,15 @@ class PieApplyTransforms(Menu): # 4 - LEFT pie.operator("apply.transformall", text="Apply All", icon='FREEZE') # 6 - RIGHT - pie.operator("clear.all", text="Clear All", icon='MANIPUL') + pie.operator("clear.all", text="Clear All", icon='NONE') # 2 - BOTTOM pie.operator("object.duplicates_make_real", text="Make Duplicates Real") # 8 - TOP - pie.operator("apply.transformlocrotscale", text="Rotation", icon='MAN_ROT').option = 'ROT' + pie.operator("apply.transformlocrotscale", text="Rotation", icon='NONE').option = 'ROT' # 7 - TOP - LEFT - pie.operator("apply.transformlocrotscale", text="Location", icon='MAN_ROT').option = 'LOC' + pie.operator("apply.transformlocrotscale", text="Location", icon='NONE').option = 'LOC' # 9 - TOP - RIGHT - pie.operator("apply.transformlocrotscale", text="Scale", icon='MAN_ROT').option = 'SCALE' + pie.operator("apply.transformlocrotscale", text="Scale", icon='NONE').option = 'SCALE' # 1 - BOTTOM - LEFT pie.operator("object.visual_transform_apply", text="Visual Transforms") # 3 - BOTTOM - RIGHT @@ -71,7 +71,7 @@ class ApplyTransLocRotPie(Operator): bl_description = "Apply Transform: Location, Rotation or Scale" bl_options = {'REGISTER', 'UNDO'} - option = EnumProperty( + option: EnumProperty( name="Type", items=[ ("LOC", "Location", "Apply Location"), @@ -109,10 +109,10 @@ class ClearMenu(Menu): def draw(self, context): layout = self.layout - layout.operator("object.location_clear", text="Clear Location", icon='MAN_TRANS') - layout.operator("object.rotation_clear", text="Clear Rotation", icon='MAN_ROT') - layout.operator("object.scale_clear", text="Clear Scale", icon='MAN_SCALE') - layout.operator("object.origin_clear", text="Clear Origin", icon='MANIPUL') + layout.operator("object.location_clear", text="Clear Location", icon='NONE') + layout.operator("object.rotation_clear", text="Clear Rotation", icon='NONE') + layout.operator("object.scale_clear", text="Clear Scale", icon='NONE') + layout.operator("object.origin_clear", text="Clear Origin", icon='NONE') # Clear all diff --git a/space_view3d_pie_menus/pie_cursor.py b/space_view3d_pie_menus/pie_cursor.py index 568cdd6d..337c0c12 100644 --- a/space_view3d_pie_menus/pie_cursor.py +++ b/space_view3d_pie_menus/pie_cursor.py @@ -23,7 +23,7 @@ bl_info = { "description": "Cursor Menu", "author": "pitiwazou, meta-androcto", "version": (0, 1, 0), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View", "warning": "", "wiki_url": "", @@ -69,16 +69,16 @@ class Snap_CursorMenu(Menu): icon='CLIPUV_HLT').use_offset = False # 6 - RIGHT pie.operator("view3d.snap_selected_to_cursor", - text="Selection to Cursor (Offset)", icon='CURSOR').use_offset = True + text="Selection to Cursor (Keep Offset)", icon='PIVOT_CURSOR').use_offset = True # 2 - BOTTOM pie.operator("view3d.snap_cursor_selected_to_center1", - text="Selected & Cursor to Center", icon='ALIGN') + text="Selected & Cursor to Center", icon='NONE') # 8 - TOP - pie.operator("view3d.snap_cursor_to_center", text="Cursor to Center", icon='CLIPUV_DEHLT') + pie.operator("view3d.snap_cursor_to_center", text="Cursor to World Origin", icon='CLIPUV_DEHLT') # 7 - TOP - LEFT - pie.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected", icon='ROTACTIVE') + pie.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected", icon='NONE') # 9 - TOP - RIGHT - pie.operator("view3d.snap_cursor_to_active", text="Cursor to Active", icon='BBOX') + pie.operator("view3d.snap_cursor_to_active", text="Cursor to Active", icon='NONE') # 1 - BOTTOM - LEFT pie.operator("view3d.snap_selected_to_grid", text="Selection to Grid", icon='GRID') # 3 - BOTTOM - RIGHT diff --git a/space_view3d_pie_menus/pie_delete_menu.py b/space_view3d_pie_menus/pie_delete_menu.py index 4a8e002c..0632877e 100644 --- a/space_view3d_pie_menus/pie_delete_menu.py +++ b/space_view3d_pie_menus/pie_delete_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Edit mode V/E/F Delete Modes", "author": "pitiwazou, meta-androcto", "version": (0, 1, 0), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "Mesh Edit Mode", "warning": "", "wiki_url": "", @@ -57,13 +57,13 @@ class PieDelete(Menu): # 1 - BOTTOM - LEFT box = pie.split().column() box.operator("mesh.dissolve_limited", text="Limited Dissolve", icon='STICKY_UVS_LOC') - box.operator("mesh.delete_edgeloop", text="Delete Edge Loops", icon='BORDER_LASSO') + box.operator("mesh.delete_edgeloop", text="Delete Edge Loops", icon='NONE') box.operator("mesh.edge_collapse", text="Edge Collapse", icon='UV_EDGESEL') # 3 - BOTTOM - RIGHT box = pie.split().column() - box.operator("mesh.delete", text="Only Edge & Faces", icon='SPACE2').type = 'EDGE_FACE' + box.operator("mesh.delete", text="Only Edge & Faces", icon='NONE').type = 'EDGE_FACE' box.operator("mesh.delete", text="Only Faces", icon='UV_FACESEL').type = 'ONLY_FACE' - box.operator("mesh.remove_doubles", text="Remove Doubles", icon='ORTHO') + box.operator("mesh.remove_doubles", text="Remove Doubles", icon='NONE') classes = ( diff --git a/space_view3d_pie_menus/pie_editor_switch_menu.py b/space_view3d_pie_menus/pie_editor_switch_menu.py index 67f40ff8..093f46b6 100644 --- a/space_view3d_pie_menus/pie_editor_switch_menu.py +++ b/space_view3d_pie_menus/pie_editor_switch_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Switch Editor Type Menu", "author": "saidenka", "version": (0, 1, 0), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "All Editors", "warning": "", "wiki_url": "", @@ -41,7 +41,7 @@ from bpy.props import ( class AreaPieMenu(Menu): - bl_idname = "INFO_MT_window_pie" + bl_idname = "TOPBAR_MT_window_pie" bl_label = "Pie Menu" bl_description = "Window Pie Menus" @@ -73,21 +73,21 @@ class AreaPieEditor(Menu): # 6 - RIGHT pie.menu(AreaTypePieAnim.bl_idname, text="Animation Editors", icon="ACTION") # 2 - BOTTOM - pie.operator(SetAreaType.bl_idname, text="Property", icon="BUTS").types = "PROPERTIES" + pie.operator(SetAreaType.bl_idname, text="Property", icon="PROPERTIES").types = "PROPERTIES" # 8 - TOP pie.operator(SetAreaType.bl_idname, text="3D View", icon="MESH_CUBE").types = "VIEW_3D" # 7 - TOP - LEFT - pie.operator(SetAreaType.bl_idname, text="UV/Image Editor", icon="IMAGE_COL").types = "IMAGE_EDITOR" + pie.operator(SetAreaType.bl_idname, text="UV/Image Editor", icon="NONE").types = "IMAGE_EDITOR" # 9 - TOP - RIGHT pie.operator(SetAreaType.bl_idname, text="Node Editor", icon="NODETREE").types = "NODE_EDITOR" # 1 - BOTTOM - LEFT - pie.operator(SetAreaType.bl_idname, text="Outliner", icon="OOPS").types = "OUTLINER" + pie.operator(SetAreaType.bl_idname, text="Outliner", icon="NONE").types = "OUTLINER" # 3 - BOTTOM - RIGHT pie.menu(AreaTypePieOther.bl_idname, text="More Editors", icon="QUESTION") class AreaTypePieOther(Menu): - bl_idname = "INFO_MT_window_pie_area_type_other" + bl_idname = "TOPBAR_MT_window_pie_area_type_other" bl_label = "Editor Type (other)" bl_description = "Is pie menu change editor type (other)" @@ -95,7 +95,7 @@ class AreaTypePieOther(Menu): # 4 - LEFT self.layout.operator(SetAreaType.bl_idname, text="Logic Editor", icon="LOGIC").types = "LOGIC_EDITOR" # 6 - RIGHT - self.layout.operator(SetAreaType.bl_idname, text="File Browser", icon="FILESEL").types = "FILE_BROWSER" + self.layout.operator(SetAreaType.bl_idname, text="File Browser", icon="FILEBROWSER").types = "FILE_BROWSER" # 2 - BOTTOM self.layout.operator(SetAreaType.bl_idname, text="Python Console", icon="CONSOLE").types = "CONSOLE" # 8 - TOP @@ -114,7 +114,7 @@ class SetAreaType(Operator): bl_description = "Change Editor Type" bl_options = {'REGISTER'} - types = StringProperty(name="Area Type") + types: StringProperty(name="Area Type") def execute(self, context): context.area.type = self.types @@ -122,7 +122,7 @@ class SetAreaType(Operator): class AreaTypePieAnim(Menu): - bl_idname = "INFO_MT_window_pie_area_type_anim" + bl_idname = "TOPBAR_MT_window_pie_area_type_anim" bl_label = "Editor Type (Animation)" bl_description = "Menu for changing editor type (animation related)" diff --git a/space_view3d_pie_menus/pie_manipulator_menu.py b/space_view3d_pie_menus/pie_manipulator_menu.py index 2690c094..68895901 100644 --- a/space_view3d_pie_menus/pie_manipulator_menu.py +++ b/space_view3d_pie_menus/pie_manipulator_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Extended Manipulator Menu", "author": "pitiwazou, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View", "warning": "", "wiki_url": "", @@ -37,111 +37,6 @@ from bpy.types import ( ) -class ManipTranslate(Operator): - bl_idname = "manip.translate" - bl_label = "Manip Translate" - bl_options = {'REGISTER', 'UNDO'} - bl_description = " Show Translate" - - def execute(self, context): - if context.space_data.show_manipulator is False: - context.space_data.show_manipulator = True - context.space_data.transform_manipulators = {'TRANSLATE'} - if context.space_data.transform_manipulators != {'TRANSLATE'}: - context.space_data.transform_manipulators = {'TRANSLATE'} - return {'FINISHED'} - - -class ManipRotate(Operator): - bl_idname = "manip.rotate" - bl_label = "Manip Rotate" - bl_options = {'REGISTER', 'UNDO'} - bl_description = " Show Rotate" - - def execute(self, context): - if context.space_data.show_manipulator is False: - context.space_data.show_manipulator = True - context.space_data.transform_manipulators = {'ROTATE'} - if context.space_data.transform_manipulators != {'ROTATE'}: - context.space_data.transform_manipulators = {'ROTATE'} - return {'FINISHED'} - - -class ManipScale(Operator): - bl_idname = "manip.scale" - bl_label = "Manip Scale" - bl_options = {'REGISTER', 'UNDO'} - bl_description = " Show Scale" - - def execute(self, context): - if context.space_data.show_manipulator is False: - context.space_data.show_manipulator = True - context.space_data.transform_manipulators = {'SCALE'} - if context.space_data.transform_manipulators != {'SCALE'}: - context.space_data.transform_manipulators = {'SCALE'} - return {'FINISHED'} - - -class TranslateRotate(Operator): - bl_idname = "translate.rotate" - bl_label = "Translate Rotate" - bl_options = {'REGISTER', 'UNDO'} - bl_description = " Show Translate/Rotate" - - def execute(self, context): - if context.space_data.show_manipulator is False: - context.space_data.show_manipulator = True - context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE'} - if context.space_data.transform_manipulators != {'TRANSLATE', 'ROTATE'}: - context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE'} - return {'FINISHED'} - - -class TranslateScale(Operator): - bl_idname = "translate.scale" - bl_label = "Translate Scale" - bl_options = {'REGISTER', 'UNDO'} - bl_description = " Show Translate/Scale" - - def execute(self, context): - if context.space_data.show_manipulator is False: - context.space_data.show_manipulator = True - context.space_data.transform_manipulators = {'TRANSLATE', 'SCALE'} - if context.space_data.transform_manipulators != {'TRANSLATE', 'SCALE'}: - context.space_data.transform_manipulators = {'TRANSLATE', 'SCALE'} - return {'FINISHED'} - - -class RotateScale(Operator): - bl_idname = "rotate.scale" - bl_label = "Rotate Scale" - bl_options = {'REGISTER', 'UNDO'} - bl_description = " Show Rotate/Scale" - - def execute(self, context): - if context.space_data.show_manipulator is False: - context.space_data.show_manipulator = True - context.space_data.transform_manipulators = {'ROTATE', 'SCALE'} - if context.space_data.transform_manipulators != {'ROTATE', 'SCALE'}: - context.space_data.transform_manipulators = {'ROTATE', 'SCALE'} - return {'FINISHED'} - - -class TranslateRotateScale(Operator): - bl_idname = "translate.rotatescale" - bl_label = "Translate Rotate Scale" - bl_options = {'REGISTER', 'UNDO'} - bl_description = "Show All" - - def execute(self, context): - if context.space_data.show_manipulator is False: - context.space_data.show_manipulator = True - context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE', 'SCALE'} - if context.space_data.transform_manipulators != {'TRANSLATE', 'ROTATE', 'SCALE'}: - context.space_data.transform_manipulators = {'TRANSLATE', 'ROTATE', 'SCALE'} - return {'FINISHED'} - - class WManupulators(Operator): bl_idname = "w.manupulators" bl_label = "W Manupulators" @@ -149,13 +44,7 @@ class WManupulators(Operator): bl_description = " Show/Hide Manipulator" def execute(self, context): - - if context.space_data.show_manipulator is True: - context.space_data.show_manipulator = False - - elif context.space_data.show_manipulator is False: - context.space_data.show_manipulator = True - + context.space_data.show_gizmo_tool = not context.space_data.show_gizmo_tool return {'FINISHED'} @@ -168,32 +57,17 @@ class PieManipulator(Menu): layout = self.layout pie = layout.menu_pie() # 4 - LEFT - pie.operator("rotate.scale", text="Rotate/Scale") + pie.operator("wm.tool_set_by_name", text="Translate", icon='NONE').name = "Move" # 6 - RIGHT - pie.operator("manip.rotate", text="Rotate", icon='MAN_ROT') + pie.operator("wm.tool_set_by_name", text="Rotate", icon='NONE').name = "Rotate" # 2 - BOTTOM - pie.operator("translate.rotatescale", text="Translate/Rotate/Scale") + pie.operator("wm.tool_set_by_name", text="Scale", icon='NONE').name = "Scale" # 8 - TOP - pie.operator("w.manupulators", text="Show/Hide Toggle", icon='MANIPUL') - # 7 - TOP - LEFT - pie.operator("translate.rotate", text="Translate/Rotate") - # 9 - TOP - RIGHT - pie.operator("manip.translate", text="Translate", icon='MAN_TRANS') - # 1 - BOTTOM - LEFT - pie.operator("translate.scale", text="Translate/Scale") - # 3 - BOTTOM - RIGHT - pie.operator("manip.scale", text="Scale", icon='MAN_SCALE') + pie.operator("w.manupulators", text="Show/Hide Toggle", icon='NONE') classes = ( PieManipulator, - ManipTranslate, - ManipRotate, - ManipScale, - TranslateRotate, - TranslateScale, - RotateScale, - TranslateRotateScale, WManupulators, ) diff --git a/space_view3d_pie_menus/pie_modes_menu.py b/space_view3d_pie_menus/pie_modes_menu.py index 40f6ba4a..943e85ce 100644 --- a/space_view3d_pie_menus/pie_modes_menu.py +++ b/space_view3d_pie_menus/pie_modes_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Switch between 3d view object/edit modes", "author": "pitiwazou, meta-androcto, italic", "version": (0, 1, 2), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View", "warning": "", "wiki_url": "", @@ -163,7 +163,7 @@ class SetObjectModePie(Operator): bl_description = "I set the interactive mode of object" bl_options = {'REGISTER'} - mode = bpy.props.StringProperty(name="Interactive mode", default="OBJECT") + mode: bpy.props.StringProperty(name="Interactive mode", default="OBJECT") def execute(self, context): if (context.active_object): @@ -276,7 +276,7 @@ class PieObjectEditotherModes(Menu): box.operator("verts.edges", text="Vertex/Edges", icon='VERTEXSEL') box.operator("verts.edgesfaces", text="Vertex/Edges/Faces", icon='OBJECT_DATAMODE') box.operator("wm.context_toggle", text="Limit to Visible", - icon="ORTHO").data_path = "space_data.use_occlude_geometry" + icon="NONE").data_path = "space_data.use_occlude_geometry" class PieObjectEditMode(Menu): @@ -429,7 +429,7 @@ class PieObjectEditMode(Menu): pie.operator("view3d.pie_interactive_mode_grease_pencil", icon="GREASEPENCIL") else: message = "Active Object has only Object Mode available" if ob \ - and ob.type in {"LAMP", "CAMERA", "EMPTY", "SPEAKER"} else \ + and ob.type in {"LIGHT", "CAMERA", "EMPTY", "SPEAKER"} else \ "No active object found. Please select one first" pie = layout.menu_pie() pie.separator() diff --git a/space_view3d_pie_menus/pie_orientation_menu.py b/space_view3d_pie_menus/pie_orientation_menu.py index a9de9fa6..04e77c95 100644 --- a/space_view3d_pie_menus/pie_orientation_menu.py +++ b/space_view3d_pie_menus/pie_orientation_menu.py @@ -24,7 +24,7 @@ bl_info = { "name": "Hotkey: 'Alt + Spacebar'", "author": "Italic_", "version": (1, 1, 0), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "description": "Set Transform Orientations", "location": "3D View", "category": "Orientation Pie"} @@ -44,14 +44,14 @@ class OrientPoll(Operator): bl_label = "Orientation Poll" bl_options = {'INTERNAL'} - space = StringProperty() + space: StringProperty() @classmethod def poll(cls, context): return context.space_data.type == "VIEW_3D" def execute(self, context): - context.space_data.transform_orientation = self.space + context.scene.transform_orientation = self.space return {'FINISHED'} diff --git a/space_view3d_pie_menus/pie_origin.py b/space_view3d_pie_menus/pie_origin.py index 84344d7e..58f22d41 100644 --- a/space_view3d_pie_menus/pie_origin.py +++ b/space_view3d_pie_menus/pie_origin.py @@ -23,7 +23,7 @@ bl_info = { "description": "Origin Snap/Place Menu", "author": "pitiwazou, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View", "warning": "", "wiki_url": "", @@ -105,10 +105,10 @@ class PieOriginPivot(Menu): if obj and obj.type == 'MESH': # 4 - LEFT pie.operator("object.origin_set", text="Origin to Center of Mass", - icon='BBOX').type = 'ORIGIN_CENTER_OF_MASS' + icon='NONE').type = 'ORIGIN_CENTER_OF_MASS' # 6 - RIGHT pie.operator("object.origin_set", text="Origin To 3D Cursor", - icon='CURSOR').type = 'ORIGIN_CURSOR' + icon='PIVOT_CURSOR').type = 'ORIGIN_CURSOR' # 2 - BOTTOM pie.operator("object.pivotobottom", text="Origin to Bottom", icon='TRIA_DOWN') @@ -117,27 +117,27 @@ class PieOriginPivot(Menu): icon='SNAP_INCREMENT') # 7 - TOP - LEFT pie.operator("object.origin_set", text="Geometry To Origin", - icon='BBOX').type = 'GEOMETRY_ORIGIN' + icon='NONE').type = 'GEOMETRY_ORIGIN' # 9 - TOP - RIGHT pie.operator("object.origin_set", text="Origin To Geometry", - icon='ROTATE').type = 'ORIGIN_GEOMETRY' + icon='NONE').type = 'ORIGIN_GEOMETRY' else: # 4 - LEFT pie.operator("object.origin_set", text="Origin to Center of Mass", - icon='BBOX').type = 'ORIGIN_CENTER_OF_MASS' + icon='NONE').type = 'ORIGIN_CENTER_OF_MASS' # 6 - RIGHT pie.operator("object.origin_set", text="Origin To 3D Cursor", - icon='CURSOR').type = 'ORIGIN_CURSOR' + icon='PIVOT_CURSOR').type = 'ORIGIN_CURSOR' # 2 - BOTTOM pie.operator("object.pivot2selection", text="Origin To Selection", icon='SNAP_INCREMENT') # 8 - TOP pie.operator("object.origin_set", text="Origin To Geometry", - icon='ROTATE').type = 'ORIGIN_GEOMETRY' + icon='NONE').type = 'ORIGIN_GEOMETRY' # 7 - TOP - LEFT pie.operator("object.origin_set", text="Geometry To Origin", - icon='BBOX').type = 'GEOMETRY_ORIGIN' + icon='NONE').type = 'GEOMETRY_ORIGIN' classes = ( diff --git a/space_view3d_pie_menus/pie_pivot_point_menu.py b/space_view3d_pie_menus/pie_pivot_point_menu.py index df6600a3..157364ff 100644 --- a/space_view3d_pie_menus/pie_pivot_point_menu.py +++ b/space_view3d_pie_menus/pie_pivot_point_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Set Pivot Point Menu", "author": "seb_k, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View", "warning": "", "wiki_url": "", diff --git a/space_view3d_pie_menus/pie_proportional_menu.py b/space_view3d_pie_menus/pie_proportional_menu.py index 479b57d4..f1722ce1 100644 --- a/space_view3d_pie_menus/pie_proportional_menu.py +++ b/space_view3d_pie_menus/pie_proportional_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Proportional Object/Edit Tools", "author": "pitiwazou, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View Object & Edit modes", "warning": "", "wiki_url": "", diff --git a/space_view3d_pie_menus/pie_save_open_menu.py b/space_view3d_pie_menus/pie_save_open_menu.py index 3d48e591..e71f7924 100644 --- a/space_view3d_pie_menus/pie_save_open_menu.py +++ b/space_view3d_pie_menus/pie_save_open_menu.py @@ -21,7 +21,7 @@ bl_info = { "name": "Hotkey: 'Ctrl S'", "description": "Save/Open & File Menus", - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "All Editors", "warning": "", "wiki_url": "", @@ -46,17 +46,17 @@ class PieSaveOpen(Menu): layout = self.layout pie = layout.menu_pie() # 4 - LEFT - pie.operator("wm.read_homefile", text="New", icon='NEW') + pie.operator("wm.read_homefile", text="New", icon='FILE_NEW') # 6 - RIGHT pie.menu("pie.link", text="Link", icon='LINK_BLEND') # 2 - BOTTOM pie.menu("pie.fileio", text="Import/Export Menu", icon='IMPORT') # 8 - TOP - pie.operator("file.save_incremental", text="Incremental Save", icon='SAVE_COPY') + pie.operator("file.save_incremental", text="Incremental Save", icon='NONE') # 7 - TOP - LEFT pie.operator("wm.save_mainfile", text="Save", icon='FILE_TICK') # 9 - TOP - RIGHT - pie.operator("wm.save_as_mainfile", text="Save As...", icon='SAVE_AS') + pie.operator("wm.save_as_mainfile", text="Save As...", icon='NONE') # 1 - BOTTOM - LEFT pie.operator("wm.open_mainfile", text="Open file", icon='FILE_FOLDER') # 3 - BOTTOM - RIGHT @@ -84,7 +84,7 @@ class pie_recover(Menu): layout = self.layout pie = layout.menu_pie() box = pie.split().column() - box.operator("wm.recover_auto_save", text="Recover Auto Save...", icon='RECOVER_AUTO') + box.operator("wm.recover_auto_save", text="Recover Auto Save...", icon='NONE') box.operator("wm.recover_last_session", text="Recover Last Session", icon='RECOVER_LAST') box.operator("wm.revert_mainfile", text="Revert", icon='FILE_REFRESH') @@ -97,9 +97,9 @@ class pie_fileio(Menu): layout = self.layout pie = layout.menu_pie() box = pie.split().column() - box.menu("INFO_MT_file_import", icon='IMPORT') + box.menu("TOPBAR_MT_file_import", icon='IMPORT') box.separator() - box.menu("INFO_MT_file_export", icon='EXPORT') + box.menu("TOPBAR_MT_file_export", icon='EXPORT') class ExternalData(Menu): diff --git a/space_view3d_pie_menus/pie_sculpt_menu.py b/space_view3d_pie_menus/pie_sculpt_menu.py index 91f3147a..5c70718e 100644 --- a/space_view3d_pie_menus/pie_sculpt_menu.py +++ b/space_view3d_pie_menus/pie_sculpt_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Sculpt Brush Menu", "author": "pitiwazou, meta-androcto", "version": (0, 1, 0), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "W key", "warning": "", "wiki_url": "", @@ -123,7 +123,7 @@ class PieSculptthree(Menu): layout.operator("paint.brush_select", text='Snakehook', icon='BRUSH_SNAKE_HOOK').sculpt_tool = 'SNAKE_HOOK' layout.operator("paint.brush_select", - text='Twist', icon='BRUSH_ROTATE').sculpt_tool = 'ROTATE' + text='Twist', icon='BRUSH_ROTATE').sculpt_tool = 'NONE' classes = ( diff --git a/space_view3d_pie_menus/pie_select_menu.py b/space_view3d_pie_menus/pie_select_menu.py index 091e18ed..e47fb5e6 100644 --- a/space_view3d_pie_menus/pie_select_menu.py +++ b/space_view3d_pie_menus/pie_select_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Object/Edit mode Selection Menu", "author": "pitiwazou, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View", "warning": "", "wiki_url": "", @@ -44,7 +44,7 @@ class PieSelectionsMore(Menu): pie = layout.menu_pie() box = pie.split().column() box.operator("object.select_by_type", text="Select By Type", icon='SNAP_VOLUME') - box.operator("object.select_grouped", text="Select Grouped", icon='ROTATE') + box.operator("object.select_grouped", text="Select Grouped", icon='NONE') box.operator("object.select_linked", text="Select Linked", icon='CONSTRAINT_BONE') box.menu("VIEW3D_MT_select_object_more_less", text="More/Less") @@ -58,7 +58,7 @@ class PieSelectionsOM(Menu): layout = self.layout pie = layout.menu_pie() # 4 - LEFT - pie.operator("object.select_by_layer", text="Select By Layer", icon='LAYER_ACTIVE') + pie.row().label(text="") # 6 - RIGHT pie.operator("object.select_random", text="Select Random", icon='GROUP_VERTEX') # 2 - BOTTOM @@ -66,11 +66,11 @@ class PieSelectionsOM(Menu): icon='ZOOM_PREVIOUS').action = 'INVERT' # 8 - TOP pie.operator("object.select_all", text="Select All Toggle", - icon='RENDER_REGION').action = 'TOGGLE' + icon='NONE').action = 'TOGGLE' # 7 - TOP - LEFT - pie.operator("view3d.select_circle", text="Circle Select", icon='BORDER_LASSO') + pie.operator("view3d.select_circle", text="Circle Select", icon='NONE') # 9 - TOP - RIGHT - pie.operator("view3d.select_border", text="Border Select", icon='BORDER_RECT') + pie.operator("view3d.select_box", text="Box Select", icon='NONE') # 1 - BOTTOM - LEFT pie.operator("object.select_camera", text="Select Camera", icon='CAMERA_DATA') # 3 - BOTTOM - RIGHT @@ -86,8 +86,8 @@ class PieSelectionsEM(Menu): layout = self.layout pie = layout.menu_pie() # 4 - LEFT - pie.operator("view3d.select_border", text="Border Select", - icon='BORDER_RECT') + pie.operator("view3d.select_box", text="Box Select", + icon='NONE') # 6 - RIGHT pie.menu("object.selectloopselection", text="Select Loop Menu", icon='LOOPSEL') # 2 - BOTTOM @@ -104,7 +104,7 @@ class PieSelectionsEM(Menu): icon='FULLSCREEN_EXIT').action = 'INVERT' # 1 - BOTTOM - LEFT pie.operator("view3d.select_circle", text="Circle Select", - icon='BORDER_LASSO') + icon='NONE') # 3 - BOTTOM - RIGHT pie.menu("object.selectallbyselection", text="Multi Select Menu", icon='SNAP_EDGE') diff --git a/space_view3d_pie_menus/pie_shading_menu.py b/space_view3d_pie_menus/pie_shading_menu.py index e7eb03b9..26da5d45 100644 --- a/space_view3d_pie_menus/pie_shading_menu.py +++ b/space_view3d_pie_menus/pie_shading_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Viewport Shading Menus", "author": "pitiwazou, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3D View", "warning": "", "wiki_url": "", @@ -43,7 +43,7 @@ class PieShadingView(Menu): layout = self.layout pie = layout.menu_pie() - pie.prop(context.space_data, "viewport_shade", expand=True) + pie.prop(context.space_data.shading, "type", expand=True) if context.active_object: if context.mode == 'EDIT_MESH': diff --git a/space_view3d_pie_menus/pie_snap_menu.py b/space_view3d_pie_menus/pie_snap_menu.py index b5ca502c..c1ee22e7 100644 --- a/space_view3d_pie_menus/pie_snap_menu.py +++ b/space_view3d_pie_menus/pie_snap_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Snap Element Menu", "author": "pitiwazou, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "3d View", "warning": "", "wiki_url": "", @@ -60,7 +60,7 @@ class PieSnaping(Menu): # 1 - BOTTOM - LEFT pie.operator("snap.alignrotation", text="Align rotation", icon='SNAP_NORMAL') # 3 - BOTTOM - RIGHT - pie.operator("wm.call_menu_pie", text="Snap Target", icon='SNAP_SURFACE').name = "snap.targetmenu" + pie.operator("wm.call_menu_pie", text="Snap Target", icon='NONE').name = "snap.targetmenu" class SnapActive(Operator): @@ -89,10 +89,9 @@ class SnapVolume(Operator): ts = context.tool_settings if ts.use_snap is False: ts.use_snap = True - ts.snap_element = 'VOLUME' - - if ts.snap_element != 'VOLUME': - ts.snap_element = 'VOLUME' + ts.snap_elements = {'VOLUME'} + if ts.snap_elements != {'VOLUME'}: + ts.snap_elements = {'VOLUME'} return {'FINISHED'} @@ -106,10 +105,10 @@ class SnapFace(Operator): if ts.use_snap is False: ts.use_snap = True - ts.snap_element = 'FACE' + ts.snap_elements = {'FACE'} - if ts.snap_element != 'FACE': - ts.snap_element = 'FACE' + if ts.snap_elements != {'FACE'}: + ts.snap_elements = {'FACE'} return {'FINISHED'} @@ -123,10 +122,10 @@ class SnapEdge(Operator): if ts.use_snap is False: ts.use_snap = True - ts.snap_element = 'EDGE' + ts.snap_elements = {'EDGE'} - if ts.snap_element != 'EDGE': - ts.snap_element = 'EDGE' + if ts.snap_elements != {'EDGE'}: + ts.snap_elements = {'EDGE'} return {'FINISHED'} @@ -140,10 +139,10 @@ class SnapVertex(Operator): if ts.use_snap is False: ts.use_snap = True - ts.snap_element = 'VERTEX' + ts.snap_elements = {'VERTEX'} - if ts.snap_element != 'VERTEX': - ts.snap_element = 'VERTEX' + if ts.snap_elements != {'VERTEX'}: + ts.snap_elements = {'VERTEX'} return {'FINISHED'} @@ -157,10 +156,10 @@ class SnapIncrement(Operator): if ts.use_snap is False: ts.use_snap = True - ts.snap_element = 'INCREMENT' + ts.snap_elements = {'INCREMENT'} - if ts.snap_element != 'INCREMENT': - ts.snap_element = 'INCREMENT' + if ts.snap_elements != {'INCREMENT'}: + ts.snap_elements = {'INCREMENT'} return {'FINISHED'} @@ -185,7 +184,8 @@ class SnapTargetVariable(Operator): bl_idname = "object.snaptargetvariable" bl_label = "Snap Target Variable" bl_options = {'REGISTER', 'UNDO'} - variable = bpy.props.StringProperty() + + variable: bpy.props.StringProperty() @classmethod def poll(cls, context): diff --git a/space_view3d_pie_menus/pie_views_numpad_menu.py b/space_view3d_pie_menus/pie_views_numpad_menu.py index 02cf5464..6c55bfe4 100644 --- a/space_view3d_pie_menus/pie_views_numpad_menu.py +++ b/space_view3d_pie_menus/pie_views_numpad_menu.py @@ -23,7 +23,7 @@ bl_info = { "description": "Viewport Numpad Menus", "author": "pitiwazou, meta-androcto", "version": (0, 1, 1), - "blender": (2, 77, 0), + "blender": (2, 80, 0), "location": "Q key", "warning": "", "wiki_url": "", @@ -108,17 +108,17 @@ class PieViewNumpad(Menu): rd = scene.render # 4 - LEFT - pie.operator("view3d.viewnumpad", text="Left", icon='TRIA_LEFT').type = 'LEFT' + pie.operator("view3d.view_axis", text="Left", icon='TRIA_LEFT').type = 'LEFT' # 6 - RIGHT - pie.operator("view3d.viewnumpad", text="Right", icon='TRIA_RIGHT').type = 'RIGHT' + pie.operator("view3d.view_axis", text="Right", icon='TRIA_RIGHT').type = 'RIGHT' # 2 - BOTTOM - pie.operator("view3d.viewnumpad", text="Bottom", icon='TRIA_DOWN').type = 'BOTTOM' + pie.operator("view3d.view_axis", text="Bottom", icon='TRIA_DOWN').type = 'BOTTOM' # 8 - TOP - pie.operator("view3d.viewnumpad", text="Top", icon='TRIA_UP').type = 'TOP' + pie.operator("view3d.view_axis", text="Top", icon='TRIA_UP').type = 'TOP' # 7 - TOP - LEFT - pie.operator("view3d.viewnumpad", text="Front").type = 'FRONT' + pie.operator("view3d.view_axis", text="Front").type = 'FRONT' # 9 - TOP - RIGHT - pie.operator("view3d.viewnumpad", text="Back").type = 'BACK' + pie.operator("view3d.view_axis", text="Back").type = 'BACK' # 1 - BOTTOM - LEFT box = pie.split().column() row = box.row(align=True) @@ -131,8 +131,8 @@ class PieViewNumpad(Menu): icon='LOCKED').data_path = "space_data.lock_camera" row = box.row(align=True) - row.operator("view3d.viewnumpad", text="View Cam", icon='VISIBLE_IPO_ON').type = 'CAMERA' - row.operator("view3d.camera_to_view", text="Cam to view", icon='MAN_TRANS') + row.operator("view3d.view_camera", text="View Cam", icon='VISIBLE_IPO_ON') + row.operator("view3d.camera_to_view", text="Cam to view", icon='NONE') icon_locked = 'LOCKED' if ob and ob.lock_rotation[0] is False else \ 'UNLOCKED' if ob and ob.lock_rotation[0] is True else 'LOCKED' @@ -143,7 +143,7 @@ class PieViewNumpad(Menu): row = box.row(align=True) row.prop(rd, "use_border", text="Border") # 3 - BOTTOM - RIGHT - pie.menu(PieViewallSelGlobEtc.bl_idname, text="View Menu", icon='BBOX') + pie.menu(PieViewallSelGlobEtc.bl_idname, text="View Menu", icon='NONE') classes = ( diff --git a/space_view3d_spacebar_menu.py b/space_view3d_spacebar_menu.py index 0831c0d5..c6a8c93f 100644 --- a/space_view3d_spacebar_menu.py +++ b/space_view3d_spacebar_menu.py @@ -64,10 +64,10 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_AddMenu", icon='OBJECT_DATAMODE') layout.menu("VIEW3D_MT_View_Directions", icon='ZOOM_ALL') - layout.menu("VIEW3D_MT_View_Navigation", icon='ROTATE') - layout.menu("VIEW3D_MT_View_Toggle", icon='SPLITSCREEN') + layout.menu("VIEW3D_MT_View_Navigation", icon='PIVOT_BOUNDBOX') + layout.menu("VIEW3D_MT_View_Toggle", icon='WORKSPACE') layout.operator("view3d.snap_cursor_to_center", - text="Cursor to Center") + text="Cursor to World Origin") layout.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid") layout.menu("VIEW3D_MT_UndoS", icon='ARROW_LEFTRIGHT') @@ -91,9 +91,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -121,16 +121,16 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): layout.menu("VIEW3D_MT_Select_Edit_Mesh", icon='RESTRICT_SELECT_OFF') layout.menu("VIEW3D_MT_Edit_Multi", icon='VERTEXSEL') UseSeparator(self, context) - layout.menu("INFO_MT_mesh_add", text="Add Mesh", icon='OUTLINER_OB_MESH') + layout.menu("VIEW3D_MT_mesh_add", text="Add Mesh", icon='OUTLINER_OB_MESH') layout.menu("VIEW3D_MT_Edit_Mesh", text="Mesh", icon='MESH_DATA') UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenuEdit", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_EditCursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_EditCursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) layout.menu("VIEW3D_MT_UV_Map", icon='MOD_UVPROJECT') layout.menu("VIEW3D_MT_edit_mesh_specials", icon='SOLO_OFF') - layout.menu("VIEW3D_MT_edit_mesh_extrude", icon='ORTHO') + layout.menu("VIEW3D_MT_edit_mesh_extrude", icon='XRAY') UseSeparator(self, context) layout.operator_menu_enum("object.modifier_add", "type", icon='MODIFIER') layout.operator_menu_enum("object.constraint_add", @@ -230,9 +230,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -259,13 +259,13 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): layout.menu("VIEW3D_MT_Select_Edit_Curve", icon='RESTRICT_SELECT_OFF') UseSeparator(self, context) - layout.menu("INFO_MT_curve_add", text="Add Curve", + layout.menu("VIEW3D_MT_curve_add", text="Add Curve", icon='OUTLINER_OB_CURVE') layout.menu("VIEW3D_MT_Edit_Curve", icon='CURVE_DATA') UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') layout.menu("VIEW3D_MT_EditCurveCtrlpoints", icon='CURVE_BEZCURVE') layout.menu("VIEW3D_MT_EditCurveSpecials", @@ -294,9 +294,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -321,11 +321,11 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): layout.menu("VIEW3D_MT_View_Menu", icon='ZOOM_ALL') layout.menu("VIEW3D_MT_Select_Edit_Surface", icon='RESTRICT_SELECT_OFF') UseSeparator(self, context) - layout.menu("INFO_MT_surface_add", text="Add Surface", + layout.menu("VIEW3D_MT_surface_add", text="Add Surface", icon='OUTLINER_OB_SURFACE') layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) layout.prop_menu_enum(settings, "proportional_edit", icon="PROP_CON") @@ -357,9 +357,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -388,7 +388,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): icon='OUTLINER_OB_META') layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) layout.prop_menu_enum(settings, "proportional_edit", icon="PROP_CON") @@ -418,9 +418,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -470,9 +470,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): layout.menu("VIEW3D_MT_Object", icon='VIEW3D') UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') - layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -489,7 +489,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): layout.operator("view3d.properties", icon='MENU_PANEL') # Lamp Object Mode # - if obj and obj.type == 'LAMP' and obj.mode in {'OBJECT'}: + if obj and obj.type == 'LIGHT' and obj.mode in {'OBJECT'}: layout.operator_context = 'INVOKE_REGION_WIN' layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM') @@ -501,9 +501,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): layout.menu("VIEW3D_MT_Object", icon='VIEW3D') UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenuLite", icon='MANIPUL') - layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -533,9 +533,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenuArmature", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -561,7 +561,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): layout.menu("VIEW3D_MT_Select_Edit_Armature", icon='RESTRICT_SELECT_OFF') UseSeparator(self, context) - layout.menu("INFO_MT_armature_add", text="Add Armature", + layout.menu("VIEW3D_MT_armature_add", text="Add Armature", icon='OUTLINER_OB_ARMATURE') layout.menu("VIEW3D_MT_Edit_Armature", text="Armature", icon='OUTLINER_DATA_ARMATURE') @@ -570,8 +570,8 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenuArmatureEdit", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR') - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_armature_specials", icon='SOLO_OFF') layout.menu("VIEW3D_MT_edit_armature_roll", icon='BONE_DATA') @@ -599,10 +599,10 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): layout.menu("VIEW3D_MT_TransformMenuArmaturePose", icon='MANIPUL') layout.menu("VIEW3D_MT_pose_transform", icon='EMPTY_DATA') UseSeparator(self, context) - layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR') layout.menu("VIEW3D_MT_PoseCopy", icon='FILE') - if arm.draw_type in {'BBONE', 'ENVELOPE'}: + if arm.display_type in {'BBONE', 'ENVELOPE'}: layout.operator("transform.transform", text="Scale Envelope Distance").mode = 'BONE_SIZE' @@ -635,9 +635,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -666,7 +666,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenu", icon='PIVOT_CURSOR') UseSeparator(self, context) layout.prop_menu_enum(settings, "proportional_edit", icon="PROP_CON") @@ -695,9 +695,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenuLite", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.menu("VIEW3D_MT_object_specials", text="Specials", icon='SOLO_OFF') @@ -726,9 +726,9 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): layout.menu("VIEW3D_MT_Object", icon='VIEW3D') UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenuLite", icon='MANIPUL') - layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR') UseSeparator(self, context) - layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE') + layout.menu("VIEW3D_MT_ParentMenu", icon='PIVOT_ACTIVE') layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP') UseSeparator(self, context) layout.operator_menu_enum("object.constraint_add", @@ -754,7 +754,7 @@ class VIEW3D_MT_Space_Dynamic_Menu(Menu): UseSeparator(self, context) layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL') layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR') - layout.menu("VIEW3D_MT_CursorMenuLite", icon='CURSOR') + layout.menu("VIEW3D_MT_CursorMenuLite", icon='PIVOT_CURSOR') UseSeparator(self, context) layout.prop_menu_enum(settings, "proportional_edit", icon="PROP_CON") @@ -807,7 +807,6 @@ class VIEW3D_MT_Object(Menu): layout.menu("VIEW3D_MT_object_track") layout.menu("VIEW3D_MT_object_animation") UseSeparator(self, context) - layout.menu("VIEW3D_MT_object_game") layout.menu("VIEW3D_MT_object_showhide") UseSeparator(self, context) layout.operator_menu_enum("object.convert", "target") @@ -821,18 +820,18 @@ class VIEW3D_MT_AddMenu(Menu): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - layout.menu("INFO_MT_mesh_add", text="Add Mesh", + layout.menu("VIEW3D_MT_mesh_add", text="Add Mesh", icon='OUTLINER_OB_MESH') - layout.menu("INFO_MT_curve_add", text="Add Curve", + layout.menu("VIEW3D_MT_curve_add", text="Add Curve", icon='OUTLINER_OB_CURVE') - layout.menu("INFO_MT_surface_add", text="Add Surface", + layout.menu("VIEW3D_MT_surface_add", text="Add Surface", icon='OUTLINER_OB_SURFACE') layout.operator_menu_enum("object.metaball_add", "type", icon='OUTLINER_OB_META') layout.operator("object.text_add", text="Add Text", icon='OUTLINER_OB_FONT') UseSeparator(self, context) - layout.menu("INFO_MT_armature_add", text="Add Armature", + layout.menu("VIEW3D_MT_armature_add", text="Add Armature", icon='OUTLINER_OB_ARMATURE') layout.operator("object.add", text="Lattice", icon='OUTLINER_OB_LATTICE').type = 'LATTICE' @@ -842,8 +841,8 @@ class VIEW3D_MT_AddMenu(Menu): UseSeparator(self, context) layout.operator("object.camera_add", text="Camera", icon='OUTLINER_OB_CAMERA') - layout.operator_menu_enum("object.lamp_add", "type", - icon="OUTLINER_OB_LAMP") + layout.operator_menu_enum("object.light_add", "type", + icon="OUTLINER_OB_LIGHT") UseSeparator(self, context) layout.operator_menu_enum("object.effector_add", "type", text="Force Field", @@ -851,18 +850,18 @@ class VIEW3D_MT_AddMenu(Menu): layout.menu("VIEW3D_MT_object_quick_effects", text="Quick Effects", icon='PARTICLES') UseSeparator(self, context) - has_groups = (len(bpy.data.groups) > 0) + has_groups = (len(bpy.data.collections) > 0) col_group = layout.column() col_group.enabled = has_groups - if not has_groups or len(bpy.data.groups) > 10: + if not has_groups or len(bpy.data.collections) > 10: col_group.operator_context = 'INVOKE_REGION_WIN' - col_group.operator("object.group_instance_add", - text="Group Instance..." if has_groups else "No Groups in Data", + col_group.operator("object.collection_instance_add", + text="Collection Instance..." if has_groups else "No Groups in Data", icon='GROUP_VERTEX') else: - col_group.operator_menu_enum("object.group_instance_add", "group", - text="Group Instance", icon='GROUP_VERTEX') + col_group.operator_menu_enum("object.collection_instance_add", "collection", + text="Collection Instance", icon='GROUP_VERTEX') # ********** Object Manipulator ********** @@ -975,18 +974,20 @@ class VIEW3D_MT_CursorMenu(Menu): layout.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected") layout.operator("view3d.snap_cursor_to_center", - text="Cursor to Center") + text="Cursor to World Origin") layout.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid") layout.operator("view3d.snap_cursor_to_active", text="Cursor to Active") UseSeparator(self, context) - layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor").use_offset = False - layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor (Offset)").use_offset = True + layout.operator("view3d.snap_selected_to_cursor", + text="Selection to Cursor").use_offset = False + layout.operator("view3d.snap_selected_to_cursor", + text="Selection to Cursor (Keep Offset)").use_offset = True layout.operator("view3d.snap_selected_to_grid", text="Selection to Grid") layout.operator("view3d.snap_cursor_selected_to_center", - text="Selection and Cursor to Center") + text="Selection and Cursor to World Origin") UseSeparator(self, context) layout.menu("VIEW3D_MT_Pivot") layout.operator("view3d.pivot_cursor", @@ -1006,18 +1007,20 @@ class VIEW3D_MT_CursorMenuLite(Menu): layout.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected") layout.operator("view3d.snap_cursor_to_center", - text="Cursor to Center") + text="Cursor to World Origin") layout.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid") layout.operator("view3d.snap_cursor_to_active", text="Cursor to Active") UseSeparator(self, context) - layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor").use_offset = False - layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor (Offset)").use_offset = True + layout.operator("view3d.snap_selected_to_cursor", + text="Selection to Cursor").use_offset = False + layout.operator("view3d.snap_selected_to_cursor", + text="Selection to Cursor (Keep Offset)").use_offset = True layout.operator("view3d.snap_selected_to_grid", text="Selection to Grid") layout.operator("view3d.snap_cursor_selected_to_center", - text="Selection and Cursor to Center") + text="Selection and Cursor to World Origin") UseSeparator(self, context) layout.menu("VIEW3D_MT_Pivot") layout.operator("view3d.pivot_cursor", @@ -1175,12 +1178,12 @@ class VIEW3D_MT_GroupMenu(Menu): def draw(self, context): layout = self.layout - layout.operator("group.create") - layout.operator("group.objects_add_active") + layout.operator("collection.create") + layout.operator("collection.objects_add_active") UseSeparator(self, context) - layout.operator("group.objects_remove") - layout.operator("group.objects_remove_all") - layout.operator("group.objects_remove_active") + layout.operator("collection.objects_remove") + layout.operator("collection.objects_remove_all") + layout.operator("collection.objects_remove_active") # ********** Object Camera Options ********** @@ -1348,7 +1351,7 @@ class VIEW3D_MT_Edit_Multi(Menu): prop = layout.operator("wm.context_set_value", text="Vertex & Face Select", - icon='ORTHO') + icon='XRAY') prop.value = "(True, False, True)" prop.data_path = "tool_settings.mesh_select_mode" @@ -1412,7 +1415,7 @@ class VIEW3D_MT_EditCursorMenu(Menu): layout.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected") layout.operator("view3d.snap_cursor_to_center", - text="Cursor to Center") + text="Cursor to World Origin") layout.operator("view3d.snap_cursor_to_grid", text="Cursor to Grid") layout.operator("view3d.snap_cursor_to_active", @@ -1420,8 +1423,10 @@ class VIEW3D_MT_EditCursorMenu(Menu): layout.operator("view3d.snap_cursor_to_edge_intersection", text="Cursor to Edge Intersection") UseSeparator(self, context) - layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor").use_offset = False - layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor (Offset)").use_offset = True + layout.operator("view3d.snap_selected_to_cursor", + text="Selection to Cursor").use_offset = False + layout.operator("view3d.snap_selected_to_cursor", + text="Selection to Cursor (Keep Offset)").use_offset = True layout.operator("view3d.snap_selected_to_grid", text="Selection to Grid") UseSeparator(self, context) @@ -1648,7 +1653,7 @@ class VIEW3D_MT_Hide_Masks(Menu): props = layout.operator("paint.mask_lasso_gesture", text="Lasso Mask") UseSeparator(self, context) - props = layout.operator("view3d.select_border", text="Box Mask", icon="BORDER_RECT") + props = layout.operator("view3d.select_box", text="Box Mask", icon="BORDER_RECT") props = layout.operator("paint.hide_show", text="Box Hide") props.action = 'HIDE' props.area = 'INSIDE' @@ -1890,7 +1895,7 @@ class VIEW3D_MT_TransformMenu(Menu): layout = self.layout layout.menu("VIEW3D_MT_ManipulatorMenu1") UseSeparator(self, context) - layout.operator("transform.translate", text="Grab/Move") + layout.operator("transform.translate", text="Move") layout.operator("transform.rotate", text="Rotate") layout.operator("transform.resize", text="Scale") UseSeparator(self, context) @@ -1920,7 +1925,7 @@ class VIEW3D_MT_TransformMenuEdit(Menu): layout = self.layout layout.menu("VIEW3D_MT_ManipulatorMenu1") UseSeparator(self, context) - layout.operator("transform.translate", text="Grab/Move") + layout.operator("transform.translate", text="Move") layout.operator("transform.rotate", text="Rotate") layout.operator("transform.resize", text="Scale") UseSeparator(self, context) @@ -1950,7 +1955,7 @@ class VIEW3D_MT_TransformMenuLite(Menu): layout = self.layout layout.menu("VIEW3D_MT_ManipulatorMenu1") UseSeparator(self, context) - layout.operator("transform.translate", text="Grab/Move") + layout.operator("transform.translate", text="Move") layout.operator("transform.rotate", text="Rotate") layout.operator("transform.resize", text="Scale") UseSeparator(self, context) @@ -1971,7 +1976,7 @@ class VIEW3D_MT_TransformMenuCamera(Menu): layout.menu("VIEW3D_MT_ManipulatorMenu1") layout.menu("VIEW3D_MT_object_clear") layout.menu("VIEW3D_MT_object_apply") - layout.operator("transform.translate", text="Grab/Move") + layout.operator("transform.translate", text="Move") layout.operator("transform.rotate", text="Rotate") layout.operator("transform.resize", text="Scale") layout.operator("object.align") @@ -1990,7 +1995,7 @@ class VIEW3D_MT_TransformMenuArmature(Menu): layout.menu("VIEW3D_MT_ManipulatorMenu1") UseSeparator(self, context) - layout.operator("transform.translate", text="Grab/Move") + layout.operator("transform.translate", text="Move") layout.operator("transform.rotate", text="Rotate") layout.operator("transform.resize", text="Scale") UseSeparator(self, context) @@ -2016,7 +2021,7 @@ class VIEW3D_MT_TransformMenuArmatureEdit(Menu): layout = self.layout layout.menu("VIEW3D_MT_ManipulatorMenu1") UseSeparator(self, context) - layout.operator("transform.translate", text="Grab/Move") + layout.operator("transform.translate", text="Move") layout.operator("transform.rotate", text="Rotate") layout.operator("transform.resize", text="Scale") UseSeparator(self, context) @@ -2038,7 +2043,7 @@ class VIEW3D_MT_TransformMenuArmaturePose(Menu): def draw(self, context): layout = self.layout layout.menu("VIEW3D_MT_ManipulatorMenu1") - layout.operator("transform.translate", text="Grab/Move") + layout.operator("transform.translate", text="Move") layout.operator("transform.rotate", text="Rotate") layout.operator("transform.resize", text="Scale") UseSeparator(self, context) @@ -2052,9 +2057,9 @@ class VIEW3D_MT_TransformMenuArmaturePose(Menu): layout.operator("pose.user_transforms_clear", text="Reset unkeyed") obj = context.object if obj.type == 'ARMATURE' and obj.mode in {'EDIT', 'POSE'}: - if obj.data.draw_type == 'BBONE': + if obj.data.display_type == 'BBONE': layout.operator("transform.transform", text="Scale BBone").mode = 'BONE_SIZE' - elif obj.data.draw_type == 'ENVELOPE': + elif obj.data.display_type == 'ENVELOPE': layout.operator("transform.transform", text="Scale Envelope Distance").mode = 'BONE_SIZE' layout.operator("transform.transform", text="Scale Radius").mode = 'BONE_ENVELOPE' @@ -2086,7 +2091,7 @@ class VIEW3D_MT_View_Border(Menu): layout.operator_context = 'INVOKE_REGION_WIN' layout.operator("view3d.clip_border", text="Clipping Border...") layout.operator("view3d.zoom_border", text="Zoom Border...") - layout.operator("view3d.render_border", text="Render Border...").camera_only = False + layout.operator("view3d.render_border", text="Render Border...") class VIEW3D_MT_View_Toggle(Menu): @@ -2223,8 +2228,8 @@ class VIEW3D_MT_Shade(Menu): layout.operator("OBJECT_OT_shade_flat") UseSeparator(self, context) - layout.operator("view3d.display_wire_all", text="Wire all", icon='WIRE') - layout.prop(context.object, "show_x_ray", text="X-Ray", icon="META_CUBE") + layout.operator("view3d.display_wire_all", text="Wire all", icon='SHADING_WIRE') + layout.prop(context.object, "show_in_front", text="X-Ray", icon="META_CUBE") UseSeparator(self, context) layout.prop(context.space_data.fx_settings, "use_ssao", @@ -2267,7 +2272,7 @@ class VIEW3D_MT_Select_Object(Menu): def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") UseSeparator(self, context) layout.operator("object.select_all").action = 'TOGGLE' @@ -2317,7 +2322,7 @@ class VIEW3D_MT_Select_Edit_Mesh(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") UseSeparator(self, context) layout.operator("mesh.select_all").action = 'TOGGLE' @@ -2383,7 +2388,7 @@ class VIEW3D_MT_Select_Edit_Curve(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") UseSeparator(self, context) layout.operator("curve.select_all").action = 'TOGGLE' @@ -2408,7 +2413,7 @@ class VIEW3D_MT_SelectArmatureMenu(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("armature.select_all") layout.operator("armature.select_inverse", text="Inverse") layout.operator("armature.select_hierarchy", @@ -2432,7 +2437,7 @@ class VIEW3D_MT_Select_Edit_Armature(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") UseSeparator(self, context) @@ -2475,7 +2480,7 @@ class VIEW3D_MT_Select_Pose(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") UseSeparator(self, context) layout.operator("pose.select_all").action = 'TOGGLE' @@ -2556,7 +2561,7 @@ class VIEW3D_MT_Select_Edit_Surface(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") UseSeparator(self, context) layout.operator("curve.select_all").action = 'TOGGLE' @@ -2577,7 +2582,7 @@ class VIEW3D_MT_SelectMetaball(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") UseSeparator(self, context) layout.operator("mball.select_all").action = 'TOGGLE' @@ -2590,7 +2595,7 @@ class VIEW3D_MT_Select_Edit_Metaball(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") layout.operator("mball.select_all").action = 'TOGGLE' layout.operator("mball.select_all", text="Inverse").action = 'INVERT' @@ -2615,7 +2620,7 @@ class VIEW3D_MT_Select_Particle(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") UseSeparator(self, context) @@ -2642,7 +2647,7 @@ class VIEW3D_MT_Select_Edit_Lattice(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") UseSeparator(self, context) layout.operator("lattice.select_mirror") @@ -2662,7 +2667,7 @@ class VIEW3D_MT_Select_Gpencil(Menu): def draw(self, context): layout = self.layout - layout.operator("gpencil.select_border") + layout.operator("gpencil.select_box") layout.operator("gpencil.select_circle") UseSeparator(self, context) @@ -2700,7 +2705,7 @@ class VIEW3D_MT_Select_Paint_Mask(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") layout.operator("paint.face_select_all").action = 'TOGGLE' layout.operator("paint.face_select_all", text="Inverse").action = 'INVERT' @@ -2712,7 +2717,7 @@ class VIEW3D_MT_Select_Paint_Mask_Vertex(Menu): def draw(self, context): layout = self.layout - layout.operator("view3d.select_border") + layout.operator("view3d.select_box") layout.operator("view3d.select_circle") layout.operator("paint.vert_select_all").action = 'TOGGLE' layout.operator("paint.vert_select_all", text="Inverse").action = 'INVERT' @@ -2921,7 +2926,7 @@ class SetOriginToSelected(Operator): # Code thanks to Isaac Weaver (wisaac) D1963 class SnapCursSelToCenter(Operator): bl_idname = "view3d.snap_cursor_selected_to_center" - bl_label = "Snap Cursor & Selection to Center" + bl_label = "Snap Cursor & Selection to World Origin" bl_description = ("Snap 3D cursor and selected objects to the center \n" "Works only in Object Mode") diff --git a/system_blend_info.py b/system_blend_info.py index 8b2a0939..65b1d9f9 100644 --- a/system_blend_info.py +++ b/system_blend_info.py @@ -102,10 +102,10 @@ class OBJECT_PT_blendinfo(bpy.types.Panel): icon='CAMERA_DATA') row = ob_cols[1].row() - lamps = [o for o in objects.values() if o.type == 'LAMP'] + lamps = [o for o in objects.values() if o.type == 'LIGHT'] num = len(lamps) row.label(text=quantity_string(num, "Lamp", "Lamps"), - icon='LAMP_DATA') + icon='LIGHT_DATA') row = ob_cols[0].row() armatures = [o for o in objects.values() if o.type == 'ARMATURE'] @@ -159,9 +159,9 @@ class OBJECT_PT_blendinfo(bpy.types.Panel): icon='CAMERA_DATA') row = db_cols[1].row() - num = len(bpy.data.lamps) + num = len(bpy.data.lights) row.label(text=quantity_string(num, "Lamp", "Lamps"), - icon='LAMP_DATA') + icon='LIGHT_DATA') row = db_cols[0].row() num = len(bpy.data.armatures) diff --git a/system_demo_mode/__init__.py b/system_demo_mode/__init__.py index 000fcef1..b07da919 100644 --- a/system_demo_mode/__init__.py +++ b/system_demo_mode/__init__.py @@ -21,14 +21,15 @@ bl_info = { "name": "Demo Mode", "author": "Campbell Barton", - "blender": (2, 57, 0), + "blender": (2, 80, 0), "location": "Demo Menu", "description": "Demo mode lets you select multiple blend files and loop over them.", "warning": "", "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/System/Demo_Mode#Running_Demo_Mode", "support": 'OFFICIAL', - "category": "System"} + "category": "System", +} # To support reload properly, try to access a package var, if it's there, reload everything if "bpy" in locals(): @@ -39,12 +40,12 @@ if "bpy" in locals(): import bpy from bpy.props import ( - StringProperty, - BoolProperty, - IntProperty, - FloatProperty, - EnumProperty, - ) + StringProperty, + BoolProperty, + IntProperty, + FloatProperty, + EnumProperty, +) class DemoModeSetup(bpy.types.Operator): @@ -57,85 +58,86 @@ class DemoModeSetup(bpy.types.Operator): # to the class instance from the operator settings before calling. # these are used to create the file list. - directory = StringProperty( - name="Search Path", - description="Directory used for importing the file", - maxlen=1024, - subtype='DIR_PATH', - ) - random_order = BoolProperty( - name="Random Order", - description="Select files randomly", - default=False, - ) - mode = EnumProperty( - name="Method", - items=(('AUTO', "Auto", ""), - ('PLAY', "Play", ""), - ('RENDER', "Render", ""), - ), - ) - - run = BoolProperty( - name="Run Immediately!", - description="Run demo immediately", - default=True, - ) - exit = BoolProperty( - name="Exit", - description="Run once and exit", - default=False, - ) + directory: StringProperty( + name="Search Path", + description="Directory used for importing the file", + maxlen=1024, + subtype='DIR_PATH', + ) + random_order: BoolProperty( + name="Random Order", + description="Select files randomly", + default=False, + ) + mode: EnumProperty( + name="Method", + items=( + ('AUTO', "Auto", ""), + ('PLAY', "Play", ""), + ('RENDER', "Render", ""), + ) + ) + + run: BoolProperty( + name="Run Immediately!", + description="Run demo immediately", + default=True, + ) + exit: BoolProperty( + name="Exit", + description="Run once and exit", + default=False, + ) # these are mapped directly to the config! # # anim # ==== - anim_cycles = IntProperty( - name="Cycles", - description="Number of times to play the animation", - min=1, max=1000, - default=2, - ) - anim_time_min = FloatProperty( - name="Time Min", - description="Minimum number of seconds to show the animation for " - "(for small loops)", - min=0.0, max=1000.0, - soft_min=1.0, soft_max=1000.0, - default=4.0, - ) - anim_time_max = FloatProperty( - name="Time Max", - description="Maximum number of seconds to show the animation for " - "(in case the end frame is very high for no reason)", - min=0.0, max=100000000.0, - soft_min=1.0, soft_max=100000000.0, - default=8.0, - ) - anim_screen_switch = FloatProperty( - name="Screen Switch", - description="Time between switching screens (in seconds) " - "or 0 to disable", - min=0.0, max=100000000.0, - soft_min=1.0, soft_max=60.0, - default=0.0, - ) + anim_cycles: IntProperty( + name="Cycles", + description="Number of times to play the animation", + min=1, max=1000, + default=2, + ) + anim_time_min: FloatProperty( + name="Time Min", + description="Minimum number of seconds to show the animation for " + "(for small loops)", + min=0.0, max=1000.0, + soft_min=1.0, soft_max=1000.0, + default=4.0, + ) + anim_time_max: FloatProperty( + name="Time Max", + description="Maximum number of seconds to show the animation for " + "(in case the end frame is very high for no reason)", + min=0.0, max=100000000.0, + soft_min=1.0, soft_max=100000000.0, + default=8.0, + ) + anim_screen_switch: FloatProperty( + name="Screen Switch", + description="Time between switching screens (in seconds) " + "or 0 to disable", + min=0.0, max=100000000.0, + soft_min=1.0, soft_max=60.0, + default=0.0, + ) # # render # ====== - display_render = FloatProperty( - name="Render Delay", - description="Time to display the rendered image before moving on " - "(in seconds)", - min=0.0, max=60.0, - default=4.0, - ) - anim_render = BoolProperty( - name="Render Anim", - description="Render entire animation (render mode only)", - default=False, - ) + display_render: FloatProperty( + name="Render Delay", + description="Time to display the rendered image before moving on " + "(in seconds)", + min=0.0, max=60.0, + default=4.0, + ) + anim_render: BoolProperty( + name="Render Anim", + description="Render entire animation (render mode only)", + default=False, + ) def execute(self, context): from . import config @@ -168,13 +170,13 @@ class DemoModeSetup(bpy.types.Operator): layout = self.layout box = layout.box() - box.label("Search *.blend recursively") - box.label("Writes: demo.py config text") + box.label(text="Search *.blend recursively") + box.label(text="Writes: demo.py config text") layout.prop(self, "run") layout.prop(self, "exit") - layout.label("Generate Settings:") + layout.label(text="Generate Settings:") row = layout.row() row.prop(self, "mode", expand=True) layout.prop(self, "random_order") @@ -184,7 +186,7 @@ class DemoModeSetup(bpy.types.Operator): layout.separator() sub = layout.column() sub.active = (mode in {'AUTO', 'PLAY'}) - sub.label("Animate Settings:") + sub.label(text="Animate Settings:") sub.prop(self, "anim_cycles") sub.prop(self, "anim_time_min") sub.prop(self, "anim_time_max") @@ -193,7 +195,7 @@ class DemoModeSetup(bpy.types.Operator): layout.separator() sub = layout.column() sub.active = (mode in {'AUTO', 'RENDER'}) - sub.label("Render Settings:") + sub.label(text="Render Settings:") sub.prop(self, "display_render") @@ -244,20 +246,27 @@ def menu_func(self, context): layout.separator() +classes = ( + DemoModeSetup, + DemoModeRun, +) + def register(): - bpy.utils.register_class(DemoModeSetup) - bpy.utils.register_class(DemoModeRun) + from bpy.utils import register_class + for cls in classes: + register_class(cls) - bpy.types.INFO_MT_file.prepend(menu_func) + bpy.types.TOPBAR_MT_file.prepend(menu_func) extern_demo_mode_register() def unregister(): - bpy.utils.unregister_class(DemoModeSetup) - bpy.utils.unregister_class(DemoModeRun) + from bpy.utils import unregister_class + for cls in classes: + unregister_class(cls) - bpy.types.INFO_MT_file.remove(menu_func) + bpy.types.TOPBAR_MT_file.remove(menu_func) extern_demo_mode_unregister() diff --git a/system_demo_mode/config.py b/system_demo_mode/config.py index 24e25be5..b3a7aad4 100644 --- a/system_demo_mode/config.py +++ b/system_demo_mode/config.py @@ -65,7 +65,7 @@ def as_string(dirpath, random_order, exit, **kwargs): "\n", "exit = %r\n" % exit, "\n", - ] + ] # All these work but use nicest formatting! if 0: # works but not nice to edit. diff --git a/system_demo_mode/demo_mode.py b/system_demo_mode/demo_mode.py index 9b94378c..b365fdfc 100644 --- a/system_demo_mode/demo_mode.py +++ b/system_demo_mode/demo_mode.py @@ -44,23 +44,26 @@ DEMO_CFG = "demo.py" # populate from script global_config_files = [] -global_config = dict(anim_cycles=1, - anim_render=False, - anim_screen_switch=0.0, - anim_time_max=60.0, - anim_time_min=4.0, - mode='AUTO', - display_render=4.0) +global_config = dict( + anim_cycles=1, + anim_render=False, + anim_screen_switch=0.0, + anim_time_max=60.0, + anim_time_min=4.0, + mode='AUTO', + display_render=4.0, +) # switch to the next file in 2 sec. -global_config_fallback = dict(anim_cycles=1, - anim_render=False, - anim_screen_switch=0.0, - anim_time_max=60.0, - anim_time_min=4.0, - mode='AUTO', - display_render=4.0) - +global_config_fallback = dict( + anim_cycles=1, + anim_render=False, + anim_screen_switch=0.0, + anim_time_max=60.0, + anim_time_min=4.0, + mode='AUTO', + display_render=4.0, +) global_state = { "init_time": 0.0, @@ -154,7 +157,7 @@ def demo_mode_next_file(step=1): def demo_mode_timer_add(): - global_state["timer"] = bpy.context.window_manager.event_timer_add(0.8, bpy.context.window) + global_state["timer"] = bpy.context.window_manager.event_timer_add(0.8, window=bpy.context.window) def demo_mode_timer_remove(): @@ -253,9 +256,10 @@ def demo_mode_update(): demo_mode_next_file() return # above cycles and minimum display time - if (time_total > global_config["anim_time_min"]) and \ - (global_state["anim_cycles"] > global_config["anim_cycles"]): - + if ( + (time_total > global_config["anim_time_min"]) and + (global_state["anim_cycles"] > global_config["anim_cycles"]) + ): # looped enough now. demo_mode_next_file() return @@ -418,12 +422,12 @@ class DemoModeControl(bpy.types.Operator): bl_idname = "wm.demo_mode_control" bl_label = "Control" - mode = bpy.props.EnumProperty(items=( - ('PREV', "Prev", ""), - ('PAUSE', "Pause", ""), - ('NEXT', "Next", ""), - ), - name="Mode") + mode: bpy.props.EnumProperty( + items=(('PREV', "Prev", ""), + ('PAUSE', "Pause", ""), + ('NEXT', "Next", "")), + name="Mode" + ) def execute(self, context): mode = self.mode @@ -441,7 +445,7 @@ def menu_func(self, context): layout = self.layout layout.operator_context = 'EXEC_DEFAULT' row = layout.row(align=True) - row.label("Demo Mode:") + row.label(text="Demo Mode:") if not DemoMode.enabled: row.operator("wm.demo_mode", icon='PLAY', text="") else: diff --git a/system_property_chart.py b/system_property_chart.py index 028786c9..3fc5ff13 100644 --- a/system_property_chart.py +++ b/system_property_chart.py @@ -179,8 +179,8 @@ def _property_chart_draw(self, context): col.label(text="Properties") row = col.row(align=True) row.menu("SCENE_MT_properties_presets", text=bpy.types.SCENE_MT_properties_presets.bl_label) - row.operator("scene.properties_preset_add", text="", icon="ZOOMIN") - row.operator("scene.properties_preset_add", text="", icon="ZOOMOUT").remove_active = True + row.operator("scene.properties_preset_add", text="", icon='ADD') + row.operator("scene.properties_preset_add", text="", icon='REMOVE').remove_active = True # edit the display props col.prop(id_storage, self._PROP_STORAGE_ID, text="") diff --git a/ui_layer_manager.py b/ui_layer_manager.py index 018aad26..d3e20e69 100644 --- a/ui_layer_manager.py +++ b/ui_layer_manager.py @@ -277,11 +277,11 @@ class SCENE_OT_namedlayer_toggle_wire(Operator): group_layers = scene.layergroups[group_idx].layers layers = obj.layers if True in {layer and group_layer for layer, group_layer in zip(layers, group_layers)}: - obj.draw_type = display + obj.display_type = display scene.layergroups[group_idx].use_wire = use_wire else: if obj.layers[layer_idx]: - obj.draw_type = display + obj.display_type = display scene.namedlayers.layers[layer_idx].use_wire = use_wire return {'FINISHED'} @@ -580,8 +580,8 @@ class SCENE_PT_namedlayer_groups(Panel): row.template_list("SCENE_UL_namedlayer_groups", "", scene, "layergroups", scene, "layergroups_index") col = row.column(align=True) - col.operator("scene.namedlayer_group_add", icon='ZOOMIN', text="").layers = scene.layers - col.operator("scene.namedlayer_group_remove", icon='ZOOMOUT', text="").group_idx = group_idx + col.operator("scene.namedlayer_group_add", icon='ADD', text="").layers = scene.layers + col.operator("scene.namedlayer_group_remove", icon='REMOVE', text="").group_idx = group_idx if bool(scene.layergroups): layout.prop(scene.layergroups[group_idx], "layers", text="", toggle=True) diff --git a/ui_translate/__init__.py b/ui_translate/__init__.py index 5ac0db95..4029bae1 100644 --- a/ui_translate/__init__.py +++ b/ui_translate/__init__.py @@ -21,11 +21,11 @@ bl_info = { "name": "Manage UI translations", "author": "Bastien Montagne", - "version": (1, 1, 4), - "blender": (2, 79, 0), + "version": (1, 1, 5), + "blender": (2, 80, 0), "location": "Main \"File\" menu, text editor, any UI control", - "description": "Allow to manage UI translations directly from Blender " - "(update main po files, update scripts' translations, etc.)", + "description": "Allows managing UI translations directly from Blender " + "(update main .po files, update scripts' translations, etc.)", "warning": "Still in development, not all features are fully implemented yet!", "wiki_url": "http://wiki.blender.org/index.php/Dev:Doc/How_to/Translate_Blender", "support": 'OFFICIAL', @@ -42,15 +42,12 @@ if "bpy" in locals(): else: import bpy from . import ( - settings, - edit_translation, - update_svn, - update_addon, - update_ui, - ) - - -import os + settings, + edit_translation, + update_svn, + update_addon, + update_ui, + ) classes = settings.classes + edit_translation.classes + update_svn.classes + update_addon.classes + update_ui.classes @@ -59,18 +56,21 @@ classes = settings.classes + edit_translation.classes + update_svn.classes + upd def register(): for cls in classes: bpy.utils.register_class(cls) + bpy.types.WindowManager.i18n_update_svn_settings = \ - bpy.props.PointerProperty(type=update_ui.I18nUpdateTranslationSettings) + bpy.props.PointerProperty(type=update_ui.I18nUpdateTranslationSettings) # Init addon's preferences (unfortunately, as we are using an external storage for the properties, # the load/save user preferences process has no effect on them :( ). if __name__ in bpy.context.user_preferences.addons: pref = bpy.context.user_preferences.addons[__name__].preferences + import os if os.path.isfile(pref.persistent_data_path): pref._settings.load(pref.persistent_data_path, reset=True) def unregister(): - del bpy.types.WindowManager.i18n_update_svn_settings for cls in classes: bpy.utils.unregister_class(cls) + + del bpy.types.WindowManager.i18n_update_svn_settings diff --git a/ui_translate/edit_translation.py b/ui_translate/edit_translation.py index c66e2fac..8fdf676a 100644 --- a/ui_translate/edit_translation.py +++ b/ui_translate/edit_translation.py @@ -18,30 +18,24 @@ # <pep8 compliant> +import os +import shutil if "bpy" in locals(): import importlib importlib.reload(settings) importlib.reload(utils_i18n) else: import bpy + from bpy.types import Operator from bpy.props import ( - BoolProperty, - CollectionProperty, - EnumProperty, - FloatProperty, - FloatVectorProperty, - IntProperty, - PointerProperty, - StringProperty, - ) + BoolProperty, + EnumProperty, + StringProperty, + ) from . import settings from bl_i18n_utils import utils as utils_i18n -import os -import shutil - - # A global cache for I18nMessages objects, as parsing po files takes a few seconds. PO_CACHE = {} @@ -52,21 +46,30 @@ def _get_messages(lang, fname): return PO_CACHE[fname] -class UI_OT_i18n_edittranslation_update_mo(bpy.types.Operator): - """Try to "compile" given po file into relevant blender.mo file """ \ - """(WARNING: it will replace the official mo file in your user dir!)""" +class UI_OT_i18n_edittranslation_update_mo(Operator): + """Try to "compile" given po file into relevant blender.mo file + (WARNING: it will replace the official mo file in your user dir!)""" bl_idname = "ui.i18n_edittranslation_update_mo" bl_label = "Edit Translation Update Mo" - # "Parameters" - lang = StringProperty(description="Current (translated) language", - options={'SKIP_SAVE'}) - po_file = StringProperty(description="Path to the matching po file", - subtype='FILE_PATH', options={'SKIP_SAVE'}) - clean_mo = BoolProperty(description="Clean up (remove) all local " - "translation files, to be able to use " - "all system's ones again", - default=False, options={'SKIP_SAVE'}) + # Operator Arguments + lang: StringProperty( + description="Current (translated) language", + options={'SKIP_SAVE'}, + ) + + po_file: StringProperty( + description="Path to the matching po file", + subtype='FILE_PATH', + options={'SKIP_SAVE'}, + ) + + clean_mo: BoolProperty( + description="Remove all local translation files, to be able to use the system ones again", + default=False, + options={'SKIP_SAVE'} + ) + # /End Operator Arguments def execute(self, context): if self.clean_mo: @@ -85,65 +88,169 @@ class UI_OT_i18n_edittranslation_update_mo(bpy.types.Operator): return {'FINISHED'} -class UI_OT_i18n_edittranslation(bpy.types.Operator): - """Translate the label and tooltip of the property defined by given 'parameters'""" +class UI_OT_i18n_edittranslation(Operator): + """Translate the label and tooltip of the given property""" bl_idname = "ui.edittranslation" bl_label = "Edit Translation" - # "Parameters" - but_label = StringProperty(description="Label of the control", options={'SKIP_SAVE'}) - rna_label = StringProperty(description="RNA-defined label of the control, if any", options={'SKIP_SAVE'}) - enum_label = StringProperty(description="Label of the enum item of the control, if any", options={'SKIP_SAVE'}) - but_tip = StringProperty(description="Tip of the control", options={'SKIP_SAVE'}) - rna_tip = StringProperty(description="RNA-defined tip of the control, if any", options={'SKIP_SAVE'}) - enum_tip = StringProperty(description="Tip of the enum item of the control, if any", options={'SKIP_SAVE'}) - rna_struct = StringProperty(description="Identifier of the RNA struct, if any", options={'SKIP_SAVE'}) - rna_prop = StringProperty(description="Identifier of the RNA property, if any", options={'SKIP_SAVE'}) - rna_enum = StringProperty(description="Identifier of the RNA enum item, if any", options={'SKIP_SAVE'}) - rna_ctxt = StringProperty(description="RNA context for label", options={'SKIP_SAVE'}) - - lang = StringProperty(description="Current (translated) language", options={'SKIP_SAVE'}) - po_file = StringProperty(description="Path to the matching po file", subtype='FILE_PATH', options={'SKIP_SAVE'}) + # Operator Arguments + but_label: StringProperty( + description="Label of the control", + options={'SKIP_SAVE'}, + ) + + rna_label: StringProperty( + description="RNA-defined label of the control, if any", + options={'SKIP_SAVE'}, + ) + + enum_label: StringProperty( + description="Label of the enum item of the control, if any", + options={'SKIP_SAVE'}, + ) + + but_tip: StringProperty( + description="Tip of the control", + options={'SKIP_SAVE'}, + ) + + rna_tip: StringProperty( + description="RNA-defined tip of the control, if any", + options={'SKIP_SAVE'}, + ) + + enum_tip: StringProperty( + description="Tip of the enum item of the control, if any", + options={'SKIP_SAVE'}, + ) + + rna_struct: StringProperty( + description="Identifier of the RNA struct, if any", + options={'SKIP_SAVE'}, + ) + + rna_prop: StringProperty( + description="Identifier of the RNA property, if any", + options={'SKIP_SAVE'}, + ) + + rna_enum: StringProperty( + description="Identifier of the RNA enum item, if any", + options={'SKIP_SAVE'}, + ) + + rna_ctxt: StringProperty( + description="RNA context for label", + options={'SKIP_SAVE'}, + ) + + lang: StringProperty( + description="Current (translated) language", + options={'SKIP_SAVE'}, + ) + + po_file: StringProperty( + description="Path to the matching po file", + subtype='FILE_PATH', + options={'SKIP_SAVE'}, + ) # Found in po file. - org_but_label = StringProperty(description="Original label of the control", options={'SKIP_SAVE'}) - org_rna_label = StringProperty(description="Original RNA-defined label of the control, if any", - options={'SKIP_SAVE'}) - org_enum_label = StringProperty(description="Original label of the enum item of the control, if any", - options={'SKIP_SAVE'}) - org_but_tip = StringProperty(description="Original tip of the control", options={'SKIP_SAVE'}) - org_rna_tip = StringProperty(description="Original RNA-defined tip of the control, if any", options={'SKIP_SAVE'}) - org_enum_tip = StringProperty(description="Original tip of the enum item of the control, if any", - options={'SKIP_SAVE'}) - - flag_items = (('FUZZY', "Fuzzy", "Message is marked as fuzzy in po file"), - ('ERROR', "Error", "Some error occurred with this message"), - ) - but_label_flags = EnumProperty(items=flag_items, description="Flags about the label of the button", - options={'SKIP_SAVE', 'ENUM_FLAG'}) - rna_label_flags = EnumProperty(items=flag_items, description="Flags about the RNA-defined label of the button", - options={'SKIP_SAVE', 'ENUM_FLAG'}) - enum_label_flags = EnumProperty(items=flag_items, description="Flags about the RNA enum item label of the button", - options={'SKIP_SAVE', 'ENUM_FLAG'}) - but_tip_flags = EnumProperty(items=flag_items, description="Flags about the tip of the button", - options={'SKIP_SAVE', 'ENUM_FLAG'}) - rna_tip_flags = EnumProperty(items=flag_items, description="Flags about the RNA-defined tip of the button", - options={'SKIP_SAVE', 'ENUM_FLAG'}) - enum_tip_flags = EnumProperty(items=flag_items, description="Flags about the RNA enum item tip of the button", - options={'SKIP_SAVE', 'ENUM_FLAG'}) - - stats_str = StringProperty(description="Stats from opened po", options={'SKIP_SAVE'}) - update_po = BoolProperty(description="Update po file, try to rebuild mo file, and refresh Blender UI", - default=False, options={'SKIP_SAVE'}) - update_mo = BoolProperty(description="Try to rebuild mo file, and refresh Blender UI", - default=False, options={'SKIP_SAVE'}) - clean_mo = BoolProperty(description="Clean up (remove) all local translation files, to be able to use " - "all system's ones again", - default=False, options={'SKIP_SAVE'}) + org_but_label: StringProperty( + description="Original label of the control", + options={'SKIP_SAVE'}, + ) + + org_rna_label: StringProperty( + description="Original RNA-defined label of the control, if any", + options={'SKIP_SAVE'}, + ) + + org_enum_label: StringProperty( + description="Original label of the enum item of the control, if any", + options={'SKIP_SAVE'}, + ) + + org_but_tip: StringProperty( + description="Original tip of the control", + options={'SKIP_SAVE'}, + ) + + org_rna_tip: StringProperty( + description="Original RNA-defined tip of the control, if any", options={'SKIP_SAVE'} + ) + + org_enum_tip: StringProperty( + description="Original tip of the enum item of the control, if any", + options={'SKIP_SAVE'}, + ) + + flag_items = ( + ('FUZZY', "Fuzzy", "Message is marked as fuzzy in po file"), + ('ERROR', "Error", "Some error occurred with this message"), + ) + + but_label_flags: EnumProperty( + description="Flags about the label of the button", + items=flag_items, + options={'SKIP_SAVE', 'ENUM_FLAG'}, + ) + + rna_label_flags: EnumProperty( + description="Flags about the RNA-defined label of the button", + items=flag_items, + options={'SKIP_SAVE', 'ENUM_FLAG'}, + ) + + enum_label_flags: EnumProperty( + description="Flags about the RNA enum item label of the button", + items=flag_items, + options={'SKIP_SAVE', 'ENUM_FLAG'}, + ) + + but_tip_flags: EnumProperty( + description="Flags about the tip of the button", + items=flag_items, + options={'SKIP_SAVE', 'ENUM_FLAG'}, + ) + + rna_tip_flags: EnumProperty( + description="Flags about the RNA-defined tip of the button", + items=flag_items, + options={'SKIP_SAVE', 'ENUM_FLAG'}, + ) + + enum_tip_flags: EnumProperty( + description="Flags about the RNA enum item tip of the button", + items=flag_items, + options={'SKIP_SAVE', 'ENUM_FLAG'}, + ) + + stats_str: StringProperty( + description="Stats from opened po", options={'SKIP_SAVE'}) + + update_po: BoolProperty( + description="Update po file, try to rebuild mo file, and refresh Blender's UI", + default=False, + options={'SKIP_SAVE'}, + ) + + update_mo: BoolProperty( + description="Try to rebuild mo file, and refresh Blender's UI", + default=False, + options={'SKIP_SAVE'}, + ) + + clean_mo: BoolProperty( + description="Remove all local translation files, to be able to use the system ones again", + default=False, + options={'SKIP_SAVE'}, + ) + # /End Operator Arguments def execute(self, context): if not hasattr(self, "msgmap"): - self.report('ERROR', "Looks like you did not invoke this operator first!") + self.report('ERROR', "invoke() needs to be called before execute()") return {'CANCELLED'} msgs = _get_messages(self.lang, self.po_file) @@ -152,7 +259,6 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator): if 'ERROR' in getattr(self, mmap["msg_flags"]): continue k = mmap["key"] -# print(k) if k not in done_keys and len(k) == 1: k = tuple(k)[0] msgs.msgs[k].msgstr = getattr(self, mmap["msgstr"]) @@ -160,7 +266,7 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator): done_keys.add(k) if self.update_po: - # Try to overwrite po file, may fail if we have no good rights... + # Try to overwrite .po file, may fail if there are no permissions. try: msgs.write(kind='PO', dest=self.po_file) except Exception as e: @@ -175,19 +281,20 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator): return {'FINISHED'} def invoke(self, context, event): - self.msgmap = {"but_label": {"msgstr": "but_label", "msgid": "org_but_label", - "msg_flags": "but_label_flags", "key": set()}, - "rna_label": {"msgstr": "rna_label", "msgid": "org_rna_label", - "msg_flags": "rna_label_flags", "key": set()}, - "enum_label": {"msgstr": "enum_label", "msgid": "org_enum_label", - "msg_flags": "enum_label_flags", "key": set()}, - "but_tip": {"msgstr": "but_tip", "msgid": "org_but_tip", - "msg_flags": "but_tip_flags", "key": set()}, - "rna_tip": {"msgstr": "rna_tip", "msgid": "org_rna_tip", - "msg_flags": "rna_tip_flags", "key": set()}, - "enum_tip": {"msgstr": "enum_tip", "msgid": "org_enum_tip", - "msg_flags": "enum_tip_flags", "key": set()}, - } + self.msgmap = { + "but_label": { + "msgstr": "but_label", "msgid": "org_but_label", "msg_flags": "but_label_flags", "key": set()}, + "rna_label": { + "msgstr": "rna_label", "msgid": "org_rna_label", "msg_flags": "rna_label_flags", "key": set()}, + "enum_label": { + "msgstr": "enum_label", "msgid": "org_enum_label", "msg_flags": "enum_label_flags", "key": set()}, + "but_tip": { + "msgstr": "but_tip", "msgid": "org_but_tip", "msg_flags": "but_tip_flags", "key": set()}, + "rna_tip": { + "msgstr": "rna_tip", "msgid": "org_rna_tip", "msg_flags": "rna_tip_flags", "key": set()}, + "enum_tip": { + "msgstr": "enum_tip", "msgid": "org_enum_tip", "msg_flags": "enum_tip_flags", "key": set()}, + } msgs = _get_messages(self.lang, self.po_file) msgs.find_best_messages_matches(self, self.msgmap, self.rna_ctxt, self.rna_struct, self.rna_prop, self.rna_enum) @@ -229,9 +336,8 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator): if self.org_but_label or self.org_rna_label or self.org_enum_label: # XXX Can't use box, labels are not enough readable in them :/ box = layout.box() - #box = layout box.label(text="Labels:") - split = box.split(percentage=0.15) + split = box.split(factor=0.15) col1 = split.column() col2 = split.column() if self.org_but_label: @@ -268,9 +374,8 @@ class UI_OT_i18n_edittranslation(bpy.types.Operator): if self.org_but_tip or self.org_rna_tip or self.org_enum_tip: # XXX Can't use box, labels are not enough readable in them :/ box = layout.box() - #box = layout box.label(text="Tool Tips:") - split = box.split(percentage=0.15) + split = box.split(factor=0.15) col1 = split.column() col2 = split.column() if self.org_but_tip: diff --git a/ui_translate/settings.py b/ui_translate/settings.py index 20f7c82e..97bddbc3 100644 --- a/ui_translate/settings.py +++ b/ui_translate/settings.py @@ -18,40 +18,46 @@ # <pep8 compliant> +import os + if "bpy" in locals(): import importlib importlib.reload(settings_i18n) else: import bpy + from bpy.types import ( + Operator, + AddonPreferences, + ) from bpy.props import ( - BoolProperty, - CollectionProperty, - EnumProperty, - FloatProperty, - FloatVectorProperty, - IntProperty, - PointerProperty, - StringProperty, - ) + BoolProperty, + StringProperty, + ) from bl_i18n_utils import settings as settings_i18n -import os - - settings = settings_i18n.I18nSettings() -class UI_OT_i18n_settings_load(bpy.types.Operator): +# Operators ################################################################### + +class UI_OT_i18n_settings_load(Operator): """Load translations' settings from a persistent JSon file""" bl_idname = "ui.i18n_settings_load" bl_label = "I18n Load Settings" bl_option = {'REGISTER'} - # "Parameters" - filepath = StringProperty(description="Path to the saved settings file", - subtype='FILE_PATH') - filter_glob = StringProperty(default="*.json", options={'HIDDEN'}) + # Operator Arguments + filepath: StringProperty( + subtype='FILE_PATH', + description="Path to the saved settings file", + ) + + filter_glob: StringProperty( + default="*.json", + options={'HIDDEN'} + ) + # /End Operator Arguments def invoke(self, context, event): if not self.properties.is_property_set("filepath"): @@ -67,16 +73,23 @@ class UI_OT_i18n_settings_load(bpy.types.Operator): return {'FINISHED'} -class UI_OT_i18n_settings_save(bpy.types.Operator): +class UI_OT_i18n_settings_save(Operator): """Save translations' settings in a persistent JSon file""" bl_idname = "ui.i18n_settings_save" bl_label = "I18n Save Settings" bl_option = {'REGISTER'} - # "Parameters" - filepath = StringProperty(description="Path to the saved settings file", - subtype='FILE_PATH') - filter_glob = StringProperty(default="*.json", options={'HIDDEN'}) + # Operator Arguments + filepath: StringProperty( + description="Path to the saved settings file", + subtype='FILE_PATH', + ) + + filter_glob: StringProperty( + default="*.json", + options={'HIDDEN'}, + ) + # /End Operator Arguments def invoke(self, context, event): if not self.properties.is_property_set("filepath"): @@ -92,18 +105,20 @@ class UI_OT_i18n_settings_save(bpy.types.Operator): return {'FINISHED'} +# Addon Preferences ########################################################### + def _setattr(self, name, val): print(self, name, val) setattr(self, name, val) -class UI_AP_i18n_settings(bpy.types.AddonPreferences): +class UI_AP_i18n_settings(AddonPreferences): bl_idname = __name__.split(".")[0] # We want "top" module name! bl_option = {'REGISTER'} _settings = settings - WARN_MSGID_NOT_CAPITALIZED = BoolProperty( + WARN_MSGID_NOT_CAPITALIZED: BoolProperty( name="Warn Msgid Not Capitalized", description="Warn about messages not starting by a capitalized letter (with a few allowed exceptions!)", default=True, @@ -111,7 +126,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences): set=lambda self, val: _setattr(self._settings, "WARN_MSGID_NOT_CAPITALIZED", val), ) - GETTEXT_MSGFMT_EXECUTABLE = StringProperty( + GETTEXT_MSGFMT_EXECUTABLE: StringProperty( name="Gettext 'msgfmt' executable", description="The gettext msgfmt 'compiler'. You’ll likely have to edit it if you’re under Windows", subtype='FILE_PATH', @@ -120,7 +135,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences): set=lambda self, val: setattr(self._settings, "GETTEXT_MSGFMT_EXECUTABLE", val), ) - FRIBIDI_LIB = StringProperty( + FRIBIDI_LIB: StringProperty( name="Fribidi Library", description="The FriBidi C compiled library (.so under Linux, .dll under windows...), you’ll likely have " "to edit it if you’re under Windows, e.g. using the one included in svn's libraries repository", @@ -130,7 +145,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences): set=lambda self, val: setattr(self._settings, "FRIBIDI_LIB", val), ) - SOURCE_DIR = StringProperty( + SOURCE_DIR: StringProperty( name="Source Root", description="The Blender source root path", subtype='FILE_PATH', @@ -139,7 +154,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences): set=lambda self, val: setattr(self._settings, "SOURCE_DIR", val), ) - I18N_DIR = StringProperty( + I18N_DIR: StringProperty( name="Translation Root", description="The bf-translation repository", subtype='FILE_PATH', @@ -148,7 +163,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences): set=lambda self, val: setattr(self._settings, "I18N_DIR", val), ) - SPELL_CACHE = StringProperty( + SPELL_CACHE: StringProperty( name="Spell Cache", description="A cache storing validated msgids, to avoid re-spellchecking them", subtype='FILE_PATH', @@ -157,7 +172,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences): set=lambda self, val: setattr(self._settings, "SPELL_CACHE", val), ) - PY_SYS_PATHS = StringProperty( + PY_SYS_PATHS: StringProperty( name="Import Paths", description="Additional paths to add to sys.path (';' separated)", default="", @@ -165,7 +180,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences): set=lambda self, val: setattr(self._settings, "PY_SYS_PATHS", val), ) - persistent_data_path = StringProperty( + persistent_data_path: StringProperty( name="Persistent Data Path", description="The name of a json file storing those settings (unfortunately, Blender's system " "does not work here)", @@ -187,7 +202,7 @@ class UI_AP_i18n_settings(bpy.types.AddonPreferences): layout.prop(self, "PY_SYS_PATHS") layout.separator() - split = layout.split(0.75) + split = layout.split(factor=0.75) col = split.column() col.prop(self, "persistent_data_path") row = col.row() diff --git a/ui_translate/update_addon.py b/ui_translate/update_addon.py index bac3ac45..38b0ac8a 100644 --- a/ui_translate/update_addon.py +++ b/ui_translate/update_addon.py @@ -25,16 +25,12 @@ if "bpy" in locals(): importlib.reload(bl_extract_messages) else: import bpy + from bpy.types import Operator from bpy.props import ( - BoolProperty, - CollectionProperty, - EnumProperty, - FloatProperty, - FloatVectorProperty, - IntProperty, - PointerProperty, - StringProperty, - ) + BoolProperty, + EnumProperty, + StringProperty, + ) from . import settings from bl_i18n_utils import utils as utils_i18n from bl_i18n_utils import bl_extract_messages @@ -49,7 +45,8 @@ import subprocess import tempfile -##### Helpers ##### +# Helpers ################################################################### + def validate_module(op, context): module_name = op.module_name addon = getattr(context, "active_addon", None) @@ -91,16 +88,27 @@ def enum_addons(self, context): return _cached_enum_addons -##### Operators ##### +# Operators ################################################################### + # This one is a helper one, as we sometimes need another invoke function (like e.g. file selection)... -class UI_OT_i18n_addon_translation_invoke(bpy.types.Operator): +class UI_OT_i18n_addon_translation_invoke(Operator): """Wrapper operator which will invoke given op after setting its module_name""" bl_idname = "ui.i18n_addon_translation_invoke" bl_label = "Update I18n Add-on" bl_property = "module_name" - module_name = EnumProperty(items=enum_addons, name="Add-on", description="Add-on to process", options=set()) - op_id = StringProperty(name="Operator Name", description="Name (id) of the operator to invoke") + # Operator Arguments + module_name: EnumProperty( + name="Add-on", + description="Add-on to process", + items=enum_addons, + options=set(), + ) + op_id: StringProperty( + name="Operator Name", + description="Name (id) of the operator to invoke", + ) + # /End Operator Arguments def invoke(self, context, event): global _cached_enum_addons @@ -116,17 +124,24 @@ class UI_OT_i18n_addon_translation_invoke(bpy.types.Operator): op = bpy.ops for item in self.op_id.split('.'): op = getattr(op, item, None) - #print(self.op_id, item, op) if op is None: return {'CANCELLED'} return op('INVOKE_DEFAULT', module_name=self.module_name) -class UI_OT_i18n_addon_translation_update(bpy.types.Operator): + +class UI_OT_i18n_addon_translation_update(Operator): """Update given add-on's translation data (found as a py tuple in the add-on's source code)""" bl_idname = "ui.i18n_addon_translation_update" bl_label = "Update I18n Add-on" - module_name = EnumProperty(items=enum_addons, name="Add-on", description="Add-on to process", options=set()) + # Operator Arguments + module_name: EnumProperty( + name="Add-on", + description="Add-on to process", + items=enum_addons, + options=set() + ) + # /End Operator Arguments def execute(self, context): global _cached_enum_addons @@ -174,13 +189,23 @@ class UI_OT_i18n_addon_translation_update(bpy.types.Operator): return {'FINISHED'} -class UI_OT_i18n_addon_translation_import(bpy.types.Operator): +class UI_OT_i18n_addon_translation_import(Operator): """Import given add-on's translation data from PO files""" bl_idname = "ui.i18n_addon_translation_import" bl_label = "I18n Add-on Import" - module_name = EnumProperty(items=enum_addons, name="Add-on", description="Add-on to process", options=set()) - directory = StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'}) + # Operator Arguments + module_name: EnumProperty( + name="Add-on", + description="Add-on to process", options=set(), + items=enum_addons, + ) + + directory: StringProperty( + subtype='FILE_PATH', maxlen=1024, + options={'HIDDEN', 'SKIP_SAVE'} + ) + # /End Operator Arguments def _dst(self, trans, path, uid, kind): if kind == 'PO': @@ -253,16 +278,37 @@ class UI_OT_i18n_addon_translation_import(bpy.types.Operator): return {'FINISHED'} -class UI_OT_i18n_addon_translation_export(bpy.types.Operator): +class UI_OT_i18n_addon_translation_export(Operator): """Export given add-on's translation data as PO files""" + bl_idname = "ui.i18n_addon_translation_export" bl_label = "I18n Add-on Export" - module_name = EnumProperty(items=enum_addons, name="Add-on", description="Add-on to process", options=set()) - use_export_pot = BoolProperty(name="Export POT", default=True, description="Export (generate) a POT file too") - use_update_existing = BoolProperty(name="Update Existing", default=True, - description="Update existing po files, if any, instead of overwriting them") - directory = StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'}) + # Operator Arguments + module_name: EnumProperty( + name="Add-on", + description="Add-on to process", + items=enum_addons, + options=set() + ) + + use_export_pot: BoolProperty( + name="Export POT", + description="Export (generate) a POT file too", + default=True, + ) + + use_update_existing: BoolProperty( + name="Update Existing", + description="Update existing po files, if any, instead of overwriting them", + default=True, + ) + + directory: StringProperty( + subtype='FILE_PATH', maxlen=1024, + options={'HIDDEN', 'SKIP_SAVE'} + ) + # /End Operator Arguments def _dst(self, trans, path, uid, kind): if kind == 'PO': diff --git a/ui_translate/update_svn.py b/ui_translate/update_svn.py index 81435046..28c1f46a 100644 --- a/ui_translate/update_svn.py +++ b/ui_translate/update_svn.py @@ -25,16 +25,11 @@ if "bpy" in locals(): importlib.reload(utils_languages_menu) else: import bpy + from bpy.types import Operator from bpy.props import ( - BoolProperty, - CollectionProperty, - EnumProperty, - FloatProperty, - FloatVectorProperty, - IntProperty, - PointerProperty, - StringProperty, - ) + BoolProperty, + EnumProperty, + ) from . import settings from bl_i18n_utils import utils as utils_i18n from bl_i18n_utils import utils_languages_menu @@ -46,13 +41,20 @@ import subprocess import tempfile -##### Operators ##### -class UI_OT_i18n_updatetranslation_svn_branches(bpy.types.Operator): +# Operators ################################################################### + +class UI_OT_i18n_updatetranslation_svn_branches(Operator): """Update i18n svn's branches (po files)""" bl_idname = "ui.i18n_updatetranslation_svn_branches" bl_label = "Update I18n Branches" - use_skip_pot_gen = BoolProperty(name="Skip POT", default=False, description="Skip POT file generation") + # Operator Arguments + use_skip_pot_gen: BoolProperty( + name="Skip POT", + description="Skip POT file generation", + default=False, + ) + # /End Operator Arguments def execute(self, context): if not hasattr(self, "settings"): @@ -108,7 +110,7 @@ class UI_OT_i18n_updatetranslation_svn_branches(bpy.types.Operator): return wm.invoke_props_dialog(self) -class UI_OT_i18n_updatetranslation_svn_trunk(bpy.types.Operator): +class UI_OT_i18n_updatetranslation_svn_trunk(Operator): """Update i18n svn's branches (po files)""" bl_idname = "ui.i18n_updatetranslation_svn_trunk" bl_label = "Update I18n Trunk" @@ -166,14 +168,27 @@ class UI_OT_i18n_updatetranslation_svn_trunk(bpy.types.Operator): return {'FINISHED'} -class UI_OT_i18n_updatetranslation_svn_statistics(bpy.types.Operator): - """Create or extend a 'i18n_info.txt' Text datablock containing statistics and checks about """ - """current branches and/or trunk""" +class UI_OT_i18n_updatetranslation_svn_statistics(Operator): + """Create or extend a 'i18n_info.txt' Text datablock + + It will contain statistics and checks about current branches and/or trunk. + """ bl_idname = "ui.i18n_updatetranslation_svn_statistics" bl_label = "Update I18n Statistics" - use_branches = BoolProperty(name="Check Branches", default=True, description="Check po files in branches") - use_trunk = BoolProperty(name="Check Trunk", default=False, description="Check po files in trunk") + # Operator Arguments + use_branches: BoolProperty( + name="Check Branches", + description="Check po files in branches", + default=True, + ) + + use_trunk: BoolProperty( + name="Check Trunk", + description="Check po files in trunk", + default=False, + ) + # /End Operator Arguments report_name = "i18n_info.txt" @@ -220,7 +235,6 @@ class UI_OT_i18n_updatetranslation_svn_statistics(bpy.types.Operator): return {'FINISHED'} - def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) diff --git a/ui_translate/update_ui.py b/ui_translate/update_ui.py index 91cee4ce..8fb79bef 100644 --- a/ui_translate/update_ui.py +++ b/ui_translate/update_ui.py @@ -18,74 +18,137 @@ # <pep8 compliant> +import os + if "bpy" in locals(): import importlib importlib.reload(settings) importlib.reload(utils_i18n) else: import bpy + from bpy.types import ( + Operator, + Panel, + PropertyGroup, + UIList, + ) from bpy.props import ( - BoolProperty, - CollectionProperty, - EnumProperty, - FloatProperty, - FloatVectorProperty, - IntProperty, - PointerProperty, - StringProperty, - ) + BoolProperty, + IntProperty, + StringProperty, + CollectionProperty, + ) from . import settings from bl_i18n_utils import utils as utils_i18n from bpy.app.translations import pgettext_iface as iface_ -import os +# Data ######################################################################## -##### Data ##### -class I18nUpdateTranslationLanguage(bpy.types.PropertyGroup): - """Settings/info about a language""" - uid = StringProperty(name="Language ID", default="", description="ISO code, like fr_FR") - num_id = IntProperty(name="Numeric ID", default=0, min=0, description="Numeric ID (readonly!)") - name = StringProperty(name="Language Name", default="", - description="English language name/label (like \"French (Français)\")") - use = BoolProperty(name="Use", default=True, description="Use this language in current operator") - po_path = StringProperty(name="PO File Path", default="", subtype='FILE_PATH', - description="Path to the relevant po file in branches") - po_path_trunk = StringProperty(name="PO Trunk File Path", default="", subtype='FILE_PATH', - description="Path to the relevant po file in trunk") - mo_path_trunk = StringProperty(name="MO File Path", default="", subtype='FILE_PATH', - description="Path to the relevant mo file") - po_path_git = StringProperty(name="PO Git Master File Path", default="", subtype='FILE_PATH', - description="Path to the relevant po file in Blender's translations git repository") - - -class I18nUpdateTranslationSettings(bpy.types.PropertyGroup): +class I18nUpdateTranslationLanguage(PropertyGroup): + """Settings/info about a language.""" + + uid: StringProperty( + name="Language ID", + description="ISO code (eg. \"fr_FR\")", + default="", + ) + + num_id: IntProperty( + name="Numeric ID", + description="Numeric ID (read only!)", + default=0, min=0, + ) + + name: StringProperty( + name="Language Name", + description="Language label (eg. \"French (Français)\")", + default="", + ) + + use: BoolProperty( + name="Use", + description="If this language should be used in the current operator", + default=True, + ) + + po_path: StringProperty( + name="PO File Path", + description="Path to the relevant po file in branches", + subtype='FILE_PATH', + default="", + ) + + po_path_trunk: StringProperty( + name="PO Trunk File Path", + description="Path to the relevant po file in trunk", + subtype='FILE_PATH', + default="", + ) + + mo_path_trunk: StringProperty( + name="MO File Path", + description="Path to the relevant mo file", + subtype='FILE_PATH', + default="", + ) + + po_path_git: StringProperty( + name="PO Git Master File Path", + description="Path to the relevant po file in Blender's translations git repository", + subtype='FILE_PATH', + default="", + ) + + +class I18nUpdateTranslationSettings(PropertyGroup): """Settings/info about a language""" - langs = CollectionProperty(name="Languages", type=I18nUpdateTranslationLanguage, - description="Languages to update in branches") - active_lang = IntProperty(name="Active Language", default=0, - description="Index of active language in langs collection") - pot_path = StringProperty(name="POT File Path", default="", subtype='FILE_PATH', - description="Path to the pot template file") - is_init = BoolProperty(default=False, options={'HIDDEN'}, - description="Whether these settings have already been auto-set or not") - - -##### UI ##### -class UI_UL_i18n_languages(bpy.types.UIList): + + langs: CollectionProperty( + name="Languages", + type=I18nUpdateTranslationLanguage, + description="Languages to update in branches", + ) + + active_lang: IntProperty( + name="Active Language", + default=0, + description="Index of active language in langs collection", + ) + + pot_path: StringProperty( + name="POT File Path", + description="Path to the pot template file", + subtype='FILE_PATH', + default="", + ) + + is_init: BoolProperty( + description="Whether these settings have already been auto-set or not", + default=False, + options={'HIDDEN'}, + ) + + +# UI ########################################################################## + +class UI_UL_i18n_languages(UIList): + """ """ + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - #assert(isinstance(item, bpy.types.I18nUpdateTranslationLanguage)) if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.label(item.name, icon_value=icon) + layout.label(text=item.name, icon_value=icon) layout.prop(item, "use", text="") elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - layout.label(item.uid) + layout.label(text=item.uid) layout.prop(item, "use", text="") -class UI_PT_i18n_update_translations_settings(bpy.types.Panel): +class UI_PT_i18n_update_translations_settings(Panel): + """ """ + bl_label = "I18n Update Translation" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" @@ -102,7 +165,7 @@ class UI_PT_i18n_update_translations_settings(bpy.types.Panel): layout.label(text="Could not init languages data!") layout.label(text="Please edit the preferences of the UI Translate add-on") else: - split = layout.split(0.75) + split = layout.split(factor=0.75) split.template_list("UI_UL_i18n_languages", "", i18n_sett, "langs", i18n_sett, "active_lang", rows=8) col = split.column() col.operator("ui.i18n_updatetranslation_svn_init_settings", text="Reset Settings") @@ -132,7 +195,7 @@ class UI_PT_i18n_update_translations_settings(bpy.types.Panel): layout.prop(i18n_sett, "pot_path") layout.separator() - layout.label("Add-ons:") + layout.label(text="Add-ons:") row = layout.row() op = row.operator("ui.i18n_addon_translation_invoke", text="Refresh I18n Data...") op.op_id = "ui.i18n_addon_translation_update" @@ -142,9 +205,11 @@ class UI_PT_i18n_update_translations_settings(bpy.types.Panel): op.op_id = "ui.i18n_addon_translation_import" -##### Operators ##### -class UI_OT_i18n_updatetranslation_svn_init_settings(bpy.types.Operator): +# Operators ################################################################### + +class UI_OT_i18n_updatetranslation_svn_init_settings(Operator): """Init settings for i18n svn's update operators""" + bl_idname = "ui.i18n_updatetranslation_svn_init_settings" bl_label = "Init I18n Update Settings" bl_option = {'REGISTER'} @@ -200,14 +265,25 @@ class UI_OT_i18n_updatetranslation_svn_init_settings(bpy.types.Operator): return {'FINISHED'} -class UI_OT_i18n_updatetranslation_svn_settings_select(bpy.types.Operator): +class UI_OT_i18n_updatetranslation_svn_settings_select(Operator): """(De)select (or invert selection of) all languages for i18n svn's update operators""" + bl_idname = "ui.i18n_updatetranslation_svn_settings_select" bl_label = "Init I18n Update Select Languages" - use_select = BoolProperty(name="Select All", default=True, description="Select all if True, else deselect all") - use_invert = BoolProperty(name="Invert Selection", default=False, - description="Inverse selection (overrides 'Select All' when True)") + # Operator Arguments + use_select: BoolProperty( + name="Select All", + description="Select all if True, else deselect all", + default=True, + ) + + use_invert: BoolProperty( + name="Invert Selection", + description="Inverse selection (overrides 'Select All' when True)", + default=False, + ) + # /End Operator Arguments @classmethod def poll(cls, context): diff --git a/uv_texture_atlas.py b/uv_texture_atlas.py index b655c83c..b3bc1ec6 100644 --- a/uv_texture_atlas.py +++ b/uv_texture_atlas.py @@ -51,7 +51,7 @@ def check_all_objects_visible(self, context): group = scene.ms_lightmap_groups[scene.ms_lightmap_groups_index] isAllObjectsVisible = True bpy.ops.object.select_all(action='DESELECT') - for thisObject in bpy.data.groups[group.name].objects: + for thisObject in bpy.data.collections[group.name].objects: isThisObjectVisible = False # scene.objects.active = thisObject for thisLayerNumb in range(20): @@ -68,7 +68,7 @@ def check_group_exist(self, context, use_report=True): scene = context.scene group = scene.ms_lightmap_groups[scene.ms_lightmap_groups_index] - if group.name in bpy.data.groups: + if group.name in bpy.data.collections: return True else: if use_report: @@ -95,8 +95,8 @@ class TexAtl_Main(Panel): row.template_list("UI_UL_list", "template_list_controls", scene, "ms_lightmap_groups", scene, "ms_lightmap_groups_index", rows=2, maxrows=5) col = row.column(align=True) - col.operator("scene.ms_add_lightmap_group", icon='ZOOMIN', text="") - col.operator("scene.ms_del_lightmap_group", icon='ZOOMOUT', text="") + col.operator("scene.ms_add_lightmap_group", icon='ADD', text="") + col.operator("scene.ms_del_lightmap_group", icon='REMOVE', text="") row = self.layout.row(align=True) @@ -127,15 +127,15 @@ class TexAtl_Main(Panel): self.layout.prop(group, 'unwrap_type', text='Lightmap', expand=True) row = self.layout.row() row.operator( - "object.ms_auto", text="Auto Unwrap", icon="LAMP_SPOT") + "object.ms_auto", text="Auto Unwrap", icon="LIGHT_SPOT") row.prop(group, 'autoUnwrapPrecision', text='') self.layout.label(text="Manual Unwrap:") row = self.layout.row() row.operator( - "object.ms_run", text="StartManualUnwrap", icon="LAMP_SPOT") + "object.ms_run", text="StartManualUnwrap", icon="LIGHT_SPOT") row.operator( - "object.ms_run_remove", text="FinishManualUnwrap", icon="LAMP_SPOT") + "object.ms_run_remove", text="FinishManualUnwrap", icon="LIGHT_SPOT") class TexAtl_RunAuto(Operator): @@ -162,7 +162,7 @@ class TexAtl_RunAuto(Operator): if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT', toggle=False) - if group.bake is True and bpy.data.groups[group.name].objects: + if group.bake is True and bpy.data.collections[group.name].objects: # Check if objects are all on the visible Layers. isAllObjVisible = check_all_objects_visible(self, context) @@ -209,7 +209,7 @@ class TexAtl_RunStart(Operator): if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT', toggle=False) - if group.bake is True and bpy.data.groups[group.name].objects: + if group.bake is True and bpy.data.collections[group.name].objects: # Check if objects are all on the visible Layers. isAllObjVisible = check_all_objects_visible(self, context) @@ -257,7 +257,7 @@ class TexAtl_RunFinish(Operator): if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT', toggle=False) - if group.bake is True and bpy.data.groups[group.name].objects: + if group.bake is True and bpy.data.collections[group.name].objects: # Check if objects are all on the visible Layers. isAllObjVisible = check_all_objects_visible(self, context) @@ -354,9 +354,9 @@ class TexAtl_AddSelectedToGroup(Operator): scene.ms_lightmap_groups_index].name # Create a New Group if it was deleted. - obj_group = bpy.data.groups.get(group_name) + obj_group = bpy.data.collections.get(group_name) if obj_group is None: - obj_group = bpy.data.groups.new(group_name) + obj_group = bpy.data.collections.new(group_name) # Add objects to a group if bpy.ops.object.mode_set.poll(): @@ -387,7 +387,7 @@ class TexAtl_SelectGroup(Operator): bpy.ops.object.mode_set(mode='OBJECT', toggle=False) bpy.ops.object.select_all(action='DESELECT') - obj_group = bpy.data.groups[group_name] + obj_group = bpy.data.collections[group_name] for object in obj_group.objects: object.select = True return {'FINISHED'} @@ -415,7 +415,7 @@ class TexAtl_RemoveFromGroup(Operator): for group in scene.ms_lightmap_groups: group_name = group.name - obj_group = bpy.data.groups[group_name] + obj_group = bpy.data.collections[group_name] for object in context.selected_objects: scene.objects.active = object @@ -451,7 +451,7 @@ class TexAtl_RemoveOtherUVs(Operator): bpy.ops.object.mode_set(mode='OBJECT', toggle=False) # bpy.ops.object.select_all(action='DESELECT') - obj_group = bpy.data.groups[group_name] + obj_group = bpy.data.collections[group_name] # Remove other UVs of selected objects for object in context.selected_objects: @@ -482,7 +482,7 @@ class TexAtl_AddLightmapGroup(Operator): def execute(self, context): scene = context.scene - obj_group = bpy.data.groups.new(self.name) + obj_group = bpy.data.collections.new(self.name) item = scene.ms_lightmap_groups.add() item.name = obj_group.name @@ -513,7 +513,7 @@ class TexAtl_DelLightmapGroup(Operator): group_name = scene.ms_lightmap_groups[idx].name # Remove Group - group = bpy.data.groups.get(group_name) + group = bpy.data.collections.get(group_name) if group is not None: # Unhide Objects if they are hidden @@ -521,7 +521,7 @@ class TexAtl_DelLightmapGroup(Operator): obj.hide_render = False obj.hide = False - bpy.data.groups.remove(group, do_unlink=True) + bpy.data.collections.remove(group, do_unlink=True) # Remove Lightmap Group scene.ms_lightmap_groups.remove(scene.ms_lightmap_groups_index) @@ -553,7 +553,7 @@ class TexAtl_CreateLightmap(Operator): image.generated_type = 'COLOR_GRID' image.generated_width = self.resolutionX image.generated_height = self.resolutionY - obj_group = bpy.data.groups[self.group_name] + obj_group = bpy.data.collections[self.group_name] # non MESH objects for removal list NON_MESH_LIST = [] @@ -623,7 +623,7 @@ class TexAtl_MergeObjects(Operator): # We do the MergeList because we will duplicate grouped objects mergeList = [] - for object in bpy.data.groups[self.group_name].objects: + for object in bpy.data.collections[self.group_name].objects: mergeList.append(object) for object in mergeList: @@ -746,7 +746,7 @@ class TexAtl_SeparateObjects(Operator): bpy.ops.object.select_all(action='DESELECT') ob_merged.hide = False ob_merged.select = True - groupSeparate = bpy.data.groups.new(ob_merged.name) + groupSeparate = bpy.data.collections.new(ob_merged.name) groupSeparate.objects.link(ob_merged) ob_merged.select = False |