MarlinMesh.scad 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /**************************************\
  2. * *
  3. * OpenSCAD Mesh Display *
  4. * by Thinkyhead - April 2017 *
  5. * *
  6. * Copy the grid output from Marlin, *
  7. * paste below as shown, and use *
  8. * OpenSCAD to see a visualization *
  9. * of your mesh. *
  10. * *
  11. \**************************************/
  12. $t = 0.15; // comment out during animation!
  13. X = 0; Y = 1;
  14. L = 0; R = 1; F = 2; B = 3;
  15. //
  16. // Sample Mesh - Replace with your own
  17. //
  18. measured_z = [
  19. [ -1.20, -1.13, -1.09, -1.03, -1.19 ],
  20. [ -1.16, -1.25, -1.27, -1.25, -1.08 ],
  21. [ -1.13, -1.26, -1.39, -1.31, -1.18 ],
  22. [ -1.09, -1.20, -1.26, -1.21, -1.18 ],
  23. [ -1.13, -0.99, -1.03, -1.06, -1.32 ]
  24. ];
  25. //
  26. // An offset to add to all points in the mesh
  27. //
  28. zadjust = 0;
  29. //
  30. // Mesh characteristics
  31. //
  32. bed_size = [ 200, 200 ];
  33. mesh_inset = [ 10, 10, 10, 10 ]; // L, F, R, B
  34. mesh_bounds = [
  35. [ mesh_inset[L], mesh_inset[F] ],
  36. [ bed_size[X] - mesh_inset[R], bed_size[Y] - mesh_inset[B] ]
  37. ];
  38. mesh_size = mesh_bounds[1] - mesh_bounds[0];
  39. // NOTE: Marlin meshes already subtract the probe offset
  40. NAN = 0; // Z to use for un-measured points
  41. //
  42. // Geometry
  43. //
  44. max_z_scale = 100; // Scale at Time 0.5
  45. min_z_scale = 10; // Scale at Time 0.0 and 1.0
  46. thickness = 0.5; // thickness of the mesh triangles
  47. tesselation = 1; // levels of tesselation from 0-2
  48. alternation = 2; // direction change modulus (try it)
  49. //
  50. // Appearance
  51. //
  52. show_plane = true;
  53. show_labels = true;
  54. show_coords = true;
  55. arrow_length = 5;
  56. label_font_lg = "Arial";
  57. label_font_sm = "Arial";
  58. mesh_color = [1,1,1,0.5];
  59. plane_color = [0.4,0.6,0.9,0.6];
  60. //================================================ Derive useful values
  61. big_z = max_2D(measured_z,0);
  62. lil_z = min_2D(measured_z,0);
  63. mean_value = (big_z + lil_z) / 2.0;
  64. mesh_points_y = len(measured_z);
  65. mesh_points_x = len(measured_z[0]);
  66. xspace = mesh_size[X] / (mesh_points_x - 1);
  67. yspace = mesh_size[Y] / (mesh_points_y - 1);
  68. // At $t=0 and $t=1 scale will be 100%
  69. z_scale_factor = min_z_scale + (($t > 0.5) ? 1.0 - $t : $t) * (max_z_scale - min_z_scale) * 2;
  70. //
  71. // Min and max recursive functions for 1D and 2D arrays
  72. // Return the smallest or largest value in the array
  73. //
  74. function some_1D(b,i) = (i<len(b)-1) ? (b[i] && some_1D(b,i+1)) : b[i] != 0;
  75. function some_2D(a,j) = (j<len(a)-1) ? some_2D(a,j+1) : some_1D(a[j], 0);
  76. function min_1D(b,i) = (i<len(b)-1) ? min(b[i], min_1D(b,i+1)) : b[i];
  77. function min_2D(a,j) = (j<len(a)-1) ? min_2D(a,j+1) : min_1D(a[j], 0);
  78. function max_1D(b,i) = (i<len(b)-1) ? max(b[i], max_1D(b,i+1)) : b[i];
  79. function max_2D(a,j) = (j<len(a)-1) ? max_2D(a,j+1) : max_1D(a[j], 0);
  80. //
  81. // Get the corner probe points of a grid square.
  82. //
  83. // Input : x,y grid indexes
  84. // Output : An array of the 4 corner points
  85. //
  86. function grid_square(x,y) = [
  87. [x * xspace, y * yspace, z_scale_factor * (measured_z[y][x] - mean_value)],
  88. [x * xspace, (y+1) * yspace, z_scale_factor * (measured_z[y+1][x] - mean_value)],
  89. [(x+1) * xspace, (y+1) * yspace, z_scale_factor * (measured_z[y+1][x+1] - mean_value)],
  90. [(x+1) * xspace, y * yspace, z_scale_factor * (measured_z[y][x+1] - mean_value)]
  91. ];
  92. // The corner point of a grid square with Z centered on the mean
  93. function pos(x,y,z) = [x * xspace, y * yspace, z_scale_factor * (z - mean_value)];
  94. //
  95. // Draw the point markers and labels
  96. //
  97. module point_markers(show_home=true) {
  98. // Mark the home position 0,0
  99. if (show_home)
  100. translate([1,1]) color([0,0,0,0.25])
  101. cylinder(r=1, h=z_scale_factor, center=true);
  102. for (x=[0:mesh_points_x-1], y=[0:mesh_points_y-1]) {
  103. z = measured_z[y][x] - zadjust;
  104. down = z < mean_value;
  105. xyz = pos(x, y, z);
  106. translate([ xyz[0], xyz[1] ]) {
  107. // Show the XY as well as the Z!
  108. if (show_coords) {
  109. color("black")
  110. translate([0,0,0.5]) {
  111. $fn=8;
  112. rotate([0,0]) {
  113. posx = floor(mesh_bounds[0][X] + x * xspace);
  114. posy = floor(mesh_bounds[0][Y] + y * yspace);
  115. text(str(posx, ",", posy), 2, label_font_sm, halign="center", valign="center");
  116. }
  117. }
  118. }
  119. translate([ 0, 0, xyz[2] ]) {
  120. // Label each point with the Z
  121. v = z - mean_value;
  122. if (show_labels) {
  123. color(abs(v) < 0.1 ? [0,0.5,0] : [0.25,0,0])
  124. translate([0,0,down?-10:10]) {
  125. $fn=8;
  126. rotate([90,0])
  127. text(str(z), 6, label_font_lg, halign="center", valign="center");
  128. if (v)
  129. translate([0,0,down?-6:6]) rotate([90,0])
  130. text(str(down || !v ? "" : "+", v), 3, label_font_sm, halign="center", valign="center");
  131. }
  132. }
  133. // Show an arrow pointing up or down
  134. if (v) {
  135. rotate([0, down ? 180 : 0]) translate([0,0,-1])
  136. cylinder(
  137. r1=0.5,
  138. r2=0.1,
  139. h=arrow_length, $fn=12, center=1
  140. );
  141. }
  142. else
  143. color([1,0,1,0.4]) sphere(r=1.0, $fn=20, center=1);
  144. }
  145. }
  146. }
  147. }
  148. //
  149. // Split a square on the diagonal into
  150. // two triangles and render them.
  151. //
  152. // s : a square
  153. // alt : a flag to split on the other diagonal
  154. //
  155. module tesselated_square(s, alt=false) {
  156. add = [0,0,thickness];
  157. p1 = [
  158. s[0], s[1], s[2], s[3],
  159. s[0]+add, s[1]+add, s[2]+add, s[3]+add
  160. ];
  161. f1 = alt
  162. ? [ [0,1,3], [4,5,1,0], [4,7,5], [5,7,3,1], [7,4,0,3] ]
  163. : [ [0,1,2], [4,5,1,0], [4,6,5], [5,6,2,1], [6,4,0,2] ];
  164. f2 = alt
  165. ? [ [1,2,3], [5,6,2,1], [5,6,7], [6,7,3,2], [7,5,1,3] ]
  166. : [ [0,2,3], [4,6,2,0], [4,7,6], [6,7,3,2], [7,4,0,3] ];
  167. // Use the other diagonal
  168. polyhedron(points=p1, faces=f1);
  169. polyhedron(points=p1, faces=f2);
  170. }
  171. /**
  172. * The simplest mesh display
  173. */
  174. module simple_mesh(show_plane=show_plane) {
  175. if (show_plane) color(plane_color) cube([mesh_size[X], mesh_size[Y], thickness]);
  176. color(mesh_color)
  177. for (x=[0:mesh_points_x-2], y=[0:mesh_points_y-2])
  178. tesselated_square(grid_square(x, y));
  179. }
  180. /**
  181. * Subdivide the mesh into smaller squares.
  182. */
  183. module bilinear_mesh(show_plane=show_plane,tesselation=tesselation) {
  184. if (show_plane) color(plane_color) translate([-5,-5]) cube([mesh_size[X]+10, mesh_size[Y]+10, thickness]);
  185. if (some_2D(measured_z, 0)) {
  186. tesselation = tesselation % 4;
  187. color(mesh_color)
  188. for (x=[0:mesh_points_x-2], y=[0:mesh_points_y-2]) {
  189. square = grid_square(x, y);
  190. if (tesselation < 1) {
  191. tesselated_square(square,(x%alternation)-(y%alternation));
  192. }
  193. else {
  194. subdiv_4 = subdivided_square(square);
  195. if (tesselation < 2) {
  196. for (i=[0:3]) tesselated_square(subdiv_4[i],i%alternation);
  197. }
  198. else {
  199. for (i=[0:3]) {
  200. subdiv_16 = subdivided_square(subdiv_4[i]);
  201. if (tesselation < 3) {
  202. for (j=[0:3]) tesselated_square(subdiv_16[j],j%alternation);
  203. }
  204. else {
  205. for (j=[0:3]) {
  206. subdiv_64 = subdivided_square(subdiv_16[j]);
  207. if (tesselation < 4) {
  208. for (k=[0:3]) tesselated_square(subdiv_64[k]);
  209. }
  210. }
  211. }
  212. }
  213. }
  214. }
  215. }
  216. }
  217. }
  218. //
  219. // Subdivision helpers
  220. //
  221. function ctrz(a) = (a[0][2]+a[1][2]+a[3][2]+a[2][2])/4;
  222. function avgx(a,i) = (a[i][0]+a[(i+1)%4][0])/2;
  223. function avgy(a,i) = (a[i][1]+a[(i+1)%4][1])/2;
  224. function avgz(a,i) = (a[i][2]+a[(i+1)%4][2])/2;
  225. //
  226. // Convert one square into 4, applying bilinear averaging
  227. //
  228. // Input : 1 square (4 points)
  229. // Output : An array of 4 squares
  230. //
  231. function subdivided_square(a) = [
  232. [ // SW square
  233. a[0], // SW
  234. [a[0][0],avgy(a,0),avgz(a,0)], // CW
  235. [avgx(a,1),avgy(a,0),ctrz(a)], // CC
  236. [avgx(a,1),a[0][1],avgz(a,3)] // SC
  237. ],
  238. [ // NW square
  239. [a[0][0],avgy(a,0),avgz(a,0)], // CW
  240. a[1], // NW
  241. [avgx(a,1),a[1][1],avgz(a,1)], // NC
  242. [avgx(a,1),avgy(a,0),ctrz(a)] // CC
  243. ],
  244. [ // NE square
  245. [avgx(a,1),avgy(a,0),ctrz(a)], // CC
  246. [avgx(a,1),a[1][1],avgz(a,1)], // NC
  247. a[2], // NE
  248. [a[2][0],avgy(a,0),avgz(a,2)] // CE
  249. ],
  250. [ // SE square
  251. [avgx(a,1),a[0][1],avgz(a,3)], // SC
  252. [avgx(a,1),avgy(a,0),ctrz(a)], // CC
  253. [a[2][0],avgy(a,0),avgz(a,2)], // CE
  254. a[3] // SE
  255. ]
  256. ];
  257. //================================================ Run the plan
  258. translate([-mesh_size[X] / 2, -mesh_size[Y] / 2]) {
  259. $fn = 12;
  260. point_markers();
  261. bilinear_mesh();
  262. }