diff --git a/resources/data/embossed_text.obj b/resources/data/embossed_text.obj
new file mode 100644
index 0000000000..8f6f09f41e
--- /dev/null
+++ b/resources/data/embossed_text.obj
@@ -0,0 +1,2555 @@
+v -18.02 2 -1
+v -17.5 -0.13 -1
+v -18.02 -1.94 -1
+v 5.62 0.93 -1
+v 5.93 0.58 -1
+v 5.65 0.49 -1
+v -17.5 0.33 -1
+v -15.07 -1.94 -1
+v -17.5 -1.47 -1
+v -15.31 -0.13 -1
+v -15.31 0.33 -1
+v -17.5 1.54 -1
+v -11.91 0.85 -1
+v -11.73 0.54 -1
+v -12.12 0.7 -1
+v -15.16 1.54 -1
+v -15.16 2 -1
+v -15.07 -1.47 -1
+v 6.78 -1.58 -1
+v 6.62 -1.76 -1
+v 6.37 -1.52 -1
+v 6.69 -1.14 -1
+v 6.56 -1.35 -1
+v -9.33 0.59 -1
+v -9.81 2 -1
+v -9.33 2 -1
+v 6.78 -1.94 -1
+v 7.23 -1.94 -1
+v 6.74 2 -1
+v 7.23 2 -1
+v 6.74 0.59 -1
+v 5.95 0.98 -1
+v 2.05 -1.62 -1
+v 2.5 -1.41 -1
+v 2.25 -1.79 -1
+v 5.33 0.1 -1
+v 5.24 -0.34 -1
+v 4.78 -0.1 -1
+v -6.35 -1.79 -1
+v -6.25 -1 -1
+v -6 -1.5 -1
+v 4.74 -0.51 -1
+v 5.27 -0.88 -1
+v -4.84 -1.11 -1
+v -4.74 -0.66 -1
+v -4.26 -0.84 -1
+v 6.2 0.95 -1
+v 6.5 0.83 -1
+v 6.16 0.56 -1
+v 5.09 -1.59 -1
+v 4.9 -1.29 -1
+v 5.41 -1 -1
+v 5.34 -1.82 -1
+v 5.65 -1.5 -1
+v 5.87 -1.59 -1
+v 5.65 -1.96 -1
+v 3.96 -1.69 -1
+v 3.68 -1.28 -1
+v 4.2 -1.37 -1
+v 5.98 -2 -1
+v 3.81 -1.02 -1
+v 6.44 -1.9 -1
+v 4.78 -0.92 -1
+v 4.89 0.26 -1
+v 5 0.49 -1
+v 5.46 0.31 -1
+v 5.25 0.75 -1
+v 6.38 0.47 -1
+v 6.62 0.22 -1
+v 6.76 -0.15 -1
+v 10.39 -1.52 -1
+v 10.65 -1.93 -1
+v 10.36 -1.98 -1
+v 6.78 -0.72 -1
+v 6.09 -1.61 -1
+v 6.22 -1.98 -1
+v -8.06 -1.33 -1
+v -7.92 -1.11 -1
+v -7.69 -1.6 -1
+v 3.54 0.88 -1
+v 3.45 0.47 -1
+v 3.15 0.98 -1
+v 2.49 -1.91 -1
+v 2.78 -1.57 -1
+v -9.02 -1.9 -1
+v -9.2 -1.77 -1
+v -8.9 -1.55 -1
+v -7.85 -0.13 -1
+v -7.33 -0.27 -1
+v -7.83 -0.68 -1
+v 2.35 -1.21 -1
+v -7.83 0.74 -1
+v -8.22 0.47 -1
+v -8.04 0.87 -1
+v -8.94 0.49 -1
+v -9.19 0.24 -1
+v -8.29 0.95 -1
+v -8.49 0.58 -1
+v -8.69 0.98 -1
+v 2.23 -0.95 -1
+v 1.69 -0.75 -1
+v 2.19 -0.63 -1
+v 2.21 -0.23 -1
+v -4.33 -1.16 -1
+v -4.43 -1.4 -1
+v -7.98 0.23 -1
+v 4.12 0.37 -1
+v 3.77 0 -1
+v 3.67 0.25 -1
+v -12.82 0.95 -1
+v -13.02 0.53 -1
+v -13.11 0.98 -1
+v -9.33 -0.09 -1
+v -8.54 -1.61 -1
+v -8.81 -1.98 -1
+v -13.91 -0.15 -1
+v -13.98 0.51 -1
+v -13.85 0.14 -1
+v -9.32 -1.02 -1
+v -9.18 -1.31 -1
+v -9.36 -1.58 -1
+v -9.81 -1.94 -1
+v -9.36 -1.94 -1
+v -8.42 -2 -1
+v -8.26 -1.51 -1
+v -7.42 -1.12 -1
+v -12.3 0.48 -1
+v -11.97 0.41 -1
+v -9.09 0.83 -1
+v -8.74 0.56 -1
+v -7.66 0.57 -1
+v -7.47 0.27 -1
+v -7.35 -0.81 -1
+v -7.53 -1.38 -1
+v -7.97 -1.84 -1
+v 10.58 -1.51 -1
+v 17.42 -1.96 -1
+v 17.58 -1.5 -1
+v 17.73 -1.98 -1
+v 17.47 -1.37 -1
+v 17.02 -1.64 -1
+v 16.98 -1.27 -1
+v 10.22 -1.5 -1
+v 9.62 -1.27 -1
+v 10.11 -1.37 -1
+v 9.65 -1.64 -1
+v 9.26 0.91 -1
+v 9.61 0.54 -1
+v 9.26 0.54 -1
+v -9.37 -0.58 -1
+v 9.61 1.62 -1
+v 10.1 0.91 -1
+v 9.61 0.91 -1
+v 10.58 0.91 -1
+v 10.58 0.54 -1
+v 10.1 0.54 -1
+v 10.1 1.92 -1
+v 9.82 -1.86 -1
+v 10.05 -1.96 -1
+v 12.64 -1.97 -1
+v 12.11 -2 -1
+v 12.26 -1.61 -1
+v 17.76 -1.52 -1
+v 17.95 -1.51 -1
+v 18.02 -1.93 -1
+v 16.98 0.54 -1
+v 16.63 0.91 -1
+v 16.98 0.91 -1
+v 17.46 0.54 -1
+v 17.46 0.91 -1
+v 12.51 -1.57 -1
+v 16.98 1.62 -1
+v 17.95 0.91 -1
+v 17.95 0.54 -1
+v 17.46 1.92 -1
+v 16.63 0.54 -1
+v 12.89 -1.28 -1
+v 13.41 -1.37 -1
+v 13.17 -1.69 -1
+v 13.02 -1.02 -1
+v 17.19 -1.86 -1
+v 15.13 -0.06 -1
+v 15.83 0.91 -1
+v 16.41 0.91 -1
+v 15.42 -0.42 -1
+v 12.72 -1.46 -1
+v 15.15 -0.81 -1
+v 14.39 -1.94 -1
+v 14.85 -0.45 -1
+v 13.52 -1.08 -1
+v 12.91 -1.87 -1
+v 11.46 -1.79 -1
+v 11.71 -1.41 -1
+v 11.7 -1.91 -1
+v 11.43 -0.23 -1
+v 11.4 -0.63 -1
+v 10.9 -0.75 -1
+v 11.99 -1.57 -1
+v 11.26 -1.62 -1
+v 11.56 -1.21 -1
+v 11.1 -1.4 -1
+v 11.44 -0.95 -1
+v 10.99 -1.15 -1
+v 14.49 0.91 -1
+v 13.89 0.91 -1
+v -14.41 0.91 -1
+v 11.48 0.04 -1
+v 10.92 -0.19 -1
+v 10.99 0.1 -1
+v 13.02 -0.23 -1
+v 11.11 0.36 -1
+v 11.58 0.24 -1
+v 11.27 0.58 -1
+v 11.74 0.42 -1
+v 11.47 0.75 -1
+v 12.01 0.55 -1
+v 11.7 0.88 -1
+v 11.95 0.95 -1
+v 12.36 0.58 -1
+v 12.36 0.98 -1
+v 12.66 0.47 -1
+v 12.76 0.88 -1
+v 12.98 0.76 -1
+v 12.88 0.25 -1
+v 13.17 0.59 -1
+v 13.33 0.37 -1
+v 12.99 0 -1
+v 13.48 0.02 -1
+v 13.54 -0.63 -1
+v -13.75 0.32 -1
+v -13.6 0.45 -1
+v -13.32 0.56 -1
+v -13.62 0.85 -1
+v -13.38 0.95 -1
+v -12.85 0.43 -1
+v -12.75 0.25 -1
+v -12.41 0.69 -1
+v -12.14 0.2 -1
+v -12.22 -0.1 -1
+v -12.71 0.02 -1
+v -10.83 0.81 -1
+v -11.23 0.48 -1
+v -11.13 0.95 -1
+v -11.67 0.95 -1
+v -12.71 -1.94 -1
+v -12.22 -1.94 -1
+v -11.02 0.06 -1
+v -11.08 0.33 -1
+v -10.54 0.21 -1
+v -11.41 0.98 -1
+v -11.41 0.55 -1
+v -10.53 -1.94 -1
+v -11.01 -1.94 -1
+v -10.63 0.56 -1
+v -14.41 -1.94 -1
+v -12.59 0.85 -1
+v -13.82 0.7 -1
+v -13.98 0.91 -1
+v 3.81 -0.23 -1
+v 4.33 -0.63 -1
+v -13.93 -1.94 -1
+v 3.51 -1.46 -1
+v 4.31 -1.08 -1
+v 3.43 -1.97 -1
+v 3.7 -1.87 -1
+v 3.3 -1.57 -1
+v 3.05 -1.61 -1
+v 1.89 -1.4 -1
+v 1.71 -0.19 -1
+v 2.26 0.04 -1
+v 3.96 0.59 -1
+v 3.77 0.76 -1
+v 2.74 0.95 -1
+v 2.8 0.55 -1
+v -4.97 -1.33 -1
+v 2.48 0.88 -1
+v 2.53 0.42 -1
+v 2.06 0.58 -1
+v 2.36 0.24 -1
+v 1.89 0.36 -1
+v 1.78 0.1 -1
+v -5.73 0.98 -1
+v -5.47 0.57 -1
+v -5.75 0.56 -1
+v -2.89 -1.58 -1
+v -2.57 -1.61 -1
+v -2.41 -2 -1
+v 0.62 0.31 -1
+v 0.84 0.77 -1
+v 0.99 0.62 -1
+v 1.78 -1.15 -1
+v 1.1 0.43 -1
+v -5.86 -1.98 -1
+v -6.12 -1.91 -1
+v 0.62 -0.88 -1
+v 0.4 -0.8 -1
+v 0.73 -0.38 -1
+v 2.9 -2 -1
+v 16.49 -1.94 -1
+v 15.89 -1.94 -1
+v 2.26 0.75 -1
+v 3.14 0.58 -1
+v 4.27 0.02 -1
+v -0.56 -1.87 -1
+v -0.37 -1.47 -1
+v -0.25 -1.98 -1
+v -4.24 -0.23 -1
+v -0.47 0.01 -1
+v -0.78 -0.38 -1
+v -0.91 -0.22 -1
+v -5.04 0.88 -1
+v -5.16 0.47 -1
+v -5.29 0.96 -1
+v 0.95 -1.75 -1
+v 0.72 -1.89 -1
+v 0.61 -1.47 -1
+v -0.5 -0.54 -1
+v -0.28 -0.09 -1
+v -5.97 0.48 -1
+v 1.13 -1.56 -1
+v 0.73 -1.32 -1
+v -6.67 0.44 -1
+v -6.18 0.31 -1
+v -6.32 0.09 -1
+v 0.77 -1.15 -1
+v 1.23 -1.33 -1
+v 0.5 0.46 -1
+v 0.26 0.57 -1
+v 0.62 0.88 -1
+v -0.55 0.18 -1
+v -1.01 0.03 -1
+v -4.45 0.38 -1
+v -4.33 0.13 -1
+v -4.9 0.21 -1
+v -4.76 -0.13 -1
+v -6.71 -1.4 -1
+v -6.55 -1.62 -1
+v -5.69 -1.6 -1
+v -5.58 -2 -1
+v -4.65 -1.67 -1
+v -5.19 -1.51 -1
+v -5.22 -1.96 -1
+v -5.4 -1.59 -1
+v -6.39 -0.88 -1
+v -6.83 -1.15 -1
+v -6.92 -0.72 -1
+v -6.42 -0.34 -1
+v -6.89 -0.14 -1
+v -6.81 0.18 -1
+v -6.48 0.66 -1
+v -6.2 0.85 -1
+v -4.81 0.76 -1
+v -4.61 0.59 -1
+v -4.89 -1.83 -1
+v 1.16 0.18 -1
+v 0.69 0.11 -1
+v -0.98 0.41 -1
+v -1.52 -1.33 -1
+v -1.63 -1.56 -1
+v -2.02 -1.32 -1
+v -1.51 -0.87 -1
+v -1.98 -1.15 -1
+v -2.02 -1 -1
+v -0.5 0.88 -1
+v -0.37 0.5 -1
+v -0.78 0.71 -1
+v -0.15 0.57 -1
+v -0.09 0.98 -1
+v 0.34 0.96 -1
+v -2.92 0.57 -1
+v -3.27 0.88 -1
+v -2.86 0.98 -1
+v -2.49 0.57 -1
+v -3.54 0.71 -1
+v -3.13 0.5 -1
+v -3.28 0.35 -1
+v -0.51 0.35 -1
+v -3.26 -0.54 -1
+v -3.05 -0.09 -1
+v -2.35 -0.8 -1
+v -1.8 -1.75 -1
+v -2.03 -1.89 -1
+v -2.14 -1.47 -1
+v -0.78 -1.73 -1
+v -0.54 -1.26 -1
+v -1 -1.42 -1
+v 0.18 -1.61 -1
+v 0.46 -1.56 -1
+v 0.34 -2 -1
+v 1.24 -0.87 -1
+v 1.27 -1.1 -1
+v 1.02 -0.54 -1
+v 0.73 -1 -1
+v 1.15 -0.68 -1
+v -3.13 -1.47 -1
+v -3.01 -1.98 -1
+v -3.33 -1.87 -1
+v -0.62 -1.01 -1
+v -2.29 -1.56 -1
+v -1.1 -1.08 -1
+v -2.02 -0.38 -1
+v -2.13 -0.88 -1
+v -0.13 -1.58 -1
+v -1.74 -0.54 -1
+v -2.13 0.31 -1
+v -1.65 0.43 -1
+v -2.06 0.11 -1
+v -3.23 0.01 -1
+v -3.54 -0.38 -1
+v -3.38 -1.01 -1
+v -3.3 -1.26 -1
+v -3.76 -1.42 -1
+v -3.67 -0.22 -1
+v -3.78 0.03 -1
+v -3.74 0.41 -1
+v -3.31 0.18 -1
+v -2.41 0.96 -1
+v -2.13 0.88 -1
+v -2.25 0.46 -1
+v -1.91 0.77 -1
+v -1.76 0.62 -1
+v -1.59 0.18 -1
+v -1.48 -1.1 -1
+v -1.6 -0.68 -1
+v -3.55 -1.73 -1
+v -3.86 -1.08 -1
+v 13.81 -1.94 -1
+v -18.02 2 1
+v -18.02 -1.94 1
+v -17.5 -0.13 1
+v 5.62 0.93 1
+v 5.63 0.47 1
+v 5.9 0.58 1
+v -17.5 0.33 1
+v -15.07 -1.94 1
+v -17.5 -1.47 1
+v -15.31 -0.13 1
+v -15.31 0.33 1
+v -17.5 1.54 1
+v -11.91 0.85 1
+v -12.12 0.7 1
+v -11.75 0.53 1
+v -15.16 1.54 1
+v -15.16 2 1
+v -15.07 -1.47 1
+v 6.78 -1.58 1
+v 6.39 -1.5 1
+v 6.62 -1.76 1
+v -9.33 0.59 1
+v -9.33 2 1
+v -9.81 2 1
+v 6.78 -1.94 1
+v 7.23 -1.94 1
+v 6.74 2 1
+v 6.74 0.59 1
+v 7.23 2 1
+v 5.95 0.98 1
+v 2.05 -1.62 1
+v 2.25 -1.79 1
+v 2.52 -1.43 1
+v -7.33 -0.33 1
+v -7.92 0.1 1
+v -7.83 -0.33 1
+v 5.4 0.23 1
+v 4.89 0.26 1
+v 5.27 -0.13 1
+v 4.74 -0.51 1
+v 5.24 -0.68 1
+v -6.35 -1.79 1
+v -5.97 -1.51 1
+v -6.18 -1.33 1
+v 4.78 -0.92 1
+v 5.34 -1.12 1
+v 6.2 0.95 1
+v 6.16 0.56 1
+v 6.53 0.81 1
+v 5.09 -1.59 1
+v 4.9 -1.29 1
+v 5.34 -1.82 1
+v 5.67 -1.51 1
+v 5.47 -1.33 1
+v 5.87 -1.59 1
+v 5.65 -1.96 1
+v 3.93 -1.71 1
+v 4.18 -1.4 1
+v 3.68 -1.28 1
+v 5.98 -2 1
+v 3.81 -1.02 1
+v 6.44 -1.9 1
+v 4.78 -0.1 1
+v 5.02 0.51 1
+v 5.27 0.77 1
+v 6.36 0.48 1
+v 6.61 0.24 1
+v 6.75 -0.11 1
+v 10.4 -1.52 1
+v 10.34 -1.98 1
+v 10.65 -1.93 1
+v 6.73 -1.02 1
+v 6.78 -0.68 1
+v 6.63 -1.26 1
+v 6.11 -1.6 1
+v 6.22 -1.98 1
+v 3.54 0.88 1
+v 3.2 0.98 1
+v 3.42 0.48 1
+v -7.99 -1.24 1
+v -7.53 -1.38 1
+v -7.85 -0.88 1
+v 2.49 -1.91 1
+v 2.81 -1.58 1
+v -9.02 -1.9 1
+v -9.02 -1.48 1
+v -9.2 -1.77 1
+v 2.35 -1.21 1
+v -7.83 0.74 1
+v -8.04 0.87 1
+v -8.24 0.49 1
+v -9.21 0.21 1
+v -8.96 0.47 1
+v -8.29 0.95 1
+v -8.65 0.98 1
+v -8.52 0.58 1
+v 2.24 -0.98 1
+v 2.19 -0.63 1
+v 1.71 -0.86 1
+v 2.21 -0.23 1
+v 1.69 -0.31 1
+v -4.33 -1.16 1
+v -4.76 -0.87 1
+v -4.45 -1.43 1
+v -8.05 0.31 1
+v 4.12 0.37 1
+v 3.66 0.26 1
+v 3.77 0.03 1
+v -12.82 0.95 1
+v -13.11 0.98 1
+v -13.04 0.54 1
+v -9.37 -0.35 1
+v -8.52 -1.6 1
+v -8.79 -1.58 1
+v -8.81 -1.98 1
+v -9.31 -0.02 1
+v -13.92 -0.18 1
+v -13.85 0.14 1
+v -13.98 0.51 1
+v -9.81 -1.94 1
+v -9.36 -1.94 1
+v -9.36 -1.58 1
+v -8.57 -2 1
+v -8.23 -1.5 1
+v -8.32 -1.98 1
+v -7.39 -1.02 1
+v -9.25 -1.19 1
+v -9.35 -0.87 1
+v -12.3 0.48 1
+v -11.99 0.39 1
+v -9.06 0.85 1
+v -8.74 0.56 1
+v -7.66 0.57 1
+v -7.45 0.23 1
+v -7.69 -1.6 1
+v -8.01 -1.87 1
+v 10.58 -1.51 1
+v 17.42 -1.96 1
+v 17.7 -1.98 1
+v 17.59 -1.5 1
+v 17.48 -1.38 1
+v 16.98 -1 1
+v 17.02 -1.62 1
+v 10.23 -1.5 1
+v 9.61 -1 1
+v 9.65 -1.62 1
+v 10.11 -1.38 1
+v 9.26 0.91 1
+v 9.26 0.54 1
+v 9.61 0.54 1
+v 9.61 1.62 1
+v 9.61 0.91 1
+v 10.1 0.91 1
+v 10.58 0.54 1
+v 10.58 0.91 1
+v 10.1 0.54 1
+v 10.1 1.92 1
+v 9.8 -1.84 1
+v 10.05 -1.96 1
+v 12.6 -1.98 1
+v 12.26 -1.61 1
+v 12.06 -2 1
+v 17.77 -1.52 1
+v 18.02 -1.93 1
+v 17.95 -1.51 1
+v 16.98 0.54 1
+v 16.98 0.91 1
+v 16.63 0.91 1
+v 17.46 0.54 1
+v 17.46 0.91 1
+v 12.51 -1.57 1
+v 16.98 1.62 1
+v 17.95 0.54 1
+v 17.95 0.91 1
+v 17.46 1.92 1
+v 16.63 0.54 1
+v 12.89 -1.28 1
+v 13.15 -1.71 1
+v 13.4 -1.4 1
+v 13.02 -1.02 1
+v 17.16 -1.84 1
+v 15.13 -0.06 1
+v 16.41 0.91 1
+v 15.83 0.91 1
+v 15.42 -0.42 1
+v 12.72 -1.46 1
+v 15.15 -0.81 1
+v 14.85 -0.45 1
+v 14.39 -1.94 1
+v 13.52 -1.08 1
+v 12.91 -1.87 1
+v 11.46 -1.79 1
+v 11.7 -1.91 1
+v 11.74 -1.43 1
+v 11.43 -0.23 1
+v 10.9 -0.31 1
+v 11.4 -0.63 1
+v 12.02 -1.58 1
+v 11.26 -1.62 1
+v 11.56 -1.21 1
+v 11.1 -1.4 1
+v 11.45 -0.98 1
+v 10.99 -1.15 1
+v 10.92 -0.86 1
+v 14.49 0.91 1
+v 13.89 0.91 1
+v -14.41 0.91 1
+v 11.47 0.01 1
+v 10.99 0.1 1
+v 13.02 -0.23 1
+v 11.11 0.36 1
+v 11.58 0.24 1
+v 11.27 0.58 1
+v 11.72 0.4 1
+v 11.47 0.75 1
+v 11.99 0.55 1
+v 11.7 0.88 1
+v 11.95 0.95 1
+v 12.32 0.58 1
+v 12.41 0.98 1
+v 12.64 0.48 1
+v 12.76 0.88 1
+v 12.98 0.76 1
+v 12.87 0.26 1
+v 13.17 0.59 1
+v 13.33 0.37 1
+v 12.98 0.03 1
+v 13.45 0.12 1
+v 13.51 -0.17 1
+v 13.54 -0.63 1
+v -13.75 0.32 1
+v -13.6 0.45 1
+v -13.35 0.55 1
+v -13.62 0.85 1
+v -13.38 0.95 1
+v -12.87 0.45 1
+v -12.41 0.69 1
+v -12.76 0.27 1
+v -12.15 0.17 1
+v -12.71 0.04 1
+v -12.22 -0.12 1
+v -10.8 0.79 1
+v -11.1 0.94 1
+v -11.23 0.48 1
+v -11.67 0.95 1
+v -12.71 -1.94 1
+v -12.22 -1.94 1
+v -11.02 0.08 1
+v -10.54 0.18 1
+v -11.1 0.35 1
+v -11.44 0.56 1
+v -11.41 0.98 1
+v -10.53 -1.94 1
+v -11.01 -1.94 1
+v -10.61 0.53 1
+v -14.41 -1.94 1
+v -12.59 0.85 1
+v -13.82 0.7 1
+v -13.98 0.91 1
+v 4.33 -0.63 1
+v 3.81 -0.23 1
+v -13.93 -1.94 1
+v 3.51 -1.46 1
+v 4.31 -1.08 1
+v 3.39 -1.98 1
+v 3.7 -1.87 1
+v 3.3 -1.57 1
+v 3.05 -1.61 1
+v 1.89 -1.4 1
+v 2.25 0.01 1
+v 3.96 0.59 1
+v 3.77 0.76 1
+v 2.74 0.95 1
+v 2.77 0.55 1
+v 2.48 0.88 1
+v 2.51 0.4 1
+v 2.06 0.58 1
+v 2.36 0.24 1
+v -4.61 0.59 1
+v -4.98 0.3 1
+v -4.84 0.09 1
+v 1.89 0.36 1
+v 1.78 0.1 1
+v -6.92 -0.66 1
+v -6.32 -1.12 1
+v -6.42 -0.68 1
+v -2.86 -1.59 1
+v -2.64 -2 1
+v -2.54 -1.6 1
+v 0.62 0.31 1
+v 0.99 0.62 1
+v 0.84 0.77 1
+v 1.78 -1.15 1
+v 1.1 0.43 1
+v -6.03 -1.95 1
+v 0.61 -0.88 1
+v 0.76 -0.39 1
+v 0.38 -0.79 1
+v 2.85 -2 1
+v 16.49 -1.94 1
+v 15.89 -1.94 1
+v 2.26 0.75 1
+v 3.11 0.58 1
+v 4.23 0.12 1
+v 4.3 -0.17 1
+v -4.9 -1.24 1
+v -0.56 -1.87 1
+v -0.29 -1.97 1
+v -0.36 -1.49 1
+v -4.24 -0.28 1
+v -4.74 -0.32 1
+v -4.26 -0.88 1
+v -0.52 0.07 1
+v -0.91 -0.22 1
+v -0.78 -0.38 1
+v -6.39 -0.14 1
+v 0.95 -1.75 1
+v 0.61 -1.47 1
+v 0.72 -1.89 1
+v -5.69 0.98 1
+v -6 0.47 1
+v -5.68 0.58 1
+v 1.13 -1.56 1
+v 0.73 -1.32 1
+v 1.23 -1.33 1
+v 0.77 -1.15 1
+v 0.5 0.46 1
+v 0.62 0.88 1
+v 0.31 0.55 1
+v -0.34 -0.07 1
+v -0.53 -0.53 1
+v -4.45 0.38 1
+v -4.33 0.13 1
+v -6.71 -1.4 1
+v -6.55 -1.62 1
+v -5.58 -2 1
+v -5.75 -1.59 1
+v -4.68 -1.69 1
+v -5.16 -1.49 1
+v -5.22 -1.96 1
+v -5.47 -1.6 1
+v -6.83 -1.15 1
+v -6.89 -0.14 1
+v -6.81 0.18 1
+v -6.25 0.22 1
+v -6.67 0.44 1
+v -6.48 0.66 1
+v -6.16 0.87 1
+v -5.4 0.56 1
+v -5.13 0.92 1
+v -5.19 0.48 1
+v -4.81 0.76 1
+v -4.89 -1.83 1
+v 0.21 -1.6 1
+v 0.12 -2 1
+v 0.43 -1.98 1
+v 1.16 0.18 1
+v 0.69 0.11 1
+v -0.99 -0.04 1
+v -1.02 0.15 1
+v -0.55 0.21 1
+v -0.97 0.44 1
+v -1.52 -1.33 1
+v -2.02 -1.32 1
+v -1.63 -1.56 1
+v -1.51 -0.87 1
+v -2.02 -1 1
+v -1.98 -1.15 1
+v -0.48 0.89 1
+v -0.76 0.73 1
+v -0.39 0.49 1
+v -0.18 0.56 1
+v -0.05 0.98 1
+v 0.06 0.58 1
+v 0.34 0.96 1
+v -2.94 0.56 1
+v -2.82 0.98 1
+v -3.24 0.89 1
+v -2.69 0.58 1
+v -3.52 0.73 1
+v -3.28 0.35 1
+v -3.15 0.49 1
+v -3.31 0.21 1
+v -3.78 0.15 1
+v -3.28 0.07 1
+v -0.51 0.35 1
+v -3.29 -0.53 1
+v -2.37 -0.79 1
+v -3.1 -0.07 1
+v -1.8 -1.75 1
+v -2.14 -1.47 1
+v -2.03 -1.89 1
+v -0.54 -1.26 1
+v -0.81 -1.71 1
+v -1.02 -1.39 1
+v 0.48 -1.55 1
+v 1.24 -0.87 1
+v 1.27 -1.1 1
+v 0.73 -1 1
+v 1.02 -0.54 1
+v 1.15 -0.68 1
+v -3.12 -1.49 1
+v -3.33 -1.87 1
+v -3.05 -1.97 1
+v -0.62 -1.01 1
+v -2.32 -1.98 1
+v -2.27 -1.55 1
+v -1.1 -1.08 1
+v -1.99 -0.39 1
+v -2.14 -0.88 1
+v -0.1 -1.59 1
+v -1.74 -0.54 1
+v -2.13 0.31 1
+v -2.06 0.11 1
+v -1.65 0.43 1
+v -3.54 -0.38 1
+v -3.38 -1.01 1
+v -3.78 -1.39 1
+v -3.3 -1.26 1
+v -3.67 -0.22 1
+v -3.75 -0.04 1
+v -3.73 0.44 1
+v -2.44 0.55 1
+v -2.41 0.96 1
+v -2.13 0.88 1
+v -2.25 0.46 1
+v -1.91 0.77 1
+v -1.76 0.62 1
+v -1.59 0.18 1
+v -1.48 -1.1 1
+v -1.6 -0.68 1
+v -3.57 -1.71 1
+v -3.86 -1.08 1
+v 13.81 -1.94 1
+f 1 2 3
+f 4 5 6
+f 7 2 1
+f 8 3 9
+f 10 7 11
+f 10 2 7
+f 12 7 1
+f 13 14 15
+f 16 12 17
+f 17 12 1
+f 2 9 3
+f 9 18 8
+f 19 20 21
+f 22 19 23
+f 24 25 26
+f 27 19 28
+f 29 30 31
+f 4 32 5
+f 33 34 35
+f 36 37 38
+f 39 40 41
+f 42 37 43
+f 44 45 46
+f 47 48 49
+f 50 51 52
+f 53 52 54
+f 55 56 54
+f 57 58 59
+f 55 60 56
+f 58 61 59
+f 62 21 20
+f 54 56 53
+f 53 50 52
+f 52 51 43
+f 63 43 51
+f 42 43 63
+f 37 42 38
+f 36 38 64
+f 36 64 65
+f 66 65 67
+f 36 65 66
+f 66 67 6
+f 4 6 67
+f 49 5 47
+f 5 32 47
+f 49 48 68
+f 31 68 48
+f 31 69 68
+f 31 70 69
+f 71 72 73
+f 22 74 28
+f 19 22 28
+f 21 23 19
+f 75 21 62
+f 76 75 62
+f 60 55 75
+f 60 75 76
+f 77 78 79
+f 80 81 82
+f 83 34 84
+f 85 86 87
+f 88 89 90
+f 33 91 34
+f 92 93 94
+f 24 95 96
+f 97 98 99
+f 100 101 102
+f 102 101 103
+f 94 93 97
+f 104 105 44
+f 92 106 93
+f 107 108 109
+f 110 111 112
+f 25 24 113
+f 114 115 87
+f 116 117 118
+f 119 120 121
+f 122 121 123
+f 124 115 114
+f 87 115 85
+f 125 124 114
+f 78 90 126
+f 120 87 86
+f 121 120 86
+f 122 119 121
+f 127 15 128
+f 24 96 113
+f 24 129 95
+f 130 129 99
+f 95 129 130
+f 98 130 99
+f 93 98 97
+f 92 131 106
+f 106 131 132
+f 88 106 132
+f 89 88 132
+f 133 90 89
+f 126 90 133
+f 134 78 126
+f 79 78 134
+f 135 77 79
+f 135 125 77
+f 135 124 125
+f 71 136 72
+f 137 138 139
+f 140 141 142
+f 71 73 143
+f 144 145 146
+f 147 148 149
+f 113 150 122
+f 151 152 153
+f 152 154 155
+f 153 152 156
+f 156 152 155
+f 152 151 157
+f 150 119 122
+f 156 144 148
+f 153 156 148
+f 153 148 147
+f 156 145 144
+f 25 113 122
+f 158 146 145
+f 143 158 145
+f 159 158 143
+f 143 73 159
+f 160 161 162
+f 163 164 165
+f 165 139 163
+f 166 167 168
+f 139 138 163
+f 166 169 142
+f 169 140 142
+f 170 169 168
+f 162 171 160
+f 172 170 168
+f 170 173 174
+f 172 175 170
+f 169 170 174
+f 176 167 166
+f 177 178 179
+f 178 177 180
+f 169 166 168
+f 140 181 141
+f 182 183 184
+f 140 138 181
+f 185 182 184
+f 138 137 181
+f 186 177 179
+f 187 188 189
+f 190 178 180
+f 191 160 186
+f 192 193 194
+f 191 186 179
+f 195 196 197
+f 171 186 160
+f 198 194 193
+f 199 200 193
+f 201 202 200
+f 203 197 202
+f 204 182 205
+f 206 117 116
+f 207 208 209
+f 207 195 208
+f 197 208 195
+f 197 196 202
+f 203 202 201
+f 199 201 200
+f 192 199 193
+f 194 198 161
+f 162 161 198
+f 210 196 195
+f 209 211 207
+f 212 211 213
+f 207 211 212
+f 214 213 215
+f 212 213 214
+f 216 214 217
+f 214 215 217
+f 216 217 218
+f 219 216 220
+f 216 218 220
+f 221 220 222
+f 219 220 221
+f 222 223 221
+f 224 223 225
+f 221 223 224
+f 225 226 224
+f 224 226 227
+f 227 226 228
+f 210 227 228
+f 229 210 228
+f 230 117 231
+f 232 231 233
+f 234 232 233
+f 111 110 235
+f 127 236 237
+f 238 239 240
+f 241 242 243
+f 244 14 13
+f 239 245 240
+f 196 210 229
+f 246 245 239
+f 247 248 249
+f 236 238 240
+f 127 238 236
+f 14 128 15
+f 244 250 251
+f 14 244 251
+f 242 251 243
+f 252 247 249
+f 247 252 253
+f 248 254 249
+f 206 116 255
+f 248 241 254
+f 242 241 248
+f 250 243 251
+f 256 237 235
+f 237 236 235
+f 235 110 256
+f 128 238 127
+f 232 112 111
+f 232 234 112
+f 233 231 257
+f 258 117 206
+f 231 117 257
+f 230 118 117
+f 102 259 260
+f 261 255 116
+f 57 262 58
+f 61 263 59
+f 264 262 265
+f 262 57 265
+f 264 266 262
+f 266 264 267
+f 100 91 268
+f 103 269 270
+f 103 101 269
+f 107 109 271
+f 271 109 272
+f 272 81 80
+f 273 82 274
+f 275 44 105
+f 276 274 277
+f 278 277 279
+f 280 270 281
+f 282 283 284
+f 281 270 269
+f 285 286 287
+f 288 289 290
+f 291 101 100
+f 292 288 290
+f 293 294 41
+f 291 100 268
+f 268 91 33
+f 294 39 41
+f 83 35 34
+f 295 296 297
+f 298 83 84
+f 267 298 84
+f 264 298 267
+f 299 300 187
+f 280 279 270
+f 102 103 259
+f 280 278 279
+f 278 301 277
+f 276 277 301
+f 274 276 273
+f 274 82 302
+f 81 302 82
+f 109 81 272
+f 107 303 108
+f 108 303 259
+f 259 303 260
+f 299 187 185
+f 304 305 306
+f 307 46 45
+f 308 309 310
+f 311 312 313
+f 314 315 316
+f 317 308 318
+f 282 284 319
+f 320 314 321
+f 322 323 324
+f 321 325 326
+f 327 328 329
+f 318 296 317
+f 308 317 309
+f 330 308 331
+f 332 333 334
+f 333 307 335
+f 336 40 337
+f 293 338 339
+f 293 41 338
+f 340 341 275
+f 342 343 341
+f 39 337 40
+f 344 40 345
+f 40 336 345
+f 346 344 345
+f 347 344 346
+f 346 348 347
+f 324 348 349
+f 347 348 324
+f 324 349 322
+f 322 350 323
+f 319 350 351
+f 323 350 319
+f 282 319 351
+f 283 282 313
+f 312 283 313
+f 312 311 352
+f 334 352 353
+f 312 352 334
+f 353 332 334
+f 334 333 335
+f 335 307 45
+f 46 104 44
+f 105 340 275
+f 341 340 354
+f 342 341 354
+f 339 343 342
+f 339 338 343
+f 355 356 292
+f 331 308 310
+f 289 327 329
+f 330 331 357
+f 358 359 360
+f 361 362 363
+f 364 365 366
+f 365 364 367
+f 367 364 368
+f 328 368 369
+f 288 292 356
+f 327 289 288
+f 328 367 368
+f 329 328 369
+f 370 371 372
+f 370 372 373
+f 374 375 376
+f 365 377 366
+f 378 379 380
+f 377 357 366
+f 357 377 330
+f 381 382 383
+f 305 384 385
+f 384 386 385
+f 387 388 389
+f 297 296 318
+f 390 391 325
+f 295 392 393
+f 297 392 295
+f 392 394 393
+f 325 393 390
+f 393 394 390
+f 325 391 326
+f 320 321 326
+f 314 316 321
+f 315 388 316
+f 389 388 315
+f 395 396 397
+f 398 385 386
+f 286 399 287
+f 386 400 398
+f 401 402 380
+f 384 305 304
+f 306 305 403
+f 389 306 403
+f 387 389 403
+f 363 402 404
+f 360 362 358
+f 405 406 407
+f 379 378 408
+f 408 378 409
+f 410 411 412
+f 413 408 409
+f 408 413 414
+f 415 376 416
+f 371 375 374
+f 371 370 375
+f 373 417 418
+f 373 372 417
+f 419 418 420
+f 420 421 405
+f 407 406 422
+f 421 406 405
+f 420 405 419
+f 373 418 419
+f 376 415 374
+f 414 415 416
+f 416 408 414
+f 401 380 379
+f 361 423 362
+f 402 401 404
+f 383 382 399
+f 363 404 424
+f 361 363 424
+f 362 423 358
+f 360 359 381
+f 381 383 360
+f 287 399 382
+f 395 397 425
+f 412 411 425
+f 412 426 410
+f 425 411 395
+f 395 285 396
+f 396 285 287
+f 182 185 189
+f 205 182 189
+f 189 188 427
+f 187 189 185
+f 28 74 70
+f 28 70 30
+f 70 31 30
+f 428 429 430
+f 431 432 433
+f 434 428 430
+f 435 436 429
+f 437 438 434
+f 437 434 430
+f 439 428 434
+f 440 441 442
+f 443 444 439
+f 444 428 439
+f 430 429 436
+f 436 435 445
+f 446 447 448
+f 449 450 451
+f 452 453 446
+f 454 455 456
+f 431 433 457
+f 458 459 460
+f 461 462 463
+f 464 465 466
+f 466 467 468
+f 469 470 471
+f 472 473 468
+f 474 475 476
+f 477 473 478
+f 479 480 481
+f 482 480 483
+f 484 485 486
+f 482 483 487
+f 486 485 488
+f 489 448 447
+f 480 479 483
+f 479 481 477
+f 481 473 477
+f 472 478 473
+f 467 472 468
+f 466 490 467
+f 466 465 490
+f 464 491 465
+f 464 492 491
+f 464 432 492
+f 431 492 432
+f 475 474 433
+f 433 474 457
+f 475 493 476
+f 455 476 493
+f 455 493 494
+f 455 494 495
+f 496 497 498
+f 499 453 500
+f 446 453 499
+f 446 499 501
+f 447 446 501
+f 502 489 447
+f 503 489 502
+f 487 502 482
+f 487 503 502
+f 504 505 506
+f 507 508 509
+f 510 511 460
+f 512 513 514
+f 458 460 515
+f 516 517 518
+f 449 519 520
+f 521 522 523
+f 524 525 526
+f 525 527 528
+f 517 521 518
+f 529 530 531
+f 516 518 532
+f 533 534 535
+f 536 537 538
+f 451 539 449
+f 540 541 542
+f 449 539 543
+f 544 545 546
+f 547 548 549
+f 550 540 542
+f 541 512 542
+f 541 513 512
+f 551 540 552
+f 463 509 553
+f 549 514 513
+f 549 513 554
+f 547 549 554
+f 547 554 555
+f 556 557 441
+f 449 543 519
+f 449 520 558
+f 559 522 558
+f 520 559 558
+f 523 522 559
+f 518 521 523
+f 516 532 560
+f 462 561 560
+f 532 462 560
+f 461 561 462
+f 553 461 463
+f 508 553 509
+f 562 508 507
+f 563 562 507
+f 563 507 551
+f 563 551 552
+f 552 540 550
+f 496 498 564
+f 565 566 567
+f 568 569 570
+f 496 571 497
+f 572 573 574
+f 575 576 577
+f 578 579 580
+f 580 581 582
+f 579 583 580
+f 583 581 580
+f 580 584 578
+f 539 547 555
+f 583 577 572
+f 579 577 583
+f 579 575 577
+f 583 572 574
+f 451 547 539
+f 585 574 573
+f 571 574 585
+f 586 571 585
+f 571 586 497
+f 587 588 589
+f 590 591 592
+f 591 590 566
+f 593 594 595
+f 566 590 567
+f 593 569 596
+f 596 569 568
+f 597 594 596
+f 588 587 598
+f 599 594 597
+f 597 600 601
+f 599 597 602
+f 596 600 597
+f 603 593 595
+f 604 605 606
+f 606 607 604
+f 596 594 593
+f 568 570 608
+f 609 610 611
+f 568 608 567
+f 612 610 609
+f 567 608 565
+f 613 605 604
+f 614 615 616
+f 617 607 606
+f 618 613 587
+f 619 620 621
+f 618 605 613
+f 622 623 624
+f 598 587 613
+f 625 621 620
+f 626 621 627
+f 628 627 629
+f 630 629 631
+f 623 631 624
+f 632 633 609
+f 634 544 546
+f 635 636 623
+f 635 623 622
+f 631 629 624
+f 630 628 629
+f 626 627 628
+f 619 621 626
+f 620 589 625
+f 588 625 589
+f 637 622 624
+f 636 635 638
+f 639 640 638
+f 635 639 638
+f 641 642 640
+f 639 641 640
+f 643 644 641
+f 641 644 642
+f 643 645 644
+f 646 647 643
+f 643 647 645
+f 648 649 647
+f 646 648 647
+f 649 648 650
+f 651 652 650
+f 648 651 650
+f 652 651 653
+f 651 654 653
+f 654 655 653
+f 654 656 655
+f 637 656 654
+f 657 656 637
+f 658 659 546
+f 660 661 659
+f 662 661 660
+f 538 663 536
+f 556 664 665
+f 666 667 668
+f 669 670 671
+f 672 440 442
+f 668 667 673
+f 624 657 637
+f 674 668 673
+f 675 676 677
+f 665 667 666
+f 556 665 666
+f 442 441 557
+f 672 678 679
+f 442 678 672
+f 671 670 678
+f 680 676 675
+f 675 681 680
+f 677 676 682
+f 634 683 544
+f 677 682 669
+f 671 677 669
+f 679 678 670
+f 684 663 664
+f 664 663 665
+f 663 684 536
+f 557 556 666
+f 660 538 537
+f 660 537 662
+f 661 685 659
+f 686 634 546
+f 659 685 546
+f 658 546 545
+f 525 687 688
+f 689 544 683
+f 484 486 690
+f 488 485 691
+f 692 693 690
+f 690 693 484
+f 692 690 694
+f 694 695 692
+f 524 696 515
+f 527 697 528
+f 533 698 534
+f 698 699 534
+f 699 504 506
+f 700 701 505
+f 702 703 701
+f 704 705 703
+f 706 707 708
+f 709 710 697
+f 711 712 713
+f 710 528 697
+f 714 715 716
+f 526 525 528
+f 717 718 719
+f 720 524 526
+f 721 718 717
+f 720 696 524
+f 696 458 515
+f 722 470 469
+f 510 460 459
+f 723 724 725
+f 726 511 510
+f 695 511 726
+f 692 695 726
+f 727 614 728
+f 709 697 705
+f 525 688 527
+f 709 705 704
+f 704 703 729
+f 702 729 703
+f 701 700 702
+f 701 730 505
+f 506 505 730
+f 534 699 506
+f 533 535 731
+f 731 535 732
+f 535 688 732
+f 688 687 732
+f 727 612 614
+f 531 530 733
+f 734 735 736
+f 737 738 739
+f 740 741 742
+f 713 743 711
+f 744 745 746
+f 747 748 749
+f 750 751 744
+f 751 752 753
+f 754 755 756
+f 757 758 725
+f 757 742 758
+f 759 708 760
+f 760 708 737
+f 761 762 712
+f 722 763 764
+f 722 764 470
+f 765 733 766
+f 767 766 768
+f 469 471 762
+f 712 762 471
+f 712 769 761
+f 711 769 712
+f 711 743 770
+f 743 771 770
+f 772 773 771
+f 743 772 771
+f 773 772 774
+f 748 775 774
+f 772 748 774
+f 747 775 748
+f 776 777 747
+f 749 776 747
+f 778 777 776
+f 778 779 777
+f 707 706 779
+f 778 707 779
+f 706 708 759
+f 708 738 737
+f 738 530 739
+f 739 530 529
+f 531 733 765
+f 766 780 765
+f 767 780 766
+f 763 767 768
+f 763 768 764
+f 781 782 783
+f 784 721 785
+f 786 741 740
+f 787 786 740
+f 719 755 754
+f 788 789 787
+f 790 791 792
+f 793 794 795
+f 796 797 798
+f 798 799 796
+f 799 800 796
+f 801 802 800
+f 756 802 801
+f 717 785 721
+f 754 717 719
+f 801 800 799
+f 755 802 756
+f 803 804 805
+f 803 806 804
+f 807 808 809
+f 810 811 812
+f 798 797 813
+f 814 815 816
+f 813 797 789
+f 789 788 813
+f 740 788 787
+f 817 818 819
+f 757 740 742
+f 736 820 821
+f 821 820 822
+f 781 783 823
+f 724 757 725
+f 824 753 825
+f 723 826 827
+f 724 723 827
+f 827 826 828
+f 753 824 826
+f 826 824 828
+f 753 752 825
+f 750 752 751
+f 744 751 745
+f 746 745 823
+f 783 746 823
+f 829 830 831
+f 832 822 820
+f 716 833 834
+f 822 832 835
+f 836 815 837
+f 821 734 736
+f 735 838 736
+f 782 838 735
+f 781 838 782
+f 794 839 837
+f 791 790 795
+f 840 841 842
+f 816 843 814
+f 844 845 846
+f 847 843 812
+f 812 848 847
+f 849 810 808
+f 805 807 809
+f 805 809 803
+f 806 850 851
+f 850 852 851
+f 806 851 804
+f 853 854 852
+f 854 840 855
+f 841 856 842
+f 855 840 842
+f 854 853 840
+f 850 853 852
+f 808 807 849
+f 811 810 849
+f 848 812 811
+f 843 816 812
+f 836 816 815
+f 793 795 857
+f 837 839 836
+f 818 834 819
+f 794 858 839
+f 793 858 794
+f 795 790 857
+f 791 817 792
+f 817 791 818
+f 833 819 834
+f 829 859 830
+f 845 859 846
+f 845 844 860
+f 859 829 846
+f 829 831 714
+f 831 715 714
+f 715 833 716
+f 609 615 612
+f 633 615 609
+f 615 861 616
+f 614 612 615
+f 453 495 500
+f 453 456 495
+f 495 456 455
+f 18 445 8
+f 435 8 445
+f 9 436 18
+f 445 18 436
+f 2 430 9
+f 436 9 430
+f 10 437 2
+f 430 2 437
+f 11 438 10
+f 437 10 438
+f 7 434 11
+f 438 11 434
+f 12 439 7
+f 434 7 439
+f 16 443 12
+f 439 12 443
+f 17 444 16
+f 443 16 444
+f 1 428 17
+f 444 17 428
+f 3 429 1
+f 428 1 429
+f 8 435 3
+f 429 3 435
+f 76 503 60
+f 487 60 503
+f 62 489 76
+f 503 76 489
+f 20 448 62
+f 489 62 448
+f 19 446 20
+f 448 20 446
+f 27 452 19
+f 446 19 452
+f 28 453 27
+f 452 27 453
+f 30 456 28
+f 453 28 456
+f 29 454 30
+f 456 30 454
+f 31 455 29
+f 454 29 455
+f 48 476 31
+f 455 31 476
+f 47 474 48
+f 476 48 474
+f 32 457 47
+f 474 47 457
+f 4 431 32
+f 457 32 431
+f 67 492 4
+f 431 4 492
+f 65 491 67
+f 492 67 491
+f 64 465 65
+f 491 65 465
+f 38 490 64
+f 465 64 490
+f 42 467 38
+f 490 38 467
+f 63 472 42
+f 467 42 472
+f 51 478 63
+f 472 63 478
+f 50 477 51
+f 478 51 477
+f 53 479 50
+f 477 50 479
+f 56 483 53
+f 479 53 483
+f 60 487 56
+f 483 56 487
+f 55 482 75
+f 502 75 482
+f 54 480 55
+f 482 55 480
+f 52 481 54
+f 480 54 481
+f 481 52 473
+f 43 473 52
+f 473 43 468
+f 37 468 43
+f 468 37 466
+f 36 466 37
+f 466 36 464
+f 66 464 36
+f 6 432 66
+f 464 66 432
+f 5 433 6
+f 432 6 433
+f 49 475 5
+f 433 5 475
+f 68 493 49
+f 475 49 493
+f 69 494 68
+f 493 68 494
+f 70 495 69
+f 494 69 495
+f 74 500 70
+f 495 70 500
+f 22 499 74
+f 500 74 499
+f 499 22 501
+f 23 501 22
+f 21 447 23
+f 501 23 447
+f 75 502 21
+f 447 21 502
+f 550 124 552
+f 135 563 124
+f 552 124 563
+f 79 562 135
+f 563 135 562
+f 134 508 79
+f 562 79 508
+f 126 553 134
+f 508 134 553
+f 133 553 126
+f 89 461 133
+f 553 133 461
+f 132 561 89
+f 461 89 561
+f 131 560 132
+f 561 132 560
+f 92 516 131
+f 560 131 516
+f 94 517 92
+f 516 92 517
+f 97 521 94
+f 517 94 521
+f 99 522 97
+f 521 97 522
+f 129 558 99
+f 522 99 558
+f 24 449 129
+f 558 129 449
+f 26 450 24
+f 449 24 450
+f 25 451 26
+f 450 26 451
+f 122 547 25
+f 451 25 547
+f 123 548 122
+f 547 122 548
+f 121 549 123
+f 548 123 549
+f 86 514 121
+f 549 121 514
+f 85 512 86
+f 514 86 512
+f 115 542 85
+f 512 85 542
+f 124 550 115
+f 542 115 550
+f 87 541 114
+f 540 114 541
+f 541 87 513
+f 120 513 87
+f 513 120 554
+f 119 554 120
+f 554 119 555
+f 150 555 119
+f 555 150 539
+f 113 539 150
+f 539 113 543
+f 96 519 113
+f 543 113 519
+f 95 520 96
+f 519 96 520
+f 130 559 95
+f 520 95 559
+f 98 523 130
+f 559 130 523
+f 93 518 98
+f 523 98 518
+f 106 532 93
+f 518 93 532
+f 532 106 462
+f 88 462 106
+f 462 88 463
+f 90 463 88
+f 463 90 509
+f 78 509 90
+f 509 78 507
+f 77 507 78
+f 125 551 77
+f 507 77 551
+f 114 540 125
+f 551 125 540
+f 72 498 73
+f 497 73 498
+f 136 564 72
+f 498 72 564
+f 71 496 136
+f 564 136 496
+f 143 571 71
+f 496 71 571
+f 145 574 143
+f 571 143 574
+f 156 583 145
+f 574 145 583
+f 155 581 156
+f 583 156 581
+f 154 582 155
+f 581 155 582
+f 152 580 154
+f 582 154 580
+f 157 584 152
+f 580 152 584
+f 151 578 157
+f 584 157 578
+f 153 579 151
+f 578 151 579
+f 147 575 153
+f 579 153 575
+f 149 576 147
+f 575 147 576
+f 148 577 149
+f 576 149 577
+f 144 572 148
+f 577 148 572
+f 146 573 144
+f 572 144 573
+f 158 585 146
+f 573 146 585
+f 159 586 158
+f 585 158 586
+f 73 497 159
+f 586 159 497
+f 165 591 139
+f 566 139 591
+f 164 592 165
+f 591 165 592
+f 163 590 164
+f 592 164 590
+f 138 567 163
+f 590 163 567
+f 140 568 138
+f 567 138 568
+f 169 596 140
+f 568 140 596
+f 174 600 169
+f 596 169 600
+f 173 601 174
+f 600 174 601
+f 170 597 173
+f 601 173 597
+f 175 602 170
+f 597 170 602
+f 172 599 175
+f 602 175 599
+f 168 594 172
+f 599 172 594
+f 167 595 168
+f 594 168 595
+f 176 603 167
+f 595 167 603
+f 166 593 176
+f 603 176 593
+f 142 569 166
+f 593 166 569
+f 141 570 142
+f 569 142 570
+f 181 608 141
+f 570 141 608
+f 137 565 181
+f 608 181 565
+f 139 566 137
+f 565 137 566
+f 160 587 161
+f 589 161 587
+f 191 618 160
+f 587 160 618
+f 179 605 191
+f 618 191 605
+f 178 606 179
+f 605 179 606
+f 190 617 178
+f 606 178 617
+f 180 607 190
+f 617 190 607
+f 177 604 180
+f 607 180 604
+f 186 613 177
+f 604 177 613
+f 171 598 186
+f 613 186 598
+f 162 588 171
+f 598 171 588
+f 198 625 162
+f 588 162 625
+f 193 621 198
+f 625 198 621
+f 200 627 193
+f 621 193 627
+f 202 629 200
+f 627 200 629
+f 196 624 202
+f 629 202 624
+f 229 657 196
+f 624 196 657
+f 228 656 229
+f 657 229 656
+f 656 228 655
+f 226 653 228
+f 655 228 653
+f 225 652 226
+f 653 226 652
+f 223 650 225
+f 652 225 650
+f 222 649 223
+f 650 223 649
+f 220 647 222
+f 649 222 647
+f 218 645 220
+f 647 220 645
+f 217 644 218
+f 645 218 644
+f 215 642 217
+f 644 217 642
+f 213 640 215
+f 642 215 640
+f 211 638 213
+f 640 213 638
+f 209 636 211
+f 638 211 636
+f 208 623 209
+f 636 209 623
+f 197 623 208
+f 623 197 631
+f 203 630 197
+f 631 197 630
+f 201 628 203
+f 630 203 628
+f 199 626 201
+f 628 201 626
+f 192 619 199
+f 626 199 619
+f 194 620 192
+f 619 192 620
+f 161 589 194
+f 620 194 589
+f 195 622 210
+f 637 210 622
+f 207 635 195
+f 622 195 635
+f 212 639 207
+f 635 207 639
+f 214 641 212
+f 639 212 641
+f 216 643 214
+f 641 214 643
+f 219 646 216
+f 643 216 646
+f 221 648 219
+f 646 219 648
+f 224 651 221
+f 648 221 651
+f 227 654 224
+f 651 224 654
+f 210 637 227
+f 654 227 637
+f 116 544 261
+f 689 261 544
+f 118 545 116
+f 544 116 545
+f 230 658 118
+f 545 118 658
+f 231 659 230
+f 658 230 659
+f 232 660 231
+f 659 231 660
+f 111 538 232
+f 660 232 538
+f 235 663 111
+f 538 111 663
+f 236 665 235
+f 663 235 665
+f 240 667 236
+f 665 236 667
+f 245 673 240
+f 667 240 673
+f 246 674 245
+f 673 245 674
+f 239 668 246
+f 674 246 668
+f 238 666 239
+f 668 239 666
+f 128 557 238
+f 666 238 557
+f 14 442 128
+f 557 128 442
+f 251 678 14
+f 442 14 678
+f 242 671 251
+f 678 251 671
+f 248 677 242
+f 671 242 677
+f 247 675 248
+f 677 248 675
+f 253 681 247
+f 675 247 681
+f 252 680 253
+f 681 253 680
+f 249 676 252
+f 680 252 676
+f 254 682 249
+f 676 249 682
+f 241 669 254
+f 682 254 669
+f 243 670 241
+f 669 241 670
+f 250 679 243
+f 670 243 679
+f 244 672 250
+f 679 250 672
+f 13 440 244
+f 672 244 440
+f 15 441 13
+f 440 13 441
+f 127 556 15
+f 441 15 556
+f 237 664 127
+f 556 127 664
+f 256 684 237
+f 664 237 684
+f 110 536 256
+f 684 256 536
+f 112 537 110
+f 536 110 537
+f 234 662 112
+f 537 112 662
+f 233 661 234
+f 662 234 661
+f 257 685 233
+f 661 233 685
+f 117 546 257
+f 685 257 546
+f 258 686 117
+f 546 117 686
+f 206 634 258
+f 686 258 634
+f 255 683 206
+f 634 206 683
+f 261 689 255
+f 683 255 689
+f 264 692 298
+f 726 298 692
+f 265 693 264
+f 692 264 693
+f 57 484 265
+f 693 265 484
+f 59 485 57
+f 484 57 485
+f 263 691 59
+f 485 59 691
+f 61 488 263
+f 691 263 488
+f 58 486 61
+f 488 61 486
+f 262 690 58
+f 486 58 690
+f 266 694 262
+f 690 262 694
+f 267 695 266
+f 694 266 695
+f 84 511 267
+f 695 267 511
+f 34 460 84
+f 511 84 460
+f 91 515 34
+f 460 34 515
+f 100 524 91
+f 515 91 524
+f 102 525 100
+f 524 100 525
+f 260 687 102
+f 525 102 687
+f 303 732 260
+f 687 260 732
+f 732 303 731
+f 107 533 303
+f 731 303 533
+f 271 698 107
+f 533 107 698
+f 272 699 271
+f 698 271 699
+f 80 504 272
+f 699 272 504
+f 82 505 80
+f 504 80 505
+f 273 700 82
+f 505 82 700
+f 276 702 273
+f 700 273 702
+f 301 729 276
+f 702 276 729
+f 278 704 301
+f 729 301 704
+f 280 709 278
+f 704 278 709
+f 281 710 280
+f 709 280 710
+f 269 528 281
+f 710 281 528
+f 101 528 269
+f 528 101 526
+f 291 720 101
+f 526 101 720
+f 268 696 291
+f 720 291 696
+f 33 458 268
+f 696 268 458
+f 35 459 33
+f 458 33 459
+f 83 510 35
+f 459 35 510
+f 298 726 83
+f 510 83 726
+f 103 527 259
+f 688 259 527
+f 270 697 103
+f 527 103 697
+f 279 705 270
+f 697 270 705
+f 277 703 279
+f 705 279 703
+f 274 701 277
+f 703 277 701
+f 302 730 274
+f 701 274 730
+f 81 506 302
+f 730 302 506
+f 109 534 81
+f 506 81 534
+f 108 535 109
+f 534 109 535
+f 259 688 108
+f 535 108 688
+f 342 767 339
+f 763 339 767
+f 354 780 342
+f 767 342 780
+f 340 765 354
+f 780 354 765
+f 105 531 340
+f 765 340 531
+f 104 529 105
+f 531 105 529
+f 46 739 104
+f 529 104 739
+f 307 737 46
+f 739 46 737
+f 333 760 307
+f 737 307 760
+f 332 759 333
+f 760 333 759
+f 353 706 332
+f 759 332 706
+f 352 779 353
+f 706 353 779
+f 311 777 352
+f 779 352 777
+f 313 777 311
+f 282 747 313
+f 777 313 747
+f 351 775 282
+f 747 282 775
+f 350 774 351
+f 775 351 774
+f 322 773 350
+f 774 350 773
+f 349 771 322
+f 773 322 771
+f 348 770 349
+f 771 349 770
+f 346 711 348
+f 770 348 711
+f 345 769 346
+f 711 346 769
+f 336 761 345
+f 769 345 761
+f 337 762 336
+f 761 336 762
+f 39 469 337
+f 762 337 469
+f 294 722 39
+f 469 39 722
+f 293 722 294
+f 339 763 293
+f 722 293 763
+f 768 338 764
+f 41 470 338
+f 764 338 470
+f 40 471 41
+f 470 41 471
+f 471 40 712
+f 344 712 40
+f 712 344 713
+f 347 713 344
+f 713 347 743
+f 324 743 347
+f 743 324 772
+f 323 772 324
+f 319 748 323
+f 772 323 748
+f 284 749 319
+f 748 319 749
+f 283 749 284
+f 749 283 776
+f 312 778 283
+f 776 283 778
+f 334 707 312
+f 778 312 707
+f 707 334 708
+f 335 708 334
+f 708 335 738
+f 45 738 335
+f 738 45 530
+f 44 530 45
+f 530 44 733
+f 275 733 44
+f 341 766 275
+f 733 275 766
+f 343 768 341
+f 766 341 768
+f 338 768 343
+f 782 389 783
+f 315 746 389
+f 783 389 746
+f 314 744 315
+f 746 315 744
+f 320 750 314
+f 744 314 750
+f 326 752 320
+f 750 320 752
+f 391 825 326
+f 752 326 825
+f 390 824 391
+f 825 391 824
+f 394 828 390
+f 824 390 828
+f 392 827 394
+f 828 394 827
+f 297 724 392
+f 827 392 724
+f 318 757 297
+f 724 297 757
+f 308 757 318
+f 757 308 740
+f 330 740 308
+f 740 330 788
+f 377 813 330
+f 788 330 813
+f 365 798 377
+f 813 377 798
+f 367 799 365
+f 798 365 799
+f 328 801 367
+f 799 367 801
+f 801 328 756
+f 327 754 328
+f 756 328 754
+f 288 717 327
+f 754 327 717
+f 356 785 288
+f 717 288 785
+f 355 784 356
+f 785 356 784
+f 292 721 355
+f 784 355 721
+f 290 718 292
+f 721 292 718
+f 289 719 290
+f 718 290 719
+f 329 755 289
+f 719 289 755
+f 369 802 329
+f 755 329 802
+f 368 800 369
+f 802 369 800
+f 364 796 368
+f 800 368 796
+f 366 797 364
+f 796 364 797
+f 357 789 366
+f 797 366 789
+f 331 787 357
+f 789 357 787
+f 787 331 786
+f 310 741 331
+f 786 331 741
+f 309 742 310
+f 741 310 742
+f 317 758 309
+f 742 309 758
+f 296 725 317
+f 758 317 725
+f 295 723 296
+f 725 296 723
+f 393 826 295
+f 723 295 826
+f 325 753 393
+f 826 393 753
+f 321 751 325
+f 753 325 751
+f 316 745 321
+f 751 321 745
+f 388 823 316
+f 745 316 823
+f 387 781 388
+f 823 388 781
+f 403 838 387
+f 781 387 838
+f 305 736 403
+f 838 403 736
+f 385 820 305
+f 736 305 820
+f 398 832 385
+f 820 385 832
+f 400 835 398
+f 832 398 835
+f 386 822 400
+f 835 400 822
+f 384 821 386
+f 822 386 821
+f 304 734 384
+f 821 384 734
+f 306 735 304
+f 734 304 735
+f 389 782 306
+f 735 306 782
+f 715 287 833
+f 382 819 287
+f 833 287 819
+f 381 817 382
+f 819 382 817
+f 359 792 381
+f 817 381 792
+f 358 790 359
+f 792 359 790
+f 423 857 358
+f 790 358 857
+f 361 793 423
+f 857 423 793
+f 424 858 361
+f 793 361 858
+f 404 839 424
+f 858 424 839
+f 401 836 404
+f 839 404 836
+f 379 816 401
+f 836 401 816
+f 408 816 379
+f 816 408 812
+f 416 812 408
+f 812 416 810
+f 376 808 416
+f 810 416 808
+f 375 809 376
+f 808 376 809
+f 370 803 375
+f 809 375 803
+f 373 806 370
+f 803 370 806
+f 806 373 850
+f 419 853 373
+f 850 373 853
+f 405 840 419
+f 853 419 840
+f 407 841 405
+f 840 405 841
+f 422 856 407
+f 841 407 856
+f 406 842 422
+f 856 422 842
+f 421 855 406
+f 842 406 855
+f 420 854 421
+f 855 421 854
+f 418 852 420
+f 854 420 852
+f 417 851 418
+f 852 418 851
+f 372 804 417
+f 851 417 804
+f 371 805 372
+f 804 372 805
+f 374 807 371
+f 805 371 807
+f 415 849 374
+f 807 374 849
+f 414 811 415
+f 849 415 811
+f 811 414 848
+f 413 847 414
+f 848 414 847
+f 409 843 413
+f 847 413 843
+f 378 814 409
+f 843 409 814
+f 380 815 378
+f 814 378 815
+f 402 837 380
+f 815 380 837
+f 363 794 402
+f 837 402 794
+f 362 795 363
+f 794 363 795
+f 360 791 362
+f 795 362 791
+f 383 818 360
+f 791 360 818
+f 399 834 383
+f 818 383 834
+f 286 716 399
+f 834 399 716
+f 285 714 286
+f 716 286 714
+f 395 829 285
+f 714 285 829
+f 411 846 395
+f 829 395 846
+f 410 844 411
+f 846 411 844
+f 426 860 410
+f 844 410 860
+f 412 845 426
+f 860 426 845
+f 425 859 412
+f 845 412 859
+f 397 830 425
+f 859 425 830
+f 396 831 397
+f 830 397 831
+f 287 715 396
+f 831 396 715
+f 187 614 188
+f 616 188 614
+f 300 728 187
+f 614 187 728
+f 299 727 300
+f 728 300 727
+f 185 612 299
+f 727 299 612
+f 184 610 185
+f 612 185 610
+f 183 611 184
+f 610 184 611
+f 182 609 183
+f 611 183 609
+f 204 632 182
+f 609 182 632
+f 205 633 204
+f 632 204 633
+f 189 615 205
+f 633 205 615
+f 427 861 189
+f 615 189 861
+f 188 616 427
+f 861 427 616
diff --git a/resources/icons/add_text_modifier.svg b/resources/icons/add_text_modifier.svg
new file mode 100644
index 0000000000..0a8376741f
--- /dev/null
+++ b/resources/icons/add_text_modifier.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/icons/add_text_negative.svg b/resources/icons/add_text_negative.svg
new file mode 100644
index 0000000000..866dd076e0
--- /dev/null
+++ b/resources/icons/add_text_negative.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/icons/add_text_part.svg b/resources/icons/add_text_part.svg
new file mode 100644
index 0000000000..4a75a5d833
--- /dev/null
+++ b/resources/icons/add_text_part.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/icons/make_bold.svg b/resources/icons/make_bold.svg
new file mode 100644
index 0000000000..7d8eb226c1
--- /dev/null
+++ b/resources/icons/make_bold.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/icons/make_italic.svg b/resources/icons/make_italic.svg
new file mode 100644
index 0000000000..c91ee041a7
--- /dev/null
+++ b/resources/icons/make_italic.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/icons/make_unbold.svg b/resources/icons/make_unbold.svg
new file mode 100644
index 0000000000..41f92d1561
--- /dev/null
+++ b/resources/icons/make_unbold.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/icons/make_unitalic.svg b/resources/icons/make_unitalic.svg
new file mode 100644
index 0000000000..9cefaa6383
--- /dev/null
+++ b/resources/icons/make_unitalic.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/icons/search.svg b/resources/icons/search.svg
index 6421c7e055..c6cdd007e4 100644
--- a/resources/icons/search.svg
+++ b/resources/icons/search.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
+
+
diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp
index a8fbc2a87d..807d9ef4c4 100644
--- a/src/admesh/shared.cpp
+++ b/src/admesh/shared.cpp
@@ -207,6 +207,30 @@ bool its_write_obj(const indexed_triangle_set &its, const char *file)
return true;
}
+bool its_write_obj(const indexed_triangle_set& its, const std::vector &color, const char* file)
+{
+ Slic3r::CNumericLocalesSetter locales_setter;
+ FILE* fp = fopen(file, "w");
+ if (fp == nullptr) {
+ return false;
+ }
+
+ for (size_t i = 0; i < its.vertices.size(); ++i)
+ fprintf(fp, "v %f %f %f %f %f %f\n",
+ its.vertices[i](0),
+ its.vertices[i](1),
+ its.vertices[i](2),
+ color[i](0),
+ color[i](1),
+ color[i](2));
+ for (size_t i = 0; i < its.indices.size(); ++i)
+ fprintf(fp, "f %d %d %d\n",
+ its.indices[i][0] + 1,
+ its.indices[i][1] + 1,
+ its.indices[i][2] + 1);
+ fclose(fp);
+ return true;
+}
// Check validity of the mesh, assert on error.
bool stl_validate(const stl_file *stl, const indexed_triangle_set &its)
diff --git a/src/admesh/stl.h b/src/admesh/stl.h
index 8c30a6ae5d..ac51ae1b68 100644
--- a/src/admesh/stl.h
+++ b/src/admesh/stl.h
@@ -303,6 +303,17 @@ extern bool its_write_obj(const indexed_triangle_set &its, const char *file);
extern bool its_write_off(const indexed_triangle_set &its, const char *file);
extern bool its_write_vrml(const indexed_triangle_set &its, const char *file);
+
+typedef Eigen::Matrix obj_color; // Vec3f
+///
+/// write idexed triangle set into obj file with color
+///
+/// input model
+/// color of stored model
+/// define place to store
+/// True on success otherwise FALSE
+extern bool its_write_obj(const indexed_triangle_set& its, const std::vector &color, const char* file);
+
extern bool stl_write_dxf(stl_file *stl, const char *file, char *label);
inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) {
normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]);
diff --git a/src/imgui/CMakeLists.txt b/src/imgui/CMakeLists.txt
index 235afe1105..5239eb3d5c 100644
--- a/src/imgui/CMakeLists.txt
+++ b/src/imgui/CMakeLists.txt
@@ -3,14 +3,17 @@ project(imgui)
add_library(imgui STATIC
imconfig.h
+ imgui.cpp
imgui.h
+ imgui_demo.cpp
+ imgui_draw.cpp
imgui_internal.h
+ imgui_stdlib.cpp
+ imgui_stdlib.h
+ imgui_tables.cpp
+ imgui_widgets.cpp
+ # imgui STB
imstb_rectpack.h
imstb_textedit.h
imstb_truetype.h
- imgui_tables.cpp
- imgui.cpp
- imgui_demo.cpp
- imgui_draw.cpp
- imgui_widgets.cpp
)
diff --git a/src/imgui/README.md b/src/imgui/README.md
index c937d975b7..58008bded0 100644
--- a/src/imgui/README.md
+++ b/src/imgui/README.md
@@ -4,6 +4,19 @@ For more information go to https://github.com/ocornut/imgui
THIS DIRECTORY CONTAINS THE imgui-1.83 ad5d1a8 SOURCE DISTRIBUTION.
+
Customized with the following commits:
f93d0001baa5443da2c6510d11b03c675e652418
b71d787f695c779e571865d5214d4da8d50aa7c5
+
+imgui_stdlib.h + imgui_stdlib.cpp are move from directory /imgui/misc/cpp/
+InputText() wrappers for C++ standard library (STL) type: std::string.
+This is also an example of how you may wrap your own similar types.
+
+imstb_truetype.h modification:
+
+Hot fix for open symbolic fonts on windows
+62bdfe6f8d04b88e8bd511cd613be80c0baa7f55
+
+Hot fix for open curved fonts mainly on MAC
+2148e49f75d82cb19dc6ec409fb7825296ed005c
diff --git a/src/imgui/imgui_stdlib.cpp b/src/imgui/imgui_stdlib.cpp
new file mode 100644
index 0000000000..cb1fe1743d
--- /dev/null
+++ b/src/imgui/imgui_stdlib.cpp
@@ -0,0 +1,76 @@
+// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
+// This is also an example of how you may wrap your own similar types.
+
+// Compatibility:
+// - std::string support is only guaranteed to work from C++11.
+// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture)
+
+// Changelog:
+// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
+
+#include "imgui.h"
+#include "imgui_stdlib.h"
+
+struct InputTextCallback_UserData
+{
+ std::string* Str;
+ ImGuiInputTextCallback ChainCallback;
+ void* ChainCallbackUserData;
+};
+
+static int InputTextCallback(ImGuiInputTextCallbackData* data)
+{
+ InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData;
+ if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
+ {
+ // Resize string callback
+ // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want.
+ std::string* str = user_data->Str;
+ IM_ASSERT(data->Buf == str->c_str());
+ str->resize(data->BufTextLen);
+ data->Buf = (char*)str->c_str();
+ }
+ else if (user_data->ChainCallback)
+ {
+ // Forward to user callback, if any
+ data->UserData = user_data->ChainCallbackUserData;
+ return user_data->ChainCallback(data);
+ }
+ return 0;
+}
+
+bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+ flags |= ImGuiInputTextFlags_CallbackResize;
+
+ InputTextCallback_UserData cb_user_data;
+ cb_user_data.Str = str;
+ cb_user_data.ChainCallback = callback;
+ cb_user_data.ChainCallbackUserData = user_data;
+ return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
+}
+
+bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+ flags |= ImGuiInputTextFlags_CallbackResize;
+
+ InputTextCallback_UserData cb_user_data;
+ cb_user_data.Str = str;
+ cb_user_data.ChainCallback = callback;
+ cb_user_data.ChainCallbackUserData = user_data;
+ return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data);
+}
+
+bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+ flags |= ImGuiInputTextFlags_CallbackResize;
+
+ InputTextCallback_UserData cb_user_data;
+ cb_user_data.Str = str;
+ cb_user_data.ChainCallback = callback;
+ cb_user_data.ChainCallbackUserData = user_data;
+ return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
+}
diff --git a/src/imgui/imgui_stdlib.h b/src/imgui/imgui_stdlib.h
new file mode 100644
index 0000000000..f860b0c780
--- /dev/null
+++ b/src/imgui/imgui_stdlib.h
@@ -0,0 +1,22 @@
+// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
+// This is also an example of how you may wrap your own similar types.
+
+// Compatibility:
+// - std::string support is only guaranteed to work from C++11.
+// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture)
+
+// Changelog:
+// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
+
+#pragma once
+
+#include
+
+namespace ImGui
+{
+ // ImGui::InputText() with std::string
+ // Because text input needs dynamic resizing, we need to setup a callback to grow the capacity
+ IMGUI_API bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+ IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+ IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+}
diff --git a/src/imgui/imstb_truetype.h b/src/imgui/imstb_truetype.h
index fc815d7452..90a4a31445 100644
--- a/src/imgui/imstb_truetype.h
+++ b/src/imgui/imstb_truetype.h
@@ -1437,6 +1437,7 @@ static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, in
switch(ttUSHORT(data+encoding_record)) {
case STBTT_PLATFORM_ID_MICROSOFT:
switch (ttUSHORT(data+encoding_record+2)) {
+ case STBTT_MS_EID_SYMBOL:
case STBTT_MS_EID_UNICODE_BMP:
case STBTT_MS_EID_UNICODE_FULL:
// MS/Unicode
@@ -1734,7 +1735,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
// now start the new one
start_off = !(flags & 1);
- if (start_off) {
+ if (start_off && (i + 1) < n) {
// if we start off with an off-curve point, then when we need to find a point on the curve
// where we can start, and we need to save some state for when we wraparound.
scx = x;
diff --git a/src/libslic3r/AABBTreeLines.hpp b/src/libslic3r/AABBTreeLines.hpp
index 8432feda74..6426b7d121 100644
--- a/src/libslic3r/AABBTreeLines.hpp
+++ b/src/libslic3r/AABBTreeLines.hpp
@@ -90,20 +90,21 @@ inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over
// Finding a closest line, its closest point and squared distance to the closest point
// Returns squared distance to the closest point or -1 if the input is empty.
+// or no closer point than max_sq_dist
template
-inline typename VectorType::Scalar squared_distance_to_indexed_lines(const std::vector &lines,
- const TreeType &tree,
- const VectorType &point,
- size_t &hit_idx_out,
- Eigen::PlainObjectBase &hit_point_out)
+inline typename VectorType::Scalar squared_distance_to_indexed_lines(
+ const std::vector &lines,
+ const TreeType &tree,
+ const VectorType &point,
+ size_t &hit_idx_out,
+ Eigen::PlainObjectBase &hit_point_out,
+ typename VectorType::Scalar max_sqr_dist = std::numeric_limits::infinity())
{
- using Scalar = typename VectorType::Scalar;
+ using Scalar = typename VectorType::Scalar;
+ if (tree.empty()) return Scalar(-1);
auto distancer = detail::IndexedLinesDistancer{lines, tree, point};
- return tree.empty() ?
- Scalar(-1) :
- AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0),
- std::numeric_limits::infinity(),
- hit_idx_out, hit_point_out);
+ return AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(
+ distancer, size_t(0), Scalar(0), max_sqr_dist, hit_idx_out, hit_point_out);
}
// Returns all lines within the given radius limit
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index d3b7a3b856..8c879cde05 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -39,6 +39,7 @@ static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-conte
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
+const std::string AppConfig::SECTION_EMBOSS_STYLE = "font";
void AppConfig::reset()
{
diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp
index cd0f1a5aeb..5658f142d5 100644
--- a/src/libslic3r/AppConfig.hpp
+++ b/src/libslic3r/AppConfig.hpp
@@ -167,6 +167,7 @@ public:
static const std::string SECTION_FILAMENTS;
static const std::string SECTION_MATERIALS;
+ static const std::string SECTION_EMBOSS_STYLE;
private:
template
diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp
index ecc36de7b2..a501680a1f 100644
--- a/src/libslic3r/BoundingBox.hpp
+++ b/src/libslic3r/BoundingBox.hpp
@@ -53,7 +53,7 @@ public:
PointClass size() const;
double radius() const;
void translate(coordf_t x, coordf_t y) { assert(this->defined); PointClass v(x, y); this->min += v; this->max += v; }
- void translate(const Vec2d &v) { this->min += v; this->max += v; }
+ void translate(const PointClass &v) { this->min += v; this->max += v; }
void offset(coordf_t delta);
BoundingBoxBase inflated(coordf_t delta) const throw() { BoundingBoxBase out(*this); out.offset(delta); return out; }
PointClass center() const;
@@ -174,6 +174,7 @@ public:
BoundingBox rotated(double angle, const Point ¢er) const;
void rotate(double angle) { (*this) = this->rotated(angle); }
void rotate(double angle, const Point ¢er) { (*this) = this->rotated(angle, center); }
+ bool intersects(const BoundingBox &other) const { return this->min(0) <= other.max(0) && this->max(0) >= other.min(0) && this->min(1) <= other.max(1) && this->max(1) >= other.min(1); }
// Align the min corner to a grid of cell_size x cell_size cells,
// to encompass the original bounding box.
void align_to_grid(const coord_t cell_size);
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 53cbd14666..bee7ceb628 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -19,6 +19,7 @@ set(SLIC3R_SOURCES
pchheader.hpp
AStar.hpp
AABBTreeIndirect.hpp
+ AABBTreeLines.hpp
AABBMesh.hpp
AABBMesh.cpp
BoundingBox.cpp
@@ -42,9 +43,13 @@ set(SLIC3R_SOURCES
EdgeGrid.hpp
ElephantFootCompensation.cpp
ElephantFootCompensation.hpp
+ Emboss.cpp
+ Emboss.hpp
enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
+ ExPolygonsIndex.cpp
+ ExPolygonsIndex.hpp
Extruder.cpp
Extruder.hpp
ExtrusionEntity.cpp
@@ -178,6 +183,7 @@ set(SLIC3R_SOURCES
Model.hpp
ModelArrange.hpp
ModelArrange.cpp
+ #ModelVolumeType.hpp
MultiMaterialSegmentation.cpp
MultiMaterialSegmentation.hpp
MeshNormals.hpp
@@ -194,6 +200,8 @@ set(SLIC3R_SOURCES
MutablePriorityQueue.hpp
NormalUtils.cpp
NormalUtils.hpp
+ NSVGUtils.cpp
+ NSVGUtils.hpp
ObjectID.cpp
ObjectID.hpp
PerimeterGenerator.cpp
@@ -266,6 +274,7 @@ set(SLIC3R_SOURCES
Technologies.hpp
Tesselate.cpp
Tesselate.hpp
+ TextConfiguration.hpp
TreeSupport.cpp
TreeSupport.hpp
TreeModelVolumes.cpp
@@ -280,6 +289,8 @@ set(SLIC3R_SOURCES
Utils.hpp
Time.cpp
Time.hpp
+ Timer.cpp
+ Timer.hpp
Thread.cpp
Thread.hpp
TriangleSelector.cpp
@@ -403,8 +414,14 @@ cmake_policy(SET CMP0011 NEW)
find_package(CGAL REQUIRED)
cmake_policy(POP)
-add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp TryCatchSignal.hpp
- TryCatchSignal.cpp Geometry/VoronoiUtilsCgal.hpp Geometry/VoronoiUtilsCgal.cpp)
+add_library(libslic3r_cgal STATIC
+ CutSurface.hpp CutSurface.cpp
+ Geometry/VoronoiUtilsCgal.hpp Geometry/VoronoiUtilsCgal.cpp
+ IntersectionPoints.hpp IntersectionPoints.cpp
+ MeshBoolean.hpp MeshBoolean.cpp
+ TryCatchSignal.hpp TryCatchSignal.cpp
+ Triangulation.hpp Triangulation.cpp
+)
target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options
@@ -476,6 +493,7 @@ if (APPLE)
# This flag prevents the need for minimum SDK version 10.14
# currently, PS targets v10.12
target_compile_options(libslic3r PUBLIC "-fno-aligned-allocation")
+ target_compile_options(libslic3r_cgal PUBLIC "-fno-aligned-allocation")
endif ()
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp
new file mode 100644
index 0000000000..375b6b7a0c
--- /dev/null
+++ b/src/libslic3r/CutSurface.cpp
@@ -0,0 +1,3990 @@
+#include "CutSurface.hpp"
+
+/// models_input.obj - Check transormation of model to each others
+/// projection_center.obj - circle representing center of projection with correct distance
+/// {M} .. model index
+/// model/model{M}.off - CGAL model created from index_triangle_set
+/// model_neg/model{M}.off - CGAL model created for differenciate (multi volume object)
+/// shape.off - CGAL model created from shapes
+/// constrained/model{M}.off - Visualization of inside and outside triangles
+/// Green - not along constrained edge
+/// Red - sure that are inside
+/// Purple - sure that are outside
+/// (only along constrained edge)
+/// filled/model{M}.off - flood fill green triangles inside of red area
+/// - Same meaning of color as constrained
+/// {N} .. Order of cutted Area of Interestmodel from model surface
+/// model_AOIs/{M}/cutAOI{N}.obj - Extracted Area of interest from corefined model
+/// model_AOIs/{M}/outline{N}.obj - Outline of Cutted Area
+/// {O} .. Order number of patch
+/// patches/patch{O}.off
+/// result.obj - Merged result its
+/// result_contours/{O}.obj - visualization of contours for result patches
+//#define DEBUG_OUTPUT_DIR std::string("C:/data/temp/cutSurface/")
+
+using namespace Slic3r;
+#include "ExPolygonsIndex.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+// libslic3r
+#include "TriangleMesh.hpp" // its_merge
+#include "Utils.hpp" // next_highest_power_of_2
+#include "ClipperUtils.hpp" // union_ex + offset_ex
+
+namespace priv {
+
+using Project = Emboss::IProjection;
+using Project3d = Emboss::IProject3d;
+
+///
+/// Set true for indices out of area of interest
+///
+/// Flag to convert triangle to cgal
+/// model
+/// Convert 2d point to pair of 3d points
+/// 2d bounding box define AOI
+void set_skip_for_out_of_aoi(std::vector &skip_indicies,
+ const indexed_triangle_set &its,
+ const Project &projection,
+ const BoundingBox &shapes_bb);
+
+///
+/// Set true for indicies outward and almost parallel together.
+/// Note: internally calculate normals
+///
+/// Flag to convert triangle to cgal
+/// model
+/// Direction to measure angle
+/// Maximal allowed angle between opposit normal and
+/// projection direction [in DEG]
+void set_skip_by_angle(std::vector &skip_indicies,
+ const indexed_triangle_set &its,
+ const Project3d &projection,
+ double max_angle = 89.);
+
+
+using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel;
+using CutMesh = CGAL::Surface_mesh;
+using CutMeshes = std::vector;
+
+using VI = CGAL::SM_Vertex_index;
+using HI = CGAL::SM_Halfedge_index;
+using EI = CGAL::SM_Edge_index;
+using FI = CGAL::SM_Face_index;
+using P3 = CGAL::Epick::Point_3;
+
+///
+/// Convert triangle mesh model to CGAL Surface_mesh
+/// Filtrate out opposite triangles
+/// Add property map for source face index
+///
+/// Model
+/// Flags that triangle should be skiped
+/// When true triangle will flip normal
+/// CGAL mesh - half edge mesh
+CutMesh to_cgal(const indexed_triangle_set &its,
+ const std::vector &skip_indicies,
+ bool flip = false);
+
+///
+/// Covert 2d shape (e.g. Glyph) to CGAL model
+/// NOTE: internaly create
+/// edge_shape_map .. Property map to store conversion from edge to contour
+/// face_shape_map .. Property map to store conversion from face to contour
+///
+/// 2d shapes to project
+/// Define transformation 2d point into 3d
+/// CGAL model of extruded shape
+CutMesh to_cgal(const ExPolygons &shapes, const Project &projection);
+// function to check result of projection. 2d int32_t -> 3d double
+bool exist_duplicit_vertex(const CutMesh& mesh);
+
+
+///
+/// IntersectingElement
+///
+/// Adress polygon inside of ExPolygon
+/// Keep information about source of vertex:
+/// - from face (one of 2 possible)
+/// - from edge (one of 2 possible)
+///
+/// V1~~~~~V2
+/// | f1 /:
+/// | / :
+/// e1| /e2:
+/// | / :
+/// |/ f2 :
+/// V1'~~~~V2'
+///
+/// | .. edge
+/// / .. edge
+/// : .. foreign edge - neighbor
+/// ~ .. no care edge - idealy should not cross model
+/// V1,V1' .. projected 2d point to 3d
+/// V2,V2' .. projected 2d point to 3d
+///
+/// Vertex indexing
+/// V1 .. i (vertex_base + 2x index of point in polygon)
+/// V1' .. i + 1
+/// V2 .. j = i + 2 || 0 (for last i in polygon)
+/// V2' .. j + 1
+///
+/// f1 .. text_face_1 (triangle face made by side of shape contour)
+/// f2 .. text_face_2
+/// e1 .. text_edge_1 (edge on side of face made by side of shape contour)
+/// e2 .. text_edge_2
+///
+///
+struct IntersectingElement
+{
+ // identify source point in shapes
+ uint32_t shape_point_index{std::numeric_limits::max()};
+
+ // store together type, is_first, is_last
+ unsigned char attr{std::numeric_limits::max()};
+
+ // vertex or edge ID, where edge ID is the index of the source point.
+ // There are 4 consecutive indices generated for a single contour edge:
+ // 0th - 1st text edge (straight)
+ // 1th - 1st text face
+ // 2nd - 2nd text edge (diagonal)
+ // 3th - 2nd text face
+ // Type of intersecting element from extruded shape( 3d )
+ // NOTE: type must be storable to 3bit -> max value is 7
+ enum class Type: unsigned char {
+ edge_1 = 0,
+ face_1 = 1,
+ edge_2 = 2,
+ face_2 = 3,
+ undefined = 4
+ };
+
+ IntersectingElement &set_type(Type t)
+ {
+ attr = static_cast(
+ attr + (int) t - (int) get_type());
+ return *this;
+ }
+ void set_is_first(){ attr += 8; }
+ void set_is_last(){ attr += 16; }
+ Type get_type() const { return static_cast(attr % 8);}
+ bool is_first() const { return 8 <= attr && attr < 16; }
+ bool is_last() const { return attr >= 16; }
+};
+
+// stored in model made by shape
+using EdgeShapeMap = CutMesh::Property_map;
+using FaceShapeMap = CutMesh::Property_map;
+
+// stored in surface source - pointer to EdgeShapeMap | FaceShapeMap
+using VertexShapeMap = CutMesh::Property_map;
+
+// stored in model made by shape
+const std::string edge_shape_map_name = "e:IntersectingElement";
+const std::string face_shape_map_name = "f:IntersectingElement";
+
+// stored in surface source
+const std::string vert_shape_map_name = "v:IntersectingElement";
+
+///
+/// Flag for faces in CGAL mesh
+///
+enum class FaceType {
+ // face inside of the cutted shape
+ inside,
+ // face outside of the cutted shape
+ outside,
+ // face without constrained edge (In or Out)
+ not_constrained,
+
+ // Helper flag that inside was processed
+ inside_processed
+};
+using FaceTypeMap = CutMesh::Property_map;
+const std::string face_type_map_name = "f:side";
+
+// Conversion one vertex index to another
+using CvtVI2VI = CutMesh::Property_map;
+// Each Patch track outline vertex conversion to tource model
+const std::string patch_source_name = "v:patch_source";
+
+// For VI that should be reduced, contain VI to use instead of reduced
+// Other VI are invalid
+using ReductionMap = CvtVI2VI;
+const std::string vertex_reduction_map_name = "v:reduction";
+
+// A property map containing the constrained-or-not status of each edge
+using EdgeBoolMap = CutMesh::Property_map;
+const std::string is_constrained_edge_name = "e:is_constrained";
+
+///
+/// Create map to reduce unnecesary triangles,
+/// Triangles are made by divided quad to two triangles
+/// on side of cutting shape mesh
+/// Note: also use from mesh (have to be created)
+/// face_type_map .. Type of shape inside / outside
+/// vert_shape_map .. Source of outline vertex
+///
+/// Reduction map from vertex to vertex,
+/// when key == value than no reduction
+/// Faces of one
+/// Input object
+void create_reduce_map(ReductionMap &reduction_map, const CutMesh &meshes);
+
+// Patch made by Cut area of interest from model
+// connected faces(triangles) and outlines(halfEdges) for one surface cut
+using CutAOI = std::pair, std::vector>;
+// vector of Cutted Area of interest cutted from one CGAL model
+using CutAOIs = std::vector;
+// vector of CutAOIs for each model
+using VCutAOIs = std::vector;
+
+///
+/// Create AOIs(area of interest) on model surface
+///
+/// Input model converted to CGAL
+/// NOTE: will be extended by corefine edge
+/// 2d contours
+/// [const]Model made by shapes
+/// NOTE: Can't be definde as const because of corefine function input definition,
+/// but it is.
+/// Wanted projection distance
+/// Convert index to shape point from ExPolygons
+/// Patches from model surface
+CutAOIs cut_from_model(CutMesh &cgal_model,
+ const ExPolygons &shapes,
+ /*const*/ CutMesh &cgal_shape,
+ float projection_ratio,
+ const ExPolygonsIndices &s2i);
+
+using Loop = std::vector;
+using Loops = std::vector;
+
+///
+/// Create closed loops of contour vertices created from open half edges
+///
+/// Unsorted half edges
+/// Source mesh for half edges
+/// Closed loops
+Loops create_loops(const std::vector &outlines, const CutMesh &mesh);
+
+// To track during diff_models,
+// what was cutted off, from CutAOI
+struct SurfacePatch
+{
+ // converted cut to CGAL mesh
+ // Mesh is reduced.
+ // (do not contain divided triangles on contour - created by side Quad)
+ CutMesh mesh;
+ // CvtVI2VI cvt = mesh.property_map(patch_source_name);
+ // Conversion VI from this patch to source VI(model) is stored in mesh property
+
+ // Outlines - converted CutAOI.second (half edges)
+ // to loops (vertex indicies) by function create_loops
+ Loops loops;
+
+ // bounding box of mesh
+ BoundingBoxf3 bb;
+
+ //// Data needed to find best projection distances
+ // index of source model in models
+ size_t model_id;
+ // index of source CutAOI
+ size_t aoi_id;
+ // index of shape from ExPolygons
+ size_t shape_id = 0;
+
+ // flag that this patch contain whole CutAOI
+ bool is_whole_aoi = true;
+};
+using SurfacePatches = std::vector;
+
+struct ModelCutId
+{
+ // index of model
+ uint32_t model_index;
+ // index of cut inside model
+ uint32_t cut_index;
+};
+
+///
+/// Keep conversion from VCutAOIs to Index and vice versa
+/// Model_index .. contour(or hole) poin from ExPolygons
+/// Index .. continous number
+///
+class ModelCut2index
+{
+ std::vector m_offsets;
+ // for check range of index
+ uint32_t m_count;
+
+public:
+ ModelCut2index(const VCutAOIs &cuts);
+ uint32_t calc_index(const ModelCutId &id) const;
+ ModelCutId calc_id(uint32_t index) const;
+ uint32_t get_count() const { return m_count; };
+ const std::vector &get_offsets() const { return m_offsets; }
+};
+
+///
+/// Differenciate other models
+///
+/// Patches from meshes
+/// Source points for Cutted AOIs
+/// NOTE: Create Reduction map as mesh property - clean on end
+/// Original models without cut modifications
+/// used for differenciation
+/// NOTE: Clip function modify Mesh
+/// Define projection direction
+/// Cuts differenciate by models - Patch
+SurfacePatches diff_models(VCutAOIs &cuts,
+ /*const*/ CutMeshes &cut_models,
+ /*const*/ CutMeshes &models,
+ const Project3d &projection);
+
+///
+/// Checking whether patch is uninterrupted cover of whole expolygon it belongs.
+///
+/// Part of surface to check
+/// Source shape
+/// Source of cut
+/// True when cover whole expolygon otherwise false
+bool is_over_whole_expoly(const CutAOI &cutAOI,
+ const ExPolygon &shape,
+ const CutMesh &mesh);
+
+///
+/// Checking whether patch is uninterrupted cover of whole expolygon it belongs.
+///
+/// Part of surface to check
+/// Source shape
+/// True when cover whole expolygon otherwise false
+bool is_over_whole_expoly(const SurfacePatch &patch,
+ const ExPolygons &shapes,
+ const VCutAOIs &cutAOIs,
+ const CutMeshes &meshes);
+///
+/// Unptoject points from outline loops of patch
+///
+/// Contain loops and vertices
+/// Know how to project from 3d to 2d
+/// Range of unprojected points x .. min, y .. max value
+/// Unprojected points in loops
+Polygons unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range);
+
+///
+/// Unproject points from loops and create expolygons
+///
+/// Patch to convert on expolygon
+/// Convert 3d point to 2d
+/// Range of unprojected points x .. min, y .. max value
+/// Expolygon represent patch in 2d
+ExPolygon to_expoly(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range);
+
+///
+/// To select surface near projection distance
+///
+struct ProjectionDistance
+{
+ // index of source model
+ uint32_t model_index = std::numeric_limits::max();
+
+ // index of CutAOI
+ uint32_t aoi_index = std::numeric_limits::max();
+
+ // index of Patch
+ uint32_t patch_index = std::numeric_limits::max();
+
+ // signed distance to projection
+ float distance = std::numeric_limits::max();
+};
+// addresed by ExPolygonsIndices
+using ProjectionDistances = std::vector;
+
+// each point in shapes has its ProjectionDistances
+using VDistances = std::vector;
+
+///
+/// Calculate distances for SurfacePatches outline points
+/// NOTE:
+/// each model has to have "vert_shape_map" .. Know source of new vertices
+///
+/// Part of surface
+/// Vertices position
+/// Mesh created by shapes
+/// Count of contour points in shapes
+/// Define best distnace
+/// Projection distances of cutted shape points
+VDistances calc_distances(const SurfacePatches &patches,
+ const CutMeshes &models,
+ const CutMesh &shapes_mesh,
+ size_t count_shapes_points,
+ float projection_ratio);
+
+///
+/// Select distances in similar depth between expolygons
+///
+/// All distances - Vector distances for each shape point
+/// Vector of letters
+/// Pivot for start projection in 2d
+/// Convert index to addresss inside of shape
+/// Cutted parts from surface
+/// Closest distance projection indexed by points in shapes(see s2i)
+ProjectionDistances choose_best_distance(
+ const VDistances &distances,
+ const ExPolygons &shapes,
+ const Point &start,
+ const ExPolygonsIndices &s2i,
+ const SurfacePatches &patches);
+
+///
+/// Create mask for patches
+///
+/// For each point selected closest distance
+/// All patches
+/// All patches
+/// Mask of used patch
+std::vector select_patches(const ProjectionDistances &best_distances,
+ const SurfacePatches &patches,
+
+ const ExPolygons &shapes,
+ const ExPolygonsIndices &s2i,
+ const VCutAOIs &cutAOIs,
+ const CutMeshes &meshes,
+ const Project &projection);
+
+///
+/// Merge two surface cuts together
+/// Added surface cut will be consumed
+///
+/// Surface cut to extend
+/// Surface cut to consume
+void append(SurfaceCut &sc, SurfaceCut &&sc_add);
+
+///
+/// Convert patch to indexed_triangle_set
+///
+/// Part of surface
+/// Converted patch
+SurfaceCut patch2cut(SurfacePatch &patch);
+
+///
+/// Merge masked patches to one surface cut
+///
+/// All patches
+/// NOTE: Not const because One needs to add property for Convert indices
+/// Mash for using patch
+/// Result surface cut
+SurfaceCut merge_patches(/*const*/ SurfacePatches &patches,
+ const std::vector &mask);
+
+#ifdef DEBUG_OUTPUT_DIR
+void prepare_dir(const std::string &dir);
+void initialize_store(const std::string &dir_to_clear);
+///
+/// Debug purpose store of mesh with colored face by face type
+///
+/// Input mesh, could add property color
+/// NOTE: Not const because need to [optionaly] append color property map
+/// Color source
+/// File to store
+void store(const CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string &dir, bool is_filled = false);
+void store(const ExPolygons &shapes, const std::string &svg_file);
+void store(const CutMesh &mesh, const ReductionMap &reduction_map, const std::string &dir);
+void store(const CutAOIs &aois, const CutMesh &mesh, const std::string &dir);
+void store(const SurfacePatches &patches, const std::string &dir);
+void store(const Vec3f &vertex, const Vec3f &normal, const std::string &file, float size = 2.f);
+//void store(const ProjectionDistances &pds, const VCutAOIs &aois, const CutMeshes &meshes, const std::string &file, float width = 0.2f/* [in mm] */);
+using Connection = std::pair; using Connections = std::vector;
+void store(const ExPolygons &shapes, const std::vector &mask_distances, const Connections &connections, const std::string &file_svg);
+void store(const SurfaceCut &cut, const std::string &file, const std::string &contour_dir);
+void store(const std::vector &models, const std::string &obj_filename);
+void store(const std::vector&models, const std::string &dir);
+void store(const Emboss::IProjection &projection, const Point &point_to_project, float projection_ratio, const std::string &obj_filename);
+#endif // DEBUG_OUTPUT_DIR
+} // namespace privat
+
+#ifdef DEBUG_OUTPUT_DIR
+#include "libslic3r/SVG.hpp"
+#include
+#include
+#endif // DEBUG_OUTPUT_DIR
+
+SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes,
+ const std::vector &models,
+ const Emboss::IProjection &projection,
+ float projection_ratio)
+{
+ assert(!models.empty());
+ assert(!shapes.empty());
+ if (models.empty() || shapes.empty() ) return {};
+
+#ifdef DEBUG_OUTPUT_DIR
+ priv::initialize_store(DEBUG_OUTPUT_DIR);
+ priv::store(models, DEBUG_OUTPUT_DIR + "models_input.obj");
+ priv::store(shapes, DEBUG_OUTPUT_DIR + "shapes.svg");
+#endif // DEBUG_OUTPUT_DIR
+
+ // for filter out triangles out of bounding box
+ BoundingBox shapes_bb = get_extents(shapes);
+#ifdef DEBUG_OUTPUT_DIR
+ priv::store(projection, shapes_bb.center(), projection_ratio, DEBUG_OUTPUT_DIR + "projection_center.obj");
+#endif // DEBUG_OUTPUT_DIR
+
+ // for filttrate opposite triangles and a little more
+ const float max_angle = 89.9f;
+ priv::CutMeshes cgal_models; // source for patch
+ priv::CutMeshes cgal_neg_models; // model used for differenciate patches
+ cgal_models.reserve(models.size());
+ for (const indexed_triangle_set &its : models) {
+ std::vector skip_indicies(its.indices.size(), {false});
+ priv::set_skip_for_out_of_aoi(skip_indicies, its, projection, shapes_bb);
+
+ // create model for differenciate cutted patches
+ bool flip = true;
+ cgal_neg_models.push_back(priv::to_cgal(its, skip_indicies, flip));
+
+ // cut out more than only opposit triangles
+ priv::set_skip_by_angle(skip_indicies, its, projection, max_angle);
+ cgal_models.push_back(priv::to_cgal(its, skip_indicies));
+ }
+#ifdef DEBUG_OUTPUT_DIR
+ priv::store(cgal_models, DEBUG_OUTPUT_DIR + "model/");// model[0-N].off
+ priv::store(cgal_neg_models, DEBUG_OUTPUT_DIR + "model_neg/"); // model[0-N].off
+#endif // DEBUG_OUTPUT_DIR
+
+ priv::CutMesh cgal_shape = priv::to_cgal(shapes, projection);
+#ifdef DEBUG_OUTPUT_DIR
+ CGAL::IO::write_OFF(DEBUG_OUTPUT_DIR + "shape.off", cgal_shape); // only debug
+#endif // DEBUG_OUTPUT_DIR
+
+ // create tool for convert index to shape Point adress and vice versa
+ ExPolygonsIndices s2i(shapes);
+ priv::VCutAOIs model_cuts;
+ // cut shape from each cgal model
+ for (priv::CutMesh &cgal_model : cgal_models) {
+ priv::CutAOIs cutAOIs = priv::cut_from_model(
+ cgal_model, shapes, cgal_shape, projection_ratio, s2i);
+#ifdef DEBUG_OUTPUT_DIR
+ size_t index = &cgal_model - &cgal_models.front();
+ priv::store(cutAOIs, cgal_model, DEBUG_OUTPUT_DIR + "model_AOIs/" + std::to_string(index) + "/"); // only debug
+#endif // DEBUG_OUTPUT_DIR
+ model_cuts.push_back(std::move(cutAOIs));
+ }
+
+ priv::SurfacePatches patches = priv::diff_models(model_cuts, cgal_models, cgal_neg_models, projection);
+#ifdef DEBUG_OUTPUT_DIR
+ priv::store(patches, DEBUG_OUTPUT_DIR + "patches/");
+#endif // DEBUG_OUTPUT_DIR
+ if (patches.empty()) return {};
+
+ // fix - convert shape_point_id to expolygon index
+ // save 1 param(s2i) from diff_models call
+ for (priv::SurfacePatch &patch : patches)
+ patch.shape_id = s2i.cvt(patch.shape_id).expolygons_index;
+
+ // calc distance to projection for all outline points of cutAOI(shape)
+ // it is used for distiguish the top one
+ uint32_t shapes_points = s2i.get_count();
+ // for each point collect all projection distances
+ priv::VDistances distances = priv::calc_distances(patches, cgal_models, cgal_shape, shapes_points, projection_ratio);
+
+ Point start = shapes_bb.center(); // only align center
+
+ // Use only outline points
+ // for each point select best projection
+ priv::ProjectionDistances best_projection = priv::choose_best_distance(distances, shapes, start, s2i, patches);
+ std::vector use_patch = priv::select_patches(best_projection, patches,
+ shapes, s2i,model_cuts, cgal_models, projection);
+ SurfaceCut result = merge_patches(patches, use_patch);
+ //*/
+
+#ifdef DEBUG_OUTPUT_DIR
+ priv::store(result, DEBUG_OUTPUT_DIR + "result.obj", DEBUG_OUTPUT_DIR + "result_contours/");
+#endif // DEBUG_OUTPUT_DIR
+ return result;
+}
+
+indexed_triangle_set Slic3r::cut2model(const SurfaceCut &cut,
+ const Emboss::IProject3d &projection)
+{
+ assert(!cut.empty());
+ size_t count_vertices = cut.vertices.size() * 2;
+ size_t count_indices = cut.indices.size() * 2;
+
+ // indices from from zig zag
+ for (const auto &c : cut.contours) {
+ assert(!c.empty());
+ count_indices += c.size() * 2;
+ }
+
+ indexed_triangle_set result;
+ result.vertices.reserve(count_vertices);
+ result.indices.reserve(count_indices);
+
+ // front
+ result.vertices.insert(result.vertices.end(),
+ cut.vertices.begin(), cut.vertices.end());
+ result.indices.insert(result.indices.end(),
+ cut.indices.begin(), cut.indices.end());
+
+ // back
+ for (const Vec3f &v : cut.vertices) {
+ Vec3d vd = v.cast();
+ Vec3d vd2 = projection.project(vd);
+ result.vertices.push_back(vd2.cast());
+ }
+
+ size_t back_offset = cut.vertices.size();
+ for (const auto &i : cut.indices) {
+ // check range of indices in cut
+ assert(i.x() + back_offset < result.vertices.size());
+ assert(i.y() + back_offset < result.vertices.size());
+ assert(i.z() + back_offset < result.vertices.size());
+ assert(i.x() >= 0 && i.x() < cut.vertices.size());
+ assert(i.y() >= 0 && i.y() < cut.vertices.size());
+ assert(i.z() >= 0 && i.z() < cut.vertices.size());
+ // Y and Z is swapped CCW triangles for back side
+ result.indices.emplace_back(i.x() + back_offset,
+ i.z() + back_offset,
+ i.y() + back_offset);
+ }
+
+ // zig zag indices
+ for (const auto &contour : cut.contours) {
+ size_t prev_front_index = contour.back();
+ size_t prev_back_index = back_offset + prev_front_index;
+ for (size_t front_index : contour) {
+ assert(front_index < cut.vertices.size());
+ size_t back_index = back_offset + front_index;
+ result.indices.emplace_back(front_index, prev_front_index, back_index);
+ result.indices.emplace_back(prev_front_index, prev_back_index, back_index);
+ prev_front_index = front_index;
+ prev_back_index = back_index;
+ }
+ }
+
+ assert(count_vertices == result.vertices.size());
+ assert(count_indices == result.indices.size());
+ return result;
+}
+
+// set_skip_for_out_of_aoi helping functions
+namespace priv {
+// define plane
+using PointNormal = std::pair;
+using PointNormals = std::array;
+
+///
+/// Check
+///
+///
+///
+///
+///
+bool is_out_of(const Vec3d &v, const PointNormal &point_normal);
+
+using IsOnSides = std::vector>;
+///
+/// Check if triangle t has all vertices out of any plane
+///
+/// Triangle
+/// Flag is vertex index out of plane
+/// True when triangle is out of one of plane
+bool is_all_on_one_side(const Vec3i &t, const IsOnSides& is_on_sides);
+
+} // namespace priv
+
+bool priv::is_out_of(const Vec3d &v, const PointNormal &point_normal)
+{
+ const Vec3d& p = point_normal.first;
+ const Vec3d& n = point_normal.second;
+ double signed_distance = (v - p).dot(n);
+ return signed_distance > 1e-5;
+};
+
+bool priv::is_all_on_one_side(const Vec3i &t, const IsOnSides& is_on_sides) {
+ for (size_t side = 0; side < 4; side++) {
+ bool result = true;
+ for (auto vi : t) {
+ if (!is_on_sides[vi][side]) {
+ result = false;
+ break;
+ }
+ }
+ if (result) return true;
+ }
+ return false;
+}
+
+void priv::set_skip_for_out_of_aoi(std::vector &skip_indicies,
+ const indexed_triangle_set &its,
+ const Project &projection,
+ const BoundingBox &shapes_bb)
+{
+ assert(skip_indicies.size() == its.indices.size());
+ // 1`*----* 2`
+ // / 2 /|
+ // 1 *----* |
+ // | | * 3`
+ // | |/
+ // 0 *----* 3
+ //////////////////
+ std::array, 4> bb;
+ int index = 0;
+ for (Point v :
+ {shapes_bb.min, Point{shapes_bb.min.x(), shapes_bb.max.y()},
+ shapes_bb.max, Point{shapes_bb.max.x(), shapes_bb.min.y()}})
+ bb[index++] = projection.create_front_back(v);
+
+ // define planes to test
+ // 0 .. under
+ // 1 .. left
+ // 2 .. above
+ // 3 .. right
+ size_t prev_i = 3;
+ // plane is defined by point and normal
+ PointNormals point_normals;
+ for (size_t i = 0; i < 4; i++) {
+ const Vec3d &p1 = bb[i].first;
+ const Vec3d &p2 = bb[i].second;
+ const Vec3d &p3 = bb[prev_i].first;
+ prev_i = i;
+
+ Vec3d v1 = p2 - p1;
+ v1.normalize();
+ Vec3d v2 = p3 - p1;
+ v2.normalize();
+
+ Vec3d normal = v2.cross(v1);
+ normal.normalize();
+
+ point_normals[i] = {p1, normal};
+ }
+ // same meaning as point normal
+ IsOnSides is_on_sides(its.vertices.size(), {false,false,false,false});
+
+ // inspect all vertices when it is out of bounding box
+ tbb::parallel_for(tbb::blocked_range(0, its.vertices.size()),
+ [&its, &point_normals, &is_on_sides](const tbb::blocked_range &range) {
+ for (size_t i = range.begin(); i < range.end(); ++i) {
+ Vec3d v = its.vertices[i].cast();
+ // under + above
+ for (int side : {0, 2}) {
+ if (is_out_of(v, point_normals[side])) {
+ is_on_sides[i][side] = true;
+ // when it is under it can't be above
+ break;
+ }
+ }
+ // left + right
+ for (int side : {1, 3}) {
+ if (is_out_of(v, point_normals[side])) {
+ is_on_sides[i][side] = true;
+ // when it is on left side it can't be on right
+ break;
+ }
+ }
+ }
+ }); // END parallel for
+
+ // inspect all triangles, when it is out of bounding box
+ tbb::parallel_for(tbb::blocked_range(0, its.indices.size()),
+ [&its, &is_on_sides, &skip_indicies](const tbb::blocked_range &range) {
+ for (size_t i = range.begin(); i < range.end(); ++i) {
+ if (is_all_on_one_side(its.indices[i], is_on_sides))
+ skip_indicies[i] = true;
+ }
+ }); // END parallel for
+}
+
+indexed_triangle_set Slic3r::its_mask(const indexed_triangle_set &its,
+ const std::vector &mask)
+{
+ if (its.indices.size() != mask.size()) {
+ assert(false);
+ return {};
+ }
+
+ std::vector cvt_vetices(its.vertices.size(), {std::numeric_limits::max()});
+ size_t vertices_count = 0;
+ size_t faces_count = 0;
+ for (const auto &t : its.indices) {
+ size_t index = &t - &its.indices.front();
+ if (!mask[index]) continue;
+ ++faces_count;
+ for (const auto vi : t) {
+ uint32_t &cvt = cvt_vetices[vi];
+ if (cvt == std::numeric_limits::max())
+ cvt = vertices_count++;
+ }
+ }
+ if (faces_count == 0) return {};
+
+ indexed_triangle_set result;
+ result.indices.reserve(faces_count);
+ result.vertices = std::vector(vertices_count);
+ for (size_t i = 0; i < its.vertices.size(); ++i) {
+ uint32_t index = cvt_vetices[i];
+ if (index == std::numeric_limits::max()) continue;
+ result.vertices[index] = its.vertices[i];
+ }
+
+ for (const stl_triangle_vertex_indices &f : its.indices)
+ if (mask[&f - &its.indices.front()])
+ result.indices.push_back(stl_triangle_vertex_indices(
+ cvt_vetices[f[0]], cvt_vetices[f[1]], cvt_vetices[f[2]]));
+
+ return result;
+}
+
+indexed_triangle_set Slic3r::its_cut_AoI(const indexed_triangle_set &its,
+ const BoundingBox &bb,
+ const Emboss::IProjection &projection)
+{
+ std::vector skip_indicies(its.indices.size(), false);
+ priv::set_skip_for_out_of_aoi(skip_indicies, its, projection, bb);
+ // invert values in vector of bool
+ skip_indicies.flip();
+ return its_mask(its, skip_indicies);
+}
+
+void priv::set_skip_by_angle(std::vector &skip_indicies,
+ const indexed_triangle_set &its,
+ const Project3d &projection,
+ double max_angle)
+{
+ assert(max_angle < 90. && max_angle > 89.);
+ assert(skip_indicies.size() == its.indices.size());
+ float threshold = static_cast(cos(max_angle / 180. * M_PI));
+ for (const stl_triangle_vertex_indices& face : its.indices) {
+ size_t index = &face - &its.indices.front();
+ if (skip_indicies[index]) continue;
+ Vec3f n = its_face_normal(its, face);
+ const Vec3f& v = its.vertices[face[0]];
+ const Vec3d vd = v.cast();
+ // Improve: For Orthogonal Projection it is same for each vertex
+ Vec3d projectedd = projection.project(vd);
+ Vec3f projected = projectedd.cast();
+ Vec3f project_dir = projected - v;
+ project_dir.normalize();
+ float cos_alpha = project_dir.dot(n);
+ if (cos_alpha > threshold) continue;
+ skip_indicies[index] = true;
+ }
+}
+
+priv::CutMesh priv::to_cgal(const indexed_triangle_set &its,
+ const std::vector &skip_indicies,
+ bool flip)
+{
+ const std::vector &vertices = its.vertices;
+ const std::vector &indices = its.indices;
+
+ std::vector use_vetices(vertices.size(), {false});
+
+ size_t vertices_count = 0;
+ size_t faces_count = 0;
+ size_t edges_count = 0;
+
+ for (const auto &t : indices) {
+ size_t index = &t - &indices.front();
+ if (skip_indicies[index]) continue;
+ ++faces_count;
+ size_t count_used_vertices = 0;
+ for (const auto vi : t) {
+ if (!use_vetices[vi]) {
+ ++vertices_count;
+ use_vetices[vi] = true;
+ } else {
+ ++count_used_vertices;
+ }
+ }
+ switch (count_used_vertices) {
+ case 3: break; // all edges are already counted
+ case 2: edges_count += 2; break;
+ case 1:
+ case 0: edges_count += 3; break;
+ default: assert(false);
+ }
+ }
+ assert(vertices_count <= vertices.size());
+ assert(edges_count <= (indices.size() * 3));
+ assert(faces_count <= indices.size());
+
+ CutMesh result;
+ result.reserve(vertices_count, edges_count, faces_count);
+
+ std::vector to_filtrated_vertices_index(vertices.size());
+ size_t filtrated_vertices_index = 0;
+ for (size_t i = 0; i < vertices.size(); ++i)
+ if (use_vetices[i]) {
+ to_filtrated_vertices_index[i] = VI(filtrated_vertices_index);
+ ++filtrated_vertices_index;
+ }
+
+ for (const stl_vertex& v : vertices) {
+ if (!use_vetices[&v - &vertices.front()]) continue;
+ result.add_vertex(CutMesh::Point{v.x(), v.y(), v.z()});
+ }
+
+ if (!flip) {
+ for (const stl_triangle_vertex_indices &f : indices) {
+ if (skip_indicies[&f - &indices.front()]) continue;
+ result.add_face(to_filtrated_vertices_index[f[0]],
+ to_filtrated_vertices_index[f[1]],
+ to_filtrated_vertices_index[f[2]]);
+ }
+ } else {
+ for (const stl_triangle_vertex_indices &f : indices) {
+ if (skip_indicies[&f - &indices.front()]) continue;
+ result.add_face(to_filtrated_vertices_index[f[2]],
+ to_filtrated_vertices_index[f[1]],
+ to_filtrated_vertices_index[f[0]]);
+ }
+ }
+
+ return result;
+}
+
+bool priv::exist_duplicit_vertex(const CutMesh &mesh) {
+ std::vector points;
+ points.reserve(mesh.vertices().size());
+ // copy points
+ for (VI vi : mesh.vertices()) {
+ const P3 &p = mesh.point(vi);
+ points.emplace_back(p.x(), p.y(), p.z());
+ }
+ std::sort(points.begin(), points.end(), [](const Vec3d &v1, const Vec3d &v2) {
+ return v1.x() < v2.x() ||
+ (v1.x() == v2.x() &&
+ (v1.y() < v2.y() ||
+ (v1.y() == v2.y() &&
+ v1.z() < v2.z())));
+ });
+ // find first duplicit
+ auto it = std::adjacent_find(points.begin(), points.end());
+ return it != points.end();
+}
+
+priv::CutMesh priv::to_cgal(const ExPolygons &shapes,
+ const Project &projection)
+{
+ if (shapes.empty()) return {};
+
+ CutMesh result;
+ EdgeShapeMap edge_shape_map = result.add_property_map(edge_shape_map_name).first;
+ FaceShapeMap face_shape_map = result.add_property_map(face_shape_map_name).first;
+
+ std::vector indices;
+ auto insert_contour = [&projection, &indices, &result,
+ &edge_shape_map, &face_shape_map]
+ (const Polygon &polygon) {
+ indices.clear();
+ indices.reserve(polygon.points.size() * 2);
+ size_t num_vertices_old = result.number_of_vertices();
+ for (const Point &polygon_point : polygon.points) {
+ auto [front, back] = projection.create_front_back(polygon_point);
+ P3 v_front{front.x(), front.y(), front.z()};
+ VI vi1 = result.add_vertex(v_front);
+ assert(vi1.idx() == (indices.size() + num_vertices_old));
+ indices.push_back(vi1);
+
+ P3 v_back{back.x(), back.y(), back.z()};
+ VI vi2 = result.add_vertex(v_back);
+ assert(vi2.idx() == (indices.size() + num_vertices_old));
+ indices.push_back(vi2);
+ }
+
+ auto find_edge = [&result](FI fi, VI from, VI to) {
+ HI hi = result.halfedge(fi);
+ for (; result.target(hi) != to; hi = result.next(hi));
+ assert(result.source(hi) == from);
+ assert(result.target(hi) == to);
+ return result.edge(hi);
+ };
+
+ uint32_t contour_index = static_cast(num_vertices_old / 2);
+ for (int32_t i = 0; i < int32_t(indices.size()); i += 2) {
+ bool is_first = i == 0;
+ bool is_last = size_t(i + 2) >= indices.size();
+ int32_t j = is_last ? 0 : (i + 2);
+
+ FI fi1 = result.add_face(indices[i], indices[j], indices[i + 1]);
+ EI ei1 = find_edge(fi1, indices[i + 1], indices[i]);
+ EI ei2 = find_edge(fi1, indices[j], indices[i + 1]);
+ FI fi2 = result.add_face(indices[j], indices[j + 1], indices[i + 1]);
+ IntersectingElement element {contour_index, (unsigned char)IntersectingElement::Type::undefined};
+ if (is_first) element.set_is_first();
+ if (is_last) element.set_is_last();
+ edge_shape_map[ei1] = element.set_type(IntersectingElement::Type::edge_1);
+ face_shape_map[fi1] = element.set_type(IntersectingElement::Type::face_1);
+ edge_shape_map[ei2] = element.set_type(IntersectingElement::Type::edge_2);
+ face_shape_map[fi2] = element.set_type(IntersectingElement::Type::face_2);
+ ++contour_index;
+ }
+ };
+
+ size_t count_point = count_points(shapes);
+ result.reserve(result.number_of_vertices() + 2 * count_point,
+ result.number_of_edges() + 4 * count_point,
+ result.number_of_faces() + 2 * count_point);
+
+ // Identify polygon
+ for (const ExPolygon &shape : shapes) {
+ insert_contour(shape.contour);
+ for (const Polygon &hole : shape.holes)
+ insert_contour(hole);
+ }
+ assert(!exist_duplicit_vertex(result));
+ return result;
+}
+
+priv::ModelCut2index::ModelCut2index(const VCutAOIs &cuts)
+{
+ // prepare offsets
+ m_offsets.reserve(cuts.size());
+ uint32_t offset = 0;
+ for (const CutAOIs &model_cuts: cuts) {
+ m_offsets.push_back(offset);
+ offset += model_cuts.size();
+ }
+ m_count = offset;
+}
+
+uint32_t priv::ModelCut2index::calc_index(const ModelCutId &id) const
+{
+ assert(id.model_index < m_offsets.size());
+ uint32_t offset = m_offsets[id.model_index];
+ uint32_t res = offset + id.cut_index;
+ assert(((id.model_index+1) < m_offsets.size() && res < m_offsets[id.model_index+1]) ||
+ ((id.model_index+1) == m_offsets.size() && res < m_count));
+ return res;
+}
+
+priv::ModelCutId priv::ModelCut2index::calc_id(uint32_t index) const
+{
+ assert(index < m_count);
+ ModelCutId result{0,0};
+ // find shape index
+ for (size_t model_index = 1; model_index < m_offsets.size(); ++model_index) {
+ if (m_offsets[model_index] > index) break;
+ result.model_index = model_index;
+ }
+ result.cut_index = index - m_offsets[result.model_index];
+ return result;
+}
+
+// cut_from_model help functions
+namespace priv {
+
+///
+/// Track source of intersection
+/// Help for anotate inner and outer faces
+///
+struct Visitor {
+ const CutMesh &object;
+ const CutMesh &shape;
+
+ // Properties of the shape mesh:
+ EdgeShapeMap edge_shape_map;
+ FaceShapeMap face_shape_map;
+
+ // Properties of the object mesh.
+ VertexShapeMap vert_shape_map;
+
+ // check for anomalities
+ bool* is_valid;
+
+ // keep source of intersection for each intersection
+ // used to copy data into vert_shape_map
+ std::vector intersections;
+
+ ///
+ /// Called when a new intersection point is detected.
+ /// The intersection is detected using a face of tm_f and an edge of tm_e.
+ /// Intersecting an edge hh_edge from tm_f with a face h_e of tm_e.
+ /// https://doc.cgal.org/latest/Polygon_mesh_processing/classPMPCorefinementVisitor.html#a00ee0ca85db535a48726a92414acda7f
+ ///
+ /// The id of the intersection point, starting at 0. Ids are consecutive.
+ /// Dimension of a simplex part of face(h_e) that is intersected by edge(h_f):
+ /// 0 for vertex: target(h_e)
+ /// 1 for edge: h_e
+ /// 2 for the interior of face: face(h_e)
+ ///
+ /// A halfedge from tm_f indicating the simplex intersected:
+ /// if sdim==0 the target of h_f is the intersection point,
+ /// if sdim==1 the edge of h_f contains the intersection point in its interior,
+ /// if sdim==2 the face of h_f contains the intersection point in its interior.
+ /// @Vojta: Edge of tm_f, see is_target_coplanar & is_source_coplanar whether any vertex of h_f is coplanar with face(h_e).
+ ///
+ /// A halfedge from tm_e
+ /// @Vojta: Vertex, halfedge or face of tm_e intersected by h_f, see comment at sdim.
+ ///
+ /// Mesh containing h_f
+ /// Mesh containing h_e
+ /// True if the target of h_e is the intersection point
+ /// @Vojta: source(h_f) is coplanar with face(made by h_e).
+ /// True if the source of h_e is the intersection point
+ /// @Vojta: target(h_f) is coplanar with face(h_e).
+ void intersection_point_detected(std::size_t i_id,
+ int sdim,
+ HI h_f,
+ HI h_e,
+ const CutMesh &tm_f,
+ const CutMesh &tm_e,
+ bool is_target_coplanar,
+ bool is_source_coplanar);
+
+ ///
+ /// Called when a new vertex is added in tm (either an edge split or a vertex inserted in the interior of a face).
+ /// Fill vertex_shape_map by intersections
+ ///
+ /// Order number of intersection point
+ /// New added vertex
+ /// Affected mesh
+ void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm);
+
+ // Not used visitor functions
+ void before_subface_creations(FI /* f_old */, CutMesh &/* mesh */){}
+ void after_subface_created(FI /* f_new */, CutMesh &/* mesh */) {}
+ void after_subface_creations(CutMesh&) {}
+ void before_subface_created(CutMesh&) {}
+ void before_edge_split(HI /* h */, CutMesh& /* tm */) {}
+ void edge_split(HI /* hnew */, CutMesh& /* tm */) {}
+ void after_edge_split() {}
+ void add_retriangulation_edge(HI /* h */, CutMesh& /* tm */) {}
+};
+
+///
+/// Distiquish face type for half edge
+///
+/// Define face
+/// Mesh to process
+/// Vertices of mesh made by shapes
+/// Keep information about source of created vertex
+///
+/// Convert index to shape point from ExPolygons
+/// Face type defined by hi
+bool is_face_inside(HI hi,
+ const CutMesh &mesh,
+ const CutMesh &shape_mesh,
+ const VertexShapeMap &vertex_shape_map,
+ const ExPolygonsIndices &shape2index);
+
+///
+/// Face with constrained edge are inside/outside by type of intersection
+/// Other set to not_constrained(still it could be inside/outside)
+///
+/// [Output] property map with type of faces
+/// Mesh to process
+/// Keep information about source of created vertex
+/// Dynamic Edge Constrained Map of bool
+/// Vertices of mesh made by shapes
+/// Convert index to shape point from ExPolygons
+void set_face_type(FaceTypeMap &face_type_map,
+ const CutMesh &mesh,
+ const VertexShapeMap &vertex_shape_map,
+ const EdgeBoolMap &ecm,
+ const CutMesh &shape_mesh,
+ const ExPolygonsIndices &shape2index);
+
+///
+/// Change FaceType from not_constrained to inside
+/// For neighbor(or neighbor of neighbor of ...) of inside triangles.
+/// Process only not_constrained triangles
+///
+/// Corefined mesh
+/// In/Out map with faces type
+void flood_fill_inner(const CutMesh &mesh, FaceTypeMap &face_type_map);
+
+///
+/// Collect connected inside faces
+/// Collect outline half edges
+///
+/// Queue of face to process - find connected
+/// [Output] collected Face indices from mesh
+/// [Output] collected Halfedge indices from mesh
+/// Use flag inside / outside
+/// NOTE: Modify in function: inside -> inside_processed
+/// mesh to process
+void collect_surface_data(std::queue &process,
+ std::vector &faces,
+ std::vector &outlines,
+ FaceTypeMap &face_type_map,
+ const CutMesh &mesh);
+
+///
+/// Create areas from mesh surface
+///
+/// Model
+/// Cutted shapes
+/// Define Triangles of interest.
+/// Edge between inside / outside.
+/// NOTE: Not const because it need to flag proccessed faces
+/// Areas of interest from mesh
+CutAOIs create_cut_area_of_interests(const CutMesh &mesh,
+ const ExPolygons &shapes,
+ FaceTypeMap &face_type_map);
+
+} // namespace priv
+
+void priv::Visitor::intersection_point_detected(std::size_t i_id,
+ int sdim,
+ HI h_f,
+ HI h_e,
+ const CutMesh &tm_f,
+ const CutMesh &tm_e,
+ bool is_target_coplanar,
+ bool is_source_coplanar)
+{
+ if (i_id >= intersections.size()) {
+ size_t capacity = Slic3r::next_highest_power_of_2(i_id + 1);
+ intersections.reserve(capacity);
+ intersections.resize(capacity);
+ }
+
+ const IntersectingElement *intersection_ptr = nullptr;
+ if (&tm_e == &shape) {
+ assert(&tm_f == &object);
+ switch (sdim) {
+ case 1:
+ // edge x edge intersection
+ intersection_ptr = &edge_shape_map[shape.edge(h_e)];
+ break;
+ case 2:
+ // edge x face intersection
+ intersection_ptr = &face_shape_map[shape.face(h_e)];
+ break;
+ default: assert(false);
+ }
+ if (is_target_coplanar)
+ vert_shape_map[object.source(h_f)] = intersection_ptr;
+ if (is_source_coplanar)
+ vert_shape_map[object.target(h_f)] = intersection_ptr;
+ } else {
+ assert(&tm_f == &shape && &tm_e == &object);
+ assert(!is_target_coplanar);
+ assert(!is_source_coplanar);
+ if (is_target_coplanar || is_source_coplanar)
+ *is_valid = false;
+ intersection_ptr = &edge_shape_map[shape.edge(h_f)];
+ if (sdim == 0) vert_shape_map[object.target(h_e)] = intersection_ptr;
+ }
+
+ if (intersection_ptr->shape_point_index == std::numeric_limits::max()) {
+ // there is unexpected intersection
+ // Top (or Bottom) shape contour edge (or vertex) intersection
+ // Suggest to change projection min/max limits
+ *is_valid = false;
+ }
+ intersections[i_id] = intersection_ptr;
+}
+
+void priv::Visitor::new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm)
+{
+ assert(&tm == &object);
+ assert(i_id < intersections.size());
+ const IntersectingElement *intersection_ptr = intersections[i_id];
+ assert(intersection_ptr != nullptr);
+ // intersection was not filled in function intersection_point_detected
+ //assert(intersection_ptr->point_index != std::numeric_limits::max());
+ vert_shape_map[v] = intersection_ptr;
+}
+
+bool priv::is_face_inside(HI hi,
+ const CutMesh &mesh,
+ const CutMesh &shape_mesh,
+ const VertexShapeMap &vertex_shape_map,
+ const ExPolygonsIndices &shape2index)
+{
+ VI vi_from = mesh.source(hi);
+ VI vi_to = mesh.target(hi);
+ // This face has a constrained edge.
+ const IntersectingElement &shape_from = *vertex_shape_map[vi_from];
+ const IntersectingElement &shape_to = *vertex_shape_map[vi_to];
+ assert(shape_from.shape_point_index != std::numeric_limits::max());
+ assert(shape_from.attr != (unsigned char) IntersectingElement::Type::undefined);
+ assert(shape_to.shape_point_index != std::numeric_limits::max());
+ assert(shape_to.attr != (unsigned char) IntersectingElement::Type::undefined);
+
+ // index into contour
+ uint32_t i_from = shape_from.shape_point_index;
+ uint32_t i_to = shape_to.shape_point_index;
+ IntersectingElement::Type type_from = shape_from.get_type();
+ IntersectingElement::Type type_to = shape_to.get_type();
+ if (i_from == i_to && type_from == type_to) {
+ // intersecting element must be face
+ assert(type_from == IntersectingElement::Type::face_1 ||
+ type_from == IntersectingElement::Type::face_2);
+
+ // count of vertices is twice as count of point in the contour
+ uint32_t i = i_from * 2;
+ // j is next contour point in vertices
+ uint32_t j = i + 2;
+ if (shape_from.is_last()) {
+ ExPolygonsIndex point_id = shape2index.cvt(i_from);
+ point_id.point_index = 0;
+ j = shape2index.cvt(point_id)*2;
+ }
+
+ // opposit point(in triangle face) to edge
+ const P3 &p = mesh.point(mesh.target(mesh.next(hi)));
+
+ // abc is source triangle face
+ CGAL::Sign abcp = type_from == IntersectingElement::Type::face_1 ?
+ CGAL::orientation(shape_mesh.point(VI(i)),
+ shape_mesh.point(VI(i + 1)),
+ shape_mesh.point(VI(j)), p) :
+ // type_from == IntersectingElement::Type::face_2
+ CGAL::orientation(shape_mesh.point(VI(j)),
+ shape_mesh.point(VI(i + 1)),
+ shape_mesh.point(VI(j + 1)), p);
+ return abcp == CGAL::POSITIVE;
+ } else if (i_from < i_to || (i_from == i_to && type_from < type_to)) {
+ bool is_last = shape_to.is_last() && shape_from.is_first();
+ // check continuity of indicies
+ assert(i_from == i_to || is_last || (i_from + 1) == i_to);
+ return !is_last;
+ } else {
+ assert(i_from > i_to || (i_from == i_to && type_from > type_to));
+ bool is_last = shape_to.is_first() && shape_from.is_last();
+ // check continuity of indicies
+ assert(i_from == i_to || is_last || (i_to + 1) == i_from);
+ return is_last;
+ }
+
+ assert(false);
+ return false;
+}
+
+void priv::set_face_type(FaceTypeMap &face_type_map,
+ const CutMesh &mesh,
+ const VertexShapeMap &vertex_shape_map,
+ const EdgeBoolMap &ecm,
+ const CutMesh &shape_mesh,
+ const ExPolygonsIndices &shape2index)
+{
+ for (EI ei : mesh.edges()) {
+ if (!ecm[ei]) continue;
+ HI hi = mesh.halfedge(ei);
+ FI fi = mesh.face(hi);
+ bool is_inside = is_face_inside(hi, mesh, shape_mesh, vertex_shape_map, shape2index);
+ face_type_map[fi] = is_inside ? FaceType::inside : FaceType::outside;
+ HI hi_op = mesh.opposite(hi);
+ assert(hi_op.is_valid());
+ if (!hi_op.is_valid()) continue;
+ FI fi_op = mesh.face(hi_op);
+ assert(fi_op.is_valid());
+ if (!fi_op.is_valid()) continue;
+ face_type_map[fi_op] = (!is_inside) ? FaceType::inside : FaceType::outside;
+ }
+}
+
+priv::CutAOIs priv::cut_from_model(CutMesh &cgal_model,
+ const ExPolygons &shapes,
+ CutMesh &cgal_shape,
+ float projection_ratio,
+ const ExPolygonsIndices &s2i)
+{
+ // pointer to edge or face shape_map
+ VertexShapeMap vert_shape_map = cgal_model.add_property_map(vert_shape_map_name, nullptr).first;
+
+ // detect anomalities in visitor.
+ bool is_valid = true;
+ // NOTE: map are created when convert shapes to cgal model
+ const EdgeShapeMap& edge_shape_map = cgal_shape.property_map(edge_shape_map_name).first;
+ const FaceShapeMap& face_shape_map = cgal_shape.property_map(face_shape_map_name).first;
+ Visitor visitor{cgal_model, cgal_shape, edge_shape_map, face_shape_map, vert_shape_map, &is_valid};
+
+ // a property map containing the constrained-or-not status of each edge
+ EdgeBoolMap ecm = cgal_model.add_property_map(is_constrained_edge_name, false).first;
+ const auto &p = CGAL::parameters::visitor(visitor)
+ .edge_is_constrained_map(ecm)
+ .throw_on_self_intersection(false);
+ const auto& q = CGAL::parameters::do_not_modify(true);
+ CGAL::Polygon_mesh_processing::corefine(cgal_model, cgal_shape, p, q);
+
+ if (!is_valid) return {};
+
+ FaceTypeMap face_type_map = cgal_model.add_property_map(face_type_map_name, FaceType::not_constrained).first;
+
+ // Select inside and outside face in model
+ set_face_type(face_type_map, cgal_model, vert_shape_map, ecm, cgal_shape, s2i);
+#ifdef DEBUG_OUTPUT_DIR
+ store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "constrained/"); // only debug
+#endif // DEBUG_OUTPUT_DIR
+
+ // flood fill the other faces inside the region.
+ flood_fill_inner(cgal_model, face_type_map);
+
+#ifdef DEBUG_OUTPUT_DIR
+ store(cgal_model, face_type_map, DEBUG_OUTPUT_DIR + "filled/", true); // only debug
+#endif // DEBUG_OUTPUT_DIR
+
+ // IMPROVE: AOIs area could be created during flood fill
+ return create_cut_area_of_interests(cgal_model, shapes, face_type_map);
+}
+
+void priv::flood_fill_inner(const CutMesh &mesh,
+ FaceTypeMap &face_type_map)
+{
+ std::vector process;
+ // guess count of connected not constrained triangles
+ size_t guess_size = 128;
+ process.reserve(guess_size);
+
+ // check if neighbor(one of three in triangle) has type inside
+ auto has_inside_neighbor = [&mesh, &face_type_map](FI fi) {
+ HI hi = mesh.halfedge(fi);
+ HI hi_end = hi;
+ auto exist_next = [&hi, &hi_end, &mesh]() -> bool {
+ hi = mesh.next(hi);
+ return hi != hi_end;
+ };
+ // loop over 3 half edges of face
+ do {
+ HI hi_opposite = mesh.opposite(hi);
+ // open edge doesn't have opposit half edge
+ if (!hi_opposite.is_valid()) continue;
+ FI fi_opposite = mesh.face(hi_opposite);
+ if (!fi_opposite.is_valid()) continue;
+ if (face_type_map[fi_opposite] == FaceType::inside) return true;
+ } while (exist_next());
+ return false;
+ };
+
+ for (FI fi : mesh.faces()) {
+ FaceType type = face_type_map[fi];
+ if (type != FaceType::not_constrained) continue;
+ if (!has_inside_neighbor(fi)) continue;
+ assert(process.empty());
+ process.push_back(fi);
+ //store(mesh, face_type_map, DEBUG_OUTPUT_DIR + "progress.off");
+
+ while (!process.empty()) {
+ FI process_fi = process.back();
+ process.pop_back();
+ // Do not fill twice
+ FaceType& process_type = face_type_map[process_fi];
+ if (process_type == FaceType::inside) continue;
+ process_type = FaceType::inside;
+
+ // check neighbor triangle
+ HI hi = mesh.halfedge(process_fi);
+ HI hi_end = hi;
+ auto exist_next = [&hi, &hi_end, &mesh]() -> bool {
+ hi = mesh.next(hi);
+ return hi != hi_end;
+ };
+ do {
+ HI hi_opposite = mesh.opposite(hi);
+ // open edge doesn't have opposit half edge
+ if (!hi_opposite.is_valid()) continue;
+ FI fi_opposite = mesh.face(hi_opposite);
+ if (!fi_opposite.is_valid()) continue;
+ FaceType type_opposite = face_type_map[fi_opposite];
+ if (type_opposite == FaceType::not_constrained)
+ process.push_back(fi_opposite);
+ } while (exist_next());
+ }
+ }
+}
+
+void priv::collect_surface_data(std::queue &process,
+ std::vector &faces,
+ std::vector &outlines,
+ FaceTypeMap &face_type_map,
+ const CutMesh &mesh)
+{
+ assert(!process.empty());
+ assert(faces.empty());
+ assert(outlines.empty());
+ while (!process.empty()) {
+ FI fi = process.front();
+ process.pop();
+
+ FaceType &fi_type = face_type_map[fi];
+ // Do not process twice
+ if (fi_type == FaceType::inside_processed) continue;
+ assert(fi_type == FaceType::inside);
+ // flag face as processed
+ fi_type = FaceType::inside_processed;
+ faces.push_back(fi);
+
+ // check neighbor triangle
+ HI hi = mesh.halfedge(fi);
+ HI hi_end = hi;
+ do {
+ HI hi_opposite = mesh.opposite(hi);
+ // open edge doesn't have opposit half edge
+ if (!hi_opposite.is_valid()) {
+ outlines.push_back(hi);
+ hi = mesh.next(hi);
+ continue;
+ }
+ FI fi_opposite = mesh.face(hi_opposite);
+ if (!fi_opposite.is_valid()) {
+ outlines.push_back(hi);
+ hi = mesh.next(hi);
+ continue;
+ }
+ FaceType side = face_type_map[fi_opposite];
+ if (side == FaceType::inside) {
+ process.emplace(fi_opposite);
+ } else if (side == FaceType::outside) {
+ // store outlines
+ outlines.push_back(hi);
+ }
+ hi = mesh.next(hi);
+ } while (hi != hi_end);
+ }
+}
+
+void priv::create_reduce_map(ReductionMap &reduction_map, const CutMesh &mesh)
+{
+ const VertexShapeMap &vert_shape_map = mesh.property_map(vert_shape_map_name).first;
+ const EdgeBoolMap &ecm = mesh.property_map(is_constrained_edge_name).first;
+
+ // check if vertex was made by edge_2 which is diagonal of quad
+ auto is_reducible_vertex = [&vert_shape_map](VI reduction_from) -> bool {
+ const IntersectingElement *ie = vert_shape_map[reduction_from];
+ if (ie == nullptr) return false;
+ IntersectingElement::Type type = ie->get_type();
+ return type == IntersectingElement::Type::edge_2;
+ };
+
+ ///
+ /// Append reduction or change existing one.
+ ///
+ /// HalEdge between outside and inside face.
+ /// Target vertex will be reduced, source vertex left
+ /// [[maybe_unused]] &face_type_map, &is_reducible_vertex are need only in debug
+ auto add_reduction = [&] //&reduction_map, &mesh, &face_type_map, &is_reducible_vertex
+ (HI hi) {
+ VI erase = mesh.target(hi);
+ VI left = mesh.source(hi);
+ assert(is_reducible_vertex(erase));
+ assert(!is_reducible_vertex(left));
+ VI &vi = reduction_map[erase];
+ // check if it is first add
+ if (!vi.is_valid())
+ reduction_map[erase] = left;
+ // I have no better rule than take the first
+ // for decide which reduction will be better
+ // But it could be use only one of them
+ };
+
+ for (EI ei : mesh.edges()) {
+ if (!ecm[ei]) continue;
+ HI hi = mesh.halfedge(ei);
+ VI vi = mesh.target(hi);
+ if (is_reducible_vertex(vi)) add_reduction(hi);
+
+ HI hi_op = mesh.opposite(hi);
+ VI vi_op = mesh.target(hi_op);
+ if (is_reducible_vertex(vi_op)) add_reduction(hi_op);
+ }
+#ifdef DEBUG_OUTPUT_DIR
+ store(mesh, reduction_map, DEBUG_OUTPUT_DIR + "reduces/");
+#endif // DEBUG_OUTPUT_DIR
+}
+
+priv::CutAOIs priv::create_cut_area_of_interests(const CutMesh &mesh,
+ const ExPolygons &shapes,
+ FaceTypeMap &face_type_map)
+{
+ // IMPROVE: Create better heuristic for count.
+ size_t faces_per_cut = mesh.faces().size() / shapes.size();
+ size_t outlines_per_cut = faces_per_cut / 2;
+ size_t cuts_per_model = shapes.size() * 2;
+
+ CutAOIs result;
+ result.reserve(cuts_per_model);
+
+ // It is faster to use one queue for all cuts
+ std::queue process;
+ for (FI fi : mesh.faces()) {
+ if (face_type_map[fi] != FaceType::inside) continue;
+
+ CutAOI cut;
+ std::vector &faces = cut.first;
+ std::vector &outlines = cut.second;
+
+ // faces for one surface cut
+ faces.reserve(faces_per_cut);
+ // outline for one surface cut
+ outlines.reserve(outlines_per_cut);
+
+ assert(process.empty());
+ // Process queue of faces to separate to surface_cut
+ process.push(fi);
+ collect_surface_data(process, faces, outlines, face_type_map, mesh);
+ assert(!faces.empty());
+ assert(!outlines.empty());
+ result.emplace_back(std::move(cut));
+ }
+ return result;
+}
+
+namespace priv {
+
+///
+/// Calculate projection distance of point [in mm]
+///
+/// Point to calc distance
+/// Index of point on contour
+/// Model of cutting shape
+/// Ratio for best projection distance
+/// Distance of point from best projection
+float calc_distance(const P3 &p,
+ uint32_t pi,
+ const CutMesh &shapes_mesh,
+ float projection_ratio);
+
+}
+
+float priv::calc_distance(const P3 &p,
+ uint32_t pi,
+ const CutMesh &shapes_mesh,
+ float projection_ratio)
+{
+ // It is known because shapes_mesh is created inside of private space
+ VI vi_start(2 * pi);
+ VI vi_end(2 * pi + 1);
+
+ // Get range for intersection
+ const P3 &start = shapes_mesh.point(vi_start);
+ const P3 &end = shapes_mesh.point(vi_end);
+
+ // find index in vector with biggest difference
+ size_t max_i = 0;
+ float max_val = 0.f;
+ for (size_t i = 0; i < 3; i++) {
+ float val = start[i] - end[i];
+ // abs value
+ if (val < 0.f) val *= -1.f;
+ if (max_val < val) {
+ max_val = val;
+ max_i = i;
+ }
+ }
+
+ float from_start = p[max_i] - start[max_i];
+ float best_distance = projection_ratio * (end[max_i] - start[max_i]);
+ return from_start - best_distance;
+}
+
+priv::VDistances priv::calc_distances(const SurfacePatches &patches,
+ const CutMeshes &models,
+ const CutMesh &shapes_mesh,
+ size_t count_shapes_points,
+ float projection_ratio)
+{
+ priv::VDistances result(count_shapes_points);
+ for (const SurfacePatch &patch : patches) {
+ // map is created during intersection by corefine visitor
+ const VertexShapeMap &vert_shape_map =
+ models[patch.model_id].property_map(vert_shape_map_name).first;
+ uint32_t patch_index = &patch - &patches.front();
+ // map is created during patch creation / dividing
+ const CvtVI2VI& cvt = patch.mesh.property_map(patch_source_name).first;
+ // for each point on outline
+ for (const Loop &loop : patch.loops)
+ for (const VI &vi_patch : loop) {
+ VI vi_model = cvt[vi_patch];
+ if (!vi_model.is_valid()) continue;
+ const IntersectingElement *ie = vert_shape_map[vi_model];
+ if (ie == nullptr) continue;
+ assert(ie->shape_point_index != std::numeric_limits::max());
+ assert(ie->attr != (unsigned char) IntersectingElement::Type::undefined);
+ uint32_t pi = ie->shape_point_index;
+ assert(pi <= count_shapes_points);
+ std::vector &pds = result[pi];
+ uint32_t model_index = patch.model_id;
+ uint32_t aoi_index = patch.aoi_id;
+ //uint32_t hi_index = &hi - &patch.outline.front();
+ const P3 &p = patch.mesh.point(vi_patch);
+ float distance = calc_distance(p, pi, shapes_mesh, projection_ratio);
+ pds.push_back({model_index, aoi_index, patch_index, distance});
+ }
+ }
+ return result;
+}
+
+
+#include "libslic3r/AABBTreeLines.hpp"
+#include "libslic3r/Line.hpp"
+// functions for choose_best_distance
+namespace priv {
+
+// euler square size of vector stored in Point
+float calc_size_sq(const Point &p);
+
+// structure to store index and distance together
+struct ClosePoint
+{
+ // index of closest point from another shape
+ uint32_t index = std::numeric_limits::max();
+ // squere distance to index
+ float dist_sq = std::numeric_limits::max();
+};
+
+struct SearchData{
+// IMPROVE: float lines are enough
+std::vector lines;
+// convert line index into Shape point index
+std::vector cvt;
+// contain lines from prev point to Point index
+AABBTreeIndirect::Tree<2, double> tree;
+};
+
+SearchData create_search_data(const ExPolygons &shapes, const std::vector& mask);
+uint32_t get_closest_point_index(const SearchData &sd, size_t line_idx, const Vec2d &hit_point, const ExPolygons &shapes, const ExPolygonsIndices &s2i);
+
+// use AABB Tree Lines to find closest point
+uint32_t find_closest_point_index(const Point &p, const ExPolygons &shapes, const ExPolygonsIndices &s2i, const std::vector &mask);
+
+std::pair find_closest_point_pair(const ExPolygons &shapes, const std::vector &done_shapes, const ExPolygonsIndices &s2i, const std::vector &mask);
+
+// Search for closest projection to wanted distance
+const ProjectionDistance *get_closest_projection(const ProjectionDistances &distance, float wanted_distance);
+
+// fill result around known index inside one polygon
+void fill_polygon_distances(const ProjectionDistance &pd, uint32_t index, const ExPolygonsIndex &id, ProjectionDistances & result, const ExPolygon &shape, const VDistances &distances);
+
+// search for closest projection for expolygon
+// choose correct cut by source point
+void fill_shape_distances(uint32_t start_index, const ProjectionDistance *start_pd, ProjectionDistances &result, const ExPolygonsIndices &s2i, const ExPolygon &shape, const VDistances &distances);
+
+// find close points between finished and unfinished ExPolygons
+ClosePoint find_close_point(const Point &p, ProjectionDistances &result, std::vector& finished_shapes,const ExPolygonsIndices &s2i, const ExPolygons &shapes);
+
+}
+
+float priv::calc_size_sq(const Point &p){
+ // NOTE: p.squaredNorm() can't be use due to overflow max int value
+ return (float) p.x() * p.x() + (float) p.y() * p.y();
+}
+
+priv::SearchData priv::create_search_data(const ExPolygons &shapes,
+ const std::vector &mask)
+{
+ // IMPROVE: Use float precission (it is enough)
+ SearchData sd;
+ sd.lines.reserve(mask.size());
+ sd.cvt.reserve(mask.size());
+ size_t index = 0;
+ auto add_lines = [&sd, &index, &mask]
+ (const Polygon &poly) {
+ Vec2d prev = poly.back().cast();
+ bool use_point = mask[index + poly.points.size() - 1];
+ for (const Point &p : poly.points) {
+ if (!use_point) {
+ use_point = mask[index];
+ if (use_point) prev = p.cast();
+ } else if (!mask[index]) {
+ use_point = false;
+ } else {
+ Vec2d p_d = p.cast();
+ sd.lines.emplace_back(prev, p_d);
+ sd.cvt.push_back(index);
+ prev = p_d;
+ }
+ ++index;
+ }
+ };
+
+ for (const ExPolygon &shape : shapes) {
+ add_lines(shape.contour);
+ for (const Polygon &hole : shape.holes) add_lines(hole);
+ }
+ sd.tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(sd.lines);
+ return sd;
+}
+
+uint32_t priv::get_closest_point_index(const SearchData &sd,
+ size_t line_idx,
+ const Vec2d &hit_point,
+ const ExPolygons &shapes,
+ const ExPolygonsIndices &s2i)
+{
+ const Linef &line = sd.lines[line_idx];
+ Vec2d dir = line.a - line.b;
+ Vec2d dir_abs = dir.cwiseAbs();
+ // use x coordinate
+ int i = (dir_abs.x() > dir_abs.y())? 0 :1;
+
+ bool use_index = abs(line.a[i] - hit_point[i]) >
+ abs(line.b[i] - hit_point[i]);
+ size_t point_index = sd.cvt[line_idx];
+
+ // Lambda used only for check result
+ [[maybe_unused]] auto is_same = [&s2i, &shapes]
+ (const Vec2d &p, size_t i) -> bool {
+ auto id = s2i.cvt(i);
+ const ExPolygon &shape = shapes[id.expolygons_index];
+ const Polygon &poly = (id.polygon_index == 0) ?
+ shape.contour :
+ shape.holes[id.polygon_index - 1];
+ Vec2i p_ = p.cast();
+ return p_ == poly[id.point_index];
+ };
+
+ if (use_index) {
+ assert(is_same(line.b, point_index));
+ return point_index;
+ }
+ auto id = s2i.cvt(point_index);
+ if (id.point_index != 0) {
+ assert(is_same(line.a, point_index - 1));
+ return point_index - 1;
+ }
+ const ExPolygon &shape = shapes[id.expolygons_index];
+ size_t count_polygon_points = (id.polygon_index == 0) ?
+ shape.contour.size() :
+ shape.holes[id.polygon_index - 1].size();
+ size_t prev_point_index = point_index + (count_polygon_points - 1);
+ assert(is_same(line.a, prev_point_index));
+ // return previous point index
+ return prev_point_index;
+}
+
+// use AABB Tree Lines
+uint32_t priv::find_closest_point_index(const Point &p,
+ const ExPolygons &shapes,
+ const ExPolygonsIndices &s2i,
+ const std::vector &mask)
+{
+ SearchData sd = create_search_data(shapes, mask);
+ size_t line_idx = std::numeric_limits::max();
+ Vec2d hit_point;
+ Vec2d p_d = p.cast();
+ [[maybe_unused]] double distance_sq =
+ AABBTreeLines::squared_distance_to_indexed_lines(
+ sd.lines, sd.tree, p_d, line_idx, hit_point);
+ assert(distance_sq > 0);
+
+ // IMPROVE: one could use line ratio to find closest point
+ return get_closest_point_index(sd, line_idx, hit_point, shapes, s2i);
+}
+
+std::pair priv::find_closest_point_pair(
+ const ExPolygons &shapes,
+ const std::vector &done_shapes,
+ const ExPolygonsIndices &s2i,
+ const std::vector &mask)
+{
+ assert(mask.size() == s2i.get_count());
+ assert(done_shapes.size() == shapes.size());
+ std::vector unfinished_mask = mask; // copy
+
+ size_t index = 0;
+ for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
+ size_t count = count_points(shapes[shape_index]);
+ if (done_shapes[shape_index]) {
+ for (size_t i = 0; i < count; ++i, ++index)
+ unfinished_mask[index] = false;
+ } else {
+ index += count;
+ }
+ }
+ assert(index == s2i.get_count());
+ SearchData sd = create_search_data(shapes, unfinished_mask);
+
+ struct ClosestPair
+ {
+ size_t finish_idx = std::numeric_limits::max();
+ size_t unfinished_line_idx = std::numeric_limits::max();
+ Vec2d hit_point = Vec2d();
+ double distance_sq = std::numeric_limits::max();
+ } cp;
+
+ index = 0;
+ for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
+ const ExPolygon shape = shapes[shape_index];
+ if (!done_shapes[shape_index]) {
+ index += count_points(shape);
+ continue;
+ }
+
+ auto search_in_polygon = [&index, &cp, &sd, &mask](const Polygon& polygon) {
+ for (size_t i = 0; i < polygon.size(); ++i, ++index) {
+ if (mask[index] == false) continue;
+ Vec2d p_d = polygon[i].cast();
+ size_t line_idx = std::numeric_limits::max();
+ Vec2d hit_point;
+ double distance_sq = AABBTreeLines::squared_distance_to_indexed_lines(
+ sd.lines, sd.tree, p_d, line_idx, hit_point, cp.distance_sq);
+ if (distance_sq < 0 ||
+ distance_sq >= cp.distance_sq) continue;
+ assert(line_idx < sd.lines.size());
+ cp.distance_sq = distance_sq;
+ cp.unfinished_line_idx = line_idx;
+ cp.hit_point = hit_point;
+ cp.finish_idx = index;
+ }
+ };
+ search_in_polygon(shape.contour);
+ for (const Polygon& hole: shape.holes)
+ search_in_polygon(hole);
+ }
+ assert(index == s2i.get_count());
+ // check that exists result
+ if (cp.finish_idx == std::numeric_limits::max()) {
+ return std::make_pair(std::numeric_limits::max(),
+ std::numeric_limits::max());
+ }
+
+ size_t unfinished_idx = get_closest_point_index(sd, cp.unfinished_line_idx, cp.hit_point, shapes, s2i);
+ return std::make_pair(cp.finish_idx, unfinished_idx);
+}
+
+const priv::ProjectionDistance *priv::get_closest_projection(
+ const ProjectionDistances &distance, float wanted_distance)
+{
+ // minimal distance
+ float min_d = std::numeric_limits::max();
+ const ProjectionDistance *min_pd = nullptr;
+ for (const ProjectionDistance &pd : distance) {
+ float d = std::fabs(pd.distance - wanted_distance);
+ // There should be limit for maximal distance
+ if (min_d > d) {
+ min_d = d;
+ min_pd = &pd;
+ }
+ }
+ return min_pd;
+}
+
+void priv::fill_polygon_distances(const ProjectionDistance &pd,
+ uint32_t index,
+ const ExPolygonsIndex &id,
+ ProjectionDistances &result,
+ const ExPolygon &shape,
+ const VDistances &distances)
+{
+ const Points& points = (id.polygon_index == 0) ?
+ shape.contour.points :
+ shape.holes[id.polygon_index - 1].points;
+ // border of indexes for Polygon
+ uint32_t first_index = index - id.point_index;
+ uint32_t last_index = first_index + points.size();
+
+ uint32_t act_index = index;
+ const ProjectionDistance* act_pd = &pd;
+
+ // Copy starting pd to result
+ result[act_index] = pd;
+
+ auto exist_next = [&distances, &act_index, &act_pd, &result]
+ (uint32_t nxt_index) {
+ const ProjectionDistance *nxt_pd = get_closest_projection(distances[nxt_index] ,act_pd->distance);
+ // exist next projection distance ?
+ if (nxt_pd == nullptr) return false;
+
+ // check no rewrite result
+ assert(result[nxt_index].aoi_index == std::numeric_limits::max());
+ // copy founded projection to result
+ result[nxt_index] = *nxt_pd; // copy
+
+ // next
+ act_index = nxt_index;
+ act_pd = &result[nxt_index];
+ return true;
+ };
+
+ // last index in circle
+ uint32_t finish_index = (index == first_index) ? (last_index - 1) :
+ (index - 1);
+ // Positive iteration inside polygon
+ do {
+ uint32_t nxt_index = act_index + 1;
+ // close loop of indexes inside of contour
+ if (nxt_index == last_index) nxt_index = first_index;
+ // check that exist next
+ if (!exist_next(nxt_index)) break;
+ } while (act_index != finish_index);
+
+ // when all results for polygon are set no neccessary to iterate negative
+ if (act_index == finish_index) return;
+
+ act_index = index;
+ act_pd = &pd;
+ // Negative iteration inside polygon
+ do {
+ uint32_t nxt_index = (act_index == first_index) ?
+ (last_index-1) : (act_index - 1);
+ // When iterate negative it must be split to parts
+ // and can't iterate in circle
+ assert(nxt_index != index);
+ // check that exist next
+ if (!exist_next(nxt_index)) break;
+ } while (true);
+}
+
+// IMPROVE: when select distance fill in all distances from Patch
+void priv::fill_shape_distances(uint32_t start_index,
+ const ProjectionDistance *start_pd,
+ ProjectionDistances &result,
+ const ExPolygonsIndices &s2i,
+ const ExPolygon &shape,
+ const VDistances &distances)
+{
+ uint32_t expolygons_index = s2i.cvt(start_index).expolygons_index;
+ uint32_t first_shape_index = s2i.cvt({expolygons_index, 0, 0});
+ do {
+ fill_polygon_distances(*start_pd, start_index, s2i.cvt(start_index),result, shape, distances);
+ // seaching only inside shape, return index of closed finished point
+ auto find_close_finished_point = [&first_shape_index, &shape, &result]
+ (const Point &p) -> ClosePoint {
+ uint32_t index = first_shape_index;
+ ClosePoint cp;
+ auto check_finished_points = [&cp, &result, &index, &p]
+ (const Points& pts) {
+ for (const Point &p_ : pts) {
+ // finished point with some distances
+ if (result[index].aoi_index == std::numeric_limits::max()) {
+ ++index;
+ continue;
+ }
+ float distance = calc_size_sq(p_ - p);
+ if (cp.dist_sq > distance) {
+ cp.dist_sq = distance;
+ cp.index = index;
+ }
+ ++index;
+ }
+ };
+ check_finished_points(shape.contour.points);
+ for (const Polygon &h : shape.holes)
+ check_finished_points(h.points);
+ return cp;
+ };
+
+ // find next closest pair of points
+ // (finished + unfinished) in ExPolygon
+ start_index = std::numeric_limits::max(); // unfinished_index
+ uint32_t finished_index = std::numeric_limits::max();
+ float dist_sq = std::numeric_limits::max();
+
+ // first index in shape
+ uint32_t index = first_shape_index;
+ auto check_unfinished_points = [&index, &result, &distances, &find_close_finished_point, &dist_sq, &start_index, &finished_index]
+ (const Points& pts) {
+ for (const Point &p : pts) {
+ // try find unfinished
+ if (result[index].aoi_index !=
+ std::numeric_limits::max() ||
+ distances[index].empty()) {
+ ++index;
+ continue;
+ }
+ ClosePoint cp = find_close_finished_point(p);
+ if (dist_sq > cp.dist_sq) {
+ dist_sq = cp.dist_sq;
+ start_index = index;
+ finished_index = cp.index;
+ }
+ ++index;
+ }
+ };
+ // for each unfinished points
+ check_unfinished_points(shape.contour.points);
+ for (const Polygon &h : shape.holes)
+ check_unfinished_points(h.points);
+ } while (start_index != std::numeric_limits::max());
+}
+
+priv::ClosePoint priv::find_close_point(const Point &p,
+ ProjectionDistances &result,
+ std::vector &finished_shapes,
+ const ExPolygonsIndices &s2i,
+ const ExPolygons &shapes)
+{
+ // result
+ ClosePoint cp;
+ // for all finished points
+ for (uint32_t shape_index = 0; shape_index < shapes.size(); ++shape_index) {
+ if (!finished_shapes[shape_index]) continue;
+ const ExPolygon &shape = shapes[shape_index];
+ uint32_t index = s2i.cvt({shape_index, 0, 0});
+ auto find_close_point_in_points = [&p, &cp, &index, &result]
+ (const Points &pts){
+ for (const Point &p_ : pts) {
+ // Exist result (is finished) ?
+ if (result[index].aoi_index ==
+ std::numeric_limits::max()) {
+ ++index;
+ continue;
+ }
+ float distance_sq = calc_size_sq(p - p_);
+ if (cp.dist_sq > distance_sq) {
+ cp.dist_sq = distance_sq;
+ cp.index = index;
+ }
+ ++index;
+ }
+ };
+ find_close_point_in_points(shape.contour.points);
+ // shape could be inside of another shape's hole
+ for (const Polygon& h:shape.holes)
+ find_close_point_in_points(h.points);
+ }
+ return cp;
+}
+
+// IMPROVE: when select distance fill in all distances from Patch
+priv::ProjectionDistances priv::choose_best_distance(
+ const VDistances &distances, const ExPolygons &shapes, const Point &start, const ExPolygonsIndices &s2i, const SurfacePatches &patches)
+{
+ assert(distances.size() == count_points(shapes));
+
+ // vector of patches for shape
+ std::vector> shapes_patches(shapes.size());
+ for (const SurfacePatch &patch : patches)
+ shapes_patches[patch.shape_id].push_back(&patch-&patches.front());
+
+ // collect one closest projection for each outline point
+ ProjectionDistances result(distances.size());
+
+ // store info about finished shapes
+ std::vector finished_shapes(shapes.size(), {false});
+
+ // wanted distance from ideal projection
+ // Distances are relative to projection distance
+ // so first wanted distance is the closest one (ZERO)
+ float wanted_distance = 0.f;
+
+ std::vector mask_distances(s2i.get_count(), {true});
+ for (const auto &d : distances)
+ if (d.empty()) mask_distances[&d - &distances.front()] = false;
+
+ // Select point from shapes(text contour) which is closest to center (all in 2d)
+ uint32_t unfinished_index = find_closest_point_index(start, shapes, s2i, mask_distances);
+
+#ifdef DEBUG_OUTPUT_DIR
+ Connections connections;
+ connections.reserve(shapes.size());
+ connections.emplace_back(unfinished_index, unfinished_index);
+#endif // DEBUG_OUTPUT_DIR
+
+ do {
+ const ProjectionDistance* pd = get_closest_projection(distances[unfinished_index], wanted_distance);
+ // selection of closest_id should proove that pd has value
+ // (functions: get_closest_point_index and find_close_point_in_points)
+ assert(pd != nullptr);
+ uint32_t expolygons_index = s2i.cvt(unfinished_index).expolygons_index;
+ const ExPolygon &shape = shapes[expolygons_index];
+ std::vector &shape_patches = shapes_patches[expolygons_index];
+ if (shape_patches.size() == 1){
+ // Speed up, only one patch so copy distance from patch
+ uint32_t first_shape_index = s2i.cvt({expolygons_index, 0, 0});
+ uint32_t laset_shape_index = first_shape_index + count_points(shape);
+ for (uint32_t i = first_shape_index; i < laset_shape_index; ++i) {
+ const ProjectionDistances &pds = distances[i];
+ if (pds.empty()) continue;
+ // check that index belongs to patch
+ assert(pds.front().patch_index == shape_patches.front());
+ result[i] = pds.front();
+ if (pds.size() == 1) continue;
+
+ float relative_distance = fabs(result[i].distance - pd->distance);
+ // patch could contain multiple value for one outline point
+ // so choose closest to start point
+ for (uint32_t pds_index = 1; pds_index < pds.size(); ++pds_index) {
+ // check that index still belongs to same patch
+ assert(pds[pds_index].patch_index == shape_patches.front());
+ float relative_distance2 = fabs(pds[pds_index].distance - pd->distance);
+ if (relative_distance > relative_distance2) {
+ relative_distance = relative_distance2;
+ result[i] = pds[pds_index];
+ }
+ }
+ }
+ } else {
+ // multiple patches for expolygon
+ // check that exist patch to fill shape
+ assert(!shape_patches.empty());
+ fill_shape_distances(unfinished_index, pd, result, s2i, shape, distances);
+ }
+
+ finished_shapes[expolygons_index] = true;
+ // The most close points between finished and unfinished shapes
+ auto [finished, unfinished] = find_closest_point_pair(
+ shapes, finished_shapes, s2i, mask_distances);
+
+ // detection of end (best doesn't have value)
+ if (finished == std::numeric_limits::max()) break;
+
+ assert(unfinished != std::numeric_limits::max());
+ const ProjectionDistance &closest_pd = result[finished];
+ // check that best_cp is finished and has result
+ assert(closest_pd.aoi_index != std::numeric_limits::max());
+ wanted_distance = closest_pd.distance;
+ unfinished_index = unfinished;
+
+#ifdef DEBUG_OUTPUT_DIR
+ connections.emplace_back(finished, unfinished);
+#endif // DEBUG_OUTPUT_DIR
+ } while (true); //(unfinished_index != std::numeric_limits::max());
+#ifdef DEBUG_OUTPUT_DIR
+ store(shapes, mask_distances, connections, DEBUG_OUTPUT_DIR + "closest_points.svg");
+#endif // DEBUG_OUTPUT_DIR
+ return result;
+}
+
+// functions to help 'diff_model'
+namespace priv {
+const VI default_vi(std::numeric_limits::max());
+
+// Keep info about intersection source
+struct Source{ HI hi; int sdim=0;};
+using Sources = std::vector;
+const std::string vertex_source_map_name = "v:SourceIntersecting";
+using VertexSourceMap = CutMesh::Property_map;
+
+///
+/// Corefine visitor
+/// Store intersection source for vertices of constrained edge of tm1
+/// Must be used with corefine flag no modification of tm2
+///
+struct IntersectionSources
+{
+ const CutMesh *patch; // patch
+ const CutMesh *model; // const model
+
+ VertexSourceMap vmap;
+
+ // keep sources from call intersection_point_detected
+ // until call new_vertex_added
+ Sources* sources;
+
+ // count intersections
+ void intersection_point_detected(std::size_t i_id,
+ int sdim,
+ HI h_f,
+ HI h_e,
+ const CutMesh &tm_f,
+ const CutMesh &tm_e,
+ bool is_target_coplanar,
+ bool is_source_coplanar)
+ {
+ Source source;
+ if (&tm_e == model) {
+ source = {h_e, sdim};
+ // check other CGAL model that is patch
+ assert(&tm_f == patch);
+ if (is_target_coplanar) {
+ assert(sdim == 0);
+ vmap[tm_f.source(h_f)] = source;
+ }
+ if (is_source_coplanar) {
+ assert(sdim == 0);
+ vmap[tm_f.target(h_f)] = source;
+ }
+
+ // clear source to be able check that this intersection source is
+ // not used any more
+ if (is_source_coplanar || is_target_coplanar) source = {};
+ } else {
+ source = {h_f, sdim};
+ assert(&tm_f == model && &tm_e == patch);
+ assert(!is_target_coplanar);
+ assert(!is_source_coplanar);
+ // if (is_target_coplanar) vmap[tm_e.source(h_e)] = source;
+ // if (is_source_coplanar) vmap[tm_e.target(h_e)] = source;
+ // if (sdim == 0)
+ // vmap[tm_e.target(h_e)] = source;
+ }
+
+ // By documentation i_id is consecutive.
+ // check id goes in a row, without skips
+ assert(sources->size() == i_id);
+ // add source of intersection
+ sources->push_back(source);
+ }
+
+ ///
+ /// Store VI to intersections by i_id
+ ///
+ /// Order number of intersection point
+ /// New added vertex
+ /// Affected mesh
+ void new_vertex_added(std::size_t i_id, VI v, const CutMesh &tm)
+ {
+ // check that it is first insertation into item of vmap
+ assert(!vmap[v].hi.is_valid());
+ // check valid addresing into sources
+ assert(i_id < sources->size());
+ // check that source has value
+ assert(sources->at(i_id).hi.is_valid());
+ vmap[v] = sources->at(i_id);
+ }
+
+ // Not used visitor functions
+ void before_subface_creations(FI /* f_old */, CutMesh & /* mesh */) {}
+ void after_subface_created(FI /* f_new */, CutMesh & /* mesh */) {}
+ void after_subface_creations(CutMesh &) {}
+ void before_subface_created(CutMesh &) {}
+ void before_edge_split(HI /* h */, CutMesh & /* tm */) {}
+ void edge_split(HI /* hnew */, CutMesh & /* tm */) {}
+ void after_edge_split() {}
+ void add_retriangulation_edge(HI /* h */, CutMesh & /* tm */) {}
+};
+
+///
+/// Create map1 and map2
+///
+/// Convert tm1.face to type
+/// Corefined mesh
+/// Source of intersection
+/// Identify constrainde edge
+/// Convert tm1.face to type
+void create_face_types(FaceTypeMap &map,
+ const CutMesh &tm1,
+ const CutMesh &tm2,
+ const EdgeBoolMap &ecm,
+ const VertexSourceMap &sources);
+
+///
+/// Implement 'cut' Minus 'clipper', where clipper is reverse input Volume
+/// NOTE: clipper will be modified (corefined by cut) !!!
+///
+/// differ from
+/// differ what
+/// True on succes, otherwise FALSE
+bool clip_cut(SurfacePatch &cut, CutMesh clipper);
+
+BoundingBoxf3 bounding_box(const CutAOI &cut, const CutMesh &mesh);
+BoundingBoxf3 bounding_box(const CutMesh &mesh);
+BoundingBoxf3 bounding_box(const SurfacePatch &ecut);
+
+///
+/// Create patch
+///
+/// Define patch faces
+/// Source of fis
+/// NOTE: Need temporary add property map for convert vertices
+/// Options to reduce vertices from fis.
+/// NOTE: Used for skip vertices made by diagonal edge in rectangle of shape side
+/// Patch
+SurfacePatch create_surface_patch(const std::vector &fis,
+ /*const*/ CutMesh &mesh,
+ const ReductionMap *rmap = nullptr);
+
+} // namespace priv
+
+void priv::create_face_types(FaceTypeMap &map,
+ const CutMesh &tm1,
+ const CutMesh &tm2,
+ const EdgeBoolMap &ecm,
+ const VertexSourceMap &sources)
+{
+ auto get_intersection_source = [&tm2](const Source& s1, const Source& s2)->FI{
+ // when one of sources is face than return it
+ FI fi1 = tm2.face(s1.hi);
+ if (s1.sdim == 2) return fi1;
+ FI fi2 = tm2.face(s2.hi);
+ if (s2.sdim == 2) return fi2;
+ // both vertices are made by same source triangle
+ if (fi1 == fi2) return fi1;
+
+ // when one from sources is edge second one decide side of triangle triangle
+ HI hi1_opposit = tm2.opposite(s1.hi);
+ FI fi1_opposit;
+ if (hi1_opposit.is_valid())
+ fi1_opposit = tm2.face(hi1_opposit);
+ if (fi2 == fi1_opposit) return fi2;
+
+ HI hi2_opposit = tm2.opposite(s2.hi);
+ FI fi2_opposit;
+ if (hi2_opposit.is_valid())
+ fi2_opposit = tm2.face(hi2_opposit);
+ if (fi1 == fi2_opposit) return fi1;
+ if (fi1_opposit.is_valid() && fi1_opposit == fi2_opposit)
+ return fi1_opposit;
+
+ // when intersection is vertex need loop over neighbor
+ for (FI fi_around_hi1 : tm2.faces_around_target(s1.hi)) {
+ for (FI fi_around_hi2 : tm2.faces_around_target(s2.hi)) {
+ if (fi_around_hi1 == fi_around_hi2)
+ return fi_around_hi1;
+ }
+ }
+
+ // should never rich it
+ // Exist case when do not know source triangle for decide side of intersection
+ assert(false);
+ return FI();
+ };
+
+ for (FI fi : tm1.faces()) map[fi] = FaceType::not_constrained;
+ for (EI ei1 : tm1.edges()) {
+ if (!get(ecm, ei1)) continue;
+
+ // get faces from tm1 (f1a + f1b)
+ HI hi1 = tm1.halfedge(ei1);
+ assert(hi1.is_valid());
+ FI f1a = tm1.face(hi1);
+ assert(f1a.is_valid());
+ HI hi_op = tm1.opposite(hi1);
+ assert(hi_op.is_valid());
+ FI f1b = tm1.face(hi_op);
+ assert(f1b.is_valid());
+
+ // get faces from tm2 (f2a + f2b)
+ VI vi1_source = tm1.source(hi1);
+ assert(vi1_source.is_valid());
+ VI vi1_target = tm1.target(hi1);
+ assert(vi1_target.is_valid());
+
+ const Source &s_s = sources[vi1_source];
+ const Source &s_t = sources[vi1_target];
+ FI fi2 = get_intersection_source(s_s, s_t);
+
+ // in release solve situation that face was NOT deduced
+ if (!fi2.is_valid()) continue;
+
+ HI hi2 = tm2.halfedge(fi2);
+ std::array t;
+ size_t ti =0;
+ for (VI vi2 : tm2.vertices_around_face(hi2))
+ t[ti++] = &tm2.point(vi2);
+
+ // triangle tip from face f1a
+ VI vi1a_tip = tm1.target(tm1.next(hi1));
+ assert(vi1a_tip.is_valid());
+ const P3 &p = tm1.point(vi1a_tip);
+
+ // check if f1a is behinde f2a
+ // inside mean it will be used
+ // outside will be discarded
+ if (CGAL::orientation(*t[0], *t[1], *t[2], p) == CGAL::POSITIVE) {
+ map[f1a] = FaceType::inside;
+ map[f1b] = FaceType::outside;
+ } else {
+ map[f1a] = FaceType::outside;
+ map[f1b] = FaceType::inside;
+ }
+ }
+}
+
+#include
+#include
+bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper)
+{
+ CutMesh& tm = cut.mesh;
+ // create backup for case that there is no intersection
+ CutMesh backup_copy = tm;
+
+ class ExistIntersectionClipVisitor: public CGAL::Polygon_mesh_processing::Corefinement::Default_visitor
+ {
+ bool* exist_intersection;
+ public:
+ ExistIntersectionClipVisitor(bool *exist_intersection): exist_intersection(exist_intersection){}
+ void intersection_point_detected(std::size_t, int , HI, HI, const CutMesh&, const CutMesh&, bool, bool)
+ { *exist_intersection = true;}
+ };
+ bool exist_intersection = false;
+ ExistIntersectionClipVisitor visitor{&exist_intersection};
+
+ // namep parameters for model tm and function clip
+ const auto &np_tm = CGAL::parameters::visitor(visitor)
+ .throw_on_self_intersection(false);
+
+ // name parameters for model clipper and function clip
+ const auto &np_c = CGAL::parameters::throw_on_self_intersection(false);
+ // Can't use 'do_not_modify', when Ture than clipper has to be closed !!
+ // .do_not_modify(true);
+ // .throw_on_self_intersection(false); is set automaticaly by param 'do_not_modify'
+ // .clip_volume(false); is set automaticaly by param 'do_not_modify'
+
+ bool suc = CGAL::Polygon_mesh_processing::clip(tm, clipper, np_tm, np_c);
+
+ // true if the output surface mesh is manifold.
+ // If false is returned tm and clipper are only corefined.
+ assert(suc);
+ // decide what TODO when can't clip source object !?!
+ if (!exist_intersection || !suc) {
+ // TODO: test if cut is fully in or fully out!!
+ cut.mesh = backup_copy;
+ return false;
+ }
+ return true;
+}
+
+BoundingBoxf3 priv::bounding_box(const CutAOI &cut, const CutMesh &mesh) {
+ const P3& p_from_cut = mesh.point(mesh.target(mesh.halfedge(cut.first.front())));
+ Vec3d min(p_from_cut.x(), p_from_cut.y(), p_from_cut.z());
+ Vec3d max = min;
+ for (FI fi : cut.first) {
+ for(VI vi: mesh.vertices_around_face(mesh.halfedge(fi))){
+ const P3& p = mesh.point(vi);
+ for (size_t i = 0; i < 3; ++i) {
+ if (min[i] > p[i]) min[i] = p[i];
+ if (max[i] < p[i]) max[i] = p[i];
+ }
+ }
+ }
+ return BoundingBoxf3(min, max);
+}
+
+BoundingBoxf3 priv::bounding_box(const CutMesh &mesh)
+{
+ const P3 &p_from_cut = *mesh.points().begin();
+ Vec3d min(p_from_cut.x(), p_from_cut.y(), p_from_cut.z());
+ Vec3d max = min;
+ for (VI vi : mesh.vertices()) {
+ const P3 &p = mesh.point(vi);
+ for (size_t i = 0; i < 3; ++i) {
+ if (min[i] > p[i]) min[i] = p[i];
+ if (max[i] < p[i]) max[i] = p[i];
+ }
+ }
+ return BoundingBoxf3(min, max);
+}
+
+BoundingBoxf3 priv::bounding_box(const SurfacePatch &ecut) {
+ return bounding_box(ecut.mesh);
+}
+
+priv::SurfacePatch priv::create_surface_patch(const std::vector &fis,
+ /* const */ CutMesh &mesh,
+ const ReductionMap *rmap)
+{
+ auto is_counted = mesh.add_property_map("v:is_counted").first;
+ uint32_t count_vertices = 0;
+ if (rmap == nullptr) {
+ for (FI fi : fis)
+ for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi)))
+ if (!is_counted[vi]) {
+ is_counted[vi] = true;
+ ++count_vertices;
+ }
+ } else {
+ for (FI fi : fis)
+ for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
+ // Will vertex be reduced?
+ if ((*rmap)[vi].is_valid()) continue;
+ if (!is_counted[vi]) {
+ is_counted[vi] = true;
+ ++count_vertices;
+ }
+ }
+ }
+ mesh.remove_property_map(is_counted);
+
+ uint32_t count_faces = fis.size();
+ // IMPROVE: Value is greater than neccessary, count edges used twice
+ uint32_t count_edges = count_faces*3;
+
+ CutMesh cm;
+ cm.reserve(count_vertices, count_edges, count_faces);
+
+ // vertex conversion function from mesh VI to result VI
+ CvtVI2VI mesh2result = mesh.add_property_map("v:mesh2result").first;
+
+ if (rmap == nullptr) {
+ for (FI fi : fis) {
+ std::array t;
+ int index = 0;
+ for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
+ VI &vi_cvt = mesh2result[vi];
+ if (!vi_cvt.is_valid()) {
+ vi_cvt = VI(cm.vertices().size());
+ cm.add_vertex(mesh.point(vi));
+ }
+ t[index++] = vi_cvt;
+ }
+ cm.add_face(t[0], t[1], t[2]);
+ }
+ } else {
+ for (FI fi :fis) {
+ std::array t;
+ int index = 0;
+ bool exist_reduction = false;
+ for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) {
+ VI vi_r = (*rmap)[vi];
+ if (vi_r.is_valid()) {
+ exist_reduction = true;
+ vi = vi_r;
+ }
+ VI &vi_cvt = mesh2result[vi];
+ if (!vi_cvt.is_valid()) {
+ vi_cvt = VI(cm.vertices().size());
+ cm.add_vertex(mesh.point(vi));
+ }
+ t[index++] = vi_cvt;
+ }
+
+ // prevent add reduced triangle
+ if (exist_reduction &&
+ (t[0] == t[1] ||
+ t[1] == t[2] ||
+ t[2] == t[0]))
+ continue;
+
+ cm.add_face(t[0], t[1], t[2]);
+ }
+ }
+
+ assert(count_vertices == cm.vertices().size());
+ assert((rmap == nullptr && count_faces == cm.faces().size()) ||
+ (rmap != nullptr && count_faces >= cm.faces().size()));
+ assert(count_edges >= cm.edges().size());
+
+ // convert VI from this patch to source VI, when exist
+ CvtVI2VI cvt = cm.add_property_map(patch_source_name).first;
+ // vi_s .. VertexIndex into mesh (source)
+ // vi_d .. new VertexIndex in cm (destination)
+ for (VI vi_s : mesh.vertices()) {
+ VI vi_d = mesh2result[vi_s];
+ if (!vi_d.is_valid()) continue;
+ cvt[vi_d] = vi_s;
+ }
+ mesh.remove_property_map(mesh2result);
+ return {std::move(cm)};
+}
+
+// diff_models help functions
+namespace priv {
+
+struct SurfacePatchEx
+{
+ SurfacePatch patch;
+
+ // flag that part will be deleted
+ bool full_inside = false;
+ // flag that Patch could contain more than one part
+ bool just_cliped = false;
+};
+using SurfacePatchesEx = std::vector;
+
+
+using BBS = std::vector;
+///
+/// Create bounding boxes for AOI
+///
+/// Cutted AOI from models
+/// Source points of cuts
+/// Bounding boxes
+BBS create_bbs(const VCutAOIs &cuts, const CutMeshes &cut_models);
+
+using Primitive = CGAL::AABB_face_graph_triangle_primitive;
+using Traits = CGAL::AABB_traits;
+using Ray = EpicKernel::Ray_3;
+using Tree = CGAL::AABB_tree;
+using Trees = std::vector;
+///
+/// Create AABB trees for check when patch is whole inside of model
+///
+/// Source for trees
+/// trees
+Trees create_trees(const CutMeshes &models);
+
+///
+/// Check whether bounding box has intersection with model
+///
+/// Bounding box to check
+/// Model to check with
+/// All bounding boxes from VCutAOIs
+/// Help index into VCutAOIs
+/// True when exist bounding boxes intersection
+bool has_bb_intersection(const BoundingBoxf3 &bb,
+ size_t model_index,
+ const BBS &bbs,
+ const ModelCut2index &m2i);
+
+///
+/// Only for model without intersection
+/// Use ray (in projection direction) from a point from patch
+/// and count intersections: pair .. outside | odd .. inside
+///
+/// Patch to check
+/// Model converted to AABB tree
+/// Define direction of projection
+/// True when patch point lay inside of model defined by tree,
+/// otherwise FALSE
+bool is_patch_inside_of_model(const SurfacePatch &patch,
+ const Tree &tree,
+ const Project3d &projection);
+
+///
+/// Return some shape point index which identify shape
+/// NOTE: Used to find expolygon index
+///
+/// Used to search source shapes poin
+///
+/// shape point index
+uint32_t get_shape_point_index(const CutAOI &cut, const CutMesh &model);
+
+using PatchNumber = CutMesh::Property_map;
+///
+/// Separate triangles singned with number n
+///
+/// Face indices owned by separate patch
+/// Original patch
+/// NOTE: Can't be const. For indexing vetices need temporary add property map
+/// conversion map
+/// Just separated patch
+SurfacePatch separate_patch(const std::vector &fis,
+ /* const*/ SurfacePatch &patch,
+ const CvtVI2VI &cvt_from);
+
+///
+/// Separate connected triangles into it's own patches
+/// new patches are added to back of input patches
+///
+/// index into patches
+/// In/Out Patches
+void divide_patch(size_t i, SurfacePatchesEx &patches);
+
+///
+/// Fill outline in patches by open edges
+///
+/// Input/Output meshes with open edges
+void collect_open_edges(SurfacePatches &patches);
+
+} // namespace priv
+
+std::vector priv::create_bbs(const VCutAOIs &cuts,
+ const CutMeshes &cut_models)
+{
+ size_t count = 0;
+ for (const CutAOIs &cut : cuts) count += cut.size();
+
+ std::vector bbs;
+ bbs.reserve(count);
+ for (size_t model_index = 0; model_index < cut_models.size(); ++model_index) {
+ const CutMesh &cut_model = cut_models[model_index];
+ const CutAOIs &cutAOIs = cuts[model_index];
+ for (size_t cut_index = 0; cut_index < cutAOIs.size(); ++cut_index) {
+ const CutAOI &cut = cutAOIs[cut_index];
+ bbs.push_back(bounding_box(cut, cut_model));
+ }
+ }
+ return bbs;
+}
+
+
+priv::Trees priv::create_trees(const CutMeshes &models) {
+ Trees result;
+ result.reserve(models.size());
+ for (const CutMesh &model : models) {
+ Tree tree;
+ tree.insert(faces(model).first, faces(model).second, model);
+ tree.build();
+ result.emplace_back(std::move(tree));
+ }
+ return result;
+}
+
+bool priv::has_bb_intersection(const BoundingBoxf3 &bb,
+ size_t model_index,
+ const BBS &bbs,
+ const ModelCut2index &m2i)
+{
+ const auto&offsets = m2i.get_offsets();
+ // for cut index with model_index2
+ size_t start = offsets[model_index];
+ size_t next = model_index + 1;
+ size_t end = (next < offsets.size()) ? offsets[next] : m2i.get_count();
+ for (size_t bb_index = start; bb_index < end; bb_index++)
+ if (bb.intersects(bbs[bb_index])) return true;
+ return false;
+}
+
+bool priv::is_patch_inside_of_model(const SurfacePatch &patch,
+ const Tree &tree,
+ const Project3d &projection)
+{
+ // TODO: Solve model with hole in projection direction !!!
+ const P3 &a = patch.mesh.point(VI(0));
+ Vec3d a_(a.x(), a.y(), a.z());
+ Vec3d b_ = projection.project(a_);
+ P3 b(b_.x(), b_.y(), b_.z());
+
+ Ray ray_query(a, b);
+ size_t count = tree.number_of_intersected_primitives(ray_query);
+ bool is_in = (count % 2) == 1;
+
+ // try opposit direction result should be same, otherwise open model is used
+ //Vec3f c_ = a_ - (b_ - a_); // opposit direction
+ //P3 c(c_.x(), c_.y(), c_.z());
+ //Ray ray_query2(a, b);
+ //size_t count2 = tree.number_of_intersected_primitives(ray_query2);
+ //bool is_in2 = (count2 % 2) == 1;
+ assert(((tree.number_of_intersected_primitives(
+ Ray(a, P3(2 * a.x() - b.x(),
+ 2 * a.y() - b.y(),
+ 2 * a.z() - b.z()))) %
+ 2) == 1) == is_in);
+ return is_in;
+}
+
+uint32_t priv::get_shape_point_index(const CutAOI &cut, const CutMesh &model)
+{
+ // map is created during intersection by corefine visitor
+ const VertexShapeMap &vert_shape_map = model.property_map(vert_shape_map_name).first;
+ // for each half edge of outline
+ for (HI hi : cut.second) {
+ VI vi = model.source(hi);
+ const IntersectingElement *ie = vert_shape_map[vi];
+ if (ie == nullptr) continue;
+ assert(ie->shape_point_index != std::numeric_limits::max());
+ return ie->shape_point_index;
+ }
+ // can't found any intersecting element in cut
+ assert(false);
+ return 0;
+}
+
+priv::SurfacePatch priv::separate_patch(const std::vector& fis,
+ SurfacePatch &patch,
+ const CvtVI2VI &cvt_from)
+{
+ assert(patch.mesh.is_valid());
+ SurfacePatch patch_new = create_surface_patch(fis, patch.mesh);
+ patch_new.bb = bounding_box(patch_new.mesh);
+ patch_new.aoi_id = patch.aoi_id;
+ patch_new.model_id = patch.model_id;
+ patch_new.shape_id = patch.shape_id;
+ // fix cvt
+ CvtVI2VI cvt = patch_new.mesh.property_map(patch_source_name).first;
+ for (VI &vi : cvt) {
+ if (!vi.is_valid()) continue;
+ vi = cvt_from[vi];
+ }
+ return patch_new;
+}
+
+void priv::divide_patch(size_t i, SurfacePatchesEx &patches)
+{
+ SurfacePatchEx &patch_ex = patches[i];
+ assert(patch_ex.just_cliped);
+ patch_ex.just_cliped = false;
+
+ SurfacePatch& patch = patch_ex.patch;
+ CutMesh& cm = patch.mesh;
+ assert(!cm.faces().empty());
+ std::string patch_number_name = "f:patch_number";
+ CutMesh::Property_map is_processed = cm.add_property_map(patch_number_name, false).first;
+
+ const CvtVI2VI& cvt_from = patch.mesh.property_map(patch_source_name).first;
+
+ std::vector fis;
+ fis.reserve(cm.faces().size());
+
+ SurfacePatchesEx new_patches;
+ std::vector queue;
+ // IMPROVE: create groups around triangles and than connect groups
+ for (FI fi_cm : cm.faces()) {
+ if (is_processed[fi_cm]) continue;
+ assert(queue.empty());
+ queue.push_back(fi_cm);
+ if (!fis.empty()) {
+ // Be carefull after push to patches,
+ // all ref on patch contain non valid values
+ SurfacePatchEx patch_ex_n;
+ patch_ex_n.patch = separate_patch(fis, patch, cvt_from);
+ patch_ex_n.patch.is_whole_aoi = false;
+ new_patches.push_back(std::move(patch_ex_n));
+ fis.clear();
+ }
+ // flood fill from triangle fi_cm to surrounding
+ do {
+ FI fi_q = queue.back();
+ queue.pop_back();
+ if (is_processed[fi_q]) continue;
+ is_processed[fi_q] = true;
+ fis.push_back(fi_q);
+ HI hi = cm.halfedge(fi_q);
+ for (FI fi : cm.faces_around_face(hi)) {
+ // by documentation The face descriptor may be the null face, and it may be several times the same face descriptor.
+ if (!fi.is_valid()) continue;
+ if (!is_processed[fi]) queue.push_back(fi);
+ }
+ } while (!queue.empty());
+ }
+ cm.remove_property_map(is_processed);
+ assert(!fis.empty());
+
+ // speed up for only one patch - no dividing (the most common)
+ if (new_patches.empty()) {
+ patch.bb = bounding_box(cm);
+ patch.is_whole_aoi = false;
+ } else {
+ patch = separate_patch(fis, patch, cvt_from);
+ patches.insert(patches.end(), new_patches.begin(), new_patches.end());
+ }
+}
+
+void priv::collect_open_edges(SurfacePatches &patches) {
+ std::vector open_half_edges;
+ for (SurfacePatch &patch : patches) {
+ open_half_edges.clear();
+ const CutMesh &mesh = patch.mesh;
+ for (FI fi : mesh.faces()) {
+ HI hi1 = mesh.halfedge(fi);
+ assert(hi1.is_valid());
+ HI hi2 = mesh.next(hi1);
+ assert(hi2.is_valid());
+ HI hi3 = mesh.next(hi2);
+ assert(hi3.is_valid());
+ // Is fi triangle?
+ assert(mesh.next(hi3) == hi1);
+ for (HI hi : {hi1, hi2, hi3}) {
+ HI hi_op = mesh.opposite(hi);
+ FI fi_op = mesh.face(hi_op);
+ if (!fi_op.is_valid())
+ open_half_edges.push_back(hi);
+ }
+ }
+ patch.loops = create_loops(open_half_edges, mesh);
+ }
+}
+
+priv::SurfacePatches priv::diff_models(VCutAOIs &cuts,
+ /*const*/ CutMeshes &cut_models,
+ /*const*/ CutMeshes &models,
+ const Project3d &projection)
+{
+ // IMPROVE: when models contain ONE mesh. It is only about convert cuts to patches
+ // and reduce unneccessary triangles on contour
+
+ //Convert model_index and cut_index into one index
+ priv::ModelCut2index m2i(cuts);
+
+ // create bounding boxes for cuts
+ std::vector bbs = create_bbs(cuts, cut_models);
+ Trees trees(models.size());
+
+ SurfacePatches patches;
+
+ // queue of patches for one AOI (permanent with respect to for loop)
+ SurfacePatchesEx aoi_patches;
+
+ //SurfacePatches aoi_patches;
+ patches.reserve(m2i.get_count()); // only approximation of count
+ size_t index = 0;
+ for (size_t model_index = 0; model_index < models.size(); ++model_index) {
+ CutAOIs &model_cuts = cuts[model_index];
+ CutMesh &cut_model_ = cut_models[model_index];
+ const CutMesh &cut_model = cut_model_;
+ ReductionMap vertex_reduction_map = cut_model_.add_property_map(vertex_reduction_map_name).first;
+ create_reduce_map(vertex_reduction_map, cut_model);
+
+ for (size_t cut_index = 0; cut_index < model_cuts.size(); ++cut_index, ++index) {
+ const CutAOI &cut = model_cuts[cut_index];
+ SurfacePatchEx patch_ex;
+ SurfacePatch &patch = patch_ex.patch;
+ patch = create_surface_patch(cut.first, cut_model_, &vertex_reduction_map);
+ patch.bb = bbs[index];
+ patch.aoi_id = cut_index;
+ patch.model_id = model_index;
+ patch.shape_id = get_shape_point_index(cut, cut_model);
+ patch.is_whole_aoi = true;
+
+ aoi_patches.clear();
+ aoi_patches.push_back(patch_ex);
+ for (size_t model_index2 = 0; model_index2 < models.size(); ++model_index2) {
+ // do not clip source model itself
+ if (model_index == model_index2) continue;
+ for (SurfacePatchEx &patch_ex : aoi_patches) {
+ SurfacePatch &patch = patch_ex.patch;
+ if (has_bb_intersection(patch.bb, model_index2, bbs, m2i) &&
+ clip_cut(patch, models[model_index2])){
+ patch_ex.just_cliped = true;
+ } else {
+ // build tree on demand
+ // NOTE: it is possible not neccessary: e.g. one model
+ Tree &tree = trees[model_index2];
+ if (tree.empty()) {
+ const CutMesh &model = models[model_index2];
+ auto f_range = faces(model);
+ tree.insert(f_range.first, f_range.second, model);
+ tree.build();
+ }
+ if (is_patch_inside_of_model(patch, tree, projection))
+ patch_ex.full_inside = true;
+ }
+ }
+ // erase full inside
+ for (size_t i = aoi_patches.size(); i != 0; --i) {
+ auto it = aoi_patches.begin() + (i - 1);
+ if (it->full_inside) aoi_patches.erase(it);
+ }
+
+ // detection of full AOI inside of model
+ if (aoi_patches.empty()) break;
+
+ // divide cliped into parts
+ size_t end = aoi_patches.size();
+ for (size_t i = 0; i < end; ++i)
+ if (aoi_patches[i].just_cliped)
+ divide_patch(i, aoi_patches);
+ }
+
+ if (!aoi_patches.empty()) {
+ patches.reserve(patches.size() + aoi_patches.size());
+ for (SurfacePatchEx &patch : aoi_patches)
+ patches.push_back(std::move(patch.patch));
+
+ }
+ }
+ cut_model_.remove_property_map(vertex_reduction_map);
+ }
+
+ // Also use outline inside of patches(made by non manifold models)
+ // IMPROVE: trace outline from AOIs
+ collect_open_edges(patches);
+ return patches;
+}
+
+bool priv::is_over_whole_expoly(const SurfacePatch &patch,
+ const ExPolygons &shapes,
+ const VCutAOIs &cutAOIs,
+ const CutMeshes &meshes)
+{
+ if (!patch.is_whole_aoi) return false;
+ return is_over_whole_expoly(cutAOIs[patch.model_id][patch.aoi_id],
+ shapes[patch.shape_id],
+ meshes[patch.model_id]);
+}
+
+bool priv::is_over_whole_expoly(const CutAOI &cutAOI,
+ const ExPolygon &shape,
+ const CutMesh &mesh)
+{
+ // NonInterupted contour is without other point and contain all from shape
+ const VertexShapeMap &vert_shape_map = mesh.property_map(vert_shape_map_name).first;
+ for (HI hi : cutAOI.second) {
+ const IntersectingElement *ie_s = vert_shape_map[mesh.source(hi)];
+ const IntersectingElement *ie_t = vert_shape_map[mesh.target(hi)];
+ if (ie_s == nullptr || ie_t == nullptr)
+ return false;
+
+ assert(ie_s->attr != (unsigned char) IntersectingElement::Type::undefined);
+ assert(ie_t->attr != (unsigned char) IntersectingElement::Type::undefined);
+
+ // check if it is neighbor indices
+ uint32_t i_s = ie_s->shape_point_index;
+ uint32_t i_t = ie_t->shape_point_index;
+ assert(i_s != std::numeric_limits::max());
+ assert(i_t != std::numeric_limits::max());
+ if (i_s == std::numeric_limits::max() ||
+ i_t == std::numeric_limits::max())
+ return false;
+
+ // made by same index
+ if (i_s == i_t) continue;
+
+ // order from source to target
+ if (i_s > i_t) {
+ std::swap(i_s, i_t);
+ std::swap(ie_s, ie_t);
+ }
+ // Must be after fix order !!
+ bool is_last_polygon_segment = ie_s->is_first() && ie_t->is_last();
+ if (is_last_polygon_segment) {
+ std::swap(i_s, i_t);
+ std::swap(ie_s, ie_t);
+ }
+
+ // Is continous indices
+ if (!is_last_polygon_segment &&
+ (ie_s->is_last() || (i_s + 1) != i_t))
+ return false;
+
+ IntersectingElement::Type t_s = ie_s->get_type();
+ IntersectingElement::Type t_t = ie_t->get_type();
+ if (t_s == IntersectingElement::Type::undefined ||
+ t_t == IntersectingElement::Type::undefined)
+ return false;
+
+ // next segment must start with edge intersection
+ if (t_t != IntersectingElement::Type::edge_1)
+ return false;
+
+ // After face1 must be edge2 or face2
+ if (t_s == IntersectingElement::Type::face_1)
+ return false;
+ }
+
+ // When all open edges are on contour than there is NO holes is shape
+ auto is_open = [&mesh](HI hi)->bool {
+ HI opposite = mesh.opposite(hi);
+ return !mesh.face(opposite).is_valid();
+ };
+
+ std::vector opens; // copy
+ opens.reserve(cutAOI.second.size());
+ for (HI hi : cutAOI.second) // from lower to bigger
+ if (is_open(hi)) opens.push_back(hi);
+ std::sort(opens.begin(), opens.end());
+
+ for (FI fi: cutAOI.first) {
+ HI face_hi = mesh.halfedge(fi);
+ for (HI hi : mesh.halfedges_around_face(face_hi)) {
+ if (!is_open(hi)) continue;
+ // open edge
+ auto lb = std::lower_bound(opens.begin(), opens.end(), hi);
+ if (lb == opens.end() || *lb != hi)
+ return false; // not in contour
+ }
+ }
+ return true;
+}
+
+std::vector priv::select_patches(const ProjectionDistances &best_distances,
+ const SurfacePatches &patches,
+
+ const ExPolygons &shapes,
+ const ExPolygonsIndices &s2i,
+ const VCutAOIs &cutAOIs,
+ const CutMeshes &meshes,
+ const Project &projection)
+{
+ // extension to cover numerical mistake made by back projection patch from 3d to 2d
+ const float extend_delta = 5.f / Emboss::SHAPE_SCALE; // [Font points scaled by Emboss::SHAPE_SCALE]
+
+ // vector of patches for shape
+ std::vector> used_shapes_patches(shapes.size());
+ std::vector in_distances(patches.size(), {false});
+ for (const ProjectionDistance &d : best_distances) {
+ // exist valid projection for shape point?
+ if (d.patch_index == std::numeric_limits::max()) continue;
+ if (in_distances[d.patch_index]) continue;
+ in_distances[d.patch_index] = true;
+
+ ExPolygonsIndex id = s2i.cvt(&d - &best_distances.front());
+ used_shapes_patches[id.expolygons_index].push_back(d.patch_index);
+ }
+
+ // vector of patches for shape
+ std::vector> shapes_patches(shapes.size());
+ for (const SurfacePatch &patch : patches)
+ shapes_patches[patch.shape_id].push_back(&patch - &patches.front());
+
+#ifdef DEBUG_OUTPUT_DIR
+ std::string store_dir = DEBUG_OUTPUT_DIR + "select_patches/";
+ prepare_dir(store_dir);
+#endif // DEBUG_OUTPUT_DIR
+
+ for (size_t shape_index = 0; shape_index < shapes.size(); shape_index++) {
+ const ExPolygon &shape = shapes[shape_index];
+ std::vector &used_shape_patches = used_shapes_patches[shape_index];
+ if (used_shape_patches.empty()) continue;
+ // is used all exist patches?
+ if (used_shapes_patches.size() == shapes_patches[shape_index].size()) continue;
+ if (used_shape_patches.size() == 1) {
+ uint32_t patch_index = used_shape_patches.front();
+ const SurfacePatch &patch = patches[patch_index];
+ if (is_over_whole_expoly(patch, shapes, cutAOIs, meshes)) continue;
+ }
+
+ // only shapes containing multiple patches
+ // or not full filled are back projected (hard processed)
+
+ // intersection of converted patches to 2d
+ ExPolygons fill;
+ fill.reserve(used_shape_patches.size());
+
+ // Heuristics to predict which patch to be used need average patch depth
+ Vec2d used_patches_depth(std::numeric_limits::max(), std::numeric_limits::min());
+ for (uint32_t patch_index : used_shape_patches) {
+ ExPolygon patch_area = to_expoly(patches[patch_index], projection, used_patches_depth);
+ //*/
+ ExPolygons patch_areas = offset_ex(patch_area, extend_delta);
+ fill.insert(fill.end(), patch_areas.begin(), patch_areas.end());
+ /*/
+ // without save extension
+ fill.push_back(patch_area);
+ //*/
+ }
+ fill = union_ex(fill);
+
+ // not cutted area of expolygon
+ ExPolygons rest = diff_ex(ExPolygons{shape}, fill, ApplySafetyOffset::Yes);
+#ifdef DEBUG_OUTPUT_DIR
+ BoundingBox shape_bb = get_extents(shape);
+ SVG svg(store_dir + "shape_" + std::to_string(shape_index) + ".svg", shape_bb);
+ svg.draw(fill, "darkgreen");
+ svg.draw(rest, "green");
+#endif // DEBUG_OUTPUT_DIR
+
+ // already filled by multiple patches
+ if (rest.empty()) continue;
+
+ // find patches overlaped rest area
+ struct PatchShape{
+ uint32_t patch_index;
+ ExPolygon shape;
+ ExPolygons intersection;
+ double depth_range_center_distance; // always positive
+ };
+ using PatchShapes = std::vector;
+ PatchShapes patch_shapes;
+
+ double used_patches_depth_center = (used_patches_depth[0] + used_patches_depth[1]) / 2;
+
+ // sort used_patches for faster search
+ std::sort(used_shape_patches.begin(), used_shape_patches.end());
+ for (uint32_t patch_index : shapes_patches[shape_index]) {
+ // check is patch already used
+ auto it = std::lower_bound(used_shape_patches.begin(), used_shape_patches.end(), patch_index);
+ if (it != used_shape_patches.end() && *it == patch_index) continue;
+
+ // Heuristics to predict which patch to be used need average patch depth
+ Vec2d patche_depth_range(std::numeric_limits::max(), std::numeric_limits::min());
+ ExPolygon patch_shape = to_expoly(patches[patch_index], projection, patche_depth_range);
+ double depth_center = (patche_depth_range[0] + patche_depth_range[1]) / 2;
+ double depth_range_center_distance = std::fabs(used_patches_depth_center - depth_center);
+
+ ExPolygons patch_intersection = intersection_ex(ExPolygons{patch_shape}, rest);
+ if (patch_intersection.empty()) continue;
+
+ patch_shapes.push_back({patch_index, patch_shape, patch_intersection, depth_range_center_distance});
+ }
+
+ // nothing to add
+ if (patch_shapes.empty()) continue;
+ // only one solution to add
+ if (patch_shapes.size() == 1) {
+ used_shape_patches.push_back(patch_shapes.front().patch_index);
+ continue;
+ }
+
+ // Idea: Get depth range of used patches and add patches in order by distance to used depth center
+ std::sort(patch_shapes.begin(), patch_shapes.end(), [](const PatchShape &a, const PatchShape &b)
+ { return a.depth_range_center_distance < b.depth_range_center_distance; });
+
+#ifdef DEBUG_OUTPUT_DIR
+ for (size_t i = patch_shapes.size(); i > 0; --i) {
+ const PatchShape &p = patch_shapes[i - 1];
+ int gray_level = (i * 200) / patch_shapes.size();
+ std::stringstream color;
+ color << "#" << std::hex << std::setfill('0') << std::setw(2) << gray_level << gray_level << gray_level;
+ svg.draw(p.shape, color.str());
+ Point text_pos = get_extents(p.shape).center().cast();
+ svg.draw_text(text_pos, std::to_string(i-1).c_str(), "orange", std::ceil(shape_bb.size().x() / 20 * 0.000001));
+ //svg.draw(p.intersection, color.str());
+ }
+#endif // DEBUG_OUTPUT_DIR
+
+ for (const PatchShape &patch : patch_shapes) {
+ // Check when exist some place to fill
+ ExPolygons patch_intersection = intersection_ex(patch.intersection, rest);
+ if (patch_intersection.empty()) continue;
+
+ // Extend for sure
+ ExPolygons intersection = offset_ex(patch.intersection, extend_delta);
+ rest = diff_ex(rest, intersection, ApplySafetyOffset::Yes);
+
+ used_shape_patches.push_back(patch.patch_index);
+ if (rest.empty()) break;
+ }
+
+ // QUESTION: How to select which patch to use? How to sort them?
+ // Now is used back projection distance from used patches
+ //
+ // Idealy by outline depth: (need ray cast into patches)
+ // how to calc wanted depth - idealy by depth of outline help to overlap
+ // how to calc patch depth - depth in place of outline position
+ // Which outline to use between
+
+ }
+
+ std::vector result(patches.size(), {false});
+ for (const std::vector &patches: used_shapes_patches)
+ for (uint32_t patch_index : patches) {
+ assert(patch_index < result.size());
+ // check only onece insertation of patch
+ assert(!result[patch_index]);
+ result[patch_index] = true;
+ }
+ return result;
+}
+
+priv::Loops priv::create_loops(const std::vector &outlines, const CutMesh& mesh)
+{
+ Loops loops;
+ Loops unclosed;
+ for (HI hi : outlines) {
+ VI vi_s = mesh.source(hi);
+ VI vi_t = mesh.target(hi);
+ Loop *loop_move = nullptr;
+ Loop *loop_connect = nullptr;
+ for (std::vector &cut : unclosed) {
+ if (cut.back() != vi_s) continue;
+ if (cut.front() == vi_t) {
+ // cut closing
+ loop_move = &cut;
+ } else {
+ loop_connect = &cut;
+ }
+ break;
+ }
+ if (loop_move != nullptr) {
+ // index of closed cut
+ size_t index = loop_move - &unclosed.front();
+ // move cut to result
+ loops.emplace_back(std::move(*loop_move));
+ // remove it from unclosed cut
+ unclosed.erase(unclosed.begin() + index);
+ } else if (loop_connect != nullptr) {
+ // try find tail to connect cut
+ Loop *loop_tail = nullptr;
+ for (Loop &cut : unclosed) {
+ if (cut.front() != vi_t) continue;
+ loop_tail = &cut;
+ break;
+ }
+ if (loop_tail != nullptr) {
+ // index of tail
+ size_t index = loop_tail - &unclosed.front();
+ // move to connect vector
+ loop_connect->insert(loop_connect->end(),
+ make_move_iterator(loop_tail->begin()),
+ make_move_iterator(loop_tail->end()));
+ // remove tail from unclosed cut
+ unclosed.erase(unclosed.begin() + index);
+ } else {
+ loop_connect->push_back(vi_t);
+ }
+ } else { // not found
+ bool create_cut = true;
+ // try to insert to front of cut
+ for (Loop &cut : unclosed) {
+ if (cut.front() != vi_t) continue;
+ cut.insert(cut.begin(), vi_s);
+ create_cut = false;
+ break;
+ }
+ if (create_cut)
+ unclosed.emplace_back(std::vector{vi_s, vi_t});
+ }
+ }
+ assert(unclosed.empty());
+ return loops;
+}
+
+Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range)
+{
+ assert(!patch.loops.empty());
+ if (patch.loops.empty()) return {};
+
+ // NOTE: this method is working only when patch did not contain outward faces
+ Polygons polys;
+ polys.reserve(patch.loops.size());
+ // project conture into 2d space to fillconvert outlines to
+
+ size_t count = 0;
+ for (const Loop &l : patch.loops) count += l.size();
+ std::vector depths;
+ depths.reserve(count);
+
+ Points pts;
+ for (const Loop &l : patch.loops) {
+ pts.clear();
+ pts.reserve(l.size());
+ for (VI vi : l) {
+ const P3 &p3 = patch.mesh.point(vi);
+ Vec3d p(p3.x(), p3.y(), p3.z());
+ double depth;
+ std::optional p2_opt = projection.unproject(p, &depth);
+ if (depth_range[0] > depth) depth_range[0] = depth; // min
+ if (depth_range[1] < depth) depth_range[1] = depth; // max
+ // Check when appear that skip is enough for poit which can't be unprojected
+ // - it could break contour
+ assert(p2_opt.has_value());
+ if (!p2_opt.has_value()) continue;
+
+ pts.push_back(p2_opt->cast());
+ depths.push_back(static_cast(depth));
+ }
+ // minimal is triangle
+ assert(pts.size() >= 3);
+ if (pts.size() < 3) continue;
+
+ polys.emplace_back(pts);
+ }
+
+ assert(!polys.empty());
+ return polys;
+}
+
+ExPolygon priv::to_expoly(const SurfacePatch &patch, const Project &projection, Vec2d &depth_range)
+{
+ Polygons polys = unproject_loops(patch, projection, depth_range);
+ // should not be used when no opposit triangle are counted so should not create overlaps
+ ClipperLib::PolyFillType fill_type = ClipperLib::PolyFillType::pftEvenOdd;
+ ExPolygons expolys = Slic3r::union_ex(polys, fill_type);
+ assert(expolys.size() == 1);
+ if (expolys.empty()) return {};
+ return expolys.front();
+}
+
+SurfaceCut priv::patch2cut(SurfacePatch &patch)
+{
+ CutMesh &mesh = patch.mesh;
+
+ std::string convert_map_name = "v:convert";
+ CutMesh::Property_map convert_map =
+ mesh.add_property_map(convert_map_name).first;
+
+ size_t indices_size = mesh.faces().size();
+ size_t vertices_size = mesh.vertices().size();
+
+ SurfaceCut sc;
+ sc.indices.reserve(indices_size);
+ sc.vertices.reserve(vertices_size);
+ for (VI vi : mesh.vertices()) {
+ // vi order is is not sorted
+ // assert(vi.idx() == sc.vertices.size());
+ // vi is not continous
+ // assert(vi.idx() < vertices_size);
+ convert_map[vi] = sc.vertices.size();
+ const P3 &p = mesh.point(vi);
+ sc.vertices.emplace_back(p.x(), p.y(), p.z());
+ }
+
+ for (FI fi : mesh.faces()) {
+ HI hi = mesh.halfedge(fi);
+ assert(mesh.next(hi).is_valid());
+ assert(mesh.next(mesh.next(hi)).is_valid());
+ // Is fi triangle?
+ assert(mesh.next(mesh.next(mesh.next(hi))) == hi);
+
+ // triangle indicies
+ Vec3i ti;
+ size_t i = 0;
+ for (VI vi : { mesh.source(hi),
+ mesh.target(hi),
+ mesh.target(mesh.next(hi))})
+ ti[i++] = convert_map[vi];
+ sc.indices.push_back(ti);
+ }
+
+ sc.contours.reserve(patch.loops.size());
+ for (const Loop &loop : patch.loops) {
+ sc.contours.push_back({});
+ std::vector &contour = sc.contours.back();
+ contour.reserve(loop.size());
+ for (VI vi : loop) contour.push_back(convert_map[vi]);
+ }
+
+ // Not neccessary, clean and free memory
+ mesh.remove_property_map(convert_map);
+ return sc;
+}
+
+void priv::append(SurfaceCut &sc, SurfaceCut &&sc_add)
+{
+ if (sc.empty()) {
+ sc = std::move(sc_add);
+ return;
+ }
+
+ if (!sc_add.contours.empty()) {
+ SurfaceCut::Index offset = static_cast(
+ sc.vertices.size());
+ size_t require = sc.contours.size() + sc_add.contours.size();
+ if (sc.contours.capacity() < require) sc.contours.reserve(require);
+ for (std::vector &cut : sc_add.contours)
+ for (SurfaceCut::Index &i : cut) i += offset;
+ Slic3r::append(sc.contours, std::move(sc_add.contours));
+ }
+ its_merge(sc, std::move(sc_add));
+}
+
+SurfaceCut priv::merge_patches(SurfacePatches &patches, const std::vector& mask)
+{
+ SurfaceCut result;
+ for (SurfacePatch &patch : patches) {
+ size_t index = &patch - &patches.front();
+ if (!mask[index]) continue;
+ append(result, patch2cut(patch));
+ }
+ return result;
+}
+
+#ifdef DEBUG_OUTPUT_DIR
+void priv::prepare_dir(const std::string &dir){
+ namespace fs = std::filesystem;
+ if (fs::exists(dir)) {
+ for (auto &path : fs::directory_iterator(dir)) fs::remove_all(path);
+ } else {
+ fs::create_directories(dir);
+ }
+}
+
+namespace priv{
+int reduction_order = 0;
+int filled_order = 0;
+int constrained_order = 0;
+int diff_patch_order = 0;
+
+} // namespace priv
+
+void priv::initialize_store(const std::string& dir)
+{
+ // clear previous output
+ prepare_dir(dir);
+ reduction_order = 0;
+ filled_order = 0;
+ constrained_order = 0;
+ diff_patch_order = 0;
+}
+
+void priv::store(const Vec3f &vertex,
+ const Vec3f &normal,
+ const std::string &file,
+ float size)
+{
+ int flatten = 20;
+ size_t min_i = 0;
+ for (size_t i = 1; i < 3; i++)
+ if (normal[min_i] > normal[i]) min_i = i;
+ Vec3f up_ = Vec3f::Zero();
+ up_[min_i] = 1.f;
+ Vec3f side = normal.cross(up_).normalized() * size;
+ Vec3f up = side.cross(normal).normalized() * size;
+
+ indexed_triangle_set its;
+ its.vertices.reserve(flatten + 1);
+ its.indices.reserve(flatten);
+
+ its.vertices.push_back(vertex);
+ its.vertices.push_back(vertex + up);
+ size_t max_i = static_cast(flatten);
+ for (size_t i = 1; i < max_i; i++) {
+ float angle = i * 2 * M_PI / flatten;
+ Vec3f v = vertex + sin(angle) * side + cos(angle) * up;
+ its.vertices.push_back(v);
+ its.indices.emplace_back(0, i, i + 1);
+ }
+ its.indices.emplace_back(0, flatten, 1);
+ its_write_obj(its, file.c_str());
+}
+
+void priv::store(const CutMesh &mesh, const FaceTypeMap &face_type_map, const std::string& dir, bool is_filled)
+{
+ std::string off_file;
+ if (is_filled) {
+ if (filled_order == 0) prepare_dir(dir);
+ off_file = dir + "model" + std::to_string(filled_order++) + ".off";
+ }else{
+ if (constrained_order == 0) prepare_dir(dir);
+ off_file = dir + "model" + std::to_string(constrained_order++) + ".off";
+ }
+
+ CutMesh &mesh_ = const_cast(mesh);
+ auto face_colors = mesh_.add_property_map("f:color").first;
+ for (FI fi : mesh.faces()) {
+ auto &color = face_colors[fi];
+ switch (face_type_map[fi]) {
+ case FaceType::inside: color = CGAL::Color{100, 250, 100}; break; // light green
+ case FaceType::inside_processed: color = CGAL::Color{170, 0, 0}; break; // dark red
+ case FaceType::outside: color = CGAL::Color{100, 0, 100}; break; // purple
+ case FaceType::not_constrained: color = CGAL::Color{127, 127, 127}; break; // gray
+ default: color = CGAL::Color{0, 0, 255}; // blue
+ }
+ }
+ CGAL::IO::write_OFF(off_file, mesh);
+ mesh_.remove_property_map(face_colors);
+}
+
+void priv::store(const ExPolygons &shapes, const std::string &svg_file) {
+ SVG svg(svg_file);
+ svg.draw(shapes);
+}
+
+void priv::store(const CutMesh &mesh, const ReductionMap &reduction_map, const std::string& dir)
+{
+ if (reduction_order == 0) prepare_dir(dir);
+ std::string off_file = dir + "model" + std::to_string(reduction_order++) + ".off";
+
+ CutMesh &mesh_ = const_cast(mesh);
+ auto vertex_colors = mesh_.add_property_map("v:color").first;
+ // initialize to gray color
+ for (VI vi: mesh.vertices())
+ vertex_colors[vi] = CGAL::Color{127, 127, 127};
+
+ for (VI reduction_from : mesh.vertices()) {
+ VI reduction_to = reduction_map[reduction_from];
+ if (!reduction_to.is_valid()) continue;
+ vertex_colors[reduction_from] = CGAL::Color{255, 0, 0};
+ vertex_colors[reduction_to] = CGAL::Color{0, 0, 255};
+ }
+
+ CGAL::IO::write_OFF(off_file, mesh);
+ mesh_.remove_property_map(vertex_colors);
+}
+
+namespace priv {
+indexed_triangle_set create_indexed_triangle_set(const std::vector &faces,
+ const CutMesh &mesh);
+} // namespace priv
+
+indexed_triangle_set priv::create_indexed_triangle_set(
+ const std::vector &faces, const CutMesh &mesh)
+{
+ std::vector vertices;
+ vertices.reserve(faces.size() * 2);
+
+ indexed_triangle_set its;
+ its.indices.reserve(faces.size());
+ for (FI fi : faces) {
+ HI hi = mesh.halfedge(fi);
+ HI hi_end = hi;
+
+ int ti = 0;
+ Vec3i t;
+
+ do {
+ VI vi = mesh.source(hi);
+ auto res = std::find(vertices.begin(), vertices.end(), vi);
+ t[ti++] = res - vertices.begin();
+ if (res == vertices.end()) vertices.push_back(vi);
+ hi = mesh.next(hi);
+ } while (hi != hi_end);
+
+ its.indices.push_back(t);
+ }
+
+ its.vertices.reserve(vertices.size());
+ for (VI vi : vertices) {
+ const auto &p = mesh.point(vi);
+ its.vertices.emplace_back(p.x(), p.y(), p.z());
+ }
+ return its;
+}
+
+void priv::store(const CutAOIs &aois, const CutMesh &mesh, const std::string &dir) {
+ auto create_outline_its =
+ [&mesh](const std::vector &outlines) -> indexed_triangle_set {
+ static const float line_width = 0.1f;
+ indexed_triangle_set its;
+ its.indices.reserve(2*outlines.size());
+ its.vertices.reserve(outlines.size()*4);
+ for (HI hi : outlines) {
+ //FI fi = mesh.face(hi);
+ VI vi_a = mesh.source(hi);
+ VI vi_b = mesh.target(hi);
+ VI vi_c = mesh.target(mesh.next(hi));
+ P3 p3_a = mesh.point(vi_a);
+ P3 p3_b = mesh.point(vi_b);
+ P3 p3_c = mesh.point(vi_c);
+
+ Vec3f a(p3_a.x(), p3_a.y(), p3_a.z());
+ Vec3f b(p3_b.x(), p3_b.y(), p3_b.z());
+ Vec3f c(p3_c.x(), p3_c.y(), p3_c.z());
+
+ Vec3f v1 = b - a; // from a to b
+ v1.normalize();
+ Vec3f v2 = c - a; // from a to c
+ v2.normalize();
+ Vec3f norm = v1.cross(v2);
+ norm.normalize();
+ Vec3f perp_to_edge = norm.cross(v1);
+ perp_to_edge.normalize();
+ Vec3f dir = -perp_to_edge * line_width;
+
+ size_t ai = its.vertices.size();
+ its.vertices.push_back(a);
+ size_t bi = its.vertices.size();
+ its.vertices.push_back(b);
+ size_t ai2 = its.vertices.size();
+ its.vertices.push_back(a + dir);
+ size_t bi2 = its.vertices.size();
+ its.vertices.push_back(b + dir);
+
+ its.indices.push_back(Vec3i(ai, ai2, bi));
+ its.indices.push_back(Vec3i(ai2, bi2, bi));
+ }
+ return its;
+ };
+
+ prepare_dir(dir);
+ for (const auto &aoi : aois) {
+ size_t index = &aoi - &aois.front();
+ std::string file = dir + "aoi" + std::to_string(index) + ".obj";
+ indexed_triangle_set its = create_indexed_triangle_set(aoi.first, mesh);
+ its_write_obj(its, file.c_str());
+
+ // exist some outline?
+ if (aoi.second.empty()) continue;
+ std::string file_outline = dir + "outline" + std::to_string(index) + ".obj";
+ indexed_triangle_set outline = create_outline_its(aoi.second);
+ its_write_obj(outline, file_outline.c_str());
+ }
+}
+
+void priv::store(const SurfacePatches &patches, const std::string &dir) {
+ prepare_dir(dir);
+ for (const priv::SurfacePatch &patch : patches) {
+ size_t index = &patch - &patches.front();
+ if (patch.mesh.faces().empty()) continue;
+ CGAL::IO::write_OFF(dir + "patch" + std::to_string(index) + ".off", patch.mesh);
+ }
+}
+//
+//void priv::store(const ProjectionDistances &pds,
+// const VCutAOIs &aois,
+// const CutMeshes &meshes,
+// const std::string &file,
+// float width)
+//{
+// // create rectangle for each half edge from projection distances
+// indexed_triangle_set its;
+// its.vertices.reserve(4 * pds.size());
+// its.indices.reserve(2 * pds.size());
+// for (const ProjectionDistance &pd : pds) {
+// if (pd.aoi_index == std::numeric_limits::max()) continue;
+// HI hi = aois[pd.model_index][pd.aoi_index].second[pd.hi_index];
+// const CutMesh &mesh = meshes[pd.model_index];
+// VI vi1 = mesh.source(hi);
+// VI vi2 = mesh.target(hi);
+// VI vi3 = mesh.target(mesh.next(hi));
+// const P3 &p1 = mesh.point(vi1);
+// const P3 &p2 = mesh.point(vi2);
+// const P3 &p3 = mesh.point(vi3);
+// Vec3f v1(p1.x(), p1.y(), p1.z());
+// Vec3f v2(p2.x(), p2.y(), p2.z());
+// Vec3f v3(p3.x(), p3.y(), p3.z());
+//
+// Vec3f v12 = v2 - v1;
+// v12.normalize();
+// Vec3f v13 = v3 - v1;
+// v13.normalize();
+// Vec3f n = v12.cross(v13);
+// n.normalize();
+// Vec3f side = n.cross(v12);
+// side.normalize();
+// side *= -width;
+//
+// uint32_t i = its.vertices.size();
+// its.vertices.push_back(v1);
+// its.vertices.push_back(v1+side);
+// its.vertices.push_back(v2);
+// its.vertices.push_back(v2+side);
+//
+// its.indices.emplace_back(i, i + 1, i + 2);
+// its.indices.emplace_back(i + 2, i + 1, i + 3);
+// }
+// its_write_obj(its, file.c_str());
+//}
+
+void priv::store(const ExPolygons &shapes, const std::vector &mask, const Connections &connections, const std::string &file_svg)
+{
+ auto bb = get_extents(shapes);
+ int width = get_extents(shapes.front()).size().x() / 70;
+
+ SVG svg(file_svg, bb);
+ svg.draw(shapes);
+
+ ExPolygonsIndices s2i(shapes);
+ auto get_point = [&shapes, &s2i](size_t i)->Point {
+ auto id = s2i.cvt(i);
+ const ExPolygon &s = shapes[id.expolygons_index];
+ const Polygon &p = (id.polygon_index == 0) ?
+ s.contour :
+ s.holes[id.polygon_index - 1];
+ return p[id.point_index];
+ };
+
+ bool is_first = true;
+ for (const Connection &c : connections) {
+ if (is_first) {
+ is_first = false;
+ Point p = get_point(c.first);
+ svg.draw(p, "purple", 4 * width);
+ continue;
+ }
+ Point p1 = get_point(c.first);
+ Point p2 = get_point(c.second);
+ svg.draw(Line(p1, p2), "red", width);
+ }
+
+ for (size_t i = 0; i < s2i.get_count(); i++) {
+ Point p = get_point(i);
+ svg.draw(p, "black", 2*width);
+ if (!mask[i])
+ svg.draw(p, "white", width);
+ }
+ svg.Close();
+}
+
+namespace priv {
+///
+/// Create model consist of rectangles for each contour edge
+///
+///
+///
+///
+indexed_triangle_set create_contour_its(const indexed_triangle_set& its, const std::vector &contour);
+
+///
+/// Getter on triangle tip (third vertex of face)
+///
+/// First vertex index
+/// Second vertex index
+/// Source model
+/// Tip Vertex index
+unsigned int get_triangle_tip(unsigned int vi1,
+ unsigned int vi2,
+ const indexed_triangle_set &its);
+}
+
+
+unsigned int priv::get_triangle_tip(unsigned int vi1,
+ unsigned int vi2,
+ const indexed_triangle_set &its)
+{
+ assert(vi1 < its.vertices.size());
+ assert(vi2 < its.vertices.size());
+ for (const auto &t : its.indices) {
+ unsigned int tvi = std::numeric_limits::max();
+ for (const auto &vi : t) {
+ unsigned int vi_ = static_cast(vi);
+ if (vi_ == vi1) continue;
+ if (vi_ == vi2) continue;
+ if (tvi == std::numeric_limits::max()) {
+ tvi = vi_;
+ } else {
+ tvi = std::numeric_limits