Announcements
Attention for Customers without Multi-Factor Authentication or Single Sign-On - OTP Verification rolls out April 2025. Read all about it here.

In-Canvas render through API

Anonymous

In-Canvas render through API

Anonymous
Not applicable

Working on a script to generate in-canvas rendered animation from a design mode script that animates a model.

 

At this point, just working on a building block to render a single frame and save to the first of an image sequence using this script:

import adsk.core, adsk.fusion, traceback

def run(context):
    ui = None
    try:
        app  :adsk.core.Application = adsk.core.Application.get()
        ui   :adsk.core.UserInterface = app.userInterface

        des  :adsk.fusion.Design = app.activeProduct
        root :adsk.fusion.Component = des.rootComponent

        # Render Workspace
        renderWS = ui.workspaces.itemById("FusionRenderEnvironment")
        renderWS.activate()

        # Render
        cmdDefs :adsk.core.CommandDefinitions = ui.commandDefinitions
        cmdDef  :adsk.core.CommandDefinition = cmdDefs.itemById('InCanvasRenderCommand')
        cmdDef.execute()
        adsk.doEvents()

        # Save Image File
        imageFolder = 'F:/CAD Tests/Animation Sequence/'
        frameNumber = 0
        filename = imageFolder + "Test-" + str(frameNumber).zfill(4) + '.jpg'
        app.activeViewport.saveAsImageFile(filename, 0, 0) 
        adsk.doEvents()          

        # Design Workspace
        designWS = ui.workspaces.itemById("FusionDesignEnvironment")
        designWS.activate()        
        


    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

 

Two problems so far:

  • Running script from design mode, it does shift to render mode and renders the model . . . But, while the save file code does work, it captures the initial, pre-rendered image rather than waiting for the render to complete and then save the rendered image.
  • The code to switch back to design mode does not work.  I've tried "Design" and "Model" but neither works.  Correct syntax?  I'm assuming that I do need to go back to design mode for each step in the model animation.

Also . . . I'm assuming that the resolution option in app.activeViewport.saveAsImageFile(filename, 0, 0) will not work for the rendered image which, I believe, is limited to monitor resolution (even if it were to upscale the image, it wouldn't really improve the resolution).  Is this correct?   If so, I'm assuming that resolution could be improved by adding a higher resolution monitor.

 

Thanks

 

 

 

 

 

 

1 Like
Reply
Accepted solutions (1)
1,868 Views
6 Replies
Replies (6)

BrianEkins
Mentor
Mentor
Accepted solution

The API doesn't support the Render workspace.  You can use the API to switch to the workspace and to call commands, including the "In-Canvas Render" command, as you've found, but without explicit API support you are limited in what you can do.  To save renderings there are three options.

  1. The default rendering in the Design workspace.  You can save these at any desired resolution using the Viewport.saveAsImageFile method.
  2. You can switch to the Render workspace and use the default rendering there.  It's more photo-realistic than the design workspace rendering.  You can also use the saveAsImageFile method to save any resolution image.
  3. You can invoke the "In-Canvas Render" command to create a ray-traced image.  The problem with this is that it needs to run for indeterminate amount of time before it's ready.  This time will depend on the resolution and the complexity of the models, materials, and lighting.  You can also use the saveAsImageFile to capture this image, but it's only valid if you capture it at the current viewport resolution.  I tried writing some code using this but it was problematic.  The API needs to provide some support for this before it's possible to use in a realistic way.
---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
2 Likes

Anonymous
Not applicable

Brian,

 

I'm currently using your #1 successfully, but am still playing with #3.  It seems that adsk.doEvents() just isn't waiting until the render is finished (might a simple change get it to do so?). 

 

Going on this theory, this code just executes adsk.doEvents() an absurd number of times to try to get the render to finish before saving the file:

 

#Test 4 Renders and Saves Sequence

import adsk.core, adsk.fusion, adsk.cam, traceback

app = adsk.core.Application.get()
design = adsk.fusion.Design.cast(app.activeProduct)
root = design.rootComponent

def run(context):
    ui = None
    try:

        # Get Animation Parameters
        ui  = app.userInterface
        ui.messageBox('Starting Render Test 1')

        frameNumber = 0

        for i in range(4):
            adsk.doEvents()
            render(frameNumber)
            frameNumber += 1

        # Done
        ui.messageBox('Finished')

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def render(frameNumber):

    ui  = app.userInterface

    # Render Workspace
    renderWS = ui.workspaces.itemById("FusionRenderEnvironment")
    renderWS.activate()
    adsk.doEvents()          

    # Render
    cmdDefs :adsk.core.CommandDefinitions = ui.commandDefinitions
    cmdDef  :adsk.core.CommandDefinition = cmdDefs.itemById('InCanvasRenderCommand')
    cmdDef.execute()

    for i in range(20000):
        adsk.doEvents()        

    # Save Image File
    imageFolder = 'F:/CAD Tests/Animation Sequence/'
    filename = imageFolder + "Test-" + str(frameNumber).zfill(4) + '.jpg'
    app.activeViewport.saveAsImageFile(filename, 0, 0) 
    adsk.doEvents() 
 

 

