Flat UV Projection in Python
-
Hello!
I'm creating a polygon object in an ObjectData plugin. I would like to layout its UVs with Flat projection (similar to BodyPaint's Flat Projection button). I found this post with code taken from this post on the forum. This seems to be what I need, but it's old and I ran into some issues with methods unavailable to the Python API. Here is my port of the code to Python:import c4d,math #This code creates the equivalent to the flat mapping button in the BP UV options #Define a value macro to be used several times later on SIGDIG=5.0 def TrimDecimal(num, digits): n = None n = num * math.pow(10.0, digits) n = math.copysign(1, n) * abs(math.floor(n + 0.5)) return n / math.pow(10.0, digits) def FrontalMapUVs(op, mg): if op is None: return false numFaces = op.GetPolygonCount() numVerts = op.GetPointCount() pVerts = op.GetPointW() #GetPointW doesn't exist in the Python API pPolys = op.GetPolygonW() pUVTag = op.MakeVariableTag(c4d.Tuvw, numFaces) if pUVTag is None: return false pUVHndl = pUVTag.GetDataAddressW() #GetDataAddressW doesn't exist in the Python API if pUVHndl is None: return false lenxinv, lenyinv, lenzinv = None, None, None vMin = c4d.Vector(100000.0) vMax = c4d.Vector(-100000.0) for ndx in range(numVerts): pt = pVerts[ndx] * mg #pVerts won't work because GetPointW doesn't exist if pt.x < vMin.x: vMin.x = pt.x if pt.x > vMax.x: vMax.x = pt.x if pt.y < vMin.y: vMin.y = pt.y if pt.y > vMax.y: vMax.y = pt.y if pt.z < vMin.z: vMin.z = pt.z if pt.z > vMax.z: vMax.z = pt.z mapSize = c4d.Vector(abs(vMax.x - vMin.x),abs(vMax.y - vMin.y),abs(vMax.z - vMin.z)) mapCenter = c4d.Vector(vMin.x+(mapSize.x*0.5),vMin.y+(mapSize.y*0.5),vMin.z+(mapSize.z*0.5)) if mapSize.x != 0.0: lenxinv = 1.0 / mapSize.x else: lenxinv = 0.0 if mapSize.y != 0.0: lenyinv = 1.0 / mapSize.y else: lenyinv = 0.0 if mapSize.z != 0.0: lenzinv = 1.0 / mapSize.z else: lenzinv = 0.0 # Walk the list of polygons and map the UVs for ndx in range(numFaces): """ Not sure what to do here as UVWStruct & UVWTag.Get do not exist in the Python API uvw = c4d.UVWStruct() pUVTag.Get(pUVHndl, ndx, uvw) """ pt_a = (pVerts[pPolys[ndx].a] - mapCenter) + mg.off pt_b = (pVerts[pPolys[ndx].b] - mapCenter) + mg.off pt_c = (pVerts[pPolys[ndx].c] - mapCenter) + mg.off pt_d = (pVerts[pPolys[ndx].d] - mapCenter) + mg.off uvw.a.x = TrimDecimal((pt_a.x*lenxinv)+0.5, SIGDIG) uvw.a.y = TrimDecimal((-pt_a.y*lenyinv)+0.5, SIGDIG) uvw.b.x = TrimDecimal((pt_b.x*lenxinv)+0.5, SIGDIG) uvw.b.y = TrimDecimal((-pt_b.y*lenyinv)+0.5, SIGDIG) uvw.c.x = TrimDecimal((pt_c.x*lenxinv)+0.5, SIGDIG) uvw.c.y = TrimDecimal((-pt_c.y*lenyinv)+0.5, SIGDIG) uvw.d.x = TrimDecimal((pt_d.x*lenxinv)+0.5, SIGDIG) uvw.d.y = TrimDecimal((-pt_d.y*lenyinv)+0.5, SIGDIG) """ commented out because of UVWStruct missing above """ #pUVTag.Set(pUVHndl, ndx, uvw) return true if __name__=='__main__': FrontalMapUVs(op,op.GetMg()) #I'm sending the selected object's global matrix, but I'm not sure if it's correct to do.
I noted some of the issues in the code's comments, but to sum up, I couldn't get it to work because these were missing in Python:
- PolygonObject.GetPointW: Gets the start of the writable points array.
- UVWTag.GetDataAddressW: Gets a handle to the writable UVW data.
- UVWTag.Get: Gets the UVW coordinates for a polygon.
- UVWTag.Set: Sets the UVW coordinates for a polygon.
- UVWStruct: Holds UVW tag variable coordinates data.
I also didn't know which Global Matrix I should send to the
FrontalMapUVs
function. I'd assume it wasop
, but it seems strange for that to be a parameter if it's already acceptingop
.If there are no Python equivalents to these missing C++ methods, is there another way to do Flat UV Projection in Python with an ObjectData plugin?
Thank you!
-
Hi,
your code looks a bit overly complicated and c-ish. There might very well be a reason for all the gymnastics that code does, but at least I am not able to see that reason. Here is how I would do it in Python.
Cheers,
zipit"""Simple example for generating uvw data. """ import c4d # To implement other projections, you have just to implement another # projection function ... def planar_projection(frame, points): """Projects points into the XY plane. Args: frame (c4d.Matrix): The projection frame. points (list[c4d.Vector]): The points to project. Returns: list[c4d.Vector]:The projected points. """ points = [p * frame for p in points] return [c4d.Vector(p.x, p.y, 0) for p in points] def remap_to_texcoords(points): """Remaps a list of points to the common uv half range. The function assumes the x and y coordinates of the points to be the relevant coordinates and the points are being remapped based on the XY bounding rectangle. Args: points (list[c4d.Vector]): The points to remap. Returns: list[c4d.Vector]:The remapped points. """ # Get the texture coordinates bounding rectangle. xmin, xmax = float("inf"), float("-inf") ymin, ymax = float("inf"), float("-inf") for p in points: xmin = p.x if p.x < xmin else xmin xmax = p.x if p.x > xmax else xmax ymin = p.y if p.y < ymin else ymin ymax = p.y if p.y > ymax else ymax # Remap the points from object coordinates to texture coordinates # in the common uv half range. points = [c4d.Vector(c4d.utils.RangeMap(p.x, xmin, xmax, 0., 1., True), c4d.utils.RangeMap(p.y, ymin, ymax, 0., 1., True), 0) for p in points] return points def create_uvw_tag(node, frame, f): """Creates an uvw tag for an object, a frame and a projection. Args: node (c4d.PolygonObject): The node. frame (c4d.Matrix): The texture projection frame. f (callable): The projection function. Returns: c4d.UVWTag: The uvw tag. """ # Project the points of the node. points = f(frame, node.GetAllPoints()) # Convert these points to texture space. uv_points = remap_to_texcoords(points) # Create the actual tag polygons = node.GetAllPolygons() uvw_tag = c4d.UVWTag(len(polygons)) # We just go over all polygons and with that index our projected and # remapped points into the uvw tag. for i, cpoly in enumerate(polygons): a, b = uv_points[cpoly.a], uv_points[cpoly.b] c, d = uv_points[cpoly.c], uv_points[cpoly.d] uvw_tag.SetSlow(i, a, b, c, d) return uvw_tag def main(): """ """ if not isinstance(op, c4d.PolygonObject): msg = "Please select a polygon object." raise TypeError(msg) tag = create_uvw_tag(op, c4d.Matrix(), f=planar_projection) op.InsertTag(tag) c4d.EventAdd() if __name__ == "__main__": main()
-
Thank you very much @zipit ! That works great.