Vis Automations · Python over OLE/COM

Headless COM Automation

Drives Vis Mockup with no clicks through its COM server VFFrame.Application: open a JT, read the tree, find, measure, read metadata, set the camera, snapshot, section, show PMI, capture and export.

Verification passing on Vis 2606.0 build 26134 + 2606.2610 · 2026-10-29 · repo tc-vis-automation 684255a

The server

Installing Vis registers the COM server VFFrame.Application (CLSID {50BE6892-9BD6-11D1-88EB-006097D423FB}, LocalServer32 Products\Mockup\VisView.exe). Its VisAppAutomation property is the root of the API: documents, views, the node tree, PMI, sections, clearance, snapshots, image capture, and export to JT, PLMXML, STEP and VRML. A Python client with pywin32 drives it with no clicks. Plain dynamic dispatch is the way: makepy refuses VFFrame because it carries no type information, and out-parameter methods return tuples under dynamic dispatch anyway.

The minimum script (EXERCISED)

import win32com.client.dynamic
vmu = win32com.client.dynamic.Dispatch("VFFrame.Application")   # launches VisView.exe
app = vmu.VisAppAutomation                                       # API root
doc = app.Documents.OpenDocument(r"C:\path\model.jt")
view = app.Documents.ActiveView
view.AllNodesOn()          # REQUIRED: a fresh open renders nothing
view.FitAll(); view.UpdateView()
view.CaptureImage(r"C:\out\shot.png")    # .png .jpg .bmp .ps by extension
nodes = view.AllNodes                    # VisDispNodeList
for i in range(nodes.NumNodes):          # zero-based; no Count / Item
    print(nodes.Node(i).PrintableName)
doc.CloseDocument(); app.Quit()

Call shapes, each read back in the validation run (EXERCISED)

TaskCall shapeRead back as proof
Findview.GetNodeFromCADID(cadid); view.Find(0, 0, "I-DEAS Part Number", 2, "123-006", "")same node back; 4 hits; fake value 0
Geometrynode.BBox (6 floats); node.GetPhysicalProperties()max > min; area, volume > 0; centroid inside BBox
Metadatanode.MetaDataProperties .NumProperties, .GetProperty(i), .GetPropertyByName(k)14 props; both reads agree
Isolateview.AllNodesOff(); node.Visible = True; FitAll()0 visible, blank capture; one part renders
Cameraview.SetCameraPosition(up, lookAt, eye); GetCameraPosition()read back; iso differs from front by 17% of pixels
Snapshotview.SnapShotManager.AddSnapShot(n); .NumSnapShots; .Name(i)0 to 1, "Snapshot 1"
Sectionview.SectionManager.CreateSection(offset, nx, ny, nz); .Clipping = 1; DeleteAllSections()9% pixel change; restored exactly
Exportdoc.Export(2, path, "") PLMXML; 0 JT; 4 STEP81 PLMXML instances; JT Version header; STEP ISO-10303-21
PMIview.PMIManager.PMIQuery.GetAllPMIInModel() .NumPMIEntities, .PMIEntity(i).Name, .Show(), .Hide()64 on 1_PMI_Block.jt; callouts on vs off

Export type codes from the API reference: DIRECTMODEL (JT) 0, VRML 1, PLMXML 2, ROBFACE 3, STEP 4, BLK 5. JT, PLMXML and STEP were run; VRML, ROBFACE and BLK are DERIVED.

The tools in the repo

tools/vis_smoke.pyOpen, show all, fit, capture, check the capture has geometry, quit. Exit 0 pass, 1 a check failed, 2 could not run. Writes smoke_report.json.
tools/vis_validate.pyThe twelve tasks above in one session, each asserting on what it read back or wrote. Writes captures and validate_report.json.
tools/vis_census.pyCensus of a folder of JT files: which file is the top assembly, node counts, and per part its name, instance count, bounding box, volume and metadata. Read-only; writes census.json.

All three refuse to start while a Vis session is open, because Dispatch would attach to it. See Traps.