Started moving base classes into Path.Base module

This commit is contained in:
Markus Lampert
2022-08-13 18:11:56 -07:00
parent 9925c71d36
commit 998347e97f
67 changed files with 474 additions and 508 deletions

View File

@@ -24,7 +24,6 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Part
import Path
import PathScripts.PathGeom as PathGeom
import math
# lazily loaded modules
@@ -56,7 +55,7 @@ def endPoints(edgeOrWire):
pts.extend([e.valueAt(e.LastParameter) for e in edgeOrWire.Edges])
unique = []
for p in pts:
cnt = len([p2 for p2 in pts if PathGeom.pointsCoincide(p, p2)])
cnt = len([p2 for p2 in pts if Path.Geom.pointsCoincide(p, p2)])
if 1 == cnt:
unique.append(p)
@@ -64,7 +63,7 @@ def endPoints(edgeOrWire):
pfirst = edgeOrWire.valueAt(edgeOrWire.FirstParameter)
plast = edgeOrWire.valueAt(edgeOrWire.LastParameter)
if PathGeom.pointsCoincide(pfirst, plast):
if Path.Geom.pointsCoincide(pfirst, plast):
return None
return [pfirst, plast]
@@ -73,7 +72,7 @@ def endPoints(edgeOrWire):
def includesPoint(p, pts):
"""includesPoint(p, pts) ... answer True if the collection of pts includes the point p"""
for pt in pts:
if PathGeom.pointsCoincide(p, pt):
if Path.Geom.pointsCoincide(p, pt):
return True
return False
@@ -246,7 +245,7 @@ class Extension(object):
e2 = e0.copy()
off = self.length.Value * direction
e2.translate(off)
e2 = PathGeom.flipEdge(e2)
e2 = Path.Geom.flipEdge(e2)
e1 = Part.Edge(
Part.LineSegment(
e0.valueAt(e0.LastParameter), e2.valueAt(e2.FirstParameter)
@@ -295,7 +294,7 @@ class Extension(object):
tangent = e0.tangentAt(midparam)
Path.Log.track("tangent", tangent, self.feature, self.sub)
normal = tangent.cross(FreeCAD.Vector(0, 0, 1))
if PathGeom.pointsCoincide(normal, FreeCAD.Vector(0, 0, 0)):
if Path.Geom.pointsCoincide(normal, FreeCAD.Vector(0, 0, 0)):
return None
return self._getDirectedNormal(e0.valueAt(midparam), normal.normalize())
@@ -326,7 +325,7 @@ class Extension(object):
Path.Log.track()
length = self.length.Value
if PathGeom.isRoughly(0, length) or not self.sub:
if Path.Geom.isRoughly(0, length) or not self.sub:
Path.Log.debug("no extension, length=%.2f, sub=%s" % (length, self.sub))
return None
@@ -347,7 +346,7 @@ class Extension(object):
if direction is None:
return None
if PathGeom.pointsCoincide(normal, direction):
if Path.Geom.pointsCoincide(normal, direction):
r = circle.Radius - length
else:
r = circle.Radius + length
@@ -365,7 +364,7 @@ class Extension(object):
# Determine if rotational alignment is necessary for new arc
rotationAdjustment = arcAdjustmentAngle(edge, e3)
if not PathGeom.isRoughly(rotationAdjustment, 0.0):
if not Path.Geom.isRoughly(rotationAdjustment, 0.0):
e3.rotate(
edge.Curve.Center,
FreeCAD.Vector(0.0, 0.0, 1.0),
@@ -424,7 +423,7 @@ class Extension(object):
subFace = Part.Face(sub)
featFace = Part.Face(feature.Wires[0])
isOutside = True
if not PathGeom.isRoughly(featFace.Area, subFace.Area):
if not Path.Geom.isRoughly(featFace.Area, subFace.Area):
length = -1.0 * length
isOutside = False
@@ -560,9 +559,9 @@ def getExtendOutlineFace(
bbx = f.BoundBox
zNorm = abs(f.normalAt(0.0, 0.0).z)
if (
PathGeom.isRoughly(zNorm, 1.0)
and PathGeom.isRoughly(bbx.ZMax - bbx.ZMin, 0.0)
and PathGeom.isRoughly(bbx.ZMin, face.BoundBox.ZMin)
Path.Geom.isRoughly(zNorm, 1.0)
and Path.Geom.isRoughly(bbx.ZMax - bbx.ZMin, 0.0)
and Path.Geom.isRoughly(bbx.ZMin, face.BoundBox.ZMin)
):
if bbx.ZMin < zmin:
bottom_faces.append(f)
@@ -610,7 +609,7 @@ def getWaterlineFace(base_shape, face):
# Get top face(s) of envelope at face height
rawList = list()
for f in env.Faces:
if PathGeom.isRoughly(f.BoundBox.ZMin, faceHeight):
if Path.Geom.isRoughly(f.BoundBox.ZMin, faceHeight):
rawList.append(f)
# make compound and extrude downward
rawComp = Part.makeCompound(rawList)

View File

@@ -27,7 +27,6 @@ import FreeCADGui
import Path
import Path.Op.Gui.Base as PathOpGui
import PathScripts.PathFeatureExtensions as FeatureExtensions
import PathScripts.PathGeom as PathGeom
import PathScripts.PathGui as PathGui
# lazily loaded modules
@@ -361,13 +360,13 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
if extendCorners:
def edgesMatchShape(e0, e1):
flipped = PathGeom.flipEdge(e1)
flipped = Path.Geom.flipEdge(e1)
if flipped:
return PathGeom.edgesMatch(e0, e1) or PathGeom.edgesMatch(
return Path.Geom.edgesMatch(e0, e1) or Path.Geom.edgesMatch(
e0, flipped
)
else:
return PathGeom.edgesMatch(e0, e1)
return Path.Geom.edgesMatch(e0, e1)
self.extensionEdges = extensionEdges
Path.Log.debug("extensionEdges.values(): {}".format(extensionEdges.values()))

View File

@@ -1,789 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2016 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2021 Schildkroet *
# * *
# * 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
import Path
import math
from FreeCAD import Vector
from PySide import QtCore
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Part = LazyLoader("Part", globals(), "Part")
__title__ = "PathGeom - geometry utilities for Path"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://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
translate = FreeCAD.Qt.translate
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
class Side:
"""Class to determine and define the side a Path is on, or Vectors are in relation to each other."""
Left = +1
Right = -1
Straight = 0
On = 0
@classmethod
def toString(cls, side):
"""toString(side)
Returns a string representation of the enum value."""
if side == cls.Left:
return "Left"
if side == cls.Right:
return "Right"
return "On"
@classmethod
def of(cls, ptRef, pt):
"""of(ptRef, pt)
Determine the side of pt in relation to ptRef.
If both Points are viewed as vectors with their origin in (0,0,0)
then the two vectors either form a straight line (On) or pt
lies in the left or right hemisphere in regards to ptRef."""
d = -ptRef.x * pt.y + ptRef.y * pt.x
if d < 0:
return cls.Left
if d > 0:
return cls.Right
return cls.Straight
CmdMoveRapid = ["G0", "G00"]
CmdMoveStraight = ["G1", "G01"]
CmdMoveCW = ["G2", "G02"]
CmdMoveCCW = ["G3", "G03"]
CmdMoveDrill = ["G73", "G81", "G82", "G83"]
CmdMoveArc = CmdMoveCW + CmdMoveCCW
CmdMove = CmdMoveStraight + CmdMoveArc + CmdMoveDrill
CmdMoveAll = CmdMove + CmdMoveRapid
def isRoughly(float1, float2, error=Tolerance):
"""isRoughly(float1, float2, [error=Tolerance])
Returns true if the two values are the same within a given error."""
return math.fabs(float1 - float2) <= error
def pointsCoincide(p1, p2, error=Tolerance):
"""pointsCoincide(p1, p2, [error=Tolerance])
Return True if two points are roughly identical (see also isRoughly)."""
return (
isRoughly(p1.x, p2.x, error)
and isRoughly(p1.y, p2.y, error)
and isRoughly(p1.z, p2.z, error)
)
def edgesMatch(e0, e1, error=Tolerance):
"""edgesMatch(e0, e1, [error=Tolerance]
Return true if the edges start and end at the same point and have the same type of curve."""
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, error)
for i in range(len(e0.Vertexes))
)
def edgeConnectsTo(edge, vector, error=Tolerance):
"""edgeConnectsTop(edge, vector, error=Tolerance)
Returns True if edge connects to given vector."""
return pointsCoincide(
edge.valueAt(edge.FirstParameter), vector, error
) or pointsCoincide(edge.valueAt(edge.LastParameter), vector, error)
def getAngle(vector):
"""getAngle(vector)
Returns the angle [-pi,pi] of a vector using the X-axis as the reference.
Positive angles for vertexes in the upper hemisphere (positive y values)
and negative angles for the lower hemisphere."""
a = vector.getAngle(Vector(1, 0, 0))
if vector.y < 0:
return -a
return a
def diffAngle(a1, a2, direction="CW"):
"""diffAngle(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
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.SurfaceOfRevolution:
return isHorizontal(obj.Surface.Direction)
if type(obj.Surface) != Part.BSplineSurface:
Path.Log.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 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:
Path.Log.info(
translate("PathGeom", "edge %s not handled, assuming not vertical")
% type(obj.Curve)
)
return None
Path.Log.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)
if type(obj.Surface) == Part.SurfaceOfRevolution:
return isVertical(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)
Path.Log.error(translate("PathGeom", "isHorizontal(%s) not supported") % obj)
return None
def commandEndPoint(cmd, defaultPoint=Vector(), X="X", Y="Y", Z="Z"):
"""commandEndPoint(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 xy(point):
"""xy(point)
Convenience function to return the projection of the Vector in the XY-plane."""
return Vector(point.x, point.y, 0)
def speedBetweenPoints(p0, p1, hSpeed, vSpeed):
if isRoughly(hSpeed, vSpeed):
return hSpeed
d = p1 - p0
if isRoughly(0.0, d.z):
return hSpeed
if isRoughly(0.0, d.x) and isRoughly(0.0, d.y):
return vSpeed
# need to interpolate between hSpeed and vSpeed depending on the pitch
pitch = 2 * math.atan2(xy(d).Length, math.fabs(d.z)) / math.pi
while pitch < 0:
pitch = pitch + 1
while pitch > 1:
pitch = pitch - 1
Path.Log.debug(
" pitch = %g %g (%.2f, %.2f, %.2f) -> %.2f"
% (pitch, math.atan2(xy(d).Length, d.z), d.x, d.y, d.z, xy(d).Length)
)
speed = vSpeed + pitch * (hSpeed - vSpeed)
if speed > hSpeed and speed > vSpeed:
return max(hSpeed, vSpeed)
if speed < hSpeed and speed < vSpeed:
return min(hSpeed, vSpeed)
return speed
def cmdsForEdge(edge, flip=False, useHelixForBSpline=True, segm=50, hSpeed=0, vSpeed=0):
"""cmdsForEdge(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:
if hSpeed > 0 and vSpeed > 0:
pt2 = (
edge.valueAt(edge.FirstParameter)
if not flip
else edge.valueAt(edge.LastParameter)
)
params.update({"F": speedBetweenPoints(pt, pt2, hSpeed, vSpeed)})
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 hasattr(edge.Curve, "Axis") and (
(
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"
if pointsCoincide(p1, p3):
# A full circle
offset = edge.Curve.Center - pt
else:
pd = Part.Circle(xy(p1), xy(p2), xy(p3)).Center
Path.Log.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 = xy(p1)
pb = xy(p2)
pc = xy(p3)
offset = Part.Circle(pa, pb, pc).Center - pa
Path.Log.debug(
"**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)"
% (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)
)
Path.Log.debug(
"**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)"
% (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)
)
Path.Log.debug("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z))
params.update({"I": offset.x, "J": offset.y, "K": (p3.z - p1.z) / 2})
# G2/G3 commands are always performed at hSpeed
if hSpeed > 0:
params.update({"F": hSpeed})
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
points = edge.discretize(Deflection=0.01)
if flip:
points = points[::-1]
commands = []
if points:
p0 = points[0]
for p in points[1:]:
params = {"X": p.x, "Y": p.y, "Z": p.z}
if hSpeed > 0 and vSpeed > 0:
params["F"] = speedBetweenPoints(p0, p, hSpeed, vSpeed)
cmd = Path.Command("G1", params)
# print("***** {}".format(cmd))
commands.append(cmd)
p0 = p
# print commands
return commands
def edgeForCmd(cmd, startPoint):
"""edgeForCmd(cmd, startPoint).
Returns an Edge representing the given command, assuming a given startPoint."""
Path.Log.debug("cmd: {}".format(cmd))
Path.Log.debug("startpoint {}".format(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):
Path.Log.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)
Path.Log.debug(
"Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f"
% (d, center.x, center.y, center.z, angle / math.pi)
)
R = A.Length
Path.Log.debug(
"arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)"
% (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y)
)
Path.Log.debug(
"arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d)
)
Path.Log.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
Path.Log.debug(
"arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)"
% (
startPoint.x,
startPoint.y,
midPoint.x,
midPoint.y,
endPoint.x,
endPoint.y,
)
)
Path.Log.debug("StartPoint:{}".format(startPoint))
Path.Log.debug("MidPoint:{}".format(midPoint))
Path.Log.debug("EndPoint:{}".format(endPoint))
if pointsCoincide(startPoint, endPoint, 0.001):
return Part.makeCircle(R, center, FreeCAD.Vector(0, 0, 1))
else:
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)):
"""wireForPath(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)
if not edges:
return (None, rapid)
return (Part.Wire(edges), rapid)
def wiresForPath(path, startPoint=Vector(0, 0, 0)):
"""wiresForPath(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:
if len(edges) > 0:
wires.append(Part.Wire(edges))
edges = []
startPoint = commandEndPoint(cmd, startPoint)
if edges:
wires.append(Part.Wire(edges))
return wires
def arcToHelix(edge, z0, z1):
"""arcToHelix(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):
"""helixToArc(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):
"""splitArcAt(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."""
p = edge.Curve.parameter(pt)
e0 = Part.Arc(edge.Curve.copy(), edge.FirstParameter, p).toShape()
e1 = Part.Arc(edge.Curve.copy(), p, edge.LastParameter).toShape()
return [e0, e1]
def splitEdgeAt(edge, pt):
"""splitEdgeAt(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 = []
Path.Log.debug("shapes: {}".format(shapes))
for shape in shapes:
connected = [f for f in combined if isRoughly(shape.distToShape(f)[0], 0.0)]
Path.Log.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)
def flipEdge(edge):
"""flipEdge(edge)
Flips given edge around so the new Vertexes[0] was the old Vertexes[-1] and vice versa, without changing the shape.
Currently only lines, line segments, circles and arcs are supported."""
if Part.Line == type(edge.Curve) and not edge.Vertexes:
return Part.Edge(
Part.Line(
edge.valueAt(edge.LastParameter), edge.valueAt(edge.FirstParameter)
)
)
elif Part.Line == type(edge.Curve) or Part.LineSegment == type(edge.Curve):
return Part.Edge(
Part.LineSegment(edge.Vertexes[-1].Point, edge.Vertexes[0].Point)
)
elif Part.Circle == type(edge.Curve):
# Create an inverted circle
circle = Part.Circle(edge.Curve.Center, -edge.Curve.Axis, edge.Curve.Radius)
# Rotate the circle appropriately so it starts at edge.valueAt(edge.LastParameter)
circle.rotate(
FreeCAD.Placement(
circle.Center,
circle.Axis,
180 - math.degrees(edge.LastParameter + edge.Curve.AngleXU),
)
)
# Now the edge always starts at 0 and LastParameter is the value range
arc = Part.Edge(circle, 0, edge.LastParameter - edge.FirstParameter)
return arc
elif type(edge.Curve) in [Part.BSplineCurve, Part.BezierCurve]:
if type(edge.Curve) == Part.BSplineCurve:
spline = edge.Curve
else:
spline = edge.Curve.toBSpline()
mults = spline.getMultiplicities()
weights = spline.getWeights()
knots = spline.getKnots()
poles = spline.getPoles()
perio = spline.isPeriodic()
ratio = spline.isRational()
degree = spline.Degree
ma = max(knots)
mi = min(knots)
knots = [ma + mi - k for k in knots]
mults.reverse()
weights.reverse()
poles.reverse()
knots.reverse()
flipped = Part.BSplineCurve()
flipped.buildFromPolesMultsKnots(
poles, mults, knots, perio, degree, weights, ratio
)
return Part.Edge(flipped)
elif type(edge.Curve) == Part.OffsetCurve:
return edge.reversed()
Path.Log.warning(
translate("PathGeom", "%s not supported for flipping") % type(edge.Curve)
)
def flipWire(wire):
"""Flip the entire wire and all its edges so it is being processed the other way around."""
edges = [flipEdge(e) for e in wire.Edges]
edges.reverse()
Path.Log.debug(edges)
return Part.Wire(edges)
def makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0):
"""makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0)...
Function to create boundbox face, with possible extra offset and custom Z-height."""
p1 = FreeCAD.Vector(bBox.XMin - offset, bBox.YMin - offset, zHeight)
p2 = FreeCAD.Vector(bBox.XMax + offset, bBox.YMin - offset, zHeight)
p3 = FreeCAD.Vector(bBox.XMax + offset, bBox.YMax + offset, zHeight)
p4 = FreeCAD.Vector(bBox.XMin - offset, bBox.YMax + offset, zHeight)
L1 = Part.makeLine(p1, p2)
L2 = Part.makeLine(p2, p3)
L3 = Part.makeLine(p3, p4)
L4 = Part.makeLine(p4, p1)
return Part.Face(Part.Wire([L1, L2, L3, L4]))
# Method to combine faces if connected
def combineHorizontalFaces(faces):
"""combineHorizontalFaces(faces)...
This function successfully identifies and combines multiple connected faces and
works on multiple independent faces with multiple connected faces within the list.
The return value is a list of simplified faces.
The Adaptive op is not concerned with which hole edges belong to which face.
Attempts to do the same shape connecting failed with TechDraw.findShapeOutline() and
PathGeom.combineConnectedShapes(), so this algorithm was created.
"""
horizontal = list()
offset = 10.0
topFace = None
innerFaces = list()
# Verify all incoming faces are at Z=0.0
for f in faces:
if f.BoundBox.ZMin != 0.0:
f.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - f.BoundBox.ZMin))
# Make offset compound boundbox solid and cut incoming face extrusions from it
allFaces = Part.makeCompound(faces)
if hasattr(allFaces, "Area") and isRoughly(allFaces.Area, 0.0):
msg = translate(
"PathGeom",
"Zero working area to process. Check your selection and settings.",
)
Path.Log.info(msg)
return horizontal
afbb = allFaces.BoundBox
bboxFace = makeBoundBoxFace(afbb, offset, -5.0)
bboxSolid = bboxFace.extrude(FreeCAD.Vector(0.0, 0.0, 10.0))
extrudedFaces = list()
for f in faces:
extrudedFaces.append(f.extrude(FreeCAD.Vector(0.0, 0.0, 6.0)))
# Fuse all extruded faces together
allFacesSolid = extrudedFaces.pop()
for i in range(len(extrudedFaces)):
temp = extrudedFaces.pop().fuse(allFacesSolid)
allFacesSolid = temp
cut = bboxSolid.cut(allFacesSolid)
# Debug
# Part.show(cut)
# FreeCAD.ActiveDocument.ActiveObject.Label = "cut"
# Identify top face and floating inner faces that are the holes in incoming faces
for f in cut.Faces:
fbb = f.BoundBox
if isRoughly(fbb.ZMin, 5.0) and isRoughly(fbb.ZMax, 5.0):
if (
isRoughly(afbb.XMin - offset, fbb.XMin)
and isRoughly(afbb.XMax + offset, fbb.XMax)
and isRoughly(afbb.YMin - offset, fbb.YMin)
and isRoughly(afbb.YMax + offset, fbb.YMax)
):
topFace = f
else:
innerFaces.append(f)
if not topFace:
return horizontal
outer = [Part.Face(w) for w in topFace.Wires[1:]]
if outer:
for f in outer:
f.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - f.BoundBox.ZMin))
if innerFaces:
# inner = [Part.Face(f.Wire1) for f in innerFaces]
inner = innerFaces
for f in inner:
f.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - f.BoundBox.ZMin))
innerComp = Part.makeCompound(inner)
outerComp = Part.makeCompound(outer)
cut = outerComp.cut(innerComp)
for f in cut.Faces:
horizontal.append(f)
else:
horizontal = outer
return horizontal

