cargo fmt

This commit is contained in:
Hakan 2026-05-12 10:36:15 +03:00
commit ca07268c7c
204 changed files with 13988 additions and 5516 deletions

View file

@ -12,7 +12,7 @@ use crate::scene::wire_model::{SnapHint, TangentGeom};
/// world offset can be subtracted with f64 precision in tessellate.rs.
pub struct TextStroke {
pub strokes: Vec<Vec<[f32; 2]>>,
pub origin: [f64; 2],
pub origin: [f64; 2],
}
#[allow(dead_code)]

View file

@ -120,7 +120,7 @@ impl Camera {
let (ray_origin, ray_dir) = match self.projection {
Projection::Perspective => {
let near_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 0.0));
let far_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 1.0));
let far_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 1.0));
let dir = (far_pt - near_pt).normalize();
(near_pt, dir)
}

View file

@ -6,10 +6,10 @@
//! Coordinate convention: 2D entities live in the **XZ** plane (Y = elevation).
//! Shape X → along the linetype direction; Shape Y → perpendicular in XZ.
use crate::entities::text_support::resolve_dxf_special_chars;
use crate::linetypes::{ComplexLt, LtSegment};
use crate::scene::cxf;
use crate::scene::wire_model::WireModel;
use crate::entities::text_support::resolve_dxf_special_chars;
// ── Public entry point ────────────────────────────────────────────────────
@ -166,10 +166,8 @@ pub fn apply_along(
);
for stroke in &text_strokes {
if stroke.len() >= 2 {
let pts: Vec<[f32; 3]> = stroke
.iter()
.map(|&[sx, sy]| [sx, sy, insert[2]])
.collect();
let pts: Vec<[f32; 3]> =
stroke.iter().map(|&[sx, sy]| [sx, sy, insert[2]]).collect();
strokes.push(pts);
}
}
@ -239,14 +237,27 @@ fn scale_segments(segs: &[LtSegment], scale: f32) -> Vec<LtSeg> {
LtSegment::Dash(l) => LtSeg::Dash(l * scale),
LtSegment::Space(l) => LtSeg::Space(l * scale),
LtSegment::Dot => LtSeg::Dot,
LtSegment::Shape { name, x, y, scale: sh_scale, rot_deg } => LtSeg::Shape {
LtSegment::Shape {
name,
x,
y,
scale: sh_scale,
rot_deg,
} => LtSeg::Shape {
name: name.clone(),
x: x * scale,
y: y * scale,
scale: *sh_scale * scale,
rot_deg: *rot_deg,
},
LtSegment::Text { text, style, x, y, scale: tx_scale, rot_deg } => LtSeg::Text {
LtSegment::Text {
text,
style,
x,
y,
scale: tx_scale,
rot_deg,
} => LtSeg::Text {
text: text.clone(),
style: style.clone(),
x: x * scale,

View file

@ -85,11 +85,16 @@ static FONTS: OnceLock<HashMap<String, CxfFile>> = OnceLock::new();
static WARNED_GLYPHS: OnceLock<Mutex<HashSet<(String, char)>>> = OnceLock::new();
fn warn_missing_glyph(font_name: &str, ch: char) {
if ch.is_ascii() { return; }
if ch.is_ascii() {
return;
}
let set = WARNED_GLYPHS.get_or_init(|| Mutex::new(HashSet::new()));
if let Ok(mut guard) = set.lock() {
if guard.insert((font_name.to_string(), ch)) {
eprintln!("cxf: glyph U+{:04X} ('{}') not found in font '{font_name}'", ch as u32, ch);
eprintln!(
"cxf: glyph U+{:04X} ('{}') not found in font '{font_name}'",
ch as u32, ch
);
}
}
}
@ -265,7 +270,9 @@ pub fn tessellate_text_ex(
match chars.peek().copied() {
Some('L') => {
chars.next();
if underline.is_none() { underline = Some(cursor_x); }
if underline.is_none() {
underline = Some(cursor_x);
}
continue;
}
Some('l') => {
@ -277,7 +284,9 @@ pub fn tessellate_text_ex(
}
Some('O') => {
chars.next();
if overline.is_none() { overline = Some(cursor_x); }
if overline.is_none() {
overline = Some(cursor_x);
}
continue;
}
Some('o') => {
@ -289,13 +298,18 @@ pub fn tessellate_text_ex(
}
Some('K') => {
chars.next();
if strikethrough.is_none() { strikethrough = Some(cursor_x); }
if strikethrough.is_none() {
strikethrough = Some(cursor_x);
}
continue;
}
Some('k') => {
chars.next();
if let Some(s) = strikethrough.take() {
out.push(vec![xform(s, STRIKE_Y, 0.0), xform(cursor_x, STRIKE_Y, 0.0)]);
out.push(vec![
xform(s, STRIKE_Y, 0.0),
xform(cursor_x, STRIKE_Y, 0.0),
]);
}
continue;
}
@ -307,15 +321,30 @@ pub fn tessellate_text_ex(
let render_ch: char = if ch == '%' && chars.peek() == Some(&'%') {
chars.next(); // consume second '%'
match chars.peek().map(|c| c.to_ascii_lowercase()) {
Some('d') => { chars.next(); '°' }
Some('p') => { chars.next(); '±' }
Some('c') => { chars.next(); '⌀' }
Some('%') => { chars.next(); '%' }
Some('d') => {
chars.next();
'°'
}
Some('p') => {
chars.next();
'±'
}
Some('c') => {
chars.next();
'⌀'
}
Some('%') => {
chars.next();
'%'
}
Some('u') => {
chars.next();
underline = match underline.take() {
Some(start) => {
out.push(vec![xform(start, UNDER_Y, 0.0), xform(cursor_x, UNDER_Y, 0.0)]);
out.push(vec![
xform(start, UNDER_Y, 0.0),
xform(cursor_x, UNDER_Y, 0.0),
]);
None
}
None => Some(cursor_x),
@ -326,7 +355,10 @@ pub fn tessellate_text_ex(
chars.next();
overline = match overline.take() {
Some(start) => {
out.push(vec![xform(start, OVER_Y, 0.0), xform(cursor_x, OVER_Y, 0.0)]);
out.push(vec![
xform(start, OVER_Y, 0.0),
xform(cursor_x, OVER_Y, 0.0),
]);
None
}
None => Some(cursor_x),
@ -338,7 +370,9 @@ pub fn tessellate_text_ex(
let mut digits = String::with_capacity(3);
for _ in 0..3 {
match chars.peek() {
Some(&c) if c.is_ascii_digit() => { digits.push(chars.next().unwrap()); }
Some(&c) if c.is_ascii_digit() => {
digits.push(chars.next().unwrap());
}
_ => break,
}
}
@ -346,15 +380,21 @@ pub fn tessellate_text_ex(
if let Ok(n) = digits.parse::<u32>() {
if let Some(c) = char::from_u32(n) {
c
} else { continue; }
} else { continue; }
} else {
continue;
}
} else {
continue;
}
} else {
// Partial digit sequence — advance as unknown glyph and move on
cursor_x += (6.0 + font.letter_spacing) * wf;
continue;
}
}
_ => { continue; } // unknown %%x — skip silently
_ => {
continue;
} // unknown %%x — skip silently
}
} else {
ch
@ -388,13 +428,22 @@ pub fn tessellate_text_ex(
// Close any decoration spans that weren't explicitly closed.
if let Some(start) = underline {
out.push(vec![xform(start, UNDER_Y, 0.0), xform(cursor_x, UNDER_Y, 0.0)]);
out.push(vec![
xform(start, UNDER_Y, 0.0),
xform(cursor_x, UNDER_Y, 0.0),
]);
}
if let Some(start) = overline {
out.push(vec![xform(start, OVER_Y, 0.0), xform(cursor_x, OVER_Y, 0.0)]);
out.push(vec![
xform(start, OVER_Y, 0.0),
xform(cursor_x, OVER_Y, 0.0),
]);
}
if let Some(start) = strikethrough {
out.push(vec![xform(start, STRIKE_Y, 0.0), xform(cursor_x, STRIKE_Y, 0.0)]);
out.push(vec![
xform(start, STRIKE_Y, 0.0),
xform(cursor_x, STRIKE_Y, 0.0),
]);
}
out

View file

@ -54,8 +54,10 @@ pub fn grips_to_screen(
/// Parameters match the `to_px` closure in `paper_canvas.rs`.
pub fn grips_to_screen_paper(
grips: &[GripDef],
tx: f32, ty: f32,
half_w: f32, half_h: f32,
tx: f32,
ty: f32,
half_w: f32,
half_h: f32,
bounds: Rectangle,
) -> Vec<(usize, Point, bool, GripShape)> {
grips
@ -74,8 +76,10 @@ pub fn grips_to_screen_paper(
pub fn find_hit_grip_paper(
cursor: Point,
grips: &[GripDef],
tx: f32, ty: f32,
half_w: f32, half_h: f32,
tx: f32,
ty: f32,
half_w: f32,
half_h: f32,
bounds: Rectangle,
) -> Option<(usize, bool, Vec3)> {
let mut best_dist = GRIP_THRESHOLD_PX;

View file

@ -109,7 +109,9 @@ pub fn box_hit<'a>(
}
let sp = world_to_screen(Vec3::new(px, py, pz), view_proj, bounds);
if crossing {
if inside(sp) { hit = true; }
if inside(sp) {
hit = true;
}
if let Some(p0) = prev {
if !hit {
hit = segments_intersect(p0, sp, box_tl, box_tr)
@ -119,13 +121,23 @@ pub fn box_hit<'a>(
}
}
} else {
if !inside(sp) { all_inside = false; }
if !inside(sp) {
all_inside = false;
}
}
prev = Some(sp);
}
let result = if crossing { hit } else { all_inside && prev.is_some() };
if result { Some(wire.name.as_str()) } else { None }
let result = if crossing {
hit
} else {
all_inside && prev.is_some()
};
if result {
Some(wire.name.as_str())
} else {
None
}
})
.collect()
}
@ -167,20 +179,34 @@ pub fn poly_hit<'a>(
}
let sp = world_to_screen(Vec3::new(px, py, pz), view_proj, bounds);
if crossing {
if point_in_polygon(sp, poly) { hit = true; }
if point_in_polygon(sp, poly) {
hit = true;
}
if !hit {
if let Some(p0) = prev {
if segment_crosses_polygon(p0, sp, poly) { hit = true; }
if segment_crosses_polygon(p0, sp, poly) {
hit = true;
}
}
}
} else {
if !point_in_polygon(sp, poly) { all_inside = false; }
if !point_in_polygon(sp, poly) {
all_inside = false;
}
}
prev = Some(sp);
}
let result = if crossing { hit } else { all_inside && prev.is_some() };
if result { Some(wire.name.as_str()) } else { None }
let result = if crossing {
hit
} else {
all_inside && prev.is_some()
};
if result {
Some(wire.name.as_str())
} else {
None
}
})
.collect()
}

View file

@ -49,7 +49,14 @@ impl ImageModel {
let opacity = 1.0 - img.fade as f32 / 100.0;
let (pixels, width, height) = load_pixels(&img.file_path)?;
Some(Self { file_path: img.file_path.clone(), pixels: Arc::new(pixels), width, height, opacity, corners })
Some(Self {
file_path: img.file_path.clone(),
pixels: Arc::new(pixels),
width,
height,
opacity,
corners,
})
}
}

File diff suppressed because it is too large Load diff

View file

@ -65,13 +65,20 @@ impl<'a> canvas::Program<Message> for PaperCanvas<'a> {
let mut frame = canvas::Frame::new(renderer, bounds.size());
// ── Desk background (area outside the paper sheet) ────────────────────
const DESK: Color = Color { r: 0.22, g: 0.24, b: 0.28, a: 1.0 };
const DESK: Color = Color {
r: 0.22,
g: 0.24,
b: 0.28,
a: 1.0,
};
frame.fill_rectangle(Point::ORIGIN, bounds.size(), DESK);
// ── White paper area — use layout paper limits (actual paper size).
if let Some(((px0, py0), (px1, py1))) = self.scene.paper_limits().map(
|((x0, y0), (x1, y1))| ((x0 as f32, y0 as f32), (x1 as f32, y1 as f32)),
) {
if let Some(((px0, py0), (px1, py1))) = self
.scene
.paper_limits()
.map(|((x0, y0), (x1, y1))| ((x0 as f32, y0 as f32), (x1 as f32, y1 as f32)))
{
let tl = to_px(px0, py1);
let br = to_px(px1, py0);
let pw = br.x - tl.x;
@ -156,11 +163,7 @@ impl<'a> canvas::Program<Message> for PaperCanvas<'a> {
/// Draw one HatchModel (solid, gradient, or pattern) onto `frame`.
/// Patterns and gradients are approximated as solid fills in 2-D canvas mode.
fn draw_hatch(
frame: &mut canvas::Frame,
hatch: &HatchModel,
to_px: &impl Fn(f32, f32) -> Point,
) {
fn draw_hatch(frame: &mut canvas::Frame, hatch: &HatchModel, to_px: &impl Fn(f32, f32) -> Point) {
if hatch.boundary.is_empty() {
return;
}
@ -204,4 +207,3 @@ fn draw_hatch(
}
}
}

View file

@ -20,7 +20,7 @@ use iced::wgpu::util::DeviceExt;
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Face3DVertex {
pub position: [f32; 3],
pub color: [f32; 4],
pub color: [f32; 4],
}
impl Face3DVertex {
@ -58,7 +58,11 @@ impl Face3DGpu {
/// emits 2 triangles per face.
/// - `all_wires`: all entity wires — `fill_tris` holds pre-triangulated
/// fill data for PolyfaceMesh / PolygonMesh.
pub fn from_wires(device: &wgpu::Device, face3d_wires: &[WireModel], all_wires: &[WireModel]) -> Self {
pub fn from_wires(
device: &wgpu::Device,
face3d_wires: &[WireModel],
all_wires: &[WireModel],
) -> Self {
let mut vertices: Vec<Face3DVertex> = Vec::with_capacity(face3d_wires.len() * 6);
// Face3D quads (4 key_vertices → 2 triangles).
@ -69,7 +73,10 @@ impl Face3DGpu {
let [r, g, b, a] = wire.color;
let fill_color = [r * 0.45, g * 0.45, b * 0.45, a];
let p = &wire.key_vertices;
let v = |i: usize| Face3DVertex { position: p[i], color: fill_color };
let v = |i: usize| Face3DVertex {
position: p[i],
color: fill_color,
};
vertices.push(v(0));
vertices.push(v(1));
vertices.push(v(2));
@ -86,7 +93,10 @@ impl Face3DGpu {
let [r, g, b, a] = wire.color;
let fill_color = [r * 0.45, g * 0.45, b * 0.45, a];
for &position in &wire.fill_tris {
vertices.push(Face3DVertex { position, color: fill_color });
vertices.push(Face3DVertex {
position,
color: fill_color,
});
}
}

View file

@ -5,9 +5,7 @@
// binding 1 — BoundaryData (16384 bytes) boundary polygon vertices
// binding 2 — FamilyBatchData (1296 bytes) up to 16 line families + 128 dash values
use crate::scene::hatch_model::{
HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS,
};
use crate::scene::hatch_model::{HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS};
use iced::wgpu;
use iced::wgpu::util::DeviceExt;

View file

@ -114,7 +114,10 @@ impl ImageGpu {
});
// ── Opacity uniform ───────────────────────────────────────────────
let params = ImageParams { opacity: model.opacity.clamp(0.0, 1.0), _pad: [0.0; 3] };
let params = ImageParams {
opacity: model.opacity.clamp(0.0, 1.0),
_pad: [0.0; 3],
};
let _params_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("image.params"),
contents: bytemuck::bytes_of(&params),
@ -146,12 +149,30 @@ impl ImageGpu {
// UV: BL=(0,1), BR=(1,1), TR=(1,0), TL=(0,0)
let [p0, p1, p2, p3] = model.corners;
let verts = [
ImageVertex { pos: p0, uv: [0.0, 1.0] },
ImageVertex { pos: p1, uv: [1.0, 1.0] },
ImageVertex { pos: p2, uv: [1.0, 0.0] },
ImageVertex { pos: p0, uv: [0.0, 1.0] },
ImageVertex { pos: p2, uv: [1.0, 0.0] },
ImageVertex { pos: p3, uv: [0.0, 0.0] },
ImageVertex {
pos: p0,
uv: [0.0, 1.0],
},
ImageVertex {
pos: p1,
uv: [1.0, 1.0],
},
ImageVertex {
pos: p2,
uv: [1.0, 0.0],
},
ImageVertex {
pos: p0,
uv: [0.0, 1.0],
},
ImageVertex {
pos: p2,
uv: [1.0, 0.0],
},
ImageVertex {
pos: p3,
uv: [0.0, 0.0],
},
];
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("image.vbuf"),

View file

@ -609,7 +609,11 @@ impl Pipeline {
self.gpu_wires = wires
.iter()
.filter(|w| {
let segs = w.points.len().saturating_sub(1).min(wire_gpu::MAX_SEGS_PER_WIRE);
let segs = w
.points
.len()
.saturating_sub(1)
.min(wire_gpu::MAX_SEGS_PER_WIRE);
if total_segs + segs > MAX_TOTAL_SEGS {
skipped += 1;
false
@ -641,31 +645,42 @@ impl Pipeline {
pub fn compute_wire_scissors(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) {
let w = clip_w as f32;
let h = clip_h as f32;
self.wire_pixel_scissors = self.gpu_wires.iter().map(|wire| {
let [x0, y0, x1, y1] = wire.vp_scissor?;
let corners = [
view_proj.project_point3(glam::Vec3::new(x0, y0, 0.0)),
view_proj.project_point3(glam::Vec3::new(x1, y0, 0.0)),
view_proj.project_point3(glam::Vec3::new(x0, y1, 0.0)),
view_proj.project_point3(glam::Vec3::new(x1, y1, 0.0)),
];
let px: Vec<f32> = corners.iter().map(|c| (c.x + 1.0) * 0.5 * w).collect();
let py: Vec<f32> = corners.iter().map(|c| (1.0 - c.y) * 0.5 * h).collect();
let sx0 = px.iter().cloned().fold(f32::INFINITY, f32::min).max(0.0) as u32;
let sy0 = py.iter().cloned().fold(f32::INFINITY, f32::min).max(0.0) as u32;
let sx1 = (px.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as u32).min(clip_w);
let sy1 = (py.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as u32).min(clip_h);
if sx1 <= sx0 || sy1 <= sy0 { return None; }
Some([sx0, sy0, sx1 - sx0, sy1 - sy0])
}).collect();
self.wire_pixel_scissors = self
.gpu_wires
.iter()
.map(|wire| {
let [x0, y0, x1, y1] = wire.vp_scissor?;
let corners = [
view_proj.project_point3(glam::Vec3::new(x0, y0, 0.0)),
view_proj.project_point3(glam::Vec3::new(x1, y0, 0.0)),
view_proj.project_point3(glam::Vec3::new(x0, y1, 0.0)),
view_proj.project_point3(glam::Vec3::new(x1, y1, 0.0)),
];
let px: Vec<f32> = corners.iter().map(|c| (c.x + 1.0) * 0.5 * w).collect();
let py: Vec<f32> = corners.iter().map(|c| (1.0 - c.y) * 0.5 * h).collect();
let sx0 = px.iter().cloned().fold(f32::INFINITY, f32::min).max(0.0) as u32;
let sy0 = py.iter().cloned().fold(f32::INFINITY, f32::min).max(0.0) as u32;
let sx1 = (px.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as u32).min(clip_w);
let sy1 = (py.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as u32).min(clip_h);
if sx1 <= sx0 || sy1 <= sy0 {
return None;
}
Some([sx0, sy0, sx1 - sx0, sy1 - sy0])
})
.collect();
}
/// Upload all 3DFACE entities as two batched GPU objects:
/// - `gpu_face3d_fill`: filled triangles (1 buffer, 1 draw call)
/// - `gpu_face3d_edges`: merged edge wires (1 buffer, 1 draw call)
pub fn upload_face3d(&mut self, device: &wgpu::Device, face3d_wires: &[WireModel], all_wires: &[WireModel]) {
let has_fills = !face3d_wires.is_empty()
|| all_wires.iter().any(|w| !w.fill_tris.is_empty());
pub fn upload_face3d(
&mut self,
device: &wgpu::Device,
face3d_wires: &[WireModel],
all_wires: &[WireModel],
) {
let has_fills =
!face3d_wires.is_empty() || all_wires.iter().any(|w| !w.fill_tris.is_empty());
if !has_fills {
self.gpu_face3d_fill = None;
self.gpu_face3d_edges = vec![];
@ -725,7 +740,12 @@ impl Pipeline {
let vp = clip_bounds;
let msaa = &self.msaa_view;
let [r, g, b, a] = bg_color;
let clear_color = wgpu::Color { r: r as f64, g: g as f64, b: b as f64, a: a as f64 };
let clear_color = wgpu::Color {
r: r as f64,
g: g as f64,
b: b as f64,
a: a as f64,
};
// ── Pass 1: hatch fills ────────────────────────────────────────────
{

View file

@ -90,18 +90,18 @@ impl CubeRegion {
11 => (PI, -FRAC_PI_4), // EDGE_BOT_BACK
12 => (FRAC_PI_2, -FRAC_PI_4),
13 => (-FRAC_PI_2, -FRAC_PI_4),
14 => (FRAC_PI_4, 0.0), // EDGE_FRONT_RIGHT (between 0 and PI/2)
15 => (-FRAC_PI_4, 0.0), // EDGE_FRONT_LEFT
16 => (PI * 0.75, 0.0), // EDGE_BACK_RIGHT (between PI and PI/2)
17 => (-PI * 0.75, 0.0), // EDGE_BACK_LEFT
18 => (FRAC_PI_4, iso), // CORNER_TPF_R (FRONT+RIGHT)
19 => (-FRAC_PI_4, iso), // CORNER_TPF_L (FRONT+LEFT)
20 => (PI * 0.75, iso), // CORNER_TBK_R (BACK+RIGHT)
21 => (-PI * 0.75, iso), // CORNER_TBK_L (BACK+LEFT)
22 => (FRAC_PI_4, -iso), // CORNER_BTF_R
23 => (-FRAC_PI_4, -iso), // CORNER_BTF_L
24 => (PI * 0.75, -iso), // CORNER_BBK_R
25 => (-PI * 0.75, -iso), // CORNER_BBK_L
14 => (FRAC_PI_4, 0.0), // EDGE_FRONT_RIGHT (between 0 and PI/2)
15 => (-FRAC_PI_4, 0.0), // EDGE_FRONT_LEFT
16 => (PI * 0.75, 0.0), // EDGE_BACK_RIGHT (between PI and PI/2)
17 => (-PI * 0.75, 0.0), // EDGE_BACK_LEFT
18 => (FRAC_PI_4, iso), // CORNER_TPF_R (FRONT+RIGHT)
19 => (-FRAC_PI_4, iso), // CORNER_TPF_L (FRONT+LEFT)
20 => (PI * 0.75, iso), // CORNER_TBK_R (BACK+RIGHT)
21 => (-PI * 0.75, iso), // CORNER_TBK_L (BACK+LEFT)
22 => (FRAC_PI_4, -iso), // CORNER_BTF_R
23 => (-FRAC_PI_4, -iso), // CORNER_BTF_L
24 => (PI * 0.75, -iso), // CORNER_BBK_R
25 => (-PI * 0.75, -iso), // CORNER_BBK_L
_ => (0.0, 0.0),
}
}
@ -909,11 +909,11 @@ pub fn build_geometry() -> (Vec<CubeVertex>, Vec<u32>) {
&mut is,
);
for &([sx, sy, sz], region) in &[
([1.0f32, 1.0, 1.0], CORNER_TBK_R), // sy=+1 = BACK yönü
([1.0f32, 1.0, 1.0], CORNER_TBK_R), // sy=+1 = BACK yönü
([-1.0, 1.0, 1.0], CORNER_TBK_L),
([1.0, 1.0, -1.0], CORNER_BBK_R),
([-1.0, 1.0, -1.0], CORNER_BBK_L),
([1.0, -1.0, 1.0], CORNER_TPF_R), // sy=-1 = FRONT yönü
([1.0, -1.0, 1.0], CORNER_TPF_R), // sy=-1 = FRONT yönü
([-1.0, -1.0, 1.0], CORNER_TPF_L),
([1.0, -1.0, -1.0], CORNER_BTF_R),
([-1.0, -1.0, -1.0], CORNER_BTF_L),
@ -934,32 +934,32 @@ pub fn build_geometry() -> (Vec<CubeVertex>, Vec<u32>) {
pub fn region_centroids() -> [[f32; 3]; NUM_REGIONS] {
let m = (F + E) * 0.5;
[
[0.0, 0.0, E], // FACE_TOP
[0.0, 0.0, -E], // FACE_BOTTOM
[0.0, -E, 0.0], // FACE_FRONT (geometry Y=-E)
[0.0, E, 0.0], // FACE_BACK (geometry Y=+E)
[E, 0.0, 0.0], // FACE_RIGHT
[-E, 0.0, 0.0], // FACE_LEFT
[0.0, -m, m], // EDGE_TOP_FRONT
[0.0, m, m], // EDGE_TOP_BACK
[m, 0.0, m], // EDGE_TOP_RIGHT
[-m, 0.0, m], // EDGE_TOP_LEFT
[0.0, -m, -m], // EDGE_BOT_FRONT
[0.0, m, -m], // EDGE_BOT_BACK
[m, 0.0, -m], // EDGE_BOT_RIGHT
[-m, 0.0, -m], // EDGE_BOT_LEFT
[m, -m, 0.0], // EDGE_FRONT_RIGHT
[-m, -m, 0.0], // EDGE_FRONT_LEFT
[m, m, 0.0], // EDGE_BACK_RIGHT
[-m, m, 0.0], // EDGE_BACK_LEFT
[m, -m, m], // CORNER_TPF_R (geometry sy=-1 = FRONT)
[-m, -m, m], // CORNER_TPF_L
[m, m, m], // CORNER_TBK_R (geometry sy=+1 = BACK)
[-m, m, m], // CORNER_TBK_L
[m, -m, -m], // CORNER_BTF_R (geometry sy=-1 = FRONT)
[-m, -m, -m], // CORNER_BTF_L
[m, m, -m], // CORNER_BBK_R (geometry sy=+1 = BACK)
[-m, m, -m], // CORNER_BBK_L
[0.0, 0.0, E], // FACE_TOP
[0.0, 0.0, -E], // FACE_BOTTOM
[0.0, -E, 0.0], // FACE_FRONT (geometry Y=-E)
[0.0, E, 0.0], // FACE_BACK (geometry Y=+E)
[E, 0.0, 0.0], // FACE_RIGHT
[-E, 0.0, 0.0], // FACE_LEFT
[0.0, -m, m], // EDGE_TOP_FRONT
[0.0, m, m], // EDGE_TOP_BACK
[m, 0.0, m], // EDGE_TOP_RIGHT
[-m, 0.0, m], // EDGE_TOP_LEFT
[0.0, -m, -m], // EDGE_BOT_FRONT
[0.0, m, -m], // EDGE_BOT_BACK
[m, 0.0, -m], // EDGE_BOT_RIGHT
[-m, 0.0, -m], // EDGE_BOT_LEFT
[m, -m, 0.0], // EDGE_FRONT_RIGHT
[-m, -m, 0.0], // EDGE_FRONT_LEFT
[m, m, 0.0], // EDGE_BACK_RIGHT
[-m, m, 0.0], // EDGE_BACK_LEFT
[m, -m, m], // CORNER_TPF_R (geometry sy=-1 = FRONT)
[-m, -m, m], // CORNER_TPF_L
[m, m, m], // CORNER_TBK_R (geometry sy=+1 = BACK)
[-m, m, m], // CORNER_TBK_L
[m, -m, -m], // CORNER_BTF_R (geometry sy=-1 = FRONT)
[-m, -m, -m], // CORNER_BTF_L
[m, m, -m], // CORNER_BBK_R (geometry sy=+1 = BACK)
[-m, m, -m], // CORNER_BBK_L
]
}

View file

@ -146,8 +146,18 @@ impl WireGpu {
for wire in wires {
let color = wire.color;
let pat0 = [wire.pattern[0], wire.pattern[1], wire.pattern[2], wire.pattern[3]];
let pat1 = [wire.pattern[4], wire.pattern[5], wire.pattern[6], wire.pattern[7]];
let pat0 = [
wire.pattern[0],
wire.pattern[1],
wire.pattern[2],
wire.pattern[3],
];
let pat1 = [
wire.pattern[4],
wire.pattern[5],
wire.pattern[6],
wire.pattern[7],
];
let half_width = wire.line_weight_px * 0.5;
let n = wire.points.len();
let raw_segs = n.saturating_sub(1);
@ -183,8 +193,12 @@ impl WireGpu {
for i in 0..capped_segs {
let a = wire.points[i];
let b = wire.points[i + 1];
if !a[0].is_finite() || !a[1].is_finite() || !a[2].is_finite()
|| !b[0].is_finite() || !b[1].is_finite() || !b[2].is_finite()
if !a[0].is_finite()
|| !a[1].is_finite()
|| !a[2].is_finite()
|| !b[0].is_finite()
|| !b[1].is_finite()
|| !b[2].is_finite()
{
continue;
}
@ -193,18 +207,25 @@ impl WireGpu {
let make = |which_end: f32, side: f32| -> WireVertex {
let dist = if which_end < 0.5 { dist_a } else { dist_b };
WireVertex {
pos_a: a, pos_b: b, which_end, side, color,
distance: dist, half_width,
pos_a: a,
pos_b: b,
which_end,
side,
color,
distance: dist,
half_width,
pattern_length: wire.pattern_length,
_pad: 0.0, pat0, pat1,
_pad: 0.0,
pat0,
pat1,
}
};
vertices.push(make(0.0, -1.0));
vertices.push(make(1.0, -1.0));
vertices.push(make(1.0, 1.0));
vertices.push(make(1.0, 1.0));
vertices.push(make(0.0, -1.0));
vertices.push(make(1.0, 1.0));
vertices.push(make(0.0, 1.0));
vertices.push(make(1.0, 1.0));
vertices.push(make(0.0, 1.0));
}
}

View file

@ -52,7 +52,8 @@ impl shader::Primitive for PaperViewportPrimitive {
bounds: &Rectangle,
viewport: &Viewport,
) {
self.0.prepare(&mut pipeline.0, device, queue, bounds, viewport);
self.0
.prepare(&mut pipeline.0, device, queue, bounds, viewport);
}
fn render(
@ -163,10 +164,13 @@ impl Scene {
/// Returns (entity_color, pattern_length, pattern, line_weight_px, aci).
pub(super) fn render_style(&self, e: &EntityType) -> ([f32; 4], f32, [f32; 8], f32, u8) {
let (color, pl, pat, lw, aci) = render_style_for(&self.document, e);
let bg = if self.current_layout == "Model" { self.bg_color } else { self.paper_bg_color };
let bg = if self.current_layout == "Model" {
self.bg_color
} else {
self.paper_bg_color
};
(adapt_to_bg(color, bg), pl, pat, lw, aci)
}
}
// ── Document-only render-style helpers (no &self, safe to call from parallel contexts) ──
@ -338,7 +342,6 @@ impl Scene {
}
}
/// Build a Primitive that renders model-space content through a specific
/// paper-space viewport's camera, applying its layer-freeze list.
pub(super) fn build_viewport_primitive(
@ -415,10 +418,7 @@ impl Scene {
}
}
pub(super) fn viewcube_mouse_interaction(
&self,
state: &CameraState,
) -> mouse::Interaction {
pub(super) fn viewcube_mouse_interaction(&self, state: &CameraState) -> mouse::Interaction {
if state.hover_region.is_some() {
mouse::Interaction::Pointer
} else {
@ -479,7 +479,9 @@ fn split_face3d_wires(
let mut face3d = Vec::new();
let mut others = Vec::new();
for w in wires {
let is_face3d = w.name.parse::<u64>()
let is_face3d = w
.name
.parse::<u64>()
.ok()
.and_then(|v| document.get_entity(Handle::new(v)))
.map(|e| matches!(e, EntityType::Face3D(_)))

View file

@ -13,11 +13,11 @@
use std::collections::HashSet;
use std::f64::consts::TAU;
use acadrust::entities::acis::{
SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatFace, SatLoop,
SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatTorusSurface, SatVertex,
};
use acadrust::entities::acis::types::Sense;
use acadrust::entities::acis::{
SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatFace, SatLoop, SatPlaneSurface,
SatPoint, SatPointer, SatSphereSurface, SatTorusSurface, SatVertex,
};
use acadrust::entities::{Body, Region, Solid3D};
use crate::scene::mesh_model::MeshModel;
@ -38,7 +38,9 @@ fn tessellate_sat(sat: &SatDocument, name: String, color: [f32; 4]) -> Option<Me
for face in sat.faces() {
let surf_ptr = face.surface();
let Some(surf_rec) = sat.resolve(surf_ptr) else { continue };
let Some(surf_rec) = sat.resolve(surf_ptr) else {
continue;
};
match surf_rec.entity_type.as_str() {
"plane-surface" => {
if let Some(plane) = SatPlaneSurface::from_record(surf_rec) {
@ -63,11 +65,24 @@ fn tessellate_sat(sat: &SatDocument, name: String, color: [f32; 4]) -> Option<Me
_ => {}
}
}
if indices.is_empty() { return None; }
Some(MeshModel { name, verts, normals, indices, color, selected: false })
if indices.is_empty() {
return None;
}
Some(MeshModel {
name,
verts,
normals,
indices,
color,
selected: false,
})
}
fn parse_acis(sat_fn: impl FnOnce() -> Option<SatDocument>, is_binary: bool, sab_data: &[u8]) -> Option<SatDocument> {
fn parse_acis(
sat_fn: impl FnOnce() -> Option<SatDocument>,
is_binary: bool,
sab_data: &[u8],
) -> Option<SatDocument> {
if let Some(doc) = sat_fn() {
return Some(doc);
}
@ -79,14 +94,22 @@ fn parse_acis(sat_fn: impl FnOnce() -> Option<SatDocument>, is_binary: bool, sab
/// Tessellate a `Region` entity (2D planar ACIS body) into a `MeshModel`.
pub fn tessellate_region(region: &Region, color: [f32; 4]) -> Option<MeshModel> {
let sat = parse_acis(|| region.parse_sat(), region.acis_data.is_binary, &region.acis_data.sab_data)?;
let sat = parse_acis(
|| region.parse_sat(),
region.acis_data.is_binary,
&region.acis_data.sab_data,
)?;
let name = region.common.handle.value().to_string();
tessellate_sat(&sat, name, color)
}
/// Tessellate a `Body` entity (3D ACIS body) into a `MeshModel`.
pub fn tessellate_body(body: &Body, color: [f32; 4]) -> Option<MeshModel> {
let sat = parse_acis(|| body.parse_sat(), body.acis_data.is_binary, &body.acis_data.sab_data)?;
let sat = parse_acis(
|| body.parse_sat(),
body.acis_data.is_binary,
&body.acis_data.sab_data,
)?;
let name = body.common.handle.value().to_string();
tessellate_sat(&sat, name, color)
}
@ -96,7 +119,11 @@ pub fn tessellate_body(body: &Body, color: [f32; 4]) -> Option<MeshModel> {
/// Returns `None` when the entity has no parseable SAT data or produces no
/// triangles (e.g. the solid uses only unsupported surface types).
pub fn tessellate_solid3d(solid: &Solid3D, color: [f32; 4]) -> Option<MeshModel> {
let sat = parse_acis(|| solid.parse_sat(), solid.acis_data.is_binary, &solid.acis_data.sab_data)?;
let sat = parse_acis(
|| solid.parse_sat(),
solid.acis_data.is_binary,
&solid.acis_data.sab_data,
)?;
let name = solid.common.handle.value().to_string();
tessellate_sat(&sat, name, color)
}
@ -261,7 +288,11 @@ fn tess_cone_face(
let segs_u = CIRC_SEGS;
let segs_v = segs_u / 4; // height subdivisions
let theta_span = if full_circle { TAU } else { theta_max - theta_min };
let theta_span = if full_circle {
TAU
} else {
theta_max - theta_min
};
let h_span = h_max - h_min;
if h_span.abs() < 1e-10 || theta_span.abs() < 1e-10 {
@ -317,7 +348,9 @@ fn tess_cone_face(
/// Compute a point on a cone/cylinder surface.
#[inline]
fn cone_pt(
cx: f64, cy: f64, cz: f64,
cx: f64,
cy: f64,
cz: f64,
axis: [f64; 3],
u_dir: [f64; 3],
v_dir: [f64; 3],
@ -338,7 +371,9 @@ fn cone_pt(
/// `full_circle` is true when there are no boundary vertices (e.g. a sphere or
/// a cylinder with no seam edge).
fn angular_range(
cx: f64, cy: f64, cz: f64,
cx: f64,
cy: f64,
cz: f64,
axis: [f64; 3],
u_dir: [f64; 3],
v_dir: [f64; 3],
@ -397,7 +432,7 @@ fn tess_sphere_face(
let nv = GRID_V;
for j in 0..nv {
let phi0 = std::f64::consts::PI * (j as f64 / nv as f64); // 0..π
let phi0 = std::f64::consts::PI * (j as f64 / nv as f64); // 0..π
let phi1 = std::f64::consts::PI * ((j + 1) as f64 / nv as f64);
for i in 0..nu {
@ -429,13 +464,7 @@ fn tess_sphere_face(
}
#[inline]
fn sphere_dir(
pole: [f64; 3],
u_dir: [f64; 3],
v_dir: [f64; 3],
theta: f64,
phi: f64,
) -> [f64; 3] {
fn sphere_dir(pole: [f64; 3], u_dir: [f64; 3], v_dir: [f64; 3], theta: f64, phi: f64) -> [f64; 3] {
let sin_phi = phi.sin();
let cos_phi = phi.cos();
let cos_theta = theta.cos();
@ -477,10 +506,18 @@ fn tess_torus_face(
let theta1 = TAU * ((i + 1) as f64 / nu as f64);
let p = [
torus_pt(cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta0, phi0),
torus_pt(cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta0, phi1),
torus_pt(cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta1, phi1),
torus_pt(cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta1, phi0),
torus_pt(
cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta0, phi0,
),
torus_pt(
cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta0, phi1,
),
torus_pt(
cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta1, phi1,
),
torus_pt(
cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta1, phi0,
),
];
// Outward tube normal.
@ -505,14 +542,16 @@ fn tess_torus_face(
#[inline]
fn torus_pt(
cx: f64, cy: f64, cz: f64,
cx: f64,
cy: f64,
cz: f64,
axis: [f64; 3],
u_dir: [f64; 3],
v_dir: [f64; 3],
major_r: f64,
minor_r: f64,
theta: f64, // tube angle
phi: f64, // revolution angle
theta: f64, // tube angle
phi: f64, // revolution angle
) -> [f64; 3] {
// Ring center at angle phi.
let ring = [

View file

@ -13,8 +13,10 @@
// Entities not handled by acad_to_truck (Viewport, Hatch, …) are tessellated
// by the legacy geometry() path so nothing regresses.
use acadrust::entities::{Dimension, Leader, LeaderContentType, MultiLeader, MultiLeaderPathType, Text};
use crate::entities::multileader::catmull_rom_pts;
use acadrust::entities::{
Dimension, Leader, LeaderContentType, MultiLeader, MultiLeaderPathType, Text,
};
use acadrust::types::{Color as AcadColor, Vector3};
use acadrust::{CadDocument, EntityType, Handle};
use glam::Vec3;
@ -76,18 +78,35 @@ pub fn tessellate(
} else {
xdata.get_record("AcAnnoPO").is_some()
};
if is_annotative { anno_scale } else { 1.0 }
if is_annotative {
anno_scale
} else {
1.0
}
};
// MultiLeader/Leader need anno_scale for arrow/dogleg/text — handle before generic path.
if let EntityType::MultiLeader(ml) = entity {
return tessellate_multileader_single(
document, handle, ml, selected, entity_color, line_weight_px, world_offset, anno_scale,
document,
handle,
ml,
selected,
entity_color,
line_weight_px,
world_offset,
anno_scale,
);
}
if let EntityType::Leader(leader) = entity {
return tessellate_leader_single(
handle, leader, selected, entity_color, line_weight_px, world_offset, anno_scale,
handle,
leader,
selected,
entity_color,
line_weight_px,
world_offset,
anno_scale,
);
}
@ -107,7 +126,10 @@ pub fn tessellate(
// Annotation scale: scale glyph strokes relative to the text
// insertion point (first group's origin) so multi-line MText
// lines spread apart correctly as well as growing in size.
let ref_origin = stroke_groups.first().map(|g| g.origin).unwrap_or([0.0, 0.0]);
let ref_origin = stroke_groups
.first()
.map(|g| g.origin)
.unwrap_or([0.0, 0.0]);
let ref_lx = (ref_origin[0] - ox) as f32;
let ref_ly = (ref_origin[1] - oy) as f32;
for group in &stroke_groups {
@ -141,11 +163,11 @@ pub fn tessellate(
snap_pts,
tangent_geoms: te.tangent_geoms,
aci: 0,
key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
};
}
@ -174,9 +196,9 @@ pub fn tessellate(
aci: 0,
key_vertices: te.key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
};
}
_ => {}
@ -187,7 +209,9 @@ pub fn tessellate(
if let TruckTessResult::Lines(points) = tessellate_edge(&e, world_offset) {
let [ox, oy, oz] = world_offset;
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.collect();
return WireModel {
@ -203,9 +227,9 @@ pub fn tessellate(
aci: 0,
key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
};
}
}
@ -214,7 +238,9 @@ pub fn tessellate(
if let TruckTessResult::Lines(points) = tessellate_wire(&w, world_offset) {
let [ox, oy, oz] = world_offset;
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.collect();
return WireModel {
@ -230,9 +256,9 @@ pub fn tessellate(
aci: 0,
key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
};
}
}
@ -242,15 +268,25 @@ pub fn tessellate(
// leader, mesh, solid2d, etc.). Subtract world_offset so the
// geometry lands in local space alongside Line/Arc/Circle.
let [ox, oy, oz] = world_offset;
let local_pts: Vec<[f32; 3]> = points.into_iter().map(|[x, y, z]| {
if x.is_nan() { [x, y, z] }
else { [x - ox as f32, y - oy as f32, z - oz as f32] }
}).collect();
let local_pts: Vec<[f32; 3]> = points
.into_iter()
.map(|[x, y, z]| {
if x.is_nan() {
[x, y, z]
} else {
[x - ox as f32, y - oy as f32, z - oz as f32]
}
})
.collect();
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.collect();
let fill_tris: Vec<[f32; 3]> = te.fill_tris.into_iter()
let fill_tris: Vec<[f32; 3]> = te
.fill_tris
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.collect();
return WireModel {
@ -274,12 +310,20 @@ pub fn tessellate(
TruckObject::SegmentedLines(points) => {
let [ox, oy, oz] = world_offset;
let local_pts: Vec<[f32; 3]> = points.into_iter().map(|[x, y, z]| {
if x.is_nan() { [x, y, z] }
else { [x - ox as f32, y - oy as f32, z - oz as f32] }
}).collect();
let local_pts: Vec<[f32; 3]> = points
.into_iter()
.map(|[x, y, z]| {
if x.is_nan() {
[x, y, z]
} else {
[x - ox as f32, y - oy as f32, z - oz as f32]
}
})
.collect();
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
let key_vertices: Vec<[f32; 3]> = te
.key_vertices
.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.collect();
return WireModel {
@ -295,7 +339,7 @@ pub fn tessellate(
aci: 0,
key_vertices,
plinegen: false,
vp_scissor: None,
vp_scissor: None,
aabb: WireModel::UNBOUNDED_AABB,
fill_tris: vec![],
};
@ -317,11 +361,7 @@ pub fn tessellate(
_ => None,
};
if let Some(p) = por {
let sp = Vec3::new(
(p.x - ox) as f32,
(p.y - oy) as f32,
(p.z - oz) as f32,
);
let sp = Vec3::new((p.x - ox) as f32, (p.y - oy) as f32, (p.z - oz) as f32);
wm.snap_pts.push((sp, SnapHint::Insertion));
}
return wm;
@ -344,9 +384,9 @@ pub fn tessellate(
tangent_geoms,
key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
}
}
@ -370,8 +410,10 @@ pub fn tessellate_dimension(
let (arrow_size, dimexo, dimexe) = document
.dim_styles
.iter()
.find(|s| s.name.eq_ignore_ascii_case(style_name)
|| (style_name.trim().is_empty() && s.name.eq_ignore_ascii_case("Standard")))
.find(|s| {
s.name.eq_ignore_ascii_case(style_name)
|| (style_name.trim().is_empty() && s.name.eq_ignore_ascii_case("Standard"))
})
.map(|s| {
let scale = (if s.dimscale > 1e-6 { s.dimscale } else { 1.0 }) * anno_scale as f64;
(
@ -403,9 +445,9 @@ pub fn tessellate_dimension(
tangent_geoms: vec![],
key_vertices,
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
}];
if let Some(text) = dimension_text_entity(dim) {
@ -437,12 +479,15 @@ fn tessellate_leader_single(
world_offset: [f64; 3],
anno_scale: f32,
) -> WireModel {
let color = if selected { WireModel::SELECTED } else { entity_color };
let color = if selected {
WireModel::SELECTED
} else {
entity_color
};
let name = handle.value().to_string();
let [ox, oy, oz] = world_offset;
let p3 = |v: &Vector3| -> [f32; 3] {
[(v.x - ox) as f32, (v.y - oy) as f32, (v.z - oz) as f32]
};
let p3 =
|v: &Vector3| -> [f32; 3] { [(v.x - ox) as f32, (v.y - oy) as f32, (v.z - oz) as f32] };
let nan = [f32::NAN; 3];
let verts = &leader.vertices;
@ -472,7 +517,10 @@ fn tessellate_leader_single(
let key_vertices: Vec<[f32; 3]> = verts.iter().map(|v| p3(v)).collect();
for i in 0..verts.len().saturating_sub(1) {
tangents.push(TangentGeom::Line { p1: p3(&verts[i]), p2: p3(&verts[i + 1]) });
tangents.push(TangentGeom::Line {
p1: p3(&verts[i]),
p2: p3(&verts[i + 1]),
});
}
if leader.arrow_enabled {
@ -487,15 +535,27 @@ fn tessellate_leader_single(
let (s, c) = a.sin_cos();
let tip_f = p3(tip);
points.push(nan);
points.push([tip_f[0] + (dx*c - dy*s)*sz, tip_f[1] + (dx*s + dy*c)*sz, tip_f[2]]);
points.push([
tip_f[0] + (dx * c - dy * s) * sz,
tip_f[1] + (dx * s + dy * c) * sz,
tip_f[2],
]);
points.push(tip_f);
points.push([tip_f[0] + (dx*c + dy*s)*sz, tip_f[1] + (-dx*s + dy*c)*sz, tip_f[2]]);
points.push([
tip_f[0] + (dx * c + dy * s) * sz,
tip_f[1] + (-dx * s + dy * c) * sz,
tip_f[2],
]);
}
if leader.hookline_enabled {
let last = verts.last().unwrap();
let prev = &verts[verts.len() - 2];
let sign = if (last.x - prev.x) >= 0.0 { 1.0_f32 } else { -1.0_f32 };
let sign = if (last.x - prev.x) >= 0.0 {
1.0_f32
} else {
-1.0_f32
};
let land_len = leader.text_height as f32 * 1.5 * anno_scale;
let last_f = p3(last);
points.push(nan);
@ -532,7 +592,11 @@ fn tessellate_multileader_single(
world_offset: [f64; 3],
anno_scale: f32,
) -> WireModel {
let color = if selected { WireModel::SELECTED } else { entity_color };
let color = if selected {
WireModel::SELECTED
} else {
entity_color
};
let name = handle.value().to_string();
let nan = [f32::NAN; 3];
let [ox, oy, oz] = world_offset;
@ -556,35 +620,52 @@ fn tessellate_multileader_single(
snap_pts.push((Vec3::from(cp_f), SnapHint::Node));
for line in &root.lines {
if line.points.is_empty() { continue; }
if line.points.is_empty() {
continue;
}
if !invisible {
if !first { points.push(nan); }
if !first {
points.push(nan);
}
first = false;
let mut ctrl: Vec<[f32; 3]> = line.points.iter().map(|p| p3(p)).collect();
let last_f = *ctrl.last().unwrap_or(&cp_f);
let dist = ((last_f[0]-cp_f[0]).powi(2) + (last_f[1]-cp_f[1]).powi(2)).sqrt();
if dist > 1e-9 { ctrl.push(cp_f); }
let dist = ((last_f[0] - cp_f[0]).powi(2) + (last_f[1] - cp_f[1]).powi(2)).sqrt();
if dist > 1e-9 {
ctrl.push(cp_f);
}
for &c in &ctrl {
key_verts.push(c);
snap_pts.push((Vec3::from(c), SnapHint::Node));
}
if ml.path_type == MultiLeaderPathType::Spline && ctrl.len() >= 2 {
for pt in catmull_rom_pts(&ctrl, 8) { points.push(pt); }
for pt in catmull_rom_pts(&ctrl, 8) {
points.push(pt);
}
} else {
for &c in &ctrl { points.push(c); }
for &c in &ctrl {
points.push(c);
}
}
for i in 0..ctrl.len().saturating_sub(1) {
tangents.push(TangentGeom::Line { p1: ctrl[i], p2: ctrl[i + 1] });
tangents.push(TangentGeom::Line {
p1: ctrl[i],
p2: ctrl[i + 1],
});
}
}
if draw_arrow {
let tip = &line.points[0];
let tip_f = p3(tip);
let next = if line.points.len() >= 2 { line.points[1] } else { *cp };
let next = if line.points.len() >= 2 {
line.points[1]
} else {
*cp
};
let dx = (next.x - tip.x) as f32;
let dy = (next.y - tip.y) as f32;
let dl = (dx * dx + dy * dy).sqrt().max(1e-9);
@ -592,9 +673,17 @@ fn tessellate_multileader_single(
let a = std::f32::consts::PI / 6.0;
let (s, c) = a.sin_cos();
points.push(nan);
points.push([tip_f[0] + (dx*c - dy*s)*arrow_size, tip_f[1] + (dx*s + dy*c)*arrow_size, tip_f[2]]);
points.push([
tip_f[0] + (dx * c - dy * s) * arrow_size,
tip_f[1] + (dx * s + dy * c) * arrow_size,
tip_f[2],
]);
points.push(tip_f);
points.push([tip_f[0] + (dx*c + dy*s)*arrow_size, tip_f[1] + (-dx*s + dy*c)*arrow_size, tip_f[2]]);
points.push([
tip_f[0] + (dx * c + dy * s) * arrow_size,
tip_f[1] + (-dx * s + dy * c) * arrow_size,
tip_f[2],
]);
}
}
@ -604,16 +693,27 @@ fn tessellate_multileader_single(
let d = ml.dogleg_length * anno_scale as f64;
points.push(nan);
points.push(cp_f);
points.push([(cp.x + dir.x/dl*d - ox) as f32, (cp.y + dir.y/dl*d - oy) as f32, cp_f[2]]);
points.push([
(cp.x + dir.x / dl * d - ox) as f32,
(cp.y + dir.y / dl * d - oy) as f32,
cp_f[2],
]);
}
}
// Text strokes — scale height, keep insertion point fixed.
if ml.content_type == LeaderContentType::MText && !ml.context.text_string.is_empty() {
let height = if ml.context.text_height > 0.0 { ml.context.text_height } else { ml.text_height };
let height = if ml.context.text_height > 0.0 {
ml.context.text_height
} else {
ml.text_height
};
let ins = &ml.context.text_location;
let z = (ins.z - oz) as f32;
snap_pts.push((Vec3::new((ins.x - ox) as f32, (ins.y - oy) as f32, z), SnapHint::Node));
snap_pts.push((
Vec3::new((ins.x - ox) as f32, (ins.y - oy) as f32, z),
SnapHint::Node,
));
let style = crate::entities::text_support::resolve_text_style("STANDARD", document);
let strokes = crate::scene::cxf::tessellate_text_ex(
[ins.x as f32, ins.y as f32],
@ -625,7 +725,9 @@ fn tessellate_multileader_single(
&ml.context.text_string,
);
for stroke in &strokes {
if stroke.len() < 2 { continue; }
if stroke.len() < 2 {
continue;
}
points.push(nan);
for &[x, y] in stroke {
points.push([x - ox as f32, y - oy as f32, z]);
@ -732,9 +834,8 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
let normal = (h.normal.x, h.normal.y, h.normal.z);
// Convert a 2D OCS hatch boundary point to WCS, then subtract world_offset.
let to_wcs = |x: f64, y: f64| -> [f32; 3] {
let (wx, wy, wz) = crate::scene::transform::ocs_point_to_wcs(
(x, y, h.elevation), normal,
);
let (wx, wy, wz) =
crate::scene::transform::ocs_point_to_wcs((x, y, h.elevation), normal);
[(wx - ox) as f32, (wy - oy) as f32, (wz - oz) as f32]
};
let mut pts: Vec<[f32; 3]> = Vec::new();
@ -757,7 +858,9 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
acadrust::entities::BoundaryEdge::Line(ln) => {
let p0 = to_wcs(ln.start.x, ln.start.y);
let p1 = to_wcs(ln.end.x, ln.end.y);
if !pts.is_empty() { pts.push([f32::NAN; 3]); }
if !pts.is_empty() {
pts.push([f32::NAN; 3]);
}
pts.push(p0);
pts.push(p1);
key_verts.push(p0);
@ -767,16 +870,12 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
let sa = arc.start_angle;
let ea = arc.end_angle;
let ccw = arc.counter_clockwise;
let (sa, ea) = if ccw {
(sa, ea)
} else {
(TAU - sa, TAU - ea)
};
let (sa, ea) = if ccw { (sa, ea) } else { (TAU - sa, TAU - ea) };
let span = ea - sa;
let segs = ((span / TAU) * 32.0)
.ceil()
.max(4.0) as u32;
if !pts.is_empty() { pts.push([f32::NAN; 3]); }
let segs = ((span / TAU) * 32.0).ceil().max(4.0) as u32;
if !pts.is_empty() {
pts.push([f32::NAN; 3]);
}
for i in 0..=segs {
let t = sa + span * (i as f64 / segs as f64);
let p = to_wcs(
@ -784,9 +883,14 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
arc.center.y + arc.radius * t.sin() as f64,
);
pts.push(p);
if i == 0 || i == segs { key_verts.push(p); }
if i == 0 || i == segs {
key_verts.push(p);
}
}
snap_pts.push((Vec3::from(to_wcs(arc.center.x, arc.center.y)), SnapHint::Center));
snap_pts.push((
Vec3::from(to_wcs(arc.center.x, arc.center.y)),
SnapHint::Center,
));
}
acadrust::entities::BoundaryEdge::EllipticArc(ell) => {
let r_maj = ((ell.major_axis_endpoint.x * ell.major_axis_endpoint.x
@ -805,10 +909,11 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
} else {
ea - sa + std::f32::consts::TAU
};
let segs = ((span / std::f32::consts::TAU) * 32.0)
.ceil()
.max(4.0) as u32;
if !pts.is_empty() { pts.push([f32::NAN; 3]); }
let segs =
((span / std::f32::consts::TAU) * 32.0).ceil().max(4.0) as u32;
if !pts.is_empty() {
pts.push([f32::NAN; 3]);
}
for i in 0..=segs {
let t = sa + span * (i as f32 / segs as f32);
let lx = r_maj * t.cos();
@ -817,9 +922,14 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
let ocs_y = ell.center.y + (lx * rot.sin() + ly * rot.cos()) as f64;
let p = to_wcs(ocs_x, ocs_y);
pts.push(p);
if i == 0 || i == segs { key_verts.push(p); }
if i == 0 || i == segs {
key_verts.push(p);
}
}
snap_pts.push((Vec3::from(to_wcs(ell.center.x, ell.center.y)), SnapHint::Center));
snap_pts.push((
Vec3::from(to_wcs(ell.center.x, ell.center.y)),
SnapHint::Center,
));
}
_ => {}
}
@ -837,7 +947,7 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
let y0 = (ole.lower_right_corner.y - oy) as f32;
let x1 = (ole.lower_right_corner.x - ox) as f32;
let y1 = (ole.upper_left_corner.y - oy) as f32;
let z = (ole.upper_left_corner.z - oz) as f32;
let z = (ole.upper_left_corner.z - oz) as f32;
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
// Degenerate / unknown size — show a small cross.
let s = 0.5_f32;
@ -845,12 +955,20 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
}
let pts = vec![
// Outer rectangle
[x0, y0, z], [x1, y0, z], [x1, y0, z], [x1, y1, z],
[x1, y1, z], [x0, y1, z], [x0, y1, z], [x0, y0, z],
[x0, y0, z],
[x1, y0, z],
[x1, y0, z],
[x1, y1, z],
[x1, y1, z],
[x0, y1, z],
[x0, y1, z],
[x0, y0, z],
// Diagonal X
[x0, y0, z], [x1, y1, z],
[x0, y0, z],
[x1, y1, z],
[f32::NAN, f32::NAN, f32::NAN],
[x1, y0, z], [x0, y1, z],
[x1, y0, z],
[x0, y1, z],
];
(pts, vec![], vec![], vec![[x0, y0, z], [x1, y1, z]])
}
@ -870,8 +988,8 @@ fn solid_wire_fallback(entity: &EntityType, world_offset: [f64; 3]) -> Vec<[f32;
let [ox, oy, oz] = world_offset;
let wires: &[acadrust::entities::Wire] = match entity {
EntityType::Solid3D(s) => &s.wires,
EntityType::Region(r) => &r.wires,
EntityType::Body(b) => &b.wires,
EntityType::Region(r) => &r.wires,
EntityType::Body(b) => &b.wires,
_ => return vec![],
};
@ -908,14 +1026,32 @@ fn dimension_geometry(
let second = lv(d.second_point);
let def = lv(d.definition_point);
let axis = normalized_or(second - first, Vec3::X);
append_linear_dimension(&mut points, first, second, def, axis, arrow_size, dimexo, dimexe);
append_linear_dimension(
&mut points,
first,
second,
def,
axis,
arrow_size,
dimexo,
dimexe,
);
}
Dimension::Linear(d) => {
let first = lv(d.first_point);
let second = lv(d.second_point);
let def = lv(d.definition_point);
let axis = Vec3::new(d.rotation.cos() as f32, d.rotation.sin() as f32, 0.0);
append_linear_dimension(&mut points, first, second, def, normalized_or(axis, Vec3::X), arrow_size, dimexo, dimexe);
append_linear_dimension(
&mut points,
first,
second,
def,
normalized_or(axis, Vec3::X),
arrow_size,
dimexo,
dimexe,
);
}
Dimension::Radius(d) => {
let center = lv(d.angle_vertex);
@ -923,7 +1059,12 @@ fn dimension_geometry(
let text = dimension_text_position(dim, world_offset);
add_segment(&mut points, center, point);
add_segment(&mut points, point, text);
append_arrow(&mut points, point, normalized_or(center - point, Vec3::X), arrow_size);
append_arrow(
&mut points,
point,
normalized_or(center - point, Vec3::X),
arrow_size,
);
}
Dimension::Diameter(d) => {
let p1 = lv(d.angle_vertex);
@ -982,9 +1123,9 @@ fn append_linear_dimension(
let sign1 = if offset1 >= 0.0 { 1.0_f32 } else { -1.0 };
let sign2 = if offset2 >= 0.0 { 1.0_f32 } else { -1.0 };
let ext1_start = first + perp * (sign1 * dimexo);
let ext1_end = d1 + perp * (sign1 * dimexe);
let ext1_end = d1 + perp * (sign1 * dimexe);
let ext2_start = second + perp * (sign2 * dimexo);
let ext2_end = d2 + perp * (sign2 * dimexe);
let ext2_end = d2 + perp * (sign2 * dimexe);
add_segment(points, ext1_start, ext1_end);
add_segment(points, ext2_start, ext2_end);
add_segment(points, d1, d2);
@ -1081,13 +1222,35 @@ fn dimension_snap_pts(dim: &Dimension, world_offset: [f64; 3]) -> Vec<(Vec3, Sna
};
let node = |v: acadrust::types::Vector3| (lv(v), SnapHint::Node);
match dim {
Dimension::Linear(d) => vec![node(d.first_point), node(d.second_point), node(d.definition_point)],
Dimension::Aligned(d) => vec![node(d.first_point), node(d.second_point), node(d.definition_point)],
Dimension::Linear(d) => vec![
node(d.first_point),
node(d.second_point),
node(d.definition_point),
],
Dimension::Aligned(d) => vec![
node(d.first_point),
node(d.second_point),
node(d.definition_point),
],
Dimension::Radius(d) => vec![node(d.angle_vertex), node(d.definition_point)],
Dimension::Diameter(d) => vec![node(d.angle_vertex), node(d.definition_point)],
Dimension::Angular2Ln(d) => vec![node(d.angle_vertex), node(d.first_point), node(d.second_point), node(d.definition_point)],
Dimension::Angular3Pt(d) => vec![node(d.angle_vertex), node(d.first_point), node(d.second_point), node(d.definition_point)],
Dimension::Ordinate(d) => vec![node(d.definition_point), node(d.feature_location), node(d.leader_endpoint)],
Dimension::Angular2Ln(d) => vec![
node(d.angle_vertex),
node(d.first_point),
node(d.second_point),
node(d.definition_point),
],
Dimension::Angular3Pt(d) => vec![
node(d.angle_vertex),
node(d.first_point),
node(d.second_point),
node(d.definition_point),
],
Dimension::Ordinate(d) => vec![
node(d.definition_point),
node(d.feature_location),
node(d.leader_endpoint),
],
}
}
@ -1172,17 +1335,30 @@ fn dimension_text_position(dim: &Dimension, world_offset: [f64; 3]) -> Vec3 {
fn dimension_text_height(dim: &Dimension) -> f64 {
let scale = (dim.measurement().abs() * 0.12).clamp(0.25, 2.0);
if scale.is_finite() { scale } else { 0.25 }
if scale.is_finite() {
scale
} else {
0.25
}
}
fn vec3_local(v: Vector3, off: [f64; 3]) -> Vec3 {
Vec3::new((v.x - off[0]) as f32, (v.y - off[1]) as f32, (v.z - off[2]) as f32)
Vec3::new(
(v.x - off[0]) as f32,
(v.y - off[1]) as f32,
(v.z - off[2]) as f32,
)
}
fn offset_snap_pts(pts: Vec<(Vec3, SnapHint)>, off: [f64; 3]) -> Vec<(Vec3, SnapHint)> {
let [ox, oy, oz] = off;
pts.into_iter()
.map(|(p, h)| (Vec3::new(p.x - ox as f32, p.y - oy as f32, p.z - oz as f32), h))
.map(|(p, h)| {
(
Vec3::new(p.x - ox as f32, p.y - oy as f32, p.z - oz as f32),
h,
)
})
.collect()
}

View file

@ -98,10 +98,7 @@ pub fn ocs_axes(normal: (f64, f64, f64)) -> ((f64, f64, f64), (f64, f64, f64)) {
/// Transform a single OCS point to WCS using the given normal vector.
#[inline]
pub fn ocs_point_to_wcs(
ocs: (f64, f64, f64),
normal: (f64, f64, f64),
) -> (f64, f64, f64) {
pub fn ocs_point_to_wcs(ocs: (f64, f64, f64), normal: (f64, f64, f64)) -> (f64, f64, f64) {
let (ax, ay) = ocs_axes(normal);
let (ox, oy, oz) = ocs;
let (nx, ny, nz) = normal;

View file

@ -44,7 +44,11 @@ pub enum TruckTessResult {
/// raw world coordinates are in the millions (e.g. Turkish UTM).
#[inline]
fn to_local(x: f64, y: f64, z: f64, off: [f64; 3]) -> [f32; 3] {
[(x - off[0]) as f32, (y - off[1]) as f32, (z - off[2]) as f32]
[
(x - off[0]) as f32,
(y - off[1]) as f32,
(z - off[2]) as f32,
]
}
// ── Vertex ────────────────────────────────────────────────────────────────

View file

@ -24,14 +24,22 @@ pub struct ViewportPane<'a> {
impl<'a> ViewportPane<'a> {
pub fn model(scene: &'a Scene, show_viewcube: bool) -> Self {
Self { scene, mode: ViewportPaneMode::Model, show_viewcube }
Self {
scene,
mode: ViewportPaneMode::Model,
show_viewcube,
}
}
/// One paper-space viewport: model content rendered through its own camera.
/// See [`ViewportPaneMode::Paper`] for why this is currently unused.
#[allow(dead_code)]
pub fn paper(scene: &'a Scene, handle: Handle) -> Self {
Self { scene, mode: ViewportPaneMode::Paper { handle }, show_viewcube: false }
Self {
scene,
mode: ViewportPaneMode::Paper { handle },
show_viewcube: false,
}
}
}
@ -62,7 +70,8 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for PaperViewportPan
_cursor: mouse::Cursor,
bounds: Rectangle,
) -> Self::Primitive {
self.scene.build_active_viewport_primitive(self.handle, state.hover_region, bounds)
self.scene
.build_active_viewport_primitive(self.handle, state.hover_region, bounds)
}
fn update(
@ -101,10 +110,12 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for ViewportPane<'a>
) -> Self::Primitive {
match &self.mode {
ViewportPaneMode::Model => {
self.scene.build_primitive(state.hover_region, bounds, self.show_viewcube)
self.scene
.build_primitive(state.hover_region, bounds, self.show_viewcube)
}
ViewportPaneMode::Paper { handle } => {
self.scene.build_viewport_primitive(*handle, state.hover_region, bounds)
self.scene
.build_viewport_primitive(*handle, state.hover_region, bounds)
}
}
}

View file

@ -76,8 +76,12 @@ impl WireModel {
pub const CYAN: [f32; 4] = [0.25, 0.85, 1.00, 1.0];
pub const SELECTED: [f32; 4] = [0.15, 0.55, 1.00, 1.0];
/// Sentinel AABB that never rejects any snap query.
pub const UNBOUNDED_AABB: [f32; 4] =
[f32::NEG_INFINITY, f32::NEG_INFINITY, f32::INFINITY, f32::INFINITY];
pub const UNBOUNDED_AABB: [f32; 4] = [
f32::NEG_INFINITY,
f32::NEG_INFINITY,
f32::INFINITY,
f32::INFINITY,
];
/// Create a solid wire (no dash pattern, 1px weight).
pub fn solid(name: String, points: Vec<[f32; 3]>, color: [f32; 4], selected: bool) -> Self {