SOLVED Enable Isoline Editing in tool plugin

Hi, I'v searched the forum but I didn't find any solution.
I'm developing a tool plugin with python for polygon manipulation.
Is it possible to enable deformed isonline editing in the viewport for the selected object under a HN?

thanks

edit [ferdinand]: changed the title the word in the question

Hello @mdr74,

I apologize for the longer wait, but due to the length of your code example I pushed dealing with the example to a spot where I had more time. The solution simple though, as you never tell your plugin implementation, i.e., Cinema 4D, that you do want to support editing modes. This is done with the info argument passed on plugin registration. See attached example at the end for details.

Cheers,
Ferdinand

"""Example for a ToolData plugin which supports editing modes and the viewport
conventions that come with them.

As discussed in:
    https://plugincafe.maxon.net/topic/13415/
"""

import c4d

TOOL_PLUGIN_ID = 1045691

class ToolSelector(c4d.plugins.ToolData):
    """Just a dummy tool which does not do anything.
    """
    pass

if __name__ == "__main__":
    c4d.plugins.RegisterToolPlugin(
        id=TOOL_PLUGIN_ID,
        str="Tool Selector",
        # You did pass in here 0, i.e., no flags. But in order to support edit
        # states, e.g., Mpolygon, one has to pass in PLUGINFLAG_TOOL_EDITSTATES
        # Note that this flag is a bitmask, so one could also pass in for
        # example:
        #
        #   info= c4d.PLUGINFLAG_TOOL_EDITSTATES | c4d.PLUGINFLAG_TOOL_TWEAK
        #
        # to support both edit modes and tweak mode. So, one can chain together
        # as many flags as one wants with the or-operator.
        info=c4d.PLUGINFLAG_TOOL_EDITSTATES,
        icon=None,
        help="tool selector",
        dat=ToolSelector(),
    )

Hello @mdr74,

thank you for reaching out to us. You can set the display mode of a single or all viewports programmatically, but you cannot do this on a per object basis to my knowledge, since Cinema 4D simply does not support it. Find below an example for how to set the Deformed Editing mode programmatically. If you want to restrict it to cases where the active object is child of a HyperNurbs object, you could do so by retrieving the active object, then its parent and then checking for that parent being of type c4d.Osds, e.g.:

parent = op.GetUp()
if isinstance(parent, c4d.C4DAtom) and parent.CheckType(c4d.Osds):
    ...

Cheers,
Ferdinand

"""Example for modifying a parameter of all viewports.

Runs as a script manager script, toggles the Deformed Editing state of all 
viewports.

As discussed in:
    https://plugincafe.maxon.net/topic/13415
"""

import c4d

# Viewports are organized as BaseView/BaseDraw instances which are 
# attached to a document. Just as in Cinema, we have to change the viewport 
# settings per viewport. So, when we want to change all of them, we have to 
# obtain all viewports.
viewports = [doc.GetBaseDraw(i) for i in range(doc.GetBaseDrawCount())]
print (f"All viewports attached to {doc}: {viewports}.")

# We can also get specific viewports, e.g., the active one or the one which 
# is going to be the rendering viewport.
activeViewport = doc.GetActiveBaseDraw()
renderViewport = doc.GetRenderBaseDraw()
print (f"The active viewport is: {activeViewport}")
print (f"The rendering viewport is: {renderViewport}")

# A viewport is just another BaseList2D. So, setting a parameter, e.g., the
# IsoLine Editing mode, is nothing special and just another parameter.

# We go over all viewports and toggle their Deformed Editing state.
for vp in viewports:
    vp[c4d.BASEDRAW_DATA_DEFORMEDEDIT] = not vp[c4d.BASEDRAW_DATA_DEFORMEDEDIT]

# Since we did modify a node, the viewports, we have to push an update event 
# to Cinema 4D, in order to let the modifications be reflected in the UI.
c4d.EventAdd()

# In a ToolData scenario you might want to cache the current Deformed Editing 
# state before you overwrite it, as otherwise the state will linger after 
# your tool has been deselected. The user might not like that ;) A good
# place to set and cache the Deformed Editing mode would be ToolData.InitTool
# and FreeTool would be a good place to set the Deformed Editing mode back to
# its original value. You could also attach the setting and caching of the mode
# to other methods, e.g. input methods or `Message()`, so that you can catch
# the user switching the active object while having your tool active, but I personally
# would not bother with this.

Thank you.
But I realise I used the wrong option.
I was trying to enable the isoline editing while in polygon mode inside my tool.
I know that some tools, like Loop cut for example, are not using isoline display, but I don't know if it's a choice or is it impossible to enable it.
While using my tool, in the viewport settings the option is enabled, but I think it also depends on some unknown features

