"Frame Selected Elements" command not working when running script through c4dpy?
-
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?
-
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.
-
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 sincec4dpy
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,
FerdinandThe 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()
-
@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.
-
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 underc4dpy
.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 thatc4dpy
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 callingc4d.documents.SetActiveDocument
which causes all sorts of hiccups in Cinema, not onlyc4dpy
. We also found out that the Python docs do not mention this requirement and will update them accordingly.Thank you for your understanding,
Ferdinand -
@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!