uv_bake_texture_to_vcols: moved to contrib: T63750
[blender-addons-contrib.git] / amaranth / misc / dupli_group_id.py
1 #  This program is free software; you can redistribute it and/or
2 #  modify it under the terms of the GNU General Public License
3 #  as published by the Free Software Foundation; either version 2
4 #  of the License, or (at your option) any later version.
5 #
6 #  This program is distributed in the hope that it will be useful,
7 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
8 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9 #  GNU General Public License for more details.
10 #
11 #  You should have received a copy of the GNU General Public License
12 #  along with this program; if not, write to the Free Software Foundation,
13 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14 """
15 Object ID for Dupli Groups
16 Say you have a linked character or asset, you can now set an Object ID for the
17 entire instance (the objects in the group), and use it with the Object Index
18 pass later in compositing. Something that I always wanted and it wasn't
19 possible!
20
21 In order for the Object ID to be loaded afterwards on computers without
22 Amaranth installed, it will automatically create a text file (called
23 AmaranthStartup.py) and save it inside the .blend, this will autorun on
24 startup and set the OB IDs. Remember to have auto-run python scripts on your
25 startup preferences.
26
27 Set a Pass Index and press "Apply Object ID to Duplis" on the Relations panel,
28 Object Properties.
29 """
30
31
32 import bpy
33 from amaranth.scene.debug import AMTH_SCENE_OT_blender_instance_open
34
35
36 # Some settings are bound to be saved on a startup py file
37 # TODO: refactor this, amth_text should not be declared as a global variable,
38 #       otherwise becomes confusing when you call it in the classes below.
39 def amaranth_text_startup(context):
40
41     amth_text_name = "AmaranthStartup.py"
42     amth_text_exists = False
43
44     global amth_text
45
46     try:
47         if bpy.data.texts:
48             for tx in bpy.data.texts:
49                 if tx.name == amth_text_name:
50                     amth_text_exists = True
51                     amth_text = bpy.data.texts[amth_text_name]
52                     break
53                 else:
54                     amth_text_exists = False
55
56         if not amth_text_exists:
57             bpy.ops.text.new()
58             amth_text = bpy.data.texts[((len(bpy.data.texts) * -1) + 1)]
59             amth_text.name = amth_text_name
60             amth_text.write("# Amaranth Startup Script\nimport bpy\n")
61             amth_text.use_module = True
62
63         return amth_text_exists
64     except AttributeError:
65         return None
66
67
68 # FEATURE: Dupli  Group Path
69 def ui_dupli_group_library_path(self, context):
70
71     ob = context.object
72
73     row = self.layout.row()
74     row.alignment = "LEFT"
75
76     if ob and ob.instance_collection and ob.instance_collection.library:
77         lib = ob.instance_collection.library.filepath
78
79         row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
80                      text="Library: %s" % lib,
81                      emboss=False,
82                      icon="LINK_BLEND").filepath = lib
83 # // FEATURE: Dupli  Group Path
84
85
86 # FEATURE: Object ID for objects inside DupliGroups
87 class AMTH_OBJECT_OT_id_dupligroup(bpy.types.Operator):
88
89     """Set the Object ID for objects in the dupli group"""
90     bl_idname = "object.amaranth_object_id_duplis"
91     bl_label = "Apply Object ID to Duplis"
92
93     clear = False
94
95     @classmethod
96     def poll(cls, context):
97         return context.active_object.instance_collection
98
99     def execute(self, context):
100         self.__class__.clear = False
101         ob = context.active_object
102         amaranth_text_startup(context)
103         script_exists = False
104         script_intro = "# OB ID: %s" % ob.name
105         obdata = 'bpy.data.objects[" % s"]' % ob.name
106         # TODO: cleanup script var using format or template strings
107         script = "%s" % (
108             "\nif %(obdata)s and %(obdata)s.instance_collection and %(obdata)s.pass_index != 0: %(obname)s \n"
109             "    for dob in %(obdata)s.instance_collection.objects: %(obname)s \n"
110             "        dob.pass_index = %(obdata)s.pass_index %(obname)s \n" %
111             {"obdata": obdata, "obname": script_intro})
112
113         for txt in bpy.data.texts:
114             if txt.name == amth_text.name:
115                 for li in txt.lines:
116                     if script_intro == li.body:
117                         script_exists = True
118                         continue
119
120         if not script_exists:
121             amth_text.write("\n")
122             amth_text.write(script_intro)
123             amth_text.write(script)
124
125         if ob and ob.instance_collection:
126             if ob.pass_index != 0:
127                 for dob in ob.instance_collection.objects:
128                     dob.pass_index = ob.pass_index
129
130         self.report({"INFO"},
131                     "%s ID: %s to all objects in this Dupli Group" % (
132                         "Applied" if not script_exists else "Updated",
133                         ob.pass_index))
134
135         return {"FINISHED"}
136
137
138 class AMTH_OBJECT_OT_id_dupligroup_clear(bpy.types.Operator):
139
140     """Clear the Object ID from objects in dupli group"""
141     bl_idname = "object.amaranth_object_id_duplis_clear"
142     bl_label = "Clear Object ID from Duplis"
143
144     @classmethod
145     def poll(cls, context):
146         return context.active_object.instance_collection
147
148     def execute(self, context):
149         context.active_object.pass_index = 0
150         AMTH_OBJECT_OT_id_dupligroup.clear = True
151         amth_text_exists = amaranth_text_startup(context)
152         match_first = "# OB ID: %s" % context.active_object.name
153
154         if amth_text_exists:
155             for txt in bpy.data.texts:
156                 if txt.name == amth_text.name:
157                     for li in txt.lines:
158                         if match_first in li.body:
159                             li.body = ""
160                             continue
161
162         self.report({"INFO"}, "Object IDs back to normal")
163         return {"FINISHED"}
164
165
166 def ui_object_id_duplis(self, context):
167
168     if context.active_object.instance_collection:
169         split = self.layout.split()
170         row = split.row(align=True)
171         row.enabled = context.active_object.pass_index != 0
172         row.operator(
173             AMTH_OBJECT_OT_id_dupligroup.bl_idname)
174         row.operator(
175             AMTH_OBJECT_OT_id_dupligroup_clear.bl_idname,
176             icon="X", text="")
177         split.separator()
178
179         if AMTH_OBJECT_OT_id_dupligroup.clear:
180             self.layout.label(text="Next time you save/reload this file, "
181                               "object IDs will be back to normal",
182                               icon="INFO")
183 # // FEATURE: Object ID for objects inside DupliGroups
184
185
186 def register():
187     bpy.utils.register_class(AMTH_OBJECT_OT_id_dupligroup)
188     bpy.utils.register_class(AMTH_OBJECT_OT_id_dupligroup_clear)
189     bpy.types.OBJECT_PT_instancing.append(ui_dupli_group_library_path)
190     bpy.types.OBJECT_PT_relations.append(ui_object_id_duplis)
191
192
193 def unregister():
194     bpy.utils.unregister_class(AMTH_OBJECT_OT_id_dupligroup)
195     bpy.utils.unregister_class(AMTH_OBJECT_OT_id_dupligroup_clear)
196     bpy.types.OBJECT_PT_instancing.remove(ui_dupli_group_library_path)
197     bpy.types.OBJECT_PT_relations.remove(ui_object_id_duplis)