Group Details Private

administrators

  • RE: Plugin module import freezes on startup

    Hey Jacob,

    Thank you for the added data. First of all, I have invited you to the forum to end this middle man communication, which is a bit odd.

    The pyz file is part of python ...

    I am aware of what pyz is, I just pointed this out because I of course looked inside your package and found all the py_armor obfuscated code and the injected binaries in there. So, I pointed out that this is bit more than just "packaged in a pyz file for ease of distribution [...]" as Lasse/you put it, the goal is here clearly obfuscation. Which is also relevant for support, as it limits what you and I can see (without getting hacky).

    My finding with the 10mb file freeze comes from my trial and error ... mean[t] when you run a script from Extensions -> User Scripts.

    Your code also freezes when you load it as a Script Manager script. That is what I did with the last package from Lasse, and now also yours. The code in your script is again wrong, which is why it won't freeze until you fix it. This is the code I found:

    2bd4290e-78b2-43d4-936d-1e2a7eaf366b-image.png

    And I fixed it then to this. When I ran it then, Cinema 4D froze for two minutes or so, after that it opened a myriad of dialogs to then terminate into two crash dialogs (it was pure luck that I let it run for so long, Lasses previous version might have acted similar, but there I killed the C4D process, as soon as I saw the 'beach ball of death' cursor on MacOS).

    69fcb5da-ac49-477e-8f70-9daeb1daa1aa-image.png

    ⚠ Please read my answer below carefully, as I already pointed out most of this in my previous posting.

    • I would STRONGLY suggest debugging this without obfuscation. Maxon also cannot debug larger sections of code or test further packages for you. I understand that obfuscation might not be your choice, but it will make your life harder in debugging this, as you always fly blind. We of course still will provide support, but you have to provide more than "it does not work/crashes/freezes, please help us", especially when this is not code tied to our APIs.
    • Attach a debugger from the Script Manager and see why your code crashes/freezes (see link in last posting when unsure how to do this). But you need an un-obfuscated code base for this to make any sense.
    • Defer your loading to a later point, e.g., C4DPL_PROGRAM_STARTED, when you have issues in the direct plugin registration context. In that case you would always register your plugin, but then only execute it when the your own license check succeeded.
      • But you absolutely cannot ship a plugin which freezes Cinema 4D for multiple minutes on startup or when invoking your plugin because your licensing takes so long. When we see this in the wild, we will have to blacklist your plugin IDs, as this damages the brand Cinema 4D. Please use threading then to not block the main thread with your long running code.
      • What I did not notice before is that you apparently try to open multiple dialogs (for me it opened multiple dialogs when I ran the script). The GUI and many other systems are not yet available when Cinema 4D is still booting, e.g., in the splash screen. You can expect all systems to be up and running when C4DPL_STARTACTIVITY is emitted, but it is better to wait for C4DPL_PROGRAM_STARTED for long running tasks (i.e., the two events I tested in my previous posting).
      • Please also keep in mind that Cinema 4D has its own anti-piracy measures. Python plugins are sort of special in that they work slightly different than native C++ plugin modules (the Python C++ module shipped by Maxon sort of acts as a surrogate for Python plugins in the module registration phase). But Cinema 4D won't allow plugin threads to start their own processes at this point (which you might be trying to do with your injected binaries), and threading should also be avoided at this point, as the job system of Cinema 4D might be still booting. What you are meant to do in PluginStart (or the __main__ context of a pyp file), is register your plugins. You can run some quick logic there, but you are certainly not meant to start communicating with servers and opening GUIs there. You can read here a bit more about this from a C++ system perspective.
    • I would recommend to do your license check in the background in its own thread once C4DPL_PROGRAM_STARTED has been emitted (so that you can also open dialogs to signal errors).
    • An alternative would be to do it when the user clicks the button of your command. But you should also here put it into its own thread, so that it does not block everything else.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Editable Object Plugin returns Null after scaling

    Hello @JH23,

    Thank you for reaching out to us. Your code is looks correct, but you should not return None on a critical error (e.g., when you run out of memory to allocate things), but return c4d.BaseObject(c4d.Onull) instead, that is also what Cinema 4D will do, when it cannot make sense out of your method. E.g. it should be:

    if not(cube := c4d.BaseObject(c4d.Ocube)):
        return c4d.BaseObject(c4d.Onull)
    
    cube[c4d.ID_BASEOBJECT_REL_SCALE, c4d.VECTOR_X] = 2.0
    ...
    

    Also, does your object have child inputs it depends on? E.g., like an Extrude object which depends on a child spline? I assume it does not, right? You should then change your manual dirty check to this, because your code also tries to build the children of this node which could lead to issues.

    # Only build the cache of the object when one of its parameters has changed or when there is no cache.
    if not op.IsDirty(c4d.DIRTY_DATA) and op.GetCache():
        return op.GetCache() # you could also pass the hh here, but that is not necessary in this case
    

    Do you see any errors in the console when this happens? Cinema 4D returning a null object for a cache means that building the cache failed. There must be somewhere a bug in your code, I am not sure though that my suggestions will fix it.

    I would also like to know if there is a specific way to return several objects or a specific one when making the generator editable, making it more customizable.

    A cache must always terminate into a singular object because it is just part of an object hierarchy. When you want your cache to contain multiple things, you must parent them to a null and return that null as your cache.

    As to reacting to CTSO, yes that is possible, but not in Python. When an object is being built for CSTO, its HierachyHelp->GetFlags() will be BUILDFLAGS::EXTERNALRENDERER | BUILDFLAGS::ISOPARM but HierachyHelp has not been wrapped for Python. There is MSG_CURRENTSTATE_END emitted, and you can capture this in Python too via NodeData::Message, but that is only the signal that CSTO has finished (and that you might want to revert to a non-CSTO specialized cache).

    But in general, we advise against such trickery where objects switch out their cache in certain contexts. It is valid to do this, but often requires intimate knowledge of the API to be done sucessfully.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Plugin module import freezes on startup

    Hey @lasselauch,

    Thank you for reaching out to us. In general, your problem is simply out of scope of support, as you are trying to get the aescripts third-party library going.

    The license script is packaged in a pyz file for ease of distribution [...]

    That is not quite the truth, is it? The package is clearly zipped to further increase the obfuscation which happens with pyarmor inside and to hide away its injected binaries (in /aescripts_PythonLicenser/aescripts_PythonLicenser/bin). Which also explains the package size which is otherwise not obtainable with pure Python code.

    One problem we're facing is that for C4D plugins C4D gets stuck on startup when showing Initializing Plugins 100%. After a lot of trial and error I found out that this happens ONLY when the pyz file is bigger than ~10mb. This doesn't happen if you run a script that imports the pyz. Only for plugins that gets loaded on startup.

    1. You could easily defer the loading of your package when Cinema 4D and the size would be the culprit (but that is rather unlikely because Cinema 4D loads itself 100's of megabytes when the modules are loaded). But I went ahead and did it and deferred the loading to C4DPL_PROGRAM_STARTED (the latest point in the boot sequence, this is when the user sees the c4d UI for the first time) and also to C4DPL_STARTACTIVITY (a bit earlier, but after all modules have been loaded). See PluginMessage for details. This does not really change the outcome; it just freezes then at a later point, e.g., to the point when the user sees the UI.
    import os
    import sys
    import c4d
    
    lic_settings = {
        ...
    }
    
    def PluginMessage(id, data):
        if id==c4d.C4DPL_PROGRAM_STARTED:
            current_directory = os.path.dirname(os.path.abspath(__file__))
            pyz_path = os.path.join(current_directory, 'res', "aescripts_PythonLicenser.pyz")
            sys.path.insert(0, pyz_path)
            from aescripts_PythonLicenser import aesl
    
        return True
    
    1. I then had a look at your claim that 'this doesn't happen if you run a script that imports the pyz'. Your Demo_Script.py is faulty as it does not define the import path correctly, it is missing the 'res' directory and therefore only raises an import error. When you fix that, it just freezes as much as the other code (which is not very surprising since executing a Script Manager script and code after C4DPL_PROGRAM_STARTED are more or less the same thing).
    import sys
    import os
    
    # Must be os.path.join(dir, "res", "aescripts_PythonLicenser.pyz")
    sys.path.insert(
        0,
        os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "aescripts_PythonLicenser.pyz"
        ),
    )
    from aescripts_PythonLicenser import (
        aesl,
        UpdateStatus,
        aesl_log,
        show_logs,
        run_license_tests,
    )
    

    Conclusion

    The TLDR is here that you have there an obfuscated library which seems to freeze in any context in Cinema 4D. This could either be a flat-out bug in your library where it never exits some loop or due to the library trying to use features that are not available in Cinema 4D (multiprocessing, tkinter, asyncio, etc.). I would recommend that you get an un-obfuscated version of the library, and then try to load it from a Script Manager script with a debugger attached, to see where it hangs.

    Also, py_armor and all the other things the aescripts library does will not deter any cracker. They will just crack your encrypted pyp file (which I assume you planned on doing), and remove the aescripts library. You cannot even make binary language (e.g. C++) executables crackproof, and for languages like Python, it is just a joke for anyone even remotely competent. py_armor is not a good idea. So, it stands to be questioned if all that effort is worth it. The most effective licensing system is a low effort server request for a valid user/serial number which keeps honest users honest. Crackers will remove that too, but then you have at least not poured days, weeks, or even months into a system which will be cracked within 72 hours after release.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Document copied for Picture viewer doesnt copy contents of child of a TagData

    Hello @ECHekman,

    Thanks for providing the information that the init function should not modify the scene graph. Ill take this into account in the future.

    It is not only NodeData::Init but the majority of NodeData methods and the methods of derived classes that cannot modify the scene graph of a loaded document, as for example the scene graph they are attached to. The reason is that such methods, as for example NodeData::Init, TagData:Execute, ObjectData::GetVirtualObjects, and many more, run in their own threads to parallelize scene execution. Modifying the scene graph of a loaded document from a non-main-thread (i.e., parallel) context can lead to race conditions and access violations and are therefore strictly forbidden. Forbidden are primarily allocations and deallocations, parameter get/set access is allowed (as long as it does not allocate or deallocate a pointed object) but also discouraged outside of TagData::Execute. For details, see Cinema 4D Threads Manual.

    I went through that example you linked, but I' m a bit unclear why it is necessary to manually create the handling of a custom and parallel nodegraph branch when plugin GeListNodes are already graphs themselves?

    I am not quite sure how you mean 'parallel', but a GeListNode implements both hierarchal (i.e., tree) relations with InsertUnder, GetUp, GetDown, GetNext etc. and arbitrary (i.e., graph) relations with GetBranchInfo. The hierarchy of a node type, e.g., Obase - a BaseObject or Tmytag - your tag class, are always meant to be of the same type and follow the conventions of their base class. Tags are not meant to have children. You must implement your own branch to declare your own non-hierarchical relation between your plugin tag and your basic node.

    ⚠ The workaround Maxime suggested, just implementing NodeData::CopyTo is unfortunately not valid.

    First of all, you must always implement all three serialization functions Read, Write, and CopyTo, you can never only implement only one of them.

    But storing nodes irregularly in this manner can lead to access violations as Cinema 4D is then not aware that this quasi-branch exists and might try to execute multiple things at once (via NodeData::GetAccessedObjects), leading to read/write access violations. Moreover, I am fairly sure that this might lead to event or call starvation when Cinema finds there such a dangling BaseList2D (NodeData) instance under your BaseTag (TagData). Or even worse, it will ignore completely it in some contexts, because tags are not supposed to have children.

    You must implement a branch in your case, I would recommend following the SDK example as it is slightly cleaner than the forum preview Maxime linked to.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Feedback on C4D Plugin – Mapping Textures to a Pixel Grid (Cinema 4D 2024 SDK)

    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:

    1. 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.
    2. 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.
    3. 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

    posted in Cinema 4D SDK
  • RE: How to create a control and switch between multiple fields

    I'm not sure I understand you correctly, you do not have to use QuickTabRadio bar with resource, you just retrieve the int value of the parameter, in the case of the previous example I share with a GetInt32(c4d.MGCLONER_VOLUMEINSTANCES_MODE).

    If you can provide a code example of what is blocking you that would be nice.
    You can find a non-exhaustive list of type of control available in *res file within the C++ documentation in Description Resource.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK
  • RE: Document copied for Picture viewer doesnt copy contents of child of a TagData

    Hey you are not supposed to modify the scene graph during the init function.
    Adding children to a tag while it may be possible is as far as I know not something done in Cinema 4D. so for optimization purpose I would not be surprised that they are never copied at all.

    With that's said I would recommend you to use a custom branch to store your BaseList2D as demonstrated in C++ SDK: Custom Branching Code Example .
    In last case you should implement the CopyTo to properly copy your data when your node is copied. But again modifying the scene Graph during the Init is not the way to got.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK
  • RE: Determine object category (Generator, Deformer, etc.) via Python

    Hey the internal function is not exposed in Python neither in C++. Sadly the function also use a function that is only exposed in C++ with no proper workaround.

    Here is the C++ implementation of the function

    inline cinema::OBJECTCATEGORY ObjectToCategory(const cinema::BaseObject& object)
    {
    	return ObjectToCategory(object.GetType(), object.GetInfo());
    }
    
    inline cinema::OBJECTCATEGORY ObjectToCategory(cinema::Int32 type, cinema::Int32 objectInfo)
    {
    	cinema::OBJECTCATEGORY category = cinema::OBJECTCATEGORY::OTHER;
    
    	switch (type)
    	{
    		case Oweighteffector:	category = cinema::OBJECTCATEGORY::DEFORMER;		break; // FIX[4850]
    		case Ojoint:					category = cinema::OBJECTCATEGORY::JOINT;				break; // FIX[4853]
    		case Onull:						category = cinema::OBJECTCATEGORY::NULLOBJECT;	break;
    		case Opolygon:				category = cinema::OBJECTCATEGORY::POLYGON;			break;
    		case Osds:						category = cinema::OBJECTCATEGORY::HYPERNURBS;	break;
    		case Ocamera:				
    		case Orscamera:
    			category = cinema::OBJECTCATEGORY::CAMERA; break;
    		case Olight:				
    		case Orslight:
    			category = cinema::OBJECTCATEGORY::LIGHT; 
    			break;
    
    		case Oenvironment:
    		case Oforeground:
    		case Obackground:
    		case Ofloor:
    		case Osky:
    		case Ostage:
    		case Oselection:				category = cinema::OBJECTCATEGORY::SCENE; break;
    
    		case Oworkplane:				category = cinema::OBJECTCATEGORY::GRID; break;
    
    		case 1001414: // ID_TP_PARTICLEGEOMETRY
    		case Ofpgroup: // new particle group
    		case Ofpmultigroup: // new particle multi group
    		case Oparticle:					category = cinema::OBJECTCATEGORY::PARTICLE; break;
    
    		case Ovolume:
    		case Osimulationscene:
    		// ITEM#128166 Alembic: Viewport and Selection Filter recognizes it as Spline
    		case Oalembicgenerator:	category = cinema::OBJECTCATEGORY::GENERATOR; break;
    
    			// ITEM#9437 MoGraph and Spline Display Filter
    		case Omgcloner:					category = cinema::OBJECTCATEGORY::GENERATOR; break; // Omgcloner
    		case Ohair: 						category = cinema::OBJECTCATEGORY::HAIR; break; // Ohair
    
    		default:
    		{
    			// This function is not exposed in Python, so use objectInfo directly
    			if (!cinema::C4DOS_Bo->ObjectTypeToCategory(type, category))
    			{
    				// ITEM#133494 Spline filter doesn't filter splines
    				// i gave ISSPLINE the highest priority back and added switches for alembic and cloner generators above
    				if (objectInfo & OBJECT_ISSPLINE)
    					category = cinema::OBJECTCATEGORY::SPLINE;
    				else if (objectInfo & OBJECT_GENERATOR)
    					category = cinema::OBJECTCATEGORY::GENERATOR;
    				else if (objectInfo & OBJECT_MODIFIER)
    					category = cinema::OBJECTCATEGORY::DEFORMER;
    				else if (objectInfo & OBJECT_PARTICLEMODIFIER)
    					category = cinema::OBJECTCATEGORY::PARTICLE;
    				else if (objectInfo & OBJECT_FIELDOBJECT)
    					category = cinema::OBJECTCATEGORY::FIELD;
    			}
    			break;
    		}
    	}
    
    	return category;
    }
    

    With that's said I've made a note for myself to implement this function in Python (no ETA).
    Cheers,
    Maxime.

    posted in Cinema 4D SDK
  • RE: How to create a control and switch between multiple fields

    Hey @lanxiang I guess you are using *.res file to define the layout.
    For the red part this is the first one is defined like that:

    LONG MGCLONER_VOLUMEINSTANCES_MODE
    {
    	CUSTOMGUI QUICKTABRADIO;
    	CYCLE
    	{
    		MGCLONER_VOLUMEINSTANCES_MODE_NONE;
    		MGCLONER_VOLUMEINSTANCES_MODE_RENDERINSTANCE;
    		MGCLONER_VOLUMEINSTANCES_MODE_RENDERMULTIINSTANCE;
    	}
    }
    

    Then based on the value you will need to hide/unhide other elements within the GetDDescription method as shown here.

    The green one is a vector with a step of 1 and its defined like that:

    VECTOR	MG_GRID_RESOLUTION
    {
    	MIN 1;
    	MAX 2000000;
    	STEP 1;
    	OPEN;
    }
    

    Cheers,
    Maxime.

    posted in Cinema 4D SDK
  • RE: Forcing GetDDescription() refresh in ToolData plugin (EventAdd not working)

    Hey @Viktor-Velicko I'm busy this week and I will be able to have a deeper look at it only next week.

    With that's said here are few hints (at least what I would try first).

    • Send COREMSG_CINEMA_FORCE_AM_UPDATE.
    • Call GeUpdateUI.
    • It may not be possible at all to do during a drag operation (but I will ask about it only once the two previous solutions are confirmed to not work, which I do not have the time to test right now).

    Cheers,
    Maxime

    posted in Cinema 4D SDK