From a8fda8df0bdad1765159ae638fe8cdae21b8be0e Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Fri, 9 Nov 2018 16:56:59 -0200 Subject: [PATCH] Arch: Fixed buggy import/export of 2D objects to IFC --- src/Mod/Arch/ArchAxis.py | 2 +- src/Mod/Arch/ArchBuildingPart.py | 5 +- src/Mod/Arch/ArchComponent.py | 5 +- src/Mod/Arch/ArchSite.py | 1 + src/Mod/Arch/importIFC.py | 222 ++++++++++++++++++++----------- 5 files changed, 156 insertions(+), 79 deletions(-) diff --git a/src/Mod/Arch/ArchAxis.py b/src/Mod/Arch/ArchAxis.py index a028c5ab94..093e443b8b 100644 --- a/src/Mod/Arch/ArchAxis.py +++ b/src/Mod/Arch/ArchAxis.py @@ -542,7 +542,7 @@ class _ViewProviderAxis: tx = coin.SoAsciiText() tx.justification = coin.SoText2.LEFT t = vobj.Object.Labels[i] - if isinstance(t,unicode): + if sys.version_info.major < 3 and isinstance(t,unicode): t = t.encode("utf8") tx.string.setValue(t) if hasattr(vobj,"FontSize"): diff --git a/src/Mod/Arch/ArchBuildingPart.py b/src/Mod/Arch/ArchBuildingPart.py index c5c780b9b5..c7c8761a50 100644 --- a/src/Mod/Arch/ArchBuildingPart.py +++ b/src/Mod/Arch/ArchBuildingPart.py @@ -548,7 +548,8 @@ class ViewProviderBuildingPart: colors = self.getColors(obj) if colors and hasattr(obj.ViewObject,"DiffuseColor"): if len(colors) == len(obj.Shape.Faces): - obj.ViewObject.DiffuseColor = colors + if colors != obj.ViewObject.DiffuseColor: + obj.ViewObject.DiffuseColor = colors def getColors(self,obj): @@ -576,6 +577,8 @@ class ViewProviderBuildingPart: def onChanged(self,vobj,prop): + #print(vobj.Object.Label," - ",prop) + if prop == "ShapeColor": if hasattr(vobj,"ShapeColor"): l = vobj.ShapeColor diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index a8128378c8..75de380c18 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -763,13 +763,12 @@ class ViewProviderComponent: def onChanged(self,vobj,prop): #print(vobj.Object.Name, " : changing ",prop) - if prop == "Visibility": + #if prop == "Visibility": #for obj in vobj.Object.Additions+vobj.Object.Subtractions: # if (Draft.getType(obj) == "Window") or (Draft.isClone(obj,"Window",True)): # obj.ViewObject.Visibility = vobj.Visibility # this would now hide all previous windows... Not the desired behaviour anymore. - pass - elif prop == "DiffuseColor": + if prop == "DiffuseColor": if hasattr(vobj.Object,"CloneOf"): if vobj.Object.CloneOf: if len(vobj.Object.CloneOf.ViewObject.DiffuseColor) > 1: diff --git a/src/Mod/Arch/ArchSite.py b/src/Mod/Arch/ArchSite.py index 3d82d84ad3..e86da64af4 100644 --- a/src/Mod/Arch/ArchSite.py +++ b/src/Mod/Arch/ArchSite.py @@ -508,6 +508,7 @@ class _ViewProviderSite(ArchFloor._ViewProviderFloor): def __init__(self,vobj): ArchFloor._ViewProviderFloor.__init__(self,vobj) + vobj.addExtension("Gui::ViewProviderGroupExtensionPython", self) self.setProperties(vobj) def setProperties(self,vobj): diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index a09130e575..fd97fd3141 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -501,14 +501,14 @@ def insert(filename,docname,skip=[],only=[],root=None): if it.Items[0].id() == r.id(): colors[m.RepresentedMaterial.id()] = (c.Red,c.Green,c.Blue) - # move IfcGrids from products to annotations + # remove any leftover annotations from products tp = [] for product in products: if product.is_a("IfcGrid") and not (product in annotations): annotations.append(product) - else: + elif not (product in annotations): tp.append(product) - products = tp + products = sorted(tp,key=lambda prod: prod.id()) # only import a list of IDs and their children if only: @@ -526,7 +526,7 @@ def insert(filename,docname,skip=[],only=[],root=None): from FreeCAD import Base progressbar = Base.ProgressIndicator() progressbar.start("Importing IFC objects...",len(products)) - if DEBUG: print("Processing",len(products),"objects...") + if DEBUG: print("Processing",len(products),"BIM objects...") if FITVIEW_ONIMPORT and FreeCAD.GuiUp: overallboundbox = None @@ -1076,6 +1076,7 @@ def insert(filename,docname,skip=[],only=[],root=None): # processing remaining (normal) groups swallowed = [] + remaining = {} for host,children in groups.items(): if ifcfile[host].is_a("IfcGroup"): if ifcfile[host].Name: @@ -1093,7 +1094,7 @@ def insert(filename,docname,skip=[],only=[],root=None): grp.addObject(objects[child]) swallowed.append(child) else: - if DEBUG: print("unable to add object: #", child, " to group: #", ifcfile[host].id(), ", ", grp_name) + remaining[child] = grp if MERGE_MODE_ARCH == 3: @@ -1163,7 +1164,7 @@ def insert(filename,docname,skip=[],only=[],root=None): Arch.addComponents(cobs,objects[host]) if DEBUG: FreeCAD.ActiveDocument.recompute() - if DEBUG: print("done.") + if DEBUG and first: print("done.") FreeCAD.ActiveDocument.recompute() @@ -1178,13 +1179,21 @@ def insert(filename,docname,skip=[],only=[],root=None): # 2D elements - if DEBUG and annotations:print("Creating 2D geometry...",end="") + if DEBUG and annotations: + print("Processing",len(annotations),"2D objects...") + prodcount = count + count = 0 scaling = getScaling(ifcfile) #print "scaling factor =",scaling for annotation in annotations: + anno = None aid = annotation.id() + + count += 1 + if DEBUG: print(count,"/",len(annotations),"object #"+str(aid),":",annotation.is_a(),end="") + if aid in skip: continue # user given id skip list if annotation.is_a() in SKIP: @@ -1221,6 +1230,7 @@ def insert(filename,docname,skip=[],only=[],root=None): if PREFIX_NUMBERS: name = "ID" + str(aid) + " " + name anno = Arch.makeAxisSystem(axes,name) + print(" axis") else: name = "Annotation" if annotation.Name: @@ -1233,28 +1243,35 @@ def insert(filename,docname,skip=[],only=[],root=None): shapes2d = [] for rep in annotation.Representation.Representations: if rep.RepresentationIdentifier in ["Annotation","FootPrint","Axis"]: - shapes2d.extend(setRepresentation(rep,scaling)) + sh = setRepresentation(rep,scaling) + if sh in FreeCAD.ActiveDocument.Objects: + # dirty hack: setRepresentation might return an object directly if non-shape based (texts for ex) + anno = sh + else: + shapes2d.extend(sh) if shapes2d: sh = Part.makeCompound(shapes2d) - pc = str(int((float(count)/(len(products)+len(annotations))*100)))+"% " - if DEBUG: print(pc,"creating object ",aid," : Annotation with shape: ",sh) + if DEBUG: print(" shape") anno = FreeCAD.ActiveDocument.addObject("Part::Feature",name) anno.Shape = sh p = getPlacement(annotation.ObjectPlacement,scaling) if p: # and annotation.is_a("IfcAnnotation"): anno.Placement = p - # placing in container if needed - if anno: - for host,children in additions.items(): - if (aid in children) and (host in objects.keys()): - Arch.addComponents(anno,objects[host]) + else: + if DEBUG: print(" no shape") - count += 1 + # placing in container if needed + + if anno: + if aid in remaining.keys(): + remaining[aid].addObject(anno) + else: + for host,children in additions.items(): + if (aid in children) and (host in objects.keys()): + Arch.addComponents(anno,objects[host]) FreeCAD.ActiveDocument.recompute() - if DEBUG and annotations: print("done.") - # Materials if DEBUG and materials: print("Creating materials...",end="") @@ -1385,7 +1402,15 @@ class recycler: return c def createIfcAxis2Placement3D(self,p1,p2,p3): - key = str(p1.Coordinates) + str(p2.DirectionRatios) + str(p3.DirectionRatios) + if p2: + tp2 = str(p2.DirectionRatios) + else: + tp2 = "None" + if p3: + tp3 = str(p3.DirectionRatios) + else: + tp3 = "None" + key = str(p1.Coordinates) + tp2 + tp3 if self.compress and key in self.axis2placement3ds: self.spared += 1 return self.axis2placement3ds[key] @@ -1515,7 +1540,7 @@ def export(exportList,filename): # clean objects list of unwanted types objectslist = [obj for obj in objectslist if obj not in annotations] objectslist = Arch.pruneIncluded(objectslist) - objectslist = [obj for obj in objectslist if Draft.getType(obj) not in ["Material","MaterialContainer"]] + objectslist = [obj for obj in objectslist if Draft.getType(obj) not in ["Material","MaterialContainer","WorkingPlaneProxy"]] if FULL_PARAMETRIC: objectslist = Arch.getAllChildren(objectslist) products = {} # { Name: IfcEntity, ... } @@ -1934,7 +1959,7 @@ def export(exportList,filename): pass # Quantities - + if hasattr(obj,"IfcAttributes"): quantities = [] if ("ExportHeight" in obj.IfcAttributes) and obj.IfcAttributes["ExportHeight"] and hasattr(obj,"Height"): @@ -2168,54 +2193,10 @@ def export(exportList,filename): imd = ifcfile.createIfcMaterialDefinitionRepresentation(None,None,[isr],mat) ifcfile.createIfcRelAssociatesMaterial(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'MaterialLink','',relobjs,mat) - # groups - - sortedgroups = [] - while groups: - for g in groups.keys(): - okay = True - for c in groups[g]: - if Draft.getType(FreeCAD.ActiveDocument.getObject(c)) in ["Group","VisGroup"]: - okay = False - for s in sortedgroups: - if s[0] == c: - okay = True - if okay: - sortedgroups.append([g,groups[g]]) - for g in sortedgroups: - if g[0] in groups.keys(): - del groups[g[0]] - #print "sorted groups:",sortedgroups - containers = {} - for g in sortedgroups: - if g[1]: - children = [] - for o in g[1]: - if o in products.keys(): - children.append(products[o]) - if children: - name = FreeCAD.ActiveDocument.getObject(g[0]).Label - if sys.version_info.major < 3: - name = name.encode("utf8") - grp = ifcfile.createIfcGroup(ifcopenshell.guid.compress(uuid.uuid1().hex),history,name,'',None) - products[g[0]] = grp - spatialelements[g[0]] = grp - ass = ifcfile.createIfcRelAssignsToGroup(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'GroupLink','',children,None,grp) - # stack groups inside containers - stack = {} - for g in sortedgroups: - go = FreeCAD.ActiveDocument.getObject(g[0]) - for parent in go.InList: - if hasattr(parent,"Group") and (go in parent.Group): - if (parent.Name in spatialelements) and (g[0] in spatialelements): - stack.setdefault(parent.Name,[]).append(spatialelements[g[0]]) - for k,v in stack.items(): - ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'GroupStackLink','',spatialelements[k],v) - # 2D objects + annos = {} if EXPORT_2D: - annos = [] curvestyles = {} if annotations and DEBUG: print("exporting 2D objects...") for anno in annotations: @@ -2228,15 +2209,21 @@ def export(exportList,filename): sh = anno.Shape.copy() sh.scale(0.001) # to meters ehc = [] + curves = [] for w in sh.Wires: - reps.append(createCurve(ifcfile,w)) + curves.append(createCurve(ifcfile,w)) for e in w.Edges: ehc.append(e.hashCode()) + if curves: + reps.append(ifcfile.createIfcGeometricCurveSet(curves)) + curves = [] for e in sh.Edges: if e.hashCode not in ehc: - reps.append(createCurve(ifcfile,e)) + curves.append(createCurve(ifcfile,e)) + if curves: + reps.append(ifcfile.createIfcGeometricCurveSet(curves)) elif anno.isDerivedFrom("App::Annotation"): - l = anno.Position + l = FreeCAD.Vector(anno.Position).multiply(0.001) pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) s = ";".join(anno.LabelText) @@ -2244,6 +2231,18 @@ def export(exportList,filename): s = s.encode("utf8") txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT") reps = [txt] + elif Draft.getType(anno) == "DraftText": + l = FreeCAD.Vector(anno.Placement.Base).multiply(0.001) + pos = ifcbin.createIfcCartesianPoint((l.x,l.y,l.z)) + tpl = ifcbin.createIfcAxis2Placement3D(pos,None,None) + s = ";".join(anno.Text) + if sys.version_info.major < 3: + s = s.encode("utf8") + txt = ifcfile.createIfcTextLiteral(s,tpl,"LEFT") + reps = [txt] + else: + print("Unable to handle object",anno.Label) + continue for coldef in ["LineColor","TextColor","ShapeColor"]: if hasattr(obj.ViewObject,coldef): @@ -2266,12 +2265,70 @@ def export(exportList,filename): if sys.version_info.major < 3: l = l.encode("utf8") ann = ifcfile.createIfcAnnotation(ifcopenshell.guid.compress(uuid.uuid1().hex),history,l,'',None,gpl,rep) - annos.append(ann) - if annos: + annos[anno.Name] = ann + + # groups + + sortedgroups = [] + swallowed = [] + while groups: + for g in groups.keys(): + okay = True + for c in groups[g]: + if Draft.getType(FreeCAD.ActiveDocument.getObject(c)) in ["Group","VisGroup"]: + okay = False + for s in sortedgroups: + if s[0] == c: + okay = True + if okay: + sortedgroups.append([g,groups[g]]) + for g in sortedgroups: + if g[0] in groups.keys(): + del groups[g[0]] + #print "sorted groups:",sortedgroups + containers = {} + for g in sortedgroups: + if g[1]: + children = [] + for o in g[1]: + if o in products.keys(): + children.append(products[o]) + elif o in annos.keys(): + children.append(annos[o]) + swallowed.append(annos[o]) + if children: + name = FreeCAD.ActiveDocument.getObject(g[0]).Label + if sys.version_info.major < 3: + name = name.encode("utf8") + grp = ifcfile.createIfcGroup(ifcopenshell.guid.compress(uuid.uuid1().hex),history,name,'',None) + products[g[0]] = grp + spatialelements[g[0]] = grp + ass = ifcfile.createIfcRelAssignsToGroup(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'GroupLink','',children,None,grp) + + # stack groups inside containers + + stack = {} + for g in sortedgroups: + go = FreeCAD.ActiveDocument.getObject(g[0]) + for parent in go.InList: + if hasattr(parent,"Group") and (go in parent.Group): + if (parent.Name in spatialelements) and (g[0] in spatialelements): + stack.setdefault(parent.Name,[]).append(spatialelements[g[0]]) + for k,v in stack.items(): + ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'GroupStackLink','',spatialelements[k],v) + + # add remaining 2D objects to default host + + if annos: + remaining = [anno for anno in annos.values() if not anno in swallowed] + if remaining: if not defaulthost: defaulthost = ifcfile.createIfcBuildingStorey(ifcopenshell.guid.compress(uuid.uuid1().hex),history,"Default Storey",'',None,None,None,None,"ELEMENT",None) ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'DefaultStoreyLink','',buildings[0],[defaulthost]) - ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'AnnotationsLink','',annos,defaulthost) + ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'AnnotationsLink','',remaining,defaulthost) + + + if DEBUG: print("writing ",filename,"...") @@ -2287,7 +2344,7 @@ def export(exportList,filename): os.remove(templatefile) if DEBUG and ifcbin.compress: - f = pyopen(filename,"rb") + f = pyopen(filename,"r") s = len(f.read().split("\n")) f.close() print("Compression ratio:",int((float(ifcbin.spared)/(s+ifcbin.spared))*100),"%") @@ -2459,7 +2516,7 @@ def getEdgesAngle(edge1, edge2): angle = vec1.getAngle(vec2) angle = math.degrees(angle) return angle - + def checkRectangle(edges): """ checkRectangle(edges=[]): This function checks whether the given form rectangle or not. It will return True when edges form rectangular shape or return False @@ -2917,6 +2974,20 @@ def setRepresentation(representation,scaling=1000): 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 = [] @@ -2936,6 +3007,9 @@ def setRepresentation(representation,scaling=1000): 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