mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 05:09:20 +02:00
420 lines
13 KiB
Python
420 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
# draws the icons of the scenery editor into editor_icons.png, next to this script: white shapes on a transparent ground,
|
|
# 64 x 64 pixels each, 8 in a row, in the order of editor_icons::icon (editor/editorIcons.hpp).
|
|
# the editor tints them as it draws them; the png is built into the executable by CMake, see CMakeLists.txt.
|
|
# needs Pillow: python3 editor_icons.py
|
|
import math
|
|
import os
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
CELL = 64
|
|
SUPER = 4 # drawn this many times bigger, then scaled down for smooth edges
|
|
S = CELL * SUPER
|
|
W = 18 # width of the lines, at the bigger size
|
|
COLUMNS = 8
|
|
|
|
|
|
def p(x, y):
|
|
# coordinates given on a 64 x 64 grid
|
|
return (x * SUPER, y * SUPER)
|
|
|
|
|
|
def line(d, points, width=W):
|
|
pts = [p(*q) for q in points]
|
|
d.line(pts, fill=255, width=width, joint="curve")
|
|
for q in (pts[0], pts[-1]):
|
|
r = width / 2
|
|
d.ellipse((q[0] - r, q[1] - r, q[0] + r, q[1] + r), fill=255)
|
|
|
|
|
|
def poly(d, points, outline=False, width=W):
|
|
pts = [p(*q) for q in points]
|
|
if outline:
|
|
line(d, points + [points[0]], width)
|
|
else:
|
|
d.polygon(pts, fill=255)
|
|
|
|
|
|
def circle(d, x, y, r, outline=False, width=W):
|
|
box = (p(x - r, y - r), p(x + r, y + r))
|
|
if outline:
|
|
d.ellipse(box, outline=255, width=width)
|
|
else:
|
|
d.ellipse(box, fill=255)
|
|
|
|
|
|
def rect(d, x0, y0, x1, y1, outline=False, width=W, radius=0):
|
|
box = (p(x0, y0), p(x1, y1))
|
|
if outline:
|
|
d.rounded_rectangle(box, radius=radius * SUPER, outline=255, width=width)
|
|
else:
|
|
d.rounded_rectangle(box, radius=radius * SUPER, fill=255)
|
|
|
|
|
|
def arrowhead(d, x, y, angle, size=9):
|
|
# filled head pointing at angle (degrees, 0 = right, 90 = down)
|
|
a = math.radians(angle)
|
|
tip = (x + math.cos(a) * size * 0.6, y + math.sin(a) * size * 0.6)
|
|
left = (x + math.cos(a + 2.5) * size, y + math.sin(a + 2.5) * size)
|
|
right = (x + math.cos(a - 2.5) * size, y + math.sin(a - 2.5) * size)
|
|
poly(d, [tip, left, right])
|
|
|
|
|
|
def arc(d, x, y, r, start, end, width=W):
|
|
box = (p(x - r, y - r), p(x + r, y + r))
|
|
d.arc(box, start, end, fill=255, width=width)
|
|
|
|
|
|
def dashed(d, a, b, dash=6, gap=4, width=W):
|
|
length = math.hypot(b[0] - a[0], b[1] - a[1])
|
|
if length == 0:
|
|
return
|
|
ux, uy = (b[0] - a[0]) / length, (b[1] - a[1]) / length
|
|
t = 0.0
|
|
while t < length:
|
|
e = min(t + dash, length)
|
|
line(d, [(a[0] + ux * t, a[1] + uy * t), (a[0] + ux * e, a[1] + uy * e)], width)
|
|
t = e + gap
|
|
|
|
|
|
# the icons, in the order of editor_icons::icon
|
|
|
|
|
|
def select(d):
|
|
poly(d, [(16, 8), (16, 50), (26, 40), (33, 56), (40, 53), (33, 37), (47, 37)])
|
|
|
|
|
|
def insert(d):
|
|
# a box, with a plus over its corner
|
|
poly(d, [(8, 26), (28, 16), (48, 26), (28, 36)], outline=True)
|
|
line(d, [(8, 26), (8, 46), (28, 56), (48, 46), (48, 26)])
|
|
line(d, [(28, 36), (28, 56)])
|
|
circle(d, 48, 14, 11)
|
|
|
|
|
|
def brush(d):
|
|
line(d, [(52, 8), (30, 32)], W + 4)
|
|
poly(d, [(26, 30), (34, 38), (24, 52), (8, 56), (12, 42)])
|
|
for x, y in ((44, 40), (52, 30), (50, 50)):
|
|
circle(d, x, y, 3)
|
|
|
|
|
|
def area_fill(d):
|
|
outline = [(8, 18), (34, 8), (56, 24), (50, 54), (16, 52)]
|
|
for i in range(len(outline)):
|
|
dashed(d, outline[i], outline[(i + 1) % len(outline)], 6, 5, W - 4)
|
|
for x, y in ((24, 38), (38, 30), (36, 44)):
|
|
poly(d, [(x, y - 9), (x + 6, y + 4), (x - 6, y + 4)])
|
|
|
|
|
|
def copy_to_bank(d):
|
|
rect(d, 18, 6, 46, 26, outline=True, radius=3)
|
|
line(d, [(32, 26), (32, 40)])
|
|
arrowhead(d, 32, 42, 90, 12)
|
|
line(d, [(8, 40), (8, 56), (56, 56), (56, 40)])
|
|
|
|
|
|
def translate(d):
|
|
line(d, [(32, 10), (32, 54)])
|
|
line(d, [(10, 32), (54, 32)])
|
|
arrowhead(d, 32, 8, 270, 13)
|
|
arrowhead(d, 32, 56, 90, 13)
|
|
arrowhead(d, 8, 32, 180, 13)
|
|
arrowhead(d, 56, 32, 0, 13)
|
|
|
|
|
|
def rotate(d):
|
|
# turning clockwise, the gap at the top
|
|
arc(d, 32, 34, 21, 300, 225)
|
|
a = math.radians(225)
|
|
arrowhead(d, 32 + math.cos(a) * 21, 34 + math.sin(a) * 21, 315, 15)
|
|
circle(d, 32, 34, 4)
|
|
|
|
|
|
def scale(d):
|
|
rect(d, 8, 30, 34, 56, radius=2)
|
|
for a, b in (((8, 8), (56, 8)), ((56, 8), (56, 56)), ((8, 8), (8, 22)), ((42, 56), (56, 56))):
|
|
dashed(d, a, b, 6, 5, W - 6)
|
|
line(d, [(30, 34), (48, 16)])
|
|
arrowhead(d, 50, 14, 315, 14)
|
|
|
|
|
|
def local_space(d):
|
|
# the axes turned with the model
|
|
o = (22, 42)
|
|
for angle, length in ((-20, 34), (-110, 30), (150, 18)):
|
|
a = math.radians(angle)
|
|
e = (o[0] + math.cos(a) * length, o[1] + math.sin(a) * length)
|
|
line(d, [o, e], W - 2)
|
|
arrowhead(d, e[0], e[1], angle, 11)
|
|
circle(d, o[0], o[1], 5)
|
|
|
|
|
|
def ortho(d):
|
|
# the plan seen from over it: a grid without perspective
|
|
rect(d, 8, 8, 56, 56, outline=True, width=W - 2)
|
|
for t in (24, 40):
|
|
line(d, [(t, 8), (t, 56)], W - 8)
|
|
line(d, [(8, t), (56, t)], W - 8)
|
|
|
|
|
|
def perspective(d):
|
|
# the grid going away
|
|
poly(d, [(20, 14), (44, 14), (58, 54), (6, 54)], outline=True, width=W - 2)
|
|
line(d, [(28, 14), (24, 54)], W - 8)
|
|
line(d, [(36, 14), (40, 54)], W - 8)
|
|
line(d, [(15, 30), (49, 30)], W - 8)
|
|
|
|
|
|
def sculpt(d):
|
|
poly(d, [(4, 56), (22, 26), (32, 38), (42, 22), (60, 56)])
|
|
line(d, [(50, 18), (50, 4)], W - 4)
|
|
arrowhead(d, 50, 3, 270, 11)
|
|
|
|
|
|
def smooth(d):
|
|
pts = [(x, 40 - 12 * math.sin((x - 6) / 52 * math.pi)) for x in range(6, 59, 2)]
|
|
line(d, pts)
|
|
line(d, [(6, 54), (58, 54)], W - 6)
|
|
circle(d, 32, 28, 14, outline=True, width=W - 8)
|
|
|
|
|
|
def chunks(d):
|
|
for x, y in ((6, 6), (33, 6), (6, 33)):
|
|
rect(d, x, y, x + 25, y + 25, radius=3)
|
|
rect(d, 33, 33, 58, 58, outline=True, width=W - 6, radius=3)
|
|
line(d, [(45.5, 39), (45.5, 52)], W - 8)
|
|
line(d, [(39, 45.5), (52, 45.5)], W - 8)
|
|
|
|
|
|
def orthophoto(d):
|
|
rect(d, 6, 10, 58, 54, outline=True, radius=4)
|
|
poly(d, [(10, 50), (26, 28), (36, 40), (42, 34), (54, 50)])
|
|
circle(d, 44, 21, 5)
|
|
|
|
|
|
def road(d):
|
|
line(d, [(24, 8), (8, 56)])
|
|
line(d, [(40, 8), (56, 56)])
|
|
dashed(d, (32, 8), (32, 56), 8, 6, W - 4)
|
|
|
|
|
|
def place(d):
|
|
# a pin standing on the place
|
|
poly(d, [(32, 58), (18, 34), (46, 34)])
|
|
circle(d, 32, 26, 15)
|
|
|
|
|
|
def lanes(d):
|
|
for x in (14, 50):
|
|
line(d, [(x, 6), (x, 58)], W - 4)
|
|
dashed(d, (32, 6), (32, 58), 7, 6, W - 6)
|
|
line(d, [(23, 52), (23, 22)], W - 6)
|
|
arrowhead(d, 23, 18, 270, 10)
|
|
line(d, [(41, 12), (41, 42)], W - 6)
|
|
arrowhead(d, 41, 46, 90, 10)
|
|
|
|
|
|
def lay_track(d):
|
|
# two rails on sleepers, going up to the right
|
|
for t in range(0, 6):
|
|
x, y = 10 + t * 9, 54 - t * 9
|
|
line(d, [(x - 8, y - 8), (x + 8, y + 8)], W - 8)
|
|
line(d, [(4, 50), (50, 4)], W - 4)
|
|
line(d, [(14, 60), (60, 14)], W - 4)
|
|
|
|
|
|
def switch(d):
|
|
line(d, [(18, 60), (18, 4)], W - 2)
|
|
line(d, [(34, 60), (34, 4)], W - 2)
|
|
pts = [(18 + 30 * (1 - math.cos(t / 20 * math.pi / 2)), 52 - t * 2.4) for t in range(0, 21)]
|
|
line(d, pts, W - 2)
|
|
pts = [(34 + 22 * (1 - math.cos(t / 20 * math.pi / 2)), 60 - t * 2.4) for t in range(0, 21)]
|
|
line(d, pts, W - 2)
|
|
|
|
|
|
def straight(d):
|
|
line(d, [(12, 52), (52, 12)])
|
|
rect(d, 4, 44, 20, 60, radius=2)
|
|
rect(d, 44, 4, 60, 20, radius=2)
|
|
|
|
|
|
def curve(d):
|
|
arc(d, 56, 56, 44, 180, 270)
|
|
dashed(d, (56, 56), (25, 25), 5, 4, W - 8)
|
|
circle(d, 12, 56, 6)
|
|
circle(d, 56, 12, 6)
|
|
circle(d, 25, 25, 6)
|
|
|
|
|
|
def signal(d):
|
|
line(d, [(32, 34), (32, 60)], W - 2)
|
|
line(d, [(20, 60), (44, 60)], W - 2)
|
|
rect(d, 20, 4, 44, 38, radius=10)
|
|
|
|
|
|
def objects(d):
|
|
# a post by the track with a board on it
|
|
line(d, [(24, 30), (24, 60)], W - 2)
|
|
rect(d, 10, 6, 38, 30, radius=3)
|
|
line(d, [(42, 60), (60, 60)], W - 6)
|
|
line(d, [(48, 52), (60, 52)], W - 6)
|
|
|
|
|
|
def vehicle(d):
|
|
poly(d, [(4, 22), (50, 22), (60, 32), (60, 46), (4, 46)])
|
|
circle(d, 16, 52, 6)
|
|
circle(d, 46, 52, 6)
|
|
line(d, [(4, 58), (60, 58)], W - 8)
|
|
|
|
|
|
def profile(d):
|
|
pts = [(6, 46), (22, 30), (38, 36), (58, 14)]
|
|
line(d, pts)
|
|
for q in pts:
|
|
circle(d, q[0], q[1], 5)
|
|
line(d, [(6, 58), (58, 58)], W - 8)
|
|
|
|
|
|
def speed(d):
|
|
arc(d, 32, 40, 24, 180, 360)
|
|
for a in (200, 240, 280, 320):
|
|
r = math.radians(a)
|
|
line(d, [(32 + math.cos(r) * 15, 40 + math.sin(r) * 15), (32 + math.cos(r) * 20, 40 + math.sin(r) * 20)], W - 8)
|
|
line(d, [(32, 40), (46, 24)], W - 2)
|
|
circle(d, 32, 40, 6)
|
|
|
|
|
|
def joints(d):
|
|
line(d, [(4, 26), (26, 26)])
|
|
line(d, [(38, 26), (60, 26)])
|
|
line(d, [(4, 40), (26, 40)])
|
|
line(d, [(38, 44), (60, 44)])
|
|
rect(d, 28, 14, 36, 54, outline=True, width=W - 10, radius=2)
|
|
|
|
|
|
def infra(d):
|
|
# a catenary mast with its arm and the wire hanging from it
|
|
line(d, [(14, 60), (14, 6)], W + 2)
|
|
line(d, [(14, 12), (54, 12)], W - 4)
|
|
line(d, [(14, 26), (46, 12)], W - 8)
|
|
line(d, [(44, 12), (44, 28)], W - 8)
|
|
pts = [(x, 30 + 0.012 * (x - 36) ** 2) for x in range(14, 61, 2)]
|
|
line(d, pts, W - 6)
|
|
|
|
|
|
def gauge(d):
|
|
# the outline of the structure gauge over the rails
|
|
line(d, [(12, 52), (12, 22), (22, 8), (42, 8), (52, 22), (52, 52)], W - 4)
|
|
line(d, [(4, 56), (60, 56)], W - 6)
|
|
for x in (24, 40):
|
|
rect(d, x - 3, 48, x + 3, 56)
|
|
|
|
|
|
def turntable(d):
|
|
# the pit with its bridge, and the tracks leading out of it
|
|
circle(d, 32, 32, 20, outline=True, width=W - 4)
|
|
line(d, [(18, 46), (46, 18)], W)
|
|
for a in (-90, -30, 30):
|
|
r = math.radians(a)
|
|
line(d, [(32 + math.cos(r) * 24, 32 + math.sin(r) * 24), (32 + math.cos(r) * 31, 32 + math.sin(r) * 31)], W - 4)
|
|
|
|
|
|
# the fields of work of the editor, in the row over the toolbar
|
|
|
|
|
|
def work_surroundings(d):
|
|
# a tree and a house by it
|
|
poly(d, [(18, 4), (32, 30), (4, 30)])
|
|
poly(d, [(18, 14), (34, 44), (2, 44)])
|
|
line(d, [(18, 44), (18, 60)], W - 2)
|
|
poly(d, [(36, 34), (49, 22), (62, 34)])
|
|
rect(d, 39, 34, 59, 60)
|
|
|
|
|
|
def work_tracks(d):
|
|
# the track going away, rails on sleepers
|
|
line(d, [(8, 60), (26, 4)], W - 4)
|
|
line(d, [(56, 60), (38, 4)], W - 4)
|
|
for y, half in ((54, 28), (40, 22), (28, 17), (18, 13), (10, 10)):
|
|
line(d, [(32 - half, y), (32 + half, y)], W - 8 if y > 20 else W - 10)
|
|
|
|
|
|
def work_roads(d):
|
|
# a car seen from its side
|
|
poly(d, [(4, 44), (4, 34), (14, 30), (22, 18), (44, 18), (52, 30), (60, 34), (60, 44)])
|
|
circle(d, 17, 46, 8)
|
|
circle(d, 47, 46, 8)
|
|
|
|
|
|
def work_terrain(d):
|
|
# the mesh of the ground over a hill
|
|
def at(u, v):
|
|
# u across 0..1, v from the back (0) to the front (1)
|
|
half = 18 + 12 * v
|
|
x = 32 + (u - 0.5) * 2 * half
|
|
hill = 18 * math.exp(-((u - 0.5) ** 2) / 0.07 - ((v - 0.35) ** 2) / 0.15)
|
|
return (x, 24 + 34 * v - hill)
|
|
steps = [i / 12 for i in range(13)]
|
|
for v in (0.0, 0.33, 0.66, 1.0):
|
|
line(d, [at(u, v) for u in steps], W - 6)
|
|
for u in (0.0, 0.33, 0.66, 1.0):
|
|
line(d, [at(u, v) for v in steps], W - 6)
|
|
|
|
|
|
ICONS = [select, insert, brush, area_fill, copy_to_bank, translate, rotate, scale,
|
|
local_space, ortho, perspective, sculpt, smooth, chunks, orthophoto, road,
|
|
place, lanes, lay_track, switch, straight, curve, signal, objects,
|
|
vehicle, profile, speed, joints, infra, gauge, turntable, work_surroundings,
|
|
work_tracks, work_roads, work_terrain]
|
|
|
|
|
|
def cutouts(name, d):
|
|
# the parts drawn in the colour of the ground: the plus of the insert, the hole of the pin, the lights of the signal
|
|
if name == "insert":
|
|
line(d, [(48, 8), (48, 20)], W - 8)
|
|
line(d, [(42, 14), (54, 14)], W - 8)
|
|
elif name == "place":
|
|
circle(d, 32, 26, 6)
|
|
elif name == "signal":
|
|
circle(d, 32, 14, 5)
|
|
circle(d, 32, 28, 5)
|
|
elif name == "vehicle":
|
|
rect(d, 10, 27, 20, 35)
|
|
rect(d, 26, 27, 36, 35)
|
|
rect(d, 42, 27, 50, 35)
|
|
elif name == "work_surroundings":
|
|
# the door and the gap between the crowns of the tree
|
|
rect(d, 46, 46, 52, 60)
|
|
line(d, [(9, 31), (27, 31)], 8)
|
|
elif name == "work_roads":
|
|
# the windows, and the gaps around the wheels
|
|
poly(d, [(25, 22), (31, 22), (31, 30), (19, 30)])
|
|
poly(d, [(35, 22), (42, 22), (47, 30), (35, 30)])
|
|
circle(d, 17, 46, 11, outline=True, width=12)
|
|
circle(d, 47, 46, 11, outline=True, width=12)
|
|
circle(d, 17, 46, 3)
|
|
circle(d, 47, 46, 3)
|
|
|
|
|
|
def main():
|
|
rows = (len(ICONS) + COLUMNS - 1) // COLUMNS
|
|
atlas = Image.new("RGBA", (COLUMNS * CELL, rows * CELL), (255, 255, 255, 0))
|
|
for index, draw in enumerate(ICONS):
|
|
mask = Image.new("L", (S, S), 0)
|
|
d = ImageDraw.Draw(mask)
|
|
draw(d)
|
|
holes = Image.new("L", (S, S), 0)
|
|
cutouts(draw.__name__, ImageDraw.Draw(holes))
|
|
mask.paste(0, (0, 0), holes)
|
|
mask = mask.resize((CELL, CELL), Image.LANCZOS)
|
|
icon = Image.new("RGBA", (CELL, CELL), (255, 255, 255, 0))
|
|
icon.putalpha(mask)
|
|
atlas.paste(icon, ((index % COLUMNS) * CELL, (index // COLUMNS) * CELL))
|
|
atlas.save(os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor_icons.png"), optimize=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|