Arch: Misc improvements for IFC workflow

* Added utility to make non-parametric Arch component
* Ability for all Arch components to be a clone of another Arch component of same type
* Modified the Draft Clone tool to produce Arch Clones if applicable
* Fixed Arch Roofs so they can be based on a solid shape like other Arch objects
* Ability to change the Root element to be imported in IFC preferences
* Ability to import IFC files also as compounds, Part shapes or non-parametric Arch objects
* Added an "only" parameter to importIFC.open() to import only a certain object ID.
* Ability to read colors (IfcSurfaceStyle) from IFC objects
This commit is contained in:
Yorik van Havre
2015-04-08 12:34:48 -03:00
parent 3baafccb60
commit 86cf572f34
18 changed files with 579 additions and 98 deletions

View File

@@ -246,24 +246,36 @@ def explore(filename=None):
return
def open(filename,skip=[]):
def open(filename,skip=[],only=[],root=None):
"opens an IFC file in a new document"
docname = os.path.splitext(os.path.basename(filename))[0]
doc = FreeCAD.newDocument(docname)
doc.Label = docname
doc = insert(filename,doc.Name,skip)
doc = insert(filename,doc.Name,skip,only,root)
return doc
def insert(filename,docname,skip=[]):
"imports the contents of an IFC file"
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)."""
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
DEBUG = p.GetBool("ifcDebug",False)
PREFIX_NUMBERS = p.GetBool("ifcPrefixNumbers",False)
SKIP = p.GetString("ifcSkip","")
SKIP = p.GetString("ifcSkip","").split(",")
SEPARATE_OPENINGS = p.GetBool("ifcSeparateOpenings",False)
ROOT_ELEMENT = p.GetString("ifcRootElement","IfcProduct")
if root:
ROOT_ELEMENT = root
MERGE_MODE = p.GetInt("ifcImportMode",0)
if MERGE_MODE > 0:
SEPARATE_OPENINGS = False
READ_COLORS = p.GetBool("ifcReadColors",True)
if not SEPARATE_OPENINGS:
SKIP.append("IfcOpeningElement")
try:
import ifcopenshell
@@ -271,13 +283,15 @@ def insert(filename,docname,skip=[]):
FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n")
return
if DEBUG: print "opening ",filename,"..."
if DEBUG: print "Opening ",filename,"...",
try:
doc = FreeCAD.getDocument(docname)
except:
doc = FreeCAD.newDocument(docname)
FreeCAD.ActiveDocument = doc
if DEBUG: print "done."
global ifcfile # keeping global for debugging purposes
if isinstance(filename,unicode):
import sys #workaround since ifcopenshell currently can't handle unicode filenames
@@ -293,15 +307,19 @@ def insert(filename,docname,skip=[]):
sites = ifcfile.by_type("IfcSite")
buildings = ifcfile.by_type("IfcBuilding")
floors = ifcfile.by_type("IfcBuildingStorey")
products = ifcfile.by_type("IfcProduct")
products = ifcfile.by_type(ROOT_ELEMENT)
openings = ifcfile.by_type("IfcOpeningElement")
annotations = ifcfile.by_type("IfcAnnotation")
if DEBUG: print "Building relationships table...",
# building relations tables
objects = {} # { id:object, ... }
additions = {} # { host:[child,...], ... }
subtractions = [] # [ [opening,host], ... ]
properties = {} # { host:[property, ...], ... }
colors = {} # { id:(r,g,b) }
shapes = {} # { id:shaoe } only used for merge mode
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"):
@@ -312,101 +330,239 @@ def insert(filename,docname,skip=[]):
for obj in r.RelatedObjects:
if r.RelatingPropertyDefinition.is_a("IfcPropertySet"):
properties.setdefault(obj.id(),[]).extend([e.id() for e in r.RelatingPropertyDefinition.HasProperties])
if READ_COLORS:
for r in ifcfile.by_type("IfcStyledItem"):
if r.Item and 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
for p in ifcfile.by_type("IfcProduct"):
if p.Representation:
for it in p.Representation.Representations:
if it.Items:
if it.Items[0].id() == r.Item.id():
colors[p.id()] = (c.Red,c.Green,c.Blue)
elif it.Items[0].is_a("IfcBooleanResult"):
if (it.Items[0].FirstOperand.id() == r.Item.id()):
colors[p.id()] = (c.Red,c.Green,c.Blue)
if only: # only import a list of IDs and their children
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))
# products
for product in products:
pid = product.id()
guid = product.GlobalId
ptype = product.is_a()
if DEBUG: print count,"/",len(products)," creating object ",pid," : ",ptype,
name = product.Name or str(ptype[3:])
if PREFIX_NUMBERS: name = "ID" + str(pid) + " " + name
obj = None
baseobj = None
brep = None
if (ptype == "IfcOpeningElement") and (not SEPARATE_OPENINGS): continue
if pid in skip: continue # user given id skip list
if ptype in SKIP: continue # preferences-set type skip list
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
try:
cr = ifcopenshell.geom.create_shape(settings,product)
brep = cr.geometry.brep_data
except:
pass
pass # IfcOpenShell will yield an error if a given product has no shape, but we don't care
if brep:
if DEBUG: print " ",str(len(brep)/1000),"k ",
shape = Part.Shape()
shape.importBrepFromString(brep)
shape.scale(1000.0) # IfcOpenShell always outputs in meters
if not shape.isNull():
baseobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
baseobj.Shape = shape
for freecadtype,ifctypes in typesmap.iteritems():
if ptype in ifctypes:
obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name)
if MERGE_MODE > 0:
if ptype == "IfcSpace": # do not add spaces to compounds
if DEBUG: print "skipping space ",pid
else:
shapes[pid] = shape
if DEBUG: print shape.Solids
baseobj = shape
else:
baseobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body")
baseobj.Shape = shape
else:
if DEBUG: print "null shape ",
if not shape.isValid():
if DEBUG: print "invalid shape. Skipping"
continue
else:
if DEBUG: print " no brep "
if MERGE_MODE == 0:
# full Arch objects
for freecadtype,ifctypes in typesmap.items():
if ptype in ifctypes:
obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name)
obj.Label = name
# setting role
try:
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:
pass
# setting uid
if hasattr(obj,"IfcAttributes"):
a = obj.IfcAttributes
a["IfcUID"] = str(guid)
obj.IfcAttributes = a
break
if not obj:
obj = baseobj
if obj:
sols = str(baseobj.Shape.Solids) if hasattr(baseobj,"Shape") else "[]"
if DEBUG: print sols
objects[pid] = obj
elif MERGE_MODE == 1:
# non-parametric Arch objects
if ptype in ["IfcSite","IfcBuilding","IfcBuildingStorey"]:
for freecadtype,ifctypes in typesmap.items():
if ptype in ifctypes:
obj = getattr(Arch,"make"+freecadtype)(baseobj=None,name=name)
obj.Label = name
objects[pid] = obj
elif baseobj:
obj = Arch.makeComponent(baseobj,name=name)
obj.Label = name
# setting role
try:
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:
pass
# setting uid
objects[pid] = obj
elif MERGE_MODE == 2:
# Part shapes
if ptype in ["IfcSite","IfcBuilding","IfcBuildingStorey"]:
for freecadtype,ifctypes in typesmap.items():
if ptype in ifctypes:
obj = getattr(Arch,"make"+freecadtype)(baseobj=None,name=name)
obj.Label = name
objects[pid] = obj
elif baseobj:
obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
obj.Label = name
obj.Shape = shape
if obj:
# properties
if pid in properties:
if hasattr(obj,"IfcAttributes"):
a = obj.IfcAttributes
a["IfcUID"] = str(guid)
for p in properties[pid]:
o = ifcfile[p]
if o.is_a("IfcPropertySingleValue"):
a[o.Name] = str(o.NominalValue)
obj.IfcAttributes = a
break
if not obj:
obj = baseobj
if obj:
sh = baseobj.Shape.ShapeType if hasattr(baseobj,"Shape") else "None"
sols = str(baseobj.Shape.Solids) if hasattr(baseobj,"Shape") else ""
pc = str(int((float(count)/(len(products)+len(annotations))*100)))+"% "
if DEBUG: print pc,"creating object ",pid," : ",ptype, " with shape: ",sh," ",sols
objects[pid] = obj
# properties
if pid in properties:
if hasattr(obj,"IfcAttributes"):
a = obj.IfcAttributes
for p in properties[pid]:
o = ifcfile[p]
if o.is_a("IfcPropertySingleValue"):
a[o.Name] = str(o.NominalValue)
obj.IfcAttributes = a
# color
if FreeCAD.GuiUp and (pid in colors):
if DEBUG: print " setting color: ",colors[pid]
obj.ViewObject.ShapeColor = colors[pid]
# if DEBUG is on, recompute after each shape
if DEBUG: FreeCAD.ActiveDocument.recompute()
count += 1
progressbar.next()
progressbar.stop()
FreeCAD.ActiveDocument.recompute()
if DEBUG: print "Processing relationships..."
if MERGE_MODE == 3:
if DEBUG: print "Joining shapes..."
for host,children in additions.items():
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 shapes
obj = FreeCAD.ActiveDocument.addObject("Part::Feature","Unclaimed")
obj.Shape = Part.makeCompound(shapes.values())
# subtractions
if SEPARATE_OPENINGS:
for subtraction in subtractions:
if (subtraction[0] in objects.keys()) and (subtraction[1] in objects.keys()):
#print objects[subtraction[0]].Name, objects[subtraction[1]].Name
Arch.removeComponents(objects[subtraction[0]],objects[subtraction[1]])
# additions
for host,children in additions.iteritems():
if host in objects.keys():
cobs = [objects[child] for child in children if child in objects.keys()]
if cobs:
Arch.addComponents(cobs,objects[host])
FreeCAD.ActiveDocument.recompute()
else:
# cleaning bad shapes
for obj in objects.values():
if obj.isDerivedFrom("Part::Feature"):
if obj.Shape.isNull():
Arch.rebuildArchShape(obj)
if DEBUG: print "Processing relationships..."
# subtractions
if SEPARATE_OPENINGS:
for subtraction in subtractions:
if (subtraction[0] in objects.keys()) and (subtraction[1] in objects.keys()):
if DEBUG: print "subtracting ",objects[subtraction[0]].Name, " from ", objects[subtraction[1]].Name
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 = [objects[child] for child in children if child in objects.keys()]
if cobs:
if DEBUG and (len(cobs) > 10) and ( not(Draft.getType(objects[host]) in ["Site","Building","Floor"])):
# avoid huge fusions
print "more than 10 shapes to add: skipping."
else:
if DEBUG: print "adding ",cobs, " to ", objects[host].Name
Arch.addComponents(cobs,objects[host])
if DEBUG: FreeCAD.ActiveDocument.recompute()
FreeCAD.ActiveDocument.recompute()
if DEBUG: print "Cleaning..."
# cleaning bad shapes
for obj in objects.values():
if obj.isDerivedFrom("Part::Feature"):
if obj.Shape.isNull():
Arch.rebuildArchShape(obj)
FreeCAD.ActiveDocument.recompute()
@@ -433,10 +589,11 @@ def insert(filename,docname,skip=[]):
count += 1
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
import FreeCADGui
FreeCADGui.SendMsgToActiveView("ViewFit")
print "Finished importing."
return doc
@@ -552,7 +709,7 @@ def export(exportList,filename):
elif ifctype == "IfcBuilding":
args = args + ["ELEMENT",None,None,None]
elif ifctype == "IfcBuildingStorey":
args = args + ["ELEMENT",None]
args = args + ["ELEMENT",obj.Placement.Base.z]
# creating the product
product = getattr(ifcfile,"create"+ifctype)(*args)
@@ -580,22 +737,26 @@ def export(exportList,filename):
for key in obj.IfcAttributes:
if not (key in ["IfcUID","FlagForceBrep"]):
r = obj.IfcAttributes[key].strip(")").split("(")
tp = r[0]
val = "(".join(r[1:])
val = val.strip("'")
val = val.strip('"')
if DEBUG: print " property ",key," : ",str(val), " (", str(tp), ")"
if tp in ["IfcLabel","IfcText","IfcIdentifier"]:
val = str(val)
elif tp == "IfcBoolean":
if val == ".T.":
val = True
else:
val = False
elif tp == "IfcInteger":
val = int(val)
if len(r) == 1:
tp = "IfcText"
val = r[0]
else:
val = float(val)
tp = r[0]
val = "(".join(r[1:])
val = val.strip("'")
val = val.strip('"')
if DEBUG: print " property ",key," : ",str(val), " (", str(tp), ")"
if tp in ["IfcLabel","IfcText","IfcIdentifier"]:
val = str(val)
elif tp == "IfcBoolean":
if val == ".T.":
val = True
else:
val = False
elif tp == "IfcInteger":
val = int(val)
else:
val = float(val)
props.append(ifcfile.createIfcPropertySingleValue(str(key),None,ifcfile.create_entity(str(tp),val),None))
if props:
pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'PropertySet',None,props)
@@ -781,8 +942,11 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
for fcface in fcsolid.Faces:
for e in fcface.Edges:
if not isinstance(e.Curve,Part.Line):
curves = True
if e.curvatureAt(e.FirstParameter+(e.LastParameter-e.FirstParameter)/2) > 0.0001:
curves = True
break
if curves:
shapetype = "triangulated"
tris = fcsolid.tessellate(tessellation)
for tri in tris[1]:
pts = [ifcfile.createIfcCartesianPoint(tuple(tris[0][i])) for i in tri]
@@ -791,6 +955,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
face = ifcfile.createIfcFace([bound])
faces.append(face)
else:
shapetype = "brep"
for fcface in fcsolid.Faces:
loops = []
verts = [v.Point for v in Part.Wire(DraftGeomUtils.sortEdges(fcface.OuterWire.Edges)).Vertexes]
@@ -820,7 +985,6 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess
shell = ifcfile.createIfcClosedShell(faces)
shape = ifcfile.createIfcFacetedBrep(shell)
shapes.append(shape)
shapetype = "brep"
if shapes: