Skip to content

Commit 2b27b86

Browse files
committed
- IfcGeom: Major performance improvement by only sewing faces of IfcConnectedFaceSets when needed for boolean operations
- Automatic tests for several well known IFC files on the Internet - IfcBlender: Make visibility in render consistent with 3d view - Some compatibility for Ifc2x files with IfcShapeRepresentations and RepresentationIdentifier 'IAI'
1 parent a9a5f8d commit 2b27b86

File tree

7 files changed

+395
-12
lines changed

7 files changed

+395
-12
lines changed

src/ifcblender/io_import_scene_ifc/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def import_ifc(filename, use_names, process_relations):
7878
else:
7979
valid_file = IfcImport.Init(filename)
8080
if not valid_file:
81+
IfcImport.CleanUp()
8182
return False
8283
print("Done reading file")
8384
id_to_object = {}
@@ -136,6 +137,7 @@ def import_ifc(filename, use_names, process_relations):
136137
ob.id, ob_guid, ob_name, ob_type
137138

138139
bob.hide = ob_type == 'IfcSpace' or ob_type == 'IfcOpeningElement'
140+
bob.hide_render = bob.hide
139141

140142
if ob.id not in id_to_object: id_to_object[ob.id] = []
141143
id_to_object[ob.id].append(bob)

src/ifcgeom/IfcGeom.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242

4343
#include "../ifcgeom/IfcShapeList.h"
4444

