Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Flat UV Projection in Python

    Cinema 4D SDK
    python
    2
    3
    785
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • ?
      A Former User
      last edited by A Former User

      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 was op, but it seems strange for that to be a parameter if it's already accepting op.

      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!

      1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand
        last edited by ferdinand

        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()
        
        

        MAXON SDK Specialist
        developers.maxon.net

        ? 1 Reply Last reply Reply Quote 1
        • ?
          A Former User @ferdinand
          last edited by A Former User

          Thank you very much @zipit ! That works great.

          1 Reply Last reply Reply Quote 0
          • First post
            Last post