ruff format was never run repo-wide; CI now enforces 'ruff format --check', so normalize every module once. No behavior changes. Co-authored-by: Cursor <cursoragent@cursor.com>
166 lines
4.7 KiB
Python
166 lines
4.7 KiB
Python
"""Tests for engineering.gear — pure-math + drawing orchestration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
import pytest
|
|
|
|
from engineering.gear import (
|
|
draw_gear_section_aa,
|
|
draw_helical_gear_front_view,
|
|
generate_full_gear_outline,
|
|
generate_involute_flank,
|
|
involute_xy,
|
|
)
|
|
from engineering.layers import (
|
|
ensure_engineering_layers,
|
|
ensure_standard_linetypes,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pure math (sync)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_involute_xy_at_zero_returns_base_point():
|
|
x, y = involute_xy(10.0, 0.0)
|
|
assert math.isclose(x, 10.0, abs_tol=1e-9)
|
|
assert math.isclose(y, 0.0, abs_tol=1e-9)
|
|
|
|
|
|
def test_generate_involute_flank_min_30_points():
|
|
flank = generate_involute_flank(base_r=20.0, root_r=18.0, outer_r=25.0, n_points=5)
|
|
assert len(flank) >= 30
|
|
|
|
|
|
def test_generate_involute_flank_radius_progression():
|
|
base_r, root_r, outer_r = 20.0, 18.0, 25.0
|
|
flank = generate_involute_flank(base_r=base_r, root_r=root_r, outer_r=outer_r)
|
|
first_r = math.hypot(*flank[0])
|
|
last_r = math.hypot(*flank[-1])
|
|
assert math.isclose(first_r, root_r, abs_tol=1e-6)
|
|
assert math.isclose(last_r, outer_r, rel_tol=1e-3)
|
|
|
|
|
|
def test_generate_full_gear_outline_closed():
|
|
pts = generate_full_gear_outline(module=3.0, teeth=24)
|
|
assert pts[0] == pts[-1]
|
|
|
|
|
|
def test_generate_full_gear_outline_point_count():
|
|
teeth = 24
|
|
pts = generate_full_gear_outline(module=3.0, teeth=teeth)
|
|
# At minimum: teeth * (40 right + 40 left) = 1920 unique flank points,
|
|
# plus tip and root arc samples. Closing duplicate adds 1.
|
|
assert len(pts) >= teeth * 40
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Drawing orchestration (async, ezdxf backend)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_helical_gear_front_view_returns_metadata(backend):
|
|
await ensure_standard_linetypes(backend)
|
|
await ensure_engineering_layers(backend)
|
|
res = await draw_helical_gear_front_view(
|
|
backend,
|
|
module=3,
|
|
teeth=24,
|
|
helix_angle=20.0,
|
|
hand="RH",
|
|
center=(0.0, 0.0),
|
|
)
|
|
expected_keys = {
|
|
"outline",
|
|
"pitch_circle",
|
|
"base_circle",
|
|
"outer_circle",
|
|
"root_circle",
|
|
"helix_symbol",
|
|
"helix_label",
|
|
"bore",
|
|
"keyway",
|
|
"metadata",
|
|
}
|
|
assert expected_keys.issubset(res.keys())
|
|
md = res["metadata"]
|
|
for k in (
|
|
"module",
|
|
"teeth",
|
|
"pressure_angle",
|
|
"helix_angle",
|
|
"hand",
|
|
"center",
|
|
"bore_diameter",
|
|
"keyway_width",
|
|
"keyway_depth",
|
|
"pitch_radius",
|
|
"outer_radius",
|
|
"base_radius",
|
|
"root_radius",
|
|
):
|
|
assert k in md, f"metadata missing key {k!r}"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_helical_gear_with_bore_and_keyway_handles_present(backend):
|
|
await ensure_standard_linetypes(backend)
|
|
await ensure_engineering_layers(backend)
|
|
res = await draw_helical_gear_front_view(
|
|
backend,
|
|
module=3,
|
|
teeth=24,
|
|
helix_angle=20.0,
|
|
bore_diameter=25.0,
|
|
keyway_width=8.0,
|
|
keyway_depth=4.0,
|
|
)
|
|
assert res["bore"] is not None
|
|
assert res["keyway"] is not None
|
|
assert "bore" in res["keyway"]
|
|
assert "keyway_polyline" in res["keyway"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_helical_gear_metadata_radii_correct(backend):
|
|
await ensure_standard_linetypes(backend)
|
|
await ensure_engineering_layers(backend)
|
|
res = await draw_helical_gear_front_view(
|
|
backend,
|
|
module=3,
|
|
teeth=24,
|
|
helix_angle=20.0,
|
|
pressure_angle=20.0,
|
|
)
|
|
md = res["metadata"]
|
|
assert math.isclose(md["pitch_radius"], 36.0, abs_tol=1e-6)
|
|
assert math.isclose(md["outer_radius"], 39.0, abs_tol=1e-6)
|
|
assert math.isclose(md["base_radius"], 36.0 * math.cos(math.radians(20.0)), abs_tol=1e-3)
|
|
assert math.isclose(md["root_radius"], 32.25, abs_tol=1e-6)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_section_aa_does_not_draw_helix_lines(backend):
|
|
await ensure_standard_linetypes(backend)
|
|
await ensure_engineering_layers(backend)
|
|
front = await draw_helical_gear_front_view(
|
|
backend,
|
|
module=3,
|
|
teeth=24,
|
|
helix_angle=20.0,
|
|
bore_diameter=25.0,
|
|
keyway_width=8.0,
|
|
keyway_depth=4.0,
|
|
)
|
|
section = await draw_gear_section_aa(
|
|
backend,
|
|
gear_metadata=front["metadata"],
|
|
x_offset=200.0,
|
|
face_width=40.0,
|
|
)
|
|
assert "helix" not in section
|
|
assert "helix_symbol" not in section
|