feat(automation): richer query geometry + layers/header introspection

query now emits geometry for Ellipse/Text/MText/Polyline/Insert (on top
of Line/Circle/Arc/Point). Add layers op (name, ACI/RGB colour, on/off,
frozen, locked + current layer) and header op (units, PDMODE/PDSIZE,
LTSCALE, annotation scale, current layer/text style). ocs.py gains
layers()/header(). Lets an agent read the drawing's structure and
settings, not just entity counts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-18 01:50:58 +03:00
commit d4f5ed3135
3 changed files with 80 additions and 1 deletions

View file

@ -19,7 +19,9 @@ can act → observe → act.
| `{"op":"open","path":"plan.dwg"}` | entity summary |
| `{"op":"run","cmd":"LAYER Walls"}` | `{"ok":true,"cmd":...,"entities":N,"added":D}` |
| `{"op":"entities"}` | `{"ok":true,"total":N,"by_type":{"Line":42,...}}` |
| `{"op":"query","type":"Line","layer":"Walls"}` | per-entity `{handle,type,layer,…geometry}` (filters + `limit` optional) |
| `{"op":"query","type":"Line","layer":"Walls"}` | per-entity `{handle,type,layer,…geometry}` (Line/Circle/Arc/Point/Ellipse/Text/MText/Polyline/Insert; filters + `limit` optional) |
| `{"op":"layers"}` | layers `{name,color,off,frozen,locked}` + the current layer |
| `{"op":"header"}` | drawing variables (units, PDMODE/PDSIZE, LTSCALE, …) |
| `{"op":"select","handles":["2B"]}` | set the selection (by `handles`, `type`, or `layer`; `clear` to deselect) → `{"ok":true,"selected":N}` |
| `{"op":"undo"}` / `{"op":"redo"}` | step the document history → entity summary |
| `{"op":"save","path":"out.dwg"}` | `{"ok":true,"saved":"out.dwg"}` (path optional once opened/saved) |

View file

@ -79,6 +79,14 @@ class Ocs:
"""List entities (handle, type, layer, geometry), optionally filtered."""
return self._send(op="query", type=type, layer=layer, limit=limit)
def layers(self) -> dict[str, Any]:
"""List layers (name, color, on/off, frozen, locked) and the current one."""
return self._send(op="layers")
def header(self) -> dict[str, Any]:
"""Read drawing header variables (units, PDMODE/PDSIZE, LTSCALE, …)."""
return self._send(op="header")
def select(
self,
handles: Optional[list[str]] = None,

View file

@ -86,6 +86,31 @@ fn entity_json(e: &acadrust::EntityType) -> Value {
E::Point(p) => {
map.insert("location".into(), v3(p.location));
}
E::Ellipse(el) => {
map.insert("center".into(), v3(el.center));
map.insert("major_axis".into(), v3(el.major_axis));
}
E::Text(t) => {
map.insert("value".into(), json!(t.value));
map.insert("position".into(), v3(t.insertion_point));
map.insert("height".into(), json!(t.height));
}
E::MText(t) => {
map.insert("value".into(), json!(t.value));
map.insert("position".into(), v3(t.insertion_point));
map.insert("height".into(), json!(t.height));
}
E::LwPolyline(pl) => {
let pts: Vec<Value> = pl
.vertices
.iter()
.map(|v| json!([v.location.x, v.location.y]))
.collect();
map.insert("vertices".into(), json!(pts));
}
E::Insert(ins) => {
map.insert("block".into(), json!(ins.block_name));
}
_ => {}
}
obj
@ -151,6 +176,50 @@ impl OpenCADStudio {
}
"entities" => self.entity_summary(),
"query" => self.entity_query(&req),
"layers" => {
let i = self.active_tab;
let layers: Vec<Value> = self
.tabs[i]
.scene
.document
.layers
.iter()
.map(|l| {
let mut o = json!({
"name": l.name,
"off": l.is_off(),
"frozen": l.is_frozen(),
"locked": l.is_locked(),
});
let m = o.as_object_mut().expect("json object");
if let Some(aci) = l.color.index() {
m.insert("color".into(), json!(aci));
}
if let Some((r, g, b)) = l.color.rgb() {
m.insert("rgb".into(), json!([r, g, b]));
}
o
})
.collect();
json!({
"ok": true,
"current": self.tabs[i].scene.document.header.current_layer_name,
"layers": layers,
})
}
"header" => {
let h = &self.tabs[self.active_tab].scene.document.header;
json!({
"ok": true,
"current_layer": h.current_layer_name,
"current_text_style": h.current_text_style_name,
"insertion_units": h.insertion_units,
"pdmode": h.point_display_mode,
"pdsize": h.point_display_size,
"ltscale": h.linetype_scale,
"annotation_scale_value": h.annotation_scale_value,
})
}
"undo" => {
let _ = self.update(super::Message::Undo);
self.entity_summary()