Commit c1462ef4 authored by sumpfralle's avatar sumpfralle

added some more documentation for the new push->follow algorithm


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@702 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent ea3b1274
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="910.52441"
height="2845.2439"
id="svg2"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="push_follow_strategy.svg">
<title
id="title4292">Sketches for Push-Follow toolpath strategy</title>
<defs
id="defs4">
<linearGradient
id="linearGradient3644">
<stop
style="stop-color:#df2235;stop-opacity:1;"
offset="0"
id="stop3646" />
<stop
id="stop3652"
offset="0.5"
style="stop-color:#e8818c;stop-opacity:1;" />
<stop
style="stop-color:#df2235;stop-opacity:1;"
offset="1"
id="stop3648" />
</linearGradient>
<inkscape:path-effect
effect="spiro"
id="path-effect2844"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect3632"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect3628"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect3624"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect3620"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect3616"
is_visible="true" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="-20.720168 : 469.94063 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="723.37431 : 469.94063 : 1"
inkscape:persp3d-origin="351.32707 : 294.54693 : 1"
id="perspective10" />
<inkscape:perspective
id="perspective3600"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3644"
id="linearGradient4226"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-694.74386,-694.75982)"
x1="287.60236"
y1="883.64081"
x2="381.3631"
y2="883.64081" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3644"
id="linearGradient4228"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-1694.7439,-694.75982)"
x1="287.60236"
y1="883.64081"
x2="381.3631"
y2="883.64081" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3644"
id="linearGradient4230"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-1134.7439,-694.75982)"
x1="287.60236"
y1="883.64081"
x2="381.3631"
y2="883.64081" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3644"
id="linearGradient4234"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(165.26314,-694.75982)"
x1="287.60236"
y1="883.64081"
x2="381.3631"
y2="883.64081" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3644"
id="linearGradient4236"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(1025.2631,-694.75982)"
x1="287.60236"
y1="883.64081"
x2="381.3631"
y2="883.64081" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3644"
id="linearGradient4240"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-274.73686,-694.75982)"
x1="287.60236"
y1="883.64081"
x2="381.3631"
y2="883.64081" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35306407"
inkscape:cx="379.0174"
inkscape:cy="872.93317"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-global="true"
showguides="true"
inkscape:snap-bbox="false"
inkscape:bbox-nodes="false"
inkscape:bbox-paths="false"
inkscape:snap-bbox-edge-midpoints="false"
inkscape:snap-bbox-midpoints="false"
inkscape:window-width="1024"
inkscape:window-height="742"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="false"
inkscape:snap-smooth-nodes="false"
inkscape:snap-nodes="false">
<inkscape:grid
type="xygrid"
id="grid3632"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Sketches for Push-Follow toolpath strategy</dc:title>
<dc:date>2010/09/25</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Lars Kruse</dc:title>
</cc:Agent>
</dc:creator>
<dc:source>https://pycam.svn.sourceforge.net/svnroot/pycam/trunk/artwork</dc:source>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(1603.5528,-33.73877)">
<g
id="g3891"
transform="translate(-1926.0692,1400)"
inkscape:export-filename="/home/lars/subversion/pycam/trunk/artwork/pics/case3a.png"
inkscape:export-xdpi="40.016376"
inkscape:export-ydpi="40.016376">
<g
transform="translate(-41.6624,0)"
id="g3734">
<path
sodipodi:nodetypes="csaacccc"
id="path3698"
d="m 453.8655,39.399761 c 0,0 14.3721,-9.70579 21.46016,-3.70003 8.92695,7.56386 16.03483,9.82794 24.42021,14.06011 7.69164,3.88203 15.07588,10.80419 23.68019,10.36008 8.49429,-0.43843 22.20018,-12.5801 22.20018,-12.5801 l 0,252.342049 -91.76074,0 0,-260.482109 z"
style="fill:url(#linearGradient4234);fill-opacity:1;stroke:none" />
<path
sodipodi:nodetypes="cccc"
id="path3702"
d="M 595.09253,97.352465 642.03303,449.38533 511.9649,133.69284 595.09253,97.352465 z"
style="fill:#2e980b;fill-opacity:1;stroke:none" />
</g>
<text
id="text3708"
y="513.15857"
x="506.96362"
style="font-size:28px;font-style:normal;font-weight:normal;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
y="513.15857"
x="506.96362"
id="tspan3710"
sodipodi:role="line">Case 3a</tspan><tspan
id="tspan3712"
y="548.15857"
x="506.96362"
sodipodi:role="line">Two triangle vertices</tspan><tspan
id="tspan3714"
y="583.15857"
x="506.96362"
sodipodi:role="line">are above the cutter level.</tspan><tspan
id="tspan3716"
y="618.15857"
x="506.96362"
sodipodi:role="line">The triangle's normal</tspan><tspan
id="tspan3718"
y="653.15857"
x="506.96362"
sodipodi:role="line">points downward.</tspan></text>
</g>
<g
id="g4249"
inkscape:export-filename="/home/lars/subversion/pycam/trunk/artwork/pics/case4.png"
inkscape:export-xdpi="40.00053"
inkscape:export-ydpi="40.00053">
<g
transform="translate(-2829.8646,2220)"
id="g3796">
<path
sodipodi:nodetypes="csaacccc"
id="path3774"
d="m 1313.8655,39.399761 c 0,0 14.3721,-9.70579 21.4602,-3.70003 8.9269,7.56386 16.0348,9.82794 24.4202,14.06011 7.6916,3.88203 15.0758,10.80419 23.6802,10.36008 8.4942,-0.43843 22.2001,-12.5801 22.2001,-12.5801 l 0,252.342049 -91.7607,0 0,-260.482109 z"
style="fill:url(#linearGradient4236);fill-opacity:1;stroke:none" />
<path
style="fill:#2e980b;fill-opacity:1;stroke:none"
d="M 1469.4959,85.481778 1506.299,200.54947 1367.884,212.08419 1469.4959,85.481778 z"
id="path3778"
sodipodi:nodetypes="cccc" />
</g>
<text
id="text3780"
y="2626.5864"
x="-1419.0509"
style="font-size:28px;font-style:normal;font-weight:normal;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
y="2626.5864"
x="-1419.0509"
id="tspan3782"
sodipodi:role="line">Case 4</tspan><tspan
id="tspan3784"
y="2661.5864"
x="-1419.0509"
sodipodi:role="line">The complete triangle is</tspan><tspan
id="tspan3790"
y="2696.5864"
x="-1419.0509"
sodipodi:role="line">above the cutter level.</tspan></text>
</g>
<g
id="g3852"
transform="translate(1.9550781,0)"
inkscape:export-filename="/home/lars/subversion/pycam/trunk/artwork/pics/case1a.png"
inkscape:export-xdpi="40"
inkscape:export-ydpi="40">
<g
transform="translate(-1160,0)"
id="g3844">
<path
sodipodi:nodetypes="csaacccc"
id="path3676"
d="m -406.1415,39.399761 c 0,0 14.3721,-9.70579 21.46016,-3.70003 8.92695,7.56386 16.03483,9.82794 24.42021,14.06011 7.69164,3.88203 15.07588,10.80419 23.68019,10.36008 8.49429,-0.43843 22.20018,-12.5801 22.20018,-12.5801 l 0,252.342049 -91.76074,0 0,-260.482109 z"
style="fill:url(#linearGradient4226);fill-opacity:1;stroke:none" />
<path
sodipodi:nodetypes="cccc"
id="path3680"
d="m -236.6722,331.72503 119.56148,49.09019 -149.86704,149.94979 30.30556,-199.03998 z"
style="fill:#2e980b;fill-opacity:1;stroke:none" />
</g>
<text
id="text3686"
y="588.5863"
x="-1421.2794"
style="font-size:28px;font-style:normal;font-weight:normal;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
id="tspan3690"
y="588.5863"
x="-1421.2794"
sodipodi:role="line">Case 1a</tspan><tspan
id="tspan3692"
y="623.5863"
x="-1421.2794"
sodipodi:role="line">The triangle is completely</tspan><tspan
id="tspan3732"
y="658.5863"
x="-1421.2794"
sodipodi:role="line">below the cutter level.</tspan></text>
</g>
<g
id="g4282"
inkscape:export-filename="/home/lars/subversion/pycam/trunk/artwork/pics/case2a.png"
inkscape:export-xdpi="39.983322"
inkscape:export-ydpi="39.983322">
<g
transform="translate(-656.45438,700)"
id="g3818">
<path
style="fill:url(#linearGradient4230);fill-opacity:1;stroke:none"
d="m -846.1415,39.399761 c 0,0 14.3721,-9.70579 21.46016,-3.70003 8.92695,7.56386 16.03483,9.82794 24.42021,14.06011 7.69164,3.88203 15.07588,10.80419 23.68019,10.36008 8.49429,-0.43843 22.20018,-12.5801 22.20018,-12.5801 l 0,252.342049 -91.76074,0 0,-260.482109 z"
id="path3802"
sodipodi:nodetypes="csaacccc" />
<path
style="fill:#2e980b;fill-opacity:1;stroke:none"
d="m -694.41479,223.23423 13.90036,49.09019 -17.42372,149.94979 3.52336,-199.03998 z"
id="path3806"
sodipodi:nodetypes="cccc" />
</g>
<text
id="text3808"
y="1213.5863"
x="-1419.4132"
style="font-size:28px;font-style:normal;font-weight:normal;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
y="1213.5863"
x="-1419.4132"
id="tspan3810"
sodipodi:role="line">Case 2a</tspan><tspan
id="tspan3812"
y="1248.5863"
x="-1419.4132"
sodipodi:role="line">The projection of the</tspan><tspan
id="tspan3814"
y="1283.5863"
x="-1419.4132"
sodipodi:role="line">triangle onto the xy plane</tspan><tspan
id="tspan3816"
y="1318.5863"
x="-1419.4132"
sodipodi:role="line">results in a line.</tspan></text>
</g>
<g
id="g3880"
transform="translate(-997.84187,700)"
inkscape:export-filename="/home/lars/subversion/pycam/trunk/artwork/pics/case2b.png"
inkscape:export-xdpi="40.016376"
inkscape:export-ydpi="40.016376">
<path
sodipodi:nodetypes="cccc"
id="path3657"
d="M 217.26916,118.99432 48.442967,431.44954 128.64982,99.566541 217.26916,118.99432 z"
style="fill:#2e980b;fill-opacity:1;stroke:none" />
<path
sodipodi:nodetypes="csaacccc"
id="rect3642"
d="m 13.865497,39.399761 c 0,0 14.3721,-9.70579 21.46016,-3.70003 8.92695,7.56386 16.03483,9.82794 24.42021,14.06011 7.691642,3.88203 15.075882,10.80419 23.680192,10.36008 8.49429,-0.43843 22.200181,-12.5801 22.200181,-12.5801 l 0,252.342049 -91.760743,0 0,-260.482109 z"
style="fill:url(#linearGradient4240);fill-opacity:1;stroke:none" />
<text
id="text3660"
y="478.15857"
x="115.72456"
style="font-size:28px;font-style:normal;font-weight:normal;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
y="478.15857"
x="115.72456"
id="tspan3662"
sodipodi:role="line">Case 2b</tspan><tspan
id="tspan3664"
y="513.15857"
x="115.72456"
sodipodi:role="line">One or two vertices</tspan><tspan
id="tspan3720"
y="548.15857"
x="115.72456"
sodipodi:role="line">of the triangle are</tspan><tspan
id="tspan3724"
y="583.15857"
x="115.72456"
sodipodi:role="line">above the cutter</tspan><tspan
id="tspan3728"
y="618.15857"
x="115.72456"
sodipodi:role="line">level. The triangle's</tspan><tspan
id="tspan3730"
y="653.15857"
x="115.72456"
sodipodi:role="line">normal points upwards.</tspan></text>
</g>
<g
id="g4265"
inkscape:export-filename="/home/lars/subversion/pycam/trunk/artwork/pics/case1b.png"
inkscape:export-xdpi="40.000576"
inkscape:export-ydpi="40.000576">
<g
transform="translate(364.74354,0)"
id="g3838">
<path
style="fill:url(#linearGradient4228);fill-opacity:1;stroke:none"
d="m -1406.1415,39.399761 c 0,0 14.3721,-9.70579 21.4602,-3.70003 8.9269,7.56386 16.0348,9.82794 24.4202,14.06011 7.6916,3.88203 15.0759,10.80419 23.6802,10.36008 8.4943,-0.43843 22.2001,-12.5801 22.2001,-12.5801 l 0,252.342049 -91.7607,0 0,-260.482109 z"
id="path3824"
sodipodi:nodetypes="csaacccc" />
<path
style="fill:#2e980b;fill-opacity:1;stroke:none"
d="m -1240.2797,238.04743 152.2072,11.36564 -191.5695,0.96017 39.3623,-12.32581 z"
id="path3828"
sodipodi:nodetypes="cccc" />
</g>
<text
id="text3830"
y="383.15857"
x="-881.57727"
style="font-size:28px;font-style:normal;font-weight:normal;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
y="383.15857"
x="-881.57727"
id="tspan3832"
sodipodi:role="line">Case 1b</tspan><tspan
id="tspan3834"
y="418.15857"
x="-881.57727"
sodipodi:role="line">The vertices of the triangle</tspan><tspan
id="tspan3836"
y="453.15857"
x="-881.57727"
sodipodi:role="line">are in the same xy plane.</tspan></text>
</g>
<g
id="g3902"
transform="translate(-1828.8596,1400)"
inkscape:export-filename="/home/lars/subversion/pycam/trunk/artwork/pics/case3b.png"
inkscape:export-xdpi="40.016376"
inkscape:export-ydpi="40.016376">
<g
transform="translate(-49.750192,0)"
id="g3766">
<use
x="0"
y="0"
xlink:href="#rect3642"
id="use3748"
transform="translate(840,0)"
width="744.09448"
height="1052.3622" />
<path
sodipodi:nodetypes="cccc"
id="use3750"
d="m 1015.5041,418.08145 -100.6534,-272.8995 210.0226,212.90241 -109.3692,59.99709 z"
style="fill:#2e980b;fill-opacity:1;stroke:none" />
</g>
<text
id="text3754"
y="513.15857"
x="940.29596"
style="font-size:28px;font-style:normal;font-weight:normal;text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
y="513.15857"
x="940.29596"
id="tspan3756"
sodipodi:role="line">Case 3b</tspan><tspan
id="tspan3758"
y="548.15857"
x="944.75299"
sodipodi:role="line">One triangle vertex is </tspan><tspan
id="tspan3760"
y="583.15857"
x="940.29596"
sodipodi:role="line">above the cutter level.</tspan><tspan
id="tspan3762"
y="618.15857"
x="940.29596"
sodipodi:role="line">The triangle's normal</tspan><tspan
id="tspan3764"
y="653.15857"
x="940.29596"
sodipodi:role="line">points downward.</tspan></text>
</g>
<text
xml:space="preserve"
style="font-size:28px;font-style:normal;font-weight:normal;text-align:start;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
x="-1195.6948"
y="2281.3574"
id="text2894"><tspan
sodipodi:role="line"
id="tspan2896"
x="-1195.6948"
y="2281.3574">see the related blog posting:</tspan><tspan
sodipodi:role="line"
x="-1195.6948"
y="2316.3574"
id="tspan2898">http://fab.senselab.org/node/43</tspan></text>
</g>
</svg>
......@@ -20,6 +20,9 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
# take a look at the related blog posting describing this algorithm:
# http://fab.senselab.org/node/43
from pycam.Geometry.Point import Point, Vector
from pycam.Geometry.Line import Line
from pycam.Geometry.Plane import Plane
......@@ -133,6 +136,7 @@ class WaterlineTriangles:
continue
cp, dist = current_shifted.get_intersection(neighbour_shifted, infinite_lines=True)
cp2, dist2 = neighbour_shifted.get_intersection(current_shifted, infinite_lines=True)
# TODO: add an arc (composed of lines) for a soft corner (not required, but nicer)
if dist < epsilon:
self.shifted_lines[current] = None
index -= 1
......@@ -256,9 +260,11 @@ class Waterline:
break
# ignore triangles below the z level
if triangle.maxz < z:
# Case 1a
continue
# ignore triangles pointing upwards or downwards
if triangle.normal.cross(self._up_vector).norm == 0:
# Case 1b
continue
edge_collisions = self.get_collision_waterline_of_triangle(triangle, z)
if not edge_collisions:
......@@ -295,6 +301,7 @@ class Waterline:
if triangle.minz > z:
# the triangle is completely above z
# try all edges
# Case (4)
proj_points = []
for p in triangle.get_points():
proj_p = plane.get_point_projection(p)
......@@ -327,6 +334,8 @@ class Waterline:
edge = Line(edge.p2, edge.p1)
outer_edges = [edge]
else:
# some parts of the triangle are above and some below the cutter level
# Cases (2a), (2b), (3a) and (3b)
points_above = [plane.get_point_projection(p) for p in triangle.get_points() if p.z > z]
waterline = plane.intersect_triangle(triangle)
if waterline is None:
......@@ -344,13 +353,16 @@ class Waterline:
if (p != waterline.p1) and (p != waterline.p2)]
potential_edges = []
if len(points_above) == 0:
# part of case (2a)
outer_edges = [waterline]
elif len(points_above) == 1:
other_point = points_above[0]
dot = other_point.sub(waterline.p1).cross(waterline.dir).dot(self._up_vector)
if dot > 0:
# Case (2b)
outer_edges = [waterline]
elif dot < 0:
# Case (3b)
edges = []
edges.append(Line(waterline.p1, other_point))
edges.append(Line(waterline.p2, other_point))
......@@ -362,6 +374,7 @@ class Waterline:
outer_edges.append(edge)
else:
# the three points are on one line
# part of case (2a)
edges = []
edges.append(waterline)
edges.append(Line(waterline.p1, other_point))
......@@ -377,9 +390,11 @@ class Waterline:
other_point = points_above[0]
dot = other_point.sub(waterline.p1).cross(waterline.dir).dot(self._up_vector)
if dot > 0:
# Case (2b)
# the other two points are on the right side
outer_edges = [waterline]
elif dot < 0:
# Case (3a)
edge = Line(points_above[0], points_above[1])
if edge.dir.cross(triangle.normal).dot(self._up_vector) < 0:
outer_edges = [Line(edge.p2, edge.p1)]
......@@ -388,6 +403,8 @@ class Waterline:
else:
edges = []
# pick the longest combination of two of these points
# part of case (2a)
# TODO: maybe we should use the waterline instead? (otherweise the line could be too long and thus connections to the adjacent waterlines are not discovered? Test this with an appropriate test model.)
points = [waterline.p1, waterline.p2] + points_above
for p1 in points:
for p2 in points:
......@@ -406,6 +423,7 @@ class Waterline:
continue
direction = direction.mul(self.get_max_length())
edge_dir = edge.p2.sub(edge.p1)
# TODO: adapt the number of potential starting positions to the length of the line
for factor in (0.5, 0.0, 1.0, 0.25, 0.75):
start = edge.p1.add(edge_dir.mul(factor))
# We need to use the triangle collision algorithm here - because we
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment