Development

NX-Style changes nothing in FreeCAD. It is a FreeCAD module that rebuilds the main window at start, plus a launcher that gives it a private profile.

Repository layout

Path Holds

launch_nx.py

The launcher: finds FreeCAD, prepares the NX profile, starts FreeCAD with -M NXStyle (and Strata and Katydid), runs the test harnesses on a hidden desktop with a memory ceiling. Standard library only.

NXStyle/

The FreeCAD module. InitGui.py hands over to nxstyle.boot.

NXStyle/nxstyle/resources/ribbons/

The ribbons, as JSON.

NXStyle/nxstyle/resources/*.qss

The NX light theme and the ribbon’s stylesheet.

vendor/freecad-strata/

Strata, as a git submodule.

vendor/Katydid/

Katydid, as a git submodule.

tests/

Launcher unit tests.

tools/

gen_docs.py (the generated parts of these docs), tutorial_table.py, iso10303_dictionary.py (the EXPRESS dictionary from the ISO parts), carbon_spec.py (the Carbon API schemas the tests check against).

docs/

This site’s AsciiDoc sources (Antora), the replay results and the old Markdown notes.

The module

File Does

boot.py

Waits until FreeCAD’s main window is shown, then builds the shell.

shell.py

The NX window around FreeCAD’s: ribbon row, Top Border Bar, Resource Bar, navigators, cue line, application switching.

ribbon.py

Builds the ribbon from the JSON files, with FreeCAD’s own command actions; fits each page to the window (compact, collapsed groups, More menu) and the group right-click menu.

ribbon_layout.py

The fitting plan, the user’s saved ribbon layout (nxstyle.ribbon.json) and the window-size matrix (no FreeCAD).

layout_check.py

The self-test’s window-size checks: clipping and reachability measured from widget geometry.

theme.py

The theme’s colour tokens, filled into the stylesheets (no FreeCAD).

fem_guide.py

The Pre/Post application’s guided workflow: which analysis steps are done and which comes next.

commands.py

Commands NX has and FreeCAD has not: Snap View, Show and Hide, Expressions, Repeat Command, the extrude-first workflow, Suppress, Measure Bodies, background presets.

shortcuts.py

The NX key table (TABLE), mapped onto FreeCAD commands.

navigation.py

NX mouse handling: an event filter on each 3D view takes over MB2 and the MB3 hold, and holds MB1 back for QuickPick and select-through.

radial.py

The radial menu.

triad.py

The view triad in the lower-left corner.

selection_core.py

Occurrence paths, the selection scope, the collector model, tangent and connected chains, the select-through choice (no FreeCAD).

selection_filter.py

The Type Filter and the Selection Scope. FreeCAD has one selection gate at a time; the gate manager keeps the global filter underneath and the gates of running commands on top (manager().push(layer) / pop(token)), leaves the gate of FreeCAD’s own open dialog alone, and puts the global gate back when a dialog closes. Every refusal carries a reason.

quickpick.py

QuickPick: the list of everything under the cursor, with the refused rows greyed.

collector_ui.py

The selection collector widget, the curve rules on FreeCAD edges, and Edge Blend.

navigator_status.py

The Part Navigator’s status strip and the marks on suppressed and failed features.

sketch_core.py

NX’s Geometric Constraints gallery in FreeCAD terms, sketch element names, which line Collinear moves, and which tangencies FreeCAD’s trim dropped (no FreeCAD).

sketch_ui.py

Collinear (point-on-curve constraints, none redundant), the Geometric Constraints menu ©, and Quick Trim’s guard that puts back the tangencies FreeCAD’s trim drops.

finder.py

The Command Finder.

showhide.py

The Show and Hide panel.

gdt.py, gdt_ui.py

Feature Control Frame and Datum Feature Symbol for TechDraw.

pmi.py, pmi_ui.py

PMI: semantics, ISO GPS / ASME checks, inspection plan, gauge sizes (pmi.py, no FreeCAD); the PMI objects, their 3D display, the PMI tab’s dialogs and Inherit PMI (pmi_ui.py).

iso.py

ISO 286 limits and fits, ISO 2768 general tolerances (no FreeCAD).

stackup.py, stackup_ui.py

Tolerance stack-up engine (no FreeCAD) and the Tolerance Stackup object, dialog and loop display.

clearance_core.py, clearance.py

Assembly Clearance: the classification (no FreeCAD), clearance sets, the Clearance Browser, Simple Interference.

claims.py

Volume claims and Check Volume Claims.

package.py, package_ui.py

Release packages: manifest, checksums, zip, verify (no FreeCAD); Publish Release Package.

pmi_views.py

PMI model views: the standard views, their frames and camera orientation, the label layout in a view’s plane (no FreeCAD); the view objects and Display in Views are in pmi_ui.py.

ap242.py

STEP AP242 with semantic PMI: the mapping onto OpenCASCADE’s XCAF enumerations and back, the file checks and signed tolerances (no FreeCAD), the writer and the reader through pythonocc.

p21.py, express.py, iso10303.py

ISO 10303-21 files read and written; EXPRESS (ISO 10303-11) schemas read and STEP files checked against them; the bundled dictionary of Parts 42, 43, 55, 108, 111, 112, the rules of Parts 55, 108, 111 and the python -m nxstyle.iso10303 command (no FreeCAD). Parametric STEP (ISO 10303-55, -108, -111)

parametric.py, parametric_fc.py, parametric_ui.py

Parametric models to and from ISO 10303-55/108/111 entities (no FreeCAD); a FreeCAD document to and from that model, the dual-model export and the import; the three Exchange commands.

drf.py

Datum reference frames: the degrees of freedom datums arrest, in order, and the motions left free (no FreeCAD).

sheetmetal.py, sheetmetal_ui.py

Bend allowance, bend deduction and flat length (no FreeCAD); Sheet Metal Preferences and Flat Pattern on the bundled SheetMetal add-on.

resources/ribbons/laser_cut.json

Laser Cut: the bundled LCInterlocking add-on’s commands under NX-style names.

lcinterlocking.py

An import hook that gives an upstream LCInterlocking (from the Addon Manager) the bundled fork’s two Qt 6 fixes when its task panel module loads (no FreeCAD).

resources/ribbons/modeling.json (Analysis › Molded Part Validation)

The bundled DFM Workbench’s commands under NX names; optional items, so the group disappears when the add-on is off. The launcher (find_dfm, --install-dfm-deps) supplies its OCP.

alignment.py, alignment_ui.py

Pin location: fit, translation, rotation, pointing error and thermal mismatch (no FreeCAD); the Pin Location analysis object and dialog.

position_eval.py, position_ui.py

Position of a measured pattern: bonus, the upper row against the datums, the lower row’s minimax best fit (no FreeCAD); the Position Evaluation object and its measurement table.

msa.py, msa_ui.py

Measurement capability: gauge repeatability by the range method, TUR, ISO 14253-1 acceptance limits (no FreeCAD); the Gauge Study object.

titleblock.py, titleblock_ui.py

Title block values and the revision block SVG (no FreeCAD); Populate Title Block from Strata releases and part attributes.

refs.py

Picked faces as stable references; positions along a direction; hole or pin.

prefs.py

NX values in FreeCAD’s parameter tree (they land in the NX profile’s user.cfg).

precise_input.py, quantity_core.py

Typed values keep their decimals in FreeCAD’s quantity fields, which otherwise cut a value to the Decimals preference when they lose focus or are filled from a stored value; how many decimals a value needs (quantity_core.py, no FreeCAD).

geomcheck.py

The tutorials' solid comparison: symmetric-difference volume and exact bounding boxes against a Part API reference.

config.py

Settings and their defaults (Settings).

plm.py

Strata as the data management.

carbon.py, carbon_ui.py

Carbon as the manufacturing system: the HTTP client, publishing a release package (item, revision, model, documents, BOM, inspection plan) and the status (no FreeCAD; also python -m nxstyle.carbon); the Manufacturing commands, the connection dialog and the assembly’s BOM from FreeCAD links. Manufacturing with Carbon

qt.py

Qt shims: the same code on FreeCAD 1.0 (PySide2) and 1.1 (PySide6).

smoke.py, tutorial.py

The self-test and the tutorial replay (Testing); smoke_selection.py holds the self-test’s selection scenarios and smoke_section.py its Extrude and Revolve sections; tutorial_katydid.py, tutorial_precision.py and tutorial_inspection.py hold tutorials O–R, S–Z and AA–AC.

Ribbon files

Each application has a JSON file; apps.json lists the applications and common.json holds tabs that several applications share.

{
  "title": "Modeling",
  "workbench": "PartDesignWorkbench",
  "tabs": [
    {"name": "Home", "groups": [
      {"name": "Feature", "items": [
        {"cmd": ["NXStyle_Extrude", "PartDesign_Pad"], "label": "Extrude", "size": "large"},
        {"dropdown": "Datum", "items": [
          {"cmd": "PartDesign_Plane", "label": "Datum Plane"}
        ]}
      ]}
    ]},
    {"include": "common:View"}
  ]
}
  • cmd is a FreeCAD command name, or a list of candidates: the first that exists wins, so one file serves FreeCAD 1.0 and 1.1.

  • label is the NX name on the button; size is large or small.

  • "optional": true leaves the button out quietly when the command does not exist (Strata’s commands, newer FreeCAD commands). Without it, a missing command is reported by the self-test.

  • {"include": "common:View"} inserts a shared tab.

  • A tab with "contextual": true (the Sketch tab in sketch.json) appears while it applies.

  • "essential" (top level) lists the application’s main commands, as names or lists of candidates. Their groups give up room last when the window is narrow, and the self-test checks that they stay reachable at every window size.

  • A group’s "rank" (a whole number, 0 by default, 10 for a group holding an essential command) sets the order in which groups give up room: lowest first.

  • A group’s "step" ties it to a guided workflow (simulation.json, checked by fem_guide.py).

  • In apps.json, an application with "ribbon": null gets a ribbon generated from its workbench’s toolbars. "guide": "fem" turns on the guided workflow.

How a page fits its width is decided by ribbon_layout.py, which uses neither Qt nor FreeCAD so that the unit tests can check it. ribbon.py measures the groups and applies the plan, and layout_check.py measures the result for the self-test.

Theme colours

theme.py holds every colour of the NX theme as a token (accent, chrome, tint and so on). The stylesheets write @nx-<token> in place of a colour, and theme.stylesheet() fills the tokens in when it loads them. An unknown token is an error. Python code uses theme.color("accent"). tests/test_theme.py fails if a stylesheet, or the shell, ribbon, finder or PLM module, contains a literal colour.

The NX command to FreeCAD command tables are generated from these files.

Keyboard shortcuts

shortcuts.TABLE lists each key as an Entry with the NX command, the key, where it works and the FreeCAD command. Section comments (# ---- …) group the table, and the Keyboard shortcuts page follows those groups. The self-test presses every key.

Strata

plm.py hosts Strata through its embedding API (freecad-strata 1f71005 on):

import strata.boot as sb
from strata.ui import theme

sb.configure(host_toolbar=True, panel_area="left",
             shortcuts={"Strata_Compare": None, "Strata_Sync": None})
sb.on_ready(lambda: ribbon.add_actions(sb.actions().values()))   # plm.py then calls sb.activate_commands()
theme.set_overrides(light={"accent": "#00838F"})
  • host_toolbar=True: Strata adds no toolbar or workbench of its own and never re-activates the workbench.

  • panel_area="left": the panel is made with the navigators.

  • actions(): Strata’s commands as QActions, enabled with the active project.

  • theme.set_overrides: Strata’s panels in NX-Style’s colours.

  • shortcuts= (freecad-strata 37db86f on): Strata’s own keys are Ctrl+Shift+H, V, D, R and Y. D and Y are NX’s Drafting and Layout keys, so Compare and Sync get none; NX-Style’s key pass would take D and Y back anyway, but Strata’s Tools-menu entries would then show keys that do nothing.

  • activate_commands(): in host mode Strata binds its keys only when this runs; plm.py calls it before NX-Style takes its keys back.

The submodule is pinned; update it with git submodule update --remote vendor/freecad-strata and commit the new pin after the self-test passes.

Katydid

Katydid is its own project and stays unmodified in vendor/Katydid; NX-Style adds no code to it.

  • The launcher loads it with -M, like Strata (find_katydid): off with --no-katydid or "algorithmic_modeling": "none", and not loaded twice when a Katydid sits in the NX profile’s Mod folder.

  • apps.json offers it as the Algorithmic Modeling application (KatydidWorkbench), with its ribbon in algorithmic.json.

  • Katydid registers Katydid_Canvas and Katydid_Bake when FreeCAD starts (Katydid a0a9e8a on), so Canvas is also on the shared Tools tab (common.json, "optional": true). Starting Katydid’s workbench opens the canvas, so the workbench is never preloaded; switching to the application starts it.

  • The canvas is a floating QDockWidget named KatydidDock; NX-Style leaves it out of the navigator tab strip.

Update the pin the same way: git submodule update --remote vendor/Katydid, then the self-test.

Carbon

Carbon is a server, not an add-on, so it is not a submodule and the launcher loads nothing for it (find_carbon only reports how it is set up; --no-carbon sets NXSTYLE_CARBON=0, which leaves the commands unregistered). It is AGPL-3.0 with commercial Enterprise files, so NX-Style copies none of it and talks to it over its HTTP API only (why).

  • carbon.Client.call("items.createPart", {…​}) is POST <server>/api/v1/items/createPart with the arguments as the body and the key as a bearer token; lists answer {"results", "count"}, refusals {"code", "status", "message", "data": {"category"}}. 429 and 503 are retried (Retry-After).

  • carbon.OPERATIONS lists every operation used. tests/data/carbon-openapi.json holds their schemas, cut from Carbon’s published OpenAPI document by python tools/carbon_spec.py (or from a server’s own /api/v1/openapi.json); the stand-in server of the tests rejects any request those schemas reject.

  • carbon.Publisher does the reads and either makes or lists the writes (dry_run), which is what Review shows.

  • Uploads take two steps, as in Carbon’s own browser upload: a signed URL from Carbon (createModelUploadUrl, createItemDocumentUploadUrl), a PUT of the bytes to it (without the API key), then for a model insertModelUpload.

When Carbon renames an operation, tools/carbon_spec.py says which; tests/test_carbon.py then fails until carbon.py follows.

Rules the code keeps

  • Never write to the normal FreeCAD profile: the launcher refuses any profile that overlaps it.

  • Metric by default.

  • Use FreeCAD’s real commands; add an NX-Style command only where FreeCAD has none.

  • No Siemens icons, text or assets.