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
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    "Frame Selected Elements" command not working when running script through c4dpy?

    Cinema 4D SDK
    3
    6
    1.1k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • O
      OblivionDawn
      last edited by OblivionDawn

      Hi, I'm a Cinema 4D veteran but I'm very new to Python and coding in general.

      I've made a little script that imports an .obj file into a specific scene, frames the geometry, and then renders it. When I run it in script manager it works great. However, when I run it via c4dpy/commandline, everything works fine except for the "Frame Selected Elements" command.

      c4d.CallCommand(13038) # Frame Selected Elements
      

      It just seems to ignore that command and then goes on to render the .obj at whatever size it was imported as.

      As I said, I'm still new to programming/scripting, but I'm assuming that since there's no GUI/viewport when running a script through c4dpy, the Frame Selected Elements command simply isn't available. I could be completely wrong though.

      Does anyone know if there's a workaround for this, or if I'm simply tackling the problem the wrong way?

      1 Reply Last reply Reply Quote 0
      • M
        mp5gosu
        last edited by mp5gosu

        Maybe this script helps you to frame your objects manually. The called helper methods should be self-explanatory.
        If you have any questions, feel free to ask.

        import c4d
        import sys
        import math
         
        from .. import Helpers
         
         
        class FramedCamera(object):
            """
            Modifies a given camera to frame a given object.
            The object to frame must be a clone since it is modified
            """
         
         
            def __init__(self, frame_obj, camera, azimuth=00, elevation=0):
                self.frame_object(frame_obj, camera, azimuth, elevation)
         
            # calculate the bounding sphere based on the camera FOV
         
            def calc_bsphere(self, op, camera):
                """Calculates a bounding sphere from a given object considering the FOV
         
                Args:
                    op (c4d.BaseObject): The object to calculate the bounding sphere from
                    camera (c4d.BaseObject): The camera to get the FOV from
         
                Returns:
                    list: The bounding sphere center and radius
                """
         
                points = Helpers.GetAllPoints(op)
                if not points:
                    raise ValueError("Object {} has no points".format(op.GetName()))
         
                p_min = c4d.Vector(sys.float_info.max)
                p_max = c4d.Vector(-sys.float_info.max)
         
                for p in points:
                    p_min = c4d.Vector(min(p.x, p_min.x), min(p.y, p_min.y), min(p.z, p_min.z))
                    p_max = c4d.Vector(max(p.x, p_max.x), max(p.y, p_max.y), max(p.z, p_max.z))
         
                center = (p_min + p_max) * 0.5
                radius = 0
                for p in points:
                    radius = max(radius, (center - p).GetLength())
         
                fov_h = camera[c4d.CAMERAOBJECT_FOV]
                fov_v = camera[c4d.CAMERAOBJECT_FOV_VERTICAL]
                radius = (radius * 1.1) / math.sin(max(fov_h, fov_v) * 0.5)
                return center, radius
         
            # LookAt function
            def look_at(self, origin, target):
                """
                Creates an orientation matrix from two vectors
         
                Args:
                    origin (c4d.Vector): The Vector to be used to orient
                    target (c4d.Vector): The target vector
         
                Returns:
                    c4d.Matrix: The orientation matrix
                """
                mat = c4d.Matrix()
                up_temp = c4d.Vector(0, 1.0, 0)
         
                v_fwd = (target - origin).GetNormalized()
                v_right = up_temp.Cross(v_fwd).GetNormalized()
                v_up = v_fwd.Cross(v_right).GetNormalized()
         
                mat.off = origin
                mat.v1 = v_right
                mat.v2 = v_up
                mat.v3 = v_fwd
         
                return mat
         
            # frame objects
            def frame_object(self, op, camera, elevation, azimuth):
                """
                Places and orients a camera to fit a given object An optional angle (H,P) can be given.
         
                Args:
                    op (c4d.BaseObject): The object to frame
                    camera (c4d.BaseObject): The camera that is used
                    elevation (float): Camera Heading, in radians
                    azimuth (float): Camera Pitch, in radians
         
                Returns:
                    bool: True for success, False otherwise.
                """
         
                Helpers.convertInstances(op)
         
                # Get bounding sphere depending on camera FOV
                b_sphere = self.calc_bsphere(op, camera)
                if not b_sphere:
                    raise ValueError("Bounding sphere cannot be calculated")
         
                # Set camera position and direction
                center, radius = b_sphere
                camera.SetMg(c4d.Matrix())
                cam_pos = c4d.Vector(center)
                cam_pos.x = cam_pos.x + math.cos(azimuth) * math.sin(elevation) * radius
                cam_pos.y = cam_pos.y + math.sin(azimuth) * radius
                cam_pos.z = cam_pos.z + math.cos(azimuth) * math.cos(elevation) * -radius
         
                camera.SetMg(self.look_at(cam_pos, center))
        

        Side note. This code comes from a prototype, therefore it is not optimized/comes with some syntactical flaws.

        1 Reply Last reply Reply Quote 0
        • ferdinandF
          ferdinand
          last edited by ferdinand

          Hi @OblivionDawn,

          thank you for reaching out to us and thank you @mp5gosu for providing a workaround. Principally speaking, cdpy comes with BaseDraw instances, i.e., BaseView instances, i.e., view ports and a camera attached to them, which is required to carry out the framing of an object. But since c4dpy is GUI-less, they are being initialized with a "null"-frame so to speak, i.e., a frame with zero dimensions. The script attached to the end of the post will spit out something like this:

          Active BaseDraw safe-frame: {'cl': 0, 'ct': 0, 'cr': 0, 'cb': 0}

          Although unlikely, this could be a reason why 'Frame Selected Elements' is failing for you. It will probably take me some time to investigate this and all possibilities of failure. I will report back once I have come to a conclusion.

          Cheers,
          Ferdinand

          The little c4dpy script:

          import c4d
          import os
          
          
          def main():
              """
              """
              path = os.path.split(__file__)[0]
              file = os.path.join(path, "cube.c4d")
              doc = c4d.documents.LoadDocument(file, c4d.SCENEFILTER_OBJECTS, None)
              if doc is None:
                  raise RuntimeError("Could not load document.")
              bd = doc.GetActiveBaseDraw()
              if bd is not None:
                  print ("Active BaseDraw safe-frame:", bd.GetSafeFrame())
              c4d.documents.InsertBaseDocument(doc)
              c4d.documents.SetActiveDocument(doc)
              c4d.CallCommand(13038)
              doc = c4d.documents.GetActiveDocument()
              print ("Active document state:", doc)
              file = os.path.join(path, "cube_mod.c4d")
              res = c4d.documents.SaveDocument(doc, file, c4d.SAVEDOCUMENTFLAGS_NONE,
                                               c4d.FORMAT_C4DEXPORT)
              print (res)
          
          
          if __name__ == '__main__':
              main()
          
          

          MAXON SDK Specialist
          developers.maxon.net

          1 Reply Last reply Reply Quote 0
          • O
            OblivionDawn
            last edited by

            @mp5gosu, thanks for that workaround, I'll give it a shot and let you know if I have any questions.

            @ferdinand, interesting, thanks for looking into this issue and I look forward to hearing what you come up with.

            1 Reply Last reply Reply Quote 0
            • ferdinandF
              ferdinand
              last edited by ferdinand

              Hi @OblivionDawn,

              so I had a little debug session with c4dpy and the outcome is that the command is not bugged, but does not meet the requirements to be run under c4dpy.

              While framing an object technically does not require a viewport, but only a camera with its parameters like projection and field of view, the "Frame Selected Elements" command factually does require a viewport. The reason is that the command is grouped together with a whole architecture of viewport commands which first check if they can get hold of the active viewport. If not, they will bail their execution. This will fail in c4dpy due to the fact that c4dpy is being run GUI-less.

              Due to this being bound into the mentioned architecture of viewport commands we consider this to be an acceptable limitation of c4dpy and not a bug and therefore will not modify this behaviour. We have to ask you to resort to a custom object framing solution. @mp5gosu has already given you a nice example for such a solution (thanks again 😄).

              edit: Regarding the dead documents (I modified my previous posts to prevent confusing future readers), I was just a bit stupid and forgot to call c4d.documents.InsertDocument before calling c4d.documents.SetActiveDocument which causes all sorts of hiccups in Cinema, not only c4dpy. We also found out that the Python docs do not mention this requirement and will update them accordingly.

              Thank you for your understanding,
              Ferdinand

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 0
              • M
                mp5gosu
                last edited by

                @ferdinand said in "Frame Selected Elements" command not working when running script through c4dpy?:

                forgot to call c4d.documents.InsertDocument before calling c4d.documents.SetActiveDocument which causes all sorts of hiccups in Cinema, not only c4dpy

                This is a real classic! I stumbled over this several times! 🙂

                1 Reply Last reply Reply Quote 0
                • First post
                  Last post