mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
* claim: #6452 * Engine: a pattern or mirror copy of a join keeps its ends as drawn (#6452) The copy's tool is rebuilt against the current state, which already holds the source feature's own material; the embedded-join-end trim (#2059) read that material as the target already filling the copy's bottom band and trimmed it away before the copy matrix was applied. Mirroring or patterning a join on a hollow part silently lost the floor-level slice. Copies now skip the trim: their ends are where the source drew them. * chore: re-trigger duplicate PR check --------- Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
This commit is contained in:
@@ -5334,11 +5334,15 @@ const APART_GAP: f64 = 1e-3;
|
||||
|
||||
/// The tool bodies a feature adds or removes (extrude, revolve, primitives), in the current state.
|
||||
/// `to_body` is the body a To Object extent runs to (an extrude), when it found one.
|
||||
/// `copy` builds the tool of a pattern's or mirror's source: the state already holds the source
|
||||
/// feature's own material, so a join end that reads as buried in the target there may be bare
|
||||
/// material where the copy lands — its ends are left as drawn (#6452).
|
||||
fn feature_tools_with_constructor(
|
||||
doc: &Document,
|
||||
vals: &BTreeMap<String, Value>,
|
||||
f: &Feature,
|
||||
st: &ModelState,
|
||||
copy: bool,
|
||||
constructor: &mut Option<ExtrudeConstructor>,
|
||||
to_body: &mut Option<String>,
|
||||
) -> Result<Vec<Traced>> {
|
||||
@@ -5599,7 +5603,7 @@ fn feature_tools_with_constructor(
|
||||
}
|
||||
}
|
||||
let surroundings = if exact_planar_cut || touching_batch { None } else { Surroundings::of(st, *operation, &plain_targets) };
|
||||
let join_surroundings = if *operation == Operation::Join && !touching_batch {
|
||||
let join_surroundings = if *operation == Operation::Join && !touching_batch && !copy {
|
||||
// Trimming may discard a tool's material, so only bodies this feature
|
||||
// actually joins can establish that the discarded end is already filled.
|
||||
let participants = if plain_targets.is_empty() { &f.participants } else { &plain_targets };
|
||||
@@ -7013,7 +7017,7 @@ fn replay(
|
||||
// points follow their named faces, as the feature's own did (#2121): a To Object
|
||||
// extrude's copies reach the face where it is now, not where it was picked.
|
||||
let moved = with_named_face_points(doc, st, src).map(|(g, _)| g);
|
||||
let tools = feature_tools_with_constructor(doc, vals, moved.as_ref().unwrap_or(src), st, &mut None, &mut None)?;
|
||||
let tools = feature_tools_with_constructor(doc, vals, moved.as_ref().unwrap_or(src), st, true, &mut None, &mut None)?;
|
||||
for (k, m) in mats.iter().enumerate() {
|
||||
let start = copies.len();
|
||||
for t in &tools {
|
||||
@@ -7612,7 +7616,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
| FeatureKind::Sweep { operation, targets, .. } => {
|
||||
let mut constructor = None;
|
||||
let mut to_body = None;
|
||||
let tools = feature_tools_with_constructor(doc, vals, f, st, &mut constructor, &mut to_body)?;
|
||||
let tools = feature_tools_with_constructor(doc, vals, f, st, false, &mut constructor, &mut to_body)?;
|
||||
// Cuts through sheet metal become cut-outs of its flat pattern.
|
||||
if matches!(operation, Operation::Cut | Operation::Intersect) && matches!(f.kind, FeatureKind::Extrude { .. }) && !st.sheets.is_empty() {
|
||||
let bodies: Vec<Body> = tools.iter().map(|t| t.body.clone()).collect();
|
||||
|
||||
@@ -1155,3 +1155,51 @@ fn editing_a_rolled_back_pattern_says_it_is_not_built() {
|
||||
assert_eq!(reply["rolled_back"], json!(true), "{reply}");
|
||||
assert_eq!(reply["warning"], "1 of 1 cut copies remove no material", "{reply}");
|
||||
}
|
||||
|
||||
/// A 100 x 60 x 30 box shelled 5 mm (floor included) with a 20 x 11 boss joined to its +Y wall:
|
||||
/// 67,500 mm³ hollow, 73,500 mm³ with the boss, which adds 6,000 mm³ (#6452).
|
||||
fn hollow_box_with_join_boss() -> Session {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "Base"}));
|
||||
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [-50, -30], "p1": [50, 30]}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "Base", "distance": 30, "operation": "new", "name": "Block", "body_name": "Box"}));
|
||||
run(&mut s, "solid.shell", json!({"faces": [[0, 0, 30]], "thickness": 5, "name": "Shell"}));
|
||||
assert!(rel(volume(&mut s), 67500.0) < 1e-6, "hollow box");
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY", "name": "Boss"}));
|
||||
run(&mut s, "sketch.line", json!({"closed": true, "points": [[20, 29], [40, 29], [40, 40], [20, 40]]}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"sketch": "Boss", "distance": 30, "operation": "join", "targets": ["Box"], "name": "Join Boss"}));
|
||||
assert!(rel(volume(&mut s), 73500.0) < 1e-6, "joined boss");
|
||||
s
|
||||
}
|
||||
|
||||
/// The copy of a join is built where the source stands, and the source's own material used to
|
||||
/// read as the target already filling the copy's bottom band: the mirrored boss lost its
|
||||
/// 20 x 10 x 5 mm floor-level slice with no warning (#6452).
|
||||
#[test]
|
||||
fn mirror_of_a_join_on_a_hollow_part_copies_the_whole_boss() {
|
||||
let mut s = hollow_box_with_join_boss();
|
||||
run(&mut s, "solid.mirror", json!({"features": ["Join Boss"], "plane": "XZ", "name": "Mirror Boss"}));
|
||||
assert!(rel(volume(&mut s), 79500.0) < 1e-6, "mirrored copy lost material");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mirror_of_a_join_on_a_hollow_part_copies_the_whole_boss_across_a_custom_plane() {
|
||||
let mut s = hollow_box_with_join_boss();
|
||||
run(&mut s, "solid.mirror", json!({"features": ["Join Boss"], "plane": {"origin": [0, 0, 0], "normal": [0, 1, 0]}, "name": "Mirror Boss"}));
|
||||
assert!(rel(volume(&mut s), 79500.0) < 1e-6, "mirrored copy lost material");
|
||||
}
|
||||
|
||||
/// A pure translation loses the same slice: the copy must be congruent to the original,
|
||||
/// whatever filled the space where its tool was rebuilt (#6452).
|
||||
#[test]
|
||||
fn rectangular_pattern_of_a_join_on_a_hollow_part_copies_the_whole_boss() {
|
||||
let mut s = hollow_box_with_join_boss();
|
||||
run(
|
||||
&mut s,
|
||||
"solid.pattern.rectangular",
|
||||
json!({"features": ["Join Boss"], "dir1": [0, -1, 0], "count1": 2, "spacing1": 69, "name": "Pattern Boss"}),
|
||||
);
|
||||
assert!(rel(volume(&mut s), 79500.0) < 1e-6, "patterned copy lost material");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user