#!/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, scale=1, sides=4, N=0): radians = math.pi / 180.0 degrees = 1 / radians wall = scale * 2 # 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 * .2, N) # vertical Z coordinate for edge angle surface # create mating-surface angled edge profile rs = math.tan(math.pi / sides) rr = wall * 2 edge_points = ( (yv, 0), # initial +Y (yv, rr), # +Y +Z (yv - rs * rr, rr), # -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 = .2 sphere_offset = xv * .105 #ph = wall * 3 # create core of "male" connection side plug_box = ( cq.Workplane("XY") .box(xv * 0.21, scale * 5, scale * 6) .translate((-xv * center, iw - rs * 3, wall + scale* 3)) .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 - rs * 3, wall + scale * 3)) ) 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) # determines the tightness of fit tang_separation = 1.3 y_off = iw - scale * 12 # create two "female" support tangs left_tang = ( cq.Workplane("XY") .box(xv * 0.05, y_off, scale * 5.7 ) .translate((-xv * -center+sphere_offset * tang_separation, iw - y_off * .5, wall + scale * 3)) .edges("|X") .fillet(rr * .1) ) section.add(left_tang) # duplicate existing part right_tang = left_tang.translate((-sphere_offset * tang_separation * 2, 0, 0)) section.add(right_tang) # this connects the free-turning # tangs to the polygon body rear_supp = ( cq.Workplane("XY") .box(xv * 0.34, yv * 0.1, scale * 7) .translate((xv * center, iw - y_off * 0.92, 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 - rs * 3, wall + scale * 3)) ) 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 # sides sides = 4 match mode: case 0: # create two small test items print("Creating test items:") polygon = build_poly(show,R, scale, sides, N=sides) polygon.export(f"polygon_face_full_{sides}_sides.stl") polygon = build_poly(show,R, 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.") 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