forked from 1b-agent/opencadstudio-upstream
Complete Tolerance host model coverage
This commit is contained in:
1 parent
0304596cf8
commit
3387031038
6 files changed
+205
-10
No files matched your search
@@ -583,7 +583,9 @@ fn generate_entity_coverage(out_dir: &Path, registry: &TypeRegistry) {
|
||||
("Solid", "first_corner" | "second_corner" | "third_corner" | "fourth_corner" | "normal" | "thickness") |
|
||||
("Face3D", "first_corner" | "second_corner" | "third_corner" | "fourth_corner" | "invisible_edges") |
|
||||
("Insert", "insert_point" | "x_scale" | "y_scale" | "z_scale" | "rotation" | "normal" |
|
||||
"column_count" | "row_count" | "column_spacing" | "row_spacing")) {
|
||||
"column_count" | "row_count" | "column_spacing" | "row_spacing") |
|
||||
("Tolerance", "insertion_point" | "direction" | "normal" | "text" |
|
||||
"dimension_style_name" | "text_height" | "dimension_gap")) {
|
||||
"transaction_geometry"
|
||||
} else { "type_conversion_only" };
|
||||
properties.push(PropertyCoverage {
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
"XLine": ["layer", "base_point", "direction"],
|
||||
"Solid": ["layer", "first_corner", "second_corner", "third_corner", "fourth_corner", "normal", "thickness", "is_trace"],
|
||||
"Face3D": ["layer", "first_corner", "second_corner", "third_corner", "fourth_corner", "invisible_edges"],
|
||||
"Insert": ["layer", "insert_point", "x_scale", "y_scale", "z_scale", "rotation", "normal", "column_count", "row_count", "column_spacing", "row_spacing"]
|
||||
"Insert": ["layer", "insert_point", "x_scale", "y_scale", "z_scale", "rotation", "normal", "column_count", "row_count", "column_spacing", "row_spacing"],
|
||||
"Tolerance": ["layer", "insertion_point", "direction", "normal", "text", "dimension_style_name", "text_height", "dimension_gap"]
|
||||
},
|
||||
"readable": {
|
||||
"Insert": ["block_name"]
|
||||
"Insert": ["block_name"],
|
||||
"Tolerance": ["dimension_style_handle"]
|
||||
},
|
||||
"aliases": {
|
||||
"Polyline": {"closed": "flags"},
|
||||
|
||||
@@ -79,6 +79,17 @@ pub fn validate_new_canvas_entity(entity: &crate::host::EntityType) -> Result<()
|
||||
return Err("Face3D.invisible_edges has unknown bits".into());
|
||||
}
|
||||
}
|
||||
EntityType::Tolerance(value) => {
|
||||
finite_vector("Tolerance.insertion_point", &value.insertion_point)?;
|
||||
unit_direction("Tolerance.direction", &value.direction)?;
|
||||
solid_normal("Tolerance.normal", &value.normal)?;
|
||||
if value.text.trim().is_empty() { return Err("Tolerance.text is empty".into()); }
|
||||
if value.dimension_style_name.trim().is_empty() { return Err("Tolerance.dimension_style_name is empty".into()); }
|
||||
if !value.text_height.is_finite() || value.text_height <= 0.0 {
|
||||
return Err("Tolerance.text_height must be finite and greater than zero".into());
|
||||
}
|
||||
if !value.dimension_gap.is_finite() { return Err("Tolerance.dimension_gap must be finite".into()); }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
@@ -201,11 +212,49 @@ pub fn validate_entity_mutation(
|
||||
return Err("Insert array counts must be greater than zero".into());
|
||||
}
|
||||
}
|
||||
(EntityType::Tolerance(old), EntityType::Tolerance(new)) => {
|
||||
changed3("Tolerance.insertion_point", &old.insertion_point, &new.insertion_point)?;
|
||||
if !same3(&old.direction, &new.direction) { unit_direction("Tolerance.direction", &new.direction)?; }
|
||||
if !same3(&old.normal, &new.normal) { solid_normal("Tolerance.normal", &new.normal)?; }
|
||||
if old.text != new.text && new.text.trim().is_empty() { return Err("Tolerance.text is empty".into()); }
|
||||
if old.dimension_style_name != new.dimension_style_name && new.dimension_style_name.trim().is_empty() {
|
||||
return Err("Tolerance.dimension_style_name is empty".into());
|
||||
}
|
||||
if old.text_height.to_bits() != new.text_height.to_bits()
|
||||
&& (!new.text_height.is_finite() || new.text_height <= 0.0) {
|
||||
return Err("Tolerance.text_height must be finite and greater than zero".into());
|
||||
}
|
||||
if old.dimension_gap.to_bits() != new.dimension_gap.to_bits() && !new.dimension_gap.is_finite() {
|
||||
return Err("Tolerance.dimension_gap must be finite".into());
|
||||
}
|
||||
if old.dimension_style_handle != new.dimension_style_handle
|
||||
&& !(old.dimension_style_name != new.dimension_style_name && new.dimension_style_handle.is_none()) {
|
||||
return Err("Tolerance.dimension_style_handle is read-only".into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate references which require the surrounding document. Keeping this
|
||||
/// in the host API gives every scripting adapter the same reference rules.
|
||||
#[cfg(feature = "host")]
|
||||
pub fn validate_canvas_entity_references(
|
||||
document: &crate::host::CadDocument,
|
||||
entity: &crate::host::EntityType,
|
||||
) -> Result<(), String> {
|
||||
use crate::host::EntityType;
|
||||
if let EntityType::Tolerance(value) = entity {
|
||||
let found = value.dimension_style_handle.filter(|handle| !handle.is_null()).map_or_else(
|
||||
|| document.dim_styles.iter().any(|style| style.name.eq_ignore_ascii_case(value.dimension_style_name.trim())),
|
||||
|handle| document.dim_styles.iter().any(|style| style.handle == handle),
|
||||
);
|
||||
if !found { return Err(format!("Tolerance dimension style {:?} does not exist", value.dimension_style_name)); }
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clone a canvas entity with a new layer, preserving its other snapshot
|
||||
/// fields. Internal records and opaque fallbacks are excluded.
|
||||
#[cfg(feature = "host")]
|
||||
@@ -256,7 +305,7 @@ mod tests {
|
||||
if entry.scope != EntityScope::Canvas || !matches!(entry.kind.as_str(),
|
||||
"Point" | "Line" | "Circle" | "Arc" | "Ellipse" | "Polyline" |
|
||||
"Polyline2D" | "Polyline3D" | "LwPolyline" | "Spline" | "Text" | "MText" |
|
||||
"Ray" | "XLine" | "Solid" | "Face3D" | "Insert") {
|
||||
"Ray" | "XLine" | "Solid" | "Face3D" | "Insert" | "Tolerance") {
|
||||
assert!(entry.properties.iter().all(|p| p.model_access != ModelAccess::ReadWrite
|
||||
|| (entry.scope == EntityScope::Canvas && p.name == "layer")), "{}", entry.kind);
|
||||
}
|
||||
@@ -288,9 +337,33 @@ mod tests {
|
||||
"Insert.rotation".to_owned(), "Insert.normal".to_owned(),
|
||||
"Insert.column_count".to_owned(), "Insert.row_count".to_owned(),
|
||||
"Insert.column_spacing".to_owned(), "Insert.row_spacing".to_owned(),
|
||||
"Tolerance.insertion_point".to_owned(), "Tolerance.direction".to_owned(),
|
||||
"Tolerance.normal".to_owned(), "Tolerance.text".to_owned(),
|
||||
"Tolerance.dimension_style_name".to_owned(), "Tolerance.text_height".to_owned(),
|
||||
"Tolerance.dimension_gap".to_owned(),
|
||||
]));
|
||||
}
|
||||
|
||||
#[cfg(feature = "host")]
|
||||
#[test]
|
||||
fn tolerance_geometry_and_style_references_are_validated() {
|
||||
use crate::host::acadrust::{self, types::Vector3};
|
||||
let document = acadrust::CadDocument::new();
|
||||
let mut tolerance = acadrust::entities::Tolerance::with_text(
|
||||
Vector3::new(1.0, 2.0, 0.0), "{\\Fgdt;p}%%v0.1");
|
||||
let entity = acadrust::EntityType::Tolerance(tolerance.clone());
|
||||
validate_new_canvas_entity(&entity).unwrap();
|
||||
validate_canvas_entity_references(&document, &entity).unwrap();
|
||||
tolerance.direction = Vector3::new(2.0, 0.0, 0.0);
|
||||
assert!(validate_new_canvas_entity(&acadrust::EntityType::Tolerance(tolerance.clone()))
|
||||
.unwrap_err().contains("unit vector"));
|
||||
tolerance.direction = Vector3::UNIT_X;
|
||||
tolerance.dimension_style_name = "Missing".into();
|
||||
assert!(validate_canvas_entity_references(
|
||||
&document, &acadrust::EntityType::Tolerance(tolerance))
|
||||
.unwrap_err().contains("does not exist"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_write_coverage_matches_canvas_scope() {
|
||||
let catalog: EntityCoverageCatalog = serde_json::from_str(get_embedded_entity_coverage_json()).unwrap();
|
||||
|
||||
@@ -105,3 +105,5 @@ Start with the **Phase 8 integration harness and coverage ledger**. Re-evaluate
|
||||
- The opt-in `staged_python_point_pick_and_cancel_over_real_ipc` test passes with the same runner. It requests point and entity picks through Python, drives the app's active-command point/object-pick callbacks, polls the returned tokens through a later Python run, and verifies Enter cancellation. The callbacks are invoked by the test harness, not by a physical GUI pointer event; live GUI verification remains open.
|
||||
- The opt-in `staged_python_tabs_isolate_tokens_and_notifications` test passes with two real OCS document tabs and the staged runner. A token created in tab 0 cannot be observed or consumed in tab 1 before or after completion. Host-to-plugin DrawingChanged notifications for both tab ids are delivered over IPC and drained only by their owning tab. A 257-event burst reports overflow, and `DocumentTabClosed` discards queued events for that tab.
|
||||
- Added `doc.create_entity(kind, **properties)` and `doc.delete_entity(entity_or_handle)` to the high-level Python document model. Unit tests cover forwarding and managed identity fields. The real staged-plugin Line lifecycle now uses these methods rather than low-level `ocs.add`/`remove_entity`.
|
||||
- Closed the first new-kind increment for `Tolerance`. The host coverage catalog now maps insertion point, direction, normal, text, dimension style name, text height, and gap; validates geometry and document style references; keeps the style handle read-only; and leaves the undocumented DWG short unmapped. The thin adapter adds generated conversion plus stale style-handle clearing when the style name changes.
|
||||
- `staged_python_tolerance_lifecycle_over_real_ipc` passes the full C/R/E/D/U/I/W/V/P gate with the actual runner: high-level create/read/transaction edit/delete, live canvas selection, undo/redo, invalid direction and missing style rollback, untouched-field preservation, and DWG/DXF reopen. Tolerance is recorded as **Complete** in the ledger. The queue now continues with `Shape`.
|
||||
@@ -1,13 +1,13 @@
|
||||
# OCS canvas kind coverage ledger
|
||||
|
||||
Use with [the completion plan](plugin-host-model-completion-plan.md). The baseline below is conservative as of 19 September 2026. `Mapped` means at least one non-layer field is exposed for Python writes; it does **not** mean the kind passes the completion gate. `Update-only` means Python creation is unavailable. `Layer-only` means raw inspection and canvas layer edits exist, but geometry is not mapped. Record actual evidence or a blocking issue before changing any status to `Complete`.
|
||||
Use with [the completion plan](plugin-host-model-completion-plan.md). This ledger is current as of 19 September 2026: **18 of 43 canvas kinds have explicit geometry writes** (17 creatable, plus update-only `Insert`), **1 kind is complete**, and 25 remain layer-only. `Mapped` means at least one non-layer field is exposed for Python writes; it does **not** mean the kind passes the completion gate. `Update-only` means Python creation is unavailable. `Layer-only` means raw inspection and canvas layer edits exist, but geometry is not mapped. Record actual evidence or a blocking issue before changing any status to `Complete`.
|
||||
|
||||
Completion gate codes: **C** = create through Python document model; **R** = read; **E** = edit defining geometry and another property; **D** = delete; **U** = undo/redo and atomic failure; **I** = real IPC plus GUI/canvas pick or observable behavior; **W** = DWG and DXF save/reopen; **V** = invalid-input checks and untouched-field/reference preservation; **P** = portable H7 v5 build. A kind is `Complete` only when **C/R/E/D/U/I/W/V/P** all pass and its property catalog matches implementation. `Blocked` requires a specific dependency or failing fixture.
|
||||
|
||||
| Kind | Baseline | Gate evidence / blocker |
|
||||
|---|---|---|
|
||||
| Point | Mapped | Host transaction test now checks invalid batch rollback, grouped edit undo, and redo; creation/deletion history, real plugin execution, GUI, and DWG/DXF gates remain pending. |
|
||||
| Line | Integration-tested | High-level document-model create/delete and transaction edit pass through the actual staged-plugin IPC runner; three undo entries, undo/redo, duplicate-handle atomic rejection, and edited DWG/DXF reopen pass in `staged_python_plugin_line_lifecycle_over_real_ipc`. GUI pick/visible-geometry observation and broader invalid-value/unmapped-field preservation checks remain pending; not complete. |
|
||||
| Line | Integration-tested | High-level document-model create/delete and transaction edit pass through the actual staged-plugin IPC runner; three undo entries, undo/redo, duplicate-handle and nonfinite-coordinate rejection, full typed-entity preservation, Line object-pick routing, and edited DWG/DXF reopen pass. A second editable property outside defining endpoint geometry still needs integration coverage before this row is complete. |
|
||||
| Circle | Mapped | Full gate pending. |
|
||||
| Arc | Mapped | Full gate pending. |
|
||||
| Ellipse | Mapped | Existing focused DWG conversion test; full gate pending. |
|
||||
@@ -23,7 +23,7 @@ Completion gate codes: **C** = create through Python document model; **R** = rea
|
||||
| Solid | Mapped | Focused DWG round trip; full gate pending. |
|
||||
| Face3D | Mapped | Focused DWG round trip; full gate pending. |
|
||||
| Insert | Update-only | Transform transaction and undo tested; Python creation, deletion, real IPC/GUI and DWG/DXF gates pending. |
|
||||
| Tolerance | Layer-only | Next new kind after integration harness. |
|
||||
| Tolerance | **Complete** | `staged_python_tolerance_lifecycle_over_real_ipc` passes C/R/E/D/U/I/W/V/P with the staged API v7 plugin: high-level create/read/transaction edit/delete; insertion, direction, and text edits; live canvas selection; grouped undo/redo; invalid direction and missing style atomic rollback; exact untouched common/style/private-field preservation; and edited DWG plus DXF reopen. Host-owned coverage exposes placement, direction, normal, text, style name, height and gap; style handle is read-only and the undocumented DWG short remains unmapped. `tolerance_geometry_and_style_references_are_validated` and the existing Tolerance tessellation tests cover reference and rendered-frame behavior. |
|
||||
| Shape | Layer-only | Requires valid shape/style fixture. |
|
||||
| AttributeDefinition | Layer-only | Requires block-definition fixture. |
|
||||
| AttributeEntity | Layer-only | Requires linked Insert and attribute-sequence fixture. |
|
||||
|
||||
+119
-3
@@ -154,6 +154,9 @@ impl<'a> HostSession<'a> {
|
||||
}
|
||||
ocs_plugin_api::entity_coverage::validate_entity_mutation(existing, entity)
|
||||
.map_err(|error| format!("entity {handle:?}: {error}"))?;
|
||||
ocs_plugin_api::entity_coverage::validate_canvas_entity_references(
|
||||
self.document(), entity,
|
||||
).map_err(|error| format!("entity {handle:?}: {error}"))?;
|
||||
if self.app.tabs[self.tab].scene.is_layer_locked(handle) {
|
||||
return Err(format!("entity {handle:?} is on a locked layer"));
|
||||
}
|
||||
@@ -641,7 +644,7 @@ fn plugin_step_to_result(step: ocs_plugin_api::host::CommandStep) -> crate::comm
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::app::OpenCADStudio;
|
||||
use acadrust::entities::Point;
|
||||
use acadrust::entities::{Line, Point};
|
||||
use acadrust::xdata::XDataValue;
|
||||
use ocs_plugin_api::host::DocumentReader;
|
||||
|
||||
@@ -742,11 +745,23 @@ mod tests {
|
||||
assert_eq!(handles.len(), 1, "Python add did not reach the host");
|
||||
let handle = handles[0];
|
||||
assert!(matches!(host.document().get_entity(handle), Some(EntityType::Line(line)) if line.end.x == 1.0));
|
||||
let before_edit = host.document().get_entity(handle).unwrap().clone();
|
||||
dispatch(&mut host, &format!(
|
||||
"PY_EVAL ocs.update_many('Move line', [{{'handle':{},'end':{{'x':5.0,'y':0.0,'z':0.0}}}}])",
|
||||
handle.value()
|
||||
));
|
||||
assert!(matches!(host.document().get_entity(handle), Some(EntityType::Line(line)) if line.end.x == 5.0));
|
||||
let mut expected_after_edit = before_edit;
|
||||
let EntityType::Line(expected_line) = &mut expected_after_edit else { unreachable!() };
|
||||
expected_line.end.x = 5.0;
|
||||
assert_eq!(host.document().get_entity(handle), Some(&expected_after_edit),
|
||||
"partial Python edit must preserve every unmentioned common and geometry field");
|
||||
dispatch(&mut host, &format!(
|
||||
"PY_EVAL ocs.update_many('Reject NaN', [{{'handle':{},'start':{{'x':float('nan'),'y':0.0,'z':0.0}}}}])",
|
||||
handle.value()
|
||||
));
|
||||
assert_eq!(host.document().get_entity(handle), Some(&expected_after_edit),
|
||||
"invalid geometry changed the line");
|
||||
assert!(host.app.command_line.history.last().unwrap().text.contains("finite coordinates"));
|
||||
dispatch(&mut host, &format!(
|
||||
"PY_EVAL ocs.update_many('Reject duplicate', [{{'handle':{},'end':{{'x':8.0,'y':0.0,'z':0.0}}}},{{'handle':{},'end':{{'x':9.0,'y':0.0,'z':0.0}}}}])",
|
||||
handle.value(), handle.value()
|
||||
@@ -778,6 +793,104 @@ mod tests {
|
||||
assert!(app.tabs[0].scene.document.get_entity(handle).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staged_python_tolerance_lifecycle_over_real_ipc() {
|
||||
let Some(plugin_path) = std::env::var_os("OCS_TEST_PYTHON_PLUGIN") else { return; };
|
||||
let runner_path = std::env::var_os("OCS_PLUGIN_RUNNER_EXE")
|
||||
.expect("set OCS_PLUGIN_RUNNER_EXE to the built OpenCADStudio executable");
|
||||
assert!(std::path::Path::new(&runner_path).is_file());
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.tabs[0].is_start = false;
|
||||
let mut host = HostSession::new(&mut app, 0);
|
||||
let process = ocs_plugin_api::process::PluginProcess::spawn(
|
||||
std::path::Path::new(&plugin_path), &mut host,
|
||||
crate::plugin::v4_support::notification_handler(),
|
||||
).expect("spawn staged Python plugin");
|
||||
let dispatch = |host: &mut HostSession<'_>, command: &str| {
|
||||
assert!(process.dispatch(host, command, &mut |_| {}).expect("Python dispatch"));
|
||||
};
|
||||
dispatch(&mut host, concat!(
|
||||
"PY_EVAL ocs.active_document.create_entity('Tolerance',",
|
||||
"insertion_point={'x':1.0,'y':2.0,'z':0.0},",
|
||||
"text='POSITION%%v0.1').handle"
|
||||
));
|
||||
let handle = host.document().entities().next().expect("Python created a Tolerance")
|
||||
.common().handle;
|
||||
let Some(EntityType::Tolerance(created)) = host.document().get_entity(handle) else {
|
||||
panic!("expected Tolerance");
|
||||
};
|
||||
assert_eq!(created.insertion_point, acadrust::types::Vector3::new(1.0, 2.0, 0.0));
|
||||
assert_eq!(created.dimension_style_name, "Standard");
|
||||
let created = created.clone();
|
||||
dispatch(&mut host, &format!(
|
||||
"PY_EVAL ocs.active_document.entities[{}].text", handle.value()));
|
||||
assert!(host.app.command_line.history.last().unwrap().text.contains("POSITION%%v0.1"));
|
||||
|
||||
let script_path = std::env::temp_dir().join(format!(
|
||||
"ocs_tolerance_document_model_{}.py", std::process::id()));
|
||||
std::fs::write(&script_path, format!(concat!(
|
||||
"doc = ocs.active_document\n",
|
||||
"tolerance = doc.entities[{}]\n",
|
||||
"with doc.transaction('Edit tolerance'):\n",
|
||||
" tolerance.insertion_point = (4.0, 5.0, 0.0)\n",
|
||||
" tolerance.direction = (0.0, 1.0, 0.0)\n",
|
||||
" tolerance.text = 'POSITION%%v0.2'\n",
|
||||
"doc.selection = [tolerance]\n"
|
||||
), handle.value())).unwrap();
|
||||
dispatch(&mut host, &format!("PY_RUN {}", script_path.display()));
|
||||
let _ = std::fs::remove_file(&script_path);
|
||||
let expected = host.document().get_entity(handle).unwrap().clone();
|
||||
let EntityType::Tolerance(edited) = &expected else { unreachable!() };
|
||||
assert_eq!(edited.insertion_point, acadrust::types::Vector3::new(4.0, 5.0, 0.0));
|
||||
assert_eq!(edited.direction, acadrust::types::Vector3::UNIT_Y);
|
||||
assert_eq!(edited.text, "POSITION%%v0.2");
|
||||
assert_eq!(edited.common, created.common);
|
||||
assert_eq!(edited.normal, created.normal);
|
||||
assert_eq!(edited.dimension_style_name, created.dimension_style_name);
|
||||
assert_eq!(edited.dimension_style_handle, created.dimension_style_handle);
|
||||
assert_eq!(edited.text_height, created.text_height);
|
||||
assert_eq!(edited.dimension_gap, created.dimension_gap);
|
||||
assert_eq!(edited.dwg_unknown_short, created.dwg_unknown_short);
|
||||
assert_eq!(host.selection(), vec![handle], "Python selection did not reach the canvas");
|
||||
|
||||
dispatch(&mut host, &format!(
|
||||
"PY_EVAL ocs.update_many('Reject direction', [{{'handle':{},'direction':{{'x':2.0,'y':0.0,'z':0.0}}}}])",
|
||||
handle.value()));
|
||||
assert_eq!(host.document().get_entity(handle), Some(&expected));
|
||||
assert!(host.app.command_line.history.last().unwrap().text.contains("unit vector"));
|
||||
dispatch(&mut host, &format!(
|
||||
"PY_EVAL ocs.update_many('Reject style', [{{'handle':{},'dimension_style_name':'Missing'}}])",
|
||||
handle.value()));
|
||||
assert_eq!(host.document().get_entity(handle), Some(&expected));
|
||||
assert!(host.app.command_line.history.last().unwrap().text.contains("does not exist"));
|
||||
|
||||
let dwg_bytes = acadrust::DwgWriter::write_to_vec(host.document()).unwrap();
|
||||
let dwg_doc = acadrust::DwgReader::from_stream(std::io::Cursor::new(dwg_bytes)).read().unwrap();
|
||||
assert!(matches!(dwg_doc.get_entity(handle), Some(EntityType::Tolerance(value))
|
||||
if value.insertion_point.x == 4.0 && value.text == "POSITION%%v0.2"));
|
||||
let dxf_bytes = acadrust::DxfWriter::new(host.document()).write_to_vec().unwrap();
|
||||
let dxf_doc = acadrust::DxfReader::from_reader(std::io::Cursor::new(dxf_bytes))
|
||||
.unwrap().read().unwrap();
|
||||
assert!(matches!(dxf_doc.get_entity(handle), Some(EntityType::Tolerance(value))
|
||||
if value.insertion_point.x == 4.0 && value.text == "POSITION%%v0.2"));
|
||||
|
||||
dispatch(&mut host, &format!("PY_EVAL ocs.active_document.delete_entity({})", handle.value()));
|
||||
assert!(host.document().get_entity(handle).is_none());
|
||||
drop(process);
|
||||
drop(host);
|
||||
app.finish_pending_history(0);
|
||||
assert_eq!(app.tabs[0].history.undo_stack.len(), 3);
|
||||
app.undo_steps(1);
|
||||
assert_eq!(app.tabs[0].scene.document.get_entity(handle), Some(&expected));
|
||||
app.undo_steps(1);
|
||||
assert!(matches!(app.tabs[0].scene.document.get_entity(handle), Some(EntityType::Tolerance(value))
|
||||
if value.insertion_point.x == 1.0 && value.text == "POSITION%%v0.1"));
|
||||
app.undo_steps(1);
|
||||
assert!(app.tabs[0].scene.document.get_entity(handle).is_none());
|
||||
app.redo_steps(3);
|
||||
assert!(app.tabs[0].scene.document.get_entity(handle).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staged_python_point_pick_and_cancel_over_real_ipc() {
|
||||
let Some(plugin_path) = std::env::var_os("OCS_TEST_PYTHON_PLUGIN") else { return; };
|
||||
@@ -823,7 +936,10 @@ mod tests {
|
||||
|
||||
let target = {
|
||||
let mut host = HostSession::new(&mut app, 0);
|
||||
host.add_entity(EntityType::Point(Point::at(acadrust::types::Vector3::new(6.0, 7.0, 0.0))))
|
||||
host.add_entity(EntityType::Line(Line::from_points(
|
||||
acadrust::types::Vector3::new(6.0, 7.0, 0.0),
|
||||
acadrust::types::Vector3::new(9.0, 7.0, 0.0),
|
||||
)))
|
||||
};
|
||||
let entity_token = request(&mut app, "Pick an entity", true);
|
||||
assert!(app.tabs[0].active_cmd.as_ref().unwrap().needs_entity_pick());
|
||||
|
||||
Reference in new issue
Block a user