draftwright 0.1.1__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: draftwright
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Automated technical-drawing generation for build123d
5
5
  Project-URL: Homepage, https://github.com/pzfreo/draftwright
6
6
  Project-URL: Repository, https://github.com/pzfreo/draftwright
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "draftwright"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  description = "Automated technical-drawing generation for build123d"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -810,6 +810,29 @@ def _analyse(step_file, title, number, tolerance, drawn_by, out, scale=None, pag
810
810
  ISO_X = sv_right + right_avail / 2
811
811
  ISO_Y = PV_Y
812
812
 
813
+ # When the standard iso zone (right of SV) is narrower than the natural iso
814
+ # extent, check whether the upper-right zone — right of FV/PV and above the SV
815
+ # y-range — offers more room. This zone shares no y-range with the SV, so the
816
+ # iso can sit there without conflicting with SV annotations.
817
+ _iso_natural = bbox_max * SCALE * 0.7
818
+ _ur_left = FV_X + fv_hw + gap_fv_sv # = sv_left_edge; clears FV/PV right strips
819
+ _sv_top = FV_Y + fv_hh # SV_Y == FV_Y; sv_top == FV_Y + fv_hh
820
+ _ur_bottom = _sv_top + DIM_PAD
821
+ _ur_w = max(0.0, iso_right_limit - _ur_left)
822
+ _ur_h = max(0.0, (PAGE_H - margin) - _ur_bottom)
823
+ _std_min = min(right_avail, PAGE_H - 2 * margin)
824
+ _ur_min = min(_ur_w, _ur_h)
825
+ if _std_min < _iso_natural and _ur_min > _std_min:
826
+ ISO_X = (_ur_left + iso_right_limit) / 2
827
+ ISO_Y = (_ur_bottom + PAGE_H - margin) / 2
828
+ iso_left_limit = _ur_left
829
+ iso_bottom_limit = _ur_bottom
830
+ iso_in_upper_right = True
831
+ else:
832
+ iso_left_limit = sv_right
833
+ iso_bottom_limit = margin
834
+ iso_in_upper_right = False
835
+
813
836
  # ------------------------------------------------------------------
814
837
  # Strip / zone construction.
815
838
  # Phase 1: defines regions only — annotation functions still use their
@@ -909,6 +932,9 @@ def _analyse(step_file, title, number, tolerance, drawn_by, out, scale=None, pag
909
932
  SV_Y=SV_Y,
910
933
  ISO_X=ISO_X,
911
934
  ISO_Y=ISO_Y,
935
+ iso_left_limit=iso_left_limit,
936
+ iso_bottom_limit=iso_bottom_limit,
937
+ iso_in_upper_right=iso_in_upper_right,
912
938
  # View half-extents in page units (convenient for strip arithmetic)
913
939
  fv_hw=fv_hw,
914
940
  fv_hh=fv_hh,
@@ -1254,15 +1280,22 @@ def _auto_annotate(dwg, a):
1254
1280
  return a.PV_Y + (y - a.cy) * a.SCALE
1255
1281
 
1256
1282
  # Tighten right-strip outer_limits to the actual iso view left edge now
1257
- # that the iso has been projected and fitted. Guard: only apply if the
1258
- # limit is above the strip cursor an overflowing iso can produce
1259
- # _iso_x_limit < cursor, which would silence every right-strip allocation.
1283
+ # that the iso has been projected and fitted. Always apply so that any
1284
+ # future allocations are bounded; warn when the cursor has already passed
1285
+ # the limit (dims already placed may overlap the iso view).
1260
1286
  _iso_x0, _, _, _ = _iso_bbox(dwg)
1261
1287
  _iso_x_limit = _iso_x0 - 4
1262
- for _rs in (a.fv_zones.right, a.pv_zones.right, a.sv_zones.right):
1263
- if _iso_x_limit > _rs._cursor:
1264
- _rs.outer_limit = min(_rs.outer_limit, _iso_x_limit)
1265
- else:
1288
+ # When the iso sits above the SV (upper-right zone), the SV right strip shares
1289
+ # no y-range with the iso, so tightening sv_zones.right by iso_x would set
1290
+ # outer_limit below the strip anchor and break all SV annotation allocations.
1291
+ _right_strips = (
1292
+ (a.fv_zones.right, a.pv_zones.right)
1293
+ if a.iso_in_upper_right
1294
+ else (a.fv_zones.right, a.pv_zones.right, a.sv_zones.right)
1295
+ )
1296
+ for _rs in _right_strips:
1297
+ _rs.outer_limit = min(_rs.outer_limit, _iso_x_limit)
1298
+ if _rs._cursor >= _iso_x_limit:
1266
1299
  _log.warning(
1267
1300
  "right-strip cursor %.1f >= iso_x limit %.1f: right-strip dims"
1268
1301
  " may overlap iso view (iso view overflows into annotation zone)",
@@ -2663,25 +2696,31 @@ def _project_iso(dwg, a, scale, shape_s=None):
2663
2696
  dwg._coords["iso"] = ViewCoordinates(axes, a.ISO_X, a.ISO_Y, a.cx, a.cy, a.cz, scale)
2664
2697
 
2665
2698
 
2666
- def _fit_iso_view(dwg, a):
2667
- """Shrink the iso view to fit its page region, captioning it NTS (#75).
2699
+ def _fit_iso_view(dwg, a, annotate: bool = True):
2700
+ """Scale the iso view to fill its page zone, captioning it NTS when the
2701
+ scale differs from sheet scale. Pass ``annotate=False`` to suppress the
2702
+ NTS note (used when ``auto_dims=False``).
2668
2703
 
2669
- The layout reserves ~0.7 × bbox_max for the iso column, but the true
2670
- projected extent can be wider (long prismatic parts), pushing the iso past
2671
- the page edge or into the side view's dimension space. When the projected
2672
- iso bbox overflows the region, re-project at a clean fraction of sheet
2673
- scale and add an "ISO VIEW (NTS)" caption below it.
2704
+ The iso is always centred at (ISO_X, ISO_Y) which sits at the centre of
2705
+ the available zone. The projection is linear, so the factor needed to
2706
+ fill the zone can be computed from the measured extents without iteration.
2707
+
2708
+ - Overflow (needed < 1): shrink with 2 % safety margin.
2709
+ - Under-fill (needed > 1): grow to 90 % of zone, leaving breathing room.
2710
+ - Within 5 % of sheet scale: leave as-is (no NTS label).
2674
2711
  """
2675
- region = (a.sv_right, a.margin, a.iso_right_limit, a.PAGE_H - a.margin)
2712
+ # Use the precomputed iso zone limits. When iso is in the upper-right zone,
2713
+ # any section view sits below the iso's y-range so its x-extent doesn't
2714
+ # constrain the iso region.
2715
+ region_left = a.iso_left_limit
2716
+ if not a.iso_in_upper_right and "section_aa" in dwg.views:
2717
+ sec_vis, sec_hid = dwg.views["section_aa"]
2718
+ sec_right = sec_vis.bounding_box().max.X
2719
+ if sec_hid:
2720
+ sec_right = max(sec_right, sec_hid.bounding_box().max.X)
2721
+ region_left = max(region_left, sec_right + 4)
2722
+ region = (region_left, a.iso_bottom_limit, a.iso_right_limit, a.PAGE_H - a.margin)
2676
2723
  bb = _iso_bbox(dwg)
2677
- # Exact check (no tolerance): the lint's view_out_of_bounds is exact, so
2678
- # accepting a sub-tolerance overflow here would pass the fit yet fail lint.
2679
- if _bbox_within(bb, region, tol=0.0):
2680
- return
2681
- # Orthographic projection is linear and the view centre maps to
2682
- # (ISO_X, ISO_Y), so each bbox side's offset from the centre scales
2683
- # exactly with the shape scale — the factor needed to fit can be computed
2684
- # from the measured extents, costing a single re-projection.
2685
2724
  ratios = [
2686
2725
  avail / extent
2687
2726
  for extent, avail in (
@@ -2693,21 +2732,32 @@ def _fit_iso_view(dwg, a):
2693
2732
  if extent > 0
2694
2733
  ]
2695
2734
  needed = min(ratios, default=1.0)
2696
- factor = next((f for f in _ISO_SHRINK_FACTORS if f <= needed), _ISO_SHRINK_FACTORS[-1])
2735
+ if needed >= 1.0:
2736
+ # Iso fits; grow to 90 % of zone — leaves comfortable breathing room.
2737
+ margin_pct = 0.90
2738
+ else:
2739
+ # Iso overflows; shrink to just fit with 2 % safety margin.
2740
+ margin_pct = 0.98
2741
+ factor = math.floor(needed * margin_pct * 10000) / 10000
2742
+ if needed >= 1.0:
2743
+ factor = max(factor, 1.0) # grow branch must never shrink
2744
+ if abs(factor - 1.0) < 0.05:
2745
+ return # within 5 % of sheet scale — no rescale, no NTS label
2697
2746
  _project_iso(dwg, a, a.SCALE * factor)
2698
2747
  bb = _iso_bbox(dwg)
2699
- if not _bbox_within(bb, region):
2748
+ if factor < 1.0 and not _bbox_within(bb, region):
2700
2749
  _log.warning("Iso view still overflows its page region at %g× sheet scale", factor)
2701
- font = dwg.draft.font_size
2702
- dwg.add(
2703
- Note(
2704
- "ISO VIEW (NTS)",
2705
- (a.ISO_X, max(bb[1] - 2 * font, a.margin + font)),
2706
- dwg.draft,
2707
- ),
2708
- "note_iso_nts",
2709
- )
2710
- _log.info("Iso view shrunk to %g× sheet scale (NTS)", factor)
2750
+ if annotate:
2751
+ font = dwg.draft.font_size
2752
+ dwg.add(
2753
+ Note(
2754
+ "ISO VIEW (NTS)",
2755
+ (a.ISO_X, max(bb[1] - 2 * font, a.margin + font)),
2756
+ dwg.draft,
2757
+ ),
2758
+ "note_iso_nts",
2759
+ )
2760
+ _log.info("Iso view scaled to %g× sheet scale%s", factor, " (NTS)" if annotate else "")
2711
2761
 
2712
2762
 
2713
2763
  def build_drawing(
@@ -2777,11 +2827,24 @@ def build_drawing(
2777
2827
  dwg.add_view("plan", part_s, (cxs, cys, czs + dist), (0, 1, 0), (a.PV_X, a.PV_Y), scaled=True)
2778
2828
  dwg.add_view("side", part_s, (cxs + dist, cys, czs), (0, 0, 1), (a.SV_X, a.SV_Y), scaled=True)
2779
2829
  _project_iso(dwg, a, a.SCALE, shape_s=part_s)
2780
- _fit_iso_view(dwg, a)
2781
2830
 
2782
2831
  if auto_dims:
2832
+ # Snapshot outer_limits before _auto_annotate tightens them against the
2833
+ # initial (possibly overflowing) iso. After _fit_iso_view rescales the
2834
+ # iso we restore all three right strips to min(original, final_iso_x_limit)
2835
+ # so each strip reflects actual final geometry, not the transient state.
2836
+ _fv_ol = a.fv_zones.right.outer_limit
2837
+ _pv_ol = a.pv_zones.right.outer_limit
2838
+ _sv_ol = a.sv_zones.right.outer_limit
2783
2839
  _auto_annotate(dwg, a)
2840
+ _fit_iso_view(dwg, a)
2841
+ _final_iso_x_lim = _iso_bbox(dwg)[0] - 4
2842
+ a.fv_zones.right.outer_limit = min(_fv_ol, _final_iso_x_lim)
2843
+ a.pv_zones.right.outer_limit = min(_pv_ol, _final_iso_x_lim)
2844
+ if not a.iso_in_upper_right:
2845
+ a.sv_zones.right.outer_limit = min(_sv_ol, _final_iso_x_lim)
2784
2846
  else:
2847
+ _fit_iso_view(dwg, a, annotate=False)
2785
2848
  _add_title_block(dwg, a)
2786
2849
  return dwg
2787
2850
 
@@ -1135,49 +1135,55 @@ def test_clear_annotations_keep_custom_and_unnamed_removed():
1135
1135
 
1136
1136
 
1137
1137
  @pytest.fixture(scope="module")
1138
- def shrunk_iso_drawing():
1139
- # #75 fixture — NIST CTC-01-like plate at 1:5 on A3: the iso overflows at
1140
- # sheet scale and is auto-shrunk. Module-scoped; tests must not mutate it.
1138
+ def ctc01_a3_drawing():
1139
+ # Fixture — NIST CTC-01-like plate at 1:5 on A3. Module-scoped; tests must not mutate it.
1141
1140
  return build_drawing(Box(800, 450, 150), scale=0.2, page="A3")
1142
1141
 
1143
1142
 
1144
1143
  @pytest.mark.timeout(120)
1145
- def test_iso_overflow_shrinks_with_nts_note(shrunk_iso_drawing):
1146
- # #75 — at sheet scale the iso would run past the A3 page edge; it must be
1147
- # re-projected smaller and captioned NTS.
1144
+ def test_ctc01_iso_uses_upper_right_zone(ctc01_a3_drawing):
1145
+ # #75 updated wide/flat part on A3: the iso is repositioned into the upper-right
1146
+ # zone (above the SV, right of FV/PV) where it fits at sheet scale. No NTS label.
1148
1147
  from draftwright.make_drawing import _iso_bbox
1149
1148
 
1150
- dwg = shrunk_iso_drawing
1149
+ dwg = ctc01_a3_drawing
1151
1150
  labels = [getattr(a, "label", "") for a in dwg.annotations]
1152
- assert "ISO VIEW (NTS)" in labels
1151
+ assert "ISO VIEW (NTS)" not in labels # iso now fits at sheet scale — no NTS
1153
1152
  x0, y0, x1, y1 = _iso_bbox(dwg)
1154
1153
  assert (
1155
1154
  x1 <= dwg.page_w - 10 + 0.5 and x0 >= 0 and y0 >= 10 - 0.5 and y1 <= dwg.page_h - 10 + 0.5
1156
1155
  )
1156
+ # iso must be significantly larger than the old 65 mm (shrunken) view
1157
+ assert (x1 - x0) > 100
1157
1158
 
1158
1159
 
1159
1160
  @pytest.mark.timeout(120)
1160
- def test_shrunk_iso_keeps_world_to_page_mapping(shrunk_iso_drawing):
1161
- # After the NTS shrink, dwg.at("iso", ...) must still map world points to
1162
- # the page: the centroid lands on the view centre and offsets scale by the
1163
- # shrunk view scale, not the sheet scale.
1164
- dwg = shrunk_iso_drawing
1161
+ def test_ctc01_iso_world_to_page_mapping(ctc01_a3_drawing):
1162
+ # dwg.at("iso", ...) must map world points to page even after the iso is
1163
+ # repositioned to the upper-right zone (still projected at sheet scale).
1164
+ dwg = ctc01_a3_drawing
1165
1165
  cx, cy, cz = dwg.centroid
1166
1166
  centre = dwg.at("iso", cx, cy, cz)
1167
1167
  vis, _hid = dwg.views["iso"]
1168
1168
  bb = vis.bounding_box()
1169
1169
  assert bb.min.X < centre[0] < bb.max.X and bb.min.Y < centre[1] < bb.max.Y
1170
- # This fixture shrinks the iso to 1/2 sheet scale (see the NTS test above).
1171
- # World +Z maps to page +Y; the offset must use the shrunk view scale.
1170
+ iso_scale = dwg._coords["iso"]._scale
1172
1171
  raised = dwg.at("iso", cx, cy, cz + 100)
1173
- assert raised[1] - centre[1] == pytest.approx(100 * dwg.scale * 0.5)
1172
+ assert raised[1] - centre[1] == pytest.approx(100 * iso_scale)
1174
1173
 
1175
1174
 
1176
1175
  @pytest.mark.timeout(60)
1177
- def test_iso_that_fits_is_not_shrunk():
1176
+ def test_iso_stays_within_page_bounds():
1177
+ # Whether scaled up or not, the iso must always lie within the page margin.
1178
+ from draftwright.make_drawing import _iso_bbox
1179
+
1178
1180
  dwg = build_drawing(Box(30, 20, 10))
1179
- labels = [getattr(a, "label", "") for a in dwg.annotations]
1180
- assert "ISO VIEW (NTS)" not in labels
1181
+ x0, y0, x1, y1 = _iso_bbox(dwg)
1182
+ margin = 10
1183
+ assert x0 >= margin - 0.5
1184
+ assert y0 >= margin - 0.5
1185
+ assert x1 <= dwg.page_w - margin + 0.5
1186
+ assert y1 <= dwg.page_h - margin + 0.5
1181
1187
 
1182
1188
 
1183
1189
  @pytest.mark.timeout(60)
File without changes
File without changes
File without changes
File without changes
File without changes