It almost works (the problem you had?).  The strange thing is that it captures images 0, 2, . . . correctly (fully rendered) but images 1,3,. . . are pre-rendered. 

 

Of course, one would have to do a test render to set a value for the adsk.doEvents() loop (the render time might be input at script start and the loop count calculated automatically).  But, I'd be willing to do this if I could get all rendered images.  Note that this is for a fairly simple render with background off and the loop count could be even more absurd for a more complex render.

 

Thanks

 

 

 

 

0 Likes

Anonymous
Not applicable

Brian,

 

Some progress . . .

 

I tried some manual renders and noticed that after 1st execution of in-canvas render, the icon turns into a stop icon.  A second manual execution just stops, going back to pre-render image . . . exactly what the script was doing.   So, added another in-canvas render command after saving the 1st image to stop the current render.  Now get 4 saved, rendered images.   Haven't yet integrated this into the model animation script, but hope that will work. 

 

Question:  Is it necessary to get back to the Design environment (to step the animation) after rendering and saving an image?  I couldn't get the animation script to go back to Design . . . suggestion?   But, I think it may not be necessary here . . . just want to know how to do it.

 

#Test 4 Renders and Saves Sequence

import adsk.core, adsk.fusion, adsk.cam, traceback

app = adsk.core.Application.get()
design = adsk.fusion.Design.cast(app.activeProduct)
root = design.rootComponent

def run(context):
    ui = None
    try:

        # Setup
        ui  = app.userInterface
        ui.messageBox('Starting Render Test 1')

        # Render Workspace
        renderWS = ui.workspaces.itemById("FusionRenderEnvironment")
        renderWS.activate()
        adsk.doEvents()                 

        for i in range(4):
            render(i)
     
        # Done
        ui.messageBox('Finished')

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def render(frameNumber):

    ui  = app.userInterface    

    # Render
    cmdDefs :adsk.core.CommandDefinitions = ui.commandDefinitions
    cmdDef  :adsk.core.CommandDefinition = cmdDefs.itemById('InCanvasRenderCommand')
    cmdDef.execute()

    for i in range(40000):
        adsk.doEvents()   

    # Save Image File
    imageFolder = 'F:/CAD Tests/Animation Sequence/'
    filename = imageFolder + "Test-" + str(frameNumber).zfill(4) + '.jpg'
    app.activeViewport.saveAsImageFile(filename, 0, 0) 

    cmdDef  :adsk.core.CommandDefinition = cmdDefs.itemById('InCanvasRenderCommand')   
    cmdDef.execute()       
   

 

It does seem that this is more of a hack than a solid solution and wonder it you know of any other way to implement the delay other than the loop. 

 

Thanks

0 Likes

Anonymous
Not applicable

Brian,

 

It does work with the animation, folding the previous render test script into the cylindrical joint animation script using:

 

# Animate and Render Cylindrical Joint Rotation
# Name Joint to animate: "Cyl Rot Drive Joint"
# Render Animation Steps and Capture Images

import adsk.core, adsk.fusion, adsk.cam, traceback
import math
import time

app = adsk.core.Application.get()
design = adsk.fusion.Design.cast(app.activeProduct)
root = design.rootComponent

def run(context):
    ui = None
    try:

        # Get Animation Parameters
        ui  = app.userInterface
        ui.messageBox('Starting Revolute Joint Animation')


        msg = 'Number of Revolutions [1-4]'
        (iptStr, cancelled) = ui.inputBox(msg)
        if cancelled:
            return

        revs = max(1,min(int(iptStr),4))  # Numberof Joint Revolutions

        msg = 'Number of Sub-Steps [1-10]'
        (iptStr, cancelled) = ui.inputBox(msg)
        if cancelled:
            return

        noSubSteps = max(1,min(int(iptStr),10))  # Number of 1 degree substeps

        # Call Animation Function
        for i in range(revs):     
            stepAngle(noSubSteps)  # For each revolution

        # Done
        ui.messageBox('Finished')


    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))


