#Noesis Python model import+export test module, imports/exports some data from/to a made-up format from inc_noesis import * #registerNoesisTypes is called by Noesis to allow the script to register formats. #Do not implement this function in script files unless you want them to be dedicated format modules! def registerNoesisTypes(): handle = noesis.register("Noesis Python Test Model", ".noepy") noesis.setHandlerTypeCheck(handle, noepyCheckType) noesis.setHandlerLoadModel(handle, noepyLoadModel) noesis.setHandlerWriteModel(handle, noepyWriteModel) noesis.setHandlerWriteAnim(handle, noepyWriteAnim) #any noesis.NMSHAREDFL_* flags can be applied here, to affect the model which is handed off to the exporter. #adding noesis.NMSHAREDFL_FLATWEIGHTS_FORCE4 would force us to 4 weights per vert. noesis.setTypeSharedModelFlags(handle, noesis.NMSHAREDFL_FLATWEIGHTS) #noesis.logPopup() #print("The log can be useful for catching debug prints from preview loads.\nBut don't leave it on when you release your script, or it will probably annoy people.") return 1 NOEPY_HEADER = 0x1337455 NOEPY_VERSION = 0x7178173 #check if it's this type based on the data def noepyCheckType(data): if len(data) < 8: return 0 bs = NoeBitStream(data) if bs.readInt() != NOEPY_HEADER: return 0 if bs.readInt() != NOEPY_VERSION: return 0 return 1 #read it def noepyLoadModel(data, mdlList): bs = NoeBitStream(data) if bs.readInt() != NOEPY_HEADER: return 0 if bs.readInt() != NOEPY_VERSION: return 0 #no need to explicitly free the context (created contexts are auto-freed after the handler), but DO NOT hold any references to it outside of this method ctx = rapi.rpgCreateContext() numMeshes = bs.readInt() for i in range(0, numMeshes): meshName = bs.readString() meshMat = bs.readString() numIdx = bs.readInt() numPos = bs.readInt() numNrm = bs.readInt() numUVs = bs.readInt() numTan = bs.readInt() numClr = bs.readInt() flatWeightsPerVert = bs.readInt() rapi.rpgSetName(meshName) rapi.rpgSetMaterial(meshMat) triangles = bs.readBytes(numIdx * 4) positions = bs.readBytes(numPos * 12) normals = bs.readBytes(numPos * 12) if numNrm == numPos else None uvs = bs.readBytes(numPos * 12) if numUVs == numPos else None tans = bs.readBytes(numPos * 48) if numTan == numPos else None colors = bs.readBytes(numPos * 16) if numClr == numPos else None flatWeightIdx = bs.readBytes(numPos * 4 * flatWeightsPerVert) if flatWeightsPerVert > 0 else None flatWeightVal = bs.readBytes(numPos * 4 * flatWeightsPerVert) if flatWeightsPerVert > 0 else None rapi.rpgBindPositionBuffer(positions, noesis.RPGEODATA_FLOAT, 12) rapi.rpgBindNormalBuffer(normals, noesis.RPGEODATA_FLOAT, 12) rapi.rpgBindUV1Buffer(uvs, noesis.RPGEODATA_FLOAT, 12) rapi.rpgBindColorBuffer(colors, noesis.RPGEODATA_FLOAT, 16, 4) rapi.rpgBindBoneIndexBuffer(flatWeightIdx, noesis.RPGEODATA_INT, 4 * flatWeightsPerVert, flatWeightsPerVert) rapi.rpgBindBoneWeightBuffer(flatWeightVal, noesis.RPGEODATA_FLOAT, 4 * flatWeightsPerVert, flatWeightsPerVert) numMorphFrames = bs.readInt() for j in range(0, numMorphFrames): numMFPos = bs.readInt() numMFNrm = bs.readInt() morphPosAr = bs.readBytes(numMFPos * 12) rapi.rpgFeedMorphTargetPositions(morphPosAr, noesis.RPGEODATA_FLOAT, 12) if numMFNrm > 0: morphNrmAr = bs.readBytes(numMFNrm * 12) rapi.rpgFeedMorphTargetNormals(morphNrmAr, noesis.RPGEODATA_FLOAT, 12) rapi.rpgCommitMorphFrame(numMFPos) rapi.rpgCommitMorphFrameSet() rapi.rpgCommitTriangles(triangles, noesis.RPGEODATA_INT, numIdx, noesis.RPGEO_TRIANGLE, 1) rapi.rpgClearBufferBinds() #reset in case a subsequent mesh doesn't have the same components #the mesh could also be drawn in immediate mode like this: #rapi.immBegin(noesis.RPGEO_TRIANGLE) #for i in range(0, numIdx): # idx = noeUnpackFrom(" 0: bs.writeBytes(noePack("i" * len(mesh.positions) * mesh.flatWeightsPerVert, *mesh.flatWeightIdx)) bs.writeBytes(noePack("f" * len(mesh.positions) * mesh.flatWeightsPerVert, *mesh.flatWeightVal)) bs.writeInt(len(mesh.morphList)) for mf in mesh.morphList: bs.writeInt(len(mf.positions)) bs.writeInt(len(mf.normals)) for vec in mf.positions: bs.writeBytes(vec.toBytes()) for vec in mf.normals: bs.writeBytes(vec.toBytes()) bs.writeInt(len(mdl.bones)) for bone in mdl.bones: noepyWriteBone(bs, bone) bs.writeInt(len(anims)) for anim in anims: bs.writeString(anim.name) bs.writeInt(len(anim.bones)) for bone in anim.bones: noepyWriteBone(bs, bone) bs.writeInt(anim.numFrames) bs.writeFloat(anim.frameRate) bs.writeInt(len(anim.frameMats)) for mat in anim.frameMats: bs.writeBytes(mat.toBytes()) return 1 #when you want animation data to be written out with a model format, you should make a handler like this that catches it and defers it def noepyWriteAnim(anims, bs): #it's good practice for an animation-deferring handler to inform the user that the format only supports joint model-anim export if rapi.isGeometryTarget() == 0: print("WARNING: Stand-alone animations cannot be written to the .noepy format.") return 0 rapi.setDeferredAnims(anims) return 0 #write bone def noepyWriteBone(bs, bone): bs.writeInt(bone.index) bs.writeString(bone.name) bs.writeString(bone.parentName) bs.writeInt(bone.parentIndex) bs.writeBytes(bone.getMatrix().toBytes()) #read bone def noepyReadBone(bs): boneIndex = bs.readInt() boneName = bs.readString() bonePName = bs.readString() bonePIndex = bs.readInt() boneMat = NoeMat43.fromBytes(bs.readBytes(48)) return NoeBone(boneIndex, boneName, boneMat, bonePName, bonePIndex)