Example for a Post-Processing script for 3D-Import (Collada)

wombatstampedewombatstampede Posts: 30Member

The intent of this post is to show how helpful the post-processing function of the 3D import can be.

Here's the basic reference:
http://docs.godotengine.org/en/stable/learning/features/3d/importing_3d_scenes.html

What you can't to with post-processing:
- You can't change much on the update-policy. Godot decides what objects are overwritten/recreated when re-importing updated models.
- You can't access any objects/data which the Godot import didn't import.

What you can do:
- You can add/remove and update any object of the imported 3d scene after the import was completed.
- You can use any other GDScript functions (write to files etc.)
- You can decide whether the import was successful or not.

This example deals with auto-creating collision box shapes for walls an other boxes.
It can auto-create shapes when the surface was assigned a specific material which name contains "-collide".
It can auto-create boxshapes inside other physics objects (-colonly and -rigid) when the box contains the name "-colbox".
It can initialize physics objects with specific values for i.e. layer & mask when the scene contains an object with "-preset" in its name.

This script is a kind of specialized. But perhaps it serves as an "inspiration" for you to create your own post-processing scripts.

Here's an example 3d-scene in Blender:
Note the material name "LockWall-collide" or the "#preset..." and "-rigid", "-colbox" objects in the object tree.

Here's the result after import + post-process in Godot:

The script is in the next post. (Post limit)


Tags :

