Merge pull request #4344 from revu-design/bensewell-fixifcopening4343

IFC Reading: Fix opening reading.
pull/4338/head^2
Kim Kulling 2022-01-23 12:15:38 +01:00 committed by GitHub
commit d45af75a5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 382 additions and 75 deletions

View File

@ -699,7 +699,7 @@ void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedArea
continue;
}
GenerateOpenings(openings, std::vector<IfcVector3>(1, IfcVector3(1, 0, 0)), temp, false, true);
GenerateOpenings(openings, temp, false, true);
result.Append(temp);
vit += pcount;

View File

@ -190,7 +190,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
std::copy(outer_vit, outer_vit+outer_polygon_size,
std::back_inserter(temp.mVerts));
GenerateOpenings(fake_openings, normals, temp, false, false);
GenerateOpenings(fake_openings, temp, false, false);
result.Append(temp);
}
@ -529,6 +529,31 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect
return m;
}
const auto closeDistance = 1e-6;
bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt2) {
if(pt1.Coordinates.size() != pt2.Coordinates.size())
{
IFCImporter::LogWarn("unable to compare differently-dimensioned points");
return false;
}
auto coord1 = pt1.Coordinates.begin();
auto coord2 = pt2.Coordinates.begin();
// we're just testing each dimension separately rather than doing euclidean distance, as we're
// looking for very close coordinates
for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++)
{
if(std::fabs(*coord1 - *coord2) > closeDistance)
return false;
}
return true;
}
bool areClose(IfcVector3 pt1,IfcVector3 pt2) {
return (std::fabs(pt1.x - pt2.x) < closeDistance &&
std::fabs(pt1.y - pt2.y) < closeDistance &&
std::fabs(pt1.z - pt2.z) < closeDistance);
}
// Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary.
void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve,
const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings)
@ -592,7 +617,21 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
nors.push_back(IfcVector3());
continue;
}
nors.push_back(((bounds.mVerts[2] - bounds.mVerts[0]) ^ (bounds.mVerts[1] - bounds.mVerts[0])).Normalize());
auto nor = ((bounds.mVerts[2] - bounds.mVerts[0]) ^ (bounds.mVerts[1] - bounds.mVerts[0])).Normalize();
auto vI0 = bounds.mVertcnt[0];
for(size_t faceI = 0; faceI < bounds.mVertcnt.size(); faceI++)
{
if(bounds.mVertcnt[faceI] >= 3) {
// do a check that this is at least parallel to the base plane
auto nor2 = ((bounds.mVerts[vI0 + 2] - bounds.mVerts[vI0]) ^ (bounds.mVerts[vI0 + 1] - bounds.mVerts[vI0])).Normalize();
if(!areClose(nor,nor2)) {
std::stringstream msg;
msg << "Face " << faceI << " is not parallel with face 0 - opening on entity " << solid.GetID();
IFCImporter::LogWarn(msg.str().c_str());
}
}
}
nors.push_back(nor);
}
}
@ -613,7 +652,7 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
out.push_back(in[i] + dir);
if( openings ) {
if( (in[i] - in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) {
if( (in[i] - in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings, temp, true, true, dir) ) {
++sides_with_openings;
}
@ -622,31 +661,33 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
}
}
if( openings ) {
if(openings) {
for(TempOpening& opening : *conv.apply_openings) {
if( !opening.wallPoints.empty() ) {
IFCImporter::LogError("failed to generate all window caps");
if(!opening.wallPoints.empty()) {
std::stringstream msg;
msg << "failed to generate all window caps on ID " << (int)solid.GetID();
IFCImporter::LogError(msg.str().c_str());
}
opening.wallPoints.clear();
}
}
size_t sides_with_v_openings = 0;
if( has_area ) {
if(has_area) {
for( size_t n = 0; n < 2; ++n ) {
if( n > 0 ) {
for( size_t i = 0; i < in.size(); ++i )
for(size_t n = 0; n < 2; ++n) {
if(n > 0) {
for(size_t i = 0; i < in.size(); ++i)
out.push_back(in[i] + dir);
}
else {
for( size_t i = in.size(); i--; )
for(size_t i = in.size(); i--; )
out.push_back(in[i]);
}
curmesh.mVertcnt.push_back(static_cast<unsigned int>(in.size()));
if( openings && in.size() > 2 ) {
if( GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) {
if(openings && in.size() > 2) {
if(GenerateOpenings(*conv.apply_openings,temp,true,true,dir)) {
++sides_with_v_openings;
}
@ -656,8 +697,10 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
}
}
if( openings && (sides_with_openings == 1 || sides_with_v_openings == 2 ) ) {
IFCImporter::LogWarn("failed to resolve all openings, presumably their topology is not supported by Assimp");
if (openings && (sides_with_openings == 1 || sides_with_v_openings == 2)) {
std::stringstream msg;
msg << "failed to resolve all openings, presumably their topology is not supported by Assimp - ID " << solid.GetID() << " sides_with_openings " << sides_with_openings << " sides_with_v_openings " << sides_with_v_openings;
IFCImporter::LogWarn(msg.str().c_str());
}
IFCImporter::LogVerboseDebug("generate mesh procedurally by extrusion (IfcExtrudedAreaSolid)");
@ -781,7 +824,9 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned
return false;
}
else {
IFCImporter::LogWarn("skipping unknown IfcGeometricRepresentationItem entity, type is ", geo.GetClassName());
std::stringstream toLog;
toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID();
IFCImporter::LogWarn(toLog.str().c_str());
return false;
}

View File

@ -58,6 +58,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endif
#include <iterator>
#include <forward_list>
#include <deque>
namespace Assimp {
namespace IFC {
@ -73,7 +75,7 @@ namespace Assimp {
// fallback method to generate wall openings
bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors,
bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
TempMesh& curmesh);
@ -1140,7 +1142,6 @@ IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour, const TempMesh
// ------------------------------------------------------------------------------------------------
bool GenerateOpenings(std::vector<TempOpening>& openings,
const std::vector<IfcVector3>& nors,
TempMesh& curmesh,
bool check_intersection,
bool generate_connection_geometry,
@ -1340,7 +1341,7 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
MergeWindowContours(temp_contour, other, poly);
if (poly.size() > 1) {
return TryAddOpenings_Poly2Tri(openings, nors, curmesh);
return TryAddOpenings_Poly2Tri(openings, curmesh);
}
else if (poly.size() == 0) {
IFCImporter::LogWarn("ignoring duplicate opening");
@ -1427,8 +1428,289 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
return true;
}
std::vector<IfcVector2> GetContourInPlane2D(std::shared_ptr<TempMesh> mesh,IfcMatrix3 planeSpace,
IfcVector3 planeNor,IfcFloat planeOffset,
IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first,bool& ok) {
std::vector<IfcVector2> contour;
const auto outernor = ((mesh->mVerts[2] - mesh->mVerts[0]) ^ (mesh->mVerts[1] - mesh->mVerts[0])).Normalize();
const IfcFloat dot = planeNor * outernor;
if(std::fabs(dot) < 1.f - 1e-6f) {
std::stringstream msg;
msg << "Skipping: Unaligned opening (" << planeNor.x << ", " << planeNor.y << ", " << planeNor.z << ")";
msg << " . ( " << outernor.x << ", " << outernor.y << ", " << outernor.z << ") = " << dot;
IFCImporter::LogDebug(msg.str().c_str());
ok = false;
return contour;
}
const std::vector<IfcVector3>& va = mesh->mVerts;
if(va.size() <= 2) {
std::stringstream msg;
msg << "Skipping: Only " << va.size() << " verticies in opening mesh.";
IFCImporter::LogDebug(msg.str().c_str());
ok = false;
return contour;
}
for(const IfcVector3& xx : mesh->mVerts) {
IfcVector3 vv = planeSpace * xx,vv_extr = planeSpace * (xx + extrusionDir);
const bool is_extruded_side = std::fabs(vv.z - planeOffset) > std::fabs(vv_extr.z - planeOffset);
if(first) {
first = false;
if(dot > 0.f) {
wall_extrusion = extrusionDir;
if(is_extruded_side) {
wall_extrusion = -wall_extrusion;
}
}
}
// XXX should not be necessary - but it is. Why? For precision reasons?
vv = is_extruded_side ? vv_extr : vv;
contour.push_back(IfcVector2(vv.x,vv.y));
}
ok = true;
return contour;
}
const float close { 1e-6f };
static bool isClose(IfcVector2 first,IfcVector2 second) {
auto diff = (second - first);
return (std::fabs(diff.x) < close && std::fabs(diff.y) < close);
}
static void logSegment(std::pair<IfcVector2,IfcVector2> segment) {
std::stringstream msg2;
msg2 << " Segment: \n";
msg2 << " " << segment.first.x << " " << segment.first.y << " \n";
msg2 << " " << segment.second.x << " " << segment.second.y << " \n";
IFCImporter::LogInfo(msg2.str().c_str());
}
std::vector<std::vector<IfcVector2>> GetContoursInPlane3D(std::shared_ptr<TempMesh> mesh,IfcMatrix3 planeSpace,
IfcFloat planeOffset) {
{
std::stringstream msg;
msg << "GetContoursInPlane3D: planeSpace is \n";
msg << planeSpace.a1 << " " << planeSpace.a2 << " " << planeSpace.a3 << " " << "\n";
msg << planeSpace.b1 << " " << planeSpace.b2 << " " << planeSpace.b3 << " " << "\n";
msg << planeSpace.c1 << " " << planeSpace.c2 << " " << planeSpace.c3 << " " << "\n";
msg << "\n planeOffset is " << planeOffset;
IFCImporter::LogInfo(msg.str().c_str());
}
// we'll put our line segments in here, and then merge them together into contours later
std::deque<std::pair<IfcVector2,IfcVector2>> lineSegments;
// find the lines giving the intersection of the faces with the plane - we'll work in planeSpace throughout.
size_t vI0{ 0 }; // vertex index for first vertex in plane
for(auto nVertices : mesh->mVertcnt) { // iterate over faces
{
std::stringstream msg;
msg << "GetContoursInPlane3D: face (transformed) is \n";
for(auto vI = vI0; vI < vI0 + nVertices; vI++) {
auto v = planeSpace * mesh->mVerts[vI];
msg << " " << v.x << " " << v.y << " " << v.z << " " << "\n";
}
IFCImporter::LogInfo(msg.str().c_str());
}
if(nVertices <= 2) // not a plane, a point or line
{
std::stringstream msg;
msg << "GetContoursInPlane3D: found point or line when expecting plane (only " << nVertices << " vertices)";
IFCImporter::LogWarn(msg.str().c_str());
vI0 += nVertices;
continue;
}
auto v0 = planeSpace * mesh->mVerts[vI0];
// now calculate intersections between face and plane
IfcVector2 firstPoint;
bool gotFirstPoint(false);
if(std::fabs(v0.z - planeOffset) < close) {
// first point is on the plane
firstPoint.x = v0.x;
firstPoint.y = v0.y;
gotFirstPoint = true;
}
auto vn = v0;
for(auto vI = vI0 + 1; vI < vI0 + nVertices; vI++) {
auto vp = vn;
vn = planeSpace * mesh->mVerts[vI];
IfcVector3 intersection;
if(std::fabs(vn.z - planeOffset) < close) {
// on the plane
intersection = vn;
}
else if((vn.z > planeOffset) != (vp.z > planeOffset))
{
// passes through the plane
auto vdir = vn - vp;
auto scale = (planeOffset - vp.z) / vdir.z;
intersection = vp + scale * vdir;
}
else {
// nowhere near - move on
continue;
}
if(!gotFirstPoint) {
if(std::fabs(vp.z - planeOffset) < close) {
// just had a second line along the plane
firstPoint.x = vp.x;
firstPoint.y = vp.y;
IfcVector2 secondPoint(intersection.x,intersection.y);
auto s = std::pair<IfcVector2,IfcVector2>(firstPoint,secondPoint);
logSegment(s);
lineSegments.push_back(s);
// next firstpoint should be this one
}
else {
// store the first intersection point
firstPoint.x = intersection.x;
firstPoint.y = intersection.y;
gotFirstPoint = true;
}
}
else {
// now got the second point, so store the pair
IfcVector2 secondPoint(intersection.x,intersection.y);
auto s = std::pair<IfcVector2,IfcVector2>(firstPoint,secondPoint);
logSegment(s);
lineSegments.push_back(s);
// - note that we don't move onto the next face as a non-convex face can create two or more intersections with a plane
gotFirstPoint = false;
}
}
if(gotFirstPoint) {
IFCImporter::LogWarn("GetContoursInPlane3D: odd number of intersections with plane");
}
vI0 += nVertices;
}
{
std::stringstream msg;
msg << "GetContoursInPlane3D: found " << lineSegments.size() << " line segments:\n";
IFCImporter::LogInfo(msg.str().c_str());
for(auto& s : lineSegments) {
logSegment(s);
}
}
// now merge contours until we have the best-looking polygons we can
std::vector<Contour> contours;
while(!lineSegments.empty()) {
// start with a polygon and make the best closed contour we can
const auto& firstSeg = lineSegments.front();
std::deque<IfcVector2> contour{ firstSeg.first, firstSeg.second };
lineSegments.pop_front();
bool foundNextPoint{ true };
bool closedContour{ false };
while(foundNextPoint) {
foundNextPoint = false;
for(auto nextSeg = lineSegments.begin(); nextSeg != lineSegments.end(); nextSeg++) {
// see if we can match up both ends - in which case we've closed the contour
if((isClose(contour.front(),nextSeg->first) && isClose(contour.back(),nextSeg->second)) ||
(isClose(contour.back(),nextSeg->first) && isClose(contour.front(),nextSeg->second))
) {
lineSegments.erase(nextSeg);
closedContour = true;
break;
}
// otherwise, see if we can match up either end
foundNextPoint = true;
if(isClose(contour.front(),nextSeg->first)) {
contour.push_front(nextSeg->second);
}
else if(isClose(contour.front(),nextSeg->second)) {
contour.push_front(nextSeg->first);
}
else if(isClose(contour.back(),nextSeg->first)) {
contour.push_back(nextSeg->second);
}
else if(isClose(contour.back(),nextSeg->second)) {
contour.push_back(nextSeg->first);
}
else {
foundNextPoint = false;
}
if(foundNextPoint) {
lineSegments.erase(nextSeg);
break;
}
}
}
if(!closedContour) {
IFCImporter::LogWarn("GetContoursInPlane3D: did not close contour");
}
// now add the contour if we can
if(contour.size() <= 2) {
IFCImporter::LogWarn("GetContoursInPlane3D: discarding line/point contour");
continue;
}
Contour c{};
for(auto p : contour)
{
c.push_back(p);
}
contours.push_back(c);
}
{
std::stringstream msg;
msg << "GetContoursInPlane3D: found " << contours.size() << " contours:\n";
for(auto c : contours) {
msg << " Contour: \n";
for(auto p : c) {
msg << " " << p.x << " " << p.y << " \n";
}
}
IFCImporter::LogInfo(msg.str().c_str());
}
return contours;
}
std::vector<std::vector<IfcVector2>> GetContoursInPlane(std::shared_ptr<TempMesh> mesh,IfcMatrix3 planeSpace,
IfcVector3 planeNor,IfcFloat planeOffset,
IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first) {
if(mesh->mVertcnt.size() == 1)
{
bool ok;
auto contour = GetContourInPlane2D(mesh,planeSpace,planeNor,planeOffset,extrusionDir,wall_extrusion,first,ok);
if(ok)
return std::vector<std::vector<IfcVector2>> {contour};
else
return std::vector<std::vector<IfcVector2>> {};
}
else
{
return GetContoursInPlane3D(mesh,planeSpace,planeOffset);
}
}
// ------------------------------------------------------------------------------------------------
bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors,
bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
TempMesh& curmesh)
{
IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings");
@ -1498,61 +1780,41 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std:
try {
ClipperLib::Clipper clipper_holes;
size_t c = 0;
for(const TempOpening& t :openings) {
const IfcVector3& outernor = nors[c++];
const IfcFloat dot = nor * outernor;
if (std::fabs(dot)<1.f-1e-6f) {
continue;
}
for(const TempOpening& t : openings) {
auto contours = GetContoursInPlane(t.profileMesh,m,nor,coord,t.extrusionDir,wall_extrusion,first);
const std::vector<IfcVector3>& va = t.profileMesh->mVerts;
if(va.size() <= 2) {
continue;
}
for(auto& contour : contours) {
// scale to clipping space
ClipperLib::Polygon hole;
for(IfcVector2& pip : contour) {
pip.x = (pip.x - vmin.x) / vmax.x;
pip.y = (pip.y - vmin.y) / vmax.y;
std::vector<IfcVector2> contour;
for(const IfcVector3& xx : t.profileMesh->mVerts) {
IfcVector3 vv = m * xx, vv_extr = m * (xx + t.extrusionDir);
const bool is_extruded_side = std::fabs(vv.z - coord) > std::fabs(vv_extr.z - coord);
if (first) {
first = false;
if (dot > 0.f) {
wall_extrusion = t.extrusionDir;
if (is_extruded_side) {
wall_extrusion = - wall_extrusion;
}
}
hole.push_back(ClipperLib::IntPoint(to_int64(pip.x),to_int64(pip.y)));
}
// XXX should not be necessary - but it is. Why? For precision reasons?
vv = is_extruded_side ? vv_extr : vv;
contour.push_back(IfcVector2(vv.x,vv.y));
if(!ClipperLib::Orientation(hole)) {
std::reverse(hole.begin(),hole.end());
// assert(ClipperLib::Orientation(hole));
}
/*ClipperLib::Polygons pol_temp(1), pol_temp2(1);
pol_temp[0] = hole;
ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0);
hole = pol_temp2[0];*/
clipper_holes.AddPolygon(hole,ClipperLib::ptSubject);
{
std::stringstream msg;
msg << "- added polygon ";
for(auto elem : hole) {
msg << " (" << elem.X << ", " << elem.Y << ")";
}
IFCImporter::LogDebug(msg.str().c_str());
}
}
ClipperLib::Polygon hole;
for(IfcVector2& pip : contour) {
pip.x = (pip.x - vmin.x) / vmax.x;
pip.y = (pip.y - vmin.y) / vmax.y;
hole.push_back(ClipperLib::IntPoint( to_int64(pip.x), to_int64(pip.y) ));
}
if (!ClipperLib::Orientation(hole)) {
std::reverse(hole.begin(), hole.end());
// assert(ClipperLib::Orientation(hole));
}
/*ClipperLib::Polygons pol_temp(1), pol_temp2(1);
pol_temp[0] = hole;
ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0);
hole = pol_temp2[0];*/
clipper_holes.AddPolygon(hole,ClipperLib::ptSubject);
}
clipper_holes.Execute(ClipperLib::ctUnion,holes_union,

View File

@ -307,7 +307,6 @@ void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedArea
// IFCOpenings.cpp
bool GenerateOpenings(std::vector<TempOpening>& openings,
const std::vector<IfcVector3>& nors,
TempMesh& curmesh,
bool check_intersection,
bool generate_connection_geometry,

View File

@ -388,15 +388,16 @@ void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev)
ai_assert(nullptr != message);
// Check whether this is a repeated message
if (!::strncmp(message, lastMsg, lastLen - 1)) {
auto thisLen = ::strlen(message);
if (thisLen == lastLen - 1 && !::strncmp(message, lastMsg, lastLen - 1)) {
if (!noRepeatMsg) {
noRepeatMsg = true;
message = "Skipping one or more lines with the same contents\n";
} else
return;
}
return;
} else {
// append a new-line character to the message to be printed
lastLen = ::strlen(message);
lastLen = thisLen;
::memcpy(lastMsg, message, lastLen + 1);
::strcat(lastMsg + lastLen, "\n");