AnasSarkiz/magnetic-shutter-remote-r8
A USB‑C rechargeable battery-powered ESP32‑C3 wireless controller with Li‑ion charging, protected 3.3‑V buck‑boost regulation, thermal/safety monitoring, status LEDs, buttons, programming UART, crystal clock, and 2.4‑GHz antenna matching network.
- Version
- 0.4.0-prototype
- License
- unset
- Stars
- 0
scripts/export-product-prints.py
"""Manufacturing CSG from the same native JSCAD plans; never repair/display meshes."""
from pathlib import Path
import json
import hashlib
import importlib.metadata
import numpy as np
import trimesh
from manifold3d import Manifold, CrossSection, OpType, Error
ROOT = Path(__file__).resolve().parents[1]
def solid_from_plan(plan):
kind = plan['type']
if kind == 'colorize':
return solid_from_plan(plan['shape'])
if kind == 'cuboid':
return Manifold.cube(plan['size'], center=True)
if kind == 'cylinder':
# Match each source plan's cylinder resolution exactly.
return Manifold.cylinder(plan['height'], plan['radius'],
circular_segments=plan.get('resolution',32), center=True).translate(plan['center'])
if kind == 'rotate':
return solid_from_plan(plan['shape']).rotate(tuple(np.rad2deg(plan['angles'])))
if kind == 'translate':
return solid_from_plan(plan['shape']).translate(plan['vector'])
if kind in {'union', 'subtract', 'intersect', 'hull'}:
shapes = [solid_from_plan(shape) for shape in plan['shapes']]
if kind == 'hull':
return Manifold.batch_hull(shapes)
return Manifold.batch_boolean(shapes, {'union': OpType.Add, 'subtract': OpType.Subtract, 'intersect': OpType.Intersect}[kind])
if kind == 'extrudeLinear' and plan['shape']['type'] == 'polygon':
return CrossSection([plan['shape']['points']]).extrude(plan['options']['height'])
raise ValueError(f'Unsupported manufacturing operation: {kind}')
def export_prints():
plans = json.loads((ROOT/'product/print-plans.json').read_text())
native = json.loads((ROOT/'product/geometry-review.json').read_text())
native_parts = {part['name']: part for part in native['parts']}
reports = []
for part in plans:
solid = solid_from_plan(part['plan'])
if solid.status() != Error.NoError or solid.is_empty():
raise ValueError(f'Invalid manufacturing solid: {part["name"]}')
mesh = solid.to_mesh()
result = trimesh.Trimesh(vertices=np.asarray(mesh.vert_properties)[:, :3],
faces=np.asarray(mesh.tri_verts), process=False)
source = native_parts[part['name']]
volume_delta = abs(solid.volume()-source['volumeMm3'])
# Kernel must reproduce the actual native facet geometry, not substitute a shape.
if volume_delta >= 1e-5 or volume_delta > source['volumeMm3']*1e-7:
raise ValueError(f'Native/manufacturing volume mismatch: {part["name"]} {volume_delta}')
if not np.allclose(result.bounds, source['localBoundsMm'], atol=1e-5, rtol=0):
raise ValueError(f'Native/manufacturing bounds mismatch: {part["name"]}')
connected_solids = len(solid.decompose())
if connected_solids != 1:
raise ValueError(f'Disconnected print part: {part["name"]}: {connected_solids}')
if not result.is_watertight or not result.is_winding_consistent:
raise ValueError(f'Nonmanifold manufacturing mesh: {part["name"]}')
output = ROOT/'product/prints'/f'{part["name"]}.stl'
output.parent.mkdir(parents=True, exist_ok=True)
output.write_bytes(result.export(file_type='stl'))
# Re-read the actual emitted file, not only its in-memory solid.
readback = trimesh.load_mesh(output, process=True)
if not readback.is_watertight or not readback.is_winding_consistent:
raise ValueError(f'STL readback failed: {part["name"]}')
if np.any(readback.area_faces == 0):
raise ValueError(f'Zero-area triangle in actual STL: {part["name"]}')
reports.append({'file':str(output.relative_to(ROOT)),
'sha256':hashlib.sha256(output.read_bytes()).hexdigest(),
'triangles':len(result.faces), 'connected_solids':connected_solids, 'watertight':True,
'winding_consistent':True, 'zero_area_triangles':0, 'native_volume_delta_mm3':volume_delta,
'bounds_mm':result.bounds.tolist(), 'manufacturing_status':str(solid.status())})
report = {'tool_versions': {name:importlib.metadata.version(name) for name in ['manifold3d','trimesh','numpy']},
'source_plans_sha256':hashlib.sha256((ROOT/'product/print-plans.json').read_bytes()).hexdigest(),
'native_geometry_sha256':hashlib.sha256((ROOT/'product/geometry-review.json').read_bytes()).hexdigest(),
'printed_parts':reports}
(ROOT/'product/print-mesh-review.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps(report,indent=2))
if __name__ == '__main__':
export_prints()