forked from 1b-agent/opencadstudio-upstream
Fix IMPORT naming, messages and 3D Studio limits
STEP assemblies: a link that closes a loop in the assembly is dropped, so no block inserts itself; the walk is iterative. Each imported block gets a name the drawing does not use yet instead of silently reusing an existing definition, inserts are pointed at the names the blocks got, and an insert of a block that could not be defined is left out with the error reported. Names that come from files (STEP products, 3D Studio objects, materials and cameras, WMF font faces) go through a new shared scene::sanitized_table_name, which applies the block-name rules (reserved and control characters, 255 characters). 3D Studio: meshes with more vertices than a polyface mesh can index are split over several meshes. Single-layer mode uses the current layer. Materials are only created when the drawing has a material dictionary. Camera views get the current view's proportions and the perspective flag. Messages show paths as given, the file-not-found and read-failure messages are plain text of our own, and the "Press Esc" line is gone since the import cannot be cancelled. The true-colour packing is shared. "Select ACIS File" and "Import WMF" get catalog entries and keys in all 22 locales. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
dbc428b237
commit
a0ed20df28
28 files changed
+205
-88
No files matched your search
+39
-21
@@ -30,19 +30,14 @@ fn ratio(file_mm: f64, drawing_mm: Option<f64>) -> f64 {
|
||||
/// A block name from a product's, with the characters names cannot hold
|
||||
/// replaced.
|
||||
fn block_name(name: &str) -> String {
|
||||
let name: String = name
|
||||
.trim()
|
||||
.chars()
|
||||
.map(|c| if "<>/\\\":;?*|,=`".contains(c) { '_' } else { c })
|
||||
.collect();
|
||||
if name.is_empty() { "Part".to_string() } else { name }
|
||||
crate::scene::sanitized_table_name(name, "Part")
|
||||
}
|
||||
|
||||
/// A STEP presentation colour as the drawing colour it imports as: the
|
||||
/// standard index colour it is close to — red, yellow, green, cyan, blue,
|
||||
/// magenta, white, or the dark grey 250 (which black also becomes) — and
|
||||
/// otherwise the true colour.
|
||||
// ponytail: the closeness bands are fitted to measured imports (saturation
|
||||
// ponytail: the closeness bands are fitted to sample files (saturation
|
||||
// at least 0.75, value at least 0.825 and hue within 7.5 degrees for the six
|
||||
// hues; greys from 0.16 to 0.39 for 250 and from 0.935 for white); refit
|
||||
// them if a colour on a band edge comes out differently.
|
||||
@@ -114,22 +109,43 @@ fn step_blocks(model: crate::io::step_import::StepModel, scale: f64) -> Imported
|
||||
}
|
||||
names.push(name);
|
||||
}
|
||||
// Children before parents.
|
||||
// Children before parents. A link that closes a loop in the file's
|
||||
// assembly (a product that would contain itself) is dropped, so no
|
||||
// block ends up inserting itself.
|
||||
let mut order = Vec::new();
|
||||
let mut dropped: std::collections::HashSet<(usize, usize)> = Default::default();
|
||||
let mut state = vec![0u8; model.products.len()];
|
||||
fn visit(k: usize, model: &crate::io::step_import::StepModel, state: &mut [u8], order: &mut Vec<usize>) {
|
||||
if state[k] != 0 {
|
||||
return;
|
||||
}
|
||||
state[k] = 1;
|
||||
for (child, _) in &model.products[k].children {
|
||||
visit(*child, model, state, order);
|
||||
}
|
||||
state[k] = 2;
|
||||
order.push(k);
|
||||
}
|
||||
for &root in &model.roots {
|
||||
visit(root, &model, &mut state, &mut order);
|
||||
if state[root] != 0 {
|
||||
continue;
|
||||
}
|
||||
state[root] = 1;
|
||||
// Each product being walked and the next of its children to visit.
|
||||
let mut stack = vec![(root, 0usize)];
|
||||
while let Some((k, next)) = stack.last_mut() {
|
||||
let k = *k;
|
||||
match model.products[k].children.get(*next) {
|
||||
Some(&(child, _)) => {
|
||||
let link = (k, *next);
|
||||
*next += 1;
|
||||
match state[child] {
|
||||
0 => {
|
||||
state[child] = 1;
|
||||
stack.push((child, 0));
|
||||
}
|
||||
1 => {
|
||||
dropped.insert(link);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
state[k] = 2;
|
||||
order.push(k);
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let blocks = order
|
||||
.iter()
|
||||
@@ -147,7 +163,9 @@ fn step_blocks(model: crate::io::step_import::StepModel, scale: f64) -> Imported
|
||||
product
|
||||
.children
|
||||
.iter()
|
||||
.map(|(child, placement)| {
|
||||
.enumerate()
|
||||
.filter(|(n, _)| !dropped.contains(&(k, *n)))
|
||||
.map(|(_, (child, placement))| {
|
||||
let mut insert = placed_insert(&names[*child], placement);
|
||||
insert.common_mut().color = Color::ByBlock;
|
||||
insert
|
||||
|
||||
+42
-21
@@ -8,6 +8,8 @@ use std::collections::HashMap;
|
||||
use codec::entities::{PolyfaceFace, PolyfaceMesh, PolyfaceVertex};
|
||||
use codec::types::Vector3;
|
||||
|
||||
use crate::scene::sanitized_table_name;
|
||||
|
||||
pub struct TdsMaterial {
|
||||
pub name: String,
|
||||
pub ambient: Option<[u8; 3]>,
|
||||
@@ -258,6 +260,8 @@ pub struct TdsOptions {
|
||||
/// they first use them — as they do when the import splits by
|
||||
/// material or asks — rather than the file's vertex list.
|
||||
pub compact: bool,
|
||||
/// The layer every mesh goes on with [`LayerMode::Single`].
|
||||
pub single_layer: String,
|
||||
}
|
||||
|
||||
/// One imported mesh.
|
||||
@@ -284,14 +288,24 @@ pub struct TdsImport {
|
||||
pub messages: Vec<String>,
|
||||
}
|
||||
|
||||
/// A polyface mesh of `faces` of `mesh`: its vertices in file order, or
|
||||
/// Polyface meshes of `faces` of `mesh`: its vertices in file order, or
|
||||
/// (`compact`) only those the faces use, in the order they first use them.
|
||||
fn polyface(mesh: &TdsMesh, faces: &[usize], compact: bool) -> PolyfaceMesh {
|
||||
let mut out = PolyfaceMesh::new();
|
||||
/// A polyface mesh numbers its vertices in 16 bits, so a mesh with more
|
||||
/// vertices than that is always compact, and the faces that need further
|
||||
/// vertices go on further meshes.
|
||||
fn polyfaces(mesh: &TdsMesh, faces: &[usize], compact: bool) -> Vec<PolyfaceMesh> {
|
||||
const MAX_VERTICES: usize = i16::MAX as usize;
|
||||
let compact = compact || mesh.vertices.len() > MAX_VERTICES;
|
||||
let vertex = |v: usize| {
|
||||
let p = mesh.vertices[v];
|
||||
PolyfaceVertex::new(Vector3::new(p[0], p[1], p[2]))
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
let mut current = PolyfaceMesh::new();
|
||||
let mut index: HashMap<usize, i16> = HashMap::new();
|
||||
if !compact {
|
||||
for (k, v) in mesh.vertices.iter().enumerate() {
|
||||
index.insert(k, out.add_vertex(PolyfaceVertex::new(Vector3::new(v[0], v[1], v[2]))));
|
||||
for k in 0..mesh.vertices.len() {
|
||||
index.insert(k, current.add_vertex(vertex(k)));
|
||||
}
|
||||
}
|
||||
for &f in faces {
|
||||
@@ -299,14 +313,15 @@ fn polyface(mesh: &TdsMesh, faces: &[usize], compact: bool) -> PolyfaceMesh {
|
||||
if face.iter().any(|v| *v >= mesh.vertices.len()) {
|
||||
continue;
|
||||
}
|
||||
let ids = face.map(|v| {
|
||||
*index.entry(v).or_insert_with(|| {
|
||||
let p = mesh.vertices[v];
|
||||
out.add_vertex(PolyfaceVertex::new(Vector3::new(p[0], p[1], p[2])))
|
||||
})
|
||||
});
|
||||
out.add_face(PolyfaceFace::triangle(ids[0], ids[1], ids[2]));
|
||||
let new = face.iter().filter(|v| !index.contains_key(v)).count();
|
||||
if current.vertices.len() + new > MAX_VERTICES {
|
||||
out.push(std::mem::replace(&mut current, PolyfaceMesh::new()));
|
||||
index.clear();
|
||||
}
|
||||
let ids = face.map(|v| *index.entry(v).or_insert_with(|| current.add_vertex(vertex(v))));
|
||||
current.add_face(PolyfaceFace::triangle(ids[0], ids[1], ids[2]));
|
||||
}
|
||||
out.push(current);
|
||||
out
|
||||
}
|
||||
|
||||
@@ -365,24 +380,30 @@ pub fn convert(scene: &TdsScene, options: &TdsOptions) -> TdsImport {
|
||||
out.materials.push(m.clone());
|
||||
}
|
||||
}
|
||||
// Object and material names come from the file, so they
|
||||
// are made fit to name a layer.
|
||||
let layer = match options.layers {
|
||||
LayerMode::ByObject => name.to_uppercase(),
|
||||
LayerMode::ByMaterial => material.as_deref().unwrap_or("0").to_uppercase(),
|
||||
LayerMode::ByObject => sanitized_table_name(&name.to_uppercase(), "0"),
|
||||
LayerMode::ByMaterial => {
|
||||
sanitized_table_name(&material.as_deref().unwrap_or("0").to_uppercase(), "0")
|
||||
}
|
||||
LayerMode::ByObjectColor => match mesh.color {
|
||||
Some(c) => format!("COLOR{c:02}"),
|
||||
None => "COLORNONE".to_string(),
|
||||
},
|
||||
LayerMode::Single => "AVLAYER".to_string(),
|
||||
LayerMode::Single => options.single_layer.clone(),
|
||||
};
|
||||
out.parts.push(TdsPart {
|
||||
layer,
|
||||
material,
|
||||
mesh: polyface(mesh, &faces, compact),
|
||||
});
|
||||
for polyface in polyfaces(mesh, &faces, compact) {
|
||||
out.parts.push(TdsPart {
|
||||
layer: layer.clone(),
|
||||
material: material.clone(),
|
||||
mesh: polyface,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
TdsObject::Camera(camera) => {
|
||||
let name = name.to_uppercase();
|
||||
let name = sanitized_table_name(&name.to_uppercase(), "CAMERA");
|
||||
out.messages.push(format!("Creating camera {name}"));
|
||||
let direction = [0, 1, 2].map(|k| camera.position[k] - camera.target[k]);
|
||||
let distance = direction.iter().map(|d| d * d).sum::<f64>().sqrt();
|
||||
|
||||
Reference in new issue
Block a user