Spaces:
Running
Running
File size: 6,460 Bytes
0772b5a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | import pytest
import numpy as np
from solver.dsl_parser import DSLParser
from solver.engine import GeometryEngine
def test_solve_cube():
"""
Test solving for a cube ABCD.A1B1C1D1 with side a=5.
Verify all 8 vertices, 12 edges, faces, and 3D coordinates.
"""
dsl = """
POINT(A, 0, 0, 0)
POINT(B, 5, 0, 0)
POINT(C, 5, 5, 0)
POINT(D, 0, 5, 0)
POINT(A1)
POINT(B1)
POINT(C1)
POINT(D1)
LENGTH(AA1, 5)
PERPENDICULAR_PLANE(AA1, ABCD)
CUBE(ABCD_A1B1C1D1)
"""
parser = DSLParser()
engine = GeometryEngine()
points, constraints, is_3d = parser.parse(dsl)
assert is_3d is True
assert len(points) == 8
result = engine.solve(points, constraints, is_3d)
assert result is not None
coords = result["coordinates"]
# Check bottom base vertices
assert coords["C"][2] == pytest.approx(0.0, abs=1e-3)
assert coords["D"][0] == pytest.approx(0.0, abs=1e-3)
assert coords["D"][1] == pytest.approx(5.0, abs=1e-3)
assert coords["D"][2] == pytest.approx(0.0, abs=1e-3)
# Check top base vertices
assert coords["A1"][0] == pytest.approx(0.0, abs=1e-3)
assert coords["A1"][1] == pytest.approx(0.0, abs=1e-3)
assert abs(coords["A1"][2]) == pytest.approx(5.0, abs=1e-3)
# Verify solids and faces
assert "solids" in result
assert any(s["type"] == "cube" for s in result["solids"])
assert "faces" in result
assert len(result["faces"]) >= 6 # 6 faces for a cube
def test_solve_regular_tetrahedron():
"""
Test solving for a regular tetrahedron ABCD with edge a=6.
"""
dsl = """
POINT(A, 0, 0, 0)
POINT(B, 6, 0, 0)
POINT(C)
POINT(D)
LENGTH(AC, 6)
LENGTH(BC, 6)
LENGTH(AD, 6)
LENGTH(BD, 6)
LENGTH(CD, 6)
TETRAHEDRON(ABCD)
"""
parser = DSLParser()
engine = GeometryEngine()
points, constraints, is_3d = parser.parse(dsl)
assert is_3d is True
result = engine.solve(points, constraints, is_3d)
assert result is not None
coords = result["coordinates"]
# Check edge lengths
for p1, p2 in [("A","B"), ("A","C"), ("A","D"), ("B","C"), ("B","D"), ("C","D")]:
v1 = np.array(coords[p1])
v2 = np.array(coords[p2])
dist = np.linalg.norm(v2 - v1)
assert dist == pytest.approx(6.0, abs=1e-2)
# 4 faces
assert any(s["type"] == "tetrahedron" for s in result.get("solids", []))
assert len(result.get("faces", [])) >= 4
def test_solve_prism_full_edges():
"""
Test that triangular prism ABC_A1B1C1 generates all 9 edges (3 base1, 3 base2, 3 lateral).
"""
dsl = """
POINT(A, 0, 0, 0)
POINT(B, 4, 0, 0)
POINT(C, 0, 3, 0)
POINT(A1)
POINT(B1)
POINT(C1)
LENGTH(AA1, 8)
PERPENDICULAR_PLANE(AA1, ABC)
PRISM(ABC_A1B1C1)
"""
parser = DSLParser()
engine = GeometryEngine()
points, constraints, is_3d = parser.parse(dsl)
assert is_3d is True
result = engine.solve(points, constraints, is_3d)
assert result is not None
# Check drawing phases contain all segments
all_segments = []
for phase in result["drawing_phases"]:
all_segments.extend(phase["segments"])
# Base 1 edges: AB, BC, CA
# Base 2 edges: A1B1, B1C1, C1A1
# Lateral edges: AA1, BB1, CC1
expected_pairs = [
{"A", "B"}, {"B", "C"}, {"C", "A"},
{"A1", "B1"}, {"B1", "C1"}, {"C1", "A1"},
{"A", "A1"}, {"B", "B1"}, {"C", "C1"}
]
for pair in expected_pairs:
assert any(set(seg) == pair for seg in all_segments), f"Missing segment: {pair}"
def test_perpendicular_plane_constraint():
"""
Test PERPENDICULAR_PLANE(SO, ABCD) where ABCD is square on z=0.
Apex S must lie directly on z-axis (SO along z-axis).
"""
dsl = """
POINT(A, 0, 0, 0)
POINT(B, 4, 0, 0)
POINT(C, 4, 4, 0)
POINT(D, 0, 4, 0)
POINT(O, 2, 2, 0)
POINT(S)
LENGTH(SO, 6)
PERPENDICULAR_PLANE(SO, ABCD)
PYRAMID(S_ABCD)
"""
parser = DSLParser()
engine = GeometryEngine()
points, constraints, is_3d = parser.parse(dsl)
result = engine.solve(points, constraints, is_3d)
assert result is not None
coords = result["coordinates"]
assert coords["S"][0] == pytest.approx(2.0, abs=1e-3)
assert coords["S"][1] == pytest.approx(2.0, abs=1e-3)
assert abs(coords["S"][2]) == pytest.approx(6.0, abs=1e-3)
def test_coplanar_constraint():
"""
Test COPLANAR(A, B, C, D) constraint.
Points A(0,0,0), B(1,0,0), C(0,1,0) define XY-plane (z=0).
Point D with D_x=2, D_y=3 should have D_z=0 under COPLANAR constraint.
"""
dsl = """
POINT(A, 0, 0, 0)
POINT(B, 1, 0, 0)
POINT(C, 0, 1, 0)
POINT(D)
LENGTH(AD, 5)
ANGLE(D, A, B, 0)
COPLANAR(A, B, C, D)
"""
parser = DSLParser()
engine = GeometryEngine()
points, constraints, is_3d = parser.parse(dsl)
result = engine.solve(points, constraints, is_3d)
assert result is not None
coords = result["coordinates"]
assert coords["D"][2] == pytest.approx(0.0, abs=1e-3)
def test_cone_and_cylinder_dsl():
"""
Test CONE and CYLINDER parsing and metadata creation.
"""
dsl = """
POINT(O, 0, 0, 0)
POINT(S, 0, 0, 10)
CONE(S, O, 4, 10)
POINT(O1, 0, 0, 0)
POINT(O2, 0, 0, 8)
CYLINDER(O1, O2, 3)
"""
parser = DSLParser()
points, constraints, is_3d = parser.parse(dsl)
assert is_3d is True
engine = GeometryEngine()
result = engine.solve(points, constraints, is_3d)
assert result is not None
solids = result.get("solids", [])
assert any(s["type"] == "cone" and s["radius"] == 4.0 for s in solids)
assert any(s["type"] == "cylinder" and s["radius"] == 3.0 for s in solids)
def test_multichar_point_names():
"""
Test parsing and solving geometry with multi-character point names: A1, B1, M1, S_1, A'.
"""
dsl = """
POINT(A, 0, 0, 0)
POINT(B, 4, 0, 0)
POINT(M1)
MIDPOINT(M1, A, B)
"""
parser = DSLParser()
points, constraints, is_3d = parser.parse(dsl)
assert any(p.id == "M1" for p in points)
engine = GeometryEngine()
result = engine.solve(points, constraints, is_3d)
assert result is not None
coords = result["coordinates"]
assert coords["M1"][0] == pytest.approx(2.0, abs=1e-3)
assert coords["M1"][1] == pytest.approx(0.0, abs=1e-3)
if __name__ == "__main__":
pytest.main([__file__])
|