Cleanup: line length
[blender.git] / release / scripts / startup / bl_operators / object.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8-80 compliant>
20
21 import bpy
22 from bpy.types import Operator
23 from bpy.props import (
24     BoolProperty,
25     EnumProperty,
26     IntProperty,
27     StringProperty,
28 )
29
30
31 class SelectPattern(Operator):
32     """Select objects matching a naming pattern"""
33     bl_idname = "object.select_pattern"
34     bl_label = "Select Pattern"
35     bl_options = {'REGISTER', 'UNDO'}
36
37     pattern: StringProperty(
38         name="Pattern",
39         description="Name filter using '*', '?' and "
40         "'[abc]' unix style wildcards",
41         maxlen=64,
42         default="*",
43     )
44     case_sensitive: BoolProperty(
45         name="Case Sensitive",
46         description="Do a case sensitive compare",
47         default=False,
48     )
49     extend: BoolProperty(
50         name="Extend",
51         description="Extend the existing selection",
52         default=True,
53     )
54
55     def execute(self, context):
56
57         import fnmatch
58
59         if self.case_sensitive:
60             pattern_match = fnmatch.fnmatchcase
61         else:
62             pattern_match = (lambda a, b:
63                              fnmatch.fnmatchcase(a.upper(), b.upper()))
64         is_ebone = False
65         is_pbone = False
66         obj = context.object
67         if obj and obj.mode == 'POSE':
68             items = obj.data.bones
69             if not self.extend:
70                 bpy.ops.pose.select_all(action='DESELECT')
71             is_pbone = True
72         elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT':
73             items = obj.data.edit_bones
74             if not self.extend:
75                 bpy.ops.armature.select_all(action='DESELECT')
76             is_ebone = True
77         else:
78             items = context.visible_objects
79             if not self.extend:
80                 bpy.ops.object.select_all(action='DESELECT')
81
82         # Can be pose bones, edit bones or objects
83         for item in items:
84             if pattern_match(item.name, self.pattern):
85
86                 # hrmf, perhaps there should be a utility function for this.
87                 if is_ebone:
88                     item.select = True
89                     item.select_head = True
90                     item.select_tail = True
91                     if item.use_connect:
92                         item_parent = item.parent
93                         if item_parent is not None:
94                             item_parent.select_tail = True
95                 elif is_pbone:
96                     item.select = True
97                 else:
98                     item.select_set(True)
99
100         return {'FINISHED'}
101
102     def invoke(self, context, event):
103         wm = context.window_manager
104         return wm.invoke_props_popup(self, event)
105
106     def draw(self, context):
107         layout = self.layout
108
109         layout.prop(self, "pattern")
110         row = layout.row()
111         row.prop(self, "case_sensitive")
112         row.prop(self, "extend")
113
114     @classmethod
115     def poll(cls, context):
116         obj = context.object
117         return (not obj) or (obj.mode == 'OBJECT') or (obj.type == 'ARMATURE')
118
119
120 class SelectCamera(Operator):
121     """Select the active camera"""
122     bl_idname = "object.select_camera"
123     bl_label = "Select Camera"
124     bl_options = {'REGISTER', 'UNDO'}
125
126     extend: BoolProperty(
127         name="Extend",
128         description="Extend the selection",
129         default=False
130     )
131
132     def execute(self, context):
133         scene = context.scene
134         view_layer = context.view_layer
135         view = context.space_data
136         if view.type == 'VIEW_3D' and view.use_local_camera:
137             camera = view.camera
138         else:
139             camera = scene.camera
140
141         if camera is None:
142             self.report({'WARNING'}, "No camera found")
143         elif camera.name not in scene.objects:
144             self.report({'WARNING'}, "Active camera is not in this scene")
145         else:
146             if not self.extend:
147                 bpy.ops.object.select_all(action='DESELECT')
148             view_layer.objects.active = camera
149             # camera.hide = False  # XXX TODO where is this now?
150             camera.select_set(True)
151             return {'FINISHED'}
152
153         return {'CANCELLED'}
154
155
156 class SelectHierarchy(Operator):
157     """Select object relative to the active object's position """ \
158         """in the hierarchy"""
159     bl_idname = "object.select_hierarchy"
160     bl_label = "Select Hierarchy"
161     bl_options = {'REGISTER', 'UNDO'}
162
163     direction: EnumProperty(
164         items=(('PARENT', "Parent", ""),
165                ('CHILD', "Child", ""),
166                ),
167         name="Direction",
168         description="Direction to select in the hierarchy",
169         default='PARENT',
170     )
171     extend: BoolProperty(
172         name="Extend",
173         description="Extend the existing selection",
174         default=False,
175     )
176
177     @classmethod
178     def poll(cls, context):
179         return context.object
180
181     def execute(self, context):
182         view_layer = context.view_layer
183         select_new = []
184         act_new = None
185
186         selected_objects = context.selected_objects
187         obj_act = context.object
188
189         if context.object not in selected_objects:
190             selected_objects.append(context.object)
191
192         if self.direction == 'PARENT':
193             for obj in selected_objects:
194                 parent = obj.parent
195
196                 if parent:
197                     if obj_act == obj:
198                         act_new = parent
199
200                     select_new.append(parent)
201
202         else:
203             for obj in selected_objects:
204                 select_new.extend(obj.children)
205
206             if select_new:
207                 select_new.sort(key=lambda obj_iter: obj_iter.name)
208                 act_new = select_new[0]
209
210         # don't edit any object settings above this
211         if select_new:
212             if not self.extend:
213                 bpy.ops.object.select_all(action='DESELECT')
214
215             for obj in select_new:
216                 obj.select_set(True)
217
218             view_layer.objects.active = act_new
219             return {'FINISHED'}
220
221         return {'CANCELLED'}
222
223
224 class SubdivisionSet(Operator):
225     """Sets a Subdivision Surface Level (1-5)"""
226
227     bl_idname = "object.subdivision_set"
228     bl_label = "Subdivision Set"
229     bl_options = {'REGISTER', 'UNDO'}
230
231     level: IntProperty(
232         name="Level",
233         min=-100, max=100,
234         soft_min=-6, soft_max=6,
235         default=1,
236     )
237     relative: BoolProperty(
238         name="Relative",
239         description=("Apply the subsurf level as an offset "
240                      "relative to the current level"),
241         default=False,
242     )
243
244     @classmethod
245     def poll(cls, context):
246         obs = context.selected_editable_objects
247         return (obs is not None)
248
249     def execute(self, context):
250         level = self.level
251         relative = self.relative
252
253         if relative and level == 0:
254             return {'CANCELLED'}  # nothing to do
255
256         if not relative and level < 0:
257             self.level = level = 0
258
259         def set_object_subd(obj):
260             for mod in obj.modifiers:
261                 if mod.type == 'MULTIRES':
262                     if not relative:
263                         if level > mod.total_levels:
264                             sub = level - mod.total_levels
265                             for _ in range(sub):
266                                 bpy.ops.object.multires_subdivide(modifier="Multires")
267
268                         if obj.mode == 'SCULPT':
269                             if mod.sculpt_levels != level:
270                                 mod.sculpt_levels = level
271                         elif obj.mode == 'OBJECT':
272                             if mod.levels != level:
273                                 mod.levels = level
274                         return
275                     else:
276                         if obj.mode == 'SCULPT':
277                             if mod.sculpt_levels + level <= mod.total_levels:
278                                 mod.sculpt_levels += level
279                         elif obj.mode == 'OBJECT':
280                             if mod.levels + level <= mod.total_levels:
281                                 mod.levels += level
282                         return
283
284                 elif mod.type == 'SUBSURF':
285                     if relative:
286                         mod.levels += level
287                     else:
288                         if mod.levels != level:
289                             mod.levels = level
290
291                     return
292
293             # add a new modifier
294             try:
295                 if obj.mode == 'SCULPT':
296                     mod = obj.modifiers.new("Multires", 'MULTIRES')
297                     if level > 0:
298                         for _ in range(level):
299                             bpy.ops.object.multires_subdivide(modifier="Multires")
300                 else:
301                     mod = obj.modifiers.new("Subdivision", 'SUBSURF')
302                     mod.levels = level
303             except:
304                 self.report({'WARNING'},
305                             "Modifiers cannot be added to object: " + obj.name)
306
307         for obj in context.selected_editable_objects:
308             set_object_subd(obj)
309
310         return {'FINISHED'}
311
312
313 class ShapeTransfer(Operator):
314     """Copy another selected objects active shape to this one by """ \
315         """applying the relative offsets"""
316
317     bl_idname = "object.shape_key_transfer"
318     bl_label = "Transfer Shape Key"
319     bl_options = {'REGISTER', 'UNDO'}
320
321     mode: EnumProperty(
322         items=(('OFFSET',
323                 "Offset",
324                 "Apply the relative positional offset",
325                 ),
326                ('RELATIVE_FACE',
327                 "Relative Face",
328                 "Calculate relative position (using faces)",
329                 ),
330                ('RELATIVE_EDGE',
331                 "Relative Edge",
332                 "Calculate relative position (using edges)",
333                 ),
334                ),
335         name="Transformation Mode",
336         description="Relative shape positions to the new shape method",
337         default='OFFSET',
338     )
339     use_clamp: BoolProperty(
340         name="Clamp Offset",
341         description=("Clamp the transformation to the distance each "
342                      "vertex moves in the original shape"),
343         default=False,
344     )
345
346     def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
347
348         def me_nos(verts):
349             return [v.normal.copy() for v in verts]
350
351         def me_cos(verts):
352             return [v.co.copy() for v in verts]
353
354         def ob_add_shape(ob, name):
355             me = ob.data
356             key = ob.shape_key_add(from_mix=False)
357             if len(me.shape_keys.key_blocks) == 1:
358                 key.name = "Basis"
359                 key = ob.shape_key_add(from_mix=False)  # we need a rest
360             key.name = name
361             ob.active_shape_key_index = len(me.shape_keys.key_blocks) - 1
362             ob.show_only_shape_key = True
363
364         from mathutils.geometry import barycentric_transform
365         from mathutils import Vector
366
367         if use_clamp and mode == 'OFFSET':
368             use_clamp = False
369
370         me = ob_act.data
371         orig_key_name = ob_act.active_shape_key.name
372
373         orig_shape_coords = me_cos(ob_act.active_shape_key.data)
374
375         orig_normals = me_nos(me.vertices)
376         # actual mesh vertex location isn't as reliable as the base shape :S
377         # orig_coords = me_cos(me.vertices)
378         orig_coords = me_cos(me.shape_keys.key_blocks[0].data)
379
380         for ob_other in objects:
381             if ob_other.type != 'MESH':
382                 self.report({'WARNING'},
383                             ("Skipping '%s', "
384                              "not a mesh") % ob_other.name)
385                 continue
386             me_other = ob_other.data
387             if len(me_other.vertices) != len(me.vertices):
388                 self.report({'WARNING'},
389                             ("Skipping '%s', "
390                              "vertex count differs") % ob_other.name)
391                 continue
392
393             target_normals = me_nos(me_other.vertices)
394             if me_other.shape_keys:
395                 target_coords = me_cos(me_other.shape_keys.key_blocks[0].data)
396             else:
397                 target_coords = me_cos(me_other.vertices)
398
399             ob_add_shape(ob_other, orig_key_name)
400
401             # editing the final coords, only list that stores wrapped coords
402             target_shape_coords = [v.co for v in
403                                    ob_other.active_shape_key.data]
404
405             median_coords = [[] for i in range(len(me.vertices))]
406
407             # Method 1, edge
408             if mode == 'OFFSET':
409                 for i, vert_cos in enumerate(median_coords):
410                     vert_cos.append(target_coords[i] +
411                                     (orig_shape_coords[i] - orig_coords[i]))
412
413             elif mode == 'RELATIVE_FACE':
414                 for poly in me.polygons:
415                     idxs = poly.vertices[:]
416                     v_before = idxs[-2]
417                     v = idxs[-1]
418                     for v_after in idxs:
419                         pt = barycentric_transform(orig_shape_coords[v],
420                                                    orig_coords[v_before],
421                                                    orig_coords[v],
422                                                    orig_coords[v_after],
423                                                    target_coords[v_before],
424                                                    target_coords[v],
425                                                    target_coords[v_after],
426                                                    )
427                         median_coords[v].append(pt)
428                         v_before = v
429                         v = v_after
430
431             elif mode == 'RELATIVE_EDGE':
432                 for ed in me.edges:
433                     i1, i2 = ed.vertices
434                     v1, v2 = orig_coords[i1], orig_coords[i2]
435                     edge_length = (v1 - v2).length
436                     n1loc = v1 + orig_normals[i1] * edge_length
437                     n2loc = v2 + orig_normals[i2] * edge_length
438
439                     # now get the target nloc's
440                     v1_to, v2_to = target_coords[i1], target_coords[i2]
441                     edlen_to = (v1_to - v2_to).length
442                     n1loc_to = v1_to + target_normals[i1] * edlen_to
443                     n2loc_to = v2_to + target_normals[i2] * edlen_to
444
445                     pt = barycentric_transform(orig_shape_coords[i1],
446                                                v2, v1, n1loc,
447                                                v2_to, v1_to, n1loc_to)
448                     median_coords[i1].append(pt)
449
450                     pt = barycentric_transform(orig_shape_coords[i2],
451                                                v1, v2, n2loc,
452                                                v1_to, v2_to, n2loc_to)
453                     median_coords[i2].append(pt)
454
455             # apply the offsets to the new shape
456             from functools import reduce
457             VectorAdd = Vector.__add__
458
459             for i, vert_cos in enumerate(median_coords):
460                 if vert_cos:
461                     co = reduce(VectorAdd, vert_cos) / len(vert_cos)
462
463                     if use_clamp:
464                         # clamp to the same movement as the original
465                         # breaks copy between different scaled meshes.
466                         len_from = (orig_shape_coords[i] -
467                                     orig_coords[i]).length
468                         ofs = co - target_coords[i]
469                         ofs.length = len_from
470                         co = target_coords[i] + ofs
471
472                     target_shape_coords[i][:] = co
473
474         return {'FINISHED'}
475
476     @classmethod
477     def poll(cls, context):
478         obj = context.active_object
479         return (obj and obj.mode != 'EDIT')
480
481     def execute(self, context):
482         ob_act = context.active_object
483         objects = [ob for ob in context.selected_editable_objects
484                    if ob != ob_act]
485
486         if 1:  # swap from/to, means we can't copy to many at once.
487             if len(objects) != 1:
488                 self.report({'ERROR'},
489                             ("Expected one other selected "
490                              "mesh object to copy from"))
491
492                 return {'CANCELLED'}
493             ob_act, objects = objects[0], [ob_act]
494
495         if ob_act.type != 'MESH':
496             self.report({'ERROR'}, "Other object is not a mesh")
497             return {'CANCELLED'}
498
499         if ob_act.active_shape_key is None:
500             self.report({'ERROR'}, "Other object has no shape key")
501             return {'CANCELLED'}
502         return self._main(ob_act, objects, self.mode, self.use_clamp)
503
504
505 class JoinUVs(Operator):
506     """Transfer UV Maps from active to selected objects """ \
507         """(needs matching geometry)"""
508     bl_idname = "object.join_uvs"
509     bl_label = "Transfer UV Maps"
510     bl_options = {'REGISTER', 'UNDO'}
511
512     @classmethod
513     def poll(cls, context):
514         obj = context.active_object
515         return (obj and obj.type == 'MESH')
516
517     def _main(self, context):
518         import array
519         obj = context.active_object
520         mesh = obj.data
521
522         is_editmode = (obj.mode == 'EDIT')
523         if is_editmode:
524             bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
525
526         if not mesh.uv_layers:
527             self.report({'WARNING'},
528                         "Object: %s, Mesh: '%s' has no UVs"
529                         % (obj.name, mesh.name))
530         else:
531             nbr_loops = len(mesh.loops)
532
533             # seems to be the fastest way to create an array
534             uv_array = array.array('f', [0.0] * 2) * nbr_loops
535             mesh.uv_layers.active.data.foreach_get("uv", uv_array)
536
537             objects = context.selected_editable_objects[:]
538
539             for obj_other in objects:
540                 if obj_other.type == 'MESH':
541                     obj_other.data.tag = False
542
543             for obj_other in objects:
544                 if obj_other != obj and obj_other.type == 'MESH':
545                     mesh_other = obj_other.data
546                     if mesh_other != mesh:
547                         if mesh_other.tag is False:
548                             mesh_other.tag = True
549
550                             if len(mesh_other.loops) != nbr_loops:
551                                 self.report({'WARNING'}, "Object: %s, Mesh: "
552                                             "'%s' has %d loops (for %d faces),"
553                                             " expected %d\n"
554                                             % (obj_other.name,
555                                                mesh_other.name,
556                                                len(mesh_other.loops),
557                                                len(mesh_other.polygons),
558                                                nbr_loops,
559                                                ),
560                                             )
561                             else:
562                                 uv_other = mesh_other.uv_layers.active
563                                 if not uv_other:
564                                     mesh_other.uv_layers.new()
565                                     uv_other = mesh_other.uv_layers.active
566                                     if not uv_other:
567                                         self.report({'ERROR'}, "Could not add "
568                                                     "a new UV map tp object "
569                                                     "'%s' (Mesh '%s')\n"
570                                                     % (obj_other.name,
571                                                        mesh_other.name,
572                                                        ),
573                                                     )
574
575                                 # finally do the copy
576                                 uv_other.data.foreach_set("uv", uv_array)
577
578         if is_editmode:
579             bpy.ops.object.mode_set(mode='EDIT', toggle=False)
580
581     def execute(self, context):
582         self._main(context)
583         return {'FINISHED'}
584
585
586 class MakeDupliFace(Operator):
587     """Convert objects into dupli-face instanced"""
588     bl_idname = "object.make_dupli_face"
589     bl_label = "Make Dupli-Face"
590     bl_options = {'REGISTER', 'UNDO'}
591
592     @staticmethod
593     def _main(context):
594         from mathutils import Vector
595
596         SCALE_FAC = 0.01
597         offset = 0.5 * SCALE_FAC
598         base_tri = (Vector((-offset, -offset, 0.0)),
599                     Vector((+offset, -offset, 0.0)),
600                     Vector((+offset, +offset, 0.0)),
601                     Vector((-offset, +offset, 0.0)),
602                     )
603
604         def matrix_to_quad(matrix):
605             # scale = matrix.median_scale
606             trans = matrix.to_translation()
607             rot = matrix.to_3x3()  # also contains scale
608
609             return [(rot @ b) + trans for b in base_tri]
610         scene = context.scene
611         linked = {}
612         for obj in context.selected_objects:
613             data = obj.data
614             if data:
615                 linked.setdefault(data, []).append(obj)
616
617         for data, objects in linked.items():
618             face_verts = [axis for obj in objects
619                           for v in matrix_to_quad(obj.matrix_world)
620                           for axis in v]
621             nbr_verts = len(face_verts) // 3
622             nbr_faces = nbr_verts // 4
623
624             faces = list(range(nbr_verts))
625
626             mesh = bpy.data.meshes.new(data.name + "_dupli")
627
628             mesh.vertices.add(nbr_verts)
629             mesh.loops.add(nbr_faces * 4)  # Safer than nbr_verts.
630             mesh.polygons.add(nbr_faces)
631
632             mesh.vertices.foreach_set("co", face_verts)
633             mesh.loops.foreach_set("vertex_index", faces)
634             mesh.polygons.foreach_set("loop_start", range(0, nbr_faces * 4, 4))
635             mesh.polygons.foreach_set("loop_total", (4,) * nbr_faces)
636             mesh.update()  # generates edge data
637
638             # pick an object to use
639             obj = objects[0]
640
641             ob_new = bpy.data.objects.new(mesh.name, mesh)
642             base = scene.objects.link(ob_new)
643             base.layers[:] = obj.layers
644
645             ob_inst = bpy.data.objects.new(data.name, data)
646             base = scene.objects.link(ob_inst)
647             base.layers[:] = obj.layers
648
649             for obj in objects:
650                 scene.objects.unlink(obj)
651
652             ob_new.instance_type = 'FACES'
653             ob_inst.parent = ob_new
654             ob_new.use_instance_faces_scale = True
655             ob_new.instance_faces_scale = 1.0 / SCALE_FAC
656
657             ob_inst.select_set(True)
658             ob_new.select_set(True)
659
660     def execute(self, context):
661         self._main(context)
662         return {'FINISHED'}
663
664
665 class IsolateTypeRender(Operator):
666     """Hide unselected render objects of same type as active """ \
667         """by setting the hide render flag"""
668     bl_idname = "object.isolate_type_render"
669     bl_label = "Restrict Render Unselected"
670     bl_options = {'REGISTER', 'UNDO'}
671
672     def execute(self, context):
673         act_type = context.object.type
674
675         for obj in context.visible_objects:
676
677             if obj.select_get():
678                 obj.hide_render = False
679             else:
680                 if obj.type == act_type:
681                     obj.hide_render = True
682
683         return {'FINISHED'}
684
685
686 class ClearAllRestrictRender(Operator):
687     """Reveal all render objects by setting the hide render flag"""
688     bl_idname = "object.hide_render_clear_all"
689     bl_label = "Clear All Restrict Render"
690     bl_options = {'REGISTER', 'UNDO'}
691
692     def execute(self, context):
693         for obj in context.scene.objects:
694             obj.hide_render = False
695         return {'FINISHED'}
696
697
698 class TransformsToDeltas(Operator):
699     """Convert normal object transforms to delta transforms, """ \
700         """any existing delta transforms will be included as well"""
701     bl_idname = "object.transforms_to_deltas"
702     bl_label = "Transforms to Deltas"
703     bl_options = {'REGISTER', 'UNDO'}
704
705     mode: EnumProperty(
706         items=(
707             ('ALL', "All Transforms", "Transfer location, rotation, and scale transforms"),
708             ('LOC', "Location", "Transfer location transforms only"),
709             ('ROT', "Rotation", "Transfer rotation transforms only"),
710             ('SCALE', "Scale", "Transfer scale transforms only"),
711         ),
712         name="Mode",
713         description="Which transforms to transfer",
714         default='ALL',
715     )
716     reset_values: BoolProperty(
717         name="Reset Values",
718         description=("Clear transform values after transferring to deltas"),
719         default=True,
720     )
721
722     @classmethod
723     def poll(cls, context):
724         obs = context.selected_editable_objects
725         return (obs is not None)
726
727     def execute(self, context):
728         for obj in context.selected_editable_objects:
729             if self.mode in {'ALL', 'LOC'}:
730                 self.transfer_location(obj)
731
732             if self.mode in {'ALL', 'ROT'}:
733                 self.transfer_rotation(obj)
734
735             if self.mode in {'ALL', 'SCALE'}:
736                 self.transfer_scale(obj)
737
738         return {'FINISHED'}
739
740     def transfer_location(self, obj):
741         obj.delta_location += obj.location
742
743         if self.reset_values:
744             obj.location.zero()
745
746     def transfer_rotation(self, obj):
747         # TODO: add transforms together...
748         if obj.rotation_mode == 'QUATERNION':
749             obj.delta_rotation_quaternion += obj.rotation_quaternion
750
751             if self.reset_values:
752                 obj.rotation_quaternion.identity()
753         elif obj.rotation_mode == 'AXIS_ANGLE':
754             pass  # Unsupported
755         else:
756             delta = obj.delta_rotation_euler.copy()
757             obj.delta_rotation_euler = obj.rotation_euler
758             obj.delta_rotation_euler.rotate(delta)
759
760             if self.reset_values:
761                 obj.rotation_euler.zero()
762
763     def transfer_scale(self, obj):
764         obj.delta_scale[0] *= obj.scale[0]
765         obj.delta_scale[1] *= obj.scale[1]
766         obj.delta_scale[2] *= obj.scale[2]
767
768         if self.reset_values:
769             obj.scale[:] = (1, 1, 1)
770
771
772 class TransformsToDeltasAnim(Operator):
773     """Convert object animation for normal transforms to delta transforms"""
774     bl_idname = "object.anim_transforms_to_deltas"
775     bl_label = "Animated Transforms to Deltas"
776     bl_options = {'REGISTER', 'UNDO'}
777
778     @classmethod
779     def poll(cls, context):
780         obs = context.selected_editable_objects
781         return (obs is not None)
782
783     def execute(self, context):
784         # map from standard transform paths to "new" transform paths
785         STANDARD_TO_DELTA_PATHS = {
786             "location": "delta_location",
787             "rotation_euler": "delta_rotation_euler",
788             "rotation_quaternion": "delta_rotation_quaternion",
789             # "rotation_axis_angle" : "delta_rotation_axis_angle",
790             "scale": "delta_scale"
791         }
792         DELTA_PATHS = STANDARD_TO_DELTA_PATHS.values()
793
794         # try to apply on each selected object
795         for obj in context.selected_editable_objects:
796             adt = obj.animation_data
797             if (adt is None) or (adt.action is None):
798                 self.report({'WARNING'},
799                             "No animation data to convert on object: %r" %
800                             obj.name)
801                 continue
802
803             # first pass over F-Curves: ensure that we don't have conflicting
804             # transforms already (e.g. if this was applied already) [#29110]
805             existingFCurves = {}
806             for fcu in adt.action.fcurves:
807                 # get "delta" path - i.e. the final paths which may clash
808                 path = fcu.data_path
809                 if path in STANDARD_TO_DELTA_PATHS:
810                     # to be converted - conflicts may exist...
811                     dpath = STANDARD_TO_DELTA_PATHS[path]
812                 elif path in DELTA_PATHS:
813                     # already delta - check for conflicts...
814                     dpath = path
815                 else:
816                     # non-transform - ignore
817                     continue
818
819                 # a delta path like this for the same index shouldn't
820                 # exist already, otherwise we've got a conflict
821                 if dpath in existingFCurves:
822                     # ensure that this index hasn't occurred before
823                     if fcu.array_index in existingFCurves[dpath]:
824                         # conflict
825                         self.report({'ERROR'},
826                                     "Object '%r' already has '%r' F-Curve(s). "
827                                     "Remove these before trying again" %
828                                     (obj.name, dpath))
829                         return {'CANCELLED'}
830                     else:
831                         # no conflict here
832                         existingFCurves[dpath] += [fcu.array_index]
833                 else:
834                     # no conflict yet
835                     existingFCurves[dpath] = [fcu.array_index]
836
837             # if F-Curve uses standard transform path
838             # just append "delta_" to this path
839             for fcu in adt.action.fcurves:
840                 if fcu.data_path == "location":
841                     fcu.data_path = "delta_location"
842                     obj.location.zero()
843                 elif fcu.data_path == "rotation_euler":
844                     fcu.data_path = "delta_rotation_euler"
845                     obj.rotation_euler.zero()
846                 elif fcu.data_path == "rotation_quaternion":
847                     fcu.data_path = "delta_rotation_quaternion"
848                     obj.rotation_quaternion.identity()
849                 # XXX: currently not implemented
850                 # ~ elif fcu.data_path == "rotation_axis_angle":
851                 # ~    fcu.data_path = "delta_rotation_axis_angle"
852                 elif fcu.data_path == "scale":
853                     fcu.data_path = "delta_scale"
854                     obj.scale = 1.0, 1.0, 1.0
855
856         # hack: force animsys flush by changing frame, so that deltas get run
857         context.scene.frame_set(context.scene.frame_current)
858
859         return {'FINISHED'}
860
861
862 class DupliOffsetFromCursor(Operator):
863     """Set offset used for collection instances based on cursor position"""
864     bl_idname = "object.instance_offset_from_cursor"
865     bl_label = "Set Offset From Cursor"
866     bl_options = {'INTERNAL', 'UNDO'}
867
868     @classmethod
869     def poll(cls, context):
870         return (context.active_object is not None)
871
872     def execute(self, context):
873         scene = context.scene
874         collection = context.collection
875
876         collection.instance_offset = scene.cursor_location
877
878         return {'FINISHED'}
879
880
881 class LoadImageAsEmpty:
882     bl_options = {'REGISTER', 'UNDO'}
883
884     filepath: StringProperty(
885         subtype='FILE_PATH'
886     )
887
888     filter_image: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
889     filter_folder: BoolProperty(default=True, options={'HIDDEN', 'SKIP_SAVE'})
890
891     view_align: BoolProperty(
892         name="Align to view",
893         default=True
894     )
895
896     @classmethod
897     def poll(cls, context):
898         return context.mode == "OBJECT"
899
900     def invoke(self, context, event):
901         context.window_manager.fileselect_add(self)
902         return {'RUNNING_MODAL'}
903
904     def execute(self, context):
905         scene = context.scene
906         space = context.space_data
907         cursor = scene.cursor_location
908
909         try:
910             image = bpy.data.images.load(self.filepath, check_existing=True)
911         except RuntimeError as ex:
912             self.report({"ERROR"}, str(ex))
913             return {"CANCELLED"}
914
915         bpy.ops.object.empty_add(
916             'INVOKE_REGION_WIN',
917             type='IMAGE',
918             location=cursor,
919             view_align=self.view_align,
920         )
921
922         obj = context.active_object
923         obj.data = image
924         obj.empty_display_size = 5.0
925         self.set_settings(context, obj)
926         return {'FINISHED'}
927
928     def set_settings(self, context, obj):
929         pass
930
931
932 class LoadBackgroundImage(LoadImageAsEmpty, Operator):
933     """Add a reference image into the background behind objects"""
934     bl_idname = "object.load_background_image"
935     bl_label = "Load Background Image"
936
937     def set_settings(self, context, obj):
938         obj.empty_image_depth = 'BACK'
939         obj.empty_image_side = 'FRONT'
940
941         if context.space_data.type == 'VIEW_3D':
942             if not context.space_data.region_3d.is_perspective:
943                 obj.show_empty_image_perspective = False
944
945
946 class LoadReferenceImage(LoadImageAsEmpty, Operator):
947     """Add a reference image into the scene between objects"""
948     bl_idname = "object.load_reference_image"
949     bl_label = "Load Reference Image"
950
951     def set_settings(self, context, obj):
952         pass
953
954
955 class OBJECT_OT_assign_property_defaults(Operator):
956     """Assign the current values of custom properties as their defaults, """ \
957     """for use as part of the rest pose state in NLA track mixing"""
958     bl_idname = "object.assign_property_defaults"
959     bl_label = "Assign Custom Property Values as Default"
960     bl_options = {'UNDO', 'REGISTER'}
961
962     process_data: BoolProperty(name="Process data properties", default=True)
963     process_bones: BoolProperty(name="Process bone properties", default=True)
964
965     @classmethod
966     def poll(cls, context):
967         obj = context.active_object
968         return obj is not None and obj.library is None and obj.mode in {'POSE', 'OBJECT'}
969
970     @staticmethod
971     def assign_defaults(obj):
972         from rna_prop_ui import rna_idprop_ui_prop_default_set
973
974         rna_properties = {'_RNA_UI'} | {prop.identifier for prop in obj.bl_rna.properties if prop.is_runtime}
975
976         for prop, value in obj.items():
977             if prop not in rna_properties:
978                 rna_idprop_ui_prop_default_set(obj, prop, value)
979
980     def execute(self, context):
981         obj = context.active_object
982
983         self.assign_defaults(obj)
984
985         if self.process_bones and obj.pose:
986             for pbone in obj.pose.bones:
987                 self.assign_defaults(pbone)
988
989         if self.process_data and obj.data and obj.data.library is None:
990             self.assign_defaults(obj.data)
991
992             if self.process_bones and isinstance(obj.data, bpy.types.Armature):
993                 for bone in obj.data.bones:
994                     self.assign_defaults(bone)
995
996         return {'FINISHED'}
997
998
999 classes = (
1000     ClearAllRestrictRender,
1001     DupliOffsetFromCursor,
1002     IsolateTypeRender,
1003     JoinUVs,
1004     LoadBackgroundImage,
1005     LoadReferenceImage,
1006     MakeDupliFace,
1007     SelectCamera,
1008     SelectHierarchy,
1009     SelectPattern,
1010     ShapeTransfer,
1011     SubdivisionSet,
1012     TransformsToDeltas,
1013     TransformsToDeltasAnim,
1014     OBJECT_OT_assign_property_defaults,
1015 )