Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Unread
    • Recent
    • Tags
    • Users
    • Login
    1. Maxon Developers Forum
    2. CJtheTiger
    • Profile
    • Following 0
    • Followers 0
    • Topics 17
    • Posts 53
    • Best 6
    • Controversial 0
    • Groups 0

    CJtheTiger

    @CJtheTiger

    9
    Reputation
    17
    Profile views
    53
    Posts
    0
    Followers
    0
    Following
    Joined Last Online

    CJtheTiger Unfollow Follow

    Best posts made by CJtheTiger

    • Free Python Plugin: Quad Disc

      Quad Disc

      After more time than I'd like to admit I'd like to show you my Quad Disc plugin.
      The plugin creates a primitive disc which consists solely of quads. No more triangles, no more weird deformations with Deformers, no more artefacts when using a Subdivision Surface.
      ccc1308d-8e89-4c89-aeb0-c0521b3d458c-Cinema_4D_m3jQZixz2X.gif
      https://github.com/YoYoFreakCJ/C4D-QuadDisc

      Installation and Usage

      Installation and usage instructions are in the GitHub readme.

      Comment

      Why not simply use a default Disc with a Remesher you ask? Because that would trigger a remesh everytime the radius of the disc changes, and controlling the poly count would be annoying. I ran into this a lot with a rig I'm working on right now, and that's why I created this plugin.

      I hope something like this will ship natively with a future C4D version. Feel free to steal this code. Just don't sell it.

      posted in General Talk plugin-information download
      CJtheTigerC
      CJtheTiger
    • RE: Error when removing bound joints

      Addition

      The error does not occur immediately when Remove is being called but after all operations of the plugin are finished and Cinema 4D takes over again.

      Code

      I uploaded the code to GitHub:
      https://github.com/YoYoFreakCJ/C4D-AdvancedIkSpline
      Note: the framework code is excluded.

      To reproduce the issue with this code:

      1. Build and run the plugin.
      2. In C4D in the Extensions menu choose Advanced IK Spline.
      3. In the Advanced IK Spline change the mode to Bind to make the joints show up in the object manager.
      4. Bind the joints to any geometry.
      5. In the Advanced IK Spline change the mode back to Setup.
      6. Change any of the parameters (e.g. Width).

      The plugin will crash after this.

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger
    • RE: Register to object change

      I would like to add the solution I chose to close this topic.

      The Tag Solution did indeed work very well. Execute got called plenty of times and I was easily able to filter for the change of the object I was looking for.

      But what I actually did was create a whole new class deriving from ObjectData and listen to the message MSG_DESCRIPTION_POSTSETPARAMETER to perform my actions right then. In the first post I said that my plugin creates a null. I replaced all those nulls with my new ObjectData class. So my object hierarchy is similiar to this:

      • MainPluginObject : ObjectData -> Needs to be accessed by HelperObject.
        • HelperObject : ObjectData -> Accesses MainPluginObject in its Message function.

      Also note that thread awareness is required.

      The reason I chose this structure this is the following:

      I would like the user to Current State to Object my MainPluginObject. Before that the HelperObject might not be visible in the object manager depending on the values of some properties in MainPluginObject. But once CSTO is called I want all objects to be shown in the object manager. If I had chosen the Tag Solution the user might delete the tag by accident and break everything. Now of course the user can still break things by deleting or moving the HelperObject out of the hierarchy of MainPluginObject but this I already have to validate anyways. Having a similiar check in a tag just adds another point of error which I'd like to avoid.

      So I'll mark this as my answer after taking everything @ferdinand said into consideration.

      Again, thank you very much for your assistance! You never let us down! ❤

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger
    • RE: NewObj error: no suitable conversion

      Hi @i_mazlov,

      no wonder I felt like I was missing something! And now that the iferr_scope is declared I have to bubble it up all the way and that's how I'll get proper error handling. Sweet!

      To be honest I read about error handling before but was hoping I could skip it during the prototyping phase. But now that I gotta do it, once I'm out of prototyping it'll even be a lot more stable. Plus I also gotta learn about References now which is probably a good idea.

      2c37be53-a3ce-4695-afdb-ab8d3410d19b-image.png

      Thanks for the quick help! Have a nice rest of the day!

      Best wishes,

      CJ

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger
    • RE: Python: Localized Plugin Name

      It seems like I found the working combination.

      The STRINGTABLE in res\strings_en-US\c4d_strings.str must not be named. In my previous response I pasted the contents which as you can see started with STRINGTABLE omyobject whereas it should simply be STRINGTABLE without a name.

      • I guess this str-file cannot contain named STRINGTABLEs. My first suspicion was that the error was caused by trying to define two STRINGTABLEs with the same name (since I had another STRINGTABLE with that name in the description folder) but even choosing another name in the c4d_strings.str resulted in the same error. Only when removing the name did I get rid of the error.

      I added the plugin ID to both the res\c4d_symbols.h and the res\description\omyobject.h as an enum member called Omyobject.

      • Having the ID in res\c4d_symbols.h allows for having a value outside of description resources (i.e. dialogs) which I required for the plugin name, as @m_adam already explained.
      • Having the ID in res\description\omyobject.h allows for description resources to access an accordingly named value in the STRINGTABLE in res\strings_xx-XX\description\omyobject.str. It also defines c4d.Omyobject which I'm now using in c4d.plugins.RegisterObjectPlugin to access the plugin name in res\strings_xx-XX\c4d_strings.str.
        • Note that this requires the plugin ID to be defined in two places (res\c4d_symbols.h and res\description\omyobject.h) which decreases maintainability.
        • Also note that I'm using the plugin ID from the description header to get a value from a non-description STRINGTABLE which sure enough does work but is probably not the cleanest solution.

      Does this sort of sound right? Is this the intended way?

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger
    • RE: How to properly load async data into C++ generator plugin?

      Good morning @Havremunken,

      Is there a particular message I am listening for? Or is there another way to be notified of a parameter change outside of GetVirtualObjects?

      In order to listen to parameter changes you can implement the Message function in your plugin and listen for the MSG_DESCRIPTION_POSTSETPARAMETER message. Cast the incoming data argument to DescriptionPostSetValue to extract the ID of the changed parameter and get the value through the node argument.

      Sample code taken from here and adjusted slightly:

      Bool OMyPlugin::Message(GeListNode* node, Int32 type, void* data) {
          BaseContainer* bc;
          ...
          if (type == MSG_DESCRIPTION_POSTSETPARAMETER)
          {
              // Get message data
              DescriptionPostSetValue* dparm = (DescriptionPostSetValue*)data;
              Int32 parameterId = (*(dparm->descid))[0].id;
              bc = static_cast<BaseObject*>(node)->GetDataInstance();
      
              switch (parameterId)
              {
              case FILE_PATH: // Check for the parameter ID of the file path parameter.
                  // Load the file via bc->GetString(FILE_PATH).
                  ApplicationOutput(bc->GetString(FILE_PATH));
                  break;
              }
          }
          ...
      }
      

      This way you could also initiate the cancellation of the current loading process in case the file path is being changed while a file is already being loaded. Alternatively you could lock the parameter in the description using GetDEnabling while the file is being loaded.

      From a UX point of view I recommend indicating the current processing status to the user so the user knows whether a file is loading or if the loading has finished/failed.

      And a personal flavor hint: I'm used to providing control buttons (Load/Start/Import/... and Cancel) for potential long running operations. This way it is clear and intuitive to the user how to use your object.

      Cheers,
      Daniel

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger

    Latest posts made by CJtheTiger

    • RE: Reset Tool in Interaction Tag

      Hi @ferdinand,

      Thank you for another great response.

      Yes, my question is indeed about how to reset the tool to the state it had before my script tinkered with it. I was hoping there is an easy way to do this judging by the doc paragraph I quoted, but it sounds like there isn't. I'll try and be clearer about my question in the future.


      Your two suggestions both seem fine, so thank you very much for those!

      Being responsible with what we know is part of what makes this job fun. 🙂


      This here:

      I'm also not sure whether this shared-axis-behavior is intentional?

      was aimed towards the behavior and intention of the Interaction Tag, not towards the default value of the tool flag. Let me explain:

      From my understanding one of the purposes of the Interaction Tag is to manipulate the object Proxy through the object Controller which holds the Interaction Tag. Since the tag effectively prevents the Controller from being manipulated and instead directs all of this to Proxy I believe that the origin of the manipulation matrix should not be in the middle of Proxy and Controller (which it is as you can see in my previous gif) but directly on the object axis of Proxy instead.

      But if this should be a support ticket instead of a forum discussion I can go ahead and do that of course, I just figured you might know of a good reason why the way it is may actually be intended.

      Cheers,
      Daniel

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger
    • RE: How to properly load async data into C++ generator plugin?

      Hi guys,

      I might be completely off here but let me throw this into the room:

      • These two lines in Message of the SceneHookData of @ferdinand 's pseudo code will block the current thread:
            while (AssetIsLoading(url))
              continue;
      
      • This means that Message in the SceneHookData will only ever finish once the asset has in fact finished loading.
      • This means that GVO of the calling ObjectData will also block until it has finished loading.

      Let's just assume the worst case where a user creates a large number of ObjectDatas and sets all their URLs at once to different values, and loading and processing a single URL would take multiple seconds. Would this cause issues for C4D since so many threads would be blocking?

      Please correct me if I misunderstood something.

      Cheers,
      Daniel

      EDIT
      Adding to @Havremunken 's response: Threads like this one really help a ton for building up that basic understanding of how C4D works, and @ferdinand provides in-depth answers explaining everything we need to know. I can't thank this man enough.

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger
    • Reset Tool in Interaction Tag

      Hello coders,

      I was playing around with the Interaction Tag. I'll call the object holding the Interaction Tag the Controller and the proxy object the Proxy. Here's what I want to do:

      • When the user drags the Controller using the left mouse button the Proxy should be moved.
      • When the user drags the Controller using the right mouse button the Proxy should be scaled.

      So first I enable Use Right Mouse Button in the Output tab, then in the Scripting tab in the Python script in mouseDown() I check for the right mouse button and eventually set the relevant tool:

      def mouseDown():    
              bc = c4d.BaseContainer()    
              c4d.gui.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, bc)    
              right_mouse_down = bc[c4d.BFM_INPUT_VALUE] == True  
          
              if right_mouse_down:    
                  doc.SetAction(200000089) # Scale    
              else:    
                  doc.SetAction(200000088) # Move
      

      So far so good. But when right-click-dragging the Proxy will be scaled using the shared axis of the Proxy and the Controller:
      1c6c1554-adca-4d51-8634-a821e46c419d-Cinema_4D_pl2YX5S7O9.gif
      This already seemed weird but I tried to work around it because obviously I want to scale the object on its own axis. So I added this line to the script to enable the Per-Object Transform of the scale tool after activating it:

              tool_data = doc.GetActiveToolData()
              tool_data[c4d.MDATA_AXIS_LOCALMANIPULATION] = True
      

      eb64552e-5282-4ae8-ada0-4f12b896a3da-Cinema_4D_fFuoaUeo2J.gif

      Works fine. However being a good little developer I want to reset the tool to its original state after we're done. The C4D help states for the function mouseUp() of the Interaction Tag:

      mouseUp
      This is called when the user releases the mouse button and signals the end of the user interaction. You can reset tools or kill off any memory-hungry objects that have been allocated in mouseDown or mouseDrag at this point.

      But how do I reset the tool? I do not want to just set the flag to False since the user might've set it to True deliberately regardless of my Interaction Tag so it should regain this expected state.


      Demo

      Here's a simple demo scene (2024.2.0) on my personal OneDrive:
      https://1drv.ms/u/s!At78FKXjEGEomLQx70lV4DUgjN5dbw?e=iwNum5

      To see the behavior:

      1. Activate the Scale tool.
      2. Set the flag Per-Object-Transform to True.
      3. Activate another tool (for example move or rotate).
      4. Make sure no object is selected, then right-click-drag the sphere which will scale the cube.
      5. After releasing the right mouse button, activate the Scale tool and check the flag Per-Object-Transform. It is now unset.

      Intentional Shared-Axis-Behavior?

      I'm also not sure whether this shared-axis-behavior is intentional? Because if you're trying to manipulate a proxy using this tag you most likely want to do it while completely disregarding the axis of the object holding the tag. Correct me if I'm wrong.

      Cheers,
      Daniel

      posted in Cinema 4D SDK 2024 python
      CJtheTigerC
      CJtheTiger
    • RE: How to properly load async data into C++ generator plugin?

      Good morning @Havremunken,

      Is there a particular message I am listening for? Or is there another way to be notified of a parameter change outside of GetVirtualObjects?

      In order to listen to parameter changes you can implement the Message function in your plugin and listen for the MSG_DESCRIPTION_POSTSETPARAMETER message. Cast the incoming data argument to DescriptionPostSetValue to extract the ID of the changed parameter and get the value through the node argument.

      Sample code taken from here and adjusted slightly:

      Bool OMyPlugin::Message(GeListNode* node, Int32 type, void* data) {
          BaseContainer* bc;
          ...
          if (type == MSG_DESCRIPTION_POSTSETPARAMETER)
          {
              // Get message data
              DescriptionPostSetValue* dparm = (DescriptionPostSetValue*)data;
              Int32 parameterId = (*(dparm->descid))[0].id;
              bc = static_cast<BaseObject*>(node)->GetDataInstance();
      
              switch (parameterId)
              {
              case FILE_PATH: // Check for the parameter ID of the file path parameter.
                  // Load the file via bc->GetString(FILE_PATH).
                  ApplicationOutput(bc->GetString(FILE_PATH));
                  break;
              }
          }
          ...
      }
      

      This way you could also initiate the cancellation of the current loading process in case the file path is being changed while a file is already being loaded. Alternatively you could lock the parameter in the description using GetDEnabling while the file is being loaded.

      From a UX point of view I recommend indicating the current processing status to the user so the user knows whether a file is loading or if the loading has finished/failed.

      And a personal flavor hint: I'm used to providing control buttons (Load/Start/Import/... and Cancel) for potential long running operations. This way it is clear and intuitive to the user how to use your object.

      Cheers,
      Daniel

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger
    • RE: 3D -> 2D and back with perspective

      Good morning Ilia,

      Thanks for your detailed response even though this is out of support scope!


      Let me explain what I meant by this:

      involve more computations than I'd like it to

      Using ProjectPointOnLine() the calculations by the script are as follows:

      1. Transform 3D points to 2D screen space using WS().
      2. Calculate my desired new point in screen space.
      3. Transform the new point to 3D world space by projecting the new point onto the 3D line using ProjectPointOnLine().
      4. Transform the new point back from 3D world space to 2D screen space using WS().
      5. Do more things using these correct 2D coordinates of the new point.

      The issue of this topic is addressed by step 3 but to further work properly with the point in 2D space I also need step 4. I in fact do not only need the 2D coordinates but the z component as well. I feel like there must be a mathematical formula to get the correct Z value without transforming all three vector components back and forth in steps 3 and 4. So the issue I have with the "hacky" solution is purely to reduce the amount of performed calculations to a bare minimum which to be completely honest is just a means to satisfy this itch of mine to optimize as much as possible. It's part of what makes programming fun to me. 🙂

      Also it would really be interesting to grasp the mathematical concept behind this. As someone who taught himself 3D programming by reading a bunch of game dev tutorials 15 years ago this is super interesting. But enough rambling.


      Regarding your question:

      Why can't you first do your computations in screen space and only then use the ProjectPointOnLine() function?

      In my example the script I'm currently working on takes the new point as a base and creates some more points around that which are not on the line so I can't use ProjectPointOnLine() for those. For that the new point must have the correct coordinates already so I can derive the other new points correctly, otherwise their position in the world will be skewed as well.


      The frustum concept is something I'll have to take some time to fully understand how I can utilize that. Thanks for the hint.

      Have a nice weekend,
      Daniel

      posted in General Talk
      CJtheTigerC
      CJtheTiger
    • RE: 3D -> 2D and back with perspective

      Hacky Solution

      I stumbled across BaseView.ProjectPointOnLine. This takes a line in 3D space and tries to project "a given mouse coordinate" onto it. Don't know why they'd specifically point out that this is for mouse coordinates when it seems to also work for any camera screen coordinates.

      So here's what I did:

      1. Calculate the screen position of the desired point new_point_s.
      2. Call BaseView.ProjectPointOnLine. Use the world position of Start and Target for the line and new_point_s for the point to project onto the line like this:
      new_point_w = bd.ProjectPointOnLine(start_pos_w, target_pos_w - start_pos_w, new_point_s.x, new_point_s.y)
      

      While this technically works it does involve more computations than I'd like it to because it does include all of this overhead of projecting that screen position onto the line which I don't believe to be the best solution to this. Imagine I actually want to work with the point in screen space before translating it back to world space, I'd have to do another round-trip of WS-ing (to actually get the correct new_point_s with the correct z component) and finally SW-ing that back to world again. Yikes.

      Proper

      I stumbled across this document which I think talks about what I need:
      https://www.comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf
      And while I don't expect you to read through those two pages what is important is (12) on page 2 which I think should translate to this:

      new_z = 1.0 / ( (1.0 / start_pos_w.z) + length_s_flat_delta * ( (1.0 - target_pos_w.z) - (1.0 / start_pos_w.z) ) )
      

      However this does not produce the correct result. Isn't this document talking about what I need?

      Cheers,
      Daniel

      posted in General Talk
      CJtheTigerC
      CJtheTiger
    • 3D -> 2D and back with perspective

      Hello coders,

      since this is a question about algorithms I figured I'd post this here. Also this is mostly me hoping that someone will drop some wisdom on me in terms of 3D programming.

      Problem

      So I got two different positions in 3D space, Start and Target. When I look at them in the C4D viewport I need to calculate a new point which is between the two points, but 30 pixels before the target.

      Take a look at this example where the distance in pixels on the screen is 298, so I need a new point which is 268 pixels away from Start (298px - 30px), blue X marks the desired spot.
      af599930-9005-43c7-a0e6-19532e6b5b02-image.png
      However this point must exist in world space.

      So my thought process was this:

      1. Get screen coordinates of both points using BaseView.WS.
      2. Calculate how far away the new point is from Start in percent relative to the screen distance to Target.
      3. Create a new point in screen space using this calculated ratio by doing Start + Direction * Distance * Ratio.
      4. Transform to world coordinates using BaseView.SW.

      The new point will be in the correct screen coordinates, so x and y are fine. However z is messed up which I can only assume is because of the camera perspective.
      1ed14989-5288-47c1-8dd0-f41c3f3ab471-image.png
      Note in the following demo how both the target and the new point are always the same distance apart from each other in Perspective View while the z coordinate in Top View prevents the point from being on the desired pink line between the two points:
      52d7d72d-d3aa-4632-8644-ddff9f0bba7f-Cinema_4D_VliCT7fk6O.gif
      This gets more apparent when moving the target closer to the camera.

      Using a parallel camera (instead of perspective) of course does not have this issue.
      778887e1-a768-47c1-8b5b-e29779e56232-Cinema_4D_RryWEthfdm.gif
      See how it's always nicely on the pink line?

      And that's where I'm stuck. How do I fix this? Can anyone give me a hint?

      Here's my code and a demo scene.

      import c4d
      import math
      
      doc: c4d.documents.BaseDocument # The document the object `op` is contained in.
      op: c4d.BaseObject # The Python generator object holding this code.
      hh: "PyCapsule" # A HierarchyHelp object, only defined when main is executed.
      
      def main() -> c4d.BaseObject:
          bd = doc.GetRenderBaseDraw()
          if bd is None:
              return c4d.BaseObject(c4d.Onull)
      
          # Note for variable names postix:
          # > _w      = World
          # > _s      = Screen
          # > _s_flat = Screen without z coordinate
      
          # World positions of start and target.
          start_pos_w = doc.SearchObject('Start').GetAbsPos()
          target_pos_w = doc.SearchObject('Target').GetAbsPos()
      
          # The distance in pixels the new point should be away from point 2.
          distance_from_target = 30.0
      
          # Calculate the screen coordinates of the points.
          start_pos_s = bd.WS(start_pos_w)
          target_pos_s = bd.WS(target_pos_w)
      
          # Calling WS creates a z coordinate to describe the distance of the
          # point to the camera. To correctly calculate the 2D distance the
          # z axis must be ignored.
          start_pos_s_flat = c4d.Vector(start_pos_s.x, start_pos_s.y, 0)
          target_pos_s_flat = c4d.Vector(target_pos_s.x, target_pos_s.y, 0)
      
          # Get the direction and distance of both points in flat screen space.
          direction_s_flat = (target_pos_s_flat - start_pos_s_flat).GetNormalized()
          length_s_flat = (target_pos_s_flat - start_pos_s_flat).GetLength()
      
          # Calculate the position of the new point in screen space with respect
          # to the z coordinate:
          # 1.  Calculate the ratio how far away the new point is from the
          #     target in percent.
          # 1.a Subtract the distance_from_target from the flat length.
          length_s_flat_delta = length_s_flat - distance_from_target
          # 1.b Divide delta by the flat length to get the ratio.
          ratio = length_s_flat_delta / length_s_flat
          # 2.  Calculate the new point in "deep" screen space.
          # 2.a Get direction and length of the screen space positions.
          direction_s = (target_pos_s - start_pos_s).GetNormalized()
          length_s = (target_pos_s - start_pos_s).GetLength()
          # 2.b Use the ratio to calculate the new point in "deep" screen space.
          new_point_s = start_pos_s + (direction_s * length_s * ratio)
      
          # Transform the new point to world coordinates.
          new_point_w = bd.SW(new_point_s)
      
          # Create some output to visualize the result.
          points = [start_pos_w, new_point_w, target_pos_w]
      
          poly_obj = c4d.BaseObject(c4d.Opolygon)
          poly_obj.ResizeObject(len(points), 0)
      
          for i, point in enumerate(points):
              poly_obj.SetPoint(i, point)
      
          return poly_obj
      

      Link to demo scene (2024.2.0) on my OneDrive:
      https://1drv.ms/u/s!At78FKXjEGEomLMOaoZcJaA2TSwblg?e=8wfev1

      Cheers,
      Daniel

      posted in General Talk programming
      CJtheTigerC
      CJtheTiger
    • RE: Object Plugin - Generator Objects - Hiding the child objects in Editor View

      Hi @ThomasB,

      ~~these generators are (probably) plugins of type ObjectData. These plugins implement the GetVirtualObjects method which returns what will be drawn on the screen, and only this will be drawn with complete disregard of the children. They do use their children as inputs to generate the result which you then see but the children themselves are not returned by GetVirtualObjects.

      See also the documentation on ObjectData.~~

      Seems I was wrong about this, sorry. Maybe this plays together with TouchDependenceList but I'm not sure so I'll just keep my mouth shut now.

      Cheers,
      Daniel

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger
    • RE: Controlling drag&drop using MSG_DESCRIPTION_CHECKDRAGANDDROP

      Hi @m_adam,

      thanks a lot, that did the trick.

      To close this topic off here's a snippet to allow other objects but not the current object to be dropped in there:

      def Message(self, node: GeListNode, type: int, data: object) -> bool:
      
          if type == c4d.MSG_DESCRIPTION_CHECKDRAGANDDROP:
      
              relevant_id = c4d.DescID(c4d.YOUR_PARAM) # Use the ID of the parameter of your object that you want to check.
              current_id: c4d.DescID = data['id']
              
              if relevant_id.IsPartOf(current_id)[0]:        
                  dragged_element = data["element"]        
                  is_same_object = node == dragged_element        
                  data['result'] = not is_same_object        
                  return True
              
              return True
      

      Cheers,
      Daniel

      posted in Cinema 4D SDK
      CJtheTigerC
      CJtheTiger
    • RE: Python Linting in VSCode?

      Hi @m_adam,

      I coincidentally followed the steps on there when I installed the VS Code extension way back. It's pretty straight forward.

      PyLint in VS Code immediately ramped up my CPU usage so I got rid of that extension. But maybe there's a trick that someone else knows about that could help me out. I'm always open for suggestions and alternatives.

      What IDE are you using if I may ask? Did you setup anything special to make life easier?

      Best regards,
      Daniel

      posted in General Talk
      CJtheTigerC
      CJtheTiger