forked from uhlik/blendmaxwell
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexport.py
executable file
·5427 lines (4719 loc) · 235 KB
/
export.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# ##### 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,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import os
import math
import shlex
import subprocess
import uuid
import shutil
import struct
import json
import sys
import re
import string
import bpy
from mathutils import Matrix, Vector
from bpy_extras import io_utils
import bmesh
from mathutils.geometry import barycentric_transform
from mathutils.bvhtree import BVHTree
import numpy
from .log import log, clear_log, LogStyles
from . import utils
from . import maths
from . import system
from . import rfbin
from . import mxs
from . import tmpio
AXIS_CONVERSION = Matrix(((1.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, -1.0, 0.0))).to_4x4()
ROTATE_X_90 = Matrix.Rotation(math.radians(90.0), 4, 'X')
ROTATE_X_MINUS_90 = Matrix.Rotation(math.radians(-90.0), 4, 'X')
# FIXME: (IMPORTANT) simple cube with default subdivision modifier refuses to render (An edge connecting two vertices was specified more than once. It's likely that an incident face was flipped)
# - 2.76 order of faces is different than in 2.77. this might be the problem because from 2.76 it renders fine
# - creating mesh again from data (bot orders), the result is the same too.. and this is the same for polygons/tessfaces/bmesh.faces
# - in 2.76 everything works, in 2.77 nothing, maxwell just don't like it. i have no idea what's going on.
# - also it seems to be not caused by split normals, reverting to old scheme doesn't fix anything.
# - ok, made it to work on cube and cube with one face triangulated. does not work on monkey, even though it should. what a mystery. will try again soon..
# TODO: restore instancer support for my personal use (python only)
# NOTE: grass: preview in viewport is wrong, looks like before parenting (i think), but i can't get back to modifier once is created without whole python crashing..
# NOTE: particles/cloner: problematic scenario: object with particles (particles or cloner is used) is a child of arbitrary transformed parent. the result is, one particle is misplaced far away. cloner can be fixed by putting object in scene root and changing it to use external bin (using embedded particles will not fix it). particles can be fixed by using external bin, there is no difference in hierarchy change. maybe add checkbox to fix this automatically or add warning when problematic scenario is detected. anyway, bug is reported (and hopefuly acknowledged) and now i've got two options, either write quick and dirty fix or leave it as it should be and wait for the fix. both are correct..
# TODO: do something with sharp edges, auto smooth and custom normals..
# NOTE: check hair children particles again, seems to be crashing when exporting with uvs. put there warning at least
# TODO: more standardized setting render file type and bit depth, this will require change in render workflow and that is dangerous..
# TODO: check if in case of some error during exporting, everything is cleaned up and won't cause problems during next export
# NOTE: from Maxwell 3.2.0.4 beta changelog: Studio: Fixed when exporting MXMs material names were cropped if they contained dots. - remove dot changing mechanism whet this is out. maybe not.. not very importat and will work with older version. or check version and then decide. this will require version.py to return version number instead of true/false.
# NOTE: in some cases meshes with no polygons have to be exported, e.g. mesh with particle system (already fixed), look for other examples/uses, or maybe just swap it to empty at the end
# TODO: put wireframe scene creation to special export operator, remove all wireframe related stuff from normal workflow. also when done this way, no ugly hacking is needed to put new object during render export (which might crash blender). also implement wireframe without special switches and functions. modify current scene, rewrite serialized scene data and then pass to external script as regular scene.
# NOTE: particle data with foreach_get, well, in particles there is a problem, i export only alive particles and each have to be checked first, and with hair, oh well, it was not easy to get it working and now i don't want to break it.. so i guess this will stay as it is..
# TODO: when exporting motion blur, move timeline just once per step, eg. add each step to all objects at once per timeline move.
# TODO: export group instances
class MXSExportStats():
materials = 0
cameras = 0
empties = 0
meshes = 0
vertices = 0
triangles = 0
instances = 0
particles = 0
particles_number = 0
hairs = 0
hair_curves = 0
references = 0
volumetrics = 0
modifiers = 0
def __init__(self):
pass
def add(self, o, ):
if(o.m_type == 'MATERIAL'):
self.materials += 1
elif(o.m_type == 'CAMERA'):
self.cameras += 1
elif(o.m_type == 'EMPTY'):
self.empties += 1
elif(o.m_type == 'MESH'):
self.meshes += 1
self.vertices += len(o.m_vertices[0])
self.triangles += len(o.m_triangles)
elif(o.m_type == 'MESH_INSTANCE'):
self.instances += 1
elif(o.m_type == 'SCENE'):
pass
elif(o.m_type == 'ENVIRONMENT'):
pass
elif(o.m_type == 'PARTICLES'):
self.particles += 1
self.particles_number += o.stat_num
elif(o.m_type == 'HAIR'):
self.hairs += 1
self.hair_curves += o.m_data['HAIR_GUIDES_COUNT'][0]
elif(o.m_type == 'REFERENCE'):
self.references += 1
elif(o.m_type == 'VOLUMETRICS'):
self.volumetrics += 1
elif(o.m_type == 'SUBDIVISION'):
self.modifiers += 1
elif(o.m_type == 'SCATTER'):
self.modifiers += 1
elif(o.m_type == 'GRASS'):
self.modifiers += 1
elif(o.m_type == 'CLONER'):
self.modifiers += 1
elif(o.m_type == 'SEA'):
self.modifiers += 1
elif(o.m_type == 'WIREFRAME_CONTAINER'):
self.empties += 1
elif(o.m_type == 'WIREFRAME_BASE'):
self.meshes += 1
self.vertices += len(o.m_vertices)
self.triangles += len(o.m_triangles)
elif(o.m_type == 'WIREFRAME_INSTANCES'):
self.instances += 1
else:
pass
def int_to_short_notation(self, n, precision=1, ):
if(round(n / 10 ** 12, precision) >= 1):
r = int(round(n / 10 ** 12, precision))
return '{}t'.format(r)
elif(round(n / 10 ** 9, precision) >= 1):
r = int(round(n / 10 ** 9, precision))
return '{}g'.format(r)
elif(round(n / 10 ** 6, precision) >= 1):
r = int(round(n / 10 ** 6, precision))
return '{}m'.format(r)
elif(round(n / 10 ** 3, precision) >= 1):
r = int(round(n / 10 ** 3, precision))
return '{}k'.format(r)
else:
r = round(n / 10 ** 3, precision)
if(r >= 0.5):
return '{}k'.format(r)
else:
return '{}'.format(n)
def pprint(self):
log("{}".format("-" * 30), 1, )
log("export statistics:", 1, )
log("materials: {}".format(self.int_to_short_notation(self.materials)), 2, prefix="", )
log("cameras: {}".format(self.int_to_short_notation(self.cameras)), 2, prefix="", )
log("empties: {}".format(self.int_to_short_notation(self.empties)), 2, prefix="", )
log("meshes: {} (verts: {}, tris: {})".format(self.int_to_short_notation(self.meshes),
self.int_to_short_notation(self.vertices),
self.int_to_short_notation(self.triangles)), 2, prefix="", )
log("instances: {}".format(self.int_to_short_notation(self.instances)), 2, prefix="", )
log("particles: {} ({})".format(self.int_to_short_notation(self.particles),
self.int_to_short_notation(self.particles_number)), 2, prefix="", )
log("hairs: {} ({})".format(self.int_to_short_notation(self.hairs),
self.int_to_short_notation(self.hair_curves)), 2, prefix="", )
log("references: {}".format(self.int_to_short_notation(self.references)), 2, prefix="", )
log("volumetrics: {}".format(self.int_to_short_notation(self.volumetrics)), 2, prefix="", )
log("modifiers: {}".format(self.int_to_short_notation(self.modifiers)), 2, prefix="", )
class MXSExport():
def __init__(self, mxs_path, engine=None, ):
# clear db, before we start, previous error will cause more errors
MXSDatabase.clear()
MXSMotionBlurHelper.clear()
clear_log()
log("{0} {1} {0}".format("-" * 30, self.__class__.__name__), 0, LogStyles.MESSAGE, prefix="", )
ok = system.check_pymaxwell_version()
if(ok):
log("pymaxwell version >= {}".format(system.REQUIRED), 1, )
self.mxs_path = os.path.realpath(mxs_path)
log("exporting mxs: '{}'".format(self.mxs_path), 1, LogStyles.MESSAGE, )
self.engine = engine
self.progress_current = 0
self.progress_count = 0
self.context = bpy.context
self.uuid = uuid.uuid1()
mx = self.context.scene.maxwell_render
self.use_instances = mx.export_use_instances
self.use_wireframe = mx.export_use_wireframe
self.use_subdivision = mx.export_use_subdivision
self._prepare()
self._export()
self._finish()
MXSDatabase.clear()
MXSMotionBlurHelper.clear()
for me in bpy.data.meshes:
if(me.users == 0):
if(not me.use_fake_user):
# just log it, there is no need to remove orphan meshes because all meshes created during export should be removed now.
# user might need those meshes in future..
# NOTE: this can be removed once i am sure there is no other leak. now just scare user..
log("orphan mesh: '{}' is it intentional?".format(me.name), 1, LogStyles.ERROR, )
# bpy.data.meshes.remove(me)
def _progress(self, progress=0.0, ):
if(progress == 0.0):
progress = self.progress_current / self.progress_count
self.progress_current += 1
if(self.engine is not None):
if(system.PLATFORM == 'Darwin'):
# on Mac OS X report progress up to 3/4, then external script is called which will take some time
# and better not to over complicate things reporting that too.. would be problematic..
progress = maths.remap(progress, 0.0, 1.0, 0.0, 0.75)
elif(system.PLATFORM == 'Linux' or system.PLATFORM == 'Windows'):
pass
self.engine.update_progress(progress)
def _prepare(self):
self.hierarchy = []
if(system.PLATFORM == 'Darwin'):
# Mac OS X specific
self.data = []
self.serialized_data = []
mx = self.context.scene.maxwell_render
self.keep_intermediates = mx.export_keep_intermediates
h, t = os.path.split(self.mxs_path)
n, e = os.path.splitext(t)
self.tmp_dir = utils.tmp_dir(purpose='export_scene', uid=self.uuid, use_blend_name=True, )
self.mesh_data_paths = []
self.hair_data_paths = []
self.part_data_paths = []
self.wire_data_paths = []
self.scene_data_name = "{0}-{1}.json".format(n, self.uuid)
self.script_name = "{0}-{1}.py".format(n, self.uuid)
elif(system.PLATFORM == 'Linux' or system.PLATFORM == 'Windows'):
self.mxs = mxs.MXSWriter(path=self.mxs_path, append=False, )
# self.hierarchy = []
def _collect(self):
"""Collect scene objects.
what it does (unordered):
- Filter all scene objects and collect only objects needed for scene export. Meshes (and instances), empties, cameras and sun.
- Remove all objects on hidden layers and with hide_render: True, substitute with an empty if needed for correct hierarchy.
- Sort all objects by type, determine base meshes for instanced objects (if use_instances: True).
- Try to convert non-mesh objects to mesh and if result is renderable, include them as well.
- Covert dupli-objects to real meshes or instances.
Return filtered scene hierarchy.
"""
objs = self.context.scene.objects
# sort objects
def sort_objects():
meshes = []
empties = []
cameras = []
bases = []
instances = []
convertible_meshes = []
convertible_bases = []
convertible_instances = []
others = []
might_be_renderable = ['CURVE', 'SURFACE', 'FONT', ]
for o in objs:
if(o.type == 'MESH'):
if(self.use_instances):
if(o.data.users > 1):
instances.append(o)
else:
meshes.append(o)
else:
meshes.append(o)
elif(o.type == 'EMPTY'):
empties.append(o)
elif(o.type == 'CAMERA'):
cameras.append(o)
elif(o.type in might_be_renderable):
# convertibles.append(o)
if(self.use_instances):
if(o.data.users > 1):
convertible_instances.append(o)
else:
convertible_meshes.append(o)
else:
convertible_meshes.append(o)
else:
others.append(o)
instance_groups = {}
for o in instances:
if(o.data.name not in instance_groups):
instance_groups[o.data.name] = [o, ]
else:
instance_groups[o.data.name].append(o)
bases_names = []
for n, g in instance_groups.items():
nms = [o.name for o in g]
ls = sorted(nms)
bases_names.append(ls[0])
insts = instances[:]
instances = []
for o in insts:
if(o.name in bases_names):
bases.append(o)
else:
instances.append(o)
convertible_instance_groups = {}
for o in convertible_instances:
if(o.data.name not in convertible_instance_groups):
convertible_instance_groups[o.data.name] = [o, ]
else:
convertible_instance_groups[o.data.name].append(o)
convertible_bases_names = []
for n, g in convertible_instance_groups.items():
nms = [o.name for o in g]
ls = sorted(nms)
convertible_bases_names.append(ls[0])
convertible_insts = convertible_instances[:]
convertible_instances = []
for o in convertible_insts:
if(o.name in convertible_bases_names):
convertible_bases.append(o)
else:
convertible_instances.append(o)
return {'meshes': meshes,
'empties': empties,
'cameras': cameras,
'bases': bases,
'instances': instances,
'convertible_meshes': convertible_meshes,
'convertible_bases': convertible_bases,
'convertible_instances': convertible_instances,
'others': others, }
so = sort_objects()
# visibility
mx = self.context.scene.maxwell_render
layers = self.context.scene.layers
render_layers = self.context.scene.render.layers.active.layers
def check_visibility(o):
"""Objects which are in visible layers and have hide_render: False are considered visible,
objects which are only hidden from viewport are renderable, therefore visible."""
# if(mx.render_use_layers == 'RENDER'):
# for i, l in enumerate(o.layers):
# if(render_layers[i] is True and l is True and o.hide_render is False):
# return True
# else:
# for i, l in enumerate(o.layers):
# if(layers[i] is True and l is True and o.hide_render is False):
# return True
if(o.hide_render is True):
return False
s = None
r = None
for i, l in enumerate(o.layers):
if(layers[i] is True and l is True):
s = True
break
for i, l in enumerate(o.layers):
if(render_layers[i] is True and l is True):
r = True
break
if(s and r):
return True
return False
# export type
might_be_renderable = ['CURVE', 'SURFACE', 'FONT', ]
c_bases_meshes = []
c_instance_meshes = []
no_polygons_meshes = []
def export_type(o):
"""determine export type, if convertible, try convert to mesh and store result"""
t = 'EMPTY'
m = None
c = False
if(o.type == 'MESH'):
m = o.data
if(self.use_instances):
if(o.data.users > 1):
if(o in so['bases']):
t = 'BASE_INSTANCE'
else:
t = 'INSTANCE'
else:
if(len(o.data.polygons) > 0):
t = 'MESH'
else:
# case when object has no polygons, but with modifiers applied it will have..
me = o.to_mesh(self.context.scene, True, 'RENDER', )
if(len(me.polygons) > 0):
t = 'MESH'
# remove mesh, was created only for testing..
bpy.data.meshes.remove(me)
# in case mesh without polygons has particles systems to be exported
if(len(o.particle_systems) > 0):
for ps in o.particle_systems:
if(ps.settings.maxwell_render.use is not 'NONE'):
t = 'MESH'
no_polygons_meshes.append(o)
# else:
# t = 'EMPTY'
else:
if(len(o.data.polygons) > 0):
t = 'MESH'
else:
# case when object has no polygons, but with modifiers applied it will have..
me = o.to_mesh(self.context.scene, True, 'RENDER', )
if(len(me.polygons) > 0):
t = 'MESH'
# remove mesh, was created only for testing..
bpy.data.meshes.remove(me)
# in case mesh without polygons has particles systems to be exported
if(len(o.particle_systems) > 0):
for ps in o.particle_systems:
if(ps.settings.maxwell_render.use is not 'NONE'):
t = 'MESH'
no_polygons_meshes.append(o)
# else:
# t = 'EMPTY'
elif(o.type == 'EMPTY'):
# t = 'EMPTY'
if(o.maxwell_render.reference.enabled):
t = 'REFERENCE'
elif(o.maxwell_render.volumetrics.enabled):
t = 'VOLUMETRICS'
else:
pass
elif(o.type == 'CAMERA'):
t = 'CAMERA'
elif(o.type in might_be_renderable):
me = o.to_mesh(self.context.scene, True, 'RENDER', )
if(me is not None):
if(self.use_instances):
if(len(me.polygons) > 0):
if(o.data.users > 1):
if(o in so['convertible_bases']):
t = 'BASE_INSTANCE'
m = me
c = True
c_bases_meshes.append([o, me])
else:
# seems like those meshes are not removed when finished..
c_instance_meshes.append([o, me])
t = 'INSTANCE'
# m = me
m = None
for cbmo, cbmme in c_bases_meshes:
if(cbmo.data == o.data):
m = cbmme
break
if(m is None):
m = me
c = True
else:
t = 'MESH'
m = me
c = True
else:
# pass
bpy.data.meshes.remove(me)
else:
if(len(me.polygons) > 0):
t = 'MESH'
m = me
c = True
else:
bpy.data.meshes.remove(me)
# me = o.to_mesh(self.context.scene, True, 'RENDER', )
# if(me is not None):
# if(len(me.polygons) > 0):
# t = 'MESH'
# m = me
# c = True
# # else:
# # t = 'EMPTY'
# # else:
# # t = 'EMPTY'
elif(o.type == 'LAMP'):
if(o.data.type == 'SUN'):
t = 'SUN'
# else:
# t = 'EMPTY'
return t, m, c
# object hierarchy
def hierarchy():
h = []
def get_object_hierarchy(o):
r = []
for ch in o.children:
t, m, c = export_type(ch)
p = {'object': ch,
'children': get_object_hierarchy(ch),
'export': check_visibility(ch),
'export_type': t,
'mesh': m,
'converted': c,
'parent': o,
'type': ch.type, }
r.append(p)
return r
for ob in objs:
if(ob.parent is None):
t, m, c = export_type(ob)
p = {'object': ob,
'children': get_object_hierarchy(ob),
'export': check_visibility(ob),
'export_type': t,
'mesh': m,
'converted': c,
'parent': None,
'type': ob.type, }
h.append(p)
return h
h = hierarchy()
# print(c_bases_meshes)
# fix the leak, at last!
for _, me in c_instance_meshes:
bpy.data.meshes.remove(me)
# particle instances with hidden bases
class KillRecursiveFunctions(Exception):
pass
def is_psys_instances_marked_to_export(obj):
def walk(o):
for c in o['children']:
walk(c)
ob = o['object']
if(ob == obj):
if(o['export']):
raise KillRecursiveFunctions("here it is!")
try:
for o in h:
walk(o)
except KillRecursiveFunctions:
return True
def is_hidden_base(hob):
for ob in objs:
if(len(ob.particle_systems) > 0):
for ps in ob.particle_systems:
marked = is_psys_instances_marked_to_export(ob)
if(marked):
pset = ps.settings
# check if there are any alive particles
ok = False
for p in ps.particles:
if(p.alive_state == "ALIVE"):
ok = True
break
if(not ok):
# if there are no alive particles, it can't be hidden base because it can't be swapped to one of instances
return False
if(pset.maxwell_render.use == 'PARTICLE_INSTANCES'):
if(pset.render_type in ['OBJECT', 'GROUP', ]):
if(pset.render_type == 'GROUP' and pset.dupli_group is not None):
for do in pset.dupli_group.objects:
if(do == hob):
return True
elif(pset.render_type == 'OBJECT' and pset.dupli_object is not None):
if(pset.dupli_object == hob):
return True
return False
def walk(o):
for c in o['children']:
walk(c)
ob = o['object']
hidden_base = is_hidden_base(ob)
if(hidden_base):
o['export'] = True
o['extra_options'] = {'hidden_base': True, }
for o in h:
walk(o)
# if object is not visible and has renderable children, swap type to EMPTY and mark for export
def renderable_children(o):
r = False
for c in o['children']:
if(c['export'] is True):
r = True
return r
def walk(o):
for c in o['children']:
walk(c)
ob = o['object']
if(o['export'] is False and renderable_children(o)):
o['export_type'] = 'EMPTY'
o['export'] = True
# if(ob.type in might_be_renderable):
# if(o['converted']):
# # log(o, style=LogStyles.ERROR, )
# bpy.data.meshes.remove(o['mesh'])
try:
if(o['converted']):
# log(o, style=LogStyles.ERROR, )
bpy.data.meshes.remove(o['mesh'])
except KeyError:
pass
for o in h:
walk(o)
# mark to remove all redundant empties
append_types = ['MESH', 'BASE_INSTANCE', 'INSTANCE', 'REFERENCE', 'VOLUMETRICS', ]
def check_renderables_in_tree(oo):
ov = []
def walk(o):
for c in o['children']:
walk(c)
if((o['export_type'] in append_types) and o['export'] is True):
# keep instances (Maxwell 3)
# keep: meshes, bases - both with export: True
# (export: False are hidden objects, and should be already swapped to empties if needed for hierarchy)
# > meshes..
# > bases can have children, bases are real meshes
ov.append(True)
else:
# remove: empties, bases, instances, suns, meshes and bases with export: False (hidden objects) and reference enabled: False
# > empties can be removed
# > instances are moved to base level, because with instances hierarchy is irrelevant
# > suns are not objects
# > meshes and bases, see above
ov.append(False)
for o in oo['children']:
walk(o)
# keep empty if it has dupli group assigned
if(oo['object'].dupli_type == 'GROUP'):
if(oo['object'].dupli_group):
return True
if(len(ov) == 0):
# nothing found, can be removed
return False
if(sum(ov) == 0):
# found only object which can be removed
return False
# otherwise always True
return True
def walk(o):
for c in o['children']:
walk(c)
# current object is empty
if(o['export_type'] == 'EMPTY'):
# check all children if there are some renderable one, if so, keep current empty
keep = check_renderables_in_tree(o)
if(keep is False):
# if not, do not export it
o['export'] = False
for o in h:
walk(o)
# split objects to lists
instances = []
meshes = []
empties = []
cameras = []
bases = []
suns = []
references = []
# asset_references = []
volumetrics = []
def walk(o):
for c in o['children']:
walk(c)
if(o['export'] is not False):
# only object marked for export..
if(o['export_type'] == 'MESH'):
meshes.append(o)
elif(o['export_type'] == 'EMPTY'):
empties.append(o)
elif(o['export_type'] == 'CAMERA'):
cameras.append(o)
elif(o['export_type'] == 'BASE_INSTANCE'):
bases.append(o)
elif(o['export_type'] == 'INSTANCE'):
instances.append(o)
elif(o['export_type'] == 'SUN'):
suns.append(o)
elif(o['export_type'] == 'REFERENCE'):
references.append(o)
elif(o['export_type'] == 'VOLUMETRICS'):
volumetrics.append(o)
else:
# objects that will not export/render, remove meshes if they are converted from non-mesh object
if(o['converted']):
me = o['mesh']
# using the EAFP principle, hehe, lazy me..
try:
if(me.users == 0 and not me.use_fake_user):
bpy.data.meshes.remove(me)
except ReferenceError:
# already removed..
pass
for o in h:
walk(o)
self._meshes = meshes
self._bases = bases
self._instances = instances
self._empties = empties
self._cameras = cameras
self._references = references
# self._asset_references = asset_references
self._volumetrics = volumetrics
# no visible camera
if(len(self._cameras) == 0):
log("no visible and active camera in scene!", 2, LogStyles.WARNING)
log("trying to find hidden active camera..", 3)
ac = self.context.scene.camera
if(ac is not None):
# there is one active in scene, try to find it
def walk(o):
for c in o['children']:
walk(c)
ob = o['object']
if(ob == ac):
cam = o
cam['export'] = True
self._cameras.append(cam)
log("found active camera: '{}' and added to scene.".format(cam['object'].name), 3)
for o in h:
walk(o)
# dupliverts / duplifaces
self._duplicates = []
def find_dupli_object(obj):
for o in self._meshes:
ob = o['object']
if(ob == obj):
return o
for o in self._bases:
ob = o['object']
if(ob == obj):
return o
return None
def put_to_bases(o):
if(o not in self._bases and o in self._meshes):
self._meshes.remove(o)
self._bases.append(o)
# duplicate list, because i might modify it while looping it..
class Unique():
# increments its value each time it is accessed or used as string.
# it is overkill a bit, because i can just increment an int after each of its use, but this is good practice and might be useful in future..
def __init__(self, v=0, ):
self.__v = v
@property
def value(self):
v = self.__v
# increment each time it is accessed
self.__v += 1
return v
def __str__(self):
return str(self.value)
unique = Unique()
meshes = self._meshes[:]
for o in meshes:
ob = o['object']
if(ob.dupli_type != 'NONE'):
if(ob.dupli_type == 'FACES' or ob.dupli_type == 'VERTS' or ob.dupli_type == 'GROUP'):
ob.dupli_list_create(self.context.scene, settings='RENDER')
for dli in ob.dupli_list:
do = dli.object
# i've just spent half an hour trying to understand why these lousy matrices does not work
# then suddenly i realized that calling dupli_list_clear might remove them from memory
# and i am just getting some garbage data..
# remember this in future, and do NOT use data after freeing them from memory
dm = dli.matrix.copy()
di = dli.index
io = find_dupli_object(do)
if(self.use_instances):
put_to_bases(io)
if(io is not None):
nm = "{}-duplicator-{}-{}-{}".format(ob.name, io['object'].name, di, unique)
d = {'object': do,
'dupli_name': nm,
'dupli_matrix': dm,
'children': [],
'export': True,
'export_type': 'INSTANCE',
'mesh': io['mesh'],
'converted': False,
# 'parent': None,
'parent': o,
'type': 'MESH', }
self._duplicates.append(d)
ob.dupli_list_clear()
if(len(ob.particle_systems) > 0):
for ps in ob.particle_systems:
pset = ps.settings
if(pset.maxwell_render.use == 'PARTICLE_INSTANCES'):
if(pset.render_type in ['OBJECT', 'GROUP', ]):
mpi = pset.maxwell_render.instances
def process_dupli_list(ob):
ob.dupli_list_create(self.context.scene, settings='RENDER')
for dli in ob.dupli_list:
do = dli.object
dm = dli.matrix.copy()
di = dli.index
io = find_dupli_object(do)
if(self.use_instances and io is not None):
put_to_bases(io)
if(io is not None):
nm = "{}-duplicator-{}-{}-{}".format(ob.name, io['object'].name, di, unique)
d = {'object': do,
'dupli_name': nm,
'dupli_matrix': dm,
'children': [],
'export': True,
'export_type': 'INSTANCE',
'mesh': io['mesh'],
'converted': False,
# 'parent': None,
'extra_options': {'hide': mpi.hide, },
'parent': o,
'type': 'MESH', }
self._duplicates.append(d)
else:
log("{} > {} > {}: instance base object not visible and renderable".format(ob.name, ps.name, 'PARTICLE_INSTANCES'), 2, LogStyles.WARNING, )
ob.dupli_list_clear()
if(pset.render_type == 'GROUP' and pset.dupli_group is not None):
process_dupli_list(ob)
elif(pset.render_type == 'OBJECT' and pset.dupli_object is not None):
process_dupli_list(ob)
else:
log("{} > {} > {}: invalid settings, see 'Maxwell Particle Instances' panel.".format(ob.name, ps.name, 'PARTICLE_INSTANCES'), 2, LogStyles.WARNING, )
else:
# this is user's error, is clearly visible in ui that only OBJECT and GROUP are supported
# no.. report it as well..
log("{} > {} > {}: invalid settings, see 'Maxwell Particle Instances' panel.".format(ob.name, ps.name, 'PARTICLE_INSTANCES'), 2, LogStyles.WARNING, )
empties = self._empties[:]
for o in empties:
ob = o['object']
# check for objects that are exported as empty (eg real empty or mesh without faces), those cas still carry duplis
# or check for meshes with zero faces but vertex duplis
if((ob.dupli_type == 'GROUP' and ob.dupli_group) or (ob.type == 'MESH' and len(ob.data.polygons) == 0 and ob.dupli_type == 'VERTS')):
ob.dupli_list_create(self.context.scene, settings='RENDER')
for dli in ob.dupli_list:
do = dli.object
dm = dli.matrix.copy()
di = dli.index
io = find_dupli_object(do)
if(self.use_instances):
put_to_bases(io)
if(io is not None):
nm = "{}-duplicator-{}-{}-{}".format(ob.name, io['object'].name, di, unique)
d = {'object': do,
'dupli_name': nm,
'dupli_matrix': dm,
'children': [],
'export': True,
'export_type': 'INSTANCE',
'mesh': io['mesh'],
'converted': False,
'parent': o,
'type': 'MESH', }
self._duplicates.append(d)
ob.dupli_list_clear()
# find instances without base and change first one to base, quick and dirty..
# this case happens when object (by name chosen as base) is on hidden layer and marked to be not exported
# also, hope this is the last change of this nasty piece of code..
def find_base_object_name(mnm):
for bo in self._bases:
if(bo['mesh'].name == mnm):
return bo['object'].name
instances2 = self._instances[:]
for o in instances2:
if(find_base_object_name(o['mesh'].name) is None):
o['export_type'] = 'BASE_INSTANCE'
self._bases.append(o)
self._instances.remove(o)
# overriden instances
instances2 = self._instances[:]
for o in instances2:
m = o['object'].maxwell_render
if(m.override_instance):
o['export_type'] = 'MESH'
o['override_instance'] = o['object'].data
self._meshes.append(o)
self._instances.remove(o)
# other objects and modifiers
particles = []
modifiers = []
def walk(o):
for c in o['children']:
walk(c)
if(o['export'] is not False):
ob = o['object']
if(len(ob.particle_systems) != 0):
for ps in ob.particle_systems:
mx = ps.settings.maxwell_render
mod = None
for m in ob.modifiers:
if(m.type == 'PARTICLE_SYSTEM'):
if(m.particle_system == ps):
mod = m
break
if(not mod.show_render):
# not renderable, skip
continue
p = {'object': ps,
'children': [],
'export': mod.show_render,
'export_type': '',
'parent': ob,
'psys': ps,
'type': None, }
p['export_type'] = mx.use
if(mx.use in ['PARTICLES', 'HAIR', ]):
particles.append(p)
# those two should be put into hierarchy, they are real objects.. the rest are just modifiers
o['children'].append(p)
else:
# in case of cloner..
modifiers.append(p)
if(ob.maxwell_render.scatter.enabled):
p = {'object': ob, 'children': [], 'export': True, 'parent': ob, 'type': None, 'export_type': 'SCATTER', }
modifiers.append(p)
if(ob.maxwell_render.subdivision.enabled):
p = {'object': ob, 'children': [], 'export': True, 'parent': ob, 'type': None, 'export_type': 'SUBDIVISION', }
modifiers.append(p)
if(ob.maxwell_render.sea.enabled):