freecad-cam/Mod/cam-dev/ref-fusion/CAM360/ManufacturingAdvisor/CAMFunctions.py

1884 lines
83 KiB
Python

import re
import time
import adsk.cam
import adsk.core
import adsk.fusion
from CAMFunctionContext import CAMFunctionContext
from CAMFunctionTasks import CAMFunctionTaskManager
from CAMFunctionUtils import (
HoleAttributes,
areMatchingCaseInsensitiveNames,
classifyHoleGroups,
colorFaces,
createAutomaticOrientationStudy,
createManufacturingModel,
designProduct,
extractNameAndRegexEnabled,
findAdditiveArrangeInSetup,
findMatchingAdditiveSetups,
findMatchingParents,
findMatchingSetups,
findMatchingSetupsByType,
findObjectByName,
getActionStringFromMode,
getAppearanceByColorName,
getListAsString,
getManufacturingModelBRepBodies,
getManufacturingModelByName,
getToolByName,
getToolLibrary,
getValidOccurrences,
hasMatchingCaseInsensitiveName,
init,
isNonEmptyString,
millHoleGroup,
millPockets,
recognizeHoleGroups,
recognizePockets,
regenerateToolpaths,
selectOperations,
setAdditiveSetup,
setCAMParameters,
setTurningSetup,
)
from ModelInput import ModelInput, ModelInputMode, extractModelInput
from ToolDefinition import (
ToolLibraryType,
extractToolDefinition,
extractToolLibraryDefinition,
)
@CAMFunctionContext.execute_if_unblocked
def set_milling_operation(
mode: str,
operation_name: dict | None = None,
operation_type: str | None = None,
parent_name: dict | None = None,
tool_definition: dict | None = None,
**kwargs,
):
action: str = getActionStringFromMode(mode)
with CAMFunctionTaskManager.nextTask(f"{action.capitalize()} operations") as task:
_, _, _, _, cam = init()
# Get the specifics of the input name arguments
parent_name_str, parent_name_regex_enabled = extractNameAndRegexEnabled(parent_name)
operation_name_str, operation_name_regex_enabled = extractNameAndRegexEnabled(operation_name)
toolDefinition = extractToolDefinition(
toolDefinitionInput=tool_definition, cam=cam
)
# Get the list of parent objects to work with
foundParents, parents, _ = findMatchingParents(
parentName=parent_name_str,
regexEnabled=parent_name_regex_enabled,
cam=cam,
errorMessage=f"Error {action} operation:",
)
if not foundParents:
return
task.updateUI()
# Get the tool library if provided
toolLibraryDefinition = (
toolDefinition.toolLibraryDefinition if toolDefinition else None
)
with task.makeSubTask("Finding library", progressRange=(0, 40)) as libraryTask:
if toolLibraryDefinition and isNonEmptyString(toolLibraryDefinition.name):
toolDefinition.toolLibraryDefinition.toolLibrary = getToolLibrary(
toolLibraryDefinition=toolLibraryDefinition,
task=libraryTask,
)
if toolLibraryDefinition.toolLibrary is None:
CAMFunctionContext.warn(
message=f"Warning {action} operation:",
warning=f"Tool library '{toolLibraryDefinition.name}' not found. Tool will not be set.",
)
# Get the tool to set, if provided
tool: adsk.cam.Tool | None = None
toolMissingWarning: bool = False
with task.makeSubTask("Finding tool", progressRange=(40, 50)) as toolTask:
if toolDefinition:
if not isNonEmptyString(toolDefinition.name):
if toolLibraryDefinition:
warningMsg = None
if isNonEmptyString(toolDefinition.toolLibraryDefinition.name):
warningMsg = f"Tool library '{toolDefinition.toolLibraryDefinition.name}'"
elif (
toolDefinition.toolLibraryDefinition.type
!= ToolLibraryType.ALL
):
warningMsg = f"Tool library type '{toolDefinition.toolLibraryDefinition.type.value}'"
if isNonEmptyString(warningMsg):
toolMissingWarning = True
CAMFunctionContext.warn(
message=f"Warning {action} operation:",
warning=f"{warningMsg} is specified but no tool name is provided. Tool will not be set.",
)
else:
tool = getToolByName(
toolDefinition=toolDefinition,
task=toolTask,
)
if tool is None:
toolMissingWarning = True
CAMFunctionContext.warn(
message=f"Warning {action} operation:",
warning=f"Tool '{toolDefinition.name}' not found. Tool will not be set.",
)
# Check something has been provided to update if updating
if mode == "u":
if (not tool) and ("CAMParameters" not in kwargs):
# If a tool information was specified, but no tool was found,
# we have already warned about it.
if not toolMissingWarning:
CAMFunctionContext.warn(
message=f"Warning {action} operation:",
warning="No tool or parameters provided to update.",
)
return
# getting all required operations
if mode == "x":
if not isNonEmptyString(operation_type):
CAMFunctionContext.fail(
message="Error creating operation:",
error=f"Operation type not specified for new operation in '{parents[0].name}'."
)
return
# Check that the operation type refers to a valid strategy
try:
strategy = adsk.cam.OperationStrategy.createFromString(operation_type)
except Exception:
CAMFunctionContext.fail(
message="Error creating operation:",
error=f"Invalid operation type '{operation_type}' specified."
)
return
createTask = task.makeSubTask(
message="Creating operations",
progressRange=(50, 100),
numberOfTasks=len(parents)
)
with createTask:
for i, parent in enumerate(parents):
with createTask.makeSubTaskByIndex(index=i, numberOfTasks=2) as opTask:
# Check if the strategy is compatible with the parent setup
# This check must be done by internal name
setup: adsk.cam.Setup = parent.parentSetup
compatibleStrategies: list[str] = [strategy.name for strategy in setup.operations.compatibleStrategies if strategy.isGenerationAllowed]
if strategy.name not in compatibleStrategies:
CAMFunctionContext.fail(
message="Error creating operation:",
error=f"{strategy.title} strategy not available in '{parent.name}'. It may imply that this operation requires the manufacturing extension, a feature flag, or does not exist."
)
return
# Create the operation
operationInput: adsk.cam.OperationInput = parent.operations.createInput(operation_type)
if isNonEmptyString(operation_name_str):
operationInput.displayName = operation_name_str
if tool is not None:
operationInput.tool = tool
warningParams = []
with opTask.makeSubTaskByIndex(index=0) as paramTask:
if "CAMParameters" in kwargs:
warningParams = setCAMParameters(
inputParameters=kwargs["CAMParameters"],
camParameters=operationInput.parameters,
task=paramTask,
)
async def worker() -> adsk.cam.Operation:
return parent.operations.add(operationInput)
with opTask.makeSubTaskByIndex(index=1) as workerTask:
operation = workerTask.runAsyncWorker(worker=worker)
if len(warningParams) > 0:
CAMFunctionContext.warn(
message=f"Created the {strategy.title} operation named '{operation.name}' in '{parent.name}' with warnings:",
warning=f"Cannot set parameter(s): {getListAsString(warningParams)}. It may imply that the parameter(s) do not exist for this operation.",
)
else:
# If the operation name is different from the actual name of the created operation, interrupt the execution
if isNonEmptyString(operation_name_str) and operation_name_str != operation.name:
CAMFunctionContext.blockFutureExecution(
warning=f"Created the {strategy.title} operation named '{operation.name}' in '{parent.name}' (instead of '{operation_name_str}')."
)
else:
CAMFunctionContext.succeed(message=f"Created the {strategy.title} operation named '{operation.name}' in '{parent.name}'.")
elif mode == "u" or mode == "d":
selectedOperations = []
# Get all operations inside the selected setups that match the criteria
def isCandidateOperation(operation: adsk.cam.Operation) -> bool:
if isNonEmptyString(operation_name_str):
if not hasMatchingCaseInsensitiveName(
item=operation,
name=operation_name_str,
regexEnabled=operation_name_regex_enabled,
):
return False
if isNonEmptyString(operation_type):
if operation.strategy != operation_type:
return False
return True
for parent in parents:
for operation in parent.operations:
if isCandidateOperation(operation):
selectedOperations.append(operation)
task.updateUI()
if len(selectedOperations) == 0:
if parent_name_str is None:
CAMFunctionContext.fail(
message=f"Error {action} operation:",
error="Operation not found.",
)
else:
CAMFunctionContext.fail(
message=f"Error {action} operation:",
error=f"Operation not found in '{parents[0].name}'.",
)
return
updateTask = task.makeSubTask(
message=f"{action.capitalize()} operations",
progressRange=(50, 100),
numberOfTasks=len(selectedOperations),
)
with updateTask:
for i, operation in enumerate(selectedOperations):
with updateTask.makeSubTaskByIndex(index=i) as operationTask:
operation_name_str = operation.name
if mode == "d":
parent_name = operation.parent.name
operation.deleteMe()
CAMFunctionContext.succeed(message=f"Deleted '{operation_name_str}' from '{parent_name}'.")
if mode == "u":
warningParams: list[str] = []
if "CAMParameters" in kwargs:
warningParams = setCAMParameters(
inputParameters=kwargs["CAMParameters"],
camParameters=operation.parameters,
task=operationTask,
)
if tool is not None:
operation.tool = tool
if len(warningParams) > 0:
CAMFunctionContext.warn(
message=f"Updated '{operation_name_str}' in '{operation.parent.name}' with warnings:",
warning=f"Cannot set parameter(s): {getListAsString(warningParams)}. It may imply that the parameter(s) do not exist for this operation.",
)
else:
CAMFunctionContext.succeed(message=f"Updated '{operation_name_str}' in '{operation.parent.name}'.")
@CAMFunctionContext.execute_if_unblocked
def set_empty_toolpath(
mode: str = "s",
parent_name: dict | None = None,
**kwargs,
):
# Action verb according to the mode
if mode == "s":
action = "suppressing"
elif mode == "d":
action = "deleting"
else:
CAMFunctionContext.fail(message="Error setting empty toolpaths:", error="Invalid mode. Use 's' for suppressing or 'd' for deleting.")
return
with CAMFunctionTaskManager.nextTask(f"{action.capitalize()} toolpaths") as task:
_, _, _, _, cam = init()
# Get the specifics of the input name arguments
parent_name_str, regex_enabled = extractNameAndRegexEnabled(parent_name)
# Find parent objects matching the inputs
foundParents, parents, selectingAllParents = findMatchingParents(
parentName=parent_name_str,
regexEnabled=regex_enabled,
cam=cam,
errorMessage=f"Error {action} empty toolpaths:",
)
if not foundParents:
return
task.updateUI()
# Loop through the parents and find empty toolpaths, following the rules:
# - Protected toolpaths are not modified
# - A toolpath is considered empty if:
# - it is valid and has no moves (isToolpathValid and not hasToolpath)
# - it is not valid and has no moves
# (not isToolpathValid and operationState == NoToolpathOperationState)
#
# Toolpath can be suppressed if:
# - it is not protected
# - it is empty
# - it is not suppressed
#
# Toolpath can be deleted if:
# - it is not protected
# - it is empty or suppressed
# in API documentation, "Toolpaths do not exist for suppressed operations."
#
# Determine candidate toolpaths based on the mode
def isCandidateOperation(operation):
if operation.isProtected:
return False
isEmpty = (
(operation.isToolpathValid and not operation.hasToolpath) or
(not operation.isToolpathValid and operation.operationState == adsk.cam.OperationStates.NoToolpathOperationState)
)
if mode == "s":
return isEmpty and not operation.isSuppressed
elif mode == "d":
return isEmpty or operation.isSuppressed
else:
return False
emptyToolpaths = []
for parent in parents:
operations = parent.operations
for i in range(operations.count):
operation = operations.item(i)
if isCandidateOperation(operation):
emptyToolpaths.append(operation)
# If no empty toolpaths are found, show a warning as there is nothing to do
if len(emptyToolpaths) == 0:
if selectingAllParents:
CAMFunctionContext.warn(message=f"Warning {action} empty toolpaths:", warning="No empty toolpaths found.")
else:
for parent in parents:
CAMFunctionContext.warn(
message=f"Warning {action} empty toolpaths:",
warning=f"No empty toolpaths found in '{parent.name}'.",
)
return
# Perform the action on the empty toolpaths
for i, operation in enumerate(emptyToolpaths):
task.updateTaskProgress(i, len(emptyToolpaths))
name = operation.name
parentName = operation.parent.name
if mode == "s":
operation.isSuppressed = True
CAMFunctionContext.succeed(message=f"Suppressed empty toolpath '{name}' in '{parentName}'.")
elif mode == "d":
operation.deleteMe()
CAMFunctionContext.succeed(message=f"Deleted empty toolpath '{name}' in '{parentName}'.")
@CAMFunctionContext.execute_if_unblocked
def protect_toolpath(
parent_name: dict | None = None,
**kwargs,
):
with CAMFunctionTaskManager.nextTask("Protecting toolpaths") as task:
_, _, _, _, cam = init()
# Get the specifics of the input name arguments
parent_name_str, regex_enabled = extractNameAndRegexEnabled(parent_name)
# Get the scope of parent objects that we want to look for toolpaths to protect
foundParents, parents, selectingAllParents = findMatchingParents(
parentName=parent_name_str,
regexEnabled=regex_enabled,
cam=cam,
errorMessage="Error protecting toolpaths:",
)
if not foundParents:
return
task.updateUI()
# Check that the parent setups are applicable for protection
def isApplicableSetupType(parent):
setup: adsk.cam.Setup = parent.parentSetup
return setup.parameters.itemByName("job_type").expression in ["'milling'", "'turning'", "'jet'"]
nonApplicableParents = [parent for parent in parents if not isApplicableSetupType(parent)]
for parent in nonApplicableParents:
CAMFunctionContext.warn(
message="Warning protecting toolpaths:",
warning=f"Toolpath(s) in '{parent.name}' are not supported for protection.",
)
# Loop through the applicable parent objects and find toolpaths with no warnings or errors, and are not protected
applicableParents = [parent for parent in parents if isApplicableSetupType(parent)]
if len(applicableParents) == 0:
# No applicable parents found, which has already been warned about
return
task.updateUI()
def isCandidateOperation(operation):
return not operation.hasWarning and not operation.hasError and not operation.isProtected
toolpathsToProtect = []
for setup in applicableParents:
operations = setup.operations
for i in range(operations.count):
operation = operations.item(i)
if isCandidateOperation(operation):
toolpathsToProtect.append(operation)
# If no toolpaths are found that don't have problems, show a warning as there is nothing to do
if len(toolpathsToProtect) == 0:
if selectingAllParents:
CAMFunctionContext.warn(message="Warning protecting toolpaths:", warning="No applicable toolpaths found.")
else:
for setup in parents:
CAMFunctionContext.warn(
message="Warning protecting toolpaths:",
warning=f"No applicable toolpaths found in '{setup.name}'.",
)
return
# Perform the action on the applicable toolpaths
for i, operation in enumerate(toolpathsToProtect):
task.updateTaskProgress(i, len(toolpathsToProtect))
operation.isProtected = True
CAMFunctionContext.succeed(message=f"Protected toolpath '{operation.name}' in '{operation.parent.name}'.")
@CAMFunctionContext.execute_if_unblocked
def regenerate_toolpath(
mode: str = "invalid",
select_toolpaths: bool = False,
**kwargs,
):
# Check mode argument, and determine the target string accordingly
if mode == "invalid":
targetStr = "all invalid toolpaths"
elif mode == "all":
targetStr = "all toolpaths"
else:
CAMFunctionContext.fail(
message="Error regenerating toolpaths:",
error="Invalid mode. Use 'invalid' for invalid toolpaths or 'all' for all toolpaths.",
)
return
with CAMFunctionTaskManager.nextTask("Regenerating toolpaths") as task:
_, ui, _, _, cam = init()
# Generate all toolpaths, getting the list of operations that were generated
regenRange = (0, 90 if select_toolpaths else 100)
with task.makeSubTask(progressRange=regenRange) as regenTask:
operations: list[adsk.cam.Operation] = regenerateToolpaths(
cam=cam,
task=regenTask,
skipValid=mode == "invalid",
)
if select_toolpaths:
# Select all generated toolpaths
with task.makeSubTask("Selecting toolpaths", progressRange=(90, 100)) as selectTask:
selectOperations(ui=ui, operations=operations, task=selectTask)
actionStr = "Regenerated and selected" if select_toolpaths else "Regenerated"
CAMFunctionContext.succeed(message=f"{actionStr} {targetStr}.")
@CAMFunctionContext.execute_if_unblocked
def set_tool(old_tool_number, new_tool_number):
with CAMFunctionTaskManager.nextTask(message="Setting tool") as task:
_, _, _, _, cam = init()
if cam.setups.count < 1:
CAMFunctionContext.fail(
message="Error setting tool:",
error="No setups available.",
)
return
if cam.allOperations.count < 1:
CAMFunctionContext.fail(
message="Error setting tool:",
error="No operations available.",
)
return
# Look for the new tool in document tool library
tool_lib = cam.documentToolLibrary
new_tool = None
with task.makeSubTask("Finding tool", progressRange=(0, 10)) as findToolTask:
for i, tool in enumerate(tool_lib):
findToolTask.updateTaskProgress(i, tool_lib.count)
toolNumber = tool.parameters.itemByName("tool_number").value.value
if toolNumber == new_tool_number:
new_tool = tool
break
# Error handling for if the new tool isn't found
if not new_tool:
CAMFunctionContext.fail(
message="Error setting tool:",
error=f"Cannot find tool with tool number {new_tool_number}.",
)
return
# Get all operations that use the old tool number
operations = [
op
for op in cam.allOperations
if op.tool.parameters.itemByName("tool_number").value.value
== old_tool_number
]
if len(operations) == 0:
CAMFunctionContext.warn(
message="Warning setting tool:",
warning=f"No operations found using tool number {old_tool_number}.",
)
return
# Replace old tool with new tool in all operations
# Note that this will make old toolpaths invalid, and they will need to be regenerated
with task.makeSubTask("Setting tool", progressRange=(10, 100)) as setToolTask:
for i, op in enumerate(operations):
setToolTask.updateTaskProgress(i, len(operations))
toolNumber = op.tool.parameters.itemByName("tool_number").value.value
if toolNumber == old_tool_number:
op.tool = new_tool
CAMFunctionContext.succeed(message=f"Set tool number {old_tool_number} to {new_tool_number} in operation '{op.name}'.")
@CAMFunctionContext.execute_if_unblocked
def set_setup(
operation_type: adsk.cam.OperationTypes,
setup_name: dict | None = None,
mode: str = "x",
additive_technology: str | None = None,
model: dict | None = None,
**kwargs,
):
action: str = getActionStringFromMode(mode)
with CAMFunctionTaskManager.nextTask(f"{action.capitalize()} setup") as task:
_, ui, _, _, cam = init()
# Get the specifics of the input name arguments
setup_name_str, regex_enabled = extractNameAndRegexEnabled(setup_name)
# Get the model inputs from the optional argument
modelInput: ModelInput = extractModelInput(model)
# select setups
setups = []
if mode == "d" or mode == "u":
if operation_type != -1:
foundSetups, setups, _ = findMatchingSetupsByType(
setupName=setup_name_str,
regexEnabled=regex_enabled,
cam=cam,
operationType=operation_type,
errorMessage=f"Error {action} setup:",
)
else:
foundSetups, setups, _ = findMatchingSetups(
setupName=setup_name_str,
regexEnabled=regex_enabled,
cam=cam,
errorMessage=f"Error {action} setup:",
)
if not foundSetups:
return
if mode == "u" and "CAMParameters" not in kwargs:
CAMFunctionContext.fail(
message=f"Error {action} setup:",
error="No parameters are updated.",
)
return
if mode == "d": # delete
for i, setup in enumerate(setups):
task.updateTaskProgress(i, len(setups))
name = setup.name
setup.deleteMe()
CAMFunctionContext.succeed(message=f"Deleted '{name}'.")
elif mode == "u": # update
with task.makeSubTask(numberOfTasks=len(setups)) as paramTask:
for i, setup in enumerate(setups):
with paramTask.makeSubTaskByIndex(i, message=f"Updating '{setup.name}'") as setTask:
warningParams = setCAMParameters(
inputParameters=kwargs["CAMParameters"],
camParameters=setup.parameters,
task=setTask,
)
if len(warningParams) > 0:
CAMFunctionContext.warn(
message=f"Updated '{setup.name}' with warnings:",
warning=f"Cannot set parameter(s): {getListAsString(warningParams)}. It may imply that the parameter(s) do not exist for this setup.",
)
else:
CAMFunctionContext.succeed(message=f"Updated '{setup.name}'.")
elif mode == "x": # create
setupInput = cam.setups.createInput(operation_type)
# Fusion will take care of setup name if it doesn't exist
if isNonEmptyString(setup_name_str):
setupInput.name = setup_name_str
# Allow up to 60% in case of creating an additive setup
inputTask = task.makeSubTask(
message="Gathering inputs",
progressRange=(0, 60),
)
with inputTask:
# Set the model inputs if provided
if not modelInput.attachModelToSetupInput(
ui=ui,
cam=cam,
errorMessage=f"Error {action} setup:",
warningMessage=f"Warning {action} setup:",
setupInput=setupInput,
):
return
# Set additional inputs for specific operation types
if operation_type == adsk.cam.OperationTypes.TurningOperation:
if not setTurningSetup(
cam=cam,
modelInputMode=modelInput.mode,
setupInput=setupInput,
):
return
elif operation_type == adsk.cam.OperationTypes.AdditiveOperation:
# Machine, print setting, and models are required for additive setups
if not setAdditiveSetup(
cam=cam,
additiveTechnology=additive_technology,
manufacturingModelName=modelInput.manufacturingModelName,
setupInput=setupInput,
task=inputTask,
):
return
# Create the setup
with task.makeSubTask(progressRange=(60, 90)) as createTask:
async def worker() -> adsk.cam.Setup:
return cam.setups.add(setupInput)
setup = createTask.runAsyncWorker(worker=worker)
setups.append(setup)
# Set CAM parameters if provided
#
# This is purposefully done after the setup is created as many
# parameters relating to stock will fail to be validated in the
# setup inputs if some of them are missing/have default values
warningParams: list[str] = []
with task.makeSubTask(progressRange=(90, 100)) as paramTask:
if "CAMParameters" in kwargs:
warningParams = setCAMParameters(
inputParameters=kwargs["CAMParameters"],
camParameters=setup.parameters,
task=paramTask,
)
if isNonEmptyString(setup_name_str) and setup_name_str != setup.name:
CAMFunctionContext.blockFutureExecution(
warning=f"Created '{setup.name}' instead of '{setup_name_str}'.",
)
if len(warningParams) > 0:
CAMFunctionContext.warn(
message=f"Created '{setup.name}' with warnings:",
warning=f"Cannot set parameter(s): {getListAsString(warningParams)}. It may imply that the parameter(s) do not exist for this setup.",
)
else:
CAMFunctionContext.succeed(message=f"Created '{setup.name}'.")
@CAMFunctionContext.execute_if_unblocked
def set_additive_arrange(
setup_name: dict | None = None,
mode: str = "x",
**kwargs,
):
action: str = getActionStringFromMode(mode)
with CAMFunctionTaskManager.nextTask(message=f"{action.capitalize()} arrange") as task:
_, _, _, _, cam = init()
# Get the specifics of the input name arguments
setup_name_str, regex_enabled = extractNameAndRegexEnabled(setup_name)
# Select additive setups to work with
foundSetups, setups = findMatchingAdditiveSetups(
setupName=setup_name_str,
regexEnabled=regex_enabled,
cam=cam,
errorMessage=f"Error {action} additive arrange:",
)
if not foundSetups:
return
task.updateUI()
# Get all additive arranges inside the selected setups
# This is a list of (setup, arrange) tuples, where arrange is None if
# no arrange is found in the setup
setupArranges = [(setup, findAdditiveArrangeInSetup(setup)) for setup in setups]
# List of existing arranges, which will be used for deletion or update
existingArranges = [arrange for _, arrange in setupArranges if arrange is not None]
# List of setups that do not have an arrange, which will be used for creation
setupsWithoutArranges = [setup for setup, arrange in setupArranges if arrange is None]
task.updateUI()
if (mode == "u" or mode == "d") and len(existingArranges) == 0:
CAMFunctionContext.fail(
message=f"Error {action} additive arrange:",
error="No additive arrange found in the selected setups.",
)
return
if mode == "u" and "CAMParameters" not in kwargs:
CAMFunctionContext.fail(
message="Error updating additive arrange:",
error="No parameters are updated.",
)
return
if mode == "d": # delete
for i, arrange in enumerate(existingArranges):
task.updateTaskProgress(i, len(existingArranges))
parentSetup = arrange.parentSetup
arrange.deleteMe()
CAMFunctionContext.succeed(message=f"Deleted additive arrange for '{parentSetup.name}'.")
return
# Determine the number of shared steps to perform for the task mode
numberOfNewArranges: int = 0
numberOfUpdatingArranges: int = 0
if mode == "x": # create
numberOfNewArranges = len(setupsWithoutArranges)
if "CAMParameters" in kwargs:
numberOfUpdatingArranges = numberOfNewArranges
elif mode == "u": # update, and we know there are CAMParameters
numberOfUpdatingArranges = len(existingArranges)
numberOfSteps: int = numberOfNewArranges + numberOfUpdatingArranges
with task.makeSubTask(numberOfTasks=numberOfSteps) as arrangeTask:
arrangesToUpdate = []
if mode == "x": # create
for i, setup in enumerate(setupsWithoutArranges):
with arrangeTask.makeSubTaskByIndex(index=i, message="Creating arrange") as createTask:
operationInput = setup.operations.createInput('additive_arrange')
arrange = setup.operations.add(operationInput)
arrangesToUpdate.append(arrange)
CAMFunctionContext.succeed(message=f"Created additive arrange for '{setup.name}'.")
elif mode == "u": # update
arrangesToUpdate = existingArranges
if (mode == "u" or mode == "x") and "CAMParameters" in kwargs:
for i, arrange in enumerate(arrangesToUpdate):
updateTask = arrangeTask.makeSubTaskByIndex(
index=i + numberOfNewArranges,
message=f"Updating '{arrange.name}'",
)
with updateTask:
warningParams = setCAMParameters(
inputParameters=kwargs["CAMParameters"],
camParameters=arrange.parameters,
task=updateTask,
)
updatedOrCreated = "Updated" if mode == "u" else "Created"
if len(warningParams) > 0:
CAMFunctionContext.warn(
message=f"{updatedOrCreated} '{arrange.name}' with warnings:",
warning=f"Cannot set parameter(s): {getListAsString(warningParams)}. It may imply that the parameter(s) do not exist for this operation.",
)
else:
CAMFunctionContext.succeed(message=f"{updatedOrCreated} '{arrange.name}'.")
@CAMFunctionContext.execute_if_unblocked
def set_additive_orientation_study(
mode: str,
setup_name: dict | None = None,
orientation_name: dict | None = None,
**kwargs,
):
action: str = getActionStringFromMode(mode)
errorMessage = f"Error {action} automatic orientation study:"
warningMessage = f"Warning {action} automatic orientation study:"
with CAMFunctionTaskManager.nextTask(f"{action.capitalize()} study") as task:
_, _, _, _, cam = init()
# Get the specifics of the input name arguments
setup_name_str, regex_enabled = extractNameAndRegexEnabled(setup_name)
orientation_name_str, orientation_name_regex_enabled = extractNameAndRegexEnabled(orientation_name)
# Find the additive setup(s) from setup_name in the document
foundSetups, setups = findMatchingAdditiveSetups(
setupName=setup_name_str,
regexEnabled=regex_enabled,
cam=cam,
errorMessage=errorMessage,
)
if not foundSetups:
return
# Check something has been provided to update if updating
if mode == "u":
if not kwargs.get("CAMParameters"):
CAMFunctionContext.warn(
message=warningMessage,
warning="No parameters provided to update.",
)
return
# Collect the created/updated orientations so they can be
# (re-)generated in one go
orientations: list[adsk.cam.Operation] = []
if mode == "x":
# Create automatic orientation studies for each setup
with task.makeSubTask(numberOfTasks=len(setups), progressRange=(0, 70)) as setupTask:
for i, setup in enumerate(setups):
with setupTask.makeSubTaskByIndex(index=i) as studyTask:
createdOrientations = createAutomaticOrientationStudy(
cam=cam,
setup=setup,
orientationName=orientation_name_str,
camParameters=kwargs.get("CAMParameters"),
task=studyTask,
)
orientations.extend(createdOrientations)
elif mode == "u" or mode == "d":
# Collect existing orientation studies (with their setups) for
# update/deletion from the respective Orientations container
existingOrientations: list[tuple[adsk.cam.Setup, adsk.cam.Operation]] = []
def isCandidateOrientation(orientation: adsk.cam.Operation) -> bool:
if isNonEmptyString(orientation_name_str):
return hasMatchingCaseInsensitiveName(
item=orientation,
name=orientation_name_str,
regexEnabled=orientation_name_regex_enabled,
)
return True
for setup in setups:
container: adsk.cam.CAMAdditiveContainer = setup.additiveContainerByType(
adsk.cam.CAMAdditiveContainerTypes.OptimizedOrientationCAMAdditiveContainerType
)
if container:
for operation in container.allOperations:
if isCandidateOrientation(operation):
existingOrientations.append((setup, operation))
if len(existingOrientations) == 0:
if setup_name_str is None:
CAMFunctionContext.fail(
message=errorMessage,
error="Orientation study not found in setups.",
)
else:
CAMFunctionContext.fail(
message=errorMessage,
error=f"Orientation study not found in setup '{setups[0].name}'.",
)
return
if mode == "d":
deleteTask = task.makeSubTask(progressRange=(0, 100), numberOfTasks=len(existingOrientations))
with deleteTask:
for i, (setup, orientation) in enumerate(existingOrientations):
with deleteTask.makeSubTaskByIndex(index=i) as studyTask:
orientation_name_str = orientation.name
orientation.deleteMe()
CAMFunctionContext.succeed(
message=f"Deleted '{orientation_name_str}' from '{setup.name}'."
)
return
updateTask = task.makeSubTask(numberOfTasks=len(existingOrientations), progressRange=(0, 70))
with updateTask:
for i, (setup, orientation) in enumerate(existingOrientations):
orientations.append(orientation)
with updateTask.makeSubTaskByIndex(index=i) as studyTask:
warningParams = setCAMParameters(
inputParameters=kwargs["CAMParameters"],
camParameters=orientation.parameters,
task=studyTask,
)
if len(warningParams) > 0:
CAMFunctionContext.warn(
message=f"Updated '{orientation.name}' in '{setup.name}' with warnings:",
warning=f"Cannot set parameter(s): {getListAsString(warningParams)}. It may imply that the parameter(s) do not exist for this operation.",
)
else:
CAMFunctionContext.succeed(
message=f"Updated '{orientation.name}' in '{setup.name}'."
)
# Generate all orientation studies
if len(orientations) > 0:
with task.makeSubTask(message="Generating orientations", progressRange=(70, 100)) as generateTask:
regenerateToolpaths(
cam=cam,
operations=orientations,
task=generateTask,
)
@CAMFunctionContext.execute_if_unblocked
def create_manufacturing_model(name:str = None):
"""
Create a new manufacturing model, with given name if provided.
Future CAM functions will be blocked if the model was created with a
different name than the one provided.
"""
with CAMFunctionTaskManager.nextTask("Creating manufacturing model") as task:
_, _, _, _, cam = init()
createManufacturingModel(cam=cam, name=name, task=task)
@CAMFunctionContext.execute_if_unblocked
def color_holes(manufacturing_model_name: str, color_name: str, color_code: tuple[int]):
with CAMFunctionTaskManager.nextTask("Coloring holes") as task:
app, _, _, _, cam = init()
# Get the appearance
appearance = getAppearanceByColorName(designProduct(), color_name, color_code)
if appearance is None:
CAMFunctionContext.fail(
message="Error coloring holes:",
error=f"Cannot create appearance with color {color_code}",
)
return
task.updateUI()
# Get the manufacturing model
manufacturingModel = None
with task.makeSubTask(message="Getting manufacturing model", progressRange=(0, 10)) as getModelTask:
if isNonEmptyString(manufacturing_model_name):
manufacturingModel = getManufacturingModelByName(cam, manufacturing_model_name)
if manufacturingModel is None:
CAMFunctionContext.fail(
message="Error coloring holes:",
error=f"Manufacturing model '{manufacturing_model_name}' not found",
)
return
elif cam.manufacturingModels.count > 0:
manufacturingModel = cam.manufacturingModels.item(0)
else:
manufacturingModel = createManufacturingModel(cam=cam, task=getModelTask)
# Get the Bodies from model
manufacturingModelBody = getManufacturingModelBRepBodies(manufacturingModel)
if len(manufacturingModelBody) == 0:
CAMFunctionContext.warn(
message="Warning coloring holes:",
warning=f"No bodies found for '{manufacturingModel.name}'",
)
return
task.updateUI()
# Recognize holes
with task.makeSubTask(progressRange=(10, 90), message="Recognizing holes") as recognizeTask:
holeGroups: adsk.cam.RecognizedHoleGroups = recognizeHoleGroups(
bodies=manufacturingModelBody,
task=recognizeTask,
)
if holeGroups.count == 0:
CAMFunctionContext.warn(message="Warning coloring holes:", warning=f"No holes found in the bodies of '{manufacturingModel.name}'")
return
# Collect the faces to color
facesToColor: list[adsk.fusion.BRepFace] = [
face
for holeGroup in holeGroups
for hole in holeGroup
for i in range(hole.segmentCount)
for face in hole.segment(i).faces
]
task.updateUI()
# Color the hole faces
with task.makeSubTask(progressRange=(90, 100)) as colorTask:
colorFaces(
app=app,
faces=facesToColor,
appearance=appearance,
task=colorTask,
)
CAMFunctionContext.succeed(f"Colored holes of '{manufacturingModel.name}' in {color_name}.")
@CAMFunctionContext.execute_if_unblocked
def color_pockets(manufacturing_model_name: str, color_name: str, color_code: tuple[float]):
with CAMFunctionTaskManager.nextTask("Coloring pockets") as task:
app, _, _, _, cam = init()
# Get the appearance
appearance = getAppearanceByColorName(designProduct(), color_name, color_code)
if appearance is None:
CAMFunctionContext.fail(
message="Error coloring pockets:",
error=f"Cannot create appearance with color {color_code}",
)
return
task.updateUI()
# Get the manufacturing model
manufacturingModel = None
with task.makeSubTask(message="Getting manufacturing model", progressRange=(0, 10)) as getModelTask:
if isNonEmptyString(manufacturing_model_name):
manufacturingModel = getManufacturingModelByName(cam, manufacturing_model_name)
if manufacturingModel is None:
CAMFunctionContext.fail(
message="Error coloring pockets:",
error=f"Manufacturing model '{manufacturing_model_name}' not found",
)
return
elif cam.manufacturingModels.count > 0:
manufacturingModel = cam.manufacturingModels.item(0)
else:
manufacturingModel = createManufacturingModel(cam=cam, task=getModelTask)
# Get the Bodies from model
manufacturingModelBodies = getManufacturingModelBRepBodies(manufacturingModel)
if len(manufacturingModelBodies) == 0:
CAMFunctionContext.warn(message="Warning coloring pockets:", warning=f"No bodies found for '{manufacturingModel.name}'")
return
task.updateUI()
# Recognize Pockets
with task.makeSubTask(progressRange=(10, 90), message="Recognizing pockets") as recognizeTask:
pockets: adsk.cam.RecognizedPockets = recognizePockets(
collections=manufacturingModelBodies,
parentTask=recognizeTask,
)
if len(pockets) == 0:
CAMFunctionContext.warn(
message="Warning coloring pockets:",
warning=f"No pockets found in the bodies of '{manufacturingModel.name}'",
)
return
# Collect the faces to color
facesToColor: list[adsk.fusion.BRepFace] = [
face for pocket in pockets for face in pocket.faces
]
task.updateUI()
# Color the pocket faces
with task.makeSubTask(progressRange=(90, 100)) as colorTask:
colorFaces(
app=app,
faces=facesToColor,
appearance=appearance,
task=colorTask,
)
CAMFunctionContext.succeed(f"Colored pockets of '{manufacturingModel.name}' in {color_name}.")
@CAMFunctionContext.execute_if_unblocked
def select_orientation_rank(
setup_name: dict | None = None,
rank: int = 1
):
with CAMFunctionTaskManager.nextTask("Selecting results") as task:
_, _, _, _, cam = init()
if not isinstance(rank, int) or rank < 0:
CAMFunctionContext.fail(
message="Error selecting orientation result:",
error=f"Invalid rank chosen: {rank}.",
)
return
# Get the specifics of the input name arguments
setup_name_str, regex_enabled = extractNameAndRegexEnabled(setup_name)
# Find the additive setup(s) from setup_name in the document
foundSetups, setups = findMatchingAdditiveSetups(
setupName=setup_name_str,
regexEnabled=regex_enabled,
cam=cam,
errorMessage="Error selecting orientation result:",
)
if not foundSetups:
return
# Select orientation results for each orientation study in each setup
with task.makeSubTask(numberOfTasks=len(setups)) as setupsTask:
for i, setup in enumerate(setups):
operations = setup.allOperations
orientations = []
for operation in operations:
# if it is an operation of type 'automatic_orientation', add it to the list
if isinstance(operation, adsk.cam.Operation) and operation.strategy == 'automatic_orientation':
orientations.append(operation)
if len(orientations) == 0:
CAMFunctionContext.fail(
message="Error selecting orientation result:",
error=f"No automatic orientation found in setup '{setup.name}'.",
)
return
with setupsTask.makeSubTaskByIndex(index=i, numberOfTasks=len(orientations)) as setupTask:
for j, orientation in enumerate(orientations):
with setupTask.makeSubTaskByIndex(index=j, numberOfTasks=3) as orientationTask:
# Wait until the orientation study is generated
with orientationTask.makeSubTaskByIndex(index=0, message="Generating orientation"):
while orientation.isGenerating:
time.sleep(0.1)
# Check if the orientation study is valid and the results exist
if orientation.operationState != adsk.cam.OperationStates.IsValidOperationState:
CAMFunctionContext.fail(
message="Error selecting orientation result:",
error=f"No results available for selection in orientation study '{orientation.name}' in setup '{setup.name}'.",
)
continue
# Find the orientation results from the generated data collection
with orientationTask.makeSubTaskByIndex(index=1, message="Finding result"):
generatedData = orientation.generatedDataCollection
orientationResults = None
orientationResultsData = generatedData.itemByIdentifier(adsk.cam.GeneratedDataType.OptimizedOrientationGeneratedDataType)
if not isinstance(orientationResultsData, adsk.cam.OptimizedOrientationResults):
CAMFunctionContext.fail(
message="Error selecting orientation result:",
error=f"No optimized orientation results found for '{orientation.name}' in setup '{setup.name}'.",
)
continue
# Select the orientation result by rank
with orientationTask.makeSubTaskByIndex(index=2, message="Selecting result"):
orientationResults: adsk.cam.OptimizedOrientationResults = orientationResultsData
if rank == 0:
# Select the initial result if rank is 0
orientationResults.currentOrientationResult = orientationResults.initialOrientationResult
CAMFunctionContext.succeed(
message=f"Selected the initial orientation for '{orientation.name}' in setup '{setup.name}'."
)
else:
# Select the orientation result by rank
rankIndex = rank - 1
if rankIndex >= orientationResults.count:
CAMFunctionContext.fail(
message="Error selecting orientation result:",
error=f"Rank {rank} is not available for '{orientation.name}' in setup '{setup.name}'.",
)
continue
orientationResults.currentOrientationResult = orientationResults.item(rankIndex)
CAMFunctionContext.succeed(
message=f"Selected the orientation result ranked {rank} for '{orientation.name}' in setup '{setup.name}'."
)
@CAMFunctionContext.execute_if_unblocked
def create_drilling_operations_for_holes(
setup_name: str,
hole_type: str = "all",
drilling_tool_library_definition: dict | None = None,
milling_tool_library_definition: dict | None = None,
):
"""
Create drilling operations for all/simple/counterbore holes in the
specified setup.
If tool libraries are provided, tools will be chosen from those libraries,
otherwise the document tool library will be used.
"""
with CAMFunctionTaskManager.nextTask("Drilling holes") as task:
_, _, _, _, cam = init()
drillingToolLibraryDefinition = extractToolLibraryDefinition(
toolLibraryDefinitionInput=drilling_tool_library_definition, cam=cam
)
millingToolLibraryDefinition = extractToolLibraryDefinition(
toolLibraryDefinitionInput=milling_tool_library_definition, cam=cam
)
# Validate the hole_type input
if hole_type not in ["all", "simple", "counterbore"]:
CAMFunctionContext.fail(
message="Error creating drilling operations for holes:",
error=f"Invalid hole type '{hole_type}'. Valid values are 'all', 'simple', or 'counterbore'."
)
return
# Get the setup
foundSetups, setups, _ = findMatchingSetups(
setupName=setup_name,
cam=cam,
regexEnabled=False,
errorMessage="Error creating drilling operations for holes:",
)
if not foundSetups:
return
task.updateUI()
setup: adsk.cam.Setup = setups[0]
# Get the tool libraries if specified, otherwise use the document tool library
with task.makeSubTask(
"Finding drilling library", progressRange=(0, 25)
) as drillLibraryTask:
if drillingToolLibraryDefinition and isNonEmptyString(
drillingToolLibraryDefinition.name
):
drillingToolLibraryDefinition.toolLibrary = getToolLibrary(
toolLibraryDefinition=drillingToolLibraryDefinition,
task=drillLibraryTask,
)
if drillingToolLibraryDefinition.toolLibrary is None:
CAMFunctionContext.fail(
message="Error creating drilling operations for holes:",
error=f"Drilling tool library '{drillingToolLibraryDefinition.name}' not found.",
)
return
with task.makeSubTask(
"Finding milling library", progressRange=(25, 35)
) as millLibraryTask:
if millingToolLibraryDefinition and isNonEmptyString(
millingToolLibraryDefinition.name
):
millingToolLibraryDefinition.toolLibrary = getToolLibrary(
toolLibraryDefinition=millingToolLibraryDefinition,
task=millLibraryTask,
)
if millingToolLibraryDefinition.toolLibrary is None:
CAMFunctionContext.fail(
message="Error creating drilling operations for holes:",
error=f"Milling tool library '{millingToolLibraryDefinition.name}' not found.",
)
return
# Get the BRep bodies used by the setup
setupBodies: list[adsk.fusion.BRepBody] = []
for model in setup.models:
if model.objectType == adsk.fusion.Occurrence.classType():
# Include bodies in the model itself
setupBodies.extend(body for body in model.bRepBodies)
# Include bodies in the child occurrences of the model
occs: list[adsk.fusion.Occurrence] = getValidOccurrences(model)
setupBodies.extend(body for occ in occs for body in occ.bRepBodies)
else:
setupBodies.append(model)
if len(setupBodies) == 0:
CAMFunctionContext.fail(
message="Error creating drilling operations for holes:",
error=f"No bodies found in setup '{setup.name}'."
)
return
# Recognize holes in the bodies
with task.makeSubTask(progressRange=(35, 50), message="Recognizing holes") as recognizeTask:
holeGroups: adsk.cam.RecognizedHoleGroups = recognizeHoleGroups(
bodies=setupBodies,
task=recognizeTask,
)
if holeGroups.count == 0:
CAMFunctionContext.fail(
message="Error creating drilling operations for holes:",
error=f"No holes found in the bodies of setup '{setup.name}'."
)
return
# Classify the holes by their profile and type
with task.makeSubTask(progressRange=(50, 60), message="Classifying holes") as classifyTask:
classifiedHoles: list[tuple[adsk.cam.RecognizedHoleGroup, HoleAttributes]] = classifyHoleGroups(
holeGroups=holeGroups,
task=classifyTask,
)
# Split into simple and counterbore hole groups into separate lists
simpleHoles = [(hole, attrs) for hole, attrs in classifiedHoles if attrs & HoleAttributes.SIMPLE]
counterboreHoles = [(hole, attrs) for hole, attrs in classifiedHoles if attrs & HoleAttributes.COUNTERBORE]
createSimple: bool = hole_type in ["all", "simple"]
createCounterbore: bool = hole_type in ["all", "counterbore"]
numberOfSimpleHoles: int = 0
if createSimple:
numberOfSimpleHoles = len(simpleHoles)
if numberOfSimpleHoles == 0:
CAMFunctionContext.warn(
message="Warning creating drilling operations for holes:",
warning=f"No simple holes found in the bodies of setup '{setup.name}'."
)
numberOfCounterboreHoles: int = 0
if createCounterbore:
numberOfCounterboreHoles = len(counterboreHoles)
if numberOfCounterboreHoles == 0:
CAMFunctionContext.warn(
message="Warning creating drilling operations for holes:",
warning=f"No counterbore holes found in the bodies of setup '{setup.name}'."
)
numberOfHolesToMill: int = numberOfSimpleHoles + numberOfCounterboreHoles
if numberOfHolesToMill == 0:
# No holes to mill, exit early
return
with task.makeSubTask(progressRange=(60, 100), message="Creating operations", numberOfTasks=numberOfHolesToMill) as millTask:
# Create drilling operations for simple holes
if createSimple and len(simpleHoles) > 0:
for i, (holeGroup, attrs) in enumerate(simpleHoles):
with millTask.makeSubTaskByIndex(index=i) as holeTask:
millHoleGroup(
setup=setup,
holeGroup=holeGroup,
holeAttributes=attrs,
drillingToolLibraryDefinition=drillingToolLibraryDefinition,
millingToolLibraryDefinition=millingToolLibraryDefinition,
task=holeTask,
)
CAMFunctionContext.succeed(
message=f"Created drilling operations for {len(simpleHoles)} simple hole groups in setup '{setup.name}'."
)
# Create drilling operations for counterbore holes
if createCounterbore and len(counterboreHoles) > 0:
for i, (holeGroup, attrs) in enumerate(counterboreHoles):
with millTask.makeSubTaskByIndex(index=i + numberOfSimpleHoles) as holeTask:
millHoleGroup(
setup=setup,
holeGroup=holeGroup,
holeAttributes=attrs,
drillingToolLibraryDefinition=drillingToolLibraryDefinition,
millingToolLibraryDefinition=millingToolLibraryDefinition,
task=holeTask,
)
CAMFunctionContext.succeed(
message=f"Created drilling operations for {len(counterboreHoles)} counterbore hole groups in setup '{setup.name}'."
)
@CAMFunctionContext.execute_if_unblocked
def create_milling_operations_for_pockets(
setup_name: str,
operation_type: str,
tool_definition: dict | None = None,
pocket_type: str = "all",
):
with CAMFunctionTaskManager.nextTask("Milling pockets") as task:
_, _, _, _, cam = init()
ERROR_MESSAGE = "Error creating operations to mill pockets:"
WARN_MESSAGE = "Warning creating operations to mill pockets:"
toolDefinition = extractToolDefinition(
toolDefinitionInput=tool_definition, cam=cam
)
foundSetups, setups, _ = findMatchingSetups(setup_name, False, cam, ERROR_MESSAGE)
if not foundSetups:
return
task.updateUI()
setup = setups[0]
if setup.models.count == 0:
CAMFunctionContext.fail(
message=ERROR_MESSAGE, error=f"Setup '{setup.name}' has no models."
)
return
toolLibraryDefinition = (
toolDefinition.toolLibraryDefinition if toolDefinition else None
)
with task.makeSubTask("Finding library", progressRange=(0, 30)) as libraryTask:
if toolLibraryDefinition and isNonEmptyString(toolLibraryDefinition.name):
toolDefinition.toolLibraryDefinition.toolLibrary = getToolLibrary(
toolLibraryDefinition=toolLibraryDefinition,
task=libraryTask,
)
if toolLibraryDefinition.toolLibrary is None:
CAMFunctionContext.warn(
message=WARN_MESSAGE,
warning=f"Tool library '{toolLibraryDefinition.name}' not found.",
)
# Get the tool to set, if provided
tool: adsk.cam.Tool | None = None
with task.makeSubTask("Finding tool", progressRange=(30, 40)) as toolTask:
if toolDefinition:
if not isNonEmptyString(toolDefinition.name):
if toolLibraryDefinition:
warningMsg = None
if isNonEmptyString(toolLibraryDefinition.name):
warningMsg = f"Tool library '{toolLibraryDefinition.name}'"
elif toolLibraryDefinition.type != ToolLibraryType.ALL:
warningMsg = f"Tool library type '{toolLibraryDefinition.type.value}'"
if isNonEmptyString(warningMsg):
CAMFunctionContext.warn(
message=WARN_MESSAGE,
warning=f"{warningMsg} is specified but no tool name is provided. Tool will not be set.",
)
else:
tool = getToolByName(
toolDefinition=toolDefinition,
task=toolTask,
)
if tool is None:
CAMFunctionContext.warn(
message=WARN_MESSAGE,
warning=f"Tool '{toolDefinition.name}' not found. Tool will not be set.",
)
with task.makeSubTask(progressRange=(40, 50), message="Recognizing pockets") as recognizeTask:
allPockets: adsk.cam.RecognizedPockets = recognizePockets(
collections=setup.models,
parentTask=recognizeTask,
)
targetPockets = []
if pocket_type == "all":
targetPockets = allPockets
else:
for pocket in allPockets:
if (
(pocket_type == "open" and not pocket.isClosed)
or (pocket_type == "closed" and pocket.isClosed)
or (pocket_type == "blind" and not pocket.isThrough)
or (
pocket_type == "blind closed"
and not pocket.isThrough
and pocket.isClosed
)
or (pocket_type == "through" and pocket.isThrough)
):
targetPockets.append(pocket)
if len(targetPockets) == 0:
CAMFunctionContext.warn(
message=f"Warning creating {operation_type} toolpath:",
warning=f"No {pocket_type if pocket_type != 'all' else ''} pockets found in setup '{setup.name}'.",
)
return
with task.makeSubTask(progressRange=(50, 100), message="Creating operations") as millTask:
millPockets(operation_type, targetPockets, setup, tool, millTask)
CAMFunctionContext.succeed(
f"Successfully created operations to mill {pocket_type} pockets in Setup '{setup.name}'."
)
@CAMFunctionContext.execute_if_unblocked
def get_parameters(item_name: str):
with CAMFunctionTaskManager.nextTask("Getting parameters") as task:
_, _, _, _, cam = init()
foundItem = None
# Look for a setup that matches the given name
with task.makeSubTask(progressRange=(0, 45)) as findTask:
setups = cam.setups
for i, setup in enumerate(setups):
findTask.updateTaskProgress(i, setups.count)
if hasMatchingCaseInsensitiveName(setup, item_name):
foundItem = setup
break
# If not found in setups, check operations
with task.makeSubTask(progressRange=(45, 90)) as findTask:
if foundItem is None:
allOperations = cam.allOperations
for i, operation in enumerate(allOperations):
findTask.updateTaskProgress(i, allOperations.count)
if hasMatchingCaseInsensitiveName(operation, item_name):
foundItem = operation
break
if foundItem is None:
CAMFunctionContext.fail(
message="Error getting parameters:",
error=f"Item with name '{item_name}' could not be found.",
)
return
params = foundItem.parameters
paramsToShow = []
with task.makeSubTask(progressRange=(90, 100)) as paramTask:
for i, param in enumerate(params):
paramTask.updateTaskProgress(i, params.count)
if (
param.isValid
and not param.isDeprecated
and param.isVisible
and param.isEditable
and param.isEnabled
):
paramsToShow.append(param.fullTitle)
if not paramsToShow:
CAMFunctionContext.warn(
message="Warning getting parameters:",
warning=f"No valid parameters found for '{foundItem.name}'.",
)
else:
CAMFunctionContext.succeed(
message=f"Retrieved parameters for '{foundItem.name}': <list_placeholder>",
list=paramsToShow,
)
@CAMFunctionContext.execute_if_unblocked
def rename_object(
current_name: str,
new_name: str,
object_type: str
):
_, _, _, _, cam = init()
with CAMFunctionTaskManager.nextTask("Renaming object") as task:
if not isNonEmptyString(new_name):
CAMFunctionContext.fail(
message="Error renaming object:",
error="Cannot determine the new name.",
)
return
with task.makeSubTask(progressRange=(0, 95)) as findTask:
# Find the object by name
matchingObject, typeStr = findObjectByName(cam, current_name, object_type, findTask)
with task.makeSubTask(progressRange=(95, 100)):
if matchingObject:
oldName: str = matchingObject.name
matchingObject.name = new_name
if matchingObject.name != new_name:
CAMFunctionContext.blockFutureExecution(
warning=f"Renamed {typeStr} '{oldName}' to '{matchingObject.name}' instead of '{new_name}'."
)
return
CAMFunctionContext.succeed(
message=f"Renamed {typeStr} '{oldName}' to '{new_name}'."
)
else:
CAMFunctionContext.fail(
message="Error renaming object:",
error=f"No objects found with name '{current_name}'.",
)
@CAMFunctionContext.execute_if_unblocked
def apply_template_to_parent(
template_name: str,
template_library_location: str,
parent_name: str,
template_library_folder_name: str | None = None,
):
"""
Applies a template from the template library to the specified parent object.
"""
ERROR_MESSAGE = "Error applying template:"
folderMessageString = (
f" the '{template_library_folder_name}' folder in"
if isNonEmptyString(template_library_folder_name)
else ""
)
with CAMFunctionTaskManager.nextTask("Applying template") as task:
_, _, _, _, cam = init()
# Convert the template_library_location string to an adsk.cam.LibraryLocations enum
# https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-EB5D852F-3EC5-4B95-9A34-AA373E01E043
possibleLocationStrings = [
"LocalLibraryLocation", # Represents the local folder in the library.
# "CloudLibraryLocation", # Represents the cloud folder in the library.
# "NetworkLibraryLocation", # For internal use only.
# "OnlineSamplesLibraryLocation", # For internal use only.
# "ExternalLibraryLocation", # Represents an external folder that is not in the library.
"Fusion360LibraryLocation", # Represents the fusion 360 folder in the library.
]
libraryLocation = None
if template_library_location == "LocalLibraryLocation":
libraryLocation = adsk.cam.LibraryLocations.LocalLibraryLocation
elif template_library_location == "Fusion360LibraryLocation":
libraryLocation = adsk.cam.LibraryLocations.Fusion360LibraryLocation
else:
CAMFunctionContext.fail(
message=ERROR_MESSAGE,
error=f"Invalid template library location '{template_library_location}'. Valid values are {possibleLocationStrings}.",
)
return
# Get the library manager to find the template
with task.makeSubTask(
message="Loading template", progressRange=(0, 30)
) as loadTask:
libraryManager = adsk.cam.CAMManager.get().libraryManager
templateLibrary = libraryManager.templateLibrary
libraryURL = templateLibrary.urlByLocation(libraryLocation)
# Get all asset names
#
# This can be a long operation, so we run it asynchronously and
# indeterminately increment the progress bar
async def worker() -> list[adsk.core.URL]:
return templateLibrary.childAssetURLs(libraryURL)
assetURLs = loadTask.runAsyncWorker(worker=worker)
# Create a dict of Template URL string to object containing information about the template
assetInfoDict = {
assetURL.toString(): {
"name": templateLibrary.templateAtURL(assetURL).name,
"assetURL": assetURL,
"parentLeafName": assetURL.parent.leafName,
}
for assetURL in assetURLs
}
# Find the template by name. There may be multiple templates with the same name in different folders.
candidateTemplateInfos = []
for _, assetInfo in assetInfoDict.items():
if areMatchingCaseInsensitiveNames(template_name, assetInfo["name"]):
# If no folder name is provided, store the template.
if not isNonEmptyString(template_library_folder_name):
candidateTemplateInfos.append(assetInfo)
continue
# If a folder name is provided, check it matches the parent leaf name.
if (
# For (root) level templates, the parent leaf name is empty
areMatchingCaseInsensitiveNames(
template_library_folder_name, "(root)"
)
and not assetInfo["parentLeafName"]
) or (
# Otherwise, check if the folder name matches the parent leaf name
areMatchingCaseInsensitiveNames(
template_library_folder_name, assetInfo["parentLeafName"]
)
):
candidateTemplateInfos.append(assetInfo)
continue
if len(candidateTemplateInfos) == 0:
CAMFunctionContext.fail(
message=ERROR_MESSAGE,
error=f"Template '{template_name}' not found in{folderMessageString} the {template_library_location} library.",
)
return
elif len(candidateTemplateInfos) > 1:
# Get the names of all the parent folders of these matched templates
# This will help the user to pick the correct template without having to search through the library
parentFolderNames = []
for templateInfo in candidateTemplateInfos:
if templateInfo["parentLeafName"] == "": # Root folder
parentFolderNames.append("(root)")
else:
parentFolderNames.append(templateInfo["parentLeafName"])
CAMFunctionContext.fail(
message=ERROR_MESSAGE,
error=f"Multiple templates found for name '{template_name}'. Please specify the containing folder. Folders found with this template: {getListAsString(parentFolderNames)}",
)
return
# Construct the URL of the template that we are looking for
template = templateLibrary.templateAtURL(candidateTemplateInfos[0]["assetURL"])
# Find the parent object to apply the template to
foundParents, parents, _ = findMatchingParents(
parentName=parent_name,
cam=cam,
regexEnabled=False,
errorMessage=ERROR_MESSAGE,
)
if not foundParents:
return
if len(parents) != 1:
CAMFunctionContext.fail(
message=ERROR_MESSAGE,
error=f"Multiple objects found for name '{parent_name}'. Please specify a more specific name.",
)
return
parent = parents[0]
task.updateUI()
# Convert the template to a template input
templateInput: adsk.cam.CreateFromCAMTemplateInput = (
adsk.cam.CreateFromCAMTemplateInput.create()
)
templateInput.camTemplate = template
# Apply the template to the setup
with task.makeSubTask(message="Applying template", progressRange=(30, 100)) as createTask:
try:
# At the moment, we don't prevent the UI messagebox error from popping up if the operations are not compatible with the setup.
# This could be improved by using the .operations property of the template
# (see here https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-0586D2B2-0683-462B-B3B8-B1E387CEC0F4)
# However, until this property is taken OUT of "preview" mode, I don't think we should use it.
#
# This can be a long operation, so we run it asynchronously and
# indeterminately increment the progress bar
async def worker() -> list[adsk.cam.OperationBase]:
return parent.createFromCAMTemplate2(templateInput)
createTask.runAsyncWorker(worker=worker)
except Exception:
CAMFunctionContext.fail(
message=ERROR_MESSAGE,
error="Template is not compatible with the setup.",
)
return
CAMFunctionContext.succeed(
message=f"Successfully applied template '{template.name}' to '{parent.name}' from{folderMessageString} the {template_library_location} library."
)
@CAMFunctionContext.execute_if_unblocked
def highlight_operation(
parent_name: dict | None = None,
operation_name: dict | None = None,
operation_type: str | None = None,
):
with CAMFunctionTaskManager.nextTask("Highlighting operations") as task:
_, ui, _, _, cam = init()
# Get the specifics of the input name arguments
parent_name_str, parent_name_regex_enabled = extractNameAndRegexEnabled(
parent_name
)
operation_name_str, operation_name_regex_enabled = extractNameAndRegexEnabled(
operation_name
)
# If operation_type is provided, validate it is a valid strategy
strategy: adsk.cam.OperationStrategy | None = None
if isNonEmptyString(operation_type):
try:
strategy = adsk.cam.OperationStrategy.createFromString(operation_type)
except Exception:
CAMFunctionContext.fail(
message="Error highlighting operation:",
error=f"Invalid operation type '{operation_type}' specified.",
)
return
# Find the parent object(s) from parent_name in the document
foundParents, parents, _ = findMatchingParents(
parentName=parent_name_str,
cam=cam,
regexEnabled=parent_name_regex_enabled,
errorMessage="Error highlighting operation:",
)
if not foundParents:
return
# Helper method for deciding if a candidate operation is to be highlighted
def shouldHighlightOperation(operation: adsk.cam.OperationBase) -> bool:
if isNonEmptyString(operation_name_str):
if not hasMatchingCaseInsensitiveName(
item=operation,
name=operation_name_str,
regexEnabled=operation_name_regex_enabled,
):
return False
if strategy is not None:
if operation.strategy != strategy.name:
return False
return True
# Collect the operations to highlight
operationsToHighlight: list[adsk.cam.OperationBase] = []
with task.makeSubTask(
progressRange=(0, 80), numberOfTasks=len(parents)
) as collectTask:
for i, parent in enumerate(parents):
with collectTask.makeSubTaskByIndex(index=i):
operationsToHighlight.extend(
operation
for operation in parent.operations
if shouldHighlightOperation(operation)
)
# Don't change the current selection if there was nothing to highlight
if len(operationsToHighlight) == 0:
CAMFunctionContext.warn(
message="Warning highlighting operation:",
warning="No operations found matching the specified criteria.",
)
return
# Highlight the operations
with task.makeSubTask(progressRange=(80, 100)) as highlightTask:
selectOperations(
ui=ui,
operations=operationsToHighlight,
task=highlightTask,
)
if len(operationsToHighlight) == 1:
CAMFunctionContext.succeed(
message=f"Highlighted '{operationsToHighlight[0].name}'."
)
else:
CAMFunctionContext.succeed(
message=f"Highlighted {len(operationsToHighlight)} operations."
)