Externalize Assimp

This commit is contained in:
Dane Johnson
2021-02-05 11:22:37 -06:00
parent d84681c23a
commit 35d18c0f16
1032 changed files with 42 additions and 441932 deletions

View File

@@ -1,829 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2010, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file IFCBoolean.cpp
* @brief Implements a subset of Ifc boolean operations
*/
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
#include "code/Importer/IFC/IFCUtil.h"
#include "code/Common/PolyTools.h"
#include "code/PostProcessing/ProcessHelper.h"
#include <assimp/Defines.h>
#include <iterator>
#include <tuple>
namespace Assimp {
namespace IFC {
// ------------------------------------------------------------------------------------------------
// Calculates intersection between line segment and plane. To catch corner cases, specify which side you prefer.
// The function then generates a hit only if the end is beyond a certain margin in that direction, filtering out
// "very close to plane" ghost hits as long as start and end stay directly on or within the given plane side.
bool IntersectSegmentPlane(const IfcVector3& p,const IfcVector3& n, const IfcVector3& e0,
const IfcVector3& e1, bool assumeStartOnWhiteSide, IfcVector3& out)
{
const IfcVector3 pdelta = e0 - p, seg = e1 - e0;
const IfcFloat dotOne = n*seg, dotTwo = -(n*pdelta);
// if segment ends on plane, do not report a hit. We stay on that side until a following segment starting at this
// point leaves the plane through the other side
if( std::abs(dotOne + dotTwo) < 1e-6 )
return false;
// if segment starts on the plane, report a hit only if the end lies on the *other* side
if( std::abs(dotTwo) < 1e-6 )
{
if( (assumeStartOnWhiteSide && dotOne + dotTwo < 1e-6) || (!assumeStartOnWhiteSide && dotOne + dotTwo > -1e-6) )
{
out = e0;
return true;
}
else
{
return false;
}
}
// ignore if segment is parallel to plane and far away from it on either side
// Warning: if there's a few thousand of such segments which slowly accumulate beyond the epsilon, no hit would be registered
if( std::abs(dotOne) < 1e-6 )
return false;
// t must be in [0..1] if the intersection point is within the given segment
const IfcFloat t = dotTwo / dotOne;
if( t > 1.0 || t < 0.0 )
return false;
out = e0 + t*seg;
return true;
}
// ------------------------------------------------------------------------------------------------
void FilterPolygon(std::vector<IfcVector3>& resultpoly)
{
if( resultpoly.size() < 3 )
{
resultpoly.clear();
return;
}
IfcVector3 vmin, vmax;
ArrayBounds(resultpoly.data(), static_cast<unsigned int>(resultpoly.size()), vmin, vmax);
// filter our IfcFloat points - those may happen if a point lies
// directly on the intersection line or directly on the clipping plane
const IfcFloat epsilon = (vmax - vmin).SquareLength() / 1e6f;
FuzzyVectorCompare fz(epsilon);
std::vector<IfcVector3>::iterator e = std::unique(resultpoly.begin(), resultpoly.end(), fz);
if( e != resultpoly.end() )
resultpoly.erase(e, resultpoly.end());
if( !resultpoly.empty() && fz(resultpoly.front(), resultpoly.back()) )
resultpoly.pop_back();
}
// ------------------------------------------------------------------------------------------------
void WritePolygon(std::vector<IfcVector3>& resultpoly, TempMesh& result)
{
FilterPolygon(resultpoly);
if( resultpoly.size() > 2 )
{
result.mVerts.insert(result.mVerts.end(), resultpoly.begin(), resultpoly.end());
result.mVertcnt.push_back(static_cast<unsigned int>(resultpoly.size()));
}
}
// ------------------------------------------------------------------------------------------------
void ProcessBooleanHalfSpaceDifference(const Schema_2x3::IfcHalfSpaceSolid* hs, TempMesh& result,
const TempMesh& first_operand,
ConversionData& /*conv*/)
{
ai_assert(hs != NULL);
const Schema_2x3::IfcPlane* const plane = hs->BaseSurface->ToPtr<Schema_2x3::IfcPlane>();
if(!plane) {
IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid");
return;
}
// extract plane base position vector and normal vector
IfcVector3 p,n(0.f,0.f,1.f);
if (plane->Position->Axis) {
ConvertDirection(n,plane->Position->Axis.Get());
}
ConvertCartesianPoint(p,plane->Position->Location);
if(!IsTrue(hs->AgreementFlag)) {
n *= -1.f;
}
// clip the current contents of `meshout` against the plane we obtained from the second operand
const std::vector<IfcVector3>& in = first_operand.mVerts;
std::vector<IfcVector3>& outvert = result.mVerts;
std::vector<unsigned int>::const_iterator begin = first_operand.mVertcnt.begin(),
end = first_operand.mVertcnt.end(), iit;
outvert.reserve(in.size());
result.mVertcnt.reserve(first_operand.mVertcnt.size());
unsigned int vidx = 0;
for(iit = begin; iit != end; vidx += *iit++) {
unsigned int newcount = 0;
bool isAtWhiteSide = (in[vidx] - p) * n > -1e-6;
for( unsigned int i = 0; i < *iit; ++i ) {
const IfcVector3& e0 = in[vidx + i], e1 = in[vidx + (i + 1) % *iit];
// does the next segment intersect the plane?
IfcVector3 isectpos;
if( IntersectSegmentPlane(p, n, e0, e1, isAtWhiteSide, isectpos) ) {
if( isAtWhiteSide ) {
// e0 is on the right side, so keep it
outvert.push_back(e0);
outvert.push_back(isectpos);
newcount += 2;
}
else {
// e0 is on the wrong side, so drop it and keep e1 instead
outvert.push_back(isectpos);
++newcount;
}
isAtWhiteSide = !isAtWhiteSide;
}
else
{
if( isAtWhiteSide ) {
outvert.push_back(e0);
++newcount;
}
}
}
if (!newcount) {
continue;
}
IfcVector3 vmin,vmax;
ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax);
// filter our IfcFloat points - those may happen if a point lies
// directly on the intersection line. However, due to IfcFloat
// precision a bitwise comparison is not feasible to detect
// this case.
const IfcFloat epsilon = (vmax-vmin).SquareLength() / 1e6f;
FuzzyVectorCompare fz(epsilon);
std::vector<IfcVector3>::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz );
if (e != outvert.end()) {
newcount -= static_cast<unsigned int>(std::distance(e,outvert.end()));
outvert.erase(e,outvert.end());
}
if (fz(*( outvert.end()-newcount),outvert.back())) {
outvert.pop_back();
--newcount;
}
if(newcount > 2) {
result.mVertcnt.push_back(newcount);
}
else while(newcount-->0) {
result.mVerts.pop_back();
}
}
IFCImporter::LogDebug("generating CSG geometry by plane clipping (IfcBooleanClippingResult)");
}
// ------------------------------------------------------------------------------------------------
// Check if e0-e1 intersects a sub-segment of the given boundary line.
// note: this functions works on 3D vectors, but performs its intersection checks solely in xy.
// New version takes the supposed inside/outside state as a parameter and treats corner cases as if
// the line stays on that side. This should make corner cases more stable.
// Two million assumptions! Boundary should have all z at 0.0, will be treated as closed, should not have
// segments with length <1e-6, self-intersecting might break the corner case handling... just don't go there, ok?
bool IntersectsBoundaryProfile(const IfcVector3& e0, const IfcVector3& e1, const std::vector<IfcVector3>& boundary,
const bool isStartAssumedInside, std::vector<std::pair<size_t, IfcVector3> >& intersect_results,
const bool halfOpen = false)
{
ai_assert(intersect_results.empty());
// determine winding order - necessary to detect segments going "inwards" or "outwards" from a point directly on the border
// positive sum of angles means clockwise order when looking down the -Z axis
IfcFloat windingOrder = 0.0;
for( size_t i = 0, bcount = boundary.size(); i < bcount; ++i ) {
IfcVector3 b01 = boundary[(i + 1) % bcount] - boundary[i];
IfcVector3 b12 = boundary[(i + 2) % bcount] - boundary[(i + 1) % bcount];
IfcVector3 b1_side = IfcVector3(b01.y, -b01.x, 0.0); // rotated 90° clockwise in Z plane
// Warning: rough estimate only. A concave poly with lots of small segments each featuring a small counter rotation
// could fool the accumulation. Correct implementation would be sum( acos( b01 * b2) * sign( b12 * b1_side))
windingOrder += (b1_side.x*b12.x + b1_side.y*b12.y);
}
windingOrder = windingOrder > 0.0 ? 1.0 : -1.0;
const IfcVector3 e = e1 - e0;
for( size_t i = 0, bcount = boundary.size(); i < bcount; ++i ) {
// boundary segment i: b0-b1
const IfcVector3& b0 = boundary[i];
const IfcVector3& b1 = boundary[(i + 1) % bcount];
IfcVector3 b = b1 - b0;
// segment-segment intersection
// solve b0 + b*s = e0 + e*t for (s,t)
const IfcFloat det = (-b.x * e.y + e.x * b.y);
if( std::abs(det) < 1e-6 ) {
// no solutions (parallel lines)
continue;
}
IfcFloat b_sqlen_inv = 1.0 / b.SquareLength();
const IfcFloat x = b0.x - e0.x;
const IfcFloat y = b0.y - e0.y;
const IfcFloat s = (x*e.y - e.x*y) / det; // scale along boundary edge
const IfcFloat t = (x*b.y - b.x*y) / det; // scale along given segment
const IfcVector3 p = e0 + e*t;
#ifdef ASSIMP_BUILD_DEBUG
const IfcVector3 check = b0 + b*s - p;
ai_assert((IfcVector2(check.x, check.y)).SquareLength() < 1e-5);
#endif
// also calculate the distance of e0 and e1 to the segment. We need to detect the "starts directly on segment"
// and "ends directly at segment" cases
bool startsAtSegment, endsAtSegment;
{
// calculate closest point to each end on the segment, clamp that point to the segment's length, then check
// distance to that point. This approach is like testing if e0 is inside a capped cylinder.
IfcFloat et0 = (b.x*(e0.x - b0.x) + b.y*(e0.y - b0.y)) * b_sqlen_inv;
IfcVector3 closestPosToE0OnBoundary = b0 + std::max(IfcFloat(0.0), std::min(IfcFloat(1.0), et0)) * b;
startsAtSegment = (closestPosToE0OnBoundary - IfcVector3(e0.x, e0.y, 0.0)).SquareLength() < 1e-12;
IfcFloat et1 = (b.x*(e1.x - b0.x) + b.y*(e1.y - b0.y)) * b_sqlen_inv;
IfcVector3 closestPosToE1OnBoundary = b0 + std::max(IfcFloat(0.0), std::min(IfcFloat(1.0), et1)) * b;
endsAtSegment = (closestPosToE1OnBoundary - IfcVector3(e1.x, e1.y, 0.0)).SquareLength() < 1e-12;
}
// Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments
if( endsAtSegment && !halfOpen )
continue;
// Line segment starts at boundary -> generate a hit only if following that line would change the INSIDE/OUTSIDE
// state. This should catch the case where a connected set of segments has a point directly on the boundary,
// one segment not hitting it because it ends there and the next segment not hitting it because it starts there
// Should NOT generate a hit if the segment only touches the boundary but turns around and stays inside.
if( startsAtSegment )
{
IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder;
bool isGoingInside = (inside_dir * e) > 0.0;
if( isGoingInside == isStartAssumedInside )
continue;
// only insert the point into the list if it is sufficiently far away from the previous intersection point.
// This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
if( !intersect_results.empty() && intersect_results.back().first == i - 1 )
{
const IfcVector3 diff = intersect_results.back().second - e0;
if( IfcVector2(diff.x, diff.y).SquareLength() < 1e-10 )
continue;
}
intersect_results.push_back(std::make_pair(i, e0));
continue;
}
// for a valid intersection, s and t should be in range [0,1]. Including a bit of epsilon on s, potential double
// hits on two consecutive boundary segments are filtered
if( s >= -1e-6 * b_sqlen_inv && s <= 1.0 + 1e-6*b_sqlen_inv && t >= 0.0 && (t <= 1.0 || halfOpen) )
{
// only insert the point into the list if it is sufficiently far away from the previous intersection point.
// This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
if( !intersect_results.empty() && intersect_results.back().first == i - 1 )
{
const IfcVector3 diff = intersect_results.back().second - p;
if( IfcVector2(diff.x, diff.y).SquareLength() < 1e-10 )
continue;
}
intersect_results.push_back(std::make_pair(i, p));
}
}
return !intersect_results.empty();
}
// ------------------------------------------------------------------------------------------------
// note: this functions works on 3D vectors, but performs its intersection checks solely in xy.
bool PointInPoly(const IfcVector3& p, const std::vector<IfcVector3>& boundary)
{
// even-odd algorithm: take a random vector that extends from p to infinite
// and counts how many times it intersects edges of the boundary.
// because checking for segment intersections is prone to numeric inaccuracies
// or double detections (i.e. when hitting multiple adjacent segments at their
// shared vertices) we do it thrice with different rays and vote on it.
// the even-odd algorithm doesn't work for points which lie directly on
// the border of the polygon. If any of our attempts produces this result,
// we return false immediately.
std::vector<std::pair<size_t, IfcVector3> > intersected_boundary;
size_t votes = 0;
IntersectsBoundaryProfile(p, p + IfcVector3(1.0, 0, 0), boundary, true, intersected_boundary, true);
votes += intersected_boundary.size() % 2;
intersected_boundary.clear();
IntersectsBoundaryProfile(p, p + IfcVector3(0, 1.0, 0), boundary, true, intersected_boundary, true);
votes += intersected_boundary.size() % 2;
intersected_boundary.clear();
IntersectsBoundaryProfile(p, p + IfcVector3(0.6, -0.6, 0.0), boundary, true, intersected_boundary, true);
votes += intersected_boundary.size() % 2;
return votes > 1;
}
// ------------------------------------------------------------------------------------------------
void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPolygonalBoundedHalfSpace* hs, TempMesh& result,
const TempMesh& first_operand,
ConversionData& conv)
{
ai_assert(hs != NULL);
const Schema_2x3::IfcPlane* const plane = hs->BaseSurface->ToPtr<Schema_2x3::IfcPlane>();
if(!plane) {
IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid");
return;
}
// extract plane base position vector and normal vector
IfcVector3 p,n(0.f,0.f,1.f);
if (plane->Position->Axis) {
ConvertDirection(n,plane->Position->Axis.Get());
}
ConvertCartesianPoint(p,plane->Position->Location);
if(!IsTrue(hs->AgreementFlag)) {
n *= -1.f;
}
n.Normalize();
// obtain the polygonal bounding volume
std::shared_ptr<TempMesh> profile = std::shared_ptr<TempMesh>(new TempMesh());
if(!ProcessCurve(hs->PolygonalBoundary, *profile.get(), conv)) {
IFCImporter::LogError("expected valid polyline for boundary of boolean halfspace");
return;
}
// determine winding order by calculating the normal.
IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(profile->mVerts.data(), profile->mVerts.size());
IfcMatrix4 proj_inv;
ConvertAxisPlacement(proj_inv,hs->Position);
// and map everything into a plane coordinate space so all intersection
// tests can be done in 2D space.
IfcMatrix4 proj = proj_inv;
proj.Inverse();
// clip the current contents of `meshout` against the plane we obtained from the second operand
const std::vector<IfcVector3>& in = first_operand.mVerts;
std::vector<IfcVector3>& outvert = result.mVerts;
std::vector<unsigned int>& outvertcnt = result.mVertcnt;
outvert.reserve(in.size());
outvertcnt.reserve(first_operand.mVertcnt.size());
unsigned int vidx = 0;
std::vector<unsigned int>::const_iterator begin = first_operand.mVertcnt.begin();
std::vector<unsigned int>::const_iterator end = first_operand.mVertcnt.end();
std::vector<unsigned int>::const_iterator iit;
for( iit = begin; iit != end; vidx += *iit++ )
{
// Our new approach: we cut the poly along the plane, then we intersect the part on the black side of the plane
// against the bounding polygon. All the white parts, and the black part outside the boundary polygon, are kept.
std::vector<IfcVector3> whiteside, blackside;
{
const IfcVector3* srcVertices = &in[vidx];
const size_t srcVtxCount = *iit;
if( srcVtxCount == 0 )
continue;
IfcVector3 polyNormal = TempMesh::ComputePolygonNormal(srcVertices, srcVtxCount, true);
// if the poly is parallel to the plane, put it completely on the black or white side
if( std::abs(polyNormal * n) > 0.9999 )
{
bool isOnWhiteSide = (srcVertices[0] - p) * n > -1e-6;
std::vector<IfcVector3>& targetSide = isOnWhiteSide ? whiteside : blackside;
targetSide.insert(targetSide.end(), srcVertices, srcVertices + srcVtxCount);
}
else
{
// otherwise start building one polygon for each side. Whenever the current line segment intersects the plane
// we put a point there as an end of the current segment. Then we switch to the other side, put a point there, too,
// as a beginning of the current segment, and simply continue accumulating vertices.
bool isCurrentlyOnWhiteSide = ((srcVertices[0]) - p) * n > -1e-6;
for( size_t a = 0; a < srcVtxCount; ++a )
{
IfcVector3 e0 = srcVertices[a];
IfcVector3 e1 = srcVertices[(a + 1) % srcVtxCount];
IfcVector3 ei;
// put starting point to the current mesh
std::vector<IfcVector3>& trgt = isCurrentlyOnWhiteSide ? whiteside : blackside;
trgt.push_back(srcVertices[a]);
// if there's an intersection, put an end vertex there, switch to the other side's mesh,
// and add a starting vertex there, too
bool isPlaneHit = IntersectSegmentPlane(p, n, e0, e1, isCurrentlyOnWhiteSide, ei);
if( isPlaneHit )
{
if( trgt.empty() || (trgt.back() - ei).SquareLength() > 1e-12 )
trgt.push_back(ei);
isCurrentlyOnWhiteSide = !isCurrentlyOnWhiteSide;
std::vector<IfcVector3>& newtrgt = isCurrentlyOnWhiteSide ? whiteside : blackside;
newtrgt.push_back(ei);
}
}
}
}
// the part on the white side can be written into the target mesh right away
WritePolygon(whiteside, result);
// The black part is the piece we need to get rid of, but only the part of it within the boundary polygon.
// So we now need to construct all the polygons that result from BlackSidePoly minus BoundaryPoly.
FilterPolygon(blackside);
// Complicated, II. We run along the polygon. a) When we're inside the boundary, we run on until we hit an
// intersection, which means we're leaving it. We then start a new out poly there. b) When we're outside the
// boundary, we start collecting vertices until we hit an intersection, then we run along the boundary until we hit
// an intersection, then we switch back to the poly and run on on this one again, and so on until we got a closed
// loop. Then we continue with the path we left to catch potential additional polys on the other side of the
// boundary as described in a)
if( !blackside.empty() )
{
// poly edge index, intersection point, edge index in boundary poly
std::vector<std::tuple<size_t, IfcVector3, size_t> > intersections;
bool startedInside = PointInPoly(proj * blackside.front(), profile->mVerts);
bool isCurrentlyInside = startedInside;
std::vector<std::pair<size_t, IfcVector3> > intersected_boundary;
for( size_t a = 0; a < blackside.size(); ++a )
{
const IfcVector3 e0 = proj * blackside[a];
const IfcVector3 e1 = proj * blackside[(a + 1) % blackside.size()];
intersected_boundary.clear();
IntersectsBoundaryProfile(e0, e1, profile->mVerts, isCurrentlyInside, intersected_boundary);
// sort the hits by distance from e0 to get the correct in/out/in sequence. Manually :-( I miss you, C++11.
if( intersected_boundary.size() > 1 )
{
bool keepSorting = true;
while( keepSorting )
{
keepSorting = false;
for( size_t b = 0; b < intersected_boundary.size() - 1; ++b )
{
if( (intersected_boundary[b + 1].second - e0).SquareLength() < (intersected_boundary[b].second - e0).SquareLength() )
{
keepSorting = true;
std::swap(intersected_boundary[b + 1], intersected_boundary[b]);
}
}
}
}
// now add them to the list of intersections
for( size_t b = 0; b < intersected_boundary.size(); ++b )
intersections.push_back(std::make_tuple(a, proj_inv * intersected_boundary[b].second, intersected_boundary[b].first));
// and calculate our new inside/outside state
if( intersected_boundary.size() & 1 )
isCurrentlyInside = !isCurrentlyInside;
}
// we got a list of in-out-combinations of intersections. That should be an even number of intersections, or
// we're fucked.
if( (intersections.size() & 1) != 0 )
{
IFCImporter::LogWarn("Odd number of intersections, can't work with that. Omitting half space boundary check.");
continue;
}
if( intersections.size() > 1 )
{
// If we started outside, the first intersection is a out->in intersection. Cycle them so that it
// starts with an intersection leaving the boundary
if( !startedInside )
for( size_t b = 0; b < intersections.size() - 1; ++b )
std::swap(intersections[b], intersections[(b + intersections.size() - 1) % intersections.size()]);
// Filter pairs of out->in->out that lie too close to each other.
for( size_t a = 0; intersections.size() > 0 && a < intersections.size() - 1; /**/ )
{
if( (std::get<1>(intersections[a]) - std::get<1>(intersections[(a + 1) % intersections.size()])).SquareLength() < 1e-10 )
intersections.erase(intersections.begin() + a, intersections.begin() + a + 2);
else
a++;
}
if( intersections.size() > 1 && (std::get<1>(intersections.back()) - std::get<1>(intersections.front())).SquareLength() < 1e-10 )
{
intersections.pop_back(); intersections.erase(intersections.begin());
}
}
// no intersections at all: either completely inside the boundary, so everything gets discarded, or completely outside.
// in the latter case we're implementional lost. I'm simply going to ignore this, so a large poly will not get any
// holes if the boundary is smaller and does not touch it anywhere.
if( intersections.empty() )
{
// starting point was outside -> everything is outside the boundary -> nothing is clipped -> add black side
// to result mesh unchanged
if( !startedInside )
{
outvertcnt.push_back(static_cast<unsigned int>(blackside.size()));
outvert.insert(outvert.end(), blackside.begin(), blackside.end());
continue;
}
else
{
// starting point was inside the boundary -> everything is inside the boundary -> nothing is spared from the
// clipping -> nothing left to add to the result mesh
continue;
}
}
// determine the direction in which we're marching along the boundary polygon. If the src poly is faced upwards
// and the boundary is also winded this way, we need to march *backwards* on the boundary.
const IfcVector3 polyNormal = IfcMatrix3(proj) * TempMesh::ComputePolygonNormal(blackside.data(), blackside.size());
bool marchBackwardsOnBoundary = (profileNormal * polyNormal) >= 0.0;
// Build closed loops from these intersections. Starting from an intersection leaving the boundary we
// walk along the polygon to the next intersection (which should be an IS entering the boundary poly).
// From there we walk along the boundary until we hit another intersection leaving the boundary,
// walk along the poly to the next IS and so on until we're back at the starting point.
// We remove every intersection we "used up", so any remaining intersection is the start of a new loop.
while( !intersections.empty() )
{
std::vector<IfcVector3> resultpoly;
size_t currentIntersecIdx = 0;
while( true )
{
ai_assert(intersections.size() > currentIntersecIdx + 1);
std::tuple<size_t, IfcVector3, size_t> currintsec = intersections[currentIntersecIdx + 0];
std::tuple<size_t, IfcVector3, size_t> nextintsec = intersections[currentIntersecIdx + 1];
intersections.erase(intersections.begin() + currentIntersecIdx, intersections.begin() + currentIntersecIdx + 2);
// we start with an in->out intersection
resultpoly.push_back(std::get<1>(currintsec));
// climb along the polygon to the next intersection, which should be an out->in
size_t numPolyPoints = (std::get<0>(currintsec) > std::get<0>(nextintsec) ? blackside.size() : 0)
+ std::get<0>(nextintsec) - std::get<0>(currintsec);
for( size_t a = 1; a <= numPolyPoints; ++a )
resultpoly.push_back(blackside[(std::get<0>(currintsec) + a) % blackside.size()]);
// put the out->in intersection
resultpoly.push_back(std::get<1>(nextintsec));
// generate segments along the boundary polygon that lie in the poly's plane until we hit another intersection
IfcVector3 startingPoint = proj * std::get<1>(nextintsec);
size_t currentBoundaryEdgeIdx = (std::get<2>(nextintsec) + (marchBackwardsOnBoundary ? 1 : 0)) % profile->mVerts.size();
size_t nextIntsecIdx = SIZE_MAX;
while( nextIntsecIdx == SIZE_MAX )
{
IfcFloat t = 1e10;
size_t nextBoundaryEdgeIdx = marchBackwardsOnBoundary ? (currentBoundaryEdgeIdx + profile->mVerts.size() - 1) : currentBoundaryEdgeIdx + 1;
nextBoundaryEdgeIdx %= profile->mVerts.size();
// vertices of the current boundary segments
IfcVector3 currBoundaryPoint = profile->mVerts[currentBoundaryEdgeIdx];
IfcVector3 nextBoundaryPoint = profile->mVerts[nextBoundaryEdgeIdx];
// project the two onto the polygon
if( std::abs(polyNormal.z) > 1e-5 )
{
currBoundaryPoint.z = startingPoint.z + (currBoundaryPoint.x - startingPoint.x) * polyNormal.x/polyNormal.z + (currBoundaryPoint.y - startingPoint.y) * polyNormal.y/polyNormal.z;
nextBoundaryPoint.z = startingPoint.z + (nextBoundaryPoint.x - startingPoint.x) * polyNormal.x/polyNormal.z + (nextBoundaryPoint.y - startingPoint.y) * polyNormal.y/polyNormal.z;
}
// build a direction that goes along the boundary border but lies in the poly plane
IfcVector3 boundaryPlaneNormal = ((nextBoundaryPoint - currBoundaryPoint) ^ profileNormal).Normalize();
IfcVector3 dirAtPolyPlane = (boundaryPlaneNormal ^ polyNormal).Normalize() * (marchBackwardsOnBoundary ? -1.0 : 1.0);
// if we can project the direction to the plane, we can calculate a maximum marching distance along that dir
// until we finish that boundary segment and continue on the next
if( std::abs(polyNormal.z) > 1e-5 )
{
t = std::min(t, (nextBoundaryPoint - startingPoint).Length());
}
// check if the direction hits the loop start - if yes, we got a poly to output
IfcVector3 dirToThatPoint = proj * resultpoly.front() - startingPoint;
IfcFloat tpt = dirToThatPoint * dirAtPolyPlane;
if( tpt > -1e-6 && tpt <= t && (dirToThatPoint - tpt * dirAtPolyPlane).SquareLength() < 1e-10 )
{
nextIntsecIdx = intersections.size(); // dirty hack to end marching along the boundary and signal the end of the loop
t = tpt;
}
// also check if the direction hits any in->out intersections earlier. If we hit one, we can switch back
// to marching along the poly border from that intersection point
for( size_t a = 0; a < intersections.size(); a += 2 )
{
dirToThatPoint = proj * std::get<1>(intersections[a]) - startingPoint;
tpt = dirToThatPoint * dirAtPolyPlane;
if( tpt > -1e-6 && tpt <= t && (dirToThatPoint - tpt * dirAtPolyPlane).SquareLength() < 1e-10 )
{
nextIntsecIdx = a; // switch back to poly and march on from this in->out intersection
t = tpt;
}
}
// if we keep marching on the boundary, put the segment end point to the result poly and well... keep marching
if( nextIntsecIdx == SIZE_MAX )
{
resultpoly.push_back(proj_inv * nextBoundaryPoint);
currentBoundaryEdgeIdx = nextBoundaryEdgeIdx;
startingPoint = nextBoundaryPoint;
}
// quick endless loop check
if( resultpoly.size() > blackside.size() + profile->mVerts.size() )
{
IFCImporter::LogError("Encountered endless loop while clipping polygon against poly-bounded half space.");
break;
}
}
// we're back on the poly - if this is the intersection we started from, we got a closed loop.
if( nextIntsecIdx >= intersections.size() )
{
break;
}
// otherwise it's another intersection. Continue marching from there.
currentIntersecIdx = nextIntsecIdx;
}
WritePolygon(resultpoly, result);
}
}
}
IFCImporter::LogDebug("generating CSG geometry by plane clipping with polygonal bounding (IfcBooleanClippingResult)");
}
// ------------------------------------------------------------------------------------------------
void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid* as, TempMesh& result,
const TempMesh& first_operand,
ConversionData& conv)
{
ai_assert(as != NULL);
// This case is handled by reduction to an instance of the quadrify() algorithm.
// Obviously, this won't work for arbitrarily complex cases. In fact, the first
// operand should be near-planar. Luckily, this is usually the case in Ifc
// buildings.
std::shared_ptr<TempMesh> meshtmp = std::shared_ptr<TempMesh>(new TempMesh());
ProcessExtrudedAreaSolid(*as,*meshtmp,conv,false);
std::vector<TempOpening> openings(1, TempOpening(as,IfcVector3(0,0,0),meshtmp,std::shared_ptr<TempMesh>()));
result = first_operand;
TempMesh temp;
std::vector<IfcVector3>::const_iterator vit = first_operand.mVerts.begin();
for(unsigned int pcount : first_operand.mVertcnt) {
temp.Clear();
temp.mVerts.insert(temp.mVerts.end(), vit, vit + pcount);
temp.mVertcnt.push_back(pcount);
// The algorithms used to generate mesh geometry sometimes
// spit out lines or other degenerates which must be
// filtered to avoid running into assertions later on.
// ComputePolygonNormal returns the Newell normal, so the
// length of the normal is the area of the polygon.
const IfcVector3& normal = temp.ComputeLastPolygonNormal(false);
if (normal.SquareLength() < static_cast<IfcFloat>(1e-5)) {
IFCImporter::LogWarn("skipping degenerate polygon (ProcessBooleanExtrudedAreaSolidDifference)");
continue;
}
GenerateOpenings(openings, std::vector<IfcVector3>(1,IfcVector3(1,0,0)), temp, false, true);
result.Append(temp);
vit += pcount;
}
IFCImporter::LogDebug("generating CSG geometry by geometric difference to a solid (IfcExtrudedAreaSolid)");
}
// ------------------------------------------------------------------------------------------------
void ProcessBoolean(const Schema_2x3::IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv)
{
// supported CSG operations:
// DIFFERENCE
if(const Schema_2x3::IfcBooleanResult* const clip = boolean.ToPtr<Schema_2x3::IfcBooleanResult>()) {
if(clip->Operator != "DIFFERENCE") {
IFCImporter::LogWarn("encountered unsupported boolean operator: " + (std::string)clip->Operator);
return;
}
// supported cases (1st operand):
// IfcBooleanResult -- call ProcessBoolean recursively
// IfcSweptAreaSolid -- obtain polygonal geometry first
// supported cases (2nd operand):
// IfcHalfSpaceSolid -- easy, clip against plane
// IfcExtrudedAreaSolid -- reduce to an instance of the quadrify() algorithm
const Schema_2x3::IfcHalfSpaceSolid* const hs = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcHalfSpaceSolid>(conv.db);
const Schema_2x3::IfcExtrudedAreaSolid* const as = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcExtrudedAreaSolid>(conv.db);
if(!hs && !as) {
IFCImporter::LogError("expected IfcHalfSpaceSolid or IfcExtrudedAreaSolid as second clipping operand");
return;
}
TempMesh first_operand;
if(const Schema_2x3::IfcBooleanResult* const op0 = clip->FirstOperand->ResolveSelectPtr<Schema_2x3::IfcBooleanResult>(conv.db)) {
ProcessBoolean(*op0,first_operand,conv);
}
else if (const Schema_2x3::IfcSweptAreaSolid* const swept = clip->FirstOperand->ResolveSelectPtr<Schema_2x3::IfcSweptAreaSolid>(conv.db)) {
ProcessSweptAreaSolid(*swept,first_operand,conv);
}
else {
IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand");
return;
}
if(hs) {
const Schema_2x3::IfcPolygonalBoundedHalfSpace* const hs_bounded = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcPolygonalBoundedHalfSpace>(conv.db);
if (hs_bounded) {
ProcessPolygonalBoundedBooleanHalfSpaceDifference(hs_bounded, result, first_operand, conv);
}
else {
ProcessBooleanHalfSpaceDifference(hs, result, first_operand, conv);
}
}
else {
ProcessBooleanExtrudedAreaSolidDifference(as, result, first_operand, conv);
}
}
else {
IFCImporter::LogWarn("skipping unknown IfcBooleanResult entity, type is " + boolean.GetClassName());
}
}
} // ! IFC
} // ! Assimp
#endif

