1
$\begingroup$

I'm researching the Blender API while working on a custom exporter. The features I want to implement are similar to the IQM exporter. I'm trying to get a better understanding of some of the API calls used in the IQM exporter.

1. Calculating Tangents

IQM calculates the tangents using the code below. Why not use Mesh.calc_tangents() instead?

def calcTangents(self):
    # See "Tangent Space Calculation" at http://www.terathon.com/code/tangent.html
    for v in self.verts:
        v.tangent = mathutils.Vector((0.0, 0.0, 0.0))
        v.bitangent = mathutils.Vector((0.0, 0.0, 0.0))
    for (v0, v1, v2) in self.tris:
        dco1 = v1.coord - v0.coord
        dco2 = v2.coord - v0.coord
        duv1 = v1.uv - v0.uv
        duv2 = v2.uv - v0.uv
        tangent = dco2*duv1.y - dco1*duv2.y
        bitangent = dco2*duv1.x - dco1*duv2.x
        if dco2.cross(dco1).dot(bitangent.cross(tangent)) < 0:
            tangent.negate()
            bitangent.negate()
        v0.tangent += tangent
        v1.tangent += tangent
        v2.tangent += tangent
        v0.bitangent += bitangent
        v1.bitangent += bitangent
        v2.bitangent += bitangent
    for v in self.verts:    
        v.tangent = v.tangent - v.normal*v.tangent.dot(v.normal)
        v.tangent.normalize()
        if v.normal.cross(v.tangent).dot(v.bitangent) < 0:
            v.bitangent = -1.0
        else:
            v.bitangent = 1.0
   # ...

2. Converting the selected object(s) to mesh.

The collectMeshes() function loops over the selected objects and when the selected object is not a obj.type == 'MESH' is skips the object. Next it converts the object into a mesh. Why would you convert an obj with the MESH type using obj.to_mesh()? Isn't an object of type MESH already an MESH ?

# ...
def collectMeshes(context, bones, scale, matfun, useskel = True, usecol = False, filetype = 'IQM'):
    vertwarn = []
    objs = context.selected_objects #context.scene.objects
    meshes = []
    for obj in objs:
        if obj.type == 'MESH':
            data = obj.to_mesh(context.scene, False, 'PREVIEW')
            if not data.polygons:
                continue
# ....

3. calc_split_normals()

Still in the collectMeshes() function, the script calls obj.calc_normals_split. The documentation says Calculate split vertex normals, which preserve sharp edges. The description is pretty clear, but I'm curious why this function is used in the first place; could this have a deeper reason? Why not use the default normals instead? The call is made here in the exporter.

# ...
if not data.polygons:
    continue
data.calc_normals_split()
coordmatrix = obj.matrix_world
normalmatrix = coordmatrix.inverted().transposed()
# ...

4. what's the purpose of this line?

Continuing in collectMeshes I see the if() as shown below:

# ...
if all([ data.vertices[i].co == data.vertices[face.vertices[0]].co for i in face.vertices[1:] ]):
    continue
# ...

What is the purpose of this line. Am I correct to say that it checks if the vertices of the face are already stored in data.vertices? And if so, it skips this vertex?

5. Getting the vertices and triangles

A big chunk of the collectMeshes() function deals with gathering the vertices and indices. It seems that the IQM exporter creates a new Mesh instance per material; it does this by creating a key from obj.name, matindex and a matfun(). I'm not 100% of my understanding of the code is correct tbh.

Once it has created a new Mesh instance or retrieved an existing one, it loops over face.loop_indices. The documentation says the following about a loop: reference a single vertex and edge. Therefore iterating over the face.loop_indices and then retrieving the specific loop can be used to retrieve the different vertex coordinate, uv, normal, etc. Is this a correct understanding of how one would retrieve vertex data?

The IQM exporter iterates over data.polygons like this:

for face in data.polygons:
    if len(face.vertices) < 3:
        continue

    if all([ data.vertices[i].co == data.vertices[face.vertices[0]].co for i in face.vertices[1:] ]):
        print("Already exists", face.vertices[0].co)
        continue

    uvface = uvfaces and uvfaces[face.index]
    material = os.path.basename(uvface.image.filepath) if uvface and uvface.image else ''
    matindex = face.material_index

    try:
        mesh = materials[obj.name, matindex, material] 
    except:
        try:
            matprefix = (data.materials and data.materials[matindex].name) or ''
        except:
            matprefix = ''

        mesh = Mesh(obj.name, matfun(matprefix, material), data.vertices)
        meshes.append(mesh)
        materials[obj.name, matindex, material] = mesh

    verts = mesh.verts
    vertmap = mesh.vertmap
    faceverts = []
    for loopidx in face.loop_indices:
        loop = data.loops[loopidx]
        v = data.vertices[loop.vertex_index]
        vertco = coordmatrix * v.co

6. Storing the triangle indices

The last thing that collectMeshes() does is storing the triangles. The length of faceverts is 4 in most cases. It's looping over the vertices of the current face, starting at 2 and basically selecting only index 0, 1, 2. Why is index 3 ignored? (it's clear that they change winding order too). See the specific line of the IQM exporter.

for i in range(2, len(faceverts)):
    mesh.tris.append((faceverts[0], faceverts[i], faceverts[i-1]))
$\endgroup$

closed as too broad by Ray Mairlot, Duarte Farrajota Ramos, p2or, X-27 the fluffy unicorn, dr. Sybren Nov 27 '18 at 14:13

Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 1
    $\begingroup$ Hi. Please only ask one question at a time. You are welcome to ask multiple questions but as separate posts. $\endgroup$ – Ray Mairlot Nov 17 '18 at 13:44

Browse other questions tagged or ask your own question.