45+
#define FACESET_AS_COMPOUND 1
46+
4547
namespace IfcGeom {
4648
bool convert_wire_to_face(const TopoDS_Wire& wire, TopoDS_Face& face);
4749
bool convert_shapes(const IfcUtil::IfcBaseClass* L, ShapeList& result);
@@ -51,6 +53,9 @@ namespace IfcGeom {
5153
bool convert_curve(const IfcUtil::IfcBaseClass* L, Handle(Geom_Curve)& result);
5254
bool convert_face(const IfcUtil::IfcBaseClass* L, TopoDS_Face& result);
5355
bool convert_openings(const Ifc2x3::IfcProduct::ptr entity, const Ifc2x3::IfcRelVoidsElement::list& openings, const ShapeList& entity_shapes, const gp_Trsf& entity_trsf, ShapeList& cut_shapes);
56+
bool create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& solid);
57+
bool is_compound(const TopoDS_Shape& shape);
58+
const TopoDS_Shape& ensure_fit_for_subtraction(const TopoDS_Shape& shape, TopoDS_Shape& solid);
5459
bool profile_helper(int numVerts, float* verts, int numFillets, int* filletIndices, float* filletRadii, gp_Trsf2d trsf, TopoDS_Face& face);
5560
float shape_volume(const TopoDS_Shape& s);
5661
namespace Cache {

src/ifcgeom/IfcGeomFunctions.cpp

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,40 @@
8282

8383
#include "../ifcgeom/IfcGeom.h"
8484

85+
bool IfcGeom::create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& shape) {
86+
BRepOffsetAPI_Sewing builder;
87+
builder.SetTolerance(0.01);
88+
TopExp_Explorer exp(compound,TopAbs_FACE);
89+
if ( ! exp.More() ) return false;
90+
for ( ; exp.More(); exp.Next() ) {
91+
TopoDS_Face face = TopoDS::Face(exp.Current());
92+
builder.Add(face);
93+
}
94+
builder.Perform();
95+
shape = builder.SewedShape();
96+
try {
97+
ShapeFix_Solid sf_solid;
98+
sf_solid.LimitTolerance(0.01);
99+
shape = sf_solid.SolidFromShell(TopoDS::Shell(shape));
100+
} catch(...) {}
101+
return true;
102+
}
103+
104+
bool IfcGeom::is_compound(const TopoDS_Shape& shape) {
105+
bool has_solids = TopExp_Explorer(shape,TopAbs_SHELL).More() != 0;
106+
bool has_shells = TopExp_Explorer(shape,TopAbs_SHELL).More() != 0;
107+
bool has_compounds = TopExp_Explorer(shape,TopAbs_COMPOUND).More() != 0;
108+
bool has_faces = TopExp_Explorer(shape,TopAbs_FACE).More() != 0;
109+
return has_compounds && has_faces && !has_solids && !has_shells;
110+
}
111+
112+
const TopoDS_Shape& IfcGeom::ensure_fit_for_subtraction(const TopoDS_Shape& shape, TopoDS_Shape& solid) {
113+
const bool is_comp = IfcGeom::is_compound(shape);
114+
if ( ! is_comp ) return shape;
115+
IfcGeom::create_solid_from_compound(shape,solid);
116+
return solid;
117+
}
118+
85119
bool IfcGeom::convert_openings(const Ifc2x3::IfcProduct::ptr entity, const Ifc2x3::IfcRelVoidsElement::list& openings,
86120
const ShapeList& entity_shapes, const gp_Trsf& entity_trsf, ShapeList& cut_shapes) {
87121
// Iterate over IfcOpeningElements
@@ -116,7 +150,8 @@ bool IfcGeom::convert_openings(const Ifc2x3::IfcProduct::ptr entity, const Ifc2x
116150

117151
// Iterate over the shapes of the IfcProduct
118152
for ( IfcGeom::ShapeList::const_iterator it3 = entity_shapes.begin(); it3 != entity_shapes.end(); ++ it3 ) {
119-
const TopoDS_Shape& entity_shape_unlocated = *(it3->second);
153+
TopoDS_Shape entity_shape_solid;
154+
const TopoDS_Shape& entity_shape_unlocated = IfcGeom::ensure_fit_for_subtraction(*(it3->second),entity_shape_solid);
120155
const gp_GTrsf& entity_shape_gtrsf = *(it3->first);
121156
TopoDS_Shape entity_shape;
122157
if ( entity_shape_gtrsf.Form() == gp_Other ) {
@@ -128,7 +163,8 @@ bool IfcGeom::convert_openings(const Ifc2x3::IfcProduct::ptr entity, const Ifc2x
128163

129164
// Iterate over the shapes of the IfcOpeningElements
130165
for ( IfcGeom::ShapeList::const_iterator it4 = opening_shapes.begin(); it4 != opening_shapes.end(); ++ it4 ) {
131-
const TopoDS_Shape& opening_shape_unlocated = *(it4->second);
166+
TopoDS_Shape opening_shape_solid;
167+
const TopoDS_Shape& opening_shape_unlocated = IfcGeom::ensure_fit_for_subtraction(*(it4->second),opening_shape_solid);
132168
const gp_GTrsf& opening_shape_gtrsf = *(it4->first);
133169
if ( opening_shape_gtrsf.Form() == gp_Other ) {
134170
Ifc::LogMessage("warning","Applying non uniform transformation to opening of:",entity->entity);
@@ -143,12 +179,16 @@ bool IfcGeom::convert_openings(const Ifc2x3::IfcProduct::ptr entity, const Ifc2x
143179

144180
const float original_shape_volume = shape_volume(entity_shape);
145181

146-
entity_shape = BRepAlgoAPI_Cut(entity_shape,opening_shape);
182+
BRepAlgoAPI_Cut brep_cut(entity_shape,opening_shape);
183+
184+
if ( brep_cut.IsDone() ) {
185+
entity_shape = brep_cut;
147186

148-
const float volume_after_subtraction = shape_volume(entity_shape);
187+
const float volume_after_subtraction = shape_volume(entity_shape);
149188

150-
if ( ALMOST_THE_SAME(original_shape_volume,volume_after_subtraction) )
151-
Ifc::LogMessage("warning","Warning subtraction yields unchanged volume:",entity->entity);
189+
if ( ALMOST_THE_SAME(original_shape_volume,volume_after_subtraction) )
190+
Ifc::LogMessage("warning","Warning subtraction yields unchanged volume:",entity->entity);
191+
}
152192
}
153193
cut_shapes.push_back(IfcGeom::LocationShape(new gp_GTrsf(),new TopoDS_Shape(entity_shape)));
154194
}

src/ifcgeom/IfcGeomObjects.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,10 +299,15 @@ IfcGeomObjects::IfcGeomObject* _get() {
299299

300300
// Has the list of IfcProducts for this representation been initialized?
301301
if ( ! entities ) {
302-
if ( shaperep->RepresentationIdentifier() != "Body" &&
303-
shaperep->RepresentationIdentifier() != "Facetation" ) {
304-
_nextShape();
305-
continue;
302+
if ( shaperep->hasRepresentationIdentifier() ) {
303+
const std::string representation_identifier = shaperep->RepresentationIdentifier();
304+
if ( shaperep->hasRepresentationType() && representation_identifier == "IAI" && shaperep->RepresentationType() != "BoundingBox" ) {
305+
// Allow for Ifc 2x compatibility
306+
} else if ( representation_identifier != "Body" &&
307+
representation_identifier != "Facetation" ) {
308+
_nextShape();
309+
continue;
310+
}
306311
}
307312
Ifc2x3::IfcProductRepresentation::list prodreps = shaperep->OfProductRepresentation();
308313
entities = Ifc2x3::IfcProduct::list( new IfcTemplatedEntityList<Ifc2x3::IfcProduct>() );

src/ifcgeom/IfcGeomShapes.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ bool IfcGeom::convert(const Ifc2x3::IfcPolygonalBoundedHalfSpace::ptr l, TopoDS_
132132
if ( ! IfcGeom::convert_wire(l->PolygonalBoundary(),wire) || ! wire.Closed() ) return false;
133133
gp_Trsf trsf;
134134
convert(l->Position(),trsf);
135-
TopoDS_Shape extrusion = BRepPrimAPI_MakePrism(BRepBuilderAPI_MakeFace(wire),gp_Vec(0,0,20000.0));
136-
gp_Trsf down; down.SetTranslation(gp_Vec(0,0,-10000.0));
135+
TopoDS_Shape extrusion = BRepPrimAPI_MakePrism(BRepBuilderAPI_MakeFace(wire),gp_Vec(0,0,200.0));
136+
gp_Trsf down; down.SetTranslation(gp_Vec(0,0,-100.0));
137137
extrusion.Move(down*trsf);
138138
shape = BRepAlgoAPI_Cut(extrusion,halfspace);
139139
return true;
@@ -179,27 +179,41 @@ bool IfcGeom::convert(const Ifc2x3::IfcBooleanClippingResult::ptr l, TopoDS_Shap
179179
return true;
180180
}
181181
bool IfcGeom::convert(const Ifc2x3::IfcConnectedFaceSet::ptr l, TopoDS_Shape& shape) {
182+
#ifdef FACESET_AS_COMPOUND
183+
TopoDS_Compound compound;
184+
BRep_Builder builder;
185+
builder.MakeCompound(compound);
186+
#else
182187
BRepOffsetAPI_Sewing builder;
183188
builder.SetTolerance(0.01);
189+
#endif
184190
Ifc2x3::IfcFace::list faces = l->CfsFaces();
185191
bool facesAdded = false;
186192
for( Ifc2x3::IfcFace::it it = faces->begin(); it != faces->end(); ++ it ) {
187193
TopoDS_Face face;
188194
if ( IfcGeom::convert_face(*it,face) ) {
195+
#ifdef FACESET_AS_COMPOUND
196+
builder.Add(compound,face);
197+
#else
189198
builder.Add(face);
199+
#endif
190200
facesAdded = true;
191201
} else {
192202
Ifc::LogMessage("Warning","Invalid face:",(*it)->entity);
193203
}
194204
}
195205
if ( ! facesAdded ) return false;
206+
#ifdef FACESET_AS_COMPOUND
207+
shape = compound;
208+
#else
196209
builder.Perform();
197210
shape = builder.SewedShape();
198211
try {
199212
ShapeFix_Solid solid;
200213
solid.LimitTolerance(0.01);
201214
shape = solid.SolidFromShell(TopoDS::Shell(shape));
202215
} catch(...) {}
216+
#endif
203217
return true;
204218
}
205219
bool IfcGeom::convert(const Ifc2x3::IfcMappedItem::ptr l, ShapeList& shapes) {

test/bpy.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
###############################################################################
2+
# #
3+
# This file is part of IfcOpenShell. #
4+
# #
5+
# IfcOpenShell is free software: you can redistribute it and/or modify #
6+
# it under the terms of the Lesser GNU General Public License as published by #
7+
# the Free Software Foundation, either version 3.0 of the License, or #
8+
# (at your option) any later version. #
9+
# #
10+
# IfcOpenShell is distributed in the hope that it will be useful, #
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
13+
# Lesser GNU General Public License for more details. #
14+
# #
15+
# You should have received a copy of the Lesser GNU General Public License #
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
17+
# #
18+
###############################################################################
19+
20+
###############################################################################
21+
# #
22+
# Do not run this script directly, it is called by run.py #
23+
# #
24+
###############################################################################
25+
26+
import os
27+
import bpy
28+
import sys
29+
import time
30+
try: from io_import_scene_ifc import import_ifc
31+
except:
32+
print("[Error] Unable to import IfcOpenShell")
33+
sys.exit(1)
34+
from mathutils import Vector as V
35+
from math import radians
36+
37+
scn = bpy.context.scene
38+
39+
for ob in bpy.data.objects:
40+
scn.objects.unlink(ob)
41+
42+
if sys.argv[-2] != 'render': sys.exit(0)
43+
44+
fn = sys.argv[-1]
45+
t1 = time.time()
46+
succes = import_ifc(fn,True,False)
47+
if not succes:
48+
print("[Error] Import of %s failed"%fn)
49+
sys.exit(1)
50+
51+
dt = time.time()-t1
52+
print ("[Notice] Conversion took %.2f seconds"%dt)
53+
54+
cam = bpy.data.cameras.new('cam')
55+
cam.angle = radians(45)
56+
cam_ob = bpy.data.objects.new('cam',cam)
57+
scn.objects.link(cam_ob)
58+
cam_ob.rotation_euler = (radians(67),0,radians(45))
59+
60+
scn.camera = cam_ob
61+
scn.render.resolution_percentage = 100
62+
scn.render.resolution_x = 2048
63+
scn.render.resolution_y = 2048
64+
scn.world.horizon_color = (1,1,1)
65+
scn.world.ambient_color = (0.05,0.05,0.05)
66+
scn.render.color_mode = 'RGBA'
67+
68+
for m in bpy.data.materials: m.specular_intensity = 0
69+
def material(name,settings):
70+
m = bpy.data.materials.get(name)
71+
if not m: return
72+
for k,v in settings.items():
73+
setattr(m,k,v)
74+
75+
material("IfcWall",{"diffuse_color":[1,1,1],"diffuse_intensity":1})
76+
material("IfcWallStandardCase",{"diffuse_color":[1,1,1],"diffuse_intensity":1})
77+
material("IfcSite",{"diffuse_color":[0.4,0.5,0.25]})
78+
material("IfcSlab",{"diffuse_color":[0.2,0.2,0.2]})
79+
material("IfcDoor",{"diffuse_color":[0.25,0.1,0.05]})
80+
material("IfcWindow",{"diffuse_color":[0.5,0.7,0.5],"use_transparency":True,"alpha":0.3})
81+
material("IfcRoof",{"diffuse_color":[0.35,0.2,0.2]})
82+
material("IfcFurnishingElement",{"diffuse_color":[0.8,0.7,0.5]})
83+
material("IfcBuildingElementPro",{"diffuse_intensity":0.6})
84+
85+
def create_lamp(eul):
86+
l = bpy.data.lamps.new('l','SUN')
87+
l.energy = 1.3
88+
l_ob = bpy.data.objects.new('l',l)
89+
scn.objects.link(l_ob)
90+
l_ob.rotation_euler = [radians(e) for e in eul]
91+
92+
create_lamp((20,0,60))
93+
create_lamp((0,-120,-50))
94+
95+
A = V([1e9]*3)
96+
B = V([-1e9]*3)
97+
98+
for ob in scn.objects:
99+
if ob.type != 'MESH': continue
100+
if ob.hide: continue
101+
bb = [ob.matrix_world*V(x) for x in ob.bound_box]
102+
for b in bb:
103+
for i in range(3):
104+
if b[i] < A[i]: A[i] = b[i]
105+
if b[i] > B[i]: B[i] = b[i]
106+
107+
C = V((1.2,-1.2,0.6))
108+
D = B-A
109+
E = max(D)*C + (A+B)/2
110+
111+
cam_ob.location = E
112+
cam.clip_end = max(D) * 10
113+
114+
scn.render.filepath = os.path.join("output",os.path.basename(fn)+".png")
115+
116+
bpy.ops.render.render(write_still=True)
117+
bpy.ops.wm.save_mainfile(filepath=os.path.join("output",os.path.basename(fn)+".blend"),compress=True)
118+
119+
sys.exit(0)

0 commit comments

Comments
 (0)