View File

@@ -1,616 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file IFCProfile.cpp
* @brief Read profile and curves entities from IFC files
*/
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
#include "IFCUtil.h"
namespace Assimp {
namespace IFC {
namespace {
// --------------------------------------------------------------------------------
// Conic is the base class for Circle and Ellipse
// --------------------------------------------------------------------------------
class Conic : public Curve {
public:
// --------------------------------------------------
Conic(const Schema_2x3::IfcConic& entity, ConversionData& conv)
: Curve(entity,conv) {
IfcMatrix4 trafo;
ConvertAxisPlacement(trafo,*entity.Position,conv);
// for convenience, extract the matrix rows
location = IfcVector3(trafo.a4,trafo.b4,trafo.c4);
p[0] = IfcVector3(trafo.a1,trafo.b1,trafo.c1);
p[1] = IfcVector3(trafo.a2,trafo.b2,trafo.c2);
p[2] = IfcVector3(trafo.a3,trafo.b3,trafo.c3);
}
// --------------------------------------------------
bool IsClosed() const {
return true;
}
// --------------------------------------------------
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
ai_assert( InRange( a ) );
ai_assert( InRange( b ) );
a *= conv.angle_scale;
b *= conv.angle_scale;
a = std::fmod(a,static_cast<IfcFloat>( AI_MATH_TWO_PI ));
b = std::fmod(b,static_cast<IfcFloat>( AI_MATH_TWO_PI ));
const IfcFloat setting = static_cast<IfcFloat>( AI_MATH_PI * conv.settings.conicSamplingAngle / 180.0 );
return static_cast<size_t>( std::ceil(std::abs( b-a)) / setting);
}
// --------------------------------------------------
ParamRange GetParametricRange() const {
return std::make_pair(static_cast<IfcFloat>( 0. ), static_cast<IfcFloat>( AI_MATH_TWO_PI / conv.angle_scale ));
}
protected:
IfcVector3 location, p[3];
};
// --------------------------------------------------------------------------------
// Circle
// --------------------------------------------------------------------------------
class Circle : public Conic {
public:
// --------------------------------------------------
Circle(const Schema_2x3::IfcCircle& entity, ConversionData& conv)
: Conic(entity,conv)
, entity(entity)
{
}
// --------------------------------------------------
IfcVector3 Eval(IfcFloat u) const {
u = -conv.angle_scale * u;
return location + static_cast<IfcFloat>(entity.Radius)*(static_cast<IfcFloat>(std::cos(u))*p[0] +
static_cast<IfcFloat>(std::sin(u))*p[1]);
}
private:
const Schema_2x3::IfcCircle& entity;
};
// --------------------------------------------------------------------------------
// Ellipse
// --------------------------------------------------------------------------------
class Ellipse : public Conic {
public:
// --------------------------------------------------
Ellipse(const Schema_2x3::IfcEllipse& entity, ConversionData& conv)
: Conic(entity,conv)
, entity(entity) {
// empty
}
// --------------------------------------------------
IfcVector3 Eval(IfcFloat u) const {
u = -conv.angle_scale * u;
return location + static_cast<IfcFloat>(entity.SemiAxis1)*static_cast<IfcFloat>(std::cos(u))*p[0] +
static_cast<IfcFloat>(entity.SemiAxis2)*static_cast<IfcFloat>(std::sin(u))*p[1];
}
private:
const Schema_2x3::IfcEllipse& entity;
};
// --------------------------------------------------------------------------------
// Line
// --------------------------------------------------------------------------------
class Line : public Curve {
public:
// --------------------------------------------------
Line(const Schema_2x3::IfcLine& entity, ConversionData& conv)
: Curve(entity,conv) {
ConvertCartesianPoint(p,entity.Pnt);
ConvertVector(v,entity.Dir);
}
// --------------------------------------------------
bool IsClosed() const {
return false;
}
// --------------------------------------------------
IfcVector3 Eval(IfcFloat u) const {
return p + u*v;
}
// --------------------------------------------------
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
ai_assert( InRange( a ) );
ai_assert( InRange( b ) );
// two points are always sufficient for a line segment
return a==b ? 1 : 2;
}
// --------------------------------------------------
void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
ai_assert( InRange( a ) );
ai_assert( InRange( b ) );
if (a == b) {
out.mVerts.push_back(Eval(a));
return;
}
out.mVerts.reserve(out.mVerts.size()+2);
out.mVerts.push_back(Eval(a));
out.mVerts.push_back(Eval(b));
}
// --------------------------------------------------
ParamRange GetParametricRange() const {
const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity();
return std::make_pair(-inf,+inf);
}
private:
IfcVector3 p,v;
};
// --------------------------------------------------------------------------------
// CompositeCurve joins multiple smaller, bounded curves
// --------------------------------------------------------------------------------
class CompositeCurve : public BoundedCurve {
typedef std::pair< std::shared_ptr< BoundedCurve >, bool > CurveEntry;
public:
// --------------------------------------------------
CompositeCurve(const Schema_2x3::IfcCompositeCurve& entity, ConversionData& conv)
: BoundedCurve(entity,conv)
, total() {
curves.reserve(entity.Segments.size());
for(const Schema_2x3::IfcCompositeCurveSegment& curveSegment :entity.Segments) {
// according to the specification, this must be a bounded curve
std::shared_ptr< Curve > cv(Curve::Convert(curveSegment.ParentCurve,conv));
std::shared_ptr< BoundedCurve > bc = std::dynamic_pointer_cast<BoundedCurve>(cv);
if (!bc) {
IFCImporter::LogError("expected segment of composite curve to be a bounded curve");
continue;
}
if ( (std::string)curveSegment.Transition != "CONTINUOUS" ) {
IFCImporter::LogDebug("ignoring transition code on composite curve segment, only continuous transitions are supported");
}
curves.push_back( CurveEntry(bc,IsTrue(curveSegment.SameSense)) );
total += bc->GetParametricRangeDelta();
}
if (curves.empty()) {
throw CurveError("empty composite curve");
}
}
// --------------------------------------------------
IfcVector3 Eval(IfcFloat u) const {
if (curves.empty()) {
return IfcVector3();
}
IfcFloat acc = 0;
for(const CurveEntry& entry : curves) {
const ParamRange& range = entry.first->GetParametricRange();
const IfcFloat delta = std::abs(range.second-range.first);
if (u < acc+delta) {
return entry.first->Eval( entry.second ? (u-acc) + range.first : range.second-(u-acc));
}
acc += delta;
}
// clamp to end
return curves.back().first->Eval(curves.back().first->GetParametricRange().second);
}
// --------------------------------------------------
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
ai_assert( InRange( a ) );
ai_assert( InRange( b ) );
size_t cnt = 0;
IfcFloat acc = 0;
for(const CurveEntry& entry : curves) {
const ParamRange& range = entry.first->GetParametricRange();
const IfcFloat delta = std::abs(range.second-range.first);
if (a <= acc+delta && b >= acc) {
const IfcFloat at = std::max(static_cast<IfcFloat>( 0. ),a-acc), bt = std::min(delta,b-acc);
cnt += entry.first->EstimateSampleCount( entry.second ? at + range.first : range.second - bt, entry.second ? bt + range.first : range.second - at );
}
acc += delta;
}
return cnt;
}
// --------------------------------------------------
void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
ai_assert( InRange( a ) );
ai_assert( InRange( b ) );
const size_t cnt = EstimateSampleCount(a,b);
out.mVerts.reserve(out.mVerts.size() + cnt);
for(const CurveEntry& entry : curves) {
const size_t cnt = out.mVerts.size();
entry.first->SampleDiscrete(out);
if (!entry.second && cnt != out.mVerts.size()) {
std::reverse(out.mVerts.begin()+cnt,out.mVerts.end());
}
}
}
// --------------------------------------------------
ParamRange GetParametricRange() const {
return std::make_pair(static_cast<IfcFloat>( 0. ),total);
}
private:
std::vector< CurveEntry > curves;
IfcFloat total;
};
// --------------------------------------------------------------------------------
// TrimmedCurve can be used to trim an unbounded curve to a bounded range
// --------------------------------------------------------------------------------
class TrimmedCurve : public BoundedCurve {
public:
// --------------------------------------------------
TrimmedCurve(const Schema_2x3::IfcTrimmedCurve& entity, ConversionData& conv)
: BoundedCurve(entity,conv),
base(std::shared_ptr<const Curve>(Curve::Convert(entity.BasisCurve,conv)))
{
typedef std::shared_ptr<const STEP::EXPRESS::DataType> Entry;
// for some reason, trimmed curves can either specify a parametric value
// or a point on the curve, or both. And they can even specify which of the
// two representations they prefer, even though an information invariant
// claims that they must be identical if both are present.
// oh well.
bool have_param = false, have_point = false;
IfcVector3 point;
for(const Entry sel :entity.Trim1) {
if (const ::Assimp::STEP::EXPRESS::REAL* const r = sel->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
range.first = *r;
have_param = true;
break;
}
else if (const Schema_2x3::IfcCartesianPoint* const r = sel->ResolveSelectPtr<Schema_2x3::IfcCartesianPoint>(conv.db)) {
ConvertCartesianPoint(point,*r);
have_point = true;
}
}
if (!have_param) {
if (!have_point || !base->ReverseEval(point,range.first)) {
throw CurveError("IfcTrimmedCurve: failed to read first trim parameter, ignoring curve");
}
}
have_param = false, have_point = false;
for(const Entry sel :entity.Trim2) {
if (const ::Assimp::STEP::EXPRESS::REAL* const r = sel->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
range.second = *r;
have_param = true;
break;
}
else if (const Schema_2x3::IfcCartesianPoint* const r = sel->ResolveSelectPtr<Schema_2x3::IfcCartesianPoint>(conv.db)) {
ConvertCartesianPoint(point,*r);
have_point = true;
}
}
if (!have_param) {
if (!have_point || !base->ReverseEval(point,range.second)) {
throw CurveError("IfcTrimmedCurve: failed to read second trim parameter, ignoring curve");
}
}
agree_sense = IsTrue(entity.SenseAgreement);
if( !agree_sense ) {
std::swap(range.first,range.second);
}
// "NOTE In case of a closed curve, it may be necessary to increment t1 or t2
// by the parametric length for consistency with the sense flag."
if (base->IsClosed()) {
if( range.first > range.second ) {
range.second += base->GetParametricRangeDelta();
}
}
maxval = range.second-range.first;
ai_assert(maxval >= 0);
}
// --------------------------------------------------
IfcVector3 Eval(IfcFloat p) const {
ai_assert(InRange(p));
return base->Eval( TrimParam(p) );
}
// --------------------------------------------------
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
ai_assert( InRange( a ) );
ai_assert( InRange( b ) );
return base->EstimateSampleCount(TrimParam(a),TrimParam(b));
}
// --------------------------------------------------
void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const {
ai_assert(InRange(a) && InRange(b));
return base->SampleDiscrete(out,TrimParam(a),TrimParam(b));
}
// --------------------------------------------------
ParamRange GetParametricRange() const {
return std::make_pair(static_cast<IfcFloat>( 0. ),maxval);
}
private:
// --------------------------------------------------
IfcFloat TrimParam(IfcFloat f) const {
return agree_sense ? f + range.first : range.second - f;
}
private:
ParamRange range;
IfcFloat maxval;
bool agree_sense;
std::shared_ptr<const Curve> base;
};
// --------------------------------------------------------------------------------
// PolyLine is a 'curve' defined by linear interpolation over a set of discrete points
// --------------------------------------------------------------------------------
class PolyLine : public BoundedCurve {
public:
// --------------------------------------------------
PolyLine(const Schema_2x3::IfcPolyline& entity, ConversionData& conv)
: BoundedCurve(entity,conv)
{
points.reserve(entity.Points.size());
IfcVector3 t;
for(const Schema_2x3::IfcCartesianPoint& cp : entity.Points) {
ConvertCartesianPoint(t,cp);
points.push_back(t);
}
}
// --------------------------------------------------
IfcVector3 Eval(IfcFloat p) const {
ai_assert(InRange(p));
const size_t b = static_cast<size_t>(std::floor(p));
if (b == points.size()-1) {
return points.back();
}
const IfcFloat d = p-static_cast<IfcFloat>(b);
return points[b+1] * d + points[b] * (static_cast<IfcFloat>( 1. )-d);
}
// --------------------------------------------------
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
ai_assert(InRange(a) && InRange(b));
return static_cast<size_t>( std::ceil(b) - std::floor(a) );
}
// --------------------------------------------------
ParamRange GetParametricRange() const {
return std::make_pair(static_cast<IfcFloat>( 0. ),static_cast<IfcFloat>(points.size()-1));
}
private:
std::vector<IfcVector3> points;
};
} // anon
// ------------------------------------------------------------------------------------------------
Curve* Curve::Convert(const IFC::Schema_2x3::IfcCurve& curve,ConversionData& conv) {
if(curve.ToPtr<Schema_2x3::IfcBoundedCurve>()) {
if(const Schema_2x3::IfcPolyline* c = curve.ToPtr<Schema_2x3::IfcPolyline>()) {
return new PolyLine(*c,conv);
}
if(const Schema_2x3::IfcTrimmedCurve* c = curve.ToPtr<Schema_2x3::IfcTrimmedCurve>()) {
return new TrimmedCurve(*c,conv);
}
if(const Schema_2x3::IfcCompositeCurve* c = curve.ToPtr<Schema_2x3::IfcCompositeCurve>()) {
return new CompositeCurve(*c,conv);
}
}
if(curve.ToPtr<Schema_2x3::IfcConic>()) {
if(const Schema_2x3::IfcCircle* c = curve.ToPtr<Schema_2x3::IfcCircle>()) {
return new Circle(*c,conv);
}
if(const Schema_2x3::IfcEllipse* c = curve.ToPtr<Schema_2x3::IfcEllipse>()) {
return new Ellipse(*c,conv);
}
}
if(const Schema_2x3::IfcLine* c = curve.ToPtr<Schema_2x3::IfcLine>()) {
return new Line(*c,conv);
}
// XXX OffsetCurve2D, OffsetCurve3D not currently supported
return NULL;
}
#ifdef ASSIMP_BUILD_DEBUG
// ------------------------------------------------------------------------------------------------
bool Curve::InRange(IfcFloat u) const {
const ParamRange range = GetParametricRange();
if (IsClosed()) {
return true;
}
const IfcFloat epsilon = Math::getEpsilon<float>();
return u - range.first > -epsilon && range.second - u > -epsilon;
}
#endif
// ------------------------------------------------------------------------------------------------
IfcFloat Curve::GetParametricRangeDelta() const {
const ParamRange& range = GetParametricRange();
return std::abs(range.second - range.first);
}
// ------------------------------------------------------------------------------------------------
size_t Curve::EstimateSampleCount(IfcFloat a, IfcFloat b) const {
(void)(a); (void)(b);
ai_assert( InRange( a ) );
ai_assert( InRange( b ) );
// arbitrary default value, deriving classes should supply better suited values
return 16;
}
// ------------------------------------------------------------------------------------------------
IfcFloat RecursiveSearch(const Curve* cv, const IfcVector3& val, IfcFloat a, IfcFloat b,
unsigned int samples, IfcFloat threshold, unsigned int recurse = 0, unsigned int max_recurse = 15) {
ai_assert(samples>1);
const IfcFloat delta = (b-a)/samples, inf = std::numeric_limits<IfcFloat>::infinity();
IfcFloat min_point[2] = {a,b}, min_diff[2] = {inf,inf};
IfcFloat runner = a;
for (unsigned int i = 0; i < samples; ++i, runner += delta) {
const IfcFloat diff = (cv->Eval(runner)-val).SquareLength();
if (diff < min_diff[0]) {
min_diff[1] = min_diff[0];
min_point[1] = min_point[0];
min_diff[0] = diff;
min_point[0] = runner;
}
else if (diff < min_diff[1]) {
min_diff[1] = diff;
min_point[1] = runner;
}
}
ai_assert( min_diff[ 0 ] != inf );
ai_assert( min_diff[ 1 ] != inf );
if ( std::fabs(a-min_point[0]) < threshold || recurse >= max_recurse) {
return min_point[0];
}
// fix for closed curves to take their wrap-over into account
if (cv->IsClosed() && std::fabs(min_point[0]-min_point[1]) > cv->GetParametricRangeDelta()*0.5 ) {
const Curve::ParamRange& range = cv->GetParametricRange();
const IfcFloat wrapdiff = (cv->Eval(range.first)-val).SquareLength();
if (wrapdiff < min_diff[0]) {
const IfcFloat t = min_point[0];
min_point[0] = min_point[1] > min_point[0] ? range.first : range.second;
min_point[1] = t;
}
}
return RecursiveSearch(cv,val,min_point[0],min_point[1],samples,threshold,recurse+1,max_recurse);
}
// ------------------------------------------------------------------------------------------------
bool Curve::ReverseEval(const IfcVector3& val, IfcFloat& paramOut) const
{
// note: the following algorithm is not guaranteed to find the 'right' parameter value
// in all possible cases, but it will always return at least some value so this function
// will never fail in the default implementation.
// XXX derive threshold from curve topology
static const IfcFloat threshold = 1e-4f;
static const unsigned int samples = 16;
const ParamRange& range = GetParametricRange();
paramOut = RecursiveSearch(this,val,range.first,range.second,samples,threshold);
return true;
}
// ------------------------------------------------------------------------------------------------
void Curve::SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
ai_assert( InRange( a ) );
ai_assert( InRange( b ) );
const size_t cnt = std::max(static_cast<size_t>(0),EstimateSampleCount(a,b));
out.mVerts.reserve( out.mVerts.size() + cnt + 1);
IfcFloat p = a, delta = (b-a)/cnt;
for(size_t i = 0; i <= cnt; ++i, p += delta) {
out.mVerts.push_back(Eval(p));
}
}
// ------------------------------------------------------------------------------------------------
bool BoundedCurve::IsClosed() const {
return false;
}
// ------------------------------------------------------------------------------------------------
void BoundedCurve::SampleDiscrete(TempMesh& out) const {
const ParamRange& range = GetParametricRange();
ai_assert( range.first != std::numeric_limits<IfcFloat>::infinity() );
ai_assert( range.second != std::numeric_limits<IfcFloat>::infinity() );
return SampleDiscrete(out,range.first,range.second);
}
} // IFC
} // Assimp
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER

