Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Subdivision Surface Matrix.scad
- //
- // Version 1, September 18, 2025
- // By: Stone Age Sculptor
- // License: CC0
- //
- // Version 2, September 26, 2025
- // By: Stone Age Sculptor
- // License: CC0
- // Changes:
- // Turned the surface into a vase.
- // The point cloud is used for a polyhedron().
- // These are the first tests.
- // The script is not finished.
- //
- include <StoneAgeLib/StoneAgeLib.scad>
- $fn = $preview ? 5 : 50;
- thickness = 0.5;
- divisions = $preview ? 3 : 5;
- vase_mode = true; // true for the polyhedron vase
- epsilon = 0.001;
- method = vase_mode ? "1" : "1path";
- // Example that looks like a cloth.
- matrix1 =
- [
- [ [0,0,0], [10,0,-1], [20,0,+5], [30,0,+0], [40,0,5], ],
- [ [0,10,0], [10,10,-10],[20,10,0], [30,10,10], [40,10,-5], ],
- [ [0,20,10], [10,20,10], [20,20,10],[30,20,0], [40,20,5], ],
- [ [0,30,-10],[10,30,-10],[20,30,20],[30,30,-10],[40,30,-5], ],
- ];
- // Example with twist and turn.
- matrix2 =
- [
- [ [-10,12,15], [-20,5,-5], [-20,-5,-5], [-5,5,-15], ],
- [ [30,5,0], [30,2,0],[30,-2,0], [30,-5,0], ],
- [ [40,0,5], [40,0,2],[40,0,-2], [40,0,-5], ],
- [ [50,-5,0], [50,-2,0],[50,2,0], [50,5,0], ],
- [ [110,-20,0], [100,-10,10], [100,10,10],[100,50,0], ],
- [ [100,-10,50],[100,-2,50],[100,2,50],[100,10,50], ],
- [ [80,-50,20],[60,-32,30],[60,-25,30],[80,-10,20], ],
- ];
- // Example with a vase.
- // Each row is not a path, but a closed shape.
- // A row must be counter-clockwise.
- matrix3 =
- [
- [ [50,50,0], [-50,50,0], [-50,-50,0], [50,-30,0], ],
- [ [50,30,30], [-50,50,10], [-50,-40,30], [50,-60,30], ],
- [ [50,50,60], [-50,70,50], [-50,-50,60], [50,-60,50], ],
- [ [30,20,90], [-40,20,90], [-16,-26,84], [30,-40,100], ],
- [ [30,20,110], [-10,20,150], [-10,-40,150], [30,-40,110], ],
- [ [50,20,110], [50,20,150], [50,-20,150], [50,-20,110], ],
- [ [60,20,100], [100,20,110], [100,-20,110], [60,-20,100], ],
- [ [60,10,50], [100,10,50], [100,-30,100], [60,-30,90], ],
- [ [60,-80,50], [90,-80,50], [90,-60,100], [60,-60,90], ],
- [ [60,-80,200], [80,-80,200], [80,-60,200], [60,-60,200], ],
- ];
- // Select an example
- matrix = matrix3;
- // Get the number of rows and columns of the matrix.
- columns = len(matrix[0]);
- rows = len(matrix);
- // Show the edges in brown.
- tube_width = 0.4;
- if($preview)
- {
- color("SaddleBrown",0.5)
- {
- // tubes for rows
- if(!vase_mode)
- {
- for(r=[0:rows-1],c=[0:columns-2])
- hull()
- for(inc=[0,1])
- translate(matrix[r][c+inc])
- sphere(d=tube_width);
- }
- else
- {
- // For vase mode, make the rows closed loops.
- for(r=[0:rows-1],c=[0:columns-1])
- hull()
- for(inc=[0,1])
- {
- c3 = (c+inc) % columns;
- translate(matrix[r][c3])
- sphere(d=tube_width);
- }
- }
- // tubes for columns
- for(c=[0:columns-1],r=[0:rows-2])
- hull()
- for(inc=[0,1])
- translate(matrix[r+inc][c])
- sphere(d=tube_width);
- }
- }
- // Show control points in red.
- control_point_size = 2.4;
- if($preview)
- {
- color("Red")
- {
- for(row=[0:rows-1],column=[0:columns-1])
- translate(matrix[row][column])
- sphere(d=control_point_size);
- }
- }
- // Create two lists, for subdivided rows and subdivided columns.
- subrows =
- [
- // Pick a single row, and subdivide that.
- for(r=[0:rows-1])
- Subdivision(matrix[r],divisions=divisions,method=method),
- ];
- subcolumns =
- [
- // Gather the data of a column, and subdivide that.
- for(c=[0:columns-1])
- let(list = [for(i=[0:rows-1]) matrix[i][c]])
- Subdivision(list,divisions=divisions,method="1path"),
- ];
- // Weave the subdivision between the new points.
- weaverows =
- [
- for(i=[0:len(subcolumns[0])-1])
- let(list = [ for(j=[0:columns-1]) subcolumns[j][i] ])
- Subdivision(list,divisions=divisions, method=method),
- ];
- weavecolumns =
- [
- for(i=[0:len(subrows[0])-1])
- let(list = [ for(j=[0:rows-1]) subrows[j][i] ])
- Subdivision(list,divisions=divisions, method="1path"),
- ];
- echo(str("Before subdivision: rows = ",rows,", columns = ",columns));
- echo(str("After subdivision: rows = ",len(weaverows),", columns = ",len(weavecolumns)));
- // Show all the new points.
- *color("Black")
- for(i=[0:len(weaverows)-1],j=[0:len(weaverows[0])-1])
- translate(weaverows[i][j])
- sphere(d=thickness);
- // Using the columns is the same?
- *color("OrangeRed")
- for(i=[0:len(weavecolumns)-1],j=[0:len(weavecolumns[0])-1])
- translate(weavecolumns[i][j])
- sphere(d=thickness);
- // Show the surface with a thickness.
- // The surface can be build around little spheres
- // with a thickness, but that is slow.
- // Now a tiny cube is used.
- *color("SkyBlue",0.5)
- {
- if(!vase_mode)
- {
- for(i=[0:len(weaverows)-2],j=[0:len(weaverows[0])-2])
- hull()
- for(i2=[0,1],j2=[0,1])
- translate(weaverows[i+i2][j+j2])
- // sphere(d=thickness);
- cube(epsilon);
- }
- else
- {
- // If vase_mode is selected, also make
- // the surface that wraps around to the points
- // at index 0.
- for(i=[0:len(weaverows)-2],j=[0:len(weaverows[0])-1])
- hull()
- for(i2=[0,1],j2=[0,1])
- {
- i3 = i+i2;
- j3 = (j+j2) % (len(weaverows[0]));
- translate(weaverows[i3][j3])
- // sphere(d=thickness);
- cube(epsilon);
- }
- }
- }
- // Make something useful from the point cloud,
- // by turning it into a polyhedron.
- // I didn't know how to put the "Start" function
- // into the "LayersToPolyhedron()" function,
- // so I need to make a start before building the shape.
- vnf_first_layer = PolyhedronPointsStart(weaverows[0]);
- vnf = LayersToPolyhedron(vnf_first_layer,weaverows);
- // Show the result
- color("SkyBlue")
- polyhedron(vnf[0],vnf[1]);
- // ===============================================
- // Below are the polyhedron functions that are
- // not finished yet.
- //
- // I wrote them from scratch, but I did use
- // the term "VNF" from the BOSL2 library.
- // Using the VNF structure also makes these
- // function compatible with the BOSL2 library.
- //
- // A VNF stands for "vertices and faces".
- // It consists of the points and the faces for a polyhedron.
- // A function that builds a polyhedron from
- // an array with points.
- function LayersToPolyhedron(vnf,layers,index=1) =
- let(new_vnf = PolyhedronPointsAdd(vnf,layers[index]))
- index < len(layers) - 1 ?
- LayersToPolyhedron(new_vnf,layers,index+1) : new_vnf;
- // PolygonPointsToPolyhedron
- // =========================
- // This function turns a list of 2D coordinates
- // (for a polygon) into points and faces
- // for a polyhedron.
- //
- // The last face is the top face, so it can be used
- // with the PolyhedronPointsAdd() function.
- //
- // Parameters:
- // points
- // A list of 2D coordinates.
- // The coordinates should be in anti-clockwise order.
- // height
- // The height of the polyhedron.
- // If the height is not specified, then a flat VNF should be returned.
- // A flat VNF is not valid, but it can be used with
- // the function PolyhedronPointsAdd().
- // Return:
- // An array with points and faces.
- // The BOSL2 library calls that a "VNF" structure.
- //
- function PolygonPointsToPolyhedron(points,height) =
- let(n=len(points))
- // Bottom 3D points on the xy-plane.
- let(points3D_A = [for(i=[0:n-1]) [points[i].x,points[i].y,0]])
- // Top 3D points.
- let(points3D_B = is_undef(height) ? [] : [for(i=[0:n-1]) [points[i].x,points[i].y,height]])
- let(points3D = concat(points3D_A,points3D_B))
- // The bottom face will be okay,
- // if the coordinates are in anti-clockwise order.
- let(face_bottom = [[for(i=[0:n-1]) i]])
- let(m = is_undef(height) ? n : 2*n)
- let(face_top = [[for(i=[0:n-1]) m-1-i]])
- let(faces_sides = is_undef(height) ? [] :
- [
- for(i=[0:n-1])
- let(inc = (i+1) % n)
- [i,n+i,n+inc,inc]
- ])
- // Concatenate all the faces together.
- let(faces = concat(face_bottom,faces_sides,face_top))
- // Return a VNF (points and faces).
- [points3D,faces];
- // PolyhedronPointsStart
- // =====================
- // This function takes a list of 3D points,
- // and converts it into a flat VNF without height.
- // There are no side faces.
- // The points are just the single layer points.
- //
- // Note:
- // This is not a valid polyhedron in OpenSCAD.
- // It is used to build upon by adding layers.
- function PolyhedronPointsStart(points) =
- let(n=len(points))
- let(bottomface = [[ for(i=[0:n-1]) i]])
- let(topface = [[ for(i=[0:n-1]) n-1-i]])
- let(faces=concat(bottomface,topface))
- [points,faces];
- // PolyhedronPointsAdd
- // ===================
- // Take the VNF of a polyhedron,
- // and add a new layer on top.
- // This is meant for a solid vase structure,
- // and the new layer should have coordinates
- // in 3D with the same amount of coordinates
- // as the previous layer.
- //
- // Parameters:
- // VNF: The data with points and faces
- // for a polyhedron.
- // points: A list of 3D points that
- // extends the polyhedron.
- // The coordinates of the new layer
- // should be in anti-clockwise order.
- //
- // Returns: A VNF.
- //
- // To do:
- // Allow to select any face, and grow the shape
- // from there.
- //
- function PolyhedronPointsAdd(VNF,points) =
- let(n_points = len(VNF[0]))
- let(n_faces = len(VNF[1]))
- let(n_layer = len(points))
- // Remove the last face,
- // assuming it is the top face.
- let(faces_open = [for(i=[0:n_faces-2]) VNF[1][i] ])
- // Add the new points.
- let(newpoints = concat(VNF[0],points))
- let(newface = [[for(i=[0:n_layer-1]) n_points+n_layer-1-i]])
- let(newsidefaces =
- [
- for(i=[0:n_layer-1])
- let(inc = (i+1) % n_layer)
- [n_points-n_layer+i,n_points+i,n_points+inc,n_points-n_layer+inc]
- ])
- let(newfaces = concat(faces_open,newsidefaces,newface))
- [newpoints,newfaces];
- // This function returns the number of vertices
- // for a certain face of the VNF.
- function VerticesCount(VNF,face) = len(VNF[1][face]);
Advertisement
Add Comment
Please, Sign In to add comment