diff --git a/src/Base/VectorPy.xml b/src/Base/VectorPy.xml
index 4171ac2a14..6286e4b27b 100644
--- a/src/Base/VectorPy.xml
+++ b/src/Base/VectorPy.xml
@@ -20,90 +20,127 @@
- add(Vector) - return the sum of the two vectors
+ add(Vector)
+ returns the sum of this and another vector
+
- sub(Vector) - return the difference of the two vectors
+ sub(Vector)
+ returns the difference of this and another vector
+
- scale(Float,Float,Float) - scale (multiplies) this vector by a factor
+ scale(Float,Float,Float)
+ scales (multiplies) this vector by a factor
+
- multiply(Float) - multiplies (scales) this vector by a single factor
+ multiply(Float)
+ multiplies (scales) this vector by a single factor
+
- dot(Vector) - return the dot product of the two vectors
+ dot(Vector)
+ returns the dot product of the this vector with another one
+
- cross(Vector) - return the cross product of the two vectors
+ cross(Vector)
+ returns the cross product between this and another vector
+
- getAngle(Vector) - return the angle in radians between the two vectors
+ getAngle(Vector)
+ returns the angle in radians between this and another vector
+
- Normalize the vector to the length of 1.0
+ normalize()
+ normalizes the vector to the length of 1.0
+
- Deliver the projection point to a given line
+ projectToLine(Vector,Vector)
+ projects the vector on a line defined by a base point and a direction
+
- Deliver the projection point to a given plane
+ projectToPlane(Vector,Vector)
+ projects the vector on a plane defined by a base point and a normal
+
- Deliver the distance of the point to a given line
+ distanceToLine(Vector,Vector)
+ returns the distance between this vector and a line defined by
+ a base point and a direction
+
- Deliver the distance of the point to a given line segment
+ distanceToLineSegment(Vector,Vector)
+ returns the distance between this vector and a line segment defined by
+ a base point and a direction
+
- Deliver the distance of the point to a given plane
+ distanceToPlane(Vector,Vector)
+ returns the distance between this vector and a plane defined by
+ a base point and a normal
+
- To read or modifiy the length of the vector
+ Length([Float]) -> Float
+ gets or sets the length of this vector
+
- The X component of the vector
+ x([Float]) -> Float
+ gets or sets the X component of this vector
+
- The Y component of the vector
+ y([Float]) -> Float
+ gets or sets the Y component of this vector
+
- The Z component of the vector
+ z([Float]) -> Float
+ gets or sets the Z component of this vector
+
@@ -128,4 +165,4 @@
{ return *(getVectorPtr()); }
-
+
diff --git a/src/Mod/Arch/ArchVRM.py b/src/Mod/Arch/ArchVRM.py
new file mode 100644
index 0000000000..a316ee3acd
--- /dev/null
+++ b/src/Mod/Arch/ArchVRM.py
@@ -0,0 +1,315 @@
+#***************************************************************************
+#* *
+#* Copyright (c) 2012 *
+#* 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 *
+#* *
+#***************************************************************************
+
+"The FreeCAD Arch Vector Rendering Module"
+
+import FreeCAD,math,Part
+from draftlibs import fcvec,fcgeo
+
+DEBUG = True # if we want debug messages
+MAXLOOP = 10 # the max number of loop before abort
+
+class Renderer:
+ "A renderer object"
+ def __init__(self,wp=None):
+ """
+ Creates a renderer with a default Draft WorkingPlane
+ Use like this:
+
+ import ArchVRM
+ p = ArchVRM.Renderer()
+ p.add(App.ActiveDocument.ActiveObject)
+ p.sort()
+ p.buildDummy()
+ """
+ self.wp = wp
+ self.faces = []
+ self.oriented = False
+ self.trimmed = False
+ self.sorted = False
+ if not self.wp:
+ import WorkingPlane
+ self.wp = WorkingPlane.plane()
+ if DEBUG: print "Renderer initialized on " + str(self.wp)
+
+ def __str__(self):
+ return "Arch Renderer: " + str(len(self.faces)) + " faces projected on " + str(self.wp)
+
+ def setWorkingPlane(self,wp):
+ "sets a Draft WorkingPlane for this renderer"
+ self.wp = wp
+ if DEBUG: print "Renderer set on " + str(self.wp)
+
+ def add(self,faces):
+ "add faces, shape or object to this renderer"
+ if isinstance(faces,list):
+ f = faces
+ elif hasattr(faces,"Faces"):
+ f = faces.Faces
+ elif hasattr(faces,"Shape"):
+ f = faces.Shape.Faces
+ if DEBUG: print "adding ", len(f), " faces"
+ self.faces.extend(f)
+ self.oriented = False
+ self.trimmed = False
+ self.sorted = False
+
+ def clean(self):
+ "removes all faces from this renderer"
+ self.faces = []
+ self.oriented = False
+ self.trimmed = False
+ self.sorted = False
+
+ def isVisible(self,face):
+ "returns True if the given face points in the view direction"
+ normal = face.normalAt(0,0)
+ if DEBUG: print "checking face normal ", normal, " against ", self.wp.axis, " : ", math.degrees(normal.getAngle(self.wp.axis))
+ if normal.getAngle(self.wp.axis) < math.pi/2:
+ return True
+ return False
+
+ def reorient(self):
+ "reorients the faces on the WP"
+ if not self.faces:
+ return
+ self.faces = [self.projectFace(f) for f in self.faces]
+ self.oriented = True
+
+ def removeHidden(self):
+ "removes faces pointing outwards"
+ if not self.faces:
+ return
+ faces = []
+ for f in (self.faces):
+ if self.isVisible(f):
+ faces.append(f)
+ if DEBUG: print len(self.faces)-len(faces) , " faces removed, ", len(faces), " faces retained"
+ self.faces = faces
+ self.trimmed = True
+
+ def projectFace(self,face):
+ "projects a single face on the WP"
+ verts = []
+ edges = fcgeo.sortEdges(face.Edges)
+ for e in edges:
+ v = e.Vertexes[0].Point
+ v = self.wp.getLocalCoords(v)
+ verts.append(v)
+ verts.append(verts[0])
+ try:
+ sh = Part.makePolygon(verts)
+ sh = Part.Face(sh)
+ except:
+ if DEBUG: print "Error: Unable to project face on the WP"
+ return None
+ else:
+ return sh
+
+ def flattenFace(self,face):
+ "Returns a face where all vertices have Z = 0"
+ verts = []
+ edges = fcgeo.sortEdges(face.Edges)
+ for e in edges:
+ v = e.Vertexes[0].Point
+ verts.append(FreeCAD.Vector(v.x,v.y,0))
+ verts.append(verts[0])
+ try:
+ sh = Part.makePolygon(verts)
+ sh = Part.Face(sh)
+ except:
+ if DEBUG: print "Error: Unable to flatten face"
+ return None
+ else:
+ return sh
+
+ def isInside(self,vert,face):
+ "Returns True if the vert is inside the face in Z projection"
+
+
+ def zOverlaps(self,face1,face2):
+ "Checks if face1 overlaps face2 in Z direction"
+
+ face1 = self.flattenFace(face1)
+ face2 = self.flattenFace(face2)
+
+ # first we check if one of the verts is inside the other face
+ for v in face1.Vertexes:
+ if self.isInside(v,face2):
+ return True
+
+ # even so, faces can still overlap if their edges cross each other
+ for e1 in face1.Edges:
+ for e2 in face2.Edges:
+ if fcgeo.findIntersection(e1,e2):
+ return True
+
+ return False
+
+ def sortFaces(self,face1,face2):
+ "zsorts two faces. Returns 1 if face1 is closer, 2 if face2 is closer, 0 otherwise"
+
+ # theory from
+ # http://www.siggraph.org/education/materials/HyperGraph/scanline/visibility/painter.htm
+ # and practical application http://vrm.ao2.it/ (blender vector renderer)
+
+ b1 = face1.BoundBox
+ b2 = face2.BoundBox
+
+ if DEBUG: print "comparing face1: normal ", face1.normalAt(0,0), " with face2: normal ", face2.normalAt(0,0)
+
+ # test 1: if faces don't overlap, no comparison possible
+ if DEBUG: print "doing test 1"
+ if b1.XMax < b2.XMin:
+ return 0
+ if b1.XMin > b2.XMax:
+ return 0
+ if b1.YMax < b2.YMin:
+ return 0
+ if b1.YMin > b2.YMax:
+ return 0
+ if DEBUG: print "passed, faces are overlapping"
+
+ # test 2: if Z bounds dont overlap, it's easy to know the closest
+ if DEBUG: print "doing test 2"
+ if b1.ZMax < b2.ZMin:
+ return 2
+ if b2.ZMax < b1.ZMin:
+ return 1
+ if DEBUG: print "passed, faces Z are crossed"
+
+ # test 3: all verts of face1 are behind the plane of face2
+ if DEBUG: print "doing test 3"
+ norm = face2.normalAt(0,0)
+ behind = 0
+ for v in face1.Vertexes:
+ dv = v.Point.sub(face2.Vertexes[0].Point)
+ dv = fcvec.project(dv,norm)
+ if dv.Length:
+ if dv.getAngle(norm) > 0.1:
+ behind += 1
+ if behind == len(face1.Vertexes):
+ return 2
+ if DEBUG: print "passed, face 1 is not behind"
+
+ # test 4: all verts of face2 are in front of the plane of face1
+ if DEBUG: print "doing test 4"
+ norm = face1.normalAt(0,0)
+ front = 0
+ for v in face2.Vertexes:
+ dv = v.Point.sub(face1.Vertexes[0].Point)
+ dv = fcvec.project(dv,norm)
+ if dv.Length:
+ if dv.getAngle(norm) < 0.1:
+ front += 1
+ if front == len(face2.Vertexes):
+ return 2
+ if DEBUG: print "passed, face 2 is not in front"
+
+ # test 5: see if faces projections don't overlap, vertexwise
+ if DEBUG: print "doing test 5"
+ if not self.zOverlaps(face1,face2):
+ return 0
+ elif not self.zOverlaps(face2,face1):
+ return 0
+ if DEBUG: print "passed, faces are overlapping"
+
+ if DEBUG: print "Houston, all tests passed, and still no results"
+ return 0
+
+ def sort(self):
+ "projects a shape on the WP"
+ if not self.faces:
+ return
+ if not self.trimmed:
+ self.removeHidden()
+ if not self.oriented:
+ self.reorient()
+ faces = self.faces[:]
+ if DEBUG: print "sorting faces: ", faces
+ sfaces = []
+ safecount = 0
+ while faces:
+ f1 = faces[0]
+ for f2 in faces[1:]:
+ if DEBUG: print "Scanning %d faces" % len(faces)
+ z = self.sortFaces(f1,f2)
+ if z == 1:
+ faces.remove(f2)
+ sfaces.insert(0,f2)
+ elif z == 2:
+ faces.remove(f1)
+ sfaces.append(f2)
+ break
+ safecount += 1
+ if DEBUG: print "iteration ",safecount
+ if safecount > (10 * len(faces)):
+ if DEBUG: print "too long loop, aborting"
+ break
+ if DEBUG: print "done Z sorting. ", len(faces), " faces retained, ", len(self.faces)-len(faces), " faces lost."
+ self.faces = faces
+ self.sorted = True
+
+ def buildDummy(self):
+ "Builds a dummy object with faces spaced on the Z axis, for visual check"
+ z = 0
+ if not self.sorted:
+ self.sort()
+ faces = []
+ for f in self.faces[:]:
+ ff = self.flattenFace(f)
+ ff.translate(FreeCAD.Vector(0,0,z))
+ faces.append(ff)
+ z += 1
+ if faces:
+ c = Part.makeCompound(faces)
+ Part.show(c)
+
+ def getSVG(self):
+ "Returns a SVG fragment"
+ if not self.sorted:
+ self.sort()
+ svg = ''
+ for f in self.faces:
+ svg +='\n'
+ return svg
diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py
index f5c056ed78..324aad4a50 100644
--- a/src/Mod/Draft/DraftTools.py
+++ b/src/Mod/Draft/DraftTools.py
@@ -2368,8 +2368,12 @@ class Upgrade(Modifier):
else:
# only closed wires
for w in wires:
- f = Part.Face(w)
- faces.append(f)
+ if fcgeo.isPlanar(w):
+ f = Part.Face(w)
+ faces.append(f)
+ else:
+ msg(translate("draft", "One wire is not planar, upgrade not done\n"))
+ self.nodelete = True
for f in faces:
if not curves:
msg(translate("draft", "Found a closed wire: making a Draft wire\n"))
@@ -2390,8 +2394,16 @@ class Upgrade(Modifier):
edges.append(Part.Line(p1,p0).toShape())
w = Part.Wire(fcgeo.sortEdges(edges))
if len(edges) == 1:
- msg(translate("draft", "Found 1 open edge: making a line\n"))
- newob = Draft.makeWire(w,closed=False)
+ if len(w.Vertexes) == 2:
+ msg(translate("draft", "Found 1 open edge: making a line\n"))
+ newob = Draft.makeWire(w,closed=False)
+ elif len(w.Vertexes) == 1:
+ msg(translate("draft", "Found 1 circular edge: making a circle\n"))
+ c = w.Edges[0].Curve.Center
+ r = w.Edges[0].Curve.Radius
+ p = FreeCAD.Placement()
+ p.move(c)
+ newob = Draft.makeCircle(r,p)
else:
msg(translate("draft", "Found 1 open wire: closing it\n"))
if not curves:
diff --git a/src/Mod/Draft/draftlibs/fcgeo.py b/src/Mod/Draft/draftlibs/fcgeo.py
index 9863a2b1a3..ee38aa8ee0 100755
--- a/src/Mod/Draft/draftlibs/fcgeo.py
+++ b/src/Mod/Draft/draftlibs/fcgeo.py
@@ -911,6 +911,20 @@ def isCoplanar(faces):
return False
return True
+def isPlanar(shape):
+ "checks if the given shape is planar"
+ if len(shape.Vertexes) <= 3:
+ return True
+ pts = [v.Point for v in shape.Vertexes[0:3]]
+ bt = Part.Face(Part.makePolygon(pts+[pts[0]]))
+ n = bt.normalAt(0,0)
+ for p in shape.Vertexes[3:]:
+ pv = p.Point.sub(pts[0])
+ rv = fcvec.project(pv,n)
+ if not fcvec.isNull(rv):
+ return False
+ return True
+
def findWires(edges):
'''finds connected edges in the list, and returns a list of lists containing edges
that can be connected'''
diff --git a/src/WindowsInstaller/LibPack.wxs b/src/WindowsInstaller/LibPack.wxs
index cbe94cdc66..37b4da8ce6 100644
--- a/src/WindowsInstaller/LibPack.wxs
+++ b/src/WindowsInstaller/LibPack.wxs
@@ -71,6 +71,10 @@
+
+
+
+