From ec6ea813c5c21a9dda43314bb773b97a7507b8ae Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Thu, 25 Jun 2020 14:42:38 +0200 Subject: [PATCH] Arch: New multicore IFC importer --- src/Mod/Arch/ArchBuildingPart.py | 11 +- src/Mod/Arch/ArchIFC.py | 5 +- src/Mod/Arch/ArchMaterial.py | 8 +- src/Mod/Arch/ArchSpace.py | 17 +- src/Mod/Arch/CMakeLists.txt | 1 + src/Mod/Arch/Resources/ui/preferences-ifc.ui | 71 ++++- src/Mod/Arch/importIFC.py | 7 +- src/Mod/Arch/importIFCHelper.py | 15 +- src/Mod/Arch/importIFCmulticore.py | 263 +++++++++++++++++++ 9 files changed, 379 insertions(+), 19 deletions(-) create mode 100644 src/Mod/Arch/importIFCmulticore.py diff --git a/src/Mod/Arch/ArchBuildingPart.py b/src/Mod/Arch/ArchBuildingPart.py index 5f4d5497e1..ce7c7fd9cc 100644 --- a/src/Mod/Arch/ArchBuildingPart.py +++ b/src/Mod/Arch/ArchBuildingPart.py @@ -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: diff --git a/src/Mod/Arch/ArchIFC.py b/src/Mod/Arch/ArchIFC.py index 0f39cc14e7..f2502b22d1 100644 --- a/src/Mod/Arch/ArchIFC.py +++ b/src/Mod/Arch/ArchIFC.py @@ -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. diff --git a/src/Mod/Arch/ArchMaterial.py b/src/Mod/Arch/ArchMaterial.py index de817b83f6..c4481e35e8 100644 --- a/src/Mod/Arch/ArchMaterial.py +++ b/src/Mod/Arch/ArchMaterial.py @@ -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 diff --git a/src/Mod/Arch/ArchSpace.py b/src/Mod/Arch/ArchSpace.py index 793841fab7..ae8160e71d 100644 --- a/src/Mod/Arch/ArchSpace.py +++ b/src/Mod/Arch/ArchSpace.py @@ -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") diff --git a/src/Mod/Arch/CMakeLists.txt b/src/Mod/Arch/CMakeLists.txt index 1e9a2041cd..b12846a045 100644 --- a/src/Mod/Arch/CMakeLists.txt +++ b/src/Mod/Arch/CMakeLists.txt @@ -14,6 +14,7 @@ SET(Arch_SRCS importIFC.py importIFClegacy.py importIFCHelper.py + importIFCmulticore.py exportIFCHelper.py Arch.py ArchBuilding.py diff --git a/src/Mod/Arch/Resources/ui/preferences-ifc.ui b/src/Mod/Arch/Resources/ui/preferences-ifc.ui index c1ad754e10..5ce5a33834 100644 --- a/src/Mod/Arch/Resources/ui/preferences-ifc.ui +++ b/src/Mod/Arch/Resources/ui/preferences-ifc.ui @@ -20,25 +20,25 @@ 9 - - - - Show this dialog when importing - - - ifcShowDialog - - - Mod/Arch - - - General options + + + + Show this dialog when importing + + + ifcShowDialog + + + Mod/Arch + + + @@ -76,6 +76,46 @@ One object is the base object, the others are clones. + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 16 + 20 + + + + + + + + Number of cores to use: + + + + + + + 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. + + + ifcMulticore + + + Mod/Arch + + + + + @@ -422,6 +462,11 @@ FreeCAD object properties qPixmapFromMimeSource + + Gui::PrefSpinBox + QSpinBox +
Gui/PrefWidgets.h
+
Gui::PrefCheckBox QCheckBox diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index 01afd84675..2aeca48d81 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -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 diff --git a/src/Mod/Arch/importIFCHelper.py b/src/Mod/Arch/importIFCHelper.py index a07f6324e2..d05f8dc719 100644 --- a/src/Mod/Arch/importIFCHelper.py +++ b/src/Mod/Arch/importIFCHelper.py @@ -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 diff --git a/src/Mod/Arch/importIFCmulticore.py b/src/Mod/Arch/importIFCmulticore.py new file mode 100644 index 0000000000..5065a5135a --- /dev/null +++ b/src/Mod/Arch/importIFCmulticore.py @@ -0,0 +1,263 @@ +# *************************************************************************** +# * Copyright (c) 2020 Yorik van Havre * +# * * +# * 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) +