Skip to content

Commit ab7f4c3

Browse files
authored
Merge branch 'develop' into main
2 parents 289309c + e9d8ade commit ab7f4c3

File tree

8 files changed

+234
-7
lines changed

8 files changed

+234
-7
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@ src/cpp/_deps/fastfloat-src/
6969
src/cpp/_deps/glm-src/
7070
src/cpp/_deps/spdlog-src/
7171
src/cpp/_deps/tinycpptest-src/
72-
src/cpp/_deps/tinynurbs-src/
72+
src/cpp/_deps/tinynurbs-src/

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"postversion": "node src/setversion.js",
4444
"benchmark": "ts-node ./tests/benchmark/benchmark.ts",
4545
"regression": "node --max-old-space-size=8192 ./tests/regression/regression.mjs",
46+
"regression-save-glb": "node --max-old-space-size=8192 ./tests/regression/regression-save-glb.mjs",
4647
"regression-update": "node --max-old-space-size=8192 ./tests/regression/regression.mjs update",
4748
"test": "jest --runInBand ",
4849
"gen-docs": "typedoc --out dist/docs && cpy ./banner.png ./dist/docs",

src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,9 +1955,9 @@ namespace webifc::geometry
19551955
_loader.MoveToArgumentOffset(expressID, 1);
19561956
auto placementID = _loader.GetRefArgument();
19571957
_loader.MoveToArgumentOffset(expressID, 2);
1958-
double SegmentStart = ReadLenghtMeasure();
1958+
double SegmentStart = ReadLengthMeasure();
19591959
_loader.MoveToArgumentOffset(expressID, 4);
1960-
double SegmentEnd = ReadLenghtMeasure();
1960+
double SegmentEnd = ReadLengthMeasure();
19611961
_loader.MoveToArgumentOffset(expressID, 6);
19621962
auto curveID = _loader.GetRefArgument();
19631963

@@ -3028,7 +3028,7 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const
30283028
if (_loader.GetTokenType() != parsing::IfcTokenType::EMPTY)
30293029
{
30303030
_loader.StepBack();
3031-
lnSegment = ReadLenghtMeasure();
3031+
lnSegment = ReadLengthMeasure();
30323032
}
30333033

30343034
_loader.MoveToArgumentOffset(expressID, 5);
@@ -3621,7 +3621,7 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const
36213621
return _angleUnits;
36223622
}
36233623

3624-
double IfcGeometryLoader::ReadLenghtMeasure() const
3624+
double IfcGeometryLoader::ReadLengthMeasure() const
36253625
{
36263626
parsing::IfcTokenType t = _loader.GetTokenType();
36273627
if (t == parsing::IfcTokenType::LABEL)

src/cpp/web-ifc/geometry/IfcGeometryLoader.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ namespace webifc::geometry
6565
IfcTrimmingSelect GetTrimSelect(uint32_t DIM, std::vector<uint32_t> &tapeOffsets) const;
6666
void ComputeCurve(uint32_t expressID, IfcCurve &curve, uint8_t dimensions, bool edge, int sameSense = -1, int trimSense = -1, IfcTrimmingArguments trim = {}) const;
6767
void convertAngleUnits(double &Degrees, double &Rad) const;
68-
double ReadLenghtMeasure() const;
68+
double ReadLengthMeasure() const;
6969
std::vector<IfcSegmentIndexSelect> ReadCurveIndices() const;
7070
const webifc::parsing::IfcLoader &_loader;
7171
const webifc::schema::IfcSchemaManager &_schemaManager;

src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,21 @@ namespace webifc::geometry
290290
resultMesh.hasColor = false;
291291
}
292292

293+
294+
// Sometimes, a geometric item like IFCEXTRUDEDAREASOLID has a color assigned.
295+
// With flatten() and BoolProcess, that color gets lost. Restore it here:
296+
if (!mesh.hasGeometry && mesh.children.size() > 0)
297+
{
298+
auto geometricItem = mesh.children.front();
299+
std::optional<glm::dvec4> geometricItemColor = geometricItem.GetColor();
300+
if (geometricItemColor.has_value())
301+
{
302+
glm::dvec4 colorValue = geometricItemColor.value();
303+
resultMesh.color = colorValue;
304+
resultMesh.hasColor = true;
305+
}
306+
}
307+
293308
return resultMesh;
294309
}
295310
else

