Arch: New multicore IFC importer

This commit is contained in:
Yorik van Havre
2020-06-25 14:42:38 +02:00
parent 59bdba9c49
commit d43588d240
9 changed files with 379 additions and 19 deletions

View File

@@ -461,7 +461,7 @@ class BuildingPart(ArchIFC.IfcProduct):
return g
def touchChildren(self,obj):
"Touches all descendents where applicable"
for child in obj.Group:
@@ -472,6 +472,15 @@ class BuildingPart(ArchIFC.IfcProduct):
elif Draft.getType(child) in ["Group","BuildingPart"]:
self.touchChildren(child)
def addObject(self,obj,child):
"Adds an object to the group of this BuildingPart"
if not child in obj.Group:
g = obj.Group
g.append(child)
obj.Group = g
class ViewProviderBuildingPart:

View File

@@ -12,7 +12,10 @@ else:
import ArchIFCSchema
IfcTypes = [''.join(map(lambda x: x if x.islower() else " "+x, t[3:]))[1:] for t in ArchIFCSchema.IfcProducts.keys()]
def uncamel(t):
return ''.join(map(lambda x: x if x.islower() else " "+x, t[3:]))[1:]
IfcTypes = [uncamel(t) for t in ArchIFCSchema.IfcProducts.keys()]
class IfcRoot:
"""This class defines the common methods and properties for managing IFC data.

View File

@@ -44,7 +44,7 @@ __url__ = "http://www.freecadweb.org"
# This module provides tools to add materials to
# Arch objects
def makeMaterial(name="Material"):
def makeMaterial(name="Material",color=None,transparency=None):
'''makeMaterial(name): makes an Material object'''
if not FreeCAD.ActiveDocument:
@@ -56,6 +56,12 @@ def makeMaterial(name="Material"):
if FreeCAD.GuiUp:
_ViewProviderArchMaterial(obj.ViewObject)
getMaterialContainer().addObject(obj)
if color:
obj.Color = color[:3]
if len(color) > 3:
obj.Transparency = color[3]*100
if transparency:
obj.Transparency = transparency
return obj

View File

@@ -348,6 +348,15 @@ class _Space(ArchComponent.Component):
objs.append((o.Object,el))
obj.Boundaries = objs
def addObject(self,obj,child):
"Adds an object to this Space"
if not child in obj.Group:
g = obj.Group
g.append(child)
obj.Group = g
def getShape(self,obj):
"computes a shape from a base shape and/or boundary faces"
@@ -358,8 +367,8 @@ class _Space(ArchComponent.Component):
pl = obj.Placement
#print("starting compute")
# 1: if we have a base shape, we use it
# 1: if we have a base shape, we use it
if obj.Base:
if hasattr(obj.Base,'Shape'):
if obj.Base.Shape.Solids:
@@ -379,6 +388,12 @@ class _Space(ArchComponent.Component):
else:
bb.add(b[0].Shape.BoundBox)
if not bb:
# compute area even if we are not calculating the shape
if obj.Shape and obj.Shape.Solids:
if hasattr(obj.Area,"Value"):
a = self.getArea(obj)
if obj.Area.Value != a:
obj.Area = a
return
shape = Part.makeBox(bb.XLength,bb.YLength,bb.ZLength,FreeCAD.Vector(bb.XMin,bb.YMin,bb.ZMin))
#print("created shape from boundbox")

View File

@@ -14,6 +14,7 @@ SET(Arch_SRCS
importIFC.py
importIFClegacy.py
importIFCHelper.py
importIFCmulticore.py
exportIFCHelper.py
Arch.py
ArchBuilding.py

View File

@@ -20,25 +20,25 @@
<property name="margin">
<number>9</number>
</property>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_7">
<property name="text">
<string>Show this dialog when importing</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcShowDialog</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>General options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_7">
<property name="text">
<string>Show this dialog when importing</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcShowDialog</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="gui::prefcheckbox_5">
<property name="toolTip">
@@ -76,6 +76,46 @@ One object is the base object, the others are clones.</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>16</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Number of cores to use:</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefSpinBox" name="spinBox">
<property name="toolTip">
<string>EXPERIMENAL - The number of cores to use in multicore mode. Keep 0 to disable multicore mode, or 1 to use multicore mode in single-core mode (safer if you get crashes). Max value should be your number of cores - 1, ex: 3 of you have a quad-core CPU.</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>ifcMulticore</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Arch</cstring>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@@ -422,6 +462,11 @@ FreeCAD object properties</string>
<layoutdefault spacing="6" margin="11"/>
<pixmapfunction>qPixmapFromMimeSource</pixmapfunction>
<customwidgets>
<customwidget>
<class>Gui::PrefSpinBox</class>
<extends>QSpinBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefCheckBox</class>
<extends>QCheckBox</extends>

View File

@@ -170,7 +170,8 @@ def getPreferences():
'SPLIT_LAYERS': p.GetBool("ifcSplitLayers",False),
'FITVIEW_ONIMPORT': p.GetBool("ifcFitViewOnImport",False),
'ALLOW_INVALID': p.GetBool("ifcAllowInvalid",False),
'REPLACE_PROJECT': p.GetBool("ifcReplaceProject",False)
'REPLACE_PROJECT': p.GetBool("ifcReplaceProject",False),
'MULTICORE':p.GetInt("ifcMulticore",0)
}
if preferences['MERGE_MODE_ARCH'] > 0:
@@ -217,6 +218,10 @@ def insert(srcfile,docname,skip=[],only=[],root=None,preferences=None):
# read preference settings
if preferences is None:
preferences = getPreferences()
if preferences["MULTICORE"] and (not hasattr(srcfile,"by_guid")):
import importIFCmulticore
return importIFCmulticore.insert(srcfile,docname,preferences)
try:
import ifcopenshell

View File

@@ -340,11 +340,24 @@ def buildRelMaterialColors(ifcfile, prodrepr):
pass
def getColorFromMaterial(material):
if material.HasRepresentation:
rep = material.HasRepresentation[0]
if hasattr(rep,"Representations") and rep.Representations:
rep = rep.Representations[0]
if rep.is_a("IfcStyledRepresentation"):
return getColorFromStyledItem(rep)
return None
def getColorFromStyledItem(styled_item):
# styled_item should be a IfcStyledItem
if styled_item.is_a("IfcStyledRepresentation"):
styled_item = styled_item.Items[0]
if not styled_item.is_a("IfcStyledItem"):
print("Not a IfcStyledItem passed.")
return None
rgb_color = None

View File

@@ -0,0 +1,263 @@
# ***************************************************************************
# * Copyright (c) 2020 Yorik van Havre <yorik@uncreated.net> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program 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 *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
from __future__ import print_function
"""FreeCAD IFC importer - Multicore version"""
import sys
import time
import os
import FreeCAD
import Draft
import Arch
import importIFC
import importIFCHelper
from FreeCAD import Base
import ArchIFC
layers = {} # ifcid : Draft_Layer
materials = {} #ifcid : Arch_Material
objects = {} #ifcid : Arch_Component
def open(filename):
"opens an IFC file in a new document"
return insert(filename)
def insert(filename,docname=None,preferences=None):
"""imports the contents of an IFC file in the given document"""
import ifcopenshell
from ifcopenshell import geom
# reset global values
global layers
global materials
global objects
layers = {}
materials = {}
objects = {}
# statistics
starttime = time.time() # in seconds
filesize = os.path.getsize(filename) * 0.000001 # in megabytes
print("Opening",filename+",",round(filesize,2),"Mb")
# setup ifcopenshell
if not preferences:
preferences = importIFC.getPreferences()
settings = ifcopenshell.geom.settings()
settings.set(settings.USE_BREP_DATA,True)
settings.set(settings.SEW_SHELLS,True)
settings.set(settings.USE_WORLD_COORDS,True)
if preferences['SEPARATE_OPENINGS']:
settings.set(settings.DISABLE_OPENING_SUBTRACTIONS,True)
if preferences['SPLIT_LAYERS'] and hasattr(settings,"APPLY_LAYERSETS"):
settings.set(settings.APPLY_LAYERSETS,True)
# setup document
if not FreeCAD.ActiveDocument:
if not docname:
docname = os.path.splitext(os.path.basename(filename))[0]
doc = FreeCAD.newDocument(docname)
doc.Label = docname
FreeCAD.setActiveDocument(doc.Name)
# open the file
ifcfile = ifcopenshell.open(filename)
progressbar = Base.ProgressIndicator()
productscount = len(ifcfile.by_type("IfcProduct"))
progressbar.start("Importing "+str(productscount)+" products...",productscount)
cores = preferences["MULTICORE"]
iterator = ifcopenshell.geom.iterator(settings,ifcfile,cores)
iterator.initialize()
count = 0
# process objects
while True:
item = iterator.get()
if item:
brep = item.geometry.brep_data
ifcproduct = ifcfile.by_id(item.guid)
obj = createProduct(ifcproduct,brep)
progressbar.next(True)
writeProgress(count,productscount,starttime)
count += 1
if not iterator.next():
break
# finished
progressbar.stop()
FreeCAD.ActiveDocument.recompute()
endtime = round(time.time()-starttime,1)
fs = round(filesize,1)
ratio = int(endtime/filesize)
endtime = "%02d:%02d" % (divmod(endtime, 60))
writeProgress() # this cleans the line
print("Finished importing",fs,"Mb in",endtime,"s, or",ratio,"s/Mb")
return FreeCAD.ActiveDocument
def writeProgress(count=None,total=None,starttime=None):
"""write progress to console"""
if not FreeCAD.GuiUp:
if count is None:
sys.stdout.write("\r")
return
r = count/total
elapsed = round(time.time()-starttime,1)
if r:
rest = elapsed*((1-r)/r)
eta = "%02d:%02d" % (divmod(rest, 60))
else:
eta = "--:--"
hashes = '#'*int(r*10)+' '*int(10-r*10)
fstring = '\rImporting '+str(total)+' products... [{0}] {1}%, ETA: {2}'
sys.stdout.write(fstring.format(hashes, int(r*100),eta))
def createProduct(ifcproduct,brep):
"""creates an Arch object from an IFC product"""
import Part
shape = Part.Shape()
shape.importBrepFromString(brep,False)
shape.scale(1000.0) # IfcOpenShell outputs in meters
if ifcproduct.is_a("IfcSpace"):
obj = Arch.makeSpace()
else:
obj = Arch.makeComponent()
obj.Shape = shape
if ifcproduct.Name:
obj.Label = ifcproduct.Name
objects[ifcproduct.id()] = obj
setAttributes(obj,ifcproduct)
setProperties(obj,ifcproduct)
createLayer(obj,ifcproduct)
createMaterial(obj,ifcproduct)
createModelStructure(obj,ifcproduct)
return obj
def setAttributes(obj,ifcproduct):
"""sets the IFC attributes of a component"""
ifctype = ArchIFC.uncamel(ifcproduct.is_a())
if ifctype in ArchIFC.IfcTypes:
obj.IfcType = ifctype
for attr in dir(ifcproduct):
if attr in obj.PropertiesList:
value = getattr(ifcproduct,attr)
if value:
try:
setattr(obj,attr,value)
except:
pass
def setProperties(obj,ifcproduct):
"""sets the IFC properties of a component"""
props = obj.IfcProperties
for prel in ifcproduct.IsDefinedBy:
if prel.is_a("IfcRelDefinesByProperties"):
pset = prel.RelatingPropertyDefinition
if pset.is_a("IfcPropertySet"):
for prop in pset.HasProperties:
if hasattr(prop,"NominalValue"):
propname = prop.Name+";;"+pset.Name
v = [p.strip("'") for p in str(prop.NominalValue).strip(")").split(")")]
propvalue = ";;".join(v)
def createLayer(obj,ifcproduct):
"""sets the layer of a component"""
global layers
if ifcproduct.Representation:
for rep in ifcproduct.Representation.Representations:
for layer in rep.LayerAssignments:
if not layer.id() in layers:
layers[layer.id()] = Draft.makeLayer(layer.Name)
layers[layer.id()].Proxy.addObject(layers[layer.id()],obj)
def createMaterial(obj,ifcproduct):
"""sets the material of a component"""
global materials
for association in ifcproduct.HasAssociations:
if association.is_a("IfcRelAssociatesMaterial"):
material = association.RelatingMaterial
if material.is_a("IfcMaterialList"):
material = material.Materials[0] # take the first one for now...
if material.is_a("IfcMaterial"):
if not material.id() in materials:
color = importIFCHelper.getColorFromMaterial(material)
materials[material.id()] = Arch.makeMaterial(material.Name,color=color)
obj.Material = materials[material.id()]
def createModelStructure(obj,ifcobj):
"""sets the parent containers of an IFC object"""
global objects
parentlist = []
if hasattr(ifcobj,"ContainedInStructure"):
for rel in ifcobj.ContainedInStructure:
parentlist.append(rel.RelatingStructure)
elif hasattr(ifcobj,"Decomposes"):
for rel in ifcobj.Decomposes:
if rel.is_a("IfcRelAggregates"):
parentlist.append(rel.RelatingObject)
for parent in parentlist:
if not parent.id() in objects:
if parent.is_a("IfcProject"):
parentobj = Arch.makeProject()
elif parent.is_a("IfcSite"):
parentobj = Arch.makeSite()
else:
parentobj = Arch.makeBuildingPart()
parentobj.Label = parent.Name
setAttributes(parentobj,parent)
setProperties(parentobj,parent)
createModelStructure(parentobj,parent)
objects[parent.id()] = parentobj
if hasattr(objects[parent.id()].Proxy,"addObject"):
objects[parent.id()].Proxy.addObject(objects[parent.id()],obj)