From 320ec2e166af2505df76d74891d3a08953affb3a Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Thu, 28 Aug 2025 09:14:23 -0700 Subject: [PATCH 01/10] feat: Add tree-initialize stub --- cadquery/assembly.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index 1aac2cb3d..7c089c749 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -409,9 +409,12 @@ def constrain(self, *args, param=None): return self - def solve(self, verbosity: int = 0) -> "Assembly": + def solve(self, verbosity: int = 0, tree_initialize: bool = False) -> "Assembly": """ Solve the constraints. + + Set `tree_initialize` to true to set the constraints by tree + exploration. """ # Get all entities and number them. First entity is marked as locked @@ -470,6 +473,18 @@ def solve(self, verbosity: int = 0) -> "Assembly": if len(ents) < 2: raise ValueError("At least two entities need to be constrained") + if tree_initialize: + """ + Set the locations + """ + for obj in locked: + print("Locked", obj) + + for ixs, pod in constraints: + (src, dst), t, _ = pod + print(f"{t}[{ixs}], {src.Coord()} <- {dst.Coord()}") + src.SetCoord(*dst.Coord()) + # instantiate the solver scale = self.toCompound().BoundingBox().DiagonalLength solver = ConstraintSolver(locs, constraints, locked=locked, scale=scale) From 1c2a6efbc273d4b36049552f8e7f048aa0904606 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Thu, 28 Aug 2025 10:57:45 -0700 Subject: [PATCH 02/10] feat: Add constraint graph --- cadquery/assembly.py | 47 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index 7c089c749..98e361d53 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -78,6 +78,40 @@ def _define_grammar(): _grammar = _define_grammar() +class ConstraintGraph: + """ + Auxiliary structure for tracking constraint relations + + Each constraint connects two quantities together. This is a undirected + graph. + """ + def __init__(self): + self.adjlist = dict() + # Index of fixed objects + self.locked = set() + # Map from ordered tuples to constraints for fast solving + self.constraints = dict() + + def add_locked(self, i): + self.locked.add(i) + def add_binary(self, i, j, pods): + d = self.adjlist.get(i, set()) + d[j] = pods + self.adjlist[i] = d + + d = self.adjlist.get(j, set()) + d[i] = pods + self.adjlist[j] = d + + def __str__(self): + def format_i(i): + return f"!{i}" if i in self.locked else str(i) + adjlist = [ + f"{format_i(src)} -> {[format_i(j) for j in dst]}" + for src, dst in self.adjlist.items() + ] + return "\n".join(adjlist) + class Assembly(object): """Nested assembly of Workplane and Shape objects defining their relative positions.""" @@ -456,12 +490,19 @@ def solve(self, verbosity: int = 0, tree_initialize: bool = False) -> "Assembly" locs = [self.objects[n].loc for n in ents] + cgraph = ConstraintGraph() + # construct the constraint mapping constraints = [] for c in self.constraints: ixs = tuple(ents[obj] for obj in c.objects) pods = c.toPODs() + if tree_initialize: + if len(ixs) == 2: + i, j = ixs + cgraph.add_binary(i, j, pods) + for pod in pods: constraints.append((ixs, pod)) @@ -477,8 +518,12 @@ def solve(self, verbosity: int = 0, tree_initialize: bool = False) -> "Assembly" """ Set the locations """ + + # Resolve locks for obj in locked: - print("Locked", obj) + cgraph.add_locked(obj) + + print(cgraph) for ixs, pod in constraints: (src, dst), t, _ = pod From 6df67cd4912f0970c9c079ae2cb056c3b89c91fa Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sat, 30 Aug 2025 17:21:20 -0700 Subject: [PATCH 03/10] feat: Constraint graph solving --- cadquery/assembly.py | 157 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 26 deletions(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index 98e361d53..80e9d43ea 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -14,6 +14,7 @@ from typing_extensions import Literal from typish import instance_of from uuid import uuid1 as uuid +from enum import Enum from .cq import Workplane from .occ_impl.shapes import Shape, Compound @@ -78,30 +79,88 @@ def _define_grammar(): _grammar = _define_grammar() + +class _Quantity(Enum): + POINT = 1 + AXIS = 2 + + def __repr__(self): + match self: + case _Quantity.POINT: return "p" + case _Quantity.AXIS: return "a" + + class ConstraintGraph: """ Auxiliary structure for tracking constraint relations Each constraint connects two quantities together. This is a undirected - graph. + graph. Moreover, each node on the graph is a pair (objid, property) """ - def __init__(self): + def __init__(self, n_objs: int): + self.n_objs = n_objs self.adjlist = dict() # Index of fixed objects self.locked = set() # Map from ordered tuples to constraints for fast solving self.constraints = dict() - def add_locked(self, i): - self.locked.add(i) - def add_binary(self, i, j, pods): - d = self.adjlist.get(i, set()) - d[j] = pods - self.adjlist[i] = d + def add_unary(self, i, pods): + """ + Unary constraint + """ + (_, kind, _) = pods + match kind: + case "Fixed": + self.locked.add((i, _Quantity.POINT)) + self.locked.add((i, _Quantity.AXIS)) + case "FixedPoint": + self.locked.add((i, _Quantity.POINT)) + case "FixedAxis": + self.locked.add((i, _Quantity.AXIS)) + case "FixedRotation": + self.locked.add((i, _Quantity.AXIS)) - d = self.adjlist.get(j, set()) - d[i] = pods - self.adjlist[j] = d + def add_binary(self, i, j, pods): + """ + Binary constraint + """ + quantity = [] + ((src, dst), kind, param), = pods + + # Only keep the constraints with 0 degrees of freedom + match kind: + case "Axis": + if param and float(param) not in {0.0, 180.0}: + return + quantity = [_Quantity.AXIS] + case "PointInPlane": + return + case "PointOnLine": + return + case "Point": + if param and float(param) != 0.0: + return + quantity = [_Quantity.POINT] + case "Plane": + if param and float(param) not in {0.0, 180.0}: + return + quantity = [ + _Quantity.POINT, + _Quantity.AXIS, + ] + + for q in quantity: + d = self.adjlist.get((i, q), dict()) + d[(j, q)] = pods + self.adjlist[(j, q)] = d + + d = self.adjlist.get((j, q), dict()) + d[(j, q)] = [ + ((dst, src), kind, param) + for ((src, dst), kind, param) in pods + ] + self.adjlist[(j, q)] = d def __str__(self): def format_i(i): @@ -112,6 +171,57 @@ def format_i(i): ] return "\n".join(adjlist) + @staticmethod + def zero_point_binary_constraint(pods): + print("pods:", pods) + ((src, dst), kind, param), = pods + match kind: + case "Point": + dst.SetCoord(*src.Coord()) + case "Axis": + if param == 0.0: + dst.SetCoord(*src.Coord()) + else: + dst.SetCoord(*(-src).Coord()) + case "Plane": + raise ValueError("not supported yet") + case _: + raise ValueError(f"Unknown binary constraint: {kind}") + + def solve(self, verbosity: int = 0): + remaining = { + (i, key) + for i in range(self.n_objs) + for key in _Quantity + if (i, key) not in self.locked + } + def start_solve(node): + li = [node] + while li: + (i, qty) = li.pop() + if (i, qty) in remaining: + remaining.remove((i, qty)) + # Query the adjlist + for ((j, p), pods) in self.adjlist.get((i, qty), {}).items(): + if (j, p) in remaining: + li.append((j, p)) + else: + continue + + if verbosity > 3: + print(f"Solving {(i, qty)} -> {(j, p)} with {len(pods)} pods") + # Zero the constraint (i,qty) to (j, p) + ConstraintGraph.zero_point_binary_constraint(pods) + # Start solving from each of the locked nodes + if verbosity > 3: + print(f"Start solving from locked: {len(self.locked)}") + for node in self.locked: + start_solve(node) + if verbosity > 3: + print(f"Remaining: {len(self.locked)}") + while remaining: + node = remaining.pop() + start_solve(node) class Assembly(object): """Nested assembly of Workplane and Shape objects defining their relative positions.""" @@ -490,7 +600,7 @@ def solve(self, verbosity: int = 0, tree_initialize: bool = False) -> "Assembly" locs = [self.objects[n].loc for n in ents] - cgraph = ConstraintGraph() + cgraph = ConstraintGraph(len(ents)) # construct the constraint mapping constraints = [] @@ -499,9 +609,12 @@ def solve(self, verbosity: int = 0, tree_initialize: bool = False) -> "Assembly" pods = c.toPODs() if tree_initialize: - if len(ixs) == 2: - i, j = ixs - cgraph.add_binary(i, j, pods) + match len(ixs): + case 1: + cgraph.add_unary(ixs[0], pods) + case 2: + i, j = ixs + cgraph.add_binary(i, j, pods) for pod in pods: constraints.append((ixs, pod)) @@ -518,17 +631,9 @@ def solve(self, verbosity: int = 0, tree_initialize: bool = False) -> "Assembly" """ Set the locations """ - - # Resolve locks - for obj in locked: - cgraph.add_locked(obj) - - print(cgraph) - - for ixs, pod in constraints: - (src, dst), t, _ = pod - print(f"{t}[{ixs}], {src.Coord()} <- {dst.Coord()}") - src.SetCoord(*dst.Coord()) + if verbosity > 3: + print(cgraph) + cgraph.solve(verbosity) # instantiate the solver scale = self.toCompound().BoundingBox().DiagonalLength From a27744eeceda036fa3b7526430facd224438c70a Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sat, 30 Aug 2025 21:51:22 -0700 Subject: [PATCH 04/10] fix: Break compound constraints --- cadquery/assembly.py | 138 +++++++++++++++++++------------------------ 1 file changed, 62 insertions(+), 76 deletions(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index 80e9d43ea..47712a9d8 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -85,17 +85,17 @@ class _Quantity(Enum): AXIS = 2 def __repr__(self): - match self: - case _Quantity.POINT: return "p" - case _Quantity.AXIS: return "a" - + if self == _Quantity.POINT: + return "p" + else: + return "a" class ConstraintGraph: """ Auxiliary structure for tracking constraint relations Each constraint connects two quantities together. This is a undirected - graph. Moreover, each node on the graph is a pair (objid, property) + graph. Moreover, each node on the graph is a pair (object id, property) """ def __init__(self, n_objs: int): self.n_objs = n_objs @@ -105,62 +105,52 @@ def __init__(self, n_objs: int): # Map from ordered tuples to constraints for fast solving self.constraints = dict() - def add_unary(self, i, pods): + def add_unary(self, i, pod): """ Unary constraint """ - (_, kind, _) = pods - match kind: - case "Fixed": - self.locked.add((i, _Quantity.POINT)) - self.locked.add((i, _Quantity.AXIS)) - case "FixedPoint": - self.locked.add((i, _Quantity.POINT)) - case "FixedAxis": - self.locked.add((i, _Quantity.AXIS)) - case "FixedRotation": - self.locked.add((i, _Quantity.AXIS)) + src, kind, param = pod + if kind == "Fixed": + self.locked.add((i, _Quantity.POINT)) + self.locked.add((i, _Quantity.AXIS)) + elif kind == "FixedPoint": + src.setCoord(*param) + self.locked.add((i, _Quantity.POINT)) + elif kind == "FixedAxis": + src.setCoord(*param) + self.locked.add((i, _Quantity.AXIS)) - def add_binary(self, i, j, pods): + def add_binary(self, i, j, pod): """ Binary constraint """ - quantity = [] - ((src, dst), kind, param), = pods + (src, dst), kind, param = pod + q = None # Only keep the constraints with 0 degrees of freedom - match kind: - case "Axis": - if param and float(param) not in {0.0, 180.0}: - return - quantity = [_Quantity.AXIS] - case "PointInPlane": + assert kind != "Plane", "Compound constraints should not exist here" + if kind in {"PointInPlane", "PointOnLine"}: + return + elif kind == "Axis": + if param and float(param) not in {0.0, 180.0}: return - case "PointOnLine": + q = _Quantity.AXIS + elif kind == "Point": + if param and param != 0.0: return - case "Point": - if param and float(param) != 0.0: - return - quantity = [_Quantity.POINT] - case "Plane": - if param and float(param) not in {0.0, 180.0}: - return - quantity = [ - _Quantity.POINT, - _Quantity.AXIS, - ] - - for q in quantity: - d = self.adjlist.get((i, q), dict()) - d[(j, q)] = pods - self.adjlist[(j, q)] = d - - d = self.adjlist.get((j, q), dict()) - d[(j, q)] = [ - ((dst, src), kind, param) - for ((src, dst), kind, param) in pods - ] - self.adjlist[(j, q)] = d + q = _Quantity.POINT + else: + raise ValueError(f"Unknown kind {kind}") + + # Insert into adjacency lists + + d = self.adjlist.get((i, q), dict()) + d[(j, q)] = pod + self.adjlist[(j, q)] = d + + d = self.adjlist.get((j, q), dict()) + d[(j, q)] = ((dst, src), kind, param) + self.adjlist[(j, q)] = d def __str__(self): def format_i(i): @@ -172,21 +162,17 @@ def format_i(i): return "\n".join(adjlist) @staticmethod - def zero_point_binary_constraint(pods): - print("pods:", pods) - ((src, dst), kind, param), = pods - match kind: - case "Point": + def zero_point_binary_constraint(pod): + (src, dst), kind, param = pod + if kind == "Axis": + if param and float(param) == 180.0: + dst.SetCoord(*(-src).Coord()) + else: dst.SetCoord(*src.Coord()) - case "Axis": - if param == 0.0: - dst.SetCoord(*src.Coord()) - else: - dst.SetCoord(*(-src).Coord()) - case "Plane": - raise ValueError("not supported yet") - case _: - raise ValueError(f"Unknown binary constraint: {kind}") + elif kind == "Point": + dst.SetCoord(*src.Coord()) + else: + raise ValueError(f"Illegal binary constraint {kind}") def solve(self, verbosity: int = 0): remaining = { @@ -202,16 +188,16 @@ def start_solve(node): if (i, qty) in remaining: remaining.remove((i, qty)) # Query the adjlist - for ((j, p), pods) in self.adjlist.get((i, qty), {}).items(): + for ((j, p), pod) in self.adjlist.get((i, qty), {}).items(): if (j, p) in remaining: li.append((j, p)) else: continue if verbosity > 3: - print(f"Solving {(i, qty)} -> {(j, p)} with {len(pods)} pods") + print(f"Solving {(i, qty)} -> {(j, p)} with kind {pod[1]}") # Zero the constraint (i,qty) to (j, p) - ConstraintGraph.zero_point_binary_constraint(pods) + ConstraintGraph.zero_point_binary_constraint(pod) # Start solving from each of the locked nodes if verbosity > 3: print(f"Start solving from locked: {len(self.locked)}") @@ -223,6 +209,7 @@ def start_solve(node): node = remaining.pop() start_solve(node) + class Assembly(object): """Nested assembly of Workplane and Shape objects defining their relative positions.""" @@ -553,7 +540,7 @@ def constrain(self, *args, param=None): return self - def solve(self, verbosity: int = 0, tree_initialize: bool = False) -> "Assembly": + def solve(self, verbosity: int = 0, tree_initialize: bool = True) -> "Assembly": """ Solve the constraints. @@ -608,17 +595,16 @@ def solve(self, verbosity: int = 0, tree_initialize: bool = False) -> "Assembly" ixs = tuple(ents[obj] for obj in c.objects) pods = c.toPODs() - if tree_initialize: - match len(ixs): - case 1: - cgraph.add_unary(ixs[0], pods) - case 2: - i, j = ixs - cgraph.add_binary(i, j, pods) - for pod in pods: constraints.append((ixs, pod)) + if tree_initialize: + if len(ixs) == 1: + cgraph.add_unary(ixs[0], pod) + elif len(ixs) == 2: + i, j = ixs + cgraph.add_binary(i, j, pod) + # check if any constraints were specified if not constraints: raise ValueError("At least one constraint required") From 0839a4c184448bae0fe7dc1c8ec2fec8f8865158 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sat, 30 Aug 2025 21:59:13 -0700 Subject: [PATCH 05/10] fix: Skip islands --- cadquery/assembly.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index 47712a9d8..be7232ee7 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -177,8 +177,7 @@ def zero_point_binary_constraint(pod): def solve(self, verbosity: int = 0): remaining = { (i, key) - for i in range(self.n_objs) - for key in _Quantity + for i, key in self.adjlist if (i, key) not in self.locked } def start_solve(node): @@ -204,7 +203,7 @@ def start_solve(node): for node in self.locked: start_solve(node) if verbosity > 3: - print(f"Remaining: {len(self.locked)}") + print(f"Remaining points: {len(remaining)}") while remaining: node = remaining.pop() start_solve(node) From 222fe4550dfd32e8afd4b58b57bad9d127a4ff9f Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sat, 30 Aug 2025 22:12:21 -0700 Subject: [PATCH 06/10] fix: Object sharing --- cadquery/assembly.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index be7232ee7..8cb3285f1 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -15,6 +15,7 @@ from typish import instance_of from uuid import uuid1 as uuid from enum import Enum +from collections import defaultdict from .cq import Workplane from .occ_impl.shapes import Shape, Compound @@ -99,7 +100,7 @@ class ConstraintGraph: """ def __init__(self, n_objs: int): self.n_objs = n_objs - self.adjlist = dict() + self.adjlist = defaultdict(lambda: defaultdict(dict)) # Index of fixed objects self.locked = set() # Map from ordered tuples to constraints for fast solving @@ -143,14 +144,8 @@ def add_binary(self, i, j, pod): raise ValueError(f"Unknown kind {kind}") # Insert into adjacency lists - - d = self.adjlist.get((i, q), dict()) - d[(j, q)] = pod - self.adjlist[(j, q)] = d - - d = self.adjlist.get((j, q), dict()) - d[(j, q)] = ((dst, src), kind, param) - self.adjlist[(j, q)] = d + self.adjlist[(i, q)][(j, q)] = pod + self.adjlist[(j, q)][(i, q)] = ((dst, src), kind, param) def __str__(self): def format_i(i): From 90b948367767add118a8c9ff12ab7e3ff34a16c4 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sat, 30 Aug 2025 22:18:13 -0700 Subject: [PATCH 07/10] chore: Format code --- cadquery/assembly.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index 8cb3285f1..d4d658198 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -46,6 +46,7 @@ PATH_DELIM = "/" + # entity selector grammar definition def _define_grammar(): @@ -91,6 +92,7 @@ def __repr__(self): else: return "a" + class ConstraintGraph: """ Auxiliary structure for tracking constraint relations @@ -98,6 +100,7 @@ class ConstraintGraph: Each constraint connects two quantities together. This is a undirected graph. Moreover, each node on the graph is a pair (object id, property) """ + def __init__(self, n_objs: int): self.n_objs = n_objs self.adjlist = defaultdict(lambda: defaultdict(dict)) @@ -150,6 +153,7 @@ def add_binary(self, i, j, pod): def __str__(self): def format_i(i): return f"!{i}" if i in self.locked else str(i) + adjlist = [ f"{format_i(src)} -> {[format_i(j) for j in dst]}" for src, dst in self.adjlist.items() @@ -170,11 +174,8 @@ def zero_point_binary_constraint(pod): raise ValueError(f"Illegal binary constraint {kind}") def solve(self, verbosity: int = 0): - remaining = { - (i, key) - for i, key in self.adjlist - if (i, key) not in self.locked - } + remaining = {(i, key) for i, key in self.adjlist if (i, key) not in self.locked} + def start_solve(node): li = [node] while li: @@ -182,7 +183,7 @@ def start_solve(node): if (i, qty) in remaining: remaining.remove((i, qty)) # Query the adjlist - for ((j, p), pod) in self.adjlist.get((i, qty), {}).items(): + for (j, p), pod in self.adjlist.get((i, qty), {}).items(): if (j, p) in remaining: li.append((j, p)) else: @@ -192,6 +193,7 @@ def start_solve(node): print(f"Solving {(i, qty)} -> {(j, p)} with kind {pod[1]}") # Zero the constraint (i,qty) to (j, p) ConstraintGraph.zero_point_binary_constraint(pod) + # Start solving from each of the locked nodes if verbosity > 3: print(f"Start solving from locked: {len(self.locked)}") @@ -467,12 +469,12 @@ def _subloc(self, name: str) -> Tuple[Location, str]: @overload def constrain( self, q1: str, q2: str, kind: ConstraintKind, param: Any = None - ) -> "Assembly": - ... + ) -> "Assembly": ... @overload - def constrain(self, q1: str, kind: ConstraintKind, param: Any = None) -> "Assembly": - ... + def constrain( + self, q1: str, kind: ConstraintKind, param: Any = None + ) -> "Assembly": ... @overload def constrain( @@ -483,14 +485,16 @@ def constrain( s2: Shape, kind: ConstraintKind, param: Any = None, - ) -> "Assembly": - ... + ) -> "Assembly": ... @overload def constrain( - self, id1: str, s1: Shape, kind: ConstraintKind, param: Any = None, - ) -> "Assembly": - ... + self, + id1: str, + s1: Shape, + kind: ConstraintKind, + param: Any = None, + ) -> "Assembly": ... def constrain(self, *args, param=None): """ @@ -813,8 +817,12 @@ def __iter__( color = self.color if self.color else color if self.obj: - yield self.obj if isinstance(self.obj, Shape) else Compound.makeCompound( - s for s in self.obj.vals() if isinstance(s, Shape) + yield ( + self.obj + if isinstance(self.obj, Shape) + else Compound.makeCompound( + s for s in self.obj.vals() if isinstance(s, Shape) + ) ), name, loc, color for ch in self.children: From 0631676c87fa680adb08ee310dcc67f3bd5748ca Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sat, 30 Aug 2025 22:21:23 -0700 Subject: [PATCH 08/10] doc: Update documentation --- cadquery/assembly.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index d4d658198..151122bc2 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -95,10 +95,11 @@ def __repr__(self): class ConstraintGraph: """ - Auxiliary structure for tracking constraint relations + Auxiliary structure for tracking constraint relations. - Each constraint connects two quantities together. This is a undirected - graph. Moreover, each node on the graph is a pair (object id, property) + We store an undirected graph of (object id, quantity) nodes. Every binary + constraint with 0 degrees of freedom links two property nodes. We then + traverse through the graph to solve each constraint to its local minimum. """ def __init__(self, n_objs: int): From cc47296bd7c50002c30d8489a3d45cb8b94be0af Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sat, 30 Aug 2025 22:34:52 -0700 Subject: [PATCH 09/10] fix: Handle tuples for Fixed{Point,Axis} --- cadquery/assembly.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index 151122bc2..8ec192813 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -114,14 +114,16 @@ def add_unary(self, i, pod): """ Unary constraint """ - src, kind, param = pod + srcs, kind, param = pod if kind == "Fixed": self.locked.add((i, _Quantity.POINT)) self.locked.add((i, _Quantity.AXIS)) elif kind == "FixedPoint": + src, = srcs src.setCoord(*param) self.locked.add((i, _Quantity.POINT)) elif kind == "FixedAxis": + src, = srcs src.setCoord(*param) self.locked.add((i, _Quantity.AXIS)) From 49ce861247e03a2bff1776f0f3b4638cfd75d23e Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sat, 30 Aug 2025 22:57:26 -0700 Subject: [PATCH 10/10] chore: Format code --- cadquery/assembly.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index 8ec192813..1c960c11e 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -119,12 +119,12 @@ def add_unary(self, i, pod): self.locked.add((i, _Quantity.POINT)) self.locked.add((i, _Quantity.AXIS)) elif kind == "FixedPoint": - src, = srcs - src.setCoord(*param) + (src,) = srcs + src.SetCoord(*param) self.locked.add((i, _Quantity.POINT)) elif kind == "FixedAxis": - src, = srcs - src.setCoord(*param) + (src,) = srcs + src.SetCoord(*param) self.locked.add((i, _Quantity.AXIS)) def add_binary(self, i, j, pod): @@ -472,12 +472,14 @@ def _subloc(self, name: str) -> Tuple[Location, str]: @overload def constrain( self, q1: str, q2: str, kind: ConstraintKind, param: Any = None - ) -> "Assembly": ... + ) -> "Assembly": + ... @overload def constrain( self, q1: str, kind: ConstraintKind, param: Any = None - ) -> "Assembly": ... + ) -> "Assembly": + ... @overload def constrain( @@ -488,7 +490,8 @@ def constrain( s2: Shape, kind: ConstraintKind, param: Any = None, - ) -> "Assembly": ... + ) -> "Assembly": + ... @overload def constrain( @@ -497,7 +500,8 @@ def constrain( s1: Shape, kind: ConstraintKind, param: Any = None, - ) -> "Assembly": ... + ) -> "Assembly": + ... def constrain(self, *args, param=None): """