Sorry for the confusion.
Do you need me to change the post title?

Hello,

I did change the title for you. In the script I did post, you only have to change the parameter symbol, i.e., c4d.BASEDRAW_DATA_DEFORMEDEDIT to c4d.BASEDRAW_DATA_SDSEDIT. As I said in the script, a BaseDraw is just another BaseList2D, nothing special about it.

Cheers,
Ferdinand

@ferdinand sorry to bother you again.
I think I'm not able to exactly describe my request, maybe it's my poor english or bad terminology 😉

I'm trying to create a tool plugin for expanding the selection tool with custom functionality.
And when I switch to it, the isoline editing (still enabled in the basedraw) seems disabled in the viewport.

If I edit a polygon object under HN I'd like to have the same display that I have with the move tool, but I have the same display of the loop cut tool. I don't know if it's possible or where I have to set a specific option (maybe in the draw method ?)

I added this:

print(bd[c4d.BASEDRAW_DATA_SDSEDIT])
print(bd.IsViewOpen(doc))

in the Draw and MouseInput method and the result is always 1 and True

I'm using ViewportSelect GetNearestPolygon to select the polygon under the mouse.

I hope it's everything clear...🤞

thank you

Hello @mdr74,

@ferdinand sorry to bother you again I think I'm not able to exactly describe my request, maybe it's my poor english or bad terminology 😉

You are not bothering us, we are here for answering questions and communication can be hard, especially when it must be conducted in a language which is not our first/native language. We understand this, so no need to feel rushed.

I'm trying to create a tool plugin for expanding the selection tool with custom functionality. And when I switch to it, the isoline editing (still enabled in the basedraw) seems disabled in the viewport.

Hm, you lost me a bit there. What do you mean by 'expanding the selection tool'? You cannot really extend existing functionalities of Cinema 4D via our API, at least it is not clear to me what you mean by that. When you want to implement a tool in Python, you must implement a ToolData plugin. But you cannot extend the ToolData or more specifically ToolDescriptionData plugins provided by Cinema 4D, e.g. one of the selection tools. Are you writing a Python script for the Script Manger which does 'some stuff' to a scene and then invokes a tool via CallCommand()?

If I edit a polygon object under HN I'd like to have the same display that I have with the move tool, but I have the same display of the loop cut tool. I don't know if it's possible or where I have to set a specific option (maybe in the draw method ?)

Hm, again, the context would be helpful, but I assume you are talking about ToolData.Draw here. So, you seem to be in a ToolData context. Invoking a tool should not change the viewport representation of objects and you certainly do not have to draw stuff yourself, except for things like handles and more complex information in special cases. I am also not quite sure what is meant with "edit" here, since the move tool is also an edit operation? Are you talking about the difference between Model mode and the polygon data modes Point, Edge and Polygon?

I added this:

print(bd[c4d.BASEDRAW_DATA_SDSEDIT])
print(bd.IsViewOpen(doc))

in the Draw and MouseInput method and the result is always 1 and True. I'm using ViewportSelect GetNearestPolygon to select the polygon under the mouse. I hope it's everything clear.

Well, as long you or the user does not change BASEDRAW_DATA_SDSEDIT, it is not surprising that it does not change its value. So, the bottom line seems to be here, that you have a ToolData plugin and as soon as the user does activate that ToolData plugin, the viewports of Cinema 4D ignore if they have the Isoline Editing flag enabled or not. They will then always draw geometry as if BASEDRAW_DATA_SDSEDIT was False, am I understanding this correctly?

This is certainly not a usual behavior. Could be a bug or a mistake on your side, hard to say without code. To get here any further, you will have to share code with us, as we otherwise cannot confirm a bug or point out a misconception/bug of your code. You can share code privately with us if you are not at liberty of sharing it publicly.

The code should be a condensed example if possible and be accompanied by a description of when and where something is not working as you would expect it to. Read more about sharing posts and how formulate technical questions in our Forum Guidelines.

Cheers,
Ferdinand

@ferdinand
Hi,
here's the code ( a simplified version but with the same problem 😞 )

import c4d, os, sys, pprint, itertools, math, random, time
from dataclasses import dataclass, field
from typing import List
from itertools import islice
from c4d import plugins, gui
from c4d import utils as u
from c4d import Vector as vc

# Be sure to use a unique ID obtained from www.plugincafe.com
TOOL_PLUGIN_ID = 1045691
GET_KEYS = False
DEBUG = False


class TimeController(object):
    """This object makes sure to sleep as long as necessary to keep a specific
    time-rate."""

    def __init__(self, twait):
        self.twait = twait
        self.lastCall = 0

    def __call__(self):
        t = time.process_time()
        delta = t - self.lastCall
        if delta < self.twait:
            time.sleep(self.twait - delta)
        self.lastCall = time.process_time()


