@bldrs-ai/conway 0.7.727 → 0.7.766

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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/examples/browser.ts"],"names":[],"mappings":""}
@@ -0,0 +1,388 @@
1
+ #!/usr/bin/env node
2
+ import { exit } from 'process';
3
+ import IfcStepParser from '../ifc/ifc_step_parser';
4
+ import ParsingBuffer from '../parsing/parsing_buffer';
5
+ import { ParseResult } from '../step/parsing/step_parser';
6
+ import EntityTypesIfc from '../ifc/ifc4_gen/entity_types_ifc.gen';
7
+ import fs from 'fs';
8
+ import Logger from '../logging/logger';
9
+ import Environment from '../utilities/environment';
10
+ import * as readline from 'node:readline';
11
+ import { stdin as input, stdout as output } from 'node:process';
12
+ // ---------------------------------------------------------------------
13
+ // 1. Grab the path to the model from command line arguments
14
+ // ---------------------------------------------------------------------
15
+ Environment.checkEnvironment();
16
+ Logger.initializeWasmCallbacks();
17
+ const modelPath = process.argv[2];
18
+ if (!modelPath) {
19
+ console.error('Usage: browser <path_to_model>.ifc');
20
+ process.exit(1);
21
+ }
22
+ // ---------------------------------------------------------------------
23
+ // 2. Load & Parse the IFC
24
+ // ---------------------------------------------------------------------
25
+ let indexIfcBuffer;
26
+ try {
27
+ indexIfcBuffer = fs.readFileSync(modelPath);
28
+ }
29
+ catch (ex) {
30
+ Logger.error('Couldn\'t read file, check that it is accessible at the specified path.');
31
+ exit();
32
+ }
33
+ if (indexIfcBuffer === void 0) {
34
+ Logger.error('Couldn\'t read file, check that it is accessible at the specified path.');
35
+ exit();
36
+ }
37
+ Logger.createStatistics(0);
38
+ const parser = IfcStepParser.Instance;
39
+ const bufferInput = new ParsingBuffer(indexIfcBuffer);
40
+ // Parse header
41
+ // eslint-disable-next-line no-unused-vars
42
+ const [stepHeader, resultHeader] = parser.parseHeader(bufferInput);
43
+ // Parse main data
44
+ const [parseResult, model] = parser.parseDataToModel(bufferInput);
45
+ // Check parse result
46
+ switch (parseResult) {
47
+ case ParseResult.COMPLETE:
48
+ break;
49
+ case ParseResult.INCOMPLETE:
50
+ Logger.warning('Parse incomplete but no errors');
51
+ break;
52
+ case ParseResult.INVALID_STEP:
53
+ Logger.error('Invalid STEP detected');
54
+ break;
55
+ case ParseResult.MISSING_TYPE:
56
+ Logger.error('Missing STEP type');
57
+ break;
58
+ case ParseResult.SYNTAX_ERROR:
59
+ Logger.error(`Syntax error at line ${bufferInput.lineCount}`);
60
+ break;
61
+ default:
62
+ }
63
+ if (!model) {
64
+ Logger.error('Model not loaded.');
65
+ process.exit(1);
66
+ }
67
+ // ---------------------------------------------------------------------
68
+ // 3. Build arrays for IFC classes & entity types
69
+ // ---------------------------------------------------------------------
70
+ const nonEmptyTypeIDNoSubtypes = model.nonEmptyTypeIDs();
71
+ const ifcClasses = Array.from(nonEmptyTypeIDNoSubtypes || []).map((item) => String(EntityTypesIfc[item]));
72
+ const entityTypes = Array.from(nonEmptyTypeIDNoSubtypes || []);
73
+ // A helper to parse something like
74
+ // "IFCBUILDINGELEMENTPROXY[#30]" => { className: "IFCBUILDINGELEMENTPROXY", expressID: 30 }
75
+ /**
76
+ * Parses a token containing a class name and an optional express ID.
77
+ *
78
+ * @param {string} token - The token to be parsed. Expected format: "CLASSNAME[#ID]".
79
+ * @return {{ className: string, expressID?: number }}
80
+ */
81
+ function parseClassAndOptionalID(token) {
82
+ // e.g. IFCBUILDINGELEMENTPROXY[#30]
83
+ const match = token.match(/^(.+)\[#(\d+)\]$/);
84
+ if (match) {
85
+ const className = match[1].toUpperCase();
86
+ const expressID = parseInt(match[2], 10);
87
+ return { className, expressID };
88
+ }
89
+ return { className: token.toUpperCase() };
90
+ }
91
+ // Gather suggestions like "IFCBUILDINGELEMENTPROXY[#30]" for each instance of a class
92
+ /**
93
+ * Retrieves instance suggestions based on the specified class name within an IFC model.
94
+ *
95
+ * @param {string} clsName - The name of the class for which to retrieve suggestions.
96
+ * @param {IfcStepModel} theModel - The IFC model containing class and entity information.
97
+ * @return {string[]} An array of instance suggestions in the format "ClassName[#ID]".
98
+ */
99
+ function getInstanceSuggestions(clsName, theModel) {
100
+ const idx = ifcClasses.indexOf(clsName);
101
+ if (idx < 0) {
102
+ return [];
103
+ }
104
+ const elementTypeID = entityTypes[idx];
105
+ const ctor = theModel.schema.constructors[elementTypeID];
106
+ if (!ctor) {
107
+ return [];
108
+ }
109
+ const results = [];
110
+ for (const ent of theModel.types(ctor)) {
111
+ // We'll assume ent.expressID is the numeric ID
112
+ const stepId = ent.expressID;
113
+ if (typeof stepId === 'number') {
114
+ results.push(`${clsName}[#${stepId}]`);
115
+ }
116
+ }
117
+ return results;
118
+ }
119
+ // We build a 3-tuple for each field => [fieldName, fieldDescription, fieldData]
120
+ /**
121
+ * Retrieves an array of local fields from the given IFC entity along with
122
+ * their descriptions and associated data.
123
+ *
124
+ * @param {StepEntityBase<EntityTypesIfc>} entity - The IFC entity from which
125
+ * to extract fields and their data.
126
+ * @return {[string, EntityFieldDescription<EntityTypesIfc>, unknown][]}
127
+ * An array of tuples, where each tuple contains:
128
+ * - The field name as a string.
129
+ * - The field description object.
130
+ * - The associated data for the field.
131
+ */
132
+ function getLocalFieldsWithData(entity) {
133
+ return entity.orderedFields.map(([fieldName, fieldDesc]) => {
134
+ // For this example, we assume dynamic property references the actual data
135
+ const data = entity[fieldName];
136
+ return [fieldName, fieldDesc, data];
137
+ });
138
+ }
139
+ // The main recursive function that navigates dotted paths, now also supporting "Class[#ID]"
140
+ /**
141
+ * Retrieves the value of a field or subfields from an IFC model by traversing a dotted path.
142
+ *
143
+ * @param {string} dottedPath - The path to the desired field,
144
+ * with tokens separated by dots (e.g., "ClassName[#ID].fieldName").
145
+ * @param {IfcStepModel} theModel - The IFC model containing class and entity information.
146
+ * @return {{
147
+ * isEntity: boolean;
148
+ * subfieldNames: string[];
149
+ * value: any;
150
+ * }} An object containing:
151
+ * - `isEntity`: A boolean indicating whether the final value is an entity with subfields.
152
+ * - `subfieldNames`: An array of subfield names if the final value is an entity,
153
+ * otherwise an empty array.
154
+ * - `value`: The value of the field if it is a primitive type, otherwise `null`.
155
+ */
156
+ function getFieldValueOrSubfields(dottedPath, theModel) {
157
+ const tokens = dottedPath.split('.');
158
+ if (!tokens.length) {
159
+ return { isEntity: false, subfieldNames: [], value: null };
160
+ }
161
+ // parse first token => e.g. "IFCBUILDINGELEMENTPROXY[#30]"
162
+ const { className, expressID } = parseClassAndOptionalID(tokens[0]);
163
+ // find the class in ifcClasses
164
+ const idx = ifcClasses.indexOf(className);
165
+ if (idx < 0) {
166
+ return { isEntity: false, subfieldNames: [], value: null };
167
+ }
168
+ const elementTypeID = entityTypes[idx];
169
+ const ctor = theModel.schema.constructors[elementTypeID];
170
+ if (!ctor) {
171
+ return { isEntity: false, subfieldNames: [], value: null };
172
+ }
173
+ // If an ID is specified, find that particular entity; otherwise take the first instance
174
+ let currentEntity = null;
175
+ if (expressID !== undefined) {
176
+ for (const e of theModel.types(ctor)) {
177
+ if (e.expressID === expressID) {
178
+ currentEntity = e;
179
+ break;
180
+ }
181
+ }
182
+ if (!currentEntity) {
183
+ // user specified e.g. "IFCBUILDINGELEMENTPROXY[#999]" but no such entity
184
+ return { isEntity: false, subfieldNames: [], value: null };
185
+ }
186
+ }
187
+ else {
188
+ // no ID => pick first
189
+ for (const e of theModel.types(ctor)) {
190
+ currentEntity = e;
191
+ break;
192
+ }
193
+ if (!currentEntity) {
194
+ return { isEntity: false, subfieldNames: [], value: null };
195
+ }
196
+ }
197
+ let currentValue = currentEntity;
198
+ // Navigate subfields for subsequent tokens
199
+ for (let i = 1; i < tokens.length; i++) {
200
+ const fieldName = tokens[i].toLowerCase();
201
+ if (!currentValue || typeof currentValue !== 'object') {
202
+ return { isEntity: false, subfieldNames: [], value: currentValue };
203
+ }
204
+ if (!('orderedFields' in currentValue)) {
205
+ return { isEntity: false, subfieldNames: [], value: currentValue };
206
+ }
207
+ const entity = currentValue;
208
+ const localFields = getLocalFieldsWithData(entity);
209
+ const tuple = localFields.find(([fName]) => fName.toLowerCase() === fieldName);
210
+ if (!tuple) {
211
+ return { isEntity: false, subfieldNames: [], value: null };
212
+ }
213
+ // eslint-disable-next-line no-unused-vars
214
+ const [foundName, foundDesc, foundData] = tuple;
215
+ if (foundData === null) {
216
+ return { isEntity: false, subfieldNames: [], value: null };
217
+ }
218
+ // If it's an array, pick first item
219
+ if (Array.isArray(foundData)) {
220
+ currentValue = foundData.length ? foundData[0] : [];
221
+ }
222
+ else {
223
+ currentValue = foundData;
224
+ }
225
+ }
226
+ // If final value is an entity => gather subfields
227
+ if (currentValue &&
228
+ typeof currentValue === 'object' &&
229
+ 'orderedFields' in currentValue) {
230
+ const subEntity = currentValue;
231
+ const subLocal = getLocalFieldsWithData(subEntity);
232
+ const subfieldNames = subLocal.map(([fn]) => fn);
233
+ return { isEntity: true, subfieldNames, value: null };
234
+ }
235
+ // Otherwise it's a primitive
236
+ return { isEntity: false, subfieldNames: [], value: currentValue };
237
+ }
238
+ // ---------------------------------------------------------------------
239
+ // 4. The completer function
240
+ // - No dot => also show instance suggestions like IFCBUILDINGELEMENTPROXY[#30]
241
+ // - With dot => partial subfields
242
+ // ---------------------------------------------------------------------
243
+ /**
244
+ * Provides autocomplete suggestions based on the user's input, which can include class names,
245
+ * instance IDs, or subfields within IFC entities.
246
+ *
247
+ * @param {string} line - The current line of input entered by the user.
248
+ * @return {[string[], string]} A tuple containing:
249
+ * - An array of suggestions matching the user's input.
250
+ * - The current token being matched, which can be a class name, partial ID, or subfield.
251
+ */
252
+ function completer(line) {
253
+ const trimmed = line.trim();
254
+ // If there's whitespace, skip advanced completions
255
+ if (trimmed.includes(' ')) {
256
+ return [[], trimmed];
257
+ }
258
+ const tokens = trimmed.split('.');
259
+ if (tokens.length === 1) {
260
+ // ========== CASE A: top-level (no dot) ==========
261
+ const partial = tokens[0].toLowerCase();
262
+ // 1) Detect if user typed something like Class[# or Class[#3
263
+ // Example: "IFCPROPERTYSINGLEVALUE[#3"
264
+ // eslint-disable-next-line no-useless-escape
265
+ const bracketMatch = partial.match(/^([^\[]+)\[#(\d*)$/);
266
+ if (bracketMatch) {
267
+ // bracketMatch[1] => the class name (lowercased)
268
+ // bracketMatch[2] => the partial ID number as a string (possibly empty)
269
+ const partialClass = bracketMatch[1].trim(); // e.g. "ifcpropertysinglevalue"
270
+ // eslint-disable-next-line no-unused-vars
271
+ const partialId = bracketMatch[2].trim(); // e.g. "3" (may be empty "" if user typed "[#")
272
+ // Filter IFC classes by partialClass
273
+ const classMatches = ifcClasses.filter((cls) => cls.toLowerCase().startsWith(partialClass));
274
+ // For each matched class, gather instance suggestions, then filter out
275
+ // only those that still match the entire typed string so far (including the bracket).
276
+ let instanceMatches = [];
277
+ for (const cls of classMatches) {
278
+ const allInstances = getInstanceSuggestions(cls, model);
279
+ // the user typed something like "ifcpropertysinglevalue[#3"
280
+ // so we want to see which of our suggestions (e.g. "IFCPROPERTYSINGLEVALUE[#37]")
281
+ // starts with exactly what the user typed
282
+ instanceMatches = instanceMatches.concat(allInstances.filter((s) => s.toLowerCase().startsWith(partial)));
283
+ }
284
+ // If no classes match, or if no instance suggestions match, you could choose to:
285
+ // 1) fallback to all classes, or
286
+ // 2) show nothing
287
+ // For simplicity, let's show the instanceMatches if available; if none found,
288
+ // fallback to the original classMatches.
289
+ if (instanceMatches.length > 0) {
290
+ return [instanceMatches, tokens[0]];
291
+ }
292
+ else {
293
+ // We might also allow the user to see class names again in case they typed
294
+ // "IFCPROPERTYSINGLEVALUE[#" but there's no partial ID to match yet.
295
+ return [classMatches, tokens[0]];
296
+ }
297
+ }
298
+ else {
299
+ // ========== No bracket => fallback to your original approach ==========
300
+ // Filter IFC classes by partial
301
+ const classMatches = ifcClasses.filter((cls) => cls.toLowerCase().startsWith(partial));
302
+ // Also gather instance suggestions for each matched class
303
+ let instanceMatches = [];
304
+ for (const cls of classMatches) {
305
+ const allInstances = getInstanceSuggestions(cls, model);
306
+ instanceMatches = instanceMatches.concat(allInstances.filter((s) => s.toLowerCase().startsWith(partial)));
307
+ }
308
+ // Combine
309
+ const suggestions = [...classMatches, ...instanceMatches];
310
+ // If no suggestions found, fallback to all classes:
311
+ if (!suggestions.length) {
312
+ return [ifcClasses, tokens[0]];
313
+ }
314
+ else {
315
+ return [suggestions, tokens[0]];
316
+ }
317
+ }
318
+ }
319
+ else {
320
+ // ========== CASE B: There's at least one dot ==========
321
+ const lastToken = tokens[tokens.length - 1];
322
+ // The path up to (but not including) the final token
323
+ const pathTokens = tokens.slice(0, -1);
324
+ const pathForNavigation = pathTokens.join('.');
325
+ const { isEntity, subfieldNames } = getFieldValueOrSubfields(pathForNavigation, model);
326
+ if (!isEntity) {
327
+ return [[], trimmed];
328
+ }
329
+ // If lastToken is empty => user typed a trailing dot => show all subfields
330
+ if (!lastToken) {
331
+ return [subfieldNames, lastToken];
332
+ }
333
+ else {
334
+ // partial text => filter subfields
335
+ const lower = lastToken.toLowerCase();
336
+ const matches = subfieldNames.filter((name) => name.toLowerCase().startsWith(lower));
337
+ const suggestions = matches.length ? matches : subfieldNames;
338
+ return [suggestions, lastToken];
339
+ }
340
+ }
341
+ }
342
+ // ---------------------------------------------------------------------
343
+ // 5. Create Readline with the custom completer
344
+ // ---------------------------------------------------------------------
345
+ const rl = readline.createInterface({
346
+ input,
347
+ output,
348
+ prompt: '> ',
349
+ completer,
350
+ terminal: true, // ensure readline sees TAB in many terminals
351
+ });
352
+ // ---------------------------------------------------------------------
353
+ // 6. Prompt user
354
+ // ---------------------------------------------------------------------
355
+ console.log(`Loaded model: ${modelPath}`);
356
+ console.log('Type an IFC class name (partial) and press Tab (e.g. IfcB -> IFCBUILDING).');
357
+ console.log('Also try "IFCBUILDINGELEMENTPROXY[#" to see instance completions.');
358
+ console.log('Use a dot for subfields (e.g. IFCBUILDINGELEMENTPROXY[#30].Name). ' +
359
+ 'Press Enter to see final value.\n');
360
+ rl.prompt();
361
+ // ---------------------------------------------------------------------
362
+ // 7. On Enter: parse entire dotted path again to see if it's a primitive
363
+ // ---------------------------------------------------------------------
364
+ rl.on('line', (inputLine) => {
365
+ const command = inputLine.trim();
366
+ if (command.toLowerCase() === 'exit') {
367
+ console.log('Goodbye!');
368
+ process.exit(0);
369
+ }
370
+ const { isEntity, subfieldNames, value } = getFieldValueOrSubfields(command, model);
371
+ if (isEntity) {
372
+ console.log(`This is still an entity. Possible subfields:\n ${subfieldNames.join(', ')}`);
373
+ }
374
+ else if (value !== null && value !== undefined) {
375
+ console.log(`${JSON.stringify(value)}`);
376
+ }
377
+ else {
378
+ console.log(`No or invalid value for "${command}".`);
379
+ }
380
+ rl.prompt();
381
+ });
382
+ // ---------------------------------------------------------------------
383
+ // 8. Handle Ctrl+C
384
+ // ---------------------------------------------------------------------
385
+ rl.on('SIGINT', () => {
386
+ console.log('\nCaught interrupt signal. Exiting.');
387
+ process.exit(0);
388
+ });
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * IFC Model Validator
4
+ *
5
+ * See the project README or the validator.md for details.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/examples/validator.ts"],"names":[],"mappings":";AAEA;;;;GAIG"}