From d678faa30518e561ab47302f227bdf965defca9f Mon Sep 17 00:00:00 2001 From: Marius Unsel Date: Fri, 27 Feb 2026 02:05:50 +0100 Subject: [PATCH] add every older ipynb file --- HeadphoneRing.ipynb | 83 ++ README.md | 4 + SyncGrips.ipynb | 845 ++++++++++++++++++ Untitled.ipynb | 308 +++++++ dempf.ipynb | 333 +++++++ lookup_comparison.ipynb | 116 +++ pretosius.ipynb | 685 +++++++++++++++ regal.ipynb | 343 ++++++++ smartphonestand.ipynb | 149 ++++ smue2.ipynb | 1812 +++++++++++++++++++++++++++++++++++++++ v2_SyncGrips.ipynb | 220 +++++ 11 files changed, 4898 insertions(+) create mode 100644 HeadphoneRing.ipynb create mode 100644 SyncGrips.ipynb create mode 100644 Untitled.ipynb create mode 100644 dempf.ipynb create mode 100644 lookup_comparison.ipynb create mode 100644 pretosius.ipynb create mode 100644 regal.ipynb create mode 100644 smartphonestand.ipynb create mode 100644 smue2.ipynb create mode 100644 v2_SyncGrips.ipynb diff --git a/HeadphoneRing.ipynb b/HeadphoneRing.ipynb new file mode 100644 index 0000000..ff7c51f --- /dev/null +++ b/HeadphoneRing.ipynb @@ -0,0 +1,83 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "28e3cc06-aac4-4154-94d7-000f17d4f3fa", + "metadata": {}, + "source": [ + "# headphone cushing ring" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ddf6db8-8ffc-4200-940b-797d58e0e9a3", + "metadata": {}, + "outputs": [], + "source": [ + "import cadquery as cq\n", + "\n", + "\n", + "from jupyter_cadquery import (\n", + " versions,\n", + " show, PartGroup, Part, \n", + " get_viewer, close_viewer, get_viewers, close_viewers, open_viewer, set_defaults, get_defaults, open_viewer,\n", + " get_pick,\n", + ")\n", + "from jupyter_cadquery.ocp_utils import webcol_to_cq\n", + "\n", + "from jupyter_cadquery.replay import replay, enable_replay, disable_replay\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "215f449e-c975-43fe-a826-e3904c113025", + "metadata": {}, + "outputs": [], + "source": [ + "headphone_ring = (cq.Workplane(\"XY\")\n", + ".circle(43/2)\n", + ".circle(48/2)\n", + ".extrude(3)\n", + " #.faces(\"" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sensor_width = 35\n", + "wall_thickness = 3\n", + "\n", + "w1 = sensor_width + 2* wall_thickness #sensor casing\n", + "w2 = 150 #sensor center \n", + "h = 85 # total height\n", + "h1 = 20 # sensor casing height\n", + "h2 = 12\n", + "h3 = 35# sensor casing + mpu height\n", + "r = 25 #grip radius\n", + "r1 = 8\n", + "locs = ((0,h+10), (-10,h-10),(10,h-10)) #locations of screws\n", + "handle_len = w2+w1-20-6\n", + "\n", + "\n", + "with BuildPart() as slim_cutout:\n", + " with BuildSketch(Plane.XY.offset(12)) as slim_sk:\n", + " Polygon((w2/2+10,50),(w2/2-w1+10,0),\\\n", + " (0,0),(0,60),(60,90),\\\n", + " align=Align.MIN)\n", + " mirror(about=Plane.YZ)\n", + " extrude(amount=-30)\n", + " chamfer(length=12, length2=20, objects=slim_cutout.edges().group_by(Axis.Z)[-1])\n", + "\n", + "slim_cutout2 = (Rotation(0,180,0)*Location((0,0,-w1))*slim_cutout.part)\n", + "\n", + "with BuildPart() as slim_cutout_mpu:\n", + " with BuildSketch(Plane.XY.offset(12)) as slim_sk_mpu:\n", + " with Locations((-w2/2-10,0)):\n", + " Polygon((w2/2-w1+10,0),(w2/2+10,50),\\\n", + " (60,90),(0,60),(-60,90),\\\n", + " (-w2/2-10,35),(-w2/2-w1/2+55,35),(-w2/2-w1/2+55,0),\n", + " align=Align.MIN)\n", + " #mirror(about=Plane.YZ)\n", + " raw_slim_cutout_mpu = extrude(amount=-30)\n", + " chamfer(length=12, length2=20, objects=slim_cutout_mpu.edges().group_by(Axis.Z)[-1][2])\n", + " chamfer(length2=12, length=20, objects=slim_cutout_mpu.edges().group_by(Axis.Z)[-1][2])\n", + " chamfer(length2=12, length=20, objects=slim_cutout_mpu.edges().group_by(Axis.Z)[-1][6])\n", + " chamfer(length2=12, length=10, objects=slim_cutout_mpu.edges().group_by(Axis.Z)[-1][2])\n", + "\n", + "with BuildPart() as slim_cutout2_mpu:\n", + " add(Location((0,0,47))*raw_slim_cutout_mpu)\n", + " chamfer(length=12, length2=20, objects=slim_cutout2_mpu.edges().group_by(Axis.Z)[0][2])\n", + " chamfer(length2=12, length=20, objects=slim_cutout2_mpu.edges().group_by(Axis.Z)[0][2])\n", + " chamfer(length2=12, length=20, objects=slim_cutout2_mpu.edges().group_by(Axis.Z)[0][6])\n", + " chamfer(length2=12, length=10, objects=slim_cutout2_mpu.edges().group_by(Axis.Z)[0][2])\n", + "\n", + "with BuildPart() as inner_cutout:\n", + " with BuildSketch(Plane.XY.offset(w1-10)) as handle_sk:\n", + " with Locations((0,h)):\n", + " Circle(r*2.4)\n", + " extrude(amount=30)\n", + " \n", + "with BuildPart() as screwholes:\n", + " with BuildSketch() as screwholes_sk:\n", + " with Locations(*locs):\n", + " Circle(1.5)\n", + " extrude(amount=12)\n", + " with BuildSketch() as screwholes_sk1:\n", + " with Locations(*locs):\n", + " Circle(3)\n", + " extrude(amount=2)\n", + " \n", + "r_sens = 6\n", + " \n", + "with BuildPart() as sensor_cutout:\n", + " with BuildSketch(Plane.XZ) as sensor_sk1:\n", + " d = 0\n", + " with Locations((w2/2+d,w1/2),(-w2/2-d,w1/2)):\n", + " Rectangle(sensor_width-4, sensor_width-4)\n", + " sensor_space = extrude(amount=-14)\n", + " faces = (sensor_space.edges().group_by(Axis.Y)[-1] | Axis.Z)\n", + " chamfer(objects=faces, length=6)\n", + " \n", + " with BuildSketch(Plane.XZ) as sensor_sk:\n", + " with Locations((w2/2,w1/2),(-w2/2,w1/2)):\n", + " RectangleRounded(sensor_width, sensor_width, r_sens)\n", + " extrude(amount=-10)\n", + " \n", + " \n", + "with BuildPart() as snapfit_cutout:\n", + " d = 2\n", + " l = 10\n", + " hc=3\n", + " with BuildSketch(Plane.XY.offset(w1/2-l/2)) as snap_sk:\n", + " with Locations((w2/2+w1/2-1.5*d,hc),(-w2/2-w1/2+1.5*d,hc)):\n", + " Rectangle(d, d, rotation=45)\n", + " extrude(amount=l)\n", + " \n", + "\n", + "with BuildPart() as cable_paths:\n", + " with BuildSketch(Plane.XZ) as sensor_sk:\n", + " with Locations((w2/2-w1/2+2,w1/2),(-w2/2+w1/2-2,w1/2)):\n", + " Rectangle(2, 4)\n", + " extrude(amount=-12)\n", + " \n", + "with BuildPart() as cable_paths_mpu:\n", + " with BuildSketch(Plane.XZ) as sensor_sk:\n", + " with Locations((w2/2-w1/2+2,w1/2)):\n", + " Rectangle(2, 4)\n", + " extrude(amount=-12)\n", + " with BuildSketch(Plane.XZ) as sensor_sk:\n", + " with Locations((-w2/2+w1/2-2,w1/2)):\n", + " Rectangle(2, 4)\n", + " extrude(amount=-22)\n", + " \n", + "with BuildPart() as tunnels: \n", + " with BuildLine() as tunnel_right:\n", + " Spline((w2/2-w1/2+1,8.5,w1/2),(0,h-r+3,w1/2), tangents=((-0.1,0.05,0),(0,1,0)))\n", + " with BuildSketch(Plane.YZ.offset(w2/2-w1/2+1)) as tunnel_hole:\n", + " with Locations((8.5, w1/2)):\n", + " Circle(3)\n", + " tunnel = sweep(path=tunnel_right, mode=Mode.ADD)\n", + " mirror(objects=tunnel, about=Plane.YZ)\n", + "\n", + "with BuildPart() as tunnels_mpu: \n", + " with BuildLine() as tunnel_right:\n", + " Spline((-w2/2-w1/2+54, h3-15+1.5, w1/2),(0, h-r+3, w1/2), tangents=((0.1,0.05,0),(0,1,0)))\n", + " with BuildSketch(Plane.YZ.offset(-w2/2-w1/2+54)) as tunnel_mpu_r_hole:\n", + " with Locations((h3-15+1.5, w1/2)):\n", + " Circle(3)\n", + " tunnel = sweep(path=tunnel_right, mode=Mode.ADD)\n", + " with BuildLine() as tunnel_left:\n", + " Spline((w2/2-w1/2,8.5,w1/2),(0,h-r+3,w1/2), tangents=((-0.1,0.05,0),(0,1,0)))\n", + " with BuildSketch(Plane.YZ.offset(w2/2-w1/2+1)) as tunnel_mpu_l_hole:\n", + " with Locations((8.5, w1/2)):\n", + " Circle(3)\n", + " sweep(path=tunnel_left, mode=Mode.ADD)\n", + " \n", + "mpu_width = 28\n", + "mpu_len = 53\n", + "mpu_h = 15\n", + "with BuildPart() as mpu_cutout:\n", + " lift = 20\n", + " with BuildSketch(Plane.XZ.offset(-h3-lift)) as mpu_sk:\n", + " with Locations((-w2/2-w1/2+mpu_len/2+2, w1/2)):\n", + " RectangleRounded(mpu_len,28, 4)\n", + " extrude(amount=lift+mpu_h)\n", + " \n", + "with BuildPart() as mpu_standoff:\n", + " standoff_len = 10\n", + " x = -(w2/2+w1/2-mpu_len-1)\n", + " y1 = w1/2-mpu_width/2+standoff_len/2\n", + " y2 = w1/2+mpu_width/2-standoff_len/2\n", + " with BuildSketch(Plane.XZ.offset(-h3+mpu_h)) as standoff_sk:\n", + " with Locations((x, y1),\\\n", + " (x, y2)):\n", + " Rectangle(2,standoff_len)\n", + " extrude(amount=-7)\n", + " \n", + "with BuildPart() as grip:\n", + " with BuildSketch() as base_sk:\n", + " Polygon((w2/2+w1/2,0),(w2/2-w1/2,0),\\\n", + " (w2/2-w1/2-20,h2),(0,h2),(0,h+2.3*r),\\\n", + " (w2/2-w1/2, h1+10),(w2/2+w1/2, h1),\\\n", + " align=Align.MIN)\n", + " mirror(about=Plane.YZ)\n", + " fillet(radius=r,objects=base_sk.vertices().group_by(Axis.Y)[-1])\n", + " fillet(radius=r*2,objects=base_sk.vertices().group_by(Axis.Y)[3])\n", + " fillet(radius=10,objects=base_sk.vertices().group_by(Axis.Y)[2])\n", + " fillet(radius=2*r,objects=base_sk.vertices().group_by(Axis.Y)[1])\n", + " extrude(amount=w1)\n", + " fillet(radius=r1, objects=grip.edges().group_by(Axis.Z)[0].sort_by(Axis.Y)[-1])\n", + " with BuildSketch(Plane.XY.offset(10)) as pole_cutout:\n", + " with Locations((0,h)):\n", + " Circle(r+0.5)\n", + " extrude(amount=100, mode=Mode.SUBTRACT)\n", + " fillet(radius=r1, objects=grip.edges().group_by(Axis.Y)[-6])\n", + " fillet(radius=r1, objects=grip.edges().group_by(Axis.X)[0].sort_by(Axis.Z)[-1])\n", + " add(screwholes,mode=Mode.SUBTRACT)\n", + " add(sensor_cutout,mode=Mode.SUBTRACT)\n", + " add(cable_paths, mode=Mode.SUBTRACT)\n", + " add(slim_cutout,mode=Mode.SUBTRACT)\n", + " add(slim_cutout2,mode=Mode.SUBTRACT)\n", + " add(tunnels,mode=Mode.SUBTRACT)\n", + " add(inner_cutout,mode=Mode.SUBTRACT)\n", + " add(snapfit_cutout, mode=Mode.SUBTRACT)\n", + " \n", + "with BuildPart() as grip_mpu:\n", + " with BuildSketch() as base_sk:\n", + " with Locations((-w2/2-w1/2,0)):\n", + " Polygon((w2/2+w1/2,0),(w2/2-w1/2,0),\\\n", + " (w2/2-w1/2-20,h2),(-w2/2+w1/2+20,h2),(-w2/2+w1/2,0),(-w2/2-w1/2,0),\\\n", + " (-w2/2-w1/2,h3),(-w2/2-w1/2+55,h3+1),\\\n", + " #(-r,h),\\\n", + " (-6.5,h+2.77*r),\\\n", + " #(r,h),\\\n", + " (w2/2-w1/2, h1+10),(w2/2+w1/2, h1),\\\n", + " \n", + " align=Align.MIN)\n", + " #mirror(about=Plane.YZ)\n", + " fillet(radius=r,objects=base_sk.vertices().group_by(Axis.Y)[-1])\n", + " fillet(radius=r,objects=base_sk.vertices().group_by(Axis.Y)[5])\n", + " fillet(radius=r*2,objects=base_sk.vertices().group_by(Axis.Y)[3])\n", + " fillet(radius=10,objects=base_sk.vertices().group_by(Axis.Y)[2])\n", + " fillet(radius=2*r,objects=base_sk.vertices().group_by(Axis.Y)[1])\n", + " extrude(amount=w1)\n", + " #\n", + " with BuildSketch(Plane.XY.offset(10)) as pole_cutout:\n", + " with Locations((0,h)):\n", + " Circle(r+.5)\n", + " extrude(amount=100, mode=Mode.SUBTRACT)\n", + " fillet(radius=r1, objects=grip_mpu.edges().sort_by(Axis.Y)[-7])\n", + " fillet(radius=r1, objects=grip_mpu.edges().sort_by(Axis.Y)[-6])\n", + " fillet(radius=r1, objects=grip_mpu.edges().group_by(Axis.Z)[0].sort_by(Axis.Y)[-1])\n", + " fillet(radius=r1, objects=grip_mpu.edges().group_by(Axis.Z)[-1].sort_by(Axis.Y)[-1])\n", + " add(screwholes, mode=Mode.SUBTRACT)\n", + " add(sensor_cutout, mode=Mode.SUBTRACT)\n", + " add(cable_paths_mpu, mode=Mode.SUBTRACT)\n", + " add(slim_cutout_mpu, mode=Mode.SUBTRACT)\n", + " add(slim_cutout2_mpu, mode=Mode.SUBTRACT)\n", + " add(tunnels_mpu, mode=Mode.SUBTRACT)\n", + " #usb slot\n", + " with BuildSketch(Plane.YZ.offset(-w2/2-w1/2)) as usb_sk:\n", + " with Locations((h3-6, w1/2)):\n", + " SlotCenterToCenter(6,4, rotation=90)\n", + " extrude(amount=10, mode=Mode.SUBTRACT)\n", + " add(mpu_cutout,mode=Mode.SUBTRACT)\n", + " add(inner_cutout,mode=Mode.SUBTRACT)\n", + " add(mpu_standoff)\n", + " add(snapfit_cutout, mode=Mode.SUBTRACT)\n", + "\n", + " \n", + "with BuildPart() as nut_m3_slot:\n", + " with BuildSketch() as base_sk:\n", + " sw = 3.3\n", + " RegularPolygon(sw,6)\n", + " with Locations((50,0)):\n", + " RegularPolygon(sw,6)\n", + " make_hull()\n", + " extrude(amount=3)\n", + "\n", + "with BuildPart() as handle:\n", + " with BuildSketch(Plane.XY.offset(10)) as handle_sk:\n", + " with Locations((0,h)):\n", + " Circle(r)\n", + " extrude(amount=handle_len)\n", + " with BuildSketch(Plane.XY.offset(10)) as screwholes_sk:\n", + " with Locations(*locs):\n", + " Circle(1.5)\n", + " extrude(amount=30, mode=Mode.SUBTRACT)\n", + " with BuildSketch(Plane.XY.offset(10+handle_len)) as screwholes_sk:\n", + " with Locations(*locs):\n", + " Circle(1.5)\n", + " extrude(amount=-30, mode=Mode.SUBTRACT)\n", + " # add tunnel\n", + " with BuildLine(Plane.YZ) as tnl:\n", + " rr=10\n", + " j = JernArc((h-r-0.5,w1/2), (1,0), arc_size=90, radius=rr)\n", + " l = Line(j@1, j@1+(0,handle_len-w1/2-2*rr))\n", + " JernArc(l@1, (0,1), arc_size=90, radius=rr)\n", + " with BuildSketch(Plane.XZ.offset(-h+r+0.5)) as tunnel_hole:\n", + " with Locations((0, w1/2)):\n", + " Circle(3)\n", + " sweep(path=tnl, mode=Mode.SUBTRACT)\n", + " # add nut slots \n", + " with Locations((0,10+h,20), (0,10+h, handle_len),\\\n", + " (10,h-10,20), (10,h-10, handle_len)):\n", + " add(nut_m3_slot, mode=Mode.SUBTRACT)\n", + " with Locations((0,10+h,20), (0,10+h, handle_len),\\\n", + " (10,h-10,20), (10,h-10, handle_len)):\n", + " add(nut_m3_slot, mode=Mode.SUBTRACT)\n", + " with Locations((-10,h-10,20), (-10,h-10, handle_len)):\n", + " add(Rotation(0,0,180)*nut_m3_slot.part, mode=Mode.SUBTRACT)\n", + " \n", + " \n", + "grip = Rotation(0,180,0)*Location((0,0,-handle_len-20))*grip.part\n", + "#show(grip_mpu, tunnels_mpu, transparent=True,reset_camera=False) \n", + "#show(grip,transparent=True,reset_camera=False)\n", + "#show(grip_mpu,mpu_standoff, transparent=True, reset_camera=False)\n", + "#show(grip, inner_cutout, transparent=True,reset_camera=False)\n", + "#show(tunnel_hole,tnl,grip_mpu,Rotation(0,180,0)*Location((0,0,-handle_len-20))*grip.part, handle,transparent=True,reset_camera=False)\n", + "show( grip, grip_mpu, handle,transparent=True, reset_camera=False)\n", + "#show(slim_cutout_mpu, reset_camera=False)\n", + "#show(grip, sensor_cutout, tunnel_right,transparent=True,reset_camera=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ce464618-0d41-45ab-b17b-9ec93350721e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100% ⋮————————————————————————————————————————————————————————————⋮ (2/2) 0.06s\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with BuildPart() as sensorfoot_housing:\n", + " with BuildSketch(Plane.XZ) as housing_sk:\n", + " with Locations((w2/2, w1/2)):\n", + " RectangleRounded(sensor_width-0.0, sensor_width-0.0, r_sens+0.5)\n", + "\n", + " extrude(amount=-6)\n", + " add(Location((-0.25,0,0))*snapfit_cutout.part)\n", + " opening=sensorfoot_housing.faces().sort_by(Axis.Y)[-1]\n", + " \n", + " offset(amount=-1, openings=opening, side=Side.RIGHT)\n", + " \n", + " with Locations((w2/2,-2,w1/2)):\n", + " Cone(bottom_radius=15, top_radius=12, height=6, rotation=(90,0,0), mode=Mode.SUBTRACT)\n", + " with Locations((w2/2-sensor_width/2, 6, w1/2)):\n", + " Box(7,10,7, mode=Mode.SUBTRACT)\n", + " \n", + "\n", + "with BuildPart() as sensorfoot_bbox:\n", + " with BuildSketch(Plane.XZ) as housing_sk:\n", + " with Locations((w2/2, w1/2)):\n", + " RectangleRounded(sensor_width-4, sensor_width-4, r_sens+0.5)\n", + " extrude(amount=20, both=True)\n", + " \n", + "x_sens = 14.25\n", + "with BuildPart() as sensorfoot:\n", + " with Locations((w2/2,-1.5,w1/2)):\n", + " Cone(bottom_radius=15, top_radius=12, height=6, rotation=(90,0,0), mode=Mode.ADD)\n", + " with Locations((w2/2,2.5,w1/2)):\n", + " Cone(bottom_radius=10, top_radius=15, height=2, rotation=(90,0,0), mode=Mode.ADD)\n", + " with Locations((w2/2-sensor_width/2+x_sens, 3.5, w1/2)):\n", + " Box(12,5,30, mode=Mode.SUBTRACT)\n", + " with Locations((w2/2-sensor_width/2+x_sens, 0, w1/2)):\n", + " Cone(bottom_radius=3, top_radius=2, height=2, rotation=(90,0,0), mode=Mode.SUBTRACT)\n", + " \n", + " #with Locations((w2/2-sensor_width/2+x_sens,2,w1/2)):\n", + " # Box(11,2,30)\n", + " with Locations((w2/2-sensor_width/2+5,2,w1/2), (w2/2+sensor_width/2-5,2,w1/2)):\n", + " Box(3,2,30)\n", + " add(sensorfoot_bbox, mode=Mode.INTERSECT)\n", + " \n", + "\n", + "\n", + "show( sensorfoot, sensorfoot_housing,transparent=True,reset_camera=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "03be83e0-4493-44b5-b38a-85c8007838c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100% ⋮————————————————————————————————————————————————————————————⋮ (2/2) 0.07s\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 85, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with BuildPart() as mpu_cover:\n", + " lift = 20\n", + " th = 1.\n", + " with BuildSketch(Plane.XZ.offset(-h3)) as mpu_sk:\n", + " with Locations((-w2/2-w1/2+mpu_len/2+2, w1/2)):\n", + " RectangleRounded(mpu_len,28, 4)\n", + " extrude(amount=1)\n", + " with BuildSketch(mpu_cover.faces().sort_by(Axis.Y)[0]) as stand_sk:\n", + " with Locations((0,-14+th/2), (0,14-th/2)):\n", + " Rectangle(20,th)\n", + " extrude(amount=14)\n", + " buttonlocs = ((-mpu_len/2+5,-t), (-mpu_len/2+5,t))\n", + " with BuildSketch(mpu_cover.faces().sort_by(Axis.Y)[-1]) as holes_sk:\n", + " t = 9\n", + " rr = 2\n", + " dd = 6\n", + " ddd = 0.4\n", + " with Locations(*buttonlocs):\n", + " with Locations((1,0)):\n", + " RectangleRounded(dd+4,dd,rr)\n", + " RectangleRounded(dd+4-ddd,dd-ddd,rr-ddd/2, mode=Mode.SUBTRACT)\n", + " with Locations((5,0)):\n", + " Rectangle(6,2, mode=Mode.SUBTRACT)\n", + " extrude(amount=-2, mode=Mode.SUBTRACT)\n", + " with BuildSketch(mpu_cover.faces().sort_by(Axis.Y)[-1]) as buttons_sk:\n", + " with Locations(*buttonlocs):\n", + " RectangleRounded(5,5,1.5)\n", + " extrude(amount=-5)\n", + " with BuildSketch(mpu_cover.faces().sort_by(Axis.Y)[-1]) as buttons_sk:\n", + " with Locations((2.5,0)):\n", + " Rectangle(32.5,19)\n", + " extrude(amount=-3, mode=Mode.SUBTRACT)\n", + " \n", + " \n", + "show(grip_mpu,mpu_cover,transparent=True,reset_camera=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "8d3ecdf0-94b8-4e65-95f9-785784f7cf57", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 86, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert False\n", + "foldername = \"SyncGripSTEP/\"\n", + "export_step(sensorfoot.part,foldername+\"sensorfoot.step\")\n", + "export_step(sensorfoot_housing.part,foldername+\"sensorfoot-housing.step\")\n", + "export_step(grip,foldername+\"grip.step\")\n", + "export_step(grip_mpu.part,foldername+\"grip-mpu.step\")\n", + "export_step(handle.part,foldername+\"handle.step\")\n", + "export_step(mpu_cover.part, foldername+\"mpu_cover.step\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ec1e400-2779-4f43-94c5-07a8d18c0023", + "metadata": {}, + "outputs": [], + "source": [ + "with BuildPart() as nut_m3:\n", + " with BuildSketch() as base_sk:\n", + " RegularPolygon(3.005,6)\n", + " extrude(amount=2.4)\n", + " chamfer(nut_m3.edges(), length=0.2)\n", + " Cylinder(1.5,6, mode=Mode.SUBTRACT)\n", + " \n", + "\n", + " \n", + " \n", + "show(nut_m3, nut_m3_slot, transparent=True, reset_camera=False)" + ] + }, + { + "cell_type": "markdown", + "id": "1693d74f-3a6d-4206-91cd-51f126a8285d", + "metadata": {}, + "source": [ + "# export SVG" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "09205ef3-d30d-4da8-a2f0-0f82e397b7cd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "WARNING, exporting non-planar shape to 2D format.\n", + " This is probably not what you want.\n", + " 1820 points found outside the XY plane.\n" + ] + } + ], + "source": [ + "foldername = \"SyncGripSVG/\"\n", + "\n", + "fullgrip = Compound(children=[grip, grip_mpu.part, handle.part])\n", + "grips = (Location((-200,0,0))*mirror(fullgrip, about=Plane.YZ)) + (Location((200,0,0))*Rotation(0,0,0)*fullgrip)\n", + "\n", + "\n", + "exporter = ExportSVG(unit= Unit.MM, \\\n", + " scale= 1, \\\n", + " margin= 0, \\\n", + " fit_to_stroke= True, \\\n", + " precision = 6, \\\n", + " fill_color= None, \\\n", + " line_weight = 0.09)\n", + "exporter.add_layer(\"Layer 1\", fill_color=(0, 0, 0), line_color=(255, 255, 255))\n", + "exporter.add_shape(grip, layer=\"Layer 1\")\n", + "exporter.write(foldername+\"grip.svg\")" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "id": "38da9b1c-8880-4ba2-abef-f9ae0bb3ddb5", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def drawSVG(part,view_port_origin, filename):\n", + " visible, hidden = part.project_to_viewport(view_port_origin, viewport_up=(0,1,0))\n", + " max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\n", + " exporter = ExportSVG(scale=100 / max_dimension,\\\n", + " fill_color=(12,12,12),\n", + " line_weight=1)\n", + " exporter.add_layer(\"Visible\",\\\n", + " line_weight=0.3,\\\n", + " fill_color=None,\\\n", + " line_color=(75,75,75))\n", + " exporter.add_layer(\"Hidden\", \\\n", + " line_color=(144,44,77), \\\n", + " line_weight=0.2,\\\n", + " line_type=LineType.ISO_DOT)\n", + " exporter.add_shape(visible, layer=\"Visible\")\n", + " #exporter.add_shape(hidden, layer=\"Hidden\")\n", + " exporter.write(foldername+filename+\".svg\")" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "068bc1c1-0d80-43cc-8773-e0aaba5a3772", + "metadata": {}, + "outputs": [], + "source": [ + "view_port_origin=(-200, 85, 250)\n", + "fullgrip = Compound(children=[grip, grip_mpu.part, handle.part])\n", + "drawSVG(fullgrip,view_port_origin, \"fullgrip\")" + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "id": "fa7fbea4-6725-4b48-aca0-748084e43833", + "metadata": {}, + "outputs": [], + "source": [ + "view_port_origin=(w2/2, 90, -100)\n", + "#sensorfoot_explode = Compound(children=[Location((0,30,0))*sensorfoot.part, sensorfoot_housing.part])\n", + "sensorfoot_normal = Location((0,0,70))*Compound(children=[sensorfoot.part, sensorfoot_housing.part])\n", + "#drawout = Compound(children=[sensorfoot_explode, sensorfoot_normal])\n", + "drawSVG(sensorfoot_normal, view_port_origin, \"sensorfoot\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5e75454-c676-4ba7-9c4b-156cfe07c475", + "metadata": {}, + "outputs": [], + "source": [ + "rf = 30.5/2 # radius of a flask\n", + "nrows = 2\n", + "ncols = 3\n", + "\n", + "t = 2 # wall thickness\n", + "ti = 0.4 # wall thickness inbetween flasks\n", + "df = rf * 2 + ti\n", + "flask_locs = list((i*df,j*df) for i in range(ncols) for j in range(nrows))\n", + "\n", + "w = ncols*df-ti+2*t\n", + "d = nrows*df-ti+2*t\n", + "h = 25\n", + "\n", + "dd = rf+0.2\n", + "cuts_locs = list(((i+0.5)*df, j) for i in range(ncols-1) for j in [-rf-t-dd, d-rf-t+dd])\n", + "cuts1_locs = list((j,(i+0.5)*df) for i in range(nrows-1) for j in [-rf-t-dd, w-rf-t+dd])\n", + "\n", + "\n", + "with BuildPart() as flasks_case:\n", + " with BuildSketch() as case_sk:\n", + " with Locations((-rf-t,-rf-t)):\n", + " RectangleRounded(w, d, rf+t, align=Align.MIN)\n", + " with Locations(*cuts_locs):\n", + " Circle(rf+t, mode=Mode.SUBTRACT)\n", + " with Locations(*cuts1_locs):\n", + " Circle(rf+t, mode=Mode.SUBTRACT)\n", + " \n", + " \n", + " extrude(amount=h) \n", + " fillet(flasks_case.edges()|Axis.Z,radius=rf)\n", + " with BuildSketch() as flasks_sk:\n", + " with Locations(*flask_locs):\n", + " Circle(rf) \n", + " extrude(amount=h-t, mode=Mode.SUBTRACT)\n", + " with BuildSketch() as inner_sk:\n", + " with Locations((-rf+t, -rf+t )):\n", + " RectangleRounded(w-4*t, d-4*t, rf, align=Align.MIN)\n", + " extrude(amount=h-t-8, mode=Mode.SUBTRACT)\n", + " fillet(flasks_case.edges().sort_by(Axis.Z)[0], radius=1)\n", + " \n", + "show(flasks_case, reset_camera=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "316a54b1-f07f-4b7c-88d2-288ee0f45306", + "metadata": {}, + "outputs": [], + "source": [ + "flasks_case.part.export_step(\"flasks.step\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "864538fc-57f4-4c15-bf15-7e76b5b0b5d4", + "metadata": {}, + "outputs": [], + "source": [ + "from math import sin, cos, asin,pi\n", + "\n", + "def arcd(y):\n", + " return 2*asin(y*0.5)\n", + "\n", + "def secant_phi(length, radius):\n", + " # calculates tht angle of a secant on a circle\n", + " # with given length and the radius of a circle\n", + " return arcd(length/radius)\n", + "\n", + "dt = 2 # how thick the holder should be\n", + "ds = 6 # how big the step at the end of the gripper should be\n", + "r1 = 15.25-dt # the radius of the hole of the weight plate\n", + "h1 = 22 # the height of the gripper\n", + "\n", + "\n", + "dphi = 15./180.*pi # degree of flex\n", + "# how big do i need to make the radius so when it is flexed, \n", + "# the original radius and angle is reached?\n", + "# when flexed, both grippers should be parallel\n", + "phi_flexed_cutout = secant_phi(r1*2 , r)\n", + "phi_flexed = 2*pi - phi_flexed_cutout\n", + "phi_unflexed = phi_flexed - dphi\n", + "# now we need the circumfence of that arc and the new radius\n", + "circ_flexed = 2*(phi_flexed)*r\n", + "r_unflexed = circ_flexed/(2*(phi_unflexed))\n", + "\n", + "r2 = r_unflexed / 2.0\n", + "phi0 = ((2*pi- phi_flexed)/2) - pi/2\n", + "\n", + "p1 = (r_unflexed * cos( phi0), r_unflexed * sin( phi0))\n", + "p2 = (p1[0] +h1*sin(dphi), p1[1]-h1*cos(dphi))\n", + "p3 =(p2[0] + ds *cos(dphi), p2[1]+ ds *sin(dphi))\n", + "\n", + "\n", + "with BuildPart() as calibration_plate_gripper:\n", + " with BuildSketch() as profile_sketch:\n", + " with BuildLine() as line_builder:\n", + " #a = CenterArc(center=(0,0),\\\n", + " # radius=r_unflexed, \\\n", + " # start_angle=phi0, \\\n", + " # arc_size=circ_flexed/2)\n", + " RadiusArc((0,r_unflexed), p1, r_unflexed)\n", + " line = Polyline(\\\n", + " p1,\\\n", + " p2,\\\n", + " p3,\\\n", + " close=False)\n", + " \n", + " \n", + " ##line1 = fillet(radius=6,objects=line.vertices())\n", + " w = offset(amount=dt, side=Side.LEFT) \n", + " \n", + " \n", + " make_face()\n", + " extrude(amount=7)\n", + " mirror(about=Plane.YZ)\n", + " \n", + " \n", + "show(calibration_plate_gripper, Cylinder(25,100),reset_camera=False, transparent=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9cf1c8e8-adff-4fb0-8453-73e5dbd54c8f", + "metadata": {}, + "outputs": [], + "source": [ + "foldername = \"SyncGripSTEP/\"\n", + "\n", + "calibration_plate_gripper.part.export_step(foldername+\"calibration_plate_gripper.step\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32b7d490-319f-4aa7-8fa4-056bad961d6f", + "metadata": {}, + "outputs": [], + "source": [ + "phi_unflexed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "669af16d-c398-4e9c-a048-5da88fc54efc", + "metadata": {}, + "outputs": [], + "source": [ + "phi_flexed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb1e25ab-df4d-41a3-b5da-f2952efb8d32", + "metadata": {}, + "outputs": [], + "source": [ + "phi0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5fd2ce3-b39f-4668-89d5-d1810172cdca", + "metadata": {}, + "outputs": [], + "source": [ + "circ_flexed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f02d3a1-4bf3-4dce-ab79-e8cc97ccdf06", + "metadata": {}, + "outputs": [], + "source": [ + "offset?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee4e8291-b51d-403a-a589-e48a0710e77c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Untitled.ipynb b/Untitled.ipynb new file mode 100644 index 0000000..b28d41b --- /dev/null +++ b/Untitled.ipynb @@ -0,0 +1,308 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "476d2190-282f-4bb3-a9c0-d9e352235d18", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting auto display for cadquery Workplane and Shape\n" + ] + } + ], + "source": [ + "import timeit\n", + "tic = timeit.default_timer()\n", + "\n", + "import cadquery as cq\n", + "from build123d import *\n", + "\n", + "from jupyter_cadquery import (\n", + " versions,\n", + " show, PartGroup, Part, \n", + " get_viewer, close_viewer, get_viewers, close_viewers, open_viewer, set_defaults, get_defaults, open_viewer,\n", + " get_pick,\n", + ")\n", + "from jupyter_cadquery.ocp_utils import webcol_to_cq\n", + "\n", + "from jupyter_cadquery.replay import replay, enable_replay, disable_replay\n", + "close_viewers()\n", + "set_defaults(\n", + " cad_width=640, \n", + " height=480, \n", + " viewer=\"syncgrips_v2\"\n", + ")\n", + "cv = open_viewer(\"zementmischer\",\n", + " anchor=\"right\", \n", + " glass=True, \n", + " theme=\"dark\",\n", + " tools=True) # sets default viewer\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "27291ccc-aebc-4828-b447-19603567caa8", + "metadata": {}, + "outputs": [], + "source": [ + "with BuildPart() as bearing:\n", + " with BuildSketch() as sk:\n", + " Circle(11)\n", + " Circle(4, mode=Mode.SUBTRACT)\n", + " extrude(amount=7)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "016a58c2-a252-4d58-9617-4626b02e56c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100% ⋮————————————————————————————————————————————————————————————⋮ (2/2) 0.32s\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with BuildPart() as holder:\n", + " with BuildSketch(Plane.XY.offset(0)) as slim_sk:\n", + " Polygon((-5,0),(5,0),(0,5), \\\n", + " align=Align.CENTER)\n", + " with BuildSketch(Plane.XY.offset(120)) as slim_sk:\n", + " Polygon((-10,0),(10,0),(0,10), \\\n", + " align=Align.CENTER)\n", + " #mirror(about=Plane.YZ)\n", + " #extrude(amount=-30)\n", + " #sweep()\n", + " loft()\n", + " \n", + "show(holder,bearing, reset_camera=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "46db8226-8b74-4c63-bc48-83cc40ae79da", + "metadata": {}, + "outputs": [], + "source": [ + "d = 10.2\n", + "d1 = 11.3\n", + "l = 38.7\n", + "l1 = 39.7\n", + "l2 = 63\n", + "b = 0.5\n", + "h = 1.8\n", + "h1 = 0.8\n", + "with BuildPart() as camcap:\n", + " with BuildSketch() as sk:\n", + " SlotCenterToCenter(l2-d1+2*b,d1+2*b)\n", + " extrude(amount=h+b)\n", + " f = camcap.faces().sort_by(Axis.Z)[0]\n", + " with BuildSketch(f) as sk1:\n", + " with Locations((-(l2-l1)/2-b,0)):\n", + " SlotCenterToCenter(l-d+b,d)\n", + " extrude(amount=-h, mode=Mode.SUBTRACT)\n", + " with BuildSketch(f) as sk2:\n", + " with Locations((-(l2-l1)/2-b,0)):\n", + " SlotCenterToCenter(l1-d1+b,d1)\n", + " extrude(amount=-h1, mode=Mode.SUBTRACT)\n", + " with BuildSketch(f) as sk3:\n", + " with Locations((-(l2)/2+49.7,0)):\n", + " Circle(9.5/2)\n", + " extrude(amount=-h1, mode=Mode.SUBTRACT)\n", + " fillet(camcap.edges().sort_by(Axis.Z)[-1], radius=0.5)\n", + "camcap" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "id": "e16ea143-59f6-4366-9a51-9473eefdce3a", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_33/1870784379.py:1: DeprecationWarning: Use the `export_step` function instead\n", + " camcap.part.export_step(\"camcap_note10.step\")\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "camcap.part.export_step(\"camcap_note10.step\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a33280cb-fc8c-4cb7-bf4b-6a707534f02b", + "metadata": {}, + "outputs": [], + "source": [ + "hs = 9.5\n", + "with BuildPart() as frontcamcap:\n", + " with BuildSketch() as sk:\n", + " Circle(8)\n", + " with Locations((0,8)):\n", + " Rectangle(20, 3)\n", + " make_hull()\n", + " extrude(amount=b)\n", + " chamfer(frontcamcap.edges().sort_by(Axis.Z)[0], length=b*0.9)\n", + " with Locations((0,0,hs)):\n", + " add(frontcamcap.part.mirror(Plane.XY))\n", + " with Locations((0,8+1.5+b/2,hs/2)):\n", + " Box(20,b, hs)\n", + " \n", + " \n", + "frontcamcap" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "403932d5-0058-4480-89de-0ec633436000", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "export_step(frontcamcap.part, \"frontcamcap.step\")" + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "04750084-62c3-4bfa-8c5a-d172b4e70451", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 64, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with BuildPart() as akaso_adapter:\n", + " Box(8.5, 70, 20)\n", + " #fillet(akaso_adapter.edges()|Axis.Z, radius=2)\n", + " with Locations((4,0,16)):\n", + " b = Box(16, 14.5, 20)\n", + " with Locations((0,0,3)):\n", + " Cylinder(5.5/2,20, rotation=(90,0,0), mode=Mode.SUBTRACT)\n", + " edgs = b.faces().sort_by(Axis.Z)[-1].edges() | Axis.Y\n", + " fillet(edgs, radius=7.7)\n", + " with Locations((4,0,14)):\n", + " with Locations((0,-2.7*1,2),(0,2.7*1,2)):\n", + " Box(18, 2.7,20, mode=Mode.SUBTRACT)\n", + " fs = akaso_adapter.faces().group_by(Axis.X)[-1].edges().group_by(Axis.Z)[0]\n", + " fillet(fs, radius=7.7)\n", + "show(akaso_adapter, reset_camera=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "c30c3133-590e-4f13-be94-eb129e93775f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "export_step(akaso_adapter.part, \"akaso.step\")" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "ca641769-775b-4ce8-99a1-211dcc221ab5", + "metadata": {}, + "outputs": [], + "source": [ + "akaso_adapter.faces().group_by(Axis.X)[-1].edges().group_by(Axis.Z)[0]# | Axis.Y" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "729fa291-80fa-4aa7-ac9b-6f15a887409a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/dempf.ipynb b/dempf.ipynb new file mode 100644 index 0000000..071b94f --- /dev/null +++ b/dempf.ipynb @@ -0,0 +1,333 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "f4f3de42-6096-4701-98ff-6b5034dfcc97", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting auto display for cadquery Workplane and Shape\n" + ] + } + ], + "source": [ + "import timeit\n", + "tic = timeit.default_timer()\n", + "\n", + "import cadquery as cq\n", + "from math import sin, cos, pi\n", + "\n", + "\n", + "\n", + "from jupyter_cadquery import (\n", + " versions,\n", + " show, PartGroup, Part, \n", + " get_viewer, close_viewer, get_viewers, close_viewers, open_viewer, set_defaults, get_defaults, open_viewer,\n", + " get_pick,\n", + ")\n", + "\n", + "from jupyter_cadquery.replay import replay, enable_replay, disable_replay\n", + "''\n", + "\n", + "from build123d import *" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "be6cd832-8790-46db-aea3-e8b44b8609ca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'viewer': 'dempf',\n", + " 'anchor': 'right',\n", + " 'cad_width': 600,\n", + " 'tree_width': 250,\n", + " 'height': 600,\n", + " 'theme': 'light',\n", + " 'pinning': False,\n", + " 'angular_tolerance': 0.2,\n", + " 'deviation': 0.1,\n", + " 'edge_accuracy': None,\n", + " 'default_color': (232, 176, 36),\n", + " 'default_edge_color': '#707070',\n", + " 'optimal_bb': False,\n", + " 'render_normals': False,\n", + " 'render_edges': True,\n", + " 'render_mates': False,\n", + " 'parallel': False,\n", + " 'mate_scale': 1,\n", + " 'control': 'trackball',\n", + " 'up': 'Z',\n", + " 'axes': False,\n", + " 'axes0': False,\n", + " 'grid': [False, False, False],\n", + " 'ticks': 10,\n", + " 'ortho': True,\n", + " 'transparent': False,\n", + " 'black_edges': False,\n", + " 'ambient_intensity': 0.75,\n", + " 'direct_intensity': 0.15,\n", + " 'reset_camera': True,\n", + " 'show_parent': True,\n", + " 'show_bbox': False,\n", + " 'position': None,\n", + " 'quaternion': None,\n", + " 'target': None,\n", + " 'zoom': None,\n", + " 'zoom_speed': 1.0,\n", + " 'pan_speed': 1.0,\n", + " 'rotate_speed': 1.0,\n", + " 'collapse': 1,\n", + " 'tools': True,\n", + " 'glass': True,\n", + " 'timeit': False,\n", + " 'js_debug': False}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "close_viewers()\n", + "\n", + "set_defaults(viewer=\"dempf\",\n", + " cad_width=600, \n", + " #height=480,\n", + " )\n", + "\n", + "\n", + "cv = open_viewer(\"dempf\",\n", + " anchor=\"right\", \n", + " glass=True, \n", + " tools=False) # sets default viewer\n", + "\n", + "\n", + "cv.grid = [not g for g in cv.widget.grid]\n", + "\n", + "cv.axes = not cv.axes\n", + "cv.axes0 = not cv.axes0\n", + "cv.transparent = not cv.transparent\n", + "cv.black_edges = not cv.black_edges\n", + "#enable_replay(False, False)\n", + "#show_object = replay\n", + "\n", + "get_defaults()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "fad67912-407f-4263-a496-287ea7160753", + "metadata": {}, + "outputs": [], + "source": [ + "def seal_metal_pipe():\n", + " pipe = (cq.Workplane(\"XY\")\n", + " .sketch()\n", + " .circle(20, mode=\"c\", tag=\"circ\") \n", + " .select(\"circ\")\n", + " .wires()\n", + " .distribute(8)\n", + " .rect(2,2)\n", + " .finalize()\n", + " .extrude(2)\n", + " )\n", + " return pipe\n", + "\n", + "#.circle(15)\n", + "## .circle(14)\n", + "# .extrude(60)\n", + "# .faces(\">Z\")\n", + "\n", + "pipe = seal_metal_pipe()\n", + "pipe" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d6ed6a98-1cc6-489a-8b65-e5d99837d8da", + "metadata": {}, + "outputs": [], + "source": [ + "cv1 = show(pipe)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "495f885e-5b92-4453-8c96-5c532fd7d46a", + "metadata": {}, + "outputs": [], + "source": [ + "pipe" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "14a5ae42-0246-4999-9818-0df5e5c0bfc7", + "metadata": {}, + "outputs": [], + "source": [ + "from cadquery.selectors import AreaNthSelector\n", + "\n", + "result = (\n", + " cq.Workplane(\"XY\")\n", + " .rect(20, 20)\n", + " .extrude(10)\n", + " .edges(\"|Z or Z\")\n", + " .shell(2)\n", + " .faces(\">Z\")\n", + " .wires(AreaNthSelector(-1))\n", + " .toPending()\n", + " .workplane()\n", + " .offset2D(-1)\n", + " .extrude(1)\n", + " .faces(\">Z[-2]\")\n", + " .wires(AreaNthSelector(0))\n", + " .toPending()\n", + " .workplane()\n", + " .cutBlind(2)\n", + ")\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "10ef4488-c40a-4dc2-a723-2061632b8529", + "metadata": {}, + "outputs": [], + "source": [ + "with BuildPart() as p:\n", + " with BuildSketch() as pipe:\n", + " Circle(20)\n", + " Circle(19, mode=Mode.SUBTRACT)\n", + " extrude(amount=20)\n", + " with BuildSketch() as s:\n", + " with PolarLocations(20,8):\n", + " Rectangle(5,2)\n", + " extrude(amount=2, mode=Mode.SUBTRACT)\n", + " \n", + "p" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "027849ae-2b65-4473-8c5e-744691b85c2e", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "pipe = (cq.Workplane(\"XY\")\n", + " .sketch()\n", + " .circle(20)\n", + " .circle(19, mode=\"s\")\n", + " .finalize()\n", + " .extrude(40, both=False)\n", + " )\n", + " \n", + "holes = (pipe.faces(\">Z\")\n", + " .sketch()\n", + " .circle(20, mode=\"c\", tag=\"circ\") \n", + " .select(\"circ\")\n", + " .wires()\n", + " .distribute(8)\n", + " .rect(3,2)\n", + " .finalize()\n", + " .extrude(-2, combine=\"s\")\n", + " .faces(\" 2\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpolyline\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;241m.\u001b[39msketch()\n\u001b[1;32m 4\u001b[0m \n\u001b[1;32m 5\u001b[0m \u001b[38;5;241m.\u001b[39mrect(\u001b[38;5;241m2\u001b[39m,\u001b[38;5;241m100\u001b[39m)\n\u001b[1;32m 6\u001b[0m \u001b[38;5;241m.\u001b[39mrect(\u001b[38;5;241m1.5\u001b[39m,\u001b[38;5;241m4\u001b[39m)\n\u001b[1;32m 7\u001b[0m \n\u001b[1;32m 8\u001b[0m )\n\u001b[1;32m 9\u001b[0m yoghurt_extender \n", + "\u001b[0;31mTypeError\u001b[0m: Workplane.polyline() missing 1 required positional argument: 'listOfXYTuple'" + ] + } + ], + "source": [ + "yoghurt_extender =(cq.Workplane(\"XZ\")\n", + " .polyline()\n", + " .sketch()\n", + " \n", + " .rect(2,100)\n", + " .rect(1.5,4)\n", + " \n", + " )\n", + "yoghurt_extender " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51ec37d0-ea15-433a-9aad-16e05837e93b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c4ea0f7f-9ac1-4ff0-9740-787a17cba1bc", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/lookup_comparison.ipynb b/lookup_comparison.ipynb new file mode 100644 index 0000000..6d07d08 --- /dev/null +++ b/lookup_comparison.ipynb @@ -0,0 +1,116 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d352b9f0-e16e-48cb-b28f-ce3014a2fdc7", + "metadata": {}, + "source": [ + "## When is a hashmap cheaper than a list?" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "730d1fb1-5c50-48b1-ad9e-291bc5ae490d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total runtime: 0.72s\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import timeit\n", + "import numpy as np\n", + "from random import randint\n", + "import matplotlib.pyplot as plt\n", + "\n", + "\n", + "tic = timeit.default_timer()\n", + "\n", + "n_max_elems = 500\n", + "\n", + "\n", + "meas_list = np.zeros(n_max_elems)\n", + "meas_hm = np.zeros(n_max_elems)\n", + "\n", + "for i in range(n_max_elems):\n", + " l = list(np.random.permutation([float(j) for j in range(i+1)]))\n", + " h = {j:0 for j in l}\n", + " #idx = randint(0,i)\n", + " \n", + " dt_acc = 0.0\n", + " for j in range(i):\n", + " tic_list = timeit.default_timer()\n", + " value_list = float(j) in l\n", + " toc_list = timeit.default_timer()\n", + " dt_list = toc_list - tic_list\n", + " dt_acc += dt_list\n", + " meas_list[i] = dt_acc / (i+1) \n", + " \n", + " dt_acc = 0.0\n", + " for j in range(i):\n", + " tic_hm = timeit.default_timer()\n", + " value_hm = float(j) in h\n", + " toc_hm = timeit.default_timer()\n", + " dt_hm = toc_hm - tic_hm\n", + " dt_acc += dt_hm\n", + " meas_hm[i] = dt_acc / (i+1)\n", + " \n", + "toc = timeit.default_timer()\n", + "print(f\"Total runtime: {toc-tic:.2}s\")\n", + "\n", + "\n", + "plt.plot(meas_list, label=\"list\")\n", + "plt.plot(meas_hm, label=\"hashmap\")\n", + "plt.legend()\n", + "plt.xlabel(\"collection size\")\n", + "plt.ylabel(\"mean lookuptime in seconds\")\n", + "plt.title(\"Mean of existency lookups for every element vs size\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f358b7a6-1203-44e8-9192-d3fd56c796e9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pretosius.ipynb b/pretosius.ipynb new file mode 100644 index 0000000..c235573 --- /dev/null +++ b/pretosius.ipynb @@ -0,0 +1,685 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "f027debb-4ad0-4ea0-a564-321f3c1bd63c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting auto display for cadquery Workplane and Shape\n" + ] + }, + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import timeit\n", + "tic = timeit.default_timer()\n", + "\n", + "import cadquery as cq\n", + "from math import sin, cos, pi\n", + "\n", + "\n", + "\n", + "from jupyter_cadquery import (\n", + " versions,\n", + " show, PartGroup, Part, \n", + " get_viewer, close_viewer, get_viewers, close_viewers, open_viewer, set_defaults, get_defaults, open_viewer,\n", + " get_pick,\n", + ")\n", + "\n", + "from jupyter_cadquery.replay import replay, enable_replay, disable_replay\n", + "''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cde98536-33ac-4609-b1fe-50af35bdc752", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6af7a60f-ac0c-46cf-8956-a168cd3246cb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Enabling jupyter_cadquery replay\n" + ] + } + ], + "source": [ + "cv = open_viewer(\"pretosius\",\n", + " anchor=\"right\", \n", + " glass=True, \n", + " tools=False) # sets default viewer\n", + "\n", + "\n", + "cv.grid = [not g for g in cv.widget.grid]\n", + "\n", + "cv.axes = not cv.axes\n", + "cv.axes0 = not cv.axes0\n", + "cv.transparent = not cv.transparent\n", + "cv.black_edges = not cv.black_edges\n", + "enable_replay(False, False)\n", + "show_object = replay" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b943c6ad-a678-4af7-8139-b03cad539b33", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Defaulting to user installation because normal site-packages is not writeable\n", + "Collecting git+https://github.com/gumyr/build123d\n", + " Cloning https://github.com/gumyr/build123d to /tmp/pip-req-build-_no6109s\n", + " Running command git clone --filter=blob:none --quiet https://github.com/gumyr/build123d /tmp/pip-req-build-_no6109s\n", + " fatal: unable to access 'https://github.com/gumyr/build123d/': Could not resolve host: github.com\n", + " \u001b[1;31merror\u001b[0m: \u001b[1msubprocess-exited-with-error\u001b[0m\n", + " \n", + " \u001b[31m×\u001b[0m \u001b[32mgit clone --\u001b[0m\u001b[32mfilter\u001b[0m\u001b[32m=\u001b[0m\u001b[32mblob\u001b[0m\u001b[32m:none --quiet \u001b[0m\u001b[4;32mhttps://github.com/gumyr/build123d\u001b[0m\u001b[32m \u001b[0m\u001b[32m/tmp/\u001b[0m\u001b[32mpip-req-build-_no6109s\u001b[0m did not run successfully.\n", + " \u001b[31m│\u001b[0m exit code: \u001b[1;36m128\u001b[0m\n", + " \u001b[31m╰─>\u001b[0m See above for output.\n", + " \n", + " \u001b[1;35mnote\u001b[0m: This error originates from a subprocess, and is likely not a problem with pip.\n", + "\u001b[1;31merror\u001b[0m: \u001b[1msubprocess-exited-with-error\u001b[0m\n", + "\n", + "\u001b[31m×\u001b[0m \u001b[32mgit clone --\u001b[0m\u001b[32mfilter\u001b[0m\u001b[32m=\u001b[0m\u001b[32mblob\u001b[0m\u001b[32m:none --quiet \u001b[0m\u001b[4;32mhttps://github.com/gumyr/build123d\u001b[0m\u001b[32m \u001b[0m\u001b[32m/tmp/\u001b[0m\u001b[32mpip-req-build-_no6109s\u001b[0m did not run successfully.\n", + "\u001b[31m│\u001b[0m exit code: \u001b[1;36m128\u001b[0m\n", + "\u001b[31m╰─>\u001b[0m See above for output.\n", + "\n", + "\u001b[1;35mnote\u001b[0m: This error originates from a subprocess, and is likely not a problem with pip.\n", + "\u001b[33mWARNING: There was an error checking the latest version of pip.\u001b[0m\u001b[33m\n", + "\u001b[0m" + ] + } + ], + "source": [ + "!pip install git+https://github.com/gumyr/build123d" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "44eb609a-c382-4d07-b224-d08cd3bf8107", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Solid at 0x7f27ec7477f0, Center(0.5, 1.0, 1.5)\n", + "└── Shell at 0x7f2857577f70, Center(0.5, 1.0, 1.5)\n", + " ├── Face at 0x7f2857577e70, Center(0.0, 1.0, 1.5)\n", + " ├── Face at 0x7f27ec747630, Center(1.0, 1.0, 1.5)\n", + " ├── Face at 0x7f27ec2af930, Center(0.5, 0.0, 1.5)\n", + " ├── Face at 0x7f27ec2e58b0, Center(0.5, 2.0, 1.5)\n", + " ├── Face at 0x7f27ec10cf70, Center(0.5, 1.0, 0.0)\n", + " └── Face at 0x7f27ec10fe30, Center(0.5, 1.0, 3.0)\n", + "\n" + ] + } + ], + "source": [ + "from build123d import *\n", + "box=Solid.make_box(1,2,3)\n", + "print(box.show_topology(limit_class=\"Face\"))\n", + "box" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "52baf674-915f-4a06-9015-61692f01601e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "width = 3\n", + "height = 6\n", + "ri = 10\n", + "ro = ri+0.3\n", + "\n", + "s = 0.8\n", + "s1 = 0.7\n", + "p = Pos(ri,0,0)\n", + "x = 1.8+ri\n", + "pad = 2\n", + "with BuildPart() as ring:\n", + " with BuildSketch(Plane.XZ) as sk:\n", + " with BuildLine() as outline:\n", + " c1 = Spline([(x,0,-pad),(x,0,height+pad)], tangents=[(1,0),(-1, 0)], tangent_scalars=[s,s])\n", + " c1 = Spline([(x,0,-pad),(x,0,height+pad)], tangents=[(-1,0),(1, 0)], tangent_scalars=[s1,s1])\n", + " make_face()\n", + " revolve(axis=Axis.Z)\n", + " \n", + " \n", + "\n", + "show(ring, transparent=True) \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b9016caa-241a-4e76-a7ff-cad8cb0521bc", + "metadata": {}, + "outputs": [], + "source": [ + "def string_to_binary_string(s:str):\n", + " return ''.join([\"{0:08b}\".format(ord(c)) for c in s])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "17ba4d69-d787-484f-b04e-a14668b1632b", + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "Edges are disconnected", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[6], line 68\u001b[0m\n\u001b[1;32m 66\u001b[0m binstr \u001b[38;5;241m=\u001b[39m string_to_binary_string(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmunsel\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 67\u001b[0m binstr \u001b[38;5;241m=\u001b[39m string_to_binary_string(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnonpostrans \u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m---> 68\u001b[0m signal \u001b[38;5;241m=\u001b[39m \u001b[43mbuild_signal\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbinstr\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mri\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43mwidth\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m0.4\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 69\u001b[0m show(signal,ring, transparent\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", + "Cell \u001b[0;32mIn[6], line 61\u001b[0m, in \u001b[0;36mbuild_signal\u001b[0;34m(binstring, ri)\u001b[0m\n\u001b[1;32m 59\u001b[0m Circle(\u001b[38;5;241m0.5\u001b[39m)\n\u001b[1;32m 60\u001b[0m \u001b[38;5;66;03m#return path_builder\u001b[39;00m\n\u001b[0;32m---> 61\u001b[0m \u001b[43msweep\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtransition\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mTransition\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mROUND\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 62\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m signal\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/build123d/operations_generic.py:989\u001b[0m, in \u001b[0;36msweep\u001b[0;34m(sections, path, multisection, is_frenet, transition, normal, binormal, clean, mode)\u001b[0m\n\u001b[1;32m 987\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m context \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mor\u001b[39;00m context \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m context\u001b[38;5;241m.\u001b[39mpending_edges:\n\u001b[1;32m 988\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpath must be provided\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m--> 989\u001b[0m path_wire \u001b[38;5;241m=\u001b[39m \u001b[43mWire\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcontext\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpending_edges\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 990\u001b[0m context\u001b[38;5;241m.\u001b[39mpending_edges \u001b[38;5;241m=\u001b[39m []\n\u001b[1;32m 991\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/build123d/topology.py:7789\u001b[0m, in \u001b[0;36mWire.__init__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 7787\u001b[0m obj \u001b[38;5;241m=\u001b[39m wire\u001b[38;5;241m.\u001b[39mwrapped\n\u001b[1;32m 7788\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m edges:\n\u001b[0;32m-> 7789\u001b[0m obj \u001b[38;5;241m=\u001b[39m \u001b[43mWire\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_make_wire\u001b[49m\u001b[43m(\u001b[49m\u001b[43medges\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43msequenced\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43msequenced\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 7791\u001b[0m \u001b[38;5;28msuper\u001b[39m()\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__init__\u001b[39m(\n\u001b[1;32m 7792\u001b[0m obj\u001b[38;5;241m=\u001b[39mobj,\n\u001b[1;32m 7793\u001b[0m label\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m label \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m label,\n\u001b[1;32m 7794\u001b[0m color\u001b[38;5;241m=\u001b[39mcolor,\n\u001b[1;32m 7795\u001b[0m parent\u001b[38;5;241m=\u001b[39mparent,\n\u001b[1;32m 7796\u001b[0m )\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/build123d/topology.py:8085\u001b[0m, in \u001b[0;36mWire._make_wire\u001b[0;34m(cls, edges, sequenced)\u001b[0m\n\u001b[1;32m 8083\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mWire is empty\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 8084\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m wire_builder\u001b[38;5;241m.\u001b[39mError() \u001b[38;5;241m==\u001b[39m BRepBuilderAPI_DisconnectedWire:\n\u001b[0;32m-> 8085\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mEdges are disconnected\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 8087\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m wire_builder\u001b[38;5;241m.\u001b[39mWire()\n", + "\u001b[0;31mValueError\u001b[0m: Edges are disconnected" + ] + } + ], + "source": [ + "from math import sin, cos, pi\n", + "\n", + "width = 3\n", + "height = 6\n", + "ri = 10\n", + "ro = ri+0.3\n", + "\n", + "def build_signal(binstring, ri):\n", + " n = len(binstring)\n", + "\n", + " \n", + " ro = ri+1\n", + " dphi =2*pi/(n+0)\n", + " dp = dphi/10\n", + " dh = min(dp*ro, 18*pi/180)\n", + " \n", + " a = 1\n", + " tngu = lambda phi, d: (a*cos(phi), a*sin(phi), d)\n", + " tngd = lambda phi, d: (-a*cos(phi), -a*sin(phi), d)\n", + "\n", + " with BuildPart() as signal:\n", + " with BuildLine(Plane.XY) as path_builder:\n", + " segments = []\n", + " h = 0 if binstring[0] == \"0\" else height\n", + " same_bits_streak = 0\n", + " for i, b in enumerate(binstring):\n", + " pos_flank = b == \"0\" and binstring[(i+1)%n] == \"1\"\n", + " neg_flank = b == \"1\" and binstring[(i+1)%n] == \"0\"\n", + " same_bits_streak += 1\n", + " if neg_flank or pos_flank or (i == n-1):\n", + " phi0 = dphi*(i-same_bits_streak+1)\n", + " phi1 = dphi*(i-same_bits_streak+1+0.5*same_bits_streak)\n", + " phi2 = dphi*(i+1)\n", + " phi0_pad = phi0 + dp\n", + " phi2_pad = phi2 - dp\n", + " #if segments:\n", + " # segments.append(Spline(segments[-1]@1,p1, tangents=[segments[-1]%1, tngu(phi2, -1)]))\n", + " segments.append(ThreePointArc(\n", + " (ri * cos(phi0_pad), ri * sin(phi0_pad),h),\n", + " (ri * cos(phi1), ri * sin(phi1),h),\n", + " (ri * cos(phi2_pad), ri * sin(phi2_pad),h),\n", + " ))\n", + " same_bits_streak = 0\n", + " p1 = (ri * cos(phi2), ri * sin(phi2),height-dh)\n", + " p2 = (ri * cos(phi2), ri * sin(phi2),dh)\n", + " #l = segments[i]\n", + " if neg_flank:\n", + " h = 0\n", + " segments.append(Spline(segments[-1]@1,p1, tangents=[segments[-1]%1, tngu(phi2, -1)]))\n", + " segments.append(Spline(p1,p2,\n", + " tangents=[tngu(phi2, -1), tngd(phi2, -1)]))\n", + " if pos_flank:\n", + " h = height\n", + " segments.append(Spline(p2,p1,\n", + " tangents=[tngu(phi2, 1), tngd(phi2, 1)]))\n", + "\n", + " # l1 = segments[0]\n", + " with BuildSketch(Plane( segments[0] @ 0, z_dir=segments[0] % 0)) as x_section:\n", + " Circle(0.5)\n", + " #return path_builder\n", + " sweep(transition=Transition.ROUND)\n", + " return signal\n", + "\n", + "binstr = '0101100'#\n", + "binstr = '1101000'#\n", + "binstr = string_to_binary_string(\"munsel\")\n", + "binstr = string_to_binary_string(\"nonpostrans \")\n", + "signal = build_signal(binstr, ri+width+0.4)\n", + "show(signal,ring, transparent=True)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "7646c441-da20-47e2-9047-fb4a02a0a85d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100% ⋮————————————————————————————————————————————————————————————⋮ (2/2) 0.02s\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "def build_signal(binstring, ri):\n", + " n = len(binstring)\n", + " ro = ri+1\n", + " dphi =2*pi/(n+0) # how much angle per bit\n", + " dp = 0.81/ri/pi#dphi/10 # how much of that used for curvature\n", + " dh = min(dp*ro, 10*pi/180)\n", + " \n", + " a = 1\n", + " tngu = lambda phi, d: (a*cos(phi), a*sin(phi), d)\n", + " tngd = lambda phi, d: (-a*cos(phi), -a*sin(phi), d)\n", + " \n", + " def build_segments(pre_segments=[]):\n", + " segments = []\n", + " h = 0 if binstring[0] == \"0\" else height\n", + " same_bits_streak = 0\n", + " i_hoz = 0\n", + " ns = len(pre_segments)\n", + " for i, b in enumerate(binstring):\n", + " pos_flank = b == \"0\" and binstring[(i+1)%n] == \"1\"\n", + " neg_flank = b == \"1\" and binstring[(i+1)%n] == \"0\"\n", + " same_bits_streak += 1\n", + " if neg_flank or pos_flank or (i == n-1):\n", + " phi0 = dphi*(i-same_bits_streak+1)\n", + " phi1 = dphi*(i-same_bits_streak+1+0.5*same_bits_streak)\n", + " phi2 = dphi*(i+1)\n", + " phi0_pad = phi0 + dp\n", + " phi2_pad = phi2 - dp\n", + " #if segments:\n", + " # segments.append(Spline(segments[-1]@1,p1, tangents=[segments[-1]%1, tngu(phi2, -1)]))\n", + " segments.append(ThreePointArc(\n", + " (ri * cos(phi0_pad), ri * sin(phi0_pad),h),\n", + " (ri * cos(phi1), ri * sin(phi1),h),\n", + " (ri * cos(phi2_pad), ri * sin(phi2_pad),h),\n", + " ))\n", + " i_hoz += 1\n", + " same_bits_streak = 0\n", + " p1 = (ri * cos(phi2), ri * sin(phi2),height-dh)\n", + " p2 = (ri * cos(phi2), ri * sin(phi2),dh)\n", + " #l = segments[i]\n", + " if neg_flank:\n", + " h = 0\n", + " if pre_segments:\n", + " segments.append(Spline(pre_segments[i_hoz-1]@1,p1, tangents=[pre_segments[i_hoz-1]%1, tngu(phi2, -1)]))\n", + " segments.append(Spline(p1,p2,\n", + " tangents=[tngu(phi2, -1), tngd(phi2, -1)]))\n", + " segments.append(Spline(p2, pre_segments[i_hoz%ns]@0, tangents=[tngd(phi2, -1), pre_segments[i_hoz%ns]%0]))\n", + " if pos_flank:\n", + " h = height\n", + " if pre_segments:\n", + " segments.append(Spline(pre_segments[i_hoz-1]@1,p2, tangents=[pre_segments[i_hoz-1]%1, tngu(phi2, 1)]))\n", + " segments.append(Spline(p2,p1,\n", + " tangents=[tngu(phi2, 1), tngd(phi2, 1)]))\n", + " segments.append(Spline(p1, pre_segments[i_hoz%ns]@0, tangents=[tngd(phi2, 1), pre_segments[i_hoz%ns]%0]))\n", + "\n", + " return segments\n", + " \n", + " pre_segments = build_segments()#???\n", + "\n", + "\n", + " with BuildPart() as signal:\n", + " with BuildLine(Plane.XY) as path_builder:\n", + " #path_builder.mode=Mode.PRIVATE\n", + " segments = build_segments(pre_segments)\n", + " #segments = build_segments()\n", + " # l1 = segments[0]\n", + " with BuildSketch(Plane(segments[0] @ 0, z_dir=segments[0] % 0)) as x_section:\n", + " Circle(0.4)\n", + " #Rectangle(0.8,0.8)\n", + " #return path_builder\n", + " sweep()\n", + " #sweep(transition=Transition.ROUND)\n", + " signal.label=\"decor\"\n", + " #return pre_segments\n", + " return path_builder\n", + " #return signal\n", + "\n", + "#binstr = '0101100'#\n", + "#binstr = '110101000'#\n", + "#binstr = string_to_binary_string(\"munsel\")\n", + "binstr = string_to_binary_string(\"salty\")\n", + "signal = build_signal(binstr, ri+width+0.3)\n", + "show(signal,ring, transparent=True)\n", + "#show(signal, transparent=True, reset_camera=False)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d2d71180-9e82-4907-9579-e1accdb756e7", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'BuildLine' has no attribute 'part'. Did you intend '.line.part'?", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[14], line 10\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m (left, right)\n\u001b[1;32m 9\u001b[0m \u001b[38;5;66;03m#ld, lu = \u001b[39;00m\n\u001b[0;32m---> 10\u001b[0m a,b,c,d \u001b[38;5;241m=\u001b[39m split_part(\u001b[43msignal\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpart\u001b[49m)\n\u001b[1;32m 11\u001b[0m \u001b[38;5;66;03m#cv = show(ld,lu, axes=True, axes0=True, transparent=True)\u001b[39;00m\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/build123d/build_common.py:761\u001b[0m, in \u001b[0;36mBuilder.__getattr__\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m 759\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__getattr__\u001b[39m(\u001b[38;5;28mself\u001b[39m, name):\n\u001b[1;32m 760\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"The user is likely trying to reference the builder's object\"\"\"\u001b[39;00m\n\u001b[0;32m--> 761\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m(\n\u001b[1;32m 762\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m has no attribute \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 763\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDid you intend \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m<\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m>.\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_obj_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m?\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 764\u001b[0m )\n", + "\u001b[0;31mAttributeError\u001b[0m: 'BuildLine' has no attribute 'part'. Did you intend '.line.part'?" + ] + } + ], + "source": [ + "def split_part(part):\n", + " #left, right = \n", + " return split(part, bisect_by=Plane.XZ, keep=Keep.BOTH).solids()\n", + " #ld, lu = split(left,bisect_by=Plane.YZ, keep=Keep.BOTH).solids()\n", + " #rd, ru = split(right,bisect_by=Plane.YZ, keep=Keep.BOTH).solids()\n", + " #return ld, lu#, rd, ru\n", + " return (left, right)\n", + "\n", + "#ld, lu = \n", + "a,b,c,d = split_part(signal.part)\n", + "#cv = show(ld,lu, axes=True, axes0=True, transparent=True)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "71186b01-15d9-4cdc-b70c-f94f75106ef9", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'd' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[13], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43md\u001b[49m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m#neg_ring = ring.part - signal.part\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;66;03m#show(neg_ring)\u001b[39;00m\n", + "\u001b[0;31mNameError\u001b[0m: name 'd' is not defined" + ] + } + ], + "source": [ + "d\n", + "#neg_ring = ring.part - signal.part\n", + "#show(neg_ring)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e6b70bbb-5ceb-4440-ab01-7caeb06c6f17", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'BuildLine' has no attribute 'part'. Did you intend '.line.part'?", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[12], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m product \u001b[38;5;241m=\u001b[39m Compound(label\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mring\u001b[39m\u001b[38;5;124m\"\u001b[39m, children\u001b[38;5;241m=\u001b[39m[ring\u001b[38;5;241m.\u001b[39mpart, \u001b[43msignal\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpart\u001b[49m])\n\u001b[1;32m 2\u001b[0m show(product)\n", + "File \u001b[0;32m~/.local/lib/python3.10/site-packages/build123d/build_common.py:761\u001b[0m, in \u001b[0;36mBuilder.__getattr__\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m 759\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__getattr__\u001b[39m(\u001b[38;5;28mself\u001b[39m, name):\n\u001b[1;32m 760\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"The user is likely trying to reference the builder's object\"\"\"\u001b[39;00m\n\u001b[0;32m--> 761\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m(\n\u001b[1;32m 762\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m has no attribute \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 763\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDid you intend \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m<\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m>.\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_obj_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m?\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 764\u001b[0m )\n", + "\u001b[0;31mAttributeError\u001b[0m: 'BuildLine' has no attribute 'part'. Did you intend '.line.part'?" + ] + } + ], + "source": [ + "product = Compound(label=\"ring\", children=[ring.part, signal.part])\n", + "show(product)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d278f9c-5e81-4341-89bd-6840d21028da", + "metadata": {}, + "outputs": [], + "source": [ + "#show(signal)\n", + "product.export_step(\"nonpostrans_ascii_ring.step\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0f50802-29c0-4960-918b-245c9b71a3e9", + "metadata": {}, + "outputs": [], + "source": [ + "fullring = ring.part + signal.part" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d306d91-b798-445a-b91c-e99887d539df", + "metadata": {}, + "outputs": [], + "source": [ + "#cq.exporters.export(signal, \"signal.stl\", cq.exporters.ExportTypes.STL)\n", + "\n", + "view_port_origin=(-100, -50, 30)\n", + "visible, hidden = ring.project_to_viewport(view_port_origin)\n", + "\n", + "max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\n", + "exporter = ExportSVG(scale=100 / max_dimension)\n", + "exporter.add_layer(\"Visible\")\n", + "exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n", + "exporter.add_shape(visible, layer=\"Visible\")\n", + "exporter.add_shape(hidden, layer=\"Hidden\")\n", + "exporter.write(\"nonpostrans_ring.svg\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a5cfcf5-9811-4004-8f29-d56fa444a399", + "metadata": {}, + "outputs": [], + "source": [ + "from build123d import *\n", + "import copy" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "c485de3b-7837-496a-9664-283ed97ea2d9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "w_cut_inside = 71.0 w_cut_outside = 21.0\n", + "w_cut_inside = 71.0 w_cut_outside = 21.0\n" + ] + } + ], + "source": [ + "def build_psorias_release(L = 216, W = 127, H = 135,PEAK2PEAK = 160, h_bottom = 15):\n", + "\n", + " R0 = 40\n", + " R1 = 8\n", + " R2 = 40\n", + " R3 = 30\n", + " w_cut_inside = (PEAK2PEAK / 2) - R1-1\n", + " w_cut_outside = (L / 2) - w_cut_inside - (2 * R1)\n", + " print(f\"{w_cut_inside = } {w_cut_outside = }\")\n", + " \n", + " h_bend = 70\n", + " w_bend = w_cut_outside - 8\n", + " h_rect = 10\n", + " w_slope = 10\n", + "\n", + "\n", + " with BuildPart() as p:\n", + " with BuildSketch(Plane.XZ) as s:\n", + " with Locations((0,H-R0)):\n", + " Circle(R0)\n", + " with Locations((0,0)):\n", + " Rectangle(W, h_rect)\n", + " make_hull()\n", + " extrude(amount=L/2)\n", + " with BuildSketch(Plane.YZ.offset(-W/2)) as s:\n", + " with Locations((-(L-w_cut_inside)/2, H+h_rect/2)):\n", + " Rectangle(w_cut_inside, h_rect)\n", + " with Locations(((-L+w_cut_inside-w_slope-R2-1)/2, h_bottom+h_rect/2)): \n", + " Rectangle(w_cut_inside-w_slope-R2, h_rect)\n", + " with Locations((-(L-w_cut_inside+R2-w_slope)/2, h_bottom+R2)): \n", + " Circle(R2)\n", + " make_hull()\n", + " extrude(amount=W, mode=Mode.SUBTRACT)\n", + " with BuildSketch(Plane.YZ.offset(-W/2)) as s:\n", + " with Locations((-w_cut_outside/2, H+h_rect/2)):\n", + " Rectangle(w_cut_outside, h_rect)\n", + " with Locations(((h_rect)/2, h_rect)): \n", + " Rectangle(h_rect, h_rect)\n", + " with Locations((-w_bend+R3, h_bend)): \n", + " Circle(R3)\n", + " make_hull()\n", + " extrude(amount=W, mode=Mode.SUBTRACT) \n", + " fillet(p.edges().sort_by(Axis.Z)[-1], radius=R1) \n", + " fillet(p.edges().sort_by(Axis.Z)[-3], radius=R1)\n", + " #scale(by=(1,1,0.8), mode=Mode.SUBTRACT)\n", + " mirror(p.part, about=Plane(p.faces().group_by(Axis.Y)[0][0]))\n", + " return p\n", + "\n", + "p = build_psorias_release()\n", + "p1 = build_psorias_release(L = 216, W = 110, H = 100,PEAK2PEAK = 160, h_bottom = 15)\n", + "p2 = p.part - p1.part.locate(Location((0.,0,-25.)))\n", + "\n", + "p2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfe10241-66e6-43c9-839e-16f1dfbe2925", + "metadata": {}, + "outputs": [], + "source": [ + "p1 = copy.copy(p.part)\n", + "p1 = scale(p1, by=(0.8,0.95,0.8)).locate(Location((0.,-(1-0.95)*W/2.-2.5,-25.)))\n", + "p2 = p.part-p1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc50e074-b223-4c10-9dc4-bbece6206876", + "metadata": {}, + "outputs": [], + "source": [ + "p2.export_step(\"psoas_releaser.step\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "beae3c3c-7cea-40f3-9e30-10b87e8fcbdd", + "metadata": {}, + "outputs": [], + "source": [ + "import build123d as b1 \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "016ae351-fc44-456c-834a-15cb59e42327", + "metadata": {}, + "outputs": [], + "source": [ + "b1.topology." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/regal.ipynb b/regal.ipynb new file mode 100644 index 0000000..567e3b8 --- /dev/null +++ b/regal.ipynb @@ -0,0 +1,343 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "a0dc6484-8b39-4f43-a765-11e567de1746", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting auto display for cadquery Workplane and Shape\n", + "import runtime: 2.6s\n" + ] + } + ], + "source": [ + "import timeit\n", + "\n", + "tictime = 0\n", + "def tic():\n", + " global tictime\n", + " tictime = timeit.default_timer()\n", + "\n", + "def toclog(custom_msg=None):\n", + " toc = timeit.default_timer()\n", + " runtime = toc - tictime\n", + " msg = \"runtime\" if not custom_msg else custom_msg\n", + " print(f\"{msg}: {runtime:2.2}s\")\n", + " \n", + "tic()\n", + "\n", + "\n", + "\n", + "from build123d import *\n", + "from jupyter_cadquery import (\n", + " versions,\n", + " show, PartGroup, Part, \n", + " get_viewer, close_viewer, get_viewers, close_viewers, open_viewer, set_defaults, get_defaults, open_viewer,\n", + " get_pick,\n", + ")\n", + "from jupyter_cadquery.ocp_utils import webcol_to_cq\n", + "\n", + "from jupyter_cadquery.replay import replay, enable_replay, disable_replay\n", + "\n", + "toclog(\"import runtime\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6b46efca-c212-4c0d-9e39-a6d82d51bbec", + "metadata": {}, + "outputs": [], + "source": [ + "cv = open_viewer(\"REGAL\",\n", + " anchor=\"right\", \n", + " glass=True, \n", + " tools=False) # sets default viewer\n", + "\n", + "\n", + "cv.grid = [not g for g in cv.widget.grid]\n", + "\n", + "cv.axes = not cv.axes\n", + "cv.axes0 = not cv.axes0\n", + "cv.transparent = not cv.transparent\n", + "cv.black_edges = not cv.black_edges\n", + "\n", + "set_defaults(\n", + " cad_width=640, \n", + " height=480, \n", + " viewer=\"REGAL\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "44dd6c5a-c307-4284-8074-ec9086df4239", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32;1m\n", + "*******************************************************************\n", + "****** Statistics on Transfer (Write) ******\u001b[0m\n", + "\u001b[32;1m\n", + "*******************************************************************\n", + "****** Transfer Mode = 0 I.E. As Is ******\u001b[0m\n", + "\u001b[32;1m****** Transferring Shape, ShapeType = 0 ******\u001b[0m\n", + "\u001b[32;1m** WorkSession : Sending all data\u001b[0m\n", + "\u001b[32;1m Step File Name : regal_5x5.step(15716 ents) Write Done\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_223/3924664858.py:37: DeprecationWarning: Use the `export_step` function instead\n", + " regal.export_step(f'regal_{n_hor_boards}x{n_vert_boards}.step')\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from build123d import *\n", + "\n", + "h = 200\n", + "w = 200\n", + "d = 80\n", + "t = 3\n", + "n_vert_boards = 5\n", + "n_hor_boards = 5\n", + "dw = (w-t)/(n_vert_boards-1)\n", + "dh = (h-t)/(n_hor_boards-1)\n", + "tol = 1\n", + "\n", + "\n", + "with BuildPart() as cutouts:\n", + " with BuildSketch(Plane.XZ.offset(d/2)) as sk:\n", + " with Locations(*tuple((j*dw-tol,dh*i-tol) \\\n", + " for i in range(n_hor_boards) \\\n", + " for j in range(n_vert_boards))):\n", + " Rectangle(t+tol*2,t+tol*2, align=Align.MIN)\n", + " extrude(amount=d/2+tol)\n", + "\n", + "with BuildPart() as vboards:\n", + " with BuildSketch(Plane.XZ) as sk:\n", + " with Locations(*tuple((dw*i, 0.0) for i in range(n_vert_boards))):\n", + " Rectangle(t, h, align=Align.MIN)\n", + " extrude(amount=d)\n", + " add(cutouts.part, mode=Mode.SUBTRACT)\n", + " \n", + "with BuildPart() as hboards: \n", + " with BuildSketch(Plane.XZ.offset(d/2)) as sk:\n", + " with Locations(*tuple((0.0,dh*i) for i in range(n_hor_boards))):\n", + " Rectangle(w,t, align=Align.MIN)\n", + " extrude(amount=d)\n", + " add(cutouts.part, mode=Mode.SUBTRACT)\n", + "\n", + "\n", + "hboards = hboards.part.translate((0,d/2,0))\n", + "\n", + "regal = Compound(label=\"regal\", children=(vboards.part, hboards))\n", + "\n", + "regal.export_step(f'regal_{n_hor_boards}x{n_vert_boards}.step')" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ee75ab94-20d0-421e-a30b-06c4ae888206", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100% ⋮————————————————————————————————————————————————————————————⋮ (2/2) 0.09s\n" + ] + } + ], + "source": [ + "packed = pack((vertboards.part, horboards),\\\n", + " padding=5, align_z=False)\n", + "Compound(packed)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "7e29334c-787c-474b-aab5-e92b72e93c66", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Versions:\n", + "- jupyter_cadquery 3.5.2\n", + "- cad_viewer_widget 1.4.1\n", + "- open cascade 7.6.3\n", + "\n", + "Plugins loaded:\n", + "- build123d\n", + "- cadquery-massembly\n" + ] + } + ], + "source": [ + "versions()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "e80b28dd-4739-4d79-a47e-d5c17e6fb3e9", + "metadata": {}, + "outputs": [], + "source": [ + "with BuildPart() as cupbuilder:\n", + " with BuildSketch() as sk:\n", + " Circle(30)\n", + " extrude(amount=60)\n", + " with Locations((30, 0, 10), (30, 0, 45)):\n", + " b = Box(15, 40, 10)\n", + " #b.edges().sort_by(b.edges(),Axis.is_parallel(Axis.Z))\n", + " with BuildSketch(cupbuilder.faces().sort_by(Axis.Z)[-1]) as sk1:\n", + " Circle(25)\n", + " extrude(amount=-55, mode=Mode.SUBTRACT)\n", + " #offset(amount=-6, openings=f)\n", + " fillet(cupbuilder.edges().sort_by(Axis.Z)[-2], radius=3)\n", + " with Locations((32,0,30)):\n", + " Box(4, 30, 60, mode=Mode.SUBTRACT)\n", + " with Locations((36,0,30)): \n", + " Box(8, 4, 60, mode=Mode.SUBTRACT)\n", + "cupbuilder" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "2551e7ff-dde4-40a6-9ad6-63cd513a5ea7", + "metadata": {}, + "outputs": [], + "source": [ + "b.edges().pa" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d7bbd569-8c84-4d3a-8466-ac1fa95ff480", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0.6.1.dev11+g31c2aaa'" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from build123d import __version__\n", + "__version__" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "e7ec04b2-3b3d-4f66-9374-b8dbe9445c72", + "metadata": {}, + "outputs": [], + "source": [ + "sheet_thickness = 4 * MM\n", + "\n", + "with BuildPart() as side:\n", + " d = Vector(1, 0, 0).rotate(Axis.Y, 60)\n", + " with BuildLine(Plane.XZ) as side_line:\n", + " l1 = Line((0, 65), (170 / 2, 65))\n", + " l2 = PolarLine(l1 @ 1, length=65, direction=d, length_mode=LengthMode.VERTICAL)\n", + " l3 = Line(l2 @ 1, (170 / 2, 0))\n", + " fillet(side_line.vertices(), 7)\n", + " make_brake_formed(\n", + " thickness=sheet_thickness,\n", + " station_widths=[40, 40, 40, 112.52 / 2, 112.52 / 2, 112.52 / 2],\n", + " side=Side.RIGHT,\n", + " )\n", + " fe = side.edges().filter_by(Axis.Z).group_by(Axis.Z)[0].sort_by(Axis.Y)[-1]\n", + " fillet(fe, radius=7)\n", + "side" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "bc9fe2d7-c65f-481d-93ae-46ad90172418", + "metadata": {}, + "outputs": [ + { + "ename": "ImportError", + "evalue": "cannot import name '__version__' from 'os' (/opt/conda/envs/cq/lib/python3.10/os.py)", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[2], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mos\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m __version__\n", + "\u001b[0;31mImportError\u001b[0m: cannot import name '__version__' from 'os' (/opt/conda/envs/cq/lib/python3.10/os.py)" + ] + } + ], + "source": [ + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c9865a4-0062-4f8b-9f57-48170042bcee", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/smartphonestand.ipynb b/smartphonestand.ipynb new file mode 100644 index 0000000..7fe6c85 --- /dev/null +++ b/smartphonestand.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "bad426fe-bce7-427b-938b-ccf52358420d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting auto display for cadquery Workplane and Shape\n" + ] + } + ], + "source": [ + "import timeit\n", + "tic = timeit.default_timer()\n", + "\n", + "import cadquery as cq\n", + "from build123d import *\n", + "\n", + "from jupyter_cadquery import (\n", + " versions,\n", + " show, PartGroup, Part, \n", + " get_viewer, close_viewer, get_viewers, close_viewers, open_viewer, set_defaults, get_defaults, open_viewer,\n", + " get_pick,\n", + ")\n", + "from jupyter_cadquery.ocp_utils import webcol_to_cq\n", + "\n", + "from jupyter_cadquery.replay import replay, enable_replay, disable_replay\n", + "close_viewers()\n", + "set_defaults(\n", + " cad_width=640, \n", + " height=480, \n", + " viewer=\"smartphonestand\"\n", + ")\n", + "cv = open_viewer(\"smartphonestand\",\n", + " anchor=\"right\", \n", + " glass=True, \n", + " theme=\"dark\",\n", + " tools=True) # sets default viewer\n", + "\n", + "\n", + "#cv.grid = [not g for g in cv.widget.grid]\n", + "\n", + "#cv.axes = not cv.axes\n", + "#cv.axes0 = not cv.axes0\n", + "#cv.transparent = not cv.transparent\n", + "#cv.black_edges = not cv.black_edges\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "897c1e60-a09b-4143-99c6-52f5e42bf60e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with BuildPart() as part_builder:\n", + " with BuildSketch() as profile_sketch:\n", + " with BuildLine() as line_builder:\n", + " line = Polyline((100, -30),(-30, -30),(0, -10),\\\n", + " (-30,10),(-20,20), (0,0), (70,70), close=False)\n", + " line1 = fillet(radius=6,objects=line.vertices())\n", + " w = offset(amount=3) \n", + " \n", + " make_face()\n", + " extrude(amount=70)\n", + " f = part_builder.faces().sort_by(Axis.Y)[-11]\n", + " with BuildSketch() as bump_sk:\n", + " with Locations((-15,18)):\n", + " Circle(3)\n", + " extrude(amount=70)\n", + " with BuildSketch(f) as cutout_sketch:\n", + " with Locations((-7,0)):\n", + " RectangleRounded(35, 15, 2)\n", + " #SlotCenterToCenter(10,9, rotation=90)\n", + " #Circle(6)\n", + " extrude(amount=35, both=True, mode=Mode.SUBTRACT)\n", + " \n", + "show(part_builder,reset_camera=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "ca8fe07f-818b-458d-989c-e93d248a8bec", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "export_step(part_builder.part, \"phonestand1.step\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b70ab33-cade-49e7-afc6-579769e9aee7", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/smue2.ipynb b/smue2.ipynb new file mode 100644 index 0000000..423cce8 --- /dev/null +++ b/smue2.ipynb @@ -0,0 +1,1812 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "11191589-d59e-453c-9473-98f7a5200e89", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting auto display for cadquery Workplane and Shape\n" + ] + } + ], + "source": [ + "import timeit\n", + "tic = timeit.default_timer()\n", + "\n", + "import cadquery as cq\n", + "\n", + "\n", + "from jupyter_cadquery import (\n", + " versions,\n", + " show, PartGroup, Part, \n", + " get_viewer, close_viewer, get_viewers, close_viewers, open_viewer, set_defaults, get_defaults, open_viewer,\n", + " get_pick,\n", + ")\n", + "from jupyter_cadquery.ocp_utils import webcol_to_cq\n", + "\n", + "from jupyter_cadquery.replay import replay, enable_replay, disable_replay\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0bf54acd-c9dd-4d79-87a3-93245059765f", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Versions:\n", + "- jupyter_cadquery 3.5.2\n", + "- cad_viewer_widget 1.4.1\n", + "- open cascade 7.6.3\n", + "\n", + "Plugins loaded:\n", + "- build123d\n", + "- cadquery-massembly\n" + ] + } + ], + "source": [ + "close_viewers()\n", + "versions()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ba2360de-f361-44ec-83a8-fa516edf320d", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "cv = open_viewer(\"smue2\",\n", + " anchor=\"right\", \n", + " glass=True, \n", + " theme=\"dark\",\n", + " tools=True) # sets default viewer\n", + "\n", + "\n", + "#cv.grid = [not g for g in cv.widget.grid]\n", + "\n", + "#cv.axes = not cv.axes\n", + "#cv.axes0 = not cv.axes0\n", + "#cv.transparent = not cv.transparent\n", + "#cv.black_edges = not cv.black_edges\n", + "\n", + "set_defaults(\n", + " cad_width=640, \n", + " height=480, \n", + " viewer=\"smue2\"\n", + ")\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6c0bf0b7-4559-487a-a059-95896c3ce3d0", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "from math import asin,pi\n", + "\n", + "def arcd(y):\n", + " return 2*asin(y*0.5)\n", + "\n", + "def row_phi(size, radius):\n", + " return arcd(size/radius)\n", + "\n", + "tol = 0.5\n", + "\n", + "column_width = 19.3\n", + "plate_thickness = 4.0\n", + "\n", + "#column_yoff = 20.2\n", + "column_yoff = 18.0\n", + "switch_height = 15.4\n", + "switch_travel = 3.9\n", + "pad = 1\n", + "buttonhole_size = 14.0\n", + "cap_size = 18.0\n", + "cap_height = 11.5\n", + "\n", + "y0 = 0#-column_width*0.5\n", + "z0 =30\n", + "x0=-55\n", + "\n", + "endz = 65\n", + "endx = 110\n", + "endy = 6*column_width+5*pad\n", + "\n", + "radii = [60,60,68,64,44,44]\n", + "#radii = [47,47,48,48,44,44]\n", + "# global angle to rotate everything around after generation\n", + "radius = max(radii) + switch_height\n", + "angle_y = row_phi(column_yoff, radius)/(pi)*180 * 2\n", + "\n", + "radmodp= lambda r : r+switch_height#-switch_travel+plate_thickness\n", + "radmoda = lambda r : r+switch_height- cap_height\n", + "\n", + "def column(radius, full=False, n_buttons=4, r=None, tol=0.0, overwidth=0.0):\n", + " \n", + " angle_button = row_phi(column_yoff, radmoda(radius))/(pi)*180\n", + " radius = radmodp(radius)\n", + " angle_rev = angle_button*n_buttons\n", + " if r:\n", + " angle_rev = r\n", + " \n", + " l = radius+plate_thickness+tol if full else plate_thickness\n", + " y0 = -tol if full else 0\n", + "\n", + " #radius = radius+tol if full else radius\n", + " \n", + " base = (cq.Workplane(\"YZ\")\n", + " .move(-tol, -plate_thickness+y0)\n", + " .rect(column_width+tol+overwidth, l, False)\n", + " .revolve(angle_rev,(-50,radius),(50,radius), False)\n", + " )\n", + " return base\n", + "\n", + "\n", + "def buttonhole(i, j,r):\n", + " \n", + " angle = row_phi(column_yoff, radmoda(r))/(pi)*180*(0.5+j)\n", + " r = radmodp(r)\n", + " # r = r+switch_height-switch_travel + plate_thickness\n", + " # h = 4\n", + " return (cq.Workplane(\"XY\")\n", + " .workplane(offset=switch_height/2)\n", + " .box(cap_size, cap_size ,switch_height)\n", + " .faces(\"Y\").val().outerWire()\n", + " right_finger_left_face = fingers[i+1].faces(\"Y\")#.edges(\">Z\")#.fillet(1)\n", + " \n", + " )\n", + " cutout = rotate_keyplate(cutout).faces(\"Y\").edges(\"X\")\n", + " .workplane(centerOption='CenterOfMass')\n", + " .moveTo(-endy/2+y0, switch_height)#plate_thickness*0.5)\n", + " .line(endy-y0,0)\n", + " .line(0,h1)\n", + " .line(-endy+y0,0)\n", + " .close()\n", + " .val()\n", + " )\n", + "\n", + " middle = (cq.Workplane(\"YZ\")\n", + " .workplane(offset=80)\n", + " .moveTo(-h, z0)\n", + " .spline(pnts, includeCurrent=False)\n", + " .line(0,30)\n", + " .line(-endy, 0)\n", + " .close()\n", + " .val()\n", + " )\n", + "\n", + " end = (cq.Workplane(\"YZ\")\n", + " .workplane(offset=endx)\n", + " .moveTo(y0,30-h)\n", + " .line(endy-y0,0)\n", + " .line(0,h1)\n", + " .line(-endy+y0,0)\n", + " .close()\n", + " .val()\n", + " )\n", + " \n", + " ans = cq.Solid.makeLoft([top, middle, end])\n", + " return ans\n", + "\n", + "#handrest_cutout = build_handrest_cuttout(raw_buttonplate)\n", + "#testrender()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8c526fc5-55a3-453d-afa5-878726a5c480", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "def build_xy_contour():\n", + " return (cq.Workplane(\"XY\")\n", + " .workplane(offset=-20)\n", + " .moveTo(x0,y0)\n", + " .line(0,endy)\n", + " .line(endx-x0,0)\n", + " .line(0,-endy) \n", + " .close()\n", + " .extrude(endz)\n", + " \n", + " )\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "314eae90-2aef-410a-9e46-97fa6b4f7d61", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "def build_electronics_cutout():\n", + " return (cq.Workplane(\"XY\")\n", + " .workplane(offset=-15)\n", + " .rect(50, 50)\n", + " .workplane(offset=30)\n", + " .rect(55,55)\n", + " .loft()\n", + " .fillet(5)\n", + " .translate([0, 56,0])\n", + " )\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "bc87c49c-9b74-4da6-9059-e86d44453b24", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "def build_keyplate_overhang_cutout(radii):\n", + " \n", + " def column(radius, n_buttons=4, r=None):\n", + " angle_button = row_phi(column_yoff, radmoda(radius))/(pi)*180\n", + " radius = radmodp(radius)\n", + " angle_rev = angle_button*(n_buttons)\n", + "\n", + "\n", + " base = (cq.Workplane(\"YZ\")\n", + " .move(0, -plate_thickness*5)\n", + " .rect(column_width, 5*plate_thickness, False)\n", + " .revolve(angle_rev,(-50,radius),(50,radius), False)\n", + " )\n", + " return base\n", + " \n", + " def plate(add=True):\n", + " n = 6 if add else 4\n", + " radius = max(radii)\n", + " \n", + " fingers = []\n", + " for i,r in enumerate(radii):\n", + "\n", + " finger = (column(r, n)\n", + " .translate([0,(column_width+pad)*i,0])\n", + " )\n", + " fingers.append(finger)\n", + " bridges = []\n", + " for i in range(len(fingers)-1):\n", + " left_finger_right_face = fingers[i].faces(\">Y\").val().outerWire()\n", + " right_finger_left_face = fingers[i+1].faces(\"Y\").wires().val()\n", + " f1 = c1.faces(\">Y[0]\").fillet(5)\n", + " return thumbplate\n", + "\n", + "def build_thumbplate_holes(r=44):\n", + " #r += switch_height \n", + " h = buttonholes([r], n_buttons=2)\n", + " h1 = position_second_col_thumbplate(h, r)\n", + " holes = h + h1\n", + " holes = position_thumbplate(holes, r)\n", + " return holes\n", + "\n", + "def build_thumbplate_cutspace(r=44, phi=None, yoff=0, width=22): \n", + " phipad = row_phi(tol, radmoda(r))/(pi)*180\n", + " angle_button = row_phi(column_yoff, radmoda(r))/(pi)*180\n", + " angle_rev = angle_button*2+(phipad*2)\n", + " angle_2nd = 20\n", + " #r = radmodp(r)\n", + " dr = 30\n", + " r0 = r\n", + " c0 = (cq\n", + " .Workplane(\"YZ\")\n", + " .workplane(offset=0)#-tol)\n", + " .move(-column_width, -plate_thickness+y0-50)\n", + " .rect(column_width*5, 200, False)\n", + " .extrude(30))\n", + " #r = radmodp(r)\n", + " c = column(r, full=True, n_buttons=2, r=angle_rev, tol=3*tol)\n", + " \n", + " #.translate([0,yoff,0])\n", + " c = c.rotate((0,-r,r),(0,r,r), -phipad)\n", + " c1 = position_second_col_thumbplate(c, r0)#c.translate([0, width+tol, 0])\n", + " \n", + " \n", + " c = column(r, full=True, n_buttons=4, tol=tol).translate([0,yoff,0])\n", + " c = c.rotate((0,-r,r),(0,r,r), -phipad)\n", + " \n", + " f = c.faces(\">Y\").wires().val()\n", + " f1 = c1.faces(\">X[2]\")\n", + " #.edges(\"|Z and >>X[2]\").fillet(10)\n", + " .edges(\"|Z and >>Y[2]\").fillet(30)\n", + " .edges(\"|Z and <Y\")\n", + " .workplane(centerOption='CenterOfMass')\n", + " #.wires().toPending()\n", + " #.move(0, -switch_height+switch_travel)\n", + " .circle(r-switch_travel)\n", + " .workplane(offset=150, centerOption='CenterOfMass')\n", + " .move(10,0)\n", + " .circle(20)\n", + " .loft()#.clean()\n", + " .edges(\">>Y[2]\").fillet(290)\n", + " .rotate((0,-50,r),(0,50,r), 75)\n", + " .translate([0,2*column_yoff,0])\n", + " )\n", + "\n", + "def build_thumb_overhang_cutout(radii):\n", + " \n", + " def column(radius, n_buttons=2, r=None):\n", + " #radius+=switch_height\n", + " angle_button = row_phi(column_yoff, radius)/(pi)*180\n", + " angle_rev = angle_button*(n_buttons)\n", + " s = 5\n", + "\n", + " base = (cq.Workplane(\"YZ\")\n", + " .move(-32, 0)#-plate_thickness*s)\n", + " .rect(column_width+28, s*plate_thickness, False)\n", + " .revolve(angle_rev,(-50,radius),(50,radius), False)\n", + " )\n", + " return base\n", + " \n", + " def plate(add=True):\n", + " n = 4 if add else 2\n", + " fingers = []\n", + " for i,r in enumerate(radii):\n", + "\n", + " finger = (column(r, n)\n", + " #.translate([0,(column_width+pad)*i,0])\n", + " )\n", + " finger = position_second_col_thumbplate(finger,r) if i == 1 else finger\n", + " fingers.append(finger)\n", + " bridges = []\n", + " for i in range(len(fingers)-1):\n", + " left_finger_right_face = fingers[i].faces(\">Y\").val().outerWire()\n", + " right_finger_left_face = fingers[i+1].faces(\"Y\").edges(\"<Y\").val().outerWire()\n", + " buttonplate_bridge = cq.Solid.makeLoft([f2, f1])\n", + " return buttonplate_bridge\n", + " #return buttonplate_bridge_b\n" + ] + }, + { + "cell_type": "markdown", + "id": "c14ffb97-a514-4af9-ab58-a4434f9591f1", + "metadata": {}, + "source": [ + "# create slot" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "5792be7b-db30-4b0f-93ca-581c8991d5ea", + "metadata": {}, + "outputs": [], + "source": [ + "def build_slot():\n", + " box =( cq.Workplane(\"YZ\").workplane(offset=x0)\n", + " .moveTo(2.5*column_yoff,-10)\n", + " .sketch()\n", + " .rect(20,20)\n", + " .finalize()\n", + " .extrude(50)\n", + " .edges(\"Y\").edges(\"%LINE\").edges(\">>X[1] or >>X[0]\").fillet(8)\n", + "holes = rotate_keyplate(buttonholes(radii))\n", + "\n", + "buttonplate_cutspace = build_buttonplate(radii,True, angle_y*4).rotate((0,-50,radius),(0,50,radius), -angle_y-0.4)\n", + "buttonplate_cutspace1 = (build_buttonplate([radii[0]],True, angle_y*4)\n", + " .rotate((0,-50,radius),(0,50,radius), -angle_y-0.4)\n", + " #.rotate((0,-50,radii[0]),(0,50,radii[0]), angle_y*0.2)\n", + " .translate([0,-column_yoff*0.8,0]) )\n", + "#holes\n", + "hole1, stem1 = build_screwholes([0.,column_width+pad/2])\n", + "hole2, stem2 = build_screwholes([20.,5*(column_width+pad)-0.5,2.5])\n", + "slot, usbsocket = build_slot()\n", + "\n", + "xy_contour = build_xy_contour()\n", + "\n", + "handrest_cutout = build_handrest_cuttout(raw_buttonplate)\n", + "thumb_handrest_cutout = build_thumb_handrest_cuttout()\n", + "electronics_cutout = build_electronics_cutout()\n", + "overhang_cutout = rotate_keyplate(build_keyplate_overhang_cutout(radii))\n", + "bottom_cutout = build_bottom_cutout()\n", + "\n", + "\n", + "\n", + "pcb_cutouts = build_base_pcb_cutouts(radii)\n", + "wire_tunnels = build_wire_tunnels()\n", + "thumbplate = build_thumbplate()\n", + "thumbholes = build_thumbplate_holes()\n", + "thumbplate_cutspace = build_thumbplate_cutspace()\n", + "thumb_cutout = build_thumb_cutout()\n", + "thumbcontour = build_thumbcontour()\n", + "thumb_overhang_cutout = build_thumb_overhang_cutout([40])\n", + "#thumb_overhang_cutout -= thumb_overhang_cutout & buttonplate_cutspace\n", + "\n", + "thumb_pcb_cutout = build_thumb_pcb_cutouts()\n", + "\n", + "buttonplate_bridge = build_buttonplate_bridge()\n", + "\n", + "#raw_buttonplate += buttonplate_bridge\n", + "buttonplate = ((raw_buttonplate + buttonplate_bridge)- thumbholes - holes-hole1-hole2) \n", + "thumbplate = thumbplate - thumbholes - holes\n", + "buttonplate += thumbplate\n", + "\n", + "#testrender()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8dbe4f9-6e2d-42aa-98e0-890c67faa7f1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "54dc0c80-cb21-4ed3-beb8-85192e7a52bf", + "metadata": {}, + "outputs": [], + "source": [ + "def build_contour():\n", + "# fuse both contours together and make seam smooth\n", + " r1 = 60\n", + " r2 = r1\n", + " contour = xy_contour + thumbcontour\n", + " contour = (contour.faces(\"-Y\").edges(\"|Z and >X\").fillet(r1)\n", + ".edges(\"|Z\").edges(\"<Z\").fillet(12)\n", + " .edges(\"|Z and >>Y[0]\").fillet(5)\n", + " .faces(\">Y[0]\").fillet(3)\n", + " base = base - thumbholes\n", + " return base\n", + "\n", + "base = build_base()\n", + "#testrender()\n", + "base\n", + "#keyboard = show_product()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81768916-1d16-4d20-b337-9d1a59a42341", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "1af586cd-f2dc-4cd4-8daf-09957973b3c3", + "metadata": {}, + "source": [ + "# add ridge to base" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c47df18b-fb87-4dc9-b4be-50347b26ffeb", + "metadata": {}, + "outputs": [], + "source": [ + "from build123d import *" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "dbd66d5c-ccc0-4921-914b-64a2ecab6f39", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def add_ridge(contour, base):\n", + " path = contour.faces(\"" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def testrender():\n", + " return show(cq.Assembly()\n", + " #.add(xy_contour, color=webcol_to_cq(\"#11abdda4\"))\n", + " #.add(thumbcontour, color=webcol_to_cq(\"#ddab11a4\"))\n", + " #.add(contour, color=webcol_to_cq(\"#33ff6622\"))\n", + " .add(base, color=webcol_to_cq(\"#33ff66aa\"))\n", + " #.add(wire_tunnels, color=webcol_to_cq(\"#aa44ff99\"))\n", + " #.add(thumbplate, color=webcol_to_cq(\"#aa44ff99\"))\n", + " #.add(buttonplate_cutspace1, color=webcol_to_cq(\"#33abdd4a\"))\n", + " #.add(thumbplate_cutspace, color=webcol_to_cq(\"#1166ff33\")) \n", + " #.add(pcb_cutouts, color=webcol_to_cq(\"#aa44ff99\")) \n", + " #.add(thumb_cutout, color=webcol_to_cq(\"#ff66ff33\"))\n", + " #.add(handrest_cutout, color=webcol_to_cq(\"#aa110022\")) \n", + " .add(thumb_handrest_cutout, color=webcol_to_cq(\"#aa440022\"))\n", + " #.add(thumb_overhang_cutout, color=webcol_to_cq(\"#aa44ff99\"))\n", + " #.add(thumb_pcb_cutout, color=webcol_to_cq(\"#aa44ff99\"))\n", + " #.add(buttonplate, color=webcol_to_cq(\"#aa44ff99\"))\n", + " #.add(raw_buttonplate, color=webcol_to_cq(\"#aa44ff99\"))\n", + " #.add(hole1, color=webcol_to_cq(\"#ffa22aa44\"))\n", + " #.add(hole2, color=webcol_to_cq(\"#ffa22aa44\"))\n", + " #.add(holes, color=webcol_to_cq(\"#ffa22aa44\"))\n", + " #.add(thumbholes, color=webcol_to_cq(\"#ffa22aa44\"))\n", + " #.add(thumb_switches, color=webcol_to_cq(\"#00000055\"))\n", + " #.add(thumb_caps, color=webcol_to_cq(\"#efefef55\"))\n", + " #.add(buttonplate_bridge, color=webcol_to_cq(\"#ffaa4499\"))\n", + " #.add(overhang_cutout, color=webcol_to_cq(\"#aa44ff22\"))\n", + " , axes=True, axes0=True\n", + " , ambient_intensity=1, theme=\"dark\", reset_camera=False\n", + " ,tools=True, render_normals=False)\n", + "\n", + "testrender()" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "3db7cb62-4476-48d9-a12c-b3efb73d6095", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "#base = build_base()\n", + "#handrest_top = (base.faces(\"X\")\n", + "# )\n", + "\n", + "#dowel = (handrest_top.faces(\" 2\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 3\u001b[0m ticc \u001b[38;5;241m=\u001b[39m timeit\u001b[38;5;241m.\u001b[39mdefault_timer()\n\u001b[1;32m 4\u001b[0m base1 \u001b[38;5;241m=\u001b[39m base \u001b[38;5;241m+\u001b[39m buttonplate\u001b[38;5;241m.\u001b[39mtranslate([\u001b[38;5;241m0\u001b[39m,\u001b[38;5;241m0\u001b[39m,\u001b[38;5;241m74\u001b[39m]) \u001b[38;5;241m+\u001b[39m switches\u001b[38;5;241m.\u001b[39mtranslate([\u001b[38;5;241m0\u001b[39m,\u001b[38;5;241m0\u001b[39m,\u001b[38;5;241m130\u001b[39m])\n", + "\u001b[0;31mAssertionError\u001b[0m: " + ] + } + ], + "source": [ + "#base = build_base()\n", + "assert False\n", + "ticc = timeit.default_timer()\n", + "base1 = base + buttonplate.translate([0,0,74]) + switches.translate([0,0,130])\n", + "base1 = base1 + thumb_switches.translate([0,0,130]) +thumb_caps.translate([0,0,180])+ caps.translate([0,0,180])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ad8fe33-18c5-4fa2-834d-bab85827bceb", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "hidden_color = (0, 255, 255)\n", + "stroke_color = (255, 0, 255)\n", + "stroke_color = (0xc7, 0xf8, 255)\n", + "#c7f8ffff\n", + "cq.exporters.export(\n", + " base1,\n", + " basepath+\"/smue_explode.svg\",\n", + " opt={\n", + " \"width\": 300,\n", + " \"height\": 300,\n", + " \"marginLeft\": 10,\n", + " \"marginTop\": 10,\n", + " \"showAxes\": False,\n", + " \"projectionDir\": (100, -20, 70),\n", + " \"strokeWidth\": 0.25,\n", + " \"strokeColor\": stroke_color,\n", + " \"hiddenColor\": hidden_color,\n", + " \"showHidden\": False,\n", + " },\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "201bab5c-213a-498f-9241-1077343430ce", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "toc = timeit.default_timer()\n", + "runtime = toc-ticc\n", + "total_runtime = toc -tic\n", + "print(f\"svg export runtime: {runtime:2.2f}s\")\n", + "print(f\"total runtime: {total_runtime:2.2f}s\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "28f7ea15-de5d-4f03-b7cd-3a6930593144", + "metadata": {}, + "source": [ + "# real finger tip trajectory instead of circles" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "153074b2-8e5c-44e0-b34d-bfbe7ac1b749", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "assert False\n", + "\n", + "\n", + "from numpy import cos, sin, linspace, pi\n", + "import matplotlib.pyplot as plt\n", + "from math import atan2\n", + "\n", + "\n", + "# define finger segment sizes\n", + "40+20+18\n", + "50+40+20\n", + "proximal_phalange = 40\n", + "intermediate_phalange = 20\n", + "distal_phalange = 18\n", + "phi_start = pi/4 #45deg\n", + "phi_end = pi/9 #10deg\n", + "\n", + "phi = linspace(phi_start, phi_end)\n", + "\n", + "x_proximal = -proximal_phalange * cos(phi) \n", + "y_proximal = - proximal_phalange * sin(phi)\n", + "\n", + "x_intermediate = -intermediate_phalange * cos(2*phi)\n", + "y_intermediate = -intermediate_phalange * sin(2*phi)\n", + "\n", + "x_distal = -distal_phalange * cos(3*phi)\n", + "y_distal = -distal_phalange * sin(3*phi)\n", + "\n", + "x = x_proximal + x_intermediate + x_distal\n", + "y = y_proximal + y_intermediate + y_distal\n", + "\n", + "end_x_dir = x[0] - x[1]\n", + "end_y_dir = y[0] - y[1]\n", + "rho = atan2(end_y_dir, end_x_dir)\n", + "x = x - x[0]\n", + "y = y - y[0]\n", + "\n", + "apts = np.vstack([x, y]).T\n", + "lengths = np.sqrt(np.sum(np.diff(apts, axis=0)**2, axis=1)) # Length between corners\n", + "total_length = np.sum(lengths)\n", + "pts = [tuple(pt) for pt in apts]\n", + "print(f\"{total_length = :.2f}mm {rho = }\")\n", + "plt.plot(x,y)\n", + "\n", + "path = (cq.Workplane(\"XZ\")\n", + " .polyline(pts)\n", + ")\n", + "\n", + "part = ( cq.Workplane(\"YZ\").rotateAboutCenter([0, 1, 0], rho*2*pi)\n", + " .rect(column_width, plate_thickness)\n", + " #.sweep(path)\n", + ")\n", + "show(part, path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed64e437-0ba6-4957-b8fb-af831e742f55", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "path = (cq.Workplane(\"XZ\")\n", + " .polyline(pts)\n", + ")\n", + "\n", + "part = ( cq.Workplane(\"YZ\").rotateAboutCenter([0, 1, 0], rho*2*pi)\n", + " .rect(column_width, plate_thickness)\n", + " #.sweep(path)\n", + ")\n", + "show(part, path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b50622c-c504-4c87-94de-c9493d59d402", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "pts = [(0,0), (0,1), (3,1), (3,0)] # Corners of rectangle of height 1, length 3\n", + "apts = np.array(pts) # Make it a numpy array\n", + "lengths = np.sqrt(np.sum(np.diff(apts, axis=0)**2, axis=1)) # Length between corners\n", + "total_length = np.sum(lengths)\n", + "apts" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + }, + "name": "smue2.ipynb" + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/v2_SyncGrips.ipynb b/v2_SyncGrips.ipynb new file mode 100644 index 0000000..de8ef3d --- /dev/null +++ b/v2_SyncGrips.ipynb @@ -0,0 +1,220 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "667ffcc6-563f-442b-9726-e15a2a45c01f", + "metadata": {}, + "source": [ + "# SyncGrips v2" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c8a0e581-2963-424c-a258-f1de92180674", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting auto display for cadquery Workplane and Shape\n" + ] + } + ], + "source": [ + "import timeit\n", + "tic = timeit.default_timer()\n", + "\n", + "import cadquery as cq\n", + "from build123d import *\n", + "\n", + "from jupyter_cadquery import (\n", + " versions,\n", + " show, PartGroup, Part, \n", + " get_viewer, close_viewer, get_viewers, close_viewers, open_viewer, set_defaults, get_defaults, open_viewer,\n", + " get_pick,\n", + ")\n", + "from jupyter_cadquery.ocp_utils import webcol_to_cq\n", + "\n", + "from jupyter_cadquery.replay import replay, enable_replay, disable_replay\n", + "close_viewers()\n", + "set_defaults(\n", + " cad_width=640, \n", + " height=480, \n", + " viewer=\"syncgrips_v2\"\n", + ")\n", + "cv = open_viewer(\"syncgrips_v2\",\n", + " anchor=\"right\", \n", + " glass=True, \n", + " theme=\"dark\",\n", + " tools=True) # sets default viewer\n" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "e694a533-04eb-4783-84ca-faf15d983361", + "metadata": {}, + "outputs": [], + "source": [ + "sensor_width = 35\n", + "r_sens = 6\n", + "wall_thickness = 3\n", + "t = 2.5 # sheet thickness\n", + "w1 = sensor_width + 2* wall_thickness #sensor casing\n", + "w2 = 150 #sensors center placed at corners of w2*w2 square\n", + "h = 85 # total height\n", + "h1 = 20 # sensor casing height\n", + "h2 = 12\n", + "h3 = 35# sensor casing + mpu height\n", + "r = 25 # grip radius\n", + "w3 = 20 # upper mount dx \n", + "r1 = 8 # bending radius\n", + "locs = ((0,h+10), (-10,h-10),(10,h-10)) #locations of screws\n", + "handle_len = w2+w1\n", + "l = w2/2+sensor_width+r1+5 # half of the length" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "2461ad9b-3cf5-4517-9b66-cee2e0bc5b5b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100% ⋮————————————————————————————————————————————————————————————⋮ (3/3) 0.23s\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 108, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with BuildPart() as part_builder:\n", + " with BuildSketch() as profile_sketch:\n", + " with BuildLine() as line_builder:\n", + " line = Polyline((-w3, h+r/2),(-w3, h-r),(-l, 0),\\\n", + " (l,0),(w3,h-r), (w3,h+r/2), close=False)\n", + " line1 = fillet(radius=r1,objects=line.vertices())\n", + " w = offset(amount=t) \n", + " make_face()\n", + " extrude(amount=w1)\n", + " \n", + "with BuildPart() as holes:\n", + " with BuildSketch(part_builder.faces().filter_by(Plane.YZ)[0]) as hole_sk:\n", + " dh = 10\n", + " dl = 5\n", + " with Locations((-dl,-dh),(dl,dh),(-dl,dh),(dl,-dh)):\n", + " Circle(2.05)\n", + " #SlotCenterToCenter(4,4, rotation=90)\n", + " extrude(amount=-50, both=True)\n", + " \n", + "with BuildPart() as handle:\n", + " with BuildSketch(Plane.XY) as profile_sk:\n", + " with Locations((0,h)):\n", + " Circle(r)\n", + " extrude(amount=handle_len)\n", + " ch = r-w3+4\n", + " with Locations((w3+ch/2-t,h,w1/2), (-w3-ch/2+t,h,w1/2), (w3+ch/2-t,h,w2+w1/2), (-w3-ch/2+t,h,w2+w1/2)):\n", + " Box(ch, r*2, w1, mode=Mode.SUBTRACT)\n", + " add(holes, mode=Mode.SUBTRACT)\n", + " \n", + "with BuildPart() as sensors:\n", + " dz = -5\n", + " ds = 4\n", + " with Locations((w2/2+ds,dz,w1/2), (w2/2+ds,dz,w2+w1/2)):\n", + " #Box(w1, 5, w1, mode=Mode.ADD)\n", + " add(sensor_cutout)\n", + " with Locations(((-w2/2-ds,dz,w1/2),(-w2/2-ds,dz,w2+w1/2))):\n", + " add(sensor_cutout.part.mirror(Plane.YZ))\n", + " \n", + "upper_sheet = part_builder.part - holes.part - sensors.part\n", + "lower_sheet = Location((0,0,handle_len-w1))*upper_sheet\n", + "show(upper_sheet, lower_sheet,handle.part,reset_camera=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "74b1b118-4c15-4e03-a029-670d8198db72", + "metadata": {}, + "outputs": [], + "source": [ + "part_builder.faces().filter_by(Plane.YZ)[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "id": "6fe18aef-3215-43e4-8d00-1ffd45a44f1d", + "metadata": {}, + "outputs": [], + "source": [ + "with BuildPart() as sensor_cutout:\n", + " with BuildSketch(Plane.XZ) as sensor_sk1:\n", + " d = 0\n", + " #Rectangle(22,26)\n", + " \n", + " sensor_space = extrude(amount=-14)\n", + " faces = (sensor_space.edges().group_by(Axis.Y)[-1] | Axis.Z)\n", + " #chamfer(objects=faces, length=6)\n", + " \n", + "with BuildPart() as sensorhole:\n", + " with BuildSketch(Plane.XZ) as sensorhole_sk: \n", + " with Locations((-sensor_width/2,0)):\n", + " Circle(1.55)\n", + " extrude(amount=-14)" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "id": "ffef54a9-57a6-4477-9836-2c770b451182", + "metadata": {}, + "outputs": [], + "source": [ + "sensor_cutout.part + sensor_cutout.part.mirror(Plane.YZ)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80097045-32aa-4744-a56a-9892e60d361a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}