class ToolSelector(c4d.plugins.ToolData):
    def __init__(self):
        self.obj = None
        self.base_select = None
        self.mouseCoordinate = None
        self.direction = None
        self.poly_sel_array = []
        self.pick_data = False
        self.selector_tag = None

    def InitTool(self, doc, data, bt):
        self.mouseCoordinate = None
        self.direction = None
        self.pick_data = False
        self.poly_sel_array = []
        self.selector_tag = None
        self.obj = None

        return True

    def InitObject(self, doc):
        if self.obj:
            self.base_select = self.obj.GetPolygonS()
            doc.SetActiveObject(self.obj, 0)
            doc.SetMode(c4d.Mpolygons)

        c4d.EventAdd()

        return True


    def FreeTool(self, doc, data):
        self.obj = None

    def GetState(self, doc):
        return c4d.CMD_ENABLED

    def Debug(self, msg):
        c4d.CallCommand(13957)  # Konsole loeschen

        return True

    def KeyboardInput(self, doc, data, bd, win, msg):
        key = msg.GetInt32(c4d.BFM_INPUT_CHANNEL)
        cstr = msg.GetString(c4d.BFM_INPUT_ASC)
        mod = msg.GetInt32(c4d.BFM_INPUT_QUALIFIER)


        if key == c4d.KEY_ESC:
            c4d.CallCommand(300001111)

            return True


        elif msg[c4d.BFM_INPUT_ASC] == "\t":
            self.pick_data = True
            c4d.DrawViews(
                    c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW
                    | c4d.DRAWFLAGS_NO_THREAD
                    | c4d.DRAWFLAGS_NO_ANIMATION
                )
            c4d.EventAdd()

            return True

        return False

    def CallViewportSelect(self, viewport_select, bd, doc):
        frame = bd.GetFrame()
        w = frame["cr"] - frame["cl"] + 1
        h = frame["cb"] - frame["ct"] + 1

        viewport_select.Init(
            w,
            h,
            bd,
            [self.obj],
            c4d.Mpolyedgepoint,
            True,
            c4d.VIEWPORTSELECTFLAGS_USE_HN | c4d.VIEWPORTSELECTFLAGS_USE_DEFORMERS | c4d.VIEWPORTSELECTFLAGS_FORCE_USE_DEFORMERS,
        )

    def MouseInput(self, doc, data, bd, win, msg):
        if not self.obj:
            self.obj = doc.GetActiveObject() if doc.GetActiveObject() else None

            if self.obj:
                self.InitObject(doc)
            else:
                return True

        bc = c4d.BaseContainer()
        pick_poly = None

        mouse_x = msg[c4d.BFM_INPUT_X]
        mouse_y = msg[c4d.BFM_INPUT_Y]

        myPoint = c4d.Vector(mouse_x, mouse_y, 0)

        viewport_select = c4d.utils.ViewportSelect()
        self.CallViewportSelect(viewport_select, bd, doc)

        win.MouseDragStart(c4d.KEY_MLEFT, mouse_x, mouse_y, c4d.MOUSEDRAGFLAGS_DONTHIDEMOUSE)

        if msg[c4d.BFM_INPUT_QUALIFIER] != c4d.QSHIFT:
            self.base_select.DeselectAll()
            self.poly_sel_array = []

        controller = TimeController(40 / 1000)

        while True:
            controller()

            result, m_deltax, m_deltay, channels = win.MouseDrag()
            delta = c4d.Vector(m_deltax, m_deltay, 0)
            myPoint = myPoint + delta

            if result == c4d.MOUSEDRAGRESULT_CONTINUE:
                self.dragging = True

                pick_poly = viewport_select.GetNearestPolygon(self.obj, int(myPoint.x), int(myPoint.y))

                if pick_poly["i"] not in self.poly_sel_array:
                    self.poly_sel_array.append(pick_poly["i"])

                doc.AddUndo(c4d.UNDOTYPE_CHANGE_SELECTION, self.obj)
                self.base_select.Select(pick_poly["i"])

                c4d.DrawViews(
                    c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW
                    | c4d.DRAWFLAGS_NO_THREAD
                    | c4d.DRAWFLAGS_NO_ANIMATION
                )

            elif result in (c4d.MOUSEDRAGRESULT_FINISHED, c4d.MOUSEDRAGRESULT_ESCAPE):
                self.dragging = False
                break

        win.MouseDragEnd()

        if pick_poly:
            if pick_poly["i"] not in self.poly_sel_array:
                self.poly_sel_array.append(pick_poly["i"])

        c4d.EventAdd()

        return True

    def Draw(self, doc, data, bd, bh, bt, flags):

        return c4d.DRAWRESULT_OK

    def GetCursorInfo(self, doc, data, bd, x, y, bc):
        if bc.GetId() == c4d.BFM_CURSORINFO_REMOVE:
            self.mouseCoordinate = c4d.Vector(-1)
            return True

        c4d.DrawViews()

        self.mouseCoordinate = c4d.Vector(x, y, 0)

        if self.pick_data:
            mouse_x = x
            mouse_y = y

            viewport_select = c4d.utils.ViewportSelect()

            pick_objects = viewport_select.PickObject(
                bd, doc, int(mouse_x), int(mouse_y), rad=0, flags=c4d.VIEWPORT_PICK_FLAGS_0
            )

            if pick_objects:
                if pick_objects[0] != self.obj:
                    self.poly_sel_array = []

                self.obj = pick_objects[0]

                while not isinstance(self.obj, c4d.PolygonObject):
                    self.obj = ObjectData(self.obj.GetDown())


                self.InitObject(doc)
                self.pick_data = False

            c4d.EventAdd()
            return True

        return True

    def AllocSubDialog(self, bc):

        return SettingsDialog(None)


if __name__ == "__main__":

    plugins.RegisterToolPlugin(
        id=TOOL_PLUGIN_ID,
        str="Tool Selector",
        info=0,
        icon=None,
        help="tool selector",
        dat=ToolSelector(),
    )


and two images to show what I'm trying to reach:

with Mpolygons mode and the move tool active:

image.jpg

and when my tool is active:

image.jpg

what I meant with "expanding" is creating a tool that can remember the selection order of polygons.
and use this selection for custom functions.

thanks again 😊
please tell me if you need more information

Hello @mdr74,

thanks, that is okay, will have a look.

Cheers,
Ferdinand

Hello @mdr74,

I apologize for the longer wait, but due to the length of your code example I pushed dealing with the example to a spot where I had more time. The solution simple though, as you never tell your plugin implementation, i.e., Cinema 4D, that you do want to support editing modes. This is done with the info argument passed on plugin registration. See attached example at the end for details.

Cheers,
Ferdinand

"""Example for a ToolData plugin which supports editing modes and the viewport
conventions that come with them.

As discussed in:
    https://plugincafe.maxon.net/topic/13415/
"""

import c4d

TOOL_PLUGIN_ID = 1045691

class ToolSelector(c4d.plugins.ToolData):
    """Just a dummy tool which does not do anything.
    """
    pass

if __name__ == "__main__":
    c4d.plugins.RegisterToolPlugin(
        id=TOOL_PLUGIN_ID,
        str="Tool Selector",
        # You did pass in here 0, i.e., no flags. But in order to support edit
        # states, e.g., Mpolygon, one has to pass in PLUGINFLAG_TOOL_EDITSTATES
        # Note that this flag is a bitmask, so one could also pass in for
        # example:
        #
        #   info= c4d.PLUGINFLAG_TOOL_EDITSTATES | c4d.PLUGINFLAG_TOOL_TWEAK
        #
        # to support both edit modes and tweak mode. So, one can chain together
        # as many flags as one wants with the or-operator.
        info=c4d.PLUGINFLAG_TOOL_EDITSTATES,
        icon=None,
        help="tool selector",
        dat=ToolSelector(),
    )

@ferdinand
😊 Thanks.
I will be away in the next days.
I will try it as soon as possible and inform you about the result.

P.S . when the "info" is set as in your example, the isoline editing checkbox in the viewport configuration is automatically seen by the plugin or I have to configure the basedraw setting in the code?

Hi @mdr74,

it will just carry out what the current state of the BaseDraw instance is. So, when you always want isoline editing to be enabled with your tool, you must implement it yourself like described in my first posting here.

Without PLUGINFLAG_TOOL_EDITSTATES you will always get the following viewport output while in one of the edit modes with your plugin active, no matter if BASEDRAW_DATA_SDSEDIT is enabled for the BaseDraw or not:
alt text

With the flag PLUGINFLAG_TOOL_EDITSTATES, you will get the following viewport output when BASEDRAW_DATA_SDSEDIT is enabled for the viewport:

and the following output when BASEDRAW_DATA_SDSEDIT is disabled for the viewport:
alt text

Cheers,
Ferdinand

Thanks Ferdinand.
with info=c4d.PLUGINFLAG_TOOL_EDITSTATES everything works fine.

I mark your answer as correct.
is it the right way to set the topic as solved?

Hi @mdr74,

yes, it is and thanks for doing it!

Cheers,
Ferdinand