Organize projects into folders with READMEs and preview renders.

Move the Ring solar siding adapter into its own directory, add repo and
project documentation, and introduce a preview script (OpenSCAD+xvfb or
PyVista fallback) for catalog images.
This commit is contained in:
2026-06-13 17:09:11 -04:00
parent 31e750b4d8
commit 5d15a1fd82
6 changed files with 349 additions and 0 deletions
@@ -0,0 +1,136 @@
// Ring Solar Charger — Vinyl Siding Adapter Plate
// BACKUP: flat upper rear + slow lower taper, no bead coping groove yet.
// Units: millimeters. Vanilla OpenSCAD only.
/* [Plate] */
plate_width = 93.1;
plate_height = 171.45;
min_wall_thickness = 3.0;
edge_round = 0;
/* [Siding Profile] */
siding_projection = 27.0;
face_inset_depth = 8.0;
flat_upper_ratio = 0.55;
course_pitch = 165.1;
profile_segments = 8;
profile_mode = "curved";
rear_clearance = 0.2;
/* [Mounting Holes] */
mount_hole_offset_top = 50.0;
mount_hole_offset_bottom = 44.7;
mount_hole_dia = 4.5;
mount_hole_x = 0;
/* [Export] */
part_mode = "full";
profile_strip_width = 20;
/* [Hidden] */
$fn = 64;
epsilon = 0.01;
function projection_z() = siding_projection - rear_clearance;
function face_inset_z() = projection_z() - face_inset_depth;
function face_y_flat_end() = plate_height * (1 - flat_upper_ratio);
function lower_curve_z(y) =
let(
y1 = face_y_flat_end(),
t = y / max(y1, epsilon),
ease = pow(sin(t * 90), 1.4),
z_bottom = projection_z(),
z_top = face_inset_z()
)
z_top + (z_bottom - z_top) * (1 - ease);
function stepped_rear_z(y) =
y >= face_y_flat_end() ? face_inset_z() : projection_z();
function rear_z_at(y) =
y >= face_y_flat_end() ? face_inset_z()
: profile_mode == "stepped" ? stepped_rear_z(y)
: lower_curve_z(y);
function rear_profile_points() =
let(step = plate_height / max(profile_segments * 12, 24))
[for (y = [0 : step : plate_height])
[y, rear_z_at(y)]];
function max_rear_z() =
let(pts = rear_profile_points())
max([for (p = pts) p[1]]);
function front_z() = max_rear_z() + min_wall_thickness;
function rear_y_min() =
min([for (p = rear_profile_points()) p[1]]);
function minkowski_active() =
edge_round > 0 && min_wall_thickness >= 2 * edge_round + 1;
function hole_y_min() =
rear_y_min() - (minkowski_active() ? edge_round : 0) - epsilon;
function hole_y_max() =
front_z() + (minkowski_active() ? edge_round : 0) + epsilon;
function cross_section_polygon() =
let(
rear_pts = rear_profile_points(),
fz = front_z()
)
[
for (p = rear_pts) p,
[plate_height, fz],
[0, fz]
];
module cross_section_2d() {
polygon(cross_section_polygon());
}
module adapter_body(width) {
linear_extrude(width, center = true)
cross_section_2d();
}
module mount_holes() {
y_min = hole_y_min();
y_max = hole_y_max();
hole_depth = y_max - y_min;
center_y = (y_min + y_max) / 2;
for (y_pos = [plate_height - mount_hole_offset_top, mount_hole_offset_bottom]) {
translate([y_pos, center_y, mount_hole_x])
rotate([90, 0, 0])
cylinder(h = hole_depth, d = mount_hole_dia, center = true);
}
}
module rounded_body(width) {
if (minkowski_active()) {
minkowski() {
adapter_body(max(width - 2 * edge_round, 1));
sphere(r = edge_round);
}
} else {
adapter_body(width);
}
}
module adapter_plate() {
width = part_mode == "profile_strip" ? profile_strip_width : plate_width;
if (part_mode == "full") {
difference() {
rounded_body(width);
mount_holes();
}
} else {
rounded_body(width);
}
}
adapter_plate();