Import_3ds: Improved distance cue node setup
[blender-addons.git] / io_scene_3ds / import_3ds.py
blob085ef99d8ffd58f707304d65bd1adb61ce21ab5f
1 # SPDX-FileCopyrightText: 2005 Bob Holcomb
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import os
6 import bpy
7 import time
8 import math
9 import struct
10 import mathutils
11 from bpy_extras.image_utils import load_image
12 from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
13 from pathlib import Path
15 BOUNDS_3DS = []
17 ###################
18 # Data Structures #
19 ###################
21 # Some of the chunks that we will see
22 # >----- Primary Chunk, at the beginning of each file
23 PRIMARY = 0x4D4D
25 # >----- Main Chunks
26 OBJECTINFO = 0x3D3D # This gives the version of the mesh and is found right before the material and object information
27 VERSION = 0x0002 # This gives the version of the .3ds file
28 EDITKEYFRAME = 0xB000 # This is the header for all of the key frame info
30 # >----- Data Chunks, used for various attributes
31 COLOR_F = 0x0010 # color defined as 3 floats
32 COLOR_24 = 0x0011 # color defined as 3 bytes
33 LIN_COLOR_24 = 0x0012 # linear byte color
34 LIN_COLOR_F = 0x0013 # linear float color
35 PCT_SHORT = 0x0030 # percentage short
36 PCT_FLOAT = 0x0031 # percentage float
37 MASTERSCALE = 0x0100 # Master scale factor
39 # >----- sub defines of OBJECTINFO
40 BITMAP = 0x1100 # The background image name
41 USE_BITMAP = 0x1101 # The background image flag
42 SOLIDBACKGND = 0x1200 # The background color (RGB)
43 USE_SOLIDBGND = 0x1201 # The background color flag
44 VGRADIENT = 0x1300 # The background gradient colors
45 USE_VGRADIENT = 0x1301 # The background gradient flag
46 O_CONSTS = 0x1500 # The origin of the 3D cursor
47 AMBIENTLIGHT = 0x2100 # The color of the ambient light
48 FOG = 0x2200 # The fog atmosphere settings
49 USE_FOG = 0x2201 # The fog atmosphere flag
50 FOG_BGND = 0x2210 # The fog atmosphere background flag
51 DISTANCE_CUE = 0x2300 # The distance cue atmosphere settings
52 USE_DISTANCE_CUE = 0x2301 # The distance cue atmosphere flag
53 LAYER_FOG = 0x2302 # The fog layer atmosphere settings
54 USE_LAYER_FOG = 0x2303 # The fog layer atmosphere flag
55 DCUE_BGND = 0x2310 # The distance cue background flag
56 MATERIAL = 0xAFFF # This stored the texture info
57 OBJECT = 0x4000 # This stores the faces, vertices, etc...
59 # >------ sub defines of MATERIAL
60 MAT_NAME = 0xA000 # This holds the material name
61 MAT_AMBIENT = 0xA010 # Ambient color of the object/material
62 MAT_DIFFUSE = 0xA020 # This holds the color of the object/material
63 MAT_SPECULAR = 0xA030 # Specular color of the object/material
64 MAT_SHINESS = 0xA040 # Roughness of the object/material (percent)
65 MAT_SHIN2 = 0xA041 # Shininess of the object/material (percent)
66 MAT_SHIN3 = 0xA042 # Reflection of the object/material (percent)
67 MAT_TRANSPARENCY = 0xA050 # Transparency value of material (percent)
68 MAT_XPFALL = 0xA052 # Transparency falloff value
69 MAT_REFBLUR = 0xA053 # Reflection blurring value
70 MAT_SELF_ILLUM = 0xA080 # # Material self illumination flag
71 MAT_TWO_SIDE = 0xA081 # Material is two sided flag
72 MAT_DECAL = 0xA082 # Material mapping is decaled flag
73 MAT_ADDITIVE = 0xA083 # Material has additive transparency flag
74 MAT_SELF_ILPCT = 0xA084 # Self illumination strength (percent)
75 MAT_WIRE = 0xA085 # Material wireframe rendered flag
76 MAT_FACEMAP = 0xA088 # Face mapped textures flag
77 MAT_PHONGSOFT = 0xA08C # Phong soften material flag
78 MAT_WIREABS = 0xA08E # Wire size in units flag
79 MAT_WIRESIZE = 0xA087 # Rendered wire size in pixels
80 MAT_SHADING = 0xA100 # Material shading method
81 MAT_USE_XPFALL = 0xA240 # Transparency falloff flag
82 MAT_USE_REFBLUR = 0xA250 # Reflection blurring flag
84 # >------ sub defines of MATERIAL_MAP
85 MAT_TEXTURE_MAP = 0xA200 # This is a header for a new texture map
86 MAT_SPECULAR_MAP = 0xA204 # This is a header for a new specular map
87 MAT_OPACITY_MAP = 0xA210 # This is a header for a new opacity map
88 MAT_REFLECTION_MAP = 0xA220 # This is a header for a new reflection map
89 MAT_BUMP_MAP = 0xA230 # This is a header for a new bump map
90 MAT_BUMP_PERCENT = 0xA252 # Normalmap strength (percent)
91 MAT_TEX2_MAP = 0xA33A # This is a header for a secondary texture
92 MAT_SHIN_MAP = 0xA33C # This is a header for a new roughness map
93 MAT_SELFI_MAP = 0xA33D # This is a header for a new emission map
94 MAT_MAP_FILEPATH = 0xA300 # This holds the file name of the texture
95 MAT_MAP_TILING = 0xA351 # 2nd bit (from LSB) is mirror UV flag
96 MAT_MAP_TEXBLUR = 0xA353 # Texture blurring factor (float 0-1)
97 MAT_MAP_USCALE = 0xA354 # U axis scaling
98 MAT_MAP_VSCALE = 0xA356 # V axis scaling
99 MAT_MAP_UOFFSET = 0xA358 # U axis offset
100 MAT_MAP_VOFFSET = 0xA35A # V axis offset
101 MAT_MAP_ANG = 0xA35C # UV rotation around the z-axis in rad
102 MAT_MAP_COL1 = 0xA360 # Map Color1
103 MAT_MAP_COL2 = 0xA362 # Map Color2
104 MAT_MAP_RCOL = 0xA364 # Red mapping
105 MAT_MAP_GCOL = 0xA366 # Green mapping
106 MAT_MAP_BCOL = 0xA368 # Blue mapping
108 # >------ sub defines of OBJECT
109 OBJECT_MESH = 0x4100 # This lets us know that we are reading a new object
110 OBJECT_LIGHT = 0x4600 # This lets us know we are reading a light object
111 OBJECT_CAMERA = 0x4700 # This lets us know we are reading a camera object
112 OBJECT_HIERARCHY = 0x4F00 # This lets us know the hierachy id of the object
113 OBJECT_PARENT = 0x4F10 # This lets us know the parent id of the object
115 # >------ Sub defines of LIGHT
116 LIGHT_SPOTLIGHT = 0x4610 # The target of a spotlight
117 LIGHT_OFF = 0x4620 # The light is off
118 LIGHT_ATTENUATE = 0x4625 # Light attenuate flag
119 LIGHT_RAYSHADE = 0x4627 # Light rayshading flag
120 LIGHT_SPOT_SHADOWED = 0x4630 # Light spot shadow flag
121 LIGHT_LOCAL_SHADOW = 0x4640 # Light shadow values 1
122 LIGHT_LOCAL_SHADOW2 = 0x4641 # Light shadow values 2
123 LIGHT_SPOT_SEE_CONE = 0x4650 # Light spot cone flag
124 LIGHT_SPOT_RECTANGLE = 0x4651 # Light spot rectangle flag
125 LIGHT_SPOT_OVERSHOOT = 0x4652 # Light spot overshoot flag
126 LIGHT_SPOT_PROJECTOR = 0x4653 # Light spot bitmap name
127 LIGHT_EXCLUDE = 0x4654 # Light excluded objects
128 LIGHT_RANGE = 0x4655 # Light range
129 LIGHT_SPOT_ROLL = 0x4656 # The roll angle of the spot
130 LIGHT_SPOT_ASPECT = 0x4657 # Light spot aspect flag
131 LIGHT_RAY_BIAS = 0x4658 # Light ray bias value
132 LIGHT_INNER_RANGE = 0x4659 # The light inner range
133 LIGHT_OUTER_RANGE = 0x465A # The light outer range
134 LIGHT_MULTIPLIER = 0x465B # The light energy factor
135 LIGHT_ATTENUATE = 0x4625 # Light attenuation flag
136 LIGHT_AMBIENT_LIGHT = 0x4680 # Light ambient flag
138 # >------ sub defines of CAMERA
139 OBJECT_CAM_RANGES = 0x4720 # The camera range values
141 # >------ sub defines of OBJECT_MESH
142 OBJECT_VERTICES = 0x4110 # The objects vertices
143 OBJECT_VERTFLAGS = 0x4111 # The objects vertex flags
144 OBJECT_FACES = 0x4120 # The objects faces
145 OBJECT_MATERIAL = 0x4130 # The objects face material
146 OBJECT_UV = 0x4140 # The vertex UV texture coordinates
147 OBJECT_SMOOTH = 0x4150 # The objects face smooth groups
148 OBJECT_TRANS_MATRIX = 0x4160 # The objects Matrix
150 # >------ sub defines of EDITKEYFRAME
151 KF_AMBIENT = 0xB001 # Keyframe ambient node
152 KF_OBJECT = 0xB002 # Keyframe object node
153 KF_OBJECT_CAMERA = 0xB003 # Keyframe camera node
154 KF_TARGET_CAMERA = 0xB004 # Keyframe target node
155 KF_OBJECT_LIGHT = 0xB005 # Keyframe light node
156 KF_TARGET_LIGHT = 0xB006 # Keyframe light target node
157 KF_OBJECT_SPOT_LIGHT = 0xB007 # Keyframe spotlight node
158 KFDATA_KFSEG = 0xB008 # Keyframe start and stop
159 KFDATA_CURTIME = 0xB009 # Keyframe current frame
160 KFDATA_KFHDR = 0xB00A # Keyframe node header
162 # >------ sub defines of KEYFRAME_NODE
163 OBJECT_NODE_HDR = 0xB010 # Keyframe object node header
164 OBJECT_INSTANCE_NAME = 0xB011 # Keyframe object name for dummy objects
165 OBJECT_PRESCALE = 0xB012 # Keyframe object prescale
166 OBJECT_PIVOT = 0xB013 # Keyframe object pivot position
167 OBJECT_BOUNDBOX = 0xB014 # Keyframe object boundbox
168 MORPH_SMOOTH = 0xB015 # Auto smooth angle for keyframe mesh objects
169 POS_TRACK_TAG = 0xB020 # Keyframe object position track
170 ROT_TRACK_TAG = 0xB021 # Keyframe object rotation track
171 SCL_TRACK_TAG = 0xB022 # Keyframe object scale track
172 FOV_TRACK_TAG = 0xB023 # Keyframe camera field of view track
173 ROLL_TRACK_TAG = 0xB024 # Keyframe camera roll track
174 COL_TRACK_TAG = 0xB025 # Keyframe light color track
175 MORPH_TRACK_TAG = 0xB026 # Keyframe object morph smooth track
176 HOTSPOT_TRACK_TAG = 0xB027 # Keyframe spotlight hotspot track
177 FALLOFF_TRACK_TAG = 0xB028 # Keyframe spotlight falloff track
178 HIDE_TRACK_TAG = 0xB029 # Keyframe object hide track
179 OBJECT_NODE_ID = 0xB030 # Keyframe object node id
180 PARENT_NAME = 0x80F0 # Object parent name tree (dot seperated)
181 ROOT_OBJECT = 0xFFFF
183 global scn
184 scn = None
186 object_dictionary = {}
187 parent_dictionary = {}
188 matrix_transform = {}
189 object_matrix = {}
192 class Chunk:
193 __slots__ = (
194 "ID",
195 "length",
196 "bytes_read",
198 # we don't read in the bytes_read, we compute that
199 binary_format = '<HI'
201 def __init__(self):
202 self.ID = 0
203 self.length = 0
204 self.bytes_read = 0
206 def dump(self):
207 print('ID: ', self.ID)
208 print('ID in hex: ', hex(self.ID))
209 print('length: ', self.length)
210 print('bytes_read: ', self.bytes_read)
213 def read_chunk(file, chunk):
214 temp_data = file.read(struct.calcsize(chunk.binary_format))
215 data = struct.unpack(chunk.binary_format, temp_data)
216 chunk.ID = data[0]
217 chunk.length = data[1]
218 # update the bytes read function
219 chunk.bytes_read = 6
221 # if debugging
222 # chunk.dump()
225 def read_string(file):
226 # read in the characters till we get a null character
227 s = []
228 while True:
229 c = file.read(1)
230 if c == b'\x00':
231 break
232 s.append(c)
233 # print('string: ', s)
235 # Remove the null character from the string
236 # print("read string", s)
237 return str(b''.join(s), "utf-8", "replace"), len(s) + 1
240 def skip_to_end(file, skip_chunk):
241 buffer_size = skip_chunk.length - skip_chunk.bytes_read
242 binary_format = '%ic' % buffer_size
243 file.read(struct.calcsize(binary_format))
244 skip_chunk.bytes_read += buffer_size
247 #############
248 # MATERIALS #
249 #############
251 def add_texture_to_material(image, contextWrapper, pct, extend, alpha, scale, offset, angle, tint1, tint2, mapto):
252 shader = contextWrapper.node_principled_bsdf
253 nodetree = contextWrapper.material.node_tree
254 shader.location = (-300, 0)
255 nodes = nodetree.nodes
256 links = nodetree.links
258 if mapto == 'COLOR':
259 mixer = nodes.new(type='ShaderNodeMixRGB')
260 mixer.label = "Mixer"
261 mixer.inputs[0].default_value = pct / 100
262 mixer.inputs[1].default_value = (
263 tint1[:3] + [1] if tint1 else shader.inputs['Base Color'].default_value[:])
264 contextWrapper._grid_to_location(1, 2, dst_node=mixer, ref_node=shader)
265 img_wrap = contextWrapper.base_color_texture
266 image.alpha_mode = 'CHANNEL_PACKED'
267 links.new(mixer.outputs[0], shader.inputs['Base Color'])
268 if tint2 is not None:
269 img_wrap.colorspace_name = 'Non-Color'
270 mixer.inputs[2].default_value = tint2[:3] + [1]
271 links.new(img_wrap.node_image.outputs[0], mixer.inputs[0])
272 else:
273 links.new(img_wrap.node_image.outputs[0], mixer.inputs[2])
274 elif mapto == 'ROUGHNESS':
275 img_wrap = contextWrapper.roughness_texture
276 elif mapto == 'METALLIC':
277 shader.location = (300,300)
278 img_wrap = contextWrapper.metallic_texture
279 elif mapto == 'SPECULARITY':
280 shader.location = (300,0)
281 img_wrap = contextWrapper.specular_tint_texture
282 if tint1:
283 img_wrap.node_dst.inputs['Coat Tint'].default_value = tint1[:3] + [1]
284 if tint2:
285 img_wrap.node_dst.inputs['Sheen Tint'].default_value = tint2[:3] + [1]
286 elif mapto == 'ALPHA':
287 shader.location = (-300,0)
288 img_wrap = contextWrapper.alpha_texture
289 img_wrap.use_alpha = False
290 links.new(img_wrap.node_image.outputs[0], img_wrap.socket_dst)
291 elif mapto == 'EMISSION':
292 shader.location = (0,-900)
293 img_wrap = contextWrapper.emission_color_texture
294 elif mapto == 'NORMAL':
295 shader.location = (300, 300)
296 img_wrap = contextWrapper.normalmap_texture
297 elif mapto == 'TEXTURE':
298 img_wrap = nodes.new(type='ShaderNodeTexImage')
299 img_wrap.label = image.name
300 contextWrapper._grid_to_location(0, 2, dst_node=img_wrap, ref_node=shader)
301 for node in nodes:
302 if node.label == 'Mixer':
303 spare = node.inputs[1] if node.inputs[1].is_linked is False else node.inputs[2]
304 socket = spare if spare.is_linked is False else node.inputs[0]
305 links.new(img_wrap.outputs[0], socket)
306 if node.type == 'TEX_COORD':
307 links.new(node.outputs['UV'], img_wrap.inputs[0])
308 if shader.inputs['Base Color'].is_linked is False:
309 links.new(img_wrap.outputs[0], shader.inputs['Base Color'])
311 img_wrap.image = image
312 img_wrap.extension = 'REPEAT'
314 if mapto != 'TEXTURE':
315 img_wrap.scale = scale
316 img_wrap.translation = offset
317 img_wrap.rotation[2] = angle
319 if extend == 'mirror':
320 img_wrap.extension = 'MIRROR'
321 elif extend == 'decal':
322 img_wrap.extension = 'EXTEND'
323 elif extend == 'noWrap':
324 img_wrap.extension = 'CLIP'
326 if alpha == 'alpha':
327 own_node = img_wrap.node_image
328 contextWrapper.material.blend_method = 'HASHED'
329 links.new(own_node.outputs[1], img_wrap.socket_dst)
330 for link in links:
331 if link.from_node.type == 'TEX_IMAGE' and link.to_node.type == 'MIX_RGB':
332 tex = link.from_node.image.name
333 own_map = img_wrap.node_mapping
334 if tex == image.name:
335 links.new(link.from_node.outputs[1], img_wrap.socket_dst)
336 try:
337 nodes.remove(own_map)
338 nodes.remove(own_node)
339 except:
340 pass
341 for imgs in bpy.data.images:
342 if imgs.name[-3:].isdigit():
343 if not imgs.users:
344 bpy.data.images.remove(imgs)
346 shader.location = (300, 300)
347 contextWrapper._grid_to_location(1, 0, dst_node=contextWrapper.node_out, ref_node=shader)
350 #############
351 # MESH DATA #
352 #############
354 childs_list = []
355 parent_list = []
357 def process_next_chunk(context, file, previous_chunk, imported_objects,
358 CONSTRAIN, FILTER, IMAGE_SEARCH, WORLD_MATRIX,
359 KEYFRAME, APPLY_MATRIX, CONVERSE, MEASURE, CURSOR):
361 contextObName = None
362 contextWorld = None
363 contextLamp = None
364 contextCamera = None
365 contextMaterial = None
366 contextAlpha = None
367 contextColor = None
368 contextWrapper = None
369 contextMatrix = None
370 contextReflection = None
371 contextTransmission = None
372 contextMesh_vertls = None
373 contextMesh_facels = None
374 contextMesh_flag = None
375 contextMeshMaterials = []
376 contextMesh_smooth = None
377 contextMeshUV = None
378 contextTrack_flag = False
380 # TEXTURE_DICT = {}
381 MATDICT = {}
383 # Localspace variable names, faster.
384 SZ_FLOAT = struct.calcsize('f')
385 SZ_2FLOAT = struct.calcsize('2f')
386 SZ_3FLOAT = struct.calcsize('3f')
387 SZ_4FLOAT = struct.calcsize('4f')
388 SZ_U_INT = struct.calcsize('I')
389 SZ_U_SHORT = struct.calcsize('H')
390 SZ_4U_SHORT = struct.calcsize('4H')
391 SZ_4x3MAT = struct.calcsize('ffffffffffff')
393 object_dict = {} # object identities
394 object_list = [] # for hierarchy
395 object_parent = [] # index of parent in hierarchy, 0xFFFF = no parent
396 pivot_list = [] # pivots with hierarchy handling
397 trackposition = {} # keep track to position for target calculation
399 def putContextMesh(context, ContextMesh_vertls, ContextMesh_facels, ContextMesh_flag,
400 ContextMeshMaterials, ContextMesh_smooth, WORLD_MATRIX):
402 bmesh = bpy.data.meshes.new(contextObName)
404 if ContextMesh_facels is None:
405 ContextMesh_facels = []
407 if ContextMesh_vertls:
408 bmesh.vertices.add(len(ContextMesh_vertls) // 3)
409 bmesh.vertices.foreach_set("co", ContextMesh_vertls)
411 nbr_faces = len(ContextMesh_facels)
412 bmesh.polygons.add(nbr_faces)
413 bmesh.loops.add(nbr_faces * 3)
414 eekadoodle_faces = []
415 for v1, v2, v3 in ContextMesh_facels:
416 eekadoodle_faces.extend((v3, v1, v2) if v3 == 0 else (v1, v2, v3))
417 bmesh.polygons.foreach_set("loop_start", range(0, nbr_faces * 3, 3))
418 bmesh.loops.foreach_set("vertex_index", eekadoodle_faces)
420 if bmesh.polygons and contextMeshUV:
421 bmesh.uv_layers.new()
422 uv_faces = bmesh.uv_layers.active.data[:]
423 else:
424 uv_faces = None
426 for mat_idx, (matName, faces) in enumerate(ContextMeshMaterials):
427 if matName is None:
428 bmat = None
429 else:
430 bmat = MATDICT.get(matName)
431 # in rare cases no materials defined.
433 bmesh.materials.append(bmat) # can be None
434 if bmesh.polygons:
435 for fidx in faces:
436 bmesh.polygons[fidx].material_index = mat_idx
437 else:
438 print("\tError: Mesh has no faces!")
440 if uv_faces:
441 uvl = bmesh.uv_layers.active.data[:]
442 for fidx, pl in enumerate(bmesh.polygons):
443 face = ContextMesh_facels[fidx]
444 v1, v2, v3 = face
446 # eekadoodle
447 if v3 == 0:
448 v1, v2, v3 = v3, v1, v2
450 uvl[pl.loop_start].uv = contextMeshUV[v1 * 2: (v1 * 2) + 2]
451 uvl[pl.loop_start + 1].uv = contextMeshUV[v2 * 2: (v2 * 2) + 2]
452 uvl[pl.loop_start + 2].uv = contextMeshUV[v3 * 2: (v3 * 2) + 2]
453 # always a tri
455 bmesh.validate()
456 bmesh.update()
458 ob = bpy.data.objects.new(contextObName, bmesh)
459 object_dictionary[contextObName] = ob
460 context.view_layer.active_layer_collection.collection.objects.link(ob)
461 imported_objects.append(ob)
463 if ContextMesh_flag:
464 """Bit 0 (0x1) sets edge CA visible, Bit 1 (0x2) sets edge BC visible and
465 Bit 2 (0x4) sets edge AB visible. In Blender we use sharp edges for those flags."""
466 for f, pl in enumerate(bmesh.polygons):
467 face = ContextMesh_facels[f]
468 faceflag = ContextMesh_flag[f]
469 edge_ab = bmesh.edges[bmesh.loops[pl.loop_start].edge_index]
470 edge_bc = bmesh.edges[bmesh.loops[pl.loop_start + 1].edge_index]
471 edge_ca = bmesh.edges[bmesh.loops[pl.loop_start + 2].edge_index]
472 if face[2] == 0:
473 edge_ab, edge_bc, edge_ca = edge_ca, edge_ab, edge_bc
474 if faceflag & 0x1:
475 edge_ca.use_edge_sharp = True
476 if faceflag & 0x2:
477 edge_bc.use_edge_sharp = True
478 if faceflag & 0x4:
479 edge_ab.use_edge_sharp = True
481 if ContextMesh_smooth:
482 for f, pl in enumerate(bmesh.polygons):
483 smoothface = ContextMesh_smooth[f]
484 bmesh.polygons[f].use_smooth = True if smoothface > 0 else False
485 else:
486 bmesh.polygons.foreach_set("use_smooth", [False] * len(bmesh.polygons))
488 if contextMatrix:
489 if WORLD_MATRIX:
490 ob.matrix_world = contextMatrix
491 else:
492 ob.matrix_local = contextMatrix
493 object_matrix[ob] = contextMatrix.copy()
495 # a spare chunk
496 new_chunk = Chunk()
497 temp_chunk = Chunk()
499 CreateBlenderObject = False
500 CreateCameraObject = False
501 CreateLightObject = False
502 CreateTrackData = False
504 CreateWorld = 'WORLD' in FILTER
505 CreateMesh = 'MESH' in FILTER
506 CreateLight = 'LIGHT' in FILTER
507 CreateCamera = 'CAMERA' in FILTER
508 CreateEmpty = 'EMPTY' in FILTER
510 def read_short(temp_chunk):
511 temp_data = file.read(SZ_U_SHORT)
512 temp_chunk.bytes_read += SZ_U_SHORT
513 return struct.unpack('<H', temp_data)[0]
515 def read_long(temp_chunk):
516 temp_data = file.read(SZ_U_INT)
517 temp_chunk.bytes_read += SZ_U_INT
518 return struct.unpack('<I', temp_data)[0]
520 def read_float(temp_chunk):
521 temp_data = file.read(SZ_FLOAT)
522 temp_chunk.bytes_read += SZ_FLOAT
523 return struct.unpack('<f', temp_data)[0]
525 def read_float_array(temp_chunk):
526 temp_data = file.read(SZ_3FLOAT)
527 temp_chunk.bytes_read += SZ_3FLOAT
528 return [float(val) for val in struct.unpack('<3f', temp_data)]
530 def read_byte_color(temp_chunk):
531 temp_data = file.read(struct.calcsize('3B'))
532 temp_chunk.bytes_read += 3
533 return [float(col) / 255 for col in struct.unpack('<3B', temp_data)]
535 def read_texture(new_chunk, temp_chunk, name, mapto):
536 uscale, vscale, uoffset, voffset, angle = 1.0, 1.0, 0.0, 0.0, 0.0
537 contextWrapper.use_nodes = True
538 tint1 = tint2 = None
539 extend = 'wrap'
540 alpha = False
541 pct = 70
543 contextWrapper.base_color = contextColor[:]
544 contextWrapper.metallic = contextMaterial.metallic
545 contextWrapper.roughness = contextMaterial.roughness
546 contextWrapper.transmission = contextTransmission
547 contextWrapper.specular = contextMaterial.specular_intensity
548 contextWrapper.specular_tint = contextMaterial.specular_color[:]
549 contextWrapper.emission_color = contextMaterial.line_color[:3]
550 contextWrapper.emission_strength = contextMaterial.line_priority / 100
551 contextWrapper.alpha = contextMaterial.diffuse_color[3] = contextAlpha
552 contextWrapper.node_principled_bsdf.inputs['Coat Weight'].default_value = contextReflection
554 while (new_chunk.bytes_read < new_chunk.length):
555 read_chunk(file, temp_chunk)
556 if temp_chunk.ID == PCT_SHORT:
557 pct = read_short(temp_chunk)
559 elif temp_chunk.ID == MAT_MAP_FILEPATH:
560 texture_name, read_str_len = read_string(file)
561 img = load_image(texture_name, dirname, place_holder=False, recursive=IMAGE_SEARCH, check_existing=True)
562 temp_chunk.bytes_read += read_str_len # plus one for the null character that gets removed
564 elif temp_chunk.ID == MAT_BUMP_PERCENT:
565 contextWrapper.normalmap_strength = (float(read_short(temp_chunk) / 100))
566 elif mapto in {'COLOR', 'SPECULARITY'} and temp_chunk.ID == MAT_MAP_TEXBLUR:
567 contextWrapper.node_principled_bsdf.inputs['Sheen Weight'].default_value = float(read_float(temp_chunk))
569 elif temp_chunk.ID == MAT_MAP_TILING:
570 """Control bit flags, 0x1 activates decaling, 0x2 activates mirror, 0x8 activates inversion,
571 0x10 deactivates tiling, 0x20 activates summed area sampling, 0x40 activates alpha source,
572 0x80 activates tinting, 0x100 ignores alpha, 0x200 activates RGB tint. Bits 0x80, 0x100, and 0x200
573 are only used with TEXMAP, TEX2MAP, and SPECMAP chunks. 0x40, when used with a TEXMAP, TEX2MAP, or SPECMAP chunk
574 must be accompanied with a tint bit, either 0x100 or 0x200, tintcolor will be processed if colorchunks are present."""
575 tiling = read_short(temp_chunk)
576 if tiling & 0x1:
577 extend = 'decal'
578 elif tiling & 0x2:
579 extend = 'mirror'
580 elif tiling & 0x8:
581 extend = 'invert'
582 elif tiling & 0x10:
583 extend = 'noWrap'
584 if tiling & 0x20:
585 alpha = 'sat'
586 if tiling & 0x40:
587 alpha = 'alpha'
588 if tiling & 0x80:
589 tint = 'tint'
590 if tiling & 0x100:
591 tint = 'noAlpha'
592 if tiling & 0x200:
593 tint = 'RGBtint'
595 elif temp_chunk.ID == MAT_MAP_USCALE:
596 uscale = read_float(temp_chunk)
597 elif temp_chunk.ID == MAT_MAP_VSCALE:
598 vscale = read_float(temp_chunk)
599 elif temp_chunk.ID == MAT_MAP_UOFFSET:
600 uoffset = read_float(temp_chunk)
601 elif temp_chunk.ID == MAT_MAP_VOFFSET:
602 voffset = read_float(temp_chunk)
603 elif temp_chunk.ID == MAT_MAP_ANG:
604 angle = read_float(temp_chunk)
605 elif temp_chunk.ID == MAT_MAP_COL1:
606 tint1 = read_byte_color(temp_chunk)
607 elif temp_chunk.ID == MAT_MAP_COL2:
608 tint2 = read_byte_color(temp_chunk)
610 skip_to_end(file, temp_chunk)
611 new_chunk.bytes_read += temp_chunk.bytes_read
613 # add the map to the material in the right channel
614 if img:
615 add_texture_to_material(img, contextWrapper, pct, extend, alpha, (uscale, vscale, 1),
616 (uoffset, voffset, 0), angle, tint1, tint2, mapto)
618 def apply_constrain(vec):
619 convector = mathutils.Vector.Fill(3, (CONSTRAIN * 0.1))
620 consize = mathutils.Vector(vec) * convector if CONSTRAIN != 0.0 else mathutils.Vector(vec)
621 return consize
623 def get_hierarchy(tree_chunk):
624 child_id = read_short(tree_chunk)
625 childs_list.insert(child_id, contextObName)
626 parent_list.insert(child_id, None)
627 if child_id in parent_list:
628 idp = parent_list.index(child_id)
629 parent_list[idp] = contextObName
630 return child_id
632 def get_parent(tree_chunk, child_id=-1):
633 parent_id = read_short(tree_chunk)
634 if parent_id > len(childs_list):
635 parent_list[child_id] = parent_id
636 parent_list.extend([None] * (parent_id - len(parent_list)))
637 parent_list.insert(parent_id, contextObName)
638 elif parent_id < len(childs_list):
639 parent_list[child_id] = childs_list[parent_id]
641 def calc_target(loca, target):
642 pan = tilt = 0.0
643 plane = loca + target
644 angle = math.radians(90) # Target triangulation
645 check_sign = abs(loca.y) < abs(target.y)
646 check_axes = abs(loca.x - target.x) > abs(loca.y - target.y)
647 plane_y = plane.y if check_sign else -1 * plane.y
648 sign_xy = plane.x if check_axes else plane.y
649 axis_xy = plane_y if check_axes else plane.x
650 hyp = math.sqrt(pow(plane.x,2) + pow(plane.y,2))
651 dia = math.sqrt(pow(hyp,2) + pow(plane.z,2))
652 yaw = math.atan2(math.copysign(hyp, sign_xy), axis_xy)
653 bow = math.acos(hyp / dia) if dia != 0 else 0
654 turn = angle - yaw if check_sign else angle + yaw
655 tilt = angle - bow if loca.z > target.z else angle + bow
656 pan = yaw if check_axes else turn
657 return tilt, pan
659 def read_track_data(track_chunk):
660 """Trackflags 0x1, 0x2 and 0x3 are for looping. 0x8, 0x10 and 0x20
661 locks the XYZ axes. 0x100, 0x200 and 0x400 unlinks the XYZ axes."""
662 tflags = read_short(track_chunk)
663 contextTrack_flag = tflags
664 temp_data = file.read(SZ_U_INT * 2)
665 track_chunk.bytes_read += SZ_U_INT * 2
666 nkeys = read_long(track_chunk)
667 for i in range(nkeys):
668 nframe = read_long(track_chunk)
669 nflags = read_short(track_chunk)
670 for f in range(bin(nflags)[-5:].count('1')):
671 temp_data = file.read(SZ_FLOAT) # Check for spline terms
672 track_chunk.bytes_read += SZ_FLOAT
673 trackdata = read_float_array(track_chunk)
674 keyframe_data[nframe] = trackdata
675 return keyframe_data
677 def read_track_angle(track_chunk):
678 temp_data = file.read(SZ_U_SHORT * 5)
679 track_chunk.bytes_read += SZ_U_SHORT * 5
680 nkeys = read_long(track_chunk)
681 for i in range(nkeys):
682 nframe = read_long(track_chunk)
683 nflags = read_short(track_chunk)
684 for f in range(bin(nflags)[-5:].count('1')):
685 temp_data = file.read(SZ_FLOAT) # Check for spline terms
686 track_chunk.bytes_read += SZ_FLOAT
687 angle = read_float(track_chunk)
688 keyframe_angle[nframe] = math.radians(angle)
689 return keyframe_angle
691 dirname = os.path.dirname(file.name)
693 # loop through all the data for this chunk (previous chunk) and see what it is
694 while (previous_chunk.bytes_read < previous_chunk.length):
695 read_chunk(file, new_chunk)
697 # Check the Version chunk
698 if new_chunk.ID == VERSION:
699 # read in the version of the file
700 temp_data = file.read(SZ_U_INT)
701 version = struct.unpack('<I', temp_data)[0]
702 new_chunk.bytes_read += 4 # read the 4 bytes for the version number
703 # this loader works with version 3 and below, but may not with 4 and above
704 if version > 3:
705 print("\tNon-Fatal Error: Version greater than 3, may not load correctly: ", version)
707 # The main object info chunk
708 elif new_chunk.ID == OBJECTINFO:
709 process_next_chunk(context, file, new_chunk, imported_objects,
710 CONSTRAIN, FILTER, IMAGE_SEARCH, WORLD_MATRIX,
711 KEYFRAME, APPLY_MATRIX, CONVERSE, MEASURE, CURSOR)
713 # keep track of how much we read in the main chunk
714 new_chunk.bytes_read += temp_chunk.bytes_read
716 # If material chunk
717 elif new_chunk.ID == MATERIAL:
718 contextAlpha = True
719 contextReflection = False
720 contextTransmission = False
721 contextColor = mathutils.Color((0.8, 0.8, 0.8))
722 contextMaterial = bpy.data.materials.new('Material')
723 contextWrapper = PrincipledBSDFWrapper(contextMaterial, is_readonly=False, use_nodes=False)
725 elif new_chunk.ID == MAT_NAME:
726 material_name, read_str_len = read_string(file)
727 # plus one for the null character that ended the string
728 new_chunk.bytes_read += read_str_len
729 contextMaterial.name = material_name.rstrip() # remove trailing whitespace
730 MATDICT[material_name] = contextMaterial
732 elif new_chunk.ID == MAT_AMBIENT:
733 read_chunk(file, temp_chunk)
734 # to not loose this data, ambient color is stored in line color
735 if temp_chunk.ID == COLOR_F:
736 contextMaterial.line_color[:3] = read_float_array(temp_chunk)
737 elif temp_chunk.ID == COLOR_24:
738 contextMaterial.line_color[:3] = read_byte_color(temp_chunk)
739 else:
740 skip_to_end(file, temp_chunk)
741 new_chunk.bytes_read += temp_chunk.bytes_read
743 elif new_chunk.ID == MAT_DIFFUSE:
744 read_chunk(file, temp_chunk)
745 if temp_chunk.ID == COLOR_F:
746 contextColor = mathutils.Color(read_float_array(temp_chunk))
747 contextMaterial.diffuse_color[:3] = contextColor
748 elif temp_chunk.ID == COLOR_24:
749 contextColor = mathutils.Color(read_byte_color(temp_chunk))
750 contextMaterial.diffuse_color[:3] = contextColor
751 else:
752 skip_to_end(file, temp_chunk)
753 new_chunk.bytes_read += temp_chunk.bytes_read
755 elif new_chunk.ID == MAT_SPECULAR:
756 read_chunk(file, temp_chunk)
757 if temp_chunk.ID == COLOR_F:
758 contextMaterial.specular_color = read_float_array(temp_chunk)
759 elif temp_chunk.ID == COLOR_24:
760 contextMaterial.specular_color = read_byte_color(temp_chunk)
761 else:
762 skip_to_end(file, temp_chunk)
763 new_chunk.bytes_read += temp_chunk.bytes_read
765 elif new_chunk.ID == MAT_SHINESS:
766 read_chunk(file, temp_chunk)
767 if temp_chunk.ID == PCT_SHORT:
768 contextMaterial.roughness = 1 - (float(read_short(temp_chunk) / 100))
769 elif temp_chunk.ID == PCT_FLOAT:
770 contextMaterial.roughness = 1.0 - float(read_float(temp_chunk))
771 else:
772 skip_to_end(file, temp_chunk)
773 new_chunk.bytes_read += temp_chunk.bytes_read
775 elif new_chunk.ID == MAT_SHIN2:
776 read_chunk(file, temp_chunk)
777 if temp_chunk.ID == PCT_SHORT:
778 contextMaterial.specular_intensity = float(read_short(temp_chunk) / 100)
779 elif temp_chunk.ID == PCT_FLOAT:
780 contextMaterial.specular_intensity = float(read_float(temp_chunk))
781 else:
782 skip_to_end(file, temp_chunk)
783 new_chunk.bytes_read += temp_chunk.bytes_read
785 elif new_chunk.ID == MAT_SHIN3:
786 read_chunk(file, temp_chunk)
787 if temp_chunk.ID == PCT_SHORT:
788 contextMaterial.metallic = float(read_short(temp_chunk) / 100)
789 elif temp_chunk.ID == PCT_FLOAT:
790 contextMaterial.metallic = float(read_float(temp_chunk))
791 else:
792 skip_to_end(file, temp_chunk)
793 new_chunk.bytes_read += temp_chunk.bytes_read
795 elif new_chunk.ID == MAT_TRANSPARENCY:
796 read_chunk(file, temp_chunk)
797 if temp_chunk.ID == PCT_SHORT:
798 contextAlpha = 1 - (float(read_short(temp_chunk) / 100))
799 contextMaterial.diffuse_color[3] = contextAlpha
800 elif temp_chunk.ID == PCT_FLOAT:
801 contextAlpha = 1.0 - float(read_float(temp_chunk))
802 contextMaterial.diffuse_color[3] = contextAlpha
803 else:
804 skip_to_end(file, temp_chunk)
805 if contextAlpha < 1:
806 contextMaterial.blend_method = 'BLEND'
807 new_chunk.bytes_read += temp_chunk.bytes_read
809 elif new_chunk.ID == MAT_XPFALL:
810 read_chunk(file, temp_chunk)
811 if temp_chunk.ID == PCT_SHORT:
812 contextTransmission = float(abs(read_short(temp_chunk) / 100))
813 else:
814 skip_to_end(file, temp_chunk)
815 new_chunk.bytes_read += temp_chunk.bytes_read
817 elif new_chunk.ID == MAT_REFBLUR:
818 read_chunk(file, temp_chunk)
819 if temp_chunk.ID == PCT_SHORT:
820 contextReflection = float(read_short(temp_chunk) / 100)
821 elif temp_chunk.ID == PCT_FLOAT:
822 contextReflection = float(read_float(temp_chunk))
823 else:
824 skip_to_end(file, temp_chunk)
825 new_chunk.bytes_read += temp_chunk.bytes_read
827 elif new_chunk.ID == MAT_SELF_ILPCT:
828 read_chunk(file, temp_chunk)
829 if temp_chunk.ID == PCT_SHORT:
830 contextMaterial.line_priority = int(read_short(temp_chunk))
831 elif temp_chunk.ID == PCT_FLOAT:
832 contextMaterial.line_priority = (float(read_float(temp_chunk)) * 100)
833 else:
834 skip_to_end(file, temp_chunk)
835 new_chunk.bytes_read += temp_chunk.bytes_read
837 elif new_chunk.ID == MAT_SHADING:
838 shading = read_short(new_chunk)
839 if shading >= 2:
840 contextWrapper.use_nodes = True
841 contextWrapper.base_color = contextColor[:]
842 contextWrapper.metallic = contextMaterial.metallic
843 contextWrapper.roughness = contextMaterial.roughness
844 contextWrapper.transmission = contextTransmission
845 contextWrapper.specular = contextMaterial.specular_intensity
846 contextWrapper.specular_tint = contextMaterial.specular_color[:]
847 contextWrapper.emission_color = contextMaterial.line_color[:3]
848 contextWrapper.emission_strength = contextMaterial.line_priority / 100
849 contextWrapper.alpha = contextMaterial.diffuse_color[3] = contextAlpha
850 contextWrapper.node_principled_bsdf.inputs['Coat Weight'].default_value = contextReflection
851 contextWrapper.use_nodes = False
852 if shading >= 3:
853 contextWrapper.use_nodes = True
855 elif new_chunk.ID == MAT_TEXTURE_MAP:
856 read_texture(new_chunk, temp_chunk, "Diffuse", 'COLOR')
858 elif new_chunk.ID == MAT_SPECULAR_MAP:
859 read_texture(new_chunk, temp_chunk, "Specular", 'SPECULARITY')
861 elif new_chunk.ID == MAT_OPACITY_MAP:
862 read_texture(new_chunk, temp_chunk, "Opacity", 'ALPHA')
864 elif new_chunk.ID == MAT_REFLECTION_MAP:
865 read_texture(new_chunk, temp_chunk, "Reflect", 'METALLIC')
867 elif new_chunk.ID == MAT_BUMP_MAP:
868 read_texture(new_chunk, temp_chunk, "Bump", 'NORMAL')
870 elif new_chunk.ID == MAT_BUMP_PERCENT:
871 read_chunk(file, temp_chunk)
872 if temp_chunk.ID == PCT_SHORT:
873 contextWrapper.normalmap_strength = (float(read_short(temp_chunk) / 100))
874 elif temp_chunk.ID == PCT_FLOAT:
875 contextWrapper.normalmap_strength = float(read_float(temp_chunk))
876 else:
877 skip_to_end(file, temp_chunk)
878 new_chunk.bytes_read += temp_chunk.bytes_read
880 elif new_chunk.ID == MAT_SHIN_MAP:
881 read_texture(new_chunk, temp_chunk, "Shininess", 'ROUGHNESS')
883 elif new_chunk.ID == MAT_SELFI_MAP:
884 read_texture(new_chunk, temp_chunk, "Emit", 'EMISSION')
886 elif new_chunk.ID == MAT_TEX2_MAP:
887 read_texture(new_chunk, temp_chunk, "Tex", 'TEXTURE')
889 # If cursor location
890 elif CURSOR and new_chunk.ID == O_CONSTS:
891 context.scene.cursor.location = read_float_array(new_chunk)
893 # If ambient light chunk
894 elif CreateWorld and new_chunk.ID == AMBIENTLIGHT:
895 path, filename = os.path.split(file.name)
896 realname, ext = os.path.splitext(filename)
897 contextWorld = bpy.data.worlds.new("Ambient: " + realname)
898 context.scene.world = contextWorld
899 read_chunk(file, temp_chunk)
900 if temp_chunk.ID == COLOR_F:
901 contextWorld.color[:] = read_float_array(temp_chunk)
902 elif temp_chunk.ID == LIN_COLOR_F:
903 contextWorld.color[:] = read_float_array(temp_chunk)
904 else:
905 skip_to_end(file, temp_chunk)
906 new_chunk.bytes_read += temp_chunk.bytes_read
908 # If background chunk
909 elif CreateWorld and new_chunk.ID == SOLIDBACKGND:
910 backgroundcolor = mathutils.Color((0.1, 0.1, 0.1))
911 if contextWorld is None:
912 path, filename = os.path.split(file.name)
913 realname, ext = os.path.splitext(filename)
914 contextWorld = bpy.data.worlds.new("Background: " + realname)
915 context.scene.world = contextWorld
916 contextWorld.use_nodes = True
917 worldnodes = contextWorld.node_tree.nodes
918 backgroundnode = worldnodes['Background']
919 read_chunk(file, temp_chunk)
920 if temp_chunk.ID == COLOR_F:
921 backgroundcolor = read_float_array(temp_chunk)
922 elif temp_chunk.ID == LIN_COLOR_F:
923 backgroundcolor = read_float_array(temp_chunk)
924 else:
925 skip_to_end(file, temp_chunk)
926 backgroundmix = next((wn for wn in worldnodes if wn.type in {'MIX', 'MIX_RGB'}), False)
927 backgroundnode.inputs[0].default_value[:3] = backgroundcolor
928 if backgroundmix:
929 backgroundmix.inputs[2].default_value[:3] = backgroundcolor
930 new_chunk.bytes_read += temp_chunk.bytes_read
932 # If bitmap chunk
933 elif CreateWorld and new_chunk.ID == BITMAP:
934 bitmap_name, read_str_len = read_string(file)
935 if contextWorld is None:
936 path, filename = os.path.split(file.name)
937 realname, ext = os.path.splitext(filename)
938 contextWorld = bpy.data.worlds.new("Bitmap: " + realname)
939 context.scene.world = contextWorld
940 contextWorld.use_nodes = True
941 links = contextWorld.node_tree.links
942 nodes = contextWorld.node_tree.nodes
943 bitmap_mix = nodes.new(type='ShaderNodeMixRGB')
944 bitmapnode = nodes.new(type='ShaderNodeTexEnvironment')
945 bitmapping = nodes.new(type='ShaderNodeMapping')
946 bitmap_mix.label = "Background Mix"
947 bitmapnode.label = "Bitmap: " + bitmap_name
948 bitmap_mix.inputs[2].default_value = nodes['Background'].inputs[0].default_value
949 bitmapnode.image = load_image(bitmap_name, dirname, place_holder=False, recursive=IMAGE_SEARCH, check_existing=True)
950 bitmap_mix.inputs[0].default_value = 0.5 if bitmapnode.image is not None else 1.0
951 bitmapnode.location = (-520, 400)
952 bitmap_mix.location = (-200, 360)
953 bitmapping.location = (-740, 400)
954 coordinates = next((wn for wn in nodes if wn.type == 'TEX_COORD'), False)
955 links.new(bitmap_mix.outputs[0], nodes['Background'].inputs[0])
956 links.new(bitmapnode.outputs[0], bitmap_mix.inputs[1])
957 links.new(bitmapping.outputs[0], bitmapnode.inputs[0])
958 if not coordinates:
959 coordinates = nodes.new(type='ShaderNodeTexCoord')
960 coordinates.location = (-1340, 400)
961 if not bitmapping.inputs['Vector'].is_linked:
962 links.new(coordinates.outputs[0], bitmapping.inputs[0])
963 new_chunk.bytes_read += read_str_len
965 # If gradient chunk:
966 elif CreateWorld and new_chunk.ID == VGRADIENT:
967 if contextWorld is None:
968 path, filename = os.path.split(file.name)
969 realname, ext = os.path.splitext(filename)
970 contextWorld = bpy.data.worlds.new("Gradient: " + realname)
971 context.scene.world = contextWorld
972 contextWorld.use_nodes = True
973 links = contextWorld.node_tree.links
974 nodes = contextWorld.node_tree.nodes
975 gradientnode = nodes.new(type='ShaderNodeValToRGB')
976 layerweight = nodes.new(type='ShaderNodeLayerWeight')
977 conversion = nodes.new(type='ShaderNodeMath')
978 normalnode = nodes.new(type='ShaderNodeNormal')
979 coordinate = next((wn for wn in nodes if wn.type == 'TEX_COORD'), False)
980 backgroundmix = next((wn for wn in nodes if wn.type in {'MIX', 'MIX_RGB'}), False)
981 mappingnode = next((wn for wn in nodes if wn.type == 'MAPPING'), False)
982 conversion.location = (-740, -60)
983 layerweight.location = (-940, 170)
984 normalnode.location = (-1140, 300)
985 gradientnode.location = (-520, -20)
986 gradientnode.label = "Gradient"
987 conversion.operation = 'MULTIPLY_ADD'
988 conversion.name = conversion.label = "Multiply"
989 links.new(conversion.outputs[0], gradientnode.inputs[0])
990 links.new(layerweight.outputs[1], conversion.inputs[0])
991 links.new(layerweight.outputs[0], conversion.inputs[1])
992 links.new(normalnode.outputs[1], conversion.inputs[2])
993 links.new(normalnode.outputs[0], layerweight.inputs[1])
994 links.new(normalnode.outputs[1], layerweight.inputs[0])
995 if not coordinate:
996 coordinate = nodes.new(type='ShaderNodeTexCoord')
997 coordinate.location = (-1340, 400)
998 links.new(coordinate.outputs[6], normalnode.inputs[0])
999 if backgroundmix:
1000 links.new(gradientnode.outputs[0], backgroundmix.inputs[2])
1001 else:
1002 links.new(gradientnode.outputs[0], nodes['Background'].inputs[0])
1003 if mappingnode and not mappingnode.inputs['Vector'].is_linked:
1004 links.new(coordinate.outputs[0], mappingnode.inputs[0])
1005 gradientnode.color_ramp.elements.new(read_float(new_chunk))
1006 read_chunk(file, temp_chunk)
1007 if temp_chunk.ID == COLOR_F:
1008 gradientnode.color_ramp.elements[0].color[:3] = read_float_array(temp_chunk)
1009 elif temp_chunk.ID == LIN_COLOR_F:
1010 gradientnode.color_ramp.elements[0].color[:3] = read_float_array(temp_chunk)
1011 else:
1012 skip_to_end(file, temp_chunk)
1013 new_chunk.bytes_read += temp_chunk.bytes_read
1014 read_chunk(file, temp_chunk)
1015 if temp_chunk.ID == COLOR_F:
1016 gradientnode.color_ramp.elements[1].color[:3] = read_float_array(temp_chunk)
1017 elif temp_chunk.ID == LIN_COLOR_F:
1018 gradientnode.color_ramp.elements[1].color[:3] = read_float_array(temp_chunk)
1019 else:
1020 skip_to_end(file, temp_chunk)
1021 new_chunk.bytes_read += temp_chunk.bytes_read
1022 read_chunk(file, temp_chunk)
1023 if temp_chunk.ID == COLOR_F:
1024 gradientnode.color_ramp.elements[2].color[:3] = read_float_array(temp_chunk)
1025 elif temp_chunk.ID == LIN_COLOR_F:
1026 gradientnode.color_ramp.elements[2].color[:3] = read_float_array(temp_chunk)
1027 else:
1028 skip_to_end(file, temp_chunk)
1029 new_chunk.bytes_read += temp_chunk.bytes_read
1031 # If fog chunk:
1032 elif CreateWorld and new_chunk.ID == FOG:
1033 if contextWorld is None:
1034 path, filename = os.path.split(file.name)
1035 realname, ext = os.path.splitext(filename)
1036 contextWorld = bpy.data.worlds.new("Fog: " + realname)
1037 context.scene.world = contextWorld
1038 contextWorld.use_nodes = True
1039 links = contextWorld.node_tree.links
1040 nodes = contextWorld.node_tree.nodes
1041 fognode = nodes.new(type='ShaderNodeVolumeAbsorption')
1042 fognode.label = "Fog"
1043 fognode.location = (10, 20)
1044 volumemix = next((wn for wn in worldnodes if wn.name == "Volume" and wn.type in {'ADD_SHADER', 'MIX_SHADER'}), False)
1045 if volumemix:
1046 links.new(fognode.outputs[0], volumemix.inputs[1])
1047 else:
1048 links.new(fognode.outputs[0], nodes['World Output'].inputs[1])
1049 contextWorld.mist_settings.use_mist = True
1050 contextWorld.mist_settings.start = read_float(new_chunk)
1051 nearfog = read_float(new_chunk) * 0.01
1052 contextWorld.mist_settings.depth = read_float(new_chunk)
1053 farfog = read_float(new_chunk) * 0.01
1054 fognode.inputs[1].default_value = (nearfog + farfog) * 0.5
1055 read_chunk(file, temp_chunk)
1056 if temp_chunk.ID == COLOR_F:
1057 fognode.inputs[0].default_value[:3] = read_float_array(temp_chunk)
1058 elif temp_chunk.ID == LIN_COLOR_F:
1059 fognode.inputs[0].default_value[:3] = read_float_array(temp_chunk)
1060 else:
1061 skip_to_end(file, temp_chunk)
1062 new_chunk.bytes_read += temp_chunk.bytes_read
1063 elif CreateWorld and new_chunk.ID == FOG_BGND:
1064 pass
1066 # If layer fog chunk:
1067 elif CreateWorld and new_chunk.ID == LAYER_FOG:
1068 """Fog options flags are bit 20 (0x100000) for background fogging,
1069 bit 0 (0x1) for bottom falloff, and bit 1 (0x2) for top falloff."""
1070 if contextWorld is None:
1071 path, filename = os.path.split(file.name)
1072 realname, ext = os.path.splitext(filename)
1073 contextWorld = bpy.data.worlds.new("LayerFog: " + realname)
1074 context.scene.world = contextWorld
1075 contextWorld.use_nodes = True
1076 links = contextWorld.node_tree.links
1077 nodes = contextWorld.node_tree.nodes
1078 worldout = nodes.get("World Output")
1079 worldfog = worldout.inputs[1]
1080 litepath = nodes.new(type='ShaderNodeLightPath')
1081 layerfog = nodes.new(type='ShaderNodeVolumeScatter')
1082 fognode = next((wn for wn in worldnodes if wn.type == 'VOLUME_ABSORPTION'), False)
1083 if fognode:
1084 cuenode = next((wn for wn in worldnodes if wn.type == 'MAP_RANGE'), False)
1085 mxvolume = nodes.new(type='ShaderNodeMixShader')
1086 mxvolume.label = mxvolume.name = "Volume"
1087 mxvolume.location = (220, 0)
1088 cuesource = cuenode.outputs[0] if cuenode else litepath.outputs[7]
1089 cuetarget = cuenode.inputs[3] if cuenode else mxvolume.inputs[0]
1090 links.new(fognode.outputs[0], mxvolume.inputs[1])
1091 links.new(litepath.outputs[7], cuetarget)
1092 links.new(cuesource, mxvolume.inputs[0])
1093 links.new(mxvolume.outputs[0], worldfog)
1094 worldfog = mxvolume.inputs[2]
1095 layerfog.label = "Layer Fog"
1096 layerfog.location = (10, -120)
1097 worldout.location = (440, 160)
1098 litepath.location = (-200, 70)
1099 links.new(layerfog.outputs[0], worldfog)
1100 links.new(litepath.outputs[8], layerfog.inputs[2])
1101 links.new(litepath.outputs[2], nodes['Background'].inputs[1])
1102 contextWorld.mist_settings.use_mist = True
1103 contextWorld.mist_settings.start = read_float(new_chunk)
1104 contextWorld.mist_settings.height = read_float(new_chunk)
1105 density = read_float(new_chunk) # Density
1106 layerfog.inputs[1].default_value = density if density < 1 else density * 0.01
1107 layerfog_flag = read_long(new_chunk)
1108 if layerfog_flag == 0:
1109 contextWorld.mist_settings.falloff = 'LINEAR'
1110 if layerfog_flag & 0x1:
1111 contextWorld.mist_settings.falloff = 'QUADRATIC'
1112 if layerfog_flag & 0x2:
1113 contextWorld.mist_settings.falloff = 'INVERSE_QUADRATIC'
1114 read_chunk(file, temp_chunk)
1115 if temp_chunk.ID == COLOR_F:
1116 layerfog.inputs[0].default_value[:3] = read_float_array(temp_chunk)
1117 elif temp_chunk.ID == LIN_COLOR_F:
1118 layerfog.inputs[0].default_value[:3] = read_float_array(temp_chunk)
1119 else:
1120 skip_to_end(file, temp_chunk)
1121 new_chunk.bytes_read += temp_chunk.bytes_read
1123 # If distance cue chunk:
1124 elif CreateWorld and new_chunk.ID == DISTANCE_CUE:
1125 if contextWorld is None:
1126 path, filename = os.path.split(file.name)
1127 realname, ext = os.path.splitext(filename)
1128 contextWorld = bpy.data.worlds.new("DistanceCue: " + realname)
1129 context.scene.world = contextWorld
1130 contextWorld.use_nodes = True
1131 links = contextWorld.node_tree.links
1132 nodes = contextWorld.node_tree.nodes
1133 distcue_node = nodes.new(type='ShaderNodeMapRange')
1134 camera_data = nodes.new(type='ShaderNodeCameraData')
1135 distcue_node.label = "Distance Cue"
1136 distcue_node.clamp = False
1137 distcue_mix = next((wn for wn in worldnodes if wn.name == "Volume" and wn.type == 'MIX_SHADER'), False)
1138 distcuepath = next((wn for wn in worldnodes if wn.type == 'LIGHT_PATH'), False)
1139 if not distcuepath:
1140 distcuepath = nodes.new(type='ShaderNodeLightPath')
1141 distcue_node.location = (-940, 10)
1142 distcuepath.location = (-1140, 70)
1143 camera_data.location = (-1340, 170)
1144 raysource = distcuepath.outputs[7] if distcue_mix else distcuepath.outputs[0]
1145 raytarget = distcue_mix.inputs[0] if distcue_mix else nodes['Background'].inputs[1]
1146 links.new(camera_data.outputs[1], distcue_node.inputs[1])
1147 links.new(camera_data.outputs[2], distcue_node.inputs[0])
1148 links.new(raysource, distcue_node.inputs[4])
1149 links.new(distcue_node.outputs[0], raytarget)
1150 distcue_node.inputs[0].name = "Distance"
1151 distcue_node.inputs[2].name = "Near"
1152 distcue_node.inputs[3].name = "Far"
1153 distcue_node.inputs[1].default_value = read_float(new_chunk) # Near Cue
1154 distcue_node.inputs[2].default_value = read_float(new_chunk) # Near Dim
1155 distcue_node.inputs[4].default_value = contextWorld.light_settings.distance = read_float(new_chunk) # Far Cue
1156 distcue_node.inputs[3].default_value = read_float(new_chunk) # Far Dim
1157 elif CreateWorld and new_chunk.ID == DCUE_BGND:
1158 pass
1160 elif CreateWorld and new_chunk.ID in {USE_FOG, USE_LAYER_FOG}:
1161 context.view_layer.use_pass_mist = True
1163 # If object chunk - can be mesh, light and spot or camera
1164 elif new_chunk.ID == OBJECT:
1165 if CreateBlenderObject:
1166 putContextMesh(context, contextMesh_vertls, contextMesh_facels, contextMesh_flag,
1167 contextMeshMaterials, contextMesh_smooth, WORLD_MATRIX)
1169 contextMesh_vertls = []
1170 contextMesh_facels = []
1171 contextMeshMaterials = []
1172 contextMesh_flag = None
1173 contextMesh_smooth = None
1174 contextMeshUV = None
1175 contextMatrix = None
1177 CreateBlenderObject = True if CreateMesh else False
1178 CreateLightObject = CreateCameraObject = False
1179 contextObName, read_str_len = read_string(file)
1180 new_chunk.bytes_read += read_str_len
1182 # If mesh chunk
1183 elif new_chunk.ID == OBJECT_MESH:
1184 pass
1186 elif CreateMesh and new_chunk.ID == OBJECT_VERTICES:
1187 """Worldspace vertex locations"""
1188 num_verts = read_short(new_chunk)
1189 contextMesh_vertls = struct.unpack('<%df' % (num_verts * 3), file.read(SZ_3FLOAT * num_verts))
1190 new_chunk.bytes_read += SZ_3FLOAT * num_verts
1192 elif CreateMesh and new_chunk.ID == OBJECT_FACES:
1193 num_faces = read_short(new_chunk)
1194 temp_data = file.read(SZ_4U_SHORT * num_faces)
1195 new_chunk.bytes_read += SZ_4U_SHORT * num_faces # 4 short ints x 2 bytes each
1196 contextMesh_facels = struct.unpack('<%dH' % (num_faces * 4), temp_data)
1197 contextMesh_flag = [contextMesh_facels[i] for i in range(3, (num_faces * 4) + 3, 4)]
1198 contextMesh_facels = [contextMesh_facels[i - 3:i] for i in range(3, (num_faces * 4) + 3, 4)]
1200 elif CreateMesh and new_chunk.ID == OBJECT_MATERIAL:
1201 material_name, read_str_len = read_string(file)
1202 new_chunk.bytes_read += read_str_len # remove 1 null character.
1203 num_faces_using_mat = read_short(new_chunk)
1204 temp_data = file.read(SZ_U_SHORT * num_faces_using_mat)
1205 new_chunk.bytes_read += SZ_U_SHORT * num_faces_using_mat
1206 temp_data = struct.unpack('<%dH' % (num_faces_using_mat), temp_data)
1207 contextMeshMaterials.append((material_name, temp_data))
1208 # look up the material in all the materials
1210 elif CreateMesh and new_chunk.ID == OBJECT_SMOOTH:
1211 temp_data = file.read(SZ_U_INT * num_faces)
1212 smoothgroup = struct.unpack('<%dI' % (num_faces), temp_data)
1213 new_chunk.bytes_read += SZ_U_INT * num_faces
1214 contextMesh_smooth = smoothgroup
1216 elif CreateMesh and new_chunk.ID == OBJECT_UV:
1217 num_uv = read_short(new_chunk)
1218 temp_data = file.read(SZ_2FLOAT * num_uv)
1219 new_chunk.bytes_read += SZ_2FLOAT * num_uv
1220 contextMeshUV = struct.unpack('<%df' % (num_uv * 2), temp_data)
1222 elif CreateMesh and new_chunk.ID == OBJECT_TRANS_MATRIX:
1223 # How do we know the matrix size? 54 == 4x4 48 == 4x3
1224 temp_data = file.read(SZ_4x3MAT)
1225 mtx = list(struct.unpack('<ffffffffffff', temp_data))
1226 new_chunk.bytes_read += SZ_4x3MAT
1227 contextMatrix = mathutils.Matrix(
1228 (mtx[:3] + [0], mtx[3:6] + [0], mtx[6:9] + [0], mtx[9:] + [1])).transposed()
1230 # If hierarchy chunk
1231 elif CreateMesh and new_chunk.ID == OBJECT_HIERARCHY:
1232 child_id = get_hierarchy(new_chunk)
1233 elif CreateMesh and new_chunk.ID == OBJECT_PARENT:
1234 get_parent(new_chunk, child_id)
1236 # If light chunk
1237 elif new_chunk.ID == OBJECT_LIGHT: # Basic lamp support
1238 CreateBlenderObject = False
1239 if not CreateLight:
1240 contextObName = None
1241 skip_to_end(file, new_chunk)
1242 else:
1243 CreateLightObject = True
1244 light = bpy.data.lights.new(contextObName, 'POINT')
1245 contextLamp = bpy.data.objects.new(contextObName, light)
1246 context.view_layer.active_layer_collection.collection.objects.link(contextLamp)
1247 imported_objects.append(contextLamp)
1248 object_dictionary[contextObName] = contextLamp
1249 contextLamp.data.use_shadow = False
1250 contextLamp.location = read_float_array(new_chunk) # Position
1251 contextMatrix = None # Reset matrix
1252 elif CreateLightObject and new_chunk.ID == COLOR_F: # Color
1253 contextLamp.data.color = read_float_array(new_chunk)
1254 elif CreateLightObject and new_chunk.ID == LIGHT_OUTER_RANGE: # Distance
1255 contextLamp.data.cutoff_distance = read_float(new_chunk)
1256 elif CreateLightObject and new_chunk.ID == LIGHT_INNER_RANGE: # Radius
1257 contextLamp.data.shadow_soft_size = (read_float(new_chunk) * 0.01)
1258 elif CreateLightObject and new_chunk.ID == LIGHT_MULTIPLIER: # Intensity
1259 contextLamp.data.energy = (read_float(new_chunk) * 1000)
1260 elif CreateLightObject and new_chunk.ID == LIGHT_ATTENUATE: # Attenuation
1261 contextLamp.data.use_custom_distance = True
1263 # If spotlight chunk
1264 elif CreateLightObject and new_chunk.ID == LIGHT_SPOTLIGHT: # Spotlight
1265 contextLamp.data.type = 'SPOT'
1266 spot = mathutils.Vector(read_float_array(new_chunk)) # Spot location
1267 aim = calc_target(contextLamp.location, spot) # Target
1268 contextLamp.rotation_euler.x = aim[0]
1269 contextLamp.rotation_euler.z = aim[1]
1270 hotspot = read_float(new_chunk) # Hotspot
1271 beam_angle = read_float(new_chunk) # Beam angle
1272 contextLamp.data.spot_size = math.radians(beam_angle)
1273 contextLamp.data.spot_blend = 1.0 - (hotspot / beam_angle)
1274 elif CreateLightObject and new_chunk.ID == LIGHT_SPOT_ROLL: # Roll
1275 contextLamp.rotation_euler.y = read_float(new_chunk)
1276 elif CreateLightObject and new_chunk.ID == LIGHT_SPOT_SHADOWED: # Shadow flag
1277 contextLamp.data.use_shadow = True
1278 elif CreateLightObject and new_chunk.ID == LIGHT_LOCAL_SHADOW2: # Shadow parameters
1279 contextLamp.data.shadow_buffer_bias = read_float(new_chunk)
1280 contextLamp.data.shadow_buffer_clip_start = (read_float(new_chunk) * 0.1)
1281 temp_data = file.read(SZ_U_SHORT)
1282 new_chunk.bytes_read += SZ_U_SHORT
1283 elif CreateLightObject and new_chunk.ID == LIGHT_SPOT_SEE_CONE: # Cone flag
1284 contextLamp.data.show_cone = True
1285 elif CreateLightObject and new_chunk.ID == LIGHT_SPOT_RECTANGLE: # Square flag
1286 contextLamp.data.use_square = True
1287 elif CreateLightObject and new_chunk.ID == LIGHT_SPOT_ASPECT: # Aspect
1288 contextLamp.empty_display_size = read_float(new_chunk)
1289 elif CreateLightObject and new_chunk.ID == LIGHT_SPOT_PROJECTOR: # Projection
1290 contextLamp.data.use_nodes = True
1291 nodes = contextLamp.data.node_tree.nodes
1292 links = contextLamp.data.node_tree.links
1293 mix = nodes.new(type='ShaderNodeMixRGB')
1294 rgb = nodes.new(type='ShaderNodeRGB')
1295 mix.blend_type = 'LINEAR_LIGHT'
1296 mix.label = "Emission Color"
1297 emit = nodes.get("Emission")
1298 emit.label = "Projector"
1299 emit.location = (80, 300)
1300 rgb.location = (-380, 60)
1301 mix.location = (-140, 340)
1302 gobo_name, read_str_len = read_string(file)
1303 new_chunk.bytes_read += read_str_len
1304 projection = nodes.new(type='ShaderNodeTexImage')
1305 promapping = nodes.new(type='ShaderNodeMapping')
1306 protxcoord = nodes.new(type='ShaderNodeTexCoord')
1307 prolitpath = nodes.new(type='ShaderNodeLightPath')
1308 prolitfall = nodes.new(type='ShaderNodeLightFalloff')
1309 projection.label = "Gobo: " + gobo_name
1310 protxcoord.label = "Gobo Coordinate"
1311 promapping.vector_type = 'TEXTURE'
1312 prolitfall.location = (-720, 20)
1313 projection.location = (-480, 440)
1314 promapping.location = (-720, 440)
1315 protxcoord.location = (-940, 440)
1316 prolitpath.location = (-940, 180)
1317 projection.image = load_image(gobo_name, dirname, place_holder=False, recursive=IMAGE_SEARCH, check_existing=True)
1318 emit.inputs[0].default_value[:3] = mix.inputs[2].default_value[:3] = rgb.outputs[0].default_value[:3] = contextLamp.data.color
1319 links.new(emit.outputs[0], nodes['Light Output'].inputs[0])
1320 links.new(promapping.outputs[0], projection.inputs[0])
1321 links.new(protxcoord.outputs[2], promapping.inputs[0])
1322 links.new(prolitpath.outputs[8], prolitfall.inputs[0])
1323 links.new(prolitpath.outputs[7], prolitfall.inputs[1])
1324 links.new(prolitfall.outputs[1], emit.inputs[1])
1325 links.new(prolitfall.outputs[0], mix.inputs[0])
1326 links.new(projection.outputs[0], mix.inputs[1])
1327 links.new(mix.outputs[0], emit.inputs[0])
1328 links.new(rgb.outputs[0], mix.inputs[2])
1329 elif CreateLightObject and new_chunk.ID == OBJECT_HIERARCHY: # Hierarchy
1330 child_id = get_hierarchy(new_chunk)
1331 elif CreateLightObject and new_chunk.ID == OBJECT_PARENT:
1332 get_parent(new_chunk, child_id)
1334 # If camera chunk
1335 elif new_chunk.ID == OBJECT_CAMERA: # Basic camera support
1336 CreateBlenderObject = False
1337 if not CreateCamera:
1338 contextObName = None
1339 skip_to_end(file, new_chunk)
1340 else:
1341 CreateCameraObject = True
1342 camera = bpy.data.cameras.new(contextObName)
1343 contextCamera = bpy.data.objects.new(contextObName, camera)
1344 context.view_layer.active_layer_collection.collection.objects.link(contextCamera)
1345 imported_objects.append(contextCamera)
1346 object_dictionary[contextObName] = contextCamera
1347 contextCamera.location = read_float_array(new_chunk) # Position
1348 focus = mathutils.Vector(read_float_array(new_chunk))
1349 direction = calc_target(contextCamera.location, focus) # Target
1350 contextCamera.rotation_euler.x = direction[0]
1351 contextCamera.rotation_euler.y = read_float(new_chunk) # Roll
1352 contextCamera.rotation_euler.z = direction[1]
1353 contextCamera.data.lens = read_float(new_chunk) # Focal length
1354 contextMatrix = None # Reset matrix
1355 elif CreateCameraObject and new_chunk.ID == OBJECT_CAM_RANGES: # Range
1356 camrange = read_float(new_chunk)
1357 startrange = camrange if camrange >= 0.01 else 0.1
1358 contextCamera.data.clip_start = startrange * CONSTRAIN
1359 contextCamera.data.clip_end = read_float(new_chunk) * CONSTRAIN
1360 elif CreateCameraObject and new_chunk.ID == OBJECT_HIERARCHY: # Hierarchy
1361 child_id = get_hierarchy(new_chunk)
1362 elif CreateCameraObject and new_chunk.ID == OBJECT_PARENT:
1363 get_parent(new_chunk, child_id)
1365 # start keyframe section
1366 elif new_chunk.ID == EDITKEYFRAME:
1367 pass
1369 elif KEYFRAME and new_chunk.ID == KFDATA_KFSEG:
1370 start = read_long(new_chunk)
1371 context.scene.frame_start = start
1372 stop = read_long(new_chunk)
1373 context.scene.frame_end = stop
1375 elif KEYFRAME and new_chunk.ID == KFDATA_CURTIME:
1376 current = read_long(new_chunk)
1377 context.scene.frame_current = current
1379 # including these here means their OB_NODE_HDR are scanned
1380 elif new_chunk.ID in {KF_AMBIENT, KF_OBJECT, KF_OBJECT_CAMERA, KF_OBJECT_LIGHT, KF_OBJECT_SPOT_LIGHT}:
1381 tracktype = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[1]
1382 tracking = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[-1]
1383 spotting = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[-2]
1384 object_id = hierarchy = ROOT_OBJECT
1385 child = None
1386 if not CreateWorld and tracking == 'AMBIENT':
1387 skip_to_end(file, new_chunk)
1388 if not CreateLight and tracking == 'LIGHT':
1389 skip_to_end(file, new_chunk)
1390 if not CreateCamera and tracking == 'CAMERA':
1391 skip_to_end(file, new_chunk)
1393 elif CreateTrackData and new_chunk.ID in {KF_TARGET_CAMERA, KF_TARGET_LIGHT}:
1394 tracktype = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[1]
1395 tracking = str([kf for kf,ck in globals().items() if ck == new_chunk.ID][0]).split("_")[-1]
1396 child = None
1397 if not CreateLight and tracking == 'LIGHT':
1398 skip_to_end(file, new_chunk)
1399 if not CreateCamera and tracking == 'CAMERA':
1400 skip_to_end(file, new_chunk)
1402 elif new_chunk.ID == OBJECT_NODE_ID:
1403 object_id = read_short(new_chunk)
1405 elif new_chunk.ID == OBJECT_NODE_HDR:
1406 object_name, read_str_len = read_string(file)
1407 new_chunk.bytes_read += read_str_len
1408 temp_data = file.read(SZ_U_INT)
1409 new_chunk.bytes_read += SZ_U_INT
1410 hierarchy = read_short(new_chunk)
1411 child = object_dictionary.get(object_name)
1412 if child is None:
1413 if CreateWorld and tracking == 'AMBIENT':
1414 child = context.scene.world
1415 child.use_nodes = True
1416 nodetree = child.node_tree
1417 links = nodetree.links
1418 nodes = nodetree.nodes
1419 backlite = nodes.get("Background")
1420 worldout = nodes.get("World Output")
1421 ambilite = nodes.new(type='ShaderNodeRGB')
1422 raymixer = nodes.new(type='ShaderNodeMix')
1423 mathnode = nodes.new(type='ShaderNodeMath')
1424 ambinode = nodes.new(type='ShaderNodeEmission')
1425 mixshade = nodes.new(type='ShaderNodeMixShader')
1426 litefall = nodes.new(type='ShaderNodeLightFalloff')
1427 raymixer.label = "Ambient Mix"
1428 ambilite.label = "Ambient Color"
1429 raymixer.inputs[3].name = "Ambient"
1430 raymixer.inputs[2].name = "Background"
1431 mixshade.label = mixshade.name = "Surface"
1432 litepath = next((n for n in nodes if n.type == 'LIGHT_PATH'), False)
1433 ambinode.inputs[0].default_value[:3] = child.color
1434 if not litepath:
1435 litepath = nodes.new('ShaderNodeLightPath')
1436 ambinode.location = (10, 160)
1437 worldout.location = (440, 160)
1438 mixshade.location = (220, 280)
1439 ambilite.location = (-200, -30)
1440 raymixer.location = (-200, 170)
1441 litepath.location = (-1340, 70)
1442 mathnode.location = (-1140, 100)
1443 litefall.location = (-1140, -80)
1444 links.new(litepath.outputs[0], mathnode.inputs[0])
1445 links.new(litepath.outputs[3], mathnode.inputs[1])
1446 links.new(litepath.outputs[5], litefall.inputs[0])
1447 links.new(litepath.outputs[2], litefall.inputs[1])
1448 links.new(litefall.outputs[0], raymixer.inputs[2])
1449 links.new(mathnode.outputs[0], backlite.inputs[1])
1450 links.new(mathnode.outputs[0], ambinode.inputs[1])
1451 links.new(mathnode.outputs[0], raymixer.inputs[3])
1452 links.new(raymixer.outputs[0], mixshade.inputs[0])
1453 links.new(ambinode.outputs[0], mixshade.inputs[2])
1454 links.new(ambilite.outputs[0], ambinode.inputs[0])
1455 links.new(mixshade.outputs[0], worldout.inputs[0])
1456 links.new(backlite.outputs[0], mixshade.inputs[1])
1457 ambinode.label = object_name if object_name != '$AMBIENT$' else "Ambient"
1458 elif CreateEmpty and tracking == 'OBJECT' and object_name == '$$$DUMMY':
1459 child = bpy.data.objects.new(object_name, None) # Create an empty object
1460 context.view_layer.active_layer_collection.collection.objects.link(child)
1461 imported_objects.append(child)
1462 else:
1463 tracking = tracktype = None
1464 if tracktype != 'TARGET' and tracking != 'AMBIENT':
1465 object_dict[object_id] = child
1466 object_list.append(child)
1467 object_parent.append(hierarchy)
1468 pivot_list.append(mathutils.Vector((0.0, 0.0, 0.0)))
1470 elif new_chunk.ID == PARENT_NAME:
1471 parent_name, read_str_len = read_string(file)
1472 parent_dictionary.setdefault(parent_name, []).append(child)
1473 new_chunk.bytes_read += read_str_len
1475 elif new_chunk.ID == OBJECT_INSTANCE_NAME and tracking == 'OBJECT':
1476 instance_name, read_str_len = read_string(file)
1477 if child.name == '$$$DUMMY':
1478 child.name = instance_name
1479 else: # Child is an instance
1480 child = child.copy()
1481 child.name = object_name + "." + instance_name
1482 context.view_layer.active_layer_collection.collection.objects.link(child)
1483 object_dict[object_id] = child
1484 object_list[-1] = child
1485 object_dictionary[child.name] = child
1486 new_chunk.bytes_read += read_str_len
1488 elif new_chunk.ID == OBJECT_PIVOT and tracking == 'OBJECT': # Pivot
1489 pivot = read_float_array(new_chunk)
1490 pivot_list[len(pivot_list) - 1] = mathutils.Vector(pivot)
1492 elif new_chunk.ID == MORPH_SMOOTH and tracking == 'OBJECT': # Smooth angle
1493 smooth_angle = read_float(new_chunk)
1494 if child.data is not None: # Check if child is a dummy
1495 child.data.set_sharp_from_angle(angle=smooth_angle)
1497 elif KEYFRAME and new_chunk.ID == COL_TRACK_TAG and tracking == 'AMBIENT': # Ambient
1498 keyframe_data = {}
1499 keyframe_data[0] = ambinode.inputs[0].default_value[:3] = child.color[:]
1500 child.color = read_track_data(new_chunk)[0]
1501 ambilite.outputs[0].default_value[:3] = child.color
1502 for keydata in keyframe_data.items():
1503 child.color = ambilite.outputs[0].default_value[:3] = keydata[1]
1504 child.keyframe_insert(data_path="color", frame=keydata[0])
1505 nodetree.keyframe_insert(data_path="nodes[\"RGB\"].outputs[0].default_value", frame=keydata[0])
1506 contextTrack_flag = False
1508 elif KEYFRAME and new_chunk.ID == COL_TRACK_TAG and tracking == 'LIGHT': # Color
1509 keyframe_data = {}
1510 keyframe_data[0] = child.data.color[:]
1511 child.data.color = read_track_data(new_chunk)[0]
1512 child.data.use_nodes = True
1513 tree = child.data.node_tree
1514 emitnode = tree.nodes.get("Emission")
1515 emitnode.inputs[0].default_value[:3] = child.data.color
1516 colornode = next((nd for nd in tree.nodes if nd.type == 'RGB'), False)
1517 lightfall = next((nd for nd in tree.nodes if nd.type == 'LIGHT_FALLOFF'), False)
1518 if not colornode:
1519 colornode = tree.nodes.new('ShaderNodeRGB')
1520 colornode.location = (-380, 60)
1521 tree.links.new(colornode.outputs[0], emitnode.inputs[0])
1522 if not lightfall:
1523 lightfall = tree.nodes.new('ShaderNodeLightFalloff')
1524 lightpath = tree.nodes.new('ShaderNodeLightPath')
1525 lightfall.location = (-720, 20)
1526 lightpath.location = (-940, 180)
1527 tree.links.new(lightpath.outputs[8], lightfall.inputs[0])
1528 tree.links.new(lightpath.outputs[7], lightfall.inputs[1])
1529 tree.links.new(lightfall.outputs[1], emitnode.inputs[1])
1530 colornode.outputs[0].default_value[:3] = child.data.color
1531 for keydata in keyframe_data.items():
1532 child.data.color = colornode.outputs[0].default_value[:3] = keydata[1]
1533 child.data.keyframe_insert(data_path="color", frame=keydata[0])
1534 tree.keyframe_insert(data_path="nodes[\"RGB\"].outputs[0].default_value", frame=keydata[0])
1535 contextTrack_flag = False
1537 elif KEYFRAME and new_chunk.ID == POS_TRACK_TAG and tracktype == 'OBJECT': # Translation
1538 keyframe_data = {}
1539 keyframe_data[0] = child.location[:]
1540 trackpos = mathutils.Vector(read_track_data(new_chunk)[0])
1541 loca_mtx = mathutils.Matrix.Translation(-1*trackpos)
1542 matrix_transform[child.name] = loca_mtx
1543 child.location = trackpos
1544 if child.type in {'LIGHT', 'CAMERA'}:
1545 trackposition[0] = child.location
1546 CreateTrackData = True
1547 if contextTrack_flag & 0x8: # Flag 0x8 locks X axis
1548 child.lock_location[0] = True
1549 if contextTrack_flag & 0x10: # Flag 0x10 locks Y axis
1550 child.lock_location[1] = True
1551 if contextTrack_flag & 0x20: # Flag 0x20 locks Z axis
1552 child.lock_location[2] = True
1553 for keydata in keyframe_data.items():
1554 trackposition[keydata[0]] = keydata[1] # Keep track to position for target calculation
1555 child.location = apply_constrain(keydata[1]) if hierarchy == ROOT_OBJECT else mathutils.Vector(keydata[1])
1556 if MEASURE != 1.0:
1557 child.location = child.location * MEASURE
1558 if hierarchy == ROOT_OBJECT:
1559 child.location.rotate(CONVERSE)
1560 if not contextTrack_flag & 0x100: # Flag 0x100 unlinks X axis
1561 child.keyframe_insert(data_path="location", index=0, frame=keydata[0])
1562 if not contextTrack_flag & 0x200: # Flag 0x200 unlinks Y axis
1563 child.keyframe_insert(data_path="location", index=1, frame=keydata[0])
1564 if not contextTrack_flag & 0x400: # Flag 0x400 unlinks Z axis
1565 child.keyframe_insert(data_path="location", index=2, frame=keydata[0])
1566 contextTrack_flag = False
1568 elif KEYFRAME and new_chunk.ID == POS_TRACK_TAG and tracktype == 'TARGET': # Target position
1569 keyframe_data = {}
1570 location = child.location
1571 keyframe_data[0] = trackposition[0]
1572 target = mathutils.Vector(read_track_data(new_chunk)[0])
1573 direction = calc_target(location, target)
1574 child.rotation_euler.x = direction[0]
1575 child.rotation_euler.z = direction[1]
1576 for keydata in keyframe_data.items():
1577 track = trackposition.get(keydata[0], child.location)
1578 locate = mathutils.Vector(track)
1579 target = mathutils.Vector(keydata[1])
1580 direction = calc_target(locate, target)
1581 rotate = mathutils.Euler((direction[0], 0.0, direction[1]), 'XYZ').to_matrix()
1582 scale = mathutils.Vector.Fill(3, (CONSTRAIN * 0.1)) if CONSTRAIN != 0.0 else child.scale
1583 transformation = mathutils.Matrix.LocRotScale(locate, rotate, scale)
1584 child.matrix_world = transformation
1585 if MEASURE != 1.0:
1586 child.matrix_world = mathutils.Matrix.Scale(MEASURE,4) @ child.matrix_world
1587 if hierarchy == ROOT_OBJECT:
1588 child.matrix_world = CONVERSE @ child.matrix_world
1589 child.keyframe_insert(data_path="rotation_euler", index=0, frame=keydata[0])
1590 child.keyframe_insert(data_path="rotation_euler", index=2, frame=keydata[0])
1591 contextTrack_flag = False
1593 elif KEYFRAME and new_chunk.ID == ROT_TRACK_TAG and tracktype == 'OBJECT': # Rotation
1594 keyframe_rotation = {}
1595 keyframe_rotation[0] = child.rotation_axis_angle[:]
1596 tflags = read_short(new_chunk)
1597 temp_data = file.read(SZ_U_INT * 2)
1598 new_chunk.bytes_read += SZ_U_INT * 2
1599 nkeys = read_long(new_chunk)
1600 if tflags & 0x8: # Flag 0x8 locks X axis
1601 child.lock_rotation[0] = True
1602 if tflags & 0x10: # Flag 0x10 locks Y axis
1603 child.lock_rotation[1] = True
1604 if tflags & 0x20: # Flag 0x20 locks Z axis
1605 child.lock_rotation[2] = True
1606 for i in range(nkeys):
1607 nframe = read_long(new_chunk)
1608 nflags = read_short(new_chunk)
1609 for f in range(bin(nflags)[-5:].count('1')):
1610 temp_data = file.read(SZ_FLOAT) # Check for spline term values
1611 new_chunk.bytes_read += SZ_FLOAT
1612 temp_data = file.read(SZ_4FLOAT)
1613 rotation = struct.unpack('<4f', temp_data)
1614 new_chunk.bytes_read += SZ_4FLOAT
1615 keyframe_rotation[nframe] = rotation
1616 rad, axis_x, axis_y, axis_z = keyframe_rotation[0]
1617 trackrot = mathutils.Quaternion((axis_x, axis_y, axis_z), -rad) # Why negative?
1618 rota_mtx = mathutils.Matrix.Rotation(trackrot.angle, 4, trackrot.axis)
1619 transrot = matrix_transform.get(child.name)
1620 if transrot is not None:
1621 matrix_transform[child.name] = rota_mtx.inverted() @ transrot
1622 child.rotation_euler = trackrot.to_euler()
1623 for keydata in keyframe_rotation.items():
1624 rad, axis_x, axis_y, axis_z = keydata[1]
1625 child.rotation_euler = mathutils.Quaternion((axis_x, axis_y, axis_z), -rad).to_euler()
1626 if hierarchy == ROOT_OBJECT:
1627 child.rotation_euler.rotate(CONVERSE)
1628 if not tflags & 0x100: # Flag 0x100 unlinks X axis
1629 child.keyframe_insert(data_path="rotation_euler", index=0, frame=keydata[0])
1630 if not tflags & 0x200: # Flag 0x200 unlinks Y axis
1631 child.keyframe_insert(data_path="rotation_euler", index=1, frame=keydata[0])
1632 if not tflags & 0x400: # Flag 0x400 unlinks Z axis
1633 child.keyframe_insert(data_path="rotation_euler", index=2, frame=keydata[0])
1635 elif KEYFRAME and new_chunk.ID == SCL_TRACK_TAG and tracktype == 'OBJECT': # Scale
1636 keyframe_data = {}
1637 keyframe_data[0] = child.scale[:]
1638 trackscale = mathutils.Vector(read_track_data(new_chunk)[0])
1639 scale_mtx = mathutils.Matrix.Diagonal(trackscale)
1640 transscale = matrix_transform.get(child.name)
1641 if transscale is not None:
1642 matrix_transform[child.name] = scale_mtx.to_4x4() @ transscale
1643 child.scale = trackscale
1644 if contextTrack_flag & 0x8: # Flag 0x8 locks X axis
1645 child.lock_scale[0] = True
1646 if contextTrack_flag & 0x10: # Flag 0x10 locks Y axis
1647 child.lock_scale[1] = True
1648 if contextTrack_flag & 0x20: # Flag 0x20 locks Z axis
1649 child.lock_scale[2] = True
1650 for keydata in keyframe_data.items():
1651 child.scale = apply_constrain(keydata[1]) if hierarchy == ROOT_OBJECT else mathutils.Vector(keydata[1])
1652 if not contextTrack_flag & 0x100: # Flag 0x100 unlinks X axis
1653 child.keyframe_insert(data_path="scale", index=0, frame=keydata[0])
1654 if not contextTrack_flag & 0x200: # Flag 0x200 unlinks Y axis
1655 child.keyframe_insert(data_path="scale", index=1, frame=keydata[0])
1656 if not contextTrack_flag & 0x400: # Flag 0x400 unlinks Z axis
1657 child.keyframe_insert(data_path="scale", index=2, frame=keydata[0])
1658 contextTrack_flag = False
1660 elif KEYFRAME and new_chunk.ID == ROLL_TRACK_TAG and tracktype == 'OBJECT': # Roll angle
1661 keyframe_angle = {}
1662 keyframe_angle[0] = child.rotation_euler.y
1663 child.rotation_euler.y = read_track_angle(new_chunk)[0]
1664 for keydata in keyframe_angle.items():
1665 child.rotation_euler.y = keydata[1]
1666 if hierarchy == ROOT_OBJECT:
1667 child.rotation_euler.rotate(CONVERSE)
1668 child.keyframe_insert(data_path="rotation_euler", index=1, frame=keydata[0])
1670 elif KEYFRAME and new_chunk.ID == FOV_TRACK_TAG and tracking == 'CAMERA': # Field of view
1671 keyframe_angle = {}
1672 keyframe_angle[0] = child.data.angle
1673 child.data.angle = read_track_angle(new_chunk)[0]
1674 for keydata in keyframe_angle.items():
1675 child.data.lens = (child.data.sensor_width / 2) / math.tan(keydata[1] / 2)
1676 child.data.keyframe_insert(data_path="lens", frame=keydata[0])
1678 elif KEYFRAME and new_chunk.ID == HOTSPOT_TRACK_TAG and tracking == 'LIGHT' and spotting == 'SPOT': # Hotspot
1679 keyframe_angle = {}
1680 cone_angle = math.degrees(child.data.spot_size)
1681 keyframe_angle[0] = cone_angle-(child.data.spot_blend * math.floor(cone_angle))
1682 hot_spot = math.degrees(read_track_angle(new_chunk)[0])
1683 child.data.spot_blend = 1.0 - (hot_spot / cone_angle)
1684 for keydata in keyframe_angle.items():
1685 child.data.spot_blend = 1.0 - (math.degrees(keydata[1]) / cone_angle)
1686 child.data.keyframe_insert(data_path="spot_blend", frame=keydata[0])
1688 elif KEYFRAME and new_chunk.ID == FALLOFF_TRACK_TAG and tracking == 'LIGHT' and spotting == 'SPOT': # Falloff
1689 keyframe_angle = {}
1690 keyframe_angle[0] = math.degrees(child.data.spot_size)
1691 child.data.spot_size = read_track_angle(new_chunk)[0]
1692 for keydata in keyframe_angle.items():
1693 child.data.spot_size = keydata[1]
1694 child.data.keyframe_insert(data_path="spot_size", frame=keydata[0])
1696 else:
1697 buffer_size = new_chunk.length - new_chunk.bytes_read
1698 binary_format = '%ic' % buffer_size
1699 temp_data = file.read(struct.calcsize(binary_format))
1700 new_chunk.bytes_read += buffer_size
1702 # update the previous chunk bytes read
1703 previous_chunk.bytes_read += new_chunk.bytes_read
1705 # FINISHED LOOP
1706 # There will be a number of objects still not added
1707 if CreateBlenderObject:
1708 putContextMesh(context, contextMesh_vertls, contextMesh_facels, contextMesh_flag,
1709 contextMeshMaterials, contextMesh_smooth, WORLD_MATRIX)
1711 # Fix transform
1712 if APPLY_MATRIX:
1713 for obj, mtx in matrix_transform.items():
1714 cld = object_dictionary.get(obj)
1715 if (cld and cld.data) and cld.type == 'MESH':
1716 cld.data.transform(mtx)
1718 # Assign parents to objects
1719 # Check if we need to assign first because doing so recalcs the depsgraph
1720 for ind, ob in enumerate(object_list):
1721 if ob is None:
1722 continue
1723 parent = object_parent[ind]
1724 if parent == ROOT_OBJECT:
1725 ob.parent = None
1726 elif parent not in object_dict:
1727 try:
1728 ob.parent = object_list[parent]
1729 except Exception as exc:
1730 print("\tError: ", exc)
1731 else: # get parent from node_id number
1732 try:
1733 ob.parent = object_dict.get(parent)
1734 except: # self to parent exception
1735 pass
1737 #pivot_list[ind] += pivot_list[parent] # Not sure this is correct, should parent space matrix be applied before combining?
1739 # if parent name
1740 parent_dictionary.pop(None, ...)
1741 for par, objs in parent_dictionary.items():
1742 parent = object_dictionary.get(par)
1743 for ob in objs:
1744 if parent is not None:
1745 ob.parent = parent
1746 parent_dictionary.clear()
1748 # If hierarchy
1749 hierarchy = dict(zip(childs_list, parent_list))
1750 hierarchy.pop(None, ...)
1751 for idt, (child, parent) in enumerate(hierarchy.items()):
1752 child_obj = object_dictionary.get(child)
1753 parent_obj = object_dictionary.get(parent)
1754 if child_obj and parent_obj is not None:
1755 child_obj.parent = parent_obj
1757 # fix pivots
1758 for ind, ob in enumerate(object_list):
1759 if ob is None:
1760 continue
1761 elif ob.type == 'MESH':
1762 pivot = pivot_list[ind]
1763 pivot_matrix = object_matrix.get(ob, mathutils.Matrix()) # unlikely to fail
1764 pivot_matrix = mathutils.Matrix.Translation(-1 * pivot)
1765 # pivot_matrix = mathutils.Matrix.Translation(pivot_matrix.to_3x3() @ -pivot)
1766 ob.data.transform(pivot_matrix)
1767 if APPLY_MATRIX:
1768 cld = ob
1769 mat = mathutils.Matrix()
1770 while cld.parent:
1771 trans = matrix_transform.get(cld.parent.name)
1772 if trans is not None:
1773 mat = trans @ mat
1774 cld = cld.parent
1775 if ob.type == 'MESH' and ob.data and ob.parent:
1776 ob.data.transform(mat)
1779 ##########
1780 # IMPORT #
1781 ##########
1783 def load_3ds(filepath, context, CONSTRAIN=10.0, UNITS=False, IMAGE_SEARCH=True,
1784 FILTER=None, WORLD_MATRIX=False, KEYFRAME=True, APPLY_MATRIX=True,
1785 CONVERSE=None, CURSOR=False, PIVOT=False):
1787 print("importing 3DS: %r..." % (filepath), end="")
1789 if bpy.ops.object.select_all.poll():
1790 bpy.ops.object.select_all(action='DESELECT')
1792 MEASURE = 1.0
1793 duration = time.time()
1794 current_chunk = Chunk()
1795 file = open(filepath, 'rb')
1797 # here we go!
1798 read_chunk(file, current_chunk)
1799 if current_chunk.ID != PRIMARY:
1800 print("\tFatal Error: Not a valid 3ds file: %r" % filepath)
1801 file.close()
1802 return
1804 if CONSTRAIN:
1805 BOUNDS_3DS[:] = [1 << 30, 1 << 30, 1 << 30, -1 << 30, -1 << 30, -1 << 30]
1806 else:
1807 del BOUNDS_3DS[:]
1809 # fixme, make unglobal, clear in case
1810 object_dictionary.clear()
1811 matrix_transform.clear()
1812 object_matrix.clear()
1813 scn = context.scene
1815 if UNITS:
1816 unit_length = scn.unit_settings.length_unit
1817 if unit_length == 'MILES':
1818 MEASURE = 1609.344
1819 elif unit_length == 'KILOMETERS':
1820 MEASURE = 1000.0
1821 elif unit_length == 'FEET':
1822 MEASURE = 0.3048
1823 elif unit_length == 'INCHES':
1824 MEASURE = 0.0254
1825 elif unit_length == 'CENTIMETERS':
1826 MEASURE = 0.01
1827 elif unit_length == 'MILLIMETERS':
1828 MEASURE = 0.001
1829 elif unit_length == 'THOU':
1830 MEASURE = 0.0000254
1831 elif unit_length == 'MICROMETERS':
1832 MEASURE = 0.000001
1834 context.window.cursor_set('WAIT')
1835 imported_objects = [] # Fill this list with objects
1836 process_next_chunk(context, file, current_chunk, imported_objects, CONSTRAIN, FILTER,
1837 IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME, APPLY_MATRIX, CONVERSE, MEASURE, CURSOR)
1839 # fixme, make unglobal
1840 matrix_transform.clear()
1841 object_dictionary.clear()
1842 object_matrix.clear()
1845 if APPLY_MATRIX:
1846 for ob in imported_objects:
1847 if ob.type == 'MESH':
1848 ob.data.transform(ob.matrix_local.inverted())
1851 if UNITS:
1852 unit_mtx = mathutils.Matrix.Scale(MEASURE,4)
1853 for ob in imported_objects:
1854 if ob.type == 'MESH':
1855 ob.data.transform(unit_mtx)
1857 if CONVERSE and not KEYFRAME:
1858 for ob in imported_objects:
1859 ob.location.rotate(CONVERSE)
1860 ob.rotation_euler.rotate(CONVERSE)
1862 # Select all new objects
1863 for ob in imported_objects:
1864 if ob.type == 'LIGHT' and ob.data.type == 'SPOT':
1865 square = math.sqrt(pow(1.0,2) + pow(1.0,2))
1866 aspect = ob.empty_display_size
1867 ob.scale.x = (aspect * square / (math.sqrt(pow(aspect,2) + 1.0)))
1868 ob.scale.y = (square / (math.sqrt(pow(aspect,2) + 1.0)))
1869 ob.scale.z = 1.0
1870 ob.select_set(True)
1871 if ob.type == 'MESH':
1872 if PIVOT:
1873 bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN')
1874 if not APPLY_MATRIX: # Reset transform
1875 bpy.ops.object.rotation_clear()
1876 bpy.ops.object.location_clear()
1877 bpy.ops.object.scale_clear()
1879 context.view_layer.update()
1881 axis_min = [1000000000] * 3
1882 axis_max = [-1000000000] * 3
1883 global_clamp_size = CONSTRAIN * 10000
1884 if global_clamp_size != 0.0:
1885 # Get all object bounds
1886 for ob in imported_objects:
1887 for v in ob.bound_box:
1888 for axis, value in enumerate(v):
1889 if axis_min[axis] > value:
1890 axis_min[axis] = value
1891 if axis_max[axis] < value:
1892 axis_max[axis] = value
1894 # Scale objects
1895 max_axis = max(axis_max[0] - axis_min[0],
1896 axis_max[1] - axis_min[1],
1897 axis_max[2] - axis_min[2])
1898 scale = 1.0
1900 while global_clamp_size < max_axis * scale:
1901 scale = scale / 10.0
1903 mtx_scale = mathutils.Matrix.Scale(scale, 4)
1904 for obj in imported_objects:
1905 if obj.parent is None:
1906 obj.matrix_world = mtx_scale @ obj.matrix_world
1908 for screen in bpy.data.screens:
1909 for area in screen.areas:
1910 if area.type == 'VIEW_3D':
1911 area.spaces[0].clip_start = scale * 0.1
1912 area.spaces[0].clip_end = scale * 10000
1914 context.window.cursor_set('DEFAULT')
1915 print(" done in %.4f sec." % (time.time() - duration))
1916 file.close()
1919 def load(operator, context, files=None, directory="", filepath="", constrain_size=0.0, use_scene_unit=False,
1920 use_image_search=True, object_filter=None, use_world_matrix=False, use_keyframes=True,
1921 use_apply_transform=True, global_matrix=None, use_cursor=False, use_center_pivot=False, use_collection=False):
1923 # Get the active collection
1924 collection_init = context.view_layer.active_layer_collection.collection
1926 # Load each selected file
1927 for file in files:
1928 # Create new collections if activated (collection name = 3ds file name)
1929 if use_collection:
1930 collection = bpy.data.collections.new(Path(file.name).stem)
1931 context.scene.collection.children.link(collection)
1932 context.view_layer.active_layer_collection = context.view_layer.layer_collection.children[collection.name]
1933 load_3ds(Path(directory, file.name), context, CONSTRAIN=constrain_size, UNITS=use_scene_unit,
1934 IMAGE_SEARCH=use_image_search, FILTER=object_filter, WORLD_MATRIX=use_world_matrix, KEYFRAME=use_keyframes,
1935 APPLY_MATRIX=use_apply_transform, CONVERSE=global_matrix, CURSOR=use_cursor, PIVOT=use_center_pivot,)
1937 # Retrive the initial collection as active
1938 active = context.view_layer.layer_collection.children.get(collection_init.name)
1939 if active is not None:
1940 context.view_layer.active_layer_collection = active
1942 return {'FINISHED'}