mirror of
https://github.com/aculix/knotchy.git
synced 2026-10-27 07:15:53 +00:00
Initial release
This commit is contained in:
commit
4b67728cce
108 files changed
+13310
No files matched your search
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generates the knot icons' geometry; run via scripts/make-icons.sh."""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
NAVY = "#132441"
|
||||
CORAL = "#FF8A5B"
|
||||
|
||||
|
||||
def trefoil(cx, cy, size, gap):
|
||||
n = 1200
|
||||
pts = []
|
||||
for i in range(n):
|
||||
t = 2 * math.pi * i / n
|
||||
pts.append((math.sin(t) + 2 * math.sin(2 * t), -(math.cos(t) - 2 * math.cos(2 * t))))
|
||||
xs = [p[0] for p in pts]
|
||||
ys = [p[1] for p in pts]
|
||||
mx, my = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2
|
||||
span = max(max(xs) - min(xs), max(ys) - min(ys))
|
||||
p = [(cx + (x - mx) / span * size, cy + (y - my) / span * size) for x, y in pts]
|
||||
|
||||
# Self-crossings: points far apart along the curve that land on each other.
|
||||
cands = []
|
||||
for i in range(n):
|
||||
for j in range(i + n // 6, n):
|
||||
if j - i > n - n // 6:
|
||||
continue
|
||||
d = (p[i][0] - p[j][0]) ** 2 + (p[i][1] - p[j][1]) ** 2
|
||||
if d < (size * 0.004) ** 2:
|
||||
cands.append((d, i, j))
|
||||
cands.sort()
|
||||
crossings = []
|
||||
for _, i, j in cands:
|
||||
far = lambda a, b: min(abs(a - b) % n, n - abs(a - b) % n) > 40
|
||||
if all(far(i, a) and far(j, b) for a, b in crossings):
|
||||
crossings.append((i, j))
|
||||
if len(crossings) == 3:
|
||||
break
|
||||
assert len(crossings) == 3, crossings
|
||||
|
||||
# Alternate crossings go under; hide points within `gap` (straight-line) of each.
|
||||
visits = sorted([i for i, _ in crossings] + [j for _, j in crossings])
|
||||
under = visits[1::2]
|
||||
hidden = set()
|
||||
for u in under:
|
||||
for d in range(-n // 10, n // 10 + 1):
|
||||
k = (u + d) % n
|
||||
if math.dist(p[k], p[u]) < gap:
|
||||
hidden.add(k)
|
||||
|
||||
strands, cur = [], []
|
||||
start = next(i for i in range(n) if i in hidden)
|
||||
for k in range(1, n + 1):
|
||||
i = (start + k) % n
|
||||
if i in hidden:
|
||||
if len(cur) > 1:
|
||||
strands.append(cur)
|
||||
cur = []
|
||||
else:
|
||||
cur.append(p[i])
|
||||
if len(cur) > 1:
|
||||
strands.append(cur)
|
||||
assert len(strands) == 3
|
||||
return [s[::3] + [s[-1]] for s in strands]
|
||||
|
||||
|
||||
def path(strand):
|
||||
return "M" + " L".join(f"{x:.2f},{y:.2f}" for x, y in strand)
|
||||
|
||||
|
||||
# Each gap is half the crossing strand's width, plus the round cap, plus clear space.
|
||||
ICON_STROKE, GLYPH_STROKE = 8.5, 1.9
|
||||
icon = trefoil(50, 51, 62, ICON_STROKE + 1.3)
|
||||
glyph = trefoil(9, 9.3, 14.2, GLYPH_STROKE + 0.6)
|
||||
|
||||
os.makedirs(os.path.join(ROOT, "assets"), exist_ok=True)
|
||||
json.dump({"icon": icon, "iconStroke": ICON_STROKE, "glyph": glyph, "glyphStroke": GLYPH_STROKE,
|
||||
"navy": NAVY, "coral": CORAL},
|
||||
open(os.path.join(ROOT, "scripts/icons/knot.json"), "w"))
|
||||
|
||||
strokes = "".join(f'<path d="{path(s)}"/>' for s in icon)
|
||||
open(os.path.join(ROOT, "assets/knotchy-icon.svg"), "w").write(
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">'
|
||||
f'<rect width="100" height="100" rx="22.5" fill="{NAVY}"/>'
|
||||
f'<g fill="none" stroke="{CORAL}" stroke-width="8.5" stroke-linecap="round" stroke-linejoin="round">{strokes}</g></svg>\n')
|
||||
open(os.path.join(ROOT, "assets/knotchy-mark.svg"), "w").write(
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="15 18 70 66">'
|
||||
f'<g fill="none" stroke="{CORAL}" stroke-width="8.5" stroke-linecap="round" stroke-linejoin="round">{strokes}</g></svg>\n')
|
||||
|
||||
points = ",\n".join(
|
||||
" [" + ", ".join(f"CGPoint(x: {x:.2f}, y: {y:.2f})" for x, y in s) + "]" for s in glyph)
|
||||
open(os.path.join(ROOT, "Sources/Knotchy/KnotGlyph.swift"), "w").write(f'''// Generated by scripts/icons/knot.py; run scripts/make-icons.sh to change it.
|
||||
import AppKit
|
||||
|
||||
enum KnotGlyph {{
|
||||
static let strands: [[CGPoint]] = [
|
||||
{points},
|
||||
]
|
||||
|
||||
static let menuBarImage: NSImage = {{
|
||||
let image = NSImage(size: NSSize(width: 18, height: 18), flipped: true) {{ _ in
|
||||
NSColor.black.setStroke()
|
||||
for strand in strands {{
|
||||
let path = NSBezierPath()
|
||||
path.move(to: strand[0])
|
||||
for point in strand.dropFirst() {{ path.line(to: point) }}
|
||||
path.lineWidth = 1.9
|
||||
path.lineCapStyle = .round
|
||||
path.lineJoinStyle = .round
|
||||
path.stroke()
|
||||
}}
|
||||
return true
|
||||
}}
|
||||
image.isTemplate = true
|
||||
image.accessibilityDescription = "Knotchy"
|
||||
return image
|
||||
}}()
|
||||
}}
|
||||
''')
|
||||
print("knot: 3 strands; wrote knot.json, assets/*.svg, Sources/Knotchy/KnotGlyph.swift")
|
||||
Reference in new issue
Block a user