diff --git a/cadquery/assembly.py b/cadquery/assembly.py index fdf98dd73..630bac69c 100644 --- a/cadquery/assembly.py +++ b/cadquery/assembly.py @@ -627,6 +627,33 @@ def export( return self + def toBOM( + self, indent: int = 0, _result: Optional[List[Dict[str, Any]]] = None, + ) -> List[Dict[str, Any]]: + """ + Generate a Bill of Materials (BOM) for this assembly. + + Returns a flat list of dictionaries, one per component, with fields: + - name: component name + - level: nesting depth (0 = root) + - has_shape: whether the component has geometry + + :param indent: current nesting level (used internally for recursion) + :return: list of BOM line items + """ + + if _result is None: + _result = [] + + _result.append( + {"name": self.name, "level": indent, "has_shape": self.obj is not None,} + ) + + for child in self.children: + child.toBOM(indent=indent + 1, _result=_result) + + return _result + @classmethod def importStep(cls, path: str, unit: UnitLiterals = "MM") -> Self: """ diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 7d3c665a9..a0dbd74f7 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -6553,14 +6553,23 @@ def plane() -> Face: return _shape(BRepBuilderAPI_MakeFace(pln_geom, -INF, INF, -INF, INF).Face(), Face) -def box(w: float, l: float, h: float) -> Solid: +def box(length: float, width: float, height: float) -> Solid: """ - Construct a solid box. + Construct a solid box centered on the XY plane. + + :param length: box size along the X axis + :param width: box size along the Y axis + :param height: box size along the Z axis """ return _shape( BRepPrimAPI_MakeBox( - gp_Ax2(Vector(-w / 2, -l / 2, 0).toPnt(), Vector(0, 0, 1).toDir()), w, l, h + gp_Ax2( + Vector(-length / 2, -width / 2, 0).toPnt(), Vector(0, 0, 1).toDir(), + ), + length, + width, + height, ).Shape(), Solid, ) diff --git a/tests/test_assembly.py b/tests/test_assembly.py index 08d4a5f21..72fd524d9 100644 --- a/tests/test_assembly.py +++ b/tests/test_assembly.py @@ -2603,3 +2603,21 @@ def test_name_geometries(tmpdir): assert len([l for l in lines if "top_face" in l]) == 2 assert len([l for l in lines if "plane_" in l]) == 2 assert len([l for l in lines if "seg_" in l]) == 3 + + +def test_toBOM(): + + assy = cq.Assembly(name="root") + assy.add(box(1, 1, 1), name="part1") + + subassy = cq.Assembly(name="sub") + subassy.add(box(2, 2, 2), name="part2") + assy.add(subassy, name="sub") + + bom = assy.toBOM() + + assert len(bom) == 4 + assert bom[0] == {"name": "root", "level": 0, "has_shape": False} + assert bom[1] == {"name": "part1", "level": 1, "has_shape": True} + assert bom[2] == {"name": "sub", "level": 1, "has_shape": False} + assert bom[3] == {"name": "part2", "level": 2, "has_shape": True}