From f378fca56aa343fceb386350849ba634658767b8 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Wed, 18 Jul 2018 15:07:54 -0300 Subject: [PATCH] Arch: Support of shared profiles in extrusions imported from IFC --- src/Mod/Arch/ArchComponent.py | 59 +++++++++++------- src/Mod/Arch/importIFC.py | 110 +++++++++++++++++++++++----------- 2 files changed, 113 insertions(+), 56 deletions(-) diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index c633804be3..530fa2d3de 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -382,6 +382,8 @@ class Component: if hasattr(obj.Base,"LengthFwd"): if obj.Base.LengthFwd.Value: extrusion = extrusion.multiply(obj.Base.LengthFwd.Value) + if not self.isIdentity(obj.Base.Placement): + placement = placement.multiply(obj.Base.Placement) return (base,extrusion,placement) elif obj.Base.isDerivedFrom("Part::MultiFuse"): rshapes = [] @@ -421,6 +423,7 @@ class Component: """returns a shape that is a copy of the original shape but centered on the (0,0) origin, and a placement that is needed to reposition that shape to its original location/orientation""" + import DraftGeomUtils,math if not isinstance(shape,list): shape = [shape] @@ -473,7 +476,7 @@ class Component: #print("Processing subshapes of ",obj.Label, " : ",obj.Additions) if placement: - if placement.isIdentity(): + if self.isIdentity(placement): placement = None else: placement = FreeCAD.Placement(placement) @@ -570,6 +573,7 @@ class Component: def spread(self,obj,shape,placement=None): "spreads this shape along axis positions" + points = None if hasattr(obj,"Axis"): if obj.Axis: @@ -589,6 +593,14 @@ class Component: shape = Part.makeCompound(shps) return shape + def isIdentity(self,placement): + + "checks if a placement is *almost* zero" + + if (placement.Base.Length < 0.000001) and (placement.Rotation.Angle < 0.000001): + return True + return False + def applyShape(self,obj,shape,placement,allowinvalid=False,allownosolid=False): "checks and cleans the given shape, and apply it to the object" @@ -608,20 +620,23 @@ class Component: pass else: shape = r + p = self.spread(obj,shape,placement).Placement.copy() # for some reason this gets zeroed in next line obj.Shape = self.spread(obj,shape,placement) - if not placement.isIdentity(): + if not self.isIdentity(placement): obj.Placement = placement + else: + obj.Placement = p else: if allownosolid: obj.Shape = self.spread(obj,shape,placement) - if not placement.isIdentity(): + if not self.isIdentity(placement): obj.Placement = placement else: FreeCAD.Console.PrintWarning(obj.Label + " " + translate("Arch","has no solid")+"\n") else: if allowinvalid: obj.Shape = self.spread(obj,shape,placement) - if not placement.isIdentity(): + if not self.isIdentity(placement): obj.Placement = placement else: FreeCAD.Console.PrintWarning(obj.Label + " " + translate("Arch","has an invalid shape")+"\n") @@ -1157,7 +1172,7 @@ class ComponentTaskPanel: self.ifcButton.setText(QtGui.QApplication.translate("Arch", "Edit IFC properties", None)) def editIfcProperties(self): - + if hasattr(self,"ifcEditor"): if self.ifcEditor: self.ifcEditor.hide() @@ -1169,7 +1184,7 @@ class ComponentTaskPanel: if not isinstance(self.obj.IfcProperties,dict): return import Arch_rc,csv,os - + # get presets self.ptypes = SimplePropertyTypes + MeasurePropertyTypes self.plabels = [''.join(map(lambda x: x if x.islower() else " "+x, t[3:]))[1:] for t in self.ptypes] @@ -1180,7 +1195,7 @@ class ComponentTaskPanel: reader = csv.reader(csvfile, delimiter=';') for row in reader: self.psetdefs[row[0]] = row[1:] - self.psetkeys = [''.join(map(lambda x: x if x.islower() else " "+x, t[5:]))[1:] for t in self.psetdefs.keys()] + self.psetkeys = [''.join(map(lambda x: x if x.islower() else " "+x, t[5:]))[1:] for t in self.psetdefs.keys()] self.psetkeys.sort() self.ifcEditor = FreeCADGui.PySideUic.loadUi(":/ui/DialogIfcProperties.ui") # center the dialog over FreeCAD window @@ -1253,7 +1268,7 @@ class ComponentTaskPanel: self.ifcEditor.show() def acceptIfcProperties(self): - + if hasattr(self,"ifcEditor") and self.ifcEditor: self.ifcEditor.hide() ifcdict = {} @@ -1285,7 +1300,7 @@ class ComponentTaskPanel: del self.ifcEditor def addIfcProperty(self,idx=0,pset=None,prop=None,ptype=None): - + if hasattr(self,"ifcEditor") and self.ifcEditor: if not pset: sel = self.ifcEditor.treeProperties.selectedIndexes() @@ -1311,9 +1326,9 @@ class ComponentTaskPanel: pset.appendRow([it1,it2,it3]) if idx != 0: self.ifcEditor.comboProperty.setCurrentIndex(0) - + def addIfcPset(self,idx=0): - + if hasattr(self,"ifcEditor") and self.ifcEditor: if idx == 1: top = QtGui.QStandardItem(QtGui.QApplication.translate("Arch", "New property set", None)) @@ -1338,7 +1353,7 @@ class ComponentTaskPanel: self.ifcEditor.treeProperties.setFirstColumnSpanned(i, idx, True) self.ifcEditor.treeProperties.expandAll() self.ifcEditor.comboPset.setCurrentIndex(0) - + def removeIfcProperty(self): if hasattr(self,"ifcEditor") and self.ifcEditor: @@ -1353,17 +1368,17 @@ class ComponentTaskPanel: if FreeCAD.GuiUp: class IfcEditorDelegate(QtGui.QStyledItemDelegate): - - + + def __init__(self, parent=None, dialog=None, ptypes=[], plabels=[], *args): - + self.dialog = dialog QtGui.QStyledItemDelegate.__init__(self, parent, *args) self.ptypes = ptypes self.plabels = plabels - + def createEditor(self,parent,option,index): - + if index.column() == 0: # property name editor = QtGui.QLineEdit(parent) elif index.column() == 1: # property type @@ -1385,9 +1400,9 @@ if FreeCAD.GuiUp: editor = QtGui.QLineEdit(parent) editor.setObjectName("editor_"+ptype) return editor - + def setEditorData(self, editor, index): - + if index.column() == 0: editor.setText(index.data()) elif index.column() == 1: @@ -1418,9 +1433,9 @@ if FreeCAD.GuiUp: editor.setValue(0) else: editor.setText(index.data()) - + def setModelData(self, editor, model, index): - + if index.column() == 0: model.setData(index,editor.text()) elif index.column() == 1: @@ -1442,6 +1457,6 @@ if FreeCAD.GuiUp: - + diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index cfe14af136..81fc57b34f 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -385,6 +385,7 @@ def insert(filename,docname,skip=[],only=[],root=None): openings = ifcfile.by_type("IfcOpeningElement") annotations = ifcfile.by_type("IfcAnnotation") materials = ifcfile.by_type("IfcMaterial") + profiles = {} # to store reused extrusion profiles {ifcid:fcobj,...} if DEBUG: print("Building relationships table...",end="") @@ -618,23 +619,48 @@ def insert(filename,docname,skip=[],only=[],root=None): if GET_EXTRUSIONS: ex = Arch.getExtrusionData(shape) if ex: - print("extrusion ",end="") - baseface = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_footprint") - # bug in ifcopenshell? Some faces of a shell may have non-null placement - # workaround to remove the bad placement: exporting/reimporting as step - if not ex[0].Placement.isNull(): - import tempfile - fd, tf = tempfile.mkstemp(suffix=".stp") - ex[0].exportStep(tf) - f = Part.read(tf) - os.close(fd) - os.remove(tf) - else: - f = ex[0] - baseface.Shape = f + # check for extrusion profile + baseface = None + profileid = None + addplacement = None + if product.Representation: + if product.Representation.Representations: + if product.Representation.Representations[0].is_a("IfcShapeRepresentation"): + if product.Representation.Representations[0].Items: + if product.Representation.Representations[0].Items[0].is_a("IfcExtrudedAreaSolid"): + profileid = product.Representation.Representations[0].Items[0].SweptArea.id() + if profileid and profileid in profiles: + # reuse existing profile + print("shared extrusion ",end="") + baseface = profiles[profileid] + addplacement = FreeCAD.Placement() + addplacement.Rotation = FreeCAD.Rotation(baseface.Shape.Faces[0].normalAt(0,0),ex[0].Faces[0].normalAt(0,0)) + addplacement.Base = addplacement.Rotation.multVec(ex[0].CenterOfMass.sub(baseface.Shape.CenterOfMass)) + if not baseface: + print("extrusion ",end="") + baseface = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_footprint") + # bug in ifcopenshell? Some faces of a shell may have non-null placement + # workaround to remove the bad placement: exporting/reimporting as step + if not ex[0].Placement.isNull(): + import tempfile + fd, tf = tempfile.mkstemp(suffix=".stp") + ex[0].exportStep(tf) + f = Part.read(tf) + os.close(fd) + os.remove(tf) + else: + f = ex[0] + baseface.Shape = f + if profileid: + profiles[profileid] = baseface baseobj = FreeCAD.ActiveDocument.addObject("Part::Extrusion",name+"_body") baseobj.Base = baseface - baseobj.Dir = ex[1] + if addplacement: + baseobj.Placement.Rotation = addplacement.Rotation + baseobj.Placement.move(addplacement.Base) + baseobj.Dir = addplacement.Rotation.inverted().multVec(ex[1]) + else: + baseobj.Dir = ex[1] if FreeCAD.GuiUp: baseface.ViewObject.hide() if (not baseobj): @@ -694,13 +720,15 @@ def insert(filename,docname,skip=[],only=[],root=None): if FreeCAD.GuiUp and baseobj: if hasattr(baseobj,"ViewObject"): baseobj.ViewObject.hide() - if ptype == "IfcBuildingStorey": obj.Placement.Base.z = product.Elevation*1000 + if ptype == "IfcBuildingStorey": + if product.Elevation: + obj.Placement.Base.z = product.Elevation * 1000 # setting role try: if hasattr(obj,"IfcRole"): - obj.IfcRole = ptype[3:] + obj.IfcRole = ''.join(map(lambda x: x if x.islower() else " "+x, ptype[3:]))[1:] else: # pre-0.18 objects, only support a small subset of types r = ptype[3:] @@ -726,8 +754,11 @@ def insert(filename,docname,skip=[],only=[],root=None): obj = Arch.makeComponent(baseobj,name=name) if obj: - sols = str(obj.Shape.Solids) if hasattr(obj,"Shape") else "" - if DEBUG: print(sols,end="") + s = "" + if hasattr(obj,"Shape"): + if obj.Shape.Solids: + s = str(len(obj.Shape.Solids))+" solids" + if DEBUG: print(s,end="") objects[pid] = obj elif (MERGE_MODE_ARCH == 1 and archobj) or (MERGE_MODE_STRUCT == 0 and not archobj): @@ -738,7 +769,9 @@ def insert(filename,docname,skip=[],only=[],root=None): for freecadtype,ifctypes in typesmap.items(): if ptype in ifctypes: obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name) - if ptype == "IfcBuildingStorey": obj.Placement.Base.z = product.Elevation*1000 + if ptype == "IfcBuildingStorey": + if product.Elevation: + obj.Placement.Base.z = product.Elevation * 1000 elif baseobj: obj = Arch.makeComponent(baseobj,name=name,delete=True) @@ -750,7 +783,9 @@ def insert(filename,docname,skip=[],only=[],root=None): for freecadtype,ifctypes in typesmap.items(): if ptype in ifctypes: obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name) - if ptype == "IfcBuildingStorey": obj.Placement.Base.z = product.Elevation*1000 + if ptype == "IfcBuildingStorey": + if product.Elevation: + obj.Placement.Base.z = product.Elevation * 1000 elif baseobj: obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name) obj.Shape = shape @@ -968,13 +1003,17 @@ def insert(filename,docname,skip=[],only=[],root=None): else: if DEBUG: print("Processing Arch relationships...",end="") + first = True # subtractions if SEPARATE_OPENINGS: for subtraction in subtractions: if (subtraction[0] in objects.keys()) and (subtraction[1] in objects.keys()): - if DEBUG: print("subtracting ",objects[subtraction[0]].Label, " from ", objects[subtraction[1]].Label) + if DEBUG and first: + print("") + first = False + if DEBUG: print(" subtracting ",objects[subtraction[0]].Label, " from ", objects[subtraction[1]].Label) Arch.removeComponents(objects[subtraction[0]],objects[subtraction[1]]) if DEBUG: FreeCAD.ActiveDocument.recompute() @@ -984,11 +1023,14 @@ def insert(filename,docname,skip=[],only=[],root=None): if host in objects.keys(): cobs = [objects[child] for child in children if child in objects.keys()] if cobs: + if DEBUG and first: + print("") + first = False if DEBUG and (len(cobs) > 10) and (not(Draft.getType(objects[host]) in ["Site","Building","Floor","BuildingPart"])): # avoid huge fusions print("more than 10 shapes to add: skipping.") else: - if DEBUG: print("adding ",len(cobs), " object(s) to ", objects[host].Label) + if DEBUG: print(" adding ",len(cobs), " object(s) to ", objects[host].Label) Arch.addComponents(cobs,objects[host]) if DEBUG: FreeCAD.ActiveDocument.recompute() @@ -1130,11 +1172,11 @@ def insert(filename,docname,skip=[],only=[],root=None): class recycler: - + "a mechanism to reuse ifc entities if needed" def __init__(self,ifcfile): - + self.ifcfile = ifcfile self.compress = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcCompress",True) self.cartesianpoints = {(0,0,0):self.ifcfile[8]} # from template @@ -1147,7 +1189,7 @@ class recycler: self.ssrenderings = {} self.transformationoperators = {} self.spared = 0 - + def createIfcCartesianPoint(self,points): if self.compress and points in self.cartesianpoints: self.spared += 1 @@ -1200,7 +1242,7 @@ class recycler: if self.compress: self.axis2placement3ds[key] = c return c - + def createIfcLocalPlacement(self,gpl): key = str(gpl.Location.Coordinates) + str(gpl.Axis.DirectionRatios) + str(gpl.RefDirection.DirectionRatios) if self.compress and key in self.localplacements: @@ -1211,7 +1253,7 @@ class recycler: if self.compress: self.localplacements[key] = c return c - + def createIfcColourRgb(self,r,g,b): key = (r,g,b) if self.compress and key in self.rgbs: @@ -1222,7 +1264,7 @@ class recycler: if self.compress: self.rgbs[key] = c return c - + def createIfcSurfaceStyleRendering(self,col): key = (col.Red,col.Green,col.Blue) if self.compress and key in self.ssrenderings: @@ -1317,9 +1359,9 @@ def export(exportList,filename): groups = {} # { Host: [Child,Child,...] } profiledefs = {} # { ProfileDefString:profiledef,...} shapedefs = {} # { ShapeDefString:[shapes],... } - + # reusable entity system - + global ifcbin ifcbin = recycler(ifcfile) @@ -1381,9 +1423,9 @@ def export(exportList,filename): continue if (Draft.getType(obj) == "BuildingPart") and hasattr(obj,"IfcRole") and (obj.IfcRole == "Undefined"): ifctype = "IfcBuildingStorey" # export BuildingParts as Storeys if their type wasn't explicitely set - + # export grids - + if ifctype in ["IfcAxis","IfcAxisSystem","IfcGrid"]: ifctype = "IfcGrid" ifcaxes = [] @@ -1946,7 +1988,7 @@ def export(exportList,filename): FreeCAD.ActiveDocument.recompute() os.remove(templatefile) - + if DEBUG and ifcbin.compress: f = pyopen(filename,"rb") s = len(f.read().split("\n"))