Navigation

    • Register
    • Login
    • Search
    • Categories
    1. Home
    2. dskeithbuck
    • Profile
    • Following
    • Followers
    • Topics
    • Posts
    • Best
    • Groups

    dskeithbuck

    @dskeithbuck

    Creative Technologist at Buck Design. This is my work account.

    16
    Reputation
    35
    Posts
    340
    Profile views
    0
    Followers
    0
    Following
    Joined Last Online
    Website buck.tv/ Location Los Angeles, CA

    dskeithbuck Follow

    Posts made by dskeithbuck

    • RE: Left-Hand Local Coordinates to Right-Hand Local Coordinates?

      Thank you @zipit and @m_magalhaes for your replies! @zipit your response put me on the right track. The issue was that I was trying to do all of the conversions using C4D's left-handed coordinate matrices, and it was auto-fixing my "malformed" matrices.

      I ended up porting the Three.js Matrix4 and Euler classes from JS to python and using them in my script.

      So, the final code (leaving out the ported methods) looked something like:

              mg = self.op.GetMg()
              parent_mg = self.op.GetUpMg()
      
              c4d_to_three = c4d.Matrix(
                  off=c4d.Vector(0),
                  v1=c4d.Vector(1, 0, 0),
                  v2=c4d.Vector(0, 1, 0),
                  v3=c4d.Vector(0, 0, -1)
              )
      
              # Convert to new coordinate space
              # http://www.techart3d.com/2016/02/convert-left-handed-to-right-handed-coordinates/
      
              mg_three_coords = c4d_to_three * mg * c4d_to_three
              parent_mg_three_coords = c4d_to_three * parent_mg * c4d_to_three
      
              mg_mat4 = Matrix4(mg_three_coords)
              parent_mg_mat4 = Matrix4(parent_mg_three_coords)
      
              inv_parent_mg_mat4 = parent_mg_mat4.Clone()
              inv_parent_mg_mat4 = inv_parent_mg_mat4.Inverse()
      
              node_local = inv_parent_mg_mat4.Clone()
              node_local = node_local.MultiplyMatrices(inv_parent_mg_mat4, mg_mat4)
      
              position, scale, rotation = node_local.Decompose()
      
              if position != c4d.Vector(0):
                  self.props["position"] = position
      
              if scale != c4d.Vector(1):
                  self.props["scale"] = scale
      
              if rotation != c4d.Vector(0):
                  self.props["rotation"] = rotation
      
      posted in General Programming & Plugin Discussions
      dskeithbuck
    • Copying All Compatible Properties from One Object to Another?

      Hi,

      Is there a preferred way of copying all compatible object properties from an object of one type, to an object of another type? Things, I'm thinking of:

      • Name
      • Matrix
      • Layers
      • Visibility
      • Tags
      • Animation
      • Compatible Properties / Animation - So probably, PSR, but maybe also Visibility or Time Tracks, etc.

      I think I know how to approach this manually, but don't want to duplicate efforts if there's an API method, or existing snippet that does this as I'm afraid of leaving out something critical, and/or future proofing against future API additions.
      Thanks!

      Donovan

      posted in Cinema 4D Development
      dskeithbuck
    • RE: Best Practices for Reporting Errors to Users?

      @s_bach & @zipit - Thank you both for the thoughtful replies!

      I think I'll opt for the status bar for most user-facing feedback, and console logging for anything bigger. Watching the "What could possibly go wrong?" talk now.

      posted in Cinema 4D Development
      dskeithbuck
    • Best Practices for Reporting Errors to Users?

      Hi,

      I've been seeing a lot more usage of code like raise ValueError("Invalid path selected") in the Python examples. As I understand it, these errors tend to get surfaced in the Console - which is useful to developers, but less useful to Users.

      Here's a simple script that asks a user to select an image in a folder containing multiple images.

      import c4d
      
      def get_images_path():
          image_path = c4d.storage.LoadDialog(
              type=c4d.FILESELECTTYPE_IMAGES,
              title="Select an image in the folder you want to bulk rename",
              flags=c4d.FILESELECT_LOAD
          )
          
          if not image_path:
              raise ValueError("Valid image path not selected.")
          
          return image_path
      
      def main():
          images_path = get_images_path()
          
          c4d.gui.MessageDialog(images_path)
      
      if __name__=='__main__':
          main()
      

      How should I gracefuly accept a user hitting "Cancel"? How should I provide the user with a helpful error if they've selected the wrong filetype (let's for example pretend I'm looking for .png files rather than misc image formats)?

      Should we use popup messages? Status bar updates? Console prints?

      Do I need to do something like:

      try:
          images_path = get_images_path()
      except ValueError as error:
          c4d.gui.MessageDialog(error)
      

      ...every time I call any method that might throw an error? Or is there a way to auto-promote exceptions/errors as alert dialogs/status updates?
      Thanks,

      Donovan

      posted in Cinema 4D Development
      dskeithbuck
    • Selecting Objects in Order

      I encountered what I believed was a bug in doc.SetActiveObjects(object, c4d.SELECTION_ADD). The objects that I selected would return a different order than I expected when I used doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER|c4d.GETACTIVEOBJECTFLAGS_CHILDREN).


      Here's the bug report I wrote up:

      Issue

      doc.SetActiveObject() does not seem to track object selection order.

      To Reproduce

      1. Create a new project file.
      2. Add 3 cubes: A, B, C
      3. Select them in order: A, B, C
      4. Create a new script, and paste this in:
      """Name-en-US: Print and Reverse Selection
      Description-en-US: Prints the names of all objects in the order they were selected. Then attempts to reverse selection order.
      """
      
      import c4d
      from c4d import gui
      
      def main():
          # Get Active Objects
          active_objects = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER|c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
          if not active_objects:
              return
          
          # Collect the names of all objects, deselect as you go.
          names = []
          for obj in active_objects:
              names.append(obj.GetName())
              doc.SetActiveObject(obj, c4d.SELECTION_SUB)
          
          # Print the name of the objects based on their initial selection order.
          print names
          
          # Reselect the objects in reverse order
          for obj in reversed(active_objects):
              doc.SetActiveObject(obj, c4d.SELECTION_ADD)
          
          # Let C4D know something has changed
          c4d.EventAdd()
      
      if __name__=='__main__':
          main()
      
      1. Run this script.

      The console prints: ['a', 'b', 'c']

      1. Run this script again.

      BUG: The console again prints: ['a', 'b', 'c']

      EXPECTED: The console prints the new reverse selection order. ['c', 'b', 'a']

      Reference

      Documentation

      • c4d.documents.BaseDocument — Cinema 4D SDK 21.026 documentation

      Related Posts

      • active Objects SELECTIONORDER | PluginCafé
      • Adding to document selection issue | PluginCafé
      • selection order and shift-select | PluginCafé

      Turns out, it isn't a bug, but is instead a documentation issue. By calling doc.GetActiveObject() after each selection (as described here) you can update the selection caches and the selection order will appropriately update.

          # Reselect the objects in reverse order
          for obj in reversed(active_objects):
              doc.SetActiveObject(obj, c4d.SELECTION_ADD)
              
              # Update selection caches, needed if you want to use SELECTIONORDER
              # Reference: https://plugincafe.maxon.net/topic/9307/12387_adding-to-document-selection-issue/3
              doc.GetActiveObject()
      

      Request

      Please add this information to the doc.SetActiveObject, doc.SetSelection, and doc.GetActiveObjects sections of the Python and C++ documentation, and mention it as a possible fix for the Shift Select bug which I've encountered previously.

      Thank you,

      Donovan

      posted in Cinema 4D Development
      dskeithbuck
    • RE: Styling BitmapButtons

      @zipit said in Styling BitmapButtons:

      While I cannot say much about your problem, here are some bits that you might (or might not) find useful.

      Thank you for taking the time to respond!

      1. Have you tried / is it possible for your task to define your dialog resource as a file system rather than by code?

      My interface includes a large list of dynamic elements. I'm not sure that I'll be able to give each a unique ID unless I create them with code. But I'll see if I can restructure at least some of this.

      Although I cannot find the exact passage right now, it says somewhere in the docs, that you should use files, since it is the only way to get all features, except for menus, which are only constructable by code.

      Gotcha. Good to know. The docs have really improved, but there's lots of little tidbits like this that you'll only see if you read every page of every parent class rather than seeing all of the relevant info in the method definition.

      1. I came to the (unconfirmed) conclusion, that some of the more exotic CustomGui data types are apparently not fully supported for dialog resources (the same definition for a SPLINE gadget worked just fine in a description resource) and wrote my own little thing.

      Ah. That makes sense as well. I think for now I'll live with the stylistic limitations of the current BitmapButton as it's easier than writing my own.
      Thanks!

      Donovan

      posted in Cinema 4D Development
      dskeithbuck
    • Styling BitmapButtons

      Hi,

      I'm hoping to add some Bitmap Buttons to a project I'm working on, but I'm having difficulty styling them (and initially: creating them).

          def AddIconButton(self, button_id, title, icon_id, tooltip, border=c4d.BORDER_ROUND):
              """
              Reference: https://plugincafe.maxon.net/topic/11462/understanding-setcommanddragid/2
              """
      
              bc = c4d.BaseContainer()
              bc.SetBool(c4d.BITMAPBUTTON_BUTTON, True)
              bc.SetInt32(c4d.BITMAPBUTTON_ICONID1, icon_id)
              bc.SetInt32(c4d.BITMAPBUTTON_OUTBORDER, border)
              bc.SetString(c4d.BITMAPBUTTON_TOOLTIP, tooltip)
              bc.SetString(c4d.BITMAPBUTTON_STRING, title)
              icon_width_height = 24
              bc.SetInt32(c4d.BITMAPBUTTON_FORCE_SIZE, icon_width_height)
              button = self.AddCustomGui(button_id, c4d.CUSTOMGUI_BITMAPBUTTON, title, c4d.BFH_LEFT, 0, 0, bc)
      
              return button
      

      A lot of the styling flags seem to have no impact. Specifically:

      • BITMAPBUTTON_BORDER
      • BITMAPBUTTON_STRING

      I created some simple test code to show each of the styles and the buttons looked identical:

      
              bitmapbutton_border_styles = [("BORDER_NONE", c4d.BORDER_NONE),
              ("BORDER_THIN_IN", c4d.BORDER_THIN_IN),
              ("BORDER_THIN_OUT", c4d.BORDER_THIN_OUT),
              ("BORDER_IN", c4d.BORDER_IN),
              ("BORDER_OUT", c4d.BORDER_OUT),
              ("BORDER_GROUP_IN", c4d.BORDER_GROUP_IN),
              ("BORDER_GROUP_OUT", c4d.BORDER_GROUP_OUT),
              ("BORDER_OUT2", c4d.BORDER_OUT2),
              ("BORDER_OUT3", c4d.BORDER_OUT3),
              ("BORDER_BLACK", c4d.BORDER_BLACK),
              ("BORDER_ACTIVE_1", c4d.BORDER_ACTIVE_1),
              ("BORDER_ACTIVE_2", c4d.BORDER_ACTIVE_2),
              ("BORDER_ACTIVE_3", c4d.BORDER_ACTIVE_3),
              ("BORDER_ACTIVE_4", c4d.BORDER_ACTIVE_4),
              ("BORDER_GROUP_TOP", c4d.BORDER_GROUP_TOP),
              ("BORDER_ROUND", c4d.BORDER_ROUND),
              ("BORDER_SCHEME_EDIT", c4d.BORDER_SCHEME_EDIT),
              ("BORDER_SCHEME_EDIT_NUMERIC", c4d.BORDER_SCHEME_EDIT_NUMERIC),
              ("BORDER_OUT3l", c4d.BORDER_OUT3l),
              ("BORDER_OUT3r", c4d.BORDER_OUT3r),
              ("BORDER_THIN_INb", c4d.BORDER_THIN_INb),
              ("BORDER_MASK", c4d.BORDER_MASK),
              ("BORDER_TEXT_DOTTED", c4d.BORDER_TEXT_DOTTED),
              ("BORDER_WITH_TITLE_MONO", c4d.BORDER_WITH_TITLE_MONO),
              ("BORDER_WITH_TITLE_BOLD", c4d.BORDER_WITH_TITLE_BOLD),
              ("BORDER_WITH_TITLE", c4d.BORDER_WITH_TITLE)]
      
              for border_id_name, border_id in bitmapbutton_border_styles:
                  self.AddIconButton(0, border_id_name, ICON_NEW_PROJECT, border_id_name, border=border_id)
      

      Ideally, I'd love a button that matches the look/feel of docked icons w/ text:
      69f1aaa2-626f-4699-9e3e-ef2e4290bff1-image.png

      Any suggestions?

      Also, FWIW, some sample code for how to add BitmapButtons inside of the BitmapButton class would be really helpful. I struggled for quite a while trying to create a button using c4d.gui.BitmapButtonCustomGui() but was stumped when I couldn't figure out how to add it to a dialog.
      Thank you,

      Donovan

      posted in Cinema 4D Development
      dskeithbuck
    • RE: Distributing Python Plugins that have Dependencies

      @m_adam said in Distributing Python Plugins that have Dependencies:

      As an Idea but didn't try and will not have the time to do it today (so if you try, do it at your own risks), but you could try to install things using directly the python executable from the resource folder

      I'm moving onto different parts of development, but I'll likely want to investigate this later and will post an update if I do. Thanks!

      posted in Cinema 4D Development
      dskeithbuck
    • RE: Distributing Python Plugins that have Dependencies

      @m_adam said in Distributing Python Plugins that have Dependencies:

      While I know is not the perfect solution, and is a really quick and dirty I would say find bellow a script to setup a build with some python module.

      Hi Maxime,

      Your script got me most of the way there, but I ran into some issues.

      It installs libraries to the default location for system python libraries rather than C4D's lib folder. So, I started modifying it to install to userprefs/python27/lib using the --target flag. But I ran into issues with spaces in the pathname. Which led me to switch to calling with a list of commands/parameters rather than a string.

      After some tweaking, I arrived at this:

      """Install C4D Python Modules
      Installs python modules in C4D's embedded python installation
      
      ## Authors
      
      Maxime of Maxon Computer
      Donovan Keith of Buck Design
      
      Reference:
      https://plugincafe.maxon.net/topic/11775/distributing-python-plugins-that-have-dependencies/8
      
      ## Requirements
      
      Cinema 4D R21+
      
      ## Usage Instructions
      
      1. Copy this file onto your desktop.
      2. Open `terminal` (Mac OS) or `cmd` (Windows) and navigate to your Cinema 4D installation.
      3. `$ c4dpy`
          1. This will run a Cinema 4D specific version of python.
          2. If prompted to login, do so.
          3. If you get an HTTP/authentication error:
              1. Open Cinema 4D.
              2. Edit > Preferences and click on "Open Preferences Folder"
              3. Go up one level in finder/explorer.
              4. Look for a folder with the same name and a `_p` suffix.
              5. Delete this folder.
              6. Try running `c4dpy` again from the console.
      4. Exit the interactive python session by typing `exit()`
      5. Run this script.  
      `>>> c4dpy "/path/to/this/script/install_c4dpy_modules.py"`
      
      ## Known Limitations
      
      1. Hasn't been tested on Mac OS
      2. Is checking the `c4dpy` installation for whether a python module has been installed, but is installing in the `c4d` installation.
      
      """
      
      print "C4D Py Requirements Installer"
      
      import subprocess
      import urllib
      import sys
      import c4d
      import os
      import ssl
      import shutil
      import importlib
      import maxon
      
      currentDir = os.path.dirname(__file__)
      c4dpyPath = sys.executable
      c4dpyTempFolder = c4d.storage.GeGetC4DPath(c4d.C4D_PATH_STARTUPWRITE)
      
      # Because we're using `c4dpy`, it will pull a different prefs folder from the user's c4d installation.
      # `Maxon Cinema 4D R21_64C2B3BD_p` becomes `Maxon Cinema 4D R21_64C2B3BD`
      c4dTempFolder = c4dpyTempFolder[:-2] if c4dpyTempFolder.endswith("_p") else c4dpyTempFolder
      
      c4dDir = maxon.Application.GetUrl(maxon.APPLICATION_URLTYPE.STARTUP_DIR).GetPath()
      if os.name != "nt" and not c4dDir.startswith(os.sep):
          c4dDir = os.sep + c4dDir
      
      # Just using the `--user` flag with `pip` will install to the System python dir, rather than the
      # C4D embedded python's dir. So we specify exactly where we want to install.
      c4dUserPythonLibPath = os.path.join(c4dTempFolder, "python27", "libs")
      
      try:
          import pip
          print "pip already installed"
      except ImportError:
          print('start downloading get-pip.py')
      
          url = 'https://bootstrap.pypa.io/get-pip.py'
          pipPath = os.path.join(c4dTempFolder, "get-pip.py")
      
          # Quick hack for MAC until https://plugincafe.maxon.net/topic/11370/urllib2-urlopen-fails-on-c4d-for-mac/8 is fixed
          f = os.path.join(c4dDir, "resource", "ssl", "cacert.pem")
          context = ssl.create_default_context(cafile=f)
      
          # Downloads pip
          urllib.urlretrieve(url, pipPath, context=context)
      
          print('start installing pip')
      
          if os.name == "nt":
              subprocess.call([c4dpyPath, pipPath, "--no-warn-script-location", "--user"], shell=True)
          else:
              os.system("{0} {1} {2} {3}".format(c4dpyPath, pipPath, "--no-warn-script-location", "--user"))
      
          shutil.rmtree(pipPath, ignore_errors=True)
      
      def installModule(moduleName):
          try:
              importlib.import_module(moduleName)
              print "{0} already installed".format(moduleName)
          except ImportError:
      
              try:
                  print('start installing {0}'.format(moduleName))
      
                  # We build up the command as list rather than a string so that Windows will properly handle paths that include spaces
                  cmd = [c4dpyPath, '-m', 'pip', 'install', moduleName, '--target', c4dUserPythonLibPath]
                  subprocess.call(cmd)
              except subprocess.CalledProcessError as e:
                  print e.output
              else:
                  print('{0} installation is done'.format(moduleName))
      
      # Add any modules you want installed to this list.
      required_modules = ["requests"]
      
      for module in required_modules:
          installModule(module)
      
      posted in Cinema 4D Development
      dskeithbuck
    • RE: Distributing Python Plugins that have Dependencies

      @m_adam It's a _p suffix. Deleting it eliminated the error messages and allowed me to log in. Thank you!

      Is there any chance that c4dpy.exe can get its license from the C4D installation without having to manually enter it in the command prompt? It's not too much of an issue for me, but I imagine seeing the terminal will be a bit intimidating for end users.

      posted in Cinema 4D Development
      dskeithbuck