Skip to content

Commit c97197d

Browse files
committed
Fixes #162; We had kept track of Bill of Materials (BOM) metadata in a global variable in the solid.utils module. Instead, it's now stored on objects themselves.
1 parent 98e741e commit c97197d

File tree

3 files changed

+51
-18
lines changed

3 files changed

+51
-18
lines changed

pyproject.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "solidpython"
3-
version = "1.0.4"
3+
version = "1.0.5"
44
description = "Python interface to the OpenSCAD declarative geometry language"
55
authors = ["Evan Jones <evan_t_jones@mac.com>"]
66
homepage = "https://github.com/SolidCode/SolidPython"
@@ -42,7 +42,18 @@ regex = "^2019.4"
4242
[tool.poetry.dev-dependencies]
4343
tox = "^tox 3.11"
4444

45+
4546
[build-system]
46-
requires = ["poetry>=0.12"]
47+
requires = [
48+
"poetry>=0.12",
49+
# See https://github.com/pypa/setuptools/issues/2353#issuecomment-683781498
50+
# for the rest of these requirements,
51+
# -ETJ 31 December 2020
52+
"setuptools>=30.3.0,<50",
53+
"wheel",
54+
"pytest-runner",
55+
"setuptools_scm>=3.3.1",
56+
]
57+
4758
build-backend = "poetry.masonry.api"
4859

solid/examples/bom_scad.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,28 +93,29 @@ def doohickey(c):
9393

9494

9595
def assembly():
96+
nut = m3_nut()
9697
return union()(
9798
doohickey(c='blue'),
9899
translate((-10, 0, doohickey_h / 2))(m3_12()),
99100
translate((0, 0, doohickey_h / 2))(m3_16()),
100101
translate((10, 0, doohickey_h / 2))(m3_12()),
101102
# Nuts
102-
translate((-10, 0, -nut_height - doohickey_h / 2))(m3_nut()),
103-
translate((0, 0, -nut_height - doohickey_h / 2))(m3_nut()),
104-
translate((10, 0, -nut_height - doohickey_h / 2))(m3_nut()),
103+
translate((-10, 0, -nut_height - doohickey_h / 2))(nut),
104+
translate((0, 0, -nut_height - doohickey_h / 2))(nut),
105+
translate((10, 0, -nut_height - doohickey_h / 2))(nut),
105106
)
106107

107108

108109
if __name__ == '__main__':
109110
out_dir = sys.argv[1] if len(sys.argv) > 1 else None
110111

111112
a = assembly()
112-
bom = bill_of_materials()
113+
bom = bill_of_materials(a)
113114
file_out = scad_render_to_file(a, out_dir=out_dir)
114115

115116
print(f"{__file__}: SCAD file written to: \n{file_out}")
116117
print(bom)
117118

118119
print("Or, Spreadsheet-ready TSV:\n\n")
119-
bom = bill_of_materials(csv=True)
120+
bom = bill_of_materials(a, csv=True)
120121
print(bom)

solid/utils.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ def section_cut_xz(body: OpenSCADObject, y_cut_point:float=0) -> OpenSCADObject:
483483
# =====================
484484
# Any part defined in a method can be automatically counted using the
485485
# `@bom_part()` decorator. After all parts have been created, call
486-
# `bill_of_materials()`
486+
# `bill_of_materials(<SCAD_OBJ>)`
487487
# to generate a report. See `examples/bom_scad.py` for usage
488488
#
489489
# Additional columns can be added (such as leftover material or URL to part)
@@ -493,7 +493,6 @@ def section_cut_xz(body: OpenSCADObject, y_cut_point:float=0) -> OpenSCADObject:
493493
# populate the new columns in order of their addition via bom_headers, or
494494
# keyworded arguments can be used in any order.
495495

496-
g_parts_dict = {}
497496
g_bom_headers: List[str] = []
498497

499498
def set_bom_headers(*args):
@@ -504,31 +503,53 @@ def bom_part(description: str='', per_unit_price:float=None, currency: str='US$'
504503
def wrap(f):
505504
name = description if description else f.__name__
506505

507-
elements = {}
508-
elements.update({'Count':0, 'currency':currency, 'Unit Price':per_unit_price})
506+
elements = {'name': name, 'Count':0, 'currency':currency, 'Unit Price':per_unit_price}
509507
# This update also adds empty key value pairs to prevent key exceptions.
510508
elements.update(dict(zip_longest(g_bom_headers, args, fillvalue='')))
511509
elements.update(kwargs)
512510

513-
g_parts_dict[name] = elements
514-
515511
def wrapped_f(*wargs, **wkwargs):
516-
name = description if description else f.__name__
517-
g_parts_dict[name]['Count'] += 1
518-
return f(*wargs, **wkwargs)
512+
scad_obj = f(*wargs, **wkwargs)
513+
scad_obj.add_trait('BOM', elements)
514+
return scad_obj
519515

520516
return wrapped_f
521517

522518
return wrap
523519

524-
def bill_of_materials(csv:bool=False) -> str:
520+
def bill_of_materials(root_obj:OpenSCADObject, csv:bool=False) -> str:
521+
traits_dicts = _traits_bom_dicts(root_obj)
522+
# Build a single dictionary from the ones stored on each child object
523+
# (This is an adaptation of an earlier version, and probably not the most
524+
# direct way to accomplish this)
525+
all_bom_traits = {}
526+
for traits_dict in traits_dicts:
527+
name = traits_dict['name']
528+
if name in all_bom_traits:
529+
all_bom_traits[name]['Count'] += 1
530+
else:
531+
all_bom_traits[name] = traits_dict
532+
all_bom_traits[name]['Count'] = 1
533+
bom = _make_bom(all_bom_traits, csv)
534+
return bom
535+
536+
def _traits_bom_dicts(root_obj:OpenSCADObject) -> List[Dict[str, float]]:
537+
all_child_traits = [_traits_bom_dicts(c) for c in root_obj.children]
538+
child_traits = [item for subl in all_child_traits for item in subl if item]
539+
bom_trait = root_obj.get_trait('BOM')
540+
if bom_trait:
541+
child_traits.append(bom_trait)
542+
return child_traits
543+
544+
def _make_bom(bom_parts_dict: Dict[str, float], csv:bool=False, ) -> str:
525545
field_names = ["Description", "Count", "Unit Price", "Total Price"]
526546
field_names += g_bom_headers
527547

528548
rows = []
529549

530550
all_costs: Dict[str, float] = {}
531-
for desc, elements in g_parts_dict.items():
551+
for desc, elements in bom_parts_dict.items():
552+
row = []
532553
count = elements['Count']
533554
currency = elements['currency']
534555
price = elements['Unit Price']

0 commit comments

Comments
 (0)