View File

@@ -23,8 +23,7 @@
import FreeCAD
import FreeCADGui
import Path
import PathScripts.PathGeom as PathGeom
import PathScripts.PathUtil as PathUtil
import Path.Base.Util as PathUtil
from PySide import QtGui, QtCore
from PySide import QtCore, QtGui
@@ -75,7 +74,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None):
attrValue = attr.Value if hasattr(attr, "Value") else attr
isDiff = False
if not PathGeom.isRoughly(attrValue, value):
if not Path.Geom.isRoughly(attrValue, value):
isDiff = True
else:
if hasattr(obj, "ExpressionEngine"):
@@ -85,7 +84,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None):
exprSet = True
Path.Log.debug('prop = "expression": {} = "{}"'.format(prp, expr))
value = FreeCAD.Units.Quantity(obj.evalExpression(expr)).Value
if not PathGeom.isRoughly(attrValue, value):
if not Path.Geom.isRoughly(attrValue, value):
isDiff = True
break
if exprSet:

View File

@@ -22,8 +22,8 @@
import FreeCAD
import Path
import Path.Base.Util as PathUtil
import PathGui
import PathScripts.PathUtil as PathUtil
import importlib
__title__ = "Path Icon ViewProvider"

View File

@@ -24,12 +24,12 @@ from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Path
import Path.Base.Util as PathUtil
from Path.Post.Processor import PostProcessor
import Path.Tool.Controller as PathToolController
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathSetupSheet as PathSetupSheet
import PathScripts.PathStock as PathStock
import PathScripts.PathUtil as PathUtil
import json
import time
import Path