View File

@@ -1,888 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2010, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file IFCGeometry.cpp
* @brief Geometry conversion and synthesis for IFC
*/
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
#include "IFCUtil.h"
#include "Common/PolyTools.h"
#include "PostProcessing/ProcessHelper.h"
#ifdef ASSIMP_USE_HUNTER
# include <poly2tri/poly2tri.h>
# include <polyclipping/clipper.hpp>
#else
# include "../contrib/poly2tri/poly2tri/poly2tri.h"
# include "../contrib/clipper/clipper.hpp"
#endif
#include <memory>
#include <iterator>
namespace Assimp {
namespace IFC {
// ------------------------------------------------------------------------------------------------
bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/)
{
size_t cnt = 0;
for(const Schema_2x3::IfcCartesianPoint& c : loop.Polygon) {
IfcVector3 tmp;
ConvertCartesianPoint(tmp,c);
meshout.mVerts.push_back(tmp);
++cnt;
}
meshout.mVertcnt.push_back(static_cast<unsigned int>(cnt));
// zero- or one- vertex polyloops simply ignored
if (meshout.mVertcnt.back() > 1) {
return true;
}
if (meshout.mVertcnt.back()==1) {
meshout.mVertcnt.pop_back();
meshout.mVerts.pop_back();
}
return false;
}
// ------------------------------------------------------------------------------------------------
void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1)
{
// handle all trivial cases
if(inmesh.mVertcnt.empty()) {
return;
}
if(inmesh.mVertcnt.size() == 1) {
result.Append(inmesh);
return;
}
ai_assert(std::count(inmesh.mVertcnt.begin(), inmesh.mVertcnt.end(), 0) == 0);
typedef std::vector<unsigned int>::const_iterator face_iter;
face_iter begin = inmesh.mVertcnt.begin(), end = inmesh.mVertcnt.end(), iit;
std::vector<unsigned int>::const_iterator outer_polygon_it = end;
// major task here: given a list of nested polygon boundaries (one of which
// is the outer contour), reduce the triangulation task arising here to
// one that can be solved using the "quadrulation" algorithm which we use
// for pouring windows out of walls. The algorithm does not handle all
// cases but at least it is numerically stable and gives "nice" triangles.
// first compute normals for all polygons using Newell's algorithm
// do not normalize 'normals', we need the original length for computing the polygon area
std::vector<IfcVector3> normals;
inmesh.ComputePolygonNormals(normals,false);
// One of the polygons might be a IfcFaceOuterBound (in which case `master_bounds`
// is its index). Sadly we can't rely on it, the docs say 'At most one of the bounds
// shall be of the type IfcFaceOuterBound'
IfcFloat area_outer_polygon = 1e-10f;
if (master_bounds != (size_t)-1) {
ai_assert(master_bounds < inmesh.mVertcnt.size());
outer_polygon_it = begin + master_bounds;
}
else {
for(iit = begin; iit != end; ++iit) {
// find the polygon with the largest area and take it as the outer bound.
IfcVector3& n = normals[std::distance(begin,iit)];
const IfcFloat area = n.SquareLength();
if (area > area_outer_polygon) {
area_outer_polygon = area;
outer_polygon_it = iit;
}
}
}
ai_assert(outer_polygon_it != end);
const size_t outer_polygon_size = *outer_polygon_it;
const IfcVector3& master_normal = normals[std::distance(begin, outer_polygon_it)];
// Generate fake openings to meet the interface for the quadrulate
// algorithm. It boils down to generating small boxes given the
// inner polygon and the surface normal of the outer contour.
// It is important that we use the outer contour's normal because
// this is the plane onto which the quadrulate algorithm will
// project the entire mesh.
std::vector<TempOpening> fake_openings;
fake_openings.reserve(inmesh.mVertcnt.size()-1);
std::vector<IfcVector3>::const_iterator vit = inmesh.mVerts.begin(), outer_vit;
for(iit = begin; iit != end; vit += *iit++) {
if (iit == outer_polygon_it) {
outer_vit = vit;
continue;
}
// Filter degenerate polygons to keep them from causing trouble later on
IfcVector3& n = normals[std::distance(begin,iit)];
const IfcFloat area = n.SquareLength();
if (area < 1e-5f) {
IFCImporter::LogWarn("skipping degenerate polygon (ProcessPolygonBoundaries)");
continue;
}
fake_openings.push_back(TempOpening());
TempOpening& opening = fake_openings.back();
opening.extrusionDir = master_normal;
opening.solid = NULL;
opening.profileMesh = std::make_shared<TempMesh>();
opening.profileMesh->mVerts.reserve(*iit);
opening.profileMesh->mVertcnt.push_back(*iit);
std::copy(vit, vit + *iit, std::back_inserter(opening.profileMesh->mVerts));
}
// fill a mesh with ONLY the main polygon
TempMesh temp;
temp.mVerts.reserve(outer_polygon_size);
temp.mVertcnt.push_back(static_cast<unsigned int>(outer_polygon_size));
std::copy(outer_vit, outer_vit+outer_polygon_size,
std::back_inserter(temp.mVerts));
GenerateOpenings(fake_openings, normals, temp, false, false);
result.Append(temp);
}
// ------------------------------------------------------------------------------------------------
void ProcessConnectedFaceSet(const Schema_2x3::IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv)
{
for(const Schema_2x3::IfcFace& face : fset.CfsFaces) {
// size_t ob = -1, cnt = 0;
TempMesh meshout;
for(const Schema_2x3::IfcFaceBound& bound : face.Bounds) {
if(const Schema_2x3::IfcPolyLoop* const polyloop = bound.Bound->ToPtr<Schema_2x3::IfcPolyLoop>()) {
if(ProcessPolyloop(*polyloop, meshout,conv)) {
// The outer boundary is better determined by checking which
// polygon covers the largest area.
//if(bound.ToPtr<IfcFaceOuterBound>()) {
// ob = cnt;
//}
//++cnt;
}
}
else {
IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is " + bound.Bound->GetClassName());
continue;
}
// And this, even though it is sometimes TRUE and sometimes FALSE,
// does not really improve results.
/*if(!IsTrue(bound.Orientation)) {
size_t c = 0;
for(unsigned int& c : meshout.vertcnt) {
std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c);
cnt += c;
}
}*/
}
ProcessPolygonBoundaries(result, meshout);
}
}
// ------------------------------------------------------------------------------------------------
void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
{
TempMesh meshout;
// first read the profile description
if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.mVerts.size()<=1) {
return;
}
IfcVector3 axis, pos;
ConvertAxisPlacement(axis,pos,solid.Axis);
IfcMatrix4 tb0,tb1;
IfcMatrix4::Translation(pos,tb0);
IfcMatrix4::Translation(-pos,tb1);
const std::vector<IfcVector3>& in = meshout.mVerts;
const size_t size=in.size();
bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2;
const IfcFloat max_angle = solid.Angle*conv.angle_scale;
if(std::fabs(max_angle) < 1e-3) {
if(has_area) {
result = meshout;
}
return;
}
const unsigned int cnt_segments = std::max(2u,static_cast<unsigned int>(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F));
const IfcFloat delta = max_angle/cnt_segments;
has_area = has_area && std::fabs(max_angle) < AI_MATH_TWO_PI_F*0.99;
result.mVerts.reserve(size*((cnt_segments+1)*4+(has_area?2:0)));
result.mVertcnt.reserve(size*cnt_segments+2);
IfcMatrix4 rot;
rot = tb0 * IfcMatrix4::Rotation(delta,axis,rot) * tb1;
size_t base = 0;
std::vector<IfcVector3>& out = result.mVerts;
// dummy data to simplify later processing
for(size_t i = 0; i < size; ++i) {
out.insert(out.end(),4,in[i]);
}
for(unsigned int seg = 0; seg < cnt_segments; ++seg) {
for(size_t i = 0; i < size; ++i) {
const size_t next = (i+1)%size;
result.mVertcnt.push_back(4);
const IfcVector3 base_0 = out[base+i*4+3],base_1 = out[base+next*4+3];
out.push_back(base_0);
out.push_back(base_1);
out.push_back(rot*base_1);
out.push_back(rot*base_0);
}
base += size*4;
}
out.erase(out.begin(),out.begin()+size*4);
if(has_area) {
// leave the triangulation of the profile area to the ear cutting
// implementation in aiProcess_Triangulate - for now we just
// feed in two huge polygons.
base -= size*8;
for(size_t i = size; i--; ) {
out.push_back(out[base+i*4+3]);
}
for(size_t i = 0; i < size; ++i ) {
out.push_back(out[i*4]);
}
result.mVertcnt.push_back(static_cast<unsigned int>(size));
result.mVertcnt.push_back(static_cast<unsigned int>(size));
}
IfcMatrix4 trafo;
ConvertAxisPlacement(trafo, solid.Position);
result.Transform(trafo);
IFCImporter::LogDebug("generate mesh procedurally by radial extrusion (IfcRevolvedAreaSolid)");
}
// ------------------------------------------------------------------------------------------------
void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, TempMesh& result, ConversionData& conv)
{
const Curve* const curve = Curve::Convert(*solid.Directrix, conv);
if(!curve) {
IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)");
return;
}
const unsigned int cnt_segments = conv.settings.cylindricalTessellation;
const IfcFloat deltaAngle = AI_MATH_TWO_PI/cnt_segments;
TempMesh temp;
curve->SampleDiscrete(temp, solid.StartParam, solid.EndParam);
const std::vector<IfcVector3>& curve_points = temp.mVerts;
const size_t samples = curve_points.size();
result.mVerts.reserve(cnt_segments * samples * 4);
result.mVertcnt.reserve((cnt_segments - 1) * samples);
std::vector<IfcVector3> points;
points.reserve(cnt_segments * samples);
if(curve_points.empty()) {
IFCImporter::LogWarn("curve evaluation yielded no points (IfcSweptDiskSolid)");
return;
}
IfcVector3 current = curve_points[0];
IfcVector3 previous = current;
IfcVector3 next;
IfcVector3 startvec;
startvec.x = 1.0f;
startvec.y = 1.0f;
startvec.z = 1.0f;
unsigned int last_dir = 0;
// generate circles at the sweep positions
for(size_t i = 0; i < samples; ++i) {
if(i != samples - 1) {
next = curve_points[i + 1];
}
// get a direction vector reflecting the approximate curvature (i.e. tangent)
IfcVector3 d = (current-previous) + (next-previous);
d.Normalize();
// figure out an arbitrary point q so that (p-q) * d = 0,
// try to maximize ||(p-q)|| * ||(p_last-q_last)||
IfcVector3 q;
bool take_any = false;
for (unsigned int i = 0; i < 2; ++i, take_any = true) {
if ((last_dir == 0 || take_any) && std::abs(d.x) > 1e-6) {
q.y = startvec.y;
q.z = startvec.z;
q.x = -(d.y * q.y + d.z * q.z) / d.x;
last_dir = 0;
break;
}
else if ((last_dir == 1 || take_any) && std::abs(d.y) > 1e-6) {
q.x = startvec.x;
q.z = startvec.z;
q.y = -(d.x * q.x + d.z * q.z) / d.y;
last_dir = 1;
break;
}
else if ((last_dir == 2 && std::abs(d.z) > 1e-6) || take_any) {
q.y = startvec.y;
q.x = startvec.x;
q.z = -(d.y * q.y + d.x * q.x) / d.z;
last_dir = 2;
break;
}
}
q *= solid.Radius / q.Length();
startvec = q;
// generate a rotation matrix to rotate q around d
IfcMatrix4 rot;
IfcMatrix4::Rotation(deltaAngle,d,rot);
for (unsigned int seg = 0; seg < cnt_segments; ++seg, q *= rot ) {
points.push_back(q + current);
}
previous = current;
current = next;
}
// make quads
for(size_t i = 0; i < samples - 1; ++i) {
const aiVector3D& this_start = points[ i * cnt_segments ];
// locate corresponding point on next sample ring
unsigned int best_pair_offset = 0;
float best_distance_squared = 1e10f;
for (unsigned int seg = 0; seg < cnt_segments; ++seg) {
const aiVector3D& p = points[ (i+1) * cnt_segments + seg];
const float l = (p-this_start).SquareLength();
if(l < best_distance_squared) {
best_pair_offset = seg;
best_distance_squared = l;
}
}
for (unsigned int seg = 0; seg < cnt_segments; ++seg) {
result.mVerts.push_back(points[ i * cnt_segments + (seg % cnt_segments)]);
result.mVerts.push_back(points[ i * cnt_segments + (seg + 1) % cnt_segments]);
result.mVerts.push_back(points[ (i+1) * cnt_segments + ((seg + 1 + best_pair_offset) % cnt_segments)]);
result.mVerts.push_back(points[ (i+1) * cnt_segments + ((seg + best_pair_offset) % cnt_segments)]);
IfcVector3& v1 = *(result.mVerts.end()-1);
IfcVector3& v2 = *(result.mVerts.end()-2);
IfcVector3& v3 = *(result.mVerts.end()-3);
IfcVector3& v4 = *(result.mVerts.end()-4);
if (((v4-v3) ^ (v4-v1)) * (v4 - curve_points[i]) < 0.0f) {
std::swap(v4, v1);
std::swap(v3, v2);
}
result.mVertcnt.push_back(4);
}
}
IFCImporter::LogDebug("generate mesh procedurally by sweeping a disk along a curve (IfcSweptDiskSolid)");
}
// ------------------------------------------------------------------------------------------------
IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut)
{
const std::vector<IfcVector3>& out = curmesh.mVerts;
IfcMatrix3 m;
ok = true;
// The input "mesh" must be a single polygon
const size_t s = out.size();
ai_assert( curmesh.mVertcnt.size() == 1 );
ai_assert( curmesh.mVertcnt.back() == s);
const IfcVector3 any_point = out[s-1];
IfcVector3 nor;
// The input polygon is arbitrarily shaped, therefore we might need some tries
// until we find a suitable normal. Note that Newell's algorithm would give
// a more robust result, but this variant also gives us a suitable first
// axis for the 2D coordinate space on the polygon plane, exploiting the
// fact that the input polygon is nearly always a quad.
bool done = false;
size_t idx( 0 );
for (size_t i = 0; !done && i < s-2; done || ++i) {
idx = i;
for (size_t j = i+1; j < s-1; ++j) {
nor = -((out[i]-any_point)^(out[j]-any_point));
if(std::fabs(nor.Length()) > 1e-8f) {
done = true;
break;
}
}
}
if(!done) {
ok = false;
return m;
}
nor.Normalize();
norOut = nor;
IfcVector3 r = (out[idx]-any_point);
r.Normalize();
//if(d) {
// *d = -any_point * nor;
//}
// Reconstruct orthonormal basis
// XXX use Gram Schmidt for increased robustness
IfcVector3 u = r ^ nor;
u.Normalize();
m.a1 = r.x;
m.a2 = r.y;
m.a3 = r.z;
m.b1 = u.x;
m.b2 = u.y;
m.b3 = u.z;
m.c1 = -nor.x;
m.c2 = -nor.y;
m.c3 = -nor.z;
return m;
}
// 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)
{
// Outline: 'curve' is now a list of vertex points forming the underlying profile, extrude along the given axis,
// forming new triangles.
const bool has_area = solid.SweptArea->ProfileType == "AREA" && curve.mVerts.size() > 2;
if( solid.Depth < 1e-6 ) {
if( has_area ) {
result.Append(curve);
}
return;
}
result.mVerts.reserve(curve.mVerts.size()*(has_area ? 4 : 2));
result.mVertcnt.reserve(curve.mVerts.size() + 2);
std::vector<IfcVector3> in = curve.mVerts;
// First step: transform all vertices into the target coordinate space
IfcMatrix4 trafo;
ConvertAxisPlacement(trafo, solid.Position);
IfcVector3 vmin, vmax;
MinMaxChooser<IfcVector3>()(vmin, vmax);
for(IfcVector3& v : in) {
v *= trafo;
vmin = std::min(vmin, v);
vmax = std::max(vmax, v);
}
vmax -= vmin;
const IfcFloat diag = vmax.Length();
IfcVector3 dir = IfcMatrix3(trafo) * extrusionDir;
// reverse profile polygon if it's winded in the wrong direction in relation to the extrusion direction
IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(in.data(), in.size());
if( profileNormal * dir < 0.0 )
std::reverse(in.begin(), in.end());
std::vector<IfcVector3> nors;
const bool openings = !!conv.apply_openings && conv.apply_openings->size();
// Compute the normal vectors for all opening polygons as a prerequisite
// to TryAddOpenings_Poly2Tri()
// XXX this belongs into the aforementioned function
if( openings ) {
if( !conv.settings.useCustomTriangulation ) {
// it is essential to apply the openings in the correct spatial order. The direction
// doesn't matter, but we would screw up if we started with e.g. a door in between
// two windows.
std::sort(conv.apply_openings->begin(), conv.apply_openings->end(), TempOpening::DistanceSorter(in[0]));
}
nors.reserve(conv.apply_openings->size());
for(TempOpening& t : *conv.apply_openings) {
TempMesh& bounds = *t.profileMesh.get();
if( bounds.mVerts.size() <= 2 ) {
nors.push_back(IfcVector3());
continue;
}
nors.push_back(((bounds.mVerts[2] - bounds.mVerts[0]) ^ (bounds.mVerts[1] - bounds.mVerts[0])).Normalize());
}
}
TempMesh temp;
TempMesh& curmesh = openings ? temp : result;
std::vector<IfcVector3>& out = curmesh.mVerts;
size_t sides_with_openings = 0;
for( size_t i = 0; i < in.size(); ++i ) {
const size_t next = (i + 1) % in.size();
curmesh.mVertcnt.push_back(4);
out.push_back(in[i]);
out.push_back(in[next]);
out.push_back(in[next] + dir);
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) ) {
++sides_with_openings;
}
result.Append(temp);
temp.Clear();
}
}
if( openings ) {
for(TempOpening& opening : *conv.apply_openings) {
if( !opening.wallPoints.empty() ) {
IFCImporter::LogError("failed to generate all window caps");
}
opening.wallPoints.clear();
}
}
size_t sides_with_v_openings = 0;
if( has_area ) {
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--; )
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) ) {
++sides_with_v_openings;
}
result.Append(temp);
temp.Clear();
}
}
}
if( openings && ((sides_with_openings == 1 && sides_with_openings) || (sides_with_v_openings == 2 && sides_with_v_openings)) ) {
IFCImporter::LogWarn("failed to resolve all openings, presumably their topology is not supported by Assimp");
}
IFCImporter::LogDebug("generate mesh procedurally by extrusion (IfcExtrudedAreaSolid)");
// If this is an opening element, store both the extruded mesh and the 2D profile mesh
// it was created from. Return an empty mesh to the caller.
if( collect_openings && !result.IsEmpty() ) {
ai_assert(conv.collect_openings);
std::shared_ptr<TempMesh> profile = std::shared_ptr<TempMesh>(new TempMesh());
profile->Swap(result);
std::shared_ptr<TempMesh> profile2D = std::shared_ptr<TempMesh>(new TempMesh());
profile2D->mVerts.insert(profile2D->mVerts.end(), in.begin(), in.end());
profile2D->mVertcnt.push_back(static_cast<unsigned int>(in.size()));
conv.collect_openings->push_back(TempOpening(&solid, dir, profile, profile2D));
ai_assert(result.IsEmpty());
}
}
// ------------------------------------------------------------------------------------------------
void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, TempMesh& result,
ConversionData& conv, bool collect_openings)
{
TempMesh meshout;
// First read the profile description.
if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.mVerts.size()<=1) {
return;
}
IfcVector3 dir;
ConvertDirection(dir,solid.ExtrudedDirection);
dir *= solid.Depth;
// Some profiles bring their own holes, for which we need to provide a container. This all is somewhat backwards,
// and there's still so many corner cases uncovered - we really need a generic solution to all of this hole carving.
std::vector<TempOpening> fisherPriceMyFirstOpenings;
std::vector<TempOpening>* oldApplyOpenings = conv.apply_openings;
if( const Schema_2x3::IfcArbitraryProfileDefWithVoids* const cprofile = solid.SweptArea->ToPtr<Schema_2x3::IfcArbitraryProfileDefWithVoids>() ) {
if( !cprofile->InnerCurves.empty() ) {
// read all inner curves and extrude them to form proper openings.
std::vector<TempOpening>* oldCollectOpenings = conv.collect_openings;
conv.collect_openings = &fisherPriceMyFirstOpenings;
for (const Schema_2x3::IfcCurve* curve : cprofile->InnerCurves) {
TempMesh curveMesh, tempMesh;
ProcessCurve(*curve, curveMesh, conv);
ProcessExtrudedArea(solid, curveMesh, dir, tempMesh, conv, true);
}
// and then apply those to the geometry we're about to generate
conv.apply_openings = conv.collect_openings;
conv.collect_openings = oldCollectOpenings;
}
}
ProcessExtrudedArea(solid, meshout, dir, result, conv, collect_openings);
conv.apply_openings = oldApplyOpenings;
}
// ------------------------------------------------------------------------------------------------
void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, TempMesh& meshout,
ConversionData& conv)
{
if(const Schema_2x3::IfcExtrudedAreaSolid* const solid = swept.ToPtr<Schema_2x3::IfcExtrudedAreaSolid>()) {
ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings);
}
else if(const Schema_2x3::IfcRevolvedAreaSolid* const rev = swept.ToPtr<Schema_2x3::IfcRevolvedAreaSolid>()) {
ProcessRevolvedAreaSolid(*rev,meshout,conv);
}
else {
IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is " + swept.GetClassName());
}
}
// ------------------------------------------------------------------------------------------------
bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned int matid, std::set<unsigned int>& mesh_indices,
ConversionData& conv)
{
bool fix_orientation = false;
std::shared_ptr< TempMesh > meshtmp = std::make_shared<TempMesh>();
if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr<Schema_2x3::IfcShellBasedSurfaceModel>()) {
for(std::shared_ptr<const Schema_2x3::IfcShell> shell :shellmod->SbsmBoundary) {
try {
const ::Assimp::STEP::EXPRESS::ENTITY& e = shell->To<::Assimp::STEP::EXPRESS::ENTITY>();
const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To<Schema_2x3::IfcConnectedFaceSet>();
ProcessConnectedFaceSet(fs,*meshtmp.get(),conv);
}
catch(std::bad_cast&) {
IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet");
}
}
fix_orientation = true;
}
else if(const Schema_2x3::IfcConnectedFaceSet* fset = geo.ToPtr<Schema_2x3::IfcConnectedFaceSet>()) {
ProcessConnectedFaceSet(*fset,*meshtmp.get(),conv);
fix_orientation = true;
}
else if(const Schema_2x3::IfcSweptAreaSolid* swept = geo.ToPtr<Schema_2x3::IfcSweptAreaSolid>()) {
ProcessSweptAreaSolid(*swept,*meshtmp.get(),conv);
}
else if(const Schema_2x3::IfcSweptDiskSolid* disk = geo.ToPtr<Schema_2x3::IfcSweptDiskSolid>()) {
ProcessSweptDiskSolid(*disk,*meshtmp.get(),conv);
}
else if(const Schema_2x3::IfcManifoldSolidBrep* brep = geo.ToPtr<Schema_2x3::IfcManifoldSolidBrep>()) {
ProcessConnectedFaceSet(brep->Outer,*meshtmp.get(),conv);
fix_orientation = true;
}
else if(const Schema_2x3::IfcFaceBasedSurfaceModel* surf = geo.ToPtr<Schema_2x3::IfcFaceBasedSurfaceModel>()) {
for(const Schema_2x3::IfcConnectedFaceSet& fc : surf->FbsmFaces) {
ProcessConnectedFaceSet(fc,*meshtmp.get(),conv);
}
fix_orientation = true;
}
else if(const Schema_2x3::IfcBooleanResult* boolean = geo.ToPtr<Schema_2x3::IfcBooleanResult>()) {
ProcessBoolean(*boolean,*meshtmp.get(),conv);
}
else if(geo.ToPtr<Schema_2x3::IfcBoundingBox>()) {
// silently skip over bounding boxes
return false;
}
else {
IFCImporter::LogWarn("skipping unknown IfcGeometricRepresentationItem entity, type is " + geo.GetClassName());
return false;
}
// Do we just collect openings for a parent element (i.e. a wall)?
// In such a case, we generate the polygonal mesh as usual,
// but attach it to a TempOpening instance which will later be applied
// to the wall it pertains to.
// Note: swep area solids are added in ProcessExtrudedAreaSolid(),
// which returns an empty mesh.
if(conv.collect_openings) {
if (!meshtmp->IsEmpty()) {
conv.collect_openings->push_back(TempOpening(geo.ToPtr<Schema_2x3::IfcSolidModel>(),
IfcVector3(0,0,0),
meshtmp,
std::shared_ptr<TempMesh>()));
}
return true;
}
if (meshtmp->IsEmpty()) {
return false;
}
meshtmp->RemoveAdjacentDuplicates();
meshtmp->RemoveDegenerates();
if(fix_orientation) {
// meshtmp->FixupFaceOrientation();
}
aiMesh* const mesh = meshtmp->ToMesh();
if(mesh) {
mesh->mMaterialIndex = matid;
mesh_indices.insert(static_cast<unsigned int>(conv.meshes.size()));
conv.meshes.push_back(mesh);
return true;
}
return false;
}
// ------------------------------------------------------------------------------------------------
void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd,
ConversionData& /*conv*/)
{
if (!mesh_indices.empty()) {
std::set<unsigned int>::const_iterator it = mesh_indices.cbegin();
std::set<unsigned int>::const_iterator end = mesh_indices.cend();
nd->mNumMeshes = static_cast<unsigned int>(mesh_indices.size());
nd->mMeshes = new unsigned int[nd->mNumMeshes];
for(unsigned int i = 0; it != end && i < nd->mNumMeshes; ++i, ++it) {
nd->mMeshes[i] = *it;
}
}
}
// ------------------------------------------------------------------------------------------------
bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item,
std::set<unsigned int>& mesh_indices, unsigned int mat_index,
ConversionData& conv)
{
ConversionData::MeshCacheIndex idx(&item, mat_index);
ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(idx);
if (it != conv.cached_meshes.end()) {
std::copy((*it).second.begin(),(*it).second.end(),std::inserter(mesh_indices, mesh_indices.end()));
return true;
}
return false;
}
// ------------------------------------------------------------------------------------------------
void PopulateMeshCache(const Schema_2x3::IfcRepresentationItem& item,
const std::set<unsigned int>& mesh_indices, unsigned int mat_index,
ConversionData& conv)
{
ConversionData::MeshCacheIndex idx(&item, mat_index);
conv.cached_meshes[idx] = mesh_indices;
}
// ------------------------------------------------------------------------------------------------
bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, unsigned int matid,
std::set<unsigned int>& mesh_indices,
ConversionData& conv)
{
// determine material
unsigned int localmatid = ProcessMaterials(item.GetID(), matid, conv, true);
if (!TryQueryMeshCache(item,mesh_indices,localmatid,conv)) {
if(ProcessGeometricItem(item,localmatid,mesh_indices,conv)) {
if(mesh_indices.size()) {
PopulateMeshCache(item,mesh_indices,localmatid,conv);
}
}
else return false;
}
return true;
}
} // ! IFC
} // ! Assimp
#endif

