From 85e695ff8d1b3f90c3ed6e114c98decc21d4dfaa Mon Sep 17 00:00:00 2001
From: wes <wes@wesk.tech>
Date: Mon, 22 Apr 2024 14:45:06 -0400
Subject: [PATCH] initial commit

---
 __init__.py             |   0
 coaster.py              |  59 +++++++++++++++
 dice.py                 |  50 +++++++++++++
 enclosure.py            | 159 ++++++++++++++++++++++++++++++++++++++++
 light_holder.py         |  53 ++++++++++++++
 oil_cap.py              |  15 ++++
 picture_frame.py        |  53 ++++++++++++++
 planter.py              |  95 ++++++++++++++++++++++++
 power_bar_holder.py     |  19 +++++
 solder_stand.py         |  32 ++++++++
 tape_holder.py          |  57 ++++++++++++++
 threaded_tool_holder.py |  29 ++++++++
 tool_holder.py          |  38 ++++++++++
 tool_holder_final.py    |  55 ++++++++++++++
 utilities.py            |  16 ++++
 xmas_coaster.py         |  93 +++++++++++++++++++++++
 16 files changed, 823 insertions(+)
 create mode 100644 __init__.py
 create mode 100755 coaster.py
 create mode 100644 dice.py
 create mode 100755 enclosure.py
 create mode 100644 light_holder.py
 create mode 100644 oil_cap.py
 create mode 100644 picture_frame.py
 create mode 100644 planter.py
 create mode 100644 power_bar_holder.py
 create mode 100644 solder_stand.py
 create mode 100644 tape_holder.py
 create mode 100644 threaded_tool_holder.py
 create mode 100644 tool_holder.py
 create mode 100644 tool_holder_final.py
 create mode 100644 utilities.py
 create mode 100755 xmas_coaster.py

diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/coaster.py b/coaster.py
new file mode 100755
index 0000000..1fef543
--- /dev/null
+++ b/coaster.py
@@ -0,0 +1,59 @@
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+import cadquery as cq
+
+coaster_radius = 75
+xmas_tree_offset = -35
+xmas_tree_height = 12
+xmas_tree_width = 12
+
+def makeTriangle(self, width, height, x_offset=0, y_offset=0):
+   return (self.moveTo(x_offset-(width), y_offset).makePolygon(
+      [
+       (0,0,0),(width,height,0), (width*2,0,0),
+      ]
+   )
+   .cutBlind(30)
+   .clean())
+
+def makeXmasTree(self):
+  return (self.center(0,0)
+  .triangle(xmas_tree_width, xmas_tree_height, y_offset=-(xmas_tree_offset))
+  .triangle(xmas_tree_width+3, xmas_tree_height+3, y_offset=-(xmas_tree_offset+8))
+  .triangle(xmas_tree_width+1, xmas_tree_height+13, y_offset=-(xmas_tree_offset+15))
+  .triangle(xmas_tree_width-2, xmas_tree_height+16, y_offset=-(xmas_tree_offset+20))
+  .moveTo(0, -(xmas_tree_offset+22))
+  .rect(5,8)
+  .cutBlind(30))
+   
+Workplane.triangle = makeTriangle
+Workplane.xmasTree = makeXmasTree
+
+
+polygon = (Workplane()
+                .makePolygon(
+                    regularPolygon(
+                        nSides = 16, 
+                        radius = coaster_radius, 
+                        thetaStart = 0, 
+                        thetaEnd = 360
+                    )
+                )
+                .extrude(10)
+                .xmasTree()
+                .workplane()
+            )
+
+text_solid = (Workplane()
+        .text("David Kerfoot",
+              25, 25,
+              clean=False,
+              combine=False,
+              fontPath="/home/wes/cad_files/StackyardPersonalUse-16Dj.ttf")
+        )
+
+
+result = polygon.cut(text_solid)
+
+cq.exporters.export(result, "/home/wes/cad_files/coaster.step")
diff --git a/dice.py b/dice.py
new file mode 100644
index 0000000..adbd68e
--- /dev/null
+++ b/dice.py
@@ -0,0 +1,50 @@
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import cadquery as cq
+import functools as fnc
+import itertools as it
+from cad_utilities.shapes import makeDice
+from typing import cast
+from cadquery import Face
+from cqmore import Workplane
+from cqmore.polyhedron import tetrahedron, hexahedron, octahedron, dodecahedron, icosahedron
+
+radius = 20
+font_name = 'Arial Black'
+font_distance = 2
+detail = 0
+
+dice = (Workplane()
+           .polyhedron(
+               *icosahedron(radius, detail)
+           )
+       )
+
+faces = dice.faces().vals()
+nums = len(faces)
+texts = Workplane()
+for i in range(nums):
+    print(i)
+    if i == 0:
+        text_to_write = "20"
+        font_size = 8.5
+    else:
+        text_to_write = str(nums-i)
+        font_size = 8.5
+    texts.add(
+        Workplane(faces[i])
+            .workplane(origin = cast(Face, faces[i]).Center())
+            .text(
+                text_to_write, 
+                font_size, 
+                -font_distance,
+                fontPath="/home/deck/cad_files/SEVESBRG.TTF"
+            )
+    )
+
+dice = dice.cut(texts)
+
+cq.exporters.export(dice, "/home/deck/cad_files/dice.step")
+
diff --git a/enclosure.py b/enclosure.py
new file mode 100755
index 0000000..7e3c589
--- /dev/null
+++ b/enclosure.py
@@ -0,0 +1,159 @@
+import cadquery as cq
+
+socket_distance_x = 10.8
+socket_distance_y = 36.55
+socket_width = 28
+
+# the width of the bezel on the side of the inner relay
+side_bezel_width = 3.14
+
+# how far the plug in the middle is from the side
+# where the socket that powers the relay coil is
+distance_from_socket_to_end = 46
+
+relay_x_size = 120.2
+relay_y_size = 168
+
+# dimensions of the socket for coil power on the side
+end_socket_width = 30.4
+end_socket_height = 22.6
+
+# the distance from the side of the socket to the edge of the box
+# for the socket that powers the relay coil
+end_socket_distance_from_side = 3.47 + 3 # add 3mm for tape
+
+reset_button_dist_from_socket = 5.27
+reset_width = 18.7
+reset_height = 35.04
+reset_lip_height = 3.37 # probably not needed
+
+# parameter definitions
+p_outerWidth = relay_x_size + 15    # Outer width of box enclosure
+p_outerLength = relay_y_size  # Outer length of box enclosure
+p_outerHeight = 33.6 + 30  # Outer height of box enclosure
+
+p_thickness = 3.0  # Thickness of the box walls
+p_sideRadius = 10.0  # Radius for the curves around the sides of the box
+p_topAndBottomRadius = (
+    2.0  # Radius for the curves on the top and bottom edges of the box
+)
+
+p_screwpostInset = 12.0  # How far in from the edges the screw posts should be place.
+p_screwpostID = 4.0  # Inner Diameter of the screw post holes, should be roughly screw diameter not including threads
+p_screwpostOD = 10.0  # Outer Diameter of the screw posts.\nDetermines overall thickness of the posts
+
+p_boreDiameter = 8.0  # Diameter of the counterbore hole, if any
+p_boreDepth = 1.0  # Depth of the counterbore hole, if
+p_countersinkDiameter = 0.0  # Outer diameter of countersink. Should roughly match the outer diameter of the screw head
+p_countersinkAngle = 90.0  # Countersink angle (complete angle between opposite sides, not from center to one side)
+p_flipLid = True  # Whether to place the lid with the top facing down or not.
+p_lipHeight = 1.0  # Height of lip on the underside of the lid.\nSits inside the box body for a snug fit.
+
+# outer shell
+oshell = (
+    cq.Workplane("XY")
+    .rect(p_outerWidth, p_outerLength)
+    .extrude(p_outerHeight + p_lipHeight)
+)
+
+# weird geometry happens if we make the fillets in the wrong order
+if p_sideRadius > p_topAndBottomRadius:
+    oshell = oshell.edges("|Z").fillet(p_sideRadius)
+    oshell = oshell.edges("#Z").fillet(p_topAndBottomRadius)
+else:
+    oshell = oshell.edges("#Z").fillet(p_topAndBottomRadius)
+    oshell = oshell.edges("|Z").fillet(p_sideRadius)
+
+# inner shell
+ishell = (
+    oshell.faces("<Z")
+    .workplane(p_thickness, True)
+    .rect((p_outerWidth - 2.0 * p_thickness), (p_outerLength - 2.0 * p_thickness))
+    .extrude(
+        (p_outerHeight - 2.0 * p_thickness), False
+    )  # set combine false to produce just the new boss
+)
+ishell = ishell.edges("|Z").fillet(p_sideRadius - p_thickness)
+
+# make the box outer box
+box = oshell.cut(ishell)
+
+# make the screw posts
+POSTWIDTH = p_outerWidth - 2.0 * p_screwpostInset
+POSTLENGTH = p_outerLength - 2.0 * p_screwpostInset
+
+box = (
+    box.faces(">Z")
+    .workplane(-p_thickness)
+    .rect(POSTWIDTH, POSTLENGTH, forConstruction=True)
+    .vertices()
+    .circle(p_screwpostOD / 2.0)
+    .circle(p_screwpostID / 2.0)
+    .extrude(-1.0 * (p_outerHeight + p_lipHeight - p_thickness), True)
+)
+
+# split lid into top and bottom parts
+(lid, bottom) = (
+    box.faces(">Z")
+    .workplane(-p_thickness - p_lipHeight)
+    .split(keepTop=True, keepBottom=True)
+    .all()
+)  # splits into two solids
+
+lid = (lid.workplane()
+       .rect(socket_width+socket_distance_x, socket_distance_y+(socket_width),
+             forConstruction=True)
+       .tag("sockets")
+       .vertices()
+       .rect(socket_width, socket_width)
+       .cutThruAll())
+
+bottom = (bottom
+          .faces(">Y")
+          .workplane()
+          .center(0, -end_socket_height+8)
+          .rect(end_socket_width, end_socket_height)
+          .cutBlind("next"))
+
+
+#lid = (lid.center(socket_width-8, socket_distance_y+(socket_width)-3)
+#       .rect(reset_height, reset_width)
+#       .cutThruAll())
+
+# translate the lid, and subtract the bottom from it to produce the lid inset
+lowerLid = lid.translate((0, 0, -p_lipHeight))
+cutlip = lowerLid.cut(bottom).translate(
+    (p_outerWidth + p_thickness, 0, p_thickness - p_outerHeight + p_lipHeight)
+)
+
+# compute centers for screw holes
+topOfLidCenters = (
+    cutlip.faces(">Z")
+    .workplane(centerOption="CenterOfMass")
+    .rect(POSTWIDTH, POSTLENGTH, forConstruction=True)
+    .vertices()
+)
+
+# add holes of the desired type
+if p_boreDiameter > 0 and p_boreDepth > 0:
+    print("first")
+    topOfLid = topOfLidCenters.cboreHole(
+        p_screwpostID, p_boreDiameter, p_boreDepth, 2.0 * p_thickness
+    )
+elif p_countersinkDiameter > 0 and p_countersinkAngle > 0:
+    print("second")
+    topOfLid = topOfLidCenters.cskHole(
+        p_screwpostID, p_countersinkDiameter, p_countersinkAngle, 2.0 * p_thickness
+    )
+else:
+    print("third")
+    topOfLid = topOfLidCenters.hole(p_screwpostID, 2.0 * p_thickness)
+
+# flip lid upside down if desired
+if p_flipLid:
+    topOfLid = topOfLid.rotateAboutCenter((1, 0, 0), 180)
+
+# return the combined result
+result = topOfLid.union(bottom)
+
+cq.exporters.export(result, "/home/wes/cad_files/enclosure.step")
\ No newline at end of file
diff --git a/light_holder.py b/light_holder.py
new file mode 100644
index 0000000..082859b
--- /dev/null
+++ b/light_holder.py
@@ -0,0 +1,53 @@
+import cadquery as cq
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import functools as fnc
+import itertools as it
+
+
+inner_len = 100
+inner_width = 68.8
+inner_height = 49.7
+
+outer_len = 131.9
+outer_thickness = 5.26
+outer_width = inner_width
+
+hole_diameter = 6
+
+hole_dist_from_long = 10.2
+hole_dist_from_short = 5.37
+
+recess_height = 61.9
+recess_width = 11.9
+
+clip_thickness = 15
+clip_length = 65
+clip_width = 60
+
+clip_distance_from_sensor = 90 # how far it is from the enclosure
+
+result = (Workplane().
+            box(outer_len, outer_width, outer_thickness)
+            .edges("|Z").fillet(0.9))
+
+
+result = result.moveTo(outer_width-recess_width, 0).rect(recess_width, recess_height).extrude(5)
+result = result.moveTo(-outer_width+recess_width, 0).rect(recess_width, recess_height).extrude(5)
+result = result.workplane(offset=-(outer_thickness/2)-0.33).moveTo(-outer_len/2, 0).rect(clip_length/2, clip_width).extrude(outer_thickness)
+result = result.workplane(offset=-0.33).moveTo(inner_height+(clip_length/2.0)-1, 0).box(clip_width/1.8, clip_width/2.5, outer_thickness).moveTo(outer_len/2+26, 0).cylinder(10, 5/2, combine="cut")
+result = result.moveTo(outer_len/2.4+30, 0).cylinder(10, 12/2, combine="cut")
+result = result.faces(">Z[0]").moveTo(-70, 0).rect(10, clip_width).extrude(clip_length)
+
+result = (result.faces(">Z[1]").moveTo(0,0)
+            .rect(outer_len-hole_dist_from_short-hole_diameter*2,
+                  outer_width-hole_dist_from_long-hole_diameter*3,
+                  forConstruction=True)
+            .vertices().cylinder(outer_thickness*3, hole_diameter/2, combine="cut"))
+
+
+
+
+cq.exporters.export(result, "/home/deck/cad_files/light_holder.step")
diff --git a/oil_cap.py b/oil_cap.py
new file mode 100644
index 0000000..1d60778
--- /dev/null
+++ b/oil_cap.py
@@ -0,0 +1,15 @@
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import cadquery as cq
+import functools as fnc
+import itertools as it
+from cad_utilities.shapes import makeTriangle
+
+
+cone = cq.Solid.makeCone(8/2, 1, 28)
+
+result = Workplane().circle(8.3/2).extrude(30).cut(cone)
+
+cq.exporters.export(result, "/home/deck/cad_files/oil_cap.step")
\ No newline at end of file
diff --git a/picture_frame.py b/picture_frame.py
new file mode 100644
index 0000000..ca640d3
--- /dev/null
+++ b/picture_frame.py
@@ -0,0 +1,53 @@
+import cadquery as cq
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import functools as fnc
+import itertools as it
+
+picture_height = 178.3
+length = picture_height+5
+picture_length = 4.2
+width = picture_length + 10
+picture_width = 127
+height = picture_width + 10
+
+triangle_size = 25
+triangle_width = width+15
+
+
+def makeTriangle(self, width, height, x_offset=0, y_offset=0):
+   return (self.moveTo(x_offset-(width), y_offset).makePolygon(
+      [
+       (width, height, 0), (width*2, 0, 0), (width*2, width*2, 0),
+      ]
+   ))
+
+def add_triangle(plane, width, height, offset):
+    return plane.triangle(width, height, y_offset=offset)
+
+Workplane.triangle = makeTriangle
+
+result = Workplane("front").box(length, width, height)
+
+result = result.workplane().box(length-20, width, height-15, True, "cut")
+
+result = result.workplane().box(length-2, picture_length, picture_width+2, True, "cut")
+
+result = result.faces("<X[4]").workplane(0, True).rect(width, height).cutBlind(1)
+
+result = (result.faces("<X[0]")
+          .workplane()
+          .rect(width, height)
+          .extrude(triangle_size*0.5)
+          .faces("<Z[4]")
+          .workplane()
+          .triangle(triangle_size , triangle_size, 0, -(triangle_size))
+          .extrude(triangle_width)
+          .faces("<Z[1]")
+          .workplane()
+          .triangle(triangle_size,triangle_size, 0, -(triangle_size))
+          .extrude(triangle_width))
+
+cq.exporters.export(result, "/home/deck/cad_files/small_frame.step")
diff --git a/planter.py b/planter.py
new file mode 100644
index 0000000..8369848
--- /dev/null
+++ b/planter.py
@@ -0,0 +1,95 @@
+from cq_warehouse.bearing import SingleRowDeepGrooveBallBearing
+from cq_warehouse.fastener import *
+from cq_warehouse.thread import *
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from cqmore.polyhedron import polarZonohedra, Polyhedron
+from cq_warehouse.extensions import Workplane
+import functools as fnc
+import itertools as it
+import cadquery as cq
+import cqmore
+
+Workplane = cqmore.extend(Workplane)
+
+planter_height = 210
+planter_radius = 130
+
+plate_offset = 47
+plate_thickness = 3
+
+zonohedra = polarZonohedra(8, 49)
+
+outer_points = [[n*(planter_height/3.5) for n in vec] for vec in zonohedra.points]
+
+outer_faces = zonohedra.faces
+
+outer_poly = Polyhedron(outer_points, outer_faces)
+
+result = Workplane().polyhedron(*outer_poly)
+
+holder_angle_offset = 20
+
+result = (result.workplane(offset=-38).makePolygon(
+                                regularPolygon(nSides=4,
+                                               radius=planter_radius-40,
+                                               thetaStart=holder_angle_offset,
+                                               thetaEnd=360+holder_angle_offset),
+                                forConstruction=True)
+                            .vertices().cylinder(10, 7, combine="cut"))
+
+cone = cq.Solid.makeCone(planter_radius, 33, planter_height+95)
+
+result = result.workplane().cut(cone)
+
+result = result.workplane(offset=-40).cylinder(planter_height/1.45, planter_radius*0.58, combine="cut")
+result = result.workplane(offset=-16).cylinder(planter_height/2.3, planter_radius/2.5, combine="cut")
+result = result.workplane(offset=-5).cylinder(planter_height/2.3, planter_radius/1.85, combine="cut")
+
+#result = result.workplane(offset=-10).makePolygon(regularPolygon(nSides=4, radius=planter_radius/2.8, thetaStart=holder_angle_offset, thetaEnd=360+holder_angle_offset)).extrude(planter_height/2.8, combine="cut")
+
+result = result.workplane(offset=40).makePolygon(regularPolygon(nSides=8, radius=planter_radius/2.2, thetaStart=holder_angle_offset, thetaEnd=360+holder_angle_offset)).extrude(planter_height/15.5, combine="cut")
+
+
+circle_plate = Workplane().cylinder(plate_thickness, planter_radius/2.2)
+
+# Split this out into a different part
+circle_plate = (circle_plate.workplane(offset=plate_offset+plate_thickness)
+          .makePolygon(
+              star(
+                  outerRadius=planter_radius/2.6,
+                  innerRadius=planter_radius/4.9,
+                  n=8),
+              forConstruction=True)
+          .vertices().cskHole(8, 8, 0.5, depth=None))
+
+simple = False
+
+screw = ButtonHeadScrew(size="M16-2", fastener_type="iso7380_1", length=10 * MM, simple=simple)
+
+scaled_screw = screw.scale(0.92)
+
+screw_result = Workplane().circle(20).extrude(10).union(scaled_screw)
+
+result = result.workplane(offset=planter_height/2).cylinder(80, 65, combine="cut")
+result = result.workplane(offset=(planter_height/2)-27).makePolygon(regularPolygon(nSides=11,
+                                                                                   radius=planter_radius/1.85,
+                                                                                   thetaStart=holder_angle_offset,
+                                                                                   thetaEnd=360+holder_angle_offset)).extrude(10)
+result = result.workplane(offset=(planter_height/2)-40).cylinder(80, 8.1, combine="cut")
+result = result.workplane(offset=(planter_height/2)-15.6).threadedHole(screw, 20, simple=simple, fit="Loose")
+
+assembly = cq.Assembly()
+
+assembly.add(screw_result, loc=cq.Location((0,0,0)))
+assembly.add(result, loc=cq.Location((100,100,100)))
+assembly.add(circle_plate, loc=cq.Location((1100,1100,1100)))
+
+try:
+    show_object(result)
+except NameError:
+    pass
+
+cq.exporters.export(screw_result, "/home/deck/cad_files/planter_screw.step")
+cq.exporters.export(result, "/home/deck/cad_files/planter.step")
+cq.exporters.export(circle_plate, "/home/deck/cad_files/planter_plate.step")
diff --git a/power_bar_holder.py b/power_bar_holder.py
new file mode 100644
index 0000000..97059b4
--- /dev/null
+++ b/power_bar_holder.py
@@ -0,0 +1,19 @@
+import cadquery as cq
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import functools as fnc
+import itertools as it
+
+post_width = 3.5
+holder_length = 19
+holder_height = 4
+holder_width = 1
+post_height = 10
+
+result = Workplane().box(holder_length, holder_height, holder_width).edges("|Z").fillet(0.9)
+
+result = result.workplane(offset=4.5).cylinder(post_height, post_width/2)
+
+cq.exporters.export(result, "/home/deck/cad_files/power_holder_new.step")
diff --git a/solder_stand.py b/solder_stand.py
new file mode 100644
index 0000000..850277a
--- /dev/null
+++ b/solder_stand.py
@@ -0,0 +1,32 @@
+import cadquery as cq
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import functools as fnc
+import itertools as it
+
+height = 30
+width = 94
+length = 130
+stand_height=23
+
+socket_radius = 10.7/2
+
+result = Workplane().box(length, width, height).edges("|Z").fillet(0.9)
+
+
+result = result.workplane(offset=5).box(length-5, width-20, height, combine="cut")
+
+result = (result.workplane(offset=10).moveTo(0, -(width/2)+5)
+          .box(length=length, width=0.85, height=stand_height, combine="cut"))
+
+result = (result.workplane(offset=10).moveTo(0, (width/2)-5)
+          .box(length=length, width=0.85, height=stand_height, combine="cut"))
+
+result = result.faces(">X[3]").workplane().moveTo(-25,-5).sphere(socket_radius, combine="cut")
+result = result.faces(">X[1]").box(12.5,5.5,7.5, combine="cut")
+
+result = result.faces(">X[0]").center(15, -10).text("Danger!", 8.5, -1.6, fontPath="/home/wes/cad_files/StackyardPersonalUse-16Dj.ttf")
+
+cq.exporters.export(result, "/home/deck/cad_files/solder_stand.step")
diff --git a/tape_holder.py b/tape_holder.py
new file mode 100644
index 0000000..0713f49
--- /dev/null
+++ b/tape_holder.py
@@ -0,0 +1,57 @@
+import cadquery as cq
+from cq_warehouse.fastener import SocketHeadCapScrew
+from cq_warehouse.bearing import SingleRowDeepGrooveBallBearing
+import cq_warehouse.extensions
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import functools as fnc
+import itertools as it
+
+
+def makeTriangle(self, width, height, x_offset=0, y_offset=0):
+   return (self.move(x_offset, y_offset).makePolygon(
+      [
+       (height/2, width*20, -width), (0, width, 0), (width, height, 0)
+      ]
+   ))
+
+def add_triangle(plane, width, height, offset):
+    return plane.triangle(width, height, y_offset=offset)
+
+Workplane.triangle = makeTriangle
+
+# must be 17 mm wide
+# gap must be at least 4 mm
+
+holder_dim = (18, 30, 30)
+holder_slot_dim = (holder_dim[0] + holder_dim[0]*0.65, 
+                   holder_dim[1]-holder_dim[1]*0.2,
+                   holder_dim[1]-holder_dim[1]*0.865) # should be around 4mm
+
+pole_dim = (18, 10)
+lip_dim = (pole_dim[0], 15)
+pole_length = 230
+
+print(holder_slot_dim)
+
+result = Workplane().box(*holder_dim)
+result = result.workplane().moveTo(0, -5).box(*holder_slot_dim, True, "cut")
+result = (result.faces(">Z").workplane()
+          .moveTo(0, (holder_dim[0]) - 8)
+          .rect(*pole_dim).extrude(pole_length))
+
+support_width = 10
+
+result = result.faces(">Y[1]").workplane().triangle(support_width,
+                                                    support_width,
+                                                    -support_width/2,
+                                                    -holder_dim[0]).extrude(15)
+
+result = (result.faces(">Z[3]").center(0, pole_length-lip_dim[0]/4).workplane()
+          .moveTo(0, lip_dim[1])
+          .rect(*lip_dim).extrude(pole_dim[0]/4))
+
+cq.exporters.export(result, "/home/deck/cad_files/tape_holder.step")
+
diff --git a/threaded_tool_holder.py b/threaded_tool_holder.py
new file mode 100644
index 0000000..8c2e874
--- /dev/null
+++ b/threaded_tool_holder.py
@@ -0,0 +1,29 @@
+import cadquery as cq
+from cq_warehouse.fastener import SocketHeadCapScrew
+from cq_warehouse.bearing import SingleRowDeepGrooveBallBearing
+from cq_warehouse.thread import *
+from cq_warehouse.fastener import *
+from cq_warehouse.extensions import Workplane
+from random import randint, choice
+import functools as fnc
+import itertools as it
+
+# must be 17 mm wide
+# gap must be at least 4 mm
+
+pole_length = 50*MM
+
+plate = (
+    cq.Workplane()
+    .circle(8)
+    .extrude(pole_length)
+)
+
+simple = False
+screw = HexHeadScrew("M8-1.25", length=pole_length/2, fastener_type="din931", simple=simple)
+threaded_hole = plate.faces(">Z[1]").threadedHole(screw, pole_length/2, simple=simple, fit="Loose")
+show_object(threaded_hole)
+
+cq.exporters.export(threaded_hole, "/home/deck/cad_files/threaded_tool_holder.step")
+cq.exporters.export(screw, "/home/deck/cad_files/threaded_tool_holder_screw.step")
+
diff --git a/tool_holder.py b/tool_holder.py
new file mode 100644
index 0000000..e5fa994
--- /dev/null
+++ b/tool_holder.py
@@ -0,0 +1,38 @@
+import cadquery as cq
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import functools as fnc
+import itertools as it
+
+
+top_rect_len = 6.28
+bottom_rect_len = 21.5
+sides_width = 4
+base_thickness = 7
+
+height = 2.5
+
+
+result = (cq.Workplane().moveTo(-top_rect_len+2, 0)
+        .box(sides_width, bottom_rect_len, height)
+        .edges("|Z").fillet(1.25))
+
+
+result = (result.faces(">Z[0]").circle(1.5)
+    .workplane()
+    .circle(sides_width-1)
+    .loft()
+    .clean())
+
+result = result.faces(">Z[0]").box(bottom_rect_len*6, bottom_rect_len*2, base_thickness)
+result = (result
+    .faces(">Z").workplane()
+    .rect(bottom_rect_len*5, bottom_rect_len*1, forConstruction=True)
+    .vertices()
+    .cboreHole(4,8,7, depth=None))
+
+
+
+cq.exporters.export(result, "/home/deck/cad_files/power_bar_holder.step")
diff --git a/tool_holder_final.py b/tool_holder_final.py
new file mode 100644
index 0000000..2317c4b
--- /dev/null
+++ b/tool_holder_final.py
@@ -0,0 +1,55 @@
+import cadquery as cq
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import functools as fnc
+import itertools as it
+
+wall_width = 12
+length = 90
+width = 150
+height = 100
+magnet_height = 27
+magnet_width = 27
+magnet_thickness = 5
+bottom_thickness = 12
+
+first_magnet_position = 35
+second_magnet_position = -35
+
+magnet_slot_offset = 10
+second_slot_offset = -25
+
+result = Workplane("front").box(length, width, height)
+result = result.workplane().center(0, -bottom_thickness).workplane().box(length-wall_width,
+                                                           width-wall_width,
+                                                           height-wall_width,
+                                                           True,
+                                                           "cut")
+
+result = (result.faces(">Z").workplane(0, True)
+                           .moveTo(0, first_magnet_position).rect(magnet_width,
+                                               magnet_height).cutBlind(magnet_thickness))
+
+result = (result.faces(">Z").workplane(0, True)
+                            .moveTo(0, magnet_slot_offset).rect(magnet_width,
+                                                magnet_height).cutBlind(magnet_thickness)
+                            .faces(">Z")
+                            .moveTo(0, magnet_slot_offset)
+                            .rect(magnet_width, magnet_height).extrude(2))
+
+result = (result.faces(">Z").workplane(0, True)
+                           .moveTo(0, second_magnet_position).rect(magnet_width,
+                                               magnet_height).cutBlind(magnet_thickness))
+
+result = (result.faces(">Z").workplane(0, True)
+                            .moveTo(0, second_magnet_position+second_slot_offset).rect(magnet_width,
+                                                magnet_height).cutBlind(magnet_thickness)
+                            .faces(">Z")
+                            .moveTo(0, second_magnet_position+second_slot_offset)
+                            .rect(magnet_width, magnet_height).extrude(2))
+
+
+
+cq.exporters.export(result, "/home/deck/cad_files/tool_holder.step")
diff --git a/utilities.py b/utilities.py
new file mode 100644
index 0000000..fed104b
--- /dev/null
+++ b/utilities.py
@@ -0,0 +1,16 @@
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import cadquery as cq
+import functools as fnc
+import itertools as it
+
+def makeTriangle(self, width, height, x_offset=0, y_offset=0):
+   return (self.moveTo(x_offset-(width), y_offset).makePolygon(
+      [
+       (0,0,0),(width,height,0), (width*2,0,0),
+      ]
+   )
+   .cutBlind(30)
+   .clean())
diff --git a/xmas_coaster.py b/xmas_coaster.py
new file mode 100755
index 0000000..01e0964
--- /dev/null
+++ b/xmas_coaster.py
@@ -0,0 +1,93 @@
+from cqmore import Workplane
+from cqmore.curve import archimedeanSpiral, circle
+from cqmore.polygon import regularPolygon, star
+from random import randint, choice
+import cadquery as cq
+import functools as fnc
+import itertools as it
+from cad_utilities.shapes import makeTriangle
+
+coaster_radius = 50
+
+def add_triangle(plane, width, height, offset):
+    return plane.triangle(width, height, y_offset=offset)
+
+def addXmas(self, offsets):
+    xmas_tree_offset = -25
+    xmas_tree_height = 8
+    xmas_tree_width = 6
+    w_off, h_off, y_off = offsets
+    return self.triangle(xmas_tree_width+(choice([-1,1])*w_off),
+                         xmas_tree_height+(choice([-1,1])*h_off),
+                         y_offset=-(xmas_tree_offset+(y_off)))
+
+
+def makeXmasTree(self):
+   xmas_tree_offset = -25
+   xmas_tree_height = 8
+   xmas_tree_width = 6
+   
+   offsets = [(0,0,0)] + [tuple(randint(1, 5) for _ in range(4)) for _ in range(3)]
+   print(offsets)
+   
+   offsets_added = list(it.accumulate(offsets, lambda t1, t2: list(a+b for a,b in zip(t1, t2))))
+   
+   last_y = offsets_added[-1][2]
+   
+   #offsets = [(0,0,0), (3,2,6), (4,11,13), (4,13,16)]
+   
+   tree = fnc.reduce(addXmas, offsets_added, self.center(0,0))
+   
+   return (tree
+  .moveTo(0, -(xmas_tree_offset+last_y))
+  .rect(2,5)
+  .cutThruAll())
+   
+Workplane.triangle = makeTriangle
+Workplane.xmasTree = makeXmasTree
+
+
+polygon = (Workplane()
+                .makePolygon(
+                    regularPolygon(
+                        nSides = 16, 
+                        radius = coaster_radius, 
+                        thetaStart = 0, 
+                        thetaEnd = 360
+                    )
+                )
+                .extrude(7, clean=True, combine=True, taper=15)
+                .xmasTree()
+            )
+
+result = (polygon.workplane()
+        .text("Wes",
+              10, 40,
+              clean=True,
+              combine="cut",
+              fontPath="/home/wes/TiltNeon-Regular-VariableFont_XROT,YROT.ttf")
+        )
+
+
+result = (result.faces("<Z").workplane()
+          .rect(50, 50, forConstruction=True)
+          .vertices()
+          .circle(5)
+          .extrude(-2, combine="cut"))
+
+result = (result.faces(">Z").workplane()
+                .makePolygon(
+                    regularPolygon(
+                        nSides = 10, 
+                        radius = coaster_radius-5, 
+                        thetaStart = 0, 
+                        thetaEnd = 360
+                    ),
+                    forConstruction=True
+                )
+          .vertices()
+          .circle(2)
+          .extrude(5))
+
+cq.exporters.export(result, "/home/deck/cad_files/coaster.step")
+