updates
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Measure straight-on clear eye apertures in the Shapr3D mask export.
|
||||
|
||||
Run with: uv run --no-project --with build123d python hardware/analyze_mask_step.py
|
||||
This is read-only. A 0.5 mm sightline grid gives approximate, not print-fit,
|
||||
measurements; faceted CAD and the assembled print may differ.
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
|
||||
from build123d import Axis, import_step
|
||||
from scipy.ndimage import distance_transform_edt
|
||||
import numpy as np
|
||||
|
||||
|
||||
STEP = Path(__file__).resolve().parents[1] / "stls/freddy_fazbear_mask.step"
|
||||
GRID_MM = 0.5
|
||||
mask = import_step(str(STEP))
|
||||
solids = mask.solids()
|
||||
|
||||
|
||||
def clear_aperture(solid_index: int, x_start: int, seed_x: int) -> None:
|
||||
"""Flood-fill only the enclosed eye opening, not empty space off-mask."""
|
||||
solid = solids[solid_index]
|
||||
xs = [x_start + i * GRID_MM for i in range(241)]
|
||||
zs = [160 + i * GRID_MM for i in range(141)]
|
||||
open_cells = set()
|
||||
for zi, z in enumerate(zs):
|
||||
for xi, x in enumerate(xs):
|
||||
axis = Axis((x, -200, z), (0, 1, 0))
|
||||
if not solid.find_intersection_points(axis):
|
||||
open_cells.add((xi, zi))
|
||||
|
||||
seed = (round((seed_x - x_start) / GRID_MM), round((195 - 160) / GRID_MM))
|
||||
assert seed in open_cells, f"Expected opening seed is not clear: {seed}"
|
||||
component = {seed}
|
||||
queue = deque([seed])
|
||||
while queue:
|
||||
xi, zi = queue.popleft()
|
||||
for neighbor in ((xi - 1, zi), (xi + 1, zi), (xi, zi - 1), (xi, zi + 1)):
|
||||
if neighbor in open_cells and neighbor not in component:
|
||||
component.add(neighbor)
|
||||
queue.append(neighbor)
|
||||
assert all(0 < xi < len(xs) - 1 and 0 < zi < len(zs) - 1 for xi, zi in component), (
|
||||
"The eye component reached the scan boundary; expand the grid or inspect the model."
|
||||
)
|
||||
|
||||
x_coords = [xs[xi] for xi, _ in component]
|
||||
z_coords = [zs[zi] for _, zi in component]
|
||||
rows = {}
|
||||
columns = {}
|
||||
for xi, zi in component:
|
||||
rows.setdefault(zi, []).append(xi)
|
||||
columns.setdefault(xi, []).append(zi)
|
||||
widest_row = max(rows, key=lambda row: max(rows[row]) - min(rows[row]))
|
||||
tallest_column = max(columns, key=lambda col: max(columns[col]) - min(columns[col]))
|
||||
print(f"Eye half {solid_index}: {len(component)} clear samples, grid {GRID_MM} mm")
|
||||
print(f" projected clear bounds: X {min(x_coords):.1f}..{max(x_coords):.1f} mm, "
|
||||
f"Z {min(z_coords):.1f}..{max(z_coords):.1f} mm")
|
||||
print(f" max horizontal clear width: "
|
||||
f"{(max(rows[widest_row]) - min(rows[widest_row])) * GRID_MM:.1f} mm "
|
||||
f"at Z={zs[widest_row]:.1f} mm")
|
||||
print(f" max vertical clear height: "
|
||||
f"{(max(columns[tallest_column]) - min(columns[tallest_column])) * GRID_MM:.1f} mm "
|
||||
f"at X={xs[tallest_column]:.1f} mm")
|
||||
print(f" clear-area centroid: X={sum(x_coords)/len(x_coords):.1f}, "
|
||||
f"Z={sum(z_coords)/len(z_coords):.1f} mm")
|
||||
aperture = np.zeros((len(zs), len(xs)), dtype=bool)
|
||||
for xi, zi in component:
|
||||
aperture[zi, xi] = True
|
||||
distances = distance_transform_edt(aperture) * GRID_MM
|
||||
center_zi, center_xi = np.unravel_index(np.argmax(distances), distances.shape)
|
||||
print(f" largest straight-on inscribed circle: radius about "
|
||||
f"{distances[center_zi, center_xi]:.1f} mm at "
|
||||
f"X={xs[center_xi]:.1f}, Z={zs[center_zi]:.1f} mm")
|
||||
|
||||
|
||||
clear_aperture(0, -110, -30)
|
||||
clear_aperture(1, 15, 60)
|
||||
Reference in New Issue
Block a user