Navigation

    • Register
    • Login
        No matches found
    • Search
    1. Home
    2. ferdinand
    ferdinand

    ferdinand

    @ferdinand

    499
    Reputation
    1440
    Posts
    545
    Profile views
    6
    Followers
    0
    Following
    Joined Last Online

    • Profile
    • More
      • Following
      • Followers
      • Topics
      • Posts
      • Best
      • Groups
    ferdinand Follow

    Best posts made by ferdinand

    RE: Welcome Mr. Hoppe

    Hi,

    thanks for the kind words both from Maxon and the community. I am looking forward to my upcoming adventures with the SDK Team and Cinema community.

    Cheers,
    Ferdinand

    posted in Maxon Announcements •
    RE: Modified Pop Up Menu

    Hi,

    as @Cairyn said the problem is unreachable code. I also just saw now that you did assign the same ID to all your buttons in your CreateLayout(). Ressource and dialog element IDs should be unique. I would generally recommend to define your dialogs using a resource, but here is an example on how to do it in code.

    BUTTON_BASE_ID = 1000
    BUTTON_NAMES = ["Button1", "Button2", "Button3", "Button4", "Button5"]
    BUTTON_DATA = {BUTTON_BASE_ID + i: name for i, name in enumerate(BUTTON_NAMES)}
    
    class MyDialog(gui.GeDialog):
    
        def CreateLayout(self):
            """
            """
            self.GroupBegin(id=1013, flags=c4d.BFH_SCALEFIT, cols=5, rows=4)
            for element_id, element_name in BUTTON_DATA.items():
                self.AddButton(element_id, c4d.BFV_MASK, initw=100, 
                               name=element_name)
            self.GroupEnd()
            return True
    
        def Command(self, id, msg):
            """
            """
            if id == BUTTON_BASE_ID:
                print "First button has been clicked"
            elif id == BUTTON_BASE_ID + 1:
                print "Second button has been clicked"
            # ...
            if id in BUTTON_DATA.keys(): # or just if id in BUTTON_DATA
                self.Close()
            return True
    
    posted in Cinema 4D SDK •
    RE: Reading proper decimal values on lower numbers?

    Hi,

    that your script is not working has not anything to do with pseudo decimals, but the fact that you are treating numbers as strings (which is generally a bad idea) in a not very careful manner. When you truncate the string representation of a number which is represented in scientific notation (with an exponent), then you also truncate that exponent and therefor change the value of the number.

    To truncate a float you can either take the floor of my_float * 10 ** digits and then divide by 10 ** digits again or use the keyword round.

    data = [0.03659665587738824,
            0.00018878623163019122,
            1.1076812650509394e-03,
            1.3882258325566638e-06]
    
    for n in data:
        rounded = round(n, 4)
        floored = int(n * 10000) / 10000
        print(n, rounded, floored)
    
    0.03659665587738824 0.0366 0.0365
    0.00018878623163019122 0.0002 0.0001
    0.0011076812650509394 0.0011 0.0011
    1.3882258325566637e-06 0.0 0.0
    [Finished in 0.1s]
    

    Cheers
    zipit

    posted in General Talk •
    RE: GetAllTextures from materials only

    Hi,

    sorry for all the confusion. You have to pass actual instances of objects. The following code does what you want (and this time I actually tried it myself ;)).

    import c4d
    
    def main():
        """
        """
        bc = doc.GetAllTextures(ar=doc.GetMaterials())
        for cid, value in bc:
            print cid, value
    
    if __name__=='__main__':
       main()
    

    Cheers,
    zipit

    posted in Cinema 4D SDK •
    RE: How to fillet a ObjectData edge in Python?

    Hi,

    the nature of your problem remains a bit unclear to me. You talk about polygonal data and beveling edges, which I would interpret as the question for bevelling in a network of curves, .e.g. a polygonal topology, but you show the image of a curve and also your code operates in a plane. I assume you want interpret your mesh as a series of cross-sections/curves?

    That aside, beveling (either curves or curves in surfaces, i.e. edges) is an application of interpolation and can therefor implemented in various forms, here are some approaches:

    1. Piecing in arc segments. This what you are basically trying to do from the looks of your code. You normally would properly define a series of transforms (i.e. "matrices" in Cinema) to transform a base vector and by that construct the vertices making up the arc segment. This solution is not very suited for a data of intersecting curve networks, i.e. surfaces, it works better in something like a 2D Vector app.

    2. Than there are multiple ways to construct a smooth curve over a series points. The easiest one are Chaikin's Curves which also have spawned a whole group of algorithms all following more or less the same one fourth three fourth idea and some of them can also be applied to meshes.

    3. The next obvious candidates would be quadratic or cubic polynomials/curves. They can the can also be viewed as a combination of linear interpolations. This is called De Casteljau's algorithm. To get from multiple curves segments to splines, you just piece them together (there are multiple ways to do this). But in you case of beveling you probably will only need a single curve. Depending on how deep you venture into this topic this reaches from splines and curves (.e.g. a B-Spline or a Bezier spline) to surfaces (e.g. a NURBS surface).

    4. The last one, and that is the one Cinema probably uses for its edge tool is just Subdivision Surfaces. There are also multiple algorithms of varying complexity, but classical Catmull-Clark SDS have very nice properties (producing only quads for example) and are very easy to implement. The problem here is how to restrict the subdivision part of the algorithm so that you only generate new edges along the edge loop and not perpendicular to it and then come up with the correct modified weights for the smoothing.

    Its all a bit vague, but your question is very broad, so I thought this might help.

    edit: Forgot this, but should have put this first - Implementing a bevel tool is not trivial. Covering the default case is not that hard, but covering all the corner cases is, especially when you want to maintain secondary mesh attributes like texture coordinates. I would not go for reinventing the wheel here. Is there a reason why you do not want to use Cinema's edge bevel? Does it not work with SendModellingCommand?

    Cheers,
    zipit

    posted in General Talk •
    RE: Object materials won't show up in final render

    Hi,

    you use GetActiveDocument() in a NodeData environment. You cannot do this, since nodes are also executed when their document is not the active document (while rendering for example - documents get cloned for rendering).

    Cheers
    zipit

    posted in Cinema 4D SDK •
    RE: Moving a Point Along an Arc by Distance from another Point

    Hoi,

    @blastframe said in Moving a Point Along an Arc by Distance from another Point:

    • In your code comment you mention spherical interpolation between the arc segment. Is that the same as the quadratic bezier interpolation from the example you provided (code below)? I think that's also called slerp?

    Yeah, slerp is a synonym for spherical linear interpolation. While linear interpolation, a.k.a. lerp, gives you points on the line segment spanned by its two input vectors, a spherical linear interpolation gives you points on the arc segment spanned between the unit vectors of the two input vectors. Cinema does not have a slerp for vectors for some reason, only one for quaternions. The whole interpolation and interpolation naming situation in Cinema's classic API is a huge mess IMHO. So you would have to write your own slerp. And slerp is not directly related to splines, only in the sense that you will need it when you want to linearly place points on that spline, as you then have to do the arc-length parametrization of that spline for which you will need slerp.

    • How do I get the points to stop at the limits of the arc? In this case, it would seem to be limiting the angle to 0 and math.pi, but what if the start & end angles are different?

    Basically the same way you did it in your code. The computed angle is an angle in radians. So to limit it to let's say [0°, 180°] you would have to clamp it to [0 rad,π rad]. There is also the problem that angles work the other way around in relation to the circle functions sine and cosine and your example. They "start on the right side" of the unit circle and then go counter clockwise. Which is why my code flips things around. Getting back to "your setup" should just a matter of inverting the angle, i.e. -theta instead of theta, and then adding π, i.e. 180°.

    • If I'm not to use c4d.Vector.GetDistance, how would you recommend I get the geodesic norm? I found modules online like geopy, but I'd rather do it with standard Python.

    Its the same thing in green. You get the Euclidean norm, a.k.a the "length" of your vectors, implying the radius of the circle they are sitting on. Then you can compute the angle spanned between them with the dot product. Finally you put this into the relation shown above, solve for the arc length and you are done. And to be clear, Geodesic norm is just a fancy name for saying the distance between two points on a sphere, while the Euclidean norm is the distance in a plane.

    Cheers,
    zipit

    posted in General Talk •
    RE: More Examples for GeUserArea?

    Hi,

    you have to invoke AddUserArea and then attach an instance of your implemented type to it. Something like this:

    my_user_area = MyUserAreaType()
    self.AddUserArea(1000,*other_arguments)
    self.AttachUserArea(my_user_area, 1000)
    

    I have attached an example which does some things you are trying to do (rows of things, highlighting stuff, etc.). The gadget is meant to display a list of boolean values and the code is over five years old. I had a rather funny idea of what good Python should look like then and my attempts of documentation were also rather questionable. I just wrapped the gadget into a quick example dialog you could run as a script. I did not maintain the code, so there might be newer and better ways to do things now.

    Also a warning: GUI stuff is usually a lot of work and very little reward IMHO.

    Cheers
    zipit

    import c4d
    import math
    import random
    
    from c4d import gui
    
    # Pattern Gadget
    IDC_SELECTLOOP_CELLSIZE = [32, 32]
    IDC_SELECTLOOP_GADGET_MINW = 400
    IDC_SELECTLOOP_GADGET_MINH = 32
    
    class ExampleDialog(gui.GeDialog):
        """
        """
        def CreateLayout(self):
            """
            """
    
            self.Pattern = c4d.BaseContainer()
            for i in range(10):
                self.Pattern[i] = random.choice([True, False])
            self.PatternSize = len(self.Pattern)
    
            self.gadget = Patterngadget(host=self)
            self.AddUserArea(1000, c4d.BFH_FIT, 400, 32)
            self.AttachUserArea(self.gadget, 1000)
            return True
    
    class Patterngadget(gui.GeUserArea):
        """
        A gui gadget to modify and display boolean patterns.
        """
    
        def __init__(self, host):
            """
            :param host: The hosting BaseToolData instance
            """
            self.Host = host
            self.BorderWidth = None
            self.CellPerColumn = None
            self.CellWidht = IDC_SELECTLOOP_CELLSIZE[0]
            self.CellHeight = IDC_SELECTLOOP_CELLSIZE[1]
            self.Columns = None
            self.Height = None
            self.Width = None
            self.MinHeight = IDC_SELECTLOOP_GADGET_MINH
            self.MinWidht = IDC_SELECTLOOP_GADGET_MINW
            self.MouseX = None
            self.MouseY = None
    
        """------------------------------------------------------------------------
            Overridden methods
            --------------------------------------------------------------------"""
    
        def Init(self):
            """
            Init the gadget.
            :return : Bool
            """
            self._get_colors()
            return True
    
        def GetMinSize(self):
            """
            Resize the gadget
            :return : int, int
            """
            return int(self.MinWidht), int(self.MinHeight)
    
        def Sized(self, w, h):
            """
            Get the gadgets height and width
            """
            self.Height, self.Width = int(h), int(w)
            self._fit_gadget()
    
        def Message(self, msg, result):
            """
            Fetch and store mouse over events
            :return : bool
            """
            if msg.GetId() == c4d.BFM_GETCURSORINFO:
                base = self.Local2Screen()
                if base:
                    self.MouseX = msg.GetLong(c4d.BFM_DRAG_SCREENX) - base['x']
                    self.MouseY = msg.GetLong(c4d.BFM_DRAG_SCREENY) - base['y']
                    self.Redraw()
                    self.SetTimer(1000)
            return gui.GeUserArea.Message(self, msg, result)
    
        def InputEvent(self, msg):
            """
            Fetch and store mouse clicks
            :return : bool
            """
            if not isinstance(msg, c4d.BaseContainer):
                return True
            if msg.GetLong(c4d.BFM_INPUT_DEVICE) == c4d.BFM_INPUT_MOUSE:
                if msg.GetLong(c4d.BFM_INPUT_CHANNEL) == c4d.BFM_INPUT_MOUSELEFT:
                    base = self.Local2Global()
                    if base:
                        x = msg.GetLong(c4d.BFM_INPUT_X) - base['x']
                        y = msg.GetLong(c4d.BFM_INPUT_Y) - base['y']
                        pid = self._get_id(x, y)
                        if pid <= self.Host.PatternSize:
                            self.Host.Pattern[pid] = not self.Host.Pattern[pid]
                            self.Redraw()
            return True
    
        def Timer(self, msg):
            """
            Timer loop to catch OnMouseExit
            """
            base = self.Local2Global()
            bc = c4d.BaseContainer()
            res = gui.GetInputState(c4d.BFM_INPUT_MOUSE,
                                    c4d.BFM_INPUT_MOUSELEFT, bc)
            mx = bc.GetLong(c4d.BFM_INPUT_X) - base['x']
            my = bc.GetLong(c4d.BFM_INPUT_Y) - base['y']
            if res:
                if not (mx >= 0 and mx <= self.Width and
                        my >= 0 and my <= self.Height):
                    self.SetTimer(0)
                    self.Redraw()
    
        def DrawMsg(self, x1, y1, x2, y2, msg):
            """
            Draws the gadget
            """
            # double buffering
            self.OffScreenOn(x1, y1, x2, y2)
            # background & border
            self.DrawSetPen(self.ColBackground)
            self.DrawRectangle(x1, y1, x2, y2)
            if self.BorderWidth:
                self.DrawBorder(c4d.BORDER_THIN_IN, x1, y1,
                                self.BorderWidth + 2, y2 - 1)
            # draw pattern
            for pid, state in self.Host.Pattern:
                x, y = self._get_rect(pid)
                self._draw_cell(x, y, state, self._is_focus(x, y))
    
        """------------------------------------------------------------------------
            Public methods
            --------------------------------------------------------------------"""
    
        def Update(self, cid=None):
            """
            Update the gadget.
            :param cid: A pattern id to toggle.
            """
            if cid and cid < self.Host.PatternSize:
                self.Host.Pattern[cid] = not self.Host.Pattern[cid]
            self._fit_gadget()
            self.Redraw()
    
        """------------------------------------------------------------------------
            Private methods
            --------------------------------------------------------------------"""
    
        def _get_colors(self, force=False):
            """
            Set the drawing colors.
            :return : Bool
            """
            self.ColScale = 1.0 / 255.0
            if self.IsEnabled() or force:
                self.ColBackground = self._get_color_vector(c4d.COLOR_BG)
                self.ColCellActive = c4d.GetViewColor(
                    c4d.VIEWCOLOR_ACTIVEPOINT) * 0.9
                self.ColCellFocus = self._get_color_vector(c4d.COLOR_BGFOCUS)
                self.ColCellInactive = self._get_color_vector(c4d.COLOR_BGEDIT)
                self.ColEdgeDark = self._get_color_vector(c4d.COLOR_EDGEDK)
                self.ColEdgeLight = self._get_color_vector(c4d.COLOR_EDGELT)
            else:
                self.ColBackground = self._get_color_vector(c4d.COLOR_BG)
                self.ColCellActive = self._get_color_vector(c4d.COLOR_BG)
                self.ColCellFocus = self._get_color_vector(c4d.COLOR_BG)
                self.ColCellInactive = self._get_color_vector(c4d.COLOR_BG)
                self.ColEdgeDark = self._get_color_vector(c4d.COLOR_EDGEDK)
                self.ColEdgeLight = self._get_color_vector(c4d.COLOR_EDGELT)
            return True
    
        def _get_cell_pen(self, state, _is_focus):
            """
            Get the color for cell depending on its state.
            :param state   : The state
            :param _is_focus : If the cell is hoovered.
            :return        : c4d.Vector()
            """
            if state:
                pen = self.ColCellActive
            else:
                pen = self.ColCellInactive
            if self.IsEnabled() and _is_focus:
                return (pen + c4d.Vector(2)) * 1/3
            else:
                return pen
    
        def _draw_cell(self, x, y, state, _is_focus):
            """
            Draws a gadget cell.
            :param x:       local x
            :param y:       local y
            :param state:   On/Off
            :param _is_focus: MouseOver state
            """
            # left and top bright border
            self.DrawSetPen(self.ColEdgeLight)
            self.DrawLine(x, y, x + self.CellWidht, y)
            self.DrawLine(x, y, x, y + self.CellHeight)
            # bottom and right dark border
            self.DrawSetPen(self.ColEdgeDark)
            self.DrawLine(x, y + self.CellHeight - 1, x +
                          self.CellWidht - 1, y + self.CellHeight - 1)
            self.DrawLine(x + self.CellWidht - 1, y, x +
                          self.CellWidht - 1, y + self.CellHeight - 1)
            # cell content
            self.DrawSetPen(self._get_cell_pen(state, _is_focus))
            self.DrawRectangle(x + 1, y + 1, x + self.CellWidht -
                               2, y + self.CellHeight - 2)
    
        def _get_rect(self, pid, offset=1):
            """
            Get the drawing rect for an array id.
            :param pid    : the pattern id
            :param offset : the pixel border offset
            :return       : int, int
            """
            pid = int(pid)
            col = pid / self.CellPerColumn
            head = pid % self.CellPerColumn
            return self.CellWidht * head + offset, self.CellHeight * col + offset
    
        def _get_id(self, x, y):
            """
            Get the array id for a coord within the gadget.
            :param x : local x
            :param y : local y
            :return  : int
            """
            col = (y - 1) / self.CellHeight
            head = (x - 1) / self.CellWidht
            return col * self.CellPerColumn + head
    
        def _is_focus(self, x, y):
            """
            Test if the cell coords are under the cursor.
            :param x : local x
            :param y : local y
            :return  : bool
            """
            if (self.MouseX >= x and self.MouseX <= x + self.CellWidht and
                    self.MouseY >= y and self.MouseY <= y + self.CellHeight):
                self.MouseX = c4d.NOTOK
                self.MouseY = c4d.NOTOK
                return True
            else:
                return False
    
        def _fit_gadget(self):
            """
            Fit the gadget size to the the array
            """
            oldHeight = self.MinHeight
            self.CellPerColumn = int((self.Width - 2) / self.CellWidht)
            self.Columns = math.ceil(
                self.Host.PatternSize / self.CellPerColumn) + 1
            self.MinHeight = int(IDC_SELECTLOOP_GADGET_MINH * self.Columns) + 3
            self.MinWidht = int(IDC_SELECTLOOP_GADGET_MINW)
            self.BorderWidth = self.CellWidht * self.CellPerColumn
            if oldHeight != self.MinHeight:
                self.LayoutChanged()
    
        def _get_color_vector(self, cid):
            """
            Get a color vector from a color ID.
            :param cid : The color ID
            :return    : c4d.Vector()
            """
            dic = self.GetColorRGB(cid)
            if dic:
                return c4d.Vector(float(dic['r']) * self.ColScale,
                                  float(dic['g']) * self.ColScale,
                                  float(dic['b']) * self.ColScale)
            else:
                return c4d.Vector()
    
    if __name__ == "__main__":
        dlg = ExampleDialog()
        dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=400, defaulth=400)
    
    posted in Cinema 4D SDK •
    RE: Python Generator Mimicking Cloner That Can Modify Individual Cloner Parameters (Except PSR)?

    Hi @bentraje,

    thank you for reaching out to us. As @Cairyn already said, there is little which prevents you from writing your own cloner. Depending on the complexity of the cloner, this can however be quite complex. Just interpolating between two positions or placing objects in a grid array is not too hard. But recreating all the complexity of a MoGraph cloner will be. It is also not necessary. Here are two more straight forward solutions:

    1. As I said in the last thread (the one you did link), MoGraph is perfectly capable of driving arbitrary parameters of an object. I overlooked in the old thread that you did ask what I meant with ""drive the attributes of an object or its material"", so the file cube_bend_mograph.c4d shows you how you can drive the strength parameter of a bend object with MoGraph. The difference I then made was that while you cannot drive such attributes directly via the particle (arrays), but you can let MoGraphs interpolation system take care of it. Please keep in mind that I am everything but an expert for MoGraph. There are probably more elegant solutions for all this, but all this is out of scope for this forum. Please contact technical support or refer to Cineversity for questions about MoGraph.
    2. One problem of MoGraph is that driving more than one arbitrary parameter of an object is a bit tedious. Since MoGraph's sole principle is interpolation and it can only interpolate on a single axis (between parameters), you will then have to start interpolating between two cloners to drive a second, third, fourth, etc. parameter. Here it would make sense to write a custom solution to have less complicated setups. Rather than rewriting a cloner, it would be however more sensible to just modify the cache of a cloner. This will only work when the cloner is in the "Instance Mode" "Instance", as otherwise the cache will only contain the instance wrappers (a bit confusing the naming here ^^). You could do this all inside a GVO, including building the cloner and its cache, but I did provide here a solution which relies on linking a cloner whose cache one wishes to modify. The idea is then simple, walk over all bend objects in the cache and modify their bend strength. You can find a scene file and the code below.

    Cheers,
    Ferdinand

    file: cube_bend_python.c4d
    code:

    """Example for modifying the cache of a node and returning it as the output
    of a generator.
    
    This specific example drives the bend strength of bend objects contained in
    a Mograph cloner object. The example is designed for a Python generator 
    object with a specific set of user data values. Please use the provided c4d
    file if possible.
    
    Note:
        This example makes use of the function `CacheIterator()` for cache 
        iteration which has been proposed on other threads for the task of walking
        a cache, looking for specific nodes. One can pass in one or multiple type
        symbols for the node types to be retrieved from the cache. I did not 
        unpack the topic of caches here any further.
    
        We are aware that robust cache walking can be a complex subject and 
        already did discuss adding such functionality to the SDK toolset in the
        future, but for now users have to do that on their own.
    
    As discussed in:
        plugincafe.maxon.net/topic/13275/
    """
    
    import c4d
    
    # The cookie cutter cache iterator template, can be treated as a black-box,
    # as it has little to do with the threads subject.
    def CacheIterator(op, types=None):
        """An iterator for the elements of a BaseObject cache.
    
        Handles both "normal" and deformed caches and has the capability to 
        filter by node type.
    
        Args:
            op (c4d.BaseObject): The node to walk the cache for.
            types (Union[list, tuple, int, None], optional): A collection of type
             IDs from one of which a yielded node has to be derived from. Will
             yield all node types if None. Defaults to None.
    
        Yields:
            c4d.BaseObject: A cache element of op.
    
        Raises:
            TypeError: On argument type violations.
        """
        if not isinstance(op, c4d.BaseObject):
            msg = "Expected a BaseObject or derived class, got: {0}"
            raise TypeError(msg.format(op.__class__.__name__))
    
        if isinstance(types, int):
            types = (types, )
        if not isinstance(types, (tuple, list, type(None))):
            msg = "Expected a tuple, list or None, got: {0}"
            raise TypeError(msg.format(types.__class__.__name__))
    
        # Try to retrieve the deformed cache of op.
        temp = op.GetDeformCache()
        if temp is not None:
            for obj in CacheIterator(temp, types):
                yield obj
    
        # Try to retrieve the cache of op.
        temp = op.GetCache()
        if temp is not None:
            for obj in CacheIterator(temp, types):
                yield obj
    
        # If op is not a control object.
        if not op.GetBit(c4d.BIT_CONTROLOBJECT):
            # Yield op if it is derived from one of the passed type symbols.
            if types is None or any([op.IsInstanceOf(t) for t in types]):
                yield op
    
        # Walk the hierarchy of the cache.
        temp = op.GetDown()
        while temp:
            for obj in CacheIterator(temp, types):
                yield obj
            temp = temp.GetNext()
    
    
    def main():
        """
        """
        # The user data.
        node = op[c4d.ID_USERDATA, 1]
        angle = op[c4d.ID_USERDATA, 2]
        fieldList = op[c4d.ID_USERDATA, 3]
    
        # Lazy parameter validation ;)
        if None in (node, angle, fieldList):
            raise AttributeError("Non-existent or non-populated user data.")
    
        # Get the cache of the node and clone it (so that we have ownership).
        cache = node.GetDeformCache() or node.GetCache()
        if cache is None:
            return c4d.BaseObject(c4d.Onull)
        clone = cache.GetClone(c4d.COPYFLAGS_NONE)
    
        # Iterate over all bend objects in the cache ...
        for bend in CacheIterator(clone, c4d.Obend):
            # ..., sample the field list for the bend object position, ...
            fieldInput = c4d.modules.mograph.FieldInput([bend.GetMg().off], 1)
            fieldOutput = fieldList.SampleListSimple(op, fieldInput,
                                                     c4d.FIELDSAMPLE_FLAG_VALUE)
            if (not isinstance(fieldOutput, c4d.modules.mograph.FieldOutput) or
                    fieldOutput.GetCount() < 1):
                raise RuntimeError("Error sampling field input.")
            # ... and set the bend strength with that field weight as a multiple 
            # of the angle defined in the user data.
            bend[c4d.DEFORMOBJECT_STRENGTH] = angle * fieldOutput.GetValue(0)
    
        # Return the clone's cache.
        return clone
    
    posted in Cinema 4D SDK •
    RE: Implementing a watermark on render

    Hello @shetal,

    thank you for reaching out to us. The reformulation of your question and the conformance with the forum guidelines on tagging is also much appreciated.

    About your question: As stated in the forum guidelines, we cannot provide full solutions for questions, but provide answers for specific questions. Which is why I will not show here any example code, the first step would have to be made by you. I will instead roughly line out the purpose of and workflow around VideoPostData, which I assume is what you are looking for anyway.

    VideoPostData is derived from NodeData, the base class to implement a node for a classic API scene graph. Node means here 'something that lives inside a document and is an addressable entity', examples for such nodes are materials, shaders, objects, tags, ..., and as such 'video post' node. As mentioned in its class description, VideoPostData is a versatile plugin interface which can be used to intervene a rendering process in multiple ways. The most tangible place for VideoPostData in the app is the render settings where video post plugins can be added as effects for a rendering process as shown below with the built-in water mark video post node.

    12dc3981-c9af-4e3b-80a3-a1e5dabc2a42-image.png

    VideoPostData is an effect, meaning that you cannot use it to invoke a rendering process and on its own it also cannot forcibly add itself to a rendering and must be included manually with the RenderData, the render settings of a rendering. However, a user could make a render setting which includes such watermark effect part of his or her default render settings. One could also implement another plugin interface, SceneHookData, to automatically add such effect to every active document. We would not encourage that though, as this could be confusing or infuriating for users. Finally, such VideoPostData plugin would be visible by default like all NodeData plugins, i.e., it would appear as something in menus that the user can add and interact with. To prevent this if desired, one would have to register the plugin with the flag PLUGINFLAG_HIDE suppress it from popping up in the 'Effect ...' button menu. I cannot tell you with certainty if it is possible to hide programmatically added effect nodes from the users view in the effect list of a render settings. There are some flags which can be used to hide instances of nodes, but I would have to test myself if this also applies in this list, it is more likely that this will not be possible.

    To implement a VideoPostData plugin interface, one can override multiple methods and take different approaches, the most commonly used one is to override VideoPostData::Execute(Link) which will be called multiple times for each rendered frame. The method follows a flag/message logic which is commonly used in Cinema 4D's classic API, where one gets passed in a flag which signalizes in which context the method is being called. Here the context is at which state of the rendering this call is being made, and the chain is:

    • VIDEOPOSTCALL::FRAMESEQUENCE - Series of images starts.
    • VIDEOPOSTCALL::FRAME - Image render starts.
    • VIDEOPOSTCALL::SUBFRAME - Sub-frame starts.
    • VIDEOPOSTCALL::RENDER - Render precalculation.
    • VIDEOPOSTCALL::INNER - Render precalculation.
    • VIDEOPOSTCALL::INNER - Immediately after rendering.
    • VIDEOPOSTCALL::RENDER - Immediately after shader cleanup.
    • VIDEOPOSTCALL::SUBFRAME - Sub-frame rendering done.
    • VIDEOPOSTCALL::FRAME - Frame rendering done.
    • VIDEOPOSTCALL::FRAMESEQUENCE - Complete rendering process finished.

    These flags are accompanied by information if the flags denotes the opening or closing of that 'step' in the rendering process. A developer often then restricts its plugin functionality to a certain flag. I.e., in your case you only want to execute some code when the closing VIDEOPOSTCALL::FRAME is being passed, i.e., after a single frame and all its sub-frames have been rendered. Execute() also passes in a pointer to a VideoPostStruct(Link) which carries information about the ongoing rendering. One of its fields is render, a pointer to a Render(Link). This data structure represents a rendering with multiple buffers and provides the method GetBuffer() which returns a pointer to VPBuffer buffer. In your case you would want to retrieve the RGBA buffer for the rendering by requesting the VPBUFFER_RGBA buffer (Link) with GetBuffer().

    This buffer is then finally the pixel buffer, the bitmap data you want to modify. The buffer is being read and written in a line wise fashion with VPBuffer::GetLine() and ::SetLine(). Here you would have to superimpose your watermark information onto the frame. I would do this in a shader like fashion, i.e., write a function which I can query for a texture coordinate for every pixel/fragment in every line and it will then return an RBGA value which I could then combine with the RGBA information which is in the buffer at that coordinate. The details on that depend on what you want to do, e.g.,

    • Should the watermark be tiled across the frame or just live in a 'corner'?
    • Should it contain alpha information?
    • Can the user influence it, or is it just a png file on disk?
    • etc...

    and the answers to that are mostly algorithmic and not directly connected to our API which limits the amount of support we can provide for them. If this all sounds very confusing to you, it might be helpful to look at our video post examples I did post in the previous thread, e.g., vpreconstructimage.cpp, as this will probably make things less daunting.

    If you decide that you do not want to take this route for technical or complexity reasons, you could write a SceneHookData plugin which listens via NodeData::Message for MSG_MULTI_RENDERNOTIFICATION(Link), a message family which is being sent in the context of a rendering. There you would have to evaluate the start field in the RenderNotificationData(Link) accompanying the message, to determine if the call is for the start or end of a rendering. Then you could grab the rendering output file(s) on disk with the help of the render settings from disk and 'manually' superimpose your watermark information. This will come with the drawback that you might have to deal with compressed video files like mpeg or Avi and all the image formats. Some complexity in that can be hidden away with our BaseBitmap type I did mention in my last posting, but not all of it. There is also the fact that you might run into problems when this plugin runs on a render server, where you cannot easily obtain write or even read access to files of the render output.

    I hope this give you some desired guidance,
    Ferdinand

    posted in Cinema 4D SDK •

    Latest posts made by ferdinand

    RE: How to make a Python Plugin...

    Hello @noseman,

    Thank you for posting this. Readers should however keep in mind that this code snippet is bound to a plugin id (1059500) and therefore can only be registered once per Cinema 4D installation. To ship scripts to other users or to have multiple scripts packed up in this form, users must register a plugin each time they implement this skeleton.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Draw editable spline in the viewport

    Hello @baca,

    Thank you for reaching out to us. I will answer in bullet points.

    1. You are overwriting the wrong method. When setting the ObjectData flag OBJECT_ISSPLINE, one should overwrite GetContour and not GetVirtualObjects. The former is the method to implement spline object generators, while the latter is the method for polygon object generators (and other things).
    2. Even when one flags a generator plugin as OBJECT_POINTOBJECT or OBJECT_POLYGONOBJECT, one must do all the document mode dependent drawing and logic oneself. These flags are merely indicators for Cinema 4D that a plugin implements such logic. There is no free lunch here 😞
    3. Drawing a spline in such gradient fashion is possible, but there are no automatisms for it. One must draw multiple line segments to realize drawing a line segment with a gradient. As with most drawing operations, Python is not the ideal language for this, and one might run into slowdowns quickly.
    4. While we have internally quite a few cases which mimic point/polygon objects in this fashion, like for example the Bezier object, we have none for splines. I do not see anything right away which would prevent you from implementing such custom spline object, but you are still entering unexplored territory here. You might encounter hard to solve or even unsolvable problems.
    5. While it is technically possible to write this yourself, it should be pointed out that the internal implementations all rely on PointObject::DisplayPoints, a non-public method of the PointObject exposed in the public API, to draw the vertices of an object, including selection states and transform gizmos.

    So, your code is 'correct' for the most part, at least for a polygon object. You return something in GVO to let Cinema 4D handle the polygon shading and then draw vertices and edges on top of that (which is what you are supposed to do). However, what at least I would not do, is storing the point data in self.LINE_DRAW. I would suggest accessing the cache of the node instead.

    In the end it is also not quite clear what you want to do. I understand that you want to draw the vertices and 'gradient' for the line segments of a spline. What is not quite clear to me is if you actually need an object (the stated could also be a tool) and if the user should be able to select and manipulate vertices. I would be best if you could clarify this.

    Especially regarding the point of wanting to draw a gradient of line segments, I would strongly suggest moving on to C++, as you will have much better chances to implement such a custom thing in that language.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Why are there no Sketch and Toon shaders?

    Hey @blastframe,

    Thank you for reaching out to us.

    @blastframe said in Why are there no Sketch and Toon shaders?:

    There are in C++ shader symbols as xartshader, xcelshader, xhatchingshader, and xspotshader.

    This is not quite true. These are the resource files for the shaders you were linking to, if they would be shader identifiers, they would start with a capital letter, e.g., Xartshader. Sometimes we simply forget to expose type symbols for the nodes we expose. But since the exposure of parameter IDs is automated, this is not that much of a hassle, as one only must hardcode the type identifier then. So, it is no oversight in Python, they simply do not exist.

    The parameter symbols are documented as for any other node type for these "special" cases. You can find the parameter documentation for the Art shader for example here.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Using Path Selection

    Hello @DGLopez,

    Thank you for reaching out to us. Normally you would solve such things with SendModelingCommand, CallUVCommand or similar methods. The problem here is: Although the Path Selection tool might seem to be a member of the old or new modelling kernel, it actually is not.

    It apparently was initially written for the UV-Editor, and still lives there, but then was repurposed as a more general tool. As a side-effect, the tool has fallen a bit off the wagon regarding being covered as either a modelling or uv command and has no command identifier which could be called from the public API. Since the tool has complex GUI inputs, you cannot just write to the tool container and then invoke CallCommand either, as you will then will be unable to define the start and end point of the path.

    You will have to write such functionality yourself.

    Thank you for your understanding,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: Python writing to desktop

    Hello @jpeters,

    welcome to the Plugin Café and thank you for reaching out to us. I have moved your question to General Talk since it is not related to our APIs. I would recommend having a look at our Forum Guidelines for future postings. Your question is also out of scope of support (as it is about the Python library/language). I will briefly answer here, but please understand that we cannot provide extended support on standard libraries or a language in general.

    I cannot quite reproduce what you are reporting. It is true that Windows is quite locked down these days and prevents you from writing to multiple paths in the windows installation when you do not have admin rights. But the desktop is usually not among these paths (that would be a bit ridiculous).

    I think the problem in your example is that you intend to do string formatting without actually doing it (you are missing the f-string prefix) and therefore attempt to write to the string literal C:\\Users\\{username}\\Desktop\\TurnTableTool, which Windows then is going to prevent, as it won't let you create new directories in \Users\ without admin rights. {username} should also be an illegal directory name.

    If everything fails, you can still run Cinema 4D with admin rights, to propagate these admin rights to the Python VM. But as demonstrated by the example below, this should not be necessary.

    Cheers,
    Ferdinand

    import c4d
    import os
    
    def main() -> None:
        """
        """
        user = os.getlogin()
        # You were missing the string fromating in your code (the f prefix), so you did 
        # not actually compose a new string.
        composed = f"C:\\Users\\{user}\\Desktop"
        print (composed)
        
        # But I would do it differently anyways:
        desktop = os.path.join(os.path.join(os.environ['USERPROFILE']), 'Desktop')
        print (desktop)
        
        directoryPath = os.path.join(desktop, "TurnTableTool")
        if not os.path.exists(directoryPath):
            os.makedirs(directoryPath)
        
        filePath = os.path.join(directoryPath, "myFile.txt")
        with open(filePath, "w") as file:
            file.write("Hello world!")
            
        print (f"'{filePath}' does exist: {os.path.exists(filePath)}")
        
    
    if __name__ == '__main__':
        main()
    
    C:\Users\f_hoppe\Desktop
    C:\Users\f_hoppe\Desktop
    'C:\Users\f_hoppe\Desktop\TurnTableTool\myFile.txt' does exist: True
    
    posted in General Talk •
    RE: Compute distance from a point to a mesh

    Hi,

    I am looking for hit point. Then if I understand well, my only way would be to use GeRayCollider on all objects ?

    A point-something projection will also give you a "hit-point" in some sense. The question is if you want to find the closest point for a point on some geometry (projection), or a point on a geometry for a point and a ray (intersection). GeRayCollider realizes ray-triangle intersection testing for PolygonObject instances. For multiple objects you must initialize multiple collider instances (or reinitialize one instance). But you cannot intersection test a null, a spline, or joint object as shown in your screenshot as they do not contain any triangles. When you are just interested in the closest point in some discrete geometry, you can use ViewportSelect or the more modern maxon API equivalent maxon::DistanceQueryInterface with a slightly different and more abstract purpose. ViewportSelect can also be used to pick the object in a scene which is closest to a point (for the cursor for example) for further testing with other methods.

    In maxon::GeometryUtilsInterface you will find some basic building blocks for writing something of your own.

    posted in Cinema 4D SDK •
    RE: Compute distance from a point to a mesh

    Hello @user168462,

    Thank you for reaching out to us. You clearly tried to be precise in your question and we appreciate this, but there are still some ambiguities we must resolve first.

    Compute distance from a point to a mesh
    [...] which is hit by the blue line from the camera [...]

    You talk first about points and meshes, and then about points and lines, and show a bunch of null objects and joints. Intersection testing (sets of) line segments and (sets of) triangles requires different approaches.

    In general, one should also add that intersection testing two lines in 3D (or similar cases as a line segment and a ray) is very unlikely to yield an intersection due to the vastness of 3D space and floating-point precision. What one usually does there is define a tolerance, where one then searches the closest point pair on the two line segments, tests if the distance of that pair is below that tolerance, and then considers these two lines to intersect (although they technically do not).

    Connected two that is your fuzziness regarding what you want to compute: An intersection or the projection? Your thread-title implies that you are interested in projections (i.e., computing the closest point p on some geometry G for a query point q). However, later you switch to terminology which more implies ray-casting/intersection testing as you give a ray direction and use words as along. But ray casting will not "compute [the] distance from a point to a mesh" but the intersection point(s) (when there is a hit) for the geometry and the ray. The line segment formed by the query point and intersection points has a length, but it would be pure coincidence if it were equal to the shortest distance (projection) of the query point and geometry.

    When you are interested in the shortest distance between a query point q and some geometry G, ray casting is not the correct method, you must project q onto G. Your example mentions some radius to look in, which I interpret as sort of a search window for the ray casting, but this is not only very error prone or very computationally complex as you might have to ray-cast millions of times to fill that window, but also unnecessary, you can just project the point.

    This is all slightly out of scope of support, as these are more principal techniques rather than things in our APIs.

    1. GeRayCollider is ray-triangle intersection testing helper class with some light optimization built into it. You cannot carry out ray-line-segment intersections with it. If you want to intersection test multiple objects, you have to intersection test them one by one.
    2. VolumeData::TraceGeometry is bound to the rendering process.
    3. There is also the class ViewportSelect with which you can "pick" objects in a viewport.

    Added to that is that there is no real geometry in your screenshot, there are joints, null objects, and camera object. None of which provides any explicit discrete geometry as polygons or line segments. You can of course "write stuff around that" which then fills in the data for the visualizations these objects use. But you cannot just intersection test a joint object or a null object which is displayed as a circle.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: How to get/set single vector value from USERDATA

    Hello @mikeudin,

    Thank you for reaching out to us. Vectors represented by the type c4d.Vector are immutable in Cinema 4D, so one intentionally cannot change one of the components of a vector after it has been instantiated (matrices are not immutable over their vector components on the other hand). To achieve what you want to do, you must write:

    old = Cube[c4d.ID_USERDATA, 2]
    Cube[c4d.ID_USERDATA, 2] = c4d.Vector(old.x, old.y, 12)
    

    The following would also be possible, but it is not an improvement IMHO:

    Cube[c4d.ID_USERDATA, 2] = c4d.Vector(Cube[c4d.ID_USERDATA, 2].x, Cube[c4d.ID_USERDATA, 2].y, 12)
    

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •
    RE: StringToNumber to BaseTime

    If you simply want to set the document's min time, why not just do this ...

    Well, the question is what that value is supposed to be. Judging from the context I assumed it to be a value in frames and not seconds. Your BaseTime would be equal to frame 330 for a document with 30 FPS. And also, the input is meant to be a string.

    posted in Cinema 4D SDK •
    RE: read data from a "capsule object"

    Hello @jesse,

    Thank you for reaching out to us. It is always best to be as precise as possible with questions. I assume you are talking about MSG_DRAGANDDROP and its data input argument being of type PyCapsule? A PyCapsule is a wrapper type for arguments from the C/C++ backend of Python. Such value is effectively meaningless in Python and mostly intended to be passed through. You cannot 'crack into' it.

    It would be best if you would describe:

    1. Which plugin interface/hook you are implementing.
    2. Where the message and which message is received, and which part of the message data you are interested in.
    3. What you want to achieve abstractly.

    Otherwise, it will be extremely hard for us to give you a precise answer.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK •