• 0 Votes
    2 Posts
    99 Views
    ferdinandF
    Hey @Pheolix, Welcome to the Maxon developers forum and its community, it is great to have you with us! Getting Started Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules. Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment. Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support. Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads. It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: How to Ask Questions. About your First Question Your code looks generally good, especially for someone who is starting out with the API you did really well. With that being said, I do not really understand what you want to do: ... plugin that maps and arranges textures onto a pixel grid. The goal is to make it easier to create voxel-style or Minecraft-like models by linking real-world units (e.g., centimeters) to pixels. (for example, 1 pixel = 6.25 cm) A few pointers: A CommandData plugin is the perfect choice when you want to manipulate the scene without any restrictions and are fine with always having to press a button run your logic. Scene element plugins, e.g., objects, tags, etc. on the other hand will carry out their logic on their own when a scene update is invoked. But they come with the restriction that their major payload functions (ObjectData::Execucte, ObjectData::GetVirtualObjects, TagData::Execute, etc.) run in their own threads (so that scene execution is parallelized) and therefore are subject to threading restrictions (I am aware that you are on C++, but the Python docs are better on this subject). So, for example, in a TagData::Execute you would not be allowed to allocate a new UVW tag on the object that is also hosting your plugin tag. But you could implement a button in the description of the tag, which when clicked cerates your setup (because TagData::Message runs on the main thread and you therefore are there allowed to add and remove scene data). With TagData:Execute you could then continuously update the UVW tag you are targeting on each scene update (changing parameter values of other scene elements is fine when tags are executed). This workflow is not necessarily better than a command, I am just showing you an option. Commands are also easier to implement for beginners than a scene element. When you talk about units, you should be aware that both the object and texture coordinate system are unitless. What you see in edit fields, is just smoke and mirrors. We recently talked here about this subject. You did get the major gist of our error handling but what you do with maxon::Failed is not quite correct. It is meant to test the return value of a Result<T> for having returned an error instance instead of T. When you want to indicate an error, you must return an error, e.g.,: // Not correct. if (!doc || !selectedObject || !bitmap || !foundTag) return maxon::FAILED;T // This is how one indicates that a function failed because something was a nullptr. if (!doc || !selectedObject || !bitmap || !foundTag) return maxon::NullptrError(MAXON_SOURCE_LOCATION, "Could not get hold of scene data."_s); // For a function which is of type Result<void>, its also totally fine to do this on an error. void functions // can fail successfully, it is up to you to decide if an error is critical enough to halt execution of if you just // want it to silently terminate. if (!doc || !selectedObject || !bitmap || !foundTag) return maxon::OK; // we are okay with failing here. For details see Error handling and Error Types Cheers, Ferdinand
  • Retrieve the current Unit and listen to changes

    Cinema 4D SDK c++ s24
    12
    0 Votes
    12 Posts
    1k Views
    ferdinandF
    Hey @Cankar001, Good to hear that you found your solution! One minor thing - you should avoid ApplicationOutput in production code, as it leads to console spam which we want to avoid in Cinema 4D. Using it in test code is fine. See Debug and Output Functions for alternatives. An even better way to do what you did in your code would be to use error handling. E.g., your code could look like this: // Your original function, I turned this into a function using our error system, indicated by the // Result<T> return type. static maxon::Result<maxon::Float> GetCurrentUnitScale(const BaseDocument* const document) { // The error scope handler for this function, i.e., all error returns exit through this handler. iferr_scope; // When there is no document, we return an error. When printed, this will then print the error // message and the source code location, e.g., myfile.cpp:123. if (!document) return maxon::NullptrError(MAXON_SOURCE_LOCATION, "Invalid document pointer."_s); // Your code goes here. // ... return 1.0f; } // A function calling this function which does use error handling itself. static maxon::Result<void> Foo(const BaseDocument* const document) { iferr_scope; // Call the function and return, i.e., propagate the error upwards when there is one. const maxon::Float unitScale = GetCurrentUnitScale(document) iferr_return; // voids in the Result<T> return type are returned with maxon::OK. return maxon::OK; } // A function calling this function which does not use error handling itself, i.e., the error // must terminate here. static bool Bar(const BaseDocument* const document) { // Here we use a manual scope handler to terminate the error chain. You have often to do this in // Cinema API (e.g., ObjectData::GetVirtualObjects), as methods there are not error handled // opposed to the Maxon API. iferr_scope_handler { // Print a debug message with the passed in error #err and the name of this function. And // force a debugger to halt when some condition is met. WarningOutput("@ failed with error: @"_s, MAXON_FUNCTIONNAME, err); if (someErrorCondition) DebugStop(); return false; }; // Call the function (we still have to handle the error with an iferr_return), and then let it // terminate in our manual scope handler. const maxon::Float unitScale = GetCurrentUnitScale(document) iferr_return; return true; } Cheers, Ferdinand
  • 0 Votes
    6 Posts
    757 Views
    C
    @m_adam Thank you very much for your help in the last days! This worked as well as expected
  • WebSocket usage in C++

    Cinema 4D SDK c++ windows macos s24
    6
    1
    0 Votes
    6 Posts
    725 Views
    C
    Hi @m_adam, following your typescript example it worked now to connect! Thank you very much for your help Merry Christmas and a Happy New Year!
  • 0 Votes
    2 Posts
    692 Views
    ferdinandF
    Hello @Hohlucha, Welcome to the Maxon developers forum and its community, it is great to have you with us! Getting Started Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules. Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment. Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support. Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads. It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions. About your First Question This is a development forum and your question does not seem to be development related. Please read our forum guidelines lined out above. I have moved this topic into General Talk for now. When this is indeed an end user question we must ask you to use our Support Center, the developer forum is not the right place for end user questions. When this is a development question, then please line out the current code you have and provide a meaningful problem description. Cheers, Ferdinand
  • Importing pythonapi from ctypes freezes C4D

    Cinema 4D SDK s24 macos python
    7
    0 Votes
    7 Posts
    1k Views
    ferdinandF
    Hey @lasselauch, We are not able to reproduce this crash on an Intel, M1, or M3 MacBook with 2024.4.0. Please provide and submit a crash report when this is still a problem for you. I would also recommend reinstalling Cinema 4D to rule out that your installation was damaged. Cheers, Ferdinand
  • Error compiling S24 plugin

    Moved General Talk s24 windows macos c++
    4
    0 Votes
    4 Posts
    996 Views
    ferdinandF
    Hey @Cankar001, thank you for reaching out to us. It is great to hear that you found your solution! But I have troubles understanding all details of the rest of your question. However: PluginStart ist just an alias for the plugin message C4DPL_INIT. A Cinema 4D instance has various stages in its startup and teardown sequence, as expressed by the plugin messages C4DPL_MESSAGES (see also: Plugin Messages). Through out the startup and teardown of Cinema 4D not all systems are available, as binaries/modules are loaded and unloaded and the systems associated with them are being pulled up and torn down. In the end it depends a bit on what you tried to do concretely, but generally speaking, it could very well be that something is not yet "up" when C4DPL_INIT is emitted. But in general, most modules, registries, etc. should be accessible at this point (plugins are loaded after the core and unloaded before the core). To get here a more precise answer, I would recommend opening a new topic with concrete code. Cheers, Ferdinand
  • 0 Votes
    8 Posts
    2k Views
    C
    @i_mazlov , Do you speak Russian?
  • Plugin works with R23 and doesn't work with S24.

    General Talk python r23 s24
    4
    0 Votes
    4 Posts
    921 Views
    K
    Got it. Thank you.
  • Updating my r20 C++ plugins to r21 and up

    Cinema 4D SDK
    9
    0 Votes
    9 Posts
    2k Views
    R
    @kbar, thank SO MUCH. I will look into it.
  • Multi threading in C4D python

    Cinema 4D SDK python s24
    3
    0 Votes
    3 Posts
    880 Views
    A
    thanks Ferdinand this cleared things up for me
  • 0 Votes
    3 Posts
    990 Views
    eZioPanE
    It works like a charm! Thanks a lot!
  • Create custom menu category for nodes?

    Cinema 4D SDK s24 c++
    2
    1
    0 Votes
    2 Posts
    617 Views
    ManuelM
    Hi, Categories are also assets. You can use the CategoryAssetInterface to create a category asset. and you can "parent" this category to another category using SetAssetCategory. maxon::Result<maxon::AssetDescription> CreateCategoryAsset( const maxon::AssetRepositoryRef& repository, const maxon::String& name, const maxon::Id& category) { iferr_scope; if (name.IsEmpty()) return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION, "Invalid category name."_s); // Create and store a new category asset. maxon::CategoryAsset categoryAsset = maxon::CategoryAssetInterface::Create() iferr_return; maxon::Id categoryId = maxon::AssetInterface::MakeUuid("category", false) iferr_return; maxon::AssetDescription assetDescription = repository.StoreAsset( categoryId, categoryAsset) iferr_return; // Set the category name. maxon::LanguageRef language = maxon::Resource::GetCurrentLanguage(); assetDescription.StoreMetaString(maxon::OBJECT::BASE::NAME, name, language) iferr_return; // Set the category of the asset when the category is not the empty id. if (!category.IsEmpty()) { maxon::CategoryAssetInterface::SetAssetCategory(assetDescription, category) iferr_return; } ApplicationOutput("Created category asset with the id: '@'", assetDescription.GetId()); return assetDescription; } Cheers, Manuel
  • Collapse/Fold groups by default in nodegraph UI

    Cinema 4D SDK s24 c++
    4
    1
    0 Votes
    4 Posts
    908 Views
    ManuelM
    This is already fixed now, but i can't say when the update will be available to public.
  • PYTHONPATH not appended to sys.path in Cinema 4D 24

    Cinema 4D SDK s24 python
    6
    0 Votes
    6 Posts
    1k Views
    F
    Excellent! Thanks you a lot Regards,
  • Unique ID for maxon::nodes::Node

    Cinema 4D SDK c++ s24
    3
    1
    0 Votes
    3 Posts
    602 Views
    O
    Hello, When translating the nodes in our system we need each of them to have a unique ID or name. That's how we identify each node, e.g if I have two or more texture nodes (with the same ID) used on different materials and assigned to different objects, it would only show one texture rendered to all objects as all textures are assigned one similar ID. For now, I solved this by finding a uniqueID for the corresponding material and concatenating it with the node's ID. As for the error message it only says Error and nothing more. I will send the text via email. Thank you.
  • 1 Votes
    3 Posts
    698 Views
    H
    Ok, thanks for the confirmation. Already fixed it manually myself.
  • Layers checker

    Cinema 4D SDK python s24 sdk
    4
    0 Votes
    4 Posts
    988 Views
    ROMANR
    @mogh I didn't know about this function @ferdinand Thank you a lot for your detailed response. I will chek posts which you recommended
  • 0 Votes
    3 Posts
    816 Views
    M
    Thank You @ferdinand Access an Enclosing Entity from a Subordinate Entity, was the solution I was searching for hence I needed to alter the GeDialog from inside the TreeViewFunctions. This seems to be the only solution hence these checkboxes belong to the treeview ?!? Anyway its working and I am very happy User sets checkbox -> Sum is calculated -> button is activated. I updated the example code (and removed the now unnecessary button to prevent further confusion) with the desired behavior for future readers. kind regards mogh """ Adapted from: https://developers.maxon.net/forum/topic/10654/14102_using-customgui-listview/2 """ import c4d import random # Be sure to use a unique ID obtained from [URL-REMOVED] PLUGIN_ID = 1000010 # TEST ID ONLY # TreeView Column IDs. ID_CHECKBOX = 1 ID_NAME = 2 ID_OTHER = 3 ID_LONGFILENAME = 4 MY_BUTON = 5 HOW_MANY = 6 ID_FILESIZE = 7 HOW_MANY_MB = 8 MY_CALC = 9 TREEVIEW = 99 # A tuple of characters to select from (a-z). CHARACTERS = tuple( chr ( n) for n in range(97, 122)) class TextureObject(object): """ Class which represent a texture, aka an Item in our list """ def __init__(self): self.texturePath = TextureObject.RandomString(5, 10) self.otherData = TextureObject.RandomString(5, 20) self.longfilename = "-" self._selected = False self.filesize = TextureObject.RandomNumber() @staticmethod def RandomString(minLength, maxLength): """Returns a string of random characters with a length between #minLength and #maxLength. The characters are taken from the 97 (a) to 122 (z) ASCII range. """ return "".join( (random.choice(CHARACTERS) for _ in range(minLength, maxLength))) @staticmethod def RandomNumber(): return random.randrange(1, 99) @property def IsSelected(self): return self._selected def Select(self): self._selected = True def Deselect(self): self._selected = False def __repr__(self): return str(self) def __str__(self): return self.texturePath class ListView(c4d.gui.TreeViewFunctions): def __init__(self, host): """Initializes a ListView with a host. Args: host (c4d.gui.GeDialog): The hosting dialog. """ # Add ten mock data texture objects. self.listOfTexture = [TextureObject() for _ in range(10)] if not isinstance(host, c4d.gui.GeDialog): raise TypeError("Expected {} for argument 'host'. Received: {}".format(c4d.gui.GeDialog, host)) else: self._host = host def DoSomethingWithDialog(self): """Calls the dialog over the stored reference. """ self._host.calc_selected() def IsResizeColAllowed(self, root, userdata, lColID): return True def IsTristate(self, root, userdata): return False def GetColumnWidth(self, root, userdata, obj, col, area): """Measures the width of cells. Although this function is called #GetColumnWidth and has a #col, it is not only executed by column but by cell. So, when there is a column with items requiring the width 5, 10, and 15, then there is no need for evaluating all items. Each item can return its ideal width and Cinema 4D will then pick the largest value. Args: root (any): The root node of the tree view. userdata (any): The user data of the tree view. obj (any): The item for the current cell. col (int): The index of the column #obj is contained in. area (GeUserArea): An already initialized GeUserArea to measure the width of strings. Returns: TYPE: Description """ # The default width of a column is 80 units. width = 80 # Replace the width with the text width. area is a prepopulated # user area which has already setup all the font stuff, we can # measure right away. if col == ID_NAME: return area.DrawGetTextWidth(obj.texturePath) + 5 if col == ID_OTHER: return area.DrawGetTextWidth(obj.otherData) + 5 if col == ID_LONGFILENAME: return area.DrawGetTextWidth(obj.longfilename) + 5 return width def IsMoveColAllowed(self, root, userdata, lColID): # The user is allowed to move all columns. # TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui. return True def GetFirst(self, root, userdata): """ Return the first element in the hierarchy, or None if there is no element. """ rValue = None if not self.listOfTexture else self.listOfTexture[0] return rValue def GetDown(self, root, userdata, obj): """ Return a child of a node, since we only want a list, we return None everytime """ return None def GetNext(self, root, userdata, obj): """ Returns the next Object to display after arg:'obj' """ rValue = None currentObjIndex = self.listOfTexture.index(obj) nextIndex = currentObjIndex + 1 if nextIndex < len(self.listOfTexture): rValue = self.listOfTexture[nextIndex] return rValue def GetPred(self, root, userdata, obj): """ Returns the previous Object to display before arg:'obj' """ rValue = None currentObjIndex = self.listOfTexture.index(obj) predIndex = currentObjIndex - 1 if 0 <= predIndex < len(self.listOfTexture): rValue = self.listOfTexture[predIndex] return rValue def GetId(self, root, userdata, obj): """ Return a unique ID for the element in the TreeView. """ return hash(obj) def Select(self, root, userdata, obj, mode): """ Called when the user selects an element. """ # I only use the checkbox to select list elemenmts """ if mode == c4d.SELECTION_NEW: for tex in self.listOfTexture: tex.Deselect() obj.Select() elif mode == c4d.SELECTION_ADD: obj.Select() elif mode == c4d.SELECTION_SUB: obj.Deselect() """ def IsSelected(self, root, userdata, obj): """ Returns: True if *obj* is selected, False if not. """ return obj.IsSelected def SetCheck(self, root, userdata, obj, column, checked, msg): """ Called when the user clicks on a checkbox for an object in a `c4d.LV_CHECKBOX` column. """ if checked: obj.Select() else: obj.Deselect() self.DoSomethingWithDialog() def IsChecked(self, root, userdata, obj, column): """ Returns: (int): Status of the checkbox in the specified *column* for *obj*. """ if obj.IsSelected: return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED else: return c4d.LV_CHECKBOX_ENABLED def GetName(self, root, userdata, obj): """ Returns the name to display for arg:'obj', only called for column of type LV_TREE """ return str(obj) # Or obj.texturePath def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor): """ Draw into a Cell, only called for column of type LV_USER """ if col in (ID_OTHER, ID_LONGFILENAME): text = obj.otherData if col == ID_OTHER else obj.longfilename canvas = drawinfo["frame"] textWidth = canvas.DrawGetTextWidth(text) textHeight = canvas.DrawGetFontHeight() xpos = drawinfo["xpos"] ypos = drawinfo["ypos"] + drawinfo["height"] if (drawinfo["width"] < textWidth): while (drawinfo["width"] < textWidth): if len(text) <= 4: text = "..." break text = text[:-4] + "..." textWidth = canvas.DrawGetTextWidth(text) textWidth = canvas.DrawGetTextWidth(text) drawinfo["frame"].DrawText(text, xpos, ypos - int(textHeight * 1.1)) if col == ID_FILESIZE: text = obj.filesize canvas = drawinfo["frame"] # w = geUserArea.DrawGetTextWidth(name) h = canvas.DrawGetFontHeight() # xpos = drawinfo["xpos"] + 10 xpos = drawinfo["xpos"] + drawinfo["width"] - canvas.DrawGetTextWidth(text) ypos = drawinfo["ypos"] + drawinfo["height"] drawinfo["frame"].DrawText(text, xpos, ypos - int(h * 1.1)) def DoubleClick(self, root, userdata, obj, col, mouseinfo): """ Called when the user double-clicks on an entry in the TreeView. Returns: (bool): True if the double-click was handled, False if the default action should kick in. The default action will invoke the rename procedure for the object, causing `SetName()` to be called. """ c4d.gui.MessageDialog("You clicked on " + str(obj)) return True def DeletePressed(self, root, userdata): "Called when a delete event is received." for tex in reversed(self.listOfTexture): if tex.IsSelected: self.listOfTexture.remove(tex) class TestDialog(c4d.gui.GeDialog): _treegui = None # Our CustomGui TreeView # _listView = ListView() # Our Instance of c4d.gui.TreeViewFunctions def __init__(self): """ """ # Create an instance of ListView and give it access to the dialog # which carries it. self._listView = ListView(host=self) self.overall = len(self._listView.listOfTexture) def CreateLayout(self): # Create the TreeView GUI. customgui = c4d.BaseContainer() customgui.SetBool(c4d.TREEVIEW_BORDER, c4d.BORDER_THIN_IN) customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True) # True if the tree view may have a header line. customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, True) # True if no lines should be drawn. customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True) # True if the user can move the columns. customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True) # True if the column width can be changed by the user. customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True) # True if all lines have the same height. customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True) # Alternate background per line. customgui.SetBool(c4d.TREEVIEW_CURSORKEYS, True) # True if cursor keys should be processed. customgui.SetBool(c4d.TREEVIEW_NOENTERRENAME, False) # Suppresses the rename popup when the user presses enter. if self.GroupBegin(id=1000, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, rows=2, cols=1, groupflags=c4d.BORDER_OUT): self.GroupBorderSpace(left=5, top=5, right=5, bottom=5) self.GroupBorderNoTitle(borderstyle=c4d.BORDER_NONE) if self.GroupBegin(id=1001, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, rows=2, cols=3, groupflags=c4d.BORDER_OUT): self.GroupBorderNoTitle(borderstyle=c4d.BORDER_NONE) self._treegui = self.AddCustomGui(TREEVIEW, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, minw=430, minh=160, customdata=customgui) if not self._treegui: print("[ERROR]: Could not create TreeView") return False self.GroupEnd() if self.GroupBegin(id=1002, flags=c4d.BFH_FIT | c4d.BFV_FIT, rows=2, cols=3, groupflags=c4d.BORDER_OUT): self.GroupBorderNoTitle(borderstyle=c4d.BORDER_NONE) self.AddStaticText(HOW_MANY, flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_LEFT | c4d.BFH_SCALE, name="Selected: 0 / " + str(self.overall)) self.AddStaticText(HOW_MANY_MB, flags=c4d.BFV_CENTER | c4d.BFV_SCALE | c4d.BFH_LEFT | c4d.BFH_SCALE, name="Filesize Sum: __________") self.AddButton(MY_BUTON, c4d.BFH_CENTER, name="Enable me by check-boxing") self.Enable(MY_BUTON, False) self.GroupEnd() self.GroupEnd() return True def calc_selected(self): """this is a helper to calculate the selected elements and to enable the button :return: # slected and sum of the selecteed filsize """ selected = 0 filsizesum = 0 self.overall = len(self._listView.listOfTexture) # update our count for fileitem in self._listView.listOfTexture: if fileitem.IsSelected is True: filsizesum += fileitem.filesize selected += 1 if selected > 0: self.Enable(MY_BUTON, True) else: self.Enable(MY_BUTON, False) sel_string = "Selected: " + str(selected) + " / " + str(self.overall) self.SetString(HOW_MANY, sel_string) mb_string = "Filesize Sum: " + str(filsizesum) self.SetString(HOW_MANY_MB, mb_string) return selected, filsizesum def InitValues(self): # Initialize the column layout for the TreeView. layout = c4d.BaseContainer() layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX) layout.SetLong(ID_NAME, c4d.LV_TREE) layout.SetLong(ID_LONGFILENAME, c4d.LV_USER) layout.SetLong(ID_OTHER, c4d.LV_USER) layout.SetLong(ID_FILESIZE, c4d.LV_USER) self._layout = layout self._treegui.SetLayout(5, layout) # Set the header titles. self._treegui.SetHeaderText(ID_CHECKBOX, "Check") self._treegui.SetHeaderText(ID_NAME, "Name") self._treegui.SetHeaderText(ID_LONGFILENAME, "Long Filename") self._treegui.SetHeaderText(ID_OTHER, "Other") self._treegui.SetHeaderText(ID_FILESIZE, "Filesize") self._treegui.Refresh() # Set TreeViewFunctions instance used by our CUSTOMGUI_TREEVIEW self._treegui.SetRoot(self._treegui, self._listView, None) return True def Command(self, id, msg): # Click on button if id == MY_BUTON: newID = int(len(self._listView.listOfTexture) + 1) tex = TextureObject() tex.texturePath = "Some new data " + str(newID) tex.longfilename = TextureObject.RandomString(20, 40) self._listView.listOfTexture.append(tex) self.calc_selected() self._treegui.Refresh() return True class MenuCommand(c4d.plugins.CommandData): dialog = None def Execute(self, doc): if self.dialog is None: self.dialog = TestDialog() return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=300, defaultw=430) def RestoreLayout(self, sec_ref): if self.dialog is None: self.dialog = TestDialog() return self.dialog.Restore(PLUGIN_ID, secret=sec_ref) def main(): c4d.plugins.RegisterCommandPlugin( PLUGIN_ID, "Python TreeView Example", 0, None, "Python TreeView Example", MenuCommand()) if __name__ == "__main__": main() [URL-REMOVED] @maxon: This section contained a non-resolving link which has been removed.
  • particle direction driven by texture

    General Talk
    3
    2
    0 Votes
    3 Posts
    922 Views
    H
    Thank you alot Ferdinand for your time, thoughs and help. I really appreciate that:)' I will get my head in it and try to figure it out.