diff --git a/docs/usage.md b/docs/usage.md index 010f688..13dafe3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -172,7 +172,7 @@ This is achieved by having a pair of modules: - //! Place inserts in the bottom of the posts and push them home with a soldering iron with a conical bit heated to 200°C. // module handle_assembly() pose([225, 0, 150], [0, 0, 14]) //! Printed part with inserts in place - assembly("handle") { + assembly("handle", ngb = true) { translate_z(handle_height()) stl_colour(pp1_colour) vflip() handle_stl(); @@ -201,6 +201,9 @@ When the parent assembly is shown exploded the handle's screws will be exploded Note also the `pose([225, 0, 150], [0, 0, 14])` call before the `assembly()` call. This allows the sub-assembly to be posed differently in its build step but doesn't affect its orientation in the parent assembly. The pose parameters are the rotation and the translation taken from the GUI. +Setting `ngb = true` in the `assembly()` prevents the handle assembly appearing as a columun in the top level BOM in the build instructions. +Instead its parts are merged into the parent BOM so the correct quantites are listed. + ### Exploded diagrams A lot of vitamins explode themselves when `$explode=1`. This is done with module `explode()` that can be passed a Z offset, or a 3D vector that gives the displacement diff --git a/examples/MainsBreakOutBox/bom/bom.json b/examples/MainsBreakOutBox/bom/bom.json index 8c311a8..687392e 100644 --- a/examples/MainsBreakOutBox/bom/bom.json +++ b/examples/MainsBreakOutBox/bom/bom.json @@ -2,6 +2,7 @@ { "name": "base_assembly", "big": null, + "ngb": false, "count": 1, "assemblies": {}, "vitamins": { @@ -20,6 +21,7 @@ { "name": "feet_assembly", "big": null, + "ngb": false, "count": 1, "assemblies": { "base_assembly": 1 @@ -46,6 +48,7 @@ { "name": "mains_in_assembly", "big": null, + "ngb": false, "count": 1, "assemblies": { "feet_assembly": 1 @@ -82,6 +85,7 @@ { "name": "main_assembly", "big": null, + "ngb": false, "count": 1, "assemblies": { "mains_in_assembly": 1 diff --git a/readme.md b/readme.md index f1e750f..c06bf90 100644 --- a/readme.md +++ b/readme.md @@ -3809,7 +3809,7 @@ Veroboard with mounting holes, track breaks, removed tracks, solder points and c | `vero_mounting_hole_positions(type)` | Positions children at the mounting holes | | `vero_mounting_holes(type, h = 100)` | Drill mounting holes in a panel | | `veroboard(type)` | Draw specified veroboard with missing tracks and track breaks | -| `veroboard_assembly(type, height, thickness, flip = false)` | Draw the assembly with components and fasteners in place | +| `veroboard_assembly(type, height, thickness, flip = false, ngb = false)` | Draw the assembly with components and fasteners in place | ![veroboard](tests/png/veroboard.png) @@ -6003,6 +6003,9 @@ Assembly views shown in the instructions can be large or small and this is deduc parts are used. This heuristic isn't always correct, so the default can be overridden by setting the `big` parameter of `assembly` to `true` or `false`. +Setting the `ngb` parameter of `assembly` to `true` removes its column from the global BOM and merges it parts into its parent assembly column of the global BOM. +This is to prevent the global BOM page becoming too wide in large projects by having it include just the major assemblies. + The example below shows how to define a vitamin and incorporate it into an assembly with sub-assemblies and make an exploded view. The resulting flat BOM is shown but heirachical BOMs are also generated for real projects. @@ -6022,7 +6025,7 @@ The resulting flat BOM is shown but heirachical BOMs are also generated for real ### Modules | Module | Description | |:--- |:--- | -| `assembly(name, big = undef)` | Name an assembly that will appear on the BOM, there needs to a module named `_assembly` to make it. `big` can force big or small assembly diagrams. | +| `assembly(name, big = undef, ngb = false)` | Name an assembly that will appear on the BOM, there needs to a module named `_assembly` to make it. `big` can force big or small assembly diagrams. | | `dxf(name)` | Name a dxf that will appear on the BOM, there needs to a module named `_dxf` to make it | | `explode(d, explode_children = false, offset = [0,0,0])` | Explode children by specified Z distance or vector `d`, option to explode grand children | | `hidden()` | Make item invisible, except on the BOM | diff --git a/scripts/bom.py b/scripts/bom.py index 5a384a5..db5ff23 100755 --- a/scripts/bom.py +++ b/scripts/bom.py @@ -60,6 +60,7 @@ class BOM: def __init__(self, name): self.name = name self.big = None + self.ngb = False self.count = 1 self.vitamins = {} self.printed = {} @@ -73,6 +74,7 @@ class BOM: return { "name" : self.name, "big" : self.big, + "ngb" : self.ngb, "count" : self.count, "assemblies" : assemblies, "vitamins" : {v : self.vitamins[v].data() for v in self.vitamins}, diff --git a/scripts/views.py b/scripts/views.py index 18c0814..926c28f 100755 --- a/scripts/views.py +++ b/scripts/views.py @@ -36,6 +36,7 @@ import blurb import bom import shutil import re +import copy from colorama import Fore def is_assembly(s): @@ -101,6 +102,27 @@ def usage(): print("\nusage:\n\t views [target_config] [_assembly] ... [_assembly] - Create assembly images and readme.") sys.exit(1) +types = ["vitamins", "printed", "routed"] + +def merged(bom): + bom = copy.deepcopy(bom) + for aname in bom["assemblies"]: + count = bom["assemblies"][aname] + for ass in flat_bom: + if ass['name'] == aname and ass['ngb']: + merged_assembly = merged(ass) + total = ass['count'] + for t in types: + for thing in merged_assembly[t]: + items = merged_assembly[t][thing]['count'] * count // total + if thing in bom[t]: + bom[t][thing]['count'] += items + else: + bom[t][thing] = merged_assembly[t][thing] + bom[t][thing]['count'] = items + break + return bom + def views(target, do_assemblies = None): done_assemblies = [] # @@ -242,8 +264,8 @@ def views(target, do_assemblies = None): # # Global BOM # + global_bom = [merged(ass) for ass in flat_bom if not ass['ngb']] print('\n## Parts list', file = doc_file) - types = ["vitamins", "printed", "routed"] headings = {"vitamins" : "vitamins", "printed" : "3D printed parts", "routed" : "CNC routed parts"} things = {} for t in types: @@ -255,19 +277,22 @@ def views(target, do_assemblies = None): things[t][thing] += ass[t][thing]["count"] else: things[t][thing] = ass[t][thing]["count"] - for ass in flat_bom: + for ass in global_bom: name = titalise(ass["name"][:-9]).replace(' ',' ') + if ass["count"] > 1: + name = "%d x %s" % (ass["count"], name) print('| %s ' % name, file = doc_file, end = '') print('| TOTALS | |', file = doc_file) - print(('|---:' * len(flat_bom) + '|---:|:---|'), file = doc_file) + print(('|---:' * len(global_bom) + '|---:|:---|'), file = doc_file) for t in types: if things[t]: totals = {} - heading = headings[t][0:1].upper() + headings[t][1:] - print(('| ' * len(flat_bom) + '| | **%s** |') % heading, file = doc_file) + grand_total2 = 0 + heading = headings[t][0].upper() + headings[t][1:] + print(('| ' * len(global_bom) + '| | **%s** |') % heading, file = doc_file) for thing in sorted(things[t], key = lambda s: s.split(":")[-1]): - for ass in flat_bom: + for ass in global_bom: count = ass[t][thing]["count"] if thing in ass[t] else 0 print('| %s ' % pad(count if count else '.', 2, 1), file = doc_file, end = '') name = ass["name"] @@ -275,15 +300,17 @@ def views(target, do_assemblies = None): totals[name] += count else: totals[name] = count + grand_total2 += count print('| %s | %s |' % (pad(things[t][thing], 2, 1), pad(thing.split(":")[-1], 2)), file = doc_file) grand_total = 0 - for ass in flat_bom: + for ass in global_bom: name = ass["name"] total = totals[name] if name in totals else 0 print('| %s ' % pad(total if total else '.', 2, 1), file = doc_file, end = '') grand_total += total print("| %s | %s |" % (pad(grand_total, 2, 1), pad('Total %s count' % headings[t], 2)), file = doc_file) + assert grand_total == grand_total2 print(file = doc_file) if len(blurbs) > 2: print(blurbs[2], file = doc_file) diff --git a/utils/core/bom.scad b/utils/core/bom.scad index 203f415..66b5f5f 100644 --- a/utils/core/bom.scad +++ b/utils/core/bom.scad @@ -25,6 +25,9 @@ //! parts are used. //! This heuristic isn't always correct, so the default can be overridden by setting the `big` parameter of `assembly` to `true` or `false`. //! +//! Setting the `ngb` parameter of `assembly` to `true` removes its column from the global BOM and merges it parts into its parent assembly column of the global BOM. +//! This is to prevent the global BOM page becoming too wide in large projects by having it include just the major assemblies. +//! //! The example below shows how to define a vitamin and incorporate it into an assembly with sub-assemblies and make an exploded view. //! The resulting flat BOM is shown but heirachical BOMs are also generated for real projects. // @@ -84,9 +87,9 @@ module pose_vflip(exploded = undef) //! Pose an STL or assembly for render children(); -module assembly(name, big = undef) { //! Name an assembly that will appear on the BOM, there needs to a module named `_assembly` to make it. `big` can force big or small assembly diagrams. +module assembly(name, big = undef, ngb = false) { //! Name an assembly that will appear on the BOM, there needs to a module named `_assembly` to make it. `big` can force big or small assembly diagrams. if(bom_mode()) { - args = is_undef(big) ? "" : str("(big=", big, ")"); + args = is_undef(big) && !ngb ? "" : str("(big=", big, ", ngb=", ngb, ")"); echo(str("~", name, "_assembly", args, "{")); } no_pose()