View File

@@ -25,11 +25,11 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import FreeCADGui
import Path
import Path.Base.Util as PathUtil
import PathScripts.PathJob as PathJob
import PathScripts.PathJobDlg as PathJobDlg
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathStock as PathStock
import PathScripts.PathUtil as PathUtil
import json
import os

View File

@@ -25,10 +25,10 @@ from collections import Counter
import FreeCAD
import FreeCADGui
import Path
import Path.Base.Util as PathUtil
import PathScripts.PathJob as PathJob
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathStock as PathStock
import PathScripts.PathUtil as PathUtil
import glob
import os

View File

@@ -28,9 +28,9 @@ from pivy import coin
import FreeCAD
import FreeCADGui
import Path
import Path.Base.Util as PathUtil
import Path.Tool.Gui.Bit as PathToolBitGui
import Path.Tool.Gui.Controller as PathToolControllerGui
import PathScripts.PathGeom as PathGeom
import PathScripts.PathGuiInit as PathGuiInit
import PathScripts.PathJob as PathJob
import PathScripts.PathJobCmd as PathJobCmd
@@ -38,7 +38,6 @@ import PathScripts.PathJobDlg as PathJobDlg
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathSetupSheetGui as PathSetupSheetGui
import PathScripts.PathStock as PathStock
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
import json
import math
@@ -1087,7 +1086,7 @@ class TaskPanel:
if flip:
v = axis.negative()
if PathGeom.pointsCoincide(abs(v), abs(normal)):
if Path.Geom.pointsCoincide(abs(v), abs(normal)):
# Selection is already aligned with the axis of rotation leading
# to a (0,0,0) cross product for rotation.
# --> Need to flip the object around one of the "other" axis.
@@ -1128,9 +1127,9 @@ class TaskPanel:
% (normal.x, normal.y, normal.z, sub.Orientation)
)
if PathGeom.pointsCoincide(axis, normal):
if Path.Geom.pointsCoincide(axis, normal):
alignSel(sel, normal, True)
elif PathGeom.pointsCoincide(axis, FreeCAD.Vector() - normal):
elif Path.Geom.pointsCoincide(axis, FreeCAD.Vector() - normal):
alignSel(sel, FreeCAD.Vector() - normal, True)
else:
alignSel(sel, normal)
@@ -1139,9 +1138,9 @@ class TaskPanel:
normal = (
sub.Vertexes[1].Point - sub.Vertexes[0].Point
).normalize()
if PathGeom.pointsCoincide(
if Path.Geom.pointsCoincide(
axis, normal
) or PathGeom.pointsCoincide(axis, FreeCAD.Vector() - normal):
) or Path.Geom.pointsCoincide(axis, FreeCAD.Vector() - normal):
# Don't really know the orientation of an edge, so let's just flip the object
# and if the user doesn't like it they can flip again
alignSel(sel, normal, True)

