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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
"""Geometry-solving joints and reusable origins; all writes use PTransaction."""
from contextlib import contextmanager
import math
import adsk.core
import adsk.fusion
from . import compute
from .core import concrete, identity, items, ref, require, resolve, same
from .geometry import native, owner, root_entity
from .joint_state import (geometry_state, matrix_data, origin_data,
regular_state)
from .sketches import component, named, quantity
from .assemblies import configure_joint, joint_result
GEOMETRY_TYPES = ("BRepFace", "BRepEdge", "BRepVertex", "SketchPoint", "ConstructionPoint",
"SketchLine", "SketchArc", "SketchCircle", "SketchEllipse", "SketchEllipticalArc")
MOTION_METHODS = {"rigid": "Rigid", "revolute": "Revolute", "slider": "Slider", "cylindrical": "Cylindrical",
"pin_slot": "PinSlot", "planar": "Planar", "ball": "Ball"}
@contextmanager
def before_timeline(design, entity):
"""Restore the exact caller marker, including on native setter exceptions."""
position = design.timeline.markerPosition
timeline = entity.timelineObject
require(timeline is not None and timeline.isValid, "timeline_unavailable", "This edit requires a valid parametric timeline item.")
try:
require(timeline.rollTo(True), "timeline_roll_failed", "Could not roll immediately before the joint item.")
yield
finally:
design.timeline.markerPosition = position
require(design.timeline.markerPosition == position, "timeline_restore_failed", "Could not restore the original timeline marker.")
def geometry_from_entity(entity, key_name=None, edge=None):
entity, edge = concrete(entity), concrete(edge)
kind = entity.objectType.split("::")[-1]
geometry = getattr(entity, "geometry", None)
shape = geometry.objectType.split("::")[-1] if geometry is not None else ""
default = "middle" if kind == "BRepFace" and shape in ("Cylinder", "Cone") else "center"
key_name = key_name or default
key = getattr(adsk.fusion.JointKeyPointTypes, {"center": "CenterKeyPoint", "start": "StartKeyPoint",
"middle": "MiddleKeyPoint", "end": "EndKeyPoint"}[key_name])
factory = adsk.fusion.JointGeometry
if kind in ("BRepVertex", "SketchPoint", "ConstructionPoint"):
require(key_name == "center" and edge is None, "invalid_joint_geometry", "Point attachments have no edge/key-point choice.")
result = factory.createByPoint(entity)
elif kind == "BRepEdge" or kind.startswith("Sketch"):
require(edge is None, "invalid_joint_geometry", "An edge qualifier belongs only to a planar face.")
require(shape in ("Line3D", "Arc3D", "Circle3D", "Ellipse3D", "EllipticalArc3D"),
"unsupported_joint_geometry", "Use a line or analytical arc/circle/ellipse attachment.")
require(shape not in ("Circle3D", "Ellipse3D") or key_name == "center", "invalid_joint_geometry", "Closed circles/ellipses require center.")
result = factory.createByCurve(entity, key)
elif kind == "BRepFace" and shape == "Plane":
require(edge is not None or key_name == "center", "invalid_joint_geometry", "A planar face requires center or one of its explicit edges.")
if edge is not None:
require(any(same(native(item), native(edge)) for item in items(entity.edges)), "invalid_joint_geometry", "Selected edge does not belong to this face.")
result = factory.createByPlanarFace(entity, edge, key)
else:
require(kind == "BRepFace" and shape in ("Cylinder", "Cone", "Sphere", "Torus") and edge is None,
"unsupported_joint_geometry", "Joint face attachment requires a plane, cylinder, cone, sphere or torus.")
result = factory.createByNonPlanarFace(entity, key)
require(result is not None and result.isValid, "invalid_joint_geometry", "Fusion did not create joint geometry.")
return result
def selector(design, value, raw, definition=None):
if "origin" in raw:
require(definition is None, "invalid_joint_geometry", "A joint origin must be attached to geometry, not another origin.")
origin = resolve(design, value, raw["origin"], ("JointOrigin",))
root_entity(design, origin)
return origin
entity = resolve(design, value, raw["entity"], GEOMETRY_TYPES)
edge = resolve(design, value, raw["edge"], ("BRepEdge",)) if "edge" in raw else None
for item in (entity, edge):
if item is None:
continue
if definition is None:
root_entity(design, item)
else:
require(getattr(item, "assemblyContext", None) is None and same(owner(item), definition),
"wrong_component", "Joint-origin inputs require native geometry in the selected component definition.")
if edge is not None:
require(same(getattr(entity, "assemblyContext", None), getattr(edge, "assemblyContext", None)),
"invalid_context", "Face and edge must refer to the same occurrence instance.")
return geometry_from_entity(entity, raw.get("keyPoint"), edge)
def attachment_context(geometry):
geometry = concrete(geometry)
entity = concrete(geometry if geometry.objectType.endswith("::JointOrigin") else geometry.entityOne)
require(entity is not None and entity.isValid, "lost_joint_geometry", "Attachment has no live source entity.")
return getattr(entity, "assemblyContext", None)
def check_pair(design, first, second, contexts=None):
contexts = list(contexts) if contexts is not None else [attachment_context(g) for g in (first, second)]
require(not same(*contexts), "invalid_joint", "Joint attachments must belong to distinct root/component instances.")
for context in contexts:
if context is not None:
root_entity(design, context)
if all(context is not None for context in contexts):
a, b = [context.fullPathName for context in contexts]
require(not a.startswith(b + "+") and not b.startswith(a + "+"), "invalid_joint", "Cannot joint an occurrence to its own descendant.")
require(not all(context is None or context.isGrounded for context in contexts),
"grounded_conflict", "Both joint sides are grounded; unground the intended movable instance explicitly.")
return contexts
def set_motion(subject, args):
kind = args["type"]
require(not (kind in ("rigid", "ball") and ("axis" in args or "slideAxis" in args)), "invalid_joint_axes", "This joint motion has no configurable axis choice.")
require(kind == "pin_slot" or "slideAxis" not in args, "invalid_joint_axes", "slideAxis is specific to pin-slot joints.")
method = getattr(subject, "setAs" + MOTION_METHODS[kind] + "JointMotion")
directions = adsk.fusion.JointDirections
axis = getattr(directions, args.get("axis", "z").upper() + "AxisJointDirection")
if kind == "rigid":
ok = method()
elif kind == "ball":
ok = method(directions.ZAxisJointDirection, directions.XAxisJointDirection)
elif kind == "pin_slot":
require(args.get("axis", "z") != args.get("slideAxis", "x"), "invalid_joint_axes", "Pin-slot rotation and slide axes must differ.")
ok = method(axis, getattr(directions, args.get("slideAxis", "x").upper() + "AxisJointDirection"))
else:
ok = method(axis)
require(ok, "joint_failed", "Fusion refused the requested motion definition.")
def lock_arguments(args, joint=None):
"""Rigid motion has no lock setter; its implicit native lock flag is false."""
kind = args.get("type")
if kind is None:
motion = joint.jointMotion
require(motion is not None, "unsupported_inferred_joint", "Cannot determine the joint's native motion definition.")
rigid = motion.objectType == "adsk::fusion::RigidJointMotion"
else:
rigid = kind == "rigid"
if rigid:
require(not args.get("locked", False), "unsupported_joint_lock", "Rigid joints cannot be locked; omit locked or use false.")
# Verify the implicit flag even when a rigid redefinition omitted it.
return {**args, "locked": False}
return args
def apply_lock(joint, args):
if "locked" not in args:
return
motion = joint.jointMotion
require(motion is not None, "unsupported_inferred_joint", "Cannot apply a lock without a native motion definition.")
if motion.objectType == "adsk::fusion::RigidJointMotion":
require(not args["locked"], "joint_edit_refused", "The native joint remained rigid and cannot accept a lock.")
# Native rigid setters throw even for false. setAsRigidJointMotion
# clears an existing moving-joint lock; regular_result checks readback.
return
joint.isLocked = args["locked"]
def expected_parameters(design, args, names):
result = {}
for name in names:
if name in args:
unit = "deg" if name == "angle" else "mm"
inp = quantity(design, args[name], unit)
number = design.unitsManager.evaluateExpression(inp.stringValue, unit)
result[name] = (inp, number * (180 / math.pi if unit == "deg" else 10))
return result
def verify_parameters(actual, expected):
for name, (_, wanted) in expected.items():
require(math.isclose(actual[name]["value"], wanted, abs_tol=1e-6, rel_tol=1e-8),
"joint_parameter_refused", "Joint did not retain the requested " + name + ".")
def capture_pending(design):
if design.snapshots.hasPendingSnapshot:
require(design.snapshots.add() is not None, "joint_capture_failed", "Could not capture pending joint state.")
return True
return False
def source_reference(entity, value):
entity = concrete(entity)
if entity is None:
return None
component = owner(entity)
require(component is not None, 'unsupported_joint_geometry', 'Attachment source has no readable component owner.')
return {'reference': ref(entity, value), 'ownerId': identity(component)}
def capture_attachment(geometry, value, context=None):
"""Freeze graph locators/scalars before solve; retain no transient geometry."""
geometry = concrete(geometry)
state = geometry_state(geometry)
result = {'type': geometry.objectType, 'context': ref(context, value) if context is not None else None}
if geometry.objectType.endswith('::JointOrigin'):
result['origin'] = source_reference(geometry, value)
else:
result.update({key: state[key] for key in ('geometryType', 'keyPointType')})
result['sources'] = {key: source_reference(getattr(geometry, key), value)
for key in ('entityOne', 'entityTwo', 'planeOne', 'planeTwo')}
result['tangent'] = {key: state[key] for key in ('tangentFaceType', 'tangentFaceParamOne', 'tangentFaceParamTwo') if key in state}
return result
def resolve_source(design, value, saved):
if saved is None:
return None
entity = resolve(design, value, saved['reference'])
require(owner(entity) is not None and identity(owner(entity)) == saved['ownerId'],
'joint_attachment_refused', 'Attachment source changed component owner.')
return entity
def verify_attachment(design, value, actual, saved, occurrence=None, definition=False):
"""Compare fresh native API entities, never stale wrappers or token text."""
actual = concrete(actual)
require(actual is not None and actual.isValid and actual.objectType == saved['type'],
'joint_attachment_refused', 'Joint attachment type changed.')
expected_context = resolve(design, value, saved['context'], ('Occurrence',)) if saved['context'] else None
require(same(occurrence, expected_context), 'joint_attachment_refused', 'Joint resolved a different occurrence instance.')
require(not definition or (occurrence is None and expected_context is None and 'origin' not in saved),
'joint_attachment_refused', 'Definition verification is only valid for native origin geometry.')
context = attachment_context(actual)
require(definition or context is None or same(context, occurrence),
'joint_attachment_refused', 'Attachment proxy disagrees with the joint occurrence.')
if 'origin' in saved:
require(same(native(actual), native(resolve_source(design, value, saved['origin']))),
'joint_attachment_refused', 'Joint origin identity changed.')
return
state = geometry_state(actual)
require(all(state[key] == saved[key] for key in ('geometryType', 'keyPointType'))
and all(state.get(key) == wanted for key, wanted in saved['tangent'].items()),
'joint_attachment_refused', 'Joint attachment definition changed.')
for key, locator in saved['sources'].items():
found, wanted = concrete(getattr(actual, key)), resolve_source(design, value, locator)
require((found is None and wanted is None) or (found is not None and wanted is not None
and found.objectType == wanted.objectType and same(native(found), native(wanted))),
'joint_attachment_refused', 'Joint attachment source did not read back: ' + key)
found_context = getattr(found, 'assemblyContext', None) if found is not None else None
require(found_context is None or (definition and same(found_context.component, owner(wanted)))
or (not definition and same(found_context, occurrence)),
'joint_attachment_refused', 'Joint attachment source has a different occurrence context.')
def validate_frame(matrix):
require(len(matrix) == 16 and all(math.isfinite(v) for v in matrix), "joint_frame_unavailable", "Attachment frame is incomplete.")
axes = [[matrix[row * 4 + col] for row in range(3)] for col in range(3)]
require(all(math.isclose(sum(v*v for v in axis), 1, abs_tol=1e-6) for axis in axes)
and all(abs(sum(a*b for a,b in zip(axes[i],axes[k]))) < 1e-6 for i,k in ((0,1),(0,2),(1,2))),
"joint_frame_unavailable", "Attachment frame is not orthonormal.")
cross = [axes[0][1]*axes[1][2]-axes[0][2]*axes[1][1], axes[0][2]*axes[1][0]-axes[0][0]*axes[1][2], axes[0][0]*axes[1][1]-axes[0][1]*axes[1][0]]
require(sum(a*b for a,b in zip(cross,axes[2])) > 1-1e-6, "joint_frame_unavailable", "Attachment frame is not right-handed.")
def attachment_frames(joint):
# Native joint transforms already locate root-owned joint anchors in root
# space. JointGeometry frames use another axis convention and can become
# unreadable after compute even while isValid remains true. Do not rebuild
# them from native faces or apply occurrence transforms a second time.
require(getattr(joint, 'assemblyContext', None) is None
and same(joint.parentComponent, joint.parentComponent.parentDesign.rootComponent),
'root_joint_required', 'Frame readback requires a root-owned joint.')
matrices = [matrix_data(joint.geometryOneTransform), matrix_data(joint.geometryTwoTransform)]
for matrix in matrices:
validate_frame(matrix)
return {"space": "native_joint_api", "originSpace": "root", "translationUnit": "mm", "transformLayout": "row-major",
"source": "Joint.geometryOneTransform / Joint.geometryTwoTransform",
"axisConvention": "native joint transform; distinct from transient JointGeometry axes",
"one": matrices[0], "two": matrices[1]}
def initial_alignment(joint, offset, angle, kind):
frames = attachment_frames(joint)
one, two = frames["one"], frames["two"]
delta = [one[i]-two[i] for i in (3,7,11)]
distance = math.sqrt(sum(v*v for v in delta))
require(math.isclose(distance, abs(offset), abs_tol=1e-5, rel_tol=1e-7),
"joint_alignment_failed", "Solved attachment origins do not match the requested offset magnitude.", actualDistance=distance, expectedDistance=abs(offset))
if abs(offset) > 1e-7:
axis = [two[i] for i in (2,6,10)]
axial = sum(a*b for a,b in zip(delta,axis))
require(math.sqrt(max(0, distance*distance-axial*axial)) < 1e-5,
"joint_alignment_failed", "Joint offset is not along the attachment Z axis.")
axes_checked = kind != "ball" and abs(angle) < 1e-9
if axes_checked:
dot = sum(one[i]*two[i] for i in (2,6,10))
require(abs(abs(dot)-1) < 1e-6, "joint_alignment_failed", "Solved attachment Z axes are not collinear.")
return {**frames, "originDistance": distance, "offsetMagnitudeVerified": True,
"zeroAngleAxisCollinearityVerified": axes_checked,
"orientationSenseVerification": "native angle/flip parameter readback; physical orientation requires independent occurrence geometry acceptance"}
def poses(design):
return [(o, list(o.transform2.asArray()), bool(o.isGrounded), bool(getattr(o, "isGroundToParent", False)))
for o in items(design.rootComponent.allOccurrences)]
def pose_result(design, before):
live = items(design.rootComponent.allOccurrences)
require(len(live) == len(before), "joint_structure_changed", "Joint solving unexpectedly changed the occurrence count.")
# each occurrence against the live one at its path, not against every live one (that was n squared
# native comparisons: hours on a 4,579-occurrence assembly)
live_by_path = {o.fullPathName: o for o in live}
changed = []
for occurrence, old, grounded, ground_to_parent in before:
require(occurrence.isValid and same(occurrence, live_by_path.get(occurrence.fullPathName)), "joint_structure_changed",
"Joint solving replaced an occurrence.")
current = list(occurrence.transform2.asArray())
moved = any(not math.isclose(a, b, abs_tol=1e-8, rel_tol=1e-8) for a, b in zip(current, old))
require(bool(occurrence.isGrounded) == grounded and bool(getattr(occurrence, "isGroundToParent", False)) == ground_to_parent,
"grounding_changed", "Joint solving changed grounding state.")
require(not grounded or not moved, "grounded_conflict", "Joint solving moved a grounded occurrence.")
if moved:
changed.append({"occurrenceId": identity(occurrence), "path": occurrence.fullPathName,
"before": [v * 10 if i in (3, 7, 11) else v for i, v in enumerate(old)],
"after": matrix_data(occurrence.transform2)})
return {"space": "root", "translationUnit": "mm", "transformLayout": "row-major", "changedOccurrences": changed,
"groundedOccurrencesPreserved": True}
def regular_result(design, joint, fresh, args, expected, attachments, before, original_value):
result = joint_result(joint, fresh, args)
state = result["joint"]
verify_parameters(state, expected)
for key, prop in (("flipped", "isFlipped"), ("locked", "isLocked")):
require(key not in args or getattr(joint, prop) == args[key], "joint_edit_refused", "Joint did not retain " + key)
if "type" in args:
require(state["motionType"].endswith("::" + MOTION_METHODS[args["type"]] + "JointMotion"), "joint_edit_refused", "Joint motion type did not read back.")
verify_motion_axes(state["axes"], args)
contexts = (joint.occurrenceOne, joint.occurrenceTwo)
for (prop, expected_attachment), context in zip(attachments.items(), contexts):
verify_attachment(design, original_value, getattr(joint, prop), expected_attachment, context)
result["placement"] = pose_result(design, before)
result["attachmentFrames"] = attachment_frames(joint)
return result
def verify_motion_axes(actual, args):
kind = args["type"]
field = {"revolute": "rotationAxis", "slider": "slideDirection", "cylindrical": "rotationAxis",
"pin_slot": "rotationAxis", "planar": "normalDirection"}.get(kind)
expected = {field: args.get("axis", "z")} if field else {}
if kind == "pin_slot":
expected["slideDirection"] = args.get("slideAxis", "x")
if kind == "ball":
expected.update(pitchDirection="z", yawDirection="x")
for key, axis in expected.items():
require(actual[key] == getattr(adsk.fusion.JointDirections, axis.upper() + "AxisJointDirection"),
"joint_axis_refused", "Joint did not retain the requested " + key + ".")
def regular_joint_create(design, value, args):
args = lock_arguments({"flipped": False, **args})
require(not design.snapshots.hasPendingSnapshot, "pending_position_changes", "Capture or revert pending positions before regular-joint solving.")
first, second = [selector(design, value, args[key]) for key in ("geometryOne", "geometryTwo")]
expected_contexts = check_pair(design, first, second)
attachments = {prop: capture_attachment(geometry, value, context) for prop, geometry, context in
zip(('geometryOrOriginOne', 'geometryOrOriginTwo'), (first, second), expected_contexts)}
before = poses(design)
expected = expected_parameters(design, {"angle": 0, "offset": 0, **args}, ("angle", "offset"))
inp = design.rootComponent.joints.createInput(first, second)
require(inp is not None, "joint_failed", "Fusion did not return JointInput.")
for name, (quantity_input, _) in expected.items():
setattr(inp, name, quantity_input)
inp.isFlipped = args.get("flipped", False)
set_motion(inp, args)
joint = named(design.rootComponent.joints.add(inp), args)
# Fusion solves the joint as it is added; the write's own settle decides on a full recompute. Faults
# the model already carried are allowed, as in settle: the joint may not add or change one.
compute.midway(design)
alignment = initial_alignment(joint, expected["offset"][1], expected["angle"][1], args["type"])
apply_lock(joint, args)
configure_joint(design, joint, args)
capture_pending(design)
def result(fresh):
row = regular_result(design, joint, fresh, args, expected, attachments, before, value)
row["initialAlignment"] = alignment
return row
return result
def regular_joint_edit(design, value, args):
require(not design.snapshots.hasPendingSnapshot, "pending_position_changes", "Capture or revert pending positions before regular-joint redefinition.")
joint = resolve(design, value, args["joint"], ("Joint",))
changed = set(args) & {"geometryOne", "geometryTwo", "angle", "offset", "offsetX", "offsetY", "flipped", "locked", "type"}
require(changed, "invalid_input", "Supply a geometry, offset, angle, flip, lock or motion-definition change.")
args = lock_arguments(args, joint)
require(getattr(joint, "assemblyContext", None) is None and same(joint.parentComponent, design.rootComponent),
"root_joint_required", "Regular-joint redefinition currently requires a root-owned joint.")
regular_state(joint) # Fail before rollback if attachment readback is incomplete.
require("type" in args or not ({"axis", "slideAxis"} & set(args)), "invalid_input", "Axis changes require the motion type.")
attachments = {prop: selector(design, value, args[key]) if key in args else getattr(joint, prop)
for key, prop in (("geometryOne", "geometryOrOriginOne"), ("geometryTwo", "geometryOrOriginTwo"))}
expected_contexts = [attachment_context(attachments[prop]) if key in args else getattr(joint, occurrence)
for key,prop,occurrence in (("geometryOne","geometryOrOriginOne","occurrenceOne"),("geometryTwo","geometryOrOriginTwo","occurrenceTwo"))]
check_pair(design, *attachments.values(), contexts=expected_contexts)
saved_attachments = {prop: capture_attachment(geometry, value, context)
for (prop, geometry), context in zip(attachments.items(), expected_contexts)}
expected = expected_parameters(design, args, ("angle", "offset", "offsetX", "offsetY"))
before = poses(design)
with before_timeline(design, joint):
for key, prop in (("geometryOne", "geometryOrOriginOne"), ("geometryTwo", "geometryOrOriginTwo")):
if key in args:
setattr(joint, prop, attachments[prop])
if "type" in args:
set_motion(joint, args)
if "flipped" in args:
joint.isFlipped = args["flipped"]
for name, (quantity_input, _) in expected.items():
getattr(joint, name).expression = quantity_input.stringValue
apply_lock(joint, args)
capture_pending(design)
return lambda fresh: regular_result(design, joint, fresh, args, expected, saved_attachments, before, value)
def origin_axes(design, value, args, comp):
result = {}
for key in ("xAxisEntity", "zAxisEntity"):
if key in args:
entity = resolve(design, value, args[key], ("ConstructionAxis", "SketchLine", "BRepEdge"))
require(getattr(entity, "assemblyContext", None) is None and same(owner(entity), comp),
"wrong_component", "Orientation axes must be native geometry in the origin's component.")
require(entity.geometry.objectType.endswith(("::Line3D", "::InfiniteLine3D")), "invalid_axis", "Orientation requires straight linear geometry.")
result[key] = entity
return result
def origin_result(design, origin, fresh, args, expected, geometry, axes, original_value):
result = origin_data(origin, fresh)
verify_parameters(result, expected)
for key in ("name", "flipped"):
require(key not in args or result[key] == args[key], "joint_origin_refused", "Origin did not retain " + key)
# A native origin belongs to a definition, but Fusion can return its geometry
# as a root-context proxy. Match native source and owner, not that incidental
# instance. Regular joints keep the strict occurrence-context check above.
verify_attachment(design, original_value, origin.geometry, geometry, definition=True)
for key, locator in axes.items():
require(same(native(concrete(getattr(origin, key))), native(resolve_source(design, original_value, locator))),
"joint_origin_refused", "Origin axis did not read back.")
return {"jointOrigin": result, "editScope": "component_definition"}
def joint_origin_create(design, value, args):
args = {"flipped": False, **args}
comp = component(design, value, args)
geometry = selector(design, value, args["geometry"], comp)
axes = origin_axes(design, value, args, comp)
saved_geometry = capture_attachment(geometry, value)
saved_axes = {key: source_reference(entity, value) for key, entity in axes.items()}
expected = expected_parameters(design, {"angle": 0, "offsetX": 0, "offsetY": 0, "offsetZ": 0, **args}, ("angle", "offsetX", "offsetY", "offsetZ"))
inp = comp.jointOrigins.createInput(geometry)
require(inp is not None, "joint_origin_failed", "Fusion did not return JointOriginInput.")
inp.isFlipped = args.get("flipped", False)
for name, (quantity_input, _) in expected.items():
setattr(inp, name, quantity_input)
for key, entity in axes.items():
setattr(inp, key, entity)
origin = named(comp.jointOrigins.add(inp), args)
return lambda fresh: origin_result(design, origin, fresh, args, expected, saved_geometry, saved_axes, value)
def joint_origin_edit(design, value, args):
origin = resolve(design, value, args["origin"], ("JointOrigin",))
require(getattr(origin, "assemblyContext", None) is None, "definition_reference_required", "Edit the native origin definition.")
require(set(args) & {"name", "geometry", "flipped", "angle", "offsetX", "offsetY", "offsetZ", "xAxisEntity", "zAxisEntity"}, "invalid_input", "Supply an origin change.")
geometry = selector(design, value, args["geometry"], origin.parentComponent) if "geometry" in args else origin.geometry
axes = origin_axes(design, value, args, origin.parentComponent)
saved_geometry = capture_attachment(geometry, value)
saved_axes = {key: source_reference(entity, value) for key, entity in axes.items()}
expected = expected_parameters(design, args, ("angle", "offsetX", "offsetY", "offsetZ"))
with before_timeline(design, origin):
if "geometry" in args:
origin.geometry = geometry
if "flipped" in args:
origin.isFlipped = args["flipped"]
for key, entity in axes.items():
setattr(origin, key, entity)
for name, (quantity_input, _) in expected.items():
getattr(origin, name).expression = quantity_input.stringValue
if "name" in args:
origin.name = args["name"]
return lambda fresh: origin_result(design, origin, fresh, args, expected, saved_geometry, saved_axes, value)
def joint_origin_delete(design, value, args):
origin = resolve(design, value, args["origin"], ("JointOrigin",))
require(getattr(origin, "assemblyContext", None) is None, "definition_reference_required", "Delete the native origin definition.")
name = origin.name
require(origin.deleteMe(), "delete_failed", "Fusion refused joint-origin deletion.")
return {"deletedJointOrigin": name}
OPERATIONS = {name: globals()[name] for name in ("regular_joint_create", "regular_joint_edit", "joint_origin_create", "joint_origin_edit", "joint_origin_delete")}