View File

@@ -1,988 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file IFCLoad.cpp
* @brief Implementation of the Industry Foundation Classes loader.
*/
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
#include <iterator>
#include <limits>
#include <tuple>
#ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC
# ifdef ASSIMP_USE_HUNTER
# include <minizip/unzip.h>
# else
# include <unzip.h>
# endif
#endif
#include "IFCLoader.h"
#include "../STEPParser/STEPFileReader.h"
#include "IFCUtil.h"
#include <assimp/MemoryIOWrapper.h>
#include <assimp/scene.h>
#include <assimp/Importer.hpp>
#include <assimp/importerdesc.h>
namespace Assimp {
template<> const char* LogFunctions<IFCImporter>::Prefix()
{
static auto prefix = "IFC: ";
return prefix;
}
}
using namespace Assimp;
using namespace Assimp::Formatter;
using namespace Assimp::IFC;
/* DO NOT REMOVE this comment block. The genentitylist.sh script
* just looks for names adhering to the IfcSomething naming scheme
* and includes all matches in the whitelist for code-generation. Thus,
* all entity classes that are only indirectly referenced need to be
* mentioned explicitly.
IfcRepresentationMap
IfcProductRepresentation
IfcUnitAssignment
IfcClosedShell
IfcDoor
*/
namespace {
// forward declarations
void SetUnits(ConversionData& conv);
void SetCoordinateSpace(ConversionData& conv);
void ProcessSpatialStructures(ConversionData& conv);
void MakeTreeRelative(ConversionData& conv);
void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType& dt,ConversionData& conv);
} // anon
static const aiImporterDesc desc = {
"Industry Foundation Classes (IFC) Importer",
"",
"",
"",
aiImporterFlags_SupportBinaryFlavour,
0,
0,
0,
0,
"ifc ifczip stp"
};
// ------------------------------------------------------------------------------------------------
// Constructor to be privately used by Importer
IFCImporter::IFCImporter()
{}
// ------------------------------------------------------------------------------------------------
// Destructor, private as well
IFCImporter::~IFCImporter()
{
}
// ------------------------------------------------------------------------------------------------
// Returns whether the class can handle the format of the given file.
bool IFCImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
{
const std::string& extension = GetExtension(pFile);
if (extension == "ifc" || extension == "ifczip" ) {
return true;
} else if ((!extension.length() || checkSig) && pIOHandler) {
// note: this is the common identification for STEP-encoded files, so
// it is only unambiguous as long as we don't support any further
// file formats with STEP as their encoding.
const char* tokens[] = {"ISO-10303-21"};
const bool found( SearchFileHeaderForToken( pIOHandler, pFile, tokens, 1 ) );
return found;
}
return false;
}
// ------------------------------------------------------------------------------------------------
// List all extensions handled by this loader
const aiImporterDesc* IFCImporter::GetInfo () const
{
return &desc;
}
// ------------------------------------------------------------------------------------------------
// Setup configuration properties for the loader
void IFCImporter::SetupProperties(const Importer* pImp)
{
settings.skipSpaceRepresentations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS,true);
settings.useCustomTriangulation = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_CUSTOM_TRIANGULATION,true);
settings.conicSamplingAngle = std::min(std::max((float) pImp->GetPropertyFloat(AI_CONFIG_IMPORT_IFC_SMOOTHING_ANGLE, AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE), 5.0f), 120.0f);
settings.cylindricalTessellation = std::min(std::max(pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IFC_CYLINDRICAL_TESSELLATION, AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION), 3), 180);
settings.skipAnnotations = true;
}
// ------------------------------------------------------------------------------------------------
// Imports the given file into the given scene structure.
void IFCImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
{
std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile));
if (!stream) {
ThrowException("Could not open file for reading");
}
// if this is a ifczip file, decompress its contents first
if(GetExtension(pFile) == "ifczip") {
#ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC
unzFile zip = unzOpen( pFile.c_str() );
if(zip == NULL) {
ThrowException("Could not open ifczip file for reading, unzip failed");
}
// chop 'zip' postfix
std::string fileName = pFile.substr(0,pFile.length() - 3);
std::string::size_type s = pFile.find_last_of('\\');
if(s == std::string::npos) {
s = pFile.find_last_of('/');
}
if(s != std::string::npos) {
fileName = fileName.substr(s+1);
}
// search file (same name as the IFCZIP except for the file extension) and place file pointer there
if(UNZ_OK == unzGoToFirstFile(zip)) {
do {
// get file size, etc.
unz_file_info fileInfo;
char filename[256];
unzGetCurrentFileInfo( zip , &fileInfo, filename, sizeof(filename), 0, 0, 0, 0 );
if (GetExtension(filename) != "ifc") {
continue;
}
uint8_t* buff = new uint8_t[fileInfo.uncompressed_size];
LogInfo("Decompressing IFCZIP file");
unzOpenCurrentFile(zip);
size_t total = 0;
int read = 0;
do {
int bufferSize = fileInfo.uncompressed_size < INT16_MAX ? fileInfo.uncompressed_size : INT16_MAX;
void* buffer = malloc(bufferSize);
read = unzReadCurrentFile(zip, buffer, bufferSize);
if (read > 0) {
memcpy((char*)buff + total, buffer, read);
total += read;
}
free(buffer);
} while (read > 0);
size_t filesize = fileInfo.uncompressed_size;
if (total == 0 || size_t(total) != filesize)
{
delete[] buff;
ThrowException("Failed to decompress IFC ZIP file");
}
unzCloseCurrentFile( zip );
stream.reset(new MemoryIOStream(buff,fileInfo.uncompressed_size,true));
break;
if (unzGoToNextFile(zip) == UNZ_END_OF_LIST_OF_FILE) {
ThrowException("Found no IFC file member in IFCZIP file (1)");
}
} while(true);
}
else {
ThrowException("Found no IFC file member in IFCZIP file (2)");
}
unzClose(zip);
#else
ThrowException("Could not open ifczip file for reading, assimp was built without ifczip support");
#endif
}
std::unique_ptr<STEP::DB> db(STEP::ReadFileHeader(stream));
const STEP::HeaderInfo& head = static_cast<const STEP::DB&>(*db).GetHeader();
if(!head.fileSchema.size() || head.fileSchema.substr(0,3) != "IFC") {
ThrowException("Unrecognized file schema: " + head.fileSchema);
}
if (!DefaultLogger::isNullLogger()) {
LogDebug("File schema is \'" + head.fileSchema + '\'');
if (head.timestamp.length()) {
LogDebug("Timestamp \'" + head.timestamp + '\'');
}
if (head.app.length()) {
LogDebug("Application/Exporter identline is \'" + head.app + '\'');
}
}
// obtain a copy of the machine-generated IFC scheme
::Assimp::STEP::EXPRESS::ConversionSchema schema;
Schema_2x3::GetSchema(schema);
// tell the reader which entity types to track with special care
static const char* const types_to_track[] = {
"ifcsite", "ifcbuilding", "ifcproject"
};
// tell the reader for which types we need to simulate STEPs reverse indices
static const char* const inverse_indices_to_track[] = {
"ifcrelcontainedinspatialstructure", "ifcrelaggregates", "ifcrelvoidselement", "ifcreldefinesbyproperties", "ifcpropertyset", "ifcstyleditem"
};
// feed the IFC schema into the reader and pre-parse all lines
STEP::ReadFile(*db, schema, types_to_track, inverse_indices_to_track);
const STEP::LazyObject* proj = db->GetObject("ifcproject");
if (!proj) {
ThrowException("missing IfcProject entity");
}
ConversionData conv(*db,proj->To<Schema_2x3::IfcProject>(),pScene,settings);
SetUnits(conv);
SetCoordinateSpace(conv);
ProcessSpatialStructures(conv);
MakeTreeRelative(conv);
// NOTE - this is a stress test for the importer, but it works only
// in a build with no entities disabled. See
// scripts/IFCImporter/CPPGenerator.py
// for more information.
#ifdef ASSIMP_IFC_TEST
db->EvaluateAll();
#endif
// do final data copying
if (conv.meshes.size()) {
pScene->mNumMeshes = static_cast<unsigned int>(conv.meshes.size());
pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]();
std::copy(conv.meshes.begin(),conv.meshes.end(),pScene->mMeshes);
// needed to keep the d'tor from burning us
conv.meshes.clear();
}
if (conv.materials.size()) {
pScene->mNumMaterials = static_cast<unsigned int>(conv.materials.size());
pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]();
std::copy(conv.materials.begin(),conv.materials.end(),pScene->mMaterials);
// needed to keep the d'tor from burning us
conv.materials.clear();
}
// apply world coordinate system (which includes the scaling to convert to meters and a -90 degrees rotation around x)
aiMatrix4x4 scale, rot;
aiMatrix4x4::Scaling(static_cast<aiVector3D>(IfcVector3(conv.len_scale)),scale);
aiMatrix4x4::RotationX(-AI_MATH_HALF_PI_F,rot);
pScene->mRootNode->mTransformation = rot * scale * conv.wcs * pScene->mRootNode->mTransformation;
// this must be last because objects are evaluated lazily as we process them
if ( !DefaultLogger::isNullLogger() ){
LogDebug((Formatter::format(),"STEP: evaluated ",db->GetEvaluatedObjectCount()," object records"));
}
}
namespace {
// ------------------------------------------------------------------------------------------------
void ConvertUnit(const Schema_2x3::IfcNamedUnit& unit,ConversionData& conv)
{
if(const Schema_2x3::IfcSIUnit* const si = unit.ToPtr<Schema_2x3::IfcSIUnit>()) {
if(si->UnitType == "LENGTHUNIT") {
conv.len_scale = si->Prefix ? ConvertSIPrefix(si->Prefix) : 1.f;
IFCImporter::LogDebug("got units used for lengths");
}
if(si->UnitType == "PLANEANGLEUNIT") {
if (si->Name != "RADIAN") {
IFCImporter::LogWarn("expected base unit for angles to be radian");
}
}
}
else if(const Schema_2x3::IfcConversionBasedUnit* const convu = unit.ToPtr<Schema_2x3::IfcConversionBasedUnit>()) {
if(convu->UnitType == "PLANEANGLEUNIT") {
try {
conv.angle_scale = convu->ConversionFactor->ValueComponent->To<::Assimp::STEP::EXPRESS::REAL>();
ConvertUnit(*convu->ConversionFactor->UnitComponent,conv);
IFCImporter::LogDebug("got units used for angles");
}
catch(std::bad_cast&) {
IFCImporter::LogError("skipping unknown IfcConversionBasedUnit.ValueComponent entry - expected REAL");
}
}
}
}
// ------------------------------------------------------------------------------------------------
void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType& dt,ConversionData& conv)
{
try {
const ::Assimp::STEP::EXPRESS::ENTITY& e = dt.To<::Assimp::STEP::EXPRESS::ENTITY>();
const Schema_2x3::IfcNamedUnit& unit = e.ResolveSelect<Schema_2x3::IfcNamedUnit>(conv.db);
if(unit.UnitType != "LENGTHUNIT" && unit.UnitType != "PLANEANGLEUNIT") {
return;
}
ConvertUnit(unit,conv);
}
catch(std::bad_cast&) {
// not entity, somehow
IFCImporter::LogError("skipping unknown IfcUnit entry - expected entity");
}
}
// ------------------------------------------------------------------------------------------------
void SetUnits(ConversionData& conv)
{
// see if we can determine the coordinate space used to express.
for(size_t i = 0; i < conv.proj.UnitsInContext->Units.size(); ++i ) {
ConvertUnit(*conv.proj.UnitsInContext->Units[i],conv);
}
}
// ------------------------------------------------------------------------------------------------
void SetCoordinateSpace(ConversionData& conv)
{
const Schema_2x3::IfcRepresentationContext* fav = NULL;
for(const Schema_2x3::IfcRepresentationContext& v : conv.proj.RepresentationContexts) {
fav = &v;
// Model should be the most suitable type of context, hence ignore the others
if (v.ContextType && v.ContextType.Get() == "Model") {
break;
}
}
if (fav) {
if(const Schema_2x3::IfcGeometricRepresentationContext* const geo = fav->ToPtr<Schema_2x3::IfcGeometricRepresentationContext>()) {
ConvertAxisPlacement(conv.wcs, *geo->WorldCoordinateSystem, conv);
IFCImporter::LogDebug("got world coordinate system");
}
}
}
// ------------------------------------------------------------------------------------------------
void ResolveObjectPlacement(aiMatrix4x4& m, const Schema_2x3::IfcObjectPlacement& place, ConversionData& conv)
{
if (const Schema_2x3::IfcLocalPlacement* const local = place.ToPtr<Schema_2x3::IfcLocalPlacement>()){
IfcMatrix4 tmp;
ConvertAxisPlacement(tmp, *local->RelativePlacement, conv);
m = static_cast<aiMatrix4x4>(tmp);
if (local->PlacementRelTo) {
aiMatrix4x4 tmp;
ResolveObjectPlacement(tmp,local->PlacementRelTo.Get(),conv);
m = tmp * m;
}
}
else {
IFCImporter::LogWarn("skipping unknown IfcObjectPlacement entity, type is " + place.GetClassName());
}
}
// ------------------------------------------------------------------------------------------------
bool ProcessMappedItem(const Schema_2x3::IfcMappedItem& mapped, aiNode* nd_src, std::vector< aiNode* >& subnodes_src, unsigned int matid, ConversionData& conv)
{
// insert a custom node here, the carthesian transform operator is simply a conventional transformation matrix
std::unique_ptr<aiNode> nd(new aiNode());
nd->mName.Set("IfcMappedItem");
// handle the Cartesian operator
IfcMatrix4 m;
ConvertTransformOperator(m, *mapped.MappingTarget);
IfcMatrix4 msrc;
ConvertAxisPlacement(msrc,*mapped.MappingSource->MappingOrigin,conv);
msrc = m*msrc;
std::set<unsigned int> meshes;
const size_t old_openings = conv.collect_openings ? conv.collect_openings->size() : 0;
if (conv.apply_openings) {
IfcMatrix4 minv = msrc;
minv.Inverse();
for(TempOpening& open :*conv.apply_openings){
open.Transform(minv);
}
}
unsigned int localmatid = ProcessMaterials(mapped.GetID(),matid,conv,false);
const Schema_2x3::IfcRepresentation& repr = mapped.MappingSource->MappedRepresentation;
bool got = false;
for(const Schema_2x3::IfcRepresentationItem& item : repr.Items) {
if(!ProcessRepresentationItem(item,localmatid,meshes,conv)) {
IFCImporter::LogWarn("skipping mapped entity of type " + item.GetClassName() + ", no representations could be generated");
}
else got = true;
}
if (!got) {
return false;
}
AssignAddedMeshes(meshes,nd.get(),conv);
if (conv.collect_openings) {
// if this pass serves us only to collect opening geometry,
// make sure we transform the TempMesh's which we need to
// preserve as well.
if(const size_t diff = conv.collect_openings->size() - old_openings) {
for(size_t i = 0; i < diff; ++i) {
(*conv.collect_openings)[old_openings+i].Transform(msrc);
}
}
}
nd->mTransformation = nd_src->mTransformation * static_cast<aiMatrix4x4>( msrc );
subnodes_src.push_back(nd.release());
return true;
}
// ------------------------------------------------------------------------------------------------
struct RateRepresentationPredicate {
int Rate(const Schema_2x3::IfcRepresentation* r) const {
// the smaller, the better
if (! r->RepresentationIdentifier) {
// neutral choice if no extra information is specified
return 0;
}
const std::string& name = r->RepresentationIdentifier.Get();
if (name == "MappedRepresentation") {
if (!r->Items.empty()) {
// take the first item and base our choice on it
const Schema_2x3::IfcMappedItem* const m = r->Items.front()->ToPtr<Schema_2x3::IfcMappedItem>();
if (m) {
return Rate(m->MappingSource->MappedRepresentation);
}
}
return 100;
}
return Rate(name);
}
int Rate(const std::string& r) const {
if (r == "SolidModel") {
return -3;
}
// give strong preference to extruded geometry.
if (r == "SweptSolid") {
return -10;
}
if (r == "Clipping") {
return -5;
}
// 'Brep' is difficult to get right due to possible voids in the
// polygon boundaries, so take it only if we are forced to (i.e.
// if the only alternative is (non-clipping) boolean operations,
// which are not supported at all).
if (r == "Brep") {
return -2;
}
// Curves, bounding boxes - those will most likely not be loaded
// as we can't make any use out of this data. So consider them
// last.
if (r == "BoundingBox" || r == "Curve2D") {
return 100;
}
return 0;
}
bool operator() (const Schema_2x3::IfcRepresentation* a, const Schema_2x3::IfcRepresentation* b) const {
return Rate(a) < Rate(b);
}
};
// ------------------------------------------------------------------------------------------------
void ProcessProductRepresentation(const Schema_2x3::IfcProduct& el, aiNode* nd, std::vector< aiNode* >& subnodes, ConversionData& conv)
{
if(!el.Representation) {
return;
}
// extract Color from metadata, if present
unsigned int matid = ProcessMaterials( el.GetID(), std::numeric_limits<uint32_t>::max(), conv, false);
std::set<unsigned int> meshes;
// we want only one representation type, so bring them in a suitable order (i.e try those
// that look as if we could read them quickly at first). This way of reading
// representation is relatively generic and allows the concrete implementations
// for the different representation types to make some sensible choices what
// to load and what not to load.
const STEP::ListOf< STEP::Lazy< Schema_2x3::IfcRepresentation >, 1, 0 >& src = el.Representation.Get()->Representations;
std::vector<const Schema_2x3::IfcRepresentation*> repr_ordered(src.size());
std::copy(src.begin(),src.end(),repr_ordered.begin());
std::sort(repr_ordered.begin(),repr_ordered.end(),RateRepresentationPredicate());
for(const Schema_2x3::IfcRepresentation* repr : repr_ordered) {
bool res = false;
for(const Schema_2x3::IfcRepresentationItem& item : repr->Items) {
if(const Schema_2x3::IfcMappedItem* const geo = item.ToPtr<Schema_2x3::IfcMappedItem>()) {
res = ProcessMappedItem(*geo,nd,subnodes,matid,conv) || res;
}
else {
res = ProcessRepresentationItem(item,matid,meshes,conv) || res;
}
}
// if we got something meaningful at this point, skip any further representations
if(res) {
break;
}
}
AssignAddedMeshes(meshes,nd,conv);
}
typedef std::map<std::string, std::string> Metadata;
// ------------------------------------------------------------------------------------------------
void ProcessMetadata(const Schema_2x3::ListOf< Schema_2x3::Lazy< Schema_2x3::IfcProperty >, 1, 0 >& set, ConversionData& conv, Metadata& properties,
const std::string& prefix = "",
unsigned int nest = 0) {
for(const Schema_2x3::IfcProperty& property : set) {
const std::string& key = prefix.length() > 0 ? (prefix + "." + property.Name) : property.Name;
if (const Schema_2x3::IfcPropertySingleValue* const singleValue = property.ToPtr<Schema_2x3::IfcPropertySingleValue>()) {
if (singleValue->NominalValue) {
if (const ::Assimp::STEP::EXPRESS::STRING* str = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::STRING>()) {
std::string value = static_cast<std::string>(*str);
properties[key]=value;
}
else if (const ::Assimp::STEP::EXPRESS::REAL* val = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
float value = static_cast<float>(*val);
std::stringstream s;
s << value;
properties[key]=s.str();
}
else if (const ::Assimp::STEP::EXPRESS::INTEGER* val = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::INTEGER>()) {
int64_t value = static_cast<int64_t>(*val);
std::stringstream s;
s << value;
properties[key]=s.str();
}
}
}
else if (const Schema_2x3::IfcPropertyListValue* const listValue = property.ToPtr<Schema_2x3::IfcPropertyListValue>()) {
std::stringstream ss;
ss << "[";
unsigned index=0;
for(const Schema_2x3::IfcValue::Out& v : listValue->ListValues) {
if (!v) continue;
if (const ::Assimp::STEP::EXPRESS::STRING* str = v->ToPtr<::Assimp::STEP::EXPRESS::STRING>()) {
std::string value = static_cast<std::string>(*str);
ss << "'" << value << "'";
}
else if (const ::Assimp::STEP::EXPRESS::REAL* val = v->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
float value = static_cast<float>(*val);
ss << value;
}
else if (const ::Assimp::STEP::EXPRESS::INTEGER* val = v->ToPtr<::Assimp::STEP::EXPRESS::INTEGER>()) {
int64_t value = static_cast<int64_t>(*val);
ss << value;
}
if (index+1<listValue->ListValues.size()) {
ss << ",";
}
index++;
}
ss << "]";
properties[key]=ss.str();
}
else if (const Schema_2x3::IfcComplexProperty* const complexProp = property.ToPtr<Schema_2x3::IfcComplexProperty>()) {
if(nest > 2) { // mostly arbitrary limit to prevent stack overflow vulnerabilities
IFCImporter::LogError("maximum nesting level for IfcComplexProperty reached, skipping this property.");
}
else {
ProcessMetadata(complexProp->HasProperties, conv, properties, key, nest + 1);
}
}
else {
properties[key]="";
}
}
}
// ------------------------------------------------------------------------------------------------
void ProcessMetadata(uint64_t relDefinesByPropertiesID, ConversionData& conv, Metadata& properties)
{
if (const Schema_2x3::IfcRelDefinesByProperties* const pset = conv.db.GetObject(relDefinesByPropertiesID)->ToPtr<Schema_2x3::IfcRelDefinesByProperties>()) {
if (const Schema_2x3::IfcPropertySet* const set = conv.db.GetObject(pset->RelatingPropertyDefinition->GetID())->ToPtr<Schema_2x3::IfcPropertySet>()) {
ProcessMetadata(set->HasProperties, conv, properties);
}
}
}
// ------------------------------------------------------------------------------------------------
aiNode* ProcessSpatialStructure(aiNode* parent, const Schema_2x3::IfcProduct& el, ConversionData& conv,
std::vector<TempOpening>* collect_openings = nullptr ) {
const STEP::DB::RefMap& refs = conv.db.GetRefs();
// skip over space and annotation nodes - usually, these have no meaning in Assimp's context
bool skipGeometry = false;
if(conv.settings.skipSpaceRepresentations) {
if(el.ToPtr<Schema_2x3::IfcSpace>()) {
IFCImporter::LogDebug("skipping IfcSpace entity due to importer settings");
skipGeometry = true;
}
}
if(conv.settings.skipAnnotations) {
if(el.ToPtr<Schema_2x3::IfcAnnotation>()) {
IFCImporter::LogDebug("skipping IfcAnnotation entity due to importer settings");
return nullptr;
}
}
// add an output node for this spatial structure
aiNode *nd(new aiNode );
nd->mName.Set(el.GetClassName()+"_"+(el.Name?el.Name.Get():"Unnamed")+"_"+el.GlobalId);
nd->mParent = parent;
conv.already_processed.insert(el.GetID());
// check for node metadata
STEP::DB::RefMapRange children = refs.equal_range(el.GetID());
if (children.first!=refs.end()) {
Metadata properties;
if (children.first==children.second) {
// handles single property set
ProcessMetadata((*children.first).second, conv, properties);
} else {
// handles multiple property sets (currently all property sets are merged,
// which may not be the best solution in the long run)
for (STEP::DB::RefMap::const_iterator it=children.first; it!=children.second; ++it) {
ProcessMetadata((*it).second, conv, properties);
}
}
if (!properties.empty()) {
aiMetadata* data = aiMetadata::Alloc( static_cast<unsigned int>(properties.size()) );
unsigned int index( 0 );
for ( const Metadata::value_type& kv : properties ) {
data->Set( index++, kv.first, aiString( kv.second ) );
}
nd->mMetaData = data;
}
}
if(el.ObjectPlacement) {
ResolveObjectPlacement(nd->mTransformation,el.ObjectPlacement.Get(),conv);
}
std::vector<TempOpening> openings;
IfcMatrix4 myInv;
bool didinv = false;
// convert everything contained directly within this structure,
// this may result in more nodes.
std::vector< aiNode* > subnodes;
try {
// locate aggregates and 'contained-in-here'-elements of this spatial structure and add them in recursively
// on our way, collect openings in *this* element
STEP::DB::RefMapRange range = refs.equal_range(el.GetID());
for(STEP::DB::RefMapRange range2 = range; range2.first != range.second; ++range2.first) {
// skip over meshes that have already been processed before. This is strictly necessary
// because the reverse indices also include references contained in argument lists and
// therefore every element has a back-reference hold by its parent.
if (conv.already_processed.find((*range2.first).second) != conv.already_processed.end()) {
continue;
}
const STEP::LazyObject& obj = conv.db.MustGetObject((*range2.first).second);
// handle regularly-contained elements
if(const Schema_2x3::IfcRelContainedInSpatialStructure* const cont = obj->ToPtr<Schema_2x3::IfcRelContainedInSpatialStructure>()) {
if(cont->RelatingStructure->GetID() != el.GetID()) {
continue;
}
for(const Schema_2x3::IfcProduct& pro : cont->RelatedElements) {
if(pro.ToPtr<Schema_2x3::IfcOpeningElement>()) {
// IfcOpeningElement is handled below. Sadly we can't use it here as is:
// The docs say that opening elements are USUALLY attached to building storey,
// but we want them for the building elements to which they belong.
continue;
}
aiNode* const ndnew = ProcessSpatialStructure(nd,pro,conv,nullptr);
if(ndnew) {
subnodes.push_back( ndnew );
}
}
}
// handle openings, which we collect in a list rather than adding them to the node graph
else if(const Schema_2x3::IfcRelVoidsElement* const fills = obj->ToPtr<Schema_2x3::IfcRelVoidsElement>()) {
if(fills->RelatingBuildingElement->GetID() == el.GetID()) {
const Schema_2x3::IfcFeatureElementSubtraction& open = fills->RelatedOpeningElement;
// move opening elements to a separate node since they are semantically different than elements that are just 'contained'
std::unique_ptr<aiNode> nd_aggr(new aiNode());
nd_aggr->mName.Set("$RelVoidsElement");
nd_aggr->mParent = nd;
nd_aggr->mTransformation = nd->mTransformation;
std::vector<TempOpening> openings_local;
aiNode* const ndnew = ProcessSpatialStructure( nd_aggr.get(),open, conv,&openings_local);
if (ndnew) {
nd_aggr->mNumChildren = 1;
nd_aggr->mChildren = new aiNode*[1]();
nd_aggr->mChildren[0] = ndnew;
if(openings_local.size()) {
if (!didinv) {
myInv = aiMatrix4x4(nd->mTransformation ).Inverse();
didinv = true;
}
// we need all openings to be in the local space of *this* node, so transform them
for(TempOpening& op :openings_local) {
op.Transform( myInv*nd_aggr->mChildren[0]->mTransformation);
openings.push_back(op);
}
}
subnodes.push_back( nd_aggr.release() );
}
}
}
}
for(;range.first != range.second; ++range.first) {
// see note in loop above
if (conv.already_processed.find((*range.first).second) != conv.already_processed.end()) {
continue;
}
if(const Schema_2x3::IfcRelAggregates* const aggr = conv.db.GetObject((*range.first).second)->ToPtr<Schema_2x3::IfcRelAggregates>()) {
if(aggr->RelatingObject->GetID() != el.GetID()) {
continue;
}
// move aggregate elements to a separate node since they are semantically different than elements that are just 'contained'
std::unique_ptr<aiNode> nd_aggr(new aiNode());
nd_aggr->mName.Set("$RelAggregates");
nd_aggr->mParent = nd;
nd_aggr->mTransformation = nd->mTransformation;
nd_aggr->mChildren = new aiNode*[aggr->RelatedObjects.size()]();
for(const Schema_2x3::IfcObjectDefinition& def : aggr->RelatedObjects) {
if(const Schema_2x3::IfcProduct* const prod = def.ToPtr<Schema_2x3::IfcProduct>()) {
aiNode* const ndnew = ProcessSpatialStructure(nd_aggr.get(),*prod,conv,NULL);
if(ndnew) {
nd_aggr->mChildren[nd_aggr->mNumChildren++] = ndnew;
}
}
}
subnodes.push_back( nd_aggr.release() );
}
}
conv.collect_openings = collect_openings;
if(!conv.collect_openings) {
conv.apply_openings = &openings;
}
if (!skipGeometry) {
ProcessProductRepresentation(el, nd, subnodes, conv);
conv.apply_openings = conv.collect_openings = nullptr;
}
if (subnodes.size()) {
nd->mChildren = new aiNode*[subnodes.size()]();
for(aiNode* nd2 : subnodes) {
nd->mChildren[nd->mNumChildren++] = nd2;
nd2->mParent = nd;
}
}
} catch(...) {
// it hurts, but I don't want to pull boost::ptr_vector into -noboost only for these few spots here
std::for_each(subnodes.begin(),subnodes.end(),delete_fun<aiNode>());
throw;
}
ai_assert(conv.already_processed.find(el.GetID()) != conv.already_processed.end());
conv.already_processed.erase(conv.already_processed.find(el.GetID()));
return nd;
}
// ------------------------------------------------------------------------------------------------
void ProcessSpatialStructures(ConversionData& conv)
{
// XXX add support for multiple sites (i.e. IfcSpatialStructureElements with composition == COMPLEX)
// process all products in the file. it is reasonable to assume that a
// file that is relevant for us contains at least a site or a building.
const STEP::DB::ObjectMapByType& map = conv.db.GetObjectsByType();
ai_assert(map.find("ifcsite") != map.end());
const STEP::DB::ObjectSet* range = &map.find("ifcsite")->second;
if (range->empty()) {
ai_assert(map.find("ifcbuilding") != map.end());
range = &map.find("ifcbuilding")->second;
if (range->empty()) {
// no site, no building - fail;
IFCImporter::ThrowException("no root element found (expected IfcBuilding or preferably IfcSite)");
}
}
std::vector<aiNode*> nodes;
for(const STEP::LazyObject* lz : *range) {
const Schema_2x3::IfcSpatialStructureElement* const prod = lz->ToPtr<Schema_2x3::IfcSpatialStructureElement>();
if(!prod) {
continue;
}
IFCImporter::LogDebug("looking at spatial structure `" + (prod->Name ? prod->Name.Get() : "unnamed") + "`" + (prod->ObjectType? " which is of type " + prod->ObjectType.Get():""));
// the primary sites are referenced by an IFCRELAGGREGATES element which assigns them to the IFCPRODUCT
const STEP::DB::RefMap& refs = conv.db.GetRefs();
STEP::DB::RefMapRange ref_range = refs.equal_range(conv.proj.GetID());
for(; ref_range.first != ref_range.second; ++ref_range.first) {
if(const Schema_2x3::IfcRelAggregates* const aggr = conv.db.GetObject((*ref_range.first).second)->ToPtr<Schema_2x3::IfcRelAggregates>()) {
for(const Schema_2x3::IfcObjectDefinition& def : aggr->RelatedObjects) {
// comparing pointer values is not sufficient, we would need to cast them to the same type first
// as there is multiple inheritance in the game.
if (def.GetID() == prod->GetID()) {
IFCImporter::LogDebug("selecting this spatial structure as root structure");
// got it, this is one primary site.
nodes.push_back(ProcessSpatialStructure(NULL, *prod, conv, NULL));
}
}
}
}
}
size_t nb_nodes = nodes.size();
if (nb_nodes == 0) {
IFCImporter::LogWarn("failed to determine primary site element, taking all the IfcSite");
for (const STEP::LazyObject* lz : *range) {
const Schema_2x3::IfcSpatialStructureElement* const prod = lz->ToPtr<Schema_2x3::IfcSpatialStructureElement>();
if (!prod) {
continue;
}
nodes.push_back(ProcessSpatialStructure(NULL, *prod, conv, NULL));
}
nb_nodes = nodes.size();
}
if (nb_nodes == 1) {
conv.out->mRootNode = nodes[0];
}
else if (nb_nodes > 1) {
conv.out->mRootNode = new aiNode("Root");
conv.out->mRootNode->mParent = NULL;
conv.out->mRootNode->mNumChildren = static_cast<unsigned int>(nb_nodes);
conv.out->mRootNode->mChildren = new aiNode*[conv.out->mRootNode->mNumChildren];
for (size_t i = 0; i < nb_nodes; ++i) {
aiNode* node = nodes[i];
node->mParent = conv.out->mRootNode;
conv.out->mRootNode->mChildren[i] = node;
}
}
else {
IFCImporter::ThrowException("failed to determine primary site element");
}
}
// ------------------------------------------------------------------------------------------------
void MakeTreeRelative(aiNode* start, const aiMatrix4x4& combined)
{
// combined is the parent's absolute transformation matrix
const aiMatrix4x4 old = start->mTransformation;
if (!combined.IsIdentity()) {
start->mTransformation = aiMatrix4x4(combined).Inverse() * start->mTransformation;
}
// All nodes store absolute transformations right now, so we need to make them relative
for (unsigned int i = 0; i < start->mNumChildren; ++i) {
MakeTreeRelative(start->mChildren[i],old);
}
}
// ------------------------------------------------------------------------------------------------
void MakeTreeRelative(ConversionData& conv)
{
MakeTreeRelative(conv.out->mRootNode,IfcMatrix4());
}
} // !anon
#endif

