def boms(target=None, assembly=None): try: bom_dir = set_config(target, usage) + "bom" if assembly: bom_dir += "/accessories" if not os.path.isdir(bom_dir): os.makedirs(bom_dir) else: assembly = "main_assembly" if os.path.isdir(bom_dir): shutil.rmtree(bom_dir) sleep(0.1) os.makedirs(bom_dir) # # Find the scad file that makes the module # scad_file = find_scad_file(assembly) if not scad_file: raise Exception("can't find source for " + assembly) # # make a file to use the module # bom_maker_name = source_dir + "/bom.scad" with open(bom_maker_name, "w") as f: f.write("use <%s>\n" % scad_file) f.write("%s();\n" % assembly) # # Run openscad # openscad.run("-D", "$bom=2", "-D", "$preview=true", "--hardwarnings", "-o", "openscad.echo", "-d", bom_dir + "/bom.deps", bom_maker_name) os.remove(bom_maker_name) print("Generating bom ...", end=" ") main = parse_bom("openscad.echo", assembly) if assembly == "main_assembly": main.print_bom(True, open(bom_dir + "/bom.txt", "wt")) for ass in main.assemblies: with open(bom_dir + "/" + ass + ".txt", "wt") as f: bom = main.assemblies[ass] print(bom.make_name(ass) + ":", file=f) bom.print_bom(False, f) data = [ main.assemblies[ass].flat_data() for ass in main.ordered_assemblies ] with open(bom_dir + "/bom.json", 'w') as outfile: json.dump(data, outfile, indent=4) print("done") except Exception as e: print(str(e)) sys.exit(1)
def render(target, type): # # Make the target directory # top_dir = set_config(target, usage) target_dir = top_dir + type + 's' bom_dir = top_dir + 'bom' if not os.path.isdir(target_dir): os.makedirs(target_dir) # # Find all the parts # parts = bom_to_parts(bom_dir, type) # # Remove unused png files # for file in os.listdir(target_dir): if file.endswith('.png'): if not file[:-4] + '.' + type in parts: print("Removing %s" % file) os.remove(target_dir + '/' + file) for part in parts: part_file = target_dir + '/' + part png_name = target_dir + '/' + part[:-4] + '.png' # # make a file to import the stl # if mtime(part_file) > mtime(png_name): png_maker_name = "png.scad" with open(png_maker_name, "w") as f: f.write('color([0, 146/255, 0]) import("%s");\n' % part_file) cam = "--camera=0,0,0,70,0,315,500" if type == 'stl' else "--camera=0,0,0,0,0,0,500" render = "--preview" if type == 'stl' else "--render" tmp_name = 'tmp.png' openscad.run(colour_scheme, "--projection=p", "--imgsize=4096,4096", cam, render, "--autocenter", "--viewall", "-o", tmp_name, png_maker_name) do_cmd(( "magick " + tmp_name + " -trim -resize 280x280 -background %s -gravity Center -extent 280x280 -bordercolor %s -border 10 %s" % (background, background, tmp_name)).split()) update_image(tmp_name, png_name) os.remove(png_maker_name)
def views(target, do_assemblies=None): done_assemblies = [] # # Make the target directory # top_dir = set_config(target) target_dir = top_dir + 'assemblies' deps_dir = top_dir + "deps" bom_dir = top_dir + "bom" if not os.path.isdir(target_dir): os.makedirs(target_dir) if not os.path.isdir(deps_dir): os.makedirs(deps_dir) times.read_times(target_dir) bounds_fname = top_dir + 'stls/bounds.json' with open(bounds_fname) as json_file: bounds_map = json.load(json_file) # # Find all the assemblies # assemblies = bom_to_assemblies(bom_dir, bounds_map) for file in os.listdir(target_dir): if file.endswith('.png'): assembly = file[:-4].replace('_assembled', '_assembly') if assembly.endswith('_tn'): assembly = assembly[:-3] if not assembly in assemblies: print("Removing %s" % file) os.remove(target_dir + '/' + file) # # Find all the scad files # main_blurb = None lib_dir = os.environ['OPENSCADPATH'] + '/NopSCADlib/printed' for dir in [source_dir, lib_dir]: for filename in os.listdir(dir): if filename.endswith('.scad'): # # find any modules with names ending in _assembly # with open(dir + "/" + filename, "r") as f: lines = f.readlines() line_no = 0 for line in lines: words = line.split() if len(words) and words[0] == "module": module = words[1].split('(')[0] if is_assembly(module): if module in assemblies: # # Scrape the assembly instructions # for ass in flat_bom: if ass["name"] == module: if not "blurb" in ass: ass["blurb"] = blurb.scrape_module_blurb( lines[:line_no]) break if not do_assemblies or module in do_assemblies: # # make a file to use the module # png_maker_name = 'png.scad' with open(png_maker_name, "w") as f: f.write("use <%s/%s>\n" % (dir, filename)) f.write("%s();\n" % module) # # Run openscad on th created file # dname = deps_name(deps_dir, filename) for explode in [0, 1]: png_name = target_dir + '/' + module + '.png' if not explode: png_name = png_name.replace( '_assembly', '_assembled') changed = check_deps(png_name, dname) changed = times.check_have_time( changed, png_name) tmp_name = 'tmp.png' if changed: print(changed) t = time.time() openscad.run( "-D$pose=1", "-D$explode=%d" % explode, colour_scheme, "--projection=p", "--imgsize=4096,4096", "--autocenter", "--viewall", "-d", dname, "-o", tmp_name, png_maker_name) times.add_time(png_name, t) do_cmd([ "magick", tmp_name, "-trim", "-resize", "1004x1004", "-bordercolor", background, "-border", "10", tmp_name ]) update_image(tmp_name, png_name) tn_name = png_name.replace( '.png', '_tn.png') if mtime(png_name) > mtime(tn_name): do_cmd(( "magick " + png_name + " -trim -resize 280x280 -background " + background + " -gravity Center -extent 280x280 -bordercolor " + background + " -border 10 " + tmp_name).split()) update_image(tmp_name, tn_name) os.remove(png_maker_name) done_assemblies.append(module) else: if module == 'main_assembly': main_blurb = blurb.scrape_module_blurb( lines[:line_no]) line_no += 1 times.print_times() # # Build the document # for print_mode in [True, False]: doc_name = top_dir + "readme.md" with open(doc_name, "wt") as doc_file: # # Title, description and picture # project = ' '.join( word[0].upper() + word[1:] for word in os.path.basename(os.getcwd()).split('_')) print('<a name="TOP"></a>\n# %s' % project, file=doc_file) main_file = bom.find_scad_file('main_assembly') if not main_file: raise Exception("can't find source for main_assembly") text = blurb.scrape_blurb(source_dir + '/' + main_file) if len(text): print(text, file=doc_file, end='') else: if print_mode: print(Fore.MAGENTA + "Missing project description" + Fore.WHITE) print('![Main Assembly](assemblies/%s.png)\n' % flat_bom[-1]["name"].replace('_assembly', '_assembled'), file=doc_file) eop(print_mode, doc_file, first=True) # # Build TOC # print('## Table of Contents', file=doc_file) print('1. [Parts list](#Parts_list)', file=doc_file) for ass in flat_bom: name = ass["name"] cap_name = name.replace('_', ' ').title() print('1. [%s](#%s)' % (cap_name, name), file=doc_file) print(file=doc_file) eop(print_mode, doc_file) # # Global BOM # print('<a name="Parts_list"></a>\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: things[t] = {} for ass in flat_bom: for t in types: for thing in ass[t]: if thing in things[t]: things[t][thing] += ass[t][thing] else: things[t][thing] = ass[t][thing] for ass in flat_bom: name = ass["name"][:-9].replace('_', ' ').title().replace( ' ', ' ') print( '| <span style="writing-mode: vertical-rl; text-orientation: mixed;">%s</span> ' % name, file=doc_file, end='') print( '| <span style="writing-mode: vertical-rl; text-orientation: mixed;">TOTALS</span> | |', file=doc_file) print(('|--:' * len(flat_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) for thing in sorted(things[t], key=lambda s: s.split(":")[-1]): for ass in flat_bom: count = ass[t][thing] if thing in ass[t] else 0 print('| %s ' % pad(count if count else '.', 2, 1), file=doc_file, end='') name = ass["name"] if name in totals: totals[name] += count else: totals[name] = 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: 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) print(file=doc_file) eop(print_mode, doc_file) # # Assembly instructions # for ass in flat_bom: name = ass["name"] cap_name = name.replace('_', ' ').title() if ass["count"] > 1: print('<a name="%s"></a>\n## %d x %s' % (name, ass["count"], cap_name), file=doc_file) else: print('<a name="%s"></a>\n## %s' % (name, cap_name), file=doc_file) vitamins = ass["vitamins"] if vitamins: print("### Vitamins", file=doc_file) print("|Qty|Description|", file=doc_file) print("|--:|:----------|", file=doc_file) for v in sorted(vitamins, key=lambda s: s.split(":")[-1]): print("|%d|%s|" % (vitamins[v], v.split(":")[1]), file=doc_file) print("\n", file=doc_file) printed = ass["printed"] if printed: print('### 3D Printed parts', file=doc_file) keys = sorted(list(printed.keys())) for i in range(len(keys)): p = keys[i] print('%s %d x %s |' % ('\n|' if not (i % 3) else '', printed[p], p), file=doc_file, end='') if (i % 3) == 2 or i == len(printed) - 1: n = (i % 3) + 1 print('\n|%s' % ('--|' * n), file=doc_file) for j in range(n): part = keys[i - n + j + 1] print('| ![%s](stls/%s) %s' % (part, part.replace('.stl', '.png'), '|\n' if j == j - 1 else ''), end='', file=doc_file) print('\n', file=doc_file) print('\n', file=doc_file) routed = ass["routed"] if routed: print("### CNC Routed parts", file=doc_file) keys = sorted(list(routed.keys())) for i in range(len(keys)): r = keys[i] print('%s %d x %s |' % ('\n|' if not (i % 3) else '', routed[r], r), file=doc_file, end='') if (i % 3) == 2 or i == len(routed) - 1: n = (i % 3) + 1 print('\n|%s' % ('--|' * n), file=doc_file) for j in range(n): part = keys[i - n + j + 1] print('| ![%s](dxfs/%s) %s' % (part, part.replace('.dxf', '.png'), '|\n' if j == j - 1 else ''), end='', file=doc_file) print('\n', file=doc_file) print('\n', file=doc_file) sub_assemblies = ass["assemblies"] if sub_assemblies: print("### Sub-assemblies", file=doc_file) keys = sorted(list(sub_assemblies.keys())) for i in range(len(keys)): a = keys[i] print('%s %d x %s |' % ('\n|' if not (i % 3) else '', sub_assemblies[a], a), file=doc_file, end='') if (i % 3) == 2 or i == len(keys) - 1: n = (i % 3) + 1 print('\n|%s' % ('--|' * n), file=doc_file) for j in range(n): a = keys[i - n + j + 1].replace( '_assembly', '_assembled') print('| ![%s](assemblies/%s) %s' % (a, a + '_tn.png', '|\n' if j == j - 1 else ''), end='', file=doc_file) print('\n', file=doc_file) print('\n', file=doc_file) small = not ass["big"] suffix = '_tn.png' if small else '.png' print('### Assembly instructions', file=doc_file) print('![%s](assemblies/%s)\n' % (name, name + suffix), file=doc_file) if "blurb" in ass and ass["blurb"]: print(ass["blurb"], file=doc_file) else: if print_mode: print( Fore.MAGENTA + "Missing instructions for %s" % name, Fore.WHITE) name = name.replace('_assembly', '_assembled') print('![%s](assemblies/%s)\n' % (name, name + suffix), file=doc_file) eop(print_mode, doc_file, last=ass == flat_bom[-1] and not main_blurb) # # If main module is suppressed print any blurb here # if main_blurb: print(main_blurb, file=doc_file) eop(print_mode, doc_file, last=True) # # Convert to HTML # html_name = "printme.html" if print_mode else "readme.html" with open(top_dir + html_name, "wt") as html_file: do_cmd(("python -m markdown -x tables -x sane_lists " + doc_name).split(), html_file) # # Spell check # do_cmd('codespell -L od readme.md'.split()) # # List the ones we didn't find # missing = set() for assembly in assemblies + (do_assemblies if do_assemblies else []): if assembly not in done_assemblies: missing.add(assembly) if missing: for assembly in missing: print(Fore.MAGENTA + "Could not find a module called", assembly, Fore.WHITE) sys.exit(1)
def render(target, type): # # Make the target directory # top_dir = set_config(target, usage) tmp_dir = mktmpdir(top_dir) target_dir = top_dir + type + 's' bom_dir = top_dir + 'bom' if not os.path.isdir(target_dir): os.makedirs(target_dir) # # Find all the parts # parts = bom_to_parts(bom_dir, type) # # Read the json bom to get the colours # bom_file = bom_dir + "/bom.json" with open(bom_file) as json_file: flat_bom = json.load(json_file) things = {'stl': 'printed', 'dxf': 'routed'}[type] colours = {} for ass in flat_bom: for part in ass[things]: obj = ass[things][part] if "colour" in obj: colours[part] = obj["colour"] # # Remove unused png files # for file in os.listdir(target_dir): if file.endswith('.png'): if not file[:-4] + '.' + type in parts: print("Removing %s" % file) os.remove(target_dir + '/' + file) # # Render the parts # for part in parts: part_file = target_dir + '/' + part png_name = target_dir + '/' + part[:-4] + '.png' # # make a file to import the stl # if mtime(part_file) > mtime(png_name): png_maker_name = tmp_dir + "/png.scad" pp1 = [0, 146 / 255, 0] colour = pp1 if part in colours: colour = colours[part] if not '[' in colour: colour = '"' + colour + '"' with open(png_maker_name, "w") as f: f.write('color(%s) import("%s");\n' % (colour, reltmp(part_file, target))) cam = "--camera=0,0,0,70,0,315,500" if type == 'stl' else "--camera=0,0,0,0,0,0,500" render = "--preview" if type == 'stl' or colour != pp1 else "--render" tmp_name = tmp_dir + '/' + part[:-4] + '.png' openscad.run("-o", tmp_name, png_maker_name, colour_scheme, "--projection=p", "--imgsize=4096,4096", cam, render, "--autocenter", "--viewall") do_cmd(( "magick " + tmp_name + " -trim -resize 280x280 -background %s -gravity Center -extent 280x280 -bordercolor %s -border 10 %s" % (background, background, tmp_name)).split()) update_image(tmp_name, png_name) os.remove(png_maker_name) # # Remove tmp dir # rmtmpdir(tmp_dir)
def make_parts(target, part_type, parts=None): # # Make the target directory # top_dir = set_config(target) target_dir = top_dir + part_type + 's' deps_dir = top_dir + "deps" if not os.path.isdir(target_dir): os.makedirs(target_dir) if not os.path.isdir(deps_dir): os.makedirs(deps_dir) times.read_times(target_dir) # # Decide which files to make # if parts: targets = list( parts) #copy the list so we dont modify the list passed in else: targets = bom_to_parts(target_dir, part_type) for file in os.listdir(target_dir): if file.endswith('.' + part_type): if not file in targets: print("Removing %s" % file) os.remove(target_dir + '/' + file) # # Read existing STL bounds # if part_type == 'stl': bounds_fname = target_dir + '/bounds.json' try: with open(bounds_fname) as json_file: bounds_map = json.load(json_file) except: bounds_map = {} # # Find all the scad files # lib_dir = os.environ['OPENSCADPATH'] + '/NopSCADlib/printed' module_suffix = '_dxf' if part_type == 'svg' else '_' + part_type for dir in [source_dir, lib_dir]: for filename in os.listdir(dir): if filename[-5:] == ".scad": # # find any modules ending in _<part_type> # with open(dir + "/" + filename, "r") as f: for line in f.readlines(): words = line.split() if (len(words) and words[0] == "module"): module = words[1].split('(')[0] if module.endswith(module_suffix): base_name = module[:-4] part = base_name + '.' + part_type if part in targets: # # make a file to use the module # part_maker_name = part_type + ".scad" with open(part_maker_name, "w") as f: f.write("use <%s/%s>\n" % (dir, filename)) f.write("%s();\n" % module) # # Run openscad on the created file # part_file = target_dir + "/" + part dname = deps_name(deps_dir, filename) changed = check_deps(part_file, dname) changed = times.check_have_time( changed, part) if part_type == 'stl' and not changed and not part in bounds_map: changed = "No bounds" if changed: print(changed) t = time.time() openscad.run("-D$bom=1", "-d", dname, "-o", part_file, part_maker_name) times.add_time(part, t) if part_type == 'stl': bounds = c14n_stl.canonicalise( part_file) bounds_map[part] = bounds targets.remove(part) os.remove(part_maker_name) # # Write new bounds file # if part_type == 'stl': with open(bounds_fname, 'w') as outfile: json.dump(bounds_map, outfile, indent=4) # # List the ones we didn't find # if targets: for part in targets: if part[-4:] != '.' + part_type: print(part, "is not a", part_type, "file") else: print("Could not find a module called", part[:-4] + module_suffix, "to make", part) sys.exit(1) times.print_times()
def views(target, do_assemblies=None): done_assemblies = [] # # Make the target directory # top_dir = set_config(target, usage) tmp_dir = mktmpdir(top_dir) target_dir = top_dir + 'assemblies' deps_dir = target_dir + "/deps" bom_dir = top_dir + "bom" if not os.path.isdir(target_dir): os.makedirs(target_dir) if not os.path.isdir(deps_dir): os.makedirs(deps_dir) times.read_times(target_dir) options.check_options(deps_dir) bounds_fname = top_dir + 'stls/bounds.json' with open(bounds_fname) as json_file: bounds_map = json.load(json_file) # # Find all the assemblies and remove any old views # assemblies = bom_to_assemblies(bom_dir, bounds_map) lc_assemblies = [ass.lower() for ass in assemblies] for file in os.listdir(target_dir): if file.endswith('.png'): assembly = file[:-4].replace('_assembled', '_assembly') if assembly.endswith('_tn'): assembly = assembly[:-3] if not assembly in assemblies: print("Removing %s" % file) os.remove(target_dir + '/' + file) # # Find all the scad files # main_blurb = None main_assembly, main_file = bom.main_assembly(target) pngs = [] for dir in source_dirs(bom_dir): if os.path.isdir(dir): for filename in os.listdir(dir): if filename.endswith('.scad'): # # find any modules with names ending in _assembly # with open(dir + "/" + filename, "r") as f: lines = f.readlines() line_no = 0 for line in lines: words = line.split() if len(words) and words[0] == "module": module = words[1].split('(')[0] if is_assembly(module): lc_module = module.lower() if lc_module in lc_assemblies: real_name = assemblies[lc_assemblies.index( lc_module)] # # Scrape the assembly instructions # for ass in flat_bom: if ass["name"] == real_name: zoomed = ass['zoomed'] if not "blurb" in ass: ass["blurb"] = blurb.scrape_module_blurb( lines[:line_no]) break # # Run openscad on the created file # dname = deps_name(deps_dir, filename) for explode in [0, 1]: # # Generate png name # png_name = target_dir + '/' + real_name + '.png' if not explode: png_name = png_name.replace( '_assembly', '_assembled') pngs.append(png_name) if not do_assemblies or real_name in do_assemblies: changed = check_deps( png_name, dname) changed = times.check_have_time( changed, png_name) changed = options.have_changed( changed, png_name) tmp_name = tmp_dir + '/' + real_name + '.png' if changed: print(changed) # # make a file to use the module # png_maker_name = tmp_dir + '/png.scad' with open(png_maker_name, "w") as f: f.write( "include <NopSCADlib/global_defs.scad>\n" ) f.write( "use <%s/%s>\n" % (reltmp(dir, target), filename)) f.write("%s();\n" % module) t = time.time() target_def = [ '-D$target="%s"' % target ] if target else [] cwd_def = [ '-D$cwd="%s"' % os.getcwd().replace( '\\', '/') ] view_def = [ '--viewall', '--autocenter' ] if not ( zoomed & (1 << explode) ) else [ '--camera=0,0,0,55,0,25,140' ] openscad.run_list( [ "-o", tmp_name, png_maker_name ] + options.list() + target_def + cwd_def + view_def + [ "-D$pose=1", "-D$explode=%d" % explode, colour_scheme, "--projection=p", image_size, "-d", dname ]) times.add_time(png_name, t) do_cmd([ "magick", tmp_name, "-trim", "-resize", "1004x1004", "-bordercolor", background, "-border", "10", tmp_name ]) update_image( tmp_name, png_name) os.remove(png_maker_name) tn_name = png_name.replace( '.png', '_tn.png') if mtime(png_name) > mtime( tn_name): do_cmd(( "magick " + png_name + " -trim -resize 280x280 -background " + background + " -gravity Center -extent 280x280 -bordercolor " + background + " -border 10 " + tmp_name).split()) update_image(tmp_name, tn_name) done_assemblies.append(real_name) else: if module == main_assembly: main_blurb = blurb.scrape_module_blurb( lines[:line_no]) line_no += 1 # # Build the document # doc_name = top_dir + "readme.md" with open(doc_name, "wt") as doc_file: # # Title, description and picture # project = ' '.join( word[0].upper() + word[1:] for word in os.path.basename(os.getcwd()).split('_')) print('<a name="TOP"></a>', file=doc_file) print('# %s' % project, file=doc_file) text = blurb.scrape_blurb(source_dir + '/' + main_file) blurbs = blurb.split_blurb(text) if len(text): print(blurbs[0], file=doc_file) else: print(Fore.MAGENTA + "Missing project description" + Fore.WHITE) # # Only add the image if the first blurb section doesn't contain one. # if not re.search(r'\!\[.*\]\(.*\)', blurbs[0], re.MULTILINE): print('![Main Assembly](assemblies/%s.png)\n' % flat_bom[-1]["name"].replace('_assembly', '_assembled'), file=doc_file) eop(doc_file, first=True) # # Build TOC # print('## Table of Contents', file=doc_file) print('1. [Parts list](#Parts_list)', file=doc_file) for ass in flat_bom: name = ass["name"] cap_name = titalise(name) print('1. [%s](#%s)' % (cap_name, name), file=doc_file) print(file=doc_file) if len(blurbs) > 1: print(blurbs[1], file=doc_file) eop(doc_file) # # Global BOM # global_bom = [merged(ass) for ass in flat_bom if not ass['ngb']] print('<a name="Parts_list"></a>\n## Parts list', file=doc_file) headings = { "vitamins": "vitamins", "printed": "3D printed parts", "routed": "CNC routed parts" } things = {} for t in types: things[t] = {} for ass in flat_bom: for t in types: for thing in ass[t]: if thing in things[t]: things[t][thing] += ass[t][thing]["count"] else: things[t][thing] = ass[t][thing]["count"] for ass in global_bom: name = titalise(ass["name"][:-9]).replace(' ', ' ') if ass["count"] > 1: name = "%d x %s" % (ass["count"], name) print( '| <span style="writing-mode: vertical-rl; text-orientation: mixed;">%s</span> ' % name, file=doc_file, end='') print( '| <span style="writing-mode: vertical-rl; text-orientation: mixed;">TOTALS</span> | |', file=doc_file) print(('|---:' * len(global_bom) + '|---:|:---|'), file=doc_file) for t in types: if things[t]: totals = {} 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 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"] if name in totals: 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 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) eop(doc_file) # # Assembly instructions # for ass in flat_bom: name = ass["name"] cap_name = titalise(name) print('<a name="%s"></a>' % name, file=doc_file) if ass["count"] > 1: print('## %d x %s' % (ass["count"], cap_name), file=doc_file) else: print('## %s' % cap_name, file=doc_file) vitamins = ass["vitamins"] if vitamins: print("### Vitamins", file=doc_file) print("|Qty|Description|", file=doc_file) print("|---:|:----------|", file=doc_file) for v in sorted(vitamins, key=lambda s: s.split(":")[-1]): print("|%d|%s|" % (vitamins[v]["count"], v.split(":")[1]), file=doc_file) print("\n", file=doc_file) printed = ass["printed"] if printed: print('### 3D Printed parts', file=doc_file) keys = sorted(list(printed.keys())) for i, p in enumerate(keys): print( '%s %d x %s |' % ('\n|' if not (i % 3) else '', printed[p]["count"], p), file=doc_file, end='') if (i % 3) == 2 or i == len(printed) - 1: n = (i % 3) + 1 print('\n|%s' % ('---|' * n), file=doc_file) for j in range(n): part = keys[i - n + j + 1] print('| ![%s](stls/%s) %s' % (part, part.replace('.stl', '.png'), '|\n' if j == j - 1 else ''), end='', file=doc_file) print('\n', file=doc_file) print('\n', file=doc_file) routed = ass["routed"] if routed: print("### CNC Routed parts", file=doc_file) keys = sorted(list(routed.keys())) for i, r in enumerate(keys): print( '%s %d x %s |' % ('\n|' if not (i % 3) else '', routed[r]["count"], r), file=doc_file, end='') if (i % 3) == 2 or i == len(routed) - 1: n = (i % 3) + 1 print('\n|%s' % ('---|' * n), file=doc_file) for j in range(n): part = keys[i - n + j + 1] print('| ![%s](dxfs/%s) %s' % (part, part.replace('.dxf', '.png'), '|\n' if j == j - 1 else ''), end='', file=doc_file) print('\n', file=doc_file) print('\n', file=doc_file) sub_assemblies = ass["assemblies"] if sub_assemblies: print("### Sub-assemblies", file=doc_file) keys = sorted(list(sub_assemblies.keys())) for i, a in enumerate(keys): print('%s %d x %s |' % ('\n|' if not (i % 3) else '', sub_assemblies[a], a), file=doc_file, end='') if (i % 3) == 2 or i == len(keys) - 1: n = (i % 3) + 1 print('\n|%s' % ('---|' * n), file=doc_file) for j in range(n): a = keys[i - n + j + 1].replace( '_assembly', '_assembled') print('| ![%s](assemblies/%s) %s' % (a, a + '_tn.png', '|\n' if j == j - 1 else ''), end='', file=doc_file) print('\n', file=doc_file) print('\n', file=doc_file) small = not ass["big"] suffix = '_tn.png' if small else '.png' print('### Assembly instructions', file=doc_file) print('![%s](assemblies/%s)\n' % (name, name + suffix), file=doc_file) if "blurb" in ass and ass["blurb"]: print(ass["blurb"], file=doc_file) else: print(Fore.MAGENTA + "Missing instructions for %s" % name, Fore.WHITE) name = name.replace('_assembly', '_assembled') print('![%s](assemblies/%s)\n' % (name, name + suffix), file=doc_file) eop(doc_file, last=ass == flat_bom[-1] and not main_blurb) # # If main module is suppressed print any blurb here # if main_blurb: print(main_blurb, file=doc_file) eop(doc_file, last=True) # # Convert to HTML # html_name = top_dir + 'readme.html' t = time.time() with open(html_name, "wt") as html_file: do_cmd( ("python -m markdown -x tables -x sane_lists " + doc_name).split(), html_file) times.add_time(html_name, t) times.print_times(pngs + [html_name]) # # Make the printme.html by replacing empty spans that invisibly mark the page breaks by page break divs. # with open(html_name, 'rt') as src: lines = src.readlines() i = 0 with open(top_dir + 'printme.html', 'wt') as dst: while i < len(lines): line = lines[i] if line.startswith( '<p><span></span>'): # Empty span used to mark page breaks i += 1 if lines[i].startswith( '<a href="#TOP">Top</a>' ): # The first page break won't have one i += 1 if i < len(lines) and lines[ i] == '<hr />\n': # The last page break doesn't have one dst.write( '<div style="page-break-after: always;"></div>\n') i += 1 else: dst.write(line) i += 1 # # Remove tmp dir # rmtmpdir(tmp_dir) # # Spell check # do_cmd(('codespell -L od ' + top_dir + 'readme.md').split()) # # List the ones we didn't find # missing = set() for assembly in assemblies + (do_assemblies if do_assemblies else []): if assembly not in done_assemblies: missing.add(assembly) if missing: for assembly in missing: print(Fore.MAGENTA + "Could not find a module called", assembly, Fore.WHITE) sys.exit(1)
def make_parts(target, part_type, parts=None): # # Check list of parts is the correct type # if parts: for p in parts: if not p.endswith('.' + part_type): usage(part_type) # # Make the target directory # top_dir = set_config(target, lambda: usage(part_type)) target_dir = top_dir + part_type + 's' deps_dir = target_dir + "/deps" bom_dir = top_dir + "bom" tmp_dir = mktmpdir(top_dir) if not os.path.isdir(target_dir): os.makedirs(target_dir) if not os.path.isdir(deps_dir): os.makedirs(deps_dir) if os.path.isdir(top_dir + '/deps'): #old location shutil.rmtree(top_dir + '/deps') times.read_times(target_dir) # # Decide which files to make # all_parts = bom_to_parts(bom_dir, part_type) if parts: targets = list( parts) #copy the list so we dont modify the list passed in else: targets = list(all_parts) for file in os.listdir(target_dir): if file.endswith('.' + part_type): if not file in targets: print("Removing %s" % file) os.remove(target_dir + '/' + file) # # Read existing STL bounds # if part_type == 'stl': bounds_fname = target_dir + '/bounds.json' try: with open(bounds_fname) as json_file: bounds_map = json.load(json_file) except: bounds_map = {} # # Find all the scad files # module_suffix = '_dxf' if part_type == 'svg' else '_' + part_type for dir in source_dirs(bom_dir): if targets and os.path.isdir(dir): for filename in os.listdir(dir): if targets and filename[-5:] == ".scad": # # find any modules ending in _<part_type> # with open(dir + "/" + filename, "r") as f: for line in f.readlines(): words = line.split() if (len(words) and words[0] == "module"): module = words[1].split('(')[0] if module.endswith(module_suffix): base_name = module[:-4] part = base_name + '.' + part_type if part in targets: # # Run openscad on the created file # part_file = target_dir + "/" + part dname = deps_name(deps_dir, filename) changed = check_deps(part_file, dname) changed = times.check_have_time( changed, part) if part_type == 'stl' and not changed and not part in bounds_map: changed = "No bounds" if changed: print(changed) # # make a file to use the module # part_maker_name = tmp_dir + '/' + part_type + ".scad" with open(part_maker_name, "w") as f: f.write("use <%s/%s>\n" % (reltmp(dir, target), filename)) f.write("%s();\n" % module) t = time.time() openscad.run( "-D$bom=1", "-d", dname, "-o", part_file, part_maker_name) times.add_time(part, t) if part_type == 'stl': bounds = c14n_stl.canonicalise( part_file) bounds_map[part] = bounds os.remove(part_maker_name) targets.remove(part) # # Write new bounds file # if part_type == 'stl': with open(bounds_fname, 'w') as outfile: json.dump(bounds_map, outfile, indent=4) # # Remove tmp dir # rmtmpdir(tmp_dir) # # List the ones we didn't find # if targets: for part in targets: print("Could not find a module called", part[:-4] + module_suffix, "to make", part) usage(part_type) times.print_times(all_parts)
def plateup(target, part_type, usage = None): # # Make the target directory # top_dir = set_config(target, usage) parts_dir = top_dir + part_type + 's' target_dir = parts_dir + '/' + target_dirs[part_type] source_dir1 = source_dirs[part_type] source_dir2 = top_dir + source_dirs[part_type] # # Loop through source directories # all_used = [] all_sources = [] all_parts = [] read_times = False for dir in [source_dir1, source_dir2]: if not os.path.isdir(dir): continue if not os.path.isdir(target_dir): os.makedirs(target_dir) if not read_times: times.read_times(target_dir) read_times = True # # Make the deps dir # deps_dir = parts_dir + "/deps" if not os.path.isdir(deps_dir): os.makedirs(deps_dir) if os.path.isdir(dir + '/deps'): #old deps shutil.rmtree(dir + '/deps') # # Decide which files to make # sources = [file for file in os.listdir(dir) if file.endswith('.scad')] all_sources += sources # # Run OpenSCAD on the source files to make the targets # target_def = ['-D$target="%s"' % target] if target else [] cwd_def = ['-D$cwd="%s"' % os.getcwd().replace('\\', '/')] for src in sources: src_file = dir + '/' + src part = src[:-4] + part_type all_parts.append(part) part_file = target_dir + '/' + part uses_file = deps_dir + '/' + src[:-4] + 'txt' dname = deps_name(deps_dir, src) oldest = part_file if mtime(part_file) < mtime(uses_file) else uses_file changed = check_deps(oldest, dname) used = [] if changed: print(changed) t = time.time() openscad.run_list(["-D$bom=1"] + target_def + cwd_def + ["-d", dname, "-o", part_file, src_file]) if part_type == 'stl': c14n_stl.canonicalise(part_file) times.add_time(part, t) log_name = 'openscad.log' # # Add the files on the BOM to the used list # with open(log_name) as file: for line in file.readlines(): match = re.match(r'^ECHO: "~(.*?\.' + part_type + r').*"$', line) if match: used.append(match.group(1)) with open(uses_file, "wt") as file: for part in used: print(part, file = file) else: with open(uses_file, "rt") as file: for line in file: used.append(line[:-1]) all_used += used copied = [] if all_sources: # # Copy files that are not included # for file in os.listdir(parts_dir): if file.endswith('.' + part_type) and not file in all_used: src = parts_dir + '/' + file dst = target_dir + '/' + file if mtime(src) > mtime(dst): print("Copying %s to %s" % (src, dst)) shutil.copyfile(src, dst) copied.append(file) # # Remove any cruft # targets = [file[:-4] + part_type for file in all_sources] for file in os.listdir(target_dir): if file.endswith('.' + part_type): if not file in targets and not file in copied: print("Removing %s" % file) os.remove(target_dir + '/' + file) targets = [file[:-4] + 'txt' for file in all_sources] for file in os.listdir(deps_dir): if file.endswith('.' + 'txt'): if not file in targets: print("Removing %s" % file) os.remove(deps_dir + '/' + file) times.print_times(all_parts)
def plateup(target, part_type, usage=None): # # Make the target directory # top_dir = set_config(target, usage) parts_dir = top_dir + part_type + 's' target_dir = parts_dir + '/' + target_dirs[part_type] source_dir = top_dir + source_dirs[part_type] deps_dir = source_dir + "/deps" if not os.path.isdir(source_dir): return if not os.path.isdir(target_dir): os.makedirs(target_dir) if not os.path.isdir(deps_dir): os.makedirs(deps_dir) # # Decide which files to make # sources = [ file for file in os.listdir(source_dir) if file.endswith('.scad') ] # # Run OpenSCAD on the source files to make the targets # used = [] for src in sources: src_file = source_dir + '/' + src part_file = target_dir + '/' + src[:-4] + part_type dname = deps_name(deps_dir, src) changed = check_deps(part_file, dname) if changed: print(changed) openscad.run("-D$bom=1", "-d", dname, "-o", part_file, src_file) if part_type == 'stl': c14n_stl.canonicalise(part_file) log_name = 'openscad.log' else: log_name = 'openscad.echo' openscad.run_silent("-D$bom=1", "-o", log_name, src_file) # # Add the files on the BOM to the used list # with open(log_name) as file: for line in file.readlines(): if line.startswith('ECHO: "~') and line.endswith('.' + part_type + '"\n'): used.append(line[8:-2]) # # Copy file that are not included # copied = [] for file in os.listdir(parts_dir): if file.endswith('.' + part_type) and not file in used: src = parts_dir + '/' + file dst = target_dir + '/' + file if mtime(src) > mtime(dst): print("Copying %s to %s" % (src, dst)) copyfile(src, dst) copied.append(file) # # Remove any cruft # targets = [file[:-4] + part_type for file in sources] for file in os.listdir(target_dir): if file.endswith('.' + part_type): if not file in targets and not file in copied: print("Removing %s" % file) os.remove(target_dir + '/' + file)
def plateup(target, part_type, usage=None): # # Make the target directory # top_dir = set_config(target, usage) parts_dir = top_dir + part_type + 's' target_dir = parts_dir + '/' + target_dirs[part_type] source_dir1 = source_dirs[part_type] source_dir2 = top_dir + source_dirs[part_type] # # Loop through source directories # used = [] all_sources = [] for dir in [source_dir1, source_dir2]: if not os.path.isdir(dir): continue if not os.path.isdir(target_dir): os.makedirs(target_dir) # # Make the deps dir # deps_dir = dir + "/deps" if not os.path.isdir(deps_dir): os.makedirs(deps_dir) # # Decide which files to make # sources = [file for file in os.listdir(dir) if file.endswith('.scad')] all_sources += sources # # Run OpenSCAD on the source files to make the targets # for src in sources: src_file = dir + '/' + src part_file = target_dir + '/' + src[:-4] + part_type dname = deps_name(deps_dir, src) changed = check_deps(part_file, dname) if changed: print(changed) openscad.run("-D$bom=1", "-d", dname, "-o", part_file, src_file) if part_type == 'stl': c14n_stl.canonicalise(part_file) log_name = 'openscad.log' else: log_name = 'openscad.echo' openscad.run_silent("-D$bom=1", "-o", log_name, src_file) # # Add the files on the BOM to the used list # with open(log_name) as file: for line in file.readlines(): match = re.match(r'^ECHO: "~(.*?\.' + part_type + r').*"$', line) if match: used.append(match.group(1)) # # Copy file that are not included # copied = [] for file in os.listdir(parts_dir): if file.endswith('.' + part_type) and not file in used: src = parts_dir + '/' + file dst = target_dir + '/' + file if mtime(src) > mtime(dst): print("Copying %s to %s" % (src, dst)) copyfile(src, dst) copied.append(file) # # Remove any cruft # targets = [file[:-4] + part_type for file in all_sources] for file in os.listdir(target_dir): if file.endswith('.' + part_type): if not file in targets and not file in copied: print("Removing %s" % file) os.remove(target_dir + '/' + file)