diff options
author | Julian Eisel <eiseljulian@gmail.com> | 2020-03-10 04:09:05 +0300 |
---|---|---|
committer | Julian Eisel <eiseljulian@gmail.com> | 2020-03-10 04:09:05 +0300 |
commit | 3f13c42a84cca060f6aefd69f107921599b6bee3 (patch) | |
tree | 8f01dbb856b7ed8e19decca37d88a8c469077aa3 | |
parent | c444673d3c8e21e44f570ff8185218f39081a87b (diff) | |
parent | d674b04ac05dc656b58f678949c8aa83c41c96f4 (diff) |
Merge branch 'master' into soc-2019-openxr
106 files changed, 1508 insertions, 549 deletions
diff --git a/add_camera_rigs/__init__.py b/add_camera_rigs/__init__.py index faf06314..6f9f9eeb 100644 --- a/add_camera_rigs/__init__.py +++ b/add_camera_rigs/__init__.py @@ -18,13 +18,12 @@ bl_info = { "name": "Add Camera Rigs", - "author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard", - "version": (1, 4, 2), + "author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard, Flavio Perez", + "version": (1, 4, 3), "blender": (2, 80, 0), "location": "View3D > Add > Camera > Dolly or Crane Rig", "description": "Adds a Camera Rig with UI", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "camera/camera_rigs.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/camera/camera_rigs.html", "tracker_url": "https://github.com/waylow/add_camera_rigs/issues", "category": "Camera", } diff --git a/add_camera_rigs/build_rigs.py b/add_camera_rigs/build_rigs.py index 1a37bc6e..57cadc0e 100644 --- a/add_camera_rigs/build_rigs.py +++ b/add_camera_rigs/build_rigs.py @@ -17,18 +17,33 @@ # ##### END GPL LICENSE BLOCK ##### import bpy -from bpy_extras import object_utils from bpy.types import Operator -from math import radians, pi +from bpy_extras import object_utils +from mathutils import Vector from rna_prop_ui import rna_idprop_ui_prop_get +from math import pi + from .create_widgets import (create_root_widget, - create_widget, - create_camera_widget, - create_aim_widget, - ) + create_camera_widget, create_aim_widget, + create_circle_widget, create_corner_widget) + + +def create_prop_driver(rig, cam, prop_from, prop_to): + """Create driver to a property on the rig""" + driver = cam.data.driver_add(prop_to) + driver.driver.type = 'SCRIPTED' + var = driver.driver.variables.new() + var.name = 'var' + var.type = 'SINGLE_PROP' + # Target the custom bone property + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["%s"]' % prop_from + driver.driver.expression = 'var' -def create_dolly_bones(rig, bone_layers): + +def create_dolly_bones(rig): + """Create bones for the dolly camera rig""" bones = rig.data.edit_bones # Add new bones @@ -39,7 +54,7 @@ def create_dolly_bones(rig, bone_layers): ctrl_aim_child = bones.new("Aim_shape_rotation-MCH") ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7) - ctrl_aim_child.layers = bone_layers + ctrl_aim_child.layers = tuple(i == 1 for i in range(32)) ctrl_aim = bones.new("Aim") ctrl_aim.head = (0.0, 10.0, 1.7) @@ -57,7 +72,8 @@ def create_dolly_bones(rig, bone_layers): ctrl_aim_child.parent = ctrl_aim -def create_crane_bones(rig, bone_layers): +def create_crane_bones(rig): + """Create bones for the crane camera rig""" bones = rig.data.edit_bones # Add new bones @@ -68,7 +84,7 @@ def create_crane_bones(rig, bone_layers): ctrl_aim_child = bones.new("Aim_shape_rotation-MCH") ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7) - ctrl_aim_child.layers = bone_layers + ctrl_aim_child.layers = tuple(i == 1 for i in range(32)) ctrl_aim = bones.new("Aim") ctrl_aim.head = (0.0, 10.0, 1.7) @@ -112,34 +128,13 @@ def create_crane_bones(rig, bone_layers): pose_bones["Crane_height"].lock_scale = (True, False, True) -def build_camera_rig(context, mode): - bone_layers = tuple(i == 1 for i in range(32)) - view_layer = bpy.context.view_layer - - rig_name = mode.capitalize() + "_Rig" - rig_data = bpy.data.armatures.new(rig_name) - rig = object_utils.object_data_add(context, rig_data, name=rig_name) - rig["rig_id"] = "%s" % rig_name - view_layer.objects.active = rig - rig.location = context.scene.cursor.location - - bpy.ops.object.mode_set(mode='EDIT') - - # Add new bones - if mode == "DOLLY": - create_dolly_bones(rig, bone_layers) - elif mode == "CRANE": - create_crane_bones(rig, bone_layers) - +def setup_3d_rig(rig, cam): + """Finish setting up Dolly and Crane rigs""" # Jump into object mode and change bones to euler bpy.ops.object.mode_set(mode='OBJECT') pose_bones = rig.pose.bones - for b in pose_bones: - b.rotation_mode = 'XYZ' - - # Add custom properties to the armature’s Camera bone, - # so that all properties may be animated in a single action - # Add driver after the camera is created + for bone in pose_bones: + bone.rotation_mode = 'XYZ' # Lens property pb = pose_bones['Camera'] @@ -150,22 +145,6 @@ def build_camera_rig(context, mode): prop["max"] = 1000000.0 prop["soft_max"] = 5000.0 - # DOF Focus Distance property - pb = pose_bones['Camera'] - pb["focus_distance"] = 10.0 - prop = rna_idprop_ui_prop_get(pb, "focus_distance", create=True) - prop["default"] = 10.0 - prop["min"] = 0.0 - - # DOF F-Stop property - pb = pose_bones['Camera'] - pb["aperture_fstop"] = 2.8 - prop = rna_idprop_ui_prop_get(pb, "aperture_fstop", create=True) - prop["default"] = 2.8 - prop["min"] = 0.0 - prop["soft_min"] = 0.1 - prop["soft_max"] = 128.0 - # Build the widgets root_widget = create_root_widget("Camera_Root") camera_widget = create_camera_widget("Camera") @@ -176,7 +155,7 @@ def build_camera_rig(context, mode): pose_bones["Aim"].custom_shape = aim_widget pose_bones["Camera"].custom_shape = camera_widget - # Set the "At" field to the child + # Set the "At" field to the shape mecanism pose_bones["Aim"].custom_shape_transform = pose_bones["Aim_shape_rotation-MCH"] # Add constraints to bones @@ -189,55 +168,359 @@ def build_camera_rig(context, mode): con.subtarget = "Aim" con.use_target_z = True - # Change display to BBone: it just looks nicer - bpy.context.object.data.display_type = 'BBONE' - # Change display to wire for object - bpy.context.object.display_type = 'WIRE' + cam.data.display_size = 1.0 + cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x + + create_prop_driver(rig, cam, "lens", "lens") + + +def create_2d_bones(context, rig, cam): + """Create bones for the 2D camera rig""" + scene = context.scene + bones = rig.data.edit_bones + + # Add new bones + bones = rig.data.edit_bones + root = bones.new("Root") + root.tail = Vector((0.0, 0.0, 1.0)) + root.show_wire = True + + ctrl = bones.new('Camera') + ctrl.tail = Vector((0.0, 0.0, 1.0)) + ctrl.show_wire = True + + left_corner = bones.new("Left_corner") + left_corner.head = (-3, 10, -2) + left_corner.tail = left_corner.head + Vector((0.0, 0.0, 1.0)) + left_corner.show_wire = True + + right_corner = bones.new("Right_corner") + right_corner.head = (3, 10, -2) + right_corner.tail = right_corner.head + Vector((0.0, 0.0, 1.0)) + right_corner.show_wire = True + corner_distance_x = (left_corner.head - right_corner.head).length + corner_distance_y = -left_corner.head.z + corner_distance_z = left_corner.head.y + + center = bones.new("Center-MCH") + center.head = ((right_corner.head + left_corner.head) / 2.0) + center.tail = center.head + Vector((0.0, 0.0, 1.0)) + center.layers = tuple(i == 1 for i in range(32)) + center.show_wire = True + + # Setup hierarchy + ctrl.parent = root + left_corner.parent = root + right_corner.parent = root + center.parent = root + + # Jump into object mode and change bones to euler + bpy.ops.object.mode_set(mode='OBJECT') + pose_bones = rig.pose.bones + for bone in pose_bones: + bone.rotation_mode = 'XYZ' + + # Bone drivers + center_drivers = pose_bones["Center-MCH"].driver_add("location") + + # Center X driver + driver = center_drivers[0].driver + driver.type = 'AVERAGE' + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_X' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Center Y driver + driver = center_drivers[1].driver + driver.type = 'SCRIPTED' + + driver.expression = '({distance_x} - (left_x-right_x))*(res_y/res_x)/2 + (left_y + right_y)/2'.format(distance_x=corner_distance_x) + + for direction in ('x', 'y'): + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = '%s_%s' % (corner, direction) + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'res_' + direction + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'SCENE' + var.targets[0].id = scene + var.targets[0].data_path = 'render.resolution_' + direction + + # Center Z driver + driver = center_drivers[2].driver + driver.type = 'AVERAGE' + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_Z' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Bone constraints + con = pose_bones["Camera"].constraints.new('DAMPED_TRACK') + con.target = rig + con.subtarget = "Center-MCH" + con.track_axis = 'TRACK_NEGATIVE_Z' + + # Build the widgets + left_widget = create_corner_widget("Left_corner", reverse=True) + right_widget = create_corner_widget("Right_corner") + parent_widget = create_circle_widget("Root", radius=0.5) + camera_widget = create_circle_widget("Camera_2D", radius=0.3) + + # Add the custom bone shapes + pose_bones["Left_corner"].custom_shape = left_widget + pose_bones["Right_corner"].custom_shape = right_widget + pose_bones["Root"].custom_shape = parent_widget + pose_bones["Camera"].custom_shape = camera_widget + + # Lock the relevant loc, rot and scale + pose_bones["Left_corner"].lock_rotation = (True,) * 3 + pose_bones["Right_corner"].lock_rotation = (True,) * 3 + pose_bones["Camera"].lock_rotation = (True,) * 3 + pose_bones["Camera"].lock_scale = (True,) * 3 + + # Camera settings + + cam.data.sensor_fit = "HORIZONTAL" # Avoids distortion in portrait format + + # Property to switch between rotation and switch mode + pose_bones["Camera"]['rotation_shift'] = 0.0 + prop = rna_idprop_ui_prop_get(pose_bones["Camera"], 'rotation_shift', create=True) + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 0.0 + prop["soft_max"] = 1.0 + prop["description"] = 'rotation_shift' + + # Rotation / shift switch driver + driver = con.driver_add('influence').driver + driver.expression = '1 - rotation_shift' + + var = driver.variables.new() + var.name = 'rotation_shift' + var.type = 'SINGLE_PROP' + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]' + + # Focal length driver + driver = cam.data.driver_add('lens').driver + driver.expression = 'abs({distance_z} - (left_z + right_z)/2 + cam_z) * 36 / frame_width'.format(distance_z=corner_distance_z) + + var = driver.variables.new() + var.name = 'frame_width' + var.type = 'LOC_DIFF' + var.targets[0].id = rig + var.targets[0].bone_target = "Left_corner" + var.targets[0].transform_space = 'WORLD_SPACE' + var.targets[1].id = rig + var.targets[1].bone_target = "Right_corner" + var.targets[1].transform_space = 'WORLD_SPACE' + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + '_z' + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_Z' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'cam_z' + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = "Camera" + var.targets[0].transform_type = 'LOC_Z' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Orthographic scale driver + driver = cam.data.driver_add('ortho_scale').driver + driver.expression = 'abs({distance_x} - (left_x - right_x))'.format(distance_x=corner_distance_x) + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + '_x' + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_X' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Shift driver X + driver = cam.data.driver_add('shift_x').driver + + driver.expression = 'rotation_shift * (((left_x + right_x)/2 - cam_x) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36)'.format(distance_z=corner_distance_z) + + var = driver.variables.new() + var.name = 'rotation_shift' + var.type = 'SINGLE_PROP' + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]' + + for direction in ('x', 'z'): + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = '%s_%s' % (corner, direction) + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'cam_' + direction + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = "Camera" + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'lens' + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'CAMERA' + var.targets[0].id = cam.data + var.targets[0].data_path = 'lens' + + # Shift driver Y + driver = cam.data.driver_add('shift_y').driver + + driver.expression = 'rotation_shift * -(({distance_y} - (left_y + right_y)/2 + cam_y) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36 - (res_y/res_x)/2)'.format(distance_y=corner_distance_y, distance_z=corner_distance_z) + + var = driver.variables.new() + var.name = 'rotation_shift' + var.type = 'SINGLE_PROP' + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]' + + for direction in ('y', 'z'): + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = '%s_%s' % (corner, direction) + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'cam_' + direction + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = "Camera" + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + for direction in ('x', 'y'): + var = driver.variables.new() + var.name = 'res_' + direction + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'SCENE' + var.targets[0].id = scene + var.targets[0].data_path = 'render.resolution_' + direction + + var = driver.variables.new() + var.name = 'lens' + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'CAMERA' + var.targets[0].id = cam.data + var.targets[0].data_path = 'lens' + + +def build_camera_rig(context, mode): + """Create stuff common to all camera rigs.""" # Add the camera object cam_name = "%s_Camera" % mode.capitalize() cam_data = bpy.data.cameras.new(cam_name) cam = object_utils.object_data_add(context, cam_data, name=cam_name) - view_layer.objects.active = cam context.scene.camera = cam - cam.data.display_size = 1.0 - cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x + # Add the rig object + rig_name = mode.capitalize() + "_Rig" + rig_data = bpy.data.armatures.new(rig_name) + rig = object_utils.object_data_add(context, rig_data, name=rig_name) + rig["rig_id"] = "%s" % rig_name + rig.location = context.scene.cursor.location + + bpy.ops.object.mode_set(mode='EDIT') + # Add new bones and setup specific rigs + if mode == "DOLLY": + create_dolly_bones(rig) + setup_3d_rig(rig, cam) + elif mode == "CRANE": + create_crane_bones(rig) + setup_3d_rig(rig, cam) + elif mode == "2D": + create_2d_bones(context, rig, cam) + + # Parent the camera to the rig cam.location = (0.0, -1.0, 0.0) # Move the camera to the correct position cam.parent = rig cam.parent_type = "BONE" cam.parent_bone = "Camera" + # Change display to BBone: it just looks nicer + rig.data.display_type = 'BBONE' + # Change display to wire for object + rig.display_type = 'WIRE' + # Lock camera transforms cam.lock_location = (True,) * 3 cam.lock_rotation = (True,) * 3 cam.lock_scale = (True,) * 3 + # Add custom properties to the armature’s Camera bone, + # so that all properties may be animated in a single action + + pose_bones = rig.pose.bones + + # DOF Focus Distance property + pb = pose_bones['Camera'] + pb["focus_distance"] = 10.0 + prop = rna_idprop_ui_prop_get(pb, "focus_distance", create=True) + prop["default"] = 10.0 + prop["min"] = 0.0 + + # DOF F-Stop property + pb = pose_bones['Camera'] + pb["aperture_fstop"] = 2.8 + prop = rna_idprop_ui_prop_get(pb, "aperture_fstop", create=True) + prop["default"] = 2.8 + prop["min"] = 0.0 + prop["soft_min"] = 0.1 + prop["soft_max"] = 128.0 + # Add drivers to link the camera properties to the custom props # on the armature - for prop_from, prop_to in (("lens", "lens"), - ("focus_distance", "dof.focus_distance"), - ("aperture_fstop", "dof.aperture_fstop")): - driver = cam.data.driver_add(prop_to) - driver.driver.type = 'SCRIPTED' - var = driver.driver.variables.new() - var.name = 'var' - var.type = 'SINGLE_PROP' - - # Target the custom bone property - var.targets[0].id = rig - var.targets[0].data_path = 'pose.bones["Camera"]["%s"]' % prop_from - driver.driver.expression = 'var' + create_prop_driver(rig, cam, "focus_distance", "dof.focus_distance") + create_prop_driver(rig, cam, "aperture_fstop", "dof.aperture_fstop") # Make the rig the active object - for ob in view_layer.objects: - ob.select_set(False) + view_layer = context.view_layer + for obj in view_layer.objects: + obj.select_set(False) rig.select_set(True) view_layer.objects.active = rig - return rig - class OBJECT_OT_build_camera_rig(Operator): bl_idname = "object.build_camera_rig" @@ -245,11 +528,12 @@ class OBJECT_OT_build_camera_rig(Operator): bl_description = "Build a Camera Rig" bl_options = {'REGISTER', 'UNDO'} - mode: bpy.props.EnumProperty(items= - (('DOLLY',) * 3, - ('CRANE',) * 3,), + mode: bpy.props.EnumProperty(items=(('DOLLY', 'Dolly', 'Dolly rig'), + ('CRANE', 'Crane', 'Crane rig',), + ('2D', '2D', '2D rig')), name="mode", - description="", default="DOLLY") + description="Type of camera to create", + default="DOLLY") def execute(self, context): # Build the rig @@ -260,18 +544,23 @@ class OBJECT_OT_build_camera_rig(Operator): def add_dolly_crane_buttons(self, context): """Dolly and crane entries in the Add Object > Camera Menu""" if context.mode == 'OBJECT': - op = self.layout.operator( + self.layout.operator( OBJECT_OT_build_camera_rig.bl_idname, text="Dolly Camera Rig", - icon='CAMERA_DATA' - ) - op.mode = "DOLLY" - op = self.layout.operator( + icon='VIEW_CAMERA' + ).mode = "DOLLY" + + self.layout.operator( OBJECT_OT_build_camera_rig.bl_idname, text="Crane Camera Rig", - icon='CAMERA_DATA' - ) - op.mode = "CRANE" + icon='VIEW_CAMERA' + ).mode = "CRANE" + + self.layout.operator( + OBJECT_OT_build_camera_rig.bl_idname, + text="2D Camera Rig", + icon='PIVOT_BOUNDBOX' + ).mode = "2D" classes = ( diff --git a/add_camera_rigs/composition_guides_menu.py b/add_camera_rigs/composition_guides_menu.py index c3ff43e5..16537525 100644 --- a/add_camera_rigs/composition_guides_menu.py +++ b/add_camera_rigs/composition_guides_menu.py @@ -19,7 +19,7 @@ import bpy from bpy.types import Panel -from .operators import get_arm_and_cam +from .operators import get_rig_and_cam class ADD_CAMERA_RIGS_PT_composition_guides(Panel): bl_label = "Composition Guides" @@ -29,7 +29,7 @@ class ADD_CAMERA_RIGS_PT_composition_guides(Panel): def draw(self, context): layout = self.layout - arm, cam = get_arm_and_cam(context.active_object) + rig, cam = get_rig_and_cam(context.active_object) cam = cam.data layout.prop(cam, "show_safe_areas") diff --git a/add_camera_rigs/create_widgets.py b/add_camera_rigs/create_widgets.py index f19fc206..72a8c70d 100644 --- a/add_camera_rigs/create_widgets.py +++ b/add_camera_rigs/create_widgets.py @@ -1,4 +1,24 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + import bpy +from mathutils import Vector +from math import cos, sin, pi def create_widget(name): @@ -29,6 +49,44 @@ def create_widget(name): return obj +def create_corner_widget(name, reverse=False): + """Create a wedge-shaped widget""" + obj = create_widget(name) + if not obj.data.vertices: + reverse = -1 if reverse else 1 + verts = (Vector((reverse * 0.0, 0.0, 0.0)), + Vector((reverse * 0.0, 1.0, 0.0)), + Vector((reverse * -0.1, 1.0, 0.0)), + Vector((reverse * -0.1, 0.1, 0.0)), + Vector((reverse * -1.0, 0.1, 0.0)), + Vector((reverse * -1.0, 0.0, 0.0)), + ) + edges = [(n, (n+1) % len(verts)) for n in range(len(verts))] + + mesh = obj.data + mesh.from_pydata(verts, edges, ()) + mesh.update() + return obj + + +def create_circle_widget(name, radius=1.0): + """Create a circle-shaped widget""" + obj = create_widget(name) + if not obj.data.vertices: + vert_n = 16 + verts = [] + for n in range(vert_n): + angle = n / vert_n * 2*pi + verts.append(Vector((cos(angle) * radius, + sin(angle) * radius, 0.0))) + edges = [(n, (n+1) % len(verts)) for n in range(len(verts))] + + mesh = obj.data + mesh.from_pydata(verts, edges, ()) + mesh.update() + return obj + + def create_root_widget(name): """Create a compass-shaped widget""" obj = create_widget(name) diff --git a/add_camera_rigs/operators.py b/add_camera_rigs/operators.py index 7468007e..058ed146 100644 --- a/add_camera_rigs/operators.py +++ b/add_camera_rigs/operators.py @@ -20,7 +20,7 @@ import bpy from bpy.types import Operator -def get_arm_and_cam(obj): +def get_rig_and_cam(obj): if obj.type == 'ARMATURE': cam = None for child in obj.children: @@ -32,7 +32,8 @@ def get_arm_and_cam(obj): elif (obj.type == 'CAMERA' and obj.parent is not None and "rig_id" in obj.parent - and obj.parent["rig_id"].lower() in {"dolly_rig", "crane_rig"}): + and obj.parent["rig_id"].lower() in {"dolly_rig", + "crane_rig", "2d_rig"}): return obj.parent, obj return None, None @@ -41,25 +42,31 @@ class CameraRigMixin(): @classmethod def poll(cls, context): if context.active_object is not None: - return get_arm_and_cam(context.active_object) != (None, None) + return get_rig_and_cam(context.active_object) != (None, None) return False -class ADD_CAMERA_RIGS_OT_set_scene_camera(Operator, CameraRigMixin): +class ADD_CAMERA_RIGS_OT_set_scene_camera(Operator): bl_idname = "add_camera_rigs.set_scene_camera" bl_label = "Make Camera Active" bl_description = "Makes the camera parented to this rig the active scene camera" + @classmethod + def poll(cls, context): + if context.active_object is not None: + rig, cam = get_rig_and_cam(context.active_object) + if cam is not None: + return cam is not context.scene.camera + + return False + def execute(self, context): - arm, cam = get_arm_and_cam(context.active_object) + rig, cam = get_rig_and_cam(context.active_object) scene_cam = context.scene.camera - if cam is not None and cam is not scene_cam: - context.scene.camera = cam - return {'FINISHED'} - - return {'CANCELLED'} + context.scene.camera = cam + return {'FINISHED'} class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin): @@ -68,7 +75,7 @@ class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin): bl_description = "Add marker to current frame then bind rig camera to it (for camera switching)" def execute(self, context): - arm, cam = get_arm_and_cam(context.active_object) + rig, cam = get_rig_and_cam(context.active_object) marker = context.scene.timeline_markers.new( "cam_" + str(context.scene.frame_current), @@ -85,15 +92,15 @@ class ADD_CAMERA_RIGS_OT_add_dof_object(Operator, CameraRigMixin): bl_description = "Create Empty and add as DOF Object" def execute(self, context): - arm, cam = get_arm_and_cam(context.active_object) - bone = arm.data.bones['Aim_shape_rotation-MCH'] + rig, cam = get_rig_and_cam(context.active_object) + bone = rig.data.bones['Aim_shape_rotation-MCH'] # Add Empty empty_obj = bpy.data.objects.new("EmptyDOF", None) context.scene.collection.objects.link(empty_obj) # Parent to Aim Child bone - empty_obj.parent = arm + empty_obj.parent = rig empty_obj.parent_type = "BONE" empty_obj.parent_bone = "Aim_shape_rotation-MCH" diff --git a/add_camera_rigs/ui_panels.py b/add_camera_rigs/ui_panels.py index 63fe158a..0dc3c69e 100644 --- a/add_camera_rigs/ui_panels.py +++ b/add_camera_rigs/ui_panels.py @@ -19,7 +19,7 @@ import bpy from bpy.types import Panel -from .operators import get_arm_and_cam, CameraRigMixin +from .operators import get_rig_and_cam, CameraRigMixin class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin): @@ -30,66 +30,87 @@ class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin): def draw(self, context): active_object = context.active_object - arm, cam = get_arm_and_cam(context.active_object) - pose_bones = arm.pose.bones + rig, cam = get_rig_and_cam(context.active_object) + pose_bones = rig.pose.bones cam_data = cam.data + layout = self.layout + + # Camera lens + if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"): + layout.prop(pose_bones["Camera"], '["lens"]', + text="Focal Length (mm)") + + col = layout.column(align=True) + col.label(text="Clipping:") + col.prop(cam_data, "clip_start", text="Start") + col.prop(cam_data, "clip_end", text="End") - layout = self.layout.box().column() - layout.label(text="Clipping:") - layout.prop(cam_data, "clip_start", text="Start") - layout.prop(cam_data, "clip_end", text="End") layout.prop(cam_data, "type") - layout.prop(cam_data.dof, "use_dof") - if cam_data.dof.use_dof: - if cam_data.dof.focus_object is None: - layout.operator("add_camera_rigs.add_dof_object", - text="Add DOF Empty", icon="OUTLINER_OB_EMPTY") - layout.prop(pose_bones["Camera"], - '["focus_distance"]', text="Focus Distance") - layout.prop(pose_bones["Camera"], - '["aperture_fstop"]', text="F-Stop") + # DoF + col = layout.column(align=True) + col.prop(cam_data.dof, "use_dof") + if cam_data.dof.use_dof: + if rig["rig_id"].lower() in ("crane_rig", "dolly_rig"): + if cam_data.dof.focus_object is None: + col.operator("add_camera_rigs.add_dof_object", + text="Add DOF Empty", icon="OUTLINER_OB_EMPTY") + else: + col.prop(cam_data.dof, "focus_object") + row = col.row(align=True) + row.active = cam_data.dof.focus_object is None + row.prop(pose_bones["Camera"], + '["focus_distance"]', text="Focus Distance") + col.prop(pose_bones["Camera"], + '["aperture_fstop"]', text="F-Stop") + + # Viewport display layout.prop(active_object, 'show_in_front', toggle=False, text='Show in Front') layout.prop(cam_data, "show_limits") - layout.prop(cam_data, "show_passepartout") + col = layout.column(align=True) + col.prop(cam_data, "show_passepartout") if cam_data.show_passepartout: - layout.prop(cam_data, "passepartout_alpha") + col.prop(cam_data, "passepartout_alpha") - layout.row().separator() - # Added the comp guides here + # Composition guides layout.popover( panel="ADD_CAMERA_RIGS_PT_composition_guides", text="Composition Guides",) - layout.row().separator() - layout.prop(cam, + # Props and operators + col = layout.column(align=True) + col.prop(cam, "hide_select", text="Make Camera Unselectable") - - layout.operator("add_camera_rigs.add_marker_bind", - text="Add Marker and Bind", icon="MARKER_HLT") - if context.scene.camera is not cam: - layout.operator("add_camera_rigs.set_scene_camera", - text="Make Camera Active", icon='CAMERA_DATA') - - # Camera lens - layout.separator() - layout.prop(pose_bones["Camera"], '["lens"]', text="Focal Length (mm)") - - # Track to Constraint - layout.label(text="Tracking:") - layout.prop(pose_bones["Camera"].constraints["Track To"], - 'influence', text="Aim Lock", slider=True) - - if arm["rig_id"].lower() == "crane_rig": - col = layout.box().column() + col.operator("add_camera_rigs.add_marker_bind", + text="Add Marker and Bind", icon="MARKER_HLT") + col.operator("add_camera_rigs.set_scene_camera", + text="Make Camera Active", icon='CAMERA_DATA') + + if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"): + # Track to Constraint + col = layout.column(align=True) + col.label(text="Tracking:") + col.prop(pose_bones["Camera"].constraints["Track To"], + 'influence', text="Aim Lock", slider=True) # Crane arm stuff - col.label(text="Crane Arm:") - col.prop(pose_bones["Crane_height"], - 'scale', index=1, text="Arm Height") - col.prop(pose_bones["Crane_arm"], - 'scale', index=1, text="Arm Length") + if rig["rig_id"].lower() == "crane_rig": + col = layout.column(align=True) + col.label(text="Crane Arm:") + col.prop(pose_bones["Crane_height"], + 'scale', index=1, text="Arm Height") + col.prop(pose_bones["Crane_arm"], + 'scale', index=1, text="Arm Length") + + # 2D rig stuff + elif rig["rig_id"].lower() == "2d_rig": + col = layout.column(align=True) + col.label(text="2D Rig:") + col.prop(pose_bones["Camera"], '["rotation_shift"]', + text="Rotation/Shift") + if cam.data.sensor_width != 36: + col.label(text="Please set Camera Sensor Width to 36", icon="ERROR") def register(): diff --git a/add_curve_extra_objects/__init__.py b/add_curve_extra_objects/__init__.py index 6f4ac24f..407e2fe2 100644 --- a/add_curve_extra_objects/__init__.py +++ b/add_curve_extra_objects/__init__.py @@ -16,8 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### # Contributed to by: -# testscreenings, Alejandro Omar Chocano Vasquez, Jimmy Hazevoet, meta-androcto # -# Cmomoney, Jared Forsyth, Adam Newgas, Spivak Vladimir, Jared Forsyth, Atom # +# testscreenings, Alejandro Omar Chocano Vasquez, Jimmy Hazevoet, meta-androcto +# Cmomoney, Jared Forsyth, Adam Newgas, Spivak Vladimir, Jared Forsyth, Atom # Antonio Osprite, Marius Giurgi (DolphinDream) bl_info = { @@ -28,8 +28,7 @@ bl_info = { "location": "View3D > Add > Curve > Extra Objects", "description": "Add extra curve object types", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_curve/extra_objects.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/extra_objects.html", "category": "Add Curve", } diff --git a/add_curve_ivygen.py b/add_curve_ivygen.py index 07228e1b..d67225eb 100644 --- a/add_curve_ivygen.py +++ b/add_curve_ivygen.py @@ -27,8 +27,7 @@ bl_info = { "description": "Adds generated ivy to a mesh object starting " "at the 3D cursor", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_curve/ivy_gen.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/ivy_gen.html", "category": "Add Curve", } diff --git a/add_curve_sapling/__init__.py b/add_curve_sapling/__init__.py index 8d607616..b12beada 100644 --- a/add_curve_sapling/__init__.py +++ b/add_curve_sapling/__init__.py @@ -26,8 +26,7 @@ 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'"), - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_curve/sapling.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/sapling.html", "category": "Add Curve", } diff --git a/add_mesh_BoltFactory/__init__.py b/add_mesh_BoltFactory/__init__.py index d763d13f..34833a49 100644 --- a/add_mesh_BoltFactory/__init__.py +++ b/add_mesh_BoltFactory/__init__.py @@ -23,8 +23,7 @@ bl_info = { "blender": (2, 80, 0), "location": "View3D > Add > Mesh", "description": "Add a bolt or nut", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_mesh/boltfactory.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/boltfactory.html", "category": "Add Mesh", } diff --git a/add_mesh_discombobulator/__init__.py b/add_mesh_discombobulator/__init__.py index b6a6e84a..c1039608 100644 --- a/add_mesh_discombobulator/__init__.py +++ b/add_mesh_discombobulator/__init__.py @@ -27,8 +27,7 @@ bl_info = { "location": "View3D > Add > Mesh", "description": "Add Discombobulator", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_mesh/discombobulator.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/discombobulator.html", "category": "Add Mesh", } diff --git a/add_mesh_extra_objects/__init__.py b/add_mesh_extra_objects/__init__.py index cea0cb2d..4f00c450 100644 --- a/add_mesh_extra_objects/__init__.py +++ b/add_mesh_extra_objects/__init__.py @@ -31,8 +31,7 @@ bl_info = { "location": "View3D > Add > Mesh", "description": "Add extra mesh object types", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_mesh/mesh_extra_objects.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/mesh_extra_objects.html", "category": "Add Mesh", } diff --git a/add_mesh_geodesic_domes/__init__.py b/add_mesh_geodesic_domes/__init__.py index dc153993..c7137614 100644 --- a/add_mesh_geodesic_domes/__init__.py +++ b/add_mesh_geodesic_domes/__init__.py @@ -27,8 +27,7 @@ bl_info = { "location": "View3D > Add > Mesh", "description": "Create geodesic dome type objects.", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_mesh/geodesic_domes.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/geodesic_domes.html", "category": "Add Mesh", } diff --git a/animation_add_corrective_shape_key.py b/animation_add_corrective_shape_key.py index 6faca951..01e13cde 100644 --- a/animation_add_corrective_shape_key.py +++ b/animation_add_corrective_shape_key.py @@ -25,8 +25,7 @@ bl_info = { "blender": (2, 80, 0), "location": "Object Data > Shape Keys Specials or Search", "description": "Creates a corrective shape key for the current pose", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "animation/corrective_shape_keys.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/corrective_shape_keys.html", "category": "Animation", } diff --git a/animation_animall.py b/animation_animall.py index efc4e841..141c032a 100644 --- a/animation_animall.py +++ b/animation_animall.py @@ -24,8 +24,7 @@ bl_info = { "location": "3D View > Toolbox > Animation tab > AnimAll", "description": "Allows animation of mesh, lattice, curve and surface data", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "animation/animall.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/animall.html", "category": "Animation", } diff --git a/ant_landscape/__init__.py b/ant_landscape/__init__.py index 5af0d261..fa0b7b5a 100644 --- a/ant_landscape/__init__.py +++ b/ant_landscape/__init__.py @@ -27,8 +27,7 @@ bl_info = { "location": "View3D > Sidebar > Create Tab", "description": "Another Noise Tool: Landscape and Displace", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_mesh/ant_landscape.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/ant_landscape.html", "category": "Add Mesh", } diff --git a/archimesh/__init__.py b/archimesh/__init__.py index 10d6c9a6..99692591 100644 --- a/archimesh/__init__.py +++ b/archimesh/__init__.py @@ -32,8 +32,7 @@ bl_info = { "version": (1, 2, 2), "blender": (2, 80, 0), "description": "Generate rooms, doors, windows, and other architecture objects", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_mesh/archimesh.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/archimesh.html", "category": "Add Mesh" } diff --git a/blender_id/__init__.py b/blender_id/__init__.py index 9dd8a504..93b09124 100644 --- a/blender_id/__init__.py +++ b/blender_id/__init__.py @@ -28,8 +28,7 @@ bl_info = { 'location': 'Add-on preferences', 'description': 'Stores your Blender ID credentials for usage with other add-ons', - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "system/blender_id.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/system/blender_id.html", 'category': 'System', 'support': 'OFFICIAL', } diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py index a45c9b9a..35a5b1ec 100644 --- a/blenderkit/__init__.py +++ b/blenderkit/__init__.py @@ -24,8 +24,7 @@ bl_info = { "location": "View3D > Properties > BlenderKit", "description": "Online BlenderKit library (materials, models, brushes and more)", "warning": "", - "doc_url": "https://youtu.be/1hVgcQhIAo8" - "Scripts/Add_Mesh/BlenderKit", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_mesh/blenderkit.html", "category": "3D View", } diff --git a/bone_selection_sets.py b/bone_selection_sets.py index 8862244b..8e07f418 100644 --- a/bone_selection_sets.py +++ b/bone_selection_sets.py @@ -24,8 +24,7 @@ bl_info = { "location": "Properties > Object Data (Armature) > Selection Sets", "description": "List of Bone sets for easy selection while animating", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "animation/bone_selection_sets.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/bone_selection_sets.html", "category": "Animation", } diff --git a/btrace/__init__.py b/btrace/__init__.py index d0f41d5e..aaf0fc15 100644 --- a/btrace/__init__.py +++ b/btrace/__init__.py @@ -25,8 +25,7 @@ bl_info = { "location": "View3D > Sidebar > Create Tab", "description": "Tools for converting/animating objects/particles into curves", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_curve/btracer.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/btracer.html", "category": "Add Curve", } diff --git a/camera_turnaround.py b/camera_turnaround.py index 5742fb79..669fc656 100644 --- a/camera_turnaround.py +++ b/camera_turnaround.py @@ -23,8 +23,7 @@ bl_info = { "blender": (2, 80, 0), "location": "View3D > Sidebar > View Tab > Turnaround Camera", "description": "Add a camera rotation around selected object", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "animation/turnaround_camera.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/turnaround_camera.html", "category": "Animation", } diff --git a/curve_assign_shapekey.py b/curve_assign_shapekey.py index 7e971035..18e34c0c 100644 --- a/curve_assign_shapekey.py +++ b/curve_assign_shapekey.py @@ -27,8 +27,7 @@ bl_info = { "blender": (2, 80, 0), "location": "View 3D > Sidebar > Edit Tab", "description": "Assigns one or more Bezier curves as shape keys to another Bezier curve", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_curve/assign_shape_keys.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/assign_shape_keys.html", "category": "Add Curve", } diff --git a/curve_simplify.py b/curve_simplify.py index 85df1dba..0109530a 100644 --- a/curve_simplify.py +++ b/curve_simplify.py @@ -24,8 +24,7 @@ bl_info = { "location": "3D View, Dopesheet & Graph Editors", "description": "Simplify Curves: 3dview, Dopesheet, Graph. Distance Merge: 3d view curve edit", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_curve/simplify_curves.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/simplify_curves.html", "category": "Add Curve", } @@ -597,10 +596,10 @@ def main_rd(context, distance = 0.01): if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='EDIT') - for curve in selected_Curves: - bezier_dellist = [] - dellist = [] + bezier_dellist = [] + dellist = [] + for curve in selected_Curves: for spline in curve.data.splines: if spline.type == 'BEZIER': if len(spline.bezier_points) > 1: diff --git a/curve_tools/__init__.py b/curve_tools/__init__.py index 50641f0a..2fcec1d9 100644 --- a/curve_tools/__init__.py +++ b/curve_tools/__init__.py @@ -30,8 +30,7 @@ bl_info = { "location": "View3D > Tool Shelf > Edit Tab", "warning": "WIP", "doc_url": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "add_curve/curve_tools.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/curve_tools.html", "category": "Add Curve", } diff --git a/development_edit_operator.py b/development_edit_operator.py index 21947f78..f9e0b911 100644 --- a/development_edit_operator.py +++ b/development_edit_operator.py @@ -25,8 +25,7 @@ bl_info = { "location": "Text Editor > Sidebar > Edit Operator", "description": "Opens source file of chosen operator or call locations, if source not available", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "development/edit_operator.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/development/edit_operator.html", "category": "Development", } diff --git a/development_icon_get.py b/development_icon_get.py index d2278bed..6e2154ae 100644 --- a/development_icon_get.py +++ b/development_icon_get.py @@ -26,8 +26,7 @@ bl_info = { "version": (1, 4, 0), "blender": (2, 80, 0), "location": "Text Editor > Dev Tab > Icon Viewer", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "development/icon_viewer.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/development/icon_viewer.html", "category": "Development", } diff --git a/development_iskeyfree.py b/development_iskeyfree.py index 8893eed1..e8215497 100644 --- a/development_iskeyfree.py +++ b/development_iskeyfree.py @@ -25,8 +25,7 @@ bl_info = { "blender": (2, 80, 0), "location": "Text Editor > Sidebar > Dev Tab", "description": "Find free shortcuts, inform about used and print a key list", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "development/is_key_free.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/development/is_key_free.html", "category": "Development", } diff --git a/io_anim_bvh/__init__.py b/io_anim_bvh/__init__.py index 1065b25f..3a17632e 100644 --- a/io_anim_bvh/__init__.py +++ b/io_anim_bvh/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "File > Import-Export", "description": "Import-Export BVH from armature objects", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/anim_bvh.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/anim_bvh.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/io_anim_camera.py b/io_anim_camera.py index 24972a5d..6130b552 100644 --- a/io_anim_camera.py +++ b/io_anim_camera.py @@ -26,8 +26,7 @@ bl_info = { "location": "File > Export > Cameras & Markers (.py)", "description": "Export Cameras & Markers (.py)", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/anim_camera.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/anim_camera.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/io_anim_nuke_chan/__init__.py b/io_anim_nuke_chan/__init__.py index 6c33ff57..3fca3f07 100644 --- a/io_anim_nuke_chan/__init__.py +++ b/io_anim_nuke_chan/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "File > Import/Export > Nuke (.chan)", "description": "Import/Export object's animation with nuke", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/anim_nuke_chan.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/anim_nuke_chan.html", "category": "Import-Export", } diff --git a/io_coat3D/__init__.py b/io_coat3D/__init__.py index ea199bea..7275e158 100644 --- a/io_coat3D/__init__.py +++ b/io_coat3D/__init__.py @@ -24,8 +24,7 @@ bl_info = { "location": "Scene > 3D-Coat Applink", "description": "Transfer data between 3D-Coat/Blender", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/coat3D.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/coat3D.html", "category": "Import-Export", } diff --git a/io_curve_svg/__init__.py b/io_curve_svg/__init__.py index a7a3fe3a..03b1755e 100644 --- a/io_curve_svg/__init__.py +++ b/io_curve_svg/__init__.py @@ -25,8 +25,7 @@ bl_info = { "location": "File > Import > Scalable Vector Graphics (.svg)", "description": "Import SVG as curves", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/curve_svg.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/curve_svg.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/io_export_dxf/__init__.py b/io_export_dxf/__init__.py index e4d035c3..e9083bb5 100644 --- a/io_export_dxf/__init__.py +++ b/io_export_dxf/__init__.py @@ -24,8 +24,7 @@ bl_info = { "location": "File > Export > AutoCAD DXF", "description": "The script exports Blender geometry to DXF format r12 version.", "warning": "Under construction! Visit Wiki for details.", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/scene_dxf.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_dxf.html", "category": "Import-Export", } diff --git a/io_export_paper_model.py b/io_export_paper_model.py index 1329a304..10658dd6 100644 --- a/io_export_paper_model.py +++ b/io_export_paper_model.py @@ -17,8 +17,7 @@ bl_info = { "location": "File > Export > Paper Model", "warning": "", "description": "Export printable net of the active mesh", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/paper_model.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/paper_model.html", "category": "Import-Export", } diff --git a/io_export_pc2.py b/io_export_pc2.py index b40b2a77..8dda3cd1 100644 --- a/io_export_pc2.py +++ b/io_export_pc2.py @@ -24,8 +24,7 @@ bl_info = { "location": "File > Export > Pointcache (.pc2)", "description": "Export mesh Pointcache data (.pc2)", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/pc2.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/pc2.html", "category": "Import-Export", } diff --git a/io_import_dxf/__init__.py b/io_import_dxf/__init__.py index bff93e9e..c24f6a82 100644 --- a/io_import_dxf/__init__.py +++ b/io_import_dxf/__init__.py @@ -38,8 +38,7 @@ bl_info = { "blender": (2, 80, 0), "location": "File > Import > AutoCAD DXF", "description": "Import files in the Autocad DXF format (.dxf)", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/scene_dxf.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_dxf.html", "category": "Import-Export", } diff --git a/io_import_images_as_planes.py b/io_import_images_as_planes.py index dc25bee0..5783d70e 100644 --- a/io_import_images_as_planes.py +++ b/io_import_images_as_planes.py @@ -27,8 +27,7 @@ bl_info = { "description": "Imports images and creates planes with the appropriate aspect ratio. " "The images are mapped to the planes.", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/images_as_planes.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/images_as_planes.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/io_import_palette/__init__.py b/io_import_palette/__init__.py index e8f03185..c2c15d5d 100644 --- a/io_import_palette/__init__.py +++ b/io_import_palette/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "File > Import", "description": "Import Palettes", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/palettes.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/palettes.html", "category": "Import-Export", } diff --git a/io_mesh_atomic/__init__.py b/io_mesh_atomic/__init__.py index fd77c31d..81a518b7 100644 --- a/io_mesh_atomic/__init__.py +++ b/io_mesh_atomic/__init__.py @@ -63,8 +63,7 @@ bl_info = { "blender": (2, 80, 0), "location": "File -> Import -> PDB (.pdb) and File -> Import -> XYZ (.xyz)", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/mesh_atomic.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_atomic.html", "category": "Import-Export", } diff --git a/io_mesh_ply/__init__.py b/io_mesh_ply/__init__.py index 86ab7999..5e12bfce 100644 --- a/io_mesh_ply/__init__.py +++ b/io_mesh_ply/__init__.py @@ -25,8 +25,7 @@ bl_info = { "blender": (2, 82, 0), "location": "File > Import-Export", "description": "Import-Export PLY mesh data with UVs and vertex colors", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/mesh_ply.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_ply.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/io_mesh_stl/__init__.py b/io_mesh_stl/__init__.py index 17af58f1..7af1c2d1 100644 --- a/io_mesh_stl/__init__.py +++ b/io_mesh_stl/__init__.py @@ -25,8 +25,7 @@ bl_info = { "blender": (2, 81, 6), "location": "File > Import-Export", "description": "Import-Export STL files", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/mesh_stl.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_stl.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/io_mesh_uv_layout/__init__.py b/io_mesh_uv_layout/__init__.py index b376fda4..21b68c65 100644 --- a/io_mesh_uv_layout/__init__.py +++ b/io_mesh_uv_layout/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "Image-Window > UVs > Export UV Layout", "description": "Export the UV layout as a 2D graphic", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/mesh_uv_layout.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/mesh_uv_layout.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index dcffe3ab..40ca5fc4 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "File > Import-Export", "description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/scene_fbx.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_fbx.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index de7bbe7a..49f6f470 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -15,12 +15,12 @@ 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, 2, 35), + "version": (1, 2, 38), 'blender': (2, 82, 7), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', 'warning': '', - 'doc_url': "https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html", + 'doc_url': "{BLENDER_MANUAL_URL}/addons/import_export/scene_gltf2.html", 'tracker_url': "https://github.com/KhronosGroup/glTF-Blender-IO/issues/", 'support': 'OFFICIAL', 'category': 'Import-Export', @@ -856,11 +856,26 @@ class ImportGLTF2(Operator, ImportHelper): description="How normals are computed during import", default="NORMALS") + bone_heuristic: EnumProperty( + name="Bone Dir", + items=( + ("BLENDER", "Blender (+Y)", + "Round-trips bone directions in glTFs exported from Blender.\n" + "Bone tips are placed on their local +Y axis (in glTF space)"), + ("TEMPERANCE", "Temperance", + "Okay for many different models.\n" + "Bone tips are placed at a child's root") + ), + description="Heuristic for placing bones. Tries to make bones pretty", + default="TEMPERANCE", + ) + def draw(self, context): layout = self.layout layout.prop(self, 'import_pack_images') layout.prop(self, 'import_shading') + layout.prop(self, 'bone_heuristic') def execute(self, context): return self.import_gltf2(context) diff --git a/io_scene_gltf2/blender/com/gltf2_blender_math.py b/io_scene_gltf2/blender/com/gltf2_blender_math.py index fb342bc4..b3f13f65 100755 --- a/io_scene_gltf2/blender/com/gltf2_blender_math.py +++ b/io_scene_gltf2/blender/com/gltf2_blender_math.py @@ -170,3 +170,45 @@ def transform_value(value: Vector, _: Matrix = Matrix.Identity(4)) -> Vector: def round_if_near(value: float, target: float) -> float: """If value is very close to target, round to target.""" return value if abs(value - target) > 2.0e-6 else target + +def scale_rot_swap_matrix(rot): + """Returns a matrix m st. Scale[s] Rot[rot] = Rot[rot] Scale[m s]. + If rot.to_matrix() is a signed permutation matrix, works for any s. + Otherwise works only if s is a uniform scaling. + """ + m = nearby_signed_perm_matrix(rot) # snap to signed perm matrix + m.transpose() # invert permutation + for i in range(3): + for j in range(3): + m[i][j] = abs(m[i][j]) # discard sign + return m + +def nearby_signed_perm_matrix(rot): + """Returns a signed permutation matrix close to rot.to_matrix(). + (A signed permutation matrix is like a permutation matrix, except + the non-zero entries can be ±1.) + """ + m = rot.to_matrix() + x, y, z = m[0], m[1], m[2] + + # Set the largest entry in the first row to ±1 + a, b, c = abs(x[0]), abs(x[1]), abs(x[2]) + i = 0 if a >= b and a >= c else 1 if b >= c else 2 + x[i] = 1 if x[i] > 0 else -1 + x[(i+1) % 3] = 0 + x[(i+2) % 3] = 0 + + # Same for second row: only two columns to consider now. + a, b = abs(y[(i+1) % 3]), abs(y[(i+2) % 3]) + j = (i+1) % 3 if a >= b else (i+2) % 3 + y[j] = 1 if y[j] > 0 else -1 + y[(j+1) % 3] = 0 + y[(j+2) % 3] = 0 + + # Same for third row: only one column left + k = (0 + 1 + 2) - i - j + z[k] = 1 if z[k] > 0 else -1 + z[(k+1) % 3] = 0 + z[(k+2) % 3] = 0 + + return m 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 1e155ffd..7194798f 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py @@ -39,8 +39,8 @@ def gather_mesh(blender_mesh: bpy.types.Mesh, extensions=__gather_extensions(blender_mesh, vertex_groups, modifiers, export_settings), extras=__gather_extras(blender_mesh, vertex_groups, modifiers, export_settings), name=__gather_name(blender_mesh, vertex_groups, modifiers, export_settings), + weights=__gather_weights(blender_mesh, vertex_groups, modifiers, export_settings), primitives=__gather_primitives(blender_mesh, blender_object, vertex_groups, modifiers, material_names, export_settings), - weights=__gather_weights(blender_mesh, vertex_groups, modifiers, export_settings) ) if len(mesh.primitives) == 0: @@ -134,15 +134,6 @@ def __gather_weights(blender_mesh: bpy.types.Mesh, modifiers: Optional[bpy.types.ObjectModifiers], export_settings ) -> Optional[List[float]]: - - # Seems that in some files, when using Apply Modifier, shape_keys return an error - # ReferenceError: StructRNA of type Mesh has been removed - # Because shapekeys are not exported in that case, we can return None - try: - blender_mesh.shape_keys - except: - return None - if not export_settings[MORPH] or not blender_mesh.shape_keys: return None diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py index db1115d0..cca86432 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py @@ -59,6 +59,8 @@ def gather_primitives( material = gltf2_blender_gather_materials.gather_material(blender_material, double_sided, export_settings) + # NOTE: gather_material may invalidate blender_mesh (see #932), + # so make sure not to access blender_mesh again after this point except IndexError: # no material at that index pass diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py index 62c179c1..6f65de43 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py @@ -58,12 +58,14 @@ class BlenderBoneAnim(): else: translation_keyframes = (gltf.loc_gltf_to_blender(vals) for vals in values) + final_translations = vnode.base_locs_to_final_locs(translation_keyframes) + # Calculate pose bone trans from final bone trans edit_trans, edit_rot = vnode.editbone_trans, vnode.editbone_rot edit_rot_inv = edit_rot.conjugated() pose_translations = [ edit_rot_inv @ (trans - edit_trans) - for trans in translation_keyframes + for trans in final_translations ] BlenderBoneAnim.fill_fcurves( @@ -93,12 +95,14 @@ class BlenderBoneAnim(): else: quat_keyframes = [gltf.quaternion_gltf_to_blender(vals) for vals in values] + final_rots = vnode.base_rots_to_final_rots(quat_keyframes) + # Calculate pose bone rotation from final bone rotation edit_rot = vnode.editbone_rot edit_rot_inv = edit_rot.conjugated() pose_rots = [ edit_rot_inv @ rot - for rot in quat_keyframes + for rot in final_rots ] # Manage antipodal quaternions @@ -133,10 +137,13 @@ class BlenderBoneAnim(): else: scale_keyframes = [gltf.scale_gltf_to_blender(vals) for vals in values] + final_scales = vnode.base_scales_to_final_scales(scale_keyframes) + pose_scales = final_scales # no change needed + BlenderBoneAnim.fill_fcurves( obj.animation_data.action, keys, - scale_keyframes, + pose_scales, group_name, blender_path, animation.samplers[channel.sampler].interpolation diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py index a0205483..b6369b8b 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_animation_node.py @@ -74,20 +74,22 @@ class BlenderNodeAnim(): blender_path = "location" group_name = "Location" num_components = 3 + values = [gltf.loc_gltf_to_blender(vals) for vals in values] + values = vnode.base_locs_to_final_locs(values) if vnode.parent is not None and gltf.vnodes[vnode.parent].type == VNode.Bone: # Nodes with a bone parent need to be translated - # backwards by their bone length (always 1 currently) - off = Vector((0, -1, 0)) - values = [gltf.loc_gltf_to_blender(vals) + off for vals in values] - else: - values = [gltf.loc_gltf_to_blender(vals) for vals in values] + # backwards from the tip to the root + bone_length = gltf.vnodes[vnode.parent].bone_length + off = Vector((0, -bone_length, 0)) + values = [vals + off for vals in values] elif channel.target.path == "rotation": blender_path = "rotation_quaternion" group_name = "Rotation" num_components = 4 values = [gltf.quaternion_gltf_to_blender(vals) for vals in values] + values = vnode.base_rots_to_final_rots(values) # Manage antipodal quaternions for i in range(1, len(values)): @@ -99,6 +101,7 @@ class BlenderNodeAnim(): group_name = "Scale" num_components = 3 values = [gltf.scale_gltf_to_blender(vals) for vals in values] + values = vnode.base_scales_to_final_scales(values) coords = [0] * (2 * len(keys)) coords[::2] = (key[0] * fps for key in keys) diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py index 8e6c1950..e00e2449 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_gltf.py @@ -24,7 +24,26 @@ class BlenderGlTF(): @staticmethod def create(gltf): - """Create glTF main method.""" + """Create glTF main method, with optional profiling""" + profile = bpy.app.debug_value == 102 + if profile: + import cProfile, pstats, io + from pstats import SortKey + pr = cProfile.Profile() + pr.enable() + BlenderGlTF._create(gltf) + pr.disable() + s = io.StringIO() + sortby = SortKey.TIME + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + print(s.getvalue()) + else: + BlenderGlTF._create(gltf) + + @staticmethod + def _create(gltf): + """Create glTF main worker method.""" BlenderGlTF.set_convert_functions(gltf) BlenderGlTF.pre_compute(gltf) BlenderScene.create(gltf) diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_node.py b/io_scene_gltf2/blender/imp/gltf2_blender_node.py index d1ffdbe9..6ce91ea3 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_node.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_node.py @@ -78,7 +78,7 @@ class BlenderNode(): set_extras(obj, pynode.extras) # Set transform - trans, rot, scale = vnode.trs + trans, rot, scale = vnode.trs() obj.location = trans obj.rotation_mode = 'QUATERNION' obj.rotation_quaternion = rot @@ -96,8 +96,8 @@ class BlenderNode(): obj.parent_bone = parent_vnode.blender_bone_name # Nodes with a bone parent need to be translated - # backwards by their bone length (always 1 currently) - obj.location += Vector((0, -1, 0)) + # backwards from the tip to the root + obj.location += Vector((0, -parent_vnode.bone_length, 0)) bpy.data.scenes[gltf.blender_scene].collection.objects.link(obj) @@ -138,6 +138,7 @@ class BlenderNode(): editbone.head = arma_mat @ Vector((0, 0, 0)) editbone.tail = arma_mat @ Vector((0, 1, 0)) editbone.align_roll(arma_mat @ Vector((0, 0, 1)) - editbone.head) + editbone.length = vnode.bone_length if isinstance(id, int): pynode = gltf.data.nodes[id] @@ -161,7 +162,7 @@ class BlenderNode(): # BoneTRS = EditBone * PoseBone # Set PoseBone to make BoneTRS = vnode.trs. - t, r, s = vnode.trs + t, r, s = vnode.trs() et, er = vnode.editbone_trans, vnode.editbone_rot pose_bone.location = er.conjugated() @ (t - et) pose_bone.rotation_mode = 'QUATERNION' diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py b/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py index 07030a62..a4df6f3f 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_primitive.py @@ -14,6 +14,8 @@ import bpy from mathutils import Vector, Matrix +import numpy as np +# import time from .gltf2_blender_material import BlenderMaterial from ...io.imp.gltf2_io_binary import BinaryData @@ -194,20 +196,27 @@ class BlenderPrimitive(): layer_name = 'Col' if set_num == 0 else 'Col.%03d' % set_num layer = BlenderPrimitive.get_layer(bme.loops.layers.color, layer_name) - colors = BinaryData.get_data_from_accessor(gltf, attributes['COLOR_%d' % set_num], cache=True) + # colors is a 2d array: [N][3 or 4] + gltf_attr_name = 'COLOR_%d' % set_num + colors_raw = BinaryData.get_data_from_accessor(gltf, attributes[gltf_attr_name], cache=True) + colors = np.array(colors_raw, dtype=np.float32) is_rgba = len(colors[0]) == 4 - + if not is_rgba: + # RGB -> RGBA + ones = np.ones((colors.shape[0], 1)) + colors = np.concatenate((colors, ones), axis=1) # add alpha channel + + srgb_colors = color_linear_to_srgb(colors) + # t = time.perf_counter() + # This needs to be a tight loop because it runs over all vertices, + # which is why this code looks a little odd. for bidx, pidx in vert_idxs: - color = colors[pidx] - col = ( - color_linear_to_srgb(color[0]), - color_linear_to_srgb(color[1]), - color_linear_to_srgb(color[2]), - color[3] if is_rgba else 1.0, - ) + color = srgb_colors[pidx] + col = (color[0], color[1], color[2], color[3]) # fastest this way for loop in bme_verts[bidx].link_loops: loop[layer] = col + # print(f'store colors: {time.perf_counter() - t}') set_num += 1 diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_vnode.py b/io_scene_gltf2/blender/imp/gltf2_blender_vnode.py index bd5edcd1..554c09e9 100644 --- a/io_scene_gltf2/blender/imp/gltf2_blender_vnode.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_vnode.py @@ -15,6 +15,8 @@ import bpy from mathutils import Vector, Quaternion, Matrix +from ..com.gltf2_blender_math import scale_rot_swap_matrix, nearby_signed_perm_matrix + def compute_vnodes(gltf): """Computes the tree of virtual nodes. Copies the glTF nodes into a tree of VNodes, then performs a series of @@ -26,6 +28,7 @@ def compute_vnodes(gltf): fixup_multitype_nodes(gltf) correct_cameras_and_lights(gltf) pick_bind_pose(gltf) + prettify_bones(gltf) calc_bone_matrices(gltf) @@ -45,17 +48,60 @@ class VNode: self.parent = None self.type = VNode.Object self.is_arma = False - self.trs = ( + self.base_trs = ( Vector((0, 0, 0)), Quaternion((1, 0, 0, 0)), Vector((1, 1, 1)), ) + # Additional rotations before/after the base TRS. + # Allows per-vnode axis adjustment. See local_rotation. + self.rotation_after = Quaternion((1, 0, 0, 0)) + self.rotation_before = Quaternion((1, 0, 0, 0)) + # Indices of the glTF node where the mesh, etc. came from. # (They can get moved around.) self.mesh_node_idx = None self.camera_node_idx = None self.light_node_idx = None + def trs(self): + # (final TRS) = (rotation after) (base TRS) (rotation before) + t, r, s = self.base_trs + m = scale_rot_swap_matrix(self.rotation_before) + return ( + self.rotation_after @ t, + self.rotation_after @ r @ self.rotation_before, + m @ s, + ) + + def base_locs_to_final_locs(self, base_locs): + ra = self.rotation_after + return [ra @ loc for loc in base_locs] + + def base_rots_to_final_rots(self, base_rots): + ra, rb = self.rotation_after, self.rotation_before + return [ra @ rot @ rb for rot in base_rots] + + def base_scales_to_final_scales(self, base_scales): + m = scale_rot_swap_matrix(self.rotation_before) + return [m @ scale for scale in base_scales] + +def local_rotation(gltf, vnode_id, rot): + """Appends a local rotation to vnode's world transform: + (new world transform) = (old world transform) @ (rot) + without changing the world transform of vnode's children. + + For correctness, rot must be a signed permutation of the axes + (eg. (X Y Z)->(X -Z Y)) OR vnode's scale must always be uniform. + """ + gltf.vnodes[vnode_id].rotation_before @= rot + + # Append the inverse rotation after children's TRS to cancel it out. + rot_inv = rot.conjugated() + for child in gltf.vnodes[vnode_id].children: + gltf.vnodes[child].rotation_after = \ + rot_inv @ gltf.vnodes[child].rotation_after + def init_vnodes(gltf): # Map of all VNodes. The keys are arbitrary IDs. @@ -67,7 +113,7 @@ def init_vnodes(gltf): gltf.vnodes[i] = vnode vnode.name = pynode.name or 'Node_%d' % i vnode.children = list(pynode.children or []) - vnode.trs = get_node_trs(gltf, pynode) + vnode.base_trs = get_node_trs(gltf, pynode) if pynode.mesh is not None: vnode.mesh_node_idx = i if pynode.camera is not None: @@ -216,7 +262,7 @@ def move_skinned_meshes(gltf): ) if ok_to_move: reparent(gltf, id, new_parent=arma) - vnode.trs = ( + vnode.base_trs = ( Vector((0, 0, 0)), Quaternion((1, 0, 0, 0)), Vector((1, 1, 1)), @@ -304,35 +350,13 @@ def correct_cameras_and_lights(gltf): if gltf.camera_correction is None: return - trs = (Vector((0, 0, 0)), gltf.camera_correction, Vector((1, 1, 1))) - - ids = list(gltf.vnodes.keys()) - for id in ids: - vnode = gltf.vnodes[id] - - # Move the camera/light onto a new child and set its rotation - # TODO: "hard apply" the rotation without creating a new node - # (like we'll need to do for bones) + for id, vnode in gltf.vnodes.items(): + needs_correction = \ + vnode.camera_node_idx is not None or \ + vnode.light_node_idx is not None - if vnode.camera_node_idx is not None: - new_id = str(id) + '.camera-correction' - gltf.vnodes[new_id] = VNode() - gltf.vnodes[new_id].name = vnode.name + ' Correction' - gltf.vnodes[new_id].trs = trs - gltf.vnodes[new_id].camera_node_idx = vnode.camera_node_idx - gltf.vnodes[new_id].parent = id - vnode.children.append(new_id) - vnode.camera_node_idx = None - - if vnode.light_node_idx is not None: - new_id = str(id) + '.light-correction' - gltf.vnodes[new_id] = VNode() - gltf.vnodes[new_id].name = vnode.name + ' Correction' - gltf.vnodes[new_id].trs = trs - gltf.vnodes[new_id].light_node_idx = vnode.light_node_idx - gltf.vnodes[new_id].parent = id - vnode.children.append(new_id) - vnode.light_node_idx = None + if needs_correction: + local_rotation(gltf, id, gltf.camera_correction) def pick_bind_pose(gltf): @@ -345,14 +369,102 @@ def pick_bind_pose(gltf): if vnode.type == VNode.Bone: # For now, use the node TR for bind pose. # TODO: try calculating from inverseBindMatices? - vnode.bind_trans = Vector(vnode.trs[0]) - vnode.bind_rot = Quaternion(vnode.trs[1]) + vnode.bind_trans = Vector(vnode.base_trs[0]) + vnode.bind_rot = Quaternion(vnode.base_trs[1]) # Initialize editbones to match bind pose vnode.editbone_trans = Vector(vnode.bind_trans) vnode.editbone_rot = Quaternion(vnode.bind_rot) +def prettify_bones(gltf): + """ + Prettify bone lengths/directions. + """ + def visit(vnode_id, parent_rot=None): # Depth-first walk + vnode = gltf.vnodes[vnode_id] + rot = None + + if vnode.type == VNode.Bone: + vnode.bone_length = pick_bone_length(gltf, vnode_id) + rot = pick_bone_rotation(gltf, vnode_id, parent_rot) + if rot is not None: + rotate_edit_bone(gltf, vnode_id, rot) + + for child in vnode.children: + visit(child, parent_rot=rot) + + visit('root') + +MIN_BONE_LENGTH = 0.004 # too small and bones get deleted + +def pick_bone_length(gltf, bone_id): + """Heuristic for bone length.""" + vnode = gltf.vnodes[bone_id] + + child_locs = [ + gltf.vnodes[child].editbone_trans + for child in vnode.children + if gltf.vnodes[child].type == VNode.Bone + ] + child_locs = [loc for loc in child_locs if loc.length > MIN_BONE_LENGTH] + if child_locs: + return min(loc.length for loc in child_locs) + + if gltf.vnodes[vnode.parent].type == VNode.Bone: + return gltf.vnodes[vnode.parent].bone_length + + if vnode.editbone_trans.length > MIN_BONE_LENGTH: + return vnode.editbone_trans.length + + return 1 + +def pick_bone_rotation(gltf, bone_id, parent_rot): + """Heuristic for bone rotation. + A bone's tip lies on its local +Y axis so rotating a bone let's us + adjust the bone direction. + """ + if bpy.app.debug_value == 100: + return None + + if gltf.import_settings['bone_heuristic'] == 'BLENDER': + return Quaternion((2**0.5/2, 2**0.5/2, 0, 0)) + elif gltf.import_settings['bone_heuristic'] == 'TEMPERANCE': + return temperance(gltf, bone_id, parent_rot) + +def temperance(gltf, bone_id, parent_rot): + vnode = gltf.vnodes[bone_id] + + # Try to put our tip at the centroid of our children + child_locs = [ + gltf.vnodes[child].editbone_trans + for child in vnode.children + if gltf.vnodes[child].type == VNode.Bone + ] + child_locs = [loc for loc in child_locs if loc.length > MIN_BONE_LENGTH] + if child_locs: + centroid = sum(child_locs, Vector((0, 0, 0))) + rot = Vector((0, 1, 0)).rotation_difference(centroid) + rot = nearby_signed_perm_matrix(rot).to_quaternion() + return rot + + return parent_rot + +def rotate_edit_bone(gltf, bone_id, rot): + """Rotate one edit bone without affecting anything else.""" + gltf.vnodes[bone_id].editbone_rot @= rot + # Cancel out the rotation so children aren't affected. + rot_inv = rot.conjugated() + for child_id in gltf.vnodes[bone_id].children: + child = gltf.vnodes[child_id] + if child.type == VNode.Bone: + child.editbone_trans = rot_inv @ child.editbone_trans + child.editbone_rot = rot_inv @ child.editbone_rot + # Need to rotate the bone's final TRS by the same amount so skinning + # isn't affected. + local_rotation(gltf, bone_id, rot) + + def calc_bone_matrices(gltf): """ Calculate the transformations from bone space to arma space in the bind @@ -380,6 +492,3 @@ def calc_bone_matrices(gltf): visit(child) visit('root') - - -# TODO: add pass to rotate/resize bones so they look pretty diff --git a/io_scene_gltf2/io/com/gltf2_io_color_management.py b/io_scene_gltf2/io/com/gltf2_io_color_management.py index 56b3a246..58dc3a27 100644 --- a/io_scene_gltf2/io/com/gltf2_io_color_management.py +++ b/io_scene_gltf2/io/com/gltf2_io_color_management.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import numpy as np def color_srgb_to_scene_linear(c): """ @@ -29,8 +30,27 @@ def color_linear_to_srgb(c): Convert from linear to sRGB color space. Source: Cycles addon implementation, node_color.h. + c may be a single color value or an array. + + If c's last dimension is 4, it's assumed to be RGBA and the + alpha channel is not converted. """ - if c < 0.0031308: - return 0.0 if c < 0.0 else c * 12.92 + if type(c) in (list, np.ndarray): + colors = np.array(c, np.float32) if type(c) == list else c + if colors.ndim > 1 and colors.shape[-1] == 4: + colors_noa = colors[..., 0:3] # only process RGB for speed + else: + colors_noa = colors + not_small = colors_noa >= 0.0031308 + small_result = np.where(colors_noa < 0.0, 0.0, colors_noa * 12.92) + large_result = 1.055 * np.power(colors_noa, 1.0 / 2.4, where=not_small) - 0.055 + result = np.where(not_small, large_result, small_result) + if colors.ndim > 1 and colors.shape[-1] == 4: + # copy alpha from original + result = np.concatenate((result, colors[..., 3, np.newaxis]), axis=-1) + return result else: - return 1.055 * pow(c, 1.0 / 2.4) - 0.055 + if c < 0.0031308: + return 0.0 if c < 0.0 else c * 12.92 + else: + return 1.055 * pow(c, 1.0 / 2.4) - 0.055 diff --git a/io_scene_obj/__init__.py b/io_scene_obj/__init__.py index 7cd0f72f..997deee2 100644 --- a/io_scene_obj/__init__.py +++ b/io_scene_obj/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "File > Import-Export", "description": "Import-Export OBJ, Import OBJ mesh, UV's, materials and textures", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/scene_obj.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_obj.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/io_scene_x3d/__init__.py b/io_scene_x3d/__init__.py index 232ee479..ba355d08 100644 --- a/io_scene_x3d/__init__.py +++ b/io_scene_x3d/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "File > Import-Export", "description": "Import-Export X3D, Import VRML2", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/scene_x3d.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/scene_x3d.html", "category": "Import-Export", } diff --git a/io_shape_mdd/__init__.py b/io_shape_mdd/__init__.py index 0078b537..fa59fe0d 100644 --- a/io_shape_mdd/__init__.py +++ b/io_shape_mdd/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "File > Import-Export", "description": "Import-Export MDD as mesh shape keys", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "import_export/shape_mdd.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/shape_mdd.html", "support": 'OFFICIAL', "category": "Import-Export", } diff --git a/lighting_dynamic_sky.py b/lighting_dynamic_sky.py index 86fa6c06..1b22a3a4 100644 --- a/lighting_dynamic_sky.py +++ b/lighting_dynamic_sky.py @@ -27,8 +27,7 @@ bl_info = { "location": "View3D > Sidebar > Create Tab", "description": "Creates Dynamic Sky for Cycles", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "lighting/dynamic_sky.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/lighting/dynamic_sky.html", "category": "Lighting", } diff --git a/lighting_tri_lights.py b/lighting_tri_lights.py index 3e864694..a5478974 100644 --- a/lighting_tri_lights.py +++ b/lighting_tri_lights.py @@ -9,8 +9,7 @@ bl_info = { "description": "Add 3 Point Lighting to Selected / Active Object", "warning": "", "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "lighting/trilighting.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/lighting/trilighting.html", "category": "Lighting", } diff --git a/magic_uv/__init__.py b/magic_uv/__init__.py index c1b78e40..8630038a 100644 --- a/magic_uv/__init__.py +++ b/magic_uv/__init__.py @@ -35,8 +35,7 @@ bl_info = { "description": "UV Toolset. See Add-ons Preferences for details", "warning": "", "support": "COMMUNITY", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "uv/magic_uv.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/uv/magic_uv.html", "tracker_url": "https://github.com/nutti/Magic-UV", "category": "UV", } diff --git a/materials_library_vx/__init__.py b/materials_library_vx/__init__.py index eaed03de..d03afeaa 100644 --- a/materials_library_vx/__init__.py +++ b/materials_library_vx/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "Properties > Material", "description": "Material Library VX", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "materials/material_library.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/materials/material_library.html", "tracker_url": "", "category": "Material", } diff --git a/materials_utils/__init__.py b/materials_utils/__init__.py index 9223ac72..84b10fc1 100644 --- a/materials_utils/__init__.py +++ b/materials_utils/__init__.py @@ -40,8 +40,7 @@ bl_info = { "location": "View3D > Shift + Q key", "description": "Menu of material tools (assign, select..) in the 3D View", "warning": "Beta", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "materials/material_utils.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/materials/material_utils.html", "category": "Material" } diff --git a/measureit/__init__.py b/measureit/__init__.py index 79e41589..82e00589 100644 --- a/measureit/__init__.py +++ b/measureit/__init__.py @@ -32,7 +32,7 @@ bl_info = { "version": (1, 8, 1), "blender": (2, 80, 0), "description": "Tools for measuring objects.", - "doc_url": "https://docs.blender.org/manual/en/dev/addons" + "doc_url": "{BLENDER_MANUAL_URL}/addons" "/3d_view/measureit.html", "category": "3D View" } diff --git a/mesh_auto_mirror.py b/mesh_auto_mirror.py index 8359e58e..8c21ccfa 100644 --- a/mesh_auto_mirror.py +++ b/mesh_auto_mirror.py @@ -14,8 +14,7 @@ bl_info = { "blender": (2, 80, 0), "location": "View 3D > Sidebar > Edit Tab > AutoMirror (panel)", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "mesh/auto_mirror.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/auto_mirror.html", "category": "Mesh", } diff --git a/mesh_bsurfaces.py b/mesh_bsurfaces.py index f1ae463e..705776d3 100644 --- a/mesh_bsurfaces.py +++ b/mesh_bsurfaces.py @@ -24,8 +24,7 @@ bl_info = { "blender": (2, 80, 0), "location": "View3D EditMode > Sidebar > Edit Tab", "description": "Modeling and retopology tool", - "doc_url": "https://docs.blender.org/manual/nb/dev/addons/" - "mesh/bsurfaces.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/bsurfaces.html", "category": "Mesh", } @@ -29,8 +29,7 @@ bl_info = { "location": "Editmode > F", "warning": "", "description": "Extends the 'Make Edge/Face' functionality", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "mesh/f2.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/f2.html", "category": "Mesh", } diff --git a/mesh_inset/__init__.py b/mesh_inset/__init__.py index 4dbd8472..74148dfe 100644 --- a/mesh_inset/__init__.py +++ b/mesh_inset/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "3DView Operator", "description": "Make an inset inside selection using straight skeleton algorithm.", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "mesh/inset_straight_skeleton.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/inset_straight_skeleton.html", "category": "Mesh", } diff --git a/mesh_looptools.py b/mesh_looptools.py index 3abaacc9..e2e78960 100644 --- a/mesh_looptools.py +++ b/mesh_looptools.py @@ -28,8 +28,7 @@ bl_info = { "location": "View3D > Sidebar > Edit Tab / Edit Mode Context Menu", "warning": "", "description": "Mesh modelling toolkit. Several tools to aid modelling", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "mesh/looptools.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/looptools.html", "category": "Mesh", } diff --git a/mesh_tools/__init__.py b/mesh_tools/__init__.py index d55ce7e6..f93baea9 100644 --- a/mesh_tools/__init__.py +++ b/mesh_tools/__init__.py @@ -29,8 +29,7 @@ bl_info = { "location": "View3D > Sidebar > Edit Tab / Edit Mode Context Menu", "warning": "", "description": "Mesh modelling toolkit. Several tools to aid modelling", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "mesh/edit_mesh_tools.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/edit_mesh_tools.html", "category": "Mesh", } diff --git a/node_arrange.py b/node_arrange.py index 6d3ed807..bab8b333 100644 --- a/node_arrange.py +++ b/node_arrange.py @@ -24,8 +24,7 @@ bl_info = { "location": "Node Editor > Properties > Trees", "description": "Node Tree Arrangement Tools", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "node/node_arrange.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_arrange.html", "tracker_url": "https://github.com/JuhaW/NodeArrange/issues", "category": "Node" } diff --git a/node_presets.py b/node_presets.py index 85a262e1..d6a70898 100644 --- a/node_presets.py +++ b/node_presets.py @@ -25,8 +25,7 @@ bl_info = { "location": "Node Editors > Add > Template", "description": "Add node groups directly to the node editors", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "node/node_presets.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_presets.html", "category": "Node", } diff --git a/node_wrangler.py b/node_wrangler.py index eba5b60d..3526d2c7 100644 --- a/node_wrangler.py +++ b/node_wrangler.py @@ -24,8 +24,7 @@ bl_info = { "location": "Node Editor Toolbar or Shift-W", "description": "Various tools to enhance and speed up node-based workflow", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "node/node_wrangler.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/node/node_wrangler.html", "category": "Node", } diff --git a/object_boolean_tools.py b/object_boolean_tools.py index 7ea231e9..3000f588 100644 --- a/object_boolean_tools.py +++ b/object_boolean_tools.py @@ -25,8 +25,7 @@ bl_info = { "blender": (2, 80, 0), "location": "View3D > Sidebar > Edit Tab", "description": "Bool Tool Hotkey: Ctrl Shift B", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "object/bool_tools.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/object/bool_tools.html", "category": "Object", } diff --git a/object_carver/__init__.py b/object_carver/__init__.py index 883d5b76..dc21fb4d 100644 --- a/object_carver/__init__.py +++ b/object_carver/__init__.py @@ -24,8 +24,7 @@ bl_info = { "blender": (2, 80, 0), "location": "3D View > Ctrl/Shift/x", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "object/carver.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/object/carver.html", "support": 'COMMUNITY', "category": "Object" } diff --git a/object_collection_manager/__init__.py b/object_collection_manager/__init__.py index 580a46e0..39a22906 100644 --- a/object_collection_manager/__init__.py +++ b/object_collection_manager/__init__.py @@ -22,12 +22,11 @@ bl_info = { "name": "Collection Manager", "description": "Manage collections and their objects", "author": "Ryan Inch", - "version": (1,9,2), + "version": (1,9,3), "blender": (2, 80, 0), "location": "View3D - Object Mode (Shortcut - M)", "warning": '', # used for warning icon and text in addons panel - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "interface/collection_manager.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/collection_manager.html", "category": "Interface", } @@ -45,12 +44,28 @@ else: from . import ui import bpy +from bpy.types import PropertyGroup from bpy.props import ( CollectionProperty, IntProperty, BoolProperty, + PointerProperty, ) + +class CollectionManagerProperties(PropertyGroup): + cm_list_collection: CollectionProperty(type=internals.CMListCollection) + cm_list_index: IntProperty(update=ui.update_selection) + + show_exclude: BoolProperty(default=True, name="Exclude from View Layer") + show_selectable: BoolProperty(default=True, name="Selectable") + show_hide_viewport: BoolProperty(default=True, name="Hide in Viewport") + show_disable_viewport: BoolProperty(default=False, name="Disable in Viewports") + show_render: BoolProperty(default=False, name="Disable in Renders") + + in_phantom_mode: BoolProperty(default=False) + + addon_keymaps = [] classes = ( @@ -75,23 +90,14 @@ classes = ( ui.CM_UL_items, ui.CollectionManager, ui.CMRestrictionTogglesPanel, + CollectionManagerProperties, ) def register(): for cls in classes: bpy.utils.register_class(cls) - bpy.types.Scene.CMListCollection = CollectionProperty(type=internals.CMListCollection) - bpy.types.Scene.CMListIndex = IntProperty(update=ui.update_selection) - - bpy.types.Scene.show_exclude = BoolProperty(default=True, name="Exclude from View Layer") - bpy.types.Scene.show_selectable = BoolProperty(default=True, name="Selectable") - bpy.types.Scene.show_hideviewport = BoolProperty(default=True, name="Hide in Viewport") - bpy.types.Scene.show_disableviewport = BoolProperty(default=False, name="Disable in Viewports") - bpy.types.Scene.show_render = BoolProperty(default=False, name="Disable in Renders") - - bpy.types.Scene.CM_Phantom_Mode = BoolProperty(default=False) - + bpy.types.Scene.collection_manager = PointerProperty(type=CollectionManagerProperties) # create the global menu hotkey wm = bpy.context.window_manager @@ -103,16 +109,7 @@ def unregister(): for cls in classes: bpy.utils.unregister_class(cls) - del bpy.types.Scene.CMListCollection - del bpy.types.Scene.CMListIndex - - del bpy.types.Scene.show_exclude - del bpy.types.Scene.show_selectable - del bpy.types.Scene.show_hideviewport - del bpy.types.Scene.show_disableviewport - del bpy.types.Scene.show_render - - del bpy.types.Scene.CM_Phantom_Mode + del bpy.types.Scene.collection_manager # remove keymaps when add-on is deactivated for km, kmi in addon_keymaps: diff --git a/object_collection_manager/internals.py b/object_collection_manager/internals.py index 64c48150..5267b1c6 100644 --- a/object_collection_manager/internals.py +++ b/object_collection_manager/internals.py @@ -65,10 +65,11 @@ def update_collection_tree(context): max_lvl = 0 row_index = 0 - init_laycol_list = context.view_layer.layer_collection.children + layer_collection = context.view_layer.layer_collection + init_laycol_list = layer_collection.children master_laycol = {"id": 0, - "name": context.view_layer.layer_collection.name, + "name": layer_collection.name, "lvl": -1, "row_index": -1, "visible": True, @@ -76,7 +77,7 @@ def update_collection_tree(context): "expanded": True, "parent": None, "children": [], - "ptr": context.view_layer.layer_collection + "ptr": layer_collection } get_all_collections(context, init_laycol_list, master_laycol, collection_tree, visible=True) @@ -120,15 +121,17 @@ def get_all_collections(context, collections, parent, tree, level=0, visible=Fal def update_property_group(context): update_collection_tree(context) - context.scene.CMListCollection.clear() + context.scene.collection_manager.cm_list_collection.clear() create_property_group(context, collection_tree) def create_property_group(context, tree): global in_filter + cm = context.scene.collection_manager + for laycol in tree: - new_cm_listitem = context.scene.CMListCollection.add() + new_cm_listitem = cm.cm_list_collection.add() new_cm_listitem.name = laycol["name"] if laycol["has_children"]: diff --git a/object_collection_manager/operators.py b/object_collection_manager/operators.py index bd23f8a9..7f693ac9 100644 --- a/object_collection_manager/operators.py +++ b/object_collection_manager/operators.py @@ -120,7 +120,7 @@ class ExpandSublevelOperator(Operator): # set selected row to the collection you're expanding/collapsing and update tree view - context.scene.CMListIndex = self.index + context.scene.collection_manager.cm_list_index = self.index update_property_group(context) return {'FINISHED'} @@ -192,7 +192,8 @@ class CMExcludeOperator(Operator): if modifiers == {"shift"}: # isolate/de-isolate exclusion of collections - active_layer_collections = [x["ptr"] for x in layer_collections.values() if not x["ptr"].exclude] + active_layer_collections = [x["ptr"] for x in layer_collections.values() + if not x["ptr"].exclude] # check if previous state should be restored if cls.isolated and self.name == target: @@ -206,7 +207,8 @@ class CMExcludeOperator(Operator): cls.isolated = False # check if all collections should be enabled - elif len(active_layer_collections) == 1 and active_layer_collections[0].name == self.name: + elif (len(active_layer_collections) == 1 and + active_layer_collections[0].name == self.name): # enable all collections for item in layer_collections.values(): item["ptr"].exclude = False @@ -441,8 +443,8 @@ class CMRestrictSelectOperator(Operator): laycol = layer_collections[self.name] # get active collections - active_layer_collections = [x["ptr"] for x in layer_collections.values() \ - if x["ptr"].collection.hide_select == False] + active_layer_collections = [x["ptr"] for x in layer_collections.values() + if x["ptr"].collection.hide_select == False] # check if previous state should be restored if cls.isolated and self.name == target: @@ -456,7 +458,8 @@ class CMRestrictSelectOperator(Operator): cls.isolated = False # check if all collections should be enabled - elif len(active_layer_collections) == 1 and active_layer_collections[0].name == self.name: + elif (len(active_layer_collections) == 1 and + active_layer_collections[0].name == self.name): # make all collections selectable for item in layer_collections.values(): item["ptr"].collection.hide_select = False @@ -622,17 +625,19 @@ class CMUnRestrictSelectAllOperator(Operator): keep_history = False for item in layer_collections.values(): + collection = item["ptr"].collection + if event.shift: keep_history = True - select_all_history.append(item["ptr"].collection.hide_select) - item["ptr"].collection.hide_select = not item["ptr"].collection.hide_select + select_all_history.append(collection.hide_select) + collection.hide_select = not collection.hide_select else: - if item["ptr"].collection.hide_select: + if collection.hide_select: keep_history = True - select_all_history.append(item["ptr"].collection.hide_select) - item["ptr"].collection.hide_select = False + select_all_history.append(collection.hide_select) + collection.hide_select = False if not keep_history: del rto_history["select_all"][view_layer] @@ -677,8 +682,8 @@ class CMHideOperator(Operator): laycol = layer_collections[self.name] # get active collections - active_layer_collections = [x["ptr"] for x in layer_collections.values() \ - if x["ptr"].hide_viewport == False] + active_layer_collections = [x["ptr"] for x in layer_collections.values() + if x["ptr"].hide_viewport == False] # check if previous state should be restored if cls.isolated and self.name == target: @@ -692,7 +697,8 @@ class CMHideOperator(Operator): cls.isolated = False # check if all collections should be enabled - elif len(active_layer_collections) == 1 and active_layer_collections[0].name == self.name: + elif (len(active_layer_collections) == 1 and + active_layer_collections[0].name == self.name): # show all collections for laycol in layer_collections.values(): laycol["ptr"].hide_viewport = False @@ -913,8 +919,8 @@ class CMDisableViewportOperator(Operator): laycol = layer_collections[self.name] # get active collections - active_layer_collections = [x["ptr"] for x in layer_collections.values() \ - if x["ptr"].collection.hide_viewport == False] + active_layer_collections = [x["ptr"] for x in layer_collections.values() + if x["ptr"].collection.hide_viewport == False] # check if previous state should be restored if cls.isolated and self.name == target: @@ -928,7 +934,8 @@ class CMDisableViewportOperator(Operator): cls.isolated = False # check if all collections should be enabled - elif len(active_layer_collections) == 1 and active_layer_collections[0].name == self.name: + elif (len(active_layer_collections) == 1 and + active_layer_collections[0].name == self.name): # enable all collections in viewport for laycol in layer_collections.values(): laycol["ptr"].collection.hide_viewport = False @@ -1094,18 +1101,19 @@ class CMUnDisableViewportAllOperator(Operator): keep_history = False for item in layer_collections.values(): + collection = item["ptr"].collection + if event.shift: keep_history = True - disable_all_history.append(item["ptr"].collection.hide_viewport) - item["ptr"].collection.hide_viewport = not \ - item["ptr"].collection.hide_viewport + disable_all_history.append(collection.hide_viewport) + collection.hide_viewport = not collection.hide_viewport else: - if item["ptr"].collection.hide_viewport: + if collection.hide_viewport: keep_history = True - disable_all_history.append(item["ptr"].collection.hide_viewport) - item["ptr"].collection.hide_viewport = False + disable_all_history.append(collection.hide_viewport) + collection.hide_viewport = False if not keep_history: del rto_history["disable_all"][view_layer] @@ -1150,8 +1158,8 @@ class CMDisableRenderOperator(Operator): laycol = layer_collections[self.name] # get active collections - active_layer_collections = [x["ptr"] for x in layer_collections.values() \ - if x["ptr"].collection.hide_render == False] + active_layer_collections = [x["ptr"] for x in layer_collections.values() + if x["ptr"].collection.hide_render == False] # check if previous state should be restored if cls.isolated and self.name == target: @@ -1165,7 +1173,8 @@ class CMDisableRenderOperator(Operator): cls.isolated = False # check if all collections should be enabled - elif len(active_layer_collections) == 1 and active_layer_collections[0].name == self.name: + elif (len(active_layer_collections) == 1 and + active_layer_collections[0].name == self.name): # allow render of all collections for laycol in layer_collections.values(): laycol["ptr"].collection.hide_render = False @@ -1331,18 +1340,19 @@ class CMUnDisableRenderAllOperator(Operator): keep_history = False for item in layer_collections.values(): + collection = item["ptr"].collection + if event.shift: keep_history = True - render_all_history.append(item["ptr"].collection.hide_render) - item["ptr"].collection.hide_render = not \ - item["ptr"].collection.hide_render + render_all_history.append(collection.hide_render) + collection.hide_render = not collection.hide_render else: - if item["ptr"].collection.hide_render: + if collection.hide_render: keep_history = True - render_all_history.append(item["ptr"].collection.hide_render) - item["ptr"].collection.hide_render = False + render_all_history.append(collection.hide_render) + collection.hide_render = False if not keep_history: del rto_history["render_all"][view_layer] @@ -1367,6 +1377,8 @@ class CMRemoveCollectionOperator(Operator): def execute(self, context): global rto_history + cm = context.scene.collection_manager + laycol = layer_collections[self.collection_name] collection = laycol["ptr"].collection parent_collection = laycol["parent"]["ptr"].collection @@ -1389,8 +1401,8 @@ class CMRemoveCollectionOperator(Operator): update_property_group(context) - if len(context.scene.CMListCollection) == context.scene.CMListIndex: - context.scene.CMListIndex = len(context.scene.CMListCollection) - 1 + if len(cm.cm_list_collection) == cm.cm_list_index: + cm.cm_list_index = len(cm.cm_list_collection) - 1 update_property_group(context) @@ -1413,12 +1425,13 @@ class CMNewCollectionOperator(Operator): global rto_history new_collection = bpy.data.collections.new('Collection') - scn = context.scene + cm = context.scene.collection_manager + # if there are collections - if len(scn.CMListCollection) > 0: + if len(cm.cm_list_collection) > 0: # get selected collection - laycol = layer_collections[scn.CMListCollection[scn.CMListIndex].name] + laycol = layer_collections[cm.cm_list_collection[cm.cm_list_index].name] # add new collection if self.child: @@ -1428,7 +1441,7 @@ class CMNewCollectionOperator(Operator): # update tree view property update_property_group(context) - scn.CMListIndex = layer_collections[new_collection.name]["row_index"] + cm.cm_list_index = layer_collections[new_collection.name]["row_index"] else: laycol["parent"]["ptr"].collection.children.link(new_collection) @@ -1436,16 +1449,16 @@ class CMNewCollectionOperator(Operator): # update tree view property update_property_group(context) - scn.CMListIndex = layer_collections[new_collection.name]["row_index"] + cm.cm_list_index = layer_collections[new_collection.name]["row_index"] # if no collections add top level collection and select it else: - scn.collection.children.link(new_collection) + context.scene.collection.children.link(new_collection) # update tree view property update_property_group(context) - scn.CMListIndex = 0 + cm.cm_list_index = 0 global rename rename[0] = True @@ -1482,18 +1495,18 @@ class CMPhantomModeOperator(Operator): global phantom_history global rto_history - scn = context.scene - view_layer = context.view_layer.name + cm = context.scene.collection_manager + view_layer = context.view_layer # enter Phantom Mode - if not scn.CM_Phantom_Mode: + if not cm.in_phantom_mode: - scn.CM_Phantom_Mode = True + cm.in_phantom_mode = True # save current visibility state - phantom_history["view_layer"] = view_layer + phantom_history["view_layer"] = view_layer.name - laycol_iter_list = [context.view_layer.layer_collection.children] + laycol_iter_list = [view_layer.layer_collection.children] while len(laycol_iter_list) > 0: new_laycol_iter_list = [] for laycol_iter in laycol_iter_list: @@ -1514,13 +1527,13 @@ class CMPhantomModeOperator(Operator): # save current rto history for rto, history, in rto_history.items(): - if history.get(view_layer, None): - phantom_history[rto+"_history"] = deepcopy(history[view_layer]) + if history.get(view_layer.name, None): + phantom_history[rto+"_history"] = deepcopy(history[view_layer.name]) # return to normal mode else: - laycol_iter_list = [context.view_layer.layer_collection.children] + laycol_iter_list = [view_layer.layer_collection.children] while len(laycol_iter_list) > 0: new_laycol_iter_list = [] for laycol_iter in laycol_iter_list: @@ -1551,15 +1564,15 @@ class CMPhantomModeOperator(Operator): # restore previous rto history for rto, history, in rto_history.items(): - if view_layer in history: - del history[view_layer] + if view_layer.name in history: + del history[view_layer.name] if phantom_history[rto+"_history"]: - history[view_layer] = deepcopy(phantom_history[rto+"_history"]) + history[view_layer.name] = deepcopy(phantom_history[rto+"_history"]) phantom_history[rto+"_history"].clear() - scn.CM_Phantom_Mode = False + cm.in_phantom_mode = False return {'FINISHED'} diff --git a/object_collection_manager/ui.py b/object_collection_manager/ui.py index dfdeb889..dcd804fa 100644 --- a/object_collection_manager/ui.py +++ b/object_collection_manager/ui.py @@ -49,12 +49,12 @@ class CollectionManager(Operator): def draw(self, context): layout = self.layout - scn = context.scene - view_layer = context.view_layer.name + cm = context.scene.collection_manager + view_layer = context.view_layer - if view_layer != self.last_view_layer: + if view_layer.name != self.last_view_layer: update_collection_tree(context) - self.last_view_layer = view_layer + self.last_view_layer = view_layer.name title_row = layout.split(factor=0.5) main = title_row.row() @@ -63,7 +63,7 @@ class CollectionManager(Operator): main.label(text="Collection Manager") - view.prop(context.view_layer, "use", text="") + view.prop(view_layer, "use", text="") view.separator() window = context.window @@ -104,48 +104,54 @@ class CollectionManager(Operator): sec2 = toggle_row.row() sec2.alignment = 'RIGHT' - if scn.show_exclude: - exclude_all_history = rto_history["exclude_all"].get(view_layer, []) + if cm.show_exclude: + exclude_all_history = rto_history["exclude_all"].get(view_layer.name, []) depress = True if len(exclude_all_history) else False sec2.operator("view3d.un_exclude_all_collections", text="", icon='CHECKBOX_HLT', depress=depress) - if scn.show_selectable: - select_all_history = rto_history["select_all"].get(view_layer, []) + if cm.show_selectable: + select_all_history = rto_history["select_all"].get(view_layer.name, []) depress = True if len(select_all_history) else False sec2.operator("view3d.un_restrict_select_all_collections", text="", icon='RESTRICT_SELECT_OFF', depress=depress) - if scn.show_hideviewport: - hide_all_history = rto_history["hide_all"].get(view_layer, []) + if cm.show_hide_viewport: + hide_all_history = rto_history["hide_all"].get(view_layer.name, []) depress = True if len(hide_all_history) else False sec2.operator("view3d.un_hide_all_collections", text="", icon='HIDE_OFF', depress=depress) - if scn.show_disableviewport: - disable_all_history = rto_history["disable_all"].get(view_layer, []) + if cm.show_disable_viewport: + disable_all_history = rto_history["disable_all"].get(view_layer.name, []) depress = True if len(disable_all_history) else False sec2.operator("view3d.un_disable_viewport_all_collections", text="", icon='RESTRICT_VIEW_OFF', depress=depress) - if scn.show_render: - render_all_history = rto_history["render_all"].get(view_layer, []) + if cm.show_render: + render_all_history = rto_history["render_all"].get(view_layer.name, []) depress = True if len(render_all_history) else False sec2.operator("view3d.un_disable_render_all_collections", text="", icon='RESTRICT_RENDER_OFF', depress=depress) - layout.row().template_list("CM_UL_items", "", context.scene, "CMListCollection", context.scene, "CMListIndex", rows=15, sort_lock=True) + layout.row().template_list("CM_UL_items", "", + cm, "cm_list_collection", + cm, "cm_list_index", + rows=15, + sort_lock=True) addcollec_row = layout.row() - addcollec_row.operator("view3d.add_collection", text="Add Collection", icon='COLLECTION_NEW').child = False + addcollec_row.operator("view3d.add_collection", text="Add Collection", + icon='COLLECTION_NEW').child = False - addcollec_row.operator("view3d.add_collection", text="Add SubCollection", icon='COLLECTION_NEW').child = True + addcollec_row.operator("view3d.add_collection", text="Add SubCollection", + icon='COLLECTION_NEW').child = True phantom_row = layout.row() - toggle_text = "Disable " if scn.CM_Phantom_Mode else "Enable " + toggle_text = "Disable " if cm.in_phantom_mode else "Enable " phantom_row.operator("view3d.toggle_phantom_mode", text=toggle_text+"Phantom Mode") - if scn.CM_Phantom_Mode: + if cm.in_phantom_mode: view.enabled = False addcollec_row.enabled = False @@ -154,23 +160,27 @@ class CollectionManager(Operator): wm = context.window_manager update_property_group(context) - self.view_layer = context.view_layer.name + + cm = context.scene.collection_manager + view_layer = context.view_layer + + self.view_layer = view_layer.name # sync selection in ui list with active layer collection try: - active_laycol_name = context.view_layer.active_layer_collection.name + active_laycol_name = view_layer.active_layer_collection.name active_laycol_row_index = layer_collections[active_laycol_name]["row_index"] - context.scene.CMListIndex = active_laycol_row_index + cm.cm_list_index = active_laycol_row_index except: - context.scene.CMListIndex = -1 + cm.cm_list_index = -1 # check if in phantom mode and if it's still viable - if context.scene.CM_Phantom_Mode: + if cm.in_phantom_mode: if set(layer_collections.keys()) != set(phantom_history["initial_state"].keys()): - context.scene.CM_Phantom_Mode = False + cm.in_phantom_mode = False - if context.view_layer.name != phantom_history["view_layer"]: - context.scene.CM_Phantom_Mode = False + if view_layer.name != phantom_history["view_layer"]: + cm.in_phantom_mode = False # handle window sizing max_width = 960 @@ -191,12 +201,12 @@ class CollectionManager(Operator): def update_selection(self, context): - scn = context.scene + cm = context.scene.collection_manager - if scn.CMListIndex == -1: + if cm.cm_list_index == -1: return - selected_item = scn.CMListCollection[scn.CMListIndex] + selected_item = cm.cm_list_collection[cm.cm_list_index] layer_collection = layer_collections[selected_item.name]["ptr"] context.view_layer.active_layer_collection = layer_collection @@ -250,8 +260,8 @@ class CM_UL_items(UIList): def draw_item(self, context, layout, data, item, icon, active_data,active_propname, index): self.use_filter_show = True - scn = context.scene - view_layer = context.view_layer.name + cm = context.scene.collection_manager + view_layer = context.view_layer laycol = layer_collections[item.name] collection = laycol["ptr"].collection @@ -267,13 +277,15 @@ class CM_UL_items(UIList): # add expander if collection has children to make UIList act like tree view if laycol["has_children"]: if laycol["expanded"]: - prop = row.operator("view3d.expand_sublevel", text="", icon='DISCLOSURE_TRI_DOWN', emboss=False) + prop = row.operator("view3d.expand_sublevel", text="", + icon='DISCLOSURE_TRI_DOWN', emboss=False) prop.expand = False prop.name = item.name prop.index = index else: - prop = row.operator("view3d.expand_sublevel", text="", icon='DISCLOSURE_TRI_RIGHT', emboss=False) + prop = row.operator("view3d.expand_sublevel", text="", + icon='DISCLOSURE_TRI_RIGHT', emboss=False) prop.expand = True prop.name = item.name prop.index = index @@ -286,7 +298,7 @@ class CM_UL_items(UIList): name_row = row.row() - #if rename[0] and index == scn.CMListIndex: + #if rename[0] and index == cm.cm_list_index: #name_row.activate_init = True #rename[0] = False @@ -308,72 +320,77 @@ class CM_UL_items(UIList): row_setcol.enabled = False - prop = row_setcol.operator("view3d.set_collection", text="", icon=icon, emboss=False) + prop = row_setcol.operator("view3d.set_collection", text="", + icon=icon, emboss=False) prop.collection_index = laycol["id"] prop.collection_name = item.name - if scn.show_exclude: - exclude_history_base = rto_history["exclude"].get(view_layer, {}) + if cm.show_exclude: + exclude_history_base = rto_history["exclude"].get(view_layer.name, {}) exclude_target = exclude_history_base.get("target", "") exclude_history = exclude_history_base.get("history", []) - depress = True if len(exclude_history) and exclude_target == item.name else False - emboss = True if len(exclude_history) and exclude_target == item.name else False + highlight = bool(exclude_history and exclude_target == item.name) icon = 'CHECKBOX_DEHLT' if laycol["ptr"].exclude else 'CHECKBOX_HLT' - row.operator("view3d.exclude_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name + row.operator("view3d.exclude_collection", text="", icon=icon, + emboss=highlight, depress=highlight).name = item.name - if scn.show_selectable: - select_history_base = rto_history["select"].get(view_layer, {}) + if cm.show_selectable: + select_history_base = rto_history["select"].get(view_layer.name, {}) select_target = select_history_base.get("target", "") select_history = select_history_base.get("history", []) - depress = True if len(select_history) and select_target == item.name else False - emboss = True if len(select_history) and select_target == item.name else False - icon = 'RESTRICT_SELECT_ON' if laycol["ptr"].collection.hide_select else 'RESTRICT_SELECT_OFF' + highlight = bool(select_history and select_target == item.name) + icon = ('RESTRICT_SELECT_ON' if laycol["ptr"].collection.hide_select else + 'RESTRICT_SELECT_OFF') - row.operator("view3d.restrict_select_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name + row.operator("view3d.restrict_select_collection", text="", icon=icon, + emboss=highlight, depress=highlight).name = item.name - if scn.show_hideviewport: - hide_history_base = rto_history["hide"].get(view_layer, {}) + if cm.show_hide_viewport: + hide_history_base = rto_history["hide"].get(view_layer.name, {}) hide_target = hide_history_base.get("target", "") hide_history = hide_history_base.get("history", []) - depress = True if len(hide_history) and hide_target == item.name else False - emboss = True if len(hide_history) and hide_target == item.name else False + highlight = bool(hide_history and hide_target == item.name) icon = 'HIDE_ON' if laycol["ptr"].hide_viewport else 'HIDE_OFF' - row.operator("view3d.hide_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name + row.operator("view3d.hide_collection", text="", icon=icon, + emboss=highlight, depress=highlight).name = item.name - if scn.show_disableviewport: - disable_history_base = rto_history["disable"].get(view_layer, {}) + if cm.show_disable_viewport: + disable_history_base = rto_history["disable"].get(view_layer.name, {}) disable_target = disable_history_base.get("target", "") disable_history = disable_history_base.get("history", []) - depress = True if len(disable_history) and disable_target == item.name else False - emboss = True if len(disable_history) and disable_target == item.name else False - icon = 'RESTRICT_VIEW_ON' if laycol["ptr"].collection.hide_viewport else 'RESTRICT_VIEW_OFF' + highlight = bool(disable_history and disable_target == item.name) + icon = ('RESTRICT_VIEW_ON' if laycol["ptr"].collection.hide_viewport else + 'RESTRICT_VIEW_OFF') - row.operator("view3d.disable_viewport_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name + row.operator("view3d.disable_viewport_collection", text="", icon=icon, + emboss=highlight, depress=highlight).name = item.name - if scn.show_render: - render_history_base = rto_history["render"].get(view_layer, {}) + if cm.show_render: + render_history_base = rto_history["render"].get(view_layer.name, {}) render_target = render_history_base.get("target", "") render_history = render_history_base.get("history", []) - depress = True if len(render_history) and render_target == item.name else False - emboss = True if len(render_history) and render_target == item.name else False - icon = 'RESTRICT_RENDER_ON' if laycol["ptr"].collection.hide_render else 'RESTRICT_RENDER_OFF' + highlight = bool(render_history and render_target == item.name) + icon = ('RESTRICT_RENDER_ON' if laycol["ptr"].collection.hide_render else + 'RESTRICT_RENDER_OFF') - row.operator("view3d.disable_render_collection", text="", icon=icon, emboss=emboss, depress=depress).name = item.name + row.operator("view3d.disable_render_collection", text="", icon=icon, + emboss=highlight, depress=highlight).name = item.name rm_op = split.row() rm_op.alignment = 'RIGHT' - rm_op.operator("view3d.remove_collection", text="", icon='X', emboss=False).collection_name = item.name + rm_op.operator("view3d.remove_collection", text="", icon='X', + emboss=False).collection_name = item.name - if scn.CM_Phantom_Mode: + if cm.in_phantom_mode: name_row.enabled = False row_setcol.enabled = False rm_op.enabled = False @@ -432,13 +449,13 @@ class CMRestrictionTogglesPanel(Panel): bl_region_type = 'HEADER' def draw(self, context): + cm = context.scene.collection_manager layout = self.layout - row = layout.row() - row.prop(context.scene, "show_exclude", icon='CHECKBOX_HLT', icon_only=True) - row.prop(context.scene, "show_selectable", icon='RESTRICT_SELECT_OFF', icon_only=True) - row.prop(context.scene, "show_hideviewport", icon='HIDE_OFF', icon_only=True) - row.prop(context.scene, "show_disableviewport", icon='RESTRICT_VIEW_OFF', icon_only=True) - row.prop(context.scene, "show_render", icon='RESTRICT_RENDER_OFF', icon_only=True) + row.prop(cm, "show_exclude", icon='CHECKBOX_HLT', icon_only=True) + row.prop(cm, "show_selectable", icon='RESTRICT_SELECT_OFF', icon_only=True) + row.prop(cm, "show_hide_viewport", icon='HIDE_OFF', icon_only=True) + row.prop(cm, "show_disable_viewport", icon='RESTRICT_VIEW_OFF', icon_only=True) + row.prop(cm, "show_render", icon='RESTRICT_RENDER_OFF', icon_only=True) diff --git a/object_color_rules.py b/object_color_rules.py index ff1b1ab2..1d60e295 100644 --- a/object_color_rules.py +++ b/object_color_rules.py @@ -23,8 +23,7 @@ bl_info = { "blender": (2, 80, 0), "location": "Properties > Object Buttons", "description": "Rules for assigning object color (for object & wireframe colors).", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "object/color_rules.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/object/color_rules.html", "category": "Object", } diff --git a/object_edit_linked.py b/object_edit_linked.py index 88ea91cb..b6445981 100644 --- a/object_edit_linked.py +++ b/object_edit_linked.py @@ -24,8 +24,7 @@ bl_info = { "blender": (2, 80, 0), "location": "File > External Data / View3D > Sidebar > Item Tab", "description": "Allows editing of objects linked from a .blend library.", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "object/edit_linked_library.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/object/edit_linked_library.html", "category": "Object", } diff --git a/object_fracture_cell/__init__.py b/object_fracture_cell/__init__.py index 04b47706..2e6d3b08 100644 --- a/object_fracture_cell/__init__.py +++ b/object_fracture_cell/__init__.py @@ -24,8 +24,7 @@ bl_info = { "location": "Viewport Object Menu -> Quick Effects", "description": "Fractured Object Creation", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "object/cell_fracture.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/object/cell_fracture.html", "category": "Object", } diff --git a/object_print3d_utils/__init__.py b/object_print3d_utils/__init__.py index 04b03a7e..9a2aefbe 100644 --- a/object_print3d_utils/__init__.py +++ b/object_print3d_utils/__init__.py @@ -24,7 +24,7 @@ bl_info = { "blender": (2, 82, 0), "location": "3D View > Sidebar", "description": "Utilities for 3D printing", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/mesh/3d_print_toolbox.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/3d_print_toolbox.html", "support": 'OFFICIAL', "category": "Mesh", } diff --git a/object_skinify.py b/object_skinify.py index 58263c9f..177f8de9 100644 --- a/object_skinify.py +++ b/object_skinify.py @@ -24,8 +24,7 @@ bl_info = { "location": "Pose Mode > Sidebar > Create Tab", "description": "Creates a mesh object from selected bones", "warning": "Work in progress", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "object/skinify.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/object/skinify.html", "category": "Object", } diff --git a/paint_palette.py b/paint_palette.py index 54b6eb17..10b8fe31 100644 --- a/paint_palette.py +++ b/paint_palette.py @@ -27,8 +27,7 @@ bl_info = { "location": "Image Editor and 3D View > Any Paint mode > Color Palette or Weight Palette panel", "description": "Palettes for color and weight paint modes", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "paint/paint_palettes.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/paint/paint_palettes.html", "category": "Paint", } diff --git a/real_snow.py b/real_snow.py new file mode 100644 index 00000000..f1091b2d --- /dev/null +++ b/real_snow.py @@ -0,0 +1,415 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +bl_info = { + "name": "Real Snow", + "description": "Generate snow mesh", + "author": "Wolf <wolf.art3d@gmail.com>", + "version": (1, 1), + "blender": (2, 83, 0), + "location": "View 3D > Properties Panel", + "doc_url": "https://github.com/macio97/Real-Snow", + "tracker_url": "https://github.com/macio97/Real-Snow/issues", + "support": "COMMUNITY", + "category": "Object", + } + + +# Libraries +import math +import os +import random +import time + +import bpy +import bmesh +from bpy.props import BoolProperty, FloatProperty, IntProperty, PointerProperty +from bpy.types import Operator, Panel, PropertyGroup +from mathutils import Vector + + +# Panel +class REAL_PT_snow(Panel): + bl_space_type = "VIEW_3D" + bl_context = "objectmode" + bl_region_type = "UI" + bl_label = "Snow" + bl_category = "Real Snow" + + def draw(self, context): + scn = context.scene + settings = scn.snow + layout = self.layout + + col = layout.column(align=True) + col.prop(settings, 'coverage', slider=True) + col.prop(settings, 'height') + + layout.use_property_split = True + layout.use_property_decorate = False + flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True) + col = flow.column() + col.prop(settings, 'vertices') + + row = layout.row(align=True) + row.scale_y = 1.5 + row.operator("snow.create", text="Add Snow", icon="FREEZE") + + +class SNOW_OT_Create(Operator): + bl_idname = "snow.create" + bl_label = "Create Snow" + bl_description = "Create snow" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context) -> bool: + return bool(context.selected_objects) + + def execute(self, context): + coverage = context.scene.snow.coverage + height = context.scene.snow.height + vertices = context.scene.snow.vertices + + # get list of selected objects except non-mesh objects + input_objects = [obj for obj in context.selected_objects if obj.type == 'MESH'] + snow_list = [] + # start UI progress bar + length = len(input_objects) + context.window_manager.progress_begin(0, 10) + timer=0 + for obj in input_objects: + # timer + context.window_manager.progress_update(timer) + # duplicate mesh + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(True) + context.view_layer.objects.active = obj + object_eval = obj.evaluated_get(context.view_layer.depsgraph) + mesh_eval = bpy.data.meshes.new_from_object(object_eval) + snow_object = bpy.data.objects.new("Snow", mesh_eval) + snow_object.matrix_world = obj.matrix_world + context.collection.objects.link(snow_object) + bpy.ops.object.select_all(action='DESELECT') + context.view_layer.objects.active = snow_object + snow_object.select_set(True) + bpy.ops.object.mode_set(mode = 'EDIT') + bm_orig = bmesh.from_edit_mesh(snow_object.data) + bm_copy = bm_orig.copy() + bm_copy.transform(obj.matrix_world) + bm_copy.normal_update() + # get faces data + delete_faces(vertices, bm_copy, snow_object) + ballobj = add_metaballs(context, height, snow_object) + context.view_layer.objects.active = snow_object + surface_area = area(snow_object) + snow = add_particles(context, surface_area, height, coverage, snow_object, ballobj) + add_modifiers(snow) + # place inside collection + context.view_layer.active_layer_collection = context.view_layer.layer_collection + if "Snow" not in context.scene.collection.children: + coll = bpy.data.collections.new("Snow") + context.scene.collection.children.link(coll) + else: + coll = bpy.data.collections["Snow"] + coll.objects.link(snow) + context.view_layer.layer_collection.collection.objects.unlink(snow) + add_material(snow) + # parent with object + snow.parent = obj + snow.matrix_parent_inverse = obj.matrix_world.inverted() + # add snow to list + snow_list.append(snow) + # update progress bar + timer += 0.1 / length + # select created snow meshes + for s in snow_list: + s.select_set(True) + # end progress bar + context.window_manager.progress_end() + + return {'FINISHED'} + + +def add_modifiers(snow): + bpy.ops.object.transform_apply(location=False, scale=True, rotation=False) + # decimate the mesh to get rid of some visual artifacts + snow.modifiers.new("Decimate", 'DECIMATE') + snow.modifiers["Decimate"].ratio = 0.5 + snow.modifiers.new("Subdiv", "SUBSURF") + snow.modifiers["Subdiv"].render_levels = 1 + snow.modifiers["Subdiv"].quality = 1 + snow.cycles.use_adaptive_subdivision = True + + +def add_particles(context, surface_area: float, height: float, coverage: float, snow_object: bpy.types.Object, ballobj: bpy.types.Object): + # approximate the number of particles to be emitted + number = int(surface_area*50*(height**-2)*((coverage/100)**2)) + bpy.ops.object.particle_system_add() + particles = snow_object.particle_systems[0] + psettings = particles.settings + psettings.type = 'HAIR' + psettings.render_type = 'OBJECT' + # generate random number for seed + random_seed = random.randint(0, 1000) + particles.seed = random_seed + # set particles object + psettings.particle_size = height + psettings.instance_object = ballobj + psettings.count = number + # convert particles to mesh + bpy.ops.object.select_all(action='DESELECT') + context.view_layer.objects.active = ballobj + ballobj.select_set(True) + bpy.ops.object.convert(target='MESH') + snow = bpy.context.active_object + snow.scale = [0.09, 0.09, 0.09] + bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') + bpy.ops.object.select_all(action='DESELECT') + snow_object.select_set(True) + bpy.ops.object.delete() + snow.select_set(True) + return snow + + +def add_metaballs(context, height: float, snow_object: bpy.types.Object) -> bpy.types.Object: + ball_name = "SnowBall" + ball = bpy.data.metaballs.new(ball_name) + ballobj = bpy.data.objects.new(ball_name, ball) + bpy.context.scene.collection.objects.link(ballobj) + # these settings have proven to work on a large amount of scenarios + ball.resolution = 0.7*height+0.3 + ball.threshold = 1.3 + element = ball.elements.new() + element.radius = 1.5 + element.stiffness = 0.75 + ballobj.scale = [0.09, 0.09, 0.09] + return ballobj + + +def delete_faces(vertices, bm_copy, snow_object: bpy.types.Object): + # find upper faces + if vertices: + selected_faces = [face.index for face in bm_copy.faces if face.select] + # based on a certain angle, find all faces not pointing up + down_faces = [face.index for face in bm_copy.faces if Vector((0, 0, -1.0)).angle(face.normal, 4.0) < (math.pi/2.0+0.5)] + bm_copy.free() + bpy.ops.mesh.select_all(action='DESELECT') + # select upper faces + mesh = bmesh.from_edit_mesh(snow_object.data) + for face in mesh.faces: + if vertices: + if not face.index in selected_faces: + face.select = True + if face.index in down_faces: + face.select = True + # delete unneccessary faces + faces_select = [face for face in mesh.faces if face.select] + bmesh.ops.delete(mesh, geom=faces_select, context='FACES_KEEP_BOUNDARY') + mesh.free() + bpy.ops.object.mode_set(mode = 'OBJECT') + + +def area(obj: bpy.types.Object) -> float: + bm_obj = bmesh.new() + bm_obj.from_mesh(obj.data) + bm_obj.transform(obj.matrix_world) + area = sum(face.calc_area() for face in bm_obj.faces) + bm_obj.free + return area + + +def add_material(obj: bpy.types.Object): + mat_name = "Snow" + # if material doesn't exist, create it + if mat_name in bpy.data.materials: + bpy.data.materials[mat_name].name = mat_name+".001" + mat = bpy.data.materials.new(mat_name) + mat.use_nodes = True + nodes = mat.node_tree.nodes + # delete all nodes + for node in nodes: + nodes.remove(node) + # add nodes + output = nodes.new('ShaderNodeOutputMaterial') + principled = nodes.new('ShaderNodeBsdfPrincipled') + vec_math = nodes.new('ShaderNodeVectorMath') + com_xyz = nodes.new('ShaderNodeCombineXYZ') + dis = nodes.new('ShaderNodeDisplacement') + mul1 = nodes.new('ShaderNodeMath') + add1 = nodes.new('ShaderNodeMath') + add2 = nodes.new('ShaderNodeMath') + mul2 = nodes.new('ShaderNodeMath') + mul3 = nodes.new('ShaderNodeMath') + ramp1 = nodes.new('ShaderNodeValToRGB') + ramp2 = nodes.new('ShaderNodeValToRGB') + ramp3 = nodes.new('ShaderNodeValToRGB') + vor = nodes.new('ShaderNodeTexVoronoi') + noise1 = nodes.new('ShaderNodeTexNoise') + noise2 = nodes.new('ShaderNodeTexNoise') + noise3 = nodes.new('ShaderNodeTexNoise') + mapping = nodes.new('ShaderNodeMapping') + coord = nodes.new('ShaderNodeTexCoord') + # change location + output.location = (100, 0) + principled.location = (-200, 500) + vec_math.location = (-400, 400) + com_xyz.location = (-600, 400) + dis.location = (-200, -100) + mul1.location = (-400, -100) + add1.location = (-600, -100) + add2.location = (-800, -100) + mul2.location = (-1000, -100) + mul3.location = (-1000, -300) + ramp1.location = (-500, 150) + ramp2.location = (-1300, -300) + ramp3.location = (-1000, -500) + vor.location = (-1500, 200) + noise1.location = (-1500, 0) + noise2.location = (-1500, -200) + noise3.location = (-1500, -400) + mapping.location = (-1700, 0) + coord.location = (-1900, 0) + # change node parameters + principled.distribution = "MULTI_GGX" + principled.subsurface_method = "RANDOM_WALK" + principled.inputs[0].default_value[0] = 0.904 + principled.inputs[0].default_value[1] = 0.904 + principled.inputs[0].default_value[2] = 0.904 + principled.inputs[1].default_value = 1 + principled.inputs[2].default_value[0] = 0.36 + principled.inputs[2].default_value[1] = 0.46 + principled.inputs[2].default_value[2] = 0.6 + principled.inputs[3].default_value[0] = 0.904 + principled.inputs[3].default_value[1] = 0.904 + principled.inputs[3].default_value[2] = 0.904 + principled.inputs[5].default_value = 0.224 + principled.inputs[7].default_value = 0.1 + principled.inputs[13].default_value = 0.1 + vec_math.operation = "MULTIPLY" + vec_math.inputs[1].default_value[0] = 0.5 + vec_math.inputs[1].default_value[1] = 0.5 + vec_math.inputs[1].default_value[2] = 0.5 + com_xyz.inputs[0].default_value = 0.36 + com_xyz.inputs[1].default_value = 0.46 + com_xyz.inputs[2].default_value = 0.6 + dis.inputs[1].default_value = 0.1 + dis.inputs[2].default_value = 0.3 + mul1.operation = "MULTIPLY" + mul1.inputs[1].default_value = 0.1 + mul2.operation = "MULTIPLY" + mul2.inputs[1].default_value = 0.6 + mul3.operation = "MULTIPLY" + mul3.inputs[1].default_value = 0.4 + ramp1.color_ramp.elements[0].position = 0.525 + ramp1.color_ramp.elements[1].position = 0.58 + ramp2.color_ramp.elements[0].position = 0.069 + ramp2.color_ramp.elements[1].position = 0.757 + ramp3.color_ramp.elements[0].position = 0.069 + ramp3.color_ramp.elements[1].position = 0.757 + vor.feature = "N_SPHERE_RADIUS" + vor.inputs[2].default_value = 30 + noise1.inputs[2].default_value = 12 + noise2.inputs[2].default_value = 2 + noise2.inputs[3].default_value = 4 + noise3.inputs[2].default_value = 1 + noise3.inputs[3].default_value = 4 + mapping.inputs[3].default_value[0] = 12 + mapping.inputs[3].default_value[1] = 12 + mapping.inputs[3].default_value[2] = 12 + # link nodes + link = mat.node_tree.links + link.new(principled.outputs[0], output.inputs[0]) + link.new(vec_math.outputs[0], principled.inputs[2]) + link.new(com_xyz.outputs[0], vec_math.inputs[0]) + link.new(dis.outputs[0], output.inputs[2]) + link.new(mul1.outputs[0], dis.inputs[0]) + link.new(add1.outputs[0], mul1.inputs[0]) + link.new(add2.outputs[0], add1.inputs[0]) + link.new(mul2.outputs[0], add2.inputs[0]) + link.new(mul3.outputs[0], add2.inputs[1]) + link.new(ramp1.outputs[0], principled.inputs[12]) + link.new(ramp2.outputs[0], mul3.inputs[0]) + link.new(ramp3.outputs[0], add1.inputs[1]) + link.new(vor.outputs[4], ramp1.inputs[0]) + link.new(noise1.outputs[0], mul2.inputs[0]) + link.new(noise2.outputs[0], ramp2.inputs[0]) + link.new(noise3.outputs[0], ramp3.inputs[0]) + link.new(mapping.outputs[0], vor.inputs[0]) + link.new(mapping.outputs[0], noise1.inputs[0]) + link.new(mapping.outputs[0], noise2.inputs[0]) + link.new(mapping.outputs[0], noise3.inputs[0]) + link.new(coord.outputs[3], mapping.inputs[0]) + # set displacement and add material + mat.cycles.displacement_method = "DISPLACEMENT" + obj.data.materials.append(mat) + + +# Properties +class SnowSettings(PropertyGroup): + coverage : IntProperty( + name = "Coverage", + description = "Percentage of the object to be covered with snow", + default = 100, + min = 0, + max = 100, + subtype = 'PERCENTAGE' + ) + + height : FloatProperty( + name = "Height", + description = "Height of the snow", + default = 0.3, + step = 1, + precision = 2, + min = 0.1, + max = 1 + ) + + vertices : BoolProperty( + name = "Selected Faces", + description = "Add snow only on selected faces", + default = False + ) + + +############################################################################################# +classes = ( + REAL_PT_snow, + SNOW_OT_Create, + SnowSettings + ) + +register, unregister = bpy.utils.register_classes_factory(classes) + +# Register +def register(): + for cls in classes: + bpy.utils.register_class(cls) + bpy.types.Scene.snow = PointerProperty(type=SnowSettings) + + +# Unregister +def unregister(): + for cls in classes: + bpy.utils.unregister_class(cls) + del bpy.types.Scene.snow + + +if __name__ == "__main__": + register() diff --git a/render_auto_tile_size.py b/render_auto_tile_size.py index b1cac20b..078513c6 100644 --- a/render_auto_tile_size.py +++ b/render_auto_tile_size.py @@ -24,8 +24,7 @@ bl_info = { "blender": (2, 80, 0), "location": "Render Settings > Performance", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "render/auto_tile_size.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/render/auto_tile_size.html", "category": "Render", } diff --git a/render_copy_settings/__init__.py b/render_copy_settings/__init__.py index 32a5fb0c..ecc3603d 100644 --- a/render_copy_settings/__init__.py +++ b/render_copy_settings/__init__.py @@ -26,8 +26,7 @@ bl_info = { "location": "Render buttons (Properties window)", "description": "Allows to copy a selection of render settings " "from current scene to others.", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "render/copy_settings.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/render/copy_settings.html", "category": "Render", } diff --git a/render_freestyle_svg.py b/render_freestyle_svg.py index a6936bff..cb27ae64 100644 --- a/render_freestyle_svg.py +++ b/render_freestyle_svg.py @@ -26,7 +26,7 @@ bl_info = { "location": "Properties > Render > Freestyle SVG Export", "description": "Exports Freestyle's stylized edges in SVG format", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/render/render_freestyle_svg.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/render/render_freestyle_svg.html", "support": 'OFFICIAL', "category": "Render", } diff --git a/render_povray/__init__.py b/render_povray/__init__.py index 8d6b7540..5080aab9 100644 --- a/render_povray/__init__.py +++ b/render_povray/__init__.py @@ -102,7 +102,7 @@ bl_info = { "blender": (2, 81, 0), "location": "Render Properties > Render Engine > Persistence of Vision", "description": "Persistence of Vision integration for blender", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/render/povray.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/render/povray.html", "category": "Render", } diff --git a/rigify/__init__.py b/rigify/__init__.py index a6e7fa87..00777a78 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -25,8 +25,7 @@ bl_info = { "blender": (2, 81, 0), "description": "Automatic rigging from building-block components", "location": "Armature properties, Bone properties, View3d tools panel, Armature Add menu", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "rigging/rigify/index.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/rigging/rigify/index.html", "category": "Rigging", } diff --git a/rigify/rigs/experimental/super_chain.py b/rigify/rigs/experimental/super_chain.py index 346c16b8..a5af8323 100644 --- a/rigify/rigs/experimental/super_chain.py +++ b/rigify/rigs/experimental/super_chain.py @@ -558,7 +558,7 @@ class Rig: ) invert_last = True - if self.params.wgt_align_axis not in {'y' or '-y'}: + if self.params.wgt_align_axis not in {'y', '-y'}: invert_last = False create_chain_widget( diff --git a/space_clip_editor_refine_solution.py b/space_clip_editor_refine_solution.py index 13985c2a..457af286 100644 --- a/space_clip_editor_refine_solution.py +++ b/space_clip_editor_refine_solution.py @@ -30,8 +30,7 @@ bl_info = { "description": "Refine motion solution by setting track weight according" " to reprojection error", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "video_tools/refine_tracking.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/video_tools/refine_tracking.html", "category": "Video Tools", } diff --git a/space_view3d_3d_navigation.py b/space_view3d_3d_navigation.py index c8b27323..fea417fe 100644 --- a/space_view3d_3d_navigation.py +++ b/space_view3d_3d_navigation.py @@ -30,8 +30,7 @@ bl_info = { "location": "View3D > Sidebar > View Tab", "description": "Navigate the Camera & 3D View from the Toolshelf", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "3d_view/3d_navigation.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/3d_view/3d_navigation.html", "category": "3D View", } diff --git a/space_view3d_align_tools.py b/space_view3d_align_tools.py index c021ff06..8bfa1f6b 100644 --- a/space_view3d_align_tools.py +++ b/space_view3d_align_tools.py @@ -26,8 +26,7 @@ bl_info = { "location": "View3D > Sidebar > Item Tab", "description": "Align Selected Objects to Active Object", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "object/align_tools.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/object/align_tools.html", "category": "Object", } diff --git a/space_view3d_brush_menus/__init__.py b/space_view3d_brush_menus/__init__.py index 3607630a..c040bd9d 100644 --- a/space_view3d_brush_menus/__init__.py +++ b/space_view3d_brush_menus/__init__.py @@ -27,7 +27,7 @@ bl_info = { "blender": (2, 80, 0), "location": "Spacebar in Sculpt/Paint Modes", "warning": '', - "doc_url": "https://docs.blender.org/manual/en/dev/addons/interface/brush_menus.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/brush_menus.html", "category": "Interface", } diff --git a/space_view3d_copy_attributes.py b/space_view3d_copy_attributes.py index c4bf4c42..4c426551 100644 --- a/space_view3d_copy_attributes.py +++ b/space_view3d_copy_attributes.py @@ -25,8 +25,7 @@ bl_info = { "blender": (2, 80, 0), "location": "View3D > Ctrl-C", "description": "Copy Attributes Menu from Blender 2.4", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "interface/copy_attributes.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/copy_attributes.html", "category": "Interface", } diff --git a/space_view3d_math_vis/__init__.py b/space_view3d_math_vis/__init__.py index 23995374..c56e5f7f 100644 --- a/space_view3d_math_vis/__init__.py +++ b/space_view3d_math_vis/__init__.py @@ -25,8 +25,7 @@ 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", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "3d_view/math_vis_console.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/3d_view/math_vis_console.html", "support": "OFFICIAL", "category": "3D View", } diff --git a/space_view3d_modifier_tools.py b/space_view3d_modifier_tools.py index a4bc1d0e..aaef9b6a 100644 --- a/space_view3d_modifier_tools.py +++ b/space_view3d_modifier_tools.py @@ -25,8 +25,7 @@ bl_info = { "location": "Properties > Modifiers", "description": "Modifiers Specials Show/Hide/Apply Selected", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "interface/modifier_tools.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/modifier_tools.html", "category": "Interface" } diff --git a/space_view3d_pie_menus/__init__.py b/space_view3d_pie_menus/__init__.py index a9044df5..783f061a 100644 --- a/space_view3d_pie_menus/__init__.py +++ b/space_view3d_pie_menus/__init__.py @@ -39,8 +39,7 @@ bl_info = { "description": "Pie Menu Activation", "location": "Addons Preferences", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "interface/viewport_pies.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/viewport_pies.html", "category": "Interface" } diff --git a/space_view3d_spacebar_menu/__init__.py b/space_view3d_spacebar_menu/__init__.py index 51a7d4dd..0ac50e2d 100644 --- a/space_view3d_spacebar_menu/__init__.py +++ b/space_view3d_spacebar_menu/__init__.py @@ -25,8 +25,7 @@ bl_info = { "location": "View3D > Spacebar", "description": "Object Mode Context Sensitive Spacebar Menu", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "interface/context_menu.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/context_menu.html", "category": "Interface", } diff --git a/space_view3d_stored_views/__init__.py b/space_view3d_stored_views/__init__.py index 527ef040..6a174fdb 100644 --- a/space_view3d_stored_views/__init__.py +++ b/space_view3d_stored_views/__init__.py @@ -24,8 +24,7 @@ bl_info = { "blender": (2, 80, 0), "location": "View3D > Sidebar > View > Stored Views", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "3d_view/stored_views.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/3d_view/stored_views.html", "category": "3D View" } diff --git a/sun_position/__init__.py b/sun_position/__init__.py index 499cafc3..c1638ef6 100644 --- a/sun_position/__init__.py +++ b/sun_position/__init__.py @@ -38,8 +38,7 @@ bl_info = { "blender": (2, 80, 0), "location": "World > Sun Position", "description": "Show sun position with objects and/or sky texture", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "lighting/sun_position.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/lighting/sun_position.html", "category": "Lighting", } diff --git a/system_blend_info.py b/system_blend_info.py index b2e7b926..7f574cf7 100644 --- a/system_blend_info.py +++ b/system_blend_info.py @@ -28,8 +28,7 @@ bl_info = { "location": "Properties > Scene > Blend Info Panel", "description": "Show information about the .blend", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "system/blend_info.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/system/blend_info.html", "category": "System", } diff --git a/system_demo_mode/__init__.py b/system_demo_mode/__init__.py index f7cc7d7c..88a052f1 100644 --- a/system_demo_mode/__init__.py +++ b/system_demo_mode/__init__.py @@ -25,8 +25,7 @@ bl_info = { "location": "File > Demo Menu", "description": "Demo mode lets you select multiple blend files and loop over them.", "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "system/demo_mode.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/system/demo_mode.html", "support": 'OFFICIAL', "category": "System", } diff --git a/system_property_chart.py b/system_property_chart.py index 901d06ec..4eeeb48e 100644 --- a/system_property_chart.py +++ b/system_property_chart.py @@ -27,8 +27,7 @@ bl_info = { "description": ("Edit arbitrary selected properties for " "objects/sequence strips of the same type"), "warning": "", - "doc_url": "https://docs.blender.org/manual/en/dev/addons/" - "system/property_chart.html", + "doc_url": "{BLENDER_MANUAL_URL}/addons/system/property_chart.html", "category": "System", } |