Files
blender/release/scripts/startup/bl_operators/object.py

1010 lines
34 KiB
Python
Raw Normal View History

2009-11-26 15:36:23 +00:00
# ##### 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,
2010-02-12 13:34:04 +00:00
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2009-11-26 15:36:23 +00:00
#
# ##### END GPL LICENSE BLOCK #####
# <pep8-80 compliant>
2009-12-05 22:03:07 +00:00
2009-11-26 15:36:23 +00:00
import bpy
from bpy.types import Operator
2015-01-27 17:46:07 +11:00
from bpy.props import (
BoolProperty,
EnumProperty,
IntProperty,
StringProperty,
)
class SelectPattern(Operator):
"""Select objects matching a naming pattern"""
bl_idname = "object.select_pattern"
bl_label = "Select Pattern"
bl_options = {'REGISTER', 'UNDO'}
pattern: StringProperty(
2018-06-26 19:41:37 +02:00
name="Pattern",
description="Name filter using '*', '?' and "
"'[abc]' unix style wildcards",
maxlen=64,
default="*",
)
case_sensitive: BoolProperty(
2018-06-26 19:41:37 +02:00
name="Case Sensitive",
description="Do a case sensitive compare",
default=False,
)
extend: BoolProperty(
2018-06-26 19:41:37 +02:00
name="Extend",
description="Extend the existing selection",
default=True,
)
def execute(self, context):
import fnmatch
if self.case_sensitive:
pattern_match = fnmatch.fnmatchcase
else:
pattern_match = (lambda a, b:
fnmatch.fnmatchcase(a.upper(), b.upper()))
is_ebone = False
is_pbone = False
2009-12-01 14:48:36 +00:00
obj = context.object
if obj and obj.mode == 'POSE':
2009-12-01 14:48:36 +00:00
items = obj.data.bones
if not self.extend:
bpy.ops.pose.select_all(action='DESELECT')
is_pbone = True
elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT':
2009-12-01 14:48:36 +00:00
items = obj.data.edit_bones
if not self.extend:
bpy.ops.armature.select_all(action='DESELECT')
is_ebone = True
2009-12-01 14:48:36 +00:00
else:
items = context.visible_objects
if not self.extend:
bpy.ops.object.select_all(action='DESELECT')
2009-12-01 14:48:36 +00:00
# Can be pose bones, edit bones or objects
2009-12-01 14:48:36 +00:00
for item in items:
if pattern_match(item.name, self.pattern):
# hrmf, perhaps there should be a utility function for this.
if is_ebone:
Render Layers and Collections (merge from render-layers) Design Documents ---------------- * https://wiki.blender.org/index.php/Dev:2.8/Source/Layers * https://wiki.blender.org/index.php/Dev:2.8/Source/DataDesignRevised User Commit Log --------------- * New Layer and Collection system to replace render layers and viewport layers. * A layer is a set of collections of objects (and their drawing options) required for specific tasks. * A collection is a set of objects, equivalent of the old layers in Blender. A collection can be shared across multiple layers. * All Scenes have a master collection that all other collections are children of. * New collection "context" tab (in Properties Editor) * New temporary viewport "collections" panel to control per-collection visibility Missing User Features --------------------- * Collection "Filter" Option to add objects based on their names * Collection Manager operators The existing buttons are placeholders * Collection Manager drawing The editor main region is empty * Collection Override * Per-Collection engine settings This will come as a separate commit, as part of the clay-engine branch Dev Commit Log -------------- * New DNA file (DNA_layer_types.h) with the new structs We are replacing Base by a new extended Base while keeping it backward compatible with some legacy settings (i.e., lay, flag_legacy). Renamed all Base to BaseLegacy to make it clear the areas of code that still need to be converted Note: manual changes were required on - deg_builder_nodes.h, rna_object.c, KX_Light.cpp * Unittesting for main syncronization requirements - read, write, add/copy/remove objects, copy scene, collection link/unlinking, context) * New Editor: Collection Manager Based on patch by Julian Eisel This is extracted from the layer-manager branch. With the following changes: - Renamed references of layer manager to collections manager - I doesn't include the editors/space_collections/ draw and util files - The drawing code itself will be implemented separately by Julian * Base / Object: A little note about them. Original Blender code would try to keep them in sync through the code, juggling flags back and forth. This will now be handled by Depsgraph, keeping Object and Bases more separated throughout the non-rendering code. Scene.base is being cleared in doversion, and the old viewport drawing code was poorly converted to use the new bases while the new viewport code doesn't get merged and replace the old one. Python API Changes ------------------ ``` - scene.layers + # no longer exists - scene.objects + scene.scene_layers.active.objects - scene.objects.active + scene.render_layers.active.objects.active - bpy.context.scene.objects.link() + bpy.context.scene_collection.objects.link() - bpy_extras.object_utils.object_data_add(context, obdata, operator=None, use_active_layer=True, name=None) + bpy_extras.object_utils.object_data_add(context, obdata, operator=None, name=None) - bpy.context.object.select + bpy.context.object.select = True + bpy.context.object.select = False + bpy.context.object.select_get() + bpy.context.object.select_set(action='SELECT') + bpy.context.object.select_set(action='DESELECT') -AddObjectHelper.layers + # no longer exists ```
2017-02-07 10:18:38 +01:00
item.select = True
item.select_head = True
item.select_tail = True
if item.use_connect:
item_parent = item.parent
if item_parent is not None:
item_parent.select_tail = True
elif is_pbone:
item.select = True
Render Layers and Collections (merge from render-layers) Design Documents ---------------- * https://wiki.blender.org/index.php/Dev:2.8/Source/Layers * https://wiki.blender.org/index.php/Dev:2.8/Source/DataDesignRevised User Commit Log --------------- * New Layer and Collection system to replace render layers and viewport layers. * A layer is a set of collections of objects (and their drawing options) required for specific tasks. * A collection is a set of objects, equivalent of the old layers in Blender. A collection can be shared across multiple layers. * All Scenes have a master collection that all other collections are children of. * New collection "context" tab (in Properties Editor) * New temporary viewport "collections" panel to control per-collection visibility Missing User Features --------------------- * Collection "Filter" Option to add objects based on their names * Collection Manager operators The existing buttons are placeholders * Collection Manager drawing The editor main region is empty * Collection Override * Per-Collection engine settings This will come as a separate commit, as part of the clay-engine branch Dev Commit Log -------------- * New DNA file (DNA_layer_types.h) with the new structs We are replacing Base by a new extended Base while keeping it backward compatible with some legacy settings (i.e., lay, flag_legacy). Renamed all Base to BaseLegacy to make it clear the areas of code that still need to be converted Note: manual changes were required on - deg_builder_nodes.h, rna_object.c, KX_Light.cpp * Unittesting for main syncronization requirements - read, write, add/copy/remove objects, copy scene, collection link/unlinking, context) * New Editor: Collection Manager Based on patch by Julian Eisel This is extracted from the layer-manager branch. With the following changes: - Renamed references of layer manager to collections manager - I doesn't include the editors/space_collections/ draw and util files - The drawing code itself will be implemented separately by Julian * Base / Object: A little note about them. Original Blender code would try to keep them in sync through the code, juggling flags back and forth. This will now be handled by Depsgraph, keeping Object and Bases more separated throughout the non-rendering code. Scene.base is being cleared in doversion, and the old viewport drawing code was poorly converted to use the new bases while the new viewport code doesn't get merged and replace the old one. Python API Changes ------------------ ``` - scene.layers + # no longer exists - scene.objects + scene.scene_layers.active.objects - scene.objects.active + scene.render_layers.active.objects.active - bpy.context.scene.objects.link() + bpy.context.scene_collection.objects.link() - bpy_extras.object_utils.object_data_add(context, obdata, operator=None, use_active_layer=True, name=None) + bpy_extras.object_utils.object_data_add(context, obdata, operator=None, name=None) - bpy.context.object.select + bpy.context.object.select = True + bpy.context.object.select = False + bpy.context.object.select_get() + bpy.context.object.select_set(action='SELECT') + bpy.context.object.select_set(action='DESELECT') -AddObjectHelper.layers + # no longer exists ```
2017-02-07 10:18:38 +01:00
else:
item.select_set(True)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_popup(self, event)
2009-12-05 22:03:07 +00:00
def draw(self, context):
layout = self.layout
2009-12-05 22:03:07 +00:00
layout.prop(self, "pattern")
row = layout.row()
row.prop(self, "case_sensitive")
row.prop(self, "extend")
2009-11-26 15:36:23 +00:00
class SelectCamera(Operator):
"""Select the active camera"""
bl_idname = "object.select_camera"
bl_label = "Select Camera"
bl_options = {'REGISTER', 'UNDO'}
extend: BoolProperty(
2018-06-26 19:41:37 +02:00
name="Extend",
description="Extend the selection",
default=False
)
def execute(self, context):
scene = context.scene
view_layer = context.view_layer
view = context.space_data
if view.type == 'VIEW_3D' and not view.lock_camera_and_layers:
camera = view.camera
else:
camera = scene.camera
if camera is None:
self.report({'WARNING'}, "No camera found")
elif camera.name not in scene.objects:
self.report({'WARNING'}, "Active camera is not in this scene")
else:
if not self.extend:
bpy.ops.object.select_all(action='DESELECT')
view_layer.objects.active = camera
# camera.hide = False # XXX TODO where is this now?
camera.select_set(True)
return {'FINISHED'}
return {'CANCELLED'}
class SelectHierarchy(Operator):
"""Select object relative to the active object's position """ \
2018-06-26 19:41:37 +02:00
"""in the hierarchy"""
bl_idname = "object.select_hierarchy"
bl_label = "Select Hierarchy"
bl_options = {'REGISTER', 'UNDO'}
direction: EnumProperty(
2018-06-26 19:41:37 +02:00
items=(('PARENT', "Parent", ""),
('CHILD', "Child", ""),
),
name="Direction",
description="Direction to select in the hierarchy",
default='PARENT',
)
extend: BoolProperty(
2018-06-26 19:41:37 +02:00
name="Extend",
description="Extend the existing selection",
default=False,
)
@classmethod
def poll(cls, context):
return context.object
def execute(self, context):
view_layer = context.view_layer
select_new = []
act_new = None
2010-09-07 15:17:42 +00:00
selected_objects = context.selected_objects
obj_act = context.object
if context.object not in selected_objects:
selected_objects.append(context.object)
if self.direction == 'PARENT':
for obj in selected_objects:
parent = obj.parent
if parent:
if obj_act == obj:
act_new = parent
select_new.append(parent)
else:
for obj in selected_objects:
select_new.extend(obj.children)
if select_new:
select_new.sort(key=lambda obj_iter: obj_iter.name)
act_new = select_new[0]
2011-10-17 06:58:07 +00:00
# don't edit any object settings above this
if select_new:
if not self.extend:
bpy.ops.object.select_all(action='DESELECT')
for obj in select_new:
obj.select_set(True)
view_layer.objects.active = act_new
return {'FINISHED'}
2010-09-07 15:17:42 +00:00
return {'CANCELLED'}
class SubdivisionSet(Operator):
"""Sets a Subdivision Surface Level (1-5)"""
2009-11-26 15:36:23 +00:00
bl_idname = "object.subdivision_set"
bl_label = "Subdivision Set"
bl_options = {'REGISTER', 'UNDO'}
level: IntProperty(
2018-06-26 19:41:37 +02:00
name="Level",
min=-100, max=100,
soft_min=-6, soft_max=6,
default=1,
)
relative: BoolProperty(
2018-06-26 19:41:37 +02:00
name="Relative",
description=("Apply the subsurf level as an offset "
"relative to the current level"),
default=False,
)
2009-11-26 15:36:23 +00:00
@classmethod
def poll(cls, context):
obs = context.selected_editable_objects
return (obs is not None)
2009-11-26 15:36:23 +00:00
def execute(self, context):
level = self.level
relative = self.relative
if relative and level == 0:
2010-09-07 15:17:42 +00:00
return {'CANCELLED'} # nothing to do
if not relative and level < 0:
self.level = level = 0
def set_object_subd(obj):
for mod in obj.modifiers:
if mod.type == 'MULTIRES':
if not relative:
if level > mod.total_levels:
2014-07-22 12:03:15 +10:00
sub = level - mod.total_levels
2018-10-25 12:03:34 +11:00
for _ in range(sub):
2014-07-22 12:03:15 +10:00
bpy.ops.object.multires_subdivide(modifier="Multires")
if obj.mode == 'SCULPT':
if mod.sculpt_levels != level:
mod.sculpt_levels = level
elif obj.mode == 'OBJECT':
if mod.levels != level:
mod.levels = level
return
else:
if obj.mode == 'SCULPT':
2010-01-31 14:46:28 +00:00
if mod.sculpt_levels + level <= mod.total_levels:
mod.sculpt_levels += level
elif obj.mode == 'OBJECT':
2010-01-31 14:46:28 +00:00
if mod.levels + level <= mod.total_levels:
mod.levels += level
return
2010-01-31 14:46:28 +00:00
elif mod.type == 'SUBSURF':
if relative:
mod.levels += level
else:
if mod.levels != level:
mod.levels = level
return
# add a new modifier
try:
if obj.mode == 'SCULPT':
mod = obj.modifiers.new("Multires", 'MULTIRES')
if level > 0:
2018-10-25 12:03:34 +11:00
for _ in range(level):
bpy.ops.object.multires_subdivide(modifier="Multires")
else:
mod = obj.modifiers.new("Subdivision", 'SUBSURF')
mod.levels = level
except:
self.report({'WARNING'},
"Modifiers cannot be added to object: " + obj.name)
for obj in context.selected_editable_objects:
set_object_subd(obj)
return {'FINISHED'}
2009-11-26 15:36:23 +00:00
class ShapeTransfer(Operator):
"""Copy another selected objects active shape to this one by """ \
2018-06-26 19:41:37 +02:00
"""applying the relative offsets"""
bl_idname = "object.shape_key_transfer"
bl_label = "Transfer Shape Key"
bl_options = {'REGISTER', 'UNDO'}
mode: EnumProperty(
2018-06-26 19:41:37 +02:00
items=(('OFFSET',
"Offset",
"Apply the relative positional offset",
),
('RELATIVE_FACE',
"Relative Face",
"Calculate relative position (using faces)",
),
('RELATIVE_EDGE',
"Relative Edge",
"Calculate relative position (using edges)",
),
),
name="Transformation Mode",
description="Relative shape positions to the new shape method",
default='OFFSET',
)
use_clamp: BoolProperty(
2018-06-26 19:41:37 +02:00
name="Clamp Offset",
description=("Clamp the transformation to the distance each "
"vertex moves in the original shape"),
default=False,
)
def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
2010-01-31 14:46:28 +00:00
def me_nos(verts):
return [v.normal.copy() for v in verts]
def me_cos(verts):
return [v.co.copy() for v in verts]
def ob_add_shape(ob, name):
me = ob.data
key = ob.shape_key_add(from_mix=False)
if len(me.shape_keys.key_blocks) == 1:
key.name = "Basis"
key = ob.shape_key_add(from_mix=False) # we need a rest
key.name = name
ob.active_shape_key_index = len(me.shape_keys.key_blocks) - 1
ob.show_only_shape_key = True
from mathutils.geometry import barycentric_transform
from mathutils import Vector
if use_clamp and mode == 'OFFSET':
use_clamp = False
me = ob_act.data
orig_key_name = ob_act.active_shape_key.name
orig_shape_coords = me_cos(ob_act.active_shape_key.data)
2010-08-18 03:42:26 +00:00
orig_normals = me_nos(me.vertices)
# actual mesh vertex location isn't as reliable as the base shape :S
2018-06-26 19:41:37 +02:00
# orig_coords = me_cos(me.vertices)
orig_coords = me_cos(me.shape_keys.key_blocks[0].data)
for ob_other in objects:
if ob_other.type != 'MESH':
self.report({'WARNING'},
("Skipping '%s', "
"not a mesh") % ob_other.name)
continue
me_other = ob_other.data
2010-08-18 03:42:26 +00:00
if len(me_other.vertices) != len(me.vertices):
self.report({'WARNING'},
("Skipping '%s', "
"vertex count differs") % ob_other.name)
continue
2010-08-18 03:42:26 +00:00
target_normals = me_nos(me_other.vertices)
if me_other.shape_keys:
target_coords = me_cos(me_other.shape_keys.key_blocks[0].data)
else:
2010-08-18 03:42:26 +00:00
target_coords = me_cos(me_other.vertices)
ob_add_shape(ob_other, orig_key_name)
# editing the final coords, only list that stores wrapped coords
target_shape_coords = [v.co for v in
ob_other.active_shape_key.data]
2010-08-18 03:42:26 +00:00
median_coords = [[] for i in range(len(me.vertices))]
# Method 1, edge
if mode == 'OFFSET':
for i, vert_cos in enumerate(median_coords):
vert_cos.append(target_coords[i] +
(orig_shape_coords[i] - orig_coords[i]))
elif mode == 'RELATIVE_FACE':
for poly in me.polygons:
idxs = poly.vertices[:]
v_before = idxs[-2]
v = idxs[-1]
for v_after in idxs:
pt = barycentric_transform(orig_shape_coords[v],
orig_coords[v_before],
orig_coords[v],
orig_coords[v_after],
target_coords[v_before],
target_coords[v],
target_coords[v_after],
)
median_coords[v].append(pt)
v_before = v
v = v_after
elif mode == 'RELATIVE_EDGE':
for ed in me.edges:
2010-08-18 03:42:26 +00:00
i1, i2 = ed.vertices
v1, v2 = orig_coords[i1], orig_coords[i2]
edge_length = (v1 - v2).length
n1loc = v1 + orig_normals[i1] * edge_length
n2loc = v2 + orig_normals[i2] * edge_length
# now get the target nloc's
v1_to, v2_to = target_coords[i1], target_coords[i2]
edlen_to = (v1_to - v2_to).length
n1loc_to = v1_to + target_normals[i1] * edlen_to
n2loc_to = v2_to + target_normals[i2] * edlen_to
pt = barycentric_transform(orig_shape_coords[i1],
2012-10-08 08:28:05 +00:00
v2, v1, n1loc,
v2_to, v1_to, n1loc_to)
median_coords[i1].append(pt)
pt = barycentric_transform(orig_shape_coords[i2],
2012-10-08 08:28:05 +00:00
v1, v2, n2loc,
v1_to, v2_to, n2loc_to)
median_coords[i2].append(pt)
# apply the offsets to the new shape
from functools import reduce
VectorAdd = Vector.__add__
for i, vert_cos in enumerate(median_coords):
if vert_cos:
co = reduce(VectorAdd, vert_cos) / len(vert_cos)
if use_clamp:
# clamp to the same movement as the original
# breaks copy between different scaled meshes.
len_from = (orig_shape_coords[i] -
orig_coords[i]).length
ofs = co - target_coords[i]
ofs.length = len_from
co = target_coords[i] + ofs
target_shape_coords[i][:] = co
return {'FINISHED'}
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.mode != 'EDIT')
def execute(self, context):
ob_act = context.active_object
2012-02-04 11:10:41 +00:00
objects = [ob for ob in context.selected_editable_objects
if ob != ob_act]
if 1: # swap from/to, means we can't copy to many at once.
if len(objects) != 1:
self.report({'ERROR'},
("Expected one other selected "
"mesh object to copy from"))
return {'CANCELLED'}
ob_act, objects = objects[0], [ob_act]
if ob_act.type != 'MESH':
self.report({'ERROR'}, "Other object is not a mesh")
return {'CANCELLED'}
if ob_act.active_shape_key is None:
self.report({'ERROR'}, "Other object has no shape key")
return {'CANCELLED'}
return self._main(ob_act, objects, self.mode, self.use_clamp)
2010-03-06 01:40:29 +00:00
class JoinUVs(Operator):
"""Transfer UV Maps from active to selected objects """ \
2018-06-26 19:41:37 +02:00
"""(needs matching geometry)"""
bl_idname = "object.join_uvs"
bl_label = "Transfer UV Maps"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'MESH')
def _main(self, context):
import array
obj = context.active_object
mesh = obj.data
is_editmode = (obj.mode == 'EDIT')
if is_editmode:
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
if not mesh.uv_layers:
self.report({'WARNING'},
"Object: %s, Mesh: '%s' has no UVs"
% (obj.name, mesh.name))
else:
nbr_loops = len(mesh.loops)
# seems to be the fastest way to create an array
uv_array = array.array('f', [0.0] * 2) * nbr_loops
mesh.uv_layers.active.data.foreach_get("uv", uv_array)
objects = context.selected_editable_objects[:]
for obj_other in objects:
if obj_other.type == 'MESH':
obj_other.data.tag = False
for obj_other in objects:
if obj_other != obj and obj_other.type == 'MESH':
mesh_other = obj_other.data
if mesh_other != mesh:
2012-10-08 08:28:05 +00:00
if mesh_other.tag is False:
mesh_other.tag = True
if len(mesh_other.loops) != nbr_loops:
self.report({'WARNING'}, "Object: %s, Mesh: "
"'%s' has %d loops (for %d faces),"
" expected %d\n"
% (obj_other.name,
mesh_other.name,
len(mesh_other.loops),
len(mesh_other.polygons),
nbr_loops,
),
2012-10-08 08:28:05 +00:00
)
else:
uv_other = mesh_other.uv_layers.active
if not uv_other:
mesh_other.uv_layers.new()
uv_other = mesh_other.uv_layers.active
if not uv_other:
self.report({'ERROR'}, "Could not add "
"a new UV map tp object "
"'%s' (Mesh '%s')\n"
% (obj_other.name,
mesh_other.name,
),
)
2010-01-31 14:46:28 +00:00
# finally do the copy
uv_other.data.foreach_set("uv", uv_array)
if is_editmode:
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
def execute(self, context):
self._main(context)
return {'FINISHED'}
2010-03-06 01:40:29 +00:00
class MakeDupliFace(Operator):
2014-01-15 10:40:28 +01:00
"""Convert objects into dupli-face instanced"""
bl_idname = "object.make_dupli_face"
bl_label = "Make Dupli-Face"
bl_options = {'REGISTER', 'UNDO'}
2015-06-07 17:40:39 +10:00
@staticmethod
def _main(context):
from mathutils import Vector
SCALE_FAC = 0.01
offset = 0.5 * SCALE_FAC
base_tri = (Vector((-offset, -offset, 0.0)),
Vector((+offset, -offset, 0.0)),
Vector((+offset, +offset, 0.0)),
Vector((-offset, +offset, 0.0)),
)
def matrix_to_quad(matrix):
# scale = matrix.median_scale
2011-02-05 07:04:23 +00:00
trans = matrix.to_translation()
rot = matrix.to_3x3() # also contains scale
return [(rot @ b) + trans for b in base_tri]
scene = context.scene
linked = {}
for obj in context.selected_objects:
data = obj.data
if data:
linked.setdefault(data, []).append(obj)
for data, objects in linked.items():
face_verts = [axis for obj in objects
for v in matrix_to_quad(obj.matrix_world)
for axis in v]
nbr_verts = len(face_verts) // 3
nbr_faces = nbr_verts // 4
faces = list(range(nbr_verts))
mesh = bpy.data.meshes.new(data.name + "_dupli")
mesh.vertices.add(nbr_verts)
mesh.loops.add(nbr_faces * 4) # Safer than nbr_verts.
mesh.polygons.add(nbr_faces)
2010-08-18 03:42:26 +00:00
mesh.vertices.foreach_set("co", face_verts)
mesh.loops.foreach_set("vertex_index", faces)
mesh.polygons.foreach_set("loop_start", range(0, nbr_faces * 4, 4))
mesh.polygons.foreach_set("loop_total", (4,) * nbr_faces)
mesh.update() # generates edge data
# pick an object to use
obj = objects[0]
ob_new = bpy.data.objects.new(mesh.name, mesh)
base = scene.objects.link(ob_new)
base.layers[:] = obj.layers
ob_inst = bpy.data.objects.new(data.name, data)
base = scene.objects.link(ob_inst)
base.layers[:] = obj.layers
for obj in objects:
scene.objects.unlink(obj)
ob_new.instance_type = 'FACES'
ob_inst.parent = ob_new
ob_new.use_instance_faces_scale = True
ob_new.instance_faces_scale = 1.0 / SCALE_FAC
ob_inst.select_set(True)
ob_new.select_set(True)
2015-06-07 17:44:25 +10:00
def execute(self, context):
self._main(context)
return {'FINISHED'}
class IsolateTypeRender(Operator):
"""Hide unselected render objects of same type as active """ \
2018-06-26 19:41:37 +02:00
"""by setting the hide render flag"""
bl_idname = "object.isolate_type_render"
bl_label = "Restrict Render Unselected"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
act_type = context.object.type
2010-06-09 19:12:03 +00:00
for obj in context.visible_objects:
2010-06-09 19:12:03 +00:00
Render Layers and Collections (merge from render-layers) Design Documents ---------------- * https://wiki.blender.org/index.php/Dev:2.8/Source/Layers * https://wiki.blender.org/index.php/Dev:2.8/Source/DataDesignRevised User Commit Log --------------- * New Layer and Collection system to replace render layers and viewport layers. * A layer is a set of collections of objects (and their drawing options) required for specific tasks. * A collection is a set of objects, equivalent of the old layers in Blender. A collection can be shared across multiple layers. * All Scenes have a master collection that all other collections are children of. * New collection "context" tab (in Properties Editor) * New temporary viewport "collections" panel to control per-collection visibility Missing User Features --------------------- * Collection "Filter" Option to add objects based on their names * Collection Manager operators The existing buttons are placeholders * Collection Manager drawing The editor main region is empty * Collection Override * Per-Collection engine settings This will come as a separate commit, as part of the clay-engine branch Dev Commit Log -------------- * New DNA file (DNA_layer_types.h) with the new structs We are replacing Base by a new extended Base while keeping it backward compatible with some legacy settings (i.e., lay, flag_legacy). Renamed all Base to BaseLegacy to make it clear the areas of code that still need to be converted Note: manual changes were required on - deg_builder_nodes.h, rna_object.c, KX_Light.cpp * Unittesting for main syncronization requirements - read, write, add/copy/remove objects, copy scene, collection link/unlinking, context) * New Editor: Collection Manager Based on patch by Julian Eisel This is extracted from the layer-manager branch. With the following changes: - Renamed references of layer manager to collections manager - I doesn't include the editors/space_collections/ draw and util files - The drawing code itself will be implemented separately by Julian * Base / Object: A little note about them. Original Blender code would try to keep them in sync through the code, juggling flags back and forth. This will now be handled by Depsgraph, keeping Object and Bases more separated throughout the non-rendering code. Scene.base is being cleared in doversion, and the old viewport drawing code was poorly converted to use the new bases while the new viewport code doesn't get merged and replace the old one. Python API Changes ------------------ ``` - scene.layers + # no longer exists - scene.objects + scene.scene_layers.active.objects - scene.objects.active + scene.render_layers.active.objects.active - bpy.context.scene.objects.link() + bpy.context.scene_collection.objects.link() - bpy_extras.object_utils.object_data_add(context, obdata, operator=None, use_active_layer=True, name=None) + bpy_extras.object_utils.object_data_add(context, obdata, operator=None, name=None) - bpy.context.object.select + bpy.context.object.select = True + bpy.context.object.select = False + bpy.context.object.select_get() + bpy.context.object.select_set(action='SELECT') + bpy.context.object.select_set(action='DESELECT') -AddObjectHelper.layers + # no longer exists ```
2017-02-07 10:18:38 +01:00
if obj.select_get():
Apply first pass of edits to rna values from rna_booleans.txt. These are not animated and are best not change names like this too late in the release. ActionGroup.selected -> select: boolean Action Group is selected BezierSplinePoint.hidden -> hide: boolean Visibility status BezierSplinePoint.selected_control_point -> select_control_point: boolean Control point selection status BezierSplinePoint.selected_handle1 -> select_left_handle: boolean Handle 1 selection status BezierSplinePoint.selected_handle2 -> select_right_handle: boolean Handle 2 selection status Bone.restrict_select -> hide_select: boolean Bone is able to be selected Bone.selected -> select: boolean CurveMapPoint.selected -> select: boolean Selection state of the curve point EditBone.restrict_select -> hide_select: boolean Bone is able to be selected EditBone.selected -> select: boolean EditBone.selected_head -> select_head: boolean EditBone.selected_tail -> select_tail: boolean EditBone.locked -> lock: boolean Bone is not able to be transformed when in Edit Mode EditBone.hidden -> hide: boolean Bone is not visible when in Edit Mode NEGATE * FCurve.disabled -> enabled: boolean F-Curve could not be evaluated in past, so should be skipped when evaluating FCurve.locked -> lock: boolean F-Curve's settings cannot be edited FCurve.muted -> mute: boolean F-Curve is not evaluated FCurve.selected -> select: boolean F-Curve is selected for editing NEGATE * FCurve.visible -> hide: boolean F-Curve and its keyframes are shown in the Graph Editor graphs FCurveSample.selected -> select: boolean Selection status GPencilFrame.selected -> select: boolean Frame is selected for editing in the DopeSheet GPencilLayer.locked -> lock: boolean Protect layer from further editing and/or frame changes GPencilLayer.selected -> select: boolean Layer is selected for editing in the DopeSheet Keyframe.selected -> select: boolean Control point selection status Keyframe.selected_handle1 -> select_left_handle: boolean Handle 1 selection status Keyframe.selected_handle2 -> select_right_handle: boolean Handle 2 selection status MeshEdge.selected -> select: boolean MeshEdge.hidden -> hide: boolean MeshFace.hidden -> hide: boolean MeshFace.selected -> select: boolean MeshVertex.hidden -> hide: boolean MeshVertex.selected -> select: boolean MotionPathVert.selected -> select: boolean Path point is selected for editing NlaStrip.selected -> select: boolean NLA Strip is selected NlaTrack.locked -> lock: boolean NLA Track is locked NlaTrack.muted -> mute: boolean NLA Track is not evaluated NlaTrack.selected -> select: boolean NLA Track is selected Object.restrict_render -> hide_render: boolean Restrict renderability Object.restrict_select -> hide_select: boolean Restrict selection in the viewport Object.restrict_view -> hide: boolean Restrict visibility in the viewport Object.selected -> select: boolean Object selection state ObjectBase.selected -> select: boolean Object base selection state PoseBone.selected -> select: boolean Sequence.right_handle_selected -> select_right_handle: boolean Sequence.selected -> select: boolean SplinePoint.selected -> select_control_point: boolean Selection status TimelineMarker.selected -> select: boolean Marker selection state Sequence.left_handle_selected -> select_left_handle: boolean ActionGroup.locked -> lock: boolean Action Group is locked Bone.hidden -> hide: boolean Bone is not visible when it is not in Edit Mode (i.e. in Object or Pose Modes) SplinePoint.hidden -> hide: boolean Visibility status FModifier.muted -> mute: boolean F-Curve Modifier will not be evaluated note: rebaned uv_select to select_uv
2010-07-15 16:56:04 +00:00
obj.hide_render = False
else:
if obj.type == act_type:
Apply first pass of edits to rna values from rna_booleans.txt. These are not animated and are best not change names like this too late in the release. ActionGroup.selected -> select: boolean Action Group is selected BezierSplinePoint.hidden -> hide: boolean Visibility status BezierSplinePoint.selected_control_point -> select_control_point: boolean Control point selection status BezierSplinePoint.selected_handle1 -> select_left_handle: boolean Handle 1 selection status BezierSplinePoint.selected_handle2 -> select_right_handle: boolean Handle 2 selection status Bone.restrict_select -> hide_select: boolean Bone is able to be selected Bone.selected -> select: boolean CurveMapPoint.selected -> select: boolean Selection state of the curve point EditBone.restrict_select -> hide_select: boolean Bone is able to be selected EditBone.selected -> select: boolean EditBone.selected_head -> select_head: boolean EditBone.selected_tail -> select_tail: boolean EditBone.locked -> lock: boolean Bone is not able to be transformed when in Edit Mode EditBone.hidden -> hide: boolean Bone is not visible when in Edit Mode NEGATE * FCurve.disabled -> enabled: boolean F-Curve could not be evaluated in past, so should be skipped when evaluating FCurve.locked -> lock: boolean F-Curve's settings cannot be edited FCurve.muted -> mute: boolean F-Curve is not evaluated FCurve.selected -> select: boolean F-Curve is selected for editing NEGATE * FCurve.visible -> hide: boolean F-Curve and its keyframes are shown in the Graph Editor graphs FCurveSample.selected -> select: boolean Selection status GPencilFrame.selected -> select: boolean Frame is selected for editing in the DopeSheet GPencilLayer.locked -> lock: boolean Protect layer from further editing and/or frame changes GPencilLayer.selected -> select: boolean Layer is selected for editing in the DopeSheet Keyframe.selected -> select: boolean Control point selection status Keyframe.selected_handle1 -> select_left_handle: boolean Handle 1 selection status Keyframe.selected_handle2 -> select_right_handle: boolean Handle 2 selection status MeshEdge.selected -> select: boolean MeshEdge.hidden -> hide: boolean MeshFace.hidden -> hide: boolean MeshFace.selected -> select: boolean MeshVertex.hidden -> hide: boolean MeshVertex.selected -> select: boolean MotionPathVert.selected -> select: boolean Path point is selected for editing NlaStrip.selected -> select: boolean NLA Strip is selected NlaTrack.locked -> lock: boolean NLA Track is locked NlaTrack.muted -> mute: boolean NLA Track is not evaluated NlaTrack.selected -> select: boolean NLA Track is selected Object.restrict_render -> hide_render: boolean Restrict renderability Object.restrict_select -> hide_select: boolean Restrict selection in the viewport Object.restrict_view -> hide: boolean Restrict visibility in the viewport Object.selected -> select: boolean Object selection state ObjectBase.selected -> select: boolean Object base selection state PoseBone.selected -> select: boolean Sequence.right_handle_selected -> select_right_handle: boolean Sequence.selected -> select: boolean SplinePoint.selected -> select_control_point: boolean Selection status TimelineMarker.selected -> select: boolean Marker selection state Sequence.left_handle_selected -> select_left_handle: boolean ActionGroup.locked -> lock: boolean Action Group is locked Bone.hidden -> hide: boolean Bone is not visible when it is not in Edit Mode (i.e. in Object or Pose Modes) SplinePoint.hidden -> hide: boolean Visibility status FModifier.muted -> mute: boolean F-Curve Modifier will not be evaluated note: rebaned uv_select to select_uv
2010-07-15 16:56:04 +00:00
obj.hide_render = True
return {'FINISHED'}
2010-09-07 15:17:42 +00:00
class ClearAllRestrictRender(Operator):
"""Reveal all render objects by setting the hide render flag"""
bl_idname = "object.hide_render_clear_all"
bl_label = "Clear All Restrict Render"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
for obj in context.scene.objects:
2010-09-07 15:17:42 +00:00
obj.hide_render = False
return {'FINISHED'}
class TransformsToDeltas(Operator):
"""Convert normal object transforms to delta transforms, """ \
2018-06-26 19:41:37 +02:00
"""any existing delta transforms will be included as well"""
bl_idname = "object.transforms_to_deltas"
bl_label = "Transforms to Deltas"
bl_options = {'REGISTER', 'UNDO'}
mode: EnumProperty(
2018-06-26 19:41:37 +02:00
items=(
('ALL', "All Transforms", "Transfer location, rotation, and scale transforms"),
('LOC', "Location", "Transfer location transforms only"),
('ROT', "Rotation", "Transfer rotation transforms only"),
('SCALE', "Scale", "Transfer scale transforms only"),
),
name="Mode",
description="Which transforms to transfer",
default='ALL',
)
reset_values: BoolProperty(
2018-06-26 19:41:37 +02:00
name="Reset Values",
description=("Clear transform values after transferring to deltas"),
default=True,
)
@classmethod
def poll(cls, context):
obs = context.selected_editable_objects
return (obs is not None)
def execute(self, context):
for obj in context.selected_editable_objects:
if self.mode in {'ALL', 'LOC'}:
self.transfer_location(obj)
if self.mode in {'ALL', 'ROT'}:
self.transfer_rotation(obj)
if self.mode in {'ALL', 'SCALE'}:
self.transfer_scale(obj)
return {'FINISHED'}
def transfer_location(self, obj):
obj.delta_location += obj.location
if self.reset_values:
obj.location.zero()
def transfer_rotation(self, obj):
# TODO: add transforms together...
if obj.rotation_mode == 'QUATERNION':
obj.delta_rotation_quaternion += obj.rotation_quaternion
if self.reset_values:
obj.rotation_quaternion.identity()
elif obj.rotation_mode == 'AXIS_ANGLE':
pass # Unsupported
else:
delta = obj.delta_rotation_euler.copy()
obj.delta_rotation_euler = obj.rotation_euler
obj.delta_rotation_euler.rotate(delta)
if self.reset_values:
obj.rotation_euler.zero()
def transfer_scale(self, obj):
obj.delta_scale[0] *= obj.scale[0]
obj.delta_scale[1] *= obj.scale[1]
obj.delta_scale[2] *= obj.scale[2]
if self.reset_values:
obj.scale[:] = (1, 1, 1)
class TransformsToDeltasAnim(Operator):
"""Convert object animation for normal transforms to delta transforms"""
bl_idname = "object.anim_transforms_to_deltas"
bl_label = "Animated Transforms to Deltas"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
obs = context.selected_editable_objects
return (obs is not None)
def execute(self, context):
# map from standard transform paths to "new" transform paths
STANDARD_TO_DELTA_PATHS = {
2018-06-26 19:41:37 +02:00
"location": "delta_location",
"rotation_euler": "delta_rotation_euler",
"rotation_quaternion": "delta_rotation_quaternion",
# "rotation_axis_angle" : "delta_rotation_axis_angle",
"scale": "delta_scale"
}
DELTA_PATHS = STANDARD_TO_DELTA_PATHS.values()
2013-01-15 23:15:32 +00:00
# try to apply on each selected object
for obj in context.selected_editable_objects:
adt = obj.animation_data
if (adt is None) or (adt.action is None):
self.report({'WARNING'},
"No animation data to convert on object: %r" %
obj.name)
continue
2013-01-15 23:15:32 +00:00
# first pass over F-Curves: ensure that we don't have conflicting
# transforms already (e.g. if this was applied already) [#29110]
existingFCurves = {}
for fcu in adt.action.fcurves:
# get "delta" path - i.e. the final paths which may clash
path = fcu.data_path
if path in STANDARD_TO_DELTA_PATHS:
# to be converted - conflicts may exist...
dpath = STANDARD_TO_DELTA_PATHS[path]
elif path in DELTA_PATHS:
# already delta - check for conflicts...
dpath = path
else:
# non-transform - ignore
continue
2013-01-15 23:15:32 +00:00
# a delta path like this for the same index shouldn't
# exist already, otherwise we've got a conflict
if dpath in existingFCurves:
# ensure that this index hasn't occurred before
if fcu.array_index in existingFCurves[dpath]:
# conflict
self.report({'ERROR'},
2013-01-15 23:15:32 +00:00
"Object '%r' already has '%r' F-Curve(s). "
"Remove these before trying again" %
(obj.name, dpath))
return {'CANCELLED'}
else:
# no conflict here
existingFCurves[dpath] += [fcu.array_index]
else:
# no conflict yet
existingFCurves[dpath] = [fcu.array_index]
2013-01-15 23:15:32 +00:00
# if F-Curve uses standard transform path
# just append "delta_" to this path
for fcu in adt.action.fcurves:
if fcu.data_path == "location":
fcu.data_path = "delta_location"
obj.location.zero()
elif fcu.data_path == "rotation_euler":
fcu.data_path = "delta_rotation_euler"
obj.rotation_euler.zero()
elif fcu.data_path == "rotation_quaternion":
fcu.data_path = "delta_rotation_quaternion"
obj.rotation_quaternion.identity()
# XXX: currently not implemented
2018-06-26 19:41:37 +02:00
# ~ elif fcu.data_path == "rotation_axis_angle":
# ~ fcu.data_path = "delta_rotation_axis_angle"
elif fcu.data_path == "scale":
fcu.data_path = "delta_scale"
obj.scale = 1.0, 1.0, 1.0
# hack: force animsys flush by changing frame, so that deltas get run
context.scene.frame_set(context.scene.frame_current)
return {'FINISHED'}
class DupliOffsetFromCursor(Operator):
"""Set offset used for collection instances based on cursor position"""
bl_idname = "object.instance_offset_from_cursor"
bl_label = "Set Offset From Cursor"
bl_options = {'INTERNAL', 'UNDO'}
@classmethod
def poll(cls, context):
2013-01-15 23:15:32 +00:00
return (context.active_object is not None)
def execute(self, context):
scene = context.scene
collection = context.collection
collection.instance_offset = scene.cursor_location
return {'FINISHED'}
class LoadImageAsEmpty:
bl_options = {'REGISTER', 'UNDO'}
filepath: StringProperty(
subtype='FILE_PATH'
)
filter_image: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
filter_folder: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
view_align: BoolProperty(
name="Align to view",
default=True
)
@classmethod
def poll(cls, context):
return context.mode == "OBJECT"
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def execute(self, context):
scene = context.scene
space = context.space_data
cursor = scene.cursor_location
try:
image = bpy.data.images.load(self.filepath, check_existing=True)
except RuntimeError as ex:
self.report({"ERROR"}, str(ex))
return {"CANCELLED"}
bpy.ops.object.empty_add(
'INVOKE_REGION_WIN',
type='IMAGE',
location=cursor,
view_align=self.view_align,
)
obj = context.active_object
obj.data = image
obj.empty_display_size = 5.0
self.set_settings(context, obj)
return {'FINISHED'}
def set_settings(self, context, obj):
pass
class LoadBackgroundImage(LoadImageAsEmpty, Operator):
"""Add a reference image into the background behind objects"""
bl_idname = "object.load_background_image"
bl_label = "Load Background Image"
def set_settings(self, context, obj):
obj.empty_image_depth = 'BACK'
obj.empty_image_side = 'FRONT'
if context.space_data.type == 'VIEW_3D':
if not context.space_data.region_3d.is_perspective:
obj.show_empty_image_perspective = False
class LoadReferenceImage(LoadImageAsEmpty, Operator):
"""Add a reference image into the scene between objects"""
bl_idname = "object.load_reference_image"
bl_label = "Load Reference Image"
def set_settings(self, context, obj):
pass
class OBJECT_OT_assign_property_defaults(Operator):
"""Assign the current values of custom properties as their defaults, for use as part of the rest pose state in NLA track mixing"""
bl_idname = "object.assign_property_defaults"
bl_label = "Assign Custom Property Values as Default"
bl_options = {'UNDO', 'REGISTER'}
process_data: BoolProperty(name="Process data properties", default=True)
process_bones: BoolProperty(name="Process bone properties", default=True)
@classmethod
def poll(cls, context):
obj = context.active_object
return obj is not None and obj.library is None and obj.mode in {'POSE', 'OBJECT'}
@staticmethod
def assign_defaults(obj):
from rna_prop_ui import rna_idprop_ui_prop_default_set
rna_properties = {'_RNA_UI'} | {prop.identifier for prop in obj.bl_rna.properties if prop.is_runtime}
for prop, value in obj.items():
if prop not in rna_properties:
rna_idprop_ui_prop_default_set(obj, prop, value)
def execute(self, context):
obj = context.active_object
self.assign_defaults(obj)
if self.process_bones and obj.pose:
for pbone in obj.pose.bones:
self.assign_defaults(pbone)
if self.process_data and obj.data and obj.data.library is None:
self.assign_defaults(obj.data)
if self.process_bones and isinstance(obj.data, bpy.types.Armature):
for bone in obj.data.bones:
self.assign_defaults(bone)
return {'FINISHED'}
classes = (
ClearAllRestrictRender,
DupliOffsetFromCursor,
IsolateTypeRender,
JoinUVs,
LoadBackgroundImage,
LoadReferenceImage,
MakeDupliFace,
SelectCamera,
SelectHierarchy,
SelectPattern,
ShapeTransfer,
SubdivisionSet,
TransformsToDeltas,
TransformsToDeltasAnim,
OBJECT_OT_assign_property_defaults,
)