Merge branch 'fs_SLA_supports'

This commit is contained in:
Lukas Matena 2025-02-25 16:41:41 +01:00
commit ed27d06be3
82 changed files with 11831 additions and 1292 deletions

View File

@ -0,0 +1,361 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="50mm"
height="50mm"
viewBox="0 0 50 50"
version="1.1"
id="svg3403"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
sodipodi:docname="sla_support.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview3405"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="true"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="false"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
showborder="true"
borderlayer="true"
inkscape:zoom="3.3638608"
inkscape:cx="115.78957"
inkscape:cy="88.588685"
inkscape:window-width="1920"
inkscape:window-height="1129"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs3400">
<marker
style="overflow:visible"
id="Arrow2"
refX="0"
refY="0"
orient="auto-start-reverse"
inkscape:stockid="Arrow2"
markerWidth="7.6999998"
markerHeight="5.5999999"
viewBox="0 0 7.7 5.6"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="xMidYMid">
<path
transform="scale(0.7)"
d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
style="fill:context-stroke;fill-rule:evenodd;stroke:none"
id="arrow2L" />
</marker>
<marker
style="overflow:visible"
id="Arrow2-5"
refX="0"
refY="0"
orient="auto-start-reverse"
inkscape:stockid="Arrow2"
markerWidth="7.6999998"
markerHeight="5.5999999"
viewBox="0 0 7.7 5.6"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="xMidYMid">
<path
transform="scale(0.7)"
d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
style="fill:context-stroke;fill-rule:evenodd;stroke:none"
id="arrow2L-4" />
</marker>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#cccccc;stroke:#cccccc;stroke-width:2.5;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 0,11.25 H 46.858514 50"
id="path13101"
sodipodi:nodetypes="ccc" />
<text
xml:space="preserve"
style="font-size:0.25494px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#ffffff;stroke:none;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="46.68829"
y="11.313486"
id="text13105"><tspan
sodipodi:role="line"
id="tspan13103"
style="fill:#ffffff;stroke:none;stroke-width:0.25"
x="46.68829"
y="11.313486">201. - 250. layer</tspan></text>
<path
style="fill:#cccccc;stroke:#cccccc;stroke-width:2.5;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 0,6.25 H 50"
id="path13095" />
<path
style="fill:#cccccc;stroke:#cccccc;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 0,1.3745207 H 50"
id="path13003" />
<path
style="fill:#cccccc;stroke:#cccccc;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 0,1.875 H 50"
id="path13005" />
<path
style="fill:#cccccc;stroke:#cccccc;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 0,2.375 H 50"
id="path13007" />
<path
style="fill:#cccccc;stroke:#cccccc;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 0,0.87396551 H 50"
id="path12741" />
<path
style="fill:#cccccc;stroke:#cccccc;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 0,0.375 H 50"
id="path12739" />
<path
style="fill:#cccccc;stroke:#cccccc;stroke-width:0.05;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 0,0.075 H 50"
id="path9670-6" />
<path
style="fill:#cccccc;stroke:#cccccc;stroke-width:0.05;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 0,0.175 H 50"
id="path12735" />
<path
style="fill:none;stroke:#b50000;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 3.5,0 4.0915916,3.8987591 5,15 6,40"
id="path3587"
sodipodi:nodetypes="cccc" />
<text
xml:space="preserve"
style="font-size:1.05833px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:center;text-decoration-color:#000000;text-anchor:middle;fill:#364e59;stroke:none;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="9.5439215"
y="-7.5019774"
id="text4929"><tspan
sodipodi:role="line"
id="tspan4927"
style="font-size:1.05833px;fill:#364e59;stroke:none;stroke-width:0.4"
x="9.5439215"
y="-7.5019774">island supports</tspan></text>
<text
xml:space="preserve"
style="font-size:2.11667px;line-height:1.3;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:center;text-decoration-color:#000000;text-anchor:middle;fill:#364e59;stroke:none;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="24.711193"
y="52.296951"
id="text4985"><tspan
sodipodi:role="line"
id="tspan4983"
style="font-weight:normal;font-size:2.11667px;stroke-width:0.4"
x="24.711193"
y="52.296951">For loading is used only first red curve</tspan><tspan
sodipodi:role="line"
style="font-weight:normal;font-size:2.11667px;stroke-width:0.4"
x="24.711193"
y="55.048622"
id="tspan9281">Use only line segments (no curve)</tspan><tspan
sodipodi:role="line"
style="font-weight:normal;font-size:2.11667px;stroke-width:0.4"
x="24.711193"
y="57.800293"
id="tspan15589">Y coordinate of points must only increase.</tspan><tspan
sodipodi:role="line"
style="font-weight:normal;font-size:2.11667px;stroke-width:0.4"
x="24.711193"
y="60.551964"
id="tspan17460">First point.y must be zero</tspan><tspan
sodipodi:role="line"
style="font-weight:normal;font-size:2.11667px;stroke-width:0.4"
x="24.711193"
y="63.303635"
id="tspan9676">Last virtual point of curve is [last.x, ∞]</tspan></text>
<text
xml:space="preserve"
style="font-size:2.11667px;line-height:1.3;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:center;text-decoration-color:#000000;text-anchor:middle;fill:#364e59;stroke:none;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="33.019527"
y="-2.8043785"
id="text4985-7"><tspan
sodipodi:role="line"
style="font-weight:bold;font-size:2.11667px;stroke-width:0.4"
x="33.019527"
y="-2.8043785"
id="tspan9281-8">Supported radius[in mm]</tspan></text>
<rect
style="fill:#ba00ff;stroke:none;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
id="rect6552"
width="0.39999992"
height="1"
x="-0.2"
y="0" />
<text
xml:space="preserve"
style="font-size:1.05833px;line-height:1.2;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:center;text-decoration-color:#000000;text-anchor:middle;fill:#ba00ff;stroke:none;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="-0.38932067"
y="-1.6407087"
id="text6608"><tspan
sodipodi:role="line"
style="font-size:1.05833px;text-align:center;text-anchor:middle;stroke-width:0.4"
x="-0.38932067"
y="-1.6407087"
id="tspan7386">Head</tspan><tspan
sodipodi:role="line"
style="font-size:1.05833px;text-align:center;text-anchor:middle;stroke-width:0.4"
x="-0.38932067"
y="-0.3707087"
id="tspan7390">interface</tspan></text>
<path
style="fill:#ba00ff;stroke:#364e59;stroke-width:0.1;stroke-linejoin:round;stroke-dasharray:none;marker-end:url(#Arrow2);stop-color:#000000"
d="m 6.5368033,-7.2418186 -2.726239,6.27839838"
id="path7457"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-size:1.67576px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:center;text-decoration-color:#000000;text-anchor:middle;fill:#364e59;stroke:none;stroke-width:0.1;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="15.351564"
y="55.742996"
id="text9279"><tspan
sodipodi:role="line"
id="tspan9277"
style="fill:#364e59;stroke:none;stroke-width:0.1"
x="15.351564"
y="55.742996" /></text>
<path
style="fill:#364e59;stroke:#364e59;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000;marker-end:url(#Arrow2)"
d="M 29.365905,-1.4460483 H 48.609472"
id="path9401" />
<text
xml:space="preserve"
style="font-size:2.06203px;line-height:1.3;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:center;text-decoration-color:#000000;text-anchor:middle;fill:#364e59;stroke:none;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="-8.5609999"
y="41.920982"
id="text4985-7-3"><tspan
sodipodi:role="line"
style="font-weight:bold;font-size:2.11667px;stroke-width:0.4"
x="-8.5609999"
y="41.920982"
id="tspan9281-8-8">z [in mm]</tspan><tspan
sodipodi:role="line"
style="font-weight:normal;font-size:1.05833px;stroke-width:0.4"
x="-8.5609999"
y="44.620838"
id="tspan9668"
dy="-1.0011104">Print Direction</tspan></text>
<path
style="fill:#364e59;stroke:#364e59;stroke-width:0.4;stroke-linejoin:round;stroke-dasharray:none;marker-end:url(#Arrow2-5);stop-color:#000000"
d="M -2.7788881,27.725679 V 46.969247"
id="path9401-5" />
<path
style="fill:#364e59;stroke:#364e59;stroke-width:0.1;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
d="M 3.9353759,39.821947 H 58.843042"
id="path9670"
sodipodi:nodetypes="cc" />
<text
xml:space="preserve"
style="font-size:1.05833px;line-height:1.3;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#364e59;stroke:#364e59;stroke-width:0.1;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="50.865135"
y="39.457973"
id="text9674"><tspan
sodipodi:role="line"
id="tspan9672"
style="font-weight:normal;font-size:1.05833px;stroke:none;stroke-width:0.1;text-anchor:start;text-align:start"
x="59.791695"
y="39.457973">Last curve point</tspan><tspan
sodipodi:role="line"
style="font-weight:normal;font-size:1.05833px;stroke:none;stroke-width:0.1;text-anchor:start;text-align:start"
x="59.791695"
y="40.833801"
id="tspan9678">define stability requirements</tspan></text>
<text
xml:space="preserve"
style="font-size:0.25494px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#ffffff;stroke:none;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="47.136429"
y="0.44968146"
id="text12797"><tspan
sodipodi:role="line"
id="tspan12795"
style="fill:#ffffff;stroke:none;stroke-width:0.25"
x="47.136429"
y="0.44968146">6. - 10. layer</tspan></text>
<text
xml:space="preserve"
style="font-size:1.05833px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#ffffff;stroke:none;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="50.574947"
y="0.98318326"
id="text13011"><tspan
sodipodi:role="line"
style="stroke-width:0.25"
x="50.574947"
y="0.98318326"
id="tspan13013">Guid for layers height 0.05mm</tspan></text>
<text
xml:space="preserve"
style="font-size:0.25494px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#ffffff;stroke:none;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="46.987049"
y="0.9476288"
id="text13029"><tspan
sodipodi:role="line"
id="tspan13027"
style="fill:#ffffff;stroke:none;stroke-width:0.25"
x="46.987049"
y="0.9476288">16. - 20. layer</tspan></text>
<text
xml:space="preserve"
style="font-size:0.25494px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#ffffff;stroke:none;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="46.987049"
y="1.448184"
id="text13033"><tspan
sodipodi:role="line"
id="tspan13031"
style="fill:#ffffff;stroke:none;stroke-width:0.25"
x="46.987049"
y="1.448184">26. - 30. layer</tspan></text>
<text
xml:space="preserve"
style="font-size:0.25494px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#ffffff;stroke:none;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="46.987049"
y="1.941787"
id="text13037"><tspan
sodipodi:role="line"
id="tspan13035"
style="fill:#ffffff;stroke:none;stroke-width:0.25"
x="46.987049"
y="1.941787">36. - 40. layer</tspan></text>
<text
xml:space="preserve"
style="font-size:0.25494px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#ffffff;stroke:none;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="46.987049"
y="2.4423423"
id="text13041"><tspan
sodipodi:role="line"
id="tspan13039"
style="fill:#ffffff;stroke:none;stroke-width:0.25"
x="46.987049"
y="2.4423423">46. - 50. layer</tspan></text>
<text
xml:space="preserve"
style="font-size:0.25494px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#ffffff;stroke:none;stroke-width:0.25;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="46.68829"
y="6.3134861"
id="text13099"><tspan
sodipodi:role="line"
id="tspan13097"
style="fill:#ffffff;stroke:none;stroke-width:0.25"
x="46.68829"
y="6.3134861">101. - 150. layer</tspan></text>
<text
xml:space="preserve"
style="font-size:1.05833px;line-height:0;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';text-align:start;text-decoration-color:#000000;text-anchor:start;fill:#ffffff;stroke:none;stroke-width:2.5;stroke-linejoin:round;stroke-dasharray:none;stop-color:#000000"
x="14.708422"
y="-6.6856461"
id="text17458"><tspan
sodipodi:role="line"
id="tspan17456"
style="stroke-width:2.5" /></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="500" height="500">
<defs>
<linearGradient id="linearGradient">
<stop style="stop-color:#bbbbff;stop-opacity:1" offset="0" />
<stop style="stop-color:#8f8fff;stop-opacity:1" offset="0.7" />
<stop style="stop-color:#6666cd;stop-opacity:1" offset="1" />
</linearGradient>
<radialGradient cx="170" cy="200" r="200" fx="170" fy="200" id="radialGradient"
xlink:href="#linearGradient" gradientUnits="userSpaceOnUse" />
</defs>
<circle cx="250" cy="250" r="200" style="fill:url(#radialGradient)" />
</svg>

After

Width:  |  Height:  |  Size: 602 B

View File

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="500" height="500">
<defs>
<linearGradient id="linearGradient">
<stop style="stop-color:#d4ffff;stop-opacity:1;" offset="0" />
<stop style="stop-color:#00ffff;stop-opacity:1" offset="0.7" />
<stop style="stop-color:#00b1b1;stop-opacity:1;" offset="1" />
</linearGradient>
<radialGradient cx="170" cy="200" r="200" fx="170" fy="200" id="radialGradient"
xlink:href="#linearGradient" gradientUnits="userSpaceOnUse" />
</defs>
<circle cx="250" cy="250" r="200" style="fill:url(#radialGradient)" />
</svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="500" height="500">
<defs>
<linearGradient id="linearGradient">
<stop style="stop-color:#ffffff;stop-opacity:1;" offset="0" />
<stop style="stop-color:#bcbcbc;stop-opacity:1;" offset="0.5" />
<stop style="stop-color:#737373;stop-opacity:1;" offset="1" />
</linearGradient>
<radialGradient cx="170" cy="200" r="200" fx="170" fy="200" id="radialGradient"
xlink:href="#linearGradient" gradientUnits="userSpaceOnUse" />
</defs>
<circle cx="250" cy="250" r="200" style="fill:url(#radialGradient)" />
</svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="500" height="500">
<defs>
<linearGradient id="linearGradient">
<stop style="stop-color:#ffc887;stop-opacity:1;" offset="0" />
<stop style="stop-color:#fe8d4a;stop-opacity:1" offset="0.6" />
<stop style="stop-color:#be5c28;stop-opacity:1;" offset="1" />
</linearGradient>
<radialGradient cx="170" cy="200" r="200" fx="170" fy="200" id="radialGradient"
xlink:href="#linearGradient" gradientUnits="userSpaceOnUse" />
</defs>
<circle cx="250" cy="250" r="200" style="fill:url(#radialGradient)" />
</svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="500" height="500">
<defs>
<linearGradient id="linearGradient">
<stop style="stop-color:#ffffff;stop-opacity:1;" offset="0" />
<stop style="stop-color:#ffa2a2;stop-opacity:1" offset="0.7" />
<stop style="stop-color:#e87474;stop-opacity:1" offset="1" />
</linearGradient>
<radialGradient cx="170" cy="200" r="200" fx="170" fy="200" id="radialGradient"
xlink:href="#linearGradient" gradientUnits="userSpaceOnUse" />
</defs>
<circle cx="250" cy="250" r="200" style="fill:url(#radialGradient)" />
</svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 16 16"
width="16px"
height="16px"
version="1.1"
id="svg6"
sodipodi:docname="support_structure.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs10">
</defs><sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="true"
inkscape:zoom="16"
inkscape:cx="14.71875"
inkscape:cy="5.6875"
inkscape:window-width="1920"
inkscape:window-height="1129"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg6"><inkscape:grid
type="xygrid"
id="grid185" /></sodipodi:namedview><path
id="path245"
style="fill:#ed6b21;fill-opacity:1;stroke-width:5.0324"
d="M 1,13.998047 H 0.9726562 c -0.51978612,0 -0.94140625,0.421621 -0.94140625,0.941406 v 1.074219 L 1.001953,15.996092 V 15.13281 c 0,-0.07272 0.06205,-0.134765 0.1347657,-0.134765 H 14.857422 c 0.07272,0 0.134765,0.06205 0.134766,0.134765 V 16 l 1.023437,0.01367 v -1.074219 c 0,-0.519785 -0.421621,-0.941406 -0.941406,-0.941406 h -2.064908 l -0.01563,-6.6992189 c 0,-0.1205784 -0.04864,-0.2361381 -0.134766,-0.3222656 L 11.87796,5.7919339 c -0.09048,-0.090434 -0.209093,-0.1344965 -0.326172,-0.1347656 -0.11708,-2.691e-4 -0.232908,0.043455 -0.322265,0.1328125 -0.178715,0.1787146 -0.178715,0.4677697 0,0.6464844 l 0.777833,1.0186129 0.002,1.6933594 -2.9902348,3.3398435 V 7.4898105 C 9.0181212,7.2618516 8.7671402,7.0137588 8.4936842,7.0191074 8.2202282,7.0244574 8.009367,7.2292572 8.0093091,7.481998 l -0.00195,6.516049 H 3.0112622 l 0.00781,-5.9882814 4.7441406,-5.03125 C 7.9367844,2.7958116 7.9322477,2.5075532 7.7495434,2.3339844 7.6581914,2.2472 7.538273,2.204367 7.4214184,2.2070312 7.3045641,2.2096957 7.1898435,2.2582574 7.1030591,2.3496094 L 2.1909497,7.4980469 c -0.079172,0.085262 -0.1796875,0.252336 -0.1796875,0.3710937 v 1.875 1.4746094 2.779297 H 2 Z m 11.009309,-3.337891 0.002,3.337891 H 9.0659502 Z"
sodipodi:nodetypes="cssccssscccssccccsscccccscccccssccsccccccccc" /><path
id="path181"
d="m 2.5194163,0.0390625 c -0.2799997,0 -0.5,0.22000028 -0.5,0.5 v 1 c 0,0.7199993 0.4507818,1.5687504 1.0507812,1.96875 L 4.4393382,4.4199219 5.1366038,3.6816406 3.6287913,2.6777344 C 3.3087916,2.4677346 3.0194163,1.9190621 3.0194163,1.5390625 v -1 c 0,-0.27999972 -0.2200003,-0.5 -0.5,-0.5 z m 11.9999997,0 c -0.28,0 -0.5,0.22000028 -0.5,0.5 v 1 c 0,0.3799996 -0.28961,0.9286721 -0.599609,1.1386719 l -4.3398438,2.890625 c -0.2899997,0.1899998 -0.8213284,0.1899998 -1.1113281,0 L 6.8260569,4.8066406 6.1287913,5.5449219 l 1.28125,0.8535156 c 0.3099997,0.2099998 0.7093754,0.3105469 1.109375,0.3105469 0.3999996,0 0.7993753,-0.1005471 1.109375,-0.3105469 l 4.3398437,-2.890625 c 0.599999,-0.3999996 1.050781,-1.2487507 1.050781,-1.96875 v -1 c 0,-0.27999972 -0.22,-0.5 -0.5,-0.5 z"
style="fill:#808080;fill-opacity:1" /></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,228 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 16 16"
width="16px"
height="16px"
version="1.1"
id="svg6"
sodipodi:docname="support_structure_invisible.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs10">
</defs><sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="true"
inkscape:zoom="8"
inkscape:cx="2.4375"
inkscape:cy="-14.875"
inkscape:window-width="1920"
inkscape:window-height="1129"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg6"><inkscape:grid
type="xygrid"
id="grid185" /></sodipodi:namedview><path
id="path181"
d="m 2.5194163,0.0390625 c -0.2799997,0 -0.5,0.22000028 -0.5,0.5 v 1 c 0,0.7199993 0.4507818,1.5687504 1.0507812,1.96875 L 4.4393382,4.4199219 5.1366038,3.6816406 3.6287913,2.6777344 C 3.3087916,2.4677346 3.0194163,1.9190621 3.0194163,1.5390625 v -1 c 0,-0.27999972 -0.2200003,-0.5 -0.5,-0.5 z m 11.9999997,0 c -0.28,0 -0.5,0.22000028 -0.5,0.5 v 1 c 0,0.3799996 -0.28961,0.9286721 -0.599609,1.1386719 l -4.3398438,2.890625 c -0.2899997,0.1899998 -0.8213284,0.1899998 -1.1113281,0 L 6.8260569,4.8066406 6.1287913,5.5449219 l 1.28125,0.8535156 c 0.3099997,0.2099998 0.7093754,0.3105469 1.109375,0.3105469 0.3999996,0 0.7993753,-0.1005471 1.109375,-0.3105469 l 4.3398437,-2.890625 c 0.599999,-0.3999996 1.050781,-1.2487507 1.050781,-1.96875 v -1 c 0,-0.27999972 -0.22,-0.5 -0.5,-0.5 z"
style="fill:#808080;fill-opacity:1" /><circle
style="fill:#ed6b21;fill-opacity:1;stroke-width:0.0999999;stroke-linejoin:round;stop-color:#000000"
id="path891-6-7"
cx="7.2478447"
cy="2.8947182"
r="1.25" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1147"
width="1"
height="1"
x="15"
y="15" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1149"
width="1"
height="1"
x="0"
y="15.000001" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1151"
width="1"
height="1"
x="1.5"
y="14" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1153"
width="1"
height="1"
x="1.9999999"
y="13" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1155"
width="1"
height="1"
x="1.9999999"
y="11" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1157"
width="1"
height="1"
x="1.9999999"
y="9" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1165"
width="1"
height="1"
x="3.5"
y="14" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1167"
width="1"
height="1"
x="5.5"
y="14" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1169"
width="1"
height="1"
x="7.5"
y="14" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1171"
width="1"
height="1"
x="9.5"
y="14" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1173"
width="1"
height="1"
x="11.5"
y="14" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1175"
width="1"
height="1"
x="13.5"
y="14" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1177"
width="1"
height="1"
x="12"
y="13" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1179"
width="1"
height="1"
x="12"
y="11" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1181"
width="1"
height="1"
x="12"
y="9" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1183"
width="1"
height="1"
x="12"
y="6.9999995" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1185"
width="1"
height="1"
x="8"
y="13" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1187"
width="1"
height="1"
x="8"
y="11" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1189"
width="1"
height="1"
x="8"
y="9" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1193"
width="1"
height="1"
x="14.992764"
y="-0.71544611"
transform="rotate(45)" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1195"
width="1"
height="1"
x="15.119823"
y="1.0467962"
transform="rotate(45)" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1195-8"
width="1"
height="1"
x="6.7898164"
y="2.6156898"
transform="rotate(45)" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1219"
width="1"
height="1"
x="6.7273164"
y="0.60006464"
transform="rotate(45)" /><rect
style="fill:#999999;fill-opacity:1;stroke-width:0.1;stroke-linejoin:round;stop-color:#000000"
id="rect1221"
width="1"
height="1"
x="6.6726289"
y="-1.5171229"
transform="rotate(45)" /><circle
style="fill:#ed6b21;fill-opacity:1;stroke-width:0.0999999;stroke-linejoin:round;stop-color:#000000"
id="path891"
cx="8.4536505"
cy="7.967514"
r="1.25" /><circle
style="fill:#ed6b21;fill-opacity:1;stroke-width:0.0999999;stroke-linejoin:round;stop-color:#000000"
id="path891-6"
cx="11.844038"
cy="6.4523497"
r="1.25" /></svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -452,7 +452,37 @@ set(SLIC3R_SOURCES
SLA/BranchingTreeSLA.hpp
SLA/BranchingTreeSLA.cpp
SLA/ZCorrection.hpp
SLA/ZCorrection.cpp
SLA/ZCorrection.cpp
SLA/SupportIslands/EvaluateNeighbor.cpp
SLA/SupportIslands/EvaluateNeighbor.hpp
SLA/SupportIslands/ExpandNeighbor.cpp
SLA/SupportIslands/ExpandNeighbor.hpp
SLA/SupportIslands/IStackFunction.hpp
SLA/SupportIslands/LineUtils.cpp
SLA/SupportIslands/LineUtils.hpp
SLA/SupportIslands/NodeDataWithResult.hpp
SLA/SupportIslands/Parabola.hpp
SLA/SupportIslands/ParabolaUtils.cpp
SLA/SupportIslands/ParabolaUtils.hpp
SLA/SupportIslands/PointUtils.cpp
SLA/SupportIslands/PointUtils.hpp
SLA/SupportIslands/PolygonUtils.cpp
SLA/SupportIslands/PolygonUtils.hpp
SLA/SupportIslands/PostProcessNeighbor.cpp
SLA/SupportIslands/PostProcessNeighbor.hpp
SLA/SupportIslands/PostProcessNeighbors.cpp
SLA/SupportIslands/PostProcessNeighbors.hpp
SLA/SupportIslands/SampleConfig.hpp
SLA/SupportIslands/SampleConfigFactory.cpp
SLA/SupportIslands/SampleConfigFactory.hpp
SLA/SupportIslands/SupportIslandPoint.cpp
SLA/SupportIslands/SupportIslandPoint.hpp
SLA/SupportIslands/UniformSupportIsland.cpp
SLA/SupportIslands/UniformSupportIsland.hpp
SLA/SupportIslands/VectorUtils.hpp
SLA/SupportIslands/VoronoiGraph.hpp
SLA/SupportIslands/VoronoiGraphUtils.cpp
SLA/SupportIslands/VoronoiGraphUtils.hpp
BranchingTree/BranchingTree.cpp
BranchingTree/BranchingTree.hpp
BranchingTree/PointCloud.cpp
@ -524,6 +554,9 @@ foreach(_source IN ITEMS ${SLIC3R_SOURCES})
source_group("${_group_path}" FILES "${_source}")
endforeach()
# Create the source groups for source tree with root at CMAKE_CURRENT_SOURCE_DIR.
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_LIST_SLA})
if (SLIC3R_STATIC)
set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
endif ()
@ -541,6 +574,7 @@ add_library(libslic3r_cgal STATIC
MeshBoolean.hpp MeshBoolean.cpp
TryCatchSignal.hpp TryCatchSignal.cpp
Triangulation.hpp Triangulation.cpp
SLA/SupportIslands/VoronoiDiagramCGAL.hpp SLA/SupportIslands/VoronoiDiagramCGAL.cpp
)
target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(libslic3r_cgal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)

View File

@ -726,6 +726,8 @@ Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &c
{ return _clipper(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonProvider(clip), do_safety_offset); }
Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return intersection(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); }
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset)
@ -778,6 +780,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polyg
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); }
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
@ -1037,8 +1041,7 @@ Polygons union_parallel_reduce(const Polygons &subject)
});
}
Polygons simplify_polygons(const Polygons &subject)
{
Polygons simplify_polygons(const Polygons &subject) {
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
ClipperLib::Paths output;
@ -1048,27 +1051,9 @@ Polygons simplify_polygons(const Polygons &subject)
c.StrictlySimple(true);
c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true);
c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
// convert into Slic3r polygons
return to_polygons(std::move(output));
}
ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear)
{
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);
ClipperLib::PolyTree polytree;
ClipperLib::Clipper c;
// c.PreserveCollinear(true);
//FIXME StrictlySimple is very expensive! Is it needed?
c.StrictlySimple(true);
c.AddPaths(ClipperUtils::PolygonsProvider(subject), ClipperLib::ptSubject, true);
c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
// convert into ExPolygons
return PolyTreeToExPolygons(std::move(polytree));
}
Polygons top_level_islands(const Slic3r::Polygons &polygons)
{
CLIPPER_UTILS_TIME_LIMIT_MILLIS(CLIPPER_UTILS_TIME_LIMIT_DEFAULT);

View File

@ -454,6 +454,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfac
Slic3r::ExPolygons diff_ex(const Slic3r::Polygon &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
@ -477,6 +478,7 @@ inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygon
// Safety offset is applied to the clipping polygons only.
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox().
@ -639,7 +641,6 @@ void traverse_pt(const ClipperLib::PolyNodes &nodes, ExOrJustPolygons *retval)
/* OTHER */
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject);
Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject);
Polygons top_level_islands(const Slic3r::Polygons &polygons);

View File

@ -1419,21 +1419,31 @@ namespace Slic3r {
std::vector<sla::SupportPoint> sla_support_points;
if (version == 0) {
assert(object_data_points.size() % 3 == 0);
for (unsigned int i=0; i<object_data_points.size(); i+=3)
sla_support_points.emplace_back(float(std::atof(object_data_points[i+0].c_str())),
sla_support_points.push_back(sla::SupportPoint{Vec3f(
float(std::atof(object_data_points[i+0].c_str())),
float(std::atof(object_data_points[i+1].c_str())),
float(std::atof(object_data_points[i+2].c_str())),
0.4f,
false);
float(std::atof(object_data_points[i+2].c_str()))),
0.4f});
}
if (version == 1) {
auto get_support_point_type = [](double val)->sla::SupportPointType{
return (std::abs(val - 1.) < EPSILON) ? sla::SupportPointType::island :
(std::abs(val - 2.) < EPSILON) ? sla::SupportPointType::manual_add :
//(std::abs(val - 3.) < EPSILON) ? sla::SupportPointType::slope :
sla::SupportPointType::slope; // default for previous version of store points
};
assert(object_data_points.size() % 5 == 0);
for (unsigned int i=0; i<object_data_points.size(); i+=5)
sla_support_points.emplace_back(float(std::atof(object_data_points[i+0].c_str())),
float(std::atof(object_data_points[i+1].c_str())),
float(std::atof(object_data_points[i+2].c_str())),
float(std::atof(object_data_points[i+3].c_str())),
//FIXME storing boolean as 0 / 1 and importing it as float.
std::abs(std::atof(object_data_points[i+4].c_str()) - 1.) < EPSILON);
sla_support_points.push_back(
sla::SupportPoint{
Vec3f{float(std::atof(object_data_points[i+0].c_str())),
float(std::atof(object_data_points[i+1].c_str())),
float(std::atof(object_data_points[i+2].c_str()))},
float(std::atof(object_data_points[i+3].c_str())),
get_support_point_type(std::atof(object_data_points[i+4].c_str()))
});
}
if (!sla_support_points.empty())
@ -3539,10 +3549,22 @@ namespace Slic3r {
if (!sla_support_points.empty()) {
sprintf(buffer, "object_id=%d|", count);
out += buffer;
auto support_point_type_to_float = [](sla::SupportPointType t) -> float {
switch (t) {
case Slic3r::sla::SupportPointType::manual_add: return 2.f;
case Slic3r::sla::SupportPointType::island: return 1.f;
case Slic3r::sla::SupportPointType::slope: return 3.f;
default: assert(false); return 0.f;
}
};
// Store the layer height profile as a single space separated list.
for (size_t i = 0; i < sla_support_points.size(); ++i) {
sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island);
sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"),
sla_support_points[i].pos(0),
sla_support_points[i].pos(1),
sla_support_points[i].pos(2),
sla_support_points[i].head_front_radius,
support_point_type_to_float(sla_support_points[i].type));
out += buffer;
}
out += "\n";

View File

@ -19,7 +19,18 @@ namespace Slic3r {
* version 1 : ThreeMF_support_points_version=1
object_id=1|-12.055421 -2.658771 10.000000 0.4 0.0
object_id=2|-14.051745 -3.570338 5.000000 0.6 1.0
// introduced header with version number; x,y,z,head_size,is_new_island)
// introduced header with version number; x,y,z,head_size,type)
// before 2.9.1 fifth float means is_island (bool flag) -> value from 0.9999f to 1.0001f means it is support for island otherwise not. User edited points has always value zero.
// since 2.9.1 fifth float means type -> starts show user edited points
// type range value meaning
// (float is used only for compatibility, string will be better)
// from | to | meaning
// --------------------------------
// 0.9999f | 1.0001 | island (no change)
// 1.9999f | 2.0001 | manual edited points loose info about island
// 2.9999f | 3.0001 | generated point by slope ration
// all other values are readed also as slope type
*/
enum {

View File

@ -774,7 +774,7 @@ void AMFParserContext::endElement(const char * /* name */)
point(coord_idx) = float(atof(p));
if (++coord_idx == 5) {
m_object->sla_support_points.push_back(sla::SupportPoint(point));
m_object->sla_support_points.push_back(sla::SupportPoint{Vec3f(point[0], point[1], point[2]), point[3]});
coord_idx = 0;
}
if (end == nullptr)

View File

@ -239,7 +239,7 @@ int ray_circle_intersections(T r, T a, T b, T c, std::pair<Eigen::Matrix<T, 2, 1
// What if the line touches the circle?
return false;
}
return ray_circle_intersections_r2_lv2_c2(r * r, a, b, a * a + b * b, c, out);
return ray_circle_intersections_r2_lv2_c(r * r, a, b, a * a + b * b, c, out);
}
} } // namespace Slic3r::Geometry

View File

