forked from 1b-agent/opencadstudio-upstream
fix(thickness): edit persists + wide polyline extrudes as a solid tube
Entering a thickness (DXF 39 extrusion) on a wide polyline now works end to end: - The properties Thickness field is a General-group common prop, so it is routed to apply_common_prop (set_entity_thickness) instead of the geometry setter, which ignored it — the value was silently dropped and reverted to 0. - A wide polyline extrudes its whole band into a 3-D tube (outer + inner walls with top and bottom caps), not just its centreline. - The tube is real 3-D geometry, not a flat draw-order hatch: it renders solid in every view mode with true depth, so nothing shows through the walls. The flat 2-D band fill is skipped for a thickened polyline. - A fill whose triangles span depth keeps its real depth (no draw-order bias), so the extrusion's own edge wires stay in front of the surface and remain visible instead of only the silhouette. - The corner and cap edge lines read black, framing the coloured tube. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
626e8091f7
commit
3d37e1f8d8
5 files changed
+124
-3
No files matched your search
@@ -1576,7 +1576,13 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
);
|
||||
}
|
||||
}
|
||||
"linetype_scale" | "transparency" => {
|
||||
"linetype_scale" | "transparency" | "thickness" => {
|
||||
// Thickness (DXF 39 extrusion) is a General-
|
||||
// group common prop, not a geometry-group one:
|
||||
// route it to apply_common_prop (which handles
|
||||
// it via set_entity_thickness). apply_geom_prop
|
||||
// ignores it, so the edit would otherwise be a
|
||||
// silent no-op and revert to the old value.
|
||||
if let Some(entity) =
|
||||
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||
{
|
||||
|
||||
@@ -100,6 +100,83 @@ fn thick_segments(
|
||||
}
|
||||
}
|
||||
|
||||
/// Extrude a *wide* LwPolyline's band into a 3-D tube: each band segment's outer
|
||||
/// and inner boundary rises by `thickness` (along the normal) into a wall,
|
||||
/// leaving the two radial segment-end edges open (they are internal to the
|
||||
/// band). The flat bottom band is already drawn by the wide-fill path; this
|
||||
/// adds the vertical walls and their outline. Used instead of `thick_segments`
|
||||
/// (which extrudes only the centreline) when the polyline carries a width.
|
||||
fn thick_wide_band(
|
||||
pl: &LwPolyline,
|
||||
thickness: f64,
|
||||
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
|
||||
normal: (f64, f64, f64),
|
||||
key_verts: Vec<[f64; 3]>,
|
||||
tangents: Vec<TangentGeom>,
|
||||
) -> TruckEntity {
|
||||
let (origin, fills) = wide_fills(pl);
|
||||
let (nx, ny, nz) = normal;
|
||||
let t = thickness;
|
||||
let off = |p: [f64; 3]| -> [f64; 3] { [p[0] + t * nx, p[1] + t * ny, p[2] + t * nz] };
|
||||
let push_seg = |lines: &mut Vec<[f64; 3]>, a: [f64; 3], b: [f64; 3]| {
|
||||
lines.push(a);
|
||||
lines.push(b);
|
||||
lines.push([f64::NAN; 3]);
|
||||
};
|
||||
|
||||
let mut lines: Vec<[f64; 3]> = Vec::new();
|
||||
// Shaded surfaces: the vertical walls plus the top cap. The flat bottom cap
|
||||
// is already drawn by the wide-fill hatch path, so it is not repeated here.
|
||||
let mut fill_tris: Vec<[f64; 3]> = Vec::new();
|
||||
for poly in &fills {
|
||||
let n = poly.len();
|
||||
if n < 4 {
|
||||
continue;
|
||||
}
|
||||
// `polyline_segment_fill` returns the band loop as the outer boundary
|
||||
// forward then the inner boundary back, so the two transition edges
|
||||
// (half-1 → half and n-1 → 0) are the radial segment ends.
|
||||
let half = n / 2;
|
||||
let bot: Vec<[f64; 3]> = poly
|
||||
.iter()
|
||||
.map(|&[x, y]| {
|
||||
let (wx, wy, wz) = to_wcs(origin[0] + x as f64, origin[1] + y as f64);
|
||||
[wx, wy, wz]
|
||||
})
|
||||
.collect();
|
||||
let top: Vec<[f64; 3]> = bot.iter().map(|&p| off(p)).collect();
|
||||
for k in 0..n {
|
||||
// Vertical at every boundary point.
|
||||
push_seg(&mut lines, bot[k], top[k]);
|
||||
// Skip the two radial cap edges — they stay inside the band.
|
||||
if k == half - 1 || k == n - 1 {
|
||||
continue;
|
||||
}
|
||||
let kn = (k + 1) % n;
|
||||
push_seg(&mut lines, bot[k], bot[kn]); // bottom boundary
|
||||
push_seg(&mut lines, top[k], top[kn]); // top boundary
|
||||
// Wall quad (two triangles).
|
||||
fill_tris.extend_from_slice(&[
|
||||
bot[k], bot[kn], top[kn], bot[k], top[kn], top[k],
|
||||
]);
|
||||
}
|
||||
// Bottom + top caps — the band ring triangulated at each plane. The
|
||||
// bottom cap is a real 3-D surface (with depth) so the walls occlude it
|
||||
// correctly; the flat 2-D band fill is skipped for thickened polylines.
|
||||
fill_tris.extend(crate::entities::mesh::triangulate_planar(&bot));
|
||||
fill_tris.extend(crate::entities::mesh::triangulate_planar(&top));
|
||||
}
|
||||
|
||||
TruckEntity {
|
||||
pick_tris: fill_tris.clone(),
|
||||
object: TruckObject::Lines(lines),
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: tangents,
|
||||
key_vertices: key_verts,
|
||||
fill_tris,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_truck(pline: &LwPolyline) -> TruckEntity {
|
||||
let verts = &pline.vertices;
|
||||
if verts.is_empty() {
|
||||
@@ -172,6 +249,16 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
|
||||
kv.push([wbx, wby, wbz]);
|
||||
seg_data.push((ox0, oy0, ox1, oy1));
|
||||
}
|
||||
// A wide polyline extrudes its whole band into a tube (outer + inner
|
||||
// walls); a zero-width one just extrudes its centreline.
|
||||
let is_wide = pline.constant_width > 1e-9
|
||||
|| pline
|
||||
.vertices
|
||||
.iter()
|
||||
.any(|v| v.start_width > 1e-9 || v.end_width > 1e-9);
|
||||
if is_wide {
|
||||
return thick_wide_band(pline, pline.thickness, &to_wcs, normal, kv, tgs);
|
||||
}
|
||||
return thick_segments(&seg_data, &path, pline.thickness, normal, kv, tgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -1116,6 +1116,18 @@ pub fn tessellate(
|
||||
let mut out = Vec::new();
|
||||
let mut is_first = true;
|
||||
|
||||
// A thickened wide polyline's extrusion — its corner / cap edges
|
||||
// frame the solid tube and read black (like solid-with-edges
|
||||
// outlines), while the tube fill keeps the entity colour.
|
||||
let edge_color = if matches!(
|
||||
entity,
|
||||
EntityType::LwPolyline(p) if p.thickness.abs() > 1e-10
|
||||
) {
|
||||
[0.0, 0.0, 0.0, 1.0]
|
||||
} else {
|
||||
color
|
||||
};
|
||||
|
||||
if !local_pts.is_empty() {
|
||||
let (snap, keys, tangents) = if is_first {
|
||||
is_first = false;
|
||||
@@ -1137,7 +1149,7 @@ pub fn tessellate(
|
||||
name: name.clone(),
|
||||
points: local_pts,
|
||||
points_low: local_pts_low,
|
||||
color,
|
||||
color: edge_color,
|
||||
selected,
|
||||
pattern_length: 0.0,
|
||||
pattern: [0.0; 8],
|
||||
|
||||
@@ -691,6 +691,11 @@ impl Scene {
|
||||
// Wide LWPolyline and Polyline2D fills
|
||||
for entity in self.document.entities() {
|
||||
let (common, fill_origin, fills) = match entity {
|
||||
// A thickened wide LwPolyline becomes a 3-D tube: its bottom cap
|
||||
// is drawn as a real depth-tested surface by the tessellator, not
|
||||
// as this flat 2-D band (which, having only draw-order depth,
|
||||
// would show through the tube walls in a 3-D view).
|
||||
EntityType::LwPolyline(pl) if pl.thickness.abs() > 1e-10 => continue,
|
||||
EntityType::LwPolyline(pl) => {
|
||||
let (o, f) = crate::entities::lwpolyline::wide_fills(pl);
|
||||
(&pl.common, o, f)
|
||||
|
||||
@@ -211,7 +211,18 @@ impl Face3DGpu {
|
||||
}
|
||||
} else {
|
||||
let fill_color = [r, g, b, a];
|
||||
let depth = depth_of(wire);
|
||||
// A fill whose triangles span depth is a genuine 3-D surface (an
|
||||
// extruded polyline tube), not a flat coplanar overlay (2-D SOLID,
|
||||
// greek text, dimension background). Keep its real depth so the
|
||||
// fill occludes correctly and its own edge wires — coincident with
|
||||
// the surface — win the depth test and stay visible. A flat overlay
|
||||
// keeps the draw-order bias that layers it in screen order.
|
||||
let (mut zmin, mut zmax) = (f32::INFINITY, f32::NEG_INFINITY);
|
||||
for p in &wire.fill_tris {
|
||||
zmin = zmin.min(p[2]);
|
||||
zmax = zmax.max(p[2]);
|
||||
}
|
||||
let depth = if zmax - zmin > 1e-4 { 0.0 } else { depth_of(wire) };
|
||||
for (i, &position) in wire.fill_tris.iter().enumerate() {
|
||||
verts_2d.push(Face3DVertex {
|
||||
position,
|
||||
|
||||
Reference in new issue
Block a user