Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/draftwright/annotations/holes.py
Original file line number Diff line number Diff line change
Expand Up @@ -823,9 +823,9 @@ def _place_pitch_dim(
*,
ctx,
):
"""Pitch dimension between two hole-centre *locations* ``loc1``→``loc2``, labelled
``(n-1)× pitch``, placed just outside the view on the side of the row's
outward perpendicular (#92). *feature* attributes it to the source pattern (#408)."""
"""Pitch between hole centres: ``pitch`` for one gap, else ``(n-1)× pitch``.
Place it outside the view on the row's outward perpendicular (#92); *feature*
attributes it to the source pattern (#408)."""
p1 = to_page(loc1)
p2 = to_page(loc2)
ux, uy = p2[0] - p1[0], p2[1] - p1[1]
Expand Down Expand Up @@ -860,7 +860,7 @@ def _place_pitch_dim(
# A uniform array's ± applies to each identical gap, so `4× 20 ±0.05` is coherent; that is
# unlike the STEP representative, whose levels merely fall within 10% of each other, where
# a ± would claim the tolerance of values that differ.
label = f"{n - 1}× {pitch_text}"
label = str(pitch_text) if n == 2 else f"{n - 1}× {pitch_text}"

def _make(off, side_vec=side, label_offset_x=0.0):
return _dim(
Expand Down
14 changes: 11 additions & 3 deletions src/draftwright/evaluation/_hole_family_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,15 @@ def _pattern_model_outcomes(patterns, recognition, features) -> list[Outcome]:
return result


def _pitch_interval_label_matches(label: object, expected: int) -> bool:
"""A single gap has a bare value; longer spans state their interval count."""
text = str(label or "")
if expected == 1:
return re.match(r"^\s*\d+(?:\.\d+)?(?:\s|$)", text) is not None
match = re.match(r"^\s*(\d+)\s*[×x]\s", text)
return match is not None and int(match.group(1)) == expected


def _pattern_drawing_outcomes(patterns, drawing) -> list[Outcome]:
"""Per recognised pattern: did its grouping grammar reach the placed drawing?"""
from draftwright.linting.evidence import verify_measurement_claims
Expand Down Expand Up @@ -443,7 +452,7 @@ def rendered_group_count(outcome) -> bool:
return False

def rendered_interval_count(outcome, expected: int) -> bool:
"""Whether the exact pitch dimension renders its required interval multiplier."""
"""Whether the exact pitch dimension renders its required interval count."""
for name, annotation in drawing.registry.iter_named():
owns_pitch = any(
getattr(measurement, "feature", None) in outcome.features
Expand All @@ -455,8 +464,7 @@ def rendered_interval_count(outcome, expected: int) -> bool:
label = getattr(annotation, "label", None) or getattr(
annotation, "_annotate_label", None
)
match = re.match(r"^\s*(\d+)\s*[×x]\s", str(label or ""))
if match is not None and int(match.group(1)) == expected:
if _pitch_interval_label_matches(label, expected):
return True
return False

Expand Down
3 changes: 2 additions & 1 deletion src/draftwright/evaluation/_pocket_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,8 @@ def _pocket_pattern_drawing_outcomes(patterns, drawing) -> list[Outcome]:
round(gap, drawing.draft.decimal_precision) == nominal for gap in gaps
):
suffix = _tol_suffix(approved.tolerance, drawing.draft)
pitch_labels[(feature, parameter)] = f"{intervals}× {approved.value_text}{suffix}"
prefix = "" if intervals == 1 else f"{intervals}× "
pitch_labels[(feature, parameter)] = f"{prefix}{approved.value_text}{suffix}"

location_labels: dict[tuple[object, str], str] = {}
for approved in plan.locations:
Expand Down
6 changes: 3 additions & 3 deletions tests/refactor_golden/grid_plate.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@
149.4,
215.5
],
"label": "1\u00d7 20",
"label": "20",
"label_bbox": [
130.4,
142.0,
212.4,
139.1,
145.2,
214.6
],
"name": "dim_pitch_plan0_1",
Expand Down
14 changes: 7 additions & 7 deletions tests/test_hole_pattern_callouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ def test_four_corner_rectangle_states_two_pitches_without_a_bolt_circle(self):
annotation.label
for name, annotation in drawing.iter_annotations()
if name.startswith("dim_pitch_")
} == {"1× 20", "1× 30"}
} == {"20", "30"}
assert drawing.lint() == []

