diff --git a/src/Mod/Arch/ArchCommands.py b/src/Mod/Arch/ArchCommands.py index caf803a21..cdabc574f 100644 --- a/src/Mod/Arch/ArchCommands.py +++ b/src/Mod/Arch/ArchCommands.py @@ -432,14 +432,25 @@ def getShapeFromMesh(mesh,fast=True,tolerance=0.001,flat=False,cut=True): try: f = Part.Face(Part.makePolygon(pts)) except: - pass + print "getShapeFromMesh: error building face from polygon" + #pass else: faces.append(f) shell = Part.makeShell(faces) - solid = Part.Solid(shell) - solid = solid.removeSplitter() - return solid - + try: + solid = Part.Solid(shell) + except Part.OCCError: + print "getShapeFromMesh: error creating solid" + else: + try: + solid = solid.removeSplitter() + except Part.OCCError: + print "getShapeFromMesh: error removing splitter" + #pass + return solid + + #if not mesh.isSolid(): + # print "getShapeFromMesh: non-solid mesh, using slow method" faces = [] segments = mesh.getPlanarSegments(tolerance) #print len(segments) @@ -462,9 +473,11 @@ def getShapeFromMesh(mesh,fast=True,tolerance=0.001,flat=False,cut=True): if flat: return se except Part.OCCError: + print "getShapeFromMesh: error removing splitter" try: cp = Part.makeCompound(faces) except Part.OCCError: + print "getShapeFromMesh: error creating compound" return None else: return cp @@ -472,6 +485,7 @@ def getShapeFromMesh(mesh,fast=True,tolerance=0.001,flat=False,cut=True): try: solid = Part.Solid(se) except Part.OCCError: + print "getShapeFromMesh: error creating solid" return se else: return solid @@ -673,7 +687,7 @@ def pruneIncluded(objectslist,strict=False): if obj.isDerivedFrom("Part::Feature"): if not (Draft.getType(obj) in ["Window","Clone","Pipe"]): for parent in obj.InList: - if parent.isDerivedFrom("Part::Feature"): + if parent.isDerivedFrom("Part::Feature") and not (Draft.getType(parent) in ["Facebinder"]): if not parent.isDerivedFrom("Part::Part2DObject"): # don't consider 2D objects based on arch elements if hasattr(parent,"CloneOf"): @@ -936,8 +950,12 @@ def getExtrusionData(shape): return None # build faces list with normals faces = [] + import Part for f in shape.Faces: - faces.append([f,f.normalAt(0,0)]) + try: + faces.append([f,f.normalAt(0,0)]) + except Part.OCCError: + return None # find opposite normals pairs pairs = [] for i1, f1 in enumerate(faces): diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index 487e14aed..701c04136 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -31,7 +31,7 @@ Roles = ['Undefined','Beam','Chimney','Column','Covering','Curtain Wall', 'Member','Plate','Railing','Ramp','Ramp Flight','Rebar','Pile','Roof','Shading Device','Slab','Space', 'Stair','Stair Flight','Tendon','Wall','Wall Layer','Window'] -import FreeCAD,Draft,ArchCommands +import FreeCAD,Draft,ArchCommands,math from FreeCAD import Vector if FreeCAD.GuiUp: import FreeCADGui @@ -363,195 +363,43 @@ class Component: siblings.append(o) return siblings - def getAxis(self,obj): - "Returns an open wire which is the axis of this component, if applicable" - if Draft.getType(obj) == "Precast": - return None - if obj.Base: - if obj.Base.isDerivedFrom("Part::Feature"): - if obj.Base.Shape: - if (len(obj.Base.Shape.Wires) == 1) and not(obj.Base.Shape.Faces): - if not obj.Base.Shape.Wires[0].isClosed(): - return obj.Base.Shape.copy() - elif not(obj.Base.Shape.Solids): - if hasattr(obj.Base.Shape,"CenterOfMass"): - p1 = obj.Base.Shape.CenterOfMass - v = self.getExtrusionVector(obj) - if v: - p2 = p1.add(v) - import Part - return Part.Line(p1,p2).toShape() - else: - p1 = FreeCAD.Vector() - v = self.getExtrusionVector(obj) - if v: - p2 = p1.add(v) - import Part - return Part.Line(p1,p2).toShape() - return None - - def getProfiles(self,obj,noplacement=False): - "Returns the base profile(s) of this component, if applicable" - wires = [] - if Draft.getType(obj) == "Precast": - return wires - n,l,w,h = self.getDefaultValues(obj) + def getExtrusionData(self,obj): + "returns (shape,extrusion vector,placement) or None" + if hasattr(obj,"CloneOf"): + if obj.CloneOf: + data = obj.CloneOf.Proxy.getExtrusionData(obj.CloneOf) + if data: + return data if obj.Base: if obj.Base.isDerivedFrom("Part::Extrusion"): if obj.Base.Base: - base = obj.Base.Base.Shape.copy() - #if noplacement: - # base.Placement = FreeCAD.Placement() - return [base] - elif obj.Base.isDerivedFrom("Part::Feature"): - if obj.Base.Shape: - base = obj.Base.Shape.copy() - if noplacement: - base.Placement = FreeCAD.Placement() - if not base.Solids: - if base.Faces: - import DraftGeomUtils - if not DraftGeomUtils.isCoplanar(base.Faces): - return [] - return [base] - - 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 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 - if hasattr(obj,"Align"): - if obj.Align == "Left": - dvec.multiply(w) - if hasattr(obj,"Offset"): - if obj.Offset.Value: - dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value) - wire = DraftGeomUtils.offsetWire(wire,dvec2) - w2 = DraftGeomUtils.offsetWire(wire,dvec) - w1 = Part.Wire(Part.__sortEdges__(wire.Edges)) - sh = DraftGeomUtils.bind(w1,w2) - elif obj.Align == "Right": - dvec.multiply(w) - dvec = dvec.negative() - if hasattr(obj,"Offset"): - if obj.Offset.Value: - dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value) - wire = DraftGeomUtils.offsetWire(wire,dvec2) - w2 = DraftGeomUtils.offsetWire(wire,dvec) - w1 = Part.Wire(Part.__sortEdges__(wire.Edges)) - sh = DraftGeomUtils.bind(w1,w2) - elif obj.Align == "Center": - dvec.multiply(w/2) - w1 = DraftGeomUtils.offsetWire(wire,dvec) - dvec = dvec.negative() - w2 = DraftGeomUtils.offsetWire(wire,dvec) - sh = DraftGeomUtils.bind(w1,w2) - if sh: - wires.append(sh) - else: - wires.append(wire) - elif Draft.getType(obj) in ["Wall","Structure"]: - if (Draft.getType(obj) == "Structure") and (l > h) and (obj.Role != "Slab"): - if noplacement: - h2 = h/2 or 0.5 - w2 = w/2 or 0.5 - v1 = Vector(-h2,-w2,0) - v2 = Vector(h2,-w2,0) - v3 = Vector(h2,w2,0) - v4 = Vector(-h2,w2,0) - else: - h2 = h/2 or 0.5 - w2 = w/2 or 0.5 - v1 = Vector(0,-w2,-h2) - v2 = Vector(0,-w2,h2) - v3 = Vector(0,w2,h2) - v4 = Vector(0,w2,-h2) - else: - l2 = l/2 or 0.5 - w2 = w/2 or 0.5 - v1 = Vector(-l2,-w2,0) - v2 = Vector(l2,-w2,0) - v3 = Vector(l2,w2,0) - v4 = Vector(-l2,w2,0) - import Part - base = Part.makePolygon([v1,v2,v3,v4,v1]) - return [base] - return wires - - def getExtrusionVector(self,obj,noplacement=False): - "Returns an extrusion vector of this component, if applicable" - n,l,w,h = self.getDefaultValues(obj) - if Draft.getType(obj) == "Precast": - return FreeCAD.Vector() - if obj.Base: - if obj.Base.isDerivedFrom("Part::Extrusion"): - return FreeCAD.Vector(obj.Base.Dir) - if Draft.getType(obj) == "Structure": - if l > h: - v = n.multiply(l) - if noplacement: - import DraftVecUtils - v = DraftVecUtils.rounded(FreeCAD.Rotation(FreeCAD.Vector(0,1,0),-90).multVec(v)) - return v - return n.multiply(h) - - def getDefaultValues(self,obj): - "returns normal,length,width,height values from this component" - length = 0 - if hasattr(obj,"Length"): - if obj.Length.Value: - length = obj.Length.Value - width = 0 - if hasattr(obj,"Width"): - if obj.Width.Value: - width = obj.Width.Value - height = 0 - if hasattr(obj,"Height"): - if obj.Height.Value: - height = obj.Height.Value - else: - for p in obj.InList: - if Draft.getType(p) == "Floor": - if p.Height.Value: - height = p.Height.Value - default = Vector(0,0,1) - if Draft.getType(obj) == "Structure": - if length > height: - default = Vector(1,0,0) - if hasattr(obj,"Normal"): - if obj.Normal == Vector(0,0,0): - normal = default - else: - normal = Vector(obj.Normal) + base,placement = self.rebase(obj.Base.Base.Shape) + extrusion = obj.Base.Dir + if extrusion.Length == 0: + extrusion = FreeCAD.Vector(0,0,1) + if hasattr(obj.Base,"LengthForward"): + if obj.Base.LengthForward.Value: + extrusion = extrusion.multiply(obj.Base.LengthForward.Value) + return (base,extrusion,placement) + return None + + def rebase(self,shape): + import DraftGeomUtils,math + if hasattr(shape,"CenterOfMass"): + v = shape.CenterOfMass else: - normal = default - return normal,length,width,height - - def getPlacement(self,obj): - "returns a total placement for the profile of this component" + v = shape.BoundBox.Center + n = DraftGeomUtils.getNormal(shape) + r = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),n) + if round(r.Angle,8) == round(math.pi,8): + r = FreeCAD.Rotation() + shape = shape.copy() + shape.translate(v.negative()) + shape.rotate(FreeCAD.Vector(0,0,0),r.inverted().Axis,math.degrees(r.inverted().Angle)) p = FreeCAD.Placement() - if obj.Base: - p = obj.Base.Placement.multiply(p) - else: - if Draft.getType(obj) == "Structure": - n,l,w,h = self.getDefaultValues(obj) - if l > h: - p.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90) - p = obj.Placement.multiply(p) - return p + p.Base = v + p.Rotation = r + return (shape,p) def hideSubobjects(self,obj,prop): "Hides subobjects when a subobject lists change" @@ -701,33 +549,42 @@ class Component: return if not obj.Shape.Faces: return + import Drawing,Part a = 0 fset = [] - for f in obj.Shape.Faces: - ang = f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1)) - if (ang > 1.57) and (ang < 1.571): - a += f.Area - if ang < 1.5707: - fset.append(f) + for i,f in enumerate(obj.Shape.Faces): + try: + ang = f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1)) + except Part.OCCError: + print "Debug: Error computing areas for ",obj.Label,": normalAt() Face ",i + return + else: + if (ang > 1.57) and (ang < 1.571): + a += f.Area + if ang < 1.5707: + fset.append(f) if a and hasattr(obj,"VerticalArea"): if obj.VerticalArea.Value != a: obj.VerticalArea = a if fset and hasattr(obj,"HorizontalArea"): - import Drawing,Part pset = [] for f in fset: - try: - pf = Part.Face(Part.Wire(Drawing.project(f,FreeCAD.Vector(0,0,1))[0].Edges)) - except Part.OCCError: - # error in computing the areas. Better set them to zero than show a wrong value - if obj.HorizontalArea.Value != 0: - print "Error computing areas for ",obj.Label - obj.HorizontalArea = 0 - if hasattr(obj,"PerimeterLength"): - if obj.PerimeterLength.Value != 0: - obj.PerimeterLength = 0 + if f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1)) < 0.00001: + # already horizontal + pset.append(f) else: - pset.append(pf) + try: + pf = Part.Face(Part.Wire(Drawing.project(f,FreeCAD.Vector(0,0,1))[0].Edges)) + except Part.OCCError: + # error in computing the areas. Better set them to zero than show a wrong value + if obj.HorizontalArea.Value != 0: + print "Debug: Error computing areas for ",obj.Label,": unable to project face: ",str([v.Point for v in f.Vertexes])," (face normal:",f.normalAt(0,0),")" + obj.HorizontalArea = 0 + if hasattr(obj,"PerimeterLength"): + if obj.PerimeterLength.Value != 0: + obj.PerimeterLength = 0 + else: + pset.append(pf) if pset: self.flatarea = pset.pop() for f in pset: diff --git a/src/Mod/Arch/ArchPipe.py b/src/Mod/Arch/ArchPipe.py index df0d61934..aa9dd5219 100644 --- a/src/Mod/Arch/ArchPipe.py +++ b/src/Mod/Arch/ArchPipe.py @@ -162,6 +162,7 @@ class _ArchPipe(ArchComponent.Component): ArchComponent.Component.__init__(self,obj) self.Type = "Pipe" + obj.Role = ["Pipe Segment"] obj.addProperty("App::PropertyLength", "Diameter", "Arch", QT_TRANSLATE_NOOP("App::Property","The diameter of this pipe, if not based on a profile")) obj.addProperty("App::PropertyLength", "Length", "Arch", QT_TRANSLATE_NOOP("App::Property","The length of this pipe, if not based on an edge")) obj.addProperty("App::PropertyLink", "Profile", "Arch", QT_TRANSLATE_NOOP("App::Property","An optional closed profile to base this pipe on")) @@ -275,6 +276,7 @@ class _ArchPipeConnector(ArchComponent.Component): ArchComponent.Component.__init__(self,obj) self.Type = "PipeConnector" + obj.Role = ["Pipe Fitting"] obj.addProperty("App::PropertyLength", "Radius", "Arch", QT_TRANSLATE_NOOP("App::Property","The curvature radius of this connector")) obj.addProperty("App::PropertyLinkList", "Pipes", "Arch", QT_TRANSLATE_NOOP("App::Property","The pipes linked by this connector")) obj.addProperty("App::PropertyEnumeration", "ConnectorType", "Arch", QT_TRANSLATE_NOOP("App::Property","The type of this connector")) diff --git a/src/Mod/Arch/ArchPrecast.py b/src/Mod/Arch/ArchPrecast.py index a44a2bd28..bd201683f 100644 --- a/src/Mod/Arch/ArchPrecast.py +++ b/src/Mod/Arch/ArchPrecast.py @@ -44,9 +44,9 @@ else: class _Precast(ArchComponent.Component): "The base Precast class" - + def __init__(self,obj): - + ArchComponent.Component.__init__(self,obj) obj.addProperty("App::PropertyDistance","Length","Arch",QT_TRANSLATE_NOOP("App::Property","The length of this element")) obj.addProperty("App::PropertyDistance","Width","Arch",QT_TRANSLATE_NOOP("App::Property","The width of this element")) @@ -55,25 +55,19 @@ class _Precast(ArchComponent.Component): obj.addProperty("App::PropertyVectorList","Nodes","Arch",QT_TRANSLATE_NOOP("App::Property","The structural nodes of this element")) self.Type = "Precast" obj.Role = ["Beam","Column","Panel","Slab","Stairs"] - - def getProfile(self,obj,noplacement=True): - return [] - - def getExtrusionVector(self,obj,noplacement=True): - return FreeCAD.Vector() - + def execute(self,obj): - + if self.clone(obj): return class _PrecastBeam(_Precast): - + "The Precast Beam" - + def __init__(self,obj): - + _Precast.__init__(self,obj) obj.addProperty("App::PropertyDistance","Chamfer","Arch",QT_TRANSLATE_NOOP("App::Property","The size of the chamfer of this element")) obj.addProperty("App::PropertyDistance","DentLength","Arch",QT_TRANSLATE_NOOP("App::Property","The dent length of this element")) @@ -82,7 +76,7 @@ class _PrecastBeam(_Precast): obj.Role = ["Beam"] def execute(self,obj): - + if self.clone(obj): return @@ -94,12 +88,12 @@ class _PrecastBeam(_Precast): dentlength = obj.DentLength.Value dentheight = obj.DentHeight.Value dents = obj.Dents - + if (length == 0) or (width == 0) or (height == 0): return if (chamfer >= width/2) or (chamfer >= height/2): return - + import Part p = [] if chamfer > 0: @@ -174,18 +168,18 @@ class _PrecastBeam(_Precast): class _PrecastIbeam(_Precast): - + "The Precast Ibeam" - + def __init__(self,obj): - + _Precast.__init__(self,obj) obj.addProperty("App::PropertyDistance","Chamfer","Arch",QT_TRANSLATE_NOOP("App::Property","The chamfer length of this element")) obj.addProperty("App::PropertyDistance","BeamBase","Arch",QT_TRANSLATE_NOOP("App::Property","The base length of this element")) obj.Role = ["Beam"] def execute(self,obj): - + if self.clone(obj): return @@ -195,12 +189,12 @@ class _PrecastIbeam(_Precast): height = obj.Height.Value base = obj.BeamBase.Value slant = obj.Chamfer.Value - + if (length == 0) or (width == 0) or (height == 0): return if (slant*2 >= width) or (base*2+slant*2 >= height): return - + import Part p = [] p.append(Vector(0,0,0)) @@ -225,11 +219,11 @@ class _PrecastIbeam(_Precast): class _PrecastPillar(_Precast): - + "The Precast Pillar" - + def __init__(self,obj): - + _Precast.__init__(self,obj) obj.addProperty("App::PropertyDistance","Chamfer","Arch",QT_TRANSLATE_NOOP("App::Property","The size of the chamfer of this element")) obj.addProperty("App::PropertyDistance","GrooveDepth","Arch",QT_TRANSLATE_NOOP("App::Property","The groove depth of this element")) @@ -240,10 +234,10 @@ class _PrecastPillar(_Precast): obj.Role = ["Column"] def execute(self,obj): - + if self.clone(obj): return - + pl = obj.Placement length = obj.Length.Value width = obj.Width.Value @@ -259,7 +253,7 @@ class _PrecastPillar(_Precast): return if (chamfer >= width/2) or (chamfer >= length/2): return - + import Part p = [] if chamfer > 0: @@ -341,17 +335,17 @@ class _PrecastPillar(_Precast): dentshape.translate(Vector(dentoffset,0,0)) dentshape.translate(Vector(0,0,dentlevel)) shape = shape.fuse(dentshape) - + shape = self.processSubShapes(obj,shape,pl) self.applyShape(obj,shape,pl) class _PrecastPanel(_Precast): - + "The Precast Panel" - + def __init__(self,obj): - + _Precast.__init__(self,obj) obj.addProperty("App::PropertyDistance","Chamfer","Arch",QT_TRANSLATE_NOOP("App::Property","The size of the chamfer of this element")) obj.addProperty("App::PropertyDistance","DentWidth","Arch",QT_TRANSLATE_NOOP("App::Property","The dent width of this element")) @@ -359,10 +353,10 @@ class _PrecastPanel(_Precast): obj.Role = ["Plate"] def execute(self,obj): - + if self.clone(obj): return - + pl = obj.Placement length = obj.Length.Value width = obj.Width.Value @@ -370,12 +364,12 @@ class _PrecastPanel(_Precast): chamfer = obj.Chamfer.Value dentheight = obj.DentHeight.Value dentwidth = obj.DentWidth.Value - + if (length == 0) or (width == 0) or (height == 0): return if ((chamfer+dentwidth) >= width) or (dentheight >= height): return - + import Part p = [] p.append(Vector(0,0,0)) @@ -436,17 +430,17 @@ class _PrecastPanel(_Precast): shape = shape.cut(s) s.translate(Vector(0,0,height)) shape = shape.fuse(s) - + shape = self.processSubShapes(obj,shape,pl) self.applyShape(obj,shape,pl) class _PrecastSlab(_Precast): - + "The Precast Slab" - + def __init__(self,obj): - + _Precast.__init__(self,obj) obj.addProperty("App::PropertyEnumeration","SlabType","Arch",QT_TRANSLATE_NOOP("App::Property","The type of this slab")) obj.addProperty("App::PropertyDistance","SlabBase","Arch",QT_TRANSLATE_NOOP("App::Property","The size of the base of this element")) @@ -458,7 +452,7 @@ class _PrecastSlab(_Precast): obj.SlabType = ["Champagne","Hat"] def execute(self,obj): - + if self.clone(obj): return @@ -472,9 +466,9 @@ class _PrecastSlab(_Precast): holemajor = obj.HoleMajor.Value holeminor = obj.HoleMinor.Value holespacing = obj.HoleSpacing.Value - + slant = (height-base) / 3 # this gives the inclination of the vertical walls - + if (length == 0) or (width == 0) or (height == 0): return if base >= height: @@ -486,7 +480,7 @@ class _PrecastSlab(_Precast): if holemajor < holeminor: return import Part - + p = [] if slabtype == "Champagne": p.append(Vector(0,0,0)) @@ -512,7 +506,7 @@ class _PrecastSlab(_Precast): p = Part.makePolygon(p) f = Part.Face(p) shape = f.extrude(Vector(length,0,0)) - + if holenumber > 0: holespan = holenumber * holeminor + (holenumber - 1) * holespacing holestart = (width/2 - holespan/2) + holeminor/2 @@ -529,17 +523,17 @@ class _PrecastSlab(_Precast): s = tube.copy() s.translate(Vector(0,x,height/2)) shape = shape.cut(s) - + shape = self.processSubShapes(obj,shape,pl) self.applyShape(obj,shape,pl) class _PrecastStairs(_Precast): - + "The Precast Stairs" - + def __init__(self,obj): - + _Precast.__init__(self,obj) obj.addProperty("App::PropertyDistance","DownLength","Arch",QT_TRANSLATE_NOOP("App::Property","The length of the down floor of this element")) obj.addProperty("App::PropertyInteger","RiserNumber","Arch",QT_TRANSLATE_NOOP("App::Property","The number of risers in this element")) @@ -548,7 +542,7 @@ class _PrecastStairs(_Precast): obj.Role = ["Stairs"] def execute(self,obj): - + if self.clone(obj): return @@ -573,7 +567,7 @@ class _PrecastStairs(_Precast): return if length < tread: length = tread # minimum - + import math,Part p = [Vector(0,0,0)] # relative moves @@ -628,7 +622,7 @@ class _ViewProviderPrecast(ArchComponent.ViewProviderComponent): if self.Object.CloneOf: return ":/icons/Arch_Structure_Clone.svg" return ":/icons/Arch_Structure_Tree.svg" - + def setEdit(self,vobj,mode): if mode == 0: import FreeCADGui @@ -643,7 +637,7 @@ class _ViewProviderPrecast(ArchComponent.ViewProviderComponent): FreeCADGui.Control.showDialog(taskd) return True return False - + def unsetEdit(self,vobj,mode): import FreeCADGui if hasattr(self,"dentd"): @@ -654,18 +648,18 @@ class _ViewProviderPrecast(ArchComponent.ViewProviderComponent): class _PrecastTaskPanel: - + '''The TaskPanel for precast creation''' - + def __init__(self): - + import FreeCADGui from PySide import QtCore,QtGui,QtSvg self.form = QtGui.QWidget() self.grid = QtGui.QGridLayout(self.form) self.PrecastTypes = ["Beam","I-Beam","Pillar","Panel","Slab","Stairs"] self.SlabTypes = ["Champagne","Hat"] - + # image display self.preview = QtSvg.QSvgWidget(":/ui/ParametersBeam.svg") self.preview.setMaximumWidth(200) @@ -679,67 +673,67 @@ class _PrecastTaskPanel: self.valueSlabType.setCurrentIndex(0) self.grid.addWidget(self.labelSlabType,1,0,1,1) self.grid.addWidget(self.valueSlabType,1,1,1,1) - + self.labelChamfer = QtGui.QLabel() self.valueChamfer = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelChamfer,2,0,1,1) self.grid.addWidget(self.valueChamfer,2,1,1,1) - + self.labelDentLength = QtGui.QLabel() self.valueDentLength = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelDentLength,3,0,1,1) self.grid.addWidget(self.valueDentLength,3,1,1,1) - + self.labelDentWidth = QtGui.QLabel() self.valueDentWidth = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelDentWidth,4,0,1,1) self.grid.addWidget(self.valueDentWidth,4,1,1,1) - + self.labelDentHeight = QtGui.QLabel() self.valueDentHeight = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelDentHeight,5,0,1,1) self.grid.addWidget(self.valueDentHeight,5,1,1,1) - + self.labelBase = QtGui.QLabel() self.valueBase = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelBase,6,0,1,1) self.grid.addWidget(self.valueBase,6,1,1,1) - + self.labelHoleNumber = QtGui.QLabel() self.valueHoleNumber = QtGui.QSpinBox() self.grid.addWidget(self.labelHoleNumber,7,0,1,1) self.grid.addWidget(self.valueHoleNumber,7,1,1,1) - + self.labelHoleMajor = QtGui.QLabel() self.valueHoleMajor = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelHoleMajor,8,0,1,1) self.grid.addWidget(self.valueHoleMajor,8,1,1,1) - + self.labelHoleMinor = QtGui.QLabel() self.valueHoleMinor = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelHoleMinor,9,0,1,1) self.grid.addWidget(self.valueHoleMinor,9,1,1,1) - + self.labelHoleSpacing = QtGui.QLabel() self.valueHoleSpacing = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelHoleSpacing,10,0,1,1) self.grid.addWidget(self.valueHoleSpacing,10,1,1,1) - + self.labelGrooveNumber = QtGui.QLabel() self.valueGrooveNumber = QtGui.QSpinBox() self.grid.addWidget(self.labelGrooveNumber,11,0,1,1) self.grid.addWidget(self.valueGrooveNumber,11,1,1,1) - + self.labelGrooveDepth = QtGui.QLabel() self.valueGrooveDepth = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelGrooveDepth,12,0,1,1) self.grid.addWidget(self.valueGrooveDepth,12,1,1,1) - + self.labelGrooveHeight = QtGui.QLabel() self.valueGrooveHeight = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelGrooveHeight,13,0,1,1) self.grid.addWidget(self.valueGrooveHeight,13,1,1,1) - + self.labelGrooveSpacing = QtGui.QLabel() self.valueGrooveSpacing = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelGrooveSpacing,14,0,1,1) @@ -749,17 +743,17 @@ class _PrecastTaskPanel: self.valueRiserNumber = QtGui.QSpinBox() self.grid.addWidget(self.labelRiserNumber,15,0,1,1) self.grid.addWidget(self.valueRiserNumber,15,1,1,1) - + self.labelDownLength = QtGui.QLabel() self.valueDownLength = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelDownLength,16,0,1,1) self.grid.addWidget(self.valueDownLength,16,1,1,1) - + self.labelRiser = QtGui.QLabel() self.valueRiser = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelRiser,17,0,1,1) self.grid.addWidget(self.valueRiser,17,1,1,1) - + self.labelTread = QtGui.QLabel() self.valueTread = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelTread,18,0,1,1) @@ -782,7 +776,7 @@ class _PrecastTaskPanel: QtCore.QObject.connect(self.valueTread,QtCore.SIGNAL("valueChanged(double)"),self.setTread) self.retranslateUi(self.form) self.form.hide() - + def getValues(self): d = {} d["SlabType"] = self.SlabTypes[self.valueSlabType.currentIndex()] @@ -806,49 +800,49 @@ class _PrecastTaskPanel: if hasattr(self,"Dents"): d["Dents"] = self.Dents.getValues() return d - + def setChamfer(self,value): self.Chamfer = value - + def setDentLength(self,value): self.DentLength = value - + def setDentWidth(self,value): self.DentWidth = value - + def setDentHeight(self,value): self.DentHeight = value - + def setBase(self,value): self.Base = value - + def setHoleMajor(self,value): self.HoleMajor = value - + def setHoleMinor(self,value): self.HoleMinor = value - + def setHoleSpacing(self,value): self.HoleSpacing = value - + def setGrooveDepth(self,value): self.GrooveDepth = value - + def setGrooveHeight(self,value): self.GrooveHeight = value - + def setGrooveSpacing(self,value): self.GrooveSpacing = value def setDownLength(self,value): self.DownLength = value - + def setRiser(self,value): self.Riser = value - + def setTread(self,value): self.Tread = value - + def retranslateUi(self, dialog): from PySide import QtGui self.form.setWindowTitle(QtGui.QApplication.translate("Arch", "Precast elements", None, QtGui.QApplication.UnicodeUTF8)) @@ -1106,62 +1100,62 @@ class _PrecastTaskPanel: class _DentsTaskPanel: - + '''The TaskPanel for dent creation''' - + def __init__(self): - + import FreeCADGui from PySide import QtCore,QtGui,QtSvg self.form = QtGui.QWidget() self.grid = QtGui.QGridLayout(self.form) self.Rotations = ["N","S","E","O"] self.RotationAngles = [90,270,0,180] - + # dents list self.labelDents = QtGui.QLabel() self.listDents = QtGui.QListWidget() self.grid.addWidget(self.labelDents,0,0,1,2) self.grid.addWidget(self.listDents,1,0,1,2) - + # buttons self.buttonAdd = QtGui.QPushButton() self.buttonRemove = QtGui.QPushButton() self.grid.addWidget(self.buttonAdd,2,0,1,1) self.grid.addWidget(self.buttonRemove,2,1,1,1) - + # image display self.preview = QtSvg.QSvgWidget(":/ui/ParametersDent.svg") self.preview.setMaximumWidth(200) self.preview.setMinimumHeight(120) self.grid.addWidget(self.preview,3,0,1,2) - # parameters + # parameters self.labelLength = QtGui.QLabel() self.valueLength = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelLength,4,0,1,1) self.grid.addWidget(self.valueLength,4,1,1,1) - + self.labelWidth = QtGui.QLabel() self.valueWidth = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelWidth,5,0,1,1) self.grid.addWidget(self.valueWidth,5,1,1,1) - + self.labelHeight = QtGui.QLabel() self.valueHeight = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelHeight,6,0,1,1) self.grid.addWidget(self.valueHeight,6,1,1,1) - + self.labelSlant = QtGui.QLabel() self.valueSlant = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelSlant,7,0,1,1) self.grid.addWidget(self.valueSlant,7,1,1,1) - + self.labelLevel = QtGui.QLabel() self.valueLevel = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelLevel,8,0,1,1) self.grid.addWidget(self.valueLevel,8,1,1,1) - + self.labelRotation = QtGui.QLabel() self.valueRotation = QtGui.QComboBox() self.valueRotation.addItems(self.Rotations) @@ -1173,7 +1167,7 @@ class _DentsTaskPanel: self.valueOffset = FreeCADGui.UiLoader().createWidget("Gui::InputField") self.grid.addWidget(self.labelOffset,10,0,1,1) self.grid.addWidget(self.valueOffset,10,1,1,1) - + # signals/slots QtCore.QObject.connect(self.valueLength,QtCore.SIGNAL("valueChanged(double)"),self.setLength) QtCore.QObject.connect(self.valueWidth,QtCore.SIGNAL("valueChanged(double)"),self.setWidth) @@ -1187,31 +1181,31 @@ class _DentsTaskPanel: QtCore.QObject.connect(self.listDents,QtCore.SIGNAL("itemClicked(QListWidgetItem*)"),self.editDent) self.retranslateUi(self.form) self.form.hide() - + def setLength(self,value): self.Length = value self.setDent() - + def setWidth(self,value): self.Width = value self.setDent() - + def setHeight(self,value): self.Height = value self.setDent() - + def setSlant(self,value): self.Slant = value self.setDent() - + def setLevel(self,value): self.Level = value self.setDent() - + def setOffset(self,value): self.Offset = value self.setDent() - + def fillDents(self,dents): self.listDents.clear() i = 1 @@ -1219,14 +1213,14 @@ class _DentsTaskPanel: s = "Dent "+str(i)+" :"+d self.listDents.addItem(s) i += 1 - + def setDent(self,i=0): if self.listDents.currentItem(): num = str(self.listDents.currentRow()+1) rot = self.RotationAngles[self.valueRotation.currentIndex()] s = "Dent "+num+" :"+str(self.Length)+";"+str(self.Width)+";"+str(self.Height)+";"+str(self.Slant)+";"+str(self.Level)+";"+str(rot)+";"+str(self.Offset) self.listDents.currentItem().setText(s) - + def addDent(self): num = str(self.listDents.count()+1) rot = self.RotationAngles[self.valueRotation.currentIndex()] @@ -1234,11 +1228,11 @@ class _DentsTaskPanel: self.listDents.addItem(s) self.listDents.setCurrentRow(self.listDents.count()-1) self.editDent() - + def removeDent(self): if self.listDents.currentItem(): self.listDents.takeItem(self.listDents.currentRow()) - + def editDent(self,item=None): if self.listDents.currentItem(): s = self.listDents.currentItem().text() @@ -1251,7 +1245,7 @@ class _DentsTaskPanel: self.valueLevel.setText(FreeCAD.Units.Quantity(float(s[4]),FreeCAD.Units.Length).UserString) self.valueRotation.setCurrentIndex(self.RotationAngles.index(int(s[5]))) self.valueOffset.setText(FreeCAD.Units.Quantity(float(s[6]),FreeCAD.Units.Length).UserString) - + def retranslateUi(self, dialog): from PySide import QtGui self.form.setWindowTitle(QtGui.QApplication.translate("Arch", "Precast options", None, QtGui.QApplication.UnicodeUTF8)) @@ -1265,19 +1259,19 @@ class _DentsTaskPanel: self.labelLevel.setText(QtGui.QApplication.translate("Arch", "Level", None, QtGui.QApplication.UnicodeUTF8)) self.labelRotation.setText(QtGui.QApplication.translate("Arch", "Rotation", None, QtGui.QApplication.UnicodeUTF8)) self.labelOffset.setText(QtGui.QApplication.translate("Arch", "Offset", None, QtGui.QApplication.UnicodeUTF8)) - + def getValues(self): l = [] for i in range(self.listDents.count()): s = self.listDents.item(i).text() l.append(s.split(":")[1]) return l - + def makePrecast(precasttype=None,length=0,width=0,height=0,slabtype="",chamfer=0,dentlength=0,dentwidth=0,dentheight=0,dents=[],base=0,holenumber=0,holemajor=0,holeminor=0,holespacing=0,groovenumber=0,groovedepth=0,grooveheight=0,groovespacing=0,risernumber=0,downlength=0,riser=0,tread=0): - + "creates one of the precast objects in the current document" - + if precasttype == "Beam": obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Beam") _PrecastBeam(obj) @@ -1345,4 +1339,4 @@ def makePrecast(precasttype=None,length=0,width=0,height=0,slabtype="",chamfer=0 if FreeCAD.GuiUp: _ViewProviderPrecast(obj.ViewObject) return obj - + diff --git a/src/Mod/Arch/ArchSpace.py b/src/Mod/Arch/ArchSpace.py index 7c01bc882..f39d62aa2 100644 --- a/src/Mod/Arch/ArchSpace.py +++ b/src/Mod/Arch/ArchSpace.py @@ -307,7 +307,7 @@ class _Space(ArchComponent.Component): if obj.Base: if obj.Base.isDerivedFrom("Part::Feature"): if obj.Base.Shape.Solids: - shape = obj.Base.Shape.Solids[0].copy() + shape = obj.Base.Shape.copy() shape = shape.removeSplitter() # 2: if not, add all bounding boxes of considered objects and build a first shape diff --git a/src/Mod/Arch/ArchStructure.py b/src/Mod/Arch/ArchStructure.py index 9087edbf0..a7f49bb2f 100644 --- a/src/Mod/Arch/ArchStructure.py +++ b/src/Mod/Arch/ArchStructure.py @@ -374,7 +374,9 @@ class _CommandStructure: class _Structure(ArchComponent.Component): + "The Structure object" + def __init__(self,obj): ArchComponent.Component.__init__(self,obj) obj.addProperty("App::PropertyLink","Tool","Arch",QT_TRANSLATE_NOOP("App::Property","An optional extrusion path for this element")) @@ -386,7 +388,9 @@ class _Structure(ArchComponent.Component): obj.addProperty("App::PropertyVectorList","Nodes","Arch",QT_TRANSLATE_NOOP("App::Property","The structural nodes of this element")) obj.addProperty("App::PropertyString","Profile","Arch",QT_TRANSLATE_NOOP("App::Property","A description of the standard profile this element is based upon")) obj.addProperty("App::PropertyDistance","NodesOffset","Arch",QT_TRANSLATE_NOOP("App::Property","Offset distance between the centerline and the nodes line")) + obj.addProperty("App::PropertyEnumeration","FaceMaker","Arch",QT_TRANSLATE_NOOP("App::Property","The facemaker type to use to build the profile of this object")) self.Type = "Structure" + obj.FaceMaker = ["None","Simple","Cheese","Bullseye"] obj.Role = Roles def execute(self,obj): @@ -397,11 +401,22 @@ class _Structure(ArchComponent.Component): if self.clone(obj): return - normal,length,width,height = self.getDefaultValues(obj) - - # creating base shape - pl = obj.Placement + import Part, DraftGeomUtils base = None + pl = obj.Placement + extdata = self.getExtrusionData(obj) + if extdata: + base = extdata[0] + base.Placement = extdata[2].multiply(base.Placement) + extv = extdata[2].Rotation.multVec(extdata[1]) + if obj.Tool: + try: + base = obj.Tool.Shape.copy().makePipe(obj.Base.Shape.copy()) + except Part.OCCError: + FreeCAD.Console.PrintError(translate("Arch","Error: The base shape couldn't be extruded along this tool object")+"\n") + return + else: + base = base.extrude(extv) if obj.Base: if obj.Base.isDerivedFrom("Part::Feature"): if obj.Base.Shape.isNull(): @@ -410,42 +425,8 @@ class _Structure(ArchComponent.Component): if not obj.Base.Shape.Solids: # let pass invalid objects if they have solids... return - if hasattr(obj,"Tool"): - if obj.Tool: - try: - base = obj.Tool.Shape.copy().makePipe(obj.Base.Shape.copy()) - except Part.OCCError: - FreeCAD.Console.PrintError(translate("Arch","Error: The base shape couldn't be extruded along this tool object")) - return - if not base: - if not height: - return - if obj.Normal == Vector(0,0,0): - if len(obj.Base.Shape.Faces) > 0 : - normal=obj.Base.Shape.Faces[0].normalAt(.5,.5) - else: - normal = DraftGeomUtils.getNormal(obj.Base.Shape) - if not normal: - normal = FreeCAD.Vector(0,0,1) - #p = FreeCAD.Placement(obj.Base.Placement) - #normal = p.Rotation.multVec(normal) - else: - normal = Vector(obj.Normal) - normal = normal.multiply(height) + elif obj.Base.Shape.Solids: base = obj.Base.Shape.copy() - if base.Solids: - pass - elif base.Faces: - base = base.extrude(normal) - elif (len(base.Wires) == 1): - if base.Wires[0].isClosed(): - try: - base = Part.Face(base.Wires[0]) - base = base.extrude(normal) - except Part.OCCError: - FreeCAD.Console.PrintError(obj.Label+" : "+str(translate("Arch","Unable to extrude the base shape\n"))) - return - elif obj.Base.isDerivedFrom("Mesh::Feature"): if obj.Base.Mesh.isSolid(): if obj.Base.Mesh.countComponents() == 1: @@ -453,54 +434,133 @@ class _Structure(ArchComponent.Component): if sh.isClosed() and sh.isValid() and sh.Solids and (not sh.isNull()): base = sh else: - FreeCAD.Console.PrintWarning(str(translate("Arch","This mesh is an invalid solid"))) + FreeCAD.Console.PrintWarning(translate("Arch","This mesh is an invalid solid")+"\n") obj.Base.ViewObject.show() - else: - base = self.getProfiles(obj) - if base: - if length > height: - normal = normal.multiply(length) - else: - normal = normal.multiply(height) - base = Part.Face(base[0]) - base = base.extrude(normal) + if not base: + FreeCAD.Console.PrintError(translate("Arch","Error: Invalid base object")+"\n") + return base = self.processSubShapes(obj,base,pl) self.applyShape(obj,base,pl) + + def getExtrusionData(self,obj): + """returns (shape,extrusion vector,placement) or None""" + import Part,DraftGeomUtils + data = ArchComponent.Component.getExtrusionData(self,obj) + if data: + return data + length = obj.Length.Value + width = obj.Width.Value + height = obj.Height.Value + normal = None + if not height: + for p in obj.InList: + if Draft.getType(p) == "Floor": + if p.Height.Value: + height = p.Height.Value + base = None + placement = None + if obj.Base: + if obj.Base.isDerivedFrom("Part::Feature"): + if obj.Base.Shape: + if obj.Base.Shape.Solids: + return None + elif obj.Base.Shape.Faces: + if not DraftGeomUtils.isCoplanar(obj.Base.Shape.Faces): + return None + else: + base,placement = self.rebase(obj.Base.Shape) + normal = obj.Base.Shape.Faces[0].normalAt(0,0) + elif obj.Base.Shape.Wires: + baseface = None + if hasattr(obj,"FaceMaker"): + if obj.FaceMaker != "None": + try: + baseface = Part.makeFace(obj.Base.Shape.Wires,"Part::FaceMaker"+str(obj.FaceMaker)) + except: + FreeCAD.Console.PrintError(translate("Arch","Facemaker returned an error")+"\n") + return None + normal = baseface.normalAt(0,0) + if not baseface: + for w in obj.Base.Shape.Wires: + w.fix(0.1,0,1) # fixes self-intersecting wires + f = Part.Face(sh) + if baseface: + baseface = baseface.fuse(f) + else: + baseface = f + normal = f.normalAt(0,0) + base,placement = self.rebase(baseface) + elif (len(obj.Base.Shape.Edges) == 1) and (len(obj.Base.Shape.Vertexes) == 1): + # closed edge + w = Part.Wire(obj.Base.Shape.Edges[0]) + baseface = Part.Face(w) + base,placement = self.rebase(baseface) + elif length and width and height: + if (length > height) and (obj.Role != "Slab"): + h2 = height/2 or 0.5 + w2 = width/2 or 0.5 + v1 = Vector(0,-w2,-h2) + v2 = Vector(0,-w2,h2) + v3 = Vector(0,w2,h2) + v4 = Vector(0,w2,-h2) + else: + l2 = length/2 or 0.5 + w2 = width/2 or 0.5 + v1 = Vector(-l2,-w2,0) + v2 = Vector(l2,-w2,0) + v3 = Vector(l2,w2,0) + v4 = Vector(-l2,w2,0) + import Part + baseface = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1])) + base,placement = self.rebase(baseface) + if base and placement: + if obj.Normal == Vector(0,0,0): + if not normal: + normal = Vector(0,0,1) + else: + normal = Vector(obj.Normal) + if (length > height) and (obj.Role != "Slab"): + extrusion = normal.multiply(length) + else: + extrusion = normal.multiply(height) + return (base,extrusion,placement) + return None def onChanged(self,obj,prop): self.hideSubobjects(obj,prop) if prop in ["Shape","ResetNodes","NodesOffset"]: # ResetNodes is not a property but it allows us to use this function to force reset the nodes - if hasattr(obj,"Nodes"): - # update structural nodes - offset = FreeCAD.Vector() - if hasattr(obj,"NodesOffset"): - offset = FreeCAD.Vector(0,0,obj.NodesOffset.Value) - if obj.Nodes and (prop != "ResetNodes"): - if hasattr(self,"nodes"): - if self.nodes: - if obj.Nodes != self.nodes: - # nodes are set manually: don't touch them - return + nodes = None + extdata = self.getExtrusionData(obj) + if extdata: + nodes = extdata[0] + nodes.Placement = nodes.Placement.multiply(extdata[2]) + if obj.Role not in ["Slab"]: + if obj.Tool: + nodes = obj.Tool.Shape else: - # nodes haven't been calculated yet, but are set (file load) - # we calculate the nodes now but don't change the property - if obj.Role in ["Slab"]: - nodes = self.getProfiles(obj)[0] - else: - nodes = self.getAxis(obj) - if nodes: - self.nodes = [v.Point.add(offset) for v in nodes.Vertexes] + import Part + nodes = Part.Line(nodes.CenterOfMass,nodes.CenterOfMass.add(extdata[1])).toShape() + offset = FreeCAD.Vector() + if hasattr(obj,"NodesOffset"): + offset = FreeCAD.Vector(0,0,obj.NodesOffset.Value) + if obj.Nodes and (prop != "ResetNodes"): + if hasattr(self,"nodes"): + if self.nodes: + if obj.Nodes != self.nodes: + # nodes are set manually: don't touch them return - # we calculate and set the nodes - if obj.Role in ["Slab"]: - nodes = self.getProfiles(obj)[0] else: - nodes = self.getAxis(obj) - if nodes: - self.nodes = [v.Point.add(offset) for v in nodes.Vertexes] - obj.Nodes = self.nodes + # nodes haven't been calculated yet, but are set (file load) + # we set the nodes now but don't change the property + if nodes: + self.nodes = [v.Point.add(offset) for v in nodes.Vertexes] + return + # we set the nodes + if nodes: + self.nodes = [v.Point.add(offset) for v in nodes.Vertexes] + obj.Nodes = self.nodes def getNodeEdges(self,obj): "returns a list of edges from stuctural nodes" diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index 2aac7bfc6..a21a461ce 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -429,13 +429,15 @@ class _Wall(ArchComponent.Component): return import Part, DraftGeomUtils - pl = obj.Placement - normal,length,width,height = self.getDefaultValues(obj) base = None - face = None - + pl = obj.Placement + extdata = self.getExtrusionData(obj) + if extdata: + base = extdata[0] + base.Placement = extdata[2].multiply(base.Placement) + extv = extdata[2].Rotation.multVec(extdata[1]) + base = base.extrude(extv) if obj.Base: - # computing a shape from a base object if obj.Base.isDerivedFrom("Part::Feature"): if obj.Base.Shape.isNull(): return @@ -443,51 +445,8 @@ class _Wall(ArchComponent.Component): if not obj.Base.Shape.Solids: # let pass invalid objects if they have solids... return - - if hasattr(obj,"Face"): - if obj.Face > 0: - if len(obj.Base.Shape.Faces) >= obj.Face: - face = obj.Base.Shape.Faces[obj.Face-1] - if face: - # case 1: this wall is based on a specific face of its base object - normal = face.normalAt(0,0) - if normal.getAngle(Vector(0,0,1)) > math.pi/4: - normal.multiply(width) - base = face.extrude(normal) - if obj.Align == "Center": - base.translate(normal.negative().multiply(0.5)) - elif obj.Align == "Right": - base.translate(normal.negative()) - else: - normal.multiply(height) - base = face.extrude(normal) elif obj.Base.Shape.Solids: - # case 2: the base is already a solid base = obj.Base.Shape.copy() - elif obj.Base.Shape.Edges: - # case 3: the base is flat, we need to extrude it - if not obj.Base.Shape.Faces: - # set the length property - if hasattr(obj.Base.Shape,"Length"): - l = obj.Base.Shape.Length - if obj.Length != l: - obj.Length = l - profiles = self.getProfiles(obj) - 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"))) - elif obj.Base.isDerivedFrom("Mesh::Feature"): if obj.Base.Mesh.isSolid(): if obj.Base.Mesh.countComponents() == 1: @@ -495,16 +454,25 @@ class _Wall(ArchComponent.Component): if sh.isClosed() and sh.isValid() and sh.Solids and (not sh.isNull()): base = sh else: - FreeCAD.Console.PrintWarning(str(translate("Arch","This mesh is an invalid solid"))) + FreeCAD.Console.PrintWarning(translate("Arch","This mesh is an invalid solid")+"\n") obj.Base.ViewObject.show() - else: - # computing a shape from scratch - if length and width and height: - base = Part.makeBox(length,width,height) + if not base: + FreeCAD.Console.PrintError(translate("Arch","Error: Invalid base object")+"\n") + return base = self.processSubShapes(obj,base,pl) self.applyShape(obj,base,pl) + # set the length property + if obj.Base: + if obj.Base.isDerivedFrom("Part::Feature"): + if obj.Base.Shape.Edges: + if not obj.Base.Shape.Faces: + if hasattr(obj.Base.Shape,"Length"): + l = obj.Base.Shape.Length + if obj.Length.Value != l: + obj.Length = l + def onChanged(self,obj,prop): self.hideSubobjects(obj,prop) ArchComponent.Component.onChanged(self,obj,prop) @@ -517,7 +485,115 @@ class _Wall(ArchComponent.Component): if abs(abs(f.CenterOfMass.z) - abs(obj.Shape.BoundBox.ZMin)) < 0.001: faces.append(f) return faces - + + def getExtrusionData(self,obj): + """returns (shape,extrusion vector,placement) or None""" + import Part,DraftGeomUtils + data = ArchComponent.Component.getExtrusionData(self,obj) + if data: + return data + length = obj.Length.Value + width = obj.Width.Value + height = obj.Height.Value + if not height: + for p in obj.InList: + if Draft.getType(p) == "Floor": + if p.Height.Value: + height = p.Height.Value + if obj.Normal == Vector(0,0,0): + normal = Vector(0,0,1) + else: + normal = Vector(obj.Normal) + base = None + placement = None + basewires = None + if obj.Base: + if obj.Base.isDerivedFrom("Part::Feature"): + if obj.Base.Shape: + if obj.Base.Shape.Solids: + return None + elif obj.Face > 0: + if len(obj.Base.Shape.Faces) >= obj.Face: + face = obj.Base.Shape.Faces[obj.Face-1] + # this wall is based on a specific face of its base object + normal = face.normalAt(0,0) + if normal.getAngle(Vector(0,0,1)) > math.pi/4: + normal.multiply(width) + base = face.extrude(normal) + if obj.Align == "Center": + base.translate(normal.negative().multiply(0.5)) + elif obj.Align == "Right": + base.translate(normal.negative()) + else: + normal.multiply(height) + base = face.extrude(normal) + base,placement = self.rebase(base) + return (base,normal,placement) + elif obj.Base.Shape.Faces: + if not DraftGeomUtils.isCoplanar(obj.Base.Shape.Faces): + return None + else: + base,placement = self.rebase(obj.Base.Shape) + elif obj.Base.Shape.Wires: + basewires = obj.Base.Shape.Wires + elif len(obj.Base.Shape.Edges) == 1: + basewires = [Part.Wire(obj.Base.Shape.Edges)] + if basewires and width: + baseface = None + 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(normal) + if not DraftVecUtils.isNull(dvec): + dvec.normalize() + sh = None + if obj.Align == "Left": + dvec.multiply(width) + if obj.Offset.Value: + dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value) + wire = DraftGeomUtils.offsetWire(wire,dvec2) + w2 = DraftGeomUtils.offsetWire(wire,dvec) + w1 = Part.Wire(Part.__sortEdges__(wire.Edges)) + sh = DraftGeomUtils.bind(w1,w2) + elif obj.Align == "Right": + dvec.multiply(width) + dvec = dvec.negative() + if obj.Offset.Value: + dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value) + wire = DraftGeomUtils.offsetWire(wire,dvec2) + w2 = DraftGeomUtils.offsetWire(wire,dvec) + w1 = Part.Wire(Part.__sortEdges__(wire.Edges)) + sh = DraftGeomUtils.bind(w1,w2) + elif obj.Align == "Center": + dvec.multiply(width/2) + w1 = DraftGeomUtils.offsetWire(wire,dvec) + dvec = dvec.negative() + w2 = DraftGeomUtils.offsetWire(wire,dvec) + sh = DraftGeomUtils.bind(w1,w2) + if sh: + sh.fix(0.1,0,1) # fixes self-intersecting wires + f = Part.Face(sh) + if baseface: + baseface = baseface.fuse(f) + else: + baseface = f + if baseface: + base,placement = self.rebase(baseface) + else: + l2 = length/2 or 0.5 + w2 = width/2 or 0.5 + v1 = Vector(-l2,-w2,0) + v2 = Vector(l2,-w2,0) + v3 = Vector(l2,w2,0) + v4 = Vector(-l2,w2,0) + base = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1])) + placement = FreeCAD.Placement() + if base and placement: + extrusion = normal.multiply(height) + return (base,extrusion,placement) + return None class _ViewProviderWall(ArchComponent.ViewProviderComponent): "A View Provider for the Wall object" diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index d55ba24b7..fee06f0c1 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -33,18 +33,20 @@ 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"], - "Structure": ["IfcBeam", "IfcBeamStandardCase", "IfcColumn", "IfcColumnStandardCase", "IfcSlab", "IfcFooting", "IfcPile", "IfcTendon"], - "Wall": ["IfcWall", "IfcWallStandardCase", "IfcCurtainWall"], - "Window": ["IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"], - "Roof": ["IfcRoof"], - "Stairs": ["IfcStair", "IfcStairFlight", "IfcRamp", "IfcRampFlight"], - "Space": ["IfcSpace"], - "Rebar": ["IfcReinforcingBar"], - "Panel": ["IfcPlate"], - "Equipment": ["IfcFurnishingElement","IfcSanitaryTerminal","IfcFlowTerminal","IfcElectricAppliance"] +typesmap = { "Site": ["IfcSite"], + "Building": ["IfcBuilding"], + "Floor": ["IfcBuildingStorey"], + "Structure": ["IfcBeam", "IfcBeamStandardCase", "IfcColumn", "IfcColumnStandardCase", "IfcSlab", "IfcFooting", "IfcPile", "IfcTendon"], + "Wall": ["IfcWall", "IfcWallStandardCase", "IfcCurtainWall"], + "Window": ["IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"], + "Roof": ["IfcRoof"], + "Stairs": ["IfcStair", "IfcStairFlight", "IfcRamp", "IfcRampFlight"], + "Space": ["IfcSpace"], + "Rebar": ["IfcReinforcingBar"], + "Panel": ["IfcPlate"], + "Equipment": ["IfcFurnishingElement","IfcSanitaryTerminal","IfcFlowTerminal","IfcElectricAppliance"], + "Pipe": ["IfcPipeSegment"], + "PipeConnector":["IfcPipeFitting"] } # which IFC entity (product) is a structural object @@ -55,7 +57,7 @@ structuralifcobjects = ( "IfcStructuralLinearAction", "IfcStructuralLinearActionVarying", "IfcStructuralPlanarAction" ) -# specific name translations +# specific FreeCAD <-> IFC slang translations translationtable = { "Foundation":"Footing", "Floor":"BuildingStorey", "Rebar":"ReinforcingBar", @@ -63,7 +65,9 @@ translationtable = { "Foundation":"Footing", "ElectricEquipment":"ElectricAppliance", "Furniture":"FurnishingElement", "Stair Flight":"StairFlight", - "Curtain Wall":"CurtainWall" + "Curtain Wall":"CurtainWall", + "Pipe Segment":"PipeSegment", + "Pipe Fitting":"PipeFitting" } ifctemplate = """ISO-10303-21; @@ -785,7 +789,7 @@ def insert(filename,docname,skip=[],only=[],root=None): else: if DEBUG: print "no group name specified for entity: #", ifcfile[host].id(), ", entity type is used!" grp_name = ifcfile[host].is_a() + "_" + str(ifcfile[host].id()) - grp = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",grp_name) + grp = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",grp_name.encode("utf8")) objects[host] = grp for child in children: if child in objects.keys(): @@ -962,7 +966,7 @@ def export(exportList,filename): of.write(template.encode("utf8")) of.close() os.close(templatefilehandle) - global ifcfile, surfstyles, clones, sharedobjects + global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs ifcfile = ifcopenshell.open(templatefile) history = ifcfile.by_type("IfcOwnerHistory")[0] context = ifcfile.by_type("IfcGeometricRepresentationContext")[0] @@ -985,6 +989,8 @@ def export(exportList,filename): sharedobjects = {} # { BaseName: IfcRepresentationMap } count = 1 groups = {} # { Host: [Child,Child,...] } + profiledefs = {} # { ProfileDefString:profiledef,...} + shapedefs = {} # { ShapeDefString:[shapes],... } # build clones table if CREATE_CLONES: @@ -1318,6 +1324,7 @@ def export(exportList,filename): # 2D objects if EXPORT_2D: + curvestyles = {} if annotations and DEBUG: print "exporting 2D objects..." for anno in annotations: xvc = ifcfile.createIfcDirection((1.0,0.0,0.0)) @@ -1342,6 +1349,22 @@ def export(exportList,filename): tpl = ifcfile.createIfcAxis2Placement3D(pos,None,None) txt = ifcfile.createIfcTextLiteral(";".join(anno.LabelText).encode("utf8"),tpl,"LEFT") reps = [txt] + + for coldef in ["LineColor","TextColor","ShapeColor"]: + if hasattr(obj.ViewObject,coldef): + rgb = getattr(obj.ViewObject,coldef)[:3] + if rgb in curvestyles: + psa = curvestyles[rgb] + else: + col = ifcfile.createIfcColourRgb(None,rgb[0],rgb[1],rgb[2]) + cvf = ifcfile.createIfcDraughtingPredefinedCurveFont("CONTINUOUS") + ics = ifcfile.createIfcCurveStyle('Line',cvf,None,col) + psa = ifcfile.createIfcPresentationStyleAssignment([ics]) + curvestyles[rgb] = psa + for rep in reps: + isi = ifcfile.createIfcStyledItem(rep,[psa],None) + break + shp = ifcfile.createIfcShapeRepresentation(context,'Annotation','Annotation2D',reps) rep = ifcfile.createIfcProductDefinitionShape(None,None,[shp]) ann = ifcfile.createIfcAnnotation(ifcopenshell.guid.compress(uuid.uuid1().hex),history,anno.Label.encode('utf8'),'',None,gpl,rep) @@ -1390,7 +1413,7 @@ def createCurve(ifcfile,wire): if da > 0: follow = not(follow) xvc = ifcfile.createIfcDirection((1.0,0.0)) - ovc = ifcfile.createIfcCartesianPoint(tuple(e.Curve.Center)[:2]) + ovc = ifcfile.createIfcCartesianPoint(tuple(e.Curve.Center)) plc = ifcfile.createIfcAxis2Placement2D(ovc,xvc) cir = ifcfile.createIfcCircle(plc,e.Curve.Radius) curve = ifcfile.createIfcTrimmedCurve(cir,[ifcfile.createIfcParameterValue(p1)],[ifcfile.createIfcParameterValue(p2)],follow,"PARAMETER") @@ -1404,7 +1427,7 @@ def createCurve(ifcfile,wire): last = e.Vertexes[-1].Point else: last = e.Vertexes[-1].Point - pts = [ifcfile.createIfcCartesianPoint(tuple(v)[:2]) for v in verts] + pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts] curve = ifcfile.createIfcPolyline(pts) segment = ifcfile.createIfcCompositeCurveSegment("CONTINUOUS",True,curve) segments.append(segment) @@ -1422,6 +1445,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess productdef = None shapetype = "no shape" tostore = False + subplacement = None # check for clones if (not subtraction) and (not forcebrep): @@ -1447,67 +1471,62 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess if (not shapes) and (not forcebrep): profile = None if hasattr(obj,"Proxy"): - if hasattr(obj.Proxy,"getProfiles"): - p = obj.Proxy.getProfiles(obj,noplacement=True) - extrusionv = obj.Proxy.getExtrusionVector(obj,noplacement=True) - if not DraftVecUtils.isNull(extrusionv): - extrusionv.multiply(0.001) # to meters - if (len(p) == 1) and extrusionv: - p = p[0].copy() - p.scale(0.001) # to meters - r = obj.Proxy.getPlacement(obj) - r.Base = r.Base.multiply(0.001) # to meters - d = DraftGeomUtils.getNormal(p.Wires[0]) - if r.isNull() and ( (p.CenterOfMass.z > 0.001) or ( (d.getAngle(FreeCAD.Vector(0,0,1)) > 0.001) and (d.getAngle(FreeCAD.Vector(0,0,1)) < 3.14159) ) ): - # the object placement is null, but the profile is not in the XY plane. - npla = FreeCAD.Placement() - npla.Base = p.Vertexes[0].Point - nrot = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),d) - npla.Rotation = nrot - r = npla - # p.Placement = p.Placement.multiply(npla.inverse()) # move the profile to origin - not working?? - p.translate(p.Vertexes[0].Point.negative()) - p.rotate(FreeCAD.Vector(0,0,0),nrot.inverted().Axis,math.degrees(nrot.inverted().Angle)) - extrusionv = nrot.inverted().multVec(extrusionv) # move the extrusion vector to Z axis, mandatory in IFC - + if hasattr(obj.Proxy,"getExtrusionData"): + extdata = obj.Proxy.getExtrusionData(obj) + if extdata: + # convert to meters + p = extdata[0] + p.scale(0.001) + ev = extdata[1] + ev.multiply(0.001) + pl = extdata[2] + pl.Base = pl.Base.multiply(0.001) + pstr = str([v.Point for v in extdata[0].Vertexes]) + if pstr in profiledefs: + profile = profiledefs[pstr] + shapetype = "reusing profile" + else: if len(p.Edges) == 1: - pxvc = ifcfile.createIfcDirection((1.0,0.0)) povc = ifcfile.createIfcCartesianPoint((0.0,0.0)) pt = ifcfile.createIfcAxis2Placement2D(povc,pxvc) - - # extruded circle if isinstance(p.Edges[0].Curve,Part.Circle): + # extruded circle profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt, p.Edges[0].Curve.Radius) - - # extruded ellipse elif isinstance(p.Edges[0].Curve,Part.Ellipse): + # extruded ellipse profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt, p.Edges[0].Curve.MajorRadius, p.Edges[0].Curve.MinorRadius) - else: curves = False for e in p.Edges: if isinstance(e.Curve,Part.Circle): curves = True - - # extruded polyline if not curves: - w = Part.Wire(Part.__sortEdges__(p.Edges)) + # extruded polyline + w = Part.Wire(Part.__sortEdges__(p.Wires[0].Edges)) pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] pol = ifcfile.createIfcPolyline(pts) - - # extruded composite curve else: + # extruded composite curve pol = createCurve(ifcfile,p) profile = ifcfile.createIfcArbitraryClosedProfileDef("AREA",None,pol) + if profile: + profiledefs[pstr] = profile - if profile and not(DraftVecUtils.isNull(extrusionv)): - xvc = ifcfile.createIfcDirection(tuple(r.Rotation.multVec(FreeCAD.Vector(1,0,0)))) - zvc = ifcfile.createIfcDirection(tuple(r.Rotation.multVec(FreeCAD.Vector(0,0,1)))) - ovc = ifcfile.createIfcCartesianPoint(tuple(r.Base)) + if profile and not(DraftVecUtils.isNull(ev)): + #ev = pl.Rotation.inverted().multVec(ev) + #print "ev:",ev + if not tostore: + # add the object placement to the profile placement. Otherwise it'll be done later at map insert + pl2 = FreeCAD.Placement(obj.Placement) + pl2.Base = pl2.Base.multiply(0.001) + pl = pl2.multiply(pl) + xvc = ifcfile.createIfcDirection(tuple(pl.Rotation.multVec(FreeCAD.Vector(1,0,0)))) + zvc = ifcfile.createIfcDirection(tuple(pl.Rotation.multVec(FreeCAD.Vector(0,0,1)))) + ovc = ifcfile.createIfcCartesianPoint(tuple(pl.Base)) lpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc) - edir = ifcfile.createIfcDirection(tuple(FreeCAD.Vector(extrusionv).normalize())) - shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,extrusionv.Length) + edir = ifcfile.createIfcDirection(tuple(FreeCAD.Vector(ev).normalize())) + shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,ev.Length) shapes.append(shape) solidType = "SweptSolid" shapetype = "extrusion" @@ -1521,113 +1540,131 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess if hasattr(obj.Proxy,"getSubVolume"): fcshape = obj.Proxy.getSubVolume(obj) if not fcshape: - if hasattr(obj,"Shape"): - if obj.Shape: - if not obj.Shape.isNull(): - fcshape = obj.Shape - elif hasattr(obj,"Terrain"): - if obj.Terrain: - if hasattr(obj.Terrain,"Shape"): - if obj.Terrain.Shape: - if not obj.Terrain.Shape.isNull(): - fcshape = obj.Terrain.Shape + if obj.isDerivedFrom("Part::Feature"): + if False: # below is buggy. No way to duplicate shapes that way? + #if hasattr(obj,"Base") and hasattr(obj,"Additions")and hasattr(obj,"Subtractions"): + if obj.Base and (not obj.Additions) and not(obj.Subtractions): + if obj.Base.isDerivedFrom("Part::Feature"): + if obj.Base.Shape: + if obj.Base.Shape.Solids: + fcshape = obj.Base.Shape + subplacement = FreeCAD.Placement(obj.Placement) + if not fcshape: + if obj.Shape: + if not obj.Shape.isNull(): + fcshape = obj.Shape if fcshape: - solids = [] - if fcshape.Solids: - dataset = fcshape.Solids + shapedef = str([v.Point for v in fcshape.Vertexes]) + if shapedef in shapedefs: + shapes = shapedefs[shapedef] + shapetype = "reusing brep" else: - dataset = fcshape.Shells - if DEBUG: print "Warning! object contains no solids" - - # if this is a clone, place back the shapes in null position - if tostore: - for shape in dataset: - shape.Placement = FreeCAD.Placement() - - # new ifcopenshell serializer - from ifcopenshell import geom - if hasattr(geom,"serialise") and obj.isDerivedFrom("Part::Feature") and SERIALIZE: - p = geom.serialise(obj.Shape.exportBrepToString()) - productdef = ifcfile.add(p) - for rep in productdef.Representations: - rep.ContextOfItems = context - xvc = ifcfile.createIfcDirection((1.0,0.0,0.0)) - zvc = ifcfile.createIfcDirection((0.0,0.0,1.0)) - ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0)) - gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc) - placement = ifcfile.createIfcLocalPlacement(None,gpl) - shapetype = "advancedbrep" - shapes = None - else: - # old method - for fcsolid in dataset: - fcsolid.scale(0.001) # to meters - faces = [] - curves = False - shapetype = "brep" - for fcface in fcsolid.Faces: - for e in fcface.Edges: - if DraftGeomUtils.geomType(e) != "Line": - try: - if e.curvatureAt(e.FirstParameter+(e.LastParameter-e.FirstParameter)/2) > 0.0001: - curves = True - break - except Part.OCCError: - pass - if curves: - joinfacets = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcJoinCoplanarFacets",False) - usedae = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcUseDaeOptions",False) - if not joinfacets: - shapetype = "triangulated" - if usedae: - import importDAE - tris = importDAE.triangulate(fcsolid) - else: - tris = fcsolid.tessellate(tessellation) - for tri in tris[1]: - pts = [ifcfile.createIfcCartesianPoint(tuple(tris[0][i])) for i in tri] - loop = ifcfile.createIfcPolyLoop(pts) - bound = ifcfile.createIfcFaceOuterBound(loop,True) - face = ifcfile.createIfcFace([bound]) - faces.append(face) - fcsolid = Part.Shape() # empty shape so below code is not executed - else: - fcsolid = Arch.removeCurves(fcsolid,dae=usedae) - if not fcsolid: - if DEBUG: print "Error: Unable to triangulate shape" - fcsolid = Part.Shape() - - for fcface in fcsolid.Faces: - loops = [] - verts = [v.Point for v in Part.Wire(Part.__sortEdges__(fcface.OuterWire.Edges)).Vertexes] - c = fcface.CenterOfMass - v1 = verts[0].sub(c) - v2 = verts[1].sub(c) - n = fcface.normalAt(0,0) - if DraftVecUtils.angle(v2,v1,n) >= 0: - verts.reverse() # inverting verts order if the direction is couterclockwise - pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts] - loop = ifcfile.createIfcPolyLoop(pts) - bound = ifcfile.createIfcFaceOuterBound(loop,True) - loops.append(bound) - for wire in fcface.Wires: - if wire.hashCode() != fcface.OuterWire.hashCode(): - verts = [v.Point for v in Part.Wire(Part.__sortEdges__(wire.Edges)).Vertexes] - v1 = verts[0].sub(c) - v2 = verts[1].sub(c) - if DraftVecUtils.angle(v2,v1,DraftVecUtils.neg(n)) >= 0: - verts.reverse() - pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts] - loop = ifcfile.createIfcPolyLoop(pts) - bound = ifcfile.createIfcFaceBound(loop,True) - loops.append(bound) - face = ifcfile.createIfcFace(loops) - faces.append(face) - - if faces: - shell = ifcfile.createIfcClosedShell(faces) - shape = ifcfile.createIfcFacetedBrep(shell) - shapes.append(shape) + # new ifcopenshell serializer + from ifcopenshell import geom + serialized = False + if hasattr(geom,"serialise") and obj.isDerivedFrom("Part::Feature") and SERIALIZE: + if obj.Shape.Faces: + sh = obj.Shape.copy() + sh.scale(0.001) # to meters + p = geom.serialise(sh.exportBrepToString()) + if p: + productdef = ifcfile.add(p) + for rep in productdef.Representations: + rep.ContextOfItems = context + xvc = ifcfile.createIfcDirection((1.0,0.0,0.0)) + zvc = ifcfile.createIfcDirection((0.0,0.0,1.0)) + ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0)) + gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc) + placement = ifcfile.createIfcLocalPlacement(None,gpl) + shapetype = "advancedbrep" + shapes = None + serialized = True + if not serialized: + # old method + solids = [] + if fcshape.Solids: + dataset = fcshape.Solids + else: + dataset = fcshape.Shells + #if DEBUG: print "Warning! object contains no solids" + + # if this is a clone, place back the shapes in null position + if tostore: + for shape in dataset: + shape.Placement = FreeCAD.Placement() + + for fcsolid in dataset: + fcsolid.scale(0.001) # to meters + faces = [] + curves = False + shapetype = "brep" + for fcface in fcsolid.Faces: + for e in fcface.Edges: + if DraftGeomUtils.geomType(e) != "Line": + try: + if e.curvatureAt(e.FirstParameter+(e.LastParameter-e.FirstParameter)/2) > 0.0001: + curves = True + break + except Part.OCCError: + pass + if curves: + joinfacets = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcJoinCoplanarFacets",False) + usedae = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcUseDaeOptions",False) + if joinfacets: + result = Arch.removeCurves(fcsolid,dae=usedae) + if result: + fcsolid = result + else: + # fall back to standard triangulation + joinfacets = False + if not joinfacets: + shapetype = "triangulated" + if usedae: + import importDAE + tris = importDAE.triangulate(fcsolid) + else: + tris = fcsolid.tessellate(tessellation) + for tri in tris[1]: + pts = [ifcfile.createIfcCartesianPoint(tuple(tris[0][i])) for i in tri] + loop = ifcfile.createIfcPolyLoop(pts) + bound = ifcfile.createIfcFaceOuterBound(loop,True) + face = ifcfile.createIfcFace([bound]) + faces.append(face) + fcsolid = Part.Shape() # empty shape so below code is not executed + + for fcface in fcsolid.Faces: + loops = [] + verts = [v.Point for v in fcface.OuterWire.OrderedVertexes] + c = fcface.CenterOfMass + v1 = verts[0].sub(c) + v2 = verts[1].sub(c) + n = fcface.normalAt(0,0) + if DraftVecUtils.angle(v2,v1,n) >= 0: + verts.reverse() # inverting verts order if the direction is couterclockwise + pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts] + loop = ifcfile.createIfcPolyLoop(pts) + bound = ifcfile.createIfcFaceOuterBound(loop,True) + loops.append(bound) + for wire in fcface.Wires: + if wire.hashCode() != fcface.OuterWire.hashCode(): + verts = [v.Point for v in wire.OrderedVertexes] + v1 = verts[0].sub(c) + v2 = verts[1].sub(c) + if DraftVecUtils.angle(v2,v1,DraftVecUtils.neg(n)) >= 0: + verts.reverse() + pts = [ifcfile.createIfcCartesianPoint(tuple(v)) for v in verts] + loop = ifcfile.createIfcPolyLoop(pts) + bound = ifcfile.createIfcFaceBound(loop,True) + loops.append(bound) + face = ifcfile.createIfcFace(loops) + faces.append(face) + + if faces: + shell = ifcfile.createIfcClosedShell(faces) + shape = ifcfile.createIfcFacetedBrep(shell) + shapes.append(shape) + + shapedefs[shapedef] = shapes if shapes: @@ -1638,7 +1675,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess ovc = ifcfile.createIfcCartesianPoint((0.0,0.0,0.0)) gpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc) repmap = ifcfile.createIfcRepresentationMap(gpl,subrep) - pla = FreeCAD.ActiveDocument.getObject(tostore).Placement + pla = obj.Placement axis1 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0)))) axis2 = ifcfile.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0)))) origin = ifcfile.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(0.001)))