2360 lines
96 KiB
Python
2360 lines
96 KiB
Python
#***************************************************************************
|
|
#* *
|
|
#* Copyright (c) 2011 *
|
|
#* Yorik van Havre <yorik@uncreated.net> *
|
|
#* *
|
|
#* This program is free software; you can redistribute it and/or modify *
|
|
#* it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
#* as published by the Free Software Foundation; either version 2 of *
|
|
#* the License, or (at your option) any later version. *
|
|
#* for detail see the LICENCE text file. *
|
|
#* *
|
|
#* This program is distributed in the hope that it will be useful, *
|
|
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
#* GNU Library General Public License for more details. *
|
|
#* *
|
|
#* You should have received a copy of the GNU Library General Public *
|
|
#* License along with this program; if not, write to the Free Software *
|
|
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
|
#* USA *
|
|
#* *
|
|
#***************************************************************************
|
|
|
|
# WARNING ##################################################################
|
|
# #
|
|
# This module is deprecated and will be removed in a future version #
|
|
# #
|
|
############################################################################
|
|
|
|
|
|
import FreeCAD, Arch, Draft, os, sys, time, Part, DraftVecUtils, uuid, math, re
|
|
from DraftTools import translate
|
|
|
|
__title__="FreeCAD IFC importer"
|
|
__author__ = "Yorik van Havre"
|
|
__url__ = "http://www.freecadweb.org"
|
|
|
|
# config
|
|
subtractiveTypes = ["IfcOpeningElement"] # elements that must be subtracted from their parents
|
|
SCHEMA = "http://www.steptools.com/support/stdev_docs/ifcbim/ifc4.exp" # only for internal prser
|
|
MAKETEMPFILES = False # if True, shapes are passed from ifcopenshell to freecad through temp files
|
|
DEBUG = True # this is only for the python console, this value is overridden when importing through the GUI
|
|
SKIP = ["IfcBuildingElementProxy","IfcFlowTerminal","IfcFurnishingElement"] # default. overwritten by the GUI options
|
|
IFCLINE_RE = re.compile("#(\d+)[ ]?=[ ]?(.*?)\((.*)\);[\\r]?$")
|
|
PRECISION = 4 # rounding value, in number of digits
|
|
APPLYFIX = True # if true, the ifcopenshell bug-fixing function is applied when saving files
|
|
# end config
|
|
|
|
# supported ifc products (export only):
|
|
supportedIfcTypes = ["IfcSite", "IfcBuilding", "IfcBuildingStorey", "IfcBeam", "IfcBeamStandardCase",
|
|
"IfcChimney", "IfcColumn", "IfcColumnStandardCase", "IfcCovering", "IfcCurtainWall",
|
|
"IfcDoor", "IfcDoorStandardCase", "IfcMember", "IfcMemberStandardCase", "IfcPlate",
|
|
"IfcPlateStandardCase", "IfcRailing", "IfcRamp", "IfcRampFlight", "IfcRoof",
|
|
"IfcSlab", "IfcStair", "IfcStairFlight", "IfcWall","IfcSpace",
|
|
"IfcWallStandardCase", "IfcWindow", "IfcWindowStandardCase", "IfcBuildingElementProxy",
|
|
"IfcPile", "IfcFooting", "IfcReinforcingBar", "IfcTendon"]
|
|
# TODO : shading device not supported?
|
|
|
|
if open.__module__ == '__builtin__':
|
|
pyopen = open # because we'll redefine open below
|
|
|
|
def open(filename,skip=None):
|
|
"called when freecad opens a file"
|
|
docname = os.path.splitext(os.path.basename(filename))[0]
|
|
doc = FreeCAD.newDocument(docname)
|
|
doc.Label = decode(docname)
|
|
FreeCAD.ActiveDocument = doc
|
|
getConfig()
|
|
read(filename,skip)
|
|
return doc
|
|
|
|
def insert(filename,docname,skip=None):
|
|
"called when freecad wants to import a file"
|
|
try:
|
|
doc = FreeCAD.getDocument(docname)
|
|
except NameError:
|
|
doc = FreeCAD.newDocument(docname)
|
|
FreeCAD.ActiveDocument = doc
|
|
getConfig()
|
|
read(filename,skip)
|
|
return doc
|
|
|
|
def getConfig():
|
|
"Gets Arch IFC import preferences"
|
|
global SKIP, CREATE_IFC_GROUPS, ASMESH, PREFIX_NUMBERS, FORCE_PYTHON_PARSER, SEPARATE_OPENINGS, SEPARATE_PLACEMENTS, JOINSOLIDS, AGGREGATE_WINDOWS
|
|
IMPORT_IFC_FURNITURE = False
|
|
ASMESH = ["IfcFurnishingElement"]
|
|
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
|
|
CREATE_IFC_GROUPS = p.GetBool("createIfcGroups",False)
|
|
FORCE_PYTHON_PARSER = p.GetBool("forceIfcPythonParser",False)
|
|
DEBUG = p.GetBool("ifcDebug",False)
|
|
SEPARATE_OPENINGS = p.GetBool("ifcSeparateOpenings",False)
|
|
SEPARATE_PLACEMENTS = p.GetBool("ifcSeparatePlacements",False)
|
|
PREFIX_NUMBERS = p.GetBool("ifcPrefixNumbers",False)
|
|
JOINSOLIDS = p.GetBool("ifcJoinSolids",False)
|
|
AGGREGATE_WINDOWS = p.GetBool("ifcAggregateWindows",False)
|
|
skiplist = p.GetString("ifcSkip","")
|
|
if skiplist:
|
|
SKIP = skiplist.split(",")
|
|
asmeshlist = p.GetString("ifcAsMesh","")
|
|
if asmeshlist:
|
|
ASMESH = asmeshlist.split(",")
|
|
|
|
def getIfcOpenShell():
|
|
"locates and imports ifcopenshell"
|
|
global IFCOPENSHELL5
|
|
global IfcImport
|
|
IFCOPENSHELL5 = False
|
|
try:
|
|
import IfcImport
|
|
except ImportError:
|
|
try:
|
|
import ifc_wrapper as IfcImport
|
|
except ImportError:
|
|
FreeCAD.Console.PrintMessage(translate("Arch","Couldn't locate IfcOpenShell\n"))
|
|
return False
|
|
else:
|
|
IFCOPENSHELL5 = True
|
|
return True
|
|
else:
|
|
if hasattr(IfcImport,"IfcFile"):
|
|
IFCOPENSHELL5 = True
|
|
return True
|
|
|
|
def read(filename,skip=None):
|
|
"Parses an IFC file"
|
|
|
|
# parsing the IFC file
|
|
t1 = time.time()
|
|
|
|
processedIds = []
|
|
skipIds = skip
|
|
if not skipIds:
|
|
skipIds = []
|
|
elif isinstance(skipIds,int):
|
|
skipIds = [skipIds]
|
|
|
|
if getIfcOpenShell() and not FORCE_PYTHON_PARSER:
|
|
# use the IfcOpenShell parser
|
|
|
|
# preparing IfcOpenShell
|
|
if DEBUG: global ifcObjects,ifcParents
|
|
ifcObjects = {} # a table to relate ifc id with freecad object
|
|
ifcParents = {} # a table to relate ifc id with parent id
|
|
if SEPARATE_OPENINGS:
|
|
if not IFCOPENSHELL5:
|
|
if hasattr(IfcImport,"DISABLE_OPENING_SUBTRACTIONS"):
|
|
IfcImport.Settings(IfcImport.DISABLE_OPENING_SUBTRACTIONS,True)
|
|
else:
|
|
SKIP.append("IfcOpeningElement")
|
|
useShapes = False
|
|
if IFCOPENSHELL5:
|
|
useShapes = True
|
|
if hasattr(IfcImport,"clean"):
|
|
IfcImport.clean()
|
|
elif hasattr(IfcImport,"USE_BREP_DATA"):
|
|
IfcImport.Settings(IfcImport.USE_BREP_DATA,True)
|
|
useShapes = True
|
|
else:
|
|
if DEBUG: print "Warning: IfcOpenShell version very old, unable to handle Brep data"
|
|
|
|
# opening file
|
|
if IFCOPENSHELL5:
|
|
global ifc
|
|
ifc = IfcImport.open(filename)
|
|
objects = ifc.by_type("IfcProduct")
|
|
num_lines = len(objects)
|
|
relations = ifc.by_type("IfcRelAggregates") + ifc.by_type("IfcRelContainedInSpatialStructure") + ifc.by_type("IfcRelVoidsElement")
|
|
if not objects:
|
|
print "Error opening IFC file"
|
|
return
|
|
else:
|
|
num_lines = sum(1 for line in pyopen(filename))
|
|
if not IfcImport.Init(filename):
|
|
print "Error opening IFC file"
|
|
return
|
|
|
|
# processing geometry
|
|
idx = 0
|
|
while True:
|
|
objparentid = []
|
|
if IFCOPENSHELL5:
|
|
obj = objects[idx]
|
|
idx += 1
|
|
objid = int(str(obj).split("=")[0].strip("#"))
|
|
objname = obj.get_argument(obj.get_argument_index("Name"))
|
|
objtype = str(obj).split("=")[1].split("(")[0]
|
|
for r in relations:
|
|
if r.is_a("IfcRelAggregates"):
|
|
for c in getAttr(r,"RelatedObjects"):
|
|
if str(obj) == str(c):
|
|
objparentid.append(int(str(getAttr(r,"RelatingObject")).split("=")[0].strip("#")))
|
|
elif r.is_a("IfcRelContainedInSpatialStructure"):
|
|
for c in getAttr(r,"RelatedElements"):
|
|
if str(obj) == str(c):
|
|
objparentid.append(int(str(getAttr(r,"RelatingStructure")).split("=")[0].strip("#")))
|
|
elif r.is_a("IfcRelVoidsElement"):
|
|
if str(obj) == str(getAttr(r,"RelatedOpeningElement")):
|
|
objparentid.append(int(str(getAttr(r,"RelatingBuildingElement")).split("=")[0].strip("#")))
|
|
|
|
else:
|
|
if hasattr(IfcImport, 'GetBrepData'):
|
|
obj = IfcImport.GetBrepData()
|
|
else:
|
|
obj = IfcImport.Get()
|
|
objid = obj.id
|
|
idx = objid
|
|
objname = obj.name
|
|
objtype = obj.type
|
|
objparentid.append(obj.parent_id)
|
|
if DEBUG: print "["+str(int((float(idx)/num_lines)*100))+"%] parsing ",objid,": ",objname," of type ",objtype
|
|
|
|
# retrieving name
|
|
n = getCleanName(objname,objid,objtype)
|
|
|
|
# skip IDs
|
|
if objid in skipIds:
|
|
if DEBUG: print " skipping because object ID is in skip list"
|
|
nobj = None
|
|
|
|
# skip types
|
|
elif objtype in SKIP:
|
|
if DEBUG: print " skipping because type is in skip list"
|
|
nobj = None
|
|
|
|
# check if object was already processed, to workaround an ifcopenshell bug
|
|
elif objid in processedIds:
|
|
if DEBUG: print " skipping because this object was already processed"
|
|
|
|
else:
|
|
# build shape
|
|
shape = None
|
|
if useShapes:
|
|
shape = getShape(obj,objid)
|
|
|
|
# walls
|
|
if objtype in ["IfcWallStandardCase","IfcWall"]:
|
|
nobj = makeWall(objid,shape,n)
|
|
|
|
# windows
|
|
elif objtype in ["IfcWindow","IfcDoor"]:
|
|
nobj = makeWindow(objid,shape,n)
|
|
|
|
# structs
|
|
elif objtype in ["IfcBeam","IfcColumn","IfcSlab","IfcFooting"]:
|
|
nobj = makeStructure(objid,shape,objtype,n)
|
|
|
|
# roofs
|
|
elif objtype in ["IfcRoof"]:
|
|
nobj = makeRoof(objid,shape,n)
|
|
|
|
# furniture
|
|
elif objtype in ["IfcFurnishingElement"]:
|
|
nobj = FreeCAD.ActiveDocument.addObject("Part::Feature",n)
|
|
nobj.Shape = shape
|
|
|
|
# sites
|
|
elif objtype in ["IfcSite"]:
|
|
nobj = makeSite(objid,shape,n)
|
|
|
|
# floors
|
|
elif objtype in ["IfcBuildingStorey"]:
|
|
nobj = Arch.makeFloor(name=n)
|
|
nobj.Label = n
|
|
|
|
# floors
|
|
elif objtype in ["IfcBuilding"]:
|
|
nobj = Arch.makeBuilding(name=n)
|
|
nobj.Label = n
|
|
|
|
# spaces
|
|
elif objtype in ["IfcSpace"]:
|
|
nobj = makeSpace(objid,shape,n)
|
|
|
|
elif shape:
|
|
# treat as dumb parts
|
|
if DEBUG: print "Fixme: Shape-containing object not handled: ",objid, " ", objtype
|
|
nobj = FreeCAD.ActiveDocument.addObject("Part::Feature",n)
|
|
nobj.Label = n
|
|
nobj.Shape = shape
|
|
|
|
else:
|
|
# treat as meshes
|
|
if DEBUG: print "Warning: Object without shape: ",objid, " ", objtype
|
|
if hasattr(obj,"mesh"):
|
|
if not hasattr(obj.mesh, 'verts'):
|
|
obj = IfcImport.Get() # Get triangulated rep of same product
|
|
me,pl = getMesh(obj)
|
|
nobj = FreeCAD.ActiveDocument.addObject("Mesh::Feature",n)
|
|
nobj.Label = n
|
|
nobj.Mesh = me
|
|
nobj.Placement = pl
|
|
else:
|
|
if DEBUG: print "Error: Skipping object without mesh: ",objid, " ", objtype
|
|
|
|
# registering object number and parent
|
|
if objparentid:
|
|
ifcParents[objid] = []
|
|
for p in objparentid:
|
|
ifcParents[objid].append([p,not (objtype in subtractiveTypes)])
|
|
ifcObjects[objid] = nobj
|
|
processedIds.append(objid)
|
|
|
|
if IFCOPENSHELL5:
|
|
if idx >= len(objects):
|
|
break
|
|
else:
|
|
if not IfcImport.Next():
|
|
break
|
|
|
|
|
|
# processing non-geometry and relationships
|
|
parents_temp = dict(ifcParents)
|
|
import ArchCommands
|
|
#print parents_temp
|
|
|
|
while parents_temp:
|
|
id, comps = parents_temp.popitem()
|
|
for c in comps:
|
|
parent_id = c[0]
|
|
additive = c[1]
|
|
|
|
if (id <= 0) or (parent_id <= 0):
|
|
# root dummy object
|
|
parent = None
|
|
|
|
elif parent_id in ifcObjects:
|
|
parent = ifcObjects[parent_id]
|
|
# check if parent is a subtraction, if yes parent to grandparent
|
|
if parent_id in ifcParents:
|
|
for p in ifcParents[parent_id]:
|
|
if p[1] == False:
|
|
grandparent_id = p[0]
|
|
if grandparent_id in ifcObjects:
|
|
parent = ifcObjects[grandparent_id]
|
|
else:
|
|
# creating parent if needed
|
|
if IFCOPENSHELL5:
|
|
obj = ifc.by_id(parent_id)
|
|
parentid = int(str(obj).split("=")[0].strip("#"))
|
|
parentname = obj.get_argument(obj.get_argument_index("Name"))
|
|
parenttype = str(obj).split("=")[1].split("(")[0]
|
|
else:
|
|
obj = IfcImport.GetObject(parent_id)
|
|
parentid = obj.id
|
|
parentname = obj.name
|
|
parenttype = obj.type
|
|
#if DEBUG: print "["+str(int((float(idx)/num_lines)*100))+"%] parsing ",parentid,": ",parentname," of type ",parenttype
|
|
n = getCleanName(parentname,parentid,parenttype)
|
|
if parentid <= 0:
|
|
parent = None
|
|
elif parenttype == "IfcBuildingStorey":
|
|
parent = Arch.makeFloor(name=n)
|
|
parent.Label = n
|
|
elif parenttype == "IfcBuilding":
|
|
parent = Arch.makeBuilding(name=n)
|
|
parent.Label = n
|
|
elif parenttype == "IfcSite":
|
|
parent = Arch.makeSite(name=n)
|
|
parent.Label = n
|
|
elif parenttype == "IfcWindow":
|
|
parent = Arch.makeWindow(name=n)
|
|
parent.Label = n
|
|
elif parenttype == "IfcProject":
|
|
parent = None
|
|
else:
|
|
if DEBUG: print "Fixme: skipping unhandled parent: ", parentid, " ", parenttype
|
|
parent = None
|
|
# registering object number and parent
|
|
if not IFCOPENSHELL5:
|
|
if parent_ifcobj.parent_id > 0:
|
|
ifcParents[parentid] = [parent_ifcobj.parent_id,True]
|
|
parents_temp[parentid] = [parent_ifcobj.parent_id,True]
|
|
if parent and (not parentid in ifcObjects):
|
|
ifcObjects[parentid] = parent
|
|
|
|
# attributing parent
|
|
if parent and (id in ifcObjects):
|
|
if ifcObjects[id] and (ifcObjects[id].Name != parent.Name):
|
|
if additive:
|
|
if DEBUG: print "adding ",ifcObjects[id].Name, " to ",parent.Name
|
|
ArchCommands.addComponents(ifcObjects[id],parent)
|
|
else:
|
|
if DEBUG: print "removing ",ifcObjects[id].Name, " from ",parent.Name
|
|
ArchCommands.removeComponents(ifcObjects[id],parent)
|
|
if not IFCOPENSHELL5:
|
|
IfcImport.CleanUp()
|
|
|
|
else:
|
|
# use only the internal python parser
|
|
|
|
FreeCAD.Console.PrintWarning(translate("Arch","IfcOpenShell not found or disabled, falling back on internal parser.\n"))
|
|
schema=getSchema()
|
|
if schema:
|
|
if DEBUG: print "opening",filename,"..."
|
|
ifc = IfcDocument(filename,schema=schema)
|
|
else:
|
|
FreeCAD.Console.PrintWarning(translate("Arch","IFC Schema not found, IFC import disabled.\n"))
|
|
return None
|
|
t2 = time.time()
|
|
if DEBUG: print "Successfully loaded",ifc,"in %s s" % ((t2-t1))
|
|
|
|
# getting walls
|
|
for w in ifc.getEnt("IfcWallStandardCase"):
|
|
nobj = makeWall(w)
|
|
|
|
# getting windows and doors
|
|
for w in (ifc.getEnt("IfcWindow") + ifc.getEnt("IfcDoor")):
|
|
nobj = makeWindow(w)
|
|
|
|
# getting structs
|
|
for w in (ifc.getEnt("IfcSlab") + ifc.getEnt("IfcBeam") + ifc.getEnt("IfcColumn") \
|
|
+ ifc.getEnt("IfcFooting")):
|
|
nobj = makeStructure(w)
|
|
|
|
# getting floors
|
|
for f in ifc.getEnt("IfcBuildingStorey"):
|
|
group(f,ifc,"Floor")
|
|
|
|
# getting buildings
|
|
for b in ifc.getEnt("IfcBuilding"):
|
|
group(b,ifc,"Building")
|
|
|
|
# getting sites
|
|
for s in ifc.getEnt("IfcSite"):
|
|
group(s,ifc,"Site")
|
|
|
|
if DEBUG: print "done parsing. Recomputing..."
|
|
FreeCAD.ActiveDocument.recompute()
|
|
t3 = time.time()
|
|
if DEBUG: print "done processing IFC file in %s s" % ((t3-t1))
|
|
|
|
return None
|
|
|
|
|
|
def getCleanName(name,ifcid,ifctype):
|
|
"Get a clean name from an ifc object"
|
|
#print "getCleanName called",name,ifcid,ifctype
|
|
n = name
|
|
if not n:
|
|
n = ifctype
|
|
if PREFIX_NUMBERS:
|
|
n = "ID"+str(ifcid)+" "+n
|
|
#for c in ",.!?;:":
|
|
# n = n.replace(c,"_")
|
|
return n
|
|
|
|
|
|
def makeWall(entity,shape=None,name="Wall"):
|
|
"makes a wall in the freecad document"
|
|
try:
|
|
if shape:
|
|
# use ifcopenshell
|
|
if isinstance(shape,Part.Shape):
|
|
body = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
|
|
body.Shape = shape
|
|
else:
|
|
body = FreeCAD.ActiveDocument.addObject("Mesh::Feature",name+"_body")
|
|
body.Mesh = shape
|
|
wall = Arch.makeWall(body,name=name)
|
|
wall.Label = name
|
|
if DEBUG: print " made wall object ",entity,":",wall
|
|
return wall
|
|
|
|
# use internal parser
|
|
if DEBUG: print "=====> making wall",entity.id
|
|
placement = wall = wire = body = width = height = None
|
|
placement = getPlacement(entity.ObjectPlacement)
|
|
if DEBUG: print " got wall placement",entity.id,":",placement
|
|
width = entity.getProperty("Width")
|
|
height = entity.getProperty("Height")
|
|
if width and height:
|
|
if DEBUG: print " got width, height ",entity.id,":",width,"/",height
|
|
for r in entity.Representation.Representations:
|
|
if r.RepresentationIdentifier == "Axis":
|
|
wire = getWire(r.Items,placement)
|
|
wall = Arch.makeWall(wire,width,height,align="Center",name="Wall"+str(entity.id))
|
|
else:
|
|
if DEBUG: print " no height or width properties found..."
|
|
for r in entity.Representation.Representations:
|
|
if r.RepresentationIdentifier == "Body":
|
|
for b in r.Items:
|
|
if b.type == "IFCEXTRUDEDAREASOLID":
|
|
norm = getVector(b.ExtrudedDirection)
|
|
norm.normalize()
|
|
wire = getWire(b.SweptArea,placement)
|
|
wall = Arch.makeWall(wire,width=0,height=b.Depth,name="Wall"+str(entity.id))
|
|
wall.Normal = norm
|
|
if wall:
|
|
if DEBUG: print " made wall object ",entity.id,":",wall
|
|
return wall
|
|
if DEBUG: print " error: skipping wall",entity.id
|
|
return None
|
|
except:
|
|
if DEBUG: print " error: skipping wall",entity
|
|
return None
|
|
|
|
|
|
def makeWindow(entity,shape=None,name="Window"):
|
|
"makes a window in the freecad document"
|
|
try:
|
|
if shape:
|
|
# use ifcopenshell
|
|
if isinstance(shape,Part.Shape):
|
|
window = Arch.makeWindow(name=name)
|
|
window.Shape = shape
|
|
window.Label = name
|
|
if DEBUG: print " made window object ",entity,":",window
|
|
return window
|
|
|
|
# use internal parser
|
|
if DEBUG: print "=====> making window",entity.id
|
|
placement = window = wire = body = width = height = None
|
|
placement = getPlacement(entity.ObjectPlacement)
|
|
if DEBUG: print "got window placement",entity.id,":",placement
|
|
width = entity.getProperty("Width")
|
|
height = entity.getProperty("Height")
|
|
for r in entity.Representation.Representations:
|
|
if r.RepresentationIdentifier == "Body":
|
|
for b in r.Items:
|
|
if b.type == "IFCEXTRUDEDAREASOLID":
|
|
wire = getWire(b.SweptArea,placement)
|
|
window = Arch.makeWindow(wire,width=b.Depth,name=objtype+str(entity.id))
|
|
if window:
|
|
if DEBUG: print " made window object ",entity.id,":",window
|
|
return window
|
|
if DEBUG: print " error: skipping window",entity.id
|
|
return None
|
|
except:
|
|
if DEBUG: print " error: skipping window",entity
|
|
return None
|
|
|
|
|
|
def makeStructure(entity,shape=None,ifctype=None,name="Structure"):
|
|
"makes a structure in the freecad document"
|
|
try:
|
|
if shape:
|
|
# use ifcopenshell
|
|
if isinstance(shape,Part.Shape):
|
|
body = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
|
|
body.Shape = shape
|
|
else:
|
|
body = FreeCAD.ActiveDocument.addObject("Mesh::Feature",name+"_body")
|
|
body.Mesh = shape
|
|
structure = Arch.makeStructure(body,name=name)
|
|
structure.Label = name
|
|
if ifctype == "IfcBeam":
|
|
structure.Role = "Beam"
|
|
elif ifctype == "IfcColumn":
|
|
structure.Role = "Column"
|
|
elif ifctype == "IfcSlab":
|
|
structure.Role = "Slab"
|
|
elif ifctype == "IfcFooting":
|
|
structure.Role = "Foundation"
|
|
if DEBUG: print " made structure object ",entity,":",structure," (type: ",ifctype,")"
|
|
return structure
|
|
|
|
# use internal parser
|
|
if DEBUG: print "=====> making struct",entity.id
|
|
placement = structure = wire = body = width = height = None
|
|
placement = getPlacement(entity.ObjectPlacement)
|
|
if DEBUG: print "got window placement",entity.id,":",placement
|
|
width = entity.getProperty("Width")
|
|
height = entity.getProperty("Height")
|
|
for r in entity.Representation.Representations:
|
|
if r.RepresentationIdentifier == "Body":
|
|
for b in r.Items:
|
|
if b.type == "IFCEXTRUDEDAREASOLID":
|
|
wire = getWire(b.SweptArea,placement)
|
|
structure = Arch.makeStructure(wire,height=b.Depth,name=objtype+str(entity.id))
|
|
if structure:
|
|
if DEBUG: print " made structure object ",entity.id,":",structure
|
|
return structure
|
|
if DEBUG: print " error: skipping structure",entity.id
|
|
return None
|
|
except:
|
|
if DEBUG: print " error: skipping structure",entity
|
|
return None
|
|
|
|
|
|
def makeSite(entity,shape=None,name="Site"):
|
|
"makes a site in the freecad document"
|
|
try:
|
|
body = None
|
|
if shape:
|
|
# use ifcopenshell
|
|
if isinstance(shape,Part.Shape):
|
|
body = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
|
|
body.Shape = shape
|
|
else:
|
|
body = FreeCAD.ActiveDocument.addObject("Mesh::Feature",name+"_body")
|
|
body.Mesh = shape
|
|
site = Arch.makeSite(name=name)
|
|
site.Label = name
|
|
if body:
|
|
site.Terrain = body
|
|
if DEBUG: print " made site object ",entity,":",site
|
|
return site
|
|
except:
|
|
return None
|
|
|
|
def makeSpace(entity,shape=None,name="Space"):
|
|
"makes a space in the freecad document"
|
|
try:
|
|
if shape:
|
|
# use ifcopenshell
|
|
if isinstance(shape,Part.Shape):
|
|
space = Arch.makeSpace(name=name)
|
|
space.Label = name
|
|
body = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
|
|
body.Shape = shape
|
|
space.Base = body
|
|
body.ViewObject.hide()
|
|
if DEBUG: print " made space object ",entity,":",space
|
|
return space
|
|
except:
|
|
return None
|
|
|
|
|
|
def makeRoof(entity,shape=None,name="Roof"):
|
|
"makes a roof in the freecad document"
|
|
try:
|
|
if shape:
|
|
# use ifcopenshell
|
|
if isinstance(shape,Part.Shape):
|
|
roof = Arch.makeRoof(name=name)
|
|
roof.Label = name
|
|
roof.Shape = shape
|
|
if DEBUG: print " made roof object ",entity,":",roof
|
|
return roof
|
|
except:
|
|
return None
|
|
|
|
# geometry helpers ###################################################################
|
|
|
|
def getMesh(obj):
|
|
"gets mesh and placement from an IfcOpenShell object"
|
|
if IFCOPENSHELL5:
|
|
return None,None
|
|
print "fixme: mesh data not yet supported" # TODO implement this with OCC tessellate
|
|
import Mesh
|
|
meshdata = []
|
|
print obj.mesh.faces
|
|
print obj.mesh.verts
|
|
f = obj.mesh.faces
|
|
v = obj.mesh.verts
|
|
for i in range(0, len(f), 3):
|
|
face = []
|
|
for j in range(3):
|
|
vi = f[i+j]*3
|
|
face.append([v[vi],v[vi+1],v[vi+2]])
|
|
meshdata.append(face)
|
|
print meshdata
|
|
me = Mesh.Mesh(meshdata)
|
|
# get transformation matrix
|
|
m = obj.matrix
|
|
mat = FreeCAD.Matrix(m[0], m[3], m[6], m[9],
|
|
m[1], m[4], m[7], m[10],
|
|
m[2], m[5], m[8], m[11],
|
|
0, 0, 0, 1)
|
|
pl = FreeCAD.Placement(mat)
|
|
return me,pl
|
|
|
|
def getShape(obj,objid):
|
|
"gets a shape from an IfcOpenShell object"
|
|
#print "retrieving shape from obj ",objid
|
|
import Part
|
|
sh=Part.Shape()
|
|
brep_data = None
|
|
if IFCOPENSHELL5:
|
|
try:
|
|
if hasattr(IfcImport,"SEW_SHELLS"):
|
|
ss = IfcImport.SEW_SHELLS
|
|
else:
|
|
ss = 0
|
|
if SEPARATE_OPENINGS and hasattr(IfcImport,"DISABLE_OPENING_SUBTRACTIONS"):
|
|
if SEPARATE_PLACEMENTS and hasattr(IfcImport,"DISABLE_OBJECT_PLACEMENT"):
|
|
brep_data = IfcImport.create_shape(obj,IfcImport.DISABLE_OPENING_SUBTRACTIONS | IfcImport.DISABLE_OBJECT_PLACEMENT | ss)
|
|
else:
|
|
brep_data = IfcImport.create_shape(obj,IfcImport.DISABLE_OPENING_SUBTRACTIONS | ss)
|
|
else:
|
|
if SEPARATE_PLACEMENTS and hasattr(IfcImport,"DISABLE_OBJECT_PLACEMENT"):
|
|
brep_data = IfcImport.create_shape(obj,IfcImport.DISABLE_OBJECT_PLACEMENT | ss)
|
|
else:
|
|
brep_data = IfcImport.create_shape(obj, ss)
|
|
except:
|
|
print "Unable to retrieve shape data"
|
|
else:
|
|
brep_data = obj.mesh.brep_data
|
|
if brep_data:
|
|
try:
|
|
if MAKETEMPFILES:
|
|
import tempfile
|
|
tf = tempfile.mkstemp(suffix=".brp")[1]
|
|
of = pyopen(tf,"wb")
|
|
of.write(brep_data)
|
|
of.close()
|
|
sh = Part.read(tf)
|
|
os.remove(tf)
|
|
else:
|
|
sh.importBrepFromString(brep_data)
|
|
except:
|
|
print " error: malformed shape"
|
|
return None
|
|
else:
|
|
if IFCOPENSHELL5 and SEPARATE_PLACEMENTS:
|
|
p = getPlacement(getAttr(obj,"ObjectPlacement"))
|
|
if p:
|
|
sh.Placement = p
|
|
if not sh.Solids:
|
|
# try to extract a solid shape
|
|
if sh.Faces:
|
|
try:
|
|
if DEBUG: print " malformed solid. Attempting to fix..."
|
|
shell = Part.makeShell(sh.Faces)
|
|
if shell:
|
|
solid = Part.makeSolid(shell)
|
|
if solid:
|
|
sh = solid
|
|
except:
|
|
if DEBUG: print " failed to retrieve solid from object ",objid
|
|
else:
|
|
if DEBUG: print " object ", objid, " doesn't contain any geometry"
|
|
if not IFCOPENSHELL5:
|
|
m = obj.matrix
|
|
mat = FreeCAD.Matrix(m[0], m[3], m[6], m[9],
|
|
m[1], m[4], m[7], m[10],
|
|
m[2], m[5], m[8], m[11],
|
|
0, 0, 0, 1)
|
|
sh.Placement = FreeCAD.Placement(mat)
|
|
# if DEBUG: print "getting Shape from ",obj
|
|
#print "getting shape: ",sh,sh.Solids,sh.Volume,sh.isValid(),sh.isNull()
|
|
#for v in sh.Vertexes: print v.Point
|
|
if sh:
|
|
if not sh.isNull():
|
|
return sh
|
|
return None
|
|
|
|
def getPlacement(entity):
|
|
"returns a placement from the given entity"
|
|
if not entity:
|
|
return None
|
|
if DEBUG: print " getting placement ",entity
|
|
if IFCOPENSHELL5:
|
|
if isinstance(entity,int):
|
|
entity = ifc.by_id(entity)
|
|
entitytype = str(entity).split("=")[1].split("(")[0].upper()
|
|
entityid = int(str(entity).split("=")[0].strip("#"))
|
|
else:
|
|
entitytype = entity.type.upper()
|
|
entityid = entity.id
|
|
pl = None
|
|
if entitytype == "IFCAXIS2PLACEMENT3D":
|
|
x = getVector(getAttr(entity,"RefDirection"))
|
|
z = getVector(getAttr(entity,"Axis"))
|
|
if not(x) or not(z):
|
|
return None
|
|
y = z.cross(x)
|
|
loc = getVector(getAttr(entity,"Location"))
|
|
m = DraftVecUtils.getPlaneRotation(x,y,z)
|
|
pl = FreeCAD.Placement(m)
|
|
pl.move(loc)
|
|
elif entitytype == "IFCLOCALPLACEMENT":
|
|
pl = getPlacement(getAttr(entity,"PlacementRelTo"))
|
|
relpl = getPlacement(getAttr(entity,"RelativePlacement"))
|
|
if pl and relpl:
|
|
pl = relpl.multiply(pl)
|
|
elif relpl:
|
|
pl = relpl
|
|
elif entitytype == "IFCCARTESIANPOINT":
|
|
loc = getVector(entity)
|
|
pl = FreeCAD.Placement()
|
|
pl.move(loc)
|
|
if DEBUG: print " made placement for ",entityid,":",pl
|
|
return pl
|
|
|
|
def getAttr(entity,attr):
|
|
"returns the given attribute from the given entity"
|
|
if IFCOPENSHELL5:
|
|
if isinstance(entity,int):
|
|
entity = ifc.by_id(entity)
|
|
i = entity.get_argument_index(attr)
|
|
return entity.get_argument(i)
|
|
else:
|
|
return getattr(entity,attr)
|
|
|
|
def getVector(entity):
|
|
"returns a vector from the given entity"
|
|
if not entity:
|
|
return None
|
|
if DEBUG: print " getting point from ",entity
|
|
if IFCOPENSHELL5:
|
|
if isinstance(entity,int):
|
|
entity = ifc.by_id(entity)
|
|
entitytype = str(entity).split("=")[1].split("(")[0].upper()
|
|
else:
|
|
entitytype = entity.type.upper()
|
|
if entitytype == "IFCDIRECTION":
|
|
DirectionRatios = getAttr(entity,"DirectionRatios")
|
|
if len(DirectionRatios) == 3:
|
|
return FreeCAD.Vector(tuple(DirectionRatios))
|
|
else:
|
|
return FreeCAD.Vector(tuple(DirectionRatios+[0]))
|
|
elif entitytype == "IFCCARTESIANPOINT":
|
|
Coordinates = getAttr(entity,"Coordinates")
|
|
if len(Coordinates) == 3:
|
|
return FreeCAD.Vector(tuple(Coordinates))
|
|
else:
|
|
return FreeCAD.Vector(tuple(Coordinates+[0]))
|
|
return None
|
|
|
|
# below is only used by the internal parser #########################################
|
|
|
|
def decode(name):
|
|
"decodes encoded strings"
|
|
try:
|
|
decodedName = (name.decode("utf8"))
|
|
except UnicodeDecodeError:
|
|
try:
|
|
decodedName = (name.decode("latin1"))
|
|
except UnicodeDecodeError:
|
|
FreeCAD.Console.PrintError(translate("Arch", "Error: Couldn't determine character encoding\n"))
|
|
decodedName = name
|
|
return decodedName
|
|
|
|
def getSchema():
|
|
"retrieves the express schema"
|
|
custom = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetString("CustomIfcSchema","")
|
|
if custom:
|
|
if os.path.exists(custom):
|
|
if DEBUG: print "Using custom schema: ",custom.split(os.sep)[-1]
|
|
return custom
|
|
p = None
|
|
p = os.path.join(FreeCAD.ConfigGet("UserAppData"),SCHEMA.split(os.sep)[-1])
|
|
if os.path.exists(p):
|
|
return p
|
|
import ArchCommands
|
|
p = ArchCommands.download(SCHEMA)
|
|
if p:
|
|
return p
|
|
return None
|
|
|
|
def group(entity,ifc,mode=None):
|
|
"gathers the children of the given entity"
|
|
# only used by the internal parser
|
|
|
|
try:
|
|
if DEBUG: print "=====> making group",entity.id
|
|
placement = None
|
|
placement = getPlacement(entity.ObjectPlacement)
|
|
if DEBUG: print "got cell placement",entity.id,":",placement
|
|
subelements = ifc.find("IFCRELCONTAINEDINSPATIALSTRUCTURE","RelatingStructure",entity)
|
|
subelements.extend(ifc.find("IFCRELAGGREGATES","RelatingObject",entity))
|
|
elts = []
|
|
for s in subelements:
|
|
if hasattr(s,"RelatedElements"):
|
|
s = s.RelatedElements
|
|
if not isinstance(s,list): s = [s]
|
|
elts.extend(s)
|
|
elif hasattr(s,"RelatedObjects"):
|
|
s = s.RelatedObjects
|
|
if not isinstance(s,list): s = [s]
|
|
elts.extend(s)
|
|
elif hasattr(s,"RelatedObject"):
|
|
s = s.RelatedObject
|
|
if not isinstance(s,list): s = [s]
|
|
elts.extend(s)
|
|
print "found dependent elements: ",elts
|
|
|
|
groups = [['Wall',['IfcWallStandardCase'],[]],
|
|
['Window',['IfcWindow','IfcDoor'],[]],
|
|
['Structure',['IfcSlab','IfcFooting','IfcBeam','IfcColumn'],[]],
|
|
['Floor',['IfcBuildingStorey'],[]],
|
|
['Building',['IfcBuilding'],[]],
|
|
['Furniture',['IfcFurnishingElement'],[]]]
|
|
|
|
for e in elts:
|
|
for g in groups:
|
|
for t in g[1]:
|
|
if e.type.upper() == t.upper():
|
|
if hasattr(FreeCAD.ActiveDocument,g[0]+str(e.id)):
|
|
g[2].append(FreeCAD.ActiveDocument.getObject(g[0]+str(e.id)))
|
|
print "groups:",groups
|
|
|
|
comps = []
|
|
if CREATE_IFC_GROUPS:
|
|
if DEBUG: print "creating subgroups"
|
|
for g in groups:
|
|
if g[2]:
|
|
if g[0] in ['Building','Floor']:
|
|
comps.extend(g[2])
|
|
else:
|
|
fcg = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",g[0]+"s")
|
|
for o in g[2]:
|
|
fcg.addObject(o)
|
|
comps.append(fcg)
|
|
else:
|
|
for g in groups:
|
|
comps.extend(g[2])
|
|
|
|
label = entity.Name
|
|
name = mode + str(entity.id)
|
|
cell = None
|
|
if mode == "Site":
|
|
cell = Arch.makeSite(comps,name=name)
|
|
elif mode == "Floor":
|
|
cell = Arch.makeFloor(comps,name=name)
|
|
elif mode == "Building":
|
|
cell = Arch.makeBuilding(comps,name=name)
|
|
if label and cell:
|
|
cell.Label = label
|
|
except:
|
|
if DEBUG: print "error: skipping group ",entity.id
|
|
|
|
def getWire(entity,placement=None):
|
|
"returns a wire (created in the freecad document) from the given entity"
|
|
# only used by the internal parser
|
|
if DEBUG: print "making Wire from :",entity
|
|
if not entity: return None
|
|
if entity.type == "IFCPOLYLINE":
|
|
pts = []
|
|
for p in entity.Points:
|
|
pts.append(getVector(p))
|
|
return Draft.getWire(pts,placement=placement)
|
|
elif entity.type == "IFCARBITRARYCLOSEDPROFILEDEF":
|
|
pts = []
|
|
for p in entity.OuterCurve.Points:
|
|
pts.append(getVector(p))
|
|
return Draft.getWire(pts,closed=True,placement=placement)
|
|
|
|
|
|
# EXPORT ##########################################################
|
|
|
|
def export(exportList,filename):
|
|
"called when freecad exports a file"
|
|
global ifcw
|
|
ifcw = None
|
|
try:
|
|
import IfcImport as ifcw
|
|
except ImportError:
|
|
try:
|
|
import ifc_wrapper as ifcw
|
|
except ImportError:
|
|
FreeCAD.Console.PrintError(translate("Arch","Error: IfcOpenShell is not installed\n"))
|
|
print """importIFC: ifcOpenShell is not installed. IFC export is unavailable.
|
|
Note: IFC export currently requires an experimental version of IfcOpenShell
|
|
available from https://github.com/aothms/IfcOpenShell"""
|
|
return
|
|
|
|
if (not hasattr(ifcw,"IfcFile")) and (not hasattr(ifcw,"file")):
|
|
FreeCAD.Console.PrintError(translate("Arch","Error: your IfcOpenShell version is too old\n"))
|
|
print """importIFC: The version of ifcOpenShell installed on this system doesn't
|
|
have IFC export capabilities. IFC export currently requires an experimental
|
|
version of IfcOpenShell available from https://github.com/aothms/IfcOpenShell"""
|
|
return
|
|
import Arch,Draft
|
|
|
|
# creating base IFC project
|
|
getConfig()
|
|
PRECISION = Draft.precision()
|
|
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
|
|
scaling = p.GetFloat("IfcScalingFactor",1.0)
|
|
exporttxt = p.GetBool("IfcExportList",False)
|
|
forcebrep = p.GetBool("ifcExportAsBrep",False)
|
|
application = "FreeCAD"
|
|
ver = FreeCAD.Version()
|
|
version = ver[0]+"."+ver[1]+" build"+ver[2]
|
|
owner = FreeCAD.ActiveDocument.CreatedBy
|
|
company = FreeCAD.ActiveDocument.Company
|
|
project = FreeCAD.ActiveDocument.Name
|
|
ifc = IfcWriter(filename,project,owner,company,application,version)
|
|
txt = []
|
|
|
|
# get all children and reorder list to get buildings and floors processed first
|
|
objectslist = Draft.getGroupContents(exportList,walls=True,addgroups=True)
|
|
objectslist = Arch.pruneIncluded(objectslist)
|
|
|
|
sites = []
|
|
buildings = []
|
|
floors = []
|
|
groups = {}
|
|
others = []
|
|
for obj in objectslist:
|
|
otype = Draft.getType(obj)
|
|
if otype == "Site":
|
|
sites.append(obj)
|
|
elif otype == "Building":
|
|
buildings.append(obj)
|
|
elif otype == "Floor":
|
|
floors.append(obj)
|
|
elif otype == "Group":
|
|
groups[obj.Name] = []
|
|
else:
|
|
others.append(obj)
|
|
objectslist = buildings + floors + others
|
|
if DEBUG: print "adding ", len(objectslist), " objects"
|
|
|
|
global unprocessed
|
|
unprocessed = []
|
|
|
|
# process objects
|
|
for obj in objectslist:
|
|
|
|
otype = Draft.getType(obj)
|
|
name = str(obj.Label)
|
|
parent = Arch.getHost(obj)
|
|
gdata = None
|
|
fdata = None
|
|
placement = None
|
|
color = None
|
|
representation = None
|
|
descr = None
|
|
extra = None
|
|
|
|
# setting the IFC type
|
|
if hasattr(obj,"Role"):
|
|
ifctype = obj.Role.replace(" ","")
|
|
else:
|
|
ifctype = otype
|
|
if ifctype == "Foundation":
|
|
ifctype = "Footing"
|
|
elif ifctype == "Rebar":
|
|
ifctype = "ReinforcingBar"
|
|
elif ifctype in ["Part","Undefined"]:
|
|
ifctype = "BuildingElementProxy"
|
|
|
|
# getting the "Force BREP" flag
|
|
brepflag = False
|
|
if hasattr(obj,"IfcAttributes"):
|
|
if "FlagForceBrep" in obj.IfcAttributes.keys():
|
|
if obj.IfcAttributes["FlagForceBrep"] == "True":
|
|
brepflag = True
|
|
|
|
if DEBUG: print "Adding " + obj.Label + " as Ifc" + ifctype
|
|
|
|
# writing IFC data
|
|
if obj.isDerivedFrom("App::DocumentObjectGroup"):
|
|
|
|
# getting parent building
|
|
if parent:
|
|
parent = ifc.findByName("IfcBuilding",str(parent.Label))
|
|
|
|
if otype == "Site":
|
|
print " Skipping (not implemented yet)" # TODO manage sites
|
|
elif otype == "Building":
|
|
ifc.addBuilding( name=name )
|
|
elif otype == "Floor":
|
|
ifc.addStorey( building=parent, name=name )
|
|
|
|
elif obj.isDerivedFrom("Part::Feature"):
|
|
|
|
# get color
|
|
if FreeCAD.GuiUp:
|
|
color = obj.ViewObject.ShapeColor[:3]
|
|
|
|
# get parent floor
|
|
if parent:
|
|
parent = ifc.findByName("IfcBuildingStorey",str(parent.Label))
|
|
|
|
# get representation
|
|
if (not forcebrep) and (not brepflag):
|
|
gdata = getIfcExtrusionData(obj,scaling,SEPARATE_OPENINGS)
|
|
#if DEBUG: print " extrusion data for ",obj.Label," : ",gdata
|
|
if not gdata:
|
|
fdata = getIfcBrepFacesData(obj,scaling)
|
|
#if DEBUG: print " brep data for ",obj.Label," : ",fdata
|
|
if not fdata:
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
print " Error retrieving the shape of object ", obj.Label
|
|
unprocessed.append(obj)
|
|
continue
|
|
else:
|
|
if DEBUG: print " No geometry"
|
|
else:
|
|
if DEBUG: print " Brep"
|
|
else:
|
|
if DEBUG: print " Extrusion"
|
|
if gdata:
|
|
# gdata = [ type, profile data, extrusion data, placement data ]
|
|
placement = ifc.addPlacement(origin=gdata[3][0],xaxis=gdata[3][1],zaxis=gdata[3][2])
|
|
if gdata[0] == "polyline":
|
|
representation = ifc.addExtrudedPolyline(gdata[1], gdata[2], color=color)
|
|
elif gdata[0] == "circle":
|
|
representation = ifc.addExtrudedCircle(gdata[1], gdata[2], color=color)
|
|
elif gdata[0] == "ellipse":
|
|
representation = ifc.addExtrudedEllipse(gdata[1], gdata[2], color=color)
|
|
elif gdata[0] == "composite":
|
|
representation = ifc.addExtrudedCompositeCurve(gdata[1], gdata[2], color=color)
|
|
else:
|
|
print "debug: unknow extrusion type"
|
|
elif fdata:
|
|
representation = [ifc.addFacetedBrep(f, color=color) for f in fdata]
|
|
|
|
# create ifc object
|
|
ifctype = "Ifc" + ifctype
|
|
if hasattr(obj,"Description"):
|
|
descr = obj.Description
|
|
if otype == "Wall":
|
|
if gdata:
|
|
if gdata[0] == "polyline":
|
|
ifctype = "IfcWallStandardCase"
|
|
elif otype == "Structure":
|
|
if ifctype in ["IfcSlab","IfcFooting"]:
|
|
extra = ["NOTDEFINED"]
|
|
elif otype == "Window":
|
|
extra = [obj.Width.Value*scaling, obj.Height.Value*scaling]
|
|
elif otype == "Space":
|
|
extra = ["ELEMENT","INTERNAL",getIfcElevation(obj)]
|
|
elif otype == "Part":
|
|
extra = ["ELEMENT"]
|
|
if not ifctype in supportedIfcTypes:
|
|
if DEBUG: print " Type ",ifctype," is not supported yet. Exporting as IfcBuildingElementProxy instead"
|
|
ifctype = "IfcBuildingElementProxy"
|
|
extra = ["ELEMENT"]
|
|
|
|
product = ifc.addProduct( ifctype, representation, storey=parent, placement=placement, name=name, description=descr, extra=extra )
|
|
|
|
if product:
|
|
# removing openings
|
|
if SEPARATE_OPENINGS and gdata:
|
|
for o in obj.Subtractions:
|
|
print "Subtracting ",o.Label
|
|
fdata = getIfcBrepFacesData(o,scaling,sub=True)
|
|
representation = [ifc.addFacetedBrep(f, color=color) for f in fdata]
|
|
p2 = ifc.addProduct( "IfcOpeningElement", representation, storey=product, placement=None, name=str(o.Label), description=None)
|
|
|
|
# writing text log
|
|
spacer = ""
|
|
for i in range(36-len(obj.Label)):
|
|
spacer += " "
|
|
txt.append(obj.Label + spacer + ifctype)
|
|
|
|
# adding object to group, if any
|
|
for g in groups.keys():
|
|
group = FreeCAD.ActiveDocument.getObject(g)
|
|
if group:
|
|
for o in group.Group:
|
|
if o.Name == obj.Name:
|
|
groups[g].append(product)
|
|
|
|
else:
|
|
unprocessed.append(obj)
|
|
else:
|
|
if DEBUG: print "Object type ", otype, " is not supported yet."
|
|
|
|
# processing groups
|
|
for name,entities in groups.iteritems():
|
|
if entities:
|
|
o = FreeCAD.ActiveDocument.getObject(name)
|
|
if o:
|
|
if DEBUG: print "Adding group ", o.Label, " with ",len(entities)," elements"
|
|
grp = ifc.addGroup( entities, o.Label )
|
|
|
|
ifc.write()
|
|
|
|
if exporttxt:
|
|
import time, os
|
|
txtstring = "List of objects exported by FreeCAD in file\n"
|
|
txtstring += filename + "\n"
|
|
txtstring += "On " + time.ctime() + "\n"
|
|
txtstring += "\n"
|
|
txtstring += str(len(txt)) + " objects exported:\n"
|
|
txtstring += "\n"
|
|
txtstring += "Nr Name Type\n"
|
|
txtstring += "\n"
|
|
for i in range(len(txt)):
|
|
idx = str(i+1)
|
|
sp = ""
|
|
for j in range(8-len(idx)):
|
|
sp += " "
|
|
txtstring += idx + sp + txt[i] + "\n"
|
|
txtfile = os.path.splitext(filename)[0]+".txt"
|
|
f = pyopen(txtfile,"wb")
|
|
f.write(txtstring)
|
|
f.close()
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
if unprocessed:
|
|
print ""
|
|
print "WARNING: " + str(len(unprocessed)) + " objects were not exported (stored in importIFC.unprocessed):"
|
|
for o in unprocessed:
|
|
print " " + o.Label
|
|
|
|
|
|
def getTuples(data,scale=1,placement=None,normal=None,close=True):
|
|
"""getTuples(data,[scale,placement,normal,close]): returns a tuple or a list of tuples from a vector
|
|
or from the vertices of a shape. Scale can indicate a scale factor"""
|
|
rnd = False
|
|
import Part
|
|
if isinstance(data,FreeCAD.Vector):
|
|
if placement:
|
|
data = placement.multVec(data)
|
|
if rnd:
|
|
data = DraftVecUtils.rounded(data)
|
|
return (data.x*scale,data.y*scale,data.z*scale)
|
|
elif isinstance(data,Part.Shape):
|
|
t = []
|
|
if len(data.Wires) == 1:
|
|
import Part,DraftGeomUtils
|
|
data = Part.Wire(Part.__sortEdges__(data.Wires[0].Edges))
|
|
verts = data.Vertexes
|
|
try:
|
|
c = data.CenterOfMass
|
|
v1 = verts[0].Point.sub(c)
|
|
v2 = verts[1].Point.sub(c)
|
|
if DraftVecUtils.angle(v2,v1,normal) >= 0:
|
|
# inverting verts order if the direction is couterclockwise
|
|
verts.reverse()
|
|
except:
|
|
pass
|
|
for v in verts:
|
|
pt = v.Point
|
|
if placement:
|
|
if not placement.isNull():
|
|
pt = placement.multVec(pt)
|
|
if rnd:
|
|
pt = DraftVecUtils.rounded(pt)
|
|
t.append((pt.x*scale,pt.y*scale,pt.z*scale))
|
|
|
|
if close: # faceloops must not be closed, but ifc profiles must.
|
|
t.append(t[0])
|
|
else:
|
|
print "Arch.getTuples(): Wrong profile data"
|
|
return t
|
|
|
|
def getIfcExtrusionData(obj,scale=1,nosubs=False):
|
|
"""getIfcExtrusionData(obj,[scale,nosubs]): returns a closed path (a list of tuples), a tuple expressing an extrusion
|
|
vector, and a list of 3 tuples for base position, x axis and z axis. Or returns None, if a base loop and
|
|
an extrusion direction cannot be extracted. Scale can indicate a scale factor."""
|
|
|
|
CURVEMODE = "PARAMETER" # For trimmed curves. CARTESIAN or PARAMETER
|
|
|
|
if hasattr(obj,"Additions"):
|
|
if obj.Additions:
|
|
# TODO provisorily treat objs with additions as breps
|
|
return None
|
|
if hasattr(obj,"Subtractions") and not nosubs:
|
|
if obj.Subtractions:
|
|
return None
|
|
if hasattr(obj,"Proxy"):
|
|
if hasattr(obj.Proxy,"getProfiles"):
|
|
p = obj.Proxy.getProfiles(obj,noplacement=True)
|
|
v = obj.Proxy.getExtrusionVector(obj,noplacement=True)
|
|
if (len(p) == 1) and v:
|
|
p = p[0]
|
|
r = FreeCAD.Placement()
|
|
#b = p.CenterOfMass
|
|
r = obj.Proxy.getPlacement(obj)
|
|
#b = obj.Placement.multVec(FreeCAD.Vector())
|
|
#r.Rotation = DraftVecUtils.getRotation(v,FreeCAD.Vector(0,0,1))
|
|
d = [r.Base,DraftVecUtils.rounded(r.Rotation.multVec(FreeCAD.Vector(1,0,0))),DraftVecUtils.rounded(r.Rotation.multVec(FreeCAD.Vector(0,0,1)))]
|
|
#r = r.inverse()
|
|
#print "getExtrusionData: computed placement:",r
|
|
import Part
|
|
if len(p.Edges) == 1:
|
|
if isinstance(p.Edges[0].Curve,Part.Circle):
|
|
# Circle profile
|
|
r1 = p.Edges[0].Curve.Radius*scale
|
|
return "circle", [getTuples(p.Edges[0].Curve.Center,scale), r1], getTuples(v,scale), d
|
|
elif isinstance(p.Edges[0].Curve,Part.Ellipse):
|
|
# Ellipse profile
|
|
r1 = p.Edges[0].Curve.MajorRadius*scale
|
|
r2 = p.Edges[0].Curve.MinorRadius*scale
|
|
return "ellipse", [getTuples(p.Edges[0].Curve.Center,scale), r1, r2], getTuples(v,scale), d
|
|
curves = False
|
|
for e in p.Edges:
|
|
if isinstance(e.Curve,Part.Circle):
|
|
curves = True
|
|
elif not isinstance(e.Curve,Part.Line):
|
|
print "Arch.getIfcExtrusionData: Warning: unsupported edge type in profile"
|
|
if curves:
|
|
# Composite profile
|
|
ecurves = []
|
|
last = None
|
|
import DraftGeomUtils
|
|
edges = Part.__sortEdges__(p.Edges)
|
|
for e in edges:
|
|
if isinstance(e.Curve,Part.Circle):
|
|
import math
|
|
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)
|
|
if CURVEMODE == "CARTESIAN":
|
|
# BUGGY
|
|
p1 = getTuples(e.Vertexes[0].Point,scale)
|
|
p2 = getTuples(e.Vertexes[-1].Point,scale)
|
|
ecurves.append(["arc",getTuples(e.Curve.Center,scale),e.Curve.Radius*scale,[p1,p2],follow,CURVEMODE])
|
|
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
|
|
ecurves.append(["line",[getTuples(vert,scale) for vert in verts]])
|
|
return "composite", ecurves, getTuples(v,scale), d
|
|
else:
|
|
# Polyline profile
|
|
return "polyline", getTuples(p,scale), getTuples(v,scale), d
|
|
return None
|
|
|
|
def getIfcBrepFacesData(obj,scale=1,sub=False,tessellation=1):
|
|
"""getIfcBrepFacesData(obj,[scale,tesselation]): returns a list(0) of lists(1) of lists(2) of lists(3),
|
|
list(3) being a list of vertices defining a loop, list(2) describing a face from one or
|
|
more loops, list(1) being the whole solid made of several faces, list(0) being the list
|
|
of solids inside the object. Scale can indicate a scaling factor. Tesselation is the tesselation
|
|
factor to apply on curved faces."""
|
|
shape = None
|
|
if sub:
|
|
if hasattr(obj,"Proxy"):
|
|
if hasattr(obj.Proxy,"getSubVolume"):
|
|
shape = obj.Proxy.getSubVolume(obj)
|
|
if not shape:
|
|
if hasattr(obj,"Shape"):
|
|
if obj.Shape:
|
|
if not obj.Shape.isNull():
|
|
#if obj.Shape.isValid():
|
|
shape = obj.Shape
|
|
elif hasattr(obj,"Terrain"):
|
|
if obj.Terrain:
|
|
if hasattr(obj.Terrain,"Shape"):
|
|
if obj.Terrain.Shape:
|
|
if not obj.Terrain.Shape.isNull():
|
|
if obj.Terrain.Shape.isValid():
|
|
shape = obj.Terrain.Shape
|
|
if shape:
|
|
import Part
|
|
sols = []
|
|
if shape.Solids:
|
|
dataset = shape.Solids
|
|
else:
|
|
dataset = shape.Shells
|
|
print "Warning! object contains no solids"
|
|
for sol in shape.Solids:
|
|
s = []
|
|
curves = False
|
|
for face in sol.Faces:
|
|
for e in face.Edges:
|
|
if not isinstance(e.Curve,Part.Line):
|
|
curves = True
|
|
if curves:
|
|
tris = sol.tessellate(tessellation)
|
|
for tri in tris[1]:
|
|
f = []
|
|
for i in tri:
|
|
f.append(getTuples(tris[0][i],scale))
|
|
s.append([f])
|
|
else:
|
|
for face in sol.Faces:
|
|
f = []
|
|
f.append(getTuples(face.OuterWire,scale,normal=face.normalAt(0,0),close=False))
|
|
for wire in face.Wires:
|
|
if wire.hashCode() != face.OuterWire.hashCode():
|
|
f.append(getTuples(wire,scale,normal=DraftVecUtils.neg(face.normalAt(0,0)),close=False))
|
|
s.append(f)
|
|
sols.append(s)
|
|
return sols
|
|
return None
|
|
|
|
def getIfcElevation(obj):
|
|
"""getIfcElevation(obj): Returns the lowest height (Z coordinate) of this object"""
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
b = obj.Shape.BoundBox
|
|
return b.ZMin
|
|
return 0
|
|
|
|
|
|
def explore(filename=None):
|
|
"explore the contents of an ifc file in a Qt dialog"
|
|
if not filename:
|
|
from PySide import QtGui
|
|
filename = QtGui.QFileDialog.getOpenFileName(QtGui.qApp.activeWindow(),'IFC files','*.ifc')
|
|
if filename:
|
|
filename = filename[0]
|
|
if filename:
|
|
getConfig()
|
|
schema=getSchema()
|
|
d = explorer(filename,schema)
|
|
d.show()
|
|
return d
|
|
|
|
# IfcReader #############################################
|
|
|
|
class IfcSchema:
|
|
SIMPLETYPES = ["INTEGER", "REAL", "STRING", "NUMBER", "LOGICAL", "BOOLEAN"]
|
|
NO_ATTR = ["WHERE", "INVERSE","WR2","WR3", "WR4", "WR5", "UNIQUE", "DERIVE"]
|
|
|
|
def __init__(self, filename):
|
|
self.filename = filename
|
|
if not os.path.exists(filename):
|
|
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro")
|
|
p = p.GetString("MacroPath","")
|
|
filename = p + os.sep + filename
|
|
if not os.path.exists(filename):
|
|
raise ImportError("no IFCSchema file found!")
|
|
|
|
self.file = open(self.filename)
|
|
self.data = self.file.read()
|
|
self.types = self.readTypes()
|
|
self.entities = self.readEntities()
|
|
if DEBUG: print "Parsed from schema %s: %s entities and %s types" % (self.filename, len(self.entities), len(self.types))
|
|
|
|
def readTypes(self):
|
|
"""
|
|
Parse all the possible types from the schema,
|
|
returns a dictionary Name -> Type
|
|
"""
|
|
types = {}
|
|
for m in re.finditer("TYPE (.*) = (.*);", self.data):
|
|
typename, typetype = m.groups()
|
|
if typetype in self.SIMPLETYPES:
|
|
types[typename] = typetype
|
|
else:
|
|
types[typename] = "#" + typetype
|
|
|
|
return types
|
|
|
|
def readEntities(self):
|
|
"""
|
|
Parse all the possible entities from the schema,
|
|
returns a dictionary of the form:
|
|
{ name: {
|
|
"supertype": supertype,
|
|
"attributes": [{ key: value }, ..]
|
|
}}
|
|
"""
|
|
entities = {}
|
|
|
|
# Regexes must be greedy to prevent matching outer entity and end_entity strings
|
|
# Regexes have re.DOTALL to match newlines
|
|
for m in re.finditer("ENTITY (.*?)END_ENTITY;", self.data, re.DOTALL):
|
|
entity = {}
|
|
raw_entity_str = m.groups()[0]
|
|
|
|
entity["name"] = re.search("(.*?)[;|\s]", raw_entity_str).groups()[0].upper()
|
|
|
|
subtypeofmatch = re.search(".*SUBTYPE OF \((.*?)\);", raw_entity_str)
|
|
entity["supertype"] = subtypeofmatch.groups()[0].upper() if subtypeofmatch else None
|
|
|
|
# find the shortest string matched from the end of the entity type header to the
|
|
# first occurence of a NO_ATTR string (when it occurs on a new line)
|
|
inner_str = re.search(";(.*?)$", raw_entity_str, re.DOTALL).groups()[0]
|
|
|
|
attrs_str = min([inner_str.partition("\r\n "+a)[0] for a in self.NO_ATTR])
|
|
attrs = []
|
|
for am in re.finditer("(.*?) : (.*?);", attrs_str, re.DOTALL):
|
|
name, attr_type = [s.replace("\r\n\t","") for s in am.groups()]
|
|
attrs.append((name, attr_type))
|
|
|
|
entity["attributes"] = attrs
|
|
entities[entity["name"]] = entity
|
|
|
|
|
|
return entities
|
|
|
|
def getAttributes(self, name):
|
|
"""
|
|
Get all attributes af an entity, including supertypes
|
|
"""
|
|
ent = self.entities[name]
|
|
|
|
attrs = []
|
|
while ent != None:
|
|
this_ent_attrs = copy.copy(ent["attributes"])
|
|
this_ent_attrs.reverse()
|
|
attrs.extend(this_ent_attrs)
|
|
ent = self.entities.get(ent["supertype"], None)
|
|
|
|
attrs.reverse()
|
|
return attrs
|
|
|
|
def capitalize(self, name):
|
|
"returns a capitalized version of a type"
|
|
if name.upper() in self.data.upper():
|
|
i1 = self.data.upper().index(name.upper())
|
|
i2 = i1 + len(name)
|
|
name = self.data[i1:i2]
|
|
return name
|
|
|
|
class IfcFile:
|
|
"""
|
|
Parses an ifc file given by filename, entities can be retrieved by name and id
|
|
The whole file is stored in a dictionary (in memory)
|
|
"""
|
|
|
|
entsById = {}
|
|
entsByName = {}
|
|
|
|
def __init__(self, filename,schema):
|
|
self.filename = filename
|
|
self.schema = IfcSchema(schema)
|
|
self.file = open(self.filename)
|
|
self.entById, self.entsByName, self.header = self.read()
|
|
self.file.close()
|
|
if DEBUG: print "Parsed from file %s: %s entities" % (self.filename, len(self.entById))
|
|
|
|
def getEntityById(self, id):
|
|
return self.entById.get(id, None)
|
|
|
|
def getEntitiesByName(self, name):
|
|
return self.entsByName.get(name, None)
|
|
|
|
def read(self):
|
|
"""
|
|
Returns 2 dictionaries, entById and entsByName
|
|
"""
|
|
entById = {}
|
|
entsByName = {}
|
|
header = 'HEADER '
|
|
readheader = False
|
|
for line in self.file:
|
|
e = self.parseLine(line)
|
|
if e:
|
|
entById[int(e["id"])] = e
|
|
ids = e.get(e["name"],[])
|
|
ids.append(e["id"])
|
|
entsByName[e["name"]] = list(set(ids))
|
|
elif 'HEADER' in line:
|
|
readheader = True
|
|
elif readheader:
|
|
if 'ENDSEC' in line:
|
|
readheader = False
|
|
else:
|
|
header += line
|
|
|
|
return [entById, entsByName, header]
|
|
|
|
def parseLine(self, line):
|
|
"""
|
|
Parse a line
|
|
"""
|
|
m = IFCLINE_RE.search(line) # id,name,attrs
|
|
if m:
|
|
id, name, attrs = m.groups()
|
|
id = id.strip()
|
|
name = name.strip()
|
|
attrs = attrs.strip()
|
|
else:
|
|
return False
|
|
|
|
return {"id": id, "name": name, "attributes": self.parseAttributes(name, attrs)}
|
|
|
|
def parseAttributes(self, ent_name, attrs_str):
|
|
"""
|
|
Parse the attributes of a line
|
|
"""
|
|
parts = []
|
|
lastpos = 0
|
|
|
|
while lastpos < len(attrs_str):
|
|
newpos = self.nextString(attrs_str, lastpos)
|
|
parts.extend(self.parseAttribute(attrs_str[lastpos:newpos-1]))
|
|
lastpos = newpos
|
|
|
|
schema_attributes = self.schema.getAttributes(ent_name)
|
|
|
|
assert len(schema_attributes) == len(parts), \
|
|
"Expected %s attributes, got %s (entity: %s" % \
|
|
(len(schema_attributes), len(parts), ent_name)
|
|
|
|
attribute_names = [a[0] for a in schema_attributes]
|
|
|
|
return dict(zip(attribute_names, parts))
|
|
|
|
def parseAttribute(self, attr_str):
|
|
"""
|
|
Map a single attribute to a python type (recursively)
|
|
"""
|
|
parts = []
|
|
lastpos = 0
|
|
while lastpos < len(attr_str):
|
|
newpos = self.nextString(attr_str, lastpos)
|
|
s = attr_str[lastpos:newpos-1]
|
|
if (s[0] == "(" and s[-1] == ")"): # list, recurse
|
|
parts.append(self.parseAttribute(s[1:-1]))
|
|
else:
|
|
try:
|
|
parts.append(float(s)) # number, any kind
|
|
except ValueError:
|
|
if s[0] == "'" and s[-1] == "'": # string
|
|
parts.append(s[1:-1])
|
|
elif s == "$":
|
|
parts.append(None)
|
|
else:
|
|
parts.append(s) # ref, enum or other
|
|
|
|
lastpos = newpos
|
|
|
|
return parts
|
|
|
|
|
|
def nextString(self, s, start):
|
|
"""
|
|
Parse the data part of a line
|
|
"""
|
|
parens = 0
|
|
quotes = 0
|
|
|
|
for pos in range(start,len(s)):
|
|
c = s[pos]
|
|
if c == "," and parens == 0 and quotes == 0:
|
|
return pos+1
|
|
elif c == "(" and quotes == 0:
|
|
parens += 1
|
|
elif c == ")" and quotes == 0:
|
|
parens -= 1
|
|
elif c == "\'" and quotes == 0:
|
|
quotes = 1
|
|
elif c =="\'" and quotes == 1:
|
|
quotes = 0
|
|
|
|
return len(s)+1
|
|
|
|
class IfcEntity:
|
|
"a container for an IFC entity"
|
|
def __init__(self,ent,doc=None):
|
|
self.data = ent
|
|
self.id = int(ent['id'])
|
|
self.type = ent['name'].upper().strip(",[]()")
|
|
self.attributes = ent['attributes']
|
|
self.doc = doc
|
|
|
|
def __repr__(self):
|
|
return str(self.id) + ' : ' + self.type + ' ' + str(self.attributes)
|
|
|
|
def getProperties(self):
|
|
return self.doc.find('IFCRELDEFINESBYPROPERTIES','RelatedObjects',self)
|
|
|
|
def getProperty(self,propName):
|
|
"finds the value of the given property or quantity in this object, if exists"
|
|
propsets = self.doc.find('IFCRELDEFINESBYPROPERTIES','RelatedObjects',self)
|
|
if not propsets: return None
|
|
propset = []
|
|
for p in propsets:
|
|
if hasattr(p.RelatingPropertyDefinition,"HasProperties"):
|
|
propset.extend(p.RelatingPropertyDefinition.HasProperties)
|
|
elif hasattr(p.RelatingPropertyDefinition,"Quantities"):
|
|
propset.extend(p.RelatingPropertyDefinition.Quantities)
|
|
for prop in propset:
|
|
if prop.Name == propName:
|
|
print "found valid",prop
|
|
if hasattr(prop,"LengthValue"):
|
|
return prop.LengthValue
|
|
elif hasattr(prop,"AreaValue"):
|
|
return prop.AreaValue
|
|
elif hasattr(prop,"VolumeValue"):
|
|
return prop.VolumeValue
|
|
elif hasattr(prop,"NominalValue"):
|
|
return prop.NominalValue
|
|
return None
|
|
|
|
def getAttribute(self,attr):
|
|
"returns the value of the given attribute, if exists"
|
|
if hasattr(self,attr):
|
|
return self.__dict__[attr]
|
|
return None
|
|
|
|
class IfcDocument:
|
|
"an object representing an IFC document"
|
|
def __init__(self,filename,schema="IFC2X3_TC1.exp"):
|
|
f = IfcFile(filename,schema)
|
|
self.filename = filename
|
|
self.data = f.entById
|
|
self.Entities = {0:f.header}
|
|
for k,e in self.data.iteritems():
|
|
eid = int(e['id'])
|
|
self.Entities[eid] = IfcEntity(e,self)
|
|
if DEBUG: print len(self.Entities),"entities created. Creating attributes..."
|
|
for k,ent in self.Entities.iteritems():
|
|
if DEBUG: print "attributing entity ",ent
|
|
if hasattr(ent,"attributes"):
|
|
for k,v in ent.attributes.iteritems():
|
|
if DEBUG: print "parsing attribute: ",k," value ",v
|
|
if isinstance(v,str):
|
|
val = self.__clean__(v)
|
|
elif isinstance(v,list):
|
|
val = []
|
|
for item in v:
|
|
if isinstance(item,str):
|
|
val.append(self.__clean__(item))
|
|
else:
|
|
val.append(item)
|
|
else:
|
|
val = v
|
|
setattr(ent,k.strip(),val)
|
|
if DEBUG: print "Document successfully created"
|
|
|
|
def __clean__(self,value):
|
|
"turns an attribute value into something usable"
|
|
try:
|
|
val = value.strip(" ()'")
|
|
if val[:3].upper() == "IFC":
|
|
if "IFCTEXT" in val.upper():
|
|
l = val.split("'")
|
|
if len(l) == 3: val = l[1]
|
|
elif "IFCBOOLEAN" in value.upper():
|
|
l = val.split(".")
|
|
if len(l) == 3: val = l[1]
|
|
if val.upper() == "F": val = False
|
|
elif val.upper() == "T": val = True
|
|
elif "IFCREAL" in val.upper():
|
|
l = val.split("(")
|
|
if len(l) == 2: val = float(l[1].strip(")"))
|
|
else:
|
|
if '#' in val:
|
|
if "," in val:
|
|
val = val.split(",")
|
|
l = []
|
|
for subval in val:
|
|
if '#' in subval:
|
|
s = subval.strip(" #")
|
|
if DEBUG: print "referencing ",s," : ",self.getEnt(int(s))
|
|
l.append(self.getEnt(int(s)))
|
|
val = l
|
|
else:
|
|
val = val.strip()
|
|
val = val.replace("#","")
|
|
if DEBUG: print "referencing ",val," : ",self.getEnt(int(val))
|
|
val = self.getEnt(int(val))
|
|
if not val:
|
|
val = value
|
|
except:
|
|
if DEBUG: print "error parsing attribute",value
|
|
val = value
|
|
return val
|
|
|
|
def __repr__(self):
|
|
return "IFC Document: " + self.filename + ', ' + str(len(self.Entities)) + " entities "
|
|
|
|
def getEnt(self,ref):
|
|
"gets an entity by id number, or a list of entities by type"
|
|
if isinstance(ref,int):
|
|
if ref in self.Entities:
|
|
return self.Entities[ref]
|
|
elif isinstance(ref,str):
|
|
l = []
|
|
ref = ref.upper()
|
|
for k,ob in self.Entities.iteritems():
|
|
if hasattr(ob,"type"):
|
|
if ob.type == ref:
|
|
l.append(ob)
|
|
return l
|
|
return None
|
|
|
|
def search(self,pat):
|
|
"searches entities types for partial match"
|
|
l = []
|
|
pat = pat.upper()
|
|
for k,ob in self.Entities.iteritems():
|
|
if hasattr(ob,"type"):
|
|
if pat in ob.type:
|
|
if not ob.type in l:
|
|
l.append(ob.type)
|
|
return l
|
|
|
|
def find(self,pat1,pat2=None,pat3=None):
|
|
'''finds objects in the current IFC document.
|
|
arguments can be of the following form:
|
|
- (pattern): returns object types matching the given pattern (same as search)
|
|
- (type,property,value): finds, in all objects of type "type", those whose
|
|
property "property" has the given value
|
|
'''
|
|
if pat3:
|
|
bobs = self.getEnt(pat1)
|
|
obs = []
|
|
for bob in bobs:
|
|
if hasattr(bob,pat2):
|
|
if bob.getAttribute(pat2) == pat3:
|
|
obs.append(bob)
|
|
return obs
|
|
elif pat1:
|
|
ll = self.search(pat1)
|
|
obs = []
|
|
for l in ll:
|
|
obs.extend(self.getEnt(l))
|
|
return obs
|
|
return None
|
|
|
|
def explorer(filename,schema="IFC2X3_TC1.exp"):
|
|
"returns a PySide dialog showing the contents of an IFC file"
|
|
from PySide import QtCore,QtGui
|
|
ifc = IfcDocument(filename,schema)
|
|
schema = IfcSchema(schema)
|
|
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)
|
|
|
|
#print ifc.Entities
|
|
|
|
for i in ifc.Entities.keys():
|
|
e = ifc.Entities[i]
|
|
item = QtGui.QTreeWidgetItem(tree)
|
|
if hasattr(e,"id"):
|
|
item.setText(0,str(e.id))
|
|
if e.type in ["IFCWALL","IFCWALLSTANDARDCASE"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Wall_Tree.svg"))
|
|
elif e.type in ["IFCCOLUMN","IFCBEAM","IFCSLAB","IFCFOOTING"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Structure_Tree.svg"))
|
|
elif e.type in ["IFCSITE"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Site_Tree.svg"))
|
|
elif e.type in ["IFCBUILDING"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Building_Tree.svg"))
|
|
elif e.type in ["IFCSTOREY"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Floor_Tree.svg"))
|
|
elif e.type in ["IFCWINDOW"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Window_Tree.svg"))
|
|
elif e.type in ["IFCROOF"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Arch_Roof_Tree.svg"))
|
|
elif e.type in ["IFCEXTRUDEDAREASOLID","IFCCLOSEDSHELL"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Tree_Part.svg"))
|
|
elif e.type in ["IFCFACE"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Draft_SwitchMode.svg"))
|
|
elif e.type in ["IFCARBITRARYCLOSEDPROFILEDEF","IFCPOLYLOOP"]:
|
|
item.setIcon(1,QtGui.QIcon(":icons/Draft_Draft.svg"))
|
|
item.setText(2,str(schema.capitalize(e.type)))
|
|
item.setFont(2,bold);
|
|
for a in e.attributes.keys():
|
|
if hasattr(e,a):
|
|
if not a.upper() in ["ID", "GLOBALID"]:
|
|
v = getattr(e,a)
|
|
if isinstance(v,IfcEntity):
|
|
t = "Entity #" + str(v.id) + ": " + str(v.type)
|
|
elif isinstance(v,list):
|
|
t = ""
|
|
else:
|
|
t = str(v)
|
|
t = " " + str(a) + " : " + str(t)
|
|
item = QtGui.QTreeWidgetItem(tree)
|
|
item.setText(2,str(t))
|
|
if isinstance(v,list):
|
|
for vi in v:
|
|
if isinstance(vi,IfcEntity):
|
|
t = "Entity #" + str(vi.id) + ": " + str(vi.type)
|
|
else:
|
|
t = vi
|
|
t = " " + str(t)
|
|
item = QtGui.QTreeWidgetItem(tree)
|
|
item.setText(2,str(t))
|
|
|
|
d = QtGui.QDialog()
|
|
d.setObjectName("IfcExplorer")
|
|
d.setWindowTitle("Ifc Explorer")
|
|
d.resize(640, 480)
|
|
layout = QtGui.QVBoxLayout(d)
|
|
layout.addWidget(tree)
|
|
return d
|
|
|
|
# IfcWriter ########################################
|
|
|
|
class _tempEntityHolder:
|
|
"""a temporary object to store entity references
|
|
to be made into something nicer later..."""
|
|
def __init__(self):
|
|
self.refs = []
|
|
|
|
holder = _tempEntityHolder()
|
|
|
|
def uid():
|
|
"""returns a suitable GlobalID"""
|
|
u = str(uuid.uuid4())[:22]
|
|
u = u.replace("-","_")
|
|
return u
|
|
|
|
def now(string=False):
|
|
"returns a suitable Ifc Time"
|
|
if string:
|
|
return time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
|
|
else:
|
|
return int(time.time())
|
|
|
|
def getPropertyNames(entity):
|
|
"""getPropertyNames(entity): Returns a dictionary with
|
|
the numbers and names of the pythonproperties available for
|
|
this entity"""
|
|
ents = {}
|
|
if hasattr(entity,"get_argument_count"):
|
|
l = entity.get_argument_count()
|
|
else:
|
|
l = len(entity)
|
|
for i in range(l):
|
|
ents[i] = entity.get_argument_name(i)
|
|
return ents
|
|
|
|
def getTuple(vec):
|
|
"""getTuple(vec): returns a tuple from other coordinate
|
|
structures: tuple, list, 3d vector, or occ vertex"""
|
|
def fmt(t):
|
|
t = float(t)
|
|
t = round(t,PRECISION)
|
|
return t
|
|
if isinstance(vec,tuple):
|
|
return tuple([fmt(v) for v in vec])
|
|
elif isinstance(vec,list):
|
|
return tuple([fmt(v) for v in vec])
|
|
elif hasattr(vec,"x") and hasattr(vec,"y") and hasattr(vec,"z"):
|
|
return (fmt(vec.x),fmt(vec.y),fmt(vec.z))
|
|
elif hasattr(vec,"X") and hasattr(vec,"Y") and hasattr(vec,"Z"):
|
|
return (fmt(vec.X),fmt(vec.Y),fmt(vec.Z))
|
|
|
|
def getValueAndDirection(vec):
|
|
"""getValueAndDirection(vec): returns a length and a tuple
|
|
representing a normalized vector from a tuple"""
|
|
vec = getTuple(vec)
|
|
length = round(math.sqrt(vec[0]**2 + vec[1]**2 + vec[2]**2),PRECISION)
|
|
ratio = 1/length
|
|
x = round(vec[0]*ratio,PRECISION)
|
|
y = round(vec[1]*ratio,PRECISION)
|
|
z = round(vec[2]*ratio,PRECISION)
|
|
normal = (x,y,z)
|
|
return length,normal
|
|
|
|
def create(ifcdoc=None,ifcname=None,arguments=[]):
|
|
"""create(ifcdoc,ifcname,[arguments]):creates an entity
|
|
of the given name in the given document and optionally
|
|
gives it an ordered list of arguments"""
|
|
if hasattr(ifcw,"Entity"):
|
|
entity = ifcw.Entity(ifcname)
|
|
else:
|
|
entity = ifcw.entity_instance(ifcname)
|
|
if ifcdoc:
|
|
ifcdoc.add(entity)
|
|
# this is a temporary hack while ifcopenshell has no ref counting
|
|
holder.refs.append(entity)
|
|
if not isinstance(arguments,list):
|
|
arguments = [arguments]
|
|
for i in range(len(arguments)):
|
|
arg = arguments[i]
|
|
if isinstance(arg,tuple):
|
|
if len(arg) in [2,3]:
|
|
if hasattr(ifcw,"Doubles"):
|
|
arg = ifcw.Doubles(arg)
|
|
else:
|
|
arg = ifcw.doubles(arg)
|
|
entity.set_argument(i,arg)
|
|
return entity
|
|
|
|
|
|
class IfcWriter(object):
|
|
"""IfcWriter([filepath,name,owner,organization,application,version])
|
|
Creates an empty IFC document."""
|
|
|
|
def __init__(self,filepath="",name="",owner="",organization="",application="Python IFC exporter",version="0.0"):
|
|
if hasattr(ifcw,"IfcFile"):
|
|
self._fileobject = ifcw.IfcFile()
|
|
else:
|
|
self._fileobject = ifcw.file()
|
|
self._person = create(self._fileobject,"IfcPerson",[None,None,"",None,None,None,None,None])
|
|
self._org = create(self._fileobject,"IfcOrganization",[None,"",None,None,None])
|
|
pno = create(self._fileobject,"IfcPersonAndOrganization",[self._person,self._org,None])
|
|
app = create(self._fileobject,"IfcApplication",[self._org,version,application,uid()])
|
|
self._owner = create(self._fileobject,"IfcOwnerHistory",[pno,app,None,"ADDED",None,pno,app,now()])
|
|
axp = self.addPlacement(local=False)
|
|
dim0 = create(self._fileobject,"IfcDirection",getTuple((0,1,0)))
|
|
self._repcontext = create(self._fileobject,"IfcGeometricRepresentationContext",['Plan','Model',3,1.E-05,axp,dim0])
|
|
dim1 = create(self._fileobject,"IfcDimensionalExponents",[0,0,0,0,0,0,0])
|
|
dim2 = create(self._fileobject,"IfcSIUnit",[dim1,"LENGTHUNIT","MILLI","METRE"])
|
|
dim3 = create(self._fileobject,"IfcSIUnit",[dim1,"AREAUNIT",None,"SQUARE_METRE"])
|
|
dim4 = create(self._fileobject,"IfcSIUnit",[dim1,"VOLUMEUNIT",None,"CUBIC_METRE"])
|
|
dim6 = create(self._fileobject,"IfcSIUnit",[dim1,"PLANEANGLEUNIT",None,"RADIAN"])
|
|
dim7 = create(None,"IfcPlaneAngleMeasure",[1.745E-2])
|
|
dim8 = create(self._fileobject,"IfcMeasureWithUnit",[dim7,dim6])
|
|
dim9 = create(self._fileobject,"IfcConversionBasedUnit",[dim1,"PLANEANGLEUNIT","DEGREE",dim8])
|
|
units = create(self._fileobject,"IfcUnitAssignment",[[dim2,dim3,dim4,dim9]])
|
|
self.Project = create(self._fileobject,"IfcProject",[uid(),self._owner,None,None,None,None,None,[self._repcontext],units])
|
|
self.Site = None
|
|
self._storeyRelations = {}
|
|
self.BuildingProducts = []
|
|
self.Storeys = []
|
|
self.Buildings = []
|
|
self.FilePath = filepath
|
|
self.Owner = owner
|
|
self.Organization = organization
|
|
self.Name = name
|
|
|
|
def __repr__(self):
|
|
return "IFC document " + self.Name #+ " containing " + str(len(holder)) + " entities"
|
|
|
|
def __setattr__(self,key,value):
|
|
if value:
|
|
if key == "Owner":
|
|
self._person.set_argument(2,str(value))
|
|
elif key == "Organization":
|
|
self._org.set_argument(1,str(value))
|
|
elif key == "Name":
|
|
self.Project.set_argument(2,str(value))
|
|
self.__dict__.__setitem__(key,value)
|
|
|
|
def findByName(self,ifctype,name):
|
|
"finds an entity of a given ifctype by name"
|
|
objs = self._fileobject.by_type(ifctype)
|
|
for obj in objs:
|
|
if hasattr(obj,"get_argument_count"):
|
|
l = obj.get_argument_count()
|
|
else:
|
|
l = len(obj)
|
|
for i in range(l):
|
|
if obj.get_argument_name(i) == "Name":
|
|
if obj.get_argument(i) == name:
|
|
return obj
|
|
return None
|
|
|
|
def write(self,fp=None):
|
|
"writes the document to its file"
|
|
if fp:
|
|
path = fp
|
|
else:
|
|
path = self.FilePath
|
|
if path:
|
|
try:
|
|
self._fileobject.write(path)
|
|
if APPLYFIX:
|
|
print ("IfcWriter: Applying fix...")
|
|
self._fix(path)
|
|
except:
|
|
print ("IfcWriter: Error writing to "+path)
|
|
else:
|
|
print ("IfcWriter: Successfully written to "+path)
|
|
else:
|
|
print ("IfcWriter: Error: File path is not defined, unable to save")
|
|
|
|
def _fix(self,path):
|
|
"hack to fix early bugs in ifcopenshell"
|
|
import os
|
|
if os.path.exists(path):
|
|
f = pyopen(path,"rb")
|
|
lines = []
|
|
for l in f.readlines():
|
|
if "(=IFC" in l:
|
|
# adding an ifc entity without ID adds an unwanted = sign
|
|
l = l.replace("(=IFC","(IFC")
|
|
elif "IFCSIUNIT" in l:
|
|
# no way to insert * character
|
|
l = l.replace("IFCSIUNIT(#12,","IFCSIUNIT(*,")
|
|
lines.append(l)
|
|
f.close()
|
|
f = pyopen(path,"wb")
|
|
for l in lines:
|
|
f.write(l)
|
|
f.close()
|
|
|
|
def union(self,solids):
|
|
"""union(solids): creates a boolean union between all the solids of the list"""
|
|
if len(solids) == 1:
|
|
return solids[0]
|
|
else:
|
|
s1 = solids.pop(0)
|
|
s2 = solids.pop(0)
|
|
base = create(self._fileobject,"IfcBooleanResult",["UNION",s1,s2])
|
|
for s in solids:
|
|
base = create(self._fileobject,"IfcBooleanResult",["UNION",base,s])
|
|
return base
|
|
|
|
def addPlacement(self,reference=None,origin=(0,0,0),xaxis=(1,0,0),zaxis=(0,0,1),local=True,flat=False):
|
|
"""addPlacement([reference,origin,xaxis,zaxis,local]): adds a placement. origin,
|
|
xaxis and zaxis can be either tuples or 3d vectors. If local is False, a global
|
|
placement is returned, otherwise a local one."""
|
|
if flat:
|
|
xvc = create(self._fileobject,"IfcDirection",getTuple(xaxis)[:2])
|
|
ovc = create(self._fileobject,"IfcCartesianPoint",getTuple(origin)[:2])
|
|
gpl = create(self._fileobject,"IfcAxis2Placement2D",[ovc,xvc])
|
|
else:
|
|
xvc = create(self._fileobject,"IfcDirection",getTuple(xaxis))
|
|
zvc = create(self._fileobject,"IfcDirection",getTuple(zaxis))
|
|
ovc = create(self._fileobject,"IfcCartesianPoint",getTuple(origin))
|
|
gpl = create(self._fileobject,"IfcAxis2Placement3D",[ovc,zvc,xvc])
|
|
if local:
|
|
lpl = create(self._fileobject,"IfcLocalPlacement",[reference,gpl])
|
|
return lpl
|
|
else:
|
|
return gpl
|
|
|
|
def addSite(self,placement=None,name="Site",description=None,latitude=None,longitude=None,elevation=None,landtitlenumber=None,address=None):
|
|
"""makeSite(ifcdoc,project,owner,[placement,name,description]): creates a site
|
|
in the given ifc document"""
|
|
if self.Site:
|
|
return
|
|
if not placement:
|
|
placement = self.addPlacement()
|
|
self.Site = create(self._fileobject,"IfcSite",[uid(),self._owner,str(name),description,None,placement,None,None,"ELEMENT",latitude,longitude,elevation,landtitlenumber,address])
|
|
self._relate(self.Project,self.Site)
|
|
|
|
def addBuilding(self,placement=None,name="Default building",description=None):
|
|
"""addBuilding([placement,name,description]): adds a building"""
|
|
if not placement:
|
|
placement = self.addPlacement()
|
|
if not self.Site:
|
|
self.addSite()
|
|
bdg = create(self._fileobject,"IfcBuilding",[uid(),self._owner,str(name),description,None,placement,None,None,"ELEMENT",None,None,None])
|
|
self._relate(self.Site,bdg)
|
|
self.Buildings.append(bdg)
|
|
return bdg
|
|
|
|
def addStorey(self,building=None,placement=None,name="Default storey",description=None):
|
|
"""addStorey([building,placement,name,description]): adds a storey"""
|
|
if not placement:
|
|
placement = self.addPlacement()
|
|
sto = create(self._fileobject,"IfcBuildingStorey",[uid(),self._owner,str(name),description,None,placement,None,None,"ELEMENT",None])
|
|
if not building:
|
|
if self.Buildings:
|
|
building = self.Buildings[0]
|
|
else:
|
|
building = self.addBuilding()
|
|
self._relate(building,sto)
|
|
self.Storeys.append(sto)
|
|
return sto
|
|
|
|
def addGroup(self,entities,name="Default group",description=None):
|
|
"""addGroup(entities,[name,description]): adds a group with the given entities"""
|
|
if not isinstance(entities,list):
|
|
entities = [entities]
|
|
gro = create(self._fileobject,"IfcGroup",[uid(),self._owner,str(name),description,None])
|
|
rel = create(self._fileobject,"IfcRelAssignsToGroup",[uid(),self._owner,str(name)+"-relation",None,entities,"PRODUCT",gro])
|
|
return gro
|
|
|
|
def _relate(self,container,entities):
|
|
"""relate(container,entities): relates the given entities to the given
|
|
container"""
|
|
if not isinstance(entities,list):
|
|
entities = [entities]
|
|
if container.is_a("IfcBuildingStorey"):
|
|
sid = container.get_argument(0)
|
|
if sid in self._storeyRelations:
|
|
prods = self._storeyRelations[sid].get_argument(4)
|
|
self._storeyRelations[sid].set_argument(4,prods+entities)
|
|
else:
|
|
rel = create(self._fileobject,"IfcRelContainedInSpatialStructure",[uid(),self._owner,'StoreyLink','',entities,container])
|
|
self._storeyRelations[sid] = rel
|
|
else:
|
|
if entities[0].is_a("IfcOpeningElement"):
|
|
create(self._fileobject,"IfcRelVoidsElement",[uid(),self._owner,'Opening','',container,entities[0]])
|
|
else:
|
|
create(self._fileobject,"IfcRelAggregates",[uid(),self._owner,'Relationship','',container,entities])
|
|
|
|
def addProduct(self,elttype,shapes,storey=None,placement=None,name="Unnamed element",description=None,extra=None):
|
|
"""addProduct(elttype,representations,[storey,placement,name,description,extra]): creates an element of the given type
|
|
(IfcWall, IfcBeam, etc...) with the given attributes, plus the given extra attributes."""
|
|
elttype = str(elttype)
|
|
if not extra:
|
|
extra = []
|
|
if not description:
|
|
description = None
|
|
if not placement:
|
|
placement = self.addPlacement()
|
|
representations = self.addRepresentations(shapes)
|
|
prd = create(self._fileobject,"IfcProductDefinitionShape",[None,None,representations])
|
|
try:
|
|
elt = create(self._fileobject,elttype,[uid(),self._owner,name,description,None,placement,prd,None]+extra)
|
|
except:
|
|
print "unable to create an ",elttype, " with attributes: ",[uid(),self._owner,str(name),description,None,placement,prd,None]+extra
|
|
try:
|
|
if hasattr(ifcw,"Entity"):
|
|
o = ifcw.Entity(elttype)
|
|
else:
|
|
o = ifcw.entity_instance(elttype)
|
|
print "supported attributes are: "
|
|
print getPropertyNames(o)
|
|
except:
|
|
print "unable to create an element of type '"+elttype+"'"
|
|
print "WARNING: skipping object '"+name+"' of type "+elttype
|
|
return None
|
|
self.BuildingProducts.append(elt)
|
|
if not storey:
|
|
if self.Storeys:
|
|
storey = self.Storeys[0]
|
|
else:
|
|
storey = self.addStorey()
|
|
self._relate(storey,elt)
|
|
return elt
|
|
|
|
def addRepresentations(self,shapes):
|
|
"""addRepresentations(shapes,[solidType]): creates a representation from the given shape"""
|
|
solidType = "Brep"
|
|
if not isinstance(shapes,list):
|
|
if shapes.is_a("IfcExtrudedAreaSolid"):
|
|
solidType = "SweptSolid"
|
|
shapes = [shapes]
|
|
reps = [create(self._fileobject,"IfcShapeRepresentation",[self._repcontext,'Body',solidType,[shape for shape in shapes]])]
|
|
return reps
|
|
|
|
def addColor(self,rgb,rep):
|
|
"""addColor(rgb,rep): adds a RGB color definition tuple (float,float,float) to a given representation"""
|
|
col = create(self._fileobject,"IfcColourRgb",[None]+list(rgb))
|
|
ssr = create(self._fileobject,"IfcSurfaceStyleRendering",[col,None,None,None,None,None,None,None,"FLAT"])
|
|
iss = create(self._fileobject,"IfcSurfaceStyle",[None,"BOTH",[ssr]])
|
|
psa = create(self._fileobject,"IfcPresentationStyleAssignment",[[iss]])
|
|
isi = create(self._fileobject,"IfcStyledItem",[rep,[psa],None])
|
|
return isi
|
|
|
|
def addProfile(self,ifctype,data,curvetype="AREA"):
|
|
"""addProfile(ifctype,data): creates a 2D profile of the given type, with the given
|
|
data as arguments, which must be formatted correctly according to the type."""
|
|
|
|
# Expected ifctype and corresponding data formatting:
|
|
# IfcPolyLine: [ (0,0,0), (2,1,0), (3,3,0) ] # list of points
|
|
# IfcCompositeCurve: [ ["line",[ (0,0,0), (2,1,0) ] ], # list of points
|
|
# ["arc", (0,0,0), 15, [0.76, 3.1416], True, "PARAMETER"] # center, radius, [trim1, trim2], SameSense, trimtype
|
|
# ... ]
|
|
# IfcCircleProfileDef: [ (0,0,0), 15 ] # center, radius
|
|
# IfcEllipseProfileDef: [ (0,0,0), 15, 7 ] # center, radiusX, radiusY
|
|
|
|
if ifctype == "IfcPolyline":
|
|
pts = [create(self._fileobject,"IfcCartesianPoint",getTuple(p)[:2]) for p in data]
|
|
pol = create(self._fileobject,"IfcPolyline",[pts])
|
|
profile = create(self._fileobject,"IfcArbitraryClosedProfileDef",[curvetype,None,pol])
|
|
elif ifctype == "IfcCompositeCurve":
|
|
curves = []
|
|
for curve in data:
|
|
cur = None
|
|
if curve[0] == "line":
|
|
pts = [create(self._fileobject,"IfcCartesianPoint",getTuple(p)[:2]) for p in curve[1]]
|
|
cur = create(self._fileobject,"IfcPolyline",[pts])
|
|
elif curve[0] == "arc":
|
|
pla = self.addPlacement(origin=curve[1],local=False,flat=True)
|
|
cir = create(self._fileobject,"IfcCircle",[pla,curve[2]])
|
|
if curve[5] == "CARTESIAN":
|
|
# BUGGY! Impossible to add cartesian points as "embedded" entity
|
|
trim1 = create(None,"IfcCartesianPoint",getTuple(curve[3][0])[:2])
|
|
trim2 = create(None,"IfcCartesianPoint",getTuple(curve[3][1])[:2])
|
|
else:
|
|
trim1 = create(None,"IfcParameterValue",[curve[3][0]])
|
|
trim2 = create(None,"IfcParameterValue",[curve[3][1]])
|
|
cur = create(self._fileobject,"IfcTrimmedCurve",[cir,[trim1],[trim2],curve[4],curve[5]])
|
|
if cur:
|
|
seg = create(self._fileobject,"IfcCompositeCurveSegment",["CONTINUOUS",True,cur])
|
|
curves.append(seg)
|
|
ccu = create(self._fileobject,"IfcCompositeCurve",[curves,False])
|
|
profile = create(self._fileobject,"IfcArbitraryClosedProfileDef",[curvetype,None,ccu])
|
|
else:
|
|
if not isinstance(data,list):
|
|
data = [data]
|
|
p = self.addPlacement(local=False,flat=True)
|
|
profile = create(self._fileobject,ifctype,[curvetype,None,p]+data)
|
|
return profile
|
|
|
|
def addExtrusion(self,profile,extrusion,placement=None):
|
|
"""addExtrusion(profile,extrusion,[placement]): makes an
|
|
extrusion of the given polyline with the given extrusion vector"""
|
|
if not placement:
|
|
placement = self.addPlacement(local=False)
|
|
value,norm = getValueAndDirection(extrusion)
|
|
edir = create(self._fileobject,"IfcDirection",[norm])
|
|
solid = create(self._fileobject,"IfcExtrudedAreaSolid",[profile,placement,edir,value])
|
|
return solid
|
|
|
|
def addExtrudedPolyline(self,points,extrusion,placement=None,color=None):
|
|
"""addExtrudedPolyline(points,extrusion,[placement,color]): makes an extruded polyline
|
|
from the given points and the given extrusion vector"""
|
|
pol = self.addProfile("IfcPolyline",points)
|
|
if not placement:
|
|
placement = self.addPlacement(local=False)
|
|
exp = self.addExtrusion(pol,extrusion,placement)
|
|
if color:
|
|
self.addColor(color,exp)
|
|
return exp
|
|
|
|
def addExtrudedCircle(self,data,extrusion,placement=None,color=None):
|
|
"""addExtrudedCircle(data,extrusion,[placement,color]): makes an extruded circle
|
|
from the given data (center,radius) and the given extrusion vector"""
|
|
cir = self.addProfile("IfcCircleProfileDef",data[1])
|
|
if not placement:
|
|
placement = self.addPlacement(origin=data[0],local=False)
|
|
exp = self.addExtrusion(cir,extrusion,placement)
|
|
if color:
|
|
self.addColor(color,exp)
|
|
return exp
|
|
|
|
def addExtrudedEllipse(self,data,extrusion,placement=None,color=None):
|
|
"""addExtrudedEllipse(data,extrusion,[placement,color]): makes an extruded ellipse
|
|
from the given data (center,radiusx,radiusy) and the given extrusion vector"""
|
|
cir = self.addProfile("IfcEllipseProfileDef",[data[1],data[2]])
|
|
if not placement:
|
|
placement = self.addPlacement(origin=data[0],local=False)
|
|
exp = self.addExtrusion(cir,extrusion,placement)
|
|
if color:
|
|
self.addColor(color,exp)
|
|
return exp
|
|
|
|
def addExtrudedCompositeCurve(self,curves,extrusion,placement=None,color=None):
|
|
"""addExtrudedCompositeCurve(curves,extrusion,[placement,color]): makes an extruded polyline
|
|
from the given curves and the given extrusion vector"""
|
|
if not placement:
|
|
placement = self.addPlacement(local=False)
|
|
ccu = self.addProfile("IfcCompositeCurve",curves)
|
|
exp = self.addExtrusion(ccu,extrusion,placement)
|
|
if color:
|
|
self.addColor(color,exp)
|
|
return exp
|
|
|
|
def addFace(self,face):
|
|
"""addFace(face): creates a face from the given face data (a list of lists of points).
|
|
The first is the outer wire, the next are optional inner wires. They must be reversed in order"""
|
|
ifb = []
|
|
idx = 0
|
|
for f in face:
|
|
pts = []
|
|
for p in f:
|
|
#print p
|
|
if p in self.fpoints:
|
|
#print self.fpoints.index(p)
|
|
#print self.frefs
|
|
pts.append(self.frefs[self.fpoints.index(p)])
|
|
else:
|
|
pt = create(self._fileobject,"IfcCartesianPoint",getTuple(p))
|
|
pts.append(pt)
|
|
self.fpoints.append(p)
|
|
self.frefs.append(pt)
|
|
#print pts
|
|
loop = create(self._fileobject,"IfcPolyLoop",[pts])
|
|
if idx == 0:
|
|
fb = create(self._fileobject,"IfcFaceOuterBound",[loop,True])
|
|
else:
|
|
fb = create(self._fileobject,"IfcFaceBound",[loop,True])
|
|
ifb.append(fb)
|
|
idx += 1
|
|
iface = create(self._fileobject,"IfcFace",[ifb])
|
|
return iface
|
|
|
|
def addFacetedBrep(self,faces,color=None):
|
|
"""addFacetedBrep(self,faces,[color]): creates a faceted brep object from the given list
|
|
of faces (each face is a list of lists of points, inner wires are reversed)"""
|
|
self.fpoints = []
|
|
self.frefs = []
|
|
#print "adding ",len(faces)," faces"
|
|
#print faces
|
|
ifaces = [self.addFace(face) for face in faces]
|
|
sh = create(self._fileobject,"IfcClosedShell",[ifaces])
|
|
brp = create(self._fileobject,"IfcFacetedBrep",[sh])
|
|
if color:
|
|
self.addColor(color,brp)
|
|
return brp
|