View File

@@ -1,134 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file IFC.h
* @brief Declaration of the Industry Foundation Classes (IFC) loader main class
*/
#ifndef INCLUDED_AI_IFC_LOADER_H
#define INCLUDED_AI_IFC_LOADER_H
#include <assimp/BaseImporter.h>
#include <assimp/LogAux.h>
namespace Assimp {
// TinyFormatter.h
namespace Formatter {
template <typename T,typename TR, typename A> class basic_formatter;
typedef class basic_formatter< char, std::char_traits<char>, std::allocator<char> > format;
}
namespace STEP {
class DB;
}
// -------------------------------------------------------------------------------------------
/** Load the IFC format, which is an open specification to describe building and construction
industry data.
See http://en.wikipedia.org/wiki/Industry_Foundation_Classes
*/
// -------------------------------------------------------------------------------------------
class IFCImporter : public BaseImporter, public LogFunctions<IFCImporter>
{
public:
IFCImporter();
~IFCImporter();
public:
// --------------------
bool CanRead( const std::string& pFile,
IOSystem* pIOHandler,
bool checkSig
) const;
protected:
// --------------------
const aiImporterDesc* GetInfo () const;
// --------------------
void SetupProperties(const Importer* pImp);
// --------------------
void InternReadFile( const std::string& pFile,
aiScene* pScene,
IOSystem* pIOHandler
);
private:
public:
// loader settings, publicly accessible via their corresponding AI_CONFIG constants
struct Settings
{
Settings()
: skipSpaceRepresentations()
, useCustomTriangulation()
, skipAnnotations()
, conicSamplingAngle(10.f)
, cylindricalTessellation(32)
{}
bool skipSpaceRepresentations;
bool useCustomTriangulation;
bool skipAnnotations;
float conicSamplingAngle;
int cylindricalTessellation;
};
private:
Settings settings;
}; // !class IFCImporter
} // end of namespace Assimp
#endif // !INCLUDED_AI_IFC_LOADER_H

View File

@@ -1,202 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file IFCMaterial.cpp
* @brief Implementation of conversion routines to convert IFC materials to aiMaterial
*/
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
#include "IFCUtil.h"
#include <limits>
#include <assimp/material.h>
namespace Assimp {
namespace IFC {
// ------------------------------------------------------------------------------------------------
static int ConvertShadingMode(const std::string& name) {
if (name == "BLINN") {
return aiShadingMode_Blinn;
}
else if (name == "FLAT" || name == "NOTDEFINED") {
return aiShadingMode_NoShading;
}
else if (name == "PHONG") {
return aiShadingMode_Phong;
}
IFCImporter::LogWarn("shading mode "+name+" not recognized by Assimp, using Phong instead");
return aiShadingMode_Phong;
}
// ------------------------------------------------------------------------------------------------
static void FillMaterial(aiMaterial* mat,const IFC::Schema_2x3::IfcSurfaceStyle* surf,ConversionData& conv) {
aiString name;
name.Set((surf->Name? surf->Name.Get() : "IfcSurfaceStyle_Unnamed"));
mat->AddProperty(&name,AI_MATKEY_NAME);
// now see which kinds of surface information are present
for(std::shared_ptr< const IFC::Schema_2x3::IfcSurfaceStyleElementSelect > sel2 : surf->Styles) {
if (const IFC::Schema_2x3::IfcSurfaceStyleShading* shade = sel2->ResolveSelectPtr<IFC::Schema_2x3::IfcSurfaceStyleShading>(conv.db)) {
aiColor4D col_base,col;
ConvertColor(col_base, shade->SurfaceColour);
mat->AddProperty(&col_base,1, AI_MATKEY_COLOR_DIFFUSE);
if (const IFC::Schema_2x3::IfcSurfaceStyleRendering* ren = shade->ToPtr<IFC::Schema_2x3::IfcSurfaceStyleRendering>()) {
if (ren->Transparency) {
const float t = 1.f-static_cast<float>(ren->Transparency.Get());
mat->AddProperty(&t,1, AI_MATKEY_OPACITY);
}
if (ren->DiffuseColour) {
ConvertColor(col, *ren->DiffuseColour.Get(),conv,&col_base);
mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE);
}
if (ren->SpecularColour) {
ConvertColor(col, *ren->SpecularColour.Get(),conv,&col_base);
mat->AddProperty(&col,1, AI_MATKEY_COLOR_SPECULAR);
}
if (ren->TransmissionColour) {
ConvertColor(col, *ren->TransmissionColour.Get(),conv,&col_base);
mat->AddProperty(&col,1, AI_MATKEY_COLOR_TRANSPARENT);
}
if (ren->ReflectionColour) {
ConvertColor(col, *ren->ReflectionColour.Get(),conv,&col_base);
mat->AddProperty(&col,1, AI_MATKEY_COLOR_REFLECTIVE);
}
const int shading = (ren->SpecularHighlight && ren->SpecularColour)?ConvertShadingMode(ren->ReflectanceMethod):static_cast<int>(aiShadingMode_Gouraud);
mat->AddProperty(&shading,1, AI_MATKEY_SHADING_MODEL);
if (ren->SpecularHighlight) {
if(const ::Assimp::STEP::EXPRESS::REAL* rt = ren->SpecularHighlight.Get()->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
// at this point we don't distinguish between the two distinct ways of
// specifying highlight intensities. leave this to the user.
const float e = static_cast<float>(*rt);
mat->AddProperty(&e,1,AI_MATKEY_SHININESS);
}
else {
IFCImporter::LogWarn("unexpected type error, SpecularHighlight should be a REAL");
}
}
}
}
}
}
// ------------------------------------------------------------------------------------------------
unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionData& conv, bool forceDefaultMat) {
STEP::DB::RefMapRange range = conv.db.GetRefs().equal_range(id);
for(;range.first != range.second; ++range.first) {
if(const IFC::Schema_2x3::IfcStyledItem* const styled = conv.db.GetObject((*range.first).second)->ToPtr<IFC::Schema_2x3::IfcStyledItem>()) {
for(const IFC::Schema_2x3::IfcPresentationStyleAssignment& as : styled->Styles) {
for(std::shared_ptr<const IFC::Schema_2x3::IfcPresentationStyleSelect> sel : as.Styles) {
if( const IFC::Schema_2x3::IfcSurfaceStyle* const surf = sel->ResolveSelectPtr<IFC::Schema_2x3::IfcSurfaceStyle>(conv.db) ) {
// try to satisfy from cache
ConversionData::MaterialCache::iterator mit = conv.cached_materials.find(surf);
if( mit != conv.cached_materials.end() )
return mit->second;
// not found, create new material
const std::string side = static_cast<std::string>(surf->Side);
if( side != "BOTH" ) {
IFCImporter::LogWarn("ignoring surface side marker on IFC::IfcSurfaceStyle: " + side);
}
std::unique_ptr<aiMaterial> mat(new aiMaterial());
FillMaterial(mat.get(), surf, conv);
conv.materials.push_back(mat.release());
unsigned int matindex = static_cast<unsigned int>(conv.materials.size() - 1);
conv.cached_materials[surf] = matindex;
return matindex;
}
}
}
}
}
// no local material defined. If there's global one, use that instead
if ( prevMatId != std::numeric_limits<uint32_t>::max() ) {
return prevMatId;
}
// we're still here - create an default material if required, or simply fail otherwise
if ( !forceDefaultMat ) {
return std::numeric_limits<uint32_t>::max();
}
aiString name;
name.Set("<IFCDefault>");
// ConvertColorToString( color, name);
// look if there's already a default material with this base color
for( size_t a = 0; a < conv.materials.size(); ++a ) {
aiString mname;
conv.materials[a]->Get(AI_MATKEY_NAME, mname);
if ( name == mname ) {
return ( unsigned int )a;
}
}
// we're here, yet - no default material with suitable color available. Generate one
std::unique_ptr<aiMaterial> mat(new aiMaterial());
mat->AddProperty(&name,AI_MATKEY_NAME);
const aiColor4D col = aiColor4D( 0.6f, 0.6f, 0.6f, 1.0f); // aiColor4D( color.r, color.g, color.b, 1.0f);
mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE);
conv.materials.push_back(mat.release());
return (unsigned int) conv.materials.size() - 1;
}
} // ! IFC
} // ! Assimp
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER

