Post Toolpaths API Sample

Description

Demonstrates posting toolpaths in the active document.

Code Samples

/////////////////////////////////////////////////////////////////////////////////////////////////
// For this sample script to run, the active Fusion document needs at least one CAM operation. //
/////////////////////////////////////////////////////////////////////////////////////////////////

#include <Core/CoreAll.h>
#include <CAM/CAMAll.h>

#ifdef _WINDOWS
#include <shlwapi.h>
#else
#include <stdlib.h>
#endif

using namespace adsk::core;
using namespace adsk::cam;

// Define a base class pointer for common usage.
using CAMObjectPtr = Ptr<OperationBase>;

// Function to ensure pathnames use forward slashes as required by the NC Program Output Folder.
void replaceBackslashWithSlash(std::string& str)
{
    for (char& ch : str)
        if (ch == '\\')
            ch = '/';
}
// Customised messageBox to handle critical errors in one line
void criticalError(Ptr<UserInterface> ui, const std::string& message)
{
    ui->messageBox(
        message, "Critical Error", MessageBoxButtonTypes::OKButtonType, MessageBoxIconTypes::CriticalIconType);
}

extern "C" XI_EXPORT bool run(const char* context)
{
    Ptr<Application> app = Application::get();
    if (!app)
        return false;

    Ptr<UserInterface> ui = app->userInterface();
    if (!ui)
        return false;

    Ptr<Document> doc = app->activeDocument();
    if (!doc)
        return false;

    Ptr<Products> products = doc->products();
    if (!products)
        return false;

    // Check if the document has a CAMProductType, abandon cleanly if not.
    std::string operationErrorMsg = "This script requires the active document to contain at least one CAM operation.";

    Ptr<CAM> camProduct = products->itemByProductType("CAMProductType");
    if (!camProduct)
    {
        criticalError(ui, operationErrorMsg);
        return false;
    }

    // Initialize the CAM manager to access the library manager and the post processor library.
    Ptr<CAMManager> camManager = CAMManager::get();
    Ptr<CAMLibraryManager> libraryManager = camManager->libraryManager();
    Ptr<PostLibrary> postLibrary = libraryManager->postLibrary();

    // Create a query to filter post processors from the Fusion library by the vendor "Autodesk".
    Ptr<PostConfigurationQuery> postQuery = postLibrary->createQuery(LibraryLocations::Fusion360LibraryLocation);
    postQuery->vendor("Autodesk");

    // Set the post configuration to use based on Operation Type of the first Setup.
    Ptr<Setup> setup;
    Ptr<Setups> setups = camProduct->setups();
    if (setups)
    {
        setup = setups->item(0);
        if (!setup)
        {
            criticalError(ui, operationErrorMsg);
            return false;
        }
    }

    OperationTypes opType = setup->operationType();

    switch (opType)
    {
    case adsk::cam::MillingOperation:
        postQuery->capability(adsk::cam::PostCapabilities::Milling);
        break;
    case adsk::cam::TurningOperation:
        postQuery->capability(adsk::cam::PostCapabilities::Turning);
        break;
    case adsk::cam::JetOperation:
        postQuery->capability(adsk::cam::PostCapabilities::Jet);
        break;
    default:
        postQuery->capability(adsk::cam::PostCapabilities::Milling);
        break;
    }

    std::vector<Ptr<PostConfiguration>> postConfigs = postQuery->execute();

    Ptr<URL> importedURL;
    std::string ncExtension;
    std::string postRequired = "RS-274D";
    std::string postDescription = "";

    // Find the required post in the post library and import it to local library.
    for (Ptr<PostConfiguration>& config : postConfigs)
    {
        if (config->description() == postRequired)
        {
            postDescription = postRequired;
            ncExtension = config->extension();
            Ptr<URL> configUrl = URL::create("user://");
            importedURL = postLibrary->importPostConfiguration(config, configUrl, "NCProgramSamplePost.cps");
            break;
        }
    }
    // Check if the required post was found, abandon cleanly if not.
    if (postDescription.empty())
    {
        criticalError(ui, "Unable to find postprocessor " + postRequired + ".");
        return false;
    }

    // Get the imported local post config.
    Ptr<PostConfiguration> postConfig = postLibrary->postConfigurationAtURL(importedURL);

    // Prompt the user with an option to view the resulting NC file.
    DialogResults dlgResults = ui->messageBox(
        "View nc file when done?",
        "Post-processing",
        MessageBoxButtonTypes::YesNoButtonType,
        MessageBoxIconTypes::QuestionIconType);
    bool viewResult = (dlgResults == DialogResults::DialogYes);

    // Create NCProgramInput object
    Ptr<NCProgramInput> ncInput = camProduct->ncPrograms()->createInput();

    // Set the value of scenario to 1, 2 or 3 to post all, post the first setup,
    // or post only the first operation of the first setup.
    int scenario = 1;
    std::string message = "Yes \t- All toolpaths will be posted.\n\n"
                          "No \t- Toolpaths in the first setup will be posted.\n\n"
                          "Cancel\t- The first toolpath in the first setup will be posted.\n";

    // Spaces appended to the title make the messageBox wider, preventing linw wrapping.
    DialogResults mbChoice = ui->messageBox(
        message,
        "Select what should be post-processed                                   ",
        MessageBoxButtonTypes::YesNoCancelButtonType,
        MessageBoxIconTypes::QuestionIconType);

    if (mbChoice == DialogResults::DialogYes)
    {
        scenario = 1;
    }
    else if (mbChoice == DialogResults::DialogNo)
    {
        scenario = 2;
    }
    else
    {
        scenario = 3;
    }

    // Adjust NC program parameters.
    ncInput->displayName("Sample NCProgram");

    Ptr<CAMParameters> ncParameters = ncInput->parameters();
    Ptr<StringParameterValue> nc_program_filename = ncParameters->itemByName("nc_program_filename")->value();

    // Embelish the nc filename to indicate chosen scenario and programming language.
    std::string programName = "NCProgramSampleCPP" + std::to_string(scenario);
    nc_program_filename->value(programName);

    // Provide a comment at the top of the file.
    Ptr<StringParameterValue> nc_program_comment = ncParameters->itemByName("nc_program_comment")->value();
    std::string comment = "This is the Post Toolpaths API Sample NC Program";
    nc_program_comment->value(comment);

    // Open nc file in editor if earlier requested.
    Ptr<BooleanParameterValue> nc_program_openInEditor = ncParameters->itemByName("nc_program_openInEditor")->value();
    nc_program_openInEditor->value(viewResult);

    // Set temp directory as output directory.
    std::string outputFolder = camProduct->temporaryFolder();

    // Variable outputFolder may contain backslashes but we need forward slashes.
    replaceBackslashWithSlash(outputFolder);
    Ptr<StringParameterValue> nc_program_output_folder = ncParameters->itemByName("nc_program_output_folder")->value();
    nc_program_output_folder->value(outputFolder);

    // We have already declared a setups Ptr for all 3 scenarios.
    // Scenarios 2 and 3 use firstSetup.
    Ptr<Setup> firstSetup = setups->item(0);

    switch (scenario)
    {
    case 1:
        {
            // ncInput->operations() expects a list of type 'OperationBase'.
            // For example: Operation, CAMFolder, CAMPattern, Setup.
            // It will not accept CAMFolders, CAMPatterns or Setups (all plural)
            // as they are derived from core.Base. We make our own list instead.

            // Create a vector for the content of core.Base objects.
            std::vector<CAMObjectPtr> camObjects;

            for (size_t i = 0; i < setups->count(); ++i)
            {
                // Add the operation to the operations list.
                camObjects.push_back(setups->item(i));
            }
            ncInput->operations({camObjects}); // Include all setups
            break;
        }
    case 2:
        {
            ncInput->operations({firstSetup}); // Only include the first setup
            break;
        }

    case 3:
        {
            Ptr<Operation> firstOperation = firstSetup->allOperations()->item(0);

            if (firstOperation->hasToolpath())
            {
                ncInput->operations({firstOperation}); // First operation of first setup
            }
            else
            {
                criticalError(ui, "Operation has no toolpath to post");
                return false;
            }
            break;
        }
    }

    // Create a new NC program using NC Program input and specified parameters and operations.
    Ptr<NCProgram> newProgram = camProduct->ncPrograms()->add(ncInput);

    // Configure the post processor
    newProgram->postConfiguration(postConfig);

    // Example of changing a post parameter. Parameters need to be declared as their correct type.
    Ptr<CAMParameters> postParams = newProgram->postParameters();

    // Parameter builtin_tolerance is of Type FloatParameterValue.
    Ptr<FloatParameterValue> builtInTolerance = postParams->itemByName("builtin_tolerance")->value();

    // Demonstrating how to check current parameter value and modify it.
    std::string previous = "Previous builtin_tolerance: " + std::to_string(builtInTolerance->value());
    builtInTolerance->value(0.004);
    app->log(previous + "\tNew value: " + std::to_string(builtInTolerance->value()));

    // The next assignment returns null if you use a post-processor that does not support
    // block numbering - for example "XYZ" or "Generic 2D", however you will not need it in that case.
    Ptr<CAMParameter> showSequenceNumbersParam = postParams->itemByName("showSequenceNumbers");
    if (showSequenceNumbersParam)
    {
        // Parameter showSequenceNumbers is of type ChoiceParameterValue so "false" is a string not a boolean.
        Ptr<ChoiceParameterValue> showSequenceNumbers = postParams->itemByName("showSequenceNumbers")->value();

        // Demonstrating how to check current parameter value and modify it.
        previous = "Previous showSequenceNumbers: " + showSequenceNumbers->value();
        showSequenceNumbers->value("false");
        app->log(previous + "\tNew value: " + showSequenceNumbers->value());
    }
    // Update to apply changes.
    newProgram->updatePostParameters(postParams);

    // Finally post the process.
    Ptr<NCProgramPostProcessOptions> postOptions = adsk::cam::NCProgramPostProcessOptions::create();
    newProgram->postProcess(postOptions);

    // Activate CAM workspace if it is not the active one.
    if (ui->activeWorkspace()->name() != "Manufacture")
    {
        DialogResults result = ui->messageBox(
            "Activate the CAM Workspace?",
            "CAM Workspace Activate",
            MessageBoxButtonTypes::YesNoButtonType,
            MessageBoxIconTypes::QuestionIconType);
        if (result == DialogResults::DialogYes)
        {
            Ptr<Workspace> camWorkspace = ui->workspaces()->itemById("CAMEnvironment");
            camWorkspace->activate();
        }
    }

    // Indicate completion.
    ui->messageBox(
        "Post processing is complete. See file:\n" + outputFolder + "/" + programName + ncExtension,
        "Post Toolpath API Sample  \t\t\t\t\t"); // Prevent line breaks

    return true;
}
# For this sample script to run, the active Fusion document needs at least one CAM operation.

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

