75 lines
3.9 KiB
Python
75 lines
3.9 KiB
Python
# 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)
|