create-ifc-lite 1.14.13 → 1.15.1
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/dist/index.js +83 -42
- package/dist/templates/babylonjs.js +23 -26
- package/dist/templates/basic.js +40 -9
- package/dist/templates/react.js +8 -14
- package/dist/templates/server-native.js +1 -0
- package/dist/templates/server.js +6 -5
- package/dist/templates/threejs.js +15 -24
- package/dist/utils/config-fixers.js +29 -2
- package/dist/utils/gitignore.d.ts +6 -0
- package/dist/utils/gitignore.js +37 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2,22 +2,53 @@
|
|
|
2
2
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
4
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
5
|
-
import { existsSync, mkdirSync } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
5
|
+
import { existsSync, mkdirSync, rmSync, readFileSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
7
8
|
import { createBasicTemplate } from './templates/basic.js';
|
|
8
9
|
import { createThreejsTemplate } from './templates/threejs.js';
|
|
9
10
|
import { createBabylonjsTemplate } from './templates/babylonjs.js';
|
|
10
11
|
import { createReactTemplate } from './templates/react.js';
|
|
11
12
|
import { createServerTemplate } from './templates/server.js';
|
|
12
13
|
import { createServerNativeTemplate } from './templates/server-native.js';
|
|
14
|
+
/** Every template, mapped to the function that scaffolds it. */
|
|
13
15
|
const TEMPLATES = {
|
|
14
|
-
basic:
|
|
15
|
-
threejs:
|
|
16
|
-
babylonjs:
|
|
17
|
-
react:
|
|
18
|
-
server:
|
|
19
|
-
'server-native':
|
|
16
|
+
basic: createBasicTemplate,
|
|
17
|
+
threejs: createThreejsTemplate,
|
|
18
|
+
babylonjs: createBabylonjsTemplate,
|
|
19
|
+
react: createReactTemplate,
|
|
20
|
+
server: createServerTemplate,
|
|
21
|
+
'server-native': createServerNativeTemplate,
|
|
20
22
|
};
|
|
23
|
+
const TEMPLATE_NAMES = Object.keys(TEMPLATES);
|
|
24
|
+
/**
|
|
25
|
+
* `name in TEMPLATES` walks the prototype chain, so `--template toString`
|
|
26
|
+
* passed the guard and then matched no branch, silently scaffolding `basic`.
|
|
27
|
+
*/
|
|
28
|
+
function isTemplate(name) {
|
|
29
|
+
return Object.hasOwn(TEMPLATES, name);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Read this package's own version. A failure here is a broken install, not a
|
|
33
|
+
* normal condition, so report it and return a value that reads as unknown
|
|
34
|
+
* rather than a plausible number a bug report would then carry — the same
|
|
35
|
+
* lesson `@ifc-lite/cli`'s `readCliVersion` records.
|
|
36
|
+
*/
|
|
37
|
+
function readOwnVersion() {
|
|
38
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
39
|
+
try {
|
|
40
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
41
|
+
const version = pkg.version;
|
|
42
|
+
if (typeof version === 'string' && version.length > 0)
|
|
43
|
+
return version;
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.error(`Warning: could not read ${pkgPath} (${err instanceof Error ? err.message : String(err)}).`);
|
|
47
|
+
return '0.0.0-unknown';
|
|
48
|
+
}
|
|
49
|
+
console.error(`Warning: ${pkgPath} declares no "version".`);
|
|
50
|
+
return '0.0.0-unknown';
|
|
51
|
+
}
|
|
21
52
|
function printUsage() {
|
|
22
53
|
console.log(`
|
|
23
54
|
create-ifc-lite - Create IFC-Lite projects instantly
|
|
@@ -26,8 +57,9 @@ function printUsage() {
|
|
|
26
57
|
npx create-ifc-lite [project-name] [options]
|
|
27
58
|
|
|
28
59
|
Options:
|
|
29
|
-
--template <type> Template to use [default: basic]
|
|
30
|
-
--
|
|
60
|
+
-t, --template <type> Template to use [default: basic]
|
|
61
|
+
-v, --version Print the create-ifc-lite version
|
|
62
|
+
-h, --help Show this help message
|
|
31
63
|
|
|
32
64
|
Examples:
|
|
33
65
|
npx create-ifc-lite my-ifc-app
|
|
@@ -52,25 +84,38 @@ async function main() {
|
|
|
52
84
|
printUsage();
|
|
53
85
|
process.exit(0);
|
|
54
86
|
}
|
|
87
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
88
|
+
console.log(readOwnVersion());
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
55
91
|
// Parse arguments
|
|
56
|
-
let projectName
|
|
92
|
+
let projectName;
|
|
57
93
|
let template = 'basic';
|
|
58
94
|
for (let i = 0; i < args.length; i++) {
|
|
59
95
|
const arg = args[i];
|
|
60
96
|
if (arg === '--template' || arg === '-t') {
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
template
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
console.error(`Invalid template: ${t}. Available: basic, threejs, babylonjs, react, server, server-native`);
|
|
97
|
+
const value = args[++i];
|
|
98
|
+
if (value === undefined || !isTemplate(value)) {
|
|
99
|
+
console.error(`Invalid template: ${value ?? '(missing)'}. Available: ${TEMPLATE_NAMES.join(', ')}`);
|
|
67
100
|
process.exit(1);
|
|
68
101
|
}
|
|
102
|
+
template = value;
|
|
103
|
+
}
|
|
104
|
+
else if (arg.startsWith('-')) {
|
|
105
|
+
// Previously ignored, which made `--templat react` scaffold a project
|
|
106
|
+
// literally named "react" instead of reporting the typo.
|
|
107
|
+
console.error(`Unknown option: ${arg}\nRun \`npx create-ifc-lite --help\` to see the available options.`);
|
|
108
|
+
process.exit(1);
|
|
69
109
|
}
|
|
70
|
-
else if (
|
|
110
|
+
else if (projectName === undefined) {
|
|
71
111
|
projectName = arg;
|
|
72
112
|
}
|
|
113
|
+
else {
|
|
114
|
+
console.error(`Unexpected argument: ${arg}. Only one project name is accepted.`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
73
117
|
}
|
|
118
|
+
projectName ??= 'my-ifc-app';
|
|
74
119
|
// Reject path separators, '..', and names that would yield an invalid npm
|
|
75
120
|
// `name`, so join(process.cwd(), projectName) stays under cwd and the
|
|
76
121
|
// generated package.json is valid. Mirrors config-fixers.ts VALID_PACKAGE_NAME.
|
|
@@ -89,35 +134,22 @@ async function main() {
|
|
|
89
134
|
process.exit(1);
|
|
90
135
|
}
|
|
91
136
|
console.log(`\n Creating IFC-Lite project in ${targetDir}...\n`);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
mkdirSync(targetDir, { recursive: true });
|
|
138
|
+
try {
|
|
139
|
+
TEMPLATES[template](targetDir, projectName);
|
|
95
140
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
createReactTemplate(targetDir, projectName);
|
|
103
|
-
}
|
|
104
|
-
else if (template === 'server') {
|
|
105
|
-
mkdirSync(targetDir, { recursive: true });
|
|
106
|
-
createServerTemplate(targetDir, projectName);
|
|
107
|
-
}
|
|
108
|
-
else if (template === 'server-native') {
|
|
109
|
-
mkdirSync(targetDir, { recursive: true });
|
|
110
|
-
createServerNativeTemplate(targetDir, projectName);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
mkdirSync(targetDir, { recursive: true });
|
|
114
|
-
createBasicTemplate(targetDir, projectName);
|
|
141
|
+
catch (error) {
|
|
142
|
+
// Templates resolve their dependency versions from the npm registry as
|
|
143
|
+
// their first act, so an offline run fails AFTER the directory exists.
|
|
144
|
+
// Leaving it behind made the obvious retry fail with "already exists".
|
|
145
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
146
|
+
throw error;
|
|
115
147
|
}
|
|
116
148
|
console.log(` Done! Next steps:\n`);
|
|
117
149
|
console.log(` cd ${projectName}`);
|
|
118
150
|
if (template === 'server') {
|
|
119
151
|
console.log(` docker compose up -d`);
|
|
120
|
-
console.log(` npm install && npm run example`);
|
|
152
|
+
console.log(` npm install && npm run example ./your-model.ifc`);
|
|
121
153
|
console.log(`\n Server will be available at http://localhost:3001`);
|
|
122
154
|
}
|
|
123
155
|
else if (template === 'server-native') {
|
|
@@ -137,6 +169,15 @@ async function main() {
|
|
|
137
169
|
console.log();
|
|
138
170
|
}
|
|
139
171
|
main().catch((error) => {
|
|
140
|
-
|
|
172
|
+
if (error instanceof Error) {
|
|
173
|
+
console.error(`\n ${error.message}`);
|
|
174
|
+
const cause = error.cause;
|
|
175
|
+
if (cause instanceof Error)
|
|
176
|
+
console.error(` Caused by: ${cause.message}`);
|
|
177
|
+
console.error();
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.error(error);
|
|
181
|
+
}
|
|
141
182
|
process.exit(1);
|
|
142
183
|
});
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { getPackageVersion } from '../utils/config-fixers.js';
|
|
7
|
+
import { writeGitignore } from '../utils/gitignore.js';
|
|
7
8
|
/**
|
|
8
9
|
* Scaffold a Babylon.js IFC viewer project using @ifc-lite/geometry.
|
|
9
10
|
* No WebGPU required — renders via Babylon.js WebGL Engine.
|
|
@@ -20,7 +21,7 @@ export function createBabylonjsTemplate(targetDir, projectName) {
|
|
|
20
21
|
dev: 'vite',
|
|
21
22
|
build: 'tsc && vite build',
|
|
22
23
|
preview: 'vite preview',
|
|
23
|
-
|
|
24
|
+
typecheck: 'tsc --noEmit',
|
|
24
25
|
},
|
|
25
26
|
dependencies: {
|
|
26
27
|
'@ifc-lite/geometry': geometryVersion,
|
|
@@ -40,7 +41,7 @@ export function createBabylonjsTemplate(targetDir, projectName) {
|
|
|
40
41
|
strict: true,
|
|
41
42
|
esModuleInterop: true,
|
|
42
43
|
skipLibCheck: true,
|
|
43
|
-
|
|
44
|
+
noEmit: true,
|
|
44
45
|
},
|
|
45
46
|
include: ['src'],
|
|
46
47
|
}, null, 2));
|
|
@@ -83,7 +84,7 @@ export default defineConfig({
|
|
|
83
84
|
<header>
|
|
84
85
|
<h1>${projectName}</h1>
|
|
85
86
|
<input type="file" id="file-input" accept=".ifc" />
|
|
86
|
-
<span id="status">
|
|
87
|
+
<span id="status">Choose an IFC file to view</span>
|
|
87
88
|
</header>
|
|
88
89
|
<div id="canvas-container">
|
|
89
90
|
<canvas id="viewer"></canvas>
|
|
@@ -95,24 +96,6 @@ export default defineConfig({
|
|
|
95
96
|
`);
|
|
96
97
|
// src/
|
|
97
98
|
mkdirSync(join(targetDir, 'src'));
|
|
98
|
-
mkdirSync(join(targetDir, 'scripts'));
|
|
99
|
-
// scripts/fix-ifc-lite-geometry-worker.mjs
|
|
100
|
-
writeFileSync(join(targetDir, 'scripts', 'fix-ifc-lite-geometry-worker.mjs'), `import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
101
|
-
import path from 'node:path';
|
|
102
|
-
|
|
103
|
-
const entryPath = path.join(process.cwd(), 'node_modules', '@ifc-lite', 'geometry', 'dist', 'index.js');
|
|
104
|
-
|
|
105
|
-
if (!existsSync(entryPath)) {
|
|
106
|
-
process.exit(0);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const source = readFileSync(entryPath, 'utf8');
|
|
110
|
-
const patched = source.replace(/geometry\\.worker\\.ts/g, 'geometry.worker.js');
|
|
111
|
-
|
|
112
|
-
if (patched !== source) {
|
|
113
|
-
writeFileSync(entryPath, patched);
|
|
114
|
-
}
|
|
115
|
-
`);
|
|
116
99
|
// src/ifc-to-babylon.ts
|
|
117
100
|
writeFileSync(join(targetDir, 'src', 'ifc-to-babylon.ts'), `import {
|
|
118
101
|
Mesh, VertexData, StandardMaterial, Color3,
|
|
@@ -132,13 +115,23 @@ export function meshDataToBabylon(meshData: MeshData, scene: Scene): Mesh {
|
|
|
132
115
|
vertexData.indices = meshData.indices;
|
|
133
116
|
vertexData.applyToMesh(mesh);
|
|
134
117
|
|
|
118
|
+
// Fold the per-element local frame: \`positions\` are relative to \`origin\`,
|
|
119
|
+
// so the world position of vertex i is origin + positions[3i..3i+3]. Most
|
|
120
|
+
// elements carry one (it is what keeps building-scale coordinates inside f32
|
|
121
|
+
// precision); dropping it scatters every element to its local offset.
|
|
122
|
+
if (meshData.origin) {
|
|
123
|
+
mesh.position.set(meshData.origin[0], meshData.origin[1], meshData.origin[2]);
|
|
124
|
+
}
|
|
125
|
+
|
|
135
126
|
const [r, g, b, a] = meshData.color;
|
|
136
127
|
const material = new StandardMaterial('mat-' + meshData.expressId, scene);
|
|
137
128
|
material.diffuseColor = new Color3(r, g, b);
|
|
138
129
|
material.specularColor = new Color3(0.15, 0.15, 0.15);
|
|
130
|
+
// Double-sided even when opaque: IFC triangle winding isn't reliably outward,
|
|
131
|
+
// so culling back faces punches holes in walls and slabs.
|
|
132
|
+
material.backFaceCulling = false;
|
|
139
133
|
if (a < 1) {
|
|
140
134
|
material.alpha = a;
|
|
141
|
-
material.backFaceCulling = false;
|
|
142
135
|
}
|
|
143
136
|
|
|
144
137
|
mesh.material = material;
|
|
@@ -241,12 +234,13 @@ fileInput.addEventListener('change', async () => {
|
|
|
241
234
|
status.textContent = file.name + ' — ' + event.totalMeshes + ' meshes';
|
|
242
235
|
}
|
|
243
236
|
}
|
|
244
|
-
} catch (err
|
|
245
|
-
status.textContent = 'Error: ' + err.message;
|
|
237
|
+
} catch (err) {
|
|
238
|
+
status.textContent = 'Error: ' + (err instanceof Error ? err.message : String(err));
|
|
246
239
|
console.error(err);
|
|
247
240
|
}
|
|
248
241
|
});
|
|
249
242
|
`);
|
|
243
|
+
writeGitignore(targetDir);
|
|
250
244
|
// README
|
|
251
245
|
writeFileSync(join(targetDir, 'README.md'), `# ${projectName}
|
|
252
246
|
|
|
@@ -259,10 +253,13 @@ npm install
|
|
|
259
253
|
npm run dev
|
|
260
254
|
\`\`\`
|
|
261
255
|
|
|
262
|
-
Open http://localhost:
|
|
256
|
+
Open the URL Vite prints (http://localhost:5173 by default) and pick an IFC file.
|
|
257
|
+
|
|
258
|
+
No IFC file handy? [AC20-FZK-Haus.ifc](https://github.com/LTplus-AG/ifc-lite/releases/download/fixtures-v1/d42ab0dee01173106c16cd823b4614efe188fa15dd65195d95b9879bec59acc5) is a small public test model.
|
|
263
259
|
|
|
264
260
|
## Learn More
|
|
265
261
|
|
|
266
|
-
- [
|
|
262
|
+
- [IFClite Documentation](https://ifclite.dev/docs/)
|
|
263
|
+
- [Babylon.js Integration Guide](https://ifclite.dev/docs/tutorials/babylonjs-integration/)
|
|
267
264
|
`);
|
|
268
265
|
}
|
package/dist/templates/basic.js
CHANGED
|
@@ -4,21 +4,28 @@
|
|
|
4
4
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { getPackageVersion } from '../utils/config-fixers.js';
|
|
7
|
+
import { writeGitignore } from '../utils/gitignore.js';
|
|
7
8
|
/**
|
|
8
9
|
* Scaffold a minimal TypeScript project for parsing IFC files.
|
|
9
10
|
*/
|
|
10
11
|
export function createBasicTemplate(targetDir, projectName) {
|
|
11
12
|
const parserVersion = getPackageVersion('@ifc-lite/parser');
|
|
13
|
+
// `store.entityIndex.byType` is keyed by the raw STEP spelling, and the
|
|
14
|
+
// PascalCase lookup table for it lives in @ifc-lite/data.
|
|
15
|
+
const dataVersion = getPackageVersion('@ifc-lite/data');
|
|
12
16
|
// package.json
|
|
13
17
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
14
18
|
name: projectName,
|
|
15
|
-
version:
|
|
19
|
+
version: '0.1.0',
|
|
20
|
+
private: true,
|
|
16
21
|
type: 'module',
|
|
17
22
|
scripts: {
|
|
18
|
-
parse: '
|
|
23
|
+
parse: 'tsx src/index.ts',
|
|
19
24
|
build: 'tsc',
|
|
25
|
+
typecheck: 'tsc --noEmit',
|
|
20
26
|
},
|
|
21
27
|
dependencies: {
|
|
28
|
+
'@ifc-lite/data': dataVersion,
|
|
22
29
|
'@ifc-lite/parser': parserVersion,
|
|
23
30
|
},
|
|
24
31
|
devDependencies: {
|
|
@@ -44,7 +51,8 @@ export function createBasicTemplate(targetDir, projectName) {
|
|
|
44
51
|
// src/index.ts
|
|
45
52
|
mkdirSync(join(targetDir, 'src'));
|
|
46
53
|
writeFileSync(join(targetDir, 'src', 'index.ts'), `import { IfcParser } from '@ifc-lite/parser';
|
|
47
|
-
import {
|
|
54
|
+
import { IFC_ENTITY_NAMES } from '@ifc-lite/data';
|
|
55
|
+
import { existsSync, readFileSync } from 'fs';
|
|
48
56
|
|
|
49
57
|
// Example: Parse an IFC file
|
|
50
58
|
const ifcPath = process.argv[2];
|
|
@@ -57,7 +65,13 @@ if (!ifcPath) {
|
|
|
57
65
|
process.exit(1);
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
if (!existsSync(ifcPath)) {
|
|
69
|
+
console.error(\`File not found: \${ifcPath}\`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// readFileSync returns a Node Buffer (Uint8Array subclass) that may be a view
|
|
74
|
+
// into a larger pooled allocation, so slice out just this file's bytes.
|
|
61
75
|
const nodeBuffer = readFileSync(ifcPath);
|
|
62
76
|
const buffer = nodeBuffer.buffer.slice(
|
|
63
77
|
nodeBuffer.byteOffset,
|
|
@@ -67,14 +81,27 @@ const buffer = nodeBuffer.buffer.slice(
|
|
|
67
81
|
const parser = new IfcParser();
|
|
68
82
|
|
|
69
83
|
console.log('Parsing IFC file...');
|
|
70
|
-
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const store = await parser.parseColumnar(buffer);
|
|
87
|
+
|
|
88
|
+
// parseColumnar tolerates garbage input rather than throwing, so an empty
|
|
89
|
+
// store is how "that wasn't an IFC file" actually arrives.
|
|
90
|
+
if (store.entityCount === 0) {
|
|
91
|
+
throw new Error(\`No IFC entities found in \${ifcPath} — is it a STEP/IFC file?\`);
|
|
92
|
+
}
|
|
93
|
+
|
|
71
94
|
console.log('\\nFile parsed successfully!');
|
|
72
95
|
console.log(\` Entities: \${store.entityCount}\`);
|
|
73
96
|
console.log(\` Schema: \${store.schemaVersion}\`);
|
|
74
97
|
|
|
75
|
-
// Count by type
|
|
98
|
+
// Count by type. \`entityIndex.byType\` is keyed by the raw STEP spelling
|
|
99
|
+
// (IFCWALLSTANDARDCASE); IFC_ENTITY_NAMES maps that to the canonical IFC
|
|
100
|
+
// EXPRESS name (IfcWallStandardCase), which is what belongs in front of a
|
|
101
|
+
// user. Geometry resource entities are not in \`store.entities\`, so the
|
|
102
|
+
// per-entity \`getTypeName\` would answer 'Unknown' for most of this list.
|
|
76
103
|
const typeCounts = [...store.entityIndex.byType.entries()]
|
|
77
|
-
.map(([
|
|
104
|
+
.map(([stepName, ids]) => [IFC_ENTITY_NAMES[stepName] ?? stepName, ids.length] as const)
|
|
78
105
|
.sort((a, b) => b[1] - a[1])
|
|
79
106
|
.slice(0, 10);
|
|
80
107
|
|
|
@@ -82,8 +109,12 @@ parser.parseColumnar(buffer).then(store => {
|
|
|
82
109
|
for (const [type, count] of typeCounts) {
|
|
83
110
|
console.log(\` \${type}: \${count}\`);
|
|
84
111
|
}
|
|
85
|
-
})
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(\`\\nFailed to parse \${ifcPath}: \${error instanceof Error ? error.message : String(error)}\`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
}
|
|
86
116
|
`);
|
|
117
|
+
writeGitignore(targetDir);
|
|
87
118
|
// README
|
|
88
119
|
writeFileSync(join(targetDir, 'README.md'), `# ${projectName}
|
|
89
120
|
|
|
@@ -99,6 +130,6 @@ npm run parse ./your-model.ifc
|
|
|
99
130
|
## Learn More
|
|
100
131
|
|
|
101
132
|
- [IFC-Lite Documentation](https://ifclite.dev/docs/)
|
|
102
|
-
- [API Reference](https://ifclite.dev/docs/api/)
|
|
133
|
+
- [TypeScript API Reference](https://ifclite.dev/docs/api/typescript/)
|
|
103
134
|
`);
|
|
104
135
|
}
|
package/dist/templates/react.js
CHANGED
|
@@ -4,14 +4,7 @@
|
|
|
4
4
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { getPackageVersion } from '../utils/config-fixers.js';
|
|
7
|
-
|
|
8
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
9
|
-
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
10
|
-
|
|
11
|
-
`;
|
|
12
|
-
function writeSourceFile(targetDir, relativePath, content) {
|
|
13
|
-
writeFileSync(join(targetDir, relativePath), `${LICENSE_HEADER}${content}`);
|
|
14
|
-
}
|
|
7
|
+
import { writeGitignore } from '../utils/gitignore.js';
|
|
15
8
|
/**
|
|
16
9
|
* Scaffold a standalone React + Vite WebGPU viewer.
|
|
17
10
|
*/
|
|
@@ -38,9 +31,9 @@ export function createReactTemplate(targetDir, projectName) {
|
|
|
38
31
|
devDependencies: {
|
|
39
32
|
'@types/react': '^18.2.0',
|
|
40
33
|
'@types/react-dom': '^18.2.0',
|
|
41
|
-
'@vitejs/plugin-react': '^
|
|
34
|
+
'@vitejs/plugin-react': '^5.0.0',
|
|
42
35
|
typescript: '^5.3.0',
|
|
43
|
-
vite: '^
|
|
36
|
+
vite: '^7.0.0',
|
|
44
37
|
},
|
|
45
38
|
}, null, 2));
|
|
46
39
|
writeFileSync(join(targetDir, 'tsconfig.json'), JSON.stringify({
|
|
@@ -61,7 +54,7 @@ export function createReactTemplate(targetDir, projectName) {
|
|
|
61
54
|
},
|
|
62
55
|
include: ['src'],
|
|
63
56
|
}, null, 2));
|
|
64
|
-
|
|
57
|
+
writeFileSync(join(targetDir, 'vite.config.ts'), `import { defineConfig } from 'vite';
|
|
65
58
|
import react from '@vitejs/plugin-react';
|
|
66
59
|
|
|
67
60
|
const isolationHeaders = {
|
|
@@ -96,7 +89,7 @@ export default defineConfig({
|
|
|
96
89
|
</html>
|
|
97
90
|
`);
|
|
98
91
|
mkdirSync(join(targetDir, 'src'));
|
|
99
|
-
|
|
92
|
+
writeFileSync(join(targetDir, 'src/main.tsx'), `import React from 'react';
|
|
100
93
|
import ReactDOM from 'react-dom/client';
|
|
101
94
|
import App from './App.js';
|
|
102
95
|
import './styles.css';
|
|
@@ -107,7 +100,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
107
100
|
</React.StrictMode>,
|
|
108
101
|
);
|
|
109
102
|
`);
|
|
110
|
-
|
|
103
|
+
writeFileSync(join(targetDir, 'src/App.tsx'), `import { useCallback, useEffect, useRef, useState, type ChangeEvent, type DragEvent } from 'react';
|
|
111
104
|
import { GeometryProcessor } from '@ifc-lite/geometry';
|
|
112
105
|
import { Renderer } from '@ifc-lite/renderer';
|
|
113
106
|
|
|
@@ -402,7 +395,7 @@ export default function App() {
|
|
|
402
395
|
);
|
|
403
396
|
}
|
|
404
397
|
`);
|
|
405
|
-
|
|
398
|
+
writeFileSync(join(targetDir, 'src/styles.css'), `:root {
|
|
406
399
|
color-scheme: dark;
|
|
407
400
|
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
408
401
|
background: #0b1220;
|
|
@@ -555,6 +548,7 @@ body {
|
|
|
555
548
|
}
|
|
556
549
|
}
|
|
557
550
|
`);
|
|
551
|
+
writeGitignore(targetDir);
|
|
558
552
|
writeFileSync(join(targetDir, 'README.md'), `# ${projectName}
|
|
559
553
|
|
|
560
554
|
React + Vite IFC viewer using [IFC-Lite](https://github.com/LTplus-AG/ifc-lite).
|
|
@@ -15,6 +15,7 @@ export function createServerNativeTemplate(targetDir, projectName) {
|
|
|
15
15
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
16
16
|
name: projectName,
|
|
17
17
|
version: '0.1.0',
|
|
18
|
+
private: true,
|
|
18
19
|
type: 'module',
|
|
19
20
|
description: 'IFC processing server (native binary) with TypeScript client',
|
|
20
21
|
scripts: {
|
package/dist/templates/server.js
CHANGED
|
@@ -15,7 +15,7 @@ export function createServerTemplate(targetDir, projectName) {
|
|
|
15
15
|
|
|
16
16
|
services:
|
|
17
17
|
ifc-server:
|
|
18
|
-
image: ghcr.io/
|
|
18
|
+
image: ghcr.io/ltplus-ag/ifc-lite-server:latest
|
|
19
19
|
container_name: ${projectName}-server
|
|
20
20
|
ports:
|
|
21
21
|
- "\${PORT:-3001}:8080"
|
|
@@ -47,7 +47,7 @@ volumes:
|
|
|
47
47
|
|
|
48
48
|
services:
|
|
49
49
|
ifc-server:
|
|
50
|
-
image: ghcr.io/
|
|
50
|
+
image: ghcr.io/ltplus-ag/ifc-lite-server:latest
|
|
51
51
|
container_name: ${projectName}-server-dev
|
|
52
52
|
ports:
|
|
53
53
|
- "\${PORT:-3001}:8080"
|
|
@@ -167,6 +167,7 @@ cache
|
|
|
167
167
|
writeFileSync(join(targetDir, 'package.json'), JSON.stringify({
|
|
168
168
|
name: projectName,
|
|
169
169
|
version: '0.1.0',
|
|
170
|
+
private: true,
|
|
170
171
|
type: 'module',
|
|
171
172
|
description: 'IFC processing server with TypeScript client',
|
|
172
173
|
scripts: {
|
|
@@ -585,8 +586,8 @@ The Docker image works on any container platform:
|
|
|
585
586
|
|
|
586
587
|
\`\`\`bash
|
|
587
588
|
# Pull and run
|
|
588
|
-
docker pull ghcr.io/
|
|
589
|
-
docker run -p 8080:8080 -v ifc-cache:/app/cache ghcr.io/
|
|
589
|
+
docker pull ghcr.io/ltplus-ag/ifc-lite-server:latest
|
|
590
|
+
docker run -p 8080:8080 -v ifc-cache:/app/cache ghcr.io/ltplus-ag/ifc-lite-server
|
|
590
591
|
\`\`\`
|
|
591
592
|
|
|
592
593
|
### Environment Variables
|
|
@@ -604,7 +605,7 @@ RUST_LOG=info
|
|
|
604
605
|
## Learn More
|
|
605
606
|
|
|
606
607
|
- [IFC-Lite Documentation](https://ifclite.dev/docs/)
|
|
607
|
-
- [Server
|
|
608
|
+
- [Server Guide](https://ifclite.dev/docs/guide/server/)
|
|
608
609
|
- [GitHub Repository](https://github.com/LTplus-AG/ifc-lite)
|
|
609
610
|
`);
|
|
610
611
|
console.log(' Created docker-compose.yml');
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { getPackageVersion } from '../utils/config-fixers.js';
|
|
7
|
+
import { writeGitignore } from '../utils/gitignore.js';
|
|
7
8
|
/**
|
|
8
9
|
* Scaffold a Three.js IFC viewer project using @ifc-lite/geometry.
|
|
9
10
|
* No WebGPU required — renders via Three.js WebGLRenderer.
|
|
@@ -20,7 +21,7 @@ export function createThreejsTemplate(targetDir, projectName) {
|
|
|
20
21
|
dev: 'vite',
|
|
21
22
|
build: 'tsc && vite build',
|
|
22
23
|
preview: 'vite preview',
|
|
23
|
-
|
|
24
|
+
typecheck: 'tsc --noEmit',
|
|
24
25
|
},
|
|
25
26
|
dependencies: {
|
|
26
27
|
'@ifc-lite/geometry': geometryVersion,
|
|
@@ -41,7 +42,7 @@ export function createThreejsTemplate(targetDir, projectName) {
|
|
|
41
42
|
strict: true,
|
|
42
43
|
esModuleInterop: true,
|
|
43
44
|
skipLibCheck: true,
|
|
44
|
-
|
|
45
|
+
noEmit: true,
|
|
45
46
|
},
|
|
46
47
|
include: ['src'],
|
|
47
48
|
}, null, 2));
|
|
@@ -89,7 +90,7 @@ export default defineConfig({
|
|
|
89
90
|
<header>
|
|
90
91
|
<h1>${projectName}</h1>
|
|
91
92
|
<input type="file" id="file-input" accept=".ifc" />
|
|
92
|
-
<span id="status">
|
|
93
|
+
<span id="status">Choose an IFC file to view</span>
|
|
93
94
|
</header>
|
|
94
95
|
<div id="canvas-container">
|
|
95
96
|
<canvas id="viewer"></canvas>
|
|
@@ -101,24 +102,6 @@ export default defineConfig({
|
|
|
101
102
|
`);
|
|
102
103
|
// src/
|
|
103
104
|
mkdirSync(join(targetDir, 'src'));
|
|
104
|
-
mkdirSync(join(targetDir, 'scripts'));
|
|
105
|
-
// scripts/fix-ifc-lite-geometry-worker.mjs
|
|
106
|
-
writeFileSync(join(targetDir, 'scripts', 'fix-ifc-lite-geometry-worker.mjs'), `import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
107
|
-
import path from 'node:path';
|
|
108
|
-
|
|
109
|
-
const entryPath = path.join(process.cwd(), 'node_modules', '@ifc-lite', 'geometry', 'dist', 'index.js');
|
|
110
|
-
|
|
111
|
-
if (!existsSync(entryPath)) {
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const source = readFileSync(entryPath, 'utf8');
|
|
116
|
-
const patched = source.replace(/geometry\\.worker\\.ts/g, 'geometry.worker.js');
|
|
117
|
-
|
|
118
|
-
if (patched !== source) {
|
|
119
|
-
writeFileSync(entryPath, patched);
|
|
120
|
-
}
|
|
121
|
-
`);
|
|
122
105
|
// src/ifc-to-threejs.ts
|
|
123
106
|
writeFileSync(join(targetDir, 'src', 'ifc-to-threejs.ts'), `import * as THREE from 'three';
|
|
124
107
|
import type { MeshData } from '@ifc-lite/geometry';
|
|
@@ -145,6 +128,11 @@ export function meshDataToThree(mesh: MeshData): THREE.Mesh {
|
|
|
145
128
|
});
|
|
146
129
|
|
|
147
130
|
const threeMesh = new THREE.Mesh(geometry, material);
|
|
131
|
+
// Fold the per-element local frame: \`positions\` are relative to \`origin\`,
|
|
132
|
+
// so the world position of vertex i is origin + positions[3i..3i+3]. Most
|
|
133
|
+
// elements carry one (it is what keeps building-scale coordinates inside f32
|
|
134
|
+
// precision); dropping it scatters every element to its local offset.
|
|
135
|
+
if (mesh.origin) threeMesh.position.fromArray(mesh.origin);
|
|
148
136
|
threeMesh.userData.expressId = mesh.expressId;
|
|
149
137
|
return threeMesh;
|
|
150
138
|
}
|
|
@@ -260,12 +248,13 @@ fileInput.addEventListener('change', async () => {
|
|
|
260
248
|
status.textContent = file.name + ' — ' + event.totalMeshes + ' meshes';
|
|
261
249
|
}
|
|
262
250
|
}
|
|
263
|
-
} catch (err
|
|
264
|
-
status.textContent = 'Error: ' + err.message;
|
|
251
|
+
} catch (err) {
|
|
252
|
+
status.textContent = 'Error: ' + (err instanceof Error ? err.message : String(err));
|
|
265
253
|
console.error(err);
|
|
266
254
|
}
|
|
267
255
|
});
|
|
268
256
|
`);
|
|
257
|
+
writeGitignore(targetDir);
|
|
269
258
|
// README
|
|
270
259
|
writeFileSync(join(targetDir, 'README.md'), `# ${projectName}
|
|
271
260
|
|
|
@@ -278,7 +267,9 @@ npm install
|
|
|
278
267
|
npm run dev
|
|
279
268
|
\`\`\`
|
|
280
269
|
|
|
281
|
-
Open http://localhost:
|
|
270
|
+
Open the URL Vite prints (http://localhost:5173 by default) and pick an IFC file.
|
|
271
|
+
|
|
272
|
+
No IFC file handy? [AC20-FZK-Haus.ifc](https://github.com/LTplus-AG/ifc-lite/releases/download/fixtures-v1/d42ab0dee01173106c16cd823b4614efe188fa15dd65195d95b9879bec59acc5) is a small public test model.
|
|
282
273
|
|
|
283
274
|
## Learn More
|
|
284
275
|
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync, rmSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
-
import { execFileSync } from 'child_process';
|
|
6
|
+
import { execFileSync, spawnSync } from 'child_process';
|
|
7
7
|
/** Cache of npm-resolved versions to avoid redundant registry queries. */
|
|
8
8
|
const versionCache = new Map();
|
|
9
9
|
const publishedVersionsCache = new Map();
|
|
10
10
|
const VALID_PACKAGE_NAME = /^(?:@[\w.-]+\/)?[\w.-]+$/;
|
|
11
11
|
const NPM_TIMEOUT_MS = 30000;
|
|
12
12
|
const MAX_VERSION_CANDIDATES = 10;
|
|
13
|
+
/** npm's own codes for "the registry does not have that artifact". */
|
|
14
|
+
const MISSING_ARTIFACT_CODE = /\bE(404|410)\b/;
|
|
13
15
|
function readJsonFromNpm(args) {
|
|
14
16
|
const command = process.platform === 'win32' ? process.env.ComSpec ?? 'cmd.exe' : 'npm';
|
|
15
17
|
const commandArgs = process.platform === 'win32' ? ['/d', '/s', '/c', 'npm', ...args] : args;
|
|
@@ -43,6 +45,31 @@ function getVersionDependencies(packageName, version) {
|
|
|
43
45
|
}
|
|
44
46
|
return json;
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Is the version's own tarball actually downloadable?
|
|
50
|
+
*
|
|
51
|
+
* A version can be `dist-tags.latest`, be listed in the packument, and
|
|
52
|
+
* advertise a `dist.tarball` that 404s -- a publish that registered the
|
|
53
|
+
* version without storing the artifact. `@ifc-lite/server-client@3.1.0` was
|
|
54
|
+
* exactly that, so every `--template server` scaffold died at `npm install`
|
|
55
|
+
* with a 404 on a URL npm itself had handed it (#5570).
|
|
56
|
+
*
|
|
57
|
+
* `npm pack --dry-run` is the probe rather than a raw HTTP request because it
|
|
58
|
+
* resolves through npm's own registry, proxy and auth config, and writes
|
|
59
|
+
* nothing. Only an explicit "not there" answer rejects a candidate: a network
|
|
60
|
+
* failure or an auth wall must not silently push the scaffold back onto an
|
|
61
|
+
* older version, so anything else is treated as "cannot tell, assume fine".
|
|
62
|
+
*/
|
|
63
|
+
function isTarballFetchable(packageName, version) {
|
|
64
|
+
const args = ['pack', `${packageName}@${version}`, '--dry-run', '--json'];
|
|
65
|
+
const command = process.platform === 'win32' ? process.env.ComSpec ?? 'cmd.exe' : 'npm';
|
|
66
|
+
const commandArgs = process.platform === 'win32' ? ['/d', '/s', '/c', 'npm', ...args] : args;
|
|
67
|
+
const result = spawnSync(command, commandArgs, { encoding: 'utf-8', timeout: NPM_TIMEOUT_MS });
|
|
68
|
+
if (result.status === 0)
|
|
69
|
+
return true;
|
|
70
|
+
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`;
|
|
71
|
+
return !MISSING_ARTIFACT_CODE.test(output);
|
|
72
|
+
}
|
|
46
73
|
function isInstallablePublishedVersion(packageName, version) {
|
|
47
74
|
const dependencies = getVersionDependencies(packageName, version);
|
|
48
75
|
for (const [dependencyName, dependencyRange] of Object.entries(dependencies)) {
|
|
@@ -55,7 +82,7 @@ function isInstallablePublishedVersion(packageName, version) {
|
|
|
55
82
|
return false;
|
|
56
83
|
}
|
|
57
84
|
}
|
|
58
|
-
return
|
|
85
|
+
return isTarballFetchable(packageName, version);
|
|
59
86
|
}
|
|
60
87
|
/**
|
|
61
88
|
* Fetch the latest installable published version of a specific npm package.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write the .gitignore every scaffolded project needs. Without one, the first
|
|
3
|
+
* `git add .` in a fresh project commits `node_modules/` and `dist/` — and the
|
|
4
|
+
* viewer templates' `dist/` carries an 8 MB wasm bundle.
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeGitignore(targetDir: string): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
import { writeFileSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
/**
|
|
7
|
+
* Write the .gitignore every scaffolded project needs. Without one, the first
|
|
8
|
+
* `git add .` in a fresh project commits `node_modules/` and `dist/` — and the
|
|
9
|
+
* viewer templates' `dist/` carries an 8 MB wasm bundle.
|
|
10
|
+
*/
|
|
11
|
+
export function writeGitignore(targetDir) {
|
|
12
|
+
writeFileSync(join(targetDir, '.gitignore'), `# Dependencies
|
|
13
|
+
node_modules/
|
|
14
|
+
|
|
15
|
+
# Build output
|
|
16
|
+
dist/
|
|
17
|
+
|
|
18
|
+
# Environment files
|
|
19
|
+
.env
|
|
20
|
+
.env.local
|
|
21
|
+
.env.*.local
|
|
22
|
+
|
|
23
|
+
# IDE
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
|
|
29
|
+
# OS
|
|
30
|
+
.DS_Store
|
|
31
|
+
Thumbs.db
|
|
32
|
+
|
|
33
|
+
# Logs
|
|
34
|
+
*.log
|
|
35
|
+
npm-debug.log*
|
|
36
|
+
`);
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-ifc-lite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"description": "Create IFC-Lite projects with one command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"node": ">=18.0.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^26.4
|
|
34
|
+
"@types/node": "^26.6.4",
|
|
35
35
|
"typescript": "^5.3.0",
|
|
36
|
-
"vitest": "^
|
|
36
|
+
"vitest": "^5.0.3"
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://ifclite.dev/docs/",
|
|
39
39
|
"bugs": "https://github.com/LTplus-AG/ifc-lite/issues",
|