From 6482cf95d38e013686856a1a1831701785ccd061 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Fri, 6 Nov 2026 18:41:16 +0100 Subject: [PATCH 1/2] move/moved undo and solid/shell history tracking --- cadquery/occ_impl/shapes.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 40dd9dccf..ec0094094 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -1403,6 +1403,24 @@ def moved(self: T, loc: Sequence[VectorLike]) -> Shape: return self.moved(tuple(Location(l) for l in loc)) + def undoMoved(self: T) -> T: + """ + Undo (exactly) last moved operation. + """ + + return self.__class__( + self.wrapped.Located(self.wrapped.Location().NextLocation()) + ) + + def undoMove(self: T) -> T: + """ + Undo (exactly) last move operation. + """ + + self.wrapped.Location(self.wrapped.Location().NextLocation()) + + return self + def __hash__(self) -> int: return self.hashCode() @@ -5943,6 +5961,8 @@ def _update_history( # track all subshapes for shape in shapes: + op._tracked.update(shape.Solids()) + op._tracked.update(shape.Shells()) op._tracked.update(shape.Faces()) op._tracked.update(shape.Edges()) op._tracked.update(shape.Vertices()) From add645cd2ca82c9f907320053fa6c4a9ab465745 Mon Sep 17 00:00:00 2001 From: adam-urbanczyk <13981538+adam-urbanczyk@users.noreply.github.com> Date: Fri, 6 Nov 2026 18:44:31 +0100 Subject: [PATCH 2/2] Initial impl of Assembly.imprint --- cadquery/assembly.py | 55 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/cadquery/assembly.py b/cadquery/assembly.py index fdf98dd73..8229e5538 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -17,7 +17,15 @@ from itertools import chain from .cq import Workplane -from .occ_impl.shapes import Shape, Compound, isSubshape, compound +from .occ_impl.shapes import ( + Shape, + Compound, + isSubshape, + compound, + GlueLiteral, + imprint, + History, +) from .occ_impl.geom import Location from .occ_impl.assembly import Color, Material from .occ_impl.solver import ( @@ -718,7 +726,11 @@ def __iter__( Assembly iterator yielding shapes, names, locations and colors. """ - name = f"{name}/{self.name}" if name else self.name + if self.parent is None or self.parent.parent is None: + name = self.name + else: + name = f"{name}/{self.name}" + loc = loc * self.loc if loc else self.loc color = self.color if self.color else color @@ -859,3 +871,42 @@ def __setstate__(self, d): """ self.__dict__ = d + + def imprint(self, tol: float = 0.0, glue: GlueLiteral = "partial") -> Self: + """ + Imprint assembly in-place. + + NB: this function will tranform all non-Shape obj (e.g. Workplane) fields into + Shape obj fields. + """ + + hist = History() + + objs = [] + names = [] + locs = [] + + for obj, name, loc, _ in self: + objs.append(obj.moved(loc)) + names.append(name) + locs.append(loc) + + result = imprint(*objs, tol=tol, glue=glue, history=hist) + op = hist[-1] + + for obj, name, loc in zip(objs, names, locs): + obj_imp = op.modified(obj) + + subassy = self.objects[name] + + subassy.obj = obj_imp.undoMoved() + + for k in subassy._subshape_names: + k_moved = k.moved(loc) + k_imp = op.modified(k_moved) + + subassy._subshape_names[ + k_imp.undoMoved() + ] = subassy._subshape_names.pop(k) + + return self