From 40f0927705aa9f89b261f928b47f6ce555088b58 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Sat, 10 Jan 2015 19:30:36 -0200 Subject: [PATCH] Arch: Adapted IFC importer for new IfcOpenShell6 Squashed following commits: * Arch: Adapted IFC importer for IfcOpenShell6 * Arch: further fixes for ifcopenshell6 compatibility * Arch: IFC importer now converts to/from meters for better compatibility with IfcOpenShell * Arch: further fixes with ifcopenshell6 * Arch: precision adjustments in IFC exporter * Arch: small fix in makeRoof --- src/Mod/Arch/ArchComponent.py | 9 +- src/Mod/Arch/ArchRoof.py | 78 ++++++------ src/Mod/Arch/ArchSpace.py | 2 + src/Mod/Arch/Resources/ui/archprefs-import.ui | 36 ------ src/Mod/Arch/importIFC.py | 117 +++++++++++------- 5 files changed, 118 insertions(+), 124 deletions(-) diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index baaf7db33..93adf7ac0 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -26,10 +26,10 @@ __author__ = "Yorik van Havre" __url__ = "http://www.freecadweb.org" # Possible roles for IFC objects -Roles = ['Undefined','Beam','Beam Standard Case','Chimney','Column','Column Standard Case','Covering','Curtain Wall', - 'Door','Door Standard Case','Foundation','Furniture','Hydro Equipment','Electric Equipment', - 'Member','Plate','Railing','Ramp','Ramp Flight','Rebar','Pile','Roof','Shading Device','Slab','Space' - 'Stair','Stair Flight','Tendon','Wall','Wall Standard Case','Wall Layer','Window','Window Standard Case'] +Roles = ['Undefined','Beam','Chimney','Column','Covering','Curtain Wall', + 'Door','Foundation','Furniture','Hydro Equipment','Electric Equipment', + 'Member','Plate','Railing','Ramp','Ramp Flight','Rebar','Pile','Roof','Shading Device','Slab','Space', + 'Stair','Stair Flight','Tendon','Wall','Wall Layer','Window'] import FreeCAD,Draft from FreeCAD import Vector @@ -301,6 +301,7 @@ class Component: self.Type = "Component" self.Subvolume = None self.MoveWithHost = False + obj.Role = Roles def execute(self,obj): return diff --git a/src/Mod/Arch/ArchRoof.py b/src/Mod/Arch/ArchRoof.py index c3da4e5f4..75b45a3db 100644 --- a/src/Mod/Arch/ArchRoof.py +++ b/src/Mod/Arch/ArchRoof.py @@ -47,45 +47,45 @@ def makeRoof(baseobj=None,facenr=1, angles=[45.,], run = [], idrel = [0,],thickn _ViewProviderRoof(obj.ViewObject) if baseobj: obj.Base = baseobj - if obj.Base.isDerivedFrom("Part::Feature"): - if (obj.Base.Shape.Faces and obj.Face): - w = obj.Base.Shape.Faces[obj.Face-1].Wires[0] - elif obj.Base.Shape.Wires: - w = obj.Base.Shape.Wires[0] - if w: - if w.isClosed(): - edges = DraftGeomUtils.sortEdges(w.Edges) - l = len(edges) - - la = len(angles) - alist = angles - for i in range(l-la): - alist.append(angles[0]) - obj.Angles=alist - - lr = len(run) - rlist = run - for i in range(l-lr): - rlist.append(w.Edges[i].Length/2.) - obj.Runs = rlist - - lidrel = len(idrel) - rellist = idrel - for i in range(l-lidrel): - rellist.append(0) - obj.IdRel = rellist - - lthick = len(thickness) - tlist = thickness - for i in range(l-lthick): - tlist.append(thickness[0]) - obj.Thickness = tlist - - lover = len(overhang) - olist = overhang - for i in range(l-lover): - olist.append(overhang[0]) - obj.Overhang = olist + if obj.Base.isDerivedFrom("Part::Feature"): + if (obj.Base.Shape.Faces and obj.Face): + w = obj.Base.Shape.Faces[obj.Face-1].Wires[0] + elif obj.Base.Shape.Wires: + w = obj.Base.Shape.Wires[0] + if w: + if w.isClosed(): + edges = DraftGeomUtils.sortEdges(w.Edges) + l = len(edges) + + la = len(angles) + alist = angles + for i in range(l-la): + alist.append(angles[0]) + obj.Angles=alist + + lr = len(run) + rlist = run + for i in range(l-lr): + rlist.append(w.Edges[i].Length/2.) + obj.Runs = rlist + + lidrel = len(idrel) + rellist = idrel + for i in range(l-lidrel): + rellist.append(0) + obj.IdRel = rellist + + lthick = len(thickness) + tlist = thickness + for i in range(l-lthick): + tlist.append(thickness[0]) + obj.Thickness = tlist + + lover = len(overhang) + olist = overhang + for i in range(l-lover): + olist.append(overhang[0]) + obj.Overhang = olist obj.Face = facenr return obj diff --git a/src/Mod/Arch/ArchSpace.py b/src/Mod/Arch/ArchSpace.py index e35922b89..b71fc2d37 100644 --- a/src/Mod/Arch/ArchSpace.py +++ b/src/Mod/Arch/ArchSpace.py @@ -214,6 +214,8 @@ class _Space(ArchComponent.Component): def getArea(self,obj): "returns the horizontal area at the center of the space" import Part,DraftGeomUtils + if not hasattr(obj.Shape,"CenterOfMass"): + return 0 try: pl = Part.makePlane(1,1) pl.translate(obj.Shape.CenterOfMass) diff --git a/src/Mod/Arch/Resources/ui/archprefs-import.ui b/src/Mod/Arch/Resources/ui/archprefs-import.ui index ad68a4968..6acafcee4 100644 --- a/src/Mod/Arch/Resources/ui/archprefs-import.ui +++ b/src/Mod/Arch/Resources/ui/archprefs-import.ui @@ -116,42 +116,6 @@ - - - - - - A scaling factor to apply to imported IFC objects - - - Scaling factor - - - - - - - IFC files are typically written in millimeters. If you are working in a different unit, this allow you to scale all your units to be expressed in millimeters. - - - 4 - - - 99999.000000000000000 - - - 1.000000000000000 - - - IfcScalingFactor - - - Mod/Arch - - - - - diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index 52fc9eed1..71021a739 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -30,6 +30,7 @@ import os,time,tempfile,uuid,FreeCAD,Part,Draft,Arch if open.__module__ == '__builtin__': pyopen = open # because we'll redefine open below +# which IFC type must create which FreeCAD type typesmap = { "Site": ["IfcSite"], "Building": ["IfcBuilding"], "Floor": ["IfcBuildingStorey"], @@ -42,13 +43,16 @@ typesmap = { "Site": ["IfcSite"], "Rebar": ["IfcReinforcingBar"], "Equipment": ["IfcFurnishingElement","IfcSanitaryTerminal","IfcFlowTerminal","IfcElectricAppliance"] } - + +# specific name translations translationtable = { "Foundation":"Footing", "Floor":"BuildingStorey", "Rebar":"ReinforcingBar", "HydroEquipment":"SanitaryTerminal", "ElectricEquipment":"ElectricAppliance", - "Furniture":"FurnishingElement" + "Furniture":"FurnishingElement", + "Stair Flight":"StairFlight", + "Curtain Wall":"CurtainWall" } ifctemplate = """ISO-10303-21; @@ -70,11 +74,11 @@ DATA; #10=IFCDIRECTION((0.,1.,0.)); #11=IFCGEOMETRICREPRESENTATIONCONTEXT('Plan','Model',3,1.E-05,#9,#10); #12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); -#13=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.); +#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); #14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); #15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); #16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); -#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.01745),#16); +#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); #18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); #19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); #20=IFCPROJECT('$projectid',#5,'$project',$,$,$,$,(#11),#19); @@ -83,11 +87,10 @@ END-ISO-10303-21; """ -def explore(filename=None,toplevel="IfcRoot"): - """explore([filename],[toplevel]): opens a dialog showing +def explore(filename=None): + """explore([filename]): opens a dialog showing the contents of an IFC file. If no filename is given, a dialog will - pop up to choose a file. toplevel (default IFcRoot) can be used to - limit the display to a certain category of objects and its derivates.""" + pop up to choose a file.""" p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") DEBUG = p.GetBool("ifcDebug",False) @@ -95,9 +98,8 @@ def explore(filename=None,toplevel="IfcRoot"): try: import ifcopenshell except: - if DEBUG: print "using legacy importer" - import importIFClegacy - return importIFClegacy.explore(filename) + FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n") + return if not filename: from PySide import QtGui @@ -115,7 +117,6 @@ def explore(filename=None,toplevel="IfcRoot"): print "File not found" return - ifcopenshell.clean() ifc = ifcopenshell.open(filename) tree = QtGui.QTreeWidget() tree.setColumnCount(3) @@ -130,8 +131,14 @@ def explore(filename=None,toplevel="IfcRoot"): bold = QtGui.QFont() bold.setWeight(75) bold.setBold(True) + + entities = ifc.by_type("IfcRoot") + entities += ifc.by_type("IfcRepresentation") + entities += ifc.by_type("IfcRepresentationItem") + entities += ifc.by_type("IfcPlacement") + entities = sorted(entities, key=lambda eid: eid.id()) - for entity in ifc.by_type(toplevel): + for entity in entities: item = QtGui.QTreeWidgetItem(tree) if hasattr(entity,"id"): item.setText(0,str(entity.id())) @@ -161,18 +168,18 @@ def explore(filename=None,toplevel="IfcRoot"): i = 0 while True: try: - argname = entity.wrapped_data.get_argument_name(i) + argname = entity.attribute_name(i) except: break else: try: - argvalue = entity.wrapped_data.get_argument(i) + argvalue = getattr(entity,argname) except: print "Error in entity ",entity break else: if not argname in ["Id", "GlobalId"]: - if isinstance(argvalue,ifcopenshell.ifc_wrapper.entity_instance): + if isinstance(argvalue,ifcopenshell.entity_instance): t = "Entity #" + str(argvalue.id()) + ": " + str(argvalue.is_a()) elif isinstance(argvalue,list): t = "" @@ -183,7 +190,7 @@ def explore(filename=None,toplevel="IfcRoot"): item.setText(2,str(t)) if isinstance(argvalue,list): for argitem in argvalue: - if isinstance(argitem,ifcopenshell.ifc_wrapper.entity_instance): + if isinstance(argitem,ifcopenshell.entity_instance): t = "Entity #" + str(argitem.id()) + ": " + str(argitem.is_a()) else: t = argitem @@ -221,14 +228,12 @@ def insert(filename,docname,skip=[]): PREFIX_NUMBERS = p.GetBool("ifcPrefixNumbers",False) SKIP = p.GetString("ifcSkip","") SEPARATE_OPENINGS = p.GetBool("ifcSeparateOpenings",False) - SCALE = p.GetFloat("IfcScalingFactor",1.0) try: import ifcopenshell except: - if DEBUG: print "using legacy importer" - import importIFClegacy - return importIFClegacy.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,"..." try: @@ -238,13 +243,17 @@ def insert(filename,docname,skip=[]): FreeCAD.ActiveDocument = doc global ifcfile # keeping global for debugging purposes - ifcopenshell.clean() if isinstance(filename,unicode): import sys #workaround since ifcopenshell currently can't handle unicode filenames filename = filename.encode(sys.getfilesystemencoding()) ifcfile = ifcopenshell.open(filename) - shape_attributes = ifcopenshell.SEW_SHELLS - if SEPARATE_OPENINGS: shape_attributes += ifcopenshell.DISABLE_OPENING_SUBTRACTIONS + from ifcopenshell import geom + settings = ifcopenshell.geom.settings() + settings.set(settings.USE_BREP_DATA,True) + settings.set(settings.SEW_SHELLS,True) + settings.set(settings.USE_WORLD_COORDS,True) + if SEPARATE_OPENINGS: + settings.set(settings.DISABLE_OPENING_SUBTRACTIONS,True) sites = ifcfile.by_type("IfcSite") buildings = ifcfile.by_type("IfcBuilding") floors = ifcfile.by_type("IfcBuildingStorey") @@ -264,7 +273,9 @@ def insert(filename,docname,skip=[]): subtractions.append([r.RelatedOpeningElement.id(), r.RelatingBuildingElement.id()]) for r in ifcfile.by_type("IfcRelDefinesByProperties"): for obj in r.RelatedObjects: - properties.setdefault(obj.id(),[]).extend([e.id() for e in r.RelatingPropertyDefinition.HasProperties]) + if r.RelatingPropertyDefinition.is_a("IfcPropertySet"): + properties.setdefault(obj.id(),[]).extend([e.id() for e in r.RelatingPropertyDefinition.HasProperties]) + count = 0 # products for product in products: @@ -275,17 +286,20 @@ def insert(filename,docname,skip=[]): 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 - if ptype in SKIP: continue - - brep = ifcopenshell.create_shape(product,shape_attributes) + if pid in skip: continue # user given id skip list + if ptype in SKIP: continue # preferences-set type skip list + try: + cr = ifcopenshell.geom.create_shape(settings,product) + brep = cr.geometry.brep_data + except: + pass if brep: shape = Part.Shape() shape.importBrepFromString(brep) - if SCALE != 1: - shape.scale(SCALE) + shape.scale(1000.0) # IfcOpenShell always outputs in meters if not shape.isNull(): baseobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body") baseobj.Shape = shape @@ -299,6 +313,9 @@ def insert(filename,docname,skip=[]): 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 @@ -313,7 +330,8 @@ def insert(filename,docname,skip=[]): if obj: sh = baseobj.Shape.ShapeType if hasattr(baseobj,"Shape") else "None" sols = str(baseobj.Shape.Solids) if hasattr(baseobj,"Shape") else "" - if DEBUG: print "creating object ",pid," : ",ptype, " with shape: ",sh," ",sols + pc = str(int((float(count)/len(products)*100)))+"% " + if DEBUG: print pc,"creating object ",pid," : ",ptype, " with shape: ",sh," ",sols objects[pid] = obj # properties @@ -322,25 +340,31 @@ def insert(filename,docname,skip=[]): a = obj.IfcAttributes for p in properties[pid]: o = ifcfile[p] - a[o.Name] = str(o.NominalValue) + if o.is_a("IfcPropertySingleValue"): + a[o.Name] = str(o.NominalValue) obj.IfcAttributes = a - + count += 1 + + FreeCAD.ActiveDocument.recompute() + + 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()): - Arch.removeComponents(objects[subtraction[0]],objects[subtraction[1]]) - + #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() - + # cleaning bad shapes for obj in objects.values(): if obj.isDerivedFrom("Part::Feature"): @@ -365,9 +389,8 @@ def export(exportList,filename): global ifcopenshell import ifcopenshell except: - if DEBUG: print "using legacy exporter" - import importIFClegacy - return importIFClegacy.export(exportList,filename) + FreeCAD.Console.PrintError("IfcOpenShell was not found on this system. IFC support is disabled\n") + return if isinstance(filename,unicode): import sys #workaround since ifcopenshell currently can't handle unicode filenames @@ -452,9 +475,9 @@ def export(exportList,filename): if ifctype in ["IfcSlab","IfcFooting","IfcRoof"]: args = args + ["NOTDEFINED"] elif ifctype in ["IfcWindow","IfcDoor"]: - args = args + [obj.Width.Value, obj.Height.Value] + args = args + [obj.Width.Value/1000.0, obj.Height.Value/1000.0] elif ifctype == "IfcSpace": - args = args + ["ELEMENT","INTERNAL",obj.Shape.BoundBox.ZMin] + args = args + ["ELEMENT","INTERNAL",obj.Shape.BoundBox.ZMin/1000.0] elif ifctype == "IfcBuildingElementProxy": args = args + ["ELEMENT"] elif ifctype == "IfcSite": @@ -565,9 +588,12 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess if hasattr(obj.Proxy,"getProfiles"): p = obj.Proxy.getProfiles(obj,noplacement=True) extrusionv = obj.Proxy.getExtrusionVector(obj,noplacement=True) + extrusionv.multiply(0.001) # to meters if (len(p) == 1) and extrusionv: p = p[0] + p.scale(0.001) # to meters r = obj.Proxy.getPlacement(obj) + r.Base = r.Base.multiply(0.001) # to meters if len(p.Edges) == 1: @@ -624,7 +650,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess ovc = ifcfile.createIfcCartesianPoint(tuple(e.Curve.Center)[:2]) plc = ifcfile.createIfcAxis2Placement2D(ovc,xvc) cir = ifcfile.createIfcCircle(plc,e.Curve.Radius) - curve = ifcfile.createIfcTrimmedCurve(cir,[ifcfile.create_entity("IfcParameterValue",p1)],[ifcfile.create_entity("IfcParameterValue",p2)],follow,"PARAMETER") + curve = ifcfile.createIfcTrimmedCurve(cir,[ifcfile.createIfcParameterValue(p1)],[ifcfile.createIfcParameterValue(p2)],follow,"PARAMETER") else: verts = [vertex.Point for vertex in e.Vertexes] @@ -683,6 +709,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess dataset = fcshape.Shells print "Warning! object contains no solids" for fcsolid in dataset: + fcsolid.scale(0.001) # to meters faces = [] curves = False for fcface in fcsolid.Faces: