forked from 1b-agent/opencadstudio-upstream
feat: node graph overlay; move to opencadcodec/opencadkernel/opencadgraph
The codec and kernel repositories and crates are renamed opencadcodec and opencadkernel. Inside OCS they are depended on under short keys, so code reads `codec::`, `kernel::` and the new `graph::` (opencadgraph, the node graph engine). File-format and protocol spellings stay as they were: the ACIS product id `acadrust`, the ACADRUST_* switches and the plugin version keys `acadrust_version` / `acadrust_source`. A button left of MCP on the command line opens a translucent node graph over the viewport, with a palette of object, input, math, logic, list, text and geometry nodes added by click or drag. An object node owns drawing entities and its rows are the Properties panel's own rows for them: the single-object page is extracted from refresh_properties as entity_property_sections, values use the control protocol's property_json, and writes go through set_property_value with a property target override, so the panel, MCP and the graph share one read/write path. A list reaching an object row makes one entity per element. Each graph action is one undo step; the canvas pans and zooms. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
56c04408d6
commit
f537aff75c
430 files changed
+9925
-8540
No files matched your search
+53
-39
@@ -1,9 +1,9 @@
|
||||
use super::*;
|
||||
use crate::scene::model::object::PropValue;
|
||||
|
||||
fn control_color(value: &Value) -> Result<acadrust::types::Color, Value> {
|
||||
fn control_color(value: &Value) -> Result<codec::types::Color, Value> {
|
||||
if let Some(index) = value.as_i64().and_then(|v| i16::try_from(v).ok()) {
|
||||
return Ok(acadrust::types::Color::from_index(index));
|
||||
return Ok(codec::types::Color::from_index(index));
|
||||
}
|
||||
if let Some(rgb) = value
|
||||
.get("rgb")
|
||||
@@ -12,7 +12,7 @@ fn control_color(value: &Value) -> Result<acadrust::types::Color, Value> {
|
||||
{
|
||||
let component = |i: usize| rgb[i].as_u64().and_then(|v| u8::try_from(v).ok());
|
||||
return match (component(0), component(1), component(2)) {
|
||||
(Some(r), Some(g), Some(b)) => Ok(acadrust::types::Color::from_rgb(r, g, b)),
|
||||
(Some(r), Some(g), Some(b)) => Ok(codec::types::Color::from_rgb(r, g, b)),
|
||||
_ => Err(failure(
|
||||
"invalid_color",
|
||||
"RGB components must be 0 through 255",
|
||||
@@ -25,12 +25,12 @@ fn control_color(value: &Value) -> Result<acadrust::types::Color, Value> {
|
||||
))
|
||||
}
|
||||
|
||||
fn control_lineweight(value: &Value) -> Result<acadrust::types::LineWeight, Value> {
|
||||
fn control_lineweight(value: &Value) -> Result<codec::types::LineWeight, Value> {
|
||||
let raw = value
|
||||
.as_i64()
|
||||
.and_then(|v| i16::try_from(v).ok())
|
||||
.ok_or_else(|| failure("invalid_lineweight", "Use the raw lineweight value"))?;
|
||||
let candidate = acadrust::types::LineWeight::from_value(raw);
|
||||
let candidate = codec::types::LineWeight::from_value(raw);
|
||||
crate::ui::properties::lw_options()
|
||||
.iter()
|
||||
.any(|v| v.0 == candidate)
|
||||
@@ -38,13 +38,35 @@ fn control_lineweight(value: &Value) -> Result<acadrust::types::LineWeight, Valu
|
||||
.ok_or_else(|| failure("invalid_lineweight", "Value is not a standard lineweight"))
|
||||
}
|
||||
|
||||
fn color_value(color: acadrust::types::Color) -> Value {
|
||||
fn color_value(color: codec::types::Color) -> Value {
|
||||
if let Some((r, g, b)) = color.rgb() {
|
||||
json!({"rgb":[r,g,b]})
|
||||
} else {
|
||||
json!(color.index())
|
||||
}
|
||||
}
|
||||
/// One Properties-panel row as the control protocol's JSON (`id`, `label`,
|
||||
/// `kind`, `value`, `options`). The node graph moves values in this form too,
|
||||
/// so a port's value round-trips through [`OpenCADStudio::set_property_value`].
|
||||
pub(crate) fn property_json(p: &crate::scene::model::object::Property) -> Value {
|
||||
let (kind,value,options)=match &p.value{
|
||||
PropValue::ReadOnly(v)|PropValue::ReadOnlyWithTooltip{value:v,..}=>("readonly",json!(v),Value::Null),
|
||||
PropValue::EditText(v)=>("number",json!(v),Value::Null),
|
||||
PropValue::PlainText(v)=>("text",json!(v),Value::Null),
|
||||
PropValue::Hyperlink(v)=>("hyperlink",json!(v),Value::Null),
|
||||
PropValue::Choice{selected,options}=>("choice",json!(selected),json!(options)),
|
||||
PropValue::EditChoice{value,options}=>("editable_choice",json!(value),json!(options)),
|
||||
PropValue::LayerChoice(v)=>("layer",json!(v),Value::Null),
|
||||
PropValue::LinetypeChoice(v)=>("linetype",json!(v),Value::Null),
|
||||
PropValue::BoolToggle{value,..}=>("bool",json!(value),Value::Null),
|
||||
PropValue::ColorChoice(value)|PropValue::NamedColorChoice{color:value,..}=>("color",color_value(*value),Value::Null),
|
||||
PropValue::ColorVaries=>("color",Value::Null,Value::Null),
|
||||
PropValue::LwChoice(value)|PropValue::FieldLwChoice{value,..}=>("lineweight",json!(value.value()),json!(crate::ui::properties::lw_options().iter().map(|v|v.0.value()).collect::<Vec<_>>())),
|
||||
PropValue::LwVaries|PropValue::FieldLwVaries{..}=>("lineweight",Value::Null,json!(crate::ui::properties::lw_options().iter().map(|v|v.0.value()).collect::<Vec<_>>())),
|
||||
PropValue::AttrText{tag,value}=>("attribute",json!({"tag":tag,"value":value}),Value::Null),
|
||||
other=>("specialized",json!(format!("{other:?}")),Value::Null),
|
||||
};json!({"id":p.field,"label":p.label,"kind":kind,"value":value,"options":options})
|
||||
}
|
||||
pub(super) const NAMES: &[&str] = &[
|
||||
"close_modal",
|
||||
"pdf_dialog_ok",
|
||||
@@ -75,7 +97,7 @@ pub(super) const NAMES: &[&str] = &[
|
||||
];
|
||||
|
||||
/// Parse the `at` placement point for `embed_image`: `[x,y]` or `[x,y,z]`.
|
||||
fn embed_point(req: &Value) -> Result<acadrust::types::Vector3, Value> {
|
||||
fn embed_point(req: &Value) -> Result<codec::types::Vector3, Value> {
|
||||
let values = req["at"]
|
||||
.as_array()
|
||||
.filter(|v| (2..=3).contains(&v.len()))
|
||||
@@ -87,7 +109,7 @@ fn embed_point(req: &Value) -> Result<acadrust::types::Vector3, Value> {
|
||||
.filter(|v| v.is_finite())
|
||||
.ok_or_else(|| failure("invalid_point", "Expected finite coordinates"))?;
|
||||
}
|
||||
Ok(acadrust::types::Vector3::new(p[0], p[1], p[2]))
|
||||
Ok(codec::types::Vector3::new(p[0], p[1], p[2]))
|
||||
}
|
||||
impl OpenCADStudio {
|
||||
/// `embed_image` — pack the picture at `path` into an OLE2FRAME placed
|
||||
@@ -112,7 +134,7 @@ impl OpenCADStudio {
|
||||
let handle = {
|
||||
let document = &mut self.tabs[i].scene.document;
|
||||
let definition_handle = document.allocate_handle();
|
||||
let mut definition = acadrust::objects::ImageDefinition::with_dimensions(
|
||||
let mut definition = codec::objects::ImageDefinition::with_dimensions(
|
||||
path,
|
||||
image.pixel_width,
|
||||
image.pixel_height,
|
||||
@@ -120,9 +142,9 @@ impl OpenCADStudio {
|
||||
definition.handle = definition_handle;
|
||||
document.objects.insert(
|
||||
definition_handle,
|
||||
acadrust::objects::ObjectType::ImageDefinition(definition),
|
||||
codec::objects::ObjectType::ImageDefinition(definition),
|
||||
);
|
||||
let mut entity = acadrust::entities::RasterImage::with_size(
|
||||
let mut entity = codec::entities::RasterImage::with_size(
|
||||
path,
|
||||
at,
|
||||
image.pixel_width as f64,
|
||||
@@ -132,7 +154,7 @@ impl OpenCADStudio {
|
||||
);
|
||||
entity.definition_handle = Some(definition_handle);
|
||||
document
|
||||
.add_entity(acadrust::EntityType::RasterImage(entity))
|
||||
.add_entity(codec::EntityType::RasterImage(entity))
|
||||
.map_err(|e| failure("embed_failed", e))?
|
||||
};
|
||||
self.tabs[i].scene.populate_images_from_document();
|
||||
@@ -191,7 +213,7 @@ impl OpenCADStudio {
|
||||
None => crate::modules::insert::wblock::extract_block_to_doc(document, block),
|
||||
}
|
||||
} else if let Some(listed) = req["handles"].as_array() {
|
||||
let handles: Result<Vec<acadrust::Handle>, Value> = listed
|
||||
let handles: Result<Vec<codec::Handle>, Value> = listed
|
||||
.iter()
|
||||
.map(|v| {
|
||||
u64::from_str_radix(
|
||||
@@ -201,7 +223,7 @@ impl OpenCADStudio {
|
||||
.trim_start_matches("0X"),
|
||||
16,
|
||||
)
|
||||
.map(acadrust::Handle::new)
|
||||
.map(codec::Handle::new)
|
||||
.map_err(|_| {
|
||||
failure(
|
||||
"invalid_handle",
|
||||
@@ -531,25 +553,7 @@ impl OpenCADStudio {
|
||||
|
||||
pub(super) fn control_properties(&mut self) -> Value {
|
||||
self.refresh_properties();
|
||||
json!({"ok":true,"sections":self.tabs[self.active_tab].properties.sections.iter().map(|s|json!({"title":s.title,"properties":s.props.iter().map(|p|{
|
||||
let (kind,value,options)=match &p.value{
|
||||
PropValue::ReadOnly(v)|PropValue::ReadOnlyWithTooltip{value:v,..}=>("readonly",json!(v),Value::Null),
|
||||
PropValue::EditText(v)=>("number",json!(v),Value::Null),
|
||||
PropValue::PlainText(v)=>("text",json!(v),Value::Null),
|
||||
PropValue::Hyperlink(v)=>("hyperlink",json!(v),Value::Null),
|
||||
PropValue::Choice{selected,options}=>("choice",json!(selected),json!(options)),
|
||||
PropValue::EditChoice{value,options}=>("editable_choice",json!(value),json!(options)),
|
||||
PropValue::LayerChoice(v)=>("layer",json!(v),Value::Null),
|
||||
PropValue::LinetypeChoice(v)=>("linetype",json!(v),Value::Null),
|
||||
PropValue::BoolToggle{value,..}=>("bool",json!(value),Value::Null),
|
||||
PropValue::ColorChoice(value)|PropValue::NamedColorChoice{color:value,..}=>("color",color_value(*value),Value::Null),
|
||||
PropValue::ColorVaries=>("color",Value::Null,Value::Null),
|
||||
PropValue::LwChoice(value)|PropValue::FieldLwChoice{value,..}=>("lineweight",json!(value.value()),json!(crate::ui::properties::lw_options().iter().map(|v|v.0.value()).collect::<Vec<_>>())),
|
||||
PropValue::LwVaries|PropValue::FieldLwVaries{..}=>("lineweight",Value::Null,json!(crate::ui::properties::lw_options().iter().map(|v|v.0.value()).collect::<Vec<_>>())),
|
||||
PropValue::AttrText{tag,value}=>("attribute",json!({"tag":tag,"value":value}),Value::Null),
|
||||
other=>("specialized",json!(format!("{other:?}")),Value::Null),
|
||||
};json!({"id":p.field,"label":p.label,"kind":kind,"value":value,"options":options})
|
||||
}).collect::<Vec<_>>()})).collect::<Vec<_>>()})
|
||||
json!({"ok":true,"sections":self.tabs[self.active_tab].properties.sections.iter().map(|s|json!({"title":s.title,"properties":s.props.iter().map(property_json).collect::<Vec<_>>()})).collect::<Vec<_>>()})
|
||||
}
|
||||
pub(super) fn control_set_property(&mut self, req: &Value) -> Result<Task<Message>, Value> {
|
||||
self.refresh_properties();
|
||||
@@ -567,10 +571,20 @@ impl OpenCADStudio {
|
||||
"Read properties for the current selection",
|
||||
)
|
||||
})?;
|
||||
let value = req["value"]
|
||||
self.set_property_value(p, &req["value"])
|
||||
}
|
||||
|
||||
/// Write `raw` (protocol JSON, see [`property_json`]) into one Properties
|
||||
/// row through the panel's own messages, on the current property targets.
|
||||
pub(crate) fn set_property_value(
|
||||
&mut self,
|
||||
p: crate::scene::model::object::Property,
|
||||
raw: &Value,
|
||||
) -> Result<Task<Message>, Value> {
|
||||
let value = raw
|
||||
.as_str()
|
||||
.map(str::to_owned)
|
||||
.unwrap_or_else(|| req["value"].to_string());
|
||||
.unwrap_or_else(|| raw.to_string());
|
||||
Ok(match p.value {
|
||||
PropValue::EditText(_)
|
||||
| PropValue::PlainText(_)
|
||||
@@ -601,7 +615,7 @@ impl OpenCADStudio {
|
||||
self.update(Message::PropHatchPatternChanged(value))
|
||||
}
|
||||
PropValue::BoolToggle { field, value: old } => {
|
||||
let v = req["value"]
|
||||
let v = raw
|
||||
.as_bool()
|
||||
.ok_or_else(|| failure("invalid_value", "Expected boolean"))?;
|
||||
if old != v {
|
||||
@@ -621,7 +635,7 @@ impl OpenCADStudio {
|
||||
PropValue::ColorChoice(_)
|
||||
| PropValue::NamedColorChoice { .. }
|
||||
| PropValue::ColorVaries => {
|
||||
let color = control_color(&req["value"])?;
|
||||
let color = control_color(raw)?;
|
||||
if p.field == "background_color" {
|
||||
self.update(Message::PropBgColorChanged(color))
|
||||
} else if matches!(
|
||||
@@ -647,12 +661,12 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
PropValue::LwChoice(_) | PropValue::LwVaries => {
|
||||
self.update(Message::PropLwChanged(control_lineweight(&req["value"])?))
|
||||
self.update(Message::PropLwChanged(control_lineweight(raw)?))
|
||||
}
|
||||
PropValue::FieldLwChoice { field, .. } | PropValue::FieldLwVaries { field } => self
|
||||
.update(Message::PropFieldLwChanged {
|
||||
field,
|
||||
value: control_lineweight(&req["value"])?,
|
||||
value: control_lineweight(raw)?,
|
||||
}),
|
||||
PropValue::ReadOnly(_) | PropValue::ReadOnlyWithTooltip { .. } => {
|
||||
return Err(failure("readonly_property", "Property cannot be edited"))
|
||||
|
||||
+26
-26
@@ -9,7 +9,7 @@ use crate::command::EntityTransform;
|
||||
type Parsed<T> = Result<T, Value>;
|
||||
|
||||
/// `[x,y]` or `[x,y,z]` (missing z = 0) from `req[key]`.
|
||||
fn point_field(req: &Value, key: &str) -> Parsed<acadrust::types::Vector3> {
|
||||
fn point_field(req: &Value, key: &str) -> Parsed<codec::types::Vector3> {
|
||||
let values = req[key]
|
||||
.as_array()
|
||||
.filter(|v| (2..=3).contains(&v.len()))
|
||||
@@ -20,14 +20,14 @@ fn point_field(req: &Value, key: &str) -> Parsed<acadrust::types::Vector3> {
|
||||
.filter(|v| v.is_finite())
|
||||
.ok_or_else(|| failure("invalid_point", format!("{key} needs finite coordinates")))
|
||||
};
|
||||
Ok(acadrust::types::Vector3::new(coord(0)?, coord(1)?, values.get(2).and_then(Value::as_f64).unwrap_or(0.0)))
|
||||
Ok(codec::types::Vector3::new(coord(0)?, coord(1)?, values.get(2).and_then(Value::as_f64).unwrap_or(0.0)))
|
||||
}
|
||||
|
||||
fn deg_field(req: &Value, key: &str) -> f64 {
|
||||
req[key].as_f64().unwrap_or(0.0).to_radians()
|
||||
}
|
||||
|
||||
pub(super) fn hex_handles(req: &Value, key: &str) -> Parsed<Vec<acadrust::Handle>> {
|
||||
pub(super) fn hex_handles(req: &Value, key: &str) -> Parsed<Vec<codec::Handle>> {
|
||||
let list = req[key]
|
||||
.as_array()
|
||||
.filter(|v| !v.is_empty())
|
||||
@@ -41,7 +41,7 @@ pub(super) fn hex_handles(req: &Value, key: &str) -> Parsed<Vec<acadrust::Handle
|
||||
.trim_start_matches("0X"),
|
||||
16,
|
||||
)
|
||||
.map(acadrust::Handle::new)
|
||||
.map(codec::Handle::new)
|
||||
.map_err(|_| {
|
||||
failure(
|
||||
"invalid_handle",
|
||||
@@ -54,7 +54,7 @@ pub(super) fn hex_handles(req: &Value, key: &str) -> Parsed<Vec<acadrust::Handle
|
||||
|
||||
/// Every handle must exist in the document; otherwise `entity_absent` naming
|
||||
/// the missing ones so the caller can refresh instead of guessing.
|
||||
pub(super) fn require_existing(document: &acadrust::CadDocument, handles: &[acadrust::Handle]) -> Parsed<()> {
|
||||
pub(super) fn require_existing(document: &codec::CadDocument, handles: &[codec::Handle]) -> Parsed<()> {
|
||||
let missing: Vec<String> = handles
|
||||
.iter()
|
||||
.filter(|h| document.get_entity(**h).is_none())
|
||||
@@ -70,28 +70,28 @@ pub(super) fn require_existing(document: &acadrust::CadDocument, handles: &[acad
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_common_properties(spec: &Value, entity: &mut acadrust::EntityType) -> Parsed<()> {
|
||||
fn apply_common_properties(spec: &Value, entity: &mut codec::EntityType) -> Parsed<()> {
|
||||
if let Some(layer) = spec["layer"].as_str() {
|
||||
if !layer.is_empty()
|
||||
&& !matches!(
|
||||
entity,
|
||||
acadrust::EntityType::Block(_) | acadrust::EntityType::BlockEnd(_)
|
||||
codec::EntityType::Block(_) | codec::EntityType::BlockEnd(_)
|
||||
)
|
||||
{
|
||||
entity.common_mut().layer = layer.to_owned();
|
||||
}
|
||||
}
|
||||
if let Some(index) = spec["color"].as_i64().and_then(|v| i16::try_from(v).ok()) {
|
||||
entity.common_mut().color = acadrust::types::Color::from_index(index);
|
||||
entity.common_mut().color = codec::types::Color::from_index(index);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One `entities` array entry → a real entity. Geometry validation happens
|
||||
/// here so a bad definition aborts the whole batch before anything commits.
|
||||
fn build_entity(spec: &Value) -> Parsed<acadrust::EntityType> {
|
||||
use acadrust::entities::*;
|
||||
use acadrust::types::Vector3;
|
||||
fn build_entity(spec: &Value) -> Parsed<codec::EntityType> {
|
||||
use codec::entities::*;
|
||||
use codec::types::Vector3;
|
||||
let kind = spec["type"].as_str().unwrap_or("").to_ascii_uppercase();
|
||||
let entity = match kind.as_str() {
|
||||
"LINE" => {
|
||||
@@ -133,7 +133,7 @@ fn build_entity(spec: &Value) -> Parsed<acadrust::EntityType> {
|
||||
let y = point[1].as_f64().filter(|v| v.is_finite()).ok_or_else(|| {
|
||||
failure("invalid_vertices", "polyline vertices must be finite [x,y]")
|
||||
})?;
|
||||
pline.add_point(acadrust::types::Vector2::new(x, y));
|
||||
pline.add_point(codec::types::Vector2::new(x, y));
|
||||
}
|
||||
if spec["closed"].as_bool().unwrap_or(false) {
|
||||
pline.close();
|
||||
@@ -230,7 +230,7 @@ fn build_entity(spec: &Value) -> Parsed<acadrust::EntityType> {
|
||||
let y = point[1].as_f64().filter(|v| v.is_finite()).ok_or_else(|| {
|
||||
failure("invalid_boundary", "hatch boundary must be finite [x,y]")
|
||||
})?;
|
||||
vertices.push(acadrust::types::Vector2::new(x, y));
|
||||
vertices.push(codec::types::Vector2::new(x, y));
|
||||
}
|
||||
let mut hatch = Hatch::new();
|
||||
if spec["solid"].as_bool().unwrap_or(false) {
|
||||
@@ -271,7 +271,7 @@ impl OpenCADStudio {
|
||||
.as_array()
|
||||
.filter(|v| !v.is_empty())
|
||||
.ok_or_else(|| failure("entities_required", "Supply entities:[{type:…},…]"))?;
|
||||
let built: Vec<acadrust::EntityType> = list.iter().map(build_entity).collect::<Parsed<_>>()?;
|
||||
let built: Vec<codec::EntityType> = list.iter().map(build_entity).collect::<Parsed<_>>()?;
|
||||
|
||||
let i = self.active_tab;
|
||||
self.push_undo_snapshot(i, "ENTITIESCREATE");
|
||||
@@ -286,7 +286,7 @@ impl OpenCADStudio {
|
||||
if layer.is_empty() || document.layers.contains(&layer) {
|
||||
continue;
|
||||
}
|
||||
let mut layer_record = acadrust::tables::Layer::new(layer.clone());
|
||||
let mut layer_record = codec::tables::Layer::new(layer.clone());
|
||||
layer_record.handle = document.allocate_handle();
|
||||
document
|
||||
.layers
|
||||
@@ -572,11 +572,11 @@ impl OpenCADStudio {
|
||||
|
||||
// World → block space translates `base` onto the block origin; the
|
||||
// inverse places the Insert so entities keep their world position.
|
||||
let world_to_block = acadrust::types::Transform::from_translation(
|
||||
acadrust::types::Vector3::new(-base.x, -base.y, -base.z),
|
||||
let world_to_block = codec::types::Transform::from_translation(
|
||||
codec::types::Vector3::new(-base.x, -base.y, -base.z),
|
||||
);
|
||||
let block_to_world =
|
||||
acadrust::types::Transform::from_translation(insert_at);
|
||||
codec::types::Transform::from_translation(insert_at);
|
||||
self.push_undo_snapshot(i, "BLOCKDEFINE");
|
||||
// CF-01.3 (barcode re-import): `replace:true` drops the previous
|
||||
// definition — its children, markers and inserts — inside the same
|
||||
@@ -660,14 +660,14 @@ fn erase_block_definition(scene: &mut crate::scene::Scene, name: &str) -> usize
|
||||
};
|
||||
let owner = record.handle;
|
||||
let block_markers = [record.block_entity_handle, record.block_end_handle];
|
||||
let mut victims: Vec<acadrust::Handle> = scene
|
||||
let mut victims: Vec<codec::Handle> = scene
|
||||
.document
|
||||
.entities()
|
||||
.filter(|entity| {
|
||||
entity.common().owner_handle == owner
|
||||
|| matches!(
|
||||
entity,
|
||||
acadrust::EntityType::Insert(insert)
|
||||
codec::EntityType::Insert(insert)
|
||||
if insert.block_name.eq_ignore_ascii_case(name)
|
||||
)
|
||||
})
|
||||
@@ -681,8 +681,8 @@ fn erase_block_definition(scene: &mut crate::scene::Scene, name: &str) -> usize
|
||||
|
||||
/// `data:[{code,value},…]` → typed XData values; an absent or empty list
|
||||
/// means "remove this application's record".
|
||||
fn parse_xdata_values(req: &Value) -> Parsed<Option<Vec<acadrust::xdata::XDataValue>>> {
|
||||
use acadrust::xdata::XDataValue;
|
||||
fn parse_xdata_values(req: &Value) -> Parsed<Option<Vec<codec::xdata::XDataValue>>> {
|
||||
use codec::xdata::XDataValue;
|
||||
let Some(list) = req["data"].as_array() else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -728,14 +728,14 @@ fn parse_xdata_values(req: &Value) -> Parsed<Option<Vec<acadrust::xdata::XDataVa
|
||||
let raw = raw.trim_start_matches("0x").trim_start_matches("0X");
|
||||
let handle = u64::from_str_radix(raw, 16)
|
||||
.map_err(|_| failure("invalid_xdata", "code 1005 needs a valid hex handle"))?;
|
||||
XDataValue::Handle(acadrust::Handle::new(handle))
|
||||
XDataValue::Handle(codec::Handle::new(handle))
|
||||
}
|
||||
1010..=1013 => {
|
||||
let point = value.as_array().filter(|v| !v.is_empty()).ok_or_else(|| {
|
||||
failure("invalid_xdata", "codes 1010-1013 need [x,y,z]")
|
||||
})?;
|
||||
let coord = |k: usize| point.get(k).and_then(Value::as_f64).unwrap_or(0.0);
|
||||
let point = acadrust::types::Vector3::new(coord(0), coord(1), coord(2));
|
||||
let point = codec::types::Vector3::new(coord(0), coord(1), coord(2));
|
||||
match code {
|
||||
1010 => XDataValue::Point3D(point),
|
||||
1011 => XDataValue::Position3D(point),
|
||||
@@ -773,8 +773,8 @@ fn parse_xdata_values(req: &Value) -> Parsed<Option<Vec<acadrust::xdata::XDataVa
|
||||
}
|
||||
|
||||
/// JSON shape for one stored XData value.
|
||||
fn xdata_value_json(value: &acadrust::xdata::XDataValue) -> Value {
|
||||
use acadrust::xdata::XDataValue;
|
||||
fn xdata_value_json(value: &codec::xdata::XDataValue) -> Value {
|
||||
use codec::xdata::XDataValue;
|
||||
match value {
|
||||
XDataValue::String(text)
|
||||
| XDataValue::ControlString(text)
|
||||
|
||||
@@ -403,7 +403,7 @@ mod tests {
|
||||
let value = u64::from_str_radix(&handle, 16).unwrap();
|
||||
app.tabs[app.active_tab]
|
||||
.scene
|
||||
.select_entity(acadrust::Handle::new(value), false);
|
||||
.select_entity(codec::Handle::new(value), false);
|
||||
|
||||
let (sender, mut receiver) = mpsc::channel::<Envelope>(8);
|
||||
let bound = Arc::new(AtomicU16::new(0));
|
||||
@@ -475,7 +475,7 @@ mod tests {
|
||||
let value = u64::from_str_radix(&handle, 16).unwrap();
|
||||
app.tabs[app.active_tab]
|
||||
.scene
|
||||
.select_entity(acadrust::Handle::new(value), false);
|
||||
.select_entity(codec::Handle::new(value), false);
|
||||
|
||||
let (sender, mut receiver) = mpsc::channel::<Envelope>(8);
|
||||
let bound = Arc::new(AtomicU16::new(0));
|
||||
@@ -642,7 +642,7 @@ mod tests {
|
||||
envelope.reply.send(reply);
|
||||
app.tabs[app.active_tab]
|
||||
.scene
|
||||
.select_entity(acadrust::Handle::new(handle_value), false);
|
||||
.select_entity(codec::Handle::new(handle_value), false);
|
||||
let _ = app.update(if confirm {
|
||||
crate::app::Message::CommandFinalize
|
||||
} else {
|
||||
|
||||
@@ -148,7 +148,7 @@ impl OpenCADStudio {
|
||||
// The filter only gates the answer, not the user's gestures: anything
|
||||
// that does not match is deselected (and reported) at confirm time.
|
||||
let picked = self.tabs[i].scene.selected_handles_in_order();
|
||||
let mut handles: Vec<acadrust::Handle> = Vec::new();
|
||||
let mut handles: Vec<codec::Handle> = Vec::new();
|
||||
let mut ignored = 0usize;
|
||||
for handle in picked {
|
||||
let matches = self.tabs[i]
|
||||
|
||||
+36
-35
@@ -110,7 +110,7 @@ pub(super) struct State {
|
||||
events: VecDeque<Value>,
|
||||
pub(super) routing: bool,
|
||||
/// Session-scoped named handle sets for selection_set_save/load.
|
||||
selection_sets: std::collections::BTreeMap<String, Vec<acadrust::Handle>>,
|
||||
selection_sets: std::collections::BTreeMap<String, Vec<codec::Handle>>,
|
||||
/// Live `user_select` request — the client asked the person at the screen
|
||||
/// to pick entities; Some until that person answers with Enter/Escape.
|
||||
pub(super) user_select: Option<UserSelectSession>,
|
||||
@@ -190,9 +190,9 @@ fn string<'a>(req: &'a Value, key: &str) -> Result<&'a str, Value> {
|
||||
.filter(|v| !v.is_empty())
|
||||
.ok_or_else(|| failure("invalid_request", format!("Missing {key}")))
|
||||
}
|
||||
fn handle(req: &Value) -> Result<acadrust::Handle, Value> {
|
||||
fn handle(req: &Value) -> Result<codec::Handle, Value> {
|
||||
u64::from_str_radix(string(req, "handle")?.trim_start_matches("0x"), 16)
|
||||
.map(acadrust::Handle::new)
|
||||
.map(codec::Handle::new)
|
||||
.map_err(|_| failure("invalid_handle", "Expected hexadecimal handle"))
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -923,7 +923,7 @@ impl OpenCADStudio {
|
||||
v.as_str().unwrap_or("").trim_start_matches("0x"),
|
||||
16,
|
||||
)
|
||||
.map(acadrust::Handle::new)
|
||||
.map(codec::Handle::new)
|
||||
.map_err(|_| failure("invalid_handle", "Expected hexadecimal handle"))?;
|
||||
if self.tabs[i].scene.document.get_entity(h).is_none() {
|
||||
return Err(failure(
|
||||
@@ -1184,13 +1184,13 @@ impl OpenCADStudio {
|
||||
}
|
||||
pub(super) fn control_measure(&self, req: &Value) -> Value {
|
||||
let tab = &self.tabs[self.active_tab];
|
||||
let requested: Vec<acadrust::Handle> = req["handles"]
|
||||
let requested: Vec<codec::Handle> = req["handles"]
|
||||
.as_array()
|
||||
.map(|a| {
|
||||
a.iter()
|
||||
.filter_map(|v| v.as_str())
|
||||
.filter_map(|v| u64::from_str_radix(v.trim_start_matches("0x"), 16).ok())
|
||||
.map(acadrust::Handle::new)
|
||||
.map(codec::Handle::new)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| tab.scene.selected_handles_in_order());
|
||||
@@ -1311,6 +1311,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
mod actions;
|
||||
pub(crate) use actions::property_json;
|
||||
mod entities;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) mod http_bridge;
|
||||
@@ -1501,7 +1502,7 @@ mod tests {
|
||||
.document
|
||||
.entities()
|
||||
.find_map(|entity| {
|
||||
matches!(entity, acadrust::EntityType::Solid3D(_)).then(|| entity.common().handle)
|
||||
matches!(entity, codec::EntityType::Solid3D(_)).then(|| entity.common().handle)
|
||||
})
|
||||
.unwrap();
|
||||
let handle_text = format!("{:X}", handle.value());
|
||||
@@ -1527,7 +1528,7 @@ mod tests {
|
||||
.scene
|
||||
.document
|
||||
.solid_history_operation(handle),
|
||||
Some(acadrust::objects::SolidHistoryOperation::Brep(_))
|
||||
Some(codec::objects::SolidHistoryOperation::Brep(_))
|
||||
));
|
||||
|
||||
assert_eq!(request(&mut app, json!({"op":"undo"}))["ok"], true);
|
||||
@@ -1691,7 +1692,7 @@ mod tests {
|
||||
.document
|
||||
.entities()
|
||||
.find_map(|e| match e {
|
||||
acadrust::EntityType::Circle(c) => Some(c.center.y),
|
||||
codec::EntityType::Circle(c) => Some(c.center.y),
|
||||
_ => None,
|
||||
});
|
||||
assert_eq!(circle, Some(-10.0));
|
||||
@@ -1717,7 +1718,7 @@ mod tests {
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.filter(|e| matches!(e, acadrust::EntityType::Ole2Frame(_)))
|
||||
.filter(|e| matches!(e, codec::EntityType::Ole2Frame(_)))
|
||||
.count()
|
||||
};
|
||||
assert_eq!(ole_count(&app), 1);
|
||||
@@ -1742,7 +1743,7 @@ mod tests {
|
||||
let value = u64::from_str_radix(&handle, 16).unwrap();
|
||||
app.tabs[app.active_tab]
|
||||
.scene
|
||||
.select_entity(acadrust::Handle::new(value), false);
|
||||
.select_entity(codec::Handle::new(value), false);
|
||||
handle
|
||||
}
|
||||
|
||||
@@ -2034,7 +2035,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
let value = u64::from_str_radix(&handle, 16).unwrap();
|
||||
app.tabs[i].scene.select_entity(acadrust::Handle::new(value), false);
|
||||
app.tabs[i].scene.select_entity(codec::Handle::new(value), false);
|
||||
let _ = app.update(Message::CommandFinalize);
|
||||
let done = user_select_result(&mut app, "us-snap");
|
||||
assert_eq!(done["status"], "completed", "{done}");
|
||||
@@ -2090,19 +2091,19 @@ mod tests {
|
||||
let scene = &mut app.tabs[app.active_tab].scene;
|
||||
// Two layers set to different pen weights; entities fully ByLayer.
|
||||
for (name, weight) in [
|
||||
("THIN", acadrust::types::LineWeight::Value(13)),
|
||||
("THICK", acadrust::types::LineWeight::Value(50)),
|
||||
("THIN", codec::types::LineWeight::Value(13)),
|
||||
("THICK", codec::types::LineWeight::Value(50)),
|
||||
] {
|
||||
let mut layer = acadrust::tables::Layer::new(name);
|
||||
let mut layer = codec::tables::Layer::new(name);
|
||||
layer.line_weight = weight;
|
||||
let _ = scene.document.layers.add(layer);
|
||||
}
|
||||
for (name, origin) in [("THIN", 0.0), ("THICK", 3000.0)] {
|
||||
let mut line = acadrust::entities::Line::new();
|
||||
let mut line = codec::entities::Line::new();
|
||||
line.common.layer = name.to_string();
|
||||
line.start = acadrust::types::Vector3::new(origin, origin, 0.0);
|
||||
line.end = acadrust::types::Vector3::new(origin + 3000.0, origin + 2000.0, 0.0);
|
||||
scene.add_entity(acadrust::EntityType::Line(line));
|
||||
line.start = codec::types::Vector3::new(origin, origin, 0.0);
|
||||
line.end = codec::types::Vector3::new(origin + 3000.0, origin + 2000.0, 0.0);
|
||||
scene.add_entity(codec::EntityType::Line(line));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2146,28 +2147,28 @@ mod tests {
|
||||
{
|
||||
let scene = &mut app.tabs[app.active_tab].scene;
|
||||
// Frame layer carries the classic cyan ACI 4; its entity is ByLayer.
|
||||
let mut frame = acadrust::tables::Layer::new("FRAME-CYAN");
|
||||
frame.color = acadrust::types::Color::Index(4);
|
||||
let mut frame = codec::tables::Layer::new("FRAME-CYAN");
|
||||
frame.color = codec::types::Color::Index(4);
|
||||
let _ = scene.document.layers.add(frame);
|
||||
let mut frame_line = acadrust::entities::Line::new();
|
||||
let mut frame_line = codec::entities::Line::new();
|
||||
frame_line.common.layer = "FRAME-CYAN".to_string();
|
||||
frame_line.start = acadrust::types::Vector3::new(0.0, 0.0, 0.0);
|
||||
frame_line.end = acadrust::types::Vector3::new(3000.0, 0.0, 0.0);
|
||||
scene.add_entity(acadrust::EntityType::Line(frame_line));
|
||||
frame_line.start = codec::types::Vector3::new(0.0, 0.0, 0.0);
|
||||
frame_line.end = codec::types::Vector3::new(3000.0, 0.0, 0.0);
|
||||
scene.add_entity(codec::EntityType::Line(frame_line));
|
||||
|
||||
// An explicit red ACI 1 and a true-color green: index must map
|
||||
// through the CTB, the true color must stay RGB (aci 0).
|
||||
let mut red = acadrust::entities::Line::new();
|
||||
red.common.color = acadrust::types::Color::Index(1);
|
||||
red.start = acadrust::types::Vector3::new(0.0, 1000.0, 0.0);
|
||||
red.end = acadrust::types::Vector3::new(3000.0, 1000.0, 0.0);
|
||||
scene.add_entity(acadrust::EntityType::Line(red));
|
||||
let mut red = codec::entities::Line::new();
|
||||
red.common.color = codec::types::Color::Index(1);
|
||||
red.start = codec::types::Vector3::new(0.0, 1000.0, 0.0);
|
||||
red.end = codec::types::Vector3::new(3000.0, 1000.0, 0.0);
|
||||
scene.add_entity(codec::EntityType::Line(red));
|
||||
|
||||
let mut true_color = acadrust::entities::Line::new();
|
||||
true_color.common.color = acadrust::types::Color::from_true_color_value(0x0000FF00);
|
||||
true_color.start = acadrust::types::Vector3::new(0.0, 2000.0, 0.0);
|
||||
true_color.end = acadrust::types::Vector3::new(3000.0, 2000.0, 0.0);
|
||||
scene.add_entity(acadrust::EntityType::Line(true_color));
|
||||
let mut true_color = codec::entities::Line::new();
|
||||
true_color.common.color = codec::types::Color::from_true_color_value(0x0000FF00);
|
||||
true_color.start = codec::types::Vector3::new(0.0, 2000.0, 0.0);
|
||||
true_color.end = codec::types::Vector3::new(3000.0, 2000.0, 0.0);
|
||||
scene.add_entity(codec::EntityType::Line(true_color));
|
||||
}
|
||||
|
||||
let pdf = std::env::temp_dir().join(format!("ocs-plot-aci-{}.pdf", std::process::id()));
|
||||
|
||||
@@ -321,7 +321,7 @@ fn group_create_and_selection_sets() {
|
||||
let objects = &app.tabs[app.active_tab].scene.document.objects;
|
||||
assert!(objects.values().any(|o| matches!(
|
||||
o,
|
||||
acadrust::objects::ObjectType::Group(group) if group.name == "FRAME"
|
||||
codec::objects::ObjectType::Group(group) if group.name == "FRAME"
|
||||
)));
|
||||
|
||||
let r = app.automation_op(&format!(
|
||||
@@ -405,7 +405,7 @@ fn diagnostic_layer_survives_plain_io_round_trip() {
|
||||
|
||||
// Direct io round trip on a clone of the document.
|
||||
let doc = app.tabs[app.active_tab].scene.document.clone();
|
||||
let bytes = crate::io::save_to_bytes(&doc, "dxf", acadrust::DxfVersion::AC1032)
|
||||
let bytes = crate::io::save_to_bytes(&doc, "dxf", codec::DxfVersion::AC1032)
|
||||
.expect("save to bytes");
|
||||
let path = std::env::temp_dir().join(format!("ocs_diag_{}.dxf", std::process::id()));
|
||||
std::fs::write(&path, &bytes).unwrap();
|
||||
|
||||
+16
-16
@@ -119,7 +119,7 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.document.header.paper_space_linetype_scaling,
|
||||
) | (i16::from(self.tabs[i].scene.document.header.paper_space_limit_check) << 1);
|
||||
for obj in self.tabs[i].scene.document.objects.values_mut() {
|
||||
if let acadrust::objects::ObjectType::Layout(layout) = obj {
|
||||
if let codec::objects::ObjectType::Layout(layout) = obj {
|
||||
if layout.name == name {
|
||||
layout.flags = layout_flags;
|
||||
crate::scene::apply_default_page_setup(layout, &plot_style);
|
||||
@@ -154,7 +154,7 @@ impl OpenCADStudio {
|
||||
.tabs[i]
|
||||
.scene
|
||||
.plot_settings_for(&layout)
|
||||
.unwrap_or_else(|| acadrust::objects::PlotSettings::new(String::new()));
|
||||
.unwrap_or_else(|| codec::objects::PlotSettings::new(String::new()));
|
||||
if let Some(paper) = req["paper"].as_str().filter(|p| !p.is_empty()) {
|
||||
let resolved = crate::io::paper_catalog::resolve(paper)
|
||||
.ok_or_else(|| failure("invalid_paper", format!("Unknown paper '{paper}'")))?;
|
||||
@@ -163,9 +163,9 @@ impl OpenCADStudio {
|
||||
ps.paper_width = width;
|
||||
ps.paper_height = height;
|
||||
ps.rotation = if req["orientation"].as_str().is_some_and(|o| o.eq_ignore_ascii_case("portrait")) {
|
||||
acadrust::objects::PlotRotation::None
|
||||
codec::objects::PlotRotation::None
|
||||
} else {
|
||||
acadrust::objects::PlotRotation::Degrees90
|
||||
codec::objects::PlotRotation::Degrees90
|
||||
};
|
||||
}
|
||||
if let Some(fit) = req["fit"].as_bool() {
|
||||
@@ -203,7 +203,7 @@ impl OpenCADStudio {
|
||||
if let Some(window) = req["window"].as_array().filter(|v| v.len() == 4) {
|
||||
let coord = |k: usize| window[k].as_f64().unwrap_or(0.0);
|
||||
ps.set_plot_window(coord(0), coord(1), coord(2), coord(3));
|
||||
ps.plot_type = acadrust::objects::PlotType::Window;
|
||||
ps.plot_type = codec::objects::PlotType::Window;
|
||||
}
|
||||
self.push_undo_snapshot(i, "PAGESETUP");
|
||||
if !self.tabs[i].scene.set_layout_plot_settings(&layout, &ps) {
|
||||
@@ -246,7 +246,7 @@ impl OpenCADStudio {
|
||||
)?;
|
||||
|
||||
// Clone out of the source first so the two tabs never borrow together.
|
||||
let cloned: Vec<acadrust::EntityType> = handles
|
||||
let cloned: Vec<codec::EntityType> = handles
|
||||
.iter()
|
||||
.filter_map(|handle| self.tabs[source_index].scene.document.get_entity(*handle))
|
||||
.map(|entity| entity.clone())
|
||||
@@ -254,7 +254,7 @@ impl OpenCADStudio {
|
||||
|
||||
// Copy the referenced layer definitions out of the source before the
|
||||
// target borrow starts (the two tabs never borrow together).
|
||||
let mut missing_layers: Vec<acadrust::tables::Layer> = Vec::new();
|
||||
let mut missing_layers: Vec<codec::tables::Layer> = Vec::new();
|
||||
{
|
||||
let source_document = &self.tabs[source_index].scene.document;
|
||||
for entity in &cloned {
|
||||
@@ -462,22 +462,22 @@ impl OpenCADStudio {
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
FILE_IDENTITY_APP,
|
||||
Some(vec![acadrust::xdata::XDataValue::String(identity.clone())]),
|
||||
Some(vec![codec::xdata::XDataValue::String(identity.clone())]),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let mut text = acadrust::entities::Text::with_value(
|
||||
let mut text = codec::entities::Text::with_value(
|
||||
FILE_IDENTITY_APP,
|
||||
acadrust::types::Vector3::ZERO,
|
||||
codec::types::Vector3::ZERO,
|
||||
)
|
||||
.with_height(0.0001);
|
||||
text.common.invisible = true;
|
||||
let handle = self.tabs[i].scene.add_entity(acadrust::EntityType::Text(text));
|
||||
let handle = self.tabs[i].scene.add_entity(codec::EntityType::Text(text));
|
||||
crate::scene::view::dispatch::set_entity_xdata(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
FILE_IDENTITY_APP,
|
||||
Some(vec![acadrust::xdata::XDataValue::String(identity.clone())]),
|
||||
Some(vec![codec::xdata::XDataValue::String(identity.clone())]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -492,7 +492,7 @@ impl OpenCADStudio {
|
||||
const FILE_IDENTITY_APP: &str = "SPMDOCUNIQUE";
|
||||
|
||||
/// The handle of the entity carrying the file identity, if present.
|
||||
fn find_file_identity_marker(document: &acadrust::CadDocument) -> Option<acadrust::Handle> {
|
||||
fn find_file_identity_marker(document: &codec::CadDocument) -> Option<codec::Handle> {
|
||||
document
|
||||
.entities()
|
||||
.find(|entity| {
|
||||
@@ -506,14 +506,14 @@ fn find_file_identity_marker(document: &acadrust::CadDocument) -> Option<acadrus
|
||||
}
|
||||
|
||||
/// The identity GUID from the marker entity's XData.
|
||||
fn marker_identity(document: &acadrust::CadDocument, handle: acadrust::Handle) -> Option<String> {
|
||||
fn marker_identity(document: &codec::CadDocument, handle: codec::Handle) -> Option<String> {
|
||||
let record = document
|
||||
.get_entity(handle)?
|
||||
.common()
|
||||
.extended_data
|
||||
.get_record(FILE_IDENTITY_APP)?;
|
||||
match record.values.first() {
|
||||
Some(acadrust::xdata::XDataValue::String(text)) => Some(text.clone()),
|
||||
Some(codec::xdata::XDataValue::String(text)) => Some(text.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -569,7 +569,7 @@ fn read_sysvar(scene: &crate::scene::Scene, name: &str) -> Option<Value> {
|
||||
Some(value)
|
||||
}
|
||||
|
||||
fn set_sysvar(document: &mut acadrust::CadDocument, name: &str, value: &Value) -> Result<(), Value> {
|
||||
fn set_sysvar(document: &mut codec::CadDocument, name: &str, value: &Value) -> Result<(), Value> {
|
||||
let failure = |message: &str| crate::app::control::failure("invalid_sysvar_value", message);
|
||||
let header = &mut document.header;
|
||||
match name.to_ascii_lowercase().as_str() {
|
||||
|
||||
Reference in new issue
Block a user