forked from 1b-agent/opencadstudio-upstream
Complete Table host model coverage
Maps rows, columns, merged ranges, style and placement through the generic generator (now with tuple and usize support), adds grid/merge/reference validation, and keeps DXF cell merge dimensions in step with merged_ranges. staged_python_table_lifecycle_over_real_ipc passes the full gate including DWG and DXF reopen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b3f5388208
commit
0206c7c30b
12 files changed
+428
-12
No files matched your search
@@ -94,7 +94,7 @@ The host-owned catalog classifies all 48 entity variants and tracks every
|
||||
traced field, including fields marked `unmapped`. Query it with
|
||||
`doc.coverage()` or `doc.coverage("Line")`; each row includes source field,
|
||||
type, access, and current validation status. Geometry edits remain limited to
|
||||
the 26 mapped kinds.
|
||||
the 27 mapped kinds.
|
||||
For any entity, `entity.snapshot` returns a detached dictionary of its raw
|
||||
CAD fields, including `snapshot["common"]` and kind-specific nested values.
|
||||
It can inspect an unmapped kind; editing the dictionary does
|
||||
|
||||
@@ -201,6 +201,10 @@ fn helper_closure(manifest: &Manifest, registry: &TypeRegistry) -> Vec<String> {
|
||||
}
|
||||
|
||||
while let Some(name) = queue.pop() {
|
||||
if let Some(parts) = tuple_parts(&name) {
|
||||
queue.extend(parts.into_iter().map(str::to_owned));
|
||||
continue;
|
||||
}
|
||||
if BUILTIN.contains(&name.as_str())
|
||||
|| SPECIAL.contains(&name.as_str())
|
||||
|| f64_array_len(&name).is_some()
|
||||
@@ -300,6 +304,26 @@ fn is_native(type_id: &str) -> bool {
|
||||
BUILTIN.contains(&type_id)
|
||||
}
|
||||
|
||||
/// Split a tuple type id such as `(u32,CellBorder)` into its element types.
|
||||
fn tuple_parts(type_id: &str) -> Option<Vec<&str>> {
|
||||
let inner = type_id.strip_prefix('(')?.strip_suffix(')')?;
|
||||
let mut parts = Vec::new();
|
||||
let (mut depth, mut start) = (0i32, 0usize);
|
||||
for (i, ch) in inner.char_indices() {
|
||||
match ch {
|
||||
'(' | '[' | '<' => depth += 1,
|
||||
')' | ']' | '>' => depth -= 1,
|
||||
',' if depth == 0 => {
|
||||
parts.push(inner[start..i].trim());
|
||||
start = i + 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
parts.push(inner[start..].trim());
|
||||
(parts.len() >= 2).then_some(parts)
|
||||
}
|
||||
|
||||
/// `[f64; N]` fixed arrays (e.g. a 4x4 transform) convert as flat float lists.
|
||||
fn f64_array_len(type_id: &str) -> Option<usize> {
|
||||
type_id
|
||||
@@ -313,6 +337,10 @@ fn default_expr(type_id: &str) -> String {
|
||||
if let Some(n) = f64_array_len(type_id) {
|
||||
return format!("[0.0f64; {n}]");
|
||||
}
|
||||
if let Some(parts) = tuple_parts(type_id) {
|
||||
let items: Vec<String> = parts.iter().map(|part| default_expr(part)).collect();
|
||||
return format!("({})", items.join(", "));
|
||||
}
|
||||
match type_id {
|
||||
"f64" | "f32" => "0.0".to_string(),
|
||||
"i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => "0".to_string(),
|
||||
@@ -339,6 +367,14 @@ fn leaf_to_py(registry: &TypeRegistry, type_id: &str, item_expr: &str) -> String
|
||||
"Ok(vm.ctx.new_list(({item_expr}).iter().map(|item| vm.new_pyobj(*item)).collect()).into())"
|
||||
);
|
||||
}
|
||||
if let Some(parts) = tuple_parts(type_id) {
|
||||
let items: Vec<String> = parts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, part)| format!("{}?", leaf_to_py(registry, part, &format!("&({item_expr}).{i}"))))
|
||||
.collect();
|
||||
return format!("Ok(vm.ctx.new_list(vec![{}]).into())", items.join(", "));
|
||||
}
|
||||
match type_id {
|
||||
"f64" | "f32" | "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" | "bool" => {
|
||||
format!("Ok(vm.new_pyobj(*{parenthesized}))")
|
||||
@@ -365,12 +401,28 @@ fn leaf_to_py(registry: &TypeRegistry, type_id: &str, item_expr: &str) -> String
|
||||
|
||||
/// `value_expr` must already be an owned `PyObjectRef` expression. Result: a `PyResult<T>` expression.
|
||||
fn leaf_from_py(registry: &TypeRegistry, type_id: &str, value_expr: &str) -> String {
|
||||
if type_id == "u64" {
|
||||
// serde reports Rust `usize` as `u64`; the inferred cast serves both.
|
||||
return format!("{value_expr}.try_into_value::<u64>(vm).map(|n| n as _)");
|
||||
}
|
||||
if is_native(type_id) {
|
||||
return format!("{value_expr}.try_into_value::<{}>(vm)", native_rust_type(type_id));
|
||||
}
|
||||
if let Some(n) = f64_array_len(type_id) {
|
||||
return format!("py_to_f64_array::<{n}>({value_expr}, vm)");
|
||||
}
|
||||
if let Some(parts) = tuple_parts(type_id) {
|
||||
let n = parts.len();
|
||||
let items: Vec<String> = parts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, part)| format!("{}?", leaf_from_py(registry, part, &format!("items.remove(0 * {i})"))))
|
||||
.collect();
|
||||
return format!(
|
||||
"(|| -> PyResult<_> {{ let mut items: Vec<PyObjectRef> = ({value_expr}).try_into_value(vm)?; if items.len() != {n} {{ return Err(vm.new_value_error(\"expected a {n}-item list\".to_owned())); }} Ok(({})) }})()",
|
||||
items.join(", ")
|
||||
);
|
||||
}
|
||||
match type_id {
|
||||
"Vector3" => format!("py_to_vector3_dict({value_expr}, vm)"),
|
||||
"Vector2" => format!("py_to_vector2({value_expr}, vm)"),
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"Leader",
|
||||
"MLine",
|
||||
"Dimension",
|
||||
"MultiLeader"
|
||||
"MultiLeader",
|
||||
"Table"
|
||||
],
|
||||
"manual_kinds": ["Dimension"],
|
||||
"base_fields": ["handle", "layer", "owner_handle"],
|
||||
@@ -280,6 +281,35 @@
|
||||
"rust_setter": "if let Some(v) = dict.get_item_opt(\"context\", vm)? { VAR.context = merge_multileader_context(&VAR.context, v, vm)?; }"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Table": {
|
||||
"fields": {
|
||||
"insertion_point": { "required": true },
|
||||
"block_name": { "read_only": true },
|
||||
"block_record_handle": { "read_only": true },
|
||||
"data_version": { "read_only": true },
|
||||
"value_flags": { "read_only": true },
|
||||
"override_flag": { "read_only": true },
|
||||
"override_border_color": { "read_only": true },
|
||||
"override_border_line_weight": { "read_only": true },
|
||||
"override_border_visibility": { "read_only": true },
|
||||
"legacy_style_override": { "read_only": true },
|
||||
"name": { "read_only": true },
|
||||
"description": { "read_only": true },
|
||||
"field_handles": { "read_only": true },
|
||||
"base_style": { "read_only": true },
|
||||
"break_data": { "read_only": true },
|
||||
"break_ranges": { "read_only": true },
|
||||
"dwg_unknown_byte": { "exclude": true },
|
||||
"dwg_unknown_handle": { "exclude": true },
|
||||
"dwg_unknown_long1": { "exclude": true },
|
||||
"dwg_r2010_unknown_bit": { "exclude": true },
|
||||
"dwg_unknown_long2": { "exclude": true },
|
||||
"dwg_unknown_short": { "exclude": true },
|
||||
"legacy_border_colors": { "exclude": true, "_comment": "Generic LegacyBorderOverrides<T> is not expressible by the generator; legacy R13-R2000 border overrides are preserved untouched." },
|
||||
"legacy_border_line_weights": { "exclude": true },
|
||||
"legacy_border_visibility": { "exclude": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4756,7 +4756,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_eq!(emitted.len(), 26);
|
||||
assert_eq!(emitted.len(), 27);
|
||||
}
|
||||
|
||||
#[cfg(feature = "experimental-host-model")]
|
||||
|
||||
Reference in new issue
Block a user