diff --git a/src/draftwright/annotations/holes.py b/src/draftwright/annotations/holes.py index c7346a2a..e15f559c 100644 --- a/src/draftwright/annotations/holes.py +++ b/src/draftwright/annotations/holes.py @@ -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] @@ -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( diff --git a/src/draftwright/evaluation/_hole_family_evidence.py b/src/draftwright/evaluation/_hole_family_evidence.py index 5d379d49..4507d5b6 100644 --- a/src/draftwright/evaluation/_hole_family_evidence.py +++ b/src/draftwright/evaluation/_hole_family_evidence.py @@ -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 @@ -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 @@ -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 diff --git a/src/draftwright/evaluation/_pocket_evidence.py b/src/draftwright/evaluation/_pocket_evidence.py index 1cb5d8d5..b399a471 100644 --- a/src/draftwright/evaluation/_pocket_evidence.py +++ b/src/draftwright/evaluation/_pocket_evidence.py @@ -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: diff --git a/tests/refactor_golden/grid_plate.json b/tests/refactor_golden/grid_plate.json index d0115f35..1a1a5e14 100644 --- a/tests/refactor_golden/grid_plate.json +++ b/tests/refactor_golden/grid_plate.json @@ -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", diff --git a/tests/test_hole_pattern_callouts.py b/tests/test_hole_pattern_callouts.py index 37ce2ebc..fda1a005 100644 --- a/tests/test_hole_pattern_callouts.py +++ b/tests/test_hole_pattern_callouts.py @@ -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")) @@ -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()) @@ -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() @@ -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" ) @@ -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) diff --git a/tests/test_issue_1370_hole_pattern_completeness_evidence.py b/tests/test_issue_1370_hole_pattern_completeness_evidence.py index 94b09641..4f0bd8c6 100644 --- a/tests/test_issue_1370_hole_pattern_completeness_evidence.py +++ b/tests/test_issue_1370_hole_pattern_completeness_evidence.py @@ -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) @@ -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) diff --git a/tests/test_pocket_pattern.py b/tests/test_pocket_pattern.py index 3fc6eb51..8cf58857 100644 --- a/tests/test_pocket_pattern.py +++ b/tests/test_pocket_pattern.py @@ -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"] diff --git a/tests/test_pocket_pattern_completeness_evidence.py b/tests/test_pocket_pattern_completeness_evidence.py index f4b74ff2..acfdb59f 100644 --- a/tests/test_pocket_pattern_completeness_evidence.py +++ b/tests/test_pocket_pattern_completeness_evidence.py @@ -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"] diff --git a/tests/test_slot_completeness.py b/tests/test_slot_completeness.py index 8c75585f..7b7ca964 100644 --- a/tests/test_slot_completeness.py +++ b/tests/test_slot_completeness.py @@ -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 diff --git a/tests/test_slot_pattern.py b/tests/test_slot_pattern.py index 3eeeb965..5c072c65 100644 --- a/tests/test_slot_pattern.py +++ b/tests/test_slot_pattern.py @@ -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"