Started moving base classes into Path.Base module
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user