diff --git a/src/Mod/Arch/ArchCommands.py b/src/Mod/Arch/ArchCommands.py index 8ee62c546..d6b3a96fe 100644 --- a/src/Mod/Arch/ArchCommands.py +++ b/src/Mod/Arch/ArchCommands.py @@ -278,7 +278,7 @@ def getCutVolume(cutplane,shapes): v = placement.Rotation.multVec(FreeCAD.Vector(0,1,0)) if not bb.isCutPlane(placement.Base,ax): print "No objects are cut by the plane" - return None,None + return None,None,None else: corners = [FreeCAD.Vector(bb.XMin,bb.YMin,bb.ZMin), FreeCAD.Vector(bb.XMin,bb.YMax,bb.ZMin), @@ -305,7 +305,9 @@ def getCutVolume(cutplane,shapes): cutface.Placement = placement cutnormal = DraftVecUtils.scaleTo(ax,wm) cutvolume = cutface.extrude(cutnormal) - return cutface,cutvolume + cutnormal = DraftVecUtils.neg(cutnormal) + invcutvolume = cutface.extrude(cutnormal) + return cutface,cutvolume,invcutvolume def meshToShape(obj,mark=True): @@ -428,8 +430,8 @@ def download(url): else: return filepath -def check(objectslist,includehidden=True): - """check(objectslist,includehidden=True): checks if the given objects contain only solids""" +def check(objectslist,includehidden=False): + """check(objectslist,includehidden=False): checks if the given objects contain only solids""" objs = Draft.getGroupContents(objectslist) if not includehidden: objs = Draft.removeHidden(objs) @@ -439,11 +441,11 @@ def check(objectslist,includehidden=True): bad.append([o,"is not a Part-based object"]) else: s = o.Shape - if not s.isClosed(): + if (not s.isClosed()) and (not (Draft.getType(o) == "Axis")): bad.append([o,"is not closed"]) elif not s.isValid(): bad.append([o,"is not valid"]) - elif not s.Solids: + elif (not s.Solids) and (not (Draft.getType(o) == "Axis")): bad.append([o,"doesn't contain any solid"]) else: f = 0 diff --git a/src/Mod/Arch/ArchSectionPlane.py b/src/Mod/Arch/ArchSectionPlane.py index 97fe2023d..7a65180a8 100644 --- a/src/Mod/Arch/ArchSectionPlane.py +++ b/src/Mod/Arch/ArchSectionPlane.py @@ -184,10 +184,12 @@ class _ArchDrawingView: def __init__(self, obj): obj.addProperty("App::PropertyLink","Source","Base","The linked object") obj.addProperty("App::PropertyEnumeration","RenderingMode","Drawing View","The rendering mode to use") + obj.addProperty("App::PropertyBool","ShowCut","Drawing View","If cut geometry is shown or not") obj.addProperty("App::PropertyFloat","LineWidth","Drawing View","The line width of the rendered objects") obj.RenderingMode = ["Solid","Wireframe"] obj.RenderingMode = "Wireframe" obj.LineWidth = 0.35 + obj.ShowCut = False obj.Proxy = self self.Type = "DrawingView" @@ -199,6 +201,22 @@ class _ArchDrawingView: if prop in ["Source","RenderingMode"]: obj.ViewResult = self.updateSVG(obj) + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def getShape(self, obj): + "returns a flat shape representation of the view" + if hasattr(self,"baseshape"): + import Drawing + [V0,V1,H0,H1] = Drawing.project(self.baseshape,direction) + return V0.Edges+V1.Edges + else: + print "No shape has been computed yet" + return None + def updateSVG(self, obj,join=False): "encapsulates a svg fragment into a transformation node" import Part, DraftGeomUtils @@ -209,6 +227,9 @@ class _ArchDrawingView: objs = Draft.removeHidden(objs) svg = '' + st = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetFloat("CutLineThickness") + if not st: st = 2 + # generating SVG linewidth = obj.LineWidth/obj.Scale if obj.RenderingMode == "Solid": @@ -217,29 +238,43 @@ class _ArchDrawingView: render = ArchVRM.Renderer() render.setWorkingPlane(obj.Source.Placement) render.addObjects(Draft.getGroupContents(objs,walls=True)) - render.cut(obj.Source.Shape) + render.cut(obj.Source.Shape,obj.ShowCut) svg += render.getViewSVG(linewidth=linewidth) - svg += render.getSectionSVG(linewidth=linewidth*2) + svg += render.getSectionSVG(linewidth=linewidth*st) + if obj.ShowCut: + svg += render.getHiddenSVG(linewidth=linewidth) # print render.info() else: # render using the Drawing module import Drawing, Part shapes = [] + hshapes = [] + sshapes = [] p = FreeCAD.Placement(obj.Source.Placement) direction = p.Rotation.multVec(FreeCAD.Vector(0,0,1)) for o in objs: if o.isDerivedFrom("Part::Feature"): - shapes.extend(o.Shape.Solids) - cutface,cutvolume = ArchCommands.getCutVolume(obj.Source.Shape.copy(),shapes) + if o.Shape.isValid(): + shapes.extend(o.Shape.Solids) + else: + FreeCAD.Console.PrintWarning("Skipping invalid object: "+o.Name) + cutface,cutvolume,invcutvolume = ArchCommands.getCutVolume(obj.Source.Shape.copy(),shapes) if cutvolume: nsh = [] for sh in shapes: for sol in sh.Solids: c = sol.cut(cutvolume) nsh.append(c) + s = sol.section(cutface) + sshapes.append(s) + if obj.ShowCut: + c = sol.cut(invcutvolume) + hshapes.append(c) shapes = nsh - base = Part.makeCompound(shapes) + if shapes: + self.baseshape = Part.makeCompound(shapes) + svgf = Drawing.projectToSVG(self.baseshape,direction) #if shapes: # base = shapes.pop().copy() #for sh in shapes: @@ -247,12 +282,36 @@ class _ArchDrawingView: # base = base.fuse(sh) # except: # print "unable to fuse, passing..." - svgf = Drawing.projectToSVG(base,direction) if svgf: svgf = svgf.replace('stroke-width="0.35"','stroke-width="' + str(linewidth) + 'px"') svgf = svgf.replace('stroke-width="1"','stroke-width="' + str(linewidth) + 'px"') svgf = svgf.replace('stroke-width:0.01','stroke-width:' + str(linewidth) + 'px') - svg += svgf + svg += svgf + if hshapes: + hshapes = Part.makeCompound(hshapes) + svgh = Drawing.projectToSVG(hshapes,direction) + if svgh: + svgh = svgh.replace('stroke-width="0.35"','stroke-width="' + str(linewidth) + 'px"') + svgh = svgh.replace('stroke-width="1"','stroke-width="' + str(linewidth) + 'px"') + svgh = svgh.replace('stroke-width:0.01','stroke-width:' + str(linewidth) + 'px') + svgh = svgh.replace('fill="none"','fill="none"\nstroke-dasharray="0.09,0.05"') + svg += svgh + if sshapes: + edges = [] + for s in sshapes: + edges.extend(s.Edges) + wires = DraftGeomUtils.findWires(edges) + faces = [] + for w in wires: + if (w.ShapeType == "Wire") and w.isClosed(): + faces.append(Part.Face(w)) + sshapes = Part.makeCompound(faces) + svgs = Drawing.projectToSVG(sshapes,direction) + if svgs: + svgs = svgs.replace('stroke-width="0.35"','stroke-width="' + str(linewidth*st) + 'px"') + svgs = svgs.replace('stroke-width="1"','stroke-width="' + str(linewidth*st) + 'px"') + svgs = svgs.replace('stroke-width:0.01','stroke-width:' + str(linewidth*st) + 'px') + svg += svgs result = '' result += ' 1: + v1 = self.wp.getLocalCoords(edge.Vertexes[0].Point) + v2 = self.wp.getLocalCoords(edge.Vertexes[-1].Point) + return Part.Line(v1,v2).toShape() + return edge + def flattenFace(self,face): "Returns a face where all vertices have Z = 0" wires = [] @@ -219,7 +231,7 @@ class Renderer: else: return [sh]+face[1:] - def cut(self,cutplane): + def cut(self,cutplane,hidden=False): "Cuts through the shapes with a given cut plane and builds section faces" if DEBUG: print "\n\n======> Starting cut\n\n" if self.iscut: @@ -231,7 +243,7 @@ class Renderer: shps = [] for sh in self.shapes: shps.append(sh[0]) - cutface,cutvolume = ArchCommands.getCutVolume(cutplane,shps) + cutface,cutvolume,invcutvolume = ArchCommands.getCutVolume(cutplane,shps) if cutface and cutvolume: shapes = [] faces = [] @@ -246,6 +258,9 @@ class Renderer: if DraftGeomUtils.isCoplanar([f,cutface]): print "COPLANAR" sections.append([f,fill]) + if hidden: + c = sol.cut(invcutvolume) + self.hiddenEdges.extend(c.Edges) self.shapes = shapes self.faces = faces self.sections = sections @@ -555,7 +570,8 @@ class Renderer: v = e.Vertexes[-1].Point svg += 'A '+ tostr(r) + ' '+ tostr(r) +' 0 0 1 '+ tostr(v.x) +' ' svg += tostr(v.y) + ' ' - svg += 'Z ' + if len(edges) > 1: + svg += 'Z ' return svg def getViewSVG(self,linewidth=0.01): @@ -608,4 +624,25 @@ class Renderer: svg += 'fill-rule: evenodd' svg += '"/>\n' return svg + + def getHiddenSVG(self,linewidth=0.02): + "Returns a SVG fragment from cut geometry" + if DEBUG: print "Printing ", len(self.sections), " hidden faces" + if not self.oriented: + self.reorient() + svg = '' + for e in self.hiddenEdges: + svg +=' + + + + 2D rendering + + + + + + + + Show debug information during 2D rendering + + + Show renderer debug messages + + + ShowVRMDebug + + + Mod/Arch + + + + + + + + + + + Cut areas line thickness ratio + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Specifies how many times the viewed line thickness must be applied to cut lines + + + 2.000000000000000 + + + CutLineThickness + + + Mod/Arch + + + + + + + + @@ -257,6 +326,11 @@ QCheckBox
Gui/PrefWidgets.h
+ + Gui::PrefDoubleSpinBox + QDoubleSpinBox +
Gui/PrefWidgets.h
+
diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index f47ae0f09..10ee860a0 100755 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -539,57 +539,62 @@ def sortEdges(lEdges, aVertex=None): def findWires(edgeslist): - '''finds connected wires in the given list of edges''' + '''finds connected wires in the given list of edges''' - def touches(e1,e2): - if len(e1.Vertexes) < 2: - return False - if len(e2.Vertexes) < 2: - return False - if DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[0].Point): - return True - if DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[-1].Point): - return True - if DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[0].Point): - return True - if DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[-1].Point): - return True - return False - - edges = edgeslist[:] - wires = [] - lost = [] - while edges: - e = edges[0] - if not wires: - # create first group - edges.remove(e) - wires.append([e]) + def touches(e1,e2): + if len(e1.Vertexes) < 2: + return False + if len(e2.Vertexes) < 2: + return False + if DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[0].Point): + return True + if DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[-1].Point): + return True + if DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[0].Point): + return True + if DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[-1].Point): + return True + return False + + edges = edgeslist[:] + wires = [] + lost = [] + while edges: + e = edges[0] + if not wires: + # create first group + edges.remove(e) + wires.append([e]) + else: + found = False + for w in wires: + if not found: + for we in w: + if touches(e,we): + edges.remove(e) + w.append(e) + found = True + break + if not found: + if e in lost: + # we already tried this edge, and still nothing + edges.remove(e) + wires.append([e]) + lost = [] else: - found = False - for w in wires: - if not found: - for we in w: - if touches(e,we): - edges.remove(e) - w.append(e) - found = True - break - if not found: - if e in lost: - # we already tried this edge, and still nothing - edges.remove(e) - wires.append([e]) - lost = [] - else: - # put to the end of the list - edges.remove(e) - edges.append(e) - lost.append(e) - nwires = [] - for w in wires: - nwires.append(Part.Wire(w)) - return nwires + # put to the end of the list + edges.remove(e) + edges.append(e) + lost.append(e) + nwires = [] + for w in wires: + try: + wi = Part.Wire(w) + except: + print "couldn't join some edges" + else: + nwires.append(wi) + return nwires def superWire(edgeslist,closed=False): '''superWire(edges,[closed]): forces a wire between edges that don't necessarily