From bf9a0059c11a5025f6dc96553bc629b675e31de2 Mon Sep 17 00:00:00 2001 From: Devo Date: Fri, 12 Apr 2024 14:55:51 +0200 Subject: [PATCH 1/4] Cinema 4D Cast Support Cast Model support for Cinema 4D. Supports: - Meshes - Skeleton - Materials - Vertex colors - Constraints - IK handles Installation: Create a folder with the name "Cast", "io_scene_cast" or any other suitable name and place the files in there. The cast python library (cast.py) should be placed in the resource folder named "res" in the plugins directory. Requirements: Cinema 4D 2024 or newer Currently not supported: - Animations - Instances - Blend shapes --- plugins/cinema4d/import_cast.pyp | 431 ++++++++++++++++++ plugins/cinema4d/res/c4d_symbols.h | 12 + .../cinema4d/res/description/fcastloader.h | 15 + .../cinema4d/res/description/fcastloader.res | 15 + .../cinema4d/res/strings_us/c4d_strings.str | 4 + .../strings_us/description/fcastloader.str | 9 + 6 files changed, 486 insertions(+) create mode 100644 plugins/cinema4d/import_cast.pyp create mode 100644 plugins/cinema4d/res/c4d_symbols.h create mode 100644 plugins/cinema4d/res/description/fcastloader.h create mode 100644 plugins/cinema4d/res/description/fcastloader.res create mode 100644 plugins/cinema4d/res/strings_us/c4d_strings.str create mode 100644 plugins/cinema4d/res/strings_us/description/fcastloader.str diff --git a/plugins/cinema4d/import_cast.pyp b/plugins/cinema4d/import_cast.pyp new file mode 100644 index 0000000..09b3649 --- /dev/null +++ b/plugins/cinema4d/import_cast.pyp @@ -0,0 +1,431 @@ +import c4d +import os +import array +import math +from c4d import plugins, bitmaps, Vector, gui, BaseObject +import mxutils + +resDir = os.path.join(os.path.dirname(__file__), "res") +mxutils.ImportSymbols(resDir) +with mxutils.LocalImportPath(resDir): + from cast import Cast, CastColor, Model, Animation, Instance, File + +__pluginname__ = "Cast" + +class CastLoader(plugins.SceneLoaderData): + def Identify(self, node, name, probe, size): + if "cast" in name[-4:]: + return True + return False + + def Load(self, node, name, doc, filterflags, error, bt): + importCast(doc, node, name) + return c4d.FILEERROR_NONE + +def importCast(doc, node, path): + cast = Cast.load(path) + + instances = [] + + for root in cast.Roots(): + for child in root.ChildrenOfType(Model): + importModelNode(doc, node, child, path) + for child in root.ChildrenOfType(Animation): + importAnimationNode(doc, child, path) + for child in root.ChildrenOfType(Instance): + instances.append(child) + + if len(instances) > 0: + importInstanceNodes(doc, instances, path) + +""" +We are using Blender unpack_list for convinience +https://github.com/blender/blender/blob/main/scripts/modules/bpy_extras/io_utils.py#L370 +""" +def unpack_list(list_of_tuples): + flat_list = [] + flat_list_extend = flat_list.extend # a tiny bit faster + for t in list_of_tuples: + flat_list_extend(t) + return flat_list + +def utilityQuaternionToEuler(q): + x, y, z, w = q + sinr_cosp = 2 * (w * x + y * z) + cosr_cosp = 1 - 2 * (x * x + y * y) + roll = math.atan2(sinr_cosp, cosr_cosp) + + sinp = 2 * (w * y - z * x) + if abs(sinp) >= 1: + pitch = math.copysign(math.pi / 2, sinp) + else: + pitch = math.asin(sinp) + + siny_cosp = 2 * (w * z + x * y) + cosy_cosp = 1 - 2 * (y * y + z * z) + yaw = math.atan2(siny_cosp, cosy_cosp) + + return roll, pitch, -yaw + +def utilityAddTextureMaterialSlots(slotName, texPath, mat, shaderType): + shader = c4d.BaseList2D(c4d.Xbitmap) + shader[c4d.BITMAPSHADER_FILENAME] = texPath + shader.SetName(slotName) + if slotName == "normal": + mat[c4d.MATERIAL_USE_NORMAL] = True + shader[c4d.BITMAPSHADER_COLORPROFILE] = 1 # Linear color space + + mat[shaderType] = shader + mat.InsertShader(shader) + +def utilityAssignMaterialSlots(doc, material, slots, path): + switcher = { + "albedo": c4d.MATERIAL_COLOR_SHADER, + "diffuse": c4d.MATERIAL_COLOR_SHADER, + "normal": c4d.MATERIAL_NORMAL_SHADER + } + + for slot in slots: + connection = slots[slot] + if not connection.__class__ is File: + continue + if not slot in switcher: + continue + + texturePath = os.path.dirname(path) + "\\" + connection.Path() + utilityAddTextureMaterialSlots(slot, texturePath, material, switcher[slot]) + +def utilityWriteNormalTag(tag, normalList): + # Retrieves the write buffer array + buffer = tag.GetLowlevelDataAddressW() + if buffer is None: + raise RuntimeError("Failed to retrieves internal write data for the normal tag.") + + # Translates list of short int 16 to a BitSeq (string are byte in Python 2.7) + data = array.array('h') + data.fromlist(normalList) + data = data.tobytes() + buffer[:len(data)] = data + +def importMaterialNode(doc, path, material): + materials = doc.GetMaterials() + for mat in materials: + if mat.GetName() == material.Name(): + return mat + + materialNew = c4d.BaseMaterial(c4d.Mmaterial) + materialNew.SetName(material.Name()) + + utilityAssignMaterialSlots(doc, materialNew, material.Slots(), path) + + doc.InsertMaterial(materialNew) + materialNew.Message(c4d.MSG_UPDATE) + + return materialNew + +def importModelNode(doc, node, model, path): + + # Extract the name of this model from the path + modelName = model.Name() or os.path.splitext(os.path.basename(path))[0] + # Create a collection for our objects + modelNull = BaseObject(c4d.Onull) + modelNull.SetName(modelName) + doc.InsertObject(modelNull) + # Import skeleton for binds, materials for meshes + boneIndexes = importSkeletonNode( + modelNull, model.Skeleton()) + materialArray = {x.Name(): importMaterialNode(doc, path, x) + for x in model.Materials()} + + meshes = model.Meshes() + meshHandles = {} + + for mesh in meshes: + newMesh = BaseObject(c4d.Opolygon) #c4d.CallCommand(13039) #bpy.data.meshes.new("polySurfaceMesh") + newMesh.SetName(mesh.Name() or "CastMesh") # meshObj = bpy.data.objects.new(mesh.Name() or "CastMesh", newMesh) + + meshHandles[mesh.Hash()] = (newMesh) + + vertexPositions = mesh.VertexPositionBuffer() + vertexCount = int(len(vertexPositions) / 3) + + faces = mesh.FaceBuffer() + faceIndicesCount = len(faces) + facesCount = int(faceIndicesCount / 3) + faces = unpack_list([(faces[x + 2], faces[x + 1], faces[x + 0]) + for x in range(0, faceIndicesCount, 3)]) + + newMesh.ResizeObject(vertexCount, facesCount) + + # Vertice + for i in range(0, len(vertexPositions), 3): + vertexIndex = i // 3 + x, y, z = vertexPositions[i:i+3] + newMesh.SetPoint(vertexIndex, Vector(x, y, -z)) # Matching the Maya default import + + # Faces + for i in range(0, len(faces), 3): + polyIndex = i // 3 + a, b, c = faces[i:i+3] + newMesh.SetPolygon(polyIndex, c4d.CPolygon(a, b, c)) + + # UVW + for i in range(mesh.UVLayerCount()): + uvTag = c4d.UVWTag(facesCount) + uvBuffer = mesh.VertexUVLayerBuffer(0) + uvUnpacked = unpack_list([(uvBuffer[x * 2], 1.0 - uvBuffer[(x * 2) + 1]) for x in faces]) + for i in range(0, len(uvUnpacked), 6): + polyIndex = i // 6 + u1, v1, u2, v2, u3, v3 = uvUnpacked[i:i+6] + uvTag.SetSlow(polyIndex, Vector(u1, 1-v1,0), + Vector(u2, 1-v2,0), + Vector(u3, 1-v3,0), + Vector(0,0,0)) + newMesh.InsertTag(uvTag) + + # Vertex Colors + vertexColors = mesh.VertexColorBuffer() + if vertexColors is not None: + vcTag = c4d.VertexColorTag(vertexCount) + vertexColorList = [x for xs in [CastColor.fromInteger(x) for x in vertexColors] for x in xs], len(vertexColors) * 4 + vcData = vcTag.GetDataAddressW() + for i in range(0, len(vertexColorList[0]), 4): + vIndex = i // 4 + r, g, b, a = vertexColorList[0][i:i+4] + vcTag.SetPoint(vcData,None,None,vIndex,c4d.Vector4d(r, g, b, a)) + newMesh.InsertTag(vcTag) + + # Vertex Normals + vertexNormals = mesh.VertexNormalBuffer() + if vertexNormals is not None: + # Yeah I don't know what I'm doing here + normaltag = c4d.NormalTag(count=newMesh.GetPolygonCount()) + + normalListUnpacked = unpack_list([(vertexNormals[x * 3], vertexNormals[(x * 3) + 1], vertexNormals[(x * 3) + 2]) for x in faces]) + normalList = [Vector(normalListUnpacked[i], normalListUnpacked[i+1], -normalListUnpacked[i+2]) for i in range(0, len(normalListUnpacked), 3)] + + # Even if it's a Tri, you should pass a value. + for i in range(len(normalList) // 3): + normalList.insert((i + 1) * 4 - 1, Vector(0, 0, 0)) + + # Maps data from float to int16 value + normalListToSet = [int(component * 32000.0) + for n in normalList for component in (n.x, n.y, n.z)] + + utilityWriteNormalTag(normaltag, normalListToSet) + newMesh.InsertTag(normaltag) + + # Weight + if model.Skeleton() is not None and node[CAST_IMPORT_BIND_SKIN]: + skinObj = BaseObject(c4d.Oskin) + skinningMethod = mesh.SkinningMethod() + skinType = c4d.ID_CA_SKIN_OBJECT_TYPE + if skinningMethod == "linear": + skinObj[skinType] = 0 + elif skinningMethod == "quaternion": + skinObj[skinType] = 1 + doc.InsertObject(skinObj, parent=newMesh) + + weightTag = c4d.modules.character.CAWeightTag() + newMesh.InsertTag(weightTag) + for key, value in boneIndexes.items(): + weightTag.AddJoint(value) + maximumInfluence = mesh.MaximumWeightInfluence() + if maximumInfluence > 1: # Slower path for complex weights + weightBoneBuffer = mesh.VertexWeightBoneBuffer() + weightValueBuffer = mesh.VertexWeightValueBuffer() + + for x in range(vertexCount): + for j in range(maximumInfluence): + weightIndex = j + (x * maximumInfluence) + weightValue = weightTag.GetWeight(weightBoneBuffer[weightIndex], x) + weightValue += weightValueBuffer[weightIndex] + weightTag.SetWeight( + weightBoneBuffer[weightIndex], + x, + weightValue + ) + elif maximumInfluence > 0: # Fast path for simple weighted meshes + weightBoneBuffer = mesh.VertexWeightBoneBuffer() + for x in range(len(newMesh.vertices)): + weightTag.SetWeight( + weightBoneBuffer[x], + x, + 1.0 + ) + weightTag.Message(c4d.MSG_UPDATE) + c4d.EventAdd() + + meshMaterial = mesh.Material() + if meshMaterial is not None: + material = materialArray[meshMaterial.Name()] + material_tag = newMesh.MakeTag(c4d.Ttexture) + material_tag[c4d.TEXTURETAG_MATERIAL] = material + material_tag[c4d.TEXTURETAG_PROJECTION] = 6 + + doc.InsertObject(newMesh, parent=modelNull) + newMesh.Message(c4d.MSG_UPDATE) + c4d.EventAdd() + + if node[CAST_IMPORT_IK_HANDLES]: + importSkeletonIKNode(doc, modelNull, model.Skeleton(), boneIndexes) + + if node[CAST_IMPORT_CONSTRAINTS]: + importSkeletonConstraintNode(model.Skeleton(), boneIndexes) + +def importSkeletonConstraintNode(skeleton, boneIndexes): + if skeleton is None: + return + + for constraint in skeleton.Constraints(): + constraintBone = boneIndexes[constraint.ConstraintBone().Hash()] + targetBone = boneIndexes[constraint.TargetBone().Hash()] + + type = constraint.ConstraintType() + + # C4D's constraint system is a bit worse than Blender's + constraintTag = c4d.BaseTag(CONSTRAINT_TAG) + constraintBone.InsertTag(constraintTag) + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR] = 1 + + # Disabling all constraints, cause default is enabled + constraintTag[CONSTRAIN_POS] = 0 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_X] = 0 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Y] = 0 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Z] = 0 + + constraintTag[CONSTRAIN_SCALE] = 0 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_X] = 0 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Y] = 0 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Z] = 0 + + constraintTag[CONSTRAIN_ROT] = 0 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_X] = 0 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Y] = 0 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Z] = 0 + + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_MAINTAIN] = constraint.MaintainOffset() + + if type == "pt": + constraintTag[CONSTRAIN_POS] = 1 + constraintTag[CONSTRAINT_TARGET] = targetBone + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_LOCAL_P] = 1 + if not constraint.SkipX(): + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_X] = 1 + if not constraint.SkipY(): + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Y] = 1 + if not constraint.SkipZ(): + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Z] = 1 + elif type == "sc": + constraintTag[CONSTRAIN_SCALE] = 1 + constraintTag[CONSTRAINT_TARGET] = targetBone + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_LOCAL_S] = 1 + if not constraint.SkipX(): + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_X] = 1 + if not constraint.SkipY(): + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Y] = 1 + if not constraint.SkipZ(): + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Z] = 1 + elif type == "or": + constraintTag[CONSTRAIN_ROT] = 1 + constraintTag[CONSTRAINT_TARGET] = targetBone + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_LOCAL_R] = 1 + if not constraint.SkipX(): + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_X] = 1 + if not constraint.SkipY(): + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Y] = 1 + if not constraint.SkipZ(): + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Z] = 1 + else: + continue + + if constraint.Name() is not None: + constraintTag[c4d.ID_BASELIST_NAME] = constraint.Name() + +def importSkeletonIKNode(doc, modelNull, skeleton, boneIndexes): + if skeleton is None or not skeleton.IKHandles(): + return + + ikParentNull = BaseObject(c4d.Onull) + ikParentNull.SetName("IK_Handles") + doc.InsertObject(ikParentNull, modelNull) + for handle in skeleton.IKHandles(): + startBone = boneIndexes[handle.StartBone().Hash()] + endBone = boneIndexes[handle.EndBone().Hash()] + + ikTargetNull = BaseObject(c4d.Onull) + ikTargetNull.SetName(endBone.GetName() + "_IK") + ikTargetNull.SetMg(endBone.GetMg()) + ikTargetNull.SetAbsRot(Vector(0, 0, 0)) + doc.InsertObject(ikTargetNull, ikParentNull) + + ikTag = c4d.BaseTag(IK_TAG) + ikTag[c4d.ID_CA_IK_TAG_SOLVER] = 2 + ikTag[c4d.ID_CA_IK_TAG_TIP] = endBone + ikTag[c4d.ID_CA_IK_TAG_TARGET] = ikTargetNull + + poleVectorBone = handle.PoleVectorBone() + if poleVectorBone is not None: + poleVector = boneIndexes[poleVectorBone.Hash()] + ikPoleNull = BaseObject(c4d.Onull) + ikPoleNull.SetName(poleVector.GetName() + "_Pole") + ikPoleNull.SetMg(poleVector.GetMg()) + ikPoleNull.SetAbsRot(Vector(0, 0, 0)) + + doc.InsertObject(ikPoleNull, startBone) + + ikTag[c4d.ID_CA_IK_TAG_POLE] = poleVector + + startBone.InsertTag(ikTag) + +def importSkeletonNode(modelNull, skeleton): + if skeleton is None: + return None + + bones = skeleton.Bones() + handles = [None] * len(bones) + boneIndexes = {} + + for i, bone in enumerate(bones): + newBone = BaseObject(c4d.Ojoint) + newBone.SetName(bone.Name()) + + tX, tY, tZ = bone.LocalPosition() + translation = Vector(tX, tY, -tZ) + + rX, rY, rZ = utilityQuaternionToEuler(bone.LocalRotation()) + newBone.SetRotationOrder(5) + + scale_tuple = bone.Scale() or (1.0, 1.0, 1.0) + scale = Vector(scale_tuple[0], scale_tuple[1], scale_tuple[2]) + + newBone.SetAbsPos(translation) + newBone.SetAbsRot(Vector(rX, rY, rZ)) + newBone.SetAbsScale(scale) + + handles[i] = newBone + boneIndexes[bone.Hash() or i] = newBone + + if bone.ParentIndex() > -1: + handles[i].InsertUnder(handles[bone.ParentIndex()]) + else: + handles[i].InsertUnder(modelNull) + + return boneIndexes + +def importAnimationNode(doc, animation, path): + gui.MessageDialog(text="Animations are currently not supported.", type=c4d.GEMB_ICONSTOP) + +def importInstanceNodes(doc, nodes, path): + gui.MessageDialog(text="Instances are currently not supported.", type=c4d.GEMB_ICONSTOP) + +if __name__ == '__main__': + dir,fn=os.path.split(__file__) + bmp=bitmaps.BaseBitmap() + bmp.InitWith(os.path.join(dir,"res","icon.png")) + reg=plugins.RegisterSceneLoaderPlugin(id=PLUGIN_ID, + str=__pluginname__, + info=0, + g=CastLoader, + description="fcastloader", + ) diff --git a/plugins/cinema4d/res/c4d_symbols.h b/plugins/cinema4d/res/c4d_symbols.h new file mode 100644 index 0000000..7534cda --- /dev/null +++ b/plugins/cinema4d/res/c4d_symbols.h @@ -0,0 +1,12 @@ +enum +{ + PLUGIN_ID = 1062992, + + CONSTRAINT_TAG = 1019364, + CONSTRAINT_TARGET = 10001, + CONSTRAIN_POS = 10005, + CONSTRAIN_SCALE = 10006, + CONSTRAIN_ROT = 10007, + + IK_TAG = 1019561 +}; diff --git a/plugins/cinema4d/res/description/fcastloader.h b/plugins/cinema4d/res/description/fcastloader.h new file mode 100644 index 0000000..86f03ba --- /dev/null +++ b/plugins/cinema4d/res/description/fcastloader.h @@ -0,0 +1,15 @@ +#ifndef _FCASTLOADER_H__ +#define _FCASTLOADER_H__ + +enum +{ + + fies_loader = 10000, + CAST_IMPORT_CONSTRAINTS, + CAST_IMPORT_IK_HANDLES, + CAST_IMPORT_BIND_SKIN, + CAST_IMPORT_GROUP + +}; +#endif + diff --git a/plugins/cinema4d/res/description/fcastloader.res b/plugins/cinema4d/res/description/fcastloader.res new file mode 100644 index 0000000..d6b76a2 --- /dev/null +++ b/plugins/cinema4d/res/description/fcastloader.res @@ -0,0 +1,15 @@ +CONTAINER fcastloader +{ + INCLUDE Fbase; + NAME fcastloader; + + GROUP CAST_IMPORT_GROUP + { + DEFAULT 1; + + BOOL CAST_IMPORT_BIND_SKIN {} + BOOL CAST_IMPORT_IK_HANDLES {} + BOOL CAST_IMPORT_CONSTRAINTS {} + + } +} \ No newline at end of file diff --git a/plugins/cinema4d/res/strings_us/c4d_strings.str b/plugins/cinema4d/res/strings_us/c4d_strings.str new file mode 100644 index 0000000..84ba032 --- /dev/null +++ b/plugins/cinema4d/res/strings_us/c4d_strings.str @@ -0,0 +1,4 @@ +STRINGTABLE +{ + +} \ No newline at end of file diff --git a/plugins/cinema4d/res/strings_us/description/fcastloader.str b/plugins/cinema4d/res/strings_us/description/fcastloader.str new file mode 100644 index 0000000..bd49ff8 --- /dev/null +++ b/plugins/cinema4d/res/strings_us/description/fcastloader.str @@ -0,0 +1,9 @@ +STRINGTABLE fcastloader +{ + fcastloader "Cast Import"; + CAST_IMPORT_GROUP "Cast Import Settings"; + CAST_IMPORT_BIND_SKIN "Import Bind Skin"; + CAST_IMPORT_IK_HANDLES "Import IK Handles"; + CAST_IMPORT_CONSTRAINTS "Import Constraints"; + +} \ No newline at end of file From 93f2a751528cf0821f3700a7d6a9c4e314a9a8a4 Mon Sep 17 00:00:00 2001 From: Devo Date: Fri, 12 Apr 2024 15:53:04 +0200 Subject: [PATCH 2/4] fixed naming scheme --- plugins/cinema4d/res/description/fcastloader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cinema4d/res/description/fcastloader.h b/plugins/cinema4d/res/description/fcastloader.h index 86f03ba..2eaa05d 100644 --- a/plugins/cinema4d/res/description/fcastloader.h +++ b/plugins/cinema4d/res/description/fcastloader.h @@ -4,7 +4,7 @@ enum { - fies_loader = 10000, + CAST_LOADER = 10000, CAST_IMPORT_CONSTRAINTS, CAST_IMPORT_IK_HANDLES, CAST_IMPORT_BIND_SKIN, From 4fda4f01685f4e461f8bbb7ed3224f53812114a4 Mon Sep 17 00:00:00 2001 From: Devo Date: Sat, 13 Apr 2024 21:03:35 +0200 Subject: [PATCH 3/4] Cast Model import update - usage of enums - added comments - usage of bools - parenting bones in an extra loop - minor cleanup Missing: - pole https://github.com/dtzxporter/cast/pull/60#discussion_r1563040795 - usage of c4d.quaternion() https://github.com/dtzxporter/cast/pull/60#discussion_r1563024259 - removing unpack_list steps https://github.com/dtzxporter/cast/pull/60#discussion_r1563048706 --- plugins/cinema4d/import_cast.pyp | 82 +++++++++++++++++--------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/plugins/cinema4d/import_cast.pyp b/plugins/cinema4d/import_cast.pyp index 09b3649..175fce6 100644 --- a/plugins/cinema4d/import_cast.pyp +++ b/plugins/cinema4d/import_cast.pyp @@ -5,9 +5,9 @@ import math from c4d import plugins, bitmaps, Vector, gui, BaseObject import mxutils -resDir = os.path.join(os.path.dirname(__file__), "res") -mxutils.ImportSymbols(resDir) -with mxutils.LocalImportPath(resDir): +RESOURCE_DIR = os.path.join(os.path.dirname(__file__), "res") +mxutils.ImportSymbols(RESOURCE_DIR) +with mxutils.LocalImportPath(RESOURCE_DIR): from cast import Cast, CastColor, Model, Animation, Instance, File __pluginname__ = "Cast" @@ -164,7 +164,7 @@ def importModelNode(doc, node, model, path): newMesh.SetPoint(vertexIndex, Vector(x, y, -z)) # Matching the Maya default import # Faces - for i in range(0, len(faces), 3): + for i in range(0, faceIndicesCount, 3): polyIndex = i // 3 a, b, c = faces[i:i+3] newMesh.SetPolygon(polyIndex, c4d.CPolygon(a, b, c)) @@ -204,7 +204,11 @@ def importModelNode(doc, node, model, path): normalListUnpacked = unpack_list([(vertexNormals[x * 3], vertexNormals[(x * 3) + 1], vertexNormals[(x * 3) + 2]) for x in faces]) normalList = [Vector(normalListUnpacked[i], normalListUnpacked[i+1], -normalListUnpacked[i+2]) for i in range(0, len(normalListUnpacked), 3)] - # Even if it's a Tri, you should pass a value. + """ + Raw data normal structure for one polygon is 12 int16 value (4 vectors + for each vertex of a Cpolygon * 3 components for each vector), even if + the Cpolygon is a triangle. + """ for i in range(len(normalList) // 3): normalList.insert((i + 1) * 4 - 1, Vector(0, 0, 0)) @@ -219,11 +223,10 @@ def importModelNode(doc, node, model, path): if model.Skeleton() is not None and node[CAST_IMPORT_BIND_SKIN]: skinObj = BaseObject(c4d.Oskin) skinningMethod = mesh.SkinningMethod() - skinType = c4d.ID_CA_SKIN_OBJECT_TYPE if skinningMethod == "linear": - skinObj[skinType] = 0 + skinObj[c4d.ID_CA_SKIN_OBJECT_TYPE] = c4d.ID_CA_SKIN_OBJECT_TYPE_LINEAR elif skinningMethod == "quaternion": - skinObj[skinType] = 1 + skinObj[c4d.ID_CA_SKIN_OBJECT_TYPE] = c4d.ID_CA_SKIN_OBJECT_TYPE_QUAT doc.InsertObject(skinObj, parent=newMesh) weightTag = c4d.modules.character.CAWeightTag() @@ -247,7 +250,7 @@ def importModelNode(doc, node, model, path): ) elif maximumInfluence > 0: # Fast path for simple weighted meshes weightBoneBuffer = mesh.VertexWeightBoneBuffer() - for x in range(len(newMesh.vertices)): + for x in range(vertexCount): weightTag.SetWeight( weightBoneBuffer[x], x, @@ -261,7 +264,7 @@ def importModelNode(doc, node, model, path): material = materialArray[meshMaterial.Name()] material_tag = newMesh.MakeTag(c4d.Ttexture) material_tag[c4d.TEXTURETAG_MATERIAL] = material - material_tag[c4d.TEXTURETAG_PROJECTION] = 6 + material_tag[c4d.TEXTURETAG_PROJECTION] = c4d.TEXTURETAG_PROJECTION_UVW doc.InsertObject(newMesh, parent=modelNull) newMesh.Message(c4d.MSG_UPDATE) @@ -286,56 +289,56 @@ def importSkeletonConstraintNode(skeleton, boneIndexes): # C4D's constraint system is a bit worse than Blender's constraintTag = c4d.BaseTag(CONSTRAINT_TAG) constraintBone.InsertTag(constraintTag) - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR] = True # Disabling all constraints, cause default is enabled - constraintTag[CONSTRAIN_POS] = 0 - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_X] = 0 - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Y] = 0 - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Z] = 0 + constraintTag[CONSTRAIN_POS] = False + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_X] = False + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Y] = False + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Z] = False - constraintTag[CONSTRAIN_SCALE] = 0 - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_X] = 0 - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Y] = 0 - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Z] = 0 + constraintTag[CONSTRAIN_SCALE] = False + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_X] = False + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Y] = False + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Z] = False - constraintTag[CONSTRAIN_ROT] = 0 - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_X] = 0 - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Y] = 0 - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Z] = 0 + constraintTag[CONSTRAIN_ROT] = False + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_X] = False + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Y] = False + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Z] = False constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_MAINTAIN] = constraint.MaintainOffset() if type == "pt": - constraintTag[CONSTRAIN_POS] = 1 + constraintTag[CONSTRAIN_POS] = True constraintTag[CONSTRAINT_TARGET] = targetBone - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_LOCAL_P] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_LOCAL_P] = True if not constraint.SkipX(): - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_X] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_X] = True if not constraint.SkipY(): - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Y] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Y] = True if not constraint.SkipZ(): - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Z] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_Z] = True elif type == "sc": - constraintTag[CONSTRAIN_SCALE] = 1 + constraintTag[CONSTRAIN_SCALE] = True constraintTag[CONSTRAINT_TARGET] = targetBone - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_LOCAL_S] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_LOCAL_S] = True if not constraint.SkipX(): - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_X] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_X] = True if not constraint.SkipY(): - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Y] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Y] = True if not constraint.SkipZ(): - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Z] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_S_Z] = True elif type == "or": - constraintTag[CONSTRAIN_ROT] = 1 + constraintTag[CONSTRAIN_ROT] = True constraintTag[CONSTRAINT_TARGET] = targetBone - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_LOCAL_R] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_LOCAL_R] = True if not constraint.SkipX(): - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_X] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_X] = True if not constraint.SkipY(): - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Y] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Y] = True if not constraint.SkipZ(): - constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Z] = 1 + constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_R_Z] = True else: continue @@ -394,7 +397,7 @@ def importSkeletonNode(modelNull, skeleton): translation = Vector(tX, tY, -tZ) rX, rY, rZ = utilityQuaternionToEuler(bone.LocalRotation()) - newBone.SetRotationOrder(5) + newBone.SetRotationOrder(c4d.ROTATIONORDER_XYZGLOBAL) scale_tuple = bone.Scale() or (1.0, 1.0, 1.0) scale = Vector(scale_tuple[0], scale_tuple[1], scale_tuple[2]) @@ -406,6 +409,7 @@ def importSkeletonNode(modelNull, skeleton): handles[i] = newBone boneIndexes[bone.Hash() or i] = newBone + for i, bone in enumerate(bones): if bone.ParentIndex() > -1: handles[i].InsertUnder(handles[bone.ParentIndex()]) else: From d6e869d122e7a900db6c2c5c3ef3594930912631 Mon Sep 17 00:00:00 2001 From: Devo Date: Mon, 12 Aug 2024 19:44:01 +0200 Subject: [PATCH 4/4] Cinema 4D Cast Instances Support New Feature: - Instance import Improvements: - using c4d.Quaternion() - using PoleVectorBone instead of PoleBone Requirements: - Cinema 4D 2024.5 or newer --- plugins/cinema4d/import_cast.pyp | 174 ++++++++++++++++++++--------- plugins/cinema4d/res/c4d_symbols.h | 3 +- 2 files changed, 126 insertions(+), 51 deletions(-) diff --git a/plugins/cinema4d/import_cast.pyp b/plugins/cinema4d/import_cast.pyp index 175fce6..4b62e8a 100644 --- a/plugins/cinema4d/import_cast.pyp +++ b/plugins/cinema4d/import_cast.pyp @@ -1,10 +1,12 @@ import c4d import os import array -import math -from c4d import plugins, bitmaps, Vector, gui, BaseObject import mxutils + +from c4d import plugins, bitmaps, Vector, gui, BaseObject + + RESOURCE_DIR = os.path.join(os.path.dirname(__file__), "res") mxutils.ImportSymbols(RESOURCE_DIR) with mxutils.LocalImportPath(RESOURCE_DIR): @@ -22,6 +24,7 @@ class CastLoader(plugins.SceneLoaderData): importCast(doc, node, name) return c4d.FILEERROR_NONE + def importCast(doc, node, path): cast = Cast.load(path) @@ -36,8 +39,8 @@ def importCast(doc, node, path): instances.append(child) if len(instances) > 0: - importInstanceNodes(doc, instances, path) - + importInstanceNodes(doc, instances, node, path) + """ We are using Blender unpack_list for convinience https://github.com/blender/blender/blob/main/scripts/modules/bpy_extras/io_utils.py#L370 @@ -49,23 +52,21 @@ def unpack_list(list_of_tuples): flat_list_extend(t) return flat_list -def utilityQuaternionToEuler(q): - x, y, z, w = q - sinr_cosp = 2 * (w * x + y * z) - cosr_cosp = 1 - 2 * (x * x + y * y) - roll = math.atan2(sinr_cosp, cosr_cosp) - sinp = 2 * (w * y - z * x) - if abs(sinp) >= 1: - pitch = math.copysign(math.pi / 2, sinp) - else: - pitch = math.asin(sinp) +def utilityQuaternionToEuler(tempQuat): + quaternion = c4d.Quaternion() + quaternion.w = tempQuat[3] + quaternion.v = Vector(tempQuat[0], tempQuat[1], -tempQuat[2]) + + m = c4d.Matrix() + m = quaternion.GetMatrix() + mToHPB = c4d.utils.MatrixToHPB + + rotationOrder = c4d.ROTATIONORDER_HPB + rotation = mToHPB(m, rotationOrder) - siny_cosp = 2 * (w * z + x * y) - cosy_cosp = 1 - 2 * (y * y + z * z) - yaw = math.atan2(siny_cosp, cosy_cosp) + return rotation - return roll, pitch, -yaw def utilityAddTextureMaterialSlots(slotName, texPath, mat, shaderType): shader = c4d.BaseList2D(c4d.Xbitmap) @@ -78,6 +79,7 @@ def utilityAddTextureMaterialSlots(slotName, texPath, mat, shaderType): mat[shaderType] = shader mat.InsertShader(shader) + def utilityAssignMaterialSlots(doc, material, slots, path): switcher = { "albedo": c4d.MATERIAL_COLOR_SHADER, @@ -95,6 +97,7 @@ def utilityAssignMaterialSlots(doc, material, slots, path): texturePath = os.path.dirname(path) + "\\" + connection.Path() utilityAddTextureMaterialSlots(slot, texturePath, material, switcher[slot]) + def utilityWriteNormalTag(tag, normalList): # Retrieves the write buffer array buffer = tag.GetLowlevelDataAddressW() @@ -107,12 +110,13 @@ def utilityWriteNormalTag(tag, normalList): data = data.tobytes() buffer[:len(data)] = data + def importMaterialNode(doc, path, material): materials = doc.GetMaterials() for mat in materials: if mat.GetName() == material.Name(): return mat - + materialNew = c4d.BaseMaterial(c4d.Mmaterial) materialNew.SetName(material.Name()) @@ -123,8 +127,8 @@ def importMaterialNode(doc, path, material): return materialNew -def importModelNode(doc, node, model, path): +def importModelNode(doc, node, model, path): # Extract the name of this model from the path modelName = model.Name() or os.path.splitext(os.path.basename(path))[0] # Create a collection for our objects @@ -139,10 +143,10 @@ def importModelNode(doc, node, model, path): meshes = model.Meshes() meshHandles = {} - + for mesh in meshes: - newMesh = BaseObject(c4d.Opolygon) #c4d.CallCommand(13039) #bpy.data.meshes.new("polySurfaceMesh") - newMesh.SetName(mesh.Name() or "CastMesh") # meshObj = bpy.data.objects.new(mesh.Name() or "CastMesh", newMesh) + newMesh = BaseObject(c4d.Opolygon) + newMesh.SetName(mesh.Name() or "CastMesh") meshHandles[mesh.Hash()] = (newMesh) @@ -154,14 +158,14 @@ def importModelNode(doc, node, model, path): facesCount = int(faceIndicesCount / 3) faces = unpack_list([(faces[x + 2], faces[x + 1], faces[x + 0]) for x in range(0, faceIndicesCount, 3)]) - + newMesh.ResizeObject(vertexCount, facesCount) # Vertice for i in range(0, len(vertexPositions), 3): vertexIndex = i // 3 x, y, z = vertexPositions[i:i+3] - newMesh.SetPoint(vertexIndex, Vector(x, y, -z)) # Matching the Maya default import + newMesh.SetPoint(vertexIndex, Vector(x, y, -z)) # Faces for i in range(0, faceIndicesCount, 3): @@ -173,6 +177,7 @@ def importModelNode(doc, node, model, path): for i in range(mesh.UVLayerCount()): uvTag = c4d.UVWTag(facesCount) uvBuffer = mesh.VertexUVLayerBuffer(0) + uvUnpacked = unpack_list([(uvBuffer[x * 2], 1.0 - uvBuffer[(x * 2) + 1]) for x in faces]) for i in range(0, len(uvUnpacked), 6): polyIndex = i // 6 @@ -215,7 +220,7 @@ def importModelNode(doc, node, model, path): # Maps data from float to int16 value normalListToSet = [int(component * 32000.0) for n in normalList for component in (n.x, n.y, n.z)] - + utilityWriteNormalTag(normaltag, normalListToSet) newMesh.InsertTag(normaltag) @@ -257,7 +262,6 @@ def importModelNode(doc, node, model, path): 1.0 ) weightTag.Message(c4d.MSG_UPDATE) - c4d.EventAdd() meshMaterial = mesh.Material() if meshMaterial is not None: @@ -268,13 +272,16 @@ def importModelNode(doc, node, model, path): doc.InsertObject(newMesh, parent=modelNull) newMesh.Message(c4d.MSG_UPDATE) - c4d.EventAdd() if node[CAST_IMPORT_IK_HANDLES]: importSkeletonIKNode(doc, modelNull, model.Skeleton(), boneIndexes) if node[CAST_IMPORT_CONSTRAINTS]: importSkeletonConstraintNode(model.Skeleton(), boneIndexes) + c4d.EventAdd() + + return modelNull + def importSkeletonConstraintNode(skeleton, boneIndexes): if skeleton is None: @@ -290,7 +297,7 @@ def importSkeletonConstraintNode(skeleton, boneIndexes): constraintTag = c4d.BaseTag(CONSTRAINT_TAG) constraintBone.InsertTag(constraintTag) constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR] = True - + # Disabling all constraints, cause default is enabled constraintTag[CONSTRAIN_POS] = False constraintTag[c4d.ID_CA_CONSTRAINT_TAG_PSR_CONSTRAIN_P_X] = False @@ -345,6 +352,7 @@ def importSkeletonConstraintNode(skeleton, boneIndexes): if constraint.Name() is not None: constraintTag[c4d.ID_BASELIST_NAME] = constraint.Name() + def importSkeletonIKNode(doc, modelNull, skeleton, boneIndexes): if skeleton is None or not skeleton.IKHandles(): return @@ -366,21 +374,21 @@ def importSkeletonIKNode(doc, modelNull, skeleton, boneIndexes): ikTag[c4d.ID_CA_IK_TAG_SOLVER] = 2 ikTag[c4d.ID_CA_IK_TAG_TIP] = endBone ikTag[c4d.ID_CA_IK_TAG_TARGET] = ikTargetNull - + poleVectorBone = handle.PoleVectorBone() if poleVectorBone is not None: poleVector = boneIndexes[poleVectorBone.Hash()] - ikPoleNull = BaseObject(c4d.Onull) - ikPoleNull.SetName(poleVector.GetName() + "_Pole") - ikPoleNull.SetMg(poleVector.GetMg()) - ikPoleNull.SetAbsRot(Vector(0, 0, 0)) - - doc.InsertObject(ikPoleNull, startBone) - ikTag[c4d.ID_CA_IK_TAG_POLE] = poleVector + ikTag[c4d.ID_CA_IK_TAG_POLE_AXIS] = POLE_AXIS_X + ikTag[c4d.ID_CA_IK_TAG_POLE_TWIST] = poleVector[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] + poleBone = handle.PoleBone() + if poleBone is not None: + # Warn until we figure out how to emulate this effectively. + gui.MessageDialog(text="Unable to setup %s fully due to Cinema 4D not supporting pole (twist) bones." % poleBone.Name(), type=c4d.GEMB_ICONEXCLAMATION) startBone.InsertTag(ikTag) - + + def importSkeletonNode(modelNull, skeleton): if skeleton is None: return None @@ -392,18 +400,18 @@ def importSkeletonNode(modelNull, skeleton): for i, bone in enumerate(bones): newBone = BaseObject(c4d.Ojoint) newBone.SetName(bone.Name()) - + tX, tY, tZ = bone.LocalPosition() translation = Vector(tX, tY, -tZ) - rX, rY, rZ = utilityQuaternionToEuler(bone.LocalRotation()) - newBone.SetRotationOrder(c4d.ROTATIONORDER_XYZGLOBAL) + tempQuat = bone.LocalRotation() + rotation = utilityQuaternionToEuler(tempQuat) - scale_tuple = bone.Scale() or (1.0, 1.0, 1.0) - scale = Vector(scale_tuple[0], scale_tuple[1], scale_tuple[2]) + scaleTuple = bone.Scale() or (1.0, 1.0, 1.0) + scale = Vector(scaleTuple[0], scaleTuple[1], scaleTuple[2]) newBone.SetAbsPos(translation) - newBone.SetAbsRot(Vector(rX, rY, rZ)) + newBone.SetAbsRot(rotation) newBone.SetAbsScale(scale) handles[i] = newBone @@ -417,16 +425,82 @@ def importSkeletonNode(modelNull, skeleton): return boneIndexes -def importAnimationNode(doc, animation, path): + +def importAnimationNode(): gui.MessageDialog(text="Animations are currently not supported.", type=c4d.GEMB_ICONSTOP) -def importInstanceNodes(doc, nodes, path): - gui.MessageDialog(text="Instances are currently not supported.", type=c4d.GEMB_ICONSTOP) + +def importInstanceNodes(doc, nodes, node, path): + rootPath = c4d.storage.LoadDialog(title='Select the root directory where instance scenes are located', flags=2) + + if rootPath is None: + return gui.MessageDialog(text="Unable to import instances without a root directory!", type=c4d.GEMB_ICONSTOP) + + uniqueInstances = {} + + for instance in nodes: + refs = os.path.join(rootPath, instance.ReferenceFile().Path()) + + if refs in uniqueInstances: + uniqueInstances[refs].append(instance) + else: + uniqueInstances[refs] = [instance] + + name = os.path.splitext(os.path.basename(path))[0] + + # Create a collection for our objects + rootNull = BaseObject(c4d.Onull) + rootNull.SetName(name) + doc.InsertObject(rootNull) + + instanceNull = BaseObject(c4d.Onull) + instanceNull.SetName("%s_instances" % name) + instanceNull.InsertUnder(rootNull) + + sceneNull = BaseObject(c4d.Onull) + sceneNull.SetName("%s_scenes" % name) + sceneNull.InsertUnder(rootNull) + # Disable source models visibility + sceneNull[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 1 + sceneNull[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 1 + + for instancePath, instances in uniqueInstances.items(): + instanceName = os.path.splitext(os.path.basename(instancePath))[0] + + try: + cast = Cast.load(instancePath) + for root in cast.Roots(): + for child in root.ChildrenOfType(Model): + modelNull = importModelNode(doc, node, child, instancePath) + modelNull.InsertUnder(sceneNull) + except: + print("Failed to import instance: %s" % instancePath) + continue + + for instance in instances: + # Creates instance object + newInstance = c4d.InstanceObject() + + newInstance.SetName(instance.Name() or instanceName) + newInstance.InsertUnder(instanceNull) + + tX, tY, tZ = instance.Position() + translation = Vector(tX, tY, -tZ) + + tempQuat = instance.Rotation() + rotation = utilityQuaternionToEuler(tempQuat) + + scaleTuple = instance.Scale() or (1.0, 1.0, 1.0) + scale = Vector(scaleTuple[0], scaleTuple[1], scaleTuple[2]) + + newInstance.SetAbsPos(translation) + newInstance.SetAbsRot(rotation) + newInstance.SetAbsScale(scale) + + newInstance.SetReferenceObject(modelNull) + if __name__ == '__main__': - dir,fn=os.path.split(__file__) - bmp=bitmaps.BaseBitmap() - bmp.InitWith(os.path.join(dir,"res","icon.png")) reg=plugins.RegisterSceneLoaderPlugin(id=PLUGIN_ID, str=__pluginname__, info=0, diff --git a/plugins/cinema4d/res/c4d_symbols.h b/plugins/cinema4d/res/c4d_symbols.h index 7534cda..45f90ed 100644 --- a/plugins/cinema4d/res/c4d_symbols.h +++ b/plugins/cinema4d/res/c4d_symbols.h @@ -8,5 +8,6 @@ enum CONSTRAIN_SCALE = 10006, CONSTRAIN_ROT = 10007, - IK_TAG = 1019561 + IK_TAG = 1019561, + POLE_AXIS_X = 1 };