Arch: Support of shared profiles in extrusions imported from IFC

This commit is contained in:
Yorik van Havre
2018-07-18 15:07:54 -03:00
parent 9f4a5ae652
commit f378fca56a
2 changed files with 113 additions and 56 deletions

View File

@@ -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:

View File

@@ -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"))