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 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 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("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", "--imgsize=4096,4096", "-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) 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) 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 invisbly 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)