Community
Fusion API and Scripts
Got a new add-in to share? Need something specialized to be scripted? Ask questions or share what you’ve discovered with the community.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

python extrude, component's sketch goes invisible, then component is out of sync

12 REPLIES 12
Reply
Message 1 of 13
metd01567
809 Views, 12 Replies

python extrude, component's sketch goes invisible, then component is out of sync

I'm trying to extrude a profile contained in an included component.

 

It works OK from the user interface.  In an empty design, include the attached file.  The single component has a single sketch containing two profiles.  Select one or both of the profiles and extrude a 1mm body.  The sketch in the included component remains visible.  I expected this behavior, sketch visibility for included components should be locked.

 

If done through the python script below, the extrusion works but the sketch containing the profile goes invisible.  The component is then shown as out-of-date, which isn't surprising since it is now different from the reference component.  Requesting an update of the out-of-date component makes the out-of-date indication go away, but the sketch remains invisible.

 

As a work-around, the script can set isVisible to True after the extrude.  Try again but check "Restore Visibility" in the command dialog.  It's not perfect, since the component now shows as out-of-date.  A user of the script would be surprised and confused by this behavior.

 

# -*- coding: utf-8 -*-
"""
Created on Tue Aug 28 07:56:27 2018

@author: metd01567
"""

import adsk.core, adsk.fusion, traceback

# Global set of event handlers to keep them referenced for the duration of the command
_handlers = []

# Event handler that reacts to when the command is destroyed. This terminates the script.
class MyCommandDestroyHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:

            app = adsk.core.Application.get()
            ui = app.userInterface
            # When the command is done, terminate the script
            # This will release all globals which will remove all event handlers
            adsk.terminate()
        except:
            if ui:
                ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))


