#*************************************************************************** #* * #* Copyright (c) 2011 * #* Yorik van Havre * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* This program is distributed in the hope that it will be useful, * #* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Library General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with this program; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #*************************************************************************** import FreeCAD,WorkingPlane,math,Draft,ArchCommands,DraftVecUtils,ArchComponent from FreeCAD import Vector if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui from DraftTools import translate from pivy import coin from PySide.QtCore import QT_TRANSLATE_NOOP else: # \cond def translate(ctxt,txt): return txt def QT_TRANSLATE_NOOP(ctxt,txt): return txt # \endcond ## @package ArchSectionPlane # \ingroup ARCH # \brief The Section plane object and tools # # This module provides tools to build Section plane objects. # It also contains functionality to produce SVG rendering of # section planes, to be used in TechDraw and Drawing modules def makeSectionPlane(objectslist=None,name="Section"): """makeSectionPlane([objectslist]) : Creates a Section plane objects including the given objects. If no object is given, the whole document will be considered.""" if not FreeCAD.ActiveDocument: FreeCAD.Console.PrintError("No active document. Aborting\n") return obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython",name) obj.Label = translate("Arch",name) _SectionPlane(obj) if FreeCAD.GuiUp: _ViewProviderSectionPlane(obj.ViewObject) if objectslist: obj.Objects = objectslist bb = FreeCAD.BoundBox() for o in Draft.getGroupContents(objectslist): if hasattr(o,"Shape") and hasattr(o.Shape,"BoundBox"): bb.add(o.Shape.BoundBox) obj.Placement = FreeCAD.DraftWorkingPlane.getPlacement() obj.Placement.Base = bb.Center if FreeCAD.GuiUp: margin = bb.XLength*0.1 obj.ViewObject.DisplayLength = bb.XLength+margin obj.ViewObject.DisplayHeight = bb.YLength+margin return obj def makeSectionView(section,name="View"): """makeSectionView(section) : Creates a Drawing view of the given Section Plane in the active Page object (a new page will be created if none exists""" page = None for o in FreeCAD.ActiveDocument.Objects: if o.isDerivedFrom("Drawing::FeaturePage"): page = o break if not page: page = FreeCAD.ActiveDocument.addObject("Drawing::FeaturePage","Page") page.Template = Draft.getParam("template",FreeCAD.getResourceDir()+'Mod/Drawing/Templates/A3_Landscape.svg') view = FreeCAD.ActiveDocument.addObject("Drawing::FeatureViewPython",name) page.addObject(view) _ArchDrawingView(view) view.Source = section view.Label = translate("Arch","View of")+" "+section.Name return view def looksLikeDraft(o): # If there is no shape at all ignore it if not hasattr(o, 'Shape') or o.Shape.isNull(): return False # If there are solids in the object, it will be handled later # by getCutShapes if len(o.Shape.Solids) > 0: return False # If we have a shape, but no volume, it looks like a flat 2D object return o.Shape.Volume < 0.0000001 # add a little tolerance... def getCutShapes(objs,section,showHidden,groupSshapesByObject=False): import Part,DraftGeomUtils shapes = [] hshapes = [] sshapes = [] objectShapes = [] objectSshapes = [] for o in objs: if o.isDerivedFrom("Part::Feature"): if o.Shape.isNull(): pass elif section.OnlySolids: if o.Shape.isValid(): solids = [] solids.extend(o.Shape.Solids) shapes.extend(solids) objectShapes.append((o, solids)) else: print(section.Label,": Skipping invalid object:",o.Label) else: shapes.append(o.Shape) objectShapes.append((o, [o.Shape])) clip = False if hasattr(section, "Clip"): clip = section.Clip cutface,cutvolume,invcutvolume = ArchCommands.getCutVolume(section.Shape.copy(),shapes,clip) shapes =[] if cutvolume: for o, shapeList in objectShapes: tmpSshapes = [] for sh in shapeList: for sol in sh.Solids: if sol.Volume < 0: sol.reverse() c = sol.cut(cutvolume) s = sol.section(cutface) try: wires = DraftGeomUtils.findWires(s.Edges) for w in wires: f = Part.Face(w) tmpSshapes.append(f) #s = Part.Wire(s.Edges) #s = Part.Face(s) except Part.OCCError: #print "ArchDrawingView: unable to get a face" tmpSshapes.append(s) shapes.extend(c.Solids) #sshapes.append(s) if showHidden: c = sol.cut(invcutvolume) hshapes.append(c) if len(tmpSshapes) > 0: sshapes.extend(tmpSshapes) if groupSshapesByObject: objectSshapes.append((o, tmpSshapes)) if groupSshapesByObject: return shapes,hshapes,sshapes,cutface,cutvolume,invcutvolume,objectSshapes else: return shapes,hshapes,sshapes,cutface,cutvolume,invcutvolume def getFillForObject(o, defaultFill, section): if hasattr(section, 'UseMaterialColorForFill') and section.UseMaterialColorForFill: if hasattr(o, 'Material') and o.Material: material = o.Material if hasattr(material, 'Color') and material.Color: return o.Material.Color return defaultFill def getSVG(section, renderMode="Wireframe", allOn=False, showHidden=False, scale=1, rotation=0, linewidth=1, lineColor=(0.0,0.0,0.0), fontsize=1, showFill=False, fillColor=(0.8,0.8,0.8), techdraw=False,fillSpaces=False): """getSVG(section, [renderMode, allOn, showHidden, scale, rotation, linewidth, lineColor, fontsize, showFill, fillColor, techdraw, fillSpaces]): returns an SVG fragment from an Arch section plane. If allOn is True, all cut objects are shown, regardless if they are visible or not. renderMode can be Wireframe (default) or Solid to use the Arch solid renderer. If showHidden is True, the hidden geometry above the section plane is shown in dashed line. If showFill is True, the cut areas get filled with a pattern. lineColor -- Color of lines for the renderMode "Wireframe". fillColor -- If showFill is True and renderMode is "Wireframe", the cut areas are filled with fillColor. fillSpaces - If True, shows space objects as filled surfaces """ if not section.Objects: return "" import Part,DraftGeomUtils p = FreeCAD.Placement(section.Placement) direction = p.Rotation.multVec(FreeCAD.Vector(0,0,1)) objs = Draft.getGroupContents(section.Objects,walls=True,addgroups=True) if not allOn: objs = Draft.removeHidden(objs) # separate spaces and Draft objects spaces = [] nonspaces = [] drafts = [] windows = [] cutface = None for o in objs: if Draft.getType(o) == "Space": spaces.append(o) elif Draft.getType(o) in ["Dimension","Annotation","Label"]: drafts.append(o) elif o.isDerivedFrom("Part::Part2DObject"): drafts.append(o) elif looksLikeDraft(o): drafts.append(o) else: nonspaces.append(o) if Draft.getType(o) == "Window": windows.append(o) objs = nonspaces archUserParameters = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") scaledLineWidth = linewidth/scale svgLineWidth = str(scaledLineWidth) + 'px' st = archUserParameters.GetFloat("CutLineThickness",2) svgCutLineWidth = str(scaledLineWidth * st) + 'px' yt = archUserParameters.GetFloat("SymbolLineThickness",0.6) svgSymbolLineWidth = str(linewidth * yt) hiddenPattern = archUserParameters.GetString("archHiddenPattern","30,10") svgHiddenPattern = hiddenPattern.replace(" ","") fillpattern = ' 1: ss += "," ss += "FreeCAD.ActiveDocument."+o.Name ss += "]" FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Section Plane")) FreeCADGui.addModule("Arch") FreeCADGui.doCommand("section = Arch.makeSectionPlane("+ss+")") #FreeCADGui.doCommand("section.Placement = FreeCAD.DraftWorkingPlane.getPlacement()") #FreeCADGui.doCommand("Arch.makeSectionView(section)") FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() class _SectionPlane: "A section plane object" def __init__(self,obj): obj.Proxy = self self.setProperties(obj) def setProperties(self,obj): pl = obj.PropertiesList if not "Placement" in pl: obj.addProperty("App::PropertyPlacement","Placement","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The placement of this object")) if not "Shape" in pl: obj.addProperty("Part::PropertyPartShape","Shape","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The shape of this object")) if not "Objects" in pl: obj.addProperty("App::PropertyLinkList","Objects","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The objects that must be considered by this section plane. Empty means the whole document.")) if not "OnlySolids" in pl: obj.addProperty("App::PropertyBool","OnlySolids","SectionPlane",QT_TRANSLATE_NOOP("App::Property","If false, non-solids will be cut too, with possible wrong results.")) obj.OnlySolids = True if not "Clip" in pl: obj.addProperty("App::PropertyBool","Clip","SectionPlane",QT_TRANSLATE_NOOP("App::Property","If True, resulting views will be clipped to the section plane area.")) if not "UseMaterialColorForFill" in pl: obj.addProperty("App::PropertyBool","UseMaterialColorForFill","SectionPlane",QT_TRANSLATE_NOOP("App::Property","If true, the color of the objects material will be used to fill cut areas.")) obj.UseMaterialColorForFill = False self.Type = "SectionPlane" def onDocumentRestored(self,obj): self.setProperties(obj) def execute(self,obj): import Part l = 1 h = 1 if obj.ViewObject: if hasattr(obj.ViewObject,"DisplayLength"): l = obj.ViewObject.DisplayLength.Value h = obj.ViewObject.DisplayHeight.Value elif hasattr(obj.ViewObject,"DisplaySize"): # old objects l = obj.ViewObject.DisplaySize.Value h = obj.ViewObject.DisplaySize.Value p = Part.makePlane(l,h,Vector(l/2,-h/2,0),Vector(0,0,-1)) # make sure the normal direction is pointing outwards, you never know what OCC will decide... if p.normalAt(0,0).getAngle(obj.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))) > 1: p.reverse() p.Placement = obj.Placement obj.Shape = p def onChanged(self,obj,prop): # clean svg cache if needed if prop in ["Placement","Objects","OnlySolids","UseMaterialColorForFill","Clip"]: self.svgcache = None self.boolcache = None def getNormal(self,obj): return obj.Shape.Faces[0].normalAt(0,0) def __getstate__(self): return None def __setstate__(self,state): return None class _ViewProviderSectionPlane: "A View Provider for Section Planes" def __init__(self,vobj): vobj.Proxy = self self.setProperties(vobj) def setProperties(self,vobj): pl = vobj.PropertiesList d = 0 if "DisplaySize" in pl: d = vobj.DisplaySize.Value vobj.removeProperty("DisplaySize") if not "DisplayLength" in pl: vobj.addProperty("App::PropertyLength","DisplayLength","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The display length of this section plane")) if d: vobj.DisplayLength = d else: vobj.DisplayLength = 1000 if not "DisplayHeight" in pl: vobj.addProperty("App::PropertyLength","DisplayHeight","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The display height of this section plane")) if d: vobj.DisplayHeight = d else: vobj.DisplayHeight = 1000 if not "ArrowSize" in pl: vobj.addProperty("App::PropertyLength","ArrowSize","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The size of the arrows of this section plane")) vobj.ArrowSize = 50 if not "Transparency" in pl: vobj.addProperty("App::PropertyPercent","Transparency","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The transparency of this object")) vobj.Transparency = 85 if not "LineWidth" in pl: vobj.addProperty("App::PropertyFloat","LineWidth","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The line width of this object")) vobj.LineWidth = 1 if not "CutDistance" in pl: vobj.addProperty("App::PropertyLength","CutDistance","SectionPlane",QT_TRANSLATE_NOOP("App::Property","Show the cut in the 3D view")) if not "LineColor" in pl: vobj.addProperty("App::PropertyColor","LineColor","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The color of this object")) vobj.LineColor = ArchCommands.getDefaultColor("Helpers") if not "CutView" in pl: vobj.addProperty("App::PropertyBool","CutView","SectionPlane",QT_TRANSLATE_NOOP("App::Property","Show the cut in the 3D view")) if not "CutMargin" in pl: vobj.addProperty("App::PropertyLength","CutMargin","SectionPlane",QT_TRANSLATE_NOOP("App::Property","The distance between the cut plane and the actual view cut (keep this a very small value but not zero)")) vobj.CutMargin = 1 def onDocumentRestored(self,vobj): self.setProperties(vobj) def getIcon(self): import Arch_rc return ":/icons/Arch_SectionPlane_Tree.svg" def claimChildren(self): # buggy at the moment so it's disabled - it will for ex. swallow a building object directly at the root of the document... #if hasattr(self,"Object") and hasattr(self.Object,"Objects"): # return self.Object.Objects return [] def attach(self,vobj): self.Object = vobj.Object self.clip = None self.mat1 = coin.SoMaterial() self.mat2 = coin.SoMaterial() self.fcoords = coin.SoCoordinate3() #fs = coin.SoType.fromName("SoBrepFaceSet").createInstance() # this causes a FreeCAD freeze for me fs = coin.SoIndexedFaceSet() fs.coordIndex.setValues(0,7,[0,1,2,-1,0,2,3]) self.drawstyle = coin.SoDrawStyle() self.drawstyle.style = coin.SoDrawStyle.LINES self.lcoords = coin.SoCoordinate3() ls = coin.SoType.fromName("SoBrepEdgeSet").createInstance() ls.coordIndex.setValues(0,57,[0,1,-1,2,3,4,5,-1,6,7,8,9,-1,10,11,-1,12,13,14,15,-1,16,17,18,19,-1,20,21,-1,22,23,24,25,-1,26,27,28,29,-1,30,31,-1,32,33,34,35,-1,36,37,38,39,-1,40,41,42,43,44]) sep = coin.SoSeparator() psep = coin.SoSeparator() fsep = coin.SoSeparator() fsep.addChild(self.mat2) fsep.addChild(self.fcoords) fsep.addChild(fs) psep.addChild(self.mat1) psep.addChild(self.drawstyle) psep.addChild(self.lcoords) psep.addChild(ls) sep.addChild(fsep) sep.addChild(psep) vobj.addDisplayMode(sep,"Default") self.onChanged(vobj,"DisplayLength") self.onChanged(vobj,"LineColor") self.onChanged(vobj,"Transparency") self.onChanged(vobj,"CutView") def getDisplayModes(self,vobj): return ["Default"] def getDefaultDisplayMode(self): return "Default" def setDisplayMode(self,mode): return mode def updateData(self,obj,prop): if prop in ["Placement"]: self.onChanged(obj.ViewObject,"DisplayLength") self.onChanged(obj.ViewObject,"CutView") return def onChanged(self,vobj,prop): if prop == "LineColor": if hasattr(vobj,"LineColor"): l = vobj.LineColor self.mat1.diffuseColor.setValue([l[0],l[1],l[2]]) self.mat2.diffuseColor.setValue([l[0],l[1],l[2]]) elif prop == "Transparency": if hasattr(vobj,"Transparency"): self.mat2.transparency.setValue(vobj.Transparency/100.0) elif prop in ["DisplayLength","DisplayHeight","ArrowSize"]: if hasattr(vobj,"DisplayLength") and hasattr(vobj,"DisplayHeight"): ld = vobj.DisplayLength.Value/2 hd = vobj.DisplayHeight.Value/2 elif hasattr(vobj,"DisplaySize"): # old objects ld = vobj.DisplaySize.Value/2 hd = vobj.DisplaySize.Value/2 else: ld = 1 hd = 1 verts = [] fverts = [] for v in [[-ld,-hd],[ld,-hd],[ld,hd],[-ld,hd]]: if hasattr(vobj,"ArrowSize"): l1 = vobj.ArrowSize.Value if vobj.ArrowSize.Value > 0 else 0.1 else: l1 = 0.1 l2 = l1/3 pl = FreeCAD.Placement(vobj.Object.Placement) p1 = pl.multVec(Vector(v[0],v[1],0)) p2 = pl.multVec(Vector(v[0],v[1],-l1)) p3 = pl.multVec(Vector(v[0]-l2,v[1],-l1+l2)) p4 = pl.multVec(Vector(v[0]+l2,v[1],-l1+l2)) p5 = pl.multVec(Vector(v[0],v[1]-l2,-l1+l2)) p6 = pl.multVec(Vector(v[0],v[1]+l2,-l1+l2)) verts.extend([[p1.x,p1.y,p1.z],[p2.x,p2.y,p2.z]]) fverts.append([p1.x,p1.y,p1.z]) verts.extend([[p2.x,p2.y,p2.z],[p3.x,p3.y,p3.z],[p4.x,p4.y,p4.z],[p2.x,p2.y,p2.z]]) verts.extend([[p2.x,p2.y,p2.z],[p5.x,p5.y,p5.z],[p6.x,p6.y,p6.z],[p2.x,p2.y,p2.z]]) verts.extend(fverts+[fverts[0]]) self.lcoords.point.setValues(verts) self.fcoords.point.setValues(fverts) elif prop == "LineWidth": self.drawstyle.lineWidth = vobj.LineWidth elif prop in ["CutView","CutMargin"]: if hasattr(vobj,"CutView") and FreeCADGui.ActiveDocument.ActiveView: sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph() if vobj.CutView: if self.clip: sg.removeChild(self.clip) self.clip = None for o in Draft.getGroupContents(vobj.Object.Objects,walls=True): if hasattr(o.ViewObject,"Lighting"): o.ViewObject.Lighting = "One side" self.clip = coin.SoClipPlane() self.clip.on.setValue(True) norm = vobj.Object.Proxy.getNormal(vobj.Object) mp = vobj.Object.Shape.CenterOfMass mp = DraftVecUtils.project(mp,norm) dist = mp.Length #- 0.1 # to not clip exactly on the section object norm = norm.negative() marg = 1 if hasattr(vobj,"CutMargin"): marg = vobj.CutMargin.Value if mp.getAngle(norm) > 1: dist += marg dist = -dist else: dist -= marg plane = coin.SbPlane(coin.SbVec3f(norm.x,norm.y,norm.z),dist) self.clip.plane.setValue(plane) sg.insertChild(self.clip,0) else: if self.clip: sg.removeChild(self.clip) self.clip = None return def __getstate__(self): return None def __setstate__(self,state): return None def setEdit(self,vobj,mode): taskd = SectionPlaneTaskPanel() taskd.obj = vobj.Object taskd.update() FreeCADGui.Control.showDialog(taskd) return True def unsetEdit(self,vobj,mode): FreeCADGui.Control.closeDialog() return False def doubleClicked(self,vobj): self.setEdit(vobj,None) class _ArchDrawingView: def __init__(self, obj): obj.Proxy = self self.setProperties(obj) def setProperties(self,obj): pl = obj.PropertiesList if not "Source" in pl: obj.addProperty("App::PropertyLink", "Source", "Base", QT_TRANSLATE_NOOP("App::Property","The linked object")) if not "RenderingMode" in pl: obj.addProperty("App::PropertyEnumeration", "RenderingMode", "Drawing view", QT_TRANSLATE_NOOP("App::Property","The rendering mode to use")) obj.RenderingMode = ["Solid","Wireframe"] obj.RenderingMode = "Wireframe" if not "ShowCut" in pl: obj.addProperty("App::PropertyBool", "ShowCut", "Drawing view", QT_TRANSLATE_NOOP("App::Property","If cut geometry is shown or not")) if not "ShowFill" in pl: obj.addProperty("App::PropertyBool", "ShowFill", "Drawing view", QT_TRANSLATE_NOOP("App::Property","If cut geometry is filled or not")) if not "LineWidth" in pl: obj.addProperty("App::PropertyFloat", "LineWidth", "Drawing view", QT_TRANSLATE_NOOP("App::Property","The line width of the rendered objects")) obj.LineWidth = 0.35 if not "FontSize" in pl: obj.addProperty("App::PropertyLength", "FontSize", "Drawing view", QT_TRANSLATE_NOOP("App::Property","The size of the texts inside this object")) obj.FontSize = 12 if not "AlwaysOn" in pl: obj.addProperty("App::PropertyBool", "AlwaysOn", "Drawing view", QT_TRANSLATE_NOOP("App::Property","If checked, source objects are displayed regardless of being visible in the 3D model")) if not "LineColor" in pl: obj.addProperty("App::PropertyColor", "LineColor", "Drawing view",QT_TRANSLATE_NOOP("App::Property","The line color of the projected objects")) if not "FillColor" in pl: obj.addProperty("App::PropertyColor", "FillColor", "Drawing view",QT_TRANSLATE_NOOP("App::Property","The color of the cut faces (if turned on)")) obj.FillColor = (0.8, 0.8, 0.8) self.Type = "ArchSectionView" def onDocumentRestored(self, obj): self.setProperties(obj) def execute(self, obj): if hasattr(obj,"Source"): if obj.Source: svgbody = getSVG(section=obj.Source, renderMode=obj.RenderingMode, allOn=getattr(obj, 'AlwaysOn', False), showHidden=obj.ShowCut, scale=obj.Scale, linewidth=obj.LineWidth, lineColor=obj.LineColor, fontsize=obj.FontSize, showFill=obj.ShowFill, fillColor=obj.FillColor) if svgbody: result = '