forked from 1b-agent/opencadstudio-upstream
feat(python): command runner and modify wrappers (doc.command, doc.modify)
HostApi::run_command drives the real OCS command through the same primitives the automation channel uses, synchronously on the host thread: run a line, start a command and answer point/text/token/entity/selection/enter prompts, or cancel (closing anything the command opened). Refused while another command is active, for a denylist (QUIT, NEW, OPEN, SAVE*, PY_* ...) and off the active tab. Python: doc.command, doc.start_command and doc.modify (offset, trim, extend, fillet, move, copy, rotate, scale, mirror, erase). Real-IPC audit checks exact geometry for every wrapper plus guards and undo; IPC and Python unit tests; docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
795aa922b4
commit
1b1785eefb
13 files changed
+867
-1
No files matched your search
@@ -46,11 +46,58 @@ class DocumentModelTests(unittest.TestCase):
|
||||
linetype_operation=lambda op, name, options: self.calls.append(("linetype", op, name, options)) or 9,
|
||||
layout_records=lambda: [{"name": n, "current": n == "Model"} for n in ("Model", "Sheet1")],
|
||||
layout_operation=lambda op, name, options: self.calls.append(("layout", op, name, options)) or 9,
|
||||
command_step=self._command_step,
|
||||
curve_samples=lambda handle, n: {"vertices": [[0, 0], [1, 0], [2, 0]], "elevation": 5.0, "closed": False},
|
||||
)
|
||||
namespace = {"ocs": self.ocs}
|
||||
exec(MODEL.read_text(), namespace)
|
||||
self.doc = self.ocs.active_document
|
||||
|
||||
def _command_step(self, kind, options):
|
||||
self.calls.append(("step", kind, options))
|
||||
outcome = {"status": "completed", "blocked_by": None, "command": "", "prompt": "", "accepts": [],
|
||||
"options": [], "entities": 0, "added": 0, "unconsumed": [], "error": None}
|
||||
outcome.update(self.step_outcome.get(kind, {}))
|
||||
if kind == "enter":
|
||||
new = max(self.records) + 1
|
||||
self.records[new] = {"handle": new, "kind": "Line", "layer": "0", "_editable": True}
|
||||
return outcome
|
||||
|
||||
def test_command_runner(self):
|
||||
self.step_outcome = {}
|
||||
out = self.doc.command("CIRCLE 0,0 5")
|
||||
self.assertEqual(self.calls[-1], ("step", "run", {"line": "CIRCLE 0,0 5"}))
|
||||
self.assertEqual(out["status"], "completed")
|
||||
self.step_outcome = {"run": {"status": "waiting_input", "prompt": "Select object"}}
|
||||
with self.assertRaises(RuntimeError):
|
||||
self.doc.command("OFFSET")
|
||||
self.assertEqual(self.calls[-1], ("step", "cancel", None))
|
||||
self.step_outcome = {"run": {"error": "bad"}}
|
||||
with self.assertRaises(RuntimeError):
|
||||
self.doc.command("X")
|
||||
waiting = {"status": "waiting_input"}
|
||||
self.step_outcome = {"start": waiting, "point": waiting, "entity": waiting}
|
||||
with self.doc.start_command("OFFSET") as command:
|
||||
command.point((1, 2))
|
||||
self.assertEqual(self.calls[-1], ("step", "point", {"point": [1.0, 2.0, 0.0]}))
|
||||
command.entity(self.doc.entities[1], at=(0, 0, 0))
|
||||
self.assertEqual(self.calls[-1][2]["handle"], 1)
|
||||
self.assertEqual(self.calls[-1], ("step", "cancel", None))
|
||||
|
||||
def test_modify_wrappers(self):
|
||||
self.step_outcome = {}
|
||||
self.doc.modify.trim(1, (3, 0, 0))
|
||||
steps = [c for c in self.calls if c[0] == "step"]
|
||||
self.assertEqual([c[1] for c in steps], ["start", "entity", "enter"])
|
||||
self.calls.clear()
|
||||
self.doc.modify.move([1, 2], (0, 0, 0), (5, 5, 0))
|
||||
self.assertEqual(self.calls[0], ("selection", [1, 2]))
|
||||
self.assertEqual([c[1] for c in self.calls if c[0] == "step"], ["start", "point", "point"])
|
||||
self.calls.clear()
|
||||
self.doc.modify.offset(1, 2, (0, 5))
|
||||
entity_step = [c for c in self.calls if c[0] == "step" and c[1] == "entity"][0]
|
||||
self.assertEqual(entity_step[2]["point"], [1, 0, 5.0])
|
||||
|
||||
def test_batch_edit_and_rollback(self):
|
||||
line = self.doc.entities[1]
|
||||
self.assertEqual(line.start, (1, 2, 3))
|
||||
|
||||
Reference in new issue
Block a user