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 compliant>
20from bl_ui.properties_animviz import (
21    MotionPathButtonsPanel,
22    MotionPathButtonsPanel_display,
23)
24import bpy
25from bpy.types import Panel, Menu
26from rna_prop_ui import PropertyPanel
27
28
29class ObjectButtonsPanel:
30    bl_space_type = 'PROPERTIES'
31    bl_region_type = 'WINDOW'
32    bl_context = "object"
33
34
35class OBJECT_PT_context_object(ObjectButtonsPanel, Panel):
36    bl_label = ""
37    bl_options = {'HIDE_HEADER'}
38
39    def draw(self, context):
40        layout = self.layout
41        space = context.space_data
42
43        if space.use_pin_id:
44            layout.template_ID(space, "pin_id")
45        else:
46            row = layout.row()
47            row.template_ID(context.view_layer.objects, "active", filter='AVAILABLE')
48
49
50class OBJECT_PT_transform(ObjectButtonsPanel, Panel):
51    bl_label = "Transform"
52
53    def draw(self, context):
54        layout = self.layout
55        layout.use_property_split = True
56
57        ob = context.object
58
59        col = layout.column()
60        row = col.row(align=True)
61        row.prop(ob, "location")
62        row.use_property_decorate = False
63        row.prop(ob, "lock_location", text="", emboss=False, icon='DECORATE_UNLOCKED')
64
65        rotation_mode = ob.rotation_mode
66        if rotation_mode == 'QUATERNION':
67            col = layout.column()
68            row = col.row(align=True)
69            row.prop(ob, "rotation_quaternion", text="Rotation")
70            sub = row.column(align=True)
71            sub.use_property_decorate = False
72            sub.prop(ob, "lock_rotation_w", text="", emboss=False, icon='DECORATE_UNLOCKED')
73            sub.prop(ob, "lock_rotation", text="", emboss=False, icon='DECORATE_UNLOCKED')
74        elif rotation_mode == 'AXIS_ANGLE':
75            col = layout.column()
76            row = col.row(align=True)
77            row.prop(ob, "rotation_axis_angle", text="Rotation")
78
79            sub = row.column(align=True)
80            sub.use_property_decorate = False
81            sub.prop(ob, "lock_rotation_w", text="", emboss=False, icon='DECORATE_UNLOCKED')
82            sub.prop(ob, "lock_rotation", text="", emboss=False, icon='DECORATE_UNLOCKED')
83        else:
84            col = layout.column()
85            row = col.row(align=True)
86            row.prop(ob, "rotation_euler", text="Rotation")
87            row.use_property_decorate = False
88            row.prop(ob, "lock_rotation", text="", emboss=False, icon='DECORATE_UNLOCKED')
89        row = layout.row(align=True)
90        row.prop(ob, "rotation_mode", text="Mode")
91        row.label(text="", icon='BLANK1')
92
93        col = layout.column()
94        row = col.row(align=True)
95        row.prop(ob, "scale")
96        row.use_property_decorate = False
97        row.prop(ob, "lock_scale", text="", emboss=False, icon='DECORATE_UNLOCKED')
98
99
100class OBJECT_PT_delta_transform(ObjectButtonsPanel, Panel):
101    bl_label = "Delta Transform"
102    bl_parent_id = "OBJECT_PT_transform"
103    bl_options = {'DEFAULT_CLOSED'}
104
105    def draw(self, context):
106        layout = self.layout
107        layout.use_property_split = True
108
109        ob = context.object
110
111        col = layout.column()
112        col.prop(ob, "delta_location", text="Location")
113
114        rotation_mode = ob.rotation_mode
115        if rotation_mode == 'QUATERNION':
116            col.prop(ob, "delta_rotation_quaternion", text="Rotation")
117        elif rotation_mode == 'AXIS_ANGLE':
118            pass
119        else:
120            col.prop(ob, "delta_rotation_euler", text="Rotation")
121
122        col.prop(ob, "delta_scale", text="Scale")
123
124
125class OBJECT_PT_relations(ObjectButtonsPanel, Panel):
126    bl_label = "Relations"
127    bl_options = {'DEFAULT_CLOSED'}
128
129    def draw(self, context):
130        layout = self.layout
131        layout.use_property_split = True
132        flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
133
134        ob = context.object
135
136        col = flow.column()
137        col.prop(ob, "parent")
138        sub = col.column()
139        sub.prop(ob, "parent_type")
140        parent = ob.parent
141        if parent and ob.parent_type == 'BONE' and parent.type == 'ARMATURE':
142            sub.prop_search(ob, "parent_bone", parent.data, "bones")
143        sub.active = (parent is not None)
144
145        col.separator()
146
147        col = flow.column()
148
149        col.prop(ob, "track_axis", text="Tracking Axis")
150        col.prop(ob, "up_axis", text="Up Axis")
151
152        col.separator()
153
154        col = flow.column()
155
156        col.prop(ob, "pass_index")
157
158
159class COLLECTION_MT_context_menu(Menu):
160    bl_label = "Collection Specials"
161
162    def draw(self, _context):
163        layout = self.layout
164
165        layout.operator("object.collection_unlink", icon='X')
166        layout.operator("object.collection_objects_select")
167        layout.operator("object.instance_offset_from_cursor")
168
169
170class OBJECT_PT_collections(ObjectButtonsPanel, Panel):
171    bl_label = "Collections"
172    bl_options = {'DEFAULT_CLOSED'}
173
174    def draw(self, context):
175        layout = self.layout
176
177        obj = context.object
178
179        row = layout.row(align=True)
180        if bpy.data.collections:
181            row.operator("object.collection_link", text="Add to Collection")
182        else:
183            row.operator("object.collection_add", text="Add to Collection")
184        row.operator("object.collection_add", text="", icon='ADD')
185
186        obj_name = obj.name
187        for collection in bpy.data.collections:
188            # XXX this is slow and stupid!, we need 2 checks, one that's fast
189            # and another that we can be sure its not a name collision
190            # from linked library data
191            collection_objects = collection.objects
192            if obj_name in collection.objects and obj in collection_objects[:]:
193                col = layout.column(align=True)
194
195                col.context_pointer_set("collection", collection)
196
197                row = col.box().row()
198                row.prop(collection, "name", text="")
199                row.operator("object.collection_remove", text="", icon='X', emboss=False)
200                row.menu("COLLECTION_MT_context_menu", icon='DOWNARROW_HLT', text="")
201
202                row = col.box().row()
203                row.prop(collection, "instance_offset", text="")
204
205
206class OBJECT_PT_display(ObjectButtonsPanel, Panel):
207    bl_label = "Viewport Display"
208    bl_options = {'DEFAULT_CLOSED'}
209    bl_order = 10
210
211    def draw(self, context):
212        layout = self.layout
213        layout.use_property_split = True
214
215        obj = context.object
216        obj_type = obj.type
217        is_geometry = (obj_type in {'MESH', 'CURVE', 'SURFACE', 'META', 'FONT', 'VOLUME', 'HAIR', 'POINTCLOUD'})
218        is_wire = (obj_type in {'CAMERA', 'EMPTY'})
219        is_empty_image = (obj_type == 'EMPTY' and obj.empty_display_type == 'IMAGE')
220        is_dupli = (obj.instance_type != 'NONE')
221        is_gpencil = (obj_type == 'GPENCIL')
222
223        col = layout.column(heading="Show")
224        col.prop(obj, "show_name", text="Name")
225        col.prop(obj, "show_axis", text="Axis")
226
227        # Makes no sense for cameras, armatures, etc.!
228        # but these settings do apply to dupli instances
229        if is_geometry or is_dupli:
230            col.prop(obj, "show_wire", text="Wireframe")
231        if obj_type == 'MESH' or is_dupli:
232            col.prop(obj, "show_all_edges", text="All Edges")
233        if is_geometry:
234            col.prop(obj, "show_texture_space", text="Texture Space")
235            col.prop(obj.display, "show_shadows", text="Shadow")
236        col.prop(obj, "show_in_front", text="In Front")
237        # if obj_type == 'MESH' or is_empty_image:
238        #    col.prop(obj, "show_transparent", text="Transparency")
239        sub = layout.column()
240        if is_wire:
241            # wire objects only use the max. display type for duplis
242            sub.active = is_dupli
243        sub.prop(obj, "display_type", text="Display As")
244
245        if is_geometry or is_dupli or is_empty_image or is_gpencil:
246            # Only useful with object having faces/materials...
247            col.prop(obj, "color")
248
249        col = layout.column(align=False, heading="Bounds")
250        col.use_property_decorate = False
251        row = col.row(align=True)
252        sub = row.row(align=True)
253        sub.prop(obj, "show_bounds", text="")
254        sub = sub.row(align=True)
255        sub.active = obj.show_bounds or (obj.display_type == 'BOUNDS')
256        sub.prop(obj, "display_bounds_type", text="")
257        row.prop_decorator(obj, "display_bounds_type")
258
259
260class OBJECT_PT_instancing(ObjectButtonsPanel, Panel):
261    bl_label = "Instancing"
262    bl_options = {'DEFAULT_CLOSED'}
263
264    def draw(self, context):
265        layout = self.layout
266
267        ob = context.object
268
269        row = layout.row()
270        row.prop(ob, "instance_type", expand=True)
271
272        layout.use_property_split = True
273
274        if ob.instance_type == 'VERTS':
275            layout.prop(ob, "use_instance_vertices_rotation", text="Align to Vertex Normal")
276
277        elif ob.instance_type == 'COLLECTION':
278            col = layout.column()
279            col.prop(ob, "instance_collection", text="Collection")
280
281        if ob.instance_type != 'NONE' or ob.particle_systems:
282            col = layout.column(heading="Show Instancer", align=True)
283            col.prop(ob, "show_instancer_for_viewport", text="Viewport")
284            col.prop(ob, "show_instancer_for_render", text="Render")
285
286
287class OBJECT_PT_instancing_size(ObjectButtonsPanel, Panel):
288    bl_label = "Scale by Face Size"
289    bl_parent_id = "OBJECT_PT_instancing"
290
291    @classmethod
292    def poll(cls, context):
293        ob = context.object
294        return ob.instance_type == 'FACES'
295
296    def draw_header(self, context):
297
298        ob = context.object
299        self.layout.prop(ob, "use_instance_faces_scale", text="")
300
301    def draw(self, context):
302        layout = self.layout
303        ob = context.object
304        layout.use_property_split = True
305
306        layout.active = ob.use_instance_faces_scale
307        layout.prop(ob, "instance_faces_scale", text="Factor")
308
309
310class OBJECT_PT_motion_paths(MotionPathButtonsPanel, Panel):
311    #bl_label = "Object Motion Paths"
312    bl_context = "object"
313    bl_options = {'DEFAULT_CLOSED'}
314
315    @classmethod
316    def poll(cls, context):
317        return (context.object)
318
319    def draw(self, context):
320        # layout = self.layout
321
322        ob = context.object
323        avs = ob.animation_visualization
324        mpath = ob.motion_path
325
326        self.draw_settings(context, avs, mpath)
327
328
329class OBJECT_PT_motion_paths_display(MotionPathButtonsPanel_display, Panel):
330    #bl_label = "Object Motion Paths"
331    bl_context = "object"
332    bl_parent_id = "OBJECT_PT_motion_paths"
333    bl_options = {'DEFAULT_CLOSED'}
334
335    @classmethod
336    def poll(cls, context):
337        return (context.object)
338
339    def draw(self, context):
340        # layout = self.layout
341
342        ob = context.object
343        avs = ob.animation_visualization
344        mpath = ob.motion_path
345
346        self.draw_settings(context, avs, mpath)
347
348
349class OBJECT_PT_visibility(ObjectButtonsPanel, Panel):
350    bl_label = "Visibility"
351    bl_options = {'DEFAULT_CLOSED'}
352    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
353
354    @classmethod
355    def poll(cls, context):
356        return (context.object) and (context.engine in cls.COMPAT_ENGINES)
357
358    def draw(self, context):
359        layout = self.layout
360        layout.use_property_split = True
361
362        layout = self.layout
363        ob = context.object
364
365        layout.prop(ob, "hide_select", text="Selectable", toggle=False, invert_checkbox=True)
366
367        col = layout.column(heading="Show in")
368        col.prop(ob, "hide_viewport", text="Viewports", toggle=False, invert_checkbox=True)
369        col.prop(ob, "hide_render", text="Renders", toggle=False, invert_checkbox=True)
370
371        if context.object.type == 'GPENCIL':
372            col = layout.column(heading="Grease Pencil")
373            col.prop(ob, "use_grease_pencil_lights", toggle=False)
374
375
376class OBJECT_PT_custom_props(ObjectButtonsPanel, PropertyPanel, Panel):
377    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
378    _context_path = "object"
379    _property_type = bpy.types.Object
380
381
382classes = (
383    OBJECT_PT_context_object,
384    OBJECT_PT_transform,
385    OBJECT_PT_delta_transform,
386    OBJECT_PT_relations,
387    COLLECTION_MT_context_menu,
388    OBJECT_PT_collections,
389    OBJECT_PT_instancing,
390    OBJECT_PT_instancing_size,
391    OBJECT_PT_motion_paths,
392    OBJECT_PT_motion_paths_display,
393    OBJECT_PT_display,
394    OBJECT_PT_visibility,
395    OBJECT_PT_custom_props,
396)
397
398if __name__ == "__main__":  # only for live edit.
399    from bpy.utils import register_class
400    for cls in classes:
401        register_class(cls)
402