Skip to content

Add IFCTOPOLOGYREPRESENTATION. Add IFCFACESURFACE, IFCEDGE and IFCCAR… #1300

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 30, 2025
Merged
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,12 @@ examples/nodejs/*.ifc
tests/ifcfiles/private/*.ifc
tests/ifcfiles/private/*.stl
tests/ifcfiles/created.ifc

#external dependencies downloaded by Cmake
src/cpp/_deps/cdt-src/
src/cpp/_deps/earcut-src/
src/cpp/_deps/fastfloat-src/
src/cpp/_deps/glm-src/
src/cpp/_deps/spdlog-src/
src/cpp/_deps/tinycpptest-src/
src/cpp/_deps/tinynurbs-src/
26 changes: 23 additions & 3 deletions src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -902,9 +902,15 @@ namespace webifc::geometry
case schema::IFCCURVESTYLE:
{
_loader.MoveToArgumentOffset(expressID, 3);
auto foundColor = GetColor(_loader.GetRefArgument());
if (foundColor)
return foundColor;
// argument 3 (CurveColour) is optional, so check if it is set
auto tt = _loader.GetTokenType();
if (tt == parsing::REF)
{
_loader.StepBack();
auto foundColor = GetColor(_loader.GetRefArgument());
if (foundColor)
return foundColor;
}
return {};
}
case schema::IFCFILLAREASTYLEHATCHING:
Expand Down Expand Up @@ -1315,6 +1321,20 @@ namespace webifc::geometry

switch (lineType)
{

case schema::IFCEDGE:
{
_loader.MoveToArgumentOffset(expressID, 0);
glm::dvec3 p1 = GetVertexPoint(_loader.GetRefArgument());
_loader.MoveToArgumentOffset(expressID, 1);
glm::dvec3 p2 = GetVertexPoint(_loader.GetRefArgument());

IfcCurve curve;
curve.points.push_back(p1);
curve.points.push_back(p2);

return curve;
}
case schema::IFCEDGECURVE:
{
IfcTrimmingArguments ts;
Expand Down
136 changes: 134 additions & 2 deletions src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -620,8 +620,10 @@ namespace webifc::geometry

return mesh;
}
case schema::IFCTOPOLOGYREPRESENTATION:
case schema::IFCSHAPEREPRESENTATION:
{
// IFCTOPOLOGYREPRESENTATION and IFCSHAPEREPRESENTATION are identical in attributes layout
_loader.MoveToArgumentOffset(expressID, 1);
auto type = _loader.GetStringArgument();

Expand Down Expand Up @@ -674,6 +676,54 @@ namespace webifc::geometry

return mesh;
}
case schema::IFCFACESURFACE:
{
IfcGeometry geometry;
_loader.MoveToArgumentOffset(expressID, 0);
auto bounds = _loader.GetSetArgument();

std::vector<IfcBound3D> bounds3D(bounds.size());

for (size_t i = 0; i < bounds.size(); i++)
{
uint32_t boundID = _loader.GetRefArgument(bounds[i]);
bounds3D[i] = _geometryLoader.GetBound(boundID);
}

TriangulateBounds(geometry, bounds3D, expressID);

_loader.MoveToArgumentOffset(expressID, 1);
auto surfRef = _loader.GetRefArgument();

auto surface = GetSurface(surfRef);

if (surface.BSplineSurface.Active)
{
TriangulateBspline(geometry, bounds3D, surface, _geometryLoader.GetLinearScalingFactor());
}
else if (surface.CylinderSurface.Active)
{
TriangulateCylindricalSurface(geometry, bounds3D, surface, _circleSegments);
}
else if (surface.RevolutionSurface.Active)
{
TriangulateRevolution(geometry, bounds3D, surface, _circleSegments);
}
else if (surface.ExtrusionSurface.Active)
{
TriangulateExtrusion(geometry, bounds3D, surface);
}
else
{
TriangulateBounds(geometry, bounds3D, expressID);
}

_expressIDToGeometry[expressID] = geometry;
mesh.expressID = expressID;
mesh.hasGeometry = true;

break;
}
case schema::IFCTRIANGULATEDIRREGULARNETWORK:
case schema::IFCTRIANGULATEDFACESET:
{
Expand Down Expand Up @@ -1006,11 +1056,89 @@ namespace webifc::geometry

return mesh;
}
case schema::IFCBOUNDINGBOX:
// ignore bounding box
return mesh;

case schema::IFCCARTESIANPOINT:
{
// IfcCartesianPoint is derived from IfcRepresentationItem and can be used as representation item directly
IfcGeometry geom;
auto point = _geometryLoader.GetCartesianPoint3D(expressID);
geom.vertexData.push_back(point.x);
geom.vertexData.push_back(point.y);
geom.vertexData.push_back(point.z);
geom.vertexData.push_back(0); // needs to be 6 values per vertex
geom.vertexData.push_back(0);
geom.vertexData.push_back(1);
geom.indexData.push_back(0);

geom.numPoints = 1;
geom.isPolygon = true;
mesh.hasGeometry = true;
_expressIDToGeometry[expressID] = geom;

return mesh;
}
case schema::IFCEDGE:
{
// IfcEdge is derived from IfcRepresentationItem and can be used as representation item directly
IfcCurve edge = _geometryLoader.GetEdge(expressID);
IfcGeometry geom;

for (uint32_t i = 0; i < edge.points.size(); i++)
{
auto vert = edge.points[i];
geom.vertexData.push_back(vert.x);
geom.vertexData.push_back(vert.y);
geom.vertexData.push_back(vert.z);
geom.vertexData.push_back(0); // needs to be 6 values per vertex
geom.vertexData.push_back(0);
geom.vertexData.push_back(1);
geom.indexData.push_back(i);
}
geom.numPoints = edge.points.size();
geom.isPolygon = true;
mesh.hasGeometry = true;
_expressIDToGeometry[expressID] = geom;

return mesh;
}
case schema::IFCCIRCLE:
case schema::IFCPOLYLINE:
case schema::IFCINDEXEDPOLYCURVE:
case schema::IFCTRIMMEDCURVE:
// ignore polylines as meshes
return mesh;
{
auto lineProfileType = _loader.GetLineType(expressID);
IfcCurve curve = _geometryLoader.GetCurve(expressID, 3, false);

if (curve.points.size() > 0) {
IfcGeometry geom;

for (uint32_t i = 0; i < curve.points.size(); i++)
{
auto vert = curve.points[i];
geom.vertexData.push_back(vert.x);
geom.vertexData.push_back(vert.y);
geom.vertexData.push_back(vert.z);
geom.vertexData.push_back(0); // needs to be 6 values per vertex
geom.vertexData.push_back(0);
geom.vertexData.push_back(1);
geom.indexData.push_back(i);
}
geom.numPoints = curve.points.size();
geom.isPolygon = true;
mesh.hasGeometry = true;
_expressIDToGeometry[expressID] = geom;
}

return mesh;
}
case schema::IFCTEXTLITERAL:
case schema::IFCTEXTLITERALWITHEXTENT:
// TODO: save string of the text literal in IfcComposedMesh
return mesh;

default:
spdlog::error("[GetMesh()] unexpected mesh type {}", expressID, lineType);
break;
Expand Down Expand Up @@ -1454,6 +1582,10 @@ namespace webifc::geometry


auto geom = _expressIDToGeometry[composedMesh.expressID];
if (geom.isPolygon)
{
return; // only triangles here
}
if (geometry.testReverse()) geom.ReverseFaces();

auto translation = glm::dmat4(1.0);
Expand Down
1 change: 1 addition & 0 deletions src/cpp/web-ifc/geometry/representation/IfcGeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ namespace webifc::geometry {
std::vector<Plane> planes;

bool hasPlanes = false;
bool isPolygon = false;
uint32_t numPoints = 0;
uint32_t numFaces = 0;

Expand Down
2 changes: 1 addition & 1 deletion src/cpp/web-ifc/parsing/IfcLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ namespace webifc::parsing {
StepBack();
GetSetArgument();
noArguments++;
continue;
continue;
}
if (t == IfcTokenType::STRING || t == IfcTokenType::INTEGER || t == IfcTokenType::REAL || t == IfcTokenType::LABEL || t == IfcTokenType::ENUM) {
uint16_t length = _tokenStream->Read<uint16_t>();
Expand Down
109 changes: 98 additions & 11 deletions src/cpp/web-ifc/parsing/string_parsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,44 @@ namespace webifc::parsing {

bool foundRoman = false;

std::u16string utf16_from_utf8(const std::string& utf8) {
std::u16string utf16;
size_t i = 0;

while (i < utf8.size()) {
char16_t ch = 0;
unsigned char byte = utf8[i];

if (byte < 0x80) {
// 1-byte character (ASCII)
ch = byte;
i += 1;
}
else if ((byte & 0xE0) == 0xC0) {
// 2-byte character
if (i + 1 >= utf8.size()) throw std::runtime_error("Invalid UTF-8 sequence");
ch = ((byte & 0x1F) << 6) | (utf8[i + 1] & 0x3F);
i += 2;
}
else if ((byte & 0xF0) == 0xE0) {
// 3-byte character
if (i + 2 >= utf8.size()) throw std::runtime_error("Invalid UTF-8 sequence");
ch = ((byte & 0x0F) << 12) | ((utf8[i + 1] & 0x3F) << 6) | (utf8[i + 2] & 0x3F);
i += 3;
}
else {
throw std::runtime_error("Unsupported UTF-8 sequence");
}

utf16.push_back(ch);
}

return utf16;
}

void encodeCharacters(std::ostringstream &stream,std::string &data)
{
std::u16string utf16 = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(data.data());
std::u16string utf16 = utf16_from_utf8(data);
stream << "\\X2\\" << std::hex <<std::setfill('0') << std::uppercase;
for (char16_t uC : utf16) stream << std::setw(4) << static_cast<int>(uC);
stream << std::dec<< std::setw(0) << "\\X0\\";
Expand Down Expand Up @@ -53,6 +88,60 @@ namespace webifc::parsing {
if (inEncode) encodeCharacters(output,tmp);
}

std::string utf8_from_utf16(const std::u16string& u16str) {
std::string utf8;
for (char16_t ch : u16str) {
if (ch < 0x80) {
// 1-byte character
utf8.push_back(static_cast<char>(ch));
}
else if (ch < 0x800) {
// 2-byte character
utf8.push_back(static_cast<char>(0xC0 | (ch >> 6)));
utf8.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
}
else {
// 3-byte character
utf8.push_back(static_cast<char>(0xE0 | (ch >> 12)));
utf8.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
utf8.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
}
}
return utf8;
}

std::string utf8_from_utf32(const std::u32string& u32str) {
std::string utf8;
for (char32_t ch : u32str) {
if (ch < 0x80) {
// 1-byte character
utf8.push_back(static_cast<char>(ch));
}
else if (ch < 0x800) {
// 2-byte character
utf8.push_back(static_cast<char>(0xC0 | (ch >> 6)));
utf8.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
}
else if (ch < 0x10000) {
// 3-byte character
utf8.push_back(static_cast<char>(0xE0 | (ch >> 12)));
utf8.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
utf8.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
}
else if (ch <= 0x10FFFF) {
// 4-byte character
utf8.push_back(static_cast<char>(0xF0 | (ch >> 18)));
utf8.push_back(static_cast<char>(0x80 | ((ch >> 12) & 0x3F)));
utf8.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
utf8.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
}
else {
throw std::runtime_error("Invalid UTF-32 code point");
}
}
return utf8;
}

struct P21Decoder
{
public:
Expand Down Expand Up @@ -95,15 +184,14 @@ namespace webifc::parsing {
{
char d1 = getNextHex();
char d2 = getNextHex();
char str[2];
str[0] = (d1 << 4) | d2;
str[1] = 0;
auto cA = reinterpret_cast<char16_t*>(str);
char str2[2];
str2[0] = (d1 << 4) | d2;
str2[1] = 0;
auto cA = reinterpret_cast<char16_t*>(str2);
if (cA[0] >= 0x80 && cA[0] <= 0x9F) foundRoman = true;
if (foundRoman) cA[0]=checkRomanEncoding(cA[0]);
std::u16string u16str(cA, 1);
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::string utf8 = convert.to_bytes(u16str);
std::string utf8 = utf8_from_utf16(u16str);
std::copy(utf8.begin(), utf8.end(), std::back_inserter(result));
break;
}
Expand Down Expand Up @@ -237,8 +325,7 @@ namespace webifc::parsing {
bytes[i+1] = c;
}
std::u16string u16str(reinterpret_cast<char16_t*>(&bytes[0]), bytes.size() / 2);
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
utf8 = convert.to_bytes(u16str);
utf8 = utf8_from_utf16(u16str);
}
else if (T == 4)
{
Expand All @@ -251,8 +338,8 @@ namespace webifc::parsing {
bytes[i+2] = c;
}
std::u32string u32str(reinterpret_cast<char32_t*>(&bytes[0]), bytes.size() / 4);
std::wstring_convert<std::codecvt_utf8<char32_t>,char32_t> convert;
utf8 = convert.to_bytes(u32str);

utf8 = utf8_from_utf32(u32str);
}
std::copy(utf8.begin(), utf8.end(), std::back_inserter(result));
}
Expand Down