@ -40,7 +40,10 @@ public:
KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {}
KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; }
void clear() { m_nodes.clear(); }
const std::vector<size_t> &get_nodes() const { return m_nodes; }
// NOTE: Copy constructor cause failing FDM tests but not each run only from time to time.
// KDTreeIndirect(const KDTreeIndirect &rhs) : m_nodes(rhs.m_nodes), coordinate(rhs.coordinate) {}
KDTreeIndirect get_copy() const { KDTreeIndirect copy(coordinate); copy.m_nodes = m_nodes; return copy; }
void build(size_t num_indices)
{
std::vector<size_t> indices;
@ -63,10 +66,10 @@ public:
}
template<typename CoordType>
unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const
unsigned int descent_mask(const CoordType &point_coord, const double &search_radius, size_t idx, size_t dimension) const
{
CoordType dist = point_coord - this->coordinate(idx, dimension);
return (dist * dist < search_radius + CoordType(EPSILON)) ?
return (double(dist) * dist < search_radius + EPSILON) ?
// The plane intersects a hypersphere centered at point_coord of search_radius.
((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) :
// The plane does not intersect the hypersphere.
@ -213,44 +216,43 @@ std::array<size_t, K> find_closest_points(
const Tree &kdtree;
const PointType &point;
const FilterFn filter;
std::array<std::pair<size_t, CoordT>, K> results;
struct Result {
size_t index;
double distance_sq;
};
std::array<Result, K> results;
Visitor(const Tree &kdtree, const PointType &point, FilterFn filter)
: kdtree(kdtree), point(point), filter(filter)
{
results.fill(std::make_pair(Tree::npos,
std::numeric_limits<CoordT>::max()));
results.fill(Result{Tree::npos, std::numeric_limits<double>::max()});
}
unsigned int operator()(size_t idx, size_t dimension)
{
if (this->filter(idx)) {
auto dist = CoordT(0);
double distance_sq = 0.;
for (size_t i = 0; i < D; ++i) {
CoordT d = point[i] - kdtree.coordinate(idx, i);
dist += d * d;
distance_sq += double(d) * d;
}
auto res = std::make_pair(idx, dist);
auto it = std::lower_bound(results.begin(), results.end(),
res, [](auto &r1, auto &r2) {
return r1.second < r2.second;
});
Result res{idx, distance_sq};
auto lower_distance = [](const Result &r1, const Result &r2) {
return r1.distance_sq < r2.distance_sq; };
auto it = std::lower_bound(results.begin(), results.end(), res, lower_distance);
if (it != results.end()) {
std::rotate(it, std::prev(results.end()), results.end());
*it = res;
}
}
return kdtree.descent_mask(point[dimension],
results.front().second, idx,
dimension);
return kdtree.descent_mask(point[dimension], results.front().distance_sq, idx, dimension);
}
} visitor(kdtree, point, filter);
kdtree.visit(visitor);
std::array<size_t, K> ret;
for (size_t i = 0; i < K; i++) ret[i] = visitor.results[i].first;
for (size_t i = 0; i < K; i++)
ret[i] = visitor.results[i].index;
return ret;
}
@ -290,20 +292,20 @@ std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const P
struct Visitor {
const KDTreeIndirectType &kdtree;
const PointType center;
const CoordType max_distance_squared;
const double max_distance_squared;
const FilterFn filter;
std::vector<size_t> result;
Visitor(const KDTreeIndirectType &kdtree, const PointType& center, const CoordType &max_distance,
FilterFn filter) :
kdtree(kdtree), center(center), max_distance_squared(max_distance*max_distance), filter(filter) {
kdtree(kdtree), center(center), max_distance_squared(double(max_distance)*max_distance), filter(filter) {
}
unsigned int operator()(size_t idx, size_t dimension) {
if (this->filter(idx)) {
auto dist = CoordType(0);
double dist = 0.;
for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++i) {
CoordType d = center[i] - kdtree.coordinate(idx, i);
dist += d * d;
dist += double(d) * d;
}
if (dist < max_distance_squared) {
result.push_back(idx);

View File

@ -64,6 +64,18 @@ double Line::perp_distance_to(const Point &point) const
return std::abs(cross2(v, va)) / v.norm();
}
double Line::perp_signed_distance_to(const Point &point) const {
// Sign is dependent on the line orientation.
// For CCW oriented polygon is possitive distace into shape and negative outside.
// For Line({0,0},{0,2}) and point {1,1} the distance is negative one(-1).
const Line &line = *this;
const Vec2d v = (line.b - line.a).cast<double>();
const Vec2d va = (point - line.a).cast<double>();
if (line.a == line.b)
return va.norm();
return cross2(v, va) / v.norm();
}
double Line::orientation() const
{
double angle = this->atan2_();

View File

@ -216,6 +216,7 @@ public:
double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); }
double distance_to_infinite_squared(const Point &point, Point *closest_point) const { return line_alg::distance_to_infinite_squared(*this, point, closest_point); }
double perp_distance_to(const Point &point) const;
double perp_signed_distance_to(const Point &point) const;
bool parallel_to(double angle) const;
bool parallel_to(const Line& line) const;
bool perpendicular_to(double angle) const;

View File

@ -604,7 +604,6 @@ static std::vector<std::string> s_Preset_sla_print_options {
"branchingsupport_object_elevation",
"support_points_density_relative",
"support_points_minimal_distance",
"slice_closing_radius",
"slicing_mode",
"pad_enable",

View File

@ -4564,14 +4564,6 @@ void PrintConfigDef::init_sla_params()
def->min = 0;
def->set_default_value(new ConfigOptionInt(100));
def = this->add("support_points_minimal_distance", coFloat);
def->label = L("Minimal distance of the support points");
def->category = L("Supports");
def->tooltip = L("No support points will be placed closer than this threshold.");
def->sidetext = L("mm");
def->min = 0;
def->set_default_value(new ConfigOptionFloat(1.));
def = this->add("pad_enable", coBool);
def->label = L("Use pad");
def->category = L("Pad");
@ -4994,7 +4986,8 @@ static std::set<std::string> PrintConfigDef_ignore = {
"infill_only_where_needed",
"gcode_binary", // Introduced in 2.7.0-alpha1, removed in 2.7.1 (replaced by binary_gcode).
"wiping_volumes_extruders", // Removed in 2.7.3-alpha1.
"wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle" // Removed in 2.9.0
"wipe_tower_x", "wipe_tower_y", "wipe_tower_rotation_angle", // Removed in 2.9.0
"support_points_minimal_distance", // End of the using in 2.9.1 (change algorithm for the support generator)
};
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)

View File

@ -1143,11 +1143,8 @@ PRINT_CONFIG_CLASS_DEFINE(
// and the model object's bounding box bottom. Units in mm.
((ConfigOptionFloat, branchingsupport_object_elevation))/*= 5.0*/
/////// Following options influence automatic support points placement:
((ConfigOptionInt, support_points_density_relative))
((ConfigOptionFloat, support_points_minimal_distance))
// Now for the base pool (pad) /////////////////////////////////////////////

View File

@ -0,0 +1,23 @@
#include "EvaluateNeighbor.hpp"
#include "ExpandNeighbor.hpp"
using namespace Slic3r::sla;
EvaluateNeighbor::EvaluateNeighbor(VoronoiGraph::ExPath & result,
const VoronoiGraph::Node *node,
double distance_to_node,
const VoronoiGraph::Path &prev_path)
: post_process_neighbor(
std::make_unique<PostProcessNeighbors>(result,
node,
distance_to_node,
prev_path))
{}
void EvaluateNeighbor::process(CallStack &call_stack)
{
NodeDataWithResult &data = *post_process_neighbor;
call_stack.emplace(std::move(post_process_neighbor));
for (const VoronoiGraph::Node::Neighbor &neighbor : data.node->neighbors)
call_stack.emplace(std::make_unique<ExpandNeighbor>(data, neighbor));
}

View File

@ -0,0 +1,36 @@
#ifndef slic3r_SLA_SuppotstIslands_EvaluateNeighbor_hpp_
#define slic3r_SLA_SuppotstIslands_EvaluateNeighbor_hpp_
#include <memory>
#include "IStackFunction.hpp"
#include "PostProcessNeighbors.hpp"
#include "VoronoiGraph.hpp"
namespace Slic3r::sla {
/// <summary>
/// create on stack
/// 1 * PostProcessNeighbors
/// N * ExpandNode
/// </summary>
class EvaluateNeighbor : public IStackFunction
{
std::unique_ptr<PostProcessNeighbors> post_process_neighbor;
public:
EvaluateNeighbor(
VoronoiGraph::ExPath & result,
const VoronoiGraph::Node *node,
double distance_to_node = 0.,
const VoronoiGraph::Path &prev_path = VoronoiGraph::Path({}, 0.));
/// <summary>
/// create on stack
/// 1 * PostProcessNeighbors
/// N * ExpandNode
/// </summary>
virtual void process(CallStack &call_stack);
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_EvaluateNeighbor_hpp_

View File

@ -0,0 +1,44 @@
#include "ExpandNeighbor.hpp"
#include "VoronoiGraphUtils.hpp"
using namespace Slic3r::sla;
ExpandNeighbor::ExpandNeighbor(
NodeDataWithResult & data,
const VoronoiGraph::Node::Neighbor &neighbor)
: data(data)
, neighbor(neighbor)
{}
void ExpandNeighbor::process(CallStack &call_stack)
{
if (data.skip_nodes.find(neighbor.node) != data.skip_nodes.end()) return;
// detection of circle
auto circle_opt = VoronoiGraphUtils::create_circle(data.act_path,
neighbor);
if (circle_opt.has_value()) {
size_t circle_index = data.result.circles.size();
data.circle_indexes.push_back(circle_index);
data.result.circles.push_back(*circle_opt);
return;
}
// create copy of path(not circles, not side_branches)
const VoronoiGraph::Node &next_node = *neighbor.node;
// is next node leaf ?
if (next_node.neighbors.size() == 1) {
VoronoiGraph::Path side_branch({&next_node}, neighbor.length());
data.side_branches.push(std::move(side_branch));
return;
}
auto post_process_neighbor = std::make_unique<PostProcessNeighbor>(data);
VoronoiGraph::ExPath &neighbor_path = post_process_neighbor->neighbor_path;
call_stack.emplace(std::move(post_process_neighbor));
call_stack.emplace(
std::make_unique<EvaluateNeighbor>(neighbor_path, neighbor.node,
neighbor.length(),
data.act_path));
}

View File

@ -0,0 +1,35 @@
#ifndef slic3r_SLA_SuppotstIslands_ExpandNeighbor_hpp_
#define slic3r_SLA_SuppotstIslands_ExpandNeighbor_hpp_
#include "IStackFunction.hpp"
#include "VoronoiGraph.hpp"
#include "PostProcessNeighbor.hpp"
#include "EvaluateNeighbor.hpp"
namespace Slic3r::sla {
/// <summary>
/// Expand neighbor to
/// - PostProcessNeighbor
/// - EvaluateNeighbor
/// </summary>
class ExpandNeighbor : public IStackFunction
{
NodeDataWithResult & data;
const VoronoiGraph::Node::Neighbor &neighbor;
public:
ExpandNeighbor(NodeDataWithResult & data,
const VoronoiGraph::Node::Neighbor &neighbor);
/// <summary>
/// Expand neighbor to
/// - PostProcessNeighbor
/// - EvaluateNeighbor
/// </summary>
/// <param name="call_stack">Output callStack</param>
virtual void process(CallStack &call_stack);
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_ExpandNeighbor_hpp_

View File

@ -0,0 +1,23 @@
#ifndef slic3r_SLA_SuppotstIslands_IStackFunction_hpp_
#define slic3r_SLA_SuppotstIslands_IStackFunction_hpp_
#include <stack>
#include <memory>
namespace Slic3r::sla {
/// <summary>
/// Interface for objects inside of CallStack.
/// It is way to prevent stack overflow inside recurrent functions.
/// </summary>
class IStackFunction
{
public:
virtual ~IStackFunction() = default;
virtual void process(std::stack<std::unique_ptr<IStackFunction>> &call_stack) = 0;
};
using CallStack = std::stack<std::unique_ptr<IStackFunction>>;
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_IStackFunction_hpp_

View File

@ -0,0 +1,458 @@
#include "LineUtils.hpp"
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Geometry/Circle.hpp>
#include <functional>
#include "VectorUtils.hpp"
#include "PointUtils.hpp"
using namespace Slic3r::sla;
// sort counter clock wise lines
void LineUtils::sort_CCW(Lines &lines, const Point& center)
{
std::function<double(const Line &)> calc = [&center](const Line &line) {
Point p = line.a - center;
return std::atan2(p.y(), p.x());
};
VectorUtils::sort_by(lines, calc);
}
bool LineUtils::is_parallel_y(const Line &line) {
coord_t x_change = line.a.x() - line.b.x();
return (x_change == 0);
}
bool LineUtils::is_parallel_y(const Linef &line)
{
double x_change = line.a.x() - line.b.x();
return (fabs(x_change) < std::numeric_limits<double>::epsilon());
}
std::optional<Slic3r::Line> LineUtils::crop_ray(const Line & ray,
const Point &center,
double radius)
{
if (is_parallel_y(ray)) {
coord_t x = ray.a.x();
coord_t diff = x - center.x();
coord_t abs_diff = abs(diff);
if (abs_diff > radius) return {};
// create cross points
double move_y = sqrt(radius * radius - static_cast<double>(x) * x);
coord_t y = static_cast<coord_t>(std::round(move_y));
coord_t cy = center.y();
Point first(x, cy + y);
Point second(x,cy - y);
return Line(first, second);
} else {
Line moved_line(ray.a - center, ray.b - center);
double a, b, c;
std::tie(a, b, c) = get_param(moved_line);
std::pair<Vec2d, Vec2d> points;
int count = Slic3r::Geometry::ray_circle_intersections(
radius, a, b, c, points);
if (count != 2) return {};
return Line(points.first.cast<coord_t>() + center,
points.second.cast<coord_t>() + center);
}
}
std::optional<Slic3r::Linef> LineUtils::crop_ray(const Linef &ray,
const Point &center,
double radius)
{
Vec2d center_d = center.cast<double>();
if (is_parallel_y(ray)) {
double x = ray.a.x();
double diff = x - center_d.x();
double abs_diff = fabs(diff);
if (abs_diff > radius) return {};
// create cross points
double y = sqrt(radius * radius - x * x);
Vec2d first(x, y);
Vec2d second(x, -y);
return Linef(first + center_d,
second + center_d);
} else {
Linef moved_line(ray.a - center_d, ray.b - center_d);
double a, b, c;
std::tie(a, b, c) = get_param(moved_line);
std::pair<Vec2d, Vec2d> points;
int count = Slic3r::Geometry::ray_circle_intersections(radius, a, b,
c, points);
if (count != 2) return {};
return Linef(points.first + center_d, points.second + center_d);
}
}
std::optional<Slic3r::Line> LineUtils::crop_half_ray(const Line & half_ray,
const Point &center,
double radius)
{
std::optional<Line> segment = crop_ray(half_ray, center, radius);
if (!segment.has_value()) return {};
Point dir = LineUtils::direction(half_ray);
using fnc = std::function<bool(const Point &)>;
fnc use_point_x = [&half_ray, &dir](const Point &p) -> bool {
return (p.x() > half_ray.a.x()) == (dir.x() > 0);
};
fnc use_point_y = [&half_ray, &dir](const Point &p) -> bool {
return (p.y() > half_ray.a.y()) == (dir.y() > 0);
};
bool use_x = PointUtils::is_majorit_x(dir);
fnc use_point = (use_x) ? use_point_x : use_point_y;
bool use_a = use_point(segment->a);
bool use_b = use_point(segment->b);
if (!use_a && !use_b) return {};
if (use_a && use_b) return segment;
return Line(half_ray.a, (use_a)?segment->a : segment->b);
}
std::optional<Slic3r::Linef> LineUtils::crop_half_ray(const Linef & half_ray,
const Point &center,
double radius)
{
std::optional<Linef> segment = crop_ray(half_ray, center, radius);
if (!segment.has_value()) return {};
Vec2d dir = half_ray.b - half_ray.a;
using fnc = std::function<bool(const Vec2d &)>;
fnc use_point_x = [&half_ray, &dir](const Vec2d &p) -> bool {
return (p.x() > half_ray.a.x()) == (dir.x() > 0);
};
fnc use_point_y = [&half_ray, &dir](const Vec2d &p) -> bool {
return (p.y() > half_ray.a.y()) == (dir.y() > 0);
};
bool use_x = PointUtils::is_majorit_x(dir);
fnc use_point = (use_x) ? use_point_x : use_point_y;
bool use_a = use_point(segment->a);
bool use_b = use_point(segment->b);
if (!use_a && !use_b) return {};
if (use_a && use_b) return segment;
return Linef(half_ray.a, (use_a) ? segment->a : segment->b);
}
std::optional<Slic3r::Line> LineUtils::crop_line(const Line & line,
const Point &center,
double radius)
{
std::optional<Line> segment = crop_ray(line, center, radius);
if (!segment.has_value()) return {};
Point dir = line.b - line.a;
using fnc = std::function<bool(const Point &)>;
fnc use_point_x = [&line, &dir](const Point &p) -> bool {
return (dir.x() > 0) ? (p.x() > line.a.x()) && (p.x() < line.b.x()) :
(p.x() < line.a.x()) && (p.x() > line.b.x());
};
fnc use_point_y = [&line, &dir](const Point &p) -> bool {
return (dir.y() > 0) ? (p.y() > line.a.y()) && (p.y() < line.b.y()) :
(p.y() < line.a.y()) && (p.y() > line.b.y());
};
bool use_x = PointUtils::is_majorit_x(dir);
fnc use_point = (use_x) ? use_point_x : use_point_y;
bool use_a = use_point(segment->a);
bool use_b = use_point(segment->b);
if (!use_a && !use_b) return {};
if (use_a && use_b) return segment;
bool same_dir = (use_x) ?
((dir.x() > 0) == ((segment->b.x() - segment->a.x()) > 0)) :
((dir.y() > 0) == ((segment->b.y() - segment->a.y()) > 0)) ;
if (use_a) {
if (same_dir)
return Line(segment->a, line.b);
else
return Line(line.a, segment->a);
} else { // use b
if (same_dir)
return Line(line.a, segment->b);
else
return Line(segment->b, line.b);
}
}
std::optional<Slic3r::Linef> LineUtils::crop_line(const Linef & line,
const Point &center,
double radius)
{
std::optional<Linef> segment = crop_ray(line, center, radius);
if (!segment.has_value()) return {};
Vec2d dir = line.b - line.a;
using fnc = std::function<bool(const Vec2d &)>;
fnc use_point_x = [&line, &dir](const Vec2d &p) -> bool {
return (dir.x() > 0) ? (p.x() > line.a.x()) && (p.x() < line.b.x()) :
(p.x() < line.a.x()) && (p.x() > line.b.x());
};
fnc use_point_y = [&line, &dir](const Vec2d &p) -> bool {
return (dir.y() > 0) ? (p.y() > line.a.y()) && (p.y() < line.b.y()) :
(p.y() < line.a.y()) && (p.y() > line.b.y());
};
bool use_x = PointUtils::is_majorit_x(dir);
fnc use_point = (use_x) ? use_point_x : use_point_y;
bool use_a = use_point(segment->a);
bool use_b = use_point(segment->b);
if (!use_a && !use_b) return {};
if (use_a && use_b) return segment;
bool same_dir = (use_x) ? ((dir.x() > 0) ==
((segment->b.x() - segment->a.x()) > 0)) :
((dir.y() > 0) ==
((segment->b.y() - segment->a.y()) > 0));
if (use_a) {
if (same_dir)
return Linef(segment->a, line.b);
else
return Linef(line.a, segment->a);
} else { // use b
if (same_dir)
return Linef(line.a, segment->b);
else
return Linef(segment->b, line.b);
}
}
std::tuple<double, double, double> LineUtils::get_param(const Line &line) {
Vector normal = line.normal();
double a = normal.x();
double b = normal.y();
double c = -a * line.a.x() - b * line.a.y();
return {a, b, c};
}
std::tuple<double, double, double> LineUtils::get_param(const Linef &line)
{
Vec2d direction = line.b - line.a;
Vec2d normal(-direction.y(), direction.x());
double a = normal.x();
double b = normal.y();
double c = -a * line.a.x() - b * line.a.y();
return {a, b, c};
}
void LineUtils::draw(SVG & svg,
const Line &line,
const char *color,
coordf_t stroke_width,
const char *name,
bool side_points,
const char *color_a,
const char *color_b)
{
svg.draw(line, color, stroke_width);
bool use_name = name != nullptr;
if (use_name) {
Point middle = line.a/2 + line.b/2;
svg.draw_text(middle, name, color);
}
if (side_points) {
std::string name_a = (use_name) ? "A" : (std::string("A_") + name);
std::string name_b = (use_name) ? "B" : (std::string("B_") + name);
svg.draw_text(line.a, name_a.c_str(), color_a);
svg.draw_text(line.b, name_b.c_str(), color_b);
}
}
double LineUtils::perp_distance(const Linef &line, Vec2d p)
{
Vec2d v = line.b - line.a; // direction
Vec2d va = p - line.a;
return std::abs(cross2(v, va)) / v.norm();
}
bool LineUtils::is_parallel(const Line &first, const Line &second)
{
Vec2i64 dir1 = direction(first).cast<int64_t>();
Vec2i64 dir2 = direction(second).cast<int64_t>();
return Slic3r::cross2(dir1, dir2) == 0;
}
std::optional<Slic3r::Vec2d> LineUtils::intersection(const Line &ray1, const Line &ray2)
{
const Vec2d v1 = direction(ray1).cast<double>();
const Vec2d v2 = direction(ray2).cast<double>();
double denom = cross2(v1, v2);
if (fabs(denom) < std::numeric_limits<float>::epsilon()) return {};
const Vec2d v12 = (ray1.a - ray2.a).cast<double>();
double nume = cross2(v2, v12);
double t = nume / denom;
return (ray1.a.cast<double>() + t * v1);
}
bool LineUtils::belongs(const Line &line, const Point &point, double benevolence)
{
const Point &a = line.a;
const Point &b = line.b;
auto is_in_interval = [](coord_t value, coord_t from, coord_t to) -> bool
{
if (from < to) {
// from < value < to
if (from > value || to < value) return false;
} else {
// to < value < from
if (from < value || to > value) return false;
}
return true;
};
if (!is_in_interval(point.x(), a.x(), b.x()) ||
!is_in_interval(point.y(), a.y(), b.y()) )
{ // out of interval
return false;
}
double distance = line.perp_distance_to(point);
if (distance < benevolence) return true;
return false;
}
Slic3r::Point LineUtils::direction(const Line &line)
{
return line.b - line.a;
}
Slic3r::Point LineUtils::middle(const Line &line) {
// division before adding to prevent data type overflow
return line.a / 2 + line.b / 2;
}
double LineUtils::foot(const Line &line, const Point &point)
{
Vec2d a = line.a.cast<double>();
Vec2d vec = point.cast<double>() - a;
Vec2d b = line.b.cast<double>();
Vec2d dir = b - a;
double l2 = dir.squaredNorm();
return vec.dot(dir) / l2;
}
LineUtils::LineConnection LineUtils::create_line_connection(
const Slic3r::Lines &lines)
{
LineConnection line_connection;
static const size_t bad_index = -1;
auto insert = [&](size_t line_index, size_t connected, bool connect_by_a){
auto item = line_connection.find(line_index);
if (item == line_connection.end()) {
// create new
line_connection[line_index] = (connect_by_a) ?
std::pair<size_t, size_t>(connected, bad_index) :
std::pair<size_t, size_t>(bad_index, connected);
} else {
std::pair<size_t, size_t> &pair = item->second;
size_t &ref_index = (connect_by_a) ? pair.first : pair.second;
assert(ref_index == bad_index);
ref_index = connected;
}
};
auto inserts = [&](size_t i1, size_t i2)->bool{
bool is_l1_a_connect = true; // false => l1_b_connect
const Slic3r::Line &l1 = lines[i1];
const Slic3r::Line &l2 = lines[i2];
if (!PointUtils::is_equal(l1.a, l2.b)) return false;
if (!PointUtils::is_equal(l1.b, l2.a)) return false;
else is_l1_a_connect = false;
insert(i1, i2, is_l1_a_connect);
insert(i2, i1, !is_l1_a_connect);
return true;
};
std::vector<size_t> not_finished;
size_t prev_index = lines.size() - 1;
for (size_t index = 0; index < lines.size(); ++index) {
if (!inserts(prev_index, index)) {
bool found_index = false;
bool found_prev_index = false;
not_finished.erase(std::remove_if(not_finished.begin(),
not_finished.end(),
[&](const size_t &not_finished_index) {
if (!found_index && inserts(index, not_finished_index)) {
found_index = true;
return true;
}
if (!found_prev_index && inserts(prev_index, not_finished_index)) {
found_prev_index = true;
return true;
}
return false;
}),
not_finished.end());
if (!found_index) not_finished.push_back(index);
if (!found_prev_index) not_finished.push_back(prev_index);
}
prev_index = index;
}
assert(not_finished.empty());
return line_connection;
}
Slic3r::BoundingBox LineUtils::create_bounding_box(const Lines &lines) {
Points pts;
pts.reserve(lines.size()*2);
for (const Line &line : lines) {
pts.push_back(line.a);
pts.push_back(line.b);
}
return BoundingBox(pts);
}
std::map<size_t, size_t> LineUtils::create_line_connection_over_b(const Lines &lines)
{
std::map<size_t, size_t> line_connection;
auto inserts = [&](size_t i1, size_t i2) -> bool {
const Line &l1 = lines[i1];
const Line &l2 = lines[i2];
if (!PointUtils::is_equal(l1.b, l2.a))
return false;
assert(line_connection.find(i1) == line_connection.end());
line_connection[i1] = i2;
return true;
};
std::vector<size_t> not_finished_a;
std::vector<size_t> not_finished_b;
size_t prev_index = lines.size() - 1;
for (size_t index = 0; index < lines.size(); ++index) {
if (!inserts(prev_index, index)) {
bool found_b = false;
not_finished_b.erase(std::remove_if(not_finished_b.begin(), not_finished_b.end(),
[&](const size_t &not_finished_index) {
if (!found_b && inserts(prev_index, not_finished_index)) {
found_b = true;
return true;
}
return false;
}),not_finished_b.end());
if (!found_b) not_finished_a.push_back(prev_index);
bool found_a = false;
not_finished_a.erase(std::remove_if(not_finished_a.begin(), not_finished_a.end(),
[&](const size_t &not_finished_index) {
if (!found_a && inserts(not_finished_index, index)) {
found_a = true;
return true;
}
return false;
}),not_finished_a.end());
if (!found_a) not_finished_b.push_back(index);
}
prev_index = index;
}
assert(not_finished_a.empty());
assert(not_finished_b.empty());
return line_connection;
}
void LineUtils::draw(SVG & svg,
const Lines &lines,
const char * color,
coordf_t stroke_width,
bool ord,
bool side_points,
const char * color_a,
const char * color_b)
{
for (const auto &line : lines) {
draw(svg, line, color, stroke_width,
(ord) ? std::to_string(&line - &lines.front()).c_str() : nullptr,
side_points, color_a, color_b);
}
}

View File

@ -0,0 +1,227 @@
#ifndef slic3r_SLA_SuppotstIslands_LineUtils_hpp_
#define slic3r_SLA_SuppotstIslands_LineUtils_hpp_
#include <optional>
#include <tuple>
#include <string>
#include <map>
#include <libslic3r/Line.hpp>
#include <libslic3r/SVG.hpp>
#include "PointUtils.hpp"
namespace Slic3r::sla {
/// <summary>
/// Class which contain collection of static function
/// for work with Line and Lines.
/// QUESTION: Is it only for SLA?
/// </summary>
class LineUtils
{
public:
LineUtils() = delete;
/// <summary>
/// Sort lines to be in counter clock wise order only by point Line::a and function std::atan2
/// </summary>
/// <param name="lines">Lines to be sort</param>
/// <param name="center">Center for CCW order</param>
static void sort_CCW(Lines &lines, const Point &center);
/// <summary>
/// Create line segment intersection of line and circle
/// </summary>
/// <param name="line">Input line.</param>
/// <param name="center">Circle center.</param>
/// <param name="radius">Circle radius.</param>
/// <returns>Chord -> line segment inside circle</returns>
static std::optional<Slic3r::Line> crop_line(const Line & line,
const Point &center,
double radius);
static std::optional<Slic3r::Linef> crop_line(const Linef & line,
const Point &center,
double radius);
/// <summary>
/// Create line segment intersection of ray and circle, when exist
/// </summary>
/// <param name="ray">Input ray.</param>
/// <param name="center">Circle center.</param>
/// <param name="radius">Circle radius.</param>
/// <returns>Chord -> line segment inside circle</returns>
static std::optional<Slic3r::Line> crop_ray(const Line & ray,
const Point &center,
double radius);
static std::optional<Slic3r::Linef> crop_ray(const Linef & ray,
const Point &center,
double radius);
/// <summary>
/// Create line segment intersection of half ray(start point and direction) and circle, when exist
/// </summary>
/// <param name="half_ray">Use Line::a as start point and Line::b as direction but no limit</param>
/// <param name="center">Circle center.</param>
/// <param name="radius">Circle radius.</param>
/// <returns>Chord -> line segment inside circle</returns>
static std::optional<Slic3r::Line> crop_half_ray(const Line & half_ray,
const Point &center,
double radius);
static std::optional<Slic3r::Linef> crop_half_ray(const Linef & half_ray,
const Point &center,
double radius);
/// <summary>
/// check if line is parallel to Y
/// </summary>
/// <param name="line">Input line</param>
/// <returns>True when parallel otherwise FALSE</returns>
static bool is_parallel_y(const Line &line);
static bool is_parallel_y(const Linef &line);
/// <summary>
/// Create parametric coeficient
/// ax + by + c = 0
/// Can't be parallel to Y axis - check by function is_parallel_y
/// </summary>
/// <param name="line">Input line - cant be parallel with y axis</param>
/// <returns>a, b, c</returns>
static std::tuple<double, double, double> get_param(const Line &line);
static std::tuple<double, double, double> get_param(const Linef &line);
/// <summary>
/// Calculate distance between point and ray
/// </summary>
/// <param name="line">a and b are only for direction, not limit</param>
/// <param name="p">Point in space</param>
/// <returns>Euclid perpedicular distance</returns>
static double perp_distance(const Linef &line, Vec2d p);
/// <summary>
/// Create cross product of line direction.
/// When zero than they are parallel.
/// </summary>
/// <param name="first">First line</param>
/// <param name="second">Second line</param>
/// <returns>True when direction are same(scaled or oposit or combination) otherwise FALSE</returns>
static bool is_parallel(const Line &first, const Line &second);
/// <summary>
/// Intersection of line - no matter on line limitation
/// </summary>
/// <param name="ray1">first line</param>
/// <param name="ray2">second line</param>
/// <returns>intersection point when exist</returns>
static std::optional<Vec2d> intersection(const Line &ray1, const Line &ray2);
/// <summary>
/// Check when point lays on line and belongs line range(from point a to point b)
/// </summary>
/// <param name="line">source line</param>
/// <param name="point">point to check</param>
/// <param name="point">maximal distance from line to belongs line</param>
/// <returns>True when points lay between line.a and line.b</returns>
static bool belongs(const Line & line,
const Point &point,
double benevolence = 1.);
/// <summary>
/// Create direction from point a to point b
/// Direction vector is represented as point
/// </summary>
/// <param name="line">input line</param>
/// <returns>line direction | b - a </returns>
static Point direction(const Line &line);
/// <summary>
/// middle point, center of line
/// </summary>
/// <param name="line"></param>
/// <returns>ceneter of line | a+b / 2 </returns>
static Point middle(const Line &line);
/// <summary>
/// Calculate foot point in maner of Geometry::foot_pt
/// - without unnecessary conversion
/// </summary>
/// <param name="line">input line</param>
/// <param name="point">point to search foot on line</param>
/// <returns>ration betwen point line.a and line.b (in range from 0. to 1.)</returns>
static double foot(const Line &line, const Point& point);
// line index, <a connection, b connection>
using LineConnection = std::map<size_t, std::pair<size_t, size_t>>;
/// <summary>
/// Create data structure from exPolygon lines to find if two lines are connected
/// !! not tested
/// </summary>
/// <param name="lines">Lines created from ExPolygon</param>
/// <returns>map of connected lines.</returns>
static LineConnection create_line_connection(const Lines &lines);
/// <summary>
/// create bounding box around lines
/// </summary>
/// <param name="lines">input lines</param>
/// <returns>Bounding box</returns>
static BoundingBox create_bounding_box(const Lines &lines);
/// <summary>
/// Create data structure from exPolygon lines to store connected line indexes
/// </summary>
/// <param name="lines">Lines created from ExPolygon</param>
/// <returns>map of connected lines over point line::b</returns>
static std::map<size_t, size_t> create_line_connection_over_b(const Lines &lines);
/// <summary>
/// Comparator to sort points laying on line from point a to point b
/// </summary>
struct SortFromAToB
{
std::function<bool(const Point &, const Point &)> compare;
SortFromAToB(const Line &line)
{
Point dir = LineUtils::direction(line);
compare = (PointUtils::is_majorit_x(dir)) ?
((dir.x() < 0) ? is_x_grater : is_x_smaller) :
((dir.y() < 0) ? is_y_grater : is_y_smaller);
}
static bool is_x_grater(const Point &left, const Point &right)
{
return left.x() > right.x();
}
static bool is_x_smaller(const Point &left, const Point &right)
{
return left.x() < right.x();
}
static bool is_y_grater(const Point &left, const Point &right)
{
return left.y() > right.y();
}
static bool is_y_smaller(const Point &left, const Point &right)
{
return left.y() < right.y();
}
bool operator()(const Point &left, const Point &right)
{
return compare(left, right);
}
};
static void draw(SVG & svg,
const Line &line,
const char *color = "gray",
coordf_t stroke_width = 0,
const char *name = nullptr,
bool side_points = false,
const char *color_a = "lightgreen",
const char *color_b = "lightblue");
static void draw(SVG & svg,
const Lines &lines,
const char *color = "gray",
coordf_t stroke_width = 0,
bool ord = false, // write order as text
bool side_points = false,
const char *color_a = "lightgreen",
const char *color_b = "lightblue");
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_LineUtils_hpp_

View File

@ -0,0 +1,73 @@
#ifndef slic3r_SLA_SuppotstIslands_NodeDataWithResult_hpp_
#define slic3r_SLA_SuppotstIslands_NodeDataWithResult_hpp_
#include <vector>
#include <set>
#include "VoronoiGraph.hpp"
namespace Slic3r::sla {
/// <summary>
/// DTO for process node during depth search
/// which create longest path in voronoi graph
/// </summary>
struct NodeDataWithResult
{
// result for this node
VoronoiGraph::ExPath &result;
// actual proccessed node
const VoronoiGraph::Node *node;
// distance to this node from input node
double distance_to_node;
// path from start point to this node
// last one is actual node
VoronoiGraph::Path act_path;
// skip node when circle start - must end at this node
// set --> multiple cirle could start at same node
// previous node should be skiped to so it is initialized with it
std::set<const VoronoiGraph::Node *> skip_nodes; // circle
// store all circle indexes this node is lay on
// used to create connected circles structure
std::vector<size_t> circle_indexes;
// When circle_indexes is not empty node lays on circle
// and in this node is not searching for longest path only store side
// branches(not part of circle)
// indexes of circle ending in this node(could be more than one)
std::vector<size_t> end_circle_indexes;
// When end_circle_indexes == circle_indexes
// than it is end of circle (multi circle structure) and it is processed
// contain possible continue path
// possible empty
VoronoiGraph::ExPath::SideBranches side_branches;
public:
// append node to act path
NodeDataWithResult(
VoronoiGraph::ExPath & result,
const VoronoiGraph::Node *node,
double distance_to_node,
VoronoiGraph::Path &&act_path,
std::set<const VoronoiGraph::Node *> &&skip_nodes
)
: result(result)
, node(node)
, distance_to_node(distance_to_node)
, act_path(std::move(act_path)) // copy prev and append actual node
, skip_nodes(std::move(skip_nodes))
{
//prev_path.extend(node, distance_to_node)
//const VoronoiGraph::Node *prev_node = (prev_path.nodes.size() >= 1) ?
// prev_path.nodes.back() :
// nullptr;
//skip_nodes = {prev_node};
}
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_NodeDataWithResult_hpp_

View File

@ -0,0 +1,43 @@
#ifndef slic3r_SLA_SuppotstIslands_Parabola_hpp_
#define slic3r_SLA_SuppotstIslands_Parabola_hpp_
#include <libslic3r/Line.hpp>
#include <libslic3r/Point.hpp>
namespace Slic3r::sla {
/// <summary>
/// DTO store prabola params
/// A parabola can be defined geometrically as a set of points (locus of points) in the Euclidean plane:
/// Where distance from focus point is same as distance from line(directrix).
/// </summary>
struct Parabola
{
Line directrix;
Point focus;
Parabola(Line directrix, Point focus)
: directrix(std::move(directrix)), focus(std::move(focus))
{}
};
/// <summary>
/// DTO store segment of parabola
/// Parabola with start(from) and end(to) point lay on parabola
/// </summary>
struct ParabolaSegment: public Parabola
{
Point from;
Point to;
ParabolaSegment(Parabola parabola, Point from, Point to) :
Parabola(std::move(parabola)), from(from), to(to)
{}
ParabolaSegment(Line directrix, Point focus, Point from, Point to)
: Parabola(directrix, focus), from(from), to(to)
{}
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_Parabola_hpp_

View File

@ -0,0 +1,109 @@
#include "ParabolaUtils.hpp"
#include "PointUtils.hpp"
#include "VoronoiGraphUtils.hpp"
// sampling parabola
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Geometry/VoronoiOffset.hpp>
#include <libslic3r/Geometry/VoronoiVisualUtils.hpp>
using namespace Slic3r::sla;
double ParabolaUtils::length(const ParabolaSegment &parabola)
{
const Point &point = parabola.focus;
const Line & line = parabola.directrix;
Line norm_line(point, point + line.normal());
// sign of distance is resolved by dot product in function is_over_zero()
double scaled_x1 = norm_line.perp_distance_to(parabola.from);
double scaled_x2 = norm_line.perp_distance_to(parabola.to);
double parabola_scale = 1. / (4. * focal_length(parabola));
double x1 = scaled_x1 * parabola_scale;
double x2 = scaled_x2 * parabola_scale;
double length_x1 = parabola_arc_length(x1) / parabola_scale;
double length_x2 = parabola_arc_length(x2) / parabola_scale;
return (is_over_zero(parabola)) ?
(length_x1 + length_x2) : // interval is over zero
fabs(length_x1 - length_x2); // interval is on same side of parabola
}
double ParabolaUtils::length_by_sampling(
const ParabolaSegment &parabola,
double discretization_step)
{
using VD = Slic3r::Geometry::VoronoiDiagram;
std::vector<VD::point_type> parabola_samples({
VoronoiGraphUtils::to_point(parabola.from),
VoronoiGraphUtils::to_point(parabola.to)});
VD::point_type source_point = VoronoiGraphUtils::to_point(parabola.focus);
VD::segment_type source_segment = VoronoiGraphUtils::to_segment(parabola.directrix);
::boost::polygon::voronoi_visual_utils<double>::discretize(
source_point, source_segment, discretization_step, &parabola_samples);
double sumLength = 0;
for (size_t index = 1; index < parabola_samples.size(); ++index) {
double diffX = parabola_samples[index - 1].x() -
parabola_samples[index].x();
double diffY = parabola_samples[index - 1].y() -
parabola_samples[index].y();
double length = sqrt(diffX * diffX + diffY * diffY);
sumLength += length;
}
return sumLength;
}
double ParabolaUtils::focal_length(const Parabola &parabola)
{
// https://en.wikipedia.org/wiki/Parabola
// p = 2f; y = 1/(4f) * x^2; y = 1/(2p) * x^2
double p = parabola.directrix.perp_distance_to(parabola.focus);
double f = p / 2.;
return f;
}
bool ParabolaUtils::is_over_zero(const ParabolaSegment &parabola)
{
Vec2i64 line_direction = (parabola.directrix.b - parabola.directrix.a).cast<int64_t>();
Vec2i64 focus_from = (parabola.focus - parabola.from).cast<int64_t>();
Vec2i64 focus_to = (parabola.focus - parabola.to).cast<int64_t>();;
bool is_positive_x1 = line_direction.dot(focus_from) > 0;
bool is_positive_x2 = line_direction.dot(focus_to) > 0;
return is_positive_x1 != is_positive_x2;
}
void ParabolaUtils::draw(SVG & svg,
const ParabolaSegment &parabola,
const char * color,
coord_t width,
double discretization_step)
{
using VD = Slic3r::Geometry::VoronoiDiagram;
if (PointUtils::is_equal(parabola.from, parabola.to)) return;
std::vector<VD::point_type> parabola_samples(
{VoronoiGraphUtils::to_point(parabola.from),
VoronoiGraphUtils::to_point(parabola.to)});
VD::point_type source_point = VoronoiGraphUtils::to_point(parabola.focus);
VD::segment_type source_segment = VoronoiGraphUtils::to_segment(parabola.directrix);
::boost::polygon::voronoi_visual_utils<double>::discretize(
source_point, source_segment, discretization_step, &parabola_samples);
for (size_t index = 1; index < parabola_samples.size(); ++index) {
const auto& s0 = parabola_samples[index - 1];
const auto& s1 = parabola_samples[index];
Line l(Point(s0.x(), s0.y()), Point(s1.x(), s1.y()));
svg.draw(l, color, width);
}
}
// PRIVATE
double ParabolaUtils::parabola_arc_length(double x)
{
double sqrtRes = sqrt(1 + 4 * x * x);
return 1 / 4. * log(2 * x + sqrtRes) + 1 / 2. * x * sqrtRes;
};

View File

@ -0,0 +1,73 @@
#ifndef slic3r_SLA_SuppotstIslands_ParabolaUtils_hpp_
#define slic3r_SLA_SuppotstIslands_ParabolaUtils_hpp_
#include "Parabola.hpp"
#include <libslic3r/SVG.hpp>
namespace Slic3r::sla {
/// <summary>
/// Class which contain collection of static function
/// for work with Parabola.
/// </summary>
class ParabolaUtils
{
public:
ParabolaUtils() = delete;
/// <summary>
/// Integrate length over interval defined by points from and to
/// </summary>
/// <param name="parabola">Input segment of parabola</param>
/// <returns>Length of parabola arc</returns>
static double length(const ParabolaSegment &parabola);
/// <summary>
/// Sample parabola between points from and to by step.
/// </summary>
/// <param name="parabola">Input segment of parabola</param>
/// <param name="discretization_step">Define sampling</param>
/// <returns>Length of parabola arc</returns>
static double length_by_sampling(const ParabolaSegment &parabola,
double discretization_step = 200);
/// <summary>
/// calculate focal length of parabola
/// </summary>
/// <param name="parabola">input parabola</param>
/// <returns>Focal length</returns>
static double focal_length(const Parabola &parabola);
/// <summary>
/// Check if parabola interval (from, to) contains top of parabola
/// </summary>
/// <param name="parabola">Input segment of parabola</param>
/// <returns>True when interval contain top of parabola otherwise False</returns>
static bool is_over_zero(const ParabolaSegment &parabola);
/// <summary>
/// Connvert parabola to svg by sampling
/// </summary>
/// <param name="svg">outputfile</param>
/// <param name="parabola">parabola to draw</param>
/// <param name="color">color</param>
/// <param name="width">width</param>
/// <param name="discretization_step">step between discretized lines</param>
static void draw(SVG & svg,
const ParabolaSegment &parabola,
const char * color,
coord_t width,
double discretization_step = 1e3);
private:
/// <summary>
/// Integral of parabola: y = x^2 from zero to point x
/// https://ocw.mit.edu/courses/mathematics/18-01sc-single-variable-calculus-fall-2010/unit-4-techniques-of-integration/part-b-partial-fractions-integration-by-parts-arc-length-and-surface-area/session-78-computing-the-length-of-a-curve/MIT18_01SCF10_Ses78d.pdf
/// </summary>
/// <param name="x">x coordinate of parabola, Positive number</param>
/// <returns>Length of parabola from zero to x</returns>
static double parabola_arc_length(double x);
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_ParabolaUtils_hpp_

View File

@ -0,0 +1,40 @@
#include "PointUtils.hpp"
using namespace Slic3r::sla;
bool PointUtils::is_equal(const Point &p1, const Point &p2) {
return p1.x() == p2.x() && p1.y() == p2.y();
}
bool PointUtils::is_majorit_x(const Point &point)
{
return abs(point.x()) > abs(point.y());
}
bool PointUtils::is_majorit_x(const Vec2d &point)
{
return fabs(point.x()) > fabs(point.y());
}
Slic3r::Point PointUtils::perp(const Point &vector)
{
return Point(-vector.y(), vector.x());
}
bool PointUtils::is_same_direction(const Point &dir1, const Point &dir2)
{
// (is_majorit_x(dir1)) ? (dir1.x() > 0) == (dir2.x() > 0) :
// (dir1.y() > 0) == (dir2.y() > 0);
// Cant use majorit direction:
// [2] 750000 544907
// [2] 463525 -1426583
// !! bad idea
// Cant use dot product for int value ==> dir1.dot(dir2)
// diferent int result for input
//[2] - 128707028 93448506
//[2] 10475487 1662574
// may be overflow ??
return dir1.cast<float>().dot(dir2.cast<float>()) > 0;
}

View File

@ -0,0 +1,53 @@
#ifndef slic3r_SLA_SuppotstIslands_PointUtils_hpp_
#define slic3r_SLA_SuppotstIslands_PointUtils_hpp_
#include <libslic3r/Point.hpp>
namespace Slic3r::sla {
/// <summary>
/// Class which contain collection of static function
/// for work with Point and Points.
/// QUESTION: Is it only for SLA?
/// </summary>
class PointUtils
{
public:
PointUtils() = delete;
/// <summary>
/// Is equal p1 == p2
/// </summary>
/// <param name="p1">first point</param>
/// <param name="p2">second point</param>
/// <returns>True when equal otherwise FALSE</returns>
static bool is_equal(const Point &p1, const Point &p2);
/// <summary>
/// check if absolut value of x is grater than absolut value of y
/// </summary>
/// <param name="point">input direction</param>
/// <returns>True when mayorit vaule is X other wise FALSE(mayorit value is y)</returns>
static bool is_majorit_x(const Point &point);
static bool is_majorit_x(const Vec2d &point);
/// <summary>
/// Create perpendicular vector[-y,x]
/// </summary>
/// <param name="vector">input vector from zero to point coordinate</param>
/// <returns>Perpendicular[-vector.y, vector.x]</returns>
static Point perp(const Point &vector);
/// <summary>
/// Check if both direction are on same half of the circle
/// alpha = atan2 of direction1
/// beta = atan2 of direction2
/// beta is in range from(alpha - Pi/2) to (alpha + Pi/2)
/// </summary>
/// <param name="dir1">first direction</param>
/// <param name="dir2">second direction</param>
/// <returns>True when on same half otherwise false</returns>
static bool is_same_direction(const Point &dir1, const Point &dir2);
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_PointUtils_hpp_

View File

@ -0,0 +1,87 @@
#include "PolygonUtils.hpp"
#include "libslic3r/Geometry.hpp"
using namespace Slic3r::sla;
inline bool is_in_coord_limits(const double& value) {
return (value < std::numeric_limits<coord_t>::max()) &&
(value > std::numeric_limits<coord_t>::min());
}
Slic3r::Polygon PolygonUtils::create_regular(size_t count_points,
double radius,
const Point &center)
{
assert(radius >= 1.);
assert(count_points >= 3);
Points points;
points.reserve(count_points);
double increase_angle = 2 * M_PI / count_points;
for (size_t i = 0; i < count_points; ++i) {
double angle = i * increase_angle;
double x = cos(angle) * radius + center.x();
assert(is_in_coord_limits(x));
double y = sin(angle) * radius + center.y();
assert(is_in_coord_limits(y));
points.emplace_back(x, y);
}
return Polygon(points);
}
Slic3r::Polygon PolygonUtils::create_equilateral_triangle(double edge_size)
{
coord_t x = edge_size / 2;
coord_t y = sqrt(edge_size * edge_size - edge_size * edge_size / 4) / 2;
return {{-x, -y}, {x, -y}, {0, y}};
}
Slic3r::Polygon PolygonUtils::create_isosceles_triangle(double side, double height)
{
return {{-side / 2, 0.}, {side / 2, 0.}, {.0, height}};
}
Slic3r::Polygon PolygonUtils::create_square(double size)
{
double size_2 = size / 2;
return {{-size_2, size_2},
{-size_2, -size_2},
{size_2, -size_2},
{size_2, size_2}};
}
Slic3r::Polygon PolygonUtils::create_rect(double width, double height)
{
double x_2 = width / 2;
double y_2 = height / 2;
return {{-x_2, y_2}, {-x_2, -y_2}, {x_2, -y_2}, {x_2, y_2}};
}
bool PolygonUtils::is_ccw(const Polygon &polygon, const Point &center) {
const Point *prev = &polygon.points.back();
for (const Point &point : polygon.points) {
Geometry::Orientation o = Geometry::orient(center, *prev, point);
if (o != Geometry::Orientation::ORIENTATION_CCW) return false;
prev = &point;
}
return true;
}
bool PolygonUtils::is_not_self_intersect(const Polygon &polygon,
const Point & center)
{
auto get_angle = [&center](const Point &point) {
Point diff_point = point - center;
return atan2(diff_point.y(), diff_point.x());
};
bool found_circle_end = false; // only one can be on polygon
double prev_angle = get_angle(polygon.points.back());
for (const Point &point : polygon.points) {
double angle = get_angle(point);
if (angle < prev_angle) {
if (found_circle_end) return false;
found_circle_end = true;
}
prev_angle = angle;
}
return true;
}

View File

@ -0,0 +1,84 @@
#ifndef slic3r_SLA_SuppotstIslands_PolygonUtils_hpp_
#define slic3r_SLA_SuppotstIslands_PolygonUtils_hpp_
#include <libslic3r/Polygon.hpp>
namespace Slic3r::sla {
/// <summary>
/// Class which contain collection of static function
/// for work with Polygon.
/// </summary>
class PolygonUtils
{
public:
PolygonUtils() = delete;
/// <summary>
/// Create regular polygon with N points
/// </summary>
/// <param name="count_points">Count points of regular polygon</param>
/// <param name="radius">Radius around center</param>
/// <param name="center">Center point</param>
/// <returns>Regular Polygon with CCW points</returns>
static Polygon create_regular(size_t count_points, double radius = 10., const Point& center = Point(0,0));
/// <summary>
/// Create circle with N points
/// alias for create regular
/// </summary>
/// <param name="radius">Radius of circle</param>
/// <param name="count_points">Count points of circle</param>
/// <param name="center">Center point</param>
/// <returns>Regular Polygon with CCW points</returns>
static Polygon create_circle(double radius, size_t count_points = 10, const Point& center = Point(0,0)){
return create_regular(count_points, radius, center);
}
/// <summary>
/// Create triangle with same length for all sides
/// </summary>
/// <param name="edge_size">triangel edge size</param>
/// <returns>Equilateral triangle</returns>
static Polygon create_equilateral_triangle(double edge_size);
/// <summary>
/// Create triangle with two side with same size
/// </summary>
/// <param name="side">Size of unique side</param>
/// <param name="height">triangle height</param>
/// <returns>Isosceles Triangle </returns>
static Polygon create_isosceles_triangle(double side, double height);
/// <summary>
/// Create squar with center in [0,0]
/// </summary>
/// <param name="size"></param>
/// <returns>Square</returns>
static Polygon create_square(double size);
/// <summary>
/// Create rect with center in [0,0]
/// </summary>
/// <param name="width">width</param>
/// <param name="height">height</param>
/// <returns>Rectangle</returns>
static Polygon create_rect(double width, double height);
/// <summary>
/// check if all pairs on polygon create with center ccw triangle
/// </summary>
/// <param name="polygon">input polygon to check</param>
/// <param name="center">center point inside polygon</param>
/// <returns>True when all points in polygon are CCW with center</returns>
static bool is_ccw(const Polygon &polygon, const Point &center);
/// <summary>
/// ! Only for polygon around point, like Voronoi diagram cell
/// </summary>
/// <param name="polygon">Polygon to check</param>
/// <param name="center">Center inside polygon, points create circle around center</param>
/// <returns>True when valid without self intersection otherwise FALSE</returns>
static bool is_not_self_intersect(const Polygon &polygon, const Point &center);
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_PolygonUtils_hpp_

View File

@ -0,0 +1,40 @@
#include "PostProcessNeighbor.hpp"
#include "VoronoiGraphUtils.hpp"
using namespace Slic3r::sla;
void PostProcessNeighbor::process()
{
bool is_circle_neighbor = false;
if (neighbor_path.nodes.empty()) { // neighbor node is on circle
for (VoronoiGraph::Circle &circle : neighbor_path.circles) {
const auto &circle_item = std::find(circle.nodes.begin(),
circle.nodes.end(), data.node);
if (circle_item == circle.nodes.end())
continue; // node is NOT on circle
size_t next_circle_index = &circle -
&neighbor_path.circles.front();
size_t circle_index = data.result.circles.size() +
next_circle_index;
data.circle_indexes.push_back(circle_index);
// check if this node is end of circle
if (circle_item == circle.nodes.begin()) {
data.end_circle_indexes.push_back(circle_index);
// !! this FIX circle lenght because at detection of
// circle it will cost time to calculate it
circle.length -= data.act_path.length;
// skip twice checking of circle
data.skip_nodes.insert(circle.nodes.back());
}
is_circle_neighbor = true;
}
}
VoronoiGraphUtils::append_neighbor_branch(data.result, neighbor_path);
if (!is_circle_neighbor)
data.side_branches.push(std::move(neighbor_path));
}

View File

@ -0,0 +1,36 @@
#ifndef slic3r_SLA_SuppotstIslands_PostProcessNeighbor_hpp_
#define slic3r_SLA_SuppotstIslands_PostProcessNeighbor_hpp_
#include "IStackFunction.hpp"
#include "NodeDataWithResult.hpp"
#include "VoronoiGraph.hpp"
#include "NodeDataWithResult.hpp"
namespace Slic3r::sla {
/// <summary>
/// Decimate data from Ex path to path
/// Done after ONE neighbor is procceessed.
/// Check if node is on circle.
/// Remember ended circle
/// Merge side branches and circle information into result
/// </summary>
class PostProcessNeighbor : public IStackFunction
{
NodeDataWithResult &data;
public:
VoronoiGraph::ExPath neighbor_path; // data filled in EvaluateNeighbor
PostProcessNeighbor(NodeDataWithResult &data) : data(data) {}
virtual void process([[maybe_unused]] CallStack &call_stack)
{
process();
}
private:
void process();
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_PostProcessNeighbor_hpp_

View File

@ -0,0 +1,50 @@
#include "PostProcessNeighbors.hpp"
#include "VoronoiGraphUtils.hpp"
using namespace Slic3r::sla;
void PostProcessNeighbors::process()
{
// remember connected circle
if (circle_indexes.size() > 1) {
for (size_t circle_index : circle_indexes) {
for (size_t circle_index2 : circle_indexes) {
if (circle_index == circle_index2) continue;
result.connected_circle[circle_index].insert(circle_index2);
}
}
}
// detect end of circles in this node
if (!end_circle_indexes.empty() &&
end_circle_indexes.size() == circle_indexes.size()) {
size_t circle_index = circle_indexes.front(); // possible any of them
side_branches.push(
VoronoiGraphUtils::find_longest_path_on_circles(*node,
circle_index,
result));
circle_indexes.clear(); // resolved circles
}
// simple node on circle --> only input and output neighbor
if (side_branches.empty()) return;
// is node on unresolved circle?
if (!circle_indexes.empty()) {
// not search for longest path, it will eval on end of circle
result.side_branches[node] = side_branches;
return;
}
// create result longest path from longest side branch
VoronoiGraph::Path longest_path(std::move(side_branches.top()));
side_branches.pop();
if (!side_branches.empty()) {
result.side_branches[node] = side_branches;
}
longest_path.nodes.insert(longest_path.nodes.begin(), node);
result.nodes = std::move(longest_path.nodes);
result.length = distance_to_node + longest_path.length;
}

View File

@ -0,0 +1,47 @@
#ifndef slic3r_SLA_SuppotstIslands_PostProcessNeighbors_hpp_
#define slic3r_SLA_SuppotstIslands_PostProcessNeighbors_hpp_
#include "IStackFunction.hpp"
#include "VoronoiGraph.hpp"
#include "NodeDataWithResult.hpp"
namespace Slic3r::sla {
/// <summary>
/// call after all neighbors are processed
/// </summary>
class PostProcessNeighbors : public NodeDataWithResult, public IStackFunction
{
public:
PostProcessNeighbors(VoronoiGraph::ExPath & result,
const VoronoiGraph::Node *node,
double distance_to_node = 0.,
const VoronoiGraph::Path &prev_path =
VoronoiGraph::Path({}, 0.) // make copy
)
: NodeDataWithResult(
result, node, distance_to_node,
prev_path.extend(node, distance_to_node),
prepare_skip_nodes(prev_path)
)
{}
virtual void process([[maybe_unused]] CallStack &call_stack)
{
process();
}
private:
static std::set<const VoronoiGraph::Node *> prepare_skip_nodes(
const VoronoiGraph::Path &prev_path)
{
if (prev_path.nodes.empty()) return {};
const VoronoiGraph::Node *prev_node = prev_path.nodes.back();
return {prev_node};
}
void process();
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_PostProcessNeighbors_hpp_

View File

@ -0,0 +1,119 @@
#ifndef slic3r_SLA_SuppotstIslands_SampleConfig_hpp_
#define slic3r_SLA_SuppotstIslands_SampleConfig_hpp_
#include <libslic3r/libslic3r.h>
#define OPTION_TO_STORE_ISLAND
namespace Slic3r::sla {
/// <summary>
/// Configure how to prepare data for SupportPointGenerator
/// </summary>
struct PrepareSupportConfig
{
// Size of the steps between discretized samples on the overhanging part of layer
// Smaller value means more point to investigate in support process,
// but smaller divergence of support distances
double discretize_overhang_step = 2.; // [in mm]
// Detection of peninsula(half island)
// Peninsula contain wider one layer overhang than this value
float peninsula_min_width = scale_(2); // [in scaled mm]
// Distance from previous layer part to still supported
float peninsula_self_supported_width = scale_(1.5); // [in scaled mm]
// To be able support same 2d area multipletimes,
// It is neccessary to remove support point form near KDTree structure
// Must be greater than surface texture and lower than self supporting area
// May be use maximal island distance
float removing_delta = scale_(5.);
// Define minimal size of separable model part which will be filtered out
// Half of support head diameter is impossible to print other than sphere from support head
float minimal_bounding_sphere_radius = 0.2f; // [in mm]
};
/// <summary>
/// Configuration DTO
/// Define where is neccessary to put support point on island
/// Mainly created by SampleConfigFactory
/// </summary>
struct SampleConfig
{
// Maximal distance of support points on thin island's part
// MUST be bigger than zero
coord_t thin_max_distance = static_cast<coord_t>(scale_(5.));
// Maximal distance of support points inside of thick island's part
// MUST be bigger than zero
coord_t thick_inner_max_distance = static_cast<coord_t>(scale_(5.));
// Maximal distance of support points on outline of thick island's part
// Sample outline of Field by this value
// MUST be bigger than zero
coord_t thick_outline_max_distance = static_cast<coord_t>(scale_(5 * 3 / 4.));
// Support point head radius
// MUST be bigger than zero
coord_t head_radius = static_cast<coord_t>(scale_(.4)); // [nano meter]
// When it is possible, there will be this minimal distance from outline.
// zero when head should be on outline
coord_t minimal_distance_from_outline = 0; // [nano meter]
// Measured as sum of VD edge length from outline
// Used only when there is no space for outline offset on first/last point
// Must be bigger than minimal_distance_from_outline
coord_t maximal_distance_from_outline = static_cast<coord_t>(scale_(1.));// [nano meter]
// Maximal length of longest path in voronoi diagram to be island
// supported only by one single support point this point will be in center of path.
coord_t max_length_for_one_support_point = static_cast<coord_t>(scale_(1.));
// Maximal length of island supported by 2 points
coord_t max_length_for_two_support_points = static_cast<coord_t>(scale_(1.));
// Maximal ratio of path length for island supported by 2 points
// Used only in case when maximal_distance_from_outline is bigger than
// current island longest_path * this ratio
// Note: Preven for tiny island to contain overlapped support points
// must be smaller than 0.5 and bigger than zero
float max_length_ratio_for_two_support_points = 0.25; // |--25%--Sup----50%----Sup--25%--|
// Maximal width of line island supported in the middle of line
// Must be greater or equal to thick_min_width
coord_t thin_max_width = static_cast<coord_t>(scale_(1.));
// Minimal width to be supported by outline
// Must be smaller or equal to thin_max_width
coord_t thick_min_width = static_cast<coord_t>(scale_(1.));
// Minimal length of island's part to create tiny&thick interface
coord_t min_part_length = static_cast<coord_t>(scale_(1.));
// Term criteria for end of alignment
// Minimal change in manhatn move of support position before termination
coord_t minimal_move = static_cast<coord_t>(scale_(.01)); // devide from print resolution to quater pixel
// Maximal count of align iteration
size_t count_iteration = 100;
// Maximal distance over Voronoi diagram edges to find closest point during aligning Support point
coord_t max_align_distance = 0; // [scaled mm -> nanometers]
// There is no need to calculate with precisse island
// NOTE: Slice of Cylinder bottom has tip of trinagles on contour
// (neighbor coordinate - create issue in voronoi)
double simplification_tolerance = scale_(0.05 /*mm*/);
#ifdef OPTION_TO_STORE_ISLAND
// Only for debug purposes
std::string path = ""; // when set to empty string, no debug output is generated
#endif // OPTION_TO_STORE_ISLAND
// Configuration for data preparation
PrepareSupportConfig prepare_config;
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_SampleConfig_hpp_

View File

@ -0,0 +1,137 @@
#include "SampleConfigFactory.hpp"
using namespace Slic3r::sla;
bool SampleConfigFactory::verify(SampleConfig &cfg) {
auto verify_max = [](coord_t &c, coord_t max) {
assert(c <= max);
if (c > max) {
c = max;
return false;
}
return true;
};
auto verify_min = [](coord_t &c, coord_t min) {
assert(c >= min);
if (c < min) {
c = min;
return false;
}
return true;
};
auto verify_min_max = [](coord_t &min, coord_t &max) {
// min must be smaller than max
assert(min < max);
if (min > max) {
std::swap(min, max);
return false;
} else if (min == max) {
min /= 2; // cut in half
return false;
}
return true;
};
bool res = true;
res &= verify_min_max(cfg.max_length_for_one_support_point, cfg.max_length_for_two_support_points);
res &= verify_min_max(cfg.thick_min_width, cfg.thin_max_width); // check histeresis
res &= verify_max(cfg.max_length_for_one_support_point,
2 * cfg.thin_max_distance +
2 * cfg.head_radius +
2 * cfg.minimal_distance_from_outline);
res &= verify_min(cfg.max_length_for_one_support_point,
2 * cfg.head_radius + 2 * cfg.minimal_distance_from_outline);
res &= verify_max(cfg.max_length_for_two_support_points,
2 * cfg.thin_max_distance +
2 * 2 * cfg.head_radius +
2 * cfg.minimal_distance_from_outline);
res &= verify_min(cfg.thin_max_width,
2 * cfg.head_radius + 2 * cfg.minimal_distance_from_outline);
res &= verify_max(cfg.thin_max_width,
2 * cfg.thin_max_distance + 2 * cfg.head_radius);
if (!res) while (!verify(cfg));
return res;
}
SampleConfig SampleConfigFactory::create(float support_head_diameter_in_mm) {
SampleConfig result;
result.head_radius = static_cast<coord_t>(scale_(support_head_diameter_in_mm/2));
assert(result.minimal_distance_from_outline < result.maximal_distance_from_outline);
// https://cfl.prusa3d.com/display/SLA/Single+Supporty
// head 0.4mm cca 1.65 mm
// head 0.5mm cca 1.85 mm
// This values are used for solvig equation(to find 2.9 and 1.3)
double head_area = M_PI * sqr(support_head_diameter_in_mm / 2); // Pi r^2
result.max_length_for_one_support_point = static_cast<coord_t>(scale_(head_area * 2.9 + 1.3));
// https://cfl.prusa3d.com/display/SLA/Double+Supports+-+Rectangles
// head 0.4mm cca 6.5 mm
// Use linear dependency to max_length_for_one_support_point
result.max_length_for_two_support_points =
static_cast<coord_t>(result.max_length_for_one_support_point * 3.9);
// https://cfl.prusa3d.com/display/SLA/Double+Supports+-+Squares
// head 0.4mm cca (4.168 to 4.442) => from 3.6 to 4.2
result.thin_max_width = static_cast<coord_t>(result.max_length_for_one_support_point * 2.5);
result.thick_min_width = static_cast<coord_t>(result.max_length_for_one_support_point * 2.15);
// guessed from max_length_for_two_support_points to value 5.2mm
result.thin_max_distance = static_cast<coord_t>(result.max_length_for_two_support_points * 0.8);
// guess from experiments documented above __(not verified values)__
result.thick_inner_max_distance = result.max_length_for_two_support_points; // 6.5mm
result.thick_outline_max_distance = static_cast<coord_t>(result.max_length_for_two_support_points * 0.75); // 4.875mm
result.minimal_distance_from_outline = result.head_radius; // 0.2mm
result.maximal_distance_from_outline = result.thin_max_distance / 3; // 1.73mm
result.min_part_length = result.thin_max_distance; // 5.2mm
// Align support points
// TODO: propagate print resolution
result.minimal_move = scale_(0.1); // 0.1 mm is enough
// [in nanometers --> 0.01mm ], devide from print resolution to quater pixel is too strict
result.count_iteration = 30; // speed VS precission
result.max_align_distance = result.max_length_for_two_support_points / 2;
verify(result);
return result;
}
SampleConfig SampleConfigFactory::apply_density(const SampleConfig &current, float density) {
if (is_approx(density, 1.f))
return current;
if (density < .1f)
density = .1f; // minimal 10%
SampleConfig result = current; // copy
result.thin_max_distance = static_cast<coord_t>(current.thin_max_distance / density); // linear
result.thick_inner_max_distance = static_cast<coord_t>( // controll radius - quadratic
std::sqrt(sqr((double) current.thick_inner_max_distance) / density)
);
result.thick_outline_max_distance = static_cast<coord_t>(
current.thick_outline_max_distance / density
); // linear
// result.head_radius .. no change
// result.minimal_distance_from_outline .. no change
// result.maximal_distance_from_outline .. no change
// result.max_length_for_one_support_point .. no change
// result.max_length_for_two_support_points .. no change
verify(result);
return result;
}
#ifdef USE_ISLAND_GUI_FOR_SETTINGS
std::optional<SampleConfig> SampleConfigFactory::gui_sample_config_opt;
SampleConfig &SampleConfigFactory::get_sample_config() {
// init config
if (!gui_sample_config_opt.has_value())
// create default configuration
gui_sample_config_opt = sla::SampleConfigFactory::create(.4f);
return *gui_sample_config_opt;
}
SampleConfig SampleConfigFactory::get_sample_config(float density) {
return apply_density(get_sample_config(), density);
}
#endif // USE_ISLAND_GUI_FOR_SETTINGS

View File

@ -0,0 +1,43 @@
#ifndef slic3r_SLA_SuppotstIslands_SampleConfigFactory_hpp_
#define slic3r_SLA_SuppotstIslands_SampleConfigFactory_hpp_
#include <optional>
#include "SampleConfig.hpp"
#include "libslic3r/PrintConfig.hpp"
//#define USE_ISLAND_GUI_FOR_SETTINGS
namespace Slic3r::sla {
/// <summary>
/// Factory to create configuration
/// </summary>
class SampleConfigFactory
{
public:
SampleConfigFactory() = delete;
static bool verify(SampleConfig &cfg);
static SampleConfig create(float support_head_diameter_in_mm);
static SampleConfig apply_density(const SampleConfig& cfg, float density);
#ifdef USE_ISLAND_GUI_FOR_SETTINGS
private:
// TODO: REMOVE IT. Do not use in production
// Global variable to temporary set configuration from GUI into SLA print steps
static std::optional<SampleConfig> gui_sample_config_opt;
public:
static SampleConfig &get_sample_config();
/// <summary>
/// Create scaled copy of sample config
/// </summary>
/// <param name="density"> Scale for config values(minimal value is .1f)
/// 1.f .. no scale
/// .9f .. less support points (approx 90%)
/// 1.1f.. extend count of supports (approx to 110%) </param>
/// <returns>Scaled configuration</returns>
static SampleConfig get_sample_config(float density);
#endif // USE_ISLAND_GUI_FOR_SETTINGS
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_SampleConfigFactory_hpp_

View File

@ -0,0 +1,202 @@
#include "SupportIslandPoint.hpp"
#include "VoronoiGraphUtils.hpp"
#include "LineUtils.hpp"
using namespace Slic3r::sla;
SupportIslandPoint::SupportIslandPoint(Slic3r::Point point, Type type)
: point(std::move(point)), type(type)
{}
bool SupportIslandPoint::can_move(const Type &type)
{
static const std::set<Type> cant_move({
Type::one_bb_center_point,
Type::one_center_point,
Type::two_points,
});
return cant_move.find(type) == cant_move.end();
}
bool SupportIslandPoint::can_move() const { return can_move(type); }
coord_t SupportIslandPoint::move(const Point &destination)
{
Point diff = destination - point;
point = destination;
return abs(diff.x()) + abs(diff.y()); // Manhatn distance
}
std::string SupportIslandPoint::to_string(const Type &type)
{
static std::map<Type, std::string> type_to_string=
{{Type::one_center_point, "one_center_point"},
{Type::two_points, "two_points"},
{Type::two_points_backup, "two_points_backup"},
{Type::one_bb_center_point,"one_bb_center_point"},
{Type::thin_part, "thin_part"},
{Type::thin_part_change, "thin_part_change"},
{Type::thin_part_loop, "thin_part_loop"},
{Type::thick_part_outline, "thick_part_outline"},
{Type::thick_part_inner, "thick_part_inner"},
{Type::bad_shape_for_vd, "bad_shape_for_vd"},
{Type::permanent, "permanent"},
{Type::undefined, "undefined"}};
auto it = type_to_string.find(type);
if (it == type_to_string.end())
return to_string(Type::undefined);
return it->second;
}
///////////////
// Point on VD
///////////////
SupportCenterIslandPoint::SupportCenterIslandPoint(
VoronoiGraph::Position position,
const SampleConfig * configuration,
Type type)
: SupportIslandPoint(VoronoiGraphUtils::create_edge_point(position), type)
, configuration(configuration)
, position(position)
{}
coord_t SupportCenterIslandPoint::move(const Point &destination)
{
// move only along VD
// TODO: Start respect minimum distance from outline !!
position =
VoronoiGraphUtils::align(position, destination,
configuration->max_align_distance);
Point new_point = VoronoiGraphUtils::create_edge_point(position);
return SupportIslandPoint::move(new_point);
}
///////////////
// Point on Outline
///////////////
SupportOutlineIslandPoint::SupportOutlineIslandPoint(
Position position, std::shared_ptr<Restriction> restriction, Type type)
: SupportIslandPoint(calc_point(position, *restriction), type)
, position(position)
, restriction(std::move(restriction))
{}
bool SupportOutlineIslandPoint::can_move() const { return true; }
coord_t SupportOutlineIslandPoint::move(const Point &destination)
{
size_t index = position.index;
MoveResult closest = create_result(index, destination);
const double &length = restriction->lengths[position.index];
double distance = (1.0 - position.ratio) * length;
while (distance < restriction->max_align_distance) {
auto next_index = restriction->next_index(index);
if (!next_index.has_value()) break;
index = *next_index;
update_result(closest, index, destination);
distance += restriction->lengths[index];
}
index = position.index;
distance = static_cast<coord_t>(position.ratio) * length;
while (distance < restriction->max_align_distance) {
auto prev_index = restriction->prev_index(index);
if (!prev_index.has_value()) break;
index = *prev_index;
update_result(closest, index, destination);
distance += restriction->lengths[index];
}
// apply closest result of move
this->position = closest.position;
return SupportIslandPoint::move(closest.point);
}
Slic3r::Point SupportOutlineIslandPoint::calc_point(const Position &position, const Restriction &restriction)
{
const Line &line = restriction.lines[position.index];
Point direction = LineUtils::direction(line);
return line.a + direction * position.ratio;
}
SupportOutlineIslandPoint::MoveResult SupportOutlineIslandPoint::create_result(
size_t index, const Point &destination)
{
const Line &line = restriction->lines[index];
double line_ratio_full = LineUtils::foot(line, destination);
double line_ratio = std::clamp(line_ratio_full, 0., 1.);
Position position(index, line_ratio);
Point point = calc_point(position, *restriction);
double distance_double = (point - destination).cast<double>().norm();
coord_t distance = static_cast<coord_t>(distance_double);
return MoveResult(position, point, distance);
}
void SupportOutlineIslandPoint::update_result(MoveResult & result,
size_t index,
const Point &destination)
{
const Line &line = restriction->lines[index];
double line_ratio_full = LineUtils::foot(line, destination);
double line_ratio = std::clamp(line_ratio_full, 0., 1.);
Position position(index, line_ratio);
Point point = calc_point(position, *restriction);
Point diff = point - destination;
if (abs(diff.x()) > result.distance) return;
if (abs(diff.y()) > result.distance) return;
double distance_double = diff.cast<double>().norm();
coord_t distance = static_cast<coord_t>(distance_double);
if (result.distance > distance) {
result.distance = distance;
result.position = position;
result.point = point;
}
}
////////////////////
/// Inner Point
///////////////////////
SupportIslandInnerPoint::SupportIslandInnerPoint(
Point point, std::shared_ptr<ExPolygons> inner, Type type)
: SupportIslandPoint(point, type), inner(std::move(inner))
{}
coord_t SupportIslandInnerPoint::move(const Point &destination) {
// IMPROVE: Do not move over island hole if there is no connected island.
// Can cause bad supported area in very special case.
for (const ExPolygon& inner_expolygon: *inner)
if (inner_expolygon.contains(destination))
return SupportIslandPoint::move(destination);
// find closest line cross area border
Vec2d v1 = (destination-point).cast<double>();
double closest_ratio = 1.;
Lines lines = to_lines(*inner);
for (const Line &line : lines) {
// line intersection
const Vec2d v2 = LineUtils::direction(line).cast<double>();
double denom = cross2(v1, v2);
// is line parallel
if (fabs(denom) < std::numeric_limits<float>::epsilon()) continue;
const Vec2d v12 = (point - line.a).cast<double>();
double nume1 = cross2(v2, v12);
double t1 = nume1 / denom;
if (t1 < 0. || t1 > closest_ratio) continue; // out of line
double nume2 = cross2(v1, v12);
double t2 = nume2 / denom;
if (t2 < 0. || t2 > 1.0) continue; // out of contour
closest_ratio = t1;
}
// no correct closest point --> almost parallel cross
if (closest_ratio >= 1.) return 0;
Point new_point = point + (closest_ratio * v1).cast<coord_t>();
return SupportIslandPoint::move(new_point);
}

View File

@ -0,0 +1,311 @@
#ifndef slic3r_SLA_SuppotstIslands_SupportIslandPoint_hpp_
#define slic3r_SLA_SuppotstIslands_SupportIslandPoint_hpp_
#include <set>
#include <memory>
#include <libslic3r/Point.hpp>
#include "VoronoiGraph.hpp"
#include "SampleConfig.hpp"
namespace Slic3r::sla {
/// <summary>
/// DTO position with information about source of support point
/// </summary>
class SupportIslandPoint
{
public:
enum class Type: unsigned char {
one_bb_center_point, // for island smaller than head radius
one_center_point, // small enough to support only by one support point
two_points, // island stretched between two points
two_points_backup, // same as before but forced after divide to thin&thick with small amoutn of points
thin_part, // point on thin part of island lay on VD
thin_part_change, // on the first edge -> together with change to thick part of island
thin_part_loop, // on the last edge -> loop into itself part of island
thick_part_outline, // keep position align with island outline
thick_part_inner, // point inside wide part, without restriction on move
bad_shape_for_vd, // can't make a Voronoi diagram on the shape
permanent, // permanent support point with static position
undefined
};
Type type;
Point point;
public:
/// <summary>
/// constructor
/// </summary>
/// <param name="point">coordinate point inside a layer (in one slice)</param>
/// <param name="type">type of support point</param>
SupportIslandPoint(Point point, Type type = Type::undefined);
/// <summary>
/// virtual destructor to be inheritable
/// </summary>
virtual ~SupportIslandPoint() = default;
/// <summary>
/// static function to decide if type is possible to move or not
/// </summary>
/// <param name="type">type to distinguish</param>
/// <returns>True when is possible to move, otherwise FALSE</returns>
static bool can_move(const Type &type);
/// <summary>
/// static function to decide if type is possible to move or not
/// </summary>
/// <param name="type">type to distinguish</param>
/// <returns>True when is possible to move, otherwise FALSE</returns>
virtual bool can_move() const;
/// <summary>
/// Move position of support point close to destination
/// with support point restrictions
/// </summary>
/// <param name="destination">Wanted position</param>
/// <returns>Move distance</returns>
virtual coord_t move(const Point &destination);
/// <summary>
/// Convert type to string value
/// </summary>
/// <param name="type">Input type</param>
/// <returns>String type</returns>
static std::string to_string(const Type &type);
};
using SupportIslandPointPtr = std::unique_ptr<SupportIslandPoint>;
using SupportIslandPoints = std::vector<SupportIslandPointPtr>;
/// <summary>
/// Support point with no move during aligning
/// </summary>
class SupportIslandNoMovePoint : public SupportIslandPoint
{
public:
//constructor
using SupportIslandPoint::SupportIslandPoint;
/// <summary>
/// Can move?
/// </summary>
/// <returns>FALSE</returns>
bool can_move() const override { return false; }
/// <summary>
/// No move!
/// Should not be call.
/// </summary>
/// <param name="destination">Wanted position</param>
/// <returns>No move means zero distance</returns>
coord_t move(const Point &destination) override { return 0; }
};
/// <summary>
/// DTO Support point laying on voronoi graph edge
/// Restriction to move only on Voronoi graph
/// </summary>
class SupportCenterIslandPoint : public SupportIslandPoint
{
public:
// Define position on voronoi graph
// FYI: Lose data when voronoi graph does NOT exist
VoronoiGraph::Position position;
// hold pointer to configuration
// FYI: Lose data when configuration destruct
const SampleConfig *configuration;
public:
SupportCenterIslandPoint(VoronoiGraph::Position position,
const SampleConfig *configuration,
Type type = Type::thin_part);
bool can_move() const override{ return true; }
coord_t move(const Point &destination) override;
};
/// <summary>
/// Support point laying on Outline of island
/// Restriction to move only on outline
/// </summary>
class SupportOutlineIslandPoint : public SupportIslandPoint
{
public:
// definition of restriction
class Restriction;
struct Position
{
// index of line form island outline - index into Restriction
// adress line inside inner polygon --> SupportOutline
size_t index;
// define position on line by ratio
// from 0 (line point a)
// to 1 (line point b)
float ratio;
Position(size_t index, float ratio) : index(index), ratio(ratio) {}
};
Position position;
// store lines for allowed move - with distance from island source lines
std::shared_ptr<Restriction> restriction;
public:
SupportOutlineIslandPoint(Position position,
std::shared_ptr<Restriction> restriction,
Type type = Type::thick_part_outline);
// return true
bool can_move() const override;
/// <summary>
/// Move nearest to destination point
/// only along restriction lines
/// + change current position
/// </summary>
/// <param name="destination">Wanted support position</param>
/// <returns>move distance manhatn</returns>
coord_t move(const Point &destination) override;
/// <summary>
/// Calculate 2d point belong to line position
/// </summary>
/// <param name="position">Define position on line from restriction</param>
/// <param name="restriction">Hold lines</param>
/// <returns>Position in 2d</returns>
static Point calc_point(const Position & position,
const Restriction &restriction);
/// <summary>
/// Keep data for align support point on bordred of island
/// Define possible move of point along outline
/// IMPROVE: Should contain list of Points on outline.
/// (to keep maximal distance of neighbor points on outline)
/// </summary>
class Restriction
{
public:
// line restriction
// must be connected line.a == prev_line.b && line.b == next_line.a
Lines lines;
// keep stored line lengths
// same size as lines
std::vector<double> lengths;
// maximal distance for search nearest line to destination point during aligning
coord_t max_align_distance;
Restriction(Lines lines,
std::vector<double> lengths,
coord_t max_align_distance)
: lines(lines)
, lengths(lengths)
, max_align_distance(max_align_distance)
{
assert(lines.size() == lengths.size());
}
virtual ~Restriction() = default;
virtual std::optional<size_t> next_index(size_t index) const = 0;
virtual std::optional<size_t> prev_index(size_t index) const = 0;
};
class RestrictionLineSequence: public Restriction
{
public:
// inherit constructors
using Restriction::Restriction;
virtual std::optional<size_t> next_index(size_t index) const override
{
assert(index < lines.size());
++index;
if (index >= lines.size()) return {}; // index out of range
return index;
}
virtual std::optional<size_t> prev_index(size_t index) const override
{
assert(index < lines.size());
if (index >= lines.size()) return {}; // index out of range
if (index == 0) return {}; // no prev line
return index - 1;
}
};
class RestrictionCircleSequence : public Restriction
{
public:
// inherit constructors
using Restriction::Restriction;
virtual std::optional<size_t> next_index(size_t index) const override
{
assert(index < lines.size());
if (index >= lines.size()) return {}; // index out of range
++index;
if (index == lines.size()) return 0;
return index;
}
virtual std::optional<size_t> prev_index(size_t index) const override
{
assert(index < lines.size());
if (index >= lines.size()) return {}; // index out of range
if (index == 0) return lines.size() - 1;
return index - 1;
}
};
private:
// DTO for result of move
struct MoveResult
{
// define position on restriction line
Position position;
// point laying on restricted line
Point point;
// distance point on restricted line from destination point
coord_t distance;
MoveResult(Position position, Point point, coord_t distance)
: position(position), point(point), distance(distance)
{}
};
MoveResult create_result(size_t index, const Point &destination);
void update_result(MoveResult& result, size_t index, const Point &destination);
};
/// <summary>
/// Store pointer to inner ExPolygon for allowed move across island area
/// Give an option to move with point
/// </summary>
class SupportIslandInnerPoint: public SupportIslandPoint
{
// define inner area of island where inner point could move during aligning
std::shared_ptr<ExPolygons> inner;
public:
SupportIslandInnerPoint(Point point,
std::shared_ptr<ExPolygons> inner,
Type type = Type::thick_part_inner);
bool can_move() const override { return true; };
/// <summary>
/// Move nearest to destination point
/// only inside inner area
/// + change current position
/// </summary>
/// <param name="destination">Wanted support position</param>
/// <returns>move distance euclidean</returns>
coord_t move(const Point &destination) override;
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_SupportIslandPoint_hpp_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
#ifndef slic3r_SLA_SuppotstIslands_UniformSupportIsland_hpp_
#define slic3r_SLA_SuppotstIslands_UniformSupportIsland_hpp_
#include <libslic3r/ExPolygon.hpp>
#include "SampleConfig.hpp"
#include "SupportIslandPoint.hpp"
#include "libslic3r/SLA/SupportPointGenerator.hpp" // Peninsula
namespace Slic3r::sla {
/// <summary>
/// Distribute support points across island area defined by ExPolygon.
/// </summary>
/// <param name="island">Shape of island</param>
/// <param name="permanent">Place supported by already existing supports</param>
/// <param name="config">Configuration of support density</param>
/// <returns>Support points laying inside of the island</returns>
SupportIslandPoints uniform_support_island(
const ExPolygon &island, const Points &permanent, const SampleConfig &config);
/// <summary>
/// Distribute support points across peninsula
/// </summary>
/// <param name="peninsula">half island with anotation of the coast and land outline</param>
/// <param name="permanent">Place supported by already existing supports</param>
/// <param name="config">Density distribution parameters</param>
/// <returns>Support points laying inside of the peninsula</returns>
SupportIslandPoints uniform_support_peninsula(
const Peninsula &peninsula, const Points& permanent, const SampleConfig &config);
/// <summary>
/// Check for tests that developer do not forget disable visualization after debuging.
/// </summary>
bool is_uniform_support_island_visualization_disabled();
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_UniformSupportIsland_hpp_

View File

@ -0,0 +1,152 @@
#ifndef slic3r_SLA_SuppotstIslands_VectorUtils_hpp_
#define slic3r_SLA_SuppotstIslands_VectorUtils_hpp_
#include <vector>
#include <algorithm>
#include <numeric> // iota
#include <functional>
namespace Slic3r::sla {
/// <summary>
/// Class which contain collection of static function
/// for work with std vector
/// QUESTION: Is it only for SLA?
/// </summary>
class VectorUtils
{
public:
VectorUtils() = delete;
/// <summary>
/// For sorting a vector by calculated value
/// CACHE for calculated values
/// </summary>
/// <param name="data">vetor to be sorted</param>
/// <param name="calc">function to calculate sorting value</param>
template<typename T1, typename T2>
static void sort_by(std::vector<T1> &data, std::function<T2(const T1 &)> &calc)
{
assert(!data.empty());
if (data.size() <= 1) return;
// initialize original index locations
std::vector<size_t> idx(data.size());
std::iota(idx.begin(), idx.end(), 0);
// values used for sort
std::vector<T2> v;
v.reserve(data.size());
for (const T1 &d : data) v.emplace_back(calc(d));
// sort indexes based on comparing values in v
// using std::stable_sort instead of std::sort
// to avoid unnecessary index re-orderings
// when v contains elements of equal values
std::stable_sort(idx.begin(), idx.end(), [&v](size_t i1, size_t i2) {
return v[i1] < v[i2];
});
reorder_destructive(idx.begin(), idx.end(), data.begin());
}
/// <summary>
/// shortcut to use std::transform with alocation for result
/// </summary>
/// <param name="data">vetor to be transformed</param>
/// <param name="transform_func">lambda to transform data types</param>
/// <returns>result vector</returns>
template<typename T1, typename T2>
static std::vector<T2> transform(const std::vector<T1> &data, std::function<T2(const T1 &)> &transform_func)
{
std::vector<T2> result;
result.reserve(data.size());
std::transform(data.begin(), data.end(), std::back_inserter(result), transform_func);
return result;
}
/// <summary>
/// Reorder vector by indexes given by iterators
/// </summary>
/// <param name="order_begin">Start index</param>
/// <param name="order_end">Last index</param>
/// <param name="v">data to reorder. e.g. vector::begin()</param>
template<typename order_iterator, typename value_iterator>
static void reorder(order_iterator order_begin,
order_iterator order_end,
value_iterator v)
{
typedef typename std::iterator_traits<value_iterator>::value_type value_t;
typedef typename std::iterator_traits<order_iterator>::value_type index_t;
typedef typename std::iterator_traits<order_iterator>::difference_type diff_t;
diff_t remaining = order_end - 1 - order_begin;
for (index_t s = index_t(), d; remaining > 0; ++s) {
for (d = order_begin[s]; d > s; d = order_begin[d])
;
if (d == s) {
--remaining;
value_t temp = v[s];
while (d = order_begin[d], d != s) {
std::swap(temp, v[d]);
--remaining;
}
v[s] = temp;
}
}
}
/// <summary>
/// Same as function 'reorder' but destroy order vector for speed
/// </summary>
/// <param name="order_begin">Start index</param>
/// <param name="order_end">Last index</param>
/// <param name="v">data to reorder. e.g. vector::begin()</param>
template<typename order_iterator, typename value_iterator>
static void reorder_destructive(order_iterator order_begin,
order_iterator order_end,
value_iterator v)
{
typedef typename std::iterator_traits<value_iterator>::value_type value_t;
typedef typename std::iterator_traits<order_iterator>::value_type index_t;
typedef typename std::iterator_traits<order_iterator>::difference_type diff_t;
static const index_t done_index = static_cast<index_t>(-1);
diff_t remaining = order_end - 1 - order_begin;
// s = start sequence index
for (index_t s = index_t(); remaining > 0; ++s) {
// d = index1 for swap
index_t d = order_begin[s];
if (d == done_index) continue;
--remaining;
if (s == d) continue; // correct order
value_t temp = v[s];
do {
std::swap(temp, v[d]);
index_t d2 = done_index;
std::swap(order_begin[d], d2);
d = d2;
--remaining;
} while (d != s);
v[s] = temp;
}
}
/// <summary>
/// Insert item into sorted vector of items
/// </summary>
/// <param name="data">Sorted vector with items</param>
/// <param name="item">Item to insert</param>
/// <param name="pred">Predicate for sorting</param>
/// <returns>now inserted item</returns>
template<typename T, typename Pred>
static typename std::vector<T>::iterator insert_sorted(
std::vector<T> &data, const T &item, Pred pred)
{
auto iterator = std::upper_bound(data.begin(), data.end(), item, pred);
return data.insert(iterator, item);
}
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_VectorUtils_hpp_

View File

@ -0,0 +1,294 @@
#include "VoronoiDiagramCGAL.hpp"
#include <iostream>
// includes for defining the Voronoi diagram adaptor
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Voronoi_diagram_2.h>
#include <CGAL/Delaunay_triangulation_adaptation_traits_2.h>
#include <CGAL/Delaunay_triangulation_adaptation_policies_2.h>
#include "libslic3r/Geometry.hpp"
#include "libslic3r/Line.hpp"
#include "libslic3r/SLA/SupportIslands/LineUtils.hpp"
#include "libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp"
using namespace Slic3r;
using namespace Slic3r::sla;
// typedefs for defining the adaptor
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using DT = CGAL::Delaunay_triangulation_2<K>;
using AT = CGAL::Delaunay_triangulation_adaptation_traits_2<DT>;
using AP = CGAL::Delaunay_triangulation_caching_degeneracy_removal_policy_2<DT>;
using VD = CGAL::Voronoi_diagram_2<DT, AT, AP>;
// typedef for the result type of the point location
using Site_2 = AT::Site_2;
using Point_2 = AT::Point_2;
using Locate_result = VD::Locate_result;
using Vertex_handle = VD::Vertex_handle;
using Face_handle = VD::Face_handle;
using Halfedge_handle = VD::Halfedge_handle;
using Ccb_halfedge_circulator = VD::Ccb_halfedge_circulator;
namespace{
// conversion from double to coor_t
Slic3r::Point to_point(const Site_2 &s) { return Slic3r::Point(s.x(), s.y()); }
/// <summary>
/// Create line segment lay between given points with distance limited by maximal_distance
/// </summary>
/// <param name="point1"></param>
/// <param name="point2"></param>
/// <param name="maximal_distance">limits for result segment</param>
/// <returns>line perpendicular to line between point1 and point2</returns>
Slic3r::Line create_line_between_points(
const Point &point1, const Point &point2, double maximal_distance
) {
Point middle = point1/2 + point2/2;
Point diff = point1 - point2; // direction from point2 to point1
coord_t manhatn_distance = abs(diff.x()) + abs(diff.y());
// alligned points should not be too close
assert(manhatn_distance >= 1);
// it is not neccesary to know exact distance
// One need to know minimal distance between points.
// worst case is diagonal: sqrt(2*(0.5 * manhatn_distance)^2) =
double min_distance = manhatn_distance * .7; // 1 / sqrt(2)
double scale = maximal_distance / min_distance;
Point side_dir(-diff.y() * scale, diff.x() * scale);
return Line(middle - side_dir, middle + side_dir);
}
/// <summary>
/// Crop line which is no too far away(compare to maximal_distance) from v1(or v2)
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="v1"></param>
/// <param name="v2"></param>
/// <param name="maximal_distance"></param>
/// <returns></returns>
std::optional<Slic3r::Line> crop_line(
const Point_2 &a, const Point_2 &b,
const Point &v1, const Point &v2, double maximal_distance
) {
Point diff = v1 - v2;
Point dir(-diff.y(), diff.x());
coord_t abs_x = abs(dir.x());
coord_t abs_y = abs(dir.y());
Point middle = v1 / 2 + v2 / 2;
double a_t, b_t; // [units is size of dir]
if (abs_x > abs_y) {
a_t = (a.x() - middle.x()) / (double) dir.x();
b_t = (b.x() - middle.x()) / (double) dir.x();
} else {
a_t = (a.y() - middle.y()) / (double) dir.y();
b_t = (b.y() - middle.y()) / (double) dir.y();
}
// calculate approx distance from middle point to detect too far from middle
coord_t manhatn_distance = abs_x + abs_y;
// alligned points should not be too close
assert(manhatn_distance >= 1);
double min_distance = manhatn_distance * .7;
double a_dist = a_t * min_distance;
double b_dist = b_t * min_distance;
double scale = maximal_distance / min_distance;
Line l(to_point(a), to_point(b));
if (a_dist > maximal_distance) {
if (b_dist > maximal_distance)
return {}; // out of range
l.a = middle + (scale * dir.cast<double>()).cast<coord_t>();
} else if (a_dist < -maximal_distance) {
if (b_dist < -maximal_distance)
return {}; // out of range
l.a = middle - (scale * dir.cast<double>()).cast<coord_t>();
}
if (b_dist > maximal_distance) {
l.b = middle + (scale * dir.cast<double>()).cast<coord_t>();
} else if (b_dist < -maximal_distance)
l.b = middle - (scale * dir.cast<double>()).cast<coord_t>();
return l;
}
/// <summary>
/// Crop ray to line which is no too far away(compare to maximal_distance) from v1(or v2)
/// </summary>
/// <param name="p">ray start</param>
/// <param name="v1"></param>
/// <param name="v2"></param>
/// <param name="maximal_distance">Limits for line</param>
/// <returns></returns>
std::optional<Slic3r::Line> crop_ray(const Point_2 &ray_point, const Point &v1, const Point &v2, double maximal_distance) {
assert(maximal_distance > 0);
Point diff = v2 - v1;
Point ray_dir(-diff.y(), diff.x());
// bounds are similar as for line between points
Point middle = v1/2 + v2/2;
coord_t abs_x = abs(ray_dir.x());
coord_t abs_y = abs(ray_dir.y());
coord_t manhatn_dist = abs_x + abs_y; // maximal distance
// alligned points should not be too close
assert(manhatn_dist >= 1);
double min_distance = manhatn_dist * .7;
assert(min_distance > 0);
// count of dir from ray point to middle
double middle_t = (abs_x > abs_y) ?
// use x coor
(middle.x() - ray_point.x()) / (double) ray_dir.x() :
// use y coor
(middle.y() - ray_point.y()) / (double) ray_dir.y();
// minimal distance from ray point to middle point
double min_middle_dist = middle_t * min_distance;
if (min_middle_dist < -maximal_distance)
// ray start out of area of interest
return {};
double scale = maximal_distance / min_distance;
Point side_dir = (ray_dir.cast<double>() * scale).cast<coord_t>();
return Line(min_middle_dist > maximal_distance?
(middle - side_dir) : to_point(ray_point),
middle + side_dir);
}
std::optional<Slic3r::Line> to_line(
const Halfedge_handle &edge,
double maximal_distance
) {
// validation slow down a lot, Never appear during algorithm tunning
assert(edge->is_valid());
//if (!edge->is_valid()) return {};
if (edge->has_source()) {
// source point of edge
if (edge->has_target()) { // Line segment
assert(edge->is_segment());
return crop_line(
edge->source()->point(),
edge->target()->point(),
to_point(edge->up()->point()),
to_point(edge->down()->point()),
maximal_distance);
} else { // ray from source
assert(edge->is_ray());
return crop_ray(
edge->source()->point(),
to_point(edge->up()->point()),
to_point(edge->down()->point()),
maximal_distance);
}
} else if (edge->has_target()) { // ray from target
assert(edge->is_ray());
return crop_ray(
edge->target()->point(),
to_point(edge->down()->point()),
to_point(edge->up()->point()),
maximal_distance);
}
// infinite line between points
assert(edge->is_bisector());
return create_line_between_points(
to_point(edge->up()->point()),
to_point(edge->down()->point()),
maximal_distance
);
}
} // namespace
Polygons Slic3r::sla::create_voronoi_cells_cgal(const Points &points, coord_t max_distance) {
assert(points.size() > 1);
// Different way to fill points into VD
// delaunary triangulation
std::vector<DT::Point> dt_points;
dt_points.reserve(points.size());
for (const Point &p : points)
dt_points.emplace_back(p.x(), p.y());
DT dt(dt_points.begin(), dt_points.end());
assert(dt.is_valid());
VD vd(dt);
assert(vd.is_valid());
// voronoi diagram seems that points face order is same as inserted points
//VD vd;
//for (const Point& p: points) {
// Site_2 t(p.x(), p.y());
// vd.insert(t);
//}
Polygons cells(points.size());
size_t fit_index = 0;
// Loop over the faces of the Voronoi diagram in order of given points
for (VD::Face_iterator fit = vd.faces_begin(); fit != vd.faces_end(); ++fit, ++fit_index) {
// get source point index
// TODO: do it without search !!!
Point_2 source_point = fit->dual()->point();
Slic3r::Point source_pt(source_point.x(), source_point.y());
auto it = std::find(points.begin(), points.end(), source_pt);
assert(it != points.end());
if (it == points.end())
continue;
size_t index = it - points.begin();
assert(source_pt.x() == points[index].x());
assert(source_pt.y() == points[index].y());
// origin of voronoi face
const Point& origin = points[index];
Lines lines;
// collect croped lines of field
Ccb_halfedge_circulator ec_start = fit->ccb();
Ccb_halfedge_circulator ec = ec_start;
do {
assert(ec->is_valid());
std::optional<Slic3r::Line> line_opt = to_line(ec, max_distance);
if (!line_opt.has_value())
continue;
Line &line = *line_opt;
Geometry::Orientation orientation = Geometry::orient(origin, line.a, line.b);
// Can be rich on circle over source point edge
if (orientation == Geometry::Orientation::ORIENTATION_COLINEAR) continue;
if (orientation == Geometry::Orientation::ORIENTATION_CW) std::swap(line.a, line.b);
lines.push_back(std::move(line));
} while (++ec != ec_start);
assert(!lines.empty());
if (lines.size() > 1)
LineUtils::sort_CCW(lines, origin);
//Slic3r::SVG
// svg("C:/data/temp/CGAL_VD_face.svg",
// {origin - Point(max_distance, max_distance),
// origin + Point(max_distance, max_distance)});
//svg.draw(lines, "green");
//do {
// if (ec->has_source())
// svg.draw(to_point(ec->source()->point()));
// if (ec->has_target())
// svg.draw(to_point(ec->target()->point()));
// if (ec->is_segment())
// svg.draw(Line(to_point(ec->source()->point()), to_point(ec->target()->point())));
//} while (++ec != ec_start);
//svg.draw(origin, "red");
//svg.Close();
// preccission to decide when not connect neighbor points
double min_distance = max_distance / 1000.;
size_t count_point = 6; // count added points
// cell for current processed face
cells[index] =
VoronoiGraphUtils::to_polygon(lines, origin, max_distance, min_distance, count_point);
}
// Point_2 p;
// Locate_result lr = vd.locate(p); // Could locate face of VD - potentionaly could iterate input points
return cells;
}

View File

@ -0,0 +1,26 @@
///|/ Copyright (c) Prusa Research 2024 Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_SLA_SuppotstIslands_VoronoiDiagramCGAL_hpp_
#define slic3r_SLA_SuppotstIslands_VoronoiDiagramCGAL_hpp_
#include <libslic3r/Point.hpp>
#include <libslic3r/Polygon.hpp>
namespace Slic3r::sla {
/// <summary>
///
/// IMPROVE1: use accessor to point coordinate instead of points
/// IMPROVE2: add filter for create cell polygon only for moveable samples
/// </summary>
/// <param name="points">Input points for voronoi diagram</param>
/// <param name="max_distance">Limit for polygon made by point
/// NOTE: prerequisities input points are in max_distance only outer have infinite cell
/// which are croped to max_distance</param>
/// <returns>Polygon cell for input point</returns>
Polygons create_voronoi_cells_cgal(const Points &points, coord_t max_distance);
}
#endif // slic3r_SLA_SuppotstIslands_VoronoiDiagramCGAL_hpp_

View File

@ -0,0 +1,212 @@
#ifndef slic3r_SLA_SuppotstIslands_VoronoiGraph_hpp_
#define slic3r_SLA_SuppotstIslands_VoronoiGraph_hpp_
#include <map>
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Geometry/Voronoi.hpp>
#include <numeric>
namespace Slic3r::sla {
/// <summary>
/// DTO store skeleton With longest path
/// </summary>
struct VoronoiGraph
{
using VD = Slic3r::Geometry::VoronoiDiagram;
struct Node;
using Nodes = std::vector<const Node *>;
struct Path;
struct ExPath;
using Circle = Path;
struct Position;
std::map<const VD::vertex_type *, Node> data;
};
/// <summary>
/// Node data structure for Voronoi Graph.
/// Extend information about Voronoi vertex.
/// </summary>
struct VoronoiGraph::Node
{
// reference to Voronoi diagram VertexCategory::Inside OR
// VertexCategory::OnContour but NOT VertexCategory::Outside
const VD::vertex_type *vertex;
// longest distance to edge sum of line segment size (no euclid because of shape U)
double longestDistance;
// actual distance to edge
double distance;
struct Neighbor;
std::vector<Neighbor> neighbors;
// constructor
Node(const VD::vertex_type *vertex, double distance)
: vertex(vertex), longestDistance(0.), distance(distance), neighbors()
{}
};
/// <summary>
/// Surrond GraphNode data type.
/// Extend information about voronoi edge.
/// TODO IMPROVE: extends neighbors for store more edges
/// (cumulate Nodes with 2 neighbors - No cross)
/// </summary>
struct VoronoiGraph::Node::Neighbor
{
const VD::edge_type *edge;
// pointer on graph node structure
const Node *node;
/// <summary>
/// DTO represents size property of one Neighbor
/// </summary>
struct Size{
// length edge between vertices
double length;
// widht is distance between outlines
// maximal width
coord_t min_width;
// minimal widht
coord_t max_width;
Size(double length, coord_t min_width, coord_t max_width)
: length(length), min_width(min_width), max_width(max_width)
{}
};
std::shared_ptr<Size> size;
public:
Neighbor(const VD::edge_type * edge,
const Node * node,
std::shared_ptr<Size> size)
: edge(edge)
, node(node)
, size(std::move(size))
{}
// accessor to member
double length() const { return size->length; }
coord_t min_width() const { return size->min_width; }
coord_t max_width() const { return size->max_width; }
};
/// <summary>
/// DTO represents path over nodes of VoronoiGraph
/// store queue of Nodes
/// store length of path
/// </summary>
struct VoronoiGraph::Path
{
// row of neighbor Nodes
VoronoiGraph::Nodes nodes;
// length of path
// when circle contain length from back to front;
double length;
public:
Path() : nodes(), length(0.) {}
Path(const VoronoiGraph::Node *node) : nodes({node}), length(0.) {}
Path(VoronoiGraph::Nodes nodes, double length)
: nodes(std::move(nodes)), length(length)
{}
void append(const VoronoiGraph::Node *node, double length)
{
nodes.push_back(node);
this->length += length;
}
Path extend(const VoronoiGraph::Node *node, double length) const {
Path result(*this); // make copy
result.append(node, length);
return result;
}
struct OrderLengthFromShortest{
bool operator()(const VoronoiGraph::Path &path1,
const VoronoiGraph::Path &path2){
return path1.length > path2.length;
}
};
struct OrderLengthFromLongest{
bool operator()(const VoronoiGraph::Path &path1,
const VoronoiGraph::Path &path2){
return path1.length < path2.length;
}
};
};
/// <summary>
/// DTO
/// extends path with side branches and circles(connection of circles)
/// </summary>
struct VoronoiGraph::ExPath : public VoronoiGraph::Path
{
// not main path is stored in secondary paths
// key is pointer to source node
using SideBranches = std::priority_queue<VoronoiGraph::Path,
std::vector<VoronoiGraph::Path>,
OrderLengthFromLongest>;
using SideBranchesMap = std::map<const VoronoiGraph::Node *, SideBranches>;
// All side branches in Graph under node
// Map contains only node, which has side branche(s)
// There is NOT empty SideBranches in map !!!
SideBranchesMap side_branches;
// All circles in Graph under node
std::vector<VoronoiGraph::Circle> circles;
// alone circle does'n have record in connected_circle
// every connected circle have at least two records(firs to second &
// second to first) EXAMPLE with 3 circles(index to circles stored in
// this->circles are: c1, c2 and c3) connected together
// connected_circle[c1] = {c2, c3}; connected_circle[c2] = {c1, c3};
// connected_circle[c3] = {c1, c2};
using ConnectedCircles = std::map<size_t, std::set<size_t>>;
ConnectedCircles connected_circle;
public:
ExPath() = default;
};
/// <summary>
/// DTO
/// Extend neighbor with ratio to edge
/// For point position on VoronoiGraph use VoronoiGraphUtils::get_edge_point
/// </summary>
struct VoronoiGraph::Position
{
// neighbor is stored inside of voronoi diagram
const VoronoiGraph::Node::Neighbor* neighbor;
// define position on neighbor edge
// Value should be in range from 0. to 1. (shrinked when used)
// Value 0 means position of edge->vertex0
// Value 0.5 is on half edge way between edge->vertex0 and edge->vertex1
// Value 1 means position of edge->vertex1
double ratio;
Position(const VoronoiGraph::Node::Neighbor *neighbor, double ratio)
: neighbor(neighbor), ratio(ratio)
{}
Position(): neighbor(nullptr), ratio(0.) {}
coord_t calc_distance() const {
return static_cast<coord_t>(neighbor->length() * ratio);
}
coord_t calc_rest_distance() const
{
return static_cast<coord_t>(neighbor->length() * (1. - ratio));
}
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_VoronoiGraph_hpp_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,506 @@
#ifndef slic3r_SLA_SuppotstIslands_VoronoiGraphUtils_hpp_
#define slic3r_SLA_SuppotstIslands_VoronoiGraphUtils_hpp_
#include <optional>
#include <map>
#include <set>
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/SVG.hpp>
#include "VoronoiGraph.hpp"
#include "Parabola.hpp"
#include "SampleConfig.hpp"
#include "SupportIslandPoint.hpp"
namespace Slic3r::sla {
/// <summary>
/// Class which contain collection of static function
/// for work with Voronoi Graph.
/// </summary>
class VoronoiGraphUtils
{
using VD = Slic3r::Geometry::VoronoiDiagram;
public:
VoronoiGraphUtils() = delete;
/// <summary>
/// Convert coordinate type between voronoi and slicer format
/// </summary>
/// <param name="coor">Coordinate</param>
/// <returns>When it is possible than cast it otherwise empty optional</returns>
static coord_t to_coord(const VD::coordinate_type &coord);
/// <summary>
/// Convert Vodonoi diagram vertex type to Slicer Point
/// decrease precission by rounding
/// </summary>
/// <param name="vertex">Input point pointer(double precission)</param>
/// <returns>Converted point(int preccission)</returns>
static Point to_point(const VD::vertex_type *vertex);
/// <summary>
/// Convert Slic3r point to Vodonoi point type
/// extend precission
/// </summary>
/// <param name="vertex">Input point(int preccission)</param>
/// <returns>Converted vertex(double precission)</returns>
static VD::point_type to_point(const Point &point);
/// <summary>
/// Convert point type between voronoi and slicer format
/// </summary>
/// <param name="vertex">Input vertex</param>
/// <returns>created vector</returns>
static Vec2d to_point_d(const VD::vertex_type* vertex);
/// <summary>
/// Convert Slic3r Line to Voronoi segment type
/// </summary>
/// <param name="line">input line(int preccission)</param>
/// <returns>output segment(double precission)</returns>
static VD::segment_type to_segment(const Line &line);
/// <summary>
/// create direction from Voronoi edge
/// </summary>
/// <param name="edge">input</param>
/// <returns>direction --> (vertex1 - vertex0)</returns>
static Point to_direction(const VD::edge_type *edge);
/// <summary>
/// create direction from Voronoi edge
/// </summary>
/// <param name="edge">input</param>
/// <returns>direction --> (vertex1 - vertex0)</returns>
static Vec2d to_direction_d(const VD::edge_type *edge);
/// <summary>
/// check if coord is in limits for coord_t
/// </summary>
/// <param name="coord">input value</param>
/// <param name="source">VD source point coordinate</param>
/// <param name="max_distance">Maximal distance from source</param>
/// <returns>True when coord is in +- max_distance from source otherwise FALSE.</returns>
static bool is_coord_in_limits(const VD::coordinate_type &coord,
const coord_t & source,
double max_distance);
/// <summary>
/// Check x and y values of vertex
/// </summary>
/// <param name="vertex">input vertex</param>
/// <param name="source">VD source point</param>
/// <param name="max_distance">Maximal distance from source</param>
/// <returns>True when both coord are in limits given by source and max distance otherwise FALSE</returns>
static bool is_point_in_limits(const VD::vertex_type *vertex,
const Point & source,
double max_distance);
private:
/// <summary>
/// PRIVATE: function to help convert edge without vertex to line
/// </summary>
/// <param name="point1">VD source point</param>
/// <param name="point2">VD source point</param>
/// <param name="maximal_distance">Maximal distance from source point</param>
/// <returns>Line segment between lines</returns>
static Line create_line_between_source_points(
const Point &point1, const Point &point2, double maximal_distance);
public:
/// <summary>
/// Convert edge to line
/// only for line edge
/// crop infinite edge by maximal distance from source point
/// inspiration in VoronoiVisualUtils::clip_infinite_edge
/// </summary>
/// <param name="edge">VD edge</param>
/// <param name="points">Source point for voronoi diagram</param>
/// <param name="maximal_distance">Maximal distance from source point</param>
/// <returns>Croped line, when all line segment is out of max distance return empty optional</returns>
static std::optional<Line> to_line(const VD::edge_type &edge,
const Points & points,
double maximal_distance);
/// <summary>
/// close polygon defined by lines
/// close points will convert to their center
/// Mainly for convert to polygon
/// </summary>
/// <param name="lines">Border of polygon, sorted lines CCW</param>
/// <param name="center">Center point of polygon</param>
/// <param name="maximal_distance">Radius around center point</param>
/// <param name="minimal_distance">Merge points closer than minimal_distance</param>
/// <param name="count_points">Count checking points, create help points for result polygon</param>
/// <returns>Valid CCW polygon with center inside of polygon</returns>
static Polygon to_polygon(const Lines &lines,
const Point &center,
double maximal_distance,
double minimal_distance,
size_t count_points);
/// <summary>
/// Convert cell to polygon
/// Source for VD must be only point to create VD with only line segments
/// infinite cell are croped by maximal distance from source point
/// </summary>
/// <param name="cell">cell from VD created only by points</param>
/// <param name="points">source points for VD</param>
/// <param name="maximal_distance">maximal distance from source point - only for infinite edges(cells)</param>
/// <returns>polygon created by cell</returns>
static Polygon to_polygon(const VD::cell_type &cell,
const Points & points,
double maximal_distance);
// return node from graph by vertex, when no exists create one
static VoronoiGraph::Node *getNode(VoronoiGraph & graph,
const VD::vertex_type *vertex,
const VD::edge_type * edge,
const Lines & lines);
/// <summary>
/// Extract point from lines, belongs to cell
/// ! Source for VD must be only lines
/// Main purpose parabola focus point from lines belongs to cell
/// inspiration in VoronoiVisualUtils::retrieve_point
/// </summary>
/// <param name="lines">Source of Voronoi diagram</param>
/// <param name="cell">Cell inside of Voronoi diagram</param>
/// <returns>Point from source lines.</returns>
static Point retrieve_point(const Lines &lines, const VD::cell_type &cell);
/// <summary>
/// Extract point from lines
/// ! Source for VD must be only points
/// inspiration in VoronoiVisualUtils::retrieve_point
/// </summary>
/// <param name="points">Source of Voronoi diagram</param>
/// <param name="cell">Cell inside of Voronoi diagram</param>
/// <returns>Point from source points.</returns>
static const Point& retrieve_point(const Points &points, const VD::cell_type &cell);
private:
/// <summary>
/// PRIVATE: function to get parabola focus point
/// </summary>
/// <param name="parabola">curved edge</param>
/// <param name="lines">source lines</param>
/// <returns>Parabola focus point</returns>
static Point get_parabola_point(const VD::edge_type &parabola, const Lines &lines);
/// <summary>
/// PRIVATE: function to get parabola diretrix
/// </summary>
/// <param name="parabola">curved edge</param>
/// <param name="lines">source lines</param>
/// <returns>Parabola diretrix</returns>
static Line get_parabola_line(const VD::edge_type &parabola, const Lines &lines);
public:
/// <summary>
/// Construct parabola from curved edge
/// </summary>
/// <param name="edge">curved edge</param>
/// <param name="lines">Voronoi diagram source lines</param>
/// <returns>Parabola represented shape of edge</returns>
static Parabola get_parabola(const VD::edge_type &edge, const Lines &lines);
/// <summary>
/// Calculate length of curved edge
/// </summary>
/// <param name="edge">curved edge</param>
/// <param name="lines">Voronoi diagram source lines</param>
/// <returns>edge length</returns>
static double calculate_length_of_parabola(const VD::edge_type &edge, const Lines &lines);
/// <summary>
/// Calculate length of edge line segment or curve - parabola.
/// </summary>
/// <param name="edge">Input edge to calcuate length</param>
/// <param name="lines">Source for Voronoi diagram. It contains parabola parameters</param>
/// <returns>The length of edge</returns>
static double calculate_length(const VD::edge_type &edge, const Lines &lines);
/// <summary>
/// Calculate maximal distance to outline and multiply by two(must be similar on both side)
/// ! not used
/// </summary>
/// <param name="edge">Input edge.</param>
/// <param name="lines">Source for Voronoi diagram. It contains parabola parameters</param>
/// <returns>Maximal island width along edge</returns>
static double calculate_max_width(const VD::edge_type &edge, const Lines &lines);
/// <summary>
/// Calculate width limit(min, max) and round value to coord_t
/// </summary>
/// <param name="edge">Input edge</param>
/// <param name="lines">Source for Voronoi diagram. It contains parabola parameters</param>
/// <returns>Width range for edge.
/// First is minimal width on edge.
/// Second is maximal width on edge.</returns>
static std::pair<coord_t, coord_t> calculate_width(const VD::edge_type &edge, const Lines &lines);
private:
static std::pair<coord_t, coord_t> calculate_width_for_line(
const VD::edge_type &line_edge, const Lines &lines);
static std::pair<coord_t, coord_t> calculate_width_for_parabola(
const VD::edge_type &parabola_edge, const Lines &lines);
static std::pair<coord_t, coord_t> min_max_width(const VD::edge_type &edge, const Point &point);
public:
/// <summary>
/// calculate distances to border of island and length on skeleton
/// </summary>
/// <param name="voronoi_diagram">Input anotated voronoi diagram
/// (use function Slic3r::Voronoi::annotate_inside_outside)</param>
/// <param name="lines">Source lines for voronoi diagram</param>
/// <returns>Extended voronoi graph by distances and length</returns>
static VoronoiGraph create_skeleton(const VD &vd, const Lines &lines);
/// <summary>
/// find neighbor and return distance between nodes
/// </summary>
/// <param name="from">source of neighborse</param>
/// <param name="to">neighbor node</param>
/// <returns>When neighbor return distance between neighbor Nodes
/// otherwise no value</returns>
static const VoronoiGraph::Node::Neighbor *get_neighbor(
const VoronoiGraph::Node *from, const VoronoiGraph::Node *to);
/// <summary>
/// use function get_neighbor
/// when not neighbor assert
/// </summary>
/// <param name="from">source Node</param>
/// <param name="to">destination Node</param>
/// <returns>distance between Nodes or Assert when not neighbor</returns>
static double get_neighbor_distance(const VoronoiGraph::Node *from,
const VoronoiGraph::Node *to);
/// <summary>
/// Create longest node path over circle together with side branches
/// </summary>
/// <param name="circle">Source circle, can't be connected with another
/// circle</param> <param name="side_branches">Circle side branches from
/// nodes of circle</param> <param name="start_path">Path before circle -
/// defince input point to circle</param> <returns>Longest nodes path and
/// its length</returns>
static VoronoiGraph::Path find_longest_path_on_circle(
const VoronoiGraph::Circle & circle,
const VoronoiGraph::ExPath::SideBranchesMap &side_branches);
/// <summary>
/// Serach longest path from input_node throw Nodes in connected circles,
/// when circle is alone call find_longest_path_on_circle.
/// </summary>
/// <param name="input_node">Node on circle</param>
/// <param name="finished_circle_index">index of circle with input
/// node</param> <param name="ex_path">Hold Circles, connection of circles
/// and Side branches</param> <returns>Longest path from input
/// node</returns>
static VoronoiGraph::Path find_longest_path_on_circles(
const VoronoiGraph::Node & input_node,
size_t finished_circle_index,
const VoronoiGraph::ExPath &ex_path);
/// <summary>
/// Function for detection circle in passed path.
/// </summary>
/// <param name="path">Already passed path in Graph</param>
/// <param name="neighbor">Actual neighbor possible created circle</param>
/// <returns>Circle when exists</returns>
static std::optional<VoronoiGraph::Circle> create_circle(
const VoronoiGraph::Path & path,
const VoronoiGraph::Node::Neighbor &neighbor);
/// <summary>
/// Move source connected circles into destination
/// </summary>
/// <param name="dst">In/Out param</param>
/// <param name="src">Input possible modified, do not use it after this
/// function</param> <param name="dst_circle_count">Count of destination
/// circles before merge Source circle are append afted destination, therfore
/// all src indexes must be increased by destination circle count</param>
static void merge_connected_circle(
VoronoiGraph::ExPath::ConnectedCircles &dst,
VoronoiGraph::ExPath::ConnectedCircles &src,
size_t dst_circle_count);
/// <summary>
/// move data from source to destination
/// side_branches + circles + connected_circle
/// </summary>
/// <param name="dst">destination extended path - append data from
/// source</param> <param name="src">source extended path - data will be
/// moved to dst</param>
static void append_neighbor_branch(VoronoiGraph::ExPath &dst,
VoronoiGraph::ExPath &src);
/// <summary>
/// Heal starting from random point.
/// Compare length of all starting path with side branches
/// when side branch is longer than swap with start path
/// </summary>
/// <param name="path">IN/OUT path to be fixed after creating longest path
/// from one point</param>
static void reshape_longest_path(VoronoiGraph::ExPath &path);
/// <summary>
/// Extract the longest path from voronoi graph
/// by own function call stack(IStackFunction).
/// Restructuralize path by branch created from random point.
/// </summary>
/// <param name="start_node">Random point from outline.</param>
static VoronoiGraph::ExPath create_longest_path(
const VoronoiGraph::Node *start_node);
/// <summary>
/// Find twin neighbor
/// </summary>
/// <param name="neighbor">neighbor</param>
/// <returns>Twin neighbor</returns>
static const VoronoiGraph::Node::Neighbor *get_twin(const VoronoiGraph::Node::Neighbor& neighbor);
/// <summary>
/// Find source node of neighbor
/// </summary>
/// <param name="neighbor">neighbor</param>
/// <returns>start node</returns>
static const VoronoiGraph::Node *get_twin_node(const VoronoiGraph::Node::Neighbor& neighbor);
/// <summary>
/// Check if neighbor is in opposit direction to line direction
/// </summary>
/// <param name="edge">edge has direction from vertex0 to vertex1</param>
/// <param name="line">line has direction from point a to point b</param>
/// <returns>True when oposit direction otherwise FALSE</returns>
static bool is_opposit_direction(const VD::edge_type *edge, const Line &line);
/// <summary>
/// Create point on edge defined by neighbor
/// in distance defined by edge length ratio
/// </summary>
/// <param name="position">Containe neighbor and position ratio on neighbor</param>
/// <returns>Point laying on neighbor edge</returns>
static Point create_edge_point(const VoronoiGraph::Position& position);
static Point create_edge_point(const VD::edge_type *edge, double ratio);
/// <summary>
/// Find position on VD edge with width
/// </summary>
/// <param name="neighbor">Edge for searching position</param>
/// <param name="width">Specify place on edge</param>
/// <param name="lines">Source lines for voronoi diagram</param>
/// <returns>Position on given edge</returns>
static VoronoiGraph::Position get_position_with_width(
const VoronoiGraph::Node::Neighbor *neighbor,
coord_t width,
const Lines & lines);
/// <summary>
/// Calculate both point on source lines correspond to edge postion
/// Faster way to get both point_on_line
/// </summary>
/// <param name="position">Position on edge</param>
/// <param name="lines">Source lines of VD</param>
/// <returns>pair of point lay on outline lines correspond to position on edge
/// first -> source line of edge cell
/// second -> source line of edge twin cell </returns>
static std::pair<Point, Point> point_on_lines(
const VoronoiGraph::Position &position,
const Lines & lines);
/// <summary>
/// align "position" close to point "to"
/// </summary>
/// <param name="position">input position on VD</param>
/// <param name="to">point to align</param>
/// <param name="max_distance">maximal distance on VD for search point</param>
/// <returns>Position on VD</returns>
static VoronoiGraph::Position align(const VoronoiGraph::Position &position,
const Point & to,
double max_distance);
/// <summary>
/// Calc position by ratio to closest point laying on edge
/// </summary>
/// <param name="edge">edge to align</param>
/// <param name="point">point to align</param>
/// <param name="edge_ratio">output: ratio between vertex0 and vertex1 closest to point,
/// when less than zero vertex0 is closest point on edge
/// when grater than one vertex1 is closest point on edge</param>
/// <returns>distance edge to "to" point
/// only when result ratio is in range from 0 to 1 </returns>
static double get_distance(const VD::edge_type &edge,
const Point & point,
double & edge_ratio);
static const VoronoiGraph::Node *getFirstContourNode(
const VoronoiGraph &graph);
/// <summary>
/// Get max width from edge in voronoi graph
/// </summary>
/// <param name="longest_path">Input point to voronoi graph</param>
/// <returns>Maximal widht in graph</returns>
static coord_t get_max_width(const VoronoiGraph::ExPath &longest_path);
static coord_t get_max_width(const VoronoiGraph::Nodes &path);
static coord_t get_max_width(const VoronoiGraph::Node *node);
/// <summary>
/// Check wheather VG ends in smaller distance than given one
/// </summary>
/// <param name="position">Position in direction to checked end</param>
/// <param name="max_distance">distance to explore</param>
/// <returns>True when there is only smaller VD path to edge</returns>
static bool ends_in_distanace(const VoronoiGraph::Position &position, coord_t max_distance);
/// <summary>
/// only line created VG
/// only last neighbor
/// Calculate angle of outline(source lines) at end of voronoi diagram
/// </summary>
/// <param name="neighbor">Neighbor to calculate angle</param>
/// <returns>Angle of source lines in radians</returns>
static double outline_angle(const VoronoiGraph::Node::Neighbor &neighbor, const Lines& lines);
/// <summary>
/// Loop over neighbor in max distance from position
/// </summary>
/// <param name="position">Start of loop</param>
/// <param name="max_distance">Termination of loop</param>
/// <param name="fnc">function to process neighbor with actual distance</param>
static void for_neighbor_at_distance(
const VoronoiGraph::Position &position,
coord_t max_distance,
std::function<void(const VoronoiGraph::Node::Neighbor &, coord_t)> fnc);
public: // draw function for debug
static void draw(SVG & svg,
const VoronoiGraph &graph,
const Lines & lines,
const SampleConfig &config,
bool pointer_caption = false);
static void draw(SVG & svg,
const VD::edge_type &edge,
const Lines & lines,
const char * color,
coord_t width);
static void draw(SVG & svg,
const VoronoiGraph::Nodes &path,
coord_t width,
const char * color,
bool finish = false,
bool caption = false);
static void draw(SVG & svg,
const VoronoiGraph::ExPath &path,
coord_t width);
// draw polygon when convert from cell
static void draw(const Slic3r::Polygon &polygon,
const Slic3r::Lines & lines,
const Slic3r::Point & center);
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_VoronoiGraphUtils_hpp_

View File

@ -7,11 +7,7 @@
#include <libslic3r/Point.hpp>
namespace Slic3r {
class ModelObject;
namespace sla {
namespace Slic3r::sla {
// An enum to keep track of where the current points on the ModelObject came from.
enum class PointsStatus {
@ -21,58 +17,47 @@ enum class PointsStatus {
UserModified // User has done some edits.
};
struct SupportPoint
{
Vec3f pos;
float head_front_radius;
bool is_new_island;
SupportPoint()
: pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false)
{}
SupportPoint(float pos_x,
float pos_y,
float pos_z,
float head_radius,
bool new_island = false)
: pos(pos_x, pos_y, pos_z)
, head_front_radius(head_radius)
, is_new_island(new_island)
{}
SupportPoint(Vec3f position, float head_radius, bool new_island = false)
: pos(position)
, head_front_radius(head_radius)
, is_new_island(new_island)
{}
SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data)
: pos(data(0), data(1), data(2))
, head_front_radius(data(3))
, is_new_island(data(4) != 0.f)
{}
bool operator==(const SupportPoint &sp) const
{
float rdiff = std::abs(head_front_radius - sp.head_front_radius);
return (pos == sp.pos) && rdiff < float(EPSILON) &&
is_new_island == sp.is_new_island;
}
bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); }
template<class Archive> void serialize(Archive &ar)
{
ar(pos, head_front_radius, is_new_island);
}
// Reason of automatic support placement usage
enum class SupportPointType {
manual_add,
island, // no move, island should be grouped
slope,
//thin,
//stability,
//edge
};
/// <summary>
/// Stereolithography(SLA) support point
/// </summary>
struct SupportPoint
{
// Position on model surface
Vec3f pos = Vec3f::Zero(); // [in mm]
// radius of the touching interface
// Also define force it must keep
float head_front_radius = 0.f; // [in mm]
// type
SupportPointType type{SupportPointType::manual_add};
bool is_island() const { return type == SupportPointType::island; }
template<class Archive> void serialize(Archive &ar){
ar(pos, head_front_radius, type);
}
// unsaved changes + cache invalidation
bool operator==(const SupportPoint &sp) const {
float rdiff = std::abs(head_front_radius - sp.head_front_radius);
return (pos == sp.pos) && rdiff < float(EPSILON) && type == sp.type;
}
bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); }
};
using SupportPoints = std::vector<SupportPoint>;
SupportPoints transformed_support_points(const ModelObject &mo,
const Transform3d &trafo);
}} // namespace Slic3r::sla
} // namespace Slic3r::sla
#endif // SUPPORTPOINT_HPP

File diff suppressed because it is too large Load Diff

View File

@ -1,250 +1,232 @@
///|/ Copyright (c) Prusa Research 2020 - 2022 Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena
///|/ Copyright (c) Prusa Research 2024 Filip Sykala @Jony01
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef SLA_SUPPORTPOINTGENERATOR_HPP
#define SLA_SUPPORTPOINTGENERATOR_HPP
#include <libslic3r/AABBMesh.hpp>
#include <libslic3r/SLA/SupportPoint.hpp>
#include <libslic3r/BoundingBox.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Point.hpp>
#include <boost/container/small_vector.hpp>
#include <stdint.h>
#include <random>
#include <cmath>
#include <cstddef>
#include <functional>
#include <unordered_map>
#include <utility>
#include <vector>
#include <cinttypes>
#include <functional>
#include <boost/container/small_vector.hpp>
#include "libslic3r/Point.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/SLA/SupportIslands/SampleConfig.hpp"
namespace Slic3r {
class AABBMesh;
} // namespace Slic3r
namespace Slic3r::sla {
// #define SLA_SUPPORTPOINTGEN_DEBUG
std::vector<Vec2f> create_default_support_curve();
SampleConfig create_default_island_configuration(float head_diameter_in_mm);
namespace Slic3r { namespace sla {
/// <summary>
/// Configuration for automatic support placement
/// </summary>
struct SupportPointGeneratorConfig{
/// <summary>
/// 0 mean only one support point for each island
/// lower than one mean less amount of support points
/// 1 mean fine tuned sampling
/// more than one mean bigger amout of support points
/// </summary>
float density_relative{1.f};
class SupportPointGenerator {
public:
struct Config {
float density_relative {1.f};
float minimal_distance {1.f};
float head_diameter {0.4f};
/// <summary>
/// Size range for support point interface (head)
/// </summary>
float head_diameter = 0.4f; // [in mm]
// Originally calibrated to 7.7f, reduced density by Tamas to 70% which is 11.1 (7.7 / 0.7) to adjust for new algorithm changes in tm_suppt_gen_improve
inline float support_force() const { return 11.1f / density_relative; } // a force one point can support (arbitrary force unit)
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
};
SupportPointGenerator(const AABBMesh& emesh, const std::vector<ExPolygons>& slices,
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
SupportPointGenerator(const AABBMesh& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
const std::vector<SupportPoint>& output() const { return m_output; }
std::vector<SupportPoint>& output() { return m_output; }
struct MyLayer;
struct Structure {
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f &centroid, float area, float h) :
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), zlevel(h)
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
, unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
{}
MyLayer *layer;
const ExPolygon* polygon = nullptr;
const BoundingBox bbox;
const Vec2f centroid = Vec2f::Zero();
const float area = 0.f;
float zlevel = 0;
// How well is this ExPolygon held to the print base?
// Positive number, the higher the better.
float supports_force_this_layer = 0.f;
float supports_force_inherited = 0.f;
float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; }
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
std::chrono::milliseconds unique_id;
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
struct Link {
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
Structure *island;
float overlap_area;
};
// maximal distance to nearest support point(define radiuses per layer)
// x axis .. mean distance on layer(XY)
// y axis .. mean difference of height(Z)
// Points of lines [in mm]
std::vector<Vec2f> support_curve = create_default_support_curve();
#ifdef NDEBUG
// In release mode, use the optimized container.
boost::container::small_vector<Link, 4> islands_above;
boost::container::small_vector<Link, 4> islands_below;
#else
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
std::vector<Link> islands_above;
std::vector<Link> islands_below;
#endif
// Overhangs, that are dangling considerably.
ExPolygons dangling_areas;
// Complete overhands.
ExPolygons overhangs;
// Overhangs, where the surface must slope.
ExPolygons overhangs_slopes;
float overhangs_area = 0.f;
bool overlaps(const Structure &rhs) const {
return this->bbox.overlap(rhs.bbox) && this->polygon->overlaps(*rhs.polygon);
}
float overlap_area(const Structure &rhs) const {
double out = 0.;
if (this->bbox.overlap(rhs.bbox)) {
Polygons polys = intersection(*this->polygon, *rhs.polygon);
for (const Polygon &poly : polys)
out += poly.area();
}
return float(out);
}
float area_below() const {
float area = 0.f;
for (const Link &below : this->islands_below)
area += below.island->area;
return area;
}
Polygons polygons_below() const {
size_t cnt = 0;
for (const Link &below : this->islands_below)
cnt += 1 + below.island->polygon->holes.size();
Polygons out;
out.reserve(cnt);
for (const Link &below : this->islands_below) {
out.emplace_back(below.island->polygon->contour);
append(out, below.island->polygon->holes);
}
return out;
}
ExPolygons expolygons_below() const {
ExPolygons out;
out.reserve(this->islands_below.size());
for (const Link &below : this->islands_below)
out.emplace_back(*below.island->polygon);
return out;
}
// Positive deficit of the supports. If negative, this area is well supported. If positive, more supports need to be added.
float support_force_deficit(const float tear_pressure) const { return this->area * tear_pressure - this->supports_force_total(); }
};
struct MyLayer {
MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {}
size_t layer_id;
coordf_t print_z;
std::vector<Structure> islands;
};
struct RichSupportPoint {
Vec3f position;
Structure *island;
};
struct PointGrid3D {
struct GridHash {
std::size_t operator()(const Vec3i &cell_id) const {
return std::hash<int>()(cell_id.x()) ^ std::hash<int>()(cell_id.y() * 593) ^ std::hash<int>()(cell_id.z() * 7919);
}
};
typedef std::unordered_multimap<Vec3i, RichSupportPoint, GridHash> Grid;
Vec3f cell_size;
Grid grid;
Vec3i cell_id(const Vec3f &pos) {
return Vec3i(int(floor(pos.x() / cell_size.x())),
int(floor(pos.y() / cell_size.y())),
int(floor(pos.z() / cell_size.z())));
}
void insert(const Vec2f &pos, Structure *island) {
RichSupportPoint pt;
pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z));
pt.island = island;
grid.emplace(cell_id(pt.position), pt);
}
bool collides_with(const Vec2f &pos, float print_z, float radius) {
Vec3f pos3d(pos.x(), pos.y(), print_z);
Vec3i cell = cell_id(pos3d);
std::pair<Grid::const_iterator, Grid::const_iterator> it_pair = grid.equal_range(cell);
if (collides_with(pos3d, radius, it_pair.first, it_pair.second))
return true;
for (int i = -1; i < 2; ++ i)
for (int j = -1; j < 2; ++ j)
for (int k = -1; k < 1; ++ k) {
if (i == 0 && j == 0 && k == 0)
continue;
it_pair = grid.equal_range(cell + Vec3i(i, j, k));
if (collides_with(pos3d, radius, it_pair.first, it_pair.second))
return true;
}
return false;
}
private:
bool collides_with(const Vec3f &pos, float radius, Grid::const_iterator it_begin, Grid::const_iterator it_end) {
for (Grid::const_iterator it = it_begin; it != it_end; ++ it) {
float dist2 = (it->second.position - pos).squaredNorm();
if (dist2 < radius * radius)
return true;
}
return false;
}
};
void execute(const std::vector<ExPolygons> &slices,
const std::vector<float> & heights);
void seed(std::mt19937::result_type s) { m_rng.seed(s); }
private:
std::vector<SupportPoint> m_output;
SupportPointGenerator::Config m_config;
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
// Configuration for sampling island
SampleConfig island_configuration = create_default_island_configuration(head_diameter);
public:
enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfWithBoundary = 0x2 };
private:
void uniformly_cover(const ExPolygons& islands, Structure& structure, float deficit, PointGrid3D &grid3d, IslandCoverageFlags flags = icfNone);
void add_support_points(Structure& structure, PointGrid3D &grid3d);
void project_onto_mesh(std::vector<SupportPoint>& points) const;
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
static void output_expolygons(const ExPolygons& expolys, const std::string &filename);
static void output_structures(const std::vector<Structure> &structures);
#endif // SLA_SUPPORTPOINTGEN_DEBUG
const AABBMesh& m_emesh;
std::function<void(void)> m_throw_on_cancel;
std::function<void(int)> m_statusfn;
std::mt19937 m_rng;
// maximal allowed distance to layer part for permanent(manual edited) support
// helps to identify not wanted support points during automatic support generation.
double max_allowed_distance_sq = scale_(1) * scale_(1); // 1mm
};
void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl);
struct LayerPart; // forward decl.
using LayerParts = std::vector<LayerPart>;
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng);
void sample_expolygon_boundary(const ExPolygon &expoly, float samples_per_mm, std::vector<Vec2f> &out, std::mt19937 &rng);
using PartLink = LayerParts::const_iterator;
#ifdef NDEBUG
// In release mode, use the optimized container.
using PartLinks = boost::container::small_vector<PartLink, 4>;
#else
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
using PartLinks = std::vector<PartLink>;
#endif
}} // namespace Slic3r::sla
// Large one layer overhang that needs to be supported on the overhanging side
struct Peninsula{
// shape of peninsula
//ExPolygon shape;
#endif // SUPPORTPOINTGENERATOR_HPP
// Prev layer is extended by self support const and subtracted from current one.
// This part of layer is supported as island
ExPolygon unsuported_area;
// Flag for unsuported_area line about source
// Same size as Slic3r::to_lines(unsuported_area)
// True .. peninsula outline(coast)
// False .. connection to land(already supported by previous layer)
std::vector<bool> is_outline;
};
using Peninsulas = std::vector<Peninsula>;
// Part on layer is defined by its shape
struct LayerPart {
// Pointer to expolygon stored in input
const ExPolygon *shape;
// To detect irelevant support poinst for part
ExPolygons extend_shape;
// rectangular bounding box of shape
BoundingBox shape_extent;
// uniformly sampled shape contour
Points samples;
// Parts from previous printed layer, which is connected to current part
PartLinks prev_parts;
PartLinks next_parts;
// half island is supported as special case
Peninsulas peninsulas;
};
/// <summary>
/// Extend support point with information from layer
/// </summary>
struct LayerSupportPoint: public SupportPoint
{
// 2d coordinate on layer
// use only when part is not nullptr
Point position_on_layer; // [scaled_ unit]
// index into curve to faster found radius for current layer
size_t radius_curve_index = 0;
coord_t current_radius = 0; // [in scaled mm]
// information whether support point is active in current investigated layer
// Flagged false when no part on layer in Radius 'r' around support point
// Tool to support overlapped overhang area multiple times
bool active_in_part = true;
// When true support point position is not generated by algorithm
bool is_permanent = false;
};
using LayerSupportPoints = std::vector<LayerSupportPoint>;
/// <summary>
/// One slice divided into
/// </summary>
struct Layer
{
// Absolute distance from Zero - copy value from heights<float>
// It represents center of layer(range of layer is half layer size above and belowe)
float print_z; // [in mm]
// data for one expolygon
LayerParts parts;
};
using Layers = std::vector<Layer>;
/// <summary>
/// Keep state of Support Point generation
/// Used for resampling with different configuration
/// </summary>
struct SupportPointGeneratorData
{
// Input slices of mesh
std::vector<ExPolygons> slices;
// Keep information about layer and its height
// and connection between layers for its part
// NOTE: contain links into slices
Layers layers;
// Manualy edited supports by user should be permanent
SupportPoints permanent_supports;
};
// call during generation of support points to check cancel event
using ThrowOnCancel = std::function<void(void)>;
// call to say progress of generation into gui in range from 0 to 100
using StatusFunction= std::function<void(int)>;
struct PrepareGeneratorDataConfig
{
// Discretization of overhangs outline,
// smaller will slow down the process but will be more precise
double discretize_overhang_sample_in_mm = 2.; // [in mm]
// Define minimal width of overhang to be considered as peninsula
// (partialy island - sampled not on edge)
coord_t peninsula_width = scale_(2.); // [in scaled mm]
};
/// <summary>
/// Prepare data for generate support points
/// Used for interactive resampling to store permanent data between configuration changes.,
/// Everything which could be prepared are stored into result.
/// Need to regenerate on mesh change(Should be connected with ObjectId) OR change of slicing heights
/// </summary>
/// <param name="slices">Countour cut from mesh</param>
/// <param name="heights">Heights of the slices - Same size as slices</param>
/// <param name="config">Preparation parameters</param>
/// <param name="throw_on_cancel">Call in meanwhile to check cancel event</param>
/// <param name="statusfn">Say progress of generation into gui</param>
/// <returns>Data prepared for generate support points</returns>
SupportPointGeneratorData prepare_generator_data(
std::vector<ExPolygons> &&slices,
const std::vector<float> &heights,
const PrepareSupportConfig &config = {},
ThrowOnCancel throw_on_cancel = []() {},
StatusFunction statusfn = [](int) {}
);
/// <summary>
/// Generate support points on islands by configuration parameters
/// </summary>
/// <param name="data">Preprocessed data needed for sampling</param>
/// <param name="config">Define density of samples</param>
/// <param name="throw_on_cancel">Call in meanwhile to check cancel event</param>
/// <param name="statusfn">Progress of generation into gui</param>
/// <returns>Generated support points</returns>
LayerSupportPoints generate_support_points(
const SupportPointGeneratorData &data,
const SupportPointGeneratorConfig &config,
ThrowOnCancel throw_on_cancel = []() {},
StatusFunction statusfn = [](int) {}
);
} // namespace Slic3r::sla
// TODO: Not sure if it is neccessary & Should be in another file
namespace Slic3r{
class AABBMesh;
namespace sla {
/// <summary>
/// Move support points on surface of mesh
/// </summary>
/// <param name="points">Support points to move on surface</param>
/// <param name="mesh">Define surface for move points</param>
/// <param name="throw_on_cancel">Call in meanwhile to check cancel event</param>
/// <returns>Support points laying on mesh surface</returns>
SupportPoints move_on_mesh_surface(
const LayerSupportPoints &points,
const AABBMesh &mesh,
double allowed_move,
ThrowOnCancel throw_on_cancel = []() {}
);
}}
#endif // SLA_SUPPORTPOINTGENERATOR_HPP

View File

@ -1015,7 +1015,6 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
} else if (
opt_key == "support_points_density_relative"
|| opt_key == "support_enforcers_only"
|| opt_key == "support_points_minimal_distance"
) {
steps.emplace_back(slaposSupportPoints);
} else if (
@ -1255,8 +1254,12 @@ SLAPrintObject::get_parts_to_slice(SLAPrintObjectStep untilstep) const
sla::SupportPoints SLAPrintObject::transformed_support_points() const
{
assert(model_object());
return sla::transformed_support_points(*model_object(), trafo());
auto spts = model_object()->sla_support_points;
Transform3f tr = trafo().cast<float>();
for (sla::SupportPoint &suppt : spts) {
suppt.pos = tr * suppt.pos;
}
return spts;
}
sla::DrainHoles SLAPrintObject::transformed_drainhole_points() const

View File

@ -26,6 +26,7 @@
#include "PrintBase.hpp"
#include "SLA/SupportTree.hpp"
#include "SLA/SupportPointGenerator.hpp" // SupportPointGeneratorData
#include "Point.hpp"
#include "Format/SLAArchiveWriter.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
@ -367,6 +368,9 @@ private:
std::vector<float> m_model_height_levels;
// Precalculated data needed for interactive automatic support placement.
sla::SupportPointGeneratorData m_support_point_generator_data;
struct SupportData
{
sla::SupportableMesh input; // the input

View File

@ -18,6 +18,7 @@
#include <libslic3r/OpenVDBUtils.hpp>
#include <libslic3r/QuadricEdgeCollapse.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/KDTreeIndirect.hpp>
#include <chrono>
#include <algorithm>
#include <array>
@ -51,9 +52,9 @@
#include "libslic3r/SLA/Hollowing.hpp"
#include "libslic3r/SLA/JobController.hpp"
#include "libslic3r/SLA/RasterBase.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/SLA/SupportTree.hpp"
#include "libslic3r/SLA/SupportTreeStrategies.hpp"
#include "libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/TriangleMesh.hpp"
@ -105,8 +106,77 @@ std::string PRINT_STEP_LABELS(size_t idx)
assert(false); return "Out of bounds!";
}
using namespace sla;
/// <summary>
/// Copy permanent support points from model to permanent_supports
/// </summary>
/// <param name="permanent_supports">OUTPUT</param>
/// <param name="object_supports"></param>
/// <param name="emesh"></param>
void prepare_permanent_support_points(
SupportPoints &permanent_supports,
const SupportPoints &object_supports,
const Transform3d &object_trafo,
const AABBMesh &emesh) {
// update permanent support points
permanent_supports.clear(); // previous supports are irelevant
for (const SupportPoint &p : object_supports) {
if (p.type != SupportPointType::manual_add)
continue;
// TODO: remove transformation
// ?? Why need transform the position?
Vec3f pos = (object_trafo * p.pos.cast<double>()).cast<float>();
double dist_sq = emesh.squared_distance(pos.cast<double>());
if (dist_sq >= sqr(p.head_front_radius)) {
// TODO: inform user about skipping points, which are far from surface
assert(false);
continue; // skip points outside the mesh
}
permanent_supports.push_back(p); // copy
permanent_supports.back().pos = pos; // ?? Why need transform the position?
}
// Prevent overlapped permanent supports
auto point_accessor = [&permanent_supports](size_t idx, size_t dim) -> float & {
return permanent_supports[idx].pos[dim]; };
std::vector<size_t> indices(permanent_supports.size());
std::iota(indices.begin(), indices.end(), 0);
KDTreeIndirect<3, float, decltype(point_accessor)> tree(point_accessor, indices);
for (SupportPoint &p : permanent_supports) {
if (p.head_front_radius < 0.f)
continue; // already marked for erase
std::vector<size_t> near_indices = find_nearby_points(tree, p.pos, p.head_front_radius);
assert(!near_indices.empty());
if (near_indices.size() == 1)
continue; // only support itself
size_t index = &p - &permanent_supports.front();
for (size_t near_index : near_indices) {
if (near_index == index)
continue; // support itself
SupportPoint p_near = permanent_supports[near_index];
if ((p.pos - p_near.pos).squaredNorm() > sqr(p.head_front_radius))
continue; // not near point
// IMPROVE: investigate neighbors of the near point
// TODO: inform user about skip near point
assert(false);
permanent_supports[near_index].head_front_radius = -1.0f; // mark for erase
}
}
permanent_supports.erase(std::remove_if(permanent_supports.begin(), permanent_supports.end(),
[](const SupportPoint &p) { return p.head_front_radius < 0.f; }),permanent_supports.end());
std::sort(permanent_supports.begin(), permanent_supports.end(),
[](const SupportPoint& p1,const SupportPoint& p2){ return p1.pos.z() < p2.pos.z(); });
}
} // namespace
SLAPrint::Steps::Steps(SLAPrint *print)
: m_print{print}
, objcount{m_print->m_objects.size()}
@ -469,6 +539,29 @@ template<class Cont> BoundingBoxf3 csgmesh_positive_bb(const Cont &csg)
return bb3d;
}
void SLAPrint::Steps::prepare_for_generate_supports(SLAPrintObject &po) {
using namespace sla;
std::vector<ExPolygons> slices = po.get_model_slices(); // copy
const std::vector<float> &heights = po.m_model_height_levels;
#ifdef USE_ISLAND_GUI_FOR_SETTINGS
const PrepareSupportConfig &prepare_cfg = SampleConfigFactory::get_sample_config(po.config().support_head_front_diameter).prepare_config; // use configuration edited by GUI
#else // USE_ISLAND_GUI_FOR_SETTINGS
const PrepareSupportConfig prepare_cfg; // use Default values of the configuration
#endif // USE_ISLAND_GUI_FOR_SETTINGS
ThrowOnCancel cancel = [this]() { throw_if_canceled(); };
// scaling for the sub operations
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 200.0;
double init = current_status();
StatusFunction status = [this, d, init](unsigned st) {
double current = init + st * d;
if (std::round(current_status()) < std::round(current))
report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
};
po.m_support_point_generator_data =
prepare_generator_data(std::move(slices), heights, prepare_cfg, cancel, status);
}
// The slicing will be performed on an imaginary 1D grid which starts from
// the bottom of the bounding box created around the supported model. So
// the first layer which is usually thicker will be part of the supports
@ -543,6 +636,10 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
// We apply the printer correction offset here.
apply_printer_corrections(po, soModel);
// Prepare data for the support point generator only when supports are enabled
if (po.m_config.supports_enable.getBool())
// We need to prepare data in previous step to create interactive support point generation
prepare_for_generate_supports(po);
// po.m_preview_meshes[slaposObjectSlice] = po.get_mesh_to_print();
// report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
}
@ -610,6 +707,7 @@ static void filter_support_points_by_modifiers(sla::SupportPoints &pts,
// support points. Then we sprinkle the rest of the mesh.
void SLAPrint::Steps::support_points(SLAPrintObject &po)
{
using namespace sla;
// If supports are disabled, we can skip the model scan.
if(!po.m_config.supports_enable.getBool()) return;
@ -628,86 +726,115 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
BOOST_LOG_TRIVIAL(debug) << "Support point count "
<< mo.sla_support_points.size();
// Unless the user modified the points or we already did the calculation,
// we will do the autoplacement. Otherwise we will just blindly copy the
// frontend data into the backend cache.
if (mo.sla_points_status != sla::PointsStatus::UserModified) {
// calculate heights of slices (slices are calculated already)
const std::vector<float>& heights = po.m_model_height_levels;
throw_if_canceled();
sla::SupportPointGenerator::Config config;
const SLAPrintObjectConfig& cfg = po.config();
// the density config value is in percents:
config.density_relative = float(cfg.support_points_density_relative / 100.f);
config.minimal_distance = float(cfg.support_points_minimal_distance);
switch (cfg.support_tree_type) {
case sla::SupportTreeType::Default:
case sla::SupportTreeType::Organic:
config.head_diameter = float(cfg.support_head_front_diameter);
break;
case sla::SupportTreeType::Branching:
config.head_diameter = float(cfg.branchingsupport_head_front_diameter);
break;
}
// scaling for the sub operations
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
double init = current_status();
auto statuscb = [this, d, init](unsigned st)
{
double current = init + st * d;
if(std::round(current_status()) < std::round(current))
report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
};
// Construction of this object does the calculation.
throw_if_canceled();
sla::SupportPointGenerator auto_supports(
po.m_supportdata->input.emesh, po.get_model_slices(),
heights, config, [this]() { throw_if_canceled(); }, statuscb);
// Now let's extract the result.
std::vector<sla::SupportPoint>& points = auto_supports.output();
throw_if_canceled();
MeshSlicingParamsEx params;
params.closing_radius = float(po.config().slice_closing_radius.value);
std::vector<ExPolygons> blockers =
slice_volumes(po.model_object()->volumes,
po.m_model_height_levels, po.trafo(), params,
[](const ModelVolume *vol) {
return vol->is_support_blocker();
});
std::vector<ExPolygons> enforcers =
slice_volumes(po.model_object()->volumes,
po.m_model_height_levels, po.trafo(), params,
[](const ModelVolume *vol) {
return vol->is_support_enforcer();
});
SuppPtMask mask{blockers, enforcers, po.config().support_enforcers_only.getBool()};
filter_support_points_by_modifiers(points, mask, po.m_model_height_levels);
po.m_supportdata->input.pts = points;
BOOST_LOG_TRIVIAL(debug)
<< "Automatic support points: "
<< po.m_supportdata->input.pts.size();
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
// the update status to GLGizmoSlaSupports
report_status(-1, _u8L("Generating support points"),
SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
} else {
if (mo.sla_points_status == PointsStatus::UserModified) {
// There are either some points on the front-end, or the user
// removed them on purpose. No calculation will be done.
po.m_supportdata->input.pts = po.transformed_support_points();
return;
}
// Unless the user modified the points or we already did the calculation,
// we will do the autoplacement. Otherwise we will just blindly copy the
// frontend data into the backend cache.
// if (mo.sla_points_status != PointsStatus::UserModified)
throw_if_canceled();
const SLAPrintObjectConfig& cfg = po.config();
// the density config value is in percents:
SupportPointGeneratorConfig config;
config.density_relative = float(cfg.support_points_density_relative / 100.f);
switch (cfg.support_tree_type) {
case SupportTreeType::Default:
case SupportTreeType::Organic:
config.head_diameter = float(cfg.support_head_front_diameter);
break;
case SupportTreeType::Branching:
config.head_diameter = float(cfg.branchingsupport_head_front_diameter);
break;
}
// copy current configuration for sampling islands
#ifdef USE_ISLAND_GUI_FOR_SETTINGS
// use static variable to propagate data from GUI
config.island_configuration = SampleConfigFactory::get_sample_config(config.density_relative);
#else // USE_ISLAND_GUI_FOR_SETTINGS
config.island_configuration = SampleConfigFactory::apply_density(
SampleConfigFactory::create(config.head_diameter), config.density_relative);
#endif // USE_ISLAND_GUI_FOR_SETTINGS
// scaling for the sub operations
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
double init = current_status();
auto statuscb = [this, d, init](unsigned st)
{
double current = init + st * d;
if(std::round(current_status()) < std::round(current))
report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
};
// Construction of this object does the calculation.
throw_if_canceled();
// TODO: filter small unprintable islands in slices
// (Island with area smaller than 1 pixel was skipped in support generator)
SupportPointGeneratorData &data = po.m_support_point_generator_data;
SupportPoints &permanent_supports = data.permanent_supports;
const SupportPoints &object_supports = po.model_object()->sla_support_points;
const Transform3d& object_trafo = po.trafo();
const AABBMesh& emesh = po.m_supportdata->input.emesh;
prepare_permanent_support_points(permanent_supports, object_supports, object_trafo, emesh);
ThrowOnCancel cancel = [this]() { throw_if_canceled(); };
StatusFunction status = statuscb;
LayerSupportPoints layer_support_points = generate_support_points(data, config, cancel, status);
// Maximal move of support point to mesh surface,
// no more than height of layer
assert(po.m_model_height_levels.size() > 1);
double allowed_move = (po.m_model_height_levels[1] - po.m_model_height_levels[0]) +
std::numeric_limits<float>::epsilon();
SupportPoints support_points =
move_on_mesh_surface(layer_support_points, emesh, allowed_move, cancel);
// The Generator count with permanent support positions but do not convert to LayerSupportPoints.
// To preserve permanent 3d position it is necessary to append points after move_on_mesh_surface
support_points.insert(support_points.end(),
permanent_supports.begin(), permanent_supports.end());
throw_if_canceled();
MeshSlicingParamsEx params;
params.closing_radius = float(po.config().slice_closing_radius.value);
std::vector<ExPolygons> blockers =
slice_volumes(po.model_object()->volumes,
po.m_model_height_levels, po.trafo(), params,
[](const ModelVolume *vol) {
return vol->is_support_blocker();
});
std::vector<ExPolygons> enforcers =
slice_volumes(po.model_object()->volumes,
po.m_model_height_levels, po.trafo(), params,
[](const ModelVolume *vol) {
return vol->is_support_enforcer();
});
SuppPtMask mask{blockers, enforcers, po.config().support_enforcers_only.getBool()};
filter_support_points_by_modifiers(support_points, mask, po.m_model_height_levels);
po.m_supportdata->input.pts = support_points;
BOOST_LOG_TRIVIAL(debug)
<< "Automatic support points: "
<< po.m_supportdata->input.pts.size();
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
// the update status to GLGizmoSlaSupports
report_status(-1, _u8L("Generating support points"),
SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
}
void SLAPrint::Steps::support_tree(SLAPrintObject &po)
@ -717,10 +844,17 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
// If the zero elevation mode is engaged, we have to filter out all the
// points that are on the bottom of the object
if (is_zero_elevation(po.config())) {
remove_bottom_points(po.m_supportdata->input.pts,
float(
po.m_supportdata->input.zoffset +
EPSILON));
// remove_bottom_points
std::vector<sla::SupportPoint> &pts = po.m_supportdata->input.pts;
float lvl(po.m_supportdata->input.zoffset + EPSILON);
// get iterator to the reorganized vector end
auto endit = std::remove_if(pts.begin(), pts.end(),
[lvl](const sla::SupportPoint &sp) {
return sp.pos.z() <= lvl; });
// erase all elements after the new end
pts.erase(endit, pts.end());
}
po.m_supportdata->input.cfg = make_support_cfg(po.m_config);

View File

@ -56,6 +56,8 @@ private:
void generate_preview(SLAPrintObject &po, SLAPrintObjectStep step);
indexed_triangle_set generate_preview_vdb(SLAPrintObject &po, SLAPrintObjectStep step);
void prepare_for_generate_supports(SLAPrintObject &po);
public:
explicit Steps(SLAPrint *print);

View File

@ -19,6 +19,7 @@
#include "libslic3r/Line.hpp"
#include "libslic3r/Surface.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
namespace Slic3r {
@ -110,6 +111,19 @@ void SVG::draw(const ExPolygon &expolygon, std::string fill, const float fill_op
this->path(d, true, 0, fill_opacity);
}
void SVG::draw_original(const ExPolygon &expolygon) {
std::ostringstream d;
auto write_d = [&d](const Points &pts) {
d << "M ";
for (const Point& p: pts)
d << p.x() << " " << p.y() << " ";
d << "z "; // closed path
};
for (const Polygon &p : to_polygons(expolygon))
write_d(p.points);
path(d.str(), false /*fill*/, 1 /*stroke_width*/, 0.f /*fill opacity*/);
}
void SVG::draw_outline(const ExPolygon &expolygon, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width)
{
draw_outline(expolygon.contour, stroke_outer, stroke_width);

View File

@ -96,6 +96,9 @@ public:
void draw_text(const Point &pt, const char *text, const char *color, coordf_t font_size = 20.f);
void draw_legend(const Point &pt, const char *text, const char *color, coordf_t font_size = 10.f);
// Draw no scaled expolygon coordinates
void draw_original(const ExPolygon &exPoly);
void Close();
private:

View File

@ -478,7 +478,6 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
toggle_field("branchingsupport_max_weight_on_model", supports_en && is_branching_tree);
toggle_field("support_points_density_relative", supports_en);
toggle_field("support_points_minimal_distance", supports_en);
bool pad_en = config->opt_bool("pad_enable");

View File

@ -17,21 +17,97 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp"
#include "imgui/imgui_stdlib.h" // string input for ImGui
static const double CONE_RADIUS = 0.25;
static const double CONE_HEIGHT = 0.75;
namespace Slic3r {
namespace GUI {
using namespace Slic3r;
using namespace Slic3r::GUI;
namespace {
enum class IconType : unsigned {
show_support_points_selected,
show_support_points_unselected,
show_support_points_hovered,
show_support_structure_selected,
show_support_structure_unselected,
show_support_structure_hovered,
// automatic calc of icon's count
_count
};
IconManager::Icons init_icons(IconManager &mng, ImVec2 size = ImVec2{50, 50}) {
mng.release();
// icon order has to match the enum IconType
IconManager::InitTypes init_types {
{"support_structure_invisible.svg", size, IconManager::RasterType::color}, // show_support_points_selected
{"support_structure_invisible.svg", size, IconManager::RasterType::gray_only_data}, // show_support_points_unselected
{"support_structure_invisible.svg", size, IconManager::RasterType::color}, // show_support_points_hovered
{"support_structure.svg", size, IconManager::RasterType::color}, // show_support_structure_selected
{"support_structure.svg", size, IconManager::RasterType::gray_only_data}, // show_support_structure_unselected
{"support_structure.svg", size, IconManager::RasterType::color}, // show_support_structure_hovered
};
assert(init_types.size() == static_cast<size_t>(IconType::_count));
std::string path = resources_dir() + "/icons/";
for (IconManager::InitType &init_type : init_types)
init_type.filepath = path + init_type.filepath;
return mng.init(init_types);
}
const IconManager::Icon &get_icon(const IconManager::Icons &icons, IconType type) {
return *icons[static_cast<unsigned>(type)]; }
/// <summary>
/// Draw icon buttons to swap between show structure and only supports points
/// </summary>
/// <param name="show_support_structure">In|Out view mode</param>
/// <param name="icons">all loaded icons</param>
/// <returns>True when change is made</returns>
bool draw_view_mode(bool &show_support_structure, const IconManager::Icons &icons) {
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 8.);
ScopeGuard sg([] { ImGui::PopStyleVar(); });
if (show_support_structure) {
draw(get_icon(icons, IconType::show_support_structure_selected));
if(ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Visible support structure").c_str());
ImGui::SameLine();
if (clickable(get_icon(icons, IconType::show_support_points_unselected),
get_icon(icons, IconType::show_support_points_hovered))) {
show_support_structure = false;
return true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Click to show support points without support structure").c_str());
} else { // !show_support_structure
if (clickable(get_icon(icons, IconType::show_support_structure_unselected),
get_icon(icons, IconType::show_support_structure_hovered))) {
show_support_structure = true;
return true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Click to show support structure with pad").c_str());
ImGui::SameLine();
draw(get_icon(icons, IconType::show_support_points_selected));
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Visible support points without support structure").c_str());
}
return false;
}
} // namespace
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoSlaBase(parent, icon_filename, sprite_id, slaposDrillHoles)
{
show_sla_supports(true);
: GLGizmoSlaBase(parent, icon_filename, sprite_id, slaposDrillHoles /*slaposSupportPoints*/) {
show_sla_supports(false);
}
bool GLGizmoSlaSupports::on_init()
@ -44,8 +120,7 @@ bool GLGizmoSlaSupports::on_init()
m_desc["remove_all"] = _u8L("Remove all points");
m_desc["apply_changes"] = _u8L("Apply changes");
m_desc["discard_changes"] = _u8L("Discard changes");
m_desc["minimal_distance"] = _u8L("Minimal points distance") + ": ";
m_desc["points_density"] = _u8L("Support points density") + ": ";
m_desc["points_density"] = _u8L("Support points density");
m_desc["auto_generate"] = _u8L("Auto-generate points");
m_desc["manual_editing"] = _u8L("Manual editing");
m_desc["clipping_of_view"] = _u8L("Clipping of view")+ ": ";
@ -141,8 +216,6 @@ void GLGizmoSlaSupports::on_render()
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
show_sla_supports(!m_editing_mode);
render_volumes();
render_points(selection);
@ -195,10 +268,15 @@ void GLGizmoSlaSupports::render_points(const Selection& selection)
const Transform3d& view_matrix = camera.get_view_matrix();
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
const ColorRGBA selected_color = ColorRGBA::REDISH();
const ColorRGBA hovered_color = ColorRGBA::CYAN();
const ColorRGBA island_color = ColorRGBA::BLUEISH();
const ColorRGBA inactive_color = ColorRGBA::LIGHT_GRAY();
const ColorRGBA manual_color = ColorRGBA::ORANGE();
ColorRGBA render_color;
for (size_t i = 0; i < cache_size; ++i) {
const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i];
const bool point_selected = m_editing_mode ? m_editing_cache[i].selected : false;
const bool clipped = is_mesh_point_clipped(support_point.pos.cast<double>());
if (i < m_point_raycasters.size()) {
@ -208,22 +286,16 @@ void GLGizmoSlaSupports::render_points(const Selection& selection)
if (clipped)
continue;
render_color =
support_point.type == sla::SupportPointType::manual_add ? manual_color :
support_point.type == sla::SupportPointType::island ? island_color :
inactive_color;
// First decide about the color of the point.
if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active
render_color = { 0.f, 1.f, 1.f, 1.f };
else { // neigher hover nor picking
bool supports_new_island = m_lock_unique_islands && support_point.is_new_island;
if (m_editing_mode) {
if (point_selected)
render_color = { 1.f, 0.3f, 0.3f, 1.f};
else
if (supports_new_island)
render_color = { 0.3f, 0.3f, 1.f, 1.f };
else
render_color = { 0.7f, 0.7f, 0.7f, 1.f };
}
else
render_color = { 0.5f, 0.5f, 0.5f, 1.f };
if (m_editing_mode) {
if (size_t(m_hover_id) == i) // ignore hover state unless editing mode is active
render_color = hovered_color;
else if (m_editing_cache[i].selected)
render_color = selected_color;
}
m_cone.model.set_color(render_color);
@ -324,7 +396,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
std::pair<Vec3f, Vec3f> pos_and_normal;
if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point"));
m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second);
m_editing_cache.emplace_back(sla::SupportPoint{pos_and_normal.first, m_new_point_head_diameter/2.f}, false, pos_and_normal.second);
m_parent.set_as_dirty();
m_wait_for_up_event = true;
unregister_point_raycasters_for_picking();
@ -479,7 +551,7 @@ void GLGizmoSlaSupports::delete_selected_points(bool force)
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point"));
for (unsigned int idx=0; idx<m_editing_cache.size(); ++idx) {
if (m_editing_cache[idx].selected && (!m_editing_cache[idx].support_point.is_new_island || !m_lock_unique_islands || force)) {
if (m_editing_cache[idx].selected && (!m_editing_cache[idx].support_point.is_island() || !m_lock_unique_islands || force)) {
m_editing_cache.erase(m_editing_cache.begin() + (idx--));
}
}
@ -517,51 +589,21 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
return out;
}
/*
void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& idxs) const
{
if (aabb->is_leaf()) { // this is a facet
// corner.dot(normal) - offset
idxs.push_back(aabb->m_primitive);
}
else { // not a leaf
using CornerType = Eigen::AlignedBox<float, 3>::CornerType;
bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0))));
for (unsigned int i=1; i<8; ++i)
if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) {
find_intersecting_facets(aabb->m_left, normal, offset, idxs);
find_intersecting_facets(aabb->m_right, normal, offset, idxs);
}
}
}
void GLGizmoSlaSupports::make_line_segments() const
{
TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh);
Vec3f normal(0.f, 1.f, 1.f);
double d = 0.;
std::vector<IntersectionLine> lines;
find_intersections(&m_AABB, normal, d, lines);
ExPolygons expolys;
tms.make_expolygons_simple(lines, &expolys);
SVG svg("slice_loops.svg", get_extents(expolys));
svg.draw(expolys);
//for (const IntersectionLine &l : lines[i])
// svg.draw(l, "red", 0);
//svg.draw_outline(expolygons, "black", "blue", 0);
svg.Close();
}
*/
void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit)
{
// Keep resolution of icons for
static float rendered_line_height;
if (float line_height = ImGui::GetTextLineHeightWithSpacing();
m_icons.empty() ||
rendered_line_height != line_height) { // change of view resolution
rendered_line_height = line_height;
// need regeneration when change resolution(move between monitors)
float width = std::round(line_height / 8 + 1) * 8;
ImVec2 icon_size{width, width};
m_icons = init_icons(m_icon_manager, icon_size);
}
static float last_y = 0.0f;
static float last_h = 0.0f;
@ -595,7 +637,7 @@ RENDER_AGAIN:
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float settings_sliders_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("minimal_distance")).x, ImGuiPureWrap::calc_text_size(m_desc.at("points_density")).x) + m_imgui->scaled(1.f);
const float settings_sliders_left = ImGuiPureWrap::calc_text_size(m_desc.at("points_density")).x + m_imgui->scaled(1.f);
const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
const float diameter_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
@ -677,51 +719,91 @@ RENDER_AGAIN:
}
else { // not in editing mode:
m_imgui->disabled_begin(!is_input_enabled());
ImGui::AlignTextToFramePadding();
ImGuiPureWrap::text(m_desc.at("minimal_distance"));
ImGui::SameLine(settings_sliders_left);
ImGui::PushItemWidth(window_width - settings_sliders_left);
std::vector<const ConfigOption*> opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"});
float density = static_cast<const ConfigOptionInt*>(opts[0])->value;
float minimal_point_distance = static_cast<const ConfigOptionFloat*>(opts[1])->value;
m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm");
bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider
bool slider_edited = m_imgui->get_last_slider_status().edited; // someone is dragging the slider
bool slider_released = m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider
ImGui::AlignTextToFramePadding();
ImGuiPureWrap::text(m_desc.at("points_density"));
ImGui::SameLine(settings_sliders_left);
ImGui::SameLine();
m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%");
slider_clicked |= m_imgui->get_last_slider_status().clicked;
slider_edited |= m_imgui->get_last_slider_status().edited;
slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit;
if (draw_view_mode(m_show_support_structure, m_icons)){
show_sla_supports(m_show_support_structure);
if (m_show_support_structure) {
if (m_normal_cache.empty()) {
// first click also have to generate point
auto_generate();
} else {
reslice_until_step(slaposPad);
}
}
}
if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo
m_minimal_point_distance_stash = minimal_point_distance;
m_density_stash = density;
const char *support_points_density = "support_points_density_relative";
float density = static_cast<const ConfigOptionInt*>(get_config_options({support_points_density})[0])->value;
float old_density = density;
wxString tooltip = _L("Change amount of generated support points.");
if (m_imgui->slider_float("##density", &density, 50.f, 200.f, "%.f %%", 1.f, false, tooltip)){
if (density < 10.f) // not neccessary, but lower value seems pointless. Zero cause issues inside algorithms.
density = 10.f;
mo->config.set(support_points_density, (int) density);
}
if (slider_edited) {
mo->config.set("support_points_minimal_distance", minimal_point_distance);
mo->config.set("support_points_density_relative", (int)density);
}
if (slider_released) {
mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash);
mo->config.set("support_points_density_relative", (int)m_density_stash);
const ImGuiWrapper::LastSliderStatus &density_status = m_imgui->get_last_slider_status();
static std::optional<int> density_stash; // Value for undo/redo stack is written on stop dragging
if (!density_stash.has_value() && !is_approx(density, old_density)) // stash the values of the settings so we know what to revert to after undo
density_stash = (int)old_density;
if (density_status.deactivated_after_edit && density_stash.has_value()) { // slider released
// set configuration to value before slide
// to store this value on undo redo snapshot stack
mo->config.set(support_points_density, *density_stash);
density_stash.reset();
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change"));
mo->config.set("support_points_minimal_distance", minimal_point_distance);
mo->config.set("support_points_density_relative", (int)density);
mo->config.set(support_points_density, (int) density);
wxGetApp().obj_list()->update_and_show_object_settings_item();
}
bool generate = ImGuiPureWrap::button(m_desc.at("auto_generate"));
if (generate)
auto_generate();
}
const sla::SupportPoints &supports = m_normal_cache;
int count_user_edited = 0;
int count_island = 0;
for (const sla::SupportPoint &support : supports)
switch (support.type) {
case sla::SupportPointType::manual_add: ++count_user_edited; break;
case sla::SupportPointType::island: ++count_island; break;
//case sla::SupportPointType::slope:
default: assert(support.type == sla::SupportPointType::slope); }
std::string stats;
if (supports.empty()) {
stats = "No support points generated yet.";
} else if (count_user_edited == 0) {
stats = GUI::format("%d support points generated (%d on islands)",
(int) supports.size(), count_island);
} else {
stats = GUI::format("%d(%d manual) support points (%d on islands)",
(int) supports.size(), count_user_edited, count_island);
}
ImVec4 light_gray{0.4f, 0.4f, 0.4f, 1.0f};
ImGui::TextColored(light_gray, "%s", stats.c_str());
#ifdef USE_ISLAND_GUI_FOR_SETTINGS
ImGui::Separator();
ImGui::Text("Between delimiters is temporary GUI");
sla::SampleConfig &sample_config = sla::SampleConfigFactory::get_sample_config();
if (float overhang_sample_distance = sample_config.prepare_config.discretize_overhang_step;
m_imgui->slider_float("overhang discretization", &overhang_sample_distance, 2e-5f, 10.f, "%.2f mm")){
sample_config.prepare_config.discretize_overhang_step = overhang_sample_distance;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Smaller will slow down. Step for discretization overhang outline for test of support need");
draw_island_config();
ImGui::Text("Distribution depends on './resources/data/sla_support.svg'\ninstruction for edit are in file");
ImGui::Separator();
#endif // USE_ISLAND_GUI_FOR_SETTINGS
if (ImGuiPureWrap::button(m_desc.at("auto_generate")))
auto_generate();
ImGui::SameLine();
m_imgui->disabled_begin(!is_input_enabled() || m_normal_cache.empty());
remove_all = ImGuiPureWrap::button(m_desc.at("remove_all"));
m_imgui->disabled_end();
ImGui::Separator();
if (ImGuiPureWrap::button(m_desc.at("manual_editing")))
@ -729,10 +811,6 @@ RENDER_AGAIN:
m_imgui->disabled_end();
m_imgui->disabled_begin(!is_input_enabled() || m_normal_cache.empty());
remove_all = ImGuiPureWrap::button(m_desc.at("remove_all"));
m_imgui->disabled_end();
// ImGuiPureWrap::text("");
// ImGuiPureWrap::text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) :
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) :
@ -798,6 +876,139 @@ RENDER_AGAIN:
m_parent.set_as_dirty();
}
#ifdef USE_ISLAND_GUI_FOR_SETTINGS
void GLGizmoSlaSupports::draw_island_config() {
if (!ImGui::TreeNode("Support islands:"))
return; // no need to draw configuration for islands
sla::SampleConfig &sample_config = sla::SampleConfigFactory::get_sample_config();
ImGui::SameLine();
ImGui::Text("head radius %.2f mm", unscale<float>(sample_config.head_radius));
bool exist_change = false;
if (float max_for_one = unscale<float>(sample_config.max_length_for_one_support_point); // [in mm]
ImGui::InputFloat("One support", &max_for_one, .1f, 1.f, "%.2f mm")) {
sample_config.max_length_for_one_support_point = scale_(max_for_one);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Maximal island length (longest voronoi path)\n"
"for support island by exactly one point.\n"
"Point will be on the longest path center");
if (float max_for_two = unscale<float>(sample_config.max_length_for_two_support_points); // [in mm]
ImGui::InputFloat("Two supports", &max_for_two, .1f, 1.f, "%.2f mm")) {
sample_config.max_length_for_two_support_points = scale_(max_for_two);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Maximal island length (longest voronoi path)\n"
"for support by 2 points on path sides\n"
"To stretch the island.");
if (float thin_max_width = unscale<float>(sample_config.thin_max_width); // [in mm]
ImGui::InputFloat("Thin max width", &thin_max_width, .1f, 1.f, "%.2f mm")) {
sample_config.thin_max_width = scale_(thin_max_width);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Maximal width of line island supported in the middle of line\n"
"Must be greater than thick min width(to make hysteresis)");
if (float thick_min_width = unscale<float>(sample_config.thick_min_width); // [in mm]
ImGui::InputFloat("Thick min width", &thick_min_width, .1f, 1.f, "%.2f mm")) {
sample_config.thick_min_width = scale_(thick_min_width);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Minimal width to be supported by outline\n"
"Must be smaller than thin max width(to make hysteresis)");
if (float max_distance = unscale<float>(sample_config.thin_max_distance); // [in mm]
ImGui::InputFloat("Thin max distance", &max_distance, .1f, 1.f, "%.2f mm")) {
sample_config.thin_max_distance = scale_(max_distance);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Maximal distance of supports on thin island's part");
if (float max_distance = unscale<float>(sample_config.thick_inner_max_distance); // [in mm]
ImGui::InputFloat("Thick inner max distance", &max_distance, .1f, 1.f, "%.2f mm")) {
sample_config.thick_inner_max_distance = scale_(max_distance);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Maximal distance of supports inside thick island's part");
if (float max_distance = unscale<float>(sample_config.thick_outline_max_distance); // [in mm]
ImGui::InputFloat("Thick outline max distance", &max_distance, .1f, 1.f, "%.2f mm")) {
sample_config.thick_outline_max_distance = scale_(max_distance);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Maximal distance of supports on thick island's part outline");
if (float minimal_distance_from_outline = unscale<float>(sample_config.minimal_distance_from_outline); // [in mm]
ImGui::InputFloat("From outline", &minimal_distance_from_outline, .1f, 1.f, "%.2f mm")) {
sample_config.minimal_distance_from_outline = scale_(minimal_distance_from_outline);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("When it is possible, there will be this minimal distance from outline.\n"
"ZERO mean head center will lay on island outline\n"
"IMHO value should be bigger than head radius");
ImGui::SameLine();
if (float maximal_distance_from_outline = unscale<float>(sample_config.maximal_distance_from_outline); // [in mm]
ImGui::InputFloat("Max", &maximal_distance_from_outline, .1f, 1.f, "%.2f mm")) {
sample_config.maximal_distance_from_outline = scale_(maximal_distance_from_outline);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Measured as sum of VD edge length from outline\n"
"Used only when there is no space for outline offset on first/last point\n"
"Must be bigger than value 'From outline'");
if (float simplification_tolerance = unscale<float>(sample_config.simplification_tolerance); // [in mm]
ImGui::InputFloat("Simplify", &simplification_tolerance, .1f, 1.f, "%.2f mm")) {
sample_config.simplification_tolerance = scale_(simplification_tolerance);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("There is no need to calculate with precisse island Voronoi\n"
"NOTE: Slice of Cylinder bottom has tip of trinagles on contour\n"
"(neighbor coordinate -> create issue in boost::voronoi)\n"
"Bigger value will speed up");
ImGui::Text("Aligning termination criteria:");
if (ImGui::IsItemHovered())
ImGui::SetTooltip("After initial support placement on island, supports are aligned\n"
"to more uniformly support area of irregular island shape");
if (int count = static_cast<int>(sample_config.count_iteration);
ImGui::SliderInt("max iteration", &count, 0, 100, "%d loops" )){
sample_config.count_iteration = count;
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Align termination condition, max count of aligning calls");
if (float minimal_move = unscale<float>(sample_config.minimal_move); // [in mm]
ImGui::InputFloat("minimal move", &minimal_move, .1f, 1.f, "%.2f mm")) {
sample_config.minimal_move = scale_(minimal_move);
exist_change = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Align termination condition, when support points after align did not change their position more,\n"
"than this distance it is deduce that supports are aligned enough.\n"
"Bigger value mean speed up of aligning");
if (exist_change){
sla::SampleConfigFactory::verify(sample_config);
}
#ifdef OPTION_TO_STORE_ISLAND
bool store_islands = !sample_config.path.empty();
if (ImGui::Checkbox("StoreIslands", &store_islands)) {
if (store_islands == true)
sample_config.path = "C:/data/temp/island<<order>>.svg";
else
sample_config.path.clear();
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("Store islands into files\n<<order>> is replaced by island order number");
if (store_islands) {
ImGui::SameLine();
std::string path;
ImGui::InputText("path", &sample_config.path);
}
#endif // OPTION_TO_STORE_ISLAND
// end of tree node
ImGui::TreePop();
}
#endif // USE_ISLAND_GUI_FOR_SETTINGS
bool GLGizmoSlaSupports::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
@ -914,7 +1125,7 @@ void GLGizmoSlaSupports::on_dragging(const UpdateData &data)
{
assert(m_hover_id != -1);
if (!m_editing_mode) return;
if (m_editing_cache[m_hover_id].support_point.is_new_island && m_lock_unique_islands)
if (m_editing_cache[m_hover_id].support_point.is_island() && m_lock_unique_islands)
return;
std::pair<Vec3f, Vec3f> pos_and_normal;
@ -922,7 +1133,7 @@ void GLGizmoSlaSupports::on_dragging(const UpdateData &data)
return;
m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first;
m_editing_cache[m_hover_id].support_point.is_new_island = false;
m_editing_cache[m_hover_id].support_point.type = sla::SupportPointType::manual_add;
m_editing_cache[m_hover_id].normal = pos_and_normal.second;
}
@ -1021,7 +1232,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
mo->sla_support_points.clear();
mo->sla_support_points = m_normal_cache;
reslice_until_step(slaposPad);
reslice_until_step(m_show_support_structure ? slaposPad : slaposSupportPoints);
}
}
@ -1121,10 +1332,10 @@ void GLGizmoSlaSupports::get_data_from_backend()
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
if (po->model_object()->id() == mo->id()) {
m_normal_cache.clear();
const std::vector<sla::SupportPoint>& points = po->get_support_points();
auto mat = po->trafo().inverse().cast<float>();
for (unsigned int i=0; i<points.size();++i)
m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island));
auto mat = po->trafo().inverse().cast<float>(); // TODO: WTF trafo????? !!!!!!
for (const sla::SupportPoint &p : po->get_support_points())
m_normal_cache.emplace_back(sla::SupportPoint{mat * p.pos, p.head_front_radius, p.type});
mo->sla_points_status = sla::PointsStatus::AutoGenerated;
break;
@ -1138,27 +1349,18 @@ void GLGizmoSlaSupports::get_data_from_backend()
void GLGizmoSlaSupports::auto_generate()
{
//wxMessageDialog dlg(GUI::wxGetApp().plater(),
MessageDialog dlg(GUI::wxGetApp().plater(),
_L("Autogeneration will erase all manually edited points.") + "\n\n" +
_L("Are you sure you want to do it?") + "\n",
_L("Warning"), wxICON_WARNING | wxYES | wxNO);
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points"));
wxGetApp().CallAfter([this]() { reslice_until_step(
m_show_support_structure ? slaposPad : slaposSupportPoints); });
ModelObject* mo = m_c->selection_info()->model_object();
if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points"));
wxGetApp().CallAfter([this]() { reslice_until_step(slaposPad); });
mo->sla_points_status = sla::PointsStatus::Generating;
}
mo->sla_points_status = sla::PointsStatus::Generating;
}
void GLGizmoSlaSupports::switch_to_editing_mode()
{
wxGetApp().plater()->enter_gizmos_stack();
m_editing_mode = true;
show_sla_supports(false);
m_editing_cache.clear();
for (const sla::SupportPoint& sp : m_normal_cache)
m_editing_cache.emplace_back(sp);
@ -1172,6 +1374,7 @@ void GLGizmoSlaSupports::disable_editing_mode()
{
if (m_editing_mode) {
m_editing_mode = false;
show_sla_supports(m_show_support_structure);
wxGetApp().plater()->leave_gizmos_stack();
m_parent.set_as_dirty();
unregister_point_raycasters_for_picking();
@ -1308,11 +1511,20 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog()
gridsizer->Add(desc, -1, wxALIGN_CENTRE_VERTICAL);
}
std::vector<std::pair<std::string, wxString>> point_types;
point_types.push_back(std::make_pair("sphere_lightgray",_L("Generated point")));
point_types.push_back(std::make_pair("sphere_redish", _L("Selected support point")));
point_types.push_back(std::make_pair("sphere_orange", _L("User edited point")));
point_types.push_back(std::make_pair("sphere_blueish", _L("Island support point")));
point_types.push_back(std::make_pair("sphere_cyan", _L("Mouse hovered point")));
for (const auto &[icon_name, description] : point_types) {
auto desc = new wxStaticText(this, wxID_ANY, description);
desc->SetFont(font);
gridsizer->Add(new wxStaticBitmap(this, wxID_ANY, ScalableBitmap(this, icon_name).bmp()),
-1, wxALIGN_CENTRE_VERTICAL);
gridsizer->Add(desc, -1, wxALIGN_CENTRE_VERTICAL);
}
SetSizer(hsizer);
hsizer->SetSizeHints(this);
}
} // namespace GUI
} // namespace Slic3r

View File

@ -8,6 +8,7 @@
#include "GLGizmoSlaBase.hpp"
#include "slic3r/GUI/GLSelectionRectangle.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/IconManager.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/ObjectID.hpp"
@ -91,17 +92,21 @@ private:
void unregister_point_raycasters_for_picking();
void update_point_raycasters_for_picking_transform();
void draw_island_config();
bool m_show_support_structure = false;
bool m_lock_unique_islands = false;
bool m_editing_mode = false; // Is editing mode active?
float m_new_point_head_diameter; // Size of a new point.
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
float m_old_point_head_diameter = 0.; // the same
float m_minimal_point_distance_stash = 0.f; // and again
float m_density_stash = 0.f; // and again
mutable std::vector<CacheEntry> m_editing_cache; // a support point and whether it is currently selected
std::vector<sla::SupportPoint> m_normal_cache; // to restore after discarding changes or undo/redo
ObjectID m_old_mo_id;
IconManager m_icon_manager;
IconManager::Icons m_icons;
PickingModel m_sphere;
PickingModel m_cone;
std::vector<std::pair<std::shared_ptr<SceneRaycasterItem>, std::shared_ptr<SceneRaycasterItem>>> m_point_raycasters;

View File

@ -288,7 +288,7 @@ std::vector<IconManager::Icons> IconManager::init(const std::vector<std::string>
}
void IconManager::release() {
BOOST_LOG_TRIVIAL(error) << "Not implemented yet";
BOOST_LOG_TRIVIAL(warning) << "Not implemented yet";
}
void priv::clear(IconManager::Icons &icons) {

View File

@ -5953,7 +5953,6 @@ void TabSLAPrint::build()
optgroup = page->new_optgroup(L("Automatic generation"));
optgroup->append_single_option_line("support_points_density_relative");
optgroup->append_single_option_line("support_points_minimal_distance");
page = add_options_page(L("Pad"), "pad");
optgroup = page->new_optgroup(L("Pad"));

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg height="20.860270" width="22.311550" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<marker id="endArrow" markerHeight="8" markerUnits="strokeWidth" markerWidth="10" orient="auto" refX="1" refY="5" viewBox="0 0 10 10">
<polyline fill="darkblue" points="0,0 10,5 0,10 1,5" />
</marker>
<rect fill='white' stroke='none' x='0' y='0' width='22.311550' height='20.860270'/>
<path d="M 1716256 -11015182 1795391 -11035439 1568498 -10955916 1631011 -10984708 1564236 -10960836 1564236 -10960837 1674546 -11011825 1791112 -11041943 z " style="fill: none; stroke: black; stroke-width: 0.000010; fill-type: evenodd" fill-opacity="0.000000" />
<path d="M 11.5202 10.5927 12.3116 10.7952 10.0426 10 10.6677 10.2879 10 10.0492 10 10.0492 11.1031 10.5591 12.2688 10.8603 z " style="fill: lightgray; stroke: black; stroke-width: 0.000000; fill-type: evenodd" fill-opacity="1.000000" />
<path d="M 10 10.0492 12.0868 10.7952 11.5202 10.5927 12.3116 10.7952 z " style="fill: gray; stroke: black; stroke-width: 0.000000; fill-type: evenodd" fill-opacity="1.000000" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -86,6 +86,23 @@ TEST_CASE("Test kdtree query for a Box", "[KDTreeIndirect]")
REQUIRE(call_count < pgrid.point_count());
}
TEST_CASE("Test kdtree closests points", "[KDTreeIndirect]") {
Points pts{
Point{-9000000, 9000000},
Point{-9000000, -9000000},
Point{ 9000000, -9000000},
Point{ 9000000, 9000000},
Point{25, 25}
};
auto point_accessor = [&pts](size_t idx, size_t dim) -> coord_t & {
return pts[idx][dim];
};
KDTreeIndirect<2, coord_t, decltype(point_accessor)> tree(point_accessor, pts.size());
std::array<size_t, 5> closest = find_closest_points<5>(tree, Point{0, 0});
CHECK(closest[0] == 4);
}
//TEST_CASE("Test kdtree query for a Sphere", "[KDTreeIndirect]") {
// auto vol = BoundingBox3Base<Vec3f>{{0.f, 0.f, 0.f}, {10.f, 10.f, 10.f}};

View File

@ -31,6 +31,13 @@ TEST_CASE("Distance to line", "[Point]") {
CHECK(line.distance_to(Point{50, 50}) == Approx(50));
CHECK(line.perp_distance_to(Point{50, 50}) == Approx(50));
CHECK(line.perp_distance_to(Point{150, 50}) == Approx(50));
// possitive values are on the left side WRT line direction
CHECK(line.perp_signed_distance_to(Point{50, 50}) == Approx(50));
CHECK(line.perp_signed_distance_to(Point{50, -50}) == Approx(-50));
const Line line2{{0, 0}, {0, 100}};
CHECK(line2.perp_signed_distance_to(Point{50, 50}) == Approx(-50));
CHECK(line2.perp_signed_distance_to(Point{-50, 50}) == Approx(50));
}
TEST_CASE("Distance to diagonal line", "[Point]") {

View File

@ -2283,3 +2283,81 @@ TEST_CASE("Voronoi cell doesn't contain a source point - SPE-2298", "[VoronoiCel
REQUIRE(vd.is_valid());
}
// */
/*
//#include <libslic3r/SLA/SupportIslands/LineUtils.hpp>
TEST_CASE("bad vertex cause overflow of data type precisin when use VD result", "[VoronoiDiagram]")
{
// Points are almost in line
Points points = {
{-106641371, 61644934},
{-56376476, 32588892},
{0, 0}
};
//auto perp_distance = sla::LineUtils::perp_distance;
auto perp_distance = [](const Linef &line, Vec2d p) {
Vec2d v = line.b - line.a; // direction
Vec2d va = p - line.a;
return std::abs(cross2(v, va)) / v.norm();
};
using VD = Slic3r::Geometry::VoronoiDiagram;
VD vd;
vd.construct_voronoi(points.begin(), points.end());
// edge between source index 0 and 1 has bad vertex
size_t bad_index0 = 0;
size_t bad_index1 = 1;
for (auto &edge : vd.edges()) {
size_t i1 = edge.cell()->source_index();
size_t i2 = edge.twin()->cell()->source_index();
if ((i1 == bad_index0 && i2 == bad_index1) ||
(i1 == bad_index1 && i2 == bad_index0)) {
Vec2d p1 = points[bad_index0].cast<double>();
Vec2d p2 = points[bad_index1].cast<double>();
Vec2d middle = (p1 + p2) / 2;
// direction for edge is perpendicular point connection
Vec2d direction(p2.y() - p1.y(), p1.x() - p2.x());
const VD::vertex_type *vrtx = (edge.vertex0() == nullptr) ?
edge.vertex1() :
edge.vertex0();
if (vrtx == nullptr) continue;
Vec2d vertex(vrtx->x(), vrtx->y());
double point_distance = (p1 - p2).norm();
[[maybe_unused]] double half_point_distance = point_distance / 2;
Linef line_from_middle(middle, middle + direction); // line between source points
double distance_vertex = perp_distance(line_from_middle, vertex);
[[maybe_unused]] double distance_p1 = perp_distance(line_from_middle, p1);
[[maybe_unused]] double distance_p2 = perp_distance(line_from_middle, p2);
Linef line_from_vertex(vertex, vertex + direction);
[[maybe_unused]] double distance_middle = perp_distance(line_from_vertex, middle);
[[maybe_unused]] double distance_p1_ = perp_distance(line_from_vertex, p1);
[[maybe_unused]] double distance_p2_ = perp_distance(line_from_vertex, p2);
double maximal_distance = 9e6;
Vec2d vertex_direction = (vertex - middle);
Vec2d vertex_dir_abs(fabs(vertex_direction.x()),
fabs(vertex_direction.y()));
double divider = (vertex_dir_abs.x() > vertex_dir_abs.y()) ?
vertex_dir_abs.x() / maximal_distance :
vertex_dir_abs.y() / maximal_distance;
Vec2d vertex_dir_short = vertex_direction / divider;
Vec2d start_point = middle + vertex_dir_short;
Linef line_short(start_point, start_point + direction);
double distance_short_vertex = perp_distance(line_short, vertex);
double distance_short_middle = perp_distance(line_short, middle);
[[maybe_unused]] double distance_p1_short = perp_distance(line_short, p1);
[[maybe_unused]] double distance_p2_short = perp_distance(line_short, p2);
CHECK(distance_vertex < 10);
//CHECK(distance_middle < 10); // This is bad
CHECK(distance_short_vertex < 10);
CHECK(distance_short_middle < 10);
}
}
}*/

View File

@ -4,6 +4,10 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
sla_test_utils.hpp sla_test_utils.cpp
sla_supptgen_tests.cpp
sla_raycast_tests.cpp
sla_parabola_tests.cpp
sla_voronoi_graph_tests.cpp
sla_vectorUtils_tests.cpp
sla_lineUtils_tests.cpp
sla_supptreeutils_tests.cpp
sla_archive_readwrite_tests.cpp
sla_zcorrection_tests.cpp)
@ -18,3 +22,11 @@ endif()
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})
if (WIN32)
# Adds a post-build copy of libgmp-10.dll
add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_PREFIX_PATH}/bin/libgmp-10.dll"
$<TARGET_FILE_DIR:${_TEST_NAME}_tests>)
endif()

View File

@ -0,0 +1,59 @@
#include <catch2/catch_test_macros.hpp>
#include <libslic3r/SLA/SupportIslands/LineUtils.hpp>
using namespace Slic3r;
using namespace Slic3r::sla;
TEST_CASE("Intersection point", "[Utils], [LineUtils]")
{
Point a1(0, 0);
Point b1(3, 6);
Line l1(a1, b1);
auto intersection = LineUtils::intersection(l1, Line(Point(0, 4),
Point(5, 4)));
CHECK(intersection.has_value());
Point i_point = intersection->cast<coord_t>();
CHECK(PointUtils::is_equal(i_point, Point(2, 4)));
// same line
auto bad_intersection = LineUtils::intersection(l1, l1);
CHECK(!bad_intersection.has_value());
// oposit direction
bad_intersection = LineUtils::intersection(l1, Line(b1, a1));
CHECK(!bad_intersection.has_value());
// parallel line
bad_intersection = LineUtils::intersection(l1, Line(a1 + Point(0, 1),
b1 + Point(0, 1)));
CHECK(!bad_intersection.has_value());
// out of line segment, but ray has intersection
Line l2(Point(0, 8), Point(6, 8));
intersection = LineUtils::intersection(l1, l2);
auto intersection2 = LineUtils::intersection(l2, l1);
CHECK(intersection.has_value());
CHECK(intersection2.has_value());
i_point = intersection->cast<coord_t>();
CHECK(PointUtils::is_equal(i_point, Point(4, 8)));
CHECK(PointUtils::is_equal(i_point, intersection2->cast<coord_t>()));
Line l3(Point(-2, -2), Point(1, -2));
intersection = LineUtils::intersection(l1, l3);
intersection2 = LineUtils::intersection(l3, l1);
CHECK(intersection.has_value());
CHECK(intersection2.has_value());
i_point = intersection->cast<coord_t>();
CHECK(PointUtils::is_equal(i_point, Point(-1, -2)));
CHECK(PointUtils::is_equal(i_point, intersection2->cast<coord_t>()));
}
TEST_CASE("Point belongs to line", "[Utils], [LineUtils]")
{
Line l(Point(10, 10), Point(50, 30));
CHECK(LineUtils::belongs(l, Point(30, 20)));
CHECK(!LineUtils::belongs(l, Point(30, 30)));
CHECK(LineUtils::belongs(l, Point(30, 30), 10.));
CHECK(!LineUtils::belongs(l, Point(30, 10)));
CHECK(!LineUtils::belongs(l, Point(70, 40)));
}

View File

@ -0,0 +1,50 @@
#include "sla_test_utils.hpp"
#include <libslic3r/SLA/SupportIslands/ParabolaUtils.hpp>
using namespace Slic3r;
using namespace Slic3r::sla;
void parabola_check_length(const ParabolaSegment &parabola)
{
auto diffPoint = parabola.to - parabola.from;
double min = sqrt(diffPoint.x() * diffPoint.x() +
diffPoint.y() * diffPoint.y());
double max = static_cast<double>(diffPoint.x()) + diffPoint.y();
double len = ParabolaUtils::length(parabola);
double len2 = ParabolaUtils::length_by_sampling(parabola, 1.);
CHECK(fabs(len2 - len) < 1.);
CHECK(len >= min);
CHECK(len <= max);
}
// after generalization put to ParabolaUtils
double getParabolaY(const Parabola &parabola, double x)
{
double f = ParabolaUtils::focal_length(parabola);
Vec2d perp = parabola.directrix.normal().cast<double>();
// work only for test cases
if (perp.y() > 0.) perp *= -1.;
perp.normalize();
Vec2d v = parabola.focus.cast<double>() + perp * f;
return 1 / (4 * f) * (x - v.x()) * (x - v.x()) + v.y();
}
TEST_CASE("Parabola length", "[SupGen][Voronoi][Parabola]")
{
using namespace Slic3r::sla;
double scale = 1e6;
// U shape parabola
Parabola parabola_x2(Line({-1. * scale, -.25 * scale},
{1. * scale, -.25 * scale}),
Point(0. * scale, .25 * scale));
double from_x = 1 * scale;
double to_x = 3 * scale;
Point from(from_x, getParabolaY(parabola_x2, from_x));
Point to(to_x, getParabolaY(parabola_x2, to_x));
ParabolaSegment parabola_segment(parabola_x2, from, to);
parabola_check_length(parabola_segment);
}

View File

@ -31,52 +31,6 @@ const char *const SUPPORT_TEST_MODELS[] = {
} // namespace
TEST_CASE("Support point generator should be deterministic if seeded",
"[SLASupportGeneration], [SLAPointGen]") {
TriangleMesh mesh = load_model("A_upsidedown.obj");
AABBMesh emesh{mesh};
sla::SupportTreeConfig supportcfg;
sla::SupportPointGenerator::Config autogencfg;
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}};
auto bb = mesh.bounding_box();
double zmin = bb.min.z();
double zmax = bb.max.z();
double gnd = zmin - supportcfg.object_elevation_mm;
auto layer_h = 0.05f;
auto slicegrid = grid(float(gnd), float(zmax), layer_h);
std::vector<ExPolygons> slices = slice_mesh_ex(mesh.its, slicegrid, CLOSING_RADIUS);
point_gen.seed(0);
point_gen.execute(slices, slicegrid);
auto get_chksum = [](const std::vector<sla::SupportPoint> &pts){
int64_t chksum = 0;
for (auto &pt : pts) {
auto p = scaled(pt.pos);
chksum += p.x() + p.y() + p.z();
}
return chksum;
};
int64_t checksum = get_chksum(point_gen.output());
size_t ptnum = point_gen.output().size();
REQUIRE(point_gen.output().size() > 0);
for (int i = 0; i < 20; ++i) {
point_gen.output().clear();
point_gen.seed(0);
point_gen.execute(slices, slicegrid);
REQUIRE(point_gen.output().size() == ptnum);
REQUIRE(checksum == get_chksum(point_gen.output()));
}
}
TEST_CASE("Flat pad geometry is valid", "[SLASupportGeneration]") {
sla::PadConfig padcfg;

View File

@ -4,17 +4,29 @@
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/BoundingBox.hpp>
#include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/TriangleMeshSlicer.hpp>
#include <libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp>
#include <libslic3r/SLA/SupportIslands/SampleConfig.hpp>
#include <libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp>
#include <libslic3r/SLA/SupportIslands/UniformSupportIsland.hpp>
#include <libslic3r/SLA/SupportIslands/PolygonUtils.hpp>
#include "nanosvg/nanosvg.h" // load SVG file
#include "sla_test_utils.hpp"
namespace Slic3r { namespace sla {
using namespace Slic3r;
using namespace Slic3r::sla;
//#define STORE_SAMPLE_INTO_SVG_FILES "C:/data/temp/test_islands/sample_"
//#define STORE_ISLAND_ISSUES "C:/data/temp/issues/"
TEST_CASE("Overhanging point should be supported", "[SupGen]") {
// Pyramid with 45 deg slope
TriangleMesh mesh = make_pyramid(10.f, 10.f);
mesh.rotate_y(float(PI));
mesh.WriteOBJFile("Pyramid.obj");
//mesh.WriteOBJFile("Pyramid.obj");
sla::SupportPoints pts = calc_support_pts(mesh);
@ -54,18 +66,11 @@ double min_point_distance(const sla::SupportPoints &pts)
TEST_CASE("Overhanging horizontal surface should be supported", "[SupGen]") {
double width = 10., depth = 10., height = 1.;
TriangleMesh mesh = make_cube(width, depth, height);
TriangleMesh mesh = make_cube(width, depth, height);
mesh.translate(0., 0., 5.); // lift up
mesh.WriteOBJFile("Cuboid.obj");
sla::SupportPointGenerator::Config cfg;
sla::SupportPoints pts = calc_support_pts(mesh, cfg);
double mm2 = width * depth;
// mesh.WriteOBJFile("Cuboid.obj");
sla::SupportPoints pts = calc_support_pts(mesh);
REQUIRE(!pts.empty());
REQUIRE(pts.size() * cfg.support_force() > mm2 * cfg.tear_pressure());
REQUIRE(min_point_distance(pts) >= cfg.minimal_distance);
}
template<class M> auto&& center_around_bb(M &&mesh)
@ -84,8 +89,7 @@ TEST_CASE("Overhanging edge should be supported", "[SupGen]") {
mesh.translate(0., 0., height);
mesh.WriteOBJFile("Prism.obj");
sla::SupportPointGenerator::Config cfg;
sla::SupportPoints pts = calc_support_pts(mesh, cfg);
sla::SupportPoints pts = calc_support_pts(mesh);
Linef3 overh{ {0.f, -depth / 2.f, 0.f}, {0.f, depth / 2.f, 0.f}};
@ -97,9 +101,8 @@ TEST_CASE("Overhanging edge should be supported", "[SupGen]") {
return line_alg::distance_to(overh, Vec3d{pt.pos.cast<double>()}) < 1.;
});
REQUIRE(overh_pts.size() * cfg.support_force() > overh.length() * cfg.tear_pressure());
double ddiff = min_point_distance(pts) - cfg.minimal_distance;
REQUIRE(ddiff > - 0.1 * cfg.minimal_distance);
//double ddiff = min_point_distance(pts) - cfg.minimal_distance;
//REQUIRE(ddiff > - 0.1 * cfg.minimal_distance);
}
TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowed]") {
@ -114,9 +117,8 @@ TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowe
Vec3f mv = bb.center().cast<float>() - Vec3f{0.f, 0.f, 0.5f * h};
mesh.translate(-mv);
sla::SupportPointGenerator::Config cfg;
sla::SupportPoints pts = calc_support_pts(mesh, cfg);
sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON);
sla::SupportPoints pts = calc_support_pts(mesh);
//sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON);
REQUIRE(!pts.empty());
}
@ -132,11 +134,471 @@ TEST_CASE("Two parallel plates should be supported", "[SupGen][Hollowed]")
mesh.WriteOBJFile("parallel_plates.obj");
sla::SupportPointGenerator::Config cfg;
sla::SupportPoints pts = calc_support_pts(mesh, cfg);
sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON);
sla::SupportPoints pts = calc_support_pts(mesh);
//sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON);
REQUIRE(!pts.empty());
}
}} // namespace Slic3r::sla
Slic3r::Polygon create_cross_roads(double size, double width)
{
auto r1 = PolygonUtils::create_rect(5.3 * size, width);
r1.rotate(3.14/4);
r1.translate(2 * size, width / 2);
auto r2 = PolygonUtils::create_rect(6.1 * size, 3 / 4. * width);
r2.rotate(-3.14 / 5);
r2.translate(3 * size, width / 2);
auto r3 = PolygonUtils::create_rect(7.9 * size, 4 / 5. * width);
r3.translate(2*size, width/2);
auto r4 = PolygonUtils::create_rect(5 / 6. * width, 5.7 * size);
r4.translate(-size,3*size);
Polygons rr = union_(Polygons({r1, r2, r3, r4}));
return rr.front();
}
ExPolygon create_trinagle_with_hole(double size)
{
auto hole = PolygonUtils::create_equilateral_triangle(size / 3);
hole.reverse();
hole.rotate(3.14 / 4);
return ExPolygon(PolygonUtils::create_equilateral_triangle(size), hole);
}
ExPolygon create_square_with_hole(double size, double hole_size)
{
assert(sqrt(hole_size *hole_size / 2) < size);
auto hole = PolygonUtils::create_square(hole_size);
hole.rotate(M_PI / 4.); // 45
hole.reverse();
return ExPolygon(PolygonUtils::create_square(size), hole);
}
ExPolygon create_square_with_4holes(double size, double hole_size) {
auto hole = PolygonUtils::create_square(hole_size);
hole.reverse();
double size_4 = size / 4;
auto h1 = hole;
h1.translate(size_4, size_4);
auto h2 = hole;
h2.translate(-size_4, size_4);
auto h3 = hole;
h3.translate(size_4, -size_4);
auto h4 = hole;
h4.translate(-size_4, -size_4);
ExPolygon result(PolygonUtils::create_square(size));
result.holes = Polygons({h1, h2, h3, h4});
return result;
}
// boudary of circle
ExPolygon create_disc(double radius, double width, size_t count_line_segments)
{
double width_2 = width / 2;
auto hole = PolygonUtils::create_circle(radius - width_2,
count_line_segments);
hole.reverse();
return ExPolygon(PolygonUtils::create_circle(radius + width_2,
count_line_segments),
hole);
}
Slic3r::Polygon create_V_shape(double height, double line_width, double angle = M_PI/4) {
double angle_2 = angle / 2;
auto left_side = PolygonUtils::create_rect(line_width, height);
auto right_side = left_side;
right_side.rotate(-angle_2);
double small_move = cos(angle_2) * line_width / 2;
double side_move = sin(angle_2) * height / 2 + small_move;
right_side.translate(side_move,0);
left_side.rotate(angle_2);
left_side.translate(-side_move, 0);
auto bottom = PolygonUtils::create_rect(4 * small_move, line_width);
bottom.translate(0., -cos(angle_2) * height / 2 + line_width/2);
Polygons polygons = union_(Polygons({left_side, right_side, bottom}));
return polygons.front();
}
ExPolygon create_tiny_wide_test_1(double wide, double tiny)
{
double hole_size = wide;
double width = 2 * wide + hole_size;
double height = wide + hole_size + tiny;
auto outline = PolygonUtils::create_rect(width, height);
auto hole = PolygonUtils::create_rect(hole_size, hole_size);
hole.reverse();
int hole_move_y = height/2 - (hole_size/2 + tiny);
hole.translate(0, hole_move_y);
ExPolygon result(outline);
result.holes = {hole};
return result;
}
ExPolygon create_tiny_wide_test_2(double wide, double tiny)
{
double hole_size = wide;
double width = (3 + 1) * wide + 3 * hole_size;
double height = 2*wide + 2*tiny + 3*hole_size;
auto outline = PolygonUtils::create_rect( width, height);
auto hole = PolygonUtils::create_rect(hole_size, hole_size);
hole.reverse();
auto hole2 = hole;// copy
auto hole3 = hole; // copy
auto hole4 = hole; // copy
int hole_move_x = wide + hole_size;
int hole_move_y = wide + hole_size;
hole.translate(hole_move_x, hole_move_y);
hole2.translate(-hole_move_x, hole_move_y);
hole3.translate(hole_move_x, -hole_move_y);
hole4.translate(-hole_move_x, -hole_move_y);
auto hole5 = PolygonUtils::create_circle(hole_size / 2, 16);
hole5.reverse();
auto hole6 = hole5; // copy
hole5.translate(0, hole_move_y);
hole6.translate(0, -hole_move_y);
auto hole7 = PolygonUtils::create_equilateral_triangle(hole_size);
hole7.reverse();
auto hole8 = PolygonUtils::create_circle(hole_size/2, 7, Point(hole_move_x,0));
hole8.reverse();
auto hole9 = PolygonUtils::create_circle(hole_size/2, 5, Point(-hole_move_x,0));
hole9.reverse();
ExPolygon result(outline);
result.holes = {hole, hole2, hole3, hole4, hole5, hole6, hole7, hole8, hole9};
return result;
}
ExPolygon create_tiny_between_holes(double wide, double tiny)
{
double hole_size = wide;
double width = 2 * wide + 2*hole_size + tiny;
double height = 2 * wide + hole_size;
auto outline = PolygonUtils::create_rect(width, height);
auto holeL = PolygonUtils::create_rect(hole_size, hole_size);
holeL.reverse();
auto holeR = holeL;
int hole_move_x = (hole_size + tiny)/2;
holeL.translate(-hole_move_x, 0);
holeR.translate(hole_move_x, 0);
ExPolygon result(outline);
result.holes = {holeL, holeR};
return result;
}
// stress test for longest path
// needs reshape
ExPolygon create_mountains(double size) {
return ExPolygon({{0., 0.},
{size, 0.},
{5 * size / 6, size},
{4 * size / 6, size / 6},
{3 * size / 7, 2 * size},
{2 * size / 7, size / 6},
{size / 7, size}});
}
/// Neighbor points create trouble for voronoi - test of neccessary offseting(closing) of contour
ExPolygon create_cylinder_bottom_slice() {
indexed_triangle_set its_cylinder = its_make_cylinder(6.6551999999999998, 11.800000000000001);
MeshSlicingParams param;
Polygons polygons = slice_mesh(its_cylinder, 0.0125000002, param);
return ExPolygon{polygons.front()};
}
ExPolygon load_frog(){
TriangleMesh mesh = load_model("frog_legs.obj");
std::vector<ExPolygons> slices = slice_mesh_ex(mesh.its, {0.1f});
return slices.front()[1];
}
ExPolygon load_svg(const std::string& svg_filepath) {
struct NSVGimage *image = nsvgParseFromFile(svg_filepath.c_str(), "px", 96);
ScopeGuard sg_image([&image] { nsvgDelete(image); });
auto to_polygon = [](NSVGpath *path) {
Polygon r;
r.points.reserve(path->npts);
for (int i = 0; i < path->npts; i++)
r.points.push_back(Point(path->pts[2 * i], path->pts[2 * i + 1]));
return r;
};
for (NSVGshape *shape_ptr = image->shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next) {
const NSVGshape &shape = *shape_ptr;
if (!(shape.flags & NSVG_FLAGS_VISIBLE)) continue; // is visible
if (shape.fill.type != NSVG_PAINT_NONE) continue; // is not used fill
if (shape.stroke.type == NSVG_PAINT_NONE) continue; // exist stroke
//if (shape.strokeWidth < 1e-5f) continue; // is visible stroke width
//if (shape.stroke.color != 4278190261) continue; // is red
ExPolygon result;
for (NSVGpath *path = shape.paths; path != NULL; path = path->next) {
// Path order is reverse to path in file
if (path->next == NULL) // last path is contour
result.contour = to_polygon(path);
else
result.holes.push_back(to_polygon(path));
}
return result;
}
REQUIRE(false);
return {};
}
ExPolygons createTestIslands(double size)
{
std::string dir = std::string(TEST_DATA_DIR PATH_SEPARATOR) + "sla_islands/";
bool useFrogLeg = false;
// need post reorganization of longest path
ExPolygons result = {
// one support point
ExPolygon(PolygonUtils::create_equilateral_triangle(size)),
ExPolygon(PolygonUtils::create_square(size)),
ExPolygon(PolygonUtils::create_rect(size / 2, size)),
ExPolygon(PolygonUtils::create_isosceles_triangle(size / 2, 3 * size / 2)), // small sharp triangle
ExPolygon(PolygonUtils::create_circle(size / 2, 10)),
create_square_with_4holes(size, size / 4),
create_disc(size/4, size / 4, 10),
ExPolygon(create_V_shape(2*size/3, size / 4)),
// two support points
ExPolygon(PolygonUtils::create_isosceles_triangle(size / 2, 3 * size)), // small sharp triangle
ExPolygon(PolygonUtils::create_rect(size / 2, 3 * size)),
ExPolygon(create_V_shape(1.5*size, size/3)),
// tiny line support points
ExPolygon(PolygonUtils::create_rect(size / 2, 10 * size)), // long line
ExPolygon(create_V_shape(size*4, size / 3)),
ExPolygon(create_cross_roads(size, size / 3)),
create_disc(3*size, size / 4, 30),
create_disc(2*size, size, 12), // 3 points
create_square_with_4holes(5 * size, 5 * size / 2 - size / 3),
// Tiny and wide part together with holes
ExPolygon(PolygonUtils::create_isosceles_triangle(5. * size, 40. * size)),
create_tiny_wide_test_1(3 * size, 2 / 3. * size),
create_tiny_wide_test_2(3 * size, 2 / 3. * size),
create_tiny_between_holes(3 * size, 2 / 3. * size),
ExPolygon(PolygonUtils::create_equilateral_triangle(scale_(18.6))),
create_cylinder_bottom_slice(),
load_svg(dir + "lm_issue.svg"), // change from thick to thin and vice versa on circle
load_svg(dir + "SPE-2674.svg"), // center of longest path lay inside of the VD node
load_svg(dir + "SPE-2674_2.svg"), // missing Voronoi vertex even after the rotation of input.
// still problem
// three support points
ExPolygon(PolygonUtils::create_equilateral_triangle(3 * size)),
ExPolygon(PolygonUtils::create_circle(size, 20)),
create_mountains(size),
create_trinagle_with_hole(size),
create_square_with_hole(size, size / 2),
create_square_with_hole(size, size / 3)
};
if (useFrogLeg)
result.push_back(load_frog());
return result;
}
Points createNet(const BoundingBox& bounding_box, double distance)
{
Point size = bounding_box.size();
double distance_2 = distance / 2;
int cols1 = static_cast<int>(floor(size.x() / distance))+1;
int cols2 = static_cast<int>(floor((size.x() - distance_2) / distance))+1;
// equilateral triangle height with side distance
double h = sqrt(distance * distance - distance_2 * distance_2);
int rows = static_cast<int>(floor(size.y() / h)) +1;
int rows_2 = rows / 2;
size_t count_points = rows_2 * (cols1 + static_cast<size_t>(cols2));
if (rows % 2 == 1) count_points += cols2;
Points result;
result.reserve(count_points);
bool isOdd = true;
Point offset = bounding_box.min;
double x_max = offset.x() + static_cast<double>(size.x());
double y_max = offset.y() + static_cast<double>(size.y());
for (double y = offset.y(); y <= y_max; y += h) {
double x_offset = offset.x();
if (isOdd) x_offset += distance_2;
isOdd = !isOdd;
for (double x = x_offset; x <= x_max; x += distance) {
result.emplace_back(x, y);
}
}
assert(result.size() == count_points);
return result;
}
// create uniform triangle net and return points laying inside island
Points rasterize(const ExPolygon &island, double distance) {
BoundingBox bb;
for (const Point &pt : island.contour.points) bb.merge(pt);
Points fullNet = createNet(bb, distance);
Points result;
result.reserve(fullNet.size());
std::copy_if(fullNet.begin(), fullNet.end(), std::back_inserter(result),
[&island](const Point &p) { return island.contains(p); });
return result;
}
SupportIslandPoints test_island_sampling(const ExPolygon & island,
const SampleConfig &config)
{
auto points = uniform_support_island(island, {}, config);
Points chck_points = rasterize(island, config.head_radius); // TODO: Use resolution of printer
bool is_island_supported = true; // Check rasterized island points that exist support point in max_distance
double max_distance = config.thick_inner_max_distance;
std::vector<double> point_distances(chck_points.size(), {max_distance + 1});
for (size_t index = 0; index < chck_points.size(); ++index) {
const Point &chck_point = chck_points[index];
double &min_distance = point_distances[index];
bool exist_close_support_point = false;
for (const auto &island_point : points) {
const Point& p = island_point->point;
Point abs_diff(fabs(p.x() - chck_point.x()),
fabs(p.y() - chck_point.y()));
if (abs_diff.x() < min_distance && abs_diff.y() < min_distance) {
double distance = sqrt((double) abs_diff.x() * abs_diff.x() +
(double) abs_diff.y() * abs_diff.y());
if (min_distance > distance) {
min_distance = distance;
exist_close_support_point = true;
}
}
}
if (!exist_close_support_point) is_island_supported = false;
}
bool is_all_points_inside_island = true;
for (const auto &point : points)
if (!island.contains(point->point))
is_all_points_inside_island = false;
#ifdef STORE_ISLAND_ISSUES
if (!is_island_supported || !is_all_points_inside_island) { // visualize
static int counter = 0;
BoundingBox bb;
for (const Point &pt : island.contour.points) bb.merge(pt);
SVG svg(STORE_ISLAND_ISSUES + std::string("Error") + std::to_string(++counter) + ".svg", bb);
svg.draw(island, "blue", 0.5f);
for (auto& p : points)
svg.draw(p->point, island.contains(p->point) ? "lightgreen" : "red", config.head_radius);
for (size_t index = 0; index < chck_points.size(); ++index) {
const Point &chck_point = chck_points[index];
double distance = point_distances[index];
bool isOk = distance < max_distance;
std::string color = (isOk) ? "gray" : "red";
svg.draw(chck_point, color, config.head_radius / 4);
}
}
#endif // STORE_ISLAND_ISSUES
CHECK(!points.empty());
CHECK(is_all_points_inside_island);
// CHECK(is_island_supported); // TODO: skip special cases with one point and 2 points
return points;
}
SampleConfig create_sample_config(double size) {
float head_diameter = .4f;
return SampleConfigFactory::create(head_diameter);
//coord_t max_distance = 3 * size + 0.1;
//SampleConfig cfg;
//cfg.head_radius = size / 4;
//cfg.minimal_distance_from_outline = cfg.head_radius;
//cfg.maximal_distance_from_outline = max_distance/4;
//cfg.max_length_for_one_support_point = 2*size;
//cfg.max_length_for_two_support_points = 4*size;
//cfg.thin_max_width = size;
//cfg.thick_min_width = cfg.thin_max_width;
//cfg.thick_outline_max_distance = max_distance;
//cfg.minimal_move = static_cast<coord_t>(size/30);
//cfg.count_iteration = 100;
//cfg.max_align_distance = 0;
//return cfg;
}
#ifdef STORE_SAMPLE_INTO_SVG_FILES
namespace {
void store_sample(const SupportIslandPoints &samples, const ExPolygon &island) {
static int counter = 0;
BoundingBox bb(island.contour.points);
SVG svg((STORE_SAMPLE_INTO_SVG_FILES + std::to_string(counter++) + ".svg").c_str(), bb);
double mm = scale_(1);
svg.draw(island, "lightgray");
for (const auto &s : samples)
svg.draw(s->point, "blue", 0.2*mm);
// draw resolution
Point p(bb.min.x() + 1e6, bb.max.y() - 2e6);
svg.draw_text(p, (std::to_string(samples.size()) + " samples").c_str(), "black");
svg.draw_text(p - Point(0., 1.8e6), "Scale 1 cm ", "black");
Point start = p - Point(0., 2.3e6);
svg.draw(Line(start + Point(0., 5e5), start + Point(10*mm, 5e5)), "black", 2e5);
svg.draw(Line(start + Point(0., -5e5), start + Point(10*mm, -5e5)), "black", 2e5);
svg.draw(Line(start + Point(10*mm, 5e5), start + Point(10*mm, -5e5)), "black", 2e5);
for (int i=0; i<10;i+=2)
svg.draw(Line(start + Point(i*mm, 0.), start + Point((i+1)*mm, 0.)), "black", 1e6);
}
} // namespace
#endif // STORE_SAMPLE_INTO_SVG_FILES
/// <summary>
/// Check for correct sampling of island
/// </summary>
TEST_CASE("Uniform sample test islands", "[SupGen], [VoronoiSkeleton]")
{
//set_logging_level(5);
float head_diameter = .4f;
SampleConfig cfg = SampleConfigFactory::create(head_diameter);
//cfg.path = "C:/data/temp/islands/<<order>>.svg";
ExPolygons islands = createTestIslands(7 * scale_(head_diameter));
for (ExPolygon &island : islands) {
// information for debug which island cause problem
[[maybe_unused]] size_t debug_index = &island - &islands.front();
SupportIslandPoints points = test_island_sampling(island, cfg);
#ifdef STORE_SAMPLE_INTO_SVG_FILES
store_sample(points, island);
#endif // STORE_SAMPLE_INTO_SVG_FILES
double angle = 3.14 / 3; // cca 60 degree
island.rotate(angle);
SupportIslandPoints pointsR = test_island_sampling(island, cfg);
#ifdef STORE_SAMPLE_INTO_SVG_FILES
store_sample(pointsR, island);
#endif // STORE_SAMPLE_INTO_SVG_FILES
// points count should be the same
//CHECK(points.size() == pointsR.size())
}
}
TEST_CASE("Disable visualization", "[hide]")
{
CHECK(true);
#ifdef STORE_SAMPLE_INTO_SVG_FILES
CHECK(false);
#endif // STORE_SAMPLE_INTO_SVG_FILES
#ifdef STORE_ISLAND_ISSUES
CHECK(false);
#endif // STORE_ISLAND_ISSUES
#ifdef USE_ISLAND_GUI_FOR_SETTINGS
CHECK(false);
#endif // USE_ISLAND_GUI_FOR_SETTINGS
CHECK(is_uniform_support_island_visualization_disabled());
}

View File

@ -128,29 +128,27 @@ void test_supports(const std::string &obj_filename,
// TODO: do the cgal hole cutting...
// Create the support point generator
sla::SupportPointGenerator::Config autogencfg;
autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm);
sla::SupportPointGenerator point_gen{sm.emesh, autogencfg, [] {}, [](int) {}};
point_gen.seed(0); // Make the test repeatable
point_gen.execute(out.model_slices, out.slicegrid);
sla::SupportPointGeneratorConfig autogencfg;
sla::SupportPointGeneratorData gen_data = sla::prepare_generator_data(std::move(out.model_slices), out.slicegrid);
sla::LayerSupportPoints layer_support_points = sla::generate_support_points(gen_data, autogencfg);
double allowed_move = (out.slicegrid[1] - out.slicegrid[0]) + std::numeric_limits<float>::epsilon();
// Get the calculated support points.
sm.pts = point_gen.output();
sm.pts = sla::move_on_mesh_surface(layer_support_points, sm.emesh, allowed_move);
out.model_slices = std::move(gen_data.slices); // return ownership
int validityflags = ASSUME_NO_REPAIR;
// If there is no elevation, support points shall be removed from the
// bottom of the object.
if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
sla::remove_bottom_points(sm.pts, zmin + supportcfg.base_height_mm);
} else {
// Should be support points at least on the bottom of the model
REQUIRE_FALSE(sm.pts.empty());
//if (std::abs(supportcfg.object_elevation_mm) < EPSILON) {
// sla::remove_bottom_points(sm.pts, zmin + supportcfg.base_height_mm);
//} else {
// // Should be support points at least on the bottom of the model
// REQUIRE_FALSE(sm.pts.empty());
// Also the support mesh should not be empty.
validityflags |= ASSUME_NO_EMPTY;
}
// // Also the support mesh should not be empty.
// validityflags |= ASSUME_NO_EMPTY;
//}
// Generate the actual support tree
sla::SupportTreeBuilder treebuilder;
@ -465,7 +463,7 @@ double predict_error(const ExPolygon &p, const sla::PixelDim &pd)
sla::SupportPoints calc_support_pts(
const TriangleMesh & mesh,
const sla::SupportPointGenerator::Config &cfg)
const sla::SupportPointGeneratorConfig &cfg)
{
// Prepare the slice grid and the slices
auto bb = cast<float>(mesh.bounding_box());
@ -473,12 +471,11 @@ sla::SupportPoints calc_support_pts(
std::vector<ExPolygons> slices = slice_mesh_ex(mesh.its, heights, CLOSING_RADIUS);
// Prepare the support point calculator
sla::SupportPointGeneratorData gen_data = sla::prepare_generator_data(std::move(slices), heights);
sla::LayerSupportPoints layer_support_points = sla::generate_support_points(gen_data, cfg);
AABBMesh emesh{mesh};
sla::SupportPointGenerator spgen{emesh, cfg, []{}, [](int){}};
// Calculate the support points
spgen.seed(0);
spgen.execute(slices, heights);
return spgen.output();
double allowed_move = (heights[1] - heights[0]) + std::numeric_limits<float>::epsilon();
// Get the calculated support points.
return sla::move_on_mesh_surface(layer_support_points, emesh, allowed_move);
}

View File

@ -136,6 +136,6 @@ double predict_error(const ExPolygon &p, const sla::PixelDim &pd);
sla::SupportPoints calc_support_pts(
const TriangleMesh & mesh,
const sla::SupportPointGenerator::Config &cfg = {});
const sla::SupportPointGeneratorConfig &cfg = {});
#endif // SLA_TEST_UTILS_HPP

View File

@ -0,0 +1,25 @@
#include <catch2/catch_test_macros.hpp>
#include <libslic3r/SLA/SupportIslands/VectorUtils.hpp>
using namespace Slic3r::sla;
TEST_CASE("Reorder", "[Utils], [VectorUtils]")
{
std::vector<int> data{0, 1, 3, 2, 4, 7, 6, 5, 8};
std::vector<int> order{0, 1, 3, 2, 4, 7, 6, 5, 8};
VectorUtils::reorder(order.begin(), order.end(), data.begin());
for (size_t i = 0; i < data.size() - 1; ++i) {
CHECK(data[i] < data[i + 1]);
}
}
TEST_CASE("Reorder destructive", "[Utils], [VectorUtils]"){
std::vector<int> data {0, 1, 3, 2, 4, 7, 6, 5, 8};
std::vector<int> order{0, 1, 3, 2, 4, 7, 6, 5, 8};
VectorUtils::reorder_destructive(order.begin(), order.end(), data.begin());
for (size_t i = 0; i < data.size() - 1;++i) {
CHECK(data[i] < data[i + 1]);
}
}

View File

@ -0,0 +1,91 @@
#include "sla_test_utils.hpp"
#include <libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp>
#include <libslic3r/Geometry/VoronoiVisualUtils.hpp>
using namespace Slic3r;
using namespace Slic3r::sla;
TEST_CASE("Convert coordinate datatype", "[Voronoi]")
{
using VD = Slic3r::Geometry::VoronoiDiagram;
VD::coordinate_type coord = 101197493902.64694;
coord_t coord2 = VoronoiGraphUtils::to_coord(coord);
CHECK(coord2 > 100);
coord = -101197493902.64694;
coord2 = VoronoiGraphUtils::to_coord(coord);
CHECK(coord2 < -100);
coord = 12345.1;
coord2 = VoronoiGraphUtils::to_coord(coord);
CHECK(coord2 == 12345);
coord = -12345.1;
coord2 = VoronoiGraphUtils::to_coord(coord);
CHECK(coord2 == -12345);
coord = 12345.9;
coord2 = VoronoiGraphUtils::to_coord(coord);
CHECK(coord2 == 12346);
coord = -12345.9;
coord2 = VoronoiGraphUtils::to_coord(coord);
CHECK(coord2 == -12346);
}
void check(Slic3r::Points points, double max_distance) {
using VD = Slic3r::Geometry::VoronoiDiagram;
VD vd;
vd.construct_voronoi(points.begin(), points.end());
double max_area = M_PI * max_distance*max_distance; // circle = Pi * r^2
for (const VD::cell_type &cell : vd.cells()) {
Slic3r::Polygon polygon = VoronoiGraphUtils::to_polygon(cell, points, max_distance);
CHECK(polygon.area() < max_area);
CHECK(polygon.contains(points[cell.source_index()]));
}
}
TEST_CASE("Polygon from cell", "[Voronoi]")
{
// for debug #define SLA_SVG_VISUALIZATION_CELL_2_POLYGON in VoronoiGraphUtils
double max_distance = 1e7;
coord_t size = (int) (4e6);
coord_t half_size = size/2;
Slic3r::Points two_cols({Point(0, 0), Point(size, 0)});
check(two_cols, max_distance);
Slic3r::Points two_rows({Point(0, 0), Point(0, size)});
check(two_rows, max_distance);
Slic3r::Points two_diag({Point(0, 0), Point(size, size)});
check(two_diag, max_distance);
Slic3r::Points three({Point(0, 0), Point(size, 0), Point(half_size, size)});
check(three, max_distance);
Slic3r::Points middle_point({Point(0, 0), Point(size, half_size),
Point(-size, half_size), Point(0, -size)});
check(middle_point, max_distance);
Slic3r::Points middle_point2({Point(half_size, half_size), Point(-size, -size), Point(-size, size),
Point(size, -size), Point(size, size)});
check(middle_point2, max_distance);
Slic3r::Points diagonal_points({{-123473762, 71287970},
{-61731535, 35684428},
{0, 0},
{61731535, -35684428},
{123473762, -71287970}});
double diagonal_max_distance = 5e7;
check(diagonal_points, diagonal_max_distance);
int scale = 10;
Slic3r::Points diagonal_points2;
std::transform(diagonal_points.begin(), diagonal_points.end(),
std::back_inserter(diagonal_points2),
[&](const Slic3r::Point &p) { return p/scale; });
check(diagonal_points2, diagonal_max_distance / scale);
}