@@ -1,461 +0,0 @@
|
||||
"""One-shot generator for BPMN.puml geometric sprites. Run from repo root."""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
N = 48
|
||||
HEX = "0123456789ABCDEF"
|
||||
SS = 2
|
||||
|
||||
|
||||
def clamp01(v: float) -> float:
|
||||
return 0.0 if v < 0 else 1.0 if v > 1 else v
|
||||
|
||||
|
||||
def mix(grid: list[list[float]], x: int, y: int, v: float) -> None:
|
||||
if 0 <= x < N and 0 <= y < N:
|
||||
grid[y][x] = max(grid[y][x], clamp01(v))
|
||||
|
||||
|
||||
def ring(d: float, r: float, hw: float) -> float:
|
||||
return clamp01(1.0 - abs(d - r) / hw)
|
||||
|
||||
|
||||
def disk(d: float, r: float, hw: float = 0.65) -> float:
|
||||
if d <= r - hw:
|
||||
return 1.0
|
||||
if d >= r + hw:
|
||||
return 0.0
|
||||
return clamp01((r + hw - d) / (2 * hw))
|
||||
|
||||
|
||||
def line_cov(px: float, py: float, x0: float, y0: float, x1: float, y1: float, hw: float) -> float:
|
||||
dx, dy = x1 - x0, y1 - y0
|
||||
len2 = dx * dx + dy * dy
|
||||
if len2 < 1e-6:
|
||||
return disk(math.hypot(px - x0, py - y0), 0, hw)
|
||||
t = clamp01(((px - x0) * dx + (py - y0) * dy) / len2)
|
||||
qx, qy = x0 + t * dx, y0 + t * dy
|
||||
return disk(math.hypot(px - qx, py - qy), 0, hw)
|
||||
|
||||
|
||||
def diamond_m(px: float, py: float, cx: float, cy: float) -> float:
|
||||
return abs(px - cx) + abs(py - cy)
|
||||
|
||||
|
||||
def sample_event(
|
||||
px: float,
|
||||
py: float,
|
||||
*,
|
||||
kind: str,
|
||||
dashed: bool,
|
||||
) -> float:
|
||||
cx = cy = (N - 1) / 2
|
||||
d = math.hypot(px - cx, py - cy)
|
||||
v = 0.0
|
||||
if kind == "start":
|
||||
v = max(v, ring(d, 18.2, 1.15))
|
||||
elif kind == "inter":
|
||||
v = max(v, ring(d, 18.4, 1.05))
|
||||
v = max(v, ring(d, 14.6, 1.05))
|
||||
elif kind == "end":
|
||||
v = max(v, ring(d, 17.6, 2.55))
|
||||
elif kind == "endfill":
|
||||
v = max(v, ring(d, 17.6, 2.55))
|
||||
v = max(v, disk(d, 7.2, 0.7))
|
||||
if dashed and v > 0:
|
||||
ang = (math.atan2(py - cy, px - cx) + math.pi) / (2 * math.pi)
|
||||
if int(ang * 18) % 2 == 1:
|
||||
v *= 0.08
|
||||
return v
|
||||
|
||||
|
||||
def icon_msg(px: float, py: float, cx: float, cy: float, filled: bool) -> float:
|
||||
# envelope 16x10 around center
|
||||
x0, y0, x1, y1 = cx - 8, cy - 5, cx + 8, cy + 5
|
||||
v = 0.0
|
||||
# outer rect
|
||||
inside = (x0 + 0.8 <= px <= x1 - 0.8) and (y0 + 0.8 <= py <= y1 - 0.8)
|
||||
on_border = (
|
||||
(abs(px - x0) < 1.1 or abs(px - x1) < 1.1) and (y0 - 0.4 <= py <= y1 + 0.4)
|
||||
) or (
|
||||
(abs(py - y0) < 1.1 or abs(py - y1) < 1.1) and (x0 - 0.4 <= px <= x1 + 0.4)
|
||||
)
|
||||
if filled:
|
||||
if inside or on_border:
|
||||
v = 1.0
|
||||
# inverted flap (white cut) approximated by not drawing center of flap
|
||||
v = max(v, line_cov(px, py, x0, y0, cx, cy + 1.5, 0.9))
|
||||
v = max(v, line_cov(px, py, x1, y0, cx, cy + 1.5, 0.9))
|
||||
return v
|
||||
v = max(v, 1.0 if on_border else 0.0)
|
||||
v = max(v, line_cov(px, py, x0, y0, cx, cy + 1.8, 0.95))
|
||||
v = max(v, line_cov(px, py, x1, y0, cx, cy + 1.8, 0.95))
|
||||
return v
|
||||
|
||||
|
||||
def icon_timer(px: float, py: float, cx: float, cy: float) -> float:
|
||||
d = math.hypot(px - cx, py - cy)
|
||||
v = ring(d, 7.2, 1.0)
|
||||
v = max(v, line_cov(px, py, cx, cy, cx, cy - 5.2, 0.9))
|
||||
v = max(v, line_cov(px, py, cx, cy, cx + 3.6, cy, 0.9))
|
||||
return v
|
||||
|
||||
|
||||
def icon_error(px: float, py: float, cx: float, cy: float) -> float:
|
||||
pts = [
|
||||
(cx + 1, cy - 8),
|
||||
(cx - 2, cy - 1),
|
||||
(cx + 3, cy - 1),
|
||||
(cx - 1, cy + 8),
|
||||
(cx + 0.5, cy + 1.5),
|
||||
(cx - 3.5, cy + 1.5),
|
||||
]
|
||||
v = 0.0
|
||||
for a, b in zip(pts, pts[1:] + pts[:1]):
|
||||
v = max(v, line_cov(px, py, a[0], a[1], b[0], b[1], 1.0))
|
||||
return v
|
||||
|
||||
|
||||
def icon_signal(px: float, py: float, cx: float, cy: float, filled: bool) -> float:
|
||||
a, b, c = (cx, cy - 7.5), (cx - 7.2, cy + 6), (cx + 7.2, cy + 6)
|
||||
v = 0.0
|
||||
v = max(v, line_cov(px, py, *a, *b, 1.05))
|
||||
v = max(v, line_cov(px, py, *b, *c, 1.05))
|
||||
v = max(v, line_cov(px, py, *c, *a, 1.05))
|
||||
if filled:
|
||||
# barycentric fill
|
||||
def area(p, q, r):
|
||||
return (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0])
|
||||
|
||||
p = (px, py)
|
||||
a0 = area(a, b, c)
|
||||
if abs(a0) > 0.1:
|
||||
w1 = area(p, b, c) / a0
|
||||
w2 = area(a, p, c) / a0
|
||||
w3 = area(a, b, p) / a0
|
||||
if min(w1, w2, w3) >= -0.02:
|
||||
v = max(v, 1.0)
|
||||
return v
|
||||
|
||||
|
||||
def icon_esc(px: float, py: float, cx: float, cy: float) -> float:
|
||||
v = 0.0
|
||||
v = max(v, line_cov(px, py, cx, cy + 7, cx, cy - 2, 1.1))
|
||||
v = max(v, line_cov(px, py, cx, cy - 2, cx - 6, cy + 4, 1.05))
|
||||
v = max(v, line_cov(px, py, cx, cy - 2, cx + 6, cy + 4, 1.05))
|
||||
v = max(v, line_cov(px, py, cx - 6, cy + 4, cx + 6, cy + 4, 1.0))
|
||||
return v
|
||||
|
||||
|
||||
def icon_link(px: float, py: float, cx: float, cy: float) -> float:
|
||||
v = line_cov(px, py, cx - 8, cy, cx + 5, cy, 1.1)
|
||||
v = max(v, line_cov(px, py, cx + 5, cy, cx + 1, cy - 4, 1.05))
|
||||
v = max(v, line_cov(px, py, cx + 5, cy, cx + 1, cy + 4, 1.05))
|
||||
return v
|
||||
|
||||
|
||||
def icon_comp(px: float, py: float, cx: float, cy: float) -> float:
|
||||
v = 0.0
|
||||
for ox in (-4.2, 3.2):
|
||||
a, b, c = (cx + ox + 4, cy), (cx + ox - 3.5, cy - 6), (cx + ox - 3.5, cy + 6)
|
||||
v = max(v, line_cov(px, py, *a, *b, 1.0))
|
||||
v = max(v, line_cov(px, py, *a, *c, 1.0))
|
||||
v = max(v, line_cov(px, py, *b, *c, 1.0))
|
||||
return v
|
||||
|
||||
|
||||
def icon_cond(px: float, py: float, cx: float, cy: float) -> float:
|
||||
x0, y0, x1, y1 = cx - 6, cy - 8, cx + 6, cy + 8
|
||||
v = 0.0
|
||||
on = (
|
||||
(abs(px - x0) < 1.0 or abs(px - x1) < 1.0) and (y0 <= py <= y1)
|
||||
) or (
|
||||
(abs(py - y0) < 1.0 or abs(py - y1) < 1.0) and (x0 <= px <= x1)
|
||||
)
|
||||
if on:
|
||||
v = 1.0
|
||||
for yy in (cy - 3, cy, cy + 3):
|
||||
v = max(v, line_cov(px, py, cx - 3.5, yy, cx + 3.5, yy, 0.8))
|
||||
return v
|
||||
|
||||
|
||||
def icon_multi(px: float, py: float, cx: float, cy: float) -> float:
|
||||
r = 7.4
|
||||
pts = []
|
||||
for i in range(5):
|
||||
a = -math.pi / 2 + i * 2 * math.pi / 5
|
||||
pts.append((cx + r * math.cos(a), cy + r * math.sin(a)))
|
||||
v = 0.0
|
||||
for a, b in zip(pts, pts[1:] + pts[:1]):
|
||||
v = max(v, line_cov(px, py, *a, *b, 1.0))
|
||||
return v
|
||||
|
||||
|
||||
def icon_par_multi(px: float, py: float, cx: float, cy: float) -> float:
|
||||
v = ring(math.hypot(px - cx, py - cy), 7.4, 1.0)
|
||||
v = max(v, line_cov(px, py, cx, cy - 5, cx, cy + 5, 1.05))
|
||||
v = max(v, line_cov(px, py, cx - 5, cy, cx + 5, cy, 1.05))
|
||||
return v
|
||||
|
||||
|
||||
def icon_cancel(px: float, py: float, cx: float, cy: float) -> float:
|
||||
v = line_cov(px, py, cx - 6, cy - 6, cx + 6, cy + 6, 1.15)
|
||||
v = max(v, line_cov(px, py, cx + 6, cy - 6, cx - 6, cy + 6, 1.15))
|
||||
return v
|
||||
|
||||
|
||||
def icon_user(px: float, py: float, cx: float, cy: float) -> float:
|
||||
v = disk(math.hypot(px - cx, py - (cy - 3.2)), 3.1, 0.7)
|
||||
v = max(v, ring(math.hypot(px - cx, py - (cy - 3.2)), 3.1, 0.9))
|
||||
# shoulders
|
||||
v = max(v, line_cov(px, py, cx - 6, cy + 7, cx + 6, cy + 7, 1.1))
|
||||
v = max(v, line_cov(px, py, cx - 6, cy + 7, cx - 4, cy + 1.5, 1.0))
|
||||
v = max(v, line_cov(px, py, cx + 6, cy + 7, cx + 4, cy + 1.5, 1.0))
|
||||
return v
|
||||
|
||||
|
||||
def icon_service(px: float, py: float, cx: float, cy: float) -> float:
|
||||
v = ring(math.hypot(px - cx, py - cy), 4.2, 1.15)
|
||||
for i in range(8):
|
||||
a = i * math.pi / 4
|
||||
v = max(
|
||||
v,
|
||||
line_cov(
|
||||
px, py,
|
||||
cx + 3.2 * math.cos(a), cy + 3.2 * math.sin(a),
|
||||
cx + 7.4 * math.cos(a), cy + 7.4 * math.sin(a),
|
||||
1.05,
|
||||
),
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
def icon_script(px: float, py: float, cx: float, cy: float) -> float:
|
||||
return icon_cond(px, py, cx, cy)
|
||||
|
||||
|
||||
def icon_manual(px: float, py: float, cx: float, cy: float) -> float:
|
||||
v = line_cov(px, py, cx - 7, cy + 2, cx + 7, cy + 2, 1.2)
|
||||
v = max(v, line_cov(px, py, cx - 7, cy + 2, cx - 5, cy - 4, 1.0))
|
||||
v = max(v, line_cov(px, py, cx - 2, cy + 2, cx - 1, cy - 6, 1.0))
|
||||
v = max(v, line_cov(px, py, cx + 2, cy + 2, cx + 3, cy - 5.5, 1.0))
|
||||
v = max(v, line_cov(px, py, cx + 6, cy + 2, cx + 7, cy - 3, 1.0))
|
||||
return v
|
||||
|
||||
|
||||
def icon_rule(px: float, py: float, cx: float, cy: float) -> float:
|
||||
v = 0.0
|
||||
for yy in (cy - 6, cy, cy + 6):
|
||||
v = max(v, line_cov(px, py, cx - 7, yy, cx + 7, yy, 0.95))
|
||||
for xx in (cx - 7, cx, cx + 7):
|
||||
v = max(v, line_cov(px, py, xx, cy - 6, xx, cy + 6, 0.95))
|
||||
return v
|
||||
|
||||
|
||||
def icon_plusbox(px: float, py: float, cx: float, cy: float) -> float:
|
||||
x0, y0, x1, y1 = cx - 7, cy - 7, cx + 7, cy + 7
|
||||
v = 0.0
|
||||
on = (
|
||||
(abs(px - x0) < 1.05 or abs(px - x1) < 1.05) and (y0 <= py <= y1)
|
||||
) or (
|
||||
(abs(py - y0) < 1.05 or abs(py - y1) < 1.05) and (x0 <= px <= x1)
|
||||
)
|
||||
if on:
|
||||
v = 1.0
|
||||
v = max(v, line_cov(px, py, cx, cy - 4.5, cx, cy + 4.5, 1.05))
|
||||
v = max(v, line_cov(px, py, cx - 4.5, cy, cx + 4.5, cy, 1.05))
|
||||
return v
|
||||
|
||||
|
||||
ICONS = {
|
||||
"": None,
|
||||
"msg": lambda px, py, cx, cy: icon_msg(px, py, cx, cy, False),
|
||||
"msgf": lambda px, py, cx, cy: icon_msg(px, py, cx, cy, True),
|
||||
"timer": icon_timer,
|
||||
"error": icon_error,
|
||||
"signal": lambda px, py, cx, cy: icon_signal(px, py, cx, cy, False),
|
||||
"signalf": lambda px, py, cx, cy: icon_signal(px, py, cx, cy, True),
|
||||
"esc": icon_esc,
|
||||
"link": icon_link,
|
||||
"comp": icon_comp,
|
||||
"cond": icon_cond,
|
||||
"multi": icon_multi,
|
||||
"par": icon_par_multi,
|
||||
"cancel": icon_cancel,
|
||||
"user": icon_user,
|
||||
"service": icon_service,
|
||||
"script": icon_script,
|
||||
"manual": icon_manual,
|
||||
"rule": icon_rule,
|
||||
"plusbox": icon_plusbox,
|
||||
}
|
||||
|
||||
|
||||
def raster(draw) -> list[list[float]]:
|
||||
acc = [[0.0] * N for _ in range(N)]
|
||||
big = N * SS
|
||||
for y in range(big):
|
||||
for x in range(big):
|
||||
v = draw((x + 0.5) / SS, (y + 0.5) / SS)
|
||||
acc[y // SS][x // SS] += v
|
||||
s = float(SS * SS)
|
||||
return [[acc[y][x] / s for x in range(N)] for y in range(N)]
|
||||
|
||||
|
||||
def to_sprite(name: str, grid: list[list[float]]) -> str:
|
||||
lines = [f"sprite ${name} [{N}x{N}/16] {{"]
|
||||
for row in grid:
|
||||
lines.append("".join(HEX[min(15, int(round(v * 15)))] for v in row))
|
||||
lines.append("}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def event_grid(kind: str, icon: str, dashed: bool = False) -> list[list[float]]:
|
||||
cx = cy = (N - 1) / 2.0
|
||||
fn = ICONS[icon]
|
||||
|
||||
def draw(px, py):
|
||||
v = sample_event(px, py, kind=kind, dashed=dashed)
|
||||
if fn is not None:
|
||||
v = max(v, fn(px, py, cx, cy))
|
||||
return v
|
||||
|
||||
return raster(draw)
|
||||
|
||||
|
||||
def gateway_grid(mark: str) -> list[list[float]]:
|
||||
cx = cy = (N - 1) / 2.0
|
||||
r = 21.0
|
||||
|
||||
def draw(px, py):
|
||||
m = diamond_m(px, py, cx, cy)
|
||||
v = ring(m, r, 1.25)
|
||||
if mark == "x":
|
||||
v = max(v, line_cov(px, py, cx - 8, cy - 8, cx + 8, cy + 8, 1.25))
|
||||
v = max(v, line_cov(px, py, cx + 8, cy - 8, cx - 8, cy + 8, 1.25))
|
||||
elif mark == "+":
|
||||
v = max(v, line_cov(px, py, cx, cy - 9, cx, cy + 9, 1.3))
|
||||
v = max(v, line_cov(px, py, cx - 9, cy, cx + 9, cy, 1.3))
|
||||
elif mark == "o":
|
||||
v = max(v, ring(math.hypot(px - cx, py - cy), 7.0, 1.15))
|
||||
elif mark == "*":
|
||||
for i in range(4):
|
||||
a = i * math.pi / 4
|
||||
v = max(
|
||||
v,
|
||||
line_cov(
|
||||
px, py,
|
||||
cx - 7 * math.cos(a), cy - 7 * math.sin(a),
|
||||
cx + 7 * math.cos(a), cy + 7 * math.sin(a),
|
||||
1.05,
|
||||
),
|
||||
)
|
||||
elif mark == "ev":
|
||||
v = max(v, icon_multi(px, py, cx, cy))
|
||||
v = max(v, ring(math.hypot(px - cx, py - cy), 10.5, 0.95))
|
||||
elif mark == "pev":
|
||||
v = max(v, icon_par_multi(px, py, cx, cy))
|
||||
return v
|
||||
|
||||
return raster(draw)
|
||||
|
||||
|
||||
EVENTS = [
|
||||
("evStart", "start", "", False),
|
||||
("evStartMsg", "start", "msg", False),
|
||||
("evStartTimer", "start", "timer", False),
|
||||
("evStartSignal", "start", "signal", False),
|
||||
("evStartCond", "start", "cond", False),
|
||||
("evStartError", "start", "error", False),
|
||||
("evStartEsc", "start", "esc", False),
|
||||
("evStartMulti", "start", "multi", False),
|
||||
("evStartPar", "start", "par", False),
|
||||
("evStartNI", "start", "", True),
|
||||
("evStartMsgNI", "start", "msg", True),
|
||||
("evStartTimerNI", "start", "timer", True),
|
||||
("evStartSignalNI", "start", "signal", True),
|
||||
("evInter", "inter", "", False),
|
||||
("evCatchMsg", "inter", "msg", False),
|
||||
("evCatchTimer", "inter", "timer", False),
|
||||
("evCatchSignal", "inter", "signal", False),
|
||||
("evCatchError", "inter", "error", False),
|
||||
("evCatchEsc", "inter", "esc", False),
|
||||
("evCatchCond", "inter", "cond", False),
|
||||
("evCatchLink", "inter", "link", False),
|
||||
("evCatchComp", "inter", "comp", False),
|
||||
("evCatchCancel", "inter", "cancel", False),
|
||||
("evCatchMulti", "inter", "multi", False),
|
||||
("evCatchPar", "inter", "par", False),
|
||||
("evThrowMsg", "inter", "msgf", False),
|
||||
("evThrowSignal", "inter", "signalf", False),
|
||||
("evThrowEsc", "inter", "esc", False),
|
||||
("evThrowLink", "inter", "link", False),
|
||||
("evThrowComp", "inter", "comp", False),
|
||||
("evThrowMulti", "inter", "multi", False),
|
||||
("evEnd", "end", "", False),
|
||||
("evEndMsg", "end", "msgf", False),
|
||||
("evEndError", "end", "error", False),
|
||||
("evEndEsc", "end", "esc", False),
|
||||
("evEndCancel", "end", "cancel", False),
|
||||
("evEndComp", "end", "comp", False),
|
||||
("evEndSignal", "end", "signalf", False),
|
||||
("evEndMulti", "end", "multi", False),
|
||||
("evTerminate", "endfill", "", False),
|
||||
("evBoundMsg", "inter", "msg", False),
|
||||
("evBoundTimer", "inter", "timer", False),
|
||||
("evBoundError", "inter", "error", False),
|
||||
("evBoundEsc", "inter", "esc", False),
|
||||
("evBoundSignal", "inter", "signal", False),
|
||||
("evBoundCancel", "inter", "cancel", False),
|
||||
("evBoundComp", "inter", "comp", False),
|
||||
("evBoundCond", "inter", "cond", False),
|
||||
("evBoundMsgNI", "inter", "msg", True),
|
||||
("evBoundTimerNI", "inter", "timer", True),
|
||||
("evBoundEscNI", "inter", "esc", True),
|
||||
("evBoundSignalNI", "inter", "signal", True),
|
||||
("evBoundCondNI", "inter", "cond", True),
|
||||
]
|
||||
|
||||
GATES = [
|
||||
("gwXor", "x"),
|
||||
("gwAnd", "+"),
|
||||
("gwOr", "o"),
|
||||
("gwComplex", "*"),
|
||||
("gwEvent", "ev"),
|
||||
("gwParEvent", "pev"),
|
||||
]
|
||||
|
||||
TASK_ICONS = ["user", "service", "script", "manual", "rule", "plusbox", "msg", "msgf"]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
chunks = ["' auto-generated geometric sprites (BPMN 2.0 notation)"]
|
||||
for name, kind, icon, dashed in EVENTS:
|
||||
chunks.append(to_sprite(name, event_grid(kind, icon, dashed)))
|
||||
chunks.append("")
|
||||
for name, mark in GATES:
|
||||
chunks.append(to_sprite(name, gateway_grid(mark)))
|
||||
chunks.append("")
|
||||
cx = cy = (N - 1) / 2.0
|
||||
for icon in TASK_ICONS:
|
||||
fn = ICONS[icon]
|
||||
grid = raster(lambda px, py, fn=fn: fn(px, py, cx, cy))
|
||||
chunks.append(to_sprite("ic" + icon.capitalize() if icon != "msgf" else "icMsgf", grid))
|
||||
chunks.append("")
|
||||
# plusbox already as icPlusbox; loop/adhoc/mi as simple extra
|
||||
def loop_draw(px, py):
|
||||
v = ring(math.hypot(px - cx, py - (cy - 1)), 8.5, 1.1)
|
||||
v = max(v, line_cov(px, py, cx + 6, cy + 2, cx + 10, cy + 6, 1.05))
|
||||
v = max(v, line_cov(px, py, cx + 6, cy + 2, cx + 2, cy + 6, 1.05))
|
||||
return v
|
||||
|
||||
chunks.append(to_sprite("icLoop", raster(loop_draw)))
|
||||
chunks.append("")
|
||||
Path("_bpmn_sprites.puml").write_text("\n".join(chunks), encoding="utf-8")
|
||||
print("wrote", len(chunks), "chunks")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Download official Graphviz win64 portable into md2gost/vendor/graphviz (for exe builds)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from md2gost.dfd import ( # noqa: E402
|
||||
GRAPHVIZ_VERSION,
|
||||
GRAPHVIZ_ZIP_URL,
|
||||
fetch_graphviz,
|
||||
resolve_graphviz_engine,
|
||||
strip_graphviz_tcl_libs,
|
||||
vendor_graphviz_root,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
dest = vendor_graphviz_root()
|
||||
print(f"Graphviz {GRAPHVIZ_VERSION}: {GRAPHVIZ_ZIP_URL}")
|
||||
print(f"Куда: {dest}")
|
||||
if resolve_graphviz_engine(False) and (dest / "bin").is_dir():
|
||||
n = strip_graphviz_tcl_libs(dest)
|
||||
hit = resolve_graphviz_engine(False)
|
||||
extra = f", снял Tcl/Tk DLL Graphviz: {n}" if n else ""
|
||||
print(f"Уже есть: {hit}{extra}")
|
||||
return 0
|
||||
if fetch_graphviz(dest):
|
||||
hit = resolve_graphviz_engine(False)
|
||||
print(f"Готово: {hit or dest}")
|
||||
return 0
|
||||
print(
|
||||
"Не удалось скачать Graphviz "
|
||||
"(exe соберётся; DFD пойдёт через kroki.io или системный PATH)."
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,111 @@
|
||||
"""PyInstaller freeze helpers: pin CPython Tcl/Tk, drop Graphviz's copy."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _python_dll_dir() -> Path | None:
|
||||
candidates = [
|
||||
Path(sys.base_prefix) / "DLLs",
|
||||
Path(sys.prefix) / "DLLs",
|
||||
Path(sys.executable).resolve().parent / "DLLs",
|
||||
Path(sys.executable).resolve().parent.parent / "DLLs",
|
||||
]
|
||||
for folder in candidates:
|
||||
if (folder / "tcl86t.dll").is_file() or (folder / "tk86t.dll").is_file():
|
||||
return folder
|
||||
return None
|
||||
|
||||
|
||||
def prepare_tcltk_env() -> None:
|
||||
"""Make Analysis / isolated hook subprocesses see this interpreter's Tcl."""
|
||||
dll_dir = _python_dll_dir()
|
||||
prefix = Path(sys.base_prefix)
|
||||
tcl_lib = prefix / "tcl" / "tcl8.6"
|
||||
tk_lib = prefix / "tcl" / "tk8.6"
|
||||
if not tcl_lib.is_dir() and dll_dir is not None:
|
||||
tcl_lib = dll_dir.parent / "tcl" / "tcl8.6"
|
||||
tk_lib = dll_dir.parent / "tcl" / "tk8.6"
|
||||
if tcl_lib.is_dir():
|
||||
os.environ["TCL_LIBRARY"] = str(tcl_lib)
|
||||
if tk_lib.is_dir():
|
||||
os.environ["TK_LIBRARY"] = str(tk_lib)
|
||||
if dll_dir is not None:
|
||||
os.environ["PATH"] = str(dll_dir) + os.pathsep + os.environ.get("PATH", "")
|
||||
|
||||
|
||||
def collect_tkinterdnd2():
|
||||
datas: list = []
|
||||
hidden: list = ["tkinterdnd2", "tkinterdnd2.TkinterDnD"]
|
||||
try:
|
||||
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
|
||||
|
||||
datas += collect_data_files("tkinterdnd2")
|
||||
hidden = list(collect_submodules("tkinterdnd2")) or hidden
|
||||
except Exception:
|
||||
pass
|
||||
return datas, hidden
|
||||
|
||||
|
||||
def _toc_name(item) -> str:
|
||||
dest = str(item[0]).replace("\\", "/")
|
||||
return os.path.basename(dest).lower()
|
||||
|
||||
|
||||
def _toc_blob(item) -> str:
|
||||
dest = str(item[0]).replace("\\", "/").lower()
|
||||
src = str(item[1]).replace("\\", "/").lower() if len(item) > 1 else ""
|
||||
return dest + " " + src
|
||||
|
||||
|
||||
def _graphviz_tcl_names() -> set[str]:
|
||||
try:
|
||||
from md2gost.dfd import GRAPHVIZ_TCL_DLL_NAMES
|
||||
|
||||
return {n.lower() for n in GRAPHVIZ_TCL_DLL_NAMES}
|
||||
except Exception:
|
||||
return {
|
||||
"tcl86t.dll",
|
||||
"tk86t.dll",
|
||||
"tcl86.dll",
|
||||
"tk86.dll",
|
||||
"tcldot.dll",
|
||||
"tcldot_builtin.dll",
|
||||
"tclplan.dll",
|
||||
"gdtclft.dll",
|
||||
}
|
||||
|
||||
|
||||
def _is_graphviz_tcl(item) -> bool:
|
||||
if _toc_name(item) not in _graphviz_tcl_names():
|
||||
return False
|
||||
return "graphviz" in _toc_blob(item)
|
||||
|
||||
|
||||
def _wrap_toc(items):
|
||||
try:
|
||||
from PyInstaller.building.datastruct import TOC
|
||||
|
||||
return TOC(items)
|
||||
except Exception:
|
||||
return items
|
||||
|
||||
|
||||
def sanitize_analysis(a):
|
||||
"""Drop Graphviz Tcl/Tk DLLs and force this CPython's tcl86t/tk86t into the exe."""
|
||||
a.datas = _wrap_toc([x for x in a.datas if not _is_graphviz_tcl(x)])
|
||||
binaries = [x for x in a.binaries if not _is_graphviz_tcl(x)]
|
||||
dll_dir = _python_dll_dir()
|
||||
pin = ("tcl86t.dll", "tk86t.dll", "zlib1.dll")
|
||||
if dll_dir is not None:
|
||||
pin_lower = {n.lower() for n in pin}
|
||||
binaries = [x for x in binaries if _toc_name(x) not in pin_lower]
|
||||
for name in pin:
|
||||
path = dll_dir / name
|
||||
if path.is_file():
|
||||
binaries.append((name, str(path), "BINARY"))
|
||||
a.binaries = _wrap_toc(binaries)
|
||||
return a
|
||||
@@ -0,0 +1,33 @@
|
||||
# Runtime hook: load this CPython's Tcl/Tk before Graphviz's tcl86t.dll (8.6.10)
|
||||
# can shadow them. PyInstaller then points TCL_LIBRARY at bundled _tcl_data (8.6.12).
|
||||
def _pyi_rthook():
|
||||
import os
|
||||
import sys
|
||||
|
||||
if not getattr(sys, "frozen", False):
|
||||
return
|
||||
base = getattr(sys, "_MEIPASS", None) or os.path.dirname(sys.executable)
|
||||
os.environ["PATH"] = base + os.pathsep + os.environ.get("PATH", "")
|
||||
try:
|
||||
os.add_dll_directory(base)
|
||||
except (AttributeError, OSError):
|
||||
pass
|
||||
tcldir = os.path.join(base, "_tcl_data")
|
||||
tkdir = os.path.join(base, "_tk_data")
|
||||
if os.path.isdir(tcldir):
|
||||
os.environ["TCL_LIBRARY"] = tcldir
|
||||
if os.path.isdir(tkdir):
|
||||
os.environ["TK_LIBRARY"] = tkdir
|
||||
try:
|
||||
import ctypes
|
||||
|
||||
for name in ("tcl86t.dll", "tk86t.dll"):
|
||||
path = os.path.join(base, name)
|
||||
if os.path.isfile(path):
|
||||
ctypes.WinDLL(path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
_pyi_rthook()
|
||||
del _pyi_rthook
|
||||
Reference in New Issue
Block a user