diff options
101 files changed, 3350 insertions, 1914 deletions
diff --git a/add_curve_extra_objects/__init__.py b/add_curve_extra_objects/__init__.py index fbb0838b..e5a1bbcd 100644 --- a/add_curve_extra_objects/__init__.py +++ b/add_curve_extra_objects/__init__.py @@ -28,8 +28,8 @@ bl_info = { "location": "View3D > Add > Curve > Extra Objects", "description": "Add extra curve object types", "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Curve/Curve_Objects", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "add_curve/extra_objects.html", "category": "Add Curve" } @@ -247,9 +247,9 @@ def menu_func(self, context): layout = self.layout layout.operator_menu_enum("curve.curveaceous_galore", "ProfileType", icon='CURVE_DATA') - layout.operator_menu_enum("curve.spirals", "spiral_type", icon='CURVE_DATA') + layout.operator_menu_enum("curve.spirals", "spiral_type", icon='FORCE_VORTEX') layout.separator() - layout.operator("curve.curlycurve", text="Curly Curve", icon='CURVE_DATA') + layout.operator("curve.curlycurve", text="Curly Curve", icon='GP_ONLY_SELECTED') if context.mode != 'OBJECT': # fix in D2142 will allow to work in EDIT_CURVE return None diff --git a/add_curve_extra_objects/add_curve_simple.py b/add_curve_extra_objects/add_curve_simple.py index e0dc2807..c73cb623 100644 --- a/add_curve_extra_objects/add_curve_simple.py +++ b/add_curve_extra_objects/add_curve_simple.py @@ -817,60 +817,60 @@ def main(context, self, align_matrix, use_enter_edit_mode): Curve.data.fill_mode = 'BOTH' def menu(self, context): - oper1 = self.layout.operator(Simple.bl_idname, text="Angle", icon="MOD_CURVE") + oper1 = self.layout.operator(Simple.bl_idname, text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE") oper1.Simple_Type = "Angle" oper1.use_cyclic_u = False - oper2 = self.layout.operator(Simple.bl_idname, text="Arc", icon="MOD_CURVE") + oper2 = self.layout.operator(Simple.bl_idname, text="Arc", icon="MOD_THICKNESS") oper2.Simple_Type = "Arc" oper2.use_cyclic_u = False - oper3 = self.layout.operator(Simple.bl_idname, text="Circle", icon="MOD_CURVE") + oper3 = self.layout.operator(Simple.bl_idname, text="Circle", icon="ANTIALIASED") oper3.Simple_Type = "Circle" oper3.use_cyclic_u = True - oper4 = self.layout.operator(Simple.bl_idname, text="Distance", icon="MOD_CURVE") + oper4 = self.layout.operator(Simple.bl_idname, text="Distance", icon="DRIVER_DISTANCE") oper4.Simple_Type = "Distance" oper4.use_cyclic_u = False - oper5 = self.layout.operator(Simple.bl_idname, text="Ellipse", icon="MOD_CURVE") + oper5 = self.layout.operator(Simple.bl_idname, text="Ellipse", icon="MESH_TORUS") oper5.Simple_Type = "Ellipse" oper5.use_cyclic_u = True - oper6 = self.layout.operator(Simple.bl_idname, text="Line", icon="MOD_CURVE") + oper6 = self.layout.operator(Simple.bl_idname, text="Line", icon="MOD_SIMPLIFY") oper6.Simple_Type = "Line" oper6.use_cyclic_u = False oper6.shape = '3D' - oper7 = self.layout.operator(Simple.bl_idname, text="Point", icon="MOD_CURVE") + oper7 = self.layout.operator(Simple.bl_idname, text="Point", icon="LAYER_ACTIVE") oper7.Simple_Type = "Point" oper7.use_cyclic_u = False - oper8 = self.layout.operator(Simple.bl_idname, text="Polygon", icon="MOD_CURVE") + oper8 = self.layout.operator(Simple.bl_idname, text="Polygon", icon="SEQ_CHROMA_SCOPE") oper8.Simple_Type = "Polygon" oper8.use_cyclic_u = True - oper9 = self.layout.operator(Simple.bl_idname, text="Polygon ab", icon="MOD_CURVE") + oper9 = self.layout.operator(Simple.bl_idname, text="Polygon ab", icon="SEQ_CHROMA_SCOPE") oper9.Simple_Type = "Polygon_ab" oper9.use_cyclic_u = True - oper10 = self.layout.operator(Simple.bl_idname, text="Rectangle", icon="MOD_CURVE") + oper10 = self.layout.operator(Simple.bl_idname, text="Rectangle", icon="MESH_PLANE") oper10.Simple_Type = "Rectangle" oper10.use_cyclic_u = True - oper11 = self.layout.operator(Simple.bl_idname, text="Rhomb", icon="MOD_CURVE") + oper11 = self.layout.operator(Simple.bl_idname, text="Rhomb", icon="DECORATE_ANIMATE") oper11.Simple_Type = "Rhomb" oper11.use_cyclic_u = True - oper12 = self.layout.operator(Simple.bl_idname, text="Sector", icon="MOD_CURVE") + oper12 = self.layout.operator(Simple.bl_idname, text="Sector", icon="CON_SHRINKWRAP") oper12.Simple_Type = "Sector" oper12.use_cyclic_u = True - oper13 = self.layout.operator(Simple.bl_idname, text="Segment", icon="MOD_CURVE") + oper13 = self.layout.operator(Simple.bl_idname, text="Segment", icon="MOD_SIMPLEDEFORM") oper13.Simple_Type = "Segment" oper13.use_cyclic_u = True - oper14 = self.layout.operator(Simple.bl_idname, text="Trapezoid", icon="MOD_CURVE") + oper14 = self.layout.operator(Simple.bl_idname, text="Trapezoid", icon="MOD_EDGESPLIT") oper14.Simple_Type = "Trapezoid" oper14.use_cyclic_u = True diff --git a/add_curve_ivygen.py b/add_curve_ivygen.py index 6359b7fb..f6f0f8a3 100644 --- a/add_curve_ivygen.py +++ b/add_curve_ivygen.py @@ -27,8 +27,8 @@ bl_info = { "description": "Adds generated ivy to a mesh object starting " "at the 3D cursor", "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Curve/Ivy_Gen", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "add_curve/ivy_gen.html", "category": "Add Curve", } diff --git a/add_curve_sapling/__init__.py b/add_curve_sapling/__init__.py index 3b9bdd09..554f13f3 100644 --- a/add_curve_sapling/__init__.py +++ b/add_curve_sapling/__init__.py @@ -26,8 +26,8 @@ bl_info = { "description": ("Adds a parametric tree. The method is presented by " "Jason Weber & Joseph Penn in their paper 'Creation and Rendering of " "Realistic Trees'"), - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Curve/Sapling_Tree", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "add_curve/sapling.html", "category": "Add Curve"} if "bpy" in locals(): diff --git a/ant_landscape/__init__.py b/ant_landscape/__init__.py index 789f0a37..e2d43c57 100644 --- a/ant_landscape/__init__.py +++ b/ant_landscape/__init__.py @@ -27,8 +27,8 @@ bl_info = { "location": "View3D > Sidebar > Create Tab", "description": "Another Noise Tool: Landscape and Displace", "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Add_Mesh/ANT_Landscape", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "add_mesh/ant_landscape.html", "category": "Add Mesh", } diff --git a/btrace/__init__.py b/btrace/__init__.py index d12fe9ea..55d49e44 100644 --- a/btrace/__init__.py +++ b/btrace/__init__.py @@ -25,7 +25,8 @@ bl_info = { "location": "View3D > Sidebar > Create Tab", "description": "Tools for converting/animating objects/particles into curves", "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Curve/Btrace", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "add_curve/btracer.html", "category": "Add Curve"} import bpy diff --git a/curve_assign_shapekey.py b/curve_assign_shapekey.py index 46a2b0d1..318e2b25 100644 --- a/curve_assign_shapekey.py +++ b/curve_assign_shapekey.py @@ -27,21 +27,29 @@ bl_info = { "location": "View 3D > Sidebar > Edit Tab", "description": "Assigns one or more Bezier curves as shape keys to another Bezier curve", "category": "Add Curve", - "wiki_url": "https://github.com/Shriinivas/assignshapekey/blob/master/README.md", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "add_curve/assign_shape_keys.html", "blender": (2, 80, 0), } +alignList = [('minX', 'Min X', 'Align vertices with Min X'), + ('maxX', 'Max X', 'Align vertices with Max X'), + ('minY', 'Min Y', 'Align vertices with Min Y'), + ('maxY', 'Max Y', 'Align vertices with Max Y'), + ('minZ', 'Min Z', 'Align vertices with Min Z'), + ('maxZ', 'Max Z', 'Align vertices with Max Z')] + matchList = [('vCnt', 'Vertex Count', 'Match by vertex count'), ('bbArea', 'Area', 'Match by surface area of the bounding box'), \ ('bbHeight', 'Height', 'Match by bounding box height'), \ ('bbWidth', 'Width', 'Match by bounding box width'), ('bbDepth', 'Depth', 'Match by bounding box depth'), - ('minX', 'Min X', 'Match by bounding bon Min X'), - ('maxX', 'Max X', 'Match by bounding bon Max X'), - ('minY', 'Min Y', 'Match by bounding bon Min Y'), - ('maxY', 'Max Y', 'Match by bounding bon Max Y'), - ('minZ', 'Min Z', 'Match by bounding bon Min Z'), - ('maxZ', 'Max Z', 'Match by bounding bon Max Z')] + ('minX', 'Min X', 'Match by bounding box Min X'), + ('maxX', 'Max X', 'Match by bounding box Max X'), + ('minY', 'Min Y', 'Match by bounding box Min Y'), + ('maxY', 'Max Y', 'Match by bounding box Max Y'), + ('minZ', 'Min Z', 'Match by bounding box Min Z'), + ('maxZ', 'Max Z', 'Match by bounding box Max Z')] DEF_ERR_MARGIN = 0.0001 @@ -778,7 +786,7 @@ class AssignShapeKeysOp(Operator): matchCri2 = params.matchCri2 matchCri3 = params.matchCri3 - alignBy = params.alignList + alignBy = params.alignCos alignVal1 = params.alignVal1 alignVal2 = params.alignVal2 alignVal3 = params.alignVal3 @@ -999,20 +1007,20 @@ class AssignShapeKeyParams(bpy.types.PropertyGroup): ('localspace', 'Local Space', 'localspace')], \ description = 'Space that shape keys are evluated in') - alignList : EnumProperty(name="Vertex Alignment", items = \ + alignCos : EnumProperty(name="Vertex Alignment", items = \ [("-None-", 'Manual Alignment', "Align curve segments based on starting vertex"), \ ('vertCo', 'Vertex Coordinates', 'Align curve segments based on vertex coordinates')], \ description = 'Start aligning the vertices of target and shape keys from', default = '-None-') alignVal1 : EnumProperty(name="Value 1", - items = matchList, default = 'minX', description='First align criterion') + items = alignList, default = 'minX', description='First align criterion') alignVal2 : EnumProperty(name="Value 2", - items = matchList, default = 'maxY', description='Second align criterion') + items = alignList, default = 'maxY', description='Second align criterion') alignVal3 : EnumProperty(name="Value 3", - items = matchList, default = 'minZ', description='Third align criterion') + items = alignList, default = 'minZ', description='Third align criterion') matchParts : EnumProperty(name="Match Parts", items = \ [("-None-", 'None', "Don't match parts"), \ @@ -1062,9 +1070,9 @@ class AssignShapeKeysPanel(Panel): row.prop(params, "space") row = col.row() - row.prop(params, "alignList") + row.prop(params, "alignCos") - if(params.alignList == 'vertCo'): + if(params.alignCos == 'vertCo'): row = col.row() row.prop(params, "alignVal1") row.prop(params, "alignVal2") diff --git a/curve_simplify.py b/curve_simplify.py index 2f6d1a8f..136eebea 100644 --- a/curve_simplify.py +++ b/curve_simplify.py @@ -24,8 +24,8 @@ bl_info = { "location": "3D View, Dopesheet & Graph Editors", "description": "Simplify Curves: 3dview, Dopesheet, Graph. Distance Merge: 3d view curve edit", "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/Curve/Curve_Simplify", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "add_curve/simplify_curves.html", "category": "Add Curve", } diff --git a/curve_tools/__init__.py b/curve_tools/__init__.py index 736e60fb..47297f43 100644 --- a/curve_tools/__init__.py +++ b/curve_tools/__init__.py @@ -25,12 +25,13 @@ bl_info = { "name": "Curve Tools", "description": "Adds some functionality for bezier/nurbs curve/surface modeling", "author": "Mackraken", - "version": (0, 4, 0), + "version": (0, 4, 3), "blender": (2, 80, 0), "location": "View3D > Tool Shelf > Edit Tab", "warning": "WIP", "wiki_url": "", - "tracker_url": "", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "add_curve/curve_tools.html", "category": "Add Curve"} @@ -333,6 +334,8 @@ class VIEW3D_PT_CurvePanel(Panel): row = col.row(align=True) row.operator("curvetools.sep_outline", text="Separate Outline or selected") row = col.row(align=True) + row.operator("curvetools.bezier_curve_boolean", text="2D Curve Boolean") + row = col.row(align=True) row.operator("curvetools.bezier_points_fillet", text='Fillet') row = col.row(align=True) row.operator("curvetools.bezier_cad_handle_projection", text='Handle Projection') diff --git a/curve_tools/cad.py b/curve_tools/cad.py index 49ccf171..b7478450 100644 --- a/curve_tools/cad.py +++ b/curve_tools/cad.py @@ -66,7 +66,7 @@ class Boolean(bpy.types.Operator): @classmethod def poll(cls, context): - return util.Selected1OrMoreCurves() + return util.Selected1Curve() def execute(self, context): current_mode = bpy.context.object.mode @@ -80,7 +80,7 @@ class Boolean(bpy.types.Operator): if len(splines) != 2: self.report({'WARNING'}, 'Invalid selection. Only work to selected two spline.') return {'CANCELLED'} - bpy.ops.curvetools.spline_type_set(type='BEZIER') + bpy.ops.curve.spline_type_set(type='BEZIER') splineA = bpy.context.object.data.splines.active splineB = splines[0] if (splines[1] == splineA) else splines[1] if not internal.bezierBooleanGeometry(splineA, splineB, self.operation): @@ -169,13 +169,13 @@ class MergeEnds(bpy.types.Operator): points[0].handle_left = handle points[0].co = new_co - bpy.ops.curvetools.select_all(action='DESELECT') + bpy.ops.curve.select_all(action='DESELECT') points[1].select_control_point = True - bpy.ops.curvetools.delete() + bpy.ops.curve.delete() selected_splines[0].bezier_points[-1 if is_last_point[0] else 0].select_control_point = True selected_splines[1].bezier_points[-1 if is_last_point[1] else 0].select_control_point = True - bpy.ops.curvetools.make_segment() - bpy.ops.curvetools.select_all(action='DESELECT') + bpy.ops.curve.make_segment() + bpy.ops.curve.select_all(action='DESELECT') return {'FINISHED'} class Subdivide(bpy.types.Operator): diff --git a/curve_tools/operators.py b/curve_tools/operators.py index e4fe24dd..191aff08 100644 --- a/curve_tools/operators.py +++ b/curve_tools/operators.py @@ -13,6 +13,7 @@ from . import intersections from . import util from . import surfaces from . import mathematics +from . import internal # 1 CURVE SELECTED # ################ @@ -1020,6 +1021,133 @@ class SeparateOutline(bpy.types.Operator): bpy.ops.curve.separate() return {'FINISHED'} + +class CurveBoolean(bpy.types.Operator): + bl_idname = "curvetools.bezier_curve_boolean" + bl_description = "Curve Boolean" + bl_label = "Curve Boolean" + bl_options = {'REGISTER', 'UNDO'} + + operation: bpy.props.EnumProperty(name='Type', items=[ + ('UNION', 'Union', 'Boolean OR', 0), + ('INTERSECTION', 'Intersection', 'Boolean AND', 1), + ('DIFFERENCE', 'Difference', 'Active minus Selected', 2), + ]) + number : IntProperty( + name="Spline Number", + default=1, + min=1, + description="Spline Number" + ) + + @classmethod + def poll(cls, context): + return util.Selected1OrMoreCurves() + + def draw(self, context): + layout = self.layout + + # general options + col = layout.column() + col.prop(self, "operation") + if self.operation == 'DIFFERENCE': + col.prop(self, "number") + + def execute(self, context): + current_mode = bpy.context.object.mode + + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode = 'OBJECT') + + selected_Curves = util.GetSelectedCurves() + len_selected_curves = len(selected_Curves) + if len_selected_curves < 2: + return {'FINISHED'} + + min_number = 1 + + max_number = 0 + for iCurve in range(0, len_selected_curves): + len_splines = len(selected_Curves[iCurve].data.splines) + max_number += len_splines + + if self.number < min_number: + self.number = min_number + if self.number > max_number: + self.number = max_number + + j = 0 + first_curve = 0 + first_spline = 0 + for iCurve in range(0, len_selected_curves): + len_splines = len(selected_Curves[iCurve].data.splines) + for iSpline in range(0, len_splines): + if j == self.number: + first_curve = iCurve + first_spline = iSpline + j += 1 + + bpy.ops.object.select_all(action='DESELECT') + + spline1 = selected_Curves[first_curve].data.splines[first_spline] + matrix_world1 = selected_Curves[first_curve].matrix_world + + len_spline1 = len(spline1.bezier_points) + + dataCurve = bpy.data.curves.new(self.operation, type='CURVE') + dataCurve.dimensions = '2D' + newSpline1 = dataCurve.splines.new(type='BEZIER') + newSpline1.use_cyclic_u = True + newSpline1.bezier_points.add(len_spline1 - 1) + for n in range(0, len_spline1): + newSpline1.bezier_points[n].co = matrix_world1 @ spline1.bezier_points[n].co + newSpline1.bezier_points[n].handle_left_type = spline1.bezier_points[n].handle_left_type + newSpline1.bezier_points[n].handle_left = matrix_world1 @ spline1.bezier_points[n].handle_left + newSpline1.bezier_points[n].handle_right_type = spline1.bezier_points[n].handle_right_type + newSpline1.bezier_points[n].handle_right = matrix_world1 @ spline1.bezier_points[n].handle_right + + Curve = object_utils.object_data_add(context, dataCurve) + bpy.context.view_layer.objects.active = Curve + Curve.select_set(True) + Curve.location = (0.0, 0.0, 0.0) + + j = 0 + for iCurve in range(0, len_selected_curves): + matrix_world = selected_Curves[iCurve].matrix_world + len_splines = len(selected_Curves[iCurve].data.splines) + for iSpline in range(0, len_splines): + if iCurve == first_curve and iSpline == first_spline: + continue + spline = selected_Curves[iCurve].data.splines[iSpline] + len_spline = len(spline.bezier_points) + newSpline = dataCurve.splines.new(type='BEZIER') + newSpline.use_cyclic_u = True + newSpline.bezier_points.add(len_spline - 1) + for n in range(0, len_spline): + newSpline.bezier_points[n].co = matrix_world @ spline.bezier_points[n].co + newSpline.bezier_points[n].handle_left_type = spline.bezier_points[n].handle_left_type + newSpline.bezier_points[n].handle_left = matrix_world @ spline.bezier_points[n].handle_left + newSpline.bezier_points[n].handle_right_type = spline.bezier_points[n].handle_right_type + newSpline.bezier_points[n].handle_right = matrix_world @ spline.bezier_points[n].handle_right + + bpy.ops.object.mode_set(mode = 'EDIT') + bpy.ops.curve.select_all(action='SELECT') + splines = internal.getSelectedSplines(True, True) + if len(splines) < 2: + continue + splineA = splines[0] + splineB = splines[1] + dataCurve.splines.active = newSpline1 + + if not internal.bezierBooleanGeometry(splineA, splineB, self.operation): + self.report({'WARNING'}, 'Invalid selection.') + return {'CANCELLED'} + + j += 1 + + bpy.ops.object.mode_set (mode = current_mode) + + return {'FINISHED'} def register(): for cls in classes: @@ -1053,4 +1181,5 @@ operators = [ CurveScaleReset, Split, SeparateOutline, + CurveBoolean, ] diff --git a/curve_tools/path_finder.py b/curve_tools/path_finder.py index 366f9bcc..30c673e0 100644 --- a/curve_tools/path_finder.py +++ b/curve_tools/path_finder.py @@ -96,7 +96,7 @@ def draw_bezier_points(self, context, spline, matrix_world, path_color, path_thi points = get_bezier_points(spline, matrix_world) shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') - batch = batch_for_shader(shader, 'LINES', {"pos": points}) + batch = batch_for_shader(shader, 'POINTS', {"pos": points}) shader.bind() shader.uniform_float("color", path_color) @@ -109,7 +109,7 @@ def draw_points(self, context, spline, matrix_world, path_color, path_thickness) points = get_points(spline, matrix_world) shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') - batch = batch_for_shader(shader, 'LINES', {"pos": points}) + batch = batch_for_shader(shader, 'POINTS', {"pos": points}) shader.bind() shader.uniform_float("color", path_color) diff --git a/curve_tools/remove_doubles.py b/curve_tools/remove_doubles.py index 151b19b0..cf955980 100644 --- a/curve_tools/remove_doubles.py +++ b/curve_tools/remove_doubles.py @@ -109,7 +109,7 @@ class CurveRemvDbs(bpy.types.Operator): @classmethod def poll(cls, context): - return util.Selected1Curve() + return util.Selected1OrMoreCurves() def execute(self, context): removed=main(context, self.distance) diff --git a/io_mesh_ply/__init__.py b/io_mesh_ply/__init__.py index b89732fd..c9a79e1f 100644 --- a/io_mesh_ply/__init__.py +++ b/io_mesh_ply/__init__.py @@ -21,11 +21,10 @@ bl_info = { "name": "Stanford PLY format", "author": "Bruce Merry, Campbell Barton", - "version": (1, 0, 0), - "blender": (2, 81, 6), + "version": (1, 1, 0), + "blender": (2, 82, 0), "location": "File > Import-Export", - "description": "Import-Export PLY mesh data with UV's and vertex colors", - "warning": "", + "description": "Import-Export PLY mesh data with UVs and vertex colors", "wiki_url": "https://docs.blender.org/manual/en/latest/addons/io_mesh_ply.html", "support": 'OFFICIAL', "category": "Import-Export", @@ -34,8 +33,6 @@ bl_info = { # Copyright (C) 2004, 2005: Bruce Merry, bmerry@cs.uct.ac.za # Contributors: Bruce Merry, Campbell Barton -# To support reload properly, try to access a package var, -# if it's there, reload everything if "bpy" in locals(): import importlib if "export_ply" in locals(): @@ -44,13 +41,11 @@ if "bpy" in locals(): importlib.reload(import_ply) -import os import bpy from bpy.props import ( CollectionProperty, StringProperty, BoolProperty, - EnumProperty, FloatProperty, ) from bpy_extras.io_utils import ( @@ -81,6 +76,8 @@ class ImportPLY(bpy.types.Operator, ImportHelper): filter_glob: StringProperty(default="*.ply", options={'HIDDEN'}) def execute(self, context): + import os + paths = [os.path.join(self.directory, name.name) for name in self.files] if not paths: @@ -96,14 +93,18 @@ class ImportPLY(bpy.types.Operator, ImportHelper): @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" + bl_description = "Export as a Stanford PLY with normals, vertex colors and texture coordinates" filename_ext = ".ply" filter_glob: StringProperty(default="*.ply", options={'HIDDEN'}) + use_selection: BoolProperty( + name="Selection Only", + description="Export selected objects only", + default=False, + ) use_mesh_modifiers: BoolProperty( name="Apply Modifiers", description="Apply Modifiers to the exported mesh", @@ -136,10 +137,6 @@ class ExportPLY(bpy.types.Operator, ExportHelper): default=1.0, ) - @classmethod - def poll(cls, context): - return context.active_object is not None - def execute(self, context): from . import export_ply @@ -169,6 +166,30 @@ class ExportPLY(bpy.types.Operator, ExportHelper): pass +class PLY_PT_export_include(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Include" + bl_parent_id = "FILE_PT_operator" + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_MESH_OT_ply" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + sfile = context.space_data + operator = sfile.active_operator + + layout.prop(operator, "use_selection") + + class PLY_PT_export_transform(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' @@ -233,6 +254,7 @@ def menu_func_export(self, context): classes = ( ImportPLY, ExportPLY, + PLY_PT_export_include, PLY_PT_export_transform, PLY_PT_export_geometry, ) @@ -253,5 +275,6 @@ def unregister(): 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 db79e950..812aeb54 100644 --- a/io_mesh_ply/export_ply.py +++ b/io_mesh_ply/export_ply.py @@ -21,20 +21,12 @@ """ This script exports Stanford PLY files from Blender. It supports normals, colors, and texture coordinates per face or per vertex. -Only one mesh can be exported at a time. """ -import bpy -import os - -def save_mesh( - filepath, - mesh, - use_normals=True, - use_uv_coords=True, - use_colors=True, -): +def save_mesh(filepath, mesh, use_normals=True, use_uv_coords=True, use_colors=True): + import os + import bpy def rvec3d(v): return round(v[0], 6), round(v[1], 6), round(v[2], 6) @@ -42,47 +34,26 @@ def save_mesh( def rvec2d(v): return round(v[0], 6), round(v[1], 6) - file = open(filepath, "w", encoding="utf8", newline="\n") - fw = file.write - - has_uv = bool(mesh.uv_layers) - has_vcol = bool(mesh.vertex_colors) - - if not has_uv: + if use_uv_coords and mesh.uv_layers: + active_uv_layer = mesh.uv_layers.active.data + else: use_uv_coords = False - if not has_vcol: - use_colors = False - if not use_uv_coords: - has_uv = False - if not use_colors: - has_vcol = False - - if has_uv: - active_uv_layer = mesh.uv_layers.active - if not active_uv_layer: - use_uv_coords = False - has_uv = False - else: - active_uv_layer = active_uv_layer.data - - if has_vcol: - active_col_layer = mesh.vertex_colors.active - if not active_col_layer: - use_colors = False - has_vcol = False - else: - active_col_layer = active_col_layer.data + if use_colors and mesh.vertex_colors: + active_col_layer = mesh.vertex_colors.active.data + else: + use_colors = False # in case color = uvcoord = uvcoord_key = normal = normal_key = None - mesh_verts = mesh.vertices # save a lookup - ply_verts = [] # list of dictionaries + mesh_verts = mesh.vertices # vdict = {} # (index, normal, uv) -> new index vdict = [{} for i in range(len(mesh_verts))] + ply_verts = [] ply_faces = [[] for f in range(len(mesh.polygons))] vert_count = 0 + for i, f in enumerate(mesh.polygons): smooth = not use_normals or f.use_smooth @@ -90,12 +61,12 @@ def save_mesh( normal = f.normal[:] normal_key = rvec3d(normal) - if has_uv: + if use_uv_coords: uv = [ active_uv_layer[l].uv[:] for l in range(f.loop_start, f.loop_start + f.loop_total) ] - if has_vcol: + if use_colors: col = [ active_col_layer[l].color[:] for l in range(f.loop_start, f.loop_start + f.loop_total) @@ -109,11 +80,11 @@ def save_mesh( normal = v.normal[:] normal_key = rvec3d(normal) - if has_uv: + if use_uv_coords: uvcoord = uv[j][0], uv[j][1] uvcoord_key = rvec2d(uvcoord) - if has_vcol: + if use_colors: color = col[j] color = ( int(color[0] * 255.0), @@ -126,106 +97,141 @@ def save_mesh( vdict_local = vdict[vidx] pf_vidx = vdict_local.get(key) # Will be None initially - if pf_vidx is None: # same as vdict_local.has_key(key) + if pf_vidx is None: # Same as vdict_local.has_key(key) pf_vidx = vdict_local[key] = vert_count ply_verts.append((vidx, normal, uvcoord, color)) vert_count += 1 pf.append(pf_vidx) - fw("ply\n") - fw("format ascii 1.0\n") - fw("comment Created by Blender %s - " - "www.blender.org, source file: %r\n" % - (bpy.app.version_string, os.path.basename(bpy.data.filepath))) - - fw("element vertex %d\n" % len(ply_verts)) - - fw("property float x\n" - "property float y\n" - "property float z\n") - - if use_normals: - fw("property float nx\n" - "property float ny\n" - "property float nz\n") - if use_uv_coords: - fw("property float s\n" - "property float t\n") - if use_colors: - fw("property uchar red\n" - "property uchar green\n" - "property uchar blue\n" - "property uchar alpha\n") - - fw("element face %d\n" % len(mesh.polygons)) - fw("property list uchar uint vertex_indices\n") - fw("end_header\n") - - for i, v in enumerate(ply_verts): - fw("%.6f %.6f %.6f" % mesh_verts[v[0]].co[:]) # co + with open(filepath, "w", encoding="utf-8", newline="\n") as file: + fw = file.write + + # Header + # --------------------------- + + fw("ply\n") + fw("format ascii 1.0\n") + fw( + f"comment Created by Blender {bpy.app.version_string} - " + f"www.blender.org, source file: {os.path.basename(bpy.data.filepath)!r}\n" + ) + + fw(f"element vertex {len(ply_verts)}\n") + fw( + "property float x\n" + "property float y\n" + "property float z\n" + ) if use_normals: - fw(" %.6f %.6f %.6f" % v[1]) # no + fw( + "property float nx\n" + "property float ny\n" + "property float nz\n" + ) if use_uv_coords: - fw(" %.6f %.6f" % v[2]) # uv + fw( + "property float s\n" + "property float t\n" + ) if use_colors: - fw(" %u %u %u %u" % v[3]) # col - fw("\n") - - for pf in ply_faces: - # fw(f"{len(pf)} {' '.join(str(x) for x in pf)}\n") - fw("%d" % len(pf)) - for v in pf: - fw(" %d" % v) - fw("\n") - - file.close() - print("writing %r done" % filepath) + fw( + "property uchar red\n" + "property uchar green\n" + "property uchar blue\n" + "property uchar alpha\n" + ) + + fw(f"element face {len(mesh.polygons)}\n") + fw("property list uchar uint vertex_indices\n") + + fw("end_header\n") + + # Vertex data + # --------------------------- + + for i, v in enumerate(ply_verts): + fw("%.6f %.6f %.6f" % mesh_verts[v[0]].co[:]) + if use_normals: + fw(" %.6f %.6f %.6f" % v[1]) + if use_uv_coords: + fw(" %.6f %.6f" % v[2]) + if use_colors: + fw(" %u %u %u %u" % v[3]) + fw("\n") + + # Face data + # --------------------------- + + for pf in ply_faces: + fw(f"{len(pf)}") + for v in pf: + fw(f" {v}") + fw("\n") + + print(f"Writing {filepath!r} done") return {'FINISHED'} def save( - operator, - context, - filepath="", - use_mesh_modifiers=True, - use_normals=True, - use_uv_coords=True, - use_colors=True, - global_matrix=None + operator, + context, + filepath="", + use_selection=False, + use_mesh_modifiers=True, + use_normals=True, + use_uv_coords=True, + use_colors=True, + global_matrix=None ): - obj = context.active_object - - if global_matrix is None: - from mathutils import Matrix - global_matrix = Matrix() + import bpy + import bmesh if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') - mesh_owner_object = None - if use_mesh_modifiers and obj.modifiers: - depsgraph = context.evaluated_depsgraph_get() - mesh_owner_object = obj.evaluated_get(depsgraph) - mesh = mesh_owner_object.to_mesh() + if use_selection: + obs = context.selected_objects else: - mesh_owner_object = obj - mesh = mesh_owner_object.to_mesh() + obs = context.scene.objects - if not mesh: - raise Exception("Error, could not get mesh data from active object") + depsgraph = context.evaluated_depsgraph_get() + bm = bmesh.new() + + for ob in obs: + if use_mesh_modifiers: + ob_eval = ob.evaluated_get(depsgraph) + else: + ob_eval = ob + + try: + me = ob_eval.to_mesh() + except RuntimeError: + continue + + me.transform(ob.matrix_world) + bm.from_mesh(me) + ob_eval.to_mesh_clear() + + mesh = bpy.data.meshes.new("TMP PLY EXPORT") + bm.to_mesh(mesh) + bm.free() + + if global_matrix is not None: + mesh.transform(global_matrix) - mesh.transform(global_matrix @ obj.matrix_world) if use_normals: mesh.calc_normals() - ret = save_mesh(filepath, mesh, - use_normals=use_normals, - use_uv_coords=use_uv_coords, - use_colors=use_colors, - ) + ret = save_mesh( + filepath, + mesh, + use_normals=use_normals, + use_uv_coords=use_uv_coords, + use_colors=use_colors, + ) - mesh_owner_object.to_mesh_clear() + bpy.data.meshes.remove(mesh) return ret diff --git a/io_mesh_ply/import_ply.py b/io_mesh_ply/import_ply.py index 5da7f9d6..2bf91442 100644 --- a/io_mesh_ply/import_ply.py +++ b/io_mesh_ply/import_ply.py @@ -18,11 +18,8 @@ # <pep8 compliant> -import re -import struct - -class element_spec(object): +class ElementSpec: __slots__ = ( "name", "count", @@ -46,7 +43,7 @@ class element_spec(object): return -1 -class property_spec(object): +class PropertySpec: __slots__ = ( "name", "list_type", @@ -59,14 +56,16 @@ class property_spec(object): self.numeric_type = numeric_type def read_format(self, format, count, num_type, stream): + import struct + if format == b'ascii': if num_type == 's': ans = [] for i in range(count): s = stream[i] if not (len(s) >= 2 and s.startswith(b'"') and s.endswith(b'"')): - print('Invalid string', s) - print('Note: ply_import.py does not handle whitespace in strings') + print("Invalid string", s) + print("Note: ply_import.py does not handle whitespace in strings") return None ans.append(s[1:-1]) stream[:count] = [] @@ -103,18 +102,18 @@ class property_spec(object): return self.read_format(format, 1, self.numeric_type, stream)[0] -class object_spec(object): - __slots__ = ("specs", - ) - 'A list of element_specs' +class ObjectSpec: + __slots__ = ("specs",) + def __init__(self): + # A list of element_specs self.specs = [] def load(self, format, stream): return dict([(i.name, [i.load(format, stream) for j in range(i.count)]) for i in self.specs]) - ''' # Longhand for above LC + """ answer = {} for i in self.specs: answer[i.name] = [] @@ -123,10 +122,12 @@ class object_spec(object): Blender.Window.DrawProgressBar(float(j) / i.count, 'Loading ' + i.name) answer[i.name].append(i.load(format, stream)) return answer - ''' + """ def read(filepath): + import re + format = b'' texture = b'' version = b'1.0' @@ -154,14 +155,14 @@ def read(filepath): b'double': 'd', b'string': 's', } - obj_spec = object_spec() + obj_spec = ObjectSpec() invalid_ply = (None, None, None) with open(filepath, 'rb') as plyf: signature = plyf.readline() if not signature.startswith(b'ply'): - print('Signature line was invalid') + print("Signature line was invalid") return invalid_ply valid_header = False @@ -178,7 +179,7 @@ def read(filepath): continue elif tokens[1] == b'TextureFile': if len(tokens) < 4: - print('Invalid texture line') + print("Invalid texture line") else: texture = tokens[2] continue @@ -187,34 +188,34 @@ def read(filepath): continue elif tokens[0] == b'format': if len(tokens) < 3: - print('Invalid format line') + print("Invalid format line") return invalid_ply if tokens[1] not in format_specs: - print('Unknown format', tokens[1]) + print("Unknown format", tokens[1]) return invalid_ply try: version_test = float(tokens[2]) except Exception as ex: - print('Unknown version', ex) + print("Unknown version", ex) version_test = None if version_test != float(version): - print('Unknown version', tokens[2]) + print("Unknown version", tokens[2]) return invalid_ply del version_test format = tokens[1] elif tokens[0] == b'element': if len(tokens) < 3: - print(b'Invalid element line') + print("Invalid element line") return invalid_ply - obj_spec.specs.append(element_spec(tokens[1], int(tokens[2]))) + obj_spec.specs.append(ElementSpec(tokens[1], int(tokens[2]))) elif tokens[0] == b'property': if not len(obj_spec.specs): - print('Property without element') + print("Property without element") return invalid_ply if tokens[1] == b'list': - obj_spec.specs[-1].properties.append(property_spec(tokens[4], type_specs[tokens[2]], type_specs[tokens[3]])) + obj_spec.specs[-1].properties.append(PropertySpec(tokens[4], type_specs[tokens[2]], type_specs[tokens[3]])) else: - obj_spec.specs[-1].properties.append(property_spec(tokens[2], None, type_specs[tokens[1]])) + obj_spec.specs[-1].properties.append(PropertySpec(tokens[2], None, type_specs[tokens[1]])) if not valid_header: print("Invalid header ('end_header' line not found!)") return invalid_ply @@ -224,22 +225,20 @@ def read(filepath): return obj_spec, obj, texture -import bpy - - def load_ply_mesh(filepath, ply_name): - from bpy_extras.io_utils import unpack_face_list + import bpy obj_spec, obj, texture = read(filepath) # XXX28: use texture if obj is None: - print('Invalid file') + print("Invalid file") return uvindices = colindices = None colmultiply = None - # noindices = None # Ignore normals + # TODO import normals + # noindices = None for el in obj_spec.specs: if el.name == b'vertex': @@ -375,39 +374,39 @@ def load_ply_mesh(filepath, ply_name): if texture and uvindices: pass - # XXX28: add support for using texture. - ''' - import os - import sys - from bpy_extras.image_utils import load_image - - encoding = sys.getfilesystemencoding() - encoded_texture = texture.decode(encoding=encoding) - name = bpy.path.display_name_from_filepath(texture) - image = load_image(encoded_texture, os.path.dirname(filepath), recursive=True, place_holder=True) - - if image: - texture = bpy.data.textures.new(name=name, type='IMAGE') - texture.image = image - - material = bpy.data.materials.new(name=name) - material.use_shadeless = True - - mtex = material.texture_slots.add() - mtex.texture = texture - mtex.texture_coords = 'UV' - mtex.use_map_color_diffuse = True - - mesh.materials.append(material) - for face in mesh.uv_textures[0].data: - face.image = image - ''' + # TODO add support for using texture. + + # import os + # import sys + # from bpy_extras.image_utils import load_image + + # encoding = sys.getfilesystemencoding() + # encoded_texture = texture.decode(encoding=encoding) + # name = bpy.path.display_name_from_filepath(texture) + # image = load_image(encoded_texture, os.path.dirname(filepath), recursive=True, place_holder=True) + + # if image: + # texture = bpy.data.textures.new(name=name, type='IMAGE') + # texture.image = image + + # material = bpy.data.materials.new(name=name) + # material.use_shadeless = True + + # mtex = material.texture_slots.add() + # mtex.texture = texture + # mtex.texture_coords = 'UV' + # mtex.use_map_color_diffuse = True + + # mesh.materials.append(material) + # for face in mesh.uv_textures[0].data: + # face.image = image return mesh def load_ply(filepath): import time + import bpy t = time.time() ply_name = bpy.path.display_name_from_filepath(filepath) @@ -421,7 +420,7 @@ def load_ply(filepath): bpy.context.view_layer.objects.active = obj obj.select_set(True) - print('\nSuccessfully imported %r in %.3f sec' % (filepath, time.time() - t)) + print("\nSuccessfully imported %r in %.3f sec" % (filepath, time.time() - t)) return {'FINISHED'} diff --git a/io_mesh_stl/__init__.py b/io_mesh_stl/__init__.py index cd81b228..18246d4d 100644 --- a/io_mesh_stl/__init__.py +++ b/io_mesh_stl/__init__.py @@ -23,9 +23,8 @@ bl_info = { "author": "Guillaume Bouchard (Guillaum)", "version": (1, 1, 3), "blender": (2, 81, 6), - "location": "File > Import-Export > Stl", + "location": "File > Import-Export", "description": "Import-Export STL files", - "warning": "", "wiki_url": "https://docs.blender.org/manual/en/latest/addons/io_mesh_stl.html", "support": 'OFFICIAL', "category": "Import-Export", @@ -53,75 +52,70 @@ if "bpy" in locals(): if "blender_utils" in locals(): importlib.reload(blender_utils) -import os - import bpy from bpy.props import ( - StringProperty, - BoolProperty, - CollectionProperty, - EnumProperty, - FloatProperty, - ) + StringProperty, + BoolProperty, + CollectionProperty, + EnumProperty, + FloatProperty, +) from bpy_extras.io_utils import ( - ImportHelper, - ExportHelper, - orientation_helper, - axis_conversion, - ) + ImportHelper, + ExportHelper, + orientation_helper, + axis_conversion, +) from bpy.types import ( - Operator, - OperatorFileListElement, - ) + Operator, + OperatorFileListElement, +) @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" + bl_description = "Load STL triangle mesh data" bl_options = {'UNDO'} filename_ext = ".stl" filter_glob: StringProperty( - default="*.stl", - options={'HIDDEN'}, - ) + default="*.stl", + options={'HIDDEN'}, + ) files: CollectionProperty( - name="File Path", - type=OperatorFileListElement, - ) + name="File Path", + type=OperatorFileListElement, + ) directory: StringProperty( - subtype='DIR_PATH', - ) - + subtype='DIR_PATH', + ) global_scale: FloatProperty( - name="Scale", - soft_min=0.001, soft_max=1000.0, - min=1e-6, max=1e6, - default=1.0, - ) - + name="Scale", + soft_min=0.001, soft_max=1000.0, + min=1e-6, max=1e6, + default=1.0, + ) use_scene_unit: BoolProperty( - name="Scene Unit", - description="Apply current scene's unit (as defined by unit scale) to imported data", - default=False, - ) - + name="Scene Unit", + description="Apply current scene's unit (as defined by unit scale) to imported data", + default=False, + ) use_facet_normal: BoolProperty( - name="Facet Normals", - description="Use (import) facet normals (note that this will still give flat shading)", - default=False, - ) + name="Facet Normals", + description="Use (import) facet normals (note that this will still give flat shading)", + default=False, + ) def execute(self, context): + import os + from mathutils import Matrix from . import stl_utils from . import blender_utils - from mathutils import Matrix - paths = [os.path.join(self.directory, name.name) - for name in self.files] + paths = [os.path.join(self.directory, name.name) for name in self.files] scene = context.scene @@ -130,9 +124,10 @@ class ImportSTL(Operator, ImportHelper): if scene.unit_settings.system != 'NONE' and self.use_scene_unit: global_scale /= scene.unit_settings.scale_length - global_matrix = axis_conversion(from_forward=self.axis_forward, - from_up=self.axis_up, - ).to_4x4() @ Matrix.Scale(global_scale, 4) + global_matrix = axis_conversion( + from_forward=self.axis_forward, + from_up=self.axis_up, + ).to_4x4() @ Matrix.Scale(global_scale, 4) if not paths: paths.append(self.filepath) @@ -209,64 +204,70 @@ class STL_PT_import_geometry(bpy.types.Panel): @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" + bl_description = """Save STL triangle mesh data""" filename_ext = ".stl" filter_glob: StringProperty(default="*.stl", options={'HIDDEN'}) use_selection: BoolProperty( - name="Selection Only", - description="Export selected objects only", - default=False, - ) + name="Selection Only", + description="Export selected objects only", + default=False, + ) global_scale: FloatProperty( - name="Scale", - min=0.01, max=1000.0, - default=1.0, - ) - + name="Scale", + min=0.01, max=1000.0, + default=1.0, + ) use_scene_unit: BoolProperty( - name="Scene Unit", - description="Apply current scene's unit (as defined by unit scale) to exported data", - default=False, - ) + name="Scene Unit", + description="Apply current scene's unit (as defined by unit scale) to exported data", + default=False, + ) ascii: BoolProperty( - name="Ascii", - description="Save the file in ASCII file format", - default=False, - ) + name="Ascii", + description="Save the file in ASCII file format", + default=False, + ) use_mesh_modifiers: BoolProperty( - name="Apply Modifiers", - description="Apply the modifiers before saving", - default=True, - ) + name="Apply Modifiers", + description="Apply the modifiers before saving", + default=True, + ) batch_mode: EnumProperty( - name="Batch Mode", - items=(('OFF', "Off", "All data in one file"), - ('OBJECT', "Object", "Each object as a file"), - )) + name="Batch Mode", + items=( + ('OFF', "Off", "All data in one file"), + ('OBJECT', "Object", "Each object as a file"), + ), + ) @property def check_extension(self): return self.batch_mode == 'OFF' def execute(self, context): - from . import stl_utils - from . import blender_utils + import os import itertools from mathutils import Matrix - keywords = self.as_keywords(ignore=("axis_forward", - "axis_up", - "use_selection", - "global_scale", - "check_existing", - "filter_glob", - "use_scene_unit", - "use_mesh_modifiers", - "batch_mode" - )) + from . import stl_utils + from . import blender_utils + + keywords = self.as_keywords( + ignore=( + "axis_forward", + "axis_up", + "use_selection", + "global_scale", + "check_existing", + "filter_glob", + "use_scene_unit", + "use_mesh_modifiers", + "batch_mode" + ), + ) scene = context.scene if self.use_selection: @@ -279,9 +280,10 @@ class ExportSTL(Operator, ExportHelper): if scene.unit_settings.system != 'NONE' and self.use_scene_unit: global_scale *= scene.unit_settings.scale_length - global_matrix = axis_conversion(to_forward=self.axis_forward, - to_up=self.axis_up, - ).to_4x4() @ Matrix.Scale(global_scale, 4) + global_matrix = axis_conversion( + to_forward=self.axis_forward, + to_up=self.axis_up, + ).to_4x4() @ Matrix.Scale(global_scale, 4) if self.batch_mode == 'OFF': faces = itertools.chain.from_iterable( @@ -424,6 +426,7 @@ classes = ( STL_PT_export_geometry, ) + def register(): for cls in classes: bpy.utils.register_class(cls) diff --git a/io_mesh_stl/blender_utils.py b/io_mesh_stl/blender_utils.py index d1b14cf6..8589e196 100644 --- a/io_mesh_stl/blender_utils.py +++ b/io_mesh_stl/blender_utils.py @@ -18,10 +18,6 @@ # <pep8 compliant> -import bpy -import array -from itertools import chain - def create_and_link_mesh(name, faces, face_nors, points, global_matrix): """ @@ -29,6 +25,10 @@ def create_and_link_mesh(name, faces, face_nors, points, global_matrix): *points* and *faces* and link it in the current scene. """ + import array + from itertools import chain + import bpy + mesh = bpy.data.meshes.new(name) mesh.from_pydata(points, [], faces) @@ -77,6 +77,8 @@ def faces_from_mesh(ob, global_matrix, use_mesh_modifiers=False): Split the quad into two triangles """ + import bpy + # get the editmode data ob.update_from_editmode() diff --git a/io_mesh_stl/stl_utils.py b/io_mesh_stl/stl_utils.py index 0c108e83..ee693375 100644 --- a/io_mesh_stl/stl_utils.py +++ b/io_mesh_stl/stl_utils.py @@ -26,14 +26,9 @@ Used as a blender script, it load all the stl files in the scene: blender --python stl_utils.py -- file1.stl file2.stl file3.stl ... """ -import os -import struct -import contextlib -import itertools -from mathutils.geometry import normal - # TODO: endien + class ListDict(dict): """ Set struct with order. @@ -88,6 +83,10 @@ def _is_ascii_file(data): represents a binary file. It can be a (very *RARE* in real life, but can easily be forged) ascii file. """ + + import os + import struct + # Skip header... data.seek(BINARY_HEADER) size = struct.unpack('<I', data.read(4))[0] @@ -106,6 +105,10 @@ def _is_ascii_file(data): def _binary_read(data): # Skip header... + + import os + import struct + data.seek(BINARY_HEADER) size = struct.unpack('<I', data.read(4))[0] @@ -164,6 +167,10 @@ def _ascii_read(data): def _binary_write(filepath, faces): + import struct + import itertools + from mathutils.geometry import normal + with open(filepath, 'wb') as data: fw = data.write # header @@ -191,6 +198,8 @@ def _binary_write(filepath, faces): def _ascii_write(filepath, faces): + from mathutils.geometry import normal + with open(filepath, 'w') as data: fw = data.write header = _header_version() @@ -206,10 +215,7 @@ def _ascii_write(filepath, faces): fw('endsolid %s\n' % header) -def write_stl(filepath="", - faces=(), - ascii=False, - ): +def write_stl(filepath="", faces=(), ascii=False): """ Write a stl file from faces, diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 2d44644b..8b7439e0 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -15,7 +15,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (1, 0, 5), + "version": (1, 1, 0), 'blender': (2, 81, 6), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', @@ -57,7 +57,8 @@ import bpy from bpy.props import (StringProperty, BoolProperty, EnumProperty, - IntProperty) + IntProperty, + CollectionProperty) from bpy.types import Operator from bpy_extras.io_utils import ImportHelper, ExportHelper @@ -256,6 +257,12 @@ class ExportGLTF2_Base: default=True ) + export_def_bones: BoolProperty( + name='Export Deformation bones only', + description='Export Deformation bones only (and needed bones for hierarchy)', + default=False + ) + export_current_frame: BoolProperty( name='Use Current Frame', description='Export the scene in the current animation frame', @@ -389,11 +396,16 @@ class ExportGLTF2_Base: if self.export_animations: export_settings['gltf_frame_range'] = self.export_frame_range export_settings['gltf_force_sampling'] = self.export_force_sampling + if self.export_force_sampling: + export_settings['gltf_def_bones'] = self.export_def_bones + else: + export_settings['gltf_def_bones'] = False export_settings['gltf_nla_strips'] = self.export_nla_strips else: export_settings['gltf_frame_range'] = False export_settings['gltf_move_keyframes'] = False export_settings['gltf_force_sampling'] = False + export_settings['gltf_def_bones'] = False export_settings['gltf_skins'] = self.export_skins if self.export_skins: export_settings['gltf_all_vertex_influences'] = self.export_all_influences @@ -642,6 +654,10 @@ class GLTF_PT_export_animation_export(bpy.types.Panel): layout.prop(operator, 'export_force_sampling') layout.prop(operator, 'export_nla_strips') + row = layout.row() + row.active = operator.export_force_sampling + row.prop(operator, 'export_def_bones') + class GLTF_PT_export_animation_shapekeys(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' @@ -730,6 +746,11 @@ class ImportGLTF2(Operator, ImportHelper): filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'}) + files: CollectionProperty( + name="File Path", + type=bpy.types.OperatorFileListElement, + ) + loglevel: IntProperty( name='Log Level', description="Log Level") @@ -758,14 +779,30 @@ class ImportGLTF2(Operator, ImportHelper): return self.import_gltf2(context) def import_gltf2(self, context): - import time - from .io.imp.gltf2_io_gltf import glTFImporter - from .blender.imp.gltf2_blender_gltf import BlenderGlTF + import os self.set_debug_log() import_settings = self.as_keywords() - self.gltf_importer = glTFImporter(self.filepath, import_settings) + if self.files: + # Multiple file import + ret = {'CANCELLED'} + dirname = os.path.dirname(self.filepath) + for file in self.files: + path = os.path.join(dirname, file.name) + if self.unit_import(path, import_settings) == {'FINISHED'}: + ret = {'FINISHED'} + return ret + else: + # Single file import + return self.unit_import(self.filepath, import_settings) + + def unit_import(self, filename, import_settings): + import time + from .io.imp.gltf2_io_gltf import glTFImporter + from .blender.imp.gltf2_blender_gltf import BlenderGlTF + + self.gltf_importer = glTFImporter(filename, import_settings) success, txt = self.gltf_importer.read() if not success: self.report({'ERROR'}, txt) diff --git a/io_scene_gltf2/blender/com/gltf2_blender_extras.py b/io_scene_gltf2/blender/com/gltf2_blender_extras.py new file mode 100644 index 00000000..0be942a0 --- /dev/null +++ b/io_scene_gltf2/blender/com/gltf2_blender_extras.py @@ -0,0 +1,91 @@ +# Copyright 2018-2019 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_json import is_json_convertible + + +# Custom properties, which are in most cases present and should not be imported/exported. +BLACK_LIST = ['cycles', 'cycles_visibility', 'cycles_curves', '_RNA_UI'] + + +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 = {} + + for custom_property in blender_element.keys(): + if custom_property in BLACK_LIST: + continue + + value = __to_json_compatible(blender_element[custom_property]) + + if value is not None: + extras[custom_property] = value + + if not extras: + return None + + return extras + + +def __to_json_compatible(value): + """Make a value (usually a custom property) compatible with json""" + + if isinstance(value, bpy.types.ID): + return value + + elif isinstance(value, str): + return value + + elif isinstance(value, (int, float)): + return value + + # for list classes + elif isinstance(value, list): + value = list(value) + # make sure contents are json-compatible too + for index in range(len(value)): + value[index] = __to_json_compatible(value[index]) + return value + + # for IDPropertyArray classes + elif hasattr(value, "to_list"): + value = value.to_list() + return value + + elif hasattr(value, "to_dict"): + value = value.to_dict() + if is_json_convertible(value): + return value + + return None + + +def set_extras(blender_element, extras, exclude=[]): + """Copy extras onto a Blender object.""" + if not extras or not isinstance(extras, dict): + return + + for custom_property, value in extras.items(): + if custom_property in BLACK_LIST: + continue + if custom_property in exclude: + continue + + blender_element[custom_property] = value + diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py index bce1d60f..c7758538 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py @@ -563,7 +563,10 @@ def extract_primitives(glTF, blender_mesh, blender_object, blender_vertex_groups # - if not blender_polygon.material_index in material_idx_to_primitives: + if export_settings['gltf_materials'] is False: + primitive = material_idx_to_primitives[0] + vertex_index_to_new_indices = material_map[0] + elif not blender_polygon.material_index in material_idx_to_primitives: primitive = material_idx_to_primitives[0] vertex_index_to_new_indices = material_map[0] else: @@ -605,6 +608,7 @@ def extract_primitives(glTF, blender_mesh, blender_object, blender_vertex_groups triangles = tessellate_polygon((polyline,)) for triangle in triangles: + for triangle_index in triangle: loop_index_list.append(blender_polygon.loop_indices[triangle_index]) else: @@ -961,203 +965,14 @@ def extract_primitives(glTF, blender_mesh, blender_object, blender_vertex_groups attributes[target_tangent_id].extend(target_tangents[morph_index]) # - # Add primitive plus split them if needed. + # Add non-empty primitives # - result_primitives = [] - - for material_idx, primitive in material_idx_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): - colors.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) - - # - - # NOTE: Values used by some graphics APIs as "primitive restart" values are disallowed. - # Specifically, the value 65535 (in UINT16) cannot be used as a vertex index. - # https://github.com/KhronosGroup/glTF/issues/1142 - # https://github.com/KhronosGroup/glTF/pull/1476/files - - range_indices = 65535 - - # - - 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_idx, - 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)) + result_primitives = [ + primitive + for primitive in material_idx_to_primitives.values() + if len(primitive[INDICES_ID]) != 0 + ] print_console('INFO', 'Primitives created: ' + str(len(result_primitives))) diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py index 2e4bfe31..f4fd70dd 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py @@ -19,7 +19,7 @@ from io_scene_gltf2.io.com.gltf2_io_debug import print_console 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 ..com.gltf2_blender_extras import generate_extras from io_scene_gltf2.blender.exp import gltf2_blender_export_keys @@ -97,6 +97,14 @@ def __gather_animations(blender_scene, export_settings): to_delete_idx.append(anim_idx) + # Merging extras + # Warning, some values can be overwritten if present in multiple merged animations + if animations[anim_idx].extras is not None: + for k in animations[anim_idx].extras.keys(): + if animations[base_animation_idx].extras is None: + animations[base_animation_idx].extras = {} + animations[base_animation_idx].extras[k] = animations[anim_idx].extras[k] + offset_sampler = len(animations[base_animation_idx].samplers) for sampler in animations[anim_idx].samplers: animations[base_animation_idx].samplers.append(sampler) @@ -124,6 +132,6 @@ def __gather_animations(blender_scene, export_settings): 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 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 index 04028d20..fa0f9976 100755 --- 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 @@ -19,7 +19,7 @@ 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 - +from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins @cached def gather_animation_channel_target(channels: typing.Tuple[bpy.types.FCurve], @@ -66,7 +66,12 @@ def __gather_node(channels: typing.Tuple[bpy.types.FCurve], 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) + if export_settings["gltf_def_bones"] is False: + return gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings) + else: + bones, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object) + if blender_bone.name in [b.name for b in bones]: + return gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings) return gltf2_blender_gather_nodes.gather_node(blender_object, None, export_settings) 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 index edee0971..611cd74a 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py @@ -22,6 +22,7 @@ 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 from io_scene_gltf2.blender.exp import gltf2_blender_get +from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins @cached @@ -58,7 +59,14 @@ def gather_animation_channels(blender_action: bpy.types.Action, return [] # Then bake all bones - for bone in blender_object.data.bones: + bones_to_be_animated = [] + if export_settings["gltf_def_bones"] is False: + bones_to_be_animated = blender_object.data.bones + else: + bones_to_be_animated, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object) + bones_to_be_animated = [blender_object.pose.bones[b.name] for b in bones_to_be_animated] + + for bone in bones_to_be_animated: for p in ["location", "rotation_quaternion", "scale"]: channel = __gather_animation_channel( (), diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py index de205913..d55c20b1 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py @@ -18,6 +18,7 @@ import typing from io_scene_gltf2.io.com import gltf2_io from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_channels from io_scene_gltf2.io.com.gltf2_io_debug import print_console +from ..com.gltf2_blender_extras import generate_extras def gather_animations(blender_object: bpy.types.Object, @@ -139,6 +140,9 @@ def __gather_extras(blender_action: bpy.types.Action, blender_object: bpy.types.Object, export_settings ) -> typing.Any: + + if export_settings['gltf_extras']: + return generate_extras(blender_action) return None diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py index e6d82121..3cde0fcb 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cameras.py @@ -14,7 +14,7 @@ from . import gltf2_blender_export_keys from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached -from io_scene_gltf2.blender.exp import gltf2_blender_generate_extras +from ..com.gltf2_blender_extras import generate_extras from io_scene_gltf2.io.com import gltf2_io import bpy @@ -46,7 +46,7 @@ def __gather_extensions(blender_camera, export_settings): def __gather_extras(blender_camera, export_settings): if export_settings['gltf_extras']: - return gltf2_blender_generate_extras.generate_extras(blender_camera) + return generate_extras(blender_camera) return None diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py index 2059ea88..dce70c31 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py @@ -20,6 +20,7 @@ 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 +from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins @cached @@ -55,8 +56,15 @@ def gather_joint(blender_bone, export_settings): # traverse into children children = [] - for bone in blender_bone.children: - children.append(gather_joint(bone, export_settings)) + + if export_settings["gltf_def_bones"] is False: + for bone in blender_bone.children: + children.append(gather_joint(bone, export_settings)) + else: + _, children_, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_bone.id_data) + if blender_bone.name in children_.keys(): + for bone in children_[blender_bone.name]: + children.append(gather_joint(blender_bone.id_data.pose.bones[bone], export_settings)) # finally add to the joints array containing all the joints in the hierarchy return gltf2_io.Node( diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py index c87318d0..dbb59e21 100644 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py @@ -17,6 +17,7 @@ import math from typing import Optional, List, Dict, Any from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached +from ..com.gltf2_blender_extras import generate_extras from io_scene_gltf2.io.com import gltf2_io_lights_punctual from io_scene_gltf2.io.com import gltf2_io_debug @@ -112,6 +113,8 @@ def __gather_extensions(blender_lamp, export_settings) -> Optional[dict]: def __gather_extras(blender_lamp, export_settings) -> Optional[Any]: + if export_settings['gltf_extras']: + return generate_extras(blender_lamp) return None diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py index 3f4466f5..9b9a9abe 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py @@ -23,7 +23,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_material_occlusion_t from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree 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 ..com.gltf2_blender_extras import generate_extras from io_scene_gltf2.blender.exp import gltf2_blender_get @@ -136,7 +136,7 @@ def __gather_extensions(blender_material, export_settings): def __gather_extras(blender_material, export_settings): if export_settings['gltf_extras']: - return gltf2_blender_generate_extras.generate_extras(blender_material) + return generate_extras(blender_material) 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 index ca79ef33..3af26ced 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py @@ -18,7 +18,7 @@ from .gltf2_blender_export_keys import MORPH 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 +from ..com.gltf2_blender_extras import generate_extras from io_scene_gltf2.io.com.gltf2_io_debug import print_console @@ -76,7 +76,7 @@ def __gather_extras(blender_mesh: bpy.types.Mesh, extras = {} if export_settings['gltf_extras']: - extras = gltf2_blender_generate_extras.generate_extras(blender_mesh) or {} + extras = generate_extras(blender_mesh) or {} if export_settings[MORPH] and blender_mesh.shape_keys: morph_max = len(blender_mesh.shape_keys.key_blocks) - 1 diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py index 32745027..6ab77945 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py @@ -25,7 +25,7 @@ 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 ..com.gltf2_blender_extras import generate_extras from io_scene_gltf2.io.com import gltf2_io from io_scene_gltf2.io.com import gltf2_io_extensions @@ -135,7 +135,12 @@ def __gather_children(blender_object, blender_scene, export_settings): # blender bones if blender_object.type == "ARMATURE": root_joints = [] - for blender_bone in blender_object.pose.bones: + if export_settings["gltf_def_bones"] is False: + bones = blender_object.pose.bones + else: + bones, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object) + bones = [blender_object.pose.bones[b.name] for b in bones] + for blender_bone in bones: if not blender_bone.parent: joint = gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings) children.append(joint) @@ -214,7 +219,7 @@ def __gather_extensions(blender_object, export_settings): def __gather_extras(blender_object, export_settings): if export_settings['gltf_extras']: - return gltf2_blender_generate_extras.generate_extras(blender_object) + return generate_extras(blender_object) return None diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py index 59af7d72..18503fdd 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py @@ -67,11 +67,14 @@ def __gather_inverse_bind_matrices(blender_object, export_settings): 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))) - # build the hierarchy of nodes out of the bones - root_bones = [] - for blender_bone in blender_object.pose.bones: - if not blender_bone.parent: - root_bones.append(blender_bone) + if export_settings['gltf_def_bones'] is False: + # build the hierarchy of nodes out of the bones + root_bones = [] + for blender_bone in blender_object.pose.bones: + if not blender_bone.parent: + root_bones.append(blender_bone) + else: + _, children_, root_bones = get_bone_tree(None, blender_object) matrices = [] @@ -86,8 +89,13 @@ def __gather_inverse_bind_matrices(blender_object, export_settings): ).inverted() matrices.append(inverse_bind_matrix) - for child in bone.children: - __collect_matrices(child) + if export_settings['gltf_def_bones'] is False: + for child in bone.children: + __collect_matrices(child) + else: + if bone.name in children_.keys(): + for child in children_[bone.name]: + __collect_matrices(blender_object.pose.bones[child]) # start with the "root" bones and recurse into children, in the same ordering as the how joints are gathered for root_bone in root_bones: @@ -114,18 +122,28 @@ def __gather_inverse_bind_matrices(blender_object, export_settings): def __gather_joints(blender_object, export_settings): 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)) + if export_settings['gltf_def_bones'] is False: + # 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)) + else: + _, children_, root_joints = get_bone_tree(None, blender_object) + root_joints = [gltf2_blender_gather_joints.gather_joint(i, export_settings) for i in root_joints] # 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) + if export_settings['gltf_def_bones'] is False: + for child in node.children: + __collect_joints(child) + else: + if node.name in children_.keys(): + for child in children_[node.name]: + __collect_joints(gltf2_blender_gather_joints.gather_joint(blender_object.pose.bones[child], export_settings)) + for joint in root_joints: __collect_joints(joint) @@ -140,3 +158,30 @@ 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, blender_scene, export_settings) +@cached +def get_bone_tree(blender_dummy, blender_object): + + bones = [] + children = {} + root_bones = [] + + def get_parent(bone): + bones.append(bone.name) + if bone.parent is not None: + if bone.parent.name not in children.keys(): + children[bone.parent.name] = [] + children[bone.parent.name].append(bone.name) + get_parent(bone.parent) + else: + root_bones.append(bone.name) + + for bone in [b for b in blender_object.data.bones if b.use_deform is True]: + get_parent(bone) + + # remove duplicates + for k, v in children.items(): + children[k] = list(set(v)) + list_ = list(set(bones)) + root_ = list(set(root_bones)) + return [blender_object.data.bones[b] for b in list_], children, [blender_object.pose.bones[b] for b in root_] + diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_camera.py b/io_scene_gltf2/blender/imp/gltf2_blender_camera.py index d746c1b1..ec5f0ee4 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_camera.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_camera.py @@ -13,6 +13,7 @@ # limitations under the License. import bpy +from ..com.gltf2_blender_extras import set_extras class BlenderCamera(): @@ -29,6 +30,7 @@ class BlenderCamera(): pycamera.name = "Camera" cam = bpy.data.cameras.new(pycamera.name) + set_extras(cam, pycamera.extras) # Blender create a perspective camera by default if pycamera.type == "orthographic": diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_light.py b/io_scene_gltf2/blender/imp/gltf2_blender_light.py index 492c552e..6213091e 100644 --- a/io_scene_gltf2/blender/imp/gltf2_blender_light.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_light.py @@ -15,6 +15,8 @@ import bpy from math import pi +from ..com.gltf2_blender_extras import set_extras + class BlenderLight(): """Blender Light.""" @@ -45,6 +47,8 @@ class BlenderLight(): else: bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) + set_extras(obj.data, pylight.get('extras')) + return obj @staticmethod diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_material.py b/io_scene_gltf2/blender/imp/gltf2_blender_material.py index 55be1626..c5e3b51c 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_material.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_material.py @@ -13,6 +13,8 @@ # limitations under the License. import bpy + +from ..com.gltf2_blender_extras import set_extras from .gltf2_blender_pbrMetallicRoughness import BlenderPbr from .gltf2_blender_KHR_materials_pbrSpecularGlossiness import BlenderKHR_materials_pbrSpecularGlossiness from .gltf2_blender_KHR_materials_unlit import BlenderKHR_materials_unlit @@ -49,6 +51,8 @@ class BlenderMaterial(): mat = bpy.data.materials.new(name) pymaterial.blender_material[vertex_color] = mat.name + set_extras(mat, pymaterial.extras) + mat.use_backface_culling = (pymaterial.double_sided != True) ignore_map = False diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py index 1f7f7b66..eb92d9a3 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py @@ -16,6 +16,7 @@ import bpy import bmesh from mathutils import Vector +from ..com.gltf2_blender_extras import set_extras from .gltf2_blender_material import BlenderMaterial from .gltf2_blender_primitive import BlenderPrimitive from ...io.imp.gltf2_io_binary import BinaryData @@ -75,6 +76,8 @@ class BlenderMesh(): mesh.materials.append(bpy.data.materials[name_material]) mesh.update() + set_extras(mesh, pymesh.extras, exclude=['targetNames']) + pymesh.blender_name = mesh.name # Clear accessor cache after all primitives are done diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_node.py index 6a40aecc..a02514de 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_node.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_node.py @@ -13,6 +13,7 @@ # limitations under the License. import bpy +from ..com.gltf2_blender_extras import set_extras from .gltf2_blender_mesh import BlenderMesh from .gltf2_blender_camera import BlenderCamera from .gltf2_blender_skin import BlenderSkin @@ -76,6 +77,7 @@ class BlenderNode(): name = "Object_" + str(node_idx) obj = bpy.data.objects.new(name, mesh) + set_extras(obj, pynode.extras) obj.rotation_mode = 'QUATERNION' if gltf.blender_active_collection is not None: bpy.data.collections[gltf.blender_active_collection].objects.link(obj) @@ -104,6 +106,7 @@ class BlenderNode(): else: gltf.log.info("Blender create Camera node") obj = BlenderCamera.create(gltf, pynode.camera) + set_extras(obj, pynode.extras) BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent) # TODO default rotation of cameras ? pynode.blender_object = obj.name BlenderNode.set_parent(gltf, obj, parent) @@ -134,6 +137,7 @@ class BlenderNode(): if pynode.extensions is not None: if 'KHR_lights_punctual' in pynode.extensions.keys(): obj = BlenderLight.create(gltf, pynode.extensions['KHR_lights_punctual']['light']) + set_extras(obj, pynode.extras) obj.rotation_mode = 'QUATERNION' BlenderNode.set_transforms(gltf, node_idx, pynode, obj, parent, correction=True) pynode.blender_object = obj.name @@ -154,6 +158,7 @@ class BlenderNode(): else: gltf.log.info("Blender create Empty node") obj = bpy.data.objects.new("Node", None) + set_extras(obj, pynode.extras) obj.rotation_mode = 'QUATERNION' if gltf.blender_active_collection is not None: bpy.data.collections[gltf.blender_active_collection].objects.link(obj) diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_skin.py b/io_scene_gltf2/blender/imp/gltf2_blender_skin.py index 2758b348..9c8cd168 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_skin.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_skin.py @@ -17,7 +17,7 @@ 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 - +from ..com.gltf2_blender_extras import set_extras class BlenderSkin(): """Blender Skinning / Armature.""" @@ -130,10 +130,15 @@ class BlenderSkin(): 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 + # Custom prop on edit bone + set_extras(bone, pynode.extras) # set bind and pose transforms BlenderSkin.set_bone_transforms(gltf, skin_id, bone, node_id, parent) bpy.ops.object.mode_set(mode="OBJECT") + # Custom prop on pose bone + if pynode.blender_bone_name in obj.pose.bones: + set_extras(obj.pose.bones[pynode.blender_bone_name], pynode.extras) @staticmethod def create_vertex_groups(gltf, skin_id): diff --git a/io_scene_gltf2/io/imp/gltf2_io_binary.py b/io_scene_gltf2/io/imp/gltf2_io_binary.py index 4c5ea8f1..c72a893a 100755 --- a/io_scene_gltf2/io/imp/gltf2_io_binary.py +++ b/io_scene_gltf2/io/imp/gltf2_io_binary.py @@ -14,8 +14,6 @@ import struct import base64 -from os.path import dirname, join, isfile, basename -from urllib.parse import unquote class BinaryData(): @@ -27,23 +25,31 @@ class BinaryData(): 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] + data = BinaryData.get_buffer_view(gltf, accessor.buffer_view) # TODO initialize with 0 when not present! 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] + return data[accessor_offset:] + + @staticmethod + def get_buffer_view(gltf, buffer_view_idx): + """Get binary data for buffer view.""" + buffer_view = gltf.data.buffer_views[buffer_view_idx] + + if buffer_view.buffer in gltf.buffers.keys(): + buffer = gltf.buffers[buffer_view.buffer] + else: + # load buffer + gltf.load_buffer(buffer_view.buffer) + buffer = gltf.buffers[buffer_view.buffer] + + byte_offset = buffer_view.byte_offset + if byte_offset is None: + byte_offset = 0 + + return buffer[byte_offset:byte_offset + buffer_view.byte_length] @staticmethod def get_data_from_accessor(gltf, accessor_idx, cache=False): @@ -153,40 +159,17 @@ class BinaryData(): 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), unquote(pyimage.uri))): - with open(join(dirname(gltf.filename), unquote(pyimage.uri)), 'rb') as f_: - return f_.read(), basename(join(dirname(gltf.filename), unquote(pyimage.uri))) - else: - 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] + assert(not (pyimage.uri is not None and pyimage.buffer_view is not None)) - bufferview_offset = bufferView.byte_offset + if pyimage.uri is not None: + data, file_name = gltf.load_uri(pyimage.uri) + return data, file_name or image_name - if bufferview_offset is None: - bufferview_offset = 0 + elif pyimage.buffer_view is not None: + data = BinaryData.get_buffer_view(gltf, pyimage.buffer_view) + return data, image_name - return buffer[bufferview_offset:bufferview_offset + bufferView.byte_length], image_name + return None, None diff --git a/io_scene_gltf2/io/imp/gltf2_io_gltf.py b/io_scene_gltf2/io/imp/gltf2_io_gltf.py index 34f205f9..1a30f258 100755 --- a/io_scene_gltf2/io/imp/gltf2_io_gltf.py +++ b/io_scene_gltf2/io/imp/gltf2_io_gltf.py @@ -18,7 +18,8 @@ import logging import json import struct import base64 -from os.path import dirname, join, getsize, isfile +from os.path import dirname, join, isfile, basename +from urllib.parse import unquote class glTFImporter(): @@ -28,6 +29,7 @@ class glTFImporter(): """initialization.""" self.filename = filename self.import_settings = import_settings + self.glb_buffer = None self.buffers = {} self.accessor_cache = {} @@ -104,36 +106,34 @@ class glTFImporter(): return False, "This file is not a glTF/glb file" if self.version != 2: - return False, "glTF version doesn't match to 2" + return False, "GLB version %d unsupported" % self.version - if self.file_size != getsize(self.filename): - return False, "File size doesn't match" + if self.file_size != len(self.content): + return False, "Bad GLB: 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" + # JSON chunk is first + type_, len_, json_bytes, offset = self.load_chunk(offset) + if type_ != b"JSON": + return False, "Bad GLB: first chunk not JSON" + if len_ != len(json_bytes): + return False, "Bad GLB: length of json chunk doesn't match" try: - json_ = json.loads(str_json.decode('utf-8'), parse_constant=glTFImporter.bad_json_value) + json_str = str(json_bytes, encoding='utf-8') + json_ = json.loads(json_str, 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 + # BIN chunk is second (if it exists) + if offset < len(self.content): + type_, len_, data, offset = self.load_chunk(offset) + if type_ == b"BIN\0": + if len_ != len(data): + return False, "Bad GLB: length of BIN chunk doesn't match" + self.glb_buffer = data - self.content = None return True, None def load_chunk(self, offset): @@ -153,25 +153,25 @@ class glTFImporter(): # Check if file is gltf or glb with open(self.filename, 'rb') as f: - self.content = f.read() + self.content = memoryview(f.read()) self.is_glb_format = self.content[:4] == b'glTF' # glTF file if not self.is_glb_format: + content = str(self.content, encoding='utf-8') 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] + 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() + self.content = None return success, txt def is_node_joint(self, node_idx): @@ -190,14 +190,31 @@ class glTFImporter(): 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() + data, _file_name = self.load_uri(buffer.uri) + if data is not None: + self.buffers[buffer_idx] = data + + else: + # GLB-stored buffer + if buffer_idx == 0 and self.glb_buffer is not None: + self.buffers[buffer_idx] = self.glb_buffer + + def load_uri(self, uri): + """Loads a URI. + Returns the data and the filename of the resource, if there is one. + """ + sep = ';base64,' + if uri.startswith('data:'): + idx = uri.find(sep) + if idx != -1: + data = uri[idx + len(sep):] + return memoryview(base64.b64decode(data)), None + + path = join(dirname(self.filename), unquote(uri)) + try: + with open(path, 'rb') as f_: + return memoryview(f_.read()), basename(path) + except Exception: + self.log.error("Couldn't read file: " + path) + return None, None diff --git a/measureit/__init__.py b/measureit/__init__.py index d7e21e36..5f80de87 100644 --- a/measureit/__init__.py +++ b/measureit/__init__.py @@ -32,8 +32,8 @@ bl_info = { "version": (1, 8, 1), "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", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons" + "/3d_view/measureit.html", "category": "3D View" } diff --git a/mesh_bsurfaces.py b/mesh_bsurfaces.py index 9e9ae09d..d896d71f 100644 --- a/mesh_bsurfaces.py +++ b/mesh_bsurfaces.py @@ -20,11 +20,12 @@ bl_info = { "name": "Bsurfaces GPL Edition", "author": "Eclectiel, Spivak Vladimir(cwolf3d)", - "version": (1, 7, 1), + "version": (1, 7, 5), "blender": (2, 80, 0), "location": "View3D EditMode > Sidebar > Edit Tab", "description": "Modeling and retopology tool", - "wiki_url": "https://wiki.blender.org/index.php/Dev:Ref/Release_Notes/2.64/Bsurfaces_1.5", + "wiki_url": "https://docs.blender.org/manual/nb/dev/addons/" + "mesh/bsurfaces.html", "category": "Mesh", } @@ -62,9 +63,11 @@ from bpy.types import ( # ---------------------------- # GLOBAL -global_color = [1.0, 0.0, 0.0, 1.0] +global_color = [1.0, 0.0, 0.0, 0.3] global_offset = 0.01 global_in_front = False +global_shade_smooth = False +global_show_wire = True global_mesh_object = "" global_gpencil_object = "" global_curve_object = "" @@ -92,6 +95,8 @@ class VIEW3D_PT_tools_SURFSK_mesh(Panel): col.prop(scn, "SURFSK_mesh_color") col.prop(scn, "SURFSK_Shrinkwrap_offset") col.prop(scn, "SURFSK_in_front") + col.prop(scn, "SURFSK_shade_smooth") + col.prop(scn, "SURFSK_show_wire") col.label(text="Guide strokes:") col.row().prop(scn, "SURFSK_guide", expand=True) @@ -101,14 +106,26 @@ class VIEW3D_PT_tools_SURFSK_mesh(Panel): if scn.SURFSK_guide == 'Curve': col.prop(scn, "SURFSK_curve", text="") col.separator() - props = col.operator("gpencil.surfsk_add_surface", text="Add Surface") + col.separator() + props = col.operator("gpencil.surfsk_add_surface", text="Add Surface") col.operator("gpencil.surfsk_edit_surface", text="Edit Surface") + + col.separator() if scn.SURFSK_guide == 'GPencil': col.operator("gpencil.surfsk_add_strokes", text="Add Strokes") col.operator("gpencil.surfsk_edit_strokes", text="Edit Strokes") + col.separator() + col.operator("gpencil.surfsk_strokes_to_curves", text="Strokes to curves") + if scn.SURFSK_guide == 'Annotation': col.operator("gpencil.surfsk_add_annotation", text="Add Annotation") + col.separator() + col.operator("gpencil.surfsk_annotations_to_curves", text="Annotation to curves") + + if scn.SURFSK_guide == 'Curve': + col.operator("gpencil.surfsk_edit_curve", text="Edit curve") + col.separator() col.label(text="Initial settings:") col.prop(scn, "SURFSK_edges_U") @@ -172,11 +189,6 @@ def get_strokes_type(context): strokes_type = "GP_STROKES" except: strokes_type = "NO_STROKES" - - # Check if they are mesh - global global_mesh_object - main_object = bpy.data.objects[global_mesh_object] - total_vert_sel = len([v for v in main_object.data.vertices if v.select]) # Check if they are curves, if there aren't grease pencil strokes if context.scene.bsurfaces.SURFSK_guide == 'Curve': @@ -198,16 +210,23 @@ def get_strokes_type(context): except: strokes_type = "NO_STROKES" - # Check if there is a single stroke without any selection in the object - if strokes_num == 1 and total_vert_sel == 0: - if strokes_type == "EXTERNAL_CURVE": - strokes_type = "SINGLE_CURVE_STROKE_NO_SELECTION" - elif strokes_type == "GP_STROKES": - strokes_type = "SINGLE_GP_STROKE_NO_SELECTION" - - if strokes_num == 0 and total_vert_sel > 0: - strokes_type = "SELECTION_ALONE" - + # Check if they are mesh + try: + global global_mesh_object + self.main_object = bpy.data.objects[global_mesh_object] + total_vert_sel = len([v for v in self.main_object.data.vertices if v.select]) + + # Check if there is a single stroke without any selection in the object + if strokes_num == 1 and total_vert_sel == 0: + if strokes_type == "EXTERNAL_CURVE": + strokes_type = "SINGLE_CURVE_STROKE_NO_SELECTION" + elif strokes_type == "GP_STROKES": + strokes_type = "SINGLE_GP_STROKE_NO_SELECTION" + + if strokes_num == 0 and total_vert_sel > 0: + strokes_type = "SELECTION_ALONE" + except: + pass return strokes_type @@ -1042,6 +1061,8 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT') bpy.ops.mesh.normals_make_consistent(inside=False) bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + + self.update() return num_faces_created @@ -1383,6 +1404,8 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): for m_idx in range(len(self.main_object.modifiers)): self.main_object.modifiers[m_idx].show_viewport = self.modifiers_prev_viewport_state[m_idx] + self.update() + return # Part of the Crosshatch process that is repeated when the operator is tweaked @@ -1665,7 +1688,7 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): # on the original verts, and get them selected crosshatch_verts_to_merge = [] if self.automatic_join: - for i in range(len(ob_surface.data.vertices)): + for i in range(len(ob_surface.data.vertices)-1): # Calculate the distance from each of the connected verts to the actual vert, # and compare it with the distance they would have if joined. # If they don't change much, that vert can be joined @@ -1753,6 +1776,8 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): for m_idx in range(len(self.main_object.modifiers)): self.main_object.modifiers[m_idx].show_viewport = self.modifiers_prev_viewport_state[m_idx] + self.update() + return {'FINISHED'} def rectangular_surface(self, context): @@ -3054,6 +3079,8 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): bpy.ops.mesh.normals_make_consistent('INVOKE_REGION_WIN', inside=False) bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT') + self.update() + return{'FINISHED'} def update(self): @@ -3073,25 +3100,44 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): else: self.main_object.data.materials.append(material) bpy.context.scene.bsurfaces.SURFSK_mesh_color = global_color + except: + pass + try: global global_in_front self.main_object.show_in_front = global_in_front bpy.context.scene.bsurfaces.SURFSK_in_front = global_in_front except: pass + + try: + global global_show_wire + self.main_object.show_wire = global_show_wire + bpy.context.scene.bsurfaces.SURFSK_show_wire = global_show_wire + except: + pass + + try: + global global_shade_smooth + if global_shade_smooth: + bpy.ops.object.shade_smooth() + else: + bpy.ops.object.shade_flat() + bpy.context.scene.bsurfaces.SURFSK_shade_smooth = global_shade_smooth + except: + pass return{'FINISHED'} def execute(self, context): - bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') - - global global_mesh_object - - bsurfaces_props = bpy.context.scene.bsurfaces - self.main_object = bpy.data.objects[global_mesh_object] + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') try: + global global_mesh_object + bsurfaces_props = bpy.context.scene.bsurfaces + self.main_object = bpy.data.objects[global_mesh_object] self.main_object.select_set(True) except: self.report({'WARNING'}, "Specify the name of the object with retopology") @@ -3143,16 +3189,20 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): strokes_for_rectangular_surface = True strokes_for_crosshatch = False - bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') - self.main_object.select_set(True) - bpy.context.view_layer.objects.active = self.main_object - bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') if strokes_for_rectangular_surface: self.rectangular_surface(context) elif strokes_for_crosshatch: - self.crosshatch_surface_execute() + self.crosshatch_surface_execute(context) + + #Set Shade smooth to new polygons + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + global global_shade_smooth + if global_shade_smooth: + bpy.ops.object.shade_smooth() + else: + bpy.ops.object.shade_flat() # Delete main splines bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') @@ -3205,7 +3255,8 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): def invoke(self, context, event): - bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') bsurfaces_props = bpy.context.scene.bsurfaces self.cyclic_cross = bsurfaces_props.SURFSK_cyclic_cross @@ -3248,22 +3299,12 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): global global_gpencil_object gp = bpy.data.objects[global_gpencil_object] self.original_curve = conver_gpencil_to_curve(self, context, gp, 'GPensil') - gplayer_prefix_translated = bpy.app.translations.pgettext_data('GP_Layer') - for ob in bpy.context.selected_objects: - if ob != bpy.context.view_layer.objects.active and \ - ob.name.startswith((gplayer_prefix_translated, 'GP_Layer')): - self.original_curve = ob self.using_external_curves = False elif self.strokes_type == "GP_ANNOTATION": # Convert grease pencil strokes to curve gp = bpy.data.grease_pencils["Annotations"] self.original_curve = conver_gpencil_to_curve(self, context, gp, 'Annotation') - gplayer_prefix_translated = bpy.app.translations.pgettext_data('GP_Layer') - for ob in bpy.context.selected_objects: - if ob != bpy.context.view_layer.objects.active and \ - ob.name.startswith((gplayer_prefix_translated, 'GP_Layer')): - self.original_curve = ob self.using_external_curves = False elif self.strokes_type == "EXTERNAL_CURVE": @@ -3271,19 +3312,12 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): self.original_curve = bpy.data.objects[global_curve_object] self.using_external_curves = True - bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') - # Make sure there are no objects left from erroneous # executions of this operator, with the reserved names used here for o in bpy.data.objects: if o.name.find("SURFSKIO_") != -1: bpy.ops.object.delete({"selected_objects": [o]}) - try: - self.original_curve.select_set(True) - except: - self.report({'WARNING'}, "Specify the name of the object with curve") - return{"CANCELLED"} bpy.context.view_layer.objects.active = self.original_curve bpy.ops.object.duplicate('INVOKE_REGION_WIN') @@ -3422,32 +3456,28 @@ class GPENCIL_OT_SURFSK_add_surface(Operator): # Delete temporary strokes curve object bpy.ops.object.delete({"selected_objects": [self.temporary_curve]}) - # If "Keep strokes" option is not active, delete original strokes curve object - if not self.stopping_errors or self.is_crosshatch: - bpy.ops.object.delete({"selected_objects": [self.original_curve]}) - - # Delete grease pencil strokes - if self.strokes_type == "GP_STROKES" and not self.stopping_errors: - try: - bpy.context.scene.bsurfaces.SURFSK_gpencil.data.layers.active.clear() - except: - pass - - # Delete annotation strokes - if self.strokes_type == "GP_ANNOTATION" and not self.stopping_errors: - try: - bpy.data.grease_pencils[0].layers.active.clear() - except: - pass - - bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') - self.main_object.select_set(True) - bpy.context.view_layer.objects.active = self.main_object - # Set again since "execute()" will turn it again to its initial value self.execute(context) if not self.stopping_errors: + # Delete grease pencil strokes + if self.strokes_type == "GP_STROKES": + try: + bpy.context.scene.bsurfaces.SURFSK_gpencil.data.layers.active.clear() + except: + pass + + # Delete annotation strokes + elif self.strokes_type == "GP_ANNOTATION": + try: + bpy.data.grease_pencils[0].layers.active.clear() + except: + pass + + bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') + bpy.ops.object.delete({"selected_objects": [self.original_curve]}) + bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') + return {"FINISHED"} else: return{"CANCELLED"} @@ -3495,6 +3525,7 @@ class GPENCIL_OT_SURFSK_init(Operator): bl_idname = "gpencil.surfsk_init" bl_label = "Bsurfaces initialize" bl_description = "Bsurfaces initialize" + bl_options = {'REGISTER', 'UNDO'} active_object: PointerProperty(type=bpy.types.Object) @@ -3502,11 +3533,14 @@ class GPENCIL_OT_SURFSK_init(Operator): bs = bpy.context.scene.bsurfaces - bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') global global_color global global_offset global global_in_front + global global_show_wire + global global_shade_smooth global global_mesh_object global global_gpencil_object @@ -3515,12 +3549,23 @@ class GPENCIL_OT_SURFSK_init(Operator): mesh = bpy.data.meshes.new('BSurfaceMesh') mesh_object = object_utils.object_data_add(context, mesh) mesh_object.select_set(True) + bpy.context.view_layer.objects.active = mesh_object + mesh_object.show_all_edges = True global_in_front = bpy.context.scene.bsurfaces.SURFSK_in_front mesh_object.show_in_front = global_in_front mesh_object.display_type = 'SOLID' mesh_object.show_wire = True - bpy.context.view_layer.objects.active = mesh_object + + global_shade_smooth = bpy.context.scene.bsurfaces.SURFSK_shade_smooth + if global_shade_smooth: + bpy.ops.object.shade_smooth() + else: + bpy.ops.object.shade_flat() + + global_show_wire = bpy.context.scene.bsurfaces.SURFSK_show_wire + mesh_object.show_wire = global_show_wire + global_color = bpy.context.scene.bsurfaces.SURFSK_mesh_color material = makeMaterial("BSurfaceMesh", global_color) mesh_object.data.materials.append(material) @@ -3565,11 +3610,6 @@ class GPENCIL_OT_SURFSK_init(Operator): if context.scene.bsurfaces.SURFSK_guide == 'Annotation': bpy.ops.wm.tool_set_by_id(name="builtin.annotate") bpy.context.scene.tool_settings.annotation_stroke_placement_view3d = 'SURFACE' - - if context.scene.bsurfaces.SURFSK_guide == 'Curve': - bpy.data.objects[global_mesh_object].data.vertices.add(1) - - return {"FINISHED"} def invoke(self, context, event): if bpy.context.active_object: @@ -3586,7 +3626,8 @@ class GPENCIL_OT_SURFSK_init(Operator): class GPENCIL_OT_SURFSK_add_modifiers(Operator): bl_idname = "gpencil.surfsk_add_modifiers" bl_label = "Add Mirror and others modifiers" - bl_description = "Add modifiers: Mirror, Shrinkwrap, Subdivision, Solidify " + bl_description = "Add modifiers: Mirror, Shrinkwrap, Subdivision, Solidify" + bl_options = {'REGISTER', 'UNDO'} active_object: PointerProperty(type=bpy.types.Object) @@ -3594,7 +3635,8 @@ class GPENCIL_OT_SURFSK_add_modifiers(Operator): bs = bpy.context.scene.bsurfaces - bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') if bs.SURFSK_mesh == None: self.report({'ERROR_INVALID_INPUT'}, "Please select Mesh of BSurface or click Initialize") @@ -3667,8 +3709,12 @@ class GPENCIL_OT_SURFSK_edit_surface(Operator): bl_idname = "gpencil.surfsk_edit_surface" bl_label = "Bsurfaces edit surface" bl_description = "Edit surface mesh" + bl_options = {'REGISTER', 'UNDO'} def execute(self, context): + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') bpy.context.scene.bsurfaces.SURFSK_mesh.select_set(True) bpy.context.view_layer.objects.active = bpy.context.scene.bsurfaces.SURFSK_mesh bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT') @@ -3684,38 +3730,26 @@ class GPENCIL_OT_SURFSK_edit_surface(Operator): self.execute(context) return {"FINISHED"} - + # ---------------------------- # Add strokes operator class GPENCIL_OT_SURFSK_add_strokes(Operator): bl_idname = "gpencil.surfsk_add_strokes" bl_label = "Bsurfaces add strokes" bl_description = "Add the grease pencil strokes" + bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - # Determine the type of the strokes - self.strokes_type = get_strokes_type(context) - # Check if strokes are grease pencil strokes or a curves object - selected_objs = bpy.context.selected_objects - if self.strokes_type == "EXTERNAL_CURVE" or self.strokes_type == "SINGLE_CURVE_STROKE_NO_SELECTION": - for ob in selected_objs: - if ob != bpy.context.view_layer.objects.active: - curve_ob = ob - - bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') - bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') - curve_ob.select_set(True) - bpy.context.view_layer.objects.active = curve_ob + bpy.context.scene.bsurfaces.SURFSK_gpencil.select_set(True) + bpy.context.view_layer.objects.active = bpy.context.scene.bsurfaces.SURFSK_gpencil + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='PAINT_GPENCIL') + bpy.ops.wm.tool_set_by_id(name="builtin_brush.Draw") - bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') - else: - bpy.context.scene.bsurfaces.SURFSK_gpencil.select_set(True) - bpy.context.view_layer.objects.active = bpy.context.scene.bsurfaces.SURFSK_gpencil - bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='PAINT_GPENCIL') - bpy.ops.wm.tool_set_by_id(name="builtin_brush.Draw") - - return{"FINISHED"} + return{"FINISHED"} def invoke(self, context, event): try: @@ -3733,69 +3767,112 @@ class GPENCIL_OT_SURFSK_add_strokes(Operator): class GPENCIL_OT_SURFSK_edit_strokes(Operator): bl_idname = "gpencil.surfsk_edit_strokes" bl_label = "Bsurfaces edit strokes" - bl_description = "Edit the grease pencil strokes or curves used" + bl_description = "Edit the grease pencil strokes" + bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - # Determine the type of the strokes - self.strokes_type = get_strokes_type(context) - # Check if strokes are grease pencil strokes or a curves object - selected_objs = bpy.context.selected_objects - if self.strokes_type == "EXTERNAL_CURVE" or self.strokes_type == "SINGLE_CURVE_STROKE_NO_SELECTION": - for ob in selected_objs: - if ob != bpy.context.view_layer.objects.active: - curve_ob = ob + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') + + gpencil_object = bpy.context.scene.bsurfaces.SURFSK_gpencil + + gpencil_object.select_set(True) + bpy.context.view_layer.objects.active = gpencil_object + + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT_GPENCIL') + try: + bpy.ops.gpencil.select_all(action='SELECT') + except: + pass - bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') + def invoke(self, context, event): + try: + bpy.context.scene.bsurfaces.SURFSK_gpencil.select_set(True) + except: + self.report({'WARNING'}, "Specify the name of the object with strokes") + return{"CANCELLED"} - bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') - curve_ob.select_set(True) - bpy.context.view_layer.objects.active = curve_ob + self.execute(context) - bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') - elif self.strokes_type == "GP_STROKES" or self.strokes_type == "SINGLE_GP_STROKE_NO_SELECTION": - # Convert grease pencil strokes to curve - bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') - #bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE', use_link_strokes=False) - gp = bpy.context.scene.bsurfaces.SURFSK_gpencil - conver_gpencil_to_curve(self, context, gp, 'GPensil') - for ob in bpy.context.selected_objects: - if ob != bpy.context.view_layer.objects.active and ob.name.startswith("GP_Layer"): - ob_gp_strokes = ob + return {"FINISHED"} - ob_gp_strokes = bpy.context.object +# ---------------------------- +# Convert annotation to curves operator +class GPENCIL_OT_SURFSK_annotation_to_curves(Operator): + bl_idname = "gpencil.surfsk_annotations_to_curves" + bl_label = "Convert annotation to curves" + bl_description = "Convert annotation to curves for editing" + bl_options = {'REGISTER', 'UNDO'} - # Delete grease pencil strokes + def execute(self, context): + + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + + # Convert annotation to curve + curve = conver_gpencil_to_curve(self, context, None, 'Annotation') + + if curve != None: + # Delete annotation strokes try: - bpy.context.scene.bsurfaces.SURFSK_gpencil.data.layers.active.clear() + bpy.data.grease_pencils[0].layers.active.clear() except: pass # Clean up curves - bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') - ob_gp_strokes.select_set(True) - bpy.context.view_layer.objects.active = ob_gp_strokes - - curve_crv = ob_gp_strokes.data - bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') - bpy.ops.curve.spline_type_set('INVOKE_REGION_WIN', type="BEZIER") - bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type="AUTOMATIC") - #curve_crv.show_handles = False - #curve_crv.show_normal_face = False + curve.select_set(True) + bpy.context.view_layer.objects.active = curve + + bpy.ops.wm.tool_set_by_id(name="builtin.select_box") + + return {"FINISHED"} - elif self.strokes_type == "EXTERNAL_NO_CURVE": - self.report({'WARNING'}, "The secondary object is not a Curve.") + def invoke(self, context, event): + try: + strokes = bpy.data.grease_pencils[0].layers.active.active_frame.strokes + + strokes_num = len(strokes) + except: + self.report({'WARNING'}, "Not active annotation") return{"CANCELLED"} - elif self.strokes_type == "MORE_THAN_ONE_EXTERNAL": - self.report({'WARNING'}, "There shouldn't be more than one secondary object selected.") - return{"CANCELLED"} + self.execute(context) - elif self.strokes_type == "NO_STROKES" or self.strokes_type == "SELECTION_ALONE": - self.report({'WARNING'}, "There aren't any strokes attached to the object") - return{"CANCELLED"} + return {"FINISHED"} - else: - return{"CANCELLED"} +# ---------------------------- +# Convert strokes to curves operator +class GPENCIL_OT_SURFSK_strokes_to_curves(Operator): + bl_idname = "gpencil.surfsk_strokes_to_curves" + bl_label = "Convert strokes to curves" + bl_description = "Convert grease pencil strokes to curves for editing" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + + # Convert grease pencil strokes to curve + gp = bpy.context.scene.bsurfaces.SURFSK_gpencil + curve = conver_gpencil_to_curve(self, context, gp, 'GPensil') + + if curve != None: + # Delete grease pencil strokes + try: + bpy.context.scene.bsurfaces.SURFSK_gpencil.data.layers.active.clear() + except: + pass + + # Clean up curves + + curve.select_set(True) + bpy.context.view_layer.objects.active = curve + + bpy.ops.wm.tool_set_by_id(name="builtin.select_box") + + return {"FINISHED"} def invoke(self, context, event): try: @@ -3814,6 +3891,7 @@ class GPENCIL_OT_SURFSK_add_annotation(Operator): bl_idname = "gpencil.surfsk_add_annotation" bl_label = "Bsurfaces add annotation" bl_description = "Add annotation" + bl_options = {'REGISTER', 'UNDO'} def execute(self, context): bpy.ops.wm.tool_set_by_id(name="builtin.annotate") @@ -3826,6 +3904,34 @@ class GPENCIL_OT_SURFSK_add_annotation(Operator): self.execute(context) return {"FINISHED"} + + +# ---------------------------- +# Edit curve operator +class GPENCIL_OT_SURFSK_edit_curve(Operator): + bl_idname = "gpencil.surfsk_edit_curve" + bl_label = "Bsurfaces edit curve" + bl_description = "Edit curve" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') + bpy.context.scene.bsurfaces.SURFSK_curve.select_set(True) + bpy.context.view_layer.objects.active = bpy.context.scene.bsurfaces.SURFSK_curve + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT') + + def invoke(self, context, event): + try: + bpy.context.scene.bsurfaces.SURFSK_curve.select_set(True) + except: + self.report({'WARNING'}, "Specify the name of the object with curve") + return{"CANCELLED"} + + self.execute(context) + + return {"FINISHED"} # ---------------------------- # Reorder splines @@ -4204,41 +4310,51 @@ panels = ( def conver_gpencil_to_curve(self, context, pencil, type): - newCurve = bpy.data.curves.new('gpencil_curve', type='CURVE') + newCurve = bpy.data.curves.new(type + '_curve', type='CURVE') newCurve.dimensions = '3D' CurveObject = object_utils.object_data_add(context, newCurve) + error = False if type == 'GPensil': - strokes = pencil.data.layers.active.active_frame.strokes + try: + strokes = pencil.data.layers.active.active_frame.strokes + except: + error = True CurveObject.location = pencil.location CurveObject.rotation_euler = pencil.rotation_euler CurveObject.scale = pencil.scale elif type == 'Annotation': grease_pencil = bpy.data.grease_pencils[0] - strokes = grease_pencil.layers.active.active_frame.strokes + try: + strokes = grease_pencil.layers.active.active_frame.strokes + except: + error = True CurveObject.location = (0.0, 0.0, 0.0) CurveObject.rotation_euler = (0.0, 0.0, 0.0) CurveObject.scale = (1.0, 1.0, 1.0) - for i, stroke in enumerate(strokes): - stroke_points = strokes[i].points - data_list = [ (point.co.x, point.co.y, point.co.z) - for point in stroke_points ] - points_to_add = len(data_list)-1 + if not error: + for i, stroke in enumerate(strokes): + stroke_points = strokes[i].points + data_list = [ (point.co.x, point.co.y, point.co.z) + for point in stroke_points ] + points_to_add = len(data_list)-1 - flat_list = [] - for point in data_list: - flat_list.extend(point) + flat_list = [] + for point in data_list: + flat_list.extend(point) - spline = newCurve.splines.new(type='BEZIER') - spline.bezier_points.add(points_to_add) - spline.bezier_points.foreach_set("co", flat_list) + spline = newCurve.splines.new(type='BEZIER') + spline.bezier_points.add(points_to_add) + spline.bezier_points.foreach_set("co", flat_list) - for point in spline.bezier_points: - point.handle_left_type="AUTO" - point.handle_right_type="AUTO" + for point in spline.bezier_points: + point.handle_left_type="AUTO" + point.handle_right_type="AUTO" - return CurveObject + return CurveObject + else: + return None def update_panel(self, context): @@ -4341,6 +4457,41 @@ def update_in_front(self, context): bpy.data.objects[global_mesh_object].show_in_front = global_in_front except Exception as e: print("Select mesh object") + +def update_show_wire(self, context): + try: + global global_show_wire + global_show_wire = bpy.context.scene.bsurfaces.SURFSK_show_wire + global global_mesh_object + bpy.data.objects[global_mesh_object].show_wire = global_show_wire + except Exception as e: + print("Select mesh object") + +def update_shade_smooth(self, context): + try: + global global_shade_smooth + global_shade_smooth = bpy.context.scene.bsurfaces.SURFSK_shade_smooth + + contex_mode = bpy.context.mode + + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT') + + bpy.ops.object.select_all(action='DESELECT') + global global_mesh_object + global_mesh_object = bpy.context.scene.bsurfaces.SURFSK_mesh.name + bpy.data.objects[global_mesh_object].select_set(True) + + if global_shade_smooth: + bpy.ops.object.shade_smooth() + else: + bpy.ops.object.shade_flat() + + if contex_mode == "EDIT_MESH": + bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') + + except Exception as e: + print("Select mesh object") class BsurfPreferences(AddonPreferences): @@ -4441,7 +4592,7 @@ class BsurfacesProps(PropertyGroup): ) SURFSK_mesh_color: FloatVectorProperty( name="Mesh color", - default=(1.0, 0.0, 0.0, 1.0), + default=(1.0, 0.0, 0.0, 0.3), size=4, subtype="COLOR", min=0, @@ -4462,6 +4613,18 @@ class BsurfacesProps(PropertyGroup): default=False, update=update_in_front, ) + SURFSK_show_wire: BoolProperty( + name="Show wire", + description="Add the object’s wireframe over solid drawing", + default=False, + update=update_show_wire, + ) + SURFSK_shade_smooth: BoolProperty( + name="Shade smooth", + description="Render and display faces smooth, using interpolated Vertex Normals", + default=False, + update=update_shade_smooth, + ) classes = ( GPENCIL_OT_SURFSK_init, @@ -4470,7 +4633,10 @@ classes = ( GPENCIL_OT_SURFSK_edit_surface, GPENCIL_OT_SURFSK_add_strokes, GPENCIL_OT_SURFSK_edit_strokes, + GPENCIL_OT_SURFSK_strokes_to_curves, + GPENCIL_OT_SURFSK_annotation_to_curves, GPENCIL_OT_SURFSK_add_annotation, + GPENCIL_OT_SURFSK_edit_curve, CURVE_OT_SURFSK_reorder_splines, CURVE_OT_SURFSK_first_points, BsurfPreferences, diff --git a/mesh_looptools.py b/mesh_looptools.py index 41c1be0a..90c914cd 100644 --- a/mesh_looptools.py +++ b/mesh_looptools.py @@ -19,7 +19,7 @@ bl_info = { "name": "LoopTools", "author": "Bart Crouch", - "version": (4, 6, 7), + "version": (4, 6, 9), "blender": (2, 80, 0), "location": "View3D > Sidebar > Edit Tab / Edit Mode Context Menu", "warning": "", @@ -290,8 +290,10 @@ def calculate_plane(bm_mod, loop, method="best_fit", object=False): for i in range(itermax): vec = vec2 vec2 = mat @ vec - if vec2.length != 0: - vec2 /= vec2.length + # Calculate length with double precision to avoid problems with `inf` + vec2_length = math.sqrt(vec2[0] ** 2 + vec2[1] ** 2 + vec2[2] ** 2) + if vec2_length != 0: + vec2 /= vec2_length if vec2 == vec: break if vec2.length == 0: @@ -536,6 +538,10 @@ def get_derived_bmesh(object, bm): for mod in object.modifiers: if mod.type != 'MIRROR': mod.show_viewport = False + #leave the merge points untouched + if mod.type == 'MIRROR': + merge = mod.use_mirror_merge + mod.use_mirror_merge = False # get derived mesh bm_mod = bmesh.new() depsgraph = bpy.context.evaluated_depsgraph_get() @@ -546,6 +552,8 @@ def get_derived_bmesh(object, bm): # re-enable other modifiers for mod_name in show_viewport: object.modifiers[mod_name].show_viewport = True + if mod.type == 'MIRROR': + mod.use_mirror_merge = merge # no mirror modifiers, so no derived mesh necessary else: derived = False diff --git a/mesh_snap_utilities_line/common_utilities.py b/mesh_snap_utilities_line/common_utilities.py index 5a11a4c4..9c63c3f2 100644 --- a/mesh_snap_utilities_line/common_utilities.py +++ b/mesh_snap_utilities_line/common_utilities.py @@ -143,6 +143,8 @@ def get_snap_bm_geom(sctx, main_snap_obj, mcursor): if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]: r_bm_geom = l.face break + if r_loc is None: + r_loc = r_elem_co[0] return r_snp_obj, r_loc, r_elem, r_elem_co, r_view_vector, r_orig, r_bm, r_bm_geom diff --git a/mesh_snap_utilities_line/preferences.py b/mesh_snap_utilities_line/preferences.py index 7f27507a..98a213be 100644 --- a/mesh_snap_utilities_line/preferences.py +++ b/mesh_snap_utilities_line/preferences.py @@ -52,7 +52,7 @@ class SnapUtilitiesPreferences(bpy.types.AddonPreferences): default=False) auto_constrain : BoolProperty(name="Automatic Constraint", - description="Detects a direction to constrain depending on the position of the mouse.", + description="Detects a direction to constrain depending on the position of the mouse", default=False) incremental : FloatProperty(name="Incremental", diff --git a/mesh_snap_utilities_line/snap_context_l/__init__.py b/mesh_snap_utilities_line/snap_context_l/__init__.py index 9d879a82..3c09e537 100644 --- a/mesh_snap_utilities_line/snap_context_l/__init__.py +++ b/mesh_snap_utilities_line/snap_context_l/__init__.py @@ -331,8 +331,10 @@ class SnapContext(): 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)] - 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, tri_co + #loc = _Internal.intersect_ray_tri(*tri_co, self.last_ray[0], self.last_ray[1], False) + nor = (tri_co[1] - tri_co[0]).cross(tri_co[2] - tri_co[0]).normalized() + loc = _Internal.intersect_line_plane(self.last_ray[1], self.last_ray[1] + self.last_ray[0], tri_co[0], nor) + return loc, tri_verts, tri_co index -= num_tris diff --git a/node_wrangler.py b/node_wrangler.py index 62eb8798..41a83969 100644 --- a/node_wrangler.py +++ b/node_wrangler.py @@ -1027,7 +1027,7 @@ class NWPrincipledPreferences(bpy.types.PropertyGroup): description='Naming Components for roughness maps') gloss: StringProperty( name='Gloss', - default='gloss glossy glossyness', + default='gloss glossy glossiness', description='Naming Components for glossy maps') displacement: StringProperty( name='Displacement', diff --git a/object_print3d_utils/__init__.py b/object_print3d_utils/__init__.py index c654940f..d567bd77 100644 --- a/object_print3d_utils/__init__.py +++ b/object_print3d_utils/__init__.py @@ -21,11 +21,10 @@ bl_info = { "name": "3D-Print Toolbox", "author": "Campbell Barton", - "blender": (2, 80, 0), + "blender": (2, 82, 0), "location": "3D View > Sidebar", "description": "Utilities for 3D printing", - # TODO - # "wiki_url": "", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/mesh/3d_print_toolbox.html", "support": 'OFFICIAL', "category": "Mesh", } @@ -36,6 +35,8 @@ if "bpy" in locals(): importlib.reload(ui) importlib.reload(operators) importlib.reload(mesh_helpers) + if "export" in locals(): + importlib.reload(export) else: import math @@ -62,7 +63,6 @@ class SceneProperties(PropertyGroup): items=( ('STL', "STL", ""), ('PLY', "PLY", ""), - ('WRL', "VRML2", ""), ('X3D', "X3D", ""), ('OBJ', "OBJ", ""), ), diff --git a/object_print3d_utils/export.py b/object_print3d_utils/export.py index a294512b..2503c2f5 100644 --- a/object_print3d_utils/export.py +++ b/object_print3d_utils/export.py @@ -20,23 +20,39 @@ # Export wrappers and integration with external tools. -import os import bpy +def image_get(mat): + from bpy_extras import node_shader_utils + + if mat.use_nodes: + mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mat) + base_color_tex = mat_wrap.base_color_texture + if base_color_tex and base_color_tex.image: + return base_color_tex.image + + def image_copy_guess(filepath, objects): # 'filepath' is the path we are writing to. - import shutil - from bpy_extras import object_utils - image = None + mats = set() + for obj in objects: - image = object_utils.object_image_guess(obj) + for slot in obj.material_slots: + if slot.material: + mats.add(slot.material) + + for mat in mats: + image = image_get(mat) if image is not None: break if image is not None: + import os + import shutil + imagepath = bpy.path.abspath(image.filepath, library=image.library) if os.path.exists(imagepath): filepath_noext = os.path.splitext(filepath)[0] @@ -53,35 +69,18 @@ def image_copy_guess(filepath, objects): def write_mesh(context, report_cb): + import os + scene = context.scene - collection = context.collection layer = context.view_layer unit = scene.unit_settings print_3d = scene.print_3d - 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 path_mode = 'COPY' if print_3d.use_export_texture else 'AUTO' - - context_override = context.copy() - obj_tmp = None - - # PLY can only export single mesh objects! - if export_format == 'PLY': - context_backup = context.copy() - bpy.ops.object.mode_set(mode='OBJECT', toggle=False) - - from . import mesh_helpers - obj_tmp = mesh_helpers.object_merge(context, context_override["selected_objects"]) - context_override["active_object"] = obj_tmp - context_override["selected_objects"] = [obj_tmp] - else: - if obj not in context_override["selected_objects"]: - context_override["selected_objects"].append(obj) - export_path = bpy.path.abspath(print_3d.export_path) + obj = layer.objects.active # Create name 'export_path/blendname-objname' # add the filename component @@ -119,7 +118,6 @@ def write_mesh(context, report_cb): addon_ensure("io_mesh_stl") filepath = bpy.path.ensure_ext(filepath, ".stl") ret = bpy.ops.export_mesh.stl( - context_override, filepath=filepath, ascii=False, use_mesh_modifiers=True, @@ -130,27 +128,15 @@ def write_mesh(context, report_cb): addon_ensure("io_mesh_ply") filepath = bpy.path.ensure_ext(filepath, ".ply") ret = bpy.ops.export_mesh.ply( - context_override, filepath=filepath, use_mesh_modifiers=True, + use_selection=True, global_scale=global_scale, ) elif export_format == 'X3D': addon_ensure("io_scene_x3d") filepath = bpy.path.ensure_ext(filepath, ".x3d") ret = bpy.ops.export_scene.x3d( - context_override, - filepath=filepath, - use_mesh_modifiers=True, - use_selection=True, - path_mode=path_mode, - global_scale=global_scale, - ) - elif export_format == 'WRL': - addon_ensure("io_scene_vrml2") - filepath = bpy.path.ensure_ext(filepath, ".wrl") - ret = bpy.ops.export_scene.vrml2( - context_override, filepath=filepath, use_mesh_modifiers=True, use_selection=True, @@ -161,7 +147,6 @@ def write_mesh(context, report_cb): addon_ensure("io_scene_obj") filepath = bpy.path.ensure_ext(filepath, ".obj") ret = bpy.ops.export_scene.obj( - context_override, filepath=filepath, use_mesh_modifiers=True, use_selection=True, @@ -174,20 +159,7 @@ def write_mesh(context, report_cb): # for formats that don't support images if export_format in {'STL', 'PLY'}: if path_mode == 'COPY': - image_copy_guess(filepath, context_override["selected_objects"]) - - if obj_tmp is not None: - obj = obj_tmp - mesh = obj.data - collection.objects.unlink(obj) - bpy.data.objects.remove(obj) - bpy.data.meshes.remove(mesh) - - # restore context - for ob in context_backup["selected_objects"]: - ob.select_set(True) - - layer.objects.active = context_backup["active_object"] + image_copy_guess(filepath, context.selected_objects) if 'FINISHED' in ret: if report_cb is not None: diff --git a/object_print3d_utils/mesh_helpers.py b/object_print3d_utils/mesh_helpers.py index 37170e53..165b0fc1 100644 --- a/object_print3d_utils/mesh_helpers.py +++ b/object_print3d_utils/mesh_helpers.py @@ -20,6 +20,7 @@ # Generic helper functions, to be used by any modules. + import bmesh @@ -192,67 +193,6 @@ def bmesh_check_thick_object(obj, thickness): return array.array('i', faces_error) -def object_merge(context, objects): - """Caller must remove.""" - - import bpy - - def cd_remove_all_but_active(seq): - tot = len(seq) - if tot > 1: - act = seq.active_index - for i in range(tot - 1, -1, -1): - if i != act: - seq.remove(seq[i]) - - scene = context.scene - layer = context.view_layer - scene_collection = context.layer_collection.collection - - # deselect all - for obj in scene.objects: - obj.select_set(False) - - # add empty object - mesh_tmp = bpy.data.meshes.new(name="~tmp~") - obj_tmp = bpy.data.objects.new(name="~tmp~", object_data=mesh_tmp) - scene_collection.objects.link(obj_tmp) - obj_tmp.select_set(True) - - depsgraph = context.evaluated_depsgraph_get() - - # loop over all meshes - for obj in objects: - if obj.type != 'MESH': - continue - - # convert each to a mesh - obj_eval = obj.evaluated_get(depsgraph) - mesh_new = obj_eval.to_mesh().copy() - - # remove non-active uvs/vcols - cd_remove_all_but_active(mesh_new.vertex_colors) - 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) - scene_collection.objects.link(obj_new) - obj_new.matrix_world = obj.matrix_world - - override = context.copy() - override["active_object"] = obj_tmp - override["selected_editable_objects"] = [obj_tmp, obj_new] - - bpy.ops.object.join(override) - - bpy.data.meshes.remove(mesh_new) - obj_eval.to_mesh_clear() - - layer.update() - - return obj_tmp - - def face_is_distorted(ele, angle_distort): no = ele.normal angle_fn = no.angle diff --git a/object_print3d_utils/operators.py b/object_print3d_utils/operators.py index e82846f3..3df5e0f7 100644 --- a/object_print3d_utils/operators.py +++ b/object_print3d_utils/operators.py @@ -20,6 +20,7 @@ # All Operator + import math import bpy diff --git a/object_print3d_utils/ui.py b/object_print3d_utils/ui.py index 353060fe..af3aa0df 100644 --- a/object_print3d_utils/ui.py +++ b/object_print3d_utils/ui.py @@ -20,6 +20,7 @@ # Interface for this addon. + from bpy.types import Panel import bmesh diff --git a/object_scatter/operator.py b/object_scatter/operator.py index 742eec95..a01ca765 100644 --- a/object_scatter/operator.py +++ b/object_scatter/operator.py @@ -318,6 +318,8 @@ def scatter_from_source_point(bvhtree, point, seed, settings): assert location is not None normal.normalize() + up_direction = normal if settings.use_normal_rotation else Vector((0, 0, 1)) + # Scale min_scale = settings.scale * (1 - settings.random_scale) max_scale = settings.scale @@ -328,9 +330,9 @@ def scatter_from_source_point(bvhtree, point, seed, settings): # 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() + up_rotation = up_direction.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 + rotation = local_rotation @ up_rotation @ z_rotation return Matrix.Translation(location) @ rotation.to_4x4() @ scale_matrix(scale) diff --git a/object_scatter/ui.py b/object_scatter/ui.py index d62b8e38..b021bf3d 100644 --- a/object_scatter/ui.py +++ b/object_scatter/ui.py @@ -22,6 +22,7 @@ import math from collections import namedtuple from bpy.props import ( + BoolProperty, IntProperty, FloatProperty, PointerProperty @@ -30,7 +31,7 @@ from bpy.props import ( ScatterSettings = namedtuple("ScatterSettings", ["seed", "density", "radius", "scale", "random_scale", - "rotation", "normal_offset"]) + "rotation", "normal_offset", "use_normal_rotation"]) class ObjectScatterProperties(bpy.types.PropertyGroup): seed: IntProperty( @@ -94,6 +95,12 @@ class ObjectScatterProperties(bpy.types.PropertyGroup): description="Distance from the surface", ) + use_normal_rotation: BoolProperty( + name="Use Normal Rotation", + default=True, + description="Rotate the instances according to the surface normals", + ) + def to_settings(self): return ScatterSettings( seed=self.seed, @@ -103,6 +110,7 @@ class ObjectScatterProperties(bpy.types.PropertyGroup): random_scale=self.random_scale_percentage / 100, rotation=self.rotation, normal_offset=self.normal_offset, + use_normal_rotation=self.use_normal_rotation, ) @@ -125,6 +133,7 @@ class ObjectScatterPanel(bpy.types.Panel): col.prop(scatter, "scale", slider=True) col.prop(scatter, "random_scale_percentage", text="Randomness", slider=True) + layout.prop(scatter, "use_normal_rotation") layout.prop(scatter, "rotation", slider=True) layout.prop(scatter, "normal_offset", text="Offset", slider=True) layout.prop(scatter, "seed") diff --git a/render_povray/__init__.py b/render_povray/__init__.py index 30f18340..ed6f8569 100644 --- a/render_povray/__init__.py +++ b/render_povray/__init__.py @@ -19,13 +19,14 @@ # <pep8 compliant> bl_info = { + #coming soon: "name": "POV-3.8", "name": "POV-3.7", "author": "Campbell Barton, Maurice Raybaud, Leonid Desyatkov, " "Bastien Montagne, Constantin Rahn, Silvio Falcinelli", "version": (0, 1, 0), "blender": (2, 80, 0), - "location": "Render > Engine > POV-Ray 3.7", - "description": "POV-Ray 3.7 integration for blender", + "location": "Render > Engine > Persistence Of Vision", + "description": "POV-Ray integration for blender", "wiki_url": "https://archive.blender.org/wiki/index.php/" "Extensions:2.6/Py/Scripts/Render/POV-Ray/", "category": "Render", @@ -68,6 +69,28 @@ else: def string_strip_hyphen(name): return name.replace("-", "") + +def active_texture_name_from_uilist(self,context): + mat = context.scene.view_layers["View Layer"].objects.active.active_material + index = mat.pov.active_texture_index + name = mat.pov_texture_slots[index].name + newname = mat.pov_texture_slots[index].texture + tex = bpy.data.textures[name] + tex.name = newname + mat.pov_texture_slots[index].name = newname + + +def active_texture_name_from_search(self,context): + mat = context.scene.view_layers["View Layer"].objects.active.active_material + index = mat.pov.active_texture_index + name = mat.pov_texture_slots[index].texture_search + try: + tex = bpy.data.textures[name] + mat.pov_texture_slots[index].name = name + mat.pov_texture_slots[index].texture = name + except: + pass + ############################################################################### # Scene POV properties. ############################################################################### @@ -557,6 +580,11 @@ class RenderPovSettingsScene(PropertyGroup): # Material POV properties. ############################################################################### class MaterialTextureSlot(PropertyGroup): + bl_idname="povray_texture_slots", + bl_description="Texture_slots from Blender-2.79", + + texture : StringProperty(update=active_texture_name_from_uilist) + texture_search : StringProperty(update=active_texture_name_from_search) alpha_factor: FloatProperty( name="Alpha", @@ -4293,6 +4321,29 @@ for i in range(18): # length of world texture slots world.texture_slots.add() ''' +class MATERIAL_TEXTURE_SLOTS_UL_layerlist(bpy.types.UIList): +# texture_slots: + index: bpy.props.IntProperty(name='index') + #foo = random prop + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + ob = data + slot = item + #ma = slot.name + # draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code. + if self.layout_type in {'DEFAULT', 'COMPACT'}: + # You should always start your row layout by a label (icon + text), or a non-embossed text field, + # this will also make the row easily selectable in the list! The later also enables ctrl-click rename. + # We use icon_value of label, as our given icon is an integer value, not an enum ID. + # Note "data" names should never be translated! + if slot: + layout.prop(item, "texture", text="", emboss=False, icon='TEXTURE') + else: + layout.label(text="New", translate=False, icon_value=icon) + # 'GRID' layout type should be as compact as possible (typically a single icon!). + elif self.layout_type in {'GRID'}: + layout.alignment = 'CENTER' + layout.label(text="", icon_value=icon) + ############################################################################### # Text POV properties. @@ -4344,6 +4395,7 @@ classes = ( RenderPovSettingsCamera, RenderPovSettingsLight, RenderPovSettingsWorld, + MATERIAL_TEXTURE_SLOTS_UL_layerlist, MaterialTextureSlot, WorldTextureSlot, RenderPovSettingsMaterial, @@ -4397,7 +4449,7 @@ def register(): bpy.types.Camera.pov = PointerProperty(type=RenderPovSettingsCamera) bpy.types.Light.pov = PointerProperty(type=RenderPovSettingsLight) bpy.types.World.pov = PointerProperty(type=RenderPovSettingsWorld) - bpy.types.Material.texture_slots = CollectionProperty(type = MaterialTextureSlot) + bpy.types.Material.pov_texture_slots = CollectionProperty(type=MaterialTextureSlot) bpy.types.World.texture_slots = CollectionProperty(type = WorldTextureSlot) bpy.types.Text.pov = PointerProperty(type=RenderPovSettingsText) @@ -4415,7 +4467,8 @@ def unregister(): del bpy.types.Object.pov del bpy.types.Camera.pov del bpy.types.Light.pov - del bpy.types.World.pov + del bpy.types.World.pov + del bpy.types.Material.pov_texture_slots del bpy.types.Text.pov nodeitems_utils.unregister_node_categories("POVRAYNODES") diff --git a/render_povray/render.py b/render_povray/render.py index 56251988..7fd8fca5 100644 --- a/render_povray/render.py +++ b/render_povray/render.py @@ -3784,6 +3784,7 @@ def write_pov_ini(scene, filename_ini, filename_log, filename_pov, filename_imag class PovrayRender(bpy.types.RenderEngine): bl_idname = 'POVRAY_RENDER' bl_label = "Persitence Of Vision" + bl_use_shading_nodes_custom = False DELAY = 0.5 @staticmethod diff --git a/render_povray/ui.py b/render_povray/ui.py index 5f415727..038c632f 100644 --- a/render_povray/ui.py +++ b/render_povray/ui.py @@ -214,6 +214,15 @@ for member in dir(properties_particle): # add all "particle" panels from blende pass del properties_particle +# Example of wrapping every class 'as is' +from bl_ui import properties_output +for member in dir(properties_output): + subclass = getattr(properties_output, member) + try: + subclass.COMPAT_ENGINES.add('POVRAY_RENDER') + except: + pass +del properties_output class POV_WORLD_MT_presets(bpy.types.Menu): bl_label = "World Presets" @@ -313,7 +322,7 @@ def pov_context_tex_datablock(context): if idblock: return active_node_mat(idblock) - idblock = context.lamp + idblock = context.light if idblock: return idblock @@ -1724,7 +1733,7 @@ class MATERIAL_PT_strand(MaterialButtonsPanel, Panel): def poll(cls, context): mat = context.material engine = context.scene.render.engine - return mat and (mat.type in {'SURFACE', 'WIRE', 'HALO'}) and (engine in cls.COMPAT_ENGINES) + return mat and (mat.pov.type in {'SURFACE', 'WIRE', 'HALO'}) and (engine in cls.COMPAT_ENGINES) def draw(self, context): layout = self.layout @@ -1785,6 +1794,27 @@ class TEXTURE_MT_specials(bpy.types.Menu): layout.operator("texture.slot_copy", icon='COPYDOWN') layout.operator("texture.slot_paste", icon='PASTEDOWN') +class TEXTURE_UL_texture_slots(bpy.types.UIList): + COMPAT_ENGINES = {'POVRAY_RENDER'} + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + ob = data + slot = item + #ma = slot.name + # draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code. + if self.layout_type in {'DEFAULT', 'COMPACT'}: + # You should always start your row layout by a label (icon + text), or a non-embossed text field, + # this will also make the row easily selectable in the list! The later also enables ctrl-click rename. + # We use icon_value of label, as our given icon is an integer value, not an enum ID. + # Note "data" names should never be translated! + if slot: + layout.prop(item, "texture", text="", emboss=False, icon='TEXTURE') + else: + layout.label(text="New", translate=False, icon_value=icon) + # 'GRID' layout type should be as compact as possible (typically a single icon!). + elif self.layout_type in {'GRID'}: + layout.alignment = 'CENTER' + layout.label(text="", icon_value=icon) +''' class MATERIAL_TEXTURE_SLOTS_UL_List(UIList): """Texture Slots UIList.""" @@ -1806,7 +1836,7 @@ class MATERIAL_TEXTURE_SLOTS_UL_List(UIList): elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' layout.label("", icon = custom_icon) - +''' class WORLD_TEXTURE_SLOTS_UL_List(UIList): """Texture Slots UIList.""" @@ -1837,6 +1867,7 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel): @classmethod def poll(cls, context): engine = context.scene.render.engine + return (engine in cls.COMPAT_ENGINES) # if not (hasattr(context, "texture_slot") or hasattr(context, "texture_node")): # return False return ((context.material or @@ -1851,7 +1882,30 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel): def draw(self, context): layout = self.layout - + + scene = context.scene + layout.prop(scene, "texture_context", expand=True) + if scene.texture_context == 'MATERIAL': + mat = context.scene.view_layers["View Layer"].objects.active.active_material + row = layout.row() + row.template_list("MATERIAL_TEXTURE_SLOTS_UL_layerlist", "", mat, "pov_texture_slots", mat.pov, "active_texture_index") + col = row.column(align=True) + col.operator("pov.textureslotadd",icon='ADD',text='') + col.operator("pov.textureslotremove",icon='REMOVE',text='') + col.separator() + + if mat.pov_texture_slots: + index = mat.pov.active_texture_index + slot = mat.pov_texture_slots[index] + povtex = slot.name + tex = bpy.data.textures[povtex] + col.prop(tex,'use_fake_user',text = '') + layout.label(text='Find texture:') + layout.prop_search(slot,'texture_search',bpy.data,'textures',text='') + # else: + # for i in range(18): # length of material texture slots + # mat.pov_texture_slots.add() +''' slot = getattr(context, "texture_slot", None) node = getattr(context, "texture_node", None) space = context.space_data @@ -1956,7 +2010,7 @@ class TEXTURE_PT_POV_context_texture(TextureButtonsPanel, Panel): split.prop(slot, "output_node", text="") else: split.label(text="Type:") - +''' class TEXTURE_PT_colors(TextureButtonsPanel, Panel): bl_label = "Colors" bl_options = {'DEFAULT_CLOSED'} @@ -1992,12 +2046,46 @@ class TEXTURE_PT_colors(TextureButtonsPanel, Panel): # Texture Slot Panels # +class POV_OT_texture_slot_add(Operator): + bl_idname = "pov.textureslotadd" + bl_label = "Add" + bl_description = "Add texture_slot" + bl_options = {'REGISTER', 'UNDO'} + COMPAT_ENGINES = {'POVRAY_RENDER'} + + def execute(self,context): + + tex = bpy.data.textures.new(name = 'Texture',type = 'IMAGE') + tex.use_fake_user = True + ob = context.scene.view_layers["View Layer"].objects.active + slot = ob.active_material.pov_texture_slots.add() + slot.name = tex.name + slot.texture = tex.name + + return {'FINISHED'} + + +class POV_OT_texture_slot_remove(Operator): + bl_idname = "pov.textureslotremove" + bl_label = "Remove" + bl_description = "Remove texture_slot" + bl_options = {'REGISTER', 'UNDO'} + COMPAT_ENGINES = {'POVRAY_RENDER'} + + def execute(self,context): + pass + # tex = bpy.data.textures.new() + # tex_slot = context.object.active_material.pov_texture_slots.add() + # tex_slot.name = tex.name + + return {'FINISHED'} + class TextureSlotPanel(TextureButtonsPanel): COMPAT_ENGINES = {'POVRAY_RENDER'} @classmethod def poll(cls, context): - if not hasattr(context, "texture_slot"): + if not hasattr(context, "pov_texture_slot"): return False engine = context.scene.render.engine @@ -2030,7 +2118,7 @@ class TEXTURE_PT_povray_preview(TextureButtonsPanel, Panel): @classmethod def poll(cls, context): engine = context.scene.render.engine - if not hasattr(context, "texture_slot"): + if not hasattr(context, "pov_texture_slot"): return False tex=context.texture mat=context.material @@ -2038,8 +2126,8 @@ class TEXTURE_PT_povray_preview(TextureButtonsPanel, Panel): def draw(self, context): tex = context.texture - slot = getattr(context, "texture_slot", None) - idblock = context_tex_datablock(context) + slot = getattr(context, "pov_texture_slot", None) + idblock = pov_context_tex_datablock(context) layout = self.layout # if idblock: # layout.template_preview(tex, parent=idblock, slot=slot) @@ -2263,11 +2351,11 @@ class TEXTURE_PT_influence(TextureSlotPanel, Panel): @classmethod def poll(cls, context): - idblock = context_tex_datablock(context) + idblock = pov_context_tex_datablock(context) if isinstance(idblock, Brush): return False - if not getattr(context, "texture_slot", None): + if not getattr(context, "pov_texture_slot", None): return False engine = context.scene.render.engine @@ -2277,9 +2365,11 @@ class TEXTURE_PT_influence(TextureSlotPanel, Panel): layout = self.layout - idblock = context_tex_datablock(context) + idblock = pov_context_tex_datablock(context) - tex = context.texture_slot + # tex = context.pov_texture_slot + mat = context.material + tex = bpy.data.textures[mat.pov_texture_slots[mat.pov.active_texture_index]] def factor_but(layout, toggle, factor, name): row = layout.row(align=True) @@ -2290,7 +2380,7 @@ class TEXTURE_PT_influence(TextureSlotPanel, Panel): return sub # XXX, temp. use_map_normal needs to override. if isinstance(idblock, Material): - if idblock.type in {'SURFACE', 'WIRE'}: + if idblock.pov.type in {'SURFACE', 'WIRE'}: split = layout.split() col = split.column() @@ -2324,7 +2414,7 @@ class TEXTURE_PT_influence(TextureSlotPanel, Panel): # ~ sub = col.column() # ~ sub.active = tex.use_map_translucency or tex.map_emit or tex.map_alpha or tex.map_raymir or tex.map_hardness or tex.map_ambient or tex.map_specularity or tex.map_reflection or tex.map_mirror #~ sub.prop(tex, "default_value", text="Amount", slider=True) - elif idblock.type == 'HALO': + elif idblock.pov.type == 'HALO': layout.label(text="Halo:") split = layout.split() @@ -2337,7 +2427,7 @@ class TEXTURE_PT_influence(TextureSlotPanel, Panel): factor_but(col, "use_map_raymir", "raymir_factor", "Size") factor_but(col, "use_map_hardness", "hardness_factor", "Hardness") factor_but(col, "use_map_translucency", "translucency_factor", "Add") - elif idblock.type == 'VOLUME': + elif idblock.pov.type == 'VOLUME': layout.label(text="Volume:") split = layout.split() @@ -3234,7 +3324,13 @@ classes = ( TEXT_OT_povray_insert, TEXT_MT_insert, TEXT_PT_povray_custom_code, - TEXT_MT_templates_pov + TEXT_MT_templates_pov, + # TEXTURE_PT_context, + #TEXTURE_PT_POV_povray_texture_slots, + TEXTURE_UL_texture_slots, + POV_OT_texture_slot_add, + POV_OT_texture_slot_remove, + TEXTURE_PT_influence ) diff --git a/rigify/__init__.py b/rigify/__init__.py index 803c19c0..8363d037 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -20,7 +20,7 @@ bl_info = { "name": "Rigify", - "version": (0, 6, 0), + "version": (0, 6, 1), "author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello, Alexander Gavrilov", "blender": (2, 81, 0), "description": "Automatic rigging from building-block components", @@ -520,7 +520,7 @@ def register(): IDStore.rigify_types = CollectionProperty(type=RigifyName) IDStore.rigify_active_type = IntProperty(name="Rigify Active Type", description="The selected rig type") - IDStore.rigify_advanced_generation = BoolProperty(name="Advanced Options", + bpy.types.Armature.rigify_advanced_generation = BoolProperty(name="Advanced Options", description="Enables/disables advanced options for Rigify rig generation", default=False) @@ -528,27 +528,26 @@ def register(): if self.rigify_generate_mode == 'new': self.rigify_force_widget_update = False - IDStore.rigify_generate_mode = EnumProperty(name="Rigify Generate Rig Mode", + bpy.types.Armature.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", + bpy.types.Armature.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", + bpy.types.Armature.rigify_target_rig = PointerProperty(type=bpy.types.Object, + name="Rigify Target Rig", description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created", - default="") + poll=lambda self, obj: obj.type == 'ARMATURE' and obj.data is not self) - 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="") + bpy.types.Armature.rigify_rig_ui = PointerProperty(type=bpy.types.Text, + name="Rigify Target Rig UI", + description="Defines the UI to overwrite. If unset, 'rig_ui.py' will be used") - IDStore.rigify_rig_basename = StringProperty(name="Rigify Rig Name", + bpy.types.Armature.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="") @@ -602,19 +601,17 @@ def unregister(): del ArmStore.rigify_colors_index del ArmStore.rigify_colors_lock del ArmStore.rigify_theme_to_add + del ArmStore.rigify_advanced_generation + del ArmStore.rigify_generate_mode + del ArmStore.rigify_force_widget_update + del ArmStore.rigify_target_rig + del ArmStore.rigify_rig_ui + del ArmStore.rigify_rig_basename IDStore = bpy.types.WindowManager del IDStore.rigify_collection del IDStore.rigify_types del IDStore.rigify_active_type - del IDStore.rigify_advanced_generation - del IDStore.rigify_generate_mode - del IDStore.rigify_force_widget_update - del IDStore.rigify_target_rig - del IDStore.rigify_target_rigs - del IDStore.rigify_rig_uis - del IDStore.rigify_rig_ui - del IDStore.rigify_rig_basename del IDStore.rigify_transfer_only_selected # Classes. diff --git a/rigify/base_rig.py b/rigify/base_rig.py index 6b01c43a..b7a5d08c 100644 --- a/rigify/base_rig.py +++ b/rigify/base_rig.py @@ -258,6 +258,16 @@ class RigUtility(BoneUtilityMixin, MechanismUtilityMixin): self.owner.register_new_bone(new_name, old_name) +class RigComponent(GenerateCallbackHost, RigUtility): + """Base class for utility classes that generate part of a rig using callbacks.""" + def __init__(self, owner): + super().__init__(owner) + + self.owner.rigify_sub_objects = objects = self.owner.rigify_sub_objects or [] + + objects.append(self) + + #============================================= # Rig Stage Decorators #============================================= diff --git a/rigify/generate.py b/rigify/generate.py index 0d02c5c2..8e3ca0f0 100644 --- a/rigify/generate.py +++ b/rigify/generate.py @@ -69,36 +69,45 @@ class Generator(base_generate.BaseGenerator): def __create_rig_object(self): scene = self.scene id_store = self.id_store + meta_data = self.metarig.data # Check if the generated rig already exists, so we can # regenerate in the same object. If not, create a new # object to generate the rig in. print("Fetch rig.") - if id_store.rigify_generate_mode == 'overwrite': - name = id_store.rigify_target_rig or "rig" - try: + self.rig_new_name = name = meta_data.rigify_rig_basename or "rig" + + obj = None + + if meta_data.rigify_generate_mode == 'overwrite': + obj = meta_data.rigify_target_rig + + if not obj and name in scene.objects: obj = scene.objects[name] - self.rig_old_name = name - obj.name = self.rig_new_name or name + + if obj: + self.rig_old_name = obj.name + + obj.name = name + obj.data.name = obj.name rig_collections = filter_layer_collections_by_object(self.usable_collections, obj) self.layer_collection = (rig_collections + [self.layer_collection])[0] self.collection = self.layer_collection.collection - except KeyError: - self.rig_old_name = name - name = self.rig_new_name or name - obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) - obj.display_type = 'WIRE' - self.collection.objects.link(obj) - else: - name = self.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 + elif name in bpy.data.objects: + obj = bpy.data.objects[name] + + if not obj: + obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) obj.display_type = 'WIRE' self.collection.objects.link(obj) - id_store.rigify_target_rig = obj.name + elif obj.name not in self.collection.objects: # rig exists but was deleted + self.collection.objects.link(obj) + + meta_data.rigify_target_rig = obj obj.data.pose_position = 'POSE' self.obj = obj @@ -114,8 +123,8 @@ class Generator(base_generate.BaseGenerator): self.widget_collection = ensure_widget_collection(context) # Remove wgts if force update is set - wgts_group_name = "WGTS_" + (self.rig_old_name or obj.name) - if wgts_group_name in scene.objects and id_store.rigify_force_widget_update: + wgts_group_name = "WGTS_" + (self.rig_old_name or self.obj.name) + if wgts_group_name in scene.objects and self.metarig.data.rigify_force_widget_update: bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') for wgt in bpy.data.objects[wgts_group_name].children: @@ -327,9 +336,6 @@ class Generator(base_generate.BaseGenerator): #------------------------------------------ # Create/find the rig object and set it up - if id_store.rigify_rig_basename: - self.rig_new_name = id_store.rigify_rig_basename + "_rig" - obj = self.__create_rig_object() # Get rid of anim data in case the rig already existed diff --git a/rigify/metarigs/Animals/bird.py b/rigify/metarigs/Animals/bird.py index a3331c29..eee6b38b 100644 --- a/rigify/metarigs/Animals/bird.py +++ b/rigify/metarigs/Animals/bird.py @@ -165,47 +165,19 @@ def create(obj): bones = {} - bone = arm.edit_bones.new('spine') - bone.head[:] = -0.0000, 0.1371, 0.0894 - bone.tail[:] = -0.0000, 0.1039, 0.0907 + bone = arm.edit_bones.new('spine.003') + bone.head[:] = -0.0000, 0.0451, 0.0845 + bone.tail[:] = -0.0000, 0.0192, 0.0888 bone.roll = 0.0000 bone.use_connect = False - bones['spine'] = bone.name - bone = arm.edit_bones.new('spine.001') - bone.head[:] = -0.0000, 0.1039, 0.0907 + bones['spine.003'] = bone.name + bone = arm.edit_bones.new('spine.002') + bone.head[:] = -0.0000, 0.0451, 0.0845 bone.tail[:] = -0.0000, 0.0757, 0.0880 bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine']] - bones['spine.001'] = bone.name - bone = arm.edit_bones.new('t_feather.L') - bone.head[:] = 0.0112, 0.1017, 0.0907 - bone.tail[:] = 0.0167, 0.1345, 0.0894 - bone.roll = 0.0032 - bone.use_connect = False - bone.parent = arm.edit_bones[bones['spine']] - bones['t_feather.L'] = bone.name - bone = arm.edit_bones.new('t_feather.R') - bone.head[:] = -0.0112, 0.1017, 0.0907 - bone.tail[:] = -0.0167, 0.1345, 0.0894 - bone.roll = -0.0032 bone.use_connect = False - bone.parent = arm.edit_bones[bones['spine']] - bones['t_feather.R'] = bone.name - bone = arm.edit_bones.new('spine.002') - bone.head[:] = -0.0000, 0.0757, 0.0880 - bone.tail[:] = -0.0000, 0.0451, 0.0845 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.001']] + bone.parent = arm.edit_bones[bones['spine.003']] bones['spine.002'] = bone.name - bone = arm.edit_bones.new('spine.003') - bone.head[:] = -0.0000, 0.0451, 0.0845 - bone.tail[:] = -0.0000, 0.0192, 0.0888 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.002']] - bones['spine.003'] = bone.name bone = arm.edit_bones.new('spine.004') bone.head[:] = -0.0000, 0.0192, 0.0888 bone.tail[:] = -0.0000, -0.0106, 0.0979 @@ -213,6 +185,13 @@ def create(obj): bone.use_connect = True bone.parent = arm.edit_bones[bones['spine.003']] bones['spine.004'] = bone.name + bone = arm.edit_bones.new('spine.001') + bone.head[:] = -0.0000, 0.0757, 0.0880 + bone.tail[:] = -0.0000, 0.1039, 0.0907 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.002']] + bones['spine.001'] = bone.name bone = arm.edit_bones.new('spine.005') bone.head[:] = -0.0000, -0.0106, 0.0979 bone.tail[:] = -0.0000, -0.0298, 0.1158 @@ -248,6 +227,13 @@ def create(obj): bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.004']] bones['thigh.R'] = bone.name + bone = arm.edit_bones.new('spine') + bone.head[:] = -0.0000, 0.1039, 0.0907 + bone.tail[:] = -0.0000, 0.1371, 0.0894 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.001']] + bones['spine'] = bone.name bone = arm.edit_bones.new('shoulder.L') bone.head[:] = 0.0014, -0.0217, 0.0893 bone.tail[:] = 0.0076, -0.0020, 0.1179 @@ -283,6 +269,20 @@ def create(obj): bone.use_connect = True bone.parent = arm.edit_bones[bones['thigh.R']] bones['shin.R'] = bone.name + bone = arm.edit_bones.new('t_feather.L') + bone.head[:] = 0.0112, 0.1017, 0.0907 + bone.tail[:] = 0.0167, 0.1345, 0.0894 + bone.roll = 0.0032 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['spine']] + bones['t_feather.L'] = bone.name + bone = arm.edit_bones.new('t_feather.R') + bone.head[:] = -0.0112, 0.1017, 0.0907 + bone.tail[:] = -0.0167, 0.1345, 0.0894 + bone.roll = -0.0032 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['spine']] + bones['t_feather.R'] = bone.name bone = arm.edit_bones.new('Wing.L') bone.head[:] = 0.0089, 0.0141, 0.1157 bone.tail[:] = 0.0485, 0.0107, 0.1163 @@ -294,7 +294,7 @@ def create(obj): bone.head[:] = -0.0000, -0.0417, 0.1348 bone.tail[:] = -0.0000, -0.0458, 0.1429 bone.roll = 0.0001 - bone.use_connect = True + bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.006']] bones['neck.001'] = bone.name bone = arm.edit_bones.new('Wing.R') @@ -691,8 +691,8 @@ def create(obj): bones['tongue.003.L'] = bone.name bpy.ops.object.mode_set(mode='OBJECT') - pbone = obj.pose.bones[bones['spine']] - pbone.rigify_type = 'spines.super_spine' + pbone = obj.pose.bones[bones['spine.003']] + pbone.rigify_type = 'spines.basic_spine' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -700,70 +700,38 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.use_tail = True - except AttributeError: - pass - try: - pbone.rigify_parameters.tail_pos = 3 + pbone.rigify_parameters.pivot_pos = 1 except AttributeError: pass try: - pbone.rigify_parameters.pivot_pos = 4 - except AttributeError: - pass - try: - pbone.rigify_parameters.neck_pos = 8 - except AttributeError: - pass - try: - pbone.rigify_parameters.copy_rotation_axes = [True, False, True] + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.fk_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass - pbone = obj.pose.bones[bones['spine.001']] - pbone.rigify_type = '' + pbone = obj.pose.bones[bones['spine.002']] + pbone.rigify_type = 'spines.basic_tail' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['t_feather.L']] - pbone.rigify_type = 'basic.super_copy' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.make_widget = False + pbone.rigify_parameters.copy_rotation_axes = [True, False, True] except AttributeError: pass - pbone = obj.pose.bones[bones['t_feather.R']] - pbone.rigify_type = 'basic.super_copy' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.make_widget = False + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass - pbone = obj.pose.bones[bones['spine.002']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.003']] + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass + pbone = obj.pose.bones[bones['spine.004']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) @@ -771,7 +739,7 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.004']] + pbone = obj.pose.bones[bones['spine.001']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) @@ -859,6 +827,14 @@ def create(obj): pbone.rigify_parameters.fk_layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass + pbone = obj.pose.bones[bones['spine']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['shoulder.L']] pbone.rigify_type = 'basic.super_copy' pbone.lock_location = (False, False, False) @@ -907,6 +883,30 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['t_feather.L']] + pbone.rigify_type = 'basic.super_copy' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.make_widget = False + except AttributeError: + pass + pbone = obj.pose.bones[bones['t_feather.R']] + pbone.rigify_type = 'basic.super_copy' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.make_widget = False + except AttributeError: + pass pbone = obj.pose.bones[bones['Wing.L']] pbone.rigify_type = 'limbs.simple_tentacle' pbone.lock_location = (False, False, False) @@ -914,20 +914,31 @@ def create(obj): pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.rigify_parameters.copy_rotation_axes = [False, False, False] pbone.bone.layers = [False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: + pbone.rigify_parameters.copy_rotation_axes = [False, False, False] + except AttributeError: + pass + try: pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass pbone = obj.pose.bones[bones['neck.001']] - pbone.rigify_type = '' + pbone.rigify_type = 'spines.super_head' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass pbone = obj.pose.bones[bones['Wing.R']] pbone.rigify_type = 'limbs.simple_tentacle' pbone.lock_location = (False, False, False) @@ -935,9 +946,12 @@ def create(obj): pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.rigify_parameters.copy_rotation_axes = [False, False, False] pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: + pbone.rigify_parameters.copy_rotation_axes = [False, False, False] + except AttributeError: + pass + try: pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass @@ -1488,9 +1502,12 @@ def create(obj): bone.select = True bone.select_head = True bone.select_tail = True + bone.bbone_x = bone.bbone_z = bone.length * 0.05 arm.edit_bones.active = bone arm.layers = [(x in [0, 3, 7, 10, 13, 16, 21, 24]) for x in range(32)] + return bones + if __name__ == "__main__": create(bpy.context.active_object) diff --git a/rigify/metarigs/Animals/cat.py b/rigify/metarigs/Animals/cat.py index a065c432..66321116 100644 --- a/rigify/metarigs/Animals/cat.py +++ b/rigify/metarigs/Animals/cat.py @@ -165,40 +165,19 @@ def create(obj): bones = {} - bone = arm.edit_bones.new('tail.004') - bone.head[:] = -0.0000, 0.5531, 0.2488 - bone.tail[:] = -0.0000, 0.4543, 0.2321 - bone.roll = 0.0000 - bone.use_connect = False - bones['tail.004'] = bone.name - bone = arm.edit_bones.new('tail.003') - bone.head[:] = -0.0000, 0.4543, 0.2321 - bone.tail[:] = -0.0000, 0.3513, 0.2284 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['tail.004']] - bones['tail.003'] = bone.name - bone = arm.edit_bones.new('tail.002') - bone.head[:] = -0.0000, 0.3513, 0.2284 - bone.tail[:] = -0.0000, 0.2460, 0.2324 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['tail.003']] - bones['tail.002'] = bone.name - bone = arm.edit_bones.new('tail.001') - bone.head[:] = -0.0000, 0.2460, 0.2324 - bone.tail[:] = 0.0000, 0.1499, 0.2500 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['tail.002']] - bones['tail.001'] = bone.name bone = arm.edit_bones.new('spine') bone.head[:] = 0.0000, 0.1499, 0.2500 bone.tail[:] = 0.0000, 0.0769, 0.2272 bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['tail.001']] + bone.use_connect = False bones['spine'] = bone.name + bone = arm.edit_bones.new('tail.001') + bone.head[:] = 0.0000, 0.1499, 0.2500 + bone.tail[:] = -0.0000, 0.2460, 0.2324 + bone.roll = 0.0000 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['spine']] + bones['tail.001'] = bone.name bone = arm.edit_bones.new('spine.001') bone.head[:] = 0.0000, 0.0769, 0.2272 bone.tail[:] = 0.0000, 0.0180, 0.2240 @@ -227,6 +206,13 @@ def create(obj): bone.use_connect = False bone.parent = arm.edit_bones[bones['spine']] bones['pelvis.C'] = bone.name + bone = arm.edit_bones.new('tail.002') + bone.head[:] = -0.0000, 0.2460, 0.2324 + bone.tail[:] = -0.0000, 0.3513, 0.2284 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['tail.001']] + bones['tail.002'] = bone.name bone = arm.edit_bones.new('spine.002') bone.head[:] = 0.0000, 0.0180, 0.2240 bone.tail[:] = 0.0000, -0.0513, 0.2271 @@ -248,6 +234,13 @@ def create(obj): bone.use_connect = False bone.parent = arm.edit_bones[bones['pelvis.R']] bones['thigh.R'] = bone.name + bone = arm.edit_bones.new('tail.003') + bone.head[:] = -0.0000, 0.3513, 0.2284 + bone.tail[:] = -0.0000, 0.4543, 0.2321 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['tail.002']] + bones['tail.003'] = bone.name bone = arm.edit_bones.new('spine.003') bone.head[:] = 0.0000, -0.0513, 0.2271 bone.tail[:] = 0.0000, -0.1571, 0.2355 @@ -276,11 +269,18 @@ def create(obj): bone.use_connect = True bone.parent = arm.edit_bones[bones['thigh.R']] bones['shin.R'] = bone.name + bone = arm.edit_bones.new('tail.004') + bone.head[:] = -0.0000, 0.4543, 0.2321 + bone.tail[:] = -0.0000, 0.5531, 0.2488 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['tail.003']] + bones['tail.004'] = bone.name bone = arm.edit_bones.new('spine.004') bone.head[:] = 0.0000, -0.1571, 0.2355 bone.tail[:] = 0.0000, -0.1736, 0.2395 bone.roll = 0.0000 - bone.use_connect = True + bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.003']] bones['spine.004'] = bone.name bone = arm.edit_bones.new('Breast.C') @@ -1384,52 +1384,24 @@ def create(obj): bones['brow.T.R.003'] = bone.name bpy.ops.object.mode_set(mode='OBJECT') - pbone = obj.pose.bones[bones['tail.004']] - pbone.rigify_type = 'spines.super_spine' + pbone = obj.pose.bones[bones['spine']] + pbone.rigify_type = 'spines.basic_spine' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - try: - pbone.rigify_parameters.use_tail = True - except AttributeError: - pass - try: - pbone.rigify_parameters.pivot_pos = 6 - except AttributeError: - pass - try: - pbone.rigify_parameters.neck_pos = 9 - except AttributeError: - pass + pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.tail_pos = 4 + pbone.rigify_parameters.tweak_layers = [False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass try: - pbone.rigify_parameters.copy_rotation_axes = [True, True, True] + pbone.rigify_parameters.fk_layers = [False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass - pbone = obj.pose.bones[bones['tail.003']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['tail.002']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['tail.001']] - pbone.rigify_type = '' + pbone.rigify_type = 'spines.basic_tail' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -1437,23 +1409,15 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.tweak_layers = [False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass - pbone = obj.pose.bones[bones['spine']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.neck_pos = 5 + pbone.rigify_parameters.copy_rotation_axes = [True, True, True] except AttributeError: pass try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.connect_chain = True except AttributeError: pass pbone = obj.pose.bones[bones['spine.001']] @@ -1504,6 +1468,14 @@ def create(obj): pbone.rigify_parameters.make_control = False except AttributeError: pass + pbone = obj.pose.bones[bones['tail.002']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.002']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -1556,6 +1528,14 @@ def create(obj): pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass + pbone = obj.pose.bones[bones['tail.003']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.003']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -1588,14 +1568,26 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.004']] + pbone = obj.pose.bones[bones['tail.004']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['spine.004']] + pbone.rigify_type = 'spines.super_head' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass pbone = obj.pose.bones[bones['Breast.C']] pbone.rigify_type = 'basic.super_copy' pbone.lock_location = (False, False, False) @@ -2983,9 +2975,12 @@ def create(obj): bone.select = True bone.select_head = True bone.select_tail = True + bone.bbone_x = bone.bbone_z = bone.length * 0.05 arm.edit_bones.active = bone arm.layers = [(x in [0, 3, 5, 7, 10, 13, 16, 19]) for x in range(32)] + return bones + if __name__ == "__main__": create(bpy.context.active_object) diff --git a/rigify/metarigs/Animals/horse.py b/rigify/metarigs/Animals/horse.py index 358a9ceb..6c94a19d 100644 --- a/rigify/metarigs/Animals/horse.py +++ b/rigify/metarigs/Animals/horse.py @@ -163,60 +163,38 @@ def create(obj): arm.rigify_layers[28].selset = False arm.rigify_layers[28].group = 1 - bones = {} - bone = arm.edit_bones.new('spine') - bone.head[:] = -0.0000, 1.7610, 1.1153 - bone.tail[:] = -0.0000, 1.5754, 1.1088 - bone.roll = -0.0000 - bone.use_connect = False - bones['spine'] = bone.name - bone = arm.edit_bones.new('spine.001') - bone.head[:] = -0.0000, 1.5754, 1.1088 - bone.tail[:] = -0.0000, 1.3779, 1.1589 - bone.roll = -0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine']] - bones['spine.001'] = bone.name - bone = arm.edit_bones.new('spine.002') - bone.head[:] = -0.0000, 1.3779, 1.1589 - bone.tail[:] = -0.0000, 1.1423, 1.3128 - bone.roll = -0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.001']] - bones['spine.002'] = bone.name - bone = arm.edit_bones.new('spine.003') - bone.head[:] = -0.0000, 1.1423, 1.3128 - bone.tail[:] = -0.0000, 1.0291, 1.4191 - bone.roll = -0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.002']] - bones['spine.003'] = bone.name - bone = arm.edit_bones.new('spine.004') - bone.head[:] = -0.0000, 1.0291, 1.4191 - bone.tail[:] = -0.0000, 0.9228, 1.4526 - bone.roll = -0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.003']] - bones['spine.004'] = bone.name bone = arm.edit_bones.new('spine.005') bone.head[:] = -0.0000, 0.9228, 1.4526 bone.tail[:] = -0.0000, 0.6989, 1.4910 bone.roll = -0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.004']] + bone.use_connect = False bones['spine.005'] = bone.name + bone = arm.edit_bones.new('spine.004') + bone.head[:] = -0.0000, 0.9228, 1.4526 + bone.tail[:] = -0.0000, 1.0291, 1.4191 + bone.roll = -0.0000 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['spine.005']] + bones['spine.004'] = bone.name bone = arm.edit_bones.new('spine.006') bone.head[:] = -0.0000, 0.6989, 1.4910 - bone.tail[:] = -0.0000, 0.3824, 1.3801 + bone.tail[:] = 0.0000, 0.3824, 1.3801 bone.roll = -0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['spine.005']] bones['spine.006'] = bone.name + bone = arm.edit_bones.new('spine.003') + bone.head[:] = -0.0000, 1.0291, 1.4191 + bone.tail[:] = -0.0000, 1.1423, 1.3128 + bone.roll = -0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.004']] + bones['spine.003'] = bone.name bone = arm.edit_bones.new('spine.007') - bone.head[:] = -0.0000, 0.3824, 1.3801 - bone.tail[:] = -0.0000, 0.1316, 1.3086 + bone.head[:] = 0.0000, 0.3824, 1.3801 + bone.tail[:] = 0.0000, 0.1316, 1.3086 bone.roll = 0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['spine.006']] @@ -256,9 +234,16 @@ def create(obj): bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.006']] bones['pelvis'] = bone.name + bone = arm.edit_bones.new('spine.002') + bone.head[:] = -0.0000, 1.1423, 1.3128 + bone.tail[:] = -0.0000, 1.3779, 1.1589 + bone.roll = -0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.003']] + bones['spine.002'] = bone.name bone = arm.edit_bones.new('spine.008') - bone.head[:] = -0.0000, 0.1316, 1.3086 - bone.tail[:] = -0.0000, -0.1712, 1.2964 + bone.head[:] = 0.0000, 0.1316, 1.3086 + bone.tail[:] = 0.0000, -0.1712, 1.2964 bone.roll = 0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['spine.007']] @@ -277,9 +262,16 @@ def create(obj): bone.use_connect = True bone.parent = arm.edit_bones[bones['thigh.R']] bones['shin.R'] = bone.name + bone = arm.edit_bones.new('spine.001') + bone.head[:] = -0.0000, 1.3779, 1.1589 + bone.tail[:] = -0.0000, 1.5754, 1.1088 + bone.roll = -0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.002']] + bones['spine.001'] = bone.name bone = arm.edit_bones.new('spine.009') - bone.head[:] = -0.0000, -0.1712, 1.2964 - bone.tail[:] = -0.0000, -0.4908, 1.3031 + bone.head[:] = 0.0000, -0.1712, 1.2964 + bone.tail[:] = 0.0000, -0.4908, 1.3031 bone.roll = 0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['spine.008']] @@ -305,9 +297,16 @@ def create(obj): bone.use_connect = True bone.parent = arm.edit_bones[bones['shin.R']] bones['foot.R'] = bone.name + bone = arm.edit_bones.new('spine') + bone.head[:] = -0.0000, 1.5754, 1.1088 + bone.tail[:] = -0.0000, 1.7610, 1.1153 + bone.roll = -0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.001']] + bones['spine'] = bone.name bone = arm.edit_bones.new('spine.010') - bone.head[:] = -0.0000, -0.4908, 1.3031 - bone.tail[:] = -0.0000, -0.7593, 1.3786 + bone.head[:] = 0.0000, -0.4908, 1.3031 + bone.tail[:] = 0.0000, -0.7593, 1.3786 bone.roll = 0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['spine.009']] @@ -362,10 +361,10 @@ def create(obj): bone.parent = arm.edit_bones[bones['foot.R']] bones['r_toe.R'] = bone.name bone = arm.edit_bones.new('spine.011') - bone.head[:] = -0.0000, -0.7593, 1.3786 - bone.tail[:] = -0.0000, -0.9004, 1.5475 + bone.head[:] = 0.0000, -0.7593, 1.3786 + bone.tail[:] = 0.0000, -0.9004, 1.5475 bone.roll = 0.0000 - bone.use_connect = True + bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.010']] bones['spine.011'] = bone.name bone = arm.edit_bones.new('hair_base.05') @@ -629,8 +628,8 @@ def create(obj): bones['jaw.001'] = bone.name bpy.ops.object.mode_set(mode='OBJECT') - pbone = obj.pose.bones[bones['spine']] - pbone.rigify_type = 'spines.super_spine' + pbone = obj.pose.bones[bones['spine.005']] + pbone.rigify_type = 'spines.basic_spine' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -638,7 +637,7 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.neck_pos = 12 + pbone.rigify_parameters.pivot_pos = 3 except AttributeError: pass try: @@ -646,37 +645,37 @@ def create(obj): except AttributeError: pass try: - pbone.rigify_parameters.use_tail = True + pbone.rigify_parameters.fk_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass + pbone = obj.pose.bones[bones['spine.004']] + pbone.rigify_type = 'spines.basic_tail' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.pivot_pos = 8 + pbone.rigify_parameters.copy_rotation_axes = [True, False, True] except AttributeError: pass try: - pbone.rigify_parameters.tail_pos = 5 + pbone.rigify_parameters.connect_chain = True except AttributeError: pass try: - pbone.rigify_parameters.copy_rotation_axes = [True, False, True] + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass - pbone = obj.pose.bones[bones['spine.001']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.002']] + pbone = obj.pose.bones[bones['spine.006']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.003']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -685,30 +684,6 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.004']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.005']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.006']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.007']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -805,6 +780,14 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['spine.002']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.008']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -829,6 +812,14 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['spine.001']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.009']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -861,6 +852,14 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['spine']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.010']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -934,13 +933,21 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.011']] - pbone.rigify_type = '' + pbone.rigify_type = 'spines.super_head' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass pbone = obj.pose.bones[bones['hair_base.05']] pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) @@ -1364,9 +1371,12 @@ def create(obj): bone.select = True bone.select_head = True bone.select_tail = True + bone.bbone_x = bone.bbone_z = bone.length * 0.05 arm.edit_bones.active = bone arm.layers = [(x in [0, 3, 4, 7, 10, 13, 16, 19, 21]) for x in range(32)] + return bones + if __name__ == "__main__": create(bpy.context.active_object) diff --git a/rigify/metarigs/Animals/shark.py b/rigify/metarigs/Animals/shark.py index 1ee1d0d9..8b72a965 100644 --- a/rigify/metarigs/Animals/shark.py +++ b/rigify/metarigs/Animals/shark.py @@ -165,40 +165,40 @@ def create(obj): bones = {} - bone = arm.edit_bones.new('spine') - bone.head[:] = -0.0000, 1.3362, 0.4776 - bone.tail[:] = -0.0000, 1.0816, 0.4540 - bone.roll = 0.0000 + bone = arm.edit_bones.new('spine.003') + bone.head[:] = -0.0000, 0.3182, 0.4031 + bone.tail[:] = -0.0000, 0.0152, 0.3904 + bone.roll = 0.0001 bone.use_connect = False - bones['spine'] = bone.name - bone = arm.edit_bones.new('spine.001') - bone.head[:] = -0.0000, 1.0816, 0.4540 + bones['spine.003'] = bone.name + bone = arm.edit_bones.new('spine.002') + bone.head[:] = -0.0000, 0.3182, 0.4031 bone.tail[:] = -0.0000, 0.7152, 0.4305 bone.roll = -0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine']] - bones['spine.001'] = bone.name - bone = arm.edit_bones.new('back_fin.T.Bk') - bone.head[:] = 0.0000, 1.2501, 0.5345 - bone.tail[:] = 0.0000, 1.5211, 0.7594 - bone.roll = 0.0000 - bone.use_connect = False - bone.parent = arm.edit_bones[bones['spine']] - bones['back_fin.T.Bk'] = bone.name - bone = arm.edit_bones.new('back_fin.B.Bk') - bone.head[:] = 0.0000, 1.2305, 0.4158 - bone.tail[:] = 0.0000, 1.3289, 0.2452 - bone.roll = 0.0000 bone.use_connect = False - bone.parent = arm.edit_bones[bones['spine']] - bones['back_fin.B.Bk'] = bone.name - bone = arm.edit_bones.new('spine.002') + bone.parent = arm.edit_bones[bones['spine.003']] + bones['spine.002'] = bone.name + bone = arm.edit_bones.new('spine.001') bone.head[:] = -0.0000, 0.7152, 0.4305 - bone.tail[:] = -0.0000, 0.3182, 0.4031 + bone.tail[:] = -0.0000, 1.0816, 0.4540 bone.roll = -0.0000 bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.002']] + bones['spine.001'] = bone.name + bone = arm.edit_bones.new('spine.008') + bone.head[:] = -0.0000, 0.0152, 0.3904 + bone.tail[:] = 0.0000, -0.3259, 0.3967 + bone.roll = 0.0001 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.003']] + bones['spine.008'] = bone.name + bone = arm.edit_bones.new('spine') + bone.head[:] = -0.0000, 1.0816, 0.4540 + bone.tail[:] = -0.0000, 1.3362, 0.4776 + bone.roll = 0.0000 + bone.use_connect = True bone.parent = arm.edit_bones[bones['spine.001']] - bones['spine.002'] = bone.name + bones['spine'] = bone.name bone = arm.edit_bones.new('mid_fin.Top') bone.head[:] = 0.0000, 0.7296, 0.5396 bone.tail[:] = 0.0000, 0.7709, 0.6351 @@ -213,41 +213,6 @@ def create(obj): bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.001']] bones['mid_fin.Bot'] = bone.name - bone = arm.edit_bones.new('back_fin.T.001.Bk') - bone.head[:] = 0.0000, 1.5211, 0.7594 - bone.tail[:] = 0.0000, 1.7667, 0.9633 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['back_fin.T.Bk']] - bones['back_fin.T.001.Bk'] = bone.name - bone = arm.edit_bones.new('back_fin.B.001.Bk') - bone.head[:] = 0.0000, 1.3289, 0.2452 - bone.tail[:] = 0.0000, 1.3818, 0.1513 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['back_fin.B.Bk']] - bones['back_fin.B.001.Bk'] = bone.name - bone = arm.edit_bones.new('spine.003') - bone.head[:] = -0.0000, 0.3182, 0.4031 - bone.tail[:] = -0.0000, 0.0152, 0.3904 - bone.roll = 0.0001 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.002']] - bones['spine.003'] = bone.name - bone = arm.edit_bones.new('back_fin.T.002.Bk') - bone.head[:] = 0.0000, 1.7667, 0.9633 - bone.tail[:] = 0.0000, 1.9489, 1.1145 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['back_fin.T.001.Bk']] - bones['back_fin.T.002.Bk'] = bone.name - bone = arm.edit_bones.new('spine.008') - bone.head[:] = -0.0000, 0.0152, 0.3904 - bone.tail[:] = 0.0000, -0.3259, 0.3967 - bone.roll = 0.0001 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.003']] - bones['spine.008'] = bone.name bone = arm.edit_bones.new('spine.004') bone.head[:] = 0.0000, -0.3259, 0.3967 bone.tail[:] = 0.0000, -0.5947, 0.4044 @@ -269,6 +234,20 @@ def create(obj): bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.008']] bones['chest_fin.Bot.R'] = bone.name + bone = arm.edit_bones.new('back_fin.T.Bk') + bone.head[:] = 0.0000, 1.2501, 0.5345 + bone.tail[:] = 0.0000, 1.5211, 0.7594 + bone.roll = 0.0000 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['spine']] + bones['back_fin.T.Bk'] = bone.name + bone = arm.edit_bones.new('back_fin.B.Bk') + bone.head[:] = 0.0000, 1.2305, 0.4158 + bone.tail[:] = 0.0000, 1.3289, 0.2452 + bone.roll = 0.0000 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['spine']] + bones['back_fin.B.Bk'] = bone.name bone = arm.edit_bones.new('spine.005') bone.head[:] = 0.0000, -0.5947, 0.4044 bone.tail[:] = 0.0000, -1.2084, 0.4328 @@ -283,11 +262,25 @@ def create(obj): bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.004']] bones['top_fin'] = bone.name + bone = arm.edit_bones.new('back_fin.T.001.Bk') + bone.head[:] = 0.0000, 1.5211, 0.7594 + bone.tail[:] = 0.0000, 1.7667, 0.9633 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['back_fin.T.Bk']] + bones['back_fin.T.001.Bk'] = bone.name + bone = arm.edit_bones.new('back_fin.B.001.Bk') + bone.head[:] = 0.0000, 1.3289, 0.2452 + bone.tail[:] = 0.0000, 1.3818, 0.1513 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['back_fin.B.Bk']] + bones['back_fin.B.001.Bk'] = bone.name bone = arm.edit_bones.new('spine.006') bone.head[:] = 0.0000, -1.2084, 0.4328 bone.tail[:] = 0.0000, -1.5634, 0.4275 bone.roll = -0.0000 - bone.use_connect = True + bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.005']] bones['spine.006'] = bone.name bone = arm.edit_bones.new('shoulder.L') @@ -311,6 +304,13 @@ def create(obj): bone.use_connect = True bone.parent = arm.edit_bones[bones['top_fin']] bones['top_fin.001'] = bone.name + bone = arm.edit_bones.new('back_fin.T.002.Bk') + bone.head[:] = 0.0000, 1.7667, 0.9633 + bone.tail[:] = 0.0000, 1.9489, 1.1145 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['back_fin.T.001.Bk']] + bones['back_fin.T.002.Bk'] = bone.name bone = arm.edit_bones.new('spine.007') bone.head[:] = 0.0000, -1.5634, 0.4275 bone.tail[:] = 0.0000, -2.0661, 0.4364 @@ -411,8 +411,8 @@ def create(obj): bones['jaw.003.R'] = bone.name bpy.ops.object.mode_set(mode='OBJECT') - pbone = obj.pose.bones[bones['spine']] - pbone.rigify_type = 'spines.super_spine' + pbone = obj.pose.bones[bones['spine.002']] + pbone.rigify_type = 'spines.basic_tail' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -420,70 +420,34 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.neck_pos = 8 - except AttributeError: - pass - try: pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass try: - pbone.rigify_parameters.tail_pos = 3 - except AttributeError: - pass - try: - pbone.rigify_parameters.pivot_pos = 5 - except AttributeError: - pass - try: - pbone.rigify_parameters.use_tail = True + pbone.rigify_parameters.connect_chain = True except AttributeError: pass try: pbone.rigify_parameters.copy_rotation_axes = [True, False, True] except AttributeError: pass - pbone = obj.pose.bones[bones['spine.001']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['back_fin.T.Bk']] - pbone.rigify_type = 'limbs.super_finger' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - except AttributeError: - pass - try: - pbone.rigify_parameters.primary_rotation_axis = "Z" - except AttributeError: - pass - pbone = obj.pose.bones[bones['back_fin.B.Bk']] - pbone.rigify_type = 'limbs.super_finger' + pbone = obj.pose.bones[bones['spine.003']] + pbone.rigify_type = 'spines.basic_spine' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass try: - pbone.rigify_parameters.primary_rotation_axis = "Z" + pbone.rigify_parameters.fk_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass - pbone = obj.pose.bones[bones['spine.002']] + pbone = obj.pose.bones[bones['spine.001']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) @@ -491,23 +455,23 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['mid_fin.Top']] + pbone = obj.pose.bones[bones['spine.008']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['mid_fin.Bot']] + pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['spine']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['back_fin.T.001.Bk']] + pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['mid_fin.Top']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) @@ -515,15 +479,15 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['back_fin.B.001.Bk']] + pbone = obj.pose.bones[bones['mid_fin.Bot']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.003']] + pbone.bone.layers = [False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['spine.004']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) @@ -531,32 +495,32 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['back_fin.T.002.Bk']] - pbone.rigify_type = '' + pbone = obj.pose.bones[bones['chest_fin.Bot.L']] + pbone.rigify_type = 'basic.super_copy' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.008']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.004']] - pbone.rigify_type = '' + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass + pbone = obj.pose.bones[bones['chest_fin.Bot.R']] + pbone.rigify_type = 'basic.super_copy' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['chest_fin.Bot.L']] - pbone.rigify_type = 'basic.super_copy' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass + pbone = obj.pose.bones[bones['back_fin.T.Bk']] + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -564,11 +528,15 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass - pbone = obj.pose.bones[bones['chest_fin.Bot.R']] - pbone.rigify_type = 'basic.super_copy' + try: + pbone.rigify_parameters.primary_rotation_axis = "Z" + except AttributeError: + pass + pbone = obj.pose.bones[bones['back_fin.B.Bk']] + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -576,7 +544,11 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass + try: + pbone.rigify_parameters.primary_rotation_axis = "Z" except AttributeError: pass pbone = obj.pose.bones[bones['spine.005']] @@ -599,14 +571,38 @@ def create(obj): pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass - pbone = obj.pose.bones[bones['spine.006']] + pbone = obj.pose.bones[bones['back_fin.T.001.Bk']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['back_fin.B.001.Bk']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['spine.006']] + pbone.rigify_type = 'spines.super_head' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass pbone = obj.pose.bones[bones['shoulder.L']] pbone.rigify_type = 'basic.super_copy' pbone.lock_location = (False, False, False) @@ -639,6 +635,14 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['back_fin.T.002.Bk']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.007']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -786,9 +790,12 @@ def create(obj): bone.select = True bone.select_head = True bone.select_tail = True + bone.bbone_x = bone.bbone_z = bone.length * 0.05 arm.edit_bones.active = bone arm.layers = [(x in [0, 3, 5, 6, 8, 10]) for x in range(32)] + return bones + if __name__ == "__main__": create(bpy.context.active_object) diff --git a/rigify/metarigs/Animals/wolf.py b/rigify/metarigs/Animals/wolf.py index 4cccf085..f6150c54 100644 --- a/rigify/metarigs/Animals/wolf.py +++ b/rigify/metarigs/Animals/wolf.py @@ -165,40 +165,19 @@ def create(obj): bones = {} - bone = arm.edit_bones.new('spine') - bone.head[:] = 0.0000, 1.1044, 0.7633 - bone.tail[:] = 0.0000, 0.9624, 0.7412 - bone.roll = 0.0000 - bone.use_connect = False - bones['spine'] = bone.name - bone = arm.edit_bones.new('spine.001') - bone.head[:] = 0.0000, 0.9624, 0.7412 - bone.tail[:] = 0.0000, 0.7755, 0.7418 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine']] - bones['spine.001'] = bone.name - bone = arm.edit_bones.new('spine.002') - bone.head[:] = 0.0000, 0.7755, 0.7418 - bone.tail[:] = 0.0000, 0.5547, 0.7568 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.001']] - bones['spine.002'] = bone.name - bone = arm.edit_bones.new('spine.003') - bone.head[:] = 0.0000, 0.5547, 0.7568 - bone.tail[:] = 0.0000, 0.4418, 0.7954 - bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.002']] - bones['spine.003'] = bone.name bone = arm.edit_bones.new('spine.004') bone.head[:] = 0.0000, 0.4418, 0.7954 bone.tail[:] = 0.0000, 0.3546, 0.8059 bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.003']] + bone.use_connect = False bones['spine.004'] = bone.name + bone = arm.edit_bones.new('spine.003') + bone.head[:] = 0.0000, 0.4418, 0.7954 + bone.tail[:] = 0.0000, 0.5547, 0.7568 + bone.roll = 0.0000 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['spine.004']] + bones['spine.003'] = bone.name bone = arm.edit_bones.new('spine.005') bone.head[:] = 0.0000, 0.3546, 0.8059 bone.tail[:] = 0.0000, 0.1803, 0.7782 @@ -206,6 +185,13 @@ def create(obj): bone.use_connect = True bone.parent = arm.edit_bones[bones['spine.004']] bones['spine.005'] = bone.name + bone = arm.edit_bones.new('spine.002') + bone.head[:] = 0.0000, 0.5547, 0.7568 + bone.tail[:] = 0.0000, 0.7755, 0.7418 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.003']] + bones['spine.002'] = bone.name bone = arm.edit_bones.new('spine.006') bone.head[:] = 0.0000, 0.1803, 0.7782 bone.tail[:] = 0.0000, 0.0319, 0.7731 @@ -241,6 +227,13 @@ def create(obj): bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.005']] bones['thigh.R'] = bone.name + bone = arm.edit_bones.new('spine.001') + bone.head[:] = 0.0000, 0.7755, 0.7418 + bone.tail[:] = 0.0000, 0.9624, 0.7412 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.002']] + bones['spine.001'] = bone.name bone = arm.edit_bones.new('spine.007') bone.head[:] = 0.0000, 0.0319, 0.7731 bone.tail[:] = 0.0000, -0.0980, 0.7945 @@ -262,6 +255,13 @@ def create(obj): bone.use_connect = True bone.parent = arm.edit_bones[bones['thigh.R']] bones['shin.R'] = bone.name + bone = arm.edit_bones.new('spine') + bone.head[:] = 0.0000, 0.9624, 0.7412 + bone.tail[:] = 0.0000, 1.1044, 0.7633 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.001']] + bones['spine'] = bone.name bone = arm.edit_bones.new('spine.008') bone.head[:] = 0.0000, -0.0980, 0.7945 bone.tail[:] = 0.0000, -0.3618, 0.8375 @@ -287,7 +287,7 @@ def create(obj): bone.head[:] = 0.0000, -0.3618, 0.8375 bone.tail[:] = 0.0000, -0.4253, 0.8585 bone.roll = 0.0000 - bone.use_connect = True + bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.008']] bones['spine.009'] = bone.name bone = arm.edit_bones.new('shoulder.L') @@ -1496,63 +1496,63 @@ def create(obj): bones['brow.T.R.003'] = bone.name bpy.ops.object.mode_set(mode='OBJECT') - pbone = obj.pose.bones[bones['spine']] - pbone.rigify_type = 'spines.super_spine' + pbone = obj.pose.bones[bones['spine.004']] + pbone.rigify_type = 'spines.basic_spine' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.pivot_pos = 4 except AttributeError: pass try: - pbone.rigify_parameters.use_tail = True + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass try: - pbone.rigify_parameters.tail_pos = 4 + pbone.rigify_parameters.fk_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass + pbone = obj.pose.bones[bones['spine.003']] + pbone.rigify_type = 'spines.basic_tail' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.pivot_pos = 8 + pbone.rigify_parameters.copy_rotation_axes = [True, False, True] except AttributeError: pass try: - pbone.rigify_parameters.neck_pos = 10 + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass try: - pbone.rigify_parameters.copy_rotation_axes = [True, False, True] + pbone.rigify_parameters.connect_chain = True except AttributeError: pass - pbone = obj.pose.bones[bones['spine.001']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.002']] + pbone = obj.pose.bones[bones['spine.005']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.tweak_extra_layers = False + pbone.rigify_parameters.neck_pos = 5 except AttributeError: pass try: - pbone.rigify_parameters.tweak_layers = [False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass - pbone = obj.pose.bones[bones['spine.003']] + pbone = obj.pose.bones[bones['spine.002']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) @@ -1560,28 +1560,8 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.004']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - pbone = obj.pose.bones[bones['spine.005']] - pbone.rigify_type = '' - pbone.lock_location = (False, False, False) - pbone.lock_rotation = (False, False, False) - pbone.lock_rotation_w = False - pbone.lock_scale = (False, False, False) - pbone.rotation_mode = 'QUATERNION' - pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.neck_pos = 5 - except AttributeError: - pass - try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.tweak_layers = [False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False] except AttributeError: pass pbone = obj.pose.bones[bones['spine.006']] @@ -1656,6 +1636,14 @@ def create(obj): pbone.rigify_parameters.limb_type = "paw" except AttributeError: pass + pbone = obj.pose.bones[bones['spine.001']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.007']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -1680,6 +1668,14 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone = obj.pose.bones[bones['spine']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.008']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -1705,13 +1701,21 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.009']] - pbone.rigify_type = '' + pbone.rigify_type = 'spines.super_head' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass pbone = obj.pose.bones[bones['shoulder.L']] pbone.rigify_type = 'basic.super_copy' pbone.lock_location = (False, False, False) @@ -3219,9 +3223,12 @@ def create(obj): bone.select = True bone.select_head = True bone.select_tail = True + bone.bbone_x = bone.bbone_z = bone.length * 0.05 arm.edit_bones.active = bone arm.layers = [(x in [0, 3, 4, 5, 7, 10, 13, 16, 19]) for x in range(32)] + return bones + if __name__ == "__main__": create(bpy.context.active_object) diff --git a/rigify/metarigs/Basic/basic_human.py b/rigify/metarigs/Basic/basic_human.py index 01367a7b..01017cf4 100644 --- a/rigify/metarigs/Basic/basic_human.py +++ b/rigify/metarigs/Basic/basic_human.py @@ -252,7 +252,7 @@ def create(obj): bone.head[:] = 0.0000, 0.0114, 1.6582 bone.tail[:] = 0.0000, -0.0130, 1.7197 bone.roll = 0.0000 - bone.use_connect = True + bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.003']] bones['spine.004'] = bone.name bone = arm.edit_bones.new('shoulder.L') @@ -370,7 +370,7 @@ def create(obj): bpy.ops.object.mode_set(mode='OBJECT') pbone = obj.pose.bones[bones['spine']] - pbone.rigify_type = 'spines.super_spine' + pbone.rigify_type = 'spines.basic_spine' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -378,11 +378,11 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.neck_pos = 5 + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.fk_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass pbone = obj.pose.bones[bones['spine.001']] @@ -506,13 +506,21 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.004']] - pbone.rigify_type = '' + pbone.rigify_type = 'spines.super_head' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass pbone = obj.pose.bones[bones['shoulder.L']] pbone.rigify_type = 'basic.super_copy' pbone.lock_location = (False, False, False) @@ -676,6 +684,7 @@ def create(obj): bone.select = True bone.select_head = True bone.select_tail = True + bone.bbone_x = bone.bbone_z = bone.length * 0.05 arm.edit_bones.active = bone arm.layers = [(x in [3, 7, 10, 13, 16]) for x in range(32)] diff --git a/rigify/metarigs/Basic/basic_quadruped.py b/rigify/metarigs/Basic/basic_quadruped.py index 5aa9f657..5282df25 100644 --- a/rigify/metarigs/Basic/basic_quadruped.py +++ b/rigify/metarigs/Basic/basic_quadruped.py @@ -165,40 +165,40 @@ def create(obj): bones = {} - bone = arm.edit_bones.new('spine') - bone.head[:] = 0.0000, 1.1044, 0.7633 - bone.tail[:] = 0.0000, 0.9624, 0.7412 + bone = arm.edit_bones.new('spine.004') + bone.head[:] = 0.0000, 0.4418, 0.7954 + bone.tail[:] = 0.0000, 0.3546, 0.8059 bone.roll = 0.0000 bone.use_connect = False - bones['spine'] = bone.name - bone = arm.edit_bones.new('spine.001') - bone.head[:] = 0.0000, 0.9624, 0.7412 - bone.tail[:] = 0.0000, 0.7755, 0.7418 + bones['spine.004'] = bone.name + bone = arm.edit_bones.new('spine.003') + bone.head[:] = 0.0000, 0.4418, 0.7954 + bone.tail[:] = 0.0000, 0.5547, 0.7568 bone.roll = 0.0000 - bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine']] - bones['spine.001'] = bone.name + bone.use_connect = False + bone.parent = arm.edit_bones[bones['spine.004']] + bones['spine.003'] = bone.name bone = arm.edit_bones.new('spine.002') - bone.head[:] = 0.0000, 0.7755, 0.7418 - bone.tail[:] = 0.0000, 0.5547, 0.7568 + bone.head[:] = 0.0000, 0.5547, 0.7568 + bone.tail[:] = 0.0000, 0.7755, 0.7418 bone.roll = 0.0000 bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.001']] + bone.parent = arm.edit_bones[bones['spine.003']] bones['spine.002'] = bone.name - bone = arm.edit_bones.new('spine.003') - bone.head[:] = 0.0000, 0.5547, 0.7568 - bone.tail[:] = 0.0000, 0.4418, 0.7954 + bone = arm.edit_bones.new('spine.001') + bone.head[:] = 0.0000, 0.7755, 0.7418 + bone.tail[:] = 0.0000, 0.9624, 0.7412 bone.roll = 0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['spine.002']] - bones['spine.003'] = bone.name - bone = arm.edit_bones.new('spine.004') - bone.head[:] = 0.0000, 0.4418, 0.7954 - bone.tail[:] = 0.0000, 0.3546, 0.8059 + bones['spine.001'] = bone.name + bone = arm.edit_bones.new('spine') + bone.head[:] = 0.0000, 0.9624, 0.7412 + bone.tail[:] = 0.0000, 1.1044, 0.7633 bone.roll = 0.0000 bone.use_connect = True - bone.parent = arm.edit_bones[bones['spine.003']] - bones['spine.004'] = bone.name + bone.parent = arm.edit_bones[bones['spine.001']] + bones['spine'] = bone.name bone = arm.edit_bones.new('spine.005') bone.head[:] = 0.0000, 0.3546, 0.8059 bone.tail[:] = 0.0000, 0.1803, 0.7782 @@ -287,7 +287,7 @@ def create(obj): bone.head[:] = 0.0000, -0.3618, 0.8375 bone.tail[:] = 0.0000, -0.4253, 0.8585 bone.roll = 0.0000 - bone.use_connect = True + bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.008']] bones['spine.009'] = bone.name bone = arm.edit_bones.new('shoulder.L') @@ -405,37 +405,13 @@ def create(obj): bpy.ops.object.mode_set(mode='OBJECT') pbone = obj.pose.bones[bones['spine']] - pbone.rigify_type = 'spines.super_spine' + pbone.rigify_type = '' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - except AttributeError: - pass - try: - pbone.rigify_parameters.use_tail = True - except AttributeError: - pass - try: - pbone.rigify_parameters.tail_pos = 4 - except AttributeError: - pass - try: - pbone.rigify_parameters.pivot_pos = 8 - except AttributeError: - pass - try: - pbone.rigify_parameters.neck_pos = 10 - except AttributeError: - pass - try: - pbone.rigify_parameters.copy_rotation_axes = [True, False, True] - except AttributeError: - pass pbone = obj.pose.bones[bones['spine.001']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -452,30 +428,42 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] - try: - pbone.rigify_parameters.tweak_extra_layers = False - except AttributeError: - pass - try: - pbone.rigify_parameters.tweak_layers = [False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False] - except AttributeError: - pass pbone = obj.pose.bones[bones['spine.003']] - pbone.rigify_type = '' + pbone.rigify_type = 'spines.basic_tail' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass pbone = obj.pose.bones[bones['spine.004']] - pbone.rigify_type = '' + pbone.rigify_type = 'spines.basic_spine' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.pivot_pos = 4 + except AttributeError: + pass + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass + try: + pbone.rigify_parameters.fk_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass pbone = obj.pose.bones[bones['spine.005']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -484,14 +472,6 @@ def create(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - try: - pbone.rigify_parameters.neck_pos = 5 - except AttributeError: - pass - try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] - except AttributeError: - pass pbone = obj.pose.bones[bones['spine.006']] pbone.rigify_type = '' pbone.lock_location = (False, False, False) @@ -613,13 +593,21 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.009']] - pbone.rigify_type = '' + pbone.rigify_type = 'spines.super_head' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass pbone = obj.pose.bones[bones['shoulder.L']] pbone.rigify_type = 'basic.super_copy' pbone.lock_location = (False, False, False) @@ -811,6 +799,7 @@ def create(obj): bone.select = True bone.select_head = True bone.select_tail = True + bone.bbone_x = bone.bbone_z = bone.length * 0.05 arm.edit_bones.active = bone arm.layers = [(x in [3, 4, 7, 10, 13, 16, 19]) for x in range(32)] diff --git a/rigify/metarigs/human.py b/rigify/metarigs/human.py index bbba8eed..e4279982 100644 --- a/rigify/metarigs/human.py +++ b/rigify/metarigs/human.py @@ -70,10 +70,10 @@ def create(obj): arm.rigify_layers[5].row = 5 arm.rigify_layers[5].selset = False arm.rigify_layers[5].group = 6 - arm.rigify_layers[6].name = "Fingers (Tweak)" + arm.rigify_layers[6].name = "Fingers (Detail)" arm.rigify_layers[6].row = 6 arm.rigify_layers[6].selset = False - arm.rigify_layers[6].group = 4 + arm.rigify_layers[6].group = 5 arm.rigify_layers[7].name = "Arm.L (IK)" arm.rigify_layers[7].row = 7 arm.rigify_layers[7].selset = False @@ -252,7 +252,7 @@ def create(obj): bone.head[:] = 0.0000, 0.0114, 1.6582 bone.tail[:] = 0.0000, -0.0130, 1.7197 bone.roll = 0.0000 - bone.use_connect = True + bone.use_connect = False bone.parent = arm.edit_bones[bones['spine.003']] bones['spine.004'] = bone.name bone = arm.edit_bones.new('shoulder.L') @@ -1280,7 +1280,7 @@ def create(obj): bpy.ops.object.mode_set(mode='OBJECT') pbone = obj.pose.bones[bones['spine']] - pbone.rigify_type = 'spines.super_spine' + pbone.rigify_type = 'spines.basic_spine' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -1288,11 +1288,11 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] try: - pbone.rigify_parameters.neck_pos = 5 + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass try: - pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + pbone.rigify_parameters.fk_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass pbone = obj.pose.bones[bones['spine.001']] @@ -1416,13 +1416,21 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['spine.004']] - pbone.rigify_type = '' + pbone.rigify_type = 'spines.super_head' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass pbone = obj.pose.bones[bones['shoulder.L']] pbone.rigify_type = 'basic.super_copy' pbone.lock_location = (False, False, False) @@ -1956,7 +1964,7 @@ def create(obj): pbone.rotation_mode = 'QUATERNION' pbone.bone.layers = [True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] pbone = obj.pose.bones[bones['f_index.01.L']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -1972,7 +1980,7 @@ def create(obj): except AttributeError: pass pbone = obj.pose.bones[bones['thumb.01.L']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -1988,7 +1996,7 @@ def create(obj): except AttributeError: pass pbone = obj.pose.bones[bones['f_middle.01.L']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -2004,7 +2012,7 @@ def create(obj): except AttributeError: pass pbone = obj.pose.bones[bones['f_ring.01.L']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -2020,7 +2028,7 @@ def create(obj): except AttributeError: pass pbone = obj.pose.bones[bones['f_pinky.01.L']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -2036,7 +2044,7 @@ def create(obj): except AttributeError: pass pbone = obj.pose.bones[bones['f_index.01.R']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -2052,7 +2060,7 @@ def create(obj): except AttributeError: pass pbone = obj.pose.bones[bones['thumb.01.R']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -2068,7 +2076,7 @@ def create(obj): except AttributeError: pass pbone = obj.pose.bones[bones['f_middle.01.R']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -2084,7 +2092,7 @@ def create(obj): except AttributeError: pass pbone = obj.pose.bones[bones['f_ring.01.R']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -2100,7 +2108,7 @@ def create(obj): except AttributeError: pass pbone = obj.pose.bones[bones['f_pinky.01.R']] - pbone.rigify_type = 'limbs.simple_tentacle' + pbone.rigify_type = 'limbs.super_finger' pbone.lock_location = (False, False, False) pbone.lock_rotation = (False, False, False) pbone.lock_rotation_w = False @@ -2710,6 +2718,7 @@ def create(obj): bone.select = True bone.select_head = True bone.select_tail = True + bone.bbone_x = bone.bbone_z = bone.length * 0.05 arm.edit_bones.active = bone arm.layers = [(x in [0, 3, 5, 7, 10, 13, 16]) for x in range(32)] diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py index d7c2e870..bb5a9cbd 100644 --- a/rigify/rig_ui_template.py +++ b/rigify/rig_ui_template.py @@ -36,6 +36,7 @@ UI_IMPORTS = [ 'import math', 'import json', 'import collections', + 'import traceback', 'from math import pi', 'from bpy.props import StringProperty', 'from mathutils import Euler, Matrix, Quaternion, Vector', @@ -1147,7 +1148,6 @@ class ScriptGenerator(base_generate.GeneratorPlugin): def finalize(self): metarig = self.generator.metarig - id_store = self.generator.id_store rig_id = self.generator.rig_id vis_layers = self.obj.data.layers @@ -1162,23 +1162,27 @@ class ScriptGenerator(base_generate.GeneratorPlugin): layer_layout += [(l.name, l.row)] # Generate the UI script - if id_store.rigify_generate_mode == 'overwrite': - rig_ui_name = id_store.rigify_rig_ui or 'rig_ui.py' + if metarig.data.rigify_rig_basename: + rig_ui_name = metarig.data.rigify_rig_basename + '_rig_ui.py' else: rig_ui_name = 'rig_ui.py' - if id_store.rigify_generate_mode == 'overwrite' and rig_ui_name in bpy.data.texts.keys(): - script = bpy.data.texts[rig_ui_name] - script.clear() - else: - script = bpy.data.texts.new("rig_ui.py") + script = None + + if metarig.data.rigify_generate_mode == 'overwrite': + script = metarig.data.rigify_rig_ui + + if not script and rig_ui_name in bpy.data.texts: + script = bpy.data.texts[rig_ui_name] + + if script: + script.clear() + script.name = rig_ui_name - rig_ui_old_name = "" - if id_store.rigify_rig_basename: - rig_ui_old_name = script.name - script.name = id_store.rigify_rig_basename + "_rig_ui.py" + if script is None: + script = bpy.data.texts.new(rig_ui_name) - id_store.rigify_rig_ui = script.name + metarig.data.rigify_rig_ui = script for s in OrderedDict.fromkeys(self.ui_imports): script.write(s + "\n") diff --git a/rigify/rigs/basic/copy_chain.py b/rigify/rigs/basic/copy_chain.py index 5145d735..a3920ced 100644 --- a/rigify/rigs/basic/copy_chain.py +++ b/rigify/rigs/basic/copy_chain.py @@ -22,9 +22,10 @@ import bpy from ..chain_rigs import SimpleChainRig +from ...utils.layers import DEF_LAYER from ...utils.errors import MetarigError from ...utils.rig import connected_children_names -from ...utils.naming import strip_org, make_deformer_name +from ...utils.naming import make_derived_name from ...utils.widgets_basic import create_bone_widget from ...base_rig import BaseRig, stage @@ -41,7 +42,12 @@ class Rig(SimpleChainRig): """ Gather and validate data about the rig. """ self.make_controls = self.params.make_controls - self.make_deforms = self.params.make_deforms + + deform = self.params.make_deforms + rename = self.params.rename_to_deform + + self.make_deforms = deform and not rename + self.rename_deforms = deform and rename ############################## # Control chain @@ -92,6 +98,20 @@ class Rig(SimpleChainRig): if self.make_deforms: super().rig_deform_chain() + ############################## + # Rename To Deform + + def finalize(self): + if self.rename_deform: + new_names = [ self.rename_bone(name, make_derived_name(name, 'def')) for name in self.bones.org ] + + for name in new_names: + bone = self.get_bone(name).bone + bone.use_deform = True + bone.layers = DEF_LAYER + + ############################## + # Parameter UI @classmethod def add_parameters(self, params): @@ -101,6 +121,11 @@ class Rig(SimpleChainRig): params.make_controls = bpy.props.BoolProperty(name="Controls", default=True, description="Create control bones for the copy") params.make_deforms = bpy.props.BoolProperty(name="Deform", default=True, description="Create deform bones for the copy") + params.rename_to_deform = bpy.props.BoolProperty( + name = "Rename To Deform", + default = False, + description = "Rename the original bone itself to use as deform bone (advanced feature)" + ) @classmethod def parameters_ui(self, layout, params): @@ -111,6 +136,10 @@ class Rig(SimpleChainRig): r = layout.row() r.prop(params, "make_deforms") + if params.make_deforms: + r = layout.row() + r.prop(params, "rename_to_deform") + def create_sample(obj): """ Create a sample metarig for this rig type. diff --git a/rigify/rigs/basic/pivot.py b/rigify/rigs/basic/pivot.py new file mode 100644 index 00000000..e5d31659 --- /dev/null +++ b/rigify/rigs/basic/pivot.py @@ -0,0 +1,236 @@ +#====================== 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 compliant> + +import bpy + +from ...base_rig import BaseRig + +from ...utils.naming import make_derived_name +from ...utils.bones import set_bone_widget_transform +from ...utils.widgets_basic import create_cube_widget, create_pivot_widget +from ...utils.switch_parent import SwitchParentBuilder + + +class Rig(BaseRig): + """ A rig providing a rotation pivot control that can be moved. """ + def find_org_bones(self, pose_bone): + return pose_bone.name + + + def initialize(self): + self.make_control = self.params.make_extra_control + self.make_pivot = self.params.make_control or not self.make_control + self.make_deform = self.params.make_extra_deform + + + def generate_bones(self): + org = self.bones.org + + if self.make_control: + self.bones.ctrl.master = name = self.copy_bone(org, make_derived_name(org, 'ctrl'), parent=True) + + if self.make_pivot: + self.bones.ctrl.pivot = self.copy_bone(org, make_derived_name(org, 'ctrl', '_pivot')) + + if self.params.make_parent_switch: + self.build_parent_switch(name) + + if self.params.register_parent: + self.register_parent(name, self.get_parent_tags()) + + else: + self.bones.ctrl.pivot = self.copy_bone(org, make_derived_name(org, 'ctrl'), parent=True) + + if self.make_deform: + self.bones.deform = self.copy_bone(org, make_derived_name(org, 'def'), bbone=True) + + + def build_parent_switch(self, master_name): + pbuilder = SwitchParentBuilder(self.generator) + + org_parent = self.get_bone_parent(self.bones.org) + parents = [org_parent] if org_parent else [] + + pbuilder.build_child( + self, master_name, + context_rig=self.rigify_parent, allow_self=True, + prop_name="Parent ({})".format(master_name), + extra_parents=parents, select_parent=org_parent, + controls=lambda: self.bones.ctrl.flatten() + ) + + def get_parent_tags(self): + tags = {t.strip() for t in self.params.register_parent_tags.split(',')} + + if self.params.make_parent_switch: + tags.add('child') + + tags.discard('') + return tags + + def register_parent(self, master_name, tags): + pbuilder = SwitchParentBuilder(self.generator) + + inject = self.rigify_parent if 'injected' in tags else None + + pbuilder.register_parent( + self, self.bones.org, name=master_name, + inject_into=inject, tags=tags + ) + + + def parent_bones(self): + ctrl = self.bones.ctrl + + if self.make_pivot: + if self.make_control: + self.set_bone_parent(ctrl.pivot, ctrl.master, use_connect=False) + + self.set_bone_parent(self.bones.org, ctrl.pivot, use_connect=False) + + else: + self.set_bone_parent(self.bones.org, ctrl.master, use_connect=False) + + if self.make_deform: + self.set_bone_parent(self.bones.deform, self.bones.org, use_connect=False) + + + def configure_bones(self): + if self.make_control: + self.copy_bone_properties(self.bones.org, self.bones.ctrl.master) + + else: + self.copy_bone_properties(self.bones.org, self.bones.ctrl.pivot) + + + def rig_bones(self): + if self.make_pivot: + self.make_constraint( + self.bones.org, 'COPY_LOCATION', self.bones.ctrl.pivot, + space='LOCAL', invert_xyz=(True,)*3 + ) + + + def generate_widgets(self): + if self.make_pivot: + create_pivot_widget(self.obj, self.bones.ctrl.pivot, square=True, axis_size=2.0) + + if self.make_control: + set_bone_widget_transform(self.obj, self.bones.ctrl.master, self.bones.org) + + create_cube_widget(self.obj, self.bones.ctrl.master, radius=0.5) + + + @classmethod + def add_parameters(self, params): + params.make_control = bpy.props.BoolProperty( + name = "Control", + default = True, + description = "Create a control bone for the copy" + ) + + params.make_parent_switch = bpy.props.BoolProperty( + name = "Switchable Parent", + default = False, + description = "Allow switching the parent of the master control" + ) + + params.register_parent = bpy.props.BoolProperty( + name = "Register Parent", + default = False, + description = "Register the control as a switchable parent candidate" + ) + + params.register_parent_tags = bpy.props.StringProperty( + name = "Parent Tags", + default = "", + description = "Comma-separated tags to use for the registered parent" + ) + + params.make_extra_control = bpy.props.BoolProperty( + name = "Extra Control", + default = False, + description = "Create an optional control" + ) + + params.make_extra_deform = bpy.props.BoolProperty( + name = "Extra Deform", + default = False, + description = "Create an optional deform bone" + ) + + + @classmethod + def parameters_ui(self, layout, params): + r = layout.row() + r.prop(params, "make_extra_control", text="Master Control") + + if params.make_extra_control: + layout.prop(params, "make_parent_switch") + layout.prop(params, "register_parent") + + r = layout.row() + r.active = params.register_parent + r.prop(params, "register_parent_tags", text="Tags") + + layout.prop(params, "make_control", text="Pivot Control") + + r = layout.row() + r.prop(params, "make_extra_deform", text="Deform Bone") + + +def create_sample(obj): + """ Create a sample metarig for this rig type. + """ + # generated by rigify.utils.write_metarig + bpy.ops.object.mode_set(mode='EDIT') + arm = obj.data + + bones = {} + + bone = arm.edit_bones.new('pivot') + bone.head[:] = 0.0000, 0.0000, 0.0000 + bone.tail[:] = 0.0000, 0.5000, 0.0000 + bone.roll = 0.0000 + bone.use_connect = False + bones['pivot'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['pivot']] + pbone.rigify_type = 'basic.pivot' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + + bpy.ops.object.mode_set(mode='EDIT') + for bone in arm.edit_bones: + bone.select = False + bone.select_head = False + bone.select_tail = False + for b in bones: + bone = arm.edit_bones[bones[b]] + bone.select = True + bone.select_head = True + bone.select_tail = True + arm.edit_bones.active = bone + + return bones diff --git a/rigify/rigs/basic/super_copy.py b/rigify/rigs/basic/super_copy.py index 5abbf22e..f55dce68 100644 --- a/rigify/rigs/basic/super_copy.py +++ b/rigify/rigs/basic/super_copy.py @@ -22,9 +22,12 @@ import bpy from ...base_rig import BaseRig +from ...utils.layers import DEF_LAYER from ...utils.naming import strip_org, make_deformer_name from ...utils.widgets_basic import create_bone_widget, create_circle_widget +from itertools import repeat + class Rig(BaseRig): """ A "copy" rig. All it does is duplicate the original bone and @@ -43,7 +46,14 @@ class Rig(BaseRig): self.make_control = self.params.make_control self.make_widget = self.params.make_widget - self.make_deform = self.params.make_deform + + deform = self.params.make_deform + rename = self.params.rename_to_deform + + self.make_deform = deform and not rename + self.rename_deform = deform and rename + + self.relink = self.params.relink_constraints def generate_bones(self): @@ -64,6 +74,18 @@ class Rig(BaseRig): if self.make_deform: self.set_bone_parent(bones.deform, bones.org, use_connect=False) + if self.relink: + self.generator.disable_auto_parent(bones.org) + + parent_spec = self.params.parent_bone + if parent_spec: + old_parent = self.get_bone_parent(bones.org) + new_parent = self.find_relink_target(parent_spec, old_parent or '') or None + self.set_bone_parent(bones.org, new_parent) + + if self.make_control: + self.set_bone_parent(bones.ctrl, new_parent) + def configure_bones(self): bones = self.bones @@ -75,9 +97,53 @@ class Rig(BaseRig): def rig_bones(self): bones = self.bones + if self.relink: + for con in self.get_bone(bones.org).constraints: + parts = con.name.split('@') + + if len(parts) > 1: + self.relink_constraint(con, parts[1:]) + if self.make_control: # Constrain the original bone. - self.make_constraint(bones.org, 'COPY_TRANSFORMS', bones.ctrl) + self.make_constraint(bones.org, 'COPY_TRANSFORMS', bones.ctrl, insert_index=0) + + def relink_constraint(self, con, specs): + if con.type == 'ARMATURE': + if len(specs) == 1: + specs = repeat(specs[0]) + elif len(specs) != len(con.specs): + self.report_error("Constraint {} actually has {} targets", con.name, len(con.targets)) + + for tgt, spec in zip(con.targets, specs): + tgt.subtarget = self.find_relink_target(spec, tgt.subtarget) + + else: + if len(specs) > 1: + self.report_error("Only the Armature constraint can have multiple '@' targets: {}", con.name) + + con.subtarget = self.find_relink_target(specs[0], con.subtarget) + + def find_relink_target(self, spec, old_target): + if spec == '': + return old_target + elif spec in {'DEF', 'MCH'}: + spec = spec + '-' + strip_org(old_target) + + if spec not in self.obj.pose.bones: + # Hack: allow referring to copy rigs using Rename To Deform as DEF + if old_target.startswith('ORG-') and spec == make_deformer_name(strip_org(old_target)): + from . import copy_chain + + owner = self.generator.bone_owners.get(old_target) + + if ((isinstance(owner, Rig) and owner.rename_deform) or + (isinstance(owner, copy_chain.Rig) and owner.rename_deforms)): + return old_target + + self.report_error("Cannot find bone '{}' for relinking", spec) + + return spec def generate_widgets(self): @@ -91,6 +157,15 @@ class Rig(BaseRig): create_bone_widget(self.obj, bones.ctrl) + def finalize(self): + if self.rename_deform: + new_name = self.rename_bone(self.bones.org, make_deformer_name(self.org_name)) + + bone = self.get_bone(new_name).bone + bone.use_deform = True + bone.layers = DEF_LAYER + + @classmethod def add_parameters(self, params): """ Add the parameters of this rig type to the @@ -114,6 +189,24 @@ class Rig(BaseRig): description = "Create a deform bone for the copy" ) + params.rename_to_deform = bpy.props.BoolProperty( + name = "Rename To Deform", + default = False, + description = "Rename the original bone itself to use as deform bone (advanced feature)" + ) + + params.relink_constraints = bpy.props.BoolProperty( + name = "Relink Constraints", + default = False, + description = "For constraints with names formed like 'base@bonename', use the part after '@' as the new subtarget after all bones are created. Use '@DEF' or '@MCH' to simply prepend the prefix" + ) + + params.parent_bone = bpy.props.StringProperty( + name = "Parent", + default = "", + description = "Replace the parent with a different bone after all bones are created. Using simply DEF or MCH will prepend the prefix instead" + ) + @classmethod def parameters_ui(self, layout, params): @@ -127,6 +220,17 @@ class Rig(BaseRig): r = layout.row() r.prop(params, "make_deform") + if params.make_deform: + r = layout.row() + r.prop(params, "rename_to_deform") + + r = layout.row() + r.prop(params, "relink_constraints") + + if params.relink_constraints: + r = layout.row() + r.prop(params, "parent_bone") + def create_sample(obj): """ Create a sample metarig for this rig type. diff --git a/rigify/rigs/chain_rigs.py b/rigify/rigs/chain_rigs.py index 3f53cd69..fc070eb1 100644 --- a/rigify/rigs/chain_rigs.py +++ b/rigify/rigs/chain_rigs.py @@ -82,10 +82,10 @@ class SimpleChainRig(BaseRig): @stage.generate_widgets def make_control_widgets(self): - for ctrl in self.bones.ctrl.fk: - self.make_control_widget(ctrl) + for args in zip(count(0), self.bones.ctrl.fk): + self.make_control_widget(*args) - def make_control_widget(self, ctrl): + def make_control_widget(self, i, ctrl): create_bone_widget(self.obj, ctrl) ############################## diff --git a/rigify/rigs/experimental/super_chain.py b/rigify/rigs/experimental/super_chain.py index df0facdf..3f7ca5d6 100644 --- a/rigify/rigs/experimental/super_chain.py +++ b/rigify/rigs/experimental/super_chain.py @@ -714,10 +714,10 @@ def add_parameters(params): ) params.bbones = bpy.props.IntProperty( - name='bbone segments', - default=10, - min=1, - description='Number of segments' + name = 'B-Bone Segments', + default = 10, + min = 1, + description = 'Number of B-Bone segments' ) params.wgt_offset = bpy.props.FloatProperty( diff --git a/rigify/rigs/limbs/arm.py b/rigify/rigs/limbs/arm.py index 98a3c50f..e79edc5c 100644 --- a/rigify/rigs/limbs/arm.py +++ b/rigify/rigs/limbs/arm.py @@ -21,12 +21,15 @@ import bpy from itertools import count +from mathutils import Matrix -from ...utils.bones import BoneDict, compute_chain_x_axis, align_bone_x_axis, align_bone_z_axis +from ...utils.bones import put_bone, compute_chain_x_axis, align_bone_x_axis, align_bone_z_axis from ...utils.naming import make_derived_name from ...utils.misc import map_list +from ...utils.widgets import adjust_widget_transform_mesh from ..widgets import create_hand_widget +from ...utils.widgets_basic import create_circle_widget from ...base_rig import stage @@ -42,6 +45,8 @@ class Rig(BaseLimbRig): super().initialize() + self.make_wrist_pivot = self.params.make_ik_wrist_pivot + def prepare_bones(self): orgs = self.bones.org.main @@ -62,16 +67,76 @@ class Rig(BaseLimbRig): def register_switch_parents(self, pbuilder): super().register_switch_parents(pbuilder) - pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True) + pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True, tags={'limb_end'}) def make_ik_ctrl_widget(self, ctrl): create_hand_widget(self.obj, ctrl) #################################################### + # Palm Pivot + + def get_ik_input_bone(self): + if self.make_wrist_pivot: + return self.bones.mch.ik_wrist + else: + return self.get_ik_control_output() + + def get_extra_ik_controls(self): + controls = super().get_extra_ik_controls() + if self.make_wrist_pivot: + controls += [self.bones.ctrl.ik_wrist] + return controls + + @stage.generate_bones + def make_wrist_pivot_control(self): + if self.make_wrist_pivot: + org = self.bones.org.main[2] + self.bones.ctrl.ik_wrist = self.make_wrist_pivot_bone(org) + self.bones.mch.ik_wrist = self.copy_bone(org, make_derived_name(org, 'mch', '_ik_wrist'), scale=0.25) + + def make_wrist_pivot_bone(self, org): + name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_ik_wrist'), scale=0.5) + put_bone(self.obj, name, self.get_bone(org).tail) + return name + + @stage.parent_bones + def parent_wrist_pivot_control(self): + if self.make_wrist_pivot: + ctrl = self.bones.ctrl.ik_wrist + self.set_bone_parent(ctrl, self.get_ik_control_output()) + self.set_bone_parent(self.bones.mch.ik_wrist, ctrl) + + @stage.generate_widgets + def make_wrist_pivot_widget(self): + if self.make_wrist_pivot: + ctrl = self.bones.ctrl.ik_wrist + + if self.main_axis == 'x': + obj = create_circle_widget(self.obj, ctrl, head_tail=-0.3, head_tail_x=0.5) + else: + obj = create_circle_widget(self.obj, ctrl, head_tail=0.5, head_tail_x=-0.3) + + if obj: + org_bone = self.get_bone(self.bones.org.main[2]) + offset = org_bone.head - self.get_bone(ctrl).head + adjust_widget_transform_mesh(obj, Matrix.Translation(offset)) + + #################################################### # Settings @classmethod + def add_parameters(self, params): + super().add_parameters(params) + + params.make_ik_wrist_pivot = bpy.props.BoolProperty( + name="IK Wrist Pivot", default=False, + description="Make an extra IK hand control pivoting around the tip of the hand" + ) + + @classmethod def parameters_ui(self, layout, params): + layout.prop(params, "make_ik_wrist_pivot") + super().parameters_ui(layout, params, 'Hand') diff --git a/rigify/rigs/limbs/leg.py b/rigify/rigs/limbs/leg.py index dbdd20cb..b409d009 100644 --- a/rigify/rigs/limbs/leg.py +++ b/rigify/rigs/limbs/leg.py @@ -22,13 +22,14 @@ import bpy import math from itertools import count -from mathutils import Vector +from mathutils import Vector, Matrix from ...utils.rig import is_rig_base_bone from ...utils.bones import align_chain_x_axis, align_bone_x_axis, align_bone_y_axis, align_bone_z_axis from ...utils.bones import align_bone_to_axis, flip_bone, put_bone, align_bone_orientation from ...utils.naming import make_derived_name -from ...utils.misc import map_list +from ...utils.misc import map_list, matrix_from_axis_roll, matrix_from_axis_pair +from ...utils.widgets import adjust_widget_transform_mesh from ..widgets import create_foot_widget, create_ballsocket_widget @@ -62,10 +63,18 @@ class Rig(BaseLimbRig): super().initialize() + self.pivot_type = self.params.foot_pivot_type + self.heel_euler_order = 'ZXY' if self.main_axis == 'x' else 'XZY' + + assert self.pivot_type in {'ANKLE', 'TOE', 'ANKLE_TOE'} + def prepare_bones(self): orgs = self.bones.org.main + foot = self.get_bone(orgs[2]) - foot_x = self.vector_without_z(self.get_bone(orgs[2]).y_axis).cross((0, 0, -1)) + ik_y_axis = (0, 1, 0) + foot_y_axis = -self.vector_without_z(foot.y_axis) + foot_x = foot_y_axis.cross((0, 0, 1)) if self.params.rotation_axis == 'automatic': align_chain_x_axis(self.obj, orgs[0:2]) @@ -84,6 +93,12 @@ class Rig(BaseLimbRig): align_bone_z_axis(self.obj, orgs[2], foot_x) align_bone_z_axis(self.obj, orgs[3], -foot_x) + else: + ik_y_axis = foot_y_axis + + # Orientation of the IK main and roll control bones + self.ik_matrix = matrix_from_axis_roll(ik_y_axis, 0) + self.roll_matrix = matrix_from_axis_pair(ik_y_axis, foot_x, self.main_axis) #################################################### # EXTRA BONES @@ -92,6 +107,8 @@ class Rig(BaseLimbRig): # heel: # Heel location marker bone # ctrl: + # ik_spin: + # Toe spin control. # heel: # Foot roll control # mch: @@ -104,31 +121,71 @@ class Rig(BaseLimbRig): # IK controls def get_extra_ik_controls(self): - return [self.bones.ctrl.heel] + controls = super().get_extra_ik_controls() + [self.bones.ctrl.heel] + if self.pivot_type == 'ANKLE_TOE': + controls += [self.bones.ctrl.ik_spin] + return controls def make_ik_control_bone(self, orgs): name = self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_ik')) - - if self.params.rotation_axis == 'automatic' or self.params.auto_align_extremity: - align_bone_to_axis(self.obj, name, 'y', flip=True) - + if self.pivot_type == 'TOE': + put_bone(self.obj, name, self.get_bone(name).tail, matrix=self.ik_matrix) else: - flip_bone(self.obj, name) - - bone = self.get_bone(name) - bone.tail[2] = bone.head[2] - bone.roll = 0 - + put_bone(self.obj, name, None, matrix=self.ik_matrix) return name + def build_ik_pivot(self, ik_name, **args): + heel_bone = self.get_bone(self.bones.org.heel) + args = { + 'position': (heel_bone.head + heel_bone.tail)/2, + **args + } + return super().build_ik_pivot(ik_name, **args) + def register_switch_parents(self, pbuilder): super().register_switch_parents(pbuilder) - pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True) + pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True, tags={'limb_end'}) def make_ik_ctrl_widget(self, ctrl): - create_foot_widget(self.obj, ctrl) + obj = create_foot_widget(self.obj, ctrl) + if self.pivot_type != 'TOE': + ctrl = self.get_bone(ctrl) + org = self.get_bone(self.bones.org.main[2]) + offset = org.tail - (ctrl.custom_shape_transform or ctrl).head + adjust_widget_transform_mesh(obj, Matrix.Translation(offset)) + + #################################################### + # IK pivot controls + + def get_ik_pivot_output(self): + if self.pivot_type == 'ANKLE_TOE': + return self.bones.ctrl.ik_spin + else: + return self.get_ik_control_output() + + @stage.generate_bones + def make_ik_pivot_controls(self): + if self.pivot_type == 'ANKLE_TOE': + self.bones.ctrl.ik_spin = self.make_ik_spin_bone(self.bones.org.main) + + def make_ik_spin_bone(self, orgs): + name = self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_spin_ik')) + put_bone(self.obj, name, self.get_bone(orgs[3]).head, matrix=self.ik_matrix, scale=0.5) + return name + + @stage.parent_bones + def parent_ik_pivot_controls(self): + if self.pivot_type == 'ANKLE_TOE': + self.set_bone_parent(self.bones.ctrl.ik_spin, self.get_ik_control_output()) + + @stage.generate_widgets + def make_ik_spin_control_widget(self): + if self.pivot_type == 'ANKLE_TOE': + obj = create_ballsocket_widget(self.obj, self.bones.ctrl.ik_spin, size=0.75) + rotfix = Matrix.Rotation(math.pi/2, 4, self.main_axis.upper()) + adjust_widget_transform_mesh(obj, rotfix, local=True) #################################################### # Heel control @@ -136,25 +193,19 @@ class Rig(BaseLimbRig): @stage.generate_bones def make_heel_control_bone(self): org = self.bones.org.main[2] - name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_heel_ik'), scale=1/2) + name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_heel_ik')) + put_bone(self.obj, name, None, matrix=self.roll_matrix, scale=0.5) self.bones.ctrl.heel = name - self.align_roll_bone(org, name, -self.vector_without_z(self.get_bone(org).vector)) - @stage.parent_bones def parent_heel_control_bone(self): - self.set_bone_parent(self.bones.ctrl.heel, self.bones.ctrl.ik) + self.set_bone_parent(self.bones.ctrl.heel, self.get_ik_pivot_output(), inherit_scale='AVERAGE') @stage.configure_bones def configure_heel_control_bone(self): bone = self.get_bone(self.bones.ctrl.heel) bone.lock_location = True, True, True - - if self.main_axis == 'x': - bone.lock_rotation = False, False, True - else: - bone.lock_rotation = True, False, False - + bone.rotation_mode = self.heel_euler_order bone.lock_scale = True, True, True @stage.generate_widgets @@ -173,34 +224,27 @@ class Rig(BaseLimbRig): def make_roll_mch_bones(self, foot, toe, heel): foot_bone = self.get_bone(foot) heel_bone = self.get_bone(heel) - axis = -self.vector_without_z(foot_bone.vector) - - roll1 = self.copy_bone(foot, make_derived_name(heel, 'mch', '_roll1')) - flip_bone(self.obj, roll1) - self.align_roll_bone(foot, roll1, -foot_bone.vector) + heel_middle = (heel_bone.head + heel_bone.tail) / 2 - roll2 = self.copy_bone(toe, make_derived_name(heel, 'mch', '_roll2'), scale=1/4) - - put_bone(self.obj, roll2, (heel_bone.head + heel_bone.tail) / 2) - self.align_roll_bone(foot, roll2, -axis) + result = self.copy_bone(foot, make_derived_name(foot, 'mch', '_roll'), scale=0.25) + roll1 = self.copy_bone(toe, make_derived_name(heel, 'mch', '_roll1'), scale=0.3) + roll2 = self.copy_bone(toe, make_derived_name(heel, 'mch', '_roll2'), scale=0.3) rock1 = self.copy_bone(heel, make_derived_name(heel, 'mch', '_rock1')) - - align_bone_to_axis(self.obj, rock1, 'y', roll=0, length=heel_bone.length/2, flip=True) - align_bone_y_axis(self.obj, rock1, axis) - rock2 = self.copy_bone(heel, make_derived_name(heel, 'mch', '_rock2')) - align_bone_to_axis(self.obj, rock2, 'y', roll=0, length=heel_bone.length/2) - align_bone_y_axis(self.obj, rock2, axis) + put_bone(self.obj, roll1, None, matrix=self.roll_matrix) + put_bone(self.obj, roll2, heel_middle, matrix=self.roll_matrix) + put_bone(self.obj, rock1, heel_bone.tail, matrix=self.roll_matrix, scale=0.5) + put_bone(self.obj, rock2, heel_bone.head, matrix=self.roll_matrix, scale=0.5) - return [ rock2, rock1, roll2, roll1 ] + return [ rock2, rock1, roll2, roll1, result ] @stage.parent_bones def parent_roll_mch_chain(self): chain = self.bones.mch.heel - self.set_bone_parent(chain[0], self.bones.ctrl.ik) + self.set_bone_parent(chain[0], self.get_ik_pivot_output()) self.parent_bone_chain(chain) @stage.rig_bones @@ -208,28 +252,37 @@ class Rig(BaseLimbRig): self.rig_roll_mch_bones(self.bones.mch.heel, self.bones.ctrl.heel, self.bones.org.heel) def rig_roll_mch_bones(self, chain, heel, org_heel): - rock2, rock1, roll2, roll1 = chain + rock2, rock1, roll2, roll1, result = chain + + # This order is required for correct working of the constraints + for bone in chain: + self.get_bone(bone).rotation_mode = self.heel_euler_order - self.make_constraint(roll1, 'COPY_ROTATION', heel, space='LOCAL') - self.make_constraint(roll2, 'COPY_ROTATION', heel, space='LOCAL') + self.make_constraint(roll1, 'COPY_ROTATION', heel, space='POSE') if self.main_axis == 'x': - self.make_constraint(roll1, 'LIMIT_ROTATION', use_limit_x=True, max_x=DEG_360, space='LOCAL') - self.make_constraint(roll2, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, min_x=-DEG_360, space='LOCAL') + self.make_constraint(roll2, 'COPY_ROTATION', heel, space='LOCAL', use_xyz=(True, False, False)) + self.make_constraint(roll2, 'LIMIT_ROTATION', min_x=-DEG_360, space='LOCAL') else: - self.make_constraint(roll1, 'LIMIT_ROTATION', use_limit_z=True, max_z=DEG_360, space='LOCAL') - self.make_constraint(roll2, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, min_z=-DEG_360, space='LOCAL') + self.make_constraint(roll2, 'COPY_ROTATION', heel, space='LOCAL', use_xyz=(False, False, True)) + self.make_constraint(roll2, 'LIMIT_ROTATION', min_z=-DEG_360, space='LOCAL') direction = self.get_main_axis(self.get_bone(heel)).dot(self.get_bone(org_heel).vector) if direction < 0: rock2, rock1 = rock1, rock2 - self.make_constraint(rock1, 'COPY_ROTATION', heel, space='LOCAL') - self.make_constraint(rock2, 'COPY_ROTATION', heel, space='LOCAL') + self.make_constraint( + rock1, 'COPY_ROTATION', heel, space='LOCAL', + use_xyz=(False, True, False), + ) + self.make_constraint( + rock2, 'COPY_ROTATION', heel, space='LOCAL', + use_xyz=(False, True, False), + ) - self.make_constraint(rock1, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, max_y=DEG_360, space='LOCAL') - self.make_constraint(rock2, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, min_y=-DEG_360, space='LOCAL') + self.make_constraint(rock1, 'LIMIT_ROTATION', max_y=DEG_360, space='LOCAL') + self.make_constraint(rock2, 'LIMIT_ROTATION', min_y=-DEG_360, space='LOCAL') #################################################### @@ -237,7 +290,7 @@ class Rig(BaseLimbRig): def parent_fk_parent_bone(self, i, parent_mch, prev_ctrl, org, prev_org): if i == 3: - align_bone_orientation(self.obj, parent_mch, self.bones.mch.heel[-2]) + align_bone_orientation(self.obj, parent_mch, self.bones.mch.heel[2]) self.set_bone_parent(parent_mch, prev_org, use_connect=True) @@ -246,7 +299,7 @@ class Rig(BaseLimbRig): def rig_fk_parent_bone(self, i, parent_mch, org): if i == 3: - con = self.make_constraint(parent_mch, 'COPY_TRANSFORMS', self.bones.mch.heel[-2]) + con = self.make_constraint(parent_mch, 'COPY_TRANSFORMS', self.bones.mch.heel[2]) self.make_driver(con, 'influence', variables=[(self.prop_bone, 'IK_FK')], polynomial=[1.0, -1.0]) @@ -257,8 +310,6 @@ class Rig(BaseLimbRig): #################################################### # IK system MCH - ik_input_head_tail = 1.0 - def get_ik_input_bone(self): return self.bones.mch.heel[-1] @@ -266,14 +317,35 @@ class Rig(BaseLimbRig): def parent_ik_mch_chain(self): super().parent_ik_mch_chain() - self.set_bone_parent(self.bones.mch.ik_target, self.bones.ctrl.heel) + self.set_bone_parent(self.bones.mch.ik_target, self.bones.mch.heel[-1]) #################################################### # Settings @classmethod + def add_parameters(self, params): + super().add_parameters(params) + + items = [ + ('ANKLE', 'Ankle', + 'The foots pivots at the ankle'), + ('TOE', 'Toe', + 'The foot pivots around the base of the toe'), + ('ANKLE_TOE', 'Ankle and Toe', + 'The foots pivots at the ankle, with extra toe pivot'), + ] + + params.foot_pivot_type = bpy.props.EnumProperty( + items = items, + name = "Foot Pivot", + default = 'ANKLE_TOE' + ) + + @classmethod def parameters_ui(self, layout, params): + layout.prop(params, 'foot_pivot_type') + super().parameters_ui(layout, params, 'Foot') diff --git a/rigify/rigs/limbs/limb_rigs.py b/rigify/rigs/limbs/limb_rigs.py index 81079c05..f1eb8639 100644 --- a/rigify/rigs/limbs/limb_rigs.py +++ b/rigify/rigs/limbs/limb_rigs.py @@ -23,12 +23,12 @@ import json from ...utils.animation import add_generic_snap_fk_to_ik, add_fk_ik_snap_buttons from ...utils.rig import connected_children_names -from ...utils.bones import BoneDict, put_bone, compute_chain_x_axis, align_bone_orientation -from ...utils.bones import align_bone_x_axis, align_bone_y_axis, align_bone_z_axis +from ...utils.bones import BoneDict, put_bone, compute_chain_x_axis, align_bone_orientation, set_bone_widget_transform from ...utils.naming import strip_org, make_derived_name from ...utils.layers import ControlLayersOption from ...utils.misc import pairwise_nozip, padnone, map_list from ...utils.switch_parent import SwitchParentBuilder +from ...utils.components import CustomPivotControl from ...base_rig import stage, BaseRig @@ -64,6 +64,7 @@ class BaseLimbRig(BaseRig): self.segments = self.params.segments self.bbone_segments = self.params.bbones + self.use_ik_pivot = self.params.make_custom_pivot rot_axis = self.params.rotation_axis @@ -124,15 +125,6 @@ class BaseLimbRig(BaseRig): bone = self.get_bone(org) return bone.head + bone.vector * (seg / self.segments) - def align_roll_bone(self, org, name, y_axis): - if y_axis: - align_bone_y_axis(self.obj, name, y_axis) - - if self.main_axis == 'x': - align_bone_x_axis(self.obj, name, self.get_bone(org).x_axis) - else: - align_bone_z_axis(self.obj, name, self.get_bone(org).z_axis) - @staticmethod def vector_without_z(vector): return Vector((vector[0], vector[1], 0)) @@ -154,6 +146,8 @@ class BaseLimbRig(BaseRig): # IK controls # ik_vispole # IK pole visualization. + # ik_pivot + # Custom IK pivot (optional). # mch: # master: # Parent of the master control. @@ -161,6 +155,8 @@ class BaseLimbRig(BaseRig): # FK follow behavior. # fk[]: # FK chain parents (or None) + # ik_pivot + # Custom IK pivot result (optional). # ik_stretch # IK stretch switch implementation. # ik_target @@ -328,22 +324,30 @@ class BaseLimbRig(BaseRig): # IK controls def get_extra_ik_controls(self): - return [] + if self.component_ik_pivot: + return [self.component_ik_pivot.control] + else: + return [] def get_all_ik_controls(self): ctrl = self.bones.ctrl - return [ctrl.ik_base, ctrl.ik_pole, ctrl.ik] + self.get_extra_ik_controls() + controls = [ctrl.ik_base, ctrl.ik_pole, ctrl.ik] + return controls + self.get_extra_ik_controls() @stage.generate_bones def make_ik_controls(self): orgs = self.bones.org.main - self.bones.ctrl.ik_base = self.copy_bone(orgs[0], make_derived_name(orgs[0], 'ctrl', '_ik')) + self.bones.ctrl.ik_base = self.make_ik_base_bone(orgs) self.bones.ctrl.ik_pole = self.make_ik_pole_bone(orgs) - self.bones.ctrl.ik = self.make_ik_control_bone(orgs) + self.bones.ctrl.ik = ik_name = self.make_ik_control_bone(orgs) + self.component_ik_pivot = self.build_ik_pivot(ik_name) self.build_ik_parent_switch(SwitchParentBuilder(self.generator)) + def make_ik_base_bone(self, orgs): + return self.copy_bone(orgs[0], make_derived_name(orgs[0], 'ctrl', '_ik')) + def make_ik_pole_bone(self, orgs): name = self.copy_bone(orgs[0], make_derived_name(orgs[0], 'ctrl', '_ik_target')) @@ -357,10 +361,25 @@ class BaseLimbRig(BaseRig): def make_ik_control_bone(self, orgs): return self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_ik')) + def build_ik_pivot(self, ik_name, **args): + if self.use_ik_pivot: + return CustomPivotControl(self, 'ik_pivot', ik_name, parent=ik_name, **args) + + def get_ik_control_output(self): + if self.component_ik_pivot: + return self.component_ik_pivot.output + else: + return self.bones.ctrl.ik + def register_switch_parents(self, pbuilder): if self.rig_parent_bone: pbuilder.register_parent(self, self.rig_parent_bone) + pbuilder.register_parent( + self, self.get_ik_control_output, name=self.bones.ctrl.ik, + exclude_self=True, tags={'limb_ik', 'child'}, + ) + def build_ik_parent_switch(self, pbuilder): ctrl = self.bones.ctrl @@ -398,9 +417,13 @@ class BaseLimbRig(BaseRig): @stage.generate_widgets def make_ik_control_widgets(self): - self.make_ik_base_widget(self.bones.ctrl.ik_base) - self.make_ik_pole_widget(self.bones.ctrl.ik_pole) - self.make_ik_ctrl_widget(self.bones.ctrl.ik) + ctrl = self.bones.ctrl + + set_bone_widget_transform(self.obj, ctrl.ik, self.get_ik_control_output()) + + self.make_ik_base_widget(ctrl.ik_base) + self.make_ik_pole_widget(ctrl.ik_pole) + self.make_ik_ctrl_widget(ctrl.ik) def make_ik_base_widget(self, ctrl): if self.main_axis == 'x': @@ -453,7 +476,7 @@ class BaseLimbRig(BaseRig): ik_input_head_tail = 0.0 def get_ik_input_bone(self): - return self.bones.ctrl.ik + return self.get_ik_control_output() def get_ik_output_chain(self): return [self.bones.ctrl.ik_base, self.bones.mch.ik_end, self.bones.mch.ik_target] @@ -670,7 +693,13 @@ class BaseLimbRig(BaseRig): @stage.parent_bones def parent_tweak_mch_chain(self): - for mch, entry in zip(self.bones.mch.tweak, self.segment_table_tweak): + for args in zip(count(0), self.bones.mch.tweak, self.segment_table_tweak): + self.parent_tweak_mch_bone(*args) + + def parent_tweak_mch_bone(self, i, mch, entry): + if i == 0: + self.set_bone_parent(mch, self.rig_parent_bone, inherit_scale='FIX_SHEAR') + else: self.set_bone_parent(mch, entry.org) @stage.rig_bones @@ -694,6 +723,10 @@ class BaseLimbRig(BaseRig): elif entry.seg_idx is not None: self.make_constraint(tweak, 'COPY_SCALE', 'root', use_make_uniform=True) + if i == 0: + self.make_constraint(tweak, 'COPY_LOCATION', entry.org) + self.make_constraint(tweak, 'DAMPED_TRACK', entry.org, head_tail=1) + #################################################### # Deform chain @@ -786,17 +819,23 @@ class BaseLimbRig(BaseRig): ) params.segments = bpy.props.IntProperty( - name = 'limb segments', + name = 'Limb Segments', default = 2, min = 1, - description = 'Number of segments' + description = 'Number of limb segments' ) params.bbones = bpy.props.IntProperty( - name = 'bbone segments', + name = 'B-Bone Segments', default = 10, min = 1, - description = 'Number of segments' + description = 'Number of B-Bone segments' + ) + + params.make_custom_pivot = bpy.props.BoolProperty( + name = "Custom Pivot Control", + default = False, + description = "Create a rotation pivot control that can be repositioned arbitrarily" ) # Setting up extra layers for the FK and tweak @@ -820,6 +859,8 @@ class BaseLimbRig(BaseRig): r = layout.row() r.prop(params, "bbones") + layout.prop(params, 'make_custom_pivot', text="Custom IK Pivot") + ControlLayersOption.FK.parameters_ui(layout, params) ControlLayersOption.TWEAK.parameters_ui(layout, params) @@ -843,8 +884,6 @@ class RigifyLimbIk2FkBase: ctrl_bones: StringProperty(name="IK Controls") extra_ctrls: StringProperty(name="Extra IK Controls") - keyflags = None - def init_execute(self, context): if self.fk_bones: self.fk_bone_list = json.loads(self.fk_bones) @@ -921,13 +960,11 @@ class RigifyLimbIk2FkBase: class POSE_OT_rigify_limb_ik2fk(RigifyLimbIk2FkBase, RigifySingleUpdateMixin, bpy.types.Operator): bl_idname = "pose.rigify_limb_ik2fk_" + rig_id bl_label = "Snap IK->FK" - bl_options = {'UNDO', 'INTERNAL'} bl_description = "Snap the IK chain to FK result" class POSE_OT_rigify_limb_ik2fk_bake(RigifyLimbIk2FkBase, RigifyBakeKeyframesMixin, bpy.types.Operator): bl_idname = "pose.rigify_limb_ik2fk_bake_" + rig_id bl_label = "Apply Snap IK->FK To Keyframes" - bl_options = {'UNDO', 'INTERNAL'} bl_description = "Snap the IK chain keyframes to FK result" def execute_scan_curves(self, context, obj): @@ -968,8 +1005,6 @@ SCRIPT_UTILITIES_OP_TOGGLE_POLE = SCRIPT_UTILITIES_OP_SNAP_IK_FK + [''' class RigifyLimbTogglePoleBase(RigifyLimbIk2FkBase): use_pole: bpy.props.BoolProperty(name="Use Pole Vector") - keyflags_switch = None - def save_frame_state(self, context, obj): return get_chain_transform_matrices(obj, self.ik_bone_list) @@ -1007,13 +1042,11 @@ class RigifyLimbTogglePoleBase(RigifyLimbIk2FkBase): class POSE_OT_rigify_limb_toggle_pole(RigifyLimbTogglePoleBase, RigifySingleUpdateMixin, bpy.types.Operator): bl_idname = "pose.rigify_limb_toggle_pole_" + rig_id bl_label = "Toggle Pole" - bl_options = {'UNDO', 'INTERNAL'} bl_description = "Switch the IK chain between pole and rotation" class POSE_OT_rigify_limb_toggle_pole_bake(RigifyLimbTogglePoleBase, RigifyBakeKeyframesMixin, bpy.types.Operator): bl_idname = "pose.rigify_limb_toggle_pole_bake_" + rig_id bl_label = "Apply Toggle Pole To Keyframes" - bl_options = {'UNDO', 'INTERNAL'} bl_description = "Switch the IK chain between pole and rotation over a frame range" def execute_scan_curves(self, context, obj): diff --git a/rigify/rigs/limbs/paw.py b/rigify/rigs/limbs/paw.py index f8cb1f9f..28374eec 100644 --- a/rigify/rigs/limbs/paw.py +++ b/rigify/rigs/limbs/paw.py @@ -84,7 +84,7 @@ class Rig(BaseLimbRig): # IK controls def get_extra_ik_controls(self): - return [self.bones.ctrl.heel] + return super().get_extra_ik_controls() + [self.bones.ctrl.heel] def make_ik_control_bone(self, orgs): name = self.copy_bone(orgs[3], make_derived_name(orgs[2], 'ctrl', '_ik')) @@ -107,7 +107,7 @@ class Rig(BaseLimbRig): def register_switch_parents(self, pbuilder): super().register_switch_parents(pbuilder) - pbuilder.register_parent(self, self.bones.org.main[3], exclude_self=True) + pbuilder.register_parent(self, self.bones.org.main[3], exclude_self=True, tags={'limb_end'}) def make_ik_ctrl_widget(self, ctrl): create_foot_widget(self.obj, ctrl) @@ -126,7 +126,7 @@ class Rig(BaseLimbRig): @stage.parent_bones def parent_heel_control_bone(self): - self.set_bone_parent(self.bones.ctrl.heel, self.bones.ctrl.ik) + self.set_bone_parent(self.bones.ctrl.heel, self.get_ik_control_output()) @stage.configure_bones def configure_heel_control_bone(self): @@ -150,7 +150,7 @@ class Rig(BaseLimbRig): def parent_fk_parent_bone(self, i, parent_mch, prev_ctrl, org, prev_org): if i == 3: self.set_bone_parent(parent_mch, prev_org, use_connect=True) - self.set_bone_parent(self.bones.mch.toe_socket, self.bones.ctrl.ik) + self.set_bone_parent(self.bones.mch.toe_socket, self.get_ik_control_output()) else: super().parent_fk_parent_bone(i, parent_mch, prev_ctrl, org, prev_org) diff --git a/rigify/rigs/limbs/simple_tentacle.py b/rigify/rigs/limbs/simple_tentacle.py index 10bdd2b5..25d26e86 100644 --- a/rigify/rigs/limbs/simple_tentacle.py +++ b/rigify/rigs/limbs/simple_tentacle.py @@ -78,7 +78,7 @@ class Rig(TweakChainRig): ) # Widgets - def make_control_widget(self, ctrl): + def make_control_widget(self, i, ctrl): create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) diff --git a/rigify/rigs/limbs/super_finger.py b/rigify/rigs/limbs/super_finger.py index 1a9171a7..0b3fcd8a 100644 --- a/rigify/rigs/limbs/super_finger.py +++ b/rigify/rigs/limbs/super_finger.py @@ -24,11 +24,12 @@ import re from itertools import count from ...utils.errors import MetarigError -from ...utils.bones import flip_bone, align_chain_x_axis +from ...utils.bones import put_bone, flip_bone, align_chain_x_axis, set_bone_widget_transform from ...utils.naming import make_derived_name from ...utils.widgets import create_widget from ...utils.widgets_basic import create_circle_widget from ...utils.misc import map_list +from ...utils.layers import ControlLayersOption from ...base_rig import stage @@ -40,7 +41,7 @@ class Rig(SimpleChainRig): def initialize(self): super().initialize() - self.bbone_segments = 8 + self.bbone_segments = self.params.bbones self.first_parent = self.get_bone_parent(self.bones.org[0]) def prepare_bones(self): @@ -116,6 +117,8 @@ class Rig(SimpleChainRig): for args in zip(count(0), self.bones.ctrl.fk, self.bones.org + [None]): self.configure_control_bone(*args) + ControlLayersOption.TWEAK.assign(self.params, self.obj, self.bones.ctrl.fk) + def configure_control_bone(self, i, ctrl, org): if org: self.copy_bone_properties(org, ctrl) @@ -125,11 +128,13 @@ class Rig(SimpleChainRig): bone.lock_rotation = (True, True, True) bone.lock_scale = (True, True, True) - def make_control_widget(self, ctrl): + def make_control_widget(self, i, ctrl): if ctrl == self.bones.ctrl.fk[-1]: # Tip control create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.0) else: + set_bone_widget_transform(self.obj, ctrl, self.bones.org[i]) + create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) ############################## @@ -234,11 +239,12 @@ class Rig(SimpleChainRig): def configure_master_properties(self): master = self.bones.ctrl.master - self.make_property(master, 'finger_curve', 0.0, description="Rubber hose finger cartoon effect") + if self.bbone_segments > 1: + self.make_property(master, 'finger_curve', 0.0, description="Rubber hose finger cartoon effect") - # Create UI - panel = self.script.panel_with_selected_check(self, self.bones.ctrl.flatten()) - panel.custom_prop(master, 'finger_curve', text="Curvature", slider=True) + # Create UI + panel = self.script.panel_with_selected_check(self, self.bones.ctrl.flatten()) + panel.custom_prop(master, 'finger_curve', text="Curvature", slider=True) def rig_deform_bone(self, i, deform, org): master = self.bones.ctrl.master @@ -246,8 +252,9 @@ class Rig(SimpleChainRig): self.make_constraint(deform, 'COPY_TRANSFORMS', org) - self.make_driver(bone.bone, 'bbone_easein', variables=[(master, 'finger_curve')]) - self.make_driver(bone.bone, 'bbone_easeout', variables=[(master, 'finger_curve')]) + if self.bbone_segments > 1: + self.make_driver(bone.bone, 'bbone_easein', variables=[(master, 'finger_curve')]) + self.make_driver(bone.bone, 'bbone_easeout', variables=[(master, 'finger_curve')]) ############### # OPTIONS @@ -261,6 +268,15 @@ class Rig(SimpleChainRig): ('-X', '-X manual', ''), ('-Y', '-Y manual', ''), ('-Z', '-Z manual', '')] params.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='automatic') + params.bbones = bpy.props.IntProperty( + name = 'B-Bone Segments', + default = 10, + min = 1, + description = 'Number of B-Bone segments' + ) + + ControlLayersOption.TWEAK.add_parameters(params) + @classmethod def parameters_ui(self, layout, params): """ Create the ui for the rig parameters. @@ -269,6 +285,10 @@ class Rig(SimpleChainRig): r.label(text="Bend rotation axis:") r.prop(params, "primary_rotation_axis", text="") + layout.prop(params, 'bbones') + + ControlLayersOption.TWEAK.parameters_ui(layout, params) + def create_sample(obj): # generated by rigify.utils.write_metarig @@ -321,10 +341,6 @@ def create_sample(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' try: - pbone.rigify_parameters.separate_extra_layers = True - except AttributeError: - pass - try: pbone.rigify_parameters.extra_layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] except AttributeError: pass diff --git a/rigify/rigs/limbs/super_palm.py b/rigify/rigs/limbs/super_palm.py index 8bcbabf8..9c03b2fe 100644 --- a/rigify/rigs/limbs/super_palm.py +++ b/rigify/rigs/limbs/super_palm.py @@ -24,6 +24,7 @@ import re from math import cos, pi from itertools import count, repeat +from rigify.utils.rig import is_rig_base_bone from rigify.utils.naming import strip_org, make_derived_name from rigify.utils.widgets import create_widget from rigify.utils.misc import map_list @@ -43,7 +44,7 @@ def bone_siblings(obj, bone): bones = [] for b in parent.children: - if b.name != bone: + if b.name != bone and not is_rig_base_bone(obj, b.name): bones += [b.name] return bones diff --git a/rigify/rigs/spines/basic_spine.py b/rigify/rigs/spines/basic_spine.py index 269889cf..c2905463 100644 --- a/rigify/rigs/spines/basic_spine.py +++ b/rigify/rigs/spines/basic_spine.py @@ -19,13 +19,16 @@ # <pep8 compliant> import bpy +import math from itertools import count, repeat +from mathutils import Matrix from ...utils.errors import MetarigError from ...utils.layers import ControlLayersOption -from ...utils.naming import strip_org, make_deformer_name, make_mechanism_name -from ...utils.bones import BoneDict, put_bone, align_bone_to_axis +from ...utils.naming import strip_org, make_deformer_name, make_mechanism_name, make_derived_name +from ...utils.bones import BoneDict, put_bone, align_bone_to_axis, align_bone_orientation, set_bone_widget_transform +from ...utils.widgets import adjust_widget_transform_mesh from ...utils.widgets_basic import create_circle_widget from ...utils.misc import map_list @@ -43,6 +46,7 @@ class Rig(BaseSpineRig): # Check if user provided the pivot position self.pivot_pos = self.params.pivot_pos + self.use_fk = self.params.make_fk_controls if not (0 < self.pivot_pos < len(self.bones.org)): self.raise_error("Please specify a valid pivot bone position.") @@ -55,6 +59,9 @@ class Rig(BaseSpineRig): # ctrl: # master, hips, chest: # Main controls. + # fk: + # chest[], hips[]: + # FK controls. # tweak[]: # Tweak control chain. # mch: @@ -73,19 +80,15 @@ class Rig(BaseSpineRig): #################################################### # Master control bone - @stage.generate_bones - def make_master_control(self): - super().make_master_control() - - # Put the main control in the middle of the hip bone - base_bone = self.get_bone(self.bones.org[0]) - put_bone(self.obj, self.bones.ctrl.master, (base_bone.head + base_bone.tail) / 2) + def get_master_control_pos(self, orgs): + base_bone = self.get_bone(orgs[0]) + return (base_bone.head + base_bone.tail) / 2 #################################################### # Main control bones @stage.generate_bones - def make_control_chain(self): + def make_end_control_bones(self): orgs = self.bones.org pivot = self.pivot_pos @@ -103,33 +106,90 @@ class Rig(BaseSpineRig): return name @stage.parent_bones - def parent_control_chain(self): + def parent_end_control_bones(self): ctrl = self.bones.ctrl - self.set_bone_parent(ctrl.hips, ctrl.master) - self.set_bone_parent(ctrl.chest, ctrl.master) - - @stage.configure_bones - def configure_control_chain(self): - pass + pivot = self.get_master_control_output() + self.set_bone_parent(ctrl.hips, pivot) + self.set_bone_parent(ctrl.chest, pivot) @stage.generate_widgets - def make_control_widgets(self): + def make_end_control_widgets(self): ctrl = self.bones.ctrl mch = self.bones.mch - self.make_control_widget(ctrl.hips, mch.wgt_hips) - self.make_control_widget(ctrl.chest, mch.wgt_chest) + self.make_end_control_widget(ctrl.hips, mch.wgt_hips) + self.make_end_control_widget(ctrl.chest, mch.wgt_chest) + + def make_end_control_widget(self, ctrl, wgt_mch): + shape_bone = self.get_bone(wgt_mch) + is_horizontal = abs(shape_bone.z_axis.normalized().y) < 0.7 - def make_control_widget(self, ctrl, wgt_mch): - self.get_bone(ctrl).custom_shape_transform = self.get_bone(wgt_mch) + set_bone_widget_transform(self.obj, ctrl, wgt_mch) - create_circle_widget( + obj = create_circle_widget( self.obj, ctrl, - radius=1.0, + radius=1.2 if is_horizontal else 1.1, head_tail=0.0, head_tail_x=1.0, with_line=False, ) + if is_horizontal: + # Tilt the widget toward the ground for horizontal (animal) spines + angle = math.copysign(28, shape_bone.x_axis.x) + rotmat = Matrix.Rotation(math.radians(angle), 4, 'X') + adjust_widget_transform_mesh(obj, rotmat, local=True) + + #################################################### + # FK control bones + + @stage.generate_bones + def make_control_chain(self): + if self.use_fk: + orgs = self.bones.org + self.bones.ctrl.fk = self.fk_result = BoneDict( + hips = map_list(self.make_control_bone, count(0), orgs[0:self.pivot_pos], repeat(True)), + chest = map_list(self.make_control_bone, count(self.pivot_pos), orgs[self.pivot_pos:], repeat(False)), + ) + + def make_control_bone(self, i, org, is_hip): + name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_fk'), parent=False) + if is_hip: + put_bone(self.obj, name, self.get_bone(name).tail) + return name + + @stage.parent_bones + def parent_control_chain(self): + if self.use_fk: + chain = self.bones.mch.chain + fk = self.bones.ctrl.fk + for child, parent in zip(fk.hips, chain.hips): + self.set_bone_parent(child, parent) + for child, parent in zip(fk.chest, chain.chest): + self.set_bone_parent(child, parent) + + @stage.configure_bones + def configure_control_chain(self): + if self.use_fk: + fk = self.bones.ctrl.fk + for args in zip(count(0), fk.hips + fk.chest, self.bones.org): + self.configure_control_bone(*args) + + ControlLayersOption.FK.assign_rig(self, fk.hips + fk.chest) + + @stage.generate_widgets + def make_control_widgets(self): + if self.use_fk: + fk = self.bones.ctrl.fk + for ctrl in fk.hips: + self.make_control_widget(ctrl, True) + for ctrl in fk.chest: + self.make_control_widget(ctrl, False) + + def make_control_widget(self, ctrl, is_hip): + obj = create_circle_widget(self.obj, ctrl, radius=1.0, head_tail=0.5) + if is_hip: + adjust_widget_transform_mesh(obj, Matrix.Diagonal((1, -1, 1, 1)), local=True) + #################################################### # MCH bones associated with main controls @@ -153,16 +213,16 @@ class Rig(BaseSpineRig): @stage.parent_bones def parent_mch_control_bones(self): mch = self.bones.mch - self.set_bone_parent(mch.pivot, mch.chain.chest[0]) - self.set_bone_parent(mch.wgt_hips, mch.chain.hips[0]) - self.set_bone_parent(mch.wgt_chest, mch.chain.chest[-1]) + fk = self.fk_result + self.set_bone_parent(mch.pivot, fk.chest[0]) + self.set_bone_parent(mch.wgt_hips, fk.hips[0]) + self.set_bone_parent(mch.wgt_chest, fk.chest[-1]) + align_bone_orientation(self.obj, mch.pivot, fk.hips[-1]) @stage.rig_bones def rig_mch_control_bones(self): mch = self.bones.mch - # Is it actually intending to compute average of these, or is this really intentional? - # This effectively adds rotations of the hip and chest controls. - self.make_constraint(mch.pivot, 'COPY_TRANSFORMS', mch.chain.hips[-1], space='LOCAL') + self.make_constraint(mch.pivot, 'COPY_TRANSFORMS', self.fk_result.hips[-1], influence=0.5) #################################################### # MCH chain for distributing hip & chest transform @@ -174,6 +234,8 @@ class Rig(BaseSpineRig): hips = map_list(self.make_mch_bone, orgs[0:self.pivot_pos], repeat(True)), chest = map_list(self.make_mch_bone, orgs[self.pivot_pos:], repeat(False)), ) + if not self.use_fk: + self.fk_result = self.bones.mch.chain def make_mch_bone(self, org, is_hip): name = self.copy_bone(org, make_mechanism_name(strip_org(org)), parent=False) @@ -182,10 +244,13 @@ class Rig(BaseSpineRig): @stage.parent_bones def parent_mch_chain(self): - master = self.bones.ctrl.master + master = self.get_master_control_output() chain = self.bones.mch.chain - self.parent_bone_chain([master, *reversed(chain.hips)]) - self.parent_bone_chain([master, *chain.chest]) + fk = self.fk_result + for child, parent in zip(reversed(chain.hips), [master, *reversed(fk.hips)]): + self.set_bone_parent(child, parent) + for child, parent in zip(chain.chest, [master, *fk.chest]): + self.set_bone_parent(child, parent) @stage.rig_bones def rig_mch_chain(self): @@ -205,7 +270,7 @@ class Rig(BaseSpineRig): @stage.parent_bones def parent_tweak_chain(self): mch = self.bones.mch - chain = mch.chain + chain = self.fk_result parents = [chain.hips[0], *chain.hips[0:-1], mch.pivot, *chain.chest[1:], chain.chest[-1]] for args in zip(self.bones.ctrl.tweak, parents): self.set_bone_parent(*args) @@ -224,6 +289,13 @@ class Rig(BaseSpineRig): super().add_parameters(params) + params.make_fk_controls = bpy.props.BoolProperty( + name="FK Controls", default=True, + description="Generate an FK control chain" + ) + + ControlLayersOption.FK.add_parameters(params) + @classmethod def parameters_ui(self, layout, params): r = layout.row() @@ -231,6 +303,11 @@ class Rig(BaseSpineRig): super().parameters_ui(layout, params) + layout.prop(params, 'make_fk_controls') + + if params.make_fk_controls: + ControlLayersOption.FK.parameters_ui(layout, params) + def create_sample(obj): # generated by rigify.utils.write_metarig diff --git a/rigify/rigs/spines/basic_tail.py b/rigify/rigs/spines/basic_tail.py index 845c3ca3..be054e7d 100644 --- a/rigify/rigs/spines/basic_tail.py +++ b/rigify/rigs/spines/basic_tail.py @@ -23,7 +23,8 @@ import bpy from itertools import count from ...utils.naming import strip_org, make_derived_name -from ...utils.bones import put_bone, flip_bone, is_same_position, connect_bbone_chain_handles, align_bone_orientation +from ...utils.bones import put_bone, flip_bone, is_same_position, connect_bbone_chain_handles +from ...utils.bones import align_bone_orientation, set_bone_widget_transform from ...utils.widgets_basic import create_circle_widget from ...utils.layers import ControlLayersOption from ...utils.misc import map_list @@ -69,7 +70,7 @@ class Rig(BaseHeadTailRig): @stage.generate_widgets def make_master_control_widget(self): bone = self.bones.ctrl.master - self.get_bone(bone).custom_shape_transform = self.get_bone(self.bones.ctrl.tweak[-1]) + set_bone_widget_transform(self.obj, bone, self.bones.ctrl.tweak[-1]) create_ballsocket_widget(self.obj, bone, size=0.7) #################################################### @@ -94,7 +95,7 @@ class Rig(BaseHeadTailRig): ) # Widgets - def make_control_widget(self, ctrl): + def make_control_widget(self, i, ctrl): create_circle_widget(self.obj, ctrl, radius=0.5, head_tail=0.75) #################################################### diff --git a/rigify/rigs/spines/spine_rigs.py b/rigify/rigs/spines/spine_rigs.py index 6628289f..070a6bd3 100644 --- a/rigify/rigs/spines/spine_rigs.py +++ b/rigify/rigs/spines/spine_rigs.py @@ -24,9 +24,10 @@ from itertools import count from ...utils.layers import ControlLayersOption from ...utils.naming import make_derived_name -from ...utils.bones import align_bone_orientation, align_bone_to_axis +from ...utils.bones import align_bone_orientation, align_bone_to_axis, put_bone, set_bone_widget_transform from ...utils.widgets_basic import create_cube_widget from ...utils.switch_parent import SwitchParentBuilder +from ...utils.components import CustomPivotControl from ...base_rig import stage @@ -44,6 +45,7 @@ class BaseSpineRig(TweakChainRig): if len(self.bones.org) < 3: self.raise_error("Input to rig type must be a chain of 3 or more bones.") + self.use_torso_pivot = self.params.make_custom_pivot self.length = sum([self.get_bone(b).length for b in self.bones.org]) #################################################### @@ -52,6 +54,11 @@ class BaseSpineRig(TweakChainRig): # ctrl: # master # Main control. + # master_pivot + # Custom pivot under the master control. + # mch: + # master_pivot + # Final output of the custom pivot. # #################################################### @@ -60,17 +67,43 @@ class BaseSpineRig(TweakChainRig): @stage.generate_bones def make_master_control(self): - self.bones.ctrl.master = name = self.copy_bone(self.bones.org[0], 'torso') + self.bones.ctrl.master = name = self.make_master_control_bone(self.bones.org) + self.component_torso_pivot = self.build_master_pivot(name) + self.build_parent_switch(name) + + def get_master_control_pos(self, orgs): + return self.get_bone(orgs[0]).head + + def make_master_control_bone(self, orgs): + name = self.copy_bone(orgs[0], 'torso') + put_bone(self.obj, name, self.get_master_control_pos(orgs)) align_bone_to_axis(self.obj, name, 'y', length=self.length * 0.6) + return name - self.build_parent_switch(name) + def build_master_pivot(self, master_name, **args): + if self.use_torso_pivot: + return CustomPivotControl( + self, 'master_pivot', master_name, parent=master_name, **args + ) + + def get_master_control_output(self): + if self.component_torso_pivot: + return self.component_torso_pivot.output + else: + return self.bones.ctrl.master def build_parent_switch(self, master_name): pbuilder = SwitchParentBuilder(self.generator) - pbuilder.register_parent(self, master_name, name='Torso') + + org_parent = self.get_bone_parent(self.bones.org[0]) + parents = [org_parent] if org_parent else [] + + pbuilder.register_parent(self, self.get_master_control_output, name='Torso', tags={'torso', 'child'}) + pbuilder.build_child( self, master_name, exclude_self=True, + extra_parents=parents, select_parent=org_parent, prop_id='torso_parent', prop_name='Torso Parent', controls=lambda: self.bones.flatten('ctrl'), ) @@ -78,8 +111,8 @@ class BaseSpineRig(TweakChainRig): self.register_parent_bones(pbuilder) def register_parent_bones(self, pbuilder): - pbuilder.register_parent(self, self.bones.org[0], name='Hips', exclude_self=True) - pbuilder.register_parent(self, self.bones.org[-1], name='Chest', exclude_self=True) + pbuilder.register_parent(self, self.bones.org[0], name='Hips', exclude_self=True, tags={'hips'}) + pbuilder.register_parent(self, self.bones.org[-1], name='Chest', exclude_self=True, tags={'chest'}) @stage.parent_bones def parent_master_control(self): @@ -91,10 +124,9 @@ class BaseSpineRig(TweakChainRig): @stage.generate_widgets def make_master_control_widget(self): - create_cube_widget( - self.obj, self.bones.ctrl.master, - radius=0.5, - ) + master = self.bones.ctrl.master + set_bone_widget_transform(self.obj, master, self.get_master_control_output()) + create_cube_widget(self.obj, master, radius=0.5) #################################################### # Tweak bones @@ -117,11 +149,19 @@ class BaseSpineRig(TweakChainRig): @classmethod def add_parameters(self, params): + params.make_custom_pivot = bpy.props.BoolProperty( + name = "Custom Pivot Control", + default = False, + description = "Create a rotation pivot control that can be repositioned arbitrarily" + ) + # Setting up extra layers for the FK and tweak ControlLayersOption.TWEAK.add_parameters(params) @classmethod def parameters_ui(self, layout, params): + layout.prop(params, 'make_custom_pivot') + ControlLayersOption.TWEAK.parameters_ui(layout, params) diff --git a/rigify/rigs/spines/super_head.py b/rigify/rigs/spines/super_head.py index 9b85e5b5..15f011f7 100644 --- a/rigify/rigs/spines/super_head.py +++ b/rigify/rigs/spines/super_head.py @@ -322,7 +322,10 @@ class Rig(BaseHeadTailRig): def register_parent_bones(self): rig = self.rigify_parent or self builder = SwitchParentBuilder(self.generator) - builder.register_parent(rig, self.bones.org[-1], name='Head', exclude_self=True) + builder.register_parent( + self, self.bones.org[-1], name='Head', + inject_into=rig, exclude_self=True, tags={'head'}, + ) @stage.configure_bones def configure_bbone_chain(self): diff --git a/rigify/rigs/spines/super_spine.py b/rigify/rigs/spines/super_spine.py index 5ed1588e..86021c84 100644 --- a/rigify/rigs/spines/super_spine.py +++ b/rigify/rigs/spines/super_spine.py @@ -81,7 +81,7 @@ class Rig(SubstitutionRig, BoneUtilityMixin): bpy.ops.object.mode_set(mode='OBJECT') # Create the parts - self.assign_params(spine_orgs[0], params_copy, pivot_pos=pivot_pos) + self.assign_params(spine_orgs[0], params_copy, pivot_pos=pivot_pos, make_fk_controls=False) result = [ self.instantiate_rig(basic_spine.Rig, spine_orgs[0]) ] @@ -133,6 +133,8 @@ def add_parameters(params): def parameters_ui(layout, params): """ Create the ui for the rig parameters.""" + layout.label(text="Note: this combined rig is deprecated.", icon='INFO') + r = layout.row(align=True) r.prop(params, "use_head", toggle=True, text="Head") r.prop(params, "use_tail", toggle=True, text="Tail") diff --git a/rigify/ui.py b/rigify/ui.py index caac09ab..005aed80 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -77,6 +77,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): id_store = C.window_manager if obj.mode in {'POSE', 'OBJECT'}: + armature_id_store = C.object.data WARNING = "Warning: Some features may change after generation" show_warning = False @@ -127,7 +128,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): row.enabled = enable_generate_and_advanced - if id_store.rigify_advanced_generation: + if armature_id_store.rigify_advanced_generation: icon = 'UNLOCKED' else: icon = 'LOCKED' @@ -135,12 +136,12 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): col = layout.column() col.enabled = enable_generate_and_advanced row = col.row() - row.prop(id_store, "rigify_advanced_generation", toggle=True, icon=icon) + row.prop(armature_id_store, "rigify_advanced_generation", toggle=True, icon=icon) - if id_store.rigify_advanced_generation: + if armature_id_store.rigify_advanced_generation: row = col.row(align=True) - row.prop(id_store, "rigify_generate_mode", expand=True) + row.prop(armature_id_store, "rigify_generate_mode", expand=True) main_row = col.row(align=True).split(factor=0.3) col1 = main_row.column() @@ -148,41 +149,25 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): col1.label(text="Rig Name") row = col1.row() row.label(text="Target Rig") - row.enabled = (id_store.rigify_generate_mode == "overwrite") + row.enabled = (armature_id_store.rigify_generate_mode == "overwrite") row = col1.row() row.label(text="Target UI") - row.enabled = (id_store.rigify_generate_mode == "overwrite") + row.enabled = (armature_id_store.rigify_generate_mode == "overwrite") row = col2.row(align=True) - row.prop(id_store, "rigify_rig_basename", text="", icon="SORTALPHA") + row.prop(armature_id_store, "rigify_rig_basename", text="", icon="SORTALPHA") row = col2.row(align=True) - for i in range(0, len(id_store.rigify_target_rigs)): - id_store.rigify_target_rigs.remove(0) - - for ob in context.scene.objects: - if type(ob.data) == bpy.types.Armature and "rig_id" in ob.data: - id_store.rigify_target_rigs.add() - id_store.rigify_target_rigs[-1].name = ob.name - - row.prop_search(id_store, "rigify_target_rig", id_store, "rigify_target_rigs", text="", - icon='OUTLINER_OB_ARMATURE') - row.enabled = (id_store.rigify_generate_mode == "overwrite") - - for i in range(0, len(id_store.rigify_rig_uis)): - id_store.rigify_rig_uis.remove(0) - - for t in bpy.data.texts: - id_store.rigify_rig_uis.add() - id_store.rigify_rig_uis[-1].name = t.name + row.prop(armature_id_store, "rigify_target_rig", text="") + row.enabled = (armature_id_store.rigify_generate_mode == "overwrite") row = col2.row() - row.prop_search(id_store, "rigify_rig_ui", id_store, "rigify_rig_uis", text="", icon='TEXT') - row.enabled = (id_store.rigify_generate_mode == "overwrite") + row.prop(armature_id_store, "rigify_rig_ui", text="", icon='TEXT') + row.enabled = (armature_id_store.rigify_generate_mode == "overwrite") row = col.row() - row.prop(id_store, "rigify_force_widget_update") - if id_store.rigify_generate_mode == 'new': + row.prop(armature_id_store, "rigify_force_widget_update") + if armature_id_store.rigify_generate_mode == 'new': row.enabled = False elif obj.mode == 'EDIT': diff --git a/rigify/utils/animation.py b/rigify/utils/animation.py index 62042923..1355a0b6 100644 --- a/rigify/utils/animation.py +++ b/rigify/utils/animation.py @@ -377,20 +377,24 @@ TRANSFORM_PROPS_ROTATION = frozenset(['rotation_euler', 'rotation_quaternion', ' TRANSFORM_PROPS_SCALE = frozenset(['scale']) TRANSFORM_PROPS_ALL = frozenset(TRANSFORM_PROPS_LOCATION | TRANSFORM_PROPS_ROTATION | TRANSFORM_PROPS_SCALE) -class ActionCurveTable(object): +def transform_props_with_locks(lock_location, lock_rotation, lock_scale): + props = set() + if not lock_location: + props |= TRANSFORM_PROPS_LOCATION + if not lock_rotation: + props |= TRANSFORM_PROPS_ROTATION + if not lock_scale: + props |= TRANSFORM_PROPS_SCALE + return props + +class FCurveTable(object): "Table for efficient lookup of FCurves by properties." - def __init__(self, action): - from collections import defaultdict - self.action = find_action(action) - self.curve_map = defaultdict(dict) - self.index_action() + def __init__(self): + self.curve_map = collections.defaultdict(dict) - def index_action(self): - if not self.action: - return - - for curve in self.action.fcurves: + def index_curves(self, curves): + for curve in curves: index = curve.array_index if index < 0: index = 0 @@ -412,6 +416,24 @@ class ActionCurveTable(object): def get_custom_prop_curves(self, ptr, prop): return self.get_prop_curves(ptr, rna_idprop_quote_path(prop)) + +class ActionCurveTable(FCurveTable): + "Table for efficient lookup of Action FCurves by properties." + + def __init__(self, action): + super().__init__() + self.action = find_action(action) + if self.action: + self.index_curves(self.action.fcurves) + +class DriverCurveTable(FCurveTable): + "Table for efficient lookup of Driver FCurves by properties." + + def __init__(self, object): + super().__init__() + self.anim_data = object.animation_data + if self.anim_data: + self.index_curves(self.anim_data.drivers) '''] exec(SCRIPT_UTILITIES_CURVES[-1]) @@ -495,7 +517,23 @@ SCRIPT_UTILITIES_BAKE = SCRIPT_UTILITIES_KEYING + SCRIPT_UTILITIES_CURVES + [''' # Keyframe baking operator framework ## ####################################### -class RigifyBakeKeyframesMixin: +class RigifyOperatorMixinBase: + bl_options = {'UNDO', 'INTERNAL'} + + def init_invoke(self, context): + "Override to initialize the operator before invoke." + + def init_execute(self, context): + "Override to initialize the operator before execute." + + def before_save_state(self, context, rig): + "Override to prepare for saving state." + + def after_save_state(self, context, rig): + "Override to undo before_save_state." + + +class RigifyBakeKeyframesMixin(RigifyOperatorMixinBase): """Basic framework for an operator that updates a set of keyed frames.""" # Utilities @@ -566,6 +604,7 @@ class RigifyBakeKeyframesMixin: self.bake_state = dict() self.keyflags = get_keying_flags(context) + self.keyflags_switch = None if context.window_manager.rigify_transfer_use_all_keys: self.bake_add_curve_frames(self.bake_curve_table.curve_map) @@ -604,9 +643,15 @@ class RigifyBakeKeyframesMixin: scene = context.scene saved_state = self.bake_state - for frame in self.bake_frames: - scene.frame_set(frame) - saved_state[frame] = self.save_frame_state(context, rig) + try: + self.before_save_state(context, rig) + + for frame in self.bake_frames: + scene.frame_set(frame) + saved_state[frame] = self.save_frame_state(context, rig) + + finally: + self.after_save_state(context, rig) def bake_clean_curves_in_range(self, context, curves): "Deletes all keys from the given curves in the bake range." @@ -648,10 +693,6 @@ class RigifyBakeKeyframesMixin: "Override to execute code one time before the bake apply frame scan." pass - def init_execute(self, context): - "Override to initialize the operator." - pass - def execute(self, context): self.init_execute(context) self.bake_init(context) @@ -661,18 +702,20 @@ class RigifyBakeKeyframesMixin: if self.report_bake_empty(): return {'CANCELLED'} - self.bake_save_state(context) + try: + self.bake_save_state(context) - range, range_raw = self.bake_clean_curves_in_range(context, curves) + range, range_raw = self.bake_clean_curves_in_range(context, curves) - self.execute_before_apply(context, self.bake_rig, range, range_raw) + self.execute_before_apply(context, self.bake_rig, range, range_raw) - self.bake_apply_state(context) - return {'FINISHED'} + self.bake_apply_state(context) - def init_invoke(self, context): - "Override to initialize the operator." - pass + except Exception as e: + traceback.print_exc() + self.report({'ERROR'}, 'Exception: ' + str(e)) + + return {'FINISHED'} def invoke(self, context, event): self.init_invoke(context) @@ -683,22 +726,29 @@ class RigifyBakeKeyframesMixin: return context.window_manager.invoke_confirm(self, event) -class RigifySingleUpdateMixin: +class RigifySingleUpdateMixin(RigifyOperatorMixinBase): """Basic framework for an operator that updates only the current frame.""" - def init_execute(self, context): - pass - def execute(self, context): self.init_execute(context) obj = context.active_object self.keyflags = get_autokey_flags(context, ignore_keyset=True) self.keyflags_switch = add_flags_if_set(self.keyflags, {'INSERTKEY_AVAILABLE'}) - self.apply_frame_state(context, obj, self.save_frame_state(context, obj)) - return {'FINISHED'} - def init_invoke(self, context): - pass + try: + try: + self.before_save_state(context, obj) + state = self.save_frame_state(context, obj) + finally: + self.after_save_state(context, obj) + + self.apply_frame_state(context, obj, state) + + except Exception as e: + traceback.print_exc() + self.report({'ERROR'}, 'Exception: ' + str(e)) + + return {'FINISHED'} def invoke(self, context, event): self.init_invoke(context) @@ -773,51 +823,59 @@ def add_clear_keyframes_button(panel, *, bones=[], label='', text=''): # Generic Snap FK to IK operator ## ################################### -SCRIPT_REGISTER_OP_SNAP_FK_IK = ['POSE_OT_rigify_generic_fk2ik', 'POSE_OT_rigify_generic_fk2ik_bake'] +SCRIPT_REGISTER_OP_SNAP = ['POSE_OT_rigify_generic_snap', 'POSE_OT_rigify_generic_snap_bake'] -SCRIPT_UTILITIES_OP_SNAP_FK_IK = [''' -########################### -## Generic Snap FK to IK ## -########################### +SCRIPT_UTILITIES_OP_SNAP = [''' +############################# +## Generic Snap (FK to IK) ## +############################# -class RigifyGenericFk2IkBase: - fk_bones: StringProperty(name="FK Bone Chain") - ik_bones: StringProperty(name="IK Result Bone Chain") - ctrl_bones: StringProperty(name="IK Controls") +class RigifyGenericSnapBase: + input_bones: StringProperty(name="Input Chain") + output_bones: StringProperty(name="Output Chain") + ctrl_bones: StringProperty(name="Input Controls") + tooltip: StringProperty(name="Tooltip", default="FK to IK") + locks: bpy.props.BoolVectorProperty(name="Locked", size=3, default=[False,False,False]) undo_copy_scale: bpy.props.BoolProperty(name="Undo Copy Scale", default=False) - keyflags = None - def init_execute(self, context): - self.fk_bone_list = json.loads(self.fk_bones) - self.ik_bone_list = json.loads(self.ik_bones) + self.input_bone_list = json.loads(self.input_bones) + self.output_bone_list = json.loads(self.output_bones) self.ctrl_bone_list = json.loads(self.ctrl_bones) def save_frame_state(self, context, obj): - return get_chain_transform_matrices(obj, self.ik_bone_list) + return get_chain_transform_matrices(obj, self.input_bone_list) def apply_frame_state(self, context, obj, matrices): set_chain_transforms_from_matrices( - context, obj, self.fk_bone_list, matrices, - undo_copy_scale=self.undo_copy_scale, keyflags=self.keyflags + context, obj, self.output_bone_list, matrices, + undo_copy_scale=self.undo_copy_scale, keyflags=self.keyflags, + no_loc=self.locks[0], no_rot=self.locks[1], no_scale=self.locks[2], ) -class POSE_OT_rigify_generic_fk2ik(RigifyGenericFk2IkBase, RigifySingleUpdateMixin, bpy.types.Operator): - bl_idname = "pose.rigify_generic_fk2ik_" + rig_id - bl_label = "Snap FK->IK" - bl_options = {'UNDO', 'INTERNAL'} - bl_description = "Snap the FK chain to IK result" +class POSE_OT_rigify_generic_snap(RigifyGenericSnapBase, RigifySingleUpdateMixin, bpy.types.Operator): + bl_idname = "pose.rigify_generic_snap_" + rig_id + bl_label = "Snap Bones" + bl_description = "Snap on the current frame" -class POSE_OT_rigify_generic_fk2ik_bake(RigifyGenericFk2IkBase, RigifyBakeKeyframesMixin, bpy.types.Operator): - bl_idname = "pose.rigify_generic_fk2ik_bake_" + rig_id - bl_label = "Apply Snap FK->IK To Keyframes" - bl_options = {'UNDO', 'INTERNAL'} - bl_description = "Snap the FK chain keyframes to IK result" + @classmethod + def description(cls, context, props): + return "Snap " + props.tooltip + " on the current frame" + +class POSE_OT_rigify_generic_snap_bake(RigifyGenericSnapBase, RigifyBakeKeyframesMixin, bpy.types.Operator): + bl_idname = "pose.rigify_generic_snap_bake_" + rig_id + bl_label = "Apply Snap To Keyframes" + bl_description = "Apply snap to keyframes" + + @classmethod + def description(cls, context, props): + return "Apply snap " + props.tooltip + " to keyframes" def execute_scan_curves(self, context, obj): + props = transform_props_with_locks(*self.locks) self.bake_add_bone_frames(self.ctrl_bone_list, TRANSFORM_PROPS_ALL) - return self.bake_get_all_bone_curves(self.fk_bone_list, TRANSFORM_PROPS_ALL) + return self.bake_get_all_bone_curves(self.output_bone_list, props) '''] def add_fk_ik_snap_buttons(panel, op_single, op_bake, *, label=None, rig_name='', properties=None, clear_bones=None, compact=None): @@ -840,25 +898,37 @@ def add_fk_ik_snap_buttons(panel, op_single, op_bake, *, label=None, rig_name='' row.operator(op_bake, text='Action', icon='ACTION_TWEAK', properties=properties) add_clear_keyframes_button(row, bones=clear_bones, text='Clear') -def add_generic_snap_fk_to_ik(panel, *, fk_bones=[], ik_bones=[], ik_ctrl_bones=[], label='FK->IK', rig_name='', undo_copy_scale=False, compact=None, clear=True): +def add_generic_snap(panel, *, output_bones=[], input_bones=[], input_ctrl_bones=[], label='Snap', rig_name='', undo_copy_scale=False, compact=None, clear=True, locks=None, tooltip=None): panel.use_bake_settings() - panel.script.add_utilities(SCRIPT_UTILITIES_OP_SNAP_FK_IK) - panel.script.register_classes(SCRIPT_REGISTER_OP_SNAP_FK_IK) + panel.script.add_utilities(SCRIPT_UTILITIES_OP_SNAP) + panel.script.register_classes(SCRIPT_REGISTER_OP_SNAP) op_props = { - 'fk_bones': json.dumps(fk_bones), - 'ik_bones': json.dumps(ik_bones), - 'ctrl_bones': json.dumps(ik_ctrl_bones), - 'undo_copy_scale': undo_copy_scale, + 'output_bones': json.dumps(output_bones), + 'input_bones': json.dumps(input_bones), + 'ctrl_bones': json.dumps(input_ctrl_bones or input_bones), } - clear_bones = fk_bones if clear else None + if undo_copy_scale: + op_props['undo_copy_scale'] = undo_copy_scale + if locks is not None: + op_props['locks'] = tuple(locks[0:3]) + if tooltip is not None: + op_props['tooltip'] = tooltip + + clear_bones = output_bones if clear else None add_fk_ik_snap_buttons( - panel, 'pose.rigify_generic_fk2ik_{rig_id}', 'pose.rigify_generic_fk2ik_bake_{rig_id}', + panel, 'pose.rigify_generic_snap_{rig_id}', 'pose.rigify_generic_snap_bake_{rig_id}', label=label, rig_name=rig_name, properties=op_props, clear_bones=clear_bones, compact=compact, ) +def add_generic_snap_fk_to_ik(panel, *, fk_bones=[], ik_bones=[], ik_ctrl_bones=[], label='FK->IK', rig_name='', undo_copy_scale=False, compact=None, clear=True): + add_generic_snap( + panel, output_bones=fk_bones, input_bones=ik_bones, input_ctrl_bones=ik_ctrl_bones, + label=label, rig_name=rig_name, undo_copy_scale=undo_copy_scale, compact=compact, clear=clear + ) + ############################### # Module register/unregister ## ############################### diff --git a/rigify/utils/bones.py b/rigify/utils/bones.py index 6a09cee1..854d4428 100644 --- a/rigify/utils/bones.py +++ b/rigify/utils/bones.py @@ -657,3 +657,17 @@ def align_bone_to_axis(obj, bone_name, axis, *, length=None, roll=0, flip=False) bone_e.tail = bone_e.head + vec bone_e.roll = roll + + +def set_bone_widget_transform(obj, bone_name, transform_bone, use_size=True, scale=1.0): + assert obj.mode != 'EDIT' + + bone = obj.pose.bones[bone_name] + + if transform_bone and transform_bone != bone_name: + bone.custom_shape_transform = obj.pose.bones[transform_bone] + else: + bone.custom_shape_transform = None + + bone.use_custom_shape_bone_size = use_size + bone.custom_shape_scale = scale diff --git a/rigify/utils/components.py b/rigify/utils/components.py new file mode 100644 index 00000000..5c1ebcb6 --- /dev/null +++ b/rigify/utils/components.py @@ -0,0 +1,87 @@ +import bpy + +from .naming import make_derived_name +from .bones import put_bone, copy_bone_position, align_bone_orientation +from .widgets_basic import create_pivot_widget +from .misc import force_lazy + +from ..base_rig import RigComponent, stage + + +class CustomPivotControl(RigComponent): + """ + A utility that generates a pivot control with a custom position. + + Generates a control bone, and a MCH output bone. + """ + + def __init__( + self, rig, id_name, org_bone, *, + name=None, parent=None, position=None, matrix=None, + scale=1.0, scale_mch=None, + move_to=None, align_to=None, snap_to=None, + widget_axis=1.5, widget_cap=1.0, widget_square=True, + ): + super().__init__(rig) + + assert rig.generator.stage == 'generate_bones' + + self.bones = rig.bones + self.id_name = id_name + + self.parent = parent + self.scale = scale or 1 + self.scale_mch = scale_mch or (self.scale * 0.7) + self.move_to = move_to + self.align_to = align_to + self.snap_to = snap_to + self.widget_axis = widget_axis + self.widget_cap = widget_cap + self.widget_square = widget_square + + name = name or make_derived_name(org_bone, 'ctrl', '_pivot') + + self.do_make_bones(org_bone, name, position, matrix) + + @property + def control(self): + return self.ctrl + + @property + def output(self): + return self.mch + + def do_make_bones(self, org, name, position, matrix): + self.bones.ctrl[self.id_name] = self.ctrl = self.copy_bone(org, name, parent=not self.parent, scale=self.scale) + self.bones.mch[self.id_name] = self.mch = self.copy_bone(org, make_derived_name(name, 'mch'), scale=self.scale_mch) + + if position or matrix: + put_bone(self.obj, self.ctrl, position, matrix=matrix) + put_bone(self.obj, self.mch, position, matrix=matrix) + + def parent_bones(self): + if self.snap_to: + bone = force_lazy(self.snap_to) + copy_bone_position(self.obj, bone, self.ctrl, scale=self.scale) + copy_bone_position(self.obj, bone, self.mch, scale=self.scale_mch) + + if self.move_to: + pos = self.get_bone(force_lazy(self.move_to)).head + put_bone(self.obj, self.ctrl, pos) + put_bone(self.obj, self.mch, pos) + + if self.align_to: + self.align_to = force_lazy(self.align_to) + align_bone_orientation(self.obj, self.ctrl, self.align_to) + align_bone_orientation(self.obj, self.mch, self.align_to) + + if self.parent: + self.set_bone_parent(self.ctrl, force_lazy(self.parent)) + + self.set_bone_parent(self.mch, self.ctrl) + + def rig_bones(self): + self.make_constraint(self.mch, 'COPY_LOCATION', self.ctrl, space='LOCAL', invert_xyz=(True,)*3) + + def generate_widgets(self): + create_pivot_widget(self.obj, self.ctrl, axis_size=self.widget_axis, cap_size=self.widget_cap, square=self.widget_square) diff --git a/rigify/utils/layers.py b/rigify/utils/layers.py index 7a1bcef8..0cbd41f8 100644 --- a/rigify/utils/layers.py +++ b/rigify/utils/layers.py @@ -71,7 +71,7 @@ class ControlLayersOption: self.toggle_option = self.name+'_layers_extra' self.layers_option = self.name+'_layers' - self.toggle_name = toggle_name if toggle_name else self.toggle_option + self.toggle_name = toggle_name if toggle_name else "Assign " + self.name.title() + " Layers" def get(self, params): if getattr(params, self.toggle_option): @@ -122,10 +122,15 @@ class ControlLayersOption: setattr(params, self.layers_option, prop_layers) def parameters_ui(self, layout, params): - r = layout.row() - r.prop(params, self.toggle_option) - r.active = getattr(params, self.toggle_option) + box = layout.box() + box.prop(params, self.toggle_option) + active = getattr(params, self.toggle_option) + + if not active: + return + + r = box.row() col = r.column(align=True) row = col.row(align=True) diff --git a/rigify/utils/misc.py b/rigify/utils/misc.py index 64367bb7..20fd6a08 100644 --- a/rigify/utils/misc.py +++ b/rigify/utils/misc.py @@ -156,6 +156,14 @@ def map_apply(func, *inputs): # Misc #============================================= + +def force_lazy(value): + if callable(value): + return value() + else: + return value + + def copy_attributes(a, b): keys = dir(a) for key in keys: diff --git a/rigify/utils/rig.py b/rigify/utils/rig.py index 8c646ab5..0c07cfe6 100644 --- a/rigify/utils/rig.py +++ b/rigify/utils/rig.py @@ -283,6 +283,7 @@ def write_metarig(obj, layers=False, func_name="create", groups=False): code.append(" bone.select = True") code.append(" bone.select_head = True") code.append(" bone.select_tail = True") + code.append(" bone.bbone_x = bone.bbone_z = bone.length * 0.05") code.append(" arm.edit_bones.active = bone") # Set appropriate layers visible diff --git a/rigify/utils/switch_parent.py b/rigify/utils/switch_parent.py index c26f5e74..61721266 100644 --- a/rigify/utils/switch_parent.py +++ b/rigify/utils/switch_parent.py @@ -8,7 +8,7 @@ import json from .errors import MetarigError from .naming import strip_prefix, make_derived_name from .mechanism import MechanismUtilityMixin -from .misc import map_list, map_apply +from .misc import map_list, map_apply, force_lazy from ..base_rig import * from ..base_generate import GeneratorPlugin @@ -16,11 +16,6 @@ from ..base_generate import GeneratorPlugin from collections import defaultdict from itertools import count, repeat, chain -def _auto_call(value): - if callable(value): - return value() - else: - return value def _rig_is_child(rig, parent): if parent is None: @@ -56,7 +51,7 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin): ############################## # API - def register_parent(self, rig, bone, *, name=None, is_global=False, exclude_self=False): + def register_parent(self, rig, bone, *, name=None, is_global=False, exclude_self=False, inject_into=None, tags=None): """ Registers a bone of the specified rig as a possible parent. @@ -66,6 +61,8 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin): name Name of the parent for mouse-over hint. is_global The parent is accessible to all rigs, instead of just children of owner. exclude_self The parent is invisible to the owner rig itself. + inject_into Make this parent available to children of the specified rig. + tags Set of tags to use for default parent selection. Lazy creation: The bone parameter may be a function creating the bone on demand and @@ -74,10 +71,19 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin): assert not self.frozen assert isinstance(bone, str) or callable(bone) + assert callable(bone) or _rig_is_child(rig, self.generator.bone_owners[bone]) + assert _rig_is_child(rig, inject_into) + + real_rig = rig + + if inject_into and inject_into is not rig: + rig = inject_into + tags = (tags or set()) | {'injected'} entry = { - 'rig': rig, 'bone': bone, 'name': name, - 'is_global': is_global, 'exclude_self': exclude_self, 'used': False, + 'rig': rig, 'bone': bone, 'name': name, 'tags': tags, + 'is_global': is_global, 'exclude_self': exclude_self, + 'real_rig': real_rig, 'used': False, } if is_global: @@ -96,9 +102,13 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin): extra_parents List of bone names or (name, user_name) pairs to use as additional parents. use_parent_mch Create an intermediate MCH bone for the constraints and parent the child to it. select_parent Select the specified bone instead of the last one. + select_tags List of parent tags to try for default selection. ignore_global Ignore the is_global flag of potential parents. exclude_self Ignore parents registered by the rig itself. - context_rig Rig to use for selecting parents. + allow_self Ignore the 'exclude_self' setting of the parent. + context_rig Rig to use for selecting parents; defaults to rig. + no_implicit Only use parents listed as extra_parents. + only_selected Like no_implicit, but allow the 'default' selected parent. prop_bone Name of the bone to add the property to. prop_id Actual name of the control property. @@ -160,7 +170,10 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin): child_option_table = { 'extra_parents': None, 'prop_bone': None, 'prop_id': None, 'prop_name': None, 'controls': None, - 'select_parent': None, 'ignore_global': False, 'exclude_self': False, 'context_rig': None, + 'select_parent': None, 'ignore_global': False, + 'exclude_self': False, 'allow_self': False, + 'context_rig': None, 'select_tags': None, + 'no_implicit': False, 'only_selected': False, 'ctrl_bone': None, 'no_fix_location': False, 'no_fix_rotation': False, 'no_fix_scale': False, 'copy_location': None, 'copy_rotation': None, 'copy_scale': None, @@ -199,16 +212,23 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin): parents = [] for parent in self.get_rig_parent_candidates(child_rig): + parent_rig = parent['rig'] + + # Exclude injected parents + if parent['real_rig'] is not parent_rig: + if _rig_is_child(parent_rig, child_rig): + continue + if parent['rig'] is child_rig: - if parent['exclude_self'] or child['exclude_self']: + if (parent['exclude_self'] and not child['allow_self']) or child['exclude_self']: continue elif parent['is_global'] and not child['ignore_global']: # Can't use parents from own children, even if global (cycle risk) - if _rig_is_child(parent['rig'], child_rig): + if _rig_is_child(parent_rig, child_rig): continue else: # Required to be a child of the parent's rig - if not _rig_is_child(child_rig, parent['rig']): + if not _rig_is_child(child_rig, parent_rig): continue parent['used'] = True @@ -219,7 +239,7 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin): # Call lazy creation for parents for parent in self.parent_list: if parent['used']: - parent['bone'] = _auto_call(parent['bone']) + parent['bone'] = force_lazy(parent['bone']) def parent_bones(self): for child in self.child_list: @@ -248,34 +268,68 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin): # Build the final list of parent bone names parent_map = dict() + parent_tags = defaultdict(set) for parent in child['parents']: if parent['bone'] not in parent_map: parent_map[parent['bone']] = parent['name'] + if parent['tags']: + parent_tags[parent['bone']] |= parent['tags'] last_main_parent_bone = child['parents'][-1]['bone'] - num_main_parents = len(parent_map.items()) + extra_parents = set() - for parent in _auto_call(child['extra_parents'] or []): + for parent in force_lazy(child['extra_parents'] or []): if not isinstance(parent, tuple): parent = (parent, None) + extra_parents.add(parent[0]) if parent[0] not in parent_map: parent_map[parent[0]] = parent[1] + for parent in parent_map: + if parent in self.child_map: + parent_tags[parent] |= {'child'} + parent_bones = list(parent_map.items()) - child['parent_bones'] = parent_bones # Find which bone to select - select_bone = _auto_call(child['select_parent']) or last_main_parent_bone - select_index = num_main_parents + select_bone = force_lazy(child['select_parent']) or last_main_parent_bone + select_tags = force_lazy(child['select_tags']) or [] + + if child['no_implicit']: + assert len(extra_parents) > 0 + parent_bones = [ item for item in parent_bones if item[0] in extra_parents ] + if last_main_parent_bone not in extra_parents: + last_main_parent_bone = parent_bones[-1][0] + + for tag in select_tags: + tag_set = tag if isinstance(tag, set) else {tag} + matching = [ + bone for (bone, _) in parent_bones + if not tag_set.isdisjoint(parent_tags[bone]) + ] + if len(matching) > 0: + select_bone = matching[-1] + break + + if select_bone not in parent_map: + print("RIGIFY ERROR: Can't find bone '%s' to select as default parent of '%s'\n" % (select_bone, bone)) + select_bone = last_main_parent_bone + + if child['only_selected']: + filter_set = { select_bone, *extra_parents } + parent_bones = [ item for item in parent_bones if item[0] in filter_set ] try: select_index = 1 + next(i for i, (bone, _) in enumerate(parent_bones) if bone == select_bone) except StopIteration: - print("RIGIFY ERROR: Can't find bone '%s' to select as default parent of '%s'\n" % (select_bone, bone)) + select_index = len(parent_bones) + print("RIGIFY ERROR: Invalid default parent '%s' of '%s'\n" % (select_bone, bone)) + + child['parent_bones'] = parent_bones # Create the controlling property - prop_bone = child['prop_bone'] = _auto_call(child['prop_bone']) or bone + prop_bone = child['prop_bone'] = force_lazy(child['prop_bone']) or bone prop_name = child['prop_name'] or child['prop_id'] or 'Parent Switch' prop_id = child['prop_id'] = child['prop_id'] or 'parent_switch' @@ -294,12 +348,12 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin): no_fix = [ child[n] for n in ['no_fix_location', 'no_fix_rotation', 'no_fix_scale'] ] - child['copy'] = [ _auto_call(child[n]) for n in ['copy_location', 'copy_rotation', 'copy_scale'] ] + child['copy'] = [ force_lazy(child[n]) for n in ['copy_location', 'copy_rotation', 'copy_scale'] ] locks = tuple(bool(nofix or copy) for nofix, copy in zip(no_fix, child['copy'])) # Create the script for the property - controls = _auto_call(child['controls']) or set([prop_bone, bone]) + controls = force_lazy(child['controls']) or set([prop_bone, bone]) script = self.generator.script panel = script.panel_with_selected_check(child['rig'], controls) @@ -377,9 +431,6 @@ class RigifySwitchParentBase: items=lambda s,c: RigifySwitchParentBase.parent_items ) - keyflags = None - keyflags_switch = None - def save_frame_state(self, context, obj): return get_transform_matrix(obj, self.bone, with_constraints=False) @@ -398,16 +449,6 @@ class RigifySwitchParentBase: no_loc=self.locks[0], no_rot=self.locks[1], no_scale=self.locks[2] ) - def get_bone_props(self): - props = set() - if not self.locks[0]: - props |= TRANSFORM_PROPS_LOCATION - if not self.locks[1]: - props |= TRANSFORM_PROPS_ROTATION - if not self.locks[2]: - props |= TRANSFORM_PROPS_SCALE - return props - def init_invoke(self, context): pose = context.active_object.pose @@ -440,11 +481,10 @@ class POSE_OT_rigify_switch_parent(RigifySwitchParentBase, RigifySingleUpdateMix class POSE_OT_rigify_switch_parent_bake(RigifySwitchParentBase, RigifyBakeKeyframesMixin, bpy.types.Operator): bl_idname = "pose.rigify_switch_parent_bake_" + rig_id bl_label = "Apply Switch Parent To Keyframes" - bl_options = {'UNDO', 'INTERNAL'} bl_description = "Switch parent over a frame range, adjusting keys to preserve the bone position and orientation" def execute_scan_curves(self, context, obj): - return self.bake_add_bone_frames(self.bone, self.get_bone_props()) + return self.bake_add_bone_frames(self.bone, transform_props_with_locks(*self.locks)) def execute_before_apply(self, context, obj, range, range_raw): self.bake_replace_custom_prop_keys_constant(self.prop_bone, self.prop_id, int(self.selected)) diff --git a/rigify/utils/widgets_basic.py b/rigify/utils/widgets_basic.py index 2848e5bf..8aab5d7b 100644 --- a/rigify/utils/widgets_basic.py +++ b/rigify/utils/widgets_basic.py @@ -129,7 +129,7 @@ def create_bone_widget(rig, bone_name, r1=0.1, l1=0.0, r2=0.04, l2=1.0, bone_tra mesh.update() -def create_pivot_widget(rig, bone_name, axis_size=1.0, cap_size=1.0, square=False, bone_transform_name=None): +def create_pivot_widget(rig, bone_name, axis_size=1.0, cap_size=1.0, square=True, bone_transform_name=None): """Creates a widget similar to Plain Axes empty, but with a cross or a square on the end of each axis line. """ diff --git a/space_view3d_3d_navigation.py b/space_view3d_3d_navigation.py index 1e1cec9d..34074ad0 100644 --- a/space_view3d_3d_navigation.py +++ b/space_view3d_3d_navigation.py @@ -30,8 +30,8 @@ bl_info = { "location": "View3D > Sidebar > View Tab", "description": "Navigate the Camera & 3D View from the Toolshelf", "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/3D_interaction/3D_Navigation", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "3d_view/3d_navigation.html", "category": "3D View", } diff --git a/space_view3d_brush_menus/__init__.py b/space_view3d_brush_menus/__init__.py index 5b1245e5..1a8d677a 100644 --- a/space_view3d_brush_menus/__init__.py +++ b/space_view3d_brush_menus/__init__.py @@ -23,12 +23,11 @@ bl_info = { "name": "Dynamic Brush Menus", "description": "Fast access to brushes & tools in Sculpt and Paint Modes", "author": "Ryan Inch (Imaginer)", - "version": (1, 1, 7), + "version": (1, 1, 8), "blender": (2, 80, 0), "location": "Spacebar in Sculpt/Paint Modes", "warning": '', - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" - "Scripts/3D_interaction/Advanced_UI_Menus", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/interface/brush_menus.html", "category": "Interface"} diff --git a/space_view3d_brush_menus/brushes.py b/space_view3d_brush_menus/brushes.py index 3478d575..4d99b7d5 100644 --- a/space_view3d_brush_menus/brushes.py +++ b/space_view3d_brush_menus/brushes.py @@ -36,6 +36,8 @@ brush_icon = { "CLAY_STRIPS": 'BRUSH_CLAY_STRIPS', "CREASE": 'BRUSH_CREASE', "DRAW": 'BRUSH_SCULPT_DRAW', + "DRAW_SHARP": 'BRUSH_SCULPT_DRAW', + "ELASTIC_DEFORM": 'BRUSH_GRAB', "FILL": 'BRUSH_FILL', "FLATTEN": 'BRUSH_FLATTEN', "GRAB": 'BRUSH_GRAB', @@ -44,6 +46,7 @@ brush_icon = { "MASK": 'BRUSH_MASK', "NUDGE": 'BRUSH_NUDGE', "PINCH": 'BRUSH_PINCH', + "POSE": 'BRUSH_GRAB', "ROTATE": 'BRUSH_ROTATE', "SCRAPE": 'BRUSH_SCRAPE', "SIMPLIFY": 'BRUSH_DATA', diff --git a/space_view3d_math_vis/__init__.py b/space_view3d_math_vis/__init__.py index e110ac52..129a0618 100644 --- a/space_view3d_math_vis/__init__.py +++ b/space_view3d_math_vis/__init__.py @@ -25,8 +25,8 @@ bl_info = { "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/" - "Scripts/3D_interaction/Math_Viz", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "3d_view/math_vis_console.html", "support": "OFFICIAL", "category": "3D View", } diff --git a/space_view3d_stored_views/__init__.py b/space_view3d_stored_views/__init__.py index 9e475a9c..f35d6c16 100644 --- a/space_view3d_stored_views/__init__.py +++ b/space_view3d_stored_views/__init__.py @@ -24,8 +24,8 @@ bl_info = { "blender": (2, 80, 0), "location": "View3D > Sidebar > View > Stored Views", "warning": "", - "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.5/" - "Py/Scripts/3D_interaction/stored_views", + "wiki_url": "https://docs.blender.org/manual/en/dev/addons/" + "3d_view/stored_views.html", "category": "3D View" } |