Last active
August 9, 2019 06:52
-
-
Save thinkyhead/3b54e04c0d6144115b31600b65da124a to your computer and use it in GitHub Desktop.
A script to render a Bilinear Mesh in OpenSCAD, with animated scaling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**************************************\ | |
* * | |
* OpenSCAD Mesh Display * | |
* by Thinkyhead - April 2017 * | |
* * | |
* Copy the grid output from Marlin, * | |
* paste below as shown, and use * | |
* OpenSCAD to see a visualization * | |
* of your mesh. * | |
* * | |
* This is also adaptable to JSCAD. * | |
* * | |
\**************************************/ | |
//$t = 0.5; | |
// | |
// Mesh info and points | |
// | |
mesh_width = 200; // X Size in mm of the probed area | |
mesh_height = 200; // Y Size... | |
zprobe_offset = 0; // Added to the points | |
NAN = 0; // Z to use for un-measured points | |
measured_z = [ | |
[ -4.20, -4.13, -4.09, -4.03, -4.19 ], | |
[ -4.16, -4.25, -4.27, -4.25, -4.08 ], | |
[ -4.13, -4.26, -4.39, -4.31, -4.18 ], | |
[ -4.09, -4.20, -4.26, -4.21, -4.18 ], | |
[ -4.13, -3.99, -4.03, -4.06, -4.32 ] | |
]; | |
// | |
// Geometry | |
// | |
max_z_scale = 100; // Scale at Time 0.5 | |
min_z_scale = 10; // Scale at Time 0.2 and 0.8 (trust me) | |
thickness = 0.5; // thickness of the mesh triangles | |
tesselation = 1; // levels of tesselation from 0-2 | |
alternation = 2; // direction change modulus (try it) | |
// | |
// Appearance | |
// | |
show_plane = true; | |
show_labels = true; | |
arrow_length = 5; | |
label_font_lg = "Arial"; | |
label_font_sm = "Arial"; | |
mesh_color = [1,1,1,0.5]; | |
plane_color = [0.4,0.6,0.9,1]; | |
//================================================ Derive useful values | |
big_z = max_2D(measured_z,0); | |
lil_z = min_2D(measured_z,0); | |
mean_value = (big_z + lil_z) / 2.0; | |
mesh_points_y = len(measured_z); | |
mesh_points_x = len(measured_z[0]); | |
xspace = mesh_width / (mesh_points_x - 1); | |
yspace = mesh_height / (mesh_points_y - 1); | |
// At $t=0 and $t=1 scale will be 100% | |
z_scale_factor = min_z_scale + (($t > 0.5) ? 1.0 - $t : $t) * (max_z_scale - min_z_scale) * 2; | |
// | |
// Min and max recursive functions for 1D and 2D arrays | |
// Return the smallest or largest value in the array | |
// | |
function min_1D(b,i) = (i<len(b)-1) ? min(b[i], min_1D(b,i+1)) : b[i]; | |
function min_2D(a,j) = (j<len(a)-1) ? min_2D(a,j+1) : min_1D(a[j], 0); | |
function max_1D(b,i) = (i<len(b)-1) ? max(b[i], max_1D(b,i+1)) : b[i]; | |
function max_2D(a,j) = (j<len(a)-1) ? max_2D(a,j+1) : max_1D(a[j], 0); | |
// | |
// Get the corner probe points of a grid square. | |
// | |
// Input : x,y grid indexes | |
// Output : An array of the 4 corner points | |
// | |
function grid_square(x,y) = [ | |
[x * xspace, y * yspace, z_scale_factor * (measured_z[y][x] - mean_value)], | |
[x * xspace, (y+1) * yspace, z_scale_factor * (measured_z[y+1][x] - mean_value)], | |
[(x+1) * xspace, (y+1) * yspace, z_scale_factor * (measured_z[y+1][x+1] - mean_value)], | |
[(x+1) * xspace, y * yspace, z_scale_factor * (measured_z[y][x+1] - mean_value)] | |
]; | |
// The corner point of a grid square with Z centered on the mean | |
function pos(x,y,z) = [x * xspace, y * yspace, z_scale_factor * (z - mean_value)]; | |
// | |
// Draw the point markers and labels | |
// | |
module point_markers(show_home=true) { | |
// Mark the home position 0,0 | |
color([0,0,0,0.25]) translate([1,1]) cylinder(r=1, h=z_scale_factor, center=true); | |
for (x=[0:mesh_points_x-1], y=[0:mesh_points_y-1]) { | |
z = measured_z[y][x]; | |
down = z < mean_value; | |
translate(pos(x, y, z)) { | |
// Label each point with the Z | |
if (show_labels) { | |
v = z - mean_value; | |
color(abs(v) < 0.1 ? [0,0.5,0] : [0.25,0,0]) | |
translate([0,0,down?-10:10]) { | |
$fn=8; | |
rotate([90,0]) | |
text(str(z), 6, label_font_lg, halign="center", valign="center"); | |
translate([0,0,down?-6:6]) rotate([90,0]) | |
text(str(down ? "" : "+", v), 3, label_font_sm, halign="center", valign="center"); | |
} | |
} | |
// Show an arrow pointing up or down | |
rotate([0, down ? 180 : 0]) translate([0,0,-1]) | |
cylinder( | |
r1=0.5, | |
r2=0.1, | |
h=arrow_length, $fn=12, center=1 | |
); | |
} | |
} | |
} | |
// | |
// Split a square on the diagonal into | |
// two triangles and render them. | |
// | |
// s : a square | |
// alt : a flag to split on the other diagonal | |
// | |
module tesselated_square(s, alt=false) { | |
add = [0,0,thickness]; | |
p1 = [ | |
s[0], s[1], s[2], s[3], | |
s[0]+add, s[1]+add, s[2]+add, s[3]+add | |
]; | |
f1 = alt | |
? [ [0,1,3], [4,5,1,0], [4,7,5], [5,7,3,1], [7,4,0,3] ] | |
: [ [0,1,2], [4,5,1,0], [4,6,5], [5,6,2,1], [6,4,0,2] ]; | |
f2 = alt | |
? [ [1,2,3], [5,6,2,1], [5,6,7], [6,7,3,2], [7,5,1,3] ] | |
: [ [0,2,3], [4,6,2,0], [4,7,6], [6,7,3,2], [7,4,0,3] ]; | |
// Use the other diagonal | |
polyhedron(points=p1, faces=f1); | |
polyhedron(points=p1, faces=f2); | |
} | |
/** | |
* The simplest mesh display | |
*/ | |
module simple_mesh(show_plane=show_plane) { | |
if (show_plane) color(plane_color) cube([mesh_width, mesh_height, thickness]); | |
color(mesh_color) | |
for (x=[0:mesh_points_x-2], y=[0:mesh_points_y-2]) | |
tesselated_square(grid_square(x, y)); | |
} | |
/** | |
* Subdivide the mesh into smaller squares. | |
*/ | |
module bilinear_mesh(show_plane=show_plane,tesselation=tesselation) { | |
if (show_plane) color(plane_color) translate([-5,-5]) cube([mesh_width+10, mesh_height+10, thickness]); | |
tesselation = tesselation % 4; | |
color(mesh_color) | |
for (x=[0:mesh_points_x-2], y=[0:mesh_points_y-2]) { | |
square = grid_square(x, y); | |
if (tesselation < 1) { | |
tesselated_square(square,(x%alternation)-(y%alternation)); | |
} | |
else { | |
subdiv_4 = subdivided_square(square); | |
if (tesselation < 2) { | |
for (i=[0:3]) tesselated_square(subdiv_4[i],i%alternation); | |
} | |
else { | |
for (i=[0:3]) { | |
subdiv_16 = subdivided_square(subdiv_4[i]); | |
if (tesselation < 3) { | |
for (j=[0:3]) tesselated_square(subdiv_16[j],j%alternation); | |
} | |
else { | |
for (j=[0:3]) { | |
subdiv_64 = subdivided_square(subdiv_16[j]); | |
if (tesselation < 4) { | |
for (k=[0:3]) tesselated_square(subdiv_64[k]); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
// | |
// Subdivision helpers | |
// | |
function ctrz(a) = (a[0][2]+a[1][2]+a[3][2]+a[2][2])/4; | |
function avgx(a,i) = (a[i][0]+a[(i+1)%4][0])/2; | |
function avgy(a,i) = (a[i][1]+a[(i+1)%4][1])/2; | |
function avgz(a,i) = (a[i][2]+a[(i+1)%4][2])/2; | |
// | |
// Convert one square into 4, applying bilinear averaging | |
// | |
// Input : 1 square (4 points) | |
// Output : An array of 4 squares | |
// | |
function subdivided_square(a) = [ | |
[ // SW square | |
a[0], // SW | |
[a[0][0],avgy(a,0),avgz(a,0)], // CW | |
[avgx(a,1),avgy(a,0),ctrz(a)], // CC | |
[avgx(a,1),a[0][1],avgz(a,3)] // SC | |
], | |
[ // NW square | |
[a[0][0],avgy(a,0),avgz(a,0)], // CW | |
a[1], // NW | |
[avgx(a,1),a[1][1],avgz(a,1)], // NC | |
[avgx(a,1),avgy(a,0),ctrz(a)] // CC | |
], | |
[ // NE square | |
[avgx(a,1),avgy(a,0),ctrz(a)], // CC | |
[avgx(a,1),a[1][1],avgz(a,1)], // NC | |
a[2], // NE | |
[a[2][0],avgy(a,0),avgz(a,2)] // CE | |
], | |
[ // SE square | |
[avgx(a,1),a[0][1],avgz(a,3)], // SC | |
[avgx(a,1),avgy(a,0),ctrz(a)], // CC | |
[a[2][0],avgy(a,0),avgz(a,2)], // CE | |
a[3] // SE | |
] | |
]; | |
//================================================ Run the plan | |
translate([-mesh_width / 2, -mesh_height / 2]) { | |
$fn = 12; | |
point_markers(); | |
bilinear_mesh(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment