Setting coordinates for selected faces (Python - Beginner)



  • Hi all,

    Just starting out with Python Coding in C4D.

    I have watched some tutorials and have basic code that can zero out objects etc.

        objList = doc.GetActiveObjects(True)
        for obj in objList:
            obj.SetRelPos(c4d.Vector(0,0,0))
        c4d.EventAdd()
    
    • I am looking to take the current selection of polygons.

    • set the current coordinate manager to world mode

    • set the size of them in a particular axis (y) to zero

    Thanks in advance for any help you can offer.



  • Hi,

    the relative/absolute methods (.e.g. SetRelPos in your snippet), are special case convenience methods. The manipulation of the position, orientation and scale of objects (and their vertices/points) is normally done via their global and local matrices, which relate to the the concepts of global and local coordinates. To set the position of an object in global space you would do:

    mg = obj.GetMg() # Get the global matrix.
    mg.off = c4d.Vector(3.1415, 42,  2.7182) # the new global position
    obj.SetMg(mg) # Write the global matrix back
    

    Setting the position in local coordinates would work analogously, but you would use GetMl and SetMl.

    For your questions:

    1. Selections are encapsulated in the type c4d.BaseSelect. A c4d.PolygonObject has the method GetPolygonS which will return a BaseSelect for the currently selected polygons of that object.
    2. You cannot manipulate the coordinate manager. Cinema's Python API does not work like the old Python API for MAYA for example did work, where you could/would only imitate in code what you would do in the app. You will have to use more abstract concepts here. I described local and global matrices above.
    3. I would have to unfold multiple things to give you an answer on that, that is meaningful for a beginner. My advice would be sticking to manipulating all points and polygons first and learning the fundamental concepts of matrices, points and polygons. You can get all points of a with GetAllPoints and all polygons with GetAllPolygons when your object is a c4d.PolygonObject, i.e. is an editable object. You can come back, when you think you have mastered these topics (or earlier when you get stuck ;)).

    Cheers,
    zipit



  • Thank you for replying.

    I spent the day looking at your code and other examples. Sad to say it seems I am in quite a bit in over my head.
    I was just trying to scale some selected polygons in the Y-Axis... I never knew it would be this complicated.

    It is indeed rather disheartening.. are there some beginner examples? I have seen many that try to move selected objects.. but hardly any with selected components + scaling.

    Appreciate any small example code.. hopefully I can deduce my way around it. 😰



  • @SteveLim

    I found another thread where the code seems to work except its for World Space and also it affects all points as opposed to selected polys as in my use case.

    I tried my luck to modify the code using GetMl instead.. but no dice =)

    def set_Y_vector(obj, value):
    
        oldLocalPoints = obj.GetAllPoints()
        obj_mat = obj.GetMg()
        inv_obj_mat = ~obj_mat
    
        # Non-preallocation penalty is neglectable 
        newLocalPoints = [] 
        for p in oldLocalPoints:
            # get the world position of the current point
            worldpoint = obj_mat * p
           # set the local position on the new point by setting the y-coordinate to the desired value and transform by the inverted matrix
            newLocalPoint = inv_obj_mat * c4d.Vector(worldpoint[0], value, worldpoint[2] )
            # just append
            newLocalPoints.append(newLocalPoint)
    
        # set the new points
        obj.SetAllPoints(newLocalPoints)
        
        # notify Cinema 
        obj.Message (c4d.MSG_UPDATE) 
        c4d.EventAdd()
    


  • Hi,

    It would take me longer to dissect your script and make the answer both readable and correct, so I wrote some piece of narrative code instead. Start reading in the main function and then move on to part 1, 2 and 3.

    Cheers,
    zipit

    """ Run this in the Script Manager.
    
    You will need an editable polygon object selected with a point selection to make this
    script work. The script has three parts, which have to be commented  and uncommented
    in the main() function, all dealing with different aspects and levels of
    understanding of point manipulation.
    """
    
    import c4d
    import math
    
    def part_1():
        """ Deals with the basics.
        """
        # Points in a PointObject (and by inheritance also in a PolygonObject)
        # are defined in local coordinates. Unless you want to relate the points
        # to something in world space, there is no need to convert them.
        points = op.GetAllPoints()
    
        # We move all points point_index * 2.0 units up on the y-axis, so 
        # the point with the index 0 is not being moved at all and the 
        # point with the index 10 for example is being moved by 20 units on 
        # the y axis.
        points = [p + c4d.Vector(0, i * 2., 0) for i, p in enumerate(points)]
    
        # The same could also be written as:
        # for i, p in enumerate(points):
        #     points[i] += c4d.Vector(0, i * 2., 0)
    
        # Write the points back, update the object and tell Cinema that we have
        # modified something in the scene graph.
        op.SetAllPoints(points)
        op.Message(c4d.MSG_UPDATE)
        c4d.EventAdd()
    
    def part_2():
        """ Deals with matrices.
        """
        # Shoving around points manually can be useful, but has its limits when
        # it comes to rotating and scaling points. Here is where matrices come
        # into play.
    
        # We can construct our matrices manually using the cross product (which
        # is helpful when constructing a matrix for some kind of normal), but for
        # now we will just use one of the convenience methods provided by
        # c4d.utils.
    
        # MatrixRotY returns a rotation Matrix that rotates around the y-axis.
        # The input is in radians, so QUARTER_PI would be 45°.
        transform = c4d.utils.MatrixRotY(math.pi * .25)
    
        # This is just a normal matrix, so we can pile some more stuff onto it.
        # Also translate the points by 100 units on the y axis.
        transform.off = c4d.Vector(0, 100, 0)
        # And scale them by a factor of .5 on the z-axis
        transform.v3 *= .5
    
        # Now we just apply our transform by multiplying each point by the 
        # matrix. Applying the inverse of a matrix will do the inverse of
        # each aspect of the transform. So ~transform would scale z by the
        # factor 2, translate by -100 on the y axis and rotate by -45° on 
        # the y-axis. From which follows that p * transform * ~transform = p
    
        points = [p * transform for p in op.GetAllPoints()]
    
        # And we write our points back just like the last time.
        op.SetAllPoints(points)
        op.Message(c4d.MSG_UPDATE)
        c4d.EventAdd()
    
    def part_3():
        """ Deals with selections.
        """
        # A transform we will apply and the points.
        transform = c4d.utils.MatrixRotY(math.pi * .25)
        points = op.GetAllPoints()
    
        # Selections, i.e. BaseSelect are a bit weird in Cinema as they do not store
        # only the selected elements, but also the not selected elements and are
        # also boundless.
    
        # Get the BaseSelect for all selected points in our object.
        selection = op.GetPointS()
    
        # The documentation is not correct on GetAll(), it will not return all
        # selected elements, but all elements, selected or not, as booleans.
        state = selection.GetAll(len(points))
        print "selection state:", state
    
        # Now we can sort out our selected ids. We go over all values in
        # state and only keep the indices of elements where the state is ``True``.
        selected = [i for i, value in enumerate(state) if value]
        print "selected point ids:", selected
    
        # With these applying our transform only to a subset of points is
        # trivial.
        for index in selected:
            points[index] *= transform
    
        # And we write our points back just like the last time.
        op.SetAllPoints(points)
        op.Message(c4d.MSG_UPDATE)
        c4d.EventAdd()
    
    
    def main():
        """
        """
        # Terminate the script when the active object is not a PolygonObject
        # the variable op is predefined as the selected/active objects in a
        # Script Manager script.
        if not isinstance(op, c4d.PolygonObject):
            msg = "Requires a PolygonObject. Received: {type}"
            raise TypeError(msg.format(type=type(op)))
    
        # Once you have red and executed one part, comment the current one
        # and uncomment the next one.
        
        part_1()
        # part_2()
        # part_3()
    
    if __name__ == "__main__":
        main()
    


  • @zipit

    Thank you so much for this Zipit. I hope that I did undertstand some of the points made to an extent. For the purposes of learning, I tried to modify part2 and part3 such that the selected points were only scaling on the local Z for example.

    The code works however I had to do silly things - hack transforms of rotation and translation to zero manually to get them to play nice. (*hides). I suspect I am supposed to call direct the 'utils' for positioning directly instead of RotY first then inheriting it and setting transform.off and transform.v3 later.

    Utimately, I am trying to get a bunch of polygons (or points in your case) to be coplanar. My idea was to scale them to zero in their local Z axis (the selection's normal not the objects local Z). My current code is working except it is scaling the points to the local objects Z=0 insteal of the axis of the selection's normal .

    In my original post. I normally do this via GUI using a custom workplane set to selection whereby I can simple set size in Y to zero. The net effect is the same. ( I know I cannot do this so simply as the coordinate manager cannot be accessed directly)

    scale to 0

    
    def part_3():
    
        transform = c4d.utils.MatrixRotY(math.pi * 0)
        transform.off = c4d.Vector(0, 0, 0)
        transform.v3 *= 0
        points = op.GetAllPoints()
    
        selection = op.GetPointS()
        state = selection.GetAll(len(points))
        print "selection state:", state
    
        selected = [i for i, value in enumerate(state) if value]
        print "selected point ids:", selected
    
        for index in selected:
            points[index] *= transform
    
        op.SetAllPoints(points)
        op.Message(c4d.MSG_UPDATE)
        c4d.EventAdd()
    
    
    def main():
    
        if not isinstance(op, c4d.PolygonObject):
            msg = "Requires a PolygonObject. Received: {type}"
            raise TypeError(msg.format(type=type(op)))
    
    
        part_3()
    
    if __name__ == "__main__":
        main()
    


  • Hi,

    you would have to calculate the mean point normal and the mean position of your points to do what you want to do. While the latter is trivial, the former unfortunately requires some work in Cinema. There are normals related tags and the method CreatePhongNormals, but they depend on the presence of a phong tag. So you might have to compute the point normals manually.

    With these you can construct a transform matrix, the mean position becomes the offset and with the mean point normal you can construct the three axis of your matrix. Then you would have to multiply each point with that transform matrix, zero out the appropriate component of each point and transform the point back using the inverse matrix. The component you have to zero out / scale down depends on the way you constructed the matrix from the mean point normal. If you constructed your matrix with v3 (the z-axis) facing along the normal, it would be the z-axis for example.

    Edit: Just for completeness - you could also try to find the (multi)linear regression plane of your points - and I am not quite sure right now if that would be faster or slower than computing all these normals (probably a little bit faster) -, but that would probably be an unusually math heavy way to do it.

    Cheers,
    zipit



  • Thank you for your reply. It seems the one thing I chose to try out is too big a mountain to climb for the forseeable future. I will keep at it and try to pick up snippets here and there from other useful threads.

    Thanks again for your patience and time.



  • You could also retrieve the current matrix of the gizmo with BaseObject.GetModelingAxis.

    Cheers,
    Maxime


Log in to reply