Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Eisel <eiseljulian@gmail.com>2020-03-10 04:09:05 +0300
committerJulian Eisel <eiseljulian@gmail.com>2020-03-10 04:09:05 +0300
commit3f13c42a84cca060f6aefd69f107921599b6bee3 (patch)
tree8f01dbb856b7ed8e19decca37d88a8c469077aa3
parentc444673d3c8e21e44f570ff8185218f39081a87b (diff)
parentd674b04ac05dc656b58f678949c8aa83c41c96f4 (diff)
Merge branch 'master' into soc-2019-openxr
-rw-r--r--add_camera_rigs/__init__.py7
-rw-r--r--add_camera_rigs/build_rigs.py465
-rw-r--r--add_camera_rigs/composition_guides_menu.py4
-rw-r--r--add_camera_rigs/create_widgets.py58
-rw-r--r--add_camera_rigs/operators.py35
-rw-r--r--add_camera_rigs/ui_panels.py111
-rw-r--r--add_curve_extra_objects/__init__.py7
-rw-r--r--add_curve_ivygen.py3
-rw-r--r--add_curve_sapling/__init__.py3
-rw-r--r--add_mesh_BoltFactory/__init__.py3
-rw-r--r--add_mesh_discombobulator/__init__.py3
-rw-r--r--add_mesh_extra_objects/__init__.py3
-rw-r--r--add_mesh_geodesic_domes/__init__.py3
-rw-r--r--animation_add_corrective_shape_key.py3
-rw-r--r--animation_animall.py3
-rw-r--r--ant_landscape/__init__.py3
-rw-r--r--archimesh/__init__.py3
-rw-r--r--blender_id/__init__.py3
-rw-r--r--blenderkit/__init__.py3
-rw-r--r--bone_selection_sets.py3
-rw-r--r--btrace/__init__.py3
-rw-r--r--camera_turnaround.py3
-rw-r--r--curve_assign_shapekey.py3
-rw-r--r--curve_simplify.py9
-rw-r--r--curve_tools/__init__.py3
-rw-r--r--development_edit_operator.py3
-rw-r--r--development_icon_get.py3
-rw-r--r--development_iskeyfree.py3
-rw-r--r--io_anim_bvh/__init__.py3
-rw-r--r--io_anim_camera.py3
-rw-r--r--io_anim_nuke_chan/__init__.py3
-rw-r--r--io_coat3D/__init__.py3
-rw-r--r--io_curve_svg/__init__.py3
-rw-r--r--io_export_dxf/__init__.py3
-rw-r--r--io_export_paper_model.py3
-rw-r--r--io_export_pc2.py3
-rw-r--r--io_import_dxf/__init__.py3
-rw-r--r--io_import_images_as_planes.py3
-rw-r--r--io_import_palette/__init__.py3
-rw-r--r--io_mesh_atomic/__init__.py3
-rw-r--r--io_mesh_ply/__init__.py3
-rw-r--r--io_mesh_stl/__init__.py3
-rw-r--r--io_mesh_uv_layout/__init__.py3
-rw-r--r--io_scene_fbx/__init__.py3
-rwxr-xr-xio_scene_gltf2/__init__.py19
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_math.py42
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py11
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py2
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_animation_bone.py13
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_animation_node.py13
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_gltf.py21
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_node.py9
-rwxr-xr-xio_scene_gltf2/blender/imp/gltf2_blender_primitive.py27
-rw-r--r--io_scene_gltf2/blender/imp/gltf2_blender_vnode.py181
-rw-r--r--io_scene_gltf2/io/com/gltf2_io_color_management.py26
-rw-r--r--io_scene_obj/__init__.py3
-rw-r--r--io_scene_x3d/__init__.py3
-rw-r--r--io_shape_mdd/__init__.py3
-rw-r--r--lighting_dynamic_sky.py3
-rw-r--r--lighting_tri_lights.py3
-rw-r--r--magic_uv/__init__.py3
-rw-r--r--materials_library_vx/__init__.py3
-rw-r--r--materials_utils/__init__.py3
-rw-r--r--measureit/__init__.py2
-rw-r--r--mesh_auto_mirror.py3
-rw-r--r--mesh_bsurfaces.py3
-rw-r--r--mesh_f2.py3
-rw-r--r--mesh_inset/__init__.py3
-rw-r--r--mesh_looptools.py3
-rw-r--r--mesh_tools/__init__.py3
-rw-r--r--node_arrange.py3
-rw-r--r--node_presets.py3
-rw-r--r--node_wrangler.py3
-rw-r--r--object_boolean_tools.py3
-rw-r--r--object_carver/__init__.py3
-rw-r--r--object_collection_manager/__init__.py45
-rw-r--r--object_collection_manager/internals.py13
-rw-r--r--object_collection_manager/operators.py121
-rw-r--r--object_collection_manager/ui.py163
-rw-r--r--object_color_rules.py3
-rw-r--r--object_edit_linked.py3
-rw-r--r--object_fracture_cell/__init__.py3
-rw-r--r--object_print3d_utils/__init__.py2
-rw-r--r--object_skinify.py3
-rw-r--r--paint_palette.py3
-rw-r--r--real_snow.py415
-rw-r--r--render_auto_tile_size.py3
-rw-r--r--render_copy_settings/__init__.py3
-rw-r--r--render_freestyle_svg.py2
-rw-r--r--render_povray/__init__.py2
-rw-r--r--rigify/__init__.py3
-rw-r--r--rigify/rigs/experimental/super_chain.py2
-rw-r--r--space_clip_editor_refine_solution.py3
-rw-r--r--space_view3d_3d_navigation.py3
-rw-r--r--space_view3d_align_tools.py3
-rw-r--r--space_view3d_brush_menus/__init__.py2
-rw-r--r--space_view3d_copy_attributes.py3
-rw-r--r--space_view3d_math_vis/__init__.py3
-rw-r--r--space_view3d_modifier_tools.py3
-rw-r--r--space_view3d_pie_menus/__init__.py3
-rw-r--r--space_view3d_spacebar_menu/__init__.py3
-rw-r--r--space_view3d_stored_views/__init__.py3
-rw-r--r--sun_position/__init__.py3
-rw-r--r--system_blend_info.py3
-rw-r--r--system_demo_mode/__init__.py3
-rw-r--r--system_property_chart.py3
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",
}
diff --git a/mesh_f2.py b/mesh_f2.py
index dcea9d38..569b86de 100644
--- a/mesh_f2.py
+++ b/mesh_f2.py
@@ -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",
}