Removed PathGeom class and moved functionality into module interface.

This commit is contained in:
Markus Lampert
2018-06-19 21:38:20 -07:00
parent c5a7a69166
commit bf46c47616
21 changed files with 397 additions and 410 deletions

View File

@@ -25,8 +25,9 @@ import FreeCAD
import FreeCADGui
import Path
import math
import PathScripts.PathGeom as PathGeom
import PathScripts.PathUtils as PathUtils
import PathScripts.PathGeom
from PySide import QtCore, QtGui
"""Axis remapping Dressup object and FreeCAD command. This dressup remaps one axis of motion to another.
@@ -70,7 +71,7 @@ class ObjectDressup:
for p in path:
if p.Name in arccommands:
curVec = FreeCAD.Vector(currLocation['X'], currLocation['Y'], currLocation['Z'])
arcwire = PathScripts.PathGeom.PathGeom.edgeForCmd(p, curVec)
arcwire = PathGeom.edgeForCmd(p, curVec)
pointlist = arcwire.discretize(Deflection=d)
for point in pointlist:
newcommand = Path.Command("G1", {'X':point.x, 'Y':point.y, 'Z':point.z})

View File

@@ -29,11 +29,11 @@ import math
import Part
import Path
import PathScripts.PathDressup as PathDressup
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
from PathScripts.PathGeom import PathGeom
from PySide import QtCore, QtGui
"""Dogbone Dressup object and FreeCAD command"""

View File

@@ -25,13 +25,13 @@ import FreeCAD
import Part
import Path
import PathScripts.PathDressup as PathDressup
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
import copy
import math
from PathScripts.PathGeom import PathGeom
from PathScripts.PathDressupTagPreferences import HoldingTagPreferences
from PathScripts.PathUtils import waiting_effects
from PySide import QtCore

View File

@@ -28,12 +28,12 @@ import FreeCAD
import FreeCADGui
import Path
import PathScripts.PathDressup as PathDressup
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathUtils as PathUtils
import math
from PySide import QtCore
from PathScripts.PathGeom import PathGeom
"""LeadInOut Dressup MASHIN-CRC USE ROLL-ON ROLL-OFF to profile"""

View File

@@ -26,11 +26,11 @@ import FreeCADGui
import Path
import Part
import PathScripts.PathDressup as PathDressup
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import math
from PathScripts import PathUtils
from PathScripts.PathGeom import PathGeom
from PySide import QtCore

View File

@@ -25,13 +25,13 @@ import FreeCAD
import DraftGeomUtils
import Part
import PathScripts.PathDressup as PathDressup
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathUtils as PathUtils
import math
import sys
from PathScripts.PathDressupTagPreferences import HoldingTagPreferences
from PathScripts.PathGeom import PathGeom
from PySide import QtCore
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())

View File

@@ -27,12 +27,12 @@ import FreeCADGui
# import Path
# import PathScripts
# import PathScripts.PathDressupTag as PathDressupTag
import PathScripts.PathGeom as PathGeom
import PathScripts.PathGetPoint as PathGetPoint
import PathScripts.PathDressupHoldingTags as PathDressupTag
import PathScripts.PathLog as PathLog
import PathScripts.PathUtils as PathUtils
from PathScripts.PathGeom import PathGeom
from PathScripts.PathPreferences import PathPreferences
from PySide import QtCore, QtGui
from pivy import coin

View File

@@ -28,13 +28,13 @@ import FreeCAD
import Part
import Path
import PathScripts.PathLog as PathLog
import PathScripts.PathGeom as PathGeom
import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
import TechDraw
import traceback
from DraftGeomUtils import geomType
from PathScripts.PathGeom import PathGeom
from PathScripts.PathPreferences import PathPreferences
from PySide import QtCore

View File

@@ -31,7 +31,12 @@ import PathScripts.PathLog as PathLog
from FreeCAD import Vector
from PySide import QtCore
PathGeomTolerance = 0.000001
__title__ = "PathGeom - geometry utilities for Path"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Functions to extract and convert between Path.Command and Part.Edge and utility functions to reason about them."
Tolerance = 0.000001
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
@@ -74,404 +79,379 @@ class Side:
return cls.Right
return cls.Straight
class PathGeom:
"""Class to transform Path Commands into Edges and Wire and back again.
The interface might eventuallly become part of Path itself."""
CmdMoveRapid = ['G0', 'G00']
CmdMoveStraight = ['G1', 'G01']
CmdMoveCW = ['G2', 'G02']
CmdMoveCCW = ['G3', 'G03']
CmdMoveArc = CmdMoveCW + CmdMoveCCW
CmdMove = CmdMoveStraight + CmdMoveArc
CmdMoveRapid = ['G0', 'G00']
CmdMoveStraight = ['G1', 'G01']
CmdMoveCW = ['G2', 'G02']
CmdMoveCCW = ['G3', 'G03']
CmdMoveArc = CmdMoveCW + CmdMoveCCW
CmdMove = CmdMoveStraight + CmdMoveArc
Tolerance = PathGeomTolerance
def isRoughly(float1, float2, error=Tolerance):
"""(float1, float2, [error=%s])
Returns true if the two values are the same within a given error.""" % Tolerance
return math.fabs(float1 - float2) <= error
@classmethod
def isRoughly(cls, float1, float2, error=PathGeomTolerance):
"""(float1, float2, [error=%s])
Returns true if the two values are the same within a given error.""" % PathGeomTolerance
return math.fabs(float1 - float2) <= error
def pointsCoincide(p1, p2, error=Tolerance):
"""(p1, p2, [error=%s])
Return True if two points are roughly identical (see also isRoughly).""" % Tolerance
return isRoughly(p1.x, p2.x, error) and isRoughly(p1.y, p2.y, error) and isRoughly(p1.z, p2.z, error)
@classmethod
def pointsCoincide(cls, p1, p2, error=PathGeomTolerance):
"""(p1, p2, [error=%s])
Return True if two points are roughly identical (see also isRoughly).""" % PathGeomTolerance
return cls.isRoughly(p1.x, p2.x, error) and cls.isRoughly(p1.y, p2.y, error) and cls.isRoughly(p1.z, p2.z, error)
def edgesMatch(e0, e1, error=Tolerance):
"""(e0, e1, [error=%s]
Return true if the edges start and end at the same point and have the same type of curve.""" % Tolerance
if type(e0.Curve) != type(e1.Curve) or len(e0.Vertexes) != len(e1.Vertexes):
return False
return all(pointsCoincide(e0.Vertexes[i].Point, e1.Vertexes[i].Point) for i in range(len(e0.Vertexes)))
@classmethod
def edgesMatch(cls, e0, e1, error=PathGeomTolerance):
"""(e0, e1, [error=%s]
Return true if the edges start and end at the same point and have the same type of curve.""" % PathGeomTolerance
if type(e0.Curve) != type(e1.Curve) or len(e0.Vertexes) != len(e1.Vertexes):
return False
return all(cls.pointsCoincide(e0.Vertexes[i].Point, e1.Vertexes[i].Point) for i in range(len(e0.Vertexes)))
def edgeConnectsTo(edge, vector, error=Tolerance):
"""(edge, vector, error=%f)
Returns True if edge connects to given vector.""" % Tolerance
return pointsCoincide(edge.valueAt(edge.FirstParameter), vector) or pointsCoincide(edge.valueAt(edge.LastParameter), vector)
@classmethod
def edgeConnectsTo(cls, edge, vector, error=PathGeomTolerance):
"""(edge, vector, error=%f)
Returns True if edge connects to given vector.""" % PathGeomTolerance
return cls.pointsCoincide(edge.valueAt(edge.FirstParameter), vector) or cls.pointsCoincide(edge.valueAt(edge.LastParameter), vector)
def getAngle(vector):
"""(vector)
Returns the angle [-pi,pi] of a vector using the X-axis as the reference.
Positive angles for vertexes in the upper hemishpere (positive y values)
and negative angles for the lower hemishpere."""
a = vector.getAngle(Vector(1,0,0))
if vector.y < 0:
return -a
return a
@classmethod
def getAngle(cls, vector):
"""(vector)
Returns the angle [-pi,pi] of a vector using the X-axis as the reference.
Positive angles for vertexes in the upper hemishpere (positive y values)
and negative angles for the lower hemishpere."""
a = vector.getAngle(Vector(1,0,0))
if vector.y < 0:
return -a
return a
def diffAngle(a1, a2, direction = 'CW'):
"""(a1, a2, [direction='CW'])
Returns the difference between two angles (a1 -> a2) into a given direction."""
if direction == 'CW':
while a1 < a2:
a1 += 2*math.pi
a = a1 - a2
else:
while a2 < a1:
a2 += 2*math.pi
a = a2 - a1
return a
@classmethod
def diffAngle(cls, a1, a2, direction = 'CW'):
"""(a1, a2, [direction='CW'])
Returns the difference between two angles (a1 -> a2) into a given direction."""
if direction == 'CW':
while a1 < a2:
a1 += 2*math.pi
a = a1 - a2
else:
while a2 < a1:
a2 += 2*math.pi
a = a2 - a1
return a
@classmethod
def isVertical(cls, obj):
'''isVertical(obj) ... answer True if obj points into Z'''
if type(obj) == FreeCAD.Vector:
return PathGeom.isRoughly(obj.x, 0) and PathGeom.isRoughly(obj.y, 0)
if obj.ShapeType == 'Face':
if type(obj.Surface) == Part.Plane:
return cls.isHorizontal(obj.Surface.Axis)
if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone:
return cls.isVertical(obj.Surface.Axis)
if type(obj.Surface) == Part.Sphere:
return True
if type(obj.Surface) == Part.SurfaceOfExtrusion:
return cls.isVertical(obj.Surface.Direction)
if type(obj.Surface) != Part.BSplineSurface:
PathLog.info(translate('PathGeom', "face %s not handled, assuming not vertical") % type(obj.Surface))
return None
if obj.ShapeType == 'Edge':
if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment:
return cls.isVertical(obj.Vertexes[1].Point - obj.Vertexes[0].Point)
if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve:
return cls.isHorizontal(obj.Curve.Axis)
if type(obj.Curve) == Part.BezierCurve:
# the current assumption is that a bezier curve is vertical if its end points are vertical
return cls.isVertical(obj.Curve.EndPoint - obj.Curve.StartPoint)
if type(obj.Curve) != Part.BSplineCurve:
PathLog.info(translate('PathGeom', "edge %s not handled, assuming not vertical") % type(obj.Curve))
return None
PathLog.error(translate('PathGeom', "isVertical(%s) not supported") % obj)
def isVertical(obj):
'''isVertical(obj) ... answer True if obj points into Z'''
if type(obj) == FreeCAD.Vector:
return isRoughly(obj.x, 0) and isRoughly(obj.y, 0)
if obj.ShapeType == 'Face':
if type(obj.Surface) == Part.Plane:
return isHorizontal(obj.Surface.Axis)
if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone:
return isVertical(obj.Surface.Axis)
if type(obj.Surface) == Part.Sphere:
return True
if type(obj.Surface) == Part.SurfaceOfExtrusion:
return isVertical(obj.Surface.Direction)
if type(obj.Surface) != Part.BSplineSurface:
PathLog.info(translate('PathGeom', "face %s not handled, assuming not vertical") % type(obj.Surface))
return None
@classmethod
def isHorizontal(cls, obj):
'''isHorizontal(obj) ... answer True if obj points into X or Y'''
if type(obj) == FreeCAD.Vector:
return PathGeom.isRoughly(obj.z, 0)
if obj.ShapeType == 'Face':
if type(obj.Surface) == Part.Plane:
return cls.isVertical(obj.Surface.Axis)
if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone:
return cls.isHorizontal(obj.Surface.Axis)
if type(obj.Surface) == Part.Sphere:
return True
if type(obj.Surface) == Part.SurfaceOfExtrusion:
return cls.isHorizontal(obj.Surface.Direction)
return cls.isRoughly(obj.BoundBox.ZLength, 0.0)
if obj.ShapeType == 'Edge':
if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment:
return cls.isHorizontal(obj.Vertexes[1].Point - obj.Vertexes[0].Point)
if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve:
return cls.isVertical(obj.Curve.Axis)
return cls.isRoughly(obj.BoundBox.ZLength, 0.0)
PathLog.error(translate('PathGeom', "isHorizontal(%s) not supported") % obj)
if obj.ShapeType == 'Edge':
if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment:
return isVertical(obj.Vertexes[1].Point - obj.Vertexes[0].Point)
if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve:
return isHorizontal(obj.Curve.Axis)
if type(obj.Curve) == Part.BezierCurve:
# the current assumption is that a bezier curve is vertical if its end points are vertical
return isVertical(obj.Curve.EndPoint - obj.Curve.StartPoint)
if type(obj.Curve) != Part.BSplineCurve:
PathLog.info(translate('PathGeom', "edge %s not handled, assuming not vertical") % type(obj.Curve))
return None
PathLog.error(translate('PathGeom', "isVertical(%s) not supported") % obj)
return None
def isHorizontal(obj):
'''isHorizontal(obj) ... answer True if obj points into X or Y'''
if type(obj) == FreeCAD.Vector:
return isRoughly(obj.z, 0)
if obj.ShapeType == 'Face':
if type(obj.Surface) == Part.Plane:
return isVertical(obj.Surface.Axis)
if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone:
return isHorizontal(obj.Surface.Axis)
if type(obj.Surface) == Part.Sphere:
return True
if type(obj.Surface) == Part.SurfaceOfExtrusion:
return isHorizontal(obj.Surface.Direction)
return isRoughly(obj.BoundBox.ZLength, 0.0)
if obj.ShapeType == 'Edge':
if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment:
return isHorizontal(obj.Vertexes[1].Point - obj.Vertexes[0].Point)
if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve:
return isVertical(obj.Curve.Axis)
return isRoughly(obj.BoundBox.ZLength, 0.0)
PathLog.error(translate('PathGeom', "isHorizontal(%s) not supported") % obj)
return None
@classmethod
def commandEndPoint(cls, cmd, defaultPoint = Vector(), X='X', Y='Y', Z='Z'):
"""(cmd, [defaultPoint=Vector()], [X='X'], [Y='Y'], [Z='Z'])
Extracts the end point from a Path Command."""
x = cmd.Parameters.get(X, defaultPoint.x)
y = cmd.Parameters.get(Y, defaultPoint.y)
z = cmd.Parameters.get(Z, defaultPoint.z)
return Vector(x, y, z)
def commandEndPoint(cmd, defaultPoint = Vector(), X='X', Y='Y', Z='Z'):
"""(cmd, [defaultPoint=Vector()], [X='X'], [Y='Y'], [Z='Z'])
Extracts the end point from a Path Command."""
x = cmd.Parameters.get(X, defaultPoint.x)
y = cmd.Parameters.get(Y, defaultPoint.y)
z = cmd.Parameters.get(Z, defaultPoint.z)
return Vector(x, y, z)
@classmethod
def xy(cls, point):
"""(point)
Convenience function to return the projection of the Vector in the XY-plane."""
return Vector(point.x, point.y, 0)
def xy(point):
"""(point)
Convenience function to return the projection of the Vector in the XY-plane."""
return Vector(point.x, point.y, 0)
@classmethod
def cmdsForEdge(cls, edge, flip = False, useHelixForBSpline = True, segm = 50):
"""(edge, flip=False, useHelixForBSpline=True, segm=50) -> List(Path.Command)
Returns a list of Path.Command representing the given edge.
If flip is True the edge is considered to be backwards.
If useHelixForBSpline is True an Edge based on a BSplineCurve is considered
to represent a helix and results in G2 or G3 command. Otherwise edge has
no direct Path.Command mapping and will be approximated by straight segments.
segm is a factor for the segmentation of arbitrary curves not mapped to G1/2/3
commands. The higher the value the more segments will be used."""
pt = edge.valueAt(edge.LastParameter) if not flip else edge.valueAt(edge.FirstParameter)
params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z}
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
commands = [Path.Command('G1', params)]
else:
p1 = edge.valueAt(edge.FirstParameter) if not flip else edge.valueAt(edge.LastParameter)
p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)
p3 = pt
if (type(edge.Curve) == Part.Circle and cls.isRoughly(edge.Curve.Axis.x, 0) and cls.isRoughly(edge.Curve.Axis.y, 0)) or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve):
# This is an arc or a helix and it should be represented by a simple G2/G3 command
if edge.Curve.Axis.z < 0:
cmd = 'G2' if not flip else 'G3'
else:
cmd = 'G3' if not flip else 'G2'
if cls.pointsCoincide(p1, p3):
# A full circle
offset = edge.Curve.Center - pt
else:
pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center
PathLog.debug("**** %s.%d: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) -> center=(%.2f, %.2f)" % (cmd, flip, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, pd.x, pd.y))
# Have to calculate the center in the XY plane, using pd leads to an error if this is a helix
pa = PathGeom.xy(p1)
pb = PathGeom.xy(p2)
pc = PathGeom.xy(p3)
offset = Part.Circle(pa, pb, pc).Center - pa
PathLog.debug("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z))
PathLog.debug("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z))
PathLog.debug("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z))
params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2})
commands = [ Path.Command(cmd, params) ]
else:
# We're dealing with a helix or a more complex shape and it has to get approximated
# by a number of straight segments
eStraight = Part.Edge(Part.LineSegment(p1, p3))
esP2 = eStraight.valueAt((eStraight.FirstParameter + eStraight.LastParameter)/2)
deviation = (p2 - esP2).Length
if cls.isRoughly(deviation, 0):
return [ Path.Command('G1', {'X': p3.x, 'Y': p3.y, 'Z': p3.z}) ]
# at this point pixellation is all we can do
commands = []
segments = int(math.ceil((deviation / eStraight.Length) * segm))
#print("**** pixellation with %d segments" % segments)
dParameter = (edge.LastParameter - edge.FirstParameter) / segments
for i in range(0, segments):
if flip:
p = edge.valueAt(edge.LastParameter - (i + 1) * dParameter)
else:
p = edge.valueAt(edge.FirstParameter + (i + 1) * dParameter)
cmd = Path.Command('G1', {'X': p.x, 'Y': p.y, 'Z': p.z})
#print("***** %s" % cmd)
commands.append(cmd)
#print commands
return commands
@classmethod
def edgeForCmd(cls, cmd, startPoint):
"""(cmd, startPoint).
Returns an Edge representing the given command, assuming a given startPoint."""
endPoint = cls.commandEndPoint(cmd, startPoint)
if (cmd.Name in cls.CmdMoveStraight) or (cmd.Name in cls.CmdMoveRapid):
if cls.pointsCoincide(startPoint, endPoint):
return None
return Part.Edge(Part.LineSegment(startPoint, endPoint))
if cmd.Name in cls.CmdMoveArc:
center = startPoint + cls.commandEndPoint(cmd, Vector(0,0,0), 'I', 'J', 'K')
A = cls.xy(startPoint - center)
B = cls.xy(endPoint - center)
d = -B.x * A.y + B.y * A.x
if cls.isRoughly(d, 0, 0.005):
PathLog.debug("Half circle arc at: (%.2f, %.2f, %.2f)" % (center.x, center.y, center.z))
# we're dealing with half a circle here
angle = cls.getAngle(A) + math.pi/2
if cmd.Name in cls.CmdMoveCW:
angle -= math.pi
else:
C = A + B
angle = cls.getAngle(C)
PathLog.debug("Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f" % (d, center.x, center.y, center.z, angle / math.pi))
R = A.Length
PathLog.debug("arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)" % (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y))
PathLog.debug("arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d))
PathLog.debug("arc: R=%.2f angle=%.2f" % (R, angle/math.pi))
if cls.isRoughly(startPoint.z, endPoint.z):
midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R
PathLog.debug("arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" % (startPoint.x, startPoint.y, midPoint.x, midPoint.y, endPoint.x, endPoint.y))
return Part.Edge(Part.Arc(startPoint, midPoint, endPoint))
# It's a Helix
#print('angle: A=%.2f B=%.2f' % (cls.getAngle(A)/math.pi, cls.getAngle(B)/math.pi))
if cmd.Name in cls.CmdMoveCW:
cw = True
else:
cw = False
angle = cls.diffAngle(cls.getAngle(A), cls.getAngle(B), 'CW' if cw else 'CCW')
height = endPoint.z - startPoint.z
pitch = height * math.fabs(2 * math.pi / angle)
if angle > 0:
cw = not cw
#print("Helix: R=%.2f h=%.2f angle=%.2f pitch=%.2f" % (R, height, angle/math.pi, pitch))
helix = Part.makeHelix(pitch, height, R, 0, not cw)
helix.rotate(Vector(), Vector(0,0,1), 180 * cls.getAngle(A) / math.pi)
e = helix.Edges[0]
helix.translate(startPoint - e.valueAt(e.FirstParameter))
return helix.Edges[0]
return None
@classmethod
def wireForPath(cls, path, startPoint = Vector(0, 0, 0)):
"""(path, [startPoint=Vector(0,0,0)])
Returns a wire representing all move commands found in the given path."""
edges = []
rapid = []
if hasattr(path, "Commands"):
for cmd in path.Commands:
edge = cls.edgeForCmd(cmd, startPoint)
if edge:
if cmd.Name in cls.CmdMoveRapid:
rapid.append(edge)
edges.append(edge)
startPoint = cls.commandEndPoint(cmd, startPoint)
return (Part.Wire(edges), rapid)
@classmethod
def wiresForPath(cls, path, startPoint = Vector(0, 0, 0)):
"""(path, [startPoint=Vector(0,0,0)])
Returns a collection of wires, each representing a continuous cutting Path in path."""
wires = []
if hasattr(path, "Commands"):
edges = []
for cmd in path.Commands:
if cmd.Name in cls.CmdMove:
edges.append(cls.edgeForCmd(cmd, startPoint))
startPoint = cls.commandEndPoint(cmd, startPoint)
elif cmd.Name in cls.CmdMoveRapid:
wires.append(Part.Wire(edges))
edges = []
startPoint = cls.commandEndPoint(cmd, startPoint)
if edges:
wires.append(Part.Wire(edges))
return wires
@classmethod
def arcToHelix(cls, edge, z0, z1):
"""(edge, z0, z1)
Assuming edge is an arc it'll return a helix matching the arc starting at z0 and rising/falling to z1."""
p1 = edge.valueAt(edge.FirstParameter)
p2 = edge.valueAt(edge.LastParameter)
cmd = cls.cmdsForEdge(edge)[0]
params = cmd.Parameters
params.update({'Z': z1, 'K': (z1 - z0)/2})
command = Path.Command(cmd.Name, params)
#print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1))
#print("- %s -> %s" % (cmd, command))
return cls.edgeForCmd(command, Vector(p1.x, p1.y, z0))
@classmethod
def helixToArc(cls, edge, z = 0):
"""(edge, z=0)
Returns the projection of the helix onto the XY-plane with a given offset."""
p1 = edge.valueAt(edge.FirstParameter)
def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50):
"""(edge, flip=False, useHelixForBSpline=True, segm=50) -> List(Path.Command)
Returns a list of Path.Command representing the given edge.
If flip is True the edge is considered to be backwards.
If useHelixForBSpline is True an Edge based on a BSplineCurve is considered
to represent a helix and results in G2 or G3 command. Otherwise edge has
no direct Path.Command mapping and will be approximated by straight segments.
segm is a factor for the segmentation of arbitrary curves not mapped to G1/2/3
commands. The higher the value the more segments will be used."""
pt = edge.valueAt(edge.LastParameter) if not flip else edge.valueAt(edge.FirstParameter)
params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z}
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
commands = [Path.Command('G1', params)]
else:
p1 = edge.valueAt(edge.FirstParameter) if not flip else edge.valueAt(edge.LastParameter)
p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)
p3 = edge.valueAt(edge.LastParameter)
p01 = Vector(p1.x, p1.y, z)
p02 = Vector(p2.x, p2.y, z)
p03 = Vector(p3.x, p3.y, z)
return Part.Edge(Part.Arc(p01, p02, p03))
p3 = pt
@classmethod
def splitArcAt(cls, edge, pt):
"""(edge, pt)
Returns a list of 2 edges which together form the original arc split at the given point.
The Vector pt has to represent a point on the given arc."""
p1 = edge.valueAt(edge.FirstParameter)
p2 = pt
p3 = edge.valueAt(edge.LastParameter)
edges = []
if (type(edge.Curve) == Part.Circle and isRoughly(edge.Curve.Axis.x, 0) and isRoughly(edge.Curve.Axis.y, 0)) or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve):
# This is an arc or a helix and it should be represented by a simple G2/G3 command
if edge.Curve.Axis.z < 0:
cmd = 'G2' if not flip else 'G3'
else:
cmd = 'G3' if not flip else 'G2'
p = edge.Curve.parameter(p2)
#print("splitArcAt(%.2f, %.2f, %.2f): %.2f - %.2f - %.2f" % (pt.x, pt.y, pt.z, edge.FirstParameter, p, edge.LastParameter))
if pointsCoincide(p1, p3):
# A full circle
offset = edge.Curve.Center - pt
else:
pd = Part.Circle(xy(p1), xy(p2), xy(p3)).Center
PathLog.debug("**** %s.%d: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) -> center=(%.2f, %.2f)" % (cmd, flip, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, pd.x, pd.y))
p12 = edge.Curve.value((edge.FirstParameter + p)/2)
p23 = edge.Curve.value((p + edge.LastParameter)/2)
#print("splitArcAt: p12=(%.2f, %.2f, %.2f) p23=(%.2f, %.2f, %.2f)" % (p12.x, p12.y, p12.z, p23.x, p23.y, p23.z))
# Have to calculate the center in the XY plane, using pd leads to an error if this is a helix
pa = xy(p1)
pb = xy(p2)
pc = xy(p3)
offset = Part.Circle(pa, pb, pc).Center - pa
edges.append(Part.Edge(Part.Arc(p1, p12, p2)))
edges.append(Part.Edge(Part.Arc(p2, p23, p3)))
PathLog.debug("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z))
PathLog.debug("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z))
PathLog.debug("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z))
return edges
params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2})
commands = [ Path.Command(cmd, params) ]
@classmethod
def splitEdgeAt(cls, edge, pt):
"""(edge, pt)
Returns a list of 2 edges, forming the original edge split at the given point.
The results are undefined if the Vector representing the point is not part of the edge."""
# I could not get the OCC parameterAt and split to work ...
# pt HAS to be on the edge, otherwise the results are undefined
p1 = edge.valueAt(edge.FirstParameter)
p2 = pt
p3 = edge.valueAt(edge.LastParameter)
edges = []
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
# it's a line
return [Part.Edge(Part.LineSegment(p1, p2)), Part.Edge(Part.LineSegment(p2, p3))]
elif type(edge.Curve) == Part.Circle:
# it's an arc
return cls.splitArcAt(edge, pt)
else:
# it's a helix
arc = cls.helixToArc(edge, 0)
aes = cls.splitArcAt(arc, Vector(pt.x, pt.y, 0))
return [cls.arcToHelix(aes[0], p1.z, p2.z), cls.arcToHelix(aes[1], p2.z, p3.z)]
@classmethod
def combineConnectedShapes(cls, shapes):
done = False
while not done:
done = True
combined = []
PathLog.debug("shapes: {}".format(shapes))
for shape in shapes:
connected = [f for f in combined if cls.isRoughly(shape.distToShape(f)[0], 0.0)]
PathLog.debug(" {}: connected: {} dist: {}".format(len(combined), connected, [shape.distToShape(f)[0] for f in combined]))
if connected:
combined = [f for f in combined if f not in connected]
connected.append(shape)
combined.append(Part.makeCompound(connected))
done = False
# We're dealing with a helix or a more complex shape and it has to get approximated
# by a number of straight segments
eStraight = Part.Edge(Part.LineSegment(p1, p3))
esP2 = eStraight.valueAt((eStraight.FirstParameter + eStraight.LastParameter)/2)
deviation = (p2 - esP2).Length
if isRoughly(deviation, 0):
return [ Path.Command('G1', {'X': p3.x, 'Y': p3.y, 'Z': p3.z}) ]
# at this point pixellation is all we can do
commands = []
segments = int(math.ceil((deviation / eStraight.Length) * segm))
#print("**** pixellation with %d segments" % segments)
dParameter = (edge.LastParameter - edge.FirstParameter) / segments
for i in range(0, segments):
if flip:
p = edge.valueAt(edge.LastParameter - (i + 1) * dParameter)
else:
combined.append(shape)
shapes = combined
return shapes
p = edge.valueAt(edge.FirstParameter + (i + 1) * dParameter)
cmd = Path.Command('G1', {'X': p.x, 'Y': p.y, 'Z': p.z})
#print("***** %s" % cmd)
commands.append(cmd)
#print commands
return commands
@classmethod
def removeDuplicateEdges(cls, wire):
unique = []
for e in wire.Edges:
if not any(cls.edgesMatch(e, u) for u in unique):
unique.append(e)
return Part.Wire(unique)
def edgeForCmd(cmd, startPoint):
"""(cmd, startPoint).
Returns an Edge representing the given command, assuming a given startPoint."""
endPoint = commandEndPoint(cmd, startPoint)
if (cmd.Name in CmdMoveStraight) or (cmd.Name in CmdMoveRapid):
if pointsCoincide(startPoint, endPoint):
return None
return Part.Edge(Part.LineSegment(startPoint, endPoint))
if cmd.Name in CmdMoveArc:
center = startPoint + commandEndPoint(cmd, Vector(0,0,0), 'I', 'J', 'K')
A = xy(startPoint - center)
B = xy(endPoint - center)
d = -B.x * A.y + B.y * A.x
if isRoughly(d, 0, 0.005):
PathLog.debug("Half circle arc at: (%.2f, %.2f, %.2f)" % (center.x, center.y, center.z))
# we're dealing with half a circle here
angle = getAngle(A) + math.pi/2
if cmd.Name in CmdMoveCW:
angle -= math.pi
else:
C = A + B
angle = getAngle(C)
PathLog.debug("Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f" % (d, center.x, center.y, center.z, angle / math.pi))
R = A.Length
PathLog.debug("arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)" % (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y))
PathLog.debug("arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d))
PathLog.debug("arc: R=%.2f angle=%.2f" % (R, angle/math.pi))
if isRoughly(startPoint.z, endPoint.z):
midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R
PathLog.debug("arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" % (startPoint.x, startPoint.y, midPoint.x, midPoint.y, endPoint.x, endPoint.y))
return Part.Edge(Part.Arc(startPoint, midPoint, endPoint))
# It's a Helix
#print('angle: A=%.2f B=%.2f' % (getAngle(A)/math.pi, getAngle(B)/math.pi))
if cmd.Name in CmdMoveCW:
cw = True
else:
cw = False
angle = diffAngle(getAngle(A), getAngle(B), 'CW' if cw else 'CCW')
height = endPoint.z - startPoint.z
pitch = height * math.fabs(2 * math.pi / angle)
if angle > 0:
cw = not cw
#print("Helix: R=%.2f h=%.2f angle=%.2f pitch=%.2f" % (R, height, angle/math.pi, pitch))
helix = Part.makeHelix(pitch, height, R, 0, not cw)
helix.rotate(Vector(), Vector(0,0,1), 180 * getAngle(A) / math.pi)
e = helix.Edges[0]
helix.translate(startPoint - e.valueAt(e.FirstParameter))
return helix.Edges[0]
return None
def wireForPath(path, startPoint = Vector(0, 0, 0)):
"""(path, [startPoint=Vector(0,0,0)])
Returns a wire representing all move commands found in the given path."""
edges = []
rapid = []
if hasattr(path, "Commands"):
for cmd in path.Commands:
edge = edgeForCmd(cmd, startPoint)
if edge:
if cmd.Name in CmdMoveRapid:
rapid.append(edge)
edges.append(edge)
startPoint = commandEndPoint(cmd, startPoint)
return (Part.Wire(edges), rapid)
def wiresForPath(path, startPoint = Vector(0, 0, 0)):
"""(path, [startPoint=Vector(0,0,0)])
Returns a collection of wires, each representing a continuous cutting Path in path."""
wires = []
if hasattr(path, "Commands"):
edges = []
for cmd in path.Commands:
if cmd.Name in CmdMove:
edges.append(edgeForCmd(cmd, startPoint))
startPoint = commandEndPoint(cmd, startPoint)
elif cmd.Name in CmdMoveRapid:
wires.append(Part.Wire(edges))
edges = []
startPoint = commandEndPoint(cmd, startPoint)
if edges:
wires.append(Part.Wire(edges))
return wires
def arcToHelix(edge, z0, z1):
"""(edge, z0, z1)
Assuming edge is an arc it'll return a helix matching the arc starting at z0 and rising/falling to z1."""
p1 = edge.valueAt(edge.FirstParameter)
p2 = edge.valueAt(edge.LastParameter)
cmd = cmdsForEdge(edge)[0]
params = cmd.Parameters
params.update({'Z': z1, 'K': (z1 - z0)/2})
command = Path.Command(cmd.Name, params)
#print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1))
#print("- %s -> %s" % (cmd, command))
return edgeForCmd(command, Vector(p1.x, p1.y, z0))
def helixToArc(edge, z = 0):
"""(edge, z=0)
Returns the projection of the helix onto the XY-plane with a given offset."""
p1 = edge.valueAt(edge.FirstParameter)
p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)
p3 = edge.valueAt(edge.LastParameter)
p01 = Vector(p1.x, p1.y, z)
p02 = Vector(p2.x, p2.y, z)
p03 = Vector(p3.x, p3.y, z)
return Part.Edge(Part.Arc(p01, p02, p03))
def splitArcAt(edge, pt):
"""(edge, pt)
Returns a list of 2 edges which together form the original arc split at the given point.
The Vector pt has to represent a point on the given arc."""
p1 = edge.valueAt(edge.FirstParameter)
p2 = pt
p3 = edge.valueAt(edge.LastParameter)
edges = []
p = edge.Curve.parameter(p2)
#print("splitArcAt(%.2f, %.2f, %.2f): %.2f - %.2f - %.2f" % (pt.x, pt.y, pt.z, edge.FirstParameter, p, edge.LastParameter))
p12 = edge.Curve.value((edge.FirstParameter + p)/2)
p23 = edge.Curve.value((p + edge.LastParameter)/2)
#print("splitArcAt: p12=(%.2f, %.2f, %.2f) p23=(%.2f, %.2f, %.2f)" % (p12.x, p12.y, p12.z, p23.x, p23.y, p23.z))
edges.append(Part.Edge(Part.Arc(p1, p12, p2)))
edges.append(Part.Edge(Part.Arc(p2, p23, p3)))
return edges
def splitEdgeAt(edge, pt):
"""(edge, pt)
Returns a list of 2 edges, forming the original edge split at the given point.
The results are undefined if the Vector representing the point is not part of the edge."""
# I could not get the OCC parameterAt and split to work ...
# pt HAS to be on the edge, otherwise the results are undefined
p1 = edge.valueAt(edge.FirstParameter)
p2 = pt
p3 = edge.valueAt(edge.LastParameter)
edges = []
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
# it's a line
return [Part.Edge(Part.LineSegment(p1, p2)), Part.Edge(Part.LineSegment(p2, p3))]
elif type(edge.Curve) == Part.Circle:
# it's an arc
return splitArcAt(edge, pt)
else:
# it's a helix
arc = helixToArc(edge, 0)
aes = splitArcAt(arc, Vector(pt.x, pt.y, 0))
return [arcToHelix(aes[0], p1.z, p2.z), arcToHelix(aes[1], p2.z, p3.z)]
def combineConnectedShapes(shapes):
done = False
while not done:
done = True
combined = []
PathLog.debug("shapes: {}".format(shapes))
for shape in shapes:
connected = [f for f in combined if isRoughly(shape.distToShape(f)[0], 0.0)]
PathLog.debug(" {}: connected: {} dist: {}".format(len(combined), connected, [shape.distToShape(f)[0] for f in combined]))
if connected:
combined = [f for f in combined if f not in connected]
connected.append(shape)
combined.append(Part.makeCompound(connected))
done = False
else:
combined.append(shape)
shapes = combined
return shapes
def removeDuplicateEdges(wire):
unique = []
for e in wire.Edges:
if not any(edgesMatch(e, u) for u in unique):
unique.append(e)
return Part.Wire(unique)

View File

@@ -24,10 +24,10 @@
import FreeCAD
import FreeCADGui
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PySide
from PathScripts.PathGeom import PathGeom
__title__ = "Path UI helper and utility functions"
__author__ = "sliptonic (Brad Collette)"

View File

@@ -27,6 +27,7 @@ import DraftVecUtils
import FreeCAD
import FreeCADGui
import PathScripts.PathJob as PathJob
import PathScripts.PathGeom as PathGeom
import PathScripts.PathGui as PathGui
import PathScripts.PathLog as PathLog
import PathScripts.PathStock as PathStock
@@ -37,7 +38,6 @@ import PathScripts.PathUtils as PathUtils
import math
import sys
from PathScripts.PathGeom import PathGeom
from PathScripts.PathPreferences import PathPreferences
from PySide import QtCore, QtGui
from pivy import coin

View File

@@ -24,12 +24,12 @@
import FreeCAD
import Path
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathSetupSheet as PathSetupSheet
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
from PathScripts.PathGeom import PathGeom
from PathScripts.PathUtils import waiting_effects
from PySide import QtCore

View File

@@ -24,6 +24,7 @@
import FreeCAD
import FreeCADGui
import PathScripts.PathGeom as PathGeom
import PathScripts.PathGetPoint as PathGetPoint
import PathScripts.PathGui as PathGui
import PathScripts.PathLog as PathLog
@@ -34,7 +35,6 @@ import PathScripts.PathUtils as PathUtils
import importlib
from PySide import QtCore, QtGui
from PathScripts.PathGeom import PathGeom
from PathScripts.PathPreferences import PathPreferences
__title__ = "Path Operation UI base classes"

View File

@@ -24,13 +24,13 @@
import FreeCAD
import Part
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathPocketBase as PathPocketBase
import PathScripts.PathUtils as PathUtils
import sys
from PathScripts.PathGeom import PathGeom
from PySide import QtCore
__doc__ = "Class and implementation of the Pocket operation."

View File

@@ -24,6 +24,7 @@
import FreeCAD
import Part
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathPocketBase as PathPocketBase
@@ -31,7 +32,6 @@ import PathScripts.PathUtils as PathUtils
import TechDraw
import sys
from PathScripts.PathGeom import PathGeom
from PySide import QtCore
__title__ = "Path Pocket Shape Operation"

View File

@@ -24,13 +24,11 @@
import FreeCAD
import Path
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathUtil as PathUtil
import PySide
from PathScripts.PathGeom import PathGeom
__title__ = "Setup Sheet for a Job."
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"

View File

@@ -1,14 +1,15 @@
import os
import FreeCAD
import Path
import Part
import Mesh
import Part
import Path
import PathScripts.PathDressup as PathDressup
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathSimulator
import math
import os
from FreeCAD import Vector, Base
import PathScripts.PathLog as PathLog
from PathScripts.PathGeom import PathGeom
import PathScripts.PathDressup as PathDressup
_filePath = os.path.dirname(os.path.abspath(__file__))

View File

@@ -29,6 +29,7 @@ import numpy
import Part
import Path
import PathScripts
import PathScripts.PathGeom as PathGeom
import TechDraw
from DraftGeomUtils import geomType
@@ -36,7 +37,6 @@ from FreeCAD import Vector
from PathScripts import PathJob
from PathScripts import PathJobCmd
from PathScripts import PathLog
from PathScripts.PathGeom import PathGeom
from PySide import QtCore
from PySide import QtGui

View File

@@ -24,11 +24,11 @@
import FreeCAD
import Part
import PathScripts.PathGeom as PathGeom
import math
import unittest
from FreeCAD import Vector
from PathScripts.PathGeom import Side
class PathTestBase(unittest.TestCase):
"""Base test class with some additional asserts."""
@@ -72,11 +72,11 @@ class PathTestBase(unittest.TestCase):
self.assertCoincide(edge.valueAt(edge.FirstParameter), pt1)
self.assertCoincide(edge.valueAt(edge.LastParameter), pt2)
ptm = edge.valueAt((edge.LastParameter + edge.FirstParameter)/2)
side = Side.of(pt2 - pt1, ptm - pt1)
side = PathGeom.Side.of(pt2 - pt1, ptm - pt1)
if 'CW' == direction:
self.assertEqual(side, Side.Left)
self.assertEqual(side, PathGeom.Side.Left)
else:
self.assertEqual(side, Side.Right)
self.assertEqual(side, PathGeom.Side.Right)
def assertCircle(self, edge, pt, r):
"""Verivy that edge is a circle at given location."""

View File

@@ -26,12 +26,12 @@ import FreeCAD
import Part
import Path
import PathScripts
import PathScripts.PathGeom as PathGeom
import math
import unittest
from FreeCAD import Vector
#from PathScripts.PathDressupHoldingTags import *
from PathScripts.PathGeom import PathGeom
from PathTests.PathTestUtils import PathTestBase
class TestPathGeom(PathTestBase):

View File

@@ -43,10 +43,17 @@ if [ ! -d 'PathScripts' ]; then
exit 2
fi
EXTERNAL_MODULES+=' ArchPanel'
EXTERNAL_MODULES+=' Draft'
EXTERNAL_MODULES+=' DraftGeomUtils'
EXTERNAL_MODULES+=' DraftVecUtils'
EXTERNAL_MODULES+=' FreeCAD'
EXTERNAL_MODULES+=' FreeCADGui'
EXTERNAL_MODULES+=' Part'
EXTERNAL_MODULES+=' Path'
EXTERNAL_MODULES+=' PySide.QtCore'
EXTERNAL_MODULES+=' PySide.QtGui'
EXTERNAL_MODULES+=' FreeCAD'
EXTERNAL_MODULES+=' DraftGeomUtils'
EXTERNAL_MODULES+=' TechDraw'
EXTERNAL_MODULES+=' importlib'
ARGS+=" --errors-only"