Files
autocad-mcp-pro/tests/test_entity_corner_ops.py
T
Umutcan Edizsalan 0c7f64ceb6 fix(correctness-bundle): I16 correctness fixes + dead code removal + ruff clean
N1: COM hatch non-associative (AddHatch associativity=False)
N2: ezdxf refuses DWG saves with clear error; server.py derives fmt from extension
R19: dimension_aligned dist→distance; dimension_angular correct add_angular_dim_2l args (base/line1/line2/location)
R9: polar array 360° no-duplicate (divisor=n when fill_angle%360<ε)
R13: entity_offset side_x/side_y honored in ezdxf via cross-product (LINE) and distance-to-center (CIRCLE)
R18: drawing_new bootstrap failure surfaces status="degraded" instead of swallowing silently
Dead code: engineering/section.py, gear.generate_tooth_profile, CommandResult, _ACI_NAMES, set_layer_active, ensure_linetypes, ComBackend._batch_mode instance attr, engineering/__init__ build-seam comment
Ruff: 38 auto-fixed + 5 manual → codebase is now ruff-clean; CI lint is now blocking

184 tests pass.
2026-06-18 11:34:30 +03:00

181 lines
8.7 KiB
Python

"""Tests for trim/extend/fillet/chamfer (Task #5).
Backend coverage: ezdxf only. COM tests need a live AutoCAD instance and are
skipped unless explicitly enabled.
"""
import pytest
pytestmark = pytest.mark.asyncio
def _close(a, b, tol=1e-6):
return abs(a - b) < tol
# ─── TRIM ──────────────────────────────────────────────────────────────────
class TestTrim:
async def test_trim_keep_left(self, backend):
target = await backend.entity_create_line(0, 0, 100, 0)
cutter = await backend.entity_create_line(50, -10, 50, 10)
await backend.entity_trim(target.handle, cutter.handle, keep_x=10, keep_y=0)
info = await backend.entity_get(target.handle)
end = info.properties["end"]
# End was at (100,0); should now be at the intersection (50,0).
assert _close(end[0], 50.0)
assert _close(end[1], 0.0)
async def test_trim_keep_right(self, backend):
target = await backend.entity_create_line(0, 0, 100, 0)
cutter = await backend.entity_create_line(50, -10, 50, 10)
await backend.entity_trim(target.handle, cutter.handle, keep_x=80, keep_y=0)
info = await backend.entity_get(target.handle)
start = info.properties["start"]
assert _close(start[0], 50.0)
assert _close(start[1], 0.0)
async def test_trim_parallel_raises(self, backend):
target = await backend.entity_create_line(0, 0, 100, 0)
parallel = await backend.entity_create_line(0, 10, 100, 10)
with pytest.raises(RuntimeError, match="parallel"):
await backend.entity_trim(target.handle, parallel.handle, 50, 0)
async def test_trim_same_handle_raises(self, backend):
line = await backend.entity_create_line(0, 0, 100, 0)
with pytest.raises(RuntimeError, match="same"):
await backend.entity_trim(line.handle, line.handle, 50, 0)
async def test_trim_non_line_raises(self, backend):
line = await backend.entity_create_line(0, 0, 100, 0)
circle = await backend.entity_create_circle(50, 0, 10)
with pytest.raises(RuntimeError, match="LINE\\+LINE only"):
await backend.entity_trim(line.handle, circle.handle, 80, 0)
# ─── EXTEND ────────────────────────────────────────────────────────────────
class TestExtend:
async def test_extend_auto_endpoint(self, backend):
# Target ends at x=40; boundary is vertical at x=80.
target = await backend.entity_create_line(0, 0, 40, 0)
boundary = await backend.entity_create_line(80, -10, 80, 10)
await backend.entity_extend(target.handle, boundary.handle)
info = await backend.entity_get(target.handle)
end = info.properties["end"]
assert _close(end[0], 80.0)
assert _close(end[1], 0.0)
async def test_extend_explicit_endpoint(self, backend):
# Target is (50,0)-(100,0); extend the start side to x=20.
target = await backend.entity_create_line(50, 0, 100, 0)
boundary = await backend.entity_create_line(20, -10, 20, 10)
await backend.entity_extend(target.handle, boundary.handle, end_x=50, end_y=0)
info = await backend.entity_get(target.handle)
start = info.properties["start"]
assert _close(start[0], 20.0)
async def test_extend_parallel_raises(self, backend):
target = await backend.entity_create_line(0, 0, 50, 0)
parallel = await backend.entity_create_line(0, 10, 50, 10)
with pytest.raises(RuntimeError, match="parallel"):
await backend.entity_extend(target.handle, parallel.handle)
# ─── FILLET ────────────────────────────────────────────────────────────────
class TestFillet:
async def test_fillet_90_degree_corner(self, backend):
# L-shape: horizontal (0,0)-(100,0) + vertical (100,0)-(100,80).
# Inner fillet R=5 → arc center (95,5), tangent points (95,0) and (100,5).
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(100, 0, 100, 80)
arc = await backend.entity_fillet(l1.handle, l2.handle, radius=5)
assert arc.type == "ARC"
cx, cy = arc.properties["center"]
assert _close(cx, 95.0)
assert _close(cy, 5.0)
assert _close(arc.properties["radius"], 5.0)
async def test_fillet_trims_source_lines(self, backend):
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(100, 0, 100, 80)
await backend.entity_fillet(l1.handle, l2.handle, radius=5, trim=True)
info1 = await backend.entity_get(l1.handle)
info2 = await backend.entity_get(l2.handle)
end1 = info1.properties["end"]
start2 = info2.properties["start"]
assert _close(end1[0], 95.0) and _close(end1[1], 0.0)
assert _close(start2[0], 100.0) and _close(start2[1], 5.0)
async def test_fillet_zero_radius(self, backend):
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(100, 0, 100, 80)
result = await backend.entity_fillet(l1.handle, l2.handle, radius=0)
# No arc: result is the source line (corner-merge).
assert result.type == "LINE"
async def test_fillet_no_trim(self, backend):
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(100, 0, 100, 80)
await backend.entity_fillet(l1.handle, l2.handle, radius=5, trim=False)
info1 = await backend.entity_get(l1.handle)
# Source line endpoint should be unchanged (still 100,0).
end1 = info1.properties["end"]
assert _close(end1[0], 100.0)
async def test_fillet_parallel_raises(self, backend):
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(0, 10, 100, 10)
with pytest.raises(RuntimeError, match="parallel"):
await backend.entity_fillet(l1.handle, l2.handle, radius=5)
async def test_fillet_negative_radius_raises(self, backend):
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(100, 0, 100, 80)
with pytest.raises(RuntimeError, match=">= 0"):
await backend.entity_fillet(l1.handle, l2.handle, radius=-1)
# ─── CHAMFER ───────────────────────────────────────────────────────────────
class TestChamfer:
async def test_chamfer_symmetric(self, backend):
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(100, 0, 100, 80)
cl = await backend.entity_chamfer(l1.handle, l2.handle, dist1=4)
assert cl.type == "LINE"
s = cl.properties["start"]
e = cl.properties["end"]
# Tangent points: (96,0) and (100,4) in some order.
pts = sorted([tuple(s[:2]), tuple(e[:2])])
expected = sorted([(96.0, 0.0), (100.0, 4.0)])
for got, exp in zip(pts, expected, strict=True):
assert _close(got[0], exp[0]) and _close(got[1], exp[1])
async def test_chamfer_asymmetric(self, backend):
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(100, 0, 100, 80)
cl = await backend.entity_chamfer(l1.handle, l2.handle, dist1=4, dist2=6)
s = cl.properties["start"]
e = cl.properties["end"]
pts = sorted([tuple(s[:2]), tuple(e[:2])])
expected = sorted([(96.0, 0.0), (100.0, 6.0)])
for got, exp in zip(pts, expected, strict=True):
assert _close(got[0], exp[0]) and _close(got[1], exp[1])
async def test_chamfer_trims_source_lines(self, backend):
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(100, 0, 100, 80)
await backend.entity_chamfer(l1.handle, l2.handle, dist1=4, trim=True)
info1 = await backend.entity_get(l1.handle)
info2 = await backend.entity_get(l2.handle)
assert _close(info1.properties["end"][0], 96.0)
assert _close(info2.properties["start"][1], 4.0)
async def test_chamfer_zero_distance_raises(self, backend):
l1 = await backend.entity_create_line(0, 0, 100, 0)
l2 = await backend.entity_create_line(100, 0, 100, 80)
with pytest.raises(RuntimeError, match="> 0"):
await backend.entity_chamfer(l1.handle, l2.handle, dist1=0)