File diff suppressed because it is too large Load Diff

View File

@@ -1,190 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file IFCProfile.cpp
* @brief Read profile and curves entities from IFC files
*/
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
#include "IFCUtil.h"
namespace Assimp {
namespace IFC {
// ------------------------------------------------------------------------------------------------
void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, TempMesh& meshout, ConversionData& /*conv*/)
{
// this won't produce a valid mesh, it just spits out a list of vertices
IfcVector3 t;
for(const Schema_2x3::IfcCartesianPoint& cp : def.Points) {
ConvertCartesianPoint(t,cp);
meshout.mVerts.push_back(t);
}
meshout.mVertcnt.push_back(static_cast<unsigned int>(meshout.mVerts.size()));
}
// ------------------------------------------------------------------------------------------------
bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv)
{
std::unique_ptr<const Curve> cv(Curve::Convert(curve,conv));
if (!cv) {
IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is " + curve.GetClassName());
return false;
}
// we must have a bounded curve at this point
if (const BoundedCurve* bc = dynamic_cast<const BoundedCurve*>(cv.get())) {
try {
bc->SampleDiscrete(meshout);
}
catch(const CurveError& cv) {
IFCImporter::LogError(cv.mStr + " (error occurred while processing curve)");
return false;
}
meshout.mVertcnt.push_back(static_cast<unsigned int>(meshout.mVerts.size()));
return true;
}
IFCImporter::LogError("cannot use unbounded curve as profile");
return false;
}
// ------------------------------------------------------------------------------------------------
void ProcessClosedProfile(const Schema_2x3::IfcArbitraryClosedProfileDef& def, TempMesh& meshout, ConversionData& conv)
{
ProcessCurve(def.OuterCurve,meshout,conv);
}
// ------------------------------------------------------------------------------------------------
void ProcessOpenProfile(const Schema_2x3::IfcArbitraryOpenProfileDef& def, TempMesh& meshout, ConversionData& conv)
{
ProcessCurve(def.Curve,meshout,conv);
}
// ------------------------------------------------------------------------------------------------
void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& def, TempMesh& meshout, ConversionData& conv)
{
if(const Schema_2x3::IfcRectangleProfileDef* const cprofile = def.ToPtr<Schema_2x3::IfcRectangleProfileDef>()) {
const IfcFloat x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f;
meshout.mVerts.reserve(meshout.mVerts.size()+4);
meshout.mVerts.push_back( IfcVector3( x, y, 0.f ));
meshout.mVerts.push_back( IfcVector3(-x, y, 0.f ));
meshout.mVerts.push_back( IfcVector3(-x,-y, 0.f ));
meshout.mVerts.push_back( IfcVector3( x,-y, 0.f ));
meshout.mVertcnt.push_back(4);
}
else if( const Schema_2x3::IfcCircleProfileDef* const circle = def.ToPtr<Schema_2x3::IfcCircleProfileDef>()) {
if(def.ToPtr<Schema_2x3::IfcCircleHollowProfileDef>()) {
// TODO
}
const size_t segments = conv.settings.cylindricalTessellation;
const IfcFloat delta = AI_MATH_TWO_PI_F/segments, radius = circle->Radius;
meshout.mVerts.reserve(segments);
IfcFloat angle = 0.f;
for(size_t i = 0; i < segments; ++i, angle += delta) {
meshout.mVerts.push_back( IfcVector3( std::cos(angle)*radius, std::sin(angle)*radius, 0.f ));
}
meshout.mVertcnt.push_back(static_cast<unsigned int>(segments));
}
else if( const Schema_2x3::IfcIShapeProfileDef* const ishape = def.ToPtr<Schema_2x3::IfcIShapeProfileDef>()) {
// construct simplified IBeam shape
const IfcFloat offset = (ishape->OverallWidth - ishape->WebThickness) / 2;
const IfcFloat inner_height = ishape->OverallDepth - ishape->FlangeThickness * 2;
meshout.mVerts.reserve(12);
meshout.mVerts.push_back(IfcVector3(0,0,0));
meshout.mVerts.push_back(IfcVector3(0,ishape->FlangeThickness,0));
meshout.mVerts.push_back(IfcVector3(offset,ishape->FlangeThickness,0));
meshout.mVerts.push_back(IfcVector3(offset,ishape->FlangeThickness + inner_height,0));
meshout.mVerts.push_back(IfcVector3(0,ishape->FlangeThickness + inner_height,0));
meshout.mVerts.push_back(IfcVector3(0,ishape->OverallDepth,0));
meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,ishape->OverallDepth,0));
meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,ishape->FlangeThickness + inner_height,0));
meshout.mVerts.push_back(IfcVector3(offset+ishape->WebThickness,ishape->FlangeThickness + inner_height,0));
meshout.mVerts.push_back(IfcVector3(offset+ishape->WebThickness,ishape->FlangeThickness,0));
meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,ishape->FlangeThickness,0));
meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,0,0));
meshout.mVertcnt.push_back(12);
}
else {
IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is " + def.GetClassName());
return;
}
IfcMatrix4 trafo;
ConvertAxisPlacement(trafo, *def.Position);
meshout.Transform(trafo);
}
// ------------------------------------------------------------------------------------------------
bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv)
{
if(const Schema_2x3::IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr<Schema_2x3::IfcArbitraryClosedProfileDef>()) {
ProcessClosedProfile(*cprofile,meshout,conv);
}
else if(const Schema_2x3::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr<Schema_2x3::IfcArbitraryOpenProfileDef>()) {
ProcessOpenProfile(*copen,meshout,conv);
}
else if(const Schema_2x3::IfcParameterizedProfileDef* const cparam = prof.ToPtr<Schema_2x3::IfcParameterizedProfileDef>()) {
ProcessParametrizedProfile(*cparam,meshout,conv);
}
else {
IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is " + prof.GetClassName());
return false;
}
meshout.RemoveAdjacentDuplicates();
if (!meshout.mVertcnt.size() || meshout.mVertcnt.front() <= 1) {
return false;
}
return true;
}
} // ! IFC
} // ! Assimp
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,703 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file IFCUtil.cpp
* @brief Implementation of conversion routines for some common Ifc helper entities.
*/
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
#include "Importer/IFC/IFCUtil.h"
#include "Common/PolyTools.h"
#include "PostProcessing/ProcessHelper.h"
#include <assimp/Defines.h>
namespace Assimp {
namespace IFC {
// ------------------------------------------------------------------------------------------------
void TempOpening::Transform(const IfcMatrix4& mat) {
if(profileMesh) {
profileMesh->Transform(mat);
}
if(profileMesh2D) {
profileMesh2D->Transform(mat);
}
extrusionDir *= IfcMatrix3(mat);
}
// ------------------------------------------------------------------------------------------------
aiMesh* TempMesh::ToMesh()
{
ai_assert(mVerts.size() == std::accumulate(mVertcnt.begin(),mVertcnt.end(),size_t(0)));
if (mVerts.empty()) {
return NULL;
}
std::unique_ptr<aiMesh> mesh(new aiMesh());
// copy vertices
mesh->mNumVertices = static_cast<unsigned int>(mVerts.size());
mesh->mVertices = new aiVector3D[mesh->mNumVertices];
std::copy(mVerts.begin(),mVerts.end(),mesh->mVertices);
// and build up faces
mesh->mNumFaces = static_cast<unsigned int>(mVertcnt.size());
mesh->mFaces = new aiFace[mesh->mNumFaces];
for(unsigned int i = 0,n=0, acc = 0; i < mesh->mNumFaces; ++n) {
aiFace& f = mesh->mFaces[i];
if (!mVertcnt[n]) {
--mesh->mNumFaces;
continue;
}
f.mNumIndices = mVertcnt[n];
f.mIndices = new unsigned int[f.mNumIndices];
for(unsigned int a = 0; a < f.mNumIndices; ++a) {
f.mIndices[a] = acc++;
}
++i;
}
return mesh.release();
}
// ------------------------------------------------------------------------------------------------
void TempMesh::Clear()
{
mVerts.clear();
mVertcnt.clear();
}
// ------------------------------------------------------------------------------------------------
void TempMesh::Transform(const IfcMatrix4& mat)
{
for(IfcVector3& v : mVerts) {
v *= mat;
}
}
// ------------------------------------------------------------------------------
IfcVector3 TempMesh::Center() const
{
return (mVerts.size() == 0) ? IfcVector3(0.0f, 0.0f, 0.0f) : (std::accumulate(mVerts.begin(),mVerts.end(),IfcVector3()) / static_cast<IfcFloat>(mVerts.size()));
}
// ------------------------------------------------------------------------------------------------
void TempMesh::Append(const TempMesh& other)
{
mVerts.insert(mVerts.end(),other.mVerts.begin(),other.mVerts.end());
mVertcnt.insert(mVertcnt.end(),other.mVertcnt.begin(),other.mVertcnt.end());
}
// ------------------------------------------------------------------------------------------------
void TempMesh::RemoveDegenerates()
{
// The strategy is simple: walk the mesh and compute normals using
// Newell's algorithm. The length of the normals gives the area
// of the polygons, which is close to zero for lines.
std::vector<IfcVector3> normals;
ComputePolygonNormals(normals, false);
bool drop = false;
size_t inor = 0;
std::vector<IfcVector3>::iterator vit = mVerts.begin();
for (std::vector<unsigned int>::iterator it = mVertcnt.begin(); it != mVertcnt.end(); ++inor) {
const unsigned int pcount = *it;
if (normals[inor].SquareLength() < 1e-10f) {
it = mVertcnt.erase(it);
vit = mVerts.erase(vit, vit + pcount);
drop = true;
continue;
}
vit += pcount;
++it;
}
if(drop) {
IFCImporter::LogDebug("removing degenerate faces");
}
}
// ------------------------------------------------------------------------------------------------
IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize)
{
std::vector<IfcFloat> temp((cnt+2)*3);
for( size_t vofs = 0, i = 0; vofs < cnt; ++vofs )
{
const IfcVector3& v = vtcs[vofs];
temp[i++] = v.x;
temp[i++] = v.y;
temp[i++] = v.z;
}
IfcVector3 nor;
NewellNormal<3, 3, 3>(nor, static_cast<int>(cnt), &temp[0], &temp[1], &temp[2]);
return normalize ? nor.Normalize() : nor;
}
// ------------------------------------------------------------------------------------------------
void TempMesh::ComputePolygonNormals(std::vector<IfcVector3>& normals,
bool normalize,
size_t ofs) const
{
size_t max_vcount = 0;
std::vector<unsigned int>::const_iterator begin = mVertcnt.begin()+ofs, end = mVertcnt.end(), iit;
for(iit = begin; iit != end; ++iit) {
max_vcount = std::max(max_vcount,static_cast<size_t>(*iit));
}
std::vector<IfcFloat> temp((max_vcount+2)*4);
normals.reserve( normals.size() + mVertcnt.size()-ofs );
// `NewellNormal()` currently has a relatively strange interface and need to
// re-structure things a bit to meet them.
size_t vidx = std::accumulate(mVertcnt.begin(),begin,0);
for(iit = begin; iit != end; vidx += *iit++) {
if (!*iit) {
normals.push_back(IfcVector3());
continue;
}
for(size_t vofs = 0, cnt = 0; vofs < *iit; ++vofs) {
const IfcVector3& v = mVerts[vidx+vofs];
temp[cnt++] = v.x;
temp[cnt++] = v.y;
temp[cnt++] = v.z;
#ifdef ASSIMP_BUILD_DEBUG
temp[cnt] = std::numeric_limits<IfcFloat>::quiet_NaN();
#endif
++cnt;
}
normals.push_back(IfcVector3());
NewellNormal<4,4,4>(normals.back(),*iit,&temp[0],&temp[1],&temp[2]);
}
if(normalize) {
for(IfcVector3& n : normals) {
n.Normalize();
}
}
}
// ------------------------------------------------------------------------------------------------
// Compute the normal of the last polygon in the given mesh
IfcVector3 TempMesh::ComputeLastPolygonNormal(bool normalize) const
{
return ComputePolygonNormal(&mVerts[mVerts.size() - mVertcnt.back()], mVertcnt.back(), normalize);
}
struct CompareVector
{
bool operator () (const IfcVector3& a, const IfcVector3& b) const
{
IfcVector3 d = a - b;
IfcFloat eps = 1e-6;
return d.x < -eps || (std::abs(d.x) < eps && d.y < -eps) || (std::abs(d.x) < eps && std::abs(d.y) < eps && d.z < -eps);
}
};
struct FindVector
{
IfcVector3 v;
FindVector(const IfcVector3& p) : v(p) { }
bool operator () (const IfcVector3& p) { return FuzzyVectorCompare(1e-6)(p, v); }
};
// ------------------------------------------------------------------------------------------------
void TempMesh::FixupFaceOrientation()
{
const IfcVector3 vavg = Center();
// create a list of start indices for all faces to allow random access to faces
std::vector<size_t> faceStartIndices(mVertcnt.size());
for( size_t i = 0, a = 0; a < mVertcnt.size(); i += mVertcnt[a], ++a )
faceStartIndices[a] = i;
// list all faces on a vertex
std::map<IfcVector3, std::vector<size_t>, CompareVector> facesByVertex;
for( size_t a = 0; a < mVertcnt.size(); ++a )
{
for( size_t b = 0; b < mVertcnt[a]; ++b )
facesByVertex[mVerts[faceStartIndices[a] + b]].push_back(a);
}
// determine neighbourhood for all polys
std::vector<size_t> neighbour(mVerts.size(), SIZE_MAX);
std::vector<size_t> tempIntersect(10);
for( size_t a = 0; a < mVertcnt.size(); ++a )
{
for( size_t b = 0; b < mVertcnt[a]; ++b )
{
size_t ib = faceStartIndices[a] + b, nib = faceStartIndices[a] + (b + 1) % mVertcnt[a];
const std::vector<size_t>& facesOnB = facesByVertex[mVerts[ib]];
const std::vector<size_t>& facesOnNB = facesByVertex[mVerts[nib]];
// there should be exactly one or two faces which appear in both lists. Our face and the other side
std::vector<size_t>::iterator sectstart = tempIntersect.begin();
std::vector<size_t>::iterator sectend = std::set_intersection(
facesOnB.begin(), facesOnB.end(), facesOnNB.begin(), facesOnNB.end(), sectstart);
if( std::distance(sectstart, sectend) != 2 )
continue;
if( *sectstart == a )
++sectstart;
neighbour[ib] = *sectstart;
}
}
// now we're getting started. We take the face which is the farthest away from the center. This face is most probably
// facing outwards. So we reverse this face to point outwards in relation to the center. Then we adapt neighbouring
// faces to have the same winding until all faces have been tested.
std::vector<bool> faceDone(mVertcnt.size(), false);
while( std::count(faceDone.begin(), faceDone.end(), false) != 0 )
{
// find the farthest of the remaining faces
size_t farthestIndex = SIZE_MAX;
IfcFloat farthestDistance = -1.0;
for( size_t a = 0; a < mVertcnt.size(); ++a )
{
if( faceDone[a] )
continue;
IfcVector3 faceCenter = std::accumulate(mVerts.begin() + faceStartIndices[a],
mVerts.begin() + faceStartIndices[a] + mVertcnt[a], IfcVector3(0.0)) / IfcFloat(mVertcnt[a]);
IfcFloat dst = (faceCenter - vavg).SquareLength();
if( dst > farthestDistance ) { farthestDistance = dst; farthestIndex = a; }
}
// calculate its normal and reverse the poly if its facing towards the mesh center
IfcVector3 farthestNormal = ComputePolygonNormal(mVerts.data() + faceStartIndices[farthestIndex], mVertcnt[farthestIndex]);
IfcVector3 farthestCenter = std::accumulate(mVerts.begin() + faceStartIndices[farthestIndex],
mVerts.begin() + faceStartIndices[farthestIndex] + mVertcnt[farthestIndex], IfcVector3(0.0))
/ IfcFloat(mVertcnt[farthestIndex]);
// We accept a bit of negative orientation without reversing. In case of doubt, prefer the orientation given in
// the file.
if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 )
{
size_t fsi = faceStartIndices[farthestIndex], fvc = mVertcnt[farthestIndex];
std::reverse(mVerts.begin() + fsi, mVerts.begin() + fsi + fvc);
std::reverse(neighbour.begin() + fsi, neighbour.begin() + fsi + fvc);
// because of the neighbour index belonging to the edge starting with the point at the same index, we need to
// cycle the neighbours through to match the edges again.
// Before: points A - B - C - D with edge neighbour p - q - r - s
// After: points D - C - B - A, reversed neighbours are s - r - q - p, but the should be
// r q p s
for( size_t a = 0; a < fvc - 1; ++a )
std::swap(neighbour[fsi + a], neighbour[fsi + a + 1]);
}
faceDone[farthestIndex] = true;
std::vector<size_t> todo;
todo.push_back(farthestIndex);
// go over its neighbour faces recursively and adapt their winding order to match the farthest face
while( !todo.empty() )
{
size_t tdf = todo.back();
size_t vsi = faceStartIndices[tdf], vc = mVertcnt[tdf];
todo.pop_back();
// check its neighbours
for( size_t a = 0; a < vc; ++a )
{
// ignore neighbours if we already checked them
size_t nbi = neighbour[vsi + a];
if( nbi == SIZE_MAX || faceDone[nbi] )
continue;
const IfcVector3& vp = mVerts[vsi + a];
size_t nbvsi = faceStartIndices[nbi], nbvc = mVertcnt[nbi];
std::vector<IfcVector3>::iterator it = std::find_if(mVerts.begin() + nbvsi, mVerts.begin() + nbvsi + nbvc, FindVector(vp));
ai_assert(it != mVerts.begin() + nbvsi + nbvc);
size_t nb_vidx = std::distance(mVerts.begin() + nbvsi, it);
// two faces winded in the same direction should have a crossed edge, where one face has p0->p1 and the other
// has p1'->p0'. If the next point on the neighbouring face is also the next on the current face, we need
// to reverse the neighbour
nb_vidx = (nb_vidx + 1) % nbvc;
size_t oursideidx = (a + 1) % vc;
if( FuzzyVectorCompare(1e-6)(mVerts[vsi + oursideidx], mVerts[nbvsi + nb_vidx]) )
{
std::reverse(mVerts.begin() + nbvsi, mVerts.begin() + nbvsi + nbvc);
std::reverse(neighbour.begin() + nbvsi, neighbour.begin() + nbvsi + nbvc);
for( size_t a = 0; a < nbvc - 1; ++a )
std::swap(neighbour[nbvsi + a], neighbour[nbvsi + a + 1]);
}
// either way we're done with the neighbour. Mark it as done and continue checking from there recursively
faceDone[nbi] = true;
todo.push_back(nbi);
}
}
// no more faces reachable from this part of the surface, start over with a disjunct part and its farthest face
}
}
// ------------------------------------------------------------------------------------------------
void TempMesh::RemoveAdjacentDuplicates() {
bool drop = false;
std::vector<IfcVector3>::iterator base = mVerts.begin();
for(unsigned int& cnt : mVertcnt) {
if (cnt < 2){
base += cnt;
continue;
}
IfcVector3 vmin,vmax;
ArrayBounds(&*base, cnt ,vmin,vmax);
const IfcFloat epsilon = (vmax-vmin).SquareLength() / static_cast<IfcFloat>(1e9);
//const IfcFloat dotepsilon = 1e-9;
//// look for vertices that lie directly on the line between their predecessor and their
//// successor and replace them with either of them.
//for(size_t i = 0; i < cnt; ++i) {
// IfcVector3& v1 = *(base+i), &v0 = *(base+(i?i-1:cnt-1)), &v2 = *(base+(i+1)%cnt);
// const IfcVector3& d0 = (v1-v0), &d1 = (v2-v1);
// const IfcFloat l0 = d0.SquareLength(), l1 = d1.SquareLength();
// if (!l0 || !l1) {
// continue;
// }
// const IfcFloat d = (d0/std::sqrt(l0))*(d1/std::sqrt(l1));
// if ( d >= 1.f-dotepsilon ) {
// v1 = v0;
// }
// else if ( d < -1.f+dotepsilon ) {
// v2 = v1;
// continue;
// }
//}
// drop any identical, adjacent vertices. this pass will collect the dropouts
// of the previous pass as a side-effect.
FuzzyVectorCompare fz(epsilon);
std::vector<IfcVector3>::iterator end = base+cnt, e = std::unique( base, end, fz );
if (e != end) {
cnt -= static_cast<unsigned int>(std::distance(e, end));
mVerts.erase(e,end);
drop = true;
}
// check front and back vertices for this polygon
if (cnt > 1 && fz(*base,*(base+cnt-1))) {
mVerts.erase(base+ --cnt);
drop = true;
}
// removing adjacent duplicates shouldn't erase everything :-)
ai_assert(cnt>0);
base += cnt;
}
if(drop) {
IFCImporter::LogDebug("removing duplicate vertices");
}
}
// ------------------------------------------------------------------------------------------------
void TempMesh::Swap(TempMesh& other)
{
mVertcnt.swap(other.mVertcnt);
mVerts.swap(other.mVerts);
}
// ------------------------------------------------------------------------------------------------
bool IsTrue(const ::Assimp::STEP::EXPRESS::BOOLEAN& in)
{
return (std::string)in == "TRUE" || (std::string)in == "T";
}
// ------------------------------------------------------------------------------------------------
IfcFloat ConvertSIPrefix(const std::string& prefix)
{
if (prefix == "EXA") {
return 1e18f;
}
else if (prefix == "PETA") {
return 1e15f;
}
else if (prefix == "TERA") {
return 1e12f;
}
else if (prefix == "GIGA") {
return 1e9f;
}
else if (prefix == "MEGA") {
return 1e6f;
}
else if (prefix == "KILO") {
return 1e3f;
}
else if (prefix == "HECTO") {
return 1e2f;
}
else if (prefix == "DECA") {
return 1e-0f;
}
else if (prefix == "DECI") {
return 1e-1f;
}
else if (prefix == "CENTI") {
return 1e-2f;
}
else if (prefix == "MILLI") {
return 1e-3f;
}
else if (prefix == "MICRO") {
return 1e-6f;
}
else if (prefix == "NANO") {
return 1e-9f;
}
else if (prefix == "PICO") {
return 1e-12f;
}
else if (prefix == "FEMTO") {
return 1e-15f;
}
else if (prefix == "ATTO") {
return 1e-18f;
}
else {
IFCImporter::LogError("Unrecognized SI prefix: " + prefix);
return 1;
}
}
// ------------------------------------------------------------------------------------------------
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in)
{
out.r = static_cast<float>( in.Red );
out.g = static_cast<float>( in.Green );
out.b = static_cast<float>( in.Blue );
out.a = static_cast<float>( 1.f );
}
// ------------------------------------------------------------------------------------------------
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base)
{
if (const ::Assimp::STEP::EXPRESS::REAL* const r = in.ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
out.r = out.g = out.b = static_cast<float>(*r);
if(base) {
out.r *= static_cast<float>( base->r );
out.g *= static_cast<float>( base->g );
out.b *= static_cast<float>( base->b );
out.a = static_cast<float>( base->a );
}
else out.a = 1.0;
}
else if (const Schema_2x3::IfcColourRgb* const rgb = in.ResolveSelectPtr<Schema_2x3::IfcColourRgb>(conv.db)) {
ConvertColor(out,*rgb);
}
else {
IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity");
}
}
// ------------------------------------------------------------------------------------------------
void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in)
{
out = IfcVector3();
for(size_t i = 0; i < in.Coordinates.size(); ++i) {
out[static_cast<unsigned int>(i)] = in.Coordinates[i];
}
}
// ------------------------------------------------------------------------------------------------
void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in)
{
ConvertDirection(out,in.Orientation);
out *= in.Magnitude;
}
// ------------------------------------------------------------------------------------------------
void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in)
{
out = IfcVector3();
for(size_t i = 0; i < in.DirectionRatios.size(); ++i) {
out[static_cast<unsigned int>(i)] = in.DirectionRatios[i];
}
const IfcFloat len = out.Length();
if (len<1e-6) {
IFCImporter::LogWarn("direction vector magnitude too small, normalization would result in a division by zero");
return;
}
out /= len;
}
// ------------------------------------------------------------------------------------------------
void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z)
{
out.a1 = x.x;
out.b1 = x.y;
out.c1 = x.z;
out.a2 = y.x;
out.b2 = y.y;
out.c2 = y.z;
out.a3 = z.x;
out.b3 = z.y;
out.c3 = z.z;
}
// ------------------------------------------------------------------------------------------------
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in)
{
IfcVector3 loc;
ConvertCartesianPoint(loc,in.Location);
IfcVector3 z(0.f,0.f,1.f),r(1.f,0.f,0.f),x;
if (in.Axis) {
ConvertDirection(z,*in.Axis.Get());
}
if (in.RefDirection) {
ConvertDirection(r,*in.RefDirection.Get());
}
IfcVector3 v = r.Normalize();
IfcVector3 tmpx = z * (v*z);
x = (v-tmpx).Normalize();
IfcVector3 y = (z^x);
IfcMatrix4::Translation(loc,out);
AssignMatrixAxes(out,x,y,z);
}
// ------------------------------------------------------------------------------------------------
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in)
{
IfcVector3 loc;
ConvertCartesianPoint(loc,in.Location);
IfcVector3 x(1.f,0.f,0.f);
if (in.RefDirection) {
ConvertDirection(x,*in.RefDirection.Get());
}
const IfcVector3 y = IfcVector3(x.y,-x.x,0.f);
IfcMatrix4::Translation(loc,out);
AssignMatrixAxes(out,x,y,IfcVector3(0.f,0.f,1.f));
}
// ------------------------------------------------------------------------------------------------
void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const Schema_2x3::IfcAxis1Placement& in)
{
ConvertCartesianPoint(pos,in.Location);
if (in.Axis) {
ConvertDirection(axis,in.Axis.Get());
}
else {
axis = IfcVector3(0.f,0.f,1.f);
}
}
// ------------------------------------------------------------------------------------------------
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv)
{
if(const Schema_2x3::IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement3D>(conv.db)) {
ConvertAxisPlacement(out,*pl3);
}
else if(const Schema_2x3::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement2D>(conv.db)) {
ConvertAxisPlacement(out,*pl2);
}
else {
IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity");
}
}
// ------------------------------------------------------------------------------------------------
void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op)
{
IfcVector3 loc;
ConvertCartesianPoint(loc,op.LocalOrigin);
IfcVector3 x(1.f,0.f,0.f),y(0.f,1.f,0.f),z(0.f,0.f,1.f);
if (op.Axis1) {
ConvertDirection(x,*op.Axis1.Get());
}
if (op.Axis2) {
ConvertDirection(y,*op.Axis2.Get());
}
if (const Schema_2x3::IfcCartesianTransformationOperator3D* op2 = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3D>()) {
if(op2->Axis3) {
ConvertDirection(z,*op2->Axis3.Get());
}
}
IfcMatrix4 locm;
IfcMatrix4::Translation(loc,locm);
AssignMatrixAxes(out,x,y,z);
IfcVector3 vscale;
if (const Schema_2x3::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3DnonUniform>()) {
vscale.x = nuni->Scale?op.Scale.Get():1.f;
vscale.y = nuni->Scale2?nuni->Scale2.Get():1.f;
vscale.z = nuni->Scale3?nuni->Scale3.Get():1.f;
}
else {
const IfcFloat sc = op.Scale?op.Scale.Get():1.f;
vscale = IfcVector3(sc,sc,sc);
}
IfcMatrix4 s;
IfcMatrix4::Scaling(vscale,s);
out = locm * out * s;
}
} // ! IFC
} // ! Assimp
#endif

