fix: correct polyline arc direction, text precision, Face3D edge color, buffer chunking

- fix: polyline arc center sign was inverted (- → +) causing arcs to render on the
  wrong side in LwPolyline and Polyline2D
- fix: store glyph strokes in local space with f64 origin so world_offset subtraction
  uses f64 precision; prevents blocky text at large UTM coordinates
- fix: Face3D fill darkened to 45% so edge wires are visually distinct from fill
- fix: wire batch vertex buffer chunked to 256 MB GPU limit (was panic on large files)
- fix: gpu_face3d_edges changed from Option<WireGpu> to Vec<WireGpu> to support chunks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-04-29 20:58:44 +03:00
commit 1a19a841e6
11 changed files with 136 additions and 113 deletions

View file

@ -5,7 +5,7 @@ use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip};
use crate::entities::text_support::resolve_text_style;
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection};
use crate::scene::wire_model::SnapHint;
use crate::scene::{cxf, transform};
@ -26,8 +26,9 @@ impl TruckConvertible for AttributeDefinition {
self.default_value.clone()
};
let wf = (self.width_factor as f32).max(0.01);
let origin = [self.insertion_point.x, self.insertion_point.y];
let strokes = cxf::tessellate_text_ex(
[self.insertion_point.x as f32, self.insertion_point.y as f32],
[0.0, 0.0],
self.height as f32,
self.rotation as f32,
wf * resolved.width_factor.max(0.01),
@ -36,7 +37,7 @@ impl TruckConvertible for AttributeDefinition {
&display,
);
Some(TruckEntity {
object: TruckObject::Text(strokes),
object: TruckObject::Text(vec![TextStroke { strokes, origin }]),
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![],
key_vertices: vec![],
@ -128,8 +129,9 @@ impl TruckConvertible for AttributeEntity {
);
let resolved = resolve_text_style(&self.text_style, document);
let wf = (self.width_factor as f32).max(0.01);
let origin = [self.insertion_point.x, self.insertion_point.y];
let strokes = cxf::tessellate_text_ex(
[self.insertion_point.x as f32, self.insertion_point.y as f32],
[0.0, 0.0],
self.height as f32,
self.rotation as f32,
wf * resolved.width_factor.max(0.01),
@ -138,7 +140,7 @@ impl TruckConvertible for AttributeEntity {
&self.value,
);
Some(TruckEntity {
object: TruckObject::Text(strokes),
object: TruckObject::Text(vec![TextStroke { strokes, origin }]),
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![],
key_vertices: vec![],

View file

@ -27,8 +27,8 @@ fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] {
let py = dx / d;
let sign = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
let cx = mx - sign * px * (r - h);
let cy = my - sign * py * (r - h);
let cx = mx + sign * px * (r - h);
let cy = my + sign * py * (r - h);
let a0 = (p0[1] - cy).atan2(p0[0] - cx);
let a1 = (p1[1] - cy).atan2(p1[0] - cx);
let (sa, mut ea) = if bulge > 0.0 { (a0, a1) } else { (a1, a0) };
@ -112,8 +112,8 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
let py = dx / len;
let sagitta_sign = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
let cx = mx - sagitta_sign * px * (r - h);
let cy = my - sagitta_sign * py * (r - h);
let cx = mx + sagitta_sign * px * (r - h);
let cy = my + sagitta_sign * py * (r - h);
let mid_a = {
let a0 = (p0.y - cy).atan2(p0.x - cx);
let a1 = (p1.y - cy).atan2(p1.x - cx);

View file

@ -7,7 +7,7 @@ use crate::entities::text_support::{
measure_mtext_chars, resolve_text_style, split_mtext_lines, strip_mtext_codes, word_wrap,
};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
use crate::scene::cxf;
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::wire_model::SnapHint;
@ -122,26 +122,22 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
let vertical_text = matches!(t.drawing_direction, DrawingDirection::TopToBottom);
let rot = t.rotation as f32;
let (cos_r, sin_r) = (rot.cos(), rot.sin());
let insertion = Vec3::new(
t.insertion_point.x as f32,
t.insertion_point.y as f32,
t.insertion_point.z as f32,
);
let mut all_strokes = Vec::new();
let ins_x = t.insertion_point.x;
let ins_y = t.insertion_point.y;
let insertion = Vec3::new(ins_x as f32, ins_y as f32, t.insertion_point.z as f32);
let mut all_strokes: Vec<TextStroke> = Vec::new();
for (i, line) in lines.iter().enumerate() {
let li = i as f32;
let (ox, oy) = if vertical_text {
// Compute line offset as f32 (small relative values), apply to f64 insertion point.
let (small_x, small_y) = if vertical_text {
let col_offset = li * t.height as f32 * 1.2;
(
t.insertion_point.x as f32 + col_offset * cos_r + v_offset * (-sin_r),
t.insertion_point.y as f32 + col_offset * sin_r + v_offset * cos_r,
col_offset * cos_r + v_offset * (-sin_r),
col_offset * sin_r + v_offset * cos_r,
)
} else {
let line_y = -(li * line_h) + v_offset;
(
t.insertion_point.x as f32 + line_y * (-sin_r),
t.insertion_point.y as f32 + line_y * cos_r,
)
(line_y * (-sin_r), line_y * cos_r)
};
let line_w = if h_anchor > 0.0 {
let scale = t.height as f32 / 9.0 * style_width_factor;
@ -150,10 +146,12 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
0.0
};
let h_shift = -line_w * h_anchor;
let origin_x = ox + h_shift * cos_r;
let origin_y = oy + h_shift * sin_r;
let origin: [f64; 2] = [
ins_x + (small_x + h_shift * cos_r) as f64,
ins_y + (small_y + h_shift * sin_r) as f64,
];
let strokes = cxf::tessellate_text_ex(
[origin_x, origin_y],
[0.0, 0.0],
t.height as f32,
rot,
style_width_factor,
@ -161,7 +159,7 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
&font_name,
line,
);
all_strokes.extend(strokes);
all_strokes.push(TextStroke { strokes, origin });
}
TruckEntity {
object: TruckObject::Text(all_strokes),

View file

@ -169,8 +169,8 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
let py = dx / len;
let sagitta_sign = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
let cx = mx - sagitta_sign * px * (r - h);
let cy = my - sagitta_sign * py * (r - h);
let cx = mx + sagitta_sign * px * (r - h);
let cy = my + sagitta_sign * py * (r - h);
let mid_a = {
let a0 = (p0.y - cy).atan2(p0.x - cx);
let a1 = (p1.y - cy).atan2(p1.x - cx);

View file

@ -5,7 +5,7 @@ use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, parse_f64, square_grip};
use crate::entities::text_support::{resolve_dxf_special_chars, resolve_text_style, text_local_bounds};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
use crate::scene::cxf;
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::wire_model::SnapHint;
@ -101,14 +101,17 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
} else {
(0.0, 0.0)
};
let (cos_r, sin_r) = (rotation.cos(), rotation.sin());
let origin = [
anchor[0] - (anchor_local_x * cos_r - anchor_local_y * sin_r),
anchor[1] - (anchor_local_x * sin_r + anchor_local_y * cos_r),
let (cos_r, sin_r) = (rotation.cos() as f64, rotation.sin() as f64);
// Keep origin as f64 — large coordinates (UTM etc.) must not be cast to
// f32 here; world_offset subtraction happens later in tessellate.rs.
let anchor_f64 = [anchor[0] as f64, anchor[1] as f64];
let origin: [f64; 2] = [
anchor_f64[0] - (anchor_local_x as f64 * cos_r - anchor_local_y as f64 * sin_r),
anchor_f64[1] - (anchor_local_x as f64 * sin_r + anchor_local_y as f64 * cos_r),
];
// Pass raw value — tessellate_text_ex resolves %%x codes and emits decoration strokes.
let strokes_2d = cxf::tessellate_text_ex(
origin,
// Strokes are in glyph-local space (origin = [0,0]).
let strokes = cxf::tessellate_text_ex(
[0.0, 0.0],
t.height as f32,
rotation,
width_factor,
@ -117,7 +120,7 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
&t.value,
);
TruckEntity {
object: TruckObject::Text(strokes_2d),
object: TruckObject::Text(vec![TextStroke { strokes, origin }]),
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![],
key_vertices: vec![],

View file

@ -4,7 +4,7 @@ use glam::Vec3;
use crate::command::EntityTransform;
use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
use crate::scene::object::{GripApply, GripDef, PropSection};
use crate::scene::wire_model::SnapHint;
use crate::scene::{cxf, transform};
@ -138,14 +138,13 @@ fn tessellate_tolerance(tol: &Tolerance) -> Vec<Vec<[f32; 2]>> {
let total_w: f32 = col_widths.iter().sum();
let total_h = cell_h * rows.len() as f32;
// ── Transform helpers (world 2-D, ignoring Z for Text path) ──────────
let ox = tol.insertion_point.x as f32;
let oy = tol.insertion_point.y as f32;
// ── Transform helpers (local space — translation applied in tessellate.rs) ──
let angle = (tol.direction.y as f32).atan2(tol.direction.x as f32);
let (sa, ca) = angle.sin_cos();
// Rotate only; origin is kept as f64 and applied later with full precision.
let rot = |x: f32, y: f32| -> [f32; 2] {
[ox + x * ca - y * sa, oy + x * sa + y * ca]
[x * ca - y * sa, x * sa + y * ca]
};
let mut out: Vec<Vec<[f32; 2]>> = Vec::new();
@ -228,12 +227,12 @@ impl TruckConvertible for Tolerance {
self.insertion_point.z as f32,
);
// Build the feature-control frame: box outline + cell text, all
// represented as 2-D CXF polylines (TruckObject::Text).
let polylines = tessellate_tolerance(self);
// Build the feature-control frame in local space; origin stored as f64.
let strokes = tessellate_tolerance(self);
let origin = [self.insertion_point.x, self.insertion_point.y];
Some(TruckEntity {
object: TruckObject::Text(polylines),
object: TruckObject::Text(vec![TextStroke { strokes, origin }]),
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![],
key_vertices: vec![],