[Solved] Exporting skinned mesh in posed position

I’m having trouble getting my GLTFExporter to work. No exceptions are thrown while exporting, but when I try to open the file, I am getting different errors based on what I try to open it in.

In 3dbuilder I get “glTF parsing error: Unsupported primitive mode: LINES”

At https://gltf-viewer.donmccurdy.com/, I get Cannot set property ‘isBone’ of undefined.

In blender, I get "AttributeError: ‘Skin’ object has no attribute ‘root’

Any idea how to troubleshoot this?

Below is my simple export function :

exportModel = () => {
    var DEFAULT_OPTIONS = {
		binary: true,
		trs: false,
		onlyVisible: true,
		truncateDrawRange: false,
		embedImages: false,
		animations: [],
		forceIndices: true,
		forcePowerOfTwoTextures: false
    };

    this.exporter.parse( this.objectHolder.children, (object)=> {
        return new Promise( ( resolve, reject ) => {
            firebase.storage().ref( '/OpenOrders/newOrder/model.glb' ).put(object).then(() => {
                console.log("upload complete");
                resolve();
            }).catch( error => {
                reject(error);
            });
        });
    }, DEFAULT_OPTIONS);
}

Are you able to share the model? The error from 3D Builder sounds like a feature limitation on its side, probably supports only triangle meshes and not lines. Assuming it’s intentional that this model would have lines in it? The others I don’t know about, but https://github.com/AnalyticalGraphicsInc/gltf-vscode and http://github.khronos.org/glTF-Validator/ are often helpful.

1 Like

I have bounding boxes on all my objects. Any chance that is what is causing the problem?

I used the validator and it says its valid

Bounding boxes?

I put a box around each item and then calculate the aabb based on the animated vertexes so I can use them for raycasting for item selection by clicking

I removed the bounding boxes but now I’m getting a different error:

Runtime Error: Node type ShaderNodeBsdfPrincipled undefined

I guess I need to know more of what’s nested underneath the THREE.Scene or THREE.Object3D that you’re exporting, if you can’t share the file or demo itself. What types of three.js objects and materials are you using? Are there any *Helper.js objects in the scene?

Note that you can use glTF-Pipeline to convert the binary glb to a human-readable JSON file if that’s easier to debug.

Don,

Problem was a stupid mistake on my end. I was forgetting to include the armature with the file on export. Now objects are exporting just fine, except the skinning data isn’t being retained (i.e. everything exports in the rest position)

How do I maintain the objects in their animated position on export? Is there any easy way to do this or will I have to manually change the geometry of each object?

I don’t know the answer to that. I think you’ll need to create a demo, debug, and perhaps add an export option.

Solved. This function returns a clone of an object that is not in the rest position. Borrowed majority of the code from Mugen87’s AABB function. Also thanks to don for all your patience and help :smiley:

createPosedClone = (skinnedMesh) => {
        
    let clone = skinnedMesh.clone();
    var boneMatrices = this.skeleton.boneMatrices;
    var geometry = skinnedMesh.geometry;
    
    var position = geometry.attributes.position;
    var skinIndex = geometry.attributes.skinIndex;
    var skinWeigth = geometry.attributes.skinWeight;
    
    var bindMatrix = skinnedMesh.bindMatrix;
    var bindMatrixInverse = skinnedMesh.bindMatrixInverse;
    
    var i, j, si, sw;


    var vertex = new THREE.Vector3();
    var temp = new THREE.Vector3();
    var skinned = new THREE.Vector3();
    var skinIndices = new THREE.Vector4();
    var skinWeights = new THREE.Vector4();
    var boneMatrix = new THREE.Matrix4();
    // non-indexed geometry

    for ( i = 0; i < position.count; i++ ) {
    
        vertex.fromBufferAttribute( position, i );
        skinIndices.fromBufferAttribute( skinIndex, i );
        skinWeights.fromBufferAttribute( skinWeigth, i );
        
        // the following code section is normally implemented in the vertex shader

        vertex.applyMatrix4( bindMatrix ); // transform to bind space
        skinned.set( 0, 0, 0 );

        for ( j = 0; j < 4; j ++ ) {

            si = skinIndices.getComponent( j );
            sw = skinWeights.getComponent( j );
            boneMatrix.fromArray( boneMatrices, si * 16 );

            // weighted vertex transformation

            temp.copy( vertex ).applyMatrix4( boneMatrix ).multiplyScalar( sw );
            skinned.add( temp );

        }

        skinned.applyMatrix4( bindMatrixInverse ); // back to local space

        // change the position of the object

        clone.geometry.attributes.position.setXYZ(i, skinned.x, skinned.y, skinned.z);  
    }
    return clone;
}
2 Likes

Updated export function for applying both skinning and morph targets.

createPosedClone = (skinnedMesh) => {
        
    let clone = skinnedMesh.clone();
    var boneMatrices = this.skeleton.boneMatrices;
    var geometry = skinnedMesh.geometry;
    
    var position = geometry.attributes.position;
    var skinIndex = geometry.attributes.skinIndex;
    var skinWeigth = geometry.attributes.skinWeight;
    
    var bindMatrix = skinnedMesh.bindMatrix;
    var bindMatrixInverse = skinnedMesh.bindMatrixInverse;
    
    var i, j, si, sw;


    var vertex = new THREE.Vector3();
    var temp = new THREE.Vector3();
    var skinned = new THREE.Vector3();
    var skinIndices = new THREE.Vector4();
    var skinWeights = new THREE.Vector4();
    var boneMatrix = new THREE.Matrix4();
    // non-indexed geometry

    for ( i = 0; i < position.count; i++ ) {
    
        vertex.fromBufferAttribute( position, i );
        skinIndices.fromBufferAttribute( skinIndex, i );
        skinWeights.fromBufferAttribute( skinWeigth, i );
        
        // the following code section is normally implemented in the vertex shader

        vertex.applyMatrix4( bindMatrix ); // transform to bind space
        skinned.set( 0, 0, 0 );

        for ( j = 0; j < 4; j ++ ) {

            si = skinIndices.getComponent( j );
            sw = skinWeights.getComponent( j );
            boneMatrix.fromArray( boneMatrices, si * 16 );

            // weighted vertex transformation

            temp.copy( vertex ).applyMatrix4( boneMatrix ).multiplyScalar( sw );
            skinned.add( temp );

        }

        skinned.applyMatrix4( bindMatrixInverse ); // back to local space

        // change the position of the object

        var morphTargetArray = clone.geometry.morphAttributes.position;
        var morphInfluences = clone.morphTargetInfluences;
        var meshVertices = clone.geometry.attributes.position;

        var vA = new THREE.Vector3();
        var tempA = new THREE.Vector3();
        var target = new THREE.Vector3();
        var vertices = new THREE.Vector3();

        vertices.fromBufferAttribute(meshVertices, i); // the vertex to transform
    
        for ( var t = 0; t < morphTargetArray.length; t ++ ) {
    
            var influence = morphInfluences[ t ];
    
            if ( influence === 0 ) continue;
    
            target.fromBufferAttribute( morphTargetArray[t], i);
    
            vA.addScaledVector( tempA.subVectors( target, vertices ), influence );
    
        }
    
        skinned.add( vA ); // the transformed value

        clone.geometry.attributes.position.setXYZ(i, skinned.x, skinned.y, skinned.z);  
    }

    return clone;
}
1 Like