app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
Drew Owens
The mechanical layer on 1.12.8 with the restart fixes (1.13.0-dev.1, add-in 1.0.23)
2ef0220
15d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
"""Typed native coupling with exact joint/degree-of-freedom readback."""
import math
import adsk.fusion
from .assembly_state import joint_motion
from .core import concrete, items, number, require, resolve, same
from .joint_state import LINK_MOTIONS, entity_link, link_data, link_motion_name
from .sketches import quantity
def root_joint(design, value, raw):
joint = resolve(design, value, raw, ("Joint", "AsBuiltJoint"))
require(getattr(joint, "assemblyContext", None) is None and same(joint.parentComponent, design.rootComponent),
"root_joint_required", "Motion links currently require root-owned joints.")
require(not joint.isSuppressed, "suppressed_joint", "Unsuppress a joint before coupling its motion.")
return joint
def motion_enum(joint, name):
family = joint_motion(joint).objectType.split("::")[-1]
members = LINK_MOTIONS.get(family, {})
require(name in members, "wrong_joint_type", "The selected joint does not have motion " + name + ".")
enums = getattr(adsk.fusion, "JointMotionTypes", None)
require(enums is not None and hasattr(enums, members[name]), "unsupported_capability", "This Fusion build does not expose native motion links.")
return getattr(enums, members[name])
def inputs(design, first, second, args):
require(not same(first, second) or args["motionOne"] != args["motionTwo"], "invalid_motion_link", "Two different joint degrees of freedom are required.")
enums, values, expected = [], [], {}
for suffix, joint in (("One", first), ("Two", second)):
motion = args["motion" + suffix]
enums.append(motion_enum(joint, motion))
unit = "mm" if "lide" in motion else "deg"
inp = quantity(design, args["value" + suffix], unit, positive=True)
magnitude = design.unitsManager.evaluateExpression(inp.stringValue, unit)
require(magnitude > 0, "invalid_motion_link", "Link magnitudes must be positive; use reversed for opposite direction.")
values.append(inp)
expected["value" + suffix] = magnitude * (10 if unit == "mm" else 180 / math.pi)
return enums, values, expected
def result(link, fresh, args, first, second, expected):
data = link_data(link, fresh)
# A same-joint link can expose either null or the first joint again as its
# native second member. Canonicalize only the effective member for equality;
# link_data preserves the actual native jointTwo representation.
actual_first, actual_second = concrete(link.jointOne), concrete(link.jointTwo)
require(entity_link(actual_first) == entity_link(first)
and entity_link(actual_second or actual_first) == entity_link(second or first),
"motion_link_refused", "Linked joint identities did not read back.")
for key in ("name", "suppressed", "reversed", "motionOne", "motionTwo"):
require(key not in args or data[key] == args[key], "motion_link_refused", "Motion link did not retain " + key)
for key, wanted in expected.items():
require(math.isclose(data[key]["value"], wanted, rel_tol=1e-8, abs_tol=1e-6), "motion_link_refused", "Motion-link ratio did not read back.")
require(link.healthState == adsk.fusion.FeatureHealthStates.HealthyFeatureHealthState, "unhealthy_motion_link", "Motion link is not healthy.")
return {"motionLink": data}
def motion_value(joint, enum):
name = link_motion_name(joint, enum)
motion = joint_motion(joint)
require(hasattr(motion, name + "Value"), "motion_link_unverifiable", "Linked motion has no readable value.")
return number(getattr(motion, name + "Value"), "linked motion")
def coupling_snapshot(design):
collection = getattr(design.rootComponent, "motionLinks", None)
if collection is None:
return []
result = []
for link in items(collection):
if link.isSuppressed:
continue
first, second = concrete(link.jointOne), concrete(link.jointTwo or link.jointOne)
require(not first.isSuppressed and not second.isSuppressed, "motion_link_unverifiable", "An active link references a suppressed joint.")
result.append((link, motion_value(first, link.motionOne), motion_value(second, link.motionTwo), link_data(link)))
return result
def verify_coupling(before):
verified = []
for link, one, two, state in before:
require(link.isValid and not link.isSuppressed, "motion_link_refused", "Driving unexpectedly removed or suppressed a motion link.")
first, second = concrete(link.jointOne), concrete(link.jointTwo or link.jointOne)
require(link_data(link) == state, "motion_link_refused", "Driving unexpectedly changed motion-link membership or settings.")
delta_one = motion_value(first, link.motionOne) - one
delta_two = motion_value(second, link.motionTwo) - two
value_one, value_two = number(link.valueOne.value, "link magnitude"), number(link.valueTwo.value, "link magnitude")
require(abs(value_one) > 1e-12 and abs(value_two) > 1e-12, "motion_link_unverifiable", "Link magnitude cannot be zero.")
require(math.isclose(delta_one/value_one, (-1 if link.isReversed else 1)*delta_two/value_two, abs_tol=1e-7, rel_tol=1e-7),
"motion_link_drive_refused", "Driven motion did not propagate according to the active link ratio.")
verified.append({"name": link.name, "jointOne": entity_link(first), "jointTwo": entity_link(second), "ratioVerified": True})
return verified
def motion_link_create(design, value, args):
first = root_joint(design, value, args["jointOne"])
second = root_joint(design, value, args["jointTwo"]) if "jointTwo" in args else None
require(second is None or not same(first, second), "invalid_motion_link", "Omit jointTwo to couple two motions of jointOne.")
enums, values, expected = inputs(design, first, second or first, args)
collection = getattr(design.rootComponent, "motionLinks", None)
require(collection is not None, "unsupported_capability", "This Fusion build has no motionLinks collection.")
inp = collection.createInput(first, second)
require(inp is not None, "motion_link_failed", "Fusion did not return MotionLinkInput.")
inp.motionOne, inp.motionTwo = enums
inp.valueOne, inp.valueTwo = values
inp.isReversed = args.get("reversed", False)
link = collection.add(inp)
require(link is not None and link.isValid, "motion_link_failed", "Fusion did not create a motion link.")
if "name" in args:
link.name = args["name"]
return lambda fresh: result(link, fresh, {"reversed": False, **args}, first, second, expected)
def motion_link_edit(design, value, args):
link = resolve(design, value, args["link"], ("MotionLink",))
require(getattr(link, "assemblyContext", None) is None and same(link.parentComponent, design.rootComponent),
"root_joint_required", "Edit the root-owned motion link.")
require(set(args) & {"name", "suppressed", "reversed", "motionOne", "motionTwo", "valueOne", "valueTwo"}, "invalid_input", "Supply a motion-link change.")
first, second = concrete(link.jointOne), concrete(link.jointTwo)
coupling = set(args) & {"motionOne", "motionTwo", "valueOne", "valueTwo", "reversed"}
expected = {}
effective = dict(args)
if coupling:
old = link_data(link)
changes = {key: args.get(key, old[key]) for key in ("motionOne", "motionTwo", "reversed")}
changes.update({key: args.get(key, old[key]["expression"]) for key in ("valueOne", "valueTwo")})
effective.update(changes)
enums, values, expected = inputs(design, first, second or first, changes)
# Unlike regular-joint redefinition, setMotionData requires the link to
# exist in the evaluated timeline. Native rollback before this item
# removes its component context and fails InternalValidationError.
require(design.timeline.markerPosition == design.timeline.count, "timeline_not_at_end",
"Motion-link coupling edits require the timeline at the end.")
require(link.setMotionData(enums[0], values[0], enums[1], values[1], changes["reversed"]),
"motion_link_failed", "Fusion refused updated coupling data.")
if "name" in args:
link.name = args["name"]
if "suppressed" in args:
link.isSuppressed = args["suppressed"]
if design.snapshots.hasPendingSnapshot:
require(design.snapshots.add() is not None, "motion_link_capture_failed", "Could not capture link suppression.")
return lambda fresh: result(link, fresh, effective, first, second, expected)
def motion_link_delete(design, value, args):
link = resolve(design, value, args["link"], ("MotionLink",))
require(getattr(link, "assemblyContext", None) is None and same(link.parentComponent, design.rootComponent),
"root_joint_required", "Delete the root-owned motion link.")
name = link.name
require(link.deleteMe(), "delete_failed", "Fusion refused motion-link deletion.")
return {"deletedMotionLink": name}
OPERATIONS = {name: globals()[name] for name in ("motion_link_create", "motion_link_edit", "motion_link_delete")}