@pytest.mark.parametrize("variant", ("a", "b"))
Expand All @@ -315,7 +315,7 @@ def test_diagonal_square_grid_pitches_survive_the_exact_ink_gate(self, variant):
grid_pitches = [
annotation
for name, annotation in drawing.iter_annotations()
if name.startswith("dim_pitch_plan") and annotation.label == "1× 22.6"
if name.startswith("dim_pitch_plan") and annotation.label == "22.6"
]
assert len(grid_pitches) == 2
assert not any(issue.code == "hole_pattern_dim_dropped" for issue in drawing.lint())
Expand All @@ -336,9 +336,9 @@ def test_rect_grid_one_callout_and_two_pitch_dims(self):
assert len(hc) == 1, f"expected one grouped callout, got {hc}"
assert named[hc[0]].covers_count == 8
assert named[hc[0]].covers_diameters == (8.0,)
# both grid pitch dimensions, labelled (n-1)× pitch
# Both grid pitches survive; one gap prints its value without a multiplier.
assert len(pitch) == 2, f"expected two pitch dims, got {pitch}"
assert {named[n].label for n in pitch} == {"1× 20", "3× 25"}
assert {named[n].label for n in pitch} == {"20", "3× 25"}
# each dim runs ALONG one lattice axis — its endpoints share a coordinate
# — not diagonally across the grid; and the two are perpendicular.
axes = set()
Expand Down Expand Up @@ -373,8 +373,8 @@ def test_rect_grid_pitch_dims_not_diagonal_when_rotated(self):
dim = dwg.get_annotation(n)
sp = dim.placement_spec
span = math.hypot(sp.p2[0] - sp.p1[0], sp.p2[1] - sp.p1[1]) / scale
k, p = dim.label.split("× ")
expected = int(k) * float(p)
parts = dim.label.split("× ")
expected = float(parts[0]) if len(parts) == 1 else int(parts[0]) * float(parts[1])
assert abs(span - expected) < 1.0, (
f"{n} ({dim.label!r}) endpoint span {span:.1f} ≠ {expected:.1f} — drawn diagonally"
)
Expand All @@ -398,7 +398,7 @@ def test_x_axis_rect_grid_keeps_both_pitches_in_the_side_view(self):

assert len(pitch) == 2, f"expected two side-grid pitch dims, got {pitch}"
assert {dwg.view_of(name) for name in pitch} == {"side"}
assert {dwg.get_annotation(name).label for name in pitch} == {"1× 10", "4× 45"}
assert {dwg.get_annotation(name).label for name in pitch} == {"10", "4× 45"}
assert "hole_pattern_dim_dropped" not in {issue.code for issue in dwg.lint()}

@pytest.mark.timeout(120)
Expand Down
21 changes: 10 additions & 11 deletions tests/test_issue_1370_hole_pattern_completeness_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,8 @@ def with_wrong_pitch_ink(*args, **kwargs):
assert names, "fixture must place pitch dimensions"
for name in names:
dimension = drawing.registry.named(name)
prefix, _nominal = dimension.label.split(" ", 1)
assert prefix.endswith("×")
dimension.label = f"{prefix} 9999 WRONG"
assert dimension.label in {"20", "2× 15"}
dimension.label = "9999 WRONG"
return drawing

monkeypatch.setattr(builder, "build_drawing", with_wrong_pitch_ink)
Expand Down Expand Up @@ -218,20 +217,20 @@ def with_wrong_compound_nominals(*args, **kwargs):


def test_wrong_placed_grid_interval_count_loses_drawing_credit(monkeypatch) -> None:
import re

import draftwright.builder as builder

original = builder.build_drawing

def with_wrong_interval_count(*args, **kwargs):
drawing = original(*args, **kwargs)
names = [name for name in drawing.annotations() if name.startswith("dim_pitch_")]
assert names, "fixture must place pitch dimensions"
for name in names:
dimension = drawing.registry.named(name)
assert re.match(r"^\d+× ", dimension.label)
dimension.label = re.sub(r"^\d+× ", "9× ", dimension.label)
pitches = [
drawing.registry.named(name)
for name in drawing.annotations()
if name.startswith("dim_pitch_")
]
assert {pitch.label for pitch in pitches} == {"20", "2× 15"}
single_gap = next(pitch for pitch in pitches if pitch.label == "20")
single_gap.label = "9× 20"
return drawing

monkeypatch.setattr(builder, "build_drawing", with_wrong_interval_count)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pocket_pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def test_grid_pattern_renders_both_pitch_dims():
"6× 8 × 8 × 4 DEEP"
]
pitch_labels = sorted(dwg.get_annotation(n).label for n in names if "pitch" in n)
assert pitch_labels == ["1× 30", "2× 40"] # (rows-1)× row_pitch, (cols-1)× col_pitch
assert pitch_labels == ["2× 40", "30"] # one gap reads directly; repeated gaps retain count
assert not [x for x in dwg.lint() if x.code == "annotation_out_of_bounds"]


Expand Down
2 changes: 1 addition & 1 deletion tests/test_pocket_pattern_completeness_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ def test_legitimate_pitch_tolerance_survives_exact_drawing_observation() -> None
for name in drawing.registry.names()
if name.startswith("dim_pocketpat_pitch")
}
assert labels == {"1× 32 ±0.2", "2× 24 ±0.2"}
assert labels == {"32 ±0.2", "2× 24 ±0.2"}
assert _pocket_pattern_drawing_outcomes(
tuple(drawing.recognition().section_recess_patterns), drawing
) == ["supported"]
Expand Down
8 changes: 4 additions & 4 deletions tests/test_slot_completeness.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ def test_ctc_left_slot_position_survives_grid_pitch_carve(monkeypatch):
for row in (0, 1)
for col in (0, 1)
} == {
"1× 90",
"1× 320",
"1× 350",
"1× 650",
"90",
"320",
"350",
"650",
}
assert {
drawing.get_annotation(name).label
Expand Down
2 changes: 1 addition & 1 deletion tests/test_slot_pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def test_grid_pattern_accounts_for_both_pitch_dimensions():
"6× SLOT 6 × 12"
]
pitch_labels = sorted(dwg.get_annotation(n).label for n in names if "slotpat_pitch" in n)
assert pitch_labels == ["1× 30"]
assert pitch_labels == ["30"]
pattern = next(feature for feature in dwg.model().features if feature.kind == "slot_pattern")
assert [
parameter.value for parameter in pattern.parameters() if parameter.role == "grid_pitch"
Expand Down
Loading