From ae3bbf59887ed34e8bee73fe3b843d073f171054 Mon Sep 17 00:00:00 2001 From: Alexander Gryson Date: Fri, 23 Dec 2016 15:36:24 +0100 Subject: [PATCH 001/144] Update Raytracing Workbench icons --- .../Gui/Resources/icons/Raytrace_Camera.svg | 485 ++++++++++++--- .../Gui/Resources/icons/Raytrace_Export.svg | 222 +++++-- .../icons/Raytrace_ExportProject.svg | 401 +++++------- .../Gui/Resources/icons/Raytrace_Lux.svg | 424 ++++--------- .../Gui/Resources/icons/Raytrace_New.svg | 477 ++++++--------- .../icons/Raytrace_NewPartSegment.svg | 578 ++++++++---------- .../Gui/Resources/icons/Raytrace_Part.svg | 313 ++++++++-- .../Gui/Resources/icons/Raytrace_Render.svg | 302 +++++++-- .../Resources/icons/Raytrace_ResetCamera.svg | 513 ++++++++-------- .../Resources/icons/RaytracingWorkbench.svg | 130 +++- .../icons/preferences-raytracing.svg | 130 +++- 11 files changed, 2271 insertions(+), 1704 deletions(-) diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Camera.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Camera.svg index 6b59026505..2103607c11 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Camera.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Camera.svg @@ -1,5 +1,6 @@ + + inkscape:output_extension="org.inkscape.output.svg.inkscape" + version="1.1"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x1="37.559368" + y1="38.70792" + x2="37.612301" + y2="16.833065" /> + + + + + + + + + + + + + + + + + + + + + + + + inkscape:window-y="27" + inkscape:snap-global="true" + inkscape:window-maximized="1" + inkscape:snap-bbox="true" + inkscape:snap-nodes="false"> + + @@ -247,63 +506,149 @@ id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer"> - - - - + + + - - - - - - + transform="matrix(0.9168094,-0.24770369,0.23279633,0.86163377,-33.003716,15.008646)" + style="stroke:#2e3436;stroke-width:2.17234993" /> + style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 47,5.1739572 -0.01652,14.1043748 12.041123,1.500257 0.01019,-14.0569205 z" + id="path3810" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> + + + + + + + + + + + + + + + + + + + - - diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Export.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Export.svg index 020e14a31e..5b07675ef9 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Export.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Export.svg @@ -1,5 +1,6 @@ + + inkscape:output_extension="org.inkscape.output.svg.inkscape" + version="1.1"> + + + + + + + + + style="stop-color:#729fcf;stop-opacity:1" /> + style="stop-color:#204a87;stop-opacity:1" /> @@ -81,35 +105,64 @@ gradientTransform="translate(-1.6287944,-3.3426866)" /> - + gradientTransform="matrix(1.4500001,0,0,1.4705882,-27.45,-15.05882)" + x1="39.263832" + y1="20.978374" + x2="43.478561" + y2="42.076672" /> + + + + + x2="18.24375" + y2="17.304836" /> + + + + + + + inkscape:window-y="27" + inkscape:snap-global="true" + inkscape:snap-bbox="true" + inkscape:snap-nodes="true" + inkscape:window-maximized="0"> + + @@ -146,31 +211,86 @@ inkscape:groupmode="layer"> - + transform="matrix(1.1009599,0,0,1.1009599,-2.2923986,-2.3717661)" /> + + transform="matrix(1.1009599,0,0,1.1009599,-2.2923986,-2.3717661)" /> + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="path3802" + d="M 6.1565615,15.965209 36.278332,5.3479144 58.826043,35.382706 55.617294,47.374262 33.826043,54.695829 z" + style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + + sodipodi:nodetypes="ccssccsccccccssccsssc" + inkscape:connector-curvature="0" /> + + + + + sodipodi:type="arc" + style="fill:none;stroke:#729fcf;stroke-width:2.22290229999999989;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.59999999999999998" + id="path3845" + sodipodi:cx="29.988941" + sodipodi:cy="23.580124" + sodipodi:rx="3.2227654" + sodipodi:ry="3.2227654" + d="m 33.211707,23.580124 a 3.2227654,3.2227654 0 1 1 -6.445531,0 3.2227654,3.2227654 0 1 1 6.445531,0 z" + transform="matrix(0.89972465,0,0,0.89972465,3.486877,2.2674856)" /> + + + - - diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ExportProject.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ExportProject.svg index 5a0314fd77..e3a454c5ab 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ExportProject.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ExportProject.svg @@ -10,12 +10,12 @@ 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="48.000000px" - height="48.000000px" + width="64" + height="64" id="svg249" sodipodi:version="0.32" - inkscape:version="0.48.4 r9939" - sodipodi:docname="Raytrace_NewPartSegment.svg" + inkscape:version="0.48.5 r10040" + sodipodi:docname="Raytrace_ExportProject.svg" inkscape:export-filename="/home/jimmac/gfx/novell/pdes/trunk/docs/BIGmime-text.png" inkscape:export-xdpi="240.00000" inkscape:export-ydpi="240.00000" @@ -23,40 +23,6 @@ version="1.1"> - - - - - - - @@ -103,7 +59,7 @@ fx="24.306795" fy="42.07798" r="15.821514" - gradientTransform="matrix(1.000000,0.000000,0.000000,0.284916,-6.310056e-16,30.08928)" + gradientTransform="matrix(1,0,0,0.284916,0,30.08928)" gradientUnits="userSpaceOnUse" /> @@ -118,11 +74,11 @@ - - - - - + cx="55" + cy="125" + fx="55" + fy="125" + r="14.375" /> + r="86.70845" /> + + + + + + + + + + + inkscape:window-maximized="0"> + + @@ -1204,7 +1156,7 @@ image/svg+xml - + Jakub Steiner @@ -1234,135 +1186,64 @@ - - - - - - + inkscape:groupmode="layer" + transform="translate(0,16)" /> - - - - - - - - - - - - - - - - - - - - - + style="display:inline" + transform="translate(0,16)" /> + style="display:inline" + transform="translate(0,16)"> + + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Lux.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Lux.svg index 74371f3f5e..0884fd53fc 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Lux.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Lux.svg @@ -10,11 +10,11 @@ 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="48.000000px" - height="48.000000px" + width="64" + height="64" id="svg249" sodipodi:version="0.32" - inkscape:version="0.48.4 r9939" + inkscape:version="0.48.5 r10040" sodipodi:docname="Raytrace_Lux.svg" inkscape:export-filename="/home/jimmac/gfx/novell/pdes/trunk/docs/BIGmime-text.png" inkscape:export-xdpi="240.00000" @@ -103,7 +103,7 @@ fx="24.306795" fy="42.07798" r="15.821514" - gradientTransform="matrix(1.000000,0.000000,0.000000,0.284916,-6.310056e-16,30.08928)" + gradientTransform="matrix(1,0,0,0.284916,0,30.08928)" gradientUnits="userSpaceOnUse" /> @@ -118,11 +118,11 @@ + cx="55" + cy="125" + fx="55" + fy="125" + r="14.375" /> @@ -337,7 +337,8 @@ y="-0.66055602" width="2.7583034" x="-0.87915176" - inkscape:collect="always"> + inkscape:collect="always" + color-interpolation-filters="sRGB"> + inkscape:collect="always" + color-interpolation-filters="sRGB"> + inkscape:collect="always" + color-interpolation-filters="sRGB"> + inkscape:collect="always" + color-interpolation-filters="sRGB"> + inkscape:collect="always" + color-interpolation-filters="sRGB"> + inkscape:collect="always" + color-interpolation-filters="sRGB"> + inkscape:collect="always" + color-interpolation-filters="sRGB"> + + + + + + + + inkscape:window-maximized="0" + inkscape:snap-nodes="false" + inkscape:snap-bbox="true"> + + @@ -474,268 +533,53 @@ - - - - - - + inkscape:groupmode="layer" + transform="translate(0,16)" /> + style="display:inline" + transform="translate(0,16)"> + style="fill:#d3d7cf;fill-opacity:1;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline" + id="rect2987" + width="46" + height="58.000004" + x="-39.256264" + y="3.082428" + transform="matrix(0,-1,1,0,0,0)" /> - - - - - - - - - - - - - - - - + style="fill:url(#linearGradient3225);fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline" + id="rect2987-1" + width="41.999992" + height="54.000004" + x="-37.256268" + y="5.082428" + transform="matrix(0,-1,1,0,0,0)" /> - - - - - - - - - - - - - - - - - - - - - - - - - - + id="path4581" + d="m 27.953738,-0.2783868 c 0.961387,-0.9621509 2.611357,-0.9621509 3.57275,0 0.96872,0.9694858 0.982688,2.6112257 0.0096,3.5851413 -0.973149,0.9738799 -2.613604,0.9599 -3.582324,-0.00959 -0.961393,-0.9621509 -0.961393,-2.6134093 0,-3.5755555 z m -7.155076,8.1358372 c 0.690386,-0.6908973 1.910338,-0.707687 2.60792,-0.00956 0.711251,0.7117763 0.711251,1.9077431 0,2.6195196 -0.697582,0.698132 -1.917572,0.681348 -2.60792,-0.0096 -0.676386,-0.6769471 -0.676418,-1.9234709 0,-2.6004185 z m -7.374769,7.9159716 c 0.523659,-0.524067 1.539766,-0.524067 2.063425,0 0.545849,0.546277 0.565797,1.517912 0.0096,2.074597 -0.556247,0.556657 -1.527118,0.5367 -2.07297,-0.0096 -0.523654,-0.524036 -0.523624,-1.540978 0,-2.065012 z M 34.373249,6.1461721 c 1.646463,-1.6477879 4.371781,-1.6477879 6.018274,0 0.802302,0.8029291 1.204405,1.8570854 1.222763,2.9445859 -0.006,0.6163707 0.330188,1.153459 0.716467,1.462718 0.33438,0.267722 0.903183,0.537591 1.64307,0.363296 1.892962,-0.361344 3.824007,0.194088 5.282708,1.653935 2.324625,2.326454 2.324625,6.144022 0,8.470446 -1.454704,1.455879 -3.466518,1.996081 -5.359129,1.634835 -0.737868,-0.173835 -1.255844,0.133544 -1.566649,0.382394 -0.400482,0.320665 -0.732119,0.893843 -0.726006,1.510548 -0.01837,1.087363 -0.407393,2.090327 -1.213224,2.896791 -1.646459,1.647757 -4.371811,1.647757 -6.018274,0 -1.646493,-1.647793 -1.646493,-4.375249 0,-6.023038 0.802296,-0.80293 1.855623,-1.205348 2.942274,-1.223721 0.615986,0.0061 1.152583,-0.330514 1.46157,-0.717001 0.267506,-0.334675 0.537168,-0.903889 0.363012,-1.644392 -0.144831,-0.760024 -0.126711,-1.414603 0.01911,-2.179754 0.17366,-0.738417 -0.133473,-1.256836 -0.382128,-1.567881 -0.320448,-0.400902 -0.893266,-0.732663 -1.509358,-0.726584 -1.086484,-0.01839 -2.088693,-0.407742 -2.894489,-1.214171 -1.646494,-1.647788 -1.646494,-4.3752468 0,-6.0230069 z m -6.419511,8.8719799 c 0.961387,-0.962151 2.611357,-0.962151 3.57275,0 0.96872,0.969482 0.982688,2.611226 0.0096,3.585142 -0.973149,0.973881 -2.613604,0.959899 -3.582324,-0.0096 -0.961393,-0.962151 -0.961393,-2.613409 0,-3.575554 z m -7.155076,8.135841 c 0.690348,-0.6909 1.910338,-0.707686 2.60792,-0.0096 0.711251,0.711781 0.711251,1.907747 0,2.619526 -0.697582,0.698128 -1.917572,0.681347 -2.60792,-0.0096 -0.676418,-0.676951 -0.676418,-1.923471 0,-2.600419 z m 7.155076,7.160701 c 0.961387,-0.96215 2.611357,-0.96215 3.57275,0 0.96872,0.969482 0.982688,2.611226 0.0096,3.585104 -0.973149,0.973917 -2.613604,0.959943 -3.582324,-0.0096 -0.961393,-0.962146 -0.961393,-2.613403 0,-3.575554 z" + inkscape:transform-center-x="7.5622649" + style="fill:#f4641c;fill-opacity:1;stroke:#a03200;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + inkscape:connector-curvature="0" /> + style="display:inline" + transform="translate(0,16)"> diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_New.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_New.svg index d9935bf77d..c00657e656 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_New.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_New.svg @@ -10,12 +10,12 @@ 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="48.000000px" - height="48.000000px" + width="64" + height="64" id="svg249" sodipodi:version="0.32" - inkscape:version="0.48.4 r9939" - sodipodi:docname="drawing-landscape-A3.svg" + inkscape:version="0.48.5 r10040" + sodipodi:docname="Raytrace_New.svg" inkscape:export-filename="/home/jimmac/gfx/novell/pdes/trunk/docs/BIGmime-text.png" inkscape:export-xdpi="240.00000" inkscape:export-ydpi="240.00000" @@ -23,40 +23,6 @@ version="1.1"> - - - - - - - @@ -103,7 +59,7 @@ fx="24.306795" fy="42.07798" r="15.821514" - gradientTransform="matrix(1.000000,0.000000,0.000000,0.284916,-6.310056e-16,30.08928)" + gradientTransform="matrix(1,0,0,0.284916,0,30.08928)" gradientUnits="userSpaceOnUse" /> @@ -118,11 +74,11 @@ - - - - - + cx="55" + cy="125" + fx="55" + fy="125" + r="14.375" /> - + + + + + + + + + + + + + + + + + inkscape:window-maximized="0" + inkscape:snap-bbox="true" + inkscape:snap-nodes="false"> + + @@ -369,7 +339,7 @@ image/svg+xml - + Jakub Steiner @@ -399,175 +369,96 @@ - - - - - - + inkscape:groupmode="layer" + transform="translate(0,16)" /> - - - - - - - - - - - - - - - - - - - - - - - - - - + style="display:inline" + transform="translate(0,16)" /> + style="display:inline" + transform="translate(0,16)"> + + + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_NewPartSegment.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_NewPartSegment.svg index d772967576..fa3de95529 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_NewPartSegment.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_NewPartSegment.svg @@ -10,11 +10,11 @@ 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="48.000000px" - height="48.000000px" + width="64" + height="64" id="svg249" sodipodi:version="0.32" - inkscape:version="0.48.4 r9939" + inkscape:version="0.48.5 r10040" sodipodi:docname="Raytrace_NewPartSegment.svg" inkscape:export-filename="/home/jimmac/gfx/novell/pdes/trunk/docs/BIGmime-text.png" inkscape:export-xdpi="240.00000" @@ -23,40 +23,6 @@ version="1.1"> - - - - - - - @@ -103,7 +59,7 @@ fx="24.306795" fy="42.07798" r="15.821514" - gradientTransform="matrix(1.000000,0.000000,0.000000,0.284916,-6.310056e-16,30.08928)" + gradientTransform="matrix(1,0,0,0.284916,0,30.08928)" gradientUnits="userSpaceOnUse" /> @@ -118,11 +74,11 @@ - - - - - + cx="55" + cy="125" + fx="55" + fy="125" + r="14.375" /> + r="86.70845" /> - - - + + + + + + gradientTransform="matrix(0.89164573,0,0,0.92857782,-68.139759,-13.967648)" /> + + + + + + + + + + + + + + + + + + + + + + + + inkscape:window-maximized="0" + inkscape:snap-bbox="true" + inkscape:snap-nodes="false"> + + @@ -1091,7 +1078,7 @@ image/svg+xml - + Jakub Steiner @@ -1121,173 +1108,98 @@ - - - - - - + inkscape:groupmode="layer" + transform="translate(0,16)" /> - - - - - - - - - - - - - - - - - - - - - + style="display:inline" + transform="translate(0,16)" /> + style="display:inline" + transform="translate(0,16)"> - - - - - - - + id="layer4-3" + style="display:inline" + transform="translate(64,-6.0000001)" /> + style="fill:#d3d7cf;fill-opacity:1;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline" + id="rect2987" + width="45.999996" + height="58.000004" + x="-39" + y="2.9999962" + transform="matrix(0,-1,1,0,0,0)" /> + + + + + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Part.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Part.svg index 42935cff37..64d169b95a 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Part.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Part.svg @@ -1,5 +1,6 @@ + + inkscape:output_extension="org.inkscape.output.svg.inkscape" + version="1.1"> + + + + + + + + + + + + - - + gradientTransform="matrix(1.4500001,0,0,1.4705882,-27.45,-15.05882)" + x1="39.263832" + y1="20.978374" + x2="43.478561" + y2="42.076672" /> + + + + + + + + + + + + + + + + + + + + + + + inkscape:window-y="27" + inkscape:snap-global="true" + inkscape:snap-bbox="true" + inkscape:snap-nodes="false" + inkscape:window-maximized="0"> + + @@ -276,43 +405,101 @@ inkscape:label="Layer 1" inkscape:groupmode="layer"> + id="g3618-6" + transform="matrix(0.7549192,0,0,0.7549192,-75.337366,-54.48924)" + style="stroke:#0b1521"> + style="fill:url(#linearGradient3850-2);fill-opacity:1;fill-rule:evenodd;stroke:#0b1521;stroke-width:2.64929008000000010;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 135.56068,86.749999 29.14219,2.64929 0,37.090061 -29.14219,-2.64929 z" + id="rect3520-0" + sodipodi:nodetypes="ccccc" + inkscape:connector-curvature="0" /> + + style="fill:none;stroke:#729fcf;stroke-width:2.64929008000000010;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 138.20997,89.85552 23.84361,1.802004 0.0489,31.688886 -23.8925,-1.74829 z" + id="rect3520-3-1" + sodipodi:nodetypes="ccccc" + inkscape:connector-curvature="0" /> + + + + + + + + transform="matrix(0.7549192,0,0,0.7549192,-99.337362,-34.48924)"> - + + + style="fill:none;stroke:#fce94f;stroke-width:2.64929008;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="m 138.20997,89.85552 23.84361,1.802004 0.0489,31.688886 -23.8925,-1.74829 z" + id="rect3520-3" + sodipodi:nodetypes="ccccc" + inkscape:connector-curvature="0" /> + - - diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Render.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Render.svg index 0226ca54ed..92af413274 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Render.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Render.svg @@ -14,12 +14,36 @@ height="64px" id="svg2383" sodipodi:version="0.32" - inkscape:version="0.48.4 r9939" + inkscape:version="0.48.5 r10040" sodipodi:docname="Raytrace_Render.svg" inkscape:output_extension="org.inkscape.output.svg.inkscape" version="1.1"> + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + inkscape:window-y="27" + inkscape:window-maximized="0"> + + @@ -255,7 +396,7 @@ image/svg+xml - + @@ -263,58 +404,91 @@ id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer"> - + + x="68" + y="2.815774" /> - + + + transform="matrix(0.85867478,-0.7467709,0.70576526,0.90856459,-4.8393203,9.0566188)" /> + sodipodi:nodetypes="ccsc" /> + style="color:#000000;fill:#3465a4;fill-opacity:1;fill-rule:evenodd;stroke:#0b1521;stroke-width:2.00000023999999987;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="M 61,32 C 3,32 61,32 3,32 3,48.016258 15.983742,61 32,61 48.016258,61 61,48.016258 61,32 z" + id="path2826-7" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccsc" /> + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ResetCamera.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ResetCamera.svg index d170cb0049..feaee52718 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ResetCamera.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ResetCamera.svg @@ -10,11 +10,11 @@ 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="48.000000px" - height="48.000000px" + width="64" + height="64" id="svg249" sodipodi:version="0.32" - inkscape:version="0.48.4 r9939" + inkscape:version="0.48.5 r10040" sodipodi:docname="Raytrace_ResetCamera.svg" inkscape:export-filename="/home/jimmac/gfx/novell/pdes/trunk/docs/BIGmime-text.png" inkscape:export-xdpi="240.00000" @@ -23,40 +23,30 @@ version="1.1"> - + id="linearGradient4152"> + id="stop4154" /> + id="stop4156" /> - + id="linearGradient4144"> + + + - @@ -103,7 +83,7 @@ fx="24.306795" fy="42.07798" r="15.821514" - gradientTransform="matrix(1.000000,0.000000,0.000000,0.284916,-6.310056e-16,30.08928)" + gradientTransform="matrix(1,0,0,0.284916,0,30.08928)" gradientUnits="userSpaceOnUse" /> @@ -118,11 +98,11 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + inkscape:window-maximized="0" + inkscape:snap-nodes="false" + inkscape:object-paths="true" + inkscape:snap-bbox="true" + inkscape:snap-global="true"> + + @@ -352,7 +423,7 @@ image/svg+xml - + Jakub Steiner @@ -382,152 +453,106 @@ - - - - - - + inkscape:groupmode="layer" + transform="translate(0,16)" /> + style="display:inline" + transform="translate(0,16)"> + style="fill:#d3d7cf;fill-opacity:1;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline" + id="rect2987" + width="45.999996" + height="58.000004" + x="-39" + y="2.9999962" + transform="matrix(0,-1,1,0,0,0)" /> + style="fill:url(#linearGradient3781-3);fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline" + id="rect2987-1" + width="41.999996" + height="54.000004" + x="-37" + y="4.9999962" + transform="matrix(0,-1,1,0,0,0)" /> + id="g4196" + transform="translate(89.864057,-55.690402)"> - - - - + transform="translate(0.135943,-14.309598)" + id="g4116"> - - - - + style="fill:url(#linearGradient4210);fill-opacity:1;stroke:#042a2a;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" + d="m -81,105 14,-12 18,0 14,12 z" + id="path3294" + inkscape:connector-curvature="0" /> + style="fill:none;stroke:#34e0e2;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -75.644024,119 9.367154,-7.98022 16.55374,-0.0396 9.367154,8.03956 z" + id="path4084" + inkscape:connector-curvature="0" + transform="translate(0,-16)" + sodipodi:nodetypes="ccccc" /> + + + + + + + + + + + + - - - - - - - - + style="display:inline" + transform="translate(0,16)" /> diff --git a/src/Mod/Raytracing/Gui/Resources/icons/RaytracingWorkbench.svg b/src/Mod/Raytracing/Gui/Resources/icons/RaytracingWorkbench.svg index ec9db97b1e..4e96a649e3 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/RaytracingWorkbench.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/RaytracingWorkbench.svg @@ -14,18 +14,18 @@ height="64px" id="svg2816" version="1.1" - inkscape:version="0.47 r22583" - sodipodi:docname="New document 2"> + inkscape:version="0.48.5 r10040" + sodipodi:docname="preferences-raytracing.svg"> @@ -106,6 +106,70 @@ offset="1" id="stop3604-0" /> + + + + + + + + + + + + + + + + + inkscape:window-width="1920" + inkscape:window-height="1057" + inkscape:window-x="1912" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:snap-bbox="true" + inkscape:snap-nodes="false"> + + @@ -144,33 +218,53 @@ inkscape:groupmode="layer"> + transform="matrix(0.48061681,0,0,0.1201542,24.795032,54.351681)" /> + transform="matrix(1.1076389,0,0,1.1076389,-3.0416672,-1.6319444)" /> + + transform="matrix(0.85867478,-0.7467709,0.70576526,0.90856459,-4.8393203,9.0566186)" /> + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/preferences-raytracing.svg b/src/Mod/Raytracing/Gui/Resources/icons/preferences-raytracing.svg index ec9db97b1e..4e96a649e3 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/preferences-raytracing.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/preferences-raytracing.svg @@ -14,18 +14,18 @@ height="64px" id="svg2816" version="1.1" - inkscape:version="0.47 r22583" - sodipodi:docname="New document 2"> + inkscape:version="0.48.5 r10040" + sodipodi:docname="preferences-raytracing.svg"> @@ -106,6 +106,70 @@ offset="1" id="stop3604-0" /> + + + + + + + + + + + + + + + + + inkscape:window-width="1920" + inkscape:window-height="1057" + inkscape:window-x="1912" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:snap-bbox="true" + inkscape:snap-nodes="false"> + + @@ -144,33 +218,53 @@ inkscape:groupmode="layer"> + transform="matrix(0.48061681,0,0,0.1201542,24.795032,54.351681)" /> + transform="matrix(1.1076389,0,0,1.1076389,-3.0416672,-1.6319444)" /> + + transform="matrix(0.85867478,-0.7467709,0.70576526,0.90856459,-4.8393203,9.0566186)" /> + From 211f771f94d35c659245fccf565c4b53d93c89d4 Mon Sep 17 00:00:00 2001 From: Alexander Gryson Date: Fri, 23 Dec 2016 21:41:33 +0100 Subject: [PATCH 002/144] Added Metadata --- .../Gui/Resources/icons/Raytrace_Camera.svg | 755 ++------- .../Gui/Resources/icons/Raytrace_Export.svg | 351 +--- .../icons/Raytrace_ExportProject.svg | 1422 +++-------------- .../Gui/Resources/icons/Raytrace_Lux.svg | 661 ++------ .../Gui/Resources/icons/Raytrace_New.svg | 538 ++----- .../icons/Raytrace_NewPartSegment.svg | 1376 +++------------- .../Gui/Resources/icons/Raytrace_Part.svg | 584 ++----- .../Gui/Resources/icons/Raytrace_Render.svg | 577 ++----- .../Resources/icons/Raytrace_ResetCamera.svg | 646 ++------ .../Resources/icons/RaytracingWorkbench.svg | 319 +--- .../icons/preferences-raytracing.svg | 319 +--- 11 files changed, 1342 insertions(+), 6206 deletions(-) diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Camera.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Camera.svg index 2103607c11..ee46d40383 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Camera.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Camera.svg @@ -1,654 +1,159 @@ - - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - + + - + - + image/svg+xml - + + + + $committer + + + Raytrace_Camera + 2011-10-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Camera.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Export.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Export.svg index 5b07675ef9..f3189a7b09 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Export.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Export.svg @@ -1,296 +1,97 @@ - - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - + + + + + + - - - - - - + + + + + + - - + + - + - + image/svg+xml - + + + + $committer + + + Raytrace_Export + 2011-10-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Export.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + - - - - - - + + + + + + - - - - + + + + - - - - + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ExportProject.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ExportProject.svg index e3a454c5ab..a0daa5dab3 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ExportProject.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ExportProject.svg @@ -1,1249 +1,281 @@ - - - - - - - + + + + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - + + + - - - - - + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - - - - + + + + + + - - - - + + + + - - + + - + - + image/svg+xml - - + + - Jakub Steiner + [agryson] Alexander Gryson - http://jimmac.musichall.cz - + http://agryson.net + + Raytrace_ExportProject + 2012-02-29 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ExportProject.svg + + + FreeCAD LGPL2+ + + + + + [agryson] Alexander Gryson + + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Lux.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Lux.svg index 0884fd53fc..9d0a80c2ff 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Lux.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Lux.svg @@ -1,587 +1,134 @@ - - - - - - - + + + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - + + - - + + - + - + image/svg+xml - - + + - Jakub Steiner + [agryson] Alexander Gryson - http://jimmac.musichall.cz - + http://agryson.net + + Raytrace_Lux + 2013-09-27 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Lux.svg + + + FreeCAD LGPL2+ + + + + + [agryson] Alexander Gryson + + - - - - - - - + + + + + + + - - - - - + + + + + - - + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_New.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_New.svg index c00657e656..841a584b61 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_New.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_New.svg @@ -1,466 +1,134 @@ - - - - - - - + + + + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - + + + + - - + + - - - - + + + + - - + + - - + + - + - + image/svg+xml - - + + - Jakub Steiner + [agryson] Alexander Gryson - http://jimmac.musichall.cz - + http://agryson.net + + Raytrace_New + 2011-10-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_New.svg + + + FreeCAD LGPL2+ + + + + + [agryson] Alexander Gryson + + - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_NewPartSegment.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_NewPartSegment.svg index fa3de95529..c2f3c5393b 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_NewPartSegment.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_NewPartSegment.svg @@ -1,1205 +1,277 @@ - - - - - - - + + + + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - - + + + + + - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - + + + - - - - - + + + + + - - - - - - + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + - - - - + + + + - - + + - + - + image/svg+xml - - + + - Jakub Steiner + [agryson] Alexander Gryson - http://jimmac.musichall.cz - + http://agryson.net + + Raytrace_NewPartSegment + 2012-02-29 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_NewPartSegment.svg + + + FreeCAD LGPL2+ + + + + + [agryson] Alexander Gryson + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Part.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Part.svg index 64d169b95a..61c4eaaabe 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Part.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Part.svg @@ -1,505 +1,135 @@ - - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - + + + + - - - - - - + + + + + + - - - - + + + + - + - - + + - + - + image/svg+xml - + + + + $committer + + + Raytrace_Part + 2011-10-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Part.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - + + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Render.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Render.svg index 92af413274..cb69e2ed3e 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Render.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Render.svg @@ -1,494 +1,137 @@ - - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - - + + + + + + - - + + - - + + - - - - + + + + - - - + + + - - - - + + + + - - + + - - + + - + - + image/svg+xml - - + + + + + $committer + + + Raytrace_Render + 2013-09-17 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_Render.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ResetCamera.svg b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ResetCamera.svg index feaee52718..adfd136803 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ResetCamera.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ResetCamera.svg @@ -1,558 +1,166 @@ - - - - - - + + + + + - - - + + + - - - - + + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - + + + + - - - + + + - - - - + + + + - - - - + + + + - - - - - - + + + + + + - - - + + + - - + + - + - + image/svg+xml - - + + - Jakub Steiner + [agryson] Alexander Gryson - http://jimmac.musichall.cz - + http://agryson.net + + Raytrace_ResetCamera + 2013-10-01 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/Raytrace_ResetCamera.svg + + + FreeCAD LGPL2+ + + + + + [agryson] Alexander Gryson + + - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + - - - + + + - - - + + + - - - + + + - + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/RaytracingWorkbench.svg b/src/Mod/Raytracing/Gui/Resources/icons/RaytracingWorkbench.svg index 4e96a649e3..cad3ec758f 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/RaytracingWorkbench.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/RaytracingWorkbench.svg @@ -1,270 +1,85 @@ - - - - - - + + + + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - + + + + - - + + - - + + - - - - + + + + - - + + - + - + image/svg+xml - - + + + + + $committer + + + RaytracingWorkbench + 2016-02-26 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/RaytracingWorkbench.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + - - - - - - + + + + + + diff --git a/src/Mod/Raytracing/Gui/Resources/icons/preferences-raytracing.svg b/src/Mod/Raytracing/Gui/Resources/icons/preferences-raytracing.svg index 4e96a649e3..6d36825d98 100644 --- a/src/Mod/Raytracing/Gui/Resources/icons/preferences-raytracing.svg +++ b/src/Mod/Raytracing/Gui/Resources/icons/preferences-raytracing.svg @@ -1,270 +1,85 @@ - - - - - - + + + + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - + + + + - - + + - - + + - - - - + + + + - - + + - + - + image/svg+xml - - + + + + + $committer + + + preferences-raytracing + 2011-10-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Raytracing/Gui/Resources/icons/preferences-raytracing.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + - - - - - - + + + + + + From ef5fb087a283f4d3d3585507891ff3d97290789f Mon Sep 17 00:00:00 2001 From: Eivind Kvedalen Date: Fri, 2 Dec 2016 17:57:41 +0100 Subject: [PATCH 003/144] Moved ObjectLabelObserver from Gui::Application to App::Application. --- src/App/Application.cpp | 108 +++++++++++++++++++++++++++++++++++++++- src/Gui/Application.cpp | 106 --------------------------------------- 2 files changed, 107 insertions(+), 107 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 7754554d9e..2aa62f41dc 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -147,6 +147,34 @@ using namespace Base; using namespace App; using namespace std; +/** Observer that watches relabeled objects and make sure that the labels inside + * a document are unique. + * @note In the FreeCAD design it is explicitly allowed to have duplicate labels + * (i.e. the user visible text e.g. in the tree view) while the internal names + * are always guaranteed to be unique. + */ +class ObjectLabelObserver +{ +public: + /// The one and only instance. + static ObjectLabelObserver* instance(); + /// Destructs the sole instance. + static void destruct (); + + /** Checks the new label of the object and relabel it if needed + * to make it unique document-wide + */ + void slotRelabelObject(const App::DocumentObject&, const App::Property&); + +private: + static ObjectLabelObserver* _singleton; + + ObjectLabelObserver(); + ~ObjectLabelObserver(); + const App::DocumentObject* current; + ParameterGrp::handle _hPGrp; +}; + //========================================================================== // Application @@ -160,7 +188,6 @@ Base::ConsoleObserverFile *Application::_pConsoleObserverFile =0; AppExport std::map Application::mConfig; BaseExport extern PyObject* Base::BaseExceptionFreeCADError; - //************************************************************************** // Construction and destruction @@ -1346,6 +1373,8 @@ void Application::initApplication(void) Console().Log("Run App init script\n"); Interpreter().runString(Base::ScriptFactory().ProduceScript("CMakeVariables")); Interpreter().runString(Base::ScriptFactory().ProduceScript("FreeCADInit")); + + ObjectLabelObserver::instance(); } std::list Application::getCmdLineFiles() @@ -2261,3 +2290,80 @@ std::string Application::FindHomePath(const char* sCall) #else # error "std::string Application::FindHomePath(const char*) not implemented" #endif + +ObjectLabelObserver* ObjectLabelObserver::_singleton = 0; + +ObjectLabelObserver* ObjectLabelObserver::instance() +{ + if (!_singleton) + _singleton = new ObjectLabelObserver; + return _singleton; +} + +void ObjectLabelObserver::destruct () +{ + delete _singleton; + _singleton = 0; +} + +void ObjectLabelObserver::slotRelabelObject(const App::DocumentObject& obj, const App::Property& prop) +{ + // observe only the Label property + if (&prop == &obj.Label) { + // have we processed this (or another?) object right now? + if (current) { + return; + } + + std::string label = obj.Label.getValue(); + App::Document* doc = obj.getDocument(); + if (doc && !_hPGrp->GetBool("DuplicateLabels")) { + std::vector objectLabels; + std::vector::const_iterator it; + std::vector objs = doc->getObjects(); + bool match = false; + + for (it = objs.begin();it != objs.end();++it) { + if (*it == &obj) + continue; // don't compare object with itself + std::string objLabel = (*it)->Label.getValue(); + if (!match && objLabel == label) + match = true; + objectLabels.push_back(objLabel); + } + + // make sure that there is a name conflict otherwise we don't have to do anything + if (match && !label.empty()) { + // remove number from end to avoid lengthy names + size_t lastpos = label.length()-1; + while (label[lastpos] >= 48 && label[lastpos] <= 57) { + // if 'lastpos' becomes 0 then all characters are digits. In this case we use + // the complete label again + if (lastpos == 0) { + lastpos = label.length()-1; + break; + } + lastpos--; + } + + label = label.substr(0, lastpos+1); + label = Base::Tools::getUniqueName(label, objectLabels, 3); + this->current = &obj; + const_cast(obj).Label.setValue(label); + this->current = 0; + } + } + } +} + +ObjectLabelObserver::ObjectLabelObserver() : current(0) +{ + App::GetApplication().signalChangedObject.connect(boost::bind + (&ObjectLabelObserver::slotRelabelObject, this, _1, _2)); + _hPGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp"); + _hPGrp = _hPGrp->GetGroup("Preferences")->GetGroup("Document"); +} + +ObjectLabelObserver::~ObjectLabelObserver() +{ +} diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 5df5b8cd54..2543ad72b0 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -165,111 +165,6 @@ struct ApplicationP CommandManager commandManager; }; -/** Observer that watches relabeled objects and make sure that the labels inside - * a document are unique. - * @note In the FreeCAD design it is explicitly allowed to have duplicate labels - * (i.e. the user visible text e.g. in the tree view) while the internal names - * are always guaranteed to be unique. - */ -class ObjectLabelObserver -{ -public: - /// The one and only instance. - static ObjectLabelObserver* instance(); - /// Destructs the sole instance. - static void destruct (); - - /** Checks the new label of the object and relabel it if needed - * to make it unique document-wide - */ - void slotRelabelObject(const App::DocumentObject&, const App::Property&); - -private: - static ObjectLabelObserver* _singleton; - - ObjectLabelObserver(); - ~ObjectLabelObserver(); - const App::DocumentObject* current; - ParameterGrp::handle _hPGrp; -}; - -ObjectLabelObserver* ObjectLabelObserver::_singleton = 0; - -ObjectLabelObserver* ObjectLabelObserver::instance() -{ - if (!_singleton) - _singleton = new ObjectLabelObserver; - return _singleton; -} - -void ObjectLabelObserver::destruct () -{ - delete _singleton; - _singleton = 0; -} - -void ObjectLabelObserver::slotRelabelObject(const App::DocumentObject& obj, const App::Property& prop) -{ - // observe only the Label property - if (&prop == &obj.Label) { - // have we processed this (or another?) object right now? - if (current) { - return; - } - - std::string label = obj.Label.getValue(); - App::Document* doc = obj.getDocument(); - if (doc && !_hPGrp->GetBool("DuplicateLabels")) { - std::vector objectLabels; - std::vector::const_iterator it; - std::vector objs = doc->getObjects(); - bool match = false; - - for (it = objs.begin();it != objs.end();++it) { - if (*it == &obj) - continue; // don't compare object with itself - std::string objLabel = (*it)->Label.getValue(); - if (!match && objLabel == label) - match = true; - objectLabels.push_back(objLabel); - } - - // make sure that there is a name conflict otherwise we don't have to do anything - if (match && !label.empty()) { - // remove number from end to avoid lengthy names - size_t lastpos = label.length()-1; - while (label[lastpos] >= 48 && label[lastpos] <= 57) { - // if 'lastpos' becomes 0 then all characters are digits. In this case we use - // the complete label again - if (lastpos == 0) { - lastpos = label.length()-1; - break; - } - lastpos--; - } - - label = label.substr(0, lastpos+1); - label = Base::Tools::getUniqueName(label, objectLabels, 3); - this->current = &obj; - const_cast(obj).Label.setValue(label); - this->current = 0; - } - } - } -} - -ObjectLabelObserver::ObjectLabelObserver() : current(0) -{ - App::GetApplication().signalChangedObject.connect(boost::bind - (&ObjectLabelObserver::slotRelabelObject, this, _1, _2)); - _hPGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp"); - _hPGrp = _hPGrp->GetGroup("Preferences")->GetGroup("Document"); -} - -ObjectLabelObserver::~ObjectLabelObserver() -{ -} - static PyObject * FreeCADGui_subgraphFromObject(PyObject * /*self*/, PyObject *args) { @@ -460,7 +355,6 @@ Application::Application(bool GUIenabled) createStandardOperations(); MacroCommand::load(); } - ObjectLabelObserver::instance(); } Application::~Application() From d51188bd07e32e74af674890b711b68664fe7572 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 1 Jan 2017 22:41:28 -0800 Subject: [PATCH 004/144] Setting a minimum # interpolations for displaying an arc. --- src/Mod/Path/Gui/ViewProviderPath.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/Gui/ViewProviderPath.cpp b/src/Mod/Path/Gui/ViewProviderPath.cpp index e2c4322f8c..74e1df6906 100644 --- a/src/Mod/Path/Gui/ViewProviderPath.cpp +++ b/src/Mod/Path/Gui/ViewProviderPath.cpp @@ -52,6 +52,9 @@ #include #include + +#define ARC_MIN_SEGMENTS 20.0 // minimum # segements to interpolate an arc + #ifndef M_PI #define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846 /* pi */ @@ -306,8 +309,9 @@ void ViewProviderPath::updateData(const App::Property* prop) angle = M_PI * 2 - angle; if (angle == 0) angle = M_PI * 2; - int segments = 3/(deviation/angle); //we use a rather simple rule here, provisorily + int segments = std::max(ARC_MIN_SEGMENTS, 3.0/(deviation/angle)); //we use a rather simple rule here, provisorily double dZ = (next.z - last.z)/segments; //How far each segment will helix in Z + for (int j = 1; j < segments; j++) { //std::cout << "vector " << j << std::endl; Base::Vector3d inter; From a3427c7dbeb548ffe0f5e8d5b6d789f8d81e27ab Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 2 Jan 2017 19:39:13 +0100 Subject: [PATCH 005/144] fix major bug in ExtensionContainer::restoreExtensions --- src/App/ExtensionContainer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/App/ExtensionContainer.cpp b/src/App/ExtensionContainer.cpp index 1b5ef4f49d..6fd435415d 100644 --- a/src/App/ExtensionContainer.cpp +++ b/src/App/ExtensionContainer.cpp @@ -35,11 +35,11 @@ using namespace App; -TYPESYSTEM_SOURCE(App::ExtensionContainer, App::PropertyContainer); +TYPESYSTEM_SOURCE(App::ExtensionContainer, App::PropertyContainer) ExtensionContainer::ExtensionContainer() { -}; +} ExtensionContainer::~ExtensionContainer() { @@ -48,7 +48,7 @@ ExtensionContainer::~ExtensionContainer() { if(entry.second->isPythonExtension()) delete entry.second; } -}; +} void ExtensionContainer::registerExtension(Base::Type extension, Extension* ext) { @@ -361,7 +361,7 @@ void ExtensionContainer::restoreExtensions(Base::XMLReader& reader) { for (int i=0 ;i Date: Mon, 2 Jan 2017 13:43:47 +0100 Subject: [PATCH 006/144] FEM: code formating, flake8 and delete not used imports --- src/Mod/Fem/_CommandClearMesh.py | 2 +- src/Mod/Fem/_CommandPrintMeshInfo.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Mod/Fem/_CommandClearMesh.py b/src/Mod/Fem/_CommandClearMesh.py index d4364bd59d..8d303316d8 100644 --- a/src/Mod/Fem/_CommandClearMesh.py +++ b/src/Mod/Fem/_CommandClearMesh.py @@ -53,4 +53,4 @@ class _CommandClearMesh(FemCommands): FreeCADGui.Selection.clearSelection() -FreeCADGui.addCommand('Fem_ClearMesh',_CommandClearMesh()) +FreeCADGui.addCommand('Fem_ClearMesh', _CommandClearMesh()) diff --git a/src/Mod/Fem/_CommandPrintMeshInfo.py b/src/Mod/Fem/_CommandPrintMeshInfo.py index e1a2c12c59..fee5b5addc 100644 --- a/src/Mod/Fem/_CommandPrintMeshInfo.py +++ b/src/Mod/Fem/_CommandPrintMeshInfo.py @@ -31,7 +31,6 @@ import FreeCAD from FemCommands import FemCommands import FreeCADGui from PySide import QtCore -from PySide import QtGui class _CommandPrintMeshInfo(FemCommands): @@ -56,4 +55,4 @@ class _CommandPrintMeshInfo(FemCommands): FreeCADGui.Selection.clearSelection() -FreeCADGui.addCommand('Fem_PrintMeshInfo',_CommandPrintMeshInfo()) +FreeCADGui.addCommand('Fem_PrintMeshInfo', _CommandPrintMeshInfo()) From 437ce69b114c7cb25106ad295bb8a1ba31513ba8 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 2 Jan 2017 13:43:55 +0100 Subject: [PATCH 007/144] FEM: GMSH mesh task panel, add an ok and cancel button, remove close button --- src/Mod/Fem/_TaskPanelFemMeshGmsh.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Mod/Fem/_TaskPanelFemMeshGmsh.py b/src/Mod/Fem/_TaskPanelFemMeshGmsh.py index 109f6d9314..4e25df5bd4 100644 --- a/src/Mod/Fem/_TaskPanelFemMeshGmsh.py +++ b/src/Mod/Fem/_TaskPanelFemMeshGmsh.py @@ -60,11 +60,16 @@ class _TaskPanelFemMeshGmsh: self.update() def getStandardButtons(self): - return int(QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Close) - # show a apply and a close button - # def reject() is called on close button + return int(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel) + # show a OK, a apply and a Cancel button + # def reject() is called on Cancel button # def clicked(self, button) is needed, to access the apply button - # def accept() in no longer needed, since there is no OK button + + def accept(self): + self.set_mesh_params() + FreeCADGui.ActiveDocument.resetEdit() + FreeCAD.ActiveDocument.recompute() + return True def reject(self): FreeCADGui.ActiveDocument.resetEdit() @@ -73,6 +78,7 @@ class _TaskPanelFemMeshGmsh: def clicked(self, button): if button == QtGui.QDialogButtonBox.Apply: + self.set_mesh_params() self.run_gmsh() def get_mesh_params(self): @@ -133,7 +139,6 @@ class _TaskPanelFemMeshGmsh: self.gmsh_runs = True self.console_log("We gone start ...") self.get_active_analysis() - self.set_mesh_params() import FemGmshTools gmsh_mesh = FemGmshTools.FemGmshTools(self.obj, self.analysis) self.console_log("Start GMSH ...") From 6df6bae9d8c3aa544eea6e38fbd6de1875d7c994 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 2 Jan 2017 13:43:58 +0100 Subject: [PATCH 008/144] FEM: GMSH mesh obj, switch to FEM WB on double click on obj --- src/Mod/Fem/_ViewProviderFemMeshGmsh.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Fem/_ViewProviderFemMeshGmsh.py b/src/Mod/Fem/_ViewProviderFemMeshGmsh.py index 2c0b25c0fe..02f3a7880b 100644 --- a/src/Mod/Fem/_ViewProviderFemMeshGmsh.py +++ b/src/Mod/Fem/_ViewProviderFemMeshGmsh.py @@ -63,6 +63,7 @@ class _ViewProviderFemMeshGmsh: return def doubleClicked(self, vobj): + FreeCADGui.activateWorkbench('FemWorkbench') # Group meshing is only active on active analysis, we should make sure the analysis the mesh belongs too is active gui_doc = FreeCADGui.getDocument(vobj.Object.Document) if not gui_doc.getInEdit(): From 97b08f2bd91be4bffe7e95b82db47cd2f9d3a31f Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 2 Jan 2017 13:44:02 +0100 Subject: [PATCH 009/144] FEM: netgen mesh obj, move a new obj inside an active analysis if there is one --- src/Mod/Fem/_CommandMeshNetgenFromShape.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Mod/Fem/_CommandMeshNetgenFromShape.py b/src/Mod/Fem/_CommandMeshNetgenFromShape.py index f497fcb349..ba5f8750b9 100644 --- a/src/Mod/Fem/_CommandMeshNetgenFromShape.py +++ b/src/Mod/Fem/_CommandMeshNetgenFromShape.py @@ -30,6 +30,7 @@ __url__ = "http://www.freecadweb.org" import FreeCAD from FemCommands import FemCommands import FreeCADGui +import FemGui from PySide import QtCore @@ -50,6 +51,9 @@ class _CommandMeshNetgenFromShape(FemCommands): if(sel[0].isDerivedFrom("Part::Feature")): FreeCADGui.doCommand("App.activeDocument().addObject('Fem::FemMeshShapeNetgenObject', '" + sel[0].Name + "_Mesh')") FreeCADGui.doCommand("App.activeDocument().ActiveObject.Shape = App.activeDocument()." + sel[0].Name) + if FemGui.getActiveAnalysis(): + FreeCADGui.addModule("FemGui") + FreeCADGui.doCommand("FemGui.getActiveAnalysis().Member = FemGui.getActiveAnalysis().Member + [App.ActiveDocument.ActiveObject]") FreeCADGui.doCommand("Gui.activeDocument().setEdit(App.ActiveDocument.ActiveObject.Name)") FreeCADGui.Selection.clearSelection() From 100e1bfc2d8ce68b7621fccd0dd97f05b88bd97d Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 2 Jan 2017 13:44:05 +0100 Subject: [PATCH 010/144] FEM: solver ccx task panel, typo --- src/Mod/Fem/TaskPanelFemSolverCalculix.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Fem/TaskPanelFemSolverCalculix.ui b/src/Mod/Fem/TaskPanelFemSolverCalculix.ui index 6c2c63452a..9201524420 100644 --- a/src/Mod/Fem/TaskPanelFemSolverCalculix.ui +++ b/src/Mod/Fem/TaskPanelFemSolverCalculix.ui @@ -1,7 +1,7 @@ - MechanicalMaterial - + SolverCalculix + 0 From 1d2857eb7655c297797d88722691702ac6bb5f64 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 2 Jan 2017 13:44:09 +0100 Subject: [PATCH 011/144] FEM: GUI, move create node set tool to mesh tools --- src/Mod/Fem/Gui/Workbench.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Mod/Fem/Gui/Workbench.cpp b/src/Mod/Fem/Gui/Workbench.cpp index 32e249f297..0b5327115a 100755 --- a/src/Mod/Fem/Gui/Workbench.cpp +++ b/src/Mod/Fem/Gui/Workbench.cpp @@ -71,14 +71,13 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "Fem_MeshNetgenFromShape" << "Fem_MeshGmshFromShape" << "Fem_MeshRegion" + //<< "Fem_CreateNodesSet" << "Separator" << "Fem_MechanicalMaterial" << "Fem_MaterialMechanicalNonlinear" << "Fem_BeamSection" << "Fem_ShellThickness" << "Separator" - //<< "Fem_CreateNodesSet" - << "Separator" << "Fem_ConstraintFixed" << "Fem_ConstraintDisplacement" << "Fem_ConstraintPlaneRotation" @@ -144,14 +143,13 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "Fem_MeshNetgenFromShape" << "Fem_MeshGmshFromShape" << "Fem_MeshRegion" + << "Fem_CreateNodesSet" << "Separator" << "Fem_MechanicalMaterial" << "Fem_MaterialMechanicalNonlinear" << "Fem_BeamSection" << "Fem_ShellThickness" << "Separator" - << "Fem_CreateNodesSet" - << "Separator" << "Fem_ConstraintFixed" << "Fem_ConstraintDisplacement" << "Fem_ConstraintPlaneRotation" From 739fe3ae39efab385cb430b057c9dc07480c7e96 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 2 Jan 2017 13:44:14 +0100 Subject: [PATCH 012/144] FEM: move modules MechanicalMaterial to a more general FemMaterial --- src/Mod/Fem/App/CMakeLists.txt | 12 +++++----- src/Mod/Fem/CMakeLists.txt | 12 +++++----- .../{MechanicalMaterial.py => FemMaterial.py} | 16 +++++++------- src/Mod/Fem/FemMeshTools.py | 2 +- src/Mod/Fem/Gui/CMakeLists.txt | 1 - src/Mod/Fem/Gui/Workbench.cpp | 4 ++-- src/Mod/Fem/InitGui.py | 2 +- ...calMaterial.ui => TaskPanelFemMaterial.ui} | 6 ++--- ...hanicalMaterial.py => _CommandMaterial.py} | 22 +++++++++---------- ..._MechanicalMaterial.py => _FemMaterial.py} | 10 ++++----- ...alMaterial.py => _TaskPanelFemMaterial.py} | 10 ++++----- ...aterial.py => _ViewProviderFemMaterial.py} | 12 +++++----- 12 files changed, 54 insertions(+), 55 deletions(-) rename src/Mod/Fem/{MechanicalMaterial.py => FemMaterial.py} (82%) rename src/Mod/Fem/{TaskPanelMechanicalMaterial.ui => TaskPanelFemMaterial.ui} (98%) rename src/Mod/Fem/{_CommandMechanicalMaterial.py => _CommandMaterial.py} (80%) rename src/Mod/Fem/{_MechanicalMaterial.py => _FemMaterial.py} (92%) rename src/Mod/Fem/{_TaskPanelMechanicalMaterial.py => _TaskPanelFemMaterial.py} (98%) rename src/Mod/Fem/{_ViewProviderMechanicalMaterial.py => _ViewProviderFemMaterial.py} (90%) diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index 4e577578cb..9502efc15e 100755 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -68,7 +68,7 @@ SET(FemScripts_SRCS _CommandConstraintSelfWeight.py _CommandFEMMesh2Mesh.py _CommandMaterialMechanicalNonlinear.py - _CommandMechanicalMaterial.py + _CommandMaterial.py _CommandMeshGmshFromShape.py _CommandMeshNetgenFromShape.py _CommandMeshRegion.py @@ -87,13 +87,13 @@ SET(FemScripts_SRCS _FemShellThickness.py _FemSolverCalculix.py _FemSolverZ88.py - _MechanicalMaterial.py + _FemMaterial.py _TaskPanelFemBeamSection.py _TaskPanelFemMeshGmsh.py _TaskPanelFemMeshRegion.py _TaskPanelFemShellThickness.py _TaskPanelFemSolverCalculix.py - _TaskPanelMechanicalMaterial.py + _TaskPanelFemMaterial.py _TaskPanelShowResult.py _ViewProviderFemBeamSection.py _ViewProviderFemConstraintSelfWeight.py @@ -103,7 +103,7 @@ SET(FemScripts_SRCS _ViewProviderFemShellThickness.py _ViewProviderFemSolverCalculix.py _ViewProviderFemSolverZ88.py - _ViewProviderMechanicalMaterial.py + _ViewProviderFemMaterial.py ccxDatReader.py ccxFrdReader.py convert2TetGen.py @@ -130,7 +130,7 @@ SET(FemScripts_SRCS FemTools.py FemToolsCcx.py FemToolsZ88.py - MechanicalMaterial.py + FemMaterial.py FemSelectionObserver.py TestFem.py z88DispReader.py @@ -139,7 +139,7 @@ SET(FemScripts_SRCS TaskPanelFemMeshRegion.ui TaskPanelFemShellThickness.ui TaskPanelFemSolverCalculix.ui - TaskPanelMechanicalMaterial.ui + TaskPanelFemMaterial.ui TaskPanelShowResult.ui ) #SOURCE_GROUP("Scripts" FILES ${FemScripts_SRCS}) diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 6f3c59f51f..6a318ae8c8 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -81,12 +81,12 @@ INSTALL( _ViewProviderFemConstraintSelfWeight.py _CommandConstraintSelfWeight.py - MechanicalMaterial.py - _MechanicalMaterial.py - _ViewProviderMechanicalMaterial.py - _CommandMechanicalMaterial.py - _TaskPanelMechanicalMaterial.py - TaskPanelMechanicalMaterial.ui + FemMaterial.py + _FemMaterial.py + _ViewProviderFemMaterial.py + _CommandMaterial.py + _TaskPanelFemMaterial.py + TaskPanelFemMaterial.ui FemMaterialMechanicalNonlinear.py _FemMaterialMechanicalNonlinear.py diff --git a/src/Mod/Fem/MechanicalMaterial.py b/src/Mod/Fem/FemMaterial.py similarity index 82% rename from src/Mod/Fem/MechanicalMaterial.py rename to src/Mod/Fem/FemMaterial.py index a54ab9ef2a..de91e29651 100644 --- a/src/Mod/Fem/MechanicalMaterial.py +++ b/src/Mod/Fem/FemMaterial.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -__title__ = "MechanicalMaterial" +__title__ = "FemMaterial" __author__ = "Juergen Riegel, Bernd Hahnebach" __url__ = "http://www.freecadweb.org" @@ -28,17 +28,17 @@ __url__ = "http://www.freecadweb.org" # @{ import FreeCAD -import _MechanicalMaterial +import _FemMaterial -def makeMechanicalMaterial(name): - '''makeMaterial(name): makes an Material - name there fore is a material name or an file name for a FCMat file''' +def makeFemMaterial(name): + '''makeFemMaterial(name): makes an FEM Material + ''' obj = FreeCAD.ActiveDocument.addObject("App::MaterialObjectPython", name) - _MechanicalMaterial._MechanicalMaterial(obj) + _FemMaterial._FemMaterial(obj) if FreeCAD.GuiUp: - import _ViewProviderMechanicalMaterial - _ViewProviderMechanicalMaterial._ViewProviderMechanicalMaterial(obj.ViewObject) + import _ViewProviderFemMaterial + _ViewProviderFemMaterial._ViewProviderFemMaterial(obj.ViewObject) # FreeCAD.ActiveDocument.recompute() return obj diff --git a/src/Mod/Fem/FemMeshTools.py b/src/Mod/Fem/FemMeshTools.py index 4d76e2ed32..3cc1b6c4c7 100644 --- a/src/Mod/Fem/FemMeshTools.py +++ b/src/Mod/Fem/FemMeshTools.py @@ -402,7 +402,7 @@ def get_femelement_sets_from_group_data(femmesh, fem_objects): def get_elset_short_name(obj, i): - if hasattr(obj, "Proxy") and obj.Proxy.Type == 'MechanicalMaterial': + if hasattr(obj, "Proxy") and obj.Proxy.Type == 'FemMaterial': return 'Mat' + str(i) elif hasattr(obj, "Proxy") and obj.Proxy.Type == 'FemBeamSection': return 'Beam' + str(i) diff --git a/src/Mod/Fem/Gui/CMakeLists.txt b/src/Mod/Fem/Gui/CMakeLists.txt index 1f6779479a..ddf24d761f 100755 --- a/src/Mod/Fem/Gui/CMakeLists.txt +++ b/src/Mod/Fem/Gui/CMakeLists.txt @@ -344,7 +344,6 @@ fc_target_copy_resource(FemGui ${CMAKE_SOURCE_DIR}/src/Mod/Fem ${CMAKE_BINARY_DIR}/Mod/Fem InitGui.py - TaskPanelMechanicalMaterial.ui ) SET(FemGuiIcon_SVG diff --git a/src/Mod/Fem/Gui/Workbench.cpp b/src/Mod/Fem/Gui/Workbench.cpp index 0b5327115a..63b25d2583 100755 --- a/src/Mod/Fem/Gui/Workbench.cpp +++ b/src/Mod/Fem/Gui/Workbench.cpp @@ -73,7 +73,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "Fem_MeshRegion" //<< "Fem_CreateNodesSet" << "Separator" - << "Fem_MechanicalMaterial" + << "Fem_Material" << "Fem_MaterialMechanicalNonlinear" << "Fem_BeamSection" << "Fem_ShellThickness" @@ -145,7 +145,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "Fem_MeshRegion" << "Fem_CreateNodesSet" << "Separator" - << "Fem_MechanicalMaterial" + << "Fem_Material" << "Fem_MaterialMechanicalNonlinear" << "Fem_BeamSection" << "Fem_ShellThickness" diff --git a/src/Mod/Fem/InitGui.py b/src/Mod/Fem/InitGui.py index ae33e45f2f..dbe0ce629d 100644 --- a/src/Mod/Fem/InitGui.py +++ b/src/Mod/Fem/InitGui.py @@ -58,7 +58,7 @@ class FemWorkbench (Workbench): import _CommandAnalysis import _CommandShellThickness import _CommandBeamSection - import _CommandMechanicalMaterial + import _CommandMaterial import _CommandMaterialMechanicalNonlinear import _CommandSolverCalculix import _CommandSolverZ88 diff --git a/src/Mod/Fem/TaskPanelMechanicalMaterial.ui b/src/Mod/Fem/TaskPanelFemMaterial.ui similarity index 98% rename from src/Mod/Fem/TaskPanelMechanicalMaterial.ui rename to src/Mod/Fem/TaskPanelFemMaterial.ui index a836fec344..9c468f3a11 100644 --- a/src/Mod/Fem/TaskPanelMechanicalMaterial.ui +++ b/src/Mod/Fem/TaskPanelFemMaterial.ui @@ -1,7 +1,7 @@ - ThermalMechanicalMaterial - + FemMaterial + 0 @@ -11,7 +11,7 @@ - Mechanical material + FEM material diff --git a/src/Mod/Fem/_CommandMechanicalMaterial.py b/src/Mod/Fem/_CommandMaterial.py similarity index 80% rename from src/Mod/Fem/_CommandMechanicalMaterial.py rename to src/Mod/Fem/_CommandMaterial.py index 8976d60694..840f6718c6 100644 --- a/src/Mod/Fem/_CommandMechanicalMaterial.py +++ b/src/Mod/Fem/_CommandMaterial.py @@ -20,11 +20,11 @@ # * * # *************************************************************************** -__title__ = "_CommandMechanicalMaterial" +__title__ = "_CommandMaterial" __author__ = "Juergen Riegel, Bernd Hahnebach" __url__ = "http://www.freecadweb.org" -## @package CommandMechanicalMaterial +## @package CommandMaterial # \ingroup FEM import FreeCAD @@ -34,25 +34,25 @@ import FemGui from PySide import QtCore -class _CommandMechanicalMaterial(FemCommands): - "the Fem_MechanicalMaterial command definition" +class _CommandMaterial(FemCommands): + "the Fem_Material command definition" def __init__(self): - super(_CommandMechanicalMaterial, self).__init__() + super(_CommandMaterial, self).__init__() self.resources = {'Pixmap': 'fem-material', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Fem_MechanicalMaterial", "Mechanical material"), + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Fem_Material", "FEM material"), 'Accel': "M, M", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Fem_MechanicalMaterial", "Creates a mechanical material")} + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Fem_Material", "Creates a FEM material")} self.is_active = 'with_analysis' def Activated(self): femDoc = FemGui.getActiveAnalysis().Document if FreeCAD.ActiveDocument is not femDoc: FreeCADGui.setActiveDocument(femDoc) - FreeCAD.ActiveDocument.openTransaction("Create MechanicalMaterial") - FreeCADGui.addModule("MechanicalMaterial") - FreeCADGui.doCommand("MechanicalMaterial.makeMechanicalMaterial('MechanicalMaterial')") + FreeCAD.ActiveDocument.openTransaction("Create Material") + FreeCADGui.addModule("FemMaterial") + FreeCADGui.doCommand("FemMaterial.makeFemMaterial('FemMaterial')") FreeCADGui.doCommand("App.activeDocument()." + FemGui.getActiveAnalysis().Name + ".Member = App.activeDocument()." + FemGui.getActiveAnalysis().Name + ".Member + [App.ActiveDocument.ActiveObject]") FreeCADGui.doCommand("Gui.activeDocument().setEdit(App.ActiveDocument.ActiveObject.Name)") -FreeCADGui.addCommand('Fem_MechanicalMaterial', _CommandMechanicalMaterial()) +FreeCADGui.addCommand('Fem_Material', _CommandMaterial()) diff --git a/src/Mod/Fem/_MechanicalMaterial.py b/src/Mod/Fem/_FemMaterial.py similarity index 92% rename from src/Mod/Fem/_MechanicalMaterial.py rename to src/Mod/Fem/_FemMaterial.py index 48f72dd113..9dd0ab4bcd 100644 --- a/src/Mod/Fem/_MechanicalMaterial.py +++ b/src/Mod/Fem/_FemMaterial.py @@ -20,20 +20,20 @@ # * * # *************************************************************************** -__title__ = "_MechanicalMaterial" +__title__ = "FemMaterial" __author__ = "Juergen Riegel, Bernd Hahnebach" __url__ = "http://www.freecadweb.org" -## @package MechanicalMaterial +## @package FemMaterial # \ingroup FEM -class _MechanicalMaterial: - "The Material object" +class _FemMaterial: + "The FEM Material object" def __init__(self, obj): obj.addProperty("App::PropertyLinkSubList", "References", "Material", "List of material shapes") obj.Proxy = self - self.Type = "MechanicalMaterial" + self.Type = "FemMaterial" def execute(self, obj): return diff --git a/src/Mod/Fem/_TaskPanelMechanicalMaterial.py b/src/Mod/Fem/_TaskPanelFemMaterial.py similarity index 98% rename from src/Mod/Fem/_TaskPanelMechanicalMaterial.py rename to src/Mod/Fem/_TaskPanelFemMaterial.py index 21f706f1f5..0b87cafc8d 100644 --- a/src/Mod/Fem/_TaskPanelMechanicalMaterial.py +++ b/src/Mod/Fem/_TaskPanelFemMaterial.py @@ -20,11 +20,11 @@ # * * # *************************************************************************** -__title__ = "_TaskPanelMechanicalMaterial" +__title__ = "_TaskPanelFemMaterial" __author__ = "Juergen Riegel, Bernd Hahnebach" __url__ = "http://www.freecadweb.org" -## @package TaskPanelMechanicalMaterial +## @package TaskPanelFemMaterial # \ingroup FEM import FreeCAD @@ -34,8 +34,8 @@ from PySide import QtCore import Units -class _TaskPanelMechanicalMaterial: - '''The editmode TaskPanel for MechanicalMaterial objects''' +class _TaskPanelFemMaterial: + '''The editmode TaskPanel for FemMaterial objects''' def __init__(self, obj): FreeCADGui.Selection.clearSelection() self.sel_server = None @@ -50,7 +50,7 @@ class _TaskPanelMechanicalMaterial: self.get_references() self.references_shape_type = None - self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Fem/TaskPanelMechanicalMaterial.ui") + self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Fem/TaskPanelFemMaterial.ui") QtCore.QObject.connect(self.form.pushButton_MatWeb, QtCore.SIGNAL("clicked()"), self.goMatWeb) QtCore.QObject.connect(self.form.cb_materials, QtCore.SIGNAL("activated(int)"), self.choose_material) QtCore.QObject.connect(self.form.input_fd_young_modulus, QtCore.SIGNAL("valueChanged(double)"), self.ym_changed) diff --git a/src/Mod/Fem/_ViewProviderMechanicalMaterial.py b/src/Mod/Fem/_ViewProviderFemMaterial.py similarity index 90% rename from src/Mod/Fem/_ViewProviderMechanicalMaterial.py rename to src/Mod/Fem/_ViewProviderFemMaterial.py index 236c40b2e6..e95e6b919e 100644 --- a/src/Mod/Fem/_ViewProviderMechanicalMaterial.py +++ b/src/Mod/Fem/_ViewProviderFemMaterial.py @@ -20,19 +20,19 @@ # * * # *************************************************************************** -__title__ = "_ViewProviderMechanicalMaterial" +__title__ = "_ViewProviderFemMaterial" __author__ = "Juergen Riegel, Bernd Hahnebach" __url__ = "http://www.freecadweb.org" -## @package ViewProviderMechanicalMaterial +## @package _ViewProviderFemMaterial # \ingroup FEM import FreeCAD import FreeCADGui -class _ViewProviderMechanicalMaterial: - "A View Provider for the MechanicalMaterial object" +class _ViewProviderFemMaterial: + "A View Provider for the FemMaterial object" def __init__(self, vobj): vobj.Proxy = self @@ -51,8 +51,8 @@ class _ViewProviderMechanicalMaterial: return def setEdit(self, vobj, mode): - import _TaskPanelMechanicalMaterial - taskd = _TaskPanelMechanicalMaterial._TaskPanelMechanicalMaterial(self.Object) + import _TaskPanelFemMaterial + taskd = _TaskPanelFemMaterial._TaskPanelFemMaterial(self.Object) taskd.obj = vobj.Object FreeCADGui.Control.showDialog(taskd) return True From 846637ebc2be292c82e1e21294fe60ff6164d0ad Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 2 Jan 2017 13:44:18 +0100 Subject: [PATCH 013/144] FEM: adapt FEM examples to new material module name --- data/examples/FemCalculixCantilever2D.FCStd | Bin 23622 -> 23657 bytes data/examples/FemCalculixCantilever3D.FCStd | Bin 30436 -> 30538 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/examples/FemCalculixCantilever2D.FCStd b/data/examples/FemCalculixCantilever2D.FCStd index 7029b54be6c733b839994c65427fa31c752d128e..22bc6f4ef549897eba050e3da86b6b2987fd64c2 100644 GIT binary patch delta 17461 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 Mb#`=7fE`%^08Hz7Bme*a diff --git a/data/examples/FemCalculixCantilever3D.FCStd b/data/examples/FemCalculixCantilever3D.FCStd index f001970390c16ba8470004cbadfa18bba931d433..6a2509b01f26cf33fe1ae0b1ff37df57ea67195b 100644 GIT binary patch delta 7654 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 D?gF^j From 6ab6603f2eff07adabb85a4496f7b6da7f6a6055 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Mon, 2 Jan 2017 13:44:26 +0100 Subject: [PATCH 014/144] FEM: fix unit tests in the regard of new material module name --- src/Mod/Fem/TestFem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Fem/TestFem.py b/src/Mod/Fem/TestFem.py index 4a0f4a6ca3..cfa75bffd6 100644 --- a/src/Mod/Fem/TestFem.py +++ b/src/Mod/Fem/TestFem.py @@ -29,7 +29,7 @@ import FemToolsCcx import FreeCAD import FemAnalysis import FemSolverCalculix -import MechanicalMaterial +import FemMaterial import csv import tempfile import unittest @@ -109,7 +109,7 @@ class FemTest(unittest.TestCase): self.active_doc.recompute() def create_new_material(self): - self.new_material_object = MechanicalMaterial.makeMechanicalMaterial('MechanicalMaterial') + self.new_material_object = FemMaterial.makeFemMaterial('MechanicalMaterial') mat = self.new_material_object.Material mat['Name'] = "Steel-Generic" mat['YoungsModulus'] = "200000 MPa" @@ -362,7 +362,7 @@ class TherMechFemTest(unittest.TestCase): self.active_doc.recompute() def create_new_material(self): - self.new_material_object = MechanicalMaterial.makeMechanicalMaterial('MechanicalMaterial') + self.new_material_object = FemMaterial.makeFemMaterial('MechanicalMaterial') mat = self.new_material_object.Material mat['Name'] = "Steel-Generic" mat['YoungsModulus'] = "200000 MPa" From 88e9d26ecb0740edc162c2330fb94978e03690ea Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 29 Nov 2016 02:40:29 -0800 Subject: [PATCH 015/144] Added HoldingTags dressup with tests, using PathGeom and PathTestUtils. --- src/Mod/Path/CMakeLists.txt | 2 + src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Gui/Resources/panels/HoldingTagsEdit.ui | 240 +++++ src/Mod/Path/InitGui.py | 5 +- .../PathScripts/PathDressupHoldingTags.py | 946 ++++++++++++++++++ src/Mod/Path/PathTests/PathTestUtils.py | 48 +- .../PathTests/TestPathDressupHoldingTags.py | 546 ++++++++++ src/Mod/Path/TestPathApp.py | 5 + 8 files changed, 1785 insertions(+), 8 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui create mode 100644 src/Mod/Path/PathScripts/PathDressupHoldingTags.py create mode 100644 src/Mod/Path/PathTests/TestPathDressupHoldingTags.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 4a0d5edfc8..701ba44b1e 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -26,6 +26,7 @@ SET(PathScripts_SRCS PathScripts/PathDressup.py PathScripts/PathDressupDogbone.py PathScripts/PathDressupDragknife.py + PathScripts/PathDressupHoldingTags.py PathScripts/PathDrilling.py PathScripts/PathEngrave.py PathScripts/PathFacePocket.py @@ -73,6 +74,7 @@ SET(PathScripts_SRCS PathScripts/slic3r_pre.py PathTests/PathTestUtils.py PathTests/TestPathDepthParams.py + PathTests/TestPathDressupHoldingTags.py PathTests/TestPathGeom.py PathTests/TestPathPost.py PathTests/__init__.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index bb147e1404..5be33f8eaf 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -53,6 +53,7 @@ panels/DogboneEdit.ui panels/DrillingEdit.ui panels/EngraveEdit.ui + panels/HoldingTagsEdit.ui panels/JobEdit.ui panels/MillFaceEdit.ui panels/PocketEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui new file mode 100644 index 0000000000..465b0ad6ee --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -0,0 +1,240 @@ + + + TaskPanel + + + + 0 + 0 + 352 + 387 + + + + Holding Tags + + + + + + QFrame::NoFrame + + + 0 + + + + + 0 + 0 + 334 + 311 + + + + Tags + + + + + + + 0 + 0 + + + + true + + + 80 + + + false + + + + X + + + + + Y + + + + + Width + + + + + Height + + + + + Angle + + + + + + + + + + + Delete + + + + + + + Disable + + + + + + + Add + + + + + + + + + + + + 0 + 0 + 334 + 311 + + + + Generate + + + + + + Width + + + + + + + <html><head/><body><p>Width of each tag.</p></body></html> + + + + + + + Height + + + + + + + <html><head/><body><p>The height of the holding tag measured from the bottom of the path. By default this is set to the (estimated) height of the path.</p></body></html> + + + + + + + true + + + Angle + + + + + + + true + + + <html><head/><body><p>Angle of tag walls.</p></body></html> + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Ok + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Layout + + + + + + Count + + + + + + + <html><head/><body><p>Enter the number of tags you wish to have.</p><p><br/></p><p>Note that sometimes it's necessary to enter a larger than desired count number and disable the ones tags you don't want in order to get the holding tag layout you want.</p></body></html> + + + + + + + Spacing + + + + + + + + + + + + + Auto Apply + + + + + + + + + + + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index cc8405ef8f..e046eb5421 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -73,6 +73,7 @@ class PathWorkbench (Workbench): from PathScripts import PathProfileEdges from PathScripts import PathDressupDogbone from PathScripts import PathMillFace + from PathScripts import PathDressupHoldingTags import PathCommands # build commands list @@ -82,7 +83,7 @@ class PathWorkbench (Workbench): twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace"] threedopcmdlist = ["Path_Surfacing"] modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ] - dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife"] + dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife", "PathDressup_HoldingTags"] extracmdlist = ["Path_SelectLoop"] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"] @@ -134,7 +135,7 @@ class PathWorkbench (Workbench): if len(FreeCADGui.Selection.getSelection()) == 1: if FreeCADGui.Selection.getSelection()[0].isDerivedFrom("Path::Feature"): self.appendContextMenu("", ["Path_Inspect"]) - if FreeCADGui.Selection.getSelection()[0].Name in ["Profile", "Contour"]: + if "Profile" or "Contour" in FreeCADGui.Selection.getSelection()[0].Name: self.appendContextMenu("", ["Add_Tag"]) self.appendContextMenu("", ["Set_StartPoint"]) self.appendContextMenu("", ["Set_EndPoint"]) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py new file mode 100644 index 0000000000..4c13da8ea6 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -0,0 +1,946 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2016 sliptonic * +# * * +# * 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 FreeCADGui +import DraftGeomUtils +import Path +import Part +import math + +from PathScripts import PathUtils +from PathScripts.PathGeom import * +from PySide import QtCore, QtGui + +"""Holding Tags Dressup object and FreeCAD command""" + +# Qt tanslation handling +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) + +debugDressup = True + +def debugMarker(vector, label, color = None, radius = 0.5): + if debugDressup: + obj = FreeCAD.ActiveDocument.addObject("Part::Sphere", label) + obj.Label = label + obj.Radius = radius + obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0)) + if color: + obj.ViewObject.ShapeColor = color + +movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] +movestraight = ['G1', 'G01'] +movecw = ['G2', 'G02'] +moveccw = ['G3', 'G03'] +movearc = movecw + moveccw + +slack = 0.0000001 + +def pathCommandForEdge(edge): + pt = edge.Curve.EndPoint + params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} + if type(edge.Curve) == Part.Line: + return Part.Command('G1', params) + + p1 = edge.Curve.StartPoint + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = pt + if Side.Left == Side.of(p2 - p1, p3 - p2): + cmd = 'G3' + else: + cmd = 'G2' + offset = pt1 - edge.Curve.Center + params.update({'I': offset.x, 'J': offset.y, 'K': offset.z}) + return Part.Command(cmd, params) + + +class Tag: + + @classmethod + def FromString(cls, string): + try: + t = eval(string) + return Tag(t[0], t[1], t[2], t[3], t[4], t[5]) + except: + return None + + def __init__(self, x, y, width, height, angle, enabled=True, z=None): + self.x = x + self.y = y + self.width = math.fabs(width) + self.height = math.fabs(height) + self.actualHeight = self.height + self.angle = math.fabs(angle) + self.enabled = enabled + if z is not None: + self.createSolidsAt(z) + + def toString(self): + return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) + + def originAt(self, z): + return FreeCAD.Vector(self.x, self.y, z) + + def bottom(self): + return self.z + + def top(self): + return self.z + self.actualHeight + + def centerLine(self): + return Part.Line(self.originAt(self.bottom()), self.originAt(self.top())) + + def createSolidsAt(self, z): + self.z = z + r1 = self.width / 2 + height = self.height + if self.angle == 90 and height > 0: + self.solid = Part.makeCylinder(r1, height) + self.core = self.solid.copy() + elif self.angle > 0.0 and height > 0.0: + tangens = math.tan(math.radians(self.angle)) + dr = height / tangens + if dr < r1: + r2 = r1 - dr + self.core = Part.makeCylinder(r2, height) + else: + r2 = 0 + height = r1 * tangens + self.core = None + self.actualHeight = height + self.solid = Part.makeCone(r1, r2, height) + else: + # degenerated case - no tag + self.solid = Part.makeSphere(r1 / 10000) + self.core = None + self.solid.translate(self.originAt(z)) + if self.core: + self.core.translate(self.originAt(z)) + + class Intersection: + # An intersection with a tag has 4 markant points, where one might be optional. + # + # P1---P2 P1---P2 P2 + # | | / \ /\ + # | | / \ / \ + # | | / \ / \ + # ---P0 P3--- ---P0 P3--- ---P0 P3--- + # + # If no intersection occured the Intersection can be viewed as being + # at P3 with no additional edges. + P0 = 2 + P1 = 3 + P2 = 4 + P3 = 5 + + def __init__(self, tag): + self.tag = tag + self.state = self.P3 + self.edges = [] + self.tail = None + + def isComplete(self): + return self.state == self.P3 + + def moveEdgeToPlateau(self, edge): + if type(edge.Curve) is Part.Line: + pt1 = edge.Curve.StartPoint + pt2 = edge.Curve.EndPoint + pt1.z = self.tag.top() + pt2.z = self.tag.top() + #print("\nplateau= %s - %s" %(pt1, pt2)) + return Part.Edge(Part.Line(pt1, pt2)) + + def intersectP0Core(self, edge): + #print("----- P0 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.StartPoint) + if i: + if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + # if P0 and P1 are the same, we need to insert a segment for the rise + #print("------- insert vertical rise (%s)" % i) + self.edges.append(Part.Edge(Part.Line(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) + self.p1 = i + self.state = self.P1 + return edge + if PathGeom.pointsCoincide(i, edge.Curve.EndPoint): + #print("------- consumed (%s)" % i) + e = edge + tail = None + else: + #print("------- split at (%s)" % i) + e, tail = self.tag.splitEdgeAt(edge, i) + self.p1 = e.Curve.EndPoint + self.edges.append(self.tag.mapEdgeToSolid(e)) + self.state = self.P1 + return tail + # no intersection, the entire edge fits between P0 and P1 + #print("------- no intersection") + self.edges.append(self.tag.mapEdgeToSolid(edge)) + return None + + def intersectP0(self, edge): + if self.tag.core: + return self.intersectP0Core(edge) + # if we have no core the tip is the origin of the Tag + line = Part.Edge(self.tag.centerLine()) + i = DraftGeomUtils.findIntersection(line, edge) + if i: + if PathGeom.pointsCoincide(i[0], edge.Curve.EndPoint): + e = edge + tail = None + else: + e, tail = self.tag.splitEdgeAt(edge, i[0]) + self.state = self.P2 # P1 and P2 are identical for triangular tags + self.p1 = i[0] + self.p2 = i[0] + else: + e = edge + tail = None + self.edges.append(self.tag.mapEdgeToSolid(e)) + return tail + + + + def intersectP1(self, edge): + #print("----- P1 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.EndPoint) + if i: + if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + self.edges.append(self.tag.mapEdgeToSolid(edge)) + return self + if PathGeom.pointsCoincide(i, edge.Curve.EndPoint): + e = edge + tail = None + else: + e, tail = self.tag.splitEdgeAt(edge, i) + self.p2 = e.Curve.EndPoint + self.state = self.P2 + else: + e = edge + tail = None + self.edges.append(self.moveEdgeToPlateau(e)) + return tail + + def intersectP2(self, edge): + #print("----- P2 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.Curve.EndPoint) + if i: + if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + #print("------- insert exit plunge (%s)" % i) + self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) + e = None + tail = edge + elif PathGeom.pointsCoincide(i, edge.Curve.EndPoint): + #print("------- entire segment added (%s)" % i) + e = edge + tail = None + else: + e, tail = self.tag.splitEdgeAt(edge, i) + #if tail: + # print("----- P3 (%s - %s)" % (tail.Curve.StartPoint, tail.Curve.EndPoint)) + #else: + # print("----- P3 (---)") + self.state = self.P3 + self.tail = tail + else: + e = edge + tail = None + if e: + self.edges.append(self.tag.mapEdgeToSolid(e)) + return tail + + def intersect(self, edge): + #print("") + #print(" >>> (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + if edge and self.state == self.P0: + edge = self.intersectP0(edge) + if edge and self.state == self.P1: + edge = self.intersectP1(edge) + if edge and self.state == self.P2: + edge = self.intersectP2(edge) + return self + + + def splitEdgeAt(self, edge, pt): + p = edge.Curve.parameter(pt) + wire = edge.split(p) + return wire.Edges + + def mapEdgeToSolid(self, edge): + #print("mapEdgeToSolid: (%s %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + p1a = edge.Curve.StartPoint + p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) + e1 = Part.Edge(Part.Line(p1a, p1b)) + p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection + #print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) + + p2a = edge.Curve.EndPoint + p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) + e2 = Part.Edge(Part.Line(p2a, p2b)) + p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection + #print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) + + if type(edge.Curve) == Part.Line: + return Part.Edge(Part.Line(p1, p2)) + + def filterIntersections(self, pts, face): + if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: + return filter(lambda pt: pt.z >= self.bottom() and pt.z <= self.top(), pts) + if type(face.Surface) == Part.Plane: + c = face.Edges[0].Curve + if (type(c) == Part.Circle): + return filter(lambda pt: (pt - c.Center).Length <= c.Radius, pts) + print("==== we got a %s" % face.Surface) + + + def nextIntersectionClosestTo(self, edge, solid, refPt): + pts = [] + for index, face in enumerate(solid.Faces): + i = edge.Curve.intersect(face.Surface)[0] + ps = self.filterIntersections([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i], face) + pts.extend(ps) + if pts: + closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] + #print("--pts: %s -> %s" % (pts, closest)) + return closest + return None + + def intersect(self, edge): + inters = self.Intersection(self) + if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top(): + i = self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(edge.FirstParameter)) + if i: + inters.state = self.Intersection.P0 + inters.p0 = i + if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + inters.edges.append(edge) + return inters + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + tail = edge + else: + e,tail = self.splitEdgeAt(edge, i) + inters.edges.append(e) + return inters.intersect(tail) + # if we get here there is no intersection with the tag + inters.state = self.Intersection.P3 + inters.tail = edge + return inters + +class PathData: + def __init__(self, obj): + self.obj = obj + self.wire = PathGeom.wireForPath(obj.Base.Path) + self.edges = wire.Edges + self.base = self.findBottomWire(self.edges) + # determine overall length + self.length = self.base.Length + + def findBottomWire(self, edges): + (minZ, maxZ) = self.findZLimits(edges) + self.minZ = minZ + self.maxZ = maxZ + bottom = [e for e in edges if e.Vertexes[0].Point.z == minZ and e.Vertexes[1].Point.z == minZ] + wire = Part.Wire(bottom) + if wire.isClosed(): + return Part.Wire(self.sortedBase(bottom)) + # if we get here there are already holding tags, or we're not looking at a profile + # let's try and insert the missing pieces - another day + raise ValueError("Selected path doesn't seem to be a Profile operation.") + + def sortedBase(self, base): + # first find the exit point, where base wire is closed + edges = [e for e in self.edges if e.Curve.StartPoint.z == self.minZ and e.Curve.EndPoint.z != self.maxZ] + exit = sorted(edges, key=lambda e: -e.Curve.EndPoint.z)[0] + pt = exit.Curve.StartPoint + # then find the first base edge, and sort them until done + ordered = [] + while base: + edge = [e for e in base if e.Curve.StartPoint == pt][0] + ordered.append(edge) + base.remove(edge) + pt = edge.Curve.EndPoint + return ordered + + + def findZLimits(self, edges): + # not considering arcs and spheres in Z direction, find the highes and lowest Z values + minZ = edges[0].Vertexes[0].Point.z + maxZ = minZ + for e in edges: + for v in e.Vertexes: + if v.Point.z < minZ: + minZ = v.Point.z + if v.Point.z > maxZ: + maxZ = v.Point.z + return (minZ, maxZ) + + def shortestAndLongestPathEdge(self): + edges = sorted(self.base.Edges, key=lambda e: e.Length) + return (edges[0], edges[-1]) + + def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): + #print("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) + #for e in self.base.Edges: + # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) + + if spacing: + tagDistance = spacing + else: + if count: + tagDistance = self.base.Length / count + else: + tagDistance = self.base.Length / 4 + if width: + W = width + else: + W = self.tagWidth() + if height: + H = height + else: + H = self.tagHeight() + + + # start assigning tags on the longest segment + (shortestEdge, longestEdge) = self.shortestAndLongestPathEdge() + startIndex = 0 + for i in range(0, len(self.base.Edges)): + edge = self.base.Edges[i] + if edge.Length == longestEdge.Length: + startIndex = i + break + + startEdge = self.base.Edges[startIndex] + startCount = int(startEdge.Length / tagDistance) + if (longestEdge.Length - shortestEdge.Length) > shortestEdge.Length: + startCount = int(startEdge.Length / tagDistance) + 1 + + lastTagLength = (startEdge.Length + (startCount - 1) * tagDistance) / 2 + currentLength = startEdge.Length + + minLength = min(2. * W, longestEdge.Length) + + #print("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f)" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length)) + #print(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) + #print(" -> lastTagLength=%.2f)" % lastTagLength) + #print(" -> currentLength=%.2f)" % currentLength) + + edgeDict = { startIndex: startCount } + + for i in range(startIndex + 1, len(self.base.Edges)): + edge = self.base.Edges[i] + (currentLength, lastTagLength) = self.processEdge(i, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict) + for i in range(0, startIndex): + edge = self.base.Edges[i] + (currentLength, lastTagLength) = self.processEdge(i, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict) + + tags = [] + + for (i, count) in edgeDict.iteritems(): + edge = self.base.Edges[i] + #print(" %d: %d" % (i, count)) + #debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) + #debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) + distance = (edge.LastParameter - edge.FirstParameter) / count + for j in range(0, count): + tag = edge.Curve.value((j+0.5) * distance) + tags.append(Tag(tag.x, tag.y, W, H, angle, True)) + + return tags + + def processEdge(self, index, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict): + tagCount = 0 + currentLength += edge.Length + if edge.Length > minLength: + while lastTagLength + tagDistance < currentLength: + tagCount += 1 + lastTagLength += tagDistance + if tagCount > 0: + #print(" index=%d -> count=%d" % (index, tagCount)) + edgeDict[index] = tagCount + #else: + #print(" skipping=%-2d (%.2f)" % (index, edge.Length)) + + return (currentLength, lastTagLength) + + def tagHeight(self): + return self.maxZ - self.minZ + + def tagWidth(self): + return self.shortestAndLongestPathEdge()[1].Length / 10 + + def tagAngle(self): + return 90 + + def pathLength(self): + return self.base.Length + + def sortedTags(self, tags): + ordered = [] + for edge in self.base.Edges: + ts = [t for t in tags if DraftGeomUtils.isPtOnEdge(t.originAt(self.minZ), edge)] + for t in sorted(ts, key=lambda t: (t.originAt(self.minZ) - edge.Curve.StartPoint).Length): + tags.remove(t) + ordered.append(t) + if tags: + raise ValueError("There's something really wrong here") + return ordered + + +class ObjectDressup: + + def __init__(self, obj): + self.obj = obj + obj.addProperty("App::PropertyLink", "Base","Base", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "The base path to modify")) + obj.addProperty("App::PropertyStringList", "Tags", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_holdingTags", "Inserted tags")) + obj.setEditorMode("Tags", 2) + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): + return self.pathData.generateTags(obj, count, width, height, angle, spacing) + + + def tagIntersection(self, face, edge): + p1 = edge.Curve.StartPoint + pts = edge.Curve.intersect(face.Surface) + if pts[0]: + closest = sorted(pts[0], key=lambda pt: (pt - p1).Length)[0] + return closest + return None + + def createPath(self, edges, tagSolids): + commands = [] + i = 0 + while i != len(edges): + edge = edges[i] + while edge: + for solid in tagSolids: + for face in solid.Faces: + pt = self.tagIntersection(face, edge) + if pt: + if pt == edge.Curve.StartPoint: + pt + elif pt != edge.Curve.EndPoint: + parameter = edge.Curve.parameter(pt) + wire = edge.split(parameter) + commands.append(pathCommandForEdge(wire.Edges[0])) + edge = wire.Edges[1] + break; + else: + commands.append(pathCommandForEdge(edge)) + edge = None + i += 1 + break + if not edge: + break + if edge: + commands.append(pathCommandForEdge(edge)) + edge = None + return self.obj.Path + + + def execute(self, obj): + if not obj.Base: + return + if not obj.Base.isDerivedFrom("Path::Feature"): + return + if not obj.Base.Path: + return + if not obj.Base.Path.Commands: + return + + pathData = self.setup(obj) + if not pathData: + print("execute - no pathData") + return + + if hasattr(obj, 'Tags') and obj.Tags: + if self.fingerprint == obj.Tags: + print("execute - cache valid") + return + print("execute - tags from property") + tags = [Tag.FromString(tag) for tag in obj.Tags] + else: + print("execute - default tags") + tags = self.generateTags(obj, 4.) + + if not tags: + print("execute - no tags") + self.tags = [] + obj.Path = obj.Base.Path + return + + tagID = 0 + for tag in tags: + tagID += 1 + if tag.enabled: + #print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) + debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + + tags = pathData.sortedTags(tags) + for tag in tags: + tag.createSolidsAt(pathData.minZ) + + self.fingerprint = [tag.toString() for tag in tags] + self.tags = tags + + #obj.Path = self.createPath(pathData.edges, tags) + obj.Path = self.Base.Path + + def setTags(self, obj, tags): + obj.Tags = [tag.toString() for tag in tags] + self.execute(obj) + + def getTags(self, obj): + if hasattr(self, 'tags'): + return self.tags + return self.setup(obj).generateTags(obj, 4) + + def setup(self, obj): + if not hasattr(self, "pathData") or not self.pathData: + try: + pathData = PathData(obj) + except ValueError: + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) + return None + + ## setup the object's properties, in case they're not set yet + #obj.Count = self.tagCount(obj) + #obj.Angle = self.tagAngle(obj) + #obj.Blacklist = self.tagBlacklist(obj) + + # if the heigt isn't set, use the height of the path + #if not hasattr(obj, "Height") or not obj.Height: + # obj.Height = pathData.maxZ - pathData.minZ + # try and take an educated guess at the width + #if not hasattr(obj, "Width") or not obj.Width: + # width = sorted(pathData.base.Edges, key=lambda e: -e.Length)[0].Length / 10 + # while obj.Count > len([e for e in pathData.base.Edges if e.Length > 3*width]): + # width = widht / 2 + # obj.Width = width + + # and the tool radius, not sure yet if it's needed + #self.toolRadius = 5 + #toolLoad = PathUtils.getLastToolLoad(obj) + #if toolLoad is None or toolLoad.ToolNumber == 0: + # self.toolRadius = 5 + #else: + # tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + # if not tool or tool.Diameter == 0: + # self.toolRadius = 5 + # else: + # self.toolRadius = tool.Diameter / 2 + self.pathData = pathData + return self.pathData + + def getHeight(self, obj): + return self.pathData.tagHeight() + + def getWidth(self, obj): + return self.pathData.tagWidth() + + def getAngle(self, obj): + return self.pathData.tagAngle() + + def getPathLength(self, obj): + return self.pathData.pathLength() + +class TaskPanel: + DataTag = QtCore.Qt.ItemDataRole.UserRole + DataValue = QtCore.Qt.ItemDataRole.DisplayRole + + def __init__(self, obj): + self.obj = obj + self.form = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") + FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) + + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.Selection.removeObserver(self.s) + + def accept(self): + FreeCAD.ActiveDocument.commitTransaction() + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.Selection.removeObserver(self.s) + FreeCAD.ActiveDocument.recompute() + + def open(self): + self.s = SelObserver() + # install the function mode resident + FreeCADGui.Selection.addObserver(self.s) + + def tableWidgetItem(self, tag, val): + item = QtGui.QTableWidgetItem() + item.setTextAlignment(QtCore.Qt.AlignRight) + item.setData(self.DataTag, tag) + item.setData(self.DataValue, val) + return item + + def getFields(self): + tags = [] + for row in range(0, self.form.twTags.rowCount()): + x = self.form.twTags.item(row, 0).data(self.DataValue) + y = self.form.twTags.item(row, 1).data(self.DataValue) + w = self.form.twTags.item(row, 2).data(self.DataValue) + h = self.form.twTags.item(row, 3).data(self.DataValue) + a = self.form.twTags.item(row, 4).data(self.DataValue) + tags.append(Tag(x, y, w, h, a, True)) + print("getFields: %d" % (len(tags))) + self.obj.Proxy.setTags(self.obj, tags) + + def updateTags(self): + self.tags = self.obj.Proxy.getTags(self.obj) + self.form.twTags.blockSignals(True) + self.form.twTags.setSortingEnabled(False) + self.form.twTags.clearSpans() + print("updateTags: %d" % (len(self.tags))) + self.form.twTags.setRowCount(len(self.tags)) + for row, tag in enumerate(self.tags): + self.form.twTags.setItem(row, 0, self.tableWidgetItem(tag, tag.x)) + self.form.twTags.setItem(row, 1, self.tableWidgetItem(tag, tag.y)) + self.form.twTags.setItem(row, 2, self.tableWidgetItem(tag, tag.width)) + self.form.twTags.setItem(row, 3, self.tableWidgetItem(tag, tag.height)) + self.form.twTags.setItem(row, 4, self.tableWidgetItem(tag, tag.angle)) + self.form.twTags.setSortingEnabled(True) + self.form.twTags.blockSignals(False) + + def cleanupUI(self): + print("cleanupUI") + if debugDressup: + for obj in FreeCAD.ActiveDocument.Objects: + if obj.Name.startswith('tag'): + FreeCAD.ActiveDocument.removeObject(obj.Name) + + def updateUI(self): + print("updateUI") + self.cleanupUI() + self.getFields() + if debugDressup: + FreeCAD.ActiveDocument.recompute() + + + def whenApplyClicked(self): + print("whenApplyClicked") + self.cleanupUI() + + count = self.form.sbCount.value() + spacing = self.form.dsbSpacing.value() + width = self.form.dsbWidth.value() + height = self.form.dsbHeight.value() + angle = self.form.dsbAngle.value() + + tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle, spacing * 0.99) + + self.obj.Proxy.setTags(self.obj, tags) + self.updateTags() + if debugDressup: + # this causes a big of an echo and a double click on the spin buttons, don't know why though + FreeCAD.ActiveDocument.recompute() + + def autoApply(self): + print("autoApply") + if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked: + self.whenApplyClicked() + + def updateTagSpacing(self, count): + print("updateTagSpacing") + if count == 0: + spacing = 0 + else: + spacing = self.pathLength / count + self.form.dsbSpacing.blockSignals(True) + self.form.dsbSpacing.setValue(spacing) + self.form.dsbSpacing.blockSignals(False) + + def whenCountChanged(self): + print("whenCountChanged") + self.updateTagSpacing(self.form.sbCount.value()) + self.autoApply() + + def whenSpacingChanged(self): + print("whenSpacingChanged") + if self.form.dsbSpacing.value() == 0: + count = 0 + else: + count = int(self.pathLength / self.form.dsbSpacing.value()) + self.form.sbCount.blockSignals(True) + self.form.sbCount.setValue(count) + self.form.sbCount.blockSignals(False) + self.autoApply() + + def whenOkClicked(self): + print("whenOkClicked") + self.whenApplyClicked() + self.form.toolBox.setCurrentWidget(self.form.tbpTags) + + def setupSpinBox(self, widget, val, decimals = 2): + widget.setMinimum(0) + if decimals: + widget.setDecimals(decimals) + widget.setValue(val) + + def setFields(self): + self.pathLength = self.obj.Proxy.getPathLength(self.obj) + vHeader = self.form.twTags.verticalHeader() + vHeader.setResizeMode(QtGui.QHeaderView.Fixed) + vHeader.setDefaultSectionSize(20) + self.updateTags() + self.setupSpinBox(self.form.sbCount, self.form.twTags.rowCount(), None) + self.setupSpinBox(self.form.dsbSpacing, 0) + self.setupSpinBox(self.form.dsbHeight, self.obj.Proxy.getHeight(self.obj)) + self.setupSpinBox(self.form.dsbWidth, self.obj.Proxy.getWidth(self.obj)) + self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj)) + self.updateTagSpacing(self.form.twTags.rowCount()) + + def setupUi(self): + self.setFields() + self.form.sbCount.valueChanged.connect(self.whenCountChanged) + self.form.dsbSpacing.valueChanged.connect(self.whenSpacingChanged) + self.form.dsbHeight.valueChanged.connect(self.autoApply) + self.form.dsbWidth.valueChanged.connect(self.autoApply) + self.form.dsbAngle.valueChanged.connect(self.autoApply) + #self.form.pbAdd.clicked.connect(self.) + self.form.buttonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self.whenApplyClicked) + self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok).clicked.connect(self.whenOkClicked) + self.form.twTags.itemChanged.connect(self.updateUI) + +class SelObserver: + def __init__(self): + import PathScripts.PathSelection as PST + PST.eselect() + + def __del__(self): + import PathScripts.PathSelection as PST + PST.clear() + + def addSelection(self, doc, obj, sub, pnt): + FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') + FreeCADGui.updateGui() + +class ViewProviderDressup: + + def __init__(self, vobj): + vobj.Proxy = self + + def attach(self, vobj): + self.Object = vobj.Object + return + + def claimChildren(self): + for i in self.Object.Base.InList: + if hasattr(i, "Group"): + group = i.Group + for g in group: + if g.Name == self.Object.Base.Name: + group.remove(g) + i.Group = group + print i.Group + #FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False + return [self.Object.Base] + + def setEdit(self, vobj, mode=0): + FreeCADGui.Control.closeDialog() + panel = TaskPanel(vobj.Object) + FreeCADGui.Control.showDialog(panel) + panel.setupUi() + return True + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def onDelete(self, arg1=None, arg2=None): + '''this makes sure that the base operation is added back to the project and visible''' + FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True + PathUtils.addToJob(arg1.Object.Base) + return True + +class CommandPathDressupHoldingTags: + + def GetResources(self): + return {'Pixmap': 'Path-Dressup', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "HoldingTags Dress-up"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Creates a HoldingTags Dress-up object from a selected path")} + + 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): + + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Please select one path object\n")) + return + baseObject = selection[0] + if not baseObject.isDerivedFrom("Path::Feature"): + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "The selected object is not a path\n")) + return + if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Please select a Profile object")) + return + + # everything ok! + FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Create HoldingTags Dress-up")) + FreeCADGui.addModule("PathScripts.PathDressupHoldingTags") + FreeCADGui.addModule("PathScripts.PathUtils") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "HoldingTagsDressup")') + FreeCADGui.doCommand('dbo = PathScripts.PathDressupHoldingTags.ObjectDressup(obj)') + FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name) + FreeCADGui.doCommand('PathScripts.PathDressupHoldingTags.ViewProviderDressup(obj.ViewObject)') + FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') + FreeCADGui.doCommand('dbo.setup(obj)') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('PathDressup_HoldingTags', CommandPathDressupHoldingTags()) + +FreeCAD.Console.PrintLog("Loading PathDressupHoldingTags... done\n") diff --git a/src/Mod/Path/PathTests/PathTestUtils.py b/src/Mod/Path/PathTests/PathTestUtils.py index 30b56024ec..029ca79140 100644 --- a/src/Mod/Path/PathTests/PathTestUtils.py +++ b/src/Mod/Path/PathTests/PathTestUtils.py @@ -27,6 +27,7 @@ import Part import math import unittest +from FreeCAD import Vector from PathScripts.PathGeom import Side class PathTestBase(unittest.TestCase): @@ -48,24 +49,35 @@ class PathTestBase(unittest.TestCase): self.assertCoincide(edge.Curve.StartPoint, pt1) self.assertCoincide(edge.Curve.EndPoint, pt2) + def assertLines(self, edgs, tail, points): + """Verify that the edges match the polygon resulting from points.""" + edges = list(edgs) + if tail: + edges.append(tail) + self.assertEqual(len(edges), len(points) - 1) + + for i in range(0, len(edges)): + self.assertLine(edges[i], points[i], points[i+1]) + def assertArc(self, edge, pt1, pt2, direction = 'CW'): """Verify that edge is an arc between pt1 and pt2 with the given direction.""" - # If an Arc is wrapped into edge, then it's curve is represented as a circle - # and not as an Arc (GeomTrimmedCurve) - #self.assertIs(type(edge.Curve), Part.Arc) self.assertIs(type(edge.Curve), Part.Circle) self.assertCoincide(edge.valueAt(edge.FirstParameter), pt1) self.assertCoincide(edge.valueAt(edge.LastParameter), pt2) ptm = edge.valueAt((edge.LastParameter + edge.FirstParameter)/2) side = Side.of(pt2 - pt1, ptm - pt1) - #print("(%.2f, %.2f) (%.2f, %.2f) (%.2f, %.2f)" % (pt1.x, pt1.y, ptm.x, ptm.y, pt2.x, pt2.y)) - #print(" (%.2f, %.2f) (%.2f, %.2f) -> %s" % ((pt2-pt1).x, (pt2-pt1).y, (ptm-pt1).x, (ptm-pt1).y, Side.toString(side))) - #print(" (%.2f, %.2f) (%.2f, %.2f) -> (%.2f, %.2f)" % (pf.x,pf.y, pl.x,pl.y, pm.x, pmy)) if 'CW' == direction: self.assertEqual(side, Side.Left) else: self.assertEqual(side, Side.Right) + def assertCircle(self, edge, pt, r): + """Verivy that edge is a circle at given location.""" + curve = edge.Curve + self.assertIs(type(curve), Part.Circle) + self.assertCoincide(curve.Center, Vector(pt.x, pt.y, pt.z)) + self.assertRoughly(curve.Radius, r) + def assertCurve(self, edge, p1, p2, p3): """Verify that the edge goes through the given 3 points, representing start, mid and end point respectively.""" @@ -73,3 +85,27 @@ class PathTestBase(unittest.TestCase): self.assertCoincide(edge.valueAt(edge.LastParameter), p3) self.assertCoincide(edge.valueAt((edge.FirstParameter + edge.LastParameter)/2), p2) + def assertCylinderAt(self, solid, pt, r, h): + """Verify that solid is a cylinder at the specified location.""" + self.assertEqual(len(solid.Edges), 3) + + lid = solid.Edges[0] + hull = solid.Edges[1] + base = solid.Edges[2] + + self.assertCircle(lid, Vector(pt.x, pt.y, pt.z+h), r) + self.assertLine(hull, Vector(pt.x+r, pt.y, pt.z), Vector(pt.x+r, pt.y, pt.z+h)) + self.assertCircle(base, Vector(pt.x, pt.y, pt.z), r) + + def assertConeAt(self, solid, pt, r1, r2, h): + """Verify that solid is a cone at the specified location.""" + self.assertEqual(len(solid.Edges), 3) + + lid = solid.Edges[0] + hull = solid.Edges[1] + base = solid.Edges[2] + + self.assertCircle(lid, Vector(pt.x, pt.y, pt.z+h), r2) + self.assertLine(hull, Vector(pt.x+r1, pt.y, pt.z), Vector(pt.x+r2, pt.y, pt.z+h)) + self.assertCircle(base, Vector(pt.x, pt.y, pt.z), r1) + diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py new file mode 100644 index 0000000000..f5db3e386c --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -0,0 +1,546 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2016 sliptonic * +# * * +# * 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 Part +import Path +import PathScripts +import math +import unittest + +from FreeCAD import Vector +from PathScripts.PathDressupHoldingTags import * +from PathTests.PathTestUtils import PathTestBase + +class TestTag01BasicTag(PathTestBase): # ============= + """Unit tests for the HoldingTags dressup.""" + + def test00(self): + """Check Tag origin, serialization and de-serialization.""" + tag = Tag(77, 13, 4, 5, 90, True) + self.assertCoincide(tag.originAt(3), Vector(77, 13, 3)) + s = tag.toString() + tagCopy = Tag.FromString(s) + self.assertEqual(tag.x, tagCopy.x) + self.assertEqual(tag.y, tagCopy.y) + self.assertEqual(tag.height, tagCopy.height) + self.assertEqual(tag.width, tagCopy.width) + self.assertEqual(tag.enabled, tagCopy.enabled) + + + def test01(self): + """Verify solid and core for a 90 degree tag are identical cylinders.""" + tag = Tag(100, 200, 4, 5, 90, True) + tag.createSolidsAt(17) + + self.assertIsNotNone(tag.solid) + self.assertCylinderAt(tag.solid, Vector(100, 200, 17), 2, 5) + + self.assertIsNotNone(tag.core) + self.assertCylinderAt(tag.core, Vector(100, 200, 17), 2, 5) + + def test02(self): + """Verify trapezoidal tag has a cone shape with a lid, and cylinder core.""" + tag = Tag(0, 0, 18, 5, 45, True) + tag.createSolidsAt(0) + + self.assertIsNotNone(tag.solid) + self.assertConeAt(tag.solid, Vector(0,0,0), 9, 4, 5) + + self.assertIsNotNone(tag.core) + self.assertCylinderAt(tag.core, Vector(0,0,0), 4, 5) + + def test03(self): + """Verify pointy cone shape of tag with pointy end if width, angle and height match up.""" + tag = Tag(0, 0, 10, 5, 45, True) + tag.createSolidsAt(0) + self.assertIsNotNone(tag.solid) + self.assertConeAt(tag.solid, Vector(0,0,0), 5, 0, 5) + + self.assertIsNone(tag.core) + + def test04(self): + """Verify height adjustment if tag isn't wide eough for angle.""" + tag = Tag(0, 0, 5, 17, 60, True) + tag.createSolidsAt(0) + self.assertIsNotNone(tag.solid) + self.assertConeAt(tag.solid, Vector(0,0,0), 2.5, 0, 2.5 * math.tan((60/180.0)*math.pi)) + + self.assertIsNone(tag.core) + +class TestTag02SquareTag(PathTestBase): # ============= + """Unit tests for square tags.""" + + def test00(self): + """Verify no intersection.""" + tag = Tag( 0, 0, 4, 7, 90, True, 0) + pt1 = Vector(+5, 5, 0) + pt2 = Vector(-5, 5, 0) + edge = Part.Edge(Part.Line(pt1, pt2)) + + i = tag.intersect(edge) + self.assertIsNotNone(i) + self.assertTrue(i.isComplete()) + self.assertIsNotNone(i.edges) + self.assertFalse(i.edges) + self.assertLine(i.tail, pt1, pt2) + + def test01(self): + """Verify intersection of square tag with line ending at tag start.""" + tag = Tag( 0, 0, 8, 3, 90, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P0) + self.assertEqual(len(i.edges), 1) + self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint) + self.assertIsNone(i.tail) + + def test02(self): + """Verify intersection of square tag with line ending between P1 and P2.""" + tag = Tag( 0, 0, 8, 3, 90, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P1) + self.assertEqual(len(i.edges), 3) + p1 = Vector(4, 0, 0) + p2 = Vector(4, 0, 3) + p3 = Vector(1, 0, 3) + self.assertLine(i.edges[0], edge.Curve.StartPoint, p1) + self.assertLine(i.edges[1], p1, p2) + self.assertLine(i.edges[2], p2, p3) + self.assertIsNone(i.tail) + + # verify we stay in P1 if we add another segment + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(0, 0, 0))) + i = i.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P1) + self.assertEqual(len(i.edges), 4) + p4 = Vector(0, 0, 3) + self.assertLine(i.edges[3], p3, p4) + self.assertIsNone(i.tail) + + def test03(self): + """Verify intesection of square tag with line ending on P2.""" + tag = Tag( 0, 0, 8, 3, 90, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-4, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + self.assertEqual(len(i.edges), 3) + p0 = edge.Curve.StartPoint + p1 = Vector( 4, 0, 0) + p2 = Vector( 4, 0, 3) + p3 = Vector(-4, 0, 3) + self.assertLine(i.edges[0], p0, p1) + self.assertLine(i.edges[1], p1, p2) + self.assertLine(i.edges[2], p2, p3) + self.assertIsNone(i.tail) + + # make sure it also works if we get there not directly + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(0, 0, 0))) + i = tag.intersect(edge) + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-4, 0, 0))) + i = i.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + self.assertEqual(len(i.edges), 4) + p2a = Vector( 0, 0, 3) + self.assertLine(i.edges[0], p0, p1) + self.assertLine(i.edges[1], p1, p2) + self.assertLine(i.edges[2], p2, p2a) + self.assertLine(i.edges[3], p2a, p3) + self.assertIsNone(i.tail) + + def test04(self): + """Verify plunge down is inserted for square tag on exit.""" + tag = Tag( 0, 0, 8, 3, 90, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-5, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P3) + self.assertTrue(i.isComplete()) + self.assertEqual(len(i.edges), 4) + p0 = edge.Curve.StartPoint + p1 = Vector( 4, 0, 0) + p2 = Vector( 4, 0, 3) + p3 = Vector(-4, 0, 3) + p4 = Vector(-4, 0, 0) + p5 = edge.Curve.EndPoint + self.assertLine(i.edges[0], p0, p1) + self.assertLine(i.edges[1], p1, p2) + self.assertLine(i.edges[2], p2, p3) + self.assertLine(i.edges[3], p3, p4) + self.assertIsNotNone(i.tail) + self.assertLine(i.tail, p4, p5) + + def test05(self): + """Verify all lines between P0 and P3 are added.""" + tag = Tag( 0, 0, 4, 7, 90, True, 0) + e0 = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(+2, 0, 0))) + e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+1, 0, 0))) + e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0))) + e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0))) + e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0))) + e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0))) + e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, Vector(-5, 0, 0))) + + i = tag + for e in [e0, e1, e2, e3, e4, e5]: + i = i.intersect(e) + self.assertFalse(i.isComplete()) + i = i.intersect(e6) + self.assertTrue(i.isComplete()) + + pt0 = Vector(2, 0, 0) + pt1 = Vector(2, 0, 7) + pt2 = Vector(1, 0, 7) + pt3 = Vector(0.5, 0, 7) + pt4 = Vector(-0.5, 0, 7) + pt5 = Vector(-1, 0, 7) + pt6 = Vector(-2, 0, 7) + + self.assertEqual(len(i.edges), 8) + self.assertLines(i.edges, i.tail, [e0.Curve.StartPoint, pt0, pt1, pt2, pt3, pt4, pt5, pt6, e6.Curve.StartPoint, e6.Curve.EndPoint]) + self.assertIsNotNone(i.tail) + + def test06(self): + """Verify intersection of different z levels.""" + tag = Tag( 0, 0, 4, 7, 90, True, 0) + # for all lines below 7 we get the trapezoid + for i in range(0, 7): + p0 = Vector(5, 0, i) + p1 = Vector(2, 0, i) + p2 = Vector(2, 0, 7) + p3 = Vector(-2, 0, 7) + p4 = Vector(-2, 0, i) + p5 = Vector(-5, 0, i) + edge = Part.Edge(Part.Line(p0, p5)) + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5]) + + # for all edges at height or above the original line is used + for i in range(7, 9): + edge = Part.Edge(Part.Line(Vector(5, 0, i), Vector(-5, 0, i))) + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + +class TestTag03TrapezoidTag(PathTestBase): # ============= + """Unit tests for trapezoid tags.""" + + def test00(self): + """Verify no intersection.""" + tag = Tag( 0, 0, 8, 3, 45, True, 0) + pt1 = Vector(+5, 5, 0) + pt2 = Vector(-5, 5, 0) + edge = Part.Edge(Part.Line(pt1, pt2)) + + i = tag.intersect(edge) + self.assertIsNotNone(i) + self.assertTrue(i.isComplete()) + self.assertIsNotNone(i.edges) + self.assertFalse(i.edges) + self.assertLine(i.tail, pt1, pt2) + + def test01(self): + """Veify intersection of trapezoid tag with line ending before P1.""" + tag = Tag( 0, 0, 8, 3, 45, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P0) + self.assertEqual(len(i.edges), 1) + self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint) + self.assertIsNone(i.tail) + + # now add another segment that doesn't reach the top of the cone + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(3, 0, 0))) + i = i.intersect(edge) + # still a P0 and edge fully consumed + p1 = Vector(edge.Curve.StartPoint) + p1.z = 0 + p2 = Vector(edge.Curve.EndPoint) + p2.z = 1 # height of cone @ (3,0) + self.assertEqual(i.state, Tag.Intersection.P0) + self.assertEqual(len(i.edges), 2) + self.assertLine(i.edges[1], p1, p2) + self.assertIsNone(i.tail) + + # add another segment to verify starting point offset + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(2, 0, 0))) + i = i.intersect(edge) + # still a P0 and edge fully consumed + p3 = Vector(edge.Curve.EndPoint) + p3.z = 2 # height of cone @ (2,0) + self.assertEqual(i.state, Tag.Intersection.P0) + self.assertEqual(len(i.edges), 3) + self.assertLine(i.edges[2], p2, p3) + self.assertIsNone(i.tail) + + def test02(self): + """Verify intersection of trapezoid tag with line ending between P1 and P2""" + tag = Tag( 0, 0, 8, 3, 45, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P1) + self.assertEqual(len(i.edges), 2) + p1 = Vector(4, 0, 0) + p2 = Vector(1, 0, 3) + self.assertLine(i.edges[0], edge.Curve.StartPoint, p1) + self.assertLine(i.edges[1], p1, p2) + self.assertIsNone(i.tail) + + # verify we stay in P1 if we add another segment + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(0, 0, 0))) + i = i.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P1) + self.assertEqual(len(i.edges), 3) + p3 = Vector(0, 0, 3) + self.assertLine(i.edges[2], p2, p3) + self.assertIsNone(i.tail) + + def test03(self): + """Verify intersection of trapezoid tag with edge ending on P2.""" + tag = Tag( 0, 0, 8, 3, 45, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-1, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + p0 = Vector(edge.Curve.StartPoint) + p1 = Vector(4, 0, 0) + p2 = Vector(1, 0, 3) + p3 = Vector(-1, 0, 3) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) + self.assertIsNone(i.tail) + + # make sure we get the same result if there's another edge + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0))) + i = tag.intersect(edge) + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-1, 0, 0))) + i = i.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) + self.assertIsNone(i.tail) + + # and also if the last segment doesn't cross the entire plateau + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(0.5, 0, 0))) + i = tag.intersect(edge) + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-1, 0, 0))) + i = i.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + p2a = Vector(0.5, 0, 3) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p2a, p3]) + self.assertIsNone(i.tail) + + def test04(self): + """Verify proper down plunge on trapezoid tag exit.""" + tag = Tag( 0, 0, 8, 3, 45, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-2, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + p0 = Vector(5, 0, 0) + p1 = Vector(4, 0, 0) + p2 = Vector(1, 0, 3) + p3 = Vector(-1, 0, 3) + p4 = Vector(-2, 0, 2) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) + self.assertIsNone(i.tail) + + # make sure adding another segment doesn't change the state + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-3, 0, 0))) + i = i.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + self.assertEqual(len(i.edges), 5) + p5 = Vector(-3, 0, 1) + self.assertLine(i.edges[4], p4, p5) + self.assertIsNone(i.tail) + + # now if we complete to P3 .... + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-4, 0, 0))) + i = i.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P3) + self.assertTrue(i.isComplete()) + self.assertEqual(len(i.edges), 6) + p6 = Vector(-4, 0, 0) + self.assertLine(i.edges[5], p5, p6) + self.assertIsNone(i.tail) + + # verify proper operation if there is a single edge going through all + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-4, 0, 0))) + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P3) + self.assertTrue(i.isComplete()) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6]) + self.assertIsNone(i.tail) + + # verify tail is added as well + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-5, 0, 0))) + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P3) + self.assertTrue(i.isComplete()) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6, edge.Curve.EndPoint]) + self.assertIsNotNone(i.tail) + + def test05(self): + """Verify all lines between P0 and P3 are added.""" + tag = Tag( 0, 0, 8, 3, 45, True, 0) + e0 = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(+4, 0, 0))) + e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+2, 0, 0))) + e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0))) + e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0))) + e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0))) + e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0))) + e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, Vector(-5, 0, 0))) + + i = tag + for e in [e0, e1, e2, e3, e4, e5]: + i = i.intersect(e) + self.assertFalse(i.isComplete()) + i = i.intersect(e6) + self.assertTrue(i.isComplete()) + + p0 = Vector(4, 0, 0) + p1 = Vector(2, 0, 2) + p2 = Vector(1, 0, 3) + p3 = Vector(0.5, 0, 3) + p4 = Vector(-0.5, 0, 3) + p5 = Vector(-1, 0, 3) + p6 = Vector(-2, 0, 2) + p7 = Vector(-4, 0, 0) + + self.assertLines(i.edges, i.tail, [e0.Curve.StartPoint, p0, p1, p2, p3, p4, p5, p6, p7, e6.Curve.EndPoint]) + self.assertIsNotNone(i.tail) + + def test06(self): + """Verify intersection for different z levels.""" + tag = Tag( 0, 0, 8, 3, 45, True, 0) + # for all lines below 3 we get the trapezoid + for i in range(0, 3): + p0 = Vector(5, 0, i) + p1 = Vector(4-i, 0, i) + p2 = Vector(1, 0, 3) + p3 = Vector(-1, 0, 3) + p4 = Vector(-4+i, 0, i) + p5 = Vector(-5, 0, i) + edge = Part.Edge(Part.Line(p0, p5)) + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5]) + + # for all edges at height or above the original line is used + for i in range(3, 5): + edge = Part.Edge(Part.Line(Vector(5, 0, i), Vector(-5, 0, i))) + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + + +class TestTag04TriangularTag(PathTestBase): # ======================== + """Unit tests for tags that take on a triangular shape.""" + + def test00(self): + """Verify intersection of triangular tag with line ending at tag start.""" + tag = Tag( 0, 0, 8, 7, 45, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P0) + self.assertEqual(len(i.edges), 1) + self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint) + self.assertIsNone(i.tail) + + def test01(self): + """Verify intersection of triangular tag with line ending between P0 and P1.""" + tag = Tag( 0, 0, 8, 7, 45, True, 0) + edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(3, 0, 0))) + + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P0) + p1 = Vector(4, 0, 0) + p2 = Vector(3, 0, 1) + self.assertLines(i.edges, i.tail, [edge.Curve.StartPoint, p1, p2]) + self.assertIsNone(i.tail) + + # verify we stay in P1 if we add another segment + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(1, 0, 0))) + i = i.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P0) + self.assertEqual(len(i.edges), 3) + p3 = Vector(1, 0, 3) + self.assertLine(i.edges[2], p2, p3) + self.assertIsNone(i.tail) + + def test02(self): + """Verify proper down plunge on exit of triangular tag.""" + tag = Tag( 0, 0, 8, 7, 45, True, 0) + + p0 = Vector(5, 0, 0) + p1 = Vector(4, 0, 0) + p2 = Vector(0, 0, 4) + edge = Part.Edge(Part.Line(p0, FreeCAD.Vector(0,0,0))) + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + self.assertEqual(len(i.edges), 2) + self.assertLines(i.edges, i.tail, [p0, p1, p2]) + + # adding another segment doesn't make a difference + edge = Part.Edge(Part.Line(edge.Curve.EndPoint, FreeCAD.Vector(-3,0,0))) + i = i.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + self.assertEqual(len(i.edges), 3) + p3 = Vector(-3, 0, 1) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) + + # same result if all is one line + edge = Part.Edge(Part.Line(p0, edge.Curve.EndPoint)) + i = tag.intersect(edge) + self.assertEqual(i.state, Tag.Intersection.P2) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) + + def test03(self): + """Verify triangular tag shap on intersection.""" + tag = Tag( 0, 0, 8, 7, 45, True, 0) + + p0 = Vector(5, 0, 0) + p1 = Vector(4, 0, 0) + p2 = Vector(0, 0, 4) + p3 = Vector(-4, 0, 0) + edge = Part.Edge(Part.Line(p0, p3)) + i = tag.intersect(edge) + self.assertTrue(i.isComplete()) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) + self.assertIsNone(i.tail) + + # this should also work if there is some excess, aka tail + p4 = Vector(-5, 0, 0) + edge = Part.Edge(Part.Line(p0, p4)) + i = tag.intersect(edge) + self.assertTrue(i.isComplete()) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) + self.assertIsNotNone(i.tail) + diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 9886b77d57..8729163535 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -29,3 +29,8 @@ from PathTests.TestPathPost import PathPostTestCases from PathTests.TestPathGeom import TestPathGeom from PathTests.TestPathDepthParams import depthTestCases +from PathTests.TestPathDressupHoldingTags import TestTag01BasicTag +from PathTests.TestPathDressupHoldingTags import TestTag02SquareTag +from PathTests.TestPathDressupHoldingTags import TestTag03TrapezoidTag +from PathTests.TestPathDressupHoldingTags import TestTag04TriangularTag + From 6462d775e35c282fd9f29f501559acfd55192b93 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 29 Nov 2016 03:11:03 -0800 Subject: [PATCH 016/144] Added support for arcs on square tags. --- .../PathScripts/PathDressupHoldingTags.py | 90 ++++++++++--------- .../PathTests/TestPathDressupHoldingTags.py | 22 +++++ 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 4c13da8ea6..e501c8173f 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -173,38 +173,38 @@ class Tag: def moveEdgeToPlateau(self, edge): if type(edge.Curve) is Part.Line: - pt1 = edge.Curve.StartPoint - pt2 = edge.Curve.EndPoint - pt1.z = self.tag.top() - pt2.z = self.tag.top() - #print("\nplateau= %s - %s" %(pt1, pt2)) - return Part.Edge(Part.Line(pt1, pt2)) + z = edge.Curve.StartPoint.z + elif type(edge.Curve) is Part.Circle: + z = edge.Curve.Center.z + + edge.translate(Vector(0, 0, self.tag.top() - z)) + return edge def intersectP0Core(self, edge): - #print("----- P0 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + print("----- P0 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.StartPoint) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.FirstParameter)) if i: - if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): # if P0 and P1 are the same, we need to insert a segment for the rise - #print("------- insert vertical rise (%s)" % i) + print("------- insert vertical rise (%s)" % i) self.edges.append(Part.Edge(Part.Line(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) self.p1 = i self.state = self.P1 return edge - if PathGeom.pointsCoincide(i, edge.Curve.EndPoint): - #print("------- consumed (%s)" % i) + if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + print("------- consumed (%s)" % i) e = edge tail = None else: - #print("------- split at (%s)" % i) + print("------- split at (%s)" % i) e, tail = self.tag.splitEdgeAt(edge, i) - self.p1 = e.Curve.EndPoint + self.p1 = e.valueAt(edge.LastParameter) self.edges.append(self.tag.mapEdgeToSolid(e)) self.state = self.P1 return tail # no intersection, the entire edge fits between P0 and P1 - #print("------- no intersection") + print("------- no intersection") self.edges.append(self.tag.mapEdgeToSolid(edge)) return None @@ -215,7 +215,7 @@ class Tag: line = Part.Edge(self.tag.centerLine()) i = DraftGeomUtils.findIntersection(line, edge) if i: - if PathGeom.pointsCoincide(i[0], edge.Curve.EndPoint): + if PathGeom.pointsCoincide(i[0], edge.valueAt(edge.LastParameter)): e = edge tail = None else: @@ -232,18 +232,18 @@ class Tag: def intersectP1(self, edge): - #print("----- P1 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.Curve.EndPoint) + print("----- P1 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.LastParameter)) if i: - if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): self.edges.append(self.tag.mapEdgeToSolid(edge)) return self - if PathGeom.pointsCoincide(i, edge.Curve.EndPoint): + if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): e = edge tail = None else: e, tail = self.tag.splitEdgeAt(edge, i) - self.p2 = e.Curve.EndPoint + self.p2 = e.valueAt(edge.LastParameter) self.state = self.P2 else: e = edge @@ -252,22 +252,22 @@ class Tag: return tail def intersectP2(self, edge): - #print("----- P2 (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.Curve.EndPoint) + print("----- P2 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.valueAt(edge.LastParameter)) if i: - if PathGeom.pointsCoincide(i, edge.Curve.StartPoint): - #print("------- insert exit plunge (%s)" % i) + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + print("------- insert exit plunge (%s)" % i) self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) e = None tail = edge - elif PathGeom.pointsCoincide(i, edge.Curve.EndPoint): - #print("------- entire segment added (%s)" % i) + elif PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + print("------- entire segment added (%s)" % i) e = edge tail = None else: e, tail = self.tag.splitEdgeAt(edge, i) #if tail: - # print("----- P3 (%s - %s)" % (tail.Curve.StartPoint, tail.Curve.EndPoint)) + # print("----- P3 (%s - %s)" % (tail.valueAt(edge.FirstParameter), tail.valueAt(edge.LastParameter))) #else: # print("----- P3 (---)") self.state = self.P3 @@ -280,8 +280,8 @@ class Tag: return tail def intersect(self, edge): - #print("") - #print(" >>> (%s - %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) + print("") + print(" >>> (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) if edge and self.state == self.P0: edge = self.intersectP0(edge) if edge and self.state == self.P1: @@ -297,18 +297,18 @@ class Tag: return wire.Edges def mapEdgeToSolid(self, edge): - #print("mapEdgeToSolid: (%s %s)" % (edge.Curve.StartPoint, edge.Curve.EndPoint)) - p1a = edge.Curve.StartPoint + print("mapEdgeToSolid: (%s %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + p1a = edge.valueAt(edge.FirstParameter) p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) e1 = Part.Edge(Part.Line(p1a, p1b)) p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection - #print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) + print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) - p2a = edge.Curve.EndPoint + p2a = edge.valueAt(edge.LastParameter) p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) e2 = Part.Edge(Part.Line(p2a, p2b)) p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection - #print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) + print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) if type(edge.Curve) == Part.Line: return Part.Edge(Part.Line(p1, p2)) @@ -331,7 +331,7 @@ class Tag: pts.extend(ps) if pts: closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - #print("--pts: %s -> %s" % (pts, closest)) + print("--pts: %s -> %s" % (pts, closest)) return closest return None @@ -351,6 +351,10 @@ class Tag: e,tail = self.splitEdgeAt(edge, i) inters.edges.append(e) return inters.intersect(tail) + else: + print("No intersection found.") + else: + print("Fly by") # if we get here there is no intersection with the tag inters.state = self.Intersection.P3 inters.tail = edge @@ -379,16 +383,16 @@ class PathData: def sortedBase(self, base): # first find the exit point, where base wire is closed - edges = [e for e in self.edges if e.Curve.StartPoint.z == self.minZ and e.Curve.EndPoint.z != self.maxZ] - exit = sorted(edges, key=lambda e: -e.Curve.EndPoint.z)[0] - pt = exit.Curve.StartPoint + edges = [e for e in self.edges if e.valueAt(edge.FirstParameter).z == self.minZ and e.valueAt(edge.LastParameter).z != self.maxZ] + exit = sorted(edges, key=lambda e: -e.valueAt(edge.LastParameter).z)[0] + pt = exit.valueAt(edge.FirstParameter) # then find the first base edge, and sort them until done ordered = [] while base: - edge = [e for e in base if e.Curve.StartPoint == pt][0] + edge = [e for e in base if e.valueAt(edge.FirstParameter) == pt][0] ordered.append(edge) base.remove(edge) - pt = edge.Curve.EndPoint + pt = edge.valueAt(edge.LastParameter) return ordered @@ -508,7 +512,7 @@ class PathData: ordered = [] for edge in self.base.Edges: ts = [t for t in tags if DraftGeomUtils.isPtOnEdge(t.originAt(self.minZ), edge)] - for t in sorted(ts, key=lambda t: (t.originAt(self.minZ) - edge.Curve.StartPoint).Length): + for t in sorted(ts, key=lambda t: (t.originAt(self.minZ) - edge.valueAt(edge.FirstParameter)).Length): tags.remove(t) ordered.append(t) if tags: @@ -536,7 +540,7 @@ class ObjectDressup: def tagIntersection(self, face, edge): - p1 = edge.Curve.StartPoint + p1 = edge.valueAt(edge.FirstParameter) pts = edge.Curve.intersect(face.Surface) if pts[0]: closest = sorted(pts[0], key=lambda pt: (pt - p1).Length)[0] diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index f5db3e386c..e5e8917bd6 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -248,6 +248,28 @@ class TestTag02SquareTag(PathTestBase): # ============= self.assertTrue(s.isComplete()) self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + def test10(self): + """Verify intersection of square tag with an arc.""" + print + tag = Tag( 0, 0, 8, 3, 90, True, 0) + p1 = Vector(10, -10, 0) + p2 = Vector(10, +10, 0) + edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10}), p1) + + pi = Vector(0.8, -3.919184, 0) + pj = Vector(0.8, +3.919184, 0) + + print("(%s - %s) - %s" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter), edge.valueAt((edge.FirstParameter + edge.LastParameter)/2))) + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertEqual(len(s.edges), 4) + self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0), pi) + self.assertLine(s.edges[1], pi, pi + Vector(0, 0, 3)) + self.assertCurve(s.edges[2], pi + Vector(0, 0, 3), Vector(0, 0, 3), pj + Vector(0, 0, 3)) + self.assertLine(s.edges[3], pj + Vector(0, 0, 3), pj) + self.assertCurve(s.tail, pj, Vector(4.486010, +8.342417, 0), p2) + + class TestTag03TrapezoidTag(PathTestBase): # ============= """Unit tests for trapezoid tags.""" From 4a810bc107d3775ce758c89d6578c11f1792bd7c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Wed, 30 Nov 2016 09:24:47 -0800 Subject: [PATCH 017/144] Support for arcs and helix with tests. --- .../PathScripts/PathDressupHoldingTags.py | 96 +++++++++++++---- .../PathTests/TestPathDressupHoldingTags.py | 101 +++++++++++++++++- 2 files changed, 174 insertions(+), 23 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index e501c8173f..3441eb0423 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -175,36 +175,43 @@ class Tag: if type(edge.Curve) is Part.Line: z = edge.Curve.StartPoint.z elif type(edge.Curve) is Part.Circle: + # it's an arc z = edge.Curve.Center.z - + else: + # it's a helix -> transform to arc + z = 0 + p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) + p2 = PathGeom.xy(edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)) + p3 = PathGeom.xy(edge.valueAt(edge.LastParameter)) + edge = Part.Edge(Part.Arc(p1, p2, p3)) edge.translate(Vector(0, 0, self.tag.top() - z)) return edge def intersectP0Core(self, edge): - print("----- P0 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #print("----- P0 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.FirstParameter)) if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): # if P0 and P1 are the same, we need to insert a segment for the rise - print("------- insert vertical rise (%s)" % i) + #print("------- insert vertical rise (%s)" % i) self.edges.append(Part.Edge(Part.Line(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) self.p1 = i self.state = self.P1 return edge if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - print("------- consumed (%s)" % i) + #print("------- consumed (%s)" % i) e = edge tail = None else: - print("------- split at (%s)" % i) + #print("------- split at (%s)" % i) e, tail = self.tag.splitEdgeAt(edge, i) self.p1 = e.valueAt(edge.LastParameter) self.edges.append(self.tag.mapEdgeToSolid(e)) self.state = self.P1 return tail # no intersection, the entire edge fits between P0 and P1 - print("------- no intersection") + #print("------- no intersection") self.edges.append(self.tag.mapEdgeToSolid(edge)) return None @@ -232,36 +239,46 @@ class Tag: def intersectP1(self, edge): - print("----- P1 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #print("----- P1 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.LastParameter)) if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + #print("----- P1 edge too short") self.edges.append(self.tag.mapEdgeToSolid(edge)) return self if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + #print("----- P1 edge at end") e = edge tail = None else: + #print("----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) e, tail = self.tag.splitEdgeAt(edge, i) - self.p2 = e.valueAt(edge.LastParameter) + f = e.valueAt(e.FirstParameter) + l = e.valueAt(e.LastParameter) + #print("----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) + self.p2 = e.valueAt(e.LastParameter) self.state = self.P2 else: + #print("----- P1 no intersect") e = edge tail = None + f = e.valueAt(e.FirstParameter) + l = e.valueAt(e.LastParameter) + #print("----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) self.edges.append(self.moveEdgeToPlateau(e)) return tail def intersectP2(self, edge): - print("----- P2 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #print("----- P2 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.valueAt(edge.LastParameter)) if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - print("------- insert exit plunge (%s)" % i) + #print("------- insert exit plunge (%s)" % i) self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) e = None tail = edge elif PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - print("------- entire segment added (%s)" % i) + #print("------- entire segment added (%s)" % i) e = edge tail = None else: @@ -280,8 +297,8 @@ class Tag: return tail def intersect(self, edge): - print("") - print(" >>> (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #print("") + #print(" >>> (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) if edge and self.state == self.P0: edge = self.intersectP0(edge) if edge and self.state == self.P1: @@ -294,25 +311,62 @@ class Tag: def splitEdgeAt(self, edge, pt): p = edge.Curve.parameter(pt) wire = edge.split(p) + # split does not carry the Placement of the original curve foward ... + wire.transformShape(edge.Placement.toMatrix()) return wire.Edges def mapEdgeToSolid(self, edge): - print("mapEdgeToSolid: (%s %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + #print("mapEdgeToSolid: (%s %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) p1a = edge.valueAt(edge.FirstParameter) p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) e1 = Part.Edge(Part.Line(p1a, p1b)) p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection - print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) + #print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) p2a = edge.valueAt(edge.LastParameter) p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) e2 = Part.Edge(Part.Line(p2a, p2b)) p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection - print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) + #print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) if type(edge.Curve) == Part.Line: return Part.Edge(Part.Line(p1, p2)) + m = FreeCAD.Matrix() + m.unity() + pd = p2 - p1 + + if type(edge.Curve) == Part.Circle: + m.A32 = pd.z / pd.y + m.A34 = - m.A32 + if pd.z < 0: + m.A34 *= p2.y + else: + m.A34 *= p1.y + e = edge.transformGeometry(m).Edges[0] + return e + + # it's already a helix, just need to lift it to the plateau + m.A33 = pd.z / (p2a.z - p1a.z) + m.A34 = (1 - m.A33) + if pd.z < 0: + m.A34 *= p2a.z + else: + m.A34 *= p1a.z + + #print + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + #print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, m.A33)) + #print("**** %.2f %.2f (%.2f - %.2f)" % (pd.z, p2a.z-p1a.z, p2a.z, p1a.z)) + e = edge.transformGeometry(m).Edges[0] + pf = e.valueAt(e.FirstParameter) + pl = e.valueAt(e.LastParameter) + #print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) + #raise Exception("mensch") + return e + + def filterIntersections(self, pts, face): if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: return filter(lambda pt: pt.z >= self.bottom() and pt.z <= self.top(), pts) @@ -331,7 +385,7 @@ class Tag: pts.extend(ps) if pts: closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - print("--pts: %s -> %s" % (pts, closest)) + #print("--pts: %s -> %s" % (pts, closest)) return closest return None @@ -351,10 +405,10 @@ class Tag: e,tail = self.splitEdgeAt(edge, i) inters.edges.append(e) return inters.intersect(tail) - else: - print("No intersection found.") - else: - print("Fly by") + #else: + # print("No intersection found.") + #else: + # print("Fly by") # if we get here there is no intersection with the tag inters.state = self.Intersection.P3 inters.tail = edge diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index e5e8917bd6..4d47684982 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -250,7 +250,6 @@ class TestTag02SquareTag(PathTestBase): # ============= def test10(self): """Verify intersection of square tag with an arc.""" - print tag = Tag( 0, 0, 8, 3, 90, True, 0) p1 = Vector(10, -10, 0) p2 = Vector(10, +10, 0) @@ -259,7 +258,6 @@ class TestTag02SquareTag(PathTestBase): # ============= pi = Vector(0.8, -3.919184, 0) pj = Vector(0.8, +3.919184, 0) - print("(%s - %s) - %s" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter), edge.valueAt((edge.FirstParameter + edge.LastParameter)/2))) s = tag.intersect(edge) self.assertTrue(s.isComplete()) self.assertEqual(len(s.edges), 4) @@ -269,6 +267,25 @@ class TestTag02SquareTag(PathTestBase): # ============= self.assertLine(s.edges[3], pj + Vector(0, 0, 3), pj) self.assertCurve(s.tail, pj, Vector(4.486010, +8.342417, 0), p2) + def test20(self): + """Verify intersection of square tag with a helix.""" + tag = Tag( 0, 0, 8, 3, 90, True, 0) + p1 = Vector(10, -10, 0) + p2 = Vector(10, +10, 2) + edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) + + pi = Vector(0.8, -3.919184, 0.743623) + pj = Vector(0.8, +3.919184, 1.256377) + + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertEqual(len(s.edges), 4) + self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0.371812), pi) + self.assertLine(s.edges[1], pi, pi + Vector(0, 0, 3-pi.z)) + self.assertCurve(s.edges[2], pi + Vector(0, 0, 3-pi.z), Vector(0, 0, 3), pj + Vector(0, 0, 3-pj.z)) + self.assertLine(s.edges[3], pj + Vector(0, 0, 3-pj.z), pj) + self.assertCurve(s.tail, pj, Vector(4.486010, +8.342417, 1.628188), p2) + class TestTag03TrapezoidTag(PathTestBase): # ============= """Unit tests for trapezoid tags.""" @@ -481,6 +498,48 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertTrue(s.isComplete()) self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + def test10(self): + """Verify intersection with an arc.""" + tag = Tag( 0, 0, 8, 3, 45, True, 0) + p1 = Vector(10, -10, 0) + p2 = Vector(10, +10, 0) + edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10}), p1) + + pi = Vector(0.8, -3.919184, 0) + pj = Vector(0.05, -0.998749, 3) + pk = Vector(0.05, +0.998749, 3) + pl = Vector(0.8, +3.919184, 0) + + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertEqual(len(s.edges), 4) + self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0), pi) + self.assertCurve(s.edges[1], pi, Vector(0.314296, -2.487396, 1.470795), pj) + self.assertCurve(s.edges[2], pj, Vector(0, 0, 3), pk) + self.assertCurve(s.edges[3], pk, Vector(.3142960, +2.487396, 1.470795), pl) + self.assertCurve(s.tail, pl, Vector(4.486010, +8.342417, 0), p2) + + def test20(self): + """Verify intersection with a helix.""" + tag = Tag( 0, 0, 8, 3, 45, True, 0) + p1 = Vector(10, -10, 0) + p2 = Vector(10, +10, 2) + edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) + + pi = Vector(0.513574, -3.163498, 0.795085) + pj = Vector(0.050001, -0.998749, 3) + pk = Vector(0.050001, +0.998749, 3) + pl = Vector(0.397586, +2.791711, 1.180119) + + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertEqual(len(s.edges), 4) + self.assertCurve(s.edges[0], p1, Vector(4.153420, -8.112798, 0.397543), pi) + self.assertCurve(s.edges[1], pi, Vector(0.221698, -2.093992, 1.897543), pj) + self.assertCurve(s.edges[2], pj, Vector(0, 0, 3), pk) + self.assertCurve(s.edges[3], pk, Vector(0.182776, 1.903182, 2.090060), pl) + self.assertCurve(s.tail, pl, Vector(3.996548, +7.997409, 1.590060), p2) + class TestTag04TriangularTag(PathTestBase): # ======================== """Unit tests for tags that take on a triangular shape.""" @@ -566,3 +625,41 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) self.assertIsNotNone(i.tail) + def test10(self): + """Verify intersection with an arc.""" + tag = Tag( 0, 0, 8, 7, 45, True, 0) + p1 = Vector(10, -10, 0) + p2 = Vector(10, +10, 0) + edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10}), p1) + + pi = Vector(0.8, -3.919184, 0) + pj = Vector(0.0, 0.0, 4) + pk = Vector(0.8, +3.919184, 0) + + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertEqual(len(s.edges), 3) + self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0), pi) + self.assertCurve(s.edges[1], pi, Vector(0.202041, -2., 1.958759), pj) + self.assertCurve(s.edges[2], pj, Vector(0.202041, +2., 1.958759), pk) + self.assertCurve(s.tail, pk, Vector(4.486010, +8.342417, 0), p2) + + def test20(self): + """Verify intersection with a helix.""" + tag = Tag( 0, 0, 8, 7, 45, True, 0) + p1 = Vector(10, -10, 0) + p2 = Vector(10, +10, 2) + edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) + + pi = Vector(0.513574, -3.163498, 0.795085) + pj = Vector(0.000001, 0, 4) + pk = Vector(0.397586, +2.791711, 1.180119) + + s = tag.intersect(edge) + self.assertTrue(s.isComplete()) + self.assertEqual(len(s.edges), 3) + self.assertCurve(s.edges[0], p1, Vector(4.153420, -8.112798, 0.397543), pi) + self.assertCurve(s.edges[1], pi, Vector(0.129229, -1.602457, 2.397542), pj) + self.assertCurve(s.edges[2], pj, Vector(0.099896, 1.409940, 2.590059), pk) + self.assertCurve(s.tail, pk, Vector(3.996548, +7.997409, 1.590060), p2) + From 23196b4c69d93bf651d3823f6be8d981c0c7ff87 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 4 Dec 2016 01:18:15 -0800 Subject: [PATCH 018/144] Fixed most of the state machine issues, still a problem with intersection of edges. Need to rebase. --- .../PathScripts/PathDressupHoldingTags.py | 381 ++++++++++++------ src/Mod/Path/PathScripts/PathGeom.py | 2 + .../PathTests/TestPathDressupHoldingTags.py | 10 +- 3 files changed, 274 insertions(+), 119 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 3441eb0423..56211686fc 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -26,6 +26,7 @@ import FreeCADGui import DraftGeomUtils import Path import Part +import copy import math from PathScripts import PathUtils @@ -47,6 +48,19 @@ except AttributeError: return QtGui.QApplication.translate(context, text, disambig) debugDressup = True +debugComponents = ['P0', 'P1', 'P2', 'P3'] + +def debugPrint(comp, msg): + if debugDressup and comp in debugComponents: + print(msg) + +def debugEdge(edge, prefix, comp = None): + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + if comp: + debugPrint(comp, "%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) + else: + print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) def debugMarker(vector, label, color = None, radius = 0.5): if debugDressup: @@ -57,6 +71,17 @@ def debugMarker(vector, label, color = None, radius = 0.5): if color: obj.ViewObject.ShapeColor = color +def debugCylinder(vector, r, height, label, color = None): + if debugDressup: + obj = FreeCAD.ActiveDocument.addObject("Part::Cylinder", label) + obj.Label = label + obj.Radius = r + obj.Height = height + obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0)) + obj.ViewObject.Transparency = 90 + if color: + obj.ViewObject.ShapeColor = color + movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] movestraight = ['G1', 'G01'] movecw = ['G2', 'G02'] @@ -66,21 +91,30 @@ movearc = movecw + moveccw slack = 0.0000001 def pathCommandForEdge(edge): - pt = edge.Curve.EndPoint + pt = edge.valueAt(edge.LastParameter) params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} if type(edge.Curve) == Part.Line: - return Part.Command('G1', params) - - p1 = edge.Curve.StartPoint - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - p3 = pt - if Side.Left == Side.of(p2 - p1, p3 - p2): - cmd = 'G3' + command = Path.Command('G1', params) else: - cmd = 'G2' - offset = pt1 - edge.Curve.Center - params.update({'I': offset.x, 'J': offset.y, 'K': offset.z}) - return Part.Command(cmd, params) + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = pt + if Side.Left == Side.of(p2 - p1, p3 - p2): + cmd = 'G3' + else: + cmd = 'G2' + pa = PathGeom.xy(p1) + pb = PathGeom.xy(p2) + pc = PathGeom.xy(p3) + pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center + print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) + print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) + offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 + print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) + params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) + command = Path.Command(cmd, params) + print command + return command class Tag: @@ -117,7 +151,7 @@ class Tag: return self.z + self.actualHeight def centerLine(self): - return Part.Line(self.originAt(self.bottom()), self.originAt(self.top())) + return Part.Line(self.originAt(self.bottom() - 1), self.originAt(self.top() + 1)) def createSolidsAt(self, z): self.z = z @@ -157,6 +191,7 @@ class Tag: # # If no intersection occured the Intersection can be viewed as being # at P3 with no additional edges. + Pnone = 1 P0 = 2 P1 = 3 P2 = 4 @@ -164,18 +199,23 @@ class Tag: def __init__(self, tag): self.tag = tag - self.state = self.P3 + self.state = self.Pnone self.edges = [] self.tail = None def isComplete(self): - return self.state == self.P3 + return self.state == self.Pnone or self.state == self.P3 + + def hasEdges(self): + return self.state != self.Pnone def moveEdgeToPlateau(self, edge): if type(edge.Curve) is Part.Line: + e = copy.copy(edge) z = edge.Curve.StartPoint.z elif type(edge.Curve) is Part.Circle: # it's an arc + e = copy.copy(edge) z = edge.Curve.Center.z else: # it's a helix -> transform to arc @@ -183,45 +223,54 @@ class Tag: p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) p2 = PathGeom.xy(edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)) p3 = PathGeom.xy(edge.valueAt(edge.LastParameter)) - edge = Part.Edge(Part.Arc(p1, p2, p3)) - edge.translate(Vector(0, 0, self.tag.top() - z)) - return edge + e = Part.Edge(Part.Arc(p1, p2, p3)) + print("-------- moveEdgeToPlateau") + e.translate(Vector(0, 0, self.tag.top() - z)) + return e def intersectP0Core(self, edge): - #print("----- P0 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + debugPrint('P0', "----- P0-core") i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.FirstParameter)) if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): # if P0 and P1 are the same, we need to insert a segment for the rise - #print("------- insert vertical rise (%s)" % i) + debugPrint('P0', "------- insert vertical rise (%s)" % i) self.edges.append(Part.Edge(Part.Line(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) self.p1 = i self.state = self.P1 return edge if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - #print("------- consumed (%s)" % i) + debugPrint('P0', "------- consumed (%s)" % i) e = edge tail = None else: - #print("------- split at (%s)" % i) + debugPrint('P0', "------- split at (%s)" % i) e, tail = self.tag.splitEdgeAt(edge, i) self.p1 = e.valueAt(edge.LastParameter) - self.edges.append(self.tag.mapEdgeToSolid(e)) + self.edges.extend(self.tag.mapEdgeToSolid(e, 'P0-core-1')) self.state = self.P1 return tail # no intersection, the entire edge fits between P0 and P1 - #print("------- no intersection") - self.edges.append(self.tag.mapEdgeToSolid(edge)) + debugPrint('P0', "------- no intersection") + self.edges.extend(self.tag.mapEdgeToSolid(edge, 'P0-core-2')) return None def intersectP0(self, edge): + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + debugPrint('P0', "----- P0 %s(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) + if self.tag.core: return self.intersectP0Core(edge) + # if we have no core the tip is the origin of the Tag line = Part.Edge(self.tag.centerLine()) - i = DraftGeomUtils.findIntersection(line, edge) + debugEdge(line, "------- center line", 'P0') + #i = DraftGeomUtils.findIntersection(line, edge, True) + i = line.Curve.intersect(edge) if i: + debugPrint('P0', '------- P0 split @ (%.2f, %.2f, %.2f)' % (i[0].x, i[0].y, i[0].z)) if PathGeom.pointsCoincide(i[0], edge.valueAt(edge.LastParameter)): e = edge tail = None @@ -231,69 +280,82 @@ class Tag: self.p1 = i[0] self.p2 = i[0] else: + debugPrint('P0', '------- P0 no intersect') e = edge tail = None - self.edges.append(self.tag.mapEdgeToSolid(e)) + self.edges.extend(self.tag.mapEdgeToSolid(e, 'P0')) return tail def intersectP1(self, edge): - #print("----- P1 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.LastParameter)) if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - #print("----- P1 edge too short") - self.edges.append(self.tag.mapEdgeToSolid(edge)) - return self + debugPrint('P1', "----- P1 edge too short") + #self.edges.extend(self.tag.mapEdgeToSolid(edge, 'P1')) + self.edges.append(self.moveEdgeToPlateau(edge)) + return None if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - #print("----- P1 edge at end") + debugPrint('P1', "----- P1 edge at end") e = edge tail = None else: - #print("----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) + debugPrint('P1', "----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) e, tail = self.tag.splitEdgeAt(edge, i) f = e.valueAt(e.FirstParameter) l = e.valueAt(e.LastParameter) - #print("----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) + debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) self.p2 = e.valueAt(e.LastParameter) self.state = self.P2 else: - #print("----- P1 no intersect") + debugPrint('P1', "----- P1 no intersect") e = edge tail = None f = e.valueAt(e.FirstParameter) l = e.valueAt(e.LastParameter) - #print("----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) + debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) self.edges.append(self.moveEdgeToPlateau(e)) return tail def intersectP2(self, edge): - #print("----- P2 (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + debugPrint('P2', "----- P2 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.valueAt(edge.LastParameter)) if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - #print("------- insert exit plunge (%s)" % i) + debugPrint('P2', "------- insert exit plunge (%s)" % i) self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) e = None tail = edge elif PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - #print("------- entire segment added (%s)" % i) + debugPrint('P2', "------- entire segment added (%s)" % i) e = edge tail = None else: e, tail = self.tag.splitEdgeAt(edge, i) - #if tail: - # print("----- P3 (%s - %s)" % (tail.valueAt(edge.FirstParameter), tail.valueAt(edge.LastParameter))) - #else: - # print("----- P3 (---)") + if tail: + pf = tail.valueAt(tail.FirstParameter) + pl = tail.valueAt(tail.LastParameter) + debugPrint('P3', "----- P3 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) + else: + debugPrint('P3', "----- P3 (---)") self.state = self.P3 self.tail = tail else: + debugPrint('P2', "----- P2 no intersection") e = edge tail = None if e: - self.edges.append(self.tag.mapEdgeToSolid(e)) + pf = e.valueAt(e.FirstParameter) + pl = e.valueAt(e.LastParameter) + s = 'P2' if self.state == self.P2 else 'P3' + debugPrint(s, "----- %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (s, pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) + self.edges.extend(self.tag.mapEdgeToSolid(e, 'P2')) return tail def intersect(self, edge): @@ -310,27 +372,56 @@ class Tag: def splitEdgeAt(self, edge, pt): p = edge.Curve.parameter(pt) + + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + print("-------- splitAt((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): (%.2f, %.2f, %.2f)) -> param[%.2f -> %.2f]: %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, pt.x, pt.y, pt.z, edge.FirstParameter, edge.LastParameter, p)) + wire = edge.split(p) # split does not carry the Placement of the original curve foward ... wire.transformShape(edge.Placement.toMatrix()) return wire.Edges - def mapEdgeToSolid(self, edge): - #print("mapEdgeToSolid: (%s %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) + def mapEdgeToSolid(self, edge, label): + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + print("--------- mapEdgeToSolid-%s: %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (label, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) + p1a = edge.valueAt(edge.FirstParameter) p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) + p1a.z = self.bottom() e1 = Part.Edge(Part.Line(p1a, p1b)) p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection - #print(" p1: (%s %s) -> %s" % (p1a, p1b, p1)) + print("---------- p1: (%s %s) -> %s %d" % (p1a, p1b, p1, self.solid.isInside(p1, 0.0000001, True))) + if not p1: + raise Exception('no p1') + return [] p2a = edge.valueAt(edge.LastParameter) p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) + p2a.z = self.bottom() e2 = Part.Edge(Part.Line(p2a, p2b)) p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection - #print(" p2: (%s %s) -> %s" % (p2a, p2b, p2)) + if not p2: + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt(edge.LastParameter) + print("---------- p1: %d%d" % (self.solid.isInside(p1, 0.0000001, True), self.solid.isInside(p1, 0.0000001, False))) + print("---------- p2: %d%d" % (self.solid.isInside(p2, 0.0000001, True), self.solid.isInside(p2, 0.0000001, False))) + #if not self.solid.isInside(p1, 0.0000001, False): + # p1 is on the solid - + raise Exception('no p2') + return [] + + print("---------- %s - %s" % (p1, p2)) + print("---------- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p2.x, p2.y, p2.z, p1.x, p1.y, p1.z)) + + if PathGeom.pointsCoincide(p1, p2): + return [] if type(edge.Curve) == Part.Line: - return Part.Edge(Part.Line(p1, p2)) + e = Part.Edge(Part.Line(p1, p2)) + debugEdge(e, "-------- >>") + return [e] m = FreeCAD.Matrix() m.unity() @@ -344,15 +435,16 @@ class Tag: else: m.A34 *= p1.y e = edge.transformGeometry(m).Edges[0] - return e + debugEdge(e, "-------- >>") + return [e] # it's already a helix, just need to lift it to the plateau - m.A33 = pd.z / (p2a.z - p1a.z) + m.A33 = pd.z / (edge.valueAt(edge.LastParameter).z - edge.valueAt(edge.FirstParameter).z) m.A34 = (1 - m.A33) if pd.z < 0: - m.A34 *= p2a.z + m.A34 *= edge.valueAt(edge.LastParameter).z else: - m.A34 *= p1a.z + m.A34 *= edge.valueAt(edge.FirstParameter).z #print pf = edge.valueAt(edge.FirstParameter) @@ -364,53 +456,89 @@ class Tag: pl = e.valueAt(e.LastParameter) #print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) #raise Exception("mensch") - return e + debugEdge(e, "-------- >>") + return [e] def filterIntersections(self, pts, face): if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: + #print("it's a cone/cylinder, checking z") return filter(lambda pt: pt.z >= self.bottom() and pt.z <= self.top(), pts) if type(face.Surface) == Part.Plane: + #print("it's a plane, checking R") c = face.Edges[0].Curve if (type(c) == Part.Circle): return filter(lambda pt: (pt - c.Center).Length <= c.Radius, pts) print("==== we got a %s" % face.Surface) + def isPointOnEdge(self, pt, edge): + param = edge.Curve.parameter(pt) + if edge.FirstParameter <= param <= edge.LastParameter: + return True + if edge.LastParameter <= param <= edge.FirstParameter: + return True + if PathGeom.isRoughly(edge.FirstParameter, param) or PathGeom.isRoughly(edge.LastParameter, param): + return True + print("-------- X %.2f <= %.2f <=%.2f (%.2f, %.2f, %.2f)" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z)) + return False + def nextIntersectionClosestTo(self, edge, solid, refPt): + ef = edge.valueAt(edge.FirstParameter) + el = edge.valueAt(edge.LastParameter) + print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.z, el.x, el.y, el.z, refPt.x, refPt.y, refPt.z)) + pts = [] for index, face in enumerate(solid.Faces): i = edge.Curve.intersect(face.Surface)[0] ps = self.filterIntersections([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i], face) - pts.extend(ps) + pts.extend(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)) + if len(ps) != len(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)): + filtered = filter(lambda pt: self.isPointOnEdge(pt, edge), ps) + print("-------- ++ len(ps)=%d, len(filtered)=%d" % (len(ps), len(filtered))) + for p in ps: + included = '+' if p in filtered else '-' + print("-------- %s (%.2f, %.2f, %.2f)" % (included, p.x, p.y, p.z)) if pts: closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - #print("--pts: %s -> %s" % (pts, closest)) + for p in pts: + print("-------- - intersect pt : (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) + print("-------- -> (%.2f, %.2f, %.2f)" % (closest.x, closest.y, closest.z)) return closest + + print("-------- -> None") return None - def intersect(self, edge): + def intersect(self, edge, check = True): + print("--- intersect") inters = self.Intersection(self) - if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top(): - i = self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(edge.FirstParameter)) - if i: - inters.state = self.Intersection.P0 - inters.p0 = i - if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - inters.edges.append(edge) - return inters - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - tail = edge + if check: + if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top(): + i = self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(edge.FirstParameter)) + if i: + print("---- (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) + inters.state = self.Intersection.P0 + inters.p0 = i + if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + print("---- entire edge consumed.") + inters.edges.append(edge) + return inters + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + print("---- nothing of edge consumed.") + tail = edge + else: + print("---- split edge") + e,tail = self.splitEdgeAt(edge, i) + inters.edges.append(e) + return inters.intersect(tail) else: - e,tail = self.splitEdgeAt(edge, i) - inters.edges.append(e) - return inters.intersect(tail) - #else: - # print("No intersection found.") - #else: - # print("Fly by") + print("---- No intersection found.") + else: + print("---- Fly by") + else: + print("---- skipped") # if we get here there is no intersection with the tag - inters.state = self.Intersection.P3 + inters.state = self.Intersection.Pnone inters.tail = edge return inters @@ -418,7 +546,7 @@ class PathData: def __init__(self, obj): self.obj = obj self.wire = PathGeom.wireForPath(obj.Base.Path) - self.edges = wire.Edges + self.edges = self.wire.Edges self.base = self.findBottomWire(self.edges) # determine overall length self.length = self.base.Length @@ -430,20 +558,30 @@ class PathData: bottom = [e for e in edges if e.Vertexes[0].Point.z == minZ and e.Vertexes[1].Point.z == minZ] wire = Part.Wire(bottom) if wire.isClosed(): - return Part.Wire(self.sortedBase(bottom)) + #return Part.Wire(self.sortedBase(bottom)) + return wire # if we get here there are already holding tags, or we're not looking at a profile # let's try and insert the missing pieces - another day raise ValueError("Selected path doesn't seem to be a Profile operation.") def sortedBase(self, base): # first find the exit point, where base wire is closed - edges = [e for e in self.edges if e.valueAt(edge.FirstParameter).z == self.minZ and e.valueAt(edge.LastParameter).z != self.maxZ] - exit = sorted(edges, key=lambda e: -e.valueAt(edge.LastParameter).z)[0] - pt = exit.valueAt(edge.FirstParameter) + edges = [e for e in self.edges if e.valueAt(e.FirstParameter).z == self.minZ and e.valueAt(e.LastParameter).z != self.maxZ] + exit = sorted(edges, key=lambda e: -e.valueAt(e.LastParameter).z)[0] + pt = exit.valueAt(exit.FirstParameter) # then find the first base edge, and sort them until done ordered = [] while base: - edge = [e for e in base if e.valueAt(edge.FirstParameter) == pt][0] + edges = [e for e in base if e.valueAt(e.FirstParameter) == pt] + if not edges: + print ordered + print base + print("(%.2f, %.2f, %.2f)" % (pt.x, pt.y, pt.z)) + for e in base: + pf = e.valueAt(e.FirstParameter) + pl = e.valueAt(e.LastParameter) + print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) + edge = edges[0] ordered.append(edge) base.remove(edge) pt = edge.valueAt(edge.LastParameter) @@ -467,7 +605,7 @@ class PathData: return (edges[0], edges[-1]) def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): - #print("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) + print("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) #for e in self.base.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) @@ -601,35 +739,49 @@ class ObjectDressup: return closest return None - def createPath(self, edges, tagSolids): + def createPath(self, edges, tags): commands = [] - i = 0 - while i != len(edges): - edge = edges[i] - while edge: - for solid in tagSolids: - for face in solid.Faces: - pt = self.tagIntersection(face, edge) - if pt: - if pt == edge.Curve.StartPoint: - pt - elif pt != edge.Curve.EndPoint: - parameter = edge.Curve.parameter(pt) - wire = edge.split(parameter) - commands.append(pathCommandForEdge(wire.Edges[0])) - edge = wire.Edges[1] - break; - else: - commands.append(pathCommandForEdge(edge)) - edge = None - i += 1 - break - if not edge: - break + lastEdge = 0 + lastTag = 0 + sameTag = None + t = 0 + inters = None + edge = None + + while edge or lastEdge < len(edges): + print("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) + if not edge: + edge = edges[lastEdge] + debugEdge(edge, "======= new edge: %d/%d" % (lastEdge, len(edges))) + lastEdge += 1 + sameTag = None + + if inters: + inters = inters.intersect(edge) + else: + tIndex = (t + lastTag) % len(tags) + t += 1 + print("<<<<< lastTag=%d, t=%d, tIndex=%d, sameTag=%s >>>>>>" % (lastTag, t, tIndex, sameTag)) + inters = tags[tIndex].intersect(edge, True or tIndex != sameTag) + edge = inters.tail + + if inters.isComplete(): + if inters.hasEdges(): + sameTag = (t + lastTag - 1) % len(tags) + lastTag = sameTag + t = 1 + for e in inters.edges: + commands.append(pathCommandForEdge(e)) + inters = None + + if t >= len(tags): + # gone through all tags, consume edge and move on if edge: commands.append(pathCommandForEdge(edge)) - edge = None - return self.obj.Path + edge = None + t = 0 + + return Path.Path(commands) def execute(self, obj): @@ -648,14 +800,14 @@ class ObjectDressup: return if hasattr(obj, 'Tags') and obj.Tags: - if self.fingerprint == obj.Tags: + if False and self.fingerprint == obj.Tags: print("execute - cache valid") return print("execute - tags from property") tags = [Tag.FromString(tag) for tag in obj.Tags] else: print("execute - default tags") - tags = self.generateTags(obj, 4.) + tags = self.generateTags(obj, 2.) if not tags: print("execute - no tags") @@ -663,12 +815,14 @@ class ObjectDressup: obj.Path = obj.Base.Path return + print("execute - %d tags" % (len(tags))) tagID = 0 for tag in tags: tagID += 1 if tag.enabled: #print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) - debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + #debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + debugCylinder(tag.originAt(pathData.minZ), tag.width/2, tag.actualHeight, "tag-%02d" % tagID) tags = pathData.sortedTags(tags) for tag in tags: @@ -677,8 +831,7 @@ class ObjectDressup: self.fingerprint = [tag.toString() for tag in tags] self.tags = tags - #obj.Path = self.createPath(pathData.edges, tags) - obj.Path = self.Base.Path + obj.Path = self.createPath(pathData.edges, tags) def setTags(self, obj, tags): obj.Tags = [tag.toString() for tag in tags] @@ -690,7 +843,7 @@ class ObjectDressup: return self.setup(obj).generateTags(obj, 4) def setup(self, obj): - if not hasattr(self, "pathData") or not self.pathData: + if False or not hasattr(self, "pathData") or not self.pathData: try: pathData = PathData(obj) except ValueError: diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 3effa097f5..2fff4b1e07 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -135,6 +135,8 @@ class PathGeom: endPoint = cls.commandEndPoint(cmd, startPoint) if (cmd.Name in cls.CmdMoveStraight) or (cmd.Name in cls.CmdMoveFast): + if cls.pointsCoincide(startPoint, endPoint): + return None return Part.Edge(Part.LineSegment(startPoint, endPoint)) if cmd.Name in cls.CmdMoveArc: diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 4d47684982..13e4d91038 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -544,7 +544,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= class TestTag04TriangularTag(PathTestBase): # ======================== """Unit tests for tags that take on a triangular shape.""" - def test00(self): + def xtest00(self): """Verify intersection of triangular tag with line ending at tag start.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0))) @@ -555,7 +555,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint) self.assertIsNone(i.tail) - def test01(self): + def xtest01(self): """Verify intersection of triangular tag with line ending between P0 and P1.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(3, 0, 0))) @@ -576,7 +576,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLine(i.edges[2], p2, p3) self.assertIsNone(i.tail) - def test02(self): + def xtest02(self): """Verify proper down plunge on exit of triangular tag.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) @@ -603,7 +603,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertEqual(i.state, Tag.Intersection.P2) self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - def test03(self): + def xtest03(self): """Verify triangular tag shap on intersection.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) @@ -625,7 +625,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) self.assertIsNotNone(i.tail) - def test10(self): + def xtest10(self): """Verify intersection with an arc.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) p1 = Vector(10, -10, 0) From 198ab6db2ef2f368825c8b14d80cd7a99976ae8a Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 4 Dec 2016 07:16:58 -0800 Subject: [PATCH 019/144] Rebase on new Line/LineSegment code. --- src/Mod/Draft/DraftGeomUtils.py | 2 +- .../PathScripts/PathDressupHoldingTags.py | 24 +-- src/Mod/Path/PathTests/PathTestUtils.py | 6 +- .../PathTests/TestPathDressupHoldingTags.py | 146 +++++++++--------- 4 files changed, 89 insertions(+), 89 deletions(-) diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index d6b04f4c14..45297b3df7 100755 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -454,7 +454,7 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F return int else: - # print("DraftGeomUtils: Unsupported curve type: (" + str(edge1.Curve) + ", " + str(edge2.Curve) + ")") + print("DraftGeomUtils: Unsupported curve type: (" + str(edge1.Curve) + ", " + str(edge2.Curve) + ")") return [] def wiresIntersect(wire1,wire2): diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 56211686fc..60c76861a5 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -93,7 +93,7 @@ slack = 0.0000001 def pathCommandForEdge(edge): pt = edge.valueAt(edge.LastParameter) params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} - if type(edge.Curve) == Part.Line: + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: command = Path.Command('G1', params) else: p1 = edge.valueAt(edge.FirstParameter) @@ -151,7 +151,7 @@ class Tag: return self.z + self.actualHeight def centerLine(self): - return Part.Line(self.originAt(self.bottom() - 1), self.originAt(self.top() + 1)) + return Part.LineSegment(self.originAt(self.bottom() - 1), self.originAt(self.top() + 1)) def createSolidsAt(self, z): self.z = z @@ -210,9 +210,9 @@ class Tag: return self.state != self.Pnone def moveEdgeToPlateau(self, edge): - if type(edge.Curve) is Part.Line: + if type(edge.Curve) is Part.Line or type(edge.Curve) is Part.LineSegment: e = copy.copy(edge) - z = edge.Curve.StartPoint.z + z = edge.valueAt(edge.FirstParameter).z elif type(edge.Curve) is Part.Circle: # it's an arc e = copy.copy(edge) @@ -236,7 +236,7 @@ class Tag: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): # if P0 and P1 are the same, we need to insert a segment for the rise debugPrint('P0', "------- insert vertical rise (%s)" % i) - self.edges.append(Part.Edge(Part.Line(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) + self.edges.append(Part.Edge(Part.LineSegment(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) self.p1 = i self.state = self.P1 return edge @@ -267,8 +267,8 @@ class Tag: # if we have no core the tip is the origin of the Tag line = Part.Edge(self.tag.centerLine()) debugEdge(line, "------- center line", 'P0') - #i = DraftGeomUtils.findIntersection(line, edge, True) - i = line.Curve.intersect(edge) + i = DraftGeomUtils.findIntersection(line, edge, True) + #i = line.Curve.intersect(edge) if i: debugPrint('P0', '------- P0 split @ (%.2f, %.2f, %.2f)' % (i[0].x, i[0].y, i[0].z)) if PathGeom.pointsCoincide(i[0], edge.valueAt(edge.LastParameter)): @@ -329,7 +329,7 @@ class Tag: if i: if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): debugPrint('P2', "------- insert exit plunge (%s)" % i) - self.edges.append(Part.Edge(Part.Line(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) + self.edges.append(Part.Edge(Part.LineSegment(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) e = None tail = edge elif PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): @@ -390,7 +390,7 @@ class Tag: p1a = edge.valueAt(edge.FirstParameter) p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) p1a.z = self.bottom() - e1 = Part.Edge(Part.Line(p1a, p1b)) + e1 = Part.Edge(Part.LineSegment(p1a, p1b)) p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection print("---------- p1: (%s %s) -> %s %d" % (p1a, p1b, p1, self.solid.isInside(p1, 0.0000001, True))) if not p1: @@ -400,7 +400,7 @@ class Tag: p2a = edge.valueAt(edge.LastParameter) p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) p2a.z = self.bottom() - e2 = Part.Edge(Part.Line(p2a, p2b)) + e2 = Part.Edge(Part.LineSegment(p2a, p2b)) p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection if not p2: p1 = edge.valueAt(edge.FirstParameter) @@ -418,8 +418,8 @@ class Tag: if PathGeom.pointsCoincide(p1, p2): return [] - if type(edge.Curve) == Part.Line: - e = Part.Edge(Part.Line(p1, p2)) + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + e = Part.Edge(Part.LineSegment(p1, p2)) debugEdge(e, "-------- >>") return [e] diff --git a/src/Mod/Path/PathTests/PathTestUtils.py b/src/Mod/Path/PathTests/PathTestUtils.py index 029ca79140..518eeb90cb 100644 --- a/src/Mod/Path/PathTests/PathTestUtils.py +++ b/src/Mod/Path/PathTests/PathTestUtils.py @@ -45,9 +45,9 @@ class PathTestBase(unittest.TestCase): def assertLine(self, edge, pt1, pt2): """Verify that edge is a line from pt1 to pt2.""" - self.assertIs(type(edge.Curve), Part.LineSegment) - self.assertCoincide(edge.Curve.StartPoint, pt1) - self.assertCoincide(edge.Curve.EndPoint, pt2) + self.assertIs(type(edge.Curve), Part.Line) + self.assertCoincide(edge.valueAt(edge.FirstParameter), pt1) + self.assertCoincide(edge.valueAt(edge.LastParameter), pt2) def assertLines(self, edgs, tail, points): """Verify that the edges match the polygon resulting from points.""" diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 13e4d91038..49e6be09cc 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -97,7 +97,7 @@ class TestTag02SquareTag(PathTestBase): # ============= tag = Tag( 0, 0, 4, 7, 90, True, 0) pt1 = Vector(+5, 5, 0) pt2 = Vector(-5, 5, 0) - edge = Part.Edge(Part.Line(pt1, pt2)) + edge = Part.Edge(Part.LineSegment(pt1, pt2)) i = tag.intersect(edge) self.assertIsNotNone(i) @@ -109,18 +109,18 @@ class TestTag02SquareTag(PathTestBase): # ============= def test01(self): """Verify intersection of square tag with line ending at tag start.""" tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(4, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P0) self.assertEqual(len(i.edges), 1) - self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint) + self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) self.assertIsNone(i.tail) def test02(self): """Verify intersection of square tag with line ending between P1 and P2.""" tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P1) @@ -128,13 +128,13 @@ class TestTag02SquareTag(PathTestBase): # ============= p1 = Vector(4, 0, 0) p2 = Vector(4, 0, 3) p3 = Vector(1, 0, 3) - self.assertLine(i.edges[0], edge.Curve.StartPoint, p1) + self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), p1) self.assertLine(i.edges[1], p1, p2) self.assertLine(i.edges[2], p2, p3) self.assertIsNone(i.tail) # verify we stay in P1 if we add another segment - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(0, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(0, 0, 0))) i = i.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P1) self.assertEqual(len(i.edges), 4) @@ -145,12 +145,12 @@ class TestTag02SquareTag(PathTestBase): # ============= def test03(self): """Verify intesection of square tag with line ending on P2.""" tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-4, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-4, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) self.assertEqual(len(i.edges), 3) - p0 = edge.Curve.StartPoint + p0 = edge.valueAt(edge.FirstParameter) p1 = Vector( 4, 0, 0) p2 = Vector( 4, 0, 3) p3 = Vector(-4, 0, 3) @@ -160,9 +160,9 @@ class TestTag02SquareTag(PathTestBase): # ============= self.assertIsNone(i.tail) # make sure it also works if we get there not directly - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(0, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(0, 0, 0))) i = tag.intersect(edge) - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-4, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-4, 0, 0))) i = i.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) self.assertEqual(len(i.edges), 4) @@ -176,18 +176,18 @@ class TestTag02SquareTag(PathTestBase): # ============= def test04(self): """Verify plunge down is inserted for square tag on exit.""" tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-5, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-5, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P3) self.assertTrue(i.isComplete()) self.assertEqual(len(i.edges), 4) - p0 = edge.Curve.StartPoint + p0 = edge.valueAt(edge.FirstParameter) p1 = Vector( 4, 0, 0) p2 = Vector( 4, 0, 3) p3 = Vector(-4, 0, 3) p4 = Vector(-4, 0, 0) - p5 = edge.Curve.EndPoint + p5 = edge.valueAt(edge.LastParameter) self.assertLine(i.edges[0], p0, p1) self.assertLine(i.edges[1], p1, p2) self.assertLine(i.edges[2], p2, p3) @@ -198,13 +198,13 @@ class TestTag02SquareTag(PathTestBase): # ============= def test05(self): """Verify all lines between P0 and P3 are added.""" tag = Tag( 0, 0, 4, 7, 90, True, 0) - e0 = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(+2, 0, 0))) - e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+1, 0, 0))) - e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0))) - e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0))) - e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0))) - e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0))) - e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, Vector(-5, 0, 0))) + e0 = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(+2, 0, 0))) + e1 = Part.Edge(Part.LineSegment(e0.valueAt(e0.LastParameter), Vector(+1, 0, 0))) + e2 = Part.Edge(Part.LineSegment(e1.valueAt(e1.LastParameter), Vector(+0.5, 0, 0))) + e3 = Part.Edge(Part.LineSegment(e2.valueAt(e2.LastParameter), Vector(-0.5, 0, 0))) + e4 = Part.Edge(Part.LineSegment(e3.valueAt(e3.LastParameter), Vector(-1, 0, 0))) + e5 = Part.Edge(Part.LineSegment(e4.valueAt(e4.LastParameter), Vector(-2, 0, 0))) + e6 = Part.Edge(Part.LineSegment(e5.valueAt(e5.LastParameter), Vector(-5, 0, 0))) i = tag for e in [e0, e1, e2, e3, e4, e5]: @@ -222,7 +222,7 @@ class TestTag02SquareTag(PathTestBase): # ============= pt6 = Vector(-2, 0, 7) self.assertEqual(len(i.edges), 8) - self.assertLines(i.edges, i.tail, [e0.Curve.StartPoint, pt0, pt1, pt2, pt3, pt4, pt5, pt6, e6.Curve.StartPoint, e6.Curve.EndPoint]) + self.assertLines(i.edges, i.tail, [e0.valueAt(e0.FirstParameter), pt0, pt1, pt2, pt3, pt4, pt5, pt6, e6.valueAt(e6.FirstParameter), e6.valueAt(e6.LastParameter)]) self.assertIsNotNone(i.tail) def test06(self): @@ -236,17 +236,17 @@ class TestTag02SquareTag(PathTestBase): # ============= p3 = Vector(-2, 0, 7) p4 = Vector(-2, 0, i) p5 = Vector(-5, 0, i) - edge = Part.Edge(Part.Line(p0, p5)) + edge = Part.Edge(Part.LineSegment(p0, p5)) s = tag.intersect(edge) self.assertTrue(s.isComplete()) self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5]) # for all edges at height or above the original line is used for i in range(7, 9): - edge = Part.Edge(Part.Line(Vector(5, 0, i), Vector(-5, 0, i))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, i), Vector(-5, 0, i))) s = tag.intersect(edge) self.assertTrue(s.isComplete()) - self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + self.assertLine(s.tail, edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) def test10(self): """Verify intersection of square tag with an arc.""" @@ -295,7 +295,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= tag = Tag( 0, 0, 8, 3, 45, True, 0) pt1 = Vector(+5, 5, 0) pt2 = Vector(-5, 5, 0) - edge = Part.Edge(Part.Line(pt1, pt2)) + edge = Part.Edge(Part.LineSegment(pt1, pt2)) i = tag.intersect(edge) self.assertIsNotNone(i) @@ -307,21 +307,21 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= def test01(self): """Veify intersection of trapezoid tag with line ending before P1.""" tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(4, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P0) self.assertEqual(len(i.edges), 1) - self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint) + self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) self.assertIsNone(i.tail) # now add another segment that doesn't reach the top of the cone - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(3, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(3, 0, 0))) i = i.intersect(edge) # still a P0 and edge fully consumed - p1 = Vector(edge.Curve.StartPoint) + p1 = Vector(edge.valueAt(edge.FirstParameter)) p1.z = 0 - p2 = Vector(edge.Curve.EndPoint) + p2 = Vector(edge.valueAt(edge.LastParameter)) p2.z = 1 # height of cone @ (3,0) self.assertEqual(i.state, Tag.Intersection.P0) self.assertEqual(len(i.edges), 2) @@ -329,10 +329,10 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # add another segment to verify starting point offset - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(2, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(2, 0, 0))) i = i.intersect(edge) # still a P0 and edge fully consumed - p3 = Vector(edge.Curve.EndPoint) + p3 = Vector(edge.valueAt(edge.LastParameter)) p3.z = 2 # height of cone @ (2,0) self.assertEqual(i.state, Tag.Intersection.P0) self.assertEqual(len(i.edges), 3) @@ -342,19 +342,19 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= def test02(self): """Verify intersection of trapezoid tag with line ending between P1 and P2""" tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P1) self.assertEqual(len(i.edges), 2) p1 = Vector(4, 0, 0) p2 = Vector(1, 0, 3) - self.assertLine(i.edges[0], edge.Curve.StartPoint, p1) + self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), p1) self.assertLine(i.edges[1], p1, p2) self.assertIsNone(i.tail) # verify we stay in P1 if we add another segment - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(0, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(0, 0, 0))) i = i.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P1) self.assertEqual(len(i.edges), 3) @@ -365,11 +365,11 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= def test03(self): """Verify intersection of trapezoid tag with edge ending on P2.""" tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-1, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-1, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) - p0 = Vector(edge.Curve.StartPoint) + p0 = Vector(edge.valueAt(edge.FirstParameter)) p1 = Vector(4, 0, 0) p2 = Vector(1, 0, 3) p3 = Vector(-1, 0, 3) @@ -377,18 +377,18 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # make sure we get the same result if there's another edge - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(1, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) i = tag.intersect(edge) - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-1, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-1, 0, 0))) i = i.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) self.assertIsNone(i.tail) # and also if the last segment doesn't cross the entire plateau - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(0.5, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(0.5, 0, 0))) i = tag.intersect(edge) - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-1, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-1, 0, 0))) i = i.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) p2a = Vector(0.5, 0, 3) @@ -398,7 +398,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= def test04(self): """Verify proper down plunge on trapezoid tag exit.""" tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-2, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-2, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) @@ -411,7 +411,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # make sure adding another segment doesn't change the state - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-3, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-3, 0, 0))) i = i.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) self.assertEqual(len(i.edges), 5) @@ -420,7 +420,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # now if we complete to P3 .... - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(-4, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-4, 0, 0))) i = i.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P3) self.assertTrue(i.isComplete()) @@ -430,7 +430,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # verify proper operation if there is a single edge going through all - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-4, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-4, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P3) self.assertTrue(i.isComplete()) @@ -438,23 +438,23 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertIsNone(i.tail) # verify tail is added as well - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(-5, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-5, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P3) self.assertTrue(i.isComplete()) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6, edge.Curve.EndPoint]) + self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6, edge.valueAt(edge.LastParameter)]) self.assertIsNotNone(i.tail) def test05(self): """Verify all lines between P0 and P3 are added.""" tag = Tag( 0, 0, 8, 3, 45, True, 0) - e0 = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(+4, 0, 0))) - e1 = Part.Edge(Part.Line(e0.Curve.EndPoint, Vector(+2, 0, 0))) - e2 = Part.Edge(Part.Line(e1.Curve.EndPoint, Vector(+0.5, 0, 0))) - e3 = Part.Edge(Part.Line(e2.Curve.EndPoint, Vector(-0.5, 0, 0))) - e4 = Part.Edge(Part.Line(e3.Curve.EndPoint, Vector(-1, 0, 0))) - e5 = Part.Edge(Part.Line(e4.Curve.EndPoint, Vector(-2, 0, 0))) - e6 = Part.Edge(Part.Line(e5.Curve.EndPoint, Vector(-5, 0, 0))) + e0 = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(+4, 0, 0))) + e1 = Part.Edge(Part.LineSegment(e0.valueAt(e0.LastParameter), Vector(+2, 0, 0))) + e2 = Part.Edge(Part.LineSegment(e1.valueAt(e1.LastParameter), Vector(+0.5, 0, 0))) + e3 = Part.Edge(Part.LineSegment(e2.valueAt(e2.LastParameter), Vector(-0.5, 0, 0))) + e4 = Part.Edge(Part.LineSegment(e3.valueAt(e3.LastParameter), Vector(-1, 0, 0))) + e5 = Part.Edge(Part.LineSegment(e4.valueAt(e4.LastParameter), Vector(-2, 0, 0))) + e6 = Part.Edge(Part.LineSegment(e5.valueAt(e5.LastParameter), Vector(-5, 0, 0))) i = tag for e in [e0, e1, e2, e3, e4, e5]: @@ -472,7 +472,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= p6 = Vector(-2, 0, 2) p7 = Vector(-4, 0, 0) - self.assertLines(i.edges, i.tail, [e0.Curve.StartPoint, p0, p1, p2, p3, p4, p5, p6, p7, e6.Curve.EndPoint]) + self.assertLines(i.edges, i.tail, [e0.valueAt(e0.FirstParameter), p0, p1, p2, p3, p4, p5, p6, p7, e6.valueAt(e6.LastParameter)]) self.assertIsNotNone(i.tail) def test06(self): @@ -486,17 +486,17 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= p3 = Vector(-1, 0, 3) p4 = Vector(-4+i, 0, i) p5 = Vector(-5, 0, i) - edge = Part.Edge(Part.Line(p0, p5)) + edge = Part.Edge(Part.LineSegment(p0, p5)) s = tag.intersect(edge) self.assertTrue(s.isComplete()) self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5]) # for all edges at height or above the original line is used for i in range(3, 5): - edge = Part.Edge(Part.Line(Vector(5, 0, i), Vector(-5, 0, i))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, i), Vector(-5, 0, i))) s = tag.intersect(edge) self.assertTrue(s.isComplete()) - self.assertLine(s.tail, edge.Curve.StartPoint, edge.Curve.EndPoint) + self.assertLine(s.tail, edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) def test10(self): """Verify intersection with an arc.""" @@ -544,31 +544,31 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= class TestTag04TriangularTag(PathTestBase): # ======================== """Unit tests for tags that take on a triangular shape.""" - def xtest00(self): + def test00(self): """Verify intersection of triangular tag with line ending at tag start.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(4, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(4, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P0) self.assertEqual(len(i.edges), 1) - self.assertLine(i.edges[0], edge.Curve.StartPoint, edge.Curve.EndPoint) + self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) self.assertIsNone(i.tail) - def xtest01(self): + def test01(self): """Verify intersection of triangular tag with line ending between P0 and P1.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) - edge = Part.Edge(Part.Line(Vector(5, 0, 0), Vector(3, 0, 0))) + edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(3, 0, 0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P0) p1 = Vector(4, 0, 0) p2 = Vector(3, 0, 1) - self.assertLines(i.edges, i.tail, [edge.Curve.StartPoint, p1, p2]) + self.assertLines(i.edges, i.tail, [edge.valueAt(edge.FirstParameter), p1, p2]) self.assertIsNone(i.tail) # verify we stay in P1 if we add another segment - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, Vector(1, 0, 0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(1, 0, 0))) i = i.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P0) self.assertEqual(len(i.edges), 3) @@ -576,21 +576,21 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLine(i.edges[2], p2, p3) self.assertIsNone(i.tail) - def xtest02(self): + def test02(self): """Verify proper down plunge on exit of triangular tag.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) p0 = Vector(5, 0, 0) p1 = Vector(4, 0, 0) p2 = Vector(0, 0, 4) - edge = Part.Edge(Part.Line(p0, FreeCAD.Vector(0,0,0))) + edge = Part.Edge(Part.LineSegment(p0, FreeCAD.Vector(0,0,0))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) self.assertEqual(len(i.edges), 2) self.assertLines(i.edges, i.tail, [p0, p1, p2]) # adding another segment doesn't make a difference - edge = Part.Edge(Part.Line(edge.Curve.EndPoint, FreeCAD.Vector(-3,0,0))) + edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), FreeCAD.Vector(-3,0,0))) i = i.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) self.assertEqual(len(i.edges), 3) @@ -598,12 +598,12 @@ class TestTag04TriangularTag(PathTestBase): # ======================== self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) # same result if all is one line - edge = Part.Edge(Part.Line(p0, edge.Curve.EndPoint)) + edge = Part.Edge(Part.LineSegment(p0, edge.valueAt(edge.LastParameter))) i = tag.intersect(edge) self.assertEqual(i.state, Tag.Intersection.P2) self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - def xtest03(self): + def test03(self): """Verify triangular tag shap on intersection.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) @@ -611,7 +611,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== p1 = Vector(4, 0, 0) p2 = Vector(0, 0, 4) p3 = Vector(-4, 0, 0) - edge = Part.Edge(Part.Line(p0, p3)) + edge = Part.Edge(Part.LineSegment(p0, p3)) i = tag.intersect(edge) self.assertTrue(i.isComplete()) self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) @@ -619,13 +619,13 @@ class TestTag04TriangularTag(PathTestBase): # ======================== # this should also work if there is some excess, aka tail p4 = Vector(-5, 0, 0) - edge = Part.Edge(Part.Line(p0, p4)) + edge = Part.Edge(Part.LineSegment(p0, p4)) i = tag.intersect(edge) self.assertTrue(i.isComplete()) self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) self.assertIsNotNone(i.tail) - def xtest10(self): + def test10(self): """Verify intersection with an arc.""" tag = Tag( 0, 0, 8, 7, 45, True, 0) p1 = Vector(10, -10, 0) From a3ae53a82b386cdd034fdb243f5a9209459abf93 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 5 Dec 2016 04:30:49 -0800 Subject: [PATCH 020/144] Added arcToHelix. --- .../PathScripts/PathDressupHoldingTags.py | 68 ++++++++++++++++--- src/Mod/Path/PathScripts/PathGeom.py | 25 +++++++ src/Mod/Path/PathTests/TestPathGeom.py | 27 ++++++++ 3 files changed, 111 insertions(+), 9 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 60c76861a5..34c3d48dd2 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -267,7 +267,20 @@ class Tag: # if we have no core the tip is the origin of the Tag line = Part.Edge(self.tag.centerLine()) debugEdge(line, "------- center line", 'P0') - i = DraftGeomUtils.findIntersection(line, edge, True) + if type(edge.Curve) != Part.Circle and type(edge.Curve) != Part.Line: + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = edge.valueAt(edge.LastParameter) + p1.z = 0 + p2.z = 0 + p3.z = 0 + arc = Part.Edge(Part.Arc(p1, p2, p3)) + aps = DraftGeomUtils.findIntersection(line, arc) + for p in aps: + print("%s - p=%.2f" % (p, arc.Curve.parameter(p))) + i = [edge.valueAt(arc.Curve.parameter(p)) for p in aps] + else: + i = DraftGeomUtils.findIntersection(line, edge) #i = line.Curve.intersect(edge) if i: debugPrint('P0', '------- P0 split @ (%.2f, %.2f, %.2f)' % (i[0].x, i[0].y, i[0].z)) @@ -371,16 +384,53 @@ class Tag: def splitEdgeAt(self, edge, pt): - p = edge.Curve.parameter(pt) + # I'm getting rather tired of this interface, so I decided to implement this myself. + # How hard can it be? + # There are only 3 types of edges passing through here, Line, Circle and Helix ... + if False: + p = edge.Curve.parameter(pt) + #p = edge.parameterAt(Part.Vertex(pt.x, pt.y, pt.z)) - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - print("-------- splitAt((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): (%.2f, %.2f, %.2f)) -> param[%.2f -> %.2f]: %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, pt.x, pt.y, pt.z, edge.FirstParameter, edge.LastParameter, p)) + pf = edge.valueAt(edge.FirstParameter) + pl = edge.valueAt(edge.LastParameter) + print("-------- splitAt((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): (%.2f, %.2f, %.2f)) -> param[%.2f -> %.2f]: %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, pt.x, pt.y, pt.z, edge.FirstParameter, edge.LastParameter, p)) - wire = edge.split(p) - # split does not carry the Placement of the original curve foward ... - wire.transformShape(edge.Placement.toMatrix()) - return wire.Edges + print("-------- splitAt(%.2f <= %.2f <= %.2f" % (edge.FirstParameter, p, edge.LastParameter)) + wire = edge.split(p) + # split does not carry the Placement of the original curve foward ... + wire.transformShape(edge.Placement.toMatrix()) + return wire.Edges + 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: + edges.append(Part.LineSegment(p1, p2)) + edges.append(Part.LineSegment(p2, p3)) + elif type(edge.Curve) == Part.Circle: + # it's an arc + p = edge.Curve.parameterAt(p2) + p12 = edge.Curve.value((edge.Curve.FirstParameter + p)/2) + p23 = edge.Curve.value((p + edge.Curve.LastParameter)/2) + edges.append(Part.Edge(Part.Arc(p1, p12, p2))) + edges.append(Part.Edge(Part.Arc(p2, p23, p3))) + else: + # it's a helix + # convert to arc + p01 = FreeCAD.Vector(p1.x, p1.y, 0) + p02 = FreeCAD.Vector(p2.x, p2.y, 0) + p03 = FreeCAD.Vector(p3.x, p3.y, 0) + e0 = Part.Edge(Part.Arc(p01, p02, p03)) + # split arc + p0 = e0.Curve.parameterAt(p02) + p012 = e0.Curve.value((e0.Curve.FirstParameter + p0)/2) + p023 = e0.Curve.value((p0 + e0.Curve.LastParameter)/2) + e01 = Part.Edge(Part.Arc(p01, p012, p02)) + e02 = Part.Edge(Part.Arc(p02, p023, p03)) + # transform arcs into helical form + edges.append(self.arcToHelix(e01, p1.z, p2.z)) + edges.append(self.arcToHelix(e02, p2.z, p3.z)) + return edges def mapEdgeToSolid(self, edge, label): pf = edge.valueAt(edge.FirstParameter) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 2fff4b1e07..7e06829d8a 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -213,3 +213,28 @@ class PathGeom: wires.append(Part.Wire(edges)) return wires + @classmethod + def arcToHelix(cls, edge, z0, z1): + m = FreeCAD.Matrix() + m.unity() + + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt(edge.LastParameter) + z = p1.z + + pd = p2 - p1 + dz = z1 - z0 + + #print("arcToHelix(%.2f, %.2f): dz=%.2f, dy=%.2f, z=%.2f" % (z0 ,z1, dz, pd.y, z)) + + m.A32 = dz / pd.y + m.A34 = - m.A32 + if dz < 0: + m.A34 *= p2.y + m.A34 += z1 - z + else: + m.A34 *= p1.y + m.A34 += z0 - z + + e = edge.transformGeometry(m).Edges[0] + return e diff --git a/src/Mod/Path/PathTests/TestPathGeom.py b/src/Mod/Path/PathTests/TestPathGeom.py index 1b72b7edfd..779767ccb6 100644 --- a/src/Mod/Path/PathTests/TestPathGeom.py +++ b/src/Mod/Path/PathTests/TestPathGeom.py @@ -145,3 +145,30 @@ class TestPathGeom(PathTestBase): self.assertEqual(len(wires[1].Edges), 1) self.assertLine(wires[1].Edges[0], Vector(0,1,0), Vector(0,0,0)) + + def test60(self): + """Verify arcToHelix returns proper helix.""" + p1 = Vector(10,-10,0) + p2 = Vector(0,0,0) + p3 = Vector(10,10,0) + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 2) + self.assertCurve(e, p1, p2 + Vector(0,0,1), p3 + Vector(0,0,2)) + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 3, 7) + self.assertCurve(e, p1 + Vector(0,0,3), p2 + Vector(0,0,5), p3 + Vector(0,0,7)) + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 9, 1) + self.assertCurve(e, p1 + Vector(0,0,9), p2 + Vector(0,0,5), p3 + Vector(0,0,1)) + + dz = Vector(0,0,3) + p11 = p1 + dz + p12 = p2 + dz + p13 = p3 + dz + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 0, 8) + self.assertCurve(e, p1, p2 + Vector(0,0,4), p3 + Vector(0,0,8)) + + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 2, -2) + self.assertCurve(e, p1 + Vector(0,0,2), p2, p3 + Vector(0,0,-2)) + From 0fabe5c079da5e332a04f16c8b672d3a4ed33748 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 5 Dec 2016 14:37:36 -0800 Subject: [PATCH 021/144] Fixed helix construction. --- src/Mod/Path/PathScripts/PathGeom.py | 107 +++++++++++++++++++++---- src/Mod/Path/PathTests/TestPathGeom.py | 66 +++++++++++++++ 2 files changed, 158 insertions(+), 15 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 7e06829d8a..9280a7f51b 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -128,6 +128,33 @@ class PathGeom: Convenience function to return the projection of the Vector in the XY-plane.""" return Vector(point.x, point.y, 0) + @classmethod + def cmdForEdge(cls, edge): + pt = edge.valueAt(edge.LastParameter) + params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + command = Path.Command('G1', params) + else: + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = pt + if Side.Left == Side.of(p2 - p1, p3 - p2): + cmd = 'G3' + else: + cmd = 'G2' + pa = PathGeom.xy(p1) + pb = PathGeom.xy(p2) + pc = PathGeom.xy(p3) + pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) + offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 + #print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) + params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) + command = Path.Command(cmd, params) + #print command + return command + @classmethod def edgeForCmd(cls, cmd, startPoint): """(cmd, startPoint). @@ -215,26 +242,76 @@ class PathGeom: @classmethod def arcToHelix(cls, edge, z0, z1): - m = FreeCAD.Matrix() - m.unity() + """(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) - z = p1.z - pd = p2 - p1 - dz = z1 - z0 + cmd = cls.cmdForEdge(edge) + params = cmd.Parameters + params.update({'Z': z1, 'K': (z1 - z0)/2}) + command = Path.Command(cmd.Name, params) - #print("arcToHelix(%.2f, %.2f): dz=%.2f, dy=%.2f, z=%.2f" % (z0 ,z1, dz, pd.y, z)) + return cls.edgeForCmd(command, FreeCAD.Vector(p1.x, p1.y, z0)) - m.A32 = dz / pd.y - m.A34 = - m.A32 - if dz < 0: - m.A34 *= p2.y - m.A34 += z1 - z + + @classmethod + def helixToArc(cls, edge, z = 0): + """(edge, z=0) + Returns the projection of the helix onto the XY-plane with a given offset.""" + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + p3 = edge.valueAt(edge.LastParameter) + p01 = FreeCAD.Vector(p1.x, p1.y, z) + p02 = FreeCAD.Vector(p2.x, p2.y, z) + p03 = FreeCAD.Vector(p3.x, p3.y, z) + return Part.Edge(Part.Arc(p01, p02, p03)) + + @classmethod + def splitArcAt(cls, edge, pt): + """(edge, pt) + Returns a list of 2 edges which together form the original arc split at the given point. + The Vector pt has to represnt a point on the given arc.""" + p1 = edge.valueAt(edge.FirstParameter) + p2 = pt + p3 = edge.valueAt(edge.LastParameter) + edges = [] + + p = edge.Curve.parameter(p2) + #print("splitArcAt(%.2f, %.2f, %.2f): %.2f - %.2f - %.2f" % (pt.x, pt.y, pt.z, edge.FirstParameter, p, edge.LastParameter)) + + p12 = edge.Curve.value((edge.FirstParameter + p)/2) + p23 = edge.Curve.value((p + edge.LastParameter)/2) + #print("splitArcAt: p12=(%.2f, %.2f, %.2f) p23=(%.2f, %.2f, %.2f)" % (p12.x, p12.y, p12.z, p23.x, p23.y, p23.z)) + + edges.append(Part.Edge(Part.Arc(p1, p12, p2))) + edges.append(Part.Edge(Part.Arc(p2, p23, p3))) + + return edges + + @classmethod + def splitEdgeAt(cls, edge, pt): + """(edge, pt) + Returns a list of 2 edges, forming the original edge split at the given point. + The results are undefined if the Vector representing the point is not part of the edge.""" + # I could not get the OCC parameterAt and split to work ... + # pt HAS to be on the edge, otherwise the results are undefined + p1 = edge.valueAt(edge.FirstParameter) + p2 = pt + p3 = edge.valueAt(edge.LastParameter) + edges = [] + + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + # it's a line + return [Part.Edge(Part.LineSegment(p1, p2)), Part.Edge(Part.LineSegment(p2, p3))] + elif type(edge.Curve) == Part.Circle: + # it's an arc + return cls.splitArcAt(edge, pt) else: - m.A34 *= p1.y - m.A34 += z0 - z + # it's a helix + arc = cls.helixToArc(edge, 0) + aes = cls.splitArcAt(arc, FreeCAD.Vector(pt.x, pt.y, 0)) + return [cls.arcToHelix(aes[0], p1.z, p2.z), cls.arcToHelix(aes[1], p2.z, p3.z)] - e = edge.transformGeometry(m).Edges[0] - return e diff --git a/src/Mod/Path/PathTests/TestPathGeom.py b/src/Mod/Path/PathTests/TestPathGeom.py index 779767ccb6..7ab7a5396f 100644 --- a/src/Mod/Path/PathTests/TestPathGeom.py +++ b/src/Mod/Path/PathTests/TestPathGeom.py @@ -172,3 +172,69 @@ class TestPathGeom(PathTestBase): e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 2, -2) self.assertCurve(e, p1 + Vector(0,0,2), p2, p3 + Vector(0,0,-2)) + o = 10*math.sin(math.pi/4) + p1 = Vector(10, -10, 1) + p2 = Vector(10 - 10*math.sin(math.pi/4), -10*math.cos(math.pi/4), 1) + p3 = Vector(0, 0, 1) + e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 5) + self.assertCurve(e, Vector(10,-10,0), Vector(p2.x,p2.y,2.5), Vector(0, 0, 5)) + + + def test62(self): + """Verify splitArcAt returns proper subarcs.""" + p1 = Vector(10,-10,0) + p2 = Vector(0,0,0) + p3 = Vector(10,10,0) + + arc = Part.Edge(Part.Arc(p1, p2, p3)) + + o = 10*math.sin(math.pi/4) + p12 = Vector(10 - o, -o, 0) + p23 = Vector(10 - o, +o, 0) + + e = PathGeom.splitArcAt(arc, p2) + self.assertCurve(e[0], p1, p12, p2) + self.assertCurve(e[1], p2, p23, p3) + + p34 = Vector(10 - 10*math.sin(1*math.pi/8), -10*math.cos(1*math.pi/8), 0) + p45 = Vector(10 - 10*math.sin(5*math.pi/8), -10*math.cos(5*math.pi/8), 0) + + e = PathGeom.splitArcAt(arc, p12) + self.assertCurve(e[0], p1, p34, p12) + self.assertCurve(e[1], p12, p45, p3) + + + def test65(self): + """Verify splitEdgeAt.""" + e = PathGeom.splitEdgeAt(Part.Edge(Part.LineSegment(Vector(), Vector(2, 4, 6))), Vector(1, 2, 3)) + self.assertLine(e[0], Vector(), Vector(1,2,3)) + self.assertLine(e[1], Vector(1,2,3), Vector(2,4,6)) + + # split an arc + p1 = Vector(10,-10,1) + p2 = Vector(0,0,1) + p3 = Vector(10,10,1) + arc = Part.Edge(Part.Arc(p1, p2, p3)) + e = PathGeom.splitEdgeAt(arc, p2) + o = 10*math.sin(math.pi/4) + p12 = Vector(10 - o, -o, 1) + p23 = Vector(10 - o, +o, 1) + self.assertCurve(e[0], p1, p12, p2) + self.assertCurve(e[1], p2, p23, p3) + + + # split a helix + p1 = Vector(10,-10,0) + p2 = Vector(0,0,5) + p3 = Vector(10,10,10) + h = PathGeom.arcToHelix(arc, 0, 10) + self.assertCurve(h, p1, p2, p3) + + e = PathGeom.splitEdgeAt(h, p2) + o = 10*math.sin(math.pi/4) + p12 = Vector(10 - o, -o, 2.5) + p23 = Vector(10 - o, +o, 7.5) + pf = e[0].valueAt((e[0].FirstParameter + e[0].LastParameter)/2) + pl = e[1].valueAt((e[1].FirstParameter + e[1].LastParameter)/2) + self.assertCurve(e[0], p1, p12, p2) + self.assertCurve(e[1], p2, p23, p3) From f294821f9d3bc32f16947f5cf64b41f26e58f8c1 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 6 Dec 2016 05:03:48 -0800 Subject: [PATCH 022/144] Use PathGeom for holding tags dressup. --- .../PathScripts/PathDressupHoldingTags.py | 119 ++++-------------- src/Mod/Path/PathScripts/PathGeom.py | 3 + .../PathTests/TestPathDressupHoldingTags.py | 4 +- 3 files changed, 27 insertions(+), 99 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 34c3d48dd2..f49d320393 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -82,41 +82,6 @@ def debugCylinder(vector, r, height, label, color = None): if color: obj.ViewObject.ShapeColor = color -movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] -movestraight = ['G1', 'G01'] -movecw = ['G2', 'G02'] -moveccw = ['G3', 'G03'] -movearc = movecw + moveccw - -slack = 0.0000001 - -def pathCommandForEdge(edge): - pt = edge.valueAt(edge.LastParameter) - params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z} - if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: - command = Path.Command('G1', params) - else: - p1 = edge.valueAt(edge.FirstParameter) - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - p3 = pt - if Side.Left == Side.of(p2 - p1, p3 - p2): - cmd = 'G3' - else: - cmd = 'G2' - pa = PathGeom.xy(p1) - pb = PathGeom.xy(p2) - pc = PathGeom.xy(p3) - pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) - print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) - offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 - print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) - params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) - command = Path.Command(cmd, params) - print command - return command - - class Tag: @classmethod @@ -246,7 +211,7 @@ class Tag: tail = None else: debugPrint('P0', "------- split at (%s)" % i) - e, tail = self.tag.splitEdgeAt(edge, i) + e, tail = PathGeom.splitEdgeAt(edge, i) self.p1 = e.valueAt(edge.LastParameter) self.edges.extend(self.tag.mapEdgeToSolid(e, 'P0-core-1')) self.state = self.P1 @@ -276,9 +241,10 @@ class Tag: p3.z = 0 arc = Part.Edge(Part.Arc(p1, p2, p3)) aps = DraftGeomUtils.findIntersection(line, arc) + paramScale = (edge.LastParameter - edge.FirstParameter) / (arc.LastParameter - arc.FirstParameter) for p in aps: print("%s - p=%.2f" % (p, arc.Curve.parameter(p))) - i = [edge.valueAt(arc.Curve.parameter(p)) for p in aps] + i = [edge.valueAt(arc.Curve.parameter(p) * paramScale) for p in aps] else: i = DraftGeomUtils.findIntersection(line, edge) #i = line.Curve.intersect(edge) @@ -288,7 +254,7 @@ class Tag: e = edge tail = None else: - e, tail = self.tag.splitEdgeAt(edge, i[0]) + e, tail = PathGeom.splitEdgeAt(edge, i[0]) self.state = self.P2 # P1 and P2 are identical for triangular tags self.p1 = i[0] self.p2 = i[0] @@ -318,7 +284,7 @@ class Tag: tail = None else: debugPrint('P1', "----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) - e, tail = self.tag.splitEdgeAt(edge, i) + e, tail = PathGeom.splitEdgeAt(edge, i) f = e.valueAt(e.FirstParameter) l = e.valueAt(e.LastParameter) debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) @@ -350,7 +316,7 @@ class Tag: e = edge tail = None else: - e, tail = self.tag.splitEdgeAt(edge, i) + e, tail = PathGeom.splitEdgeAt(edge, i) if tail: pf = tail.valueAt(tail.FirstParameter) pl = tail.valueAt(tail.LastParameter) @@ -383,63 +349,14 @@ class Tag: return self - def splitEdgeAt(self, edge, pt): - # I'm getting rather tired of this interface, so I decided to implement this myself. - # How hard can it be? - # There are only 3 types of edges passing through here, Line, Circle and Helix ... - if False: - p = edge.Curve.parameter(pt) - #p = edge.parameterAt(Part.Vertex(pt.x, pt.y, pt.z)) - - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - print("-------- splitAt((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): (%.2f, %.2f, %.2f)) -> param[%.2f -> %.2f]: %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, pt.x, pt.y, pt.z, edge.FirstParameter, edge.LastParameter, p)) - - print("-------- splitAt(%.2f <= %.2f <= %.2f" % (edge.FirstParameter, p, edge.LastParameter)) - wire = edge.split(p) - # split does not carry the Placement of the original curve foward ... - wire.transformShape(edge.Placement.toMatrix()) - return wire.Edges - 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: - edges.append(Part.LineSegment(p1, p2)) - edges.append(Part.LineSegment(p2, p3)) - elif type(edge.Curve) == Part.Circle: - # it's an arc - p = edge.Curve.parameterAt(p2) - p12 = edge.Curve.value((edge.Curve.FirstParameter + p)/2) - p23 = edge.Curve.value((p + edge.Curve.LastParameter)/2) - edges.append(Part.Edge(Part.Arc(p1, p12, p2))) - edges.append(Part.Edge(Part.Arc(p2, p23, p3))) - else: - # it's a helix - # convert to arc - p01 = FreeCAD.Vector(p1.x, p1.y, 0) - p02 = FreeCAD.Vector(p2.x, p2.y, 0) - p03 = FreeCAD.Vector(p3.x, p3.y, 0) - e0 = Part.Edge(Part.Arc(p01, p02, p03)) - # split arc - p0 = e0.Curve.parameterAt(p02) - p012 = e0.Curve.value((e0.Curve.FirstParameter + p0)/2) - p023 = e0.Curve.value((p0 + e0.Curve.LastParameter)/2) - e01 = Part.Edge(Part.Arc(p01, p012, p02)) - e02 = Part.Edge(Part.Arc(p02, p023, p03)) - # transform arcs into helical form - edges.append(self.arcToHelix(e01, p1.z, p2.z)) - edges.append(self.arcToHelix(e02, p2.z, p3.z)) - return edges - def mapEdgeToSolid(self, edge, label): pf = edge.valueAt(edge.FirstParameter) pl = edge.valueAt(edge.LastParameter) print("--------- mapEdgeToSolid-%s: %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (label, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) p1a = edge.valueAt(edge.FirstParameter) - p1b = FreeCAD.Vector(p1a.x, p1a.y, p1a.z + self.height) p1a.z = self.bottom() + p1b = FreeCAD.Vector(p1a.x, p1a.y, self.height * 1.01) e1 = Part.Edge(Part.LineSegment(p1a, p1b)) p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection print("---------- p1: (%s %s) -> %s %d" % (p1a, p1b, p1, self.solid.isInside(p1, 0.0000001, True))) @@ -448,8 +365,8 @@ class Tag: return [] p2a = edge.valueAt(edge.LastParameter) - p2b = FreeCAD.Vector(p2a.x, p2a.y, p2a.z + self.height) p2a.z = self.bottom() + p2b = FreeCAD.Vector(p2a.x, p2a.y, self.height * 1.01) e2 = Part.Edge(Part.LineSegment(p2a, p2b)) p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection if not p2: @@ -518,7 +435,7 @@ class Tag: #print("it's a plane, checking R") c = face.Edges[0].Curve if (type(c) == Part.Circle): - return filter(lambda pt: (pt - c.Center).Length <= c.Radius, pts) + return filter(lambda pt: (pt - c.Center).Length <= c.Radius or PathGeom.isRoughly((pt - c.Center).Length, c.Radius), pts) print("==== we got a %s" % face.Surface) def isPointOnEdge(self, pt, edge): @@ -529,18 +446,26 @@ class Tag: return True if PathGeom.isRoughly(edge.FirstParameter, param) or PathGeom.isRoughly(edge.LastParameter, param): return True - print("-------- X %.2f <= %.2f <=%.2f (%.2f, %.2f, %.2f)" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z)) + print("-------- X %.2f <= %.2f <=%.2f (%.2f, %.2f, %.2f) %.2f:%.2f" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z, edge.Curve.parameter(edge.valueAt(edge.FirstParameter)), edge.Curve.parameter(edge.valueAt(edge.LastParameter)))) + p1 = edge.Vertexes[0] + f1 = edge.Curve.parameter(FreeCAD.Vector(p1.X, p1.Y, p1.Z)) + p2 = edge.Vertexes[1] + f2 = edge.Curve.parameter(FreeCAD.Vector(p2.X, p2.Y, p2.Z)) + print("-------- (%.2f, %.2f, %.2f):%.2f (%.2f, %.2f, %.2f):%.2f" % (p1.X, p1.Y, p1.Z, f1, p2.X, p2.Y, p2.Z, f2)) + print("-------- %s %s" % (edge.Placement, edge.Orientation)) return False def nextIntersectionClosestTo(self, edge, solid, refPt): ef = edge.valueAt(edge.FirstParameter) + em = edge.valueAt((edge.FirstParameter+edge.LastParameter)/2) el = edge.valueAt(edge.LastParameter) - print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.z, el.x, el.y, el.z, refPt.x, refPt.y, refPt.z)) + print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.z, em.x, em.y, em.z, el.x, el.y, el.z, refPt.x, refPt.y, refPt.z)) pts = [] for index, face in enumerate(solid.Faces): i = edge.Curve.intersect(face.Surface)[0] + print i ps = self.filterIntersections([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i], face) pts.extend(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)) if len(ps) != len(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)): @@ -578,7 +503,7 @@ class Tag: tail = edge else: print("---- split edge") - e,tail = self.splitEdgeAt(edge, i) + e,tail = PathGeom.splitEdgeAt(edge, i) inters.edges.append(e) return inters.intersect(tail) else: @@ -821,13 +746,13 @@ class ObjectDressup: lastTag = sameTag t = 1 for e in inters.edges: - commands.append(pathCommandForEdge(e)) + commands.append(PathGeom.cmdForEdge(e)) inters = None if t >= len(tags): # gone through all tags, consume edge and move on if edge: - commands.append(pathCommandForEdge(edge)) + commands.append(PathGeom.cmdForEdge(edge)) edge = None t = 0 diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 9280a7f51b..7f09f40013 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -254,6 +254,9 @@ class PathGeom: params.update({'Z': z1, 'K': (z1 - z0)/2}) command = Path.Command(cmd.Name, params) + print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1)) + print("- %s -> %s" % (cmd, command)) + return cls.edgeForCmd(command, FreeCAD.Vector(p1.x, p1.y, z0)) diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 49e6be09cc..902f832513 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -538,7 +538,7 @@ class TestTag03TrapezoidTag(PathTestBase): # ============= self.assertCurve(s.edges[1], pi, Vector(0.221698, -2.093992, 1.897543), pj) self.assertCurve(s.edges[2], pj, Vector(0, 0, 3), pk) self.assertCurve(s.edges[3], pk, Vector(0.182776, 1.903182, 2.090060), pl) - self.assertCurve(s.tail, pl, Vector(3.996548, +7.997409, 1.590060), p2) + self.assertCurve(s.tail, pl, Vector(3.996548, +7.997409, 1.590060), p2) class TestTag04TriangularTag(PathTestBase): # ======================== @@ -652,7 +652,7 @@ class TestTag04TriangularTag(PathTestBase): # ======================== edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) pi = Vector(0.513574, -3.163498, 0.795085) - pj = Vector(0.000001, 0, 4) + pj = Vector(0, 0, 4) pk = Vector(0.397586, +2.791711, 1.180119) s = tag.intersect(edge) From e807094eda1074d066d81346d2652705e28bd111 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 6 Dec 2016 08:18:18 -0800 Subject: [PATCH 023/144] Create cones for debugging cone shaped tags. --- .../PathScripts/PathDressupHoldingTags.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index f49d320393..66ba486563 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -82,6 +82,18 @@ def debugCylinder(vector, r, height, label, color = None): if color: obj.ViewObject.ShapeColor = color +def debugCone(vector, r1, r2, height, label, color = None): + if debugDressup: + obj = FreeCAD.ActiveDocument.addObject("Part::Cone", label) + obj.Label = label + obj.Radius1 = r1 + obj.Radius2 = r2 + obj.Height = height + obj.Placement = FreeCAD.Placement(vector, FreeCAD.Rotation(FreeCAD.Vector(0,0,1), 0)) + obj.ViewObject.Transparency = 90 + if color: + obj.ViewObject.ShapeColor = color + class Tag: @classmethod @@ -121,6 +133,8 @@ class Tag: def createSolidsAt(self, z): self.z = z r1 = self.width / 2 + self.r1 = r1 + self.r2 = r1 height = self.height if self.angle == 90 and height > 0: self.solid = Part.makeCylinder(r1, height) @@ -136,6 +150,7 @@ class Tag: height = r1 * tangens self.core = None self.actualHeight = height + self.r2 = r2 self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag @@ -791,17 +806,20 @@ class ObjectDressup: return print("execute - %d tags" % (len(tags))) + tags = pathData.sortedTags(tags) + for tag in tags: + tag.createSolidsAt(pathData.minZ) + tagID = 0 for tag in tags: tagID += 1 if tag.enabled: #print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) #debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) - debugCylinder(tag.originAt(pathData.minZ), tag.width/2, tag.actualHeight, "tag-%02d" % tagID) - - tags = pathData.sortedTags(tags) - for tag in tags: - tag.createSolidsAt(pathData.minZ) + if tag.angle != 90: + debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) + else: + debugCylinder(tag.originAt(pathData.minZ), tag.width/2, tag.actualHeight, "tag-%02d" % tagID) self.fingerprint = [tag.toString() for tag in tags] self.tags = tags From 9eca75e67381f251b2353b1e60faa98d4f2c5f9f Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 10 Dec 2016 12:13:29 -0800 Subject: [PATCH 024/144] Added pixellation of arbitrary path curve. --- src/Mod/Path/PathScripts/PathGeom.py | 80 ++++++++++++++++++---------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 7f09f40013..c0710f28f7 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -89,13 +89,13 @@ class PathGeom: return cls.pointsCoincide(edge.valueAt(edge.FirstParameter), vector) or cls.pointsCoincide(edge.valueAt(edge.LastParameter), vector) @classmethod - def getAngle(cls, vertex): - """(vertex) - Returns the angle [-pi,pi] of a vertex using the X-axis as the reference. + def getAngle(cls, vector): + """(vector) + Returns the angle [-pi,pi] of a vector using the X-axis as the reference. Positive angles for vertexes in the upper hemishpere (positive y values) and negative angles for the lower hemishpere.""" - a = vertex.getAngle(FreeCAD.Vector(1,0,0)) - if vertex.y < 0: + a = vector.getAngle(FreeCAD.Vector(1,0,0)) + if vector.y < 0: return -a return a @@ -129,31 +129,57 @@ class PathGeom: return Vector(point.x, point.y, 0) @classmethod - def cmdForEdge(cls, edge): - pt = edge.valueAt(edge.LastParameter) + def cmdsForEdge(cls, edge, flip = False, useHelixForBSpline = True): + 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: - command = Path.Command('G1', params) + commands = [Path.Command('G1', params)] else: - p1 = edge.valueAt(edge.FirstParameter) - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - p3 = pt - if Side.Left == Side.of(p2 - p1, p3 - p2): - cmd = 'G3' + if not flip: + p1 = edge.valueAt(edge.FirstParameter) + p3 = pt else: - cmd = 'G2' - pa = PathGeom.xy(p1) - pb = PathGeom.xy(p2) - pc = PathGeom.xy(p3) - pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) - #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) - offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 - #print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) - params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) - command = Path.Command(cmd, params) - #print command - return command + p1 = pt + p3 = edge.valueAt(edge.LastParameter) + p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) + if type(edge.Curve) == Part.Circle or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve): + if Side.Left == Side.of(p2 - p1, p3 - p2): + cmd = 'G3' + else: + cmd = 'G2' + print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)) + pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center + + pa = PathGeom.xy(p1) + pb = PathGeom.xy(p2) + pc = PathGeom.xy(p3) + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)) + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)) + offset = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center - p1 + #print("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z)) + params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2}) + commands = [ Path.Command(cmd, params) ] + else: + eStraight = Part.Edge(Part.LineSegment(p1, p3)) + esP2 = eStraight.valueAt((eStraight.FirstParameter + eStraight.LastParameter)/2) + deviation = (p2 - esP2).Length + if cls.isRoughly(deviation, 0): + return [ Path.Command('G1', {'X': p3.x, 'Y': p3.y, 'Z': p3.z}) ] + # at this point pixellation is all we can do + commands = [] + segments = int(math.ceil((deviation / eStraight.Length) * 1000)) + print("**** pixellation with %d segments" % segments) + dParameter = (edge.LastParameter - edge.FirstParameter) / segments + for i in range(0, segments): + if flip: + p = edge.valueAt(edge.LastParameter - (i + 1) * dParameter) + else: + p = edge.valueAt(edge.FirstParameter + (i + 1) * dParameter) + cmd = Path.Command('G1', {'X': p.x, 'Y': p.y, 'Z': p.z}) + print("***** %s" % cmd) + commands.append(cmd) + #print commands + return commands @classmethod def edgeForCmd(cls, cmd, startPoint): @@ -249,7 +275,7 @@ class PathGeom: p1 = edge.valueAt(edge.FirstParameter) p2 = edge.valueAt(edge.LastParameter) - cmd = cls.cmdForEdge(edge) + cmd = cls.cmdsForEdge(edge)[0] params = cmd.Parameters params.update({'Z': z1, 'K': (z1 - z0)/2}) command = Path.Command(cmd.Name, params) From b26a7ad7949afebf79a8daffdc9d55108a0b1012 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 10 Dec 2016 12:39:23 -0800 Subject: [PATCH 025/144] OCC based tag line generation by extruding the edges cutting through a tag and retreiving the common with the tag solid. --- .../PathScripts/PathDressupHoldingTags.py | 525 +++++----------- src/Mod/Path/PathScripts/PathGeom.py | 18 +- .../PathTests/TestPathDressupHoldingTags.py | 576 +----------------- src/Mod/Path/TestPathApp.py | 6 +- 4 files changed, 160 insertions(+), 965 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 66ba486563..a9b53e8a75 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -47,7 +47,7 @@ except AttributeError: def translate(context, text, disambig=None): return QtGui.QApplication.translate(context, text, disambig) -debugDressup = True +debugDressup = False debugComponents = ['P0', 'P1', 'P2', 'P3'] def debugPrint(comp, msg): @@ -59,7 +59,7 @@ def debugEdge(edge, prefix, comp = None): pl = edge.valueAt(edge.LastParameter) if comp: debugPrint(comp, "%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - else: + elif debugDressup: print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) def debugMarker(vector, label, color = None, radius = 0.5): @@ -127,9 +127,6 @@ class Tag: def top(self): return self.z + self.actualHeight - def centerLine(self): - return Part.LineSegment(self.originAt(self.bottom() - 1), self.originAt(self.top() + 1)) - def createSolidsAt(self, z): self.z = z r1 = self.width / 2 @@ -160,288 +157,6 @@ class Tag: if self.core: self.core.translate(self.originAt(z)) - class Intersection: - # An intersection with a tag has 4 markant points, where one might be optional. - # - # P1---P2 P1---P2 P2 - # | | / \ /\ - # | | / \ / \ - # | | / \ / \ - # ---P0 P3--- ---P0 P3--- ---P0 P3--- - # - # If no intersection occured the Intersection can be viewed as being - # at P3 with no additional edges. - Pnone = 1 - P0 = 2 - P1 = 3 - P2 = 4 - P3 = 5 - - def __init__(self, tag): - self.tag = tag - self.state = self.Pnone - self.edges = [] - self.tail = None - - def isComplete(self): - return self.state == self.Pnone or self.state == self.P3 - - def hasEdges(self): - return self.state != self.Pnone - - def moveEdgeToPlateau(self, edge): - if type(edge.Curve) is Part.Line or type(edge.Curve) is Part.LineSegment: - e = copy.copy(edge) - z = edge.valueAt(edge.FirstParameter).z - elif type(edge.Curve) is Part.Circle: - # it's an arc - e = copy.copy(edge) - z = edge.Curve.Center.z - else: - # it's a helix -> transform to arc - z = 0 - p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) - p2 = PathGeom.xy(edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)) - p3 = PathGeom.xy(edge.valueAt(edge.LastParameter)) - e = Part.Edge(Part.Arc(p1, p2, p3)) - print("-------- moveEdgeToPlateau") - e.translate(Vector(0, 0, self.tag.top() - z)) - return e - - def intersectP0Core(self, edge): - debugPrint('P0', "----- P0-core") - - i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.FirstParameter)) - if i: - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - # if P0 and P1 are the same, we need to insert a segment for the rise - debugPrint('P0', "------- insert vertical rise (%s)" % i) - self.edges.append(Part.Edge(Part.LineSegment(i, FreeCAD.Vector(i.x, i.y, self.tag.top())))) - self.p1 = i - self.state = self.P1 - return edge - if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - debugPrint('P0', "------- consumed (%s)" % i) - e = edge - tail = None - else: - debugPrint('P0', "------- split at (%s)" % i) - e, tail = PathGeom.splitEdgeAt(edge, i) - self.p1 = e.valueAt(edge.LastParameter) - self.edges.extend(self.tag.mapEdgeToSolid(e, 'P0-core-1')) - self.state = self.P1 - return tail - # no intersection, the entire edge fits between P0 and P1 - debugPrint('P0', "------- no intersection") - self.edges.extend(self.tag.mapEdgeToSolid(edge, 'P0-core-2')) - return None - - def intersectP0(self, edge): - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - debugPrint('P0', "----- P0 %s(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - - if self.tag.core: - return self.intersectP0Core(edge) - - # if we have no core the tip is the origin of the Tag - line = Part.Edge(self.tag.centerLine()) - debugEdge(line, "------- center line", 'P0') - if type(edge.Curve) != Part.Circle and type(edge.Curve) != Part.Line: - p1 = edge.valueAt(edge.FirstParameter) - p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2) - p3 = edge.valueAt(edge.LastParameter) - p1.z = 0 - p2.z = 0 - p3.z = 0 - arc = Part.Edge(Part.Arc(p1, p2, p3)) - aps = DraftGeomUtils.findIntersection(line, arc) - paramScale = (edge.LastParameter - edge.FirstParameter) / (arc.LastParameter - arc.FirstParameter) - for p in aps: - print("%s - p=%.2f" % (p, arc.Curve.parameter(p))) - i = [edge.valueAt(arc.Curve.parameter(p) * paramScale) for p in aps] - else: - i = DraftGeomUtils.findIntersection(line, edge) - #i = line.Curve.intersect(edge) - if i: - debugPrint('P0', '------- P0 split @ (%.2f, %.2f, %.2f)' % (i[0].x, i[0].y, i[0].z)) - if PathGeom.pointsCoincide(i[0], edge.valueAt(edge.LastParameter)): - e = edge - tail = None - else: - e, tail = PathGeom.splitEdgeAt(edge, i[0]) - self.state = self.P2 # P1 and P2 are identical for triangular tags - self.p1 = i[0] - self.p2 = i[0] - else: - debugPrint('P0', '------- P0 no intersect') - e = edge - tail = None - self.edges.extend(self.tag.mapEdgeToSolid(e, 'P0')) - return tail - - - - def intersectP1(self, edge): - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.core, edge.valueAt(edge.LastParameter)) - if i: - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - debugPrint('P1', "----- P1 edge too short") - #self.edges.extend(self.tag.mapEdgeToSolid(edge, 'P1')) - self.edges.append(self.moveEdgeToPlateau(edge)) - return None - if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - debugPrint('P1', "----- P1 edge at end") - e = edge - tail = None - else: - debugPrint('P1', "----- P1 split edge @ (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) - e, tail = PathGeom.splitEdgeAt(edge, i) - f = e.valueAt(e.FirstParameter) - l = e.valueAt(e.LastParameter) - debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) - self.p2 = e.valueAt(e.LastParameter) - self.state = self.P2 - else: - debugPrint('P1', "----- P1 no intersect") - e = edge - tail = None - f = e.valueAt(e.FirstParameter) - l = e.valueAt(e.LastParameter) - debugPrint('P1', "----- P1 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (f.x, f.y, f.z, l.x, l.y, l.z)) - self.edges.append(self.moveEdgeToPlateau(e)) - return tail - - def intersectP2(self, edge): - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - debugPrint('P2', "----- P2 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - i = self.tag.nextIntersectionClosestTo(edge, self.tag.solid, edge.valueAt(edge.LastParameter)) - if i: - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - debugPrint('P2', "------- insert exit plunge (%s)" % i) - self.edges.append(Part.Edge(Part.LineSegment(FreeCAD.Vector(i.x, i.y, self.tag.top()), i))) - e = None - tail = edge - elif PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - debugPrint('P2', "------- entire segment added (%s)" % i) - e = edge - tail = None - else: - e, tail = PathGeom.splitEdgeAt(edge, i) - if tail: - pf = tail.valueAt(tail.FirstParameter) - pl = tail.valueAt(tail.LastParameter) - debugPrint('P3', "----- P3 (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - else: - debugPrint('P3', "----- P3 (---)") - self.state = self.P3 - self.tail = tail - else: - debugPrint('P2', "----- P2 no intersection") - e = edge - tail = None - if e: - pf = e.valueAt(e.FirstParameter) - pl = e.valueAt(e.LastParameter) - s = 'P2' if self.state == self.P2 else 'P3' - debugPrint(s, "----- %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (s, pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - self.edges.extend(self.tag.mapEdgeToSolid(e, 'P2')) - return tail - - def intersect(self, edge): - #print("") - #print(" >>> (%s - %s)" % (edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter))) - if edge and self.state == self.P0: - edge = self.intersectP0(edge) - if edge and self.state == self.P1: - edge = self.intersectP1(edge) - if edge and self.state == self.P2: - edge = self.intersectP2(edge) - return self - - - def mapEdgeToSolid(self, edge, label): - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - print("--------- mapEdgeToSolid-%s: %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (label, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - - p1a = edge.valueAt(edge.FirstParameter) - p1a.z = self.bottom() - p1b = FreeCAD.Vector(p1a.x, p1a.y, self.height * 1.01) - e1 = Part.Edge(Part.LineSegment(p1a, p1b)) - p1 = self.nextIntersectionClosestTo(e1, self.solid, p1b) # top most intersection - print("---------- p1: (%s %s) -> %s %d" % (p1a, p1b, p1, self.solid.isInside(p1, 0.0000001, True))) - if not p1: - raise Exception('no p1') - return [] - - p2a = edge.valueAt(edge.LastParameter) - p2a.z = self.bottom() - p2b = FreeCAD.Vector(p2a.x, p2a.y, self.height * 1.01) - e2 = Part.Edge(Part.LineSegment(p2a, p2b)) - p2 = self.nextIntersectionClosestTo(e2, self.solid, p2b) # top most intersection - if not p2: - p1 = edge.valueAt(edge.FirstParameter) - p2 = edge.valueAt(edge.LastParameter) - print("---------- p1: %d%d" % (self.solid.isInside(p1, 0.0000001, True), self.solid.isInside(p1, 0.0000001, False))) - print("---------- p2: %d%d" % (self.solid.isInside(p2, 0.0000001, True), self.solid.isInside(p2, 0.0000001, False))) - #if not self.solid.isInside(p1, 0.0000001, False): - # p1 is on the solid - - raise Exception('no p2') - return [] - - print("---------- %s - %s" % (p1, p2)) - print("---------- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p2.x, p2.y, p2.z, p1.x, p1.y, p1.z)) - - if PathGeom.pointsCoincide(p1, p2): - return [] - - if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: - e = Part.Edge(Part.LineSegment(p1, p2)) - debugEdge(e, "-------- >>") - return [e] - - m = FreeCAD.Matrix() - m.unity() - pd = p2 - p1 - - if type(edge.Curve) == Part.Circle: - m.A32 = pd.z / pd.y - m.A34 = - m.A32 - if pd.z < 0: - m.A34 *= p2.y - else: - m.A34 *= p1.y - e = edge.transformGeometry(m).Edges[0] - debugEdge(e, "-------- >>") - return [e] - - # it's already a helix, just need to lift it to the plateau - m.A33 = pd.z / (edge.valueAt(edge.LastParameter).z - edge.valueAt(edge.FirstParameter).z) - m.A34 = (1 - m.A33) - if pd.z < 0: - m.A34 *= edge.valueAt(edge.LastParameter).z - else: - m.A34 *= edge.valueAt(edge.FirstParameter).z - - #print - pf = edge.valueAt(edge.FirstParameter) - pl = edge.valueAt(edge.LastParameter) - #print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z, m.A33)) - #print("**** %.2f %.2f (%.2f - %.2f)" % (pd.z, p2a.z-p1a.z, p2a.z, p1a.z)) - e = edge.transformGeometry(m).Edges[0] - pf = e.valueAt(e.FirstParameter) - pl = e.valueAt(e.LastParameter) - #print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - #raise Exception("mensch") - debugEdge(e, "-------- >>") - return [e] - - def filterIntersections(self, pts, face): if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: #print("it's a cone/cylinder, checking z") @@ -451,7 +166,7 @@ class Tag: c = face.Edges[0].Curve if (type(c) == Part.Circle): return filter(lambda pt: (pt - c.Center).Length <= c.Radius or PathGeom.isRoughly((pt - c.Center).Length, c.Radius), pts) - print("==== we got a %s" % face.Surface) + #print("==== we got a %s" % face.Surface) def isPointOnEdge(self, pt, edge): param = edge.Curve.parameter(pt) @@ -461,13 +176,11 @@ class Tag: return True if PathGeom.isRoughly(edge.FirstParameter, param) or PathGeom.isRoughly(edge.LastParameter, param): return True - print("-------- X %.2f <= %.2f <=%.2f (%.2f, %.2f, %.2f) %.2f:%.2f" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z, edge.Curve.parameter(edge.valueAt(edge.FirstParameter)), edge.Curve.parameter(edge.valueAt(edge.LastParameter)))) + #print("-------- X %.2f <= %.2f <=%.2f (%.2f, %.2f, %.2f) %.2f:%.2f" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z, edge.Curve.parameter(edge.valueAt(edge.FirstParameter)), edge.Curve.parameter(edge.valueAt(edge.LastParameter)))) p1 = edge.Vertexes[0] f1 = edge.Curve.parameter(FreeCAD.Vector(p1.X, p1.Y, p1.Z)) p2 = edge.Vertexes[1] f2 = edge.Curve.parameter(FreeCAD.Vector(p2.X, p2.Y, p2.Z)) - print("-------- (%.2f, %.2f, %.2f):%.2f (%.2f, %.2f, %.2f):%.2f" % (p1.X, p1.Y, p1.Z, f1, p2.X, p2.Y, p2.Z, f2)) - print("-------- %s %s" % (edge.Placement, edge.Orientation)) return False @@ -475,62 +188,144 @@ class Tag: ef = edge.valueAt(edge.FirstParameter) em = edge.valueAt((edge.FirstParameter+edge.LastParameter)/2) el = edge.valueAt(edge.LastParameter) - print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.z, em.x, em.y, em.z, el.x, el.y, el.z, refPt.x, refPt.y, refPt.z)) + #print("-------- intersect %s (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) refp=(%.2f, %.2f, %.2f)" % (type(edge.Curve), ef.x, ef.y, ef.z, em.x, em.y, em.z, el.x, el.y, el.z, refPt.x, refPt.y, refPt.z)) pts = [] for index, face in enumerate(solid.Faces): i = edge.Curve.intersect(face.Surface)[0] - print i + #print i ps = self.filterIntersections([FreeCAD.Vector(p.X, p.Y, p.Z) for p in i], face) pts.extend(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)) if len(ps) != len(filter(lambda pt: self.isPointOnEdge(pt, edge), ps)): filtered = filter(lambda pt: self.isPointOnEdge(pt, edge), ps) - print("-------- ++ len(ps)=%d, len(filtered)=%d" % (len(ps), len(filtered))) + #print("-------- ++ len(ps)=%d, len(filtered)=%d" % (len(ps), len(filtered))) for p in ps: included = '+' if p in filtered else '-' - print("-------- %s (%.2f, %.2f, %.2f)" % (included, p.x, p.y, p.z)) + #print("-------- %s (%.2f, %.2f, %.2f)" % (included, p.x, p.y, p.z)) if pts: closest = sorted(pts, key=lambda pt: (pt - refPt).Length)[0] - for p in pts: - print("-------- - intersect pt : (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) - print("-------- -> (%.2f, %.2f, %.2f)" % (closest.x, closest.y, closest.z)) + #for p in pts: + # print("-------- - intersect pt : (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) + #print("-------- -> (%.2f, %.2f, %.2f)" % (closest.x, closest.y, closest.z)) return closest - print("-------- -> None") + #print("-------- -> None") return None - def intersect(self, edge, check = True): - print("--- intersect") - inters = self.Intersection(self) - if check: - if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top(): - i = self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(edge.FirstParameter)) - if i: - print("---- (%.2f, %.2f, %.2f)" % (i.x, i.y, i.z)) - inters.state = self.Intersection.P0 - inters.p0 = i - if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): - print("---- entire edge consumed.") - inters.edges.append(edge) - return inters - if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): - print("---- nothing of edge consumed.") - tail = edge - else: - print("---- split edge") - e,tail = PathGeom.splitEdgeAt(edge, i) - inters.edges.append(e) - return inters.intersect(tail) - else: - print("---- No intersection found.") - else: - print("---- Fly by") + def intersects(self, edge, param): + if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top(): + return self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(param)) + return None + +class MapWireToTag: + def __init__(self, edge, tag, i): + self.tag = tag + if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), i): + tail = edge + self.commands = [] + elif PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), i): + debugEdge(edge, '++++++++ .') + self.commands = PathGeom.cmdsForEdge(edge) + tail = None else: - print("---- skipped") - # if we get here there is no intersection with the tag - inters.state = self.Intersection.Pnone - inters.tail = edge - return inters + e, tail = PathGeom.splitEdgeAt(edge, i) + debugEdge(e, '++++++++ .') + self.commands = PathGeom.cmdsForEdge(e) + self.tail = tail + self.edges = [] + self.entry = i + self.complete = False + self.wire = None + + def addEdge(self, edge): + if self.wire: + self.wire.add(edge) + else: + self.wire = Part.Wire(edge) + + def needToFlipEdge(self, edge, p): + if PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), p): + return True, edge.valueAt(edge.FirstParameter) + return False, edge.valueAt(edge.LastParameter) + + def cleanupEdges(self, edges): + # first remove all internal struts + inputEdges = copy.copy(edges) + plinths = [] + for e in edges: + p1 = e.valueAt(e.FirstParameter) + p2 = e.valueAt(e.LastParameter) + if p1.x == p2.x and p1.y == p2.y: + if not (PathGeom.edgeConnectsTo(e, self.entry) or PathGeom.edgeConnectsTo(e, self.exit)): + inputEdges.remove(e) + if p1.z > p2.z: + plinths.append(p2) + else: + plinths.append(p1) + # remove all edges that are connected to the plinths of the (former) internal struts + # including the edge that connects the entry and exit point directly + for e in copy.copy(inputEdges): + if PathGeom.edgeConnectsTo(e, self.entry) and PathGeom.edgeConnectsTo(e, self.exit): + inputEdges.remove(e) + continue + for p in plinths: + if PathGeom.edgeConnectsTo(e, p): + inputEdges.remove(e) + break + # the remaining edges form walk around the tag + # they need to be ordered and potentially flipped though + outputEdges = [] + p = self.entry + lastP = p + while inputEdges: + for e in inputEdges: + p1 = e.valueAt(e.FirstParameter) + p2 = e.valueAt(e.LastParameter) + if PathGeom.pointsCoincide(p1, p): + outputEdges.append((e,False)) + inputEdges.remove(e) + lastP = p + p = p2 + debugEdge(e, ">>>>> no flip") + break + elif PathGeom.pointsCoincide(p2, p): + outputEdges.append((e,True)) + inputEdges.remove(e) + lastP = p + p = p1 + debugEdge(e, ">>>>> flip") + break + #else: + # debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) + if lastP == p: + raise ValueError("No connection to %s" % (p)) + #else: + # print("xxxxxx (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) + return outputEdges + + def add(self, edge): + self.tail = None + if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), 0.000001, True): + self.addEdge(edge) + else: + i = self.tag.intersects(edge, edge.LastParameter) + if PathGeom.pointsCoincide(i, edge.valueAt(edge.FirstParameter)): + self.tail = edge + else: + e, tail = PathGeom.splitEdgeAt(edge, i) + self.addEdge(e) + self.tail = tail + self.exit = i + shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) + face = shell.common(self.tag.solid) + + for e,flip in self.cleanupEdges(face.Edges): + debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) + self.commands.extend(PathGeom.cmdsForEdge(e, flip, False)) + self.complete = True + + def mappingComplete(self): + return self.complete class PathData: def __init__(self, obj): @@ -548,36 +343,11 @@ class PathData: bottom = [e for e in edges if e.Vertexes[0].Point.z == minZ and e.Vertexes[1].Point.z == minZ] wire = Part.Wire(bottom) if wire.isClosed(): - #return Part.Wire(self.sortedBase(bottom)) return wire # if we get here there are already holding tags, or we're not looking at a profile # let's try and insert the missing pieces - another day raise ValueError("Selected path doesn't seem to be a Profile operation.") - def sortedBase(self, base): - # first find the exit point, where base wire is closed - edges = [e for e in self.edges if e.valueAt(e.FirstParameter).z == self.minZ and e.valueAt(e.LastParameter).z != self.maxZ] - exit = sorted(edges, key=lambda e: -e.valueAt(e.LastParameter).z)[0] - pt = exit.valueAt(exit.FirstParameter) - # then find the first base edge, and sort them until done - ordered = [] - while base: - edges = [e for e in base if e.valueAt(e.FirstParameter) == pt] - if not edges: - print ordered - print base - print("(%.2f, %.2f, %.2f)" % (pt.x, pt.y, pt.z)) - for e in base: - pf = e.valueAt(e.FirstParameter) - pl = e.valueAt(e.LastParameter) - print("(%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - edge = edges[0] - ordered.append(edge) - base.remove(edge) - pt = edge.valueAt(edge.LastParameter) - return ordered - - def findZLimits(self, edges): # not considering arcs and spheres in Z direction, find the highes and lowest Z values minZ = edges[0].Vertexes[0].Point.z @@ -721,14 +491,6 @@ class ObjectDressup: return self.pathData.generateTags(obj, count, width, height, angle, spacing) - def tagIntersection(self, face, edge): - p1 = edge.valueAt(edge.FirstParameter) - pts = edge.Curve.intersect(face.Surface) - if pts[0]: - closest = sorted(pts[0], key=lambda pt: (pt - p1).Length)[0] - return closest - return None - def createPath(self, edges, tags): commands = [] lastEdge = 0 @@ -738,36 +500,39 @@ class ObjectDressup: inters = None edge = None + mapper = None + while edge or lastEdge < len(edges): - print("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) + #print("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) if not edge: edge = edges[lastEdge] debugEdge(edge, "======= new edge: %d/%d" % (lastEdge, len(edges))) lastEdge += 1 sameTag = None - if inters: - inters = inters.intersect(edge) - else: + if mapper: + mapper.add(edge) + if mapper.mappingComplete(): + commands.extend(mapper.commands) + edge = mapper.tail + mapper = None + else: + edge = None + + if edge: tIndex = (t + lastTag) % len(tags) t += 1 - print("<<<<< lastTag=%d, t=%d, tIndex=%d, sameTag=%s >>>>>>" % (lastTag, t, tIndex, sameTag)) - inters = tags[tIndex].intersect(edge, True or tIndex != sameTag) - edge = inters.tail + i = tags[tIndex].intersects(edge, edge.FirstParameter) + if i: + mapper = MapWireToTag(edge, tags[tIndex], i) + edge = mapper.tail - if inters.isComplete(): - if inters.hasEdges(): - sameTag = (t + lastTag - 1) % len(tags) - lastTag = sameTag - t = 1 - for e in inters.edges: - commands.append(PathGeom.cmdForEdge(e)) - inters = None - if t >= len(tags): + if not mapper and t >= len(tags): # gone through all tags, consume edge and move on if edge: - commands.append(PathGeom.cmdForEdge(edge)) + debugEdge(edge, '++++++++') + commands.extend(PathGeom.cmdsForEdge(edge)) edge = None t = 0 diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index c0710f28f7..1b6d7369e3 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -130,6 +130,14 @@ class PathGeom: @classmethod def cmdsForEdge(cls, edge, flip = False, useHelixForBSpline = True): + """(edge, flip = False, useHelixForBSpline = True) -> 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. + Approximation is also the approach for edges that are neither straight lines + nor arcs (nor helixes).""" 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: @@ -147,7 +155,7 @@ class PathGeom: cmd = 'G3' else: cmd = 'G2' - print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)) + #print("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z)) pd = Part.Circle(PathGeom.xy(p1), PathGeom.xy(p2), PathGeom.xy(p3)).Center pa = PathGeom.xy(p1) @@ -168,7 +176,7 @@ class PathGeom: # at this point pixellation is all we can do commands = [] segments = int(math.ceil((deviation / eStraight.Length) * 1000)) - print("**** pixellation with %d segments" % segments) + #print("**** pixellation with %d segments" % segments) dParameter = (edge.LastParameter - edge.FirstParameter) / segments for i in range(0, segments): if flip: @@ -176,7 +184,7 @@ class PathGeom: else: p = edge.valueAt(edge.FirstParameter + (i + 1) * dParameter) cmd = Path.Command('G1', {'X': p.x, 'Y': p.y, 'Z': p.z}) - print("***** %s" % cmd) + #print("***** %s" % cmd) commands.append(cmd) #print commands return commands @@ -280,8 +288,8 @@ class PathGeom: 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)) + #print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1)) + #print("- %s -> %s" % (cmd, command)) return cls.edgeForCmd(command, FreeCAD.Vector(p1.x, p1.y, z0)) diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 902f832513..d332a5b624 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -33,7 +33,7 @@ from FreeCAD import Vector from PathScripts.PathDressupHoldingTags import * from PathTests.PathTestUtils import PathTestBase -class TestTag01BasicTag(PathTestBase): # ============= +class TestHoldingTags(PathTestBase): """Unit tests for the HoldingTags dressup.""" def test00(self): @@ -89,577 +89,3 @@ class TestTag01BasicTag(PathTestBase): # ============= self.assertIsNone(tag.core) -class TestTag02SquareTag(PathTestBase): # ============= - """Unit tests for square tags.""" - - def test00(self): - """Verify no intersection.""" - tag = Tag( 0, 0, 4, 7, 90, True, 0) - pt1 = Vector(+5, 5, 0) - pt2 = Vector(-5, 5, 0) - edge = Part.Edge(Part.LineSegment(pt1, pt2)) - - i = tag.intersect(edge) - self.assertIsNotNone(i) - self.assertTrue(i.isComplete()) - self.assertIsNotNone(i.edges) - self.assertFalse(i.edges) - self.assertLine(i.tail, pt1, pt2) - - def test01(self): - """Verify intersection of square tag with line ending at tag start.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(4, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 1) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - self.assertIsNone(i.tail) - - def test02(self): - """Verify intersection of square tag with line ending between P1 and P2.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P1) - self.assertEqual(len(i.edges), 3) - p1 = Vector(4, 0, 0) - p2 = Vector(4, 0, 3) - p3 = Vector(1, 0, 3) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), p1) - self.assertLine(i.edges[1], p1, p2) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - # verify we stay in P1 if we add another segment - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(0, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P1) - self.assertEqual(len(i.edges), 4) - p4 = Vector(0, 0, 3) - self.assertLine(i.edges[3], p3, p4) - self.assertIsNone(i.tail) - - def test03(self): - """Verify intesection of square tag with line ending on P2.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-4, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 3) - p0 = edge.valueAt(edge.FirstParameter) - p1 = Vector( 4, 0, 0) - p2 = Vector( 4, 0, 3) - p3 = Vector(-4, 0, 3) - self.assertLine(i.edges[0], p0, p1) - self.assertLine(i.edges[1], p1, p2) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - # make sure it also works if we get there not directly - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(0, 0, 0))) - i = tag.intersect(edge) - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-4, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 4) - p2a = Vector( 0, 0, 3) - self.assertLine(i.edges[0], p0, p1) - self.assertLine(i.edges[1], p1, p2) - self.assertLine(i.edges[2], p2, p2a) - self.assertLine(i.edges[3], p2a, p3) - self.assertIsNone(i.tail) - - def test04(self): - """Verify plunge down is inserted for square tag on exit.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-5, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P3) - self.assertTrue(i.isComplete()) - self.assertEqual(len(i.edges), 4) - p0 = edge.valueAt(edge.FirstParameter) - p1 = Vector( 4, 0, 0) - p2 = Vector( 4, 0, 3) - p3 = Vector(-4, 0, 3) - p4 = Vector(-4, 0, 0) - p5 = edge.valueAt(edge.LastParameter) - self.assertLine(i.edges[0], p0, p1) - self.assertLine(i.edges[1], p1, p2) - self.assertLine(i.edges[2], p2, p3) - self.assertLine(i.edges[3], p3, p4) - self.assertIsNotNone(i.tail) - self.assertLine(i.tail, p4, p5) - - def test05(self): - """Verify all lines between P0 and P3 are added.""" - tag = Tag( 0, 0, 4, 7, 90, True, 0) - e0 = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(+2, 0, 0))) - e1 = Part.Edge(Part.LineSegment(e0.valueAt(e0.LastParameter), Vector(+1, 0, 0))) - e2 = Part.Edge(Part.LineSegment(e1.valueAt(e1.LastParameter), Vector(+0.5, 0, 0))) - e3 = Part.Edge(Part.LineSegment(e2.valueAt(e2.LastParameter), Vector(-0.5, 0, 0))) - e4 = Part.Edge(Part.LineSegment(e3.valueAt(e3.LastParameter), Vector(-1, 0, 0))) - e5 = Part.Edge(Part.LineSegment(e4.valueAt(e4.LastParameter), Vector(-2, 0, 0))) - e6 = Part.Edge(Part.LineSegment(e5.valueAt(e5.LastParameter), Vector(-5, 0, 0))) - - i = tag - for e in [e0, e1, e2, e3, e4, e5]: - i = i.intersect(e) - self.assertFalse(i.isComplete()) - i = i.intersect(e6) - self.assertTrue(i.isComplete()) - - pt0 = Vector(2, 0, 0) - pt1 = Vector(2, 0, 7) - pt2 = Vector(1, 0, 7) - pt3 = Vector(0.5, 0, 7) - pt4 = Vector(-0.5, 0, 7) - pt5 = Vector(-1, 0, 7) - pt6 = Vector(-2, 0, 7) - - self.assertEqual(len(i.edges), 8) - self.assertLines(i.edges, i.tail, [e0.valueAt(e0.FirstParameter), pt0, pt1, pt2, pt3, pt4, pt5, pt6, e6.valueAt(e6.FirstParameter), e6.valueAt(e6.LastParameter)]) - self.assertIsNotNone(i.tail) - - def test06(self): - """Verify intersection of different z levels.""" - tag = Tag( 0, 0, 4, 7, 90, True, 0) - # for all lines below 7 we get the trapezoid - for i in range(0, 7): - p0 = Vector(5, 0, i) - p1 = Vector(2, 0, i) - p2 = Vector(2, 0, 7) - p3 = Vector(-2, 0, 7) - p4 = Vector(-2, 0, i) - p5 = Vector(-5, 0, i) - edge = Part.Edge(Part.LineSegment(p0, p5)) - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5]) - - # for all edges at height or above the original line is used - for i in range(7, 9): - edge = Part.Edge(Part.LineSegment(Vector(5, 0, i), Vector(-5, 0, i))) - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertLine(s.tail, edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - - def test10(self): - """Verify intersection of square tag with an arc.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 0) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10}), p1) - - pi = Vector(0.8, -3.919184, 0) - pj = Vector(0.8, +3.919184, 0) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 4) - self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0), pi) - self.assertLine(s.edges[1], pi, pi + Vector(0, 0, 3)) - self.assertCurve(s.edges[2], pi + Vector(0, 0, 3), Vector(0, 0, 3), pj + Vector(0, 0, 3)) - self.assertLine(s.edges[3], pj + Vector(0, 0, 3), pj) - self.assertCurve(s.tail, pj, Vector(4.486010, +8.342417, 0), p2) - - def test20(self): - """Verify intersection of square tag with a helix.""" - tag = Tag( 0, 0, 8, 3, 90, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 2) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) - - pi = Vector(0.8, -3.919184, 0.743623) - pj = Vector(0.8, +3.919184, 1.256377) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 4) - self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0.371812), pi) - self.assertLine(s.edges[1], pi, pi + Vector(0, 0, 3-pi.z)) - self.assertCurve(s.edges[2], pi + Vector(0, 0, 3-pi.z), Vector(0, 0, 3), pj + Vector(0, 0, 3-pj.z)) - self.assertLine(s.edges[3], pj + Vector(0, 0, 3-pj.z), pj) - self.assertCurve(s.tail, pj, Vector(4.486010, +8.342417, 1.628188), p2) - - -class TestTag03TrapezoidTag(PathTestBase): # ============= - """Unit tests for trapezoid tags.""" - - def test00(self): - """Verify no intersection.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - pt1 = Vector(+5, 5, 0) - pt2 = Vector(-5, 5, 0) - edge = Part.Edge(Part.LineSegment(pt1, pt2)) - - i = tag.intersect(edge) - self.assertIsNotNone(i) - self.assertTrue(i.isComplete()) - self.assertIsNotNone(i.edges) - self.assertFalse(i.edges) - self.assertLine(i.tail, pt1, pt2) - - def test01(self): - """Veify intersection of trapezoid tag with line ending before P1.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(4, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 1) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - self.assertIsNone(i.tail) - - # now add another segment that doesn't reach the top of the cone - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(3, 0, 0))) - i = i.intersect(edge) - # still a P0 and edge fully consumed - p1 = Vector(edge.valueAt(edge.FirstParameter)) - p1.z = 0 - p2 = Vector(edge.valueAt(edge.LastParameter)) - p2.z = 1 # height of cone @ (3,0) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 2) - self.assertLine(i.edges[1], p1, p2) - self.assertIsNone(i.tail) - - # add another segment to verify starting point offset - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(2, 0, 0))) - i = i.intersect(edge) - # still a P0 and edge fully consumed - p3 = Vector(edge.valueAt(edge.LastParameter)) - p3.z = 2 # height of cone @ (2,0) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 3) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - def test02(self): - """Verify intersection of trapezoid tag with line ending between P1 and P2""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P1) - self.assertEqual(len(i.edges), 2) - p1 = Vector(4, 0, 0) - p2 = Vector(1, 0, 3) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), p1) - self.assertLine(i.edges[1], p1, p2) - self.assertIsNone(i.tail) - - # verify we stay in P1 if we add another segment - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(0, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P1) - self.assertEqual(len(i.edges), 3) - p3 = Vector(0, 0, 3) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - def test03(self): - """Verify intersection of trapezoid tag with edge ending on P2.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-1, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - p0 = Vector(edge.valueAt(edge.FirstParameter)) - p1 = Vector(4, 0, 0) - p2 = Vector(1, 0, 3) - p3 = Vector(-1, 0, 3) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - self.assertIsNone(i.tail) - - # make sure we get the same result if there's another edge - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(1, 0, 0))) - i = tag.intersect(edge) - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-1, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - self.assertIsNone(i.tail) - - # and also if the last segment doesn't cross the entire plateau - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(0.5, 0, 0))) - i = tag.intersect(edge) - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-1, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - p2a = Vector(0.5, 0, 3) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p2a, p3]) - self.assertIsNone(i.tail) - - def test04(self): - """Verify proper down plunge on trapezoid tag exit.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-2, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - p0 = Vector(5, 0, 0) - p1 = Vector(4, 0, 0) - p2 = Vector(1, 0, 3) - p3 = Vector(-1, 0, 3) - p4 = Vector(-2, 0, 2) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) - self.assertIsNone(i.tail) - - # make sure adding another segment doesn't change the state - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-3, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 5) - p5 = Vector(-3, 0, 1) - self.assertLine(i.edges[4], p4, p5) - self.assertIsNone(i.tail) - - # now if we complete to P3 .... - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(-4, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P3) - self.assertTrue(i.isComplete()) - self.assertEqual(len(i.edges), 6) - p6 = Vector(-4, 0, 0) - self.assertLine(i.edges[5], p5, p6) - self.assertIsNone(i.tail) - - # verify proper operation if there is a single edge going through all - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-4, 0, 0))) - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P3) - self.assertTrue(i.isComplete()) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6]) - self.assertIsNone(i.tail) - - # verify tail is added as well - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(-5, 0, 0))) - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P3) - self.assertTrue(i.isComplete()) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p6, edge.valueAt(edge.LastParameter)]) - self.assertIsNotNone(i.tail) - - def test05(self): - """Verify all lines between P0 and P3 are added.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - e0 = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(+4, 0, 0))) - e1 = Part.Edge(Part.LineSegment(e0.valueAt(e0.LastParameter), Vector(+2, 0, 0))) - e2 = Part.Edge(Part.LineSegment(e1.valueAt(e1.LastParameter), Vector(+0.5, 0, 0))) - e3 = Part.Edge(Part.LineSegment(e2.valueAt(e2.LastParameter), Vector(-0.5, 0, 0))) - e4 = Part.Edge(Part.LineSegment(e3.valueAt(e3.LastParameter), Vector(-1, 0, 0))) - e5 = Part.Edge(Part.LineSegment(e4.valueAt(e4.LastParameter), Vector(-2, 0, 0))) - e6 = Part.Edge(Part.LineSegment(e5.valueAt(e5.LastParameter), Vector(-5, 0, 0))) - - i = tag - for e in [e0, e1, e2, e3, e4, e5]: - i = i.intersect(e) - self.assertFalse(i.isComplete()) - i = i.intersect(e6) - self.assertTrue(i.isComplete()) - - p0 = Vector(4, 0, 0) - p1 = Vector(2, 0, 2) - p2 = Vector(1, 0, 3) - p3 = Vector(0.5, 0, 3) - p4 = Vector(-0.5, 0, 3) - p5 = Vector(-1, 0, 3) - p6 = Vector(-2, 0, 2) - p7 = Vector(-4, 0, 0) - - self.assertLines(i.edges, i.tail, [e0.valueAt(e0.FirstParameter), p0, p1, p2, p3, p4, p5, p6, p7, e6.valueAt(e6.LastParameter)]) - self.assertIsNotNone(i.tail) - - def test06(self): - """Verify intersection for different z levels.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - # for all lines below 3 we get the trapezoid - for i in range(0, 3): - p0 = Vector(5, 0, i) - p1 = Vector(4-i, 0, i) - p2 = Vector(1, 0, 3) - p3 = Vector(-1, 0, 3) - p4 = Vector(-4+i, 0, i) - p5 = Vector(-5, 0, i) - edge = Part.Edge(Part.LineSegment(p0, p5)) - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertLines(s.edges, s.tail, [p0, p1, p2, p3, p4, p5]) - - # for all edges at height or above the original line is used - for i in range(3, 5): - edge = Part.Edge(Part.LineSegment(Vector(5, 0, i), Vector(-5, 0, i))) - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertLine(s.tail, edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - - def test10(self): - """Verify intersection with an arc.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 0) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10}), p1) - - pi = Vector(0.8, -3.919184, 0) - pj = Vector(0.05, -0.998749, 3) - pk = Vector(0.05, +0.998749, 3) - pl = Vector(0.8, +3.919184, 0) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 4) - self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0), pi) - self.assertCurve(s.edges[1], pi, Vector(0.314296, -2.487396, 1.470795), pj) - self.assertCurve(s.edges[2], pj, Vector(0, 0, 3), pk) - self.assertCurve(s.edges[3], pk, Vector(.3142960, +2.487396, 1.470795), pl) - self.assertCurve(s.tail, pl, Vector(4.486010, +8.342417, 0), p2) - - def test20(self): - """Verify intersection with a helix.""" - tag = Tag( 0, 0, 8, 3, 45, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 2) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) - - pi = Vector(0.513574, -3.163498, 0.795085) - pj = Vector(0.050001, -0.998749, 3) - pk = Vector(0.050001, +0.998749, 3) - pl = Vector(0.397586, +2.791711, 1.180119) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 4) - self.assertCurve(s.edges[0], p1, Vector(4.153420, -8.112798, 0.397543), pi) - self.assertCurve(s.edges[1], pi, Vector(0.221698, -2.093992, 1.897543), pj) - self.assertCurve(s.edges[2], pj, Vector(0, 0, 3), pk) - self.assertCurve(s.edges[3], pk, Vector(0.182776, 1.903182, 2.090060), pl) - self.assertCurve(s.tail, pl, Vector(3.996548, +7.997409, 1.590060), p2) - - -class TestTag04TriangularTag(PathTestBase): # ======================== - """Unit tests for tags that take on a triangular shape.""" - - def test00(self): - """Verify intersection of triangular tag with line ending at tag start.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(4, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 1) - self.assertLine(i.edges[0], edge.valueAt(edge.FirstParameter), edge.valueAt(edge.LastParameter)) - self.assertIsNone(i.tail) - - def test01(self): - """Verify intersection of triangular tag with line ending between P0 and P1.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - edge = Part.Edge(Part.LineSegment(Vector(5, 0, 0), Vector(3, 0, 0))) - - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - p1 = Vector(4, 0, 0) - p2 = Vector(3, 0, 1) - self.assertLines(i.edges, i.tail, [edge.valueAt(edge.FirstParameter), p1, p2]) - self.assertIsNone(i.tail) - - # verify we stay in P1 if we add another segment - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), Vector(1, 0, 0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P0) - self.assertEqual(len(i.edges), 3) - p3 = Vector(1, 0, 3) - self.assertLine(i.edges[2], p2, p3) - self.assertIsNone(i.tail) - - def test02(self): - """Verify proper down plunge on exit of triangular tag.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - - p0 = Vector(5, 0, 0) - p1 = Vector(4, 0, 0) - p2 = Vector(0, 0, 4) - edge = Part.Edge(Part.LineSegment(p0, FreeCAD.Vector(0,0,0))) - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 2) - self.assertLines(i.edges, i.tail, [p0, p1, p2]) - - # adding another segment doesn't make a difference - edge = Part.Edge(Part.LineSegment(edge.valueAt(edge.LastParameter), FreeCAD.Vector(-3,0,0))) - i = i.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertEqual(len(i.edges), 3) - p3 = Vector(-3, 0, 1) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - - # same result if all is one line - edge = Part.Edge(Part.LineSegment(p0, edge.valueAt(edge.LastParameter))) - i = tag.intersect(edge) - self.assertEqual(i.state, Tag.Intersection.P2) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - - def test03(self): - """Verify triangular tag shap on intersection.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - - p0 = Vector(5, 0, 0) - p1 = Vector(4, 0, 0) - p2 = Vector(0, 0, 4) - p3 = Vector(-4, 0, 0) - edge = Part.Edge(Part.LineSegment(p0, p3)) - i = tag.intersect(edge) - self.assertTrue(i.isComplete()) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3]) - self.assertIsNone(i.tail) - - # this should also work if there is some excess, aka tail - p4 = Vector(-5, 0, 0) - edge = Part.Edge(Part.LineSegment(p0, p4)) - i = tag.intersect(edge) - self.assertTrue(i.isComplete()) - self.assertLines(i.edges, i.tail, [p0, p1, p2, p3, p4]) - self.assertIsNotNone(i.tail) - - def test10(self): - """Verify intersection with an arc.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 0) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10}), p1) - - pi = Vector(0.8, -3.919184, 0) - pj = Vector(0.0, 0.0, 4) - pk = Vector(0.8, +3.919184, 0) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 3) - self.assertCurve(s.edges[0], p1, Vector(4.486010, -8.342417, 0), pi) - self.assertCurve(s.edges[1], pi, Vector(0.202041, -2., 1.958759), pj) - self.assertCurve(s.edges[2], pj, Vector(0.202041, +2., 1.958759), pk) - self.assertCurve(s.tail, pk, Vector(4.486010, +8.342417, 0), p2) - - def test20(self): - """Verify intersection with a helix.""" - tag = Tag( 0, 0, 8, 7, 45, True, 0) - p1 = Vector(10, -10, 0) - p2 = Vector(10, +10, 2) - edge = PathGeom.edgeForCmd(Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'J': 10, 'K': 1}), p1) - - pi = Vector(0.513574, -3.163498, 0.795085) - pj = Vector(0, 0, 4) - pk = Vector(0.397586, +2.791711, 1.180119) - - s = tag.intersect(edge) - self.assertTrue(s.isComplete()) - self.assertEqual(len(s.edges), 3) - self.assertCurve(s.edges[0], p1, Vector(4.153420, -8.112798, 0.397543), pi) - self.assertCurve(s.edges[1], pi, Vector(0.129229, -1.602457, 2.397542), pj) - self.assertCurve(s.edges[2], pj, Vector(0.099896, 1.409940, 2.590059), pk) - self.assertCurve(s.tail, pk, Vector(3.996548, +7.997409, 1.590060), p2) - diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 8729163535..098aa32209 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -29,8 +29,4 @@ from PathTests.TestPathPost import PathPostTestCases from PathTests.TestPathGeom import TestPathGeom from PathTests.TestPathDepthParams import depthTestCases -from PathTests.TestPathDressupHoldingTags import TestTag01BasicTag -from PathTests.TestPathDressupHoldingTags import TestTag02SquareTag -from PathTests.TestPathDressupHoldingTags import TestTag03TrapezoidTag -from PathTests.TestPathDressupHoldingTags import TestTag04TriangularTag - +from PathTests.TestPathDressupHoldingTags import TestHoldingTags From 1635d73b1befd917fb4774b8736ef7b347acf52c Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 10 Dec 2016 16:35:54 -0800 Subject: [PATCH 026/144] Removed core which isn't used anymore; some debugging info to figure out the save/restore issus. --- .../PathScripts/PathDressupHoldingTags.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index a9b53e8a75..35763d8b39 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -104,9 +104,14 @@ class Tag: except: return None + def toString(self): + return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) + def __init__(self, x, y, width, height, angle, enabled=True, z=None): + print("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d, %s)" % (x, y, width, height, angle/math.pi, enabled, z)) self.x = x self.y = y + self.z = z self.width = math.fabs(width) self.height = math.fabs(height) self.actualHeight = self.height @@ -115,9 +120,6 @@ class Tag: if z is not None: self.createSolidsAt(z) - def toString(self): - return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) - def originAt(self, z): return FreeCAD.Vector(self.x, self.y, z) @@ -135,27 +137,21 @@ class Tag: height = self.height if self.angle == 90 and height > 0: self.solid = Part.makeCylinder(r1, height) - self.core = self.solid.copy() elif self.angle > 0.0 and height > 0.0: tangens = math.tan(math.radians(self.angle)) dr = height / tangens if dr < r1: r2 = r1 - dr - self.core = Part.makeCylinder(r2, height) else: r2 = 0 height = r1 * tangens - self.core = None self.actualHeight = height self.r2 = r2 self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag self.solid = Part.makeSphere(r1 / 10000) - self.core = None self.solid.translate(self.originAt(z)) - if self.core: - self.core.translate(self.originAt(z)) def filterIntersections(self, pts, face): if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder: @@ -536,6 +532,8 @@ class ObjectDressup: edge = None t = 0 + for cmd in commands: + print(cmd) return Path.Path(commands) @@ -549,6 +547,10 @@ class ObjectDressup: if not obj.Base.Path.Commands: return + if obj.Path: + for cmd in obj.Path.Commands: + print(cmd) + pathData = self.setup(obj) if not pathData: print("execute - no pathData") @@ -572,6 +574,7 @@ class ObjectDressup: print("execute - %d tags" % (len(tags))) tags = pathData.sortedTags(tags) + self.setTags(obj, tags, False) for tag in tags: tag.createSolidsAt(pathData.minZ) @@ -591,9 +594,13 @@ class ObjectDressup: obj.Path = self.createPath(pathData.edges, tags) - def setTags(self, obj, tags): + def setTags(self, obj, tags, update = True): + print("setTags(.....)") + for t in tags: + print(" .... %s" % t.toString()) obj.Tags = [tag.toString() for tag in tags] - self.execute(obj) + if update: + self.execute(obj) def getTags(self, obj): if hasattr(self, 'tags'): From 5e1efba51210ceb6da9f6f00037e41fa360b31d5 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 11 Dec 2016 08:53:51 -0800 Subject: [PATCH 027/144] Fixed generation and edge case where there is no wire to cut the tag. --- .../PathScripts/PathDressupHoldingTags.py | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 35763d8b39..d286214306 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -312,12 +312,13 @@ class MapWireToTag: self.addEdge(e) self.tail = tail self.exit = i - shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) - face = shell.common(self.tag.solid) + if self.wire: + shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) + face = shell.common(self.tag.solid) - for e,flip in self.cleanupEdges(face.Edges): - debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) - self.commands.extend(PathGeom.cmdsForEdge(e, flip, False)) + for e,flip in self.cleanupEdges(face.Edges): + debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) + self.commands.extend(PathGeom.cmdsForEdge(e, flip, False)) self.complete = True def mappingComplete(self): @@ -387,6 +388,7 @@ class PathData: startIndex = 0 for i in range(0, len(self.base.Edges)): edge = self.base.Edges[i] + print(' %d: %.2f' % (i, edge.Length)) if edge.Length == longestEdge.Length: startIndex = i break @@ -401,10 +403,10 @@ class PathData: minLength = min(2. * W, longestEdge.Length) - #print("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f)" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length)) - #print(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) - #print(" -> lastTagLength=%.2f)" % lastTagLength) - #print(" -> currentLength=%.2f)" % currentLength) + print("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f) minLength=%.2f" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length, minLength)) + print(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) + print(" -> lastTagLength=%.2f)" % lastTagLength) + print(" -> currentLength=%.2f)" % currentLength) edgeDict = { startIndex: startCount } @@ -419,7 +421,7 @@ class PathData: for (i, count) in edgeDict.iteritems(): edge = self.base.Edges[i] - #print(" %d: %d" % (i, count)) + print(" %d: %d" % (i, count)) #debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) #debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) distance = (edge.LastParameter - edge.FirstParameter) / count @@ -432,15 +434,15 @@ class PathData: def processEdge(self, index, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict): tagCount = 0 currentLength += edge.Length - if edge.Length > minLength: + if edge.Length >= minLength: while lastTagLength + tagDistance < currentLength: tagCount += 1 lastTagLength += tagDistance if tagCount > 0: - #print(" index=%d -> count=%d" % (index, tagCount)) + print(" index=%d -> count=%d" % (index, tagCount)) edgeDict[index] = tagCount - #else: - #print(" skipping=%-2d (%.2f)" % (index, edge.Length)) + else: + print(" skipping=%-2d (%.2f)" % (index, edge.Length)) return (currentLength, lastTagLength) @@ -532,8 +534,8 @@ class ObjectDressup: edge = None t = 0 - for cmd in commands: - print(cmd) + #for cmd in commands: + # print(cmd) return Path.Path(commands) @@ -547,10 +549,6 @@ class ObjectDressup: if not obj.Base.Path.Commands: return - if obj.Path: - for cmd in obj.Path.Commands: - print(cmd) - pathData = self.setup(obj) if not pathData: print("execute - no pathData") @@ -595,7 +593,7 @@ class ObjectDressup: obj.Path = self.createPath(pathData.edges, tags) def setTags(self, obj, tags, update = True): - print("setTags(.....)") + print("setTags(%d, %d)" % (len(tags), update)) for t in tags: print(" .... %s" % t.toString()) obj.Tags = [tag.toString() for tag in tags] From ab382ce4362038dea022e1407481a07d0058991b Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 11 Dec 2016 10:49:10 -0800 Subject: [PATCH 028/144] Improved tag height based on obj.Base properties, if they exist. --- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index d286214306..adeae6e986 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -47,7 +47,7 @@ except AttributeError: def translate(context, text, disambig=None): return QtGui.QApplication.translate(context, text, disambig) -debugDressup = False +debugDressup = True debugComponents = ['P0', 'P1', 'P2', 'P3'] def debugPrint(comp, msg): @@ -246,13 +246,16 @@ class MapWireToTag: def cleanupEdges(self, edges): # first remove all internal struts + debugEdge(Part.Edge(Part.LineSegment(self.entry, self.exit)), '------> cleanupEdges') inputEdges = copy.copy(edges) plinths = [] for e in edges: + debugEdge(e, '........ cleanup') p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) if p1.x == p2.x and p1.y == p2.y: if not (PathGeom.edgeConnectsTo(e, self.entry) or PathGeom.edgeConnectsTo(e, self.exit)): + debugEdge(e, '......... X0 %d/%d' % (PathGeom.edgeConnectsTo(e, self.entry), PathGeom.edgeConnectsTo(e, self.exit))) inputEdges.remove(e) if p1.z > p2.z: plinths.append(p2) @@ -262,13 +265,15 @@ class MapWireToTag: # including the edge that connects the entry and exit point directly for e in copy.copy(inputEdges): if PathGeom.edgeConnectsTo(e, self.entry) and PathGeom.edgeConnectsTo(e, self.exit): + debugEdge(e, '......... X1') inputEdges.remove(e) continue for p in plinths: if PathGeom.edgeConnectsTo(e, p): + debugEdge(e, '......... X2') inputEdges.remove(e) break - # the remaining edges form walk around the tag + # the remaining edges form a walk around the tag # they need to be ordered and potentially flipped though outputEdges = [] p = self.entry @@ -447,6 +452,8 @@ class PathData: return (currentLength, lastTagLength) def tagHeight(self): + if hasattr(self.obj, 'Base') and hasattr(self.obj.Base, 'StartDepth') and hasattr(self.obj.Base, 'FinalDepth'): + return self.obj.Base.StartDepth - self.obj.Base.FinalDepth return self.maxZ - self.minZ def tagWidth(self): From 6b758e2714650b40630b99f05cb9269e79a0d4be Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 12 Dec 2016 14:39:30 -0800 Subject: [PATCH 029/144] Added support for vertical paths along the edge of a cylindrical tag. --- .../PathScripts/PathDressupHoldingTags.py | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index adeae6e986..bf7b37f802 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -215,10 +215,12 @@ class Tag: class MapWireToTag: def __init__(self, edge, tag, i): + debugEdge(edge, 'MapWireToTag(%.2f, %.2f, %.2f)' % (i.x, i.y, i.z)) self.tag = tag if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), i): tail = edge self.commands = [] + debugEdge(tail, '.........=') elif PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), i): debugEdge(edge, '++++++++ .') self.commands = PathGeom.cmdsForEdge(edge) @@ -227,6 +229,7 @@ class MapWireToTag: e, tail = PathGeom.splitEdgeAt(edge, i) debugEdge(e, '++++++++ .') self.commands = PathGeom.cmdsForEdge(e) + debugEdge(tail, '.........-') self.tail = tail self.edges = [] self.entry = i @@ -234,6 +237,7 @@ class MapWireToTag: self.wire = None def addEdge(self, edge): + debugEdge(edge, '..........') if self.wire: self.wire.add(edge) else: @@ -244,6 +248,18 @@ class MapWireToTag: return True, edge.valueAt(edge.FirstParameter) return False, edge.valueAt(edge.LastParameter) + def isEntryOrExitStrut(self, p1, p2): + p = PathGeom.xy(p1) + pEntry0 = PathGeom.xy(self.entry) + pExit0 = PathGeom.xy(self.exit) + # it can only be an entry strut if the strut coincides with the entry point and is above it + if PathGeom.pointsCoincide(p, pEntry0) and p1.z >= self.entry.z and p2.z >= self.entry.z: + return True + if PathGeom.pointsCoincide(p, pExit0) and p1.z >= self.exit.z and p2.z >= self.exit.z: + return True + return False + + def cleanupEdges(self, edges): # first remove all internal struts debugEdge(Part.Edge(Part.LineSegment(self.entry, self.exit)), '------> cleanupEdges') @@ -253,8 +269,9 @@ class MapWireToTag: debugEdge(e, '........ cleanup') p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) - if p1.x == p2.x and p1.y == p2.y: - if not (PathGeom.edgeConnectsTo(e, self.entry) or PathGeom.edgeConnectsTo(e, self.exit)): + if PathGeom.pointsCoincide(PathGeom.xy(p1), PathGeom.xy(p2)): + #it's a strut + if not self.isEntryOrExitStrut(p1, p2): debugEdge(e, '......... X0 %d/%d' % (PathGeom.edgeConnectsTo(e, self.entry), PathGeom.edgeConnectsTo(e, self.exit))) inputEdges.remove(e) if p1.z > p2.z: @@ -495,6 +512,16 @@ class ObjectDressup: def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): return self.pathData.generateTags(obj, count, width, height, angle, spacing) + def isValidTagStartIntersection(self, edge, i): + if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): + return False + p1 = edge.valueAt(edge.FirstParameter) + p2 = edge.valueAt(edge.LastParameter) + if PathGeom.pointsCoincide(PathGeom.xy(p1), PathGeom.xy(p2)): + # if this vertical goes up, it can't be the start of a tag intersection + if p1.z < p2.z: + return False + return True def createPath(self, edges, tags): commands = [] @@ -528,7 +555,7 @@ class ObjectDressup: tIndex = (t + lastTag) % len(tags) t += 1 i = tags[tIndex].intersects(edge, edge.FirstParameter) - if i: + if i and self.isValidTagStartIntersection(edge, i): mapper = MapWireToTag(edge, tags[tIndex], i) edge = mapper.tail From c1fc88c6a36b89ebf5fdb0750c1f5cb247a1dbe6 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 12 Dec 2016 20:53:58 -0800 Subject: [PATCH 030/144] Fixed caching issue. --- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index bf7b37f802..2617134b77 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -348,6 +348,7 @@ class MapWireToTag: class PathData: def __init__(self, obj): + print("PathData(%s)" % obj.Base.Name) self.obj = obj self.wire = PathGeom.wireForPath(obj.Base.Path) self.edges = self.wire.Edges @@ -640,7 +641,7 @@ class ObjectDressup: return self.setup(obj).generateTags(obj, 4) def setup(self, obj): - if False or not hasattr(self, "pathData") or not self.pathData: + if True or not hasattr(self, "pathData") or not self.pathData: try: pathData = PathData(obj) except ValueError: From d6c588e57b08a15e93fee2ecf906f0cd77e4f423 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 12 Dec 2016 22:41:18 -0800 Subject: [PATCH 031/144] Added resiliancy against vertical path elements. --- .../PathScripts/PathDressupHoldingTags.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 2617134b77..1d43a6039d 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -279,17 +279,20 @@ class MapWireToTag: else: plinths.append(p1) # remove all edges that are connected to the plinths of the (former) internal struts - # including the edge that connects the entry and exit point directly for e in copy.copy(inputEdges): - if PathGeom.edgeConnectsTo(e, self.entry) and PathGeom.edgeConnectsTo(e, self.exit): - debugEdge(e, '......... X1') - inputEdges.remove(e) - continue for p in plinths: if PathGeom.edgeConnectsTo(e, p): - debugEdge(e, '......... X2') + debugEdge(e, '......... X1') inputEdges.remove(e) break + # if there are any edges beside a direct edge remaining, the direct edge between + # entry and exit is redundant + if len(inputEdges) > 1: + for e in copy.copy(inputEdges): + if PathGeom.edgeConnectsTo(e, self.entry) and PathGeom.edgeConnectsTo(e, self.exit): + debugEdge(e, '......... X2') + inputEdges.remove(e) + # the remaining edges form a walk around the tag # they need to be ordered and potentially flipped though outputEdges = [] @@ -321,6 +324,13 @@ class MapWireToTag: # print("xxxxxx (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) return outputEdges + def shell(self): + shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) + redundant = filter(lambda f: f.Area == 0, shell.childShapes()) + if redundant: + return shell.removeShape(redundant) + return shell + def add(self, edge): self.tail = None if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), 0.000001, True): @@ -335,8 +345,7 @@ class MapWireToTag: self.tail = tail self.exit = i if self.wire: - shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) - face = shell.common(self.tag.solid) + face = self.shell().common(self.tag.solid) for e,flip in self.cleanupEdges(face.Edges): debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) From 068dc2d72dbf91ad53fd32bad1ff1fb42fb58a37 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Tue, 13 Dec 2016 11:29:16 -0800 Subject: [PATCH 032/144] Reduced logging. --- .../PathScripts/PathDressupHoldingTags.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 1d43a6039d..1f72b00947 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -47,11 +47,10 @@ except AttributeError: def translate(context, text, disambig=None): return QtGui.QApplication.translate(context, text, disambig) -debugDressup = True -debugComponents = ['P0', 'P1', 'P2', 'P3'] +debugDressup = False -def debugPrint(comp, msg): - if debugDressup and comp in debugComponents: +def debugPrint(msg): + if debugDressup: print(msg) def debugEdge(edge, prefix, comp = None): @@ -108,7 +107,7 @@ class Tag: return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) def __init__(self, x, y, width, height, angle, enabled=True, z=None): - print("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d, %s)" % (x, y, width, height, angle/math.pi, enabled, z)) + debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d, %s)" % (x, y, width, height, angle/math.pi, enabled, z)) self.x = x self.y = y self.z = z @@ -357,7 +356,7 @@ class MapWireToTag: class PathData: def __init__(self, obj): - print("PathData(%s)" % obj.Base.Name) + debugPrint("PathData(%s)" % obj.Base.Name) self.obj = obj self.wire = PathGeom.wireForPath(obj.Base.Path) self.edges = self.wire.Edges @@ -420,7 +419,7 @@ class PathData: startIndex = 0 for i in range(0, len(self.base.Edges)): edge = self.base.Edges[i] - print(' %d: %.2f' % (i, edge.Length)) + debugPrint(' %d: %.2f' % (i, edge.Length)) if edge.Length == longestEdge.Length: startIndex = i break @@ -435,10 +434,10 @@ class PathData: minLength = min(2. * W, longestEdge.Length) - print("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f) minLength=%.2f" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length, minLength)) - print(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) - print(" -> lastTagLength=%.2f)" % lastTagLength) - print(" -> currentLength=%.2f)" % currentLength) + debugPrint("length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f) minLength=%.2f" % (self.base.Length, shortestEdge.Length, shortestEdge.Length/self.base.Length, longestEdge.Length, longestEdge.Length / self.base.Length, minLength)) + debugPrint(" start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) + debugPrint(" -> lastTagLength=%.2f)" % lastTagLength) + debugPrint(" -> currentLength=%.2f)" % currentLength) edgeDict = { startIndex: startCount } @@ -453,7 +452,7 @@ class PathData: for (i, count) in edgeDict.iteritems(): edge = self.base.Edges[i] - print(" %d: %d" % (i, count)) + debugPrint(" %d: %d" % (i, count)) #debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) #debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) distance = (edge.LastParameter - edge.FirstParameter) / count @@ -471,10 +470,10 @@ class PathData: tagCount += 1 lastTagLength += tagDistance if tagCount > 0: - print(" index=%d -> count=%d" % (index, tagCount)) + debugPrint(" index=%d -> count=%d" % (index, tagCount)) edgeDict[index] = tagCount else: - print(" skipping=%-2d (%.2f)" % (index, edge.Length)) + debugPrint(" skipping=%-2d (%.2f)" % (index, edge.Length)) return (currentLength, lastTagLength) @@ -606,7 +605,7 @@ class ObjectDressup: tags = [Tag.FromString(tag) for tag in obj.Tags] else: print("execute - default tags") - tags = self.generateTags(obj, 2.) + tags = self.generateTags(obj, 4.) if not tags: print("execute - no tags") From f232096eb4689fe85e7c5f71aa290cb1f64c6723 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 15 Dec 2016 15:47:52 -0800 Subject: [PATCH 033/144] Fixed build and tests. --- .../Path/PathTests/TestPathDressupHoldingTags.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index d332a5b624..516b35e7db 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -50,27 +50,21 @@ class TestHoldingTags(PathTestBase): def test01(self): - """Verify solid and core for a 90 degree tag are identical cylinders.""" + """Verify solid for a 90 degree tag is a cylinder.""" tag = Tag(100, 200, 4, 5, 90, True) tag.createSolidsAt(17) self.assertIsNotNone(tag.solid) self.assertCylinderAt(tag.solid, Vector(100, 200, 17), 2, 5) - self.assertIsNotNone(tag.core) - self.assertCylinderAt(tag.core, Vector(100, 200, 17), 2, 5) - def test02(self): - """Verify trapezoidal tag has a cone shape with a lid, and cylinder core.""" + """Verify trapezoidal tag has a cone shape with a lid.""" tag = Tag(0, 0, 18, 5, 45, True) tag.createSolidsAt(0) self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 9, 4, 5) - self.assertIsNotNone(tag.core) - self.assertCylinderAt(tag.core, Vector(0,0,0), 4, 5) - def test03(self): """Verify pointy cone shape of tag with pointy end if width, angle and height match up.""" tag = Tag(0, 0, 10, 5, 45, True) @@ -78,8 +72,6 @@ class TestHoldingTags(PathTestBase): self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 5, 0, 5) - self.assertIsNone(tag.core) - def test04(self): """Verify height adjustment if tag isn't wide eough for angle.""" tag = Tag(0, 0, 5, 17, 60, True) @@ -87,5 +79,3 @@ class TestHoldingTags(PathTestBase): self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 2.5, 0, 2.5 * math.tan((60/180.0)*math.pi)) - self.assertIsNone(tag.core) - From 27b71ab1ae026ddb72e906bf12ce728cf4298baf Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Thu, 15 Dec 2016 18:22:09 -0800 Subject: [PATCH 034/144] Maintaining rapid commands. --- .../PathScripts/PathDressupHoldingTags.py | 41 +++++++++++++++---- src/Mod/Path/PathScripts/PathGeom.py | 11 +++-- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 1f72b00947..31d474f44e 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -53,12 +53,10 @@ def debugPrint(msg): if debugDressup: print(msg) -def debugEdge(edge, prefix, comp = None): +def debugEdge(edge, prefix, force = False): pf = edge.valueAt(edge.FirstParameter) pl = edge.valueAt(edge.LastParameter) - if comp: - debugPrint(comp, "%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) - elif debugDressup: + if force or debugDressup: print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) def debugMarker(vector, label, color = None, radius = 0.5): @@ -354,11 +352,34 @@ class MapWireToTag: def mappingComplete(self): return self.complete +class _RapidEdges: + def __init__(self, rapid): + self.rapid = rapid + + def isRapid(self, edge, removeIfFound=True): + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + v0 = edge.Vertexes[0] + v1 = edge.Vertexes[1] + for r in self.rapid: + r0 = r.Vertexes[0] + r1 = r.Vertexes[1] + if PathGeom.isRoughly(r0.X, v0.X) and PathGeom.isRoughly(r0.Y, v0.Y) and PathGeom.isRoughly(r0.Z, v0.Z) and PathGeom.isRoughly(r1.X, v1.X) and PathGeom.isRoughly(r1.Y, v1.Y) and PathGeom.isRoughly(r1.Z, v1.Z): + if removeIfFound: + self.rapid.remove(r) + return True + return False + + def p(self): + print('rapid:') + for r in self.rapid: + debugEdge(r, ' ', True) + class PathData: def __init__(self, obj): debugPrint("PathData(%s)" % obj.Base.Name) self.obj = obj - self.wire = PathGeom.wireForPath(obj.Base.Path) + self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) + self.rapid = _RapidEdges(rapid) self.edges = self.wire.Edges self.base = self.findBottomWire(self.edges) # determine overall length @@ -532,7 +553,7 @@ class ObjectDressup: return False return True - def createPath(self, edges, tags): + def createPath(self, edges, tags, rapid): commands = [] lastEdge = 0 lastTag = 0 @@ -573,7 +594,11 @@ class ObjectDressup: # gone through all tags, consume edge and move on if edge: debugEdge(edge, '++++++++') - commands.extend(PathGeom.cmdsForEdge(edge)) + if rapid.isRapid(edge, True): + v = edge.Vertexes[1] + commands.append(Path.Command('G0', {'X': v.X, 'Y': v.Y, 'Z': v.Z})) + else: + commands.extend(PathGeom.cmdsForEdge(edge)) edge = None t = 0 @@ -633,7 +658,7 @@ class ObjectDressup: self.fingerprint = [tag.toString() for tag in tags] self.tags = tags - obj.Path = self.createPath(pathData.edges, tags) + obj.Path = self.createPath(pathData.edges, tags, pathData.rapid) def setTags(self, obj, tags, update = True): print("setTags(%d, %d)" % (len(tags), update)) diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 1b6d7369e3..4eaf545106 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -63,7 +63,7 @@ class Side: class PathGeom: """Class to transform Path Commands into Edges and Wire and back again. The interface might eventuallly become part of Path itself.""" - CmdMoveFast = ['G0', 'G00'] + CmdMoveRapid = ['G0', 'G00'] CmdMoveStraight = ['G1', 'G01'] CmdMoveCW = ['G2', 'G02'] CmdMoveCCW = ['G3', 'G03'] @@ -195,7 +195,7 @@ class PathGeom: Returns an Edge representing the given command, assuming a given startPoint.""" endPoint = cls.commandEndPoint(cmd, startPoint) - if (cmd.Name in cls.CmdMoveStraight) or (cmd.Name in cls.CmdMoveFast): + if (cmd.Name in cls.CmdMoveStraight) or (cmd.Name in cls.CmdMoveRapid): if cls.pointsCoincide(startPoint, endPoint): return None return Part.Edge(Part.LineSegment(startPoint, endPoint)) @@ -247,13 +247,16 @@ class PathGeom: """(path, [startPoint=Vector(0,0,0)]) Returns a wire representing all move commands found in the given path.""" edges = [] + rapid = [] if hasattr(path, "Commands"): for cmd in path.Commands: edge = cls.edgeForCmd(cmd, startPoint) if edge: + if cmd.Name in cls.CmdMoveRapid: + rapid.append(edge) edges.append(edge) startPoint = cls.commandEndPoint(cmd, startPoint) - return Part.Wire(edges) + return (Part.Wire(edges), rapid) @classmethod def wiresForPath(cls, path, startPoint = FreeCAD.Vector(0, 0, 0)): @@ -266,7 +269,7 @@ class PathGeom: if cmd.Name in cls.CmdMove: edges.append(cls.edgeForCmd(cmd, startPoint)) startPoint = cls.commandEndPoint(cmd, startPoint) - elif cmd.Name in cls.CmdMoveFast: + elif cmd.Name in cls.CmdMoveRapid: wires.append(Part.Wire(edges)) edges = [] startPoint = cls.commandEndPoint(cmd, startPoint) From cb85072bbd34554dd6f110e3f5e7b872b9cc21ad Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 30 Dec 2016 19:16:27 -0800 Subject: [PATCH 035/144] Fixed alignment issue and unit tests. The trick is really to over-extend edges before creationg shapes for the common operation, and trying to avoid alignment of the edge with the cone's seam. --- .../Path/Gui/Resources/panels/DogboneEdit.ui | 6 +- .../Gui/Resources/panels/HoldingTagsEdit.ui | 239 +++------ .../PathScripts/PathDressupHoldingTags.py | 473 ++++++++++-------- src/Mod/Path/PathScripts/PathGeom.py | 12 + .../PathTests/TestPathDressupHoldingTags.py | 8 +- src/Mod/Path/PathTests/TestPathGeom.py | 4 +- 6 files changed, 355 insertions(+), 387 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/DogboneEdit.ui b/src/Mod/Path/Gui/Resources/panels/DogboneEdit.ui index 6b7f57f88e..d628b7e3d7 100644 --- a/src/Mod/Path/Gui/Resources/panels/DogboneEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/DogboneEdit.ui @@ -6,7 +6,7 @@ 0 0 - 352 + 376 387 @@ -27,8 +27,8 @@ 0 0 - 334 - 340 + 358 + 333 diff --git a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui index 465b0ad6ee..d8289aca7a 100644 --- a/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/HoldingTagsEdit.ui @@ -6,8 +6,8 @@ 0 0 - 352 - 387 + 363 + 530 @@ -27,8 +27,8 @@ 0 0 - 334 - 311 + 345 + 476 @@ -36,199 +36,118 @@ - - - - 0 - 0 - - - - true - - - 80 - - - false - - - - X + + + + QFormLayout::AllNonFixedFieldsGrow - - - - Y - - - - - Width - - - - - Height - - - - - Angle - - - - - - - - - + + - Delete + Width - - - - Disable + + + + <html><head/><body><p>Specify the resulting width of tags at the base.</p><p>The initial default width is based on the longest edge found in the base path.</p></body></html> - - + + - Add + Height + + + + + + + <html><head/><body><p>Height of holding tags.</p></body></html> + + + + + + + Angle + + + + + + + <html><head/><body><p>Angle of ascend and descend of the tool for the holding tag cutout.</p><p><br/></p><p>If the angle is too flat for the given width to reach the specified height the resulting tag will have a triangular shape and not as high as specified.</p></body></html> - - - - - - 0 - 0 - 334 - 311 - - - - Generate - - - - - - Width - - - - - + + - <html><head/><body><p>Width of each tag.</p></body></html> + <html><head/><body><p>List of current tags.</p><p>Edit coordinates to move tag or invoker snapper tool.</p></body></html> - - - - Height - - - - - - - <html><head/><body><p>The height of the holding tag measured from the bottom of the path. By default this is set to the (estimated) height of the path.</p></body></html> - - - - - - - true - - - Angle - - - - - - - true - - - <html><head/><body><p>Angle of tag walls.</p></body></html> - - - - - - - QDialogButtonBox::Apply|QDialogButtonBox::Ok - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Layout - - + + + - + + + false + - Count + Delete - - - <html><head/><body><p>Enter the number of tags you wish to have.</p><p><br/></p><p>Note that sometimes it's necessary to enter a larger than desired count number and disable the ones tags you don't want in order to get the holding tag layout you want.</p></body></html> + + + Add... + + + + + + + + + + Auto Generate + + + + + + + + + false + + + Replace All - Spacing + Number of Tags + + + Qt::AlignCenter - - - - - - - Auto Apply - - - diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 31d474f44e..3be16f87ed 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -29,6 +29,9 @@ import Part import copy import math +import cProfile +import time + from PathScripts import PathUtils from PathScripts.PathGeom import * from PySide import QtCore, QtGui @@ -57,7 +60,11 @@ def debugEdge(edge, prefix, force = False): pf = edge.valueAt(edge.FirstParameter) pl = edge.valueAt(edge.LastParameter) if force or debugDressup: - print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) + if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: + print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z)) + else: + pm = edge.valueAt((edge.FirstParameter+edge.LastParameter)/2) + print("%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pm.x, pm.y, pm.z, pl.x, pl.y, pl.z)) def debugMarker(vector, label, color = None, radius = 0.5): if debugDressup: @@ -104,18 +111,18 @@ class Tag: def toString(self): return str((self.x, self.y, self.width, self.height, self.angle, self.enabled)) - def __init__(self, x, y, width, height, angle, enabled=True, z=None): - debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d, %s)" % (x, y, width, height, angle/math.pi, enabled, z)) + def __init__(self, x, y, width, height, angle, enabled=True): + debugPrint("Tag(%.2f, %.2f, %.2f, %.2f, %.2f, %d)" % (x, y, width, height, angle/math.pi, enabled)) self.x = x self.y = y - self.z = z self.width = math.fabs(width) self.height = math.fabs(height) self.actualHeight = self.height self.angle = math.fabs(angle) self.enabled = enabled - if z is not None: - self.createSolidsAt(z) + + def fullWidth(self): + return 2 * self.toolRadius + self.width def originAt(self, z): return FreeCAD.Vector(self.x, self.y, z) @@ -126,14 +133,16 @@ class Tag: def top(self): return self.z + self.actualHeight - def createSolidsAt(self, z): + def createSolidsAt(self, z, R): self.z = z - r1 = self.width / 2 + self.toolRadius = R + r1 = self.fullWidth() / 2 self.r1 = r1 self.r2 = r1 height = self.height if self.angle == 90 and height > 0: self.solid = Part.makeCylinder(r1, height) + print("Part.makeCone(%f, %f)" % (r1, height)) elif self.angle > 0.0 and height > 0.0: tangens = math.tan(math.radians(self.angle)) dr = height / tangens @@ -144,10 +153,17 @@ class Tag: height = r1 * tangens self.actualHeight = height self.r2 = r2 + print("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag + print("Part.makeSphere(%f / 10000)" % (r1)) self.solid = Part.makeSphere(r1 / 10000) + if not R == 0: # testing is easier if the solid is not rotated + angle = -PathGeom.getAngle(self.originAt(0)) * 180 / math.pi + print("solid.rotate(%f)" % angle) + self.solid.rotate(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), angle) + print("solid.translate(%s)" % self.originAt(z)) self.solid.translate(self.originAt(z)) def filterIntersections(self, pts, face): @@ -206,8 +222,9 @@ class Tag: return None def intersects(self, edge, param): - if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top(): - return self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(param)) + if self.enabled: + if edge.valueAt(edge.FirstParameter).z < self.top() or edge.valueAt(edge.LastParameter).z < self.top(): + return self.nextIntersectionClosestTo(edge, self.solid, edge.valueAt(param)) return None class MapWireToTag: @@ -231,105 +248,131 @@ class MapWireToTag: self.edges = [] self.entry = i self.complete = False - self.wire = None + self.haveProblem = False def addEdge(self, edge): debugEdge(edge, '..........') - if self.wire: - self.wire.add(edge) - else: - self.wire = Part.Wire(edge) + self.edges.append(edge) def needToFlipEdge(self, edge, p): if PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), p): return True, edge.valueAt(edge.FirstParameter) return False, edge.valueAt(edge.LastParameter) - def isEntryOrExitStrut(self, p1, p2): - p = PathGeom.xy(p1) - pEntry0 = PathGeom.xy(self.entry) - pExit0 = PathGeom.xy(self.exit) - # it can only be an entry strut if the strut coincides with the entry point and is above it - if PathGeom.pointsCoincide(p, pEntry0) and p1.z >= self.entry.z and p2.z >= self.entry.z: - return True - if PathGeom.pointsCoincide(p, pExit0) and p1.z >= self.exit.z and p2.z >= self.exit.z: - return True - return False + def isEntryOrExitStrut(self, e): + p1 = e.valueAt(e.FirstParameter) + p2 = e.valueAt(e.LastParameter) + if PathGeom.pointsCoincide(p1, self.entry) and p2.z >= self.entry.z: + return 1 + if PathGeom.pointsCoincide(p2, self.entry) and p1.z >= self.entry.z: + return 1 + if PathGeom.pointsCoincide(p1, self.exit) and p2.z >= self.exit.z: + return 2 + if PathGeom.pointsCoincide(p2, self.exit) and p1.z >= self.exit.z: + return 2 + return 0 - - def cleanupEdges(self, edges): - # first remove all internal struts - debugEdge(Part.Edge(Part.LineSegment(self.entry, self.exit)), '------> cleanupEdges') - inputEdges = copy.copy(edges) - plinths = [] + def cleanupEdges(self, edges, baseEdge): + # want to remove all edges from the wire itself, and all internal struts + print("+cleanupEdges") + print(" base:") + debugEdge(baseEdge, ' ', True) + print(" edges:") for e in edges: - debugEdge(e, '........ cleanup') - p1 = e.valueAt(e.FirstParameter) - p2 = e.valueAt(e.LastParameter) - if PathGeom.pointsCoincide(PathGeom.xy(p1), PathGeom.xy(p2)): - #it's a strut - if not self.isEntryOrExitStrut(p1, p2): - debugEdge(e, '......... X0 %d/%d' % (PathGeom.edgeConnectsTo(e, self.entry), PathGeom.edgeConnectsTo(e, self.exit))) - inputEdges.remove(e) - if p1.z > p2.z: - plinths.append(p2) - else: - plinths.append(p1) - # remove all edges that are connected to the plinths of the (former) internal struts - for e in copy.copy(inputEdges): - for p in plinths: - if PathGeom.edgeConnectsTo(e, p): - debugEdge(e, '......... X1') - inputEdges.remove(e) - break - # if there are any edges beside a direct edge remaining, the direct edge between - # entry and exit is redundant - if len(inputEdges) > 1: - for e in copy.copy(inputEdges): - if PathGeom.edgeConnectsTo(e, self.entry) and PathGeom.edgeConnectsTo(e, self.exit): - debugEdge(e, '......... X2') - inputEdges.remove(e) + debugEdge(e, ' ', True) + print(":") - # the remaining edges form a walk around the tag - # they need to be ordered and potentially flipped though + haveEntry = False + for e in copy.copy(edges): + if PathGeom.edgesMatch(e, baseEdge): + debugEdge(e, '......... X0') + edges.remove(e) + elif self.isStrut(e): + typ = self.isEntryOrExitStrut(e) + debugEdge(e, '......... |%d' % typ) + if 0 == typ: # neither entry nor exit + debugEdge(e, '......... X1') + edges.remove(e) + elif 1 == typ: + haveEntry = True + + print("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) + # the remaininng edges for the path from xy(baseEdge) along the tags surface outputEdges = [] - p = self.entry - lastP = p - while inputEdges: - for e in inputEdges: + p0 = baseEdge.valueAt(baseEdge.FirstParameter) + ignoreZ = False + if not haveEntry: + ignoreZ = True + p0 = PathGeom.xy(p0) + lastP = p0 + while edges: + print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z, haveEntry, ignoreZ)) + for e in edges: p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) - if PathGeom.pointsCoincide(p1, p): - outputEdges.append((e,False)) - inputEdges.remove(e) - lastP = p - p = p2 + if PathGeom.pointsCoincide(PathGeom.xy(p1) if ignoreZ else p1, p0): + outputEdges.append((e, False)) + edges.remove(e) + lastP = None + ignoreZ = False + p0 = p2 debugEdge(e, ">>>>> no flip") break - elif PathGeom.pointsCoincide(p2, p): - outputEdges.append((e,True)) - inputEdges.remove(e) - lastP = p - p = p1 + elif PathGeom.pointsCoincide(PathGeom.xy(p2) if ignoreZ else p2, p0): + outputEdges.append((e, True)) + edges.remove(e) + lastP = None + ignoreZ = False + p0 = p1 debugEdge(e, ">>>>> flip") break - #else: - # debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) - if lastP == p: - raise ValueError("No connection to %s" % (p)) - #else: - # print("xxxxxx (%.2f, %.2f, %.2f)" % (p.x, p.y, p.z)) + else: + debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z)) + if lastP == p0: + raise ValueError("No connection to %s" % (p0)) + elif lastP: + print("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) + else: + print("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) + lastP = p0 + print("-cleanupEdges") return outputEdges - def shell(self): - shell = self.wire.extrude(FreeCAD.Vector(0, 0, 10)) - redundant = filter(lambda f: f.Area == 0, shell.childShapes()) - if redundant: - return shell.removeShape(redundant) - return shell + def isStrut(self, edge): + p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) + p2 = PathGeom.xy(edge.valueAt(edge.LastParameter)) + return PathGeom.pointsCoincide(p1, p2) + + def cmdsForEdge(self, edge): + cmds = [] + + # OCC doesn't like it very much if the shapes align with each other. So if we have a slightly + # extended edge for the last edge in list we'll use that instead for stable results. + if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.lastEdge.valueAt(self.lastEdge.FirstParameter)): + shell = self.lastEdge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) + else: + shell = edge.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) + shape = shell.common(self.tag.solid) + + if not shape.Edges: + self.haveProblem = True + + for e,flip in self.cleanupEdges(shape.Edges, edge): + debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) + cmds.extend(PathGeom.cmdsForEdge(e, flip, False)) + return cmds + + def commandsForEdges(self): + commands = [] + for e in self.edges: + if self.isStrut(e): + continue + commands.extend(self.cmdsForEdge(e)) + return commands def add(self, edge): self.tail = None + self.lastEdge = edge # see cmdsForEdge if self.tag.solid.isInside(edge.valueAt(edge.LastParameter), 0.000001, True): self.addEdge(edge) else: @@ -341,13 +384,8 @@ class MapWireToTag: self.addEdge(e) self.tail = tail self.exit = i - if self.wire: - face = self.shell().common(self.tag.solid) - - for e,flip in self.cleanupEdges(face.Edges): - debugEdge(e, '++++++++ %s' % ('.' if not flip else '@')) - self.commands.extend(PathGeom.cmdsForEdge(e, flip, False)) self.complete = True + self.commands.extend(self.commandsForEdges()) def mappingComplete(self): return self.complete @@ -399,9 +437,11 @@ class PathData: def findZLimits(self, edges): # not considering arcs and spheres in Z direction, find the highes and lowest Z values - minZ = edges[0].Vertexes[0].Point.z - maxZ = minZ + minZ = 99999999999 + maxZ = -99999999999 for e in edges: + if self.rapid.isRapid(e): + continue for v in e.Vertexes: if v.Point.z < minZ: minZ = v.Point.z @@ -476,10 +516,11 @@ class PathData: debugPrint(" %d: %d" % (i, count)) #debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) #debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) - distance = (edge.LastParameter - edge.FirstParameter) / count - for j in range(0, count): - tag = edge.Curve.value((j+0.5) * distance) - tags.append(Tag(tag.x, tag.y, W, H, angle, True)) + if 0 != count: + distance = (edge.LastParameter - edge.FirstParameter) / count + for j in range(0, count): + tag = edge.Curve.value((j+0.5) * distance) + tags.append(Tag(tag.x, tag.y, W, H, angle, True)) return tags @@ -554,6 +595,7 @@ class ObjectDressup: return True def createPath(self, edges, tags, rapid): + print("createPath") commands = [] lastEdge = 0 lastTag = 0 @@ -562,10 +604,11 @@ class ObjectDressup: inters = None edge = None + self.mappers = [] mapper = None while edge or lastEdge < len(edges): - #print("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) + debugPrint("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) if not edge: edge = edges[lastEdge] debugEdge(edge, "======= new edge: %d/%d" % (lastEdge, len(edges))) @@ -587,6 +630,7 @@ class ObjectDressup: i = tags[tIndex].intersects(edge, edge.FirstParameter) if i and self.isValidTagStartIntersection(edge, i): mapper = MapWireToTag(edge, tags[tIndex], i) + self.mappers.append(mapper) edge = mapper.tail @@ -606,8 +650,17 @@ class ObjectDressup: # print(cmd) return Path.Path(commands) + def problems(self): + return filter(lambda m: m.haveProblem, self.mappers) def execute(self, obj): + #pr = cProfile.Profile() + #pr.enable() + self.doExecute(obj) + #pr.disable() + #pr.print_stats() + + def doExecute(self,obj): if not obj.Base: return if not obj.Base.isDerivedFrom("Path::Feature"): @@ -642,7 +695,7 @@ class ObjectDressup: tags = pathData.sortedTags(tags) self.setTags(obj, tags, False) for tag in tags: - tag.createSolidsAt(pathData.minZ) + tag.createSolidsAt(pathData.minZ, self.toolRadius) tagID = 0 for tag in tags: @@ -653,12 +706,13 @@ class ObjectDressup: if tag.angle != 90: debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) else: - debugCylinder(tag.originAt(pathData.minZ), tag.width/2, tag.actualHeight, "tag-%02d" % tagID) + debugCylinder(tag.originAt(pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) self.fingerprint = [tag.toString() for tag in tags] self.tags = tags obj.Path = self.createPath(pathData.edges, tags, pathData.rapid) + print("execute - done") def setTags(self, obj, tags, update = True): print("setTags(%d, %d)" % (len(tags), update)) @@ -681,32 +735,16 @@ class ObjectDressup: FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) return None - ## setup the object's properties, in case they're not set yet - #obj.Count = self.tagCount(obj) - #obj.Angle = self.tagAngle(obj) - #obj.Blacklist = self.tagBlacklist(obj) - - # if the heigt isn't set, use the height of the path - #if not hasattr(obj, "Height") or not obj.Height: - # obj.Height = pathData.maxZ - pathData.minZ - # try and take an educated guess at the width - #if not hasattr(obj, "Width") or not obj.Width: - # width = sorted(pathData.base.Edges, key=lambda e: -e.Length)[0].Length / 10 - # while obj.Count > len([e for e in pathData.base.Edges if e.Length > 3*width]): - # width = widht / 2 - # obj.Width = width - - # and the tool radius, not sure yet if it's needed - #self.toolRadius = 5 - #toolLoad = PathUtils.getLastToolLoad(obj) - #if toolLoad is None or toolLoad.ToolNumber == 0: - # self.toolRadius = 5 - #else: - # tool = PathUtils.getTool(obj, toolLoad.ToolNumber) - # if not tool or tool.Diameter == 0: - # self.toolRadius = 5 - # else: - # self.toolRadius = tool.Diameter / 2 + self.toolRadius = 5 + toolLoad = PathUtils.getLastToolLoad(obj) + if toolLoad is None or toolLoad.ToolNumber == 0: + self.toolRadius = 5 + else: + tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + if not tool or tool.Diameter == 0: + self.toolRadius = 5 + else: + self.toolRadius = tool.Diameter / 2 self.pathData = pathData return self.pathData @@ -723,8 +761,8 @@ class ObjectDressup: return self.pathData.pathLength() class TaskPanel: - DataTag = QtCore.Qt.ItemDataRole.UserRole - DataValue = QtCore.Qt.ItemDataRole.DisplayRole + DataX = QtCore.Qt.ItemDataRole.UserRole + DataY = QtCore.Qt.ItemDataRole.UserRole + 1 def __init__(self, obj): self.obj = obj @@ -738,6 +776,7 @@ class TaskPanel: FreeCADGui.Selection.removeObserver(self.s) def accept(self): + self.getFields() FreeCAD.ActiveDocument.commitTransaction() FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() @@ -750,40 +789,38 @@ class TaskPanel: # install the function mode resident FreeCADGui.Selection.addObserver(self.s) - def tableWidgetItem(self, tag, val): - item = QtGui.QTableWidgetItem() - item.setTextAlignment(QtCore.Qt.AlignRight) - item.setData(self.DataTag, tag) - item.setData(self.DataValue, val) - return item - def getFields(self): + width = self.form.dsbWidth.value() + height = self.form.dsbHeight.value() + angle = self.form.dsbAngle.value() tags = [] - for row in range(0, self.form.twTags.rowCount()): - x = self.form.twTags.item(row, 0).data(self.DataValue) - y = self.form.twTags.item(row, 1).data(self.DataValue) - w = self.form.twTags.item(row, 2).data(self.DataValue) - h = self.form.twTags.item(row, 3).data(self.DataValue) - a = self.form.twTags.item(row, 4).data(self.DataValue) - tags.append(Tag(x, y, w, h, a, True)) - print("getFields: %d" % (len(tags))) + for i in range(0, self.form.lwTags.count()): + item = self.form.lwTags.item(i) + enabled = item.checkState() == QtCore.Qt.CheckState.Checked + x = item.data(self.DataX) + y = item.data(self.DataY) + tags.append(Tag(x, y, width, height, angle, enabled)) self.obj.Proxy.setTags(self.obj, tags) - def updateTags(self): + def updateTagsView(self): self.tags = self.obj.Proxy.getTags(self.obj) - self.form.twTags.blockSignals(True) - self.form.twTags.setSortingEnabled(False) - self.form.twTags.clearSpans() - print("updateTags: %d" % (len(self.tags))) - self.form.twTags.setRowCount(len(self.tags)) - for row, tag in enumerate(self.tags): - self.form.twTags.setItem(row, 0, self.tableWidgetItem(tag, tag.x)) - self.form.twTags.setItem(row, 1, self.tableWidgetItem(tag, tag.y)) - self.form.twTags.setItem(row, 2, self.tableWidgetItem(tag, tag.width)) - self.form.twTags.setItem(row, 3, self.tableWidgetItem(tag, tag.height)) - self.form.twTags.setItem(row, 4, self.tableWidgetItem(tag, tag.angle)) - self.form.twTags.setSortingEnabled(True) - self.form.twTags.blockSignals(False) + print("updateTagsView: %d" % (len(self.tags))) + self.form.lwTags.blockSignals(True) + self.form.lwTags.clear() + for tag in self.tags: + lbl = "(%.2f, %.2f)" % (tag.x, tag.y) + item = QtGui.QListWidgetItem(lbl) + item.setData(self.DataX, tag.x) + item.setData(self.DataY, tag.y) + if tag.enabled: + item.setCheckState(QtCore.Qt.CheckState.Checked) + else: + item.setCheckState(QtCore.Qt.CheckState.Unchecked) + flags = QtCore.Qt.ItemFlag.ItemIsSelectable + flags |= QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsUserCheckable + item.setFlags(flags) + self.form.lwTags.addItem(item) + self.form.lwTags.blockSignals(False) def cleanupUI(self): print("cleanupUI") @@ -792,67 +829,50 @@ class TaskPanel: if obj.Name.startswith('tag'): FreeCAD.ActiveDocument.removeObject(obj.Name) - def updateUI(self): - print("updateUI") - self.cleanupUI() - self.getFields() - if debugDressup: - FreeCAD.ActiveDocument.recompute() - - - def whenApplyClicked(self): - print("whenApplyClicked") + def generateNewTags(self): + print("generateNewTags") self.cleanupUI() count = self.form.sbCount.value() - spacing = self.form.dsbSpacing.value() width = self.form.dsbWidth.value() height = self.form.dsbHeight.value() angle = self.form.dsbAngle.value() - tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle, spacing * 0.99) + tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle) self.obj.Proxy.setTags(self.obj, tags) - self.updateTags() - if debugDressup: - # this causes a big of an echo and a double click on the spin buttons, don't know why though - FreeCAD.ActiveDocument.recompute() + self.updateTagsView() + #if debugDressup: + # # this causes a big of an echo and a double click on the spin buttons, don't know why though + # FreeCAD.ActiveDocument.recompute() - def autoApply(self): - print("autoApply") - if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked: - self.whenApplyClicked() +# def autoApply(self): +# print("autoApply") +# if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked: +# self.whenApplyClicked() - def updateTagSpacing(self, count): - print("updateTagSpacing") - if count == 0: - spacing = 0 - else: - spacing = self.pathLength / count - self.form.dsbSpacing.blockSignals(True) - self.form.dsbSpacing.setValue(spacing) - self.form.dsbSpacing.blockSignals(False) + def updateModel(self): + self.getFields() + self.updateTagsView() + #FreeCAD.ActiveDocument.recompute() def whenCountChanged(self): print("whenCountChanged") - self.updateTagSpacing(self.form.sbCount.value()) - self.autoApply() + count = self.form.sbCount.value() + self.form.pbGenerate.setEnabled(count) - def whenSpacingChanged(self): - print("whenSpacingChanged") - if self.form.dsbSpacing.value() == 0: - count = 0 - else: - count = int(self.pathLength / self.form.dsbSpacing.value()) - self.form.sbCount.blockSignals(True) - self.form.sbCount.setValue(count) - self.form.sbCount.blockSignals(False) - self.autoApply() + def whenTagSelectionChanged(self): + print('whenTagSelectionChanged') + item = self.form.lwTags.currentItem() + self.form.pbDelete.setEnabled(not item is None) - def whenOkClicked(self): - print("whenOkClicked") - self.whenApplyClicked() - self.form.toolBox.setCurrentWidget(self.form.tbpTags) + def deleteSelectedTag(self): + item = self.form.lwTags.currentItem() + x = item.data(self.DataX) + y = item.data(self.DataY) + tags = filter(lambda t: t.x != x or t.y != y, self.tags) + self.obj.Proxy.setTags(self.obj, tags) + self.updateTagsView() def setupSpinBox(self, widget, val, decimals = 2): widget.setMinimum(0) @@ -861,29 +881,44 @@ class TaskPanel: widget.setValue(val) def setFields(self): - self.pathLength = self.obj.Proxy.getPathLength(self.obj) - vHeader = self.form.twTags.verticalHeader() - vHeader.setResizeMode(QtGui.QHeaderView.Fixed) - vHeader.setDefaultSectionSize(20) - self.updateTags() - self.setupSpinBox(self.form.sbCount, self.form.twTags.rowCount(), None) - self.setupSpinBox(self.form.dsbSpacing, 0) + self.updateTagsView() + self.setupSpinBox(self.form.sbCount, len(self.tags), None) self.setupSpinBox(self.form.dsbHeight, self.obj.Proxy.getHeight(self.obj)) self.setupSpinBox(self.form.dsbWidth, self.obj.Proxy.getWidth(self.obj)) - self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj)) - self.updateTagSpacing(self.form.twTags.rowCount()) + self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj), 0) + self.form.dsbAngle.setMaximum(90) + self.form.dsbAngle.setSingleStep(5.) + + def updateModelHeight(self): + print('updateModelHeight') + self.updateModel() + + def updateModelWidth(self): + print('updateModelWidth') + self.updateModel() + + def updateModelAngle(self): + print('updateModelAngle') + self.updateModel() + + def updateModelTags(self): + print('updateModelTags') + self.updateModel() def setupUi(self): self.setFields() + self.whenCountChanged() + self.form.sbCount.valueChanged.connect(self.whenCountChanged) - self.form.dsbSpacing.valueChanged.connect(self.whenSpacingChanged) - self.form.dsbHeight.valueChanged.connect(self.autoApply) - self.form.dsbWidth.valueChanged.connect(self.autoApply) - self.form.dsbAngle.valueChanged.connect(self.autoApply) - #self.form.pbAdd.clicked.connect(self.) - self.form.buttonBox.button(QtGui.QDialogButtonBox.Apply).clicked.connect(self.whenApplyClicked) - self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok).clicked.connect(self.whenOkClicked) - self.form.twTags.itemChanged.connect(self.updateUI) + self.form.pbGenerate.clicked.connect(self.generateNewTags) + + self.form.dsbHeight.editingFinished.connect(self.updateModelHeight) + self.form.dsbWidth.editingFinished.connect(self.updateModelWidth) + self.form.dsbAngle.editingFinished.connect(self.updateModelAngle) + self.form.lwTags.itemChanged.connect(self.updateModelTags) + self.form.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) + + self.form.pbDelete.clicked.connect(self.deleteSelectedTag) class SelObserver: def __init__(self): diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 4eaf545106..388b109522 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -82,6 +82,18 @@ class PathGeom: Return True if two points are roughly identical (see also isRoughly).""" return cls.isRoughly(p1.x, p2.x, error) and cls.isRoughly(p1.y, p2.y, error) and cls.isRoughly(p1.z, p2.z, error) + @classmethod + def edgesMatch(cls, e0, e1, error=0.0000001): + """(e0, e1, [error=0.0000001] + 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): + return False + if not cls.pointsCoincide(e0.valueAt(e0.FirstParameter), e1.valueAt(e1.FirstParameter)): + return False + if not cls.pointsCoincide(e0.valueAt(e0.LastParameter), e1.valueAt(e1.LastParameter)): + return False + return True + @classmethod def edgeConnectsTo(cls, edge, vector): """(edge, vector) diff --git a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py index 516b35e7db..2d74688da6 100644 --- a/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py +++ b/src/Mod/Path/PathTests/TestPathDressupHoldingTags.py @@ -52,7 +52,7 @@ class TestHoldingTags(PathTestBase): def test01(self): """Verify solid for a 90 degree tag is a cylinder.""" tag = Tag(100, 200, 4, 5, 90, True) - tag.createSolidsAt(17) + tag.createSolidsAt(17, 0) self.assertIsNotNone(tag.solid) self.assertCylinderAt(tag.solid, Vector(100, 200, 17), 2, 5) @@ -60,7 +60,7 @@ class TestHoldingTags(PathTestBase): def test02(self): """Verify trapezoidal tag has a cone shape with a lid.""" tag = Tag(0, 0, 18, 5, 45, True) - tag.createSolidsAt(0) + tag.createSolidsAt(0, 0) self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 9, 4, 5) @@ -68,14 +68,14 @@ class TestHoldingTags(PathTestBase): def test03(self): """Verify pointy cone shape of tag with pointy end if width, angle and height match up.""" tag = Tag(0, 0, 10, 5, 45, True) - tag.createSolidsAt(0) + tag.createSolidsAt(0, 0) self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 5, 0, 5) def test04(self): """Verify height adjustment if tag isn't wide eough for angle.""" tag = Tag(0, 0, 5, 17, 60, True) - tag.createSolidsAt(0) + tag.createSolidsAt(0, 0) self.assertIsNotNone(tag.solid) self.assertConeAt(tag.solid, Vector(0,0,0), 2.5, 0, 2.5 * math.tan((60/180.0)*math.pi)) diff --git a/src/Mod/Path/PathTests/TestPathGeom.py b/src/Mod/Path/PathTests/TestPathGeom.py index 7ab7a5396f..b6a3703c58 100644 --- a/src/Mod/Path/PathTests/TestPathGeom.py +++ b/src/Mod/Path/PathTests/TestPathGeom.py @@ -130,12 +130,14 @@ class TestPathGeom(PathTestBase): commands.append(Path.Command('G0', {'X': 0})) commands.append(Path.Command('G1', {'Y': 0})) - wire = PathGeom.wireForPath(Path.Path(commands)) + wire,rapid = PathGeom.wireForPath(Path.Path(commands)) self.assertEqual(len(wire.Edges), 4) self.assertLine(wire.Edges[0], Vector(0,0,0), Vector(1,0,0)) self.assertLine(wire.Edges[1], Vector(1,0,0), Vector(1,1,0)) self.assertLine(wire.Edges[2], Vector(1,1,0), Vector(0,1,0)) self.assertLine(wire.Edges[3], Vector(0,1,0), Vector(0,0,0)) + self.assertEqual(len(rapid), 1) + self.assertTrue(PathGeom.edgesMatch(rapid[0], wire.Edges[2])) wires = PathGeom.wiresForPath(Path.Path(commands)) self.assertEqual(len(wires), 2) From 9bf58b1c94c9b434ace19cf611b48edc2135c347 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 30 Dec 2016 23:40:16 -0800 Subject: [PATCH 036/144] Reduced logging. --- .../PathScripts/PathDressupHoldingTags.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 3be16f87ed..aa12276c8c 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -142,7 +142,7 @@ class Tag: height = self.height if self.angle == 90 and height > 0: self.solid = Part.makeCylinder(r1, height) - print("Part.makeCone(%f, %f)" % (r1, height)) + debugPrint("Part.makeCone(%f, %f)" % (r1, height)) elif self.angle > 0.0 and height > 0.0: tangens = math.tan(math.radians(self.angle)) dr = height / tangens @@ -153,17 +153,17 @@ class Tag: height = r1 * tangens self.actualHeight = height self.r2 = r2 - print("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) + debugPrint("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag - print("Part.makeSphere(%f / 10000)" % (r1)) + debugPrint("Part.makeSphere(%f / 10000)" % (r1)) self.solid = Part.makeSphere(r1 / 10000) if not R == 0: # testing is easier if the solid is not rotated angle = -PathGeom.getAngle(self.originAt(0)) * 180 / math.pi - print("solid.rotate(%f)" % angle) + debugPrint("solid.rotate(%f)" % angle) self.solid.rotate(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), angle) - print("solid.translate(%s)" % self.originAt(z)) + debugPrint("solid.translate(%s)" % self.originAt(z)) self.solid.translate(self.originAt(z)) def filterIntersections(self, pts, face): @@ -274,13 +274,13 @@ class MapWireToTag: def cleanupEdges(self, edges, baseEdge): # want to remove all edges from the wire itself, and all internal struts - print("+cleanupEdges") - print(" base:") - debugEdge(baseEdge, ' ', True) - print(" edges:") + #print("+cleanupEdges") + #print(" base:") + debugEdge(baseEdge, ' ') + #print(" edges:") for e in edges: - debugEdge(e, ' ', True) - print(":") + debugEdge(e, ' ') + #print(":") haveEntry = False for e in copy.copy(edges): @@ -296,7 +296,7 @@ class MapWireToTag: elif 1 == typ: haveEntry = True - print("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) + #print("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) # the remaininng edges for the path from xy(baseEdge) along the tags surface outputEdges = [] p0 = baseEdge.valueAt(baseEdge.FirstParameter) @@ -306,7 +306,7 @@ class MapWireToTag: p0 = PathGeom.xy(p0) lastP = p0 while edges: - print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z, haveEntry, ignoreZ)) + #print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z, haveEntry, ignoreZ)) for e in edges: p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) @@ -331,11 +331,11 @@ class MapWireToTag: if lastP == p0: raise ValueError("No connection to %s" % (p0)) elif lastP: - print("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) + debugPrint("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) else: - print("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) + debugPrint("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) lastP = p0 - print("-cleanupEdges") + #print("-cleanupEdges") return outputEdges def isStrut(self, edge): @@ -454,7 +454,7 @@ class PathData: return (edges[0], edges[-1]) def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): - print("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) + debugPrint("generateTags(%s, %s, %s, %s, %s)" % (count, width, height, angle, spacing)) #for e in self.base.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) @@ -716,8 +716,8 @@ class ObjectDressup: def setTags(self, obj, tags, update = True): print("setTags(%d, %d)" % (len(tags), update)) - for t in tags: - print(" .... %s" % t.toString()) + #for t in tags: + # print(" .... %s" % t.toString()) obj.Tags = [tag.toString() for tag in tags] if update: self.execute(obj) From 34f21054996eb688b9f0702449aef0738697ec85 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 31 Dec 2016 14:59:07 -0800 Subject: [PATCH 037/144] Fixed edit dialog initialisation. --- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index aa12276c8c..5c3ab7f9ea 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -723,9 +723,9 @@ class ObjectDressup: self.execute(obj) def getTags(self, obj): - if hasattr(self, 'tags'): - return self.tags - return self.setup(obj).generateTags(obj, 4) + if not hasattr(self, 'tags'): + self.execute(obj) + return self.tags def setup(self, obj): if True or not hasattr(self, "pathData") or not self.pathData: From 807cf80a77751aa2846edf860c21800fcfd88ac0 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sat, 31 Dec 2016 16:37:56 -0800 Subject: [PATCH 038/144] Hide job while editing dressup. --- .../PathScripts/PathDressupHoldingTags.py | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 5c3ab7f9ea..cccab2a001 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -768,21 +768,30 @@ class TaskPanel: self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) + self.jvo = PathUtils.findParentJob(obj).ViewObject + self.jvoVisible = self.jvo.isVisible() + if self.jvoVisible: + self.jvo.hide() def reject(self): + print("reject") FreeCAD.ActiveDocument.abortTransaction() - FreeCADGui.Control.closeDialog() - FreeCAD.ActiveDocument.recompute() - FreeCADGui.Selection.removeObserver(self.s) + self.cleanup() def accept(self): + print("accept") self.getFields() FreeCAD.ActiveDocument.commitTransaction() FreeCADGui.ActiveDocument.resetEdit() + self.cleanup() + FreeCAD.ActiveDocument.recompute() + + def cleanup(self): FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() FreeCADGui.Selection.removeObserver(self.s) - FreeCAD.ActiveDocument.recompute() + if self.jvoVisible: + self.jvo.show() def open(self): self.s = SelObserver() @@ -868,11 +877,18 @@ class TaskPanel: def deleteSelectedTag(self): item = self.form.lwTags.currentItem() - x = item.data(self.DataX) - y = item.data(self.DataY) - tags = filter(lambda t: t.x != x or t.y != y, self.tags) - self.obj.Proxy.setTags(self.obj, tags) - self.updateTagsView() + if item: + x = item.data(self.DataX) + y = item.data(self.DataY) + tags = filter(lambda t: t.x != x or t.y != y, self.tags) + self.obj.Proxy.setTags(self.obj, tags) + self.updateTagsView() + + def addNewTagAt(self, point, what): + print("%s '%s'" %( point, what.Name)) + + def addNewTag(self): + FreeCADGui.Snapper.getPoint(callback=self.addNewTagAt) def setupSpinBox(self, widget, val, decimals = 2): widget.setMinimum(0) @@ -919,6 +935,7 @@ class TaskPanel: self.form.lwTags.itemSelectionChanged.connect(self.whenTagSelectionChanged) self.form.pbDelete.clicked.connect(self.deleteSelectedTag) + self.form.pbAdd.clicked.connect(self.addNewTag) class SelObserver: def __init__(self): From 8efbe9e6451c3299552b893ced42d0abf8b2a9e9 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 1 Jan 2017 19:23:19 -0800 Subject: [PATCH 039/144] Basic UI for holding tags. --- .../PathScripts/PathDressupHoldingTags.py | 257 +++++++++--------- 1 file changed, 128 insertions(+), 129 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index cccab2a001..66ff437855 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -32,6 +32,7 @@ import math import cProfile import time +from DraftGui import todo from PathScripts import PathUtils from PathScripts.PathGeom import * from PySide import QtCore, QtGui @@ -468,11 +469,11 @@ class PathData: if width: W = width else: - W = self.tagWidth() + W = self.defaultTagWidth() if height: H = height else: - H = self.tagHeight() + H = self.defaultTagHeight() # start assigning tags on the longest segment @@ -539,20 +540,17 @@ class PathData: return (currentLength, lastTagLength) - def tagHeight(self): + def defaultTagHeight(self): if hasattr(self.obj, 'Base') and hasattr(self.obj.Base, 'StartDepth') and hasattr(self.obj.Base, 'FinalDepth'): - return self.obj.Base.StartDepth - self.obj.Base.FinalDepth + return (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value return self.maxZ - self.minZ - def tagWidth(self): + def defaultTagWidth(self): return self.shortestAndLongestPathEdge()[1].Length / 10 - def tagAngle(self): + def defaultTagAngle(self): return 90 - def pathLength(self): - return self.base.Length - def sortedTags(self, tags): ordered = [] for edge in self.base.Edges: @@ -570,8 +568,11 @@ class ObjectDressup: def __init__(self, obj): self.obj = obj obj.addProperty("App::PropertyLink", "Base","Base", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "The base path to modify")) - obj.addProperty("App::PropertyStringList", "Tags", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_holdingTags", "Inserted tags")) - obj.setEditorMode("Tags", 2) + obj.addProperty("App::PropertyFloat", "Width", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Width of tags.")) + obj.addProperty("App::PropertyFloat", "Height", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Height of tags.")) + obj.addProperty("App::PropertyFloat", "Angle", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Angle of tag plunge and ascent.")) + obj.addProperty("App::PropertyVectorList", "Positions", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Locations of insterted holding tags")) + obj.addProperty("App::PropertyIntegerList", "Disabled", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Ids of disabled holding tags")) obj.Proxy = self def __getstate__(self): @@ -580,8 +581,14 @@ class ObjectDressup: def __setstate__(self, state): return None - def generateTags(self, obj, count=None, width=None, height=None, angle=90, spacing=None): - return self.pathData.generateTags(obj, count, width, height, angle, spacing) + def generateTags(self, obj, count): + if hasattr(self, "pathData"): + self.tags = self.pathData.generateTags(obj, count, obj.Width, obj.Height, obj.Angle, None) + obj.Positions = [tag.originAt(0) for tag in self.tags] + obj.Disabled = [] + else: + self.setup(obj, count) + self.execute(obj) def isValidTagStartIntersection(self, edge, i): if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): @@ -675,103 +682,87 @@ class ObjectDressup: print("execute - no pathData") return - if hasattr(obj, 'Tags') and obj.Tags: - if False and self.fingerprint == obj.Tags: - print("execute - cache valid") - return - print("execute - tags from property") - tags = [Tag.FromString(tag) for tag in obj.Tags] - else: - print("execute - default tags") - tags = self.generateTags(obj, 4.) + self.tags = [] + if hasattr(obj, "Positions"): + for i, pos in enumerate(obj.Positions): + tag = Tag(pos.x, pos.y, obj.Width, obj.Height, obj.Angle, not i in obj.Disabled) + tag.createSolidsAt(pathData.minZ, self.toolRadius) + self.tags.append(tag) - if not tags: + if not self.tags: print("execute - no tags") - self.tags = [] obj.Path = obj.Base.Path return - print("execute - %d tags" % (len(tags))) - tags = pathData.sortedTags(tags) - self.setTags(obj, tags, False) - for tag in tags: - tag.createSolidsAt(pathData.minZ, self.toolRadius) - tagID = 0 - for tag in tags: - tagID += 1 - if tag.enabled: - #print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) - #debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) - if tag.angle != 90: - debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) - else: - debugCylinder(tag.originAt(pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) + if debugDressup: + for tag in self.tags: + tagID += 1 + if tag.enabled: + print("x=%s, y=%s, z=%s" % (tag.x, tag.y, pathData.minZ)) + debugMarker(FreeCAD.Vector(tag.x, tag.y, pathData.minZ), "tag-%02d" % tagID , (1.0, 0.0, 1.0), 0.5) + if tag.angle != 90: + debugCone(tag.originAt(pathData.minZ), tag.r1, tag.r2, tag.actualHeight, "tag-%02d" % tagID) + else: + debugCylinder(tag.originAt(pathData.minZ), tag.fullWidth()/2, tag.actualHeight, "tag-%02d" % tagID) - self.fingerprint = [tag.toString() for tag in tags] - self.tags = tags - - obj.Path = self.createPath(pathData.edges, tags, pathData.rapid) + obj.Path = self.createPath(pathData.edges, self.tags, pathData.rapid) print("execute - done") - def setTags(self, obj, tags, update = True): - print("setTags(%d, %d)" % (len(tags), update)) - #for t in tags: - # print(" .... %s" % t.toString()) - obj.Tags = [tag.toString() for tag in tags] - if update: - self.execute(obj) - - def getTags(self, obj): - if not hasattr(self, 'tags'): - self.execute(obj) - return self.tags - - def setup(self, obj): - if True or not hasattr(self, "pathData") or not self.pathData: - try: - pathData = PathData(obj) - except ValueError: - FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) - return None + def setup(self, obj, generate=None): + print("setup") + self.obj = obj + try: + pathData = PathData(obj) + except ValueError: + FreeCAD.Console.PrintError(translate("PathDressup_HoldingTags", "Cannot insert holding tags for this path - please select a Profile path\n")) + return None + self.toolRadius = 5 + toolLoad = PathUtils.getLastToolLoad(obj) + if toolLoad is None or toolLoad.ToolNumber == 0: self.toolRadius = 5 - toolLoad = PathUtils.getLastToolLoad(obj) - if toolLoad is None or toolLoad.ToolNumber == 0: + else: + tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + if not tool or tool.Diameter == 0: self.toolRadius = 5 else: - tool = PathUtils.getTool(obj, toolLoad.ToolNumber) - if not tool or tool.Diameter == 0: - self.toolRadius = 5 - else: - self.toolRadius = tool.Diameter / 2 - self.pathData = pathData + self.toolRadius = tool.Diameter / 2 + self.pathData = pathData + if generate: + obj.Height = self.pathData.defaultTagHeight() + obj.Width = self.pathData.defaultTagWidth() + obj.Angle = self.pathData.defaultTagAngle() + self.generateTags(obj, generate) return self.pathData - def getHeight(self, obj): - return self.pathData.tagHeight() - - def getWidth(self, obj): - return self.pathData.tagWidth() - - def getAngle(self, obj): - return self.pathData.tagAngle() - - def getPathLength(self, obj): - return self.pathData.pathLength() + def setXyEnabled(self, triples): + positions = [] + disabled = [] + for i, (x, y, enabled) in enumerate(triples): + positions.append(FreeCAD.Vector(x, y, 0)) + if not enabled: + disabled.append(i) + self.obj.Positions = positions + self.obj.Disabled = disabled + self.execute(self.obj) class TaskPanel: DataX = QtCore.Qt.ItemDataRole.UserRole DataY = QtCore.Qt.ItemDataRole.UserRole + 1 - def __init__(self, obj): + def __init__(self, obj, jvoVisibility=None): self.obj = obj + self.obj.Proxy.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/panels/HoldingTagsEdit.ui") - FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) self.jvo = PathUtils.findParentJob(obj).ViewObject - self.jvoVisible = self.jvo.isVisible() - if self.jvoVisible: - self.jvo.hide() + if jvoVisibility is None: + FreeCAD.ActiveDocument.openTransaction(translate("PathDressup_HoldingTags", "Edit HoldingTags Dress-up")) + self.jvoVisible = self.jvo.isVisible() + if self.jvoVisible: + self.jvo.hide() + else: + self.jvoVisible = jvoVisibility def reject(self): print("reject") @@ -782,51 +773,64 @@ class TaskPanel: print("accept") self.getFields() FreeCAD.ActiveDocument.commitTransaction() - FreeCADGui.ActiveDocument.resetEdit() self.cleanup() FreeCAD.ActiveDocument.recompute() def cleanup(self): + FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() FreeCADGui.Selection.removeObserver(self.s) if self.jvoVisible: self.jvo.show() + def closeDialog(self): + print("closed") + def open(self): self.s = SelObserver() # install the function mode resident FreeCADGui.Selection.addObserver(self.s) - def getFields(self): - width = self.form.dsbWidth.value() - height = self.form.dsbHeight.value() - angle = self.form.dsbAngle.value() + def getTags(self, includeCurrent): tags = [] + index = self.form.lwTags.currentRow() for i in range(0, self.form.lwTags.count()): item = self.form.lwTags.item(i) enabled = item.checkState() == QtCore.Qt.CheckState.Checked x = item.data(self.DataX) y = item.data(self.DataY) - tags.append(Tag(x, y, width, height, angle, enabled)) - self.obj.Proxy.setTags(self.obj, tags) + print("(%.2f, %.2f) i=%d/%s" % (x, y, i, index)) + if includeCurrent or i != index: + tags.append((x, y, enabled)) + return tags + + def getTagParameters(self): + self.obj.Width = self.form.dsbWidth.value() + self.obj.Height = self.form.dsbHeight.value() + self.obj.Angle = self.form.dsbAngle.value() + + def getFields(self): + self.getTagParameters() + tags = self.getTags(True) + self.obj.Proxy.setXyEnabled(tags) def updateTagsView(self): - self.tags = self.obj.Proxy.getTags(self.obj) - print("updateTagsView: %d" % (len(self.tags))) + print("updateTagsView") self.form.lwTags.blockSignals(True) self.form.lwTags.clear() - for tag in self.tags: - lbl = "(%.2f, %.2f)" % (tag.x, tag.y) + for i, pos in enumerate(self.obj.Positions): + lbl = "%d: (%.2f, %.2f)" % (i, pos.x, pos.y) item = QtGui.QListWidgetItem(lbl) - item.setData(self.DataX, tag.x) - item.setData(self.DataY, tag.y) - if tag.enabled: - item.setCheckState(QtCore.Qt.CheckState.Checked) - else: + item.setData(self.DataX, pos.x) + item.setData(self.DataY, pos.y) + if i in self.obj.Disabled: item.setCheckState(QtCore.Qt.CheckState.Unchecked) + else: + item.setCheckState(QtCore.Qt.CheckState.Checked) flags = QtCore.Qt.ItemFlag.ItemIsSelectable - flags |= QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsUserCheckable + flags |= QtCore.Qt.ItemFlag.ItemIsEnabled + flags |= QtCore.Qt.ItemFlag.ItemIsUserCheckable item.setFlags(flags) self.form.lwTags.addItem(item) self.form.lwTags.blockSignals(False) @@ -843,22 +847,13 @@ class TaskPanel: self.cleanupUI() count = self.form.sbCount.value() - width = self.form.dsbWidth.value() - height = self.form.dsbHeight.value() - angle = self.form.dsbAngle.value() + self.obj.Proxy.generateTags(self.obj, count) - tags = self.obj.Proxy.generateTags(self.obj, count, width, height, angle) - - self.obj.Proxy.setTags(self.obj, tags) self.updateTagsView() #if debugDressup: # # this causes a big of an echo and a double click on the spin buttons, don't know why though # FreeCAD.ActiveDocument.recompute() -# def autoApply(self): -# print("autoApply") -# if self.form.cbAutoApply.checkState() == QtCore.Qt.CheckState.Checked: -# self.whenApplyClicked() def updateModel(self): self.getFields() @@ -876,19 +871,23 @@ class TaskPanel: self.form.pbDelete.setEnabled(not item is None) def deleteSelectedTag(self): - item = self.form.lwTags.currentItem() - if item: - x = item.data(self.DataX) - y = item.data(self.DataY) - tags = filter(lambda t: t.x != x or t.y != y, self.tags) - self.obj.Proxy.setTags(self.obj, tags) - self.updateTagsView() + self.obj.Proxy.setXyEnabled(self.getTags(False)) + self.updateTagsView() def addNewTagAt(self, point, what): - print("%s '%s'" %( point, what.Name)) + if what == self.obj: + print("%s '%s'" %( point, what.Name)) + tags = self.tags + tags.append((point.x, point.y, True)) + self.obj.Proxy.setXyEnabled(tags) + panel = TaskPanel(self.obj, self.jvoVisible) + todo.delay(FreeCADGui.Control.closeDialog, None) + todo.delay(FreeCADGui.Control.showDialog, panel) + todo.delay(panel.setupUi, None) def addNewTag(self): - FreeCADGui.Snapper.getPoint(callback=self.addNewTagAt) + self.tags = self.getTags(True) + FreeCADGui.Snapper.getPoint(callback=self.addNewTagAt, extradlg=[self]) def setupSpinBox(self, widget, val, decimals = 2): widget.setMinimum(0) @@ -898,10 +897,10 @@ class TaskPanel: def setFields(self): self.updateTagsView() - self.setupSpinBox(self.form.sbCount, len(self.tags), None) - self.setupSpinBox(self.form.dsbHeight, self.obj.Proxy.getHeight(self.obj)) - self.setupSpinBox(self.form.dsbWidth, self.obj.Proxy.getWidth(self.obj)) - self.setupSpinBox(self.form.dsbAngle, self.obj.Proxy.getAngle(self.obj), 0) + self.setupSpinBox(self.form.sbCount, len(self.obj.Positions), None) + self.setupSpinBox(self.form.dsbHeight, self.obj.Height) + self.setupSpinBox(self.form.dsbWidth, self.obj.Width) + self.setupSpinBox(self.form.dsbAngle, self.obj.Angle, 0) self.form.dsbAngle.setMaximum(90) self.form.dsbAngle.setSingleStep(5.) @@ -947,7 +946,7 @@ class SelObserver: PST.clear() def addSelection(self, doc, obj, sub, pnt): - FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') + #FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') FreeCADGui.updateGui() class ViewProviderDressup: @@ -1029,7 +1028,7 @@ class CommandPathDressupHoldingTags: FreeCADGui.doCommand('PathScripts.PathDressupHoldingTags.ViewProviderDressup(obj.ViewObject)') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') - FreeCADGui.doCommand('dbo.setup(obj)') + FreeCADGui.doCommand('dbo.setup(obj, 4.)') FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() From 556e25e47f2d300ff31135b445491a2eb1dc05ab Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 1 Jan 2017 19:32:37 -0800 Subject: [PATCH 040/144] Fixed another initialisation issue - depending on how the dressup is created. --- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index 66ff437855..f4f3276f16 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -586,9 +586,11 @@ class ObjectDressup: self.tags = self.pathData.generateTags(obj, count, obj.Width, obj.Height, obj.Angle, None) obj.Positions = [tag.originAt(0) for tag in self.tags] obj.Disabled = [] + return False else: self.setup(obj, count) self.execute(obj) + return True def isValidTagStartIntersection(self, edge, i): if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): @@ -847,7 +849,8 @@ class TaskPanel: self.cleanupUI() count = self.form.sbCount.value() - self.obj.Proxy.generateTags(self.obj, count) + if not self.obj.Proxy.generateTags(self.obj, count): + self.obj.Proxy.execute(self.obj) self.updateTagsView() #if debugDressup: From e92f3153796d58890f1aefd7159ee6a94cfa7c3d Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Mon, 2 Jan 2017 17:32:48 -0800 Subject: [PATCH 041/144] Changed default values for tags and disabled old popup menu. --- src/Mod/Path/InitGui.py | 2 +- src/Mod/Path/PathScripts/PathDressupHoldingTags.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index e046eb5421..b5f020413a 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -136,7 +136,7 @@ class PathWorkbench (Workbench): if FreeCADGui.Selection.getSelection()[0].isDerivedFrom("Path::Feature"): self.appendContextMenu("", ["Path_Inspect"]) if "Profile" or "Contour" in FreeCADGui.Selection.getSelection()[0].Name: - self.appendContextMenu("", ["Add_Tag"]) + #self.appendContextMenu("", ["Add_Tag"]) self.appendContextMenu("", ["Set_StartPoint"]) self.appendContextMenu("", ["Set_EndPoint"]) if "Remote" in FreeCADGui.Selection.getSelection()[0].Name: diff --git a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py index f4f3276f16..d052928062 100644 --- a/src/Mod/Path/PathScripts/PathDressupHoldingTags.py +++ b/src/Mod/Path/PathScripts/PathDressupHoldingTags.py @@ -542,14 +542,14 @@ class PathData: def defaultTagHeight(self): if hasattr(self.obj, 'Base') and hasattr(self.obj.Base, 'StartDepth') and hasattr(self.obj.Base, 'FinalDepth'): - return (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value - return self.maxZ - self.minZ + return (self.obj.Base.StartDepth - self.obj.Base.FinalDepth).Value / 2 + return (self.maxZ - self.minZ) / 2 def defaultTagWidth(self): return self.shortestAndLongestPathEdge()[1].Length / 10 def defaultTagAngle(self): - return 90 + return 45 def sortedTags(self, tags): ordered = [] From e381b2332a6a5244fe82ffcdd5bb4d9ca1c227c1 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 3 Jan 2017 16:59:16 +0800 Subject: [PATCH 042/144] Added Part.sortEdges Unlike Part.__sortEdges__ which only return a list of connected edges, and discard the rest. Part.sortEdges return a list of list of connected edges, which includes all input edges. --- src/Mod/Part/App/AppPartPy.cpp | 51 ++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 6928a3d0c2..1a5b24493d 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -143,20 +143,22 @@ extern const char* BRepBuilderAPI_FaceErrorText(BRepBuilderAPI_FaceError fe); namespace Part { struct EdgePoints { gp_Pnt v1, v2; + std::list::iterator it; TopoDS_Edge edge; }; -static std::list sort_Edges(double tol3d, const std::vector& edges) +static std::list sort_Edges(double tol3d, std::list& edges) { tol3d = tol3d * tol3d; std::list edge_points; TopExp_Explorer xp; - for (std::vector::const_iterator it = edges.begin(); it != edges.end(); ++it) { + for (std::list::iterator it = edges.begin(); it != edges.end(); ++it) { EdgePoints ep; xp.Init(*it,TopAbs_VERTEX); ep.v1 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current())); xp.Next(); ep.v2 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current())); + ep.it = it; ep.edge = *it; edge_points.push_back(ep); } @@ -170,6 +172,7 @@ static std::list sort_Edges(double tol3d, const std::vector sort_Edges(double tol3d, const std::vectorv1.SquareDistance(last) <= tol3d) { last = pEI->v2; sorted.push_back(pEI->edge); + edges.erase(pEI->it); edge_points.erase(pEI); pEI = edge_points.begin(); break; @@ -186,6 +190,7 @@ static std::list sort_Edges(double tol3d, const std::vectorv2.SquareDistance(first) <= tol3d) { first = pEI->v1; sorted.push_front(pEI->edge); + edges.erase(pEI->it); edge_points.erase(pEI); pEI = edge_points.begin(); break; @@ -198,6 +203,7 @@ static std::list sort_Edges(double tol3d, const std::vectorReversedParameter(last); TopoDS_Edge edgeReversed = BRepBuilderAPI_MakeEdge(curve->Reversed(), last, first); sorted.push_back(edgeReversed); + edges.erase(pEI->it); edge_points.erase(pEI); pEI = edge_points.begin(); break; @@ -210,6 +216,7 @@ static std::list sort_Edges(double tol3d, const std::vectorReversedParameter(last); TopoDS_Edge edgeReversed = BRepBuilderAPI_MakeEdge(curve->Reversed(), last, first); sorted.push_front(edgeReversed); + edges.erase(pEI->it); edge_points.erase(pEI); pEI = edge_points.begin(); break; @@ -357,6 +364,9 @@ public: "__sortEdges__(list of edges) -- Helper method to sort an unsorted list of edges so that afterwards\n" "two adjacent edges share a common vertex" ); + add_varargs_method("sortEdges",&Module::sortEdges2, + "sortEdges(list of edges) -- Helper method to sort a list of edges into a list of list of connected edges" + ); add_varargs_method("__toPythonOCC__",&Module::toPythonOCC, "__toPythonOCC__(shape) -- Helper method to convert an internal shape to pythonocc shape" ); @@ -1742,7 +1752,7 @@ private: } Py::Sequence list(obj); - std::vector edges; + std::list edges; for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { PyObject* item = (*it).ptr(); if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { @@ -1766,6 +1776,41 @@ private: return sorted_list; } + Py::Object sortEdges2(const Py::Tuple& args) + { + PyObject *obj; + if (!PyArg_ParseTuple(args.ptr(), "O", &obj)) { + throw Py::Exception(PartExceptionOCCError, "list of edges expected"); + } + + Py::Sequence list(obj); + std::list edges; + for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + const TopoDS_Shape& sh = static_cast(item)->getTopoShapePtr()->getShape(); + if (sh.ShapeType() == TopAbs_EDGE) + edges.push_back(TopoDS::Edge(sh)); + else { + throw Py::TypeError("shape is not an edge"); + } + } + else { + throw Py::TypeError("item is not a shape"); + } + } + + Py::List root_list; + while(edges.size()) { + std::list sorted = sort_Edges(Precision::Confusion(), edges); + Py::List sorted_list; + for (std::list::iterator it = sorted.begin(); it != sorted.end(); ++it) { + sorted_list.append(Py::Object(new TopoShapeEdgePy(new TopoShape(*it)),true)); + } + root_list.append(sorted_list); + } + return root_list; + } Py::Object toPythonOCC(const Py::Tuple& args) { PyObject *pcObj; From d585499825abf51441ea717502b0fbde82a5b22e Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 3 Jan 2017 17:05:28 +0800 Subject: [PATCH 043/144] Make DraftGeomUtils.findWires use Part.sortEdges Renamed the original DraftGeomUtils.findWires() to findWiresOld2(). The original findWires() has a bug which may cause missing edge(s). Besides, using C++ implementation of Part.sortEdges will have better performance. --- src/Mod/Draft/DraftGeomUtils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index d6b04f4c14..4efa2154b2 100755 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -870,8 +870,10 @@ def flattenWire(wire): w = Part.makePolygon(verts) return w - def findWires(edgeslist): + return [ Part.Wire(e) for e in Part.sortEdges(edgeslist)] + +def findWiresOld2(edgeslist): '''finds connected wires in the given list of edges''' def touches(e1,e2): From 0ad93186b59c1e695ae709665876ac50c5fbe66e Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 3 Jan 2017 17:02:50 +0100 Subject: [PATCH 044/144] compute signed distances of points to shape, show results of visual inspection for point clouds --- src/Mod/Inspection/App/InspectionFeature.cpp | 57 ++++++++++++- src/Mod/Inspection/App/InspectionFeature.h | 1 + .../Inspection/Gui/ViewProviderInspection.cpp | 82 +++++++++++-------- 3 files changed, 104 insertions(+), 36 deletions(-) diff --git a/src/Mod/Inspection/App/InspectionFeature.cpp b/src/Mod/Inspection/App/InspectionFeature.cpp index 4aac4d76eb..18e789f1f3 100644 --- a/src/Mod/Inspection/App/InspectionFeature.cpp +++ b/src/Mod/Inspection/App/InspectionFeature.cpp @@ -25,6 +25,9 @@ #include #include #include +#include +#include +#include #include #include @@ -419,10 +422,24 @@ float InspectNominalPoints::getDistance(const Base::Vector3f& point) // ---------------------------------------------------------------- -InspectNominalShape::InspectNominalShape(const TopoDS_Shape& shape, float /*radius*/) : _rShape(shape) +InspectNominalShape::InspectNominalShape(const TopoDS_Shape& shape, float /*radius*/) + : _rShape(shape) + , isSolid(false) { distss = new BRepExtrema_DistShapeShape(); distss->LoadS1(_rShape); + + // When having a solid then use its shell because otherwise the distance + // for inner points will always be zero + if (!_rShape.IsNull() && _rShape.ShapeType() == TopAbs_SOLID) { + TopExp_Explorer xp; + xp.Init(_rShape, TopAbs_SHELL); + if (xp.More()) { + distss->LoadS1(xp.Current()); + isSolid = true; + } + + } //distss->SetDeflection(radius); } @@ -433,11 +450,45 @@ InspectNominalShape::~InspectNominalShape() float InspectNominalShape::getDistance(const Base::Vector3f& point) { - BRepBuilderAPI_MakeVertex mkVert(gp_Pnt(point.x,point.y,point.z)); + gp_Pnt pnt3d(point.x,point.y,point.z); + BRepBuilderAPI_MakeVertex mkVert(pnt3d); distss->LoadS2(mkVert.Vertex()); + float fMinDist=FLT_MAX; - if (distss->Perform() && distss->NbSolution() > 0) + if (distss->Perform() && distss->NbSolution() > 0) { fMinDist = (float)distss->Value(); + // the shape is a solid, check if the vertex is inside + if (isSolid) { + const Standard_Real tol = 0.001; + BRepClass3d_SolidClassifier classifier(_rShape); + classifier.Perform(pnt3d, tol); + if (classifier.State() == TopAbs_IN) { + fMinDist = -fMinDist; + } + + } + else if (fMinDist > 0) { + // check if the distance was compued from a face + for (Standard_Integer index = 1; index <= distss->NbSolution(); index++) { + if (distss->SupportTypeShape1(index) == BRepExtrema_IsInFace) { + TopoDS_Shape face = distss->SupportOnShape1(index); + Standard_Real u, v; + distss->ParOnFaceS1(index, u, v); + //gp_Pnt pnt = distss->PointOnShape1(index); + BRepGProp_Face props(TopoDS::Face(face)); + gp_Vec normal; + gp_Pnt center; + props.Normal(u, v, center, normal); + gp_Vec dir(center, pnt3d); + Standard_Real scalar = normal.Dot(dir); + if (scalar < 0) { + fMinDist = -fMinDist; + } + break; + } + } + } + } return fMinDist; } diff --git a/src/Mod/Inspection/App/InspectionFeature.h b/src/Mod/Inspection/App/InspectionFeature.h index d859b7b809..f2fd7761c2 100644 --- a/src/Mod/Inspection/App/InspectionFeature.h +++ b/src/Mod/Inspection/App/InspectionFeature.h @@ -151,6 +151,7 @@ public: private: BRepExtrema_DistShapeShape* distss; const TopoDS_Shape& _rShape; + bool isSolid; }; class InspectionExport PropertyDistanceList: public App::PropertyLists diff --git a/src/Mod/Inspection/Gui/ViewProviderInspection.cpp b/src/Mod/Inspection/Gui/ViewProviderInspection.cpp index f8a644e34b..9a6d0dd943 100644 --- a/src/Mod/Inspection/Gui/ViewProviderInspection.cpp +++ b/src/Mod/Inspection/Gui/ViewProviderInspection.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -191,68 +192,83 @@ void ViewProviderInspection::updateData(const App::Property* prop) Base::Type pointId = Base::Type::fromName("Points::Feature"); Base::Type propId = App::PropertyComplexGeoData::getClassTypeId(); + std::vector points; + std::vector normals; + std::vector faces; + // set the Distance property to the correct size to sync size of material node with number // of vertices/points of the referenced geometry - const Data::ComplexGeoData* data = 0; if (object->getTypeId().isDerivedFrom(meshId)) { App::Property* prop = object->getPropertyByName("Mesh"); if (prop && prop->getTypeId().isDerivedFrom(propId)) { - data = static_cast(prop)->getComplexData(); + const Data::ComplexGeoData* data = static_cast(prop)->getComplexData(); + data->getFaces(points, faces, accuracy); } } else if (object->getTypeId().isDerivedFrom(shapeId)) { App::Property* prop = object->getPropertyByName("Shape"); if (prop && prop->getTypeId().isDerivedFrom(propId)) { - data = static_cast(prop)->getComplexData(); + const Data::ComplexGeoData* data = static_cast(prop)->getComplexData(); ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/Mod/Part"); float deviation = hGrp->GetFloat("MeshDeviation",0.2); Base::BoundBox3d bbox = data->getBoundBox(); accuracy = (float)((bbox.LengthX() + bbox.LengthY() + bbox.LengthZ())/300.0 * deviation); + data->getFaces(points, faces, accuracy); } } else if (object->getTypeId().isDerivedFrom(pointId)) { App::Property* prop = object->getPropertyByName("Points"); if (prop && prop->getTypeId().isDerivedFrom(propId)) { - data = static_cast(prop)->getComplexData(); + const Data::ComplexGeoData* data = static_cast(prop)->getComplexData(); + data->getPoints(points, normals, accuracy); } } - if (data) { - this->pcLinkRoot->removeAllChildren(); - std::vector points; - std::vector faces; - data->getFaces(points, faces, accuracy); + this->pcLinkRoot->removeAllChildren(); + this->pcLinkRoot->addChild(this->pcCoords); + this->pcCoords->point.setNum(points.size()); + SbVec3f* pts = this->pcCoords->point.startEditing(); + for (size_t i=0; i < points.size(); i++) { + const Base::Vector3d& p = points[i]; + pts[i].setValue((float)p.x,(float)p.y,(float)p.z); + } + this->pcCoords->point.finishEditing(); - this->pcLinkRoot->addChild(this->pcCoords); - this->pcCoords->point.setNum(points.size()); - SbVec3f* pts = this->pcCoords->point.startEditing(); - for (size_t i=0; i < points.size(); i++) { - const Base::Vector3d& p = points[i]; - pts[i].setValue((float)p.x,(float)p.y,(float)p.z); + if (!faces.empty()) { + SoIndexedFaceSet* face = new SoIndexedFaceSet(); + this->pcLinkRoot->addChild(face); + face->coordIndex.setNum(4*faces.size()); + int32_t* indices = face->coordIndex.startEditing(); + unsigned long j=0; + std::vector::iterator it; + for (it = faces.begin(); it != faces.end(); ++it,j++) { + indices[4*j+0] = it->I1; + indices[4*j+1] = it->I2; + indices[4*j+2] = it->I3; + indices[4*j+3] = SO_END_FACE_INDEX; } - this->pcCoords->point.finishEditing(); + face->coordIndex.finishEditing(); + } + else { + if (!normals.empty() && normals.size() == points.size()) { + SoNormal* normalNode = new SoNormal(); + normalNode->vector.setNum(normals.size()); + SbVec3f* norm = normalNode->vector.startEditing(); - if (!faces.empty()) { - SoIndexedFaceSet* face = new SoIndexedFaceSet(); - this->pcLinkRoot->addChild(face); - face->coordIndex.setNum(4*faces.size()); - int32_t* indices = face->coordIndex.startEditing(); - unsigned long j=0; - std::vector::iterator it; - for (it = faces.begin(); it != faces.end(); ++it,j++) { - indices[4*j+0] = it->I1; - indices[4*j+1] = it->I2; - indices[4*j+2] = it->I3; - indices[4*j+3] = SO_END_FACE_INDEX; + std::size_t i=0; + for (std::vector::const_iterator it = normals.begin(); it != normals.end(); ++it) { + norm[i++].setValue(static_cast(it->x), + static_cast(it->y), + static_cast(it->z)); } - face->coordIndex.finishEditing(); - } - else { - this->pcLinkRoot->addChild(this->pcPointStyle); - this->pcLinkRoot->addChild(new SoPointSet()); + + normalNode->vector.finishEditing(); + this->pcLinkRoot->addChild(normalNode); } + this->pcLinkRoot->addChild(this->pcPointStyle); + this->pcLinkRoot->addChild(new SoPointSet()); } } } From 608f7371700b2dff74279ca53239d27f61fdb699 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 3 Jan 2017 19:58:26 +0100 Subject: [PATCH 045/144] use normals if available --- src/Mod/Inspection/Gui/ViewProviderInspection.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Mod/Inspection/Gui/ViewProviderInspection.cpp b/src/Mod/Inspection/Gui/ViewProviderInspection.cpp index 9a6d0dd943..42f9c80034 100644 --- a/src/Mod/Inspection/Gui/ViewProviderInspection.cpp +++ b/src/Mod/Inspection/Gui/ViewProviderInspection.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include "ViewProviderInspection.h" @@ -193,7 +194,7 @@ void ViewProviderInspection::updateData(const App::Property* prop) Base::Type propId = App::PropertyComplexGeoData::getClassTypeId(); std::vector points; - std::vector normals; + std::vector normals; std::vector faces; // set the Distance property to the correct size to sync size of material node with number @@ -222,8 +223,13 @@ void ViewProviderInspection::updateData(const App::Property* prop) App::Property* prop = object->getPropertyByName("Points"); if (prop && prop->getTypeId().isDerivedFrom(propId)) { const Data::ComplexGeoData* data = static_cast(prop)->getComplexData(); + std::vector normals; data->getPoints(points, normals, accuracy); } + App::Property* propN = object->getPropertyByName("Normal"); + if (propN && propN->getTypeId().isDerivedFrom(Points::PropertyNormalList::getClassTypeId())) { + normals = static_cast(propN)->getValues(); + } } this->pcLinkRoot->removeAllChildren(); @@ -258,10 +264,8 @@ void ViewProviderInspection::updateData(const App::Property* prop) SbVec3f* norm = normalNode->vector.startEditing(); std::size_t i=0; - for (std::vector::const_iterator it = normals.begin(); it != normals.end(); ++it) { - norm[i++].setValue(static_cast(it->x), - static_cast(it->y), - static_cast(it->z)); + for (std::vector::const_iterator it = normals.begin(); it != normals.end(); ++it) { + norm[i++].setValue(it->x, it->y, it->z); } normalNode->vector.finishEditing(); From 0d4b185b80d0818adff88d568df5a5147af5e23f Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 3 Jan 2017 21:07:46 -0200 Subject: [PATCH 046/144] Arch: Fixed small bug in components --- src/Mod/Arch/ArchComponent.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index 96f1e85d44..902003dcb8 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -387,9 +387,11 @@ class Component: "returns (shape,extrusion vector,placement) or None" if hasattr(obj,"CloneOf"): if obj.CloneOf: - data = obj.CloneOf.Proxy.getExtrusionData(obj.CloneOf) - if data: - return data + if hasattr(obj.CloneOf,"Proxy"): + if hasattr(obj.CloneOf.Proxy,"getExtrusionData"): + data = obj.CloneOf.Proxy.getExtrusionData(obj.CloneOf) + if data: + return data if obj.Base: if obj.Base.isDerivedFrom("Part::Extrusion"): if obj.Base.Base: From f6f8363d8a2d148d7f2c405b3db70f47fca6039a Mon Sep 17 00:00:00 2001 From: HokieEngr Date: Tue, 3 Jan 2017 22:34:47 -0500 Subject: [PATCH 047/144] Updated viewport dimensions to reflect users unit schema The previous code assumed the user's unit system was MKS. The updated code uses schemaTranslate() to convert from screen units to the user's chosen system. --- src/Gui/View3DInventorViewer.cpp | 40 ++++++++++---------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index ba337b8872..3152015f76 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -92,6 +92,7 @@ #include #include #include +#include #include "View3DInventorViewer.h" #include "ViewProviderDocumentObject.h" @@ -1545,35 +1546,18 @@ void View3DInventorViewer::printDimension() else if (dimX < dimY) fHeight *= ((float)dimY)/((float)dimX); - float fLog = float(log10(fWidth)), fFac; - int nExp = int(fLog); - QString unit; + // Translate screen units into user's unit schema + Base::Quantity qWidth(Base::Quantity::MilliMetre); + Base::Quantity qHeight(Base::Quantity::MilliMetre); + qWidth.setValue(fWidth); + qHeight.setValue(fHeight); + QString wStr = Base::UnitsApi::schemaTranslate(qWidth); + QString hStr = Base::UnitsApi::schemaTranslate(qHeight); - if (nExp >= 6) { - fFac = 1.0e+6f; - unit = QLatin1String("km"); - } - else if (nExp >= 3) { - fFac = 1.0e+3f; - unit = QLatin1String("m"); - } - else if ((nExp >= 0) && (fLog > 0.0f)) { - fFac = 1.0e+0f; - unit = QLatin1String("mm"); - } - else if (nExp >= -3) { - fFac = 1.0e-3f; - unit = QLatin1String("um"); - } - else { - fFac = 1.0e-6f; - unit = QLatin1String("nm"); - } - - QString dim = QString::fromLatin1("%1 x %2 %3") - .arg(fWidth / fFac,0,'f',2) - .arg(fHeight / fFac,0,'f',2) - .arg(unit); + // Create final string and update window + QString dim = QString::fromLatin1("%1 x %2") + .arg(wStr) + .arg(hStr); getMainWindow()->setPaneText(2, dim); } else From ecd1f465b0d587c3365cb96cb5ed6624e68d84e8 Mon Sep 17 00:00:00 2001 From: AjinkyaDahale Date: Sun, 18 Dec 2016 03:31:35 +0530 Subject: [PATCH 048/144] Added DrawSketchHandlerLock --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 92 ++++++++++++++++++++- src/Mod/Sketcher/Gui/CommandCreateGeo.cpp | 1 + 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 84165edd4d..acbd0cb4d8 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -44,6 +44,7 @@ #include #include "ViewProviderSketch.h" +#include "DrawSketchHandler.h" #include "ui_InsertDatum.h" #include "EditDatumDialog.h" #include "CommandConstraints.h" @@ -63,6 +64,8 @@ namespace SketcherGui ConstraintCreationMode constraintCreationMode=Driving; +void ActivateHandler(Gui::Document *doc,DrawSketchHandler *handler); + bool isCreateConstraintActive(Gui::Document *doc) { if (doc) { @@ -927,6 +930,91 @@ bool CmdSketcherConstrainVertical::isActive(void) } +// ====================================================================================== + +/* XPM */ +static const char *cursor_createlock[]={ +"32 32 3 1", +"+ c white", +"# c red", +". c None", +"......+.........................", +"......+.........................", +"......+.........................", +"......+.........................", +"......+.........................", +"................................", +"+++++...+++++...................", +"................................", +"......+.........................", +"......+.........................", +"......+.........................", +"......+.........................", +"......+..........###............", +"................######..........", +"...............##....##.........", +"..............##......##........", +"..............##......##........", +".............############.......", +".............############.......", +".............############.......", +".............############.......", +".............############.......", +".............############.......", +".............############.......", +".............############.......", +"................................", +"................................", +"................................", +"................................", +"................................", +"................................", +"................................"}; + +/** + * @brief The DrawSketchHandlerLock class + * + * Hacking on the lines of the functions in CommandCreateGeo.cpp to make lock + * constraints on the fly. + */ +class DrawSketchHandlerLock: public DrawSketchHandler +{ +public: + DrawSketchHandlerLock() : selectionDone(false) {} + virtual ~DrawSketchHandlerLock() {} + + virtual void activated(ViewProviderSketch *) + { + setCursor(QPixmap(cursor_createlock),7,7); + } + + virtual void mouseMove(Base::Vector2d onSketchPos) + { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr); + return; + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) + { + EditPoint = onSketchPos; + selectionDone = true; + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) { + return true; + } + +protected: + bool selectionDone; + Base::Vector2d EditPoint; + std::vector sugConstr; +}; + DEF_STD_CMD_AU(CmdSketcherConstrainLock); CmdSketcherConstrainLock::CmdSketcherConstrainLock() @@ -945,6 +1033,7 @@ CmdSketcherConstrainLock::CmdSketcherConstrainLock() void CmdSketcherConstrainLock::activated(int iMsg) { Q_UNUSED(iMsg); + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerLock()); // get the selection std::vector selection = getSelection().getSelectionEx(); @@ -986,7 +1075,8 @@ void CmdSketcherConstrainLock::activated(int iMsg) Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%f)) ", selection[0].getFeatName(),GeoId,PosId,pnt.y); - if (GeoId <= Sketcher::GeoEnum::RefExt || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving + if (GeoId <= Sketcher::GeoEnum::RefExt || constraintCreationMode==Reference) { + // it is a constraint on a external line, make it non-driving const std::vector &ConStr = Obj->Constraints.getValues(); Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index dbbdb61989..dd8c5bb324 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -4746,6 +4746,7 @@ public: } return true; } + protected: bool selectionDone; Base::Vector2d EditPoint; From 1230f493d5fd389e9763191ac7d51d4682995516 Mon Sep 17 00:00:00 2001 From: AjinkyaDahale Date: Tue, 20 Dec 2016 03:07:49 +0530 Subject: [PATCH 049/144] Lock constraint can be applied with better selection Can't yet start without selection: the button on toolbar is not active without a selection yet --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 98 +++++++++++++++++++-- src/Mod/Sketcher/Gui/CommandCreateGeo.cpp | 3 +- src/Mod/Sketcher/Gui/DrawSketchHandler.cpp | 11 +-- 3 files changed, 93 insertions(+), 19 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index acbd0cb4d8..f6bbd90d27 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -932,6 +933,29 @@ bool CmdSketcherConstrainVertical::isActive(void) // ====================================================================================== +namespace SketcherGui { + class LockSelection : public Gui::SelectionFilterGate + { + App::DocumentObject* object; + public: + LockSelection(App::DocumentObject* obj) + : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj) + {} + + bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName) + { + if (pObj != this->object) + return false; + if (!sSubName || sSubName[0] == '\0') + return false; + std::string element(sSubName); + if (element.substr(0,6) == "Vertex") + return true; + return false; + } + }; +} + /* XPM */ static const char *cursor_createlock[]={ "32 32 3 1", @@ -974,44 +998,102 @@ static const char *cursor_createlock[]={ /** * @brief The DrawSketchHandlerLock class * - * Hacking on the lines of the functions in CommandCreateGeo.cpp to make lock + * Hacking along the lines of the functions in CommandCreateGeo.cpp to make lock * constraints on the fly. */ class DrawSketchHandlerLock: public DrawSketchHandler { public: DrawSketchHandlerLock() : selectionDone(false) {} - virtual ~DrawSketchHandlerLock() {} + virtual ~DrawSketchHandlerLock() + { + Gui::Selection().rmvSelectionGate(); + } virtual void activated(ViewProviderSketch *) { + Gui::Selection().rmvSelectionGate(); + Gui::Selection().addSelectionGate(new LockSelection(sketchgui->getObject())); setCursor(QPixmap(cursor_createlock),7,7); } virtual void mouseMove(Base::Vector2d onSketchPos) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr); + // If preselection Point + //int preSelPnt = sketchgui->getPreselectPoint(); + if (sketchgui->getPreselectPoint() != -1) { + setPositionText(onSketchPos); return; } + resetPositionText(); applyCursor(); } virtual bool pressButton(Base::Vector2d onSketchPos) { - EditPoint = onSketchPos; - selectionDone = true; + Q_UNUSED(onSketchPos); + // Get Preselection Point + int preSelPnt = sketchgui->getPreselectPoint(); + if (preSelPnt != -1) { + pointGeoId = Constraint::GeoUndef; + pointPosId = Sketcher::none; + sketchgui->getSketchObject()->getGeoVertexIndex(preSelPnt, pointGeoId, pointPosId); + selectionDone = true; + return true; + } return true; } virtual bool releaseButton(Base::Vector2d onSketchPos) { + Q_UNUSED(onSketchPos); + if (selectionDone) { + unsetCursor(); + resetPositionText(); + + Sketcher::SketchObject* Obj = static_cast(sketchgui->getObject()); + + Base::Vector3d pnt = Obj->getPoint(pointGeoId,pointPosId); + + // undo command open + Gui::Command::openCommand("add fixed constraint"); + Gui::Command::doCommand( + Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%f)) ", + sketchgui->getObject()->getNameInDocument(),pointGeoId,pointPosId,pnt.x); + Gui::Command::doCommand( + Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%f)) ", + sketchgui->getObject()->getNameInDocument(),pointGeoId,pointPosId,pnt.y); + + if (pointGeoId <= Sketcher::GeoEnum::RefExt || constraintCreationMode==Reference) { + // it is a constraint on a external line, make it non-driving + const std::vector &ConStr = Obj->Constraints.getValues(); + + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", + sketchgui->getObject()->getNameInDocument(),ConStr.size()-2,"False"); + + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.setDriving(%i,%s)", + sketchgui->getObject()->getNameInDocument(),ConStr.size()-1,"False"); + } + + // finish the transaction and update + Gui::Command::commitCommand(); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + bool autoRecompute = hGrp->GetBool("AutoRecompute",false); + + if(autoRecompute) + Gui::Command::updateActive(); + + // clear the selection (convenience) + Gui::Selection().clearSelection(); + + } return true; } protected: bool selectionDone; - Base::Vector2d EditPoint; + int pointGeoId; + Sketcher::PointPos pointPosId; std::vector sugConstr; }; diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index dd8c5bb324..18cf11d541 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -4729,7 +4729,6 @@ public: else static_cast(sketchgui->getObject())->solve(); - //ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true); if(continuousMode){ @@ -4878,7 +4877,7 @@ namespace SketcherGui { return false; } }; -}; +} /* XPM */ diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp index 4bc10b60d3..fb4554e7ae 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp @@ -61,16 +61,9 @@ using namespace Sketcher; //************************************************************************** // Construction/Destruction -DrawSketchHandler::DrawSketchHandler() - : sketchgui(0) -{ +DrawSketchHandler::DrawSketchHandler() : sketchgui(0) {} -} - -DrawSketchHandler::~DrawSketchHandler() -{ - -} +DrawSketchHandler::~DrawSketchHandler() {} void DrawSketchHandler::quit(void) { From 670e59eae7abcaf66ee62b60954abaeec2a7bb0c Mon Sep 17 00:00:00 2001 From: AjinkyaDahale Date: Tue, 20 Dec 2016 11:37:11 +0530 Subject: [PATCH 050/144] Lock constraint in always available when sketch is open Earlier it was available only when a selection is made --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index f6bbd90d27..ccca1656e0 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -67,6 +67,8 @@ ConstraintCreationMode constraintCreationMode=Driving; void ActivateHandler(Gui::Document *doc,DrawSketchHandler *handler); +bool isCreateGeoActive(Gui::Document *doc); + bool isCreateConstraintActive(Gui::Document *doc) { if (doc) { @@ -1197,7 +1199,8 @@ void CmdSketcherConstrainLock::updateAction(int mode) bool CmdSketcherConstrainLock::isActive(void) { - return isCreateConstraintActive( getActiveGuiDocument() ); + // return isCreateConstraintActive( getActiveGuiDocument() ); + return isCreateGeoActive( getActiveGuiDocument() ); } From 75ad582913efe3ee5027cc85952f9d3e9b6bab86 Mon Sep 17 00:00:00 2001 From: AjinkyaDahale Date: Tue, 20 Dec 2016 11:41:48 +0530 Subject: [PATCH 051/144] Lock doesn't complain "no selection" --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index ccca1656e0..8d31b07903 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -1123,8 +1123,8 @@ void CmdSketcherConstrainLock::activated(int iMsg) // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select entities from the sketch.")); +// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), +// QObject::tr("Select entities from the sketch.")); return; } @@ -1135,6 +1135,8 @@ void CmdSketcherConstrainLock::activated(int iMsg) if (SubNames.size() != 1) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select exactly one entity from the sketch.")); + // clear the selection (convenience) + getSelection().clearSelection(); return; } @@ -1145,6 +1147,8 @@ void CmdSketcherConstrainLock::activated(int iMsg) if (isEdge(GeoId,PosId) || (GeoId < 0 && GeoId >= Sketcher::GeoEnum::VAxis)) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select one vertex from the sketch other than the origin.")); + // clear the selection (convenience) + getSelection().clearSelection(); return; } From 8e1f5364af0064df44e66f9b54876136129a3b02 Mon Sep 17 00:00:00 2001 From: AjinkyaDahale Date: Sun, 25 Dec 2016 13:02:32 +0530 Subject: [PATCH 052/144] Coincident constraint making mode added One small problem remains that the origin is not selectable as one point in coincident constraint making mode. --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 171 ++++++++++++++++++-- 1 file changed, 158 insertions(+), 13 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 8d31b07903..4e4d13d322 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -936,11 +936,11 @@ bool CmdSketcherConstrainVertical::isActive(void) // ====================================================================================== namespace SketcherGui { - class LockSelection : public Gui::SelectionFilterGate + class LockConstraintSelection : public Gui::SelectionFilterGate { App::DocumentObject* object; public: - LockSelection(App::DocumentObject* obj) + LockConstraintSelection(App::DocumentObject* obj) : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj) {} @@ -1015,7 +1015,7 @@ public: virtual void activated(ViewProviderSketch *) { Gui::Selection().rmvSelectionGate(); - Gui::Selection().addSelectionGate(new LockSelection(sketchgui->getObject())); + Gui::Selection().addSelectionGate(new LockConstraintSelection(sketchgui->getObject())); setCursor(QPixmap(cursor_createlock),7,7); } @@ -1023,12 +1023,12 @@ public: { // If preselection Point //int preSelPnt = sketchgui->getPreselectPoint(); - if (sketchgui->getPreselectPoint() != -1) { - setPositionText(onSketchPos); - return; - } - resetPositionText(); - applyCursor(); +// if (sketchgui->getPreselectPoint() != -1) { +// setPositionText(onSketchPos); +// return; +// } +// resetPositionText(); +// applyCursor(); } virtual bool pressButton(Base::Vector2d onSketchPos) @@ -1046,7 +1046,8 @@ public: return true; } - virtual bool releaseButton(Base::Vector2d onSketchPos) { + virtual bool releaseButton(Base::Vector2d onSketchPos) + { Q_UNUSED(onSketchPos); if (selectionDone) { unsetCursor(); @@ -1207,6 +1208,147 @@ bool CmdSketcherConstrainLock::isActive(void) return isCreateGeoActive( getActiveGuiDocument() ); } +// ====================================================================================== + +namespace SketcherGui { + class CoincidentConstraintSelection : public Gui::SelectionFilterGate + { + App::DocumentObject* object; + public: + CoincidentConstraintSelection(App::DocumentObject* obj) + : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj) + {} + + bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName) + { + if (pObj != this->object) + return false; + if (!sSubName || sSubName[0] == '\0') + return false; + std::string element(sSubName); + if (element.substr(0,6) == "Vertex" || element.substr(0,9) == "RootPoint") + return true; + return false; + } + }; +} + +/* XPM */ +static const char *cursor_createcoincident[]={ +"32 32 3 1", +"+ c white", +"# c red", +". c None", +"......+.........................", +"......+.........................", +"......+.........................", +"......+.........................", +"......+.........................", +"................................", +"+++++...+++++...................", +"................................", +"......+.........................", +"......+.........................", +"......+.........................", +"......+.........................", +"......+.........................", +"................................", +"................................", +"................................", +".................####...........", +"................######..........", +"...............########.........", +"...............########.........", +"...............########.........", +"...............########.........", +"................######..........", +".................####...........", +"................................", +"................................", +"................................", +"................................", +"................................", +"................................", +"................................", +"................................"}; + +class DrawSketchHandlerCoincident: public DrawSketchHandler +{ +public: + DrawSketchHandlerCoincident() + { + GeoId1 = GeoId2 = Constraint::GeoUndef; + PosId1 = PosId2 = Sketcher::none; + } + virtual ~DrawSketchHandlerCoincident() + { + Gui::Selection().rmvSelectionGate(); + } + + virtual void activated(ViewProviderSketch *) + { + Gui::Selection().rmvSelectionGate(); + Gui::Selection().addSelectionGate(new CoincidentConstraintSelection(sketchgui->getObject())); + setCursor(QPixmap(cursor_createcoincident), 7, 7); + } + + virtual void mouseMove(Base::Vector2d onSketchPos) {Q_UNUSED(onSketchPos);} + + virtual bool pressButton(Base::Vector2d onSketchPos) + { + Q_UNUSED(onSketchPos); + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) + { + Q_UNUSED(onSketchPos); + int VtId = sketchgui->getPreselectPoint(); + if (VtId != -1) { + if (GeoId1 == Constraint::GeoUndef) { + sketchgui->getSketchObject()->getGeoVertexIndex(VtId,GeoId1,PosId1); + std::stringstream ss; + ss << "Vertex" << VtId + 1; + Gui::Selection().addSelection(sketchgui->getSketchObject()->getDocument()->getName(), + sketchgui->getSketchObject()->getNameInDocument(), + ss.str().c_str(), + onSketchPos.x, + onSketchPos.y, + 0.f); + } + else { + sketchgui->getSketchObject()->getGeoVertexIndex(VtId,GeoId2,PosId2); + + // Apply the constraint + Sketcher::SketchObject* Obj = static_cast(sketchgui->getObject()); + + // undo command open + Gui::Command::openCommand("add coincident constraint"); + + // check if this coincidence is already enforced (even indirectly) + bool constraintExists = Obj->arePointsCoincident(GeoId1,PosId1,GeoId2,PosId2); + if (!constraintExists && (GeoId1 != GeoId2)) { + Gui::Command::doCommand( + Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d)) ", + sketchgui->getObject()->getNameInDocument(),GeoId1,PosId1,GeoId2,PosId2); + Gui::Command::commitCommand(); + } + else { + Gui::Command::abortCommand(); + } + } + } + else { + GeoId1 = GeoId2 = Constraint::GeoUndef; + PosId1 = PosId2 = Sketcher::none; + Gui::Selection().clearSelection(); + } + return true; + } +protected: + int GeoId1, GeoId2; + Sketcher::PointPos PosId1, PosId2; +}; DEF_STD_CMD_A(CmdSketcherConstrainCoincident); @@ -1227,13 +1369,15 @@ CmdSketcherConstrainCoincident::CmdSketcherConstrainCoincident() void CmdSketcherConstrainCoincident::activated(int iMsg) { Q_UNUSED(iMsg); + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerCoincident()); + // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select vertexes from the sketch.")); +// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), +// QObject::tr("Select vertexes from the sketch.")); return; } @@ -1297,7 +1441,8 @@ void CmdSketcherConstrainCoincident::activated(int iMsg) bool CmdSketcherConstrainCoincident::isActive(void) { - return isCreateConstraintActive( getActiveGuiDocument() ); + // return isCreateConstraintActive( getActiveGuiDocument() ); + return isCreateGeoActive( getActiveGuiDocument() ); } From 43ad429734ff44990ef05b4b2674a817348a21f0 Mon Sep 17 00:00:00 2001 From: triplus Date: Mon, 7 Nov 2016 16:35:14 +0100 Subject: [PATCH 053/144] Add Part BOA multiCut, multiCommon and multiSection methods --- src/Mod/Part/App/TopoShape.cpp | 102 ++++++++++++++++++++++++++++ src/Mod/Part/App/TopoShape.h | 3 + src/Mod/Part/App/TopoShapePy.xml | 53 ++++++++++++++- src/Mod/Part/App/TopoShapePyImp.cpp | 99 +++++++++++++++++++++++++++ 4 files changed, 256 insertions(+), 1 deletion(-) diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 29f761cdf3..884ec6de75 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -1306,6 +1306,74 @@ TopoDS_Shape TopoShape::fuse(TopoDS_Shape shape) const return mkFuse.Shape(); } +TopoDS_Shape TopoShape::multiCut(const std::vector& shapes, Standard_Real tolerance) const +{ + if (this->_Shape.IsNull()) + Standard_Failure::Raise("Base shape is null"); +#if OCC_VERSION_HEX < 0x060900 + (void)shapes; + (void)tolerance; + throw Base::AttributeError("multiCut is available only in OCC 6.9.0 and up."); +#else + BRepAlgoAPI_Cut mkCut; + mkCut.SetRunParallel(true); + TopTools_ListOfShape shapeArguments,shapeTools; + shapeArguments.Append(this->_Shape); + for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { + if (it->IsNull()) + throw Base::Exception("Tool shape is null"); + if (tolerance > 0.0) + // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 + shapeTools.Append(BRepBuilderAPI_Copy(*it).Shape()); + else + shapeTools.Append(*it); + } + mkCut.SetArguments(shapeArguments); + mkCut.SetTools(shapeTools); + if (tolerance > 0.0) + mkCut.SetFuzzyValue(tolerance); + mkCut.Build(); + if (!mkCut.IsDone()) + throw Base::Exception("MultiCut failed"); + TopoDS_Shape resShape = mkCut.Shape(); + return resShape; +#endif +} + +TopoDS_Shape TopoShape::multiCommon(const std::vector& shapes, Standard_Real tolerance) const +{ + if (this->_Shape.IsNull()) + Standard_Failure::Raise("Base shape is null"); +#if OCC_VERSION_HEX < 0x060900 + (void)shapes; + (void)tolerance; + throw Base::AttributeError("multiCommon is available only in OCC 6.9.0 and up."); +#else + BRepAlgoAPI_Common mkCommon; + mkCommon.SetRunParallel(true); + TopTools_ListOfShape shapeArguments,shapeTools; + shapeArguments.Append(this->_Shape); + for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { + if (it->IsNull()) + throw Base::Exception("Tool shape is null"); + if (tolerance > 0.0) + // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 + shapeTools.Append(BRepBuilderAPI_Copy(*it).Shape()); + else + shapeTools.Append(*it); + } + mkCommon.SetArguments(shapeArguments); + mkCommon.SetTools(shapeTools); + if (tolerance > 0.0) + mkCommon.SetFuzzyValue(tolerance); + mkCommon.Build(); + if (!mkCommon.IsDone()) + throw Base::Exception("MultiCommon failed"); + TopoDS_Shape resShape = mkCommon.Shape(); + return resShape; +#endif +} + TopoDS_Shape TopoShape::multiFuse(const std::vector& shapes, Standard_Real tolerance) const { if (this->_Shape.IsNull()) @@ -1354,6 +1422,40 @@ TopoDS_Shape TopoShape::multiFuse(const std::vector& shapes, Stand return resShape; } +TopoDS_Shape TopoShape::multiSection(const std::vector& shapes, Standard_Real tolerance) const +{ + if (this->_Shape.IsNull()) + Standard_Failure::Raise("Base shape is null"); +#if OCC_VERSION_HEX < 0x060900 + (void)shapes; + (void)tolerance; + throw Base::AttributeError("multiSection is available only in OCC 6.9.0 and up."); +#else + BRepAlgoAPI_Section mkSection; + mkSection.SetRunParallel(true); + TopTools_ListOfShape shapeArguments,shapeTools; + shapeArguments.Append(this->_Shape); + for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { + if (it->IsNull()) + throw Base::Exception("Tool shape is null"); + if (tolerance > 0.0) + // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 + shapeTools.Append(BRepBuilderAPI_Copy(*it).Shape()); + else + shapeTools.Append(*it); + } + mkSection.SetArguments(shapeArguments); + mkSection.SetTools(shapeTools); + if (tolerance > 0.0) + mkSection.SetFuzzyValue(tolerance); + mkSection.Build(); + if (!mkSection.IsDone()) + throw Base::Exception("MultiSection failed"); + TopoDS_Shape resShape = mkSection.Shape(); + return resShape; +#endif +} + TopoDS_Shape TopoShape::oldFuse(TopoDS_Shape shape) const { if (this->_Shape.IsNull()) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index d1ae2c882c..3dad2df8b7 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -156,7 +156,10 @@ public: TopoDS_Shape cut(TopoDS_Shape) const; TopoDS_Shape common(TopoDS_Shape) const; TopoDS_Shape fuse(TopoDS_Shape) const; + TopoDS_Shape multiCut(const std::vector&, Standard_Real tolerance = 0.0) const; + TopoDS_Shape multiCommon(const std::vector&, Standard_Real tolerance = 0.0) const; TopoDS_Shape multiFuse(const std::vector&, Standard_Real tolerance = 0.0) const; + TopoDS_Shape multiSection(const std::vector&, Standard_Real tolerance = 0.0) const; TopoDS_Shape oldFuse(TopoDS_Shape) const; TopoDS_Shape section(TopoDS_Shape) const; std::list slice(const Base::Vector3d&, double) const; diff --git a/src/Mod/Part/App/TopoShapePy.xml b/src/Mod/Part/App/TopoShapePy.xml index d2a0f23be0..c19f61d992 100644 --- a/src/Mod/Part/App/TopoShapePy.xml +++ b/src/Mod/Part/App/TopoShapePy.xml @@ -143,11 +143,60 @@ This is a more detailed check as done in isValid(). Union of this and a given topo shape. + + + multiCut((tool1,tool2,...),[tolerance=0.0]) -> Shape + +Substraction of this and a given list of topo shapes. + +Supports: +- Fuzzy Boolean operations (global tolerance for a Boolean operation) +- Support of multiple arguments for a single Boolean operation +- Parallelization of Boolean Operations algorithm + +OCC 6.9.0 or later is required. + + + + + multiCommon((tool1,tool2,...),[tolerance=0.0]) -> Shape + +Intersection of this and a given list of topo shapes. + +Supports: +- Fuzzy Boolean operations (global tolerance for a Boolean operation) +- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s2)) +- Parallelization of Boolean Operations algorithm + +OCC 6.9.0 or later is required. + + multiFuse((tool1,tool2,...),[tolerance=0.0]) -> Shape + Union of this and a given list of topo shapes. -Beginning from OCCT 6.8.1 a tolerance value can be specified + +Supports (OCCT 6.9.0 and above): +- Fuzzy Boolean operations (global tolerance for a Boolean operation) +- Support of multiple arguments for a single Boolean operation +- Parallelization of Boolean Operations algorithm + +Beginning from OCCT 6.8.1 a tolerance value can be specified. + + + + + multiSection((tool1,tool2,...),[tolerance=0.0]) -> Shape + +Section of this and a given list of topo shapes. + +Supports: +- Fuzzy Boolean operations (global tolerance for a Boolean operation) +- Support of multiple arguments for a single Boolean operation +- Parallelization of Boolean Operations algorithm + +OCC 6.9.0 or later is required. @@ -203,6 +252,8 @@ shape of this, and the rest are those that come from corresponding shapes in list_of_other_shapes. hint: use isSame method to test shape equality +Parallelization of Boolean Operations algorithm + OCC 6.9.0 or later is required. diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index 0414b4460f..232c306b01 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -779,6 +779,72 @@ PyObject* TopoShapePy::fuse(PyObject *args) } } +PyObject* TopoShapePy::multiCut(PyObject *args) +{ + double tolerance = 0.0; + PyObject *pcObj; + if (!PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) + return NULL; + std::vector shapeVec; + Py::Sequence shapeSeq(pcObj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + else { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + return 0; + } + } + try { + TopoDS_Shape multiCutShape = this->getTopoShapePtr()->multiCut(shapeVec,tolerance); + return new TopoShapePy(new TopoShape(multiCutShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } +} + +PyObject* TopoShapePy::multiCommon(PyObject *args) +{ + double tolerance = 0.0; + PyObject *pcObj; + if (!PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) + return NULL; + std::vector shapeVec; + Py::Sequence shapeSeq(pcObj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + else { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + return 0; + } + } + try { + TopoDS_Shape multiCommonShape = this->getTopoShapePtr()->multiCommon(shapeVec,tolerance); + return new TopoShapePy(new TopoShape(multiCommonShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } +} + PyObject* TopoShapePy::multiFuse(PyObject *args) { double tolerance = 0.0; @@ -812,6 +878,39 @@ PyObject* TopoShapePy::multiFuse(PyObject *args) } } +PyObject* TopoShapePy::multiSection(PyObject *args) +{ + double tolerance = 0.0; + PyObject *pcObj; + if (!PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) + return NULL; + std::vector shapeVec; + Py::Sequence shapeSeq(pcObj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + else { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + return 0; + } + } + try { + TopoDS_Shape multiSectionShape = this->getTopoShapePtr()->multiSection(shapeVec,tolerance); + return new TopoShapePy(new TopoShape(multiSectionShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } +} + PyObject* TopoShapePy::oldFuse(PyObject *args) { PyObject *pcObj; From dfbd6aa237cb4dc48bc7d8921be70ee6f3084c6e Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 4 Jan 2017 15:39:27 +0100 Subject: [PATCH 054/144] cleanup work: overload methods instead of using new method names --- src/Mod/Part/App/TopoShape.cpp | 219 +++++++-------- src/Mod/Part/App/TopoShape.h | 8 +- src/Mod/Part/App/TopoShapePy.xml | 89 ++++--- src/Mod/Part/App/TopoShapePyImp.cpp | 353 +++++++++++++------------ src/Mod/PartDesign/App/ShapeBinder.cpp | 2 +- 5 files changed, 357 insertions(+), 314 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 884ec6de75..32eb152d58 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -1286,6 +1286,42 @@ TopoDS_Shape TopoShape::cut(TopoDS_Shape shape) const return mkCut.Shape(); } +TopoDS_Shape TopoShape::cut(const std::vector& shapes, Standard_Real tolerance) const +{ + if (this->_Shape.IsNull()) + Standard_Failure::Raise("Base shape is null"); +#if OCC_VERSION_HEX < 0x060900 + (void)shapes; + (void)tolerance; + throw Base::RuntimeError("Multi cut is available only in OCC 6.9.0 and up."); +#else + BRepAlgoAPI_Cut mkCut; + mkCut.SetRunParallel(true); + TopTools_ListOfShape shapeArguments,shapeTools; + shapeArguments.Append(this->_Shape); + for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { + if (it->IsNull()) + throw Base::ValueError("Tool shape is null"); + if (tolerance > 0.0) + // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 + shapeTools.Append(BRepBuilderAPI_Copy(*it).Shape()); + else + shapeTools.Append(*it); + } + + mkCut.SetArguments(shapeArguments); + mkCut.SetTools(shapeTools); + if (tolerance > 0.0) + mkCut.SetFuzzyValue(tolerance); + mkCut.Build(); + if (!mkCut.IsDone()) + throw Base::RuntimeError("Multi cut failed"); + + TopoDS_Shape resShape = mkCut.Shape(); + return resShape; +#endif +} + TopoDS_Shape TopoShape::common(TopoDS_Shape shape) const { if (this->_Shape.IsNull()) @@ -1296,6 +1332,42 @@ TopoDS_Shape TopoShape::common(TopoDS_Shape shape) const return mkCommon.Shape(); } +TopoDS_Shape TopoShape::common(const std::vector& shapes, Standard_Real tolerance) const +{ + if (this->_Shape.IsNull()) + Standard_Failure::Raise("Base shape is null"); +#if OCC_VERSION_HEX < 0x060900 + (void)shapes; + (void)tolerance; + throw Base::RuntimeError("Multi common is available only in OCC 6.9.0 and up."); +#else + BRepAlgoAPI_Common mkCommon; + mkCommon.SetRunParallel(true); + TopTools_ListOfShape shapeArguments,shapeTools; + shapeArguments.Append(this->_Shape); + for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { + if (it->IsNull()) + throw Base::ValueError("Tool shape is null"); + if (tolerance > 0.0) + // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 + shapeTools.Append(BRepBuilderAPI_Copy(*it).Shape()); + else + shapeTools.Append(*it); + } + + mkCommon.SetArguments(shapeArguments); + mkCommon.SetTools(shapeTools); + if (tolerance > 0.0) + mkCommon.SetFuzzyValue(tolerance); + mkCommon.Build(); + if (!mkCommon.IsDone()) + throw Base::RuntimeError("Multi common failed"); + + TopoDS_Shape resShape = mkCommon.Shape(); + return resShape; +#endif +} + TopoDS_Shape TopoShape::fuse(TopoDS_Shape shape) const { if (this->_Shape.IsNull()) @@ -1306,75 +1378,7 @@ TopoDS_Shape TopoShape::fuse(TopoDS_Shape shape) const return mkFuse.Shape(); } -TopoDS_Shape TopoShape::multiCut(const std::vector& shapes, Standard_Real tolerance) const -{ - if (this->_Shape.IsNull()) - Standard_Failure::Raise("Base shape is null"); -#if OCC_VERSION_HEX < 0x060900 - (void)shapes; - (void)tolerance; - throw Base::AttributeError("multiCut is available only in OCC 6.9.0 and up."); -#else - BRepAlgoAPI_Cut mkCut; - mkCut.SetRunParallel(true); - TopTools_ListOfShape shapeArguments,shapeTools; - shapeArguments.Append(this->_Shape); - for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { - if (it->IsNull()) - throw Base::Exception("Tool shape is null"); - if (tolerance > 0.0) - // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 - shapeTools.Append(BRepBuilderAPI_Copy(*it).Shape()); - else - shapeTools.Append(*it); - } - mkCut.SetArguments(shapeArguments); - mkCut.SetTools(shapeTools); - if (tolerance > 0.0) - mkCut.SetFuzzyValue(tolerance); - mkCut.Build(); - if (!mkCut.IsDone()) - throw Base::Exception("MultiCut failed"); - TopoDS_Shape resShape = mkCut.Shape(); - return resShape; -#endif -} - -TopoDS_Shape TopoShape::multiCommon(const std::vector& shapes, Standard_Real tolerance) const -{ - if (this->_Shape.IsNull()) - Standard_Failure::Raise("Base shape is null"); -#if OCC_VERSION_HEX < 0x060900 - (void)shapes; - (void)tolerance; - throw Base::AttributeError("multiCommon is available only in OCC 6.9.0 and up."); -#else - BRepAlgoAPI_Common mkCommon; - mkCommon.SetRunParallel(true); - TopTools_ListOfShape shapeArguments,shapeTools; - shapeArguments.Append(this->_Shape); - for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { - if (it->IsNull()) - throw Base::Exception("Tool shape is null"); - if (tolerance > 0.0) - // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 - shapeTools.Append(BRepBuilderAPI_Copy(*it).Shape()); - else - shapeTools.Append(*it); - } - mkCommon.SetArguments(shapeArguments); - mkCommon.SetTools(shapeTools); - if (tolerance > 0.0) - mkCommon.SetFuzzyValue(tolerance); - mkCommon.Build(); - if (!mkCommon.IsDone()) - throw Base::Exception("MultiCommon failed"); - TopoDS_Shape resShape = mkCommon.Shape(); - return resShape; -#endif -} - -TopoDS_Shape TopoShape::multiFuse(const std::vector& shapes, Standard_Real tolerance) const +TopoDS_Shape TopoShape::fuse(const std::vector& shapes, Standard_Real tolerance) const { if (this->_Shape.IsNull()) Standard_Failure::Raise("Base shape is null"); @@ -1383,7 +1387,7 @@ TopoDS_Shape TopoShape::multiFuse(const std::vector& shapes, Stand Standard_Failure::Raise("Fuzzy Booleans are not supported in this version of OCCT"); TopoDS_Shape resShape = this->_Shape; if (resShape.IsNull()) - throw Base::Exception("Object shape is null"); + throw Base::ValueError("Object shape is null"); for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { if (it->IsNull()) throw Base::Exception("Input shape is null"); @@ -1391,7 +1395,7 @@ TopoDS_Shape TopoShape::multiFuse(const std::vector& shapes, Stand BRepAlgoAPI_Fuse mkFuse(resShape, *it); // Let's check if the fusion has been successful if (!mkFuse.IsDone()) - throw Base::Exception("Fusion failed"); + throw Base::RuntimeError("Fusion failed"); resShape = mkFuse.Shape(); } #else @@ -1416,46 +1420,13 @@ TopoDS_Shape TopoShape::multiFuse(const std::vector& shapes, Stand mkFuse.SetFuzzyValue(tolerance); mkFuse.Build(); if (!mkFuse.IsDone()) - throw Base::Exception("MultiFusion failed"); + throw Base::RuntimeError("Multi fuse failed"); + TopoDS_Shape resShape = mkFuse.Shape(); #endif return resShape; } -TopoDS_Shape TopoShape::multiSection(const std::vector& shapes, Standard_Real tolerance) const -{ - if (this->_Shape.IsNull()) - Standard_Failure::Raise("Base shape is null"); -#if OCC_VERSION_HEX < 0x060900 - (void)shapes; - (void)tolerance; - throw Base::AttributeError("multiSection is available only in OCC 6.9.0 and up."); -#else - BRepAlgoAPI_Section mkSection; - mkSection.SetRunParallel(true); - TopTools_ListOfShape shapeArguments,shapeTools; - shapeArguments.Append(this->_Shape); - for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { - if (it->IsNull()) - throw Base::Exception("Tool shape is null"); - if (tolerance > 0.0) - // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 - shapeTools.Append(BRepBuilderAPI_Copy(*it).Shape()); - else - shapeTools.Append(*it); - } - mkSection.SetArguments(shapeArguments); - mkSection.SetTools(shapeTools); - if (tolerance > 0.0) - mkSection.SetFuzzyValue(tolerance); - mkSection.Build(); - if (!mkSection.IsDone()) - throw Base::Exception("MultiSection failed"); - TopoDS_Shape resShape = mkSection.Shape(); - return resShape; -#endif -} - TopoDS_Shape TopoShape::oldFuse(TopoDS_Shape shape) const { if (this->_Shape.IsNull()) @@ -1476,6 +1447,42 @@ TopoDS_Shape TopoShape::section(TopoDS_Shape shape) const return mkSection.Shape(); } +TopoDS_Shape TopoShape::section(const std::vector& shapes, Standard_Real tolerance) const +{ + if (this->_Shape.IsNull()) + Standard_Failure::Raise("Base shape is null"); +#if OCC_VERSION_HEX < 0x060900 + (void)shapes; + (void)tolerance; + throw Base::RuntimeError("Multi section is available only in OCC 6.9.0 and up."); +#else + BRepAlgoAPI_Section mkSection; + mkSection.SetRunParallel(true); + TopTools_ListOfShape shapeArguments,shapeTools; + shapeArguments.Append(this->_Shape); + for (std::vector::const_iterator it = shapes.begin(); it != shapes.end(); ++it) { + if (it->IsNull()) + throw Base::ValueError("Tool shape is null"); + if (tolerance > 0.0) + // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 + shapeTools.Append(BRepBuilderAPI_Copy(*it).Shape()); + else + shapeTools.Append(*it); + } + + mkSection.SetArguments(shapeArguments); + mkSection.SetTools(shapeTools); + if (tolerance > 0.0) + mkSection.SetFuzzyValue(tolerance); + mkSection.Build(); + if (!mkSection.IsDone()) + throw Base::RuntimeError("Multi section failed"); + + TopoDS_Shape resShape = mkSection.Shape(); + return resShape; +#endif +} + std::list TopoShape::slice(const Base::Vector3d& dir, double d) const { CrossSection cs(dir.x, dir.y, dir.z, this->_Shape); diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 3dad2df8b7..00f5ad1cfc 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -154,14 +154,14 @@ public: /** @name Boolean operation*/ //@{ TopoDS_Shape cut(TopoDS_Shape) const; + TopoDS_Shape cut(const std::vector&, Standard_Real tolerance = 0.0) const; TopoDS_Shape common(TopoDS_Shape) const; + TopoDS_Shape common(const std::vector&, Standard_Real tolerance = 0.0) const; TopoDS_Shape fuse(TopoDS_Shape) const; - TopoDS_Shape multiCut(const std::vector&, Standard_Real tolerance = 0.0) const; - TopoDS_Shape multiCommon(const std::vector&, Standard_Real tolerance = 0.0) const; - TopoDS_Shape multiFuse(const std::vector&, Standard_Real tolerance = 0.0) const; - TopoDS_Shape multiSection(const std::vector&, Standard_Real tolerance = 0.0) const; + TopoDS_Shape fuse(const std::vector&, Standard_Real tolerance = 0.0) const; TopoDS_Shape oldFuse(TopoDS_Shape) const; TopoDS_Shape section(TopoDS_Shape) const; + TopoDS_Shape section(const std::vector&, Standard_Real tolerance = 0.0) const; std::list slice(const Base::Vector3d&, double) const; TopoDS_Compound slices(const Base::Vector3d&, const std::vector&) const; /** diff --git a/src/Mod/Part/App/TopoShapePy.xml b/src/Mod/Part/App/TopoShapePy.xml index c19f61d992..ab11fe4eb6 100644 --- a/src/Mod/Part/App/TopoShapePy.xml +++ b/src/Mod/Part/App/TopoShapePy.xml @@ -140,35 +140,19 @@ This is a more detailed check as done in isValid(). - Union of this and a given topo shape. - - - - - multiCut((tool1,tool2,...),[tolerance=0.0]) -> Shape + Union of this and a given (list of) topo shape. +fuse(tool) -> Shape + or +fuse((tool1,tool2,...),[tolerance=0.0]) -> Shape -Substraction of this and a given list of topo shapes. +Union of this and a given list of topo shapes. -Supports: +Supports (OCCT 6.9.0 and above): - Fuzzy Boolean operations (global tolerance for a Boolean operation) - Support of multiple arguments for a single Boolean operation - Parallelization of Boolean Operations algorithm -OCC 6.9.0 or later is required. - - - - - multiCommon((tool1,tool2,...),[tolerance=0.0]) -> Shape - -Intersection of this and a given list of topo shapes. - -Supports: -- Fuzzy Boolean operations (global tolerance for a Boolean operation) -- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s2)) -- Parallelization of Boolean Operations algorithm - -OCC 6.9.0 or later is required. +Beginning from OCCT 6.8.1 a tolerance value can be specified. @@ -182,21 +166,8 @@ Supports (OCCT 6.9.0 and above): - Support of multiple arguments for a single Boolean operation - Parallelization of Boolean Operations algorithm -Beginning from OCCT 6.8.1 a tolerance value can be specified. - - - - - multiSection((tool1,tool2,...),[tolerance=0.0]) -> Shape - -Section of this and a given list of topo shapes. - -Supports: -- Fuzzy Boolean operations (global tolerance for a Boolean operation) -- Support of multiple arguments for a single Boolean operation -- Parallelization of Boolean Operations algorithm - -OCC 6.9.0 or later is required. +Beginning from OCCT 6.8.1 a tolerance value can be specified. +Deprecated: use fuse() instead. @@ -206,12 +177,36 @@ OCC 6.9.0 or later is required. - Intersection of this and a given topo shape. + Intersection of this and a given (list of) topo shape. +common(tool) -> Shape + or +common((tool1,tool2,...),[tolerance=0.0]) -> Shape + +Intersection of this and a given list of topo shapes. + +Supports: +- Fuzzy Boolean operations (global tolerance for a Boolean operation) +- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s2)) +- Parallelization of Boolean Operations algorithm + +OCC 6.9.0 or later is required. - Section of this with a given topo shape. + Section of this with a given (list of) topo shape. +section(tool) -> Shape + or +section((tool1,tool2,...),[tolerance=0.0]) -> Shape + +Section of this and a given list of topo shapes. + +Supports: +- Fuzzy Boolean operations (global tolerance for a Boolean operation) +- Support of multiple arguments for a single Boolean operation +- Parallelization of Boolean Operations algorithm + +OCC 6.9.0 or later is required. @@ -226,7 +221,19 @@ OCC 6.9.0 or later is required. - Difference of this and a given topo shape. + Difference of this and a given (list of) topo shape +cut(tool) -> Shape + or +cut((tool1,tool2,...),[tolerance=0.0]) -> Shape + +Substraction of this and a given list of topo shapes. + +Supports: +- Fuzzy Boolean operations (global tolerance for a Boolean operation) +- Support of multiple arguments for a single Boolean operation +- Parallelization of Boolean Operations algorithm + +OCC 6.9.0 or later is required. diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index 232c306b01..dcc1cd385b 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -759,90 +759,56 @@ PyObject* TopoShapePy::check(PyObject *args) PyObject* TopoShapePy::fuse(PyObject *args) { PyObject *pcObj; - if (!PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) - return NULL; - - TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); - try { - // Let's call algorithm computing a fuse operation: - TopoDS_Shape fusShape = this->getTopoShapePtr()->fuse(shape); - return new TopoShapePy(new TopoShape(fusShape)); - } - catch (Standard_Failure) { - Handle_Standard_Failure e = Standard_Failure::Caught(); - PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); - return NULL; - } - catch (const std::exception& e) { - PyErr_SetString(PartExceptionOCCError, e.what()); - return NULL; - } -} - -PyObject* TopoShapePy::multiCut(PyObject *args) -{ - double tolerance = 0.0; - PyObject *pcObj; - if (!PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) - return NULL; - std::vector shapeVec; - Py::Sequence shapeSeq(pcObj); - for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { - PyObject* item = (*it).ptr(); - if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { - shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + if (PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) { + TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); + try { + // Let's call algorithm computing a fuse operation: + TopoDS_Shape fusShape = this->getTopoShapePtr()->fuse(shape); + return new TopoShapePy(new TopoShape(fusShape)); } - else { - PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); - return 0; - } - } - try { - TopoDS_Shape multiCutShape = this->getTopoShapePtr()->multiCut(shapeVec,tolerance); - return new TopoShapePy(new TopoShape(multiCutShape)); - } - catch (Standard_Failure) { - Handle_Standard_Failure e = Standard_Failure::Caught(); - PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); - return NULL; - } - catch (const std::exception& e) { - PyErr_SetString(PartExceptionOCCError, e.what()); - return NULL; - } -} - -PyObject* TopoShapePy::multiCommon(PyObject *args) -{ - double tolerance = 0.0; - PyObject *pcObj; - if (!PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) - return NULL; - std::vector shapeVec; - Py::Sequence shapeSeq(pcObj); - for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { - PyObject* item = (*it).ptr(); - if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { - shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; } - else { - PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); - return 0; - } } - try { - TopoDS_Shape multiCommonShape = this->getTopoShapePtr()->multiCommon(shapeVec,tolerance); - return new TopoShapePy(new TopoShape(multiCommonShape)); - } - catch (Standard_Failure) { - Handle_Standard_Failure e = Standard_Failure::Caught(); - PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); - return NULL; - } - catch (const std::exception& e) { - PyErr_SetString(PartExceptionOCCError, e.what()); - return NULL; + + PyErr_Clear(); + double tolerance = 0.0; + if (PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) { + std::vector shapeVec; + Py::Sequence shapeSeq(pcObj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + else { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + return 0; + } + } + try { + TopoDS_Shape multiFusedShape = this->getTopoShapePtr()->fuse(shapeVec,tolerance); + return new TopoShapePy(new TopoShape(multiFusedShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } } + + PyErr_SetString(PyExc_TypeError, "shape or sequence of shape expected"); + return 0; } PyObject* TopoShapePy::multiFuse(PyObject *args) @@ -864,7 +830,7 @@ PyObject* TopoShapePy::multiFuse(PyObject *args) } } try { - TopoDS_Shape multiFusedShape = this->getTopoShapePtr()->multiFuse(shapeVec,tolerance); + TopoDS_Shape multiFusedShape = this->getTopoShapePtr()->fuse(shapeVec,tolerance); return new TopoShapePy(new TopoShape(multiFusedShape)); } catch (Standard_Failure) { @@ -878,39 +844,6 @@ PyObject* TopoShapePy::multiFuse(PyObject *args) } } -PyObject* TopoShapePy::multiSection(PyObject *args) -{ - double tolerance = 0.0; - PyObject *pcObj; - if (!PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) - return NULL; - std::vector shapeVec; - Py::Sequence shapeSeq(pcObj); - for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { - PyObject* item = (*it).ptr(); - if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { - shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); - } - else { - PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); - return 0; - } - } - try { - TopoDS_Shape multiSectionShape = this->getTopoShapePtr()->multiSection(shapeVec,tolerance); - return new TopoShapePy(new TopoShape(multiSectionShape)); - } - catch (Standard_Failure) { - Handle_Standard_Failure e = Standard_Failure::Caught(); - PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); - return NULL; - } - catch (const std::exception& e) { - PyErr_SetString(PartExceptionOCCError, e.what()); - return NULL; - } -} - PyObject* TopoShapePy::oldFuse(PyObject *args) { PyObject *pcObj; @@ -937,47 +870,111 @@ PyObject* TopoShapePy::oldFuse(PyObject *args) PyObject* TopoShapePy::common(PyObject *args) { PyObject *pcObj; - if (!PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) - return NULL; + if (PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) { + TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); + try { + // Let's call algorithm computing a common operation: + TopoDS_Shape comShape = this->getTopoShapePtr()->common(shape); + return new TopoShapePy(new TopoShape(comShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } + } - TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); - try { - // Let's call algorithm computing a common operation: - TopoDS_Shape comShape = this->getTopoShapePtr()->common(shape); - return new TopoShapePy(new TopoShape(comShape)); - } - catch (Standard_Failure) { - Handle_Standard_Failure e = Standard_Failure::Caught(); - PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); - return NULL; - } - catch (const std::exception& e) { - PyErr_SetString(PartExceptionOCCError, e.what()); - return NULL; + PyErr_Clear(); + double tolerance = 0.0; + if (PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) { + std::vector shapeVec; + Py::Sequence shapeSeq(pcObj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + else { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + return 0; + } + } + try { + TopoDS_Shape multiCommonShape = this->getTopoShapePtr()->common(shapeVec,tolerance); + return new TopoShapePy(new TopoShape(multiCommonShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } } + + PyErr_SetString(PyExc_TypeError, "shape or sequence of shape expected"); + return 0; } PyObject* TopoShapePy::section(PyObject *args) { PyObject *pcObj; - if (!PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) - return NULL; + if (PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) { + TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); + try { + // Let's call algorithm computing a section operation: + TopoDS_Shape secShape = this->getTopoShapePtr()->section(shape); + return new TopoShapePy(new TopoShape(secShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } + } - TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); - try { - // Let's call algorithm computing a section operation: - TopoDS_Shape secShape = this->getTopoShapePtr()->section(shape); - return new TopoShapePy(new TopoShape(secShape)); - } - catch (Standard_Failure) { - Handle_Standard_Failure e = Standard_Failure::Caught(); - PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); - return NULL; - } - catch (const std::exception& e) { - PyErr_SetString(PartExceptionOCCError, e.what()); - return NULL; + PyErr_Clear(); + double tolerance = 0.0; + if (PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) { + std::vector shapeVec; + Py::Sequence shapeSeq(pcObj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + else { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + return 0; + } + } + try { + TopoDS_Shape multiSectionShape = this->getTopoShapePtr()->section(shapeVec,tolerance); + return new TopoShapePy(new TopoShape(multiSectionShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } } + + PyErr_SetString(PyExc_TypeError, "shape or sequence of shape expected"); + return 0; } PyObject* TopoShapePy::slice(PyObject *args) @@ -1038,24 +1035,56 @@ PyObject* TopoShapePy::slices(PyObject *args) PyObject* TopoShapePy::cut(PyObject *args) { PyObject *pcObj; - if (!PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) - return NULL; + if (PyArg_ParseTuple(args, "O!", &(TopoShapePy::Type), &pcObj)) { + TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); + try { + // Let's call algorithm computing a cut operation: + TopoDS_Shape cutShape = this->getTopoShapePtr()->cut(shape); + return new TopoShapePy(new TopoShape(cutShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } + } - TopoDS_Shape shape = static_cast(pcObj)->getTopoShapePtr()->getShape(); - try { - // Let's call algorithm computing a cut operation: - TopoDS_Shape cutShape = this->getTopoShapePtr()->cut(shape); - return new TopoShapePy(new TopoShape(cutShape)); - } - catch (Standard_Failure) { - Handle_Standard_Failure e = Standard_Failure::Caught(); - PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); - return NULL; - } - catch (const std::exception& e) { - PyErr_SetString(PartExceptionOCCError, e.what()); - return NULL; + PyErr_Clear(); + double tolerance = 0.0; + if (PyArg_ParseTuple(args, "O|d", &pcObj, &tolerance)) { + std::vector shapeVec; + Py::Sequence shapeSeq(pcObj); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if (PyObject_TypeCheck(item, &(Part::TopoShapePy::Type))) { + shapeVec.push_back(static_cast(item)->getTopoShapePtr()->getShape()); + } + else { + PyErr_SetString(PyExc_TypeError, "non-shape object in sequence"); + return 0; + } + } + try { + TopoDS_Shape multiCutShape = this->getTopoShapePtr()->cut(shapeVec,tolerance); + return new TopoShapePy(new TopoShape(multiCutShape)); + } + catch (Standard_Failure) { + Handle_Standard_Failure e = Standard_Failure::Caught(); + PyErr_SetString(PartExceptionOCCError, e->GetMessageString()); + return NULL; + } + catch (const std::exception& e) { + PyErr_SetString(PartExceptionOCCError, e.what()); + return NULL; + } } + + PyErr_SetString(PyExc_TypeError, "shape or sequence of shape expected"); + return 0; } PyObject* TopoShapePy::generalFuse(PyObject *args) diff --git a/src/Mod/PartDesign/App/ShapeBinder.cpp b/src/Mod/PartDesign/App/ShapeBinder.cpp index 6c0a67f486..3d46c85cb9 100644 --- a/src/Mod/PartDesign/App/ShapeBinder.cpp +++ b/src/Mod/PartDesign/App/ShapeBinder.cpp @@ -142,7 +142,7 @@ Part::TopoShape ShapeBinder::buildShapeFromReferences( Part::Feature* obj, std:: try { if(!operators.empty() && !base.isNull()) - return base.multiFuse(operators); + return base.fuse(operators); } catch(...) { return base; From 47ed29fffdf84699de2d4ee989a18e13a6204d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Sat, 10 Dec 2016 14:44:13 +0100 Subject: [PATCH 055/144] Extensions: GeoFeatureGroup only for GeoFeature --- src/App/DocumentObjectExtension.cpp | 2 +- src/App/Extension.cpp | 2 +- src/App/Extension.h | 6 ++--- src/App/GeoFeatureGroupExtension.cpp | 25 +++++++++++++++---- src/App/GeoFeatureGroupExtension.h | 4 ++- src/App/GroupExtension.cpp | 2 +- src/App/OriginGroupExtension.cpp | 2 +- src/App/Part.cpp | 2 +- src/App/Part.h | 2 +- src/Gui/ViewProviderExtension.cpp | 2 +- src/Gui/ViewProviderExtension.h | 2 +- .../ViewProviderGeoFeatureGroupExtension.cpp | 6 ++--- src/Gui/ViewProviderGroupExtension.cpp | 2 +- src/Gui/ViewProviderOriginGroupExtension.cpp | 2 +- src/Mod/Part/App/AttachExtension.cpp | 2 +- 15 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/App/DocumentObjectExtension.cpp b/src/App/DocumentObjectExtension.cpp index 526bda7764..9c8c5a34f7 100644 --- a/src/App/DocumentObjectExtension.cpp +++ b/src/App/DocumentObjectExtension.cpp @@ -37,7 +37,7 @@ EXTENSION_PROPERTY_SOURCE(App::DocumentObjectExtension, App::Extension) DocumentObjectExtension::DocumentObjectExtension() { - initExtension(App::DocumentObjectExtension::getExtensionClassTypeId()); + initExtensionType(App::DocumentObjectExtension::getExtensionClassTypeId()); } DocumentObjectExtension::~DocumentObjectExtension() diff --git a/src/App/Extension.cpp b/src/App/Extension.cpp index 35637df6d3..519982bc5b 100644 --- a/src/App/Extension.cpp +++ b/src/App/Extension.cpp @@ -71,7 +71,7 @@ Extension::~Extension() } } -void Extension::initExtension(Base::Type type) { +void Extension::initExtensionType(Base::Type type) { m_extensionType = type; if(m_extensionType.isBad()) diff --git a/src/App/Extension.h b/src/App/Extension.h index 1dfc8dab80..4b1137ed3b 100644 --- a/src/App/Extension.h +++ b/src/App/Extension.h @@ -215,7 +215,7 @@ public: Extension(); virtual ~Extension(); - void initExtension(App::ExtensionContainer* obj); + virtual void initExtension(App::ExtensionContainer* obj); App::ExtensionContainer* getExtendedContainer() {return m_base;} const App::ExtensionContainer* getExtendedContainer() const {return m_base;} @@ -272,7 +272,7 @@ protected: friend class App::ExtensionContainer; protected: - void initExtension(Base::Type type); + void initExtensionType(Base::Type type); bool m_isPythonExtension = false; Py::Object ExtensionPythonObject; @@ -309,7 +309,7 @@ public: ExtensionPythonT() { ExtensionT::m_isPythonExtension = true; - ExtensionT::initExtension(ExtensionPythonT::getExtensionClassTypeId()); + ExtensionT::initExtensionType(ExtensionPythonT::getExtensionClassTypeId()); EXTENSION_ADD_PROPERTY(ExtensionProxy,(Py::Object())); } diff --git a/src/App/GeoFeatureGroupExtension.cpp b/src/App/GeoFeatureGroupExtension.cpp index a76afbef84..a9dadff5f5 100644 --- a/src/App/GeoFeatureGroupExtension.cpp +++ b/src/App/GeoFeatureGroupExtension.cpp @@ -45,21 +45,36 @@ EXTENSION_PROPERTY_SOURCE(App::GeoFeatureGroupExtension, App::GroupExtension) GeoFeatureGroupExtension::GeoFeatureGroupExtension(void) { - initExtension(GeoFeatureGroupExtension::getExtensionClassTypeId()); - - EXTENSION_ADD_PROPERTY(Placement,(Base::Placement())); + initExtensionType(GeoFeatureGroupExtension::getExtensionClassTypeId()); } GeoFeatureGroupExtension::~GeoFeatureGroupExtension(void) { } +void GeoFeatureGroupExtension::initExtension(ExtensionContainer* obj) { + + if(!obj->isDerivedFrom(App::GeoFeature::getClassTypeId())) + throw Base::Exception("GeoFeatureGroupExtension can only be applied to GeoFeatures"); + + App::Extension::initExtension(obj); +} + +PropertyPlacement& GeoFeatureGroupExtension::placement() { + + if(!getExtendedContainer()) + throw Base::Exception("GeoFeatureGroupExtension was not applied to GeoFeature"); + + return static_cast(getExtendedContainer())->Placement; +} + + void GeoFeatureGroupExtension::transformPlacement(const Base::Placement &transform) { // NOTE: Keep in sync with APP::GeoFeature - Base::Placement plm = this->Placement.getValue(); + Base::Placement plm = this->placement().getValue(); plm = transform * plm; - this->Placement.setValue(plm); + this->placement().setValue(plm); } std::vector GeoFeatureGroupExtension::getGeoSubObjects () const { diff --git a/src/App/GeoFeatureGroupExtension.h b/src/App/GeoFeatureGroupExtension.h index 31f22eb3d7..bc76371524 100644 --- a/src/App/GeoFeatureGroupExtension.h +++ b/src/App/GeoFeatureGroupExtension.h @@ -41,7 +41,9 @@ class AppExport GeoFeatureGroupExtension : public App::GroupExtension EXTENSION_PROPERTY_HEADER(App::GeoFeatureGroupExtension); public: - PropertyPlacement Placement; + PropertyPlacement& placement(); + + virtual void initExtension(ExtensionContainer* obj); /** * @brief transformPlacement applies transform to placement of this shape. diff --git a/src/App/GroupExtension.cpp b/src/App/GroupExtension.cpp index a070840b5d..54e3ce95a6 100644 --- a/src/App/GroupExtension.cpp +++ b/src/App/GroupExtension.cpp @@ -38,7 +38,7 @@ EXTENSION_PROPERTY_SOURCE(App::GroupExtension, App::DocumentObjectExtension) GroupExtension::GroupExtension() { - initExtension(GroupExtension::getExtensionClassTypeId()); + initExtensionType(GroupExtension::getExtensionClassTypeId()); EXTENSION_ADD_PROPERTY_TYPE(Group,(0),"Base",(App::PropertyType)(Prop_Output),"List of referenced objects"); } diff --git a/src/App/OriginGroupExtension.cpp b/src/App/OriginGroupExtension.cpp index 2f0ac1468c..739d01f838 100644 --- a/src/App/OriginGroupExtension.cpp +++ b/src/App/OriginGroupExtension.cpp @@ -39,7 +39,7 @@ EXTENSION_PROPERTY_SOURCE(App::OriginGroupExtension, App::GeoFeatureGroupExtensi OriginGroupExtension::OriginGroupExtension () { - initExtension(OriginGroupExtension::getExtensionClassTypeId()); + initExtensionType(OriginGroupExtension::getExtensionClassTypeId()); EXTENSION_ADD_PROPERTY_TYPE ( Origin, (0), 0, App::Prop_Hidden, "Origin linked to the group" ); } diff --git a/src/App/Part.cpp b/src/App/Part.cpp index 96303bad40..8030c0212d 100644 --- a/src/App/Part.cpp +++ b/src/App/Part.cpp @@ -35,7 +35,7 @@ using namespace App; -PROPERTY_SOURCE_WITH_EXTENSIONS(App::Part, App::DocumentObject) +PROPERTY_SOURCE_WITH_EXTENSIONS(App::Part, App::GeoFeature) //=========================================================================== diff --git a/src/App/Part.h b/src/App/Part.h index d34388fe48..089c0d6888 100644 --- a/src/App/Part.h +++ b/src/App/Part.h @@ -35,7 +35,7 @@ namespace App /** Base class of all geometric document objects. */ -class AppExport Part : public App::DocumentObject, public App::OriginGroupExtension +class AppExport Part : public App::GeoFeature, public App::OriginGroupExtension { PROPERTY_HEADER_WITH_EXTENSIONS(App::Part); diff --git a/src/Gui/ViewProviderExtension.cpp b/src/Gui/ViewProviderExtension.cpp index 0cbba6602b..49386eb93d 100644 --- a/src/Gui/ViewProviderExtension.cpp +++ b/src/Gui/ViewProviderExtension.cpp @@ -37,7 +37,7 @@ EXTENSION_PROPERTY_SOURCE(Gui::ViewProviderExtension, App::Extension) ViewProviderExtension::ViewProviderExtension() { - initExtension(Gui::ViewProviderExtension::getExtensionClassTypeId()); + initExtensionType(Gui::ViewProviderExtension::getExtensionClassTypeId()); } ViewProviderExtension::~ViewProviderExtension() diff --git a/src/Gui/ViewProviderExtension.h b/src/Gui/ViewProviderExtension.h index ed3e90b9d8..4e4520d100 100644 --- a/src/Gui/ViewProviderExtension.h +++ b/src/Gui/ViewProviderExtension.h @@ -97,7 +97,7 @@ public: ViewProviderExtensionPythonT() { ExtensionT::m_isPythonExtension = true; - ExtensionT::initExtension(ViewProviderExtensionPythonT::getExtensionClassTypeId()); + ExtensionT::initExtensionType(ViewProviderExtensionPythonT::getExtensionClassTypeId()); EXTENSION_ADD_PROPERTY(ExtensionProxy,(Py::Object())); } diff --git a/src/Gui/ViewProviderGeoFeatureGroupExtension.cpp b/src/Gui/ViewProviderGeoFeatureGroupExtension.cpp index e700b91dd2..41255be876 100644 --- a/src/Gui/ViewProviderGeoFeatureGroupExtension.cpp +++ b/src/Gui/ViewProviderGeoFeatureGroupExtension.cpp @@ -38,7 +38,7 @@ EXTENSION_PROPERTY_SOURCE(Gui::ViewProviderGeoFeatureGroupExtension, Gui::ViewPr ViewProviderGeoFeatureGroupExtension::ViewProviderGeoFeatureGroupExtension() { - initExtension(ViewProviderGeoFeatureGroupExtension::getExtensionClassTypeId()); + initExtensionType(ViewProviderGeoFeatureGroupExtension::getExtensionClassTypeId()); pcGroupChildren = new SoGroup(); pcGroupChildren->ref(); @@ -84,8 +84,8 @@ std::vector ViewProviderGeoFeatureGroupExtension::extensionGetDispl void ViewProviderGeoFeatureGroupExtension::extensionUpdateData(const App::Property* prop) { auto obj = getExtendedViewProvider()->getObject()->getExtensionByType(); - if (obj && prop == &obj->Placement) { - getExtendedViewProvider()->setTransformation ( obj->Placement.getValue().toMatrix() ); + if (obj && prop == &obj->placement()) { + getExtendedViewProvider()->setTransformation ( obj->placement().getValue().toMatrix() ); } else { ViewProviderGroupExtension::extensionUpdateData ( prop ); } diff --git a/src/Gui/ViewProviderGroupExtension.cpp b/src/Gui/ViewProviderGroupExtension.cpp index 317073a6cb..d1d234a62a 100644 --- a/src/Gui/ViewProviderGroupExtension.cpp +++ b/src/Gui/ViewProviderGroupExtension.cpp @@ -45,7 +45,7 @@ EXTENSION_PROPERTY_SOURCE(Gui::ViewProviderGroupExtension, Gui::ViewProviderExte ViewProviderGroupExtension::ViewProviderGroupExtension() : visible(false) { - initExtension(ViewProviderGroupExtension::getExtensionClassTypeId()); + initExtensionType(ViewProviderGroupExtension::getExtensionClassTypeId()); } ViewProviderGroupExtension::~ViewProviderGroupExtension() diff --git a/src/Gui/ViewProviderOriginGroupExtension.cpp b/src/Gui/ViewProviderOriginGroupExtension.cpp index c87341641a..226af9a9c8 100644 --- a/src/Gui/ViewProviderOriginGroupExtension.cpp +++ b/src/Gui/ViewProviderOriginGroupExtension.cpp @@ -48,7 +48,7 @@ EXTENSION_PROPERTY_SOURCE(Gui::ViewProviderOriginGroupExtension, Gui::ViewProvid ViewProviderOriginGroupExtension::ViewProviderOriginGroupExtension() { - initExtension(ViewProviderOriginGroupExtension::getExtensionClassTypeId()); + initExtensionType(ViewProviderOriginGroupExtension::getExtensionClassTypeId()); } ViewProviderOriginGroupExtension::~ViewProviderOriginGroupExtension() diff --git a/src/Mod/Part/App/AttachExtension.cpp b/src/Mod/Part/App/AttachExtension.cpp index c15cec8e21..ce6e0a773a 100644 --- a/src/Mod/Part/App/AttachExtension.cpp +++ b/src/Mod/Part/App/AttachExtension.cpp @@ -59,7 +59,7 @@ AttachExtension::AttachExtension() EXTENSION_ADD_PROPERTY_TYPE(superPlacement, (Base::Placement()), "Attachment", App::Prop_None, "Extra placement to apply in addition to attachment (in local coordinates)"); setAttacher(new AttachEngine3D);//default attacher - initExtension(AttachExtension::getExtensionClassTypeId()); + initExtensionType(AttachExtension::getExtensionClassTypeId()); } AttachExtension::~AttachExtension() From 9a3b952fb9a2d2d98f5bd33db3a9c7975033a4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Wed, 21 Dec 2016 08:51:54 +0100 Subject: [PATCH 056/144] PartDesign: Port body to be a origin group --- src/App/GroupExtension.h | 8 +- src/Mod/Part/App/BodyBase.cpp | 15 +--- src/Mod/Part/App/BodyBase.h | 21 ++---- src/Mod/PartDesign/App/Body.cpp | 73 +++++++------------ src/Mod/PartDesign/App/Body.h | 15 +--- src/Mod/PartDesign/App/BodyPyImp.cpp | 8 +- src/Mod/PartDesign/Gui/Command.cpp | 10 +-- src/Mod/PartDesign/Gui/CommandBody.cpp | 4 +- src/Mod/PartDesign/Gui/ReferenceSelection.cpp | 4 +- .../PartDesign/Gui/TaskDatumParameters.cpp | 6 +- src/Mod/PartDesign/Gui/TaskFeaturePick.cpp | 6 +- src/Mod/PartDesign/Gui/TaskPipeParameters.cpp | 14 ++-- .../Gui/TaskSketchBasedParameters.cpp | 2 +- src/Mod/PartDesign/Gui/Utils.cpp | 2 +- src/Mod/PartDesign/Gui/ViewProvider.cpp | 2 +- src/Mod/PartDesign/Gui/ViewProviderBody.cpp | 18 ++--- src/Mod/PartDesign/Gui/Workbench.cpp | 2 +- 17 files changed, 82 insertions(+), 128 deletions(-) diff --git a/src/App/GroupExtension.h b/src/App/GroupExtension.h index 01fe0fa79f..f6531020f4 100644 --- a/src/App/GroupExtension.h +++ b/src/App/GroupExtension.h @@ -49,20 +49,20 @@ public: /** Adds an object of \a sType with \a pObjectName to the document this group belongs to and * append it to this group as well. */ - DocumentObject *addObject(const char* sType, const char* pObjectName); + virtual DocumentObject *addObject(const char* sType, const char* pObjectName); /* Adds the object \a obj to this group. */ - void addObject(DocumentObject* obj); + virtual void addObject(DocumentObject* obj); /*override this function if you want only special objects */ virtual bool allowObject(DocumentObject* ) {return true;}; /** Removes an object from this group. */ - void removeObject(DocumentObject* obj); + virtual void removeObject(DocumentObject* obj); /** Removes all children objects from this group and the document. */ - void removeObjectsFromDocument(); + virtual void removeObjectsFromDocument(); /** Returns the object of this group with \a Name. If the group doesn't have such an object 0 is returned. * @note This method might return 0 even if the document this group belongs to contains an object with this name. */ diff --git a/src/Mod/Part/App/BodyBase.cpp b/src/Mod/Part/App/BodyBase.cpp index 15a32f003d..bee0fe71af 100644 --- a/src/Mod/Part/App/BodyBase.cpp +++ b/src/Mod/Part/App/BodyBase.cpp @@ -35,21 +35,14 @@ namespace Part { -PROPERTY_SOURCE(Part::BodyBase, Part::Feature) +PROPERTY_SOURCE_WITH_EXTENSIONS(Part::BodyBase, Part::Feature) BodyBase::BodyBase() { - ADD_PROPERTY(Model , (0) ); ADD_PROPERTY(Tip , (0) ); ADD_PROPERTY(BaseFeature , (0) ); } -bool BodyBase::hasFeature(const App::DocumentObject* f) const -{ - const std::vector &features = Model.getValues(); - return f == BaseFeature.getValue() || std::find(features.begin(), features.end(), f) != features.end(); -} - BodyBase* BodyBase::findBodyOf(const App::DocumentObject* f) { App::Document* doc = f->getDocument(); @@ -57,7 +50,7 @@ BodyBase* BodyBase::findBodyOf(const App::DocumentObject* f) std::vector bodies = doc->getObjectsOfType(BodyBase::getClassTypeId()); for (std::vector::const_iterator b = bodies.begin(); b != bodies.end(); b++) { BodyBase* body = static_cast(*b); - if (body->hasFeature(f)) + if (body->hasObject(f)) return body; } } @@ -73,10 +66,10 @@ bool BodyBase::isAfter(const App::DocumentObject *feature, const App::DocumentOb } if (!target || target == BaseFeature.getValue() ) { - return hasFeature (feature); + return hasObject (feature); } - const std::vector & features = Model.getValues(); + const std::vector & features = Group.getValues(); auto featureIt = std::find(features.begin(), features.end(), feature); auto targetIt = std::find(features.begin(), features.end(), target); diff --git a/src/Mod/Part/App/BodyBase.h b/src/Mod/Part/App/BodyBase.h index 3379344ee5..cf9675bfb2 100644 --- a/src/Mod/Part/App/BodyBase.h +++ b/src/Mod/Part/App/BodyBase.h @@ -25,6 +25,7 @@ #define PART_BodyBase_H #include +#include #include @@ -36,19 +37,16 @@ namespace Part * in edit or active on a workbench, the body shows only the * resulting shape to the outside (Tip link). */ -class PartExport BodyBase : public Part::Feature +class PartExport BodyBase : public Part::Feature, public App::OriginGroupExtension { - PROPERTY_HEADER(Part::BodyBase); + PROPERTY_HEADER_WITH_EXTENSIONS(Part::BodyBase); public: BodyBase(); - /// The list of features - App::PropertyLinkList Model; - /** * The final feature of the body it is associated with. - * Note: tip may either point to the BaseFeature or to some feature inside the Model list. + * Note: tip may either point to the BaseFeature or to some feature inside the Group list. * in case it points to the model the PartDesign::Body guaranties that it is a solid. */ App::PropertyLink Tip; @@ -59,23 +57,16 @@ public: */ App::PropertyLink BaseFeature; - /// Returns all Model objects prepanded by BaseFeature (if any) + /// Returns all Group objects prepanded by BaseFeature (if any) std::vector getFullModel () { std::vector rv; if ( BaseFeature.getValue () ) { rv.push_back ( BaseFeature.getValue () ); } - std::copy ( Model.getValues ().begin (), Model.getValues ().end (), std::back_inserter (rv) ); + std::copy ( Group.getValues ().begin (), Group.getValues ().end (), std::back_inserter (rv) ); return rv; } - // These methods are located here to avoid a dependency of ViewProviderSketchObject on PartDesign - /// Remove the feature from the body - virtual void removeFeature(App::DocumentObject*){} - - /// Return true if the feature belongs to this body or either the body is based on the feature - bool hasFeature(const App::DocumentObject *f) const; - /// Return true if the feature belongs to the body and is located after the target bool isAfter(const App::DocumentObject *feature, const App::DocumentObject *target) const; diff --git a/src/Mod/PartDesign/App/Body.cpp b/src/Mod/PartDesign/App/Body.cpp index 016150321c..81fb3239e0 100644 --- a/src/Mod/PartDesign/App/Body.cpp +++ b/src/Mod/PartDesign/App/Body.cpp @@ -60,15 +60,15 @@ Body::Body() { /* // Note: The following code will catch Python Document::removeObject() modifications. If the object removed is -// a member of the Body::Model, then it will be automatically removed from the Model property which triggers the +// a member of the Body::Group, then it will be automatically removed from the Group property which triggers the // following two methods // But since we require the Python user to call both Document::addObject() and Body::addFeature(), we should // also require calling both Document::removeObject and Body::removeFeature() in order to be consistent void Body::onBeforeChange(const App::Property *prop) { // Remember the feature before the current Tip. If the Tip is already at the first feature, remember the next feature - if (prop == &Model) { - std::vector features = Model.getValues(); + if (prop == &Group) { + std::vector features = Group.getValues(); if (features.empty()) { rememberTip = NULL; } else { @@ -91,8 +91,8 @@ void Body::onBeforeChange(const App::Property *prop) void Body::onChanged(const App::Property *prop) { - if (prop == &Model) { - std::vector features = Model.getValues(); + if (prop == &Group) { + std::vector features = Group.getValues(); if (features.empty()) { Tip.setValue(NULL); } else { @@ -119,7 +119,7 @@ short Body::mustExecute() const App::DocumentObject* Body::getPrevFeature(App::DocumentObject *start) const { - std::vector features = Model.getValues(); + std::vector features = Group.getValues(); if (features.empty()) return NULL; App::DocumentObject* st = (start == NULL ? Tip.getValue() : start); if (st == NULL) @@ -146,9 +146,9 @@ App::DocumentObject* Body::getPrevSolidFeature(App::DocumentObject *start) return nullptr; } - assert ( hasFeature ( start ) ); + assert ( hasObject ( start ) ); - const std::vector & features = Model.getValues(); + const std::vector & features = Group.getValues(); auto startIt = std::find ( features.rbegin(), features.rend(), start ); assert ( startIt != features.rend() ); @@ -173,9 +173,9 @@ App::DocumentObject* Body::getNextSolidFeature(App::DocumentObject *start) return nullptr; } - assert ( hasFeature ( start ) ); + assert ( hasObject ( start ) ); - const std::vector & features = Model.getValues(); + const std::vector & features = Group.getValues(); std::vector::const_iterator startIt; if ( start == baseFeature ) { @@ -264,9 +264,12 @@ Body* Body::findBodyOf(const App::DocumentObject* feature) } -void Body::addFeature(App::DocumentObject *feature) +void Body::addObject(App::DocumentObject *feature) { - insertFeature (feature, getNextSolidFeature (), /*after = */ false); + if(!isAllowed(feature)) + throw Base::Exception("Body: object is not allowed"); + + insertObject (feature, getNextSolidFeature (), /*after = */ false); // Move the Tip if we added a solid if (isSolidFeature(feature)) { Tip.setValue (feature); @@ -274,7 +277,7 @@ void Body::addFeature(App::DocumentObject *feature) } -void Body::insertFeature(App::DocumentObject* feature, App::DocumentObject* target, bool after) +void Body::insertObject(App::DocumentObject* feature, App::DocumentObject* target, bool after) { if (target) { if (target == BaseFeature.getValue()) { @@ -284,13 +287,13 @@ void Body::insertFeature(App::DocumentObject* feature, App::DocumentObject* targ } else { throw Base::Exception("Body: impossible to insert before the base object"); } - } else if (!hasFeature (target)) { + } else if (!hasObject (target)) { // Check if the target feature belongs to the body throw Base::Exception("Body: the feature we should insert relative to is not part of that body"); } } - std::vector model = Model.getValues(); + std::vector model = Group.getValues(); std::vector::iterator insertInto; // Find out the position there to insert the feature @@ -313,7 +316,7 @@ void Body::insertFeature(App::DocumentObject* feature, App::DocumentObject* targ // Insert the new feature after the given model.insert (insertInto, feature); - Model.setValues (model); + Group.setValues (model); // Set the BaseFeature property if (Body::isSolidFeature(feature)) { @@ -333,7 +336,7 @@ void Body::insertFeature(App::DocumentObject* feature, App::DocumentObject* targ } -void Body::removeFeature(App::DocumentObject* feature) +void Body::removeObject(App::DocumentObject* feature) { App::DocumentObject* nextSolidFeature = getNextSolidFeature(feature); App::DocumentObject* prevSolidFeature = getPrevSolidFeature(feature); @@ -348,7 +351,7 @@ void Body::removeFeature(App::DocumentObject* feature) } } - std::vector model = Model.getValues(); + std::vector model = Group.getValues(); std::vector::iterator it = std::find(model.begin(), model.end(), feature); // Adjust Tip feature if it is pointing to the deleted object @@ -360,9 +363,9 @@ void Body::removeFeature(App::DocumentObject* feature) } } - // Erase feature from Model + // Erase feature from Group model.erase(it); - Model.setValues(model); + Group.setValues(model); } @@ -372,8 +375,8 @@ App::DocumentObjectExecReturn *Body::execute(void) Base::Console().Error("Body '%s':\n", getNameInDocument()); App::DocumentObject* tip = Tip.getValue(); Base::Console().Error(" Tip: %s\n", (tip == NULL) ? "None" : tip->getNameInDocument()); - std::vector model = Model.getValues(); - Base::Console().Error(" Model:\n"); + std::vector model = Group.getValues(); + Base::Console().Error(" Group:\n"); for (std::vector::const_iterator m = model.begin(); m != model.end(); m++) { if (*m == NULL) continue; Base::Console().Error(" %s", (*m)->getNameInDocument()); @@ -422,14 +425,6 @@ void Body::onSettingDocument() { Part::BodyBase::onSettingDocument(); } -void Body::removeModelFromDocument() { - //delete all child objects if needed - std::set grp ( Model.getValues().begin (), Model.getValues().end() ); - for (auto obj : grp) { - this->getDocument()->remObject(obj->getNameInDocument()); - } -} - void Body::onChanged (const App::Property* prop) { if ( prop == &BaseFeature ) { App::DocumentObject *baseFeature = BaseFeature.getValue(); @@ -443,24 +438,6 @@ void Body::onChanged (const App::Property* prop) { Part::BodyBase::onChanged ( prop ); } -App::Origin *Body::getOrigin () const { - App::DocumentObject *originObj = Origin.getValue (); - - if ( !originObj ) { - std::stringstream err; - err << "Can't find Origin for \"" << getNameInDocument () << "\""; - throw Base::Exception ( err.str().c_str () ); - - } else if (! originObj->isDerivedFrom ( App::Origin::getClassTypeId() ) ) { - std::stringstream err; - err << "Bad object \"" << originObj->getNameInDocument () << "\"(" << originObj->getTypeId().getName() - << ") linked to the Origin of \"" << getNameInDocument () << "\""; - throw Base::Exception ( err.str().c_str () ); - } else { - return static_cast ( originObj ); - } -} - void Body::setupObject () { // NOTE: the code shared with App::OriginGroup App::Document *doc = getDocument (); diff --git a/src/Mod/PartDesign/App/Body.h b/src/Mod/PartDesign/App/Body.h index 0ad429eb9e..c56ddcb4aa 100644 --- a/src/Mod/PartDesign/App/Body.h +++ b/src/Mod/PartDesign/App/Body.h @@ -68,7 +68,7 @@ public: * Add the feature into the body at the current insert point. * The insertion poin is the before next solid after the Tip feature */ - void addFeature(App::DocumentObject* feature); + virtual void addObject(App::DocumentObject*) override; /** * Insert the feature into the body after the given feature. @@ -81,13 +81,10 @@ public: * * @note the methode doesn't modifies the Tip unlike addFeature() */ - void insertFeature(App::DocumentObject* feature, App::DocumentObject* target, bool after=false); + void insertObject(App::DocumentObject* feature, App::DocumentObject* target, bool after=false); /// Remove the feature from the body - void removeFeature(App::DocumentObject* feature); - - /// Delets all the objects linked to the model. - void removeModelFromDocument(); + virtual void removeObject(DocumentObject* obj) override; /** * Checks if the given document object lays after the current insert point @@ -110,6 +107,7 @@ public: * all features derived from PartDesign::Feature and Part::Datum and sketches */ static bool isAllowed(const App::DocumentObject* f); + virtual bool allowObject(DocumentObject* f) {return isAllowed(f);}; /** * Return the body which this feature belongs too, or NULL @@ -117,13 +115,8 @@ public: */ static Body *findBodyOf(const App::DocumentObject* feature); - /// Returns the origin link or throws an exception - App::Origin *getOrigin () const; - PyObject *getPyObject(void); - /// Origin linked to the property, please use getOrigin () to access it - App::PropertyLink Origin; protected: virtual void onSettingDocument(); diff --git a/src/Mod/PartDesign/App/BodyPyImp.cpp b/src/Mod/PartDesign/App/BodyPyImp.cpp index 1c80173260..dec68ce459 100644 --- a/src/Mod/PartDesign/App/BodyPyImp.cpp +++ b/src/Mod/PartDesign/App/BodyPyImp.cpp @@ -68,7 +68,7 @@ PyObject* BodyPy::addFeature(PyObject *args) Body* body = this->getBodyPtr(); try { - body->addFeature(feature); + body->addObject(feature); } catch (Base::Exception& e) { PyErr_SetString(PyExc_SystemError, e.what()); return 0; @@ -107,7 +107,7 @@ PyObject* BodyPy::insertFeature(PyObject *args) Body* body = this->getBodyPtr(); try { - body->insertFeature(feature, target, after); + body->insertObject(feature, target, after); } catch (Base::Exception& e) { PyErr_SetString(PyExc_SystemError, e.what()); return 0; @@ -126,7 +126,7 @@ PyObject* BodyPy::removeFeature(PyObject *args) Body* body = this->getBodyPtr(); try { - body->removeFeature(feature); + body->removeObject(feature); } catch (Base::Exception& e) { PyErr_SetString(PyExc_SystemError, e.what()); return 0; @@ -140,7 +140,7 @@ PyObject* BodyPy::removeModelFromDocument(PyObject *args) if (!PyArg_ParseTuple(args, "")) return 0; - getBodyPtr()->removeModelFromDocument(); + getBodyPtr()->removeObjectsFromDocument(); Py_Return; } diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index 5cf7f4af6b..9afa179886 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -385,7 +385,7 @@ void CmdPartDesignNewSketch::activated(int iMsg) supportString = std::string("(App.activeDocument().") + obj->getNameInDocument() + ", '')"; } - if (!pcActiveBody->hasFeature(obj)) { + if (!pcActiveBody->hasObject(obj)) { if ( !obj->isDerivedFrom ( App::Plane::getClassTypeId() ) ) { // TODO check here if the plane associated with right part/body (2015-09-01, Fat-Zer) @@ -409,7 +409,7 @@ void CmdPartDesignNewSketch::activated(int iMsg) auto copy = PartDesignGui::TaskFeaturePick::makeCopy(obj, sub, dlg.radioIndependent->isChecked()); if(pcActiveBody) - pcActiveBody->addFeature(copy); + pcActiveBody->addObject(copy); else if (pcActivePart) pcActivePart->addObject(copy); @@ -466,7 +466,7 @@ void CmdPartDesignNewSketch::activated(int iMsg) for (auto plane: datumPlanes) { planes.push_back ( plane ); // Check whether this plane belongs to the active body - if ( pcActiveBody && pcActiveBody->hasFeature(plane) ) { + if ( pcActiveBody && pcActiveBody->hasObject(plane) ) { if ( !pcActiveBody->isAfterInsertPoint ( plane ) ) { validPlanes++; status.push_back(PartDesignGui::TaskFeaturePick::validFeature); @@ -640,7 +640,7 @@ unsigned validateSketches(std::vector& sketches, status.push_back(PartDesignGui::TaskFeaturePick::otherPart); continue; } - } else if (!pcActiveBody->hasFeature(*s)) { + } else if (!pcActiveBody->hasObject(*s)) { // Check whether this plane belongs to a body of the same part PartDesign::Body* b = PartDesign::Body::findBodyOf(*s); if(!b) @@ -807,7 +807,7 @@ void prepareProfileBased(Gui::Command* cmd, const std::string& which, auto copy = PartDesignGui::TaskFeaturePick::makeCopy(sketches[0], "", dlg.radioIndependent->isChecked()); auto oBody = PartDesignGui::getBodyFor(sketches[0], false); if(oBody) - pcActiveBody->addFeature(copy); + pcActiveBody->addObject(copy); else pcActivePart->addObject(copy); diff --git a/src/Mod/PartDesign/Gui/CommandBody.cpp b/src/Mod/PartDesign/Gui/CommandBody.cpp index 322fb0386c..0867cebf38 100644 --- a/src/Mod/PartDesign/Gui/CommandBody.cpp +++ b/src/Mod/PartDesign/Gui/CommandBody.cpp @@ -742,7 +742,7 @@ void CmdPartDesignMoveFeatureInTree::activated(int iMsg) if ( body ) { bodyBase= body->BaseFeature.getValue(); for ( auto feat: features ) { - if ( !body->hasFeature ( feat ) ) { + if ( !body->hasObject ( feat ) ) { allFeaturesFromSameBody = false; break; } @@ -760,7 +760,7 @@ void CmdPartDesignMoveFeatureInTree::activated(int iMsg) } // Create a list of all features in this body - const std::vector & model = body->Model.getValues(); + const std::vector & model = body->Group.getValues(); // Ask user to select the target feature bool ok; diff --git a/src/Mod/PartDesign/Gui/ReferenceSelection.cpp b/src/Mod/PartDesign/Gui/ReferenceSelection.cpp index 71ad246428..f05ccb0dee 100644 --- a/src/Mod/PartDesign/Gui/ReferenceSelection.cpp +++ b/src/Mod/PartDesign/Gui/ReferenceSelection.cpp @@ -115,7 +115,7 @@ bool ReferenceSelection::allow(App::Document* pDoc, App::DocumentObject* pObj, c if (!body) { // Allow selecting Part::Datum features from the active Body return false; - } else if (!allowOtherBody && !body->hasFeature(pObj)) { + } else if (!allowOtherBody && !body->hasObject(pObj)) { return false; } @@ -229,7 +229,7 @@ void getReferencedSelection(const App::DocumentObject* thisObj, const Gui::Selec auto copy = PartDesignGui::TaskFeaturePick::makeCopy(selObj, subname, dlg.radioIndependent->isChecked()); if(selBody) - body->addFeature(copy); + body->addObject(copy); else pcActivePart->addObject(copy); diff --git a/src/Mod/PartDesign/Gui/TaskDatumParameters.cpp b/src/Mod/PartDesign/Gui/TaskDatumParameters.cpp index 46a8eed0ce..8d27dfddd2 100644 --- a/src/Mod/PartDesign/Gui/TaskDatumParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskDatumParameters.cpp @@ -136,7 +136,7 @@ bool TaskDlgDatumParameters::accept() { //the user has to decide which option we should take if external references are used bool ext = false; for(App::DocumentObject* obj : pcDatum->Support.getValues()) { - if(!pcActiveBody->hasFeature(obj) && !pcActiveBody->getOrigin()->hasObject(obj)) + if(!pcActiveBody->hasObject(obj) && !pcActiveBody->getOrigin()->hasObject(obj)) ext = true; } if(ext) { @@ -155,7 +155,7 @@ bool TaskDlgDatumParameters::accept() { int index = 0; for(App::DocumentObject* obj : pcDatum->Support.getValues()) { - if(!pcActiveBody->hasFeature(obj) && !pcActiveBody->getOrigin()->hasObject(obj)) { + if(!pcActiveBody->hasObject(obj) && !pcActiveBody->getOrigin()->hasObject(obj)) { objs.push_back(PartDesignGui::TaskFeaturePick::makeCopy(obj, subs[index], dlg.radioIndependent->isChecked())); copies.push_back(objs.back()); subs[index] = ""; @@ -176,7 +176,7 @@ bool TaskDlgDatumParameters::accept() { //we need to add the copied features to the body after the command action, as otherwise freecad crashs unexplainable for(auto obj : copies) { if(pcActiveBody) - pcActiveBody->addFeature(obj); + pcActiveBody->addObject(obj); else if (pcActivePart) pcActivePart->addObject(obj); } diff --git a/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp b/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp index 8c28822d67..86758368b2 100644 --- a/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp +++ b/src/Mod/PartDesign/Gui/TaskFeaturePick.cpp @@ -235,17 +235,17 @@ std::vector TaskFeaturePick::buildFeatures() auto copy = makeCopy(obj, "", ui->radioIndependent->isChecked()); if (*st == otherBody) { - activeBody->addFeature(copy); + activeBody->addObject(copy); } else if (*st == otherPart) { auto oBody = PartDesignGui::getBodyFor(obj, false); if (!oBody) activePart->addObject(copy); else - activeBody->addFeature(copy); + activeBody->addObject(copy); } else if (*st == notInBody) { - activeBody->addFeature(copy); + activeBody->addObject(copy); // doesn't supposed to get here anything but sketch but to be on the safe side better to check if (copy->getTypeId().isDerivedFrom(Sketcher::SketchObject::getClassTypeId())) { Sketcher::SketchObject *sketch = static_cast(copy); diff --git a/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp b/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp index c991d1b87c..663fd0f47d 100644 --- a/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp @@ -720,13 +720,13 @@ bool TaskDlgPipeParameters::accept() std::vector copies; bool ext = false; - if(!pcActiveBody->hasFeature(pcPipe->Spine.getValue()) && !pcActiveBody->getOrigin()->hasObject(pcPipe->Spine.getValue())) + if(!pcActiveBody->hasObject(pcPipe->Spine.getValue()) && !pcActiveBody->getOrigin()->hasObject(pcPipe->Spine.getValue())) ext = true; - else if(!pcActiveBody->hasFeature(pcPipe->AuxillerySpine.getValue()) && !pcActiveBody->getOrigin()->hasObject(pcPipe->AuxillerySpine.getValue())) + else if(!pcActiveBody->hasObject(pcPipe->AuxillerySpine.getValue()) && !pcActiveBody->getOrigin()->hasObject(pcPipe->AuxillerySpine.getValue())) ext = true; else { for(App::DocumentObject* obj : pcPipe->Sections.getValues()) { - if(!pcActiveBody->hasFeature(obj) && !pcActiveBody->getOrigin()->hasObject(obj)) + if(!pcActiveBody->hasObject(obj) && !pcActiveBody->getOrigin()->hasObject(obj)) ext = true; } } @@ -741,12 +741,12 @@ bool TaskDlgPipeParameters::accept() return false; else if(!dlg.radioXRef->isChecked()) { - if(!pcActiveBody->hasFeature(pcPipe->Spine.getValue()) && !pcActiveBody->getOrigin()->hasObject(pcPipe->Spine.getValue())) { + if(!pcActiveBody->hasObject(pcPipe->Spine.getValue()) && !pcActiveBody->getOrigin()->hasObject(pcPipe->Spine.getValue())) { pcPipe->Spine.setValue(PartDesignGui::TaskFeaturePick::makeCopy(pcPipe->Spine.getValue(), "", dlg.radioIndependent->isChecked()), pcPipe->Spine.getSubValues()); copies.push_back(pcPipe->Spine.getValue()); } - else if(!pcActiveBody->hasFeature(pcPipe->AuxillerySpine.getValue()) && !pcActiveBody->getOrigin()->hasObject(pcPipe->AuxillerySpine.getValue())){ + else if(!pcActiveBody->hasObject(pcPipe->AuxillerySpine.getValue()) && !pcActiveBody->getOrigin()->hasObject(pcPipe->AuxillerySpine.getValue())){ pcPipe->AuxillerySpine.setValue(PartDesignGui::TaskFeaturePick::makeCopy(pcPipe->AuxillerySpine.getValue(), "", dlg.radioIndependent->isChecked()), pcPipe->AuxillerySpine.getSubValues()); copies.push_back(pcPipe->AuxillerySpine.getValue()); @@ -756,7 +756,7 @@ bool TaskDlgPipeParameters::accept() int index = 0; for(App::DocumentObject* obj : pcPipe->Sections.getValues()) { - if(!pcActiveBody->hasFeature(obj) && !pcActiveBody->getOrigin()->hasObject(obj)) { + if(!pcActiveBody->hasObject(obj) && !pcActiveBody->getOrigin()->hasObject(obj)) { objs.push_back(PartDesignGui::TaskFeaturePick::makeCopy(obj, "", dlg.radioIndependent->isChecked())); copies.push_back(objs.back()); } @@ -781,7 +781,7 @@ bool TaskDlgPipeParameters::accept() for(auto obj : copies) { //Dead code: pcActiveBody was previously used without checking for null, so it won't be null here either. //if(pcActiveBody) - pcActiveBody->addFeature(obj); + pcActiveBody->addObject(obj); //else if (pcActivePart) // pcActivePart->addObject(obj); } diff --git a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp index 3f2607c21f..af21fdf5e2 100644 --- a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp @@ -147,7 +147,7 @@ const QByteArray TaskSketchBasedParameters::onFaceName(const QString& text) // everything is OK (we assume a Part can only have exactly 3 App::Plane objects located at the base of the feature tree) return QByteArray(); } else if (obj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId())) { - if (!activeBody->hasFeature(obj)) + if (!activeBody->hasObject(obj)) return QByteArray(); return QByteArray(); } else { diff --git a/src/Mod/PartDesign/Gui/Utils.cpp b/src/Mod/PartDesign/Gui/Utils.cpp index 18f3ed8a50..09d62ad415 100644 --- a/src/Mod/PartDesign/Gui/Utils.cpp +++ b/src/Mod/PartDesign/Gui/Utils.cpp @@ -88,7 +88,7 @@ PartDesign::Body *getBodyFor(const App::DocumentObject* obj, bool messageIfNot) return nullptr; PartDesign::Body * rv = getBody( /*messageIfNot =*/ false); - if(rv && rv->hasFeature(obj)) + if(rv && rv->hasObject(obj)) return rv; rv = PartDesign::Body::findBodyOf(obj); diff --git a/src/Mod/PartDesign/Gui/ViewProvider.cpp b/src/Mod/PartDesign/Gui/ViewProvider.cpp index d6fa1302d8..dd525e2e5d 100644 --- a/src/Mod/PartDesign/Gui/ViewProvider.cpp +++ b/src/Mod/PartDesign/Gui/ViewProvider.cpp @@ -179,7 +179,7 @@ void ViewProvider::onChanged(const App::Property* prop) { if(body) { //hide all features in the body other than this object - for(App::DocumentObject* obj : body->Model.getValues()) { + for(App::DocumentObject* obj : body->Group.getValues()) { if(obj->isDerivedFrom(PartDesign::Feature::getClassTypeId()) && obj != getObject()) { Gui::ViewProvider* vp = Gui::Application::Instance->activeDocument()->getViewProvider(obj); diff --git a/src/Mod/PartDesign/Gui/ViewProviderBody.cpp b/src/Mod/PartDesign/Gui/ViewProviderBody.cpp index 831a6e7e98..c5a99e69e5 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderBody.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderBody.cpp @@ -183,7 +183,7 @@ bool ViewProviderBody::doubleClicked(void) std::vector ViewProviderBody::claimChildren(void)const { PartDesign::Body* body= static_cast ( getObject () ); - const std::vector &model = body->Model.getValues (); + const std::vector &model = body->Group.getValues (); std::set outSet; //< set of objects not to claim (childrens of childrens) // search for objects handled (claimed) by the features @@ -220,7 +220,7 @@ std::vector ViewProviderBody::claimChildren3D(void)const { PartDesign::Body* body = static_cast(getObject()); - const std::vector & features = body->Model.getValues(); + const std::vector & features = body->Group.getValues(); std::vector rv; @@ -248,7 +248,7 @@ std::vector ViewProviderBody::claimChildren3D(void)const // bool active = body->IsActive.getValue(); // //Base::Console().Error("Body is %s\n", active ? "active" : "inactive"); // ActiveGuiDoc->signalHighlightObject(*this, Gui::Blue, active); -// std::vector features = body->Model.getValues(); +// std::vector features = body->Group.getValues(); // bool highlight = true; // App::DocumentObject* tip = body->Tip.getValue(); // for (std::vector::const_iterator f = features.begin(); f != features.end(); f++) { @@ -264,7 +264,7 @@ std::vector ViewProviderBody::claimChildren3D(void)const bool ViewProviderBody::onDelete ( const std::vector &) { // TODO May be do it conditionally? (2015-09-05, Fat-Zer) Gui::Command::doCommand(Gui::Command::Doc, - "App.getDocument(\"%s\").getObject(\"%s\").removeModelFromDocument()" + "App.getDocument(\"%s\").getObject(\"%s\").removeGroupFromDocument()" ,getObject()->getDocument()->getName(), getObject()->getNameInDocument()); return true; } @@ -273,7 +273,7 @@ void ViewProviderBody::updateData(const App::Property* prop) { PartDesign::Body* body = static_cast(getObject()); - if (prop == &body->Model || prop == &body->BaseFeature) { + if (prop == &body->Group || prop == &body->BaseFeature) { // update sizes of origins and datums updateOriginDatumSize (); //ensure all model features are in visual body mode @@ -298,7 +298,7 @@ void ViewProviderBody::slotChangedObjectApp ( const App::DocumentObject& obj, co } PartDesign::Body *body = static_cast ( getObject() ); - if ( body && body->hasFeature (&obj ) ) { + if ( body && body->hasObject (&obj ) ) { updateOriginDatumSize (); } } @@ -320,7 +320,7 @@ void ViewProviderBody::slotChangedObjectGui ( PartDesign::Body *body = static_cast ( getObject() ); App::DocumentObject *obj = vp.getObject (); - if ( body && obj && body->hasFeature ( obj ) ) { + if ( body && obj && body->hasObject ( obj ) ) { updateOriginDatumSize (); } } @@ -436,7 +436,7 @@ void ViewProviderBody::unifyVisualProperty(const App::Property* prop) { Gui::Document *gdoc = Gui::Application::Instance->getDocument ( pcObject->getDocument() ) ; PartDesign::Body *body = static_cast ( getObject() ); - auto features = body->Model.getValues(); + auto features = body->Group.getValues(); for(auto feature : features) { if(!feature->isDerivedFrom(PartDesign::Feature::getClassTypeId())) @@ -453,7 +453,7 @@ void ViewProviderBody::setVisualBodyMode(bool bodymode) { Gui::Document *gdoc = Gui::Application::Instance->getDocument ( pcObject->getDocument() ) ; PartDesign::Body *body = static_cast ( getObject() ); - auto features = body->Model.getValues(); + auto features = body->Group.getValues(); for(auto feature : features) { if(!feature->isDerivedFrom(PartDesign::Feature::getClassTypeId())) diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index 16729ac427..c413ba5558 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -193,7 +193,7 @@ void Workbench::setupContextMenu(const char* recipient, Gui::MenuItem* item) con } // if all at lest one selected feature doesn't belongs to the same body // disable the menu entry - if ( addMoveFeatureInTree && !body->hasFeature ( sel.pObject ) ) { + if ( addMoveFeatureInTree && !body->hasObject ( sel.pObject ) ) { addMoveFeatureInTree = false; } From b4a569e013860493a3361cd2c05db3ac0db885db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Thu, 22 Dec 2016 17:18:50 +0100 Subject: [PATCH 057/144] PartDesign: Change body viewprovider to group --- src/Mod/Part/App/BodyBase.cpp | 2 + src/Mod/PartDesign/App/Body.cpp | 16 -------- src/Mod/PartDesign/Gui/ViewProviderBody.cpp | 45 ++++++++++++++++----- src/Mod/PartDesign/Gui/ViewProviderBody.h | 11 +++-- 4 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/Mod/Part/App/BodyBase.cpp b/src/Mod/Part/App/BodyBase.cpp index bee0fe71af..789fe43c81 100644 --- a/src/Mod/Part/App/BodyBase.cpp +++ b/src/Mod/Part/App/BodyBase.cpp @@ -41,6 +41,8 @@ BodyBase::BodyBase() { ADD_PROPERTY(Tip , (0) ); ADD_PROPERTY(BaseFeature , (0) ); + + App::OriginGroupExtension::initExtension(this); } BodyBase* BodyBase::findBodyOf(const App::DocumentObject* f) diff --git a/src/Mod/PartDesign/App/Body.cpp b/src/Mod/PartDesign/App/Body.cpp index 81fb3239e0..861eadc44e 100644 --- a/src/Mod/PartDesign/App/Body.cpp +++ b/src/Mod/PartDesign/App/Body.cpp @@ -439,26 +439,10 @@ void Body::onChanged (const App::Property* prop) { } void Body::setupObject () { - // NOTE: the code shared with App::OriginGroup - App::Document *doc = getDocument (); - - std::string objName = std::string ( getNameInDocument() ).append ( "Origin" ); - - App::DocumentObject *originObj = doc->addObject ( "App::Origin", objName.c_str () ); - - assert ( originObj && originObj->isDerivedFrom ( App::Origin::getClassTypeId () ) ); - Origin.setValue ( originObj ); - Part::BodyBase::setupObject (); } void Body::unsetupObject () { - App::DocumentObject *origin = Origin.getValue (); - - if (origin && !origin->isDeleting ()) { - origin->getDocument ()->remObject (origin->getNameInDocument()); - } - Part::BodyBase::unsetupObject (); } diff --git a/src/Mod/PartDesign/Gui/ViewProviderBody.cpp b/src/Mod/PartDesign/Gui/ViewProviderBody.cpp index c5a99e69e5..4ec62cbcea 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderBody.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderBody.cpp @@ -61,22 +61,20 @@ using namespace PartDesignGui; const char* PartDesignGui::ViewProviderBody::BodyModeEnum[] = {"Through","Tip",NULL}; -PROPERTY_SOURCE(PartDesignGui::ViewProviderBody,PartGui::ViewProviderPart) +PROPERTY_SOURCE_WITH_EXTENSIONS(PartDesignGui::ViewProviderBody,PartGui::ViewProviderPart) ViewProviderBody::ViewProviderBody() { ADD_PROPERTY(DisplayModeBody,((long)0)); DisplayModeBody.setEnums(BodyModeEnum); - - pcBodyChildren = new SoSeparator(); - pcBodyChildren->ref(); sPixmap = "PartDesign_Body_Tree.svg"; + + Gui::ViewProviderOriginGroupExtension::initExtension(this); } ViewProviderBody::~ViewProviderBody() { - pcBodyChildren->unref (); connectChangedObjectApp.disconnect(); connectChangedObjectGui.disconnect(); } @@ -85,9 +83,9 @@ void ViewProviderBody::attach(App::DocumentObject *pcFeat) { // call parent attach method ViewProviderPart::attach(pcFeat); - - addDisplayMaskMode(pcBodyChildren, "Through"); - setDisplayMaskMode("Through"); + + //set default display mode + onChanged(&DisplayModeBody); App::Document *adoc = pcObject->getDocument (); Gui::Document *gdoc = Gui::Application::Instance->getDocument ( adoc ) ; @@ -217,26 +215,47 @@ std::vector ViewProviderBody::claimChildren(void)const std::vector ViewProviderBody::claimChildren3D(void)const + { + PartDesign::Body* body = static_cast(getObject()); + + const std::vector & features = body->Group.getValues(); + + std::vector rv; + + if ( body->Origin.getValue() ) { // Add origin + rv.push_back (body->Origin.getValue()); + } + if ( body->BaseFeature.getValue() ) { // Add Base Feature + rv.push_back (body->BaseFeature.getValue()); + } + + // Add all other stuff + std::copy (features.begin(), features.end(), std::back_inserter (rv) ); + + return rv; + } + + // TODO To be deleted (2015-09-08, Fat-Zer) //void ViewProviderBody::updateTree() //{ @@ -408,7 +427,7 @@ void ViewProviderBody::onChanged(const App::Property* prop) { ViewProvider::setOverrideMode("As Is"); overrideMode = mode; } - setDisplayMaskMode("Through"); + setDisplayMaskMode("Group"); } else { if(getOverrideMode() == "As Is") @@ -463,3 +482,11 @@ void ViewProviderBody::setVisualBodyMode(bool bodymode) { } } +std::vector< std::string > ViewProviderBody::getDisplayModes(void) const { + + //we get all dislay modes and remove the "Group" mode, as this is what we use for "Through" + //body display mode + std::vector< std::string > modes = ViewProviderPart::getDisplayModes(); + modes.erase(modes.begin()); + return modes; +} diff --git a/src/Mod/PartDesign/Gui/ViewProviderBody.h b/src/Mod/PartDesign/Gui/ViewProviderBody.h index f6fcc7f524..00e5aa5c06 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderBody.h +++ b/src/Mod/PartDesign/Gui/ViewProviderBody.h @@ -25,6 +25,7 @@ #define PARTGUI_ViewProviderBody_H #include +#include #include class SoGroup; @@ -39,10 +40,10 @@ namespace PartDesignGui { * If the Body is not active it shows only the result shape (tip). * \author jriegel */ -class PartDesignGuiExport ViewProviderBody : public PartGui::ViewProviderPart +class PartDesignGuiExport ViewProviderBody : public PartGui::ViewProviderPart, public Gui::ViewProviderOriginGroupExtension { Q_DECLARE_TR_FUNCTIONS(PartDesignGui::ViewProviderBody) - PROPERTY_HEADER(PartDesignGui::ViewProviderBody); + PROPERTY_HEADER_WITH_EXTENSIONS(PartDesignGui::ViewProviderBody); public: /// constructor @@ -59,8 +60,9 @@ public: virtual std::vector claimChildren(void)const; // returns the root node where the children gets collected(3D) - virtual SoGroup* getChildRoot(void) const {return pcBodyChildren;} virtual std::vector claimChildren3D(void)const; + + virtual std::vector< std::string > getDisplayModes(void) const; virtual void setDisplayMode(const char* ModeName); virtual void setOverrideMode(const std::string& mode); @@ -89,9 +91,6 @@ protected: /// Set Feature viewprovider into visual body mode void setVisualBodyMode(bool bodymode); private: - /// group used to store children collected by claimChildren3D() in the through (edit) mode. - SoGroup *pcBodyChildren; - static const char* BodyModeEnum[]; boost::signals::connection connectChangedObjectApp; From d2764a3c7a16bd2fd221de6cbfc9395fb0516c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Thu, 22 Dec 2016 21:26:54 +0100 Subject: [PATCH 058/144] PartDesign: Adopt python interface to body group --- src/Mod/PartDesign/App/Body.cpp | 2 +- src/Mod/PartDesign/App/Body.h | 2 +- src/Mod/PartDesign/App/BodyPy.xml | 21 ++------ src/Mod/PartDesign/App/BodyPyImp.cpp | 56 +-------------------- src/Mod/PartDesign/FeatureHole/TaskHole.py | 10 ++-- src/Mod/PartDesign/Gui/Command.cpp | 14 +++--- src/Mod/PartDesign/Gui/CommandBody.cpp | 14 +++--- src/Mod/PartDesign/Gui/CommandPrimitive.cpp | 4 +- src/Mod/PartDesign/Gui/Utils.cpp | 2 +- src/Mod/PartDesign/Gui/ViewProviderBody.cpp | 2 +- src/Mod/PartDesign/Gui/Workbench.cpp | 2 +- src/Mod/PartDesign/TestPartDesignGui.py | 10 ++-- 12 files changed, 35 insertions(+), 104 deletions(-) diff --git a/src/Mod/PartDesign/App/Body.cpp b/src/Mod/PartDesign/App/Body.cpp index 861eadc44e..3e4212e965 100644 --- a/src/Mod/PartDesign/App/Body.cpp +++ b/src/Mod/PartDesign/App/Body.cpp @@ -62,7 +62,7 @@ Body::Body() { // Note: The following code will catch Python Document::removeObject() modifications. If the object removed is // a member of the Body::Group, then it will be automatically removed from the Group property which triggers the // following two methods -// But since we require the Python user to call both Document::addObject() and Body::addFeature(), we should +// But since we require the Python user to call both Document::addObject() and Body::addObject(), we should // also require calling both Document::removeObject and Body::removeFeature() in order to be consistent void Body::onBeforeChange(const App::Property *prop) { diff --git a/src/Mod/PartDesign/App/Body.h b/src/Mod/PartDesign/App/Body.h index c56ddcb4aa..a73ab7003a 100644 --- a/src/Mod/PartDesign/App/Body.h +++ b/src/Mod/PartDesign/App/Body.h @@ -79,7 +79,7 @@ public: * and into the begin if where is InsertAfter. * @param after if true insert the feature after the target. Default is false. * - * @note the methode doesn't modifies the Tip unlike addFeature() + * @note the methode doesn't modifies the Tip unlike addObject() */ void insertObject(App::DocumentObject* feature, App::DocumentObject* target, bool after=false); diff --git a/src/Mod/PartDesign/App/BodyPy.xml b/src/Mod/PartDesign/App/BodyPy.xml index 9569b1acc6..b4889eb42a 100644 --- a/src/Mod/PartDesign/App/BodyPy.xml +++ b/src/Mod/PartDesign/App/BodyPy.xml @@ -13,14 +13,9 @@ PartDesign body class - + - addFeature(feat) - Add the given feature after the current Tip feature - - - - - insertFeatureAfter(feature, target, after=False) + insertObject(feature, target, after=False) Insert the feature into the body after the given feature. @param feature The feature to insert into the body @@ -29,19 +24,9 @@ and into the begin if where is InsertAfter. @param after if true insert the feature after the target. Default is false. - @note the methode doesn't modifies the Tip unlike addFeature() + @note the methode doesn't modifies the Tip unlike addObject() - - - removeFeature(feat) - Remove the given feature from the Body - - - - - Delets all the objects linked to the model. - - diff --git a/src/Mod/PartDesign/App/BodyPyImp.cpp b/src/Mod/PartDesign/App/BodyPyImp.cpp index dec68ce459..1650bb8fde 100644 --- a/src/Mod/PartDesign/App/BodyPyImp.cpp +++ b/src/Mod/PartDesign/App/BodyPyImp.cpp @@ -52,32 +52,7 @@ int BodyPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) return 0; } -PyObject* BodyPy::addFeature(PyObject *args) -{ - PyObject* featurePy; - if (!PyArg_ParseTuple(args, "O!", &(App::DocumentObjectPy::Type), &featurePy)) - return 0; - - App::DocumentObject* feature = static_cast(featurePy)->getDocumentObjectPtr(); - - if (!Body::isAllowed(feature)) { - PyErr_SetString(PyExc_SystemError, "Only PartDesign features, datum features and sketches can be inserted into a Body"); - return 0; - } - - Body* body = this->getBodyPtr(); - - try { - body->addObject(feature); - } catch (Base::Exception& e) { - PyErr_SetString(PyExc_SystemError, e.what()); - return 0; - } - - Py_Return; -} - -PyObject* BodyPy::insertFeature(PyObject *args) +PyObject* BodyPy::insertObject(PyObject *args) { PyObject* featurePy; PyObject* targetPy; @@ -115,32 +90,3 @@ PyObject* BodyPy::insertFeature(PyObject *args) Py_Return; } - -PyObject* BodyPy::removeFeature(PyObject *args) -{ - PyObject* featurePy; - if (!PyArg_ParseTuple(args, "O!", &(App::DocumentObjectPy::Type), &featurePy)) - return 0; - - App::DocumentObject* feature = static_cast(featurePy)->getDocumentObjectPtr(); - Body* body = this->getBodyPtr(); - - try { - body->removeObject(feature); - } catch (Base::Exception& e) { - PyErr_SetString(PyExc_SystemError, e.what()); - return 0; - } - - Py_Return; -} - -PyObject* BodyPy::removeModelFromDocument(PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) - return 0; - - getBodyPtr()->removeObjectsFromDocument(); - Py_Return; -} - diff --git a/src/Mod/PartDesign/FeatureHole/TaskHole.py b/src/Mod/PartDesign/FeatureHole/TaskHole.py index 1f5a8c0652..db05f339b1 100644 --- a/src/Mod/PartDesign/FeatureHole/TaskHole.py +++ b/src/Mod/PartDesign/FeatureHole/TaskHole.py @@ -54,18 +54,18 @@ class TaskHole: sketch = groove.Sketch plane = sketch.Support[0] axis = plane.References[0][0] - body.removeFeature(self.feature) + body.removeObject(self.feature) document.removeObject(self.feature.Name) - body.removeFeature(groove) + body.removeObject(groove) document.removeObject(groove.Name) - body.removeFeature(sketch) + body.removeObject(sketch) try: document.removeObject(sketch.Name) except: pass # This always throws an exception: "Sketch support has been deleted" from SketchObject::execute() - body.removeFeature(plane) + body.removeObject(plane) document.removeObject(plane.Name) - body.removeFeature(axis) + body.removeObject(axis) document.removeObject(axis.Name) FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog(self) diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index 9afa179886..0c5e398cf1 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -125,7 +125,7 @@ void UnifiedDatumCommand(Gui::Command &cmd, Base::Type type, std::string name) } } if (pcActiveBody) { - cmd.doCommand(Gui::Command::Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + cmd.doCommand(Gui::Command::Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", pcActiveBody->getNameInDocument(), FeatName.c_str()); } cmd.doCommand(Gui::Command::Doc,"App.activeDocument().recompute()"); // recompute the feature based on its references @@ -278,7 +278,7 @@ void CmdPartDesignShapeBinder::activated(int iMsg) doCommand(Gui::Command::Doc,"App.activeDocument().%s.Support = %s", FeatName.c_str(), support.getPyReprString().c_str()); } - doCommand(Gui::Command::Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + doCommand(Gui::Command::Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", pcActiveBody->getNameInDocument(), FeatName.c_str()); doCommand(Gui::Command::Doc,"App.activeDocument().recompute()"); // recompute the feature based on its references doCommand(Gui::Command::Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str()); @@ -429,7 +429,7 @@ void CmdPartDesignNewSketch::activated(int iMsg) doCommand(Doc,"App.activeDocument().addObject('Sketcher::SketchObject','%s')",FeatName.c_str()); doCommand(Doc,"App.activeDocument().%s.Support = %s",FeatName.c_str(),supportString.c_str()); doCommand(Doc,"App.activeDocument().%s.MapMode = '%s'",FeatName.c_str(),Attacher::AttachEngine::getModeName(Attacher::mmFlatFace).c_str()); - doCommand(Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + doCommand(Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", pcActiveBody->getNameInDocument(), FeatName.c_str()); doCommand(Gui,"App.activeDocument().recompute()"); // recompute the sketch placement based on its support //doCommand(Gui,"Gui.activeDocument().activeView().setCamera('%s')",cam.c_str()); @@ -521,7 +521,7 @@ void CmdPartDesignNewSketch::activated(int iMsg) Gui::Command::doCommand(Doc,"App.activeDocument().%s.Support = %s",FeatName.c_str(),supportString.c_str()); Gui::Command::doCommand(Doc,"App.activeDocument().%s.MapMode = '%s'",FeatName.c_str(),Attacher::AttachEngine::getModeName(Attacher::mmFlatFace).c_str()); Gui::Command::updateActive(); // Make sure the Support's Placement property is updated - Gui::Command::doCommand(Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + Gui::Command::doCommand(Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", pcActiveBody->getNameInDocument(), FeatName.c_str()); //doCommand(Gui,"Gui.activeDocument().activeView().setCamera('%s')",cam.c_str()); Gui::Command::doCommand(Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str()); @@ -584,13 +584,13 @@ void finishFeature(const Gui::Command* cmd, const std::string& FeatName, App::DocumentObject* lastSolidFeature = pcActiveBody->Tip.getValue(); if (!prevSolidFeature || prevSolidFeature == lastSolidFeature) { // If the previous feature not given or is the Tip add Feature after it. - cmd->doCommand(cmd->Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + cmd->doCommand(cmd->Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", pcActiveBody->getNameInDocument(), FeatName.c_str()); prevSolidFeature = lastSolidFeature; } else { // Insert the feature into the body after the given one. cmd->doCommand(cmd->Doc, - "App.activeDocument().%s.insertFeature(App.activeDocument().%s, App.activeDocument().%s, True)", + "App.activeDocument().%s.insertObject(App.activeDocument().%s, App.activeDocument().%s, True)", pcActiveBody->getNameInDocument(), FeatName.c_str(), prevSolidFeature->getNameInDocument()); } } @@ -1855,7 +1855,7 @@ void CmdPartDesignMultiTransform::activated(int iMsg) // Remove the Transformed feature from the Body if(pcActiveBody) - doCommand(Doc, "App.activeDocument().%s.removeFeature(App.activeDocument().%s)", + doCommand(Doc, "App.activeDocument().%s.removeObject(App.activeDocument().%s)", pcActiveBody->getNameInDocument(), trFeat->getNameInDocument()); // Create a MultiTransform feature and move the Transformed feature inside it diff --git a/src/Mod/PartDesign/Gui/CommandBody.cpp b/src/Mod/PartDesign/Gui/CommandBody.cpp index 0867cebf38..cfc4c23476 100644 --- a/src/Mod/PartDesign/Gui/CommandBody.cpp +++ b/src/Mod/PartDesign/Gui/CommandBody.cpp @@ -401,7 +401,7 @@ void CmdPartDesignMigrate::activated(int iMsg) PartDesign::ProfileBased *sketchBased = static_cast ( feature ); Part::Part2DObject *sketch = sketchBased->getVerifiedSketch( /*silent =*/ true); if ( sketch ) { - doCommand ( Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + doCommand ( Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", bodyName.c_str (), sketch->getNameInDocument() ); if ( sketch->isDerivedFrom ( Sketcher::SketchObject::getClassTypeId() ) ) { @@ -419,7 +419,7 @@ void CmdPartDesignMigrate::activated(int iMsg) } } } - doCommand ( Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + doCommand ( Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", bodyName.c_str (), feature->getNameInDocument() ); PartDesignGui::relinkToBody ( feature ); @@ -552,7 +552,7 @@ void CmdPartDesignDuplicateSelection::activated(int iMsg) for (auto feature : newFeatures) { if (PartDesign::Body::isAllowed(feature)) { - doCommand(Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + doCommand(Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", pcActiveBody->getNameInDocument(), feature->getNameInDocument()); doCommand(Gui,"Gui.activeDocument().hide(\"%s\")", feature->getNameInDocument()); } @@ -658,14 +658,14 @@ void CmdPartDesignMoveFeature::activated(int iMsg) // Remove from the source body if the feature belonged to a body if (source) { featureWasTip = (source->Tip.getValue() == feat); - doCommand(Doc,"App.activeDocument().%s.removeFeature(App.activeDocument().%s)", + doCommand(Doc,"App.activeDocument().%s.removeObject(App.activeDocument().%s)", source->getNameInDocument(), (feat)->getNameInDocument()); } App::DocumentObject* targetOldTip = target->Tip.getValue(); // Add to target body (always at the Tip) - doCommand(Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + doCommand(Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", target->getNameInDocument(), (feat)->getNameInDocument()); // Recompute to update the shape doCommand(Gui,"App.activeDocument().recompute()"); @@ -798,9 +798,9 @@ void CmdPartDesignMoveFeatureInTree::activated(int iMsg) // Remove and re-insert the feature to/from the Body // TODO if tip was moved the new position of tip is quite undetermined (2015-08-07, Fat-Zer) // TODO warn the user if we are moving an object to some place before the object's link (2015-08-07, Fat-Zer) - doCommand ( Doc,"App.activeDocument().%s.removeFeature(App.activeDocument().%s)", + doCommand ( Doc,"App.activeDocument().%s.removeObject(App.activeDocument().%s)", body->getNameInDocument(), feat->getNameInDocument() ); - doCommand ( Doc, "App.activeDocument().%s.insertFeature(App.activeDocument().%s, %s, True)", + doCommand ( Doc, "App.activeDocument().%s.insertObject(App.activeDocument().%s, %s, True)", body->getNameInDocument(), feat->getNameInDocument(), targetStr.c_str () ); } diff --git a/src/Mod/PartDesign/Gui/CommandPrimitive.cpp b/src/Mod/PartDesign/Gui/CommandPrimitive.cpp index 9c21237f20..feccacc70c 100644 --- a/src/Mod/PartDesign/Gui/CommandPrimitive.cpp +++ b/src/Mod/PartDesign/Gui/CommandPrimitive.cpp @@ -133,7 +133,7 @@ void CmdPrimtiveCompAdditive::activated(int iMsg) } - Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addFeature(App.activeDocument().%s)" + Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addObject(App.activeDocument().%s)" ,pcActiveBody->getNameInDocument(), FeatName.c_str()); Gui::Command::updateActive(); @@ -322,7 +322,7 @@ void CmdPrimtiveCompSubtractive::activated(int iMsg) FeatName.c_str()); } - Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addFeature(App.activeDocument().%s)" + Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addObject(App.activeDocument().%s)" ,pcActiveBody->getNameInDocument(), FeatName.c_str()); Gui::Command::updateActive(); diff --git a/src/Mod/PartDesign/Gui/Utils.cpp b/src/Mod/PartDesign/Gui/Utils.cpp index 09d62ad415..4e3740322a 100644 --- a/src/Mod/PartDesign/Gui/Utils.cpp +++ b/src/Mod/PartDesign/Gui/Utils.cpp @@ -221,7 +221,7 @@ void fixSketchSupport (Sketcher::SketchObject* sketch) Gui::Command::doCommand(Gui::Command::Doc,"App.activeDocument().%s.superPlacement.Base.z = %f", Datum.c_str(), offset); Gui::Command::doCommand(Gui::Command::Doc, - "App.activeDocument().%s.insertFeature(App.activeDocument().%s, App.activeDocument().%s)", + "App.activeDocument().%s.insertObject(App.activeDocument().%s, App.activeDocument().%s)", body->getNameInDocument(), Datum.c_str(), sketch->getNameInDocument()); Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.Support = (App.activeDocument().%s,[''])", diff --git a/src/Mod/PartDesign/Gui/ViewProviderBody.cpp b/src/Mod/PartDesign/Gui/ViewProviderBody.cpp index 4ec62cbcea..ee278b8409 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderBody.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderBody.cpp @@ -283,7 +283,7 @@ std::vector ViewProviderBody::claimChildren3D(void)const bool ViewProviderBody::onDelete ( const std::vector &) { // TODO May be do it conditionally? (2015-09-05, Fat-Zer) Gui::Command::doCommand(Gui::Command::Doc, - "App.getDocument(\"%s\").getObject(\"%s\").removeGroupFromDocument()" + "App.getDocument(\"%s\").getObject(\"%s\").removeObjectsFromDocument()" ,getObject()->getDocument()->getName(), getObject()->getNameInDocument()); return true; } diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index c413ba5558..d051d0bc0d 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -151,7 +151,7 @@ void Workbench::slotNewObject(const App::DocumentObject& obj) if ((obj.getDocument() == ActiveAppDoc) && (ActivePartObject != NULL)) { // Add the new object to the active Body // Note: Will this break Undo? But how else can we catch Edit->Duplicate selection? - Gui::Command::doCommand(Gui::Command::Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)", + Gui::Command::doCommand(Gui::Command::Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", ActivePartObject->getNameInDocument(), obj.getNameInDocument()); } } diff --git a/src/Mod/PartDesign/TestPartDesignGui.py b/src/Mod/PartDesign/TestPartDesignGui.py index ab158b91e9..cac04b6854 100644 --- a/src/Mod/PartDesign/TestPartDesignGui.py +++ b/src/Mod/PartDesign/TestPartDesignGui.py @@ -72,14 +72,14 @@ class PartDesignGuiTestCases(unittest.TestCase): self.BoxObj.Length=10.0 self.BoxObj.Width=10.0 self.BoxObj.Height=10.0 - self.BodySource.addFeature(self.BoxObj) + self.BodySource.addObject(self.BoxObj) App.ActiveDocument.recompute() self.Sketch = self.Doc.addObject('Sketcher::SketchObject','Sketch') self.Sketch.Support = (self.BoxObj, ('Face3',)) self.Sketch.MapMode = 'FlatFace' - self.BodySource.addFeature(self.Sketch) + self.BodySource.addObject(self.Sketch) geoList = [] geoList.append(Part.LineSegment(App.Vector(2.0,8.0,0),App.Vector(8.0,8.0,0))) @@ -108,7 +108,7 @@ class PartDesignGuiTestCases(unittest.TestCase): self.Pad.Midplane = 0 self.Pad.Offset = 0.000000 - self.BodySource.addFeature(self.Pad) + self.BodySource.addObject(self.Pad) self.Doc.recompute() Gui.SendMsgToActiveView("ViewFit") @@ -131,7 +131,7 @@ class PartDesignGuiTestCases(unittest.TestCase): self.Sketch = self.Doc.addObject('Sketcher::SketchObject','Sketch') self.Sketch.Support = (self.Doc.XY_Plane, ['']) self.Sketch.MapMode = 'FlatFace' - self.BodySource.addFeature(self.Sketch) + self.BodySource.addObject(self.Sketch) geoList = [] geoList.append(Part.LineSegment(App.Vector(-10.000000,10.000000,0),App.Vector(10.000000,10.000000,0))) @@ -160,7 +160,7 @@ class PartDesignGuiTestCases(unittest.TestCase): self.Pad.Midplane = 0 self.Pad.Offset = 0.000000 - self.BodySource.addFeature(self.Pad) + self.BodySource.addObject(self.Pad) self.Doc.recompute() Gui.SendMsgToActiveView("ViewFit") From 40cc2880e0a34e1fab3949373acc02cf2f6b3ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Thu, 22 Dec 2016 22:14:17 +0100 Subject: [PATCH 059/144] PartDesign: Fix test for new group body --- src/Mod/PartDesign/TestPartDesignGui.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/PartDesign/TestPartDesignGui.py b/src/Mod/PartDesign/TestPartDesignGui.py index cac04b6854..1ba4ce4b4c 100644 --- a/src/Mod/PartDesign/TestPartDesignGui.py +++ b/src/Mod/PartDesign/TestPartDesignGui.py @@ -120,8 +120,8 @@ class PartDesignGuiTestCases(unittest.TestCase): QtCore.QTimer.singleShot(500, cobj) Gui.runCommand('PartDesign_MoveFeature') #assert depenedencies of the Sketch - self.assertEqual(len(self.BodySource.Model), 3, "Source body feature count is wrong") - self.assertEqual(len(self.BodyTarget.Model), 0, "Target body feature count is wrong") + self.assertEqual(len(self.BodySource.Group), 3, "Source body feature count is wrong") + self.assertEqual(len(self.BodyTarget.Group), 0, "Target body feature count is wrong") def testMoveSingleFeature(self): FreeCAD.Console.PrintMessage('Testing moving one feature from one body to another\n') @@ -176,8 +176,8 @@ class PartDesignGuiTestCases(unittest.TestCase): self.assertFalse(self.Sketch.Support[0][0] in self.BodySource.Origin.OriginFeatures) self.assertTrue(self.Sketch.Support[0][0] in self.BodyTarget.Origin.OriginFeatures) - self.assertEqual(len(self.BodySource.Model), 0, "Source body feature count is wrong") - self.assertEqual(len(self.BodyTarget.Model), 2, "Target body feature count is wrong") + self.assertEqual(len(self.BodySource.Group), 0, "Source body feature count is wrong") + self.assertEqual(len(self.BodyTarget.Group), 2, "Target body feature count is wrong") def tearDown(self): FreeCAD.closeDocument("SketchGuiTest") From c4ec348fbd658c6542d9f92d60e428cc06917ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Fri, 23 Dec 2016 06:27:35 +0100 Subject: [PATCH 060/144] PartDesign: Show origin for primitive editing --- .../Gui/TaskPrimitiveParameters.cpp | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp index 2ab3738cea..a74e331800 100644 --- a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp @@ -36,13 +36,16 @@ #include "ViewProviderDatumCS.h" #include #include +#include #include #include #include #include #include +#include #include #include +#include #include using namespace PartDesignGui; @@ -217,6 +220,19 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) if(i != index) ui.widgetStack->widget(i)->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)); } + + //show the parts coordinate system axis for selection + PartDesign::Body * body = PartDesign::Body::findBodyOf(vp->getObject()); + if(body) { + try { + App::Origin *origin = body->getOrigin(); + Gui::ViewProviderOrigin* vpOrigin; + vpOrigin = static_cast(Gui::Application::Instance->getViewProvider(origin)); + vpOrigin->setTemporaryVisibility(true, true); + } catch (const Base::Exception &ex) { + Base::Console().Error ("%s\n", ex.what () ); + } + } } /* @@ -224,6 +240,18 @@ TaskBoxPrimitives::TaskBoxPrimitives(ViewProviderPrimitive* vp, QWidget* parent) */ TaskBoxPrimitives::~TaskBoxPrimitives() { + //hide the parts coordinate system axis for selection + PartDesign::Body * body = PartDesign::Body::findBodyOf ( vp->getObject() ); + if(body) { + try { + App::Origin *origin = body->getOrigin(); + Gui::ViewProviderOrigin* vpOrigin; + vpOrigin = static_cast(Gui::Application::Instance->getViewProvider(origin)); + vpOrigin->resetTemporaryVisibility(); + } catch (const Base::Exception &ex) { + Base::Console().Error ("%s\n", ex.what () ); + } + } } void TaskBoxPrimitives::onBoxHeightChanged(double v) { From dd0b7144f82ea9a940e35af5caaf031abffc1a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Fri, 23 Dec 2016 09:39:12 +0100 Subject: [PATCH 061/144] Extensions: Fix wrong group test and prevent crash --- src/App/GeoFeatureGroupExtension.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/App/GeoFeatureGroupExtension.h b/src/App/GeoFeatureGroupExtension.h index bc76371524..901b34bcfd 100644 --- a/src/App/GeoFeatureGroupExtension.h +++ b/src/App/GeoFeatureGroupExtension.h @@ -73,7 +73,8 @@ public: /// Returns true if the given DocumentObject is DocumentObjectGroup but not GeoFeatureGroup static bool isNonGeoGroup(const DocumentObject* obj) { - return obj->hasExtension(GroupExtension::getExtensionClassTypeId()); + return obj->hasExtension(GroupExtension::getExtensionClassTypeId()) & + !obj->hasExtension(GeoFeatureGroupExtension::getExtensionClassTypeId()); } }; From 109f8690ee2d02ce4e1764cbacb8ae1f39d99453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Sat, 24 Dec 2016 09:38:51 +0100 Subject: [PATCH 062/144] Extensions: Fix GeoFeatureGroup drag&drop --- src/App/GeoFeatureGroupExtension.cpp | 6 +- src/App/GroupExtension.cpp | 9 +- src/App/OriginGroupExtension.cpp | 63 ++++++++++++++ src/App/OriginGroupExtension.h | 5 ++ .../ViewProviderGeoFeatureGroupExtension.cpp | 86 +++++++++++++++++++ .../ViewProviderGeoFeatureGroupExtension.h | 5 ++ src/Gui/ViewProviderOriginGroupExtension.cpp | 58 +++++++++++++ src/Gui/ViewProviderOriginGroupExtension.h | 3 + src/Mod/PartDesign/App/Body.cpp | 12 ++- 9 files changed, 240 insertions(+), 7 deletions(-) diff --git a/src/App/GeoFeatureGroupExtension.cpp b/src/App/GeoFeatureGroupExtension.cpp index a9dadff5f5..f32872634e 100644 --- a/src/App/GeoFeatureGroupExtension.cpp +++ b/src/App/GeoFeatureGroupExtension.cpp @@ -57,7 +57,7 @@ void GeoFeatureGroupExtension::initExtension(ExtensionContainer* obj) { if(!obj->isDerivedFrom(App::GeoFeature::getClassTypeId())) throw Base::Exception("GeoFeatureGroupExtension can only be applied to GeoFeatures"); - App::Extension::initExtension(obj); + App::GroupExtension::initExtension(obj); } PropertyPlacement& GeoFeatureGroupExtension::placement() { @@ -143,9 +143,9 @@ bool GeoFeatureGroupExtension::geoHasObject (const DocumentObject* obj) const { DocumentObject* GeoFeatureGroupExtension::getGroupOfObject(const DocumentObject* obj, bool indirect) { const Document* doc = obj->getDocument(); - std::vector grps = doc->getObjectsOfType(GeoFeatureGroupExtension::getExtensionClassTypeId()); + std::vector grps = doc->getObjectsWithExtension(GeoFeatureGroupExtension::getExtensionClassTypeId()); for (std::vector::const_iterator it = grps.begin(); it != grps.end(); ++it) { - GeoFeatureGroupExtension* grp = (GeoFeatureGroupExtension*)(*it); + GeoFeatureGroupExtension* grp = (*it)->getExtensionByType(); if ( indirect ) { if (grp->geoHasObject(obj)) { return dynamic_cast(grp); diff --git a/src/App/GroupExtension.cpp b/src/App/GroupExtension.cpp index 54e3ce95a6..0ed16d8f15 100644 --- a/src/App/GroupExtension.cpp +++ b/src/App/GroupExtension.cpp @@ -63,6 +63,11 @@ void GroupExtension::addObject(DocumentObject* obj) if(!allowObject(obj)) return; + //only one group per object + auto *group = App::GroupExtension::getGroupOfObject(obj); + if(group && group != getExtendedObject()) + group->getExtensionByType()->removeObject(obj); + if (!hasObject(obj)) { std::vector grp = Group.getValues(); grp.push_back(obj); @@ -180,9 +185,9 @@ int GroupExtension::countObjectsOfType(const Base::Type& typeId) const DocumentObject* GroupExtension::getGroupOfObject(const DocumentObject* obj) { const Document* doc = obj->getDocument(); - std::vector grps = doc->getObjectsOfType(GroupExtension::getExtensionClassTypeId()); + std::vector grps = doc->getObjectsWithExtension(GroupExtension::getExtensionClassTypeId()); for (std::vector::const_iterator it = grps.begin(); it != grps.end(); ++it) { - GroupExtension* grp = (GroupExtension*)(*it); + GroupExtension* grp = (*it)->getExtensionByType(); if (grp->hasObject(obj)) return *it; } diff --git a/src/App/OriginGroupExtension.cpp b/src/App/OriginGroupExtension.cpp index 739d01f838..0235ea6b8c 100644 --- a/src/App/OriginGroupExtension.cpp +++ b/src/App/OriginGroupExtension.cpp @@ -129,6 +129,69 @@ void OriginGroupExtension::onExtendedUnsetupObject () { GeoFeatureGroupExtension::onExtendedUnsetupObject (); } +void OriginGroupExtension::relinkToOrigin(App::DocumentObject* obj) +{ + //we get all links and replace the origin objects if needed (subnames need not to change, they + //would stay the same) + std::vector< App::DocumentObject* > result; + std::vector list; + obj->getPropertyList(list); + for(App::Property* prop : list) { + if(prop->getTypeId().isDerivedFrom(App::PropertyLink::getClassTypeId())) { + + auto p = static_cast(prop); + if(!p->getValue() || !p->getValue()->isDerivedFrom(App::OriginFeature::getClassTypeId())) + continue; + + p->setValue(getOrigin()->getOriginFeature(static_cast(p->getValue())->Role.getValue())); + } + else if(prop->getTypeId().isDerivedFrom(App::PropertyLinkList::getClassTypeId())) { + auto p = static_cast(prop); + auto vec = p->getValues(); + std::vector result; + bool changed = false; + for(App::DocumentObject* o : vec) { + if(!o || !o->isDerivedFrom(App::OriginFeature::getClassTypeId())) + result.push_back(o); + else { + result.push_back(getOrigin()->getOriginFeature(static_cast(o)->Role.getValue())); + changed = true; + } + } + if(changed) + static_cast(prop)->setValues(result); + } + else if(prop->getTypeId().isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) { + auto p = static_cast(prop); + if(!p->getValue() || !p->getValue()->isDerivedFrom(App::OriginFeature::getClassTypeId())) + continue; + + p->setValue(getOrigin()->getOriginFeature(static_cast(p->getValue())->Role.getValue())); + } + else if(prop->getTypeId().isDerivedFrom(App::PropertyLinkSubList::getClassTypeId())) { + auto p = static_cast(prop); + auto vec = p->getValues(); + std::vector result; + bool changed = false; + for(App::DocumentObject* o : vec) { + if(!o || !o->isDerivedFrom(App::OriginFeature::getClassTypeId())) + result.push_back(o); + else { + result.push_back(getOrigin()->getOriginFeature(static_cast(o)->Role.getValue())); + changed = true; + } + } + if(changed) + static_cast(prop)->setValues(result); + } + } +} + +void OriginGroupExtension::addObject(DocumentObject* obj) { + relinkToOrigin(obj); + App::GeoFeatureGroupExtension::addObject(obj); +} + // Python feature --------------------------------------------------------- diff --git a/src/App/OriginGroupExtension.h b/src/App/OriginGroupExtension.h index fe5cb89f36..53938ade2a 100644 --- a/src/App/OriginGroupExtension.h +++ b/src/App/OriginGroupExtension.h @@ -62,6 +62,11 @@ public: /// Origin linked to the group PropertyLink Origin; + + //changes all links of obj to a origin to point to this groupes origin + void relinkToOrigin(App::DocumentObject* obj); + + virtual void addObject(DocumentObject* obj); protected: /// Checks integrity of the Origin diff --git a/src/Gui/ViewProviderGeoFeatureGroupExtension.cpp b/src/Gui/ViewProviderGeoFeatureGroupExtension.cpp index 41255be876..fe9d8f6d87 100644 --- a/src/Gui/ViewProviderGeoFeatureGroupExtension.cpp +++ b/src/Gui/ViewProviderGeoFeatureGroupExtension.cpp @@ -29,6 +29,9 @@ #endif #include "ViewProviderGeoFeatureGroupExtension.h" +#include "Command.h" +#include "Application.h" +#include "Document.h" #include #include @@ -91,6 +94,89 @@ void ViewProviderGeoFeatureGroupExtension::extensionUpdateData(const App::Proper } } +std::vector< App::DocumentObject* > ViewProviderGeoFeatureGroupExtension::getLinkedObjects(App::DocumentObject* obj) { + + if(!obj) + return std::vector< App::DocumentObject* >(); + + //we get all linked objects, and that recursively + std::vector< App::DocumentObject* > result; + std::vector list; + obj->getPropertyList(list); + for(App::Property* prop : list) { + if(prop->getTypeId().isDerivedFrom(App::PropertyLink::getClassTypeId())) + result.push_back(static_cast(prop)->getValue()); + else if(prop->getTypeId().isDerivedFrom(App::PropertyLinkList::getClassTypeId())) { + auto vec = static_cast(prop)->getValues(); + result.insert(result.end(), vec.begin(), vec.end()); + } + else if(prop->getTypeId().isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) + result.push_back(static_cast(prop)->getValue()); + else if(prop->getTypeId().isDerivedFrom(App::PropertyLinkSubList::getClassTypeId())) { + auto vec = static_cast(prop)->getValues(); + result.insert(result.end(), vec.begin(), vec.end()); + } + } + + //clear all null objects + result.erase(std::remove(result.begin(), result.end(), nullptr), result.end()); + + //collect all dependencies of those objects + for(App::DocumentObject *obj : result) { + auto vec = getLinkedObjects(obj); + result.insert(result.end(), vec.begin(), vec.end()); + } + + return result; +} + +void ViewProviderGeoFeatureGroupExtension::extensionDropObject(App::DocumentObject* obj) { + + // Open command + App::DocumentObject* grp = static_cast(getExtendedViewProvider()->getObject()); + App::Document* doc = grp->getDocument(); + Gui::Document* gui = Gui::Application::Instance->getDocument(doc); + gui->openCommand("Move object"); + + //links between different CS are not allowed, hence we need to ensure if all dependencies are in + //the same geofeaturegroup + auto vec = getLinkedObjects(obj); + + //remove all objects already in the correct group + vec.erase(std::remove_if(vec.begin(), vec.end(), [this](App::DocumentObject* o){ + return App::GroupExtension::getGroupOfObject(o) == this->getExtendedViewProvider()->getObject(); + }), vec.end()); + + vec.push_back(obj); + + for(App::DocumentObject* o : vec) { + // build Python command for execution + QString cmd; + cmd = QString::fromLatin1("App.getDocument(\"%1\").getObject(\"%2\").addObject(" + "App.getDocument(\"%1\").getObject(\"%3\"))") + .arg(QString::fromLatin1(doc->getName())) + .arg(QString::fromLatin1(grp->getNameInDocument())) + .arg(QString::fromLatin1(o->getNameInDocument())); + + Gui::Command::doCommand(Gui::Command::App, cmd.toUtf8()); + } + gui->commitCommand(); +} + + +void ViewProviderGeoFeatureGroupExtension::extensionDragObject(App::DocumentObject* obj) { + //links between different coordinate systems are not allowed, hence draging one object also needs + //to drag out all dependend objects + auto vec = getLinkedObjects(obj); + + //add this object + vec.push_back(obj); + + for(App::DocumentObject* obj : vec) + ViewProviderGroupExtension::extensionDragObject(obj); +} + + namespace Gui { EXTENSION_PROPERTY_SOURCE_TEMPLATE(Gui::ViewProviderGeoFeatureGroupExtensionPython, Gui::ViewProviderGeoFeatureGroupExtension) diff --git a/src/Gui/ViewProviderGeoFeatureGroupExtension.h b/src/Gui/ViewProviderGeoFeatureGroupExtension.h index 0521ac0b21..fc17992c84 100644 --- a/src/Gui/ViewProviderGeoFeatureGroupExtension.h +++ b/src/Gui/ViewProviderGeoFeatureGroupExtension.h @@ -58,8 +58,13 @@ public: virtual void extensionUpdateData(const App::Property*) override; + virtual void extensionDropObject(App::DocumentObject*); + virtual void extensionDragObject(App::DocumentObject*); + protected: SoGroup *pcGroupChildren; + + std::vector getLinkedObjects(App::DocumentObject* obj); }; typedef ViewProviderExtensionPythonT ViewProviderGeoFeatureGroupExtensionPython; diff --git a/src/Gui/ViewProviderOriginGroupExtension.cpp b/src/Gui/ViewProviderOriginGroupExtension.cpp index 226af9a9c8..d03faf42bd 100644 --- a/src/Gui/ViewProviderOriginGroupExtension.cpp +++ b/src/Gui/ViewProviderOriginGroupExtension.cpp @@ -34,6 +34,7 @@ #include "ViewProviderOrigin.h" #include "View3DInventorViewer.h" #include "View3DInventor.h" +#include "Command.h" #include #include #include @@ -196,6 +197,63 @@ void ViewProviderOriginGroupExtension::updateOriginSize () { vpOrigin->Size.setValue ( size * 1.3 ); } +void ViewProviderOriginGroupExtension::extensionDragObject(App::DocumentObject* obj) { + + //links between different coordinate systems are not allowed, hence draging one object also needs + //to drag out all dependend objects + auto vec = getLinkedObjects(obj); + + //remove all origin objects + vec.erase(std::remove_if(vec.begin(), vec.end(), [this](App::DocumentObject* o) { + return o->isDerivedFrom(App::OriginFeature::getClassTypeId());}), vec.end()); + + //add the original object + vec.push_back(obj); + + for(App::DocumentObject* obj : vec) + ViewProviderGroupExtension::extensionDragObject(obj); +} + +void ViewProviderOriginGroupExtension::extensionDropObject(App::DocumentObject* obj) { + + // Open command + App::DocumentObject* grp = static_cast(getExtendedViewProvider()->getObject()); + App::Document* doc = grp->getDocument(); + Gui::Document* gui = Gui::Application::Instance->getDocument(doc); + gui->openCommand("Move object"); + + //links between different CS are not allowed, hence we need to enure if all dependencies are in + //the same geofeaturegroup + auto vec = getLinkedObjects(obj); + + //remove all origin objects + vec.erase(std::remove_if(vec.begin(), vec.end(), [](App::DocumentObject* o) { + return o->isDerivedFrom(App::OriginFeature::getClassTypeId());}), vec.end()); + + //remove all objects already in the correct group + vec.erase(std::remove_if(vec.begin(), vec.end(), [this](App::DocumentObject* o){ + return App::GroupExtension::getGroupOfObject(o) == this->getExtendedViewProvider()->getObject(); + }), vec.end()); + + //add the original object + vec.push_back(obj); + + for(App::DocumentObject* o : vec) { + + // build Python command for execution + QString cmd; + cmd = QString::fromLatin1("App.getDocument(\"%1\").getObject(\"%2\").addObject(" + "App.getDocument(\"%1\").getObject(\"%3\"))") + .arg(QString::fromLatin1(doc->getName())) + .arg(QString::fromLatin1(grp->getNameInDocument())) + .arg(QString::fromLatin1(o->getNameInDocument())); + + Gui::Command::doCommand(Gui::Command::App, cmd.toUtf8()); + } + gui->commitCommand(); + +} + namespace Gui { EXTENSION_PROPERTY_SOURCE_TEMPLATE(Gui::ViewProviderOriginGroupExtensionPython, Gui::ViewProviderOriginGroupExtension) diff --git a/src/Gui/ViewProviderOriginGroupExtension.h b/src/Gui/ViewProviderOriginGroupExtension.h index 33137c8a77..1d42bd26ee 100644 --- a/src/Gui/ViewProviderOriginGroupExtension.h +++ b/src/Gui/ViewProviderOriginGroupExtension.h @@ -46,6 +46,9 @@ public: virtual void extensionAttach(App::DocumentObject *pcObject) override; virtual void extensionUpdateData(const App::Property* prop) override; + virtual void extensionDragObject(App::DocumentObject*) override; + virtual void extensionDropObject(App::DocumentObject*); + void updateOriginSize(); protected: diff --git a/src/Mod/PartDesign/App/Body.cpp b/src/Mod/PartDesign/App/Body.cpp index 3e4212e965..ad6ee76390 100644 --- a/src/Mod/PartDesign/App/Body.cpp +++ b/src/Mod/PartDesign/App/Body.cpp @@ -169,7 +169,7 @@ App::DocumentObject* Body::getNextSolidFeature(App::DocumentObject *start) start = Tip.getValue(); } - if ( !start ) { // no tip + if ( !start || !hasObject(start) ) { // no or faulty tip return nullptr; } @@ -268,7 +268,12 @@ void Body::addObject(App::DocumentObject *feature) { if(!isAllowed(feature)) throw Base::Exception("Body: object is not allowed"); - + + //only one group per object + auto *group = App::GroupExtension::getGroupOfObject(feature); + if(group && group != getExtendedObject()) + group->getExtensionByType()->removeObject(feature); + insertObject (feature, getNextSolidFeature (), /*after = */ false); // Move the Tip if we added a solid if (isSolidFeature(feature)) { @@ -292,6 +297,9 @@ void Body::insertObject(App::DocumentObject* feature, App::DocumentObject* targe throw Base::Exception("Body: the feature we should insert relative to is not part of that body"); } } + + //ensure that all origin links are ok + relinkToOrigin(feature); std::vector model = Group.getValues(); std::vector::iterator insertInto; From 4efd3e894a9d3c1954603c60c411d434506d44e7 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 4 Jan 2017 18:20:43 +0100 Subject: [PATCH 063/144] fix -Wunused-parameter --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 4e4d13d322..d610f64e42 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -944,7 +944,7 @@ namespace SketcherGui { : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj) {} - bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName) + bool allow(App::Document *, App::DocumentObject *pObj, const char *sSubName) { if (pObj != this->object) return false; @@ -1019,7 +1019,7 @@ public: setCursor(QPixmap(cursor_createlock),7,7); } - virtual void mouseMove(Base::Vector2d onSketchPos) + virtual void mouseMove(Base::Vector2d /*onSketchPos*/) { // If preselection Point //int preSelPnt = sketchgui->getPreselectPoint(); @@ -1219,7 +1219,7 @@ namespace SketcherGui { : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), object(obj) {} - bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName) + bool allow(App::Document *, App::DocumentObject *pObj, const char *sSubName) { if (pObj != this->object) return false; From b3341971161bc34fa7e6d4aa28b9179f4069c552 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Wed, 4 Jan 2017 21:08:54 -0200 Subject: [PATCH 064/144] Arch: decompose multifuse objects when exporting to IFC --- src/Mod/Arch/ArchCommands.py | 2 +- src/Mod/Arch/ArchComponent.py | 31 +++++++ src/Mod/Arch/ArchStructure.py | 4 +- src/Mod/Arch/ArchWall.py | 4 +- src/Mod/Arch/importIFC.py | 154 +++++++++++++++++++--------------- 5 files changed, 126 insertions(+), 69 deletions(-) diff --git a/src/Mod/Arch/ArchCommands.py b/src/Mod/Arch/ArchCommands.py index e75a6fd668..3595aef02a 100644 --- a/src/Mod/Arch/ArchCommands.py +++ b/src/Mod/Arch/ArchCommands.py @@ -968,7 +968,7 @@ def getExtrusionData(shape): return None if not shape.Solids: return None - if len(shape.Faces) < 5: + if len(shape.Faces) < 3: return None # build faces list with normals faces = [] diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index 902003dcb8..b61c71945f 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -405,6 +405,37 @@ class Component: if obj.Base.LengthForward.Value: extrusion = extrusion.multiply(obj.Base.LengthForward.Value) return (base,extrusion,placement) + elif obj.Base.isDerivedFrom("Part::MultiFuse"): + rshapes = [] + revs = [] + rpls = [] + for sub in obj.Base.Shapes: + if sub.isDerivedFrom("Part::Extrusion"): + if sub.Base: + base,placement = self.rebase(sub.Base.Shape) + extrusion = FreeCAD.Vector(sub.Dir) + if extrusion.Length == 0: + extrusion = FreeCAD.Vector(0,0,1) + else: + extrusion = placement.inverse().Rotation.multVec(extrusion) + if hasattr(sub,"LengthForward"): + if sub.LengthForward.Value: + extrusion = extrusion.multiply(sub.LengthForward.Value) + placement = obj.Placement.multiply(placement) + rshapes.append(base) + revs.append(extrusion) + rpls.append(placement) + else: + exdata = ArchCommands.getExtrusionData(sub.Shape) + if exdata: + base,placement = self.rebase(exdata[0]) + extrusion = placement.inverse().Rotation.multVec(exdata[1]) + placement = obj.Placement.multiply(placement) + rshapes.append(base) + revs.append(extrusion) + rpls.append(placement) + if rshapes and revs and rpls: + return (rshapes,revs,rpls) return None def rebase(self,shape): diff --git a/src/Mod/Arch/ArchStructure.py b/src/Mod/Arch/ArchStructure.py index 5c7ad8d37c..5a1fbbdd3a 100644 --- a/src/Mod/Arch/ArchStructure.py +++ b/src/Mod/Arch/ArchStructure.py @@ -461,7 +461,9 @@ class _Structure(ArchComponent.Component): import Part,DraftGeomUtils data = ArchComponent.Component.getExtrusionData(self,obj) if data: - return data + if not isinstance(data[0],list): + # multifuses not considered here + return data length = obj.Length.Value width = obj.Width.Value height = obj.Height.Value diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index 104c920275..8d28684b3e 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -502,7 +502,9 @@ class _Wall(ArchComponent.Component): import Part,DraftGeomUtils data = ArchComponent.Component.getExtrusionData(self,obj) if data: - return data + if not isinstance(data[0],list): + # multifuses not considered here + return data length = obj.Length.Value width = obj.Width.Value height = obj.Height.Value diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index 42251aa124..a3b01d38c7 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -1463,6 +1463,53 @@ def createCurve(ifcfile,wire): return pol +def getProfile(ifcfile,p): + """returns an IFC profile definition from a shape""" + + import Part,DraftGeomUtils + profile = None + if len(p.Edges) == 1: + pxvc = ifcfile.createIfcDirection((1.0,0.0)) + povc = ifcfile.createIfcCartesianPoint((0.0,0.0)) + pt = ifcfile.createIfcAxis2Placement2D(povc,pxvc) + if isinstance(p.Edges[0].Curve,Part.Circle): + # extruded circle + profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt,p.Edges[0].Curve.Radius) + elif isinstance(p.Edges[0].Curve,Part.Ellipse): + # extruded ellipse + profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt,p.Edges[0].Curve.MajorRadius,p.Edges[0].Curve.MinorRadius) + elif (len(p.Faces) == 1) and (len(p.Wires) > 1): + # face with holes + f = p.Faces[0] + if DraftGeomUtils.hasCurves(f.OuterWire): + outerwire = createCurve(ifcfile,f.OuterWire) + else: + w = Part.Wire(Part.__sortEdges__(f.OuterWire.Edges)) + pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] + outerwire = ifcfile.createIfcPolyline(pts) + innerwires = [] + for w in f.Wires: + if w.hashCode() != f.OuterWire.hashCode(): + if DraftGeomUtils.hasCurves(w): + innerwires.append(createCurve(ifcfile,w)) + else: + w = Part.Wire(Part.__sortEdges__(w.Edges)) + pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] + innerwires.append(ifcfile.createIfcPolyline(pts)) + profile = ifcfile.createIfcArbitraryProfileDefWithVoids("AREA",None,outerwire,innerwires) + else: + if DraftGeomUtils.hasCurves(p): + # extruded composite curve + pol = createCurve(ifcfile,p) + else: + # extruded polyline + w = Part.Wire(Part.__sortEdges__(p.Wires[0].Edges)) + pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] + pol = ifcfile.createIfcPolyline(pts) + profile = ifcfile.createIfcArbitraryClosedProfileDef("AREA",None,pol) + return profile + + def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1): """returns an IfcShapeRepresentation object or None""" @@ -1497,81 +1544,55 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess if (not shapes) and (not forcebrep): profile = None + ev = FreeCAD.Vector() if hasattr(obj,"Proxy"): if hasattr(obj.Proxy,"getExtrusionData"): extdata = obj.Proxy.getExtrusionData(obj) if extdata: # convert to meters p = extdata[0] - p.scale(0.001) + if not isinstance(p,list): + p = [p] ev = extdata[1] - ev.multiply(0.001) + if not isinstance(ev,list): + ev = [ev] pl = extdata[2] - pl.Base = pl.Base.multiply(0.001) - pstr = str([v.Point for v in extdata[0].Vertexes]) - if pstr in profiledefs: - profile = profiledefs[pstr] - shapetype = "reusing profile" - else: - if len(p.Edges) == 1: - pxvc = ifcfile.createIfcDirection((1.0,0.0)) - povc = ifcfile.createIfcCartesianPoint((0.0,0.0)) - pt = ifcfile.createIfcAxis2Placement2D(povc,pxvc) - if isinstance(p.Edges[0].Curve,Part.Circle): - # extruded circle - profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt, p.Edges[0].Curve.Radius) - elif isinstance(p.Edges[0].Curve,Part.Ellipse): - # extruded ellipse - profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt, p.Edges[0].Curve.MajorRadius, p.Edges[0].Curve.MinorRadius) - elif (len(p.Faces) == 1) and (len(p.Wires) > 1): - # face with holes - f = p.Faces[0] - if DraftGeomUtils.hasCurves(f.OuterWire): - outerwire = createCurve(ifcfile,f.OuterWire) - else: - w = Part.Wire(Part.__sortEdges__(f.OuterWire.Edges)) - pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] - outerwire = ifcfile.createIfcPolyline(pts) - innerwires = [] - for w in f.Wires: - if w.hashCode() != f.OuterWire.hashCode(): - if DraftGeomUtils.hasCurves(w): - innerwires.append(createCurve(ifcfile,w)) - else: - w = Part.Wire(Part.__sortEdges__(w.Edges)) - pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] - innerwires.append(ifcfile.createIfcPolyline(pts)) - profile = ifcfile.createIfcArbitraryProfileDefWithVoids("AREA",None,outerwire,innerwires) + if not isinstance(pl,list): + pl = [pl] + if (len(p) != len(ev)) or (len(p) != len(pl)): + raise ValueError("importIFC: Extrusion data length mismatch: "+obj.Label) + for i in range(len(p)): + pi = p[i] + pi.scale(0.001) + evi = ev[i] + evi.multiply(0.001) + pli = pl[i] + pli.Base = pli.Base.multiply(0.001) + pstr = str([v.Point for v in p[i].Vertexes]) + if pstr in profiledefs: + profile = profiledefs[pstr] + shapetype = "reusing profile" else: - if DraftGeomUtils.hasCurves(p): - # extruded composite curve - pol = createCurve(ifcfile,p) - else: - # extruded polyline - w = Part.Wire(Part.__sortEdges__(p.Wires[0].Edges)) - pts = [ifcfile.createIfcCartesianPoint(tuple(v.Point)[:2]) for v in w.Vertexes+[w.Vertexes[0]]] - pol = ifcfile.createIfcPolyline(pts) - profile = ifcfile.createIfcArbitraryClosedProfileDef("AREA",None,pol) - if profile: - profiledefs[pstr] = profile - - if profile and not(DraftVecUtils.isNull(ev)): - #ev = pl.Rotation.inverted().multVec(ev) - #print "ev:",ev - if not tostore: - # add the object placement to the profile placement. Otherwise it'll be done later at map insert - pl2 = FreeCAD.Placement(obj.Placement) - pl2.Base = pl2.Base.multiply(0.001) - pl = pl2.multiply(pl) - xvc = ifcfile.createIfcDirection(tuple(pl.Rotation.multVec(FreeCAD.Vector(1,0,0)))) - zvc = ifcfile.createIfcDirection(tuple(pl.Rotation.multVec(FreeCAD.Vector(0,0,1)))) - ovc = ifcfile.createIfcCartesianPoint(tuple(pl.Base)) - lpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc) - edir = ifcfile.createIfcDirection(tuple(FreeCAD.Vector(ev).normalize())) - shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,ev.Length) - shapes.append(shape) - solidType = "SweptSolid" - shapetype = "extrusion" + profile = getProfile(ifcfile,pi) + if profile: + profiledefs[pstr] = profile + if profile and not(DraftVecUtils.isNull(evi)): + #ev = pl.Rotation.inverted().multVec(evi) + #print "evi:",evi + if not tostore: + # add the object placement to the profile placement. Otherwise it'll be done later at map insert + pl2 = FreeCAD.Placement(obj.Placement) + pl2.Base = pl2.Base.multiply(0.001) + pli = pl2.multiply(pli) + xvc = ifcfile.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(1,0,0)))) + zvc = ifcfile.createIfcDirection(tuple(pli.Rotation.multVec(FreeCAD.Vector(0,0,1)))) + ovc = ifcfile.createIfcCartesianPoint(tuple(pli.Base)) + lpl = ifcfile.createIfcAxis2Placement3D(ovc,zvc,xvc) + edir = ifcfile.createIfcDirection(tuple(FreeCAD.Vector(evi).normalize())) + shape = ifcfile.createIfcExtrudedAreaSolid(profile,lpl,edir,evi.Length) + shapes.append(shape) + solidType = "SweptSolid" + shapetype = "extrusion" if not shapes: # brep representation @@ -1833,6 +1854,7 @@ def setRepresentation(representation,scaling=1000): result = preresult return result + def getRotation(entity): "returns a FreeCAD rotation from an IfcProduct with a IfcMappedItem representation" try: From 524ee4a679987c3514d89c592289be4773747a62 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 5 Jan 2017 14:24:03 +0100 Subject: [PATCH 065/144] fix Qt5 port of messageHandler --- src/Gui/Application.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 2543ad72b0..797e0c0118 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -1279,10 +1279,9 @@ typedef void (*_qt_msg_handler_old)(QtMsgType type, const char *msg); _qt_msg_handler_old old_qtmsg_handler = 0; #if QT_VERSION >= 0x050000 -void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &qmsg) +void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { Q_UNUSED(context); - const QChar *msg = qmsg.unicode(); #ifdef FC_DEBUG switch (type) { @@ -1290,26 +1289,26 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt case QtInfoMsg: #endif case QtDebugMsg: - Base::Console().Message("%s\n", msg); + Base::Console().Message("%s\n", msg.toUtf8().constData()); break; case QtWarningMsg: - Base::Console().Warning("%s\n", msg); + Base::Console().Warning("%s\n", msg.toUtf8().constData()); break; case QtCriticalMsg: - Base::Console().Error("%s\n", msg); + Base::Console().Error("%s\n", msg.toUtf8().constData()); break; case QtFatalMsg: - Base::Console().Error("%s\n", msg); + Base::Console().Error("%s\n", msg.toUtf8().constData()); abort(); // deliberately core dump } #ifdef FC_OS_WIN32 if (old_qtmsg_handler) - (*old_qtmsg_handler)(type, context, qmsg); + (*old_qtmsg_handler)(type, context, msg); #endif #else // do not stress user with Qt internals but write to log file if enabled Q_UNUSED(type); - Base::Console().Log("%s\n", msg); + Base::Console().Log("%s\n", msg.toUtf8().constData()); #endif } #else From 96dc57c06861922b9dde830e3bcc07e43ed11cf7 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 5 Jan 2017 14:27:19 +0100 Subject: [PATCH 066/144] replace Qt keyword slots with Q_SLOTS --- src/Gui/OnlineDocumentation.h | 2 +- src/Gui/Quarter/ContextMenu.h | 2 +- src/Gui/Quarter/SensorManager.h | 2 +- src/Mod/Part/Gui/TaskCheckGeometry.h | 2 +- src/Mod/Part/Gui/TaskDimension.h | 8 ++++---- src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Gui/OnlineDocumentation.h b/src/Gui/OnlineDocumentation.h index b4ec736e76..3a7cf57447 100644 --- a/src/Gui/OnlineDocumentation.h +++ b/src/Gui/OnlineDocumentation.h @@ -69,7 +69,7 @@ public: void pause(); void resume(); -private slots: +private Q_SLOTS: void readClient(); void discardClient(); diff --git a/src/Gui/Quarter/ContextMenu.h b/src/Gui/Quarter/ContextMenu.h index ac44f98ddf..a9965001e3 100644 --- a/src/Gui/Quarter/ContextMenu.h +++ b/src/Gui/Quarter/ContextMenu.h @@ -50,7 +50,7 @@ public: QMenu * getMenu(void) const; -public slots: +public Q_SLOTS: void changeRenderMode(QAction * action); void changeStereoMode(QAction * action); void changeTransparencyType(QAction * action); diff --git a/src/Gui/Quarter/SensorManager.h b/src/Gui/Quarter/SensorManager.h index 4b3fb7d0d0..6344cf93e6 100644 --- a/src/Gui/Quarter/SensorManager.h +++ b/src/Gui/Quarter/SensorManager.h @@ -48,7 +48,7 @@ public: SensorManager(void); ~SensorManager(); -public slots: +public Q_SLOTS: void idleTimeout(void); void delayTimeout(void); void timerQueueTimeout(void); diff --git a/src/Mod/Part/Gui/TaskCheckGeometry.h b/src/Mod/Part/Gui/TaskCheckGeometry.h index 4910412b5a..30822e6085 100644 --- a/src/Mod/Part/Gui/TaskCheckGeometry.h +++ b/src/Mod/Part/Gui/TaskCheckGeometry.h @@ -99,7 +99,7 @@ public: ~TaskCheckGeometryResults(); QString getShapeContentString(); -private slots: +private Q_SLOTS: void currentRowChanged (const QModelIndex ¤t, const QModelIndex &previous); private: diff --git a/src/Mod/Part/Gui/TaskDimension.h b/src/Mod/Part/Gui/TaskDimension.h index 15f766c0b1..6e05179f52 100644 --- a/src/Mod/Part/Gui/TaskDimension.h +++ b/src/Mod/Part/Gui/TaskDimension.h @@ -198,7 +198,7 @@ protected: QPixmap *stepActive; QPixmap *stepDone; -private slots: +private Q_SLOTS: void selectionSlot(bool checked); void buildPixmaps(); @@ -229,7 +229,7 @@ class DimensionControl : public QWidget public: explicit DimensionControl(QWidget* parent); QPushButton *resetButton; -public slots: +public Q_SLOTS: void toggle3dSlot(bool); void toggleDeltaSlot(bool); void clearAllSlot(bool); @@ -250,7 +250,7 @@ public: protected: virtual void onSelectionChanged(const Gui::SelectionChanges& msg); -protected slots: +protected Q_SLOTS: void selection1Slot(bool checked); void selection2Slot(bool checked); void resetDialogSlot(bool); @@ -326,7 +326,7 @@ public: protected: virtual void onSelectionChanged(const Gui::SelectionChanges& msg); -protected slots: +protected Q_SLOTS: void selection1Slot(bool checked); void selection2Slot(bool checked); void resetDialogSlot(bool); diff --git a/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp b/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp index ea222095c5..b2b4d781bb 100644 --- a/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp +++ b/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp @@ -181,7 +181,7 @@ signals: void clicked(); void selected(); -public slots: +public Q_SLOTS: void setColor(const QColor &color, const QString &text = QString()); protected: @@ -224,10 +224,10 @@ signals: void selected(const QColor &); void hid(); -public slots: +public Q_SLOTS: void getColorFromDialog(); -protected slots: +protected Q_SLOTS: void updateSelected(); protected: From 8ac67eae807b3086c4b9509cf125f1edaddb8474 Mon Sep 17 00:00:00 2001 From: triplus Date: Thu, 5 Jan 2017 20:11:18 +0100 Subject: [PATCH 067/144] Update BOA common and section documentation --- src/Mod/Part/App/TopoShapePy.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Part/App/TopoShapePy.xml b/src/Mod/Part/App/TopoShapePy.xml index ab11fe4eb6..edc807ca96 100644 --- a/src/Mod/Part/App/TopoShapePy.xml +++ b/src/Mod/Part/App/TopoShapePy.xml @@ -186,7 +186,7 @@ Intersection of this and a given list of topo shapes. Supports: - Fuzzy Boolean operations (global tolerance for a Boolean operation) -- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s2)) +- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s3)) - Parallelization of Boolean Operations algorithm OCC 6.9.0 or later is required. @@ -203,7 +203,7 @@ Section of this and a given list of topo shapes. Supports: - Fuzzy Boolean operations (global tolerance for a Boolean operation) -- Support of multiple arguments for a single Boolean operation +- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s3)) - Parallelization of Boolean Operations algorithm OCC 6.9.0 or later is required. From 9b07616fe6e7fb624c33f5b119bbf697adf481cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Skowro=C5=84ski?= Date: Thu, 5 Jan 2017 22:38:23 +0100 Subject: [PATCH 068/144] * Cosmetic change. Replace Qt keyword emit with Q_EMIT. * Whitespace fixes. --- src/Gui/QSint/actionpanel/taskheader_p.cpp | 4 +- src/Gui/Quarter/SignalThread.cpp | 2 +- src/Gui/iisTaskPanel/src/iisiconlabel.cpp | 220 +++++++------- src/Gui/iisTaskPanel/src/iistaskheader.cpp | 228 +++++++------- src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp | 338 ++++++++++----------- src/Tools/plugins/widget/customwidgets.cpp | 18 +- src/Tools/plugins/widget/wizard.cpp | 4 +- 7 files changed, 407 insertions(+), 407 deletions(-) diff --git a/src/Gui/QSint/actionpanel/taskheader_p.cpp b/src/Gui/QSint/actionpanel/taskheader_p.cpp index 8ea426a257..42f18b9dc8 100644 --- a/src/Gui/QSint/actionpanel/taskheader_p.cpp +++ b/src/Gui/QSint/actionpanel/taskheader_p.cpp @@ -195,7 +195,7 @@ void TaskHeader::leaveEvent ( QEvent * /*event*/ ) void TaskHeader::fold() { if (myExpandable) { - emit activated(); + Q_EMIT activated(); // Toggling the 'm_fold' member here may lead to inconsistencies with its ActionGroup. // Thus, the method setFold() was added and called from ActionGroup when required. #if 0 @@ -254,7 +254,7 @@ void TaskHeader::changeIcons() void TaskHeader::mouseReleaseEvent ( QMouseEvent * event ) { if (event->button() == Qt::LeftButton) { - emit activated(); + Q_EMIT activated(); } } diff --git a/src/Gui/Quarter/SignalThread.cpp b/src/Gui/Quarter/SignalThread.cpp index 2b7254d3ce..6d18f8636d 100644 --- a/src/Gui/Quarter/SignalThread.cpp +++ b/src/Gui/Quarter/SignalThread.cpp @@ -70,7 +70,7 @@ SignalThread::run(void) // just wait, and trigger every time we receive a signal this->waitcond.wait(&this->mutex); if (!this->isstopped) { - emit triggerSignal(); + Q_EMIT triggerSignal(); } } } diff --git a/src/Gui/iisTaskPanel/src/iisiconlabel.cpp b/src/Gui/iisTaskPanel/src/iisiconlabel.cpp index f014cbf3c6..f967eacc38 100644 --- a/src/Gui/iisTaskPanel/src/iisiconlabel.cpp +++ b/src/Gui/iisTaskPanel/src/iisiconlabel.cpp @@ -9,193 +9,193 @@ #include "iistaskpanelscheme.h" iisIconLabel::iisIconLabel(const QIcon &icon, const QString &title, QWidget *parent) - : QWidget(parent), - myPixmap(icon), - myText(title), - mySchemePointer(0), - m_over(false), - m_pressed(false), - m_changeCursorOver(true), - m_underlineOver(true) + : QWidget(parent), + myPixmap(icon), + myText(title), + mySchemePointer(0), + m_over(false), + m_pressed(false), + m_changeCursorOver(true), + m_underlineOver(true) { - setFocusPolicy(Qt::StrongFocus); - setCursor(Qt::PointingHandCursor); + setFocusPolicy(Qt::StrongFocus); + setCursor(Qt::PointingHandCursor); - myFont.setWeight(0); - myPen.setStyle(Qt::NoPen); + myFont.setWeight(0); + myPen.setStyle(Qt::NoPen); - myColor = myColorOver = myColorDisabled = QColor(); + myColor = myColorOver = myColorDisabled = QColor(); } iisIconLabel::~iisIconLabel() { - //if (m_changeCursorOver) - // QApplication::restoreOverrideCursor(); + //if (m_changeCursorOver) + // QApplication::restoreOverrideCursor(); } void iisIconLabel::setSchemePointer(iisIconLabelScheme **pointer) { - mySchemePointer = pointer; - update(); + mySchemePointer = pointer; + update(); } void iisIconLabel::setColors(const QColor &color, const QColor &colorOver, const QColor &colorOff) { - myColor = color; - myColorOver = colorOver; - myColorDisabled = colorOff; - update(); + myColor = color; + myColorOver = colorOver; + myColorDisabled = colorOff; + update(); } void iisIconLabel::setFont(const QFont &font) { - myFont = font; - update(); + myFont = font; + update(); } void iisIconLabel::setFocusPen(const QPen &pen) { - myPen = pen; - update(); + myPen = pen; + update(); } QSize iisIconLabel::sizeHint() const { - return minimumSize(); + return minimumSize(); } QSize iisIconLabel::minimumSizeHint() const { - int s = (mySchemePointer && *mySchemePointer) ? (*mySchemePointer)->iconSize : 16; - QPixmap px = myPixmap.pixmap(s,s, - isEnabled() ? QIcon::Normal : QIcon::Disabled); + int s = (mySchemePointer && *mySchemePointer) ? (*mySchemePointer)->iconSize : 16; + QPixmap px = myPixmap.pixmap(s,s, + isEnabled() ? QIcon::Normal : QIcon::Disabled); - int h = 4+px.height(); - int w = 8 + px.width(); - if (!myText.isEmpty()) { - QFontMetrics fm(myFont); - w += fm.width(myText); - h = qMax(h, 4+fm.height()); - } + int h = 4+px.height(); + int w = 8 + px.width(); + if (!myText.isEmpty()) { + QFontMetrics fm(myFont); + w += fm.width(myText); + h = qMax(h, 4+fm.height()); + } - return QSize(w+2,h+2); + return QSize(w+2,h+2); } -void iisIconLabel::paintEvent ( QPaintEvent * event ) +void iisIconLabel::paintEvent ( QPaintEvent * event ) { - Q_UNUSED(event); - QPainter p(this); + Q_UNUSED(event); + QPainter p(this); - QRect textRect(rect().adjusted(0,0,-1,0)); + QRect textRect(rect().adjusted(0,0,-1,0)); - int x = 2; + int x = 2; - if (!myPixmap.isNull()) { - int s = (mySchemePointer && *mySchemePointer) ? (*mySchemePointer)->iconSize : 16; - QPixmap px = myPixmap.pixmap(s,s, - isEnabled() ? QIcon::Normal : QIcon::Disabled); - p.drawPixmap(x,0,px); - x += px.width() + 4; - } + if (!myPixmap.isNull()) { + int s = (mySchemePointer && *mySchemePointer) ? (*mySchemePointer)->iconSize : 16; + QPixmap px = myPixmap.pixmap(s,s, + isEnabled() ? QIcon::Normal : QIcon::Disabled); + p.drawPixmap(x,0,px); + x += px.width() + 4; + } - if (!myText.isEmpty()) { - QColor text = myColor, textOver = myColorOver, textOff = myColorDisabled; - QFont fnt = myFont; - QPen focusPen = myPen; - bool underline = m_underlineOver/*, cursover = m_changeCursorOver*/; - if (mySchemePointer && *mySchemePointer) { - if (!text.isValid()) text = (*mySchemePointer)->text; - if (!textOver.isValid()) textOver = (*mySchemePointer)->textOver; - if (!textOff.isValid()) textOff = (*mySchemePointer)->textOff; - if (!fnt.weight()) fnt = (*mySchemePointer)->font; - if (focusPen.style() == Qt::NoPen) focusPen = (*mySchemePointer)->focusPen; - underline = (*mySchemePointer)->underlineOver; - //cursover = (*mySchemePointer)->cursorOver; - } + if (!myText.isEmpty()) { + QColor text = myColor, textOver = myColorOver, textOff = myColorDisabled; + QFont fnt = myFont; + QPen focusPen = myPen; + bool underline = m_underlineOver/*, cursover = m_changeCursorOver*/; + if (mySchemePointer && *mySchemePointer) { + if (!text.isValid()) text = (*mySchemePointer)->text; + if (!textOver.isValid()) textOver = (*mySchemePointer)->textOver; + if (!textOff.isValid()) textOff = (*mySchemePointer)->textOff; + if (!fnt.weight()) fnt = (*mySchemePointer)->font; + if (focusPen.style() == Qt::NoPen) focusPen = (*mySchemePointer)->focusPen; + underline = (*mySchemePointer)->underlineOver; + //cursover = (*mySchemePointer)->cursorOver; + } - p.setPen(isEnabled() ? m_over ? textOver : text : textOff); + p.setPen(isEnabled() ? m_over ? textOver : text : textOff); - if (isEnabled() && underline && m_over) - fnt.setUnderline(true); - p.setFont(fnt); + if (isEnabled() && underline && m_over) + fnt.setUnderline(true); + p.setFont(fnt); - textRect.setLeft(x); - QRect boundingRect; + textRect.setLeft(x); + QRect boundingRect; - QFontMetrics fm(fnt); + QFontMetrics fm(fnt); #if QT_VERSION >= 0x040203 - QString txt(fm.elidedText(myText, Qt::ElideRight, textRect.width())); + QString txt(fm.elidedText(myText, Qt::ElideRight, textRect.width())); #else - QString txt = myText; + QString txt = myText; #endif - p.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, txt, &boundingRect); + p.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, txt, &boundingRect); - if (hasFocus()) { - p.setPen(focusPen); - p.drawRect(boundingRect.adjusted(-2,-1,0,0)); - } - } + if (hasFocus()) { + p.setPen(focusPen); + p.drawRect(boundingRect.adjusted(-2,-1,0,0)); + } + } } void iisIconLabel::enterEvent ( QEvent * /*event*/ ) { - m_over = true; + m_over = true; - //if (m_changeCursorOver) - // QApplication::setOverrideCursor(Qt::PointingHandCursor); + //if (m_changeCursorOver) + // QApplication::setOverrideCursor(Qt::PointingHandCursor); - update(); + update(); } void iisIconLabel::leaveEvent ( QEvent * /*event*/ ) { - m_over = false; - update(); + m_over = false; + update(); - //if (m_changeCursorOver) - // QApplication::restoreOverrideCursor(); + //if (m_changeCursorOver) + // QApplication::restoreOverrideCursor(); } void iisIconLabel::mousePressEvent ( QMouseEvent * event ) { - if (event->button() == Qt::LeftButton) { - m_pressed = true; - emit pressed(); - } else - if (event->button() == Qt::RightButton) - emit contextMenu(); + if (event->button() == Qt::LeftButton) { + m_pressed = true; + Q_EMIT pressed(); + } else + if (event->button() == Qt::RightButton) + Q_EMIT contextMenu(); - update(); + update(); } void iisIconLabel::mouseReleaseEvent ( QMouseEvent * event ) { - if (event->button() == Qt::LeftButton) { - m_pressed = false; - emit released(); + if (event->button() == Qt::LeftButton) { + m_pressed = false; + Q_EMIT released(); - if (rect().contains( event->pos() )) { - emit clicked(); - emit activated(); - } - } + if (rect().contains( event->pos() )) { + Q_EMIT clicked(); + Q_EMIT activated(); + } + } - update(); + update(); } void iisIconLabel::keyPressEvent ( QKeyEvent * event ) { - switch (event->key()) { - case Qt::Key_Space: - case Qt::Key_Return: - emit activated(); - break; + switch (event->key()) { + case Qt::Key_Space: + case Qt::Key_Return: + Q_EMIT activated(); + break; - default:; - } + default:; + } - QWidget::keyPressEvent(event); + QWidget::keyPressEvent(event); } diff --git a/src/Gui/iisTaskPanel/src/iistaskheader.cpp b/src/Gui/iisTaskPanel/src/iistaskheader.cpp index 12e75cc201..4d1cd42432 100644 --- a/src/Gui/iisTaskPanel/src/iistaskheader.cpp +++ b/src/Gui/iisTaskPanel/src/iistaskheader.cpp @@ -16,38 +16,38 @@ #include "iisiconlabel.h" iisTaskHeader::iisTaskHeader(const QIcon &icon, const QString &title, bool expandable, QWidget *parent) - : QFrame(parent), - myExpandable(expandable), - m_over(false), - m_buttonOver(false), - m_fold(true), - m_opacity(0.1), - myButton(0) + : QFrame(parent), + myExpandable(expandable), + m_over(false), + m_buttonOver(false), + m_fold(true), + m_opacity(0.1), + myButton(0) { - myTitle = new iisIconLabel(icon, title, this); - myTitle->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + myTitle = new iisIconLabel(icon, title, this); + myTitle->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - connect(myTitle, SIGNAL(activated()), this, SLOT(fold())); + connect(myTitle, SIGNAL(activated()), this, SLOT(fold())); - QHBoxLayout *hbl = new QHBoxLayout(); - hbl->setMargin(2); - setLayout(hbl); + QHBoxLayout *hbl = new QHBoxLayout(); + hbl->setMargin(2); + setLayout(hbl); - hbl->addWidget(myTitle); + hbl->addWidget(myTitle); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - setScheme(iisTaskPanelScheme::defaultScheme()); - myTitle->setSchemePointer(&myLabelScheme); + setScheme(iisTaskPanelScheme::defaultScheme()); + myTitle->setSchemePointer(&myLabelScheme); - if (myExpandable) { - myButton = new QLabel(this); - hbl->addWidget(myButton); - myButton->installEventFilter(this); - myButton->setFixedWidth(myScheme->headerButtonSize.width()); - changeIcons(); - } + if (myExpandable) { + myButton = new QLabel(this); + hbl->addWidget(myButton); + myButton->installEventFilter(this); + myButton->setFixedWidth(myScheme->headerButtonSize.width()); + changeIcons(); + } } iisTaskHeader::~iisTaskHeader() @@ -57,145 +57,145 @@ iisTaskHeader::~iisTaskHeader() bool iisTaskHeader::eventFilter(QObject *obj, QEvent *event) { - switch (event->type()) { - case QEvent::MouseButtonPress: - fold(); - return true; + switch (event->type()) { + case QEvent::MouseButtonPress: + fold(); + return true; - case QEvent::Enter: - m_buttonOver = true; - changeIcons(); - return true; + case QEvent::Enter: + m_buttonOver = true; + changeIcons(); + return true; - case QEvent::Leave: - m_buttonOver = false; - changeIcons(); - return true; + case QEvent::Leave: + m_buttonOver = false; + changeIcons(); + return true; - default:; - } + default:; + } - return QFrame::eventFilter(obj, event); + return QFrame::eventFilter(obj, event); } void iisTaskHeader::setScheme(iisTaskPanelScheme *scheme) { - if (scheme) { - myScheme = scheme; - myLabelScheme = &(scheme->headerLabelScheme); + if (scheme) { + myScheme = scheme; + myLabelScheme = &(scheme->headerLabelScheme); - if (myExpandable) { - setCursor(myLabelScheme->cursorOver ? Qt::PointingHandCursor : cursor()); - changeIcons(); - } + if (myExpandable) { + setCursor(myLabelScheme->cursorOver ? Qt::PointingHandCursor : cursor()); + changeIcons(); + } - setFixedHeight(scheme->headerSize); + setFixedHeight(scheme->headerSize); - update(); - } + update(); + } } -void iisTaskHeader::paintEvent ( QPaintEvent * event ) +void iisTaskHeader::paintEvent ( QPaintEvent * event ) { - Q_UNUSED(event); - QPainter p(this); + Q_UNUSED(event); + QPainter p(this); #if QT_VERSION >= 0x040203 - if (myScheme->headerAnimation) - p.setOpacity(m_opacity+0.7); + if (myScheme->headerAnimation) + p.setOpacity(m_opacity+0.7); #endif - p.setPen(myScheme->headerBorder); - p.setBrush(myScheme->headerBackground); - if (myScheme->headerBorder.style() == Qt::NoPen) - p.drawRect(rect()); - else - p.drawRect(rect().adjusted(0,0,-1,-1)); + p.setPen(myScheme->headerBorder); + p.setBrush(myScheme->headerBackground); + if (myScheme->headerBorder.style() == Qt::NoPen) + p.drawRect(rect()); + else + p.drawRect(rect().adjusted(0,0,-1,-1)); } void iisTaskHeader::animate() { - if (!myScheme->headerAnimation) - return; + if (!myScheme->headerAnimation) + return; - if (!isEnabled()) { - m_opacity = 0.1; - update(); - return; - } + if (!isEnabled()) { + m_opacity = 0.1; + update(); + return; + } - if (m_over) { - if (m_opacity >= 0.3) { - m_opacity = 0.3; - return; - } - m_opacity += 0.05; - } else { - if (m_opacity <= 0.1) { - m_opacity = 0.1; - return; - } - m_opacity = qMax(0.1, m_opacity-0.05); - } + if (m_over) { + if (m_opacity >= 0.3) { + m_opacity = 0.3; + return; + } + m_opacity += 0.05; + } else { + if (m_opacity <= 0.1) { + m_opacity = 0.1; + return; + } + m_opacity = qMax(0.1, m_opacity-0.05); + } - QTimer::singleShot(100, this, SLOT(animate())); - update(); + QTimer::singleShot(100, this, SLOT(animate())); + update(); } void iisTaskHeader::enterEvent ( QEvent * /*event*/ ) { - m_over = true; + m_over = true; - if (isEnabled()) - QTimer::singleShot(100, this, SLOT(animate())); + if (isEnabled()) + QTimer::singleShot(100, this, SLOT(animate())); - update(); + update(); } void iisTaskHeader::leaveEvent ( QEvent * /*event*/ ) { - m_over = false; - - if (isEnabled()) - QTimer::singleShot(100, this, SLOT(animate())); + m_over = false; - update(); + if (isEnabled()) + QTimer::singleShot(100, this, SLOT(animate())); + + update(); } void iisTaskHeader::fold() { - if (myExpandable) { - emit activated(); + if (myExpandable) { + Q_EMIT activated(); - m_fold = !m_fold; - changeIcons(); - } + m_fold = !m_fold; + changeIcons(); + } } void iisTaskHeader::changeIcons() { - if (!myButton) - return; + if (!myButton) + return; - if (m_buttonOver) - { - if (m_fold) - myButton->setPixmap(myScheme->headerButtonFoldOver.pixmap(myScheme->headerButtonSize)); - else - myButton->setPixmap(myScheme->headerButtonUnfoldOver.pixmap(myScheme->headerButtonSize)); - } else - { - if (m_fold) - myButton->setPixmap(myScheme->headerButtonFold.pixmap(myScheme->headerButtonSize)); - else - myButton->setPixmap(myScheme->headerButtonUnfold.pixmap(myScheme->headerButtonSize)); - } + if (m_buttonOver) + { + if (m_fold) + myButton->setPixmap(myScheme->headerButtonFoldOver.pixmap(myScheme->headerButtonSize)); + else + myButton->setPixmap(myScheme->headerButtonUnfoldOver.pixmap(myScheme->headerButtonSize)); + } else + { + if (m_fold) + myButton->setPixmap(myScheme->headerButtonFold.pixmap(myScheme->headerButtonSize)); + else + myButton->setPixmap(myScheme->headerButtonUnfold.pixmap(myScheme->headerButtonSize)); + } } void iisTaskHeader::mouseReleaseEvent ( QMouseEvent * event ) { - if (event->button() == Qt::LeftButton) { - emit activated(); - } + if (event->button() == Qt::LeftButton) { + Q_EMIT activated(); + } } diff --git a/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp b/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp index b2b4d781bb..d40584f9ed 100644 --- a/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp +++ b/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp @@ -1,17 +1,17 @@ /**************************************************************************** ** ** This file is part of a Qt Solutions component. -** +** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). -** +** ** Contact: Qt Software Information (qt-info@nokia.com) -** -** Commercial Usage +** +** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. -** +** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software @@ -19,29 +19,29 @@ ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** +** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. -** -** GNU General Public License Usage +** +** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. -** +** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. -** +** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. -** +** ****************************************************************************/ #include @@ -169,7 +169,7 @@ class ColorPickerItem : public QFrame public: ColorPickerItem(const QColor &color = Qt::white, const QString &text = QString::null, - QWidget *parent = 0); + QWidget *parent = 0); ~ColorPickerItem(); QColor color() const; @@ -205,7 +205,7 @@ class ColorPickerPopup : public QFrame public: ColorPickerPopup(int width, bool withColorDialog, - QWidget *parent = 0); + QWidget *parent = 0); ~ColorPickerPopup(); void insertColor(const QColor &col, const QString &text, int index); @@ -217,7 +217,7 @@ public: ColorPickerItem *find(const QColor &col) const; QColor color(int index) const; - + void setLastSel(const QColor & col); signals: @@ -268,7 +268,7 @@ private: \sa QFrame */ QtColorPicker::QtColorPicker(QWidget *parent, - int cols, bool enableColorDialog) + int cols, bool enableColorDialog) : QPushButton(parent), popup(0), withColorDialog(enableColorDialog) { setFocusPolicy(Qt::StrongFocus); @@ -288,7 +288,7 @@ QtColorPicker::QtColorPicker(QWidget *parent, // Create color grid popup and connect to it. popup = new ColorPickerPopup(cols, withColorDialog, this); connect(popup, SIGNAL(selected(const QColor &)), - SLOT(setCurrentColor(const QColor &))); + SLOT(setCurrentColor(const QColor &))); connect(popup, SIGNAL(hid()), SLOT(popupClosed())); // Connect this push button's pressed() signal. @@ -434,18 +434,18 @@ void QtColorPicker::setStandardColors() void QtColorPicker::setCurrentColor(const QColor &color) { if (color.isValid() && col == color) { - emit colorSet(color); + Q_EMIT colorSet(color); return; } if (col == color || !color.isValid()) - return; + return; ColorPickerItem *item = popup->find(color); if (!item) { - insertColor(color, tr("Custom")); - item = popup->find(color); + insertColor(color, tr("Custom")); + item = popup->find(color); } - + popup->setLastSel(color); col = color; @@ -457,8 +457,8 @@ void QtColorPicker::setCurrentColor(const QColor &color) repaint(); item->setSelected(true); - emit colorChanged(color); - emit colorSet(color); + Q_EMIT colorChanged(color); + Q_EMIT colorSet(color); } /*! @@ -471,9 +471,9 @@ void QtColorPicker::insertColor(const QColor &color, const QString &text, int in { popup->insertColor(color, text, index); if (!firstInserted) { - col = color; - setText(text); - firstInserted = true; + col = color; + setText(text); + firstInserted = true; } } @@ -504,7 +504,7 @@ bool QtColorPicker::colorDialogEnabled() const \code void Drawer::mouseReleaseEvent(QMouseEvent *e) { - if (e->button() & RightButton) { + if (e->button() & RightButton) { QColor color = QtColorPicker::getColor(mapToGlobal(e->pos())); } } @@ -542,7 +542,7 @@ QColor QtColorPicker::getColor(const QPoint &point, bool allowCustomColors) Constructs the popup widget. */ ColorPickerPopup::ColorPickerPopup(int width, bool withColorDialog, - QWidget *parent) + QWidget *parent) : QFrame(parent, Qt::Popup) { setFrameStyle(QFrame::StyledPanel); @@ -553,13 +553,13 @@ ColorPickerPopup::ColorPickerPopup(int width, bool withColorDialog, cols = width; if (withColorDialog) { - moreButton = new ColorPickerButton(this); - moreButton->setFixedWidth(24); - moreButton->setFixedHeight(21); - moreButton->setFrameRect(QRect(2, 2, 20, 17)); - connect(moreButton, SIGNAL(clicked()), SLOT(getColorFromDialog())); + moreButton = new ColorPickerButton(this); + moreButton->setFixedWidth(24); + moreButton->setFixedHeight(21); + moreButton->setFrameRect(QRect(2, 2, 20, 17)); + connect(moreButton, SIGNAL(clicked()), SLOT(getColorFromDialog())); } else { - moreButton = 0; + moreButton = 0; } eventLoop = 0; @@ -586,8 +586,8 @@ ColorPickerPopup::~ColorPickerPopup() ColorPickerItem *ColorPickerPopup::find(const QColor &col) const { for (int i = 0; i < items.size(); ++i) { - if (items.at(i) && items.at(i)->color() == col) - return items.at(i); + if (items.at(i) && items.at(i)->color() == col) + return items.at(i); } return 0; @@ -626,7 +626,7 @@ void ColorPickerPopup::insertColor(const QColor &col, const QString &text, int i connect(item, SIGNAL(selected()), SLOT(updateSelected())); if (index == -1) - index = items.count(); + index = items.count(); items.insert((unsigned int)index, item); regenerateGrid(); @@ -667,19 +667,19 @@ void ColorPickerPopup::updateSelected() QLayoutItem *layoutItem; int i = 0; while ((layoutItem = grid->itemAt(i)) != 0) { - QWidget *w = layoutItem->widget(); - if (w && w->inherits("ColorPickerItem")) { - ColorPickerItem *litem = reinterpret_cast(layoutItem->widget()); - if (litem != sender()) - litem->setSelected(false); - } - ++i; + QWidget *w = layoutItem->widget(); + if (w && w->inherits("ColorPickerItem")) { + ColorPickerItem *litem = reinterpret_cast(layoutItem->widget()); + if (litem != sender()) + litem->setSelected(false); + } + ++i; } if (sender() && sender()->inherits("ColorPickerItem")) { - ColorPickerItem *item = (ColorPickerItem *)sender(); - lastSel = item->color(); - emit selected(item->color()); + ColorPickerItem *item = (ColorPickerItem *)sender(); + lastSel = item->color(); + Q_EMIT selected(item->color()); } hide(); @@ -691,7 +691,7 @@ void ColorPickerPopup::updateSelected() void ColorPickerPopup::mouseReleaseEvent(QMouseEvent *e) { if (!rect().contains(e->pos())) - hide(); + hide(); } /*! \internal @@ -705,96 +705,96 @@ void ColorPickerPopup::keyPressEvent(QKeyEvent *e) bool foundFocus = false; for (int j = 0; !foundFocus && j < grid->rowCount(); ++j) { - for (int i = 0; !foundFocus && i < grid->columnCount(); ++i) { - if (widgetAt[j][i] && widgetAt[j][i]->hasFocus()) { - curRow = j; - curCol = i; - foundFocus = true; - break; - } - } + for (int i = 0; !foundFocus && i < grid->columnCount(); ++i) { + if (widgetAt[j][i] && widgetAt[j][i]->hasFocus()) { + curRow = j; + curCol = i; + foundFocus = true; + break; + } + } } switch (e->key()) { - case Qt::Key_Left: - if (curCol > 0) --curCol; - else if (curRow > 0) { --curRow; curCol = grid->columnCount() - 1; } - break; - case Qt::Key_Right: - if (curCol < grid->columnCount() - 1 && widgetAt[curRow][curCol + 1]) ++curCol; - else if (curRow < grid->rowCount() - 1) { ++curRow; curCol = 0; } - break; - case Qt::Key_Up: - if (curRow > 0) --curRow; - else curCol = 0; - break; - case Qt::Key_Down: - if (curRow < grid->rowCount() - 1) { - QWidget *w = widgetAt[curRow + 1][curCol]; - if (w) { - ++curRow; - } else for (int i = 1; i < grid->columnCount(); ++i) { - if (!widgetAt[curRow + 1][i]) { - curCol = i - 1; - ++curRow; - break; - } - } - } - break; - case Qt::Key_Space: - case Qt::Key_Return: - case Qt::Key_Enter: { - QWidget *w = widgetAt[curRow][curCol]; - if (w && w->inherits("ColorPickerItem")) { - ColorPickerItem *wi = reinterpret_cast(w); - wi->setSelected(true); + case Qt::Key_Left: + if (curCol > 0) --curCol; + else if (curRow > 0) { --curRow; curCol = grid->columnCount() - 1; } + break; + case Qt::Key_Right: + if (curCol < grid->columnCount() - 1 && widgetAt[curRow][curCol + 1]) ++curCol; + else if (curRow < grid->rowCount() - 1) { ++curRow; curCol = 0; } + break; + case Qt::Key_Up: + if (curRow > 0) --curRow; + else curCol = 0; + break; + case Qt::Key_Down: + if (curRow < grid->rowCount() - 1) { + QWidget *w = widgetAt[curRow + 1][curCol]; + if (w) { + ++curRow; + } else for (int i = 1; i < grid->columnCount(); ++i) { + if (!widgetAt[curRow + 1][i]) { + curCol = i - 1; + ++curRow; + break; + } + } + } + break; + case Qt::Key_Space: + case Qt::Key_Return: + case Qt::Key_Enter: { + QWidget *w = widgetAt[curRow][curCol]; + if (w && w->inherits("ColorPickerItem")) { + ColorPickerItem *wi = reinterpret_cast(w); + wi->setSelected(true); - QLayoutItem *layoutItem; + QLayoutItem *layoutItem; int i = 0; - while ((layoutItem = grid->itemAt(i)) != 0) { - QWidget *w = layoutItem->widget(); - if (w && w->inherits("ColorPickerItem")) { - ColorPickerItem *litem - = reinterpret_cast(layoutItem->widget()); - if (litem != wi) - litem->setSelected(false); - } - ++i; - } + while ((layoutItem = grid->itemAt(i)) != 0) { + QWidget *w = layoutItem->widget(); + if (w && w->inherits("ColorPickerItem")) { + ColorPickerItem *litem + = reinterpret_cast(layoutItem->widget()); + if (litem != wi) + litem->setSelected(false); + } + ++i; + } - lastSel = wi->color(); - emit selected(wi->color()); - hide(); - } else if (w && w->inherits("QPushButton")) { - ColorPickerItem *wi = reinterpret_cast(w); - wi->setSelected(true); + lastSel = wi->color(); + Q_EMIT selected(wi->color()); + hide(); + } else if (w && w->inherits("QPushButton")) { + ColorPickerItem *wi = reinterpret_cast(w); + wi->setSelected(true); - QLayoutItem *layoutItem; + QLayoutItem *layoutItem; int i = 0; - while ((layoutItem = grid->itemAt(i)) != 0) { - QWidget *w = layoutItem->widget(); - if (w && w->inherits("ColorPickerItem")) { - ColorPickerItem *litem - = reinterpret_cast(layoutItem->widget()); - if (litem != wi) - litem->setSelected(false); - } - ++i; - } + while ((layoutItem = grid->itemAt(i)) != 0) { + QWidget *w = layoutItem->widget(); + if (w && w->inherits("ColorPickerItem")) { + ColorPickerItem *litem + = reinterpret_cast(layoutItem->widget()); + if (litem != wi) + litem->setSelected(false); + } + ++i; + } - lastSel = wi->color(); - emit selected(wi->color()); - hide(); - } - } - break; + lastSel = wi->color(); + Q_EMIT selected(wi->color()); + hide(); + } + } + break; case Qt::Key_Escape: hide(); break; - default: - e->ignore(); - break; + default: + e->ignore(); + break; } widgetAt[curRow][curCol]->setFocus(); @@ -806,12 +806,12 @@ void ColorPickerPopup::keyPressEvent(QKeyEvent *e) void ColorPickerPopup::hideEvent(QHideEvent *e) { if (eventLoop) { - eventLoop->exit(); + eventLoop->exit(); } setFocus(); - emit hid(); + Q_EMIT hid(); QFrame::hideEvent(e); } @@ -832,23 +832,23 @@ void ColorPickerPopup::showEvent(QShowEvent *) { bool foundSelected = false; for (int i = 0; i < grid->columnCount(); ++i) { - for (int j = 0; j < grid->rowCount(); ++j) { - QWidget *w = widgetAt[j][i]; - if (w && w->inherits("ColorPickerItem")) { - if (((ColorPickerItem *)w)->isSelected()) { - w->setFocus(); - foundSelected = true; - break; - } - } - } + for (int j = 0; j < grid->rowCount(); ++j) { + QWidget *w = widgetAt[j][i]; + if (w && w->inherits("ColorPickerItem")) { + if (((ColorPickerItem *)w)->isSelected()) { + w->setFocus(); + foundSelected = true; + break; + } + } + } } if (!foundSelected) { - if (items.count() == 0) - setFocus(); - else - widgetAt[0][0]->setFocus(); + if (items.count() == 0) + setFocus(); + else + widgetAt[0][0]->setFocus(); } } @@ -861,7 +861,7 @@ void ColorPickerPopup::regenerateGrid() int columns = cols; if (columns == -1) - columns = (int) ceil(sqrt((float) items.count())); + columns = (int) ceil(sqrt((float) items.count())); // When the number of columns grows, the number of rows will // fall. There's no way to shrink a grid, so we create a new @@ -884,8 +884,8 @@ void ColorPickerPopup::regenerateGrid() } if (moreButton) { - grid->addWidget(moreButton, crow, ccol); - widgetAt[crow][ccol] = moreButton; + grid->addWidget(moreButton, crow, ccol); + widgetAt[crow][ccol] = moreButton; } updateGeometry(); } @@ -901,12 +901,12 @@ void ColorPickerPopup::getColorFromDialog() //QRgb rgb = QColorDialog::getRgba(lastSel.rgba(), &ok, parentWidget()); QColor col = QColorDialog::getColor(lastSel,parentWidget(),0,QColorDialog::ShowAlphaChannel); if (!col.isValid()) - return; + return; //QColor col = QColor::fromRgba(rgb); insertColor(col, tr("Custom"), -1); lastSel = col; - emit selected(col); + Q_EMIT selected(col); } void ColorPickerPopup::setLastSel(const QColor & col) { lastSel = col; } @@ -916,7 +916,7 @@ void ColorPickerPopup::setLastSel(const QColor & col) { lastSel = col; } whose name is set to \a text. */ ColorPickerItem::ColorPickerItem(const QColor &color, const QString &text, - QWidget *parent) + QWidget *parent) : QFrame(parent), c(color), t(text), sel(false) { setToolTip(t); @@ -994,7 +994,7 @@ void ColorPickerItem::mouseMoveEvent(QMouseEvent *) void ColorPickerItem::mouseReleaseEvent(QMouseEvent *) { sel = true; - emit selected(); + Q_EMIT selected(); } /*! @@ -1018,14 +1018,14 @@ void ColorPickerItem::paintEvent(QPaintEvent *) p.setPen( QPen( Qt::gray, 0, Qt::SolidLine ) ); if (sel) - p.drawRect(1, 1, w - 3, h - 3); + p.drawRect(1, 1, w - 3, h - 3); p.setPen( QPen( Qt::black, 0, Qt::SolidLine ) ); p.drawRect(3, 3, w - 7, h - 7); p.fillRect(QRect(4, 4, w - 8, h - 8), QBrush(c)); if (hasFocus()) - p.drawRect(0, 0, w - 1, h - 1); + p.drawRect(0, 0, w - 1, h - 1); } /*! @@ -1062,7 +1062,7 @@ void ColorPickerButton::mouseReleaseEvent(QMouseEvent *) { setFrameShadow(Raised); repaint(); - emit clicked(); + Q_EMIT clicked(); } /*! @@ -1071,15 +1071,15 @@ void ColorPickerButton::mouseReleaseEvent(QMouseEvent *) void ColorPickerButton::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up - || e->key() == Qt::Key_Down - || e->key() == Qt::Key_Left - || e->key() == Qt::Key_Right) { - qApp->sendEvent(parent(), e); + || e->key() == Qt::Key_Down + || e->key() == Qt::Key_Left + || e->key() == Qt::Key_Right) { + qApp->sendEvent(parent(), e); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Space || e->key() == Qt::Key_Return) { - setFrameShadow(Sunken); - update(); + setFrameShadow(Sunken); + update(); } else { - QFrame::keyPressEvent(e); + QFrame::keyPressEvent(e); } } @@ -1089,16 +1089,16 @@ void ColorPickerButton::keyPressEvent(QKeyEvent *e) void ColorPickerButton::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up - || e->key() == Qt::Key_Down - || e->key() == Qt::Key_Left - || e->key() == Qt::Key_Right) { - qApp->sendEvent(parent(), e); + || e->key() == Qt::Key_Down + || e->key() == Qt::Key_Left + || e->key() == Qt::Key_Right) { + qApp->sendEvent(parent(), e); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Space || e->key() == Qt::Key_Return) { - setFrameShadow(Raised); - repaint(); - emit clicked(); + setFrameShadow(Raised); + repaint(); + Q_EMIT clicked(); } else { - QFrame::keyReleaseEvent(e); + QFrame::keyReleaseEvent(e); } } @@ -1144,8 +1144,8 @@ void ColorPickerButton::paintEvent(QPaintEvent *e) p.drawRect(r.center().x() + offset , r.center().y() + offset, 1, 1); p.drawRect(r.center().x() + offset + 4, r.center().y() + offset, 1, 1); if (hasFocus()) { - p.setPen( QPen( Qt::black, 0, Qt::SolidLine ) ); - p.drawRect(0, 0, width() - 1, height() - 1); + p.setPen( QPen( Qt::black, 0, Qt::SolidLine ) ); + p.drawRect(0, 0, width() - 1, height() - 1); } p.end(); diff --git a/src/Tools/plugins/widget/customwidgets.cpp b/src/Tools/plugins/widget/customwidgets.cpp index d96b9fb9cb..51f70a6dfa 100644 --- a/src/Tools/plugins/widget/customwidgets.cpp +++ b/src/Tools/plugins/widget/customwidgets.cpp @@ -182,7 +182,7 @@ void FileChooser::chooseFile() if (!fn.isEmpty()) { lineEdit->setText(fn); - emit fileNameSelected(fn); + Q_EMIT fileNameSelected(fn); } } @@ -595,9 +595,9 @@ QAbstractSpinBox::StepEnabled QuantitySpinBox::stepEnabled() const } return ret; } - -void QuantitySpinBox::stepBy(int steps) -{ + +void QuantitySpinBox::stepBy(int steps) +{ double step = StepSize * steps; double val = Value + step; if (val > Maximum) @@ -607,7 +607,7 @@ void QuantitySpinBox::stepBy(int steps) lineEdit()->setText(QString::fromUtf8("%L1 %2").arg(val).arg(UnitStr)); update(); -} +} void QuantitySpinBox::setUnitText(QString str) { @@ -792,9 +792,9 @@ public: UIntSpinBox::UIntSpinBox (QWidget* parent) : QSpinBox (parent) { - d = new UIntSpinBoxPrivate; + d = new UIntSpinBoxPrivate; d->mValidator = new UnsignedValidator(this->minimum(), this->maximum(), this); - connect(this, SIGNAL(valueChanged(int)), + connect(this, SIGNAL(valueChanged(int)), this, SLOT(valueChange(int))); setRange(0, 99); setValue(0); @@ -802,7 +802,7 @@ UIntSpinBox::UIntSpinBox (QWidget* parent) } UIntSpinBox::~UIntSpinBox() -{ +{ delete d->mValidator; delete d; d = 0; } @@ -1032,7 +1032,7 @@ void ColorButton::onChooseColor() if ( c.isValid() ) { setColor( c ); - emit changed(); + Q_EMIT changed(); } } diff --git a/src/Tools/plugins/widget/wizard.cpp b/src/Tools/plugins/widget/wizard.cpp index 7d788baf62..6aae3ac0af 100644 --- a/src/Tools/plugins/widget/wizard.cpp +++ b/src/Tools/plugins/widget/wizard.cpp @@ -153,7 +153,7 @@ void Wizard::setCurrentIndex(int index) textLabel->setText(stackWidget->currentWidget()->windowTitle()); _backButton->setEnabled(index > 0); _nextButton->setEnabled(index < count()-1); - emit currentIndexChanged(index); + Q_EMIT currentIndexChanged(index); } } @@ -171,7 +171,7 @@ void Wizard::setPageTitle(QString const &newTitle) { stackWidget->currentWidget()->setWindowTitle(newTitle); textLabel->setText(newTitle); - emit pageTitleChanged(newTitle); + Q_EMIT pageTitleChanged(newTitle); } WizardExtension::WizardExtension(Wizard *widget, QObject *parent) From fea0e02fc1a97d761196b7e34bc0c293413c4f67 Mon Sep 17 00:00:00 2001 From: ChrisLuck Date: Fri, 6 Jan 2017 16:03:11 +0100 Subject: [PATCH 069/144] support stepdown greater than total depth, 2nd try --- src/Mod/Path/PathScripts/PathUtils.py | 65 +++++++++++++------ src/Mod/Path/PathTests/TestPathDepthParams.py | 24 ++++++- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 2f6763b4df..4e75ef0898 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -35,6 +35,7 @@ from DraftGeomUtils import geomType import PathScripts from PathScripts import PathJob # import itertools +import numpy def cleanedges(splines, precision): '''cleanedges([splines],precision). Convert BSpline curves, Beziers, to arcs that can be used for cnc paths. @@ -840,27 +841,53 @@ class depth_params: '''returns a list of depths to be used in order from first to last. equalstep=True: all steps down before the finish pass will be equalized.''' - depths = [] if self.user_depths is not None: - depths = self.user_depths - else: - total_depth = self.start_depth - self.final_depth - if total_depth <= 0: - return depths - layers_required = int((total_depth - self.z_finish_step) / self.step_down) - partial_steplayer = (total_depth - self.z_finish_step) % self.step_down - if equalstep is True and partial_steplayer > 0: - layerstep = float((total_depth - self.z_finish_step) / (layers_required + 1)) + return self.user_depths + + total_depth = self.start_depth - self.final_depth + + if total_depth < 0: + return [] + + depths = [self.final_depth] + + # apply finish step if necessary + if self.z_finish_step > 0: + if self.z_finish_step < total_depth: + depths.append(self.z_finish_step + self.final_depth) else: - layerstep = self.step_down + return depths - for step in range(layers_required): - d = self.start_depth - ((step +1) * layerstep) - depths.append(d) - - if self.z_finish_step != 0 and depths[-1] != self.final_depth + self.z_finish_step: - depths.append(self.final_depth + self.z_finish_step) - if depths[-1] != self.final_depth: - depths.append(self.final_depth) + if equalstep: + depths += self.__equal_steps(self.start_depth, depths[-1], self.step_down)[1:] + else: + depths += self.__fixed_steps(self.start_depth, depths[-1], self.step_down)[1:] + depths.reverse() return depths + + def __equal_steps(self, start, stop, max_size): + '''returns a list of depths beginning with the bottom (included), ending + with the top (not included). + all steps are of equal size, which is as big as possible but not bigger + than max_size.''' + + steps_needed = math.ceil((start - stop) / max_size) + depths = numpy.linspace(stop, start, steps_needed, endpoint=False) + + return depths.tolist() + + def __fixed_steps(self, start, stop, size): + '''returns a list of depths beginning with the bottom (included), ending + with the top (not included). + all steps are of size 'size' except the one at the bottom wich can be + smaller.''' + + fullsteps = int((start - stop) / size) + last_step = start - (fullsteps * size) + depths = numpy.linspace(last_step, start, fullsteps, endpoint=False) + + if last_step == stop: + return depths.tolist() + else: + return [stop] + depths.tolist() diff --git a/src/Mod/Path/PathTests/TestPathDepthParams.py b/src/Mod/Path/PathTests/TestPathDepthParams.py index 244bb700eb..57e51a5d02 100644 --- a/src/Mod/Path/PathTests/TestPathDepthParams.py +++ b/src/Mod/Path/PathTests/TestPathDepthParams.py @@ -73,7 +73,7 @@ class depthTestCases(unittest.TestCase): final_depth = 10 user_depths = None - expected =[] + expected =[10] d = PU.depth_params(clearance_height, rapid_safety_space, start_depth, step_down, z_finish_step, final_depth, user_depths) r = d.get_depths() @@ -109,7 +109,7 @@ class depthTestCases(unittest.TestCase): self.assertListEqual (r, expected) def test40(self): - '''Finish depth passed in.''' + '''z_finish_step passed in.''' clearance_height= 10 rapid_safety_space = 5 @@ -161,3 +161,23 @@ class depthTestCases(unittest.TestCase): r = d.get_depths(equalstep=True) self.assertListEqual (r, expected) + def test70(self): + '''stepping down with stepdown greater than total depth''' + clearance_height= 10 + rapid_safety_space = 5 + + start_depth = 10 + step_down = 20 + z_finish_step = 1 + final_depth = 0 + user_depths = None + + expected =[1.0, 0] + + d = PU.depth_params(clearance_height, rapid_safety_space, start_depth, step_down, z_finish_step, final_depth, user_depths) + r = d.get_depths(equalstep=True) + self.assertListEqual (r, expected) + + d = PU.depth_params(clearance_height, rapid_safety_space, start_depth, step_down, z_finish_step, final_depth, user_depths) + r = d.get_depths() + self.assertListEqual (r, expected) From bf5dcbd0629036fb0b348b9a197f59af9c1e0ac1 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 6 Jan 2017 18:04:07 +0100 Subject: [PATCH 070/144] move import statement to fix error --- src/Mod/OpenSCAD/OpenSCADUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/OpenSCAD/OpenSCADUtils.py b/src/Mod/OpenSCAD/OpenSCADUtils.py index 26ab35327b..1785d430d0 100644 --- a/src/Mod/OpenSCAD/OpenSCADUtils.py +++ b/src/Mod/OpenSCAD/OpenSCADUtils.py @@ -30,10 +30,10 @@ the module ''' try: + from PySide import QtGui _encoding = QtGui.QApplication.UnicodeUTF8 def translate(context, text): "convenience function for Qt translator" - from PySide import QtGui return QtGui.QApplication.translate(context, text, None, _encoding) except AttributeError: def translate(context, text): From b6cf0e250088bbc7479892e74ebd933971da81a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Fri, 6 Jan 2017 18:23:48 +0100 Subject: [PATCH 071/144] Fix non-GUI usage in BOPTools Only define the translation related functions if the GUI is up --- src/Mod/Part/BOPTools/SplitFeatures.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Mod/Part/BOPTools/SplitFeatures.py b/src/Mod/Part/BOPTools/SplitFeatures.py index 5e9afcec9f..1d639c72a3 100644 --- a/src/Mod/Part/BOPTools/SplitFeatures.py +++ b/src/Mod/Part/BOPTools/SplitFeatures.py @@ -37,18 +37,18 @@ if FreeCAD.GuiUp: #-------------------------- translation-related code ---------------------------------------- #(see forum thread "A new Part tool is being born... JoinFeatures!" #http://forum.freecadweb.org/viewtopic.php?f=22&t=11112&start=30#p90239 ) -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except Exception: - def _fromUtf8(s): - return s -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) + try: + _fromUtf8 = QtCore.QString.fromUtf8 + except Exception: + def _fromUtf8(s): + return s + try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) + except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) #--------------------------/translation-related code ---------------------------------------- def getIconPath(icon_dot_svg): From 95b7610536b3c66db6f5b6f66a63f1c80db877d0 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 6 Jan 2017 18:40:20 +0100 Subject: [PATCH 072/144] show exceptions when activating a workbench as error message, not log message --- src/Gui/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 797e0c0118..62a1c09bad 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -1036,7 +1036,7 @@ bool Application::activateWorkbench(const char* name) } Base::Console().Error("%s\n", (const char*)msg.toLatin1()); - Base::Console().Log("%s\n", e.getStackTrace().c_str()); + Base::Console().Error("%s\n", e.getStackTrace().c_str()); if (!d->startingUp) { wc.restoreCursor(); QMessageBox::critical(getMainWindow(), QObject::tr("Workbench failure"), From 9d555859ad5cb04e6f6f1b0d279e6a1df1e60e66 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 6 Jan 2017 14:14:14 -0800 Subject: [PATCH 073/144] Check for 0 pointer in initialisation for assigning default values. --- src/Mod/Path/App/TooltablePyImp.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/App/TooltablePyImp.cpp b/src/Mod/Path/App/TooltablePyImp.cpp index d339b61950..0b2ad66070 100644 --- a/src/Mod/Path/App/TooltablePyImp.cpp +++ b/src/Mod/Path/App/TooltablePyImp.cpp @@ -121,12 +121,12 @@ int ToolPy::PyInit(PyObject* args, PyObject* kwd) else getToolPtr()->Material = Tool::MATUNDEFINED; - getToolPtr()->Diameter = PyFloat_AsDouble(dia); - getToolPtr()->LengthOffset = PyFloat_AsDouble(len); - getToolPtr()->FlatRadius = PyFloat_AsDouble(fla); - getToolPtr()->CornerRadius = PyFloat_AsDouble(cor); - getToolPtr()->CuttingEdgeAngle = PyFloat_AsDouble(ang); - getToolPtr()->CuttingEdgeHeight = PyFloat_AsDouble(hei); + getToolPtr()->Diameter = dia ? PyFloat_AsDouble(dia) : 0.0; + getToolPtr()->LengthOffset = len ? PyFloat_AsDouble(len) : 0.0; + getToolPtr()->FlatRadius = fla ? PyFloat_AsDouble(fla) : 0.0; + getToolPtr()->CornerRadius = cor ? PyFloat_AsDouble(cor) : 0.0; + getToolPtr()->CuttingEdgeAngle = ang ? PyFloat_AsDouble(ang) : 0.0; + getToolPtr()->CuttingEdgeHeight = hei ? PyFloat_AsDouble(hei) : 0.0; return 0; } From 2830179054362f3a48eba8b32645ca32a975c42a Mon Sep 17 00:00:00 2001 From: brawaga Date: Sat, 7 Jan 2017 15:01:59 +0800 Subject: [PATCH 074/144] Update Fem_ru.ts Added some more translations, corrected some existed. --- .../Fem/Gui/Resources/translations/Fem_ru.ts | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Mod/Fem/Gui/Resources/translations/Fem_ru.ts b/src/Mod/Fem/Gui/Resources/translations/Fem_ru.ts index bf355426e3..e419c17f60 100755 --- a/src/Mod/Fem/Gui/Resources/translations/Fem_ru.ts +++ b/src/Mod/Fem/Gui/Resources/translations/Fem_ru.ts @@ -32,12 +32,12 @@ Create FEM mesh - Создать МКЭ сетку + Создать МКЭ-сетку Create FEM mesh from shape - Создать МКЭ сетку из формы + Создать МКЭ-сетку из формы @@ -207,7 +207,7 @@ Add a part to the Analysis - Добавить новую часть в Анализ + Добавить новую часть в анализ @@ -225,7 +225,7 @@ Create FEM constraint for a bearing - Создать МКЭ ограничения для подшипника + Создать МКЭ-ограничения для подшипника @@ -292,12 +292,12 @@ Create FEM gear constraint - Создать МКЭ передач с ограничениями + Создать передаточное МКЭ-ограничение Create FEM constraint for a gear - Создать МКЭ с ограничениями для передач + Создать МКЭ-ограничение для передачи @@ -328,12 +328,12 @@ Create FEM pulley constraint - Создать МКЭ с ограничениями для шкивов + Создать шкивовое МКЭ-ограничение Create FEM constraint for a pulley - Create FEM constraint for a pulley + Создать МКЭ-ограничение для шкива @@ -347,7 +347,7 @@ Create a FEM analysis - Создать МКЭ анализ + Создать МКЭ-анализ @@ -358,11 +358,11 @@ Create FEM mesh - Создать МКЭ сетку + Создать МКЭ-сетку Create FEM mesh from shape - Создать МКЭ сетку из формы + Создать МКЭ-сетку из формы @@ -386,7 +386,7 @@ Select a single FEM mesh or nodes set, please. - Select a single FEM mesh or nodes set, please. + Выберите одну МКЭ-сетку или набор узлов, пожалуйста. @@ -488,12 +488,12 @@ High frequency limit - Ограничение высокой частоты + Верхнее ограничение частоты Low frequency limit - Ограничение низкой частоты + Нижнее ограничение частоты @@ -519,7 +519,7 @@ Use materials from user defined directory - Use materials from user defined directory + Использовать материалы из каталога, определённого пользователем @@ -535,7 +535,7 @@ Quadrangle - Четырехугольник + Четырёхугольник Maximum length @@ -645,7 +645,7 @@ Edit FEM mesh - Edit FEM mesh + Редактировать МКЭ-сеть @@ -666,7 +666,7 @@ FEM constraint parameters - Параметры зависимостей МКЭ + Параметры ограничений МКЭ @@ -689,7 +689,7 @@ Please use only a single reference for bearing constraint - Пожалуйста, используйте только одну ссылку для подшипников с ограничениями + Пожалуйста, используйте только одну ссылку для ограничений подшипника @@ -761,12 +761,12 @@ Mixed shape types are not possible. Use a second constraint instead - Не возможны смешанные типы формы. Вместо этого используйте второе ограничение + Не разрешены смешанные типы формы. Вместо этого используйте второе ограничение Only faces, edges and vertices can be picked - Можно выбрать только грани, ребра и вершины + Можно выбрать только грани, рёбра и вершины @@ -803,12 +803,12 @@ Mixed shape types are not possible. Use a second constraint instead - Не возможны смешанные типы формы. Вместо этого используйте второе ограничение + Не разрешены смешанные типы формы. Вместо этого используйте второе ограничение Only faces, edges and vertices can be picked - Можно выбрать только грани, ребра и вершины + Можно выбрать только грани, рёбра и вершины @@ -965,7 +965,7 @@ Leave references blank - Leave references blank + Оставить ссылки пустыми @@ -1000,7 +1000,7 @@ Working directory - Working directory + Рабочий каталог @@ -1010,12 +1010,12 @@ Analysis type - Analysis type + Тип анализа Static - Static + Статический @@ -1025,7 +1025,7 @@ Write .inp file - Write .inp file + Записать файл .inp @@ -1063,7 +1063,7 @@ choose... - выберете... + выберите... @@ -1078,7 +1078,7 @@ Leave references blank - Leave references blank + Оставить ссылки пустыми @@ -1088,7 +1088,7 @@ Add reference - Add reference + Добавить ссылку @@ -1126,7 +1126,7 @@ Pa - ПА + Па @@ -1134,7 +1134,7 @@ No active Analysis - Анализ не активен + Нет активного анализа @@ -1192,7 +1192,7 @@ Combo View - Комбо панель + Комбинированный вид combiTab @@ -1711,7 +1711,7 @@ Max. Size: - Max. Size: + Макс. размер: @@ -1726,17 +1726,17 @@ VeryCoarse - VeryCoarse + ОченьГрубо Coarse - Coarse + Грубо Moderate - Moderate + Средне @@ -1776,7 +1776,7 @@ Node count: - Счетчик узлов: + Счётчик узлов: From a0533b7c46c143b68fe8cfed70d3f8ea36c58367 Mon Sep 17 00:00:00 2001 From: ml Date: Fri, 6 Jan 2017 22:16:09 -0800 Subject: [PATCH 075/144] Changed comparison for points - fixes issue if bone is at plunge point. --- src/Mod/Path/PathScripts/PathDressupDogbone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathDressupDogbone.py b/src/Mod/Path/PathScripts/PathDressupDogbone.py index 23e56841ba..9aa008b7b7 100644 --- a/src/Mod/Path/PathScripts/PathDressupDogbone.py +++ b/src/Mod/Path/PathScripts/PathDressupDogbone.py @@ -275,7 +275,7 @@ class Chord (object): return dir == 'Back' or dir == side def connectsTo(self, chord): - return PathGeom.isRoughly(self.End, chord.Start) + return PathGeom.pointsCoincide(self.End, chord.Start) class Bone: def __init__(self, boneId, obj, lastCommand, inChord, outChord, smooth): From 58844feaa16bcee384a376b02caa2d235ba15d62 Mon Sep 17 00:00:00 2001 From: brawaga Date: Sat, 7 Jan 2017 23:55:55 +0800 Subject: [PATCH 076/144] Update Part_ru.ts Added translations, corrected some existed for grammar, syntax or style. --- .../Gui/Resources/translations/Part_ru.ts | 382 +++++++++--------- 1 file changed, 195 insertions(+), 187 deletions(-) diff --git a/src/Mod/Part/Gui/Resources/translations/Part_ru.ts b/src/Mod/Part/Gui/Resources/translations/Part_ru.ts index cc6a30d06b..5e50c917ae 100644 --- a/src/Mod/Part/Gui/Resources/translations/Part_ru.ts +++ b/src/Mod/Part/Gui/Resources/translations/Part_ru.ts @@ -7,7 +7,7 @@ Any Attacher reference type - + Любое @@ -37,7 +37,7 @@ Curve Attacher reference type - + Кривая @@ -61,13 +61,13 @@ Parabola Attacher reference type - + Парабола Hyperbola Attacher reference type - + Гипербола @@ -109,13 +109,13 @@ Object Attacher reference type - + Объект Solid Attacher reference type - Твердотельная модель + Твёрдотельная модель @@ -154,25 +154,25 @@ Focus1 AttachmentPoint mode caption - + Фокус1 Focus of ellipse, parabola, hyperbola. AttachmentPoint mode tooltip - + Фокус эллипса, параболы, гиперболы Focus2 AttachmentPoint mode caption - + Фокус2 Second focus of ellipse and hyperbola. AttachmentPoint mode tooltip - + Второй фокус эллипса и гиперболы @@ -184,13 +184,13 @@ Point is put on edge, MapPathParametr controls where. Additionally, vertex can be linked in for making a projection. AttachmentPoint mode tooltip - + Точка установлена на ребро, положение контролируется MapMathParametr. Дополнительно, можно прикрепить вершину для проецирования. Center of curvature AttachmentPoint mode caption - + Центр кривизны @@ -202,7 +202,7 @@ Center of mass AttachmentPoint mode caption - + Центр масс @@ -214,13 +214,13 @@ Intersection AttachmentPoint mode caption - Пересечение + Пересечение Not implemented AttachmentPoint mode tooltip - + Не реализовано @@ -277,7 +277,7 @@ Object's X AttachmentLine mode caption - + X объекта @@ -290,7 +290,7 @@ Object's Y AttachmentLine mode caption - + Y объекта @@ -302,13 +302,13 @@ Object's Z AttachmentLine mode caption - + Z объекта Axis of curvature AttachmentLine mode caption - + Ось кривизны @@ -320,67 +320,67 @@ Directrix1 AttachmentLine mode caption - + Директриса1 Directrix line for ellipse, parabola, hyperbola. AttachmentLine mode tooltip - + Директриса эллипса, параболы, гиперболы. Directrix2 AttachmentLine mode caption - + Директриса2 Second directrix line for ellipse and hyperbola. AttachmentLine mode tooltip - + Вторая директриса для эллипса и гиперболы Asymptote1 AttachmentLine mode caption - + Асимптота1 Asymptote of a hyperbola. AttachmentLine mode tooltip - + Асимптота гиперболы Asymptote2 AttachmentLine mode caption - + Асимптота2 Second asymptote of hyperbola. AttachmentLine mode tooltip - + Вторая асимптота гиперболы Tangent AttachmentLine mode caption - Тангенс + Касательная Line tangent to an edge. Optional vertex link defines where. AttachmentLine mode tooltip - + Касательная к ребру. Необязательная вершина определяет место касания. Normal AttachmentLine mode caption - + Нормаль @@ -423,13 +423,13 @@ Through two points AttachmentLine mode caption - + Через две точки Line that passes through two vertices. AttachmentLine mode tooltip - + Линия, проходящая через две вершины. @@ -441,7 +441,7 @@ Not implemented. AttachmentLine mode tooltip - + Не реализовано. @@ -453,7 +453,7 @@ Line that spans the shortest distance between shapes. AttachmentLine mode tooltip - + Линия, покрывающая кратчайшее расстояние между фигурами. @@ -522,7 +522,7 @@ Object's XY AttachmentPlane mode caption - + XY объекта @@ -534,7 +534,7 @@ Object's XZ AttachmentPlane mode caption - + XZ объекта @@ -546,7 +546,7 @@ Object's YZ AttachmentPlane mode caption - + YZ объекта @@ -570,13 +570,13 @@ Tangent to surface AttachmentPlane mode caption - + Касательная к поверхности Plane is made tangent to surface at vertex. AttachmentPlane mode tooltip - + Плоскость сделана касательной к поверхности в вершине. @@ -644,7 +644,7 @@ Plane by 3 points AttachmentPlane mode caption - + Плоскость по трём точкам @@ -656,13 +656,13 @@ Normal to 3 points AttachmentPlane mode caption - + Нормаль по трём точкам Plane will pass through first two vertices, and perpendicular to plane that passes through three vertices. AttachmentPlane mode tooltip - + Плоскость будет проходить через первые две вершины, перпендикулярно плоскости, проходящей через три. @@ -719,7 +719,7 @@ Object's X Y Z Attachment3D mode caption - + X Y Z объекта @@ -731,31 +731,31 @@ Object's X Z-Y Attachment3D mode caption - + X Z-Y объекта X', Y', Z' axes are matched with object's local X, Z, -Y, respectively. Attachment3D mode tooltip - + Оси X', Y', Z' сопоставляются с локальными X, Z, -Y объекта соответственно. Object's Y Z X Attachment3D mode caption - + Y Z X объекта X', Y', Z' axes are matched with object's local Y, Z, X, respectively. Attachment3D mode tooltip - + Оси X', Y', Z' сопоставляются с локальными Y, Z, X объекта соответственно. XY on plane Attachment3D mode caption - + XY на плоскости @@ -773,19 +773,19 @@ X' Y' plane is made tangent to surface at vertex. Attachment3D mode tooltip - + Плоскость X'Y' сделана касательной в вершине. Z tangent to edge Attachment3D mode caption - + Z-касательная к ребру. Z' axis is aligned to be tangent to edge. Optional vertex link defines where. Attachment3D mode tooltip - + Ось Z распологается касательно к ребру. Необязательная вершина определяет точку касания. @@ -859,7 +859,7 @@ Align XZ plane to pass through 3 points; X axis will pass through two first points. Attachment3D mode tooltip - + Расположить плоскость XZ так, чтобы проходила через три точки. Ось X пройдёт через первые две. @@ -1102,7 +1102,7 @@ Toggle 3d - Переключить 3d + Переключить 3d @@ -1168,7 +1168,7 @@ Create a cube solid - Создать твердотельный куб + Создать твёрдотельный куб Box @@ -1176,7 +1176,7 @@ Create a box solid - Создать твердотельный параллелепипед + Создать твёрдотельный параллелепипед @@ -1194,7 +1194,7 @@ Create a box solid without dialog - Создать твердотельный параллелепипед без диалога + Создать твёрдотельный параллелепипед без диалога @@ -1212,7 +1212,7 @@ Create a box solid without dialog - Создать твердотельный параллелепипед без диалога + Создать твёрдотельный параллелепипед без диалога @@ -1297,7 +1297,7 @@ Offset: - + Смещение: @@ -1358,7 +1358,7 @@ Create a cone solid - Создать твердотельный конус + Создать твёрдотельный конус @@ -1394,7 +1394,7 @@ Make a cut of two shapes - Выполнить обрезку двух фигур + Отсечь фигуру по фигуре @@ -1583,7 +1583,7 @@ Convert to solid - Преобразовать в твердые + Преобразовать в твёрдые @@ -1753,7 +1753,7 @@ Create ruled surface - Создать линейчатую ​​поверхность + Создать линейчатую поверхность @@ -1762,7 +1762,7 @@ Create a ruled surface from two curves - Создать линейчатую ​​поверхность из двух кривых + Создать линейчатую поверхность из двух кривых @@ -1829,12 +1829,12 @@ Create Cylinder... - Создайте цилиндр ... + Создать цилиндр ... Create a Cylinder - Создайте цилиндр + Создать цилиндр @@ -1842,7 +1842,7 @@ Create a sphere solid - Создать твердотельную сферу + Создать твёрдотельную сферу @@ -1914,7 +1914,7 @@ Create a torus solid - Создать твердотельный тор + Создать твёрдотельный тор @@ -1944,7 +1944,7 @@ DlgRevolution Select a shape for revolution, first. - Сначала выберите фигуру для революции. + Сначала выберите фигуру для вращения. @@ -2008,32 +2008,32 @@ X: - X: + X: Y: - Y: + Y: Z: - Z: + Z: Yaw: - + Угол рыска: Pitch: - Шаг: + Угол атаки: Roll: - + Угол крена: @@ -2173,7 +2173,7 @@ Select a shape on the left side, first - Сначала выберите фигуру на левой стороне + Сначала выберите фигуру слева @@ -2193,17 +2193,17 @@ Performing union on non-solids is not possible - Выполнение объединения без твердых тел не представляется возможным + Выполнение объединения без твёрдых тел не представляется возможным Performing intersection on non-solids is not possible - Выполнение пересечения без твердых тел не представляется возможным + Выполнение пересечения без твёрдых тел не представляется возможным Performing difference on non-solids is not possible - Выполнение вычитания без твердых тел не представляется возможным + Выполнение вычитания без твёрдых тел не представляется возможным @@ -2229,27 +2229,27 @@ If checked, direction of extrusion is reversed. - + Если включено, направление выдавливания обратное. Reversed - + Реверс Specify direction manually using X,Y,Z values. - + Указать направление вручную с использованием значений X, Y и Z. Custom direction: - + Произвольное направление: Extrude perpendicularly to plane of input shape. - + Выдавить перпендикулярно плоскости входной фигуры. @@ -2271,7 +2271,7 @@ Create solid - Создать твердое тело + Создать твёрдое тело @@ -2281,7 +2281,7 @@ Click to start selecting an edge in 3d view. - + Щёлкните, чтобы начать выделение ребра в трёхмерном виде. @@ -2297,7 +2297,7 @@ Along edge: - + Вдоль ребра: @@ -2307,7 +2307,7 @@ Along: - + Вдоль: @@ -2332,7 +2332,7 @@ Symmetric - + Симметрично @@ -2371,7 +2371,7 @@ Select a shape for extrusion, first. - Сначала выберите фигуру для экструзии. + Сначала выберите фигуру для выдавливания. @@ -2381,7 +2381,7 @@ The document '%1' doesn't exist. - The document '%1' doesn't exist. + Документ «%1» не существует. @@ -2389,17 +2389,19 @@ Creating Extrusion failed. %1 - + Выдавливание не удалось + +%1 Object not found: %1 - + Объект не найден: %1 No shapes selected for extrusion. Select some, first. - + Не выбрано фигур для выдавливания. Сначала выберите. @@ -2411,24 +2413,26 @@ Direction mode is to use an edge, but no edge is linked. - + Режим направления должен использовать ребро, но ребро не указано. Can't determine normal vector of shape to be extruded. Please use other mode. (%1) - + Не могу определить вектор нормали фигуры для выдавливания. Используйте другой режим + +(%1) Extrusion direction is zero-length. It must be non-zero. - + Направление выдавливания нулевой длины. Не должно быть так. Total extrusion length is zero (length1 == -length2). It must be nonzero. - + Нулевая суммарная длина выдавливания (длина1 == -длина2). Должна быть ненулевая. Succeeded @@ -2459,7 +2463,7 @@ Select faces - Выберете грани + Выберите грани @@ -2469,7 +2473,7 @@ Select edges - Выберете ребра + Выберите ребра @@ -2509,7 +2513,7 @@ Constant Length - Постоянная длинна + Постоянная длина @@ -2561,7 +2565,7 @@ No edge selected - Нет выбранных ребер + Нет выбранных рёбер @@ -2584,7 +2588,7 @@ Please check one or more edge entities first. Edge%1 - Грань%1 + Край%1 @@ -2595,7 +2599,7 @@ Please check one or more edge entities first. No valid shape is selected. Please select a valid shape in the drop-down box first. - Допустимый профиль не выбран. Пожалуйста сначала выберите допустимый профиль в выпадающем списке. + Допустимый профиль не выбран. Сначала выберите допустимый профиль в выпадающем списке, пожалуйста. @@ -2613,7 +2617,7 @@ Please select a valid shape in the drop-down box first. Units for export of IGES - Единицы для экспорта IGE + Единицы для экспорта IGE @@ -2657,7 +2661,7 @@ Please select a valid shape in the drop-down box first. Skip blank entities - Пропускать пустые сущностей + Пропускать пустые сущности @@ -2925,7 +2929,7 @@ Please select a valid shape in the drop-down box first. Step input file - Шаг входного файла + Входной файл STEP @@ -3007,7 +3011,7 @@ Please select a valid shape in the drop-down box first. Angle - 0 for cyl - Угол - 0 для цилиндра + Угол — 0 для цилиндра Angle0 @@ -3402,43 +3406,43 @@ Please select a valid shape in the drop-down box first. Revolution axis - + Ось вращения Center X: - + X центра: Center Y: - + Y центра: Center Z: - + Z центра: Click to set this as axis - + Щёлкнуть, чтобы выбрать эту ось вращения. Dir. X: - + X направления: Dir. Y: - + Y направления: Dir. Z: - + Z направления: @@ -3489,17 +3493,17 @@ Please select a valid shape in the drop-down box first. Create Solid - Создать твердое тело + Создать твёрдое тело Object not found: %1 - + Объект не найден: %1 Select a shape for revolution, first. - Сначала выберите фигуру для революции. + Сначала выберите фигуру для вращения. @@ -3508,12 +3512,14 @@ Please select a valid shape in the drop-down box first. Revolution axis link is invalid. %1 - + Неверная ссылка на ось вращения. + +%1 Revolution axis direction is zero-length. It must be non-zero. - + Нулевая длина оси вращения. Должна быть ненулевая. @@ -3526,7 +3532,9 @@ Please select a valid shape in the drop-down box first. Creating Revolve failed. %1 - + Создание тела вращения не удалось. + +%1 @@ -3559,12 +3567,12 @@ Please select a valid shape in the drop-down box first. Shape view - Форма представления + Представление формы Tessellation - Мозаика + Тесселяция @@ -3620,7 +3628,7 @@ Please select a valid shape in the drop-down box first. Setting a too small deviation causes the tessellation to take longerand thus freezes or slows down the GUI. - Setting a too small deviation causes the tessellation to take longerand thus freezes or slows down the GUI. + Установка слишком малого отклонения делает тесселяцию долгой и, тем самым, замедляет отзывчивость интерфейса. @@ -3628,7 +3636,7 @@ Please select a valid shape in the drop-down box first. General - Главный + Общий Export @@ -3691,7 +3699,7 @@ Please select a valid shape in the drop-down box first. Default Part colors - Цвета Деталей по умолчанию + Цвета деталей по умолчанию @@ -3772,7 +3780,7 @@ Please select a valid shape in the drop-down box first. Do you really want to cancel? - Вы действительно хотите отменить? + Вы действительно хотите прервать? @@ -3790,7 +3798,7 @@ Please select a valid shape in the drop-down box first. 3D View - 3D Вид + 3D-вид @@ -3803,7 +3811,7 @@ Please select a valid shape in the drop-down box first. Vertex/Edge/Wire/Face - Точка/Ребро/Ломаная/грань + Точка/ребро/ломаная/грань @@ -3870,7 +3878,7 @@ Please select a valid shape in the drop-down box first. Base point - Base point + Базовая точка Base point: @@ -3899,7 +3907,7 @@ Please select a valid shape in the drop-down box first. No such document '%1'. - Нет такого документа '%1'. + Нет документа «%1». @@ -3958,7 +3966,7 @@ Please select a valid shape in the drop-down box first. Select one or more edges - Выберите одино или несколько ребер + Выберите одино или несколько рёбер @@ -3983,7 +3991,7 @@ Please select a valid shape in the drop-down box first. Select a closed set of edges - Выберите ребра, образующие замкнутый контур + Выберите рёбра, образующие замкнутый контур @@ -4012,7 +4020,7 @@ Please select a valid shape in the drop-down box first. Vertex/Edge/Wire/Face - Точка/Ребро/Ломаная/грань + Точка/ребро/ломаная/грань @@ -4023,12 +4031,12 @@ Please select a valid shape in the drop-down box first. Select an edge or wire you want to sweep along. - Выбрать грани или направляющие для связи вместе. + Выбрать грани или направляющие для сдвига вдоль. Select one or more connected edges you want to sweep along. - Select one or more connected edges you want to sweep along. + Выберите одно или более связанных рёбер, вдоль которых нужно осуществить сдвиг. @@ -4038,7 +4046,7 @@ Please select a valid shape in the drop-down box first. At least one edge or wire is required. - Требуется по крайней мере одна или последовательность граней. + Требуется по крайней мере одно ребро или ломаная. @@ -4048,7 +4056,7 @@ Please select a valid shape in the drop-down box first. '%1' cannot be used as profile and path. - '%1' не может использоваться в качестве профиля и пути. + «%1» не может использоваться в качестве профиля и пути. @@ -4063,7 +4071,7 @@ Please select a valid shape in the drop-down box first. Select one or more connected edges in the 3d view and press 'Done' - Выберите один или несколько подключенных краев в 3d и нажмите «OK» + Выберите один или несколько подключенных краёв в 3d и нажмите «OK» @@ -4082,27 +4090,27 @@ Please select a valid shape in the drop-down box first. Selection accepted - + Выбор принят Reference 1 - + Ссылка 1 Reference 2 - + Ссылка 2 Reference 3 - + Ссылка 3 Reference 4 - + Ссылка 4 @@ -4123,32 +4131,32 @@ Please select a valid shape in the drop-down box first. X: - X: + X: Y: - Y: + Y: Z: - Z: + Z: Yaw: - + Угол рыска: Pitch: - Шаг: + Угол тангажа: Roll: - + Угол крена: @@ -4163,7 +4171,7 @@ Please select a valid shape in the drop-down box first. unknown error - + неизвестная ошибка @@ -4188,17 +4196,17 @@ Please select a valid shape in the drop-down box first. Face - Поверхность + Грань Edge - Ребро + Ребро Vertex - Вершина + Вершина @@ -4208,7 +4216,7 @@ Please select a valid shape in the drop-down box first. Reference%1 - + Ссылка%1 @@ -4429,7 +4437,7 @@ Please select a valid shape in the drop-down box first. Click on the faces in the 3d view to select them. - Нажмите на грани в 3D виде, чтобы выделить их. + Нажмите на грани в 3D-виде, чтобы выделить их. @@ -4444,7 +4452,7 @@ Please select a valid shape in the drop-down box first. Box selection - Выделить область + Выделить прямоугольный объём @@ -4493,7 +4501,7 @@ Please select a valid shape in the drop-down box first. Create solid - Создать твердое тело + Создать твёрдое тело @@ -4532,7 +4540,7 @@ Please select a valid shape in the drop-down box first. RectoVerso - Туда - Сюда + Туда-сюда @@ -4547,7 +4555,7 @@ Please select a valid shape in the drop-down box first. Tangent - Тангенс + Касательная @@ -4558,7 +4566,7 @@ Please select a valid shape in the drop-down box first. Self-intersection - Авто-перекрытее + Авто-перекрытие @@ -4587,7 +4595,7 @@ Please select a valid shape in the drop-down box first. Edge from vertices - Ребро из вершинам + Ребро из вершин @@ -4597,7 +4605,7 @@ Please select a valid shape in the drop-down box first. Face from edges - Грань из ребер + Грань из рёбер @@ -4612,7 +4620,7 @@ Please select a valid shape in the drop-down box first. Solid from shell - Твердое тело из оболочки + Твёрдое тело из оболочки @@ -4645,7 +4653,7 @@ Please select a valid shape in the drop-down box first. Create solid - Создать твердое тело + Создать твёрдое тело @@ -4656,7 +4664,7 @@ Please select a valid shape in the drop-down box first. Select one or more profiles and select an edge or wire in the 3D view for the sweep path. - Выбрать один или более профилей, а также выбрать грань или последовательность граней в 3D виде для очистки пути. + Выберите один или более профилей, а также грань или последовательность граней в 3D-виде для очистки пути. @@ -4671,7 +4679,7 @@ in the 3D view for the sweep path. Select faces of the source object and press 'Done' - Выбрать поверхность исходного объекта и нажать "Продолжить" + Выбрать поверхность исходного объекта и нажать «Продолжить» @@ -4750,7 +4758,7 @@ in the 3D view for the sweep path. Non-solids selected - Выбраны не твердотельные + Выбраны не твёрдотельные @@ -4758,7 +4766,7 @@ in the 3D view for the sweep path. The use of non-solids for boolean operations may lead to unexpected results. Do you want to continue? - Использование твердых тел для булевых операций может привести к непредвиденным результатам. Вы хотите продолжить? + Использование твёрдых тел для булевых операций может привести к непредвиденным результатам. Вы хотите продолжить? @@ -4803,7 +4811,7 @@ Do you want to continue? Enter tolerance for sewing shape: - Введите допустимость для шитья формы: + Введите допуск для шитья формы: @@ -4848,7 +4856,7 @@ Do you want to continue? Select two shapes or more, please. - Пожалуйста выберите несколько форм. + Пожалуйста, выберите несколько форм. @@ -4868,7 +4876,7 @@ Do you want to continue? Edit fillet edges - Изменение краев ленты + Изменение краёв ленты @@ -4888,7 +4896,7 @@ Do you want to continue? Solid - Твердотельная модель + Твёрдотельная модель @@ -4932,7 +4940,7 @@ Do you want to continue? No Error - Ошибок нет + Ошибки нет @@ -4952,17 +4960,17 @@ Do you want to continue? No 3D Curve - Отсутствует 3D искривление + Отсутствует 3D-искривление Multiple 3D Curve - Множественное 3D искривление + Множественное 3D-искривление Invalid 3D Curve - Недопустимое 3D искривление + Недопустимое 3D-искривление @@ -5022,12 +5030,12 @@ Do you want to continue? Self Intersecting Wire - Авто-пересекающаяся связб + Авто-пересекающаяся связь No Surface - Не поверхность + Нет поверхности @@ -5067,17 +5075,17 @@ Do you want to continue? Not Closed - Не закрыт + Не замкнут Not Connected - Не подключен + Не подсоединён Sub Shape Not In Shape - Подфигура не находится в форме + Подфигура не находится в фигуре @@ -5107,17 +5115,17 @@ Do you want to continue? Out Of Enum Range: - Пределы Enum: + Пределы перечисления: BOPAlgo CheckUnknown - BOPAlgo ПроверитьНеизвестную + BOPAlgo ПроверитьНеизвестную BOPAlgo BadType - BOPAlgo ЛожныйТип + BOPAlgo ЛожныйТип @@ -5132,7 +5140,7 @@ Do you want to continue? BOPAlgo NonRecoverableFace - BOPAlgo НеВосстанавимаяПоверхность + BOPAlgo НеВосстановимаяПоверхность @@ -5195,7 +5203,7 @@ Do you want to continue? Toggle 3d - Переключить 3d + Переключить 3d From 5d2e834bc38589d72ad1d39ef1cc1e33a982f5e8 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Sat, 7 Jan 2017 14:04:24 -0200 Subject: [PATCH 077/144] Draft: Fixed spline bug in DXF importer --- src/Mod/Draft/importDXF.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Draft/importDXF.py b/src/Mod/Draft/importDXF.py index 9d2b864c1c..9b5a32ff70 100644 --- a/src/Mod/Draft/importDXF.py +++ b/src/Mod/Draft/importDXF.py @@ -776,6 +776,8 @@ non-parametric curve""" warn('polygon fallback on %s' %spline) return drawSplineIterpolation(controlpoints,closed=closed,\ forceShape=forceShape,alwaysDiscretize=True) + if fitpoints and not(controlpoints): + return drawSplineIterpolation(fitpoints,closed=closed,forceShape=forceShape) try: bspline=Part.BSplineCurve() bspline.buildFromPolesMultsKnots(poles=controlpoints,mults=multvector,\ From 162c74c22ef11ceda644d340bd9902c08b621393 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Sat, 7 Jan 2017 14:20:58 -0200 Subject: [PATCH 078/144] Draft: Moved dimension decimals preference setting to the dimension preferences tab - issue #2832 --- .../Draft/Resources/ui/preferences-draft.ui | 40 ----------- .../Resources/ui/preferences-drafttexts.ui | 69 +++++++++++++++---- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/Mod/Draft/Resources/ui/preferences-draft.ui b/src/Mod/Draft/Resources/ui/preferences-draft.ui index 5eda200189..25a3af1df8 100755 --- a/src/Mod/Draft/Resources/ui/preferences-draft.ui +++ b/src/Mod/Draft/Resources/ui/preferences-draft.ui @@ -222,46 +222,6 @@ - - - - - - Dimensions precision level - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - 8 - - - 2 - - - dimPrecision - - - Mod/Draft - - - - - diff --git a/src/Mod/Draft/Resources/ui/preferences-drafttexts.ui b/src/Mod/Draft/Resources/ui/preferences-drafttexts.ui index 4c8629c7e5..213e2cc6a3 100644 --- a/src/Mod/Draft/Resources/ui/preferences-drafttexts.ui +++ b/src/Mod/Draft/Resources/ui/preferences-drafttexts.ui @@ -7,7 +7,7 @@ 0 0 522 - 462 + 473 @@ -153,6 +153,46 @@ such as "Arial:Bold" + + + + + + Number of decimals + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 8 + + + 2 + + + dimPrecision + + + Mod/Draft + + + + + @@ -364,7 +404,7 @@ such as "Arial:Bold" - + 300 @@ -405,6 +445,21 @@ such as "Arial:Bold" qPixmapFromMimeSource + + Gui::FileChooser + QWidget +
Gui/FileDialog.h
+
+ + Gui::PrefFileChooser + Gui::FileChooser +
Gui/PrefWidgets.h
+
+ + Gui::PrefSpinBox + QSpinBox +
Gui/PrefWidgets.h
+
Gui::PrefCheckBox QCheckBox @@ -425,16 +480,6 @@ such as "Arial:Bold" QDoubleSpinBox
Gui/PrefWidgets.h
- - Gui::FileChooser - QWidget -
Gui/FileDialog.h
-
- - Gui::PrefFileChooser - Gui::FileChooser -
Gui/PrefWidgets.h
-
From ba38e06bd2c1eb19cdd2b54dbe76fdc48d67014b Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:05:51 +0100 Subject: [PATCH 079/144] FEM: result object, typo --- src/Mod/Fem/App/FemResultObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/App/FemResultObject.h b/src/Mod/Fem/App/FemResultObject.h index a6202024ff..b99680fa30 100644 --- a/src/Mod/Fem/App/FemResultObject.h +++ b/src/Mod/Fem/App/FemResultObject.h @@ -49,7 +49,7 @@ public: App::PropertyFloatList Stats; /// Displacement vectors of analysis App::PropertyVectorList DisplacementVectors; - /// Lengths of displacement vestors of analysis + /// Lengths of displacement vectors of analysis App::PropertyFloatList DisplacementLengths; /// Von Mises Stress values of analysis App::PropertyFloatList StressValues; From b5bfc01a3fdaae00cc1083be000b20399d4e727b Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:09 +0100 Subject: [PATCH 080/144] FEM: frd reader, typo --- src/Mod/Fem/ccxFrdReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/ccxFrdReader.py b/src/Mod/Fem/ccxFrdReader.py index d312ebc81d..b7963db133 100644 --- a/src/Mod/Fem/ccxFrdReader.py +++ b/src/Mod/Fem/ccxFrdReader.py @@ -266,7 +266,7 @@ def readResult(frd_input): mode_disp[elem] = FreeCAD.Vector(mode_disp_x, mode_disp_y, mode_disp_z) if line[5:11] == "STRESS": mode_stress_found = True - # we found a displacement line in the frd file + # we found a stress line in the frd file if mode_stress_found and (line[1:3] == "-1"): elem = int(line[4:13]) stress_1 = float(line[13:25]) From 3ec0a50a06a1f7191371c3dc5e1e3de5556a8fff Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:14 +0100 Subject: [PATCH 081/144] FEM: VTK tools, fix value assignment --- src/Mod/Fem/App/FemVTKTools.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Fem/App/FemVTKTools.cpp b/src/Mod/Fem/App/FemVTKTools.cpp index c767beb8c8..f0d3c53f98 100644 --- a/src/Mod/Fem/App/FemVTKTools.cpp +++ b/src/Mod/Fem/App/FemVTKTools.cpp @@ -770,7 +770,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->MaxShear.getValues().empty()) { const std::vector& vec = res->MaxShear.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -782,7 +782,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->PrincipalMax.getValues().empty()) { const std::vector& vec = res->PrincipalMax.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -794,7 +794,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->PrincipalMax.getValues().empty()) { const std::vector& vec = res->PrincipalMin.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -806,7 +806,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->Temperature.getValues().empty()) { const std::vector& vec = res->Temperature.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -818,7 +818,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->UserDefined.getValues().empty()) { const std::vector& vec = res->UserDefined.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -831,7 +831,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar } - if(!res->StressValues.getValues().empty()) { + if(!res->DisplacementVectors.getValues().empty()) { const std::vector& vec = res->DisplacementVectors.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfComponents(3); From edb1f0249e67fc5b0e267bb7ea6f908f222c5af2 Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:17 +0100 Subject: [PATCH 082/144] FEM: VTK tools, only import results into vtk if they exists --- src/Mod/Fem/App/FemVTKTools.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Mod/Fem/App/FemVTKTools.cpp b/src/Mod/Fem/App/FemVTKTools.cpp index f0d3c53f98..2e30251c84 100644 --- a/src/Mod/Fem/App/FemVTKTools.cpp +++ b/src/Mod/Fem/App/FemVTKTools.cpp @@ -760,6 +760,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar const FemResultObject* res = static_cast(obj); if(!res->StressValues.getValues().empty()) { const std::vector& vec = res->StressValues.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Von Mises stress"); @@ -768,10 +769,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} if(!res->MaxShear.getValues().empty()) { const std::vector& vec = res->MaxShear.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Max shear stress (Tresca)"); @@ -780,10 +782,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} if(!res->PrincipalMax.getValues().empty()) { const std::vector& vec = res->PrincipalMax.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Maximum Principal stress"); @@ -792,10 +795,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} if(!res->PrincipalMax.getValues().empty()) { const std::vector& vec = res->PrincipalMin.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Minimum Principal stress"); @@ -804,10 +808,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} - if(!res->Temperature.getValues().empty()) { + if (!res->Temperature.getValues().empty()) { const std::vector& vec = res->Temperature.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Temperature"); @@ -816,10 +821,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} - if(!res->UserDefined.getValues().empty()) { + if (!res->UserDefined.getValues().empty()) { const std::vector& vec = res->UserDefined.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("User Defined Results"); @@ -828,11 +834,12 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} if(!res->DisplacementVectors.getValues().empty()) { const std::vector& vec = res->DisplacementVectors.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfComponents(3); data->SetName("Displacement"); @@ -843,7 +850,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar } grid->GetPointData()->AddArray(data); - } + }} } } // namespace From c72cd509bcc4ac47d12712fa1065f41404feb6bd Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:21 +0100 Subject: [PATCH 083/144] FEM: mesh group, add object --- src/Mod/Fem/App/CMakeLists.txt | 3 + src/Mod/Fem/CMakeLists.txt | 4 ++ src/Mod/Fem/FemMeshGroup.py | 49 +++++++++++++ src/Mod/Fem/_FemMeshGmsh.py | 3 + src/Mod/Fem/_FemMeshGroup.py | 40 +++++++++++ src/Mod/Fem/_ViewProviderFemMeshGmsh.py | 2 +- src/Mod/Fem/_ViewProviderFemMeshGroup.py | 89 ++++++++++++++++++++++++ 7 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Fem/FemMeshGroup.py create mode 100644 src/Mod/Fem/_FemMeshGroup.py create mode 100644 src/Mod/Fem/_ViewProviderFemMeshGroup.py diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index 9502efc15e..7b92079544 100755 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -83,6 +83,7 @@ SET(FemScripts_SRCS _FemConstraintSelfWeight.py _FemMaterialMechanicalNonlinear.py _FemMeshGmsh.py + _FemMeshGroup.py _FemMeshRegion.py _FemShellThickness.py _FemSolverCalculix.py @@ -99,6 +100,7 @@ SET(FemScripts_SRCS _ViewProviderFemConstraintSelfWeight.py _ViewProviderFemMaterialMechanicalNonlinear.py _ViewProviderFemMeshGmsh.py + _ViewProviderFemMeshGroup.py _ViewProviderFemMeshRegion.py _ViewProviderFemShellThickness.py _ViewProviderFemSolverCalculix.py @@ -122,6 +124,7 @@ SET(FemScripts_SRCS FemMaterialMechanicalNonlinear.py FemMesh2Mesh.py FemMeshGmsh.py + FemMeshGroup.py FemMeshRegion.py FemMeshTools.py FemShellThickness.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 6a318ae8c8..46bce3766b 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -55,6 +55,10 @@ INSTALL( FemMesh2Mesh.py _CommandFEMMesh2Mesh.py + FemMeshGroup.py + _FemMeshGroup.py + _ViewProviderFemMeshGroup.py + FemMeshRegion.py _FemMeshRegion.py _ViewProviderFemMeshRegion.py diff --git a/src/Mod/Fem/FemMeshGroup.py b/src/Mod/Fem/FemMeshGroup.py new file mode 100644 index 0000000000..d937ca2361 --- /dev/null +++ b/src/Mod/Fem/FemMeshGroup.py @@ -0,0 +1,49 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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 * +# * * +# *************************************************************************** + +__title__ = "FemMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## \addtogroup FEM +# @{ + +import FreeCAD +import _FemMeshGroup + + +def makeFemMeshGroup(base_mesh, use_label=False, name="FEMMeshGroup"): + '''makeFemMeshGroup([length], [name]): creates a FEM mesh region object to define properties for a regon of a FEM mesh''' + obj = FreeCAD.ActiveDocument.addObject("Fem::FeaturePython", name) + _FemMeshGroup._FemMeshGroup(obj) + obj.UseLabel = use_label + # obj.BaseMesh = base_mesh + # App::PropertyLinkList does not support append, we will use a temporary list to append the mesh group obj. to the list + tmplist = base_mesh.MeshGroupList + tmplist.append(obj) + base_mesh.MeshGroupList = tmplist + if FreeCAD.GuiUp: + import _ViewProviderFemMeshGroup + _ViewProviderFemMeshGroup._ViewProviderFemMeshGroup(obj.ViewObject) + return obj + +# @} diff --git a/src/Mod/Fem/_FemMeshGmsh.py b/src/Mod/Fem/_FemMeshGmsh.py index ebbffe79ca..0cf2ebbca3 100644 --- a/src/Mod/Fem/_FemMeshGmsh.py +++ b/src/Mod/Fem/_FemMeshGmsh.py @@ -46,6 +46,9 @@ class _FemMeshGmsh(): obj.addProperty("App::PropertyLinkList", "MeshRegionList", "Base", "Mesh regions of the mesh") obj.MeshRegionList = [] + obj.addProperty("App::PropertyLinkList", "MeshGroupList", "Base", "Mesh groups of the mesh") + obj.MeshRegionList = [] + obj.addProperty("App::PropertyLink", "Part", "FEM Mesh", "Part object to mesh") obj.Part = None diff --git a/src/Mod/Fem/_FemMeshGroup.py b/src/Mod/Fem/_FemMeshGroup.py new file mode 100644 index 0000000000..84380dac6a --- /dev/null +++ b/src/Mod/Fem/_FemMeshGroup.py @@ -0,0 +1,40 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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 * +# * * +# *************************************************************************** + +__title__ = "_FemMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## @package FemMeshGroup +# \ingroup FEM + + +class _FemMeshGroup: + "The FemMeshGroup object" + def __init__(self, obj): + obj.addProperty("App::PropertyBool", "UseLabel", "MeshGroupProperties", "The identifier used for export (True: Label, False: Name)") + obj.addProperty("App::PropertyLinkSubList", "References", "MeshGroupShapes", "List of FEM mesh group shapes") + obj.Proxy = self + self.Type = "FemMeshGroup" + + def execute(self, obj): + return diff --git a/src/Mod/Fem/_ViewProviderFemMeshGmsh.py b/src/Mod/Fem/_ViewProviderFemMeshGmsh.py index 02f3a7880b..7948933a8e 100644 --- a/src/Mod/Fem/_ViewProviderFemMeshGmsh.py +++ b/src/Mod/Fem/_ViewProviderFemMeshGmsh.py @@ -112,7 +112,7 @@ class _ViewProviderFemMeshGmsh: return None def claimChildren(self): - return self.Object.MeshRegionList + return (self.Object.MeshRegionList + self.Object.MeshGroupList) def onDelete(self, feature, subelements): try: diff --git a/src/Mod/Fem/_ViewProviderFemMeshGroup.py b/src/Mod/Fem/_ViewProviderFemMeshGroup.py new file mode 100644 index 0000000000..c03377f0be --- /dev/null +++ b/src/Mod/Fem/_ViewProviderFemMeshGroup.py @@ -0,0 +1,89 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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 * +# * * +# *************************************************************************** + +__title__ = "_ViewProviderFemMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## @package ViewProviderFemMeshGroup +# \ingroup FEM + +import FreeCAD +import FreeCADGui +from pivy import coin + + +class _ViewProviderFemMeshGroup: + "A View Provider for the FemMeshGroup object" + def __init__(self, vobj): + vobj.Proxy = self + + def getIcon(self): + return ":/icons/fem-femmesh-from-shape.svg" + + def attach(self, vobj): + self.ViewObject = vobj + self.Object = vobj.Object + self.standard = coin.SoGroup() + vobj.addDisplayMode(self.standard, "Standard") + + def getDisplayModes(self, obj): + return ["Standard"] + + def getDefaultDisplayMode(self): + return "Standard" + + def updateData(self, obj, prop): + return + + def onChanged(self, vobj, prop): + return + + def setEdit(self, vobj, mode=0): + # hide all meshes + for o in FreeCAD.ActiveDocument.Objects: + if o.isDerivedFrom("Fem::FemMeshObject"): + o.ViewObject.hide() + # show task panel + import _TaskPanelFemMeshGroup + taskd = _TaskPanelFemMeshGroup._TaskPanelFemMeshGroup(self.Object) + taskd.obj = vobj.Object + FreeCADGui.Control.showDialog(taskd) + return True + + def unsetEdit(self, vobj, mode=0): + FreeCADGui.Control.closeDialog() + return + + def doubleClicked(self, vobj): + doc = FreeCADGui.getDocument(vobj.Object.Document) + if not doc.getInEdit(): + doc.setEdit(vobj.Object.Name) + else: + FreeCAD.Console.PrintError('Active Task Dialog found! Please close this one first!\n') + return True + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None From d1954aef48250a15636361569f9103649209fd91 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:24 +0100 Subject: [PATCH 084/144] FEM: mesh group, add command to FreeCAD GUI menu and tool bar --- src/Mod/Fem/App/CMakeLists.txt | 1 + src/Mod/Fem/CMakeLists.txt | 1 + src/Mod/Fem/Gui/Workbench.cpp | 2 ++ src/Mod/Fem/InitGui.py | 1 + src/Mod/Fem/_CommandMeshGroup.py | 57 ++++++++++++++++++++++++++++++++ 5 files changed, 62 insertions(+) create mode 100644 src/Mod/Fem/_CommandMeshGroup.py diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index 7b92079544..d27e6760df 100755 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -71,6 +71,7 @@ SET(FemScripts_SRCS _CommandMaterial.py _CommandMeshGmshFromShape.py _CommandMeshNetgenFromShape.py + _CommandMeshGroup.py _CommandMeshRegion.py _CommandPrintMeshInfo.py _CommandPurgeResults.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 46bce3766b..3324b2e53f 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -58,6 +58,7 @@ INSTALL( FemMeshGroup.py _FemMeshGroup.py _ViewProviderFemMeshGroup.py + _CommandMeshGroup.py FemMeshRegion.py _FemMeshRegion.py diff --git a/src/Mod/Fem/Gui/Workbench.cpp b/src/Mod/Fem/Gui/Workbench.cpp index 63b25d2583..7f586c73b0 100755 --- a/src/Mod/Fem/Gui/Workbench.cpp +++ b/src/Mod/Fem/Gui/Workbench.cpp @@ -71,6 +71,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "Fem_MeshNetgenFromShape" << "Fem_MeshGmshFromShape" << "Fem_MeshRegion" + << "Fem_MeshGroup" //<< "Fem_CreateNodesSet" << "Separator" << "Fem_Material" @@ -143,6 +144,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "Fem_MeshNetgenFromShape" << "Fem_MeshGmshFromShape" << "Fem_MeshRegion" + << "Fem_MeshGroup" << "Fem_CreateNodesSet" << "Separator" << "Fem_Material" diff --git a/src/Mod/Fem/InitGui.py b/src/Mod/Fem/InitGui.py index dbe0ce629d..c9832faa7b 100644 --- a/src/Mod/Fem/InitGui.py +++ b/src/Mod/Fem/InitGui.py @@ -54,6 +54,7 @@ class FemWorkbench (Workbench): import _CommandFEMMesh2Mesh import _CommandMeshGmshFromShape import _CommandMeshNetgenFromShape + import _CommandMeshGroup import _CommandMeshRegion import _CommandAnalysis import _CommandShellThickness diff --git a/src/Mod/Fem/_CommandMeshGroup.py b/src/Mod/Fem/_CommandMeshGroup.py new file mode 100644 index 0000000000..242718ea75 --- /dev/null +++ b/src/Mod/Fem/_CommandMeshGroup.py @@ -0,0 +1,57 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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 * +# * * +# *************************************************************************** + +__title__ = "_CommandMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## @package CommandMeshGroup +# \ingroup FEM + +import FreeCAD +from FemCommands import FemCommands +import FreeCADGui +from PySide import QtCore + + +class _CommandMeshGroup(FemCommands): + "The Fem_MeshGroup command definition" + def __init__(self): + super(_CommandMeshGroup, self).__init__() + self.resources = {'Pixmap': 'fem-femmesh-from-shape', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Fem_MeshGroup", "FEM mesh group"), + 'Accel': "M, G", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Fem_MeshGroup", "Creates a FEM mesh group")} + self.is_active = 'with_gmsh_femmesh' + + def Activated(self): + FreeCAD.ActiveDocument.openTransaction("Create FemMeshGroup") + FreeCADGui.addModule("FemMeshGroup") + sel = FreeCADGui.Selection.getSelection() + if (len(sel) == 1): + sobj = sel[0] + if len(sel) == 1 and hasattr(sobj, "Proxy") and sobj.Proxy.Type == "FemMeshGmsh": + FreeCADGui.doCommand("FemMeshGroup.makeFemMeshGroup(App.ActiveDocument." + sobj.Name + ")") + + FreeCADGui.Selection.clearSelection() + +FreeCADGui.addCommand('Fem_MeshGroup', _CommandMeshGroup()) From 1973efac20707dd2d23a852275c600ca3ad87c2a Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:28 +0100 Subject: [PATCH 085/144] FEM: mesh group, add task panel --- src/Mod/Fem/App/CMakeLists.txt | 2 + src/Mod/Fem/CMakeLists.txt | 2 + src/Mod/Fem/TaskPanelFemMeshGroup.ui | 120 ++++++++++++++++ src/Mod/Fem/_TaskPanelFemMeshGroup.py | 190 ++++++++++++++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 src/Mod/Fem/TaskPanelFemMeshGroup.ui create mode 100644 src/Mod/Fem/_TaskPanelFemMeshGroup.py diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index d27e6760df..551d956c23 100755 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -92,6 +92,7 @@ SET(FemScripts_SRCS _FemMaterial.py _TaskPanelFemBeamSection.py _TaskPanelFemMeshGmsh.py + _TaskPanelFemMeshGroup.py _TaskPanelFemMeshRegion.py _TaskPanelFemShellThickness.py _TaskPanelFemSolverCalculix.py @@ -140,6 +141,7 @@ SET(FemScripts_SRCS z88DispReader.py TaskPanelFemBeamSection.ui TaskPanelFemMeshGmsh.ui + TaskPanelFemMeshGroup.ui TaskPanelFemMeshRegion.ui TaskPanelFemShellThickness.ui TaskPanelFemSolverCalculix.ui diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 3324b2e53f..308e3d580d 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -59,6 +59,8 @@ INSTALL( _FemMeshGroup.py _ViewProviderFemMeshGroup.py _CommandMeshGroup.py + _TaskPanelFemMeshGroup.py + TaskPanelFemMeshGroup.ui FemMeshRegion.py _FemMeshRegion.py diff --git a/src/Mod/Fem/TaskPanelFemMeshGroup.ui b/src/Mod/Fem/TaskPanelFemMeshGroup.ui new file mode 100644 index 0000000000..6a7cf9bb90 --- /dev/null +++ b/src/Mod/Fem/TaskPanelFemMeshGroup.ui @@ -0,0 +1,120 @@ + + + Form + + + + 0 + 0 + 350 + 500 + + + + Mesh group + + + + + + + 16777215 + 1677215 + + + + Identifier used for mesh export + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Name + + + true + + + + + + + Label + + + + + + + + + + + + References + + + + + + Add reference + + + + + + + + + + + + Solid + + + + + + + Face, Edge, Vertex + + + true + + + + + + + <html><head/><body><p>Selection</p></body></html> + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/Mod/Fem/_TaskPanelFemMeshGroup.py b/src/Mod/Fem/_TaskPanelFemMeshGroup.py new file mode 100644 index 0000000000..018d882a35 --- /dev/null +++ b/src/Mod/Fem/_TaskPanelFemMeshGroup.py @@ -0,0 +1,190 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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 * +# * * +# *************************************************************************** + +__title__ = "_TaskPanelFemMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## @package TaskPanelFemMeshGroup +# \ingroup FEM + +import FreeCAD +import FreeCADGui +from PySide import QtGui +from PySide import QtCore + + +class _TaskPanelFemMeshGroup: + '''The TaskPanel for editing References property of FemMeshGroup objects''' + def __init__(self, obj): + FreeCADGui.Selection.clearSelection() + self.sel_server = None + self.obj = obj + self.selection_mode_solid = False + self.selection_mode_std_print_message = "Select Faces, Edges and Vertices by single click on them to add them to the list." + self.selection_mode_solid_print_message = "Select Solids by single click on a Face or Edge which belongs to the Solid, to add the Solid to the list." + + self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Fem/TaskPanelFemMeshGroup.ui") + QtCore.QObject.connect(self.form.rb_name, QtCore.SIGNAL("toggled(bool)"), self.choose_exportidentifier_name) + QtCore.QObject.connect(self.form.rb_label, QtCore.SIGNAL("toggled(bool)"), self.choose_exportidentifier_label) + QtCore.QObject.connect(self.form.rb_standard, QtCore.SIGNAL("toggled(bool)"), self.choose_selection_mode_standard) + QtCore.QObject.connect(self.form.rb_solid, QtCore.SIGNAL("toggled(bool)"), self.choose_selection_mode_solid) + QtCore.QObject.connect(self.form.pushButton_Reference, QtCore.SIGNAL("clicked()"), self.add_references) + self.form.list_References.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.form.list_References.connect(self.form.list_References, QtCore.SIGNAL("customContextMenuRequested(QPoint)"), self.references_list_right_clicked) + + self.get_meshgroup_props() + self.update() + + def accept(self): + self.set_meshgroup_props() + if self.sel_server: + FreeCADGui.Selection.removeObserver(self.sel_server) + FreeCADGui.ActiveDocument.resetEdit() + FreeCAD.ActiveDocument.recompute() + return True + + def reject(self): + if self.sel_server: + FreeCADGui.Selection.removeObserver(self.sel_server) + FreeCADGui.ActiveDocument.resetEdit() + return True + + def get_meshgroup_props(self): + self.use_label = self.obj.UseLabel + self.references = [] + if self.obj.References: + self.tuplereferences = self.obj.References + self.get_references() + + def set_meshgroup_props(self): + self.obj.References = self.references + self.obj.UseLabel = self.use_label + + def update(self): + 'fills the widgets' + self.form.rb_name.setChecked(not self.use_label) + self.form.rb_label.setChecked(self.use_label) + self.rebuild_list_References() + + def choose_exportidentifier_name(self, state): + self.use_label = not state + + def choose_exportidentifier_label(self, state): + self.use_label = state + + def choose_selection_mode_standard(self, state): + self.selection_mode_solid = not state + if self.sel_server and not self.selection_mode_solid: + print(self.selection_mode_std_print_message) + + def choose_selection_mode_solid(self, state): + self.selection_mode_solid = state + if self.sel_server and self.selection_mode_solid: + print(self.selection_mode_solid_print_message) + + def get_references(self): + for ref in self.tuplereferences: + for elem in ref[1]: + self.references.append((ref[0], elem)) + + def references_list_right_clicked(self, QPos): + self.form.contextMenu = QtGui.QMenu() + menu_item = self.form.contextMenu.addAction("Remove Reference") + if not self.references: + menu_item.setDisabled(True) + self.form.connect(menu_item, QtCore.SIGNAL("triggered()"), self.remove_reference) + parentPosition = self.form.list_References.mapToGlobal(QtCore.QPoint(0, 0)) + self.form.contextMenu.move(parentPosition + QPos) + self.form.contextMenu.show() + + def remove_reference(self): + if not self.references: + return + currentItemName = str(self.form.list_References.currentItem().text()) + for ref in self.references: + refname_to_compare_listentry = ref[0].Name + ':' + ref[1] + if refname_to_compare_listentry == currentItemName: + self.references.remove(ref) + self.rebuild_list_References() + + def add_references(self): + '''Called if Button add_reference is triggered''' + # in constraints EditTaskPanel the selection is active as soon as the taskpanel is open + # here the addReference button EditTaskPanel has to be triggered to start selection mode + FreeCADGui.Selection.clearSelection() + # start SelectionObserver and parse the function to add the References to the widget + if self.selection_mode_solid: # print message on button click + print_message = self.selection_mode_solid_print_message + else: + print_message = self.selection_mode_std_print_message + import FemSelectionObserver + self.sel_server = FemSelectionObserver.FemSelectionObserver(self.selectionParser, print_message) + + def selectionParser(self, selection): + print('selection: ', selection[0].Shape.ShapeType, ' ', selection[0].Name, ' ', selection[1]) + if hasattr(selection[0], "Shape") and selection[1]: + elt = selection[0].Shape.getElement(selection[1]) + if self.selection_mode_solid: + # in solid selection mode use edges and faces for selection of a solid + solid_to_add = None + if elt.ShapeType == 'Edge': + found_edge = False + for i, s in enumerate(selection[0].Shape.Solids): + for e in s.Edges: + if elt.isSame(e): + if not found_edge: + solid_to_add = str(i + 1) + else: + FreeCAD.Console.PrintMessage('Edge belongs to more than one solid\n') + solid_to_add = None + found_edge = True + elif elt.ShapeType == 'Face': + found_face = False + for i, s in enumerate(selection[0].Shape.Solids): + for e in s.Faces: + if elt.isSame(e): + if not found_face: + solid_to_add = str(i + 1) + else: + FreeCAD.Console.PrintMessage('Face belongs to more than one solid\n') + solid_to_add = None + found_edge = True + if solid_to_add: + selection = (selection[0], 'Solid' + solid_to_add) + print('selection element changed to Solid: ', selection[0].Shape.ShapeType, ' ', selection[0].Name, ' ', selection[1]) + else: + return + if selection not in self.references: + self.references.append(selection) + self.rebuild_list_References() + else: + FreeCAD.Console.PrintMessage(selection[0].Name + ' --> ' + selection[1] + ' is in reference list already!\n') + + def rebuild_list_References(self): + self.form.list_References.clear() + items = [] + for ref in self.references: + item_name = ref[0].Name + ':' + ref[1] + items.append(item_name) + for listItemName in sorted(items): + self.form.list_References.addItem(listItemName) From 2537cf512cbd96380a5112f1c8545b1246719778 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:31 +0100 Subject: [PATCH 086/144] FEM: mesh group, add a needed def to mesh tools and use the new mesh group object in gmsh mesh class --- src/Mod/Fem/FemGmshTools.py | 32 +++++++++++++--- src/Mod/Fem/FemMeshTools.py | 76 +++++++++++++++++++++++-------------- 2 files changed, 75 insertions(+), 33 deletions(-) diff --git a/src/Mod/Fem/FemGmshTools.py b/src/Mod/Fem/FemGmshTools.py index bd9258dc71..f214c03a67 100644 --- a/src/Mod/Fem/FemGmshTools.py +++ b/src/Mod/Fem/FemGmshTools.py @@ -215,17 +215,39 @@ class FemGmshTools(): print(' ' + self.gmsh_bin) def get_group_data(self): + self.group_elements = {} + # TODO solid, face, edge seam not work together, some print or make it work together + # TODO handle groups for Edges and Vertexes + + # mesh groups and groups of analysis member + if not self.mesh_obj.MeshGroupList: + print (' No mesh group objects.') + else: + print (' Mesh group objects, we need to get the elements.') + for mg in self.mesh_obj.MeshGroupList: + new_group_elements = FemMeshTools.get_mesh_group_elements(mg, self.part_obj) + for ge in new_group_elements: + if ge not in self.group_elements: + self.group_elements[ge] = new_group_elements[ge] + else: + FreeCAD.Console.PrintError(" A group with this name exists already.\n") if self.analysis: print(' Group meshing.') - self.group_elements = FemMeshTools.get_analysis_group_elements(self.analysis, self.part_obj) - print(' {}'.format(self.group_elements)) + new_group_elements = FemMeshTools.get_analysis_group_elements(self.analysis, self.part_obj) + for ge in new_group_elements: + if ge not in self.group_elements: + self.group_elements[ge] = new_group_elements[ge] + else: + FreeCAD.Console.PrintError(" A group with this name exists already.\n") else: - print(' NO group meshing.') + print(' No anlysis members for group meshing.') + print(' {}'.format(self.group_elements)) + # mesh regions self.ele_length_map = {} # { 'ElementString' : element length } self.ele_node_map = {} # { 'ElementString' : [element nodes] } if not self.mesh_obj.MeshRegionList: - print (' No Mesh regions.') + print (' No mesh regions.') else: print (' Mesh regions, we need to get the elements.') if self.part_obj.Shape.ShapeType == 'Compound': @@ -279,7 +301,7 @@ class FemGmshTools(): geo = open(self.temp_file_geo, "w") geo.write('Merge "' + self.temp_file_geometry + '";\n') geo.write("\n") - if self.analysis and self.group_elements: + if self.group_elements: # print(' We gone have found elements to make mesh groups for.') geo.write("// group data\n") # we use the element name of FreeCAD which starts with 1 (example: 'Face1'), same as GMSH diff --git a/src/Mod/Fem/FemMeshTools.py b/src/Mod/Fem/FemMeshTools.py index 3cc1b6c4c7..e35940d482 100644 --- a/src/Mod/Fem/FemMeshTools.py +++ b/src/Mod/Fem/FemMeshTools.py @@ -995,54 +995,43 @@ def get_ref_shape_node_sum_geom_table(node_geom_table): return node_sum_geom_table +def get_mesh_group_elements(mesh_group_obj, aPart): + '''the Reference shapes of the mesh_group_object are searched in the Shape of aPart. If found in shape they are added to a dict + {MeshGroupIdentifier : ['ShapeType of the Elements'], [ElementID, ElementID, ...], ...} + ''' + group_elements = {} # { name : [element, element, ... , element]} + if mesh_group_obj.References: + grp_ele = get_reference_group_elements(mesh_group_obj, aPart) + group_elements[grp_ele[0]] = grp_ele[1] + else: + FreeCAD.Console.PrintError(' Empty reference in mesh group object: ' + mesh_group_obj.Name + ' ' + mesh_group_obj.Label) + return group_elements + + def get_analysis_group_elements(aAnalysis, aPart): ''' all Reference shapes of all Analysis member are searched in the Shape of aPart. If found in shape they are added to a dict {ConstraintName : ['ShapeType of the Elements'], [ElementID, ElementID, ...], ...} ''' - aShape = aPart.Shape group_elements = {} # { name : [element, element, ... , element]} empty_references = [] for m in aAnalysis.Member: if hasattr(m, "References"): - # print(m.Name) - key = m.Name - elements = [] - stype = None if m.References: - for r in m.References: - parent = r[0] - childs = r[1] - # print(parent) - # print(childs) - for child in childs: - ref_shape = get_element(parent, child) # the method getElement(element) does not return Solid elements - if not stype: - stype = ref_shape.ShapeType - elif stype != ref_shape.ShapeType: - FreeCAD.Console.PrintError('Error, two refschapes in References with different ShapeTypes.\n') - # print(ref_shape) - found_element = find_element_in_shape(aShape, ref_shape) - if found_element is not None: - elements.append(found_element) - else: - FreeCAD.Console.PrintError('Problem: No element found for: ' + str(ref_shape) + '\n') - print(' ' + m.Name) - print(' ' + str(m.References)) - print(' ' + r[0].Name) - group_elements[key] = sorted(elements) + grp_ele = get_reference_group_elements(m, aPart) + group_elements[grp_ele[0]] = grp_ele[1] else: print(' Empty reference: ' + m.Name) empty_references.append(m) if empty_references: if len(empty_references) == 1: - group_elements = get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aShape) + group_elements = get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aPart.Shape) else: FreeCAD.Console.PrintError('Problem: more than one object with empty references.\n') print('We gone try to get the empty material references anyway.\n') # ShellThickness and BeamSection could have empty references, but on solid meshes only materials should have empty references for er in empty_references: print(er.Name) - group_elements = get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aShape) + group_elements = get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aPart.Shape) # check if all groups have elements: for g in group_elements: # print(group_elements[g]) @@ -1051,6 +1040,37 @@ def get_analysis_group_elements(aAnalysis, aPart): return group_elements +def get_reference_group_elements(obj, aPart): + aShape = aPart.Shape + if hasattr(obj, "UseLabel") and obj.UseLabel: + key = obj.Label # TODO check the character of the Label, only allow underline and standard english character + else: + key = obj.Name + elements = [] + stype = None + for r in obj.References: + parent = r[0] + childs = r[1] + # print(parent) + # print(childs) + for child in childs: + ref_shape = get_element(parent, child) # the method getElement(element) does not return Solid elements + if not stype: + stype = ref_shape.ShapeType + elif stype != ref_shape.ShapeType: + FreeCAD.Console.PrintError('Error, two refschapes in References with different ShapeTypes.\n') + # print(ref_shape) + found_element = find_element_in_shape(aShape, ref_shape) + if found_element is not None: + elements.append(found_element) + else: + FreeCAD.Console.PrintError('Problem: No element found for: ' + str(ref_shape) + '\n') + print(' ' + obj.Name) + print(' ' + str(obj.References)) + print(' ' + r[0].Name) + return (key, sorted(elements)) + + def get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aShape): '''get the elementIDs if the Reference shape is empty see get_analysis_group_elements() for more informatations From 01b3db5de60ec1ac5222c52fada26a9f491fb7ef Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:35 +0100 Subject: [PATCH 087/144] FEM: frd reader, add B32 beam elements --- src/Mod/Fem/ccxFrdReader.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Mod/Fem/ccxFrdReader.py b/src/Mod/Fem/ccxFrdReader.py index b7963db133..07513db632 100644 --- a/src/Mod/Fem/ccxFrdReader.py +++ b/src/Mod/Fem/ccxFrdReader.py @@ -54,6 +54,7 @@ def readResult(frd_input): elements_quad4 = {} elements_quad8 = {} elements_seg2 = {} + elements_seg3 = {} results = [] mode_results = {} mode_disp = {} @@ -250,6 +251,14 @@ def readResult(frd_input): nd1 = int(line[3:13]) nd2 = int(line[13:23]) elements_seg2[elem] = (nd1, nd2) + elif elemType == 12: + # B32 CalculiX --> seg3 FreeCAD + # Also D element element number + # N1, N3 ,N2 Order in outpufile is 1,3,2 + nd1 = int(line[3:13]) + nd3 = int(line[13:23]) + nd2 = int(line[23:33]) + elements_seg3[elem] = (nd1, nd2, nd3) # Check if we found new eigenmode if line[5:10] == "PMODE": @@ -336,7 +345,7 @@ def readResult(frd_input): return {'Nodes': nodes, 'Hexa8Elem': elements_hexa8, 'Penta6Elem': elements_penta6, 'Tetra4Elem': elements_tetra4, 'Tetra10Elem': elements_tetra10, 'Penta15Elem': elements_penta15, 'Hexa20Elem': elements_hexa20, 'Tria3Elem': elements_tria3, 'Tria6Elem': elements_tria6, - 'Quad4Elem': elements_quad4, 'Quad8Elem': elements_quad8, 'Seg2Elem': elements_seg2, + 'Quad4Elem': elements_quad4, 'Quad8Elem': elements_quad8, 'Seg2Elem': elements_seg2, 'Seg3Elem': elements_seg3, 'Results': results} From dc3b45458cc31e33f55031c2857ffdbf5b243c6e Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:38 +0100 Subject: [PATCH 088/144] FEM: result object, add properties for stress and strain vectors --- src/Mod/Fem/App/FemResultObject.cpp | 4 ++++ src/Mod/Fem/App/FemResultObject.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Mod/Fem/App/FemResultObject.cpp b/src/Mod/Fem/App/FemResultObject.cpp index 20915c4147..5a793f786a 100644 --- a/src/Mod/Fem/App/FemResultObject.cpp +++ b/src/Mod/Fem/App/FemResultObject.cpp @@ -42,6 +42,8 @@ FemResultObject::FemResultObject() ADD_PROPERTY_TYPE(Stats,(0), "Fem",Prop_None,"Statistics of the results"); ADD_PROPERTY_TYPE(DisplacementVectors,(), "Fem",Prop_None,"List of displacement vectors"); ADD_PROPERTY_TYPE(DisplacementLengths,(0), "Fem",Prop_None,"List of displacement lengths"); + ADD_PROPERTY_TYPE(StressVectors,(), "Fem",Prop_None,"List of Stress vectors"); + ADD_PROPERTY_TYPE(StrainVectors,(), "Fem",Prop_None,"List of Strain vectors"); ADD_PROPERTY_TYPE(StressValues,(0), "Fem",Prop_None,"List of Von Misses stress values"); ADD_PROPERTY_TYPE(PrincipalMax,(0), "Fem",Prop_None,"List of First Principal (Max) stress values"); ADD_PROPERTY_TYPE(PrincipalMed,(0), "Fem",Prop_None,"List of Second Principal (Med) stress values"); @@ -59,6 +61,8 @@ FemResultObject::FemResultObject() Stats.setStatus(App::Property::ReadOnly, true); DisplacementVectors.setStatus(App::Property::ReadOnly, true); DisplacementLengths.setStatus(App::Property::ReadOnly, true); + StressVectors.setStatus(App::Property::ReadOnly, true); + StrainVectors.setStatus(App::Property::ReadOnly, true); StressValues.setStatus(App::Property::ReadOnly, true); PrincipalMax.setStatus(App::Property::ReadOnly, true); PrincipalMed.setStatus(App::Property::ReadOnly, true); diff --git a/src/Mod/Fem/App/FemResultObject.h b/src/Mod/Fem/App/FemResultObject.h index b99680fa30..1d765385e8 100644 --- a/src/Mod/Fem/App/FemResultObject.h +++ b/src/Mod/Fem/App/FemResultObject.h @@ -51,6 +51,10 @@ public: App::PropertyVectorList DisplacementVectors; /// Lengths of displacement vectors of analysis App::PropertyFloatList DisplacementLengths; + /// Stress vectors of analysis + App::PropertyVectorList StressVectors; + /// Strain vectors of analysis + App::PropertyVectorList StrainVectors; /// Von Mises Stress values of analysis App::PropertyFloatList StressValues; /// First principal Stress values of analysis From 3003b7d9c40783bff2ba4173cc30043a206cd64f Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:42 +0100 Subject: [PATCH 089/144] FEM: frd reader, add reading strain data and calculate stress and strain vector --- src/Mod/Fem/ccxFrdReader.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Mod/Fem/ccxFrdReader.py b/src/Mod/Fem/ccxFrdReader.py index 07513db632..060baeabbe 100644 --- a/src/Mod/Fem/ccxFrdReader.py +++ b/src/Mod/Fem/ccxFrdReader.py @@ -59,11 +59,14 @@ def readResult(frd_input): mode_results = {} mode_disp = {} mode_stress = {} + mode_stressv = {} + mode_strain = {} mode_temp = {} mode_disp_found = False nodes_found = False mode_stress_found = False + mode_strain_found = False mode_temp_found = False mode_time_found = False elements_found = False @@ -285,6 +288,19 @@ def readResult(frd_input): stress_5 = float(line[61:73]) stress_6 = float(line[73:85]) mode_stress[elem] = (stress_1, stress_2, stress_3, stress_4, stress_5, stress_6) + mode_stressv[elem] = FreeCAD.Vector(stress_1, stress_2, stress_3) + if line[5:13] == "TOSTRAIN": + mode_strain_found = True + # we found a strain line in the frd file + if mode_strain_found and (line[1:3] == "-1"): + elem = int(line[4:13]) + strain_1 = float(line[13:25]) + strain_2 = float(line[25:37]) + strain_3 = float(line[37:49]) +# strain_4 = float(line[49:61]) #Not used in vector +# strain_5 = float(line[61:73]) +# strain_6 = float(line[73:85]) + mode_strain[elem] = FreeCAD.Vector(strain_1, strain_2, strain_3) # Check if we found a time step if line[4:10] == "1PSTEP": mode_time_found = True @@ -318,6 +334,8 @@ def readResult(frd_input): mode_results['number'] = eigenmode mode_results['disp'] = mode_disp mode_results['stress'] = mode_stress + mode_results['stressv'] = mode_stressv + mode_results['strainv'] = mode_strain mode_results['temp'] = mode_temp mode_results['time'] = timestep results.append(mode_results) @@ -331,6 +349,8 @@ def readResult(frd_input): mode_results['number'] = eigenmode mode_results['disp'] = mode_disp mode_results['stress'] = mode_stress + mode_results['stressv'] = mode_stressv + mode_results['strainv'] = mode_strain mode_results['time'] = 0 # Dont return time if static results.append(mode_results) mode_disp = {} @@ -430,6 +450,8 @@ def importFrd(filename, analysis=None, result_name_prefix=None): break disp = result_set['disp'] + stressv = result_set['stressv'] + strainv = result_set['strainv'] no_of_values = len(disp) displacement = [] for k, v in disp.iteritems(): @@ -447,6 +469,8 @@ def importFrd(filename, analysis=None, result_name_prefix=None): if len(disp) > 0: results.DisplacementVectors = map((lambda x: x * scale), disp.values()) + results.StressVectors = map((lambda x: x * scale), stressv.values()) + results.StrainVectors = map((lambda x: x * scale), strainv.values()) results.NodeNumbers = disp.keys() if(mesh_object): results.Mesh = mesh_object From 57a94af0e104b9ffa26bfcbed1bef221ecc74695 Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:45 +0100 Subject: [PATCH 090/144] FEM: result task panel, add stress and strain vectors to possible user defined results --- src/Mod/Fem/TaskPanelShowResult.ui | 32 ++++++++++++++++++++++++++++- src/Mod/Fem/_TaskPanelShowResult.py | 12 +++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/Mod/Fem/TaskPanelShowResult.ui b/src/Mod/Fem/TaskPanelShowResult.ui index af2b547d06..4b74bd91e1 100644 --- a/src/Mod/Fem/TaskPanelShowResult.ui +++ b/src/Mod/Fem/TaskPanelShowResult.ui @@ -270,8 +270,38 @@
+ + + 0 + 17 + + + + + 16777215 + 16777215 + + + + 1 + - Available: Disp(x,y,z) Principal stresses (P1,P2,P3) + Available: Disp(x,y,z) Principal stresses(P1,P2,P3) Stress(sx,sy,sz) Strain (ex,ey,ez) + + + false + + + Qt::AlignCenter + + + true + + + -1 + + + Qt::NoTextInteraction diff --git a/src/Mod/Fem/_TaskPanelShowResult.py b/src/Mod/Fem/_TaskPanelShowResult.py index 5c713eb979..8392df24d8 100644 --- a/src/Mod/Fem/_TaskPanelShowResult.py +++ b/src/Mod/Fem/_TaskPanelShowResult.py @@ -21,7 +21,7 @@ # *************************************************************************** __title__ = "Result Control Task Panel" -__author__ = "Juergen Riegel" +__author__ = "Juergen Riegel, Michael Hindley" __url__ = "http://www.freecadweb.org" ## @package TaskPanelShowResult @@ -231,7 +231,15 @@ class _TaskPanelShowResult: x = np.array(dispvectors[:, 0]) y = np.array(dispvectors[:, 1]) z = np.array(dispvectors[:, 2]) - userdefined_eq = x + y + z + T + Von + P1 + P2 + P3 # Dummy equation to get around flake8, varibles not being used + stressvectors = np.array(self.result_object.StressVectors) + sx = np.array(stressvectors[:, 0]) + sy = np.array(stressvectors[:, 1]) + sz = np.array(stressvectors[:, 2]) + strainvectors = np.array(self.result_object.StrainVectors) + ex = np.array(strainvectors[:, 0]) + ey = np.array(strainvectors[:, 1]) + ez = np.array(strainvectors[:, 2]) + userdefined_eq = x + y + z + T + Von + P1 + P2 + P3 + sx + sy + sz + ex + ey + ez # Dummy equation to get around flake8, varibles not being used userdefined_eq = self.form.user_def_eq.toPlainText() # Get equation to be used UserDefinedFormula = eval(userdefined_eq).tolist() self.result_object.UserDefined = UserDefinedFormula From f9f682640ab874a5eceace0beba6f394acf97655 Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:49 +0100 Subject: [PATCH 091/144] FEM: VTK tools, add stress and strain vectors (x,y,z) --- src/Mod/Fem/App/FemVTKTools.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Mod/Fem/App/FemVTKTools.cpp b/src/Mod/Fem/App/FemVTKTools.cpp index 2e30251c84..e1d3faa8a5 100644 --- a/src/Mod/Fem/App/FemVTKTools.cpp +++ b/src/Mod/Fem/App/FemVTKTools.cpp @@ -851,6 +851,37 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); }} + + if(!res->StressVectors.getValues().empty()) { + const std::vector& vec = res->StressVectors.getValues(); + if (vec.size()>1) { + vtkSmartPointer data = vtkSmartPointer::New(); + data->SetNumberOfComponents(3); + data->SetName("Stress Vectors"); + + for(std::vector::const_iterator it=vec.begin(); it!=vec.end(); ++it) { + double tuple[] = {it->x, it->y , it->z}; + data->InsertNextTuple(tuple); + } + + grid->GetPointData()->AddArray(data); + }} + + if(!res->StrainVectors.getValues().empty()) { + const std::vector& vec = res->StrainVectors.getValues(); + if (vec.size()>1) { + vtkSmartPointer data = vtkSmartPointer::New(); + data->SetNumberOfComponents(3); + data->SetName("Strain Vectors"); + + for(std::vector::const_iterator it=vec.begin(); it!=vec.end(); ++it) { + double tuple[] = {it->x, it->y, it->z}; + data->InsertNextTuple(tuple); + } + + grid->GetPointData()->AddArray(data); + }} + } } // namespace From aa83257b73579d3c1ed83309e4f4d029f626913b Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:53 +0100 Subject: [PATCH 092/144] FEM: code formating, remove trailing whitspaces --- src/Mod/Fem/App/FemVTKTools.cpp | 68 ++++++++++++++++----------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Mod/Fem/App/FemVTKTools.cpp b/src/Mod/Fem/App/FemVTKTools.cpp index e1d3faa8a5..c5f8d337bb 100644 --- a/src/Mod/Fem/App/FemVTKTools.cpp +++ b/src/Mod/Fem/App/FemVTKTools.cpp @@ -108,7 +108,7 @@ template void writeVTKFile(const char* filename, vtkSmartPointer< writer->SetInputData(dataset); writer->Write(); } - + void FemVTKTools::importVTKMesh(vtkSmartPointer dataset, FemMesh* mesh) { const vtkIdType nPoints = dataset->GetNumberOfPoints(); @@ -184,7 +184,7 @@ FemMesh* FemVTKTools::readVTKMesh(const char* filename, FemMesh* mesh) Base::TimeInfo Start; Base::Console().Log("Start: read FemMesh from VTK unstructuredGrid ======================\n"); Base::FileInfo f(filename); - + if(f.hasExtension("vtu")) { vtkSmartPointer dataset = readVTKFile(filename); @@ -236,7 +236,7 @@ void exportFemMeshFaces(vtkSmartPointer grid, const SMDS_Fa quad->GetPointIds()->SetId(1, aFace->GetNode(1)->GetID()-1); quad->GetPointIds()->SetId(2, aFace->GetNode(2)->GetID()-1); quad->GetPointIds()->SetId(3, aFace->GetNode(3)->GetID()-1); - + quadArray->InsertNextCell(quad); } //quadratic triangle @@ -263,7 +263,7 @@ void exportFemMeshFaces(vtkSmartPointer grid, const SMDS_Fa quad->GetPointIds()->SetId(5, aFace->GetNode(5)->GetID()-1); quad->GetPointIds()->SetId(6, aFace->GetNode(6)->GetID()-1); quad->GetPointIds()->SetId(7, aFace->GetNode(7)->GetID()-1); - + quadQuadArray->InsertNextCell(quad); } } @@ -275,7 +275,7 @@ void exportFemMeshFaces(vtkSmartPointer grid, const SMDS_Fa if(quadTriangleArray->GetNumberOfCells()>0) grid->SetCells(VTK_QUADRATIC_TRIANGLE, quadTriangleArray); - + if(quadQuadArray->GetNumberOfCells()>0) grid->SetCells(VTK_QUADRATIC_QUAD, quadQuadArray); @@ -291,7 +291,7 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo // quadratic elemnts with 13 and 15 nodes are not added yet vtkSmartPointer quadTetraArray = vtkSmartPointer::New(); vtkSmartPointer quadHexaArray = vtkSmartPointer::New(); - + for (;aVolIter->more();) { const SMDS_MeshVolume* aVol = aVolIter->next(); @@ -314,7 +314,7 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo cell->GetPointIds()->SetId(2, aVol->GetNode(2)->GetID()-1); cell->GetPointIds()->SetId(3, aVol->GetNode(3)->GetID()-1); cell->GetPointIds()->SetId(4, aVol->GetNode(4)->GetID()-1); - + pyramidArray->InsertNextCell(cell); } if(aVol->NbNodes() == 6) { @@ -325,7 +325,7 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo cell->GetPointIds()->SetId(3, aVol->GetNode(3)->GetID()-1); cell->GetPointIds()->SetId(4, aVol->GetNode(4)->GetID()-1); cell->GetPointIds()->SetId(5, aVol->GetNode(5)->GetID()-1); - + wedgeArray->InsertNextCell(cell); } if(aVol->NbNodes() == 8) { @@ -338,7 +338,7 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo cell->GetPointIds()->SetId(5, aVol->GetNode(5)->GetID()-1); cell->GetPointIds()->SetId(6, aVol->GetNode(6)->GetID()-1); cell->GetPointIds()->SetId(7, aVol->GetNode(7)->GetID()-1); - + hexaArray->InsertNextCell(cell); } //quadratic tetrahedra @@ -371,10 +371,10 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo if(hexaArray->GetNumberOfCells()>0) grid->SetCells(VTK_HEXAHEDRON, hexaArray); - + if(quadTetraArray->GetNumberOfCells()>0) grid->SetCells(VTK_QUADRATIC_TETRA, quadTetraArray); - + if(quadHexaArray->GetNumberOfCells()>0) grid->SetCells(VTK_QUADRATIC_HEXAHEDRON, quadHexaArray); @@ -382,11 +382,11 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo void FemVTKTools::exportVTKMesh(const FemMesh* mesh, vtkSmartPointer grid) { - + SMESH_Mesh* smesh = const_cast(mesh->getSMesh()); SMESHDS_Mesh* meshDS = smesh->GetMeshDS(); const SMDS_MeshInfo& info = meshDS->GetMeshInfo(); - + //start with the nodes vtkSmartPointer points = vtkSmartPointer::New(); SMDS_NodeIteratorPtr aNodeIter = meshDS->nodesIterator(); @@ -409,11 +409,11 @@ void FemVTKTools::exportVTKMesh(const FemMesh* mesh, vtkSmartPointer grid = vtkSmartPointer::New(); exportVTKMesh(mesh, grid); //vtkSmartPointer dataset = vtkDataSet::SafeDownCast(grid); @@ -426,7 +426,7 @@ void FemVTKTools::writeVTKMesh(const char* filename, const FemMesh* mesh) else{ Base::Console().Error("file name extension is not supported to write VTK\n"); } - + Base::Console().Log(" %f: Done \n",Base::TimeInfo::diffTimeF(Start, Base::TimeInfo())); } @@ -487,7 +487,7 @@ App::DocumentObject* FemVTKTools::readFluidicResult(const char* filename, App::D Base::TimeInfo Start; Base::Console().Log("Start: read FemResult with FemMesh from VTK file ======================\n"); Base::FileInfo f(filename); - + vtkSmartPointer ds; if(f.hasExtension("vtu")) { @@ -501,7 +501,7 @@ App::DocumentObject* FemVTKTools::readFluidicResult(const char* filename, App::D { Base::Console().Error("file name extension is not supported\n"); } - + App::Document* pcDoc = App::GetApplication().getActiveDocument(); if(!pcDoc) { @@ -532,12 +532,12 @@ App::DocumentObject* FemVTKTools::readFluidicResult(const char* filename, App::D static_cast(mesh->getPropertyByName("FemMesh"))->setValue(*fmesh); static_cast(result->getPropertyByName("Mesh"))->setValue(mesh); // PropertyLink is the property type to store DocumentObject pointer - + importFluidicResult(dataset, result); pcDoc->recompute(); - + Base::Console().Log(" %f: Done \n", Base::TimeInfo::diffTimeF(Start, Base::TimeInfo())); - + return result; } @@ -561,12 +561,12 @@ void FemVTKTools::writeResult(const char* filename, const App::DocumentObject* r Base::TimeInfo Start; Base::Console().Log("Start: write FemResult or CfdResult to VTK unstructuredGrid dataset =======\n"); Base::FileInfo f(filename); - + vtkSmartPointer grid = vtkSmartPointer::New(); App::DocumentObject* mesh = static_cast(res->getPropertyByName("Mesh"))->getValue(); const FemMesh& fmesh = static_cast(mesh->getPropertyByName("FemMesh"))->getValue(); FemVTKTools::exportVTKMesh(&fmesh, grid); - + if(res->getPropertyByName("Velocity")){ FemVTKTools::exportFluidicResult(res, grid); } @@ -576,7 +576,7 @@ void FemVTKTools::writeResult(const char* filename, const App::DocumentObject* r else{ return; } - + //vtkSmartPointer dataset = vtkDataSet::SafeDownCast(grid); if(f.hasExtension("vtu")){ writeVTKFile(filename, grid); @@ -587,7 +587,7 @@ void FemVTKTools::writeResult(const char* filename, const App::DocumentObject* r else{ Base::Console().Error("file name extension is not supported to write VTK\n"); } - + Base::Console().Log(" %f: Done \n",Base::TimeInfo::diffTimeF(Start, Base::TimeInfo())); } @@ -604,7 +604,7 @@ void FemVTKTools::importFluidicResult(vtkSmartPointer dataset, App:: vars["TurbulenceEnergy"] = "k"; vars["TurbulenceDissipationRate"] = "epsilon"; vars["TurbulenceSpecificDissipation"] = "omega"; - + const int max_var_index = 11; std::vector stats(3*max_var_index, 0.0); @@ -620,10 +620,10 @@ void FemVTKTools::importFluidicResult(vtkSmartPointer dataset, App:: varids["TurbulenceDissipationRate"] = 8; //varids["TurbulenceThermalDiffusivity"] = 9; //varids["TurbulenceSpecificDissipation"] = 10; - + double ts = 0.0; // t=0.0 for static simulation static_cast(res->getPropertyByName("Time"))->setValue(ts); - + vtkSmartPointer pd = dataset->GetPointData(); const vtkIdType nPoints = dataset->GetNumberOfPoints(); if(pd->GetNumberOfArrays() == 0) { @@ -631,7 +631,7 @@ void FemVTKTools::importFluidicResult(vtkSmartPointer dataset, App:: // if pointData is empty, data may be in cellDate, cellData -> pointData interpolation is possible in VTK return; } - + std::vector nodeIds(nPoints); vtkSmartPointer vel = pd->GetArray(vars["Velocity"]); if(nPoints && vel && vel->GetNumberOfComponents() == 3) { @@ -692,7 +692,7 @@ void FemVTKTools::importFluidicResult(vtkSmartPointer dataset, App:: stats[index*3] = vmin; stats[index*3 + 2] = vmax; stats[index*3 + 1] = vmean/nPoints; - + Base::Console().Message("field \"%s\" has been loaded \n", kv.first); } } @@ -851,7 +851,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); }} - + if(!res->StressVectors.getValues().empty()) { const std::vector& vec = res->StressVectors.getValues(); if (vec.size()>1) { @@ -865,7 +865,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar } grid->GetPointData()->AddArray(data); - }} + }} if(!res->StrainVectors.getValues().empty()) { const std::vector& vec = res->StrainVectors.getValues(); @@ -880,8 +880,8 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar } grid->GetPointData()->AddArray(data); - }} - + }} + } } // namespace From 40aafe745230afd27bbbbd87f5c3247708211e02 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:07:28 +0100 Subject: [PATCH 093/144] FEM: remove precheck for load since an static analyis could be valid without loads --- src/Mod/Fem/FemTools.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Mod/Fem/FemTools.py b/src/Mod/Fem/FemTools.py index 3d11fb989d..93ed055f20 100644 --- a/src/Mod/Fem/FemTools.py +++ b/src/Mod/Fem/FemTools.py @@ -378,9 +378,7 @@ class FemTools(QtCore.QRunnable, QtCore.QObject): if self.analysis_type == "static": if not (self.fixed_constraints or self.displacement_constraints): message += "Static analysis: Neither constraint fixed nor constraint displacement defined.\n" - if self.analysis_type == "static": - if not (self.force_constraints or self.pressure_constraints or self.selfweight_constraints): - message += "Static analysis: Neither constraint force nor constraint pressure or a constraint selfweight defined.\n" + # no check in the regard of loads (constraint force, pressure, self weight) is done because an analysis without loads at all is an valid analysis too if self.analysis_type == "thermomech": if not self.initialtemperature_constraints: message += "Thermomechanical analysis: No initial temperature defined.\n" From 81e9228cf07bb22df3a95baf4ecf737ff61884ae Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 8 Jan 2017 16:47:06 +0100 Subject: [PATCH 094/144] issue #0000753: angle constraint auto places the constraints visuals including its value in the wrong place --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 41 ++++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index d610f64e42..b2a1336c71 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -29,6 +29,7 @@ #endif #include +#include #include #include #include @@ -3252,14 +3253,34 @@ void CmdSketcherConstrainAngle::activated(int iMsg) Base::Vector3d p1b = lineSeg1->getEndPoint(); Base::Vector3d p2a = lineSeg2->getStartPoint(); Base::Vector3d p2b = lineSeg2->getEndPoint(); - double length = DBL_MAX; - for (int i=0; i <= 1; i++) { - for (int j=0; j <= 1; j++) { - double tmp = ((j?p2a:p2b)-(i?p1a:p1b)).Length(); - if (tmp < length) { - length = tmp; - PosId1 = i ? Sketcher::start : Sketcher::end; - PosId2 = j ? Sketcher::start : Sketcher::end; + + // Get the intersection point in 2d of the two lines if possible + Base::Line2d line1(Base::Vector2d(p1a.x, p1a.y), Base::Vector2d(p1b.x, p1b.y)); + Base::Line2d line2(Base::Vector2d(p2a.x, p2a.y), Base::Vector2d(p2b.x, p2b.y)); + Base::Vector2d s; + if (line1.Intersect(line2, s)) { + // get the end points of the line segments that are closest to the intersection point + Base::Vector3d s3d(s.x, s.y, p1a.z); + if (Base::DistanceP2(s3d, p1a) < Base::DistanceP2(s3d, p1b)) + PosId1 = Sketcher::start; + else + PosId1 = Sketcher::end; + if (Base::DistanceP2(s3d, p2a) < Base::DistanceP2(s3d, p2b)) + PosId2 = Sketcher::start; + else + PosId2 = Sketcher::end; + } + else { + // if all points are collinear + double length = DBL_MAX; + for (int i=0; i <= 1; i++) { + for (int j=0; j <= 1; j++) { + double tmp = Base::DistanceP2((j?p2a:p2b), (i?p1a:p1b)); + if (tmp < length) { + length = tmp; + PosId1 = i ? Sketcher::start : Sketcher::end; + PosId2 = j ? Sketcher::start : Sketcher::end; + } } } } @@ -3280,8 +3301,8 @@ void CmdSketcherConstrainAngle::activated(int iMsg) } } - double ActAngle = atan2(-dir1.y*dir2.x+dir1.x*dir2.y, - dir1.x*dir2.x+dir1.y*dir2.y); + double ActAngle = atan2(dir1.x*dir2.y-dir1.y*dir2.x, + dir1.y*dir2.y+dir1.x*dir2.x); if (ActAngle < 0) { ActAngle *= -1; std::swap(GeoId1,GeoId2); From 59ef8d8b62ddca44a0635252bc749bc5cdba719b Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Sun, 8 Jan 2017 15:29:57 -0200 Subject: [PATCH 095/144] Doc: Cosmetic fixes in doxygen output --- src/Doc/BuildWebDoc.cfg.in | 2 +- src/Doc/templates/customdoxygen.css | 19 +++++++++++++++++++ src/Doc/templates/header.html | 16 ++++++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Doc/BuildWebDoc.cfg.in b/src/Doc/BuildWebDoc.cfg.in index 257643eea1..dcec68aa6d 100644 --- a/src/Doc/BuildWebDoc.cfg.in +++ b/src/Doc/BuildWebDoc.cfg.in @@ -500,7 +500,7 @@ SHOW_FILES = NO # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. -SHOW_NAMESPACES = NO +SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from diff --git a/src/Doc/templates/customdoxygen.css b/src/Doc/templates/customdoxygen.css index 53a9a9eb9f..753938007b 100644 --- a/src/Doc/templates/customdoxygen.css +++ b/src/Doc/templates/customdoxygen.css @@ -1,3 +1,19 @@ +@font-face { + font-family: 'Fira Sans'; + src: url('/fonts/FiraSans-Regular.eot'); + src: local('☺'), url('/fonts/FiraSans-Regular.woff') format('woff'), url('/fonts/FiraSans-Regular.ttf') format('truetype'), url('/fonts/FiraSans-Regular.svg') format('svg'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('/fonts/Roboto.eot'); + src: local('☺'), url('/fonts/Roboto.woff') format('woff'), url('/fonts/Roboto.ttf') format('truetype'), url('/fonts/Roboto.svg') format('svg'); + font-weight: 400; + font-style: normal; +} + h1, .h1, h2, .h2, h3, .h3{ font-weight: 200 !important; } @@ -389,3 +405,6 @@ pre.fragment { .memdoc p { text-align: left; } +body, table, div, p, dl { + font: 400 16px/22px Fira Sans,sans-serif; +} diff --git a/src/Doc/templates/header.html b/src/Doc/templates/header.html index 9d41180a0c..f0b9b181fa 100644 --- a/src/Doc/templates/header.html +++ b/src/Doc/templates/header.html @@ -43,10 +43,18 @@