-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLuxBlend_0.1.py
9724 lines (8572 loc) · 516 KB
/
LuxBlend_0.1.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
#!BPY
# -*- coding: utf-8 -*-
# coding=utf-8
"""Registration info for Blender menus:
Name: 'LuxBlend v1.0 Exporter'
Blender: 248
Group: 'Render'
Tooltip: 'Export/Render to LuxRender v0.89dev scene format (.lxs)'
"""
__author__ = "radiance, zuegs, ideasman42, luxblender, dougal2, SATtva, BinaryCortex, LordCrc, jensverwiebe"
__version__ = "1.0"
__url__ = [
"http://www.luxrender.net/",
"http://www.luxrender.net/forum/viewforum.php?f=11",
"http://www.luxrender.net/wiki/index.php/Tutorial_1:_Your_first_scene_%26_render"
]
__bpydoc__ = """\
LuxRender is an open-source rendering system for physically correct, unbiased image synthesis.
This is the Luxrender Blender Export Script.
Useful links:
- For updates: http://www.luxrender.net/forum/viewforum.php?f=11
- For Blender Tutorial: http://www.luxrender.net/wiki/index.php/Tutorial_1:_Your_first_scene_%26_render
Usage:
- Run the script from the render menu.
- Set the default location of the Luxrender.exe.
Please check the lux tutorials & forums for more information.
"""
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
# --------------------------------------------------------------------------
# LuxBlend v1.0 exporter
# --------------------------------------------------------------------------
#
# Authors and contributors:
# radiance, zuegs, ideasman42, luxblender, dougal2, SATtva, BinaryCortex,
# zukazuka, Qantorisc, zsouthboy, jensverwiebe, LordCrc
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------
######################################################
# Importing modules
######################################################
import math
import time
import os
import sys as osys
import types
import subprocess
import Blender
from Blender import Mesh, Scene, Object, Material, Modifier, Texture, Window, sys, Draw, BGL, Mathutils, Lamp, Image, Particle, Curve
import struct
# critical export function profiling
if False:
import hotshot, hotshot.stats
def profileit(printlines=1):
def _my(func):
def _func(*args, **kargs):
prof = hotshot.Profile("profiling.data")
res = prof.runcall(func, *args, **kargs)
prof.close()
stats = hotshot.stats.load("profiling.data")
stats.strip_dirs()
stats.sort_stats('time', 'calls')
print ">>>---- Begin profiling print for %s" % func.__name__
stats.print_stats(printlines)
print ">>>---- End profiling print"
return res
return _func
return _my
else:
def profileit(arg=None):
def _my(func):
return func
return _my
######################################################
# Functions
######################################################
# New name based on old with a different extension
def newFName(ext):
return Blender.Get('filename')[: -len(Blender.Get('filename').split('.', -1)[-1]) ] + ext
# some helpers
def luxstr(str):
# export os independent paths
return str.replace("\\\\", "\\").replace("\\", "/")
### relpath ##########################
def relpath(base, target):
if target[0:2] == "\\\\" or target[0:2] == "//":
return target[2:len(target)]
if not os.path.isabs(base):
base = os.path.abspath(base)
if not os.path.isabs(target):
target = os.path.abspath(target)
if os.sep == "\\":
base = os.path.normcase(base)
target = os.path.normcase(target)
if base == os.sep:
return '.' + target
baselist = base.split(os.sep)
if baselist[-1] == "":
baselist = baselist[:-1]
targetlist = target.split(os.sep)
i = 0
top = min([len(baselist), len(targetlist)])
while i < top and baselist[i] == targetlist[i]:
i+=1
if i == 0:
return os.sep.join(targetlist)
if i == len(baselist):
return os.sep.join(targetlist[i:])
else:
return ('..' + os.sep) * (len(baselist) - i) + os.sep.join(targetlist[i:])
### luxFilePath #####################
lxs_filename = ""
previewing = False
def luxFilePath(filename):
global lxs_filename, previewing
scn = Scene.GetCurrent()
pm = luxProp(scn, "pathmode", "absolute").get()
if (pm=="absolute") or previewing: # absolute paths (the old / default mode)
return filename
elif pm=="relative": # relative paths
base = os.path.dirname(lxs_filename)
return relpath(base, filename)
elif pm=="flat": # flat mode - only filename
return os.path.basename(filename)
###### RGC ##########################
def rg(col):
scn = Scene.GetCurrent()
if luxProp(scn, "RGC", "true").get()=="true":
gamma = luxProp(scn, "film.gamma", 2.2).get()
else:
gamma = 1.0
ncol = col**gamma
if luxProp(scn, "colorclamp", "false").get()=="true":
ncol = ncol * 0.9
if ncol > 0.9:
ncol = 0.9
if ncol < 0.0:
ncol = 0.0
return ncol
def texturegamma():
scn = Scene.GetCurrent()
if luxProp(scn, "RGC", "true").get()=="true":
return luxProp(scn, "film.gamma", 2.2).get()
else:
return 1.0
def exportMaterial(mat):
str = "# Material '%s'\n" %mat.name
return str+luxMaterial(mat)+"\n"
def exportMaterialGeomTag(mat):
return "%s\n"%(luxProp(mat, "link", "").get())
# generate and attach a permanent UID to the scene if there isn't any
def luxGenUID(scn):
global luxUID
guid = luxProp(scn, 'UID', '')
g = guid.get()
if (not Blender.Get('filename') and not luxUID) or not g:
print 'Lux scene UID is missing. Generating a new one...'
try:
import hashlib
h = hashlib.sha1
except ImportError:
try:
import sha
h = sha.new
except ImportError:
h = hash
try:
r = os.urandom(20)
except NotImplementedError:
import random
r = str(random.getrandbits(160))
g = h(str(sys.time())+'|'+r)
try: g = g.hexdigest()
except: g = hex(g)[2:]
print 'Generated UID:', g, "\n"
guid.set(g)
return g
def bitmask(n, max=20):
bits = []
for i in range(max-1, -1, -1):
v = pow(2, i)
if n < v:
continue
else:
n = n - v
bits.insert(0, i+1)
return bits
################################################################
dummyMat = 2394723948 # random identifier for dummy material
clayMat = None
#-------------------------------------------------
# getMaterials(obj)
# helper function to get the material list of an object in respect of obj.colbits
#-------------------------------------------------
def getMaterials(obj, compress=False):
if not obj.type in ['Mesh', 'Curve', 'Surf', 'Text', 'MBall']:
return []
global clayMat
mats = [None]*16
colbits = obj.colbits
objMats = obj.getMaterials(1)
data = obj.getData(mesh=1)
try:
dataMats = data.materials
except:
try:
dataMats = data.getMaterials(1)
except:
try:
dataMats = Curve.Get(obj.getData().getName()).getMaterials()
except:
dataMats = []
colbits = 0xffff
m = max(len(objMats), len(dataMats))
if m>0:
objMats.extend([None]*16)
dataMats.extend([None]*16)
for i in range(m):
if (colbits & (1<<i) > 0):
mats[i] = objMats[i]
else:
mats[i] = dataMats[i]
if compress:
mats = [m for m in mats if m]
slots = [m for m in mats if m]
if m==0 or not slots:
print("Warning: object %s has no material assigned" % (obj.getName()))
mats = []
# clay option
if luxProp(Scene.GetCurrent(), "clay", "false").get()=="true":
if clayMat==None:
clayMat = Material.New("lux_clayMat")
resetMatTex(clayMat)
# resetting clay material to diffuse 0.6
luxProp(clayMat, 'type', '').set('matte')
luxProp(clayMat, ':Kd', '').set(' '.join([str(rg(0.6))]*3))
for i in range(len(mats)):
if mats[i]:
mattype = luxProp(mats[i], "type", "").get()
if (mattype not in ["portal","light","boundvolume"]): mats[i] = clayMat
if not mats and clayMat is not None: mats.append(clayMat)
return mats
#-------------------------------------------------
# getModifiers(obj)
# returns modifiers stack and modifiers settings of an object
# (modifier rendering parameter is honored)
#-------------------------------------------------
def getModifiers(obj):
stack = []
s = []
for mod in obj.modifiers:
if not mod[Modifier.Settings.RENDER]: continue
for k in Modifier.Settings.keys():
try:
v = mod[getattr(Modifier.Settings, k)]
s.append(k+'='+str(v))
except KeyError:
pass
stack.append([mod.type, s])
return str(stack) if len(stack) else ''
######################################################
# luxExport class
######################################################
class luxExport:
#-------------------------------------------------
# __init__
# initializes the exporter object
#-------------------------------------------------
def __init__(self, scene, master_progress):
self.scene = scene
self.camera = scene.objects.camera
self.objects = []
self.portals = []
self.volumes = []
self.namedVolumes = []
self.hair = {'obj':{}, 'motion':{}}
self.meshes = {}
self.instances = {} # only for instances with quirks: redefined materials and modifiers
self.groups = {}
self.materials = []
self.lights = []
self.duplis = set()
self.mpb = master_progress
#-------------------------------------------------
# analyseObject(self, obj, matrix, name)
# called by analyseScene to build the lists before export
#-------------------------------------------------
def analyseObject(self, obj, matrix, name, isOriginal=True, isDupli=False):
light = False
export_emitter = False
export_emitter_mats = False
if obj.users != 0: # Blender quirk: could go negative with large amount of instances
obj_type = obj.getType()
psystems = obj.getParticleSystems()
for psys in psystems:
if ( (psys.type == Particle.TYPE['EMITTER'] or psys.type == Particle.TYPE['REACTOR']) and psys.drawAs == Particle.DRAWAS['OBJECT']):
if psys.renderEmitter: export_emitter = True
dup_obj = psys.duplicateObject
self.duplis.add(dup_obj)
obj_matrix = dup_obj.getMatrix()
obj_translation_vec = obj_matrix.translationPart()
obj_rotation_scale_mat = obj_matrix.rotationPart() # This gets a 3D submatrix with the rotation AND scale parts.
locs = psys.getLoc()
scales = psys.getSize()
rots = psys.getRot()
try:
if(len(locs) != len(scales) or len(locs) != len(rots)):
print("ERROR: Please bake particle systems before rendering")
Draw.PupMenu("ERROR: Please bake particle systems before rendering%t|OK%x1")
break
except TypeError:
break
for i in range(len(locs)) :
part_rotation_quat = Mathutils.Quaternion(rots[i])
part_rotation_mat = part_rotation_quat.toMatrix()
rotation_scale_mat = obj_rotation_scale_mat * part_rotation_mat * scales[i]
# If dup_obj is translated, the particles are translated by the same amount but
# the direction is rotated by the particle rotation. If dup_obj is rotated, that rotation
# does not affect the translation. I know it's a bit odd, but that's the way Blender does it
# and so that's why the order of the matrix multiplications is like this.
translation_vec = Mathutils.Vector(locs[i]) + part_rotation_quat*obj_translation_vec
translation_mat = Mathutils.TranslationMatrix(translation_vec)
rotation_scale_mat.resize4x4()
# Translation must be last because of the way the rotations and translations are encoded in 4D matrices.
#combined_matrix = scale_matrix*rotation_mat*translation_mat
combined_matrix = rotation_scale_mat*translation_mat
#print "combined_matrix = ", combined_matrix
self.analyseObject(dup_obj, combined_matrix, "%s.%s"%(obj.getName(), dup_obj.getName()), False, True)
#if self.analyseObject(dup_obj, combined_matrix, "%s.%s"%(obj.getName(), dup_obj.getName()), True, True): light = True
elif psys.type == Particle.TYPE['HAIR'] and psys.drawAs == Particle.DRAWAS['PATH']:
if psys.renderEmitter: export_emitter = True
if not obj in self.hair['obj']: self.hair['obj'][obj] = []
try:
if not psys.getName() in self.hair['obj'][obj]: self.hair['obj'][obj].append(psys.getName())
except AttributeError:
print 'ERROR: Installed version of Blender does not properly supports hair particles'
print ' export. Please use this version of LuxBlend with Blender 2.49b only.'
if osys.platform == 'win32':
print ' Important note for users of Blender 2.49b on Windows systems: if you'
print ' received this message, then you\'re using an inappropriate build of'
print ' Blender program. You can find the correct version build in blender.org'
print ' download section in a *zip archive* (not in an installer!).'
print
Draw.PupMenu('ERROR: Blender version does not properly supports hair export (see console for details)%t|OK%x1')
break
if not psys.renderEmitter:
export_emitter_mats = True
elif psys.drawAs == Particle.DRAWAS['GROUP']:
if psys.renderEmitter: export_emitter = True
grpObjs = obj.DupObjects
grpObjName = obj.name
for i in grpObjs:
o = i[0]
m = i[1]
# Prefix the name of all particle objects with "luxGroupParticle".
self.analyseObject(o, m, "%s.%s"%("luxGroupParticle", grpObjName), False, True)
else:
print "Unknown particle type for particle system [" + obj.name + "]."
if (obj.enableDupFrames and isOriginal):
for o, m in obj.DupObjects:
light = self.analyseObject(o, m, "%s.%s"%(name, o.getName()), False)
if (obj.enableDupGroup or obj.enableDupVerts or obj.enableDupFaces):
self.duplis.add(obj)
for o, m in obj.DupObjects:
if not o.restrictRender and not isDupli:
if obj.enableDupGroup:
objGroups = []
for g in Blender.Group.Get():
if o in g.objects: objGroups.append(g)
if not objGroups or not True in [ l in o.layers for l in self.groups[g] for g in objGroups ]:
continue
light = self.analyseObject(o, m, "%s.%s"%(name, o.getName()), True, True)
elif ((isDupli or (not obj.getParent() in self.duplis)) and ((obj_type == "Mesh") or (obj_type == "Surf") or (obj_type == "Curve") or (obj_type == "Text"))):
if (len(psystems) == 0) or export_emitter or export_emitter_mats:
mats = getMaterials(obj)
if (len(mats)>0) and (mats[0]!=None) and ((mats[0].name=="PORTAL") or (luxProp(mats[0], "type", "").get()=="portal")):
self.portals.append([obj, matrix])
elif (len(mats)>0) and (luxProp(mats[0], "type", "").get()=="boundvolume"):
self.volumes.append([obj, matrix])
else:
for mat in mats:
if (mat!=None) and (mat not in self.materials):
self.materials.append(mat)
# collect used named volumes ids
for volume_prop in ['Exterior', 'Interior']:
if luxProp(mat, '%s_vol_used'%(volume_prop), 'false').get() == 'true':
volumeId = luxProp(mat, '%s_vol_id' % (volume_prop), 0).get()
if volumeId not in self.namedVolumes:
self.namedVolumes.append(volumeId)
if (mat!=None) and ((luxProp(mat, "type", "").get()=="light") or (luxProp(mat, "emission", "false").get()=="true")) \
and luxProp(Scene.GetCurrent(), "lightgroup.disable."+luxProp(mat, "light.lightgroup", "default").get(), "false").get() != "true":
light = True
if len(psystems) == 0 or export_emitter:
mesh_name = obj.getData(name_only=True)
try:
self.meshes[mesh_name] += [obj]
except KeyError:
self.meshes[mesh_name] = [obj]
self.objects.append([obj, matrix])
elif (obj_type == "Lamp"):
ltype = obj.getData(mesh=1).getType() # data
if (ltype == Lamp.Types["Lamp"]) or (ltype == Lamp.Types["Spot"]) or (ltype == Lamp.Types["Area"]):
if luxProp(Scene.GetCurrent(), "lightgroup.disable."+luxProp(obj, "light.lightgroup", "default").get(), "false").get() != "true":
# collect used named volumes ids
for volume_prop in ['Exterior']:
if luxProp(obj, '%s_vol_used'%(volume_prop), 'false').get() == 'true':
volumeId = luxProp(obj, '%s_vol_id' % (volume_prop), 0).get()
if volumeId not in self.namedVolumes:
self.namedVolumes.append(volumeId)
self.lights.append([obj, matrix])
light = True
return light
#-------------------------------------------------
# analyseScene(self)
# this function builds the lists of object, lights, meshes and materials before export
#-------------------------------------------------
def analyseScene(self):
light = False
for g in Blender.Group.Get():
# caching groups layers
self.groups[g] = bitmask(g.layers)
for obj in self.scene.objects:
if ((obj.Layers & self.scene.Layers) > 0) and not obj.restrictRender:
if self.analyseObject(obj, obj.getMatrix(), obj.getName()): light = True
return light
#-------------------------------------------------
# exportInstanceObjName(self, mesh_name, matId, shapeId)
# format instanced material-separated mesh name
#-------------------------------------------------
def exportInstanceObjName(self, mesh_name, matId=None, shapeId=None):
s = mesh_name
if matId is None and shapeId is None:
return s
s += ':luxInstancedObj'
if matId is not None: s += ':matId%s' % matId
if shapeId is not None: s += ':shapeId%s' % shapeId
return s
#-------------------------------------------------
# exportMaterialLink(self, file, mat)
# exports material link. LuxRender "Material"
#-------------------------------------------------
def exportMaterialLink(self, file, mat):
if mat == dummyMat:
file.write("\tMaterial \"matte\" # dummy material\n")
else:
file.write("\t%s"%exportMaterialGeomTag(mat)) # use original methode
#-------------------------------------------------
# exportMaterial(self, file, mat)
# exports material. LuxRender "Texture"
#-------------------------------------------------
def exportMaterial(self, file, mat):
#print("material %s"%(mat.getName()))
file.write("\t%s"%exportMaterial(mat)) # use original methode
#-------------------------------------------------
# exportMaterials(self, file)
# exports materials to the file
#-------------------------------------------------
def exportMaterials(self, file):
#pb = exportProgressBar(len(self.materials), self.mpb)
for mat in self.materials:
#pb.counter('Exporting Materials')
self.exportMaterial(file, mat)
#-------------------------------------------------
# exportNamedVolumes(self, file)
# exports named volumes to the file
#-------------------------------------------------
def exportNamedVolumes(self, file):
#pb = exportProgressBar(len(self.namedVolumes), self.mpb)
output = ''
volumes = listNamedVolumes()
for linked, new in importedVolumeIdsTranslation.items():
if linked in self.namedVolumes:
self.namedVolumes.remove(linked)
self.namedVolumes.append(new)
for volume in volumes.values():
if volume in self.namedVolumes:
#pb.counter('Exporting Mediums Definitions')
data = getNamedVolume(volume)
output = "\t# Volume '%s'\n" % data['name']
tex = luxNamedVolumeTexture(volume)
output += "%s\nMakeNamedVolume \"%s\" %s" % (tex[0], data['name'], tex[1])
output += "\n\n"
file.write(output)
#-------------------------------------------------
# exportHairSystems(self, file)
# collects hair particles and exports hair systems
# primitives to the file
#-------------------------------------------------
def exportHairSystems(self, file):
#pb = exportProgressBar(len(self.hair), self.mpb)
clay_export = (luxProp(self.scene, 'clay', 'false').get() != 'true')
ob_moblur = (luxProp(self.camera.data, 'objectmblur', 'true').get() == 'true' and luxProp(self.camera.data, 'usemblur', 'false').get() == 'true')
frame = Blender.Get('curframe')
for obj, obj_psystems in self.hair['obj'].items():
#pb.counter('Exporting Hair Particles')
for psys in obj.getParticleSystems():
psysname = psys.getName()
if not psysname in obj_psystems: continue
if clay_export:
mat = psys.getMat() or dummyMat
else:
mat = getMaterials(obj, True)[0]
size = luxProp(mat, 'hair_thickness', 0.5).get() * luxScaleUnits('hair_thickness', 'mm', mat)
legname = '%s:%s:luxHairPrimitive:leg' % (obj.name, psysname)
jointname = '%s:%s:luxHairPrimitive:joint' % (obj.name, psysname)
primitives = {
legname: "\tShape \"cylinder\" \"float radius\" %f \"float zmin\" 0.0 \"float zmax\" 1.0\n" % (0.5*size),
jointname: "\tShape \"sphere\" \"float radius\" %f\n" % (0.5*size)
}
# exporting primitive objects
for name, shape in primitives.items():
file.write("ObjectBegin \"%s\"\n" % name)
self.exportMaterialLink(file, mat)
file.write(shape)
file.write("ObjectEnd # %s\n\n" % name)
# collecting segment objects (instanced)
self.luxCollectHairObjs(psys, jointname, legname, size)
if ob_moblur:
# to make motion blur work we must also get transform matrices from the following frame
Blender.Set('curframe', frame+1)
self.luxCollectHairObjs(psys, jointname, legname, size, True)
Blender.Set('curframe', frame)
# removing psys from the list to avoid multiple exports
self.hair['obj'][obj].remove(psysname)
# collect hair strand segment objects/matrices pairs
def luxCollectHairObjs(self, psys, jointname, legname, size, motion=False):
# matrix check helper function
def matrixHasNaN(m):
for i in range(len(m)):
for v in m[i]:
if type(v) is not float and matrixHasNaN(v): return True
elif str(v) == 'nan': return True
return False
# it seams to be a bug in Blender Python API here -- if an object
# has more than one particle system, then beginning from the
# second system the call to Particles.getLoc() results in an empty
# list for the first time
segmentsLoc = psys.getLoc()
segmentsLoc = psys.getLoc() # sic
for i, strand in enumerate(segmentsLoc):
for j in range(0, len(strand)*2-1):
j_over_2 = j/2
if j%2 == 0:
name = jointname
matrix = Mathutils.Matrix([1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [strand[j_over_2][0], strand[j_over_2][1], strand[j_over_2][2], 1.0])
else:
name = legname
m = self.getHairSegmentTransform(strand[j_over_2], strand[j_over_2+1])
matrix = Mathutils.Matrix(m[0], m[1], m[2], m[3])
# check to cull out point-sized strands
if j == 1 and not motion and matrixHasNaN(matrix[:3]) is True:
self.objects.pop()
break
obj = self.luxHair('%s_strand%s_segment%s' % (name,i,j), name)
if not motion:
self.objects.append([obj, matrix])
try:
self.instances[name]['obj_mods'][''] = ['luxHair']
except KeyError:
self.instances[name] = {'obj_mods': {'': ['luxHair']}}
else:
self.hair['motion'][obj] = matrix
# minimalistic Blender-like object for holding strand obj properties
class luxHair:
def __init__(self, objName, parentName):
self.objName = objName
self.parentName = parentName
def __cmp__(self, other):
return cmp(self.__repr__(), other.__repr__())
def __hash__(self):
return hash(self.__repr__())
def __repr__(self):
return '[Object "%s"]' % self.objName
def __str__(self):
return self.__repr__()
def getData(self, **args):
return self.parentName
def getName(self):
return self.objName
# hair export helper function (by e_jackson)
def getHairSegmentTransform(self, p1, p2):
"""
This function selects an orthogonal basis V_1 = (p2-p1), V_2, V_3 such that V_2 and V_3
have unit length and calculates a transformation matrix from standard orthnormal basis
to the selected one.
Arguments:
p1, p2 == coordinate triples of beginning and end points of a vector
Returns:
string which represents transformation matrix in a format compatible with Luxrender SDL
"""
# standard orthonormal basis
Standard_basis = [Mathutils.Vector(1.0, 0.0, 0.0), Mathutils.Vector(0.0, 1.0, 0.0), Mathutils.Vector(0.0, 0.0, 1.0)]
V = [(), (), ()]
V[2] = Mathutils.Vector(p2) - Mathutils.Vector(p1)
# we choose an ort which corresponds to the smallest absolute value of coordinate in V[0]
W = Standard_basis[0]
Length = abs(V[2].x)
for Node in zip([1, 2], [abs(V[2].y), abs(V[2].z)]) :
if Node[1] < Length :
Length = Node[1]
W = Standard_basis[Node[0]]
V[1] = V[2].cross(W)
V[1].normalize()
V[0] = V[1].cross(V[2])
V[0].normalize()
# transition matrix from standard basis to V
M = Mathutils.Matrix(V[0], V[1], V[2])
Result = []
for Count in range(3) :
Result.append([M[Count][0], M[Count][1], M[Count][2], 0.0])
Result.append([p1[0], p1[1], p1[2], 1.0])
return Result
#-------------------------------------------------
# getMeshParams(self, mat, instancedMats)
# returns mesh parameters as string
#-------------------------------------------------
def getMeshParams(self, mat, instancedMats=None):
scn = Scene.GetCurrent()
if mat != dummyMat and not instancedMats:
usesubdiv = luxProp(mat, "subdiv", "false")
usedisp = luxProp(mat, "dispmap", "false")
sharpbound = luxProp(mat, "sharpbound", "false")
nsmooth = luxProp(mat, "nsmooth", "true")
sdoffset = luxProp(mat, "sdoffset", 0.0)
usemicrodisp = luxProp(mat, "usemicrodisp", "false")
dmscale = luxProp(mat, "dmscale", 0.0)
dmoffset = luxProp(mat, "dmoffset", 0.0)
dstr = ""
if usemicrodisp.get() == "true":
nsubdivlevels = luxProp(mat, "nsubdivlevels", 50)
dstr += "\"string subdivscheme\" [\"microdisplacement\"] \"bool dmnormalsmooth\" [\"false\"] \"integer nsubdivlevels\" [%i] \"string displacementmap\" [\"%s::displacementmap\"] \"float dmscale\" [%f] \"float dmoffset\" [%f]"% (nsubdivlevels.get(), mat.getName(), dmscale.get(), dmoffset.get())
else:
if usesubdiv.get() == "true":
nlevels = luxProp(mat, "sublevels", 1)
dstr += "\"string subdivscheme\" [\"loop\"] \"integer nsubdivlevels\" [%i] \"bool dmnormalsmooth\" [\"%s\"] \"bool dmsharpboundary\" [\"%s\"]"% (nlevels.get(), nsmooth.get(), sharpbound.get())
if usedisp.get() == "true":
dstr += "\"string displacementmap\" [\"%s::dispmap.scale\"] \"float dmscale\" [-1.0] \"float dmoffset\" [%f]"%(mat.getName(), sdoffset.get()) # scale is scaled in texture
return dstr
return ""
#-------------------------------------------------
# exportMesh(self, file, mesh, mats, name, portal, instancedMats, instancedShapes)
# exports mesh to the file without any optimization
# os.path.join(filepath, filebase + "-geom.lxo")
#-------------------------------------------------
def exportMesh(self, file, mesh, mats, name, portal=False, instancedMats=None, instancedShapes=None):
export_ply = luxProp(scn, "export_ply", "true").get()
filepath = luxProp(scn, "curFilePath", "").get()
#print(" exporting mesh")
if mats == []:
mats = [dummyMat]
usedmats = [f.mat for f in mesh.faces]
i = 0
for matIndex in range(len(mats)):
if not matIndex in usedmats:
continue
if not(portal):
mat = mats[matIndex]
if not mat:
mat = dummyMat
if instancedMats:
file.write("ObjectBegin \"%s\"\n" % self.exportInstanceObjName(instancedMats, i, instancedShapes))
self.exportMaterialLink(file, mat)
mesh_str = self.getMeshParams(mats[matIndex], instancedMats)
if (export_ply == "true") and not(portal):
file.write("\tShape \"plymesh\" %s \n"% mesh_str)
else:
if not(portal):
file.write("\tShape \"mesh\" %s \"integer triindices\" [\n"% mesh_str)
else:
file.write("\tPortalShape \"mesh\" %s \"integer triindices\" [\n"% mesh_str)
if (export_ply == "true") and not(portal):
sceneName = luxProp(scn, "sceneName", "").get()
filename = sceneName + "-" + name + "-mat" + str(matIndex) + ".ply"
skip_ply = luxProp(scn, "skip_ply", "false").get()
if (skip_ply == "false"):
plyExport(filepath, filename, mesh, matIndex)
file.write("\t\"string filename\" [\"%s\"]\n"% filename)
else:
index = 0
ffaces = [f for f in mesh.faces if f.mat == matIndex]
for face in ffaces:
file.write("%d %d %d\n"%(index, index+1, index+2))
if (len(face)==4):
file.write("%d %d %d\n"%(index, index+2, index+3))
index += len(face.verts)
file.write("\t] \"point P\" [\n")
for face in ffaces:
for vertex in face:
file.write("%f %f %f\n"% tuple(vertex.co))
file.write("\t] \"normal N\" [\n")
for face in ffaces:
normal = face.no
for vertex in face:
if (face.smooth):
normal = vertex.no
file.write("%f %f %f\n"% tuple(normal))
if (mesh.faceUV):
file.write("\t] \"float uv\" [\n")
# Check if there is a render specific UV layer and make it active for export.
activeUVLayer_orig = mesh.activeUVLayer
renderUVLayer = mesh.renderUVLayer
if renderUVLayer != activeUVLayer_orig:
mesh.activeUVLayer = renderUVLayer
for face in ffaces:
for uv in face.uv:
file.write("%f %f\n"% tuple(uv))
# If we changed the active UV layer: reset it to the original.
if renderUVLayer != activeUVLayer_orig:
mesh.activeUVLayer = activeUVLayer_orig
file.write("\t]\n")
if instancedMats:
file.write("ObjectEnd # %s\n\n" % self.exportInstanceObjName(instancedMats, i, instancedShapes))
i += 1
#-------------------------------------------------
# exportMeshOpt(self, file, mesh, mats, name, portal, optNormals, instancedMats, instancedShapes)
# exports mesh to the file with optimization.
# portal: export without normals and UVs
# optNormals: speed and filesize optimization, flat faces get exported without normals
#-------------------------------------------------
def exportMeshOpt(self, file, mesh, mats, name, portal=False, optNormals=True, instancedMats=None, instancedShapes=None):
#print(" exporting optimized mesh")
shapeList, smoothFltr, shapeText = [0], [[0,1]], [""]
if portal:
normalFltr, uvFltr, shapeText = [0], [0], ["portal"] # portal, no normals, no UVs
else:
uvFltr, normalFltr, shapeText = [1], [1], ["mixed with normals"] # normals and UVs
if optNormals: # one pass for flat faces without normals and another pass for smoothed faces with normals, all with UVs
shapeList, smoothFltr, normalFltr, uvFltr, shapeText = [0,1], [[0],[1]], [0,1], [1,1], ["flat w/o normals", "smoothed with normals"]
if mats == []:
mats = [dummyMat]
usedmats = [f.mat for f in mesh.faces]
i = 0
# Check if there is a render specific UV layer and make it active for export.
activeUVLayer_orig = mesh.activeUVLayer
renderUVLayer = mesh.renderUVLayer
if renderUVLayer != activeUVLayer_orig:
mesh.activeUVLayer = renderUVLayer
for matIndex in range(len(mats)):
if not matIndex in usedmats:
continue
if not(portal):
mat = mats[matIndex]
if not mat:
mat = dummyMat
if instancedMats:
file.write("ObjectBegin \"%s\"\n" % self.exportInstanceObjName(instancedMats, i, instancedShapes))
self.exportMaterialLink(file, mat)
for shape in shapeList:
blenderExportVertexMap = []
exportVerts = []
exportFaces = []
ffaces = [f for f in mesh.faces if (f.mat == matIndex) and (f.smooth in smoothFltr[shape])]
for face in ffaces:
exportVIndices = []
index = 0
for vertex in face:
# v = [vertex.co[0], vertex.co[1], vertex.co[2]]
v = [vertex.co]
if normalFltr[shape]:
if (face.smooth):
# v.extend(vertex.no)
v.append(vertex.no)
else:
# v.extend(face.no)
v.append(face.no)
if (uvFltr[shape]) and (mesh.faceUV):
# v.extend(face.uv[index])
v.append(face.uv[index])
blenderVIndex = vertex.index
newExportVIndex = -1
length = len(v)
if (blenderVIndex < len(blenderExportVertexMap)):
for exportVIndex in blenderExportVertexMap[blenderVIndex]:
v2 = exportVerts[exportVIndex]
if (length==len(v2)) and (v == v2):
newExportVIndex = exportVIndex
break
if (newExportVIndex < 0):
newExportVIndex = len(exportVerts)
exportVerts.append(v)
while blenderVIndex >= len(blenderExportVertexMap):
blenderExportVertexMap.append([])
blenderExportVertexMap[blenderVIndex].append(newExportVIndex)
exportVIndices.append(newExportVIndex)
index += 1
exportFaces.append(exportVIndices)
if (len(exportVerts)>0):
mesh_str = self.getMeshParams(mats[matIndex], instancedMats)
if portal:
file.write("\tPortalShape \"mesh\" %s \"integer triindices\" [\n"% mesh_str)
else:
file.write("\tShape \"mesh\" %s \"integer triindices\" [\n"% mesh_str)
for face in exportFaces:
file.write("%d %d %d\n"%(face[0], face[1], face[2]))
if (len(face)==4):
file.write("%d %d %d\n"%(face[0], face[2], face[3]))
file.write("\t] \"point P\" [\n")
# for vertex in exportVerts:
# file.write("%f %f %f\n"%(vertex[0], vertex[1], vertex[2]))
file.write("".join(["%f %f %f\n"%tuple(vertex[0]) for vertex in exportVerts]))
if normalFltr[shape]:
file.write("\t] \"normal N\" [\n")
# for vertex in exportVerts:
# file.write("%f %f %f\n"%(vertex[3], vertex[4], vertex[5]))
file.write("".join(["%f %f %f\n"%tuple(vertex[1]) for vertex in exportVerts]))
if (uvFltr[shape]) and (mesh.faceUV):
file.write("\t] \"float uv\" [\n")
# for vertex in exportVerts:
# file.write("%f %f\n"%(vertex[6], vertex[7]))
file.write("".join(["%f %f\n"%tuple(vertex[2]) for vertex in exportVerts]))
else:
if (uvFltr[shape]) and (mesh.faceUV):
file.write("\t] \"float uv\" [\n")
# for vertex in exportVerts:
# file.write("%f %f\n"%(vertex[3], vertex[4]))
file.write("".join(["%f %f\n"%tuple(vertex[1]) for vertex in exportVerts]))
file.write("\t]\n")
if instancedMats:
file.write("ObjectEnd # %s\n\n" % self.exportInstanceObjName(instancedMats, i, instancedShapes))
#print(" shape(%s): %d vertices, %d faces"%(shapeText[shape], len(exportVerts), len(exportFaces)))
i += 1
# If we changed the active UV layer: reset it to the original.
if renderUVLayer != activeUVLayer_orig:
mesh.activeUVLayer = activeUVLayer_orig
#-------------------------------------------------
# exportMeshes(self, file)
# exports meshes that uses instancing (meshes that are used by at least "instancing_threshold" objects)
#-------------------------------------------------
def exportMeshes(self, file):
scn = Scene.GetCurrent()
instancing_threshold = luxProp(scn, "instancing_threshold", 2).get()
mesh_optimizing = luxProp(scn, "mesh_optimizing", "true")
mesh = Mesh.New('')
#pb = exportProgressBar(len(self.meshes), self.mpb)
for (mesh_name, objs) in self.meshes.items():
#pb.counter('Exporting Meshes')
self.instances[mesh_name] = {'obj_mats':{}, 'obj_vols':{}, 'obj_mods':{}}
allow_instancing = True
mats = getMaterials(objs[0])
for mat in mats: # don't instance if one of the materials is emissive
if (mat!=None) and (luxProp(mat, "type", "").get()=="light"):
allow_instancing = False
for obj in objs:
obj_mats = getMaterials(obj)
obj_name = obj.getName()
if obj_mats != mats:
obj_mats_used = getMaterials(obj, True)
# if an instance overrides mesh's materials, copy them
self.instances[mesh_name]['obj_mats'][obj_name] = obj_mats_used
# lets not forget volume definitions in overridden materials
self.instances[mesh_name]['obj_vols'][obj_name] = {}
for obj_mat in obj_mats_used:
self.instances[mesh_name]['obj_vols'][obj_name][obj_mat.name] = {}
for volume_prop in ['Exterior', 'Interior']:
if luxProp(obj_mat, '%s_vol_used'%(volume_prop), 'false').get() == 'true':
volId = luxProp(obj_mat, '%s_vol_id' % (volume_prop), 0).get()
else:
volId = ''
self.instances[mesh_name]['obj_vols'][obj_name][obj_mat.name][volume_prop] = volId
if not 'mesh_mats' in self.instances[mesh_name]:
self.instances[mesh_name]['mesh_mats'] = getMaterials(objs[0])
self.instances[mesh_name]['mesh_mats_used'] = getMaterials(objs[0], True)
obj_mods = getModifiers(obj)
# collect modifier configurations to export all possible shapes later
try:
self.instances[mesh_name]['obj_mods'][obj_mods].append(obj_name)
except KeyError:
self.instances[mesh_name]['obj_mods'][obj_mods] = [obj_name]
if allow_instancing and (len(objs) >= instancing_threshold):
del self.meshes[mesh_name]
j = 0 if len(self.instances[mesh_name]['obj_mods']) > 1 else None
for shape in self.instances[mesh_name]['obj_mods'].values():
mesh.getFromObject(shape[0], 0, 1)
#print("blender-mesh: %s (%d vertices, %d faces)"%(mesh_name, len(mesh.verts), len(mesh.faces)))
if not self.instances[mesh_name].has_key('mesh_mats_used'):
file.write("ObjectBegin \"%s\"\n" % self.exportInstanceObjName(mesh_name, shapeId=j))
if (mesh_optimizing.get() == "true"):
self.exportMeshOpt(file, mesh, mats, mesh_name, instancedShapes=j)
else:
self.exportMesh(file, mesh, mats, mesh_name, instancedShapes=j)
file.write("ObjectEnd # %s\n\n" % self.exportInstanceObjName(mesh_name, shapeId=j))
else:
if (mesh_optimizing.get() == "true"):
self.exportMeshOpt(file, mesh, mats, mesh_name, instancedMats=mesh_name, instancedShapes=j)
else:
self.exportMesh(file, mesh, mats, mesh_name, instancedMats=mesh_name, instancedShapes=j)
if j is not None: j += 1
mesh.verts = None
#-------------------------------------------------
# exportObjects(self, file)
# exports objects to the file
#-------------------------------------------------
def exportObjects(self, file):
# write transformation matrix without losing float precision
def preciseMatrix(m):
l = []
for i in range(len(m)):
for v in m[i]:
if type(v) is not float: l.append(preciseMatrix(v))
elif abs(v) in [0, 1.0]: l.append('%0.1f' % v)
else: l.append('%0.18f' % v)
return ' '.join(l)
scn = Scene.GetCurrent()
#cam = scn.getCurrentCamera().data
cam = scn.objects.camera.data
objectmblur = luxProp(cam, "objectmblur", "true").get()
usemblur = luxProp(cam, "usemblur", "false").get()
mesh_optimizing = luxProp(scn, "mesh_optimizing", "true").get()