# Event handler that reacts when the command definition is executed which
# results in the command being created and this event being fired.
class MyCommandCreatedHandler(adsk.core.CommandCreatedEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:

            # check workspace, must be in Model
            app = adsk.core.Application.get()
            ui = app.userInterface
            product = app.activeProduct
            design = adsk.fusion.Design.cast(product)
            if not design:
                ui.messageBox('This script is not supported in current workspace, please change to MODEL workspace and try again.')
                return False

            # Get the command that was created.
            cmd = adsk.core.Command.cast(args.command)

            ###################################
            # create and register event handlers
            onDestroy = MyCommandDestroyHandler()
            cmd.destroy.add(onDestroy)
            _handlers.append(onDestroy)

            onExecute = MyExecuteHandler()
            cmd.execute.add(onExecute)
            _handlers.append(onExecute)

            ###################################
            # add controls

            # Create a selection input for extrude profiles
            profileSelectionInput = cmd.commandInputs.addSelectionInput('profileSelection', 'Profiles', 'The selected profiles will be extruded')
            profileSelectionInput.setSelectionLimits(1)
            profileSelectionInput.addSelectionFilter("Profiles")

            # Create a checkbox input for restoring sketch visibility
            restoreVisibilityInput = cmd.commandInputs.addBoolValueInput('restoreVisibility', 'Restore Visibility', True)
            restoreVisibilityInput.value = False

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

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

        # Get the existing command definition or create it if it doesn't already exist.
        cmdDef = ui.commandDefinitions.itemById('testExtruder')
        if not cmdDef:
            cmdDef = ui.commandDefinitions.addButtonDefinition('testExtruder', 'Test Extruder', 'Command to extrude.')

        # Connect to the command created event.
        onCommandCreated = MyCommandCreatedHandler()
        cmdDef.commandCreated.add(onCommandCreated)
        _handlers.append(onCommandCreated)

        # Execute the command definition.
        cmdDef.execute()

        # Prevent this module from being terminated when the script returns, because we are waiting for event handlers to fire.
        adsk.autoTerminate(False)


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

# Event handler that reacts to any changes the user makes to any of the command inputs.
class MyExecuteHandler(adsk.core.CommandEventHandler):
    def __init__(self):
        super().__init__()
    def notify(self, args):
        try:

            app = adsk.core.Application.get()
            ui = app.userInterface
            eventArgs = adsk.core.CommandEventArgs.cast(args)

            # call the executor and terminate
            doExtrude(eventArgs.command.commandInputs)
            adsk.terminate()

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

# get the profile input, and extrude it
def doExtrude(inputs):
    app = adsk.core.Application.get()
    ui = app.userInterface
    product = app.activeProduct
    design = adsk.fusion.Design.cast(product)
    comp = design.rootComponent


    ###############################################
    # verify selections

    profileSelectionInput = inputs.itemById('profileSelection')
    if not profileSelectionInput:
        return False

    restoreVisibilityInput = inputs.itemById("restoreVisibility")
    if not restoreVisibilityInput:
        return False
    restoreVisibility = restoreVisibilityInput.value

    ###############################################
    # copy the profiles into a collection
    selectedProfiles = adsk.core.ObjectCollection.create()
    for thisEntity in range(profileSelectionInput.selectionCount):
        selectedProfiles.add(profileSelectionInput.selection(thisEntity).entity)
    if selectedProfiles == None:
        ui.messageBox('software error: profile selection could not be used')
        return False

    ###############################################
    # create a thin extrusion
    ###############################################

    # extrude a thin body from the reference profile.  thickness is arbitrary, but keep it well above the model's resolution
    thickness = adsk.core.ValueInput.createByReal(0.1)
    extrude = comp.features.extrudeFeatures.addSimple(selectedProfiles, thickness, adsk.fusion.FeatureOperations.NewBodyFeatureOperation)

    if restoreVisibility:
        selectedProfiles[0].parentSketch.isVisible = True

    return True
12 REPLIES 12
Message 2 of 13
metd01567
in reply to: metd01567

This might help: I've been able to extrude from included sketches in the past, and tried again with an existing design.  That still works, but the sketch in that case belonged to the root component of the included file.

 

Is there an extra step that I missed due to the included component heirarchy?

 

Message 3 of 13
metd01567
in reply to: metd01567

I'll have to take back that last - I extruded from a face in an included component, not a sketch.

 

As a work-around I tried extruding a thin body from the sketch within the TubeTestProfiles component, and added another selection filter:

            profileSelectionInput.addSelectionFilter("Faces")

That works, the script creates the extrude.  I don't like the thin extrusion though, it creates extra clutter.  Also, this is a simplified version of a larger script.  The full script sweeps the selected profiles along a path to create a tube, then revolves to make end-caps.  I had trouble with the revolves from the included face, I'll post a separate topic if I can't fix that.

 

Meanwhile, the original problem remains.  It looks like a bug, maybe a conflict between rules?  There's a rule that locks sketch visibility in included components.  Another rule makes the sketch containing an extrusion profile  invisible after an extrude.  The UI must suppress the "make sketch invisible" rule when it does an extrude from an included component's sketch.  But maybe the API does not.  Once the sketch visibility lock is broken, there is no facility to repair it.

 

That's idle speculation.  I'm completely open to alternative approaches to the problem:  I want to create a single set of 10 profiles that can be used as the base for derived components.  I was able to all that in a single file with 2 derived components.  But that doesn't scale to my desired 100+ components.

Message 4 of 13
metd01567
in reply to: metd01567

New alternative: if I make a copy of the included component's sketch, I might avoid this problem.  It's certainly cleaner than the extruded bodies.  If I make the extruded body in a separate component, and tuck the sketch copies there too, it will be fine.

 

I tried this with the user interface, and it works well.  So if I could do that in python, I might have a solution.

 

I wrote the following code to do the sketch projection, and this creates a new sketch with a copy of the profiles, still linked to the master in the included component.  That's a step in the right direction, it gives me a parametric link all the way to the master profiles.  I cheated a bit since I know the master sketch is on the xz plane.

 

Unfortunately the extrude bombed with "invalid profile(s) for extrude feature".  I'll work on that, maybe I've got a simple mistake.

 

    ###############################################
    # create a thin extrusion
    ###############################################

    # new code:

    # create a sketch named "SweepProfile"
referencePlane = design.activeComponent.xZConstructionPlane profileSketch = design.activeComponent.sketches.add(referencePlane, design.activeOccurrence) profileSketch.name = "SweepProfile" # Project the curves into the sketch. for profile in selectedProfiles: for loop in profile.profileLoops: for curve in loop.profileCurves: profileSketch.project(curve.sketchEntity) projectedProfiles = profileSketch.profiles ui.messageBox("curve count: " + str(projectedProfiles.count))
... back to old code - but change "selectedProfiles" to "projectedProfiles"

 

 

 

 

I'm pretty much out of ideas here.

Message 5 of 13
metd01567
in reply to: metd01567

For this sketch projection approach, I started from Brian Elkins script, in this topic: https://forums.autodesk.com/t5/fusion-360-api-and-scripts/project-geometry-into-sketch/m-p/5604586?a...

 

I ran his script as-is, and it worked if I window-selected all the curves first.  So he has a set of SketchCurve objects directly, while I go through a set of ProfileCurve objects.  When I print the type, my "curve.sketchEntity" is either a SketchLine or SketchArc, which are SketchCurves, so I'm not sure why it doesn't work.

 

The specific error on the line "profileSketch.project(curve.sketchEntity)" is:

File " ... Contents/Api/Python/packages/adsk/fusion.py", line 20793, in project return_fusion.Sketch_project(self, *args)

        RuntimeError: 2 : InternalValidationError : Utils::getObjectPath(entity, objPath, occ, contextPath)

 

Message 6 of 13
metd01567
in reply to: metd01567

I'm afraid this is getting TLDR. Maybe I'll post a separate topic if there aren't any replies here.  Anyway I've drifted from the original topic (although still looking for the same solution).  

 

I was able to isolate the problem to projecting sketches in subcomponents.

 

To keep it simple, see the modified version of Brian's script below.  I've added my alternate way of selecting the profile (set "original" to false).  If you're using the original, you've got to have curves in your selection, for mine have a profile selected.

 

Start a new design, and:

- Create a sketch in the root component called "ProjectTest"

- Create another root component sketch and draw a triangle in it

- Create a sub-component and another triangle sketch in that. 

 

With original=True, you can select the lines from either sketch, run the script, and the projected triangle appears in the ProjectTest sketch.

 

With original=False, you can select the profile  in the root component sketch and the projected triangle appears in the ProjectTest sketch.

 

With original=False, if you select the profile  in the subcomponent sketch, you get the error I listed in my last post.

 

import adsk.core, adsk.fusion, traceback

def main():
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        design = app.activeProduct

        # Get the sketch named "ProjectTest"
        sketch = design.rootComponent.sketches.itemByName('ProjectTest')

        original = True
        # Get the set of currently selected profiles.
        curves = []

        if original:
            # Get the set of currently selected entities.
            curves = []
            for selection in ui.activeSelections:
                curves.append(selection.entity)

        else:
            # Project the curves into the sketch.
            for curve in curves:
                sketch.project(curve)

            for selection in ui.activeSelections:
                entity = selection.entity
                profile = adsk.fusion.Profile.cast(entity)
                if not profile:
                    ui.messageBox("not a profile: " + entity.classType())
                else:
                    for loop in profile.profileLoops:
                        for profileCurve in loop.profileCurves:
                            sketchCurve = adsk.fusion.SketchCurve.cast(profileCurve.sketchEntity)
                            if not sketchCurve:
                                ui.messageBox("non-curve type: " + profileCurve.classType())
                            else:
                                curves.append(profileCurve.sketchEntity)

        # Project the curves into the sketch.
        for curve in curves:
            ui.messageBox("projecting curve type: " + curve.classType())
            sketch.project(curve)

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


main()

 

Message 7 of 13
metd01567
in reply to: metd01567

And I tried one more test.  I changed "sketch = design.rootComponent.sketches.itemByName('ProjectTest')" to use the activeComponent instead.  Then I created a sketch in the subcomponent, and that projects fine in to the active component.

 

So the user interface can project curves between components, but my script cannot.  It's got to be something simple.

Message 8 of 13
metd01567
in reply to: metd01567

For your convenience, I've attached a new test file.  The root and sub components each have an empty "ProjectSketch" to project into, as well as a triangle sketch to project from.

Message 9 of 13
metd01567
in reply to: metd01567

And ... when I tried more combinations, it appears that the two techniques generate the same results.  The subcomponent sketch can project a root component curve.  But not the opposite way (root sketch cannot project a subcomponent curve.  Also sketches cannot project sibling component curves.

 

I also tried including the component into another drawing, but I couldn't make anything work that way either.

 

So I'm back to zero with no solution.

Message 10 of 13
metd01567
in reply to: metd01567

I've got to move on, so here's what I'll do: export the fully dimensioned set of profile sketches as .dxf files.  There are 10 of them, see: https://forums.autodesk.com/t5/fusion-360-design-validate/workflow-for-related-component-files/td-p/...

 

Then, as I do designs based on the profiles, I'll insert the .dxf files, and maybe fix all the curves since the dimensions will be lost.

Message 11 of 13
metd01567
in reply to: metd01567

I was hoping to insert the .dxf files via python, but that didn't work for me either:

https://forums.autodesk.com/t5/fusion-360-api-and-scripts/sketch-from-dxf-not-in-timeline-using-impo...

Message 12 of 13
goyals
in reply to: metd01567

General behaviour in Fusion is to hide the sketch used for creating the feature. You can noticed that by creating a sketch in a design and later use it for extrusion then visibility of the sketch is turned off.

The reference designs are inserted as read-only design in an assembly so in case a visible sketch in reference design is used for extrusion then its visibility might not be turned off but the API is not adhering to UI behaviour and turned off the visibility of sketch in reference design. I will create a defect and fix it.

I think there won't be any need for you to try any alternative solution i.e projecting the sketches in to another component or exporting the design in dxf format if fix is available for above problem. Having said that you are welcome to report the issues you noticed in your alternative solutions. Thanks



Shyam Goyal
Sr. Software Dev. Manager
Message 13 of 13
metd01567
in reply to: goyals

Thanks, that would make my original approach work, and I like it the best.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report