forked from 1b-agent/opencadstudio-upstream
style: rustfmt the tree so the CI lint check passes
This commit is contained in:
1 parent
1317a7b4fd
commit
6025d8be44
128 files changed
+4097
-1676
No files matched your search
+121
-47
@@ -80,12 +80,13 @@ pub fn array_of_insert(document: &CadDocument, insert: Handle) -> Option<AssocAr
|
||||
let Some(dependency) = dependency(document, link.dependency) else {
|
||||
continue;
|
||||
};
|
||||
match (dependency.is_read_dependency, dependency.is_write_dependency) {
|
||||
match (
|
||||
dependency.is_read_dependency,
|
||||
dependency.is_write_dependency,
|
||||
) {
|
||||
(true, true) if dependency.dependent_on == insert => owns_insert = true,
|
||||
(false, true) => array_block = dependency.dependent_on,
|
||||
(true, false) if source_block.is_null() => {
|
||||
source_block = dependency.dependent_on
|
||||
}
|
||||
(true, false) if source_block.is_null() => source_block = dependency.dependent_on,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -95,16 +96,14 @@ pub fn array_of_insert(document: &CadDocument, insert: Handle) -> Option<AssocAr
|
||||
"AcDbAssocArrayPathParameters" => ArrayKind::Path,
|
||||
_ => return None,
|
||||
};
|
||||
(owns_insert && !array_block.is_null() && !source_block.is_null()).then_some(
|
||||
AssocArray {
|
||||
insert,
|
||||
action: handle,
|
||||
body: action.action_body,
|
||||
array_block,
|
||||
source_block,
|
||||
kind,
|
||||
},
|
||||
)
|
||||
(owns_insert && !array_block.is_null() && !source_block.is_null()).then_some(AssocArray {
|
||||
insert,
|
||||
action: handle,
|
||||
body: action.action_body,
|
||||
array_block,
|
||||
source_block,
|
||||
kind,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -181,8 +180,7 @@ pub fn column_axis(document: &CadDocument, array: &AssocArray) -> DVec3 {
|
||||
let AssociativeData::EdgeActionParam(edge) = &object.data else {
|
||||
return None;
|
||||
};
|
||||
if edge.single_dependency.action_param.name != "RowProfile" || edge.action_type != 19
|
||||
{
|
||||
if edge.single_dependency.action_param.name != "RowProfile" || edge.action_type != 19 {
|
||||
return None;
|
||||
}
|
||||
match edge.curve.get(1)? {
|
||||
@@ -307,10 +305,17 @@ pub fn polar_items(
|
||||
get("Levels", 1.0).max(1.0) as i32,
|
||||
);
|
||||
// ponytail: Direction 0 read as clockwise; no sample with 0 to confirm.
|
||||
let sign = if get("Direction", 1.0) == 0.0 { -1.0 } else { 1.0 };
|
||||
let sign = if get("Direction", 1.0) == 0.0 {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let align = get("AlignItems", 1.0) != 0.0;
|
||||
let (spacing, start, radius) =
|
||||
(get("ItemSpacing", 0.0), get("StartAngle", 0.0), get("Radius", 0.0));
|
||||
let (spacing, start, radius) = (
|
||||
get("ItemSpacing", 0.0),
|
||||
get("StartAngle", 0.0),
|
||||
get("Radius", 0.0),
|
||||
);
|
||||
let x = plane_x(normal);
|
||||
let y = normal.cross(x);
|
||||
let start_dir = x * start.cos() + y * start.sin();
|
||||
@@ -342,11 +347,21 @@ fn property_rows(kind: ArrayKind) -> &'static [(&'static str, &'static str, &'st
|
||||
match kind {
|
||||
ArrayKind::Rectangular => &[
|
||||
("assoc_array_columns", "Items", "Columns", 0),
|
||||
("assoc_array_column_spacing", "ItemSpacing", "Column spacing", 1),
|
||||
(
|
||||
"assoc_array_column_spacing",
|
||||
"ItemSpacing",
|
||||
"Column spacing",
|
||||
1,
|
||||
),
|
||||
("assoc_array_rows", "Rows", "Rows", 0),
|
||||
("assoc_array_row_spacing", "RowSpacing", "Row spacing", 1),
|
||||
("assoc_array_levels", "Levels", "Levels", 0),
|
||||
("assoc_array_level_spacing", "LevelSpacing", "Level spacing", 1),
|
||||
(
|
||||
"assoc_array_level_spacing",
|
||||
"LevelSpacing",
|
||||
"Level spacing",
|
||||
1,
|
||||
),
|
||||
],
|
||||
ArrayKind::Polar => &[
|
||||
("assoc_array_items", "Items", "Items", 0),
|
||||
@@ -355,7 +370,12 @@ fn property_rows(kind: ArrayKind) -> &'static [(&'static str, &'static str, &'st
|
||||
("assoc_array_rows", "Rows", "Rows", 0),
|
||||
("assoc_array_row_spacing", "RowSpacing", "Row spacing", 1),
|
||||
("assoc_array_levels", "Levels", "Levels", 0),
|
||||
("assoc_array_level_spacing", "LevelSpacing", "Level spacing", 1),
|
||||
(
|
||||
"assoc_array_level_spacing",
|
||||
"LevelSpacing",
|
||||
"Level spacing",
|
||||
1,
|
||||
),
|
||||
],
|
||||
ArrayKind::Path => &[
|
||||
("assoc_array_items", "Items", "Items", 0),
|
||||
@@ -363,7 +383,12 @@ fn property_rows(kind: ArrayKind) -> &'static [(&'static str, &'static str, &'st
|
||||
("assoc_array_rows", "Rows", "Rows", 0),
|
||||
("assoc_array_row_spacing", "RowSpacing", "Row spacing", 1),
|
||||
("assoc_array_levels", "Levels", "Levels", 0),
|
||||
("assoc_array_level_spacing", "LevelSpacing", "Level spacing", 1),
|
||||
(
|
||||
"assoc_array_level_spacing",
|
||||
"LevelSpacing",
|
||||
"Level spacing",
|
||||
1,
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -417,8 +442,7 @@ pub fn apply_field(
|
||||
field: &str,
|
||||
text: &str,
|
||||
) -> Result<(), String> {
|
||||
let Some(&(_, name, _, kind)) =
|
||||
property_rows(array.kind).iter().find(|row| row.0 == field)
|
||||
let Some(&(_, name, _, kind)) = property_rows(array.kind).iter().find(|row| row.0 == field)
|
||||
else {
|
||||
return Err("unknown array field".into());
|
||||
};
|
||||
@@ -460,7 +484,9 @@ pub fn update(
|
||||
set_param_value(&mut params, name, *value);
|
||||
}
|
||||
if array.kind == ArrayKind::Polar
|
||||
&& changes.iter().any(|(name, _)| matches!(*name, "Items" | "FillAngle"))
|
||||
&& changes
|
||||
.iter()
|
||||
.any(|(name, _)| matches!(*name, "Items" | "FillAngle"))
|
||||
{
|
||||
let fill = param_value(¶ms, "FillAngle").unwrap_or(std::f64::consts::TAU);
|
||||
let count = param_value(¶ms, "Items").unwrap_or(1.0).max(1.0);
|
||||
@@ -475,12 +501,10 @@ pub fn update(
|
||||
}
|
||||
let normal = base_normal(document, array);
|
||||
let placements: Vec<([i32; 3], DMat4)> = match array.kind {
|
||||
ArrayKind::Rectangular => {
|
||||
rectangular_items(¶ms, column_axis(document, array), normal)
|
||||
.into_iter()
|
||||
.map(|(location, offset)| (location, DMat4::from_translation(offset)))
|
||||
.collect()
|
||||
}
|
||||
ArrayKind::Rectangular => rectangular_items(¶ms, column_axis(document, array), normal)
|
||||
.into_iter()
|
||||
.map(|(location, offset)| (location, DMat4::from_translation(offset)))
|
||||
.collect(),
|
||||
_ => polar_items(¶ms, center, normal),
|
||||
};
|
||||
let current = body(document, array.body).ok_or("array body missing")?;
|
||||
@@ -547,7 +571,9 @@ pub fn update(
|
||||
.iter_mut()
|
||||
.find(|record| record.handle == array.source_block)
|
||||
{
|
||||
record.insert_handles.retain(|handle| !old_items.contains(handle));
|
||||
record
|
||||
.insert_handles
|
||||
.retain(|handle| !old_items.contains(handle));
|
||||
record.insert_handles.extend(new_handles);
|
||||
}
|
||||
let values_in_action = action(document, array.action).is_some_and(|a| !a.values.is_empty());
|
||||
@@ -581,11 +607,36 @@ const ARRAY_CLASSES: &[(&str, &str, i32, i32)] = &[
|
||||
("ACDBASSOCNETWORK", "AcDbAssocNetwork", 27, 45),
|
||||
("ACDBASSOCACTION", "AcDbAssocAction", 27, 45),
|
||||
("ACDBASSOCDEPENDENCY", "AcDbAssocDependency", 27, 50),
|
||||
("ACDBASSOCVERTEXACTIONPARAM", "AcDbAssocVertexActionParam", 27, 50),
|
||||
("ACDBASSOCEDGEACTIONPARAM", "AcDbAssocEdgeActionParam", 27, 50),
|
||||
("ACDBASSOCARRAYACTIONBODY", "AcDbAssocArrayActionBody", 29, 55),
|
||||
("ACDBASSOCARRAYRECTANGULARPARAMETERS", "AcDbAssocArrayRectangularParameters", 29, 55),
|
||||
("ACDBASSOCARRAYPOLARPARAMETERS", "AcDbAssocArrayPolarParameters", 29, 55),
|
||||
(
|
||||
"ACDBASSOCVERTEXACTIONPARAM",
|
||||
"AcDbAssocVertexActionParam",
|
||||
27,
|
||||
50,
|
||||
),
|
||||
(
|
||||
"ACDBASSOCEDGEACTIONPARAM",
|
||||
"AcDbAssocEdgeActionParam",
|
||||
27,
|
||||
50,
|
||||
),
|
||||
(
|
||||
"ACDBASSOCARRAYACTIONBODY",
|
||||
"AcDbAssocArrayActionBody",
|
||||
29,
|
||||
55,
|
||||
),
|
||||
(
|
||||
"ACDBASSOCARRAYRECTANGULARPARAMETERS",
|
||||
"AcDbAssocArrayRectangularParameters",
|
||||
29,
|
||||
55,
|
||||
),
|
||||
(
|
||||
"ACDBASSOCARRAYPOLARPARAMETERS",
|
||||
"AcDbAssocArrayPolarParameters",
|
||||
29,
|
||||
55,
|
||||
),
|
||||
];
|
||||
|
||||
fn register_array_classes(document: &mut CadDocument) {
|
||||
@@ -644,9 +695,12 @@ fn dictionary_entry(document: &CadDocument, dictionary: Handle, key: &str) -> Op
|
||||
|
||||
/// A dictionary stored under `key` in `parent`, created when absent.
|
||||
fn child_dictionary(document: &mut CadDocument, parent: Handle, key: &str) -> Handle {
|
||||
if let Some(handle) = dictionary_entry(document, parent, key)
|
||||
.filter(|handle| matches!(document.objects.get(handle), Some(ObjectType::Dictionary(_))))
|
||||
{
|
||||
if let Some(handle) = dictionary_entry(document, parent, key).filter(|handle| {
|
||||
matches!(
|
||||
document.objects.get(handle),
|
||||
Some(ObjectType::Dictionary(_))
|
||||
)
|
||||
}) {
|
||||
return handle;
|
||||
}
|
||||
let handle = document.allocate_handle();
|
||||
@@ -654,7 +708,9 @@ fn child_dictionary(document: &mut CadDocument, parent: Handle, key: &str) -> Ha
|
||||
dictionary.handle = handle;
|
||||
dictionary.owner = parent;
|
||||
dictionary.reactors.push(parent);
|
||||
document.objects.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
if let Some(ObjectType::Dictionary(parent)) = document.objects.get_mut(&parent) {
|
||||
parent.add_entry(key, handle);
|
||||
}
|
||||
@@ -679,7 +735,12 @@ fn model_space_network(document: &mut CadDocument) -> Handle {
|
||||
}
|
||||
let root_holder = child_dictionary(document, document.header.named_objects_dict_handle, KEY);
|
||||
let root = match dictionary_entry(document, root_holder, KEY) {
|
||||
Some(root) if matches!(document.objects.get(&root), Some(ObjectType::Associative(_))) => {
|
||||
Some(root)
|
||||
if matches!(
|
||||
document.objects.get(&root),
|
||||
Some(ObjectType::Associative(_))
|
||||
) =>
|
||||
{
|
||||
root
|
||||
}
|
||||
_ => {
|
||||
@@ -896,7 +957,10 @@ pub fn create(
|
||||
scene.define_block_raw(
|
||||
&array_name,
|
||||
codec::types::Vector3::ZERO,
|
||||
placements.iter().map(|(_, placement)| item(*placement)).collect(),
|
||||
placements
|
||||
.iter()
|
||||
.map(|(_, placement)| item(*placement))
|
||||
.collect(),
|
||||
);
|
||||
let record = |document: &CadDocument, name: &str| {
|
||||
document
|
||||
@@ -930,8 +994,11 @@ pub fn create(
|
||||
let [action, body, write_dep, read_dep, both_dep] =
|
||||
std::array::from_fn(|_| document.allocate_handle());
|
||||
let mut owned_parameters = Vec::new();
|
||||
let vertex_params =
|
||||
[("BaseNormal", normal), ("Origin", DVec3::ZERO), ("basePoint", DVec3::ZERO)];
|
||||
let vertex_params = [
|
||||
("BaseNormal", normal),
|
||||
("Origin", DVec3::ZERO),
|
||||
("basePoint", DVec3::ZERO),
|
||||
];
|
||||
for (name, point) in vertex_params {
|
||||
let handle = document.allocate_handle();
|
||||
document.objects.insert(
|
||||
@@ -1109,7 +1176,14 @@ pub fn create_from_spec(
|
||||
offset.truncate().length(),
|
||||
offset.y.atan2(offset.x),
|
||||
);
|
||||
create(scene, sources, source_point, center, ArrayKind::Polar, params)
|
||||
create(
|
||||
scene,
|
||||
sources,
|
||||
source_point,
|
||||
center,
|
||||
ArrayKind::Polar,
|
||||
params,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,11 +35,23 @@ const NO_VALUE: i16 = -9999;
|
||||
/// (DXF name, class, DWG maintenance version) of the classes used here.
|
||||
const CLASSES: &[(&str, &str, i32)] = &[
|
||||
("ACAD_EVALUATION_GRAPH", "AcDbEvalGraph", 55),
|
||||
("BLOCKVISIBILITYPARAMETER", "AcDbBlockVisibilityParameter", 55),
|
||||
(
|
||||
"BLOCKVISIBILITYPARAMETER",
|
||||
"AcDbBlockVisibilityParameter",
|
||||
55,
|
||||
),
|
||||
("BLOCKVISIBILITYGRIP", "AcDbBlockVisibilityGrip", 55),
|
||||
("BLOCKGRIPLOCATIONCOMPONENT", "AcDbBlockGripExpr", 20),
|
||||
("ACDB_DYNAMICBLOCKPURGEPREVENTER_VERSION", "AcDbDynamicBlockPurgePreventer", 61),
|
||||
("ACDB_BLOCKREPRESENTATION_DATA", "AcDbBlockRepresentationData", 58),
|
||||
(
|
||||
"ACDB_DYNAMICBLOCKPURGEPREVENTER_VERSION",
|
||||
"AcDbDynamicBlockPurgePreventer",
|
||||
61,
|
||||
),
|
||||
(
|
||||
"ACDB_BLOCKREPRESENTATION_DATA",
|
||||
"AcDbBlockRepresentationData",
|
||||
58,
|
||||
),
|
||||
];
|
||||
|
||||
/// Register the classes with the flags and versions the reference writes
|
||||
@@ -240,8 +252,7 @@ pub fn create(document: &mut CadDocument, definition: Handle, location: Vector3)
|
||||
ensure_app_id(document, app);
|
||||
}
|
||||
let dictionary = document.ensure_extension_dictionary(definition);
|
||||
let [graph, parameter, grip, x, y, preventer] =
|
||||
[(); 6].map(|_| document.allocate_handle());
|
||||
let [graph, parameter, grip, x, y, preventer] = [(); 6].map(|_| document.allocate_handle());
|
||||
|
||||
store(
|
||||
document,
|
||||
@@ -326,16 +337,15 @@ pub fn create(document: &mut CadDocument, definition: Handle, location: Vector3)
|
||||
node_data,
|
||||
active_cycles: None,
|
||||
};
|
||||
let edge = |id: i32, source_node: i32, destination_node: i32, outgoing_edges| {
|
||||
BlockEvaluationEdge {
|
||||
let edge =
|
||||
|id: i32, source_node: i32, destination_node: i32, outgoing_edges| BlockEvaluationEdge {
|
||||
id,
|
||||
next_id: 0,
|
||||
incoming_edge: 1,
|
||||
source_node,
|
||||
destination_node,
|
||||
outgoing_edges,
|
||||
}
|
||||
};
|
||||
};
|
||||
let mut graph_object = dynamic(
|
||||
graph,
|
||||
dictionary,
|
||||
@@ -393,9 +403,11 @@ pub fn create(document: &mut CadDocument, definition: Handle, location: Vector3)
|
||||
/// The names of the states of `parameter`, in list order.
|
||||
pub fn state_names(document: &CadDocument, parameter: Handle) -> Vec<String> {
|
||||
match document.objects.get(¶meter) {
|
||||
Some(ObjectType::BlockVisibilityParameter(parameter)) => {
|
||||
parameter.states.iter().map(|state| state.name.clone()).collect()
|
||||
}
|
||||
Some(ObjectType::BlockVisibilityParameter(parameter)) => parameter
|
||||
.states
|
||||
.iter()
|
||||
.map(|state| state.name.clone())
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -597,7 +609,11 @@ impl Scene {
|
||||
let Some(EntityType::Insert(reference)) = self.document.get_entity(insert) else {
|
||||
return None;
|
||||
};
|
||||
let definition = self.document.block_records.get(&reference.block_name)?.handle;
|
||||
let definition = self
|
||||
.document
|
||||
.block_records
|
||||
.get(&reference.block_name)?
|
||||
.handle;
|
||||
parameter(&self.document, definition)?;
|
||||
let name = {
|
||||
let next = self
|
||||
@@ -614,10 +630,12 @@ impl Scene {
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|record| record.handle == definition)
|
||||
.and_then(|record| match self.document.get_entity(record.block_entity_handle) {
|
||||
Some(EntityType::Block(block)) => Some(block.base_point),
|
||||
_ => None,
|
||||
})
|
||||
.and_then(
|
||||
|record| match self.document.get_entity(record.block_entity_handle) {
|
||||
Some(EntityType::Block(block)) => Some(block.base_point),
|
||||
_ => None,
|
||||
},
|
||||
)
|
||||
.unwrap_or(Vector3::ZERO);
|
||||
let originals = members(&self.document, definition);
|
||||
let copies: Vec<EntityType> = originals
|
||||
@@ -701,7 +719,10 @@ mod tests {
|
||||
Vector3::ZERO,
|
||||
vec![
|
||||
EntityType::Line(Line::from_coords(0.0, 0.0, 0.0, 1.0, 0.0, 0.0)),
|
||||
EntityType::Circle(Circle::from_center_radius(Vector3::new(0.5, 0.5, 0.0), 0.25)),
|
||||
EntityType::Circle(Circle::from_center_radius(
|
||||
Vector3::new(0.5, 0.5, 0.0),
|
||||
0.25,
|
||||
)),
|
||||
],
|
||||
);
|
||||
let definition = scene.document.block_records.get("door").unwrap().handle;
|
||||
@@ -732,7 +753,11 @@ mod tests {
|
||||
let (found, parameter) = read
|
||||
.dynamic_visibility_for_insert(insert)
|
||||
.unwrap_or_else(|| panic!("{ext}: no visibility parameter"));
|
||||
let record = read.block_records.iter().find(|r| r.handle == found).unwrap();
|
||||
let record = read
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|r| r.handle == found)
|
||||
.unwrap();
|
||||
assert_eq!(record.name, "door", "{ext}");
|
||||
let names: Vec<&str> = parameter.states.iter().map(|s| s.name.as_str()).collect();
|
||||
assert_eq!(names, ["VisibilityState0", "Open"], "{ext}");
|
||||
|
||||
Vendored
+32
-32
@@ -1072,29 +1072,27 @@ pub fn expand_insert(
|
||||
LocalSub::Wire(wire) => wire.canvas_color,
|
||||
LocalSub::Nested(_) => false,
|
||||
});
|
||||
let prototype_key = if !ins.is_array()
|
||||
&& !has_masks
|
||||
&& cache.prototype_blocks.contains(&ins.block_name)
|
||||
{
|
||||
Some(expansion_prototype_key(
|
||||
ins,
|
||||
&xform,
|
||||
ins_resolved_color,
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
ins_layer,
|
||||
ins_layer_plottable,
|
||||
selected,
|
||||
pslt_factor,
|
||||
is_xref,
|
||||
bg_color,
|
||||
anno_scale,
|
||||
suppress_root_points,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let prototype_key =
|
||||
if !ins.is_array() && !has_masks && cache.prototype_blocks.contains(&ins.block_name) {
|
||||
Some(expansion_prototype_key(
|
||||
ins,
|
||||
&xform,
|
||||
ins_resolved_color,
|
||||
ins_pat_len,
|
||||
ins_pat,
|
||||
ins_lw_px,
|
||||
ins_layer,
|
||||
ins_layer_plottable,
|
||||
selected,
|
||||
pslt_factor,
|
||||
is_xref,
|
||||
bg_color,
|
||||
anno_scale,
|
||||
suppress_root_points,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let prototype_slot = prototype_key.as_ref().map(|key| {
|
||||
let mut prototypes = cache
|
||||
.expansion_prototypes
|
||||
@@ -1348,15 +1346,17 @@ pub(crate) fn translated_prototype_wire(
|
||||
let mut wire = source.clone();
|
||||
wire.name = name.to_string();
|
||||
if let Some(stub) = &source.stub {
|
||||
wire.stub = Some(Arc::new(crate::scene::model::instance_model::InstanceStub {
|
||||
prototype: Arc::clone(&stub.prototype),
|
||||
delta: [
|
||||
stub.delta[0] + delta[0],
|
||||
stub.delta[1] + delta[1],
|
||||
stub.delta[2] + delta[2],
|
||||
],
|
||||
depth_delta: stub.depth_delta,
|
||||
}));
|
||||
wire.stub = Some(Arc::new(
|
||||
crate::scene::model::instance_model::InstanceStub {
|
||||
prototype: Arc::clone(&stub.prototype),
|
||||
delta: [
|
||||
stub.delta[0] + delta[0],
|
||||
stub.delta[1] + delta[1],
|
||||
stub.delta[2] + delta[2],
|
||||
],
|
||||
depth_delta: stub.depth_delta,
|
||||
},
|
||||
));
|
||||
}
|
||||
if let Some(instance) = wire.render_instance.as_mut() {
|
||||
for axis in 0..3 {
|
||||
|
||||
+95
-28
@@ -222,7 +222,9 @@ impl Scene {
|
||||
let unchanged = {
|
||||
let cam = self.camera.borrow();
|
||||
cam.rotation.abs_diff_eq(before.0, 1e-6)
|
||||
&& cam.target.abs_diff_eq(before.1, 1e-6 * before.1.length().max(1.0))
|
||||
&& cam
|
||||
.target
|
||||
.abs_diff_eq(before.1, 1e-6 * before.1.length().max(1.0))
|
||||
&& (cam.distance - before.2).abs() <= 1e-6 * before.2.abs().max(1.0)
|
||||
};
|
||||
// The model viewport takes a copy of the view's background; a view
|
||||
@@ -238,7 +240,9 @@ impl Scene {
|
||||
}
|
||||
if view.visual_style_handle.is_valid() {
|
||||
if let Some(handle) = self.active_tile_vport_handle() {
|
||||
if let Some(value) = self.document.vports.iter_mut().find(|v| v.handle == handle) {
|
||||
if let Some(value) =
|
||||
self.document.vports.iter_mut().find(|v| v.handle == handle)
|
||||
{
|
||||
value.visual_style_handle = view.visual_style_handle;
|
||||
}
|
||||
}
|
||||
@@ -277,7 +281,9 @@ impl Scene {
|
||||
let sheet = self.current_layout_sheet_viewport_handle();
|
||||
let background = copy_background(&mut self.document, view.background_handle);
|
||||
let old = match self.document.get_entity_mut(sheet) {
|
||||
Some(EntityType::Viewport(value)) => std::mem::replace(&mut value.background_handle, background),
|
||||
Some(EntityType::Viewport(value)) => {
|
||||
std::mem::replace(&mut value.background_handle, background)
|
||||
}
|
||||
_ => background,
|
||||
};
|
||||
erase_background(&mut self.document, old);
|
||||
@@ -324,36 +330,58 @@ impl Scene {
|
||||
if self.current_layout != "Model" {
|
||||
// Inside a viewport, that viewport's; the sheet's is not saved
|
||||
// with a paper-space view.
|
||||
return match self.active_viewport.and_then(|handle| self.document.get_entity(handle)) {
|
||||
return match self
|
||||
.active_viewport
|
||||
.and_then(|handle| self.document.get_entity(handle))
|
||||
{
|
||||
Some(EntityType::Viewport(value)) => value.background_handle,
|
||||
_ => Handle::NULL,
|
||||
};
|
||||
}
|
||||
self.active_tile_vport_handle()
|
||||
.and_then(|handle| self.document.vports.iter().find(|value| value.handle == handle))
|
||||
.and_then(|handle| {
|
||||
self.document
|
||||
.vports
|
||||
.iter()
|
||||
.find(|value| value.handle == handle)
|
||||
})
|
||||
.map_or(Handle::NULL, |value| value.background_handle)
|
||||
}
|
||||
|
||||
/// The visual style of the active Model tile's viewport record.
|
||||
pub fn active_visual_style(&self) -> Handle {
|
||||
if self.current_layout != "Model" {
|
||||
return match self.active_viewport.and_then(|handle| self.document.get_entity(handle)) {
|
||||
return match self
|
||||
.active_viewport
|
||||
.and_then(|handle| self.document.get_entity(handle))
|
||||
{
|
||||
Some(EntityType::Viewport(value)) => value.visual_style_handle,
|
||||
_ => Handle::NULL,
|
||||
};
|
||||
}
|
||||
self.active_tile_vport_handle()
|
||||
.and_then(|handle| self.document.vports.iter().find(|value| value.handle == handle))
|
||||
.and_then(|handle| {
|
||||
self.document
|
||||
.vports
|
||||
.iter()
|
||||
.find(|value| value.handle == handle)
|
||||
})
|
||||
.map_or(Handle::NULL, |value| value.visual_style_handle)
|
||||
}
|
||||
|
||||
/// Records the visual style named `name` on the active Model tile's
|
||||
/// viewport record, when the drawing has it.
|
||||
pub fn set_active_visual_style_named(&mut self, name: &str) {
|
||||
let style = self.document.objects.iter().find_map(|(handle, object)| match object {
|
||||
codec::objects::ObjectType::VisualStyle(style) if style.description == name => Some(*handle),
|
||||
_ => None,
|
||||
});
|
||||
let style = self
|
||||
.document
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|(handle, object)| match object {
|
||||
codec::objects::ObjectType::VisualStyle(style) if style.description == name => {
|
||||
Some(*handle)
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let Some(style) = style else { return };
|
||||
if self.current_layout != "Model" {
|
||||
if let Some(handle) = self.active_viewport {
|
||||
@@ -363,15 +391,29 @@ impl Scene {
|
||||
}
|
||||
return;
|
||||
}
|
||||
let Some(handle) = self.active_tile_vport_handle() else { return };
|
||||
if let Some(value) = self.document.vports.iter_mut().find(|value| value.handle == handle) {
|
||||
let Some(handle) = self.active_tile_vport_handle() else {
|
||||
return;
|
||||
};
|
||||
if let Some(value) = self
|
||||
.document
|
||||
.vports
|
||||
.iter_mut()
|
||||
.find(|value| value.handle == handle)
|
||||
{
|
||||
value.visual_style_handle = style;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_active_background(&mut self, background: Handle) {
|
||||
let Some(handle) = self.active_tile_vport_handle() else { return };
|
||||
let Some(value) = self.document.vports.iter_mut().find(|value| value.handle == handle) else {
|
||||
let Some(handle) = self.active_tile_vport_handle() else {
|
||||
return;
|
||||
};
|
||||
let Some(value) = self
|
||||
.document
|
||||
.vports
|
||||
.iter_mut()
|
||||
.find(|value| value.handle == handle)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let old = std::mem::replace(&mut value.background_handle, background);
|
||||
@@ -1075,13 +1117,21 @@ impl Scene {
|
||||
|
||||
/// Index of the current model pane's record, independent of record ordering.
|
||||
fn ucs_follow_vport_index(&self) -> Option<usize> {
|
||||
let rect = self.model_tiles.borrow().get(self.active_model_tile.get()).map(|t| t.rect);
|
||||
let rect = self
|
||||
.model_tiles
|
||||
.borrow()
|
||||
.get(self.active_model_tile.get())
|
||||
.map(|t| t.rect);
|
||||
let bounds = rect.map(Self::tile_rect_to_vport);
|
||||
self.document.vports.iter().enumerate().find_map(|(index, entry)| {
|
||||
(is_active_vport_name(&entry.name)
|
||||
&& bounds.map_or(true, |(ll, ur)| Self::vport_rect_matches(entry, ll, ur)))
|
||||
self.document
|
||||
.vports
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(index, entry)| {
|
||||
(is_active_vport_name(&entry.name)
|
||||
&& bounds.map_or(true, |(ll, ur)| Self::vport_rect_matches(entry, ll, ur)))
|
||||
.then_some(index)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Read the persisted per-viewport UCSFOLLOW bit. Paper space itself never follows.
|
||||
@@ -1108,7 +1158,9 @@ impl Scene {
|
||||
// matching their rectangles, even when the active pane is reordered.
|
||||
self.save_model_tiles_to_vports();
|
||||
if self.ucs_follow_vport_index().is_none() {
|
||||
self.document.vports.add_or_replace(codec::tables::VPort::new("*Active"));
|
||||
self.document
|
||||
.vports
|
||||
.add_or_replace(codec::tables::VPort::new("*Active"));
|
||||
}
|
||||
if let Some(index) = self.ucs_follow_vport_index() {
|
||||
if let Some(vp) = self.document.vports.iter_mut().nth(index) {
|
||||
@@ -1841,7 +1893,10 @@ fn background_dictionary(document: &mut codec::CadDocument) -> Option<Handle> {
|
||||
return None;
|
||||
};
|
||||
if let Some(existing) = root_dictionary.get("ACAD_BACKGROUND") {
|
||||
if matches!(document.objects.get(&existing), Some(ObjectType::Dictionary(_))) {
|
||||
if matches!(
|
||||
document.objects.get(&existing),
|
||||
Some(ObjectType::Dictionary(_))
|
||||
) {
|
||||
return Some(existing);
|
||||
}
|
||||
}
|
||||
@@ -1850,7 +1905,9 @@ fn background_dictionary(document: &mut codec::CadDocument) -> Option<Handle> {
|
||||
dictionary.handle = handle;
|
||||
dictionary.owner = root;
|
||||
dictionary.reactors.push(root);
|
||||
document.objects.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::Dictionary(dictionary));
|
||||
if let Some(ObjectType::Dictionary(root_dictionary)) = document.objects.get_mut(&root) {
|
||||
root_dictionary.add_entry("ACAD_BACKGROUND", handle);
|
||||
}
|
||||
@@ -1872,7 +1929,9 @@ pub fn add_background(
|
||||
object.handle = handle;
|
||||
object.owner = dictionary;
|
||||
object.reactors = vec![dictionary];
|
||||
document.objects.insert(handle, ObjectType::ClassObject(object));
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::ClassObject(object));
|
||||
if let Some(ObjectType::Dictionary(entries)) = document.objects.get_mut(&dictionary) {
|
||||
entries.add_entry(format!("{:X}", handle.value()), handle);
|
||||
}
|
||||
@@ -1897,8 +1956,12 @@ pub fn erase_background(document: &mut codec::CadDocument, handle: Handle) {
|
||||
Some(ObjectType::ClassObject(object))
|
||||
if matches!(
|
||||
object.data,
|
||||
D::SolidBackground(_) | D::GradientBackground(_) | D::ImageBackground(_)
|
||||
| D::GroundPlaneBackground(_) | D::SkyLightBackground(_) | D::IblBackground(_)
|
||||
D::SolidBackground(_)
|
||||
| D::GradientBackground(_)
|
||||
| D::ImageBackground(_)
|
||||
| D::GroundPlaneBackground(_)
|
||||
| D::SkyLightBackground(_)
|
||||
| D::IblBackground(_)
|
||||
) =>
|
||||
{
|
||||
object.owner
|
||||
@@ -1919,8 +1982,12 @@ pub fn copy_background(document: &mut codec::CadDocument, handle: Handle) -> Han
|
||||
Some(ObjectType::ClassObject(object))
|
||||
if matches!(
|
||||
object.data,
|
||||
D::SolidBackground(_) | D::GradientBackground(_) | D::ImageBackground(_)
|
||||
| D::GroundPlaneBackground(_) | D::SkyLightBackground(_) | D::IblBackground(_)
|
||||
D::SolidBackground(_)
|
||||
| D::GradientBackground(_)
|
||||
| D::ImageBackground(_)
|
||||
| D::GroundPlaneBackground(_)
|
||||
| D::SkyLightBackground(_)
|
||||
| D::IblBackground(_)
|
||||
) =>
|
||||
{
|
||||
let data = object.data.clone();
|
||||
|
||||
@@ -140,7 +140,11 @@ pub fn compare(current: &Scene, other: CadDocument) -> Comparison {
|
||||
b.1.y
|
||||
.partial_cmp(&a.1.y)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
.then(a.0.x.partial_cmp(&b.0.x).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.then(
|
||||
a.0.x
|
||||
.partial_cmp(&b.0.x)
|
||||
.unwrap_or(std::cmp::Ordering::Equal),
|
||||
)
|
||||
});
|
||||
Comparison {
|
||||
added,
|
||||
|
||||
@@ -3213,10 +3213,9 @@ fn polyline_band_width(entity: &EntityType, fill_mode: bool) -> f32 {
|
||||
/// The widest start / end width of a 2-D polyline (0 for other entities).
|
||||
fn polyline_max_width(entity: &EntityType) -> f64 {
|
||||
match entity {
|
||||
EntityType::LwPolyline(p) => p
|
||||
.vertices
|
||||
.iter()
|
||||
.fold(p.constant_width, |w, v| w.max(v.start_width).max(v.end_width)),
|
||||
EntityType::LwPolyline(p) => p.vertices.iter().fold(p.constant_width, |w, v| {
|
||||
w.max(v.start_width).max(v.end_width)
|
||||
}),
|
||||
EntityType::Polyline2D(p) => p
|
||||
.vertices
|
||||
.iter()
|
||||
|
||||
+406
-118
@@ -2494,9 +2494,7 @@ fn detach_preserved_scope(
|
||||
AssociativeData::Action(_) => {
|
||||
action_surface(document, *handle)
|
||||
.is_none_or(|surface| document.get_entity(surface).is_some())
|
||||
&& !crate::scene::assoc_array::orphaned_array_action(
|
||||
document, *handle,
|
||||
)
|
||||
&& !crate::scene::assoc_array::orphaned_array_action(document, *handle)
|
||||
}
|
||||
_ => true,
|
||||
},
|
||||
@@ -2668,9 +2666,13 @@ fn pending_swept_surfaces(document: &CadDocument) -> Vec<(Handle, Handle, Handle
|
||||
let (profile, path) = sweep_model::sweep_sources(entity)?;
|
||||
let surface = entity.common().handle;
|
||||
(!carried.contains(&surface)
|
||||
&& document.get_entity(profile).is_some_and(|e| sweep_model::assoc_source_supported(e))
|
||||
&& document.get_entity(path).is_some_and(|e| sweep_model::assoc_source_supported(e)))
|
||||
.then_some((surface, profile, path))
|
||||
&& document
|
||||
.get_entity(profile)
|
||||
.is_some_and(|e| sweep_model::assoc_source_supported(e))
|
||||
&& document
|
||||
.get_entity(path)
|
||||
.is_some_and(|e| sweep_model::assoc_source_supported(e)))
|
||||
.then_some((surface, profile, path))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
pending.sort_by_key(|(surface, _, _)| surface.value());
|
||||
@@ -2696,7 +2698,9 @@ fn pending_source_surfaces(
|
||||
let surface = entity.common().handle;
|
||||
(!carried.contains(&surface)
|
||||
&& sources.handles().iter().all(|handle| {
|
||||
document.get_entity(*handle).is_some_and(|e| sweep_model::assoc_source_supported(e))
|
||||
document
|
||||
.get_entity(*handle)
|
||||
.is_some_and(|e| sweep_model::assoc_source_supported(e))
|
||||
}))
|
||||
.then_some((surface, sources))
|
||||
})
|
||||
@@ -2733,11 +2737,20 @@ fn read_dependency(dependent_on: Handle, id: i32) -> AssocDependency {
|
||||
}
|
||||
|
||||
/// An edge action parameter reading `curve` through dependency `read`.
|
||||
fn edge_action_param(read: Handle, (action_type, subcurve_kind, curve): crate::scene::model::sweep_model::AssocCurve) -> AssociativeData {
|
||||
use codec::objects::{AssocActionParam, AssocEdgeActionParam, AssocSingleDependencyActionParam};
|
||||
fn edge_action_param(
|
||||
read: Handle,
|
||||
(action_type, subcurve_kind, curve): crate::scene::model::sweep_model::AssocCurve,
|
||||
) -> AssociativeData {
|
||||
use codec::objects::{
|
||||
AssocActionParam, AssocEdgeActionParam, AssocSingleDependencyActionParam,
|
||||
};
|
||||
AssociativeData::EdgeActionParam(AssocEdgeActionParam {
|
||||
single_dependency: AssocSingleDependencyActionParam {
|
||||
action_param: AssocActionParam { is_r2013: 1, version: 0, name: String::new() },
|
||||
action_param: AssocActionParam {
|
||||
is_r2013: 1,
|
||||
version: 0,
|
||||
name: String::new(),
|
||||
},
|
||||
dependency_class_version: 0,
|
||||
dependency: read,
|
||||
class_version: 0,
|
||||
@@ -2761,15 +2774,61 @@ fn region_subent_entries(loops: &[usize], step: i32, first_id: i32) -> Vec<i32>
|
||||
let (mut id, mut offset) = (first_id, 0usize);
|
||||
for &count in loops {
|
||||
let edge = |index: usize| 51001 + 100_000 * (offset + index % count) as i32;
|
||||
let vertex = |a: i32, b: i32| [1, 3, 1, BODY, FACE, 1, BODY, a, 1, BODY, b, 0, 0, 0, 900, 0, 900, 0, 0];
|
||||
let vertex = |a: i32, b: i32| {
|
||||
[
|
||||
1, 3, 1, BODY, FACE, 1, BODY, a, 1, BODY, b, 0, 0, 0, 900, 0, 900, 0, 0,
|
||||
]
|
||||
};
|
||||
for read in 0..count {
|
||||
if count == 1 {
|
||||
values.extend([4, 0, 5, id, step, step, 0, 0, 24, 0, 0, 1, BODY, edge(0), 0, 0, 1, 1, BODY, FACE]);
|
||||
values.extend([
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
id,
|
||||
step,
|
||||
step,
|
||||
0,
|
||||
0,
|
||||
24,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
BODY,
|
||||
edge(0),
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
BODY,
|
||||
FACE,
|
||||
]);
|
||||
values.extend([0; 14]);
|
||||
} else {
|
||||
// The dependencies start at the loop's second edge.
|
||||
let index = read + 1;
|
||||
values.extend([4, 0, 11, id, step, step, 0, 2, 2, 2, 0, 2, 2, 2, 54, 0, 0, 1, BODY, edge(index)]);
|
||||
values.extend([
|
||||
4,
|
||||
0,
|
||||
11,
|
||||
id,
|
||||
step,
|
||||
step,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
54,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
BODY,
|
||||
edge(index),
|
||||
]);
|
||||
values.extend([0, 0, 1, 1, BODY, FACE, 0, 0, 0, 0]);
|
||||
values.extend(vertex(edge(index), edge(index + count - 1)));
|
||||
values.extend([0, 0]);
|
||||
@@ -2796,9 +2855,10 @@ fn register_region_subents(document: &mut CadDocument, loops: &[usize]) -> Optio
|
||||
_ => return None,
|
||||
};
|
||||
let current = existing.and_then(|handle| match document.objects.get(&handle) {
|
||||
Some(ObjectType::Associative(AssociativeObject { data: AssociativeData::PersSubentManager(manager), .. })) => {
|
||||
Some((handle, manager.values.clone()))
|
||||
}
|
||||
Some(ObjectType::Associative(AssociativeObject {
|
||||
data: AssociativeData::PersSubentManager(manager),
|
||||
..
|
||||
})) => Some((handle, manager.values.clone())),
|
||||
_ => None,
|
||||
});
|
||||
let (handle, last, count, mut entries) = match current {
|
||||
@@ -2811,7 +2871,12 @@ fn register_region_subents(document: &mut CadDocument, loops: &[usize]) -> Optio
|
||||
if values.len() < tail + 2 || values[values.len() - tail] != last {
|
||||
return None;
|
||||
}
|
||||
(handle, last, values[1], values[2..values.len() - tail].to_vec())
|
||||
(
|
||||
handle,
|
||||
last,
|
||||
values[1],
|
||||
values[2..values.len() - tail].to_vec(),
|
||||
)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@@ -2829,20 +2894,23 @@ fn register_region_subents(document: &mut CadDocument, loops: &[usize]) -> Optio
|
||||
values.push(last);
|
||||
values.extend(1..=last);
|
||||
values.extend([0, 0]);
|
||||
document.objects.insert(handle, ObjectType::Associative(AssociativeObject {
|
||||
document.objects.insert(
|
||||
handle,
|
||||
owner: root,
|
||||
reactors: vec![root],
|
||||
dxf_name: "ACDBASSOCPERSSUBENTMANAGER".to_string(),
|
||||
cpp_class_name: "AcDbAssocPersSubentManager".to_string(),
|
||||
data: AssociativeData::PersSubentManager(AssocPersSubentManager {
|
||||
class_version: 2,
|
||||
markers: [3, 0, 2],
|
||||
values,
|
||||
final_flag: false,
|
||||
ObjectType::Associative(AssociativeObject {
|
||||
handle,
|
||||
owner: root,
|
||||
reactors: vec![root],
|
||||
dxf_name: "ACDBASSOCPERSSUBENTMANAGER".to_string(),
|
||||
cpp_class_name: "AcDbAssocPersSubentManager".to_string(),
|
||||
data: AssociativeData::PersSubentManager(AssocPersSubentManager {
|
||||
class_version: 2,
|
||||
markers: [3, 0, 2],
|
||||
values,
|
||||
final_flag: false,
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}));
|
||||
);
|
||||
Some((first..first + edges).collect())
|
||||
}
|
||||
|
||||
@@ -2864,9 +2932,20 @@ fn source_reads(
|
||||
let Some(loops) = sweep_model::region_edge_curves(entity) else {
|
||||
let curve = sweep_model::assoc_edge_curve(entity)?;
|
||||
let (edge, read) = (document.allocate_handle(), document.allocate_handle());
|
||||
objects.push((edge, parameter, "ACDBASSOCEDGEACTIONPARAM", "AcDbAssocEdgeActionParam", edge_action_param(read, curve)));
|
||||
objects.push((read, action, "ACDBASSOCDEPENDENCY", "AcDbAssocDependency",
|
||||
AssociativeData::Dependency(read_dependency(source, first_id))));
|
||||
objects.push((
|
||||
edge,
|
||||
parameter,
|
||||
"ACDBASSOCEDGEACTIONPARAM",
|
||||
"AcDbAssocEdgeActionParam",
|
||||
edge_action_param(read, curve),
|
||||
));
|
||||
objects.push((
|
||||
read,
|
||||
action,
|
||||
"ACDBASSOCDEPENDENCY",
|
||||
"AcDbAssocDependency",
|
||||
AssociativeData::Dependency(read_dependency(source, first_id)),
|
||||
));
|
||||
return Some(vec![(edge, read)]);
|
||||
};
|
||||
let sizes = loops.iter().map(Vec::len).collect::<Vec<_>>();
|
||||
@@ -2874,16 +2953,36 @@ fn source_reads(
|
||||
// The region carries the step its edge names belong to; without it (and
|
||||
// without named edges in its modeler data) the reference cannot find
|
||||
// the edges again.
|
||||
crate::scene::view::dispatch::set_entity_xdata(document, source, "ACAD_STEPID",
|
||||
Some(vec![codec::xdata::XDataValue::Integer32(ids.first()? - 1)]));
|
||||
crate::scene::view::dispatch::set_entity_xdata(
|
||||
document,
|
||||
source,
|
||||
"ACAD_STEPID",
|
||||
Some(vec![codec::xdata::XDataValue::Integer32(ids.first()? - 1)]),
|
||||
);
|
||||
let curves = loops.into_iter().flatten().collect::<Vec<_>>();
|
||||
let pairs = curves.iter().map(|_| (document.allocate_handle(), document.allocate_handle())).collect::<Vec<_>>();
|
||||
for (index, (curve, (edge, read))) in curves.into_iter().zip(pairs.iter().copied()).enumerate() {
|
||||
let pairs = curves
|
||||
.iter()
|
||||
.map(|_| (document.allocate_handle(), document.allocate_handle()))
|
||||
.collect::<Vec<_>>();
|
||||
for (index, (curve, (edge, read))) in curves.into_iter().zip(pairs.iter().copied()).enumerate()
|
||||
{
|
||||
let mut dependency = read_dependency(source, first_id + index as i32);
|
||||
dependency.read_dependency = index.checked_sub(1).map_or(Handle::NULL, |previous| pairs[previous].1);
|
||||
dependency.read_dependency = index
|
||||
.checked_sub(1)
|
||||
.map_or(Handle::NULL, |previous| pairs[previous].1);
|
||||
dependency.node = pairs.get(index + 1).map_or(Handle::NULL, |next| next.1);
|
||||
objects.push((edge, parameter, "ACDBASSOCEDGEACTIONPARAM", "AcDbAssocEdgeActionParam", edge_action_param(read, curve)));
|
||||
objects.push((read, action, "ACDBASSOCGEOMDEPENDENCY", "AcDbAssocGeomDependency",
|
||||
objects.push((
|
||||
edge,
|
||||
parameter,
|
||||
"ACDBASSOCEDGEACTIONPARAM",
|
||||
"AcDbAssocEdgeActionParam",
|
||||
edge_action_param(read, curve),
|
||||
));
|
||||
objects.push((
|
||||
read,
|
||||
action,
|
||||
"ACDBASSOCGEOMDEPENDENCY",
|
||||
"AcDbAssocGeomDependency",
|
||||
AssociativeData::GeomDependency(AssocGeomDependency {
|
||||
dependency,
|
||||
class_version: 0,
|
||||
@@ -2895,7 +2994,8 @@ fn source_reads(
|
||||
values: vec![0, 2, ids[index]],
|
||||
leading_flag: false,
|
||||
},
|
||||
})));
|
||||
}),
|
||||
));
|
||||
}
|
||||
Some(pairs)
|
||||
}
|
||||
@@ -2920,8 +3020,14 @@ fn materialize_source_surface_actions(
|
||||
let document = &*allocator.document;
|
||||
// The value parameters: name, unit type, values, and whether they
|
||||
// control the surface.
|
||||
let real = |value: f64| AssocEvalVariant { code: 40, value: AssocEvalValue::Real(value) };
|
||||
let short = |value: i32| AssocEvalVariant { code: 70, value: AssocEvalValue::Short(value as i16) };
|
||||
let real = |value: f64| AssocEvalVariant {
|
||||
code: 40,
|
||||
value: AssocEvalValue::Real(value),
|
||||
};
|
||||
let short = |value: i32| AssocEvalVariant {
|
||||
code: 70,
|
||||
value: AssocEvalValue::Short(value as i16),
|
||||
};
|
||||
let (kind, class, dxf, values, params): (
|
||||
AssocSurfaceActionKind,
|
||||
&str,
|
||||
@@ -2929,14 +3035,27 @@ fn materialize_source_surface_actions(
|
||||
Vec<(&str, i32, Vec<AssocEvalVariant>, bool)>,
|
||||
Vec<(&str, Option<Handle>)>,
|
||||
) = match sources {
|
||||
SurfaceSources::Extrude { profile, height, taper, .. } => (
|
||||
SurfaceSources::Extrude {
|
||||
profile,
|
||||
height,
|
||||
taper,
|
||||
..
|
||||
} => (
|
||||
AssocSurfaceActionKind::Extruded,
|
||||
"AcDbAssocExtrudedSurfaceActionBody",
|
||||
"ACDBASSOCEXTRUDEDSURFACEACTIONBODY",
|
||||
vec![("ExtrusionHeight", 1, vec![real(*height)], true), ("ExtrusionTaperAngle", 2, vec![real(*taper)], true)],
|
||||
vec![
|
||||
("ExtrusionHeight", 1, vec![real(*height)], true),
|
||||
("ExtrusionTaperAngle", 2, vec![real(*taper)], true),
|
||||
],
|
||||
vec![("ExtrudeProfile", Some(*profile))],
|
||||
),
|
||||
SurfaceSources::Revolve { profile, axis, angle, .. } => (
|
||||
SurfaceSources::Revolve {
|
||||
profile,
|
||||
axis,
|
||||
angle,
|
||||
..
|
||||
} => (
|
||||
AssocSurfaceActionKind::Revolved,
|
||||
"AcDbAssocRevolvedSurfaceActionBody",
|
||||
"ACDBASSOCREVOLVEDSURFACEACTIONBODY",
|
||||
@@ -2944,14 +3063,21 @@ fn materialize_source_surface_actions(
|
||||
vec![("RevolveProfile", Some(*profile)), ("RevolvePath", *axis)],
|
||||
),
|
||||
SurfaceSources::Loft { sections } => {
|
||||
let loft = document.solid_history_operations(surface).and_then(|operations| {
|
||||
operations.into_iter().find_map(|operation| match operation {
|
||||
SolidHistoryOperation::Loft(record) => record.parameters,
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
let loft = document
|
||||
.solid_history_operations(surface)
|
||||
.and_then(|operations| {
|
||||
operations
|
||||
.into_iter()
|
||||
.find_map(|operation| match operation {
|
||||
SolidHistoryOperation::Loft(record) => record.parameters,
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
let (continuity, bulge) = loft.map_or(((1, 1), (0.5, 0.5)), |p| {
|
||||
((p.start_continuity, p.end_continuity), (p.start_bulge, p.end_bulge))
|
||||
(
|
||||
(p.start_continuity, p.end_continuity),
|
||||
(p.start_bulge, p.end_bulge),
|
||||
)
|
||||
});
|
||||
(
|
||||
AssocSurfaceActionKind::Lofted,
|
||||
@@ -2960,30 +3086,84 @@ fn materialize_source_surface_actions(
|
||||
// One value per cross section: the start and end
|
||||
// settings at the ends, the defaults between them.
|
||||
vec![
|
||||
("Continuity", 0, (0..sections.len()).map(|index| short(if index == 0 { continuity.0 } else if index + 1 == sections.len() { continuity.1 } else { 1 })).collect(), false),
|
||||
("Bulge", 0, (0..sections.len()).map(|index| real(if index == 0 { bulge.0 } else if index + 1 == sections.len() { bulge.1 } else { 0.5 })).collect(), false),
|
||||
(
|
||||
"Continuity",
|
||||
0,
|
||||
(0..sections.len())
|
||||
.map(|index| {
|
||||
short(if index == 0 {
|
||||
continuity.0
|
||||
} else if index + 1 == sections.len() {
|
||||
continuity.1
|
||||
} else {
|
||||
1
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
false,
|
||||
),
|
||||
(
|
||||
"Bulge",
|
||||
0,
|
||||
(0..sections.len())
|
||||
.map(|index| {
|
||||
real(if index == 0 {
|
||||
bulge.0
|
||||
} else if index + 1 == sections.len() {
|
||||
bulge.1
|
||||
} else {
|
||||
0.5
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
false,
|
||||
),
|
||||
],
|
||||
sections.iter().map(|section| ("CrossSection", Some(*section)))
|
||||
sections
|
||||
.iter()
|
||||
.map(|section| ("CrossSection", Some(*section)))
|
||||
.chain(std::iter::once(("PathCurve", None)))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
};
|
||||
let document = &mut *allocator.document;
|
||||
let (action, body, written) = (document.allocate_handle(), document.allocate_handle(), document.allocate_handle());
|
||||
let mut objects: Vec<(Handle, Handle, &'static str, &'static str, AssociativeData)> = Vec::new();
|
||||
let (action, body, written) = (
|
||||
document.allocate_handle(),
|
||||
document.allocate_handle(),
|
||||
document.allocate_handle(),
|
||||
);
|
||||
let mut objects: Vec<(Handle, Handle, &'static str, &'static str, AssociativeData)> =
|
||||
Vec::new();
|
||||
let mut reactors: Vec<(Handle, Handle)> = vec![(surface, written)];
|
||||
let mut dependencies = vec![written];
|
||||
let mut owned = Vec::new();
|
||||
let mut writes = read_dependency(surface, 1);
|
||||
(writes.is_read_dependency, writes.is_write_dependency, writes.order) = (false, true, i32::MIN);
|
||||
objects.push((written, action, "ACDBASSOCDEPENDENCY", "AcDbAssocDependency", AssociativeData::Dependency(writes)));
|
||||
(
|
||||
writes.is_read_dependency,
|
||||
writes.is_write_dependency,
|
||||
writes.order,
|
||||
) = (false, true, i32::MIN);
|
||||
objects.push((
|
||||
written,
|
||||
action,
|
||||
"ACDBASSOCDEPENDENCY",
|
||||
"AcDbAssocDependency",
|
||||
AssociativeData::Dependency(writes),
|
||||
));
|
||||
let mut complete = true;
|
||||
for (name, source) in params.iter() {
|
||||
let parameter = document.allocate_handle();
|
||||
let mut children = Vec::new();
|
||||
if let Some(source) = source {
|
||||
let Some(reads) = source_reads(document, action, parameter, *source, dependencies.len() as i32 + 1, &mut objects) else {
|
||||
let Some(reads) = source_reads(
|
||||
document,
|
||||
action,
|
||||
parameter,
|
||||
*source,
|
||||
dependencies.len() as i32 + 1,
|
||||
&mut objects,
|
||||
) else {
|
||||
complete = false;
|
||||
break;
|
||||
};
|
||||
@@ -2993,48 +3173,88 @@ fn materialize_source_surface_actions(
|
||||
children.push(edge);
|
||||
}
|
||||
}
|
||||
objects.push((parameter, action, "ACDBASSOCPATHACTIONPARAM", "AcDbAssocPathActionParam",
|
||||
objects.push((
|
||||
parameter,
|
||||
action,
|
||||
"ACDBASSOCPATHACTIONPARAM",
|
||||
"AcDbAssocPathActionParam",
|
||||
AssociativeData::PathActionParam(AssocPathActionParam {
|
||||
compound: AssocCompoundActionParam {
|
||||
action_param: AssocActionParam { is_r2013: 1, version: 0, name: name.to_string() },
|
||||
action_param: AssocActionParam {
|
||||
is_r2013: 1,
|
||||
version: 0,
|
||||
name: name.to_string(),
|
||||
},
|
||||
class_version: 0,
|
||||
status: 0,
|
||||
parameters: children,
|
||||
child_parameter: None,
|
||||
},
|
||||
version: 0,
|
||||
})));
|
||||
}),
|
||||
));
|
||||
owned.push(parameter);
|
||||
}
|
||||
if !complete {
|
||||
continue;
|
||||
}
|
||||
objects.push((action, network, "ACDBASSOCACTION", "AcDbAssocAction", AssociativeData::Action(AssocAction {
|
||||
class_version: 2,
|
||||
geometry_status: 0,
|
||||
owning_network: network,
|
||||
action_body: body,
|
||||
action_index: 0,
|
||||
max_dependency_index: dependencies.len() as i32,
|
||||
dependencies: dependencies.iter().map(|handle| AssocActionDependency { is_owned: true, dependency: *handle }).collect(),
|
||||
owned_parameters: owned,
|
||||
values: values
|
||||
.into_iter()
|
||||
.map(|(name, unit_type, amounts, controls)| AssocValueParam {
|
||||
class_version: 0,
|
||||
name: name.to_string(),
|
||||
unit_type,
|
||||
variables: amounts.into_iter().map(|value| AssocValueParamVariable { value, handle: Handle::NULL }).collect(),
|
||||
controlled_object_dependency: if controls { written } else { Handle::NULL },
|
||||
})
|
||||
.collect(),
|
||||
})));
|
||||
objects.push((body, action, dxf, class, AssociativeData::SurfaceActionBody(AssocSurfaceActionBody {
|
||||
kind,
|
||||
action_body: AssocActionBody { version: 2 },
|
||||
surface_body: AssocSurfaceBody { version: 0, dependency: written, is_semi_associative: false, marker: 1, is_semi_override: false, grip_status: 0 },
|
||||
..Default::default()
|
||||
})));
|
||||
objects.push((
|
||||
action,
|
||||
network,
|
||||
"ACDBASSOCACTION",
|
||||
"AcDbAssocAction",
|
||||
AssociativeData::Action(AssocAction {
|
||||
class_version: 2,
|
||||
geometry_status: 0,
|
||||
owning_network: network,
|
||||
action_body: body,
|
||||
action_index: 0,
|
||||
max_dependency_index: dependencies.len() as i32,
|
||||
dependencies: dependencies
|
||||
.iter()
|
||||
.map(|handle| AssocActionDependency {
|
||||
is_owned: true,
|
||||
dependency: *handle,
|
||||
})
|
||||
.collect(),
|
||||
owned_parameters: owned,
|
||||
values: values
|
||||
.into_iter()
|
||||
.map(|(name, unit_type, amounts, controls)| AssocValueParam {
|
||||
class_version: 0,
|
||||
name: name.to_string(),
|
||||
unit_type,
|
||||
variables: amounts
|
||||
.into_iter()
|
||||
.map(|value| AssocValueParamVariable {
|
||||
value,
|
||||
handle: Handle::NULL,
|
||||
})
|
||||
.collect(),
|
||||
controlled_object_dependency: if controls { written } else { Handle::NULL },
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
));
|
||||
objects.push((
|
||||
body,
|
||||
action,
|
||||
dxf,
|
||||
class,
|
||||
AssociativeData::SurfaceActionBody(AssocSurfaceActionBody {
|
||||
kind,
|
||||
action_body: AssocActionBody { version: 2 },
|
||||
surface_body: AssocSurfaceBody {
|
||||
version: 0,
|
||||
dependency: written,
|
||||
is_semi_associative: false,
|
||||
marker: 1,
|
||||
is_semi_override: false,
|
||||
grip_status: 0,
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
));
|
||||
for (handle, owner, dxf, class, data) in objects {
|
||||
allocator.insert_associative_at(handle, owner, dxf, class, data);
|
||||
}
|
||||
@@ -3042,15 +3262,18 @@ fn materialize_source_surface_actions(
|
||||
let document = &mut *allocator.document;
|
||||
if let Some(entity) = document.get_entity_mut(target) {
|
||||
entity.common_mut().reactors.push(reactor);
|
||||
} else if let Some(ObjectType::Associative(object)) = document.objects.get_mut(&target) {
|
||||
} else if let Some(ObjectType::Associative(object)) = document.objects.get_mut(&target)
|
||||
{
|
||||
object.reactors.push(reactor);
|
||||
}
|
||||
}
|
||||
added.push(action);
|
||||
}
|
||||
for (offset, handle) in added.iter().copied().enumerate() {
|
||||
if let Some(ObjectType::Associative(AssociativeObject { data: AssociativeData::Action(action), .. })) =
|
||||
allocator.document.objects.get_mut(&handle)
|
||||
if let Some(ObjectType::Associative(AssociativeObject {
|
||||
data: AssociativeData::Action(action),
|
||||
..
|
||||
})) = allocator.document.objects.get_mut(&handle)
|
||||
{
|
||||
action.action_index = first_index + offset as i32 + 1;
|
||||
}
|
||||
@@ -3120,7 +3343,8 @@ fn materialize_swept_surface_actions(
|
||||
};
|
||||
let document = &mut *allocator.document;
|
||||
let mut next = || document.allocate_handle();
|
||||
let (action, body, written, profile_param, path_param) = (next(), next(), next(), next(), next());
|
||||
let (action, body, written, profile_param, path_param) =
|
||||
(next(), next(), next(), next(), next());
|
||||
let dependency = |dependent_on: Handle, read: bool, id: i32, order: i32| AssocDependency {
|
||||
class_version: 2,
|
||||
status: 0,
|
||||
@@ -3136,15 +3360,34 @@ fn materialize_swept_surface_actions(
|
||||
dependency_body: Handle::NULL,
|
||||
dependency_body_id: id,
|
||||
};
|
||||
let mut objects: Vec<(Handle, Handle, &'static str, &'static str, AssociativeData)> = Vec::new();
|
||||
let mut objects: Vec<(Handle, Handle, &'static str, &'static str, AssociativeData)> =
|
||||
Vec::new();
|
||||
// The profile (a region by its edges) and the path, read in that order.
|
||||
let Some(profile_reads) = source_reads(document, action, profile_param, profile, 2, &mut objects) else { continue };
|
||||
let Some(path_reads) = source_reads(document, action, path_param, path, profile_reads.len() as i32 + 2, &mut objects) else { continue };
|
||||
let Some(profile_reads) =
|
||||
source_reads(document, action, profile_param, profile, 2, &mut objects)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(path_reads) = source_reads(
|
||||
document,
|
||||
action,
|
||||
path_param,
|
||||
path,
|
||||
profile_reads.len() as i32 + 2,
|
||||
&mut objects,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let mut reactors: Vec<(Handle, Handle)> = vec![(surface, written)];
|
||||
reactors.extend(profile_reads.iter().map(|(_, read)| (profile, *read)));
|
||||
reactors.extend(path_reads.iter().map(|(_, read)| (path, *read)));
|
||||
let mut dependencies = vec![written];
|
||||
dependencies.extend(profile_reads.iter().chain(&path_reads).map(|(_, read)| *read));
|
||||
dependencies.extend(
|
||||
profile_reads
|
||||
.iter()
|
||||
.chain(&path_reads)
|
||||
.map(|(_, read)| *read),
|
||||
);
|
||||
let mut value_handles = [Handle::NULL, Handle::NULL];
|
||||
let mut network_actions = vec![action];
|
||||
// Scale (slot 0) and twist (slot 1) expressions.
|
||||
@@ -3281,22 +3524,51 @@ fn materialize_swept_surface_actions(
|
||||
grip_status: 0,
|
||||
},
|
||||
..Default::default()
|
||||
})));
|
||||
objects.push((written, action, "ACDBASSOCDEPENDENCY", "AcDbAssocDependency", AssociativeData::Dependency(dependency(surface, false, 1, i32::MIN))));
|
||||
let path_parameter = |name: &str, children: Vec<Handle>| AssociativeData::PathActionParam(AssocPathActionParam {
|
||||
compound: AssocCompoundActionParam {
|
||||
action_param: AssocActionParam { is_r2013: 1, version: 0, name: name.to_string() },
|
||||
class_version: 0,
|
||||
status: 0,
|
||||
parameters: children,
|
||||
child_parameter: None,
|
||||
},
|
||||
version: 0,
|
||||
});
|
||||
objects.push((profile_param, action, "ACDBASSOCPATHACTIONPARAM", "AcDbAssocPathActionParam",
|
||||
path_parameter("SweepProfile", profile_reads.iter().map(|(edge, _)| *edge).collect())));
|
||||
objects.push((path_param, action, "ACDBASSOCPATHACTIONPARAM", "AcDbAssocPathActionParam",
|
||||
path_parameter("SweepPath", path_reads.iter().map(|(edge, _)| *edge).collect())));
|
||||
}),
|
||||
));
|
||||
objects.push((
|
||||
written,
|
||||
action,
|
||||
"ACDBASSOCDEPENDENCY",
|
||||
"AcDbAssocDependency",
|
||||
AssociativeData::Dependency(dependency(surface, false, 1, i32::MIN)),
|
||||
));
|
||||
let path_parameter = |name: &str, children: Vec<Handle>| {
|
||||
AssociativeData::PathActionParam(AssocPathActionParam {
|
||||
compound: AssocCompoundActionParam {
|
||||
action_param: AssocActionParam {
|
||||
is_r2013: 1,
|
||||
version: 0,
|
||||
name: name.to_string(),
|
||||
},
|
||||
class_version: 0,
|
||||
status: 0,
|
||||
parameters: children,
|
||||
child_parameter: None,
|
||||
},
|
||||
version: 0,
|
||||
})
|
||||
};
|
||||
objects.push((
|
||||
profile_param,
|
||||
action,
|
||||
"ACDBASSOCPATHACTIONPARAM",
|
||||
"AcDbAssocPathActionParam",
|
||||
path_parameter(
|
||||
"SweepProfile",
|
||||
profile_reads.iter().map(|(edge, _)| *edge).collect(),
|
||||
),
|
||||
));
|
||||
objects.push((
|
||||
path_param,
|
||||
action,
|
||||
"ACDBASSOCPATHACTIONPARAM",
|
||||
"AcDbAssocPathActionParam",
|
||||
path_parameter(
|
||||
"SweepPath",
|
||||
path_reads.iter().map(|(edge, _)| *edge).collect(),
|
||||
),
|
||||
));
|
||||
for (handle, owner, dxf, class, data) in objects {
|
||||
allocator.insert_associative_at(handle, owner, dxf, class, data);
|
||||
}
|
||||
@@ -3658,13 +3930,29 @@ fn materialize_scope(
|
||||
}
|
||||
}
|
||||
refresh_foreign_values(allocator.document, &foreign_actions, parameters);
|
||||
let pending = if materialize_all_parameters { pending_swept_surfaces(allocator.document) } else { Vec::new() };
|
||||
let pending = if materialize_all_parameters {
|
||||
pending_swept_surfaces(allocator.document)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut surface_actions = materialize_swept_surface_actions(
|
||||
&mut allocator, network_handle, parameters, &pending, first_foreign + foreign_actions.len() as i32);
|
||||
let pending_sources = if materialize_all_parameters { pending_source_surfaces(allocator.document) } else { Vec::new() };
|
||||
&mut allocator,
|
||||
network_handle,
|
||||
parameters,
|
||||
&pending,
|
||||
first_foreign + foreign_actions.len() as i32,
|
||||
);
|
||||
let pending_sources = if materialize_all_parameters {
|
||||
pending_source_surfaces(allocator.document)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let source_actions = materialize_source_surface_actions(
|
||||
&mut allocator, network_handle, &pending_sources,
|
||||
first_foreign + (foreign_actions.len() + surface_actions.len()) as i32);
|
||||
&mut allocator,
|
||||
network_handle,
|
||||
&pending_sources,
|
||||
first_foreign + (foreign_actions.len() + surface_actions.len()) as i32,
|
||||
);
|
||||
surface_actions.extend(source_actions);
|
||||
|
||||
let network = AssocNetwork {
|
||||
|
||||
+33
-17
@@ -7,12 +7,10 @@
|
||||
// cache — block-definition and property caches
|
||||
pub mod annotative;
|
||||
pub mod assoc_array;
|
||||
pub mod block_visibility;
|
||||
pub mod compare;
|
||||
pub mod section_cull;
|
||||
pub mod sub_select;
|
||||
pub mod block_service;
|
||||
pub mod block_visibility;
|
||||
pub mod cache;
|
||||
pub mod compare;
|
||||
pub mod convert;
|
||||
pub mod creation_style;
|
||||
pub(crate) mod frame;
|
||||
@@ -20,6 +18,8 @@ pub mod model;
|
||||
pub mod pick;
|
||||
pub mod pipeline;
|
||||
pub(crate) mod render_graph;
|
||||
pub mod section_cull;
|
||||
pub mod sub_select;
|
||||
pub mod text;
|
||||
pub mod view;
|
||||
|
||||
@@ -4932,10 +4932,14 @@ impl Scene {
|
||||
/// Fit a layout's sheet viewport to its page. (Taking the sheet out
|
||||
/// instead would leave a viewport onto the model looking like the sheet.)
|
||||
pub fn fit_layout_sheet(&mut self, layout_name: &str) {
|
||||
let layout = self.document.objects.values().find_map(|object| match object {
|
||||
ObjectType::Layout(layout) if layout.name == layout_name => Some(layout.clone()),
|
||||
_ => None,
|
||||
});
|
||||
let layout = self
|
||||
.document
|
||||
.objects
|
||||
.values()
|
||||
.find_map(|object| match object {
|
||||
ObjectType::Layout(layout) if layout.name == layout_name => Some(layout.clone()),
|
||||
_ => None,
|
||||
});
|
||||
let Some(layout) = layout else {
|
||||
return;
|
||||
};
|
||||
@@ -7514,8 +7518,8 @@ impl Scene {
|
||||
let t_patch = iced::time::Instant::now();
|
||||
// A patch whose set will not equal the memo's runs leaves the memo
|
||||
// holding its own copies, taken while the set is still whole.
|
||||
let shareable = self.uses_resident_memo(block, frozen_layers)
|
||||
&& self.resident_runs_match_memo(block);
|
||||
let shareable =
|
||||
self.uses_resident_memo(block, frozen_layers) && self.resident_runs_match_memo(block);
|
||||
if self.uses_resident_memo(block, frozen_layers) && !shareable {
|
||||
self.detach_memo_backing();
|
||||
}
|
||||
@@ -8059,8 +8063,9 @@ impl Scene {
|
||||
apply_mode(&mut wires, mode);
|
||||
model_wires.extend(wires);
|
||||
}
|
||||
let paper = crate::scene::model::instance_model::expand_stubs(&self.paper_sheet_wires_arc())
|
||||
.into_owned();
|
||||
let paper =
|
||||
crate::scene::model::instance_model::expand_stubs(&self.paper_sheet_wires_arc())
|
||||
.into_owned();
|
||||
(paper, model_wires)
|
||||
}
|
||||
|
||||
@@ -10762,7 +10767,7 @@ impl Scene {
|
||||
let clip = view_rot * (cursor - eye).as_vec3().extend(1.0);
|
||||
if !clip.is_finite() || clip.w <= 1e-6 {
|
||||
return crate::scene::pick::interaction_index::InteractionCandidates::all(wires)
|
||||
.materialize_stubs();
|
||||
.materialize_stubs();
|
||||
}
|
||||
let ndc = clip.truncate() / clip.w;
|
||||
let screen = iced::Point::new(
|
||||
@@ -11322,7 +11327,10 @@ impl Scene {
|
||||
}
|
||||
|
||||
pub fn view_center_surface_pivot(&self, bounds: iced::Rectangle) -> Option<glam::DVec3> {
|
||||
self.surface_pivot_at(bounds, iced::Point::new(bounds.width * 0.5, bounds.height * 0.5))
|
||||
self.surface_pivot_at(
|
||||
bounds,
|
||||
iced::Point::new(bounds.width * 0.5, bounds.height * 0.5),
|
||||
)
|
||||
}
|
||||
|
||||
/// The nearest solid or mesh surface under `local` (relative to `bounds`).
|
||||
@@ -13343,13 +13351,21 @@ pub(crate) fn drop_layout_viewports(document: &mut codec::CadDocument, block: Ha
|
||||
for handle in &viewports {
|
||||
document.remove_entity(*handle);
|
||||
}
|
||||
if let Some(record) = document.block_records.iter_mut().find(|record| record.handle == block) {
|
||||
record.entity_handles.retain(|handle| !viewports.contains(handle));
|
||||
if let Some(record) = document
|
||||
.block_records
|
||||
.iter_mut()
|
||||
.find(|record| record.handle == block)
|
||||
{
|
||||
record
|
||||
.entity_handles
|
||||
.retain(|handle| !viewports.contains(handle));
|
||||
}
|
||||
for object in document.objects.values_mut() {
|
||||
if let ObjectType::Layout(layout) = object {
|
||||
if layout.block_record == block {
|
||||
layout.viewports.retain(|handle| !viewports.contains(handle));
|
||||
layout
|
||||
.viewports
|
||||
.retain(|handle| !viewports.contains(handle));
|
||||
if viewports.contains(&layout.viewport) {
|
||||
layout.viewport = Handle::NULL;
|
||||
}
|
||||
|
||||
+108
-38
@@ -673,12 +673,24 @@ pub(crate) fn brightness_contrast_lut(brightness: u8, contrast: u8) -> [u8; 256]
|
||||
let mut x = i as f64 / 255.0;
|
||||
x = match brightness.min(100) {
|
||||
0 => 0.0,
|
||||
100 => if i == 0 { 0.0 } else { 1.0 },
|
||||
100 => {
|
||||
if i == 0 {
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
b => x.powf(curve(b)),
|
||||
};
|
||||
x = match contrast.min(100) {
|
||||
0 => 0.5,
|
||||
100 => if x >= 0.5 { 1.0 } else { 0.0 },
|
||||
100 => {
|
||||
if x >= 0.5 {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
c => {
|
||||
let d = 2.0 * x - 1.0;
|
||||
0.5 + 0.5 * d.signum() * d.abs().powf(curve(c))
|
||||
@@ -705,7 +717,12 @@ fn adjusted_image_pixels(
|
||||
type Key = (String, usize, u8, u8);
|
||||
type Entry = (Arc<Vec<u8>>, Arc<Vec<u8>>);
|
||||
static CACHE: OnceLock<Mutex<HashMap<Key, Entry>>> = OnceLock::new();
|
||||
let key = (path.to_string(), Arc::as_ptr(pixels) as usize, brightness, contrast);
|
||||
let key = (
|
||||
path.to_string(),
|
||||
Arc::as_ptr(pixels) as usize,
|
||||
brightness,
|
||||
contrast,
|
||||
);
|
||||
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
||||
if let Some((_, hit)) = cache.lock().unwrap_or_else(|p| p.into_inner()).get(&key) {
|
||||
return Arc::clone(hit);
|
||||
@@ -848,7 +865,10 @@ struct NativeFootprint {
|
||||
codec_factor: u64,
|
||||
}
|
||||
|
||||
fn native_footprint(format: Option<image::ImageFormat>, color: Option<image::ColorType>) -> NativeFootprint {
|
||||
fn native_footprint(
|
||||
format: Option<image::ImageFormat>,
|
||||
color: Option<image::ColorType>,
|
||||
) -> NativeFootprint {
|
||||
let native_bpp = color.map_or(4u64, |color| match color {
|
||||
image::ColorType::L8 => 1,
|
||||
image::ColorType::La8 => 2,
|
||||
@@ -881,19 +901,17 @@ fn native_footprint(format: Option<image::ImageFormat>, color: Option<image::Col
|
||||
// chunk scratch (a TOTAL cap), so no multiplier is needed: the honest
|
||||
// peak is the coexisting native+RGBA pair, already in peak_bpp.
|
||||
let codec_factor = 1;
|
||||
NativeFootprint { peak_bpp, codec_factor }
|
||||
NativeFootprint {
|
||||
peak_bpp,
|
||||
codec_factor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Budget decision for probed pixel dimensions. Pure and cfg-independent
|
||||
/// when the budgets are passed explicitly, so tests can probe arbitrary
|
||||
/// budgets; the decode path always passes this target's constants.
|
||||
#[cfg_attr(not(test), allow(dead_code))]
|
||||
fn tier_for(
|
||||
width: u32,
|
||||
height: u32,
|
||||
max_decode_bytes: u64,
|
||||
max_display_pixels: u64,
|
||||
) -> DecodeTier {
|
||||
fn tier_for(width: u32, height: u32, max_decode_bytes: u64, max_display_pixels: u64) -> DecodeTier {
|
||||
tier_for_native(width, height, 4, 1, max_decode_bytes, max_display_pixels)
|
||||
}
|
||||
|
||||
@@ -909,11 +927,7 @@ fn tier_for_native(
|
||||
max_display_pixels: u64,
|
||||
) -> DecodeTier {
|
||||
let pixels = width as u64 * height as u64;
|
||||
if pixels
|
||||
.saturating_mul(peak_bpp)
|
||||
.saturating_mul(codec_factor)
|
||||
> max_decode_bytes
|
||||
{
|
||||
if pixels.saturating_mul(peak_bpp).saturating_mul(codec_factor) > max_decode_bytes {
|
||||
return DecodeTier::OverBudget;
|
||||
}
|
||||
if pixels <= max_display_pixels {
|
||||
@@ -925,7 +939,11 @@ fn tier_for_native(
|
||||
let (target_w, target_h) = downsample_to_cap(width, height, max_display_pixels);
|
||||
let resize_peak = pixels
|
||||
.saturating_mul(4)
|
||||
.saturating_add((width as u64).saturating_mul(target_h as u64).saturating_mul(16))
|
||||
.saturating_add(
|
||||
(width as u64)
|
||||
.saturating_mul(target_h as u64)
|
||||
.saturating_mul(16),
|
||||
)
|
||||
.saturating_add(target_w as u64 * target_h as u64 * 4);
|
||||
if resize_peak > max_decode_bytes {
|
||||
DecodeTier::OverBudget
|
||||
@@ -1037,7 +1055,10 @@ fn decode_with_budgets<R: std::io::BufRead + std::io::Seek>(
|
||||
limits.max_alloc = Some(match reader.format() {
|
||||
Some(image::ImageFormat::Gif) => probed
|
||||
.map(|(width, height, _, _)| {
|
||||
(width as u64).saturating_mul(height as u64).saturating_mul(4) + 64 * 1024 * 1024
|
||||
(width as u64)
|
||||
.saturating_mul(height as u64)
|
||||
.saturating_mul(4)
|
||||
+ 64 * 1024 * 1024
|
||||
})
|
||||
.unwrap_or(max_decode_bytes)
|
||||
.min(max_decode_bytes),
|
||||
@@ -1067,7 +1088,12 @@ fn decode_with_budgets<R: std::io::BufRead + std::io::Seek>(
|
||||
}
|
||||
};
|
||||
let rgba = if target_w != width || target_h != height {
|
||||
image::imageops::resize(&rgba, target_w, target_h, image::imageops::FilterType::Lanczos3)
|
||||
image::imageops::resize(
|
||||
&rgba,
|
||||
target_w,
|
||||
target_h,
|
||||
image::imageops::FilterType::Lanczos3,
|
||||
)
|
||||
} else {
|
||||
rgba
|
||||
};
|
||||
@@ -1123,7 +1149,10 @@ fn refusal_message(path: &str, width: u32, height: u32) -> String {
|
||||
)
|
||||
.into_owned();
|
||||
}
|
||||
let decoded_mb = (width as u64).saturating_mul(height as u64).saturating_mul(4) / (1024 * 1024);
|
||||
let decoded_mb = (width as u64)
|
||||
.saturating_mul(height as u64)
|
||||
.saturating_mul(4)
|
||||
/ (1024 * 1024);
|
||||
let limit_mb = MAX_DECODE_BYTES / (1024 * 1024);
|
||||
crate::tf!(
|
||||
"\"{name}\" is {width}×{height} px and would decode to {decoded} MB, over the {limit} MB image limit of this build — it cannot be attached. Images within the limit are attached at a reduced display resolution when needed.",
|
||||
@@ -1156,7 +1185,9 @@ pub fn decode_refusals() -> Vec<String> {
|
||||
/// Take and clear — for the drains that own every queued note (the poll and
|
||||
/// the open-settle paths).
|
||||
pub fn take_decode_refusals() -> Vec<String> {
|
||||
let mut notes = refusal_notes().lock().unwrap_or_else(|error| error.into_inner());
|
||||
let mut notes = refusal_notes()
|
||||
.lock()
|
||||
.unwrap_or_else(|error| error.into_inner());
|
||||
std::mem::take(&mut *notes)
|
||||
}
|
||||
|
||||
@@ -1170,7 +1201,9 @@ fn decode_or_note(attempt: DecodeAttempt, path: &str) -> Option<DecodedImage> {
|
||||
// one note per distinct message so the command line is not
|
||||
// spammed by a drawing that inserts it repeatedly.
|
||||
let message = refusal_message(path, width, height);
|
||||
let mut notes = refusal_notes().lock().unwrap_or_else(|error| error.into_inner());
|
||||
let mut notes = refusal_notes()
|
||||
.lock()
|
||||
.unwrap_or_else(|error| error.into_inner());
|
||||
if notes.last() != Some(&message) {
|
||||
notes.push(message);
|
||||
}
|
||||
@@ -1338,7 +1371,9 @@ mod web_fetch {
|
||||
// No stream API (or already consumed): fall back to buffering,
|
||||
// with the received-length check still applying after.
|
||||
let buffer = JsFuture::from(
|
||||
response.array_buffer().map_err(|_| RemoteFetchError::Network)?,
|
||||
response
|
||||
.array_buffer()
|
||||
.map_err(|_| RemoteFetchError::Network)?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| rejection_kind(&error))?;
|
||||
@@ -1348,7 +1383,9 @@ mod web_fetch {
|
||||
}
|
||||
return Ok(bytes);
|
||||
};
|
||||
let reader = stream.get_reader().dyn_into::<web_sys::ReadableStreamDefaultReader>()
|
||||
let reader = stream
|
||||
.get_reader()
|
||||
.dyn_into::<web_sys::ReadableStreamDefaultReader>()
|
||||
.map_err(|_| RemoteFetchError::Network)?;
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
loop {
|
||||
@@ -1387,8 +1424,7 @@ mod web_fetch {
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
let window = web_sys::window().ok_or(RemoteFetchError::Network)?;
|
||||
let controller =
|
||||
web_sys::AbortController::new().map_err(|_| RemoteFetchError::Network)?;
|
||||
let controller = web_sys::AbortController::new().map_err(|_| RemoteFetchError::Network)?;
|
||||
let init = web_sys::RequestInit::new();
|
||||
init.set_method("GET");
|
||||
init.set_signal(Some(&controller.signal()));
|
||||
@@ -1470,8 +1506,8 @@ fn fetch_remote(url: &str) -> Option<Vec<u8>> {
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web_fetch::{
|
||||
take_outcome as take_remote_fetch_outcome, take_overflowed,
|
||||
RemoteFetchError, RemoteFetchOutcome,
|
||||
take_outcome as take_remote_fetch_outcome, take_overflowed, RemoteFetchError,
|
||||
RemoteFetchOutcome,
|
||||
};
|
||||
// The capped streaming reader itself, shared with the `?file=` deep-link
|
||||
// fetch (`io::web_link`) so both doors enforce the same 64 MB / 8 s bounds.
|
||||
@@ -1510,7 +1546,10 @@ mod tests {
|
||||
"{color:?}"
|
||||
);
|
||||
}
|
||||
assert_eq!(native_footprint(Some(gif), Some(ColorType::Rgba8)).peak_bpp, 8);
|
||||
assert_eq!(
|
||||
native_footprint(Some(gif), Some(ColorType::Rgba8)).peak_bpp,
|
||||
8
|
||||
);
|
||||
assert_eq!(native_footprint(None, None).peak_bpp, 8);
|
||||
}
|
||||
|
||||
@@ -1614,12 +1653,21 @@ mod tests {
|
||||
assert_eq!(tier_for(64, 64, 1_000_000, 10_000), DecodeTier::Full);
|
||||
// Pixels over the display cap while RGBA stays within the decode
|
||||
// budget: decode, then downsample before storing.
|
||||
assert_eq!(tier_for(400, 400, 2_000_000, 10_000), DecodeTier::Downsample);
|
||||
assert_eq!(
|
||||
tier_for(400, 400, 2_000_000, 10_000),
|
||||
DecodeTier::Downsample
|
||||
);
|
||||
// …unless the resize transient (decoded + f32 intermediate + output)
|
||||
// is what overflows the budget.
|
||||
assert_eq!(tier_for(400, 400, 1_000_000, 10_000), DecodeTier::OverBudget);
|
||||
assert_eq!(
|
||||
tier_for(400, 400, 1_000_000, 10_000),
|
||||
DecodeTier::OverBudget
|
||||
);
|
||||
// RGBA over the decode budget.
|
||||
assert_eq!(tier_for(2_000, 2_000, 1_000_000, 10_000), DecodeTier::OverBudget);
|
||||
assert_eq!(
|
||||
tier_for(2_000, 2_000, 1_000_000, 10_000),
|
||||
DecodeTier::OverBudget
|
||||
);
|
||||
// Exact boundary counts stay in the lower tier (limits are ">").
|
||||
assert_eq!(tier_for(50, 200, 1_000_000, 10_000), DecodeTier::Full);
|
||||
assert_eq!(tier_for(500, 500, 1_000_000, 1_000_000), DecodeTier::Full);
|
||||
@@ -1653,10 +1701,18 @@ mod tests {
|
||||
// Over the cap: the product fits while both sides exceed 1 px, and
|
||||
// the aspect survives to within rounding (unless a side is too
|
||||
// small to express it — a 3 px side clamps to 1 px by design).
|
||||
for (width, height) in [(4097u32, 4096u32), (20000, 15000), (12345, 6700), (3, 20000000)] {
|
||||
for (width, height) in [
|
||||
(4097u32, 4096u32),
|
||||
(20000, 15000),
|
||||
(12345, 6700),
|
||||
(3, 20000000),
|
||||
] {
|
||||
let cap = 4096u64 * 4096;
|
||||
let (w, h) = downsample_to_cap(width, height, cap);
|
||||
assert!((w as u64) * h as u64 <= cap, "{width}×{height} -> {w}×{h} over cap");
|
||||
assert!(
|
||||
(w as u64) * h as u64 <= cap,
|
||||
"{width}×{height} -> {w}×{h} over cap"
|
||||
);
|
||||
assert!(w >= 1 && h >= 1);
|
||||
if w >= 8 && h >= 8 {
|
||||
let aspect = width as f64 / height as f64;
|
||||
@@ -1720,13 +1776,27 @@ mod tests {
|
||||
#[test]
|
||||
fn refusals_are_noted_once_per_message() {
|
||||
let _ = take_decode_refusals();
|
||||
let refuse = || DecodeAttempt::Refused { width: 30000, height: 30000 };
|
||||
let refuse = || DecodeAttempt::Refused {
|
||||
width: 30000,
|
||||
height: 30000,
|
||||
};
|
||||
assert!(decode_or_note(refuse(), "C:/gis/aerial.tif").is_none());
|
||||
assert!(decode_or_note(refuse(), "C:/gis/aerial.tif").is_none());
|
||||
let notes = take_decode_refusals();
|
||||
assert_eq!(notes.len(), 1, "duplicate messages collapse: {notes:?}");
|
||||
assert!(notes[0].contains("30000×30000"), "dimensions in: {}", notes[0]);
|
||||
assert!(notes[0].contains("aerial.tif"), "file name in: {}", notes[0]);
|
||||
assert!(take_decode_refusals().is_empty(), "the drain cleared the queue");
|
||||
assert!(
|
||||
notes[0].contains("30000×30000"),
|
||||
"dimensions in: {}",
|
||||
notes[0]
|
||||
);
|
||||
assert!(
|
||||
notes[0].contains("aerial.tif"),
|
||||
"file name in: {}",
|
||||
notes[0]
|
||||
);
|
||||
assert!(
|
||||
take_decode_refusals().is_empty(),
|
||||
"the drain cleared the queue"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,12 @@ pub fn stubbable(wire: &WireModel) -> bool {
|
||||
/// The XY box a prototype's wires cover, before any copy's offset.
|
||||
pub fn prototype_aabb(prototype: &[WireModel]) -> [f32; 4] {
|
||||
prototype.iter().fold(
|
||||
[f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY],
|
||||
[
|
||||
f32::INFINITY,
|
||||
f32::INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
],
|
||||
|acc, wire| {
|
||||
[
|
||||
acc[0].min(wire.aabb[0]),
|
||||
|
||||
@@ -10,8 +10,12 @@ use kernel::brep::Body;
|
||||
pub(crate) fn embedded_section(entity: &EntityType) -> Option<EmbeddedEntity> {
|
||||
match entity {
|
||||
// A region as the curve round it, as the reference records it.
|
||||
EntityType::Region(region) => Some(super::sweep_model::region_boundary_entity(entity)
|
||||
.map_or_else(|| EmbeddedEntity::Region(region.clone()), |(curve, _)| curve)),
|
||||
EntityType::Region(region) => Some(
|
||||
super::sweep_model::region_boundary_entity(entity).map_or_else(
|
||||
|| EmbeddedEntity::Region(region.clone()),
|
||||
|(curve, _)| curve,
|
||||
),
|
||||
),
|
||||
EntityType::Point(point) => Some(EmbeddedEntity::Point(point.clone())),
|
||||
_ => embedded_path(entity),
|
||||
}
|
||||
|
||||
@@ -15,10 +15,22 @@ pub const SURFACE_SOURCES_APP: &str = "OCS_SURFACE_SOURCES";
|
||||
pub enum SurfaceSources {
|
||||
/// EXTRUDE: the profile, the height along its normal (or the length of a
|
||||
/// given direction) and the taper angle.
|
||||
Extrude { profile: Handle, height: f64, direction: Option<DVec3>, taper: f64 },
|
||||
Extrude {
|
||||
profile: Handle,
|
||||
height: f64,
|
||||
direction: Option<DVec3>,
|
||||
taper: f64,
|
||||
},
|
||||
/// REVOLVE: the profile, the axis (an axis object or two points), the
|
||||
/// angle and the start angle.
|
||||
Revolve { profile: Handle, axis: Option<Handle>, start: DVec3, end: DVec3, angle: f64, start_angle: f64 },
|
||||
Revolve {
|
||||
profile: Handle,
|
||||
axis: Option<Handle>,
|
||||
start: DVec3,
|
||||
end: DVec3,
|
||||
angle: f64,
|
||||
start_angle: f64,
|
||||
},
|
||||
/// LOFT: the cross sections in order.
|
||||
Loft { sections: Vec<Handle> },
|
||||
}
|
||||
@@ -28,20 +40,31 @@ impl SurfaceSources {
|
||||
pub fn handles(&self) -> Vec<Handle> {
|
||||
match self {
|
||||
SurfaceSources::Extrude { profile, .. } => vec![*profile],
|
||||
SurfaceSources::Revolve { profile, axis, .. } => std::iter::once(*profile).chain(*axis).collect(),
|
||||
SurfaceSources::Revolve { profile, axis, .. } => {
|
||||
std::iter::once(*profile).chain(*axis).collect()
|
||||
}
|
||||
SurfaceSources::Loft { sections } => sections.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep the sources with the surface.
|
||||
pub fn link_surface_sources(document: &mut codec::CadDocument, surface: Handle, sources: &SurfaceSources) {
|
||||
pub fn link_surface_sources(
|
||||
document: &mut codec::CadDocument,
|
||||
surface: Handle,
|
||||
sources: &SurfaceSources,
|
||||
) {
|
||||
if surface.is_null() {
|
||||
return;
|
||||
}
|
||||
let point = |p: DVec3| XDataValue::Point3D(codec::types::Vector3::new(p.x, p.y, p.z));
|
||||
let values = match sources {
|
||||
SurfaceSources::Extrude { profile, height, direction, taper } => {
|
||||
SurfaceSources::Extrude {
|
||||
profile,
|
||||
height,
|
||||
direction,
|
||||
taper,
|
||||
} => {
|
||||
let mut values = vec![
|
||||
XDataValue::String("EXTRUDE".into()),
|
||||
XDataValue::Handle(*profile),
|
||||
@@ -51,7 +74,14 @@ pub fn link_surface_sources(document: &mut codec::CadDocument, surface: Handle,
|
||||
values.extend(direction.map(point));
|
||||
values
|
||||
}
|
||||
SurfaceSources::Revolve { profile, axis, start, end, angle, start_angle } => {
|
||||
SurfaceSources::Revolve {
|
||||
profile,
|
||||
axis,
|
||||
start,
|
||||
end,
|
||||
angle,
|
||||
start_angle,
|
||||
} => {
|
||||
let mut values = vec![
|
||||
XDataValue::String("REVOLVE".into()),
|
||||
XDataValue::Handle(*profile),
|
||||
@@ -67,7 +97,12 @@ pub fn link_surface_sources(document: &mut codec::CadDocument, surface: Handle,
|
||||
.chain(sections.iter().map(|handle| XDataValue::Handle(*handle)))
|
||||
.collect(),
|
||||
};
|
||||
crate::scene::view::dispatch::set_entity_xdata(document, surface, SURFACE_SOURCES_APP, Some(values));
|
||||
crate::scene::view::dispatch::set_entity_xdata(
|
||||
document,
|
||||
surface,
|
||||
SURFACE_SOURCES_APP,
|
||||
Some(values),
|
||||
);
|
||||
}
|
||||
|
||||
/// The sources an associative surface keeps.
|
||||
|
||||
@@ -15,10 +15,17 @@ fn embedded_sweep_profile(
|
||||
) -> Option<(codec::entities::EmbeddedEntity, [f64; 16])> {
|
||||
match entity {
|
||||
// A region as the curve round it, as the reference records it.
|
||||
EntityType::Region(region) => super::sweep_model::region_boundary_entity(entity).or_else(|| {
|
||||
Some((codec::entities::EmbeddedEntity::Region(region.clone()), glam::DMat4::IDENTITY.to_cols_array()))
|
||||
}),
|
||||
EntityType::Polyline3D(value) => Some((polyline_wire(value)?, glam::DMat4::IDENTITY.to_cols_array())),
|
||||
EntityType::Region(region) => {
|
||||
super::sweep_model::region_boundary_entity(entity).or_else(|| {
|
||||
Some((
|
||||
codec::entities::EmbeddedEntity::Region(region.clone()),
|
||||
glam::DMat4::IDENTITY.to_cols_array(),
|
||||
))
|
||||
})
|
||||
}
|
||||
EntityType::Polyline3D(value) => {
|
||||
Some((polyline_wire(value)?, glam::DMat4::IDENTITY.to_cols_array()))
|
||||
}
|
||||
_ => embedded_revolve_profile(entity),
|
||||
}
|
||||
}
|
||||
@@ -725,20 +732,33 @@ pub fn assoc_edge_curve(entity: &EntityType) -> Option<AssocCurve> {
|
||||
// across the major one), major and minor radius, start and end
|
||||
// parameter, 0.
|
||||
EntityType::Ellipse(ellipse) => {
|
||||
let major = glam::DVec3::new(ellipse.major_axis.x, ellipse.major_axis.y, ellipse.major_axis.z);
|
||||
let normal = glam::DVec3::new(ellipse.normal.x, ellipse.normal.y, ellipse.normal.z).try_normalize()?;
|
||||
let major = glam::DVec3::new(
|
||||
ellipse.major_axis.x,
|
||||
ellipse.major_axis.y,
|
||||
ellipse.major_axis.z,
|
||||
);
|
||||
let normal = glam::DVec3::new(ellipse.normal.x, ellipse.normal.y, ellipse.normal.z)
|
||||
.try_normalize()?;
|
||||
let radius = major.length();
|
||||
let axis = major.try_normalize()?;
|
||||
Some((17, K::Ellipse, vec![
|
||||
point(glam::DVec3::new(ellipse.center.x, ellipse.center.y, ellipse.center.z)),
|
||||
point(axis),
|
||||
point(normal.cross(axis)),
|
||||
V::Real(radius),
|
||||
V::Real(radius * ellipse.minor_axis_ratio),
|
||||
V::Real(ellipse.start_parameter),
|
||||
V::Real(ellipse.end_parameter),
|
||||
V::Real(0.0),
|
||||
]))
|
||||
Some((
|
||||
17,
|
||||
K::Ellipse,
|
||||
vec![
|
||||
point(glam::DVec3::new(
|
||||
ellipse.center.x,
|
||||
ellipse.center.y,
|
||||
ellipse.center.z,
|
||||
)),
|
||||
point(axis),
|
||||
point(normal.cross(axis)),
|
||||
V::Real(radius),
|
||||
V::Real(radius * ellipse.minor_axis_ratio),
|
||||
V::Real(ellipse.start_parameter),
|
||||
V::Real(ellipse.end_parameter),
|
||||
V::Real(0.0),
|
||||
],
|
||||
))
|
||||
}
|
||||
// NURBS (42): rational flag, a second flag (0), degree, knot
|
||||
// tolerance, then knots, weights (empty unless rational) and control
|
||||
@@ -749,20 +769,45 @@ pub fn assoc_edge_curve(entity: &EntityType) -> Option<AssocCurve> {
|
||||
// in the fit parameter (chord length) as the reference keeps them.
|
||||
let curve = crate::entities::spline::nurbs3(spline)?;
|
||||
let mut knots = curve.knots().to_vec();
|
||||
if crate::entities::spline::uses_fit_method(spline) && spline.knot_parameterization == 0 {
|
||||
let chord: f64 = spline.fit_points.windows(2)
|
||||
.map(|pair| ((pair[1].x - pair[0].x).powi(2) + (pair[1].y - pair[0].y).powi(2) + (pair[1].z - pair[0].z).powi(2)).sqrt())
|
||||
if crate::entities::spline::uses_fit_method(spline) && spline.knot_parameterization == 0
|
||||
{
|
||||
let chord: f64 = spline
|
||||
.fit_points
|
||||
.windows(2)
|
||||
.map(|pair| {
|
||||
((pair[1].x - pair[0].x).powi(2)
|
||||
+ (pair[1].y - pair[0].y).powi(2)
|
||||
+ (pair[1].z - pair[0].z).powi(2))
|
||||
.sqrt()
|
||||
})
|
||||
.sum();
|
||||
let (low, high) = (knots[0], knots[knots.len() - 1]);
|
||||
if high - low > 0.0 && chord > 0.0 && (high - low - chord).abs() > 1e-9 * chord {
|
||||
knots.iter_mut().for_each(|knot| *knot = (*knot - low) / (high - low) * chord);
|
||||
knots
|
||||
.iter_mut()
|
||||
.for_each(|knot| *knot = (*knot - low) / (high - low) * chord);
|
||||
}
|
||||
}
|
||||
let points = curve.control_points();
|
||||
if points.len() < 2 || knots.is_empty() { return None; }
|
||||
let tolerance = if spline.knot_tolerance > 0.0 { spline.knot_tolerance } else { 1e-9 };
|
||||
let points = points.iter().map(|p| glam::DVec3::from_array(*p)).collect::<Vec<_>>();
|
||||
Some(nurbs_curve(curve.degree(), tolerance, &knots, curve.weights(), &points))
|
||||
if points.len() < 2 || knots.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let tolerance = if spline.knot_tolerance > 0.0 {
|
||||
spline.knot_tolerance
|
||||
} else {
|
||||
1e-9
|
||||
};
|
||||
let points = points
|
||||
.iter()
|
||||
.map(|p| glam::DVec3::from_array(*p))
|
||||
.collect::<Vec<_>>();
|
||||
Some(nurbs_curve(
|
||||
curve.degree(),
|
||||
tolerance,
|
||||
&knots,
|
||||
curve.weights(),
|
||||
&points,
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@@ -772,25 +817,48 @@ pub fn assoc_edge_curve(entity: &EntityType) -> Option<AssocCurve> {
|
||||
/// (0), degree, knot tolerance, then knots, weights (empty unless rational)
|
||||
/// and control points, each as its length, the length reserved (at least
|
||||
/// 8) and the growth step 8, followed by the items.
|
||||
fn nurbs_curve(degree: usize, tolerance: f64, knots: &[f64], weights: &[f64], points: &[glam::DVec3]) -> AssocCurve {
|
||||
fn nurbs_curve(
|
||||
degree: usize,
|
||||
tolerance: f64,
|
||||
knots: &[f64],
|
||||
weights: &[f64],
|
||||
points: &[glam::DVec3],
|
||||
) -> AssocCurve {
|
||||
use codec::objects::{AssocCurveValue as V, AssocSubcurveKind as K};
|
||||
let rational = weights.iter().any(|weight| (weight - 1.0).abs() > 1e-12);
|
||||
let header = |values: &mut Vec<V>, count: usize| {
|
||||
values.extend([V::Int(count as i32), V::Int(if count == 0 { 0 } else { count.max(8) } as i32), V::Int(8)]);
|
||||
values.extend([
|
||||
V::Int(count as i32),
|
||||
V::Int(if count == 0 { 0 } else { count.max(8) } as i32),
|
||||
V::Int(8),
|
||||
]);
|
||||
};
|
||||
let mut values = vec![V::Bool(rational), V::Bool(false), V::Int(degree as i32), V::Real(tolerance)];
|
||||
let mut values = vec![
|
||||
V::Bool(rational),
|
||||
V::Bool(false),
|
||||
V::Int(degree as i32),
|
||||
V::Real(tolerance),
|
||||
];
|
||||
header(&mut values, knots.len());
|
||||
values.extend(knots.iter().map(|knot| V::Real(*knot)));
|
||||
let weights = if rational { weights } else { &[] };
|
||||
header(&mut values, weights.len());
|
||||
values.extend(weights.iter().map(|weight| V::Real(*weight)));
|
||||
header(&mut values, points.len());
|
||||
values.extend(points.iter().map(|p| V::Point(codec::types::Vector3::new(p.x, p.y, p.z))));
|
||||
values.extend(
|
||||
points
|
||||
.iter()
|
||||
.map(|p| V::Point(codec::types::Vector3::new(p.x, p.y, p.z))),
|
||||
);
|
||||
(42, K::Nurb3d, values)
|
||||
}
|
||||
|
||||
/// An edge parameter's curve: kind, subcurve kind and values.
|
||||
pub type AssocCurve = (i32, codec::objects::AssocSubcurveKind, Vec<codec::objects::AssocCurveValue>);
|
||||
pub type AssocCurve = (
|
||||
i32,
|
||||
codec::objects::AssocSubcurveKind,
|
||||
Vec<codec::objects::AssocCurveValue>,
|
||||
);
|
||||
|
||||
/// A region's edges as an associative action reads them, loop by loop, each
|
||||
/// loop from its second edge on as the reference lists them: lines (23),
|
||||
@@ -799,23 +867,46 @@ pub type AssocCurve = (i32, codec::objects::AssocSubcurveKind, Vec<codec::object
|
||||
pub fn region_edge_curves(entity: &EntityType) -> Option<Vec<Vec<AssocCurve>>> {
|
||||
use codec::objects::{AssocCurveValue as V, AssocSubcurveKind as K};
|
||||
use kernel::geom2d::Curve;
|
||||
let EntityType::Region(_) = entity else { return None };
|
||||
let Some((plane, loops, true)) = super::presspull_model::profile_geometry(entity) else { return None };
|
||||
let EntityType::Region(_) = entity else {
|
||||
return None;
|
||||
};
|
||||
let Some((plane, loops, true)) = super::presspull_model::profile_geometry(entity) else {
|
||||
return None;
|
||||
};
|
||||
let at = |uv: [f64; 2]| glam::DVec3::from_array(plane.point_at(uv));
|
||||
let normal = glam::DVec3::from_array(plane.x_axis).cross(glam::DVec3::from_array(plane.y_axis)).try_normalize()?;
|
||||
let normal = glam::DVec3::from_array(plane.x_axis)
|
||||
.cross(glam::DVec3::from_array(plane.y_axis))
|
||||
.try_normalize()?;
|
||||
let axis = arbitrary_x(normal);
|
||||
let across = normal.cross(axis);
|
||||
let point = |p: glam::DVec3| V::Point(codec::types::Vector3::new(p.x, p.y, p.z));
|
||||
let arc = |centre: glam::DVec3, radius: f64, start: f64, end: f64| (11, K::Arc, vec![
|
||||
point(centre), point(normal), point(axis), V::Real(radius), V::Real(start), V::Real(end), V::Real(0.0),
|
||||
]);
|
||||
let arc = |centre: glam::DVec3, radius: f64, start: f64, end: f64| {
|
||||
(
|
||||
11,
|
||||
K::Arc,
|
||||
vec![
|
||||
point(centre),
|
||||
point(normal),
|
||||
point(axis),
|
||||
V::Real(radius),
|
||||
V::Real(start),
|
||||
V::Real(end),
|
||||
V::Real(0.0),
|
||||
],
|
||||
)
|
||||
};
|
||||
let curve = |piece: &Curve| -> Option<AssocCurve> {
|
||||
match piece {
|
||||
Curve::Line(line) => {
|
||||
let (a, b) = (at(line.start), at(line.end));
|
||||
Some((23, K::LineSegment3d, vec![point(a), point(b - a)]))
|
||||
}
|
||||
Curve::Circle(circle) => Some(arc(at(circle.centre), circle.radius, 0.0, std::f64::consts::TAU)),
|
||||
Curve::Circle(circle) => Some(arc(
|
||||
at(circle.centre),
|
||||
circle.radius,
|
||||
0.0,
|
||||
std::f64::consts::TAU,
|
||||
)),
|
||||
Curve::Arc(value) => {
|
||||
let centre = at(value.centre);
|
||||
let offset = at([
|
||||
@@ -828,22 +919,44 @@ pub fn region_edge_curves(entity: &EntityType) -> Option<Vec<Vec<AssocCurve>>> {
|
||||
Curve::Ellipse(value) => {
|
||||
let ellipse = value.ellipse;
|
||||
let centre = at(ellipse.centre);
|
||||
let tip = [ellipse.centre[0] + ellipse.major_axis[0], ellipse.centre[1] + ellipse.major_axis[1]];
|
||||
let tip = [
|
||||
ellipse.centre[0] + ellipse.major_axis[0],
|
||||
ellipse.centre[1] + ellipse.major_axis[1],
|
||||
];
|
||||
let major = (at(tip) - centre).try_normalize()?;
|
||||
let end = if value.end_parameter > value.start_parameter {
|
||||
value.end_parameter
|
||||
} else {
|
||||
value.end_parameter + std::f64::consts::TAU
|
||||
};
|
||||
Some((17, K::Ellipse, vec![
|
||||
point(centre), point(major), point(normal.cross(major)),
|
||||
V::Real(ellipse.major_radius), V::Real(ellipse.minor_radius),
|
||||
V::Real(value.start_parameter), V::Real(end), V::Real(0.0),
|
||||
]))
|
||||
Some((
|
||||
17,
|
||||
K::Ellipse,
|
||||
vec![
|
||||
point(centre),
|
||||
point(major),
|
||||
point(normal.cross(major)),
|
||||
V::Real(ellipse.major_radius),
|
||||
V::Real(ellipse.minor_radius),
|
||||
V::Real(value.start_parameter),
|
||||
V::Real(end),
|
||||
V::Real(0.0),
|
||||
],
|
||||
))
|
||||
}
|
||||
Curve::Nurbs(value) => {
|
||||
let points = value.control_points().iter().map(|p| at(*p)).collect::<Vec<_>>();
|
||||
Some(nurbs_curve(value.degree(), 1e-9, value.knots(), value.weights(), &points))
|
||||
let points = value
|
||||
.control_points()
|
||||
.iter()
|
||||
.map(|p| at(*p))
|
||||
.collect::<Vec<_>>();
|
||||
Some(nurbs_curve(
|
||||
value.degree(),
|
||||
1e-9,
|
||||
value.knots(),
|
||||
value.weights(),
|
||||
&points,
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@@ -868,7 +981,11 @@ pub fn assoc_source_supported(entity: &EntityType) -> bool {
|
||||
|
||||
/// The arbitrary-axis X direction of an entity normal.
|
||||
pub(crate) fn arbitrary_x(normal: glam::DVec3) -> glam::DVec3 {
|
||||
let world = if normal.x.abs() < 1.0 / 64.0 && normal.y.abs() < 1.0 / 64.0 { glam::DVec3::Y } else { glam::DVec3::Z };
|
||||
let world = if normal.x.abs() < 1.0 / 64.0 && normal.y.abs() < 1.0 / 64.0 {
|
||||
glam::DVec3::Y
|
||||
} else {
|
||||
glam::DVec3::Z
|
||||
};
|
||||
world.cross(normal).normalize()
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@ use kernel::geom2d::{Arc, Curve, EllipseArc, Line};
|
||||
use kernel::space::{PlanarCurve, Plane, Vec3};
|
||||
|
||||
pub use super::sweep_command_model::{
|
||||
is_sweep_path, is_sweep_profile, scaled_sweep_path_refusal, spatial_profile, spatial_sweep_record, surface_sweep_record, sweep_expressions, link_sweep_expressions, link_sweep_sources, sweep_sources, SWEEP_SOURCES_APP, resweep_from_sources, surface_associativity, set_surface_associativity, assoc_edge_curve, assoc_source_supported, region_edge_curves, AssocCurve, sweep_record, sweep_refusal_code, sweep_selection_options, swept_surface_entity, swept_with_options,
|
||||
assoc_edge_curve, assoc_source_supported, is_sweep_path, is_sweep_profile,
|
||||
link_sweep_expressions, link_sweep_sources, region_edge_curves, resweep_from_sources,
|
||||
scaled_sweep_path_refusal, set_surface_associativity, spatial_profile, spatial_sweep_record,
|
||||
surface_associativity, surface_sweep_record, sweep_expressions, sweep_record,
|
||||
sweep_refusal_code, sweep_selection_options, sweep_sources, swept_surface_entity,
|
||||
swept_with_options, AssocCurve, SWEEP_SOURCES_APP,
|
||||
};
|
||||
use crate::entities::curve::entity_curve;
|
||||
|
||||
@@ -68,12 +73,16 @@ pub fn extrusion_profile_of(entity: &EntityType) -> Option<(Profile, bool)> {
|
||||
/// edges (its display wires hold one wire per edge), a lone closed edge split
|
||||
/// as a closed curve is.
|
||||
fn region_loop(entity: &EntityType) -> Option<Profile> {
|
||||
let EntityType::Region(region) = entity else { return None };
|
||||
let EntityType::Region(region) = entity else {
|
||||
return None;
|
||||
};
|
||||
if !region.acis_data.has_data() {
|
||||
return None;
|
||||
}
|
||||
let (plane, loops, _) = super::presspull_model::profile_geometry(entity)?;
|
||||
let [boundary] = loops.as_slice() else { return None };
|
||||
let [boundary] = loops.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
let single = boundary.len() == 1;
|
||||
let pieces = boundary
|
||||
.iter()
|
||||
@@ -85,7 +94,8 @@ fn region_loop(entity: &EntityType) -> Option<Profile> {
|
||||
|
||||
/// Whether a region has more than one boundary loop.
|
||||
fn region_with_holes(entity: &EntityType) -> bool {
|
||||
matches!(entity, EntityType::Region(_)) && region_profiles(entity).is_some_and(|(_, loops)| loops.len() > 1)
|
||||
matches!(entity, EntityType::Region(_))
|
||||
&& region_profiles(entity).is_some_and(|(_, loops)| loops.len() > 1)
|
||||
}
|
||||
|
||||
fn profile_pieces(curve: &Curve, closed: bool) -> Option<Vec<Curve>> {
|
||||
@@ -665,12 +675,16 @@ pub fn embedded_path(entity: &EntityType) -> Option<EmbeddedEntity> {
|
||||
/// polyline in its own coordinate system about the region's normal, read
|
||||
/// from the region's modeler edges.
|
||||
pub fn region_boundary_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f64; 16])> {
|
||||
let EntityType::Region(region) = entity else { return None };
|
||||
let EntityType::Region(region) = entity else {
|
||||
return None;
|
||||
};
|
||||
if !region.acis_data.has_data() {
|
||||
return None;
|
||||
}
|
||||
let (plane, loops, _) = super::presspull_model::profile_geometry(entity)?;
|
||||
let [boundary] = loops.as_slice() else { return None };
|
||||
let [boundary] = loops.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
let world = |v: [f64; 3]| Vector3::new(v[0], v[1], v[2]);
|
||||
let normal = world(plane.normal()?);
|
||||
let identity = glam::DMat4::IDENTITY.to_cols_array();
|
||||
@@ -715,18 +729,31 @@ pub fn region_boundary_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f
|
||||
}
|
||||
// Walk the loop head to tail; an arc run against its direction turns
|
||||
// clockwise (negative bulge).
|
||||
let close = |a: [f64; 2], b: [f64; 2]| (a[0] - b[0]).hypot(a[1] - b[1]) <= 1e-9 * (1.0 + a[0].abs().max(a[1].abs()));
|
||||
let close = |a: [f64; 2], b: [f64; 2]| {
|
||||
(a[0] - b[0]).hypot(a[1] - b[1]) <= 1e-9 * (1.0 + a[0].abs().max(a[1].abs()))
|
||||
};
|
||||
let ends = |piece: &Curve| match piece {
|
||||
Curve::Line(line) => Some((line.start, line.end, 0.0)),
|
||||
Curve::Arc(arc) => {
|
||||
let at = |angle: f64| [arc.centre[0] + arc.radius * angle.cos(), arc.centre[1] + arc.radius * angle.sin()];
|
||||
Some((at(arc.start_angle), at(arc.end_angle), (arc.sweep() / 4.0).tan()))
|
||||
let at = |angle: f64| {
|
||||
[
|
||||
arc.centre[0] + arc.radius * angle.cos(),
|
||||
arc.centre[1] + arc.radius * angle.sin(),
|
||||
]
|
||||
};
|
||||
Some((
|
||||
at(arc.start_angle),
|
||||
at(arc.end_angle),
|
||||
(arc.sweep() / 4.0).tan(),
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let (first_start, first_end, _) = ends(boundary.first()?)?;
|
||||
let second = boundary.get(1).and_then(ends);
|
||||
let mut at = if second.is_some_and(|(start, end, _)| close(first_start, start) || close(first_start, end)) {
|
||||
let mut at = if second
|
||||
.is_some_and(|(start, end, _)| close(first_start, start) || close(first_start, end))
|
||||
{
|
||||
first_end
|
||||
} else {
|
||||
first_start
|
||||
@@ -740,7 +767,13 @@ pub fn region_boundary_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f
|
||||
polyline.elevation = glam::DVec3::from_array(plane.origin).dot(axis_normal);
|
||||
for piece in boundary {
|
||||
let (start, end, bulge) = ends(piece)?;
|
||||
let (from, to, bulge) = if close(start, at) { (start, end, bulge) } else if close(end, at) { (end, start, -bulge) } else { return None };
|
||||
let (from, to, bulge) = if close(start, at) {
|
||||
(start, end, bulge)
|
||||
} else if close(end, at) {
|
||||
(end, start, -bulge)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let point = glam::DVec3::from_array(plane.point_at(from));
|
||||
let mut vertex = LwVertex::new(Vector2::new(point.dot(axis_x), point.dot(axis_y)));
|
||||
vertex.bulge = bulge;
|
||||
|
||||
+218
-60
@@ -1513,32 +1513,51 @@ impl Scene {
|
||||
/// their sources: made again from them with the values they were made
|
||||
/// with. A surface edited along with its sources keeps its own edit.
|
||||
/// Returns the surfaces rebuilt.
|
||||
pub(crate) fn refresh_associative_surfaces(&mut self, changes: &[(Handle, crate::scene::ChangeKind)]) -> Vec<(Handle, crate::scene::ChangeKind)> {
|
||||
use crate::scene::model::surface_sources::{axis_points, surface_sources, SurfaceSources, SURFACE_SOURCES_APP};
|
||||
pub(crate) fn refresh_associative_surfaces(
|
||||
&mut self,
|
||||
changes: &[(Handle, crate::scene::ChangeKind)],
|
||||
) -> Vec<(Handle, crate::scene::ChangeKind)> {
|
||||
use crate::scene::model::surface_sources::{
|
||||
axis_points, surface_sources, SurfaceSources, SURFACE_SOURCES_APP,
|
||||
};
|
||||
use crate::scene::model::sweep_model;
|
||||
use codec::objects::SolidHistoryOperation;
|
||||
let changed = changes.iter()
|
||||
let changed = changes
|
||||
.iter()
|
||||
.filter(|(_, kind)| !matches!(kind, crate::scene::ChangeKind::Removed))
|
||||
.map(|(handle, _)| *handle)
|
||||
.collect::<rustc_hash::FxHashSet<_>>();
|
||||
if changed.is_empty() || !self.document.app_ids.contains(SURFACE_SOURCES_APP) {
|
||||
return Vec::new();
|
||||
}
|
||||
let linked = self.document.entities()
|
||||
let linked = self
|
||||
.document
|
||||
.entities()
|
||||
.filter(|entity| matches!(entity, EntityType::Surface(_)))
|
||||
.filter_map(|entity| {
|
||||
let handle = entity.common().handle;
|
||||
let sources = surface_sources(entity)?;
|
||||
(!changed.contains(&handle) && sources.handles().iter().any(|source| changed.contains(source)))
|
||||
.then_some((handle, sources))
|
||||
(!changed.contains(&handle)
|
||||
&& sources
|
||||
.handles()
|
||||
.iter()
|
||||
.any(|source| changed.contains(source)))
|
||||
.then_some((handle, sources))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut rebuilt = Vec::new();
|
||||
for (handle, sources) in linked {
|
||||
let source = |handle: Handle| self.document.get_entity(handle).cloned();
|
||||
// The surface made again: a history step, or a body with its data.
|
||||
let made: Option<Result<SolidHistoryOperation, (kernel::brep::Body, codec::entities::SurfaceData)>> = match &sources {
|
||||
SurfaceSources::Extrude { profile, height, direction, taper } => (|| {
|
||||
let made: Option<
|
||||
Result<SolidHistoryOperation, (kernel::brep::Body, codec::entities::SurfaceData)>,
|
||||
> = match &sources {
|
||||
SurfaceSources::Extrude {
|
||||
profile,
|
||||
height,
|
||||
direction,
|
||||
taper,
|
||||
} => (|| {
|
||||
let entity = source(*profile)?;
|
||||
let (shape, _) = sweep_model::extrusion_profile_of(&entity)?;
|
||||
let direction = match direction {
|
||||
@@ -1546,37 +1565,86 @@ impl Scene {
|
||||
None => glam::DVec3::from_array(shape.plane.normal()?) * *height,
|
||||
};
|
||||
if self.document.solid_history_operations(handle).is_some() {
|
||||
return sweep_model::extrusion_history(&entity, None, direction.to_array(), *taper, shape.plane.origin).map(Ok);
|
||||
return sweep_model::extrusion_history(
|
||||
&entity,
|
||||
None,
|
||||
direction.to_array(),
|
||||
*taper,
|
||||
shape.plane.origin,
|
||||
)
|
||||
.map(Ok);
|
||||
}
|
||||
let body = sweep_model::extruded_surface(&entity, direction.to_array(), *taper)?;
|
||||
let EntityType::Surface(fresh) = crate::modules::insert::solid3d_cmds::empty_extruded_surface(direction, *taper) else { return None };
|
||||
let body =
|
||||
sweep_model::extruded_surface(&entity, direction.to_array(), *taper)?;
|
||||
let EntityType::Surface(fresh) =
|
||||
crate::modules::insert::solid3d_cmds::empty_extruded_surface(
|
||||
direction, *taper,
|
||||
)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
Some(Err((body, fresh.surface_data)))
|
||||
})(),
|
||||
SurfaceSources::Revolve { profile, axis, start, end, angle, start_angle } => (|| {
|
||||
SurfaceSources::Revolve {
|
||||
profile,
|
||||
axis,
|
||||
start,
|
||||
end,
|
||||
angle,
|
||||
start_angle,
|
||||
} => (|| {
|
||||
let entity = source(*profile)?;
|
||||
let (start, end) = axis
|
||||
.and_then(|axis| self.document.get_entity(axis).and_then(axis_points))
|
||||
.unwrap_or((*start, *end));
|
||||
let body = sweep_model::revolved_surface(&entity, start.to_array(), end.to_array(), *angle, *start_angle)?;
|
||||
let EntityType::Surface(fresh) = crate::modules::insert::solid3d_cmds::empty_revolved_surface(&entity, start, end, *angle, *start_angle) else { return None };
|
||||
let body = sweep_model::revolved_surface(
|
||||
&entity,
|
||||
start.to_array(),
|
||||
end.to_array(),
|
||||
*angle,
|
||||
*start_angle,
|
||||
)?;
|
||||
let EntityType::Surface(fresh) =
|
||||
crate::modules::insert::solid3d_cmds::empty_revolved_surface(
|
||||
&entity,
|
||||
start,
|
||||
end,
|
||||
*angle,
|
||||
*start_angle,
|
||||
)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
Some(Err((body, fresh.surface_data)))
|
||||
})(),
|
||||
SurfaceSources::Loft { sections } => (|| {
|
||||
let operations = self.document.solid_history_operations(handle)?;
|
||||
let mut record = operations.into_iter().find_map(|operation| match operation {
|
||||
SolidHistoryOperation::Loft(record) => Some(record),
|
||||
_ => None,
|
||||
})?;
|
||||
let mut record =
|
||||
operations
|
||||
.into_iter()
|
||||
.find_map(|operation| match operation {
|
||||
SolidHistoryOperation::Loft(record) => Some(record),
|
||||
_ => None,
|
||||
})?;
|
||||
if record.cross_sections.len() != sections.len() {
|
||||
return None;
|
||||
}
|
||||
record.cross_sections = sections.iter()
|
||||
.map(|section| source(*section).as_ref().and_then(crate::scene::model::loft_command_model::embedded_section))
|
||||
record.cross_sections = sections
|
||||
.iter()
|
||||
.map(|section| {
|
||||
source(*section)
|
||||
.as_ref()
|
||||
.and_then(crate::scene::model::loft_command_model::embedded_section)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
Some(Ok(SolidHistoryOperation::Loft(record)))
|
||||
})(),
|
||||
};
|
||||
let before = self.document.get_entity(handle).cloned().map(std::sync::Arc::new);
|
||||
let before = self
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.cloned()
|
||||
.map(std::sync::Arc::new);
|
||||
match made {
|
||||
Some(Ok(operation)) => {
|
||||
self.record_undo_before(handle, before);
|
||||
@@ -1585,11 +1653,18 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
Some(Err((body, data))) => {
|
||||
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&body) else { continue };
|
||||
let Some(display) = self.prepare_solid_model_display(handle, &body) else { continue };
|
||||
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&body)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(display) = self.prepare_solid_model_display(handle, &body) else {
|
||||
continue;
|
||||
};
|
||||
self.record_undo_before(handle, before);
|
||||
if let Some(EntityType::Surface(surface)) = self.document.get_entity_mut(handle) {
|
||||
surface.acis_data = codec::entities::AcisData::from_sat(&document.to_sat_string());
|
||||
if let Some(EntityType::Surface(surface)) = self.document.get_entity_mut(handle)
|
||||
{
|
||||
surface.acis_data =
|
||||
codec::entities::AcisData::from_sat(&document.to_sat_string());
|
||||
surface.surface_data = data;
|
||||
}
|
||||
self.register_prepared_solid_model(handle, body, display);
|
||||
@@ -1614,42 +1689,90 @@ impl Scene {
|
||||
let mut sources = Vec::new();
|
||||
let mut others = Vec::new();
|
||||
for item in self.document.objects.values() {
|
||||
let ObjectType::Associative(action) = item else { continue };
|
||||
let AssociativeData::Action(action) = &action.data else { continue };
|
||||
let Some(AssociativeData::SurfaceActionBody(body)) = object(action.action_body) else { continue };
|
||||
let Some(AssociativeData::Dependency(written)) = object(body.surface_body.dependency) else { continue };
|
||||
let ObjectType::Associative(action) = item else {
|
||||
continue;
|
||||
};
|
||||
let AssociativeData::Action(action) = &action.data else {
|
||||
continue;
|
||||
};
|
||||
let Some(AssociativeData::SurfaceActionBody(body)) = object(action.action_body) else {
|
||||
continue;
|
||||
};
|
||||
let Some(AssociativeData::Dependency(written)) = object(body.surface_body.dependency)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
// The entities a named path parameter reads (an edge of a region
|
||||
// reads through a geometry dependency).
|
||||
let read = |name: &str| -> Vec<Handle> {
|
||||
action.owned_parameters.iter().filter_map(|parameter| {
|
||||
let Some(AssociativeData::PathActionParam(path)) = object(*parameter) else { return None };
|
||||
if path.compound.action_param.name != name { return None; }
|
||||
let Some(AssociativeData::EdgeActionParam(edge)) = object(*path.compound.parameters.first()?) else { return None };
|
||||
match object(edge.single_dependency.dependency) {
|
||||
Some(AssociativeData::Dependency(dependency)) => Some(dependency.dependent_on),
|
||||
Some(AssociativeData::GeomDependency(dependency)) => Some(dependency.dependency.dependent_on),
|
||||
action
|
||||
.owned_parameters
|
||||
.iter()
|
||||
.filter_map(|parameter| {
|
||||
let Some(AssociativeData::PathActionParam(path)) = object(*parameter)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if path.compound.action_param.name != name {
|
||||
return None;
|
||||
}
|
||||
let Some(AssociativeData::EdgeActionParam(edge)) =
|
||||
object(*path.compound.parameters.first()?)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
match object(edge.single_dependency.dependency) {
|
||||
Some(AssociativeData::Dependency(dependency)) => {
|
||||
Some(dependency.dependent_on)
|
||||
}
|
||||
Some(AssociativeData::GeomDependency(dependency)) => {
|
||||
Some(dependency.dependency.dependent_on)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
let real = |name: &str| {
|
||||
action
|
||||
.values
|
||||
.iter()
|
||||
.find(|value| value.name == name)
|
||||
.and_then(|value| value.variables.first())
|
||||
.and_then(|variable| match variable.value.value {
|
||||
codec::objects::AssocEvalValue::Real(value) => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}).collect()
|
||||
})
|
||||
};
|
||||
let real = |name: &str| action.values.iter().find(|value| value.name == name)
|
||||
.and_then(|value| value.variables.first())
|
||||
.and_then(|variable| match variable.value.value {
|
||||
codec::objects::AssocEvalValue::Real(value) => Some(value),
|
||||
_ => None,
|
||||
});
|
||||
match body.kind {
|
||||
AssocSurfaceActionKind::Extruded => {
|
||||
if let (Some(profile), Some(height), Some(taper)) =
|
||||
(read("ExtrudeProfile").first().copied(), real("ExtrusionHeight"), real("ExtrusionTaperAngle"))
|
||||
{
|
||||
others.push((written.dependent_on, (body.kind, vec![profile], None, height, taper)));
|
||||
if let (Some(profile), Some(height), Some(taper)) = (
|
||||
read("ExtrudeProfile").first().copied(),
|
||||
real("ExtrusionHeight"),
|
||||
real("ExtrusionTaperAngle"),
|
||||
) {
|
||||
others.push((
|
||||
written.dependent_on,
|
||||
(body.kind, vec![profile], None, height, taper),
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
AssocSurfaceActionKind::Revolved => {
|
||||
if let (Some(profile), Some(angle)) = (read("RevolveProfile").first().copied(), real("RevolveAngle")) {
|
||||
others.push((written.dependent_on, (body.kind, vec![profile], read("RevolvePath").first().copied(), angle, 0.0)));
|
||||
if let (Some(profile), Some(angle)) = (
|
||||
read("RevolveProfile").first().copied(),
|
||||
real("RevolveAngle"),
|
||||
) {
|
||||
others.push((
|
||||
written.dependent_on,
|
||||
(
|
||||
body.kind,
|
||||
vec![profile],
|
||||
read("RevolvePath").first().copied(),
|
||||
angle,
|
||||
0.0,
|
||||
),
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -1692,7 +1815,10 @@ impl Scene {
|
||||
links.push((written.dependent_on, expressions));
|
||||
}
|
||||
// The profile and path the action reads.
|
||||
if let (Some(profile), Some(path)) = (read("SweepProfile").first().copied(), read("SweepPath").first().copied()) {
|
||||
if let (Some(profile), Some(path)) = (
|
||||
read("SweepProfile").first().copied(),
|
||||
read("SweepPath").first().copied(),
|
||||
) {
|
||||
sources.push((written.dependent_on, profile, path));
|
||||
}
|
||||
}
|
||||
@@ -1735,27 +1861,59 @@ impl Scene {
|
||||
}
|
||||
// Associative extruded, revolved and lofted surfaces follow their
|
||||
// sources here too.
|
||||
use crate::scene::model::surface_sources::{link_surface_sources, surface_sources, SurfaceSources};
|
||||
use crate::scene::model::surface_sources::{
|
||||
link_surface_sources, surface_sources, SurfaceSources,
|
||||
};
|
||||
for (surface, (kind, handles, axis, amount, taper)) in others {
|
||||
let Some(entity @ EntityType::Surface(value)) = self.document.get_entity(surface) else { continue };
|
||||
if surface_sources(entity).is_some() { continue; }
|
||||
let Some(entity @ EntityType::Surface(value)) = self.document.get_entity(surface)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if surface_sources(entity).is_some() {
|
||||
continue;
|
||||
}
|
||||
let linked = match (kind, &value.surface_data) {
|
||||
(AssocSurfaceActionKind::Extruded, codec::entities::SurfaceData::Extruded { sweep_vector, .. }) => {
|
||||
(
|
||||
AssocSurfaceActionKind::Extruded,
|
||||
codec::entities::SurfaceData::Extruded { sweep_vector, .. },
|
||||
) => {
|
||||
let profile = handles[0];
|
||||
let along = glam::DVec3::new(sweep_vector.x, sweep_vector.y, sweep_vector.z);
|
||||
// A direction other than the profile's normal is kept.
|
||||
let normal = self.document.get_entity(profile)
|
||||
.and_then(|entity| crate::scene::model::sweep_model::extrusion_profile_of(entity))
|
||||
let normal = self
|
||||
.document
|
||||
.get_entity(profile)
|
||||
.and_then(|entity| {
|
||||
crate::scene::model::sweep_model::extrusion_profile_of(entity)
|
||||
})
|
||||
.and_then(|(shape, _)| shape.plane.normal())
|
||||
.map(glam::DVec3::from_array);
|
||||
let direction = match normal {
|
||||
Some(normal) if (normal * amount - along).length() <= 1e-9 * amount.abs().max(1.0) => None,
|
||||
Some(normal)
|
||||
if (normal * amount - along).length()
|
||||
<= 1e-9 * amount.abs().max(1.0) =>
|
||||
{
|
||||
None
|
||||
}
|
||||
_ if along.length() > 1e-12 => Some(along),
|
||||
_ => None,
|
||||
};
|
||||
SurfaceSources::Extrude { profile, height: amount, direction, taper }
|
||||
SurfaceSources::Extrude {
|
||||
profile,
|
||||
height: amount,
|
||||
direction,
|
||||
taper,
|
||||
}
|
||||
}
|
||||
(AssocSurfaceActionKind::Revolved, codec::entities::SurfaceData::Revolved { axis_point, axis_vector, start_angle, .. }) => {
|
||||
(
|
||||
AssocSurfaceActionKind::Revolved,
|
||||
codec::entities::SurfaceData::Revolved {
|
||||
axis_point,
|
||||
axis_vector,
|
||||
start_angle,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
let start = glam::DVec3::new(axis_point.x, axis_point.y, axis_point.z);
|
||||
SurfaceSources::Revolve {
|
||||
profile: handles[0],
|
||||
|
||||
+4
-1
@@ -604,7 +604,10 @@ impl Scene {
|
||||
_ => model.clone(),
|
||||
};
|
||||
let style = self.render_style(entity);
|
||||
if matches!(hatch.pattern, model::hatch_model::HatchPattern::Gradient { .. }) {
|
||||
if matches!(
|
||||
hatch.pattern,
|
||||
model::hatch_model::HatchPattern::Gradient { .. }
|
||||
) {
|
||||
hatch.set_alpha(style.0[3]);
|
||||
} else {
|
||||
hatch.color = style.0;
|
||||
|
||||
@@ -3800,10 +3800,13 @@ analytic={:.1} regular={:.1} blocks={:.1}",
|
||||
.iter()
|
||||
.filter(|hatch| hatch.boundary.len() >= 3)
|
||||
.count();
|
||||
let (translucent, opaque): (Vec<HatchModel>, Vec<HatchModel>) =
|
||||
hatches.iter().cloned().partition(|hatch| hatch.color[3] < 0.999);
|
||||
let (translucent, opaque): (Vec<HatchModel>, Vec<HatchModel>) = hatches
|
||||
.iter()
|
||||
.cloned()
|
||||
.partition(|hatch| hatch.color[3] < 0.999);
|
||||
self.hatch_gpu.upload(device, queue, &opaque);
|
||||
self.hatch_translucent_gpu.upload(device, queue, &translucent);
|
||||
self.hatch_translucent_gpu
|
||||
.upload(device, queue, &translucent);
|
||||
if let Some(started) = perf_started {
|
||||
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
||||
if elapsed_ms >= 1.0 {
|
||||
@@ -4853,8 +4856,11 @@ analytic={:.1} regular={:.1} blocks={:.1}",
|
||||
// Transparent hatches blend over the lines already drawn; the
|
||||
// lines in front of them still win the depth test.
|
||||
if !self.skip_hatch_frame {
|
||||
self.hatch_translucent_gpu
|
||||
.draw(&mut pass, &self.uniform_bind_group, stencil_ref);
|
||||
self.hatch_translucent_gpu.draw(
|
||||
&mut pass,
|
||||
&self.uniform_bind_group,
|
||||
stencil_ref,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5656,10 +5662,7 @@ impl MultiPipeline {
|
||||
total.wire_arena += slot.wire_arena;
|
||||
total.block_geometry +=
|
||||
wire_gpu::block_arena_bytes(&inner.gpu_block_wires, &mut counted)
|
||||
+ wire_gpu::block_arena_bytes(
|
||||
&inner.gpu_selected_block_wires,
|
||||
&mut counted,
|
||||
);
|
||||
+ wire_gpu::block_arena_bytes(&inner.gpu_selected_block_wires, &mut counted);
|
||||
}
|
||||
for entry in self.wire_buffer_cache.values() {
|
||||
total.block_geometry += wire_gpu::block_arena_bytes(&entry.1, &mut counted);
|
||||
|
||||
@@ -508,9 +508,7 @@ pub fn block_const_bind_group_layout(
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: std::num::NonZeroU64::new(
|
||||
std::mem::size_of::<WireConst>() as u64,
|
||||
),
|
||||
min_binding_size: std::num::NonZeroU64::new(std::mem::size_of::<WireConst>() as u64),
|
||||
},
|
||||
count: None,
|
||||
}];
|
||||
@@ -1155,10 +1153,7 @@ pub struct BlockWireTarget<'a> {
|
||||
/// Device bytes the arenas behind these batches hold, counting each arena once
|
||||
/// however many batches draw out of it. `seen` carries the arenas already
|
||||
/// counted, so slots that share a cached build are not counted twice.
|
||||
pub fn block_arena_bytes(
|
||||
batches: &[BlockWireGpu],
|
||||
seen: &mut rustc_hash::FxHashSet<usize>,
|
||||
) -> u64 {
|
||||
pub fn block_arena_bytes(batches: &[BlockWireGpu], seen: &mut rustc_hash::FxHashSet<usize>) -> u64 {
|
||||
batches
|
||||
.iter()
|
||||
.filter(|batch| seen.insert(std::sync::Arc::as_ptr(&batch.arena) as usize))
|
||||
@@ -1209,8 +1204,7 @@ impl BlockWireGpu {
|
||||
let mut groups: Vec<(bool, Vec<BlockRef<'_>>)> = Vec::new();
|
||||
for &block in wires {
|
||||
let wire = block.wire;
|
||||
if color_override.is_none()
|
||||
&& !(wire.display_visible && block.named().display_visible)
|
||||
if color_override.is_none() && !(wire.display_visible && block.named().display_visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1279,8 +1273,7 @@ impl BlockWireGpu {
|
||||
})
|
||||
.collect();
|
||||
emitted_segments += records.len();
|
||||
let built =
|
||||
std::sync::Arc::new(BlockGeometryRecords { constant, records });
|
||||
let built = std::sync::Arc::new(BlockGeometryRecords { constant, records });
|
||||
// Records are device-independent, so a build the device
|
||||
// rejected poisons nothing: the next one packs these same
|
||||
// records into fresh buffers instead of caching a buffer
|
||||
@@ -1306,7 +1299,11 @@ impl BlockWireGpu {
|
||||
crate::perf_record!(
|
||||
"[perf] block-wire-geometry mode={} definitions={} arenas={} batches={} \
|
||||
segments={} bytes={} bytes_if_per_definition={} emitted={}",
|
||||
if mode.uses_storage() { "storage" } else { "packed" },
|
||||
if mode.uses_storage() {
|
||||
"storage"
|
||||
} else {
|
||||
"packed"
|
||||
},
|
||||
live_keys.len(),
|
||||
arena.arenas,
|
||||
out.len(),
|
||||
|
||||
@@ -663,7 +663,9 @@ pub(crate) fn dimension_picture_placement(
|
||||
/// placed picture has its placement folded into the subs and the dimension's
|
||||
/// own placement cleared (`clear_dim_picture_placement`); a placement left on
|
||||
/// the dimension would be applied again on top of the moved subs.
|
||||
pub(crate) fn dim_picture_placement(entity: &EntityType) -> Option<crate::command::EntityTransform> {
|
||||
pub(crate) fn dim_picture_placement(
|
||||
entity: &EntityType,
|
||||
) -> Option<crate::command::EntityTransform> {
|
||||
let EntityType::Dimension(d) = entity else {
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -71,7 +71,11 @@ impl Scene {
|
||||
)
|
||||
)
|
||||
};
|
||||
if let Some(handle) = self.selected_handles_in_order().into_iter().find(is_section) {
|
||||
if let Some(handle) = self
|
||||
.selected_handles_in_order()
|
||||
.into_iter()
|
||||
.find(is_section)
|
||||
{
|
||||
return Some(handle);
|
||||
}
|
||||
let block = self.current_layout_block_handle();
|
||||
@@ -102,25 +106,15 @@ impl Scene {
|
||||
let codec::entities::ExtendedEntityData::SectionObject(data) = &extended.data else {
|
||||
return None;
|
||||
};
|
||||
let slice_depth = crate::entities::extended::section_is_slice(extended).then(|| {
|
||||
crate::entities::extended::section_slice_depth(extended).unwrap_or(0.0)
|
||||
});
|
||||
let slice_depth = crate::entities::extended::section_is_slice(extended)
|
||||
.then(|| crate::entities::extended::section_slice_depth(extended).unwrap_or(0.0));
|
||||
let planes = Self::section_planes(data, slice_depth, None)?;
|
||||
|
||||
// Every drawn point of every object in the space, from its wires and
|
||||
// its shaded mesh.
|
||||
let block = self.current_layout_block_handle();
|
||||
let mut points: FxHashMap<Handle, Vec<[f64; 3]>> = FxHashMap::default();
|
||||
let wires = self.wires_for_block_culled(
|
||||
block,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
);
|
||||
let wires = self.wires_for_block_culled(block, None, None, None, None, None, true, None);
|
||||
for wire in &wires {
|
||||
let Some(handle) = Self::handle_from_wire_name(&wire.name) else {
|
||||
continue;
|
||||
|
||||
@@ -129,8 +129,8 @@ impl Scene {
|
||||
.iter()
|
||||
.flat_map(|&index| [index, (index + 1) % count])
|
||||
.collect();
|
||||
let Some(plane) = crate::entities::curve::lwpolyline_curve(polyline)
|
||||
.map(|curve| curve.plane)
|
||||
let Some(plane) =
|
||||
crate::entities::curve::lwpolyline_curve(polyline).map(|curve| curve.plane)
|
||||
else {
|
||||
whole.push(handle);
|
||||
continue;
|
||||
|
||||
@@ -66,7 +66,6 @@ fn fonts() -> &'static SysFonts {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/// Sorted, de-duplicated family names of every face currently in `db`.
|
||||
fn family_names(db: &fontdb::Database) -> Vec<String> {
|
||||
let mut families: Vec<String> = db
|
||||
@@ -446,14 +445,19 @@ mod tests {
|
||||
/// and whether it comes as a bare name or as a path.
|
||||
#[test]
|
||||
fn face_files_resolve_to_their_family() {
|
||||
let found = fonts().db.lock().unwrap().faces().find_map(|face| match &face.source {
|
||||
fontdb::Source::File(path) => {
|
||||
let file = path.file_name()?.to_string_lossy().into_owned();
|
||||
let family = face.families.first().map(|(name, _)| name.clone())?;
|
||||
Some((file, family))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let found = fonts()
|
||||
.db
|
||||
.lock()
|
||||
.unwrap()
|
||||
.faces()
|
||||
.find_map(|face| match &face.source {
|
||||
fontdb::Source::File(path) => {
|
||||
let file = path.file_name()?.to_string_lossy().into_owned();
|
||||
let family = face.families.first().map(|(name, _)| name.clone())?;
|
||||
Some((file, family))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let Some((file, family)) = found else {
|
||||
eprintln!("no installed face files on this host; skipping");
|
||||
return;
|
||||
|
||||
@@ -2044,7 +2044,8 @@ text_atlas={:.1} wire_arena={:.1} block_geometry={:.1}",
|
||||
impl Scene {
|
||||
/// The model-space viewport record of the active tile.
|
||||
pub(crate) fn active_tile_vport_handle(&self) -> Option<Handle> {
|
||||
self.model_tile_vport(self.active_model_tile.get()).map(|value| value.handle)
|
||||
self.model_tile_vport(self.active_model_tile.get())
|
||||
.map(|value| value.handle)
|
||||
}
|
||||
|
||||
fn model_tile_vport(&self, index: usize) -> Option<&codec::tables::VPort> {
|
||||
@@ -2562,7 +2563,12 @@ impl Scene {
|
||||
if let Some(EntityType::Viewport(value)) = self.document.get_entity(handle) {
|
||||
// The sheet keeps its page and desk: a paper-space view's
|
||||
// background is kept with the layout but not drawn.
|
||||
return build(value.visual_style_handle, value.brightness, value.contrast, Handle::NULL);
|
||||
return build(
|
||||
value.visual_style_handle,
|
||||
value.brightness,
|
||||
value.contrast,
|
||||
Handle::NULL,
|
||||
);
|
||||
}
|
||||
} else if viewport.handle.is_valid() {
|
||||
if let Some(EntityType::Viewport(value)) = self.document.get_entity(viewport.handle) {
|
||||
@@ -2571,7 +2577,11 @@ impl Scene {
|
||||
value.visual_style_handle,
|
||||
value.brightness,
|
||||
value.contrast,
|
||||
if flat { Handle::NULL } else { value.background_handle },
|
||||
if flat {
|
||||
Handle::NULL
|
||||
} else {
|
||||
value.background_handle
|
||||
},
|
||||
);
|
||||
}
|
||||
} else if self.current_layout == "Model" {
|
||||
@@ -2585,7 +2595,11 @@ impl Scene {
|
||||
value.visual_style_handle,
|
||||
value.brightness,
|
||||
value.contrast,
|
||||
if flat { Handle::NULL } else { value.background_handle },
|
||||
if flat {
|
||||
Handle::NULL
|
||||
} else {
|
||||
value.background_handle
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user