Merge branch 'fs_SLA_supports'
361
resources/data/sla_support.svg
Normal 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 |
12
resources/icons/sphere_blueish.svg
Normal 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 |
12
resources/icons/sphere_cyan.svg
Normal 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 |
12
resources/icons/sphere_lightgray.svg
Normal 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 |
12
resources/icons/sphere_orange.svg
Normal 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 |
12
resources/icons/sphere_redish.svg
Normal 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 |
68
resources/icons/support_structure.svg
Normal 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 |
228
resources/icons/support_structure_invisible.svg
Normal 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 |
@ -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}/..)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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_();
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
|
@ -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) /////////////////////////////////////////////
|
||||
|
||||
|
23
src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.cpp
Normal 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));
|
||||
}
|
36
src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.hpp
Normal 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_
|
44
src/libslic3r/SLA/SupportIslands/ExpandNeighbor.cpp
Normal 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));
|
||||
}
|
35
src/libslic3r/SLA/SupportIslands/ExpandNeighbor.hpp
Normal 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_
|
23
src/libslic3r/SLA/SupportIslands/IStackFunction.hpp
Normal 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_
|
458
src/libslic3r/SLA/SupportIslands/LineUtils.cpp
Normal 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 = [¢er](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 ¢er,
|
||||
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 ¢er,
|
||||
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 ¢er,
|
||||
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 ¢er,
|
||||
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 ¢er,
|
||||
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 ¢er,
|
||||
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 ¬_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 ¬_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 ¬_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);
|
||||
}
|
||||
}
|
227
src/libslic3r/SLA/SupportIslands/LineUtils.hpp
Normal 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 ¢er);
|
||||
|
||||
/// <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 ¢er,
|
||||
double radius);
|
||||
static std::optional<Slic3r::Linef> crop_line(const Linef & line,
|
||||
const Point ¢er,
|
||||
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 ¢er,
|
||||
double radius);
|
||||
static std::optional<Slic3r::Linef> crop_ray(const Linef & ray,
|
||||
const Point ¢er,
|
||||
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 ¢er,
|
||||
double radius);
|
||||
static std::optional<Slic3r::Linef> crop_half_ray(const Linef & half_ray,
|
||||
const Point ¢er,
|
||||
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_
|
73
src/libslic3r/SLA/SupportIslands/NodeDataWithResult.hpp
Normal 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_
|
43
src/libslic3r/SLA/SupportIslands/Parabola.hpp
Normal 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_
|
109
src/libslic3r/SLA/SupportIslands/ParabolaUtils.cpp
Normal 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 ¶bola)
|
||||
{
|
||||
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 ¶bola,
|
||||
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, ¶bola_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 ¶bola)
|
||||
{
|
||||
// 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 ¶bola)
|
||||
{
|
||||
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 ¶bola,
|
||||
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, ¶bola_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;
|
||||
};
|
73
src/libslic3r/SLA/SupportIslands/ParabolaUtils.hpp
Normal 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 ¶bola);
|
||||
|
||||
/// <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 ¶bola,
|
||||
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 ¶bola);
|
||||
|
||||
/// <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 ¶bola);
|
||||
|
||||
/// <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 ¶bola,
|
||||
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_
|
40
src/libslic3r/SLA/SupportIslands/PointUtils.cpp
Normal 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;
|
||||
}
|
53
src/libslic3r/SLA/SupportIslands/PointUtils.hpp
Normal 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_
|
87
src/libslic3r/SLA/SupportIslands/PolygonUtils.cpp
Normal 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 ¢er)
|
||||
{
|
||||
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 ¢er) {
|
||||
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 = [¢er](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;
|
||||
}
|
84
src/libslic3r/SLA/SupportIslands/PolygonUtils.hpp
Normal 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 ¢er);
|
||||
|
||||
/// <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 ¢er);
|
||||
};
|
||||
} // namespace Slic3r::sla
|
||||
#endif // slic3r_SLA_SuppotstIslands_PolygonUtils_hpp_
|
40
src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.cpp
Normal 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));
|
||||
}
|
36
src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.hpp
Normal 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_
|
50
src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.cpp
Normal 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;
|
||||
}
|
47
src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.hpp
Normal 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_
|
119
src/libslic3r/SLA/SupportIslands/SampleConfig.hpp
Normal 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_
|
137
src/libslic3r/SLA/SupportIslands/SampleConfigFactory.cpp
Normal 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 ¤t, 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
|
43
src/libslic3r/SLA/SupportIslands/SampleConfigFactory.hpp
Normal 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_
|
202
src/libslic3r/SLA/SupportIslands/SupportIslandPoint.cpp
Normal 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);
|
||||
}
|
311
src/libslic3r/SLA/SupportIslands/SupportIslandPoint.hpp
Normal 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_
|
2637
src/libslic3r/SLA/SupportIslands/UniformSupportIsland.cpp
Normal file
37
src/libslic3r/SLA/SupportIslands/UniformSupportIsland.hpp
Normal 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_
|
152
src/libslic3r/SLA/SupportIslands/VectorUtils.hpp
Normal 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_
|
294
src/libslic3r/SLA/SupportIslands/VoronoiDiagramCGAL.cpp
Normal 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;
|
||||
}
|
26
src/libslic3r/SLA/SupportIslands/VoronoiDiagramCGAL.hpp
Normal 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_
|
212
src/libslic3r/SLA/SupportIslands/VoronoiGraph.hpp
Normal 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_
|
1456
src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp
Normal file
506
src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp
Normal 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 ¢er,
|
||||
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 ¶bola, 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 ¶bola, 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 ¶bola_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_
|
@ -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
|
||||
|
@ -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 ¢roid, 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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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"));
|
||||
|
5
tests/data/sla_islands/SPE-2674.svg
Normal file
After Width: | Height: | Size: 22 KiB |
11
tests/data/sla_islands/SPE-2674_2.svg
Normal 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 |
11
tests/data/sla_islands/lm_issue.svg
Normal file
After Width: | Height: | Size: 50 KiB |
@ -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}};
|
||||
|
||||
|
@ -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]") {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
@ -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()
|
59
tests/sla_print/sla_lineUtils_tests.cpp
Normal 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)));
|
||||
}
|
50
tests/sla_print/sla_parabola_tests.cpp
Normal 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 ¶bola)
|
||||
{
|
||||
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 ¶bola, 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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
25
tests/sla_print/sla_vectorUtils_tests.cpp
Normal 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]);
|
||||
}
|
||||
}
|
91
tests/sla_print/sla_voronoi_graph_tests.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|