View File

@@ -1,216 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2020 sliptonic <shopinthewoods@gmail.com> *
# * *
# * 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 Path
__title__ = "Property type abstraction for editing purposes"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Prototype objects to allow extraction of setup sheet values and editing."
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
# Path.Log.trackModule(Path.Log.thisModule())
class Property(object):
"""Base class for all prototype properties"""
def __init__(self, name, propType, category, info):
self.name = name
self.propType = propType
self.category = category
self.info = info
self.editorMode = 0
self.value = None
def setValue(self, value):
self.value = value
def getValue(self):
return self.value
def setEditorMode(self, mode):
self.editorMode = mode
def displayString(self):
if self.value is None:
t = self.typeString()
p = "an" if t[0] in ["A", "E", "I", "O", "U"] else "a"
return "%s %s" % (p, t)
return self.value
def typeString(self):
return "Property"
def setupProperty(self, obj, name, category, value):
created = False
if not hasattr(obj, name):
obj.addProperty(self.propType, name, category, self.info)
self.initProperty(obj, name)
created = True
setattr(obj, name, value)
return created
def initProperty(self, obj, name):
pass
def setValueFromString(self, string):
self.setValue(self.valueFromString(string))
def valueFromString(self, string):
return string
class PropertyEnumeration(Property):
def typeString(self):
return "Enumeration"
def setValue(self, value):
if list == type(value):
self.enums = value
else:
super(PropertyEnumeration, self).setValue(value)
def getEnumValues(self):
return self.enums
def initProperty(self, obj, name):
setattr(obj, name, self.enums)
class PropertyQuantity(Property):
def displayString(self):
if self.value is None:
return Property.displayString(self)
return self.value.getUserPreferred()[0]
class PropertyAngle(PropertyQuantity):
def typeString(self):
return "Angle"
class PropertyDistance(PropertyQuantity):
def typeString(self):
return "Distance"
class PropertyLength(PropertyQuantity):
def typeString(self):
return "Length"
class PropertyPercent(Property):
def typeString(self):
return "Percent"
class PropertyFloat(Property):
def typeString(self):
return "Float"
def valueFromString(self, string):
return float(string)
class PropertyInteger(Property):
def typeString(self):
return "Integer"
def valueFromString(self, string):
return int(string)
class PropertyBool(Property):
def typeString(self):
return "Bool"
def valueFromString(self, string):
return bool(string)
class PropertyString(Property):
def typeString(self):
return "String"
class PropertyMap(Property):
def typeString(self):
return "Map"
def displayString(self, value):
return str(value)
class OpPrototype(object):
PropertyType = {
"App::PropertyAngle": PropertyAngle,
"App::PropertyBool": PropertyBool,
"App::PropertyDistance": PropertyDistance,
"App::PropertyEnumeration": PropertyEnumeration,
"App::PropertyFile": PropertyString,
"App::PropertyFloat": PropertyFloat,
"App::PropertyFloatConstraint": Property,
"App::PropertyFloatList": Property,
"App::PropertyInteger": PropertyInteger,
"App::PropertyIntegerList": PropertyInteger,
"App::PropertyLength": PropertyLength,
"App::PropertyLink": Property,
"App::PropertyLinkList": Property,
"App::PropertyLinkSubListGlobal": Property,
"App::PropertyMap": PropertyMap,
"App::PropertyPercent": PropertyPercent,
"App::PropertyString": PropertyString,
"App::PropertyStringList": Property,
"App::PropertyVectorDistance": Property,
"App::PropertyVectorList": Property,
"Part::PropertyPartShape": Property,
}
def __init__(self, name):
self.Label = name
self.properties = {}
self.DoNotSetDefaultValues = True
self.Proxy = None
def __setattr__(self, name, val):
if name in ["Label", "DoNotSetDefaultValues", "properties", "Proxy"]:
if name == "Proxy":
val = None # make sure the proxy is never set
return super(OpPrototype, self).__setattr__(name, val)
self.properties[name].setValue(val)
def addProperty(self, typeString, name, category, info=None):
prop = self.PropertyType[typeString](name, typeString, category, info)
self.properties[name] = prop
return self
def setEditorMode(self, name, mode):
self.properties[name].setEditorMode(mode)
def getProperty(self, name):
return self.properties[name]
def setupProperties(self, setup):
return [p for p in self.properties if p.name in setup]

