diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index ca7f70f983..7b5dc97771 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -239,6 +239,7 @@ def insert(filename,docname,skip=[],only=[],root=None): 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).""" + # read preference settings getPreferences() try: @@ -253,18 +254,22 @@ def insert(filename,docname,skip=[],only=[],root=None): except: doc = FreeCAD.newDocument(docname) FreeCAD.ActiveDocument = doc - if DEBUG: print("done.") global ROOT_ELEMENT, parametrics + # allow to override the root element if root: ROOT_ELEMENT = root - # global ifcfile # keeping global for debugging purposes + # keeping global variable for debugging purposes + # global ifcfile filename = decode(filename,utf=True) ifcfile = ifcopenshell.open(filename) + + # get file scale + ifcscale = importIFCHelper.getScaling(ifcfile) # IfcOpenShell multiplies the precision value of the file by 100 # So we raise the precision by 100 too to compensate... @@ -284,17 +289,15 @@ def insert(filename,docname,skip=[],only=[],root=None): if SPLIT_LAYERS and hasattr(settings,"APPLY_LAYERSETS"): settings.set(settings.APPLY_LAYERSETS,True) - if DEBUG: print("Building relationships table...",end="") - # build all needed tables + if DEBUG: print("Building relationships table...",end="") objects,prodrepr,additions,groups,subtractions,colors,shapes, \ structshapes,mattable,sharedobjects,parametrics,profiles, \ sites,buildings,floors,products,openings,annotations,materials, \ style_material_id = importIFCHelper.buildRelationships(ifcfile,ROOT_ELEMENT) - if DEBUG: print("done.") - # only import a list of IDs and their children + # only import a list of IDs and their children, if defined if only: ids = [] while only: @@ -304,17 +307,20 @@ def insert(filename,docname,skip=[],only=[],root=None): only.extend(additions[currentid]) products = [ifcfile[currentid] for currentid in ids] + # start the actual import, set FreeCAD UI count = 0 from FreeCAD import Base progressbar = Base.ProgressIndicator() progressbar.start("Importing IFC objects...",len(products)) - if DEBUG: print("Prsing",len(products),"BIM objects...") + if DEBUG: print("Parsing",len(products),"BIM objects...") + # Prepare the 3D view if applicable if FITVIEW_ONIMPORT and FreeCAD.GuiUp: overallboundbox = None import FreeCADGui FreeCADGui.ActiveDocument.activeView().viewAxonometric() + # Create the base project object projectImporter = importIFCHelper.ProjectImporter(ifcfile, objects) projectImporter.execute() @@ -329,11 +335,10 @@ def insert(filename,docname,skip=[],only=[],root=None): ptype = product.is_a() if DEBUG: print(count,"/",len(products),"object #"+str(pid),":",ptype,end="") - # get psets - psets = getIfcPropertySets(ifcfile, pid) + # build list of related property sets + psets = importIFCHelper.getIfcPropertySets(ifcfile, pid) # checking for full FreeCAD parametric definition, overriding everything else - if psets and FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("IfcImportFreeCADProperties",False): if "FreeCADPropertySet" in [ifcfile[pset].Name for pset in psets.keys()]: if DEBUG: print(" restoring from parametric definition...",end="") @@ -343,21 +348,22 @@ def insert(filename,docname,skip=[],only=[],root=None): if DEBUG: print("done") continue else: - print("failed.",end="") + if DEBUG: print("failed") # no parametric data, we go the good old way - name = str(ptype[3:]) if product.Name: name = product.Name if six.PY2: name = name.encode("utf8") - if PREFIX_NUMBERS: name = "ID" + str(pid) + " " + name + if PREFIX_NUMBERS: + name = "ID" + str(pid) + " " + name obj = None baseobj = None brep = None shape = None + # classify object and verify if we must skip it archobj = True # assume all objects not in structuralifcobjects are architecture structobj = False if ptype in structuralifcobjects: @@ -379,8 +385,7 @@ def insert(filename,docname,skip=[],only=[],root=None): if DEBUG: print(" skipped.") continue - # detect if this object is sharing its shape - + # check if this object is sharing its shape (mapped representation) clone = None store = None prepr = None @@ -389,18 +394,17 @@ def insert(filename,docname,skip=[],only=[],root=None): except: if DEBUG: print(" ERROR unable to get object representation",end="") if prepr and (MERGE_MODE_ARCH == 0) and archobj and CREATE_CLONES: - - for s in prepr.Representations: - if s.RepresentationIdentifier.upper() == "BODY": - if s.Items[0].is_a("IfcMappedItem"): - bid = s.Items[0].MappingSource.id() - if bid in sharedobjects: - clone = sharedobjects[bid] + for r in prepr.Representations: + if r.RepresentationIdentifier.upper() == "BODY": + if r.Items[0].is_a("IfcMappedItem"): + originalid = r.Items[0].MappingSource.id() + if originalid in sharedobjects: + clone = sharedobjects[originalid] else: - sharedobjects[bid] = None - store = bid + sharedobjects[originalid] = None + store = originalid # flag this object to be stored later - # additional setting for structural entities + # set additional setting for structural entities if hasattr(settings,"INCLUDE_CURVES"): if structobj: settings.set(settings.INCLUDE_CURVES,True) @@ -412,17 +416,23 @@ def insert(filename,docname,skip=[],only=[],root=None): except: pass # IfcOpenShell will yield an error if a given product has no shape, but we don't care, we're brave enough + # from now on we have a brep string if brep: if DEBUG: print(" "+str(int(len(brep)/1000))+"k ",end="") + # create a Part shape shape = Part.Shape() shape.importBrepFromString(brep,False) - shape.scale(1000.0) # IfcOpenShell always outputs in meters, we convert to mm, the freecad internal unit - if not shape.isNull(): + if shape.isNull(): + if DEBUG: print("null shape ",end="") + elif not shape.isValid(): + if DEBUG: print("invalid shape ",end="") + else: + + # add to the global boundbox if applicable if FITVIEW_ONIMPORT and FreeCAD.GuiUp: - # add to the global boundbox try: bb = shape.BoundBox # if DEBUG: print(' ' + str(bb),end="") @@ -437,8 +447,9 @@ def insert(filename,docname,skip=[],only=[],root=None): overallboundbox.add(bb) if (MERGE_MODE_ARCH > 0 and archobj) or structobj: + # we are not using Arch objects - # additional tweaks when not using Arch objects + # additional tweaks to set when not using Arch objects if ptype == "IfcSpace": # do not add spaces to compounds if DEBUG: print("skipping space ",pid,end="") elif structobj: @@ -456,13 +467,15 @@ def insert(filename,docname,skip=[],only=[],root=None): if DEBUG: print("clone ",end="") else: if GET_EXTRUSIONS and (MERGE_MODE_ARCH != 1): + + # recompose extrusions from a shape if ptype in ["IfcWall","IfcWallStandardCase","IfcSpace"]: sortmethod = "z" else: sortmethod = "area" ex = Arch.getExtrusionData(shape,sortmethod) # is this an extrusion? if ex: - #print("found extrusion:",ex) + # check for extrusion profile baseface = None profileid = None @@ -473,10 +486,12 @@ def insert(filename,docname,skip=[],only=[],root=None): if product.Representation.Representations[0].Items: if product.Representation.Representations[0].Items[0].is_a("IfcExtrudedAreaSolid"): profileid = product.Representation.Representations[0].Items[0].SweptArea.id() - if profileid and profileid in profiles: - # reuse existing profile + if profileid and (profileid in profiles): + + # reuse existing profile if existing print("shared extrusion ",end="") baseface = profiles[profileid] + # calculate delta placement between stored profile and this one addplacement = FreeCAD.Placement() r = FreeCAD.Rotation(baseface.Shape.Faces[0].normalAt(0,0),ex[0].Faces[0].normalAt(0,0)) @@ -488,7 +503,9 @@ def insert(filename,docname,skip=[],only=[],root=None): d = ex[0].CenterOfMass.sub(baseface.Shape.CenterOfMass) if d.Length > 0.000001: addplacement.move(d) + if not baseface: + # this is an extrusion but we haven't built the profile yet print("extrusion ",end="") import DraftGeomUtils if DraftGeomUtils.hasCurves(ex[0]) or len(ex[0].Wires) != 1: @@ -510,6 +527,7 @@ def insert(filename,docname,skip=[],only=[],root=None): # no hole and no curves, we make a Draft Wire instead baseface = Draft.makeWire([v.Point for v in ex[0].Wires[0].OrderedVertexes],closed=True) if profileid: + # store for possible shared use profiles[profileid] = baseface baseobj = FreeCAD.ActiveDocument.addObject("Part::Extrusion",name+"_body") baseobj.Base = baseface @@ -524,15 +542,12 @@ def insert(filename,docname,skip=[],only=[],root=None): if (not baseobj): baseobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body") baseobj.Shape = shape - else: - if DEBUG: print("null shape ",end="") - if not shape.isValid(): - if DEBUG: print("invalid shape ",end="") - #continue - else: + # this object has no shape (storeys, etc...) if DEBUG: print(" no brep ",end="") + # we now have the shape, we create the final object + if MERGE_MODE_ARCH == 0 and archobj: # full Arch objects @@ -544,6 +559,8 @@ def insert(filename,docname,skip=[],only=[],root=None): if clone: obj = getattr(Arch,"make"+freecadtype)(name=name) obj.CloneOf = clone + + # calculate the correct distance from the cloned object if shape: if shape.Solids: s1 = shape.Solids[0] @@ -556,7 +573,7 @@ def insert(filename,docname,skip=[],only=[],root=None): if hasattr(s1,"CenterOfMass") and hasattr(s2,"CenterOfMass"): v = s1.CenterOfMass.sub(s2.CenterOfMass) if product.Representation: - r = getRotation(product.Representation.Representations[0].Items[0].MappingTarget) + r = importIFCHelper.getRotation(product.Representation.Representations[0].Items[0].MappingTarget) if not r.isNull(): v = v.add(s2.CenterOfMass) v = v.add(r.multVec(s2.CenterOfMass.negative())) @@ -565,9 +582,11 @@ def insert(filename,docname,skip=[],only=[],root=None): else: print("failed to compute placement ",) else: + # no clone obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name) if freecadtype in ["Wall","Structure"] and baseobj and baseobj.isDerivedFrom("Part::Extrusion"): # remove intermediary extrusion for types that can extrude themselves + # TODO do this check earlier so we do not calculate it, to save time obj.Base = baseobj.Base obj.Placement = obj.Placement.multiply(baseobj.Placement) obj.Height = baseobj.Dir.Length @@ -582,15 +601,18 @@ def insert(filename,docname,skip=[],only=[],root=None): if store: sharedobjects[store] = obj + # set the placement from the storey's elevation property if ptype == "IfcBuildingStorey": if product.Elevation: - obj.Placement.Base.z = product.Elevation * getScaling(ifcfile) + obj.Placement.Base.z = product.Elevation * ifcscale break if not obj: + # we couldn't make an object of a specific type, use default arch component obj = Arch.makeComponent(baseobj,name=name) + # set additional properties obj.Label = name if DEBUG: print(": "+obj.Label+" ",end="") if hasattr(obj,"Description") and hasattr(product,"Description"): @@ -631,11 +653,13 @@ def insert(filename,docname,skip=[],only=[],root=None): # This might not coincide with the file being opened, hence some attributes are not properly read. if obj: - s = "" - if hasattr(obj,"Shape"): - if obj.Shape.Solids: - s = str(len(obj.Shape.Solids))+" solids" - if DEBUG: print(s,end="") + # print number of solids + if DEBUG: + s = "" + if hasattr(obj,"Shape"): + if obj.Shape.Solids: + s = str(len(obj.Shape.Solids))+" solids" + print(s,end="") objects[pid] = obj elif (MERGE_MODE_ARCH == 1 and archobj) or (MERGE_MODE_STRUCT == 0 and not archobj): @@ -648,7 +672,7 @@ def insert(filename,docname,skip=[],only=[],root=None): obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name) if ptype == "IfcBuildingStorey": if product.Elevation: - obj.Placement.Base.z = product.Elevation * getScaling(ifcfile) + obj.Placement.Base.z = product.Elevation * ifcscale elif baseobj: obj = Arch.makeComponent(baseobj,name=name,delete=True) obj.Label = name @@ -676,7 +700,7 @@ def insert(filename,docname,skip=[],only=[],root=None): obj = getattr(Arch,"make"+freecadtype)(baseobj=baseobj,name=name) if ptype == "IfcBuildingStorey": if product.Elevation: - obj.Placement.Base.z = product.Elevation * getScaling(ifcfile) + obj.Placement.Base.z = product.Elevation * ifcscale elif baseobj: obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name) obj.Shape = shape @@ -743,7 +767,7 @@ def insert(filename,docname,skip=[],only=[],root=None): # 0.18 behaviour: properties are saved as pset;;type;;value in IfcProperties d = obj.IfcProperties - obj.IfcProperties = getIfcProperties(ifcfile, pid, psets, d) + obj.IfcProperties = importIFCHelper.getIfcProperties(ifcfile, pid, psets, d) elif hasattr(obj,"IfcData"): @@ -778,7 +802,7 @@ def insert(filename,docname,skip=[],only=[],root=None): if product.is_a("IfcSite"): if product.RefElevation: - obj.Elevation = product.RefElevation * getScaling(ifcfile) + obj.Elevation = product.RefElevation * ifcscale if product.RefLatitude: obj.Latitude = dms2dd(*product.RefLatitude) if product.RefLongitude: @@ -984,8 +1008,6 @@ def insert(filename,docname,skip=[],only=[],root=None): prodcount = count count = 0 - scaling = getScaling(ifcfile) - #print("scaling factor =",scaling) for annotation in annotations: anno = None @@ -1009,7 +1031,7 @@ def insert(filename,docname,skip=[],only=[],root=None): uvwaxes = uvwaxes + annotation.WAxes for axis in uvwaxes: if axis.AxisCurve: - sh = setRepresentation(axis.AxisCurve,scaling) + sh = importIFCHelper.get2DShape(axis.AxisCurve,ifcscale) if sh and (len(sh[0].Vertexes) == 2): # currently only straight axes are supported sh = sh[0] l = sh.Length @@ -1043,9 +1065,9 @@ def insert(filename,docname,skip=[],only=[],root=None): shapes2d = [] for rep in annotation.Representation.Representations: if rep.RepresentationIdentifier in ["Annotation","FootPrint","Axis"]: - sh = setRepresentation(rep,scaling) + sh = importIFCHelper.get2DShape(rep,ifcscale) if sh in FreeCAD.ActiveDocument.Objects: - # dirty hack: setRepresentation might return an object directly if non-shape based (texts for ex) + # dirty hack: get2DShape might return an object directly if non-shape based (texts for ex) anno = sh else: shapes2d.extend(sh) @@ -1054,7 +1076,7 @@ def insert(filename,docname,skip=[],only=[],root=None): if DEBUG: print(" shape") anno = FreeCAD.ActiveDocument.addObject("Part::Feature",name) anno.Shape = sh - p = getPlacement(annotation.ObjectPlacement,scaling) + p = importIFCHelper.getPlacement(annotation.ObjectPlacement,ifcscale) if p: # and annotation.is_a("IfcAnnotation"): anno.Placement = p else: @@ -1156,71 +1178,6 @@ def insert(filename,docname,skip=[],only=[],root=None): return doc -# ************************************************************************************************ -# ********** helper for import IFC ************** - -def getRelProperties(ifcfile): - - # this method no longer used by this importer module - # but this relation table might be useful anyway for other purposes - - properties = {} # { objid : { psetid : [propertyid, ... ], ... }, ... } - for r in ifcfile.by_type("IfcRelDefinesByProperties"): - for obj in r.RelatedObjects: - if not obj.id() in properties: - properties[obj.id()] = {} - psets = {} - props = [] - if r.RelatingPropertyDefinition.is_a("IfcPropertySet"): - props.extend([prop.id() for prop in r.RelatingPropertyDefinition.HasProperties]) - psets[r.RelatingPropertyDefinition.id()] = props - properties[obj.id()].update(psets) - return properties - - -def getIfcPropertySets(ifcfile, pid): - - # get psets for this pid - psets = {} - for rel in ifcfile[pid].IsDefinedBy: - # the following if condition is needed in IFC2x3 only - # https://forum.freecadweb.org/viewtopic.php?f=39&t=37892#p322884 - if rel.is_a('IfcRelDefinesByProperties'): - props = [] - if rel.RelatingPropertyDefinition.is_a("IfcPropertySet"): - props.extend([prop.id() for prop in rel.RelatingPropertyDefinition.HasProperties]) - psets[rel.RelatingPropertyDefinition.id()] = props - return psets - - -def getIfcProperties(ifcfile, pid, psets, d): - - for pset in psets.keys(): - #print("reading pset: ",pset) - psetname = ifcfile[pset].Name - if six.PY2: - psetname = psetname.encode("utf8") - for prop in psets[pset]: - e = ifcfile[prop] - pname = e.Name - if six.PY2: - pname = pname.encode("utf8") - if e.is_a("IfcPropertySingleValue"): - if e.NominalValue: - ptype = e.NominalValue.is_a() - if ptype in ['IfcLabel','IfcText','IfcIdentifier','IfcDescriptiveMeasure']: - pvalue = e.NominalValue.wrappedValue - if six.PY2: - pvalue = pvalue.encode("utf8") - else: - pvalue = str(e.NominalValue.wrappedValue) - if hasattr(e.NominalValue,'Unit'): - if e.NominalValue.Unit: - pvalue += e.NominalValue.Unit - d[pname+";;"+psetname] = ptype+";;"+pvalue - #print("adding property: ",pname,ptype,pvalue," pset ",psetname) - return d - def createFromProperties(propsets,ifcfile): @@ -1307,210 +1264,3 @@ def createFromProperties(propsets,ifcfile): else: print("Unhandled FreeCAD property:",name," of type:",ptype) return obj - - -# Below are 2D helper functions needed while IfcOpenShell cannot do this itself... - - -def setRepresentation(representation,scaling=1000): - """Returns a shape from a 2D IfcShapeRepresentation""" - - def getPolyline(ent): - pts = [] - for p in ent.Points: - c = p.Coordinates - c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0) - c.multiply(scaling) - pts.append(c) - return Part.makePolygon(pts) - - def getLine(ent): - pts = [] - p1 = getVector(ent.Pnt) - p1.multiply(scaling) - pts.append(p1) - p2 = getVector(ent.Dir) - p2.multiply(scaling) - p2 = p1.add(p2) - pts.append(p2) - return Part.makePolygon(pts) - - def getCircle(ent): - c = ent.Position.Location.Coordinates - c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0) - c.multiply(scaling) - r = ent.Radius*scaling - return Part.makeCircle(r,c) - - def getCurveSet(ent): - result = [] - if ent.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]: - elts = ent.Elements - elif ent.is_a() in ["IfcLine","IfcPolyline","IfcCircle","IfcTrimmedCurve"]: - elts = [ent] - for el in elts: - if el.is_a("IfcPolyline"): - result.append(getPolyline(el)) - elif el.is_a("IfcLine"): - result.append(getLine(el)) - elif el.is_a("IfcCircle"): - result.append(getCircle(el)) - elif el.is_a("IfcTrimmedCurve"): - base = el.BasisCurve - t1 = el.Trim1[0].wrappedValue - t2 = el.Trim2[0].wrappedValue - if not el.SenseAgreement: - t1,t2 = t2,t1 - if base.is_a("IfcPolyline"): - bc = getPolyline(base) - result.append(bc) - elif base.is_a("IfcCircle"): - bc = getCircle(base) - e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape() - d = base.Position.RefDirection.DirectionRatios - v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0) - a = -DraftVecUtils.angle(v) - e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a)) - result.append(e) - elif el.is_a("IfcCompositeCurve"): - for base in el.Segments: - if base.ParentCurve.is_a("IfcPolyline"): - bc = getPolyline(base.ParentCurve) - result.append(bc) - elif base.ParentCurve.is_a("IfcCircle"): - bc = getCircle(base.ParentCurve) - e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape() - d = base.Position.RefDirection.DirectionRatios - v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0) - a = -DraftVecUtils.angle(v) - e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a)) - result.append(e) - - return result - - result = [] - if representation.is_a("IfcShapeRepresentation"): - for item in representation.Items: - if item.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]: - result = getCurveSet(item) - elif item.is_a("IfcMappedItem"): - preresult = setRepresentation(item.MappingSource.MappedRepresentation,scaling) - pla = getPlacement(item.MappingSource.MappingOrigin,scaling) - rot = getRotation(item.MappingTarget) - if pla: - if rot.Angle: - pla.Rotation = rot - for r in preresult: - #r.Placement = pla - result.append(r) - else: - result = preresult - elif item.is_a("IfcTextLiteral"): - t = Draft.makeText([item.Literal],point=getPlacement(item.Placement,scaling).Base) - return t # dirty hack... Object creation should not be done here - elif representation.is_a() in ["IfcPolyline","IfcCircle","IfcTrimmedCurve"]: - result = getCurveSet(representation) - return result - - -def getRotation(entity): - "returns a FreeCAD rotation from an IfcProduct with a IfcMappedItem representation" - try: - u = FreeCAD.Vector(entity.Axis1.DirectionRatios) - v = FreeCAD.Vector(entity.Axis2.DirectionRatios) - w = FreeCAD.Vector(entity.Axis3.DirectionRatios) - except AttributeError: - return FreeCAD.Rotation() - import WorkingPlane - p = WorkingPlane.plane(u=u,v=v,w=w) - return p.getRotation().Rotation - - -def getPlacement(entity,scaling=1000): - "returns a placement from the given entity" - - if not entity: - return None - import DraftVecUtils - pl = None - if entity.is_a("IfcAxis2Placement3D"): - x = getVector(entity.RefDirection,scaling) - z = getVector(entity.Axis,scaling) - if x and z: - y = z.cross(x) - m = DraftVecUtils.getPlaneRotation(x,y,z) - pl = FreeCAD.Placement(m) - else: - pl = FreeCAD.Placement() - loc = getVector(entity.Location,scaling) - if loc: - pl.move(loc) - elif entity.is_a("IfcLocalPlacement"): - pl = getPlacement(entity.PlacementRelTo,1) # original placement - relpl = getPlacement(entity.RelativePlacement,1) # relative transf - if pl and relpl: - pl = pl.multiply(relpl) - elif relpl: - pl = relpl - elif entity.is_a("IfcCartesianPoint"): - loc = getVector(entity,scaling) - pl = FreeCAD.Placement() - pl.move(loc) - if pl: - pl.Base = FreeCAD.Vector(pl.Base).multiply(scaling) - return pl - - -def getVector(entity,scaling=1000): - "returns a vector from the given entity" - - if not entity: - return None - v = None - if entity.is_a("IfcDirection"): - if len(entity.DirectionRatios) == 3: - v= FreeCAD.Vector(tuple(entity.DirectionRatios)) - else: - v = FreeCAD.Vector(tuple(entity.DirectionRatios+[0])) - elif entity.is_a("IfcCartesianPoint"): - if len(entity.Coordinates) == 3: - v = FreeCAD.Vector(tuple(entity.Coordinates)) - else: - v = FreeCAD.Vector(tuple(entity.Coordinates+[0])) - #if v: - # v.multiply(scaling) - return v - - -def getScaling(ifcfile): - "returns a scaling factor from file units to mm" - - def getUnit(unit): - if unit.Name == "METRE": - if unit.Prefix == "KILO": - return 1000000.0 - elif unit.Prefix == "HECTO": - return 100000.0 - elif unit.Prefix == "DECA": - return 10000.0 - elif not unit.Prefix: - return 1000.0 - elif unit.Prefix == "DECI": - return 100.0 - elif unit.Prefix == "CENTI": - return 10.0 - return 1.0 - - ua = ifcfile.by_type("IfcUnitAssignment") - if not ua: - return 1.0 - ua = ua[0] - for u in ua.Units: - if u.UnitType == "LENGTHUNIT": - if u.is_a("IfcConversionBasedUnit"): - f = getUnit(u.ConversionFactor.UnitComponent) - return f * u.ConversionFactor.ValueComponent.wrappedValue - elif u.is_a("IfcSIUnit") or u.is_a("IfcUnit"): - return getUnit(u) - return 1.0 - diff --git a/src/Mod/Arch/importIFCHelper.py b/src/Mod/Arch/importIFCHelper.py index 9bee4e21c0..2ec1412736 100644 --- a/src/Mod/Arch/importIFCHelper.py +++ b/src/Mod/Arch/importIFCHelper.py @@ -1,6 +1,13 @@ -import Arch, ArchIFC, math +import FreeCAD +import Arch +import ArchIFC +import math +import six + class ProjectImporter: + """A helper class to create a FreeCAD Arch Project object""" + def __init__(self, file, objects): self.file = file self.objects = objects @@ -176,3 +183,272 @@ def buildRelationships(ifcfile,root_element): style_material_id +def getRelProperties(ifcfile): + """Builds and returns a dictionary of {object:[properties]} from an IFC file""" + + # this method no longer used by this importer module + # but this relation table might be useful anyway for other purposes + + properties = {} # { objid : { psetid : [propertyid, ... ], ... }, ... } + for r in ifcfile.by_type("IfcRelDefinesByProperties"): + for obj in r.RelatedObjects: + if not obj.id() in properties: + properties[obj.id()] = {} + psets = {} + props = [] + if r.RelatingPropertyDefinition.is_a("IfcPropertySet"): + props.extend([prop.id() for prop in r.RelatingPropertyDefinition.HasProperties]) + psets[r.RelatingPropertyDefinition.id()] = props + properties[obj.id()].update(psets) + return properties + + +def getIfcPropertySets(ifcfile, pid): + """Returns a dicionary of {pset_id:[prop_id, prop_id...]} for an IFC object""" + + # get psets for this pid + psets = {} + for rel in ifcfile[pid].IsDefinedBy: + # the following if condition is needed in IFC2x3 only + # https://forum.freecadweb.org/viewtopic.php?f=39&t=37892#p322884 + if rel.is_a('IfcRelDefinesByProperties'): + props = [] + if rel.RelatingPropertyDefinition.is_a("IfcPropertySet"): + props.extend([prop.id() for prop in rel.RelatingPropertyDefinition.HasProperties]) + psets[rel.RelatingPropertyDefinition.id()] = props + return psets + + +def getScaling(ifcfile): + """returns a scaling factor from file units to mm""" + + def getUnit(unit): + if unit.Name == "METRE": + if unit.Prefix == "KILO": + return 1000000.0 + elif unit.Prefix == "HECTO": + return 100000.0 + elif unit.Prefix == "DECA": + return 10000.0 + elif not unit.Prefix: + return 1000.0 + elif unit.Prefix == "DECI": + return 100.0 + elif unit.Prefix == "CENTI": + return 10.0 + return 1.0 + + ua = ifcfile.by_type("IfcUnitAssignment") + if not ua: + return 1.0 + ua = ua[0] + for u in ua.Units: + if u.UnitType == "LENGTHUNIT": + if u.is_a("IfcConversionBasedUnit"): + f = getUnit(u.ConversionFactor.UnitComponent) + return f * u.ConversionFactor.ValueComponent.wrappedValue + elif u.is_a("IfcSIUnit") or u.is_a("IfcUnit"): + return getUnit(u) + return 1.0 + +def getRotation(entity): + """returns a FreeCAD rotation from an IfcProduct with a IfcMappedItem representation""" + try: + u = FreeCAD.Vector(entity.Axis1.DirectionRatios) + v = FreeCAD.Vector(entity.Axis2.DirectionRatios) + w = FreeCAD.Vector(entity.Axis3.DirectionRatios) + except AttributeError: + return FreeCAD.Rotation() + import WorkingPlane + p = WorkingPlane.plane(u=u,v=v,w=w) + return p.getRotation().Rotation + + +def getPlacement(entity,scaling=1000): + """returns a placement from the given entity""" + + if not entity: + return None + import DraftVecUtils + pl = None + if entity.is_a("IfcAxis2Placement3D"): + x = getVector(entity.RefDirection,scaling) + z = getVector(entity.Axis,scaling) + if x and z: + y = z.cross(x) + m = DraftVecUtils.getPlaneRotation(x,y,z) + pl = FreeCAD.Placement(m) + else: + pl = FreeCAD.Placement() + loc = getVector(entity.Location,scaling) + if loc: + pl.move(loc) + elif entity.is_a("IfcLocalPlacement"): + pl = getPlacement(entity.PlacementRelTo,1) # original placement + relpl = getPlacement(entity.RelativePlacement,1) # relative transf + if pl and relpl: + pl = pl.multiply(relpl) + elif relpl: + pl = relpl + elif entity.is_a("IfcCartesianPoint"): + loc = getVector(entity,scaling) + pl = FreeCAD.Placement() + pl.move(loc) + if pl: + pl.Base = FreeCAD.Vector(pl.Base).multiply(scaling) + return pl + + +def getVector(entity,scaling=1000): + """returns a vector from the given entity""" + + if not entity: + return None + v = None + if entity.is_a("IfcDirection"): + if len(entity.DirectionRatios) == 3: + v= FreeCAD.Vector(tuple(entity.DirectionRatios)) + else: + v = FreeCAD.Vector(tuple(entity.DirectionRatios+[0])) + elif entity.is_a("IfcCartesianPoint"): + if len(entity.Coordinates) == 3: + v = FreeCAD.Vector(tuple(entity.Coordinates)) + else: + v = FreeCAD.Vector(tuple(entity.Coordinates+[0])) + #if v: + # v.multiply(scaling) + return v + + + +def get2DShape(representation,scaling=1000): + """Returns a shape from a 2D IfcShapeRepresentation""" + + import Part,DraftVecUtils + + def getPolyline(ent): + pts = [] + for p in ent.Points: + c = p.Coordinates + c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0) + c.multiply(scaling) + pts.append(c) + return Part.makePolygon(pts) + + def getLine(ent): + pts = [] + p1 = getVector(ent.Pnt) + p1.multiply(scaling) + pts.append(p1) + p2 = getVector(ent.Dir) + p2.multiply(scaling) + p2 = p1.add(p2) + pts.append(p2) + return Part.makePolygon(pts) + + def getCircle(ent): + c = ent.Position.Location.Coordinates + c = FreeCAD.Vector(c[0],c[1],c[2] if len(c) > 2 else 0) + c.multiply(scaling) + r = ent.Radius*scaling + return Part.makeCircle(r,c) + + def getCurveSet(ent): + result = [] + if ent.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]: + elts = ent.Elements + elif ent.is_a() in ["IfcLine","IfcPolyline","IfcCircle","IfcTrimmedCurve"]: + elts = [ent] + for el in elts: + if el.is_a("IfcPolyline"): + result.append(getPolyline(el)) + elif el.is_a("IfcLine"): + result.append(getLine(el)) + elif el.is_a("IfcCircle"): + result.append(getCircle(el)) + elif el.is_a("IfcTrimmedCurve"): + base = el.BasisCurve + t1 = el.Trim1[0].wrappedValue + t2 = el.Trim2[0].wrappedValue + if not el.SenseAgreement: + t1,t2 = t2,t1 + if base.is_a("IfcPolyline"): + bc = getPolyline(base) + result.append(bc) + elif base.is_a("IfcCircle"): + bc = getCircle(base) + e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape() + d = base.Position.RefDirection.DirectionRatios + v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0) + a = -DraftVecUtils.angle(v) + e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a)) + result.append(e) + elif el.is_a("IfcCompositeCurve"): + for base in el.Segments: + if base.ParentCurve.is_a("IfcPolyline"): + bc = getPolyline(base.ParentCurve) + result.append(bc) + elif base.ParentCurve.is_a("IfcCircle"): + bc = getCircle(base.ParentCurve) + e = Part.ArcOfCircle(bc.Curve,math.radians(t1),math.radians(t2)).toShape() + d = base.Position.RefDirection.DirectionRatios + v = FreeCAD.Vector(d[0],d[1],d[2] if len(d) > 2 else 0) + a = -DraftVecUtils.angle(v) + e.rotate(bc.Curve.Center,FreeCAD.Vector(0,0,1),math.degrees(a)) + result.append(e) + + return result + + result = [] + if representation.is_a("IfcShapeRepresentation"): + for item in representation.Items: + if item.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]: + result = getCurveSet(item) + elif item.is_a("IfcMappedItem"): + preresult = setRepresentation(item.MappingSource.MappedRepresentation,scaling) + pla = getPlacement(item.MappingSource.MappingOrigin,scaling) + rot = getRotation(item.MappingTarget) + if pla: + if rot.Angle: + pla.Rotation = rot + for r in preresult: + #r.Placement = pla + result.append(r) + else: + result = preresult + elif item.is_a("IfcTextLiteral"): + t = Draft.makeText([item.Literal],point=getPlacement(item.Placement,scaling).Base) + return t # dirty hack... Object creation should not be done here + elif representation.is_a() in ["IfcPolyline","IfcCircle","IfcTrimmedCurve"]: + result = getCurveSet(representation) + return result + + +def getIfcProperties(ifcfile, pid, psets, d): + """builds valid property values for FreeCAD""" + + for pset in psets.keys(): + #print("reading pset: ",pset) + psetname = ifcfile[pset].Name + if six.PY2: + psetname = psetname.encode("utf8") + for prop in psets[pset]: + e = ifcfile[prop] + pname = e.Name + if six.PY2: + pname = pname.encode("utf8") + if e.is_a("IfcPropertySingleValue"): + if e.NominalValue: + ptype = e.NominalValue.is_a() + if ptype in ['IfcLabel','IfcText','IfcIdentifier','IfcDescriptiveMeasure']: + pvalue = e.NominalValue.wrappedValue + if six.PY2: + pvalue = pvalue.encode("utf8") + else: + pvalue = str(e.NominalValue.wrappedValue) + if hasattr(e.NominalValue,'Unit'): + if e.NominalValue.Unit: + pvalue += e.NominalValue.Unit + d[pname+";;"+psetname] = ptype+";;"+pvalue + #print("adding property: ",pname,ptype,pvalue," pset ",psetname) + return d