def stepAngle(subSteps):   # Animate one revolution with specified substeps

        driveJoint = root.joints.itemByName('Cyl Rot Drive Joint')
        revMotion = adsk.fusion.CylindricalJointMotion.cast(driveJoint.jointMotion)
        ui  = app.userInterface

        stepNo = 0
        imageFolder = 'F:/CAD Tests/Animation Sequence/'

        # Render Workspace
        renderWS = ui.workspaces.itemById("FusionRenderEnvironment")
        renderWS.activate()
        adsk.doEvents()        

        for step in range(0,360 * subSteps,1):  # Loop for each substep
            angle = (math.pi / 180) * step / subSteps
            revMotion.rotationValue = angle
            adsk.doEvents()  # This lets modeler finish before returning to script
            #app.activeViewport.refresh()
            #adsk.doEvents()

            # Render
            cmdDefs :adsk.core.CommandDefinitions = ui.commandDefinitions
            cmdDef  :adsk.core.CommandDefinition = cmdDefs.itemById('InCanvasRenderCommand')
            cmdDef.execute()

            for i in range(40000):
                adsk.doEvents()

            # Save Image File
            imageFolder = 'F:/CAD Tests/Animation Sequence/'    
            frameNumber = stepNo
            filename = imageFolder + "Test-" + str(frameNumber).zfill(4) + '.jpg'
            app.activeViewport.saveAsImageFile(filename, 0, 0) 
            adsk.doEvents() 

            cmdDef  :adsk.core.CommandDefinition = cmdDefs.itemById('InCanvasRenderCommand')
            cmdDef.execute()    # Stop current render; Ready for next render        
            adsk.doEvents()

            stepNo += 1      # Next substep   

 

The attached simple model (video) renders each frame in 8 to 10 seconds, rendering 360 images for a full revolution of 1 degree steps. The video  is cropped in a video editor which concatenates 2 single revolution clips and is sped up 4X using ReSpeedr after initial .mp4 render.

 

The loop count works and there's no noticeable delay from frame to frame while rendering.  I haven't tried to optimize the loop count but it doesn't seem to make much difference once it's large enough.

 

Although the image size is acceptable for most of my needs (generally insets in a 1080p video), I think that adding a 4k monitor would double the image resolution.  Do you agree?

 

Note that minimizing the gallery gives a little boost in resolution.

 

Thanks

 

 

0 Likes

Anonymous
Not applicable

Brian,

 

And one more observation . . .

 

Setting up for a "Final" render and animating/rendering with In-Canvas doesn't result in the highest quality render. Particularly, matte finishes are still speckled rather than appearing shaded.

 

I tried setting "Infinite" which does improve the quality after something on the order of 25-50 seconds of rendering (computer dependent). 

 

I've found that the loop count very roughly yields 100 to 200 loops per rendering second so, for my machine, setting the loop count to 5,000 gets reasonably far into the render with improved results.  There doesn't seem to be any problem with the script grabbing a frame mid render, then stopping the render . . . at least not for the 4 frames I've tested so far.  Interesting that the first frame seems to render only about half as long as subsequent frames . . . maybe the rendering engine just getting its bearings?

 

Very roughly, then, I get on the order of 1 frame per minute of rendering which certainly can result in long render times.  But this seems to work in the background (fusion minimized) and I've had no trouble running other software while Fusion chugs through the rendered animation.  I suspect that going to a 4k monitor might roughly quadruple this time.

 

Do you know if the In-Canvas rendering engine is the same as the cloud based engine?

 

Thanks

0 Likes

Anonymous
Not applicable

Brian,

 

More thoughts and questions . . .

 

The loop hack is still working and has produced usable animations.  But, thinking about possible API additions to streamline this . . .  and an alternate script approach using local mode render . . .

 

  • It's clear that the "Final" in-canvas render isn't very satisfying and that "Infinite" is needed,  capturing the image after a reasonable time.  But time isn't the correct parameter since it's computer speed dependent.  The correct parameter is probably the number of render iterations.  If something were added to the API, it should probably include a render iterations parameter which should be set before invoking  the in-canvas render command.  Then,  adsk.doEvents() would wait until the specified number of iterations is complete (but not stop the render) and the script could execute another render command to stop the render, but not until the image is saved since stopping the render reverts back to the pre-render image.
  • I've been reading about running a script in another thread and wonder if this might be useful if this were done with a timeout in the second thread (not ideal and effectively what I'm doing now, so probably no benefit). 
  • I've been playing with the main render command but in local mode.  This produces renders of any resolution which, through a number of manual steps, can be saved, possibly as an animation frame.   Understand that I don't want to do cloud rendering since the cost in cloud credits for even a modest sized animation is prohibitive.  Currently I'm working primarily offline (though the local render still requires being online).  I don't think that the local render uses cloud credits (several local renders didn't seem to cost anything).  The local mode doesn't offer much flexibility in render quality; although, the local "Final(75)" quality might mean that it uses 75 iterations which is probably enough.  Is that correct?  So, the question is whether the local mode could be implemented in script, rendering each animation frame and downloading it to a local drive.  I think it would want to delete the cloud image (after download) after each animation step for a simple workflow.   The goal would be to require no additional manual steps beyond running the script.
  • Documentation question . . .  I've been using the documentation link you recommended but the link to the API reference manual still seems to be broken . . . get a 404 error.  Is there another way into the reference manual?

Thanks

0 Likes