2944 lines
134 KiB
Python
2944 lines
134 KiB
Python
#***************************************************************************
|
|
#* *
|
|
#* Copyright (c) 2014 *
|
|
#* 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
|
|
|
|
__title__ = "FreeCAD IFC importer - Enhanced ifcopenshell-only version"
|
|
__author__ = "Yorik van Havre","Jonathan Wiedemann"
|
|
__url__ = "http://www.freecadweb.org"
|
|
|
|
import os,time,tempfile,uuid,FreeCAD,Part,Draft,Arch,math,DraftVecUtils
|
|
|
|
## @package importIFC
|
|
# \ingroup ARCH
|
|
# \brief IFC file format importer and exporter
|
|
#
|
|
# This module provides tools to import and export IFC files.
|
|
|
|
DEBUG = False # Set to True to see debug messages. Otherwise, totally silent
|
|
ADDDEFAULTSTOREY = False # If True, an exported file will ALWAYS have at least one storey
|
|
|
|
if open.__module__ in ['__builtin__','io']:
|
|
pyopen = open # because we'll redefine open below
|
|
|
|
# which IFC type must create which FreeCAD type
|
|
typesmap = { "Site": ["IfcSite"],
|
|
"Building": ["IfcBuilding"],
|
|
"Floor": ["IfcBuildingStorey"],
|
|
"Structure": ["IfcBeam", "IfcBeamStandardCase", "IfcColumn", "IfcColumnStandardCase", "IfcSlab", "IfcFooting", "IfcPile", "IfcTendon"],
|
|
"Wall": ["IfcWall", "IfcWallStandardCase", "IfcCurtainWall"],
|
|
"Window": ["IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"],
|
|
"Roof": ["IfcRoof"],
|
|
"Stairs": ["IfcStair", "IfcStairFlight", "IfcRamp", "IfcRampFlight"],
|
|
"Space": ["IfcSpace"],
|
|
"Rebar": ["IfcReinforcingBar"],
|
|
"Panel": ["IfcPlate"],
|
|
"Equipment": ["IfcFurnishingElement","IfcSanitaryTerminal","IfcFlowTerminal","IfcElectricAppliance"],
|
|
"Pipe": ["IfcPipeSegment"],
|
|
"PipeConnector":["IfcPipeFitting"],
|
|
"BuildingPart": ["IfcElementAssembly"]
|
|
}
|
|
|
|
# which IFC entity (product) is a structural object
|
|
structuralifcobjects = (
|
|
"IfcStructuralCurveMember", "IfcStructuralSurfaceMember",
|
|
"IfcStructuralPointConnection", "IfcStructuralCurveConnection", "IfcStructuralSurfaceConnection",
|
|
"IfcStructuralAction", "IfcStructuralPointAction",
|
|
"IfcStructuralLinearAction", "IfcStructuralLinearActionVarying", "IfcStructuralPlanarAction"
|
|
)
|
|
|
|
# specific FreeCAD <-> IFC slang translations
|
|
translationtable = { "Foundation":"Footing",
|
|
"Floor":"BuildingStorey",
|
|
"Rebar":"ReinforcingBar",
|
|
"HydroEquipment":"SanitaryTerminal",
|
|
"ElectricEquipment":"ElectricAppliance",
|
|
"Furniture":"FurnishingElement",
|
|
"Stair Flight":"StairFlight",
|
|
"Curtain Wall":"CurtainWall",
|
|
"Pipe Segment":"PipeSegment",
|
|
"Pipe Fitting":"PipeFitting"
|
|
}
|
|
|
|
# the base IFC template for export
|
|
ifctemplate = """ISO-10303-21;
|
|
HEADER;
|
|
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
|
FILE_NAME('$filename','$timestamp',('$owner','$email'),('$company'),'IfcOpenShell','IfcOpenShell','');
|
|
FILE_SCHEMA(('$ifcschema'));
|
|
ENDSEC;
|
|
DATA;
|
|
#1=IFCPERSON($,$,'$owner',$,$,$,$,$);
|
|
#2=IFCORGANIZATION($,'$company',$,$,$);
|
|
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
|
#4=IFCAPPLICATION(#2,'$version','FreeCAD','118df2cf_ed21_438e_a41');
|
|
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,$now);
|
|
#6=IFCDIRECTION((1.,0.,0.));
|
|
#7=IFCDIRECTION((0.,0.,1.));
|
|
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
|
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
|
#10=IFCDIRECTION((0.,1.,0.));
|
|
#11=IFCGEOMETRICREPRESENTATIONCONTEXT('Plan','Model',3,1.E-05,#9,#10);
|
|
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
|
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
|
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
|
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
|
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
|
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
|
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
|
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
|
#20=IFCPROJECT('$projectid',#5,'$project',$,$,$,$,(#11),#19);
|
|
ENDSEC;
|
|
END-ISO-10303-21;
|
|
"""
|
|
|
|
|
|
def decode(filename,utf=False):
|
|
|
|
"turns unicodes into strings"
|
|
|
|
if isinstance(filename,unicode):
|
|
# workaround since ifcopenshell currently can't handle unicode filenames
|
|
if utf:
|
|
encoding = "utf8"
|
|
else:
|
|
import sys
|
|
encoding = sys.getfilesystemencoding()
|
|
filename = filename.encode(encoding)
|
|
return filename
|
|
|
|
|
|
def doubleClickTree(item,column):
|
|
|
|
"a double-click callback function for the IFC explorer tool"
|
|
|
|
txt = item.text(column)
|
|
if "Entity #" in txt:
|
|
eid = txt.split("#")[1].split(":")[0]
|
|
addr = tree.findItems(eid,0,0)
|
|
if addr:
|
|
tree.scrollToItem(addr[0])
|
|
addr[0].setSelected(True)
|
|
|
|
|
|
def dd2dms(dd):
|
|
|
|
"converts decimal degrees to degrees,minutes,seconds"
|
|
|
|
dd = abs(dd)
|
|
minutes,seconds = divmod(dd*3600,60)
|
|
degrees,minutes = divmod(minutes,60)
|
|
if dd < 0:
|
|
degrees = -degrees
|
|
return (int(degrees),int(minutes),int(seconds))
|
|
|
|
def dms2dd(degrees, minutes, seconds, milliseconds=0):
|
|
|
|
"converts degrees,minutes,seconds to decimal degrees"
|
|
|
|
dd = float(degrees) + float(minutes)/60 + float(seconds)/(3600)
|
|
return dd
|
|
|
|
|
|
def getPreferences():
|
|
|
|
"""retrieves IFC preferences"""
|
|
|
|
global DEBUG, PREFIX_NUMBERS, SKIP, SEPARATE_OPENINGS
|
|
global ROOT_ELEMENT, GET_EXTRUSIONS, MERGE_MATERIALS
|
|
global MERGE_MODE_ARCH, MERGE_MODE_STRUCT, CREATE_CLONES
|
|
global FORCE_BREP, IMPORT_PROPERTIES, STORE_UID, SERIALIZE
|
|
global SPLIT_LAYERS, EXPORT_2D, FULL_PARAMETRIC, FITVIEW_ONIMPORT
|
|
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
|
|
if FreeCAD.GuiUp and p.GetBool("ifcShowDialog",False):
|
|
import FreeCADGui
|
|
FreeCADGui.showPreferences("Import-Export",0)
|
|
DEBUG = p.GetBool("ifcDebug",False)
|
|
PREFIX_NUMBERS = p.GetBool("ifcPrefixNumbers",False)
|
|
SKIP = p.GetString("ifcSkip","").split(",")
|
|
SEPARATE_OPENINGS = p.GetBool("ifcSeparateOpenings",False)
|
|
ROOT_ELEMENT = p.GetString("ifcRootElement","IfcProduct")
|
|
GET_EXTRUSIONS = p.GetBool("ifcGetExtrusions",False)
|
|
MERGE_MATERIALS = p.GetBool("ifcMergeMaterials",False)
|
|
MERGE_MODE_ARCH = p.GetInt("ifcImportModeArch",0)
|
|
MERGE_MODE_STRUCT = p.GetInt("ifcImportModeStruct",1)
|
|
if MERGE_MODE_ARCH > 0:
|
|
SEPARATE_OPENINGS = False
|
|
GET_EXTRUSIONS = False
|
|
if not SEPARATE_OPENINGS:
|
|
SKIP.append("IfcOpeningElement")
|
|
CREATE_CLONES = p.GetBool("ifcCreateClones",True)
|
|
FORCE_BREP = p.GetBool("ifcExportAsBrep",False)
|
|
IMPORT_PROPERTIES = p.GetBool("ifcImportProperties",False)
|
|
STORE_UID = p.GetBool("ifcStoreUid",True)
|
|
SERIALIZE = p.GetBool("ifcSerialize",False)
|
|
SPLIT_LAYERS = p.GetBool("ifcSplitLayers",False)
|
|
EXPORT_2D = p.GetBool("ifcExport2D",True)
|
|
FULL_PARAMETRIC = p.GetBool("IfcExportFreeCADProperties",False)
|
|
FITVIEW_ONIMPORT = p.GetBool("ifcFitViewOnImport",False)
|
|
|
|
|
|
def explore(filename=None):
|
|
|
|
"""explore([filename]): opens a dialog showing
|
|
the contents of an IFC file. If no filename is given, a dialog will
|
|
pop up to choose a file."""
|
|
|
|
getPreferences()
|
|
|
|
try:
|
|
import ifcopenshell
|
|
except:
|
|
FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n")
|
|
return
|
|
|
|
if not filename:
|
|
from PySide import QtGui
|
|
filename = QtGui.QFileDialog.getOpenFileName(QtGui.QApplication.activeWindow(),'IFC files','*.ifc')
|
|
if filename:
|
|
filename = filename[0]
|
|
|
|
from PySide import QtCore,QtGui
|
|
|
|
filename = decode(filename,utf=True)
|
|
|
|
if not os.path.exists(filename):
|
|
print("File not found")
|
|
return
|
|
|
|
# draw the widget contents
|
|
ifc = ifcopenshell.open(filename)
|
|
global tree
|
|
tree = QtGui.QTreeWidget()
|
|
tree.setColumnCount(3)
|
|
tree.setWordWrap(True)
|
|
tree.header().setDefaultSectionSize(60)
|
|
tree.header().resizeSection(0,60)
|
|
tree.header().resizeSection(1,30)
|
|
tree.header().setStretchLastSection(True)
|
|
tree.headerItem().setText(0, "ID")
|
|
tree.headerItem().setText(1, "")
|
|
tree.headerItem().setText(2, "Item and Properties")
|
|
bold = QtGui.QFont()
|
|
bold.setWeight(75)
|
|
bold.setBold(True)
|
|
|
|
entities = ifc.by_type("IfcRoot")
|
|
entities += ifc.by_type("IfcRepresentation")
|
|
entities += ifc.by_type("IfcRepresentationItem")
|
|
entities += ifc.by_type("IfcRepresentationMap")
|
|
entities += ifc.by_type("IfcPlacement")
|
|
entities += ifc.by_type("IfcProperty")
|
|
entities += ifc.by_type("IfcPhysicalSimpleQuantity")
|
|
entities += ifc.by_type("IfcMaterial")
|
|
entities += ifc.by_type("IfcProductRepresentation")
|
|
entities = sorted(entities, key=lambda eid: eid.id())
|
|
|
|
done = []
|
|
|
|
for entity in entities:
|
|
if hasattr(entity,"id"):
|
|
if entity.id() in done:
|
|
continue
|
|
done.append(entity.id())
|
|
item = QtGui.QTreeWidgetItem(tree)
|
|
item.setText(0,str(entity.id()))
|
|
if entity.is_a() in ["IfcWall","IfcWallStandardCase"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Wall_Tree.svg"))
|
|
elif entity.is_a() in ["IfcBuildingElementProxy"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Component.svg"))
|
|
elif entity.is_a() in ["IfcColumn","IfcColumnStandardCase","IfcBeam","IfcBeamStandardCase","IfcSlab","IfcFooting","IfcPile","IfcTendon"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Structure_Tree.svg"))
|
|
elif entity.is_a() in ["IfcSite"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Site_Tree.svg"))
|
|
elif entity.is_a() in ["IfcBuilding"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Building_Tree.svg"))
|
|
elif entity.is_a() in ["IfcBuildingStorey"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Floor_Tree.svg"))
|
|
elif entity.is_a() in ["IfcWindow","IfcWindowStandardCase","IfcDoor","IfcDoorStandardCase"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Window_Tree.svg"))
|
|
elif entity.is_a() in ["IfcRoof"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Roof_Tree.svg"))
|
|
elif entity.is_a() in ["IfcExtrudedAreaSolid","IfcClosedShell"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Tree_Part.svg"))
|
|
elif entity.is_a() in ["IfcFace"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Draft_SwitchMode.svg"))
|
|
elif entity.is_a() in ["IfcArbitraryClosedProfileDef","IfcPolyloop"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Draft_Draft.svg"))
|
|
elif entity.is_a() in ["IfcPropertySingleValue","IfcQuantityArea","IfcQuantityVolume"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Tree_Annotation.svg"))
|
|
elif entity.is_a() in ["IfcMaterial"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Material.svg"))
|
|
elif entity.is_a() in ["IfcReinforcingBar"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Rebar.svg"))
|
|
item.setText(2,str(entity.is_a()))
|
|
item.setFont(2,bold);
|
|
|
|
i = 0
|
|
while True:
|
|
try:
|
|
argname = entity.attribute_name(i)
|
|
except:
|
|
break
|
|
else:
|
|
try:
|
|
argvalue = getattr(entity,argname)
|
|
except:
|
|
print("Error in entity ", entity)
|
|
break
|
|
else:
|
|
if argname not in ["Id", "GlobalId"]:
|
|
colored = False
|
|
if isinstance(argvalue,ifcopenshell.entity_instance):
|
|
if argvalue.id() == 0:
|
|
t = str(argvalue)
|
|
else:
|
|
colored = True
|
|
t = "Entity #" + str(argvalue.id()) + ": " + str(argvalue.is_a())
|
|
elif isinstance(argvalue,list):
|
|
t = ""
|
|
elif isinstance(argvalue,str) or isinstance(argvalue,unicode):
|
|
t = argvalue.encode("latin1")
|
|
else:
|
|
t = str(argvalue)
|
|
t = " " + str(argname.encode("utf8")) + " : " + str(t)
|
|
item = QtGui.QTreeWidgetItem(tree)
|
|
item.setText(2,str(t))
|
|
if colored:
|
|
item.setForeground(2,QtGui.QBrush(QtGui.QColor("#005AFF")))
|
|
if isinstance(argvalue,list):
|
|
for argitem in argvalue:
|
|
colored = False
|
|
if isinstance(argitem,ifcopenshell.entity_instance):
|
|
if argitem.id() == 0:
|
|
t = str(argitem)
|
|
else:
|
|
colored = True
|
|
t = "Entity #" + str(argitem.id()) + ": " + str(argitem.is_a())
|
|
else:
|
|
t = argitem
|
|
t = " " + str(t)
|
|
item = QtGui.QTreeWidgetItem(tree)
|
|
item.setText(2,str(t))
|
|
if colored:
|
|
item.setForeground(2,QtGui.QBrush(QtGui.QColor("#005AFF")))
|
|
i += 1
|
|
|
|
d = QtGui.QDialog()
|
|
d.setObjectName("IfcExplorer")
|
|
d.setWindowTitle("Ifc Explorer")
|
|
d.resize(640, 480)
|
|
layout = QtGui.QVBoxLayout(d)
|
|
layout.addWidget(tree)
|
|
|
|
tree.itemDoubleClicked.connect(doubleClickTree)
|
|
|
|
d.exec_()
|
|
del tree
|
|
return
|
|
|
|
|
|
def open(filename,skip=[],only=[],root=None):
|
|
|
|
"opens an IFC file in a new document"
|
|
|
|
docname = os.path.splitext(os.path.basename(filename))[0]
|
|
docname = decode(docname,utf=True)
|
|
doc = FreeCAD.newDocument(docname)
|
|
doc.Label = docname
|
|
doc = insert(filename,doc.Name,skip,only,root)
|
|
return doc
|
|
|
|
|
|
def insert(filename,docname,skip=[],only=[],root=None):
|
|
|
|
"""insert(filename,docname,skip=[],only=[],root=None): imports the contents of an IFC file.
|
|
skip can contain a list of ids of objects to be skipped, only can restrict the import to
|
|
certain object ids (will also get their children) and root can be used to
|
|
import only the derivates of a certain element type (default = ifcProduct)."""
|
|
|
|
getPreferences()
|
|
|
|
try:
|
|
import ifcopenshell
|
|
except:
|
|
FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n")
|
|
return
|
|
|
|
if DEBUG: print("Opening ",filename,"...",end="")
|
|
try:
|
|
doc = FreeCAD.getDocument(docname)
|
|
except:
|
|
doc = FreeCAD.newDocument(docname)
|
|
FreeCAD.ActiveDocument = doc
|
|
|
|
if DEBUG: print("done.")
|
|
|
|
global ROOT_ELEMENT, parametrics
|
|
|
|
if root:
|
|
ROOT_ELEMENT = root
|
|
|
|
#global ifcfile # keeping global for debugging purposes
|
|
|
|
filename = decode(filename,utf=True)
|
|
ifcfile = ifcopenshell.open(filename)
|
|
|
|
# set default ifcopenshell options to work in brep mode
|
|
from ifcopenshell import geom
|
|
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 SEPARATE_OPENINGS:
|
|
settings.set(settings.DISABLE_OPENING_SUBTRACTIONS,True)
|
|
if SPLIT_LAYERS and hasattr(settings,"APPLY_LAYERSETS"):
|
|
settings.set(settings.APPLY_LAYERSETS,True)
|
|
|
|
# gather easy entity types
|
|
sites = ifcfile.by_type("IfcSite")
|
|
buildings = ifcfile.by_type("IfcBuilding")
|
|
floors = ifcfile.by_type("IfcBuildingStorey")
|
|
products = ifcfile.by_type(ROOT_ELEMENT)
|
|
openings = ifcfile.by_type("IfcOpeningElement")
|
|
annotations = ifcfile.by_type("IfcAnnotation")
|
|
materials = ifcfile.by_type("IfcMaterial")
|
|
|
|
if DEBUG: print("Building relationships table...",end="")
|
|
|
|
# building relations tables
|
|
|
|
objects = {} # { id:object, ... }
|
|
prodrepr = {} # product/representations table
|
|
additions = {} # { host:[child,...], ... }
|
|
groups = {} # { host:[child,...], ... } # used in structural IFC
|
|
subtractions = [] # [ [opening,host], ... ]
|
|
properties = {} # { objid : { psetid : [propertyid, ... ], ... }, ... }
|
|
colors = {} # { id:(r,g,b) }
|
|
shapes = {} # { id:shaoe } only used for merge mode
|
|
structshapes = {} # { id:shaoe } only used for merge mode
|
|
mattable = {} # { objid:matid }
|
|
sharedobjects = {} # { representationmapid:object }
|
|
parametrics = [] # a list of imported objects whose parametric relationships need processing after all objects have been created
|
|
profiles = {} # to store reused extrusion profiles {ifcid:fcobj,...}
|
|
|
|
for r in ifcfile.by_type("IfcRelContainedInSpatialStructure"):
|
|
additions.setdefault(r.RelatingStructure.id(),[]).extend([e.id() for e in r.RelatedElements])
|
|
for r in ifcfile.by_type("IfcRelAggregates"):
|
|
additions.setdefault(r.RelatingObject.id(),[]).extend([e.id() for e in r.RelatedObjects])
|
|
for r in ifcfile.by_type("IfcRelAssignsToGroup"):
|
|
groups.setdefault(r.RelatingGroup.id(),[]).extend([e.id() for e in r.RelatedObjects])
|
|
for r in ifcfile.by_type("IfcRelVoidsElement"):
|
|
subtractions.append([r.RelatedOpeningElement.id(), r.RelatingBuildingElement.id()])
|
|
for r in ifcfile.by_type("IfcRelDefinesByProperties"):
|
|
for obj in r.RelatedObjects:
|
|
if not obj.id() in properties:
|
|
properties[obj.id()] = {}
|
|
psets = {}
|
|
props = []
|
|
if r.RelatingPropertyDefinition.is_a("IfcPropertySet"):
|
|
props.extend([prop.id() for prop in r.RelatingPropertyDefinition.HasProperties])
|
|
psets[r.RelatingPropertyDefinition.id()] = props
|
|
properties[obj.id()].update(psets)
|
|
for r in ifcfile.by_type("IfcRelAssociatesMaterial"):
|
|
for o in r.RelatedObjects:
|
|
if r.RelatingMaterial.is_a("IfcMaterial"):
|
|
mattable[o.id()] = r.RelatingMaterial.id()
|
|
elif r.RelatingMaterial.is_a("IfcMaterialLayer"):
|
|
mattable[o.id()] = r.RelatingMaterial.Material.id()
|
|
elif r.RelatingMaterial.is_a("IfcMaterialLayerSet"):
|
|
mattable[o.id()] = r.RelatingMaterial.MaterialLayers[0].Material.id()
|
|
elif r.RelatingMaterial.is_a("IfcMaterialLayerSetUsage"):
|
|
mattable[o.id()] = r.RelatingMaterial.ForLayerSet.MaterialLayers[0].Material.id()
|
|
for p in ifcfile.by_type("IfcProduct"):
|
|
if hasattr(p,"Representation"):
|
|
if p.Representation:
|
|
for it in p.Representation.Representations:
|
|
for it1 in it.Items:
|
|
prodrepr.setdefault(p.id(),[]).append(it1.id())
|
|
if it1.is_a("IfcBooleanResult"):
|
|
prodrepr.setdefault(p.id(),[]).append(it1.FirstOperand.id())
|
|
elif it.Items[0].is_a("IfcMappedItem"):
|
|
prodrepr.setdefault(p.id(),[]).append(it1.MappingSource.MappedRepresentation.id())
|
|
if it1.MappingSource.MappedRepresentation.is_a("IfcShapeRepresentation"):
|
|
for it2 in it1.MappingSource.MappedRepresentation.Items:
|
|
prodrepr.setdefault(p.id(),[]).append(it2.id())
|
|
for r in ifcfile.by_type("IfcStyledItem"):
|
|
if r.Styles:
|
|
if r.Styles[0].is_a("IfcPresentationStyleAssignment"):
|
|
if r.Styles[0].Styles[0].is_a("IfcSurfaceStyle"):
|
|
if r.Styles[0].Styles[0].Styles[0].is_a("IfcSurfaceStyleRendering"):
|
|
if r.Styles[0].Styles[0].Styles[0].SurfaceColour:
|
|
c = r.Styles[0].Styles[0].Styles[0].SurfaceColour
|
|
if r.Item:
|
|
for p in prodrepr.keys():
|
|
if r.Item.id() in prodrepr[p]:
|
|
colors[p] = (c.Red,c.Green,c.Blue)
|
|
else:
|
|
for m in ifcfile.by_type("IfcMaterialDefinitionRepresentation"):
|
|
for it in m.Representations:
|
|
if it.Items:
|
|
if it.Items[0].id() == r.id():
|
|
colors[m.RepresentedMaterial.id()] = (c.Red,c.Green,c.Blue)
|
|
|
|
# move IfcGrids from products to annotations
|
|
tp = []
|
|
for product in products:
|
|
if product.is_a("IfcGrid") and not (product in annotations):
|
|
annotations.append(product)
|
|
else:
|
|
tp.append(product)
|
|
products = tp
|
|
|
|
# only import a list of IDs and their children
|
|
if only:
|
|
ids = []
|
|
while only:
|
|
currentid = only.pop()
|
|
ids.append(currentid)
|
|
if currentid in additions.keys():
|
|
only.extend(additions[currentid])
|
|
products = [ifcfile[currentid] for currentid in ids]
|
|
|
|
if DEBUG: print("done.")
|
|
|
|
count = 0
|
|
from FreeCAD import Base
|
|
progressbar = Base.ProgressIndicator()
|
|
progressbar.start("Importing IFC objects...",len(products))
|
|
if DEBUG: print("Processing objects...")
|
|
|
|
if FITVIEW_ONIMPORT and FreeCAD.GuiUp:
|
|
overallboundbox = None
|
|
import FreeCADGui
|
|
FreeCADGui.ActiveDocument.activeView().viewAxonometric()
|
|
|
|
# handle IFC products
|
|
|
|
for product in products:
|
|
|
|
count += 1
|
|
|
|
pid = product.id()
|
|
guid = product.GlobalId
|
|
ptype = product.is_a()
|
|
if DEBUG: print(count,"/",len(products),"object #"+str(pid),":",ptype,end="")
|
|
|
|
# checking for full FreeCAD parametric definition, overriding everything else
|
|
|
|
if pid in properties.keys():
|
|
if "FreeCADPropertySet" in [ifcfile[pset].Name for pset in properties[pid].keys()]:
|
|
if DEBUG: print(" restoring from parametric definition...",end="")
|
|
obj = createFromProperties(properties[pid],ifcfile)
|
|
if obj:
|
|
objects[pid] = obj
|
|
if DEBUG: print("done")
|
|
continue
|
|
else:
|
|
print("failed.",end="")
|
|
|
|
# no parametric data, we go the good old way
|
|
|
|
name = str(ptype[3:])
|
|
if product.Name:
|
|
name = product.Name.encode("utf8")
|
|
if PREFIX_NUMBERS: name = "ID" + str(pid) + " " + name
|
|
obj = None
|
|
baseobj = None
|
|
brep = None
|
|
shape = None
|
|
|
|
archobj = True # assume all objects not in structuralifcobjects are architecture
|
|
structobj = False
|
|
if ptype in structuralifcobjects:
|
|
archobj = False
|
|
structobj = True
|
|
if DEBUG: print(" (struct)",end="")
|
|
else:
|
|
if DEBUG: print(" (arch)",end="")
|
|
if MERGE_MODE_ARCH == 4 and archobj:
|
|
if DEBUG: print(" skipped.")
|
|
continue
|
|
if MERGE_MODE_STRUCT == 3 and not archobj:
|
|
if DEBUG: print(" skipped.")
|
|
continue
|
|
if pid in skip: # user given id skip list
|
|
if DEBUG: print(" skipped.")
|
|
continue
|
|
if ptype in SKIP: # preferences-set type skip list
|
|
if DEBUG: print(" skipped.")
|
|
continue
|
|
|
|
# detect if this object is sharing its shape
|
|
|
|
clone = None
|
|
store = None
|
|
prepr = None
|
|
try:
|
|
prepr = product.Representation
|
|
except:
|
|
if DEBUG: print(" ERROR unable to get object representation",end="")
|
|
if prepr and (MERGE_MODE_ARCH == 0) and archobj and CREATE_CLONES:
|
|
|
|
for s in prepr.Representations:
|
|
if s.RepresentationIdentifier.upper() == "BODY":
|
|
if s.Items[0].is_a("IfcMappedItem"):
|
|
bid = s.Items[0].MappingSource.id()
|
|
if bid in sharedobjects:
|
|
clone = sharedobjects[bid]
|
|
else:
|
|
sharedobjects[bid] = None
|
|
store = bid
|
|
|
|
# additional setting for structural entities
|
|
if hasattr(settings,"INCLUDE_CURVES"):
|
|
if structobj:
|
|
settings.set(settings.INCLUDE_CURVES,True)
|
|
else:
|
|
settings.set(settings.INCLUDE_CURVES,False)
|
|
try:
|
|
cr = ifcopenshell.geom.create_shape(settings,product)
|
|
brep = cr.geometry.brep_data
|
|
except:
|
|
pass # IfcOpenShell will yield an error if a given product has no shape, but we don't care, we're brave enough
|
|
|
|
if brep:
|
|
if DEBUG: print(" "+str(len(brep)/1000)+"k ",end="")
|
|
|
|
shape = Part.Shape()
|
|
shape.importBrepFromString(brep,False)
|
|
|
|
shape.scale(1000.0) # IfcOpenShell always outputs in meters, we convert to mm, the freecad internal unit
|
|
|
|
if not shape.isNull():
|
|
if FITVIEW_ONIMPORT and FreeCAD.GuiUp:
|
|
# add to the global boundbox
|
|
try:
|
|
bb = shape.BoundBox
|
|
# if DEBUG: print(' ' + str(bb),end="")
|
|
except:
|
|
bb = None
|
|
if DEBUG: print(' BB could not be computed',end="")
|
|
if bb and bb.isValid():
|
|
if not overallboundbox:
|
|
overallboundbox = bb
|
|
if not overallboundbox.isInside(bb):
|
|
FreeCADGui.SendMsgToActiveView("ViewFit")
|
|
overallboundbox.add(bb)
|
|
|
|
if (MERGE_MODE_ARCH > 0 and archobj) or structobj:
|
|
|
|
# additional tweaks when not using Arch objects
|
|
if ptype == "IfcSpace": # do not add spaces to compounds
|
|
if DEBUG: print("skipping space ",pid,end="")
|
|
elif structobj:
|
|
structshapes[pid] = shape
|
|
if DEBUG: print(shape.Solids," ",end="")
|
|
baseobj = shape
|
|
else:
|
|
shapes[pid] = shape
|
|
if DEBUG: print(shape.Solids," ",end="")
|
|
baseobj = shape
|
|
else:
|
|
|
|
# create base shape object
|
|
if clone:
|
|
if DEBUG: print("clone ",end="")
|
|
else:
|
|
if GET_EXTRUSIONS:
|
|
ex = Arch.getExtrusionData(shape) # is this an extrusion?
|
|
if ex:
|
|
# 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]
|
|
# calculate delta placement between stored profile and this one
|
|
addplacement = FreeCAD.Placement()
|
|
r = FreeCAD.Rotation(baseface.Shape.Faces[0].normalAt(0,0),ex[0].Faces[0].normalAt(0,0))
|
|
if r.Angle > 0.000001:
|
|
# use shape methods to easily obtain a correct placement
|
|
ts = Part.Shape()
|
|
ts.rotate(DraftVecUtils.tup(baseface.Shape.CenterOfMass), DraftVecUtils.tup(r.Axis), math.degrees(r.Angle))
|
|
addplacement = ts.Placement
|
|
d = ex[0].CenterOfMass.sub(baseface.Shape.CenterOfMass)
|
|
if d.Length > 0.000001:
|
|
addplacement.move(d)
|
|
if not baseface:
|
|
print("extrusion ",end="")
|
|
import DraftGeomUtils
|
|
if DraftGeomUtils.hasCurves(ex[0]) or len(ex[0].Wires) != 1:
|
|
# curves or holes? We just make a Part face
|
|
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
|
|
else:
|
|
# no hole and no curves, we make a Draft Wire instead
|
|
baseface = Draft.makeWire([v.Point for v in ex[0].Wires[0].OrderedVertexes],closed=True)
|
|
if profileid:
|
|
profiles[profileid] = baseface
|
|
baseobj = FreeCAD.ActiveDocument.addObject("Part::Extrusion",name+"_body")
|
|
baseobj.Base = baseface
|
|
if addplacement:
|
|
# apply delta placement (stored profile)
|
|
baseobj.Placement = addplacement
|
|
baseobj.Dir = addplacement.Rotation.inverted().multVec(ex[1])
|
|
else:
|
|
baseobj.Dir = ex[1]
|
|
if FreeCAD.GuiUp:
|
|
baseface.ViewObject.hide()
|
|
if (not baseobj):
|
|
baseobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
|
|
baseobj.Shape = shape
|
|
else:
|
|
if DEBUG: print("null shape ",end="")
|
|
if not shape.isValid():
|
|
if DEBUG: print("invalid shape ",end="")
|
|
#continue
|
|
|
|
else:
|
|
if DEBUG: print(" no brep ",end="")
|
|
|
|
if MERGE_MODE_ARCH == 0 and archobj:
|
|
|
|
# full Arch objects
|
|
|
|
for freecadtype,ifctypes in typesmap.items():
|
|
if ptype in ifctypes:
|
|
if clone:
|
|
obj = getattr(Arch,"make"+freecadtype)(name=name)
|
|
obj.CloneOf = clone
|
|
if shape:
|
|
if shape.Solids:
|
|
s1 = shape.Solids[0]
|
|
else:
|
|
s1 = shape
|
|
if clone.Shape.Solids:
|
|
s2 = clone.Shape.Solids[0]
|
|
else:
|
|
s1 = clone.Shape
|
|
if hasattr(s1,"CenterOfMass") and hasattr(s2,"CenterOfMass"):
|
|
v = s1.CenterOfMass.sub(s2.CenterOfMass)
|
|
if product.Representation:
|
|
r = getRotation(product.Representation.Representations[0].Items[0].MappingTarget)
|
|
if not r.isNull():
|
|
v = v.add(s2.CenterOfMass)
|
|
v = v.add(r.multVec(s2.CenterOfMass.negative()))
|
|
obj.Placement.Rotation = r
|
|
obj.Placement.move(v)
|
|
else:
|
|
print("failed to compute placement ",)
|
|
else:
|
|
obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name)
|
|
if freecadtype in ["Wall","Structure"] and baseobj and baseobj.isDerivedFrom("Part::Extrusion"):
|
|
# remove intermediary extrusion for types that can extrude themselves
|
|
obj.Base = baseobj.Base
|
|
obj.Placement = obj.Placement.multiply(baseobj.Placement)
|
|
obj.Height = baseobj.Dir.Length
|
|
obj.Normal = FreeCAD.Vector(baseobj.Dir).normalize()
|
|
bn = baseobj.Name
|
|
FreeCAD.ActiveDocument.removeObject(bn)
|
|
if (freecadtype in ["Structure","Wall"]) and not baseobj:
|
|
# remove sizes to prevent auto shape creation for types that don't require a base object
|
|
obj.Height = 0
|
|
obj.Width = 0
|
|
obj.Length = 0
|
|
if store:
|
|
sharedobjects[store] = obj
|
|
|
|
obj.Label = name
|
|
if DEBUG: print(": "+obj.Label+" ",end="")
|
|
if hasattr(obj,"Description") and hasattr(product,"Description"):
|
|
if product.Description:
|
|
obj.Description = product.Description
|
|
if FreeCAD.GuiUp and baseobj:
|
|
if hasattr(baseobj,"ViewObject"):
|
|
baseobj.ViewObject.hide()
|
|
if ptype == "IfcBuildingStorey":
|
|
if product.Elevation:
|
|
obj.Placement.Base.z = product.Elevation * 1000
|
|
|
|
# setting IFC role
|
|
|
|
try:
|
|
if hasattr(obj,"IfcRole"):
|
|
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:]
|
|
tr = dict((v,k) for k, v in translationtable.iteritems())
|
|
if r in tr.keys():
|
|
r = tr[r]
|
|
# remove the "StandardCase"
|
|
if "StandardCase" in r:
|
|
r = r[:-12]
|
|
obj.Role = r
|
|
except:
|
|
print("Unable to give IFC role ",ptype," to object ",obj.Label)
|
|
|
|
# setting uid
|
|
|
|
if hasattr(obj,"IfcAttributes"):
|
|
a = obj.IfcAttributes
|
|
a["IfcUID"] = str(guid)
|
|
obj.IfcAttributes = a
|
|
break
|
|
|
|
if not obj:
|
|
obj = Arch.makeComponent(baseobj,name=name)
|
|
|
|
if obj:
|
|
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):
|
|
|
|
# non-parametric Arch objects (just Arch components with a shape)
|
|
|
|
if ptype in ["IfcSite","IfcBuilding","IfcBuildingStorey"]:
|
|
for freecadtype,ifctypes in typesmap.items():
|
|
if ptype in ifctypes:
|
|
obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name)
|
|
if ptype == "IfcBuildingStorey":
|
|
if product.Elevation:
|
|
obj.Placement.Base.z = product.Elevation * 1000
|
|
elif baseobj:
|
|
obj = Arch.makeComponent(baseobj,name=name,delete=True)
|
|
|
|
elif (MERGE_MODE_ARCH == 2 and archobj) or (MERGE_MODE_STRUCT == 1 and not archobj):
|
|
|
|
# Part shapes
|
|
|
|
if ptype in ["IfcSite","IfcBuilding","IfcBuildingStorey"]:
|
|
for freecadtype,ifctypes in typesmap.items():
|
|
if ptype in ifctypes:
|
|
obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name)
|
|
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
|
|
|
|
if DEBUG: print("") # newline for debug prints, print for a new object should be on a new line
|
|
|
|
if obj:
|
|
|
|
obj.Label = name
|
|
objects[pid] = obj
|
|
|
|
# handle properties
|
|
|
|
if pid in properties:
|
|
if IMPORT_PROPERTIES and hasattr(obj,"IfcProperties"):
|
|
|
|
# treat as spreadsheet (pref option)
|
|
|
|
if isinstance(obj.IfcProperties,dict):
|
|
|
|
# fix property type if needed
|
|
|
|
obj.removeProperty("IfcProperties")
|
|
obj.addProperty("App::PropertyLink","IfcProperties","Component","Stores IFC properties as a spreadsheet")
|
|
|
|
ifc_spreadsheet = Arch.makeIfcSpreadsheet()
|
|
n=2
|
|
for c in properties[pid].keys():
|
|
o = ifcfile[c]
|
|
if DEBUG: print("propertyset Name",o.Name,type(o.Name))
|
|
catname = o.Name
|
|
for p in properties[pid][c]:
|
|
l = ifcfile[p]
|
|
if l.is_a("IfcPropertySingleValue"):
|
|
if DEBUG:
|
|
print("property name",l.Name,type(l.Name))
|
|
ifc_spreadsheet.set(str('A'+str(n)), catname.encode("utf8"))
|
|
ifc_spreadsheet.set(str('B'+str(n)), l.Name.encode("utf8"))
|
|
if l.NominalValue:
|
|
if DEBUG:
|
|
print("property NominalValue",l.NominalValue.is_a(),type(l.NominalValue.is_a()))
|
|
print("property NominalValue.wrappedValue",l.NominalValue.wrappedValue,type(l.NominalValue.wrappedValue))
|
|
#print("l.NominalValue.Unit",l.NominalValue.Unit,type(l.NominalValue.Unit))
|
|
ifc_spreadsheet.set(str('C'+str(n)), l.NominalValue.is_a())
|
|
if l.NominalValue.is_a() in ['IfcLabel','IfcText','IfcIdentifier','IfcDescriptiveMeasure']:
|
|
ifc_spreadsheet.set(str('D'+str(n)), "'" + str(l.NominalValue.wrappedValue.encode("utf8")))
|
|
else:
|
|
ifc_spreadsheet.set(str('D'+str(n)), str(l.NominalValue.wrappedValue))
|
|
if hasattr(l.NominalValue,'Unit'):
|
|
ifc_spreadsheet.set(str('E'+str(n)), str(l.NominalValue.Unit))
|
|
n += 1
|
|
obj.IfcProperties = ifc_spreadsheet
|
|
|
|
elif hasattr(obj,"IfcProperties") and isinstance(obj.IfcProperties,dict):
|
|
|
|
# 0.18 behaviour: properties are saved as pset;;type;;value in IfcProperties
|
|
|
|
d = obj.IfcProperties
|
|
for pset in properties[pid].keys():
|
|
psetname = ifcfile[pset].Name.encode("utf8")
|
|
for prop in properties[pid][pset]:
|
|
e = ifcfile[prop]
|
|
pname = e.Name.encode("utf8")
|
|
if e.is_a("IfcPropertySingleValue"):
|
|
ptype = e.NominalValue.is_a()
|
|
if ptype in ['IfcLabel','IfcText','IfcIdentifier','IfcDescriptiveMeasure']:
|
|
pvalue = e.NominalValue.wrappedValue.encode("utf8")
|
|
else:
|
|
pvalue = str(e.NominalValue.wrappedValue)
|
|
if hasattr(e.NominalValue,'Unit'):
|
|
if e.NominalValue.Unit:
|
|
pvalue += e.NominalValue.Unit
|
|
d[pname] = psetname+";;"+ptype+";;"+pvalue
|
|
#print ("adding property: ",pname,ptype,pvalue," pset ",psetname)
|
|
obj.IfcProperties = d
|
|
|
|
elif hasattr(obj,"IfcAttributes"):
|
|
|
|
# 0.17: properties are saved as type(value) in IfcAttributes
|
|
|
|
a = obj.IfcAttributes
|
|
for c in properties[pid].keys():
|
|
for p in properties[pid][c]:
|
|
l = ifcfile[p]
|
|
if l.is_a("IfcPropertySingleValue"):
|
|
a[l.Name.encode("utf8")] = str(l.NominalValue)
|
|
obj.IfcAttributes = a
|
|
|
|
# color
|
|
|
|
if FreeCAD.GuiUp and (pid in colors) and hasattr(obj.ViewObject,"ShapeColor"):
|
|
#if DEBUG: print(" setting color: ",int(colors[pid][0]*255),"/",int(colors[pid][1]*255),"/",int(colors[pid][2]*255))
|
|
obj.ViewObject.ShapeColor = colors[pid]
|
|
|
|
# if DEBUG is on, recompute after each shape
|
|
if DEBUG: FreeCAD.ActiveDocument.recompute()
|
|
|
|
# attached 2D elements
|
|
|
|
if product.Representation:
|
|
for r in product.Representation.Representations:
|
|
if r.RepresentationIdentifier == "FootPrint":
|
|
annotations.append(product)
|
|
break
|
|
|
|
# additional properties for specific types
|
|
|
|
if product.is_a("IfcSite"):
|
|
if product.RefElevation:
|
|
obj.Elevation = product.RefElevation*1000
|
|
if product.RefLatitude:
|
|
obj.Latitude = dms2dd(*product.RefLatitude)
|
|
if product.RefLongitude:
|
|
obj.Longitude = dms2dd(*product.RefLongitude)
|
|
if product.SiteAddress:
|
|
if product.SiteAddress.AddressLines:
|
|
obj.Address = product.SiteAddress.AddressLines[0]
|
|
if product.SiteAddress.Town:
|
|
obj.City = product.SiteAddress.Town
|
|
if product.SiteAddress.Region:
|
|
obj.Region = product.SiteAddress.Region
|
|
if product.SiteAddress.Country:
|
|
obj.Country = product.SiteAddress.Country
|
|
if product.SiteAddress.PostalCode:
|
|
obj.PostalCode = product.SiteAddress.PostalCode
|
|
|
|
try:
|
|
progressbar.next(True)
|
|
except(RuntimeError):
|
|
print("Aborted.")
|
|
progressbar.stop()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return
|
|
|
|
progressbar.stop()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
if MERGE_MODE_STRUCT == 2:
|
|
|
|
if DEBUG: print("Joining Structural shapes...",end="")
|
|
|
|
for host,children in groups.items(): # Structural
|
|
if ifcfile[host].is_a("IfcStructuralAnalysisModel"):
|
|
compound = []
|
|
for c in children:
|
|
if c in structshapes.keys():
|
|
compound.append(structshapes[c])
|
|
del structshapes[c]
|
|
if compound:
|
|
name = ifcfile[host].Name or "AnalysisModel"
|
|
if PREFIX_NUMBERS: name = "ID" + str(host) + " " + name
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
|
|
obj.Label = name
|
|
obj.Shape = Part.makeCompound(compound)
|
|
if structshapes: # remaining Structural shapes
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Feature","UnclaimedStruct")
|
|
obj.Shape = Part.makeCompound(structshapes.values())
|
|
|
|
if DEBUG: print("done")
|
|
|
|
else:
|
|
|
|
if DEBUG: print("Processing Struct relationships...",end="")
|
|
|
|
# groups
|
|
|
|
for host,children in groups.items():
|
|
if ifcfile[host].is_a("IfcStructuralAnalysisModel"):
|
|
# print(host, ' --> ', children)
|
|
obj = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup","AnalysisModel")
|
|
objects[host] = obj
|
|
if host in objects.keys():
|
|
cobs = []
|
|
childs_to_delete = []
|
|
for child in children:
|
|
if child in objects.keys():
|
|
cobs.append(objects[child])
|
|
childs_to_delete.append(child)
|
|
for c in childs_to_delete:
|
|
children.remove(c) # to not process the child again in remaining groups
|
|
if cobs:
|
|
if DEBUG: print("adding ",len(cobs), " object(s) to ", objects[host].Label)
|
|
Arch.addComponents(cobs,objects[host])
|
|
if DEBUG: FreeCAD.ActiveDocument.recompute()
|
|
|
|
if DEBUG: print("done")
|
|
|
|
if MERGE_MODE_ARCH > 2: # if ArchObj is compound or ArchObj not imported
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
# cleaning bad shapes
|
|
for obj in objects.values():
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
if obj.Shape.isNull():
|
|
Arch.rebuildArchShape(obj)
|
|
|
|
# processing remaining (normal) groups
|
|
|
|
swallowed = []
|
|
for host,children in groups.items():
|
|
if ifcfile[host].is_a("IfcGroup"):
|
|
if ifcfile[host].Name:
|
|
grp_name = ifcfile[host].Name
|
|
else:
|
|
if DEBUG: print("no group name specified for entity: #", ifcfile[host].id(), ", entity type is used!")
|
|
grp_name = ifcfile[host].is_a() + "_" + str(ifcfile[host].id())
|
|
grp = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",grp_name.encode("utf8"))
|
|
grp.Label = grp_name
|
|
objects[host] = grp
|
|
for child in children:
|
|
if child in objects.keys():
|
|
grp.addObject(objects[child])
|
|
swallowed.append(child)
|
|
else:
|
|
if DEBUG: print("unable to add object: #", child, " to group: #", ifcfile[host].id(), ", ", grp_name)
|
|
|
|
if MERGE_MODE_ARCH == 3:
|
|
|
|
# One compound per storey
|
|
|
|
if DEBUG: print("Joining Arch shapes...",end="")
|
|
|
|
for host,children in additions.items(): # Arch
|
|
if ifcfile[host].is_a("IfcBuildingStorey"):
|
|
compound = []
|
|
for c in children:
|
|
if c in shapes.keys():
|
|
compound.append(shapes[c])
|
|
del shapes[c]
|
|
if c in additions.keys():
|
|
for c2 in additions[c]:
|
|
if c2 in shapes.keys():
|
|
compound.append(shapes[c2])
|
|
del shapes[c2]
|
|
if compound:
|
|
name = ifcfile[host].Name or "Floor"
|
|
if PREFIX_NUMBERS: name = "ID" + str(host) + " " + name
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
|
|
obj.Label = name
|
|
obj.Shape = Part.makeCompound(compound)
|
|
if shapes: # remaining Arch shapes
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Feature","UnclaimedArch")
|
|
obj.Shape = Part.makeCompound(shapes.values())
|
|
|
|
if DEBUG: print("done")
|
|
|
|
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 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()
|
|
|
|
# additions
|
|
|
|
for host,children in additions.items():
|
|
if host in objects.keys():
|
|
cobs = []
|
|
for child in children:
|
|
if child in objects.keys():
|
|
if not child in swallowed: # don't add objects already in groups
|
|
cobs.append(objects[child])
|
|
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)
|
|
Arch.addComponents(cobs,objects[host])
|
|
if DEBUG: FreeCAD.ActiveDocument.recompute()
|
|
|
|
if DEBUG: print("done.")
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
# cleaning bad shapes
|
|
|
|
for obj in objects.values():
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
if obj.Shape.isNull() and not(Draft.getType(obj) in ["Site"]):
|
|
Arch.rebuildArchShape(obj)
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
# 2D elements
|
|
|
|
if DEBUG and annotations:print("Creating 2D geometry...",end="")
|
|
|
|
scaling = getScaling(ifcfile)
|
|
#print "scaling factor =",scaling
|
|
for annotation in annotations:
|
|
anno = None
|
|
aid = annotation.id()
|
|
if aid in skip:
|
|
continue # user given id skip list
|
|
if annotation.is_a() in SKIP:
|
|
continue # preferences-set type skip list
|
|
if annotation.is_a("IfcGrid"):
|
|
axes = []
|
|
uvwaxes = ()
|
|
if annotation.UAxes:
|
|
uvwaxes = annotation.UAxes
|
|
if annotation.VAxes:
|
|
uvwaxes = uvwaxes + annotation.VAxes
|
|
if annotation.WAxes:
|
|
uvwaxes = uvwaxes + annotation.WAxes
|
|
for axis in uvwaxes:
|
|
if axis.AxisCurve:
|
|
sh = setRepresentation(axis.AxisCurve,scaling)
|
|
if sh and (len(sh[0].Vertexes) == 2): # currently only straight axes are supported
|
|
sh = sh[0]
|
|
l = sh.Length
|
|
pl = FreeCAD.Placement()
|
|
pl.Base = sh.Vertexes[0].Point
|
|
pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),sh.Vertexes[-1].Point.sub(sh.Vertexes[0].Point))
|
|
o = Arch.makeAxis(1,l)
|
|
o.Length = l
|
|
o.Placement = pl
|
|
o.CustomNumber = axis.AxisTag
|
|
axes.append(o)
|
|
if axes:
|
|
name = "Grid"
|
|
if annotation.Name:
|
|
name = annotation.Name.encode("utf8")
|
|
if PREFIX_NUMBERS:
|
|
name = "ID" + str(aid) + " " + name
|
|
anno = Arch.makeAxisSystem(axes,name)
|
|
else:
|
|
name = "Annotation"
|
|
if annotation.Name:
|
|
name = annotation.Name.encode("utf8")
|
|
if "annotation" not in name.lower():
|
|
name = "Annotation " + name
|
|
if PREFIX_NUMBERS: name = "ID" + str(aid) + " " + name
|
|
shapes2d = []
|
|
for rep in annotation.Representation.Representations:
|
|
if rep.RepresentationIdentifier in ["Annotation","FootPrint","Axis"]:
|
|
shapes2d.extend(setRepresentation(rep,scaling))
|
|
if shapes2d:
|
|
sh = Part.makeCompound(shapes2d)
|
|
pc = str(int((float(count)/(len(products)+len(annotations))*100)))+"% "
|
|
if DEBUG: print(pc,"creating object ",aid," : Annotation with shape: ",sh)
|
|
anno = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
|
|
anno.Shape = sh
|
|
p = getPlacement(annotation.ObjectPlacement,scaling)
|
|
if p: # and annotation.is_a("IfcAnnotation"):
|
|
anno.Placement = p
|
|
# placing in container if needed
|
|
if anno:
|
|
for host,children in additions.items():
|
|
if (aid in children) and (host in objects.keys()):
|
|
Arch.addComponents(anno,objects[host])
|
|
|
|
count += 1
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
if DEBUG and annotations: print("done.")
|
|
|
|
# Materials
|
|
|
|
if DEBUG and materials: print("Creating materials...",end="")
|
|
#print "mattable:",mattable
|
|
#print "materials:",materials
|
|
fcmats = {}
|
|
for material in materials:
|
|
name = "Material"
|
|
if material.Name:
|
|
name = material.Name.encode("utf8")
|
|
if MERGE_MATERIALS and (name in fcmats.keys()):
|
|
mat = fcmats[name]
|
|
else:
|
|
mat = Arch.makeMaterial(name=name)
|
|
mdict = {}
|
|
if material.id() in colors:
|
|
mdict["DiffuseColor"] = str(colors[material.id()])
|
|
else:
|
|
for o,m in mattable.items():
|
|
if m == material.id():
|
|
if o in colors:
|
|
mdict["DiffuseColor"] = str(colors[o])
|
|
if mdict:
|
|
mat.Material = mdict
|
|
fcmats[name] = mat
|
|
for o,m in mattable.items():
|
|
if m == material.id():
|
|
if o in objects:
|
|
if hasattr(objects[o],"Material"):
|
|
objects[o].Material = mat
|
|
|
|
if DEBUG and materials: print("done")
|
|
|
|
# restore links from full parametric definitions
|
|
for p in parametrics:
|
|
l = FreeCAD.ActiveDocument.getObject(p[2])
|
|
if l:
|
|
setattr(p[0],p[1],l)
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
FreeCADGui.SendMsgToActiveView("ViewFit")
|
|
print("Finished importing.")
|
|
return doc
|
|
|
|
|
|
|
|
class recycler:
|
|
|
|
"the compression engine - a mechanism to reuse ifc entities if needed"
|
|
|
|
# this object has some methods identical to corresponding ifcopenshell methods,
|
|
# but it checks if a similar entity already exists before creating a new one
|
|
# to compress a new type, just add the necessary method here
|
|
|
|
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
|
|
self.directions = {(1,0,0):self.ifcfile[6],(0,0,1):self.ifcfile[7],(0,1,0):self.ifcfile[10]} # from template
|
|
self.polylines = {}
|
|
self.polyloops = {}
|
|
self.propertysinglevalues = {}
|
|
self.axis2placement3ds = {'(0.0, 0.0, 0.0)(0.0, 0.0, 1.0)(1.0, 0.0, 0.0)':self.ifcfile[9]} # from template
|
|
self.axis2placement2ds = {}
|
|
self.localplacements = {}
|
|
self.rgbs = {}
|
|
self.ssrenderings = {}
|
|
self.transformationoperators = {}
|
|
self.spared = 0
|
|
|
|
def createIfcCartesianPoint(self,points):
|
|
if self.compress and points in self.cartesianpoints:
|
|
self.spared += 1
|
|
return self.cartesianpoints[points]
|
|
else:
|
|
c = self.ifcfile.createIfcCartesianPoint(points)
|
|
if self.compress:
|
|
self.cartesianpoints[points] = c
|
|
return c
|
|
|
|
def createIfcDirection(self,points):
|
|
if self.compress and points in self.directions:
|
|
self.spared += 1
|
|
return self.directions[points]
|
|
else:
|
|
c = self.ifcfile.createIfcDirection(points)
|
|
if self.compress:
|
|
self.directions[points] = c
|
|
return c
|
|
|
|
def createIfcPolyline(self,points):
|
|
key = "".join([str(p.Coordinates) for p in points])
|
|
if self.compress and key in self.polylines:
|
|
self.spared += 1
|
|
return self.polylines[key]
|
|
else:
|
|
c = self.ifcfile.createIfcPolyline(points)
|
|
if self.compress:
|
|
self.polylines[key] = c
|
|
return c
|
|
|
|
def createIfcPolyLoop(self,points):
|
|
key = "".join([str(p.Coordinates) for p in points])
|
|
if self.compress and key in self.polyloops:
|
|
self.spared += 1
|
|
return self.polyloops[key]
|
|
else:
|
|
c = self.ifcfile.createIfcPolyLoop(points)
|
|
if self.compress:
|
|
self.polyloops[key] = c
|
|
return c
|
|
|
|
def createIfcPropertySingleValue(self,name,ptype,pvalue):
|
|
key = name + ptype + pvalue
|
|
if self.compress and key in self.propertysinglevalues:
|
|
self.spared += 1
|
|
return self.propertysinglevalues[key]
|
|
else:
|
|
c = self.ifcfile.createIfcPropertySingleValue(key,None,ifcfile.create_entity(ptype,pvalue),None)
|
|
if self.compress:
|
|
self.propertysinglevalues[key] = c
|
|
return c
|
|
|
|
def createIfcAxis2Placement3D(self,p1,p2,p3):
|
|
key = str(p1.Coordinates) + str(p2.DirectionRatios) + str(p3.DirectionRatios)
|
|
if self.compress and key in self.axis2placement3ds:
|
|
self.spared += 1
|
|
return self.axis2placement3ds[key]
|
|
else:
|
|
c = self.ifcfile.createIfcAxis2Placement3D(p1,p2,p3)
|
|
if self.compress:
|
|
self.axis2placement3ds[key] = c
|
|
return c
|
|
|
|
def createIfcAxis2Placement2D(self,p1,p2):
|
|
key = str(p1.Coordinates) + str(p2.DirectionRatios)
|
|
if self.compress and key in self.axis2placement2ds:
|
|
self.spared += 1
|
|
return self.axis2placement2ds[key]
|
|
else:
|
|
c = self.ifcfile.createIfcAxis2Placement2D(p1,p2)
|
|
if self.compress:
|
|
self.axis2placement2ds[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:
|
|
self.spared += 1
|
|
return self.localplacements[key]
|
|
else:
|
|
c = self.ifcfile.createIfcLocalPlacement(None,gpl)
|
|
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:
|
|
self.spared += 1
|
|
return self.rgbs[key]
|
|
else:
|
|
c = self.ifcfile.createIfcColourRgb(None,r,g,b)
|
|
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:
|
|
self.spared += 1
|
|
return self.ssrenderings[key]
|
|
else:
|
|
c = self.ifcfile.createIfcSurfaceStyleRendering(col,None,None,None,None,None,None,None,"FLAT")
|
|
if self.compress:
|
|
self.ssrenderings[key] = c
|
|
return c
|
|
|
|
def createIfcCartesianTransformationOperator3D(self,axis1,axis2,origin,scale,axis3):
|
|
key = str(axis1.DirectionRatios) + str(axis2.DirectionRatios) + str(origin.Coordinates) + str(scale) + str(axis3.DirectionRatios)
|
|
if self.compress and key in self.transformationoperators:
|
|
self.spared += 1
|
|
return self.transformationoperators[key]
|
|
else:
|
|
c = self.ifcfile.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,scale,axis3)
|
|
if self.compress:
|
|
self.transformationoperators[key] = c
|
|
return c
|
|
|
|
|
|
def export(exportList,filename):
|
|
|
|
|
|
"exports FreeCAD contents to an IFC file"
|
|
|
|
getPreferences()
|
|
|
|
try:
|
|
global ifcopenshell
|
|
import ifcopenshell
|
|
except:
|
|
FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n")
|
|
return
|
|
|
|
version = FreeCAD.Version()
|
|
owner = FreeCAD.ActiveDocument.CreatedBy
|
|
email = ''
|
|
if ("@" in owner) and ("<" in owner):
|
|
s = owner.split("<")
|
|
owner = s[0].strip()
|
|
email = s[1].strip(">")
|
|
global template
|
|
template = ifctemplate.replace("$version",version[0]+"."+version[1]+" build "+version[2])
|
|
if hasattr(ifcopenshell,"schema_identifier"):
|
|
schema = ifcopenshell.schema_identifier
|
|
else:
|
|
schema = "IFC2X3"
|
|
template = template.replace("$ifcschema",schema)
|
|
template = template.replace("$owner",owner)
|
|
template = template.replace("$company",FreeCAD.ActiveDocument.Company)
|
|
template = template.replace("$email",email)
|
|
template = template.replace("$now",str(int(time.time())))
|
|
template = template.replace("$projectid",FreeCAD.ActiveDocument.Uid[:22].replace("-","_"))
|
|
template = template.replace("$project",FreeCAD.ActiveDocument.Name)
|
|
template = template.replace("$filename",os.path.basename(filename))
|
|
template = template.replace("$timestamp",str(time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())))
|
|
if hasattr(ifcopenshell,"version"):
|
|
template = template.replace("IfcOpenShell","IfcOpenShell "+ifcopenshell.version)
|
|
templatefilehandle,templatefile = tempfile.mkstemp(suffix=".ifc")
|
|
of = pyopen(templatefile,"wb")
|
|
of.write(template.encode("utf8"))
|
|
of.close()
|
|
os.close(templatefilehandle)
|
|
global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs
|
|
ifcfile = ifcopenshell.open(templatefile)
|
|
history = ifcfile.by_type("IfcOwnerHistory")[0]
|
|
context = ifcfile.by_type("IfcGeometricRepresentationContext")[0]
|
|
project = ifcfile.by_type("IfcProject")[0]
|
|
objectslist = Draft.getGroupContents(exportList,walls=True,addgroups=True)
|
|
annotations = []
|
|
for obj in objectslist:
|
|
if obj.isDerivedFrom("Part::Part2DObject"):
|
|
annotations.append(obj)
|
|
elif obj.isDerivedFrom("App::Annotation") or (Draft.getType(obj) == "DraftText"):
|
|
annotations.append(obj)
|
|
elif obj.isDerivedFrom("Part::Feature"):
|
|
if obj.Shape:
|
|
if obj.Shape.Edges and (not obj.Shape.Faces):
|
|
annotations.append(obj)
|
|
# clean objects list of unwanted types
|
|
objectslist = [obj for obj in objectslist if obj not in annotations]
|
|
objectslist = Arch.pruneIncluded(objectslist)
|
|
objectslist = [obj for obj in objectslist if Draft.getType(obj) not in ["Material","MaterialContainer"]]
|
|
if FULL_PARAMETRIC:
|
|
objectslist = Arch.getAllChildren(objectslist)
|
|
products = {} # { Name: IfcEntity, ... }
|
|
subproducts = {} # { Name: IfcEntity, ... } for storing additions/subtractions and other types of subcomponents of a product
|
|
surfstyles = {} # { (r,g,b): IfcEntity, ... }
|
|
clones = {} # { Basename:[Clonename1,Clonename2,...] }
|
|
sharedobjects = {} # { BaseName: IfcRepresentationMap }
|
|
count = 1
|
|
groups = {} # { Host: [Child,Child,...] }
|
|
profiledefs = {} # { ProfileDefString:profiledef,...}
|
|
shapedefs = {} # { ShapeDefString:[shapes],... }
|
|
spatialelements = {} # {Name:IfcEntity, ... }
|
|
|
|
# reusable entity system
|
|
|
|
global ifcbin
|
|
ifcbin = recycler(ifcfile)
|
|
|
|
# build clones table
|
|
|
|
if CREATE_CLONES:
|
|
for o in objectslist:
|
|
b = Draft.getCloneBase(o,strict=True)
|
|
if b:
|
|
clones.setdefault(b.Name,[]).append(o.Name)
|
|
|
|
#print("clones table: ",clones)
|
|
#print(objectslist)
|
|
|
|
# testing if more than one site selected (forbidden in IFC)
|
|
|
|
if len(Draft.getObjectsOfType(objectslist,"Site")) > 1:
|
|
FreeCAD.Console.PrintError("More than one site is selected, which is forbidden by IFC standards. Please export only one site by IFC file.\n")
|
|
return
|
|
|
|
# products
|
|
|
|
for obj in objectslist:
|
|
|
|
# getting generic data
|
|
|
|
name = str(obj.Label.encode("utf8"))
|
|
description = str(obj.Description.encode("utf8")) if hasattr(obj,"Description") else ""
|
|
|
|
# getting uid
|
|
|
|
uid = None
|
|
if hasattr(obj,"IfcAttributes"):
|
|
if "IfcUID" in obj.IfcAttributes.keys():
|
|
uid = str(obj.IfcAttributes["IfcUID"])
|
|
if not uid:
|
|
uid = ifcopenshell.guid.compress(uuid.uuid1().hex)
|
|
# storing the uid for further use
|
|
if STORE_UID and hasattr(obj,"IfcAttributes"):
|
|
d = obj.IfcAttributes
|
|
d["IfcUID"] = uid
|
|
obj.IfcAttributes = d
|
|
|
|
# setting the IFC type + name conversions
|
|
|
|
if hasattr(obj,"IfcRole"):
|
|
ifctype = obj.IfcRole.replace(" ","")
|
|
elif hasattr(obj,"Role"):
|
|
ifctype = obj.Role.replace(" ","")
|
|
else:
|
|
ifctype = Draft.getType(obj)
|
|
if ifctype in translationtable.keys():
|
|
ifctype = translationtable[ifctype]
|
|
ifctype = "Ifc" + ifctype
|
|
if ifctype == "IfcVisGroup":
|
|
ifctype = "IfcGroup"
|
|
if ifctype == "IfcGroup":
|
|
groups[obj.Name] = [o.Name for o in obj.Group]
|
|
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 explicitly set
|
|
if (Draft.getType(obj) == "BuildingPart") and hasattr(obj,"IfcRole") and (obj.IfcRole == "Building"):
|
|
ifctype = "IfcBuilding"
|
|
|
|
# export grids
|
|
|
|
if ifctype in ["IfcAxis","IfcAxisSystem","IfcGrid"]:
|
|
ifcaxes = []
|
|
ifcpols = []
|
|
if ifctype == "IfcAxis":
|
|
# make sure this axis is not included in something else already
|
|
standalone = True
|
|
for p in obj.InList:
|
|
if hasattr(p,"Axes") and (obj in p.Axes):
|
|
if p in objectslist:
|
|
axgroups = []
|
|
standalone = False
|
|
break
|
|
if standalone:
|
|
axgroups = [obj.Proxy.getAxisData(obj)]
|
|
else:
|
|
axgroups = obj.Proxy.getAxisData(obj)
|
|
if not axgroups:
|
|
continue
|
|
ifctype = "IfcGrid"
|
|
for axg in axgroups:
|
|
ifcaxg = []
|
|
for ax in axg:
|
|
p1 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[0]).multiply(0.001)))
|
|
p2 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[1]).multiply(0.001)))
|
|
pol = ifcbin.createIfcPolyline([p1,p2])
|
|
ifcpols.append(pol)
|
|
axis = ifcfile.createIfcGridAxis(ax[2],pol,True)
|
|
ifcaxg.append(axis)
|
|
if len(ifcaxes) < 3:
|
|
ifcaxes.append(ifcaxg)
|
|
else:
|
|
ifcaxes[2] = ifcaxes[2]+ifcaxg # IfcGrid can have max 3 axes systems
|
|
u = None
|
|
v = None
|
|
w = None
|
|
if ifcaxes:
|
|
u = ifcaxes[0]
|
|
if len(ifcaxes) > 1:
|
|
v = ifcaxes[1]
|
|
if len(ifcaxes) > 2:
|
|
w = ifcaxes[2]
|
|
if u and v:
|
|
if DEBUG: print(str(count).ljust(3)," : ", ifctype, " (",str(len(ifcpols)),"axes ) : ",name)
|
|
xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
|
|
zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
|
|
ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
|
|
gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
|
|
plac = ifcbin.createIfcLocalPlacement(gpl)
|
|
cset = ifcfile.createIfcGeometricCurveSet(ifcpols)
|
|
#subc = ifcfile.createIfcGeometricRepresentationSubContext('FootPrint','Model',context,None,"MODEL_VIEW",None,None,None,None,None)
|
|
srep = ifcfile.createIfcShapeRepresentation(context,'FootPrint',"GeometricCurveSet",ifcpols)
|
|
pdef = ifcfile.createIfcProductDefinitionShape(None,None,[srep])
|
|
grid = ifcfile.createIfcGrid(uid,history,name,description,None,plac,pdef,u,v,w)
|
|
products[obj.Name] = grid
|
|
count += 1
|
|
continue
|
|
|
|
from ArchComponent import IFCTYPES
|
|
if ifctype not in IFCTYPES:
|
|
ifctype = "IfcBuildingElementProxy"
|
|
|
|
# getting the "Force BREP" flag
|
|
|
|
brepflag = False
|
|
if hasattr(obj,"IfcAttributes"):
|
|
if "FlagForceBrep" in obj.IfcAttributes.keys():
|
|
if obj.IfcAttributes["FlagForceBrep"] == "True":
|
|
brepflag = True
|
|
|
|
# getting the representation
|
|
|
|
representation,placement,shapetype = getRepresentation(ifcfile,context,obj,forcebrep=(brepflag or FORCE_BREP))
|
|
|
|
if DEBUG: print(str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name)
|
|
|
|
# setting the arguments
|
|
|
|
kwargs = {"GlobalId": uid, "OwnerHistory": history, "Name": name,
|
|
"Description": description, "ObjectPlacement": placement, "Representation": representation}
|
|
if ifctype in ["IfcSlab","IfcFooting","IfcRoof"]:
|
|
kwargs.update({"PredefinedType": "NOTDEFINED"})
|
|
elif ifctype in ["IfcWindow","IfcDoor"]:
|
|
if hasattr(obj,"Width") and hasattr(obj,"Height"):
|
|
kwargs.update({"OverallHeight": obj.Width.Value/1000.0,
|
|
"OverallWidth": obj.Height.Value/1000.0})
|
|
else:
|
|
if obj.Shape.BoundBox.XLength > obj.Shape.BoundBox.YLength:
|
|
l = obj.Shape.BoundBox.XLength
|
|
else:
|
|
l = obj.Shape.BoundBox.YLength
|
|
kwargs.update({"OverallHeight": l/1000.0,
|
|
"OverallWidth": obj.Shape.BoundBox.ZLength/1000.0})
|
|
elif ifctype == "IfcSpace":
|
|
internal = "NOTDEFINED"
|
|
if hasattr(obj,"Internal"):
|
|
if obj.Internal:
|
|
internal = "INTERNAL"
|
|
else:
|
|
internal = "EXTERNAL"
|
|
if schema == "IFC2X3":
|
|
kwargs.update({"CompositionType": "ELEMENT",
|
|
"InteriorOrExteriorSpace": internal,
|
|
"ElevationWithFlooring": obj.Shape.BoundBox.ZMin/1000.0})
|
|
else:
|
|
kwargs.update({"CompositionType": "ELEMENT",
|
|
"PredefinedType": internal,
|
|
"ElevationWithFlooring": obj.Shape.BoundBox.ZMin/1000.0})
|
|
elif ifctype == "IfcBuildingElementProxy":
|
|
if ifcopenshell.schema_identifier == "IFC4":
|
|
kwargs.update({"PredefinedType": "ELEMENT"})
|
|
else:
|
|
kwargs.update({"CompositionType": "ELEMENT"})
|
|
elif ifctype == "IfcSite":
|
|
kwargs.update({"RefLatitude":dd2dms(obj.Latitude),
|
|
"RefLongitude":dd2dms(obj.Longitude),
|
|
"RefElevation":obj.Elevation.Value/1000.0,
|
|
"SiteAddress":buildAddress(obj,ifcfile),
|
|
"CompositionType": "ELEMENT"})
|
|
elif ifctype == "IfcBuilding":
|
|
kwargs.update({"CompositionType": "ELEMENT"})
|
|
elif ifctype == "IfcBuildingStorey":
|
|
kwargs.update({"CompositionType": "ELEMENT",
|
|
"Elevation": obj.Placement.Base.z/1000.0})
|
|
elif ifctype == "IfcReinforcingBar":
|
|
kwargs.update({"NominalDiameter": obj.Diameter.Value,
|
|
"BarLength": obj.Length.Value})
|
|
|
|
# creating the product
|
|
|
|
#print(obj.Label," : ",ifctype," : ",kwargs)
|
|
product = getattr(ifcfile,"create"+ifctype)(**kwargs)
|
|
products[obj.Name] = product
|
|
if ifctype in ["IfcBuilding","IfcBuildingStorey","IfcSite","IfcSpace"]:
|
|
spatialelements[obj.Name] = product
|
|
|
|
# additions
|
|
|
|
if hasattr(obj,"Additions") and (shapetype in ["extrusion","no shape"]):
|
|
for o in obj.Additions:
|
|
r2,p2,c2 = getRepresentation(ifcfile,context,o)
|
|
if DEBUG: print(" adding ",c2," : ",o.Label)
|
|
prod2 = ifcfile.createIfcBuildingElementProxy(ifcopenshell.guid.compress(uuid.uuid1().hex),history,o.Label.encode("utf8"),None,None,p2,r2,None,"ELEMENT")
|
|
subproducts[o.Name] = prod2
|
|
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'Addition','',product,[prod2])
|
|
|
|
|
|
# subtractions
|
|
|
|
guests = []
|
|
for o in obj.InList:
|
|
if hasattr(o,"Hosts"):
|
|
for co in o.Hosts:
|
|
if co == obj:
|
|
if not o in guests:
|
|
guests.append(o)
|
|
if hasattr(obj,"Subtractions") and (shapetype in ["extrusion","no shape"]):
|
|
for o in obj.Subtractions + guests:
|
|
r2,p2,c2 = getRepresentation(ifcfile,context,o,subtraction=True)
|
|
if DEBUG: print(" subtracting ",c2," : ",o.Label)
|
|
prod2 = ifcfile.createIfcOpeningElement(ifcopenshell.guid.compress(uuid.uuid1().hex),history,o.Label.encode("utf8"),None,None,p2,r2,None)
|
|
subproducts[o.Name] = prod2
|
|
ifcfile.createIfcRelVoidsElement(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'Subtraction','',product,prod2)
|
|
|
|
# properties
|
|
|
|
ifcprop = False
|
|
if hasattr(obj,"IfcProperties"):
|
|
|
|
if obj.IfcProperties:
|
|
|
|
ifcprop = True
|
|
|
|
if isinstance(obj.IfcProperties,dict):
|
|
|
|
# IfcProperties is a dictionary
|
|
|
|
psets = {}
|
|
for key,value in obj.IfcProperties.items():
|
|
|
|
# properties in IfcProperties dict are stored as "key":"pset;;type;;value" or "key":"type;;value"
|
|
|
|
value = value.split(";;")
|
|
if len(value) == 3:
|
|
pset = value[0]
|
|
ptype = value[1]
|
|
pvalue = value[2]
|
|
elif len(value) == 2:
|
|
pset = "Default property set"
|
|
ptype = value[0]
|
|
pvalue = value[1]
|
|
else:
|
|
if DEBUG:print(" unable to export property:",key,value)
|
|
continue
|
|
|
|
#if DEBUG: print(" property ",key," : ",pvalue.encode("utf8"), " (", str(ptype), ") in ",pset)
|
|
if ptype in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']:
|
|
pvalue = pvalue.encode("utf8")
|
|
elif ptype == "IfcBoolean":
|
|
if pvalue == ".T.":
|
|
pvalue = True
|
|
else:
|
|
pvalue = False
|
|
elif ptype == "IfcInteger":
|
|
pvalue = int(pvalue)
|
|
else:
|
|
try:
|
|
pvalue = float(pvalue)
|
|
except:
|
|
try:
|
|
pvalue = FreeCAD.Units.Quantity(pvalue).Value
|
|
except:
|
|
pvalue = pvalue.encode("utf8")
|
|
if DEBUG:print(" warning: unable to export property as numeric value:",key,pvalue)
|
|
p = ifcbin.createIfcPropertySingleValue(str(key),str(ptype),pvalue)
|
|
psets.setdefault(pset,[]).append(p)
|
|
for pname,props in psets.items():
|
|
pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,pname,None,props)
|
|
ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],pset)
|
|
|
|
elif obj.IfcProperties.TypeId == 'Spreadsheet::Sheet':
|
|
|
|
# IfcProperties is a spreadsheet (deprecated)
|
|
|
|
sheet = obj.IfcProperties
|
|
propertiesDic = {}
|
|
categories = []
|
|
n=2
|
|
cell = True
|
|
while cell is True:
|
|
if hasattr(sheet,'A'+str(n)):
|
|
cat = sheet.get('A'+str(n))
|
|
key = sheet.get('B'+str(n))
|
|
tp = sheet.get('C'+str(n))
|
|
if hasattr(sheet,'D'+str(n)):
|
|
val = sheet.get('D'+str(n))
|
|
else:
|
|
val = ''
|
|
if isinstance(key, unicode):
|
|
key = key.encode("utf8")
|
|
else:
|
|
key = str(key)
|
|
#tp = tp.encode("utf8")
|
|
if tp in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']:
|
|
val = val.encode("utf8")
|
|
elif tp == "IfcBoolean":
|
|
if val == 'True':
|
|
val = True
|
|
else:
|
|
val = False
|
|
elif tp == "IfcInteger":
|
|
val = int(val)
|
|
else:
|
|
val = float(val)
|
|
unit = None
|
|
#unit = sheet.get('E'+str(n))
|
|
if cat in categories:
|
|
propertiesDic[cat].append({"key":key,"tp":tp,"val":val,"unit":unit})
|
|
else:
|
|
propertiesDic[cat] = [{"key":key,"tp":tp,"val":val,"unit":unit}]
|
|
categories.append(cat)
|
|
n += 1
|
|
else:
|
|
cell = False
|
|
for cat in propertiesDic:
|
|
props = []
|
|
for prop in propertiesDic[cat]:
|
|
if DEBUG:
|
|
print("key",prop["key"],type(prop["key"]))
|
|
print("tp",prop["tp"],type(prop["tp"]))
|
|
print("val",prop["val"],type(prop["val"]))
|
|
props.append(ifcbin.createIfcPropertySingleValue(prop["key"],prop["tp"],prop["val"]))
|
|
pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,cat,None,props)
|
|
ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],pset)
|
|
|
|
if hasattr(obj,"IfcAttributes"):
|
|
|
|
if obj.IfcAttributes:
|
|
ifcprop = True
|
|
#if DEBUG : print(" adding ifc attributes")
|
|
props = []
|
|
for key in obj.IfcAttributes:
|
|
if not (key in ["IfcUID","FlagForceBrep"]):
|
|
|
|
# (deprecated) properties in IfcAttributes dict are stored as "key":"type(value)"
|
|
|
|
r = obj.IfcAttributes[key].strip(")").split("(")
|
|
if len(r) == 1:
|
|
tp = "IfcText"
|
|
val = r[0]
|
|
else:
|
|
tp = r[0]
|
|
val = "(".join(r[1:])
|
|
val = val.strip("'")
|
|
val = val.strip('"')
|
|
#if DEBUG: print(" property ",key," : ",val.encode("utf8"), " (", str(tp), ")")
|
|
if tp in ["IfcLabel","IfcText","IfcIdentifier",'IfcDescriptiveMeasure']:
|
|
val = val.encode("utf8")
|
|
elif tp == "IfcBoolean":
|
|
if val == ".T.":
|
|
val = True
|
|
else:
|
|
val = False
|
|
elif tp == "IfcInteger":
|
|
val = int(val)
|
|
else:
|
|
val = float(val)
|
|
props.append(ifcbin.createIfcPropertySingleValue(str(key),str(tp),val))
|
|
if props:
|
|
pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'PropertySet',None,props)
|
|
ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],pset)
|
|
|
|
if not ifcprop:
|
|
#if DEBUG : print("no ifc properties to export")
|
|
pass
|
|
|
|
# Quantities
|
|
|
|
if hasattr(obj,"IfcAttributes"):
|
|
quantities = []
|
|
if ("ExportHeight" in obj.IfcAttributes) and obj.IfcAttributes["ExportHeight"] and hasattr(obj,"Height"):
|
|
quantities.append(ifcfile.createIfcQuantityLength('Height',None,None,obj.Height.Value/1000.0))
|
|
if ("ExportWidth" in obj.IfcAttributes) and obj.IfcAttributes["ExportWidth"] and hasattr(obj,"Width"):
|
|
quantities.append(ifcfile.createIfcQuantityLength('Width',None,None,obj.Width.Value/1000.0))
|
|
if ("ExportLength" in obj.IfcAttributes) and obj.IfcAttributes["ExportLength"] and hasattr(obj,"Length"):
|
|
quantities.append(ifcfile.createIfcQuantityLength('Length',None,None,obj.Length.Value/1000.0))
|
|
if ("ExportHorizontalArea" in obj.IfcAttributes) and obj.IfcAttributes["ExportHorizontalArea"] and hasattr(obj,"HorizontalArea"):
|
|
quantities.append(ifcfile.createIfcQuantityArea('HorizontalArea',None,None,obj.HorizontalArea.Value/1000000.0))
|
|
if ("ExportVerticalArea" in obj.IfcAttributes) and obj.IfcAttributes["ExportVerticalArea"] and hasattr(obj,"VerticalArea"):
|
|
quantities.append(ifcfile.createIfcQuantityArea('VerticalArea',None,None,obj.VerticalArea.Value/1000000.0))
|
|
if ("ExportVolume" in obj.IfcAttributes) and obj.IfcAttributes["ExportVolume"] and obj.isDerivedFrom("Part::Feature"):
|
|
quantities.append(ifcfile.createIfcQuantityVolume('Volume',None,None,obj.Shape.Volume/1000000000.0))
|
|
if quantities:
|
|
eltq = ifcfile.createIfcElementQuantity(ifcopenshell.guid.compress(uuid.uuid1().hex),history,"ElementQuantities",None,"FreeCAD",quantities)
|
|
ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],eltq)
|
|
|
|
if FULL_PARAMETRIC:
|
|
# exporting all the object properties
|
|
FreeCADProps = []
|
|
FreeCADGuiProps = []
|
|
FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCADType","IfcText",obj.TypeId))
|
|
FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCADName","IfcText",obj.Name))
|
|
sets = [("App",obj)]
|
|
if hasattr(obj,"Proxy"):
|
|
if obj.Proxy:
|
|
FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCADAppObject","IfcText",str(obj.Proxy.__class__)))
|
|
if FreeCAD.GuiUp:
|
|
if obj.ViewObject:
|
|
sets.append(("Gui",obj.ViewObject))
|
|
if hasattr(obj.ViewObject,"Proxy"):
|
|
if obj.ViewObject.Proxy:
|
|
FreeCADGuiProps.append(ifcbin.createIfcPropertySingleValue("FreeCADGuiObject","IfcText",str(obj.ViewObject.Proxy.__class__)))
|
|
for realm,ctx in sets:
|
|
if ctx:
|
|
for prop in ctx.PropertiesList:
|
|
if not(prop in ["IfcProperties","IfcAttributes","Shape","Proxy","ExpressionEngine","AngularDeflection","BoundingBox"]):
|
|
try:
|
|
ptype = ctx.getTypeIdOfProperty(prop)
|
|
except AttributeError:
|
|
ptype = "Unknown"
|
|
itype = None
|
|
ivalue = None
|
|
if ptype in ["App::PropertyString","App::PropertyEnumeration"]:
|
|
itype = "IfcText"
|
|
ivalue = getattr(ctx,prop)
|
|
elif ptype == "App::PropertyInteger":
|
|
itype = "IfcInteger"
|
|
ivalue = getattr(ctx,prop)
|
|
elif ptype == "App::PropertyFloat":
|
|
itype = "IfcReal"
|
|
ivalue = float(getattr(ctx,prop))
|
|
elif ptype == "App::PropertyBool":
|
|
itype = "IfcBoolean"
|
|
ivalue = getattr(ctx,prop)
|
|
elif ptype in ["App::PropertyVector","App::PropertyPlacement"]:
|
|
itype = "IfcText"
|
|
ivalue = str(getattr(ctx,prop))
|
|
elif ptype in ["App::PropertyLength","App::PropertyDistance"]:
|
|
itype = "IfcReal"
|
|
ivalue = float(getattr(ctx,prop).getValueAs("m"))
|
|
elif ptype == "App::PropertyArea":
|
|
itype = "IfcReal"
|
|
ivalue = float(getattr(ctx,prop).getValueAs("m^2"))
|
|
elif ptype == "App::PropertyLink":
|
|
t = getattr(ctx,prop)
|
|
if t:
|
|
itype = "IfcText"
|
|
ivalue = "FreeCADLink_" + t.Name
|
|
else:
|
|
if DEBUG: print("Unable to encode property ",prop," of type ",ptype)
|
|
if itype:
|
|
# TODO add description
|
|
if realm == "Gui":
|
|
FreeCADGuiProps.append(ifcbin.createIfcPropertySingleValue("FreeCADGui_"+prop,itype,ivalue))
|
|
else:
|
|
FreeCADProps.append(ifcbin.createIfcPropertySingleValue("FreeCAD_"+prop,itype,ivalue))
|
|
if FreeCADProps:
|
|
pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'FreeCADPropertySet',None,FreeCADProps)
|
|
ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],pset)
|
|
if FreeCADGuiProps:
|
|
pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'FreeCADGuiPropertySet',None,FreeCADGuiProps)
|
|
ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],pset)
|
|
|
|
count += 1
|
|
|
|
# relationships
|
|
|
|
sites = []
|
|
buildings = []
|
|
floors = []
|
|
treated = []
|
|
defaulthost = []
|
|
|
|
# buildingParts can be exported as any "normal" IFC type. In that case, gather their elements first
|
|
|
|
for bp in Draft.getObjectsOfType(objectslist,"BuildingPart"):
|
|
if not bp.IfcRole in ["Site","Building","Building Storey","Space","Undefined"]:
|
|
if bp.Name in products:
|
|
subs = []
|
|
for c in bp.Group:
|
|
if c.Name in products:
|
|
subs.append(products[c.Name])
|
|
treated.append(c.Name)
|
|
if subs:
|
|
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'Assembly','',products[bp.Name],subs)
|
|
|
|
# floors/buildingparts
|
|
|
|
for floor in Draft.getObjectsOfType(objectslist,"Floor")+Draft.getObjectsOfType(objectslist,"BuildingPart"):
|
|
if (Draft.getType(floor) == "Floor") or (hasattr(floor,"IfcRole") and floor.IfcRole == "Building Storey"):
|
|
objs = Draft.getGroupContents(floor,walls=True,addgroups=True)
|
|
objs = Arch.pruneIncluded(objs)
|
|
children = []
|
|
for c in objs:
|
|
if c.Name != floor.Name: # getGroupContents + addgroups will include the floor itself
|
|
if c.Name in products.keys():
|
|
if not (c.Name in treated):
|
|
children.append(products[c.Name])
|
|
treated.append(c.Name)
|
|
f = products[floor.Name]
|
|
if children:
|
|
ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'StoreyLink','',children,f)
|
|
floors.append(f)
|
|
defaulthost = f
|
|
|
|
# buildings
|
|
|
|
for building in Draft.getObjectsOfType(objectslist,"Building")+Draft.getObjectsOfType(objectslist,"BuildingPart"):
|
|
if (Draft.getType(building) == "Building") or (hasattr(building,"IfcRole") and building.IfcRole == "Building"):
|
|
objs = Draft.getGroupContents(building,walls=True,addgroups=True)
|
|
objs = Arch.pruneIncluded(objs)
|
|
children = []
|
|
childfloors = []
|
|
for c in objs:
|
|
if not (c.Name in treated):
|
|
if c.Name != building.Name: # getGroupContents + addgroups will include the building itself
|
|
if c.Name in products.keys():
|
|
if Draft.getType(c) in ["Floor","BuildingPart"]:
|
|
childfloors.append(products[c.Name])
|
|
treated.append(c.Name)
|
|
elif not (c.Name in treated):
|
|
children.append(products[c.Name])
|
|
treated.append(c.Name)
|
|
b = products[building.Name]
|
|
if children:
|
|
ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'BuildingLink','',children,b)
|
|
if childfloors:
|
|
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'BuildingLink','',b,childfloors)
|
|
buildings.append(b)
|
|
if not defaulthost and not ADDDEFAULTSTOREY:
|
|
defaulthost = b
|
|
|
|
# sites
|
|
|
|
for site in Draft.getObjectsOfType(objectslist,"Site"):
|
|
objs = Draft.getGroupContents(site,walls=True,addgroups=True)
|
|
objs = Arch.pruneIncluded(objs)
|
|
children = []
|
|
childbuildings = []
|
|
for c in objs:
|
|
if c.Name != site.Name: # getGroupContents + addgroups will include the building itself
|
|
if c.Name in products.keys():
|
|
if not (c.Name in treated):
|
|
if Draft.getType(c) == "Building":
|
|
childbuildings.append(products[c.Name])
|
|
treated.append(c.Name)
|
|
sites.append(products[site.Name])
|
|
|
|
if not sites:
|
|
if DEBUG: print("No site found. Adding default site")
|
|
sites = [ifcfile.createIfcSite(ifcopenshell.guid.compress(uuid.uuid1().hex),history,"Default Site",'',None,None,None,None,"ELEMENT",None,None,None,None,None)]
|
|
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'ProjectLink','',project,sites)
|
|
if not buildings:
|
|
if DEBUG: print("No building found. Adding default building")
|
|
buildings = [ifcfile.createIfcBuilding(ifcopenshell.guid.compress(uuid.uuid1().hex),history,"Default Building",'',None,None,None,None,"ELEMENT",None,None,None)]
|
|
if floors:
|
|
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'BuildingLink','',buildings[0],floors)
|
|
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'SiteLink','',sites[0],buildings)
|
|
untreated = []
|
|
for k,v in products.items():
|
|
if not(k in treated):
|
|
if k != buildings[0].Name:
|
|
if not(Draft.getType(FreeCAD.ActiveDocument.getObject(k)) in ["Site","Building","Floor","BuildingPart"]):
|
|
untreated.append(v)
|
|
elif Draft.getType(FreeCAD.ActiveDocument.getObject(k)) == "BuildingPart":
|
|
if not(FreeCAD.ActiveDocument.getObject(k).IfcRole in ["Building","Building Storey","Site","Space","Undefined"]):
|
|
untreated.append(v)
|
|
if untreated:
|
|
if not defaulthost:
|
|
defaulthost = ifcfile.createIfcBuildingStorey(ifcopenshell.guid.compress(uuid.uuid1().hex),history,"Default Storey",'',None,None,None,None,"ELEMENT",None)
|
|
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'DefaultStoreyLink','',buildings[0],[defaulthost])
|
|
ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'UnassignedObjectsLink','',untreated,defaulthost)
|
|
|
|
# materials
|
|
|
|
materials = {}
|
|
for m in Arch.getDocumentMaterials():
|
|
relobjs = []
|
|
for o in m.InList:
|
|
if hasattr(o,"Material"):
|
|
if o.Material:
|
|
if o.Material.isDerivedFrom("App::MaterialObject"):
|
|
# TODO : support multimaterials too
|
|
if o.Material.Name == m.Name:
|
|
if o.Name in products:
|
|
relobjs.append(products[o.Name])
|
|
elif o.Name in subproducts:
|
|
relobjs.append(subproducts[o.Name])
|
|
if relobjs:
|
|
mat = ifcfile.createIfcMaterial(m.Label.encode("utf8"))
|
|
materials[m.Label] = mat
|
|
rgb = None
|
|
for colorslot in ["Color","DiffuseColor","ViewColor"]:
|
|
if colorslot in m.Material:
|
|
if m.Material[colorslot]:
|
|
if m.Material[colorslot][0] == "(":
|
|
rgb = tuple([float(f) for f in m.Material[colorslot].strip("()").split(",")])
|
|
break
|
|
if rgb:
|
|
col = ifcbin.createIfcColourRgb(rgb[0],rgb[1],rgb[2])
|
|
ssr = ifcbin.createIfcSurfaceStyleRendering(col)
|
|
iss = ifcfile.createIfcSurfaceStyle(m.Label.encode("utf8"),"BOTH",[ssr])
|
|
psa = ifcfile.createIfcPresentationStyleAssignment([iss])
|
|
isi = ifcfile.createIfcStyledItem(None,[psa],None)
|
|
isr = ifcfile.createIfcStyledRepresentation(context,"Style","Material",[isi])
|
|
imd = ifcfile.createIfcMaterialDefinitionRepresentation(None,None,[isr],mat)
|
|
ifcfile.createIfcRelAssociatesMaterial(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'MaterialLink','',relobjs,mat)
|
|
|
|
# groups
|
|
|
|
sortedgroups = []
|
|
while groups:
|
|
for g in groups.keys():
|
|
okay = True
|
|
for c in groups[g]:
|
|
if Draft.getType(FreeCAD.ActiveDocument.getObject(c)) in ["Group","VisGroup"]:
|
|
okay = False
|
|
for s in sortedgroups:
|
|
if s[0] == c:
|
|
okay = True
|
|
if okay:
|
|
sortedgroups.append([g,groups[g]])
|
|
for g in sortedgroups:
|
|
if g[0] in groups.keys():
|
|
del groups[g[0]]
|
|
#print "sorted groups:",sortedgroups
|
|
containers = {}
|
|
for g in sortedgroups:
|
|
if g[1]:
|
|
children = []
|
|
for o in g[1]:
|
|
if o in products.keys():
|
|
children.append(products[o])
|
|
if children:
|
|
name = str(FreeCAD.ActiveDocument.getObject(g[0]).Label.encode("utf8"))
|
|
grp = ifcfile.createIfcGroup(ifcopenshell.guid.compress(uuid.uuid1().hex),history,name,'',None)
|
|
products[g[0]] = grp
|
|
spatialelements[g[0]] = grp
|
|
ass = ifcfile.createIfcRelAssignsToGroup(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'GroupLink','',children,None,grp)
|
|
# stack groups inside containers
|
|
stack = {}
|
|
for g in sortedgroups:
|
|
go = FreeCAD.ActiveDocument.getObject(g[0])
|
|
for parent in go.InList:
|
|
if hasattr(parent,"Group") and (go in parent.Group):
|
|
if (parent.Name in spatialelements) and (g[0] in spatialelements):
|
|
stack.setdefault(parent.Name,[]).append(spatialelements[g[0]])
|
|
for k,v in stack.items():
|
|
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'GroupStackLink','',spatialelements[k],v)
|
|
|
|
# 2D objects
|
|
|
|
if EXPORT_2D:
|
|
annos = []
|
|
curvestyles = {}
|
|
if annotations and DEBUG: print("exporting 2D objects...")
|
|
for anno in annotations:
|
|
xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
|
|
zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
|
|
ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
|
|
gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
|
|
if anno.isDerivedFrom("Part::Feature"):
|
|
reps = []
|
|
sh = anno.Shape.copy()
|
|
sh.scale(0.001) # to meters
|
|
ehc = []
|
|
for w in sh.Wires:
|
|
reps.append(createCurve(ifcfile,w))
|
|
for e in w.Edges:
|
|
ehc.append(e.hashCode())
|
|
for e in sh.Edges:
|
|
if e.hashCode not in ehc:
|
|
reps.append(createCurve(ifcfile,e))
|
|
elif anno.isDerivedFrom("App::Annotation"):
|
|
l = anno.Position
|
|
pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z))
|
|
tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None)
|
|
txt = ifcfile.createIfcTextLiteral(";".join(anno.LabelText).encode("utf8"),tpl,"LEFT")
|
|
reps = [txt]
|
|
|
|
for coldef in ["LineColor","TextColor","ShapeColor"]:
|
|
if hasattr(obj.ViewObject,coldef):
|
|
rgb = getattr(obj.ViewObject,coldef)[:3]
|
|
if rgb in curvestyles:
|
|
psa = curvestyles[rgb]
|
|
else:
|
|
col = ifcbin.createIfcColourRgb(rgb[0],rgb[1],rgb[2])
|
|
cvf = ifcfile.createIfcDraughtingPredefinedCurveFont("CONTINUOUS")
|
|
ics = ifcfile.createIfcCurveStyle('Line',cvf,None,col)
|
|
psa = ifcfile.createIfcPresentationStyleAssignment([ics])
|
|
curvestyles[rgb] = psa
|
|
for rep in reps:
|
|
isi = ifcfile.createIfcStyledItem(rep,[psa],None)
|
|
break
|
|
|
|
shp = ifcfile.createIfcShapeRepresentation(context,'Annotation','Annotation2D',reps)
|
|
rep = ifcfile.createIfcProductDefinitionShape(None,None,[shp])
|
|
ann = ifcfile.createIfcAnnotation(ifcopenshell.guid.compress(uuid.uuid1().hex),history,anno.Label.encode('utf8'),'',None,gpl,rep)
|
|
annos.append(ann)
|
|
if annos:
|
|
if not defaulthost:
|
|
defaulthost = ifcfile.createIfcBuildingStorey(ifcopenshell.guid.compress(uuid.uuid1().hex),history,"Default Storey",'',None,None,None,None,"ELEMENT",None)
|
|
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'DefaultStoreyLink','',buildings[0],[defaulthost])
|
|
ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'AnnotationsLink','',annos,defaulthost)
|
|
|
|
|
|
if DEBUG: print("writing ",filename,"...")
|
|
|
|
filename = decode(filename)
|
|
|
|
ifcfile.write(filename)
|
|
|
|
if STORE_UID:
|
|
# some properties might have been changed
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
os.remove(templatefile)
|
|
|
|
if DEBUG and ifcbin.compress:
|
|
f = pyopen(filename,"rb")
|
|
s = len(f.read().split("\n"))
|
|
f.close()
|
|
print("Compression ratio:",int((float(ifcbin.spared)/(s+ifcbin.spared))*100),"%")
|
|
del ifcbin
|
|
|
|
|
|
def buildAddress(obj,ifcfile):
|
|
|
|
|
|
a = obj.Address.encode("utf8") or None
|
|
p = obj.PostalCode.encode("utf8") or None
|
|
t = obj.City.encode("utf8") or None
|
|
r = obj.Region.encode("utf8") or None
|
|
c = obj.Country.encode("utf8") or None
|
|
if a or p or t or r or c:
|
|
addr = ifcfile.createIfcPostalAddress("SITE",'Site Address','',None,[a],None,t,r,p,c)
|
|
else:
|
|
addr = None
|
|
return addr
|
|
|
|
def createFromProperties(propsets,ifcfile):
|
|
|
|
"creates a FreeCAD parametric object from a set of properties"
|
|
|
|
obj = None
|
|
sets = []
|
|
global parametrics
|
|
appset = None
|
|
guiset = None
|
|
for pset in propsets.keys():
|
|
if ifcfile[pset].Name == "FreeCADPropertySet":
|
|
appset = {}
|
|
for pid in propsets[pset]:
|
|
p = ifcfile[pid]
|
|
appset[p.Name] = p.NominalValue.wrappedValue
|
|
elif ifcfile[pset].Name == "FreeCADGuiPropertySet":
|
|
guiset = {}
|
|
for pid in propsets[pset]:
|
|
p = ifcfile[pid]
|
|
guiset[p.Name] = p.NominalValue.wrappedValue
|
|
if appset:
|
|
oname = None
|
|
otype = None
|
|
if "FreeCADType" in appset.keys():
|
|
if "FreeCADName" in appset.keys():
|
|
obj = FreeCAD.ActiveDocument.addObject(appset["FreeCADType"],appset["FreeCADName"])
|
|
if "FreeCADAppObject" in appset:
|
|
mod,cla = appset["FreeCADAppObject"].split(".")
|
|
import importlib
|
|
mod = importlib.import_module(mod)
|
|
getattr(mod,cla)(obj)
|
|
sets.append(("App",appset))
|
|
if FreeCAD.GuiUp:
|
|
if guiset:
|
|
if "FreeCADGuiObject" in guiset:
|
|
mod,cla = guiset["FreeCADGuiObject"].split(".")
|
|
import importlib
|
|
mod = importlib.import_module(mod)
|
|
getattr(mod,cla)(obj.ViewObject)
|
|
sets.append(("Gui",guiset))
|
|
if obj and sets:
|
|
for realm,pset in sets:
|
|
if realm == "App":
|
|
target = obj
|
|
else:
|
|
target = obj.ViewObject
|
|
for key,val in pset.items():
|
|
if key.startswith("FreeCAD_") or key.startswith("FreeCADGui_"):
|
|
name = key.split("_")[1]
|
|
if name in target.PropertiesList:
|
|
if not target.getEditorMode(name):
|
|
ptype = target.getTypeIdOfProperty(name)
|
|
if ptype in ["App::PropertyString","App::PropertyEnumeration","App::PropertyInteger","App::PropertyFloat"]:
|
|
setattr(target,name,val)
|
|
elif ptype in ["App::PropertyLength","App::PropertyDistance"]:
|
|
setattr(target,name,val*1000)
|
|
elif ptype == "App::PropertyBool":
|
|
if val in [".T.",True]:
|
|
setattr(target,name,True)
|
|
else:
|
|
setattr(target,name,False)
|
|
elif ptype == "App::PropertyVector":
|
|
setattr(target,name,FreeCAD.Vector([float(s) for s in val.split("(")[1].strip(")").split(",")]))
|
|
elif ptype == "App::PropertyArea":
|
|
setattr(target,name,val*1000000)
|
|
elif ptype == "App::PropertyPlacement":
|
|
data = val.split("[")[1].strip("]").split("(")
|
|
data = [data[1].split(")")[0],data[2].strip(")")]
|
|
v = FreeCAD.Vector([float(s) for s in data[0].split(",")])
|
|
r = FreeCAD.Rotation(*[float(s) for s in data[1].split(",")])
|
|
setattr(target,name,FreeCAD.Placement(v,r))
|
|
elif ptype == "App::PropertyLink":
|
|
link = val.split("_")[1]
|
|
parametrics.append([target,name,link])
|
|
else:
|
|
print("Unhandled FreeCAD property:",name," of type:",ptype)
|
|
return obj
|
|
|
|
|
|
def createCurve(ifcfile,wire):
|
|
|
|
"creates an IfcCompositeCurve from a shape"
|
|
|
|
segments = []
|
|
pol = None
|
|
last = None
|
|
if wire.ShapeType == "edge":
|
|
edges = [edge]
|
|
else:
|
|
edges = Part.__sortEdges__(wire.Edges)
|
|
for e in edges:
|
|
if isinstance(e.Curve,Part.Circle):
|
|
follow = True
|
|
if last:
|
|
if not DraftVecUtils.equals(last,e.Vertexes[0].Point):
|
|
follow = False
|
|
last = e.Vertexes[0].Point
|
|
else:
|
|
last = e.Vertexes[-1].Point
|
|
else:
|
|
last = e.Vertexes[-1].Point
|
|
p1 = math.degrees(-DraftVecUtils.angle(e.Vertexes[0].Point.sub(e.Curve.Center)))
|
|
p2 = math.degrees(-DraftVecUtils.angle(e.Vertexes[-1].Point.sub(e.Curve.Center)))
|
|
da = DraftVecUtils.angle(e.valueAt(e.FirstParameter+0.1).sub(e.Curve.Center),e.Vertexes[0].Point.sub(e.Curve.Center))
|
|
if p1 < 0:
|
|
p1 = 360 + p1
|
|
if p2 < 0:
|
|
p2 = 360 + p2
|
|
if da > 0:
|
|
follow = not(follow)
|
|
xvc = ifcbin.createIfcDirection((1.0,0.0))
|
|
ovc = ifcbin.createIfcCartesianPoint(tuple(e.Curve.Center))
|
|
plc = ifcbin.createIfcAxis2Placement2D(ovc,xvc)
|
|
cir = ifcfile.createIfcCircle(plc,e.Curve.Radius)
|
|
curve = ifcfile.createIfcTrimmedCurve(cir,[ifcfile.createIfcParameterValue(p1)],[ifcfile.createIfcParameterValue(p2)],follow,"PARAMETER")
|
|
else:
|
|
verts = [vertex.Point for vertex in e.Vertexes]
|
|
if last:
|
|
if not DraftVecUtils.equals(last,verts[0]):
|
|
verts.reverse()
|
|
last = e.Vertexes[0].Point
|
|
else:
|
|
last = e.Vertexes[-1].Point
|
|
else:
|
|
last = e.Vertexes[-1].Point
|
|
pts = [ifcbin.createIfcCartesianPoint(tuple(v)) for v in verts]
|
|
curve = ifcbin.createIfcPolyline(pts)
|
|
segment = ifcfile.createIfcCompositeCurveSegment("CONTINUOUS",True,curve)
|
|
segments.append(segment)
|
|
if segments:
|
|
pol = ifcfile.createIfcCompositeCurve(segments,False)
|
|
return pol
|
|
|
|
|
|
def getProfile(ifcfile,p):
|
|
|
|
"""returns an IFC profile definition from a shape"""
|
|
|
|
import Part,DraftGeomUtils
|
|
profile = None
|
|
if len(p.Edges) == 1:
|
|
pxvc = ifcbin.createIfcDirection((1.0,0.0))
|
|
povc = ifcbin.createIfcCartesianPoint((0.0,0.0))
|
|
pt = ifcbin.createIfcAxis2Placement2D(povc,pxvc)
|
|
if isinstance(p.Edges[0].Curve,Part.Circle):
|
|
# extruded circle
|
|
profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt,p.Edges[0].Curve.Radius)
|
|
elif isinstance(p.Edges[0].Curve,Part.Ellipse):
|
|
# extruded ellipse
|
|
profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt,p.Edges[0].Curve.MajorRadius,p.Edges[0].Curve.MinorRadius)
|
|
elif (len(p.Faces) == 1) and (len(p.Wires) > 1):
|
|
# face with holes
|
|
f = p.Faces[0]
|
|
if DraftGeomUtils.hasCurves(f.OuterWire):
|
|
outerwire = createCurve(ifcfile,f.OuterWire)
|
|
else:
|
|
w = Part.Wire(Part.__sortEdges__(f.OuterWire.Edges))
|
|
pts = [ifcbin.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]]
|
|
outerwire = ifcbin.createIfcPolyline(pts)
|
|
innerwires = []
|
|
for w in f.Wires:
|
|
if w.hashCode() != f.OuterWire.hashCode():
|
|
if DraftGeomUtils.hasCurves(w):
|
|
innerwires.append(createCurve(ifcfile,w))
|
|
else:
|
|
w = Part.Wire(Part.__sortEdges__(w.Edges))
|
|
pts = [ifcbin.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]]
|
|
innerwires.append(ifcbin.createIfcPolyline(pts))
|
|
profile = ifcfile.createIfcArbitraryProfileDefWithVoids("AREA",None,outerwire,innerwires)
|
|
else:
|
|
if DraftGeomUtils.hasCurves(p):
|
|
# extruded composite curve
|
|
pol = createCurve(ifcfile,p)
|
|
else:
|
|
# extruded polyline
|
|
w = Part.Wire(Part.__sortEdges__(p.Wires[0].Edges))
|
|
pts = [ifcbin.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]]
|
|
pol = ifcbin.createIfcPolyline(pts)
|
|
profile = ifcfile.createIfcArbitraryClosedProfileDef("AREA",None,pol)
|
|
return profile
|
|
|
|
|
|
def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1):
|
|
|
|
"""returns an IfcShapeRepresentation object or None"""
|
|
|
|
import Part,math,DraftGeomUtils,DraftVecUtils
|
|
shapes = []
|
|
placement = None
|
|
productdef = None
|
|
shapetype = "no shape"
|
|
tostore = False
|
|
subplacement = None
|
|
|
|
# check for clones
|
|
|
|
if (not subtraction) and (not forcebrep):
|
|
for k,v in clones.items():
|
|
if (obj.Name == k) or (obj.Name in v):
|
|
if k in sharedobjects:
|
|
# base shape already exists
|
|
repmap = sharedobjects[k]
|
|
pla = obj.Placement
|
|
axis1 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0))))
|
|
axis2 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0))))
|
|
axis3 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1))))
|
|
origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(0.001)))
|
|
transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3)
|
|
mapitem = ifcfile.createIfcMappedItem(repmap,transf)
|
|
shapes = [mapitem]
|
|
solidType = "MappedRepresentation"
|
|
shapetype = "clone"
|
|
else:
|
|
# base shape not yet created
|
|
tostore = k
|
|
|
|
# unhandled case: object is duplicated because of Axis
|
|
if obj.isDerivedFrom("Part::Feature") and (len(obj.Shape.Solids) > 1) and hasattr(obj,"Axis") and obj.Axis:
|
|
forcebrep = True
|
|
|
|
if (not shapes) and (not forcebrep):
|
|
profile = None
|
|
ev = FreeCAD.Vector()
|
|
if hasattr(obj,"Proxy"):
|
|
if hasattr(obj.Proxy,"getExtrusionData"):
|
|
extdata = obj.Proxy.getExtrusionData(obj)
|
|
if extdata:
|
|
# convert to meters
|
|
p = extdata[0]
|
|
if not isinstance(p,list):
|
|
p = [p]
|
|
ev = extdata[1]
|
|
if not isinstance(ev,list):
|
|
ev = [ev]
|
|
pl = extdata[2]
|
|
if not isinstance(pl,list):
|
|
pl = [pl]
|
|
for i in range(len(p)):
|
|
pi = p[i]
|
|
pi.scale(0.001)
|
|
if i < len(ev):
|
|
evi = ev[i]
|
|
else:
|
|
evi = ev[-1]
|
|
evi.multiply(0.001)
|
|
if i < len(pl):
|
|
pli = pl[i]
|
|
else:
|
|
pli = pl[-1]
|
|
pli.Base = pli.Base.multiply(0.001)
|
|
pstr = str([v.Point for v in p[i].Vertexes])
|
|
if pstr in profiledefs:
|
|
profile = profiledefs[pstr]
|
|
shapetype = "reusing profile"
|
|
else:
|
|
profile = getProfile(ifcfile,pi)
|
|
if profile:
|
|
profiledefs[pstr] = profile
|
|
if profile and not(DraftVecUtils.isNull(evi)):
|
|
#ev = pl.Rotation.inverted().multVec(evi)
|
|
#print "evi:",evi
|
|
if not tostore:
|
|
# add the object placement to the profile placement. Otherwise it'll be done later at map insert
|
|
pl2 = FreeCAD.Placement(obj.Placement)
|
|
pl2.Base = pl2.Base.multiply(0.001)
|
|
pli = pl2.multiply(pli)
|
|
xvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(1,0,0))))
|
|
zvc = ifcbin.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(0,0,1))))
|
|
ovc = ifcbin.createIfcCartesianPoint(tuple(pli.Base))
|
|
lpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
|
|
edir = ifcbin.createIfcDirection(tuple(FreeCAD.Vector(evi).normalize()))
|
|
shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,evi.Length)
|
|
shapes.append(shape)
|
|
solidType = "SweptSolid"
|
|
shapetype = "extrusion"
|
|
elif hasattr(obj.Proxy,"getRebarData"):
|
|
# export rebars as IfcSweptDiskSolid
|
|
rdata = obj.Proxy.getRebarData(obj)
|
|
if rdata:
|
|
# convert to meters
|
|
r = rdata[1] * 0.001
|
|
for w in rdata[0]:
|
|
w.scale(0.001)
|
|
cur = createCurve(ifcfile,w)
|
|
shape = ifcfile.createIfcSweptDiskSolid(cur,r)
|
|
shapes.append(shape)
|
|
solidType = "SweptSolid"
|
|
shapetype = "extrusion"
|
|
|
|
if not shapes:
|
|
|
|
# check if we keep a null shape (additions-only object)
|
|
|
|
if (hasattr(obj,"Base") and hasattr(obj,"Width") and hasattr(obj,"Height")) and (not obj.Base) and obj.Additions and (not obj.Width.Value) and (not obj.Height.Value):
|
|
shapes = None
|
|
|
|
else:
|
|
|
|
# brep representation
|
|
|
|
fcshape = None
|
|
solidType = "Brep"
|
|
if subtraction:
|
|
if hasattr(obj,"Proxy"):
|
|
if hasattr(obj.Proxy,"getSubVolume"):
|
|
fcshape = obj.Proxy.getSubVolume(obj)
|
|
if not fcshape:
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
if False: # below is buggy. No way to duplicate shapes that way?
|
|
#if hasattr(obj,"Base") and hasattr(obj,"Additions")and hasattr(obj,"Subtractions"):
|
|
if obj.Base and (not obj.Additions) and not(obj.Subtractions):
|
|
if obj.Base.isDerivedFrom("Part::Feature"):
|
|
if obj.Base.Shape:
|
|
if obj.Base.Shape.Solids:
|
|
fcshape = obj.Base.Shape
|
|
subplacement = FreeCAD.Placement(obj.Placement)
|
|
if not fcshape:
|
|
if obj.Shape:
|
|
if not obj.Shape.isNull():
|
|
fcshape = obj.Shape
|
|
if fcshape:
|
|
shapedef = str([v.Point for v in fcshape.Vertexes])
|
|
if shapedef in shapedefs:
|
|
shapes = shapedefs[shapedef]
|
|
shapetype = "reusing brep"
|
|
else:
|
|
|
|
# new ifcopenshell serializer
|
|
|
|
from ifcopenshell import geom
|
|
serialized = False
|
|
if hasattr(geom,"serialise") and obj.isDerivedFrom("Part::Feature") and SERIALIZE:
|
|
if obj.Shape.Faces:
|
|
sh = obj.Shape.copy()
|
|
sh.scale(0.001) # to meters
|
|
p = geom.serialise(sh.exportBrepToString())
|
|
if p:
|
|
productdef = ifcfile.add(p)
|
|
for rep in productdef.Representations:
|
|
rep.ContextOfItems = context
|
|
xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
|
|
zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
|
|
ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
|
|
gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
|
|
placement = ifcbin.createIfcLocalPlacement(gpl)
|
|
shapetype = "advancedbrep"
|
|
shapes = None
|
|
serialized = True
|
|
|
|
if not serialized:
|
|
|
|
# old method
|
|
|
|
solids = []
|
|
if fcshape.Solids:
|
|
dataset = fcshape.Solids
|
|
else:
|
|
dataset = fcshape.Shells
|
|
#if DEBUG: print "Warning! object contains no solids"
|
|
|
|
# if this is a clone, place back the shapes in null position
|
|
if tostore:
|
|
for shape in dataset:
|
|
shape.Placement = FreeCAD.Placement()
|
|
|
|
for fcsolid in dataset:
|
|
fcsolid.scale(0.001) # to meters
|
|
faces = []
|
|
curves = False
|
|
shapetype = "brep"
|
|
for fcface in fcsolid.Faces:
|
|
for e in fcface.Edges:
|
|
if DraftGeomUtils.geomType(e) != "Line":
|
|
from FreeCAD import Base
|
|
try:
|
|
if e.curvatureAt(e.FirstParameter+(e.LastParameter-e.FirstParameter)/2) > 0.0001:
|
|
curves = True
|
|
break
|
|
except Part.OCCError:
|
|
pass
|
|
except Base.FreeCADError:
|
|
pass
|
|
if curves:
|
|
joinfacets = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcJoinCoplanarFacets",False)
|
|
usedae = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcUseDaeOptions",False)
|
|
if joinfacets:
|
|
result = Arch.removeCurves(fcsolid,dae=usedae)
|
|
if result:
|
|
fcsolid = result
|
|
else:
|
|
# fall back to standard triangulation
|
|
joinfacets = False
|
|
if not joinfacets:
|
|
shapetype = "triangulated"
|
|
if usedae:
|
|
import importDAE
|
|
tris = importDAE.triangulate(fcsolid)
|
|
else:
|
|
tris = fcsolid.tessellate(tessellation)
|
|
for tri in tris[1]:
|
|
pts = [ifcbin.createIfcCartesianPoint(tuple(tris[0][i])) for i in tri]
|
|
loop = ifcbin.createIfcPolyLoop(pts)
|
|
bound = ifcfile.createIfcFaceOuterBound(loop,True)
|
|
face = ifcfile.createIfcFace([bound])
|
|
faces.append(face)
|
|
fcsolid = Part.Shape() # empty shape so below code is not executed
|
|
|
|
for fcface in fcsolid.Faces:
|
|
loops = []
|
|
verts = [v.Point for v in fcface.OuterWire.OrderedVertexes]
|
|
c = fcface.CenterOfMass
|
|
v1 = verts[0].sub(c)
|
|
v2 = verts[1].sub(c)
|
|
try:
|
|
n = fcface.normalAt(0,0)
|
|
except Part.OCCError:
|
|
continue # this is a very wrong face, it probably shouldn't be here...
|
|
if DraftVecUtils.angle(v2,v1,n) >= 0:
|
|
verts.reverse() # inverting verts order if the direction is couterclockwise
|
|
pts = [ifcbin.createIfcCartesianPoint(tuple(v)) for v in verts]
|
|
loop = ifcbin.createIfcPolyLoop(pts)
|
|
bound = ifcfile.createIfcFaceOuterBound(loop,True)
|
|
loops.append(bound)
|
|
for wire in fcface.Wires:
|
|
if wire.hashCode() != fcface.OuterWire.hashCode():
|
|
verts = [v.Point for v in wire.OrderedVertexes]
|
|
if len(verts) > 1:
|
|
v1 = verts[0].sub(c)
|
|
v2 = verts[1].sub(c)
|
|
if DraftVecUtils.angle(v2,v1,DraftVecUtils.neg(n)) >= 0:
|
|
verts.reverse()
|
|
pts = [ifcbin.createIfcCartesianPoint(tuple(v)) for v in verts]
|
|
loop = ifcbin.createIfcPolyLoop(pts)
|
|
bound = ifcfile.createIfcFaceBound(loop,True)
|
|
loops.append(bound)
|
|
else:
|
|
print ("Warning: wire with one/no vertex in ",obj.Label)
|
|
face = ifcfile.createIfcFace(loops)
|
|
faces.append(face)
|
|
|
|
if faces:
|
|
shell = ifcfile.createIfcClosedShell(faces)
|
|
shape = ifcfile.createIfcFacetedBrep(shell)
|
|
shapes.append(shape)
|
|
|
|
shapedefs[shapedef] = shapes
|
|
|
|
if shapes:
|
|
|
|
if tostore:
|
|
subrep = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes)
|
|
xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
|
|
zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
|
|
ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
|
|
gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
|
|
repmap = ifcfile.createIfcRepresentationMap(gpl,subrep)
|
|
pla = obj.Placement
|
|
axis1 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0))))
|
|
axis2 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0))))
|
|
origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(0.001)))
|
|
axis3 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1))))
|
|
transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3)
|
|
mapitem = ifcfile.createIfcMappedItem(repmap,transf)
|
|
shapes = [mapitem]
|
|
sharedobjects[tostore] = repmap
|
|
solidType = "MappedRepresentation"
|
|
|
|
# set surface style
|
|
if FreeCAD.GuiUp and (not subtraction) and hasattr(obj.ViewObject,"ShapeColor"):
|
|
# only set a surface style if the object has no material.
|
|
# apparently not needed, no harm in having both.
|
|
# but they must have the same name for revit to see them
|
|
#m = False
|
|
#if hasattr(obj,"Material"):
|
|
# if obj.Material:
|
|
# if "Color" in obj.Material.Material:
|
|
# m = True
|
|
#if not m:
|
|
rgb = obj.ViewObject.ShapeColor[:3]
|
|
if rgb in surfstyles:
|
|
psa = surfstyles[rgb]
|
|
else:
|
|
m = None
|
|
if hasattr(obj,"Material"):
|
|
if obj.Material:
|
|
if obj.Material.isDerivedFrom("App::MaterialObject"):
|
|
m = obj.Material.Label.encode("utf8")
|
|
col = ifcbin.createIfcColourRgb(rgb[0],rgb[1],rgb[2])
|
|
ssr = ifcbin.createIfcSurfaceStyleRendering(col)
|
|
iss = ifcfile.createIfcSurfaceStyle(m,"BOTH",[ssr])
|
|
psa = ifcfile.createIfcPresentationStyleAssignment([iss])
|
|
surfstyles[rgb] = psa
|
|
for shape in shapes:
|
|
isi = ifcfile.createIfcStyledItem(shape,[psa],None)
|
|
|
|
xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
|
|
zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
|
|
ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
|
|
gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
|
|
placement = ifcbin.createIfcLocalPlacement(gpl)
|
|
representation = ifcfile.createIfcShapeRepresentation(context,'Body',solidType,shapes)
|
|
productdef = ifcfile.createIfcProductDefinitionShape(None,None,[representation])
|
|
|
|
return productdef,placement,shapetype
|
|
|
|
|
|
# Below are 2D helper functions needed while IfcOpenShell cannot do this itself...
|
|
|
|
|
|
def setRepresentation(representation,scaling=1000):
|
|
"""Returns a shape from a 2D IfcShapeRepresentation"""
|
|
|
|
def getPolyline(ent):
|
|
pts = []
|
|
for p in ent.Points:
|
|
c = p.Coordinates
|
|
c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0)
|
|
c.multiply(scaling)
|
|
pts.append(c)
|
|
return Part.makePolygon(pts)
|
|
|
|
def getLine(ent):
|
|
pts = []
|
|
p1 = getVector(ent.Pnt)
|
|
p1.multiply(scaling)
|
|
pts.append(p1)
|
|
p2 = getVector(ent.Dir)
|
|
p2.multiply(scaling)
|
|
p2 = p1.add(p2)
|
|
pts.append(p2)
|
|
return Part.makePolygon(pts)
|
|
|
|
def getCircle(ent):
|
|
c = ent.Position.Location.Coordinates
|
|
c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0)
|
|
c.multiply(scaling)
|
|
r = ent.Radius*scaling
|
|
return Part.makeCircle(r,c)
|
|
|
|
def getCurveSet(ent):
|
|
result = []
|
|
if ent.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]:
|
|
elts = ent.Elements
|
|
elif ent.is_a() in ["IfcLine","IfcPolyline","IfcCircle","IfcTrimmedCurve"]:
|
|
elts = [ent]
|
|
for el in elts:
|
|
if el.is_a("IfcPolyline"):
|
|
result.append(getPolyline(el))
|
|
elif el.is_a("IfcLine"):
|
|
result.append(getLine(el))
|
|
elif el.is_a("IfcCircle"):
|
|
result.append(getCircle(el))
|
|
elif el.is_a("IfcTrimmedCurve"):
|
|
base = el.BasisCurve
|
|
t1 = el.Trim1[0].wrappedValue
|
|
t2 = el.Trim2[0].wrappedValue
|
|
if not el.SenseAgreement:
|
|
t1,t2 = t2,t1
|
|
if base.is_a("IfcPolyline"):
|
|
bc = getPolyline(base)
|
|
result.append(bc)
|
|
elif base.is_a("IfcCircle"):
|
|
bc = getCircle(base)
|
|
e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape()
|
|
d = base.Position.RefDirection.DirectionRatios
|
|
v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0)
|
|
a = -DraftVecUtils.angle(v)
|
|
e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a))
|
|
result.append(e)
|
|
return result
|
|
|
|
result = []
|
|
if representation.is_a("IfcShapeRepresentation"):
|
|
for item in representation.Items:
|
|
if item.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]:
|
|
result = getCurveSet(item)
|
|
elif item.is_a("IfcMappedItem"):
|
|
preresult = setRepresentation(item.MappingSource.MappedRepresentation,scaling)
|
|
pla = getPlacement(item.MappingSource.MappingOrigin,scaling)
|
|
rot = getRotation(item.MappingTarget)
|
|
if pla:
|
|
if rot.Angle:
|
|
pla.Rotation = rot
|
|
for r in preresult:
|
|
#r.Placement = pla
|
|
result.append(r)
|
|
else:
|
|
result = preresult
|
|
elif representation.is_a() in ["IfcPolyline","IfcCircle","IfcTrimmedCurve"]:
|
|
result = getCurveSet(representation)
|
|
return result
|
|
|
|
|
|
def getRotation(entity):
|
|
"returns a FreeCAD rotation from an IfcProduct with a IfcMappedItem representation"
|
|
try:
|
|
u = FreeCAD.Vector(entity.Axis1.DirectionRatios)
|
|
v = FreeCAD.Vector(entity.Axis2.DirectionRatios)
|
|
w = FreeCAD.Vector(entity.Axis3.DirectionRatios)
|
|
except AttributeError:
|
|
return FreeCAD.Rotation()
|
|
import WorkingPlane
|
|
p = WorkingPlane.plane(u=u,v=v,w=w)
|
|
return p.getRotation().Rotation
|
|
|
|
|
|
def getPlacement(entity,scaling=1000):
|
|
"returns a placement from the given entity"
|
|
|
|
if not entity:
|
|
return None
|
|
import DraftVecUtils
|
|
pl = None
|
|
if entity.is_a("IfcAxis2Placement3D"):
|
|
x = getVector(entity.RefDirection,scaling)
|
|
z = getVector(entity.Axis,scaling)
|
|
if x and z:
|
|
y = z.cross(x)
|
|
m = DraftVecUtils.getPlaneRotation(x,y,z)
|
|
pl = FreeCAD.Placement(m)
|
|
else:
|
|
pl = FreeCAD.Placement()
|
|
loc = getVector(entity.Location,scaling)
|
|
if loc:
|
|
pl.move(loc)
|
|
elif entity.is_a("IfcLocalPlacement"):
|
|
pl = getPlacement(entity.PlacementRelTo,1) # original placement
|
|
relpl = getPlacement(entity.RelativePlacement,1) # relative transf
|
|
if pl and relpl:
|
|
pl = pl.multiply(relpl)
|
|
elif relpl:
|
|
pl = relpl
|
|
elif entity.is_a("IfcCartesianPoint"):
|
|
loc = getVector(entity,scaling)
|
|
pl = FreeCAD.Placement()
|
|
pl.move(loc)
|
|
if pl:
|
|
pl.Base = FreeCAD.Vector(pl.Base).multiply(scaling)
|
|
return pl
|
|
|
|
|
|
def getVector(entity,scaling=1000):
|
|
"returns a vector from the given entity"
|
|
|
|
if not entity:
|
|
return None
|
|
v = None
|
|
if entity.is_a("IfcDirection"):
|
|
if len(entity.DirectionRatios) == 3:
|
|
v= FreeCAD.Vector(tuple(entity.DirectionRatios))
|
|
else:
|
|
v = FreeCAD.Vector(tuple(entity.DirectionRatios+[0]))
|
|
elif entity.is_a("IfcCartesianPoint"):
|
|
if len(entity.Coordinates) == 3:
|
|
v = FreeCAD.Vector(tuple(entity.Coordinates))
|
|
else:
|
|
v = FreeCAD.Vector(tuple(entity.Coordinates+[0]))
|
|
#if v:
|
|
# v.multiply(scaling)
|
|
return v
|
|
|
|
|
|
def getScaling(ifcfile):
|
|
"returns a scaling factor from file units to mm"
|
|
|
|
def getUnit(unit):
|
|
if unit.Name == "METRE":
|
|
if unit.Prefix == "KILO":
|
|
return 1000000.0
|
|
elif unit.Prefix == "HECTO":
|
|
return 100000.0
|
|
elif unit.Prefix == "DECA":
|
|
return 10000.0
|
|
elif not unit.Prefix:
|
|
return 1000.0
|
|
elif unit.Prefix == "DECI":
|
|
return 100.0
|
|
elif unit.Prefix == "CENTI":
|
|
return 10.0
|
|
return 1.0
|
|
|
|
ua = ifcfile.by_type("IfcUnitAssignment")
|
|
if not ua:
|
|
return 1.0
|
|
ua = ua[0]
|
|
for u in ua.Units:
|
|
if u.UnitType == "LENGTHUNIT":
|
|
if u.is_a("IfcConversionBasedUnit"):
|
|
f = getUnit(u.ConversionFactor.UnitComponent)
|
|
return f * u.ConversionFactor.ValueComponent.wrappedValue
|
|
elif u.is_a("IfcSIUnit") or u.is_a("IfcUnit"):
|
|
return getUnit(u)
|
|
return 1.0
|