Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from .models import Design | |
| def clamp(value: float, low: float, high: float) -> float: | |
| return max(low, min(high, value)) | |
| class Mesh: | |
| nodes: list[dict] | |
| tets: list[list[int]] | |
| length: float | |
| width: float | |
| height: float | |
| def point_in_feature_volume(point: dict, design: Design) -> bool: | |
| x, y, z = point["x"], point["y"], point["z"] | |
| length = design.base_length_mm | |
| half_width = design.base_width_mm / 2 | |
| thickness = design.base_thickness_mm | |
| solid = 0 <= x <= length and -half_width <= y <= half_width and 0 <= z <= thickness | |
| if solid and z <= thickness: | |
| for hole in design.fixed_holes: | |
| if ((x - hole.x) ** 2 + (y - hole.y) ** 2) ** 0.5 < hole.radius: | |
| solid = False | |
| for feature in design.features: | |
| if feature.type == "lightening_hole" and ((x - feature.x) ** 2 + (y - feature.y) ** 2) ** 0.5 < feature.radius: | |
| solid = False | |
| for feature in design.features: | |
| if feature.type == "boss": | |
| radius = clamp(feature.radius, 1, 20) | |
| height = clamp(feature.height, 1, 40) | |
| if ((x - feature.x) ** 2 + (y - feature.y) ** 2) ** 0.5 <= radius and thickness <= z <= thickness + height: | |
| solid = True | |
| if feature.type == "rib": | |
| ax, ay, bx, by = feature.x, feature.y, feature.x2, feature.y2 | |
| vx, vy = bx - ax, by - ay | |
| length_sq = max(vx * vx + vy * vy, 1) | |
| t = clamp(((x - ax) * vx + (y - ay) * vy) / length_sq, 0, 1) | |
| px, py = ax + t * vx, ay + t * vy | |
| rib_height = clamp(feature.height, 1, 60) | |
| if ((x - px) ** 2 + (y - py) ** 2) ** 0.5 <= max(feature.width, 1) / 2 and thickness <= z <= thickness + rib_height: | |
| solid = True | |
| return solid | |
| def build_structured_tet_mesh(design: Design, nx: int = 7, ny: int = 5, nz: int = 5) -> Mesh: | |
| length = clamp(design.base_length_mm, 30, 240) | |
| width = clamp(design.base_width_mm, 10, 120) | |
| thickness = clamp(design.base_thickness_mm, 1, 30) | |
| max_feature_height = max( | |
| [feature.height for feature in design.features if feature.type in {"rib", "boss"}] or [0] | |
| ) | |
| height = max(thickness + max_feature_height, thickness * 2) | |
| nodes: list[dict] = [] | |
| node_id: dict[tuple[int, int, int], int] = {} | |
| tets: list[list[int]] = [] | |
| def grid_point(i: int, j: int, k: int) -> dict: | |
| return { | |
| "x": length * i / nx, | |
| "y": -width / 2 + width * j / ny, | |
| "z": height * k / nz, | |
| } | |
| def add_node(i: int, j: int, k: int) -> int: | |
| key = (i, j, k) | |
| if key in node_id: | |
| return node_id[key] | |
| idx = len(nodes) | |
| nodes.append({"id": idx, **grid_point(i, j, k)}) | |
| node_id[key] = idx | |
| return idx | |
| tet_pattern = [ | |
| [0, 1, 3, 7], | |
| [0, 3, 2, 7], | |
| [0, 2, 6, 7], | |
| [0, 6, 4, 7], | |
| [0, 4, 5, 7], | |
| [0, 5, 1, 7], | |
| ] | |
| for i in range(nx): | |
| for j in range(ny): | |
| for k in range(nz): | |
| p0 = grid_point(i, j, k) | |
| p1 = grid_point(i + 1, j + 1, k + 1) | |
| center = {"x": (p0["x"] + p1["x"]) / 2, "y": (p0["y"] + p1["y"]) / 2, "z": (p0["z"] + p1["z"]) / 2} | |
| if not point_in_feature_volume(center, design): | |
| continue | |
| corners = [ | |
| add_node(i, j, k), | |
| add_node(i + 1, j, k), | |
| add_node(i, j + 1, k), | |
| add_node(i + 1, j + 1, k), | |
| add_node(i, j, k + 1), | |
| add_node(i + 1, j, k + 1), | |
| add_node(i, j + 1, k + 1), | |
| add_node(i + 1, j + 1, k + 1), | |
| ] | |
| for tet in tet_pattern: | |
| tets.append([corners[idx] for idx in tet]) | |
| return Mesh(nodes=nodes, tets=tets, length=length, width=width, height=height) | |