geometry_sds_weights¶
Demonstrates how to read and write data of an SDS weight tag.
geometry_sds_weights.py¶
"""Demonstrates how to read and write data of an SDS weight tag.
"""
__author__: str = "Ferdinand Hoppe"
__copyright__: str = "Copyright (C) 2026 MAXON Computer GmbH"
__date__: str = "02/09/2026"
__license__: str = "Apache-2.0 License"
__version__: str = "2026.2.0"
from mxutils import CheckType
import c4d
import pprint
op: c4d.BaseObject | None # The currently active object.
doc: c4d.documents.BaseDocument # The currently active document.
def CreateSdsSetup(name: str = "") -> c4d.BaseTag:
"""Creates a simple SDS setup with a cube polygon child and an SDS weight tag, and returns the
weight tag.
Not relevant for the example, and therefore abstracted into a function.
"""
# Construct a cube polygon object and set its geometry.
polyCube: c4d.PolygonObject = CheckType(c4d.PolygonObject(8, 6))
polyCube.SetName(name)
points: list[c4d.Vector] = [
c4d.Vector(-50, -50, -50), c4d.Vector(50, -50, -50),
c4d.Vector(50, 50, -50), c4d.Vector(-50, 50, -50),
c4d.Vector(-50, -50, 50), c4d.Vector(50, -50, 50),
c4d.Vector(50, 50, 50), c4d.Vector(-50, 50, 50)
]
polyCube.SetAllPoints(points)
polygons: list[c4d.CPolygon] = [
c4d.CPolygon(0, 1, 2, 3), c4d.CPolygon(5, 4, 7, 6), c4d.CPolygon(4, 0, 3, 7),
c4d.CPolygon(1, 5, 6, 2), c4d.CPolygon(3, 2, 6, 7), c4d.CPolygon(4, 5, 1, 0)
]
for i, poly in enumerate(polygons):
polyCube.SetPolygon(i, poly)
# Create the SDS object and parent the cube under it.
sdsObject: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Osds))
sdsObject[c4d.SDSOBJECT_SUBEDITOR_CM] = 4
sdsObject[c4d.SDSOBJECT_SUBRAY_CM] = 4
sdsObject.SetName(f"SDS - {name}")
polyCube.InsertUnder(sdsObject)
doc.InsertObject(sdsObject)
# Add the SDS weight tag to the polygon object.
phongTag: c4d.BaseTag = CheckType(sdsObject.MakeTag(c4d.Tphong))
weightTag: c4d.BaseTag = CheckType(polyCube.MakeTag(c4d.Tsds))
polyCube.Message(c4d.MSG_UPDATE)
doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_NONE)
c4d.EventAdd()
return weightTag
def CloneSdsSetup(tag: c4d.BaseTag, name: str) -> c4d.BaseTag:
"""Clones the SDS setup of the given SDS object and returns the weight tag of the cloned setup.
Not relevant for the example, and therefore abstracted into a function.
"""
if not isinstance(tag, c4d.BaseTag) or tag.GetType() != c4d.Tsds:
raise TypeError("The provided tag must be an SDS weight tag.")
# Get the object of the tag and go up one level when the parent of the object is an SDS object.
# Not really an exact science but makes sense for most SDS setups.
host: c4d.BaseObject = tag.GetObject()
if host.GetUp() and host.GetUp().GetType() == c4d.Osds:
host = host.GetUp()
# Clone the host object, update its name and position, and insert it into the document.
clone: c4d.BaseObject = CheckType(host.GetClone(c4d.COPYFLAGS_NONE))
clone.SetName(f"SDS - {name}")
clone.SetMg(host.GetMg() * c4d.utils.MatrixMove(c4d.Vector(150, 0, 0)))
host.GetDocument().InsertObject(clone)
# Get the weight tag from the cloned setup and return it.
tag: c4d.BaseTag = (CheckType(clone.GetDown().GetTag(c4d.Tsds))
if clone.GetType() == c4d.Osds else
CheckType(clone.GetTag(c4d.Tsds)))
return tag
def main() -> None:
"""Called by Cinema 4D when the script is being executed.
"""
# Get the SDS tag from the active object or create a new setup when there is no active object
# or no SDS tag on the active object.
c4d.ClearPythonConsole()
sdsTag: c4d.BaseTag | None = op.GetTag(c4d.Tsds) if op else None
if sdsTag is None:
sdsTag = CreateSdsSetup("Point Weights")
# Initialize the tag (important to do this before accessing tag data) and get its data.
sdsTag.InitFromObject()
sdsData: dict = sdsTag.GetTagData()
print("Initial tag data:")
pprint.pprint(sdsData)
# {'points': 8,
# 'pointweight': [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
# 'polys': 6,
# 'polyweight': [ {'a': 0.0, 'b': 0.0, 'c': 0.0, 'd': 0.0},
# {'a': 0.0, 'b': 0.0, 'c': 0.0, 'd': 0.0},
# {'a': 0.0, 'b': 0.0, 'c': 0.0, 'd': 0.0},
# {'a': 0.0, 'b': 0.0, 'c': 0.0, 'd': 0.0},
# {'a': 0.0, 'b': 0.0, 'c': 0.0, 'd': 0.0},
# {'a': 0.0, 'b': 0.0, 'c': 0.0, 'd': 0.0}]
# Now lets write all point weights at one by setting them to a gradient from 0.0 to 1.0. Here
# we write a weight for each vertex of the the object, eight when we are using the cube from
# this example.
pointCount: int = sdsData.get("points", 0)
pointWeights: list[float] = [c4d.utils.RangeMap(
i, 0, pointCount - 1, 0.0, 1.0, True) for i in range(pointCount)]
sdsTag.SetAllPointWeights(pointWeights)
# We can also set point weights individually, for example to set the first vertex to 0.5:
sdsTag.SetPointWeight(0, 0.5)
# Now let's clone the object and do the same for edge weights.
otherTag: c4d.BaseTag = CloneSdsSetup(sdsTag, "Edge Weights")
otherTag.InitFromObject()
sdsData: dict = otherTag.GetTagData()
polyCount: int = sdsData.get("polys", 0)
f: float = 1 / polyCount
# Set again all weights at once. But due to how edges work, this can lead to funky results,
# as each edge could be shared by up to two polygons, and when we set the weight X for
# edge a of polygon 0 and the weight Y for edge b of polygon 1 (and a and b are actually the
# same edge), the competing weights must be normalized.
polyWeights: list[dict[str, float]] = [
{'a': i * f, 'b': i * f, 'c': i * f, 'd': i * f} for i in range(polyCount)
]
# otherTag.SetAllPolyWeights(polyWeights)
# It is more sensible to set weights for edges individually. Note that for triangles, we have to
# set A, B, and D. I.e., it deviates from how triangles are stored themselves in a CPolygon,
# where A, B, C, are used and D is just a duplicate of C.
pid: int = 0 # The polygon index we want to set edge weights for.
for edgeIdx in range(4):
weight: float = 1.0
otherTag.SetPolyEdgeWeight(pid, edgeIdx, weight, validate=True)
c4d.EventAdd()
if __name__ == '__main__':
main()