Comments

  • wombatstampedewombatstampede Posts: 30Member

    Here's the script:

    tool
    extends EditorScenePostImport
    
    var mdt
    var preset={}
    var presetMatch
    
    func post_import(_scene):
        mdt = MeshDataTool.new()
        presetMatch = RegEx.new()
        presetMatch.compile("-(\\w+)=(-?[0-9.]+)")
    
        getCollisionNode(_scene,_scene)
    
        print ("*** done ***")
        return _scene
    
    func getCollisionNode(node,scene):
        var delList = {}
        for N in node.get_children():
            if N.get_child_count() > 0:
                #print("["+N.get_name()+"]")
                getCollisionNode(N,scene)
            if N.is_type("CollisionObject"):
                setBodyPresets(N)
            #print(" "+N.get_type())
    
            # build collision shapes "box" for "walls" when material contains "-collide" flag in name
            # * mesh has to consist of tris (check on blender collada export)
            # * "wall" surface "quad" consists of 2 tris
            # * "bottom" tri is required to have 2pts of equal height (y-value)
    
            if (N.get_type()=="MeshInstance"): 
                var has_collide = false
                var m = N.get_mesh()
                for s in range(0,m.get_surface_count()):
                    if (m.surface_get_name(s).find("-collide")>=0):
                        if m.surface_get_primitive_type(s) == Mesh.PRIMITIVE_TRIANGLES:
                            has_collide=true
                        else:
                            print("Error: wrong surface type "+String(m.surface_get_primitive_type(s))+
                                    " for "+m.surface_get_name(s)+". Required: PRIMITIVE_TRIANGLES")
    
                if has_collide: #collide material found
                    var cscount=0
    
                    delList[N.get_name()+"_collision"]="StaticBody"
                    var sb=StaticBody.new()
                    sb.set_name(N.get_name()+"_collision")
                    N.add_child(sb)
                    sb.set_owner(scene)
                    setBodyPresets(sb)
    
                    #PRIMITIVE_TRIANGLES = 4
    
                    for s in range(0,m.get_surface_count()):
                        if (m.surface_get_name(s).find("-collide")>=0): 
                            var newname=m.surface_get_name(s).replace("-collide","")
    
                            mdt.create_from_surface(m, s)
                            for f in range(mdt.get_face_count()):
                                var v=[]
                                var cwid = 0.5 #width of collision shape (away from normal)
                                for vc in range(3):
                                    v.append(mdt.get_vertex(mdt.get_face_vertex(f,vc)))
                                var minY=min(v[0].y,min(v[1].y,v[2].y))
                                var maxY=max(v[0].y,max(v[1].y,v[2].y))
                                var vxz=[]
                                for vc in range(3): #make array of 2 verts at minY
                                    if abs(v[vc].y-minY)<0.01:
                                        vxz.append(v[vc])
                                if vxz.size()==2: #not "upper rectangle" (which has only 1 vert at minY)
                                    var norm=mdt.get_face_normal(f)
                                    #y-values are the same so this is length in xz plane
                                    var xzVec=vxz[1]-vxz[0]
                                    var lenxz=xzVec.length()
                                    var midxzOffs=xzVec/2
                                    var cshape = CollisionShape.new()
                                    var bshape = BoxShape.new()
                                    #node.add_shape(bshape,N.get_transform())
                                    cscount=cscount+1
                                    cshape.set_name(newname+"_"+String(cscount))
                                    cshape.set_shape(bshape)
                                    sb.add_child(cshape)
                                    #you need to set_owner to scene root to make a node visible in editor
                                    cshape.set_owner(scene) 
                                    bshape.set_extents(Vector3(cwid/2,(maxY-minY)/2,lenxz/2))
                                    #center of new shape
                                    cshape.set_translation(Vector3(vxz[0].x+midxzOffs.x-(norm.x*(cwid/2)),
                                            minY+((maxY-minY)/2),vxz[0].z+midxzOffs.z-(norm.z*(cwid/2))))
    
                                    cshape.rotate_y(Vector2(xzVec.x,xzVec.z).angle_to(Vector2(0,-1)))
    
                            mdt.clear()
    
            # Create collision shape for a box-object inside a -colonly (StaticBody) or -rigid (RigidBody) parent
            # * object name contains "-colbox"
            # * client object rotation is not transferred (if any)
            # * shape has extents and position of box
            # * removes automatically created shapes ("shape","SphereShape","BoxShape")
            if (N.get_type()=="MeshInstance") and (N.get_name().find("-colbox")>=0): 
                print(node.get_name()+" ->"+N.get_name())
                var newname=N.get_name().replace("-colbox","")
    
                if node.is_type("CollisionObject"): #parent can deal with collision shapes
                    print(" is collision")
    
                    var aab=N.get_aabb()
                    var cshape=null
                    var bshape=null
                    #reuse/update shape if already exists, does this ever happen?
                    if node.has_node(newname) and (N.get_type()=="CollisionShape"):
                        cshape = node.get_child(newname)
                        bshape = cshape.get_shape()
                        print(" update existing")
                    else:
                        cshape = CollisionShape.new()
                        bshape = BoxShape.new()
                        #node.add_shape(bshape,N.get_transform())
                        cshape.set_name(newname)
                        cshape.set_shape(bshape)
                        node.add_child(cshape)
                        #you need to set_owner to scene root to make a node visible in editor
                        node.get_node(newname).set_owner(scene) 
                        #cshape.set_owner(node) #required? think not
                    bshape.set_extents(aab.size/2)
                    #no rotation transferred
                    print("Pos: "+String(aab.pos)+" End: "+String(aab.end))
                    #pos + size/2 seems to work good as long as pos is min of (pos,end)
                    #todo: move basis/rotation over
                    cshape.set_translation(N.get_translation()+((aab.pos+(aab.size/2))))
                    node.remove_child(N) #maybe not required when free/queue_free is used
                    N.queue_free()
    
                    #remove shapes which are auto-added to rigidbodies(-rigid)/staticbodies (-colonly)
                    #they're replaced by colbox shapes
                    delList["shape"]="CollisionShape"
                    delList["SphereShape"]="CollisionShape"
                    delList["BoxShape"]="CollisionShape"
    
            #Objects with -preset in name allow to initialize selected presets (i.e. for RigidBody/StaticBody)
            #Example name: "Cube-preset-mask=1024-layers=3-mass=1000"
            if (N.get_type()=="MeshInstance") and (N.get_name().find("-preset")>=0): #preset values
                #print("get presets")
                presetMatch.find(N.get_name())
    
                var matchStart=0
                var prevMatchStart=0
                while presetMatch.find(N.get_name(),matchStart)>=0:
                    matchStart=presetMatch.get_capture_start(0)+presetMatch.get_capture(0).length()
                    if prevMatchStart==matchStart:#check for endless loop
                        print("error in regexpmatch endless loop")
                        break
                    else:
                        prevMatchStart=matchStart
    
                    preset[presetMatch.get_capture(1)]=presetMatch.get_capture(2)
                    print("preset: "+presetMatch.get_capture(1)+"<=>"+presetMatch.get_capture(2))
                    delList[N.get_name()]=null
        #delete child nodes after iteration (to not confuse object iteration)
        for i in delList:
            delchild(node,i,delList[i])
    
    #init selected presets if set
    func setBodyPresets(node):
        if node.is_type("PhysicsBody") or node.is_type("Area"):
            if preset.has("mask"):
                node.set_collision_mask(int(preset["mask"]))
            if preset.has("layers"):
                node.set_layer_mask(int(preset["layers"]))
        if node.is_type("RigidBody"):
            if preset.has("mass"):
                node.set_mass(float(preset["mass"]))
            if preset.has("friction"):
                node.set_friction(float(preset["friction"]))
            if preset.has("linear_damp"):
                node.set_linear_damp(float(preset["linear_damp"]))
            if preset.has("angular_damp"):
                node.set_angular_damp(float(preset["angular_damp"]))
    
    func delchild(node,childid,type=null):
        if node.has_node(childid):
            var c=node.get_node(childid)
            if (type==null) or (c.get_type()==type):
                node.remove_child(c)
                c.queue_free()
    
Sign In or Register to comment.