# Grouper: final silhouettes, isolated drawn lines and outline tables of the blueprint's views (plain Python: PIL, numpy). # python gr_views.py # reference/mask_side.png, mask_top.png, mask_bottom.png, mask_front.png, mask_back.png # filled silhouette of each view, white on black, at the ORIGINAL sheet size (1408 x 768): overlay a render on it 1:1 # reference/lines_side.png ... lines_back.png # the sheet's drawn lines inside each view (outline included), white on black, sheet size; leader lines, the section # cut line and the centre marks are erased (they are drafting, not geometry); labels outside the views are left out # build/outline_tables.json # per column (SIDE, TOP, BOTTOM) or row (FRONT, BACK): the silhouette's outer edges and the outline line's centre # (outer edge moved 0.75 px inward: the outline line is 1.5-2 px wide) # The silhouette: flood from the view box's border through non-line pixels (gr_trace.silhouette), dimension lines erased # first (gr_trace.ERASE), an opening of 2 px removes leader lines; the thin parts it also removes are put back by hand: # the antenna (SIDE), the mast (FRONT, BACK). import os, json import numpy as np from PIL import Image, ImageDraw from gr_trace import R, H, W, BOXES, silhouette, line_mask, dilate, REF, HERE THIN = { # (polygon in original pixels) the antenna tube from its foot to its tip, 3 px wide, and the foot bracket "side": [[(704.5, 302.5), (708.5, 300.5), (694.0, 268.0), (691.0, 269.0)], [(701.5, 305.5), (711.5, 305.5), (710.0, 299.5), (703.0, 299.5)]], "front": [[(230.5, 297.0), (233.5, 297.0), (233.5, 266.0), (230.5, 266.0)]], "back": [[(1204.0, 297.0), (1207.0, 297.0), (1207.0, 263.5), (1204.0, 263.5)]], } # drafting marks inside the views: (x0, y0, x1, y1) boxes erased from the line images NOT_GEOMETRY = { "side": [(668, 296, 679, 320), # "Stbizer Fin" leader's end inside the upper flank (862, 314, 870, 322), # "Battery Access Hatch" leader's end (664, 445, 670, 452), # "Stabilizer Fin" (lower left) leader's end on the keel line (700, 452, 707, 463)], # the tick beside it "top": [(760, 58, 765, 190), # section cut line A-A (dash-dot) through the ring (690, 147, 722, 153), # the ring's horizontal centre mark (left part) - the antenna in plan is kept (722, 148, 800, 151), # the ring's horizontal centre mark (630, 148, 642, 151)], # the dorsal teardrop's centre line beyond its end "bottom": [(760, 588, 765, 675), (725, 627, 800, 630)], "front": [], "back": [], } def final_mask(name): sil, _ = silhouette(BOXES[name], name=name) if name in THIN: im = Image.fromarray(sil.astype(np.uint8) * 255) d = ImageDraw.Draw(im) for poly in THIN[name]: d.polygon(poly, fill=255) sil = np.asarray(im) > 0 return sil def lines_image(name, sil): m = line_mask(name) & dilate(sil, 2) for (a, b, c, d) in NOT_GEOMETRY.get(name, []): m[b:d, a:c] = False return m if __name__ == "__main__": tables = {} for name in ("side", "top", "bottom", "front", "back"): sil = final_mask(name) Image.fromarray(sil.astype(np.uint8) * 255).save(os.path.join(REF, "mask_%s.png" % name)) ln = lines_image(name, sil) Image.fromarray(ln.astype(np.uint8) * 255).save(os.path.join(REF, "lines_%s.png" % name)) t = {} if name in ("side", "top", "bottom"): for x in range(W): ys = np.where(sil[:, x])[0] if len(ys): t[x] = [int(ys.min()), int(ys.max()), round(ys.min() + 0.75, 2), round(ys.max() - 0.75, 2)] else: for y in range(H): xs = np.where(sil[y, :])[0] if len(xs): t[y] = [int(xs.min()), int(xs.max()), round(xs.min() + 0.75, 2), round(xs.max() - 0.75, 2)] tables[name] = t print(name, "silhouette px", int(sil.sum()), "line px", int(ln.sum()), "span", min(t), max(t)) json.dump(tables, open(os.path.join(HERE, "outline_tables.json"), "w"))