Hello @mogh,
so, I had a look at your problem and this all works very much as expected for me. As stated before, we cannot debug your code for you. You will have to do that yourself. When you can show in a reasonably simple and repeatable example that Cinema 4D does handle vector/linear algebra differently between versions, we will investigate that. Not for R20 though, as this is out of the SDK support cycle. I also seriously doubt that there is a bug in Vector
or Matrix
of this magnitude in the first place (but I could be wrong).
Below you can find my solution for the problem. We cannot debug your code for such math problems for you in the future.
Cheers,
Ferdinand
The test file: transferaxis.c4d
The result:

The code:
"""Transfers the axis from one object to another.
Also handles vertices and normal tags, and provides a minimal GUI.
"""
import c4d
import array
def ReadNormalTag(tag: c4d.NormalTag) -> list[c4d.Vector]:
"""`Reads a `c4d.NormalTag` to a list of c4d.Vector.
Args:
tag: The tag to read the normals from.
Returns:
The red in normals. There are polygon_count * 4 normals, i.e. each
vertex has a normal for each polygon it is attached to. The normals
are stored in polygon groups.
Raises:
RuntimeError: When the memory of the tag cannot be red.
TypeError: When `tag` is not a `c4d.NormalTag`.
"""
if not (isinstance(tag, c4d.BaseTag) and tag.CheckType(c4d.Tnormal)):
msg = f"Expected normal tag, received: {tag}."
raise TypeError(tag)
buffer = tag.GetLowlevelDataAddressR()
if buffer is None:
msg = "Failed to retrieve memory buffer for VariableTag."
raise RuntimeError(msg)
data = array.array('h')
data.frombytes(buffer)
# Convert the int16 representation of the normals to c4d.Vector.
return [c4d.Vector(data[i-3] / 32000.0,
data[i-2] / 32000.0,
data[i-1] / 32000.0)
for i in range(3, len(data) + 3, 3)]
def WriteNormalTag(tag: c4d.NormalTag, normals: list[c4d.Vector],
normalize: bool = True) -> None:
"""`Writes a list of c4d.Vector to a `c4d.NormalTag`.
Args:
tag: The tag to write the normals to.
normals: The normals to write.
normalize: If to normalize the normals. Defaults to `True`.
Note:
Does not ensure that `normals` is only composed of c4d.Vector.
Raises:
IndexError: When `normals` does not match the size of `tag`.
RuntimeError: When the memory of the tag cannot be red.
TypeError: When `tag` is not a `c4d.NormalTag`.
"""
if not (isinstance(tag, c4d.BaseTag) and tag.CheckType(c4d.Tnormal)):
msg = f"Expected normal tag, received: {tag}."
raise TypeError(tag)
buffer = tag.GetLowlevelDataAddressW()
if buffer is None:
msg = "Failed to retrieve memory buffer for VariableTag."
raise RuntimeError(msg)
if normalize:
normals = [n.GetNormalized() for n in normals]
# Convert c4d.Vector normals to integer representation.
raw_normals = [int(component * 32000.0)
for n in normals for component in (n.x, n.y, n.z)]
# Catch input data of invalid length.
count = tag.GetDataCount()
if count * 12 != len(raw_normals):
msg = (f"Invalid data size. Expected length of {count}. "
f"Received: {len(raw_normals)}")
raise IndexError(msg)
# Write the data back.
data = array.array('h')
data.fromlist(raw_normals)
data = data.tobytes()
buffer[:len(data)] = data
def TransferAxisTo(node: c4d.BaseObject, target: c4d.BaseObject) -> None:
"""Moves the axis of node to target.
"""
mgNode = node.GetMg()
mgTarget = target.GetMg()
# Set the transform of the node to the target transform.
doc.StartUndo()
doc.AddUndo(c4d.UNDOTYPE_CHANGE, node)
node.SetMg(mgTarget)
# Exit when node is not a point object.
if not isinstance(node, c4d.PointObject):
doc.EndUndo()
return None
# Compute the transform delta between both objects with and without
# translations and transform all vertices.
mgDelta = ~mgTarget * mgNode
mgOrientationDelta = c4d.Matrix(
v1=mgDelta.v1, v2=mgDelta.v2, v3=mgDelta.v3)
node.SetAllPoints([p * mgDelta for p in op.GetAllPoints()])
node.Message(c4d.MSG_UPDATE)
# Exit when node has no normal tag.
normalTag = node.GetTag(c4d.Tnormal)
if not isinstance(normalTag, c4d.NormalTag):
doc.EndUndo()
return None
# Transform the normals in the normal tag.
newNormals = [n * mgOrientationDelta for n in ReadNormalTag(normalTag)]
doc.AddUndo(c4d.UNDOTYPE_CHANGE, normalTag)
WriteNormalTag(normalTag, newNormals, normalize=True)
doc.EndUndo()
def main() -> None:
"""Entry point that runs the code when there is an active object.
"""
if not isinstance(op, c4d.BaseObject):
c4d.gui.MessageDialog("Please select an object.")
return None
# Get all the ancestor nodes of the selected node.
ancestors, parent = [], op.GetUp()
while parent:
ancestors.insert(0, parent)
parent = parent.GetUp()
# Build and show a popup menu for all ancestors with their name and icon.
bc, idBase = c4d.BaseContainer(), 1000
for i, node in enumerate(ancestors):
bc.InsData(idBase + i, f"{node.GetName()}&i{node.GetType()}&")
res = c4d.gui.ShowPopupDialog(None, bc, c4d.MOUSEPOS, c4d.MOUSEPOS)
# The user did abort the popup.
if res == 0:
return
# Carry out the modification of the axis of op to target.
target = ancestors[res - idBase]
TransferAxisTo(op, target)
c4d.EventAdd()
if __name__ == '__main__':
main()