feat(P3): add GMSH Frontal-Delaunay tessellation engine
Introduces GMSH as an alternative to OCC BRepMesh for STEP→GLB tessellation. GMSH produces conforming meshes that eliminate fan triangles at cylinder seam edges — a structural limitation of OCC BRepMesh that cannot be fixed via deflection parameters. Changes: - render-worker/Dockerfile: install gmsh>=4.15.0 + libglu1-mesa + libxft2 - export_step_to_gltf.py: --tessellation_engine occ|gmsh CLI arg + _tessellate_with_gmsh() using BRep→GMSH→Poly_Triangulation write-back - admin.py: tessellation_engine setting (SETTINGS_DEFAULTS, SettingsOut, SettingsUpdate, validation) - export_glb.py: pass tessellation_engine to export_step_to_gltf.py CLI in both geometry and production GLB tasks - Admin.tsx: radio button UI for OCC vs GMSH selection Tested: 121 faces meshed, 0 BRepMesh fallback, 649K triangles on sample part. Clean seam edges for UV unwrap — GMSH respects B-rep periodic face boundaries. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,8 @@ SETTINGS_DEFAULTS: dict[str, str] = {
|
||||
"smtp_user": "",
|
||||
"smtp_password": "",
|
||||
"smtp_from_address": "",
|
||||
# glTF tessellation quality (OCC BRepMesh)
|
||||
# glTF tessellation quality
|
||||
"tessellation_engine": "occ", # "occ" | "gmsh" — tessellation backend
|
||||
"gltf_preview_linear_deflection": "0.1", # mm — geometry GLB for viewer
|
||||
"gltf_preview_angular_deflection": "0.1", # rad — Standard preset
|
||||
"gltf_production_linear_deflection": "0.03", # mm — production GLB
|
||||
@@ -87,6 +88,7 @@ class SettingsOut(BaseModel):
|
||||
gltf_material_quality: str = "pbr_colors"
|
||||
gltf_pbr_roughness: float = 0.4
|
||||
gltf_pbr_metallic: float = 0.6
|
||||
tessellation_engine: str = "occ"
|
||||
|
||||
|
||||
class SettingsUpdate(BaseModel):
|
||||
@@ -119,6 +121,7 @@ class SettingsUpdate(BaseModel):
|
||||
gltf_material_quality: str | None = None
|
||||
gltf_pbr_roughness: float | None = None
|
||||
gltf_pbr_metallic: float | None = None
|
||||
tessellation_engine: str | None = None
|
||||
|
||||
|
||||
@router.get("/users", response_model=list[UserOut])
|
||||
@@ -237,6 +240,7 @@ def _settings_to_out(raw: dict[str, str]) -> SettingsOut:
|
||||
gltf_material_quality=raw.get("gltf_material_quality", "pbr_colors"),
|
||||
gltf_pbr_roughness=float(raw.get("gltf_pbr_roughness", "0.4")),
|
||||
gltf_pbr_metallic=float(raw.get("gltf_pbr_metallic", "0.6")),
|
||||
tessellation_engine=raw.get("tessellation_engine", "occ"),
|
||||
)
|
||||
|
||||
|
||||
@@ -361,6 +365,10 @@ async def update_settings(
|
||||
if not (0.05 <= body.gltf_production_angular_deflection <= 1.5):
|
||||
raise HTTPException(400, detail="gltf_production_angular_deflection must be 0.05–1.5 rad")
|
||||
updates["gltf_production_angular_deflection"] = str(body.gltf_production_angular_deflection)
|
||||
if body.tessellation_engine is not None:
|
||||
if body.tessellation_engine not in {"occ", "gmsh"}:
|
||||
raise HTTPException(400, detail="tessellation_engine must be 'occ' or 'gmsh'")
|
||||
updates["tessellation_engine"] = body.tessellation_engine
|
||||
|
||||
for k, v in updates.items():
|
||||
await _save_setting(db, k, v)
|
||||
|
||||
@@ -70,6 +70,7 @@ def generate_gltf_geometry_task(self, cad_file_id: str):
|
||||
|
||||
linear_deflection = float(sys_settings.get("gltf_preview_linear_deflection", "0.1"))
|
||||
angular_deflection = float(sys_settings.get("gltf_preview_angular_deflection", "0.1"))
|
||||
tessellation_engine = sys_settings.get("tessellation_engine", "occ")
|
||||
|
||||
step = _Path(step_path_str)
|
||||
|
||||
@@ -97,10 +98,11 @@ def generate_gltf_geometry_task(self, cad_file_id: str):
|
||||
"--color_map", _json.dumps(color_map),
|
||||
"--linear_deflection", str(linear_deflection),
|
||||
"--angular_deflection", str(angular_deflection),
|
||||
"--tessellation_engine", tessellation_engine,
|
||||
]
|
||||
log_task_event(
|
||||
self.request.id,
|
||||
f"OCC tessellation: linear={linear_deflection}mm, angular={angular_deflection}rad",
|
||||
f"Tessellation ({tessellation_engine}): linear={linear_deflection}mm, angular={angular_deflection}rad",
|
||||
"info",
|
||||
)
|
||||
|
||||
@@ -231,6 +233,7 @@ def generate_gltf_production_task(self, cad_file_id: str, product_id: str | None
|
||||
smooth_angle = float(sys_settings.get("blender_smooth_angle", "30"))
|
||||
prod_linear = float(sys_settings.get("gltf_production_linear_deflection", "0.03"))
|
||||
prod_angular = float(sys_settings.get("gltf_production_angular_deflection", "0.05"))
|
||||
tessellation_engine = sys_settings.get("tessellation_engine", "occ")
|
||||
|
||||
scripts_dir = _Path(_os.environ.get("RENDER_SCRIPTS_DIR", "/render-scripts"))
|
||||
occ_script = scripts_dir / "export_step_to_gltf.py"
|
||||
@@ -247,6 +250,7 @@ def generate_gltf_production_task(self, cad_file_id: str, product_id: str | None
|
||||
"--linear_deflection", str(prod_linear),
|
||||
"--angular_deflection", str(prod_angular),
|
||||
"--sharp_threshold", str(sharp_threshold),
|
||||
"--tessellation_engine", tessellation_engine,
|
||||
]
|
||||
log_task_event(
|
||||
self.request.id,
|
||||
|
||||
Reference in New Issue
Block a user