Sun Position: fix error in HDRI mode when no env tex is selected
[blender-addons.git] / io_scene_3ds / import_3ds.py
blobfdfa6b6df906ede3fd4beba3793b67c052d1ca25
1 # SPDX-License-Identifier: GPL-2.0-or-later
2 # Copyright 2005 Bob Holcomb
4 import os
5 import time
6 import struct
7 import bpy
8 import math
9 import mathutils
10 from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
12 BOUNDS_3DS = []
15 ######################################################
16 # Data Structures
17 ######################################################
19 # Some of the chunks that we will see
20 # ----- Primary Chunk, at the beginning of each file
21 PRIMARY = 0x4D4D
23 # ------ Main Chunks
24 OBJECTINFO = 0x3D3D # This gives the version of the mesh and is found right before the material and object information
25 VERSION = 0x0002 # This gives the version of the .3ds file
26 AMBIENTLIGHT = 0x2100 # The color of the ambient light
27 EDITKEYFRAME = 0xB000 # This is the header for all of the key frame info
29 # ------ Data Chunks, used for various attributes
30 COLOR_F = 0x0010 # color defined as 3 floats
31 COLOR_24 = 0x0011 # color defined as 3 bytes
32 LIN_COLOR_24 = 0x0012 # linear byte color
33 LIN_COLOR_F = 0x0013 # linear float color
34 PCT_SHORT = 0x30 # percentage short
35 PCT_FLOAT = 0x31 # percentage float
37 # ------ sub defines of OBJECTINFO
38 MATERIAL = 0xAFFF # This stored the texture info
39 OBJECT = 0x4000 # This stores the faces, vertices, etc...
41 # >------ sub defines of MATERIAL
42 # ------ sub defines of MATERIAL_BLOCK
43 MAT_NAME = 0xA000 # This holds the material name
44 MAT_AMBIENT = 0xA010 # Ambient color of the object/material
45 MAT_DIFFUSE = 0xA020 # This holds the color of the object/material
46 MAT_SPECULAR = 0xA030 # Specular color of the object/material
47 MAT_SHINESS = 0xA040 # Roughness of the object/material (percent)
48 MAT_SHIN2 = 0xA041 # Shininess of the object/material (percent)
49 MAT_SHIN3 = 0xA042 # Reflection of the object/material (percent)
50 MAT_TRANSPARENCY = 0xA050 # Transparency value of material (percent)
51 MAT_SELF_ILLUM = 0xA080 # Self Illumination value of material
52 MAT_SELF_ILPCT = 0xA084 # Self illumination strength (percent)
53 MAT_WIRE = 0xA085 # Only render's wireframe
54 MAT_SHADING = 0xA100 # Material shading method
55 MAT_TEXTURE_MAP = 0xA200 # This is a header for a new texture map
56 MAT_SPECULAR_MAP = 0xA204 # This is a header for a new specular map
57 MAT_OPACITY_MAP = 0xA210 # This is a header for a new opacity map
58 MAT_REFLECTION_MAP = 0xA220 # This is a header for a new reflection map
59 MAT_BUMP_MAP = 0xA230 # This is a header for a new bump map
60 MAT_BUMP_PERCENT = 0xA252 # Normalmap strength (percent)
61 MAT_TEX2_MAP = 0xA33A # This is a header for a secondary texture
62 MAT_SHIN_MAP = 0xA33C # This is a header for a new roughness map
63 MAT_SELFI_MAP = 0xA33D # This is a header for a new emission map
64 MAT_MAP_FILEPATH = 0xA300 # This holds the file name of the texture
65 MAT_MAP_TILING = 0xA351 # 2nd bit (from LSB) is mirror UV flag
66 MAT_MAP_USCALE = 0xA354 # U axis scaling
67 MAT_MAP_VSCALE = 0xA356 # V axis scaling
68 MAT_MAP_UOFFSET = 0xA358 # U axis offset
69 MAT_MAP_VOFFSET = 0xA35A # V axis offset
70 MAT_MAP_ANG = 0xA35C # UV rotation around the z-axis in rad
71 MAT_MAP_COL1 = 0xA360 # Map Color1
72 MAT_MAP_COL2 = 0xA362 # Map Color2
73 MAT_MAP_RCOL = 0xA364 # Red mapping
74 MAT_MAP_GCOL = 0xA366 # Green mapping
75 MAT_MAP_BCOL = 0xA368 # Blue mapping
77 # >------ sub defines of OBJECT
78 OBJECT_MESH = 0x4100 # This lets us know that we are reading a new object
79 OBJECT_LIGHT = 0x4600 # This lets un know we are reading a light object
80 OBJECT_LIGHT_SPOT = 0x4610 # The light is a spotloght.
81 OBJECT_LIGHT_OFF = 0x4620 # The light off.
82 OBJECT_LIGHT_ATTENUATE = 0x4625
83 OBJECT_LIGHT_RAYSHADE = 0x4627
84 OBJECT_LIGHT_SHADOWED = 0x4630
85 OBJECT_LIGHT_LOCAL_SHADOW = 0x4640
86 OBJECT_LIGHT_LOCAL_SHADOW2 = 0x4641
87 OBJECT_LIGHT_SEE_CONE = 0x4650
88 OBJECT_LIGHT_SPOT_RECTANGULAR = 0x4651
89 OBJECT_LIGHT_SPOT_OVERSHOOT = 0x4652
90 OBJECT_LIGHT_SPOT_PROJECTOR = 0x4653
91 OBJECT_LIGHT_EXCLUDE = 0x4654
92 OBJECT_LIGHT_RANGE = 0x4655
93 OBJECT_LIGHT_ROLL = 0x4656
94 OBJECT_LIGHT_SPOT_ASPECT = 0x4657
95 OBJECT_LIGHT_RAY_BIAS = 0x4658
96 OBJECT_LIGHT_INNER_RANGE = 0x4659
97 OBJECT_LIGHT_OUTER_RANGE = 0x465A
98 OBJECT_LIGHT_MULTIPLIER = 0x465B
99 OBJECT_LIGHT_AMBIENT_LIGHT = 0x4680
101 OBJECT_CAMERA = 0x4700 # This lets un know we are reading a camera object
103 # >------ sub defines of CAMERA
104 OBJECT_CAM_RANGES = 0x4720 # The camera range values
106 # >------ sub defines of OBJECT_MESH
107 OBJECT_VERTICES = 0x4110 # The objects vertices
108 OBJECT_VERTFLAGS = 0x4111 # The objects vertex flags
109 OBJECT_FACES = 0x4120 # The objects faces
110 OBJECT_MATERIAL = 0x4130 # This is found if the object has a material, either texture map or color
111 OBJECT_UV = 0x4140 # The UV texture coordinates
112 OBJECT_SMOOTH = 0x4150 # The Object smooth groups
113 OBJECT_TRANS_MATRIX = 0x4160 # The Object Matrix
115 # >------ sub defines of EDITKEYFRAME
116 KFDATA_AMBIENT = 0xB001
117 KFDATA_OBJECT = 0xB002
118 KFDATA_CAMERA = 0xB003
119 KFDATA_TARGET = 0xB004
120 KFDATA_LIGHT = 0xB005
121 KFDATA_L_TARGET = 0xB006
122 KFDATA_SPOTLIGHT = 0xB007
123 KFDATA_KFSEG = 0xB008
124 # KFDATA_CURTIME = 0xB009
125 # KFDATA_KFHDR = 0xB00A
126 # >------ sub defines of KEYFRAME_NODE
127 OBJECT_NODE_HDR = 0xB010
128 OBJECT_INSTANCE_NAME = 0xB011
129 # OBJECT_PRESCALE = 0xB012
130 OBJECT_PIVOT = 0xB013
131 # OBJECT_BOUNDBOX = 0xB014
132 MORPH_SMOOTH = 0xB015
133 POS_TRACK_TAG = 0xB020
134 ROT_TRACK_TAG = 0xB021
135 SCL_TRACK_TAG = 0xB022
136 FOV_TRACK_TAG = 0xB023
137 ROLL_TRACK_TAG = 0xB024
138 COL_TRACK_TAG = 0xB025
139 # MORPH_TRACK_TAG = 0xB026
140 # HOTSPOT_TRACK_TAG = 0xB027
141 # FALLOFF_TRACK_TAG = 0xB028
142 # HIDE_TRACK_TAG = 0xB029
143 # OBJECT_NODE_ID = 0xB030
145 ROOT_OBJECT = 0xFFFF
147 global scn
148 scn = None
150 object_dictionary = {}
151 object_matrix = {}
154 class Chunk:
155 __slots__ = (
156 "ID",
157 "length",
158 "bytes_read",
160 # we don't read in the bytes_read, we compute that
161 binary_format = "<HI"
163 def __init__(self):
164 self.ID = 0
165 self.length = 0
166 self.bytes_read = 0
168 def dump(self):
169 print('ID: ', self.ID)
170 print('ID in hex: ', hex(self.ID))
171 print('length: ', self.length)
172 print('bytes_read: ', self.bytes_read)
175 def read_chunk(file, chunk):
176 temp_data = file.read(struct.calcsize(chunk.binary_format))
177 data = struct.unpack(chunk.binary_format, temp_data)
178 chunk.ID = data[0]
179 chunk.length = data[1]
180 # update the bytes read function
181 chunk.bytes_read = 6
183 # if debugging
184 # chunk.dump()
187 def read_string(file):
188 # read in the characters till we get a null character
189 s = []
190 while True:
191 c = file.read(1)
192 if c == b'\x00':
193 break
194 s.append(c)
195 # print('string: ', s)
197 # Remove the null character from the string
198 # print("read string", s)
199 return str(b''.join(s), "utf-8", "replace"), len(s) + 1
201 ######################################################
202 # IMPORT
203 ######################################################
206 def process_next_object_chunk(file, previous_chunk):
207 new_chunk = Chunk()
209 while (previous_chunk.bytes_read < previous_chunk.length):
210 # read the next chunk
211 read_chunk(file, new_chunk)
214 def skip_to_end(file, skip_chunk):
215 buffer_size = skip_chunk.length - skip_chunk.bytes_read
216 binary_format = "%ic" % buffer_size
217 file.read(struct.calcsize(binary_format))
218 skip_chunk.bytes_read += buffer_size
221 def add_texture_to_material(image, contextWrapper, pct, extend, alpha, scale, offset, angle, tintcolor, mapto):
222 shader = contextWrapper.node_principled_bsdf
223 nodetree = contextWrapper.material.node_tree
224 shader.location = (-300, 0)
225 nodes = nodetree.nodes
226 links = nodetree.links
228 if mapto == 'COLOR':
229 mixer = nodes.new(type='ShaderNodeMixRGB')
230 mixer.label = "Mixer"
231 mixer.inputs[0].default_value = pct / 100
232 mixer.inputs[1].default_value = (
233 tintcolor[:3] + [1] if tintcolor else
234 shader.inputs['Base Color'].default_value[:]
236 contextWrapper._grid_to_location(1, 2, dst_node=mixer, ref_node=shader)
237 img_wrap = contextWrapper.base_color_texture
238 links.new(img_wrap.node_image.outputs['Color'], mixer.inputs[2])
239 links.new(mixer.outputs['Color'], shader.inputs['Base Color'])
240 elif mapto == 'SPECULARITY':
241 img_wrap = contextWrapper.specular_texture
242 elif mapto == 'ALPHA':
243 shader.location = (0, -300)
244 img_wrap = contextWrapper.alpha_texture
245 elif mapto == 'METALLIC':
246 shader.location = (300, 300)
247 img_wrap = contextWrapper.metallic_texture
248 elif mapto == 'ROUGHNESS':
249 shader.location = (300, 0)
250 img_wrap = contextWrapper.roughness_texture
251 elif mapto == 'EMISSION':
252 shader.location = (-300, -600)
253 img_wrap = contextWrapper.emission_color_texture
254 elif mapto == 'NORMAL':
255 shader.location = (300, 300)
256 img_wrap = contextWrapper.normalmap_texture
257 elif mapto == 'TEXTURE':
258 img_wrap = nodes.new(type='ShaderNodeTexImage')
259 img_wrap.label = image.name
260 contextWrapper._grid_to_location(0, 2, dst_node=img_wrap, ref_node=shader)
261 for node in nodes:
262 if node.label == 'Mixer':
263 spare = node.inputs[1] if node.inputs[1].is_linked is False else node.inputs[2]
264 socket = spare if spare.is_linked is False else node.inputs[0]
265 links.new(img_wrap.outputs['Color'], socket)
266 if node.type == 'TEX_COORD':
267 links.new(node.outputs['UV'], img_wrap.inputs['Vector'])
268 if shader.inputs['Base Color'].is_linked is False:
269 links.new(img_wrap.outputs['Color'], shader.inputs['Base Color'])
271 img_wrap.image = image
272 img_wrap.extension = 'REPEAT'
274 if mapto != 'TEXTURE':
275 img_wrap.scale = scale
276 img_wrap.translation = offset
277 img_wrap.rotation[2] = angle
279 if extend == 'mirror':
280 # 3DS mirror flag can be emulated by these settings (at least so it seems)
281 # TODO: bring back mirror
282 pass
283 # texture.repeat_x = texture.repeat_y = 2
284 # texture.use_mirror_x = texture.use_mirror_y = True
285 elif extend == 'decal':
286 # 3DS' decal mode maps best to Blenders EXTEND
287 img_wrap.extension = 'EXTEND'
288 elif extend == 'noWrap':
289 img_wrap.extension = 'CLIP'
290 if alpha == 'alpha':
291 for link in links:
292 if link.from_node.type == 'TEX_IMAGE' and link.to_node.type == 'MIX_RGB':
293 tex = link.from_node.image.name
294 own_node = img_wrap.node_image
295 own_map = img_wrap.node_mapping
296 if tex == image.name:
297 links.new(link.from_node.outputs['Alpha'], img_wrap.socket_dst)
298 nodes.remove(own_map)
299 nodes.remove(own_node)
300 for imgs in bpy.data.images:
301 if imgs.name[-3:].isdigit():
302 if not imgs.users:
303 bpy.data.images.remove(imgs)
304 else:
305 links.new(img_wrap.node_image.outputs['Alpha'], img_wrap.socket_dst)
307 shader.location = (300, 300)
308 contextWrapper._grid_to_location(1, 0, dst_node=contextWrapper.node_out, ref_node=shader)
311 def process_next_chunk(context, file, previous_chunk, imported_objects, IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME):
312 from bpy_extras.image_utils import load_image
314 contextObName = None
315 contextLamp = None
316 contextCamera = None
317 contextMaterial = None
318 contextWrapper = None
319 contextMatrix = None
320 contextMesh_vertls = None
321 contextMesh_facels = None
322 contextMesh_flag = None
323 contextMeshMaterials = []
324 contextMesh_smooth = None
325 contextMeshUV = None
327 # TEXTURE_DICT = {}
328 MATDICT = {}
330 # Localspace variable names, faster.
331 SZ_FLOAT = struct.calcsize('f')
332 SZ_2FLOAT = struct.calcsize('2f')
333 SZ_3FLOAT = struct.calcsize('3f')
334 SZ_4FLOAT = struct.calcsize('4f')
335 SZ_U_INT = struct.calcsize('I')
336 SZ_U_SHORT = struct.calcsize('H')
337 SZ_4U_SHORT = struct.calcsize('4H')
338 SZ_4x3MAT = struct.calcsize('ffffffffffff')
340 object_list = [] # for hierarchy
341 object_parent = [] # index of parent in hierarchy, 0xFFFF = no parent
342 pivot_list = [] # pivots with hierarchy handling
344 def putContextMesh(
345 context,
346 myContextMesh_vertls,
347 myContextMesh_facels,
348 myContextMesh_flag,
349 myContextMeshMaterials,
350 myContextMesh_smooth,
351 WORLD_MATRIX,
353 bmesh = bpy.data.meshes.new(contextObName)
355 if myContextMesh_facels is None:
356 myContextMesh_facels = []
358 if myContextMesh_vertls:
360 bmesh.vertices.add(len(myContextMesh_vertls) // 3)
361 bmesh.vertices.foreach_set("co", myContextMesh_vertls)
363 nbr_faces = len(myContextMesh_facels)
364 bmesh.polygons.add(nbr_faces)
365 bmesh.loops.add(nbr_faces * 3)
366 eekadoodle_faces = []
367 for v1, v2, v3 in myContextMesh_facels:
368 eekadoodle_faces.extend((v3, v1, v2) if v3 == 0 else (v1, v2, v3))
369 bmesh.polygons.foreach_set("loop_start", range(0, nbr_faces * 3, 3))
370 bmesh.loops.foreach_set("vertex_index", eekadoodle_faces)
372 if bmesh.polygons and contextMeshUV:
373 bmesh.uv_layers.new()
374 uv_faces = bmesh.uv_layers.active.data[:]
375 else:
376 uv_faces = None
378 for mat_idx, (matName, faces) in enumerate(myContextMeshMaterials):
379 if matName is None:
380 bmat = None
381 else:
382 bmat = MATDICT.get(matName)
383 # in rare cases no materials defined.
385 bmesh.materials.append(bmat) # can be None
386 for fidx in faces:
387 bmesh.polygons[fidx].material_index = mat_idx
389 if uv_faces:
390 uvl = bmesh.uv_layers.active.data[:]
391 for fidx, pl in enumerate(bmesh.polygons):
392 face = myContextMesh_facels[fidx]
393 v1, v2, v3 = face
395 # eekadoodle
396 if v3 == 0:
397 v1, v2, v3 = v3, v1, v2
399 uvl[pl.loop_start].uv = contextMeshUV[v1 * 2: (v1 * 2) + 2]
400 uvl[pl.loop_start + 1].uv = contextMeshUV[v2 * 2: (v2 * 2) + 2]
401 uvl[pl.loop_start + 2].uv = contextMeshUV[v3 * 2: (v3 * 2) + 2]
402 # always a tri
404 bmesh.validate()
405 bmesh.update()
407 ob = bpy.data.objects.new(contextObName, bmesh)
408 object_dictionary[contextObName] = ob
409 context.view_layer.active_layer_collection.collection.objects.link(ob)
410 imported_objects.append(ob)
412 if myContextMesh_flag:
413 # Bit 0 (0x1) sets edge CA visible, Bit 1 (0x2) sets edge BC visible and Bit 2 (0x4) sets edge AB visible
414 # In Blender we use sharp edges for those flags
415 for f, pl in enumerate(bmesh.polygons):
416 face = myContextMesh_facels[f]
417 faceflag = myContextMesh_flag[f]
418 edge_ab = bmesh.edges[bmesh.loops[pl.loop_start].edge_index]
419 edge_bc = bmesh.edges[bmesh.loops[pl.loop_start + 1].edge_index]
420 edge_ca = bmesh.edges[bmesh.loops[pl.loop_start + 2].edge_index]
421 if face[2] == 0:
422 edge_ab, edge_bc, edge_ca = edge_ca, edge_ab, edge_bc
423 if faceflag == 1:
424 edge_ca.use_edge_sharp = True
425 elif faceflag == 2:
426 edge_bc.use_edge_sharp = True
427 elif faceflag == 3:
428 edge_ca.use_edge_sharp = True
429 edge_bc.use_edge_sharp = True
430 elif faceflag == 4:
431 edge_ab.use_edge_sharp = True
432 elif faceflag == 5:
433 edge_ca.use_edge_sharp = True
434 edge_ab.use_edge_sharp = True
435 elif faceflag == 6:
436 edge_bc.use_edge_sharp = True
437 edge_ab.use_edge_sharp = True
438 elif faceflag == 7:
439 edge_bc.use_edge_sharp = True
440 edge_ab.use_edge_sharp = True
441 edge_ca.use_edge_sharp = True
443 if myContextMesh_smooth:
444 for f, pl in enumerate(bmesh.polygons):
445 smoothface = myContextMesh_smooth[f]
446 if smoothface > 0:
447 bmesh.polygons[f].use_smooth = True
449 if contextMatrix:
450 if WORLD_MATRIX:
451 ob.matrix_world = contextMatrix
452 else:
453 ob.matrix_local = contextMatrix
454 object_matrix[ob] = contextMatrix.copy()
456 # a spare chunk
457 new_chunk = Chunk()
458 temp_chunk = Chunk()
460 CreateBlenderObject = False
461 CreateLightObject = False
462 CreateCameraObject = False
464 def read_float_color(temp_chunk):
465 temp_data = file.read(SZ_3FLOAT)
466 temp_chunk.bytes_read += SZ_3FLOAT
467 return [float(col) for col in struct.unpack('<3f', temp_data)]
469 def read_float(temp_chunk):
470 temp_data = file.read(SZ_FLOAT)
471 temp_chunk.bytes_read += SZ_FLOAT
472 return struct.unpack('<f', temp_data)[0]
474 def read_short(temp_chunk):
475 temp_data = file.read(SZ_U_SHORT)
476 temp_chunk.bytes_read += SZ_U_SHORT
477 return struct.unpack('<H', temp_data)[0]
479 def read_byte_color(temp_chunk):
480 temp_data = file.read(struct.calcsize('3B'))
481 temp_chunk.bytes_read += 3
482 return [float(col) / 255 for col in struct.unpack('<3B', temp_data)]
484 def read_texture(new_chunk, temp_chunk, name, mapto):
485 uscale, vscale, uoffset, voffset, angle = 1.0, 1.0, 0.0, 0.0, 0.0
486 contextWrapper.use_nodes = True
487 tintcolor = None
488 extend = 'wrap'
489 alpha = False
490 pct = 50
492 contextWrapper.emission_color = contextMaterial.line_color[:3]
493 contextWrapper.emission_strength = contextMaterial.line_priority / 100
494 contextWrapper.base_color = contextMaterial.diffuse_color[:3]
495 contextWrapper.specular = contextMaterial.specular_intensity
496 contextWrapper.roughness = contextMaterial.roughness
497 contextWrapper.metallic = contextMaterial.metallic
498 contextWrapper.alpha = contextMaterial.diffuse_color[3]
500 while (new_chunk.bytes_read < new_chunk.length):
501 read_chunk(file, temp_chunk)
502 if temp_chunk.ID == PCT_SHORT:
503 pct = read_short(temp_chunk)
505 elif temp_chunk.ID == MAT_MAP_FILEPATH:
506 texture_name, read_str_len = read_string(file)
507 img = load_image(texture_name, dirname, place_holder=False, recursive=IMAGE_SEARCH, check_existing=True)
508 temp_chunk.bytes_read += read_str_len # plus one for the null character that gets removed
510 elif temp_chunk.ID == MAT_MAP_USCALE:
511 uscale = read_float(temp_chunk)
512 elif temp_chunk.ID == MAT_MAP_VSCALE:
513 vscale = read_float(temp_chunk)
514 elif temp_chunk.ID == MAT_MAP_UOFFSET:
515 uoffset = read_float(temp_chunk)
516 elif temp_chunk.ID == MAT_MAP_VOFFSET:
517 voffset = read_float(temp_chunk)
519 elif temp_chunk.ID == MAT_MAP_TILING:
520 tiling = read_short(temp_chunk)
521 if tiling & 0x1:
522 extend = 'decal'
523 elif tiling & 0x2:
524 extend = 'mirror'
525 elif tiling & 0x8:
526 extend = 'invert'
527 elif tiling & 0x10:
528 extend = 'noWrap'
529 elif tiling & 0x20:
530 alpha = 'sat'
531 elif tiling & 0x40:
532 alpha = 'alpha'
533 elif tiling & 0x80:
534 tint = 'tint'
535 elif tiling & 0x100:
536 tint = 'noAlpha'
537 elif tiling & 0x200:
538 tint = 'RGBtint'
540 elif temp_chunk.ID == MAT_MAP_ANG:
541 angle = read_float(temp_chunk)
542 print("\nwarning: UV angle mapped to z-rotation")
544 elif temp_chunk.ID == MAT_MAP_COL1:
545 tintcolor = read_byte_color(temp_chunk)
547 skip_to_end(file, temp_chunk)
548 new_chunk.bytes_read += temp_chunk.bytes_read
550 # add the map to the material in the right channel
551 if img:
552 add_texture_to_material(img, contextWrapper, pct, extend, alpha, (uscale, vscale, 1),
553 (uoffset, voffset, 0), angle, tintcolor, mapto)
555 def read_track_data(temp_chunk):
556 new_chunk.bytes_read += SZ_U_SHORT * 5
557 temp_data = file.read(SZ_U_SHORT * 5)
558 temp_data = file.read(SZ_U_INT)
559 nkeys = struct.unpack('<I', temp_data)[0]
560 new_chunk.bytes_read += SZ_U_INT
561 for i in range(nkeys):
562 temp_data = file.read(SZ_U_INT)
563 nframe = struct.unpack('<I', temp_data)[0]
564 new_chunk.bytes_read += SZ_U_INT
565 temp_data = file.read(SZ_U_SHORT)
566 nflags = struct.unpack('<H', temp_data)[0]
567 new_chunk.bytes_read += SZ_U_SHORT
568 if nflags > 0:
569 temp_data = file.read(SZ_FLOAT)
570 new_chunk.bytes_read += SZ_FLOAT
571 temp_data = file.read(SZ_3FLOAT)
572 data = struct.unpack('<3f', temp_data)
573 new_chunk.bytes_read += SZ_3FLOAT
574 if nkeys > 1: # Read keyframe data
575 for i in range(nkeys - 1):
576 temp_data = file.read(SZ_U_INT)
577 kframe = struct.unpack('<I', temp_data)[0]
578 new_chunk.bytes_read += SZ_U_INT
579 temp_data = file.read(SZ_U_SHORT)
580 kflags = struct.unpack('<H', temp_data)[0]
581 new_chunk.bytes_read += SZ_U_SHORT
582 temp_data = file.read(SZ_3FLOAT)
583 kdata = struct.unpack('<3f', temp_data)
584 new_chunk.bytes_read += SZ_3FLOAT
585 if nframe == 0:
586 return data
588 def read_track_angle(temp_chunk):
589 new_chunk.bytes_read += SZ_U_SHORT * 5
590 temp_data = file.read(SZ_U_SHORT * 5)
591 temp_data = file.read(SZ_U_INT)
592 nkeys = struct.unpack('<I', temp_data)[0]
593 new_chunk.bytes_read += SZ_U_INT
594 for i in range(nkeys):
595 temp_data = file.read(SZ_U_INT)
596 nframe = struct.unpack('<I', temp_data)[0]
597 new_chunk.bytes_read += SZ_U_INT
598 temp_data = file.read(SZ_U_SHORT)
599 nflags = struct.unpack('<H', temp_data)[0]
600 new_chunk.bytes_read += SZ_U_SHORT
601 if nflags > 0:
602 temp_data = file.read(SZ_FLOAT)
603 new_chunk.bytes_read += SZ_FLOAT
604 temp_data = file.read(SZ_FLOAT)
605 angle = struct.unpack('<f', temp_data)[0]
606 new_chunk.bytes_read += SZ_FLOAT
607 if nkeys > 1: # Read keyframe data
608 for i in range(nkeys - 1):
609 temp_data = file.read(SZ_U_INT)
610 kframe = struct.unpack('<I', temp_data)[0]
611 new_chunk.bytes_read += SZ_U_INT
612 temp_data = file.read(SZ_U_SHORT)
613 kflags = struct.unpack('<H', temp_data)[0]
614 new_chunk.bytes_read += SZ_U_SHORT
615 temp_data = file.read(SZ_FLOAT)
616 kangle = struct.unpack('<f', temp_data)[0]
617 new_chunk.bytes_read += SZ_FLOAT
618 if nframe == 0:
619 return math.radians(angle)
621 dirname = os.path.dirname(file.name)
623 # loop through all the data for this chunk (previous chunk) and see what it is
624 while (previous_chunk.bytes_read < previous_chunk.length):
625 read_chunk(file, new_chunk)
627 # is it a Version chunk?
628 if new_chunk.ID == VERSION:
629 # read in the version of the file
630 temp_data = file.read(SZ_U_INT)
631 version = struct.unpack('<I', temp_data)[0]
632 new_chunk.bytes_read += 4 # read the 4 bytes for the version number
633 # this loader works with version 3 and below, but may not with 4 and above
634 if version > 3:
635 print('\tNon-Fatal Error: Version greater than 3, may not load correctly: ', version)
637 # is it an ambient light chunk?
638 elif new_chunk.ID == AMBIENTLIGHT:
639 path, filename = os.path.split(file.name)
640 realname, ext = os.path.splitext(filename)
641 world = bpy.data.worlds.new("Ambient: " + realname)
642 world.light_settings.use_ambient_occlusion = True
643 context.scene.world = world
644 read_chunk(file, temp_chunk)
645 if temp_chunk.ID == COLOR_F:
646 context.scene.world.color[:] = read_float_color(temp_chunk)
647 elif temp_chunk.ID == LIN_COLOR_F:
648 context.scene.world.color[:] = read_float_color(temp_chunk)
649 else:
650 skip_to_end(file, temp_chunk)
651 new_chunk.bytes_read += temp_chunk.bytes_read
653 # is it an object info chunk?
654 elif new_chunk.ID == OBJECTINFO:
655 process_next_chunk(context, file, new_chunk, imported_objects, IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME)
657 # keep track of how much we read in the main chunk
658 new_chunk.bytes_read += temp_chunk.bytes_read
660 # is it an object chunk?
661 elif new_chunk.ID == OBJECT:
663 if CreateBlenderObject:
664 putContextMesh(
665 context,
666 contextMesh_vertls,
667 contextMesh_facels,
668 contextMesh_flag,
669 contextMeshMaterials,
670 contextMesh_smooth,
671 WORLD_MATRIX
673 contextMesh_vertls = []
674 contextMesh_facels = []
675 contextMeshMaterials = []
676 contextMesh_flag = None
677 contextMesh_smooth = None
678 contextMeshUV = None
679 # Reset matrix
680 contextMatrix = None
682 CreateBlenderObject = True
683 contextObName, read_str_len = read_string(file)
684 new_chunk.bytes_read += read_str_len
686 # is it a material chunk?
687 elif new_chunk.ID == MATERIAL:
688 contextMaterial = bpy.data.materials.new('Material')
689 contextWrapper = PrincipledBSDFWrapper(contextMaterial, is_readonly=False, use_nodes=False)
691 elif new_chunk.ID == MAT_NAME:
692 material_name, read_str_len = read_string(file)
694 # plus one for the null character that ended the string
695 new_chunk.bytes_read += read_str_len
696 contextMaterial.name = material_name.rstrip() # remove trailing whitespace
697 MATDICT[material_name] = contextMaterial
699 elif new_chunk.ID == MAT_AMBIENT:
700 read_chunk(file, temp_chunk)
701 # only available color is emission color
702 if temp_chunk.ID == COLOR_F:
703 contextMaterial.line_color[:3] = read_float_color(temp_chunk)
704 elif temp_chunk.ID == COLOR_24:
705 contextMaterial.line_color[:3] = read_byte_color(temp_chunk)
706 else:
707 skip_to_end(file, temp_chunk)
708 new_chunk.bytes_read += temp_chunk.bytes_read
710 elif new_chunk.ID == MAT_DIFFUSE:
711 read_chunk(file, temp_chunk)
712 if temp_chunk.ID == COLOR_F:
713 contextMaterial.diffuse_color[:3] = read_float_color(temp_chunk)
714 elif temp_chunk.ID == COLOR_24:
715 contextMaterial.diffuse_color[:3] = read_byte_color(temp_chunk)
716 else:
717 skip_to_end(file, temp_chunk)
718 new_chunk.bytes_read += temp_chunk.bytes_read
720 elif new_chunk.ID == MAT_SPECULAR:
721 read_chunk(file, temp_chunk)
722 # Specular color is available
723 if temp_chunk.ID == COLOR_F:
724 contextMaterial.specular_color = read_float_color(temp_chunk)
725 elif temp_chunk.ID == COLOR_24:
726 contextMaterial.specular_color = read_byte_color(temp_chunk)
727 else:
728 skip_to_end(file, temp_chunk)
729 new_chunk.bytes_read += temp_chunk.bytes_read
731 elif new_chunk.ID == MAT_SHINESS:
732 read_chunk(file, temp_chunk)
733 if temp_chunk.ID == PCT_SHORT:
734 temp_data = file.read(SZ_U_SHORT)
735 temp_chunk.bytes_read += SZ_U_SHORT
736 contextMaterial.roughness = 1 - (float(struct.unpack('<H', temp_data)[0]) / 100)
737 elif temp_chunk.ID == PCT_FLOAT:
738 temp_data = file.read(SZ_FLOAT)
739 temp_chunk.bytes_read += SZ_FLOAT
740 contextMaterial.roughness = 1 - float(struct.unpack('f', temp_data)[0])
741 new_chunk.bytes_read += temp_chunk.bytes_read
743 elif new_chunk.ID == MAT_SHIN2:
744 read_chunk(file, temp_chunk)
745 if temp_chunk.ID == PCT_SHORT:
746 temp_data = file.read(SZ_U_SHORT)
747 temp_chunk.bytes_read += SZ_U_SHORT
748 contextMaterial.specular_intensity = (float(struct.unpack('<H', temp_data)[0]) / 100)
749 elif temp_chunk.ID == PCT_FLOAT:
750 temp_data = file.read(SZ_FLOAT)
751 temp_chunk.bytes_read += SZ_FLOAT
752 contextMaterial.specular_intensity = float(struct.unpack('f', temp_data)[0])
753 new_chunk.bytes_read += temp_chunk.bytes_read
755 elif new_chunk.ID == MAT_SHIN3:
756 read_chunk(file, temp_chunk)
757 if temp_chunk.ID == PCT_SHORT:
758 temp_data = file.read(SZ_U_SHORT)
759 temp_chunk.bytes_read += SZ_U_SHORT
760 contextMaterial.metallic = (float(struct.unpack('<H', temp_data)[0]) / 100)
761 elif temp_chunk.ID == PCT_FLOAT:
762 temp_data = file.read(SZ_FLOAT)
763 temp_chunk.bytes_read += SZ_FLOAT
764 contextMaterial.metallic = float(struct.unpack('f', temp_data)[0])
765 new_chunk.bytes_read += temp_chunk.bytes_read
767 elif new_chunk.ID == MAT_TRANSPARENCY:
768 read_chunk(file, temp_chunk)
769 if temp_chunk.ID == PCT_SHORT:
770 temp_data = file.read(SZ_U_SHORT)
771 temp_chunk.bytes_read += SZ_U_SHORT
772 contextMaterial.diffuse_color[3] = 1 - (float(struct.unpack('<H', temp_data)[0]) / 100)
773 elif temp_chunk.ID == PCT_FLOAT:
774 temp_data = file.read(SZ_FLOAT)
775 temp_chunk.bytes_read += SZ_FLOAT
776 contextMaterial.diffuse_color[3] = 1 - float(struct.unpack('f', temp_data)[0])
777 else:
778 print("Cannot read material transparency")
779 new_chunk.bytes_read += temp_chunk.bytes_read
781 elif new_chunk.ID == MAT_SELF_ILPCT:
782 read_chunk(file, temp_chunk)
783 if temp_chunk.ID == PCT_SHORT:
784 temp_data = file.read(SZ_U_SHORT)
785 temp_chunk.bytes_read += SZ_U_SHORT
786 contextMaterial.line_priority = int(struct.unpack('H', temp_data)[0])
787 elif temp_chunk.ID == PCT_FLOAT:
788 temp_data = file.read(SZ_FLOAT)
789 temp_chunk.bytes_read += SZ_FLOAT
790 contextMaterial.line_priority = (float(struct.unpack('f', temp_data)[0]) * 100)
791 new_chunk.bytes_read += temp_chunk.bytes_read
793 elif new_chunk.ID == MAT_SHADING:
794 shading = read_short(new_chunk)
795 if shading >= 2:
796 contextWrapper.use_nodes = True
797 contextWrapper.emission_color = contextMaterial.line_color[:3]
798 contextWrapper.emission_strength = contextMaterial.line_priority / 100
799 contextWrapper.base_color = contextMaterial.diffuse_color[:3]
800 contextWrapper.specular = contextMaterial.specular_intensity
801 contextWrapper.roughness = contextMaterial.roughness
802 contextWrapper.metallic = contextMaterial.metallic
803 contextWrapper.alpha = contextMaterial.diffuse_color[3]
804 contextWrapper.use_nodes = False
805 if shading >= 3:
806 contextWrapper.use_nodes = True
808 elif new_chunk.ID == MAT_TEXTURE_MAP:
809 read_texture(new_chunk, temp_chunk, "Diffuse", "COLOR")
811 elif new_chunk.ID == MAT_SPECULAR_MAP:
812 read_texture(new_chunk, temp_chunk, "Specular", "SPECULARITY")
814 elif new_chunk.ID == MAT_OPACITY_MAP:
815 contextMaterial.blend_method = 'BLEND'
816 read_texture(new_chunk, temp_chunk, "Opacity", "ALPHA")
818 elif new_chunk.ID == MAT_REFLECTION_MAP:
819 read_texture(new_chunk, temp_chunk, "Reflect", "METALLIC")
821 elif new_chunk.ID == MAT_BUMP_MAP:
822 read_texture(new_chunk, temp_chunk, "Bump", "NORMAL")
824 elif new_chunk.ID == MAT_BUMP_PERCENT:
825 read_chunk(file, temp_chunk)
826 if temp_chunk.ID == PCT_SHORT:
827 temp_data = file.read(SZ_U_SHORT)
828 temp_chunk.bytes_read += SZ_U_SHORT
829 contextWrapper.normalmap_strength = (float(struct.unpack('<H', temp_data)[0]) / 100)
830 elif temp_chunk.ID == PCT_FLOAT:
831 temp_data = file.read(SZ_FLOAT)
832 temp_chunk.bytes_read += SZ_FLOAT
833 contextWrapper.normalmap_strength = float(struct.unpack('f', temp_data)[0])
834 else:
835 skip_to_end(file, temp_chunk)
836 new_chunk.bytes_read += temp_chunk.bytes_read
838 elif new_chunk.ID == MAT_SHIN_MAP:
839 read_texture(new_chunk, temp_chunk, "Shininess", "ROUGHNESS")
841 elif new_chunk.ID == MAT_SELFI_MAP:
842 read_texture(new_chunk, temp_chunk, "Emit", "EMISSION")
844 elif new_chunk.ID == MAT_TEX2_MAP:
845 read_texture(new_chunk, temp_chunk, "Tex", "TEXTURE")
847 # mesh chunk
848 elif new_chunk.ID == OBJECT_MESH:
849 pass
851 elif new_chunk.ID == OBJECT_VERTICES:
852 """Worldspace vertex locations"""
853 temp_data = file.read(SZ_U_SHORT)
854 num_verts = struct.unpack('<H', temp_data)[0]
855 new_chunk.bytes_read += 2
856 contextMesh_vertls = struct.unpack('<%df' % (num_verts * 3), file.read(SZ_3FLOAT * num_verts))
857 new_chunk.bytes_read += SZ_3FLOAT * num_verts
858 # dummyvert is not used atm!
860 elif new_chunk.ID == OBJECT_FACES:
861 temp_data = file.read(SZ_U_SHORT)
862 num_faces = struct.unpack('<H', temp_data)[0]
863 new_chunk.bytes_read += 2
864 temp_data = file.read(SZ_4U_SHORT * num_faces)
865 new_chunk.bytes_read += SZ_4U_SHORT * num_faces # 4 short ints x 2 bytes each
866 contextMesh_facels = struct.unpack('<%dH' % (num_faces * 4), temp_data)
867 contextMesh_flag = [contextMesh_facels[i] for i in range(3, (num_faces * 4) + 3, 4)]
868 contextMesh_facels = [contextMesh_facels[i - 3:i] for i in range(3, (num_faces * 4) + 3, 4)]
870 elif new_chunk.ID == OBJECT_MATERIAL:
871 material_name, read_str_len = read_string(file)
872 new_chunk.bytes_read += read_str_len # remove 1 null character.
873 temp_data = file.read(SZ_U_SHORT)
874 num_faces_using_mat = struct.unpack('<H', temp_data)[0]
875 new_chunk.bytes_read += SZ_U_SHORT
876 temp_data = file.read(SZ_U_SHORT * num_faces_using_mat)
877 new_chunk.bytes_read += SZ_U_SHORT * num_faces_using_mat
878 temp_data = struct.unpack("<%dH" % (num_faces_using_mat), temp_data)
879 contextMeshMaterials.append((material_name, temp_data))
880 # look up the material in all the materials
882 elif new_chunk.ID == OBJECT_SMOOTH:
883 temp_data = file.read(SZ_U_INT * num_faces)
884 smoothgroup = struct.unpack('<%dI' % (num_faces), temp_data)
885 new_chunk.bytes_read += SZ_U_INT * num_faces
886 contextMesh_smooth = smoothgroup
888 elif new_chunk.ID == OBJECT_UV:
889 temp_data = file.read(SZ_U_SHORT)
890 num_uv = struct.unpack('<H', temp_data)[0]
891 new_chunk.bytes_read += 2
892 temp_data = file.read(SZ_2FLOAT * num_uv)
893 new_chunk.bytes_read += SZ_2FLOAT * num_uv
894 contextMeshUV = struct.unpack('<%df' % (num_uv * 2), temp_data)
896 elif new_chunk.ID == OBJECT_TRANS_MATRIX:
897 # How do we know the matrix size? 54 == 4x4 48 == 4x3
898 temp_data = file.read(SZ_4x3MAT)
899 data = list(struct.unpack('<ffffffffffff', temp_data))
900 new_chunk.bytes_read += SZ_4x3MAT
901 contextMatrix = mathutils.Matrix(
902 (data[:3] + [0], data[3:6] + [0], data[6:9] + [0], data[9:] + [1])).transposed()
904 elif contextObName and new_chunk.ID == OBJECT_LIGHT: # Basic lamp support.
905 # no lamp in dict that would be confusing
906 # ...why not? just set CreateBlenderObject to False
907 newLamp = bpy.data.lights.new("Lamp", 'POINT')
908 contextLamp = bpy.data.objects.new(contextObName, newLamp)
909 context.view_layer.active_layer_collection.collection.objects.link(contextLamp)
910 imported_objects.append(contextLamp)
911 object_dictionary[contextObName] = contextLamp
912 temp_data = file.read(SZ_3FLOAT)
913 contextLamp.location = struct.unpack('<3f', temp_data)
914 new_chunk.bytes_read += SZ_3FLOAT
915 contextMatrix = None # Reset matrix
916 CreateBlenderObject = False
917 CreateLightObject = True
919 elif CreateLightObject and new_chunk.ID == COLOR_F: # color
920 temp_data = file.read(SZ_3FLOAT)
921 contextLamp.data.color = struct.unpack('<3f', temp_data)
922 new_chunk.bytes_read += SZ_3FLOAT
923 elif CreateLightObject and new_chunk.ID == OBJECT_LIGHT_MULTIPLIER: # intensity
924 temp_data = file.read(SZ_FLOAT)
925 contextLamp.data.energy = (float(struct.unpack('f', temp_data)[0]) * 1000)
926 new_chunk.bytes_read += SZ_FLOAT
928 elif CreateLightObject and new_chunk.ID == OBJECT_LIGHT_SPOT: # spotlight
929 temp_data = file.read(SZ_3FLOAT)
930 contextLamp.data.type = 'SPOT'
931 spot = mathutils.Vector(struct.unpack('<3f', temp_data))
932 aim = contextLamp.location + spot
933 hypo = math.copysign(math.sqrt(pow(aim[1], 2) + pow(aim[0], 2)), aim[1])
934 track = math.copysign(math.sqrt(pow(hypo, 2) + pow(spot[2], 2)), aim[1])
935 angle = math.radians(90) - math.copysign(math.acos(hypo / track), aim[2])
936 contextLamp.rotation_euler[0] = -1 * math.copysign(angle, aim[1])
937 contextLamp.rotation_euler[2] = -1 * (math.radians(90) - math.acos(aim[0] / hypo))
938 new_chunk.bytes_read += SZ_3FLOAT
939 temp_data = file.read(SZ_FLOAT) # hotspot
940 hotspot = float(struct.unpack('f', temp_data)[0])
941 new_chunk.bytes_read += SZ_FLOAT
942 temp_data = file.read(SZ_FLOAT) # angle
943 beam_angle = float(struct.unpack('f', temp_data)[0])
944 contextLamp.data.spot_size = math.radians(beam_angle)
945 contextLamp.data.spot_blend = (1.0 - (hotspot / beam_angle)) * 2
946 new_chunk.bytes_read += SZ_FLOAT
947 elif CreateLightObject and new_chunk.ID == OBJECT_LIGHT_ROLL: # roll
948 temp_data = file.read(SZ_FLOAT)
949 contextLamp.rotation_euler[1] = float(struct.unpack('f', temp_data)[0])
950 new_chunk.bytes_read += SZ_FLOAT
952 elif contextObName and new_chunk.ID == OBJECT_CAMERA and CreateCameraObject is False: # Basic camera support
953 camera = bpy.data.cameras.new("Camera")
954 contextCamera = bpy.data.objects.new(contextObName, camera)
955 context.view_layer.active_layer_collection.collection.objects.link(contextCamera)
956 imported_objects.append(contextCamera)
957 object_dictionary[contextObName] = contextCamera
958 temp_data = file.read(SZ_3FLOAT)
959 contextCamera.location = struct.unpack('<3f', temp_data)
960 new_chunk.bytes_read += SZ_3FLOAT
961 temp_data = file.read(SZ_3FLOAT)
962 target = mathutils.Vector(struct.unpack('<3f', temp_data))
963 cam = contextCamera.location + target
964 focus = math.copysign(math.sqrt(pow(cam[1], 2) + pow(cam[0], 2)), cam[1])
965 new_chunk.bytes_read += SZ_3FLOAT
966 temp_data = file.read(SZ_FLOAT) # triangulating camera angles
967 direction = math.copysign(math.sqrt(pow(focus, 2) + pow(target[2], 2)), cam[1])
968 pitch = math.radians(90) - math.copysign(math.acos(focus / direction), cam[2])
969 contextCamera.rotation_euler[0] = -1 * math.copysign(pitch, cam[1])
970 contextCamera.rotation_euler[1] = float(struct.unpack('f', temp_data)[0])
971 contextCamera.rotation_euler[2] = -1 * (math.radians(90) - math.acos(cam[0] / focus))
972 new_chunk.bytes_read += SZ_FLOAT
973 temp_data = file.read(SZ_FLOAT)
974 contextCamera.data.lens = float(struct.unpack('f', temp_data)[0])
975 new_chunk.bytes_read += SZ_FLOAT
976 contextMatrix = None # Reset matrix
977 CreateBlenderObject = False
978 CreateCameraObject = True
980 elif new_chunk.ID == EDITKEYFRAME:
981 pass
983 elif KEYFRAME and new_chunk.ID == KFDATA_KFSEG:
984 temp_data = file.read(SZ_U_INT)
985 start = struct.unpack('<I', temp_data)[0]
986 new_chunk.bytes_read += 4
987 context.scene.frame_start = start
988 temp_data = file.read(SZ_U_INT)
989 stop = struct.unpack('<I', temp_data)[0]
990 new_chunk.bytes_read += 4
991 context.scene.frame_end = stop
993 # including these here means their EK_OB_NODE_HEADER are scanned
994 # another object is being processed
995 elif new_chunk.ID in {KFDATA_OBJECT, KFDATA_AMBIENT, KFDATA_CAMERA, KFDATA_OBJECT, KFDATA_TARGET, KFDATA_LIGHT, KFDATA_L_TARGET, }:
996 child = None
998 elif new_chunk.ID == OBJECT_NODE_HDR:
999 object_name, read_str_len = read_string(file)
1000 new_chunk.bytes_read += read_str_len
1001 temp_data = file.read(SZ_U_SHORT * 2)
1002 new_chunk.bytes_read += 4
1003 temp_data = file.read(SZ_U_SHORT)
1004 hierarchy = struct.unpack('<H', temp_data)[0]
1005 new_chunk.bytes_read += 2
1006 child = object_dictionary.get(object_name)
1007 colortrack = 'LIGHT'
1008 if child is None:
1009 if object_name == '$AMBIENT$':
1010 child = context.scene.world
1011 child.use_nodes = True
1012 colortrack = 'AMBIENT'
1013 else:
1014 child = bpy.data.objects.new(object_name, None) # Create an empty object
1015 context.view_layer.active_layer_collection.collection.objects.link(child)
1016 imported_objects.append(child)
1018 if object_name != '$AMBIENT$':
1019 object_list.append(child)
1020 object_parent.append(hierarchy)
1021 pivot_list.append(mathutils.Vector((0.0, 0.0, 0.0)))
1023 elif new_chunk.ID == OBJECT_INSTANCE_NAME:
1024 object_name, read_str_len = read_string(file)
1025 if child.name == '$$$DUMMY':
1026 child.name = object_name
1027 else:
1028 child.name += "." + object_name
1029 object_dictionary[object_name] = child
1030 new_chunk.bytes_read += read_str_len
1032 elif new_chunk.ID == OBJECT_PIVOT: # Pivot
1033 temp_data = file.read(SZ_3FLOAT)
1034 pivot = struct.unpack('<3f', temp_data)
1035 new_chunk.bytes_read += SZ_3FLOAT
1036 pivot_list[len(pivot_list) - 1] = mathutils.Vector(pivot)
1038 elif new_chunk.ID == MORPH_SMOOTH and child.type == 'MESH': # Smooth angle
1039 child.data.use_auto_smooth = True
1040 temp_data = file.read(SZ_FLOAT)
1041 smooth_angle = struct.unpack('<f', temp_data)[0]
1042 new_chunk.bytes_read += SZ_FLOAT
1043 child.data.auto_smooth_angle = math.radians(smooth_angle)
1045 elif KEYFRAME and new_chunk.ID == COL_TRACK_TAG and colortrack == 'AMBIENT': # Ambient
1046 child.node_tree.nodes['Background'].inputs[0].default_value[:3] = read_track_data(temp_chunk)
1048 elif KEYFRAME and new_chunk.ID == POS_TRACK_TAG: # Translation
1049 child.location = read_track_data(temp_chunk)
1051 elif KEYFRAME and new_chunk.ID == ROT_TRACK_TAG and child.type == 'MESH': # Rotation
1052 new_chunk.bytes_read += SZ_U_SHORT * 5
1053 temp_data = file.read(SZ_U_SHORT * 5)
1054 temp_data = file.read(SZ_U_INT)
1055 nkeys = struct.unpack('<I', temp_data)[0]
1056 new_chunk.bytes_read += SZ_U_INT
1057 for i in range(nkeys):
1058 temp_data = file.read(SZ_U_INT)
1059 nframe = struct.unpack('<I', temp_data)[0]
1060 new_chunk.bytes_read += SZ_U_INT
1061 temp_data = file.read(SZ_U_SHORT)
1062 nflags = struct.unpack('<H', temp_data)[0]
1063 new_chunk.bytes_read += SZ_U_SHORT
1064 if nflags > 0: # Check for spline term values
1065 temp_data = file.read(SZ_FLOAT)
1066 new_chunk.bytes_read += SZ_FLOAT
1067 temp_data = file.read(SZ_4FLOAT)
1068 rad, axis_x, axis_y, axis_z = struct.unpack("<4f", temp_data)
1069 new_chunk.bytes_read += SZ_4FLOAT
1070 if nkeys > 1: # Read keyframe data
1071 for i in range(nkeys - 1):
1072 temp_data = file.read(SZ_U_INT)
1073 kframe = struct.unpack('<I', temp_data)[0]
1074 new_chunk.bytes_read += SZ_U_INT
1075 temp_data = file.read(SZ_U_SHORT)
1076 kflags = struct.unpack('<H', temp_data)[0]
1077 new_chunk.bytes_read += SZ_U_SHORT
1078 temp_data = file.read(SZ_4FLOAT)
1079 rotation = struct.unpack('<4f', temp_data)
1080 new_chunk.bytes_read += SZ_4FLOAT
1081 if nframe == 0:
1082 child.rotation_euler = mathutils.Quaternion(
1083 (axis_x, axis_y, axis_z), -rad).to_euler() # why negative?
1085 elif KEYFRAME and new_chunk.ID == SCL_TRACK_TAG and child.type == 'MESH': # Scale
1086 child.scale = read_track_data(temp_chunk)
1088 elif KEYFRAME and new_chunk.ID == COL_TRACK_TAG and colortrack == 'LIGHT': # Color
1089 child.data.color = read_track_data(temp_chunk)
1091 elif KEYFRAME and new_chunk.ID == FOV_TRACK_TAG and child.type == 'CAMERA': # Field of view
1092 child.data.angle = read_track_angle(temp_chunk)
1094 elif KEYFRAME and new_chunk.ID == ROLL_TRACK_TAG and child.type == 'CAMERA': # Roll angle
1095 child.rotation_euler[1] = read_track_angle(temp_chunk)
1097 else:
1098 buffer_size = new_chunk.length - new_chunk.bytes_read
1099 binary_format = "%ic" % buffer_size
1100 temp_data = file.read(struct.calcsize(binary_format))
1101 new_chunk.bytes_read += buffer_size
1103 # update the previous chunk bytes read
1104 previous_chunk.bytes_read += new_chunk.bytes_read
1106 # FINISHED LOOP
1107 # There will be a number of objects still not added
1108 if CreateBlenderObject:
1109 if CreateLightObject or CreateCameraObject:
1110 pass
1111 else:
1112 putContextMesh(
1113 context,
1114 contextMesh_vertls,
1115 contextMesh_facels,
1116 contextMesh_flag,
1117 contextMeshMaterials,
1118 contextMesh_smooth,
1119 WORLD_MATRIX
1122 # Assign parents to objects
1123 # check _if_ we need to assign first because doing so recalcs the depsgraph
1124 for ind, ob in enumerate(object_list):
1125 parent = object_parent[ind]
1126 if parent == ROOT_OBJECT:
1127 if ob.parent is not None:
1128 ob.parent = None
1129 else:
1130 if ob.parent != object_list[parent]:
1131 if ob == object_list[parent]:
1132 print(' warning: Cannot assign self to parent ', ob)
1133 else:
1134 ob.parent = object_list[parent]
1136 # pivot_list[ind] += pivot_list[parent] # XXX, not sure this is correct, should parent space matrix be applied before combining?
1137 # fix pivots
1138 for ind, ob in enumerate(object_list):
1139 if ob.type == 'MESH':
1140 pivot = pivot_list[ind]
1141 pivot_matrix = object_matrix.get(ob, mathutils.Matrix()) # unlikely to fail
1142 pivot_matrix = mathutils.Matrix.Translation(-1 * pivot)
1143 # pivot_matrix = mathutils.Matrix.Translation(pivot_matrix.to_3x3() @ -pivot)
1144 ob.data.transform(pivot_matrix)
1147 def load_3ds(filepath,
1148 context,
1149 IMPORT_CONSTRAIN_BOUNDS=10.0,
1150 IMAGE_SEARCH=True,
1151 WORLD_MATRIX=False,
1152 KEYFRAME=True,
1153 APPLY_MATRIX=True,
1154 global_matrix=None):
1155 # global SCN
1156 # XXX
1157 # if BPyMessages.Error_NoFile(filepath):
1158 # return
1160 print("importing 3DS: %r..." % (filepath), end="")
1162 if bpy.ops.object.select_all.poll():
1163 bpy.ops.object.select_all(action='DESELECT')
1165 time1 = time.time()
1166 # time1 = Blender.sys.time()
1168 current_chunk = Chunk()
1170 file = open(filepath, 'rb')
1172 # here we go!
1173 # print 'reading the first chunk'
1174 read_chunk(file, current_chunk)
1175 if current_chunk.ID != PRIMARY:
1176 print('\tFatal Error: Not a valid 3ds file: %r' % filepath)
1177 file.close()
1178 return
1180 if IMPORT_CONSTRAIN_BOUNDS:
1181 BOUNDS_3DS[:] = [1 << 30, 1 << 30, 1 << 30, -1 << 30, -1 << 30, -1 << 30]
1182 else:
1183 del BOUNDS_3DS[:]
1185 # IMAGE_SEARCH
1187 # fixme, make unglobal, clear in case
1188 object_dictionary.clear()
1189 object_matrix.clear()
1191 scn = context.scene
1193 imported_objects = [] # Fill this list with objects
1194 process_next_chunk(context, file, current_chunk, imported_objects, IMAGE_SEARCH, WORLD_MATRIX, KEYFRAME)
1196 # fixme, make unglobal
1197 object_dictionary.clear()
1198 object_matrix.clear()
1200 # Link the objects into this scene.
1201 # Layers = scn.Layers
1203 # REMOVE DUMMYVERT, - remove this in the next release when blenders internal are fixed.
1205 if APPLY_MATRIX:
1206 for ob in imported_objects:
1207 if ob.type == 'MESH':
1208 me = ob.data
1209 me.transform(ob.matrix_local.inverted())
1211 # print(imported_objects)
1212 if global_matrix:
1213 for ob in imported_objects:
1214 if ob.type == 'MESH' and ob.parent is None:
1215 ob.matrix_world = ob.matrix_world @ global_matrix
1217 for ob in imported_objects:
1218 ob.select_set(True)
1219 if not APPLY_MATRIX: # Reset transform
1220 bpy.ops.object.rotation_clear()
1221 bpy.ops.object.location_clear()
1223 # Done DUMMYVERT
1225 if IMPORT_AS_INSTANCE:
1226 name = filepath.split('\\')[-1].split('/')[-1]
1227 # Create a group for this import.
1228 group_scn = Scene.New(name)
1229 for ob in imported_objects:
1230 group_scn.link(ob) # dont worry about the layers
1232 grp = Blender.Group.New(name)
1233 grp.objects = imported_objects
1235 grp_ob = Object.New('Empty', name)
1236 grp_ob.enableDupGroup = True
1237 grp_ob.DupGroup = grp
1238 scn.link(grp_ob)
1239 grp_ob.Layers = Layers
1240 grp_ob.sel = 1
1241 else:
1242 # Select all imported objects.
1243 for ob in imported_objects:
1244 scn.link(ob)
1245 ob.Layers = Layers
1246 ob.sel = 1
1249 context.view_layer.update()
1251 axis_min = [1000000000] * 3
1252 axis_max = [-1000000000] * 3
1253 global_clamp_size = IMPORT_CONSTRAIN_BOUNDS
1254 if global_clamp_size != 0.0:
1255 # Get all object bounds
1256 for ob in imported_objects:
1257 for v in ob.bound_box:
1258 for axis, value in enumerate(v):
1259 if axis_min[axis] > value:
1260 axis_min[axis] = value
1261 if axis_max[axis] < value:
1262 axis_max[axis] = value
1264 # Scale objects
1265 max_axis = max(axis_max[0] - axis_min[0],
1266 axis_max[1] - axis_min[1],
1267 axis_max[2] - axis_min[2])
1268 scale = 1.0
1270 while global_clamp_size < max_axis * scale:
1271 scale = scale / 10.0
1273 scale_mat = mathutils.Matrix.Scale(scale, 4)
1275 for obj in imported_objects:
1276 if obj.parent is None:
1277 obj.matrix_world = scale_mat @ obj.matrix_world
1279 # Select all new objects.
1280 print(" done in %.4f sec." % (time.time() - time1))
1281 file.close()
1284 def load(operator,
1285 context,
1286 filepath="",
1287 constrain_size=0.0,
1288 use_image_search=True,
1289 use_world_matrix=False,
1290 read_keyframe=True,
1291 use_apply_transform=True,
1292 global_matrix=None,
1295 load_3ds(filepath,
1296 context,
1297 IMPORT_CONSTRAIN_BOUNDS=constrain_size,
1298 IMAGE_SEARCH=use_image_search,
1299 WORLD_MATRIX=use_world_matrix,
1300 KEYFRAME=read_keyframe,
1301 APPLY_MATRIX=use_apply_transform,
1302 global_matrix=global_matrix,
1305 return {'FINISHED'}