Guest User

OpenSCAD script for subdivision vase

a guest
Sep 26th, 2025
164
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.04 KB | Source Code | 0 0
  1. // Subdivision Surface Matrix.scad
  2. //
  3. // Version 1, September 18, 2025
  4. // By: Stone Age Sculptor
  5. // License: CC0
  6. //
  7. // Version 2, September 26, 2025
  8. // By: Stone Age Sculptor
  9. // License: CC0
  10. // Changes:
  11. // Turned the surface into a vase.
  12. // The point cloud is used for a polyhedron().
  13. // These are the first tests.
  14. // The script is not finished.
  15. //
  16.  
  17. include <StoneAgeLib/StoneAgeLib.scad>
  18.  
  19. $fn = $preview ? 5 : 50;
  20. thickness = 0.5;
  21. divisions = $preview ? 3 : 5;
  22. vase_mode = true; // true for the polyhedron vase
  23. epsilon = 0.001;
  24.  
  25. method = vase_mode ? "1" : "1path";
  26.  
  27. // Example that looks like a cloth.
  28. matrix1 =
  29. [
  30. [ [0,0,0], [10,0,-1], [20,0,+5], [30,0,+0], [40,0,5], ],
  31. [ [0,10,0], [10,10,-10],[20,10,0], [30,10,10], [40,10,-5], ],
  32. [ [0,20,10], [10,20,10], [20,20,10],[30,20,0], [40,20,5], ],
  33. [ [0,30,-10],[10,30,-10],[20,30,20],[30,30,-10],[40,30,-5], ],
  34. ];
  35.  
  36. // Example with twist and turn.
  37. matrix2 =
  38. [
  39. [ [-10,12,15], [-20,5,-5], [-20,-5,-5], [-5,5,-15], ],
  40. [ [30,5,0], [30,2,0],[30,-2,0], [30,-5,0], ],
  41. [ [40,0,5], [40,0,2],[40,0,-2], [40,0,-5], ],
  42. [ [50,-5,0], [50,-2,0],[50,2,0], [50,5,0], ],
  43. [ [110,-20,0], [100,-10,10], [100,10,10],[100,50,0], ],
  44. [ [100,-10,50],[100,-2,50],[100,2,50],[100,10,50], ],
  45. [ [80,-50,20],[60,-32,30],[60,-25,30],[80,-10,20], ],
  46. ];
  47.  
  48. // Example with a vase.
  49. // Each row is not a path, but a closed shape.
  50. // A row must be counter-clockwise.
  51. matrix3 =
  52. [
  53. [ [50,50,0], [-50,50,0], [-50,-50,0], [50,-30,0], ],
  54. [ [50,30,30], [-50,50,10], [-50,-40,30], [50,-60,30], ],
  55. [ [50,50,60], [-50,70,50], [-50,-50,60], [50,-60,50], ],
  56. [ [30,20,90], [-40,20,90], [-16,-26,84], [30,-40,100], ],
  57. [ [30,20,110], [-10,20,150], [-10,-40,150], [30,-40,110], ],
  58. [ [50,20,110], [50,20,150], [50,-20,150], [50,-20,110], ],
  59. [ [60,20,100], [100,20,110], [100,-20,110], [60,-20,100], ],
  60. [ [60,10,50], [100,10,50], [100,-30,100], [60,-30,90], ],
  61. [ [60,-80,50], [90,-80,50], [90,-60,100], [60,-60,90], ],
  62. [ [60,-80,200], [80,-80,200], [80,-60,200], [60,-60,200], ],
  63. ];
  64.  
  65. // Select an example
  66. matrix = matrix3;
  67.  
  68. // Get the number of rows and columns of the matrix.
  69. columns = len(matrix[0]);
  70. rows = len(matrix);
  71.  
  72. // Show the edges in brown.
  73. tube_width = 0.4;
  74. if($preview)
  75. {
  76. color("SaddleBrown",0.5)
  77. {
  78. // tubes for rows
  79. if(!vase_mode)
  80. {
  81. for(r=[0:rows-1],c=[0:columns-2])
  82. hull()
  83. for(inc=[0,1])
  84. translate(matrix[r][c+inc])
  85. sphere(d=tube_width);
  86. }
  87. else
  88. {
  89. // For vase mode, make the rows closed loops.
  90. for(r=[0:rows-1],c=[0:columns-1])
  91. hull()
  92. for(inc=[0,1])
  93. {
  94. c3 = (c+inc) % columns;
  95. translate(matrix[r][c3])
  96. sphere(d=tube_width);
  97. }
  98. }
  99.  
  100. // tubes for columns
  101. for(c=[0:columns-1],r=[0:rows-2])
  102. hull()
  103. for(inc=[0,1])
  104. translate(matrix[r+inc][c])
  105. sphere(d=tube_width);
  106. }
  107. }
  108.  
  109.  
  110. // Show control points in red.
  111. control_point_size = 2.4;
  112. if($preview)
  113. {
  114. color("Red")
  115. {
  116. for(row=[0:rows-1],column=[0:columns-1])
  117. translate(matrix[row][column])
  118. sphere(d=control_point_size);
  119. }
  120. }
  121.  
  122. // Create two lists, for subdivided rows and subdivided columns.
  123. subrows =
  124. [
  125. // Pick a single row, and subdivide that.
  126. for(r=[0:rows-1])
  127. Subdivision(matrix[r],divisions=divisions,method=method),
  128. ];
  129.  
  130. subcolumns =
  131. [
  132. // Gather the data of a column, and subdivide that.
  133. for(c=[0:columns-1])
  134. let(list = [for(i=[0:rows-1]) matrix[i][c]])
  135. Subdivision(list,divisions=divisions,method="1path"),
  136. ];
  137.  
  138. // Weave the subdivision between the new points.
  139. weaverows =
  140. [
  141. for(i=[0:len(subcolumns[0])-1])
  142. let(list = [ for(j=[0:columns-1]) subcolumns[j][i] ])
  143. Subdivision(list,divisions=divisions, method=method),
  144. ];
  145.  
  146. weavecolumns =
  147. [
  148. for(i=[0:len(subrows[0])-1])
  149. let(list = [ for(j=[0:rows-1]) subrows[j][i] ])
  150. Subdivision(list,divisions=divisions, method="1path"),
  151. ];
  152.  
  153. echo(str("Before subdivision: rows = ",rows,", columns = ",columns));
  154. echo(str("After subdivision: rows = ",len(weaverows),", columns = ",len(weavecolumns)));
  155.  
  156. // Show all the new points.
  157. *color("Black")
  158. for(i=[0:len(weaverows)-1],j=[0:len(weaverows[0])-1])
  159. translate(weaverows[i][j])
  160. sphere(d=thickness);
  161.  
  162. // Using the columns is the same?
  163. *color("OrangeRed")
  164. for(i=[0:len(weavecolumns)-1],j=[0:len(weavecolumns[0])-1])
  165. translate(weavecolumns[i][j])
  166. sphere(d=thickness);
  167.  
  168. // Show the surface with a thickness.
  169. // The surface can be build around little spheres
  170. // with a thickness, but that is slow.
  171. // Now a tiny cube is used.
  172. *color("SkyBlue",0.5)
  173. {
  174. if(!vase_mode)
  175. {
  176. for(i=[0:len(weaverows)-2],j=[0:len(weaverows[0])-2])
  177. hull()
  178. for(i2=[0,1],j2=[0,1])
  179. translate(weaverows[i+i2][j+j2])
  180. // sphere(d=thickness);
  181. cube(epsilon);
  182. }
  183. else
  184. {
  185. // If vase_mode is selected, also make
  186. // the surface that wraps around to the points
  187. // at index 0.
  188. for(i=[0:len(weaverows)-2],j=[0:len(weaverows[0])-1])
  189. hull()
  190. for(i2=[0,1],j2=[0,1])
  191. {
  192. i3 = i+i2;
  193. j3 = (j+j2) % (len(weaverows[0]));
  194. translate(weaverows[i3][j3])
  195. // sphere(d=thickness);
  196. cube(epsilon);
  197. }
  198. }
  199. }
  200.  
  201. // Make something useful from the point cloud,
  202. // by turning it into a polyhedron.
  203.  
  204. // I didn't know how to put the "Start" function
  205. // into the "LayersToPolyhedron()" function,
  206. // so I need to make a start before building the shape.
  207. vnf_first_layer = PolyhedronPointsStart(weaverows[0]);
  208. vnf = LayersToPolyhedron(vnf_first_layer,weaverows);
  209.  
  210. // Show the result
  211. color("SkyBlue")
  212. polyhedron(vnf[0],vnf[1]);
  213.  
  214. // ===============================================
  215. // Below are the polyhedron functions that are
  216. // not finished yet.
  217. //
  218. // I wrote them from scratch, but I did use
  219. // the term "VNF" from the BOSL2 library.
  220. // Using the VNF structure also makes these
  221. // function compatible with the BOSL2 library.
  222. //
  223. // A VNF stands for "vertices and faces".
  224. // It consists of the points and the faces for a polyhedron.
  225.  
  226.  
  227. // A function that builds a polyhedron from
  228. // an array with points.
  229. function LayersToPolyhedron(vnf,layers,index=1) =
  230. let(new_vnf = PolyhedronPointsAdd(vnf,layers[index]))
  231. index < len(layers) - 1 ?
  232. LayersToPolyhedron(new_vnf,layers,index+1) : new_vnf;
  233.  
  234.  
  235. // PolygonPointsToPolyhedron
  236. // =========================
  237. // This function turns a list of 2D coordinates
  238. // (for a polygon) into points and faces
  239. // for a polyhedron.
  240. //
  241. // The last face is the top face, so it can be used
  242. // with the PolyhedronPointsAdd() function.
  243. //
  244. // Parameters:
  245. // points
  246. // A list of 2D coordinates.
  247. // The coordinates should be in anti-clockwise order.
  248. // height
  249. // The height of the polyhedron.
  250. // If the height is not specified, then a flat VNF should be returned.
  251. // A flat VNF is not valid, but it can be used with
  252. // the function PolyhedronPointsAdd().
  253. // Return:
  254. // An array with points and faces.
  255. // The BOSL2 library calls that a "VNF" structure.
  256. //
  257. function PolygonPointsToPolyhedron(points,height) =
  258. let(n=len(points))
  259. // Bottom 3D points on the xy-plane.
  260. let(points3D_A = [for(i=[0:n-1]) [points[i].x,points[i].y,0]])
  261. // Top 3D points.
  262. let(points3D_B = is_undef(height) ? [] : [for(i=[0:n-1]) [points[i].x,points[i].y,height]])
  263. let(points3D = concat(points3D_A,points3D_B))
  264. // The bottom face will be okay,
  265. // if the coordinates are in anti-clockwise order.
  266. let(face_bottom = [[for(i=[0:n-1]) i]])
  267. let(m = is_undef(height) ? n : 2*n)
  268. let(face_top = [[for(i=[0:n-1]) m-1-i]])
  269. let(faces_sides = is_undef(height) ? [] :
  270. [
  271. for(i=[0:n-1])
  272. let(inc = (i+1) % n)
  273. [i,n+i,n+inc,inc]
  274. ])
  275. // Concatenate all the faces together.
  276. let(faces = concat(face_bottom,faces_sides,face_top))
  277. // Return a VNF (points and faces).
  278. [points3D,faces];
  279.  
  280.  
  281. // PolyhedronPointsStart
  282. // =====================
  283. // This function takes a list of 3D points,
  284. // and converts it into a flat VNF without height.
  285. // There are no side faces.
  286. // The points are just the single layer points.
  287. //
  288. // Note:
  289. // This is not a valid polyhedron in OpenSCAD.
  290. // It is used to build upon by adding layers.
  291. function PolyhedronPointsStart(points) =
  292. let(n=len(points))
  293. let(bottomface = [[ for(i=[0:n-1]) i]])
  294. let(topface = [[ for(i=[0:n-1]) n-1-i]])
  295. let(faces=concat(bottomface,topface))
  296. [points,faces];
  297.  
  298.  
  299. // PolyhedronPointsAdd
  300. // ===================
  301. // Take the VNF of a polyhedron,
  302. // and add a new layer on top.
  303. // This is meant for a solid vase structure,
  304. // and the new layer should have coordinates
  305. // in 3D with the same amount of coordinates
  306. // as the previous layer.
  307. //
  308. // Parameters:
  309. // VNF: The data with points and faces
  310. // for a polyhedron.
  311. // points: A list of 3D points that
  312. // extends the polyhedron.
  313. // The coordinates of the new layer
  314. // should be in anti-clockwise order.
  315. //
  316. // Returns: A VNF.
  317. //
  318. // To do:
  319. // Allow to select any face, and grow the shape
  320. // from there.
  321. //
  322. function PolyhedronPointsAdd(VNF,points) =
  323. let(n_points = len(VNF[0]))
  324. let(n_faces = len(VNF[1]))
  325. let(n_layer = len(points))
  326. // Remove the last face,
  327. // assuming it is the top face.
  328. let(faces_open = [for(i=[0:n_faces-2]) VNF[1][i] ])
  329. // Add the new points.
  330. let(newpoints = concat(VNF[0],points))
  331. let(newface = [[for(i=[0:n_layer-1]) n_points+n_layer-1-i]])
  332. let(newsidefaces =
  333. [
  334. for(i=[0:n_layer-1])
  335. let(inc = (i+1) % n_layer)
  336. [n_points-n_layer+i,n_points+i,n_points+inc,n_points-n_layer+inc]
  337. ])
  338. let(newfaces = concat(faces_open,newsidefaces,newface))
  339. [newpoints,newfaces];
  340.  
  341.  
  342. // This function returns the number of vertices
  343. // for a certain face of the VNF.
  344. function VerticesCount(VNF,face) = len(VNF[1][face]);
  345.  
  346.  
Tags: OpenSCAD
Advertisement
Add Comment
Please, Sign In to add comment