# Grouper: draws the design's readings (build/gr_measure.py) over the sheet, view by view, 3x, so every number can be # checked by eye against the drawn lines; white = the concept render's strips, circuit zones and lamps. python gr_tracecheck.py -> reference/trace_side.png, trace_top.png, # trace_front.png, trace_back.png, trace_section.png # red : the silhouette's outline line (SIDE_SIL / TOP_SIL / FRONT_SIL / BACK_SIL) # yellow : the hull's loft tables (HULL_TOP, HULL_BOT, HULL_HALF) or the mid-body section built from them # cyan : waist rims, chine, crease, seams # magenta: collar, hatch, antenna, emitters, battery hatch # green : sensor drum, cradle, cheek fins # orange : thruster pods, door, windows, stern parts import os, math from PIL import Image, ImageDraw import gr_measure as m HERE = os.path.dirname(os.path.abspath(__file__)) REF = os.path.normpath(os.path.join(HERE, "..", "reference")) SHEET = Image.open(os.path.join(REF, "user_blueprint.jpg")).convert("RGB") K = 3 RED, YEL, CYA, MAG, GRN, ORA, WHT = (255, 50, 50), (255, 230, 0), (0, 255, 255), (255, 60, 255), (60, 255, 60), (255, 150, 0), (255, 255, 255) class Canvas: def __init__(self, box): self.x0, self.y0, x1, y1 = box self.im = SHEET.crop(box).resize(((x1 - self.x0) * K, (y1 - self.y0) * K), Image.LANCZOS) self.d = ImageDraw.Draw(self.im) def p(self, x, y): return ((x - self.x0 + 0.5) * K, (y - self.y0 + 0.5) * K) def line(self, pts, col, w=1, closed=False): q = [self.p(x, y) for x, y in pts] if closed: q.append(q[0]) self.d.line(q, fill=col, width=w) def rect(self, b, col, w=1): self.line([(b[0], b[1]), (b[2], b[1]), (b[2], b[3]), (b[0], b[3])], col, w, True) def circle(self, cx, cy, r, col, w=1, n=72): self.line([(cx + r * math.cos(2 * math.pi * i / n), cy + r * math.sin(2 * math.pi * i / n)) for i in range(n)], col, w, True) def text(self, x, y, s, col=WHT): self.d.text(self.p(x, y), s, fill=col) def save(self, name): self.im.save(os.path.join(REF, name)) def interp(tab, x): for (xa, ya), (xb, yb) in zip(tab, tab[1:]): if xa <= x <= xb: return ya + (yb - ya) * (x - xa) / (xb - xa) if xb != xa else ya return tab[0][1] if x < tab[0][0] else tab[-1][1] def section(W, ytop, yw, ybot, nu, nl, n=40): """the superellipse section: list of (lateral px, y) from the top round the port side to the bottom""" out = [] for i in range(n + 1): t = i / n v = 1 - t out.append((W * (1 - v ** nu) ** (1 / nu), yw - v * (yw - ytop))) for i in range(1, n + 1): t = i / n out.append((W * (1 - t ** nl) ** (1 / nl), yw + t * (ybot - yw))) return out # ---------------- SIDE c = Canvas((500, 255, 965, 490)) c.line([(x, a) for x, a, b in m.SIDE_SIL], RED) c.line([(x, b) for x, a, b in m.SIDE_SIL], RED) c.line(m.HULL_TOP, YEL, 2) c.line(m.HULL_BOT, YEL, 2) c.line(m.NOSE_FRONT, YEL, 1) c.line([(x, a) for x, a, b in m.WAIST_UPPER_RIM], CYA) c.line([(x, b) for x, a, b in m.WAIST_UPPER_RIM], CYA) c.line([(x, a) for x, a, b in m.WAIST_LOWER_RIM], CYA) c.line([(x, b) for x, a, b in m.WAIST_LOWER_RIM], CYA) c.line(m.CHINE_SIDE, CYA) c.line(m.CHEEK_CREASE, CYA) # collar (drawn profile, both sides of the axis) and the clamp axis / origin for sgn in (-1, 1): c.line([(m.CL_IMG + sgn * r, y) for r, y in m.COLLAR["side_profile"]], MAG, 2) c.line([(m.CL_IMG - 32.0, 296.0), (m.CL_IMG + 32.0, 296.0)], MAG) c.line([(m.CL_IMG, 262), (m.CL_IMG, 300)], MAG) c.text(m.CL_IMG + 2, 262, "clamp axis") c.line([(m.X0, 270), (m.X0, 290)], WHT) c.text(m.X0 - 30, 270, "origin") c.line([m.ANTENNA["foot"], m.ANTENNA["tip"]], MAG, 2) c.rect(m.ANTENNA["foot_bracket"], MAG) c.line(m.EMITTER["side_profile"], MAG, 2) # drum, flange, cradle d = m.SENSOR_DRUM c.rect((d["x_body"][0], d["y_c"] - d["r"], d["x_body"][1], d["y_c"] + d["r"]), GRN, 2) c.rect((d["flange_x"][0], d["y_c"] - d["flange_r"], d["flange_x"][1], d["y_c"] + d["flange_r"]), GRN) cr = m.CRADLE c.line(cr["upper_arm"], GRN, 2) c.line(cr["u_tube"], GRN, 2) c.line(cr["lower_rail"], GRN, 2) c.rect(cr["base_bracket"], GRN) c.rect(cr["clevis"], GRN) c.circle(cr["cross_tube_x"], cr["cross_tube_y"], 3, GRN) # windows c.line(m.SIDE_WINDOW_GLASS, ORA, 2, True) c.line(m.SIDE_WINDOW_FRAME_OUT, ORA, 1, True) fw = m.FRONT_WINDOW c.line([(interp([(y, x) for x, y in m.NOSE_FRONT], fw["y_top"]), fw["y_top"]), (interp([(y, x) for x, y in m.NOSE_FRONT], fw["y_bot"]), fw["y_bot"])], ORA, 3) # door, porthole, latches dr = m.DOOR c.rect((dr["x"][0], dr["y"][0], dr["x"][1], dr["y"][1]), ORA, 2) c.circle(*dr["porthole_c"], dr["porthole_r"][0], ORA) c.circle(*dr["porthole_c"], dr["porthole_r"][1], ORA) for (lx, ly) in dr["latches"]: c.rect((lx - 3, ly - 2, lx + 3, ly + 2), ORA) # pods: top and bottom lines of the nacelle about its axis p = m.POD c.line([(x, p["y_c"] - r) for x, r in p["radius"]], ORA, 2) c.line([(x, p["y_c"] + r) for x, r in p["radius"]], ORA, 2) c.line([(p["radius"][0][0], p["y_c"]), (p["radius"][-1][0], p["y_c"])], ORA) c.rect(p["slot"], ORA) # stern: duct, stator plane, skeg, fin and its mount du = m.DUCT c.rect((du["x_aft"], du["y_c"] - du["r_out"], du["x_fore"], du["y_c"] + du["r_out"]), ORA, 2) c.line([(du["stator_x"], du["y_c"] - du["r_inner"]), (du["stator_x"], du["y_c"] + du["r_inner"])], ORA) c.line(m.SKEG["profile"], ORA, 2) c.rect(m.STERN_FIN["side_box"], ORA, 2) c.rect((m.STERN_FIN_MOUNT["x"][0], m.STERN_FIN_MOUNT["y"][0], m.STERN_FIN_MOUNT["x"][1], m.STERN_FIN_MOUNT["y"][1]), ORA) # flank details and markings for k, v in m.FLANK_DETAILS.items(): if isinstance(v, tuple) and len(v) == 4: c.rect(v, CYA) # the concept's light strips (white, thick), circuit zones (white boxes) and lamps (white circles) c.line(m.LIGHT_STRIPS["upper"], WHT, 3) c.line(m.LIGHT_STRIPS["lower"], WHT, 3) dr = m.LIGHT_STRIPS["duct_ring"] c.line([(dr["x"], dr["y_c"] - dr["r_px"]), (dr["x"], dr["y_c"] + dr["r_px"])], WHT, 3) for k, b in m.CIRCUIT_ZONES.items(): c.rect(b, WHT) c.text(b[0], b[1] - 4, k) c.circle(m.LAMPS["emitter_L/R"]["x_lens"], m.LAMPS["emitter_L/R"]["y"], m.LAMPS["emitter_L/R"]["r_px"], WHT, 2) c.circle(m.LAMPS["pod_L/R"]["x_face"], m.LAMPS["pod_L/R"]["y"], m.LAMPS["pod_L/R"]["r_lens_m"] / m.S, WHT, 2) c.rect(m.MARKINGS["insignia_box"], WHT) c.rect(m.MARKINGS["text_box"], WHT) c.circle(*m.MARKINGS["seal_c"], m.MARKINGS["seal_r"], WHT) c.rect(m.CHEEK_FIN["x"][:1] + m.CHEEK_FIN["y"][:1] + m.CHEEK_FIN["x"][1:] + m.CHEEK_FIN["y"][1:], GRN) # seats and eye (where the cabin's seats stand) c.line([(m.CABIN_Y["seat_back_x"], 300), (m.CABIN_Y["seat_back_x"], 470)], WHT) c.text(m.CABIN_Y["seat_back_x"] + 1, 452, "seat backs") ey = m.Z0 - m.CABIN["eye_z"] / m.S c.circle(m.CABIN_Y["eye_x"], ey, 2, WHT, 2) c.save("trace_side.png") # ---------------- TOP c = Canvas((500, 40, 965, 245)) c.line([(x, a) for x, a, b in m.TOP_SIL], RED) c.line([(x, b) for x, a, b in m.TOP_SIL], RED) for sgn in (-1, 1): c.line([(x, m.YC_TOP - sgn * h) for x, h in m.HULL_HALF], YEL, 2) c.line([(x, m.YC_TOP - sgn * (p["lateral_px"] + r)) for x, r in p["radius"]], ORA, 2) c.line([(x, m.YC_TOP - sgn * (p["lateral_px"] - r)) for x, r in p["radius"]], ORA, 1) c.line([(x, m.YC_TOP + (y - m.YC_TOP) * sgn) for x, y in m.STERN_FIN["planform_top"]], ORA, 2) e = m.EMITTER c.rect((e["x"][0], m.YC_TOP - sgn * e["lateral_px"] - e["half_width_px"], e["x"][1], m.YC_TOP - sgn * e["lateral_px"] + e["half_width_px"]), MAG, 2) c.line([(x, m.YC_TOP - sgn * h) for x, h in m.DORSAL_RIDGE["top_planform"]], CYA, 2) c.line([(x, m.YC_TOP + (y - m.YC_TOP) * sgn) for x, y in m.CHINE_TOP], CYA) c.line([(x, m.YC_TOP + (y - m.YC_TOP) * sgn) for x, y in m.NOSE_DECK_SEAM], CYA) b = m.SENSOR_BOX c.rect((b["x"][0], m.YC_TOP - b["half_px"], b["x"][1], m.YC_TOP + b["half_px"]), MAG, 2) nc = m.LIGHT_STRIPS["upper_nose_crossing"] c.line([(nc["side_x"], m.YC_TOP - nc["half_m"] / m.S), (nc["side_x"], m.YC_TOP + nc["half_m"] / m.S)], WHT, 3) for r in (m.COLLAR["r_foot"], m.COLLAR["r_top_out"], m.COLLAR["r_top_in"]): c.circle(m.CL_IMG, m.YC_TOP, r, MAG, 2) c.circle(760.2, 148.9, 32.19, WHT) # where TOP draws it (6.8 px forward) c.circle(m.ANTENNA["foot"][0], m.YC_TOP, m.ANTENNA["foot_r_top_px"], MAG) c.line([(m.ANTENNA["foot"][0], m.YC_TOP), (m.ANTENNA["tip"][0], m.YC_TOP)], MAG, 2) d = m.SENSOR_DRUM c.rect((d["x_body"][0], m.YC_TOP - d["r"], d["x_body"][1], m.YC_TOP + d["r"]), GRN, 2) c.rect((d["flange_x"][0], m.YC_TOP - d["flange_r"], d["flange_x"][1], m.YC_TOP + d["flange_r"]), GRN) du = m.DUCT c.rect((du["x_aft"], m.YC_TOP - du["r_out"], du["x_fore"], m.YC_TOP + du["r_out"]), ORA, 2) c.line([(m.CL_IMG, 45), (m.CL_IMG, 240)], MAG) c.save("trace_top.png") # ---------------- FRONT and BACK: the mid-body section from the loft tables, pods, drum / duct, collar def fb(view, box, sil, to_img): c = Canvas(box) c.line([(a, y) for y, a, b in sil], RED) c.line([(b, y) for y, a, b in sil], RED) xm = 790.0 W, ytop, yw, ybot = interp(m.HULL_HALF, xm), interp(m.HULL_TOP, xm), interp(m.HULL_YW, xm), interp(m.HULL_BOT, xm) sec = section(W, ytop, yw, ybot, m.NU_MID, m.NL_MID) for sgn in (-1, 1): c.line([(to_img(sgn * l), y) for l, y in sec], YEL, 2) c.circle(to_img(sgn * p["lateral_px"]), p["y_c"], 19.5 / (m.KF if view == "front" else m.KB), ORA, 2) # collar footprint / top (its half width seen from the front or back) c.line([(to_img(sgn * 32.0), 296.0 - 1.75), (to_img(sgn * 33.5), 300.0), (to_img(sgn * 37.0), 304.5)], MAG, 2) if view == "front": d = m.SENSOR_DRUM c.circle(to_img(0), d["y_c"], d["r"] / m.KF, GRN, 2) c.circle(to_img(0), d["y_c"], d["flange_r"] / m.KF, GRN) for sgn in (-1, 1): c.line([(to_img(sgn * m.CRADLE["lateral_px"]), 402), (to_img(sgn * m.CRADLE["lateral_px"]), 470)], GRN, 2) e = m.EMITTER c.circle(to_img(sgn * e["lateral_px"]), e["lens_y"], e["lens_r_px"], MAG, 2) fw = m.FRONT_WINDOW c.line([(to_img(sgn * fw["half_top_px"]), fw["y_top"]), (to_img(sgn * fw["half_bot_px"]), fw["y_bot"])], ORA, 2) c.line([(to_img(sgn * m.FRONT_SIDE_WINDOW_LATERAL_PX[0]), 345.0), (to_img(sgn * m.FRONT_SIDE_WINDOW_LATERAL_PX[1]), 345.0), (to_img(sgn * m.FRONT_SIDE_WINDOW_LATERAL_PX[1]), 385.5), (to_img(sgn * m.FRONT_SIDE_WINDOW_LATERAL_PX[0]), 385.5)], ORA, 1, True) c.line([(to_img(-m.FRONT_WINDOW["half_top_px"]), m.FRONT_WINDOW["y_top"]), (to_img(m.FRONT_WINDOW["half_top_px"]), m.FRONT_WINDOW["y_top"])], ORA, 2) c.line([(to_img(-m.FRONT_WINDOW["half_bot_px"]), m.FRONT_WINDOW["y_bot"]), (to_img(m.FRONT_WINDOW["half_bot_px"]), m.FRONT_WINDOW["y_bot"])], ORA, 2) else: du = m.DUCT for r in (du["r_out"], du["r_inner"], du["r_hub"]): c.circle(to_img(0), du["y_c"], r / m.KB, ORA, 2) for a in du["stator_deg"]: t = math.radians(a) c.line([(to_img(du["r_hub"] * math.cos(t)), du["y_c"] - du["r_hub"] * math.sin(t)), (to_img(du["r_inner"] * math.cos(t)), du["y_c"] - du["r_inner"] * math.sin(t))], ORA, 2) sk = m.SKEG c.line([(to_img(0), 421.0), (to_img(0), 444.5)], ORA, 3) c.save("trace_%s.png" % view) fb("front", (130, 255, 335, 485), m.FRONT_SIL, lambda lat: m.XC_FRONT + lat / m.KF) fb("back", (1105, 255, 1310, 485), m.BACK_SIL, lambda lat: m.XC_BACK - lat / m.KB) # ---------------- SECTION A-A: the hull section at the collar from the loft tables, mapped into the section's pixels c = Canvas((1120, 25, 1295, 210)) xm = m.CL_IMG W, ytop, yw, ybot = interp(m.HULL_HALF, xm), interp(m.HULL_TOP, xm), interp(m.HULL_YW, xm), interp(m.HULL_BOT, xm) sec = section(W, ytop, yw, ybot, m.NU_MID, m.NL_MID) to_x = lambda lat: m.SEC_C[0] - lat / m.SEC_KX to_y = lambda ys: m.SEC_C[1] + (ys - m.SEC_Y_SIDE) / m.SEC_KY for sgn in (-1, 1): c.line([(to_x(sgn * l), to_y(y)) for l, y in sec], YEL, 2) lat = m.CABIN["seat_lateral"] / m.S half = m.CABIN["seat_width"] / 2 / m.S yb = lambda z: to_y(m.Z0 - z / m.S) c.rect((to_x(sgn * (lat - half)), yb(m.CABIN["seat_back_top_z"]), to_x(sgn * (lat + half)), yb(m.CABIN["cushion_front_z"])), ORA, 2) c.circle(to_x(sgn * lat), yb(m.CABIN["eye_z"]), 2, WHT, 2) c.line([(1150, to_y(m.Z0 - m.CABIN["deck_top_z"] / m.S)), (1262, to_y(m.Z0 - m.CABIN["deck_top_z"] / m.S))], CYA, 2) c.save("trace_section.png") print("written trace_side/top/front/back/section.png")