|
|
"""Tests for aesthetic optimization functions.""" |
|
|
|
|
|
import sys |
|
|
import traceback |
|
|
|
|
|
def test_shape_quality(): |
|
|
"""Test shape quality analysis functions.""" |
|
|
print("Testing shape quality functions...") |
|
|
try: |
|
|
from shapely.geometry import Polygon |
|
|
from core.geometry.shape_quality import ( |
|
|
analyze_shape_quality, |
|
|
get_dominant_edge_vector, |
|
|
classify_lot_type, |
|
|
get_obb_dimensions |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
rect = Polygon([(0, 0), (50, 0), (50, 40), (0, 40)]) |
|
|
score, valid = analyze_shape_quality(rect) |
|
|
print(f" Rectangle (2000m²): score={score:.3f}, valid={valid}") |
|
|
assert valid, "Rectangle should be valid" |
|
|
assert score > 0.8, "Rectangle should have high score" |
|
|
|
|
|
|
|
|
tri = Polygon([(0, 0), (40, 0), (20, 30)]) |
|
|
score, valid = analyze_shape_quality(tri) |
|
|
print(f" Triangle: score={score:.3f}, valid={valid}") |
|
|
assert not valid, "Triangle should be invalid (low rectangularity)" |
|
|
|
|
|
|
|
|
elongated = Polygon([(0, 0), (200, 0), (200, 10), (0, 10)]) |
|
|
score, valid = analyze_shape_quality(elongated) |
|
|
print(f" Elongated: score={score:.3f}, valid={valid}") |
|
|
assert not valid, "Elongated shape should be invalid" |
|
|
|
|
|
|
|
|
small = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)]) |
|
|
score, valid = analyze_shape_quality(small) |
|
|
print(f" Small lot: score={score:.3f}, valid={valid}") |
|
|
assert not valid, "Small lot should be invalid" |
|
|
|
|
|
|
|
|
vec = get_dominant_edge_vector(rect) |
|
|
print(f" Dominant edge vector: {vec}") |
|
|
assert vec is not None, "Should return vector" |
|
|
|
|
|
|
|
|
w, l, angle = get_obb_dimensions(rect) |
|
|
print(f" OBB dimensions: width={w:.1f}, length={l:.1f}, angle={angle:.1f}°") |
|
|
assert abs(w - 40) < 1, "Width should be ~40" |
|
|
assert abs(l - 50) < 1, "Length should be ~50" |
|
|
|
|
|
|
|
|
lot_type = classify_lot_type(rect) |
|
|
print(f" Rectangle type: {lot_type}") |
|
|
assert lot_type == 'commercial', "Good lot should be commercial" |
|
|
|
|
|
|
|
|
large_tri = Polygon([(0, 0), (60, 0), (30, 50)]) |
|
|
lot_type = classify_lot_type(large_tri) |
|
|
print(f" Triangle type: {lot_type}") |
|
|
assert lot_type == 'green_space', "Bad lot (large enough) should be green_space" |
|
|
|
|
|
print("✅ Shape quality tests passed") |
|
|
return True |
|
|
except Exception as e: |
|
|
print(f"❌ Shape quality test failed: {e}") |
|
|
traceback.print_exc() |
|
|
return False |
|
|
|
|
|
|
|
|
def test_orthogonal_slicer(): |
|
|
"""Test orthogonal slicing functions.""" |
|
|
print("\nTesting orthogonal slicer functions...") |
|
|
try: |
|
|
from shapely.geometry import Polygon |
|
|
from core.geometry.orthogonal_slicer import ( |
|
|
orthogonal_slice, |
|
|
subdivide_with_uniform_widths |
|
|
) |
|
|
|
|
|
|
|
|
block = Polygon([(0, 0), (100, 0), (100, 50), (0, 50)]) |
|
|
widths = [25, 25, 25, 25] |
|
|
|
|
|
lots = orthogonal_slice(block, widths) |
|
|
print(f" Orthogonal slice: {len(lots)} lots from 4 widths") |
|
|
assert len(lots) == 4, "Should create 4 lots" |
|
|
|
|
|
|
|
|
lots2, actual_widths = subdivide_with_uniform_widths(block, target_width=20) |
|
|
print(f" Uniform subdivision: {len(lots2)} lots, widths={[round(w,1) for w in actual_widths]}") |
|
|
assert len(lots2) > 0, "Should create lots" |
|
|
|
|
|
print("✅ Orthogonal slicer tests passed") |
|
|
return True |
|
|
except Exception as e: |
|
|
print(f"❌ Orthogonal slicer test failed: {e}") |
|
|
traceback.print_exc() |
|
|
return False |
|
|
|
|
|
|
|
|
def test_settings(): |
|
|
"""Test aesthetic settings in config.""" |
|
|
print("\nTesting aesthetic settings...") |
|
|
try: |
|
|
from core.config.settings import ( |
|
|
DEFAULT_SETTINGS, |
|
|
MIN_RECTANGULARITY, |
|
|
MAX_ASPECT_RATIO, |
|
|
MIN_LOT_AREA, |
|
|
DEVIATION_PENALTY_WEIGHT, |
|
|
ENABLE_LEFTOVER_MANAGEMENT |
|
|
) |
|
|
|
|
|
print(f" MIN_RECTANGULARITY = {MIN_RECTANGULARITY}") |
|
|
print(f" MAX_ASPECT_RATIO = {MAX_ASPECT_RATIO}") |
|
|
print(f" MIN_LOT_AREA = {MIN_LOT_AREA}") |
|
|
print(f" DEVIATION_PENALTY_WEIGHT = {DEVIATION_PENALTY_WEIGHT}") |
|
|
print(f" ENABLE_LEFTOVER_MANAGEMENT = {ENABLE_LEFTOVER_MANAGEMENT}") |
|
|
|
|
|
assert MIN_RECTANGULARITY == 0.75, "Should match Beauti_mode spec" |
|
|
assert MAX_ASPECT_RATIO == 4.0, "Should match Beauti_mode spec" |
|
|
assert MIN_LOT_AREA == 1000.0, "Should match Beauti_mode spec" |
|
|
|
|
|
print("✅ Settings tests passed") |
|
|
return True |
|
|
except Exception as e: |
|
|
print(f"❌ Settings test failed: {e}") |
|
|
traceback.print_exc() |
|
|
return False |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
print("=" * 50) |
|
|
print("Aesthetic Optimization Test Suite") |
|
|
print("=" * 50) |
|
|
|
|
|
results = [] |
|
|
results.append(("Shape Quality", test_shape_quality())) |
|
|
results.append(("Orthogonal Slicer", test_orthogonal_slicer())) |
|
|
results.append(("Settings", test_settings())) |
|
|
|
|
|
print("\n" + "=" * 50) |
|
|
print("Test Results:") |
|
|
print("=" * 50) |
|
|
|
|
|
for name, passed in results: |
|
|
status = "✅ PASS" if passed else "❌ FAIL" |
|
|
print(f"{status} - {name}") |
|
|
|
|
|
all_passed = all(r[1] for r in results) |
|
|
|
|
|
print("\n" + "=" * 50) |
|
|
if all_passed: |
|
|
print("✅ ALL AESTHETIC TESTS PASSED") |
|
|
sys.exit(0) |
|
|
else: |
|
|
print("❌ SOME TESTS FAILED") |
|
|
sys.exit(1) |
|
|
|