# Builds assets/Plaque.obj (+ .mtl) and one STL per colour: a single printable plaque with the Singe # logotype raised on a backing plate and the dragon lying above the word as a bas-relief (its top # half, every part filled down to the plate). It prints face up with no supports: the plate is the # XY plane and Z is up. Five bodies: dark, champagne and glass tiles, the frame (dragon cores, walls, # grout and letter walls) and the plate, for a four-colour printer with the plate sharing a filament. # Usage: python3 util/plaqueModel.py pieces.json assets/SingeText.jpeg assets/Plaque.obj [--width 160] [--gap 10] [--stl assets] [--check] import argparse import json import numpy as np from objWriter import ObjWriterT, DRAGON_MATERIALS from outline import traceBoundary, simplifyClean, signedArea from dragonModel import DragonT from textModel import LogoT, UPSCALE, PLATE_MARGIN, plateMask, writeOutputs parser = argparse.ArgumentParser() parser.add_argument('pieces') parser.add_argument('logo') parser.add_argument('out') parser.add_argument('--scale', type=float, default=0.01, help='model units per image pixel') parser.add_argument('--gap', type=float, default=10.0, help='space between the letter tops and the dragon, image pixels') parser.add_argument('--stl', default=None, help='directory to write one STL per body into') parser.add_argument('--width', type=float, default=160.0, help='width of the plaque in the STL files, millimetres') parser.add_argument('--check', action='store_true', help='voxelise the model and count separate pieces') args = parser.parse_args() scale = args.scale logo = LogoT(args.logo, scale) dragon = DragonT(json.load(open(args.pieces)), scale, printable=True, relief=True) # Layout: the word rests on Y = 0, the dragon's feet a gap above the tallest letter, both centred on X. letterTop = max(logo.toModel(x, y, 0.0)[1] for ring in logo.letters for x, y in ring) lift = np.array([0.0, letterTop + args.gap * scale, 0.0]) def raise_(p): return p + lift # One plate under both: compose the logo ink and the dragon footprint on a common canvas at one # pixel per model pixel, then outline it the way the logo plate is outlined. canvasW = 2400 canvasH = 2600 cx = canvasW // 2 cy = canvasH - 400 canvas = np.zeros((canvasH, canvasW), dtype=bool) def paste(mask, shiftX, shiftY): h, w = mask.shape canvas[shiftY:shiftY + h, shiftX:shiftX + w] |= mask ink = logo.ink.reshape(logo.ink.shape[0] // UPSCALE, UPSCALE, logo.ink.shape[1] // UPSCALE, UPSCALE).max(axis=(1, 3)) paste(ink, int(round(cx - (logo.x0 + logo.x1) / 2)), int(round(cy - logo.y1))) paste(dragon.footprint(), int(round(cx - (dragon.x0 + dragon.width / 2))), int(round(cy - dragon.y1 - lift[1] / scale))) plate = plateMask(canvas, PLATE_MARGIN, 20) ring = simplifyClean(traceBoundary(plate), 2.0) # Canvas pixels back to logo image pixels, so the logo's own mapping places the plate. ring = ring - np.array([cx - (logo.x0 + logo.x1) / 2, cy - logo.y1]) xy = np.array([logo.toModel(x, y, 0.0)[:2] for x, y in ring]) if signedArea(xy) < 0: ring = ring[::-1] writer = ObjWriterT() logo.buildPlateRing(writer, ring, 0.0) logo.buildLetters(writer, True, 0.0) dragon.build(writer, raise_) bodies = {name: name for name in ('dark', 'champagne', 'glass', 'frame', 'plate')} materials = {name: DRAGON_MATERIALS[name] for name in bodies} plateWidth = (ring[:, 0].max() - ring[:, 0].min()) * scale print('%s: %d vertices, %d faces; plate %.2f x %.2f units' % (args.out, len(writer.vertices), writer.faceCount, plateWidth, (ring[:, 1].max() - ring[:, 1].min()) * scale)) header = ['Singe plaque: the logotype and the dragon in relief on one plate, built by util/plaqueModel.py', 'Units: %g per image pixel; plate in the XY plane resting on Y = 0, Z up (print orientation), centred on X = 0' % scale] writeOutputs(writer, args.out, header, materials, bodies, args.stl, args.width, plateWidth, args.check, scale)