diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d0bee55
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+*_canute.bat
+Thumbs.db
+__pycache__
+*.pyc
+tests/bom/
+tests/deps/
+*.log
+*.html
diff --git a/box.scad b/box.scad
new file mode 100644
index 0000000..48b8def
--- /dev/null
+++ b/box.scad
@@ -0,0 +1,471 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! A box made from routed or laser cut sheet sheets and printed profiles and bezels. It can be arbitrarily large
+//! compared to the 3D printed parts because they can be cut into interlocking sections and solvent welded
+//! together. The box panels can be customised to have holes and parts mounted on them by overriding the
+//! definitions of `box_base()`, `box_front()`, etc.
+//!
+//! `box.scad` should be ```use```d and `box_assembly.scad` ```include```d.
+//!
+//! A box is defined with a list that specifies the inside dimensions, top, bottom and side sheet materials, the
+//! screw type and printed part wall thickness. This diagram shows how the various dimensions are labelled:
+//! ![](docs/box.png)
+//!
+//! Normally the side sheets are the same type but they can be overridden individually as long as the substitute has the same thickness.
+//
+include
+use
+use
+use
+
+include
+use
+
+bezel_clearance = 0.2;
+sheet_end_clearance = 1;
+sheet_slot_clearance = 0.2;
+
+function box_screw(type) = type[0]; //! Screw type to be used at the corners
+function box_wall(type) = type[1]; //! Wall thickness of 3D parts
+function box_sheets(type) = type[2]; //! Sheet type used for the sides
+function box_top_sheet(type) = type[3]; //! Sheet type for the top
+function box_base_sheet(type)= type[4]; //! Sheet type for the bottom
+function box_feet(type) = type[5]; //! True to enable feet on the bottom bezel
+function box_width(type) = type[6]; //! Internal width
+function box_depth(type) = type[7]; //! Internal depth
+function box_height(type) = type[8]; //! Internal height
+
+function box_bezel_clearance(type) = bezel_clearance;
+
+function box_corner_gap(type) = 3; //! Gap between box_sheets at the corners to connect inside and outside profiles
+function box_profile_overlap(type) = 3 + sheet_end_clearance / 2;
+
+function box_washer(type) = screw_washer(box_screw(type));
+function box_insert(type) = screw_insert(box_screw(type));
+
+function box_hole_inset(type) = washer_radius(box_washer(type)) + 1;
+function box_insert_r(type) = insert_hole_radius(box_insert(type));
+function box_insert_l(type) = insert_length(box_insert(type));
+function box_boss_r(type) = ceil(corrected_radius(box_insert_r(type)) + box_wall(type));
+
+function box_sheet_slot(type) = sheet_thickness(box_sheets(type)) + sheet_slot_clearance; // add some clearance
+function box_corner_overlap(type) = box_wall(type);
+
+function box_corner_rad(type) = box_sheet_slot(type) - sheet_slot_clearance / 2 + box_corner_gap(type) + box_corner_overlap(type);
+function box_sheet_r(type) = box_corner_rad(type) - box_sheet_slot(type) - box_corner_overlap(type);
+
+function box_screw_length(type, top) = screw_longer_than(2 * washer_thickness(box_washer(type))
+ + sheet_thickness(top ? box_top_sheet(type) : box_base_sheet(type))
+ + box_corner_gap(type) + box_profile_overlap(type) + box_insert_l(type) - 1);
+
+function box_wall_clearance(type) = box_sheet_slot(type) / 2 - sheet_thickness(box_sheets(type)) / 2;
+function box_margin(type) = box_profile_overlap(type) + box_corner_gap(type); //! How much the bezel intrudes on the specified height
+function box_intrusion(type) = box_hole_inset(type) + box_boss_r(type); //! Corner profile intrusion
+function sheet_reduction(type) = 2 * box_corner_gap(type) + sheet_end_clearance;
+function box_outset(type) = box_sheet_slot(type) + box_wall(type) - sheet_slot_clearance / 2; //! How much the bezel expands the specified internal size
+function box_inset(type) = box_wall(type) + sheet_slot_clearance / 2; //! How much the bezel intrudes on the specified width and length, away from the corners
+function box_bezel_height(type, bottom) = //! Bezel height for top or bottom
+ let(t1 = sheet_thickness(box_base_sheet(type)), t2 = sheet_thickness(box_top_sheet(type)))
+ box_corner_rad(type) + box_profile_overlap(type) + (bottom ? max(t1, t2) : t2) - sheet_thickness(box_sheets(type));
+
+grill_hole = 5;
+grill_gap = 1.9;
+module grill(width, height, r = 1000, poly = false, h = 0) { //! A staggered array of 5mm holes to make grills in sheets. Can be constrained to be circular. Set ```poly``` ```true``` for printing, ```false``` for milling.
+ nx = floor(width / (grill_hole + grill_gap));
+ xpitch = width / nx;
+ ny = floor(height / ((grill_hole + grill_gap) * cos(30)));
+ ypitch = height / ny;
+
+ extrude_if(h)
+ for(y = [0 : ny - 1], x = [0 : nx - 1 - (y % 2)]) {
+ x = -width / 2 + (x + 0.5 + (y % 2) / 2) * xpitch;
+ y = -height / 2 + (y + 0.5) * ypitch;
+ if(sqrt(sqr(x) + sqr(y)) + grill_hole / 2 <= r)
+ translate([x, y])
+ if(poly)
+ poly_circle(r = grill_hole / 2);
+ else
+ circle(d = grill_hole);
+ }
+}
+
+module box_corner_profile_2D(type) { //! The 2D shape of the corner profile.
+ t = box_sheet_slot(type);
+ difference() {
+ union() {
+ quadrant(box_hole_inset(type) + box_boss_r(type), box_boss_r(type)); // inside corner
+
+ translate([box_corner_gap(type) + box_profile_overlap(type), box_corner_gap(type) + box_profile_overlap(type)])
+ rotate(180)
+ quadrant(box_profile_overlap(type) + box_corner_rad(type), box_corner_rad(type)); // outside corner
+ }
+ translate([box_corner_gap(type), -t + sheet_slot_clearance / 2])
+ square([100, t]);
+
+ translate([-t + sheet_slot_clearance / 2, box_corner_gap(type)])
+ square([t, 100]);
+ }
+}
+
+module box_corner_profile(type) { //! Generates the corner profile STL for 3D printing.
+ stl("box_corner_profile");
+
+ length = box_height(type) - 2 * box_margin(type);
+ difference() {
+ linear_extrude(height = length, center = true, convexity = 5)
+ box_corner_profile_2D(type);
+
+ for(z = [-1, 1])
+ translate([box_hole_inset(type), box_hole_inset(type), z * length / 2])
+ insert_hole(box_insert(type), 5);
+ }
+}
+
+module box_corner_profile_section(type, section, sections) { //! Generates interlocking sections of the corner profile to allow it to be taller than the printer
+ overlap = 4;
+ length = box_height(type) - 2 * box_margin(type);
+ section_length = round_to_layer((length - overlap) / sections);
+ last_section = section >= sections - 1;
+ h = last_section ? length - (sections - 1) * section_length : section_length;
+ overlap_wall = 2;
+
+ difference() {
+ union() {
+ linear_extrude(height = h, convexity = 5)
+ box_corner_profile_2D(type);
+
+ if(!last_section) // male end always at the top
+ translate_z(section_length - 1)
+ for(i = [0 : 1], offset = i * layer_height)
+ linear_extrude(height = overlap + 1 - offset)
+ offset(1 + offset - layer_height)
+ offset(-overlap_wall - 1)
+ box_corner_profile_2D(type);
+ }
+ if(section > 0)
+ translate_z(last_section ? h : 0) { // female at bottom unless last section
+ linear_extrude(height = 2 * (overlap + layer_height), center = true, convexity = 5)
+ offset(-overlap_wall)
+ box_corner_profile_2D(type);
+
+ linear_extrude(height = 2 * layer_height, center = true, convexity = 5)
+ offset(-overlap_wall + layer_height)
+ box_corner_profile_2D(type);
+ }
+ if(!section || last_section) // insert holes always at the bottom
+ translate([box_hole_inset(type), box_hole_inset(type)])
+ insert_hole(box_insert(type), 5);
+ }
+}
+
+module box_corner_quadrants(type, width, depth)
+ for(corner = [0:3]) {
+ x = [-1,1,1,-1][corner];
+ y = [-1,-1,1,1][corner];
+
+ translate([x * width / 2, y * depth / 2, 0])
+ rotate(corner * 90)
+ quadrant(box_intrusion(type), box_boss_r(type));
+ }
+
+module box_bezel(type, bottom) { //! Generates top and bottom bezel STLs
+ stl(bottom ? "bottom_bezel" : "top_bezel");
+ feet = bottom && box_feet(type);
+ t = box_sheet_slot(type);
+ outset = box_outset(type);
+ inner_r = box_sheet_r(type);
+ foot_height = box_corner_gap(type) + sheet_thickness(box_base_sheet(type)) + washer_thickness(box_washer(type)) + screw_head_height(box_screw(type)) + box_profile_overlap(type) + 2;
+ foot_length = box_corner_rad(type) * 2;
+ height = box_bezel_height(type, bottom);
+ foot_extension = foot_height - height;
+
+ difference() {
+ translate_z(-box_profile_overlap(type)) difference() {
+ rounded_rectangle([box_width(type) + 2 * outset, box_depth(type) + 2 * outset, feet ? foot_height : height], box_corner_rad(type), false);
+ //
+ // Remove edges between the feet
+ //
+ if(feet)
+ hull() {
+ translate_z(height + 0.5)
+ cube([box_width(type) - 2 * foot_length, box_depth(type) + 2 * outset + 1, 1], center = true);
+
+ translate_z(foot_height + 1)
+ cube([box_width(type) - 2 * (foot_length - foot_extension), box_depth(type) + 2 * outset + 1, 1], center = true);
+ }
+ if(feet)
+ hull() {
+ translate_z(height + 0.5)
+ cube([box_width(type) + 2 * outset + 1, box_depth(type) - 2 * foot_length, 1], center = true);
+
+ translate_z(foot_height + 1)
+ cube([box_width(type) + 2 * outset + 1, box_depth(type) - 2 * (foot_length - foot_extension), 1], center = true);
+ }
+ }
+ //
+ // slots for side panels
+ //
+ translate_z(-box_profile_overlap(type))
+ linear_extrude(height = 2 * box_profile_overlap(type), center = true)
+ for(i = [-1, 1]) {
+ translate([i * (box_width(type) / 2 + t / 2 - sheet_slot_clearance / 2), 0])
+ square([t, box_depth(type) - 2 * box_corner_gap(type)], center = true);
+
+ translate([0, i * (box_depth(type) / 2 + t / 2 - sheet_slot_clearance / 2)])
+ square([box_width(type) - 2 * box_corner_gap(type), t], center = true);
+ }
+ //
+ // recess for top / bottom panel
+ //
+ translate_z(box_corner_gap(type))
+ rounded_rectangle([box_width(type) + bezel_clearance, box_depth(type) + bezel_clearance, height], inner_r + bezel_clearance / 2, false);
+ //
+ // leave plastic over the corner profiles
+ //
+ translate_z(-box_profile_overlap(type) - 1)
+ linear_extrude(height = box_profile_overlap(type) + box_corner_gap(type) + 2)
+ union() {
+ difference() {
+ square([box_width(type) - 2 * box_inset(type),
+ box_depth(type) - 2 * box_inset(type)], center = true);
+
+ box_corner_quadrants(type, box_width(type), box_depth(type));
+ }
+ box_screw_hole_positions(type)
+ poly_circle(screw_clearance_radius(box_screw(type)));
+ }
+ }
+}
+
+dowel_length = 20;
+dowel_wall = extrusion_width * 3;
+dowel_h_wall = layer_height * 6;
+
+
+module box_bezel_section(type, bottom, rows, cols, x, y) { //! Generates interlocking sections of the bezel to allow it to be bigger than the printer
+ w = (box_width(type) + 2 * box_outset(type)) / cols;
+ h = (box_depth(type) + 2 * box_outset(type)) / rows;
+ bw = box_outset(type) - bezel_clearance / 2;
+ bw2 = box_outset(type) + box_inset(type);
+
+ dw = bw - 2 * dowel_wall;
+ dh = box_bezel_height(type, bottom) - dowel_h_wall;
+
+ dh2 = box_profile_overlap(type) + box_corner_gap(type) - dowel_h_wall;
+
+ end_clearance = 0.5;
+ module male() {
+ rotate([90, 0, 90])
+ linear_extrude(height = dowel_length - 2 * end_clearance, center = true)
+ difference() {
+ union() {
+ h = dh - layer_height;
+ h2 = dh2 - layer_height;
+ hull() {
+ translate([bw / 2, h / 2])
+ square([dw - 1, h], center = true);
+
+ translate([bw / 2, (h - 1) / 2])
+ square([dw, h - 1], center = true);
+ }
+
+ hull() {
+ translate([bw2 / 2, h2 / 2])
+ square([bw2 - 2 * dowel_wall - 1, h2], center = true);
+
+ translate([bw2 / 2, (h2 - 1) / 2])
+ square([bw2 - 2 * dowel_wall, h2 - 1], center = true);
+ }
+ }
+ translate([bw2 / 2, 0])
+ square([box_sheet_slot(type), 2 * box_profile_overlap(type)], center = true);
+ }
+ }
+
+ module female() {
+ union() {
+ translate([0, bw / 2, dh / 2])
+ cube([dowel_length, dw, dh], center = true);
+
+ translate([0, bw2 / 2])
+ cube([dowel_length, bw2 - 2 * dowel_wall, dh2 * 2], center = true);
+
+ hull() {
+ translate([0, bw / 2, dh / 2])
+ cube([2, dw, dh], center = true);
+
+ translate([0, bw / 2, dh / 2])
+ cube([eps, dw + 2 * extrusion_width, dh], center = true);
+
+ }
+ hull() {
+ translate([0, bw2 / 2, dh2 / 2])
+ cube([2, bw2 - 2 * dowel_wall, dh2], center = true);
+
+ translate([0, bw2 / 2, dh2 / 2])
+ cube([eps, bw2 - 2 * dowel_wall + 2 * extrusion_width, dh2], center = true);
+
+ }
+ }
+ }
+
+ module support() {
+ if(!$preview)
+ translate([0, bw / 2 + dw / 2])
+ cube([dowel_length / 2 - 0.25, 2 * extrusion_width + 0.2, dh2]);
+ }
+
+ union() {
+ render() difference() {
+ union() {
+ clip(xmin = 0, xmax = w, ymin = 0, ymax = h)
+ translate([box_width(type) / 2 + box_outset(type) - x * w, box_depth(type) / 2 + box_outset(type) - y * h, box_profile_overlap(type)])
+ box_bezel(type, bottom);
+
+ if(x < cols - 1 && y == 0)
+ translate([w, 0])
+ male();
+
+ if(x > 0 && y == rows - 1)
+ translate([0, h])
+ rotate(180)
+ male();
+
+ if(y < rows - 1 && x == cols - 1)
+ translate([w, h])
+ rotate(90)
+ male();
+
+ if(y > 0 && x == 0)
+ rotate(-90)
+ male();
+ }
+
+ if(x < cols - 1 && y == rows - 1)
+ translate([w, h])
+ rotate(180)
+ female();
+
+ if(x > 0 && y == 0)
+ female();
+
+ if(y < rows - 1 && x == 0)
+ translate([0, h])
+ rotate(-90)
+ female();
+
+ if(y > 0 && x == cols - 1)
+ translate([w, 0])
+ rotate(90)
+ female();
+ }
+ if(x < cols - 1 && y == rows - 1)
+ translate([w, h])
+ rotate(180)
+ support();
+
+ if(x > 0 && y == 0)
+ support();
+
+ if(y < rows - 1 && x == 0)
+ translate([0, h])
+ rotate(-90)
+ support();
+
+ if(y > 0 && x == cols - 1)
+ translate([w, 0])
+ rotate(90)
+ support();
+ }
+}
+
+module box_shelf_blank(type) { //! Generates a 2D template for a shelf sheet
+ dxf("box_shelf");
+
+ difference() {
+ sheet_2D(box_sheets(type), box_width(type) - bezel_clearance, box_depth(type) - bezel_clearance, 1);
+
+ offset(bezel_clearance / 2)
+ box_corner_quadrants(type, box_width(type), box_depth(type));
+ }
+}
+
+module box_screw_hole_positions(type)
+ for(x = [-1, 1], y = [-1, 1])
+ translate([x * (box_width(type) / 2 - box_hole_inset(type)), y * (box_depth(type) / 2 - box_hole_inset(type))])
+ children();
+
+module box_base_blank(type) { //! Generates a 2D template for the base sheet
+ dxf("box_base");
+
+ difference() {
+ sheet_2D(box_base_sheet(type), box_width(type), box_depth(type), box_sheet_r(type));
+
+ box_screw_hole_positions(type)
+ drill(screw_clearance_radius(box_screw(type)), 0);
+ }
+}
+
+module box_top_blank(type) { //! Generates a 2D template for the top sheet
+ dxf("box_top");
+
+ difference() {
+ sheet_2D(box_top_sheet(type), box_width(type), box_depth(type), box_sheet_r(type));
+
+ box_screw_hole_positions(type)
+ drill(screw_clearance_radius(box_screw(type)), 0);
+ }
+}
+
+function subst_sheet(type, sheet) =
+ let(s = box_sheets(type))
+ sheet ? assert(sheet_thickness(sheet) == sheet_thickness(s)) sheet : s;
+
+module box_left_blank(type, sheet = false) { //! Generates a 2D template for the left sheet, ```sheet``` can be set to override the type
+ dxf("box_left");
+
+ sheet_2D(subst_sheet(type, sheet), box_depth(type) - sheet_reduction(type), box_height(type) - sheet_reduction(type), 1);
+}
+
+module box_right_blank(type, sheet = false) { //! Generates a 2D template for the right sheet, ```sheet``` can be set to override the type
+ dxf("box_right");
+
+ sheet_2D(subst_sheet(type, sheet), box_depth(type) - sheet_reduction(type), box_height(type) - sheet_reduction(type), 1);
+}
+
+module box_front_blank(type, sheet = false) { //! Generates a 2D template for the front sheet, ```sheet``` can be set to override the type
+ dxf("box_front");
+
+ sheet_2D(subst_sheet(type, sheet), box_width(type) - sheet_reduction(type), box_height(type) - sheet_reduction(type), 1);
+}
+
+module box_back_blank(type, sheet = false) { //! Generates a 2D template for the back sheet, ```sheet``` can be set to override the type
+ dxf("box_back");
+
+ sheet_2D(subst_sheet(type, sheet), box_width(type) - sheet_reduction(type), box_height(type) - sheet_reduction(type), 1);
+}
+
+module box_base(type) render_2D_sheet(box_base_sheet(type)) box_base_blank(type); //! Default base, can be overridden to customise
+module box_top(type) render_2D_sheet(box_top_sheet(type)) box_top_blank(type); //! Default top, can be overridden to customise
+module box_back(type) render_2D_sheet(box_sheets(type)) box_back_blank(type); //! Default back, can be overridden to customise
+module box_front(type) render_2D_sheet(box_sheets(type)) box_front_blank(type); //! Default front, can be overridden to customise
+module box_left(type) render_2D_sheet(box_sheets(type)) box_left_blank(type); //! Default left side, can be overridden to customise
+module box_right(type) render_2D_sheet(box_sheets(type)) box_right_blank(type); //! Default right side, can be overridden to customise
diff --git a/box_assembly.scad b/box_assembly.scad
new file mode 100644
index 0000000..9618f9e
--- /dev/null
+++ b/box_assembly.scad
@@ -0,0 +1,94 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// The assembly is ```include```d so the panel definitions can be overridden to add holes and components.
+// The _box_module also needs to be wrapped in the file that uses it so it can be called without
+// parameters to make the assembly views. E.g. module box_assembly() _box_assembly(box);
+//
+module _box_assembly(type, top = true, base = true, left = true, right = true, back = true, front = true, bezels = true, corners = 4)
+assembly("box") {
+ echo("Box:", box_width(type), box_depth(type), box_height(type));
+
+ t = sheet_thickness(box_sheets(type));
+
+ for(corner = [0 : corners - 1]) {
+ x = [-1,1,1,-1][corner];
+ y = [-1,-1,1,1][corner];
+ translate([x * (box_width(type) / 2 + 25 * exploded()), y * (box_depth(type) / 2 + 25 * exploded())])
+ rotate(corner * 90) {
+ color(pp2_colour) render()
+ box_corner_profile(type);
+
+ translate([box_hole_inset(type), box_hole_inset(type)])
+ for(z = [-1, 1])
+ rotate([z * 90 -90, 0, 0])
+ translate_z(box_height(type) / 2 - box_margin(type))
+ insert(box_insert(type));
+ }
+ }
+
+ for(z = [-1, 1]) {
+ sheet_thickness = sheet_thickness(z > 0 ? box_top_sheet(type) : box_base_sheet(type));
+
+ translate_z(z * (box_height(type) / 2 - box_corner_gap(type) + 50 * exploded()))
+ rotate([z * 90 - 90, 0, 0])
+ if(bezels && (z > 0 ? top : base))
+ color(pp1_colour) render() box_bezel(type, z < 0);
+
+ translate_z(z * (box_height(type) / 2 + sheet_thickness + 50 * exploded()))
+ box_screw_hole_positions(type)
+ rotate([z * 90 -90, 0, 0])
+ explode(50, true)
+ screw_and_washer(box_screw(type), box_screw_length(type, z > 0), true);
+ }
+ for(x = [-1, 1])
+ translate([x * (box_width(type) / 2 + t / 2 + 25 * exploded()), 0])
+ rotate([90, 0, x * 90])
+ if(x > 0) {
+ if(right)
+ box_right(type);
+ }
+ else
+ if(left)
+ box_left(type);
+
+ for(y = [-1, 1])
+ translate([0, y * (box_depth(type) / 2 + t / 2 + 25 * exploded())])
+ rotate([90, 0, y * 90 + 90])
+ if(y < 0) {
+ if(front)
+ box_front(type);
+ }
+ else
+ if(back)
+ box_back(type);
+
+ for(z = [-1, 1]) {
+ sheet_thickness = sheet_thickness(z > 0 ? box_top_sheet(type) : box_base_sheet(type));
+ translate_z(z * (box_height(type) / 2 + sheet_thickness / 2 + eps + 100 * exploded()))
+ if(z > 0) {
+ if(top)
+ box_top(type);
+ }
+ else
+ if(base)
+ box_base(type);
+ }
+}
diff --git a/butt_box.scad b/butt_box.scad
new file mode 100644
index 0000000..d473b73
--- /dev/null
+++ b/butt_box.scad
@@ -0,0 +1,261 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! A box made from CNC cut panels butted together using printed fixing blocks. Useful for making large
+//! boxes with minimal 3D printing. More blocks are added as the box gets bigger.
+//!
+//! Needs to be ```include```d rather than ```use```d to allow the panel definitions to be overridden to add holes
+//! and mounted components.
+//!
+//! A list specifies the internal dimensions, screw type, top, bottom and side sheet types and the block
+//! maximum spacing.
+//!
+//! Uses [fixing blocks](#fixing_block) and [corner blocks](#corner_block).
+//
+
+use
+use
+use
+
+function bbox_screw(type) = type[0]; //! Screw type for corner blocks
+function bbox_sheets(type) = type[1]; //! Sheet type for the sides
+function bbox_base_sheet(type)= type[2]; //! Sheet type for the base
+function bbox_top_sheet(type) = type[3]; //! Sheet type for the top
+function bbox_span(type) = type[4]; //! Maximum span between fixing blocks
+function bbox_width(type) = type[5]; //! Internal width
+function bbox_depth(type) = type[6]; //! Internal depth
+function bbox_height(type) = type[7]; //! Internal height
+
+module bbox_shelf_blank(type) { //! 2D template for a shelf
+ dxf("bbox_shelf");
+
+ sheet_2D(bbox_sheets(type), bbox_width(type), bbox_depth(type), 1);
+}
+
+function corner_block_positions(type) = let(
+ width = bbox_width(type),
+ depth = bbox_depth(type),
+ height = bbox_height(type)
+ )
+ [for(corner = [0 : 3], z = [-1, 1])
+ let(
+ x = [-1,1,1,-1][corner],
+ y = [-1,-1,1,1][corner]
+ ) translate([x * (width / 2), y * (depth / 2), z * height / 2]) *
+ rotate([z > 0 ? 180 : 0, 0, corner * 90 + (z > 0 ? 90 : 0)])
+
+ ];
+
+module corner_block_positions(type) {
+ bt = sheet_thickness(bbox_base_sheet(type));
+ tt = sheet_thickness(bbox_top_sheet(type));
+ for(p = corner_block_positions(type))
+ let($thickness = transform([0, 0, 0], p).z > 0 ? tt : bt)
+ multmatrix(p)
+ children();
+}
+
+function corner_holes(type) = [for(p = corner_block_positions(type), q = corner_block_holes(bbox_screw(type))) p * q];
+
+function fixing_block_positions(type) = let(
+ width = bbox_width(type),
+ depth = bbox_depth(type),
+ height = bbox_height(type),
+ span = bbox_span(type),
+ wspans = floor(width / span),
+ wspan = width / (wspans + 1),
+ dspans = floor(depth / span),
+ dspan = depth / (dspans + 1),
+ hspans = floor(height / span),
+ hspan = height / (hspans + 1)
+ )
+ [
+ for(i = [0 : 1 : wspans - 1], y = [-1, 1], z = [-1, 1])
+ translate([(i - (wspans - 1) / 2) * wspan, y * depth / 2, z * height / 2]) *
+ rotate([0, z * 90 + 90, y * 90 + 90]),
+
+ for(i = [0 : 1 : dspans - 1], x = [-1, 1], z = [-1, 1])
+ translate([x * width / 2, (i - (dspans - 1) / 2) * dspan, z * height / 2]) *
+ rotate([0, z * 90 + 90, x * 90]),
+
+ for(i = [0 : 1 : hspans - 1], x = [-1, 1], y = [-1, 1])
+ translate([x * width / 2, y * depth / 2, (i - (hspans - 1) / 2) * hspan]) *
+ rotate([y > 0 ? 180 : 0, x * y * 90]),
+
+ ];
+
+function side_holes(type) = [for(p = fixing_block_positions(type), q = fixing_block_holes(bbox_screw(type))) p * q];
+
+module fixing_block_positions(type) {
+ t = sheet_thickness(bbox_sheets(type));
+ bt = sheet_thickness(bbox_base_sheet(type));
+ tt = sheet_thickness(bbox_top_sheet(type));
+ h = bbox_height(type) / 2 - 1;
+ for(p = fixing_block_positions(type))
+ let(z = transform([0, 0, 0], p).z, $thickness = z > h ? tt : z < -h ? bt : t)
+ multmatrix(p)
+ children();
+}
+
+module drill_holes(type, t)
+ for(list = [corner_holes(type), side_holes(type)], p = list)
+ let(q = t * p)
+ if(abs(transform([0, 0, 0], q).z) < eps)
+ multmatrix(q)
+ drill(screw_clearance_radius(bbox_screw(type)), 0);
+
+module bbox_base_blank(type) { //! 2D template for the base
+ dxf("bbox_base");
+
+ difference() {
+ sheet_2D(bbox_base_sheet(type), bbox_width(type), bbox_depth(type), 1);
+
+ drill_holes(type, translate(bbox_height(type) / 2));
+ }
+}
+
+module bbox_top_blank(type) { //! 2D template for the top
+ dxf("bbox_top");
+
+ t = sheet_thickness(bbox_sheets(type));
+
+ difference() {
+ translate([0, t / 2])
+ sheet_2D(bbox_top_sheet(type), bbox_width(type) + 2 * t, bbox_depth(type) + t);
+
+ drill_holes(type, translate(-bbox_height(type) / 2));
+ }
+}
+
+module bbox_left_blank(type) { //! 2D template for the left side
+ dxf("bbox_left");
+
+ t = sheet_thickness(bbox_sheets(type));
+ bb = sheet_thickness(bbox_base_sheet(type));
+
+ difference() {
+ translate([-t / 2, -bb / 2])
+ sheet_2D(bbox_sheets(type), bbox_depth(type) + t, bbox_height(type) + bb);
+
+ drill_holes(type, rotate([0, 90, 90]) * translate([bbox_width(type) / 2, 0]));
+ }
+}
+
+module bbox_right_blank(type) { //! 2D template for the right side
+ dxf("bbox_right");
+
+ t = sheet_thickness(bbox_sheets(type));
+ bb = sheet_thickness(bbox_base_sheet(type));
+
+ difference() {
+ translate([t / 2, -bb / 2])
+ sheet_2D(bbox_sheets(type), bbox_depth(type) + t, bbox_height(type) + bb);
+
+ drill_holes(type, rotate([0, -90, 90]) * translate([-bbox_width(type) / 2, 0]));
+ }
+}
+
+module bbox_front_blank(type) { //! 2D template for the front
+ dxf("bbox_front");
+
+ t = sheet_thickness(bbox_sheets(type));
+ bb = sheet_thickness(bbox_base_sheet(type));
+ bt = sheet_thickness(bbox_top_sheet(type));
+
+ difference() {
+ translate([0, (bt - bb) / 2])
+ sheet_2D(bbox_sheets(type), bbox_width(type) + 2 * t, bbox_height(type) + bb + bt);
+
+ drill_holes(type, rotate([-90, 0, 0]) * translate([0, bbox_depth(type) / 2]));
+ }
+}
+
+module bbox_back_blank(type) { //! 2D template for the back
+ dxf("bbox_back");
+
+ bb = sheet_thickness(bbox_base_sheet(type));
+ t = sheet_thickness(bbox_sheets(type));
+
+ difference() {
+ translate([0, -bb / 2])
+ sheet_2D(bbox_sheets(type), bbox_width(type), bbox_height(type) + bb);
+
+ drill_holes(type, rotate([90, 0, 0]) * translate([0, -bbox_depth(type) / 2]));
+ }
+}
+
+module bbox_base(type) render_2D_sheet(bbox_base_sheet(type)) bbox_base_blank(type); //! Default base, can be overridden to customise
+module bbox_top(type) render_2D_sheet(bbox_top_sheet(type)) bbox_top_blank(type); //! Default top, can be overridden to customise
+module bbox_back(type) render_2D_sheet(bbox_sheets(type)) bbox_back_blank(type); //! Default back, can be overridden to customise
+module bbox_front(type) render_2D_sheet(bbox_sheets(type)) bbox_front_blank(type); //! Default front, can be overridden to customise
+module bbox_left(type) render_2D_sheet(bbox_sheets(type)) bbox_left_blank(type); //! Default left side, can be overridden to customise
+module bbox_right(type) render_2D_sheet(bbox_sheets(type)) bbox_right_blank(type); //! Default right side, can be overridden to customise
+
+module _bbox_assembly(type, top = true, base = true, left = true, right = true, back = true, front = true) //! The box assembly, wrap with a local copy without parameters
+assembly("bbox") {
+ width = bbox_width(type);
+ depth = bbox_depth(type);
+ height = bbox_height(type);
+ echo("Box:", width, depth, height);
+
+ t = sheet_thickness(bbox_sheets(type));
+ bt = sheet_thickness(bbox_base_sheet(type));
+ tt = sheet_thickness(bbox_top_sheet(type));
+
+ corner_block_positions(type)
+ fastened_corner_block_assembly(t, bbox_screw(type), $thickness);
+
+ fixing_block_positions(type)
+ fastened_fixing_block_assembly(t, bbox_screw(type), thickness2 = $thickness);
+
+ for(x = [-1, 1])
+ translate([x * (width / 2 + t / 2 + eps + 25 * exploded()), 0])
+ rotate([90, 0, x * 90])
+ if(x > 0) {
+ if(right)
+ bbox_right(type);
+ }
+ else
+ if(left)
+ bbox_left(type);
+
+ for(y = [-1, 1])
+ translate([0, y * (depth / 2 + t / 2 + eps + 25 * exploded())])
+ rotate([90, 0, y * 90 + 90])
+ if(y < 0) {
+ if(front)
+ bbox_front(type);
+ }
+ else
+ if(back)
+ bbox_back(type);
+
+ for(z = [-1, 1]) {
+ sheet_thickness = z > 0 ? tt : bt;
+ translate_z(z * (height / 2 + sheet_thickness / 2 + eps + 100 * exploded()))
+ if(z > 0) {
+ if(top)
+ bbox_top(type);
+ }
+ else
+ if(base)
+ bbox_base(type);
+ }
+}
diff --git a/cable_grommets.scad b/cable_grommets.scad
new file mode 100644
index 0000000..ac2366c
--- /dev/null
+++ b/cable_grommets.scad
@@ -0,0 +1,187 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Printed cable grommets for passing cables through panels avoiding sharp edges and in the case
+//! of conductive panels, an extra layer of insulation.
+//
+include
+use
+
+base = 1.25;
+slot_height = round_to_layer(1.27) + layer_height;
+wall = 1.6;
+overlap = 1.5;
+height = base + slot_height + wall + overlap;
+rad = wall + overlap;
+clearance = 0.1;
+
+module ribbon_grommet_hole(ways, h = 50, expand = true) { //! Generate a hole for a ribbon grommet
+ length = ribbon_clamp_slot(ways) + 2 * wall;
+ rad = cnc_bit_r;
+ height = base + slot_height + wall;
+ extrude_if(h)
+ offset(expand ? clearance : 0)
+ hull() {
+ translate([-length / 2, 0])
+ square([length, base]);
+
+ for(end = [-1, 1])
+ translate([end * (length / 2 - rad), height - rad])
+ drill(rad, 0);
+ }
+}
+
+module ribbon_grommet(ways, thickness) { //! Generate the STL for a printed ribbon grommet
+ stl(str("ribbon_grommet_", ways, "_", thickness));
+
+ width = 2 * (wall + clearance) + thickness;
+ slot_length = ribbon_clamp_slot(ways);
+ length = slot_length + 2 * wall + 2 * overlap;
+
+ rotate([90, 0, 0])
+ union() {
+ for(side = [-1, 1])
+ translate_z(side * (width - wall) / 2)
+ linear_extrude(height = wall, center = true, convexity = 5)
+ difference() {
+ hull() {
+ translate([-length / 2, 0])
+ square([length, base]);
+
+ for(end = [-1, 1])
+ translate([end * (length / 2 - rad), height - rad])
+ semi_circle(rad);
+ }
+ translate([-slot_length / 2, base])
+ square([slot_length, slot_height]);
+ }
+
+ linear_extrude(height = width -1, center = true)
+ difference() {
+ ribbon_grommet_hole(ways, expand = false, h = 0);
+
+ translate([-slot_length / 2, base])
+ square([slot_length, slot_height]);
+ }
+ }
+}
+
+module round_grommet_top(diameter, thickness, od = undef) { //! Generate the STL for a round grommet top half
+ stl(str("round_grommet_top_", round(diameter * 10), "_", thickness));
+ chamfer = layer_height;
+ h = wall + thickness + wall;
+ r1 = diameter / 2;
+ r2 = od == undef ? corrected_radius(r1) + wall : od / 2;
+ r3 = r2 + overlap;
+ r0 = r1 + 1;
+ union() {
+ rotate_extrude()
+ polygon([
+ [r0, 0],
+ [r3 - chamfer, 0],
+ [r3, chamfer],
+ [r3, wall],
+ [r2, wall],
+ [r2, h - chamfer],
+ [r2 - chamfer, h],
+ [r0, h],
+ ]);
+
+ render() difference() {
+ cylinder(r = r0 + eps, h = h);
+
+ poly_cylinder(r = r1, h = 100, center = true);
+ }
+ }
+}
+
+module round_grommet_bottom(diameter, od = undef) { //! Generate the STL for a round grommet bottom half
+ stl(str("round_grommet_bottom_", round(diameter * 10)));
+ chamfer = layer_height;
+ r1 = diameter / 2;
+ r2 = od == undef ? corrected_radius(r1) + wall : od / 2;
+ r3 = r2 + max(overlap, wall + chamfer);
+ rotate_extrude()
+ polygon([
+ [r2, chamfer],
+ [r2 + chamfer, 0],
+ [r3, 0],
+ [r3, wall - chamfer],
+ [r3 - chamfer, wall],
+ [r2, wall],
+ ]);
+}
+
+module round_grommet_hole(diameter, h = 100) //! Make a hole for a round grommet
+ drill(diameter / 2 + wall + clearance, h);
+
+module round_grommet_assembly(diameter, thickness, od = undef) {
+ color(pp1_colour)
+ translate_z(wall)
+ vflip()
+ round_grommet_top(diameter, thickness, od);
+
+ color(pp2_colour)
+ translate_z(-thickness)
+ vflip()
+ round_grommet_bottom(diameter, od);
+}
+
+module mouse_grommet_hole(r, h = 50, z = undef, expand = wall + clearance) //! Make a hole for a mouse grommet
+ extrude_if(h)
+ hull(){
+ R = r + expand;
+ translate([0, z == undef ? R : z])
+ semi_circle(R);
+
+ translate([-R, 0])
+ square([2 * R, eps]);
+ }
+
+module mouse_grommet(r, thickness) { //! Make the STL for a mouse grommet
+ stl(str("mouse_grommet_", r * 10, "_", thickness));
+
+ width = 2 * (wall + clearance) + thickness;
+ length = 2 * r + 2 * wall + 2 * overlap;
+
+ rotate([90, 0, 0])
+ union() {
+ for(side = [-1, 1])
+ translate_z(side * (width - wall) / 2)
+ linear_extrude(height = wall, center = true)
+ difference() {
+ mouse_grommet_hole(r + wall + overlap, z = r + wall, h = 0, expand = 0);
+
+ translate([0, wall])
+ mouse_grommet_hole(r, h = 0, expand = 0);
+ }
+ linear_extrude(height = width - 1, center = true)
+ difference() {
+ mouse_grommet_hole(r, h = 0, z = r + wall, expand = wall);
+
+ translate([0, wall])
+ mouse_grommet_hole(r, h = 0, expand = 0);
+ }
+ }
+}
+
+module ribbon_grommet_20_3_stl() ribbon_grommet(20, 3);
+module mouse_grommet_20_3_stl() mouse_grommet(2,3);
+module mouse_grommet_30_3_stl() mouse_grommet(3,3);
diff --git a/carriers.scad b/carriers.scad
new file mode 100644
index 0000000..f0c914a
--- /dev/null
+++ b/carriers.scad
@@ -0,0 +1,63 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Adapts ESP12 module to 0.1" grid. See .
+//
+$extrusion_width = 0.5;
+
+include
+
+module ESP12F_carrier_stl() { //! Generate the STL for an ESP12 carrier
+ stl("ESP12F_carrier");
+ pins = 8;
+ pitch1 = 2;
+ pitch2 = 2.54;
+ hole = pitch1 - squeezed_wall;
+ hole2 = pitch2 - 3 * extrusion_width;
+ length1 = (pins - 1) * pitch1 + hole + squeezed_wall * 2;
+ length2 = (pins - 1) * pitch2 + hole + squeezed_wall * 2;
+ height = 3;
+
+ wpitch1 = (pins - 1) * pitch1;
+ wpitch2 = ceil(wpitch1 / 2.54) * 2.54;
+
+ width1 = wpitch1 + hole + squeezed_wall * 2;
+ width2 = wpitch2 + hole2 + squeezed_wall * 2;
+
+ difference() {
+ hull() {
+ translate_z(height - eps / 2)
+ cube([width1, length1, eps], center = true);
+
+ translate_z(eps / 2)
+ cube([width2, length2, eps], center = true);
+ }
+
+ for(side = [-1, 1])
+ for(i = [0 : pins - 1])
+ hull() {
+ translate([side * wpitch1 / 2, i * pitch1 - (pins - 1) * pitch1 / 2, height])
+ cube([hole, hole, eps], center = true);
+
+ translate([side * wpitch2 / 2, i * pitch2 - (pins - 1) * pitch2 / 2])
+ cube([hole2, hole2, eps], center = true);
+ }
+ }
+}
diff --git a/core.scad b/core.scad
new file mode 100644
index 0000000..57b7b1d
--- /dev/null
+++ b/core.scad
@@ -0,0 +1,27 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Include this file to use the library
+//
+include
+//
+// Global functions and modules
+//
+use
diff --git a/corner_block.scad b/corner_block.scad
new file mode 100644
index 0000000..ffb1808
--- /dev/null
+++ b/corner_block.scad
@@ -0,0 +1,181 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Corner brackets using threaded inserts for fastening three sheets together at right angles.
+//! Defaults to M3 but other screws sizes can be specified provided they have inserts defined.
+//!
+//! See [butt_box](#Butt_box) for an example of usage.
+//!
+//! Note that the block with its inserts is defined as a sub assembly, but its fasteners get added to the parent assembly.
+//
+include
+include
+include
+use
+use
+
+def_screw = M3_cap_screw;
+wall = 3;
+overshoot = 2; // how far screw can overshoot the insert
+
+function corner_block_screw() = def_screw; //! Default screw type
+
+function corner_block_hole_offset(screw = def_screw) = //! Hole offset from the edge
+ let(insert = screw_insert(screw))
+ insert_length(insert) + max(overshoot + screw_clearance_radius(screw), insert_hole_radius(insert)) + 1;
+
+function corner_block_width(screw = def_screw) = //! Block width, depth and height
+ corner_block_hole_offset(screw) + insert_outer_d(screw_insert(screw)) / 2 + wall;
+
+function corner_block_v_hole(screw = def_screw) = let(offset = corner_block_hole_offset(screw)) translate([offset, offset]) * rotate([180, 0, 0]); //! Transform to bottom hole
+
+function corner_block_h_holes(screw = def_screw) = //! List of transforms to side holes
+ let(offset = corner_block_hole_offset(screw))
+ [translate([offset, 0, offset]) * rotate([90, 0, 0]),
+ translate([0, offset, offset - layer_height]) * rotate([90, 0, -90])];
+
+function corner_block_holes(screw) = concat([corner_block_v_hole(screw)], corner_block_h_holes(screw)); //! List of transforms to all holes
+
+module corner_block_v_hole(screw = def_screw) //! Place children at the bottom screw hole
+ multmatrix(corner_block_v_hole(screw))
+ children();
+
+module corner_block_h_holes(screw = def_screw) //! Place children at the side screw holes
+ for(p = corner_block_h_holes(screw))
+ multmatrix(p)
+ children();
+
+module corner_block_holes(screw = def_screw) //! Place children at all the holes
+ for(p = corner_block_holes(screw))
+ multmatrix(p)
+ children();
+
+module corner_block(screw = def_screw, name = false) { //! Generate the STL for a printed corner block
+ stl(name ? name : str("corner_block", "_M", screw_radius(screw) * 20));
+
+ r = 1;
+ cb_width = corner_block_width(screw);
+ cb_height = cb_width;
+ cb_depth = cb_width;
+ insert = screw_insert(screw);
+ corner_rad = insert_outer_d(insert) / 2 + wall;
+ offset = corner_block_hole_offset(screw);
+ difference() {
+ hull() {
+ translate([r, r])
+ rounded_cylinder(r = r, h = cb_height, r2 = r);
+
+ translate([r, cb_depth - r])
+ cylinder(r = r, h = cb_height - corner_rad);
+
+ translate([cb_width - r, r])
+ cylinder(r = r, h = cb_height - corner_rad);
+
+ translate([offset, offset, offset])
+ sphere(corner_rad);
+
+ translate([offset, offset])
+ cylinder(r = corner_rad, h = offset);
+
+ translate([offset, r, offset])
+ rotate([-90, 0, 180])
+ rounded_cylinder(r = corner_rad, h = r, r2 = r);
+
+ translate([r, offset, offset])
+ rotate([0, 90, 180])
+ rounded_cylinder(r = corner_rad, h = r, r2 = r);
+ }
+ corner_block_v_hole(screw)
+ insert_hole(insert, overshoot);
+
+ corner_block_h_holes(screw)
+ insert_hole(insert, overshoot, true);
+
+ children();
+ }
+}
+
+module corner_block_assembly(screw = def_screw, name = false) //! The printed block with inserts
+assembly(str("corner_block_M", 20 * screw_radius(screw))) {
+ insert = screw_insert(screw);
+
+ color(name ? pp2_colour : pp1_colour)
+ render() corner_block(screw, name) children();
+
+ corner_block_h_holes(screw)
+ insert(insert);
+
+ corner_block_v_hole(screw)
+ insert(insert);
+}
+
+module fastened_corner_block_assembly(thickness, screw = def_screw, thickness_below = undef, name = false) { //! Printed block with all fasteners
+ washer = screw_washer(screw);
+ insert = screw_insert(screw);
+ screw_length = screw_shorter_than(2 * washer_thickness(washer) + thickness + insert_length(insert) + overshoot);
+
+ corner_block_assembly(screw, name) children();
+
+ corner_block_h_holes(screw)
+ translate_z(thickness)
+ screw_and_washer(screw, screw_length, true);
+
+ thickness2 = thickness_below ? thickness_below : thickness;
+ screw_length2 = screw_shorter_than(2 * washer_thickness(washer) + thickness2 + insert_length(insert) + overshoot);
+ corner_block_v_hole(screw)
+ translate_z(thickness2)
+ screw_and_washer(screw, screw_length2, true);
+}
+
+module corner_block_M20_stl() corner_block(M2_cap_screw);
+module corner_block_M25_stl() corner_block(M2p5_cap_screw);
+module corner_block_M30_stl() corner_block(M3_cap_screw);
+module corner_block_M40_stl() corner_block(M4_cap_screw);
+//
+//! 1. Lay the blocks out and place an M2 insert in each upward facing hole.
+//! 1. Push them home with a soldering iron with a conical bit heated to 200°C.
+//! When removing the iron it helps to twist it a little anti-clockwise to release it from the thread.
+//! 1. Lay the blocks on each of their other two flat sides and repeat.
+//
+module corner_block_M20_assembly() corner_block_assembly(M2_cap_screw);
+
+//
+//! 1. Lay the blocks out and place an M2.5 insert in each upward facing hole.
+//! 1. Push them home with a soldering iron with a conical bit heated to 200°C.
+//! When removing the iron it helps to twist it a little anti-clockwise to release it from the thread.
+//! 1. Lay the blocks on each of their other two flat sides and repeat.
+//
+module corner_block_M25_assembly() corner_block_assembly(M2p5_cap_screw);
+
+//
+//! 1. Lay the blocks out and place an M3 insert in each upward facing hole.
+//! 1. Push them home with a soldering iron with a conical bit heated to 200°C.
+//! When removing the iron it helps to twist it a little anti-clockwise to release it from the thread.
+//! 1. Lay the blocks on each of their other two flat sides and repeat.
+//
+module corner_block_M30_assembly() corner_block_assembly(M3_cap_screw);
+
+//
+//! 1. Lay the blocks out and place an M4 insert in each upward facing hole.
+//! 1. Push them home with a soldering iron with a conical bit heated to 200°C.
+//! When removing the iron it helps to twist it a little anti-clockwise to release it from the thread.
+//! 1. Lay the blocks on each of their other two flat sides and repeat.
+//
+module corner_block_M40_assembly() corner_block_assembly(M4_cap_screw);
diff --git a/door_hinge.scad b/door_hinge.scad
new file mode 100644
index 0000000..060a22e
--- /dev/null
+++ b/door_hinge.scad
@@ -0,0 +1,187 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Door hinges to hang an acrylic sheet door on a 3D printer, default 6mm thick.
+//!
+//! The screws are tapped into the acrylic.
+//! Rubber door [sealing strip](#sealing_strip) is used to make it airtight and a [door_latch](#door_latch) holds it closed.
+//
+include
+include
+
+width = 18;
+thickness = 4;
+rad = 3;
+dia = 12;
+
+pin_screw = M3_cap_screw;
+screw = M3_dome_screw;
+stat_screw = M4_dome_screw;
+
+stat_width = 15;
+stat_length = 34;
+stat_clearance = 0.75;
+
+function door_hinge_pin_x() = -dia / 2; //! X offset of the hinge pin
+function door_hinge_pin_y() = -dia / 2 - stat_clearance; //! Y offset of the hinge pin
+function door_hinge_screw() = screw; //! Screw type used for hinge pin
+function door_hinge_stat_screw() = stat_screw; //! Screw use to fasten the stationary part
+function door_hinge_stat_width() = stat_width; //! Width of the stationary part
+function door_hinge_stat_length() = stat_length; //! Length of the stationary part
+
+module door_hinge_hole_positions(dir = 0) { //! Position chidren at the door hole positions
+ hole_pitch = width - 10;
+
+ for(side = [-1, 1])
+ translate([width / 2 + side * hole_pitch / 2, -dir * width / 2 -side * hole_pitch / 2])
+ children();
+}
+
+module door_hinge(door_thickness) { //! Generates STL for the moving part of the hinge
+ stl(str("door_hinge_", door_thickness));
+
+ hole_pitch = width - 10;
+
+ union() {
+ rotate([90, 0, 0])
+ linear_extrude(height = width, center = true)
+ difference() {
+ hull() {
+ translate([dia / 2, thickness + door_thickness / 2])
+ intersection() {
+ rotate(180)
+ teardrop(r = dia / 2, h = 0, truncate = false);
+
+ square([dia + 1, 2 * thickness + door_thickness], center = true);
+ }
+
+ square([1, thickness + door_thickness]);
+ }
+ translate([dia / 2, thickness + door_thickness / 2])
+ teardrop(r = screw_clearance_radius(pin_screw), h = 0);
+ }
+ linear_extrude(height = thickness)
+ difference() {
+ hull() {
+ translate([0, -width / 2])
+ square([1, width]);
+
+ for(side = [-1, 1])
+ translate([-width + rad, side * (width / 2 - rad)])
+ circle4n(rad);
+ }
+ rotate(180)
+ vflip()
+ door_hinge_hole_positions()
+ poly_circle(screw_clearance_radius(screw));
+ }
+ }
+}
+
+module door_hinge_6_stl() door_hinge(6);
+
+module door_hinge_stat_hole_positions(dir = 0) { //! Position children over the screws holes of the stationary part
+ hole_pitch = dia + (stat_length - dia) / 2;
+
+ for(side = [-1, 1])
+ translate([side * hole_pitch / 2, dir * (stat_width / 2 + washer_thickness(screw_washer(pin_screw))), thickness])
+ children();
+}
+
+module door_hinge_stat_stl() { //! Generates the STL for the stationary part
+ stl("door_hinge_stat");
+
+ union() {
+ linear_extrude(height = thickness)
+ difference() {
+ rounded_square([stat_length, stat_width], rad);
+
+ door_hinge_stat_hole_positions()
+ poly_circle(screw_clearance_radius(stat_screw));
+ }
+
+ rotate([90, 0, 0])
+ linear_extrude(height = stat_width, center = true)
+ difference() {
+ hull() {
+ translate([0, dia / 2 + stat_clearance])
+ circle(d = dia);
+
+ translate([0, 0.5])
+ square([dia, 1], center = true);
+ }
+ translate([0, dia / 2 + stat_clearance])
+ teardrop(r = screw_clearance_radius(pin_screw), h = 0);
+ }
+ }
+}
+
+module door_hinge_assembly(top, door_thickness = 6) { //! The moving assembly that goes on the door
+ dir = top ? -1 : 1;
+ pin_x = door_hinge_pin_x();
+ pin_y = door_hinge_pin_y();
+ washer = screw_washer(screw);
+ screw_length = screw_shorter_than(thickness + door_thickness + washer_thickness(washer));
+
+ translate([0, pin_y - (thickness + door_thickness / 2), dir * width / 2]) {
+ rotate([90, 0, 180])
+ color("red") door_hinge(door_thickness);
+
+ rotate([90, 0, 0])
+ door_hinge_hole_positions()
+ screw_and_washer(screw, screw_length);
+ }
+
+ translate([pin_x, pin_y, top ? 0 : -washer_thickness(screw_washer(pin_screw))])
+ washer(screw_washer(pin_screw));
+
+ translate([pin_x, pin_y, top ? washer_thickness(screw_washer(pin_screw)) + stat_width : width])
+ screw_and_washer(pin_screw, screw_longer_than(2 * washer_thickness(screw_washer(pin_screw)) + width + stat_width));
+}
+
+module door_hinge_static_assembly(top, sheet_thickness = 3) { //! The stationary assembly
+ dir = top ? -1 : 1;
+ pin_x = door_hinge_pin_x();
+
+ stat_washer = screw_washer(stat_screw);
+ stat_nut = screw_nut(stat_screw);
+ stat_screw_length = screw_longer_than(thickness + sheet_thickness + 2 * washer_thickness(stat_washer) + nut_thickness(stat_nut, true));
+
+ translate([pin_x, 0, -dir * (stat_width / 2 + washer_thickness(screw_washer(pin_screw)))])
+ rotate([90, 0, 0]) {
+ color("lime") door_hinge_stat_stl();
+
+ door_hinge_stat_hole_positions() {
+ screw_and_washer(stat_screw, stat_screw_length);
+ translate_z(-thickness - sheet_thickness)
+ vflip()
+ nut_and_washer(stat_nut, true);
+ }
+ }
+}
+
+
+module door_hinge_parts_stl() { //! Generates the STL for both parts of the hinge
+ translate([2, width / 2 + 1])
+ door_hinge_6_stl();
+
+ translate([0, -stat_width / 2 - 1])
+ door_hinge_stat_stl();
+}
diff --git a/door_latch.scad b/door_latch.scad
new file mode 100644
index 0000000..79e43d4
--- /dev/null
+++ b/door_latch.scad
@@ -0,0 +1,81 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Door latch for 6mm acrylic door for 3D printer. See [door_hinge](#door_hinge).
+//
+include
+use
+include
+
+length = 35;
+width = 12;
+height = 14.25;
+
+thickness = 5;
+rad = 3;
+
+screw = M4_hex_screw;
+
+function door_latch_screw() = screw; //! The screw used for the axle
+function door_latch_offset() = width / 2 + 1; //! Offset of the axle from the door edge
+
+nut_trap_depth = round_to_layer(screw_head_height(screw)) + 4 * layer_height;
+
+module door_latch_stl() { //! Generates the STL for the printed part
+ stl("door_latch");
+
+ ridge = 4;
+ difference() {
+ union() {
+ hull() {
+ rounded_rectangle([length, width, thickness - tan(30) * (width - ridge) / 2], rad, center = false);
+
+ translate_z(thickness / 2)
+ cube([length, ridge, thickness], center = true);
+ }
+
+ cylinder(d = width, h = height);
+ }
+ hanging_hole(nut_trap_depth, screw_clearance_radius(screw))
+ circle(r = nut_trap_radius(screw_nut(screw)), $fn = 6);
+ }
+}
+
+module door_latch_assembly(sheet_thickness = 3) { //! The assembly for a specified sheet thickess
+ washer = screw_washer(screw);
+ nut = screw_nut(screw);
+
+ screw_length = screw_longer_than(height - nut_trap_depth + sheet_thickness + 2 * washer_thickness(washer) + nut_thickness(nut, true));
+
+ translate([0, -height - washer_thickness(washer)])
+ rotate([-90, 0, 0]) {
+ color("lime") render() door_latch_stl();
+
+ translate_z(nut_trap_depth)
+ vflip()
+ screw(screw, screw_length);
+
+ translate_z(height)
+ washer(washer);
+
+ translate_z(height + sheet_thickness + washer_thickness(washer))
+ nut_and_washer(nut, true);
+ }
+}
diff --git a/fan_guard.scad b/fan_guard.scad
new file mode 100644
index 0000000..7fc10fc
--- /dev/null
+++ b/fan_guard.scad
@@ -0,0 +1,92 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+
+//
+//! Pintable fan finger guard to match the specified fan. To be ```include```d, not ```use```d.
+//!
+//! The ring spacing as well as the number of spokes can be specified, if zero a gasket is generated instead of a guard.
+//
+use
+
+function fan_guard_thickness() = 2; //! Default thickness
+
+function fan_guard_wall() = extrusion_width - layer_height / 2 + nozzle / 2 + extrusion_width / 2;
+function fan_guard_corner_r(type) = washer_diameter(screw_washer(fan_screw(type))) / 2 + 0.5; //! Corner radius of the guard
+function fan_guard_width(type) = max(2 * (fan_hole_pitch(type) + fan_guard_corner_r(type)), fan_bore(type) + 4 * fan_guard_wall()); //! Width of the guard
+
+module fan_guard(type, name = false, thickness = fan_guard_thickness(), spokes = 4, finger_width = 7, grill = false) { //! Generate the STL
+ if(thickness)
+ stl(name ? name : str("fan_guard_", fan_width(type)));
+ hole_pitch = fan_hole_pitch(type);
+ corner_radius = fan_guard_corner_r(type);
+ wall = grill ? 2 : fan_guard_wall();
+ spoke = grill ? 3 : wall;
+ width = fan_guard_width(type);
+
+ hole = fan_aperture(type) / 2;
+ max_ring_pitch = finger_width + wall;
+ inner_ring = max_ring_pitch / 2;
+ gap = hole + wall / 2 - inner_ring;
+ rings = ceil(gap / max_ring_pitch);
+ ring_pitch = gap / rings;
+ spoke_end = grill && fan_aperture(type) > fan_bore(type) ? hole - ring_pitch : hole;
+ spoke_start = grill && rings > 1 ? inner_ring + ring_pitch : inner_ring;
+ rounding = grill ? 1.5 : 0;
+
+ extrude_if(thickness) {
+ difference() {
+ offset(-rounding) offset(rounding) {
+ difference() {
+ rounded_square([width, width], r = width / 2 - hole_pitch);
+
+ fan_holes(type, !grill, !grill, h = 0);
+ }
+ if(spokes) {
+ intersection() {
+ union() {
+ for(i = [(grill ? 1 : 0) : 1 : rings - 1]) {
+ r = inner_ring + i * ring_pitch;
+
+ ring(or = r + wall / 2, ir = r - wall / 2);
+ }
+ for(i = [0 : spokes - 1])
+ rotate(i * 360 / spokes + 45)
+ translate([spoke_start, -spoke / 2])
+ square([spoke_end - spoke_start + eps, spoke]);
+ }
+ square(width - eps, center = true);
+ }
+ }
+ }
+ if(grill)
+ fan_hole_positions(type, z = 0)
+ drill(screw_clearance_radius(fan_screw(type)), 0);
+
+ }
+ if(grill)
+ difference() {
+ r = min(inner_ring + ring_pitch, fan_hub(type) / 2);
+ circle(r);
+
+ for(a = [45 : 90 : 360])
+ rotate(a)
+ translate([r / 2, 0])
+ circle(wall);
+ }
+ }
+}
diff --git a/fixing_block.scad b/fixing_block.scad
new file mode 100644
index 0000000..4f11d44
--- /dev/null
+++ b/fixing_block.scad
@@ -0,0 +1,170 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Fixing block to mount two sheets at right angles using threaded inserts.
+//! Defaults to M3 but other screw sizes can be specified provided they have inserts defined.
+//!
+//! See [butt_box](#Butt_box) for an example of usage.
+//!
+//! Note that the block with its inserts is defined as a sub assembly, but its fasteners get added to the parent assembly.
+//
+include
+include
+include
+use
+
+def_screw = M3_cap_screw;
+wall = 2.5;
+
+function fixing_block_screw() = def_screw; //! Default screw type
+function fixing_block_width(screw = def_screw) = 4 * wall + 3 * insert_outer_d(screw_insert(screw)); //! Width given screw size
+function fixing_block_depth(screw = def_screw) = //! Depth given screw size
+ let(insert = screw_insert(screw))
+ max(insert_length(insert) + wall, insert_outer_d(insert) + 2 * wall);
+
+function fixing_block_height(screw = def_screw) = fixing_block_depth(screw); //! Height given screw size, same as depth
+
+function fixing_block_h_hole(screw = def_screw) = translate(fixing_block_depth(screw) / 2) * rotate([90, 0, 0]); //! Returns transform to position the horizontal screw
+function fixing_block_v_holes(screw = def_screw) = //! Returns a list of transforms to position the vertical screws
+ let(pitch = 2 * insert_outer_d(screw_insert(screw)) + 2 * wall, offset = fixing_block_depth(screw) / 2)
+ [for(end = [-1, 1]) translate([end * pitch / 2, offset]) * rotate([180, 0, 0])];
+
+function fixing_block_holes(screw) = concat([fixing_block_h_hole(screw)], fixing_block_v_holes(screw)); //! Returns a list of transforms to position all the screws
+
+module fixing_block_h_hole(screw = def_screw) //! Position children on the horizontal hole
+ multmatrix(fixing_block_h_hole(screw))
+ children();
+
+module fixing_block_v_holes(screw = def_screw) //! Position children on the vertical holes
+ for(p = fixing_block_v_holes(screw))
+ multmatrix(p)
+ children();
+
+module fixing_block_holes(screw = def_screw) //! Position children on all the holes
+ for(p = fixing_block_holes(screw))
+ multmatrix(p)
+ children();
+
+module fixing_block_h_hole_2D(screw = def_screw) //! Position 2D child on the horizontal hole
+ translate([0, fixing_block_depth(screw) / 2])
+ children();
+
+module fixing_block(screw = def_screw) { //! Generate the STL
+ stl(str("fixing_block_M", screw_radius(screw) * 20));
+ r = 1;
+ insert = screw_insert(screw);
+ corner_rad = insert_outer_d(insert) / 2 + wall;
+ fb_width = fixing_block_width(screw);
+ fb_height = fixing_block_height(screw);
+ fb_depth = fixing_block_depth(screw);
+
+ difference() {
+ union() {
+ linear_extrude(height = fb_height, convexity = 5)
+ difference() {
+ hull() {
+ for(side = [-1, 1]) {
+ translate([side * (fb_width / 2 - corner_rad), fb_depth - corner_rad])
+ circle4n(corner_rad);
+
+ translate([side * (fb_width / 2 - r), r])
+ circle4n(r);
+ }
+ }
+ fixing_block_v_holes(screw)
+ poly_circle(screw_clearance_radius(screw));
+ }
+ }
+ translate_z(fb_height)
+ fixing_block_v_holes(screw)
+ insert_hole(insert);
+
+ fixing_block_h_hole(screw)
+ insert_hole(insert, 10, true);
+ }
+}
+
+module fixing_block_assembly(screw = def_screw) pose([55, 180, 25], [0, 4.8, 4.8]) //! Printed part with the inserts inserted
+assembly(str("fixing_block_M", 20 * screw_radius(screw))) {
+ translate_z(fixing_block_height(screw))
+ rotate([0, 180, 0])
+ color(pp1_colour) render() fixing_block(screw);
+
+ insert = screw_insert(screw);
+
+ fixing_block_v_holes(screw)
+ insert(insert);
+
+ fixing_block_h_hole(screw)
+ insert(insert);
+}
+
+module fastened_fixing_block_assembly(thickness, screw = def_screw, screw2 = undef, thickness2 = undef) { //! Assembly with fasteners in place
+ module fb_screw(screw, thickness) {
+ washer = screw_washer(screw);
+ insert = screw_insert(screw);
+ screw_length = screw_longer_than(2 * washer_thickness(washer) + thickness + insert_length(insert));
+
+ translate_z(thickness)
+ screw_and_washer(screw, screw_length, true);
+ }
+
+ no_pose() fixing_block_assembly(screw);
+
+ fixing_block_v_holes(screw)
+ fb_screw(screw, thickness2 ? thickness2 : thickness);
+
+ fixing_block_h_hole(screw)
+ fb_screw(screw2 ? screw2 : screw, thickness);
+}
+
+module fixing_block_M20_stl() fixing_block(M2_cap_screw);
+module fixing_block_M25_stl() fixing_block(M2p5_cap_screw);
+module fixing_block_M30_stl() fixing_block(M3_cap_screw);
+module fixing_block_M40_stl() fixing_block(M4_cap_screw);
+
+//
+//! 1. Lay the blocks out with the two larger holes facing upwards.
+//! 1. Place two M2 inserts into the two vertical holes of each block and push them home with a soldering iron with a conical bit heated to 200°C.
+//! When removing the iron it helps to twist it a little anti-clockwise to release it from the thread.
+//! 1. Lay the blocks on their backs and insert a third insert the same way.
+//
+module fixing_block_M20_assembly() fixing_block_assembly(M2_cap_screw);
+//
+//! 1. Lay the blocks out with the two larger holes facing upwards.
+//! 1. Place two M2.5 inserts into the two vertical holes of each block and push them home with a soldering iron with a conical bit heated to 200°C.
+//! When removing the iron it helps to twist it a little anti-clockwise to release it from the thread.
+//! 1. Lay the blocks on their backs and insert a third insert the same way.
+//
+module fixing_block_M25_assembly() fixing_block_assembly(M2p5_cap_screw);
+//
+//! 1. Lay the blocks out with the two larger holes facing upwards.
+//! 1. Place two M3 inserts into the two vertical holes of each block and push them home with a soldering iron with a conical bit heated to 200°C.
+//! When removing the iron it helps to twist it a little anti-clockwise to release it from the thread.
+//! 1. Lay the blocks on their backs and insert a third insert the same way.
+//
+module fixing_block_M30_assembly() fixing_block_assembly(M3_cap_screw);
+//
+//! 1. Lay the blocks out with the two larger holes facing upwards.
+//! 1. Place two M4 inserts into the two vertical holes of each block and push them home with a soldering iron with a conical bit heated to 200°C.
+//! When removing the iron it helps to twist it a little anti-clockwise to release it from the thread.
+//! 1. Lay the blocks on their backs and insert a third insert the same way.
+//
+module fixing_block_M40_assembly() fixing_block_assembly(M4_cap_screw);
diff --git a/foot.scad b/foot.scad
new file mode 100644
index 0000000..536587d
--- /dev/null
+++ b/foot.scad
@@ -0,0 +1,151 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Customisable printed rubber feet for equipment cases. The insert variant is better for solid feet because
+//! inserts don't grip well in rubber.
+//
+include
+include
+include
+
+foot = [25, 12, 3, 2, M4_cap_screw, 10];
+insert_foot = [20, 10, 0, 2, M3_cap_screw, 10];
+
+function foot() = foot; //! Default foot used unless a list of parameters is passed
+function insert_foot() = insert_foot; //! Default foot with insert
+
+function foot_diameter(type = foot) = type[0]; //! Outside maximum diameter
+function foot_height(type = foot) = type[1]; //! Total height
+function foot_thickness(type = foot)= type[2]; //! Thickness under the screw
+function foot_rad(type = foot) = type[3]; //! Rounded corner radius
+function foot_screw(type = foot) = type[4]; //! Screw type
+function foot_slant(type = foot) = type[5]; //! Taper angle
+
+module foot(type = foot) { //! Generate STL
+ stl("foot");
+ h = foot_height(type);
+ t = foot_thickness(type);
+ r1 = washer_radius(screw_washer(foot_screw(type)));
+ r3 = foot_diameter(type) / 2;
+ r2 = r3 - h * tan(foot_slant(type));
+ r = foot_rad(type);
+
+ union() {
+ rotate_extrude(convexity = 3) {
+ hull() {
+ translate([r1, 0])
+ square([r3 - r1, eps]);
+
+ for(x = [r1 + r, r2 - r])
+ translate([x, h - r])
+ circle4n(r);
+ }
+ }
+ linear_extrude(height = t)
+ difference() {
+ circle(r1 + eps);
+
+ poly_circle( screw_clearance_radius(foot_screw(type)));
+ }
+ }
+}
+
+module foot_assembly(t = 0, type = foot) { //! Assembly with fasteners in place for specified sheet thickness
+ screw = foot_screw(type);
+ washer = screw_washer(screw);
+ nut = screw_nut(screw);
+ squeeze = 0.5;
+ screw_length = screw_longer_than(foot_thickness(type) + t + 2 * washer_thickness(washer) + nut_thickness(nut, true) - squeeze);
+
+ vflip() explode(15, true) {
+ color(pp4_colour) foot(type);
+
+ if(t)
+ explode(15, true)
+ translate_z(foot_thickness(type))
+ screw_and_washer(screw, screw_length);
+ }
+ if(t)
+ translate_z(t)
+ nut_and_washer(nut, true);
+}
+
+module insert_foot(type = insert_foot) { //! Generate STL for foot with insert
+ stl("insert_foot");
+ h = foot_height(type);
+ r3 = foot_diameter(type) / 2;
+ r2 = r3 - h * tan(foot_slant(type));
+ r = foot_rad(type);
+
+ insert = screw_insert(foot_screw(type));
+ h2 = insert_hole_length(insert);
+ r4 = insert_hole_radius(insert);
+ r5 = r4 + 1;
+ union() {
+ rotate_extrude() {
+ union() {
+ hull() {
+ translate([r5, 0]) {
+ square([r3 - r5, eps]);
+ square([eps, h]);
+ }
+
+ translate([r2 - r, h - r])
+ circle4n(r);
+ }
+ }
+ }
+ linear_extrude(height = h2 + eps)
+ difference() {
+ circle(r5 + eps);
+
+ poly_circle(r4);
+ }
+
+ translate_z(h2)
+ cylinder(r = r5 + eps, h = h - h2);
+ }
+}
+//
+//! Place the insert in the bottom of the foot and push home with a soldering iron with a conical bit heated to 200°C.
+//
+module insert_foot_assembly(type = insert_foot) //! Printed part with insert in place
+assembly("insert_foot") {
+ screw = foot_screw(type);
+ insert = screw_insert(screw);
+
+ vflip()
+ color(pp1_colour) insert_foot(type);
+
+ translate_z(-foot_thickness(type))
+ insert(insert);
+}
+
+module fastened_insert_foot_assembly(t = 3, type = insert_foot) { //! Assembly with fasteners in place for specified sheet thickness
+ screw = foot_screw(type);
+ washer = screw_washer(screw);
+ insert = screw_insert(screw);
+ screw_length = screw_shorter_than(insert_length(insert) + t + 2 * washer_thickness(washer));
+
+ explode(-10) insert_foot_assembly(type);
+
+ translate_z(t)
+ screw_and_washer(screw, screw_length, true);
+}
diff --git a/global_defs.scad b/global_defs.scad
new file mode 100644
index 0000000..1d015fd
--- /dev/null
+++ b/global_defs.scad
@@ -0,0 +1,90 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// This file included directly or indirectly in every scad file.
+//
+// This scheme allows the following:
+// bom defaults to 0
+// Setting $bom on the command line or in the main file before including lib.scad overrides it everywhere.
+// Setting $bom after including lib overrides bom in the libs but not in the local file.
+// Setting $_bom in the local file overrides it in the local file but not in the libs.
+//
+//function is_undef(x) = x == undef;
+
+$_bom = is_undef($bom) ? 0 : $bom; // 0 no bom, 1 assemblies and stls, 2 vitamins as well
+$exploded = is_undef($explode) ? 0 : $explode; // 1 for exploded view
+layer_height = is_undef($layer_height) ? 0.25 : $layer_height; // layer heigth when printing
+extrusion_width = is_undef($extrusion_width) ? 0.5 : $extrusion_width; // filament width when printing
+nozzle = is_undef($nozzle) ? 0.45 : $nozzle; // 3D printer nozzle
+cnc_bit_r = is_undef($cnc_bit_r) ? 1.2 : $cnc_bit_r; // miniumum tool radius when milling 2D objects
+pp1_colour = is_undef($pp1_colour) ? "lime" : $pp1_colour; // printed part colour 1
+pp2_colour = is_undef($pp2_colour) ? "red" : $pp2_colour; // printed part colour 2
+pp3_colour = is_undef($pp3_colour) ? "blue" : $pp3_colour; // printed part colour 3
+pp4_colour = is_undef($pp4_colour) ? "darkorange" : $pp4_colour;// printed part colour 4
+show_rays = is_undef($show_rays) ? false : $show_rays; // show camera sight lines and light direction
+
+// Minimum wall is about two filaments wide but we extrude it closer to get better bonding
+squeezed_wall = $preview ? 2 * extrusion_width - layer_height * (1 - PI / 4)
+ : extrusion_width - layer_height / 2 + nozzle / 2 + extrusion_width / 2;
+
+inf = 1e10; // very big
+eps = 1/128; // small fudge factor to stop CSG barfing on coincident faces.
+$fa = 6;
+$fs = extrusion_width / 2;
+
+function round_to_layer(z) = ceil(z / layer_height) * layer_height;
+// Some additional named colors
+grey20 = [0.2, 0.2, 0.2];
+grey30 = [0.3, 0.3, 0.3];
+grey40 = [0.4, 0.4, 0.4];
+grey50 = [0.5, 0.5, 0.5];
+grey60 = [0.6, 0.6, 0.6];
+grey70 = [0.7, 0.7, 0.7];
+grey80 = [0.8, 0.8, 0.8];
+grey90 = [0.9, 0.9, 0.9];
+brass = "gold";
+
+/*
+ * Enums
+ */
+//
+// Screws
+//
+hs_cap = 0;
+hs_pan = 1;
+hs_cs = 2; // counter sunk
+hs_hex = 3;
+hs_grub = 4; // pulley set screw
+hs_cs_cap = 5;
+hs_dome = 6;
+//
+// Hot end descriptions
+//
+jhead = 1;
+e3d = 2;
+//
+// Face enumeration
+//
+f_bottom = 0;
+f_top = 1;
+f_left = 2;
+f_right = 3;
+f_front = 4;
+f_back = 5;
diff --git a/handle.scad b/handle.scad
new file mode 100644
index 0000000..aa295d2
--- /dev/null
+++ b/handle.scad
@@ -0,0 +1,103 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Printed handle that can be printed without needing support material due to its truncated teardrop profile.
+//
+include
+include
+include
+
+dia = 18;
+length = 90; // inside length
+height = 30; // inside height
+screw = M4_cap_screw;
+insert = screw_insert(screw);
+
+pitch = length + dia;
+
+function handle_length() = pitch + dia; //! Outside length
+function handle_width() = dia; //! The width, i.e. diameter
+function handle_height() = height + dia; //! Total height
+function handle_screw() = screw; //! The screw type
+
+module handle_screw_positions() //! Position children at the screw positions
+ for(end = [-1, 1])
+ translate([end * pitch / 2, 0])
+ children();
+
+module handle_holes(h = 100) //! Drills holes for the screws
+ handle_screw_positions()
+ drill(screw_clearance_radius(screw), h);
+
+module handle_stl() { //! generate the STL
+ stl("handle");
+
+ module end(end)
+ translate([end * pitch / 2, 0])
+ rotate_extrude()
+ intersection() {
+ rotate(180)
+ teardrop(r = dia / 2, h = 0);
+
+ translate([0, - (dia + 1) / 2])
+ square([dia / 2 + 1, dia + 1]);
+ }
+
+ translate_z(dia / 2)
+ union() {
+ hull() {
+ end(-1);
+
+ end(1);
+ }
+
+ handle_screw_positions()
+ render() difference() {
+ h = height + dia / 2;
+ cylinder(d = dia, h = h);
+
+ translate_z(h)
+ insert_hole(insert, 6);
+ }
+ }
+}
+//
+//! Place inserts in the bottom of the posts and push them home with a soldering iron with a conical bit heated to 200°C.
+//
+module handle_assembly() pose([225, 0, 150], [0, 0, 14]) //! Printed part with inserts in place
+assembly("handle") {
+ translate_z(handle_height())
+ color(pp1_colour) vflip() handle_stl();
+
+ handle_screw_positions()
+ vflip()
+ insert(insert);
+}
+
+module handle_fastened_assembly(thickness) { //! Assembly with fasteners in place
+ screw_length = screw_longer_than(thickness + insert_length(insert) + 2 * washer_thickness(screw_washer(screw)));
+
+ handle_assembly();
+
+ handle_screw_positions()
+ vflip()
+ translate_z(thickness)
+ screw_and_washer(screw, screw_length, true);
+}
diff --git a/lib.scad b/lib.scad
new file mode 100644
index 0000000..05f6efc
--- /dev/null
+++ b/lib.scad
@@ -0,0 +1,89 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Include this file to use the library
+//
+include
+
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+include
+
+use
+use
+use
+
+use
+use
+use
+use
+use
+use
+use
+
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
diff --git a/libtest.scad b/libtest.scad
new file mode 100644
index 0000000..436e15b
--- /dev/null
+++ b/libtest.scad
@@ -0,0 +1,339 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// This file shows all the parts in the library.
+//
+include
+
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+use
+
+x5 = 800;
+
+cable_grommets_y = 0;
+
+translate([x5, cable_grommets_y])
+ cable_grommets();
+
+translate([x5, cable_grommets_y + 50])
+ feet();
+
+translate([x5, cable_grommets_y + 75])
+ fixing_blocks();
+
+translate([x5, cable_grommets_y + 100])
+ corner_blocks();
+
+translate([x5, cable_grommets_y + 150])
+ ribbon_clamps();
+
+translate([x5 + 70, cable_grommets_y + 150])
+ screw_knobs();
+
+translate([x5, cable_grommets_y + 470]) {
+ door_hinges()
+ door_latches();
+}
+
+translate([x5, cable_grommets_y + 370])
+ no_explode() socket_boxes();
+
+translate([x5 + 60, cable_grommets_y + 200])
+ strap_handles();
+
+translate([x5, cable_grommets_y + 250])
+ handle();
+
+translate([900, 600])
+ box_test();
+
+translate([850, 1170])
+ bbox_test();
+
+x0 = 0;
+inserts_y = 0;
+nuts_y = inserts_y + 20;
+washers_y = nuts_y + 60;
+screws_y = washers_y + 120;
+o_rings_y = screws_y + 130;
+springs_y = o_rings_y + 20;
+sealing_strip_y = springs_y + 20;
+tubings_y = sealing_strip_y + 20;
+pillars_y = tubings_y + 20;
+leadnuts_y = pillars_y + 40;
+pulleys_y = leadnuts_y +40;
+hot_ends_y = pulleys_y + 60;
+linear_bearings_y = hot_ends_y + 50;
+sheets_y = linear_bearings_y + 50;
+pcbs_y = sheets_y + 40;
+displays_y = pcbs_y + 150;
+fans_y = displays_y + 100;
+transformers_y = fans_y + 120;
+psus_y = transformers_y + 190;
+
+translate([x0 + 20, inserts_y])
+ inserts();
+
+translate([x0, inserts_y])
+ ring_terminals();
+
+translate([x0, nuts_y])
+ nuts();
+
+translate([x0, washers_y])
+ washers();
+
+translate([x0, screws_y])
+ screws();
+
+translate([x0, o_rings_y])
+ o_rings();
+
+translate([x0, springs_y])
+ springs();
+
+translate([x0 + 50, sealing_strip_y])
+ sealing_strip_test();
+
+translate([x0, tubings_y])
+ tubings();
+
+translate([x0, pillars_y])
+ pillars();
+
+translate([x0, leadnuts_y ])
+ leadnuts();
+
+translate([x0 + 80, leadnuts_y])
+ ball_bearings();
+
+translate([x0, pulleys_y])
+ pulleys();
+
+translate([x0, linear_bearings_y]) {
+ linear_bearings();
+ rods();
+}
+
+translate([x0 + 10, hot_ends_y])
+ hot_ends();
+
+translate([x0, sheets_y])
+ sheets();
+
+translate([x0, pcbs_y])
+ pcbs();
+
+translate([x0, displays_y])
+ displays();
+
+translate([x0, fans_y]) {
+ fans();
+
+ translate_z(3)
+ fan_guards();
+}
+
+translate([x0, transformers_y])
+ variacs();
+
+translate([x0, psus_y])
+ psus();
+
+
+x1 = x0 + 100;
+zipties_y = 0;
+bulldogs_y = zipties_y + 40;
+
+translate([x1, zipties_y])
+ zipties();
+
+translate([x1, bulldogs_y])
+ bulldogs();
+
+x2 = x1 + 90;
+leds_y = 0;
+carriers_y = leds_y + 40;
+spades_y = carriers_y + 40;
+buttons_y = spades_y + 40;
+jacks_y = buttons_y + 40;
+microswitches_y = jacks_y + 40;
+rockers_y = microswitches_y + 40;
+toggles_y = rockers_y + 40;
+components_y = toggles_y + 40;
+
+translate([x2, leds_y])
+ leds();
+
+translate([x2 + 8, carriers_y])
+ carriers();
+
+translate([x2+ 38, carriers_y])
+ meters();
+
+translate([x2 + 68, carriers_y])
+ fuseholders();
+
+translate([x2, spades_y])
+ spades();
+
+translate([x2, buttons_y])
+ buttons();
+
+translate([x2, jacks_y])
+ jacks();
+
+translate([x2, microswitches_y])
+ microswitches();
+
+translate([x2, rockers_y])
+ rockers();
+
+translate([x2, toggles_y])
+ toggles();
+
+translate([x2, components_y])
+ components();
+
+
+x3 = x2 + 150;
+veroboard_y = 0;
+d_connectors_y = veroboard_y + 110;
+iecs_y = d_connectors_y + 80;
+modules_y = iecs_y + 60;
+ssrs_y = modules_y + 80;
+blowers_y = ssrs_y + 60;
+batteries_y = blowers_y + 100;
+steppers_y = batteries_y + 70;
+
+translate([x3, veroboard_y])
+ veroboard_test();
+
+translate([x3, d_connectors_y])
+ d_connectors();
+
+translate([x3, iecs_y])
+ iecs();
+
+translate([x3 + 15, modules_y])
+ microview();
+
+translate([x3 + 40, modules_y])
+ modules();
+
+translate([x3, ssrs_y])
+ ssrs();
+
+translate([x3, blowers_y])
+ blowers();
+
+translate([x3, batteries_y])
+ batteries();
+
+translate([x2, steppers_y]) // interloper
+ stepper_motors();
+
+translate([x3, transformers_y])
+ transformers();
+
+
+x4 = x3 + 220;
+belts_y = 0;
+rails_y = belts_y + 200;
+cable_strips_y = rails_y + 300;
+
+translate([x4 + 112, belts_y + 58]) {
+ belt_test();
+
+ translate([0, 60])
+ opengrab_test();
+}
+
+translate([x4, rails_y + 130])
+ rails();
+
+translate([x4, cable_strips_y])
+ cable_strips();
+
+x6 = x5 + 150;
+translate([x6, 125])
+ light_strips();
diff --git a/ribbon_clamp.scad b/ribbon_clamp.scad
new file mode 100644
index 0000000..e8aa642
--- /dev/null
+++ b/ribbon_clamp.scad
@@ -0,0 +1,119 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Clamp for ribbon cable and polypropylene strip.
+//
+include
+include
+include
+use
+
+wall = 2;
+min_wall = 2 * extrusion_width;
+screw = M3_cap_screw;
+insert = screw_insert(screw);
+screw_depth = insert_length(insert) + 1;
+
+function ribbon_clamp_hole_pitch(ways) = ribbon_clamp_slot(ways) + 2 * min_wall + 2 * corrected_radius(insert_hole_radius(insert)); //! Hole pitch
+function ribbon_clamp_width() = 2 * (insert_hole_radius(insert) + 2); //! Width
+function ribbon_clamp_length(ways) = ribbon_clamp_hole_pitch(ways) + ribbon_clamp_width(); //! Length given ways
+function ribbon_clamp_height() = screw_depth + 1; //! Height
+
+module ribbon_clamp_hole_positions(ways, side = undef) //! Place children at hole positions
+ for(x = is_undef(side) ? [-1, 1] : side)
+ translate([x * ribbon_clamp_hole_pitch(ways) / 2, 0])
+ children();
+
+module ribbon_clamp_holes(ways, h = 20) //! Drill screw holes
+ ribbon_clamp_hole_positions(ways)
+ drill(screw_clearance_radius(screw), h);
+
+module ribbon_clamp(ways) { //! Generate STL for given number of ways
+ stl(str("ribbon_clamp_", ways));
+
+ pitch = ribbon_clamp_hole_pitch(ways);
+ d = ribbon_clamp_width();
+ h = ribbon_clamp_height();
+ t = h - ribbon_clamp_slot_depth() - wall;
+
+ difference() {
+ union() {
+ hull() {
+ translate_z(h - t / 2)
+ cube([ribbon_clamp_hole_pitch(ways), d, t], center = true);
+
+ translate_z(1)
+ cube([pitch, max(wall, d - 2 * (h - t)), 2], center = true);
+ }
+ ribbon_clamp_hole_positions(ways, -1)
+ cylinder(d = d, h = h);
+
+ ribbon_clamp_hole_positions(ways, 1)
+ cylinder(d = d, h = h);
+
+ }
+
+ translate_z(h)
+ cube([ribbon_clamp_slot(ways), d + 1, ribbon_clamp_slot_depth() * 2], center = true);
+
+ ribbon_clamp_hole_positions(ways)
+ translate_z(h)
+ rotate(22.5)
+ insert_hole(insert, screw_depth - insert_length(insert));
+ }
+}
+
+module ribbon_clamp_assembly(ways) pose([55, 180, 25]) //! Printed part with inserts in place
+ assembly(str("ribbon_clamp_", ways)) {
+ h = ribbon_clamp_height();
+
+ color(pp1_colour) render()
+ translate_z(h) vflip() ribbon_clamp(ways);
+
+ ribbon_clamp_hole_positions(ways)
+ vflip()
+ insert(insert);
+}
+
+module ribbon_clamp_fastened_assembly(ways, thickness, screw = screw) { //! Clamp with fasteners in place
+ tape_l = floor(ribbon_clamp_slot(ways));
+ tape_width = 25;
+ tape_thickness = 0.5;
+
+ vitamin(str(": Tape self amalgamating silicone ",tape_l," x 25mm"));
+
+ washer = screw_washer(screw);
+ screw_length = screw_shorter_than(2 * washer_thickness(washer) + thickness + screw_depth);
+
+ ribbon_clamp_assembly(ways);
+
+ color("red") translate_z(tape_thickness / 2)
+ cube([tape_l, tape_width, tape_thickness], center = true);
+
+ ribbon_clamp_hole_positions(ways)
+ vflip()
+ translate_z(thickness)
+ screw_and_washer(screw, screw_length, true);
+}
+
+module ribbon_clamp_20_stl() ribbon_clamp(20);
+
+//! * Place inserts into the holes and press home with a soldering iron with a conical bit heated to 200°C.
+module ribbon_clamp_20_assembly() ribbon_clamp_assembly(20);
diff --git a/screw_knob.scad b/screw_knob.scad
new file mode 100644
index 0000000..d140eb5
--- /dev/null
+++ b/screw_knob.scad
@@ -0,0 +1,77 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Knob with embedded hex head screw.
+//
+include
+include
+use
+
+knob_wall = 2;
+function knob_nut_trap_depth(screw) = round_to_layer(screw_head_height(screw));
+knob_stem_h = 6;
+knob_thickness = 4;
+knob_r = 8;
+knob_wave = 1;
+knob_waves = 9;
+knob_height = knob_stem_h + knob_thickness;
+function knob_height() = knob_height;
+
+module screw_knob(screw) { //! Generate the STL foe a knob to fit the specified hex screw
+ stl(str("screw_knob_M", screw_radius(screw) * 20));
+
+ knob_stem_r = nut_trap_radius(screw_nut(screw)) + knob_wall;
+
+ function wave(a) = knob_r + sin(a * knob_waves) * knob_wave;
+
+ union() {
+ render() difference() {
+ cylinder(r = knob_stem_r, h = knob_thickness + knob_stem_h);
+
+ hanging_hole(knob_nut_trap_depth(screw), screw_clearance_radius(screw))
+ rotate(45)
+ circle(r = nut_trap_radius(screw_nut(screw)), $fn = 6);
+ }
+ linear_extrude(height = knob_thickness, convexity = 3)
+ difference() {
+ polygon(points = [for(a = [0 : 359]) [wave(a) * sin(a), wave(a) * cos(a)]]);
+
+ circle(knob_stem_r - eps);
+ }
+ }
+}
+
+//! Place the screw through the printed part
+module screw_knob_assembly(screw, length) //! Assembly with the screw in place
+assembly(str("screw_knob_M", 20 * screw_radius(screw), "_", length)) {
+ translate_z(knob_height)
+ vflip()
+ color(pp1_colour) screw_knob(screw);
+
+ translate_z(knob_height - knob_nut_trap_depth(screw))
+ rotate(-45)
+ screw(screw, length);
+}
+
+module screw_knob_M30_stl() screw_knob(M3_hex_screw);
+module screw_knob_M40_stl() screw_knob(M4_hex_screw);
+
+module screw_knob_M30_16_assembly() screw_knob_assembly(M3_hex_screw, 16);
+module screw_knob_M40_16_assembly() screw_knob_assembly(M4_hex_screw, 16);
diff --git a/scripts/blurb.py b/scripts/blurb.py
new file mode 100644
index 0000000..73c8787
--- /dev/null
+++ b/scripts/blurb.py
@@ -0,0 +1,80 @@
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+"""
+Capture Markup lines in OpenSCAD source code denoted by '//!'.
+"""
+import re
+
+def parse_line(line):
+ """ process a line, add blurb to text and return true if got to a module or function """
+ if line[:3] == '//!':
+ line = line.replace('~\n', ' \n')
+ start = 4 if line[3] == ' ' else 3
+ return False, line[start :]
+ else:
+ words = line.split()
+ return len(words) and (words[0] == "module" or words[0] == "function"), ""
+
+def _scrape_blurb(lines):
+ """ Find Markup lines before the first function or module given a list of lines."""
+ text = ""
+ for line in lines:
+ b, t = parse_line(line)
+ if b:
+ break
+ text += t
+ if len(text):
+ text += '\n'
+ return text
+
+def scrape_blurb(scad_file):
+ """ Find Markup lines before the first function or module."""
+ with open(scad_file, "rt") as file:
+ lines = file.readlines()
+ return _scrape_blurb(lines)
+
+def scrape_module_blurb(lines):
+ """ Find the Markup lines before the last function or module. """
+ text = ""
+ for line in lines:
+ b, t = parse_line(line)
+ text = "" if b else text + t
+ return text
+
+def scrape_code(scad_file):
+ """ Find the Markup lines on the first line of functions and modules. """
+ with open(scad_file, "rt") as file:
+ lines = file.readlines()
+ blurb = _scrape_blurb(lines)
+ properties = {}
+ functions = {}
+ modules = {}
+ for line in lines:
+ match = re.match(r'^function (.*\(type\)|.*\(type ?= ?.*?\)) *= *type\[.*\].*?(?://! ?(.*))?$', line)
+ if match:
+ properties[match.group(1)] = match.group(2)
+ else:
+ match = re.match(r'^function (.*?\(.*?\)).*?(?://! ?(.*))$', line)
+ if match:
+ functions[match.group(1)] = match.group(2)
+ match = re.match(r'^module (.*?\(.*?\)).*?(?://! ?(.*))$', line)
+ if match:
+ modules[match.group(1)] = match.group(2)
+
+ return { "blurb" : blurb, "properties" : properties, "functions" : functions, "modules": modules}
diff --git a/scripts/bom.py b/scripts/bom.py
new file mode 100644
index 0000000..1b79b36
--- /dev/null
+++ b/scripts/bom.py
@@ -0,0 +1,248 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+from __future__ import print_function
+
+import os
+import sys
+import shutil
+import openscad
+from time import *
+from set_config import *
+import json
+
+def find_scad_file(mname):
+ for filename in os.listdir(source_dir):
+ if filename[-5:] == ".scad":
+ #
+ # look for module which makes the assembly
+ #
+ with open(source_dir + "/" + filename, "r") as f:
+ for line in f.readlines():
+ words = line.split()
+ if len(words) and words[0] == "module":
+ module = words[1].split('(')[0]
+ if module == mname:
+ return filename
+ return None
+
+class BOM:
+ def __init__(self, name):
+ self.name = name
+ self.count = 1
+ self.vitamins = {}
+ self.printed = {}
+ self.routed = {}
+ self.assemblies = {}
+
+ def data(self, main):
+ return {
+ "name" : self.name,
+ "count" : self.count,
+ "assemblies" : [main.assemblies[ass].data(main) for ass in self.assemblies],
+ "vitamins" : self.vitamins,
+ "printed" : self.printed,
+ "routed" : self.routed
+ }
+
+ def flat_data(self):
+ assemblies = {}
+ for ass in self.assemblies:
+ assemblies[ass] = self.assemblies[ass].count
+ return {
+ "assemblies" : assemblies,
+ "vitamins" : self.vitamins,
+ "printed" : self.printed,
+ "routed" : self.routed
+ }
+
+ def add_part(self, s):
+ if s[-4:] == ".stl":
+ parts = self.printed
+ else:
+ if s[-4:] == ".dxf":
+ parts = self.routed
+ else:
+ parts = self.vitamins
+ if s in parts:
+ parts[s] += 1
+ else:
+ parts[s] = 1
+
+ def add_assembly(self, ass):
+ if ass in self.assemblies:
+ self.assemblies[ass].count += 1
+ else:
+ self.assemblies[ass] = BOM(ass)
+
+ def make_name(self, ass):
+ if self.count == 1:
+ return ass
+ return ass.replace("assembly", "assemblies")
+
+ def print_bom(self, breakdown, file = None):
+ if self.vitamins:
+ print("Vitamins:", file=file)
+ if breakdown:
+ longest = 0
+ for ass in self.assemblies:
+ name = ass.replace("_assembly","")
+ longest = max(longest, len(name))
+ for i in range(longest):
+ line = ""
+ for ass in sorted(self.assemblies):
+ name = ass.replace("_assembly","").replace("_"," ").capitalize()
+ index = i - (longest - len(name))
+ if index < 0:
+ line += " "
+ else:
+ line += (" %s " % name[index])
+ print(line[:-1], file=file)
+
+ for part in sorted(self.vitamins):
+ if ': ' in part:
+ part_no, description = part.split(': ')
+ else:
+ part_no, description = "", part
+ if breakdown:
+ for ass in sorted(self.assemblies):
+ bom = self.assemblies[ass]
+ if part in bom.vitamins:
+ file.write("%2d|" % bom.vitamins[part])
+ else:
+ file.write(" |")
+ print("%3d" % self.vitamins[part], description, file=file)
+
+ if self.printed:
+ if self.vitamins:
+ print(file=file)
+ print("Printed:", file=file)
+ for part in sorted(self.printed):
+ if breakdown:
+ for ass in sorted(self.assemblies):
+ bom = self.assemblies[ass]
+ if part in bom.printed:
+ file.write("%2d|" % bom.printed[part])
+ else:
+ file.write(" |")
+ print("%3d" % self.printed[part], part, file=file)
+
+ if self.routed:
+ print(file=file)
+ print("CNC cut:", file=file)
+ for part in sorted(self.routed):
+ if breakdown:
+ for ass in sorted(self.assemblies):
+ bom = self.assemblies[ass]
+ if part in bom.routed:
+ file.write("%2d|" % bom.routed[part])
+ else:
+ file.write(" |")
+ print("%3d" % self.routed[part], part, file=file)
+
+ if self.assemblies:
+ print(file=file)
+ print("Assemblies:", file=file)
+ for ass in sorted(self.assemblies):
+ print("%3d %s" % (self.assemblies[ass].count, self.assemblies[ass].make_name(ass)), file=file)
+
+def parse_bom(file = "openscad.log", name = None):
+ main = BOM(name)
+ stack = []
+
+ for line in open(file):
+ pos = line.find('ECHO: "~')
+ if pos > -1:
+ s = line[pos + 8 : line.rfind('"')]
+ if s[-1] == '{':
+ ass = s[:-1]
+ if stack:
+ main.assemblies[stack[-1]].add_assembly(ass) #add to nested BOM
+ stack.append(ass)
+ main.add_assembly(ass) #add to flat BOM
+ else:
+ if s[0] == '}':
+ if s[1:] != stack[-1]:
+ raise Exception("Mismatched assembly " + s[1:] + str(stack))
+ stack.pop()
+ else:
+ main.add_part(s)
+ if stack:
+ main.assemblies[stack[-1]].add_part(s)
+ return main
+
+def boms(target = None, assembly = None):
+ bom_dir = set_config(target) + "bom"
+ if assembly:
+ bom_dir += "/accessories"
+ if not os.path.isdir(bom_dir):
+ os.makedirs(bom_dir)
+ else:
+ assembly = "main_assembly"
+ if os.path.isdir(bom_dir):
+ shutil.rmtree(bom_dir)
+ sleep(0.1)
+ os.makedirs(bom_dir)
+ #
+ # Find the scad file that makes the module
+ #
+ scad_file = find_scad_file(assembly)
+ if not scad_file:
+ raise Exception("can't find source for " + assembly)
+ #
+ # make a file to use the module
+ #
+ bom_maker_name = source_dir + "/bom.scad"
+ with open(bom_maker_name, "w") as f:
+ f.write("use <%s>\n" % scad_file)
+ f.write("%s();\n" % assembly);
+ #
+ # Run openscad
+ #
+ openscad.run("-D","$bom=2","-D","$preview=true","-o", "openscad.echo", bom_maker_name)
+ os.remove(bom_maker_name)
+ print("Generating bom ...", end=" ")
+
+ main = parse_bom("openscad.echo", assembly)
+
+ if assembly == "main_assembly":
+ main.print_bom(True, open(bom_dir + "/bom.txt","wt"))
+
+ for ass in sorted(main.assemblies):
+ with open(bom_dir + "/" + ass + ".txt", "wt") as f:
+ bom = main.assemblies[ass]
+ print(bom.make_name(ass) + ":", file=f)
+ bom.print_bom(False, f)
+
+ with open(bom_dir + "/bom.json", 'w') as outfile:
+ json.dump(main.assemblies[assembly].data(main), outfile, indent = 4)
+
+ print("done")
+
+if __name__ == '__main__':
+ args = len(sys.argv)
+ if args > 1:
+ if args > 2:
+ boms(sys.argv[1], sys.argv[2])
+ else:
+ boms(sys.argv[1])
+ else:
+ boms();
diff --git a/scripts/c14n_stl.py b/scripts/c14n_stl.py
new file mode 100644
index 0000000..89cb8ff
--- /dev/null
+++ b/scripts/c14n_stl.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+#
+# OpenSCAD produces randomly ordered STL files so source control like GIT can't tell if they have changed or not.
+# This scrip orders each triangle to start with the lowest vertex first (comparing x, then y, then z)
+# It then sorts the triangles to start with the one with the lowest vertices first (comparing first vertex, second, then third)
+# This has no effect on the model but makes the STL consistent. I.e. it makes a canonical form.
+#
+
+from __future__ import print_function
+
+import sys
+
+def cmz(x):
+ ''' Convert "-0" to "0". '''
+ return '0' if x == '-0' else x
+
+class Vertex:
+ def __init__(self, x, y, z):
+ self.x, self.y, self.z = x, y, z
+ self.key = (float(x), float(y), float(z))
+
+class Normal:
+ def __init__(self, dx, dy, dz):
+ self.dx, self.dy, self.dz = dx, dy, dz
+
+class Facet:
+ def __init__(self, normal, v1, v2, v3):
+ self.normal = normal
+ if v1.key < v2.key:
+ if v1.key < v3.key:
+ self.vertices = (v1, v2, v3) #v1 is the smallest
+ else:
+ self.vertices = (v3, v1, v2) #v3 is the smallest
+ else:
+ if v2.key < v3.key:
+ self.vertices = (v2, v3, v1) #v2 is the smallest
+ else:
+ self.vertices = (v3, v1, v2) #v3 is the smallest
+
+ def key(self):
+ return (self.vertices[0].x, self.vertices[0].y, self.vertices[0].z,
+ self.vertices[1].x, self.vertices[1].y, self.vertices[1].z,
+ self.vertices[2].x, self.vertices[2].y, self.vertices[2].z)
+
+class STL:
+ def __init__(self, fname):
+ self.facets = []
+
+ with open(fname) as f:
+ words = [cmz(s.strip()) for s in f.read().split()]
+
+ if words[0] == 'solid' and words[1] == 'OpenSCAD_Model':
+ i = 2
+ while words[i] == 'facet':
+ norm = Normal(words[i + 2], words[i + 3], words[i + 4])
+ v1 = Vertex(words[i + 8], words[i + 9], words[i + 10])
+ v2 = Vertex(words[i + 12], words[i + 13], words[i + 14])
+ v3 = Vertex(words[i + 16], words[i + 17], words[i + 18])
+ i += 21
+ self.facets.append(Facet(norm, v1, v2, v3))
+
+ self.facets.sort(key = Facet.key)
+ else:
+ print("Not an OpenSCAD ascii STL file")
+ sys.exit(1)
+
+ def write(self, fname):
+ with open(fname,"wt") as f:
+ print('solid OpenSCAD_Model', file=f)
+ for facet in self.facets:
+ print(' facet normal %s %s %s' % (facet.normal.dx, facet.normal.dy, facet.normal.dz), file=f)
+ print(' outer loop', file=f)
+ for vertex in facet.vertices:
+ print(' vertex %s %s %s' % (vertex.x, vertex.y, vertex.z), file=f)
+ print(' endloop', file=f)
+ print(' endfacet', file=f)
+ print('endsolid OpenSCAD_Model', file=f)
+
+def canonicalise(fname):
+ stl = STL(fname)
+ stl.write(fname)
+
+if __name__ == '__main__':
+ if len(sys.argv) == 2:
+ canonicalise(sys.argv[1])
+ else:
+ print("usage: c14n_stl file")
+ sys.exit(1)
diff --git a/scripts/deps.py b/scripts/deps.py
new file mode 100644
index 0000000..cd0ec69
--- /dev/null
+++ b/scripts/deps.py
@@ -0,0 +1,49 @@
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+import os
+
+def mtime(file):
+ if os.path.isfile(file):
+ return os.path.getmtime(file)
+ return 0
+
+def deps_name(dir, scad_name):
+ return dir + '/' + scad_name[:-5] + '.deps'
+
+def read_deps(dname):
+ with open(dname, "rt") as file:
+ lines = file.readlines()
+ deps = []
+ for line in lines:
+ if line.startswith('\t'):
+ dep = line[1 : -1].rstrip(' \\')
+ if not dep in ['stl.scad', 'dxf.scad', 'svf.scad', 'png.scad']:
+ deps.append(dep)
+ return deps
+
+def check_deps(target_mtime, dname):
+ if not target_mtime:
+ return "target missing"
+ if not os.path.isfile(dname):
+ return "no deps"
+ deps = read_deps(dname)
+ for dep in deps:
+ if mtime(dep) > target_mtime:
+ return dep + ' changed'
+ return None
diff --git a/scripts/dxfs.py b/scripts/dxfs.py
new file mode 100644
index 0000000..cac98d2
--- /dev/null
+++ b/scripts/dxfs.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+from __future__ import print_function
+import sys
+
+from exports import make_parts
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1 and not '.' in sys.argv[1]:
+ target, parts = sys.argv[1], sys.argv[2:]
+ else:
+ target, parts = None, sys.argv[1:]
+ make_parts(target, 'dxf', parts)
diff --git a/scripts/exports.py b/scripts/exports.py
new file mode 100644
index 0000000..ad66134
--- /dev/null
+++ b/scripts/exports.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+from __future__ import print_function
+import os
+import openscad
+import sys
+import c14n_stl
+from set_config import *
+import time
+import times
+from deps import *
+
+def bom_to_parts(target_dir, part_type, assembly = None):
+ #
+ # Make a list of all the parts in the BOM
+ #
+ part_files = []
+ bom = assembly + '.txt' if assembly else "bom.txt"
+ suffix = ".dxf" if part_type == 'svg' else '.' + part_type
+ with open(target_dir + "/../bom/" + bom, "rt") as f:
+ for line in f.readlines():
+ words = line.split()
+ if words:
+ last_word = words[-1]
+ if last_word.endswith(suffix):
+ part_files.append(last_word[:-4] + '.' + part_type)
+ return part_files
+
+def make_parts(target, part_type, parts = None):
+ #
+ # Make the target directory
+ #
+ top_dir = set_config(target)
+ target_dir = top_dir + part_type + 's'
+ deps_dir = top_dir + "deps"
+ if not os.path.isdir(target_dir):
+ os.makedirs(target_dir)
+ if not os.path.isdir(deps_dir):
+ os.makedirs(deps_dir)
+ times.read_times(target_dir)
+ #
+ # Decide which files to make
+ #
+ if parts:
+ targets = list(parts) #copy the list so we dont modify the list passed in
+ else:
+ targets = bom_to_parts(target_dir, part_type)
+ for file in os.listdir(target_dir):
+ if file.endswith('.' + part_type):
+ if not file in targets:
+ print("Removing %s" % file)
+ os.remove(target_dir + '/' + file)
+ #
+ # Find all the scad files
+ #
+ lib_dir = os.environ['OPENSCADPATH'] + '/NopSCADlib'
+ used = []
+ module_suffix = '_dxf' if part_type == 'svg' else '_' + part_type
+ for dir in [source_dir, lib_dir]:
+ for filename in os.listdir(dir):
+ if filename[-5:] == ".scad":
+ #
+ # find any modules ending in _
+ #
+ with open(dir + "/" + filename, "r") as f:
+ for line in f.readlines():
+ words = line.split()
+ if(len(words) and words[0] == "module"):
+ module = words[1].split('(')[0]
+ if module.endswith(module_suffix):
+ base_name = module[:-4]
+ part = base_name + '.' + part_type
+ if part in targets:
+ #
+ # make a file to use the module
+ #
+ part_maker_name = part_type + ".scad"
+ with open(part_maker_name, "w") as f:
+ f.write("use <%s/%s>\n" % (dir, filename))
+ f.write("%s();\n" % module);
+ #
+ # Run openscad on the created file
+ #
+ part_file = target_dir + "/" + part
+ dname = deps_name(deps_dir, filename)
+ changed = check_deps(mtime(part_file), dname)
+ changed = times.check_have_time(changed, part)
+ if changed:
+ print(changed)
+ t = time.time()
+ openscad.run("-D$bom=1", "-d", dname, "-o", part_file, part_maker_name)
+ times.add_time(part, t)
+ if part_type == 'stl':
+ c14n_stl.canonicalise(part_file)
+ targets.remove(part)
+ os.remove(part_maker_name)
+ #
+ # Add the files on the BOM to the used list for plates.py
+ #
+ for line in open("openscad.log"):
+ if line[:7] == 'ECHO: "' and line[-6:] == '.' + part_type + '"\n':
+ used.append(line[7:-2])
+ #
+ # List the ones we didn't find
+ #
+ if targets:
+ for part in targets:
+ if part[-4:] != '.' + part_type:
+ print(part, "is not a", part_type, "file")
+ else:
+ print("Could not find a module called", part[:-4] + module_suffix, "to make", part)
+ sys.exit(1)
+ times.print_times()
+ return used
diff --git a/scripts/gallery.py b/scripts/gallery.py
new file mode 100644
index 0000000..4713847
--- /dev/null
+++ b/scripts/gallery.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+#
+# Find projects and add them to the gallery
+#
+from __future__ import print_function
+import os
+from colorama import Fore, init
+from tests import do_cmd
+import re
+from shutil import copyfile
+
+project_dir = '../..'
+target_dir = 'gallery'
+output_name = target_dir + '/readme.md'
+
+def gallery():
+ if not os.path.isdir(target_dir):
+ os.makedirs(target_dir)
+
+ projects = [i for i in os.listdir(project_dir) if os.path.isdir(project_dir + '/' + i + '/assemblies')]
+ with open(output_name, 'wt') as output_file:
+ for project in projects:
+ path = project_dir + '/' + project
+ print(project)
+ document = path + '/readme.md'
+ if os.path.isfile(document):
+ with open(document, 'rt') as readme:
+ for line in readme.readlines():
+ match = re.match(r"^.*!(\[.*\]\(.*\)).*$", line)
+ if match:
+ image = match.group(0)
+ if image.startswith('![Main Assembly](assemblies/'):
+ file = image[17 : -1]
+ line = line.replace(image, '![](%s.png)' % project)
+ copyfile(path + '/' + file, '%s/%s.png' %(target_dir, project))
+ else:
+ line = line.replace(image, '')
+ print(line[:-1], file = output_file)
+ if line == '---\n':
+ break;
+ else:
+ print(Fore.MAGENTA + "Can't find", document, Fore.WHITE);
+ with open(target_dir + "/readme.html", "wt") as html_file:
+ do_cmd(("python -m markdown -x tables " + output_name).split(), html_file)
+
+if __name__ == '__main__':
+ init()
+ gallery()
diff --git a/scripts/make_all.py b/scripts/make_all.py
new file mode 100644
index 0000000..bb6c4b1
--- /dev/null
+++ b/scripts/make_all.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+import sys
+
+from exports import make_parts
+from bom import boms
+from render import render
+from views import views
+
+if __name__ == '__main__':
+ target = None if len(sys.argv) == 1 else sys.argv[1]
+ boms(target)
+ for part in ['stl', 'dxf']:
+ make_parts(target, part)
+ render(target, part)
+ views(target)
diff --git a/scripts/openscad.py b/scripts/openscad.py
new file mode 100644
index 0000000..b33e547
--- /dev/null
+++ b/scripts/openscad.py
@@ -0,0 +1,38 @@
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+#
+# Run openscad
+#
+from __future__ import print_function
+
+import subprocess, sys
+
+def run(*args):
+ cmd = ["openscad"] + list(args)
+ for arg in cmd:
+ print(arg, end=" ")
+ print()
+ with open("openscad.log", "w") as log:
+ rc = subprocess.call(cmd, stdout = log, stderr = log)
+ for line in open("openscad.log", "rt"):
+ if 'ERROR:' in line or 'WARNING:' in line:
+ print(line[:-1])
+ if rc:
+ sys.exit(rc)
diff --git a/scripts/render.py b/scripts/render.py
new file mode 100644
index 0000000..6b065d1
--- /dev/null
+++ b/scripts/render.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+from __future__ import print_function
+from set_config import *
+from exports import bom_to_parts
+import os
+import openscad
+from tests import do_cmd
+from deps import mtime
+
+def render(target, type):
+ #
+ # Make the target directory
+ #
+ target_dir = set_config(target) + type + 's'
+ if not os.path.isdir(target_dir):
+ os.makedirs(target_dir)
+ #
+ # Find all the parts
+ #
+ parts = bom_to_parts(target_dir, type)
+ #
+ # Remove unused png files
+ #
+ for file in os.listdir(target_dir):
+ if file.endswith('.png'):
+ if not file[:-4] + '.' + type in parts:
+ print("Removing %s" % file)
+ os.remove(target_dir + '/' + file)
+
+ for part in parts:
+ part_file = target_dir + '/' + part
+ png_name = target_dir + '/' + part[:-4] + '.png'
+ #
+ # make a file to import the stl
+ #
+ if mtime(part_file) > mtime(png_name):
+ png_maker_name = "png.scad"
+ with open(png_maker_name, "w") as f:
+ f.write('color("lime") import("%s");\n' % part_file)
+ cam = "--camera=0,0,0,70,0,315,500" if type == 'stl' else "--camera=0,0,0,0,0,0,500"
+ render = "--preview" if type == 'stl' else "--render"
+ openscad.run("--projection=p", "--imgsize=4096,4096", cam, render, "--autocenter", "--viewall", "-o", png_name, png_maker_name);
+ do_cmd(("magick "+ png_name + " -trim -resize 280x280 -background #ffffe5 -gravity Center -extent 280x280 -bordercolor #ffffe5 -border 10 " + png_name).split())
+ os.remove(png_maker_name)
+
+if __name__ == '__main__':
+ target = sys.argv[1] if len(sys.argv) > 1 else None
+ render(target, 'stl')
+ render(target, 'dxf')
diff --git a/scripts/set_config.py b/scripts/set_config.py
new file mode 100644
index 0000000..69a6af9
--- /dev/null
+++ b/scripts/set_config.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+#
+# Set target configuration for multi-target projects that have variable configurations.
+#
+from __future__ import print_function
+
+source_dir = 'scad'
+
+import sys
+import os
+
+def valid_targets():
+ return [i[7:-5] for i in os.listdir(source_dir) if i[0:7] == "config_" and i[-5:] == ".scad"]
+
+def valid_targets_string():
+ result = ''
+ targets = valid_targets()
+ for t in targets:
+ if result:
+ if t == targets[-1]:
+ result += ' and '
+ else:
+ result += ', '
+ result += t
+ return result
+
+
+def set_config(target):
+ targets = valid_targets()
+ if not target:
+ if not targets:
+ return ""
+ print("Must specify a configuration: " + valid_targets_string())
+ sys.exit(1)
+
+ if not targets:
+ print("Not a muli-configuration project (no config_.scad files found)")
+ sys.exit(1)
+
+ if not target in targets:
+ print(target + " is not a configuration, avaliable configurations are: " + valid_targets_string())
+ sys.exit(1)
+
+ fname = source_dir + "/target.scad"
+ text = "include \n" % target;
+ line = ""
+ try:
+ with open(fname,"rt") as f:
+ line = f.read()
+ except:
+ pass
+
+ if line != text:
+ with open(fname,"wt") as f:
+ f. write(text);
+ return target + "/"
+
+if __name__ == '__main__':
+ args = len(sys.argv)
+ if args == 2:
+ set_config(sys.argv[1])
+ else:
+ print("usage: set_config config_name")
+ sys.exit(1)
diff --git a/scripts/stls.py b/scripts/stls.py
new file mode 100644
index 0000000..01d27d0
--- /dev/null
+++ b/scripts/stls.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+from __future__ import print_function
+import sys
+
+from exports import make_parts
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1 and not '.' in sys.argv[1]:
+ target, parts = sys.argv[1], sys.argv[2:]
+ else:
+ target, parts = None, sys.argv[1:]
+ make_parts(target, 'stl', parts)
diff --git a/scripts/svgs.py b/scripts/svgs.py
new file mode 100644
index 0000000..4cca176
--- /dev/null
+++ b/scripts/svgs.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+from __future__ import print_function
+import sys
+
+from exports import make_parts
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1 and not '.' in sys.argv[1]:
+ target, parts = sys.argv[1], sys.argv[2:]
+ else:
+ target, parts = None, sys.argv[1:]
+ make_parts(target, 'svg', parts)
diff --git a/scripts/tests.py b/scripts/tests.py
new file mode 100644
index 0000000..393a304
--- /dev/null
+++ b/scripts/tests.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+from __future__ import print_function
+import os
+import sys
+import openscad
+import subprocess
+import bom
+import times
+import time
+import json
+from deps import *
+from blurb import *
+
+w = 4096
+h = w
+
+def do_cmd(cmd, output = sys.stdout):
+ for arg in cmd:
+ print(arg, end = " ")
+ print()
+ subprocess.call(cmd, stdout = output)
+
+def depluralise(name):
+ if name[-3:] == "ies" and name != "zipties":
+ return name[:-3] + 'y'
+ if name[-3:] == "hes":
+ return name[:-2]
+ if name[-1:] == 's':
+ return name[:-1]
+ return name
+
+def is_plural(name):
+ return name != depluralise(name)
+
+def tests(tests):
+ scad_dir = "tests"
+ deps_dir = scad_dir + "/deps"
+ png_dir = scad_dir + "/png"
+ bom_dir = scad_dir + "/bom"
+ for dir in [deps_dir, png_dir, bom_dir]:
+ if not os.path.isdir(dir):
+ os.makedirs(dir)
+ doc_name = "readme.md"
+ index = {}
+ bodies = {}
+ times.read_times()
+ #
+ # Make cover pic if does not exist as very slow. Delete it to force an update.
+ #
+ png_name = "libtest.png"
+ scad_name = "libtest.scad"
+ if not os.path.isfile(png_name):
+ openscad.run("--projection=p", "--imgsize=%d,%d" % (w, h), "--camera=0,0,0,50,0,340,500", "--autocenter", "--viewall", "-o", png_name, scad_name);
+ do_cmd(["magick", png_name, "-trim", "-resize", "1280", "-bordercolor", "#ffffe5", "-border", "10", png_name])
+ #
+ # List of individual part files
+ #
+ scads = [i for i in os.listdir(scad_dir) if i[-5:] == ".scad"]
+
+ for scad in scads:
+ base_name = scad[:-5]
+ if not tests or base_name in tests:
+ print(base_name)
+ cap_name = base_name[0].capitalize() + base_name[1:]
+ scad_name = scad_dir + '/' + scad
+ png_name = png_dir + '/' + base_name + '.png'
+ bom_name = bom_dir + '/' + base_name + '.json'
+
+ objects_name = None
+ vits_name = 'vitamins/' + base_name + '.scad'
+ if is_plural(base_name) and os.path.isfile(vits_name):
+ objects_name = vits_name
+
+ locations = [
+ ('vitamins/' + depluralise(base_name) + '.scad', 'Vitamins'),
+ (base_name + '.scad', 'Printed'),
+ ('utils/' + base_name + '.scad', 'Utilities'),
+ ('utils/core/' + base_name + '.scad', 'Core Utilities'),
+ ]
+
+ for name, type in locations:
+ if os.path.isfile(name):
+ impl_name = name
+ break
+ else:
+ print("Can't find implementation!")
+ continue
+
+ vsplit = "N"
+ vtype = locations[0][1]
+ types = [vtype + ' A-' + vsplit[0], vtype + ' ' + chr(ord(vsplit) + 1) + '-Z'] + [loc[1] for loc in locations[1 :]]
+ if type == vtype:
+ type = types[0] if cap_name[0] <= vsplit else types[1]
+
+ if not type in bodies:
+ bodies[type] = []
+ index[type] = []
+ body = bodies[type]
+
+ index[type] += [cap_name]
+ body += ['' % cap_name]
+ body += ["## " + cap_name]
+
+ doc = None
+ if impl_name:
+ doc = scrape_code(impl_name)
+ blurb = doc["blurb"]
+ else:
+ blurb = scrape_blurb(scad_name)
+
+ if not len(blurb):
+ print("Blurb not found!")
+ else:
+ body += [ blurb ]
+
+ if objects_name:
+ body += ["[%s](%s) Object definitions.\n" % (objects_name, objects_name)]
+
+ if impl_name:
+ body += ["[%s](%s) Implementation.\n" % (impl_name, impl_name)]
+
+ body += ["[%s](%s) Code for this example.\n" % (scad_name.replace('\\','/'), scad_name)]
+
+ if doc:
+ for thing in ["properties", "functions", "modules"]:
+ things = doc[thing]
+ if things:
+ body += ['### %s\n| | |\n|:--- |:--- |' % thing.title()]
+ for item in sorted(things):
+ body += ['| ```%s``` | %s |' % (item, things[item])]
+ body += ['']
+
+ body += ["![%s](%s)\n" %(base_name, png_name)]
+
+ dname = deps_name(deps_dir, scad)
+ oldest = min(mtime(png_name), mtime(bom_name))
+ changed = check_deps(oldest, dname)
+ changed = times.check_have_time(changed, scad_name)
+ if changed:
+ print(changed)
+ t = time.time()
+ openscad.run("-D", "$bom=2", "--projection=p", "--imgsize=%d,%d" % (w, h), "--camera=0,0,0,70,0,315,500", "--autocenter", "--viewall", "-d", dname, "-o", png_name, scad_name);
+ times.add_time(scad_name, t)
+ do_cmd(["magick", png_name, "-trim", "-resize", "1000x600", "-bordercolor", "#ffffe5", "-border", "10", png_name])
+ BOM = bom.parse_bom()
+ with open(bom_name, 'wt') as outfile:
+ json.dump(BOM.flat_data(), outfile, indent = 4)
+
+ with open(bom_name, "rt") as bom_file:
+ BOM = json.load(bom_file)
+ for thing in ["vitamins", "printed", "routed", "assemblies"]:
+ things = BOM[thing]
+ if things:
+ body += ['### %s\n| | | |\n| ---:|:--- |:---|' % thing.title()]
+ for item in sorted(things, key = lambda s: s.split(":")[-1]):
+ name = item
+ desc = ''
+ if thing == "vitamins":
+ vit = item.split(':')
+ name = '```' + vit[0] + '```' if vit[0] else ''
+ while '[[' in name and ']]' in name:
+ i = name.find('[[')
+ j = name.find(']]') + 2
+ name = name.replace(name[i : j], '[ ... ]')
+ desc = vit[1]
+ body += ['| %3d | %s | %s |' % (things[item], name, desc)]
+ body += ['']
+
+ body += ['\nTop']
+ body += ["\n---"]
+
+ with open(doc_name, "wt") as doc_file:
+ print('# NopSCADlib', file = doc_file)
+ print('''\
+An ever expanding library of parts modelled in OpenSCAD useful for 3D printers and enclosures for electronics, etc.
+
+It contains lots of vitamins (the RepRap term for non-printed parts), some general purpose printed parts and
+some utilities. There are also Python scripts to generate Bills of Materials (BOMs),
+ STL files for all the printed parts and DXF files for CNC routed parts in a project.
+
+\n
+''', file = doc_file)
+
+ print('## Table of Contents', file = doc_file)
+ print('', file = doc_file)
+ n = 0
+ for type in types:
+ print(' %s | ' % type, end = '', file = doc_file)
+ n = max(n, len(index[type]))
+ print('
', file = doc_file)
+ for i in range(n):
+ print('', file = doc_file, end = '')
+ for type in types:
+ if i < len(index[type]):
+ name = index[type][i]
+ print(' ' + name + ' | ', file = doc_file, end = '')
+ else:
+ print(' | ', file = doc_file, end = '')
+ print('
', file = doc_file)
+ print('
\n\n---', file = doc_file)
+ for type in types:
+ for line in bodies[type]:
+ print(line, file = doc_file)
+ with open("readme.html", "wt") as html_file:
+ do_cmd("python -m markdown -x tables readme.md".split(), html_file)
+ times.print_times()
+ do_cmd('codespell -L od readme.md'.split())
+
+if __name__ == '__main__':
+ tests(sys.argv[1:])
diff --git a/scripts/times.py b/scripts/times.py
new file mode 100644
index 0000000..f6e7acf
--- /dev/null
+++ b/scripts/times.py
@@ -0,0 +1,71 @@
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+# show execution times and how they have changed.
+
+import json
+import time
+from colorama import Fore, init
+
+def read_times(dir = '.'):
+ global times, last_times, times_fname
+ times_fname = dir + '/times.txt'
+ init()
+ try:
+ with open(times_fname) as json_file:
+ times = json.load(json_file)
+ last_times = dict(times)
+ except:
+ times = {}
+ last_times = {}
+
+def write_times():
+ with open(times_fname, 'w') as outfile:
+ json.dump(times, outfile, indent = 4)
+
+def got_time(name):
+ return name in last_times
+
+def check_have_time(changed, name):
+ if not changed and not got_time(name):
+ changed = "no previous time"
+ return changed
+
+def add_time(name, start):
+ times[name] = round(time.time() - start, 3)
+
+def print_times():
+ write_times()
+ sorted_times = sorted(times.items(), key=lambda kv: kv[1])
+ total = 0
+ for entry in sorted_times:
+ colour = Fore.WHITE
+ key = entry[0]
+ new = entry[1]
+ delta = 0
+ if key in last_times:
+ old = last_times[key]
+ delta = new - old
+ if delta > 0.3:
+ colour = Fore.RED
+ if delta < -0.3:
+ colour = Fore.GREEN
+ print(colour + "%5.1f %5.1f %s" % (new, delta, key))
+ total += new
+ print(Fore.WHITE + "%5.1f" % total)
diff --git a/scripts/views.py b/scripts/views.py
new file mode 100644
index 0000000..8bdb095
--- /dev/null
+++ b/scripts/views.py
@@ -0,0 +1,345 @@
+#!/usr/bin/env python
+
+#
+# NopSCADlib Copyright Chris Palmer 2018
+# nop.head@gmail.com
+# hydraraptor.blogspot.com
+#
+# This file is part of NopSCADlib.
+#
+# NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+# GNU General Public License as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with NopSCADlib.
+# If not, see .
+#
+
+#
+#: Generate assembly views and intructions
+#
+from __future__ import print_function
+from set_config import *
+import openscad
+from tests import do_cmd
+import time
+import times
+from deps import *
+import os
+import json
+import blurb
+import bom
+from colorama import Fore
+
+def is_assembly(s):
+ return s[-9:] == '_assembly' or s[-11:] == '_assemblies'
+
+def add_assembly(flat_bom, bom):
+ if not bom in flat_bom:
+ big = False
+ for ass in bom["assemblies"]:
+ add_assembly(flat_bom, ass)
+ if ass["routed"]:
+ big = True
+ bom["big"] = big or bom["routed"]
+ flat_bom.append(bom)
+
+def bom_to_assemblies(bom_dir):
+ global flat_bom
+ #
+ # Make a list of all the parts in the BOM
+ #
+ bom = {}
+ bom_file = bom_dir + "/bom.json"
+ with open(bom_file) as json_file:
+ bom = json.load(json_file)
+ flat_bom = []
+ add_assembly(flat_bom, bom)
+ ass = flat_bom[-1]
+ if len(ass["assemblies"]) < 2 and not ass["vitamins"] and not ass["printed"] and not ass["routed"]:
+ flat_bom = flat_bom[:-1]
+ return [assembly["name"] for assembly in flat_bom]
+
+def eop(print_mode, project, doc_file, last = False, first = False):
+ if print_mode:
+ print('\n', file = doc_file)
+ else:
+ if not first:
+ print('[Top](#%s)' % project.lower().replace(' ', '-'), file = doc_file)
+ if not last:
+ print("\n---", file = doc_file)
+
+def pad(s, before, after = 0):
+ return ' ' * before + str(s) + ' ' * after
+
+def views(target, do_assemblies = None):
+ done_assemblies = []
+ #
+ # Make the target directory
+ #
+ top_dir = set_config(target)
+ target_dir = top_dir + 'assemblies'
+ deps_dir = top_dir + "deps"
+ bom_dir = top_dir + "bom"
+ if not os.path.isdir(target_dir):
+ os.makedirs(target_dir)
+ if not os.path.isdir(deps_dir):
+ os.makedirs(deps_dir)
+ times.read_times(target_dir)
+ #
+ # Find all the assemblies
+ #
+ assemblies = bom_to_assemblies(bom_dir)
+ for file in os.listdir(target_dir):
+ if file.endswith('.png'):
+ assembly = file[:-4].replace('_assembled', '_assembly')
+ if assembly.endswith('_tn'):
+ assembly = assembly[:-3]
+ if not assembly in assemblies:
+ print("Removing %s" % file)
+ os.remove(target_dir + '/' + file)
+ #
+ # Find all the scad files
+ #
+ main_blurb = None
+ lib_dir = os.environ['OPENSCADPATH'] + '/NopSCADlib'
+ for dir in [source_dir, lib_dir]:
+ for filename in os.listdir(dir):
+ if filename.endswith('.scad'):
+ #
+ # find any modules with names ending in _assembly
+ #
+ with open(dir + "/" + filename, "r") as f:
+ lines = f.readlines()
+ line_no = 0
+ for line in lines:
+ words = line.split()
+ if len(words) and words[0] == "module":
+ module = words[1].split('(')[0]
+ if is_assembly(module):
+ if module in assemblies:
+ #
+ # Scrape the assembly instructions
+ #
+ for ass in flat_bom:
+ if ass["name"] == module:
+ if not "blurb" in ass:
+ ass["blurb"] = blurb.scrape_module_blurb(lines[:line_no])
+ break
+ if not do_assemblies or module in do_assemblies:
+ #
+ # make a file to use the module
+ #
+ png_maker_name = 'png.scad'
+ with open(png_maker_name, "w") as f:
+ f.write("use <%s/%s>\n" % (dir, filename))
+ f.write("%s();\n" % module);
+ #
+ # Run openscad on the created file
+ #
+ dname = deps_name(deps_dir, filename)
+ for explode in [0, 1]:
+ png_name = target_dir + '/' + module + '.png'
+ if not explode:
+ png_name = png_name.replace('_assembly', '_assembled')
+ changed = check_deps(mtime(png_name), dname)
+ changed = times.check_have_time(changed, png_name)
+ if changed:
+ print(changed)
+ t = time.time()
+ openscad.run("-D$pose=1", "-D$explode=%d" % explode, "--projection=p", "--imgsize=4096,4096", "--autocenter", "--viewall", "-d", dname, "-o", png_name, png_maker_name);
+ times.add_time(png_name, t)
+ do_cmd(["magick", png_name, "-trim", "-resize", "1004x1004", "-bordercolor", "#ffffe5", "-border", "10", png_name])
+ tn_name = png_name.replace('.png', '_tn.png')
+ if mtime(png_name) > mtime(tn_name):
+ do_cmd(("magick "+ png_name + " -trim -resize 280x280 -background #ffffe5 -gravity Center -extent 280x280 -bordercolor #ffffe5 -border 10 " + tn_name).split())
+ os.remove(png_maker_name)
+ done_assemblies.append(module)
+ else:
+ if module == 'main_assembly':
+ main_blurb = blurb.scrape_module_blurb(lines[:line_no])
+ line_no += 1
+ times.print_times()
+ #
+ # Build the document
+ #
+ for print_mode in [True, False]:
+ doc_name = top_dir + "readme.md"
+ with open(doc_name, "wt") as doc_file:
+ #
+ # Title, description and picture
+ #
+ project = ' '.join(word[0].upper() + word[1:] for word in os.path.basename(os.getcwd()).split('_'))
+ print('# %s' % project, file = doc_file)
+ main_file = bom.find_scad_file('main_assembly')
+ if not main_file:
+ raise Exception("can't find source for main_assembly")
+ text = blurb.scrape_blurb(source_dir + '/' + main_file)
+ if len(text):
+ print(text, file = doc_file, end = '')
+ else:
+ if print_mode:
+ print(Fore.MAGENTA + "Missing project description" + Fore.WHITE)
+ print('![Main Assembly](assemblies/%s.png)\n' % flat_bom[-1]["name"].replace('_assembly', '_assembled'), file = doc_file)
+ eop(print_mode, project, doc_file, first = True)
+ #
+ # Build TOC
+ #
+ print('## Table of Contents', file = doc_file)
+ print('[TOC]\n', file = doc_file)
+ eop(print_mode, project, doc_file)
+ #
+ # Global BOM
+ #
+ print('## Parts list', file = doc_file)
+ vitamins = {}
+ printed = {}
+ routed = {}
+ for ass in flat_bom:
+ for v in ass["vitamins"]:
+ if v in vitamins:
+ vitamins[v] += ass["vitamins"][v]
+ else:
+ vitamins[v] = ass["vitamins"][v]
+ for p in ass["printed"]:
+ if p in printed:
+ printed[p] += ass["printed"][p]
+ else:
+ printed[p] = ass["printed"][p]
+ for ass in flat_bom:
+ name = ass["name"][:-9].replace('_', ' ').title().replace(' ',' ')
+ print('| %s ' % name, file = doc_file, end = '')
+ print('| TOTALS | |', file = doc_file)
+ print(('|--:' * len(flat_bom) + '|--:|:--|'), file = doc_file)
+ for v in sorted(vitamins, key = lambda s: s.split(":")[-1]):
+ for ass in flat_bom:
+ count = ass["vitamins"][v] if v in ass["vitamins"] else '.'
+ print('| %s ' % pad(count, 2, 1), file = doc_file, end = '')
+ print('| %s | %s |' % (pad(vitamins[v], 2, 1), pad(v.split(":")[1], 2)), file = doc_file)
+ print(('| ' * len(flat_bom) + '| | **3D Printed parts** |'), file = doc_file)
+ for p in sorted(printed):
+ for ass in flat_bom:
+ count = ass["printed"][p] if p in ass["printed"] else '.'
+ print('| %s ' % pad(count, 2, 1), file = doc_file, end = '')
+ print('| %s | %s |' % (pad(printed[p], 2, 1), pad(p, 3)), file = doc_file)
+ eop(print_mode, project, doc_file)
+ #
+ # Assembly instructions
+ #
+ for ass in flat_bom:
+ name = ass["name"]
+ cap_name = name.replace('_', ' ').title()
+ if ass["count"] > 1:
+ print("## %d x %s" % (ass["count"], cap_name), file = doc_file)
+ else:
+ print("## %s" % cap_name, file = doc_file)
+ vitamins = ass["vitamins"]
+ if vitamins:
+ print("### Vitamins", file = doc_file)
+ print("|Qty|Description|", file = doc_file)
+ print("|--:|:----------|", file = doc_file)
+ for v in vitamins:
+ print("|%d|%s|" % (vitamins[v], v.split(":")[1]), file = doc_file)
+ print("\n", file = doc_file)
+
+ printed = ass["printed"]
+ if printed:
+ print('### 3D Printed parts', file = doc_file)
+ i = 0
+ for p in printed:
+ print('%s %d x %s |' % ('\n|' if not (i % 3) else '', printed[p], p), file = doc_file, end = '')
+ if (i % 3) == 2 or i == len(printed) - 1:
+ n = (i % 3) + 1
+ print('\n|%s' % ('--|' * n), file = doc_file)
+ for j in range(n):
+ part = list(printed.keys())[i - n + j + 1]
+ print('| ![%s](stls/%s) %s' % (part, part.replace('.stl','.png'), '|\n' if j == j - 1 else ''), end = '', file = doc_file)
+ print('\n', file = doc_file)
+ i += 1
+ print('\n', file = doc_file)
+
+ routed = ass["routed"]
+ if routed:
+ print("### CNC Routed parts", file = doc_file)
+ i = 0
+ for r in routed:
+ print('%s %d x %s |' % ('\n|' if not (i % 3) else '', routed[r], r), file = doc_file, end = '')
+ if (i % 3) == 2 or i == len(routed) - 1:
+ n = (i % 3) + 1
+ print('\n|%s' % ('--|' * n), file = doc_file)
+ for j in range(n):
+ part = list(routed.keys())[i - n + j + 1]
+ print('| ![%s](dxfs/%s) %s' % (part, part.replace('.dxf','.png'), '|\n' if j == j - 1 else ''), end = '', file = doc_file)
+ print('\n', file = doc_file)
+ i += 1
+ print('\n', file = doc_file)
+
+ sub_assemblies = ass["assemblies"]
+ if sub_assemblies:
+ print("### Sub-assemblies", file = doc_file)
+ i = 0
+ for a in sub_assemblies:
+ print('%s %d x %s |' % ('\n|' if not (i % 3) else '', a["count"], a["name"]), file = doc_file, end = '')
+ if (i % 3) == 2 or i == len(sub_assemblies) - 1:
+ n = (i % 3) + 1
+ print('\n|%s' % ('--|' * n), file = doc_file)
+ for j in range(n):
+ a = sub_assemblies[i - n + j + 1]["name"].replace('_assembly', '_assembled')
+ print('| ![%s](assemblies/%s) %s' % (a, a + '_tn.png', '|\n' if j == j - 1 else ''), end = '', file = doc_file)
+ print('\n', file = doc_file)
+ i += 1
+ print('\n', file = doc_file)
+
+ small = not ass["big"]
+ suffix = '_tn.png' if small else '.png'
+ print('### Assembly instructions', file = doc_file)
+ print('![%s](assemblies/%s)\n' % (name, name + suffix), file = doc_file)
+
+ if "blurb" in ass and ass["blurb"]:
+ print(ass["blurb"], file = doc_file)
+ else:
+ if print_mode:
+ print(Fore.MAGENTA + "Missing instructions for %s" % name, Fore.WHITE)
+
+ name = name.replace('_assembly', '_assembled')
+ print('![%s](assemblies/%s)\n' % (name, name + suffix), file = doc_file)
+ eop(print_mode, project, doc_file, last = ass == flat_bom[-1] and not main_blurb)
+ #
+ # If main module is suppressed print any blurb here
+ #
+ if main_blurb:
+ print(main_blurb, file = doc_file)
+ eop(print_mode, project, doc_file, last = True)
+ #
+ # Convert to HTML
+ #
+ html_name = "printme.html" if print_mode else "readme.html"
+ with open(top_dir + html_name, "wt") as html_file:
+ do_cmd(("python -m markdown -x tables -x toc -x sane_lists " + doc_name).split(), html_file)
+ #
+ # Spell check
+ #
+ do_cmd('codespell -L od readme.md'.split())
+ #
+ # List the ones we didn't find
+ #
+ missing = set()
+ for assembly in assemblies + (do_assemblies if do_assemblies else []):
+ if assembly not in done_assemblies:
+ missing.add(assembly)
+ if missing:
+ for assembly in missing:
+ print(Fore.MAGENTA + "Could not find a module called", assembly, Fore.WHITE)
+ sys.exit(1)
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1 and sys.argv[1][-9:] != "_assembly":
+ target, assemblies = sys.argv[1], sys.argv[2:]
+ else:
+ target, assemblies = None, sys.argv[1:]
+
+ views(target, assemblies)
diff --git a/socket_box.scad b/socket_box.scad
new file mode 100644
index 0000000..aa02bc9
--- /dev/null
+++ b/socket_box.scad
@@ -0,0 +1,127 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! UK 13A socket and printed backbox with earth terminal for the panel it is mounted on.
+//
+include
+include
+include
+include
+include
+
+box_height = 19;
+base_thickness = 2;
+wall = 3.5;
+
+cable_d = 7;
+cable_z = cable_d / 2 + base_thickness + 1;
+function cable_y(type) = -mains_socket_depth(type) / 2;
+cable_x = 0;
+
+earth = M3_ringterm;
+earth_screw = ringterm_screw(earth);
+
+height = base_thickness + box_height;
+function socket_box_depth() = height; //! Outside depth of the backbox
+
+module socket_box(type) { //! Generate STL of the backbox for the specified socket
+ stl(str("socket_box_",type[0]));
+
+ screw = mains_socket_screw(type);
+ screw_clearance_radius = screw_clearance_radius(screw);
+
+ insert = screw_insert(screw);
+ insert_length = insert_length(insert);
+ insert_boss = mains_socket_insert_boss(type);
+ insert_hole_radius = insert_hole_radius(insert);
+
+ difference() {
+ linear_extrude(height = height, convexity = 5)
+ face_plate(type);
+
+ difference() {
+ translate_z(base_thickness)
+ linear_extrude(height = height, convexity = 5)
+ offset(-wall) offset(1) face_plate(type);
+
+ for(side = [-1, 1])
+ hull()
+ for(x = [1, 2])
+ translate([side * mains_socket_pitch(type) / x, 0])
+ cylinder(d = insert_boss, h = 100);
+ }
+ //
+ // Socket holes
+ //
+ translate_z(height)
+ mains_socket_hole_positions(type) {
+ poly_cylinder(r = screw_clearance_radius, h = 2 * box_height, center = true);
+
+ poly_cylinder(r = insert_hole_radius, h = 2 * insert_length, center = true);
+ }
+ //
+ // Cable hole
+ //
+ translate([cable_x, cable_y(type), cable_z])
+ rotate([90, 0, 0])
+ teardrop_plus(r = cable_d / 2, h = 30);
+ }
+}
+
+module socket_box_MKLOGIC_stl() socket_box(MKLOGIC);
+module socket_box_Contactum_stl() socket_box(Contactum);
+
+module socket_box_assembly(type) //! The box with inserts fitted
+assembly(str("socket_box_", type[0])) {
+ screw = mains_socket_screw(type);
+ insert = screw_insert(screw);
+
+ color("lime") render() socket_box(type);
+
+ mains_socket_hole_positions(type)
+ translate_z(height)
+ insert(insert);
+
+}
+
+//! * Place two inserts into the holes in the lugs and press them home with a soldering iron with a tapered bit heated to 200°C.
+module socket_box_MKLOGIC_assembly() socket_box_assembly(MKLOGIC);
+
+module socket_box_fastened_assembly(type, thickness) { //! The socket and backbox on each side of the specified panel thickness
+ screw = mains_socket_screw(type);
+ insert = screw_insert(screw);
+ screw_length = screw_longer_than(mains_socket_height(type) + thickness + insert_length(insert));
+
+ explode(-50)
+ translate_z(-height - thickness)
+ socket_box_assembly(type);
+
+ explode(50, true) {
+ mains_socket(type);
+
+ mains_socket_hole_positions(type)
+ translate_z(mains_socket_height(type))
+ screw(screw, screw_length);
+ }
+
+ mains_socket_earth_position(type)
+ rotate(-90)
+ ring_terminal_assembly(earth, thickness);
+}
diff --git a/strap_handle.scad b/strap_handle.scad
new file mode 100644
index 0000000..de20184
--- /dev/null
+++ b/strap_handle.scad
@@ -0,0 +1,189 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Retracting strap handle. Print the strap with flexible filament. Shown with default dimensions but can
+//! be fully customised by passing a list of properties.
+//
+include
+include
+include
+
+strap = [18, 2, M3_pan_screw, 3, 25];
+function strap() = strap;
+
+wall = 2;
+clearance = 0.5;
+step = 0.3;
+overlap = 3;
+panel_clearance = 0.25;
+counterbore = 1;
+
+function strap_width(type = strap) = type[0]; //! Width of strap
+function strap_thickness(type = strap) = type[1]; //! Thickness of strap
+function strap_screw(type = strap) = type[2]; //! Screw type
+function strap_panel(type = strap) = type[3]; //! Panel thickness
+function strap_extension(type = strap) = type[4]; //! How much length of the strap that can pull out
+
+function strap_insert(type) = screw_insert(strap_screw(type)); //! The insert type
+function strap_key(type) = strap_panel(type) - panel_clearance;
+function strap_height(type) = wall + max(insert_length(strap_insert(type)) - strap_key(type), strap_thickness(type) + clearance); //! Height of the ends
+function strap_end_width(type = strap) = strap_width(type) + 2 * (wall + clearance); //! Width of the ends
+function strap_boss_r(type) = wall + insert_hole_radius(strap_insert(type));
+
+function strap_min_width(type) = (strap_width(type) - 2 * (strap_boss_r(type) + clearance)) / 2;
+
+module strap_boss_shape(type)
+ hull() {
+ r = strap_boss_r(type);
+
+ circle4n(r);
+
+ translate([r / 2, 0])
+ rounded_square([r, 2 * r], r = 1);
+ }
+
+module strap_screw_positions(length, type = strap) //! Place children at the screw positions
+ for(end = [-1, 1])
+ translate([end * (length / 2 - wall - 2 * clearance - strap_min_width(type) - strap_boss_r(type) - strap_extension(type) / 2), 0])
+ rotate(end * 90 + 90)
+ children();
+
+module strap_holes(length, type = strap, h = 100) //! The panel cut outs
+ extrude_if(h)
+ strap_screw_positions(length, type)
+ offset(cnc_bit_r + 0.05)
+ offset(-step - cnc_bit_r)
+ strap_boss_shape(type);
+
+module strap(length, type = strap) { //! Generate the STL for the rubber strap
+ stl("strap");
+
+ len = length - 2 * (wall + clearance);
+ w = strap_width(type);
+
+ linear_extrude(height = strap_thickness(type), convexity = 3)
+ difference() {
+ rounded_square([len, w], w / 2 - eps);
+
+ for(end = [-1, 1])
+ translate([end * (len / 2 - strap_min_width(type) - strap_boss_r(type) - clearance), 0])
+ rotate(end * 90 + 90)
+ hull() {
+ offset(clearance)
+ strap_boss_shape(type);
+
+ translate([strap_extension(type) / 2, 0])
+ offset(clearance)
+ strap_boss_shape(type);
+ }
+ }
+}
+
+module strap_end(type = strap) { //! Generate the STL for end piece
+ stl("strap_end");
+
+ z1 = strap_height(type) - strap_thickness(type) - clearance;
+ z2 = strap_height(type) + strap_key(type);
+ r1 = strap_boss_r(type) - 1;
+
+ module outer()
+ hull() {
+ translate([0, -strap_end_width(type) / 2])
+ square([strap_boss_r(type) + overlap, strap_end_width(type)]);
+
+ translate([-strap_extension(type) / 2, 0])
+ circle(d = strap_end_width(type));
+ }
+
+ module with_hole()
+ difference() {
+ children();
+
+ circle(r1);
+ }
+
+ union() {
+ linear_extrude(height = z1)
+ with_hole()
+ outer();
+
+ translate_z(z1)
+ linear_extrude(height = strap_height(type) - z1)
+ difference() {
+ outer();
+
+ hull() {
+ translate([0, -strap_width(type) / 2 - clearance])
+ square([strap_boss_r(type) + overlap, strap_width(type) + 2 * clearance]);
+
+ translate([-strap_extension(type) / 2, 0])
+ circle(d = strap_width(type) + 2 * clearance);
+ }
+ }
+
+ linear_extrude(height = strap_height(type) - layer_height)
+ with_hole()
+ strap_boss_shape(type);
+
+ linear_extrude(height = z2)
+ with_hole()
+ offset(cnc_bit_r)
+ offset(-step - cnc_bit_r)
+ strap_boss_shape(type);
+
+ render() difference() {
+ cylinder(r = r1 + eps, h = z2);
+
+ translate_z(z2)
+ insert_hole(strap_insert(type), counterbore);
+ }
+ }
+}
+//
+//! * Place the insert into the hole and push home with a soldering iron with a tapered bit heated to 200°C.
+//
+module strap_end_assembly(type = strap)
+assembly("strap_end") {
+ color(pp1_colour)
+ strap_end(type);
+
+ translate_z(strap_height(type) + strap_key(type))
+ insert(strap_insert(type));
+}
+
+module strap_assembly(length, type = strap) { //! Assembly with screws in place
+ screw = strap_screw(type);
+ washer = screw_washer(screw);
+ penny = penny_washer(washer);
+ insert = strap_insert(type);
+
+ screw_length = screw_shorter_than(washer_thickness(washer) + washer_thickness(penny) + insert_length(insert) + panel_clearance + counterbore);
+
+ color(pp4_colour) strap(length, type);
+
+ strap_screw_positions(length, type)
+ translate_z(strap_height(type))
+ vflip() {
+ explode(-50) strap_end_assembly(type);
+
+ translate_z(strap_height(type) + strap_panel(type))
+ screw_and_washer(screw, screw_length, true, true);
+ }
+}
diff --git a/tests/annotation.scad b/tests/annotation.scad
new file mode 100644
index 0000000..37d3c1b
--- /dev/null
+++ b/tests/annotation.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/annotation.scad>
+
+module annotations() {
+ arrow();
+
+ translate_z(21)
+ label("Text", halign = "center", valign = "bottom");
+}
+
+if($preview)
+ annotations();
diff --git a/tests/ball_bearings.scad b/tests/ball_bearings.scad
new file mode 100644
index 0000000..4b5f356
--- /dev/null
+++ b/tests/ball_bearings.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+use <../utils/layout.scad>
+
+include <../vitamins/ball_bearings.scad>
+
+module ball_bearings()
+ layout([for(b = ball_bearings) bb_diameter(b)])
+ ball_bearing(ball_bearings[$i])
+ bearing_ball(3);
+
+if($preview)
+ ball_bearings();
diff --git a/tests/batteries.scad b/tests/batteries.scad
new file mode 100644
index 0000000..a7d2b5f
--- /dev/null
+++ b/tests/batteries.scad
@@ -0,0 +1,41 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/springs.scad>
+include <../vitamins/batteries.scad>
+
+module batteries()
+ layout([for(b = batteries) battery_diameter(b)], 5) let(battery=batteries[$i]) {
+ battery = batteries[$i];
+ rotate(-135) // To show Lumintop USB socket and LEDs
+ battery(battery);
+
+ contact = battery_contact(battery);
+ translate_z(battery_length(battery) / 2 + contact_pos(contact).x)
+ rotate([0, 180, 0])
+ battery_contact(contact);
+
+ translate_z(-battery_length(battery) / 2 - contact_neg(contact).x)
+ battery_contact(contact, false);
+ }
+
+if($preview)
+ batteries();
diff --git a/tests/belts.scad b/tests/belts.scad
new file mode 100644
index 0000000..d7d8d40
--- /dev/null
+++ b/tests/belts.scad
@@ -0,0 +1,74 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+include <../vitamins/belts.scad>
+include <../vitamins/screws.scad>
+include <../vitamins/inserts.scad>
+include <../vitamins/pulleys.scad>
+use <../utils/layout.scad>
+
+module belt_test() {
+ p1 = [75, -50];
+ p2 = [-75, -50];
+ p3 = [-75, 100];
+ p4 = [75, 100];
+
+ p5 = [75 - pulley_pr(GT2x20ob_pulley) - pulley_pr(GT2x16_plain_idler), -pulley_pr(GT2x16_plain_idler)];
+ p6 = [-75 + pulley_pr(GT2x20ob_pulley) + pulley_pr(GT2x16_plain_idler), -pulley_pr(GT2x16_plain_idler)];
+
+ translate(p1) pulley_assembly(GT2x20ob_pulley);
+ translate(p2) pulley_assembly(GT2x20ob_pulley);
+ translate(p3) pulley_assembly(GT2x20_toothed_idler);
+ translate(p4) pulley_assembly(GT2x20_toothed_idler);
+
+ translate(p5) {
+ pulley = GT2x16_plain_idler;
+ screw = find_screw(hs_cs_cap, pulley_bore(pulley));
+ insert = screw_insert(screw);
+
+ pulley_assembly(pulley);
+ translate_z(pulley_height(pulley) + pulley_offset(pulley) + screw_head_depth(screw, pulley_bore(pulley)))
+ screw(screw, 20);
+
+ translate_z(pulley_offset(pulley) - insert_length(insert))
+ vflip()
+ insert(insert);
+
+ }
+ translate(p6) pulley_assembly(GT2x16_plain_idler);
+
+ path = [ [p1.x, p1.y, pulley_pr(GT2x20ob_pulley)],
+ [p5.x, p5.y, -pulley_pr(GT2x16_plain_idler)],
+ [p6.x, p6.y, -pulley_pr(GT2x16_plain_idler)],
+ [p2.x, p2.y, pulley_pr(GT2x20ob_pulley)],
+ [p3.x, p3.y, pulley_pr(GT2x20ob_pulley)],
+ [p4.x, p4.y, pulley_pr(GT2x20ob_pulley)]
+ ];
+ belt = GT2x6;
+ belt(belt, path, 80, [0, belt_pitch_height(belt) - belt_thickness(belt) / 2]);
+
+ translate([-25, 0])
+ layout([for(b = belts) belt_width(b)], 10)
+ rotate([0, 90, 0])
+ belt(belts[$i], [[0, 0, 20], [0, 1, 20]]);
+}
+
+if($preview)
+ belt_test();
diff --git a/tests/bezier.scad b/tests/bezier.scad
new file mode 100644
index 0000000..7a7cd69
--- /dev/null
+++ b/tests/bezier.scad
@@ -0,0 +1,83 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/bezier.scad>
+use <../utils/sweep.scad>
+use <../utils/annotation.scad>
+
+function find_min_z(path, i = 1, best_i = 0) =
+ i >= len(path) ? best_i
+ : find_min_z(path, i + 1, path[i].z < path[best_i].z ? i : best_i);
+
+module beziers() {
+ //
+ // Make Bezier control points to give specified length
+ //
+ control_points = adjust_bezier_length([[0, 0, 100], [0, 0, 0], [200, 0, 20], [100, -100, 50]], 250);
+ //
+ // Draw control points
+ //
+ color("green")
+ for(p = control_points)
+ translate(p)
+ sphere(1);
+ //
+ // Lines joining the control points
+ //
+ color("red")
+ sweep(control_points, circle_points(0.5, $fn = 64));
+ //
+ // Bezier curve path from control points
+ //
+ curve = bezier_path(control_points);
+ //
+ // Draw the curve
+ //
+ sweep(curve, circle_points(2, $fn = 64));
+ //
+ // Length computed from control points
+ //
+ length = bezier_length(control_points);
+ //
+ // Length computed from curve
+ //
+ length2 = path_length(curve);
+ assert(str(length) == str(length2), str(length, " ", length2));
+ //
+ // Minimum Z
+ //
+ min_z = bezier_min_z(control_points);
+ i = find_min_z(curve);
+ assert(str(min_z) == str(curve[i].z));
+
+ color("blue") {
+ translate(curve[i] + [0, 0, 2])
+ arrow();
+
+ translate(curve[i] - [0, 0, 2])
+ vflip()
+ arrow();
+ }
+
+ translate(control_points[1] - [0, 0, 2])
+ label(str("bezier_length = ", length, ", bezier_min_z = ", bezier_min_z(curve)), valign = "top");
+}
+
+if($preview)
+ beziers();
diff --git a/tests/blowers.scad b/tests/blowers.scad
new file mode 100644
index 0000000..7e28072
--- /dev/null
+++ b/tests/blowers.scad
@@ -0,0 +1,41 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/blowers.scad>
+
+module blowers()
+ layout([for(b = blowers) blower_width(b)], 10, true) let(b = blowers[$i]){
+ screw = blower_screw(b);
+ washer = screw_washer(screw);
+ h = blower_lug(b);
+
+ blower(b);
+
+ blower_hole_positions(b)
+ translate_z(h)
+ screw_and_washer(screw, screw_longer_than(h + washer_thickness(washer) + 5));
+
+
+ }
+
+if($preview)
+ blowers();
diff --git a/tests/bom.scad b/tests/bom.scad
new file mode 100644
index 0000000..f6da423
--- /dev/null
+++ b/tests/bom.scad
@@ -0,0 +1,108 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! BOM and assembly demonstration
+//
+include <../core.scad>
+include <../vitamins/screws.scad>
+include <../vitamins/inserts.scad>
+include <../vitamins/sheets.scad>
+$explode = 1; // Normally set on the command line when generating assembly views with views.py
+
+screw = M3_cap_screw;
+sheet = PMMA3;
+height = 10;
+
+insert = screw_insert(screw);
+washer = screw_washer(screw);
+
+module widget(thickness) {
+ vitamin(str("widget(", thickness, "): Rivit like thing for ", thickness, "mm sheets"));
+ t = 1;
+ color("silver") {
+ cylinder(d = 3, h = thickness + 2 * eps, center = true);
+
+ for(end = [-1, 1])
+ translate_z(end * (thickness / 2 + t / 2 + eps))
+ cylinder(d = 4, h = t, center = true);
+ }
+}
+
+module widgit_stl() {
+ stl("widget");
+
+ union() {
+ rounded_rectangle([30, 30, 3], 2);
+
+ render() insert_boss(insert, height, 2.2);
+ }
+}
+
+module widgit_dxf() {
+ dxf("widget");
+
+ difference() {
+ sheet_2D(sheet, 20, 20, 1);
+
+ drill(screw_clearance_radius(screw), 0);
+ }
+}
+
+//! * Push the insert into the base with a soldering iron heated to 200°C
+module widgit_base_assembly()
+assembly("widgit_base") {
+ color(pp1_colour)
+ widgit_stl();
+
+ translate_z(height)
+ insert(insert);
+}
+
+//! * Magically insert the widget into the acrylic sheet
+module widget_top_assembly()
+assembly("widget_top") {
+ translate([-5, 5])
+ widget(sheet_thickness(sheet));
+
+ render_2D_sheet(sheet) // Must be last because it is transparent
+ widgit_dxf();
+}
+
+//! * Screw the two assemblies together
+module widgit_assembly()
+assembly("wigdit") {
+
+ widgit_base_assembly(); // Note this is not exloded because it is sub-assembly
+
+ translate_z(height) {
+ translate_z(sheet_thickness(sheet))
+ screw_and_washer(screw, screw_longer_than(sheet_thickness(sheet) + 2 * washer_thickness(washer) + 3), true);
+
+ explode(5)
+ translate_z(sheet_thickness(sheet) / 2 + eps)
+ widget_top_assembly();
+ }
+}
+
+module boms() {
+ widgit_assembly();
+}
+
+boms();
diff --git a/tests/box.scad b/tests/box.scad
new file mode 100644
index 0000000..531b06e
--- /dev/null
+++ b/tests/box.scad
@@ -0,0 +1,54 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/sheets.scad>
+include <../vitamins/inserts.scad>
+
+use <../box.scad>
+
+box = [M3_dome_screw, 3, DiBond, PMMA3, DiBond6, true, 150, 100, 70];
+
+include <../box_assembly.scad>
+
+module box_assembly() _box_assembly(box);
+
+module box_test() {
+ translate_z(box_height(box) / 2 + box_bezel_height(box, true))
+ box_assembly();
+
+ rows = 3;
+ cols = 3;
+ gap = 30;
+
+ x_pitch = (box_width(box) + 2 * box_outset(box)) / cols + gap;
+ y_pitch = (box_depth(box) + 2 * box_outset(box)) / rows + gap;
+ for(x = [0 : cols - 1], y = [0 : rows - 1])
+ translate([(x - cols / 2) * x_pitch + gap / 2, (y - rows / 2) * y_pitch + gap / 2])
+ color((x + y) % 2 ? pp1_colour : pp2_colour) box_bezel_section(box, true, rows, cols, x, y);
+
+ translate([-cols / 2 * x_pitch - 20, 0])
+ for(i = [0 : 2])
+ translate([0, (i - 1) * 20, 0])
+ color(i % 2 ? pp1_colour : pp2_colour) box_corner_profile_section(box, i, 3);
+}
+
+if($preview)
+ box_test();
diff --git a/tests/bulldogs.scad b/tests/bulldogs.scad
new file mode 100644
index 0000000..13b2d18
--- /dev/null
+++ b/tests/bulldogs.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/bulldogs.scad>
+
+module bulldogs()
+ layout([for(b = bulldogs) bulldog_depth(b)], 5, true)
+ bulldog(bulldogs[$i]);
+
+if($preview)
+ bulldogs();
diff --git a/tests/butt_box.scad b/tests/butt_box.scad
new file mode 100644
index 0000000..1a8d419
--- /dev/null
+++ b/tests/butt_box.scad
@@ -0,0 +1,66 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/sheets.scad>
+include <../vitamins/inserts.scad>
+
+include <../butt_box.scad>
+
+$explode = 0;
+
+box = [M3_dome_screw, DiBond, DiBond6, PMMA3, 250, 400, 300, 120];
+
+module bbox_assembly() _bbox_assembly(box);
+
+module bbox_test() {
+ translate_z(bbox_height(box) / 2)
+ bbox_assembly();
+}
+
+if($preview)
+ bbox_test();
+else {
+ gap = 2;
+ bb = sheet_thickness(bbox_base_sheet(box));
+ bt = sheet_thickness(bbox_top_sheet(box));
+ h = bbox_height(box) + bb;
+ h2 = h + bt;
+
+ bbox_base_blank(box);
+
+ translate([bbox_width(box) / 2 + gap + h / 2 - bb / 2, 0])
+ rotate(90)
+ bbox_right_blank(box);
+
+ translate([-bbox_width(box) / 2 - gap - h / 2 + bb / 2, 0])
+ rotate(-90)
+ bbox_left_blank(box);
+
+ translate([0, bbox_depth(box) / 2 + gap + h / 2 - bb / 2])
+ rotate(180)
+ bbox_back_blank(box);
+
+ translate([0, -bbox_depth(box) / 2 - gap - h2 / 2 - (bt - bb) / 2])
+ bbox_front_blank(box);
+
+ translate([0, -bbox_depth(box) / 2 - gap - h2 - gap - bbox_depth(box) / 2 - sheet_thickness(bbox_sheets(box))])
+ bbox_top_blank(box);
+}
diff --git a/tests/buttons.scad b/tests/buttons.scad
new file mode 100644
index 0000000..8445e43
--- /dev/null
+++ b/tests/buttons.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/buttons.scad>
+
+module buttons()
+ layout([for(b = buttons) square_button_width(b)], 5)
+ square_button(buttons[$i]);
+
+if($preview)
+ buttons();
diff --git a/tests/cable_grommets.scad b/tests/cable_grommets.scad
new file mode 100644
index 0000000..e0faa28
--- /dev/null
+++ b/tests/cable_grommets.scad
@@ -0,0 +1,36 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+use <../cable_grommets.scad>
+
+module cable_grommets() {
+ rotate(90)
+ color(pp1_colour) ribbon_grommet(20, 3);
+
+ translate([20, 0])
+ round_grommet_assembly(6, 3);
+
+ translate([40, 0])
+ rotate(90)
+ color(pp1_colour) mouse_grommet(5, 3);
+}
+
+if($preview)
+ cable_grommets();
diff --git a/tests/cable_strips.scad b/tests/cable_strips.scad
new file mode 100644
index 0000000..18b214c
--- /dev/null
+++ b/tests/cable_strips.scad
@@ -0,0 +1,38 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+use <../vitamins/cable_strip.scad>
+
+module cable_strips() {
+ depth = 50;
+ rotate(-90)
+ for(pos = [-100, 0, 100]) {
+ bezier_cable_strip(ways = 20, depth = depth, length = 150, travel = 100, pos = pos, below = 100, extra = 10);
+
+ translate([0, depth * 2])
+ rotate([0, -90, 0])
+ cable_strip(ways =20, depth = depth / 2, travel = 100, x = pos, extra = 30);
+
+
+ }
+}
+
+if($preview)
+ cable_strips();
diff --git a/tests/carriers.scad b/tests/carriers.scad
new file mode 100644
index 0000000..db4e1ce
--- /dev/null
+++ b/tests/carriers.scad
@@ -0,0 +1,25 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../carriers.scad>
+
+module carriers()
+ color(pp1_colour) ESP12F_carrier_stl();
+
+carriers();
diff --git a/tests/clip.scad b/tests/clip.scad
new file mode 100644
index 0000000..28f62a1
--- /dev/null
+++ b/tests/clip.scad
@@ -0,0 +1,26 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../core.scad>
+
+module clips() {
+ clip(xmin = 0, ymin = 0, zmin = 0, zmax = 40) sphere(50);
+}
+
+clips();
diff --git a/tests/components.scad b/tests/components.scad
new file mode 100644
index 0000000..054cf0d
--- /dev/null
+++ b/tests/components.scad
@@ -0,0 +1,57 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/components.scad>
+
+module resistors()
+ layout([for(r = resistors) resistor_diameter(r)], 10)
+ resistor(resistors[$i]);
+
+module al_clad_resistors()
+ layout([for(a = al_clad_resistors) al_clad_width(a)])
+ rotate(90)
+ al_clad_resistor_assembly(al_clad_resistors[$i], 4.7)
+ screw(al_clad_hole(al_clad_resistors[$i]) > 3 ? M3_pan_screw : M2p5_pan_screw, 16);
+
+
+module thermal_cutouts()
+ layout([for(t = thermal_cutouts) tc_length(t)])
+ thermal_cutout(thermal_cutouts[$i]);
+
+module components() {
+ resistors();
+
+ translate([0, 50])
+ TO220("Generic TO220 package");
+
+ translate([30, 50])
+ panel_USBA();
+
+ translate([0,80])
+ thermal_cutouts();
+
+ translate([0, 130])
+ al_clad_resistors();
+}
+
+if($preview)
+ components();
diff --git a/tests/corner_block.scad b/tests/corner_block.scad
new file mode 100644
index 0000000..6972430
--- /dev/null
+++ b/tests/corner_block.scad
@@ -0,0 +1,37 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../corner_block.scad>
+
+include <../vitamins/screws.scad>
+
+screws = [M2_cap_screw, M2p5_pan_screw, M3_dome_screw, M4_dome_screw];
+
+module do_corner_block(screw)
+ if($preview)
+ fastened_corner_block_assembly(3, screw = screw);
+ else
+ corner_block(screw);
+
+module corner_blocks()
+ for(i = [0 : len(screws) - 1])
+ translate([i * 30, 0])
+ do_corner_block(screws[i]);
+
+corner_blocks();
diff --git a/tests/d_connectors.scad b/tests/d_connectors.scad
new file mode 100644
index 0000000..3398208
--- /dev/null
+++ b/tests/d_connectors.scad
@@ -0,0 +1,38 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/d_connectors.scad>
+
+module d_connectors()
+ for(socket = [false, true])
+ translate([socket ? len(d_connectors) * (d_flange_width(d_connectors[0]) + 10) : 0, 0])
+ layout([for(d = d_connectors) d_flange_width(d)], 10) let(d = d_connectors[$i])
+ rotate(90) {
+ d_plug(d, socket, pcb = $i == 2, idc = $i == 1);
+
+ if(socket)
+ translate_z(d_flange_thickness(d))
+ d_connector_holes(d)
+ d_pillar();
+ }
+
+if($preview)
+ d_connectors();
diff --git a/tests/displays.scad b/tests/displays.scad
new file mode 100644
index 0000000..7f4e129
--- /dev/null
+++ b/tests/displays.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+use <../utils/layout.scad>
+
+include <../vitamins/displays.scad>
+use <../vitamins/pcb.scad>
+
+module displays()
+ layout([for(d = displays) pcb_length(display_pcb(d))], 10)
+ display(displays[$i]);
+
+if($preview)
+ displays();
diff --git a/tests/dogbones.scad b/tests/dogbones.scad
new file mode 100644
index 0000000..44b64ca
--- /dev/null
+++ b/tests/dogbones.scad
@@ -0,0 +1,34 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../global_defs.scad>
+use <../utils/dogbones.scad>
+
+module dogbones() {
+ #linear_extrude(height = eps)
+ dogbone_square([10, 20]);
+
+ #translate([15, 0])
+ dogbone_rectangle([10, 20, 5], center = false);
+
+ sq = 3;
+ translate([-5 + sq / 2 + eps, -10 + sq / 2 + eps])
+ %cube([sq, sq, 1], center = true);
+}
+
+dogbones();
diff --git a/tests/door_hinge.scad b/tests/door_hinge.scad
new file mode 100644
index 0000000..ace09fb
--- /dev/null
+++ b/tests/door_hinge.scad
@@ -0,0 +1,57 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../door_hinge.scad>
+
+include <../vitamins/sheets.scad>
+use <../vitamins/screw.scad>
+
+door_w = 50;
+door_h = 50;
+sheet = PMMA6;
+
+module door_hinges() {
+ translate([door_hinge_stat_length() / 2 - door_hinge_pin_x(), 0])
+ if($preview) {
+ for(side = [-1, 1])
+ translate_z(side * door_h / 2) {
+ door_hinge_assembly(side > 0, sheet_thickness(sheet));
+
+ door_hinge_static_assembly(side > 0, 3);
+ }
+
+ translate([door_w / 2 + eps, door_hinge_pin_y() - eps])
+ rotate([90, 0, 0])
+ render_2D_sheet(sheet)
+ difference() {
+ sheet_2D(sheet, door_w, door_h, 3);
+
+ for(z =[-1, 1])
+ translate([-door_w / 2, z * door_h / 2])
+ door_hinge_hole_positions(z)
+ drill(screw_pilot_hole(door_hinge_screw()), 0);
+ }
+ translate([door_w, 0])
+ children();
+ }
+ else
+ door_hinge_parts_stl();
+}
+
+door_hinges();
diff --git a/tests/door_latch.scad b/tests/door_latch.scad
new file mode 100644
index 0000000..9ee24dc
--- /dev/null
+++ b/tests/door_latch.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../door_latch.scad>
+
+module door_latches()
+ translate([door_latch_offset(), 0])
+ if($preview)
+ door_latch_assembly(3);
+ else
+ door_latch_stl();
+
+door_latches();
diff --git a/tests/fan_guard.scad b/tests/fan_guard.scad
new file mode 100644
index 0000000..4ac5c8b
--- /dev/null
+++ b/tests/fan_guard.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../fan_guard.scad>
+include <../vitamins/fans.scad>
+
+module fan_guards()
+ layout([for(f = fans) fan_width(f)], 10)
+ color(pp1_colour) fan_guard(fans[$i], spokes = fan_width(fans[$i]) > 40 ? 8 : 4);
+
+fan_guards();
diff --git a/tests/fans.scad b/tests/fans.scad
new file mode 100644
index 0000000..539beb1
--- /dev/null
+++ b/tests/fans.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../fan_guard.scad>
+
+include <../vitamins/fans.scad>
+
+module fans()
+ layout([for(f = fans) fan_width(f)], 10)
+ fan_assembly(fans[$i], 3 + fan_guard_thickness(), true);
+
+if($preview)
+ fans();
diff --git a/tests/fillet.scad b/tests/fillet.scad
new file mode 100644
index 0000000..4a5d0bb
--- /dev/null
+++ b/tests/fillet.scad
@@ -0,0 +1,26 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../global_defs.scad>
+use <../utils/fillet.scad>
+
+module fillets() {
+ fillet(3, 25);
+}
+
+fillets();
diff --git a/tests/fixing_block.scad b/tests/fixing_block.scad
new file mode 100644
index 0000000..145bae6
--- /dev/null
+++ b/tests/fixing_block.scad
@@ -0,0 +1,37 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../fixing_block.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+
+screws = [M2_cap_screw, M2p5_pan_screw, M3_dome_screw, M4_dome_screw];
+
+module fixing_block_test(screw)
+ if($preview)
+ fastened_fixing_block_assembly(3, screw = screw);
+ else
+ fixing_block(screw);
+
+module fixing_blocks()
+ layout([for(s = screws) fixing_block_width(s)], 5)
+ fixing_block_test(screws[$i]);
+
+fixing_blocks();
diff --git a/tests/foot.scad b/tests/foot.scad
new file mode 100644
index 0000000..39e8bb8
--- /dev/null
+++ b/tests/foot.scad
@@ -0,0 +1,37 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../foot.scad>
+
+module feet()
+ if($preview) {
+ translate([50, 0])
+ foot_assembly(3);
+
+ translate([foot_diameter(insert_foot()) / 2, 0])
+ fastened_insert_foot_assembly(3);
+ }
+ else {
+ translate([50, 0])
+ foot();
+
+ insert_foot();
+ }
+
+feet();
diff --git a/tests/fuseholder.scad b/tests/fuseholder.scad
new file mode 100644
index 0000000..313b852
--- /dev/null
+++ b/tests/fuseholder.scad
@@ -0,0 +1,27 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+use <../vitamins/fuseholder.scad>
+
+module fuseholders()
+ fuseholder(6);
+
+if($preview)
+ fuseholders();
diff --git a/tests/global.scad b/tests/global.scad
new file mode 100644
index 0000000..e987814
--- /dev/null
+++ b/tests/global.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../core.scad>
+
+module globals() {
+ linear_extrude(height = eps) {
+ semi_circle(r = 10);
+
+ translate([30, 0])
+ ellipse(15, 7);
+ }
+}
+
+rotate([70, 0, 315]) globals();
diff --git a/tests/handle.scad b/tests/handle.scad
new file mode 100644
index 0000000..42edac4
--- /dev/null
+++ b/tests/handle.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../handle.scad>
+
+module handle()
+ translate([handle_length() / 2, 0])
+ if($preview)
+ handle_fastened_assembly(3);
+ else
+ handle_stl();
+
+handle();
diff --git a/tests/hanging_hole.scad b/tests/hanging_hole.scad
new file mode 100644
index 0000000..0490908
--- /dev/null
+++ b/tests/hanging_hole.scad
@@ -0,0 +1,28 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../global_defs.scad>
+use <../utils/hanging_hole.scad>
+
+module hanging_holes() {
+ rotate(45)
+ #hanging_hole(z = 10, ir = 3, h = 30)
+ circle(r = 10);
+}
+
+hanging_holes();
diff --git a/tests/hot_ends.scad b/tests/hot_ends.scad
new file mode 100644
index 0000000..d1d7b33
--- /dev/null
+++ b/tests/hot_ends.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/hot_ends.scad>
+
+module hot_ends()
+ layout([for(h = hot_ends) 40])
+ translate([-20, 0])
+ rotate(90)
+ hot_end(hot_ends[$i], 3);
+
+if($preview)
+ hot_ends();
diff --git a/tests/iecs.scad b/tests/iecs.scad
new file mode 100644
index 0000000..971bf0a
--- /dev/null
+++ b/tests/iecs.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/iecs.scad>
+
+module iecs()
+ layout([for(i = iecs) iec_flange_h(i)], 10)
+ rotate(90)
+ iec_assembly(iecs[$i], 3);
+
+if($preview)
+ iecs();
diff --git a/tests/inserts.scad b/tests/inserts.scad
new file mode 100644
index 0000000..df39dc8
--- /dev/null
+++ b/tests/inserts.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/inserts.scad>
+
+module inserts()
+ for(i = [0: len(inserts) -1])
+ translate([10 * i, 0])
+ insert(inserts[i]);
+
+if($preview)
+ inserts();
diff --git a/tests/jack.scad b/tests/jack.scad
new file mode 100644
index 0000000..f5bfcc5
--- /dev/null
+++ b/tests/jack.scad
@@ -0,0 +1,35 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+use <../vitamins/jack.scad>
+
+module jacks() {
+ translate([0, 0])
+ jack_4mm("blue",3, "royalblue");
+
+ translate([20, 0])
+ jack_4mm_shielded("brown", 3, "sienna");
+
+ translate([40, 0])
+ post_4mm("red",3);
+}
+
+if($preview)
+ jacks();
diff --git a/tests/layout.scad b/tests/layout.scad
new file mode 100644
index 0000000..be64a27
--- /dev/null
+++ b/tests/layout.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../global_defs.scad>
+use <../utils/layout.scad>
+
+diams = [3, 7, 5, 11];
+
+module layouts() {
+ linear_extrude(height = eps)
+ layout(diams, gap = 1)
+ circle(d = diams[$i]);
+}
+
+rotate([70, 0, 315]) layouts();
diff --git a/tests/leadnuts.scad b/tests/leadnuts.scad
new file mode 100644
index 0000000..1b5251d
--- /dev/null
+++ b/tests/leadnuts.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/leadnuts.scad>
+
+module leadnuts()
+ layout([for(n = leadnuts) leadnut_flange_dia(n)], 5)
+ leadnut(leadnuts[$i]);
+
+if($preview)
+ leadnuts();
diff --git a/tests/leds.scad b/tests/leds.scad
new file mode 100644
index 0000000..53f0842
--- /dev/null
+++ b/tests/leds.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/leds.scad>
+
+module leds()
+ layout([for(l = LEDs) led_diameter(l)], 5)
+ led(LEDs[$i], ["green", "blue", "red"][$i % 3]);
+
+if($preview)
+ leds();
diff --git a/tests/light_strips.scad b/tests/light_strips.scad
new file mode 100644
index 0000000..0451630
--- /dev/null
+++ b/tests/light_strips.scad
@@ -0,0 +1,38 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/light_strips.scad>
+
+module light_strips()
+ layout([for(s = light_strips) light_strip_width(s)], 10)
+ rotate(90) let(light = light_strips[$i], segs = light_strip_segments(light, 260), d = light_strip_clip_depth(light)) {
+ light_strip(light, segs);
+
+ for(end = [-1, 1])
+ translate([end * (light_strip_cut_length(light, segs) / 2 - d / 2), 0])
+ rotate([90, 0, 90])
+ color("lime") render()
+ translate_z(-d / 2)
+ light_strip_clip(light);
+ }
+
+if($preview)
+ light_strips();
diff --git a/tests/linear_bearings.scad b/tests/linear_bearings.scad
new file mode 100644
index 0000000..ebe6485
--- /dev/null
+++ b/tests/linear_bearings.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/linear_bearings.scad>
+
+module linear_bearings()
+ layout([for(b = linear_bearings) 2 * bearing_radius(b)])
+ linear_bearing(linear_bearings[$i]);
+
+if($preview)
+ linear_bearings();
diff --git a/tests/mains_sockets.scad b/tests/mains_sockets.scad
new file mode 100644
index 0000000..b9d7832
--- /dev/null
+++ b/tests/mains_sockets.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/mains_sockets.scad>
+
+module mains_sockets()
+ layout([for(s = mains_sockets) mains_socket_width(s)], 5)
+ mains_socket(mains_sockets[$i]);
+
+if($preview)
+ mains_sockets();
diff --git a/tests/maths.scad b/tests/maths.scad
new file mode 100644
index 0000000..0f36dda
--- /dev/null
+++ b/tests/maths.scad
@@ -0,0 +1,70 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/maths.scad>
+use <../utils/annotation.scad>
+
+tip = [0, 0, 20];
+
+module shape() {
+ cylinder(d1 = 20, d2 = 0, h = tip.z);
+}
+
+t = [10, 20, 30];
+r = [45, 20, 70];
+s = [1, 0.5, 0.75];
+
+module maths() {
+ //
+ // Translate, rotate and scale the shape
+ //
+ translate(t)
+ rotate(r)
+ scale(s)
+ shape();
+ //
+ // Apply the same transformations to the vector position of the tip
+ //
+ p = transform(tip, translate(t) * rotate(r) * scale(s));
+ //
+ // Place an arrow where the tip ends up
+ //
+ translate(p)
+ arrow();
+ //
+ // Unit vector pointing at p
+ //
+ u = unit(p);
+ //
+ // Point arrow in same direction
+ //
+ z = [0, 0, 1];
+ v = cross(u, z);
+ a = acos(u * z);
+
+
+ l = 20;
+ rotate(-a, v)
+ translate_z(l)
+ vflip()
+ arrow(l);
+}
+
+rotate(45)
+ maths();
diff --git a/tests/meter.scad b/tests/meter.scad
new file mode 100644
index 0000000..a010603
--- /dev/null
+++ b/tests/meter.scad
@@ -0,0 +1,39 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+use <../vitamins/meter.scad>
+
+module meters()
+ if($preview) {
+ meter_assembly();
+
+ translate([0, meter_bezel_width() + 5])
+ vflip()
+ meter_assembly();
+
+ translate([0, -meter_bezel_width()])
+ rotate([0, 180, 0])
+ meter(colour = "blue", value = "123");
+ }
+ else
+ meter_bezel();
+
+
+meters();
diff --git a/tests/microswitches.scad b/tests/microswitches.scad
new file mode 100644
index 0000000..95ad753
--- /dev/null
+++ b/tests/microswitches.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/microswitches.scad>
+
+module microswitches()
+ layout([for(i = microswitches) microswitch_length(i)], 5)
+ microswitch(microswitches[$i]);
+
+if($preview)
+ microswitches();
diff --git a/tests/microview.scad b/tests/microview.scad
new file mode 100644
index 0000000..7a582be
--- /dev/null
+++ b/tests/microview.scad
@@ -0,0 +1,21 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+use <../vitamins/microview.scad>
+
+microview(!$preview);
diff --git a/tests/modules.scad b/tests/modules.scad
new file mode 100644
index 0000000..5bfd1d3
--- /dev/null
+++ b/tests/modules.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/modules.scad>
+
+module modules()
+ layout([for(m = modules) mod_length(m)], 5)
+ module_assembly(modules[$i], 3);
+
+if($preview)
+ modules();
diff --git a/tests/nuts.scad b/tests/nuts.scad
new file mode 100644
index 0000000..4004702
--- /dev/null
+++ b/tests/nuts.scad
@@ -0,0 +1,51 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+
+module nuts() {
+ for(nyloc = [false, true])
+ translate([0, nyloc ? 20 : 0])
+ layout([for(n = nuts) 2 * nut_radius(n)], 5)
+ nut(nuts[$i], nyloc);
+
+ translate([0, 40])
+ layout([for(n = nuts) 2 * nut_radius(n)], 5) let(n = nuts[$i]) {
+ if(n == M3_nut)
+ nut(n, brass = true);
+
+ if(n == M2p5_nut)
+ nut(n, nylon = true);
+
+ if(n == M4_nut)
+ rotate(-45)
+ wingnut(M4_wingnut);
+
+ if(n == M6_nut)
+ nut_and_washer(M6_half_nut, false);
+
+ if(n == M8_nut)
+ #nut_trap(M8_cap_screw, n, h = 30);
+ }
+}
+
+if($preview)
+ nuts();
diff --git a/tests/o_ring.scad b/tests/o_ring.scad
new file mode 100644
index 0000000..28ca861
--- /dev/null
+++ b/tests/o_ring.scad
@@ -0,0 +1,25 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+use <../vitamins/o_ring.scad>
+
+module o_rings()
+ O_ring(2.5, 1.6, 3);
+
+if($preview)
+ o_rings();
diff --git a/tests/offset.scad b/tests/offset.scad
new file mode 100644
index 0000000..bd81d11
--- /dev/null
+++ b/tests/offset.scad
@@ -0,0 +1,39 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../core.scad>
+use <../utils/offset.scad>
+
+module shape()
+ difference() {
+ cube(40, center = true);
+
+ cube([20, 20, 41], center = true);
+ }
+
+module offsets() {
+ $fn = 20;
+
+ for(x = [-1, 0, 1])
+ translate([50 * x, 0])
+ offset_3D(3 * x, chamfer_base = true)
+ shape();
+}
+
+offsets();
diff --git a/tests/opengrab.scad b/tests/opengrab.scad
new file mode 100644
index 0000000..146e023
--- /dev/null
+++ b/tests/opengrab.scad
@@ -0,0 +1,32 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+use <../vitamins/opengrab.scad>
+
+module opengrab_test() {
+ opengrab_target();
+
+ rotate(45)
+ translate_z(opengrab_target_thickness())
+ opengrab();
+}
+
+if($preview)
+ opengrab_test();
diff --git a/tests/pcbs.scad b/tests/pcbs.scad
new file mode 100644
index 0000000..195a617
--- /dev/null
+++ b/tests/pcbs.scad
@@ -0,0 +1,32 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/d_connectors.scad>
+include <../vitamins/pcbs.scad>
+
+module pcbs()
+ layout([for(p = pcbs) pcb_width(p)], 15)
+ translate([0, pcb_length(pcbs[$i]) / 2])
+ rotate(90)
+ pcb_assembly(pcbs[$i], 5 + $i, 3);
+
+if($preview)
+ pcbs();
diff --git a/tests/pillars.scad b/tests/pillars.scad
new file mode 100644
index 0000000..828efee
--- /dev/null
+++ b/tests/pillars.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/pillars.scad>
+
+module pillars()
+ layout([for(p = pillars) pillar_od(p)], 5)
+ pillar(pillars[$i]);
+
+if($preview)
+ pillars();
diff --git a/tests/polyholes.scad b/tests/polyholes.scad
new file mode 100644
index 0000000..fa61b2b
--- /dev/null
+++ b/tests/polyholes.scad
@@ -0,0 +1,77 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../core.scad>
+use <../vitamins/rod.scad>
+include <../vitamins/sheets.scad>
+
+module polyholes() {
+ module positions()
+ for(i = [1 : 10]) {
+ translate([(i * i + i) / 2 + 3 * i , 8])
+ let($r = i / 2)
+ children();
+
+ let(d = i + 0.5)
+ translate([(d * d + d) / 2 + 3 * d, 19])
+ let($r = d / 2)
+ children();
+ }
+
+ color(pp1_colour) linear_extrude(height = 3, center = true)
+ difference() {
+ square([100, 27]);
+
+ positions()
+ poly_circle(r = $r);
+ }
+
+ positions()
+ rod(d = 2 * $r, l = 8 * $r + 5);
+ //
+ // Poly rings
+ //
+ ir = 3 / 2;
+ cir = corrected_radius(ir);
+ sizes = [1.5, 2, 3, 4];
+ for(i = [0 : len(sizes) - 1])
+ translate([i * 10, -10]) {
+ color(pp1_colour) linear_extrude(height = 1)
+ poly_ring(ir = ir, or = cir + sizes[i] * extrusion_width);
+
+ rod(2 * ir, 3);
+ }
+ //
+ // Drill and slot
+ //
+ sheet = Steel06;
+ translate([10, -30])
+ render_2D_sheet(sheet)
+ difference() {
+ sheet_2D(sheet, 20, 20, 1);
+
+ translate([0, 5])
+ slot(1.5, 6, 0);
+
+ translate([0, -5])
+ drill(2, 0);
+ }
+}
+
+polyholes();
diff --git a/tests/psus.scad b/tests/psus.scad
new file mode 100644
index 0000000..9c934e7
--- /dev/null
+++ b/tests/psus.scad
@@ -0,0 +1,36 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/psus.scad>
+
+module psus()
+ layout([for(p = psus) psu_width(p)], 10) let(p = psus[$i])
+ rotate(atx_psu(p) ? 0 : 90) {
+ psu(p);
+
+ psu_screw_positions(p)
+ translate_z(3)
+ screw_and_washer(psu_screw(p), 8);
+ }
+
+if($preview)
+ psus();
diff --git a/tests/pulleys.scad b/tests/pulleys.scad
new file mode 100644
index 0000000..79212d6
--- /dev/null
+++ b/tests/pulleys.scad
@@ -0,0 +1,32 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/belts.scad>
+include <../vitamins/pulleys.scad>
+
+module pulleys()
+ layout([for(p = pulleys) pulley_flange_dia(p)])
+ rotate(-45)
+ pulley_assembly(pulleys[$i]);
+
+if($preview)
+ pulleys();
diff --git a/tests/quadrant.scad b/tests/quadrant.scad
new file mode 100644
index 0000000..2149f00
--- /dev/null
+++ b/tests/quadrant.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../global_defs.scad>
+use <../utils/quadrant.scad>
+
+
+module quadrants() {
+ linear_extrude(height = eps)
+ quadrant(10, 4);
+}
+
+rotate([70, 0, 315]) quadrants();
diff --git a/tests/rails.scad b/tests/rails.scad
new file mode 100644
index 0000000..f91b950
--- /dev/null
+++ b/tests/rails.scad
@@ -0,0 +1,48 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/rails.scad>
+use <../vitamins/nut.scad>
+
+sheet = 3;
+
+module rails()
+ layout([for(l = rails) carriage_width(rail_carriage(l))], 25)
+ rotate(-90) {
+ rail = rails[$i];
+ length = rail == MGN15 ? 260 : 200;
+ screw = rail_screw(rail);
+ nut = screw_nut(screw);
+ washer = screw_washer(screw);
+
+ rail_assembly(rail, length, rail_travel(rail, length) / 2);
+
+ rail_screws(rail, length, sheet + nut_thickness(nut, true) + washer_thickness(washer));
+
+ rail_hole_positions(rail, length, 0)
+ translate_z(-sheet)
+ vflip()
+ nut_and_washer(nut, true);
+ }
+
+if($preview)
+ rails();
diff --git a/tests/ribbon_clamp.scad b/tests/ribbon_clamp.scad
new file mode 100644
index 0000000..d797965
--- /dev/null
+++ b/tests/ribbon_clamp.scad
@@ -0,0 +1,35 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../ribbon_clamp.scad>
+use <../vitamins/wire.scad>
+
+ways = 20;
+
+module ribbon_clamps()
+ translate([ribbon_clamp_length(ways) / 2, 0])
+ if($preview) {
+ ribbon_clamp_fastened_assembly(ways, 3);
+
+ ribbon_cable(ways, 100);
+ }
+ else
+ ribbon_clamp(ways);
+
+ribbon_clamps();
diff --git a/tests/ring_terminals.scad b/tests/ring_terminals.scad
new file mode 100644
index 0000000..b46b8b3
--- /dev/null
+++ b/tests/ring_terminals.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/ring_terminals.scad>
+
+module ring_terminals()
+ layout([for(t = ring_terminals) ringterm_od(t)], 5)
+ rotate(90)
+ ring_terminal_assembly(ring_terminals[$i], 3);
+
+if($preview)
+ ring_terminals();
diff --git a/tests/rockers.scad b/tests/rockers.scad
new file mode 100644
index 0000000..5dd840d
--- /dev/null
+++ b/tests/rockers.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/rockers.scad>
+
+module rockers()
+ layout([for(r = rockers) rocker_flange_w(r)], 5)
+ rocker(rockers[$i]);
+
+if($preview)
+ rockers();
diff --git a/tests/rod.scad b/tests/rod.scad
new file mode 100644
index 0000000..f83873a
--- /dev/null
+++ b/tests/rod.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/linear_bearings.scad>
+
+use <../vitamins/rod.scad>
+
+module rods()
+ layout([for(b = linear_bearings) 2 * bearing_radius(b)])
+ rod(bearing_rod_dia(linear_bearings[$i]), 80);
+
+if($preview)
+ rods();
diff --git a/tests/round.scad b/tests/round.scad
new file mode 100644
index 0000000..dc552b0
--- /dev/null
+++ b/tests/round.scad
@@ -0,0 +1,42 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../core.scad>
+use <../utils/round.scad>
+
+module shape()
+ difference() {
+ square(40, center = true);
+
+ square([20, 20], center = true);
+ }
+
+module rounds() {
+ linear_extrude(height = eps)
+ round(or = 4, ir = 2)
+ shape();
+
+
+ translate([50, 0])
+ round_3D(or = 4, ir = 2, chamfer_base = true, $fn = 16)
+ linear_extrude(height = 40, center = true)
+ shape();
+}
+
+rounds();
diff --git a/tests/rounded_cylinder.scad b/tests/rounded_cylinder.scad
new file mode 100644
index 0000000..a55b73b
--- /dev/null
+++ b/tests/rounded_cylinder.scad
@@ -0,0 +1,32 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../global_defs.scad>
+use <../utils/rounded_cylinder.scad>
+
+
+module rounded_cylinders() {
+ linear_extrude(height = eps)
+ rounded_corner(10, 20, 3, 5);
+
+ translate([30, 10])
+ rounded_cylinder(10, 20, 3, 5, 270);
+}
+
+rounded_cylinders();
diff --git a/tests/rounded_polygon.scad b/tests/rounded_polygon.scad
new file mode 100644
index 0000000..ad4f786
--- /dev/null
+++ b/tests/rounded_polygon.scad
@@ -0,0 +1,57 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../global_defs.scad>
+use <../utils/rounded_polygon.scad>
+use <../utils/annotation.scad>
+
+r = 5;
+h = 40;
+h2 = 20;
+h3 = 30;
+w = 100;
+w2 = 70;
+w3 = 30;
+
+profile = [
+ [ -w / 2 + r, r, r],
+ [ -w / 2 + r / 2, 2 * r + eps, -eps],
+ [-w2 / 2, h - r, r],
+ [-w3 / 2, h2 + r,-r],
+ [ 0, h3 - r, r],
+ [ w3 / 2, h2 + r,-r],
+ [ w2 / 2, h - r, r],
+ [ w / 2 - r / 2, 2 * r + eps, -eps],
+ [ w / 2 - r, r, r],
+];
+
+module rounded_polygons() {
+ tangents = rounded_polygon_tangents(profile);
+ length = rounded_polygon_length(profile, tangents);
+
+ rotate([70, 0, 315])
+ linear_extrude(height = eps)
+ rounded_polygon(profile, tangents);
+
+ translate([0, -10])
+ label(str("perimeter length = ", length), valign = "top", halign = "right");
+
+}
+
+rounded_polygons();
diff --git a/tests/rounded_rectangle.scad b/tests/rounded_rectangle.scad
new file mode 100644
index 0000000..8d903cd
--- /dev/null
+++ b/tests/rounded_rectangle.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../core.scad>
+
+module rounded_rectangles() {
+ linear_extrude(height = eps)
+ rounded_square([30, 20], 3);
+
+ translate([40, 0])
+ rounded_rectangle([30, 20, 10], 3);
+}
+
+rounded_rectangles();
diff --git a/tests/screw_knob.scad b/tests/screw_knob.scad
new file mode 100644
index 0000000..28b3058
--- /dev/null
+++ b/tests/screw_knob.scad
@@ -0,0 +1,37 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../screw_knob.scad>
+
+include <../vitamins/screws.scad>
+
+screws = [M3_hex_screw, M4_hex_screw];
+
+module do_screw_knob(screw)
+ if($preview)
+ screw_knob_assembly(screw, 16);
+ else
+ screw_knob(screw);
+
+module screw_knobs()
+ for(i = [0 : len(screws) - 1])
+ translate([i * 30, 0])
+ do_screw_knob(screws[i]);
+
+screw_knobs();
diff --git a/tests/screws.scad b/tests/screws.scad
new file mode 100644
index 0000000..37b22be
--- /dev/null
+++ b/tests/screws.scad
@@ -0,0 +1,37 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+include <../vitamins/screws.scad>
+
+module screws()
+for(y = [0 : len(screw_lists) -1])
+ for(x = [0 : len(screw_lists[y]) -1]) {
+ screw = screw_lists[y][x];
+ if(screw) {
+ length = screw_max_thread(screw)
+ ? screw_longer_than(screw_max_thread(screw) + 5)
+ : screw_head_type(screw) == hs_grub ? 6 : 30;
+ translate([x * 20, y * 20])
+ screw(screw, length);
+ }
+ }
+
+if($preview)
+ screws();
diff --git a/tests/sealing_strip.scad b/tests/sealing_strip.scad
new file mode 100644
index 0000000..215f43b
--- /dev/null
+++ b/tests/sealing_strip.scad
@@ -0,0 +1,26 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../vitamins/sealing_strip.scad>
+
+module sealing_strip_test()
+ sealing_strip(100);
+
+if($preview)
+ sealing_strip_test();
diff --git a/tests/sector.scad b/tests/sector.scad
new file mode 100644
index 0000000..da05bb1
--- /dev/null
+++ b/tests/sector.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../global_defs.scad>
+use <../utils/sector.scad>
+
+
+module sectors() {
+ linear_extrude(height = eps)
+ sector(50, 45, 180);
+}
+
+rotate([70, 0, 315]) sectors();
diff --git a/tests/sheets.scad b/tests/sheets.scad
new file mode 100644
index 0000000..c7fcdbb
--- /dev/null
+++ b/tests/sheets.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/sheets.scad>
+
+width = 30;
+
+module sheets()
+ layout([for(s = sheets) width], 5)
+ render_sheet(sheets[$i]) sheet(sheets[$i], width, width, 2);
+
+if($preview)
+ sheets();
diff --git a/tests/socket_box.scad b/tests/socket_box.scad
new file mode 100644
index 0000000..851b8d0
--- /dev/null
+++ b/tests/socket_box.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+$explode = 1;
+include <../core.scad>
+use <../utils/layout.scad>
+include <../vitamins/mains_sockets.scad>
+use <../socket_box.scad>
+module socket_boxes()
+ layout([for(s = mains_sockets) mains_socket_width(s)], 20)
+ if($preview)
+ socket_box_fastened_assembly(mains_sockets[$i], 3);
+ else
+ socket_box_stl(mains_sockets[$i]);
+
+socket_boxes();
diff --git a/tests/spades.scad b/tests/spades.scad
new file mode 100644
index 0000000..b76c8a6
--- /dev/null
+++ b/tests/spades.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/spades.scad>
+
+module spades()
+ layout([for(s = spades) spade_w(s)], 5)
+ spade(spades[$i], 10);
+
+if($preview)
+ spades();
diff --git a/tests/sphere.scad b/tests/sphere.scad
new file mode 100644
index 0000000..d4b531f
--- /dev/null
+++ b/tests/sphere.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+
+r = 20;
+
+verts = [4, 8, 12, 16, 20, 24, 28, 32];
+
+module spheres()
+ for(i = [0: len(verts)-1], $fn =verts[i])
+ translate([i * 2 * r, 0])
+ sphere(r);
+
+
+spheres();
diff --git a/tests/spools.scad b/tests/spools.scad
new file mode 100644
index 0000000..d39c246
--- /dev/null
+++ b/tests/spools.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/spools.scad>
+
+module spools()
+ layout([for(s = spools) spool_height(s)], 100)
+ rotate([90, 0, 90])
+ spool(spools[$i]);
+
+if($preview)
+ spools();
diff --git a/tests/springs.scad b/tests/springs.scad
new file mode 100644
index 0000000..8d9c8ee
--- /dev/null
+++ b/tests/springs.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/springs.scad>
+
+module springs()
+ layout([for(s = springs) spring_od2(s)], 5)
+ comp_spring(springs[$i]);
+
+if($preview)
+ springs();
diff --git a/tests/ssrs.scad b/tests/ssrs.scad
new file mode 100644
index 0000000..8636cce
--- /dev/null
+++ b/tests/ssrs.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/ssrs.scad>
+
+module ssrs()
+ layout([for(s = ssrs) ssr_length(s)], 15)
+ ssr_assembly(ssrs[$i], M4_cap_screw, 3);
+
+if($preview)
+ ssrs();
diff --git a/tests/stepper_motors.scad b/tests/stepper_motors.scad
new file mode 100644
index 0000000..ef13bc1
--- /dev/null
+++ b/tests/stepper_motors.scad
@@ -0,0 +1,34 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/stepper_motors.scad>
+
+module stepper_motors()
+ layout([for(s = stepper_motors) NEMA_width(s)], 5) {
+ rotate(180)
+ NEMA(stepper_motors[$i]);
+
+ NEMA_screws(stepper_motors[$i], M3_pan_screw);
+ }
+
+if($preview)
+ stepper_motors();
diff --git a/tests/strap_handle.scad b/tests/strap_handle.scad
new file mode 100644
index 0000000..d14aa4b
--- /dev/null
+++ b/tests/strap_handle.scad
@@ -0,0 +1,34 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../strap_handle.scad>
+
+length = 150;
+
+module strap_handles()
+ if($preview)
+ strap_assembly(length);
+ else {
+ strap(length);
+
+ translate([0, 30])
+ strap_end();
+ }
+
+strap_handles();
diff --git a/tests/sweep.scad b/tests/sweep.scad
new file mode 100644
index 0000000..900199f
--- /dev/null
+++ b/tests/sweep.scad
@@ -0,0 +1,53 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/sweep.scad>
+use <../utils/maths.scad>
+use <../utils/bezier.scad>
+
+L_points = [[0, 5, 0], [ 2, 5, 0], [2, 2, 0], [10, 2, 0], [10, 0, 0], [0, 0, 0]];
+
+rad = 90;
+loop = circle_points(rad, $fn = 180);
+
+loop_x = transform_points(loop, rotate([90, -90, $t * 360]));
+
+loop_y = transform_points(loop, rotate([0, -90, $t * 360]));
+
+loop_z = transform_points(loop, rotate([$t * 360, 0, 0]));
+
+sweep(loop_z, L_points, loop = true);
+
+sweep(loop_x, L_points, loop = true);
+
+sweep(loop_y, L_points, loop = true);
+
+
+knot = [ for(i=[0:.2:359])
+ [ (19*cos(3*i) + 40)*cos(2*i),
+ (19*cos(3*i) + 40)*sin(2*i),
+ 19*sin(3*i) ] ];
+
+sweep(knot, L_points, loop = true, twist = 0);
+
+p = transform_points([[0,0,0], [20,0,5], [10,30,4], [0,0,0], [0,0,20]], scale(10));
+n = 100;
+path = bezier_path(p, n);
+
+rotate(45) sweep(path, circle_points(5, $fn = 64));
diff --git a/tests/teardrops.scad b/tests/teardrops.scad
new file mode 100644
index 0000000..9d62cd6
--- /dev/null
+++ b/tests/teardrops.scad
@@ -0,0 +1,43 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../core.scad>
+
+module teardrops() {
+ color(pp1_colour)
+ rotate([90, 0, -45])
+ linear_extrude(height = 3)
+ difference() {
+ square(40);
+
+ translate([10, 10])
+ teardrop(h = 0, r = 3);
+
+ translate([10, 20])
+ teardrop_plus(h = 0, r = 3);
+
+ translate([20, 30])
+ tearslot(h = 0, r = 3, w = 10);
+
+ translate([30, 15])
+ vertical_tearslot(h = 0, r =3, l = 10);
+ }
+}
+
+teardrops();
diff --git a/tests/toggles.scad b/tests/toggles.scad
new file mode 100644
index 0000000..f87199d
--- /dev/null
+++ b/tests/toggles.scad
@@ -0,0 +1,30 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/toggles.scad>
+
+module toggles()
+ layout([for(t = toggles) toggle_width(t)], 16)
+ toggle(toggles[$i], 3);
+
+if($preview)
+ toggles();
diff --git a/tests/transformers.scad b/tests/transformers.scad
new file mode 100644
index 0000000..19db222
--- /dev/null
+++ b/tests/transformers.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/transformers.scad>
+
+module transformers()
+ layout([for(t = transformers) tx_depth(t)], 10)
+ rotate(90)
+ transformer(transformers[$i]);
+
+if($preview)
+ transformers();
diff --git a/tests/tube.scad b/tests/tube.scad
new file mode 100644
index 0000000..6e7745d
--- /dev/null
+++ b/tests/tube.scad
@@ -0,0 +1,32 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../global_defs.scad>
+use <../utils/tube.scad>
+
+
+module tubes() {
+ linear_extrude(height = eps)
+ ring(10, 8);
+
+ translate([50, 10])
+ tube(10, 8, 30);
+}
+
+tubes();
diff --git a/tests/tubings.scad b/tests/tubings.scad
new file mode 100644
index 0000000..77e6513
--- /dev/null
+++ b/tests/tubings.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/tubings.scad>
+
+module tubings()
+ layout([for(t = tubings) tubing_od(t)], 10)
+ tubing(tubings[$i]);
+
+if($preview)
+ tubings();
diff --git a/tests/variacs.scad b/tests/variacs.scad
new file mode 100644
index 0000000..65c7cf6
--- /dev/null
+++ b/tests/variacs.scad
@@ -0,0 +1,31 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+include <../vitamins/variacs.scad>
+
+module variacs()
+ layout([for(v = variacs) 2 * (variac_bulge_dia(v) - variac_diameter(v) / 2)], 25)
+ rotate(-90)
+ variac(variacs[$i]);
+
+if($preview)
+ variacs();
diff --git a/tests/veroboard.scad b/tests/veroboard.scad
new file mode 100644
index 0000000..9deee5b
--- /dev/null
+++ b/tests/veroboard.scad
@@ -0,0 +1,48 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/screws.scad>
+use <../vitamins/veroboard.scad>
+
+z_cable_ways = 20;
+
+z_vb = ["z_vb", "z_bed_terminal", 5, z_cable_ways / 2 + 12, inch(0.1), false, M3_dome_screw,
+ [[2,2],[2,-3]], [], [5, 7, 9],
+ [
+ [3, z_cable_ways / 4 + 5.5, 0, "term254", z_cable_ways / 2, [1, 3]],
+ [0.5, z_cable_ways / 4 + 5.5, 90, "transition", z_cable_ways / 2, [1, 3]],
+ ],
+ [
+ [[0,1,3], 6], [[0,1,3], 8], [[0,1,3],[10 : 6 + z_cable_ways / 2 - 1]],
+ ]
+ ];
+
+module veroboard_test() translate([vero_length(z_vb) / 2, vero_width(z_vb) / 2]) {
+ vflip()
+ veroboard_assembly(z_vb, 12, 3);
+
+ translate([30, 0])
+ rotate(180)
+ veroboard_assembly(z_vb, 12, 3);
+}
+
+if($preview)
+ veroboard_test();
diff --git a/tests/washers.scad b/tests/washers.scad
new file mode 100644
index 0000000..a18ecb4
--- /dev/null
+++ b/tests/washers.scad
@@ -0,0 +1,47 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/washers.scad>
+
+function penny_diameter(w) = let(p = penny_washer(w)) washer_diameter(p ? p : w);
+
+module washers()
+ layout([for(w = washers) penny_diameter(w)], 2) let(w = washers[$i]) {
+ star_washer(w);
+
+ if(spring_washer_thickness(w))
+ translate([0, 20])
+ let($explode = 1)
+ spring_washer(w);
+
+ translate([0, 40])
+ washer(w);
+
+ if(penny_washer(w))
+ translate([0, 65])
+ penny_washer(w);
+
+ translate([0, 90])
+ printed_washer(w);
+ }
+
+if($preview)
+ washers();
diff --git a/tests/zipties.scad b/tests/zipties.scad
new file mode 100644
index 0000000..cafb5cd
--- /dev/null
+++ b/tests/zipties.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+use <../utils/layout.scad>
+
+include <../vitamins/zipties.scad>
+
+module zipties()
+ layout([for(z = zipties) 9], 10)
+ ziptie(zipties[$i], 5);
+
+if($preview)
+ zipties();
diff --git a/times.txt b/times.txt
new file mode 100644
index 0000000..313fdb2
--- /dev/null
+++ b/times.txt
@@ -0,0 +1,92 @@
+{
+ "tests/annotation.scad": 2.104,
+ "tests/ball_bearings.scad": 2.148,
+ "tests/batteries.scad": 3.81,
+ "tests/belts.scad": 3.212,
+ "tests/bezier.scad": 5.746,
+ "tests/blowers.scad": 2.133,
+ "tests/box.scad": 14.312,
+ "tests/bulldogs.scad": 2.465,
+ "tests/buttons.scad": 2.261,
+ "tests/butt_box.scad": 8.325,
+ "tests/cable_grommets.scad": 2.128,
+ "tests/cable_strips.scad": 2.148,
+ "tests/carriers.scad": 2.259,
+ "tests/clip.scad": 5.751,
+ "tests/components.scad": 3.266,
+ "tests/corner_block.scad": 17.288,
+ "tests/displays.scad": 1.936,
+ "tests/dogbones.scad": 1.543,
+ "tests/door_hinge.scad": 1.622,
+ "tests/door_latch.scad": 3.21,
+ "tests/d_connectors.scad": 1.743,
+ "tests/fans.scad": 1.963,
+ "tests/fan_guard.scad": 1.861,
+ "tests/fillet.scad": 1.468,
+ "tests/fixing_block.scad": 4.495,
+ "tests/foot.scad": 1.54,
+ "tests/fuseholder.scad": 1.629,
+ "tests/global.scad": 1.445,
+ "tests/handle.scad": 2.211,
+ "tests/hanging_hole.scad": 2.239,
+ "tests/hot_ends.scad": 2.875,
+ "tests/iecs.scad": 1.608,
+ "tests/inserts.scad": 1.462,
+ "tests/jack.scad": 2.241,
+ "tests/layout.scad": 1.708,
+ "tests/leadnuts.scad": 1.534,
+ "tests/leds.scad": 1.514,
+ "tests/light_strips.scad": 1.678,
+ "tests/linear_bearings.scad": 1.578,
+ "tests/maths.scad": 2.017,
+ "tests/meter.scad": 2.135,
+ "tests/microswitches.scad": 1.77,
+ "tests/microview.scad": 2.88,
+ "tests/modules.scad": 2.25,
+ "tests/nuts.scad": 1.933,
+ "tests/offset.scad": 12.661,
+ "tests/opengrab.scad": 1.69,
+ "tests/o_ring.scad": 1.826,
+ "tests/pcbs.scad": 5.944,
+ "tests/pillars.scad": 1.908,
+ "tests/polyholes.scad": 3.002,
+ "tests/psus.scad": 6.242,
+ "tests/pulleys.scad": 5.971,
+ "tests/quadrant.scad": 2.113,
+ "tests/rails.scad": 2.865,
+ "tests/ribbon_clamp.scad": 5.174,
+ "tests/ring_terminals.scad": 2.097,
+ "tests/rockers.scad": 1.49,
+ "tests/rod.scad": 2.082,
+ "tests/round.scad": 352.238,
+ "tests/rounded_cylinder.scad": 2.155,
+ "tests/rounded_polygon.scad": 1.951,
+ "tests/rounded_rectangle.scad": 1.86,
+ "tests/screws.scad": 2.783,
+ "tests/screw_knob.scad": 4.461,
+ "tests/sealing_strip.scad": 1.709,
+ "tests/sector.scad": 2.029,
+ "tests/sheets.scad": 1.907,
+ "tests/socket_13A.scad": 9.275,
+ "tests/spades.scad": 2.28,
+ "tests/spheres.scad": 1.569,
+ "tests/spools.scad": 2.278,
+ "tests/springs.scad": 3.446,
+ "tests/ssrs.scad": 3.371,
+ "tests/stepper_motors.scad": 3.43,
+ "tests/strap_handle.scad": 3.182,
+ "tests/sweep.scad": 4.344,
+ "tests/teardrops.scad": 2.764,
+ "tests/toggles.scad": 3.929,
+ "tests/transformers.scad": 2.852,
+ "tests/tube.scad": 2.773,
+ "tests/tubings.scad": 3.01,
+ "tests/variacs.scad": 3.045,
+ "tests/veroboard.scad": 3.791,
+ "tests/washers.scad": 3.706,
+ "tests/zipties.scad": 2.501,
+ "tests/bom.scad": 2.328,
+ "tests/sphere.scad": 2.092,
+ "tests/socket_box.scad": 21.617,
+ "tests/mains_sockets.scad": 15.204
+}
\ No newline at end of file
diff --git a/utils/annotation.scad b/utils/annotation.scad
new file mode 100644
index 0000000..1ca4a7c
--- /dev/null
+++ b/utils/annotation.scad
@@ -0,0 +1,40 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include <../core.scad>
+//
+//! Annotation used in this documentation
+
+module label(str, scale = 0.25, valign = "baseline", halign = "left") //! Draw text that always faces the camera
+ color("black")
+ %rotate($vpr != [0, 0, 0] ? $vpr : [70, 0, 315])
+ linear_extrude(height = eps)
+ scale(scale)
+ text(str, valign = valign, halign = halign);
+
+module arrow(length = 20) { //! Draw an arrow that faces downwards
+ d = length / 20;
+ head_r = 1.5 * d;
+
+ color("grey") %union() {
+ translate_z(head_r)
+ cylinder(d = d, h = length - head_r, $fn = 32);
+
+ cylinder(r1 = 0, r2 = head_r, h = head_r);
+ }
+}
diff --git a/utils/bezier.scad b/utils/bezier.scad
new file mode 100644
index 0000000..711f23a
--- /dev/null
+++ b/utils/bezier.scad
@@ -0,0 +1,57 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Bezier curves and function to get and adjust the length or minimum z point.
+//
+include <../global_defs.scad>
+
+function bezier(t, v) = //! Returns a point at distance ```t``` [0 - 1] along the curve with control points ```v```
+ (len(v) > 2) ? bezier(t, [for (i = [0 : len(v) - 2]) v[i] * (1 - t) + v[i + 1] * (t)])
+ : v[0] * (1 - t) + v[1] * (t);
+
+function bezier_path(v, steps = 100) = //! Returns a Bezier path from control points ```v``` with ```steps``` segments
+ [for(i = [0 : steps], t = i / steps) bezier(t, v)];
+
+function bezier_length(v, delta = 0.01, t = 0, length = 0) = //! Calculate the length of a Bezier curve from control points ```v```
+ t > 1 ? length
+ : bezier_length(v, delta, t + delta, length + norm(bezier(t, v) - bezier(t + delta, v)));
+
+function adjust_bezier(v, r) =
+ let(extension = (v[1] - v[0]) * (r - 1))
+ [v[0], v[1] + extension, v[2] + extension, v[3]];
+
+function adjust_bezier_length(v, l, eps = 0.001, r1 = 1.0, r2 = 1.5, l1, l2) = //! Adjust Bezier control points ```v``` to get the required curve length ```l```
+ let(l1 = l1 != undef ? l1 : bezier_length(adjust_bezier(v, r1)),
+ l2 = l2 != undef ? l2 : bezier_length(adjust_bezier(v, r2))
+ ) abs(l1 - l) < eps ? adjust_bezier(v, r1)
+ : let(r = r1 + (l - l1) * (r2 - r1) / (l2 - l1))
+ abs(r - r1) < abs(r - r2) ? adjust_bezier_length(v, l, eps, r, r1, undef, l1)
+ : adjust_bezier_length(v, l, eps, r, r2, undef, l2);
+
+function bezier_min_z(v, steps = 100, z = inf, i = 0) = //! Calculate the minimum z coordinate of a Bezier curve from control points ```v```
+ i <= steps ? bezier_min_z(v, steps, min(z, bezier(i / steps, v).z), i + 1) : z;
+
+function adjust_bezier_z(v, z, eps = 0.001, r1 = 1, r2 = 1.5, z1, z2) = //! Adjust Bezier control points ```v``` to get the required minimum ```z```
+ let(z1 = z1 != undef ? z1 : bezier_min_z(adjust_bezier(v, r1)),
+ z2 = z2 != undef ? z2 : bezier_min_z(adjust_bezier(v, r2))
+ ) abs(z1 - z) < eps ? adjust_bezier(v, r1)
+ : let(r = r1 + (z - z1) * (r2 - r1) / (z2 - z1))
+ abs(r - r1) < abs(r - r2) ? adjust_bezier_z(v, z, eps, r, r1, undef, z1)
+ : adjust_bezier_z(v, z, eps, r, r2, undef, z2);
diff --git a/utils/core/bom.scad b/utils/core/bom.scad
new file mode 100644
index 0000000..bf080e1
--- /dev/null
+++ b/utils/core/bom.scad
@@ -0,0 +1,112 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Bill Of Materials generation via echo and the ```bom.py``` script. Also handles exploded assembly views and posing. Assembly instructions can precede the module
+//! definition that makes the assembly.
+//!
+//! The example below shows how to define a vitamin and incorporate it into an assembly with sub-assemblies and make an exploded view. The resulting flat BOM is shown but
+//! heirachical BOMs are also generated for real projects.
+//
+function bom_mode(n = 1) = $_bom >= n && (is_undef($on_bom) || $on_bom); //! Current BOM mode, 0 = none, 1 = printed and routed parts and assemblies, 2 includes vitamins as well
+function exploded() = is_undef($exploded_parent) ? $exploded : 0; //! Returns the value of ```$exploded``` if it is defined, else ```0```
+function show_supports() = !$preview || exploded(); //! True if printed support material should be shown
+
+module no_explode() let($exploded_parent = true) children(); //! Prevent children being exploded
+module no_pose() let($posed = true) children(); //! Force children not to be posed even if parent is
+
+module explode(d, explode_children = false, offset = [0,0,0]) { //! Explode children by specified Z distance or vector ```d```, option to explode grand children
+ v = is_list(d) ? d : [0, 0, d];
+ o = is_list(offset) ? offset : [0, 0, offset];
+ if($exploded && is_undef($exploded_parent)) {
+ translate(o) // Draw the line first in case the child is transparent
+ hull() {
+ sphere(0.2);
+
+ translate(v * $exploded)
+ sphere(0.2);
+ }
+
+ translate(v * $exploded)
+ let($exploded_parent = explode_children ? undef : true)
+ children();
+ }
+ else
+ children();
+}
+
+module pose(a = [55, 0, 25], t = [0, 0, 0], exploded = undef) //! Pose an STL or assembly for rendering to png by specifying rotation ```a``` and translation ```t```, ```exploded = true for``` just the exploded view or ```false``` for unexploded only.
+ if(is_undef($pose) || !is_undef($posed) || (!is_undef(exploded) && exploded != !!exploded()))
+ children();
+ else
+ let($posed = true) // only pose the top level
+ rotate([55, 0, 25])
+ rotate([-a.x, 0, 0])
+ rotate([0, -a.y, 0])
+ rotate([0, 0, -a.z])
+ translate(-t)
+ children();
+
+
+module assembly(name) { //! Name an assembly that will appear on the BOM, there needs to a module named ```_assembly``` to make it
+ if(bom_mode())
+ echo(str("~", name, "_assembly{"));
+
+ no_pose()
+ if(is_undef($child_assembly))
+ let($child_assembly = true)
+ children();
+ else
+ no_explode()
+ children();
+
+ if(bom_mode())
+ echo(str("~}", name, "_assembly"));
+}
+
+module stl(name) { //! Name an stl that will appear on the BOM, there needs to a module named ```_stl``` to make it
+ if(bom_mode())
+ echo(str("~", name, ".stl"));
+}
+
+module dxf(name) { //! Name a dxf that will appear on the BOM, there needs to a module named ```_dxf``` to make it
+ if(bom_mode())
+ echo(str("~", name, ".dxf"));
+}
+
+function value_string(value) = is_string(value) ? str("\"", value, "\"") : str(value); //! Convert ```value``` to a string or quote it if it is already a string
+
+function arg(value, default, name = "") = //! Create string for arg if not default, helper for ```vitamin()```
+ value == default ? ""
+ : name ? str(", ", name, " = ", value_string(value))
+ : str(", ", value_string(value));
+
+module vitamin(description) { //! Describe a vitamin for the BOM entry and precede it with a module call that creates it, eg. "wigit(42): Type 42 widget"
+ if(bom_mode(2))
+ echo(str("~", description, !is_undef($hidden) ? " - not shown" : ""));
+}
+
+module not_on_bom(on = false) //! Specify the following child parts are not on the BOM, for example when they are on a PCB that comes assembled
+ let($on_bom = on)
+ children();
+
+module hidden() //! Make item invisible, except on the BOM
+ scale(1 / sqr(1024))
+ let($hidden = true)
+ children();
diff --git a/utils/core/clip.scad b/utils/core/clip.scad
new file mode 100644
index 0000000..1b526d2
--- /dev/null
+++ b/utils/core/clip.scad
@@ -0,0 +1,49 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Construct arbirarily large box to partition 3D space and clip objects, useful for creating cross sections to see the inside when debugging.
+//!
+//! Original version by Doug Moen on the OpenSCAD forum
+//
+//
+module box(xmin, ymin, zmin, xmax, ymax, zmax) //! Construct a box given its bounds
+ polyhedron(
+ [[xmin, ymin, zmin], // 0
+ [xmin, ymin, zmax], // 1
+ [xmin, ymax, zmin], // 2
+ [xmin, ymax, zmax], // 3
+ [xmax, ymin, zmin], // 4
+ [xmax, ymin, zmax], // 5
+ [xmax, ymax, zmin], // 6
+ [xmax, ymax, zmax]], // 7
+ [[7,5,1,3], // top
+ [2,0,4,6], // bottom
+ [5,4,0,1], // front
+ [3,2,6,7], // back
+ [5,7,6,4], // right
+ [0,2,3,1]] // left
+ );
+
+module clip(xmin = -inf, ymin = -inf, zmin = -inf, xmax = inf, ymax = inf, zmax = inf) //! Clip child to specified boundaries
+ render() intersection() {
+ children();
+
+ box(xmin, ymin, zmin, xmax, ymax, zmax);
+ }
diff --git a/utils/core/global.scad b/utils/core/global.scad
new file mode 100644
index 0000000..32b371c
--- /dev/null
+++ b/utils/core/global.scad
@@ -0,0 +1,64 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Global constants, functions and modules. This file is used directly or indirectly in every scad file.
+//
+include <../../global_defs.scad>
+
+function sqr(x) = x * x; //! Returns the square of ```x```
+function inch(x) = x * 25.4; //! Inch to mm conversion
+function echoit(x) = echo(x) x; //! Echo expression and return it, useful for debugging
+function in(list, x) = !!len([for(v = list) if(v == x) true]); //! Returns true if ```x``` is an element in the ```list```
+function Len(x) = is_list(x) ? len(x) : 0; //! Returns the length of a list or 0 if ```x``` is not a list
+function r2sides(r) = $fn ? $fn : ceil(max(min(360/ $fa, r * 2 * PI / $fs), 5)); //! Replicates the OpenSCAD logic to calculate the number of sides from the radius
+function r2sides4n(r) = floor((r2sides(r) + 3) / 4) * 4; //! Round up the number of sides to a multiple of 4 to ensure points land on all axes
+
+module translate_z(z) translate([0, 0, z]) children(); //! Shortcut for Z only translations
+module vflip() rotate([180, 0, 0]) children(); //! Invert children by doing a 180 flip around the X axis
+module ellipse(xr, yr) scale([1, yr / xr]) circle4n(xr); //! Draw an ellipse
+
+module extrude_if(h, center = true) //! Extrudes 2D object to 3D when ```h``` is nonzero, otherwise leaves it 2D
+ if(h)
+ linear_extrude(height = h, center = center) // 3D
+ children();
+ else
+ children(); // 2D
+
+module circle4n(r, d = undef) { //! Circle with multiple of 4 vertices
+ R = is_undef(d) ? r : d / 2;
+ circle(R, $fn = r2sides4n(R));
+}
+
+module semi_circle(r, d = undef) //! A semi circle in the positive Y domain
+ intersection() {
+ R = is_undef(d) ? r : d / 2;
+ circle4n(R);
+
+ sq = R + 1;
+ translate([-sq, 0])
+ square([2 * sq, sq]);
+ }
+
+include
+include
+include
+include
+include
+include
diff --git a/utils/core/polyholes.scad b/utils/core/polyholes.scad
new file mode 100644
index 0000000..e57b087
--- /dev/null
+++ b/utils/core/polyholes.scad
@@ -0,0 +1,79 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! A method of making 3D printed holes come out the right size regardless of the printer, providing
+//! it gets the linear dimensions right. See
+//!
+//! The module provides `poly_circle()`, `poly_cylinder()` and `poly_ring()` that is useful for making printed washers and pillars.
+//
+function sides(r) = max(round(4 * r), 3); //! Optimium number of sides for specified radius
+function corrected_radius(r, n = 0) = r / cos(180 / (n ? n : sides(r))); //! Adjusted radius to make flats lie on the circle
+function corrected_diameter(d, n = 0) = d / cos(180 / (n ? n : sides(d / 2))); //! Adjusted diameter to make flats lie on the circle
+
+module poly_circle(r, sides = 0) { //! Make a circle adjusted to print the correct size
+ n = sides ? sides : sides(r);
+ circle(r = corrected_radius(r,n), $fn = n);
+}
+
+module poly_cylinder(r, h, center = false, sides = 0) //! Make a cylinder adjusted to print the correct size
+ extrude_if(h, center)
+ poly_circle(r, sides);
+
+module poly_ring(or, ir) { //! Make a 2D ring adjusted to have the correct internal radius
+ cir = corrected_radius(ir);
+ filaments = floor((or - cir) / extrusion_width);
+ if(filaments > 3)
+ difference() {
+ circle(or);
+
+ poly_circle(ir);
+ }
+ else
+ if(filaments >= 2)
+ difference() {
+ offset(or - cir)
+ poly_circle(ir);
+
+ poly_circle(ir);
+ }
+ else
+ difference() {
+ poly_circle(or);
+
+ offset(-squeezed_wall)
+ poly_circle(or);
+ }
+}
+
+module drill(r, h = 100) //! Make a cylinder for drilling holes suitable for CNC routing, set h = 0 for circle
+ extrude_if(h)
+ circle(r = corrected_radius(r, r2sides(r)));
+//
+// Horizontal slot
+//
+module slot(r, l, h = 100) //! Make a horizontal slot suitable for CNC routing, set h = 0 for 2D version
+ extrude_if(h)
+ hull() {
+ translate([l / 2,0])
+ drill(r, 0);
+
+ translate([-l / 2,0])
+ drill(r, 0);
+ }
diff --git a/utils/core/rounded_rectangle.scad b/utils/core/rounded_rectangle.scad
new file mode 100644
index 0000000..123c5d1
--- /dev/null
+++ b/utils/core/rounded_rectangle.scad
@@ -0,0 +1,33 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Rectangle with rounded corners.
+//
+module rounded_square(size, r, center = true) //! Like ```square()``` but with with rounded corners
+{
+ $fn = r2sides4n(r);
+ offset(r) offset(-r) square(size, center = center);
+}
+
+module rounded_rectangle(size, r, center = true, xy_center = true) //! Like ```cube()``` but corners rounded in XY plane and separate centre options for xy and z.
+{
+ linear_extrude(height = size[2], center = center)
+ rounded_square([size[0], size[1]], r, xy_center);
+}
diff --git a/utils/core/sphere.scad b/utils/core/sphere.scad
new file mode 100644
index 0000000..31459c4
--- /dev/null
+++ b/utils/core/sphere.scad
@@ -0,0 +1,28 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//! Redefines `sphere()` to always have a vertex on all six half axes I.e. vertices at the poles and the equator and `$fn` a multiple of four.
+//! This ensures `hull` and `minkowski` results have the correct dimensions when spheres are placed at the corners.
+
+module sphere(r = 1, d = undef) { //! Override ```sphere``` so that has vertices on all three axes. Has the advantage of giving correct dimensions when hulled
+ R = is_undef(d) ? r : d / 2;
+ rotate_extrude($fn = r2sides4n(R))
+ rotate(-90)
+ semi_circle(R);
+}
diff --git a/utils/core/teardrops.scad b/utils/core/teardrops.scad
new file mode 100644
index 0000000..74821a0
--- /dev/null
+++ b/utils/core/teardrops.scad
@@ -0,0 +1,53 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! For making horizontal holes that don't need support material.
+//! Small holes can get away without it, but they print better with truncated teardrops.
+//
+module teardrop(h, r, center = true, truncate = true) //! For making horizontal holes that don't need support material, set ```truncate = false``` to make traditional RepRap teardrops that don't even need bridging
+ render(convexity = 5)
+ extrude_if(h, center)
+ hull() {
+ circle4n(r);
+
+ if(truncate)
+ translate([0, r / 2])
+ square([2 * r * (sqrt(2) - 1), r], center = true);
+ else
+ polygon([[0, 0], [eps, 0], [0, r * sqrt(2)]]);
+ }
+
+module teardrop_plus(h, r, center = true, truncate = true) //! Slightly bigger teardrop to allow for the 3D printing staircase effect
+ teardrop(h, r + layer_height / 4, center, truncate);
+
+module tearslot(h, r, w, center = true) //! A horizontal slot that doesn't need support material
+ extrude_if(h, center)
+ hull() {
+ translate([-w/2,0,0]) teardrop(r = r, h = 0);
+ translate([ w/2,0,0]) teardrop(r = r, h = 0);
+ }
+
+module vertical_tearslot(h, r, l, center = true) //! A vertical slot that doesn't need support material
+ extrude_if(h, center)
+ hull() {
+ translate([0, l / 2]) teardrop(0, r, true);
+ translate([0, -l / 2])
+ circle4n(r);
+ }
diff --git a/utils/dogbones.scad b/utils/dogbones.scad
new file mode 100644
index 0000000..3231fe4
--- /dev/null
+++ b/utils/dogbones.scad
@@ -0,0 +1,46 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! When square holes are cut with a CNC bit they get rounded corners. If it is important that
+//! a square cornered part fits in the hole then circles are placed in the corners making a bone shape.
+//
+include <../core.scad>
+
+module dogbone_square(size, r = cnc_bit_r, center = true) //! Square with circles at the corners
+{
+ union() {
+ square(size, center = center);
+
+ if(r > 0) {
+ origin = center ? [0, 0] : size / 2;
+ offset = r / sqrt(2);
+
+ for(x = [-1, 1], y = [-1, 1])
+ translate(origin + [x * (size.x / 2 - offset), y * (size.y / 2 - offset)])
+ drill(r, 0);
+ }
+ }
+}
+
+module dogbone_rectangle(size, r = cnc_bit_r, center = true, xy_center = true) //! Rectangle with cylinders at the corners
+{
+ extrude_if(h = size.z, center = center)
+ dogbone_square([size.x, size.y], r, xy_center);
+}
diff --git a/utils/fillet.scad b/utils/fillet.scad
new file mode 100644
index 0000000..99359c2
--- /dev/null
+++ b/utils/fillet.scad
@@ -0,0 +1,32 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Rounded fillet for adding to corners.
+//
+include <../core.scad>
+
+module fillet(r, h, center = false) //! Fillet with specified radius and height
+ extrude_if(h, center = center)
+ difference() {
+ translate([-eps, -eps, 0])
+ square(r + eps);
+
+ circle(r + eps);
+ }
diff --git a/utils/hanging_hole.scad b/utils/hanging_hole.scad
new file mode 100644
index 0000000..cb456be
--- /dev/null
+++ b/utils/hanging_hole.scad
@@ -0,0 +1,64 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Method to print holes in mid air. See
+//
+include <../core.scad>
+
+module hanging_hole(z, ir, h = 100, h2 = 100) { //! Hole radius ```ir``` hanging at the specified ```z``` value above a void who's shape is given by a 2D child
+ module polyhole(r, h, n = 8) {
+ if(h > 0)
+ rotate(180 / n) {
+ poly_cylinder(r = r, h = layer_height, sides = n);
+
+ translate_z(layer_height)
+ if(2 * n <= sides(r))
+ polyhole(r - eps, h - layer_height, n * 2);
+ else
+ poly_cylinder(r - eps, h - layer_height);
+ }
+ }
+ assert(z % layer_height == 0, str(z));
+ infill_angle = z % (2 * layer_height) ? -45 : 45;
+ below = min(z + eps, h2);
+ big = 1000;
+
+ render(convexity = 3) translate_z(z)
+ union() {
+ translate_z(2 * layer_height)
+ polyhole(ir - eps, h - 2 * layer_height);
+
+ difference() {
+ translate_z(-below)
+ linear_extrude(height = below + 2 * layer_height)
+ children();
+
+ rotate(infill_angle)
+ for(side = [-1, 1]) {
+ translate([side * (ir + big), 0, big + layer_height])
+ cube(2 * big, center = true);
+
+ translate([0, side * (ir + big), big])
+ cube(2 * big, center = true);
+
+ }
+ }
+ }
+}
diff --git a/utils/layout.scad b/utils/layout.scad
new file mode 100644
index 0000000..8945427
--- /dev/null
+++ b/utils/layout.scad
@@ -0,0 +1,33 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Layout objects in a line with equal gaps given a vector of their widths.
+//
+include <../global_defs.scad>
+
+function layout_offset(widths, i, gap = 2) = //! Calculate the offset for the ```i```th item
+ i == 0 ? widths[0] / 2
+ : layout_offset(widths, i - 1, gap) + widths[i - 1] / 2 + gap + widths[i] / 2;
+
+module layout(widths, gap = 2, no_offset = false) //! Layout children passing ```$i```
+ translate([no_offset ? -widths[0] / 2 : 0, 0])
+ for($i = [0 : len(widths) - 1])
+ translate([layout_offset(widths, $i, gap), 0])
+ children();
diff --git a/utils/maths.scad b/utils/maths.scad
new file mode 100644
index 0000000..97b6bb2
--- /dev/null
+++ b/utils/maths.scad
@@ -0,0 +1,76 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Maths utilities for minapulating vectors and matrices.
+//
+function sqr(x) = x * x;
+
+function translate(v) = let(u = is_list(v) ? len(v) == 2 ? [v.x, v.y, 0] //! Generate a 4x4 translation matrix, ```v``` can be ```[x, y]```, ```[x, y, z]``` or ```z```
+ : v
+ : [0, 0, v])
+ [ [1, 0, 0, u.x],
+ [0, 1, 0, u.y],
+ [0, 0, 1, u.z],
+ [0, 0, 0, 1] ];
+
+function rotate(a, v) = //! Generate a 4x4 rotation matrix, ```a``` can be a vector of three angles or a single angle around ```z```, or around axis ```v```
+ is_undef(v) ? let(av = is_list(a) ? a : [0, 0, a],
+ cx = cos(av[0]),
+ cy = cos(av[1]),
+ cz = cos(av[2]),
+ sx = sin(av[0]),
+ sy = sin(av[1]),
+ sz = sin(av[2]))
+ [
+ [ cy * cz, cz * sx * sy - cx * sz, cx * cz * sy + sx * sz, 0],
+ [ cy * sz, cx * cz + sx * sy * sz,-cz * sx + cx * sy * sz, 0],
+ [-sy, cy * sx, cx * cy, 0],
+ [ 0, 0, 0, 1]
+ ]
+ : let(s = sin(a),
+ c = cos(a),
+ C = 1 - c,
+ m = sqr(v.x) + sqr(v.y) + sqr(v.z), // m used instead of norm to avoid irrational roots as much as possible
+ u = v / sqrt(m))
+ [
+ [ C * v.x * v.x / m + c, C * v.x * v.y / m - u.z * s, C * v.x * v.z / m + u.y * s, 0],
+ [ C * v.y * v.x / m + u.z * s, C * v.y * v.y / m + c, C * v.y * v.z / m - u.x * s, 0],
+ [ C * v.z * v.x / m - u.y * s, C * v.z * v.y / m + u.x * s, C * v.z * v.z / m + c, 0],
+ [ 0, 0, 0, 1]
+ ];
+
+function scale(v) = let(s = is_list(v) ? v : [v, v, v]) //! Generate a 4x4 matrix that scales by ```v```, which can be a vector of xyz factors or a scalar to scale all axes equally
+ [
+ [s.x, 0, 0, 0],
+ [0, s.y, 0, 0],
+ [0, 0, s.z, 0],
+ [0, 0, 0, 1]
+ ];
+
+function vec3(v) = [v.x, v.y, v.z]; //! Return a 3 vector with the first three elements of ```v```
+function transform(v, m) = vec3(m * [v.x, v.y, v.z, 1]); //! Apply 4x4 transform to a 3 vector by extending it and cropping it again
+function transform_points(path, m) = [for(p = path) transform(p, m)]; //! Apply transform to a path
+function unit(v) = let(n = norm(v)) n ? v / n : v; //! Convert ```v``` to a unit vector
+
+function transpose(m) = [ for(j = [0 : len(m[0]) - 1]) [ for(i = [0 : len(m) - 1]) m[i][j] ] ]; //! Transpose an arbitrary size matrix
+
+function identity(n, x = 1) = [for(i = [0 : n - 1]) [for(j = [0 : n - 1]) i == j ? x : 0] ]; //! Construct an arbitrary size identity matrix
+
+function reverse(v) = let(n = len(v) - 1) n < 0 ? [] : [for(i = [0 : n]) v[n - i]]; //! Reverse a vector
diff --git a/utils/offset.scad b/utils/offset.scad
new file mode 100644
index 0000000..452cf7b
--- /dev/null
+++ b/utils/offset.scad
@@ -0,0 +1,63 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//! 3D offset using `minkowski` with a `sphere`, so very slow if `$fn` is not kept small. The offset can be positive or negative.
+//!
+//! Can be used to round corners. Positive offsets will round convex corners, negative offsets round concave corners. To round both use [`round_3D()`](#round).
+//!
+//! If `chamfer_base` is true then the bottom edge is made suitable for 3D printing by chamfering when the angle gets shallower than 45 degrees.
+include <../core.scad>
+
+module offset_3D(r, chamfer_base = false) { //! Offset 3D shape by specified radius ```r```, positive or negative.
+ module ball(r)
+ if(chamfer_base)
+ rotate_extrude()
+ intersection() {
+ rotate(180)
+ teardrop(0, r);
+
+ translate([0, -r])
+ square([r, 2 * r]);
+ }
+ else
+ sphere(r);
+
+ if(r > 0)
+ minkowski() {
+ children();
+
+ ball(r);
+ }
+ else
+ if(r < 0)
+ render() difference() {
+ cube(inf / 2, center = true);
+
+ minkowski() {
+ difference() {
+ cube(inf, center = true);
+
+ children();
+ }
+ ball(-r);
+ }
+ }
+ else
+ children();
+}
diff --git a/utils/quadrant.scad b/utils/quadrant.scad
new file mode 100644
index 0000000..7fe2da7
--- /dev/null
+++ b/utils/quadrant.scad
@@ -0,0 +1,40 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Square with one rounded corner.
+//
+include <../core.scad>
+
+module quadrant(w, r, center = false) { //! Draw a square with one rounded corner, can be centered on the arc centre, when ```center``` is ```true```.
+ offset = center ? r - w : 0;
+ translate([offset, offset])
+ hull() {
+ intersection() {
+ translate([w - r, w - r])
+ circle4n(r);
+
+ square(w);
+ }
+
+ square([w, eps]);
+
+ square([eps, w]);
+ }
+}
diff --git a/utils/round.scad b/utils/round.scad
new file mode 100644
index 0000000..bab60aa
--- /dev/null
+++ b/utils/round.scad
@@ -0,0 +1,44 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//! Round 2D shapes uisng `offset()`, which is fast and 3D shapes with [`offset_3D()`](#offset), which is very slow.
+//!
+//! A single radius can be specified or separate internal and external radii.
+//! If `chamfer_base` is `true` for `round_3D()` then the bottom edge is made suitable for 3D printing by chamfering once the
+//! the angle gets shallower than 45 degrees.
+include <../core.scad>
+use
+
+module round(r, ir = undef, or = undef) { //! Round a 2D child, single radius or separate inside and outside radii
+ IR = is_undef(ir) ? r : ir;
+ OR = is_undef(or) ? r : or;
+ offset(OR)
+ offset(-OR -IR)
+ offset(IR)
+ children();
+}
+
+module round_3D(r, ir = undef, or = undef, chamfer_base = false) { //! Round a 3D child single radius or separate inside and outside radii
+ IR = is_undef(ir) ? r : ir;
+ OR = is_undef(or) ? r : or;
+ offset_3D(OR, chamfer_base)
+ offset_3D(-OR -IR, chamfer_base)
+ offset_3D(IR, chamfer_base)
+ children();
+}
diff --git a/utils/rounded_cylinder.scad b/utils/rounded_cylinder.scad
new file mode 100644
index 0000000..caccf55
--- /dev/null
+++ b/utils/rounded_cylinder.scad
@@ -0,0 +1,47 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Cylinder with a rounded end.
+//
+include <../core.scad>
+
+module rounded_corner(r, h, r2, ir = 0) { //! 2D version
+ assert(ir <= r - r2);
+
+ translate([ir , 0])
+ hull() {
+ square([eps, h]);
+
+ square([r - ir, eps]);
+
+ translate([r - r2 - ir, h - r2])
+ intersection() {
+ circle4n(r2, $fs = 0.2);
+
+ square(r2);
+ }
+ }
+}
+
+module rounded_cylinder(r, h, r2, ir = 0, angle = 360) //! Rounded cylinder given radius ```r```, height ```h```, optional internal radius ```ir``` and optional ```angle```
+{
+ rotate_extrude(angle = angle)
+ rounded_corner(r, h, r2, ir);
+}
diff --git a/utils/rounded_polygon.scad b/utils/rounded_polygon.scad
new file mode 100644
index 0000000..11a7354
--- /dev/null
+++ b/utils/rounded_polygon.scad
@@ -0,0 +1,93 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Draw a polygon with rounded corners. Each element of the vector is the XY coordinate and a radius. Radius can be negative for a concave corner.
+//!
+//! Because the tangents need to be calculated to find the length these can be calculated separately and re-used when drawing to save calculating them twice.
+//
+include <../core.scad>
+
+function circle_tangent(p1, p2) =
+ let(
+ r1 = p1[2],
+ r2 = p2[2],
+ dx = p2.x - p1.x,
+ dy = p2.y - p1.y,
+ d = sqrt(dx * dx + dy * dy),
+ theta = atan2(dy, dx) + acos((r1 - r2) / d),
+ xa = p1.x +(cos(theta) * r1),
+ ya = p1.y +(sin(theta) * r1),
+ xb = p2.x +(cos(theta) * r2),
+ yb = p2.y +(sin(theta) * r2)
+ )[ [xa, ya], [xb, yb] ];
+
+function rounded_polygon_tangents(points) = //! Compute the straight sections needed to draw and to compute the lengths
+ let(len = len(points))
+ [for(i = [0 : len - 1])
+ let(ends = circle_tangent(points[i], points[(i + 1) % len]))
+ for(end = [0, 1])
+ ends[end]];
+
+function sumv(v, i = 0, sum = 0) = i == len(v) ? sum : sumv(v, i + 1, sum + v[i]);
+
+// the cross product of 2D vectors is the area of the parallelogram between them. We use the sign of this to decide if the angle is bigger than 180.
+function rounded_polygon_length(points, tangents) = //! Calculate the length given the point list and the list of tangents computed by ``` rounded_polygon_tangents```
+ let(
+ len = len(points),
+ indices = [0 : len - 1],
+ straights = [for(i = indices) norm(tangents[2 * i] - tangents[2 * i + 1])],
+ arcs = [for(i = indices) let(p1 = tangents[2 * i + 1],
+ p2 = tangents[(2 * i + 2) % (2 * len)],
+ corner = points[(i + 1) % len],
+ c = [corner.x, corner.y],
+ v1 = p1 - c,
+ v2 = p2 - c,
+ r = abs(corner.z),
+ a = acos((v1 * v2) / sqr(r))) PI * (cross(v1,v2) <= 0 ? a : 360 - a) * r / 180]
+ )
+ sumv(concat(straights, arcs));
+
+module rounded_polygon(points, _tangents = undef) { //! Draw the rounded polygon from the point list, can pass the tangent list to save it being calculated
+ len = len(points);
+ indices = [0 : len - 1];
+ tangents = _tangents ? _tangents : rounded_polygon_tangents(points);
+
+ difference(convexity = points) {
+ union() {
+ for(i = indices)
+ if(points[i][2] > 0)
+ hull() {
+ translate([points[i].x, points[i].y])
+ circle(points[i][2]);
+ polygon([tangents[(2 * i - 1 + 2 * len) % (2 * len)], tangents[2 * i], [points[i].x, points[i].y]]);
+ }
+
+ polygon(tangents, convexity = points);
+ }
+ for(i = indices)
+ if(points[i][2] < 0)
+ hull() {
+ translate([points[i].x, points[i].y])
+ circle(-points[i][2]);
+
+ polygon([tangents[(2 * i - 1 + 2 * len) % (2 *len)], tangents[2 * i], [points[i].x, points[i].y]]);
+ }
+ }
+}
diff --git a/utils/sector.scad b/utils/sector.scad
new file mode 100644
index 0000000..09fc237
--- /dev/null
+++ b/utils/sector.scad
@@ -0,0 +1,36 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! A sector of a circle between two angles.
+//
+include <../core.scad>
+
+module sector(r, start_angle, end_angle) { //! Create specified sector given radius ```r```, ```start_angle``` and ```end_angle```
+ R = r * sqrt(2) + 1;
+
+ if(end_angle > start_angle)
+ intersection() {
+ circle4n(r);
+
+ // A 4 triangle fan
+ polygon([[0, 0],
+ for(i = [0 : 4], a = start_angle + i * (end_angle - start_angle) / 4) R * [cos(a), sin(a)] ]);
+ }
+}
diff --git a/utils/sweep.scad b/utils/sweep.scad
new file mode 100644
index 0000000..4c2672e
--- /dev/null
+++ b/utils/sweep.scad
@@ -0,0 +1,160 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Utility to generate a polhedron by sweeping a 2D profile along a 3D path and utilities for generating paths.
+//!
+//! The initial orientation is the Y axis of the profile points towards the initial center of curvature, Frenet-Serret style.
+//! This means the first three points must not be colinear. Subsequent rotations use the minimum rotation method.
+//!
+//! The path can be open or closed. If closed sweep ensures that the start and end have the same rotation to line up.
+//! An additional twist around the path can be specified. If the path is closed this should be a multiple of 360.
+//
+include <../core.scad>
+
+use
+
+function transpose3(m) = [ [m[0].x, m[1].x, m[2].x],
+ [m[0].y, m[1].y, m[2].y],
+ [m[0].z, m[1].z, m[2].z] ];
+//
+// Frenet-Serret frame
+//
+function fs_frame(tangents) =
+ let(tangent = tangents[0],
+ normal = tangents[1] - tangents[0],
+ binormal = cross(tangent, normal),
+ z = unit(tangent),
+ x = assert(norm(binormal) > 0.00001, "first three points are colinear") unit(binormal),
+ y = unit(cross(z, x))
+ ) [[x.x, y.x, z.x],
+ [x.y, y.y, z.y],
+ [x.z, y.z, z.z]];
+//
+// Computes the rotation with minimum angle that brings UNIT vectors a to b.
+// The code fails if a and b are opposed to each other.
+//
+function rotate_from_to(a, b) =
+ let(axis = unit(cross(a, b)))
+ axis * axis >= 0.99 ? transpose3([b, axis, cross(axis, b)]) * [a, axis, cross(axis, a)]
+ : a * b > 0 ? [[ 1, 0, 0], [0, 1, 0], [0, 0, 1]]
+ : [[-1, 0, 0], [0, 1, 0], [0, 0, -1]];
+//
+// Given two rotations A and B, calculates the angle between B*[1,0,0]
+// and A*[1,0,0] that is, the total torsion angle difference between A and B.
+//
+function calculate_twist(A, B) = let(D = transpose3(B) * A) atan2(D[1][0], D[0][0]);
+//
+// Compute a 4x4 matrix to orientate a frame of the sweep given the position and a 3x3 rotation matrix.
+//
+function orientate(p, r) =
+ let(x = r[0], y = r[1], z = r[2])
+ [[x.x, y.x, z.x],
+ [x.y, y.y, z.y],
+ [x.z, y.z, z.z],
+ [p.x, p.y, p.z]];
+
+//
+// Rotate around z
+//
+function rot3_z(a) =
+ let(c = cos(a),
+ s = sin(a))
+ [ [ c, -s, 0],
+ [ s, c, 0],
+ [ 0, 0, 1] ];
+
+//
+// Calculate the unit tangent at a vertex given the indices before and after. One of these can be the same as i in the case
+// of the start and end of a non closed path.
+//
+function tangent(path, before, i, after) = unit(unit(path[after] - path[i]) - unit(path[before] - path[i]));
+//
+// Generate all the surface points of the swept volume.
+//
+function skin_points(profile, path, loop, twist = 0) =
+ let(len = len(path),
+ last = len - 1,
+
+ profile4 = [for(p = profile) [p.x, p.y, p.z, 1]],
+
+ tangents = [tangent(path, loop ? last : 0, 0, 1),
+ for(i = [1 : last - 1]) tangent(path, i - 1, i, i + 1),
+ tangent(path, last - 1, last, loop ? 0 : last)],
+
+ rotations = [for(i = 0, rot = fs_frame(tangents);
+ i < len;
+ i = i + 1,
+ rot = i < len ? rotate_from_to(tangents[i - 1], tangents[i]) * rot : undef) rot],
+
+ missmatch = loop ? calculate_twist(rotations[0], rotations[last]) : 0,
+ rotation = missmatch + twist
+ )
+ [for(i = [0 : last])
+ let(za = rotation * i / last)
+ each profile4 * orientate(path[i], rotations[i] * rot3_z(za))
+ ];
+
+function cap(facets, segment = 0) = [for(i = [0 : facets - 1]) segment ? facets * segment + i : facets - 1 - i];
+
+function quad(p, a,b,c,d) = norm(p[a] - p[c]) > norm(p[b] - p[d]) ? [[b, c, d], [b, d, a]] : [[a, b, c], [a, c, d]];
+
+function skin_faces(points, segs, facets, loop) = [for(i = [0 : facets - 1], s = [0 : segs - (loop ? 1 : 2)])
+ each quad(points,
+ s * facets + i,
+ s * facets + (i + 1) % facets,
+ ((s + 1) % segs) * facets + (i + 1) % facets,
+ ((s + 1) % segs) * facets + i)];
+
+function sweep(path, profile, loop = false, twist = 0) = //! Generate the point list and face list of the swept volume
+ let(
+ segments = len(path),
+ facets = len(profile),
+ points = skin_points(profile, path, loop, twist),
+ skin_faces = skin_faces(points, segments, facets, loop),
+ faces = loop ? skin_faces : concat([cap(facets)], skin_faces, [cap(facets, segments - 1)])
+ ) [points, faces];
+
+module sweep(path, profile, loop = false, twist = 0) { //! Draw a polyhedron that is the swept volume
+ mesh = sweep(path, profile, loop, twist);
+
+ polyhedron(points = mesh[0], faces = mesh[1]);
+}
+
+function path_length(path, i = 0, length = 0) = //! Calculated the length along a path
+ i >= len(path) - 1 ? length
+ : path_length(path, i + 1, length + norm(path[i + 1] - path[i]));
+
+function circle_points(r = 1, z = 0) = //! Generate the points of a circle, setting z makes a single turn spiral
+ let(sides = r2sides(r))
+ [for(i = [0 : sides - 1]) let(a = i * 360 / sides) [r * sin(a), r * cos(a), z * a / 360]];
+
+function rectangle_points(w, h) = [[-w/2, -h/2, 0], [-w/2, h/2, 0], [w/2, h/2, 0], [w/2, -h/2, 0]]; //! Generate the points of a rectangle
+
+function arc_points(r, a = [90, 0, 180], al = 90) = //! Generate the points of a circular arc
+ let(sides = ceil(r2sides(r) * al / 360), tf = rotate(a))
+ [for(i = [0 : sides]) let(t = i * al / sides) transform([r * sin(t), r * cos(t), 0], tf)];
+
+function before(path1, path2) = //! Translate ```path1``` so its end meets the start of ```path2``` and then concatenate
+ let(end = len(path1) - 1, offset = path2[0] - path1[end])
+ concat([for(i = [0 : end - 1]) path1[i] + offset], path2);
+
+function after(path1, path2) = //! Translate ```path2``` so its start meets the end of ```path1``` and then concatenate
+ let(end1 = len(path1) - 1, end2 = len(path2) - 1, offset = path1[end1] - path2[0])
+ concat(path1, [for(i = [1 : end2]) path2[i] + offset]);
diff --git a/utils/tube.scad b/utils/tube.scad
new file mode 100644
index 0000000..fcb497c
--- /dev/null
+++ b/utils/tube.scad
@@ -0,0 +1,33 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Simple tube or ring
+//
+include <../core.scad>
+
+module ring(or, ir) //! Create a ring with specified external and internal radii
+ difference() {
+ circle4n(or);
+ circle4n(ir);
+ }
+
+module tube(or, ir, h, center = true) //! Create a tube with specified external and internal radii and height ```h```
+ linear_extrude(height = h, center = center, convexity = 5)
+ ring(or, ir);
diff --git a/vitamins/ball_bearing.scad b/vitamins/ball_bearing.scad
new file mode 100644
index 0000000..318a17b
--- /dev/null
+++ b/vitamins/ball_bearing.scad
@@ -0,0 +1,68 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Simple model of ball bearings with seals, the colour of which can be specified. If silver they are assumed to be metal and the
+//! part number gets a ZZ suffix. Any other colour is assumed to be rubber and the suffix is -2RS.
+//!
+//! If a ball bearing has a child it is placed on its top surface, the same as nuts and washers, etc.
+//!
+//! Also single bearing balls are modelled as just a silver sphere and a BOM entry.
+//
+include <../core.scad>
+
+function bb_name(type) = type[0]; //! Part code without shield type suffix
+function bb_bore(type) = type[1]; //! Internal diameter
+function bb_diameter(type) = type[2]; //! External diameter
+function bb_width(type) = type[3]; //! Width
+function bb_colour(type) = type[4]; //! Shield colour, "silver" for metal
+function bb_rim(type) = bb_diameter(type) / 10; //! Inner and outer rim thickness
+
+module ball_bearing(type) { //! Draw a ball bearing
+ shield = bb_colour(type);
+ suffix = shield == "silver" ? "ZZ " : "-2RS ";
+ vitamin(str("ball_bearing(BB", bb_name(type), "): Ball bearing ", bb_name(type), suffix, bb_bore(type), "mm x ", bb_diameter(type), "mm x ", bb_width(type), "mm"));
+ rim = bb_rim(type);
+ h = bb_width(type);
+ od = bb_diameter(type);
+ id = bb_bore(type);
+
+ module tube(od, id, h)
+ linear_extrude(height = h, center = true, convexity = 5)
+ difference() {
+ circle(d = od);
+ circle(d = id);
+ }
+
+ color("silver") {
+ tube(od, od - rim, h);
+ tube(id + rim, id, h);
+ }
+
+ color(shield) tube(od - rim, id + rim, h - 1);
+
+ if($children)
+ translate_z(bb_width(type) / 2)
+ children();
+}
+
+module bearing_ball(dia) { //! Draw a steel bearing ball
+ vitamin(str(" bearing_ball(", dia, "): Steel ball ", dia, "mm"));
+ color("silver") sphere(d = dia);
+}
diff --git a/vitamins/ball_bearings.scad b/vitamins/ball_bearings.scad
new file mode 100644
index 0000000..23fb42d
--- /dev/null
+++ b/vitamins/ball_bearings.scad
@@ -0,0 +1,24 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+BB624 = ["624", 4, 13, 5, "blue"]; // 624 ball bearing for idlers
+BB608 = ["608", 8, 22, 7, "OrangeRed"]; // 608 bearings for wades
+
+ball_bearings = [BB624, BB608];
+
+use
diff --git a/vitamins/batteries.scad b/vitamins/batteries.scad
new file mode 100644
index 0000000..9429f42
--- /dev/null
+++ b/vitamins/batteries.scad
@@ -0,0 +1,50 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// w h t t t p n
+// i e h a a o e
+// d i i b b s g
+// t g c
+// h h k w h h d d h s
+// t n 1 2 p
+// e r
+// s i
+// s n
+// g
+bcontact = ["bcontact", 9.33, 9.75, 0.4, 2.86, 6, [1.6, 3, 5], [4.5, batt_spring]];
+
+// l d n p p c L U c
+// e i e o o o E S o
+// n a g s s l D B n
+// g m o t
+// t e d d h u a
+// t r c
+// e t
+// r
+LUMINTOP = ["LUMINTOP", "Cell LUMINTOP 18650 LION with charger", 70.7, 18.4, 13, 5, 1, "white", [[3.32, 5], [3.32, -5]], 4, bcontact];
+S25R18650 = ["S25R18650", "Cell Samsung 25R 18650 LION", 65, 18.3, 13, 10, 0, "MediumSeaGreen", [], 0, bcontact];
+AACELL = ["AACELL", "Cell AA", 50.5, 14.5, 11, 5.5, 1, "grey", [], 0, bcontact];
+AAACELL = ["AAACELL", "Cell AAA", 44.5, 10.5, 8, 3.8, 0.8, "grey", [], 0, bcontact];
+CCELL = ["CCELL", "Cell C", 50, 26.2, 20, 7.5, 1.5, "brown", [], 0, bcontact];
+DCELL = ["DCELL", "Cell D", 61.5, 34.2, 22, 8.2, 2.4, "brown", [], 0, bcontact];
+
+batteries = [AAACELL, AACELL, CCELL, DCELL, LUMINTOP, S25R18650];
+
+use
diff --git a/vitamins/battery.scad b/vitamins/battery.scad
new file mode 100644
index 0000000..7fe609e
--- /dev/null
+++ b/vitamins/battery.scad
@@ -0,0 +1,161 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Actually just single cells at the moment, shown here with mating contacts in place.
+//!
+//! Note that the [Lumintop LM34](http://www.lumintop.com/lm34c-usb-rechargeable-18650-li-ion-battery.html) has a built in charger with a USB socket and two LEDs.
+//!
+//! The battery length includes its contacts and the origin is the centre of that length. As well as drawing the battery and contacts there are functions
+//! exposing enough information to make a battery box.
+//
+include <../core.scad>
+use
+use <../utils/rounded_cylinder.scad>
+
+function battery_length(type) = type[2]; //! Total length including terminals
+function battery_diameter(type) = type[3]; //! Casing diameter
+function battery_neg_dia(type) = type[4]; //! Negative terminal diameter
+function battery_pos_dia(type) = type[5]; //! Positive terminal diameter
+function battery_pos_height(type) = type[6]; //! Positive terminal height above the casing
+function battery_colour(type) = type[7]; //! Casing colour
+function battery_led_positions(type) = type[8]; //! LED positions for Lumintop
+function battery_usb_offset(type) = type[9]; //! USB connector offset from the top
+function battery_contact(type) = type[10]; //! Contact type
+
+module battery_led_positions(type) { //! Position of the LEDs on a Lumintop
+ posns = battery_led_positions(type);
+ for($i = [0 : 1 : len(posns) - 1])
+ translate([posns[$i].x, posns[$i].y, battery_length(type) / 2 - battery_pos_height(type)])
+ children();
+}
+
+module battery(type) { //! Draw a battery
+ vitamin(str("battery(", type[0], "): ", type[1]));
+ len = battery_length(type);
+
+ l = 6;
+ iw1 = 7;
+ iw2 = 5.7;
+ ih1 = 1;
+ ih2 = 1.85;
+ h = 2.65;
+ t = 0.4;
+
+ module D() {
+ hull() {
+ translate([-iw1 / 2, h - t - ih1])
+ square([iw1, ih1]);
+
+ translate([-iw2 / 2, h - t - ih2])
+ square([iw2, ih2]);
+ }
+ }
+
+ color(battery_colour(type)) render() difference() {
+ translate_z(-battery_pos_height(type) / 2)
+ cylinder(d = battery_diameter(type), h = len - battery_pos_height(type), center = true);
+
+ if(battery_usb_offset(type))
+ translate([battery_diameter(type) / 2, 0, len / 2 - battery_usb_offset(type) + h / 2])
+ rotate([-90, 0, 90])
+ linear_extrude(height = l + 1)
+ offset(delta = t)
+ D();
+ }
+ color("gold")
+ translate_z(len / 2 - battery_pos_height(type))
+ rounded_cylinder(r = battery_pos_dia(type) / 2, h = battery_pos_height(type) + eps, r2 = 0.5);
+
+ color("silver") {
+ if(battery_usb_offset(type))
+ translate([battery_diameter(type) / 2 - 1, 0, len / 2 - battery_usb_offset(type) + h / 2])
+ rotate([-90, 0, 90]) {
+ linear_extrude(height = l)
+ difference() {
+ offset(t) D();
+ D();
+ }
+
+ translate_z(l - 1)
+ linear_extrude(height = 1)
+ D();
+ }
+
+ translate_z(-len / 2)
+ vflip()
+ cylinder(d = battery_neg_dia(type), h = eps);
+
+ }
+
+ battery_led_positions(type)
+ color(["red","green","blue"][$i])
+ cylinder(d = 1.5, h = eps);
+}
+
+function contact_width(type) = type[1]; //! Width of the flat part
+function contact_height(type) = type[2]; //! Height of the flat part
+function contact_thickness(type) = type[3]; //! Thickness of the metal
+function contact_tab_width(type) = type[4]; //! Width of the tab
+function contact_tab_length(type) = type[5]; //! Length of the tab
+function contact_pos(type) = type[6]; //! Positive contact dimple height and top and bottom internal diameter
+function contact_neg(type) = type[7]; //! Negative spring height above the plate when compressed and the spring type
+
+module battery_contact(type, pos = true) { //! Draw a positive or negative battery contact for specified battery
+ vitamin(str("battery_contact(", type[0], ", ", pos, "): Battery ", pos ? "positive" : "negative", " contact"));
+
+ neg = 9;
+
+ tw = contact_tab_width(type);
+ h = contact_height(type);
+ hole_y = -contact_tab_length(type) + tw / 2;
+ t = contact_thickness(type);
+
+ color("silver") {
+ rounded_rectangle([contact_width(type), h, t], r = 1, center = false);
+
+ translate([0, -h / 2, t])
+ rotate([90, 0, 0])
+ linear_extrude(height = t)
+ difference() {
+ hull() {
+ translate([-tw / 2, -1])
+ square([tw, 1]);
+
+ translate([0, hole_y])
+ circle(d = tw);
+ }
+ translate([0, hole_y])
+ circle(tw / 4);
+ }
+
+ if(pos) {
+ p = contact_pos(type);
+
+ cylinder(d1 = p.z + 2 * t, d2 = p.y + 2 * t, h = p.x);
+ }
+ else {
+ p = contact_neg(type);
+
+ not_on_bom()
+ translate_z(t)
+ comp_spring(p.y, p.x - t);
+ }
+ }
+}
diff --git a/vitamins/belt.scad b/vitamins/belt.scad
new file mode 100644
index 0000000..37ad1d5
--- /dev/null
+++ b/vitamins/belt.scad
@@ -0,0 +1,81 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Models timing belt running over toothed or smooth pulleys and calculates an accurate length.
+//! Only models 2D paths, so not core XY!
+//!
+//! By default the path is a closed loop but a gap length and position can be specified to make open loops.
+//!
+//! Individual teeth are not drawn, instead they are represented by a lighter colour.
+//
+include <../core.scad>
+use <../utils/rounded_polygon.scad>
+
+function belt_pitch(type) = type[1]; //! Pitch in mm
+function belt_width(type) = type[2]; //! Width in mm
+function belt_thickness(type) = type[3]; //! Total thickness including teeth
+function belt_tooth_height(type) = type[4]; //! Tooth height
+function belt_pitch_height(type) = belt_tooth_height(type) + type[4]; //! Offset of the pitch radius from the tips of the teeth
+
+function no_point(str) = chr([for(c = str) if(c == ".") ord("p") else ord(c)]);
+//
+// We model the belt path at the pitch radius of the pulleys and the pitch line of the belt to get an accurate length.
+// The belt is then drawn by offseting each side from the pitch line.
+//
+module belt(type, points, gap = 0, gap_pt = undef) { //! Draw a belt path given a set of points and pitch radii where the pulleys are. Closed loop unless a gap is specified
+ belt_colour = grey20;
+ tooth_colour = grey50;
+ width = belt_width(type);
+ pitch = belt_pitch(type);
+ thickness = belt_thickness(type);
+ part = str(type[0],pitch);
+ vitamin(str("belt(", no_point(part), "x", width, ", ", points, arg(gap, 0), arg(gap_pt, undef), "): Belt ", part," x ", width, "mm x ", length, "mm"));
+
+ len = len(points);
+
+ tangents = rounded_polygon_tangents(points);
+
+ length = ceil((rounded_polygon_length(points, tangents) - gap) / pitch) * pitch;
+
+ module shape() rounded_polygon(points, tangents);
+
+ module gap()
+ if(gap)
+ translate(gap_pt)
+ square([gap, thickness + eps], center = true);
+
+ color(belt_colour)
+ linear_extrude(height = width, center = true)
+ difference() {
+ offset(thickness - belt_pitch_height(type)) shape();
+ offset(-belt_pitch_height(type) + belt_tooth_height(type)) shape();
+ gap();
+
+ }
+ color(tooth_colour)
+ linear_extrude(height = width, center = true)
+ difference() {
+ offset(-belt_pitch_height(type) + belt_tooth_height(type)) shape();
+ offset(-belt_pitch_height(type)) shape();
+ gap();
+ }
+}
+
+function belt_length(points, gap = 0) = rounded_polygon_length(points, rounded_polygon_tangents(points)) - gap; //! Compute belt length given path and optional gap
diff --git a/vitamins/belts.scad b/vitamins/belts.scad
new file mode 100644
index 0000000..904621f
--- /dev/null
+++ b/vitamins/belts.scad
@@ -0,0 +1,35 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Belt model
+//
+// p w t t p
+// i i h o i
+// t d i o t
+// c t c t c
+// h h k h h line from tooth base
+//
+T5x6 = ["T", 5, 6, 2.2, 1.2, 0.5];
+T5x10 = ["T", 5, 10, 2.2, 1.2, 0.5];
+T2p5x6 =["T", 2.5, 6, 1.7, 0.7, 0.3];
+GT2x6 = ["GT", 2.0, 6, 1.38, 0.75, 0.254];
+
+belts = [T5x6, T5x10, T2p5x6, GT2x6];
+use
diff --git a/vitamins/blower.scad b/vitamins/blower.scad
new file mode 100644
index 0000000..f2ea839
--- /dev/null
+++ b/vitamins/blower.scad
@@ -0,0 +1,127 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Models of radial blowers.
+//
+include <../core.scad>
+use <../utils/rounded_cylinder.scad>
+
+function blower_length(type) = type[2]; //! Length of enclosing rectangle
+function blower_width(type) = type[3]; //! Width of enclosing rectangle
+function blower_depth(type) = type[4]; //! Height
+function blower_bore(type) = type[5]; //! The air intake hole diameter
+function blower_screw(type) = type[6]; //! The type of screws needed
+function blower_hub(type) = type[7]; //! Rotor hub diameter
+function blower_axis(type) = type[8]; //! XY coordinates of the axle
+function blower_screw_hole(type) = type[9]; //! Screw hole diameter
+function blower_screw_holes(type) = type[10]; //! List of XY coordinates of the screw holes
+function blower_exit(type) = type[11]; //! The width of the exit port
+function blower_hub_height(type) = type[12]; //! Height of the rotor
+function blower_base(type) = type[13]; //! Thickness of the base
+function blower_top(type) = type[14]; //! Thickness of the top
+function blower_wall(type) = type[15]; //! Side wall thickness
+function blower_lug(type) = type[16]; //! Height of the lugs
+
+fan_colour = grey20;
+
+module blower(type) { //! Draw specified blower
+ length = blower_length(type);
+ width = blower_width(type);
+ depth = blower_depth(type);
+ screw = blower_screw(type);
+
+ r1 = blower_axis(type)[0];
+ r2 = width - blower_axis(type)[1];
+ r3 = length - blower_axis(type)[0];
+ function radius(a) = a < 90 ? r1 * exp(a * ln(r2 / r1) / 90)
+ : r2 * exp((a - 90) * ln(r3 / r2) / 90);
+ function spiral(a) = let(r = radius(a)) [-r * cos(a), r * sin(a)];
+
+ module shape(inside = false)
+ union() {
+ hull() {
+ translate(blower_axis(type))
+ polygon([for(a = [0 : 1 : 360]) spiral(a)]);
+
+ if(blower_exit(type) > length / 2)
+ square([blower_exit(type), 1]);
+ }
+ offset = inside ? 5 : 0;
+ translate([0, -offset])
+ square([blower_exit(type), blower_axis(type)[1] + offset]);
+ }
+
+ vitamin(str("blower(", type[0], "): ", type[1]));
+
+ color(fan_colour) {
+ // screw lugs
+ linear_extrude(height = blower_lug(type), center = false)
+ for(hole = blower_screw_holes(type))
+ difference() {
+ hull() {
+ translate(hole)
+ circle(d = blower_screw_hole(type) + 2 * blower_wall(type));
+
+ translate(blower_axis(type))
+ circle(d = blower_screw_hole(type) + 2 * blower_wall(type) + 7);
+ }
+ circle(d = blower_screw_hole(type));
+
+ shape(true);
+ }
+ // rotor
+ translate(concat(blower_axis(type), [blower_base(type) + 1]))
+ rounded_cylinder(r = blower_hub(type) / 2, h = blower_hub_height(type) - blower_base(type) - 1, r2 = 1);
+
+ *%square([length, width]);
+
+ // base
+ linear_extrude(height = blower_base(type))
+ difference() {
+ shape();
+
+ translate(concat(blower_axis(type), [blower_base(type)]))
+ circle(d = 2);
+ }
+ // sides
+ linear_extrude(height = depth)
+ difference() {
+ shape();
+
+ offset(-blower_wall(type))
+ shape(true);
+ }
+
+ // top
+ translate_z(depth -blower_top(type))
+ linear_extrude(height = blower_top(type))
+ difference() {
+ shape();
+
+ translate(concat(blower_axis(type), [blower_base(type)]))
+ circle(d = blower_bore(type));
+ }
+ }
+}
+
+module blower_hole_positions(type) //! Translate children to screw hole positions
+ for(hole = blower_screw_holes(type))
+ translate(hole)
+ children();
diff --git a/vitamins/blowers.scad b/vitamins/blowers.scad
new file mode 100644
index 0000000..3e1b2f4
--- /dev/null
+++ b/vitamins/blowers.scad
@@ -0,0 +1,25 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+RB5015 = ["RM5015", "Blower Runda RB5015", 51.3, 51, 15, 31.5, M4_cap_screw, 26, [27.3, 25.4], 4.5, [[4.3, 45.4], [47.3,7.4]], 20, 14, 1.5, 1.3, 1.2, 15];
+PE4020 = ["PE4020", "Blower Pengda Technology 4020", 40, 40, 20, 27.5, M3_cap_screw, 22, [21.5, 20 ], 3.2, [[37,3],[3,37],[37,37]], 29.3, 17, 1.7, 1.2, 1.3, 13];
+
+blowers = [PE4020, RB5015];
+
+use
diff --git a/vitamins/bulldog.scad b/vitamins/bulldog.scad
new file mode 100644
index 0000000..874e0b2
--- /dev/null
+++ b/vitamins/bulldog.scad
@@ -0,0 +1,83 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Crude representation of a bulldog clip. The handle is not currently drawn but its length can be
+//! accessed to allow clearance. Used for holding glass on 3D printer beds but Swiss picture clips can be
+//! better.
+//
+include <../core.scad>
+use <../utils/rounded_polygon.scad>
+use <../utils/sector.scad>
+
+function bulldog_length(type) = type[1]; //! Length along the profile
+function bulldog_depth(type) = type[2]; //! Depth from the back to the front of the tubes
+function bulldog_height(type) = type[3]; //! Height at the back
+function bulldog_thickness(type) = type[4]; //! Thickness of the metal
+function bulldog_tube(type) = type[5] / 2; //! Outside diameter of the tubes
+function bulldog_radius(type) = type[6]; //! Outside radius of the back corners
+function bulldog_handle_length(type) = type[7]; //! Length that the handle protrudes from the back
+
+module bulldog_shape(depth, height, radius, tube, open) {
+ rounded_polygon([
+ [-depth / 2 + radius, -height / 2 + radius, radius],
+ [-depth / 2 + radius, height / 2 - radius, radius],
+ [ depth / 2 - tube, open / 2 + tube, -tube],
+ [ depth / 2 + tube, height / 2, tube],
+ [ depth / 2 + tube, -height / 2, tube],
+ [ depth / 2 - tube, -open / 2 - tube, -tube],
+
+ ]);
+}
+
+module bulldog(type, open = 4) { //! Draw bulldog clip open by specified amount
+ tube = bulldog_tube(type);
+ thickness = bulldog_thickness(type);
+ depth = bulldog_depth(type);
+ length = bulldog_length(type);
+ height = bulldog_height(type);
+ rad = bulldog_radius(type);
+ gap = open + thickness * 2;
+
+ vitamin(str("bulldog(", type[0], "): Bulldog clip ",length, "mm"));
+
+ color("yellow")
+ translate([depth / 2 - thickness - eps, 0])
+ rotate([90, 0, 0])
+ linear_extrude(length, center = true)
+ union() {
+ difference() {
+ bulldog_shape(depth, height, rad, tube, gap);
+
+ offset(-thickness)
+ bulldog_shape(depth, height, rad, tube, gap);
+
+ translate([depth / 2 - tube, -height])
+ square([depth, 2 * height]);
+ }
+
+ for(side = [-1, 1])
+ translate([depth / 2 - tube, side * (open / 2 + tube)])
+ difference() {
+ mirror([0, side < 0 ? 1 : 0])
+ sector(tube, -90, 210);
+ circle(tube - thickness);
+ }
+ }
+}
diff --git a/vitamins/bulldogs.scad b/vitamins/bulldogs.scad
new file mode 100644
index 0000000..23bfb5a
--- /dev/null
+++ b/vitamins/bulldogs.scad
@@ -0,0 +1,35 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Crude representation of a bulldog clip
+//
+// l d h t t r h
+// e e e h u a a
+// n p i i b d n
+// g t g c e i d
+// t h h k u l
+// h s e
+//
+small_bulldog = ["small_bulldog", 19, 12, 8, 0.25, 2.67, 1, 16];
+large_bulldog = ["large_bulldog", 25, 15,12, 0.28, 3.00, 2.4, 20];
+
+bulldogs = [small_bulldog, large_bulldog];
+
+use
diff --git a/vitamins/button.scad b/vitamins/button.scad
new file mode 100644
index 0000000..aaa3f94
--- /dev/null
+++ b/vitamins/button.scad
@@ -0,0 +1,74 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! PCB mounted buttons. Can optionally have a coloured cap
+//
+include <../core.scad>
+use <../utils/rounded_cylinder.scad>
+
+function square_button_width(type) = type[1]; //! Width and depth of the base
+function square_button_height(type) = type[2]; //! Height of the base
+function square_button_wall(type) = type[3]; //! Offset of the metal part
+function square_button_rivit(type) = type[4]; //! Size of the corner rivets
+function square_button_d(type) = type[5]; //! Button diameter
+function square_button_h(type) = type[6]; //! Height of the button above the PCB
+function square_button_cap_flange_d(type) = type[7]; //! Diameter of the flange of the cap
+function square_button_cap_d(type) = type[8]; //! Diameter of the body of the cap
+function square_button_cap_h(type) = type[9]; //! Height of the cap including the stem
+function square_button_cap_stem(type) = type[10]; //! Length of the cap stem
+function square_button_cap_flange_h(type) = type[11]; //! Height of the cap flange
+
+module square_button(type, colour = "yellow") { //! Draw square button with specified cap colour if it has a cap
+ w = square_button_width(type);
+ flange_d = square_button_cap_flange_d(type);
+ vitamin(str("square_button(", type[0], flange_d ? str(", \"", colour, "\"") : "", "): Square button ", w, "mm",
+ flange_d ? str(" with ", colour, " cap") : ""));
+ h = square_button_height(type);
+ wall = square_button_wall(type);
+ rivit = square_button_rivit(type);
+ pitch = (w/ 2 - wall - rivit * 0.75);
+ stem = square_button_cap_stem(type);
+
+ color(grey20) {
+ rounded_rectangle([w, w, h - 0.5], r = wall, center = false);
+
+ for(x = [-1, 1], y = [-1, 1])
+ translate([x * pitch, y * pitch])
+ cylinder(d = rivit, h = h);
+
+ cylinder(d = square_button_d(type), h = square_button_h(type));
+ }
+
+ color("silver")
+ translate_z(h - 0.5)
+ rounded_rectangle([w - 2 * wall, w - 2 * wall, 0.2], r = wall, center = true);
+
+ if(flange_d)
+ translate_z(square_button_h(type))
+ color(colour) rotate_extrude() {
+ square([square_button_d(type) / 2, stem]);
+
+ translate([0, stem]) {
+ square([flange_d / 2, square_button_cap_flange_h(type)]);
+
+ rounded_corner(r = square_button_cap_d(type) / 2, h = square_button_cap_h(type) - stem, r2 = 0.5);
+ }
+ }
+}
diff --git a/vitamins/buttons.scad b/vitamins/buttons.scad
new file mode 100644
index 0000000..e4f64ac
--- /dev/null
+++ b/vitamins/buttons.scad
@@ -0,0 +1,33 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// w h w r b b c c c c c
+// i e a i u u a a a a a
+// d i l v t t p p p p p
+// t g l i
+// h h t d h f d h s f
+// d t h
+button_12mm = ["button_12mm", 12, 4.0, 0.8, 1.5, 6.8, 4.3, 12.86, 11.44, 8.15, 2.7, 1.4];
+button_6mm = ["button_6mm", 6, 4.0, 0.2, 1.0, 3.5, 5.0, 0];
+button_4p5mm= ["button_4p5mm", 4.5, 3.1, 0.1, 0.9, 2.4, 4.5, 0];
+
+buttons = [button_4p5mm, button_6mm, button_12mm];
+
+use
diff --git a/vitamins/cable_strip.scad b/vitamins/cable_strip.scad
new file mode 100644
index 0000000..92bbd13
--- /dev/null
+++ b/vitamins/cable_strip.scad
@@ -0,0 +1,159 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! A strip of polypropylene used with ribbon cable to make a cable flexible in one direction only.
+//!
+//! Modelled with a Bezier spline, which is not quite the same as a miniumum energy curve but very close, epecially
+//! near the extreme positions, where the model needs to be accurate.
+//!
+//! When the sides are constrained then a circular model is more accurate.
+//
+include <../core.scad>
+cable_strip_thickness = 0.8;
+function ribbon_clamp_slot(ways) = ways * inch(0.05) + 1;
+function ribbon_clamp_slot_depth() = cable_strip_thickness + inch(0.05);
+function cable_strip_thickness() = cable_strip_thickness;
+
+use <../utils/bezier.scad>
+use <../utils/sweep.scad>
+
+cable_strip_color = "green";
+
+function cable_strip_control_points(depth, min_z, pos) = let(z = min(min_z, min_z + pos))
+[
+ [0, 0, 0], [0, 0, z], [0, depth, z], [0, depth, pos]
+];
+
+function bezier_cable_length(depth, min_z, pos) = //! Calculate a length that will achieve the desired minimum z
+ bezier_length(adjust_bezier_z(cable_strip_control_points(depth, min_z, pos), min_z));
+
+module bezier_cable_strip(ways, depth, length, travel, pos, below, extra) { //! Draw a cable strip using a Bezier curve
+ width = ceil(ribbon_clamp_slot(ways) - 1);
+
+ thickness = cable_strip_thickness;
+
+ total = 2 * extra + length;
+
+ vitamin(str("bezier_cable_strip(", ways, ", ", depth, ", ", length, ", ", travel, ", ", pos, ", ", below, ", ", extra,
+ "): Polypropylene strip ", total, "mm x ", width, "mm x ", thickness, "mm"));
+
+ c = cable_strip_control_points(depth, -below + extra, pos);
+ v = adjust_bezier_length(c, length);
+
+ steps = 100;
+ extra_v = [0, 0, extra];
+
+ path = [v[0] + extra_v, each bezier_path(v, steps), v[3] + extra_v];
+
+ color(cable_strip_color)
+ translate_z(-extra)
+ sweep(path, rectangle_points(width, thickness));
+ *echo(cable_strip_lengh = length);
+ *translate_z(-extra) sweep(v, circle_points(1));
+}
+
+function cable_strip_length(depth, travel, extra = 15) = ceil(travel / 2 + 2 * extra + PI * depth); //! Calculate circular cable strip length
+
+module cable_strip(ways, depth, travel, x, extra = 15) { //! Draw a cable stripe with a semi circular fold
+
+ width = ribbon_clamp_slot(ways);
+
+ thickness = cable_strip_thickness;
+
+ radius = depth / 2;
+
+ top = travel / 4 + extra + x / 2;
+ bottom = travel / 4 + extra - x /2;
+
+ length = max(top, bottom);
+
+ total = ceil(top + bottom + PI * depth);
+ w = floor(width - 2);
+
+ vitamin(str("cable_strip(", ways, ", ", depth, ", ", travel, ", ", x, arg(extra, 15), "): Polypropylene strip ", total, "mm x ", w, "mm x ", thickness, "mm"));
+
+ color(cable_strip_color) linear_extrude(height = w, center = true, convexity = 4)
+ difference() {
+ union() {
+ translate([-bottom, radius])
+ circle(radius);
+
+ translate([-bottom, 0])
+ square([length, depth]);
+ }
+ union() {
+ translate([-bottom, radius])
+ circle(radius - thickness);
+
+ translate([-bottom, thickness])
+ square([length + 1, depth - thickness * 2]);
+ }
+ translate([0, -thickness / 2])
+ square([travel, thickness * 2]);
+
+ translate([x, depth - thickness - thickness / 2])
+ square([travel, thickness * 2]);
+ }
+}
+
+function elliptical_cable_strip_length(p1, pmax, extra = 15) = ceil(PI * pow((pow(abs((pmax - p1)[0] / 2),1.5) + pow(75,1.5))/2, 1/1.5)) + 2 * extra;
+
+module elliptical_cable_strip(ways, p1, p2, pmax, extra = 15) {
+ width = ribbon_clamp_slot(ways);
+
+ thickness = cable_strip_thickness;
+ w = floor(width - 1);
+
+ max_delta = pmax - p1;
+ delta = p2 - p1;
+
+ A = abs(max_delta[0] / 2);
+ B = 75;
+
+ length = ceil(PI * pow((pow(A,1.5) + pow(B,1.5))/2, 1/1.5));
+ total = length + 2 * extra;
+
+ vitamin(str("elliptical_cable_strip(", ways, ", ", p1, ", ", p2, ", ", pmax, arg(extra, 15),
+ "): Polypropylene strip ", total, "mm x ", w, "mm x ", thickness, "mm"));
+
+ a = abs(delta[0] / 2);
+ b = pow(2 * pow(length / PI, 1.5) - pow(a, 1.5), 1/1.5);
+
+ translate(p1 - [a, 0, 0])
+ multmatrix(m = [ [1, 0, 0, 0],
+ [delta[1] / delta[0], 1, 0, delta[1] / 2],
+ [delta[2] / delta[0], 0, 1, delta[2] / 2],
+ [0, 0, 0, 1] ])
+
+ color(cable_strip_color) linear_extrude(height = w, center = true, convexity = 4)
+ difference() {
+ union() {
+ square([(a + thickness) * 2, extra * 2], center = true);
+ translate([0, -extra])
+ ellipse((a + thickness), b + thickness);
+ }
+ translate([0, (b + 1) / 2])
+ square([a * 2 + 1, b + 1], center = true);
+
+ square([a * 2, extra * 2], center = true);
+ translate([0, -extra])
+ ellipse(a, b);
+ }
+}
diff --git a/vitamins/component.scad b/vitamins/component.scad
new file mode 100644
index 0000000..346b1a9
--- /dev/null
+++ b/vitamins/component.scad
@@ -0,0 +1,450 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Various electronic components used in hot ends and heated beds.
+//
+
+//
+// Resistor model for hot end
+//
+include <../core.scad>
+include
+include
+include
+use <../utils/rounded_cylinder.scad>
+
+function resistor_length(type) = type[2]; //! Body length
+function resistor_diameter(type) = type[3]; //! Body diameter
+function resistor_wire_diameter(type) = type[4]; //! Wire diameter
+function resistor_wire_length(type) = type[5]; //! Wire length from body
+function resistor_hole(type) = type[6]; //! Hole big enough to glue it into
+function resistor_colour(type) = type[7]; //! Body colour
+function resistor_radial(type) = type[8]; //! Radial gives bead thermistor style body
+function resistor_sleeved(type) = type[9]; //! Are the leads sleeved
+
+splay_angle = 2; // radial lead splay angle
+
+module resistor(type) { //! Draw specified type of resitor
+ length = resistor_length(type);
+ dia = resistor_diameter(type);
+
+ vitamin(str("resistor(", type[0], "): ", type[1]));
+ //
+ // wires
+ //
+ color([0.7, 0.7, 0.7])
+ if(resistor_radial(type))
+ for(side= [-1,1])
+ translate([side * dia / 6, 0, length / 2])
+ rotate([0, splay_angle * side, 0])
+ cylinder(r = resistor_wire_diameter(type) / 2, h = resistor_wire_length(type), center = false);
+ else
+ cylinder(r = resistor_wire_diameter(type) / 2, h = length + 2 * resistor_wire_length(type), center = true);
+ //
+ // Sleeving
+ //
+ if(resistor_sleeved(type))
+ color([0.5, 0.5, 1])
+ if(resistor_radial(type))
+ for(side= [-1, 1])
+ translate([side * resistor_diameter(type) / 6, 0, length / 2]) {
+ rotate([0, splay_angle * side, 0])
+ cylinder(r = resistor_wire_diameter(type) / 2 + 0.1, h = resistor_wire_length(type) - 5, center = false); }
+ //
+ // Body
+ //
+ color(resistor_colour(type))
+ if(resistor_radial(type))
+ hull() {
+ translate_z(-length / 2 + dia / 2)
+ sphere(d = dia);
+
+ cylinder(d = dia / 2, h = length / 2);
+ }
+ else
+ rotate_extrude()
+ for(y = [0, 1])
+ mirror([0, y])
+ rounded_corner(r = dia / 2, h = length / 2, r2 = dia / 10);
+}
+
+module sleeved_resistor(type, sleeving, bare = 5, heatshrink = false) { //! Draw a resistor with sleeved leads and option heatshrink
+ resistor(type);
+ sleeving_length = resistor_wire_length(type) - bare;
+
+ for(side= [-1,1])
+ if(resistor_radial(type)) {
+ translate([side * resistor_diameter(type) / 6, 0, 0])
+ rotate([0, splay_angle * side, 0]) {
+ if(!resistor_sleeved(type))
+ translate_z(sleeving_length / 2 + resistor_length(type) / 2 + 20 * exploded())
+ tubing(sleeving, sleeving_length);
+
+ if(heatshrink)
+ translate_z(sleeving_length + resistor_length(type) / 2 + bare / 2 + 30 * exploded())
+ tubing(heatshrink);
+ }
+ }
+ else {
+ translate_z(side * (resistor_length(type) + sleeving_length + 40 * exploded()) / 2)
+ tubing(sleeving, sleeving_length);
+
+ if(heatshrink)
+ translate_z(side * (resistor_length(type) /2 + sleeving_length + 30 * exploded()))
+ tubing(heatshrink);
+ }
+}
+
+function al_clad_length(type) = type[1]; //! Body length
+function al_clad_width(type) = type[2]; //! Width including tabs
+function al_clad_tab(type) = type[3]; //! Tab width
+function al_clad_hpitch(type) = type[4]; //! Lengthways pitch between screw holes
+function al_clad_vpitch(type) = type[5]; //! Widthways pitch between screw holes
+function al_clad_thickness(type) = type[6]; //! Tab thickness
+function al_clad_hole(type) = type[7]; //! Hole diameter
+function al_clad_clearance(type) = type[8]; //! Clearance from screw hole centre to the body
+function al_clad_height(type) = type[9]; //! Body height
+function al_clad_wire_length(type) = type[10]; //! Total length including wires
+
+module al_clad_resistor_hole_positions(type) //! Position children at the screw holes of an aluminium clad resistor
+ for(end = [-1, 1])
+ translate([end * al_clad_hpitch(type) / 2, end * al_clad_vpitch(type) / 2, al_clad_thickness(type)])
+ children();
+
+module al_clad_resistor_holes(type, h = 100) //! Drill screw holes for an aluminium clad resistor
+ al_clad_resistor_hole_positions(type)
+ drill(screw_clearance_radius(al_clad_hole(type) > 3 ? M3_pan_screw : M2p5_pan_screw), h);
+
+module al_clad_resistor(type, value, leads = true) { //! Draw an aluminium clad resistor
+ vitamin(str("al_clad_resistor(", type[0], ", ", value, arg(leads, true, "leads"),
+ "): Resistor aluminium clad ", type[0], " ", value));
+ length = al_clad_length(type);
+ width = al_clad_width(type);
+ height = al_clad_height(type);
+ tab = al_clad_tab(type);
+ thickness = al_clad_thickness(type);
+ terminal_h = 4;
+ terminal_t = 1;
+ terminal_l = 5;
+
+ body = al_clad_vpitch(type) - 2 * al_clad_clearance(type);
+
+ color("silver") {
+ rotate([90, 0, 90])
+ linear_extrude(height = length, center = true)
+ hull() {
+ translate([0, al_clad_height(type) / 2])
+ intersection() {
+ square([body, al_clad_height(type)], center = true);
+
+ circle(body / 2 - eps);
+ }
+ translate([0, thickness / 2])
+ square([body, thickness], center = true);
+ }
+ linear_extrude(height = thickness)
+ difference() {
+ for(end = [-1, 1])
+ translate([end * (length - tab) / 2, end * (width - width / 2) / 2])
+ square([tab, width / 2], center = true);
+
+ al_clad_resistor_hole_positions(type)
+ circle(d = al_clad_hole(type));
+
+ }
+ if(leads) {
+ translate_z(height / 2)
+ rotate([0, 90, 0])
+ cylinder(r = 1, h = al_clad_wire_length(type) - 2 * terminal_l + eps, center = true);
+
+ for(end = [-1, 1])
+ translate([end * (al_clad_wire_length(type) - terminal_l) / 2, 0, height / 2])
+ rotate([90, 0, 0])
+ linear_extrude(height = terminal_t, center = true) difference() {
+ square([terminal_l, terminal_h], center = true);
+
+ circle(r = 1);
+ }
+ }
+ }
+ color("black")
+ translate_z(height / 2)
+ rotate([0, 90, 0])
+ cylinder(r = leads ? 3 : height / 2 - 2, h = length + eps, center = true);
+}
+
+module al_clad_resistor_assembly(type, value, sleeved = true) { //* Draw aluminium clad resistor with optional sleaving, positions children at the screw positions
+ sleeving_length = 15;
+ sleeving = HSHRNK32;
+
+ al_clad_resistor(type, value);
+
+ if(sleeved)
+ for(end = [-1, 1])
+ translate([end * (al_clad_length(type) + sleeving_length + 0) / 2, 0, al_clad_height(type) / 2])
+ rotate([0, 90, 0])
+ scale([1.5, 0.66, 1])
+ tubing(sleeving, sleeving_length);
+
+ al_clad_resistor_hole_positions(type)
+ children();
+}
+
+function TO220_thickness() = 1.5; //! Thickness of the tab of a TO220
+
+module TO220(description, leads = 3, lead_length = 16) { //! Draw a TO220 package, use ```description``` to describe what it is
+ width = 10.2;
+ inset = 1.5;
+ hole = 3.3;
+ length = 15;
+ height = 4.4;
+ lead_height = 1.9;
+ lead_t = 0.4;
+ lead_w = 0.7;
+ lead_w2 = 1.4;
+ lead_l = 4.2;
+ body = 8;
+ hole_y = 2.9;
+
+ vitamin(str("TO220(\"", description, "\"", arg(leads, 3, "leads"), arg(lead_length, 16, "lead_length"), "): ", description));
+
+ translate([0, -length + hole_y]) {
+ color("silver") {
+ linear_extrude(height = TO220_thickness())
+ difference() {
+ translate([-width / 2, inset])
+ square([width, length - inset]);
+
+ translate([0, length - hole_y])
+ circle(d = hole);
+
+ for(side = [-1, 1])
+ translate([side * width / 2, 0])
+ square([inset * 2, body * 2], center = true);
+ }
+
+ for(i = [-1 : 1])
+ if(i || leads == 3) {
+ translate([inch(0.1) * i, -lead_length / 2, lead_height])
+ cube([lead_w, lead_length, lead_t], center = true);
+
+ translate([inch(0.1) * i, -lead_l / 2, lead_height])
+ cube([lead_w2, lead_l, lead_t], center = true);
+ }
+ }
+ color("dimgrey")
+ translate([-width / 2, 0, eps])
+ cube([width, body, height]);
+ }
+ translate_z(TO220_thickness())
+ children();
+}
+
+panel_USBA_pitch = 30;
+
+module panel_USBA_hole_positions() //! Place children at hole positions
+ for(side = [-1, 1])
+ translate([side * panel_USBA_pitch / 2, 0])
+ children();
+
+module panel_USBA_holes(h = 100) { //! Make holes for USBA connector
+ corner_clearance = 2 * cnc_bit_r * (1 - 1 / sqrt(2));
+ width = 5.5 + corner_clearance;
+ length = 13 + corner_clearance;
+
+ extrude_if(h) union() {
+ rounded_square([length, width], r = cnc_bit_r);
+
+ panel_USBA_hole_positions()
+ drill(M3_clearance_radius, 0);
+ }
+}
+
+module panel_USBA() { //! Draw a panel mount USBA connector
+ vitamin("panel_USBA(): Socket USB A panel mount");
+
+ width = 12;
+ length = 40;
+ length2 = 22;
+ thickness = 5.5;
+ height = 33;
+ height2 = 27;
+ lead_dia = 10;
+ r1 = 1.5;
+ r2 = 5;
+ height3 = 9.5;
+ length3 = 17.5;
+
+ l = 17;
+ w = 13.3;
+ h = 5.7;
+ flange_t = 0.4;
+ h_flange_h = 0.8;
+ h_flange_l = 11.2;
+
+ v_flange_h = 0.8;
+ v_flange_l = 3.8;
+ tongue_w = 10;
+ tongue_t = 1.3;
+
+ vflip() {
+ color("dimgrey") {
+ linear_extrude(height = thickness)
+ difference() {
+ hull()
+ for(side = [-1, 1])
+ translate([side * (length / 2 - width / 2), 0])
+ circle(d = width);
+
+ square([length3, width + 1], center = true);
+
+ panel_USBA_hole_positions()
+ circle(M3_clearance_radius);
+ }
+
+ translate_z(height2)
+ cylinder(d = lead_dia, h = height - height2);
+
+ hull() {
+ dx = (length2 / 2 - r2);
+ dy = (width / 2 - r1);
+ translate_z(l)
+ rounded_rectangle([length2, width, 1], r = r1, center = false);
+
+ translate([-dx, -dy, height2 - r2])
+ rotate([90, 0, 0])
+ rounded_cylinder(r = r2, r2 = r1, h = r1);
+
+ translate([dx, -dy, height2 - r2])
+ rotate([90, 0, 0])
+ rounded_cylinder(r = r2, r2 = r1, h = r1);
+
+ translate([-dx, dy, height2 - r2])
+ rotate([-90, 0, 0])
+ rounded_cylinder(r = r2, r2 = r1, h = r1);
+
+ translate([dx, dy, height2 - r2])
+ rotate([-90, 0, 0])
+ rounded_cylinder(r = r2, r2 = r1, h = r1);
+ }
+
+ translate_z(height3)
+ linear_extrude(height = l - height3)
+ difference() {
+ rounded_square([length2, width], r = r1);
+
+ square([w - flange_t, h - flange_t], center = true);
+ }
+
+ linear_extrude(height = height3)
+ difference() {
+ rounded_square([length2, width], r = r1);
+
+ square([length3, width + 1], center = true);
+ }
+ }
+
+ *cube([12, 4.5, 32], center = true);
+
+ color("silver") {
+ linear_extrude(height = l)
+ difference() {
+ square([w, h], center = true);
+
+ square([w - 2 * flange_t, h - 2 * flange_t], center = true);
+ }
+
+ translate_z(l - flange_t / 2)
+ cube([w, h, flange_t], center = true);
+
+ linear_extrude(height = flange_t)
+ difference() {
+ union() {
+ square([h_flange_l, h + 2 * h_flange_h], center = true);
+
+ square([w + 2 * v_flange_h, v_flange_l], center = true);
+ }
+ square([w - 2 * flange_t, h - 2 * flange_t], center = true);
+ }
+ }
+
+ color("white")
+ translate([0, h / 2 - 1 - tongue_t / 2, l / 2])
+ cube([tongue_w, tongue_t, l], center = true);
+ }
+}
+
+function tc_length(type) = type[1]; //! Across the lugs
+function tc_width(type) = type[2]; //! Width of lugs
+function tc_thickness(type) = type[3]; //! Metal thickness
+function tc_hole_dia(type) = type[4]; //! Screw hole diameter
+function tc_hole_pitch(type) = type[5]; //! Screw hole pitch
+function tc_body_length(type) = type[6]; //! Plastic body length
+function tc_body_width(type) = type[7]; //! Plastic body width
+function tc_body_height(type) = type[8]; //! Plastic body height
+function tc_body_inset(type) = type[9]; //! How far metal is inset into the plastic body
+function tc_spade_height(type) = type[10]; //! Terminal spade height measured from base
+function tc_spade_pitch(type) = type[11]; //! Terminal spade pitch
+
+module thermal_cutout_hole_positions(type) //! Place children at hole positions
+ for(side = [-1, 1])
+ translate([side * tc_hole_pitch(type) / 2, 0])
+ children();
+
+module thermal_cutout(type) { //! Draw specified thermal cutout
+ vitamin(str("thermal_cutout(", type[0], "): Thermal cutout ", type[0]));
+
+ w = tc_width(type);
+ t = tc_thickness(type);
+ h = tc_body_height(type);
+ bw = tc_body_width(type);
+ bl = tc_body_length(type);
+ spade = spade6p4;
+
+ color("silver") {
+ linear_extrude(height = tc_thickness(type))
+ difference() {
+ hull()
+ for(side = [-1, 1])
+ translate([side *(tc_length(type) - w) / 2, 0])
+ circle(d = w);
+
+ thermal_cutout_hole_positions(type)
+ circle(d = tc_hole_dia(type));
+ }
+
+ body_inset = tc_body_inset(type);
+ translate_z((h - body_inset) / 2)
+ cube([bl - 2 * body_inset, bw + 2 * eps, h - body_inset], center = true);
+ }
+
+ color("black")
+ translate_z(h / 2 + eps)
+ cube([bl, bw, h], center = true);
+
+ for(side = [-1, 1])
+ translate([side * tc_spade_pitch(type) / 2, 0, h])
+ rotate(90)
+ spade(spade, tc_spade_height(type) - h);
+
+ translate_z(t)
+ thermal_cutout_hole_positions(type)
+ children();
+}
diff --git a/vitamins/components.scad b/vitamins/components.scad
new file mode 100644
index 0000000..27b4f8a
--- /dev/null
+++ b/vitamins/components.scad
@@ -0,0 +1,50 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Resistor model for hot end
+//
+RWM04106R80J = [ "RWM04106R80J", "Resistor RWM04106R80J 6R8 3W vitreous enamel", 12, 5, 0.8, 30, 5.5, "green", false, false];
+RIE1212UB5C5R6 = [ "RIE1212UB5C5R6", "Resistor UB5C 5R6F 5R6 3W vitreous enamel", 13, 5.9, 0.96, 35, 6.0, "gray", false, false];
+//
+// Thermistors
+//
+Honewell = [ "Honewell", "Thermistor Honeywell 135-104LAC-J01 100K 1%", 4.75, 1.8, 0.5, 28.6, 2, "red", false];
+Epcos = [ "Epcos", "Thermistor Epcos B57560G104F 100K 1%", 4.6, 2.5, 0.3, 67, 2.5, [0.8, 0.8, 0.8, 0.25], true, false];
+EpcosBlue = [ "EpcosBlue", "Thermistor Epcos B57861S104F40 100K 1%", 6.5, 2.41,0.25, 43.5,2.5, "black", true, true];
+
+resistors = [Honewell, Epcos, EpcosBlue, RWM04106R80J, RIE1212UB5C5R6];
+//
+// Aluminium clad resistors used for heated beds and dummy loads.
+//
+// l w tab hp vp t hd clr h wire
+THS10 = [ "THS10", 17, 17, 4.8, 11.3, 12.4, 2.5, 2.4, 1.9, 9, 30.0];
+THS15 = [ "THS15", 21, 21, 6.0, 14.3, 15.9, 3.2, 2.4, 1.9, 11, 36.5];
+THS25 = [ "THS25", 29, 28, 9.4, 18.3, 19.8, 3.2, 3.3, 2.8, 15, 47];
+THS50 = [ "THS50", 51, 30, 10.7, 39.7, 21.4, 3.2, 3.3, 2.8, 17, 72.5];
+
+al_clad_resistors = [ THS10, THS15, THS25, THS50];
+//
+// Thermal cutout used on heated beds.
+//
+TC = ["TC", 31.4, 9.13, 0.8, 3.9, 24, 14.56, 11.3, 14.13, 2, 25, 10];
+
+thermal_cutouts = [ TC ];
+
+use
diff --git a/vitamins/d_connector.scad b/vitamins/d_connector.scad
new file mode 100644
index 0000000..655c736
--- /dev/null
+++ b/vitamins/d_connector.scad
@@ -0,0 +1,191 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! D-connectors. Can be any number of ways, male or female, solder buckets, PCB mount or IDC, with or without pillars.
+//
+include <../core.scad>
+
+d_pillar_color = grey90;
+d_plug_shell_color = grey80;
+d_plug_insulator_color = grey20;
+
+function d_flange_length(type) = type[1]; //! Length of the flange
+function d_lengths(type) = type[2]; //! Lengths of the D for plug and socket
+function d_hole_pitch(type) = type[3]; //! Mounting hole pitch
+function d_widths(type) = type[4]; //! Widths of the D for plug and socket
+function d_flange_width(type) = type[5]; //! Width of the flange
+function d_height(type) = type[6]; //! From the front to the back of the metal part
+function d_front_height(type) = type[7]; //! From the back of the flange to the front
+function d_flange_thickness(type) = type[8]; //! Thickness of the flange
+function d_ways(type) = type[9]; //! Number of ways
+function d_mate_distance(type) = 8.5; //! Spacing when mated
+function d_pcb_offset(type) = d_height(type) - d_front_height(type) + 2; //! Height of the back of the flange above the PCB
+
+function d_slot_length(type) = d_lengths(type)[0] + 3; //! Slot to clear the back
+
+module d_connector_holes(type) //! Place children at the screw hole positions
+ for(end = [-1, 1])
+ translate([end * d_hole_pitch(type) / 2, 0])
+ children();
+
+module d_pillar() { //! Draw a pillar for a D-connector
+ vitamin("d_pillar(): D-type connector pillar");
+ rad = 5.37 / 2;
+ height = 4.5;
+ screw = 2.5;
+ screw_length = 8;
+ color(d_pillar_color) {
+ translate_z(-screw_length)
+ cylinder(d = screw, h = screw_length + 1);
+
+ linear_extrude(height = height)
+ difference() {
+ circle(r = rad, $fn = 6);
+ circle(d = screw);
+ }
+ }
+}
+
+module d_plug(type, socket = false, pcb = false, idc = false) { //! Draw specified D plug, which can be IDC, PCB or plain solder bucket
+ hole_r = 3.05 / 2;
+ dwall = 0.5;
+
+ flange_length = d_flange_length(type);
+ d_length = d_lengths(type)[socket ? 1 : 0];
+ hole_pitch = d_hole_pitch(type);
+ d_width = d_widths(type)[socket ? 1 : 0];
+ flange_width = d_flange_width(type);
+ front_height = d_front_height(type);
+ back_height = d_height(type) - front_height;
+ pins = d_ways(type);
+
+ desc = idc ? "IDC" : pcb ? "PCB mount" : "";
+ vitamin(str(socket ? "d_socket(" : "d_plug(", type[0], arg(pcb, false, "pcb"), arg(idc, false, "idc"),
+ "): D-type ", pins, " way ", desc, socket ? " socket" : " plug"));
+
+ module D(length, width, rad) {
+ d = width / 2 - rad;
+ offset = d * sin(10);
+
+ hull()
+ for(x = [-1, 1], y = [-1, 1])
+ translate([x * (length / 2 - rad) + y * x * offset, y * (width / 2 - rad)])
+ circle(rad);
+ }
+
+ module pin_positions()
+ for($i = [1 : pins])
+ translate([($i - (pins + 1) / 2) * 2.77 / 2, ($i % 2 - 0.5) * 2.84])
+ children();
+ //
+ // Shell
+ //
+ color(d_plug_shell_color) {
+ linear_extrude(height = d_flange_thickness(type))
+ difference() {
+ rounded_square([flange_length, flange_width], 2);
+
+ d_connector_holes(type)
+ circle(hole_r);
+ }
+
+ linear_extrude(height = front_height, convexity = 5)
+ difference() {
+ D(d_length, d_width, 2.5);
+ D(d_length - 2 * dwall, d_width - 2 * dwall, 2.5 - dwall);
+ }
+
+ if(!idc)
+ rotate([0,180,0])
+ linear_extrude(height = back_height, convexity = 5)
+ D(d_lengths(type)[0] + 2 * dwall, d_widths(type)[0] + 2 * dwall, 2.5 + dwall);
+
+ }
+ //
+ // Insulator
+ //
+ color(d_plug_insulator_color) {
+ translate_z(d_flange_thickness(type) + eps)
+ rotate([0, 180, 0])
+ linear_extrude(height = back_height + 1 + d_flange_thickness(type), convexity = 5)
+ D(d_length - dwall, d_width - dwall, 2.5 - dwall/2);
+
+ if(socket)
+ linear_extrude(height = front_height - eps, convexity = 5)
+ difference() {
+ D(d_length - dwall, d_width - dwall, 2.5 - dwall/2);
+
+ pin_positions()
+ circle(r = 0.7);
+ }
+ if(idc) {
+ translate_z(-2.4 / 2)
+ cube([((pins + 1) / 2) * 2.77 + 6, flange_width, 2.4], center = true);
+
+ translate_z(-14.4 / 2)
+ cube([pins * 1.27 + 7.29, flange_width, 14.4], center = true);
+ }
+ }
+ //
+ // Pins
+ //
+ color("gold") {
+ if(!socket)
+ translate_z(-0.5)
+ pin_positions()
+ hull() {
+ pin_r = 0.5;
+ cylinder(r = pin_r, h = eps);
+
+ translate_z(front_height - pin_r)
+ sphere(pin_r);
+ }
+
+ if(pcb)
+ rotate([0, 180, 0]) {
+ linear_extrude(height = back_height + 1 + 4.5)
+ pin_positions()
+ circle(r = 0.75 / 2, $fn = 12);
+
+ linear_extrude(height = back_height + 1 + 1)
+ pin_positions()
+ circle(r = 0.75, $fn = 12);
+ }
+
+ if(!pcb && !idc)
+ rotate([0, 180, 0])
+ pin_positions()
+ rotate(180 + ($i % 2) * 180)
+ render() difference() {
+ linear_extrude(height = 8)
+ difference() {
+ circle(1);
+
+ circle(0.45);
+ }
+ translate([0, 2.1, 8])
+ rotate([45, 0, 0])
+ cube([3, 3, 3], center = true);
+ }
+ }
+}
+
+module d_socket(connector, pcb = false, idc = false) //! Draw specified D socket, which can be IDC, PCB or plain solder bucket
+ d_plug(connector, true, pcb = pcb, idc = idc);
diff --git a/vitamins/d_connectors.scad b/vitamins/d_connectors.scad
new file mode 100644
index 0000000..3b49dfe
--- /dev/null
+++ b/vitamins/d_connectors.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// D-connectors
+//
+DCONN9 = ["DCONN9", 30.81, [18, 16.92], 24.99, [9.26, 8.38], 12.55, 10.72, 6.693, 1.12, 9];
+DCONN15 = ["DCONN15", 39.14, [26.25, 25.25], 33.32, [9.26, 8.38], 12.55, 10.72, 6.693, 1.12, 15];
+DCONN25 = ["DCONN25", 53.04, [40, 38.96], 47.04, [9.26, 8.38], 12.55, 10.72, 6.693, 1.12, 25];
+
+d_connectors = [DCONN9, DCONN15, DCONN25];
+
+use
diff --git a/vitamins/display.scad b/vitamins/display.scad
new file mode 100644
index 0000000..e4315a4
--- /dev/null
+++ b/vitamins/display.scad
@@ -0,0 +1,109 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! LCD dispays.
+//
+include <../core.scad>
+
+use
+use
+
+function display_width(type) = type[2]; //! Width of the metal part
+function display_height(type) = type[3]; //! Depth of the metal part
+function display_thickness(type) = type[4]; //! Height of the metal part
+function display_pcb(type) = type[5]; //! PCB mounted on the back
+function display_pcb_offset(type) = type[6]; //! 3D offset of the PCB centre
+function display_aperture(type) = type[7]; //! Size of the aperture including its depth
+function display_touch_screen(type) = type[8]; //! Touch screen position and size
+function display_threads(type) = type[9]; //! Length that studs protrude from the PCB holes
+function display_ribbon(type) = type[10]; //! Keep out region for ribbon cable
+
+function display_ts_thickness(type) = let(ts = display_touch_screen(type)) ts ? ts[1].z : 0; //! Touch screen thickness or 0
+
+function display_depth(type) = display_ts_thickness(type) + display_thickness(type) + display_pcb_offset(type).z + pcb_thickness(display_pcb(type)); //! Total thickness including touch screen and PCB
+
+module display_aperture(type, clearance, clear_pcb = false) { //! Make aperture cutout
+ aperture = display_aperture(type);
+ ts = display_touch_screen(type);
+ pcb = display_pcb(type);
+ rb = display_ribbon(type);
+
+ translate([aperture[0].x, aperture[0].y, -10])
+ cube([aperture[1].x - aperture[0].x, aperture[1].y - aperture[0].y, 20]);
+
+ if(ts)
+ translate([ts[0].x - clearance, ts[0].y - clearance, -clearance])
+ cube([ts[1].x - ts[0].x + 2 * clearance, ts[1].y - ts[0].y + 2 * clearance, ts[1].z + clearance + eps]);
+
+ if(rb)
+ translate([rb[0].x, rb[0].y,0])
+ cube([rb[1].x - rb[0].x, rb[1].y - rb[0].y, ts[1].z + display_depth(type) + 2]);
+
+ if(clear_pcb)
+ translate([display_pcb_offset(type).x, display_pcb_offset(type).y, display_depth(type) / 2 + 0.5 + display_ts_thickness(type)])
+ cube([pcb_length(pcb) + 2 * clearance, pcb_width(pcb) + 2 * clearance, display_depth(type) + 1], center = true);
+ else
+ translate_z(display_depth(type) / 2 + 0.5)
+ cube([display_width(type) + 2 * clearance, display_height(type) + 2 * clearance, display_depth(type) + 1], center = true);
+}
+
+module display(type) { //! Draw specified display
+ vitamin(str("display(", type[0], "): ", type[1]));
+
+ w = display_width(type);
+ h = display_height(type);
+ t = display_thickness(type);
+ pcb = display_pcb(type);
+
+ gap = display_pcb_offset(type).z;
+ aperture = display_aperture(type);
+ ts = display_touch_screen(type);
+
+ not_on_bom() {
+ translate_z(display_ts_thickness(type)) {
+ difference() {
+ color("silver")
+ rounded_rectangle([w, h, t], 0.5, center = false);
+
+ color("black")
+ translate([aperture[0].x, aperture[0].y, - eps])
+ cube([aperture[1].x - aperture[0].x, aperture[1].y - aperture[0].y, aperture[1].z]);
+ }
+
+ if(gap)
+ color("black")
+ translate_z(t + gap / 2)
+ cube([w - 1, h - 1, gap], center = true);
+
+ translate([0, 0, display_thickness(type)] + display_pcb_offset(type)) {
+ pcb(pcb);
+
+ if(display_threads(type))
+ pcb_screw_positions(pcb)
+ vflip()
+ screw(pcb_screw(pcb), pcb_thickness(pcb) + display_threads(type));
+ }
+ }
+ if(ts)
+ color("white", 0.15)
+ translate([ts[0].x, ts[0].y, 0])
+ cube([ts[1].x - ts[0].x, ts[1].y - ts[0].y, ts[1].z - eps]);
+ }
+}
diff --git a/vitamins/displays.scad b/vitamins/displays.scad
new file mode 100644
index 0000000..52b0606
--- /dev/null
+++ b/vitamins/displays.scad
@@ -0,0 +1,52 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+HDMI5PCB = ["", "", 121.11, 77.93, 1.65, 0, 2.2, 0, "mediumblue", false, [[4.6, 4.9], [4.6, -3.73], [97.69, -3.73], [97.69, 4.9]],
+ [[ 47.245,-2.5, 90, "usb_uA"],
+ [-53.14, -4.4, 90, "hdmi"],
+ [ 53.7, 40.6, 0, "chip", 14, 14, 1],
+ [ 59.8, 25.2, 0, "2p54socket", 13, 2, false, 13.71],
+ [ 59.8, 10.12, 0, "2p54header", 13, 2, true],
+ ],
+ []];
+
+HDMI5 = ["HDMI5", "HDMI display 5\"", 121, 76, 2.85, HDMI5PCB,
+ [0, 0, 1.9], // pcb offst
+ [[-54, -30.225], [54, 34.575, 0.5]], // aperture
+ [[-58.7, -34], [58.7, 36.25, 1]], // touch screen
+ 2, // thread length
+ [[-2.5, -39], [10.5, -33]], // clearance need for the ts ribbon
+ ];
+
+LCD1602APCB = ["", "", 80, 36, 1.65, 0, 2.9, 5, "green", false, [[-2.5, -2.5], [-2.5, 2.5], [2.5, 2.5], [2.5, -2.5]],
+ [ [-27.05, - 2.5, 0, "2p54header", 16, 1]
+ ],
+ []];
+
+LCD1602A = ["LCD1602A", "LCD display 1602A", 71.3, 24.3, 7.0, LCD1602APCB,
+ [0, 0, 0], // pcb offst
+ [[-64.5 / 2, -14.5 / 2], [64.5 / 2, 14.5 / 2, 0.6]], // aperture
+ [], // touch screen
+ 0, // thread length
+ [], // clearance need for the ts ribbon
+ ];
+
+displays = [LCD1602A, HDMI5];
+
+use
diff --git a/vitamins/e3d.scad b/vitamins/e3d.scad
new file mode 100644
index 0000000..f463c99
--- /dev/null
+++ b/vitamins/e3d.scad
@@ -0,0 +1,180 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// E3D hot ends
+//
+include <../core.scad>
+include
+include
+include
+include
+
+use <../utils/tube.scad>
+
+rad_dia = 22; // Diam of the part with ailettes
+rad_nb_ailettes = 11;
+rad_len = 26;
+
+nozzle_h = 5;
+
+module e3d_nozzle(type) {
+ color("gold") {
+ rotate_extrude()
+ polygon([
+ [0.2, 0],
+ [0.2, 2],
+ [1.5, 2],
+ [0.65, 0]
+ ]);
+
+ translate_z(2)
+ cylinder(d = 8, h = nozzle_h - 2, $fn=6);
+ }
+}
+
+resistor_len = 22;
+resistor_dia = 6;
+
+heater_width = 16;
+heater_length = 20;
+heater_height = 11.5;
+
+heater_x = 4.5;
+heater_y = heater_width / 2;
+
+fan_x_offset = rad_dia / 2 + 4;
+
+module e3d_resistor(type) {
+ translate([11 - heater_x, -3 - heater_y, heater_height / 2 + nozzle_h]) {
+ color("grey")
+ rotate([-90, 0, 0])
+ cylinder(r = resistor_dia / 2, h = resistor_len);
+
+ color("red")
+ translate([-3.5/2, resistor_len + 3.5/2 + 1, 0]) {
+ cylinder(d = 3.5, h = 36);
+
+ translate([3.5, 0, 0])
+ cylinder(r = 3.5 / 2, h = 36);
+ }
+ }
+}
+
+module heater_block(type) {
+ translate_z(-hot_end_length(type)) {
+ translate_z(nozzle_h)
+ color("lightgrey")
+ translate([-heater_x, -heater_y, 0])
+ cube([heater_length, heater_width, heater_height]);
+
+ e3d_resistor(type);
+ e3d_nozzle(type);
+ }
+}
+
+
+
+module e3d_fan_duct(type) {
+ color("DeepSkyBlue")
+ render() difference() {
+ hull() {
+ translate([-8, -23 / 2, 0])
+ cube([eps, 23, 26]);
+
+ translate([fan_x_offset, -30 / 2, 0])
+ cube([eps, 30, 30]);
+ }
+ cylinder(h = 70, d = rad_dia + 0.1, center = true); // For rad
+
+ translate_z(15)
+ rotate([0, 90, 0])
+ cylinder(d = rad_dia, h = 50);
+ }
+}
+
+module e3d_fan(type) {
+ e3d_fan_duct(type);
+
+ translate([fan_x_offset + 5, 0, 15])
+ rotate([0, 90, 0])
+ not_on_bom()
+ fan(fan30x10);
+}
+
+module e3d_hot_end(type, filament, naked = false) {
+ insulator_length = hot_end_insulator_length(type);
+ inset = hot_end_inset(type);
+ h_ailettes = rad_len / (2 * rad_nb_ailettes - 1);
+
+ vitamin(str("e3d_hot_end(", type[0], ", ", filament, "): Hot end ", hot_end_part(type), " ", filament, "mm"));
+
+ translate_z(inset - insulator_length)
+ color(hot_end_insulator_colour(type))
+ rotate_extrude()
+ difference() {
+ union() {
+ for (i = [0 : rad_nb_ailettes - 1])
+ translate([0, (2 * i) * h_ailettes])
+ square([rad_dia / 2, h_ailettes]);
+
+ square([hot_end_insulator_diameter(type) / 2, insulator_length]);
+
+ translate([0, -10])
+ square([2, 10]);
+ }
+ square([3.2 / 2, insulator_length]); // Filament hole
+
+ translate([hot_end_groove_dia(type) / 2, insulator_length - inset - hot_end_groove(type)])
+ square([100, hot_end_groove(type)]);
+ }
+
+ rotate(90)
+ heater_block(type);
+
+ if(!naked)
+ translate_z(inset - insulator_length)
+ e3d_fan();
+}
+
+module e3d_hot_end_assembly(type, filament, naked = false) {
+ bundle = 3.2;
+
+ e3d_hot_end(type, filament, naked);
+
+ // Wire and ziptie
+ if(!naked)
+ rotate(10) {
+ dia = hot_end_insulator_diameter(type);
+ scale([1, (bundle + dia) / dia])
+ translate([0, -bundle / 2, -7])
+ rotate(-110)
+ ziptie(small_ziptie, dia / 2);
+
+ translate([0, -dia / 2 - bundle / 2, 20])
+ scale([0.7, bundle / 6.4])
+ difference() {
+ tubing(HSHRNK64, 60);
+
+ translate_z(20)
+ cube([10, 10, 60], center = true);
+ }
+ }
+
+}
diff --git a/vitamins/fan.scad b/vitamins/fan.scad
new file mode 100644
index 0000000..abb940f
--- /dev/null
+++ b/vitamins/fan.scad
@@ -0,0 +1,170 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Axial fans.
+//!
+//! Can draw three styles: solid, open frame and open frame with screw bosses.
+//
+include <../core.scad>
+use
+use
+use
+use <../utils/tube.scad>
+
+fan_colour = grey20;
+
+function fan_width(type) = type[0]; //! Width of square
+function fan_depth(type) = type[1]; //! Depth of fan
+function fan_bore(type) = type[2]; //! Diameter of the hole for the blades
+function fan_hole_pitch(type) = type[3]; //! Screw hole pitch
+function fan_screw(type) = type[4]; //! Screw type
+function fan_hub(type) = type[5]; //! Diameter of the hub
+function fan_thickness(type) = type[6]; //! Thickness of the frame
+function fan_outer_diameter(type) = type[7]; //! Outside diameter of the frame
+function fan_blades(type) = type[8]; //! The number of blades
+function fan_boss_d(type) = type[9]; //! Diameter of the screw bosses
+function fan_aperture(type) = type[10] ? type[10] : fan_bore(type); //! Optional diameter for the aperture, which can be bigger than the bore it has flared corners.
+
+module fan(type) { //! Draw specified fan, origin in the centre
+ width = fan_width(type);
+ depth = fan_depth(type);
+ thickness = fan_thickness(type);
+ hole_pitch = fan_hole_pitch(type);
+ corner_radius = width / 2 - hole_pitch;
+ screw = fan_screw(type);
+
+ vitamin(str("fan(fan", width, "x", depth, "): Fan ", width, "mm x ", depth, "mm"));
+
+ module squarish(s, n) {
+ polygon([
+ for(i = [0 : n]) [i * s.x / n, s.y + (i % 2) * eps],
+ for(i = [0 : n]) [s.x - i * s.x / n, (i % 2) * eps],
+ ]);
+ }
+
+ module shape()
+ difference() {
+ //overall outside
+ rounded_square([width, width], corner_radius);
+
+ //main inside bore, less hub
+ difference() {
+ circle(fan_bore(type) / 2);
+ circle(fan_hub(type) / 2);
+ }
+
+ //Mounting holes
+ fan_hole_positions(type)
+ circle(screw_clearance_radius(screw));
+ }
+
+ color(fan_colour) {
+ middle = depth - 2 * thickness;
+ if(middle > 0) {
+ for(z = [-1, 1])
+ translate_z(z * (depth - thickness) / 2)
+ linear_extrude(height = thickness, center = true)
+ shape();
+
+ linear_extrude(height = middle, center = true)
+ difference() {
+ shape();
+ difference() {
+ circle(sqrt(2) * width / 2);
+ circle(d = fan_outer_diameter(type));
+
+ if(fan_boss_d(type))
+ for(i = [-1, 1])
+ hull()
+ for(side = [-1, 1])
+ translate([hole_pitch * side * i, hole_pitch * side])
+ circle(d = fan_boss_d(type));
+ }
+ }
+ }
+ else
+ linear_extrude(height = depth, center = true)
+ shape();
+
+ // Blades
+ blade_ir = fan_hub(type) / 2 - 1;
+ blade_len = fan_bore(type) / 2 - 0.75 - blade_ir;
+ linear_extrude(height = depth - 1, center = true, convexity = 4, twist = -30, slices = round(depth / 2))
+ for(i = [0 : fan_blades(type) - 1])
+ rotate((360 * i) / fan_blades(type))
+ translate([blade_ir, -1.5 / 2])
+ squarish([blade_len, 1.5], round(blade_len / 2));
+ }
+}
+
+module fan_hole_positions(type, z = undef) { //! Position children at the screw hole positions
+ hole_pitch = fan_hole_pitch(type);
+ for(x = [-hole_pitch, hole_pitch])
+ for(y = [-hole_pitch, hole_pitch])
+ translate([x, y, is_undef(z) ? fan_depth(type) / 2 : z])
+ children();
+}
+
+module fan_holes(type, poly = false, screws = true, h = 100) { //! Make all the holes for the fan, or just the aperture if ```screws``` is false. Set ```poly``` true for poly_holes.
+ hole_pitch = fan_hole_pitch(type);
+ screw = fan_screw(type);
+
+ extrude_if(h) {
+ if(screws)
+ fan_hole_positions(type, z = 0)
+ if(poly)
+ poly_circle(r = screw_clearance_radius(screw));
+ else
+ drill(screw_clearance_radius(screw), 0);
+
+ difference() {
+ intersection() {
+ square(fan_bore(type), center = true);
+
+ circle(d = fan_aperture(type));
+ }
+ if(screws)
+ fan_hole_positions(type, z = 0)
+ circle(d = washer_diameter(screw_washer(screw)) + 1);
+ }
+ }
+}
+
+function nut_and_washer_thickness(screw, nyloc) = washer_thickness(screw_washer(screw)) + nut_thickness(screw_nut(screw), nyloc);
+function fan_screw_depth(type) = fan_boss_d(type) ? fan_depth(type) : fan_thickness(type);
+function fan_screw_length(type, thickness) = screw_longer_than(thickness + fan_screw_depth(type) + nut_and_washer_thickness(fan_screw(type), true)); //! Screw length required
+
+module fan_assembly(type, thickness, include_fan = true) { //! Fan with its fasteners
+ translate_z(-fan_depth(type) / 2) {
+ if(include_fan)
+ fan(type);
+
+ screw = fan_screw(type);
+ nut = screw_nut(screw);
+ fan_hole_positions(type) {
+ translate_z(thickness)
+ screw_and_washer(screw, fan_screw_length(type, thickness));
+
+ translate_z(include_fan ? -fan_screw_depth(type) : 0)
+ vflip()
+ nut(nut, true);
+ }
+ }
+}
diff --git a/vitamins/fans.scad b/vitamins/fans.scad
new file mode 100644
index 0000000..55b78ce
--- /dev/null
+++ b/vitamins/fans.scad
@@ -0,0 +1,46 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include
+
+// w d b h s h t o b b a
+// i e o o c u h u l o p
+// d p r l r b i t a s p
+// t t e e e c e d s e
+// h h w d k r e r
+// p i n s d t
+// i a e d u
+// t s i r
+// c s a e
+// h
+//
+fan120x25= [120,25, 116,52.5, M4_dome_screw, 41, 4, 140, 9, 0, 137];
+fan80x38 = [80, 38, 75, 35.75, M4_dome_screw, 40, 4.3, 84, 7, 0, 85];
+fan80x25 = [80, 25, 75, 35.75, M4_dome_screw, 40, 4.3, 84, 7, 0, 85];
+fan70x15 = [70, 15, 66, 30.75, M4_dome_screw, 29, 3.8, 70 ,7, 0, undef];
+fan60x25 = [60, 25, 57, 25, M4_dome_screw, 31.5, 3.6, 64, 7, 0, 63];
+fan60x15 = [60, 15, 57, 25, M4_dome_screw, 29, 2.4, 60, 7, 7.7, 63];
+fan50x15 = [50, 15, 48, 20, M4_dome_screw, 25, 12.5,100,7, 0, undef];
+fan40x11 = [40, 11, 37, 16, M3_dome_screw, 25, 7.5,100, 9, 0, undef];
+fan30x10 = [30, 10, 27, 12, M3_dome_screw, 17, 10, 100, 5, 0, undef];
+fan25x10 = [25, 10, 24, 10, M2p5_pan_screw, 16, 10, 100, 5, 0, undef];
+
+fans = [fan25x10, fan30x10, fan40x11, fan50x15, fan60x15, fan60x25, fan70x15, fan80x25, fan80x38, fan120x25];
+
+use
diff --git a/vitamins/fuseholder.scad b/vitamins/fuseholder.scad
new file mode 100644
index 0000000..56e77f0
--- /dev/null
+++ b/vitamins/fuseholder.scad
@@ -0,0 +1,133 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! 20mm panel mount fuse holder.
+//
+include <../core.scad>
+include
+use <../utils/tube.scad>
+
+module fuseholder_hole(h = 100) //! Hole with flats for fuseholder
+ extrude_if(h)
+ intersection() {
+ circle(d = 12);
+
+ square([100, 11], center = true);
+ }
+
+function fuseholder_diameter() = 18.8; //! Outside diameter of flange
+
+module fuseholder(thickness) { //! Fuseholder with nut in place for specified panel thickness
+ vitamin(str("fuseholder(6): Fuse holder 20mm"));
+ flange_d = fuseholder_diameter();
+ flange_t = 2;
+ height = 33.2;
+ thread_d = 11.7;
+ thread = 15;
+ bot_d = 10.4;
+ top_d = 8.7;
+ flat = 10.8;
+ nut_d = 18.8;
+ nut_t = 6;
+ nut_flange_t = 1.5;
+ spade = 5.4;
+ contact_slot_z = 20;
+ contact_slot_w = 3.2;
+ contact_slot_d = 7;
+
+ contact_w = 2.8;
+ contact_t = 0.3;
+ contact_l1 = 11;
+ contact_l2 = 6;
+ //
+ // Nut
+ //
+ vflip()
+ translate_z(thickness)
+ explode(height)
+ color("dimgrey") {
+ tube(or = nut_d / 2, ir = 5, h = nut_flange_t, center = false);
+
+ linear_extrude(height = nut_t)
+ difference() {
+ circle(d = nut_d, $fn = 6);
+
+ circle(5);
+ }
+ }
+ //
+ // Body
+ //
+ explode(height + 5, offset = -height - 4) {
+ color("dimgrey") {
+ tube(or = flange_d / 2, ir = 5.2, h = flange_t, center = false);
+
+ cylinder(r = 5, h = flange_t - 1);
+
+ linear_extrude(height = flange_t)
+ difference() {
+ circle(r = 5);
+
+ square([8, 1.5], center = true);
+ }
+
+ vflip() {
+ linear_extrude(height = thread)
+ intersection() {
+ circle(d = thread_d);
+
+ square([100, 10.8], center = true);
+ }
+ render() difference() {
+ translate_z(thread)
+ cylinder(d1 = bot_d, d2 = top_d, h = height - flange_t - thread);
+
+ for(side = [-1, 1])
+ translate([side * (contact_slot_d / 2 + 1) - 1, -contact_slot_w / 2, contact_slot_z])
+ cube([2, contact_slot_w, 100]);
+ }
+ }
+ }
+ //
+ // Side contacts
+ //
+ color("silver") vflip()
+ for(side = [-1, 1])
+ translate([side * contact_slot_d / 2, 0, contact_slot_z])
+ rotate([0, -70, 90 - side * 90])
+ linear_extrude(height = contact_t, center = true) difference() {
+ hull() {
+ square([eps, contact_w], center = true);
+
+ translate([(side > 0 ? contact_l1 : contact_l2) - contact_w / 2, 0])
+ circle(d = contact_w);
+ }
+ translate([(side > 0 ? contact_l1 : contact_l2) - contact_w / 2, 0])
+ circle(d = 1);
+ }
+ //
+ // Bottom contact
+ //
+ translate_z(-height + flange_t)
+ vflip()
+ rotate(45)
+ spade(spade3, spade);
+ }
+}
diff --git a/vitamins/hot_end.scad b/vitamins/hot_end.scad
new file mode 100644
index 0000000..0132d76
--- /dev/null
+++ b/vitamins/hot_end.scad
@@ -0,0 +1,53 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Hot end models. The E3D models were originally contributed to Mendel90 by Philippe LUC @philfifi
+//!
+//! Needs updating as mostly obsolete versions.
+//
+include <../core.scad>
+
+function hot_end_style(type) = type[1]; //! Basic type, jhead or e3d
+function hot_end_part(type) = type[2]; //! Description
+function hot_end_total_length(type) = type[3]; //! Length from nozzle tip to the top
+function hot_end_inset(type) = type[4]; //! The length that goes into the mounting
+function hot_end_insulator_diameter(type) = type[5]; //! Outside diameter
+function hot_end_insulator_length(type) = type[6]; //! Length of the insulator
+function hot_end_insulator_colour(type) = type[7]; //! Colour of the insulator
+function hot_end_groove_dia(type) = type[8]; //! Groove internal diameter
+function hot_end_groove(type) = type[9]; //! Groove length
+function hot_end_duct_radius(type) = type[10]; //! Require radius to clear the heater block
+function hot_end_duct_offset(type) = type[11]; //! Offset of circular duct centre from the nozzle
+function hot_end_need_cooling(type) = hot_end_style(type) != e3d; //! Has own fan so don't need cooling hole in the duct
+function hot_end_duct_height_nozzle(type) = type[12]; //! Duct height at nozzle end
+function hot_end_duct_height_fan(type) = type[13]; //! Duct height at fan end
+
+function hot_end_length(type) = hot_end_total_length(type) - hot_end_inset(type); //! The amount the hot end extends below its mounting
+
+use
+use
+
+module hot_end(type, filament, naked = false) { //! Draw specified hot end
+ if(hot_end_style(type) == jhead)
+ jhead_hot_end_assembly(type, filament, naked);
+
+ if(hot_end_style(type) == e3d)
+ e3d_hot_end_assembly(type, filament, naked);
+}
diff --git a/vitamins/hot_ends.scad b/vitamins/hot_ends.scad
new file mode 100644
index 0000000..78fd6fc
--- /dev/null
+++ b/vitamins/hot_ends.scad
@@ -0,0 +1,44 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Hot end descriptions
+//
+// s p l i d i l c s g g d d d a d a
+// t a e n i n e o c r r u u u t u t
+// y r n s a s n l r o o c c c c
+// l t g e u g o e o o t t t n t f
+// e t t l t u w v v o a
+// h a h r e e r o h z h n
+// t p a f e z e
+// o i d w d f i l i
+// r t i i i s g e g
+// c a d u e h h
+// h t s t t t
+// h
+//
+JHeadMk4 = ["JHeadMk4", jhead, "JHead MK4", 64, 5.1, 16, 50, grey20, 12, 4.64, 14, [0, 2.94, -5], 20, 20];
+JHeadMk5 = ["JHeadMk5", jhead, "JHead MK5", 51.2, 5.1, 16, 40, grey20, 12, 4.64, 13, [0, 2.38, -5], 20, 20];
+E3Dv5 = ["E3Dv5", e3d, "E3D V5 direct", 70, 3.7, 16, 50.1, "silver", 12, 6, 15, [1, 5, -4.5], 14.5, 28];
+E3Dv6 = ["E3Dv6", e3d, "E3D V6 direct", 62, 3.7, 16, 42.7, "silver", 12, 6, 15, [1, 5, -4.5], 14, 21];
+E3D_clone = ["E3D_clone", e3d, "E3D clone aliexpress",66, 6.8, 16, 46, "silver", 12, 5.6, 15, [1, 5, -4.5], 14.5, 21];
+
+hot_ends = [JHeadMk5, E3Dv5, E3Dv6, E3D_clone];
+
+use
diff --git a/vitamins/iec.scad b/vitamins/iec.scad
new file mode 100644
index 0000000..0c7152e
--- /dev/null
+++ b/vitamins/iec.scad
@@ -0,0 +1,228 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! IEC mains inlets and outlet.
+//
+include <../core.scad>
+use
+use
+use
+use
+include
+
+function iec_part(type) = type[1]; //! Description
+function iec_screw(type) = type[2]; //! Screw type
+function iec_pitch(type) = type[3]; //! Screw hole pitch
+function iec_slot_w(type) = type[4]; //! Body width
+function iec_slot_h(type) = type[5]; //! Body height
+function iec_slot_r(type) = type[6]; //! Body corner radius
+function iec_bezel_w(type) = type[7]; //! Bezel width
+function iec_bezel_h(type) = type[8]; //! Bezel height
+function iec_bezel_r(type) = type[9]; //! Bezel corner radius
+function iec_bezel_t(type) = type[10]; //! Bezel thickness
+function iec_flange_w(type) = type[11]; //! Flange width not including the lugs
+function iec_flange_h(type) = type[12]; //! Flange height
+function iec_flange_r(type) = type[13]; //! Flange corner radius
+function iec_flange_t(type) = type[14]; //! Flange thickness
+function iec_width(type) = type[15]; //! Widest part including the lugs
+function iec_depth(type) = type[16]; //! Depth of the body below the flange
+function iec_spades(type) = type[17]; //! Spade type
+function iec_male(type) = type[18]; //! True for an outlet
+
+insert_overlap = 1.1; // chosen to make cap screws 10mm long.
+
+module iec(type) { //! Draw specified IEC connector
+ vitamin(str("iec(", type[0], "): ", iec_part(type)));
+
+ pin_w = 2;
+ pin_d = 4;
+ pin_h1 = 12;
+ pin_h2 = 15;
+ pin_chamfer = 1;
+
+ module pin(h)
+ color("silver")
+ hull() {
+ translate_z(h / 2)
+ cube([pin_w - pin_chamfer, pin_d - pin_chamfer, h], center = true);
+
+ translate_z((h - pin_chamfer) / 2)
+ cube([pin_w, pin_d, h - pin_chamfer], center = true);
+ }
+
+ socket_w = 24;
+ socket_w2 = 14;
+ socket_h = 16.5;
+ socket_h2 = 12;
+ socket_d = 17;
+ socket_r = 3;
+ socket_r2 = 0.5;
+ socket_offset = iec_bezel_h(type) / 2 - socket_h / 2 - (iec_bezel_w(type) - socket_w) / 2;
+
+ module socket_shape()
+ hull()
+ for(side = [-1, 1]) {
+ translate([side * (socket_w / 2 - socket_r), -socket_h / 2 + socket_r])
+ circle(socket_r);
+
+ translate([side * (socket_w / 2 - socket_r2), -socket_h / 2 + socket_h2 + socket_r2])
+ circle(socket_r2);
+
+ translate([side * (socket_w2 / 2 - socket_r2), socket_h / 2 - socket_r2])
+ circle(socket_r2);
+ }
+
+ module oriffice_shape()
+ translate([0, socket_offset])
+ if(iec_male(type))
+ difference() {
+ offset(3)
+ socket_shape();
+
+ difference() {
+ offset(-1) socket_shape();
+
+ translate([0, 2])
+ square([2.4, 5], center = true);
+
+ for(side = [-1, 1])
+ translate([side * 7, -2])
+ square([2.4, 5], center = true);
+ }
+ }
+ else
+ socket_shape();
+
+ color(grey20) {
+ // Flange
+ flange_t = iec_flange_t(type);
+ linear_extrude(height = flange_t)
+ difference() {
+ hull() {
+ rounded_square([iec_flange_w(type), iec_flange_h(type)], iec_flange_r(type));
+
+ iec_screw_positions(type)
+ circle(d = iec_width(type) - iec_pitch(type));
+ }
+ oriffice_shape();
+
+ iec_screw_positions(type)
+ circle(socket_r);
+ }
+ head_r = screw_head_radius(iec_screw(type));
+ screw_r = screw_clearance_radius(iec_screw(type));
+ iec_screw_positions(type)
+ rotate_extrude()
+ difference() {
+ translate([screw_r, 0])
+ square([socket_r - screw_r, flange_t]);
+
+ translate([0, flange_t - head_r])
+ rotate(45)
+ square(10);
+ }
+ // Bezel
+ translate_z(iec_flange_t(type))
+ linear_extrude(height = iec_bezel_t(type))
+ difference() {
+ rounded_square([iec_bezel_w(type), iec_bezel_h(type)], iec_bezel_r(type));
+
+ oriffice_shape();
+ }
+
+ // Body
+ h = socket_d - iec_flange_t(type) - iec_bezel_t(type);
+ translate_z(-h)
+ linear_extrude(height = h)
+ difference() {
+ rounded_square([iec_slot_w(type), iec_slot_h(type)], iec_slot_r(type));
+ oriffice_shape();
+ }
+ // Back
+ translate_z(-iec_depth(type))
+ rounded_rectangle([iec_slot_w(type), iec_slot_h(type), iec_depth(type) - h], iec_slot_r(type), center = false);
+ }
+ if(!iec_male(type))
+ translate([0, socket_offset, iec_flange_t(type) + iec_bezel_t(type) - socket_d]) {
+ translate([0, 2])
+ pin(pin_h2);
+
+ for(side = [-1, 1])
+ translate([side * 7, -2])
+ pin(pin_h1);
+ }
+ for(spade = iec_spades(type))
+ translate([spade[2], spade[3], -iec_depth(type)])
+ rotate([180, 0, spade[4]])
+ spade(spade[0], spade[1]);
+}
+
+module iec_screw_positions(type) //! Position children at the screw holes
+ for(side = [-1, 1])
+ translate([side * iec_pitch(type) / 2, 0])
+ children();
+
+module iec_holes(type, h = 100, poly = false, horizontal = false, insert = false) { //! Drill the required panel holes
+ clearance = 0.2;
+
+ iec_screw_positions(type)
+ if(insert)
+ insert_hole(screw_insert(iec_screw(type)), insert_overlap, horizontal = horizontal);
+ else
+ if(horizontal)
+ teardrop_plus(r = screw_clearance_radius(iec_screw(type)), h = h);
+ else
+ if(poly)
+ poly_cylinder(r = screw_clearance_radius(iec_screw(type)), h = h, center = true);
+ else
+ drill(screw_clearance_radius(iec_screw(type)), h);
+
+ extrude_if(h)
+ hull()
+ for(x = [-1, 1], y = [-1, 1], sag = horizontal && y > 1 ? layer_height : 0)
+ translate([x * (iec_slot_w(type) / 2 - iec_slot_r(type)), y * (iec_slot_h(type) / 2 - iec_slot_r(type) + sag )])
+ if(horizontal)
+ teardrop(0, iec_slot_r(type) + clearance / 2 + layer_height / 4);
+ else
+ drill(iec_slot_r(type) + clearance / 2, 0);
+}
+
+module iec_assembly(type, thickness) { //! Assembly with fasteners given panel thickness
+ screw = iec_screw(type);
+ washer = screw_washer(screw);
+ nut = screw_nut(screw);
+ insert = screw_insert(screw);
+ screw_length = thickness ? screw_longer_than(iec_flange_t(type) + thickness + washer_thickness(washer) + nut_thickness(nut, true))
+ : screw_shorter_than(iec_flange_t(type) + insert_hole_length(insert) + insert_overlap);
+
+ iec(type);
+
+ iec_screw_positions(type) {
+ translate_z(iec_flange_t(type))
+ screw(screw, screw_length);
+
+ if(thickness)
+ translate_z(-thickness)
+ vflip()
+ nut_and_washer(nut, true);
+ else
+ insert(insert);
+ }
+}
diff --git a/vitamins/iecs.scad b/vitamins/iecs.scad
new file mode 100644
index 0000000..d3e0ea2
--- /dev/null
+++ b/vitamins/iecs.scad
@@ -0,0 +1,47 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+include
+include
+
+fused_spades = [[spade6p4, 14, -7, 0, 0],
+ [spade6p4, 14, 7, 0, 0],
+ [spade6p4, 14, 0, 11, 0],
+ [spade4p8, 8.5, -7, -9, 90],
+ [spade4p8, 8.5, 7, -9, 90]];
+
+inlet_spades = [[spade6p4, 9, -7, -5.5, 0],
+ [spade6p4, 9, 7, -5.5, 0],
+ [spade6p4, 9, 0, 5.5, 0]];
+//
+// p s p s s s b b b b f f f f w d s m
+// a c i l l l e e e e l l l l i e p a
+// r r t o o o z z z z a a a a d p a l
+// t e c t t t e e e e n n n n t t d e
+// w h l l l l g g g g h h e
+// w h r e e e e s
+// w h r t
+// w h r t
+//
+IEC_fused_inlet = ["IEC_fused_inlet", "IEC fused inlet", M3_cs_cap_screw, 36, 27.3, 31.2, 3, 28, 31, 2, 2.5, 30, 33, 4, 2.5, 44, 21, fused_spades, false ];
+IEC_inlet = ["IEC_inlet", "IEC inlet", M3_cs_cap_screw, 40, 28.2, 20.2, 3, 28, 20.5, 4, 2.5, 37, 23, 1, 2.5, 48, 14, inlet_spades, false ];
+IEC_inlet_atx = ["IEC_inlet_atx", "IEC inlet for ATX", M3_cs_cap_screw, 40, 27.0, 19.0, 3, 30, 22, 2, 2.0, 30, 22, 2, 4.0, 50, 13, inlet_spades, false ];
+IEC_outlet = ["IEC_outlet", "IEC outlet", M3_cs_cap_screw, 40, 32, 24, 3, 28, 20.5, 2, 0.0, 29, 29, 2, 2.9, 50, 23, inlet_spades, true ];
+
+iecs = [IEC_inlet, IEC_inlet_atx, IEC_fused_inlet, IEC_outlet];
+use
diff --git a/vitamins/insert.scad b/vitamins/insert.scad
new file mode 100644
index 0000000..f85a195
--- /dev/null
+++ b/vitamins/insert.scad
@@ -0,0 +1,104 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Heatfit threaded inserts. Can be pushed into thermoplastics using a soldering iron with a conical bit set to 200°C.
+//
+include <../core.scad>
+
+function insert_length(type) = type[1]; //! Length
+function insert_outer_d(type) = type[2]; //! Outer diameter at the top
+function insert_hole_radius(type) = type[3] / 2; //! Radius of the required hole in the plastic
+function insert_screw_diameter(type) = type[4]; //! Screw size
+function insert_barrel_d(type) = type[5]; //! Diameter of the main barrel
+function insert_ring1_h(type) = type[6]; //! Height of the top and middle rings
+function insert_ring2_d(type) = type[7]; //! Diameter of the middle ring
+function insert_ring3_d(type) = type[8]; //! Diameter of the bottom ring
+
+function insert_hole_length(type) = round_to_layer(insert_length(type));
+
+module insert(type) { //! Draw specified insert
+ length = insert_length(type);
+ ring1_h = insert_ring1_h(type);
+
+ chamfer1 = (insert_ring2_d(type) - insert_barrel_d(type)) / 2;
+ chamfer2 = (insert_ring3_d(type) - insert_barrel_d(type)) / 2;
+ ring2_h = ring1_h + chamfer1;
+ gap = (length - ring1_h - ring2_h- chamfer2) / 3;
+
+ vitamin(str("insert(", type[0], "): Heatfit insert M", insert_screw_diameter(type)));
+ $fn = 64;
+ explode(20, offset =[0, 0, -5]) color(brass) translate_z(eps) {
+ vflip(){
+ r1 = insert_screw_diameter(type) / 2;
+ r2 = insert_barrel_d(type) / 2;
+ r3 = insert_ring3_d(type) / 2;
+ r4 = insert_ring2_d(type) / 2;
+ r5 = insert_outer_d(type) / 2;
+ h1 = ring1_h;
+ h2 = ring1_h + gap;
+ h3 = ring1_h + gap + ring2_h;
+ h4 = ring1_h + gap + ring2_h + gap;
+ rotate_extrude()
+ polygon([
+ [r1, 0],
+ [r1, length],
+ [r2, length],
+ [r3, length - chamfer2],
+ [r3, h4],
+ [r2, h4],
+ [r2, h3],
+ [r4, h3 - chamfer1],
+ [r4, h2],
+ [r2, h2],
+ [r2, h1],
+ [r5, h1],
+ [r5, 0],
+ ]);
+ }
+ }
+}
+
+module insert_hole(type, counterbore = 0, horizontal = false) { //! Make a hole to take an insert, ```counterbore``` is the extra length for the screw
+ h = insert_hole_length(type);
+
+ render() if(horizontal) {
+ teardrop_plus(r = insert_hole_radius(type), h = 2 * h);
+
+ if(counterbore)
+ teardrop_plus(r = insert_screw_diameter(type) / 2 + 0.1, h = 2 * (h + counterbore));
+ }
+ else {
+ poly_cylinder(r = insert_hole_radius(type), h = 2 * h, center = true);
+
+ if(counterbore)
+ poly_cylinder(r = insert_screw_diameter(type) / 2 + 0.1, h = 2 * (h + counterbore), center = true);
+ }
+}
+
+module insert_boss(type, z, wall = 2 * extrusion_width) { //! Make a boss to take an insert
+ difference() {
+ ir = insert_hole_radius(type);
+ linear_extrude(height = z)
+ poly_ring(corrected_radius(ir) + wall, insert_screw_diameter(type) / 2 + 0.1);
+
+ translate_z(z)
+ insert_hole(type, max(0, z - insert_hole_length(type) - 2 * layer_height));
+ }
+}
diff --git a/vitamins/inserts.scad b/vitamins/inserts.scad
new file mode 100644
index 0000000..4d3362c
--- /dev/null
+++ b/vitamins/inserts.scad
@@ -0,0 +1,43 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Threaded inserts
+//
+// l o h s b r r r
+// e u o c a i i i
+// n t l r r n n n
+// g e e e r g g g
+// t r w e 1 2 3
+// h d l
+// d d h d d
+// d
+//
+F1BM2 = [ "F1BM", 4.0, 3.6, 3.2, 2, 3.0, 1.0, 3.4, 3.1 ];
+F1BM2p5 = [ "F1BM2p5", 5.8, 4.6, 4.0, 2.5, 3.65, 1.6, 4.4, 3.9 ];
+F1BM3 = [ "F1BM3", 5.8, 4.6, 4.0, 3, 3.65, 1.6, 4.4, 3.9 ];
+F1BM4 = [ "F1BM4", 8.2, 6.3, 5.6, 4, 5.15, 2.3, 6.0, 5.55 ];
+
+inserts = [ F1BM2, F1BM2p5, F1BM3, F1BM4 ];
+
+function screw_insert(screw, i = 0) = let(d = screw_radius(screw) * 2)
+ i >= len(inserts) ? undef
+ : insert_screw_diameter(inserts[i]) == d ? inserts[i]
+ : screw_insert(screw, i + 1);
+use
diff --git a/vitamins/jack.scad b/vitamins/jack.scad
new file mode 100644
index 0000000..7e5b784
--- /dev/null
+++ b/vitamins/jack.scad
@@ -0,0 +1,284 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! 4mm jack sockets and binding posts. Each has a colour for the BOM entry and an optional alternative colour for display.
+//! E.g. a "brown" socket for mains live needs to be displayed as "sienna" to look realistic.
+//
+include <../core.scad>
+include
+include
+use <../utils/tube.scad>
+use <../utils/rounded_cylinder.scad>
+use
+
+function jack_4mm_hole_radius() = 8/2; //! Panel hole radius for 4mm jack
+
+module jack_4mm(colour, thickness, display_colour = false) { //! Draw a 4mm jack socket with nut positioned for specified panel thickness
+ vitamin(str("jack_4mm(\"", colour, "\", 3", arg(display_colour, false), "): 4mm jack socket ", colour));
+ flange_d = 10.6;
+ flange_t = 3;
+ flange_id = 4.6;
+ length = 28.5;
+ sleaved = 15;
+ sleaved_d = 6.4;
+ thread = 10.4;
+ thread_d = 8;
+ nut_d = 9.8;
+ nut_t = 3;
+ barrel_d = 5.4;
+ barrel = 18.5;
+ spade = 7;
+
+ explode(length, offset = -length + flange_t) {
+ color(display_colour ? display_colour : colour) rotate_extrude() difference() {
+ union() {
+ square([flange_d / 2, flange_t]);
+
+ translate([0, -sleaved])
+ square([sleaved_d / 2, sleaved]);
+
+ }
+ square([flange_id, 100], center = true);
+ }
+ color("silver") rotate_extrude() difference() {
+ union() {
+ translate([0, -thread])
+ square([thread_d / 2, thread]);
+
+ *translate([0, -nut_t - thickness])
+ square([nut_d / 2, nut_t]);
+
+ translate([0, -barrel])
+ square([barrel_d / 2, barrel]);
+ }
+ square([4, 2 * (barrel - 1)], center = true);
+ }
+ translate_z(-length + flange_t + spade)
+ vflip()
+ spade(spade4p8l, spade);
+ }
+ translate_z(-thickness)
+ explode(-length)
+ color("silver")
+ vflip()
+ tube(ir = thread_d / 2, or = nut_d / 2, h = nut_t, center = false);
+}
+
+function jack_4mm_shielded_hole_radius() = 12/2; //! Panel hole radius for 4mm shielded jack
+
+module jack_4mm_shielded(colour, thickness, display_colour = false) { //! Draw a 4mm shielded jack
+ vitamin(str("jack_4mm_shielded(\"", colour, "\", 3", arg(display_colour, false), "): 4mm shielded jack socket ", colour));
+ flange_d = 14.5;
+ flange_t = 2.5;
+ flange_id = 4.6;
+ sleaved = 21;
+ sleaved_d = 10.7;
+ thread = 14;
+ thread_d = 11.7;
+ nut_d = 14.4;
+ nut_t = 5;
+ length = 32;
+ spade = 8.5;
+
+ explode(length, offset = -length + flange_t) {
+ color(display_colour ? display_colour : colour) {
+ rounded_cylinder(r = flange_d / 2, h = flange_t, r2 = 1, ir = 4.5);
+
+ rotate_extrude() difference() {
+ union() {
+ translate([0, -sleaved])
+ square([sleaved_d / 2, sleaved + flange_t]);
+
+ translate([0, -thread])
+ square([thread_d / 2, thread]);
+ }
+ square([flange_id, 100], center = true);
+
+ difference() {
+ square([9, 2 * sleaved - 1], center = true);
+ square([6, 2 * sleaved], center = true);
+ }
+ }
+ }
+ color("silver")
+ translate_z(-length + flange_t + spade - 0.5)
+ cylinder(d = 4.8, h = 0.5);
+
+ translate_z(-length + flange_t + spade)
+ vflip()
+ spade(spade4p8ll, spade);
+ }
+
+ translate_z(-thickness)
+ explode(-length)
+ color("silver")
+ vflip()
+ tube(ir = thread_d / 2, or = nut_d / 2, h = nut_t, center = false);
+
+}
+
+function post_4mm_diameter() = 13; //! Outer diameter of 4mm binding post
+
+post_4mm_hole_radius = 7 / 2;
+post_4mm_spigot_w = 1.1;
+post_4mm_spigot_l = 1.2;
+post_4mm_spigot_h = 2.5;
+
+module post_4mm_hole(h = 100, poly = false) { //! Drill hole for 4mm binding post
+ extrude_if(h) union() {
+ r = cnc_bit_r + eps;
+ if(poly)
+ poly_circle(post_4mm_hole_radius);
+ else
+ drill(post_4mm_hole_radius, 0);
+
+ hull() {
+ translate([0, post_4mm_hole_radius + post_4mm_spigot_l - r])
+ drill(r, 0);
+
+ drill(r, 0);
+ }
+ }
+}
+
+module post_4mm(colour, thickness, display_colour = false) { //! Draw a 4mm binding post
+ vitamin(str("post_4mm(\"", colour, "\", 3", arg(display_colour, false), "): 4mm jack binding post ", colour));
+
+ actual_colour = display_colour ? display_colour : colour;
+ d = post_4mm_diameter();
+ base_h = 5;
+ collar_t = post_4mm_spigot_h;
+
+ post_od = 9.5;
+ post_metal = 0.2;
+ thread_d = 3.5;
+ thread_l = 15;
+ post_d = 7;
+ post_h = 14;
+
+ ringterm = ["", 6.3, 3.8, 16.7, 3, 1.6, 0.3, M4_dome_screw];
+
+ module washer() {
+ washer_t = 0.65;
+
+ tube(or = 7.6 / 2, ir = thread_d / 2, h = washer_t, center = false);
+
+ translate_z(washer_t)
+ children();
+ }
+
+ module nut() {
+ nut_t = 2.3;
+
+ linear_extrude(height = nut_t) difference() {
+ circle(d = 6.3 / cos(30), $fn = 6);
+
+ circle(d = thread_d);
+ }
+
+ translate_z(nut_t)
+ children();
+ }
+
+ module spigot()
+ hull()
+ for(end = [-1, 1])
+ translate([0, post_4mm_hole_radius + end * (post_4mm_spigot_l - post_4mm_spigot_w / 2)])
+ circle(d = post_4mm_spigot_w);
+
+ explode(20, offset = -thread_l) {
+ color(actual_colour) {
+ cylinder(d = d, h = base_h);
+
+ translate_z(-collar_t)
+ linear_extrude(height = base_h) {
+ circle(post_4mm_hole_radius - 0.1);
+
+ spigot();
+ }
+
+ translate_z(base_h + 1)
+ for(i = [0 : 7])
+ rotate(i * 360 / 8)
+ render() difference() {
+ rotate_extrude(angle = 360 / 8)
+ polygon([
+ [0, 0],
+ [0, 2],
+ [post_d / 2, 2],
+ [post_d / 2, 17.5],
+ [11 / 2, 17.5],
+ [12.5 / 2, 0]
+ ]);
+
+ rotate(180 /8)
+ hull() {
+ translate([6.5, 0, 3])
+ sphere(d = 2.5);
+
+ translate([6, 0, 17.5])
+ cylinder(d = 2.5, h = eps);
+ }
+ }
+ }
+
+ color("silver") {
+ translate_z(post_metal)
+ cylinder(d = post_od, h = base_h);
+
+ translate_z(base_h + post_metal + 0.1)
+ cylinder(d = post_od, h = 2);
+
+ rotate_extrude() difference() {
+ square([post_d / 2, base_h + post_metal + post_h]);
+
+ translate([0, base_h + post_metal + 1])
+ square([2, post_h]);
+
+ }
+ vflip()
+ cylinder(d = thread_d, h = thread_l);
+ }
+ }
+ explode(-15)
+ color(actual_colour) {
+ translate_z(-thickness - base_h) {
+ linear_extrude(height = base_h)
+ difference() {
+ circle(d = d);
+
+ circle(post_4mm_hole_radius);
+
+ offset(0.1) spigot();
+ }
+ tube(or = d / 2, ir = thread_d / 2, h = collar_t, center = false);
+ }
+ }
+ translate_z(-thickness - base_h)
+ explode(-20, true)
+ color("silver")
+ vflip()
+ not_on_bom()
+ washer()
+ explode(5, true) nut()
+ explode(5, true) ring_terminal(ringterm)
+ explode(5, true) washer()
+ explode(5, true) nut();
+}
diff --git a/vitamins/jhead.scad b/vitamins/jhead.scad
new file mode 100644
index 0000000..a693419
--- /dev/null
+++ b/vitamins/jhead.scad
@@ -0,0 +1,202 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! J-Head hot ends from Brian Reifsnider, hotends.com.
+//
+include <../core.scad>
+include
+include
+include
+include
+
+use
+use <../utils/tube.scad>
+
+MK4_heater = [ 12.76, 15.88, 8.22, (15.88 / 2 - 4.5), (12.76 / 2 - 0.5 - 2.5 / 2), (-15.88 / 2 + 5), 9.5, 3];
+MK5_heater = [ 12.76, 12.76, 8.22, (12.76 / 2 - 3.75), (12.76 / 2 - 0.5 - 2.5 / 2), (-12.76 / 2 + 4), 8, 2];
+
+function heater_width(type) = type[0];
+function heater_length(type) = type[1];
+function heater_height(type) = type[2];
+function resistor_x(type) = type[3];
+function thermistor_y(type) = type[4];
+function nozzle_x(type) = type[5];
+function nozzle_cone(type) = type[6];
+function nozzle_cone_length(type) = type[7];
+
+barrel_tap_dia = 5;
+
+barrel_dia = 6;
+insulator_dia = 12;
+
+module heater_block(type, resistor, thermistor) {
+ h = heater_height(type);
+
+ color("gold") {
+ render() difference() {
+ rotate([90, 0, 0])
+ linear_extrude(height = heater_width(type), center = true) difference() {
+ square([heater_length(type), h], center = true);
+
+ translate([resistor_x(type), 0])
+ circle(resistor_hole(resistor) / 2);
+ }
+
+ translate([-heater_length(type) / 2, thermistor_y(type), 0]) // hole for thermistor
+ rotate([0, 90, 0])
+ cylinder(r = resistor_hole(thermistor) / 2, h = 2 * resistor_length(thermistor), center = true);
+ }
+ //
+ // nozzle
+ //
+ cone_length = nozzle_cone_length(type);
+ cone_end_r = 1 / 2;
+ cone_start_r = nozzle_cone(type) / 2;
+ straight = 1;
+ nozzle_r = 0.4 / 2;
+ translate_z(-h / 2) vflip()
+ rotate_extrude()
+ polygon([
+ [nozzle_r, 0],
+ [cone_start_r, 0],
+ [cone_start_r, straight],
+ [cone_end_r, straight + cone_length],
+ [nozzle_r, straight + cone_length],
+ ]);
+ }
+}
+
+module jhead_hot_end(type, filament) {
+ resistor = RIE1212UB5C5R6;
+ thermistor = Epcos;
+ heater = type == JHeadMk4 ? MK4_heater : MK5_heater;
+
+ insulator_length = hot_end_insulator_length(type);
+ inset = hot_end_inset(type);
+ length = hot_end_total_length(type);
+ barrel_length = length - insulator_length;
+ vitamin(str("jhead_hot_end(", type[0], ", ", filament, "): Hot end ", hot_end_part(type), " ", filament, "mm"));
+ //
+ // insulator
+ //
+ translate_z(inset - insulator_length)
+ color(hot_end_insulator_colour(type)) rotate_extrude()
+ difference() {
+ chamfer = 2;
+ hull() {
+ translate([0, chamfer])
+ square([hot_end_insulator_diameter(type) / 2, insulator_length - chamfer]);
+
+ square([hot_end_insulator_diameter(type) / 2 - chamfer, insulator_length]);
+ }
+ square([3.2 / 2, insulator_length]);
+
+ translate([hot_end_groove_dia(type) / 2, insulator_length - hot_end_inset(type) - hot_end_groove(type)])
+ square([100, hot_end_groove(type)]);
+ }
+ //
+ // heater block
+ //
+ rotate(90)
+ translate([-nozzle_x(heater), 0, inset - insulator_length - heater_height(heater) / 2])
+ heater_block(heater, resistor, thermistor);
+}
+
+module jhead_hot_end_assembly(type, filament, naked = false) { //! Assembly with resistor, thermistor, tape, sleaving and ziptie
+ resistor = RIE1212UB5C5R6;
+ thermistor = Epcos;
+ heater = type == JHeadMk4 ? MK4_heater : MK5_heater;
+
+ insulator_length = hot_end_insulator_length(type);
+ inset = hot_end_inset(type);
+ length = hot_end_total_length(type);
+ barrel_length = length - insulator_length;
+ bundle = 3.2;
+ tape_width = 25;
+ tape_overlap = 10;
+ tape_thickness = 0.8;
+
+ jhead_hot_end(type, filament);
+
+ vitamin(": Tape self amalgamating silicone 110mm x 25mm");
+ //
+ // silcone tape
+ //
+ if(!naked)
+ color("red")
+ if(exploded())
+ translate([0, max(hot_end_insulator_diameter(type) / 2, heater_length(heater) / 2 - nozzle_x(heater)),
+ -tape_width + tape_overlap + inset - insulator_length])
+ cube([110, tape_thickness, tape_width]);
+ else
+ hull() {
+ translate_z(+ inset - insulator_length)
+ cylinder(r = hot_end_insulator_diameter(type) / 2 + 2 * tape_thickness, h = tape_overlap);
+
+ translate([0, -nozzle_x(heater), inset - insulator_length - heater_height(heater) / 2 + eps])
+ cube([heater_width(heater) + 4 * tape_thickness,
+ heater_length(heater) + 4 * tape_thickness, heater_height(heater)], center = true);
+ }
+ //
+ // Zip tie and heatshrink
+ //
+ if(!naked)
+ rotate(10) {
+ dia = hot_end_insulator_diameter(type);
+ scale([1, (bundle + dia) / dia])
+ translate([0, -bundle / 2, -7])
+ rotate(-110)
+ ziptie(small_ziptie, dia / 2);
+
+ translate([0, -dia / 2 - bundle / 2, 20])
+ scale([0.7, bundle / 6.4])
+ difference() {
+ tubing(HSHRNK64, 60);
+ if(!exploded())
+ translate_z(20)
+ cube([10, 10, 60], center = true);
+ }
+
+ }
+ wire("Red PTFE", 16, 170);
+ wire("Red PTFE", 16, 170);
+ //
+ // heater block
+ //
+ rotate(90)
+ translate([-nozzle_x(heater), 0, inset - insulator_length - heater_height(heater) / 2]) {
+ intersection() {
+ group() {
+ translate([resistor_x(heater), -exploded() * 15, 0])
+ rotate([90, 0, 0])
+ sleeved_resistor(resistor, PTFE20, bare = -10);
+
+ translate([-heater_length(heater) / 2 + resistor_length(thermistor) / 2 - exploded() * 10, thermistor_y(heater), 0])
+ rotate([90, 0, -90])
+ sleeved_resistor(thermistor, PTFE07, heatshrink = HSHRNK16);
+ }
+ if(!exploded())
+ if(naked)
+ color("grey") cylinder(r = 12, h = 100, center = true);
+ else
+ cube(1, true); // hide the wires when not exploded
+ }
+ }
+}
diff --git a/vitamins/leadnut.scad b/vitamins/leadnut.scad
new file mode 100644
index 0000000..56b570b
--- /dev/null
+++ b/vitamins/leadnut.scad
@@ -0,0 +1,67 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Nuts for leadscrews.
+//
+include <../core.scad>
+include <../utils/tube.scad>
+
+function leadnut_bore(type) = type[2]; //! Thread size
+function leadnut_od(type) = type[3]; //! Outer diameter of the shank
+function leadnut_height(type) = type[4]; //! Total height
+function leadnut_flange_dia(type) = type[5]; //! Flange diameter
+function leadnut_flange_t(type) = type[6]; //! Flange thickness
+function leadnut_flange_offset(type) = type[7]; //! Offset of the flange from the top
+function leadnut_holes(type) = type[8]; //! The number of screw holes
+function leadnut_hole_dia(type) = type[9]; //! The diameter of the screw holes
+function leadnut_hole_pitch(type) = type[10]; //! The radia pitch of the screw holes
+function leadnut_screw(type) = type[11]; //! The type of the fixing screws
+
+function leadnut_shank(type) = leadnut_height(type) - leadnut_flange_t(type) - leadnut_flange_offset(type); //! The length of the shank below the flange
+
+module leadnut_screw_positions(type) { //! Position children at the screw holes
+ holes = leadnut_holes(type);
+ for(i = [0 : holes - 1], a = i * 360 / holes + 180)
+ rotate(a)
+ translate([leadnut_hole_pitch(type), 0, leadnut_flange_t(type)])
+ rotate(45)
+ children();
+}
+
+module leadnut(type) { //! Draw specified leadnut
+ vitamin(str("leadnut(", type[0], "): ", type[1]));
+ bore_r = (leadnut_bore(type) + 0.5) / 2;
+
+ color("dimgrey") vflip()
+ translate_z(-leadnut_flange_offset(type) - leadnut_flange_t(type)) {
+ tube(or = leadnut_od(type) / 2, ir = bore_r, h = leadnut_height(type), center = false);
+
+ translate_z(leadnut_flange_offset(type))
+ linear_extrude(height = leadnut_flange_t(type))
+ difference() {
+ circle(d = leadnut_flange_dia(type));
+
+ circle(bore_r);
+
+ leadnut_screw_positions(type)
+ circle(d = leadnut_hole_dia(type));
+ }
+ }
+}
diff --git a/vitamins/leadnuts.scad b/vitamins/leadnuts.scad
new file mode 100644
index 0000000..e119cc2
--- /dev/null
+++ b/vitamins/leadnuts.scad
@@ -0,0 +1,25 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+LSN8x2 = ["LSN8x2", "Leadscrew nut 8 x 2", 8, 10.2, 15, 22, 3.5, 1.5, 4, 3.5, 8, M3_cap_screw];
+LSN8x8 = ["LSN8x8", "Leadscrew nut 8 x 8 RobotDigg",8, 12.75,19, 25.4, 4.1, 0, 3, 3.5, 19.05/2, M3_cap_screw];
+
+leadnuts = [LSN8x2, LSN8x8];
+
+use
diff --git a/vitamins/led.scad b/vitamins/led.scad
new file mode 100644
index 0000000..59ed0c2
--- /dev/null
+++ b/vitamins/led.scad
@@ -0,0 +1,56 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Standard domed through hole LEDs. Can specify colour and lead length.
+//
+include <../core.scad>
+use <../utils/rounded_cylinder.scad>
+
+function led_diameter(type) = type[1]; //! Body diameter
+function led_rim_dia(type) = type[2]; //! Rim diameter
+function led_rim_t(type) = type[3]; //! Rim height
+function led_height(type) = type[4]; //! Body height
+function led_pitch(type) = type[5]; //! Lead pitch
+function led_lead_t(type) = type[6]; //! Lead thickness
+
+function led_hole_radius(type) = led_diameter(type) / 2; //! Radius of panel hole to accept LED
+
+module led(type, colour = "red", lead = 5) { //! Draw specified LED with desired colour and led length
+ d = led_diameter(type);
+ vitamin(str("led(", type[0], arg(colour, "red"), "): LED ", d, " mm ", colour));
+
+ color(colour) {
+ rotate_extrude()
+ rounded_corner(r = d / 2, h = led_height(type), r2 = d / 2);
+
+ linear_extrude(height = led_rim_t(type))
+ difference() {
+ circle(d = led_rim_dia(type));
+
+ translate([d / 2 + eps, -5])
+ square(10);
+ }
+ }
+ color("silver")
+ for(side = [-1, 1], len = lead - side)
+ translate([side * led_pitch(type) / 2, 0, -len / 2])
+ vflip()
+ cube([led_lead_t(type), led_lead_t(type), len], center = true);
+}
diff --git a/vitamins/leds.scad b/vitamins/leds.scad
new file mode 100644
index 0000000..4da2f26
--- /dev/null
+++ b/vitamins/leds.scad
@@ -0,0 +1,34 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// d r r h p l
+// i i i e i e
+// a m m i t a
+// g c d
+// d t h h
+// t t
+//
+LED3mm = ["LED3mm", 3, 3.1, 1.0, 4.5, 2.54, 0.4];
+LED5mm = ["LED5mm", 5, 5.6, 0.9, 8.5, 2.54, 0.4];
+LED10mm = ["LED10mm", 10, 11.0, 2.0, 13.5, 2.54, 0.4];
+
+LEDs = [LED3mm, LED5mm, LED10mm];
+
+use
diff --git a/vitamins/light_strip.scad b/vitamins/light_strip.scad
new file mode 100644
index 0000000..9770bac
--- /dev/null
+++ b/vitamins/light_strip.scad
@@ -0,0 +1,166 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! LED strip lights that can be cut to size.
+//!
+//! The definitions are for the full length but they can be cut to size by specifying how many segments,
+//! which can by calcuated using ```light_strip_segments(type, max_length)```.
+//!
+//! The `light_strip_clip()` module makes a clip to go around the light that can be incorporated into a printed bracket to hold it.
+//
+include <../core.scad>
+
+function light_strip_length(type) = type[2]; //! Un-cut length
+function light_strip_leds(type) = type[3]; //! Total number of LEDs
+function light_strip_grouped(type) = type[4]; //! Number of LEDs in each group
+function light_strip_width(type) = type[5]; //! Outside width
+function light_strip_depth(type) = type[6]; //! Outside depth
+function light_strip_aperture(type) = type[7]; //! Inside width
+function light_strip_thickness(type) = type[8]; //! Metal thickness
+function light_strip_pcb_thickness(type) = type[9]; //! PCB thickness
+
+function light_strip_segments(type, max_length) = let( //! Calculate the maximum number of segments that fit in max_length
+ length = light_strip_length(type),
+ seg_length = length * light_strip_grouped(type) / light_strip_leds(type),
+ segs = floor(min(max_length, length) / seg_length)
+ )
+ assert(segs, str("max_length must be at least ", ceil(seg_length), "mm"))
+ segs;
+
+function light_strip_cut_length(type, segs) = ceil(light_strip_length(type) * segs * light_strip_grouped(type) / light_strip_leds(type)); //! Calculate cut length given segments
+
+module light_strip(type, segs = undef) { //! Draw specified light strip, segs can be used to limit the length
+ segment_length = light_strip_length(type) / (light_strip_leds(type) / light_strip_grouped(type));
+ segments = is_undef(segs) ? leds / light_strip_grouped(type) : segs;
+ l = light_strip_cut_length(type, segs);
+ vitamin(str("light_strip(", type[0], arg(segs, undef), "): Light strip ", type[1], " x ", l, "mm (", segments, " segments)"));
+ leds = light_strip_grouped(type) * segments;
+ w = light_strip_width(type);
+ d = light_strip_depth(type);
+ t = light_strip_thickness(type);
+ a = light_strip_aperture(type);
+ s = (w - a) / 2 - t;
+ p = light_strip_pcb_thickness(type);
+ x1 = w / 2;
+ x2 = x1 - t;
+ x3 = a / 2;
+ y1 = t;
+ y5 = d - s;
+ y4 = y5 - t;
+ y3 = y4 - p;
+ y2 = y3 - t;
+
+ module led_positions()
+ for(i = [0 : leds - 1])
+ translate([l * (i + 0.5) / leds - l / 2, 0])
+ children();
+
+ module segment_positions(n = segments)
+ for(i = [0 : 1 : n - 1])
+ translate([l * i / segments - l / 2, 0])
+ children();
+
+ module resistor_positions()
+ segment_positions()
+ for(end = [-1, 1], side = end > 0 ? [-1, 1] : [0])
+ translate([end * l / leds / 2 + segment_length / 2, side * 2])
+ children();
+
+ color("silver")
+ rotate([90, 0, 90])
+ linear_extrude(height = l, center = true)
+ polygon([
+ [ x1, 0], [ x1, d], [ x2, d], [ x3, y5], [ x3, y4], [ x2, y4],
+ [ x2, y3], [ x3, y3], [ x3, y2], [ x2, y2], [ x2, y1],
+ [-x2, y1], [-x2, y2], [-x3, y2], [-x3, y3], [-x2, y3], [-x2, y4],
+ [-x3, y4], [-x3, y5], [-x2, d], [-x1, d], [-x1, 0],
+ ]);
+
+ color("ghostwhite")
+ translate_z(y3 + p / 2)
+ cube([l, w - 2 * t, p], center = true);
+
+ translate_z(y4) {
+ color("white")
+ linear_extrude(height = 1.6)
+ led_positions()
+ square([5, 5], center = true);
+
+ color("yellow")
+ linear_extrude(height = 1.6 + eps)
+ led_positions()
+ circle(d = 3.5);
+
+ color("silver")
+ linear_extrude(height = 0.8)
+ led_positions()
+ for(side = [-1,1], end = [-1:1])
+ translate([side * 2.2, end * 1.6])
+ square([1, 0.9], center = true);
+
+ color("black")
+ linear_extrude(height = 0.1)
+ segment_positions(segments - 1)
+ translate([segment_length, 0])
+ square([0.2, a], center = true);
+
+ color("silver")
+ linear_extrude(height = 0.15)
+ segment_positions()
+ for(end = [-1, 1], side = [-1, 1])
+ translate([end * (segment_length / 2 - 1.25) + segment_length / 2, side * 2.5])
+ square(2.5, center = true);
+
+ color("silver")
+ linear_extrude(height = 0.55)
+ resistor_positions()
+ square([3.2, 1.5], center = true);
+
+ color("black")
+ linear_extrude(height = 0.55 + eps)
+ resistor_positions()
+ square([2.1, 1.5 + 2 * eps], center = true);
+
+ }
+
+ if(show_rays)
+ %cylinder(r = 1, h = 150);
+}
+
+wall = 1.8;
+clearance = 0.2;
+function light_strip_clip_slot(light) = light_strip_width(light) + clearance; //! Clip slot size
+function light_strip_clip_depth(light) = 10; //! Depth of the clip
+function light_strip_clip_length(light) = light_strip_clip_slot(light) + 2 * wall; //! Outside length
+function light_strip_clip_width(light) = light_strip_depth(light) + 2 * wall; //! Outside width
+
+module light_strip_clip(light) { //! Make a clip to go over the strip to be incorporated into a bracket
+ linear_extrude(height = light_strip_clip_depth(light), convexity = 2)
+ difference() {
+ translate([-light_strip_clip_length(light) / 2, -wall])
+ square([light_strip_clip_length(light), light_strip_clip_width(light)]);
+
+ translate([-light_strip_clip_slot(light) / 2, 0])
+ square([light_strip_clip_slot(light), light_strip_clip_width(light) - 2 * wall]);
+
+ translate([-light_strip_aperture(light) / 2, 0])
+ square([light_strip_aperture(light), 100]);
+ }
+}
diff --git a/vitamins/light_strips.scad b/vitamins/light_strips.scad
new file mode 100644
index 0000000..696349f
--- /dev/null
+++ b/vitamins/light_strips.scad
@@ -0,0 +1,33 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+// l l g w d a t p
+// e e r i e p h c
+// n d o d p e i b
+// g s u t t r c
+// t p h h t k t
+// h u n h
+// r e i
+// e s c
+// s k
+Rigid5050 = ["Rigid5050", "rigid SMD5050 low profile", 500, 36, 3, 14.4, 7, 10.4, 0.9, 1.2];
+RIGID5050 = ["RIGID5050", "rigid SMD5050" , 500, 36, 3, 14.4, 8.6, 10.4, 0.9, 1.6];
+
+light_strips = [Rigid5050, RIGID5050,];
+
+use
diff --git a/vitamins/linear_bearing.scad b/vitamins/linear_bearing.scad
new file mode 100644
index 0000000..1cc699b
--- /dev/null
+++ b/vitamins/linear_bearing.scad
@@ -0,0 +1,44 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! LMnUU linear bearings.
+//
+include <../core.scad>
+
+use <../utils/tube.scad>
+
+bearing_colour = grey70;
+seal_colour = grey20;
+
+function bearing_length(type) = type[1]; //! Total length
+function bearing_dia(type) = type[2]; //! Outside diameter
+function bearing_rod_dia(type) = type[3]; //! Internal diameter
+
+function bearing_radius(type) = bearing_dia(type) / 2; //! Outside radius
+
+module linear_bearing(type) { //! Draw specified linear bearing
+ vitamin(str("linear_bearing(", type[0], "): Linear bearing LM", bearing_rod_dia(type),"UU"));
+
+ casing_t = bearing_radius(type) / 10;
+ casing_ir = bearing_radius(type) - casing_t;
+
+ color(bearing_colour) tube(or = bearing_radius(type), ir = casing_ir, h = bearing_length(type));
+ color(seal_colour) tube(or = casing_ir, ir = bearing_rod_dia(type) / 2, h = bearing_length(type) - 0.5);
+}
diff --git a/vitamins/linear_bearings.scad b/vitamins/linear_bearings.scad
new file mode 100644
index 0000000..0600601
--- /dev/null
+++ b/vitamins/linear_bearings.scad
@@ -0,0 +1,33 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Linear bearings
+//
+LM12UU = ["LM12UU", 30, 21, 12];
+LM10UU = ["LM10UU", 29, 19, 10];
+LM8UU = ["LM8UU", 24, 15, 8];
+LM6UU = ["LM6UU", 19, 12, 6];
+LM5UU = ["LM5UU", 15, 10, 5];
+LM4UU = ["LM4UU", 12, 8, 4];
+LM3UU = ["LM3UU", 10, 7, 3];
+
+linear_bearings = [LM3UU, LM4UU, LM5UU, LM6UU, LM8UU, LM10UU, LM12UU];
+
+use
diff --git a/vitamins/mains_socket.scad b/vitamins/mains_socket.scad
new file mode 100644
index 0000000..e7dc2ac
--- /dev/null
+++ b/vitamins/mains_socket.scad
@@ -0,0 +1,123 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! UK 13A sockets at the moment.
+//
+include <../core.scad>
+include
+include
+include
+
+function mains_socket_width(type) = type[1]; //! Width at the base
+function mains_socket_depth(type) = type[2]; //! Depth at the base
+function mains_socket_top_w(type) = type[3]; //! Width at the top, might be tapered
+function mains_socket_top_d(type) = type[4]; //! Depth at the top, might be tapered
+function mains_socket_corner(type) = type[5]; //! Corner radius
+function mains_socket_height(type) = type[6]; //! Height
+function mains_socket_t(type) = type[7]; //! Plastic thickness
+function mains_socket_offset(type) = type[8]; //! Offset of the socket from the centre
+function mains_socket_pitch(type) = type[9]; //! Screw hole pitch
+function mains_socket_screw(type) = M3_cs_cap_screw; //! Screw type
+
+earth = M3_ringterm;
+earth_screw = ringterm_screw(earth);
+insert_wall = 3;
+
+function mains_socket_insert_boss(type) = 2 * (insert_hole_radius(screw_insert(mains_socket_screw(type))) + insert_wall);
+
+module face_plate(type)
+ rounded_square([mains_socket_width(type), mains_socket_depth(type)], mains_socket_corner(type));
+
+module mains_socket_hole_positions(type) //! Position children at the screw holes
+ for(side = [-1, 1])
+ translate([side * mains_socket_pitch(type) / 2, 0])
+ children();
+
+module mains_socket_earth_position(type) { //! Position of earth terminal for DiBond panel
+ inset = mains_socket_t(type) + washer_diameter(screw_washer(earth_screw)) / 2 + 1;
+
+ translate([-mains_socket_width(type) / 2 + inset, -mains_socket_depth(type) / 2 + inset])
+ children();
+}
+
+module mains_socket_holes(type, h = 0) { //! Panel cutout
+ mains_socket_hole_positions(type)
+ drill(screw_clearance_radius(mains_socket_screw(type)), h);
+
+ extrude_if(h)
+ offset(cnc_bit_r) offset(-cnc_bit_r) difference() {
+ offset(-7) face_plate(type);
+
+ for(side = [-1, 1])
+ hull()
+ for(x = [1, 2])
+ translate([side * mains_socket_pitch(type) / x, 0])
+ circle(4.5);
+
+ mains_socket_earth_position(type)
+ circle(d = washer_diameter(screw_washer(earth_screw)) + 2);
+ }
+
+ mains_socket_earth_position(type)
+ drill(screw_clearance_radius(earth_screw), h);
+}
+
+module mains_socket(type) { //! Draw specified 13A socket
+ offset = mains_socket_offset(type);
+ screw = mains_socket_screw(type);
+ height = mains_socket_height(type);
+
+ vitamin(str("mains_socket(", type[0], "): Mains socket 13A", offset.x || offset.y ? ", switched" : ""));
+
+ color("white") render() difference() {
+ hull() {
+ linear_extrude(height = eps)
+ face_plate(type);
+
+ linear_extrude(height = height)
+ offset(-(mains_socket_width(type) - mains_socket_top_w(type)) / 2)
+ face_plate(type);
+ }
+ // Holes for pins
+ translate([offset.x, offset.y, mains_socket_height(type)]) {
+ for(side = [-1, 1])
+ translate([side * 11.1, -11.1])
+ cube([7, 4.5, 8], center = true);
+
+ translate([0, 11.1])
+ cube([4.5, 8.5, 8], center = true);
+ }
+ // Hollow out the back
+ difference() {
+ linear_extrude(height = height - mains_socket_t(type))
+ offset(-mains_socket_t(type))
+ face_plate(type);
+
+ cube(50, center = true);
+ }
+ // Screw holes
+ mains_socket_hole_positions(type) {
+ cylinder(r = screw_clearance_radius(screw), h = 100, center = true);
+
+ translate_z(height)
+ screw_countersink(screw);
+ }
+ }
+}
diff --git a/vitamins/mains_sockets.scad b/vitamins/mains_sockets.scad
new file mode 100644
index 0000000..07250a5
--- /dev/null
+++ b/vitamins/mains_sockets.scad
@@ -0,0 +1,29 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! UK 13A sockets at the moment.
+//
+
+MKLOGIC = ["MKLOGIC", 86, 86, 86, 86, 3.6, 9, 3.6, [-9, -9], 60.3]; // Screwfix, switched
+Contactum = ["Contactum", 84, 84, 80, 80, 4.0, 10.5, 3.6, [ 0, 0], 60.3]; // Old and unswitched
+
+mains_sockets = [MKLOGIC, Contactum];
+
+use
diff --git a/vitamins/meter.scad b/vitamins/meter.scad
new file mode 100644
index 0000000..ba6f4fe
--- /dev/null
+++ b/vitamins/meter.scad
@@ -0,0 +1,130 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! LED volt meter modules available from China and a printed bezel that allows them to be mounted into a
+//! CNC cut panel. The meter is held in the bezel by melting the stakes with a soldering iron set to 200°C. The
+//! bezel is fixed in the panel with hot glue.
+//!
+//! Needs 7 segment font from to look realistic.
+//
+include <../core.scad>
+
+led_meter = ["led_meter", 22.72, 10.14, 6.3, 22.72, 11.04, 0.96, 30, 4.2, 26, 2.2 / 2];
+
+function meter() = led_meter; //! Default meter type
+
+function meter_length(type = led_meter) = type[1]; //! Length of body
+function meter_width(type = led_meter) = type[2]; //! Width of body
+function meter_height(type = led_meter) = type[3]; //! Height of body excluding PCB
+function meter_pcb_length(type = led_meter) = type[4]; //! PCB length excluding lugs
+function meter_pcb_width(type = led_meter) = type[5]; //! PCB width
+function meter_pcb_thickness(type = led_meter) = type[6]; //! PCB thickness
+function meter_lug_length(type = led_meter) = type[7]; //! PCB length including lugs
+function meter_lug_width(type = led_meter) = type[8]; //! Lug width
+function meter_hole_pitch(type = led_meter) = type[9]; //! Lug hole pitch
+function meter_hole_radius(type = led_meter) = type[10]; //! Lug hole radius
+
+module meter_hole_positions(type = led_meter) //! Position children over the holes
+ for(side = [-1, 1])
+ translate([side * meter_hole_pitch(type) / 2, 0])
+ children();
+
+module meter(type = led_meter, colour = "red", value = "888", display_colour = false) //! Draw a meter with optional colour and display value
+{
+ vitamin(str("meter(", type[0], arg(colour, "red", "colour"), "): LED meter ", colour));
+
+ color("grey")
+ translate_z(meter_height(type) / 2)
+ cube([meter_length(type), meter_width(type), meter_height(type)], center = true);
+
+ color("green")
+ translate_z(meter_height(type))
+ linear_extrude(height = meter_pcb_thickness(type))
+ difference() {
+ union() {
+ square([meter_pcb_length(type), meter_pcb_width(type)], center = true);
+
+ square([meter_lug_length(type), meter_lug_width(type)], center = true);
+ }
+ meter_hole_positions(type)
+ circle(meter_hole_radius(type));
+ }
+
+ color(display_colour ? display_colour : colour)
+ linear_extrude(height = 0.2, center = true)
+ mirror([1,0,0])
+ text(value, font = "7 segment", valign = "center", halign = "center", size = meter_width(type) - 2, spacing = 1.2);
+}
+
+clearance = 0.1;
+overlap = 1;
+flange_t = 1;
+
+function meter_bezel_wall(type = led_meter) = (meter_lug_length(type) - meter_length(type)) / 2; //! Printed bezel wall thickness
+function meter_bezel_rad(type = led_meter) = meter_bezel_wall(type); //! Printed bezel corner radius
+function meter_bezel_length(type = led_meter) = meter_length(type) + 2 * (meter_bezel_wall(type) + overlap); //! Printed bezel length
+function meter_bezel_width(type = led_meter) = meter_width(type) + 2 * (meter_bezel_wall(type) + overlap); //! Printed bezel width
+
+module meter_bezel_hole(type = led_meter, h = 100) { //! Make a hole to fit the meter Bezel
+ wall = meter_bezel_wall(type) + clearance;
+ rad = meter_bezel_rad(type) + clearance;
+ l = meter_length(type);
+ w = meter_width(type);
+
+ extrude_if(h)
+ rounded_square([l + 2 * wall, w + 2 * wall], rad);
+}
+
+module meter_bezel(type = led_meter) { //! Generate the STL for the meter bezel
+ stl("meter_bezel");
+
+ wall = meter_bezel_wall(type);
+ rad = meter_bezel_rad(type);
+ l = meter_length(type);
+ w = meter_width(type);
+ h = meter_height(type);
+
+ union() {
+ linear_extrude(height = h)
+ difference() {
+ rounded_square([l + 2 * wall, w + 2 * wall], rad);
+
+ square([l + 2 * clearance, w + 2 * clearance], center = true);
+ }
+
+ linear_extrude(height = flange_t)
+ difference() {
+ rounded_square([l + 2 * wall + 2 * overlap, w + 2 * wall + 2 * overlap], rad + overlap);
+
+ square([l + 2 * clearance, w + 2 * clearance], center = true);
+ }
+ meter_hole_positions(type)
+ cylinder(r = meter_hole_radius(type), h = h + meter_pcb_thickness(type) * 2);
+ }
+}
+
+module meter_assembly(type = led_meter, colour = "red", value = "888", display_colour = false) { //! Meter assembled into the bezel
+ vflip()
+ translate_z(-flange_t) {
+ color("dimgrey") meter_bezel(type);
+
+ meter(type, colour, value, display_colour);
+ }
+}
diff --git a/vitamins/microswitch.scad b/vitamins/microswitch.scad
new file mode 100644
index 0000000..53bbb6e
--- /dev/null
+++ b/vitamins/microswitch.scad
@@ -0,0 +1,117 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Used for limit switches.
+//
+include <../core.scad>
+
+microswitch_contact_color = "gold";
+
+function microswitch_thickness(type) = type[2]; //! Body thickness
+function microswitch_width(type) = type[3]; //! Body width
+function microswitch_length(type) = type[4]; //! Body length
+function microswitch_radius(type) = type[5]; //! Body corner radius
+function microswitch_hole_d(type) = type[6]; //! Screw hole diameter
+function microswitch_holes(type) = type[7]; //! Hole positions
+function microswitch_button_w(type) = type[8]; //! Button width
+function microswitch_button_t(type) = type[9]; //! Button thickness
+function microswitch_button_pos(type)= type[10]; //! Button position
+function microswitch_legs(type) = type[11]; //! Leg positions
+function microswitch_leg(type) = type[12]; //! Leg types
+function microswitch_body_clr(type) = type[13]; //! Body colour
+function microswitch_button_clr(type)= type[14]; //! Button colour
+
+function microswitch_lower_extent(type) = let(leg = microswitch_leg(type)) min([for(pos = microswitch_legs(type)) pos.y - leg.y / 2]); //! How far legs extend downwards
+function microswitch_right_extent(type) = let(leg = microswitch_leg(type)) max([microswitch_length(type) / 2, for(pos = microswitch_legs(type)) pos.x + leg.x / 2]); //! How far legs extend right
+
+module microswitch_hole_positions(type) //! Place children at the hole positions
+{
+ for(hole = microswitch_holes(type))
+ translate(hole)
+ children();
+}
+
+module microswitch_wire_positions(type, skip = undef) { //! Place children at the leg hole positions
+ leg = microswitch_leg(type);
+ legs = microswitch_legs(type);
+ for(i = [0 : len(legs) - 1])
+ if(i != skip)
+ let(pos = legs[i])
+ translate(leg[3] ? pos + leg[4] : pos - [leg.x, leg.y] / 6)
+ children();
+}
+
+module microswitch(type) { //! Draw specified microswitch
+ vitamin(str("microswitch(", type[0], "): Microswitch ", type[1]));
+ d = microswitch_button_t(type);
+
+ color(microswitch_body_clr(type))
+ linear_extrude(height = microswitch_thickness(type), center = true)
+ difference() { // main body
+ rounded_square([microswitch_length(type), microswitch_width(type)], microswitch_radius(type));
+
+ microswitch_hole_positions(type)
+ circle(d = microswitch_hole_d(type));
+ }
+
+ color(microswitch_button_clr(type)) // orange button
+ translate(microswitch_button_pos(type) - [0, d / 2])
+ linear_extrude(height = microswitch_button_w(type), center = true)
+ hull() {
+ circle(d = d);
+
+ translate([0, -3])
+ circle(d = d);
+ }
+
+ color(microswitch_contact_color) // yellow contacts
+ for(pos = microswitch_legs(type))
+ translate(pos) {
+ leg = microswitch_leg(type);
+ vertical = leg.y > leg.x;
+
+ if(vertical)
+ rotate([0, 90, 0])
+ linear_extrude(height = leg.x, center = true)
+ difference() {
+ square([leg.z, leg.y], center = true);
+
+ if(leg[3])
+ translate(leg[4])
+ circle(d = leg[3]);
+ }
+ else
+ rotate([90, 0, 0])
+ linear_extrude(height = leg.y, center = true)
+ difference() {
+ square([leg.x, leg.z], center = true);
+
+ if(leg[3])
+ translate(leg[4])
+ circle(d = leg[3]);
+ }
+
+ if(!vertical && pos.y < -microswitch_width(type) / 2) {
+ gap = -microswitch_width(type) / 2 - pos.y;
+ translate([-leg.x / 2 + leg.y / 2, gap / 2])
+ cube([leg.y, gap, leg.z], center = true);
+ }
+ }
+}
diff --git a/vitamins/microswitches.scad b/vitamins/microswitches.scad
new file mode 100644
index 0000000..58f2351
--- /dev/null
+++ b/vitamins/microswitches.scad
@@ -0,0 +1,33 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Microswitches
+//
+small_leg = [0.9, 3.3, 0.4, 0];
+medium_leg = [0.5, 3.9, 3.2, 1.6, [0, -0.5]];
+large_leg = [11.4, 0.8, 6.3, 1.8, [1.7, 0]];
+
+small_microswitch = ["small_microswitch", "DM1-00P-110-3", 5.8, 6.5, 12.8, 0, 2, [[-3.25, -1.65], [3.25, -1.65]], 2.9, 1.2, [-1.95, 3.75], [[-5.08, -4.95], [0, -4.9], [5.08, -4.9] ], small_leg, grey20, "white" ];
+medium_microswitch = ["medium_microswitch","SS-01 or SS-5GL", 6.4, 10.2, 19.8, 1, 2.35, [[-4.8, -2.6 ], [4.7, -2.6 ]], 3.2, 2, [-2.8, 5.8 ], [[-8.05, -7.05], [0.75, -7.05], [8.05, -7.05] ], medium_leg, grey20, "burlywood" ];
+large_microswitch = ["large_microswitch", "Saia G3 low force", 10.4, 15.9, 28.0, 2, 3.1, [[-11.1, -5.15], [11.2, 5.15]], 4, 2.75,[-9.1, 9.55], [[19.7, 2.19], [19.7, -3.45], [8.3, -10.45] ], large_leg, "ivory", "white" ];
+
+microswitches = [small_microswitch, medium_microswitch, large_microswitch];
+
+use
diff --git a/vitamins/microview.scad b/vitamins/microview.scad
new file mode 100644
index 0000000..f33c736
--- /dev/null
+++ b/vitamins/microview.scad
@@ -0,0 +1,59 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Microview OLED display with on board AVR by geekammo / Sparkfun.
+//!
+//! ```microview()``` generates the model. ```microview(true)``` makes an object to cut out a panel aperture for it.
+//!
+//! Uses STL files copyright geekammo and licenced with MIT license, see [microview/LICENSE.txt](vitamins/microview/LICENSE.txt).
+//
+include <../core.scad>
+use // for pin
+
+panel_clearance = 0.2;
+
+module microview(cutout = false) { //! Draw microview or generate a panel cutout for it
+ rotate([0, 0, -90])
+ if(cutout)
+ linear_extrude(height = 100)
+ offset(panel_clearance)
+ projection()
+ hull()
+ import("microview/GKM-003_R05_CHIP_LOWER_HOUSING.stl");
+ else {
+ vitamin("microview(): Microview OLED display");
+
+ translate_z(8.35) {
+
+ color("black")
+ import("microview/GKM-002_R05_CHIP_UPPER_HOUSING-1.stl", convexity = 2);
+
+ translate([-2, 0, 0])
+ color("dimgray")
+ cube([12.5, 15.5, 4.41], center = true);
+ }
+ color("dimgray")
+ import("microview/GKM-003_R05_CHIP_LOWER_HOUSING.stl", convexity = 2);
+
+ for(side = [-1, 1], i = [0 : 7])
+ translate([side * inch(0.35), (i - 3.5) * inch(0.1)])
+ pin();
+ }
+}
diff --git a/vitamins/microview/GKM-002_R05_CHIP_UPPER_HOUSING-1.STL b/vitamins/microview/GKM-002_R05_CHIP_UPPER_HOUSING-1.STL
new file mode 100644
index 0000000..ed64b93
Binary files /dev/null and b/vitamins/microview/GKM-002_R05_CHIP_UPPER_HOUSING-1.STL differ
diff --git a/vitamins/microview/GKM-003_R05_CHIP_LOWER_HOUSING.STL b/vitamins/microview/GKM-003_R05_CHIP_LOWER_HOUSING.STL
new file mode 100644
index 0000000..e7524c1
Binary files /dev/null and b/vitamins/microview/GKM-003_R05_CHIP_LOWER_HOUSING.STL differ
diff --git a/vitamins/microview/LICENSE.txt b/vitamins/microview/LICENSE.txt
new file mode 100644
index 0000000..d6e0a5b
--- /dev/null
+++ b/vitamins/microview/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 geekammo
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/vitamins/module.scad b/vitamins/module.scad
new file mode 100644
index 0000000..c3e4470
--- /dev/null
+++ b/vitamins/module.scad
@@ -0,0 +1,147 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Random screw down modules. Currently just DROK buck converters.
+//
+include <../core.scad>
+include
+
+function mod_part(type) = type[1]; //! Description
+function mod_length(type) = type[2]; //! Body length
+function mod_width(type) = type[3]; //! Body width
+function mod_height(type) = type[4]; //! Body height
+function mod_screw(type) = type[5]; //! Screw type
+function mod_screw_z(type)= type[6]; //! Thickness of screw lug
+function mod_hole_r(type) = type[7] / 2; //! Screw hole radius
+function mod_holes(type) = type[8]; //! Screw hole positions
+
+module mod(type) { //! Draw specified module
+ vitamin(str("mod(", type[0], "): ", mod_part(type)));
+
+ module drok_buck() {
+ l = mod_length(type);
+ w = mod_width(type);
+ h = mod_height(type);
+ body_l = 31;
+ end_t = 1.5;
+ chamfer = 2;
+ lug_l = (l - body_l) / 2 - end_t;
+ lug_w = 19;
+ lug_w2 = 10;
+ lug_t = 2;
+ lug_r = 2.5;
+ hole_r = mod_hole_r(type);
+ vane_t = 1;
+ vane_h = 5;
+ vane_l = 7;
+ vane_p = 10;
+ boss_od = 7.5;
+ boss_id = 5;
+ boss_h = 2.3;
+ boss_chamfer = 1;
+
+
+ module profile()
+ hull() {
+ translate([-w / 2, 0])
+ square([w, h - chamfer]);
+
+ translate([-w / 2 + chamfer, 0])
+ square([w - 2 * chamfer, h]);
+ }
+
+ module lug()
+ difference() {
+ hull() {
+ for(side = [-1, 1])
+ translate([side * lug_w2 / 2, lug_l - lug_r])
+ circle(lug_r);
+
+ translate([-lug_w / 2, -1])
+ square([lug_w, 1]);
+ }
+ hull()
+ for(y = [hole_r, 100])
+ translate([0, y])
+ circle(hole_r);
+ }
+
+ color("silver")
+ rotate([90, 0, 90])
+ linear_extrude(height = body_l, center = true)
+ profile();
+
+ color(grey20)
+ for(end = [-1, 1])
+ translate([end * body_l / 2, 0, 0])
+ rotate([90, 0, end * 90])
+ union() {
+ linear_extrude(height = end_t) // endcap
+ profile();
+
+ translate_z(end_t)
+ rotate([90, 0, 180])
+ linear_extrude(height = lug_t) // lug
+ lug();
+
+ for(side = [-1, 1]) {
+ translate([side * vane_p / 2, lug_t, end_t]) // buttress vanes
+ rotate([0, -90, 0])
+ linear_extrude(height = vane_t, center = true)
+ polygon([[0, 0], [0, vane_h - lug_t], [vane_l, 0]]);
+
+ translate([side * vane_p / 2, h / 2, end_t]) // bosses
+ rotate_extrude()
+ difference() {
+ hull() {
+ square([boss_od / 2,boss_h - boss_chamfer]);
+ square([boss_od / 2 - boss_chamfer, boss_h]);
+ }
+ translate([0, boss_h - boss_chamfer])
+ square([boss_id / 2, boss_h]);
+ }
+ }
+ }
+ }
+ drok_buck();
+}
+
+module mod_screw_positions(type) //! Position children at the screw positions
+ for(p = mod_holes(type))
+ translate([p.x, p.y])
+ children();
+
+module module_assembly(type, thickness) { //! Module with its fasteners in place
+ screw = mod_screw(type);
+ washer = screw_washer(screw);
+ nut = screw_nut(screw);
+ screw_length = screw_longer_than(thickness + mod_screw_z(type) + 2 * washer_thickness(washer) + nut_thickness(nut, true));
+
+ mod(type);
+
+ mod_screw_positions(type) {
+ translate_z(mod_screw_z(type))
+ nut_and_washer(nut, true);
+
+ translate_z(-thickness)
+ vflip()
+ screw_and_washer(screw, screw_length);
+ }
+}
diff --git a/vitamins/modules.scad b/vitamins/modules.scad
new file mode 100644
index 0000000..f7df40b
--- /dev/null
+++ b/vitamins/modules.scad
@@ -0,0 +1,37 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+// Random modules
+//
+
+//
+// p l w h s s h h
+// a e i e c c o o
+// r n d i r r l l
+// t g t g e e e e
+// h h h w w s
+// t d
+// z
+//
+drok_buck = ["drok_buck", "Drok buck converter", 50, 25, 20.5, M4_dome_screw, 2, 4, [[-21, 0], [21, 0]]];
+
+modules = [drok_buck];
+
+use
diff --git a/vitamins/nut.scad b/vitamins/nut.scad
new file mode 100644
index 0000000..16c7e0e
--- /dev/null
+++ b/vitamins/nut.scad
@@ -0,0 +1,140 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Default is steel but can be drawn as brass or nylon. A utility for making nut traps included.
+//!
+//! If a nut is given a child then it gets placed on its top surface.
+//
+include <../core.scad>
+use
+use
+use <../utils/rounded_cylinder.scad>
+
+function nut_size(type) = type[1]; //! Diameter of the corresponding screw
+function nut_radius(type) = type[2] / 2; //! Radius across the corners
+function nut_thickness(type, nyloc = false) = nyloc ? type[4] : type[3]; //! Thickness of plain or nyloc version
+function nut_washer(type) = type[5]; //! Corresponding washer
+function nut_trap_depth(type) = type[6]; //! Depth of nut trap
+
+function nut_flat_radius(type) = nut_radius(type) * cos(30); //! Radius across the flats
+
+module nut(type, nyloc = false, brass = false, nylon = false) { //! Draw specified nut
+ hole_rad = nut_size(type) / 2;
+ outer_rad = nut_radius(type);
+ thickness = nut_thickness(type);
+ nyloc_thickness = type[4];
+ desc = nyloc ? "nyloc" : brass ? "brass" : nylon ? "nylon" : "";
+ vitamin(str("nut(", type[0], arg(nyloc, false, "nyloc"), arg(brass, false, "brass"), arg(nylon, false, "nylon"),
+ "): Nut M", nut_size(type), " ", desc));
+
+ if(exploded() && nyloc)
+ cylinder(r = 0.2, h = 10);
+
+ color(brass? brass : nylon ? grey30: grey70) translate_z((exploded() && nyloc) ? 10 : 0) {
+ linear_extrude(height = thickness)
+ difference() {
+ circle(outer_rad, $fn = 6);
+
+ circle(hole_rad);
+ }
+ if(nyloc)
+ translate_z(-eps)
+ rounded_cylinder(r = outer_rad * cos(30) , h = nyloc_thickness, r2 = (nyloc_thickness - thickness) / 2, ir = hole_rad);
+ }
+ if($children)
+ translate_z(thickness)
+ children();
+}
+
+module nut_and_washer(type, nyloc) { //! Draw nut with corresponding washer
+ washer = nut_washer(type);
+
+ translate_z(exploded() ? 7 : 0)
+ washer(washer);
+
+ translate_z(washer_thickness(washer))
+ nut(type, nyloc);
+}
+
+module wingnut(type) { //! Draw a wingnut
+ hole_rad = nut_size(type) / 2;
+ bottom_rad = nut_radius(type);
+ top_rad = type[4] / 2;
+ thickness = nut_thickness(type);
+ wing_span = type[7];
+ wing_height = type[8];
+ wing_width = type[9];
+ wing_thickness = type[10];
+
+ top_angle = asin((wing_thickness / 2) / top_rad);
+ bottom_angle = asin((wing_thickness / 2) / bottom_rad);
+
+ vitamin(str("wingnut(", type[0], "): Wingnut M", nut_size(type)));
+
+ explode(10) color(grey70) {
+ rotate_extrude()
+ polygon([
+ [hole_rad, 0],
+ [bottom_rad, 0],
+ [top_rad,, thickness],
+ [hole_rad, thickness]
+ ]);
+ for(rot = [0, 180])
+ rotate([90, 0, rot]) linear_extrude(height = wing_thickness, center = true)
+ hull() {
+ translate([wing_span / 2 - wing_width / 2, wing_height - wing_width / 2])
+ circle(wing_width / 2);
+ polygon([
+ [bottom_rad * cos(top_angle) - eps, 0],
+ [wing_span / 2 - wing_width / 2, wing_height - wing_width / 2],
+ [top_rad * cos(top_angle) - eps, thickness],
+ ]);
+ }
+ }
+}
+function nut_trap_radius(nut, horizontal = false) = nut_radius(nut) + (horizontal ? layer_height / 4 : 0); //! Radius across the corners of a nut trap
+function nut_trap_flat_radius(nut, horizontal = false) = nut_trap_radius(nut, horizontal) * cos(30); //! Radius across the flats of a nut trap
+
+module nut_trap(screw, nut, depth = 0, horizontal = false, supported = false, h = 200) { //! Make a nut trap
+ nut_r = is_list(nut) ? nut_trap_radius(nut, horizontal) : nut + (horizontal ? layer_height / 4 : 0);
+ nut_d = depth ? depth : nut_trap_depth(nut);
+ screw_r = is_list(screw) ? screw_clearance_radius(screw) : screw;
+ render(convexity = 5) union() {
+ if(horizontal) {
+ if(screw_r)
+ teardrop_plus(r = screw_r, h = h);
+
+ cylinder(r = nut_r, h = nut_d * 2, center = true, $fn = 6);
+ }
+ else {
+ difference() {
+ union() {
+ if(screw_r)
+ poly_cylinder(r = screw_r, h = h, center = true);
+
+ cylinder(r = nut_r, h = nut_d * 2, center = true, $fn = 6);
+ }
+ if(supported)
+ translate_z(nut_d - eps)
+ cylinder(r = nut_r + eps, h = layer_height, center = false);
+ }
+ }
+ }
+}
diff --git a/vitamins/nuts.scad b/vitamins/nuts.scad
new file mode 100644
index 0000000..6de2459
--- /dev/null
+++ b/vitamins/nuts.scad
@@ -0,0 +1,56 @@
+//
+// NopSCADlib Copyright Chris Palmer 2018
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see