1 # SPDX-License-Identifier: GPL-2.0-or-later
2 # Copyright 2005 Bob Holcomb
5 Exporting is based on 3ds loader from www.gametutorials.com(Thanks DigiBen) and using information
6 from the lib3ds project (http://lib3ds.sourceforge.net/) sourcecode.
14 from bpy_extras
import node_shader_utils
16 ######################################################
18 ######################################################
20 # Some of the chunks that we will export
21 # ----- Primary Chunk, at the beginning of each file
25 VERSION
= 0x0002 # This gives the version of the .3ds file
26 KFDATA
= 0xB000 # This is the header for all of the key frame info
28 # ------ sub defines of OBJECTINFO
29 OBJECTINFO
= 0x3D3D # Main mesh object chunk before the material and object information
30 MESHVERSION
= 0x3D3E # This gives the version of the mesh
31 AMBIENTLIGHT
= 0x2100 # The color of the ambient light
32 MATERIAL
= 45055 # 0xAFFF // This stored the texture info
33 OBJECT
= 16384 # 0x4000 // This stores the faces, vertices, etc...
35 # >------ sub defines of MATERIAL
36 MATNAME
= 0xA000 # This holds the material name
37 MATAMBIENT
= 0xA010 # Ambient color of the object/material
38 MATDIFFUSE
= 0xA020 # This holds the color of the object/material
39 MATSPECULAR
= 0xA030 # Specular color of the object/material
40 MATSHINESS
= 0xA040 # Specular intensity of the object/material (percent)
41 MATSHIN2
= 0xA041 # Reflection of the object/material (percent)
42 MATSHIN3
= 0xA042 # metallic/mirror of the object/material (percent)
43 MATTRANS
= 0xA050 # Transparency value (100-OpacityValue) (percent)
44 MATSELFILPCT
= 0xA084 # Self illumination strength (percent)
45 MATSHADING
= 0xA100 # Material shading method
47 MAT_DIFFUSEMAP
= 0xA200 # This is a header for a new diffuse texture
48 MAT_SPECMAP
= 0xA204 # head for specularity map
49 MAT_OPACMAP
= 0xA210 # head for opacity map
50 MAT_REFLMAP
= 0xA220 # head for reflect map
51 MAT_BUMPMAP
= 0xA230 # head for normal map
52 MAT_BUMP_PERCENT
= 0xA252 # Normalmap strength (percent)
53 MAT_TEX2MAP
= 0xA33A # head for secondary texture
54 MAT_SHINMAP
= 0xA33C # head for roughness map
55 MAT_SELFIMAP
= 0xA33D # head for emission map
57 # >------ sub defines of MAT_MAP
58 MATMAPFILE
= 0xA300 # This holds the file name of a texture
59 MAT_MAP_TILING
= 0xa351 # 2nd bit (from LSB) is mirror UV flag
60 MAT_MAP_TEXBLUR
= 0xA353 # Texture blurring factor
61 MAT_MAP_USCALE
= 0xA354 # U axis scaling
62 MAT_MAP_VSCALE
= 0xA356 # V axis scaling
63 MAT_MAP_UOFFSET
= 0xA358 # U axis offset
64 MAT_MAP_VOFFSET
= 0xA35A # V axis offset
65 MAT_MAP_ANG
= 0xA35C # UV rotation around the z-axis in rad
66 MAP_COL1
= 0xA360 # Tint Color1
67 MAP_COL2
= 0xA362 # Tint Color2
68 MAP_RCOL
= 0xA364 # Red tint
69 MAP_GCOL
= 0xA366 # Green tint
70 MAP_BCOL
= 0xA368 # Blue tint
72 RGB
= 0x0010 # RGB float Color1
73 RGB1
= 0x0011 # RGB int Color1
74 RGBI
= 0x0012 # RGB int Color2
75 RGBF
= 0x0013 # RGB float Color2
76 PCT
= 0x0030 # Percent chunk
77 PCTF
= 0x0031 # Percent float
78 MASTERSCALE
= 0x0100 # Master scale factor
80 # >------ sub defines of OBJECT
81 OBJECT_MESH
= 0x4100 # This lets us know that we are reading a new object
82 OBJECT_LIGHT
= 0x4600 # This lets us know we are reading a light object
83 OBJECT_CAMERA
= 0x4700 # This lets us know we are reading a camera object
85 # >------ Sub defines of LIGHT
86 LIGHT_MULTIPLIER
= 0x465B # The light energy factor
87 LIGHT_SPOTLIGHT
= 0x4610 # The target of a spotlight
88 LIGHT_SPOTROLL
= 0x4656 # The roll angle of the spot
90 # >------ sub defines of CAMERA
91 OBJECT_CAM_RANGES
= 0x4720 # The camera range values
93 # >------ sub defines of OBJECT_MESH
94 OBJECT_VERTICES
= 0x4110 # The objects vertices
95 OBJECT_VERTFLAGS
= 0x4111 # The objects vertex flags
96 OBJECT_FACES
= 0x4120 # The objects faces
97 OBJECT_MATERIAL
= 0x4130 # This is found if the object has a material, either texture map or color
98 OBJECT_UV
= 0x4140 # The UV texture coordinates
99 OBJECT_SMOOTH
= 0x4150 # The objects smooth groups
100 OBJECT_TRANS_MATRIX
= 0x4160 # The Object Matrix
102 # >------ sub defines of KFDATA
103 KFDATA_KFHDR
= 0xB00A
104 KFDATA_KFSEG
= 0xB008
105 KFDATA_KFCURTIME
= 0xB009
106 KFDATA_OBJECT_NODE_TAG
= 0xB002
108 # >------ sub defines of OBJECT_NODE_TAG
109 OBJECT_NODE_ID
= 0xB030
110 OBJECT_NODE_HDR
= 0xB010
111 OBJECT_PIVOT
= 0xB013
112 OBJECT_INSTANCE_NAME
= 0xB011
113 POS_TRACK_TAG
= 0xB020
114 ROT_TRACK_TAG
= 0xB021
115 SCL_TRACK_TAG
= 0xB022
118 # So 3ds max can open files, limit names to 12 in length
119 # this is very annoying for filenames!
120 name_unique
= [] # stores str, ascii only
121 name_mapping
= {} # stores {orig: byte} mapping
125 name_fixed
= name_mapping
.get(name
)
126 if name_fixed
is not None:
129 # strip non ascii chars
130 new_name_clean
= new_name
= name
.encode("ASCII", "replace").decode("ASCII")[:12]
133 while new_name
in name_unique
:
134 new_name
= new_name_clean
+ ".%.3d" % i
137 # note, appending the 'str' version.
138 name_unique
.append(new_name
)
139 name_mapping
[name
] = new_name
= new_name
.encode("ASCII", "replace")
144 return round(uv
[0], 6), round(uv
[1], 6)
153 class _3ds_ushort(object):
154 """Class representing a short (2-byte integer) for a 3ds file.
155 *** This looks like an unsigned short H is unsigned from the struct docs - Cam***"""
156 __slots__
= ("value", )
158 def __init__(self
, val
=0):
164 def write(self
, file):
165 file.write(struct
.pack("<H", self
.value
))
168 return str(self
.value
)
171 class _3ds_uint(object):
172 """Class representing an int (4-byte integer) for a 3ds file."""
173 __slots__
= ("value", )
175 def __init__(self
, val
):
181 def write(self
, file):
182 file.write(struct
.pack("<I", self
.value
))
185 return str(self
.value
)
188 class _3ds_float(object):
189 """Class representing a 4-byte IEEE floating point number for a 3ds file."""
190 __slots__
= ("value", )
192 def __init__(self
, val
):
198 def write(self
, file):
199 file.write(struct
.pack("<f", self
.value
))
202 return str(self
.value
)
205 class _3ds_string(object):
206 """Class representing a zero-terminated string for a 3ds file."""
207 __slots__
= ("value", )
209 def __init__(self
, val
):
210 assert type(val
) == bytes
214 return (len(self
.value
) + 1)
216 def write(self
, file):
217 binary_format
= "<%ds" % (len(self
.value
) + 1)
218 file.write(struct
.pack(binary_format
, self
.value
))
221 return str(self
.value
)
224 class _3ds_point_3d(object):
225 """Class representing a three-dimensional point for a 3ds file."""
226 __slots__
= "x", "y", "z"
228 def __init__(self
, point
):
229 self
.x
, self
.y
, self
.z
= point
234 def write(self
, file):
235 file.write(struct
.pack('<3f', self
.x
, self
.y
, self
.z
))
238 return '(%f, %f, %f)' % (self
.x
, self
.y
, self
.z
)
241 # Used for writing a track
243 class _3ds_point_4d(object):
244 """Class representing a four-dimensional point for a 3ds file, for instance a quaternion."""
245 __slots__ = "x","y","z","w"
246 def __init__(self, point=(0.0,0.0,0.0,0.0)):
247 self.x, self.y, self.z, self.w = point
252 def write(self,file):
253 data=struct.pack('<4f', self.x, self.y, self.z, self.w)
257 return '(%f, %f, %f, %f)' % (self.x, self.y, self.z, self.w)
261 class _3ds_point_uv(object):
262 """Class representing a UV-coordinate for a 3ds file."""
265 def __init__(self
, point
):
271 def write(self
, file):
272 data
= struct
.pack('<2f', self
.uv
[0], self
.uv
[1])
276 return '(%g, %g)' % self
.uv
279 class _3ds_float_color(object):
280 """Class representing a rgb float color for a 3ds file."""
281 __slots__
= "r", "g", "b"
283 def __init__(self
, col
):
284 self
.r
, self
.g
, self
.b
= col
289 def write(self
, file):
290 file.write(struct
.pack('3f', self
.r
, self
.g
, self
.b
))
293 return '{%f, %f, %f}' % (self
.r
, self
.g
, self
.b
)
296 class _3ds_rgb_color(object):
297 """Class representing a (24-bit) rgb color for a 3ds file."""
298 __slots__
= "r", "g", "b"
300 def __init__(self
, col
):
301 self
.r
, self
.g
, self
.b
= col
306 def write(self
, file):
307 file.write(struct
.pack('<3B', int(255 * self
.r
), int(255 * self
.g
), int(255 * self
.b
)))
310 return '{%f, %f, %f}' % (self
.r
, self
.g
, self
.b
)
313 class _3ds_face(object):
314 """Class representing a face for a 3ds file."""
315 __slots__
= ("vindex", "flag")
317 def __init__(self
, vindex
, flag
):
324 # no need to validate every face vert. the oversized array will
327 def write(self
, file):
328 # The last short is used for face flags
329 file.write(struct
.pack("<4H", self
.vindex
[0], self
.vindex
[1], self
.vindex
[2], self
.flag
))
332 return "[%d %d %d %d]" % (self
.vindex
[0], self
.vindex
[1], self
.vindex
[2], self
.flag
)
335 class _3ds_array(object):
336 """Class representing an array of variables for a 3ds file.
338 Consists of a _3ds_ushort to indicate the number of items, followed by the items themselves.
340 __slots__
= "values", "size"
348 self
.values
.append(item
)
349 self
.size
+= item
.get_size()
355 return len(self
.values
) <= 65535
357 def write(self
, file):
358 _3ds_ushort(len(self
.values
)).write(file)
359 for value
in self
.values
:
362 # To not overwhelm the output in a dump, a _3ds_array only
363 # outputs the number of items, not all of the actual items.
365 return '(%d items)' % len(self
.values
)
368 class _3ds_named_variable(object):
369 """Convenience class for named variables."""
371 __slots__
= "value", "name"
373 def __init__(self
, name
, val
=None):
378 if self
.value
is None:
381 return self
.value
.get_size()
383 def write(self
, file):
384 if self
.value
is not None:
385 self
.value
.write(file)
387 def dump(self
, indent
):
388 if self
.value
is not None:
390 self
.name
if self
.name
else "[unnamed]",
396 class _3ds_chunk(object):
397 """Class representing a chunk in a 3ds file.
399 Chunks contain zero or more variables, followed by zero or more subchunks.
401 __slots__
= "ID", "size", "variables", "subchunks"
403 def __init__(self
, chunk_id
=0):
404 self
.ID
= _3ds_ushort(chunk_id
)
405 self
.size
= _3ds_uint(0)
409 def add_variable(self
, name
, var
):
410 """Add a named variable.
412 The name is mostly for debugging purposes."""
413 self
.variables
.append(_3ds_named_variable(name
, var
))
415 def add_subchunk(self
, chunk
):
416 """Add a subchunk."""
417 self
.subchunks
.append(chunk
)
420 """Calculate the size of the chunk and return it.
422 The sizes of the variables and subchunks are used to determine this chunk\'s size."""
423 tmpsize
= self
.ID
.get_size() + self
.size
.get_size()
424 for variable
in self
.variables
:
425 tmpsize
+= variable
.get_size()
426 for subchunk
in self
.subchunks
:
427 tmpsize
+= subchunk
.get_size()
428 self
.size
.value
= tmpsize
429 return self
.size
.value
432 for var
in self
.variables
:
433 func
= getattr(var
.value
, "validate", None)
434 if (func
is not None) and not func():
437 for chunk
in self
.subchunks
:
438 func
= getattr(chunk
, "validate", None)
439 if (func
is not None) and not func():
444 def write(self
, file):
445 """Write the chunk to a file.
447 Uses the write function of the variables and the subchunks to do the actual work."""
450 self
.size
.write(file)
451 for variable
in self
.variables
:
453 for subchunk
in self
.subchunks
:
456 def dump(self
, indent
=0):
457 """Write the chunk to a file.
459 Dump is used for debugging purposes, to dump the contents of a chunk to the standard output.
460 Uses the dump function of the named variables and the subchunks to do the actual work."""
462 "ID=%r" % hex(self
.ID
.value
),
463 "size=%r" % self
.get_size())
464 for variable
in self
.variables
:
465 variable
.dump(indent
+ 1)
466 for subchunk
in self
.subchunks
:
467 subchunk
.dump(indent
+ 1)
470 ######################################################
472 ######################################################
474 def get_material_image(material
):
475 """ Get images from paint slots."""
477 pt
= material
.paint_active_slot
478 tex
= material
.texture_paint_images
481 if slot
.type == 'IMAGE':
485 def get_uv_image(ma
):
486 """ Get image from material wrapper."""
487 if ma
and ma
.use_nodes
:
488 ma_wrap
= node_shader_utils
.PrincipledBSDFWrapper(ma
)
489 ma_tex
= ma_wrap
.base_color_texture
490 if ma_tex
and ma_tex
.image
is not None:
493 return get_material_image(ma
)
496 def make_material_subchunk(chunk_id
, color
):
497 """Make a material subchunk.
499 Used for color subchunks, such as diffuse color or ambient color subchunks."""
500 mat_sub
= _3ds_chunk(chunk_id
)
501 col1
= _3ds_chunk(RGB1
)
502 col1
.add_variable("color1", _3ds_rgb_color(color
))
503 mat_sub
.add_subchunk(col1
)
505 # col2 = _3ds_chunk(RGBI)
506 # col2.add_variable("color2", _3ds_rgb_color(color))
507 # mat_sub.add_subchunk(col2)
511 def make_percent_subchunk(chunk_id
, percent
):
512 """Make a percentage based subchunk."""
513 pct_sub
= _3ds_chunk(chunk_id
)
514 pcti
= _3ds_chunk(PCT
)
515 pcti
.add_variable("percent", _3ds_ushort(int(round(percent
* 100, 0))))
516 pct_sub
.add_subchunk(pcti
)
520 def make_texture_chunk(chunk_id
, images
):
521 """Make Material Map texture chunk."""
522 # Add texture percentage value (100 = 1.0)
523 ma_sub
= make_percent_subchunk(chunk_id
, 1)
527 filename
= bpy
.path
.basename(image
.filepath
)
528 ma_sub_file
= _3ds_chunk(MATMAPFILE
)
529 ma_sub_file
.add_variable("image", _3ds_string(sane_name(filename
)))
530 ma_sub
.add_subchunk(ma_sub_file
)
536 return ma_sub
if has_entry
else None
539 def make_material_texture_chunk(chunk_id
, texslots
, pct
):
540 """Make Material Map texture chunk given a seq. of `MaterialTextureSlot`'s
541 Paint slots are optionally used as image source if no nodes are
542 used. No additional filtering for mapping modes is done, all
543 slots are written "as is"."""
544 # Add texture percentage value
545 mat_sub
= make_percent_subchunk(chunk_id
, pct
)
548 def add_texslot(texslot
):
549 image
= texslot
.image
551 filename
= bpy
.path
.basename(image
.filepath
)
552 mat_sub_file
= _3ds_chunk(MATMAPFILE
)
553 mat_sub_file
.add_variable("mapfile", _3ds_string(sane_name(filename
)))
554 mat_sub
.add_subchunk(mat_sub_file
)
555 for link
in texslot
.socket_dst
.links
:
556 socket
= link
.from_socket
.identifier
560 # no perfect mapping for mirror modes - 3DS only has uniform mirror w. repeat=2
561 if texslot
.extension
== 'EXTEND':
563 # CLIP maps to 3DS' decal flag
564 elif texslot
.extension
== 'CLIP':
567 mat_sub_tile
= _3ds_chunk(MAT_MAP_TILING
)
568 mat_sub_tile
.add_variable("tiling", _3ds_ushort(maptile
))
569 mat_sub
.add_subchunk(mat_sub_tile
)
571 if socket
== 'Alpha':
572 mat_sub_alpha
= _3ds_chunk(MAP_TILING
)
573 alphaflag
= 0x40 # summed area sampling 0x20
574 mat_sub_alpha
.add_variable("alpha", _3ds_ushort(alphaflag
))
575 mat_sub
.add_subchunk(mat_sub_alpha
)
576 if texslot
.socket_dst
.identifier
in {'Base Color', 'Specular'}:
577 mat_sub_tint
= _3ds_chunk(MAP_TILING
) # RGB tint 0x200
578 tint
= 0x80 if texslot
.image
.colorspace_settings
.name
== 'Non-Color' else 0x200
579 mat_sub_tint
.add_variable("tint", _3ds_ushort(tint
))
580 mat_sub
.add_subchunk(mat_sub_tint
)
582 mat_sub_texblur
= _3ds_chunk(MAT_MAP_TEXBLUR
) # Based on observation this is usually 1.0
583 mat_sub_texblur
.add_variable("maptexblur", _3ds_float(1.0))
584 mat_sub
.add_subchunk(mat_sub_texblur
)
586 mat_sub_uscale
= _3ds_chunk(MAT_MAP_USCALE
)
587 mat_sub_uscale
.add_variable("mapuscale", _3ds_float(round(texslot
.scale
[0], 6)))
588 mat_sub
.add_subchunk(mat_sub_uscale
)
590 mat_sub_vscale
= _3ds_chunk(MAT_MAP_VSCALE
)
591 mat_sub_vscale
.add_variable("mapvscale", _3ds_float(round(texslot
.scale
[1], 6)))
592 mat_sub
.add_subchunk(mat_sub_vscale
)
594 mat_sub_uoffset
= _3ds_chunk(MAT_MAP_UOFFSET
)
595 mat_sub_uoffset
.add_variable("mapuoffset", _3ds_float(round(texslot
.translation
[0], 6)))
596 mat_sub
.add_subchunk(mat_sub_uoffset
)
598 mat_sub_voffset
= _3ds_chunk(MAT_MAP_VOFFSET
)
599 mat_sub_voffset
.add_variable("mapvoffset", _3ds_float(round(texslot
.translation
[1], 6)))
600 mat_sub
.add_subchunk(mat_sub_voffset
)
602 mat_sub_angle
= _3ds_chunk(MAT_MAP_ANG
)
603 mat_sub_angle
.add_variable("mapangle", _3ds_float(round(texslot
.rotation
[2], 6)))
604 mat_sub
.add_subchunk(mat_sub_angle
)
606 if texslot
.socket_dst
.identifier
in {'Base Color', 'Specular'}:
607 rgb
= _3ds_chunk(MAP_COL1
) # Add tint color
608 base
= texslot
.owner_shader
.material
.diffuse_color
[:3]
609 spec
= texslot
.owner_shader
.material
.specular_color
[:]
610 rgb
.add_variable("mapcolor", _3ds_rgb_color(spec
if texslot
.socket_dst
.identifier
== 'Specular' else base
))
611 mat_sub
.add_subchunk(rgb
)
613 # store all textures for this mapto in order. This at least is what
614 # the 3DS exporter did so far, afaik most readers will just skip
616 for slot
in texslots
:
617 if slot
.image
is not None:
621 return mat_sub
if has_entry
else None
624 def make_material_chunk(material
, image
):
625 """Make a material chunk out of a blender material.
626 Shading method is required for 3ds max, 0 for wireframe.
627 0x1 for flat, 0x2 for gouraud, 0x3 for phong and 0x4 for metal."""
628 material_chunk
= _3ds_chunk(MATERIAL
)
629 name
= _3ds_chunk(MATNAME
)
630 shading
= _3ds_chunk(MATSHADING
)
632 name_str
= material
.name
if material
else "None"
635 # name_str += image.name
637 name
.add_variable("name", _3ds_string(sane_name(name_str
)))
638 material_chunk
.add_subchunk(name
)
641 shading
.add_variable("shading", _3ds_ushort(1)) # Flat shading
642 material_chunk
.add_subchunk(make_material_subchunk(MATAMBIENT
, (0.0, 0.0, 0.0)))
643 material_chunk
.add_subchunk(make_material_subchunk(MATDIFFUSE
, (0.8, 0.8, 0.8)))
644 material_chunk
.add_subchunk(make_material_subchunk(MATSPECULAR
, (1.0, 1.0, 1.0)))
645 material_chunk
.add_subchunk(make_percent_subchunk(MATSHINESS
, 0.8))
646 material_chunk
.add_subchunk(make_percent_subchunk(MATSHIN2
, 1))
647 material_chunk
.add_subchunk(shading
)
649 elif material
and material
.use_nodes
:
650 wrap
= node_shader_utils
.PrincipledBSDFWrapper(material
)
651 shading
.add_variable("shading", _3ds_ushort(3)) # Phong shading
652 material_chunk
.add_subchunk(make_material_subchunk(MATAMBIENT
, wrap
.emission_color
[:3]))
653 material_chunk
.add_subchunk(make_material_subchunk(MATDIFFUSE
, wrap
.base_color
[:3]))
654 material_chunk
.add_subchunk(make_material_subchunk(MATSPECULAR
, material
.specular_color
[:]))
655 material_chunk
.add_subchunk(make_percent_subchunk(MATSHINESS
, 1 - wrap
.roughness
))
656 material_chunk
.add_subchunk(make_percent_subchunk(MATSHIN2
, wrap
.specular
))
657 material_chunk
.add_subchunk(make_percent_subchunk(MATSHIN3
, wrap
.metallic
))
658 material_chunk
.add_subchunk(make_percent_subchunk(MATTRANS
, 1 - wrap
.alpha
))
659 material_chunk
.add_subchunk(make_percent_subchunk(MATSELFILPCT
, wrap
.emission_strength
))
660 material_chunk
.add_subchunk(shading
)
664 if wrap
.base_color_texture
:
665 d_pct
= 0.7 + sum(wrap
.base_color
[:]) * 0.1
666 color
= [wrap
.base_color_texture
]
667 matmap
= make_material_texture_chunk(MAT_DIFFUSEMAP
, color
, d_pct
)
669 material_chunk
.add_subchunk(matmap
)
672 if wrap
.specular_texture
:
673 spec
= [wrap
.specular_texture
]
674 s_pct
= material
.specular_intensity
675 matmap
= make_material_texture_chunk(MAT_SPECMAP
, spec
, s_pct
)
677 material_chunk
.add_subchunk(matmap
)
679 if wrap
.alpha_texture
:
680 alpha
= [wrap
.alpha_texture
]
681 a_pct
= material
.diffuse_color
[3]
682 matmap
= make_material_texture_chunk(MAT_OPACMAP
, alpha
, a_pct
)
684 material_chunk
.add_subchunk(matmap
)
686 if wrap
.metallic_texture
:
687 metallic
= [wrap
.metallic_texture
]
688 m_pct
= material
.metallic
689 matmap
= make_material_texture_chunk(MAT_REFLMAP
, metallic
, m_pct
)
691 material_chunk
.add_subchunk(matmap
)
693 if wrap
.normalmap_texture
:
694 normal
= [wrap
.normalmap_texture
]
695 b_pct
= wrap
.normalmap_strength
696 matmap
= make_material_texture_chunk(MAT_BUMPMAP
, normal
, b_pct
)
698 material_chunk
.add_subchunk(matmap
)
699 material_chunk
.add_subchunk(make_percent_subchunk(MAT_BUMP_PERCENT
, b_pct
))
701 if wrap
.roughness_texture
:
702 roughness
= [wrap
.roughness_texture
]
703 r_pct
= 1 - material
.roughness
704 matmap
= make_material_texture_chunk(MAT_SHINMAP
, roughness
, r_pct
)
706 material_chunk
.add_subchunk(matmap
)
708 if wrap
.emission_color_texture
:
709 e_pct
= wrap
.emission_strength
710 emission
= [wrap
.emission_color_texture
]
711 matmap
= make_material_texture_chunk(MAT_SELFIMAP
, emission
, e_pct
)
713 material_chunk
.add_subchunk(matmap
)
715 # make sure no textures are lost. Everything that doesn't fit
716 # into a channel is exported as secondary texture
719 for link
in wrap
.material
.node_tree
.links
:
720 if link
.from_node
.type == 'TEX_IMAGE' and link
.to_node
.type == 'MIX_RGB':
721 diffuse
= [link
.from_node
.image
]
725 matmap
= make_texture_chunk(MAT_DIFFUSEMAP
, diffuse
)
727 matmap
= make_texture_chunk(MAT_TEX2MAP
, diffuse
)
729 material_chunk
.add_subchunk(matmap
)
732 shading
.add_variable("shading", _3ds_ushort(2)) # Gouraud shading
733 material_chunk
.add_subchunk(make_material_subchunk(MATAMBIENT
, material
.line_color
[:3]))
734 material_chunk
.add_subchunk(make_material_subchunk(MATDIFFUSE
, material
.diffuse_color
[:3]))
735 material_chunk
.add_subchunk(make_material_subchunk(MATSPECULAR
, material
.specular_color
[:]))
736 material_chunk
.add_subchunk(make_percent_subchunk(MATSHINESS
, 1 - material
.roughness
))
737 material_chunk
.add_subchunk(make_percent_subchunk(MATSHIN2
, material
.specular_intensity
))
738 material_chunk
.add_subchunk(make_percent_subchunk(MATSHIN3
, material
.metallic
))
739 material_chunk
.add_subchunk(make_percent_subchunk(MATTRANS
, 1 - material
.diffuse_color
[3]))
740 material_chunk
.add_subchunk(shading
)
742 slots
= [get_material_image(material
)] # can be None
745 material_chunk
.add_subchunk(make_texture_chunk(MAT_DIFFUSEMAP
, slots
))
747 return material_chunk
750 class tri_wrapper(object):
751 """Class representing a triangle.
752 Used when converting faces to triangles"""
754 __slots__
= "vertex_index", "ma", "image", "faceuvs", "offset", "flag", "group"
756 def __init__(self
, vindex
=(0, 0, 0), ma
=None, image
=None, faceuvs
=None, flag
=0, group
=0):
757 self
.vertex_index
= vindex
760 self
.faceuvs
= faceuvs
761 self
.offset
= [0, 0, 0] # offset indices
766 def extract_triangles(mesh
):
767 """Extract triangles from a mesh."""
769 mesh
.calc_loop_triangles()
770 (polygroup
, count
) = mesh
.calc_smooth_groups(use_bitflags
=True)
773 do_uv
= bool(mesh
.uv_layers
)
776 for i
, face
in enumerate(mesh
.loop_triangles
):
779 uf
= mesh
.uv_layers
.active
.data
if do_uv
else None
782 f_uv
= [uf
[lp
].uv
for lp
in face
.loops
]
783 for ma
in mesh
.materials
:
784 img
= get_uv_image(ma
) if uf
else None
789 """Flag 0x1 sets CA edge visible, Flag 0x2 sets BC edge visible, Flag 0x4 sets AB edge visible
790 Flag 0x8 indicates a U axis texture wrap seam and Flag 0x10 indicates a V axis texture wrap seam
791 In Blender we use the edge CA, BC, and AB flags for sharp edges flags"""
792 a_b
= mesh
.edges
[mesh
.loops
[face
.loops
[0]].edge_index
]
793 b_c
= mesh
.edges
[mesh
.loops
[face
.loops
[1]].edge_index
]
794 c_a
= mesh
.edges
[mesh
.loops
[face
.loops
[2]].edge_index
]
797 v1
, v2
, v3
= v3
, v1
, v2
798 a_b
, b_c
, c_a
= c_a
, a_b
, b_c
800 uv1
, uv2
, uv3
= uv3
, uv1
, uv2
803 if c_a
.use_edge_sharp
:
804 faceflag
= faceflag
+ 0x1
805 if b_c
.use_edge_sharp
:
806 faceflag
= faceflag
+ 0x2
807 if a_b
.use_edge_sharp
:
808 faceflag
= faceflag
+ 0x4
810 smoothgroup
= polygroup
[face
.polygon_index
]
813 new_tri
= tri_wrapper((v1
, v2
, v3
), face
.material_index
, img
)
815 new_tri
.faceuvs
= uv_key(uv1
), uv_key(uv2
), uv_key(uv3
)
816 new_tri
.flag
= faceflag
817 new_tri
.group
= smoothgroup
if face
.use_smooth
else 0
818 tri_list
.append(new_tri
)
823 def remove_face_uv(verts
, tri_list
):
824 """Remove face UV coordinates from a list of triangles.
825 Since 3ds files only support one pair of uv coordinates for each vertex, face uv coordinates
826 need to be converted to vertex uv coordinates. That means that vertices need to be duplicated when
827 there are multiple uv coordinates per vertex."""
829 # initialize a list of UniqueLists, one per vertex:
830 # uv_list = [UniqueList() for i in xrange(len(verts))]
831 unique_uvs
= [{} for i
in range(len(verts
))]
833 # for each face uv coordinate, add it to the UniqueList of the vertex
836 # store the index into the UniqueList for future reference:
837 # offset.append(uv_list[tri.vertex_index[i]].add(_3ds_point_uv(tri.faceuvs[i])))
839 context_uv_vert
= unique_uvs
[tri
.vertex_index
[i
]]
840 uvkey
= tri
.faceuvs
[i
]
842 offset_index__uv_3ds
= context_uv_vert
.get(uvkey
)
844 if not offset_index__uv_3ds
:
845 offset_index__uv_3ds
= context_uv_vert
[uvkey
] = len(context_uv_vert
), _3ds_point_uv(uvkey
)
847 tri
.offset
[i
] = offset_index__uv_3ds
[0]
849 # At this point, each vertex has a UniqueList containing every uv coordinate that is associated with it
852 # Now we need to duplicate every vertex as many times as it has uv coordinates and make sure the
853 # faces refer to the new face indices:
855 vert_array
= _3ds_array()
856 uv_array
= _3ds_array()
858 for i
, vert
in enumerate(verts
):
859 index_list
.append(vert_index
)
861 pt
= _3ds_point_3d(vert
.co
) # reuse, should be ok
862 uvmap
= [None] * len(unique_uvs
[i
])
863 for ii
, uv_3ds
in unique_uvs
[i
].values():
864 # add a vertex duplicate to the vertex_array for every uv associated with this vertex:
866 # add the uv coordinate to the uv array:
867 # This for loop does not give uv's ordered by ii, so we create a new map
868 # and add the uv's later
869 # uv_array.add(uv_3ds)
872 # Add the uv's in the correct order
874 # add the uv coordinate to the uv array:
877 vert_index
+= len(unique_uvs
[i
])
879 # Make sure the triangle vertex indices now refer to the new vertex list:
882 tri
.offset
[i
] += index_list
[tri
.vertex_index
[i
]]
883 tri
.vertex_index
= tri
.offset
885 return vert_array
, uv_array
, tri_list
888 def make_faces_chunk(tri_list
, mesh
, materialDict
):
889 """Make a chunk for the faces.
890 Also adds subchunks assigning materials to all faces."""
892 use_smooth
= [poly
.use_smooth
for poly
in mesh
.polygons
]
893 if True in use_smooth
:
896 materials
= mesh
.materials
900 face_chunk
= _3ds_chunk(OBJECT_FACES
)
901 face_list
= _3ds_array()
904 # Gather materials used in this mesh - mat/image pairs
906 for i
, tri
in enumerate(tri_list
):
907 face_list
.add(_3ds_face(tri
.vertex_index
, tri
.flag
))
910 ma
= materials
[tri
.ma
]
917 context_face_array
= unique_mats
[ma
, img
][1]
919 name_str
= ma
if ma
else "None"
923 context_face_array
= _3ds_array()
924 unique_mats
[ma
, img
] = _3ds_string(sane_name(name_str
)), context_face_array
926 context_face_array
.add(_3ds_ushort(i
))
927 # obj_material_faces[tri.ma].add(_3ds_ushort(i))
929 face_chunk
.add_variable("faces", face_list
)
930 for ma_name
, ma_faces
in unique_mats
.values():
931 obj_material_chunk
= _3ds_chunk(OBJECT_MATERIAL
)
932 obj_material_chunk
.add_variable("name", ma_name
)
933 obj_material_chunk
.add_variable("face_list", ma_faces
)
934 face_chunk
.add_subchunk(obj_material_chunk
)
937 obj_material_faces
= []
938 obj_material_names
= []
941 obj_material_names
.append(_3ds_string(sane_name(m
.name
)))
942 obj_material_faces
.append(_3ds_array())
943 n_materials
= len(obj_material_names
)
945 for i
, tri
in enumerate(tri_list
):
946 face_list
.add(_3ds_face(tri
.vertex_index
, tri
.flag
))
947 if (tri
.ma
< n_materials
):
948 obj_material_faces
[tri
.ma
].add(_3ds_ushort(i
))
950 face_chunk
.add_variable("faces", face_list
)
951 for i
in range(n_materials
):
952 obj_material_chunk
= _3ds_chunk(OBJECT_MATERIAL
)
953 obj_material_chunk
.add_variable("name", obj_material_names
[i
])
954 obj_material_chunk
.add_variable("face_list", obj_material_faces
[i
])
955 face_chunk
.add_subchunk(obj_material_chunk
)
958 obj_smooth_chunk
= _3ds_chunk(OBJECT_SMOOTH
)
959 for i
, tri
in enumerate(tri_list
):
960 obj_smooth_chunk
.add_variable("face_" + str(i
), _3ds_uint(tri
.group
))
961 face_chunk
.add_subchunk(obj_smooth_chunk
)
966 def make_vert_chunk(vert_array
):
967 """Make a vertex chunk out of an array of vertices."""
968 vert_chunk
= _3ds_chunk(OBJECT_VERTICES
)
969 vert_chunk
.add_variable("vertices", vert_array
)
973 def make_uv_chunk(uv_array
):
974 """Make a UV chunk out of an array of UVs."""
975 uv_chunk
= _3ds_chunk(OBJECT_UV
)
976 uv_chunk
.add_variable("uv coords", uv_array
)
981 def make_matrix_4x3_chunk(matrix):
982 matrix_chunk = _3ds_chunk(OBJECT_TRANS_MATRIX)
983 for vec in matrix.col:
985 matrix_chunk.add_variable("matrix_f", _3ds_float(f))
990 def make_mesh_chunk(ob
, mesh
, matrix
, materialDict
, translation
):
991 """Make a chunk out of a Blender mesh."""
993 # Extract the triangles from the mesh:
994 tri_list
= extract_triangles(mesh
)
997 # Remove the face UVs and convert it to vertex UV:
998 vert_array
, uv_array
, tri_list
= remove_face_uv(mesh
.vertices
, tri_list
)
1000 # Add the vertices to the vertex array:
1001 vert_array
= _3ds_array()
1002 for vert
in mesh
.vertices
:
1003 vert_array
.add(_3ds_point_3d(vert
.co
))
1008 mesh_chunk
= _3ds_chunk(OBJECT_MESH
)
1011 mesh_chunk
.add_subchunk(make_vert_chunk(vert_array
))
1014 mesh_chunk
.add_subchunk(make_faces_chunk(tri_list
, mesh
, materialDict
))
1016 # if available, add uv chunk:
1018 mesh_chunk
.add_subchunk(make_uv_chunk(uv_array
))
1020 # mesh_chunk.add_subchunk(make_matrix_4x3_chunk(matrix))
1022 # create transformation matrix chunk
1023 matrix_chunk
= _3ds_chunk(OBJECT_TRANS_MATRIX
)
1024 obj_matrix
= matrix
.transposed().to_3x3()
1026 if ob
.parent
is None:
1027 obj_translate
= translation
[ob
.name
]
1029 else: # Calculate child matrix translation relative to parent
1030 obj_translate
= translation
[ob
.name
].cross(-1 * translation
[ob
.parent
.name
])
1032 matrix_chunk
.add_variable("xx", _3ds_float(obj_matrix
[0].to_tuple(6)[0]))
1033 matrix_chunk
.add_variable("xy", _3ds_float(obj_matrix
[0].to_tuple(6)[1]))
1034 matrix_chunk
.add_variable("xz", _3ds_float(obj_matrix
[0].to_tuple(6)[2]))
1035 matrix_chunk
.add_variable("yx", _3ds_float(obj_matrix
[1].to_tuple(6)[0]))
1036 matrix_chunk
.add_variable("yy", _3ds_float(obj_matrix
[1].to_tuple(6)[1]))
1037 matrix_chunk
.add_variable("yz", _3ds_float(obj_matrix
[1].to_tuple(6)[2]))
1038 matrix_chunk
.add_variable("zx", _3ds_float(obj_matrix
[2].to_tuple(6)[0]))
1039 matrix_chunk
.add_variable("zy", _3ds_float(obj_matrix
[2].to_tuple(6)[1]))
1040 matrix_chunk
.add_variable("zz", _3ds_float(obj_matrix
[2].to_tuple(6)[2]))
1041 matrix_chunk
.add_variable("tx", _3ds_float(obj_translate
.to_tuple(6)[0]))
1042 matrix_chunk
.add_variable("ty", _3ds_float(obj_translate
.to_tuple(6)[1]))
1043 matrix_chunk
.add_variable("tz", _3ds_float(obj_translate
.to_tuple(6)[2]))
1045 mesh_chunk
.add_subchunk(matrix_chunk
)
1050 ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
1051 def make_kfdata(start=0, stop=0, curtime=0):
1052 """Make the basic keyframe data chunk"""
1053 kfdata = _3ds_chunk(KFDATA)
1055 kfhdr = _3ds_chunk(KFDATA_KFHDR)
1056 kfhdr.add_variable("revision", _3ds_ushort(0))
1057 # Not really sure what filename is used for, but it seems it is usually used
1058 # to identify the program that generated the .3ds:
1059 kfhdr.add_variable("filename", _3ds_string("Blender"))
1060 kfhdr.add_variable("animlen", _3ds_uint(stop-start))
1062 kfseg = _3ds_chunk(KFDATA_KFSEG)
1063 kfseg.add_variable("start", _3ds_uint(start))
1064 kfseg.add_variable("stop", _3ds_uint(stop))
1066 kfcurtime = _3ds_chunk(KFDATA_KFCURTIME)
1067 kfcurtime.add_variable("curtime", _3ds_uint(curtime))
1069 kfdata.add_subchunk(kfhdr)
1070 kfdata.add_subchunk(kfseg)
1071 kfdata.add_subchunk(kfcurtime)
1074 def make_track_chunk(ID, obj):
1075 """Make a chunk for track data.
1077 Depending on the ID, this will construct a position, rotation or scale track."""
1078 track_chunk = _3ds_chunk(ID)
1079 track_chunk.add_variable("track_flags", _3ds_ushort())
1080 track_chunk.add_variable("unknown", _3ds_uint())
1081 track_chunk.add_variable("unknown", _3ds_uint())
1082 track_chunk.add_variable("nkeys", _3ds_uint(1))
1083 # Next section should be repeated for every keyframe, but for now, animation is not actually supported.
1084 track_chunk.add_variable("tcb_frame", _3ds_uint(0))
1085 track_chunk.add_variable("tcb_flags", _3ds_ushort())
1086 if obj.type=='Empty':
1087 if ID==POS_TRACK_TAG:
1089 track_chunk.add_variable("position", _3ds_point_3d(obj.getLocation()))
1090 elif ID==ROT_TRACK_TAG:
1091 # rotation (quaternion, angle first, followed by axis):
1092 q = obj.getEuler().to_quaternion() # XXX, todo!
1093 track_chunk.add_variable("rotation", _3ds_point_4d((q.angle, q.axis[0], q.axis[1], q.axis[2])))
1094 elif ID==SCL_TRACK_TAG:
1096 track_chunk.add_variable("scale", _3ds_point_3d(obj.getSize()))
1098 # meshes have their transformations applied before
1099 # exporting, so write identity transforms here:
1100 if ID==POS_TRACK_TAG:
1102 track_chunk.add_variable("position", _3ds_point_3d((0.0,0.0,0.0)))
1103 elif ID==ROT_TRACK_TAG:
1104 # rotation (quaternion, angle first, followed by axis):
1105 track_chunk.add_variable("rotation", _3ds_point_4d((0.0, 1.0, 0.0, 0.0)))
1106 elif ID==SCL_TRACK_TAG:
1108 track_chunk.add_variable("scale", _3ds_point_3d((1.0, 1.0, 1.0)))
1112 def make_kf_obj_node(obj, name_to_id):
1113 """Make a node chunk for a Blender object.
1115 Takes the Blender object as a parameter. Object id's are taken from the dictionary name_to_id.
1116 Blender Empty objects are converted to dummy nodes."""
1119 # main object node chunk:
1120 kf_obj_node = _3ds_chunk(KFDATA_OBJECT_NODE_TAG)
1121 # chunk for the object id:
1122 obj_id_chunk = _3ds_chunk(OBJECT_NODE_ID)
1123 # object id is from the name_to_id dictionary:
1124 obj_id_chunk.add_variable("node_id", _3ds_ushort(name_to_id[name]))
1126 # object node header:
1127 obj_node_header_chunk = _3ds_chunk(OBJECT_NODE_HDR)
1129 if obj.type == 'Empty':
1130 # Empties are called "$$$DUMMY" and use the OBJECT_INSTANCE_NAME chunk
1131 # for their name (see below):
1132 obj_node_header_chunk.add_variable("name", _3ds_string("$$$DUMMY"))
1135 obj_node_header_chunk.add_variable("name", _3ds_string(sane_name(name)))
1136 # Add Flag variables (not sure what they do):
1137 obj_node_header_chunk.add_variable("flags1", _3ds_ushort(0))
1138 obj_node_header_chunk.add_variable("flags2", _3ds_ushort(0))
1140 # Check parent-child relationships:
1142 if (parent is None) or (parent.name not in name_to_id):
1143 # If no parent, or the parents name is not in the name_to_id dictionary,
1144 # parent id becomes -1:
1145 obj_node_header_chunk.add_variable("parent", _3ds_ushort(-1))
1147 # Get the parent's id from the name_to_id dictionary:
1148 obj_node_header_chunk.add_variable("parent", _3ds_ushort(name_to_id[parent.name]))
1151 obj_pivot_chunk = _3ds_chunk(OBJECT_PIVOT)
1152 obj_pivot_chunk.add_variable("pivot", _3ds_point_3d(obj.getLocation()))
1153 kf_obj_node.add_subchunk(obj_pivot_chunk)
1155 # add subchunks for object id and node header:
1156 kf_obj_node.add_subchunk(obj_id_chunk)
1157 kf_obj_node.add_subchunk(obj_node_header_chunk)
1159 # Empty objects need to have an extra chunk for the instance name:
1160 if obj.type == 'Empty':
1161 obj_instance_name_chunk = _3ds_chunk(OBJECT_INSTANCE_NAME)
1162 obj_instance_name_chunk.add_variable("name", _3ds_string(sane_name(name)))
1163 kf_obj_node.add_subchunk(obj_instance_name_chunk)
1165 # Add track chunks for position, rotation and scale:
1166 kf_obj_node.add_subchunk(make_track_chunk(POS_TRACK_TAG, obj))
1167 kf_obj_node.add_subchunk(make_track_chunk(ROT_TRACK_TAG, obj))
1168 kf_obj_node.add_subchunk(make_track_chunk(SCL_TRACK_TAG, obj))
1175 context
, filepath
="",
1181 # from bpy_extras.io_utils import create_derived_objects, free_derived_objects
1183 """Save the Blender scene to a 3ds file."""
1186 duration
= time
.time()
1187 # Blender.Window.WaitCursor(1)
1189 if global_matrix
is None:
1190 global_matrix
= mathutils
.Matrix()
1192 if bpy
.ops
.object.mode_set
.poll():
1193 bpy
.ops
.object.mode_set(mode
='OBJECT')
1195 scene
= context
.scene
1196 layer
= context
.view_layer
1197 depsgraph
= context
.evaluated_depsgraph_get()
1199 # Initialize the main chunk (primary):
1200 primary
= _3ds_chunk(PRIMARY
)
1201 # Add version chunk:
1202 version_chunk
= _3ds_chunk(VERSION
)
1203 version_chunk
.add_variable("version", _3ds_uint(3))
1204 primary
.add_subchunk(version_chunk
)
1206 # Init main object info chunk:
1207 object_info
= _3ds_chunk(OBJECTINFO
)
1208 mesh_version
= _3ds_chunk(MESHVERSION
)
1209 mesh_version
.add_variable("mesh", _3ds_uint(3))
1210 object_info
.add_subchunk(mesh_version
)
1212 # Add MASTERSCALE element
1213 mscale
= _3ds_chunk(MASTERSCALE
)
1214 mscale
.add_variable("scale", _3ds_float(1))
1215 object_info
.add_subchunk(mscale
)
1218 if scene
.world
is not None:
1219 ambient_chunk
= _3ds_chunk(AMBIENTLIGHT
)
1220 ambient_light
= _3ds_chunk(RGB
)
1221 ambient_light
.add_variable("ambient", _3ds_float_color(scene
.world
.color
))
1222 ambient_chunk
.add_subchunk(ambient_light
)
1223 object_info
.add_subchunk(ambient_chunk
)
1225 ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
1226 # init main key frame data chunk:
1227 kfdata = make_kfdata()
1230 # Make a list of all materials used in the selected meshes (use a dictionary,
1231 # each material is added once):
1236 objects
= [ob
for ob
in scene
.objects
if not ob
.hide_viewport
and ob
.select_get(view_layer
=layer
)]
1238 objects
= [ob
for ob
in scene
.objects
if not ob
.hide_viewport
]
1240 light_objects
= [ob
for ob
in objects
if ob
.type == 'LIGHT']
1241 camera_objects
= [ob
for ob
in objects
if ob
.type == 'CAMERA']
1244 # get derived objects
1245 # free, derived = create_derived_objects(scene, ob)
1246 derived_dict
= bpy_extras
.io_utils
.create_derived_objects(depsgraph
, [ob
])
1247 derived
= derived_dict
.get(ob
)
1252 for ob_derived
, mtx
in derived
:
1253 if ob
.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}:
1257 data
= ob_derived
.to_mesh()
1262 matrix
= global_matrix
@ mtx
1263 data
.transform(matrix
)
1264 mesh_objects
.append((ob_derived
, data
, matrix
))
1265 ma_ls
= data
.materials
1266 ma_ls_len
= len(ma_ls
)
1268 # get material/image tuples.
1273 for f
, uf
in zip(data
.polygons
, data
.uv_layers
.active
.data
):
1275 ma_index
= f
.material_index
1276 if ma_index
>= ma_ls_len
:
1277 ma_index
= f
.material_index
= 0
1278 ma
= ma_ls
[ma_index
]
1279 ma_name
= None if ma
is None else ma
.name
1280 # else there already set to none
1282 img
= get_uv_image(ma
)
1283 img_name
= None if img
is None else img
.name
1285 materialDict
.setdefault((ma_name
, img_name
), (ma
, img
))
1289 if ma
: # material may be None so check its not.
1290 materialDict
.setdefault((ma
.name
, None), (ma
, None))
1293 for f
in data
.polygons
:
1294 if f
.material_index
>= ma_ls_len
:
1295 f
.material_index
= 0
1297 # ob_derived_eval.to_mesh_clear()
1300 # free_derived_objects(ob)
1302 # Make material chunks for all materials used in the meshes:
1303 for ma_image
in materialDict
.values():
1304 object_info
.add_subchunk(make_material_chunk(ma_image
[0], ma_image
[1]))
1306 # Give all objects a unique ID and build a dictionary from object name to object id:
1307 translation
= {} # collect translation for transformation matrix
1309 for ob
, data
, matrix
in mesh_objects
:
1310 translation
[ob
.name
] = ob
.location
1311 # name_to_id[ob.name]= len(name_to_id)
1313 #for ob in empty_objects:
1314 # name_to_id[ob.name]= len(name_to_id)
1317 # Create object chunks for all meshes:
1319 for ob
, mesh
, matrix
in mesh_objects
:
1320 # create a new object chunk
1321 object_chunk
= _3ds_chunk(OBJECT
)
1323 # set the object name
1324 object_chunk
.add_variable("name", _3ds_string(sane_name(ob
.name
)))
1326 # make a mesh chunk out of the mesh:
1327 object_chunk
.add_subchunk(make_mesh_chunk(ob
, mesh
, matrix
, materialDict
, translation
))
1329 # ensure the mesh has no over sized arrays
1330 # skip ones that do!, otherwise we cant write since the array size wont
1332 if object_chunk
.validate():
1333 object_info
.add_subchunk(object_chunk
)
1335 operator
.report({'WARNING'}, "Object %r can't be written into a 3DS file")
1337 ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
1338 # make a kf object node for the object:
1339 kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
1342 # if not blender_mesh.users:
1343 # bpy.data.meshes.remove(blender_mesh)
1344 # blender_mesh.vertices = None
1348 # Create chunks for all empties:
1349 ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
1350 for ob in empty_objects:
1351 # Empties only require a kf object node:
1352 kfdata.add_subchunk(make_kf_obj_node(ob, name_to_id))
1356 # Create light object chunks
1357 for ob
in light_objects
:
1358 object_chunk
= _3ds_chunk(OBJECT
)
1359 light_chunk
= _3ds_chunk(OBJECT_LIGHT
)
1360 color_float_chunk
= _3ds_chunk(RGB
)
1361 energy_factor
= _3ds_chunk(LIGHT_MULTIPLIER
)
1362 object_chunk
.add_variable("light", _3ds_string(sane_name(ob
.name
)))
1363 light_chunk
.add_variable("location", _3ds_point_3d(ob
.location
))
1364 color_float_chunk
.add_variable("color", _3ds_float_color(ob
.data
.color
))
1365 energy_factor
.add_variable("energy", _3ds_float(ob
.data
.energy
* .001))
1366 light_chunk
.add_subchunk(color_float_chunk
)
1367 light_chunk
.add_subchunk(energy_factor
)
1369 if ob
.data
.type == 'SPOT':
1370 cone_angle
= math
.degrees(ob
.data
.spot_size
)
1371 hotspot
= cone_angle
- (ob
.data
.spot_blend
* math
.floor(cone_angle
))
1372 hypo
= math
.copysign(math
.sqrt(pow(ob
.location
[0], 2) + pow(ob
.location
[1], 2)), ob
.location
[1])
1373 pos_x
= ob
.location
[0] + (ob
.location
[1] * math
.tan(ob
.rotation_euler
[2]))
1374 pos_y
= ob
.location
[1] + (ob
.location
[0] * math
.tan(math
.radians(90) - ob
.rotation_euler
[2]))
1375 pos_z
= hypo
* math
.tan(math
.radians(90) - ob
.rotation_euler
[0])
1376 spotlight_chunk
= _3ds_chunk(LIGHT_SPOTLIGHT
)
1377 spot_roll_chunk
= _3ds_chunk(LIGHT_SPOTROLL
)
1378 spotlight_chunk
.add_variable("target", _3ds_point_3d((pos_x
, pos_y
, pos_z
)))
1379 spotlight_chunk
.add_variable("hotspot", _3ds_float(round(hotspot
, 4)))
1380 spotlight_chunk
.add_variable("angle", _3ds_float(round(cone_angle
, 4)))
1381 spot_roll_chunk
.add_variable("roll", _3ds_float(round(ob
.rotation_euler
[1], 6)))
1382 spotlight_chunk
.add_subchunk(spot_roll_chunk
)
1383 light_chunk
.add_subchunk(spotlight_chunk
)
1385 # Add light to object info
1386 object_chunk
.add_subchunk(light_chunk
)
1387 object_info
.add_subchunk(object_chunk
)
1389 # Create camera object chunks
1390 for ob
in camera_objects
:
1391 object_chunk
= _3ds_chunk(OBJECT
)
1392 camera_chunk
= _3ds_chunk(OBJECT_CAMERA
)
1393 diagonal
= math
.copysign(math
.sqrt(pow(ob
.location
[0], 2) + pow(ob
.location
[1], 2)), ob
.location
[1])
1394 focus_x
= ob
.location
[0] + (ob
.location
[1] * math
.tan(ob
.rotation_euler
[2]))
1395 focus_y
= ob
.location
[1] + (ob
.location
[0] * math
.tan(math
.radians(90) - ob
.rotation_euler
[2]))
1396 focus_z
= diagonal
* math
.tan(math
.radians(90) - ob
.rotation_euler
[0])
1397 object_chunk
.add_variable("camera", _3ds_string(sane_name(ob
.name
)))
1398 camera_chunk
.add_variable("location", _3ds_point_3d(ob
.location
))
1399 camera_chunk
.add_variable("target", _3ds_point_3d((focus_x
, focus_y
, focus_z
)))
1400 camera_chunk
.add_variable("roll", _3ds_float(round(ob
.rotation_euler
[1], 6)))
1401 camera_chunk
.add_variable("lens", _3ds_float(ob
.data
.lens
))
1402 object_chunk
.add_subchunk(camera_chunk
)
1403 object_info
.add_subchunk(object_chunk
)
1405 # Add main object info chunk to primary chunk:
1406 primary
.add_subchunk(object_info
)
1408 ''' # COMMENTED OUT FOR 2.42 RELEASE!! CRASHES 3DS MAX
1409 # Add main keyframe data chunk to primary chunk:
1410 primary.add_subchunk(kfdata)
1413 # At this point, the chunk hierarchy is completely built.
1417 # Open the file for writing:
1418 file = open(filepath
, 'wb')
1420 # Recursively write the chunks to file:
1426 # Clear name mapping vars, could make locals too
1428 name_mapping
.clear()
1430 # Debugging only: report the exporting time:
1431 # Blender.Window.WaitCursor(0)
1432 print("3ds export time: %.2f" % (time
.time() - duration
))
1434 # Debugging only: dump the chunk hierarchy: