Merge pull request #417 from dev-at-stellardeath-org/path_helix
Path: helix machining for circular holes
This commit is contained in:
commit
899c5c337e
|
@ -53,6 +53,7 @@ SET(PathScripts_SRCS
|
|||
PathScripts/PathSimpleCopy.py
|
||||
PathScripts/PathStock.py
|
||||
PathScripts/PathStop.py
|
||||
PathScripts/PathHelix.py
|
||||
PathScripts/PathSurface.py
|
||||
PathScripts/PathToolLenOffset.py
|
||||
PathScripts/PathToolLibraryManager.py
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<file>icons/Path-FaceProfile.svg</file>
|
||||
<file>icons/Path-Face.svg</file>
|
||||
<file>icons/Path-Heights.svg</file>
|
||||
<file>icons/Path-Helix.svg</file>
|
||||
<file>icons/Path-Hop.svg</file>
|
||||
<file>icons/Path-Inspect.svg</file>
|
||||
<file>icons/Path-Job.svg</file>
|
||||
|
|
548
src/Mod/Path/Gui/Resources/icons/Path-Helix.svg
Normal file
548
src/Mod/Path/Gui/Resources/icons/Path-Helix.svg
Normal file
|
@ -0,0 +1,548 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64px"
|
||||
height="64px"
|
||||
id="svg2816"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="Path-Helix.svg">
|
||||
<defs
|
||||
id="defs2818">
|
||||
<linearGradient
|
||||
id="linearGradient4513">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4515" />
|
||||
<stop
|
||||
style="stop-color:#999999;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop4517" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3681">
|
||||
<stop
|
||||
id="stop3697"
|
||||
offset="0"
|
||||
style="stop-color:#fff110;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#cf7008;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3685" />
|
||||
</linearGradient>
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 32 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="64 : 32 : 1"
|
||||
inkscape:persp3d-origin="32 : 21.333333 : 1"
|
||||
id="perspective2824" />
|
||||
<inkscape:perspective
|
||||
id="perspective3622"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3622-9"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3653"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3675"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3697"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3720"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3742"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3764"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3785"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3806"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3806-3"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3835"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3614"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3614-8"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3643"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3643-3"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3672"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3672-5"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3701"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3701-8"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective3746"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<pattern
|
||||
patternTransform="matrix(0.67643728,-0.81829155,2.4578314,1.8844554,-26.450606,18.294947)"
|
||||
id="pattern5231"
|
||||
xlink:href="#Strips1_1-4"
|
||||
inkscape:collect="always" />
|
||||
<inkscape:perspective
|
||||
id="perspective5224"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<pattern
|
||||
inkscape:stockid="Stripes 1:1"
|
||||
id="Strips1_1-4"
|
||||
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,3.4760987,3.534923)"
|
||||
height="1"
|
||||
width="2"
|
||||
patternUnits="userSpaceOnUse"
|
||||
inkscape:collect="always">
|
||||
<rect
|
||||
id="rect4483-4"
|
||||
height="2"
|
||||
width="1"
|
||||
y="-0.5"
|
||||
x="0"
|
||||
style="fill:black;stroke:none" />
|
||||
</pattern>
|
||||
<inkscape:perspective
|
||||
id="perspective5224-9"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<pattern
|
||||
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,39.618381,8.9692804)"
|
||||
id="pattern5231-4"
|
||||
xlink:href="#Strips1_1-6"
|
||||
inkscape:collect="always" />
|
||||
<inkscape:perspective
|
||||
id="perspective5224-3"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<pattern
|
||||
inkscape:stockid="Stripes 1:1"
|
||||
id="Strips1_1-6"
|
||||
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,3.4760987,3.534923)"
|
||||
height="1"
|
||||
width="2"
|
||||
patternUnits="userSpaceOnUse"
|
||||
inkscape:collect="always">
|
||||
<rect
|
||||
id="rect4483-0"
|
||||
height="2"
|
||||
width="1"
|
||||
y="-0.5"
|
||||
x="0"
|
||||
style="fill:black;stroke:none" />
|
||||
</pattern>
|
||||
<pattern
|
||||
patternTransform="matrix(0.66513382,-1.0631299,2.4167603,2.4482973,-49.762569,2.9546807)"
|
||||
id="pattern5296"
|
||||
xlink:href="#pattern5231-3"
|
||||
inkscape:collect="always" />
|
||||
<inkscape:perspective
|
||||
id="perspective5288"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<pattern
|
||||
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,-26.336284,10.887197)"
|
||||
id="pattern5231-3"
|
||||
xlink:href="#Strips1_1-4-3"
|
||||
inkscape:collect="always" />
|
||||
<pattern
|
||||
inkscape:stockid="Stripes 1:1"
|
||||
id="Strips1_1-4-3"
|
||||
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,3.4760987,3.534923)"
|
||||
height="1"
|
||||
width="2"
|
||||
patternUnits="userSpaceOnUse"
|
||||
inkscape:collect="always">
|
||||
<rect
|
||||
id="rect4483-4-6"
|
||||
height="2"
|
||||
width="1"
|
||||
y="-0.5"
|
||||
x="0"
|
||||
style="fill:black;stroke:none" />
|
||||
</pattern>
|
||||
<pattern
|
||||
patternTransform="matrix(0.42844886,-0.62155849,1.5567667,1.431396,27.948414,13.306456)"
|
||||
id="pattern5330"
|
||||
xlink:href="#Strips1_1-9"
|
||||
inkscape:collect="always" />
|
||||
<inkscape:perspective
|
||||
id="perspective5323"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<pattern
|
||||
inkscape:stockid="Stripes 1:1"
|
||||
id="Strips1_1-9"
|
||||
patternTransform="matrix(0.66772843,-1.0037085,2.4261878,2.3114548,3.4760987,3.534923)"
|
||||
height="1"
|
||||
width="2"
|
||||
patternUnits="userSpaceOnUse"
|
||||
inkscape:collect="always">
|
||||
<rect
|
||||
id="rect4483-3"
|
||||
height="2"
|
||||
width="1"
|
||||
y="-0.5"
|
||||
x="0"
|
||||
style="fill:black;stroke:none" />
|
||||
</pattern>
|
||||
<inkscape:perspective
|
||||
id="perspective5361"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective5383"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<inkscape:perspective
|
||||
id="perspective5411"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3681"
|
||||
id="linearGradient3687"
|
||||
x1="37.89756"
|
||||
y1="41.087898"
|
||||
x2="4.0605712"
|
||||
y2="40.168594"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(127.27273,-51.272729)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3681"
|
||||
id="linearGradient3695"
|
||||
x1="37.894287"
|
||||
y1="40.484772"
|
||||
x2="59.811455"
|
||||
y2="43.558987"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(127.27273,-51.272729)" />
|
||||
<linearGradient
|
||||
id="linearGradient3681-3">
|
||||
<stop
|
||||
id="stop3697-3"
|
||||
offset="0"
|
||||
style="stop-color:#fff110;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#cf7008;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3685-4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="43.558987"
|
||||
x2="59.811455"
|
||||
y1="40.484772"
|
||||
x1="37.894287"
|
||||
gradientTransform="translate(-37.00068,-20.487365)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3608"
|
||||
xlink:href="#linearGradient3681-3"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient4513-2">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4515-2" />
|
||||
<stop
|
||||
style="stop-color:#999999;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop4517-4" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="23.634638"
|
||||
fy="7.9319997"
|
||||
fx="32.151962"
|
||||
cy="7.9319997"
|
||||
cx="32.151962"
|
||||
gradientTransform="matrix(1,0,0,1.1841158,-8.5173246,-3.4097568)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient4538"
|
||||
xlink:href="#linearGradient4513-2"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient4513-1">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4515-8" />
|
||||
<stop
|
||||
style="stop-color:#999999;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop4517-6" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="23.634638"
|
||||
fy="7.9319997"
|
||||
fx="32.151962"
|
||||
cy="7.9319997"
|
||||
cx="32.151962"
|
||||
gradientTransform="matrix(1,0,0,1.1841158,-8.5173246,-3.4097568)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient4538-6"
|
||||
xlink:href="#linearGradient4513-1"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient4513-1-3">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4515-8-7" />
|
||||
<stop
|
||||
style="stop-color:#999999;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop4517-6-5" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="23.634638"
|
||||
fy="35.869175"
|
||||
fx="32.151962"
|
||||
cy="35.869175"
|
||||
cx="32.151962"
|
||||
gradientTransform="matrix(0.39497909,0,0,1.1841158,-2.716491,-26.067007)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient3069"
|
||||
xlink:href="#linearGradient4513-1-3"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient4513-1-2">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4515-8-6" />
|
||||
<stop
|
||||
style="stop-color:#999999;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop4517-6-6" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="23.634638"
|
||||
fy="35.869175"
|
||||
fx="32.151962"
|
||||
cy="35.869175"
|
||||
cx="32.151962"
|
||||
gradientTransform="matrix(0.39497909,0,0,1.1841158,-2.716491,-26.067007)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient3102"
|
||||
xlink:href="#linearGradient4513-1-2"
|
||||
inkscape:collect="always" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4513-1"
|
||||
id="radialGradient3132"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.39497909,0,0,1.1841158,29.624484,3.2399607)"
|
||||
cx="32.151962"
|
||||
cy="27.950663"
|
||||
fx="32.151962"
|
||||
fy="27.950663"
|
||||
r="23.634638" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="7.7781744"
|
||||
inkscape:cx="-38.302485"
|
||||
inkscape:cy="32.387915"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1335"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4207"
|
||||
dotted="true"
|
||||
spacingx="0.5"
|
||||
spacingy="0.5" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata2821">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#008b00;stroke-width:9;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 42,57 32,57 C 2,57 2,54.5 32,44.5 62,34.5 62,32 32,32 2,32 2,29.5 32,19.5 62,9.4999998 62,6.9999998 32,6.9999998 l -10,0"
|
||||
id="path4379"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
inkscape:transform-center-x="0.38569462"
|
||||
inkscape:transform-center-y="-1.2856487" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#radialGradient3132);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.97430003;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m 33.328125,14.73446 0,17.46591 c 0,0 0,1.310757 0,8.59375 l 17.34375,-8.59375 0,-1.90625 0,-15.55966 z m 17.34375,22.55966 -17.34375,8.5625 0,7.03125 17.34375,-8.59375 z m 0,12.09375 -14.5625,7.21875 5.9375,3.90625 8.625,-5.71875 z"
|
||||
id="rect4417"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 18 KiB |
|
@ -65,6 +65,7 @@ class PathWorkbench (Workbench):
|
|||
from PathScripts import PathCustom
|
||||
from PathScripts import PathInspect
|
||||
from PathScripts import PathSimpleCopy
|
||||
from PathScripts import PathHelix
|
||||
from PathScripts import PathEngrave
|
||||
from PathScripts import PathSurface
|
||||
from PathScripts import PathSanity
|
||||
|
@ -80,7 +81,7 @@ class PathWorkbench (Workbench):
|
|||
projcmdlist = ["Path_Job", "Path_Post", "Path_Inspect", "Path_Sanity"]
|
||||
toolcmdlist = ["Path_ToolLibraryEdit", "Path_LoadTool"]
|
||||
prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_FromShape"]
|
||||
twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace"]
|
||||
twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace", "Path_Helix"]
|
||||
threedopcmdlist = ["Path_Surfacing"]
|
||||
modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ]
|
||||
dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags"]
|
||||
|
|
818
src/Mod/Path/PathScripts/PathHelix.py
Normal file
818
src/Mod/Path/PathScripts/PathHelix.py
Normal file
|
@ -0,0 +1,818 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#***************************************************************************
|
||||
#* *
|
||||
#* Copyright (c) 2016 Lorenz Hüdepohl <dev@stellardeath.org> *
|
||||
#* *
|
||||
#* 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, Path
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore, QtGui
|
||||
from DraftTools import translate
|
||||
|
||||
from . import PathUtils
|
||||
from .PathUtils import fmt
|
||||
|
||||
"""Helix Drill object and FreeCAD command"""
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
try:
|
||||
_encoding = QtGui.QApplication.UnicodeUTF8
|
||||
def translate(context, text, disambig=None):
|
||||
return QtGui.QApplication.translate(context, text, disambig, _encoding)
|
||||
except AttributeError:
|
||||
def translate(context, text, disambig=None):
|
||||
return QtGui.QApplication.translate(context, text, disambig)
|
||||
else:
|
||||
def translate(context, text, disambig=None):
|
||||
return text
|
||||
|
||||
def z_cylinder(cyl):
|
||||
""" Test if cylinder is aligned to z-Axis"""
|
||||
if cyl.Surface.Axis.x != 0.0:
|
||||
return False
|
||||
if cyl.Surface.Axis.y != 0.0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def connected(edge, face):
|
||||
for otheredge in face.Edges:
|
||||
if edge.isSame(otheredge):
|
||||
return True
|
||||
return False
|
||||
|
||||
def cylinders_in_selection():
|
||||
from Part import Cylinder
|
||||
selections = FreeCADGui.Selection.getSelectionEx()
|
||||
|
||||
cylinders = []
|
||||
|
||||
for selection in selections:
|
||||
base = selection.Object
|
||||
cylinders.append((base, []))
|
||||
for feature in selection.SubElementNames:
|
||||
subobj = getattr(base.Shape, feature)
|
||||
if subobj.ShapeType =='Face':
|
||||
if isinstance(subobj.Surface, Cylinder):
|
||||
if z_cylinder(subobj):
|
||||
cylinders[-1][1].append(feature)
|
||||
|
||||
return cylinders
|
||||
|
||||
|
||||
def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vfeed, hfeed, direction, startside):
|
||||
"""
|
||||
center: 2-tuple
|
||||
(x0,y0) coordinates of center
|
||||
r_out, r_in: floats
|
||||
radial range, cut from outer radius r_out in layers of dr to inner radius r_in
|
||||
zmax, zmin: floats
|
||||
z-range, cut from zmax in layers of dz down to zmin
|
||||
safe_z: float
|
||||
safety layer height
|
||||
tool_diameter: float
|
||||
Width of tool
|
||||
"""
|
||||
from numpy import ceil, allclose, linspace
|
||||
|
||||
if (zmax <= zmin):
|
||||
return
|
||||
|
||||
out = "(helix_cut <{0}, {1}>, {2})".format(center[0], center[1],
|
||||
", ".join(map(str, (r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vfeed, hfeed, direction, startside))))
|
||||
|
||||
x0, y0 = center
|
||||
nz = max(int(ceil((zmax - zmin)/dz)), 2)
|
||||
zi = linspace(zmax, zmin, 2 * nz + 1)
|
||||
|
||||
if dr > tool_diameter:
|
||||
FreeCAD.Console.PrintWarning("PathHelix: Warning, shortening dr to tool diameter!\n")
|
||||
dr = tool_diameter
|
||||
|
||||
def xyz(x=None, y=None, z=None):
|
||||
out = ""
|
||||
if x is not None:
|
||||
out += " X" + fmt(x)
|
||||
if y is not None:
|
||||
out += " Y" + fmt(y)
|
||||
if z is not None:
|
||||
out += " Z" + fmt(z)
|
||||
return out
|
||||
|
||||
def rapid(x=None, y=None, z=None):
|
||||
return "G0" + xyz(x,y,z) + "\n"
|
||||
|
||||
def F(f=None):
|
||||
return (" F" + fmt(f) if f else "")
|
||||
|
||||
def feed(x=None, y=None, z=None, f=None):
|
||||
return "G1" + xyz(x,y,z) + F(f) + "\n"
|
||||
|
||||
def arc(x,y,i,j,z,f):
|
||||
if direction == "CW":
|
||||
code = "G2"
|
||||
elif direction == "CCW":
|
||||
code = "G3"
|
||||
return code + " I" + fmt(i) + " J" + fmt(j) + " X" + fmt(x) + " Y" + fmt(y) + " Z" + fmt(z) + F(f) + "\n"
|
||||
|
||||
def helix_cut_r(r):
|
||||
out = ""
|
||||
out += rapid(x=x0+r,y=y0)
|
||||
out += rapid(z=zmax + tool_diameter)
|
||||
out += feed(z=zmax,f=vfeed)
|
||||
z=zmin
|
||||
for i in range(1,nz+1):
|
||||
out += arc(x0-r, y0, i=-r, j=0.0, z = zi[2*i-1], f=hfeed)
|
||||
out += arc(x0+r, y0, i= r, j=0.0, z = zi[2*i], f=hfeed)
|
||||
out += arc(x0-r, y0, i=-r, j=0.0, z = zmin, f=hfeed)
|
||||
out += arc(x0+r, y0, i=r, j=0.0, z = zmin, f=hfeed)
|
||||
out += feed(z=zmax + tool_diameter, f=vfeed)
|
||||
out += rapid(z=safe_z)
|
||||
return out
|
||||
|
||||
assert(r_out > 0.0)
|
||||
assert(r_in >= 0.0)
|
||||
|
||||
msg = None
|
||||
if r_out < 0.0:
|
||||
msg = "r_out < 0"
|
||||
elif r_in > 0 and r_out - r_in < tool_diameter:
|
||||
msg = "r_out - r_in = {0} is < tool diameter of {1}".format(r_out - r_in, tool_diamater)
|
||||
elif r_in == 0.0 and not r_out > tool_diameter/2.:
|
||||
msg = "Cannot drill a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, tool_diameter)
|
||||
elif not startside in ["inside", "outside"]:
|
||||
msg = "Invalid value for parameter 'startside'"
|
||||
|
||||
if msg:
|
||||
out += "(ERROR: Hole at {0}:".format((x0, y0, zmax)) + msg + ")\n"
|
||||
FreeCAD.Console.PrintError("PathHelix: Hole at {0}:".format((x0, y0, zmax)) + msg + "\n")
|
||||
return out
|
||||
|
||||
if r_in > 0:
|
||||
out += "(annulus mode)\n"
|
||||
r_out = r_out - tool_diameter/2
|
||||
r_in = r_in + tool_diameter/2
|
||||
if abs((r_out - r_in) / dr) < 1e-5:
|
||||
radii = [(r_out + r_in)/2]
|
||||
else:
|
||||
nr = max(int(ceil((r_out - r_in)/dr)), 2)
|
||||
radii = linspace(r_out, r_in, nr)
|
||||
elif r_out <= 2 * dr:
|
||||
out += "(single helix mode)\n"
|
||||
radii = [r_out - tool_diameter/2]
|
||||
assert(radii[0] > 0)
|
||||
else:
|
||||
out += "(full hole mode)\n"
|
||||
r_out = r_out - tool_diameter/2
|
||||
r_in = dr/2
|
||||
|
||||
nr = max(1 + int(ceil((r_out - r_in)/dr)), 2)
|
||||
radii = linspace(r_out, r_in, nr)
|
||||
assert(all(radii > 0))
|
||||
|
||||
if startside == "inside":
|
||||
radii = radii[::-1]
|
||||
|
||||
for r in radii:
|
||||
out += "(radius {0})\n".format(r)
|
||||
out += helix_cut_r(r)
|
||||
|
||||
return out
|
||||
|
||||
def features_by_centers(base, features):
|
||||
import scipy.spatial
|
||||
features = sorted(features,
|
||||
key = lambda feature : getattr(base.Shape, feature).Surface.Radius,
|
||||
reverse = True)
|
||||
|
||||
coordinates = [(cylinder.Surface.Center.x, cylinder.Surface.Center.y) for cylinder in
|
||||
[getattr(base.Shape, feature) for feature in features]]
|
||||
|
||||
tree = scipy.spatial.KDTree(coordinates)
|
||||
seen = {}
|
||||
|
||||
by_centers = {}
|
||||
for n, feature in enumerate(features):
|
||||
if n in seen:
|
||||
continue
|
||||
seen[n] = True
|
||||
|
||||
cylinder = getattr(base.Shape, feature)
|
||||
xc, yc, _ = cylinder.Surface.Center
|
||||
by_centers[xc, yc] = {cylinder.Surface.Radius : feature}
|
||||
|
||||
for coord in tree.query_ball_point((xc, yc), cylinder.Surface.Radius):
|
||||
seen[coord] = True
|
||||
cylinder = getattr(base.Shape, features[coord])
|
||||
by_centers[xc, yc][cylinder.Surface.Radius] = features[coord]
|
||||
|
||||
return by_centers
|
||||
|
||||
class ObjectPathHelix(object):
|
||||
|
||||
def __init__(self,obj):
|
||||
# Basic
|
||||
obj.addProperty("App::PropertyLinkSubList","Features","Path",translate("Features","Selected features for the drill operation"))
|
||||
obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Set to False to disable code generation"))
|
||||
obj.addProperty("App::PropertyString","Comment","Path",translate("Comment","An optional comment for this profile, will appear in G-Code"))
|
||||
|
||||
# Helix specific
|
||||
obj.addProperty("App::PropertyEnumeration", "Direction", "Helix Drill",
|
||||
translate("Direction", "The direction of the circular cuts, clockwise (CW), or counter clockwise (CCW)"))
|
||||
obj.Direction = ['CW','CCW']
|
||||
|
||||
obj.addProperty("App::PropertyEnumeration", "StartSide", "Helix Drill",
|
||||
translate("Direction", "Start cutting from the inside or outside"))
|
||||
obj.StartSide = ['inside','outside']
|
||||
|
||||
obj.addProperty("App::PropertyLength", "DeltaR", "Helix Drill",
|
||||
translate("DeltaR", "Radius increment (must be smaller than tool diameter)"))
|
||||
|
||||
# Depth Properties
|
||||
obj.addProperty("App::PropertyDistance", "Clearance", "Depths",
|
||||
translate("Clearance","Safe distance above the top of the hole to which to retract the tool"))
|
||||
obj.addProperty("App::PropertyLength", "StepDown", "Depths",
|
||||
translate("StepDown","Incremental Step Down of Tool"))
|
||||
obj.addProperty("App::PropertyBool","UseStartDepth","Depths",
|
||||
translate("Use Start Depth","Set to True to manually specify a start depth"))
|
||||
obj.addProperty("App::PropertyDistance", "StartDepth", "Depths",
|
||||
translate("Start Depth","Starting Depth of Tool - first cut depth in Z"))
|
||||
obj.addProperty("App::PropertyBool","UseFinalDepth","Depths",
|
||||
translate("Use Final Depth","Set to True to manually specify a final depth"))
|
||||
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depths",
|
||||
translate("Final Depth","Final Depth of Tool - lowest value in Z"))
|
||||
obj.addProperty("App::PropertyDistance", "ThroughDepth", "Depths",
|
||||
translate("Through Depth","Add this amount of additional cutting depth to open-ended holes. Only used if UseFinalDepth is False"))
|
||||
|
||||
# The current tool number, read-only
|
||||
# this is apparently used internally, to keep track of tool chagnes
|
||||
obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("PathProfile","The current tool in use"))
|
||||
obj.ToolNumber = (0,0,1000,1)
|
||||
obj.setEditorMode('ToolNumber',1) #make this read only
|
||||
|
||||
obj.Proxy = self
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self,state):
|
||||
return None
|
||||
|
||||
def execute(self,obj):
|
||||
from Part import Circle, Cylinder, Plane
|
||||
from math import sqrt
|
||||
|
||||
output = '(helix cut operation'
|
||||
if obj.Comment:
|
||||
output += ', '+ str(obj.Comment)+')\n'
|
||||
else:
|
||||
output += ')\n'
|
||||
|
||||
if obj.Features:
|
||||
if not obj.Active:
|
||||
obj.Path = Path.Path("(helix cut operation inactive)")
|
||||
if obj.ViewObject:
|
||||
obj.ViewObject.Visibility = False
|
||||
return
|
||||
|
||||
toolload = PathUtils.getLastToolLoad(obj)
|
||||
|
||||
if toolload is None or toolload.ToolNumber == 0:
|
||||
FreeCAD.Console.PrintError("PathHelix: No tool selected for helix cut operation, insert a tool change operation first\n")
|
||||
obj.Path = Path.Path("(ERROR: no tool selected for helix cut operation)")
|
||||
return
|
||||
|
||||
tool = PathUtils.getTool(obj, toolload.ToolNumber)
|
||||
|
||||
zsafe = max(baseobj.Shape.BoundBox.ZMax for baseobj, features in obj.Features) + obj.Clearance.Value
|
||||
output += "G0 Z" + fmt(zsafe)
|
||||
|
||||
drill_jobs = []
|
||||
|
||||
for base, features in obj.Features:
|
||||
for center, by_radius in features_by_centers(base, features).items():
|
||||
radii = sorted(by_radius.keys(), reverse=True)
|
||||
cylinders = map(lambda radius: getattr(base.Shape, by_radius[radius]), radii)
|
||||
zsafe = max(cyl.BoundBox.ZMax for cyl in cylinders) + obj.Clearance.Value
|
||||
cur_z = cylinders[0].BoundBox.ZMax
|
||||
jobs = []
|
||||
|
||||
for cylinder in cylinders:
|
||||
# Find other edge of current cylinder
|
||||
other_edge = None
|
||||
for edge in cylinder.Edges:
|
||||
if isinstance(edge.Curve, Circle) and edge.Curve.Center.z != cur_z:
|
||||
other_edge = edge
|
||||
break
|
||||
|
||||
next_z = other_edge.Curve.Center.z
|
||||
dz = next_z - cur_z
|
||||
r = cylinder.Surface.Radius
|
||||
|
||||
if dz < 0:
|
||||
# This is a closed hole if the face connected to the current cylinder at next_z has
|
||||
# the cylinder's edge as its OuterWire
|
||||
closed = None
|
||||
for face in base.Shape.Faces:
|
||||
if connected(other_edge, face) and not face.isSame(cylinder.Faces[0]):
|
||||
wire = face.OuterWire
|
||||
if len(wire.Edges) == 1 and wire.Edges[0].isSame(other_edge):
|
||||
closed = True
|
||||
else:
|
||||
closed = False
|
||||
|
||||
if closed is None:
|
||||
raise Exception("Cannot determine if this cylinder is closed on the z = {0} side".format(next_z))
|
||||
|
||||
xc, yc, _ = cylinder.Surface.Center
|
||||
jobs.append(dict(xc=xc, yc=yc, zmin=next_z, zmax=cur_z, r_out=r, r_in=0.0, closed=closed, zsafe=zsafe))
|
||||
|
||||
elif dz > 0:
|
||||
new_jobs = []
|
||||
for job in jobs:
|
||||
if job["zmin"] < next_z < job["zmax"]:
|
||||
# split this job
|
||||
job1 = dict(job)
|
||||
job2 = dict(job)
|
||||
job1["zmin"] = next_z
|
||||
job2["zmax"] = next_z
|
||||
job2["r_in"] = r
|
||||
new_jobs.append(job1)
|
||||
new_jobs.append(job2)
|
||||
else:
|
||||
new_jobs.append(job)
|
||||
jobs = new_jobs
|
||||
else:
|
||||
FreeCAD.Console.PrintError("PathHelix: Encountered cylinder with zero height\n")
|
||||
break
|
||||
|
||||
cur_z = next_z
|
||||
|
||||
if obj.UseStartDepth:
|
||||
jobs = [job for job in jobs if job["zmin"] < obj.StartDepth.Value]
|
||||
if jobs:
|
||||
jobs[0]["zmax"] = obj.StartDepth.Value
|
||||
if obj.UseFinalDepth:
|
||||
jobs = [job for job in jobs if job["zmax"] > obj.FinalDepth.Value]
|
||||
if jobs:
|
||||
jobs[-1]["zmin"] = obj.FinalDepth.Value
|
||||
else:
|
||||
if not jobs[-1]["closed"]:
|
||||
jobs[-1]["zmin"] -= obj.ThroughDepth.Value
|
||||
|
||||
drill_jobs.extend(jobs)
|
||||
|
||||
for job in drill_jobs:
|
||||
output += helix_cut((job["xc"], job["yc"]), job["r_out"], job["r_in"], obj.DeltaR.Value,
|
||||
job["zmax"], job["zmin"], obj.StepDown.Value,
|
||||
job["zsafe"], tool.Diameter,
|
||||
toolload.VertFeed.Value, toolload.HorizFeed.Value, obj.Direction, obj.StartSide)
|
||||
output += '\n'
|
||||
|
||||
obj.Path = Path.Path(output)
|
||||
if obj.ViewObject:
|
||||
obj.ViewObject.Visibility = True
|
||||
|
||||
taskpanels = {}
|
||||
|
||||
class ViewProviderPathHelix(object):
|
||||
def __init__(self,vobj):
|
||||
vobj.Proxy = self
|
||||
|
||||
def attach(self,vobj):
|
||||
self.Object = vobj.Object
|
||||
return
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/Path-Helix.svg"
|
||||
|
||||
def setEdit(self, vobj, mode=0):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
taskpanel = TaskPanel(vobj.Object)
|
||||
FreeCADGui.Control.showDialog(taskpanel)
|
||||
taskpanels[0] = taskpanel
|
||||
return True
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
class CommandPathHelix(object):
|
||||
def GetResources(self):
|
||||
return {'Pixmap' : 'Path-Helix',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathHelix","PathHelix"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathHelix","Creates a helix cut from selected circles")}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if o.Name[:3] == "Job":
|
||||
return True
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
import FreeCADGui
|
||||
import Path
|
||||
from PathScripts import PathUtils
|
||||
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathHelix","Create a helix cut"))
|
||||
FreeCADGui.addModule("PathScripts.PathHelix")
|
||||
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","PathHelix")
|
||||
ObjectPathHelix(obj)
|
||||
ViewProviderPathHelix(obj.ViewObject)
|
||||
|
||||
obj.Features = cylinders_in_selection()
|
||||
obj.DeltaR = 1.0
|
||||
|
||||
toolLoad = PathUtils.getLastToolLoad(obj)
|
||||
if toolLoad is not None:
|
||||
obj.ToolNumber = toolLoad.ToolNumber
|
||||
tool = PathUtils.getTool(obj, toolLoad.ToolNumber)
|
||||
if tool:
|
||||
# start with 25% overlap
|
||||
obj.DeltaR = tool.Diameter * 0.75
|
||||
|
||||
obj.Active = True
|
||||
obj.Comment = ""
|
||||
|
||||
obj.Direction = "CW"
|
||||
obj.StartSide = "inside"
|
||||
|
||||
obj.Clearance = 10.0
|
||||
obj.StepDown = 1.0
|
||||
obj.UseStartDepth = False
|
||||
obj.StartDepth = 1.0
|
||||
obj.UseFinalDepth = False
|
||||
obj.FinalDepth = 0.0
|
||||
obj.ThroughDepth = 0.0
|
||||
|
||||
PathUtils.addToJob(obj)
|
||||
|
||||
obj.ViewObject.startEditing()
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def print_exceptions(func):
|
||||
from functools import wraps
|
||||
import traceback
|
||||
import sys
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except:
|
||||
ex_type, ex, tb = sys.exc_info()
|
||||
FreeCAD.Console.PrintError("".join(traceback.format_exception(ex_type, ex, tb)) + "\n")
|
||||
raise
|
||||
return wrapper
|
||||
|
||||
def print_all_exceptions(cls):
|
||||
for entry in dir(cls):
|
||||
obj = getattr(cls, entry)
|
||||
if not entry.startswith("__") and hasattr(obj, "__call__"):
|
||||
setattr(cls, entry, print_exceptions(obj))
|
||||
return cls
|
||||
|
||||
@print_all_exceptions
|
||||
class TaskPanel(object):
|
||||
|
||||
def __init__(self, obj):
|
||||
from Units import Quantity
|
||||
self.obj = obj
|
||||
|
||||
ui = FreeCADGui.UiLoader()
|
||||
layout = QtGui.QGridLayout()
|
||||
|
||||
headerStyle = "QLabel { font-weight: bold; font-size: large; }"
|
||||
grayed_out = "background-color: #d0d0d0;"
|
||||
|
||||
self.previous_value = {}
|
||||
|
||||
def addWidget(widget):
|
||||
row = layout.rowCount()
|
||||
layout.addWidget(widget, row, 0, 1, 2)
|
||||
|
||||
def addWidgets(widget1, widget2):
|
||||
row = layout.rowCount()
|
||||
layout.addWidget(widget1, row, 0)
|
||||
layout.addWidget(widget2, row, 1)
|
||||
|
||||
def heading(label):
|
||||
heading = QtGui.QLabel(label)
|
||||
heading.setStyleSheet(headerStyle)
|
||||
addWidget(heading)
|
||||
|
||||
def addQuantity(property, labelstring, activator=None, max=None):
|
||||
self.previous_value[property] = getattr(self.obj, property)
|
||||
widget = ui.createWidget("Gui::InputField")
|
||||
|
||||
if activator:
|
||||
self.previous_value[activator] = getattr(self.obj, activator)
|
||||
currently_active = getattr(self.obj, activator)
|
||||
label = QtGui.QCheckBox(labelstring)
|
||||
|
||||
def change(state):
|
||||
setattr(self.obj, activator, label.isChecked())
|
||||
if label.isChecked():
|
||||
widget.setStyleSheet("")
|
||||
else:
|
||||
widget.setStyleSheet(grayed_out)
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
label.stateChanged.connect(change)
|
||||
label.setChecked(currently_active)
|
||||
if not currently_active:
|
||||
widget.setStyleSheet(grayed_out)
|
||||
label.setToolTip(self.obj.getDocumentationOfProperty(activator))
|
||||
else:
|
||||
label = QtGui.QLabel(labelstring)
|
||||
label.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
|
||||
widget.setText(str(getattr(self.obj, property)))
|
||||
widget.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
|
||||
if max:
|
||||
# cannot use widget.setMaximum() as apparently ui.createWidget()
|
||||
# returns the object up-casted to QWidget.
|
||||
widget.setProperty("maximum", max)
|
||||
|
||||
def change(quantity):
|
||||
setattr(self.obj, property, quantity)
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
QtCore.QObject.connect(widget, QtCore.SIGNAL("valueChanged(const Base::Quantity &)"), change)
|
||||
|
||||
addWidgets(label, widget)
|
||||
return label, widget
|
||||
|
||||
def addCheckBox(property, label):
|
||||
self.previous_value[property] = getattr(self.obj, property)
|
||||
widget = QtGui.QCheckBox(label)
|
||||
widget.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
|
||||
def change(state):
|
||||
setattr(self.obj, property, widget.isChecked())
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
widget.stateChanged.connect(change)
|
||||
|
||||
widget.setChecked(getattr(self.obj, property))
|
||||
addWidget(widget)
|
||||
|
||||
def addEnumeration(property, label, options):
|
||||
self.previous_value[property] = getattr(self.obj, property)
|
||||
label = QtGui.QLabel(label)
|
||||
label.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
widget = QtGui.QComboBox()
|
||||
widget.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
for option_label, option_value in options:
|
||||
widget.addItem(option_label)
|
||||
def change(index):
|
||||
setattr(self.obj, property, options[index][1])
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
widget.currentIndexChanged.connect(change)
|
||||
addWidgets(label, widget)
|
||||
|
||||
self.featureTree = QtGui.QTreeWidget()
|
||||
self.featureTree.setMinimumHeight(200)
|
||||
self.featureTree.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
#self.featureTree.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
|
||||
#self.featureTree.setDefaultDropAction(QtCore.Qt.MoveAction)
|
||||
self.fillFeatureTree()
|
||||
sm = self.featureTree.selectionModel()
|
||||
sm.selectionChanged.connect(self.selectFeatures)
|
||||
addWidget(self.featureTree)
|
||||
self.featureTree.expandAll()
|
||||
|
||||
self.addButton = QtGui.QPushButton("Add holes")
|
||||
self.addButton.clicked.connect(self.addCylinders)
|
||||
|
||||
self.delButton = QtGui.QPushButton("Delete")
|
||||
self.delButton.clicked.connect(self.delCylinders)
|
||||
|
||||
addWidgets(self.addButton, self.delButton)
|
||||
|
||||
heading("Drill parameters")
|
||||
addCheckBox("Active", "Operation is active")
|
||||
tool = PathUtils.getTool(self.obj,self.obj.ToolNumber)
|
||||
if not tool:
|
||||
drmax = None
|
||||
else:
|
||||
drmax = tool.Diameter
|
||||
addQuantity("DeltaR", "Step in Radius", max=drmax)
|
||||
addQuantity("StepDown", "Step in Z")
|
||||
addEnumeration("Direction", "Cut direction", [("Clockwise", "CW"), ("Counter-Clockwise", "CCW")])
|
||||
addEnumeration("StartSide", "Start Side", [("Start from inside", "inside"), ("Start from outside", "outside")])
|
||||
|
||||
heading("Cutting Depths")
|
||||
addQuantity("Clearance", "Clearance Distance")
|
||||
addQuantity("StartDepth", "Absolute start height", "UseStartDepth")
|
||||
|
||||
fdcheckbox, fdinput = addQuantity("FinalDepth", "Absolute final height", "UseFinalDepth")
|
||||
tdlabel, tdinput = addQuantity("ThroughDepth", "Extra drill depth\nfor open holes")
|
||||
|
||||
# make ThroughDepth and FinalDepth mutually exclusive
|
||||
def fd_change(state):
|
||||
if fdcheckbox.isChecked():
|
||||
tdinput.setStyleSheet(grayed_out)
|
||||
else:
|
||||
tdinput.setStyleSheet("")
|
||||
fdcheckbox.stateChanged.connect(fd_change)
|
||||
|
||||
def td_change(quantity):
|
||||
fdcheckbox.setChecked(False)
|
||||
QtCore.QObject.connect(tdinput, QtCore.SIGNAL("valueChanged(const Base::Quantity &)"), td_change)
|
||||
|
||||
if obj.UseFinalDepth:
|
||||
tdinput.setStyleSheet(grayed_out)
|
||||
|
||||
# add
|
||||
widget = QtGui.QWidget()
|
||||
widget.setLayout(layout)
|
||||
self.form = widget
|
||||
|
||||
def addCylinders(self):
|
||||
features_per_base = {}
|
||||
for base, features in self.obj.Features:
|
||||
features_per_base[base] = list(set(features))
|
||||
|
||||
for base, features in cylinders_in_selection():
|
||||
for feature in features:
|
||||
if base in features_per_base:
|
||||
if not feature in features_per_base[base]:
|
||||
features_per_base[base].append(feature)
|
||||
else:
|
||||
features_per_base[base] = [feature]
|
||||
|
||||
self.obj.Features = list(features_per_base.items())
|
||||
self.featureTree.clear()
|
||||
self.fillFeatureTree()
|
||||
self.featureTree.expandAll()
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def delCylinders(self):
|
||||
del_features = []
|
||||
|
||||
def delete_feature(item, base=None):
|
||||
kind, feature = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "feature")
|
||||
|
||||
if base is None:
|
||||
base_item = item.parent().parent()
|
||||
_, base = base_item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
del_features.append((base, feature))
|
||||
item.parent().takeChild(item.parent().indexOfChild(item))
|
||||
|
||||
def delete_hole(item, base=None):
|
||||
kind, center = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "hole")
|
||||
|
||||
if base is None:
|
||||
base_item = item.parent()
|
||||
_, base = base_item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
for i in reversed(range(item.childCount())):
|
||||
delete_feature(item.child(i), base=base)
|
||||
item.parent().takeChild(item.parent().indexOfChild(item))
|
||||
|
||||
def delete_base(item):
|
||||
kind, base = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "base")
|
||||
for i in reversed(range(item.childCount())):
|
||||
delete_hole(item.child(i), base=base)
|
||||
self.featureTree.takeTopLevelItem(self.featureTree.indexOfTopLevelItem(item))
|
||||
|
||||
for item in self.featureTree.selectedItems():
|
||||
kind, info = item.data(0, QtCore.Qt.UserRole)
|
||||
if kind == "base":
|
||||
delete_base(item)
|
||||
elif kind == "hole":
|
||||
parent = item.parent()
|
||||
delete_hole(item)
|
||||
if parent.childCount() == 0:
|
||||
self.featureTree.takeTopLevelItem(self.featureTree.indexOfTopLevelItem(parent))
|
||||
elif kind =="feature":
|
||||
parent = item.parent()
|
||||
delete_feature(item)
|
||||
if parent.childCount() == 0:
|
||||
parent.parent().takeChild(parent.parent().indexOfChild(parent))
|
||||
else:
|
||||
raise Exception("No such item kind: {0}".format(kind))
|
||||
|
||||
for base, features in cylinders_in_selection():
|
||||
for feature in features:
|
||||
del_features.append((base, feature))
|
||||
|
||||
new_features = []
|
||||
for obj, features in self.obj.Features:
|
||||
for feature in features:
|
||||
if (obj, feature) not in del_features:
|
||||
new_features.append((obj, feature))
|
||||
|
||||
self.obj.Features = new_features
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def fillFeatureTree(self):
|
||||
for base, features in self.obj.Features:
|
||||
base_item = QtGui.QTreeWidgetItem()
|
||||
base_item.setText(0, base.Name)
|
||||
base_item.setData(0, QtCore.Qt.UserRole, ("base", base))
|
||||
self.featureTree.addTopLevelItem(base_item)
|
||||
for center, by_radius in features_by_centers(base, features).items():
|
||||
hole_item = QtGui.QTreeWidgetItem()
|
||||
hole_item.setText(0, "Hole at ({0[0]:.2f}, {0[1]:.2f})".format(center))
|
||||
hole_item.setData(0, QtCore.Qt.UserRole, ("hole", center))
|
||||
base_item.addChild(hole_item)
|
||||
for radius in sorted(by_radius.keys(), reverse=True):
|
||||
feature = by_radius[radius]
|
||||
cylinder = getattr(base.Shape, feature)
|
||||
cyl_item = QtGui.QTreeWidgetItem()
|
||||
cyl_item.setText(0, "Diameter {0:.2f}, {1}".format(2 * cylinder.Surface.Radius, feature))
|
||||
cyl_item.setData(0, QtCore.Qt.UserRole, ("feature", feature))
|
||||
hole_item.addChild(cyl_item)
|
||||
|
||||
def selectFeatures(self, selected, deselected):
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
def select_feature(item, base=None):
|
||||
kind, feature = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "feature")
|
||||
|
||||
if base is None:
|
||||
base_item = item.parent().parent()
|
||||
_, base = base_item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
FreeCADGui.Selection.addSelection(base, feature)
|
||||
|
||||
def select_hole(item, base=None):
|
||||
kind, center = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "hole")
|
||||
|
||||
if base is None:
|
||||
base_item = item.parent()
|
||||
_, base = base_item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
for i in range(item.childCount()):
|
||||
select_feature(item.child(i), base=base)
|
||||
|
||||
def select_base(item):
|
||||
kind, base = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "base")
|
||||
|
||||
for i in range(item.childCount()):
|
||||
select_hole(item.child(i), base=base)
|
||||
|
||||
for item in self.featureTree.selectedItems():
|
||||
kind, info = item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
if kind == "base":
|
||||
select_base(item)
|
||||
elif kind == "hole":
|
||||
select_hole(item)
|
||||
elif kind == "feature":
|
||||
select_feature(item)
|
||||
|
||||
def needsFullSpace(self):
|
||||
return True
|
||||
|
||||
def accept(self):
|
||||
FreeCADGui.ActiveDocument.resetEdit()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
|
||||
def reject(self):
|
||||
for property in self.previous_value:
|
||||
setattr(self.obj, property, self.previous_value[property])
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
FreeCADGui.ActiveDocument.resetEdit()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
FreeCADGui.addCommand('Path_Helix',CommandPathHelix())
|
Loading…
Reference in New Issue
Block a user