View File

@@ -1,413 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file IFC.cpp
* @brief Implementation of the Industry Foundation Classes loader.
*/
#ifndef INCLUDED_IFCUTIL_H
#define INCLUDED_IFCUTIL_H
#include "IFCReaderGen_2x3.h"
#include "IFCLoader.h"
#include "code/Step/STEPFile.h"
#include <assimp/mesh.h>
#include <assimp/material.h>
struct aiNode;
namespace Assimp {
namespace IFC {
typedef double IfcFloat;
// IfcFloat-precision math data types
typedef aiVector2t<IfcFloat> IfcVector2;
typedef aiVector3t<IfcFloat> IfcVector3;
typedef aiMatrix4x4t<IfcFloat> IfcMatrix4;
typedef aiMatrix3x3t<IfcFloat> IfcMatrix3;
typedef aiColor4t<IfcFloat> IfcColor4;
// ------------------------------------------------------------------------------------------------
// Helper for std::for_each to delete all heap-allocated items in a container
// ------------------------------------------------------------------------------------------------
template<typename T>
struct delete_fun {
void operator()(T* del) {
delete del;
}
};
// ------------------------------------------------------------------------------------------------
// Helper used during mesh construction. Aids at creating aiMesh'es out of relatively few polygons.
// ------------------------------------------------------------------------------------------------
struct TempMesh {
std::vector<IfcVector3> mVerts;
std::vector<unsigned int> mVertcnt;
// utilities
aiMesh* ToMesh();
void Clear();
void Transform(const IfcMatrix4& mat);
IfcVector3 Center() const;
void Append(const TempMesh& other);
bool IsEmpty() const;
void RemoveAdjacentDuplicates();
void RemoveDegenerates();
void FixupFaceOrientation();
static IfcVector3 ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize = true);
IfcVector3 ComputeLastPolygonNormal(bool normalize = true) const;
void ComputePolygonNormals(std::vector<IfcVector3>& normals, bool normalize = true, size_t ofs = 0) const;
void Swap(TempMesh& other);
};
inline
bool TempMesh::IsEmpty() const {
return mVerts.empty() && mVertcnt.empty();
}
// ------------------------------------------------------------------------------------------------
// Temporary representation of an opening in a wall or a floor
// ------------------------------------------------------------------------------------------------
struct TempOpening
{
const IFC::Schema_2x3::IfcSolidModel *solid;
IfcVector3 extrusionDir;
std::shared_ptr<TempMesh> profileMesh;
std::shared_ptr<TempMesh> profileMesh2D;
// list of points generated for this opening. This is used to
// create connections between two opposing holes created
// from a single opening instance (two because walls tend to
// have two sides). If !empty(), the other side of the wall
// has already been processed.
std::vector<IfcVector3> wallPoints;
// ------------------------------------------------------------------------------
TempOpening()
: solid()
, extrusionDir()
, profileMesh()
{
}
// ------------------------------------------------------------------------------
TempOpening(const IFC::Schema_2x3::IfcSolidModel* solid,IfcVector3 extrusionDir,
std::shared_ptr<TempMesh> profileMesh,
std::shared_ptr<TempMesh> profileMesh2D)
: solid(solid)
, extrusionDir(extrusionDir)
, profileMesh(profileMesh)
, profileMesh2D(profileMesh2D)
{
}
// ------------------------------------------------------------------------------
void Transform(const IfcMatrix4& mat); // defined later since TempMesh is not complete yet
// ------------------------------------------------------------------------------
// Helper to sort openings by distance from a given base point
struct DistanceSorter {
DistanceSorter(const IfcVector3& base) : base(base) {}
bool operator () (const TempOpening& a, const TempOpening& b) const {
return (a.profileMesh->Center()-base).SquareLength() < (b.profileMesh->Center()-base).SquareLength();
}
IfcVector3 base;
};
};
// ------------------------------------------------------------------------------------------------
// Intermediate data storage during conversion. Keeps everything and a bit more.
// ------------------------------------------------------------------------------------------------
struct ConversionData
{
ConversionData(const STEP::DB& db, const IFC::Schema_2x3::IfcProject& proj, aiScene* out,const IFCImporter::Settings& settings)
: len_scale(1.0)
, angle_scale(-1.0)
, db(db)
, proj(proj)
, out(out)
, settings(settings)
, apply_openings()
, collect_openings()
{}
~ConversionData() {
std::for_each(meshes.begin(),meshes.end(),delete_fun<aiMesh>());
std::for_each(materials.begin(),materials.end(),delete_fun<aiMaterial>());
}
IfcFloat len_scale, angle_scale;
bool plane_angle_in_radians;
const STEP::DB& db;
const IFC::Schema_2x3::IfcProject& proj;
aiScene* out;
IfcMatrix4 wcs;
std::vector<aiMesh*> meshes;
std::vector<aiMaterial*> materials;
struct MeshCacheIndex {
const IFC::Schema_2x3::IfcRepresentationItem* item; unsigned int matindex;
MeshCacheIndex() : item(NULL), matindex(0) { }
MeshCacheIndex(const IFC::Schema_2x3::IfcRepresentationItem* i, unsigned int mi) : item(i), matindex(mi) { }
bool operator == (const MeshCacheIndex& o) const { return item == o.item && matindex == o.matindex; }
bool operator < (const MeshCacheIndex& o) const { return item < o.item || (item == o.item && matindex < o.matindex); }
};
typedef std::map<MeshCacheIndex, std::set<unsigned int> > MeshCache;
MeshCache cached_meshes;
typedef std::map<const IFC::Schema_2x3::IfcSurfaceStyle*, unsigned int> MaterialCache;
MaterialCache cached_materials;
const IFCImporter::Settings& settings;
// Intermediate arrays used to resolve openings in walls: only one of them
// can be given at a time. apply_openings if present if the current element
// is a wall and needs its openings to be poured into its geometry while
// collect_openings is present only if the current element is an
// IfcOpeningElement, for which all the geometry needs to be preserved
// for later processing by a parent, which is a wall.
std::vector<TempOpening>* apply_openings;
std::vector<TempOpening>* collect_openings;
std::set<uint64_t> already_processed;
};
// ------------------------------------------------------------------------------------------------
// Binary predicate to compare vectors with a given, quadratic epsilon.
// ------------------------------------------------------------------------------------------------
struct FuzzyVectorCompare {
FuzzyVectorCompare(IfcFloat epsilon) : epsilon(epsilon) {}
bool operator()(const IfcVector3& a, const IfcVector3& b) {
return std::abs((a-b).SquareLength()) < epsilon;
}
const IfcFloat epsilon;
};
// ------------------------------------------------------------------------------------------------
// Ordering predicate to totally order R^2 vectors first by x and then by y
// ------------------------------------------------------------------------------------------------
struct XYSorter {
// sort first by X coordinates, then by Y coordinates
bool operator () (const IfcVector2&a, const IfcVector2& b) const {
if (a.x == b.x) {
return a.y < b.y;
}
return a.x < b.x;
}
};
// conversion routines for common IFC entities, implemented in IFCUtil.cpp
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in);
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base);
void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in);
void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in);
void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in);
void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z);
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in);
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in);
void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const IFC::Schema_2x3::IfcAxis1Placement& in);
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv);
void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op);
bool IsTrue(const Assimp::STEP::EXPRESS::BOOLEAN& in);
IfcFloat ConvertSIPrefix(const std::string& prefix);
// IFCProfile.cpp
bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv);
bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv);
// IFCMaterial.cpp
unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionData& conv, bool forceDefaultMat);
// IFCGeometry.cpp
IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut);
bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, unsigned int matid, std::set<unsigned int>& mesh_indices, ConversionData& conv);
void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd,ConversionData& /*conv*/);
void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, TempMesh& meshout,
ConversionData& conv);
void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, TempMesh& result,
ConversionData& conv, bool collect_openings);
// IFCBoolean.cpp
void ProcessBoolean(const Schema_2x3::IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv);
void ProcessBooleanHalfSpaceDifference(const Schema_2x3::IfcHalfSpaceSolid* hs, TempMesh& result,
const TempMesh& first_operand,
ConversionData& conv);
void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPolygonalBoundedHalfSpace* hs, TempMesh& result,
const TempMesh& first_operand,
ConversionData& conv);
void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid* as, TempMesh& result,
const TempMesh& first_operand,
ConversionData& conv);
// IFCOpenings.cpp
bool GenerateOpenings(std::vector<TempOpening>& openings,
const std::vector<IfcVector3>& nors,
TempMesh& curmesh,
bool check_intersection,
bool generate_connection_geometry,
const IfcVector3& wall_extrusion_axis = IfcVector3(0,1,0));
// IFCCurve.cpp
// ------------------------------------------------------------------------------------------------
// Custom exception for use by members of the Curve class
// ------------------------------------------------------------------------------------------------
class CurveError {
public:
CurveError(const std::string& s)
: mStr(s) {
// empty
}
std::string mStr;
};
// ------------------------------------------------------------------------------------------------
// Temporary representation for an arbitrary sub-class of IfcCurve. Used to sample the curves
// to obtain a list of line segments.
// ------------------------------------------------------------------------------------------------
class Curve {
protected:
Curve(const Schema_2x3::IfcCurve& base_entity, ConversionData& conv)
: base_entity(base_entity)
, conv(conv) {
// empty
}
public:
typedef std::pair<IfcFloat, IfcFloat> ParamRange;
virtual ~Curve() {}
// check if a curve is closed
virtual bool IsClosed() const = 0;
// evaluate the curve at the given parametric position
virtual IfcVector3 Eval(IfcFloat p) const = 0;
// try to match a point on the curve to a given parameter
// for self-intersecting curves, the result is not ambiguous and
// it is undefined which parameter is returned.
virtual bool ReverseEval(const IfcVector3& val, IfcFloat& paramOut) const;
// get the range of the curve (both inclusive).
// +inf and -inf are valid return values, the curve is not bounded in such a case.
virtual std::pair<IfcFloat,IfcFloat> GetParametricRange() const = 0;
IfcFloat GetParametricRangeDelta() const;
// estimate the number of sample points that this curve will require
virtual size_t EstimateSampleCount(IfcFloat start,IfcFloat end) const;
// intelligently sample the curve based on the current settings
// and append the result to the mesh
virtual void SampleDiscrete(TempMesh& out,IfcFloat start,IfcFloat end) const;
#ifdef ASSIMP_BUILD_DEBUG
// check if a particular parameter value lies within the well-defined range
bool InRange(IfcFloat) const;
#endif
static Curve* Convert(const IFC::Schema_2x3::IfcCurve&,ConversionData& conv);
protected:
const Schema_2x3::IfcCurve& base_entity;
ConversionData& conv;
};
// --------------------------------------------------------------------------------
// A BoundedCurve always holds the invariant that GetParametricRange()
// never returns infinite values.
// --------------------------------------------------------------------------------
class BoundedCurve : public Curve {
public:
BoundedCurve(const Schema_2x3::IfcBoundedCurve& entity, ConversionData& conv)
: Curve(entity,conv)
{}
public:
bool IsClosed() const;
public:
// sample the entire curve
void SampleDiscrete(TempMesh& out) const;
using Curve::SampleDiscrete;
};
// IfcProfile.cpp
bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv);
}
}
#endif

View File

@@ -1,432 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file STEPFileEncoding.cpp
* @brief STEP character handling, string un-escaping
*/
#include "STEPFileEncoding.h"
#include <assimp/fast_atof.h>
#ifdef ASSIMP_USE_HUNTER
# include <utf8/utf8.h>
#else
# include <contrib/utf8cpp/source/utf8.h>
#endif
#include <memory>
using namespace Assimp;
// roman1 to utf16 table
static const uint16_t mac_codetable[] = {
// 0x20 unassig./nonprint. slots
0x0020 ,
0x0021 ,
0x0022 ,
0x0023 ,
0x0024 ,
0x0025 ,
0x0026 ,
0x0027 ,
0x0028 ,
0x0029 ,
0x002A ,
0x002B ,
0x002C ,
0x002D ,
0x002E ,
0x002F ,
0x0030 ,
0x0031 ,
0x0032 ,
0x0033 ,
0x0034 ,
0x0035 ,
0x0036 ,
0x0037 ,
0x0038 ,
0x0039 ,
0x003A ,
0x003B ,
0x003C ,
0x003D ,
0x003E ,
0x003F ,
0x0040 ,
0x0041 ,
0x0042 ,
0x0043 ,
0x0044 ,
0x0045 ,
0x0046 ,
0x0047 ,
0x0048 ,
0x0049 ,
0x004A ,
0x004B ,
0x004C ,
0x004D ,
0x004E ,
0x004F ,
0x0050 ,
0x0051 ,
0x0052 ,
0x0053 ,
0x0054 ,
0x0055 ,
0x0056 ,
0x0057 ,
0x0058 ,
0x0059 ,
0x005A ,
0x005B ,
0x005C ,
0x005D ,
0x005E ,
0x005F ,
0x0060 ,
0x0061 ,
0x0062 ,
0x0063 ,
0x0064 ,
0x0065 ,
0x0066 ,
0x0067 ,
0x0068 ,
0x0069 ,
0x006A ,
0x006B ,
0x006C ,
0x006D ,
0x006E ,
0x006F ,
0x0070 ,
0x0071 ,
0x0072 ,
0x0073 ,
0x0074 ,
0x0075 ,
0x0076 ,
0x0077 ,
0x0078 ,
0x0079 ,
0x007A ,
0x007B ,
0x007C ,
0x007D ,
0x007E ,
0x0000 , // unassig.
0x00C4 ,
0x00C5 ,
0x00C7 ,
0x00C9 ,
0x00D1 ,
0x00D6 ,
0x00DC ,
0x00E1 ,
0x00E0 ,
0x00E2 ,
0x00E4 ,
0x00E3 ,
0x00E5 ,
0x00E7 ,
0x00E9 ,
0x00E8 ,
0x00EA ,
0x00EB ,
0x00ED ,
0x00EC ,
0x00EE ,
0x00EF ,
0x00F1 ,
0x00F3 ,
0x00F2 ,
0x00F4 ,
0x00F6 ,
0x00F5 ,
0x00FA ,
0x00F9 ,
0x00FB ,
0x00FC ,
0x2020 ,
0x00B0 ,
0x00A2 ,
0x00A3 ,
0x00A7 ,
0x2022 ,
0x00B6 ,
0x00DF ,
0x00AE ,
0x00A9 ,
0x2122 ,
0x00B4 ,
0x00A8 ,
0x2260 ,
0x00C6 ,
0x00D8 ,
0x221E ,
0x00B1 ,
0x2264 ,
0x2265 ,
0x00A5 ,
0x00B5 ,
0x2202 ,
0x2211 ,
0x220F ,
0x03C0 ,
0x222B ,
0x00AA ,
0x00BA ,
0x03A9 ,
0x00E6 ,
0x00F8 ,
0x00BF ,
0x00A1 ,
0x00AC ,
0x221A ,
0x0192 ,
0x2248 ,
0x2206 ,
0x00AB ,
0x00BB ,
0x2026 ,
0x00A0 ,
0x00C0 ,
0x00C3 ,
0x00D5 ,
0x0152 ,
0x0153 ,
0x2013 ,
0x2014 ,
0x201C ,
0x201D ,
0x2018 ,
0x2019 ,
0x00F7 ,
0x25CA ,
0x00FF ,
0x0178 ,
0x2044 ,
0x20AC ,
0x2039 ,
0x203A ,
0xFB01 ,
0xFB02 ,
0x2021 ,
0x00B7 ,
0x201A ,
0x201E ,
0x2030 ,
0x00C2 ,
0x00CA ,
0x00C1 ,
0x00CB ,
0x00C8 ,
0x00CD ,
0x00CE ,
0x00CF ,
0x00CC ,
0x00D3 ,
0x00D4 ,
0xF8FF ,
0x00D2 ,
0x00DA ,
0x00DB ,
0x00D9 ,
0x0131 ,
0x02C6 ,
0x02DC ,
0x00AF ,
0x02D8 ,
0x02D9 ,
0x02DA ,
0x00B8 ,
0x02DD ,
0x02DB ,
0x02C7
};
// ------------------------------------------------------------------------------------------------
bool STEP::StringToUTF8(std::string& s)
{
// very basic handling for escaped string sequences
// http://doc.spatial.com/index.php?title=InterOp:Connect/STEP&redirect=no
for (size_t i = 0; i < s.size(); ) {
if (s[i] == '\\') {
// \S\X - cp1252 (X is the character remapped to [0,127])
if (i+3 < s.size() && s[i+1] == 'S' && s[i+2] == '\\') {
// http://stackoverflow.com/questions/5586214/how-to-convert-char-from-iso-8859-1-to-utf-8-in-c-multiplatformly
ai_assert((uint8_t)s[i+3] < 0x80);
const uint8_t ch = s[i+3] + 0x80;
s[i] = 0xc0 | (ch & 0xc0) >> 6;
s[i+1] = 0x80 | (ch & 0x3f);
s.erase(i + 2,2);
++i;
}
// \X\xx - mac/roman (xx is a hex sequence)
else if (i+4 < s.size() && s[i+1] == 'X' && s[i+2] == '\\') {
const uint8_t macval = HexOctetToDecimal(s.c_str() + i + 3);
if(macval < 0x20) {
return false;
}
ai_assert(sizeof(mac_codetable) / sizeof(mac_codetable[0]) == 0x100-0x20);
const uint32_t unival = mac_codetable[macval - 0x20], *univalp = &unival;
unsigned char temp[5], *tempp = temp;
ai_assert(sizeof( unsigned char ) == 1);
utf8::utf32to8( univalp, univalp + 1, tempp );
const size_t outcount = static_cast<size_t>(tempp-temp);
s.erase(i,5);
s.insert(i, reinterpret_cast<char*>(temp), outcount);
i += outcount;
}
// \Xn\ .. \X0\ - various unicode encodings (n=2: utf16; n=4: utf32)
else if (i+3 < s.size() && s[i+1] == 'X' && s[i+2] >= '0' && s[i+2] <= '9') {
switch(s[i+2]) {
// utf16
case '2':
// utf32
case '4':
if (s[i+3] == '\\') {
const size_t basei = i+4;
size_t j = basei, jend = s.size()-3;
for (; j < jend; ++j) {
if (s[j] == '\\' && s[j+1] == 'X' && s[j+2] == '0' && s[j+3] == '\\') {
break;
}
}
if (j == jend) {
return false;
}
if (j == basei) {
s.erase(i,8);
continue;
}
if (s[i+2] == '2') {
if (((j - basei) % 4) != 0) {
return false;
}
const size_t count = (j-basei)/4;
std::unique_ptr<uint16_t[]> src(new uint16_t[count]);
const char* cur = s.c_str() + basei;
for (size_t k = 0; k < count; ++k, cur += 4) {
src[k] = (static_cast<uint16_t>(HexOctetToDecimal(cur)) << 8u) |
static_cast<uint16_t>(HexOctetToDecimal(cur+2));
}
const size_t dcount = count * 3; // this is enough to hold all possible outputs
std::unique_ptr<unsigned char[]> dest(new unsigned char[dcount]);
const uint16_t* srct = src.get();
unsigned char* destt = dest.get();
utf8::utf16to8( srct, srct + count, destt );
const size_t outcount = static_cast<size_t>(destt-dest.get());
s.erase(i,(j+4-i));
ai_assert(sizeof(unsigned char) == 1);
s.insert(i, reinterpret_cast<char*>(dest.get()), outcount);
i += outcount;
continue;
}
else if (s[i+2] == '4') {
if (((j - basei) % 8) != 0) {
return false;
}
const size_t count = (j-basei)/8;
std::unique_ptr<uint32_t[]> src(new uint32_t[count]);
const char* cur = s.c_str() + basei;
for (size_t k = 0; k < count; ++k, cur += 8) {
src[k] = (static_cast<uint32_t>(HexOctetToDecimal(cur )) << 24u) |
(static_cast<uint32_t>(HexOctetToDecimal(cur+2)) << 16u) |
(static_cast<uint32_t>(HexOctetToDecimal(cur+4)) << 8u) |
(static_cast<uint32_t>(HexOctetToDecimal(cur+6)));
}
const size_t dcount = count * 5; // this is enough to hold all possible outputs
std::unique_ptr<unsigned char[]> dest(new unsigned char[dcount]);
const uint32_t* srct = src.get();
unsigned char* destt = dest.get();
utf8::utf32to8( srct, srct + count, destt );
const size_t outcount = static_cast<size_t>(destt-dest.get());
s.erase(i,(j+4-i));
ai_assert(sizeof(unsigned char) == 1);
s.insert(i, reinterpret_cast<char*>(dest.get()), outcount);
i += outcount;
continue;
}
}
break;
// TODO: other encoding patterns?
default:
return false;
}
}
}
++i;
}
return true;
}

