#!/usr/bin/env python3 # type: ignore # ************************************************************************** # Copyright (C) 2025, Paul Lutus * # * # This program 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 2 of the License, or * # (at your option) any later version. * # * # This program 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 this program; if not, write to the * # Free Software Foundation, Inc., * # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * # ************************************************************************** # this script displays results using # CQ-Editor if launched within it import cadquery as cq import copy import math, sys # a classic interpolation algorithm def ntrp(x, xa, xb, ya, yb): return (x - xa) * (yb - ya) / (xb - xa) + ya # create perforations like those in the classic # Roman dodecahedron, with decorative rings def make_perf(assembly, R, scale, N): # generate variery of radii circ_radius = ntrp(N, 0, 11, 14, 18) * scale assembly -= cq.Workplane("XY").circle(circ_radius).extrude(16 * scale) rings = cq.Workplane("XZ") for r in (2, 4, 6): rings += ( cq.Workplane("XZ") .moveTo((circ_radius) + r * scale) .circle(0.5 * scale) .revolve() ) return assembly.cut(rings) # build_poly: # show, used in CQ-editor # single = one edge, otherwise full figure # R = radius center to vertex, # scale: sets all polygon proportions # sides = sides of a polygon # N = generate N segments def build_poly(show=True, R=45, wallv = 2,scale=1, sides=4, N=0): radians = math.pi / 180.0 degrees = 1 / radians wall = scale * wallv # wall thickness mm # X/Y values define the pie slice xv = R * math.sin(math.pi / sides) # diagonal length, X coordinate yv = R * math.cos(math.pi / sides) # diagonal height, Y coordinate # construct primary slice polygon_points = ( (0, 0), (xv, yv), (-xv, yv), (0, 0), ) section = cq.Workplane("XY").polyline(polygon_points).close().extrude(wall) # perforate polygon canter, add decorative rings section = make_perf(section, R, scale * sides * 0.2, N) # vertical Z coordinate for edge angle surface # create mating-surface angled edge profile rs = math.cos(math.pi / sides) q = wall * 5 edge_points = ( (yv, 0), # initial +Y (yv, q), # +Y +Z (yv - q * rs, q), # -Y +Z (yv, 0), # initial +Y ) edge = ( cq.Workplane("YZ") .polyline(edge_points) .close() .extrude(xv * 2) .translate((-xv, 0, 0)) ) section = section.cut(edge) # inside wall iw = yv - wall * math.tan(math.pi / sides) # print(f"yv = {yv:.4f}, iw = {iw:.4f}") # show_results(section) # return section center = 0.2 sphere_offset = xv * 0.105 # create core of "male" connection side junct_height = wall + scale * 4 iw = yv - junct_height * rs # Y arg ib = yv - wall * rs # surface height # create core of "male" connection side plug_box = ( cq.Workplane("XY") .box(xv * 0.21, scale * 5, scale * 7) .translate((-xv * center, iw + scale * 0.2, junct_height * 0.8)) .edges("|Z") .fillet(1 * scale) ) section.add(plug_box) # create two "male" connection pivots pivot_sphere_a = ( cq.Workplane("XY") .sphere(scale * 1.4) .translate((-xv * center + sphere_offset, iw, junct_height)) ) section.add(pivot_sphere_a) # duplicate existing part pivot_sphere_b = pivot_sphere_a.translate((-sphere_offset * 2, 0, 0)) section.add(pivot_sphere_b) # NOTE: fitness control variables: # "tang_separation" determines # tightness of fit -- more # separation provides easier # assembly / disassembly tang_separation = 1.3 # "tang_thickness" : thinner # provides easier disassembly tang_thickness = xv * 0.05 y_off = scale * 15 # create two "female" support tangs left_tang = ( cq.Workplane("XY") .box(tang_thickness, y_off, scale * 6.5) .translate( ( -xv * -center + sphere_offset * tang_separation, ib - y_off * 0.5, wall + scale * 3.5, ) ) .edges("|X") .fillet(0.8) ) section.add(left_tang) # duplicate existing part right_tang = left_tang.translate((-sphere_offset * tang_separation * 2, 0, 0)) section.add(right_tang) rear_supp = ( cq.Workplane("XY") .box(xv * 0.34, yv * 0.1, scale * 8) .translate((xv * center, iw - y_off * 0.8, wall + scale * 3)) ) section.add(rear_supp) testing = False if testing: # alignment testing, do not delete spherec = ( cq.Workplane("XY") .sphere(scale * 1.4) .translate((s * 0.045, yv * 0.86, wall * 2)) ) section = section.add(spherec) sphered = spherec.translate((s * 0.21, 0, 0)) section = section.add(sphered) else: # create a cylindrical opening between the tangs cylinder = ( cq.Workplane("YZ") .circle(scale * 1.4) .extrude(14 * scale) .translate( (-xv * center + sphere_offset * 2, iw, junct_height) ) ) section = section.cut(cylinder) # show_results(section) # return section # workplane for results polygon = cq.Workplane("XY") # generate N polygons # sides = N angle = 0 step = 360 / sides for i in range(N): # print(f"sweep angle : {angle}") polygon.add(section.rotate((0, 0, 0), (0, 0, 1), angle)) angle += step if show: show_results(polygon) return polygon def show_results(item): try: show_object(item) except: print(" The show_object() function only works in CQ-Editor.") def main(mode): show = True # this is for CQ-editor # radius, distance between center and vertex, mm R = 45 # default value # maintain overall scale proportional to R, # the center to vertex distance scale = R / 45 # polygon sides sides = 4 wall = 2 # mm match mode: case 0: # create working models print("Creating prototypes:") polygon = build_poly(show, R, wall,scale, sides, N=sides) polygon.export(f"polygon_face_full_{sides}_sides.stl") polygon = build_poly(show, R, wall,scale, sides, N=1) polygon.export(f"polygon_face_slice_{sides}_sides.stl") case 1: # create the full set 12 polygons print("Mode 1 not used.") case 2: print("Mode 2 not used.") print("Done.") # if __name__ == "__main__": main(0) # create test items # main(1) # create a full set of 12 polygons # main(2) # create prototype peg and array