diff --git a/src/examples/faces.cpp b/src/examples/faces.cpp
new file mode 100644
index 0000000000..5c765b860c
--- /dev/null
+++ b/src/examples/faces.cpp
@@ -0,0 +1,176 @@
+/********************************************************************************
+ * *
+ * This file is part of IfcOpenShell. *
+ * *
+ * IfcOpenShell is free software: you can redistribute it and/or modify *
+ * it under the terms of the Lesser GNU General Public License as published by *
+ * the Free Software Foundation, either version 3.0 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * IfcOpenShell is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * Lesser GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the Lesser GNU General Public License *
+ * along with this program. If not, see . *
+ * *
+ ********************************************************************************/
+
+/********************************************************************************
+ * *
+ * Example that generates various forms of IfcFace *
+ * *
+ ********************************************************************************/
+
+#include "../ifcparse/Ifc2x3.h"
+#include "../ifcparse/IfcUtil.h"
+#include "../ifcparse/IfcHierarchyHelper.h"
+
+typedef std::string S;
+typedef IfcParse::IfcGlobalId guid;
+boost::none_t const null = (static_cast(0));
+
+static int x = 0;
+
+void create_testcase(IfcHierarchyHelper& file, IfcSchema::IfcFace* face, const std::string& name) {
+ IfcSchema::IfcFace::list::ptr faces(new IfcSchema::IfcFace::list);
+ faces->push(face);
+ IfcSchema::IfcOpenShell* shell = new IfcSchema::IfcOpenShell(faces);
+
+ IfcSchema::IfcConnectedFaceSet::list::ptr shells(new IfcSchema::IfcConnectedFaceSet::list);
+ shells->push(shell);
+ IfcSchema::IfcFaceBasedSurfaceModel* model = new IfcSchema::IfcFaceBasedSurfaceModel(shells);
+
+ IfcSchema::IfcBuildingElementProxy* product = new IfcSchema::IfcBuildingElementProxy(
+ guid(), 0, name, null, null, 0, 0, null, null);
+ file.addBuildingProduct(product);
+ product->setOwnerHistory(file.getSingle());
+
+ product->setObjectPlacement(file.addLocalPlacement(0, 1000 * x++, 0));
+
+ IfcSchema::IfcRepresentation::list::ptr reps (new IfcSchema::IfcRepresentation::list);
+ IfcSchema::IfcRepresentationItem::list::ptr items (new IfcSchema::IfcRepresentationItem::list);
+
+ items->push(model);
+ IfcSchema::IfcShapeRepresentation* rep = new IfcSchema::IfcShapeRepresentation(
+ file.getRepresentationContext("Model"), S("Body"), S("SurfaceModel"), items);
+ reps->push(rep);
+
+ IfcSchema::IfcProductDefinitionShape* shape = new IfcSchema::IfcProductDefinitionShape(0, 0, reps);
+ file.addEntity(shape);
+
+ product->setRepresentation(shape);
+}
+
+int main(int argc, char** argv) {
+ IfcHierarchyHelper file;
+ {
+ IfcSchema::IfcCartesianPoint::list::ptr points (new IfcSchema::IfcCartesianPoint::list);
+ points->push(file.addTriplet(-400, -400, 0));
+ points->push(file.addTriplet(+400, -400, 0));
+ points->push(file.addTriplet(+400, +400, 0));
+ points->push(file.addTriplet(-400, +400, 0));
+ IfcSchema::IfcPolyLoop* loop = new IfcSchema::IfcPolyLoop(points);
+ IfcSchema::IfcFaceOuterBound* bound = new IfcSchema::IfcFaceOuterBound(loop, true);
+
+ IfcSchema::IfcFaceBound::list::ptr bounds (new IfcSchema::IfcFaceBound::list);
+ bounds->push(bound);
+ IfcSchema::IfcFace* face = new IfcSchema::IfcFace(bounds);
+ create_testcase(file, face, "polyloop");
+ }
+ {
+ IfcSchema::IfcCartesianPoint* point1 = file.addTriplet(+400, 0., 0.);
+ IfcSchema::IfcCartesianPoint* point2 = file.addTriplet(-400, 0., 0.);
+ IfcSchema::IfcVertexPoint* vertex1 = new IfcSchema::IfcVertexPoint(point1);
+ IfcSchema::IfcVertexPoint* vertex2 = new IfcSchema::IfcVertexPoint(point2);
+ IfcSchema::IfcCircle* circle = new IfcSchema::IfcCircle(file.addPlacement2d(), 400.);
+ IfcSchema::IfcEdgeCurve* edge1 = new IfcSchema::IfcEdgeCurve(vertex1, vertex2, circle, true);
+ IfcSchema::IfcEdgeCurve* edge2 = new IfcSchema::IfcEdgeCurve(vertex2, vertex1, circle, true);
+ IfcSchema::IfcOrientedEdge* oriented_edge1 = new IfcSchema::IfcOrientedEdge(edge1, true);
+ IfcSchema::IfcOrientedEdge* oriented_edge2 = new IfcSchema::IfcOrientedEdge(edge2, true);
+ IfcSchema::IfcOrientedEdge::list::ptr edges(new IfcSchema::IfcOrientedEdge::list);
+ edges->push(oriented_edge1);
+ edges->push(oriented_edge2);
+ IfcSchema::IfcEdgeLoop* loop = new IfcSchema::IfcEdgeLoop(edges);
+ IfcSchema::IfcFaceOuterBound* bound = new IfcSchema::IfcFaceOuterBound(loop, true);
+
+ IfcSchema::IfcFaceBound::list::ptr bounds (new IfcSchema::IfcFaceBound::list);
+ bounds->push(bound);
+ IfcSchema::IfcFace* face = new IfcSchema::IfcFace(bounds);
+ create_testcase(file, face, "circle");
+ }
+ {
+ IfcSchema::IfcCartesianPoint::list::ptr points (new IfcSchema::IfcCartesianPoint::list);
+ points->push(file.addTriplet(-400, -400, 0));
+ points->push(file.addTriplet(+400, -400, 0));
+ points->push(file.addTriplet(+400, +400, 0));
+ points->push(file.addTriplet(-400, +400, 0));
+ IfcSchema::IfcPolyLoop* loop = new IfcSchema::IfcPolyLoop(points);
+ IfcSchema::IfcFaceOuterBound* outer_bound = new IfcSchema::IfcFaceOuterBound(loop, true);
+
+ IfcSchema::IfcCartesianPoint::list::ptr points2 (new IfcSchema::IfcCartesianPoint::list);
+ points2->push(file.addTriplet(-300, -300, 0));
+ points2->push(file.addTriplet(-100, -300, 0));
+ points2->push(file.addTriplet(-100, +300, 0));
+ points2->push(file.addTriplet(-300, +300, 0));
+ IfcSchema::IfcPolyLoop* loop2 = new IfcSchema::IfcPolyLoop(points2);
+ IfcSchema::IfcFaceBound* inner_bound1 = new IfcSchema::IfcFaceBound(loop2, false);
+
+ IfcSchema::IfcCartesianPoint::list::ptr points3 (new IfcSchema::IfcCartesianPoint::list);
+ points3->push(file.addTriplet(+100, +300, 0));
+ points3->push(file.addTriplet(+300, +300, 0));
+ points3->push(file.addTriplet(+300, -300, 0));
+ points3->push(file.addTriplet(+100, -300, 0));
+ IfcSchema::IfcPolyLoop* loop3 = new IfcSchema::IfcPolyLoop(points3);
+ IfcSchema::IfcFaceBound* inner_bound2 = new IfcSchema::IfcFaceBound(loop3, true);
+
+ IfcSchema::IfcFaceBound::list::ptr bounds (new IfcSchema::IfcFaceBound::list);
+ bounds->push(inner_bound1);
+ bounds->push(outer_bound);
+ bounds->push(inner_bound2);
+ IfcSchema::IfcFace* face = new IfcSchema::IfcFace(bounds);
+ create_testcase(file, face, "polyloop with holes");
+ }
+ {
+ IfcSchema::IfcCartesianPoint::list::ptr points (new IfcSchema::IfcCartesianPoint::list);
+ points->push(file.addTriplet(-400, -400, 0));
+ points->push(file.addTriplet(-100, -400, 0));
+ points->push(file.addTriplet(-100, +400, 0));
+ points->push(file.addTriplet(-400, +400, 0));
+ IfcSchema::IfcPolyLoop* loop = new IfcSchema::IfcPolyLoop(points);
+ IfcSchema::IfcFaceOuterBound* bound1 = new IfcSchema::IfcFaceOuterBound(loop, true);
+
+ IfcSchema::IfcCartesianPoint::list::ptr points2 (new IfcSchema::IfcCartesianPoint::list);
+ points2->push(file.addTriplet(+100, +400, 0));
+ points2->push(file.addTriplet(+400, +400, 0));
+ points2->push(file.addTriplet(+400, -400, 0));
+ points2->push(file.addTriplet(+100, -400, 0));
+ IfcSchema::IfcPolyLoop* loop2 = new IfcSchema::IfcPolyLoop(points2);
+ IfcSchema::IfcFaceOuterBound* bound2 = new IfcSchema::IfcFaceOuterBound(loop2, false);
+
+ IfcSchema::IfcFaceBound::list::ptr bounds (new IfcSchema::IfcFaceBound::list);
+ bounds->push(bound1);
+ bounds->push(bound2);
+ IfcSchema::IfcFace* face = new IfcSchema::IfcFace(bounds);
+ create_testcase(file, face, "multiple outer boundaries (invalid)");
+ }
+ {
+ IfcSchema::IfcCartesianPoint::list::ptr points (new IfcSchema::IfcCartesianPoint::list);
+ points->push(file.addTriplet(-400, -400, 1e-6));
+ points->push(file.addTriplet(+400, -400, 0));
+ points->push(file.addTriplet(+400, +400, 0));
+ points->push(file.addTriplet(-400, +400, 0));
+ IfcSchema::IfcPolyLoop* loop = new IfcSchema::IfcPolyLoop(points);
+ IfcSchema::IfcFaceOuterBound* bound = new IfcSchema::IfcFaceOuterBound(loop, true);
+
+ IfcSchema::IfcFaceBound::list::ptr bounds (new IfcSchema::IfcFaceBound::list);
+ bounds->push(bound);
+ IfcSchema::IfcFace* face = new IfcSchema::IfcFace(bounds);
+ create_testcase(file, face, "imprecise polyloop");
+ }
+ const std::string filename = "faces.ifc";
+ file.header().file_name().name(filename);
+ std::ofstream f(filename.c_str());
+ f << file;
+}
\ No newline at end of file
diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp
index 410345451a..c645788dd1 100644
--- a/src/ifcconvert/IfcConvert.cpp
+++ b/src/ifcconvert/IfcConvert.cpp
@@ -120,10 +120,6 @@ int main(int argc, char** argv) {
"operand before applying the subtraction operation. This may "
"introduce a performance improvement at the risk of failing, in "
"which case the subtraction is applied one-by-one.")
- ("force-ccw-face-orientation",
- "Recompute topological face normals using Newell's Method to "
- "guarantee that face vertices are defined in a Counter Clock "
- "Wise order, even if the faces are not part of a closed shell.")
("disable-opening-subtractions",
"Specifies whether to disable the boolean subtraction of "
"IfcOpeningElement Representations from their RelatingElements.")
@@ -173,7 +169,6 @@ int main(int argc, char** argv) {
const bool convert_back_units = vmap.count("convert-back-units") != 0;
const bool sew_shells = vmap.count("sew-shells") != 0;
const bool merge_boolean_operands = vmap.count("merge-boolean-operands") != 0;
- const bool force_ccw_face_orientation = vmap.count("force-ccw-face-orientation") != 0;
const bool disable_opening_subtractions = vmap.count("disable-opening-subtractions") != 0;
const bool include_entities = vmap.count("include") != 0;
const bool include_plan = vmap.count("plan") != 0;
@@ -240,7 +235,6 @@ int main(int argc, char** argv) {
settings.set(IfcGeom::IteratorSettings::SEW_SHELLS, sew_shells);
settings.set(IfcGeom::IteratorSettings::CONVERT_BACK_UNITS, convert_back_units);
settings.set(IfcGeom::IteratorSettings::FASTER_BOOLEANS, merge_boolean_operands);
- settings.set(IfcGeom::IteratorSettings::FORCE_CCW_FACE_ORIENTATION, force_ccw_face_orientation);
settings.set(IfcGeom::IteratorSettings::DISABLE_OPENING_SUBTRACTIONS, disable_opening_subtractions);
settings.set(IfcGeom::IteratorSettings::INCLUDE_CURVES, include_plan);
settings.set(IfcGeom::IteratorSettings::EXCLUDE_SOLIDS_AND_SURFACES, !include_model);
diff --git a/src/ifcgeom/IfcGeom.h b/src/ifcgeom/IfcGeom.h
index 49a8bf8ef4..78b51bf33a 100644
--- a/src/ifcgeom/IfcGeom.h
+++ b/src/ifcgeom/IfcGeom.h
@@ -81,9 +81,6 @@ public:
// that consist of many faces is really detrimental for the performance.
// Default: 1000
GV_MAX_FACES_TO_SEW,
- // By default singular faces have no explicitly defined orientation, to
- // force faces to be defined CounterClockWise, set this value greater than zero.
- GV_FORCE_CCW_FACE_ORIENTATION,
// The length unit used the creation of TopoDS_Shapes, primarily affects the
// interpretation of IfcCartesianPoints and IfcVector magnitudes
// DefaultL 1.0
diff --git a/src/ifcgeom/IfcGeomFaces.cpp b/src/ifcgeom/IfcGeomFaces.cpp
index 1730e8ffd5..6d9a66d24e 100644
--- a/src/ifcgeom/IfcGeomFaces.cpp
+++ b/src/ifcgeom/IfcGeomFaces.cpp
@@ -47,6 +47,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -70,6 +71,7 @@
#include
#include
#include
+#include
#include
@@ -91,7 +93,6 @@
#include
#include
-#include
#include
#endif
@@ -99,116 +100,181 @@
bool IfcGeom::Kernel::convert(const IfcSchema::IfcFace* l, TopoDS_Shape& face) {
IfcSchema::IfcFaceBound::list::ptr bounds = l->Bounds();
- IfcSchema::IfcFaceBound::list::it it = bounds->begin();
- IfcSchema::IfcLoop* loop = (*it)->Bound();
- // FIXME: The assumption that the first bound is
- // the outer bound is not necessarily correct.
- TopoDS_Wire outer_wire;
- if ( ! convert_wire(loop,outer_wire) ) return false;
- BRepBuilderAPI_MakeFace mf (outer_wire);
- BRepBuilderAPI_FaceError er = mf.Error();
- if ( er == BRepBuilderAPI_NotPlanar ) {
- ShapeFix_ShapeTolerance FTol;
- FTol.SetTolerance(outer_wire, 0.01, TopAbs_WIRE);
- mf.~BRepBuilderAPI_MakeFace();
- new (&mf) BRepBuilderAPI_MakeFace(outer_wire);
- er = mf.Error();
- }
- if ( er != BRepBuilderAPI_FaceDone ) return false;
- if ( bounds->size() == 1 ) {
- face = mf.Face();
- } else {
- for( ++it; it != bounds->end(); ++ it) {
- IfcSchema::IfcLoop* loop = (*it)->Bound();
- TopoDS_Wire wire;
- if ( ! convert_wire(loop,wire) ) return false;
- mf.Add(wire);
- }
- if ( mf.IsDone() ) {
- ShapeFix_Shape sfs(mf.Face());
- sfs.Perform();
- TopoDS_Shape sfs_shape = sfs.Shape();
- bool is_face = sfs_shape.ShapeType() == TopAbs_FACE;
- // This assertion is not strictly necessary. The schema
- // does suggest there to be only one outer bound, but
- // not all models comply with this.
- if ( is_face ) {
- face = TopoDS::Face(sfs_shape);
- } else {
- face = sfs_shape;
- }
- } else {
- return false;
- }
- }
- if ( getValue(GV_FORCE_CCW_FACE_ORIENTATION)>0 ) {
- // Check the orientation of the face by comparing the
- // normal of the topological surface to the Newell's Method's
- // normal. Newell's Method is used for the normal calculation
- // as a simple edge cross product can give opposite results
- // for a concave face boundary.
- // Reference: Graphics Gems III p. 231
- BRepGProp_Face prop(TopoDS::Face(face));
- gp_Vec normal_direction;
- gp_Pnt center;
- double u1,u2,v1,v2;
- prop.Bounds(u1,u2,v1,v2);
- prop.Normal((u1+u2)/2.0,(v1+v2)/2.0,center,normal_direction);
- gp_Dir face_normal1 = gp_Dir(normal_direction.XYZ());
+ Handle(Geom_Surface) face_surface;
+ bool reversed_face_surface = false;
+ const bool is_face_surface = l->is(IfcSchema::Type::IfcFaceSurface);
- double x = 0, y = 0, z = 0;
- gp_Pnt current, previous, first;
- int n = 0;
- // Iterate over the vertices of the outer wire (discarding
- // any potential holes)
- for ( TopExp_Explorer exp(outer_wire,TopAbs_VERTEX);; exp.Next()) {
- unsigned has_more = exp.More();
- if ( has_more ) {
- const TopoDS_Vertex& v = TopoDS::Vertex(exp.Current());
- current = BRep_Tool::Pnt(v);
- } else {
- current = first;
- }
- if ( n ) {
- const double& xn = previous.X();
- const double& yn = previous.Y();
- const double& zn = previous.Z();
- const double& xn1 = current.X();
- const double& yn1 = current.Y();
- const double& zn1 = current.Z();
- x += (yn-yn1)*(zn+zn1);
- y += (xn+xn1)*(zn-zn1);
- z += (xn-xn1)*(yn+yn1);
- } else {
- first = current;
- }
- if ( !has_more ) {
- break;
- }
- previous = current;
- ++n;
- }
+ if (is_face_surface) {
+ IfcSchema::IfcFaceSurface* fs = (IfcSchema::IfcFaceSurface*) l;
+ fs->FaceSurface();
+ // FIXME: Surfaces are interpreted as a TopoDS_Shape
+ TopoDS_Shape surface_shape;
+ if (!convert_shape(fs->FaceSurface(), surface_shape)) return false;
- // If Newell's normal does not point in the same direction
- // as the topological face normal the face orientation is
- // reversed
- gp_Vec face_normal2(x,y,z);
+ // FIXME: Assert this obtaines the only face
+ TopExp_Explorer exp(surface_shape, TopAbs_FACE);
+ if (!exp.More()) return false;
- if (face_normal2.Magnitude() > ALMOST_ZERO) {
- if ( face_normal1.Dot(face_normal2) < 0 ) {
- TopAbs_Orientation o = face.Orientation();
- face.Orientation(o == TopAbs_FORWARD ? TopAbs_REVERSED : TopAbs_FORWARD);
- }
- }
+ TopoDS_Face surface = TopoDS::Face(exp.Current());
+ face_surface = BRep_Tool::Surface(surface);
}
- // It might be a good idea to globally discard faces
- // smaller than a certain treshold value. But for now
- // only when processing IfcConnectedFacesets the small
- // faces are skipped.
- // return face_area(face) > 0.0001;
- return true;
+ const int num_bounds = bounds->size();
+ int num_outer_bounds = 0;
+
+ for (IfcSchema::IfcFaceBound::list::it it = bounds->begin(); it != bounds->end(); ++it) {
+ IfcSchema::IfcFaceBound* bound = *it;
+ if (bound->is(IfcSchema::Type::IfcFaceOuterBound)) num_outer_bounds ++;
+ }
+
+ // The number of outer bounds should be one according to the schema. Also Open Cascade
+ // expects this, but it is not strictly checked. Regardless, if the number is greater,
+ // the face will still be processed as long as there are no holes. A compound of faces
+ // is returned in that case.
+ if (num_bounds > 1 && num_outer_bounds > 1 && num_bounds != num_outer_bounds) {
+ Logger::Message(Logger::LOG_ERROR, "Invalid configuration of boundaries for:", l->entity);
+ return false;
+ }
+
+ TopoDS_Compound compound;
+ BRep_Builder builder;
+ if (num_outer_bounds > 1) {
+ builder.MakeCompound(compound);
+ }
+
+ // The builder is initialized on the heap because of the various different moments
+ // of initialization depending on the configuration of surfaces and boundaries.
+ BRepBuilderAPI_MakeFace* mf = 0;
+
+ bool success = false;
+ int processed = 0;
+
+ for (int process_interior = 0; process_interior <= 1; ++process_interior) {
+ for (IfcSchema::IfcFaceBound::list::it it = bounds->begin(); it != bounds->end(); ++it) {
+ IfcSchema::IfcFaceBound* bound = *it;
+ IfcSchema::IfcLoop* loop = bound->Bound();
+
+ const bool same_sense = bound->Orientation();
+ const bool is_interior =
+ !bound->is(IfcSchema::Type::IfcFaceOuterBound) &&
+ (num_bounds > 1) &&
+ (num_outer_bounds < num_bounds);
+
+ // The exterior face boundary is processed first
+ if (is_interior == !process_interior) continue;
+
+ TopoDS_Wire wire;
+ if (!convert_wire(loop, wire)) break;
+
+ /*
+ The approach below does not result in a significant speed-up
+ if (loop->is(IfcSchema::Type::IfcPolyLoop) && processed == 0 && face_surface.IsNull()) {
+ IfcSchema::IfcPolyLoop* polyloop = (IfcSchema::IfcPolyLoop*) loop;
+ IfcSchema::IfcCartesianPoint::list::ptr points = polyloop->Polygon();
+
+ if (points->size() == 3) {
+ // Help Open Cascade by finding the plane more efficiently
+ IfcSchema::IfcCartesianPoint::list::it point_iterator = points->begin();
+ gp_Pnt a, b, c;
+ convert(*point_iterator++, a);
+ convert(*point_iterator++, b);
+ convert(*point_iterator++, c);
+ const gp_XYZ ab = (b.XYZ() - a.XYZ());
+ const gp_XYZ ac = (c.XYZ() - a.XYZ());
+ const gp_Vec cross = ab.Crossed(ac);
+ if (cross.SquareMagnitude() > ALMOST_ZERO) {
+ const gp_Dir n = cross;
+ face_surface = new Geom_Plane(a, n);
+ }
+ }
+ }
+ */
+
+ if (!same_sense) {
+ wire.Reverse();
+ }
+
+ ShapeFix_ShapeTolerance FTol;
+ FTol.SetTolerance(wire, getValue(GV_PRECISION), TopAbs_WIRE);
+
+ if (!mf) {
+ if (face_surface.IsNull()) {
+ mf = new BRepBuilderAPI_MakeFace(wire);
+ } else {
+ mf = new BRepBuilderAPI_MakeFace(face_surface, wire);
+ }
+
+ /* BRepBuilderAPI_FaceError er = mf->Error();
+ if (er == BRepBuilderAPI_NotPlanar) {
+ ShapeFix_ShapeTolerance FTol;
+ FTol.SetTolerance(wire, getValue(GV_PRECISION), TopAbs_WIRE);
+ delete mf;
+ mf = new BRepBuilderAPI_MakeFace(wire);
+ } */
+
+ if (mf->IsDone()) {
+ TopoDS_Face outer_face_bound = mf->Face();
+
+ // If the wires are reversed the face needs to be reversed as well in order
+ // to maintain the counter-clock-wise ordering of the bounding wire's vertices.
+ bool all_reversed = true;
+ TopoDS_Iterator it(outer_face_bound, false);
+ for (; it.More(); it.Next()) {
+ const TopoDS_Wire& w = TopoDS::Wire(it.Value());
+ if ((w.Orientation() != TopAbs_REVERSED) == same_sense) {
+ all_reversed = false;
+ }
+ }
+
+ if (all_reversed) {
+ outer_face_bound.Reverse();
+ }
+
+ if (num_outer_bounds > 1) {
+ builder.Add(compound, outer_face_bound);
+ delete mf; mf = 0;
+ } else if (num_bounds > 1) {
+ // Reinitialize the builder to the outer face
+ // bound in order to add holes more robustly.
+ delete mf;
+ mf = new BRepBuilderAPI_MakeFace(outer_face_bound);
+ } else {
+ face = outer_face_bound;
+ success = true;
+ }
+ } else {
+ break;
+ }
+
+ } else {
+ mf->Add(wire);
+ }
+ processed ++;
+ }
+ }
+
+ if (!success) {
+ success = processed == num_bounds;
+ if (success) {
+ if (num_outer_bounds > 1) {
+ face = compound;
+ } else {
+ success = success && mf->IsDone();
+ if (success) {
+ face = mf->Face();
+ }
+ }
+ }
+ }
+
+ if (success) {
+ ShapeFix_ShapeTolerance FTol;
+ FTol.SetTolerance(face, getValue(GV_PRECISION), TopAbs_FACE);
+ }
+
+ delete mf;
+ return success;
}
bool IfcGeom::Kernel::convert(const IfcSchema::IfcArbitraryClosedProfileDef* l, TopoDS_Shape& face) {
diff --git a/src/ifcgeom/IfcGeomFunctions.cpp b/src/ifcgeom/IfcGeomFunctions.cpp
index 317f2dd601..bfeac77032 100644
--- a/src/ifcgeom/IfcGeomFunctions.cpp
+++ b/src/ifcgeom/IfcGeomFunctions.cpp
@@ -503,7 +503,6 @@ static double point_equality_tolerance = 0.00001;
static double max_faces_to_sew = -1.0;
static double ifc_length_unit = 1.0;
static double ifc_planeangle_unit = -1.0;
-static double force_ccw_face_orientation = -1.0;
static double modelling_precision = 0.00001;
static double dimensionality = 1;
@@ -530,9 +529,6 @@ void IfcGeom::Kernel::setValue(GeomValue var, double value) {
case GV_PLANEANGLE_UNIT:
ifc_planeangle_unit = value;
break;
- case GV_FORCE_CCW_FACE_ORIENTATION:
- force_ccw_face_orientation = value;
- break;
case GV_PRECISION:
modelling_precision = value;
break;
@@ -562,9 +558,6 @@ double IfcGeom::Kernel::getValue(GeomValue var) {
case GV_PLANEANGLE_UNIT:
return ifc_planeangle_unit;
break;
- case GV_FORCE_CCW_FACE_ORIENTATION:
- return force_ccw_face_orientation;
- break;
case GV_PRECISION:
return modelling_precision;
break;
diff --git a/src/ifcgeom/IfcGeomIterator.h b/src/ifcgeom/IfcGeomIterator.h
index e0d51c22a8..64f1d89972 100644
--- a/src/ifcgeom/IfcGeomIterator.h
+++ b/src/ifcgeom/IfcGeomIterator.h
@@ -450,7 +450,6 @@ namespace IfcGeom {
unit_magnitude = 1.f;
kernel.setValue(IfcGeom::Kernel::GV_MAX_FACES_TO_SEW, settings.sew_shells() ? 1000 : -1);
- kernel.setValue(IfcGeom::Kernel::GV_FORCE_CCW_FACE_ORIENTATION, settings.force_ccw_face_orientation() ? 1 : -1);
kernel.setValue(IfcGeom::Kernel::GV_DIMENSIONALITY, (settings.include_curves() ? (settings.exclude_solids_and_surfaces() ? -1. : 0.) : +1.));
}
public:
diff --git a/src/ifcgeom/IfcGeomIteratorSettings.h b/src/ifcgeom/IfcGeomIteratorSettings.h
index 7b59ea8bbb..7adc5e9463 100644
--- a/src/ifcgeom/IfcGeomIteratorSettings.h
+++ b/src/ifcgeom/IfcGeomIteratorSettings.h
@@ -55,9 +55,6 @@ namespace IfcGeom {
// Specifies whether to compose IfcOpeningElements into a single compound
// in order to speed up the processing of opening subtractions.
static const int FASTER_BOOLEANS = 6;
- // By default singular faces have no explicitly defined orientation, to
- // force faces to be defined CounterClockWise set this to true.
- static const int FORCE_CCW_FACE_ORIENTATION = 7;
// Disables the subtraction of IfcOpeningElement representations from
// the related building element representations.
static const int DISABLE_OPENING_SUBTRACTIONS = 8;
@@ -74,7 +71,7 @@ namespace IfcGeom {
// End of settings enumeration.
private:
- bool _weld_vertices, _use_world_coords, _convert_back_units, _use_brep_data, _sew_shells, _faster_booleans, _force_ccw_face_orientation, _disable_opening_subtractions, _disable_triangulation, _apply_default_materials, _include_curves, _exclude_solids_and_surfaces;
+ bool _weld_vertices, _use_world_coords, _convert_back_units, _use_brep_data, _sew_shells, _faster_booleans, _disable_opening_subtractions, _disable_triangulation, _apply_default_materials, _include_curves, _exclude_solids_and_surfaces;
double _deflection_tolerance;
public:
IteratorSettings()
@@ -84,7 +81,6 @@ namespace IfcGeom {
, _use_brep_data(false)
, _sew_shells(false)
, _faster_booleans(false)
- , _force_ccw_face_orientation(false)
, _disable_opening_subtractions(false)
, _disable_triangulation(false)
, _apply_default_materials(false)
@@ -107,8 +103,6 @@ namespace IfcGeom {
bool& sew_shells() { return _sew_shells; }
const bool& faster_booleans() const { return _faster_booleans; }
bool& faster_booleans() { return _faster_booleans; }
- const bool& force_ccw_face_orientation() const { return _force_ccw_face_orientation; }
- bool& force_ccw_face_orientation() { return _force_ccw_face_orientation; }
const bool& disable_opening_subtractions() const { return _disable_opening_subtractions; }
bool& disable_opening_subtractions() { return _disable_opening_subtractions; }
const bool& disable_triangulation() const { return _disable_triangulation; }
@@ -143,9 +137,6 @@ namespace IfcGeom {
case SEW_SHELLS:
_sew_shells = value;
break;
- case FORCE_CCW_FACE_ORIENTATION:
- _force_ccw_face_orientation = value;
- break;
case DISABLE_OPENING_SUBTRACTIONS:
_disable_opening_subtractions = value;
break;
diff --git a/src/ifcgeom/IfcGeomWires.cpp b/src/ifcgeom/IfcGeomWires.cpp
index 48e5166723..c212ad6422 100644
--- a/src/ifcgeom/IfcGeomWires.cpp
+++ b/src/ifcgeom/IfcGeomWires.cpp
@@ -408,30 +408,15 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcEdgeLoop* l, TopoDS_Wire& resu
IfcSchema::IfcOrientedEdge::list::ptr li = l->EdgeList();
BRepBuilderAPI_MakeWire mw;
for (IfcSchema::IfcOrientedEdge::list::it it = li->begin(); it != li->end(); ++it) {
- IfcSchema::IfcOrientedEdge* e = *it;
-
- IfcSchema::IfcPoint* pnt1 = ((IfcSchema::IfcVertexPoint*) e->EdgeStart())->VertexGeometry();
- IfcSchema::IfcPoint* pnt2 = ((IfcSchema::IfcVertexPoint*) e->EdgeEnd())->VertexGeometry();
- if (!pnt1->is(IfcSchema::Type::IfcCartesianPoint) || !pnt2->is(IfcSchema::Type::IfcCartesianPoint)) {
- Logger::Message(Logger::LOG_ERROR, "Only IfcCartesianPoints are supported for VertexGeometry", l->entity);
- return false;
- }
-
- gp_Pnt p1, p2;
- if (!IfcGeom::Kernel::convert(((IfcSchema::IfcCartesianPoint*)pnt1), p1) ||
- !IfcGeom::Kernel::convert(((IfcSchema::IfcCartesianPoint*)pnt2), p2))
- {
- return false;
- }
-
- mw.Add(BRepBuilderAPI_MakeEdge(p1, p2));
- continue;
-
- IfcSchema::IfcEdge* base = e->EdgeElement();
TopoDS_Wire w;
- if (convert_wire(e->EdgeElement(), w)) {
- if (!e->Orientation()) w.Reverse();
- mw.Add(w);
+ if (convert_wire(*it, w)) {
+ if (!(*it)->Orientation()) w.Reverse();
+ TopoDS_Iterator it(w, false);
+ for (; it.More(); it.Next()) {
+ const TopoDS_Edge& e = TopoDS::Edge(it.Value());
+ mw.Add(e);
+ }
+ // mw.Add(w);
}
}
result = mw;
@@ -466,7 +451,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcEdge* l, TopoDS_Wire& result)
}
bool IfcGeom::Kernel::convert(const IfcSchema::IfcOrientedEdge* l, TopoDS_Wire& result) {
- if (convert(l->EdgeElement(), result)) {
+ if (convert_wire(l->EdgeElement(), result)) {
if (!l->Orientation()) {
result.Reverse();
}
@@ -478,7 +463,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcOrientedEdge* l, TopoDS_Wire&
bool IfcGeom::Kernel::convert(const IfcSchema::IfcSubedge* l, TopoDS_Wire& result) {
TopoDS_Wire temp;
- if (convert(l->ParentEdge(), result) && convert((IfcSchema::IfcEdge*) l, temp)) {
+ if (convert_wire(l->ParentEdge(), result) && convert((IfcSchema::IfcEdge*) l, temp)) {
TopExp_Explorer exp(result, TopAbs_EDGE);
TopoDS_Edge edge = TopoDS::Edge(exp.Current());
Standard_Real u1, u2;
diff --git a/src/ifcgeom/IfcRegister.h b/src/ifcgeom/IfcRegister.h
index 3ce2edf403..648bb70699 100644
--- a/src/ifcgeom/IfcRegister.h
+++ b/src/ifcgeom/IfcRegister.h
@@ -93,6 +93,7 @@ FACE(IfcEllipseProfileDef);
FACE(IfcCenterLineProfileDef);
FACE(IfcCompositeProfileDef);
FACE(IfcDerivedProfileDef);
+// IfcFaceSurface included
FACE(IfcFace);
WIRE(IfcEdgeCurve);
diff --git a/src/ifcgeomserver/IfcGeomServer.cpp b/src/ifcgeomserver/IfcGeomServer.cpp
index d543f60ac6..22eaca9844 100644
--- a/src/ifcgeomserver/IfcGeomServer.cpp
+++ b/src/ifcgeomserver/IfcGeomServer.cpp
@@ -321,7 +321,6 @@ int main (int argc, char** argv) {
settings.use_world_coords() = false;
settings.weld_vertices() = false;
settings.convert_back_units() = true;
- settings.force_ccw_face_orientation() = true;
settings.include_curves() = true;
iterator = new IfcGeom::Iterator(settings, data, len);
diff --git a/src/ifcwrap/IfcGeomWrapper.i b/src/ifcwrap/IfcGeomWrapper.i
index a00e003ae0..666ee342ef 100644
--- a/src/ifcwrap/IfcGeomWrapper.i
+++ b/src/ifcwrap/IfcGeomWrapper.i
@@ -95,7 +95,7 @@
%extend IfcGeom::IteratorSettings {
%pythoncode %{
- attrs = ("convert_back_units", "deflection_tolerance", "disable_opening_subtractions", "disable_triangulation", "faster_booleans", "force_ccw_face_orientation", "sew_shells", "use_brep_data", "use_world_coords", "weld_vertices")
+ attrs = ("convert_back_units", "deflection_tolerance", "disable_opening_subtractions", "disable_triangulation", "faster_booleans", "sew_shells", "use_brep_data", "use_world_coords", "weld_vertices")
def __repr__(self):
return "%s(%s)"%(self.__class__.__name__, ",".join(tuple("%s=%r"%(a, getattr(self, a)()) for a in self.attrs)))
%}
@@ -218,7 +218,6 @@
IfcGeom::Kernel kernel;
kernel.setValue(IfcGeom::Kernel::GV_MAX_FACES_TO_SEW, settings.sew_shells() ? 1000 : -1);
- kernel.setValue(IfcGeom::Kernel::GV_FORCE_CCW_FACE_ORIENTATION, settings.force_ccw_face_orientation() ? 1 : -1);
kernel.setValue(IfcGeom::Kernel::GV_DIMENSIONALITY, (settings.include_curves() ? (settings.exclude_solids_and_surfaces() ? -1. : 0.) : +1.));
IfcSchema::IfcProduct* product = (IfcSchema::IfcProduct*) instance;
diff --git a/test/input/faces.ifc b/test/input/faces.ifc
new file mode 100644
index 0000000000..edbe3a65ff
--- /dev/null
+++ b/test/input/faces.ifc
@@ -0,0 +1,164 @@
+ISO-10303-21;
+HEADER;
+FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
+FILE_NAME('faces.ifc','2015-05-20T10:50:06',(),(),'IfcOpenShell 0.5.0-dev','IfcOpenShell 0.5.0-dev','');
+FILE_SCHEMA(('IFC2X3'));
+ENDSEC;
+DATA;
+#1=IFCCARTESIANPOINT((-400.,-400.,0.));
+#2=IFCCARTESIANPOINT((400.,-400.,0.));
+#3=IFCCARTESIANPOINT((400.,400.,0.));
+#4=IFCCARTESIANPOINT((-400.,400.,0.));
+#5=IFCPERSON($,$,'',$,$,$,$,$);
+#6=IFCORGANIZATION($,'IfcOpenShell',$,$,$);
+#7=IFCPERSONANDORGANIZATION(#5,#6,$);
+#8=IFCAPPLICATION(#6,'0.5.0-dev','IfcOpenShell','IfcOpenShell');
+#9=IFCOWNERHISTORY(#7,#8,$,.ADDED.,1432111806,#7,#8,1432111806);
+#10=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
+#11=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
+#12=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
+#14=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453293),#12);
+#15=IFCCONVERSIONBASEDUNIT(#10,.PLANEANGLEUNIT.,'Degrees',#14);
+#16=IFCUNITASSIGNMENT((#11,#15));
+#17=IFCPROJECT('1HQX190X58kvRECR6voyfc',#9,$,$,$,$,$,(#51),#16);
+#18=IFCDIRECTION((1.,0.,0.));
+#19=IFCDIRECTION((0.,0.,1.));
+#20=IFCCARTESIANPOINT((0.,0.,0.));
+#21=IFCAXIS2PLACEMENT3D(#20,#19,#18);
+#22=IFCLOCALPLACEMENT($,#21);
+#23=IFCSITE('0eyqmWf4z3dANo5Kaef3tu',#9,$,$,$,#22,$,$,.ELEMENT.,$,$,$,$,$);
+#24=IFCRELAGGREGATES('18QNJy24z3FB9H1omtBuo_',#9,$,$,#17,(#23));
+#25=IFCDIRECTION((1.,0.,0.));
+#26=IFCDIRECTION((0.,0.,1.));
+#27=IFCCARTESIANPOINT((0.,0.,0.));
+#28=IFCAXIS2PLACEMENT3D(#27,#26,#25);
+#29=IFCLOCALPLACEMENT(#22,#28);
+#30=IFCBUILDING('3jWWZTz7n64v5lXI7dcOG2',#9,$,$,$,#29,$,$,.ELEMENT.,$,$,$);
+#31=IFCRELAGGREGATES('3I_AMlVAjD7AJyIfSH193g',#9,$,$,#23,(#30));
+#32=IFCDIRECTION((1.,0.,0.));
+#33=IFCDIRECTION((0.,0.,1.));
+#34=IFCCARTESIANPOINT((0.,0.,0.));
+#35=IFCAXIS2PLACEMENT3D(#34,#33,#32);
+#36=IFCLOCALPLACEMENT(#29,#35);
+#37=IFCBUILDINGSTOREY('1_ImA5b7HELAI8raWFYphY',#9,$,$,$,#36,$,$,.ELEMENT.,$);
+#38=IFCRELAGGREGATES('29hL9yBXj6lvk6BXb$wnMF',#9,$,$,#30,(#37));
+#39=IFCBUILDINGELEMENTPROXY('344_rTMFT6RuKWcRH19W6g',#9,'polyloop',$,$,#45,#58,$,$);
+#40=IFCRELCONTAINEDINSPATIALSTRUCTURE('2RQ$SfpKfBYuSRATxRwC0V',#9,$,$,(#39),#37);
+#41=IFCDIRECTION((1.,0.,0.));
+#42=IFCDIRECTION((0.,0.,1.));
+#43=IFCCARTESIANPOINT((0.,0.,0.));
+#44=IFCAXIS2PLACEMENT3D(#43,#42,#41);
+#45=IFCLOCALPLACEMENT($,#44);
+#46=IFCDIRECTION((0.,1.));
+#47=IFCDIRECTION((1.,0.,0.));
+#48=IFCDIRECTION((0.,0.,1.));
+#49=IFCCARTESIANPOINT((0.,0.,0.));
+#50=IFCAXIS2PLACEMENT3D(#49,#48,#47);
+#51=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-005,#50,#46);
+#52=IFCPOLYLOOP((#1,#2,#3,#4));
+#53=IFCFACEOUTERBOUND(#52,.T.);
+#54=IFCFACE((#53));
+#55=IFCOPENSHELL((#54));
+#56=IFCFACEBASEDSURFACEMODEL((#55));
+#57=IFCSHAPEREPRESENTATION(#51,'Body','SurfaceModel',(#56));
+#58=IFCPRODUCTDEFINITIONSHAPE($,$,(#57));
+#59=IFCCARTESIANPOINT((400.,0.,0.));
+#60=IFCCARTESIANPOINT((-400.,0.,0.));
+#61=IFCDIRECTION((1.,0.));
+#62=IFCCARTESIANPOINT((0.,0.));
+#63=IFCAXIS2PLACEMENT2D(#62,#61);
+#64=IFCBUILDINGELEMENTPROXY('35crzRJnnE9Akf8211DDa3',#9,'circle',$,$,#70,#84,$,$);
+#65=IFCRELCONTAINEDINSPATIALSTRUCTURE('3gBsuCqwb3me5EkC8o3Mpn',#9,$,$,(#64),#37);
+#66=IFCDIRECTION((1.,0.,0.));
+#67=IFCDIRECTION((0.,0.,1.));
+#68=IFCCARTESIANPOINT((1000.,0.,0.));
+#69=IFCAXIS2PLACEMENT3D(#68,#67,#66);
+#70=IFCLOCALPLACEMENT($,#69);
+#71=IFCVERTEXPOINT(#59);
+#72=IFCVERTEXPOINT(#60);
+#73=IFCCIRCLE(#63,400.);
+#74=IFCEDGECURVE(#71,#72,#73,.T.);
+#75=IFCORIENTEDEDGE(*,*,#74,.T.);
+#76=IFCEDGECURVE(#72,#71,#73,.T.);
+#77=IFCORIENTEDEDGE(*,*,#76,.T.);
+#78=IFCEDGELOOP((#75,#77));
+#79=IFCFACEOUTERBOUND(#78,.T.);
+#80=IFCFACE((#79));
+#81=IFCOPENSHELL((#80));
+#82=IFCFACEBASEDSURFACEMODEL((#81));
+#83=IFCSHAPEREPRESENTATION(#51,'Body','SurfaceModel',(#82));
+#84=IFCPRODUCTDEFINITIONSHAPE($,$,(#83));
+#85=IFCCARTESIANPOINT((-400.,-400.,0.));
+#86=IFCCARTESIANPOINT((400.,-400.,0.));
+#87=IFCCARTESIANPOINT((400.,400.,0.));
+#88=IFCCARTESIANPOINT((-400.,400.,0.));
+#89=IFCCARTESIANPOINT((-300.,-300.,0.));
+#90=IFCCARTESIANPOINT((-100.,-300.,0.));
+#91=IFCCARTESIANPOINT((-100.,300.,0.));
+#92=IFCCARTESIANPOINT((-300.,300.,0.));
+#93=IFCCARTESIANPOINT((100.,300.,0.));
+#94=IFCCARTESIANPOINT((300.,300.,0.));
+#95=IFCCARTESIANPOINT((300.,-300.,0.));
+#96=IFCCARTESIANPOINT((100.,-300.,0.));
+#97=IFCBUILDINGELEMENTPROXY('3w09rKcILCYAnKVxlLSjOP',#9,'polyloop with holes',$,$,#103,#114,$,$);
+#98=IFCRELCONTAINEDINSPATIALSTRUCTURE('2QRyhTXwH8WPAUUlchhb7x',#9,$,$,(#97),#37);
+#99=IFCDIRECTION((1.,0.,0.));
+#100=IFCDIRECTION((0.,0.,1.));
+#101=IFCCARTESIANPOINT((2000.,0.,0.));
+#102=IFCAXIS2PLACEMENT3D(#101,#100,#99);
+#103=IFCLOCALPLACEMENT($,#102);
+#104=IFCPOLYLOOP((#89,#90,#91,#92));
+#105=IFCFACEBOUND(#104,.F.);
+#106=IFCPOLYLOOP((#85,#86,#87,#88));
+#107=IFCFACEOUTERBOUND(#106,.T.);
+#108=IFCPOLYLOOP((#93,#94,#95,#96));
+#109=IFCFACEBOUND(#108,.T.);
+#110=IFCFACE((#105,#107,#109));
+#111=IFCOPENSHELL((#110));
+#112=IFCFACEBASEDSURFACEMODEL((#111));
+#113=IFCSHAPEREPRESENTATION(#51,'Body','SurfaceModel',(#112));
+#114=IFCPRODUCTDEFINITIONSHAPE($,$,(#113));
+#115=IFCCARTESIANPOINT((-400.,-400.,0.));
+#116=IFCCARTESIANPOINT((-100.,-400.,0.));
+#117=IFCCARTESIANPOINT((-100.,400.,0.));
+#118=IFCCARTESIANPOINT((-400.,400.,0.));
+#119=IFCCARTESIANPOINT((100.,400.,0.));
+#120=IFCCARTESIANPOINT((400.,400.,0.));
+#121=IFCCARTESIANPOINT((400.,-400.,0.));
+#122=IFCCARTESIANPOINT((100.,-400.,0.));
+#123=IFCBUILDINGELEMENTPROXY('0cxEkTWf97T8imCiw2hTqD',#9,'multiple outer boundaries (invalid)',$,$,#129,#138,$,$);
+#124=IFCRELCONTAINEDINSPATIALSTRUCTURE('0$orfSvG18vfnhm9zOB5l5',#9,$,$,(#123),#37);
+#125=IFCDIRECTION((1.,0.,0.));
+#126=IFCDIRECTION((0.,0.,1.));
+#127=IFCCARTESIANPOINT((3000.,0.,0.));
+#128=IFCAXIS2PLACEMENT3D(#127,#126,#125);
+#129=IFCLOCALPLACEMENT($,#128);
+#130=IFCPOLYLOOP((#115,#116,#117,#118));
+#131=IFCFACEOUTERBOUND(#130,.T.);
+#132=IFCPOLYLOOP((#119,#120,#121,#122));
+#133=IFCFACEOUTERBOUND(#132,.F.);
+#134=IFCFACE((#131,#133));
+#135=IFCOPENSHELL((#134));
+#136=IFCFACEBASEDSURFACEMODEL((#135));
+#137=IFCSHAPEREPRESENTATION(#51,'Body','SurfaceModel',(#136));
+#138=IFCPRODUCTDEFINITIONSHAPE($,$,(#137));
+#139=IFCCARTESIANPOINT((-400.,-400.,1.E-006));
+#140=IFCCARTESIANPOINT((400.,-400.,0.));
+#141=IFCCARTESIANPOINT((400.,400.,0.));
+#142=IFCCARTESIANPOINT((-400.,400.,0.));
+#143=IFCBUILDINGELEMENTPROXY('1u73lgHiH3IesmFCYzbd$l',#9,'imprecise polyloop',$,$,#149,#156,$,$);
+#144=IFCRELCONTAINEDINSPATIALSTRUCTURE('0AV1k6Kl19x9ot0jkkQV8h',#9,$,$,(#143),#37);
+#145=IFCDIRECTION((1.,0.,0.));
+#146=IFCDIRECTION((0.,0.,1.));
+#147=IFCCARTESIANPOINT((4000.,0.,0.));
+#148=IFCAXIS2PLACEMENT3D(#147,#146,#145);
+#149=IFCLOCALPLACEMENT($,#148);
+#150=IFCPOLYLOOP((#139,#140,#141,#142));
+#151=IFCFACEOUTERBOUND(#150,.T.);
+#152=IFCFACE((#151));
+#153=IFCOPENSHELL((#152));
+#154=IFCFACEBASEDSURFACEMODEL((#153));
+#155=IFCSHAPEREPRESENTATION(#51,'Body','SurfaceModel',(#154));
+#156=IFCPRODUCTDEFINITIONSHAPE($,$,(#155));
+ENDSEC;
+END-ISO-10303-21;