nodeslicer/core/pdf_exporter/sizing.py
2026-08-03 13:24:59 +03:00

112 lines
3.8 KiB
Python

"""
Dynamic Dimensioning and Scaled Metrics Engine for PDF Exporter.
Centralizes all auto-scaling calculations for grid steps, legend ticks, arrows,
line widths, font sizes, and SA2 technical measure offsets.
"""
import math
import numpy as np
MM_TO_PT = 2.834645669291339
def compute_grid_steps(w_geom: float, h_geom: float) -> tuple[float, float]:
"""
Computes human-readable main_step (mm) and minor_step (mm) for grid overlay.
"""
L = max(w_geom, h_geom)
if L > 1e-6:
step_raw = L / 10.0
exponent = int(np.round(np.log10(step_raw)))
main_step = 10.0 ** exponent
else:
main_step = 10.0
minor_step = main_step / 10.0
return main_step, minor_step
def compute_effective_stroke_and_font(
line_width_mm: float,
font_size_mm: float,
minor_step_mm: float
) -> tuple[float, float]:
"""
Calculates line_width_pt and font_size_pt proportionally based on minor_step_mm.
Scales continuously from millimetric components up to large structural models.
"""
if line_width_mm <= 0:
line_width_pt = minor_step_mm * 0.05 * MM_TO_PT
else:
line_width_pt = line_width_mm * MM_TO_PT
if font_size_mm <= 0:
font_size_pt = minor_step_mm * MM_TO_PT
else:
font_size_pt = font_size_mm * MM_TO_PT
return line_width_pt, font_size_pt
class LegendSizing:
"""
Auto-scaling calculations for the Size Scale Legend (bottom-left grid scale bar).
"""
def __init__(self, main_step_mm: float, minor_step_mm: float, line_width_pt: float, font_size_pt: float):
self.main_step = main_step_mm
self.minor_step = minor_step_mm
self.line_width_pt = line_width_pt
self.font_size_pt = font_size_pt
# Tick heights in mm proportional to main grid step
self.major_tick_mm = main_step_mm * 0.10
self.minor_tick_mm = main_step_mm * 0.05
# Y-axis height in mm (extends comfortably past major tick height)
self.y_axis_height_mm = self.major_tick_mm * 2.0
# X-axis extension past last main step tick in mm
self.x_axis_extension_mm = minor_step_mm * 2.0
# Legend Arrowhead dimensions in PT (proportional to line width)
self.arrow_len_pt = max(0.5, line_width_pt * 12.0)
self.arrow_half_w_pt = self.arrow_len_pt * 0.35
self.label_margin_h_pt = self.minor_tick_mm * 1.0
self.label_margin_w_pt = self.minor_tick_mm * 1.5
class SA2Sizing:
"""
Auto-scaling calculations for SA2 technical measurement lines and feature callouts.
"""
def __init__(self, w_obj_mm: float, h_obj_mm: float, line_width_pt: float, font_size_pt: float):
self.w_obj = w_obj_mm
self.h_obj = h_obj_mm
self.line_width_pt = line_width_pt
self.font_size_pt = font_size_pt
self.obj_ref_size = min(w_obj_mm, h_obj_mm) if min(w_obj_mm, h_obj_mm) > 0 else max(w_obj_mm, h_obj_mm)
# Dimension line offset & extension line scaling (mm) proportional to object size
self.offset_mm = self.obj_ref_size * 0.10
self.ext_overhang_mm = self.offset_mm * 0.20
self.ext_gap_mm = self.offset_mm * 0.10
# Measure arrow dimensions (PT)
self.arrow_len_pt = max(0.5, line_width_pt * 4.0)
self.arrow_half_w_pt = self.arrow_len_pt * 0.35
def get_hole_callout_lengths(self, dia_mm: float) -> tuple[float, float]:
slant_mm = dia_mm * 0.30
leader_mm = dia_mm * 0.40
return slant_mm, leader_mm
def get_arc_callout_lengths(self, r_mm: float) -> tuple[float, float]:
slant_mm = r_mm * 0.30
leader_mm = r_mm * 0.40
return slant_mm, leader_mm
def get_chamfer_callout_lengths(self, c_val_mm: float) -> tuple[float, float]:
slant_mm = c_val_mm * 1.0
leader_mm = c_val_mm * 1.5
return slant_mm, leader_mm