From 5424ad1d6ac78f6dc57fad0ab94a214d63d4b2ef Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Sun, 1 Jun 2014 21:45:48 -0300 Subject: [PATCH] Arch: Support for extruded curves in IFC export --- src/Mod/Arch/ArchCommands.py | 49 ++++++++++++++++++++---- src/Mod/Arch/ArchComponent.py | 17 +++++++-- src/Mod/Arch/ArchWall.py | 19 +++++---- src/Mod/Arch/ifcWriter.py | 68 +++++++++++++++++++++------------ src/Mod/Arch/importIFC.py | 10 +++-- src/Mod/Draft/DraftGeomUtils.py | 32 +++++++++------- 6 files changed, 136 insertions(+), 59 deletions(-) diff --git a/src/Mod/Arch/ArchCommands.py b/src/Mod/Arch/ArchCommands.py index d72ac9c84..8134bd049 100644 --- a/src/Mod/Arch/ArchCommands.py +++ b/src/Mod/Arch/ArchCommands.py @@ -35,6 +35,8 @@ __title__="FreeCAD Arch Commands" __author__ = "Yorik van Havre" __url__ = "http://www.freecadweb.org" +CURVEMODE = "PARAMETER" # For trimmed curves. CARTESIAN or PARAMETER + # module functions ############################################### def getStringList(objects): @@ -600,7 +602,7 @@ def getTuples(data,scale=1,placement=None,normal=None,close=True): if isinstance(data,FreeCAD.Vector): if placement: data = placement.multVec(data) - data = DraftVecUtils.rounded(data) + data = DraftVecUtils.rounded(data) return (data.x*scale,data.y*scale,data.z*scale) elif isinstance(data,Part.Shape): t = [] @@ -622,7 +624,7 @@ def getTuples(data,scale=1,placement=None,normal=None,close=True): if placement: if not placement.isNull(): pt = placement.multVec(pt) - pt = DraftVecUtils.rounded(pt) + 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. @@ -677,13 +679,46 @@ def getIfcExtrusionData(obj,scale=1): if curves: # Composite profile ecurves = [] - for e in p.Edges: + last = None + import DraftGeomUtils + edges = DraftGeomUtils.sortEdges(p.Edges) + for e in edges: if isinstance(e.Curve,Part.Circle): - p1 = e.FirstParameter - p2 = e.LastParameter - ecurves.append(["arc",getTuples(e.Curve.Center,scale),e.Curve.Radius*scale,[p1,p2]]) + 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: - ecurves.append(["line",[getTuples(vt.Point,scale) for vt in e.Vertexes]]) + 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 diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index a8992985a..1795a36df 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -356,10 +356,21 @@ class Component: if not base.Solids: if base.Faces: return [base] - elif base.Wires: + basewires = [] + if not base.Wires: + if len(base.Edges) == 1: + import Part + basewires = [Part.Wire(base.Edges)] + else: + basewires = base.Wires + if basewires: import DraftGeomUtils,DraftVecUtils,Part - for wire in base.Wires: - dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(n) + for wire in basewires: + e = wire.Edges[0] + if isinstance(e.Curve,Part.Circle): + dvec = e.Vertexes[0].Point.sub(e.Curve.Center) + else: + dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(n) if not DraftVecUtils.isNull(dvec): dvec.normalize() sh = None diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index 40a2d99e9..e75b27ace 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -440,14 +440,17 @@ class _Wall(ArchComponent.Component): elif obj.Base.Shape.Edges: # case 3: the base is flat, we need to extrude it profiles = self.getProfiles(obj) - normal.multiply(height) - base = profiles.pop() - base.fix(0.1,0,1) - base = base.extrude(normal) - for p in profiles: - p.fix(0.1,0,1) - p = p.extrude(normal) - base = base.fuse(p) + if profiles: + normal.multiply(height) + base = profiles.pop() + base.fix(0.1,0,1) + base = base.extrude(normal) + for p in profiles: + p.fix(0.1,0,1) + p = p.extrude(normal) + base = base.fuse(p) + else: + base = None else: base = None FreeCAD.Console.PrintError(str(translate("Arch","Error: Invalid base object"))) diff --git a/src/Mod/Arch/ifcWriter.py b/src/Mod/Arch/ifcWriter.py index d4c226c76..9f81d04b5 100644 --- a/src/Mod/Arch/ifcWriter.py +++ b/src/Mod/Arch/ifcWriter.py @@ -89,14 +89,18 @@ def getPropertyNames(entity): 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([float(v) for v in vec]) + return tuple([fmt(v) for v in vec]) elif isinstance(vec,list): - return tuple([float(v) for v in vec]) + return tuple([fmt(v) for v in vec]) elif hasattr(vec,"x") and hasattr(vec,"y") and hasattr(vec,"z"): - return (float(vec.x),float(vec.y),float(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 (float(vec.X),float(vec.Y),float(vec.Z)) + return (fmt(vec.X),fmt(vec.Y),fmt(vec.Z)) def getValueAndDirection(vec): """getValueAndDirection(vec): returns a length and a tuple @@ -364,9 +368,9 @@ class IfcDocument(object): f = open(path,"rb") lines = [] for l in f.readlines(): - if "IFCPLANEANGLEMEASURE" in l: - # bug 1: adding ifcPlaneAngleMeasure in a ifcMeasureWithUnit adds an unwanted = sign - l = l.replace("=IFCPLANEANGLEMEASURE","IFCPLANEANGLEMEASURE") + if "(=IFC" in l: + # bug 1: adding an ifc entity without ID adds an unwanted = sign + l = l.replace("(=IFC","(IFC") #elif ("FACEBOUND" in l) or ("FACEOUTERBOUND" in l): # FIXED # bug 2: booleans are exported as ints #l = l.replace(",1);",",.T.);") @@ -511,6 +515,15 @@ class IfcDocument(object): 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]) @@ -520,17 +533,24 @@ class IfcDocument(object): for curve in data: cur = None if curve[0] == "line": - cur = self.addProfile("IfcPolyline",curve[1]) + 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]]) - trim1 = create(None,"IfcParameterValue",[curve[3][0]]) - trim2 = create(None,"IfcParameterValue",[curve[3][1]]) - cur = create(self._fileobject,"IfcTrimmedCurve",[cir,[trim1],[trim2]]) + 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) - profile = create(self._fileobject,"IfcCompositeCurve",[curves,False]) + ccu = create(self._fileobject,"IfcCompositeCurve",[curves,False]) + profile = create(self._fileobject,"IfcArbitraryClosedProfileDef",[curvetype,None,ccu]) else: if not isinstance(data,list): data = [data] @@ -559,23 +579,23 @@ class IfcDocument(object): self.addColor(color,exp) return exp - def addExtrudedCircle(self,center,radius,extrusion,placement=None,color=None): - """addExtrudedCircle(radius,extrusion,[placement,color]): makes an extruded circle - from the given radius and the given extrusion vector""" - cir = self.addProfile("IfcCircleProfileDef",radius) + 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=center,local=False) + 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,center,radiusx,radiusy,extrusion,placement=None,color=None): - """addExtrudedEllipse(radiusx,radiusy,extrusion,[placement,color]): makes an extruded ellipse - from the given radii and the given extrusion vector""" - cir = self.addProfile("IfcEllipseProfileDef",[radiusx,radiusy]) + 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=center,local=False) + placement = self.addPlacement(origin=data[0],local=False) exp = self.addExtrusion(cir,extrusion,placement) if color: self.addColor(color,exp) @@ -584,9 +604,9 @@ class IfcDocument(object): 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""" - ccu = self.addProfile("IfcCompositeCurve",curves) if not placement: - placement = self.addPlacement(origin=center,local=False) + placement = self.addPlacement(local=False) + ccu = self.addProfile("IfcCompositeCurve",curves) exp = self.addExtrusion(ccu,extrusion,placement) if color: self.addColor(color,exp) diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index 7d500ef61..e2b4f6eec 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -719,7 +719,10 @@ def getShape(obj,objid): # 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 - return sh + if sh: + if not sh.isNull(): + return sh + return None def getPlacement(entity): "returns a placement from the given entity" @@ -1041,13 +1044,14 @@ def export(exportList,filename): 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][0], gdata[1][1], gdata[2], color=color) + representation = ifc.addExtrudedCircle(gdata[1], gdata[2], color=color) elif gdata[0] == "ellipse": - representation = ifc.addExtrudedEllipse(gdata[1][0], gdata[1][1], gdata[1][2], gdata[2], color=color) + representation = ifc.addExtrudedEllipse(gdata[1], gdata[2], color=color) elif gdata[0] == "composite": representation = ifc.addExtrudedCompositeCurve(gdata[1], gdata[2], color=color) else: diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index 957a00901..2198a474a 100755 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -1253,21 +1253,25 @@ def getTangent(edge,frompoint=None): return None def bind(w1,w2): - '''bind(wire1,wire2): binds 2 wires by their endpoints and - returns a face''' - if w1.isClosed() and w2.isClosed(): - d1 = w1.BoundBox.DiagonalLength - d2 = w2.BoundBox.DiagonalLength - if d1 > d2: - #w2.reverse() - return Part.Face([w1,w2]) - else: - #w1.reverse() - return Part.Face([w2,w1]) + '''bind(wire1,wire2): binds 2 wires by their endpoints and + returns a face''' + if w1.isClosed() and w2.isClosed(): + d1 = w1.BoundBox.DiagonalLength + d2 = w2.BoundBox.DiagonalLength + if d1 > d2: + #w2.reverse() + return Part.Face([w1,w2]) else: - w3 = Part.Line(w1.Vertexes[0].Point,w2.Vertexes[0].Point).toShape() - w4 = Part.Line(w1.Vertexes[-1].Point,w2.Vertexes[-1].Point).toShape() - return Part.Face(Part.Wire(w1.Edges+[w3]+w2.Edges+[w4])) + #w1.reverse() + return Part.Face([w2,w1]) + else: + try: + w3 = Part.Line(w1.Vertexes[0].Point,w2.Vertexes[0].Point).toShape() + w4 = Part.Line(w1.Vertexes[-1].Point,w2.Vertexes[-1].Point).toShape() + return Part.Face(Part.Wire(w1.Edges+[w3]+w2.Edges+[w4])) + except: + print "DraftGeomUtils: unable to bind wires" + return None def cleanFaces(shape): "removes inner edges from coplanar faces"