Merge remote-tracking branch 'origin/main' into codex/attdef-parity

# Conflicts:
#	Cargo.lock
#	src/modules/draw/draw/attdef.rs
This commit is contained in:
ramox81 committed 2026-10-02 09:09:21 +03:00
commit 53f2983e9c
77 files changed
+4034 -243

No files matched your search

+30 -7
View File
@@ -4051,7 +4051,9 @@ fn tessellate_dimension_inner(
plinegen: true,
fill_tris: vec![],
fill_tris_low: Vec::new(),
});
..Default::default()
});
}
if !ext2.is_empty() {
wires.push(WireModel {
@@ -4088,7 +4090,9 @@ fn tessellate_dimension_inner(
plinegen: true,
fill_tris: vec![],
fill_tris_low: Vec::new(),
});
..Default::default()
});
}
} else {
wires.push(WireModel {
@@ -4125,7 +4129,9 @@ fn tessellate_dimension_inner(
plinegen: true,
fill_tris: vec![],
fill_tris_low: Vec::new(),
});
..Default::default()
});
}
}
@@ -4168,7 +4174,9 @@ fn tessellate_dimension_inner(
// — not a crash. Follow-up: double-single-split via points_to_ds to
// match emit_wire's paired fill path.
fill_tris_low: Vec::new(),
});
..Default::default()
});
// Arrowheads and ticks: the dimension line's colour and weight, never
// its dashes.
if !geom.arrow_lines.is_empty() {
@@ -4224,7 +4232,9 @@ fn tessellate_dimension_inner(
plinegen: true,
fill_tris: vec![],
fill_tris_low: Vec::new(),
});
..Default::default()
});
}
// DIMTFILL: 0=none, 1=drawing background (mask), 2=DIMTFILLCLR.
@@ -4279,7 +4289,9 @@ fn tessellate_dimension_inner(
// (sub-metre error at UTM scale) — not a crash. Follow-up:
// double-single-split via points_to_ds to match emit_wire.
fill_tris_low: Vec::new(),
});
..Default::default()
});
}
}
}
@@ -4326,7 +4338,9 @@ fn tessellate_dimension_inner(
plinegen: true,
fill_tris: vec![],
fill_tris_low: Vec::new(),
});
..Default::default()
});
}
}
@@ -5855,6 +5869,15 @@ fn map_wire_ocs_to_wcs(wire: &mut WireModel, normal: Vector3) {
&wire.text_verts,
map,
);
// OCS→WCS is rigid: unit height scale, no in-plane rotation change.
wire.map_searchable_runs(
&|p| {
let mapped = map(p[0], p[1], p[2]);
[mapped.0, mapped.1, mapped.2]
},
1.0,
0.0,
);
for (point, _) in &mut wire.snap_pts {
let mapped = map(point.x, point.y, point.z);
*point = DVec3::new(mapped.0, mapped.1, mapped.2);
+2
View File
@@ -705,6 +705,7 @@ impl LeaderTess for Leader {
plinegen: true,
fill_tris: vec![],
fill_tris_low: Vec::new(),
..Default::default()
};
}
@@ -887,6 +888,7 @@ impl LeaderTess for Leader {
plinegen: true,
fill_tris,
fill_tris_low,
..Default::default()
}
}
}
+46 -16
View File
@@ -2106,9 +2106,11 @@ impl MultiLeaderTess for MultiLeader {
// error at UTM scale) — not a crash. Follow-up: double-single-split
// via points_to_ds to match emit_wire's paired fill path.
fill_tris_low: Vec::new(),
});
..Default::default()
});
let host = codec::EntityType::MultiLeader(ml.clone());
let host = codec::EntityType::MultiLeader(Box::new(ml.clone()));
for block_use in crate::scene::render_graph::entity_render_block_uses(document, &host, 1.0)
.into_iter()
.filter(|block_use| {
@@ -2299,6 +2301,8 @@ impl MultiLeaderTess for MultiLeader {
// cluster-position data.
let mut deco_pts: Vec<[f32; 3]> = Vec::new();
let mut deco_fill: Vec<[f32; 3]> = Vec::new();
let mut searchable: Vec<crate::scene::model::wire_model::SearchableTextRun> =
Vec::new();
if let Ok(mut atlas) = crate::scene::text::sdf_atlas::text_atlas().lock() {
for ts in &layout.strokes {
let is_shaped = ts
@@ -2331,17 +2335,18 @@ impl MultiLeaderTess for MultiLeader {
continue;
}
let run = ts.run.as_ref().unwrap();
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
&mut atlas,
run.height,
run.rotation,
run.width_factor,
run.oblique,
run.tracking,
&run.font,
run.bold,
&run.text,
);
let (quads, pen_adv) =
crate::scene::text::glyph_quads::layout_glyph_quads(
&mut atlas,
run.height,
run.rotation,
run.width_factor,
run.oblique,
run.tracking,
&run.font,
run.bold,
&run.text,
);
// Inline `\C` / `\c` colour wins over the entity text
// colour, matching the top-level Text arm.
let gcolor = ts
@@ -2356,6 +2361,24 @@ impl MultiLeaderTess for MultiLeader {
gcolor,
0.0,
);
{
use crate::scene::model::wire_model::{
clean_searchable_text, SearchableTextRun,
};
let visible = clean_searchable_text(&run.text);
if !visible.is_empty() {
searchable.push(SearchableTextRun {
text: visible,
origin: [ts.origin[0], ts.origin[1], z as f64],
height: run.height,
rotation: run.rotation,
color: gcolor,
bold: run.bold,
font: run.font.clone(),
adv_width: pen_adv,
});
}
}
}
}
if !sdf_verts.is_empty() || !deco_pts.is_empty() || !deco_fill.is_empty() {
@@ -2400,6 +2423,7 @@ impl MultiLeaderTess for MultiLeader {
dash_from_start: false,
dash_align_end: None,
text_verts: sdf_verts,
searchable_text: searchable,
name: name.clone(),
points: deco_pts,
points_low: Vec::new(),
@@ -2492,7 +2516,9 @@ impl MultiLeaderTess for MultiLeader {
plinegen: true,
fill_tris: vec![],
fill_tris_low: Vec::new(),
});
..Default::default()
});
}
}
@@ -2583,7 +2609,9 @@ impl MultiLeaderTess for MultiLeader {
plinegen: true,
fill_tris,
fill_tris_low: Vec::new(),
});
..Default::default()
});
}
// Text frame — closed rectangle, matches text color.
@@ -2629,7 +2657,9 @@ impl MultiLeaderTess for MultiLeader {
plinegen: true,
fill_tris: vec![],
fill_tris_low: Vec::new(),
});
..Default::default()
});
}
}
}
+35 -12
View File
@@ -1488,6 +1488,7 @@ pub fn tessellate_table(
// SDF cell text: glyph quads (per-vertex coloured) collected across all
// cells; emitted as one text-carrying wire at the end.
let mut text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new();
let mut searchable: Vec<crate::scene::model::wire_model::SearchableTextRun> = Vec::new();
let mut borders: HashMap<([u8; 4], u32), ([f32; 4], f32, Vec<[f32; 3]>)> = HashMap::default();
let mut emitted: rustc_hash::FxHashSet<(i32, i32, i32, i32, i32, i32)> =
rustc_hash::FxHashSet::default();
@@ -2016,17 +2017,18 @@ pub fn tessellate_table(
}
continue;
}
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
&mut atlas,
run.height,
run.rotation,
run.width_factor,
run.oblique,
run.tracking,
&run.font,
run.bold,
&run.text,
);
let (quads, pen_adv) =
crate::scene::text::glyph_quads::layout_glyph_quads(
&mut atlas,
run.height,
run.rotation,
run.width_factor,
run.oblique,
run.tracking,
&run.font,
run.bold,
&run.text,
);
crate::scene::pipeline::text_gpu::push_glyph_vertices(
&mut text_verts,
&quads,
@@ -2035,6 +2037,24 @@ pub fn tessellate_table(
tcol,
0.0,
);
{
use crate::scene::model::wire_model::{
clean_searchable_text, SearchableTextRun,
};
let visible = clean_searchable_text(&run.text);
if !visible.is_empty() {
searchable.push(SearchableTextRun {
text: visible,
origin: [stroke.origin[0], stroke.origin[1], to.z as f64],
height: run.height,
rotation: run.rotation,
color: tcol,
bold: run.bold,
font: run.font.clone(),
adv_width: pen_adv,
});
}
}
}
}
}
@@ -2084,7 +2104,9 @@ pub fn tessellate_table(
// (sub-metre error at UTM scale) — not a crash. Follow-up:
// double-single-split via points_to_ds to match emit_wire.
fill_tris_low: Vec::new(),
}
..Default::default()
}
};
let mut out: Vec<WireModel> = Vec::new();
@@ -2115,6 +2137,7 @@ pub fn tessellate_table(
let mut w = mk(entity_color, vec![], vec![], line_weight_px);
w.aabb = [nx as f32, ny as f32, xx as f32, xy as f32];
w.text_verts = text_verts;
w.searchable_text = searchable;
out.push(w);
}
out
+57
View File
@@ -81,6 +81,63 @@ pub trait Transformable {
fn apply_transform(&mut self, t: &EntityTransform);
}
// The codec boxes its rare wide entity kinds (Surface, Table, MultiLeader,
// Helix, Extended) so every other entity is stored narrow; the boxed ones
// answer through their contents.
impl<T: RenderConvertible + ?Sized> RenderConvertible for Box<T> {
fn to_render(&self, document: &CadDocument) -> Option<RenderEntity> {
(**self).to_render(document)
}
}
impl<T: Grippable + ?Sized> Grippable for Box<T> {
fn grips(&self) -> Vec<GripDef> {
(**self).grips()
}
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
(**self).apply_grip(grip_id, apply)
}
fn grip_menu(&self, grip_id: usize) -> Vec<GripMenuItem> {
(**self).grip_menu(grip_id)
}
fn apply_grip_menu(&mut self, grip_id: usize, action: GripMenuAction) {
(**self).apply_grip_menu(grip_id, action)
}
fn apply_grip_menu_value(&mut self, grip_id: usize, action: GripMenuAction, value: f64) {
(**self).apply_grip_menu_value(grip_id, action, value)
}
fn grip_menu_value_prompt(
&self,
grip_id: usize,
action: GripMenuAction,
) -> Option<&'static str> {
(**self).grip_menu_value_prompt(grip_id, action)
}
fn grip_menu_point_value(
&self,
grip_id: usize,
action: GripMenuAction,
point: glam::DVec3,
) -> Option<f64> {
(**self).grip_menu_point_value(grip_id, action, point)
}
}
impl<T: PropertyEditable + ?Sized> PropertyEditable for Box<T> {
fn geometry_properties(&self, text_style_names: &[String]) -> Vec<PropSection> {
(**self).geometry_properties(text_style_names)
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
(**self).apply_geom_prop(field, value)
}
}
impl<T: Transformable + ?Sized> Transformable for Box<T> {
fn apply_transform(&mut self, t: &EntityTransform) {
(**self).apply_transform(t)
}
}
/// Inquiry-time mass / area / perimeter properties for entities whose
/// 2D footprint has a meaningful area or perimeter (Circle, Arc, Line,
/// LwPolyline, Ellipse). Entities outside this set get `None` via the