View File

@@ -1,65 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#ifndef INCLUDED_AI_STEPFILEENCODING_H
#define INCLUDED_AI_STEPFILEENCODING_H
#include <string>
namespace Assimp {
namespace STEP {
// --------------------------------------------------------------------------
// Convert an ASCII STEP identifier with possibly escaped character
// sequences using foreign encodings to plain UTF8.
//
// Return false if an error occurs, s may or may not be modified in
// this case and could still contain escape sequences (even partly
// escaped ones).
bool StringToUTF8(std::string& s);
} // ! STEP
} // ! Assimp
#endif

View File

@@ -1,559 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
/** @file STEPFileReader.cpp
* @brief Implementation of the STEP file parser, which fills a
* STEP::DB with data read from a file.
*/
#include "STEPFileReader.h"
#include "STEPFileEncoding.h"
#include <assimp/TinyFormatter.h>
#include <assimp/fast_atof.h>
#include <memory>
using namespace Assimp;
namespace EXPRESS = STEP::EXPRESS;
#include <functional>
// ------------------------------------------------------------------------------------------------
std::string AddLineNumber(const std::string& s,uint64_t line /*= LINE_NOT_SPECIFIED*/, const std::string& prefix = "")
{
return line == STEP::SyntaxError::LINE_NOT_SPECIFIED ? prefix+s : static_cast<std::string>( (Formatter::format(),prefix,"(line ",line,") ",s) );
}
// ------------------------------------------------------------------------------------------------
std::string AddEntityID(const std::string& s,uint64_t entity /*= ENTITY_NOT_SPECIFIED*/, const std::string& prefix = "")
{
return entity == STEP::TypeError::ENTITY_NOT_SPECIFIED ? prefix+s : static_cast<std::string>( (Formatter::format(),prefix,"(entity #",entity,") ",s));
}
// ------------------------------------------------------------------------------------------------
STEP::SyntaxError::SyntaxError (const std::string& s,uint64_t line /* = LINE_NOT_SPECIFIED */)
: DeadlyImportError(AddLineNumber(s,line))
{
}
// ------------------------------------------------------------------------------------------------
STEP::TypeError::TypeError (const std::string& s,uint64_t entity /* = ENTITY_NOT_SPECIFIED */,uint64_t line /*= LINE_NOT_SPECIFIED*/)
: DeadlyImportError(AddLineNumber(AddEntityID(s,entity),line))
{
}
static const char *ISO_Token = "ISO-10303-21;";
static const char *FILE_SCHEMA_Token = "FILE_SCHEMA";
// ------------------------------------------------------------------------------------------------
STEP::DB* STEP::ReadFileHeader(std::shared_ptr<IOStream> stream) {
std::shared_ptr<StreamReaderLE> reader = std::shared_ptr<StreamReaderLE>(new StreamReaderLE(stream));
std::unique_ptr<STEP::DB> db = std::unique_ptr<STEP::DB>(new STEP::DB(reader));
LineSplitter &splitter = db->GetSplitter();
if (!splitter || *splitter != ISO_Token ) {
throw STEP::SyntaxError("expected magic token: " + std::string( ISO_Token ), 1);
}
HeaderInfo& head = db->GetHeader();
for(++splitter; splitter; ++splitter) {
const std::string& s = *splitter;
if (s == "DATA;") {
// here we go, header done, start of data section
++splitter;
break;
}
// want one-based line numbers for human readers, so +1
const uint64_t line = splitter.get_index()+1;
if (s.substr(0,11) == FILE_SCHEMA_Token) {
const char* sz = s.c_str()+11;
SkipSpaces(sz,&sz);
std::shared_ptr< const EXPRESS::DataType > schema = EXPRESS::DataType::Parse(sz);
// the file schema should be a regular list entity, although it usually contains exactly one entry
// since the list itself is contained in a regular parameter list, we actually have
// two nested lists.
const EXPRESS::LIST* list = dynamic_cast<const EXPRESS::LIST*>(schema.get());
if (list && list->GetSize()) {
list = dynamic_cast<const EXPRESS::LIST*>( (*list)[0].get() );
if (!list) {
throw STEP::SyntaxError("expected FILE_SCHEMA to be a list",line);
}
// XXX need support for multiple schemas?
if (list->GetSize() > 1) {
ASSIMP_LOG_WARN(AddLineNumber("multiple schemas currently not supported",line));
}
const EXPRESS::STRING* string( nullptr );
if (!list->GetSize() || !(string=dynamic_cast<const EXPRESS::STRING*>( (*list)[0].get() ))) {
throw STEP::SyntaxError("expected FILE_SCHEMA to contain a single string literal",line);
}
head.fileSchema = *string;
}
}
// XXX handle more header fields
}
return db.release();
}
namespace {
// ------------------------------------------------------------------------------------------------
// check whether the given line contains an entity definition (i.e. starts with "#<number>=")
bool IsEntityDef(const std::string& snext)
{
if (snext[0] == '#') {
// it is only a new entity if it has a '=' after the
// entity ID.
for(std::string::const_iterator it = snext.begin()+1; it != snext.end(); ++it) {
if (*it == '=') {
return true;
}
if ((*it < '0' || *it > '9') && *it != ' ') {
break;
}
}
}
return false;
}
}
// ------------------------------------------------------------------------------------------------
void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme,
const char* const* types_to_track, size_t len,
const char* const* inverse_indices_to_track, size_t len2)
{
db.SetSchema(scheme);
db.SetTypesToTrack(types_to_track,len);
db.SetInverseIndicesToTrack(inverse_indices_to_track,len2);
const DB::ObjectMap& map = db.GetObjects();
LineSplitter& splitter = db.GetSplitter();
while (splitter) {
bool has_next = false;
std::string s = *splitter;
if (s == "ENDSEC;") {
break;
}
s.erase(std::remove(s.begin(), s.end(), ' '), s.end());
// want one-based line numbers for human readers, so +1
const uint64_t line = splitter.get_index()+1;
// LineSplitter already ignores empty lines
ai_assert(s.length());
if (s[0] != '#') {
ASSIMP_LOG_WARN(AddLineNumber("expected token \'#\'",line));
++splitter;
continue;
}
// ---
// extract id, entity class name and argument string,
// but don't create the actual object yet.
// ---
const std::string::size_type n0 = s.find_first_of('=');
if (n0 == std::string::npos) {
ASSIMP_LOG_WARN(AddLineNumber("expected token \'=\'",line));
++splitter;
continue;
}
const uint64_t id = strtoul10_64(s.substr(1,n0-1).c_str());
if (!id) {
ASSIMP_LOG_WARN(AddLineNumber("expected positive, numeric entity id",line));
++splitter;
continue;
}
std::string::size_type n1 = s.find_first_of('(',n0);
if (n1 == std::string::npos) {
has_next = true;
bool ok = false;
for( ++splitter; splitter; ++splitter) {
const std::string& snext = *splitter;
if (snext.empty()) {
continue;
}
// the next line doesn't start an entity, so maybe it is
// just a continuation for this line, keep going
if (!IsEntityDef(snext)) {
s.append(snext);
n1 = s.find_first_of('(',n0);
ok = (n1 != std::string::npos);
}
else {
break;
}
}
if(!ok) {
ASSIMP_LOG_WARN(AddLineNumber("expected token \'(\'",line));
continue;
}
}
std::string::size_type n2 = s.find_last_of(')');
if (n2 == std::string::npos || n2 < n1 || n2 == s.length() - 1 || s[n2 + 1] != ';') {
has_next = true;
bool ok = false;
for( ++splitter; splitter; ++splitter) {
const std::string& snext = *splitter;
if (snext.empty()) {
continue;
}
// the next line doesn't start an entity, so maybe it is
// just a continuation for this line, keep going
if (!IsEntityDef(snext)) {
s.append(snext);
n2 = s.find_last_of(')');
ok = !(n2 == std::string::npos || n2 < n1 || n2 == s.length() - 1 || s[n2 + 1] != ';');
} else {
break;
}
}
if(!ok) {
ASSIMP_LOG_WARN(AddLineNumber("expected token \')\'",line));
continue;
}
}
if (map.find(id) != map.end()) {
ASSIMP_LOG_WARN(AddLineNumber((Formatter::format(),"an object with the id #",id," already exists"),line));
}
std::string::size_type ns = n0;
do ++ns; while( IsSpace(s.at(ns)));
std::string::size_type ne = n1;
do --ne; while( IsSpace(s.at(ne)));
std::string type = s.substr(ns,ne-ns+1);
std::transform( type.begin(), type.end(), type.begin(), &Assimp::ToLower<char> );
const char* sz = scheme.GetStaticStringForToken(type);
if(sz) {
const std::string::size_type szLen = n2-n1+1;
char* const copysz = new char[szLen+1];
std::copy(s.c_str()+n1,s.c_str()+n2+1,copysz);
copysz[szLen] = '\0';
db.InternInsert(new LazyObject(db,id,line,sz,copysz));
}
if(!has_next) {
++splitter;
}
}
if (!splitter) {
ASSIMP_LOG_WARN("STEP: ignoring unexpected EOF");
}
if ( !DefaultLogger::isNullLogger()){
ASSIMP_LOG_DEBUG((Formatter::format(),"STEP: got ",map.size()," object records with ",
db.GetRefs().size()," inverse index entries"));
}
}
// ------------------------------------------------------------------------------------------------
std::shared_ptr<const EXPRESS::DataType> EXPRESS::DataType::Parse(const char*& inout,uint64_t line, const EXPRESS::ConversionSchema* schema /*= NULL*/)
{
const char* cur = inout;
SkipSpaces(&cur);
if (*cur == ',' || IsSpaceOrNewLine(*cur)) {
throw STEP::SyntaxError("unexpected token, expected parameter",line);
}
// just skip over constructions such as IFCPLANEANGLEMEASURE(0.01) and read only the value
if (schema) {
bool ok = false;
for(const char* t = cur; *t && *t != ')' && *t != ','; ++t) {
if (*t=='(') {
if (!ok) {
break;
}
for(--t;IsSpace(*t);--t);
std::string s(cur,static_cast<size_t>(t-cur+1));
std::transform(s.begin(),s.end(),s.begin(),&ToLower<char> );
if (schema->IsKnownToken(s)) {
for(cur = t+1;*cur++ != '(';);
const std::shared_ptr<const EXPRESS::DataType> dt = Parse(cur);
inout = *cur ? cur+1 : cur;
return dt;
}
break;
}
else if (!IsSpace(*t)) {
ok = true;
}
}
}
if (*cur == '*' ) {
inout = cur+1;
return std::make_shared<EXPRESS::ISDERIVED>();
}
else if (*cur == '$' ) {
inout = cur+1;
return std::make_shared<EXPRESS::UNSET>();
}
else if (*cur == '(' ) {
// start of an aggregate, further parsing is done by the LIST factory constructor
inout = cur;
return EXPRESS::LIST::Parse(inout,line,schema);
}
else if (*cur == '.' ) {
// enum (includes boolean)
const char* start = ++cur;
for(;*cur != '.';++cur) {
if (*cur == '\0') {
throw STEP::SyntaxError("enum not closed",line);
}
}
inout = cur+1;
return std::make_shared<EXPRESS::ENUMERATION>(std::string(start, static_cast<size_t>(cur-start) ));
}
else if (*cur == '#' ) {
// object reference
return std::make_shared<EXPRESS::ENTITY>(strtoul10_64(++cur,&inout));
}
else if (*cur == '\'' ) {
// string literal
const char* start = ++cur;
for(;*cur != '\'';++cur) {
if (*cur == '\0') {
throw STEP::SyntaxError("string literal not closed",line);
}
}
if (cur[1] == '\'') {
// Vesanen: more than 2 escaped ' in one literal!
do {
for(cur += 2;*cur != '\'';++cur) {
if (*cur == '\0') {
throw STEP::SyntaxError("string literal not closed",line);
}
}
}
while(cur[1] == '\'');
}
inout = cur + 1;
// assimp is supposed to output UTF8 strings, so we have to deal
// with foreign encodings.
std::string stemp = std::string(start, static_cast<size_t>(cur - start));
if(!StringToUTF8(stemp)) {
// TODO: route this to a correct logger with line numbers etc., better error messages
ASSIMP_LOG_ERROR("an error occurred reading escape sequences in ASCII text");
}
return std::make_shared<EXPRESS::STRING>(stemp);
}
else if (*cur == '\"' ) {
throw STEP::SyntaxError("binary data not supported yet",line);
}
// else -- must be a number. if there is a decimal dot in it,
// parse it as real value, otherwise as integer.
const char* start = cur;
for(;*cur && *cur != ',' && *cur != ')' && !IsSpace(*cur);++cur) {
if (*cur == '.') {
double f;
inout = fast_atoreal_move<double>(start,f);
return std::make_shared<EXPRESS::REAL>(f);
}
}
bool neg = false;
if (*start == '-') {
neg = true;
++start;
}
else if (*start == '+') {
++start;
}
int64_t num = static_cast<int64_t>( strtoul10_64(start,&inout) );
return std::make_shared<EXPRESS::INTEGER>(neg?-num:num);
}
// ------------------------------------------------------------------------------------------------
std::shared_ptr<const EXPRESS::LIST> EXPRESS::LIST::Parse(const char*& inout,uint64_t line, const EXPRESS::ConversionSchema* schema /*= NULL*/) {
const std::shared_ptr<EXPRESS::LIST> list = std::make_shared<EXPRESS::LIST>();
EXPRESS::LIST::MemberList& members = list->members;
const char* cur = inout;
if (*cur++ != '(') {
throw STEP::SyntaxError("unexpected token, expected \'(\' token at beginning of list",line);
}
// estimate the number of items upfront - lists can grow large
size_t count = 1;
for(const char* c=cur; *c && *c != ')'; ++c) {
count += (*c == ',' ? 1 : 0);
}
members.reserve(count);
for(;;++cur) {
if (!*cur) {
throw STEP::SyntaxError("unexpected end of line while reading list");
}
SkipSpaces(cur,&cur);
if (*cur == ')') {
break;
}
members.push_back( EXPRESS::DataType::Parse(cur,line,schema));
SkipSpaces(cur,&cur);
if (*cur != ',') {
if (*cur == ')') {
break;
}
throw STEP::SyntaxError("unexpected token, expected \',\' or \')\' token after list element",line);
}
}
inout = cur+1;
return list;
}
// ------------------------------------------------------------------------------------------------
static void handleSkippedDepthFromToken(const char *a, int64_t &skip_depth ) {
if (*a == '(') {
++skip_depth;
} else if (*a == ')') {
--skip_depth;
}
}
// ------------------------------------------------------------------------------------------------
static int64_t getIdFromToken(const char *a) {
const char *tmp;
const int64_t num = static_cast<int64_t>(strtoul10_64(a + 1, &tmp));
return num;
}
// ------------------------------------------------------------------------------------------------
STEP::LazyObject::LazyObject(DB& db, uint64_t id,uint64_t /*line*/, const char* const type,const char* args)
: id(id)
, type(type)
, db(db)
, args(args)
, obj() {
// find any external references and store them in the database.
// this helps us emulate STEPs INVERSE fields.
if (!db.KeepInverseIndicesForType(type)) {
return;
}
// do a quick scan through the argument tuple and watch out for entity references
const char *a( args );
int64_t skip_depth( 0 );
while ( *a ) {
handleSkippedDepthFromToken(a, skip_depth);
/*if (*a == '(') {
++skip_depth;
} else if (*a == ')') {
--skip_depth;
}*/
if (skip_depth >= 1 && *a=='#') {
if (*(a + 1) != '#') {
/*const char *tmp;
const int64_t num = static_cast<int64_t>(strtoul10_64(a + 1, &tmp));
db.MarkRef(num, id);*/
db.MarkRef(getIdFromToken(a), id);
} else {
++a;
}
}
++a;
}
}
// ------------------------------------------------------------------------------------------------
STEP::LazyObject::~LazyObject() {
// make sure the right dtor/operator delete get called
if (obj) {
delete obj;
} else {
delete[] args;
}
}
// ------------------------------------------------------------------------------------------------
void STEP::LazyObject::LazyInit() const {
const EXPRESS::ConversionSchema& schema = db.GetSchema();
STEP::ConvertObjectProc proc = schema.GetConverterProc(type);
if (!proc) {
throw STEP::TypeError("unknown object type: " + std::string(type),id);
}
const char* acopy = args;
std::shared_ptr<const EXPRESS::LIST> conv_args = EXPRESS::LIST::Parse(acopy,STEP::SyntaxError::LINE_NOT_SPECIFIED,&db.GetSchema());
delete[] args;
args = NULL;
// if the converter fails, it should throw an exception, but it should never return NULL
try {
obj = proc(db,*conv_args);
}
catch(const TypeError& t) {
// augment line and entity information
throw TypeError(t.what(),id);
}
++db.evaluated_count;
ai_assert(obj);
// store the original id in the object instance
obj->SetID(id);
}

View File

@@ -1,71 +0,0 @@
/*
Open Asset Import Library (assimp)
----------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
#ifndef INCLUDED_AI_STEPFILEREADER_H
#define INCLUDED_AI_STEPFILEREADER_H
#include "code/Step/STEPFile.h"
namespace Assimp {
namespace STEP {
// --------------------------------------------------------------------------
/// @brief Parsing a STEP file is a twofold procedure.
/// 1) read file header and return to caller, who checks if the
/// file is of a supported schema ..
DB* ReadFileHeader(std::shared_ptr<IOStream> stream);
/// 2) read the actual file contents using a user-supplied set of
/// conversion functions to interpret the data.
void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const* types_to_track, size_t len, const char* const* inverse_indices_to_track, size_t len2);
/// @brief Helper to read a file.
template <size_t N, size_t N2>
inline
void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const (&arr)[N], const char* const (&arr2)[N2]) {
return ReadFile(db,scheme,arr,N,arr2,N2);
}
} // ! STEP
} // ! Assimp
#endif // INCLUDED_AI_STEPFILEREADER_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,114 +0,0 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
#ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
#include "StepFileImporter.h"
#include "../../Importer/STEPParser/STEPFileReader.h"
#include <assimp/importerdesc.h>
#include <assimp/DefaultIOSystem.h>
namespace Assimp {
namespace StepFile {
using namespace STEP;
static const aiImporterDesc desc = { "StepFile Importer",
"",
"",
"",
0,
0,
0,
0,
0,
"stp" };
StepFileImporter::StepFileImporter()
: BaseImporter() {
}
StepFileImporter::~StepFileImporter() {
}
bool StepFileImporter::CanRead(const std::string& file, IOSystem* pIOHandler, bool checkSig) const {
const std::string &extension = GetExtension(file);
if ( extension == "stp" || extension == "step" ) {
return true;
} else if ((!extension.length() || checkSig) && pIOHandler) {
const char* tokens[] = { "ISO-10303-21" };
const bool found(SearchFileHeaderForToken(pIOHandler, file, tokens, 1));
return found;
}
return false;
}
const aiImporterDesc *StepFileImporter::GetInfo() const {
return &desc;
}
static const std::string mode = "rb";
static const std::string StepFileSchema = "CONFIG_CONTROL_DESIGN";
void StepFileImporter::InternReadFile(const std::string &file, aiScene* pScene, IOSystem* pIOHandler) {
// Read file into memory
std::shared_ptr<IOStream> fileStream(pIOHandler->Open(file, mode));
if (!fileStream.get()) {
throw DeadlyImportError("Failed to open file " + file + ".");
}
std::unique_ptr<STEP::DB> db(STEP::ReadFileHeader(fileStream));
const STEP::HeaderInfo& head = static_cast<const STEP::DB&>(*db).GetHeader();
if (!head.fileSchema.size() || head.fileSchema != StepFileSchema) {
DeadlyImportError("Unrecognized file schema: " + head.fileSchema);
}
}
} // Namespace StepFile
} // Namespace Assimp
#endif // ASSIMP_BUILD_NO_STEP_IMPORTER

View File

@@ -1,69 +0,0 @@
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
#pragma once
#ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
#include <assimp/BaseImporter.h>
namespace Assimp {
namespace StepFile {
class StepFileImporter : public BaseImporter {
public:
StepFileImporter();
~StepFileImporter();
bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override;
const aiImporterDesc* GetInfo() const override;
protected:
void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler ) override;
private:
};
} // Namespace StepFile
} // Namespace Assimp
#endif // ASSIMP_BUILD_NO_STEP_IMPORTER

File diff suppressed because it is too large Load Diff