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.
This commit is contained in:
+2
-21
@@ -6,7 +6,6 @@ from .gear import (
|
||||
draw_spur_gear_front_view,
|
||||
generate_full_gear_outline,
|
||||
generate_involute_flank,
|
||||
generate_tooth_profile,
|
||||
involute_xy,
|
||||
)
|
||||
from .keyway import (
|
||||
@@ -32,23 +31,8 @@ from .plan_spec import (
|
||||
SheetSize,
|
||||
SnapType,
|
||||
)
|
||||
from .section import apply_section_hatch, cutting_plane_line
|
||||
|
||||
# Agent B contributes titleblock + validator. Imports are guarded so this
|
||||
# package remains usable while B is still in flight; once those modules land
|
||||
# the symbols flow through transparently.
|
||||
try:
|
||||
from .titleblock import TitleBlockMetadata, apply_iso_a3_titleblock
|
||||
except ImportError:
|
||||
TitleBlockMetadata = None # type: ignore[assignment]
|
||||
apply_iso_a3_titleblock = None # type: ignore[assignment]
|
||||
|
||||
try:
|
||||
from .validator import DrawingValidator, ValidationFinding, ValidationResult
|
||||
except ImportError:
|
||||
DrawingValidator = None # type: ignore[assignment]
|
||||
ValidationFinding = None # type: ignore[assignment]
|
||||
ValidationResult = None # type: ignore[assignment]
|
||||
from .titleblock import TitleBlockMetadata, apply_iso_a3_titleblock
|
||||
from .validator import DrawingValidator, ValidationFinding, ValidationResult
|
||||
|
||||
__all__ = [
|
||||
"draw_helical_gear_front_view",
|
||||
@@ -56,14 +40,11 @@ __all__ = [
|
||||
"draw_gear_section_aa",
|
||||
"generate_full_gear_outline",
|
||||
"generate_involute_flank",
|
||||
"generate_tooth_profile",
|
||||
"involute_xy",
|
||||
"draw_keyed_bore",
|
||||
"draw_keyway_section",
|
||||
"keyway_dimensions",
|
||||
"DIN6885_TABLE",
|
||||
"cutting_plane_line",
|
||||
"apply_section_hatch",
|
||||
"ENGINEERING_LAYERS",
|
||||
"STANDARD_LINETYPES",
|
||||
"ensure_engineering_layers",
|
||||
|
||||
@@ -34,7 +34,7 @@ _DIM_TYPES = {
|
||||
}
|
||||
|
||||
|
||||
async def _check_iso128(backend: "AutoCADBackend") -> list[Issue]:
|
||||
async def _check_iso128(backend: AutoCADBackend) -> list[Issue]:
|
||||
"""Layer lineweights must be in ISO_128_LINEWEIGHTS_MM (or -3=ByLayer
|
||||
sentinel meaning 'inherit')."""
|
||||
issues: list[Issue] = []
|
||||
@@ -69,7 +69,7 @@ async def _check_iso128(backend: "AutoCADBackend") -> list[Issue]:
|
||||
return issues
|
||||
|
||||
|
||||
async def _check_layer_color(backend: "AutoCADBackend") -> list[Issue]:
|
||||
async def _check_layer_color(backend: AutoCADBackend) -> list[Issue]:
|
||||
"""Engineering-standard layers must keep their canonical ACI color."""
|
||||
issues: list[Issue] = []
|
||||
try:
|
||||
@@ -93,7 +93,7 @@ async def _check_layer_color(backend: "AutoCADBackend") -> list[Issue]:
|
||||
return issues
|
||||
|
||||
|
||||
async def _check_construction_left(backend: "AutoCADBackend") -> list[Issue]:
|
||||
async def _check_construction_left(backend: AutoCADBackend) -> list[Issue]:
|
||||
"""CONSTRUCTION layer must be empty before drawing_finalize."""
|
||||
try:
|
||||
ents = await backend.entity_list(layer_filter="CONSTRUCTION", limit=5000)
|
||||
@@ -113,7 +113,7 @@ async def _check_construction_left(backend: "AutoCADBackend") -> list[Issue]:
|
||||
)]
|
||||
|
||||
|
||||
async def _check_untrimmed_corner(backend: "AutoCADBackend") -> list[Issue]:
|
||||
async def _check_untrimmed_corner(backend: AutoCADBackend) -> list[Issue]:
|
||||
"""Two LINE endpoints within `tol` but not exactly equal usually means a
|
||||
corner where one line overshoots — TRIM/EXTEND/FILLET was forgotten."""
|
||||
tol = 0.5 # mm — generous; tighten via PlanSpec.notes later
|
||||
@@ -156,7 +156,7 @@ async def _check_untrimmed_corner(backend: "AutoCADBackend") -> list[Issue]:
|
||||
return issues
|
||||
|
||||
|
||||
async def _check_duplicate_entities(backend: "AutoCADBackend") -> list[Issue]:
|
||||
async def _check_duplicate_entities(backend: AutoCADBackend) -> list[Issue]:
|
||||
"""Two LINEs with identical endpoints (in either direction) are duplicates."""
|
||||
try:
|
||||
lines = await backend.entity_list(type_filter="LINE", limit=5000)
|
||||
@@ -185,7 +185,7 @@ async def _check_duplicate_entities(backend: "AutoCADBackend") -> list[Issue]:
|
||||
return issues
|
||||
|
||||
|
||||
async def _check_dim_overlap(backend: "AutoCADBackend") -> list[Issue]:
|
||||
async def _check_dim_overlap(backend: AutoCADBackend) -> list[Issue]:
|
||||
"""Two dimensions whose insertion points are within `tol` likely overlap."""
|
||||
tol = 5.0 # mm
|
||||
try:
|
||||
@@ -232,7 +232,7 @@ _FOCUS_DISPATCH = {
|
||||
|
||||
|
||||
async def run_critique(
|
||||
backend: "AutoCADBackend",
|
||||
backend: AutoCADBackend,
|
||||
focus: list[CritiqueFocus] | None = None,
|
||||
) -> list[Issue]:
|
||||
"""Dispatch each requested focus check and return aggregated issues."""
|
||||
|
||||
+4
-56
@@ -71,58 +71,6 @@ def _arc_points(
|
||||
return pts
|
||||
|
||||
|
||||
def generate_tooth_profile(
|
||||
module: float,
|
||||
teeth: int,
|
||||
pressure_angle: float = 20.0,
|
||||
n_points_per_flank: int = 40,
|
||||
) -> list[tuple[float, float]]:
|
||||
"""Single tooth profile: right flank, tip arc, mirrored left flank."""
|
||||
pa = math.radians(pressure_angle)
|
||||
pitch_r = module * teeth / 2.0
|
||||
base_r = pitch_r * math.cos(pa)
|
||||
outer_r = pitch_r + module
|
||||
root_r = pitch_r - 1.25 * module
|
||||
|
||||
# Tooth half-thickness angle on the pitch circle.
|
||||
s = math.pi * module / 2.0
|
||||
theta_pitch = s / pitch_r / 2.0
|
||||
|
||||
# Find involute parameter at pitch circle so we can shift the flank to be
|
||||
# symmetric about the tooth centerline (y=0 axis after rotation).
|
||||
if outer_r > base_r:
|
||||
t_pitch = math.sqrt((pitch_r / base_r) ** 2 - 1.0) if pitch_r > base_r else 0.0
|
||||
else:
|
||||
t_pitch = 0.0
|
||||
inv_pitch = math.tan(pa) - pa # involute function at pressure angle
|
||||
# The flank generated by `generate_involute_flank` starts at theta=0 on the
|
||||
# base circle. The involute point at the pitch radius makes angle inv_pitch
|
||||
# (approx). We rotate the flank by -inv_pitch + theta_pitch so the pitch
|
||||
# crossing sits at +theta_pitch above the tooth-center axis.
|
||||
rot = -inv_pitch + theta_pitch
|
||||
|
||||
right_flank = generate_involute_flank(base_r, root_r, outer_r, n_points_per_flank)
|
||||
right_flank = _rotate_points(right_flank, rot)
|
||||
|
||||
# Left flank by mirroring across the X axis (tooth centerline).
|
||||
left_flank = [(x, -y) for x, y in right_flank]
|
||||
|
||||
# Tip arc connects the outermost right-flank point to the outermost left-flank point.
|
||||
tip_right = right_flank[-1]
|
||||
tip_left = left_flank[-1]
|
||||
a_right = math.atan2(tip_right[1], tip_right[0])
|
||||
a_left = math.atan2(tip_left[1], tip_left[0])
|
||||
if a_left > a_right:
|
||||
a_left -= 2 * math.pi
|
||||
tip_arc = _arc_points(0.0, 0.0, outer_r, a_right, a_left, n=5)
|
||||
|
||||
profile: list[tuple[float, float]] = []
|
||||
profile.extend(right_flank)
|
||||
profile.extend(tip_arc[1:-1])
|
||||
profile.extend(reversed(left_flank))
|
||||
return profile
|
||||
|
||||
|
||||
def generate_full_gear_outline(
|
||||
module: float,
|
||||
teeth: int,
|
||||
@@ -200,7 +148,7 @@ def generate_full_gear_outline(
|
||||
|
||||
|
||||
async def _draw_helix_symbol(
|
||||
backend: "AutoCADBackend",
|
||||
backend: AutoCADBackend,
|
||||
*,
|
||||
cx: float,
|
||||
cy: float,
|
||||
@@ -237,7 +185,7 @@ async def _draw_helix_symbol(
|
||||
|
||||
|
||||
async def draw_helical_gear_front_view(
|
||||
backend: "AutoCADBackend",
|
||||
backend: AutoCADBackend,
|
||||
*,
|
||||
module: float,
|
||||
teeth: int,
|
||||
@@ -330,7 +278,7 @@ async def draw_helical_gear_front_view(
|
||||
|
||||
|
||||
async def draw_spur_gear_front_view(
|
||||
backend: "AutoCADBackend",
|
||||
backend: AutoCADBackend,
|
||||
*,
|
||||
module: float,
|
||||
teeth: int,
|
||||
@@ -356,7 +304,7 @@ async def draw_spur_gear_front_view(
|
||||
|
||||
|
||||
async def draw_gear_section_aa(
|
||||
backend: "AutoCADBackend",
|
||||
backend: AutoCADBackend,
|
||||
*,
|
||||
gear_metadata: dict,
|
||||
x_offset: float,
|
||||
|
||||
@@ -56,7 +56,7 @@ def keyway_dimensions(bore_diameter: float) -> dict:
|
||||
|
||||
|
||||
async def draw_keyed_bore(
|
||||
backend: "AutoCADBackend",
|
||||
backend: AutoCADBackend,
|
||||
*,
|
||||
center: tuple[float, float],
|
||||
bore_diameter: float,
|
||||
@@ -110,7 +110,7 @@ async def draw_keyed_bore(
|
||||
|
||||
|
||||
async def draw_keyway_section(
|
||||
backend: "AutoCADBackend",
|
||||
backend: AutoCADBackend,
|
||||
*,
|
||||
center: tuple[float, float],
|
||||
bore_diameter: float,
|
||||
|
||||
@@ -71,7 +71,7 @@ LAYER_SET_REGISTRY: dict[str, list[tuple[str, int, str, float, str]]] = {
|
||||
STANDARD_LINETYPES: list[str] = ["CENTER", "HIDDEN", "PHANTOM", "DASHED", "DASHDOT"]
|
||||
|
||||
|
||||
async def ensure_standard_linetypes(backend: "AutoCADBackend") -> dict[str, str]:
|
||||
async def ensure_standard_linetypes(backend: AutoCADBackend) -> dict[str, str]:
|
||||
"""Idempotently load STANDARD_LINETYPES via backend.linetype_load."""
|
||||
try:
|
||||
existing = {ln.lower() for ln in await backend.linetype_list()}
|
||||
@@ -92,13 +92,13 @@ async def ensure_standard_linetypes(backend: "AutoCADBackend") -> dict[str, str]
|
||||
return results
|
||||
|
||||
|
||||
async def ensure_engineering_layers(backend: "AutoCADBackend") -> dict[str, str]:
|
||||
async def ensure_engineering_layers(backend: AutoCADBackend) -> dict[str, str]:
|
||||
"""Idempotently create every layer in ENGINEERING_LAYERS via backend.layer_create."""
|
||||
return await apply_layer_set(backend, "mech")
|
||||
|
||||
|
||||
async def apply_layer_set(
|
||||
backend: "AutoCADBackend", standard: str = "mech",
|
||||
backend: AutoCADBackend, standard: str = "mech",
|
||||
) -> dict[str, str]:
|
||||
"""Idempotently apply a named layer set ('mech', 'pid', 'iso13567')."""
|
||||
layer_set = LAYER_SET_REGISTRY.get(standard)
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
"""Generic cutting-plane and section-hatch helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from backends.base import AutoCADBackend
|
||||
|
||||
|
||||
async def cutting_plane_line(
|
||||
backend: "AutoCADBackend",
|
||||
*,
|
||||
p1: tuple[float, float],
|
||||
p2: tuple[float, float],
|
||||
label: str = "A",
|
||||
layer: str = "PHANTOM",
|
||||
) -> dict:
|
||||
"""Draw a section line, arrowheads at each end, and 'X-X' labels."""
|
||||
x1, y1 = float(p1[0]), float(p1[1])
|
||||
x2, y2 = float(p2[0]), float(p2[1])
|
||||
|
||||
line = await backend.entity_create_line(x1, y1, x2, y2, layer=layer)
|
||||
|
||||
dx, dy = x2 - x1, y2 - y1
|
||||
length = math.hypot(dx, dy) or 1.0
|
||||
ux, uy = dx / length, dy / length
|
||||
nx, ny = -uy, ux
|
||||
|
||||
arrow_len = max(length * 0.05, 4.0)
|
||||
arrow_w = arrow_len * 0.4
|
||||
arrows: list[str] = []
|
||||
|
||||
for tip_x, tip_y, dir_x, dir_y in (
|
||||
(x1, y1, -ux, -uy),
|
||||
(x2, y2, ux, uy),
|
||||
):
|
||||
bx = tip_x + dir_x * arrow_len
|
||||
by = tip_y + dir_y * arrow_len
|
||||
wing1_x = bx + nx * arrow_w
|
||||
wing1_y = by + ny * arrow_w
|
||||
wing2_x = bx - nx * arrow_w
|
||||
wing2_y = by - ny * arrow_w
|
||||
a1 = await backend.entity_create_line(tip_x, tip_y, wing1_x, wing1_y, layer=layer)
|
||||
a2 = await backend.entity_create_line(tip_x, tip_y, wing2_x, wing2_y, layer=layer)
|
||||
arrows.extend([a1.handle, a2.handle])
|
||||
|
||||
text = f"{label}-{label}"
|
||||
text_h = max(arrow_len, 3.5)
|
||||
offset = arrow_len * 1.5
|
||||
labels: list[str] = []
|
||||
for tip_x, tip_y, dir_x, dir_y in (
|
||||
(x1, y1, -ux, -uy),
|
||||
(x2, y2, ux, uy),
|
||||
):
|
||||
tx = tip_x + dir_x * offset + nx * offset * 0.5
|
||||
ty = tip_y + dir_y * offset + ny * offset * 0.5
|
||||
t = await backend.entity_create_text(text, tx, ty, height=text_h, layer="TEXT")
|
||||
labels.append(t.handle)
|
||||
|
||||
return {"line": line.handle, "arrows": arrows, "labels": labels}
|
||||
|
||||
|
||||
async def apply_section_hatch(
|
||||
backend: "AutoCADBackend",
|
||||
*,
|
||||
boundary_points: list[list[float]],
|
||||
pattern: str = "ANSI31",
|
||||
angle: float = 45.0,
|
||||
scale: float = 1.0,
|
||||
layer: str = "HATCH",
|
||||
) -> str:
|
||||
"""Wrapper around backend.entity_create_hatch returning the new handle."""
|
||||
info = await backend.entity_create_hatch(
|
||||
pattern=pattern,
|
||||
boundary_points=boundary_points,
|
||||
scale=scale,
|
||||
angle=angle,
|
||||
layer=layer,
|
||||
)
|
||||
return info.handle
|
||||
@@ -55,7 +55,7 @@ class TitleBlockMetadata:
|
||||
|
||||
|
||||
async def apply_iso_a3_titleblock(
|
||||
backend: "AutoCADBackend",
|
||||
backend: AutoCADBackend,
|
||||
*,
|
||||
metadata: TitleBlockMetadata,
|
||||
origin: tuple[float, float] = (0.0, 0.0),
|
||||
|
||||
@@ -62,7 +62,7 @@ class DrawingValidator:
|
||||
|
||||
async def run(
|
||||
self,
|
||||
backend: "AutoCADBackend",
|
||||
backend: AutoCADBackend,
|
||||
*,
|
||||
expected: dict | None = None,
|
||||
) -> ValidationResult:
|
||||
|
||||
Reference in New Issue
Block a user