Initial commit for the Meadow Generator addon.
[blender-addons-contrib.git] / object_physics_meadow / patch.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 compliant>
20
21 import bpy, time, sys
22 from mathutils import *
23 from bpy_extras import object_utils
24
25 from meadow import settings as _settings
26 from meadow.util import *
27
28 # supported relation types between patch objects
29 # yields (data, property) pairs to object pointer properties
30 def object_relations(ob):
31     for md in ob.modifiers:
32         if md.type == 'PARTICLE_INSTANCE':
33             yield md, "object"
34
35 #-----------------------------------------------------------------------
36
37 def patch_objects(context):
38     settings = _settings.get(context)
39     patch_group = settings.patch_group(context)
40     # ignore objects with invalid blob index
41     return [ob for ob in patch_group.objects if ob.meadow.blob_index >= 0]
42
43 def patch_group_clear(context):
44     settings = _settings.get(context)
45     patch_group = settings.patch_group(context)
46     scene = context.scene
47     
48     # local list copy to avoid messing up the iterator
49     objects = [ob for ob in patch_group.objects]
50     
51     # unlink objects to avoid invalid pointers when deleting them
52     for ob in objects:
53         for data, prop in object_relations(ob):
54             setattr(data, prop, None)
55     
56     for ob in objects:
57         scene.objects.unlink(ob)
58         patch_group.objects.unlink(ob)
59         
60         # note: this can fail if something still references the object
61         # we try to unlink own pointers above, but users might add own
62         if ob.users == 0:
63             bpy.data.objects.remove(ob)
64         else:
65             print("Warning: could not remove object %r" % ob.name)
66
67 def patch_group_assign(context, patchob):
68     settings = _settings.get(context)
69     patch_group = settings.patch_group(context)
70     
71     patch_group.objects.link(patchob)
72     # NOTE: unsetting the type is important, otherwise gathering templates
73     # a second time will include deleted objects!
74     patchob.meadow.type = 'NONE'
75
76 def patch_group_remove(context, patchob):
77     settings = _settings.get(context)
78     patch_group = settings.patch_group(context)
79     
80     patch_group.objects.unlink(patchob)
81
82 #-----------------------------------------------------------------------
83
84 # XXX WARNING: using the duplicate operator is _horribly_ slow!
85 # fortunately we can use a trick and create dupli instances first,
86 # then use "make duplicates real" once to generate actual object copies
87 """
88 def duplicate_patch_object(context, varob):
89     scene = context.scene
90     
91     scene.objects.active = varob
92     for ob in scene.objects:
93         ob.select = (ob == varob)
94     
95     bpy.ops.object.duplicate('EXEC_DEFAULT', mode='INIT')
96     
97     # find new patch duplicate among selected
98     patchob = None
99     for ob in scene.objects:
100         if ob.select and ob != varob:
101             patchob = ob
102             break
103     return patchob
104
105 def make_patch_variant(context, varob, obmat):
106     patchob = duplicate_patch_object(context, varob)
107     if patchob:
108         patchob.matrix_world = obmat
109
110 def make_patches(context, patch_mats, variant_objects):
111     scene = context.scene
112     
113     # store active/selected state to restore it afterward
114     curact = scene.objects.active
115     cursel = { ob : ob.select for ob in scene.objects }
116     
117     for obmat in patch_mats:
118         for varob in variant_objects:
119             make_patch_variant(context, varob, obmat)
120     
121     # restore active/selected state
122     scene.objects.active = curact
123     for ob in scene.objects:
124         ob.select = cursel.get(ob, False)
125 """
126
127 def make_copies(scene, gridob, childob):
128     # setup the template as dupli child of the grid
129     childmat = childob.matrix_basis
130     childob.parent = gridob
131     gridob.dupli_type = 'VERTS'
132     
133     # select duplicator object
134     select_single_object(gridob)
135     
136     # do it!
137     bpy.ops.object.duplicates_make_real(use_base_parent=False, use_hierarchy=False)
138     
139     # collect new copies
140     duplicates = [ob for ob in scene.objects if ob.select and ob != gridob]
141     
142     # un-parent again
143     childob.parent = None
144     childob.matrix_basis = childmat
145     gridob.dupli_type = 'NONE'
146     
147     return duplicates
148
149 def make_patches(context, gridob, template_objects):
150     scene = context.scene
151     gridmat = gridob.matrix_world
152     
153     patch_group_clear(context)
154     
155     temp_copies = {}
156     with ObjectSelection():
157         for tempob in template_objects:
158             # create patch copies
159             copies = make_copies(scene, gridob, tempob)
160             
161             # customize copies
162             for ob, (index, vert) in zip(copies, enumerate(gridob.data.vertices)):
163                 # put it in the patch group
164                 patch_group_assign(context, ob)
165                 # assign the index for mapping
166                 ob.meadow.blob_index = index
167                 
168                 # apply transform
169                 vertmat = Matrix.Translation(vert.co)
170                 duplimat = gridmat * vertmat
171                 ob.matrix_world = duplimat
172                 
173                 # XXX WARNING: having lots of objects in the scene slows down
174                 # the make-duplis-real operator to an absolute crawl!!
175                 # Therefore we unlink all copies here until the copying
176                 # of other objects is done
177                 scene.objects.unlink(ob)
178             
179             temp_copies[tempob] = copies
180     
181     # copying is done, re-link stuff to the scene
182     for tempob, copies in temp_copies.items():
183         for ob in copies:
184             # construct a unique name (scene.objects.link otherwise can raise an exception)
185             ob.name = "{}__{}__".format(tempob.name, str(ob.meadow.blob_index))
186             
187             scene.objects.link(ob)
188     
189     # recreate dependencies between patch objects on the copies
190     # note: let copying finish first, otherwise might miss dependencies to objects that are copied later
191     for copies in temp_copies.values():
192         for index, copyob in enumerate(copies):
193             for data, prop in object_relations(copyob):
194                 relob = getattr(data, prop, None)
195                 relcopies = temp_copies.get(relob, None)
196                 if relcopies:
197                     setattr(data, prop, relcopies[index])
198
199 #-----------------------------------------------------------------------
200
201 sim_modifier_types = { 'CLOTH', 'DYNAMIC_PAINT', 'FLUID_SIMULATION', 'PARTICLE_SYSTEM', 'SMOKE', 'SOFT_BODY' }
202
203 # stores the enabled state of sim objects to allow toggling for bakes
204 class BakeSimContext():
205     def __enter__(self):
206         scene = bpy.context.scene
207         
208         self.mod_toggles = {}
209         for ob in scene.objects:
210             for md in ob.modifiers:
211                 if md.type in sim_modifier_types:
212                     self.mod_toggles[(ob, md)] = (md.show_viewport, md.show_render)
213         
214         return self.mod_toggles
215     
216     def __exit__(self, exc_type, exc_value, traceback):
217         scene = bpy.context.scene
218         
219         for ob in scene.objects:
220             for md in ob.modifiers:
221                 if md.type in sim_modifier_types:
222                     if (ob, md) in self.mod_toggles:
223                         toggle = self.mod_toggles[(ob, md)]
224                         md.show_viewport = toggle[0]
225                         md.show_render = toggle[1]
226
227 def enable_single_sim_psys(context, sim_ob, sim_psys):
228     scene = context.scene
229     for ob in scene.objects:
230         for md in ob.modifiers:
231             if md.type not in sim_modifier_types:
232                 continue
233             
234             enable = (md.type == 'PARTICLE_SYSTEM' and md.particle_system == sim_psys)
235             
236             md.show_viewport = enable
237             md.show_render = enable
238
239 def bake_psys(context, ob, psys):
240     cache = psys.point_cache
241     context_override = context.copy()
242     context_override["point_cache"] = cache
243     
244     select_single_object(ob)
245     curpsys = ob.particle_systems.active
246     ob.particle_systems.active = psys
247     
248     if cache.is_baked:
249         bpy.ops.ptcache.free_bake(context_override)
250     
251     # make sure only the active psys is enabled
252     enable_single_sim_psys(context, ob, psys)
253     
254     bpy.ops.ptcache.bake(context_override, bake=True)
255     
256     # restore
257     ob.particle_systems.active = curpsys
258
259 def bake_all(context):
260     settings = _settings.get(context)
261     wm = context.window_manager
262     
263     total_time = 0.0
264     avg_time = 0.0
265     
266     # XXX Note: wm.progress updates are disabled for now, because the bake
267     # operator overrides this with it's own progress numbers ...
268     
269     total = count_bakeable(context)
270     #wm.progress_begin(0, total)
271     
272     num = 0
273     for ob in patch_objects(context):
274         for psys in ob.particle_systems:
275             sys.stdout.write("Baking blob {}/{} ... ".format(str(num).rjust(5), str(total).ljust(5)))
276             sys.stdout.flush()
277             
278             start_time = time.time()
279             
280             bake_psys(context, ob, psys)
281             
282             duration = time.time() - start_time
283             total_time += duration
284             avg_time = total_time / float(num + 1)
285             
286             #wm.progress_update(num)
287             time_string = lambda x: time.strftime("%H:%M:%S", time.gmtime(x)) + ".%02d" % (int(x * 100.0) % 100)
288             durstr = time_string(duration)
289             avgstr = time_string(avg_time) if avg_time > 0.0 else "--:--:--"
290             etastr = time_string(avg_time * (total - num)) if avg_time > 0.0 else "--:--:--"
291             sys.stdout.write("{} (avg. {}, ETA {})\n".format(durstr, avgstr, etastr))
292             sys.stdout.flush()
293             num += 1
294     
295     #wm.progress_end()
296
297 def count_bakeable(context):
298     num = 0
299     for ob in patch_objects(context):
300         for psys in ob.particle_systems:
301             num += 1
302     return num
303
304 def patch_objects_rebake(context):
305     settings = _settings.get(context)
306     wm = context.window_manager
307     
308     with ObjectSelection():
309         # we disable all sim modifiers selectively to make sure only one sim has to be calculated at a time
310         with BakeSimContext():
311             scene = context.scene
312             curframe = scene.frame_current
313             
314             # XXX have to set this because bake operator only bakes up to the last frame ...
315             scene.frame_current = scene.frame_end
316             
317             bake_all(context)
318             
319             scene.frame_set(curframe)