Having problems with STLExport on heroforge.com

I am trying to use three.js to export 3D models from https://heroforge.com.

I initially used Three.js STLExport example directly on the CK.character object on the website. This works but only exports a model that does not include the vertex bone transformations, i.e. the model is exported in a T-pose.

After looking around, I found this pull-request, which helped me quite a bit. I managed to get it working after poking around on Hero Forge and figuring out that I need to distinguish Skinned Meshes on object.type and that skin indicies and weights exists on object.geometry.attributes.skinIndex0 object.geometry.attributes.skinWeight0 respectively. I now get the model exported with the transformations!.. Mostly.

As you can see here it looks like the model is exported with the meshes in the right locations (except for the eyes) but something obviously goes wrong with the meshes themselves. I am guessing that I am converting or doing something wrong with the meshes them themselves, but honestly, I only started learning about 3D modelling last week and I have no clue what I am doing wrong.

Maybe someone here with more experience than me, can see what I am doing wrong?

I have included the code I use underneath (it is just this pull-request with unused code removed and very minor tweaks). I just copy the output directly into an STL-file and open it.

var STLExporter = function () { };

STLExporter.prototype = {
    constructor: STLExporter,

    parse: (function () {
        var vector = new Vector3();
        var normalMatrixWorld = new Matrix3();

        return function parse(scene) {
            var objects = [];
            var triangles = 0;

            scene.traverse(function (object) {
                if (object.isMesh) {
                    var geometry = object.geometry;

                    if (geometry.isBufferGeometry) {
                        geometry = new Geometry().fromBufferGeometry(geometry);
                    }
                    if (geometry.isGeometry) {
                        triangles += geometry.faces.length;

                        objects.push({
                            obj: object,
                            geometry: geometry,
                            matrixWorld: object.matrixWorld
                        });
                    }
                }
            });
            var output = '';
            output += 'solid exported\n';

            for (var i = 0, il = objects.length; i < il; i++) {
                var object = objects[i];
                var vertices = object.geometry.vertices;
                var faces = object.geometry.faces;
                var matrixWorld = object.matrixWorld;
                normalMatrixWorld.getNormalMatrix(matrixWorld);

                for (var j = 0, jl = faces.length; j < jl; j++) {
                    var face = faces[j];
                    vector.copy(face.normal).applyMatrix3(normalMatrixWorld).normalize();

                    output += '\tfacet normal ' + vector.x + ' ' + vector.y + ' ' + vector.z + '\n';
                    output += '\t\touter loop\n';

                    var indices = [face.a, face.b, face.c];

                    for (var k = 0; k < 3; k++) {
                        vector.copy(vertices[indices[k]]).applyMatrix4(matrixWorld);

                        if (object.obj.type == "SkinnedMesh") {
                            vector = this.boneTransform(object.obj, vertices[indices[k]], indices[k]);
                        }
                        output += '\t\t\tvertex ' + vector.x + ' ' + vector.y + ' ' + vector.z + '\n';
                    }
                    output += '\t\tendloop\n';
                    output += '\tendfacet\n';
                }
            }
            output += 'endsolid exported\n';

            return output;
        };
    }()),
    boneTransform: (function () {
        var clone = new Vector3(), result = new Vector3(), skinIndices = new Vector4(), skinWeights = new Vector4();
        var temp = new Vector3(), tempMatrix = new Matrix4(), properties = ['x', 'y', 'z', 'w'];

        return function (object, vertex, index) {
            if (object.geometry.isBufferGeometry) {
                var index4 = index * 4;
                skinIndices.fromArray(object.geometry.attributes.skinIndex0.array, index4);
                skinWeights.fromArray(object.geometry.attributes.skinWeight0.array, index4);
            } else if (object.geometry.isGeometry) {
                skinIndices.copy(object.geometry.skinIndices0[index]);
                skinWeights.copy(object.geometry.skinWeights0[index]);
            }
            var clone = vertex.clone().applyMatrix4(object.bindMatrix); result.set(0, 0, 0);

            for (var i = 0; i < 4; i++) {
                var skinWeight = skinWeights[properties[i]];

                if (skinWeight != 0) {
                    var boneIndex = skinIndices[properties[i]];

                    tempMatrix.multiplyMatrices(object.skeleton.bones[boneIndex].matrixWorld, object.skeleton.boneInverses[boneIndex]);

                    result.add(temp.copy(clone).applyMatrix4(tempMatrix).multiplyScalar(skinWeight));
                }
            }
            return clone.copy(result.applyMatrix4(object.bindMatrixInverse));
        };
    })(),
};
var exporter = new STLExporter();
exporter.parse(CK.character);

Would it be an alternative to export to glTF instead? GLTFExporter should be able to export skinned meshes and the respective animations.

Oh, i did not know that (all these formats are a bit confusing for me, I must admit). I’ll try that now, thank you!

Did you manage to do it? I also have this problem but waaaay more messy: Schermata 2020-10-11 alle 08.53.45
If I remove the bones code, I get the correct T model

I’ve updated STLExporter so instances of SkinnedMesh are now exported correctly, too. The improved exporter is available with r122.

If you want to use the code earlier, check out the respective PR: https://github.com/mrdoob/three.js/pull/20494

Uhm, unfortunately I have the error “Uncaught TypeError: object.boneTransform is not a function”.
I guess the website uses an old version of three.js?

Yes. It’s actually recommended that example files and the core library are always from the same release. In this particular case r116 is required since that’s the release where SkinnedMesh.boneTransform() was added. However, you can try to monkey-patch the method by yourself.

Understood, I’ll try to patch it, thanks for the info!

@Mugen87 I’ve patched the method and unfortunately I have the same result.
If I comment this part:

if ( object.isSkinnedMesh === true ) {
object.boneTransform( a, vA );
object.boneTransform( b, vB );
object.boneTransform( c, vC );
}

I can correctly export a T model, but with that code the result is this:
Schermata 2020-10-12 alle 18.33.24

Do you have any idea what can be the cause?

Oh, also I had to change this:

skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );

to this:

skinIndex.fromBufferAttribute( geometry.attributes.skinIndex0, index );
skinWeight.fromBufferAttribute( geometry.attributes.skinWeight0, index );

Because when logging the object, that was the name of the attribute

Why can’t you just use the current dev version of three.js?

Sorry, but I don’t understand yet how you are implementing your code.

I can’t upgrade the three.js version on the website, I don’t own it. I’m just injecting the exporter that you linked and getting this result, there’s no code written by me except for the skinIndex -> skinIndex0 edit

Depending on what three.js release the website is using, the root cause could be anything. Sorry, I’m not able to dive into this issue more closely. Maybe you can ask the owners of heroforge to upgrade their three.js version to at least r116.

No problem, maybe it was a frequent error and you knew it, thanks for the info!