@ifc-lite/cli 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/commands/clash.d.ts.map +1 -1
- package/dist/commands/clash.js +8 -1
- package/dist/commands/clash.js.map +1 -1
- package/dist/commands/gym/channels.d.ts +28 -0
- package/dist/commands/gym/channels.d.ts.map +1 -0
- package/dist/commands/gym/channels.js +115 -0
- package/dist/commands/gym/channels.js.map +1 -0
- package/dist/commands/gym/episode.d.ts +36 -0
- package/dist/commands/gym/episode.d.ts.map +1 -0
- package/dist/commands/gym/episode.js +65 -0
- package/dist/commands/gym/episode.js.map +1 -0
- package/dist/commands/gym/ops.d.ts +37 -0
- package/dist/commands/gym/ops.d.ts.map +1 -0
- package/dist/commands/gym/ops.js +78 -0
- package/dist/commands/gym/ops.js.map +1 -0
- package/dist/commands/gym.d.ts +10 -0
- package/dist/commands/gym.d.ts.map +1 -0
- package/dist/commands/gym.js +324 -0
- package/dist/commands/gym.js.map +1 -0
- package/dist/commands/simplify.d.ts +2 -0
- package/dist/commands/simplify.d.ts.map +1 -0
- package/dist/commands/simplify.js +102 -0
- package/dist/commands/simplify.js.map +1 -0
- package/dist/commands/validate.d.ts +40 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +155 -6
- package/dist/commands/validate.js.map +1 -1
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/output.d.ts +14 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +26 -0
- package/dist/output.js.map +1 -1
- package/package.json +19 -19
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* `ifc-lite gym (--model <file.ifc> | --seed <n>) [--checks schema,clash,ids] [--ids <rules.xml>]`
|
|
6
|
+
*
|
|
7
|
+
* A reset/step/reward environment loop over the existing headless checks:
|
|
8
|
+
* the skeleton of an RLVR environment for buildings (docs/vision/moonshots-tech.md
|
|
9
|
+
* M2, docs/vision/moonshots-execution-plan.md B0.4). The environment wraps a
|
|
10
|
+
* model - either a fixed file (`--model`) or a procedurally generated World
|
|
11
|
+
* Gym episode (`--seed`, see `gym/episode.ts`) - and lets an agent apply
|
|
12
|
+
* data-mutation ops, scoring each step against the same schema/clash/ids
|
|
13
|
+
* checks the `validate`, `clash`, and `ids` commands already run.
|
|
14
|
+
* Geometry-creating ops are out of scope for v0, see "op vocabulary gaps"
|
|
15
|
+
* below.
|
|
16
|
+
*
|
|
17
|
+
* Protocol (newline-delimited JSON on stdout, one JSON command per stdin line):
|
|
18
|
+
*
|
|
19
|
+
* -> (on start) {"type":"reset","observation":{...},"channels":{...}}
|
|
20
|
+
* <- {"type":"step","ops":[{"op":"setProperty","expressId":42,"psetName":"Pset_WallCommon","propName":"IsExternal","value":true}]}
|
|
21
|
+
* -> {"type":"reward","channels":{...},"done":false}
|
|
22
|
+
* <- {"type":"reset"}
|
|
23
|
+
* -> {"type":"reset","observation":{...},"channels":{...}}
|
|
24
|
+
* <- {"type":"close"}
|
|
25
|
+
* -> (process exits 0, no reply line)
|
|
26
|
+
*
|
|
27
|
+
* Malformed input never crashes the process: it replies with a structured
|
|
28
|
+
* {"type":"error","message":"..."} line and keeps reading. A `step` batch is
|
|
29
|
+
* ATOMIC: it either fully applies (one `reward` line) or leaves the session
|
|
30
|
+
* exactly as it was (one `error` line) - a malformed op mid-batch never
|
|
31
|
+
* leaves earlier ops of the same batch applied.
|
|
32
|
+
*
|
|
33
|
+
* Episode factory (B2.2): instead of `--model`, `--seed <n>` generates a
|
|
34
|
+
* World Gym benchmark model in-process (tools/world-gym/generator.mjs,
|
|
35
|
+
* dynamically imported from the repo checkout - the published npm package
|
|
36
|
+
* does not ship the generator, so `--seed` fails there with a clear error
|
|
37
|
+
* while `--model` keeps working). `[--family frame|office|auto]` pins the
|
|
38
|
+
* family; corruption follows the benchmark's deterministic Bernoulli draw at
|
|
39
|
+
* the spec's corrupt rate (tools/world-gym/benchmark/splits.mjs) unless
|
|
40
|
+
* `--corrupt` / `--no-corrupt` forces it or `--corrupt-rate <p>` overrides
|
|
41
|
+
* the rate (forcing and a rate are mutually exclusive). Mid-session,
|
|
42
|
+
* `{"type":"reset","seed":8}` (plus optional "family"/"corrupt"/"corruptRate"
|
|
43
|
+
* fields) swaps to a fresh generated episode, so an RL consumer can stream
|
|
44
|
+
* the whole benchmark through one gym process without touching generator
|
|
45
|
+
* internals. Generated-episode reset lines carry an extra `episode` field:
|
|
46
|
+
* {seed, family, corrupted}. (The `corrupted` flag is deliberately exposed:
|
|
47
|
+
* the gym is the TRAINING surface; benchmark ground truth is regenerable
|
|
48
|
+
* from the seed anyway - see tools/world-gym/benchmark/BENCHMARK.md.)
|
|
49
|
+
*
|
|
50
|
+
* Determinism: the same model plus the same op sequence must yield
|
|
51
|
+
* byte-identical reward lines - see `gym/channels.ts` for the sorting and
|
|
52
|
+
* rounding contract. Reward shaping: every channel's `score` is in [0, 1]
|
|
53
|
+
* with higher-is-better (the clash channel scores 1 for clash-free and
|
|
54
|
+
* decreases as the clash count grows; the raw count is `totalClashes`).
|
|
55
|
+
*
|
|
56
|
+
* Mutation surface: v0 supports `setProperty` / `setAttribute` /
|
|
57
|
+
* `deleteProperty`, mirroring `bim.mutate`'s method names exactly (see
|
|
58
|
+
* `gym/ops.ts`). Ops are applied via `MutablePropertyView` + `StepExporter`
|
|
59
|
+
* (the same classes `ifc-lite mutate` uses) rather than through `bim.mutate`
|
|
60
|
+
* itself, because `HeadlessBackend.mutate` (packages/cli/src/headless-backend.ts)
|
|
61
|
+
* is currently a no-op stub: `bim.mutate.setProperty()` silently does
|
|
62
|
+
* nothing in headless mode. Wiring that backend up to the same
|
|
63
|
+
* MutablePropertyView this file drives is a mechanical follow-up, not a
|
|
64
|
+
* redesign, since the op vocabulary already matches.
|
|
65
|
+
*
|
|
66
|
+
* Each step re-exports the accumulated mutation overlay to STEP text and
|
|
67
|
+
* re-parses it into a fresh `IfcDataStore` before running checks. This is
|
|
68
|
+
* deliberate, not a perf shortcut we forgot to remove: `ids`/`clash`/the
|
|
69
|
+
* schema checks all read directly from an `IfcDataStore` (via
|
|
70
|
+
* `createDataAccessor(store)` / `EntityNode(store, id)`), with no
|
|
71
|
+
* mutation-overlay awareness, so a live overlay would silently be invisible
|
|
72
|
+
* to every check. Re-parsing after export guarantees the checks see exactly
|
|
73
|
+
* what a human re-opening the mutated file would see.
|
|
74
|
+
*
|
|
75
|
+
* Op vocabulary gaps (v0, tracked for a follow-up bet, not fixed here):
|
|
76
|
+
* - Ops target entities by `expressId`, not `GlobalId`. Stable within one
|
|
77
|
+
* gym session (StepExporter never renumbers existing entities), but a
|
|
78
|
+
* GlobalId-keyed op format would be more robust for agents that only
|
|
79
|
+
* ever see a model's IFC-standard identifiers.
|
|
80
|
+
* - `observation.bounds` is always `null`: no geometry pass runs on
|
|
81
|
+
* `reset`, and even the `clash` channel's mesh pass never feeds a
|
|
82
|
+
* bounding box back into the observation.
|
|
83
|
+
* - No entity-creation ops (new walls/slabs/etc. via `packages/create`) and
|
|
84
|
+
* no episode-termination signal (`done` is always `false`); both need a
|
|
85
|
+
* real reward-shaping design, not just wiring.
|
|
86
|
+
* - `bim.mutate.setProperty/setAttribute/deleteProperty` are no-ops on
|
|
87
|
+
* `HeadlessBackend` (see above); this file works around that directly
|
|
88
|
+
* rather than fixing the backend, to keep this bet's diff scoped to one
|
|
89
|
+
* new command.
|
|
90
|
+
*/
|
|
91
|
+
import { createInterface } from 'node:readline';
|
|
92
|
+
import { readFile } from 'node:fs/promises';
|
|
93
|
+
import { basename } from 'node:path';
|
|
94
|
+
import { loadIfcFile, loadIfcBytes } from '../loader.js';
|
|
95
|
+
import { getFlag, fatal } from '../output.js';
|
|
96
|
+
import { extractPropertiesOnDemand } from '@ifc-lite/parser';
|
|
97
|
+
import { MutablePropertyView } from '@ifc-lite/mutations';
|
|
98
|
+
import { StepExporter } from '@ifc-lite/export';
|
|
99
|
+
import { GeometryProcessor } from '@ifc-lite/geometry';
|
|
100
|
+
import { IDSNamespace } from '@ifc-lite/sdk';
|
|
101
|
+
import { KNOWN_CHECKS, computeSchemaChannel, computeClashChannel, computeIdsChannel, computeObservation, } from './gym/channels.js';
|
|
102
|
+
import { parseOp, applyOp } from './gym/ops.js';
|
|
103
|
+
import { generateEpisode, parseSeed, parseCorruptRate, } from './gym/episode.js';
|
|
104
|
+
const USAGE = 'Usage: ifc-lite gym (--model <file.ifc> | --seed <n> [--family frame|office|auto] [--corrupt|--no-corrupt|--corrupt-rate <p>]) [--checks schema,clash,ids] [--ids <rules.xml>] [--locale en|de|fr]';
|
|
105
|
+
const SUPPORTED_LOCALES = ['en', 'de', 'fr'];
|
|
106
|
+
function parseChecks(raw, idsPath) {
|
|
107
|
+
if (raw === undefined) {
|
|
108
|
+
return idsPath ? new Set(['schema', 'clash', 'ids']) : new Set(['schema', 'clash']);
|
|
109
|
+
}
|
|
110
|
+
const result = new Set();
|
|
111
|
+
for (const part of raw.split(',').map(s => s.trim()).filter(Boolean)) {
|
|
112
|
+
if (!KNOWN_CHECKS.includes(part)) {
|
|
113
|
+
fatal(`Unknown check "${part}" in --checks (supported: ${KNOWN_CHECKS.join(', ')})`);
|
|
114
|
+
}
|
|
115
|
+
result.add(part);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function parseLocale(raw) {
|
|
120
|
+
if (raw === undefined)
|
|
121
|
+
return 'en';
|
|
122
|
+
if (!SUPPORTED_LOCALES.includes(raw)) {
|
|
123
|
+
fatal(`Unsupported --locale "${raw}" (supported: ${SUPPORTED_LOCALES.join(', ')})`);
|
|
124
|
+
}
|
|
125
|
+
return raw;
|
|
126
|
+
}
|
|
127
|
+
export async function gymCommand(args, io = {}) {
|
|
128
|
+
const modelPath = getFlag(args, '--model');
|
|
129
|
+
const seedFlag = getFlag(args, '--seed');
|
|
130
|
+
if (!modelPath && seedFlag === undefined)
|
|
131
|
+
fatal(USAGE);
|
|
132
|
+
if (modelPath && seedFlag !== undefined)
|
|
133
|
+
fatal(`--model and --seed are mutually exclusive\n${USAGE}`);
|
|
134
|
+
const idsPath = getFlag(args, '--ids');
|
|
135
|
+
const checks = parseChecks(getFlag(args, '--checks'), idsPath);
|
|
136
|
+
const locale = parseLocale(getFlag(args, '--locale'));
|
|
137
|
+
const output = io.output ?? process.stdout;
|
|
138
|
+
const input = io.input ?? process.stdin;
|
|
139
|
+
function send(msg) {
|
|
140
|
+
output.write(`${JSON.stringify(msg)}\n`);
|
|
141
|
+
}
|
|
142
|
+
let modelId;
|
|
143
|
+
let originalStore;
|
|
144
|
+
let episode = null;
|
|
145
|
+
async function loadEpisode(spec) {
|
|
146
|
+
const { model, episode: info } = await generateEpisode(spec);
|
|
147
|
+
originalStore = await loadIfcBytes(new TextEncoder().encode(model.content), `gym-seed-${info.seed}.ifc`);
|
|
148
|
+
episode = info;
|
|
149
|
+
modelId = `gym-seed-${info.seed}.ifc`;
|
|
150
|
+
}
|
|
151
|
+
if (modelPath) {
|
|
152
|
+
modelId = basename(modelPath);
|
|
153
|
+
originalStore = await loadIfcFile(modelPath);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const forceCorrupt = args.includes('--corrupt') ? true : args.includes('--no-corrupt') ? false : undefined;
|
|
157
|
+
const corruptRateFlag = getFlag(args, '--corrupt-rate');
|
|
158
|
+
if (forceCorrupt !== undefined && corruptRateFlag !== undefined) {
|
|
159
|
+
fatal(`--corrupt/--no-corrupt and --corrupt-rate are mutually exclusive\n${USAGE}`);
|
|
160
|
+
}
|
|
161
|
+
await loadEpisode({
|
|
162
|
+
seed: parseSeed(seedFlag, '--seed'),
|
|
163
|
+
family: getFlag(args, '--family') ?? 'auto',
|
|
164
|
+
forceCorrupt,
|
|
165
|
+
corruptRate: corruptRateFlag !== undefined ? parseCorruptRate(corruptRateFlag, '--corrupt-rate') : undefined,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const ids = new IDSNamespace();
|
|
169
|
+
let idsDoc = null;
|
|
170
|
+
if (idsPath) {
|
|
171
|
+
const idsXml = await readFile(idsPath, 'utf-8');
|
|
172
|
+
idsDoc = await ids.parse(idsXml);
|
|
173
|
+
}
|
|
174
|
+
// Lazily initialised: wasm geometry init is only worth paying for when
|
|
175
|
+
// the "clash" channel is actually requested.
|
|
176
|
+
let processor = null;
|
|
177
|
+
async function getProcessor() {
|
|
178
|
+
if (!processor) {
|
|
179
|
+
// Assign only after init() succeeds: caching the instance before a
|
|
180
|
+
// failed init would hand every later call an uninitialized processor
|
|
181
|
+
// instead of retrying (the init throw itself surfaces as a structured
|
|
182
|
+
// error line on the step that requested the clash channel).
|
|
183
|
+
const p = new GeometryProcessor();
|
|
184
|
+
await p.init();
|
|
185
|
+
processor = p;
|
|
186
|
+
}
|
|
187
|
+
return processor;
|
|
188
|
+
}
|
|
189
|
+
function createMutationView() {
|
|
190
|
+
const view = new MutablePropertyView(null, 'default');
|
|
191
|
+
view.setOnDemandExtractor((entityId) => extractPropertiesOnDemand(originalStore, entityId));
|
|
192
|
+
return view;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The committed op journal: every op of every successfully rewarded step,
|
|
196
|
+
* in order. `step` batches are atomic - a failing batch (malformed op,
|
|
197
|
+
* export/parse failure, channel failure) must leave the session exactly as
|
|
198
|
+
* it was - so the journal is the single source of truth and the view is
|
|
199
|
+
* rebuilt from it whenever a batch fails partway through.
|
|
200
|
+
*/
|
|
201
|
+
let journal = [];
|
|
202
|
+
let mutationView = createMutationView();
|
|
203
|
+
function rebuildViewFromJournal() {
|
|
204
|
+
mutationView = createMutationView();
|
|
205
|
+
for (const op of journal)
|
|
206
|
+
applyOp(mutationView, op);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Materialise the current mutation overlay into a fresh, independently
|
|
210
|
+
* parsed store. See the module doc for why re-export + re-parse is
|
|
211
|
+
* required rather than reading through the overlay directly.
|
|
212
|
+
*/
|
|
213
|
+
async function materializeStore() {
|
|
214
|
+
const schema = (originalStore.schemaVersion ?? 'IFC4');
|
|
215
|
+
const exporter = new StepExporter(originalStore, mutationView);
|
|
216
|
+
const result = exporter.export({ schema, applyMutations: true });
|
|
217
|
+
return loadIfcBytes(result.content, modelId);
|
|
218
|
+
}
|
|
219
|
+
async function computeChannels(store) {
|
|
220
|
+
const channels = {};
|
|
221
|
+
// Fixed canonical key order regardless of --checks argument order, so
|
|
222
|
+
// two runs requesting the same set in a different order still produce
|
|
223
|
+
// byte-identical JSON.
|
|
224
|
+
if (checks.has('schema'))
|
|
225
|
+
channels.schema = computeSchemaChannel(store);
|
|
226
|
+
if (checks.has('clash'))
|
|
227
|
+
channels.clash = await computeClashChannel(store, await getProcessor(), modelId);
|
|
228
|
+
if (checks.has('ids'))
|
|
229
|
+
channels.ids = await computeIdsChannel(store, ids, idsDoc, locale);
|
|
230
|
+
return channels;
|
|
231
|
+
}
|
|
232
|
+
/** Apply a step batch atomically: on ANY failure, restore the pre-batch state. */
|
|
233
|
+
async function stepBatch(rawOps) {
|
|
234
|
+
// Phase 1: validate the whole batch before anything is applied.
|
|
235
|
+
const ops = rawOps.map(raw => parseOp(raw));
|
|
236
|
+
// Phase 2: apply + materialize + score; roll back to the journal on failure.
|
|
237
|
+
try {
|
|
238
|
+
for (const op of ops)
|
|
239
|
+
applyOp(mutationView, op);
|
|
240
|
+
const store = await materializeStore();
|
|
241
|
+
const channels = await computeChannels(store);
|
|
242
|
+
journal.push(...ops);
|
|
243
|
+
return channels;
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
rebuildViewFromJournal();
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function emitReset() {
|
|
251
|
+
journal = [];
|
|
252
|
+
mutationView = createMutationView();
|
|
253
|
+
const observation = computeObservation(originalStore);
|
|
254
|
+
const channels = await computeChannels(originalStore);
|
|
255
|
+
// `episode` only exists for generated episodes; --model resets keep the
|
|
256
|
+
// exact v0 payload shape for backward compatibility.
|
|
257
|
+
send(episode ? { type: 'reset', episode, observation, channels } : { type: 'reset', observation, channels });
|
|
258
|
+
}
|
|
259
|
+
await emitReset();
|
|
260
|
+
const rl = createInterface({ input, terminal: false, crlfDelay: Infinity });
|
|
261
|
+
// A broken pipe on stdout or a stdin failure must end the loop cleanly,
|
|
262
|
+
// not take the process down with an uncaught 'error' emitter event.
|
|
263
|
+
const onStreamError = () => {
|
|
264
|
+
rl.close();
|
|
265
|
+
};
|
|
266
|
+
input.on('error', onStreamError);
|
|
267
|
+
output.on('error', onStreamError);
|
|
268
|
+
for await (const line of rl) {
|
|
269
|
+
const trimmed = line.trim();
|
|
270
|
+
if (!trimmed)
|
|
271
|
+
continue;
|
|
272
|
+
let msg;
|
|
273
|
+
try {
|
|
274
|
+
msg = JSON.parse(trimmed);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
send({ type: 'error', message: `Malformed JSON: ${err.message}` });
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const type = typeof msg === 'object' && msg !== null ? msg.type : undefined;
|
|
281
|
+
try {
|
|
282
|
+
if (type === 'step') {
|
|
283
|
+
const ops = msg.ops;
|
|
284
|
+
if (!Array.isArray(ops))
|
|
285
|
+
throw new Error('"step" command needs an "ops" array');
|
|
286
|
+
const channels = await stepBatch(ops);
|
|
287
|
+
send({ type: 'reward', channels, done: false });
|
|
288
|
+
}
|
|
289
|
+
else if (type === 'reset') {
|
|
290
|
+
const m = msg;
|
|
291
|
+
if (m.seed !== undefined) {
|
|
292
|
+
// New generated episode over the same protocol (episode factory).
|
|
293
|
+
if (m.family !== undefined && typeof m.family !== 'string') {
|
|
294
|
+
throw new Error('reset field "family" must be a string (frame|office|auto)');
|
|
295
|
+
}
|
|
296
|
+
if (m.corrupt !== undefined && typeof m.corrupt !== 'boolean') {
|
|
297
|
+
throw new Error('reset field "corrupt" must be a boolean');
|
|
298
|
+
}
|
|
299
|
+
if (m.corrupt !== undefined && m.corruptRate !== undefined) {
|
|
300
|
+
throw new Error('reset fields "corrupt" and "corruptRate" are mutually exclusive');
|
|
301
|
+
}
|
|
302
|
+
await loadEpisode({
|
|
303
|
+
seed: parseSeed(m.seed, 'reset field "seed"'),
|
|
304
|
+
family: m.family ?? 'auto',
|
|
305
|
+
forceCorrupt: m.corrupt,
|
|
306
|
+
corruptRate: m.corruptRate !== undefined ? parseCorruptRate(m.corruptRate, 'reset field "corruptRate"') : undefined,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
await emitReset();
|
|
310
|
+
}
|
|
311
|
+
else if (type === 'close') {
|
|
312
|
+
rl.close();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
send({ type: 'error', message: `Unknown command type: ${JSON.stringify(type ?? null)}` });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
send({ type: 'error', message: err.message });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=gym.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gym.js","sourceRoot":"","sources":["../../src/commands/gym.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simplify.d.ts","sourceRoot":"","sources":["../../src/commands/simplify.ts"],"names":[],"mappings":"AAoBA,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFnE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* ifc-lite simplify <file.ifc> --out light.ifc [--level 1..5] [--ids 1,2,3] [--json]
|
|
6
|
+
*
|
|
7
|
+
* Demesher (dev/testing surface for the DemeshSession SDK API): simplify
|
|
8
|
+
* element meshes — levels 1-4 drop enclosed cavities and decimate to
|
|
9
|
+
* 0.5/0.25/0.10/0.03 of the triangle count, level 5 collapses each element
|
|
10
|
+
* to its bounding box — and write a lighter IFC where each simplified
|
|
11
|
+
* element's representation is replaced by an IfcTriangulatedFaceSet.
|
|
12
|
+
* `--level` defaults to 1; without --ids every element that produced a mesh
|
|
13
|
+
* is simplified.
|
|
14
|
+
*/
|
|
15
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { getFlag, hasFlag, fatal, printJson } from '../output.js';
|
|
17
|
+
import { DemeshSession } from '@ifc-lite/export';
|
|
18
|
+
export async function simplifyCommand(args) {
|
|
19
|
+
const filePath = args.find((a) => !a.startsWith('-'));
|
|
20
|
+
if (!filePath) {
|
|
21
|
+
fatal('Usage: ifc-lite simplify <file.ifc> --out light.ifc [--level 1..5] [--ids 1,2,3] [--json]');
|
|
22
|
+
}
|
|
23
|
+
const levelStr = getFlag(args, '--level');
|
|
24
|
+
const outPath = getFlag(args, '--out');
|
|
25
|
+
const idsStr = getFlag(args, '--ids');
|
|
26
|
+
const jsonOutput = hasFlag(args, '--json');
|
|
27
|
+
const level = Number(levelStr ?? '1');
|
|
28
|
+
if (!Number.isInteger(level) || level < 1 || level > 5) {
|
|
29
|
+
fatal('--level must be an integer 1..5 (1-4 = decimation tiers, 5 = bounding box; default 1)');
|
|
30
|
+
}
|
|
31
|
+
if (!outPath)
|
|
32
|
+
fatal('--out is required. Specify output file path.');
|
|
33
|
+
const source = new Uint8Array(await readFile(filePath));
|
|
34
|
+
const session = new DemeshSession(source);
|
|
35
|
+
try {
|
|
36
|
+
let ids;
|
|
37
|
+
if (idsStr) {
|
|
38
|
+
// Strict: every token must be a positive decimal integer. parseInt
|
|
39
|
+
// would silently accept '12foo' as 12, and filtering bad tokens away
|
|
40
|
+
// would silently simplify a different selection than the user typed.
|
|
41
|
+
const tokens = idsStr.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
42
|
+
if (tokens.length === 0 || tokens.some((s) => !/^\d+$/.test(s) || Number(s) < 1 || !Number.isSafeInteger(Number(s)))) {
|
|
43
|
+
fatal('--ids must be a comma-separated list of positive integer express ids');
|
|
44
|
+
}
|
|
45
|
+
ids = tokens.map((s) => Number(s));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Every element that produced geometry, heaviest first.
|
|
49
|
+
ids = (await session.heaviest(Number.MAX_SAFE_INTEGER)).map((e) => e.expressId);
|
|
50
|
+
if (ids.length === 0)
|
|
51
|
+
fatal('No element meshes produced from this file.');
|
|
52
|
+
}
|
|
53
|
+
const simplified = await session.simplify(ids, level);
|
|
54
|
+
if (simplified.elements.length === 0) {
|
|
55
|
+
fatal(`No elements could be simplified (${simplified.skipped.length} skipped).`);
|
|
56
|
+
}
|
|
57
|
+
const exported = await session.exportIfc();
|
|
58
|
+
// The export can still skip elements the mesh pass simplified (missing
|
|
59
|
+
// representation attribute, no geometric context, ...). The exported
|
|
60
|
+
// report is the authority on what the written IFC actually contains —
|
|
61
|
+
// and when NOTHING was replaced there is no lighter file to write.
|
|
62
|
+
const replaced = exported.report.replaced.length;
|
|
63
|
+
if (replaced === 0) {
|
|
64
|
+
fatal(`Simplified ${simplified.elements.length} meshes, but none could be applied to the IFC ` +
|
|
65
|
+
`(${exported.report.skipped.length} skipped at export); not writing ${outPath}.`);
|
|
66
|
+
}
|
|
67
|
+
await writeFile(outPath, exported.bytes);
|
|
68
|
+
const trisBefore = simplified.elements.reduce((s, e) => s + e.trisBefore, 0);
|
|
69
|
+
const trisAfter = simplified.elements.reduce((s, e) => s + e.trisAfter, 0);
|
|
70
|
+
if (jsonOutput) {
|
|
71
|
+
printJson({
|
|
72
|
+
level,
|
|
73
|
+
simplified: simplified.elements.length,
|
|
74
|
+
replaced,
|
|
75
|
+
skipped: simplified.skipped,
|
|
76
|
+
exportSkipped: exported.report.skipped,
|
|
77
|
+
trianglesBefore: trisBefore,
|
|
78
|
+
trianglesAfter: trisAfter,
|
|
79
|
+
cavitiesDropped: simplified.elements.reduce((s, e) => s + e.cavitiesDropped, 0),
|
|
80
|
+
prunedEntities: exported.report.prunedEntityCount,
|
|
81
|
+
strippedOpenings: exported.report.strippedOpeningCount,
|
|
82
|
+
upconverted: exported.upconverted,
|
|
83
|
+
bytesBefore: exported.bytesBefore,
|
|
84
|
+
bytesAfter: exported.bytesAfter,
|
|
85
|
+
output: outPath,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
process.stderr.write(`Simplified ${simplified.elements.length} meshes at level ${level} ` +
|
|
90
|
+
`(${simplified.skipped.length} skipped), ${replaced} representations replaced` +
|
|
91
|
+
`${exported.report.skipped.length > 0 ? ` (${exported.report.skipped.length} skipped at export)` : ''}\n` +
|
|
92
|
+
`Triangles: ${trisBefore} -> ${trisAfter}\n` +
|
|
93
|
+
`File size: ${exported.bytesBefore} -> ${exported.bytesAfter} bytes` +
|
|
94
|
+
`${exported.upconverted ? ' (upconverted IFC2X3 -> IFC4)' : ''}\n` +
|
|
95
|
+
`Written to ${outPath}\n`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
session.destroy();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=simplify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simplify.js","sourceRoot":"","sources":["../../src/commands/simplify.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,42 @@
|
|
|
1
|
+
import { type IfcDataStore } from '@ifc-lite/parser';
|
|
2
|
+
export interface ValidationIssue {
|
|
3
|
+
severity: 'error' | 'warning' | 'info';
|
|
4
|
+
rule: string;
|
|
5
|
+
message: string;
|
|
6
|
+
/** Referencing entity's expressId (set by per-reference rules like reference-integrity). */
|
|
7
|
+
entityId?: number;
|
|
8
|
+
/** Zero-based top-level attribute slot of the offending value within the referencing entity. */
|
|
9
|
+
attributeIndex?: number;
|
|
10
|
+
/** The referenced expressId that does not exist in the file. */
|
|
11
|
+
target?: number;
|
|
12
|
+
}
|
|
13
|
+
/** One `#N` reference whose target expressId does not exist in the file. */
|
|
14
|
+
interface DanglingReference {
|
|
15
|
+
/** expressId of the entity containing the reference. */
|
|
16
|
+
entityId: number;
|
|
17
|
+
/** STEP type name of the referencing entity (UPPERCASE, as scanned). */
|
|
18
|
+
entityType: string;
|
|
19
|
+
/** Zero-based top-level attribute slot the reference sits in (nested refs report their containing slot). */
|
|
20
|
+
attributeIndex: number;
|
|
21
|
+
/** The missing expressId being referenced. */
|
|
22
|
+
target: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Walk every indexed entity record and collect `#N` references whose target
|
|
26
|
+
* does not exist in the file (rule `reference-integrity`). Uses the parsed
|
|
27
|
+
* entity index for both iteration and existence checks; entities held in the
|
|
28
|
+
* deferred index (lazily-parsed property entities) count as existing and are
|
|
29
|
+
* scanned too.
|
|
30
|
+
*/
|
|
31
|
+
export declare function collectDanglingReferences(store: IfcDataStore): DanglingReference[];
|
|
32
|
+
/**
|
|
33
|
+
* Run the structural validation checks (required entities, storeys, GlobalId
|
|
34
|
+
* uniqueness, naming, schema version, quantity completeness, reference
|
|
35
|
+
* integrity) against an already-parsed store. Pulled out of
|
|
36
|
+
* {@link validateCommand} so other consumers (tests, harnesses) reuse the
|
|
37
|
+
* exact same rules instead of re-implementing them.
|
|
38
|
+
*/
|
|
39
|
+
export declare function computeValidationIssues(store: IfcDataStore): ValidationIssue[];
|
|
1
40
|
export declare function validateCommand(args: string[]): Promise<void>;
|
|
41
|
+
export {};
|
|
2
42
|
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAgC,KAAK,YAAY,EAAkB,MAAM,kBAAkB,CAAC;AAEnG,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,4EAA4E;AAC5E,UAAU,iBAAiB;IACzB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,4GAA4G;IAC5G,cAAc,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;CAChB;AAuFD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,YAAY,GAAG,iBAAiB,EAAE,CAmBlF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,EAAE,CA2H9E;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCnE"}
|