app = adsk.core.Application.get()
ui = app.userInterface

def criticalError(message:str):
    ''' Customised messageBox to handle critical errors in one line '''
    ui.messageBox(  message,
                    'Critical Error',
                    adsk.core.MessageBoxButtonTypes.OKButtonType,
                    adsk.core.MessageBoxIconTypes.CriticalIconType)

def run(context):
    try:
        doc: adsk.core.Document = app.activeDocument
        products: adsk.core.Products = doc.products
        try:
            product: adsk.core.Product = products.itemByProductType('CAMProductType')
        except Exception:
            product = None

        # Check if the document has a CAMProductType, abandon cleanly if not.
        operationErrorMsg = 'This script requires the active document to contain at least one CAM operation.'
        if product == None:
            criticalError(operationErrorMsg)
            return

        cam = adsk.cam.CAM.cast(product)

        # Initialize the CAM manager to access the library manager and the post processor library.
        camManager = adsk.cam.CAMManager.get()
        libraryManager: adsk.cam.CAMLibraryManager = camManager.libraryManager
        postLibrary: adsk.cam.PostLibrary = libraryManager.postLibrary

        # Create a query to filter post processors from the Fusion library by the vendor "Autodesk".
        postQuery: adsk.cam.PostConfigurationQuery = \
                postLibrary.createQuery(adsk.cam.LibraryLocations.Fusion360LibraryLocation)
        postQuery.vendor = "Autodesk"

        # Set the post configuration to use based on Operation Type of the first Setup.
        try:
            firstSetupOperationType: adsk.cam.OperationTypes = cam.setups.item(0).operationType
        except:
            criticalError(operationErrorMsg)
            return

        if firstSetupOperationType == adsk.cam.OperationTypes.MillingOperation:
            postQuery.capability = adsk.cam.PostCapabilities.Milling
        elif firstSetupOperationType == adsk.cam.OperationTypes.TurningOperation:
            postQuery.capability = adsk.cam.PostCapabilities.Turning
        elif firstSetupOperationType == adsk.cam.OperationTypes.JetOperation:
            postQuery.capability = adsk.cam.PostCapabilities.Jet

        postConfigs: list[adsk.cam.PostConfiguration] = postQuery.execute()

        # Check for post configs being available.
        if len(postConfigs) == 0:
            criticalError('No post-configurations found.')
            return
    
        # Find the post required in the post library and import it to local library.
        postRequired = "RS-274D"
        postDescription = None
        for config in postConfigs:
            if config.description == postRequired:
                postDescription = postRequired
                ncExtension = config.extension
                url = adsk.core.URL.create("user://")
                importedURL = postLibrary.importPostConfiguration(config, url, "NCProgramSamplePost.cps")
                break

        # Check if the required post was found, abandon cleanly if not.
        if not postDescription:
            criticalError(f'Unable to find postprocessor {postRequired}.')
            return
        
        # Get the imported local post config.
        postConfig: adsk.cam.PostConfiguration = postLibrary.postConfigurationAtURL(importedURL)

        # Prompt the user with an option to view the resulting NC file.
        dlgResults: adsk.core.DialogResults = ui.messageBox(
                                    'View results when post is complete?', 'Post-processing',
                                    adsk.core.MessageBoxButtonTypes.YesNoButtonType,
                                    adsk.core.MessageBoxIconTypes.QuestionIconType)
        viewResult: bool  = (dlgResults == adsk.core.DialogResults.DialogYes)

        # Create ncInput object.
        ncInput: adsk.cam.NCProgramInput = cam.ncPrograms.createInput()

        # Set the value of scenario to 1, 2 or 3 to post all, post the first setup,
        # or post only the first operation of the first setup.
        scenario = 1

        # Spaces appended to the title make the messageBox wider, preventing linw wrapping.
        mbChoice: adsk.core.DialogResults = ui.messageBox(
                                    "Yes \t- All toolpaths will be posted.\n\n" \
                                    "No \t- Toolpaths in the first setup will be posted.\n\n" \
                                    "Cancel\t- The first toolpath in the first setup will be posted.\n",
                                    "Select what should be post-processed                                   ",
                                    adsk.core.MessageBoxButtonTypes.YesNoCancelButtonType, 
                                    adsk.core.MessageBoxIconTypes.QuestionIconType)

        if (mbChoice == adsk.core.DialogResults.DialogYes):
            scenario = 1
        elif (mbChoice == adsk.core.DialogResults.DialogNo):
            scenario = 2
        else:
            scenario = 3

        # Adjust NC program parameters.
        ncInput.displayName = 'Sample NCProgram'

        # Embelish the nc filename to indicate chosen scenario and programming language.
        programName:str = f'NCProgramSamplePy{scenario}'
        programfilename: adsk.cam.CAMParameter = ncInput.parameters.itemByName('nc_program_filename')
        programfilename.value.value = programName

        # Provide a comment at the top of the file.
        comment: adsk.cam.CAMParameter = ncInput.parameters.itemByName('nc_program_comment')
        comment.value.value = "This is the Post Toolpaths API Sample NC Program"

        # Open nc file in editor if earlier requested.
        openInEditor: adsk.cam.CAMParameter = ncInput.parameters.itemByName('nc_program_openInEditor')
        openInEditor.value.value = viewResult

        # Set temp directory as output directory
        # Make the path valid for Fusion by replacing \\ to / in the path
        outputFolder = str(cam.temporaryFolder).replace('\\', '/') 
        ncInput.parameters.itemByName('nc_program_output_folder').value.value = outputFolder
        
        if scenario == 1:
            # ncInput.operations() expects a list of type 'OperationsBase': Operation, CAMFolder, CAMPattern, Setup
            # It will not accept CAMFolders, CAMPatterns or Setups (all plural) as they are derived from core.Base
            opsBaseList: list[adsk.cam.OperationBase] = []
            setups: adsk.cam.Setups = cam.setups
            for setup in setups:
                opsBaseList.append(setup)
            ncInput.operations = opsBaseList # Include all setups
        elif scenario == 2:
            ncInput.operations = [cam.setups.item(0)]  # Only include the first setup
        elif scenario == 3:
            firstSetup: adsk.cam.Setup = cam.setups.item(0)
            firstOperation: adsk.core.Base = firstSetup.allOperations.item(0)
            if firstOperation.hasToolpath:
                ncInput.operations = [firstOperation]  # Only include the first operation of the first setup
            else:
                ui.messageBox('Operation has no toolpath to post')
                return

        # Create a new NC program using the current NC Program input including specified parameters and operations
        newProgram: adsk.cam.NCProgram = cam.ncPrograms.add(ncInput)

        # Configure the post processor
        newProgram.postConfiguration = postConfig

        # Example of changing a post parameter. Parameters need to be declared as their correct type.
        postParams: adsk.cam.CAMParameters = newProgram.postParameters

        # Parameter builtin_tolerance is of Type FloatParameterValue.
        builtInTolerance: adsk.cam.FloatParameterValue = postParams.itemByName('builtin_tolerance')
        builtInTolerance.value.value = 0.004

        # Parameter showSequenceNumbers is of type ChoiceParameterValue so "false" is a string not a boolean.
        showSequenceNumbers: adsk.cam.ChoiceParameterValue = postParams.itemByName('showSequenceNumbers')

        # Not all posts support SequenceNumbers
        if showSequenceNumbers:
            showSequenceNumbers.value.value = str('false')
        else:
            criticalError(f'Parameter showSequenceNumbers cannot be modified for postprocessor: {postRequired} - skipping!')

        # Update to apply changes.
        newProgram.updatePostParameters(postParams)

        # Finally post the process.
        postOptions = adsk.cam.NCProgramPostProcessOptions.create()
        newProgram.postProcess(postOptions)

        # Prompt user with an option to switch to the CAM workspace if it's not already active
        if ui.activeWorkspace.name != 'Manufacture':
            activateCAMWorkspace: adsk.core.DialogResults = ui.messageBox(
                                    f'Activate the CAM Workspace?','CAM Workspace Activate',
                                    adsk.core.MessageBoxButtonTypes.YesNoButtonType,
                                    adsk.core.MessageBoxIconTypes.QuestionIconType)

            if activateCAMWorkspace == adsk.core.DialogResults.DialogYes:
                camWorkspace: adsk.core.Workspace = ui.workspaces.itemById("CAMEnvironment")
                camWorkspace.activate()

        # Indicate completion.
        ui.messageBox(f'See file:\n{outputFolder}/{programName}{ncExtension}',
                      'Post processing is complete  \t\t\t\t\t'); # Prevent line breaks of the pathname

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