#*************************************************************************** #* * #* Copyright (c) 2014 * #* Yorik van Havre * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* This program is distributed in the hope that it will be useful, * #* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Library General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with this program; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #*************************************************************************** from __future__ import print_function __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