# Conway

Conway powers [Bldrs Share](https://github.com/bldrs-ai/Share), bringing high-quality, precision CAD to the web. This cutting-edge CAD engine, designed specifically for IFC and STEP formats, offers advanced geometric representation, enabling teams to open and visualize intricate models with exceptional accuracy and speed.
 
Conway includes two major subcomponents:
  - **[IFC-gen](https://github.com/bldrs-ai/conway/tree/main/src/ifc/ifc4_gen)**, a mostly autogenerated TypeScript framework (~400kloc) for full API coverage parsing of the IFC 2x3, 4 compliant `*.ifc` files, and initial support for [STEP AP2xx](https://en.wikipedia.org/wiki/ISO_10303-21) `*.step` files to support Automotive and 3D-printing applications.  This is based on original work by [hypar-io/IFC-gen](https://github.com/hypar-io/IFC-gen), further developed by bldrs-ai for Conway.  The runtime is fully open-source in this project.  Please contact us for full access to our generation pipeline.
  - **[conway-geom](https://github.com/bldrs-ai/conway-geom) WASM core**, bldrs-ai’s rewrite of [web-ifc](https://github.com/ThatOpen/engine_web-ifc), engineered for high-performance and to support the full suite of open CAD standards within the IFC and STEP families.

## Getting Started

### Codex Setup
1. Ignore the below and refer to AGENTS.md.

### Windows Setup
1. [Install MinGW-64](https://github.com/msys2/msys2-installer/releases/download/2022-06-03/msys2-x86_64-20220603.exe) and add ```g++.exe``` location to your PATH variable.

### MacOS Setup 

1. Install the `gmake` and `node` dependencies via Homebrew (```brew install gmake node```).

### EMSDK Setup
1. Clone the [EMSDK](https://github.com/emscripten-core/emsdk) repo and add it to your path (see their instructions)
2. Conway is using `6.0.2` (or run `scripts/setup-emsdk.sh`, which installs the pinned version)
```
> cd $EMSDK
> ./emsdk install 6.0.2
> ./emsdk activate 6.0.2
> cd $CONWAY
conway> emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 6.0.2
...
```

### Initial Build

Clone the Conway repository, then in the root directory of the repository:
```
# Make sure EMSDK environment is set up.
yarn setup
yarn build
yarn test
```

### Example Uses
You can now load your IFC files. From the Conway root:
1. ```yarn browser model.ifc```
2. ```yarn validator model.ifc "IFCWINDOW.OverallHeight <= 1500"```

See the full example docs at [Browser.md](examples/Browser.md) and [Validator.md](examples/Validator.md)

## Development
Update your client with changes since your last sync:
```
git pull
yarn setup
```

For the full build of both conway (TypeScript) and conway-geom (WASM subproject):
```
yarn build
```

Build just conway, not conway-geom, e.g. for updating just the Conway API or tools:
```
yarn build-incremental
```

#### Running tests without building conway-geom (no EMSDK)

The jest suite loads the conway-geom WASM binaries, which are normally produced
by the EMSDK/GENie native build. If you only touch the TypeScript, you can skip
that toolchain entirely: `yarn setup` builds the TypeScript (`build-incremental`)
and then runs `yarn wasm-prebuilt`, which fetches the WASM `Dist/` bundle from
the last-published `@bldrs-ai/conway` npm package — so a fresh `yarn setup && yarn
test` works with no EMSDK. Pin a build with `CONWAY_PREBUILT_WASM_VERSION`, or
refresh with `yarn wasm-prebuilt --force`.
This is a convenience for TS-only work — if you change the `conway-geom`
submodule you must do a real `yarn build` to get matching binaries.

For a full, clean rebuild:
```
yarn build-rebuild
```

You can also run the tests with jest:
```
yarn test
```

And finally, using the watch functionality, you can also have the code automatically rebuild on change and also re-run the tests using:
```
yarn build-test-watch
```

# IFC Parser Console Test Application

Conway has a test application for parsing IFC step files to see the performance and included entity types at src/core/ifc/ifc_command_line_main.ts. 

You can set up an alias to reference the latest package or build from source and use yarn cli:

```
alias conway='yarn cli'
OR
alias conway='npx --package=@bldrs-ai/conway@latest exec cli'
```

It can be run with:

```
conway [ifc file path]
# To output geometry
conway -g [ifc file path]
# Use -m 1024 for larger files
```

### CLI Geometry Output Options

You can now output various geometry formats using the CLI:

```bash
conway [ifc file path] [options]
```

**Examples:**
- Output all geometry (default GLTF + GLB):
  ```bash
  conway index.ifc --geometry
  ```
- Output only GLTF:
  ```bash
  conway index.ifc --geometry --gltf
  ```
- Output only GLB:
  ```bash
  conway index.ifc --geometry --glb
  ```
- Output GLTF with Draco compression:
  ```bash
  conway index.ifc --geometry --gltf-draco
  ```
- Output GLB with Draco compression:
  ```bash
  conway index.ifc --geometry --glb-draco
  ```
- Run geometry processing without saving output files:
  ```bash
  conway index.ifc --geometry --nooutput
  ```

You can also combine flags as needed. For example:
```bash
conway index.ifc --geometry --gltf-draco --glb-draco
```

Run `conway --help` for the full list of available flags and options.

The included index.ifc in the repo is recommended for testing.

# Profiling WASM Builds in Node
Profiling Conway, including building a Conway-Geom WASM binary with DWARF information and generating a flame graph with WASM symbols, is possible via the following steps:
1. Run *build-profile-conway_geom* from Conway's package.json 
2. Profile your app: 
```
node --prof --experimental-specifier-resolution=node ./compiled/src/ifc/ifc_command_line_main.js <model.ifc> -g
```
3. An isolate*.log file will be generated. Run:
```
node --prof-process --preprocess -j isolate*.log > v8.json # generate a V8 log
```
4. Go to https://mapbox.github.io/flamebearer/ and drop the log file to see a detailed flame graph.

# Problems with renaming in GIT merges

Because of the large number of files in conway that are code changes sometimes causing large modifications in merges, especially if generation locations are changed, it's sometimes necessary to up the limit of the number of renames in the git config for merging. It can be done like so:

```
git config merge.renameLimit 99999
```

You may also wish to use a low rename threshold no-commit merge strategy for some of these situations to increase likelyhood that files will be related in the merge process and to track some of the more complicated changes:

```
git merge -X rename-threshold=25 --no-commit
```

# CI and Testing

Every PR is gated on two checks defined in `.github/workflows/build.yml`:

| Job | What it does |
|---|---|
| `build` | `yarn install`, WASM + TS compile (WASM cached on the `conway-geom` submodule SHA), `yarn test`, `yarn lint`, and a Tier-A geometry-digest check of the in-repo `data/` models against committed goldens. |
| `run-ifc-regression` | `needs: build`. Reuses the same WASM cache. Runs the regression batch against the public `test-models` ref (`TEST_MODELS_REF`, default `main`), pinned per-run to the resolved commit SHA; posts a per-PR comment with the resolved SHA + `failed.csv` / `errors.csv` / perf summaries, and uploads the candidate npm tarball + `perf.csv` as workflow artifacts. |

A merge to `main` re-runs those two jobs and then chains into `auto-publish` (see [Releases](#releases) below).

## Regression batch

The same batch the regression CI job runs can be invoked locally — see [regression/README.md](regression/README.md) for digest / verbose / batch modes and the catalog of model fixtures. CI tracks `TEST_MODELS_REF` near the top of `build.yml` (default `main`), resolved to a commit SHA per run and recorded in the PR comment + job summary, so each run is reproducible without relying on test-models cutting tags.

### Tier A: in-repo quick-check models (`data/`)

The `build` job also runs a fast, hermetic geometry gate over every `data/*.ifc` model — no `test-models` clone and no token, so it protects every PR including forks. Each model's geometry digest (the same `ifc_regression_main.js -d` digest the batch produces) is diffed against its committed golden `data/<name>.csv`; a mismatch fails `build` with a re-bless hint. The goldens are checked in (generated by CI's pinned EMSDK toolchain), so the gate is **live**.

**Re-blessing an intended change.** Because the correct digest is only knowable from a CI run on the new code, the gate fails first: that same run uploads the recomputed digests as the `tierA-goldens-<run_id>` artifact, so you download it, commit the new `data/<name>.csv`, and push. One PR, but a fail-then-rebless round-trip rather than a single edit. A brand-new `data/*.ifc` model with no golden yet only warns (it doesn't fail) and emits its candidate digest the same way, to bootstrap.

## Performance benchmarks

**Tier 1 — Conway-only perf in CI (live).** Every regression run emits a `perf.csv` of `parseTimeMs / geometryTimeMs / totalTimeMs / rssMb / heapUsedMb / heapTotalMb` per model. The top-10 slowest are posted in the PR comment; the full CSV is uploaded as a workflow artifact. This piggybacks on the existing regression batch so cost is ~0 extra runner minutes.

**Tier 2 — full headless-three perf in CI (live).** Two jobs,
`perf-three-public` and `perf-three-private`, run on `push: main`
(`needs: run-ifc-regression`). Each downloads the candidate Conway tarball
that the regression job packed, clones [headless-three](https://github.com/bldrs-ai/headless-three)
at a pinned `H3_SHA`, and forces the whole H3 → adapter → conway chain onto
the candidate via a yarn `resolutions` override (no `yarn link`), then runs
`scripts/benchmark.cjs` to time every model. The public job posts a
per-model table to the merge's PR; the private job (`test-models-private`,
gated on `secrets.TEST_MODELS_PRIVATE_TOKEN`, never on PR events so forks
can't leak the secret) posts aggregate stats only, so no private model
names appear in the public thread. Both compute a cross-version delta vs
the previous `push: main` run (via the `.github/actions/perf-delta`
composite action) and upload the detail CSV as a workflow artifact.


# Releases

**Releases are continuous.** Every green merge to `main` auto-tags and
publishes to npm at the default `latest` dist-tag. The version is
`<major>.<PR>.<commit>` where:

- `major` comes from `package.json` on `main` (only the first segment;
  the other two are recomputed on every release)
- `PR` is the GitHub PR number that produced the merge commit
- `commit` is `git rev-list HEAD --count` at the merge commit (the
  global commit position)

So a merge of PR #321 at the 985th commit on main publishes as
`1.321.985`. Each release is uniquely traceable to a PR and a commit.

The auto-publish job lives in `.github/workflows/build.yml` and runs as
`needs: [build, run-ifc-regression]` on `push: branches: [main]`. It
parses the PR number from the merge commit message, stamps the
computed version into `package.json` + `src/version/version.ts` in
CI's working tree (not committed back), rebuilds, publishes to npm,
and then tags the merge commit.

**Auth:** npm Trusted Publishing via GitHub OIDC. A Trusted Publisher
configured at npmjs.com on `@bldrs-ai/conway` is bound to this repo +
`build.yml`. No long-lived `NPM_TOKEN` secret is used; the workflow
gets a short-lived publish token per run via OIDC.

### Normal flow: ship a change

1. Open a PR with your change. CI runs `build` and `run-ifc-regression`.
2. Merge once both checks are green.
3. On main push, CI re-runs `build` + `run-ifc-regression`, then
   `auto-publish` tags and ships. Watch the workflow run in the Actions
   tab; the new version is on npm within ~5-10 min of the merge.

### Bumping major

Edit the `version` field in `package.json` on a PR — only the first
segment matters (the `PR` and `commit` parts are recomputed). For
example, change `"version": "1.0.0"` to `"version": "2.0.0"` to start
the `2.x.x` line. The first auto-publish after that lands ships
`2.<PR>.<commit>`.

There is no minor bump — `PR` lives in that slot — so increasing
sequential versions across the `1.x` line look like `1.300.x`,
`1.301.x`, etc., as PRs land.

### Rolling into headless-three and Share
The same flow applies to both — do **H3 first, then Share**:
```
cd $H3_DIR
git fetch upstream                                       # or origin if not on a fork
git checkout -b conway-<VERSION> upstream/main
# Edit package.json dep for @bldrs-ai/conway to "<VERSION>"
yarn install
yarn build && yarn test
yarn serve
# Smoke test the local candidate: load all sample models, load a local model,
# exercise dialogs, etc.
git add . && git commit -m "Upgrade conway from <OLD> to <VERSION>"
git push origin HEAD
```
Then:
1. Send PR for review.
2. On merge, Netlify auto-builds and deploys to prod; watch the deploy logs.
3. Smoke test prod (same checks as local).
4. Post in `#bot` or `#share`: "New Conway <VERSION> in prod" with a link
   to the changelog.

### Blessing a `stable` version
After Share prod is verified on a given Conway version:
```
# Create the GitHub release from the auto-pushed tag
gh release create <VERSION> --generate-notes

# Promote the npm package to the stable dist-tag
npm dist-tag add @bldrs-ai/conway@<VERSION> stable
```


# Roadmap

The CI / release pipeline is continuous and complete: `build` gates
`run-ifc-regression`, which gates the headless-three perf jobs, and every
green merge to `main` auto-publishes (see [Releases](#releases)). The
umbrella waterfall (#316) and performance-in-CI (#314) issues are closed.
The optional follow-ups below remain.

### Headless-three perf on PRs

The `perf-three-public` / `perf-three-private` jobs run on `push: main`
only — to keep PR wall-time down and to keep the private-models token off
PR events (forks can't be trusted with it). Running them per-PR, behind a
gate that withholds the private job from fork PRs, would catch H3
render-time regressions before merge instead of just after.

### Golden `errors.csv` diffing + regression renders (#288)

Fail a PR when its `errors.csv` diverges from a checked-in golden — "New
errors detected. Copy errors.csv to golden/errors.csv to accept changes"
— and attach per-model renders to the regression comment so geometry
regressions are visible without downloading artifacts.