src/cpp/web-ifc/geometry/IfcGeometryProcessor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ namespace webifc::geometry
5858
private:
5959
IfcGeometryProcessor(const IfcGeometrySettings &settings,std::unordered_map<uint32_t, IfcGeometry> expressIDToGeometry,const IfcGeometryLoader &geometryLoader,glm::dmat4 transformation, const parsing::IfcLoader &loader, booleanManager boolEngine, const schema::IfcSchemaManager &schemaManager, bool isCoordinated, uint32_t expressIdCyl, uint32_t expressIdRect, glm::dmat4 coordinationMatrix, IfcGeometry predefinedCylinder, IfcGeometry predefinedCube);
6060
IfcGeometrySettings _settings;
61+
6162
std::optional<glm::dvec4> GetStyleItemFromExpressId(uint32_t expressID);
6263
void AddFaceToGeometry(uint32_t expressID, IfcGeometry &geometry);
6364
IfcGeometry GetBrep(uint32_t expressID);
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import * as THREE from "three";
2+
import { readFileSync, writeFileSync, readdirSync, readSync, openSync, existsSync, mkdirSync } from "fs";
3+
import * as path from "path";
4+
const { createHash } = await import('node:crypto');
5+
import { IfcAPI } from "../../dist/web-ifc-api-node.js";
6+
import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js";
7+
import { Blob, FileReader } from 'vblob';
8+
global.Blob = Blob;
9+
global.FileReader = FileReader;
10+
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
11+
import AdmZip from 'adm-zip';
12+
13+
const REGRESSION_FILES_DIR = "./tests/ifcfiles/";
14+
const REGRESSION_RESULT_FILE = "./tests/regression/results.json";
15+
const OUTPUT_GLB_DIR = path.join("tests", "regression", "glb");
16+
17+
var materials = {};
18+
var ifcAPI;
19+
var regressionResults = {};
20+
21+
async function RunRegression() {
22+
try {
23+
let update = false;
24+
if (process.argv.includes("update")) update = true;
25+
ifcAPI = new IfcAPI();
26+
await ifcAPI.Init();
27+
let files = await GetRegressionFiles();
28+
for (let fileName of files) {
29+
let properFileName = fileName.replaceAll("\\", "/");
30+
regressionResults[properFileName] = await CreateModelResuts(fileName);
31+
regressionResults[properFileName] = createHash('sha256')
32+
.update(JSON.stringify(regressionResults[properFileName]))
33+
.digest('hex');
34+
}
35+
if (update) {
36+
writeFileSync(REGRESSION_RESULT_FILE, JSON.stringify(regressionResults));
37+
console.log("--------Results Updated-----------");
38+
} else {
39+
let regressionResultsCurrent = JSON.parse(readFileSync(REGRESSION_RESULT_FILE));
40+
console.log("--------Regression Results-----------");
41+
let passTests = true;
42+
try {
43+
for (let fileName in regressionResults) {
44+
let normFileName = fileName.replaceAll("\\", "/");
45+
if (normFileName in regressionResultsCurrent) {
46+
if (regressionResultsCurrent[normFileName] == regressionResults[normFileName])
47+
console.log(normFileName + " - PASS");
48+
else {
49+
console.log(normFileName + " - FAIL");
50+
passTests = false;
51+
}
52+
} else console.log("Could not find: " + normFileName);
53+
}
54+
} catch (e) {
55+
console.log(e);
56+
}
57+
if (!passTests) {
58+
console.log("One or more models failed - please verify the models and if you are happy run npm run regression-update");
59+
process.exit(1);
60+
}
61+
}
62+
} catch (e) {
63+
console.log(e);
64+
}
65+
}
66+
67+
async function GetRegressionFiles() {
68+
let files = readdirSync(REGRESSION_FILES_DIR + "public/")
69+
.filter((f) => (f.endsWith(".ifc") || f.endsWith(".ifczip")))
70+
.map((f) => path.join(REGRESSION_FILES_DIR + "public/", f));
71+
let privateFiles = [];
72+
try {
73+
privateFiles = readdirSync(REGRESSION_FILES_DIR + "private/")
74+
.filter((f) => (f.endsWith(".ifc") || f.endsWith(".ifczip")))
75+
.map((f) => path.join(REGRESSION_FILES_DIR + "private/", f));
76+
} catch (e) {}
77+
return files.concat(privateFiles);
78+
}
79+
80+
async function CreateModelResuts(filename) {
81+
let modelID;
82+
console.log("Parsing: " + filename);
83+
if (filename.includes(".ifczip")) {
84+
let zip = new AdmZip(filename);
85+
zip.getEntries().forEach(function (zipEntry) {
86+
let ifcdata = zipEntry.getData();
87+
modelID = ifcAPI.OpenModel(ifcdata);
88+
});
89+
} else {
90+
let file = openSync(filename);
91+
let retriever = function (offset, size) {
92+
let data = new Uint8Array(size);
93+
let bytesRead = readSync(file, data, 0, size, offset);
94+
if (bytesRead <= 0) return new Uint8Array(0);
95+
return data;
96+
}
97+
try {
98+
modelID = ifcAPI.OpenModelFromCallback(retriever);
99+
} catch (e) {
100+
console.log(e);
101+
}
102+
}
103+
let geometries = [];
104+
ifcAPI.StreamAllMeshes(modelID, (mesh) => {
105+
const placedGeometries = mesh.geometries;
106+
for (let i = 0; i < placedGeometries.size(); i++) {
107+
const placedGeometry = placedGeometries.get(i);
108+
let meshObj = getPlacedGeometry(modelID, placedGeometry);
109+
let geom = meshObj.geometry.applyMatrix4(meshObj.matrix);
110+
geometries.push(geom);
111+
}
112+
});
113+
114+
console.log("Parsed Model: " + filename + " - Loading " + geometries.length + " geometries");
115+
if (geometries.length > 0) {
116+
const combinedGeometry = BufferGeometryUtils.mergeGeometries(geometries);
117+
const mat = new THREE.MeshStandardMaterial({ side: THREE.DoubleSide });
118+
mat.vertexColors = true;
119+
const mergedMesh = new THREE.Mesh(combinedGeometry, mat);
120+
const scene = new THREE.Scene();
121+
scene.add(mergedMesh);
122+
const exporter = new GLTFExporter();
123+
124+
// Ensure output folder exists
125+
if (!existsSync(OUTPUT_GLB_DIR)) {
126+
mkdirSync(OUTPUT_GLB_DIR, { recursive: true });
127+
}
128+
129+
return new Promise((resolve, reject) => {
130+
exporter.parse(
131+
scene,
132+
(glb) => {
133+
// glb is an ArrayBuffer when { binary: true } is used.
134+
const baseName = path.basename(filename, path.extname(filename));
135+
const outFile = path.join(OUTPUT_GLB_DIR, baseName + ".glb");
136+
writeFileSync(outFile, Buffer.from(glb));
137+
console.log("Saved GLB file:", outFile);
138+
resolve(glb);
139+
},
140+
(e) => { reject(e); },
141+
{ binary: true }
142+
);
143+
});
144+
}
145+
}
146+
147+
function getPlacedGeometry(modelID, placedGeometry) {
148+
const geometry = getBufferGeometry(modelID, placedGeometry);
149+
const material = getMeshMaterial(placedGeometry.color);
150+
const mesh = new THREE.Mesh(geometry, material);
151+
mesh.matrix = getMeshMatrix(placedGeometry.flatTransformation);
152+
mesh.matrixAutoUpdate = false;
153+
return mesh;
154+
}
155+
156+
function getBufferGeometry(modelID, placedGeometry) {
157+
const geometry = ifcAPI.GetGeometry(modelID, placedGeometry.geometryExpressID);
158+
const verts = ifcAPI.GetVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize());
159+
const indices = ifcAPI.GetIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize());
160+
const bufferGeometry = ifcGeometryToBuffer(placedGeometry.color, verts, indices);
161+
geometry.delete();
162+
return bufferGeometry;
163+
}
164+
165+
function getMeshMaterial(color) {
166+
let colID = `${color.x}${color.y}${color.z}${color.w}`;
167+
if (materials[colID]) {
168+
return materials[colID];
169+
}
170+
const col = new THREE.Color(color.x, color.y, color.z);
171+
const material = new THREE.MeshStandardMaterial({
172+
color: col,
173+
side: THREE.DoubleSide,
174+
});
175+
material.transparent = color.w !== 1;
176+
if (material.transparent) material.opacity = color.w;
177+
materials[colID] = material;
178+
return material;
179+
}
180+
181+
function getMeshMatrix(matrix) {
182+
const mat = new THREE.Matrix4();
183+
mat.fromArray(matrix);
184+
return mat;
185+
}
186+
187+
function ifcGeometryToBuffer(color, vertexData, indexData) {
188+
const geometry = new THREE.BufferGeometry();
189+
let posFloats = new Float32Array(vertexData.length / 2);
190+
let normFloats = new Float32Array(vertexData.length / 2);
191+
let colorFloats = new Float32Array(vertexData.length / 2);
192+
for (let i = 0; i < vertexData.length; i += 6) {
193+
posFloats[i / 2 + 0] = vertexData[i + 0];
194+
posFloats[i / 2 + 1] = vertexData[i + 1];
195+
posFloats[i / 2 + 2] = vertexData[i + 2];
196+
normFloats[i / 2 + 0] = vertexData[i + 3];
197+
normFloats[i / 2 + 1] = vertexData[i + 4];
198+
normFloats[i / 2 + 2] = vertexData[i + 5];
199+
colorFloats[i / 2 + 0] = color.x;
200+
colorFloats[i / 2 + 1] = color.y;
201+
colorFloats[i / 2 + 2] = color.z;
202+
}
203+
geometry.setAttribute("position", new THREE.BufferAttribute(posFloats, 3));
204+
geometry.setAttribute("normal", new THREE.BufferAttribute(normFloats, 3));
205+
geometry.setAttribute("color", new THREE.BufferAttribute(colorFloats, 3));
206+
geometry.setIndex(new THREE.BufferAttribute(indexData, 1));
207+
return geometry;
208+
}
209+
210+
RunRegression().then(res => console.log("FINISHED"));

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4191,4 +4191,4 @@ yn@3.1.1:
41914191
yocto-queue@^0.1.0:
41924192
version "0.1.0"
41934193
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
4194-
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
4194+
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==

0 commit comments

Comments
 (0)