[Bf-extensions-cvs] SVN commit: /data/svn/bf-extensions [3293] trunk/py/scripts/addons/ io_scene_3ds: patch: [#30986] 3DS importer/ exporter material improvements and bug fixes.

Campbell Barton ideasman42 at gmail.com
Sun Apr 22 01:24:10 CEST 2012


Revision: 3293
          http://projects.blender.org/scm/viewvc.php?view=rev&root=bf-extensions&revision=3293
Author:   campbellbarton
Date:     2012-04-21 23:24:09 +0000 (Sat, 21 Apr 2012)
Log Message:
-----------
patch: [#30986] 3DS importer/exporter material improvements and bug fixes.
from Alexander Gessler (aramis_acg)

This patch extends the 3DS importer and exporter to have better material support and unifies their respective feature sets.

This includes:
- UV scale,offset
- tiling/wrap modes
- improved conversion of mapping targets
- better warnings

Additionally, the exporter now handles specular, opacity and normal maps correctly (the importer did this already). (I think the way how textures were written previously was non 3DS-compliant).

I tested this against assimp's 3DS test suite [1] as well as my personal 3DS test sets. Exported 3DS files were successfully read by FBXConv and assimp.

[1] http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/test/

Modified Paths:
--------------
    trunk/py/scripts/addons/io_scene_3ds/export_3ds.py
    trunk/py/scripts/addons/io_scene_3ds/import_3ds.py

Modified: trunk/py/scripts/addons/io_scene_3ds/export_3ds.py
===================================================================
--- trunk/py/scripts/addons/io_scene_3ds/export_3ds.py	2012-04-20 23:47:39 UTC (rev 3292)
+++ trunk/py/scripts/addons/io_scene_3ds/export_3ds.py	2012-04-21 23:24:09 UTC (rev 3293)
@@ -49,9 +49,22 @@
 MATDIFFUSE = 0xA020  # This holds the color of the object/material
 MATSPECULAR = 0xA030  # SPecular color of the object/material
 MATSHINESS = 0xA040  # ??
-MATMAP = 0xA200  # This is a header for a new material
-MATMAPFILE = 0xA300  # This holds the file name of the texture
 
+MAT_DIFFUSEMAP = 0xA200  # This is a header for a new diffuse texture
+MAT_OPACMAP = 0xA210  # head for opacity map
+MAT_BUMPMAP = 0xA230  # read for normal map
+MAT_SPECMAP = 0xA204  # read for specularity map
+
+#>------ sub defines of MAT_???MAP
+MATMAPFILE = 0xA300  # This holds the file name of a texture
+
+MAT_MAP_TILING = 0xa351   # 2nd bit (from LSB) is mirror UV flag
+MAT_MAP_USCALE = 0xA354   # U axis scaling
+MAT_MAP_VSCALE = 0xA356   # V axis scaling
+MAT_MAP_UOFFSET = 0xA358  # U axis offset
+MAT_MAP_VOFFSET = 0xA35A  # V axis offset
+MAT_MAP_ANG = 0xA35C      # UV rotation around the z-axis in rad
+
 RGB1 = 0x0011
 RGB2 = 0x0012
 
@@ -423,10 +436,10 @@
 # EXPORT
 ######################################################
 
-def get_material_images(material):
+def get_material_image_texslots(material):
     # blender utility func.
     if material:
-        return [s.texture.image for s in material.texture_slots if s and s.texture.type == 'IMAGE' and s.texture.image]
+        return [s for s in material.texture_slots if s and s.texture.type == 'IMAGE' and s.texture.image]
 
     return []
 # 	images = []
@@ -454,24 +467,78 @@
     return mat_sub
 
 
-def make_material_texture_chunk(chunk_id, images):
-    """Make Material Map texture chunk
+def make_material_texture_chunk(chunk_id, texslots, tess_uv_image=None):
+    """Make Material Map texture chunk given a seq. of `MaterialTextureSlot`'s
+
+        `tess_uv_image` is optionally used as image source if the slots are
+        empty. No additional filtering for mapping modes is done, all
+        slots are written "as is".
     """
+
     mat_sub = _3ds_chunk(chunk_id)
+    has_entry = False
 
-    def add_image(image):
-        import bpy
+    import bpy
+
+    def add_texslot(texslot):
+        texture = texslot.texture
+        image = texture.image
+
         filename = bpy.path.basename(image.filepath)
         mat_sub_file = _3ds_chunk(MATMAPFILE)
         mat_sub_file.add_variable("mapfile", _3ds_string(sane_name(filename)))
         mat_sub.add_subchunk(mat_sub_file)
 
-    for image in images:
-        add_image(image)
+        maptile = 0
 
-    return mat_sub
+        # no perfect mapping for mirror modes - 3DS only has uniform mirror w. repeat=2
+        if texture.extension == 'REPEAT' and (texture.use_mirror_x and texture.repeat_x > 1) \
+           or (texture.use_mirror_y and texture.repeat_y > 1):
+            maptile |= 0x2
+        # CLIP maps to 3DS' decal flag
+        elif texture.extension == 'CLIP':
+            maptile |= 0x10
 
+        mat_sub_tile = _3ds_chunk(MAT_MAP_TILING)
+        mat_sub_tile.add_variable("maptiling", _3ds_ushort(maptile))
+        mat_sub.add_subchunk(mat_sub_tile)
 
+        mat_sub_uscale = _3ds_chunk(MAT_MAP_USCALE)
+        mat_sub_uscale.add_variable("mapuscale", _3ds_float(texslot.scale[0]))
+        mat_sub.add_subchunk(mat_sub_uscale)
+
+        mat_sub_vscale = _3ds_chunk(MAT_MAP_VSCALE)
+        mat_sub_vscale.add_variable("mapuscale", _3ds_float(texslot.scale[1]))
+        mat_sub.add_subchunk(mat_sub_vscale)
+
+        mat_sub_uoffset = _3ds_chunk(MAT_MAP_UOFFSET)
+        mat_sub_uoffset.add_variable("mapuoffset", _3ds_float(texslot.offset[0]))
+        mat_sub.add_subchunk(mat_sub_uoffset)
+
+        mat_sub_voffset = _3ds_chunk(MAT_MAP_VOFFSET)
+        mat_sub_voffset.add_variable("mapvoffset", _3ds_float(texslot.offset[1]))
+        mat_sub.add_subchunk(mat_sub_voffset)
+
+    # store all textures for this mapto in order. This at least is what
+    # the 3DS exporter did so far, afaik most readers will just skip
+    # over 2nd textures.
+    for slot in texslots:
+        add_texslot(slot)
+        has_entry = True
+
+    # image from tess. UV face - basically the code above should handle
+    # this already. No idea why its here so keep it :-)
+    if tess_uv_image and not has_entry:
+        has_entry = True
+
+        filename = bpy.path.basename(tess_uv_image.filepath)
+        mat_sub_file = _3ds_chunk(MATMAPFILE)
+        mat_sub_file.add_variable("mapfile", _3ds_string(sane_name(filename)))
+        mat_sub.add_subchunk(mat_sub_file)
+
+    return mat_sub if has_entry else None
+
+
 def make_material_chunk(material, image):
     '''Make a material chunk out of a blender material.'''
     material_chunk = _3ds_chunk(MATERIAL)
@@ -495,13 +562,41 @@
         material_chunk.add_subchunk(make_material_subchunk(MATDIFFUSE, material.diffuse_color[:]))
         material_chunk.add_subchunk(make_material_subchunk(MATSPECULAR, material.specular_color[:]))
 
-        images = get_material_images(material)  # can be None
-        if image:
-            images.append(image)
+        slots = get_material_image_texslots(material)  # can be None
 
-        if images:
-            material_chunk.add_subchunk(make_material_texture_chunk(MATMAP, images))
+        if slots:
 
+            spec = [s for s in slots if s.use_map_specular or s.use_map_color_spec]
+            matmap = make_material_texture_chunk(MAT_SPECMAP, spec)
+            if matmap:
+                material_chunk.add_subchunk(matmap)
+
+            alpha = [s for s in slots if s.use_map_alpha]
+            matmap = make_material_texture_chunk(MAT_OPACMAP, alpha)
+            if matmap:
+                material_chunk.add_subchunk(matmap)
+
+            normal = [s for s in slots if s.use_map_normal]
+            matmap = make_material_texture_chunk(MAT_BUMPMAP, normal)
+            if matmap:
+                material_chunk.add_subchunk(matmap)
+
+            # make sure no textures are lost. Everything that doesn't fit
+            # into a channel is exported as diffuse texture with a
+            # warning.
+            diffuse = []
+            for s in slots:
+                if s.use_map_color_diffuse:
+                    diffuse.append(s)
+                elif not (s in normal or s in alpha or s in spec):
+                    print('\nwarning: failed to map texture to 3DS map channel, assuming diffuse')
+                    diffuse.append(s)
+
+            if diffuse:
+                matmap = make_material_texture_chunk(MAT_DIFFUSEMAP, diffuse, image)
+                if matmap:
+                    material_chunk.add_subchunk(matmap)
+
     return material_chunk
 
 

Modified: trunk/py/scripts/addons/io_scene_3ds/import_3ds.py
===================================================================
--- trunk/py/scripts/addons/io_scene_3ds/import_3ds.py	2012-04-20 23:47:39 UTC (rev 3292)
+++ trunk/py/scripts/addons/io_scene_3ds/import_3ds.py	2012-04-21 23:24:09 UTC (rev 3293)
@@ -66,6 +66,13 @@
 MAT_BUMP_MAP = 0xA230  # This is a header for a new bump map
 MAT_MAP_FILEPATH = 0xA300  # This holds the file name of the texture
 
+MAT_MAP_TILING = 0xa351   # 2nd bit (from LSB) is mirror UV flag
+MAT_MAP_USCALE = 0xA354   # U axis scaling
+MAT_MAP_VSCALE = 0xA356   # V axis scaling
+MAT_MAP_UOFFSET = 0xA358  # U axis offset
+MAT_MAP_VOFFSET = 0xA35A  # V axis offset
+MAT_MAP_ANG = 0xA35C      # UV rotation around the z-axis in rad
+
 MAT_FLOAT_COLOR = 0x0010  # color defined as 3 floats
 MAT_24BIT_COLOR = 0x0011  # color defined as 3 bytes
 
@@ -211,7 +218,7 @@
     skip_chunk.bytes_read += buffer_size
 
 
-def add_texture_to_material(image, texture, material, mapto):
+def add_texture_to_material(image, texture, scale, offset, extension, material, mapto):
     #print('assigning %s to %s' % (texture, material))
 
     if mapto not in {'COLOR', 'SPECULARITY', 'ALPHA', 'NORMAL'}:
@@ -226,6 +233,18 @@
     mtex.texture_coords = 'UV'
     mtex.use_map_color_diffuse = False
 
+    mtex.scale = (scale[0], scale[1], 1.0)
+    mtex.offset = (offset[0], offset[1], 0.0)
+
+    texture.extension = 'REPEAT'
+    if extension == 'mirror':
+        # 3DS mirror flag can be emulated by these settings (at least so it seems)
+        texture.repeat_x = texture.repeat_y = 2
+        texture.use_mirror_x = texture.use_mirror_y = True
+    elif extension == 'decal':
+        # 3DS' decal mode maps best to Blenders CLIP
+        texture.extension = 'CLIP'
+
     if mapto == 'COLOR':
         mtex.use_map_color_diffuse = True
     elif mapto == 'SPECULARITY':
@@ -255,6 +274,7 @@
 # 	TEXMODE = Mesh.FaceModes['TEX']
 
     # Localspace variable names, faster.
+    STRUCT_SIZE_FLOAT = struct.calcsize('f')
     STRUCT_SIZE_2FLOAT = struct.calcsize('2f')
     STRUCT_SIZE_3FLOAT = struct.calcsize('3f')
     STRUCT_SIZE_4FLOAT = struct.calcsize('4f')
@@ -330,7 +350,7 @@
 
                     uvl[pl.loop_start].uv = contextMeshUV[v1 * 2: (v1 * 2) + 2]
                     uvl[pl.loop_start + 1].uv = contextMeshUV[v2 * 2: (v2 * 2) + 2]
-                    uvl[pl.loop_start + 2].uv = contextMeshUV[v3 * 2: ( v3 * 2) + 2]
+                    uvl[pl.loop_start + 2].uv = contextMeshUV[v3 * 2: (v3 * 2) + 2]
                     # always a tri
 
         bmesh.validate()
@@ -352,10 +372,20 @@
     CreateBlenderObject = False
 
     def read_float_color(temp_chunk):
-        temp_data = file.read(struct.calcsize('3f'))
-        temp_chunk.bytes_read += 12
+        temp_data = file.read(STRUCT_SIZE_3FLOAT)
+        temp_chunk.bytes_read += STRUCT_SIZE_3FLOAT
         return [float(col) for col in struct.unpack('<3f', temp_data)]
 
+    def read_float(temp_chunk):
+        temp_data = file.read(STRUCT_SIZE_FLOAT)
+        temp_chunk.bytes_read += STRUCT_SIZE_FLOAT
+        return struct.unpack('<f', temp_data)[0]
+
+    def read_short(temp_chunk):
+        temp_data = file.read(STRUCT_SIZE_UNSIGNED_SHORT)
+        temp_chunk.bytes_read += STRUCT_SIZE_UNSIGNED_SHORT
+        return struct.unpack('<H', temp_data)[0]
+
     def read_byte_color(temp_chunk):
         temp_data = file.read(struct.calcsize('3B'))
         temp_chunk.bytes_read += 3
@@ -364,24 +394,46 @@
     def read_texture(new_chunk, temp_chunk, name, mapto):

@@ Diff output truncated at 10240 characters. @@


More information about the Bf-extensions-cvs mailing list