Skip to content

Instantly share code, notes, and snippets.

@zeffii
Created August 31, 2025 13:20
Show Gist options
  • Save zeffii/570f947c500551bd659b03a44880c2d1 to your computer and use it in GitHub Desktop.
Save zeffii/570f947c500551bd659b03a44880c2d1 to your computer and use it in GitHub Desktop.
pattern parsre .pat
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