Inspection automation rarely crashes. It keeps running and returns a plausible wrong number, in a report that looks correct, signed by someone who had no reason to doubt it.
I write and validate the scripts that decide whether parts pass. Below: what I take on, then a catalogue of 38 documented ways this goes wrong — assembled from ZEISS's own documentation, release notes, published standards, and public practitioner threads.
2027 renames the entire Scripted* class family to Custom*. A diff of the published 2026 and 2027 API specifications shows 200 documented symbols becoming 339 — 44 removed, 183 added — and the scriptedelements and scripted_checks_util namespaces replaced outright. Separately, the examples repository is mid-migration to Python 3.14 with major-version jumps across numpy, pillow, opencv and websockets.
Worth doing before you upgrade, not after. The mechanical part is provable from two public URLs; you can check my work.
A script that decides pass/fail is quality-system software. 21 CFR 820.70(i) requires software automating any part of the quality system to be validated for its intended use under an established protocol, with documentation maintained; FDA's QMSR amendments took effect 2026-02-02. In most shops the inspection script has no protocol, no version, and no owner.
ZEISS already ships every primitive for this — versioned manifests, pytest integration, coverage. Nobody assembles them into a package. That is the whole job.
One published case: a ~7 mm bushing, 259 points, no filters, no outliers, measured three ways — maximum inscribed 7.07, least-squares 7.14, minimum circumscribed 7.33. 0.26 mm of spread from the choice of fit alone, larger than most tolerances on such a feature, with nothing in the report recording which was used.
Overnight and cell-integrated runs that survive an interactive dialog, and a path from inspection result to your QMS that isn't a PDF scrape. Provenance preserved through alignment and cavity — a merged CSV that loses which alignment produced which row is not a record.
Thirty-eight entries across seven domains. Every entry carries its evidentiary standing, because a catalogue that overstates what it knows is worth less than no catalogue at all.
Getting from a GUI action to code that runs twice. Everyone starts here; most stop here.
gom.script.* command layer has no reference documentationZEISS documents 22 gom.api.* namespaces across 91 entries. The gom.script.* layer — where import, alignment, inspection creation, export and reporting actually live — appears roughly 8 times in the entire 296 KB specification, always incidentally, with no reference section. The spec concedes it covers only a subset of the App programming specification
. Autocompletion doesn't reach it either.
The recorder is the sanctioned discovery mechanism and it emits structurally non-reusable code — enumerated lists like inspection['DISTANCE_1'] and literal template names. One user's conclusion: I cannot make this python script parametric for all programs if I can't do this.
This is the ceiling nearly every self-taught scripter hits, and nothing in the recorder's output hints at the way out.
Asked to script a report stage range, a user recorded the interaction and got four set_tab_visible calls plus one real command. His verdict: Not helpful for me. There are gui actions included.
Some operations emit nothing at all — a user trying to align report labels was told the software is not set up to do this.
gom.ElementSelection takes a 'category' key whose value is a flat alternating key/value list masquerading as a dict. A user who stated plainly he was familiar with all types of loops
still could not extract a name from the result, and resorted to regex-scraping the repr. A how-to page exists; the vocabulary is still not derivable from it.
Breakpoints existed in GOM Inspect 2021 and were gone in 2022, replaced by a VS Code extension. Users have pushed back four years running; a March 2026 thread is still open. One respondent names the affected population exactly: many 'non-professional programmers' work [with the integrated editor], who are simply system maintainers, measurement technicians or other users.
The software moves under the automation, on an annual schedule.
Scripted* class family was renamed to Custom* in 2027A mechanical diff of the published 2026 and 2027 API specifications: 200 documented gom.* symbols become 339 — 44 removed, 183 added. ScriptedElement → CustomElement, ScriptedActual → CustomActual, and the rest; the scriptedelements and scripted_checks_util namespaces are replaced outright.
Verifiable in under a minute. Several 2027 how-to URLs that exist on 2026 now return 404, and the examples repository still declares 'software-version': 'ZEISS INSPECT 2023' in places. Following the vendor's own examples leads to removed APIs.
From 2025, gom.read_parameters(globals()) is required to make parameters available. A script written before 2025 keeps running after the upgrade and simply sees stale defaults. The documented development idiom — guarding with if "variable_1" not in globals() — compounds it, because the fallback value is indistinguishable from a real one.
Projects still come in part-less (2016) and part-based (2018) flavours. Old proxy references don't fail loudly — per the documentation, a mapping takes place and the reference of the unique part is returned instead. The script runs; the elements it resolves may not be the ones intended.
The 2026 release notes state support for ISO 5459:2024 as default for planar datum features
. Read that as an operator: the default rule for establishing planar datums changed in a minor-year release. Datum establishment determines the datum reference frame, and the DRF determines every position and orientation value reported against it.
The deepest verifiable moat and the smallest tickets. Nobody bluffs a pywin32 bootstrap.
The single most repeated packaging failure: When I am working on the App all the included sub scripts load fine, but when I run the script after hitting the 'Finalize App' the script stops with the error that the imported scripts are not found.
Another replier: I too had a similar issue & remember banging my head against the wall for a solid day.
A live trigger is already in flight: the examples repository's open “Migration to Python 3.14.x” pull request, filed 2026-08-20 across 182 files, bumps numpy, pillow, opencv-python and websockets — every one a major-version jump. A Python 3.9-era App estate is heading into a coordinated migration.
Since 2023, each App has a separate script execution environment
— a script imports only from its own App. Sharing needs an "environment" key in every participating manifest, and the provider App must be finalized before its modules import, so the error appears during development and vanishes in production.
Documented verbatim: services are registered immediately when the App is installed, but they are not started automatically!
They sit STOPPED until someone starts them by hand. Custom diagrams execute as services, so a freshly deployed diagram App does nothing at all — with no error to explain why.
.addon is a hand-editable ZIP with a developer-generated UUID and no build pipelineAn App file is a ZIP archive with pre-defined folder- and filenames… it can also be unzipped/edited/zipped manually if necessary.
Folder names declare content type by convention. Workspaces must be written by hand. There is no build step, which means nothing to put a test or a version gate into.
An App can request an interpreter via "python-version" in its manifest. The catch is documented: Protected Apps can only be used with Python interpreters approved by ZEISS.
Commercial protection and version pinning are mutually exclusive — a constraint that surfaces only when you try to ship.
Architecture work with no vendor answer. The value is obvious the morning after an overnight run fails.
The starting-options page documents -license, -nosplash, -minimized, -config, -eval and -script. Nowhere is a headless or no-render mode documented. An overnight run across hundreds of parts means a full GUI process per invocation — and recorded scripts are full of interactive calls that block until someone clicks.
The working pattern is to string-format a dict into the -eval argument and read it inside the target script as a global that may or may not exist. An implicit, unvalidated, injection-prone contract between two processes — and when it fails, it fails by producing defaults rather than by stopping.
Two constraints interlock into the hardest design problem on the platform. The FAQ: Due the Python Global Interpreter Lock, the ZEISS INSPECT App API was implemented non-thread-safe only.
And the services guide: gom.script.* commands cannot be used in a service script!
So the concurrent tier cannot drive the application.
AttributeErrorA user hit module 'gom' has no attribute 'app' on a script that had worked for years. import gom succeeded; only gom.app was missing. His own diagnosis: It happened because my license was not detected properly.
A licensing failure wearing the costume of a code defect will consume a day.
An operator with a working export pipeline wanted to move it into Kiosk for operator-proofing: I can't find anything allowing me to run a script.
One post, no reply. A user in March 2026 asked the same thing from the other direction. The operator-facing mode is precisely where automation is most needed and least reachable.
Export, aggregation, integration. The largest budgets and the most contested space.
The most damning single line in the evidence base, from a user describing his live production process: Currently we scrap out PDFs to create csv files for our records.
The report PDF has become the system of record, and the machine-readable data is reconstructed from it afterwards — a lossy, unvalidatable round trip in a shop that plainly cares about records.
A user exporting a section from an offset plane as CSV — an entirely ordinary request — hits An error occurred during executing the XSLT file. Choose or enter a delimiter
, then This export format does not support element 'Plane Y +5.000 mm'
, and reports he can never enable the OK button.
The shape repeats across current topic titles: extracting strain across a large number of projects, bulk point inspection, automatic STL export, combining reports from two programs. The data exists inside N saved projects; the user needs one table. The GUI is built around one open project at a time.
Filed August 2026. Building a PDF filename from a stage-specific keyword, a user found the script only gives the value for the first stage
, and diagnosed it himself: in_stage counts only enabled stages while stages holds all of them. The practical result is eight parts inheriting part one's serial number, in filenames that look perfectly ordinary.
Certified Q-DAS export and a PiWeb REST API both exist. Between a check has a result and the MES knows this serial passed sits work nobody has done for you — Q-DAS DFQ files need specific K-fields populated before records are uniquely assignable, and nothing populates them by default.
Aerospace and automotive parts are routinely reported in several alignments. One user's actual process changes the alignment, exports a table per alignment into its own folder, then merges the CSVs — with nothing in the merged artifact recording which alignment produced which row.
The only domain a competing software consultant cannot enter at all.
A user measuring a ~7 mm circular bushing — 259 points, no filters, no outliers — reports three answers from the same data: maximum inscribed 7.07, least-squares 7.14, minimum circumscribed 7.33. That is 0.26 mm of spread, far larger than most tolerances on such a feature, produced entirely by the choice of fit. Nothing in the report records which was chosen, or why.
A user building an ISO GPS template from midplane datums reports the actual fitted plane and the displayed coordinate are not perfectly aligned. There appears to be a slight angular difference as well as a small positional offset.
The replies expose three separate traps at once — datum precedence, feature selection, and constraint order.
On an airfoil twist analysis: the Zeiss software automatically assigns clockwise as the positive direction for rotation (twist) analysis, I cannot find anywhere why this is or see any option to change it.
Over roughly two weeks the thread produced no authoritative answer — the best on offer was a guess.
ISO 14253-1:2017 requires the tolerance be reduced by the expanded measurement uncertainty to prove conformity, and expanded by it to prove nonconformity — leaving a band where neither can be proven. ISO/IEC 17025:2017 §7.8.6.1 requires a laboratory issuing a conformity statement to document the decision rule applied.
F4 needs a number, and most optical shops do not have one per characteristic. ISO 15530-3 derives task-specific uncertainty by substitution using calibrated workpieces, from four contributions — process systematic error, the measurement procedure, artifact calibration uncertainty, and material variation. It is explicitly task-specific, which is why a single system-level figure cannot stand in for it.
Compliance has a budget line. The vendor structurally cannot sell you your own quality system.
21 CFR 820.70(i) requires software used to automate any part of production or the quality system to be validated for its intended use under an established protocol, with documentation maintained. FDA's QMSR amendments took effect 2026-02-02, incorporating ISO 13485:2016 by reference. In most shops the inspection script has no protocol, no version, and no owner.
The App manifest already carries version, author, UUID, software-version and licensing fields. Testing is first-class — Both unit testing and integration testing is done with pytest and the coverage extension pytest-cov.
The gap is not capability. It is that nobody has assembled the pieces into a validation package.
Discovery works only if filenames start with test_ and test scripts contain only test_-prefixed functions — This means: no code outside of function definitions.
That forces the code under test to be importable functions too, which recorded-then-edited linear scripts are not. The path from recorded script to testable code is a rewrite.
Project history is what a shop points at when asked how a result was produced, and scripting degrades it in both directions. A user generating elements programmatically asks whether many actions can become one history step — I am making many points, but i would like to have it like one step
— with no answer in thread and no documented mechanism.
IATF 16949 §7.1.5.1.1 requires statistical studies analysing variation for each type of inspection and test equipment in the control plan; PPAP requires MSA studies in the submission. For a structured-light system the operator-reproducibility component is real — fixturing, spray, scan strategy — and the vendor's own example App carries a not-for-production disclaimer.
AS9102 Form 3 requires every characteristic on the design data to be uniquely ballooned, with the balloon number matching the row number exactly, and nothing on the drawing omitted. On a 247-characteristic drawing that is 247 rows that must reconcile — measurement living in INSPECT as named elements, accountability living in a spreadsheet.
Nothing here was reproduced in running software. Every entry marked [verified] means a cited source says this — never I observed this. It is assembled from vendor documentation, release notes, published standards, and public practitioner threads spanning 2018 to 2026 across several ZEISS INSPECT versions.
Some entries may describe behaviour already fixed in the release you are running. Before any of it is asserted about your installation, it gets reproduced against your specific version.
Two entries are weaker than the rest and are flagged as such: F4 rests on inferred vendor behaviour around decision rules, and D5 on a pair of unanswered forum posts. Neither should be quoted as settled.
If an entry describes something you recognise, that is the useful place to start — bring the number. Equally useful: telling me an entry is wrong, or fixed in your version. The catalogue is versioned and corrections are credited.