View File

@@ -1,150 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2020 sliptonic <shopinthewoods@gmail.com> *
# * *
# * 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 *
# * *
# ***************************************************************************
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Path
import re
__title__ = "Generic property container to store some values."
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "A generic container for typed properties in arbitrary categories."
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
translate = FreeCAD.Qt.translate
SupportedPropertyType = {
"Angle": "App::PropertyAngle",
"Bool": "App::PropertyBool",
"Distance": "App::PropertyDistance",
"Enumeration": "App::PropertyEnumeration",
"File": "App::PropertyFile",
"Float": "App::PropertyFloat",
"Integer": "App::PropertyInteger",
"Length": "App::PropertyLength",
"Percent": "App::PropertyPercent",
"String": "App::PropertyString",
}
def getPropertyTypeName(o):
for typ in SupportedPropertyType:
if SupportedPropertyType[typ] == o:
return typ
raise IndexError()
class PropertyBag(object):
"""Property container object."""
CustomPropertyGroups = "CustomPropertyGroups"
CustomPropertyGroupDefault = "User"
def __init__(self, obj):
obj.addProperty(
"App::PropertyStringList",
self.CustomPropertyGroups,
"Base",
QT_TRANSLATE_NOOP("App::Property", "List of custom property groups"),
)
self.onDocumentRestored(obj)
def __getstate__(self):
return None
def __setstate__(self, state):
return None
def __sanitizePropertyName(self, name):
if len(name) == 0:
return
clean = name[0]
for i in range(1, len(name)):
if name[i] == " ":
clean += name[i + 1].upper()
i += 1
elif name[i - 1] != " ":
clean += name[i]
return clean
def onDocumentRestored(self, obj):
self.obj = obj
obj.setEditorMode(self.CustomPropertyGroups, 2) # hide
def getCustomProperties(self):
"""getCustomProperties() ... Return a list of all custom properties created in this container."""
return [
p
for p in self.obj.PropertiesList
if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups
]
def addCustomProperty(self, propertyType, name, group=None, desc=None):
"""addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group."""
if desc is None:
desc = ""
if group is None:
group = self.CustomPropertyGroupDefault
groups = self.obj.CustomPropertyGroups
name = self.__sanitizePropertyName(name)
if not re.match("^[A-Za-z0-9_]*$", name):
raise ValueError("Property Name can only contain letters and numbers")
if not group in groups:
groups.append(group)
self.obj.CustomPropertyGroups = groups
self.obj.addProperty(propertyType, name, group, desc)
return name
def refreshCustomPropertyGroups(self):
"""refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties."""
customGroups = []
for p in self.obj.PropertiesList:
group = self.obj.getGroupOfProperty(p)
if group in self.obj.CustomPropertyGroups and not group in customGroups:
customGroups.append(group)
self.obj.CustomPropertyGroups = customGroups
def Create(name="PropertyBag"):
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", name)
obj.Proxy = PropertyBag(obj)
return obj
def IsPropertyBag(obj):
"""Returns True if the supplied object is a property container (or its Proxy)."""
if type(obj) == PropertyBag:
return True
if hasattr(obj, "Proxy"):
return IsPropertyBag(obj.Proxy)
return False

View File

@@ -24,10 +24,10 @@ from PySide import QtCore, QtGui
import FreeCAD
import FreeCADGui
import Path
import Path.Base.PropertyBag as PathPropertyBag
import Path.Base.Util as PathUtil
import PathScripts.PathIconViewProvider as PathIconViewProvider
import PathScripts.PathPropertyBag as PathPropertyBag
import PathScripts.PathPropertyEditor as PathPropertyEditor
import PathScripts.PathUtil as PathUtil
import re

View File

@@ -33,8 +33,8 @@ from PySide import QtCore, QtGui
import FreeCAD
import FreeCADGui
import Path
import Path.Base.Util as PathUtil
import PathScripts
import PathScripts.PathUtil as PathUtil
import PathScripts.PathPreferences as PathPreferences
from collections import Counter
from datetime import datetime

View File

@@ -22,9 +22,8 @@
import FreeCAD
import Path
import PathScripts.PathGeom as PathGeom
import Path.Base.Util as PathUtil
import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype
import PathScripts.PathUtil as PathUtil
from PySide.QtCore import QT_TRANSLATE_NOOP
__title__ = "Setup Sheet for a Job."
@@ -234,7 +233,7 @@ class SetupSheet:
return None
def hasDefaultToolRapids(self):
return PathGeom.isRoughly(self.obj.VertRapid.Value, 0) and PathGeom.isRoughly(
return Path.Geom.isRoughly(self.obj.VertRapid.Value, 0) and Path.Geom.isRoughly(
self.obj.HorizRapid.Value, 0
)

View File

@@ -23,12 +23,12 @@
import FreeCAD
import FreeCADGui
import Path
import Path.Base.Util as PathUtil
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathScripts.PathGui as PathGui
import PathScripts.PathIconViewProvider as PathIconViewProvider
import PathScripts.PathSetupSheet as PathSetupSheet
import PathScripts.PathSetupSheetOpPrototypeGui as PathSetupSheetOpPrototypeGui
import PathScripts.PathUtil as PathUtil
from PySide import QtCore, QtGui

View File

@@ -22,10 +22,9 @@
import FreeCAD
import Path
import Path.Base.Util as PathUtil
import Path.Dressup.Utils as PathDressup
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathScripts.PathGeom as PathGeom
import PathScripts.PathUtil as PathUtil
import PathScripts.PathJob as PathJob
import PathSimulator
import math
@@ -399,14 +398,14 @@ class PathSimulation:
self.PerformCutBoolean()
def RapidMove(self, cmd, curpos):
path = PathGeom.edgeForCmd(cmd, curpos) # hack to overcome occ bug
path = Path.Geom.edgeForCmd(cmd, curpos) # hack to overcome occ bug
if path is None:
return curpos
return path.valueAt(path.LastParameter)
# get a solid representation of a tool going along path
def GetPathSolid(self, tool, cmd, pos):
toolPath = PathGeom.edgeForCmd(cmd, pos)
toolPath = Path.Geom.edgeForCmd(cmd, pos)
startDir = toolPath.tangentAt(0)
startDir[2] = 0.0
endPos = toolPath.valueAt(toolPath.LastParameter)

View File

@@ -1,175 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * 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 purpose of this file is to collect some handy functions. The reason they
are not in PathUtils (and there is this confusing naming going on) is that
PathUtils depends on PathJob. Which makes it impossible to use the functions
and classes defined there in PathJob.
So if you add to this file and think about importing anything from PathScripts
other than Path.Log, then it probably doesn't belong here.
"""
import FreeCAD
import six
import Path
translate = FreeCAD.Qt.translate
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
def _getProperty(obj, prop):
o = obj
attr = obj
name = None
for name in prop.split("."):
o = attr
if not hasattr(o, name):
break
attr = getattr(o, name)
if o == attr:
Path.Log.warning(
translate("PathGui", "%s has no property %s (%s))")
% (obj.Label, prop, name)
)
return (None, None, None)
# Path.Log.debug("found property %s of %s (%s: %s)" % (prop, obj.Label, name, attr))
return (o, attr, name)
def getProperty(obj, prop):
"""getProperty(obj, prop) ... answer obj's property defined by its canonical name."""
o, attr, name = _getProperty(obj, prop)
return attr
def getPropertyValueString(obj, prop):
"""getPropertyValueString(obj, prop) ... answer a string representation of an object's property's value."""
attr = getProperty(obj, prop)
if hasattr(attr, "UserString"):
return attr.UserString
return str(attr)
def setProperty(obj, prop, value):
"""setProperty(obj, prop, value) ... set the property value of obj's property defined by its canonical name."""
o, attr, name = _getProperty(obj, prop)
if not attr is None and type(value) == str:
if type(attr) == int:
value = int(value, 0)
elif type(attr) == bool:
value = value.lower() in ["true", "1", "yes", "ok"]
if o and name:
setattr(o, name, value)
# NotValidBaseTypeIds = ['Sketcher::SketchObject']
NotValidBaseTypeIds = []
def isValidBaseObject(obj):
"""isValidBaseObject(obj) ... returns true if the object can be used as a base for a job."""
if hasattr(obj, "getParentGeoFeatureGroup") and obj.getParentGeoFeatureGroup():
# Can't link to anything inside a geo feature group anymore
Path.Log.debug("%s is inside a geo feature group" % obj.Label)
return False
if hasattr(obj, "BitBody") and hasattr(obj, "BitShape"):
# ToolBit's are not valid base objects
return False
if obj.TypeId in NotValidBaseTypeIds:
Path.Log.debug("%s is blacklisted (%s)" % (obj.Label, obj.TypeId))
return False
if hasattr(obj, "Sheets") or hasattr(
obj, "TagText"
): # Arch.Panels and Arch.PanelCut
Path.Log.debug("%s is not an Arch.Panel" % (obj.Label))
return False
import Part
return not Part.getShape(obj).isNull()
def isSolid(obj):
"""isSolid(obj) ... return True if the object is a valid solid."""
import Part
shape = Part.getShape(obj)
return not shape.isNull() and shape.Volume and shape.isClosed()
def opProperty(op, prop):
"""opProperty(op, prop) ... return the value of property prop of the underlying operation (or None if prop does not exist)"""
if hasattr(op, prop):
return getattr(op, prop)
if hasattr(op, "Base"):
return opProperty(op.Base, prop)
return None
def toolControllerForOp(op):
"""toolControllerForOp(op) ... return the tool controller used by the op.
If the op doesn't have its own tool controller but has a Base object, return its tool controller.
Otherwise return None."""
return opProperty(op, "ToolController")
def getPublicObject(obj):
"""getPublicObject(obj) ... returns the object which should be used to reference a feature of the given object."""
if hasattr(obj, "getParentGeoFeatureGroup"):
body = obj.getParentGeoFeatureGroup()
if body:
return getPublicObject(body)
return obj
def clearExpressionEngine(obj):
"""clearExpressionEngine(obj) ... removes all expressions from obj.
There is currently a bug that invalidates the DAG if an object
is deleted that still has one or more expressions attached to it.
Use this function to remove all expressions before deletion."""
if hasattr(obj, "ExpressionEngine"):
for attr, expr in obj.ExpressionEngine:
obj.setExpression(attr, None)
def toUnicode(string):
"""toUnicode(string) ... returns a unicode version of string regardless of the python version."""
return six.text_type(string)
def isString(string):
"""isString(string) ... return True if string is a string, regardless of string type and python version."""
return isinstance(string, six.string_types)
def keyValueIter(dictionary):
"""keyValueIter(dict) ... return iterable object over dictionary's (key,value) tuples."""
return six.iteritems(dictionary)

View File

@@ -25,7 +25,6 @@ import FreeCAD
from FreeCAD import Vector
from PySide import QtCore
import Path
import PathScripts.PathGeom as PathGeom
import PathScripts.PathJob as PathJob
import math
from numpy import linspace
@@ -122,8 +121,8 @@ def horizontalEdgeLoop(obj, edge):
loops = [
w
for w in wires
if all(PathGeom.isHorizontal(e) for e in w.Edges)
and PathGeom.isHorizontal(Part.Face(w))
if all(Path.Geom.isHorizontal(e) for e in w.Edges)
and Path.Geom.isHorizontal(Part.Face(w))
]
if len(loops) == 1:
return loops[0]
@@ -146,7 +145,7 @@ def horizontalFaceLoop(obj, face, faceList=None):
faces = [
"Face%d" % (i + 1)
for i, f in enumerate(obj.Shape.Faces)
if any(e.hashCode() in hashes for e in f.Edges) and PathGeom.isVertical(f)
if any(e.hashCode() in hashes for e in f.Edges) and Path.Geom.isVertical(f)
]
if faceList and not all(f in faces for f in faceList):
@@ -161,7 +160,7 @@ def horizontalFaceLoop(obj, face, faceList=None):
# trace-backs single edge spikes don't contribute to the bound box
uniqueEdges = []
for edge in outline.Edges:
if any(PathGeom.edgesMatch(edge, e) for e in uniqueEdges):
if any(Path.Geom.edgesMatch(edge, e) for e in uniqueEdges):
continue
uniqueEdges.append(edge)
w = Part.Wire(uniqueEdges)
@@ -172,10 +171,10 @@ def horizontalFaceLoop(obj, face, faceList=None):
bb2 = w.BoundBox
if (
w.isClosed()
and PathGeom.isRoughly(bb1.XMin, bb2.XMin)
and PathGeom.isRoughly(bb1.XMax, bb2.XMax)
and PathGeom.isRoughly(bb1.YMin, bb2.YMin)
and PathGeom.isRoughly(bb1.YMax, bb2.YMax)
and Path.Geom.isRoughly(bb1.XMin, bb2.XMin)
and Path.Geom.isRoughly(bb1.XMax, bb2.XMax)
and Path.Geom.isRoughly(bb1.YMin, bb2.YMin)
and Path.Geom.isRoughly(bb1.YMax, bb2.YMax)
):
return faces
return None
@@ -721,7 +720,7 @@ class depth_params(object):
def __filter_roughly_equal_depths(self, depths):
"""Depths arrive sorted from largest to smallest, positive to negative.
Return unique list of depths, using PathGeom.isRoughly() method to determine
Return unique list of depths, using Path.Geom.isRoughly() method to determine
if the two values are equal. Only one of two consecutive equals are removed.
The assumption is that there are not enough consecutively roughly-equal depths
@@ -731,7 +730,7 @@ class depth_params(object):
depthcopy = sorted(depths) # make a copy and sort low to high
keep = [depthcopy[0]]
for depth in depthcopy[1:]:
if not PathGeom.isRoughly(depth, keep[-1]):
if not Path.Geom.isRoughly(depth, keep[-1]):
keep.append(depth)
keep.reverse() # reverse results back high to low
return keep