Created
August 31, 2025 13:20
-
-
Save zeffii/570f947c500551bd659b03a44880c2d1 to your computer and use it in GitHub Desktop.
pattern parsre .pat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import math | |
def parse_pat(filepath): | |
patterns = {} | |
current_pattern = None | |
with open(filepath, "r") as f: | |
for raw_line in f: | |
line = raw_line.strip() | |
if not line or line.startswith(";"): | |
continue | |
if line.startswith("*"): | |
# Header line | |
header = line[1:].split(",", 1) | |
name = header[0].strip() | |
desc = header[1].strip() if len(header) > 1 else "" | |
patterns[name] = {"description": desc, "lines": []} | |
current_pattern = name | |
else: | |
# Definition line | |
parts = [float(p) for p in line.split(",")] | |
angle, x0, y0, dx, dy, *dashes = parts | |
patterns[current_pattern]["lines"].append({ | |
"angle": math.radians(angle), | |
"origin": (x0, y0), | |
"delta": (dx, dy), | |
"dashes": dashes | |
}) | |
return patterns | |
def hatch_geometry(pattern, bbox): | |
""" | |
pattern: dict from parse_pat()[pattern_name] | |
bbox: (xmin, ymin, xmax, ymax) | |
Returns: list of line segments [(x1,y1,x2,y2), ...] | |
""" | |
xmin, ymin, xmax, ymax = bbox | |
segments = [] | |
for line_def in pattern["lines"]: | |
angle = line_def["angle"] | |
dx, dy = line_def["delta"] | |
x0, y0 = line_def["origin"] | |
# direction vector of hatch line | |
vx = math.cos(angle) | |
vy = math.sin(angle) | |
# perpendicular vector (for shifting parallel lines) | |
px, py = -vy, vx | |
# Estimate how many parallel lines needed to fill bbox | |
diag = math.hypot(xmax - xmin, ymax - ymin) | |
n_lines = int(diag / math.hypot(dx, dy)) + 5 # pad | |
for i in range(-n_lines, n_lines): | |
# base point of this parallel line | |
base_x = x0 + i * dx | |
base_y = y0 + i * dy | |
# Create long segment across bbox | |
# We'll clip later (just approximate span) | |
span = diag * 2 | |
x1 = base_x - vx * span | |
y1 = base_y - vy * span | |
x2 = base_x + vx * span | |
y2 = base_y + vy * span | |
# Clip to bbox (simple parametric clip) | |
clipped = clip_line((x1, y1), (x2, y2), bbox) | |
if not clipped: | |
continue | |
(cx1, cy1), (cx2, cy2) = clipped | |
# Apply dash pattern | |
if not line_def["dashes"]: | |
segments.append((cx1, cy1, cx2, cy2)) | |
else: | |
dash_segments = dashify((cx1, cy1), (cx2, cy2), line_def["dashes"]) | |
segments.extend(dash_segments) | |
return segments | |
def clip_line(p1, p2, bbox): | |
""" Liang–Barsky clipping against rectangular bbox """ | |
xmin, ymin, xmax, ymax = bbox | |
x1, y1 = p1 | |
x2, y2 = p2 | |
dx = x2 - x1 | |
dy = y2 - y1 | |
p = [-dx, dx, -dy, dy] | |
q = [x1 - xmin, xmax - x1, y1 - ymin, ymax - y1] | |
u1, u2 = 0.0, 1.0 | |
for pi, qi in zip(p, q): | |
if pi == 0: | |
if qi < 0: | |
return None | |
else: | |
t = -qi / pi | |
if pi < 0: | |
if t > u2: return None | |
if t > u1: u1 = t | |
else: | |
if t < u1: return None | |
if t < u2: u2 = t | |
cx1 = x1 + u1 * dx | |
cy1 = y1 + u1 * dy | |
cx2 = x1 + u2 * dx | |
cy2 = y1 + u2 * dy | |
return (cx1, cy1), (cx2, cy2) | |
def dashify(p1, p2, dashes): | |
""" Break a line segment into dash/gap segments """ | |
x1, y1 = p1 | |
x2, y2 = p2 | |
total_len = math.hypot(x2 - x1, y2 - y1) | |
vx = (x2 - x1) / total_len | |
vy = (y2 - y1) / total_len | |
segs = [] | |
pos = 0.0 | |
dash_index = 0 | |
while pos < total_len: | |
dash_len = abs(dashes[dash_index % len(dashes)]) | |
draw = dashes[dash_index % len(dashes)] > 0 | |
next_pos = min(total_len, pos + dash_len) | |
if draw: | |
sx = x1 + vx * pos | |
sy = y1 + vy * pos | |
ex = x1 + vx * next_pos | |
ey = y1 + vy * next_pos | |
segs.append((sx, sy, ex, ey)) | |
pos = next_pos | |
dash_index += 1 | |
return segs |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment