Compare commits

73 Commits

Author SHA1 Message Date
0335944118 Proper fillet 2023-03-31 10:10:18 -03:00
80a027b8f1 Move keyboard up 2mm 2023-03-31 09:59:20 -03:00
222240c259 Adjust hole position 2023-03-31 09:58:38 -03:00
a5274c0534 Fillet properly 2023-03-30 17:47:43 -03:00
d5f4a1f358 Added hole for HDMI out 2023-03-30 10:24:33 -03:00
f1521523e6 Keyboard mount was wrong, it worked because of looser tolerances in previous version 2023-03-30 09:36:15 -03:00
2dc9510a04 Move screen pillars a little back (interference with keyboard 2023-03-30 09:34:24 -03:00
682fb38852 Make all screen pillar holes come from the bottom 2023-03-29 09:40:43 -03:00
9bff855ad7 Vents a bit more sturdy 2023-03-29 09:25:39 -03:00
3203c4c860 account for shell when positioning the keyboard 2023-03-29 09:25:10 -03:00
3b1722f5ff Change dimensions of kbd pillars 2023-03-29 09:05:10 -03:00
6e594b0b86 Adjust screen mount pillars 2023-03-28 11:33:40 -03:00
74a17e04ec Adjust screen mount so it works with new base 2023-03-28 11:20:34 -03:00
0a2bba4067 Add 2 more pillars, adjust component position 2023-03-28 11:15:41 -03:00
68b407f5b5 oops 2023-03-28 10:46:48 -03:00
f5bf232f0e Reworked keyboard code so it's more correct and general 2023-03-28 10:36:58 -03:00
bf302deab1 Reordered components so kbd doesn't interfere with other things 2023-03-27 17:15:49 -03:00
c49f9c6348 Fixed hexagon vents, unified punch_hole / extrude_shape APIs 2023-03-27 16:57:55 -03:00
68849b5aca Make usb_offset nicer 2023-03-27 11:23:57 -03:00
2553525623 Refactored kbd into module 2023-03-21 15:48:36 -03:00
61a723c713 Missing file 2023-03-21 10:56:17 -03:00
d1e7b589c8 Refactored pillars to join screen (adds concept of screw holes) 2023-03-21 10:21:04 -03:00
cdf2e33f64 Refactored usb hub module 2023-03-20 15:04:19 -03:00
9bdabfdd98 Refactor audio plug module 2023-03-20 13:19:14 -03:00
9904a412a3 Refactor hex vents into helper function 2023-03-15 11:17:39 -03:00
d64654f7b1 Added hole support for CPU holder 2023-03-15 10:46:36 -03:00
4f1b09ab95 Refactor zero_holder to match battery_holder, added vents 2023-03-15 10:15:40 -03:00
d9749bc38e Refactor battery_holder with single add entry point 2023-03-14 14:42:01 -03:00
7aeb2cc0c1 Fancy battery holder design 2023-03-08 17:24:43 -03:00
4037ac4a1a comment 2023-03-01 16:15:05 -03:00
b3f813eaf1 lint 2023-03-01 16:13:57 -03:00
576edc839b Massive refactor that changes nothing in the shape of the case :-) 2023-03-01 15:47:06 -03:00
120638f50f Remove dead code 2023-02-24 16:00:29 -03:00
d9f7d7f7a9 Tweaks in screen mount so it has some rounder edges and slightly smaller viewport 2023-02-24 15:59:01 -03:00
0dd1c858e1 lint 2023-02-24 10:46:19 -03:00
6ea773109b Fix minor issues with holes positions and shapes 2023-02-24 10:44:35 -03:00
b5deb70cbd Rearrange right side to put USB plug in the back and audio on the side 2023-02-23 13:20:49 -03:00
7fbf323e45 CPU and battery stands with holes for screws 2023-02-23 10:08:57 -03:00
84f5978454 USB hub holder + parametrization 2023-02-22 18:17:02 -03:00
a894e8319c Adjust battery holder parameters 2023-02-22 15:02:10 -03:00
af3e1824f4 Integrate pogo plug receptacle into base 2023-02-22 14:17:29 -03:00
67db8765d8 lint 2023-02-22 12:20:36 -03:00
e307750c8d New audio plug holes, moved USB hub plug so that's doable 2023-02-22 11:25:50 -03:00
804f012b19 Fix size in pogo plug 2023-02-17 17:59:02 -03:00
2baebe8895 Added pogo plug connector 2023-02-17 17:55:20 -03:00
8d962eba14 Cadquery 2 2023-02-17 14:32:01 -03:00
d4e309f081 Battery and CPU holder adjustments 2023-02-17 10:08:05 -03:00
424396a266 Adjust battery holder dimensions 2023-02-16 15:58:30 -03:00
9840e6ad70 Make base taller so battery fits 2023-02-16 15:08:21 -03:00
ccb3220b1b Tweak battery positioning 2023-02-16 15:00:40 -03:00
31630ab1b0 lint 2023-02-16 14:57:51 -03:00
64db220a46 Integrated Battery holder 2023-02-16 14:57:32 -03:00
4624a2531f Integrated CPU holder 2023-02-16 14:42:31 -03:00
be6245aa3b Removed cable hole in screen mount
This cut is not needed with the lower keyboard position.
2023-02-01 10:28:58 -03:00
4b0cf1cfa0 Cleanup and adjustments for rev2
* Removed holes that make no sense anymore
* Made case shorter to avoid unsightly gap between screen and keyboard
* Added comments
2023-02-01 10:21:29 -03:00
6eb3eba4f2 Re-add back strip so mounting pillars print easier 2023-01-30 18:01:50 -03:00
dbaa9832f8 Move USB hole up a little 2023-01-30 16:09:48 -03:00
76af86ac70 Added 6th screen mounting pillar, improved kbd cable hole placement and screen mounting code 2023-01-30 10:57:33 -03:00
6cbd7e2b15 Tweaks 2023-01-27 17:01:37 -03:00
459021ff83 Playing with screen mount parameters, angle to 20 degrees 2023-01-27 16:11:28 -03:00
4f2c1e35f6 Redesigned screen holder for 30 degree screen 2023-01-27 15:43:54 -03:00
1e89b41995 Redesigned screen holder for 30 degree screen 2023-01-27 15:32:04 -03:00
37d96b0e4f Bring the keyboard 1.5 mm lower INTO the case 2023-01-27 13:16:09 -03:00
b85ba1f3e1 2-level kbd mounting pillars 2023-01-27 11:23:04 -03:00
a6d3f32797 larger hole for kbd mounting screws 2023-01-27 11:16:40 -03:00
92291013c6 Smaller kbd pillars, fix base splitting 2023-01-26 15:11:16 -03:00
d048e19cec Starting rev 2
* Adjusted sizes and other parameters for the new 65% keyboard
* Commented battery holder (waiting on the 18650 charger)
2023-01-26 11:08:58 -03:00
bebc4d5729 Move keyboard cable hole 6mm to the left 2022-12-02 11:26:52 -03:00
26dc83baf9 Larger kbd cable hole makes shape simpler 2022-12-02 10:05:27 -03:00
29e2464bef Bring back missing screen mounting pillar 2022-12-02 10:04:23 -03:00
4386b1c017 Split the screen mount so it needs fewer supports 2022-12-01 15:20:57 -03:00
8058118491 Slightly larger hole for power switch 2022-12-01 12:29:33 -03:00
4279547773 Refactor screen_mount script a bit 2022-11-30 14:29:42 -03:00
25 changed files with 894 additions and 53407 deletions

View File

@ -0,0 +1,50 @@
# Hole to expose a USB audio card (YMMV)
import cadquery as cq
from utils import punch_hole
# The hole is for a random USB sound card.
# Consumers should set proper offsets for the hole
holes = [
# 2-jack plug
{
"x": 0,
"y": 4,
"shape": cq.Sketch().trapezoid(17, 5, 90, mode="a").vertices().fillet(2),
},
]
def add(
*,
model,
width,
height,
thickness,
offset_x,
offset_y,
bottom_face,
back_face,
shell_t
):
# Holes
if back_face:
for hole in holes:
model = punch_hole(
model=model,
face=back_face,
# FIXME: This is weird because it's the RIGHT side,
# So it's height instead of w
# need to work on making these coherent
w=height,
h=thickness,
x_offset=offset_x - 17 / 2,
y_offset=shell_t,
hole=hole,
depth=shell_t,
)
return model

View File

@ -0,0 +1,151 @@
import cadquery as cq
from utils import extrude_shape, punch_hole, hex_vents
stand_positions = [(3.5, 3.5), (61.5, 3.5), (61.5, 52.5), (3.5, 52.5)]
stands = (
cq.Sketch().push(stand_positions).circle(3, mode="a").circle(2.65 / 2, mode="s")
)
pillar_height = 7
width = 85
height = 56
# This is a holder for DuPont cables so they connect to this
# things' pogo pins which are used to power the CPU
pin_positions = [(3.5, 0), (4 * 2.54 + 3.5, 0)]
pin_holder_width = 25
pin_holder_height = 15
pin_holder = (
cq.Sketch()
.polygon(
[
(0.5, 0),
(pin_holder_width, 0),
(pin_holder_width, pin_holder_height),
(0, pin_holder_height),
(0.5, 0),
],
mode="a",
)
.push(pin_positions)
.polygon(
[(0, 0), (2.6, 0), (2.6, pin_holder_height), (0, pin_holder_height), (0, 0)],
mode="s",
)
)
elements = [
# Battery holder stands
{
"x": 0,
"y": 0,
"shape": stands,
"height": pillar_height,
},
{
"x": 0,
"y": 0,
"shape": cq.Sketch().push(stand_positions).circle(5),
"height": 0,
},
# Pogo pin connector channels
{
"x": 3.5,
"y": 43.5,
"shape": pin_holder,
"height": 3,
},
# Perimeter
{
"x": width / 2,
"y": height / 2,
"shape": (
cq.Sketch()
.trapezoid(width, height, 90, mode="a")
.trapezoid(width - 2, height - 2, 90, mode="s")
.vertices()
.fillet(3)
),
"height": 0.2,
},
]
vents = hex_vents(size=3, width=width, height=height)
# Hole distances are relative to the rightmost pillar
# seen from the back of the case, that's why they are negative
# Heights are relative to base of pillars
# All distances are measured to the CENTER of the hole
holes = [
# Power inlet
{
"x": -18,
"y": -1 + pillar_height,
"shape": cq.Sketch().trapezoid(12, 6.5, 90, mode="a").vertices().fillet(1),
},
# Power button
{
"x": -70,
"y": 5.5 + pillar_height,
"shape": cq.Sketch().trapezoid(7, 7, 90, mode="a").vertices().fillet(1),
},
]
def add(
*,
model,
width,
height,
thickness,
offset_x,
offset_y,
bottom_face,
back_face,
shell_t
):
if bottom_face:
# Vents
for vent in vents:
model = punch_hole(
model=model,
face=bottom_face,
w=width,
h=height,
x_offset=offset_x + vent["x"],
y_offset=shell_t + offset_y + vent["y"],
hole=vent,
depth=shell_t,
)
# Battery holder stands and pogo pin holder
for element in elements:
model = extrude_shape(
model=model,
face=bottom_face,
w=width,
h=height,
x_offset=offset_x,
y_offset=shell_t + offset_y,
element=element,
height=-(element["height"] + shell_t),
)
if back_face:
# Holes
for hole in holes:
model = punch_hole(
model=model,
face=back_face,
w=width,
h=thickness,
x_offset=width - offset_x,
y_offset=shell_t,
hole=hole,
depth=shell_t,
)
return model

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,28 +0,0 @@
import cadquery2 as cq
from cadquery2 import exporters
lower_stands = (
cq.Sketch().push([(0, 0), (58, 0), (58, 23), (0, 23)]).circle(3, mode="a")
)
higher_stands = (
cq.Sketch().push([(0, 0), (58, 0), (58, 23), (0, 23)]).circle(2.65 / 2, mode="a")
)
model = (
cq.Workplane("XY")
.workplane()
.box(75, 40, 2)
.edges("+Z")
.fillet(3)
.faces(">Z")
.workplane(centerOption="CenterOfBoundBox")
.center(-29, -11.5)
.placeSketch(lower_stands)
.extrude(4)
.workplane()
.placeSketch(higher_stands)
.extrude(9)
)
exporters.export(model, "cpu_holder.stl")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
# Hole to expose a USB audio card (YMMV)
import cadquery as cq
from utils import punch_hole
# The hole is for a random USB sound card.
# Consumers should set proper offsets for the hole
holes = [
# Hole for HDMI female adapter
{
"x": 0,
"y": 7,
"shape": cq.Sketch().trapezoid(22, 12.5, 90, mode="a").vertices().fillet(2),
},
]
def add(
*,
model,
width,
height,
thickness,
offset_x,
offset_y,
bottom_face,
back_face,
shell_t
):
# Holes
if back_face:
for hole in holes:
model = punch_hole(
model=model,
face=back_face,
w=width,
h=thickness,
x_offset=width - offset_x,
y_offset=shell_t,
hole=hole,
depth=shell_t,
)
return model

103
notebook_nueva/keyboard.py Normal file
View File

@ -0,0 +1,103 @@
import cadquery as cq
import math
# Size of the kbd board
kbd_height = 95.5
kbd_width = 305
back_thickness = 19
front_thickness = 12
# Pythagoras
actual_height = (kbd_height**2 - (back_thickness - front_thickness) ** 2) ** 0.5
kbd_angle = math.acos(actual_height / kbd_height) * 180 / math.pi
kbd_pillar_positions = [
(19, 16),
(142.5, 25.5),
(kbd_width - 20, 16),
(23.5, 79.5),
(145.5, 82.5),
(kbd_width - 19, 79.5),
]
elements = [
# Shorter pillars
{
"x": 0,
"y": 0,
"z": 5.5,
"shape": cq.Sketch().push(kbd_pillar_positions).circle(5, mode="a"),
},
# Taller pillars with holes for self-tapping screws
{
"x": 0,
"y": 0,
"z": 2.5,
"shape": (
cq.Sketch()
.push(kbd_pillar_positions)
.circle(2.4, mode="a")
.circle(1.1, mode="s")
),
},
]
def add(
*,
model,
width,
height,
thickness,
offset_x,
offset_y,
bottom_face,
back_face,
shell_t
):
# This one is special, it creates angled things and cuts off the
# case, so ... it's going to do weird stuff
if bottom_face:
model = (
model.faces(bottom_face)
.workplane(centerOption="CenterOfBoundBox", offset=-front_thickness)
.center(
-width / 2,
height / 2,
)
.transformed(rotate=cq.Vector(kbd_angle, 0, 0))
.tag("kbd_sloped")
)
for element in elements:
model = (
model.workplaneFromTagged("kbd_sloped")
.center(offset_x + element["x"], -offset_y - element["y"])
.workplane(offset=element["z"])
.placeSketch(element["shape"])
.extrude(100)
)
model = (
model.workplaneFromTagged("mid_height")
.transformed(offset=cq.Vector(0, 0, -thickness / 2))
.split(keepTop=True)
.faces(">X")
.workplane(centerOption="CenterOfBoundBox")
.center(-height / 2, -thickness / 2)
.placeSketch(
cq.Sketch().polygon(
[
[0, front_thickness],
[actual_height, back_thickness],
[actual_height, 1000],
[0, 1000],
[0, front_thickness],
]
)
)
.cutBlind(-1000)
)
return model

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,13 @@
import cadquery2 as cq
from cadquery2 import exporters
import cadquery as cq
from cadquery import exporters
import audio_plug
import battery_holder
import hdmi_out
import keyboard
import screen_pillars
import usb_hub
import zero_holder as cpu_holder
# Base for the notebook. Basically a kbd base that extends back
# as much as possible
@ -8,88 +16,54 @@ from cadquery2 import exporters
shell_t = 3
# Size of the kbd board
kbd_height = 98
kbd_width = 286
kbd_height = 95.5
kbd_width = 305
kbd_angle = 5
# Size of the whole object
width = kbd_width + 2 * shell_t
height = 164 # Max bed size
thickness = 20 + shell_t # 20 inside
height = 159
thickness = 27 + shell_t # 27 inside
# Insert Positions
ti_radius = 2.35
ti_depth = 6.25
# Positions are determined by measuring the keyboard
# mounting holes
kbd_pillars = (
cq.Sketch()
.push(
[
(19, -16.5),
(133, -16.5),
(247.5, -16.5),
(24, -86),
(142.5, -91),
(261.5, -86),
]
)
.circle(6, mode="a")
# Holes for M3 threaded inserts
.circle(ti_radius, mode="s")
)
# These are placed where convenient, and are used to join the top and bottom
# parts of the case.
# Measured from top-left corner OUTSIDE
mounting_pillar_positions = [
(6, -6),
(width - 6, -6),
(width - 6, -40),
(120, -6),
(170, -6),
(6, 6),
(6, 48),
(120, 6),
(170, 6),
(width - 6, 6),
(width - 6, 30),
(120, 48),
(170, 48),
]
screen_pillars.init(mounting_pillar_positions, thickness - shell_t)
mounting_pillars = (
cq.Sketch()
.push(mounting_pillar_positions)
.trapezoid(12, 12, 90, mode="a")
.circle(1.8, mode="s")
)
screw_holes = cq.Sketch().push(mounting_pillar_positions).circle(3, mode="a")
battery_holder = (
cq.Sketch()
.polygon(
[(-67, 5), (0, 5), (0, -12), (-67, -12), (-67, 5)],
mode="a",
)
.trapezoid(83, 83, 90, mode="a")
.trapezoid(80, 80, 90, mode="s")
.polygon(
[(-67, 3), (0, 3), (0, -10), (-67, -10), (-67, 3)],
mode="s",
)
# Cutout for the
.polygon(
[(-67, 30), (0, 30), (0, 12), (-67, 12), (-67, 30)],
mode="s",
)
)
# Thing to "grab" the hub so it stays in place
# Distance from left edge to center of USB plug
usb_offset = width - 48
power_in = cq.Sketch().circle(5, mode="a")
usb_in = cq.Sketch().trapezoid(13, 5.5, 90, mode="a")
switch_in = cq.Sketch().trapezoid(13.5, 8, 90, mode="a")
# CPU holder position from back-left corner of the case
cpu_offset_x = 180
cpu_offset_y = 3
# Battery holder position from back-left corner of the case
battery_offset_x = 15
battery_offset_y = 3
# Motherboard mount
# Offset for the USB port from back-right corner of the case
usb_offset = 48
def model():
return (
# Create the basic shape of the case bottom.
model = (
cq.Workplane("XY")
.workplane(offset=thickness / 2)
.tag("mid_height")
@ -99,71 +73,112 @@ def model():
.fillet(2)
.faces(">Z")
.shell(-shell_t)
# Battery holder
.workplaneFromTagged("mid_height")
.center(-width / 2 + shell_t + 65, height / 2 - shell_t - 45)
.placeSketch(battery_holder)
.extrude(-height / 2)
# Power cable inlet
.faces("<X")
.workplane(centerOption="CenterOfBoundBox")
.center(-height / 2 + shell_t + 48.5, -3)
.placeSketch(power_in)
.cutBlind(-shell_t)
# USB inlet
.faces(">X")
.workplane(centerOption="CenterOfBoundBox")
.center(-height / 2 + shell_t + 50, -5)
.placeSketch(usb_in)
.cutBlind(-shell_t)
# Hole for power switch
.faces(">Y")
.workplane(centerOption="CenterOfBoundBox")
.center(0, 0)
.placeSketch(switch_in)
.cutBlind(-shell_t)
# Slanted mounting pillars on the kbd top
.faces(">Z")
.workplane(centerOption="CenterOfBoundBox")
# Top-left kbd corner inside the box
.center(-width / 2 + shell_t, kbd_height - height / 2 + shell_t)
.transformed(rotate=cq.Vector(kbd_angle, 0, 0))
.tag("sloped")
.placeSketch(kbd_pillars)
.extrude(-1000)
# Remove the excess extrusion
.workplaneFromTagged("mid_height")
.transformed(offset=cq.Vector(0, 0, -thickness / 2))
.split(keepTop=True)
# Slope for the beyboard
.workplaneFromTagged("sloped")
.split(keepBottom=True)
# Pillars to join with top half
.workplaneFromTagged("mid_height")
.workplane(offset=-thickness / 2, centerOption="CenterOfBoundBox")
.center(-width / 2, height / 2)
.placeSketch(mounting_pillars)
.extrude(thickness)
# Holes to insert screws from the bottom
.workplaneFromTagged("mid_height")
.workplane(offset=-thickness / 2, centerOption="CenterOfBoundBox")
.center(-width / 2, height / 2)
.placeSketch(screw_holes)
# 13 is 20-7 (screw thread length - threaded insert depth)
.cutBlind(thickness - 13)
)
# Now the basic box shape is in place, start adding things
# and cutting holes.
model = usb_hub.add(
model=model,
width=width,
height=height,
thickness=thickness,
bottom_face="<Z",
back_face=">Y",
offset_x=width - usb_offset,
offset_y=0,
shell_t=shell_t,
)
# Hole for audio in right side
model = audio_plug.add(
model=model,
width=width,
height=height,
thickness=thickness,
offset_x=111, # Offset from the front-left corner
offset_y=0, # Offset from the bottom
bottom_face=None,
back_face=">X",
shell_t=shell_t,
)
# Hole for HDMI out in the back
model = hdmi_out.add(
model=model,
width=width,
height=height,
thickness=thickness,
offset_x=138,
offset_y=0,
bottom_face=None,
back_face=">Y",
shell_t=shell_t,
)
model = cpu_holder.add(
model=model,
width=width,
height=height,
thickness=thickness,
offset_x=cpu_offset_x,
offset_y=cpu_offset_y,
bottom_face="<Z",
back_face=None, # Not exposing the holes
shell_t=shell_t,
)
# This adds all the holes and extrusions for the battery system
model = battery_holder.add(
model=model,
width=width,
height=height,
thickness=thickness,
offset_x=battery_offset_x,
offset_y=battery_offset_y,
bottom_face="<Z",
back_face=">Y",
shell_t=shell_t,
)
model = screen_pillars.add(
model=model,
width=width,
height=height,
thickness=thickness,
offset_x=0,
offset_y=0,
bottom_face="<Z",
back_face=None,
shell_t=shell_t,
)
model = keyboard.add(
model=model,
width=width,
height=height,
thickness=thickness,
bottom_face="<Z",
back_face=None,
offset_x=shell_t,
offset_y=kbd_height + shell_t,
shell_t=shell_t,
)
return model
if __name__ == "__main__":
model = model()
left_cutout = cq.Sketch().polygon(
[(0, 0), (160, 0), (160, -100), (135, -100), (135, -200), (0, -200), (0, 0)],
[(0, 0), (width / 2, 0), (width / 2, -height), (0, -height), (0, 0)],
mode="a",
)
right_side = (
model()
.faces("<Z")
model.faces("<Z")
.workplaneFromTagged("mid_height")
.transformed(offset=cq.Vector(0, 0, -thickness / 2))
.center(-width / 2, height / 2)
@ -175,20 +190,17 @@ if __name__ == "__main__":
right_cutout = cq.Sketch().polygon(
[
(160, 0),
(width / 2, 0),
(width, 0),
(width, -height),
(135, -height),
(135, -100),
(160, -100),
(160, 0),
(width / 2, -height),
(width / 2, 0),
],
mode="a",
)
left_side = (
model()
.faces("<Z")
model.faces("<Z")
.workplaneFromTagged("mid_height")
.transformed(offset=cq.Vector(0, 0, -thickness / 2))
.center(-width / 2, height / 2)
@ -197,4 +209,4 @@ if __name__ == "__main__":
)
exporters.export(left_side, "left_side.stl")
exporters.export(model(), "model.stl")
exporters.export(model, "model.stl")

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
import cadquery2 as cq
from cadquery2 import exporters
import cadquery as cq
from cadquery import exporters
from modelo import (
kbd_height,
@ -10,17 +10,16 @@ from modelo import (
ti_radius,
)
ti_radius = 2.5
# Size of the whole object
width = kbd_width + 2 * shell_t
height = 59
thickness = 62 # Will be shorter after construction
height = 66
height_bottom = 59
thickness = 48 # Will be shorter after construction
# Visible screen size
vis_w = 220
vis_h = 58
viewport_cutout = cq.Sketch().trapezoid(vis_w, vis_h, 90, mode="a")
vis_w = 219
vis_h = 55
viewport_cutout = cq.Sketch().trapezoid(vis_w, vis_h, 90, mode="a").vertices().fillet(2)
# Whole screen size
scr_w = 231
@ -28,6 +27,9 @@ scr_h = 65
scr_thickness = 5.5
screen_cutout = cq.Sketch().trapezoid(scr_w, scr_h, 90, mode="a")
# Screen angle
scr_angle = 20
# Circuit board and cable hole.
# This is in the back of the screen, and is a bit shorter in height than the
# screen. It's wider so it removes enough material to make the shape simpler.
@ -38,13 +40,17 @@ board_cutout = cq.Sketch().trapezoid(
mode="a",
)
kbd_cable_hole = cq.Sketch().trapezoid(15, 5, 90, mode="a").vertices().fillet(1)
kbd_cable_hole = cq.Sketch().trapezoid(20, 9, 90, mode="a").vertices().fillet(1)
# y needs to be inverted because this is the top side, adn there's 2 pillars we don't use
mounting_pillar_positions = [(x, -y) for x, y in mounting_pillar_positions[:-2]]
mounting_pillars = (
cq.Sketch()
.polygon([(0, 0), (width, 0), (width, -12), (0, -12), (0, 0)], mode="a")
.push(mounting_pillar_positions)
.trapezoid(-12, 12, 90, mode="a")
.circle(ti_radius, mode="s")
.clean()
)
@ -54,9 +60,9 @@ def model():
.workplane()
.tag("mid_height")
.box(width, height, thickness)
# The screen goes at a 45 degree angle
# The screen goes rotated
.faces(">Z")
.transformed(rotate=(45, 0, 0))
.transformed(rotate=(scr_angle, 0, 0))
# Move the screen "lower" so it doesn't interfere
# so much with the back
.center(0, -2)
@ -65,6 +71,35 @@ def model():
# of the inclined screen
.placeSketch(cq.Sketch().trapezoid(1000, 1000, 90, mode="a"))
.cutBlind(1000)
# Trim the top
.workplaneFromTagged("mid_height")
.workplane(offset=21)
.placeSketch(cq.Sketch().trapezoid(1000, 1000, 90, mode="a"))
.cutBlind(100)
# Make bottom smaller to fit with base
.faces(">X")
.workplane(centerOption="CenterOfBoundBox")
.center(-height / 2, -thickness / 2)
.placeSketch(
cq.Sketch()
.polygon(
[
(height_bottom, 0),
(height_bottom, thickness / 3),
(height, thickness - 21),
(height, thickness),
(height + 5, thickness + 5),
(height + 5, 0),
(height_bottom, 0),
]
)
.vertices()
.fillet(3)
)
.cutBlind(-1000)
# Fillet top of the object
.edges("|X and >Z")
.fillet(3)
# Cut off viewport hole so we can see the screen
.workplaneFromTagged("slanted")
.placeSketch(viewport_cutout)
@ -76,11 +111,6 @@ def model():
.center(-3, 0)
.placeSketch(screen_cutout)
.cutBlind(-scr_thickness)
# Trim the top
.workplaneFromTagged("mid_height")
.workplane(offset=21)
.placeSketch(cq.Sketch().trapezoid(1000, 1000, 90, mode="a"))
.cutBlind(100)
# Make it hollow
.faces("<Z")
# Can't be exactly shell_t because cq fails
@ -90,44 +120,38 @@ def model():
.workplane(offset=-scr_thickness, centerOption="CenterOfBoundBox")
.placeSketch(board_cutout)
.cutBlind(-6)
# Fillet top of the object
.edges(">Z and |X")
.fillet(5)
# Make small hole for the keyboard cable
.faces(">Y")
.workplane(offset=-5, centerOption="CenterOfBoundBox")
.center(-width / 2 + 128, -23)
.placeSketch(kbd_cable_hole)
.cutBlind(-1000)
# Pillars to join with bottom half
.workplaneFromTagged("mid_height")
.workplane(offset=-thickness / 2, centerOption="CenterOfBoundBox")
.center(-width / 2, height / 2)
.center(-width / 2, height_bottom - height / 2)
.placeSketch(mounting_pillars)
.extrude(10)
# Fillet the front edge of the screen case so it looks softer
.edges(">(0, -10, 5)")
.fillet(2)
)
exporters.export(model(), "screen_mount.stl")
if __name__ == "__main__":
split_offset = -133
print("Exporting")
exporters.export(model(), "screen_mount.stl")
right_side = (
model()
.faces(">X")
.workplane(centerOption="CenterOfBoundBox", offset=split_offset)
.center(0, height / 2)
.split(keepTop=True)
)
offset_width = -width / 2
exporters.export(right_side, "right_screen_mount.stl")
right_side = (
model()
.faces(">X")
.workplane(centerOption="CenterOfBoundBox", offset=offset_width)
.split(keepTop=True)
)
left_side = (
model()
.faces(">X")
.workplane(centerOption="CenterOfBoundBox", offset=split_offset)
.center(0, height / 2)
.split(keepBottom=True)
)
exporters.export(right_side, "right_screen_mount.stl")
exporters.export(left_side, "left_screen_mount.stl")
left_side = (
model()
.faces(">X")
.workplane(centerOption="CenterOfBoundBox", offset=offset_width)
.split(keepBottom=True)
)
exporters.export(left_side, "left_screen_mount.stl")

Binary file not shown.

View File

@ -0,0 +1,76 @@
from utils import extrude_shape, punch_hole
import cadquery as cq
elements = None
bottom_holes = None # Not really vents FIXME
def init(positions, thickness):
"""Because these need to match in multiple models, we create the
elemments dynamically"""
global elements, bottom_holes
elements = [
{
"x": 0,
"y": 0,
"shape": cq.Sketch().push(positions).trapezoid(12, 12, 90, mode="a"),
"height": thickness,
}
]
bottom_holes = [
{
"x": 0,
"y": 0,
"shape": cq.Sketch().push(positions).circle(3, mode="a"),
"depth": thickness - 13, # (screw thread length - threaded insert depth)
},
{
"x": 0,
"y": 0,
"shape": cq.Sketch().push(positions).circle(1.8, mode="a"),
"depth": 100,
},
]
def add(
*,
model,
width,
height,
thickness,
offset_x,
offset_y,
bottom_face,
back_face,
shell_t
):
if bottom_face:
# Mounting pillars
for element in elements:
model = extrude_shape(
model=model,
face=bottom_face,
w=width,
h=height,
x_offset=offset_x,
y_offset=shell_t + offset_y,
element=element,
height=-(element["height"] + shell_t),
)
# Screw holes
for hole in bottom_holes:
model = punch_hole(
model=model,
face=bottom_face,
w=width,
h=height,
x_offset=offset_x,
y_offset=shell_t + offset_y,
hole=hole,
depth=hole["depth"],
)
return model

74
notebook_nueva/usb_hub.py Normal file
View File

@ -0,0 +1,74 @@
import cadquery as cq
from utils import punch_hole, extrude_shape
# Measurements for my USB hub, YMMV
# The hole is for a USB-A plug, y is measured in the hub
# (from the bottom face to middle of the hole)
# Consumers should set proper offsets for the hole
holes = [
# USB-A port
{
"x": 0,
"y": 4,
"shape": cq.Sketch().trapezoid(13, 5, 90, mode="a").vertices().fillet(2),
},
]
elements = [
# Thing to grab the hub
{
"x": 0,
"y": 5,
"shape": (
cq.Sketch().trapezoid(22, 10, 90, mode="a").trapezoid(17, 10, 90, mode="s")
),
"height": 8,
}
]
def add(
*,
model,
width,
height,
thickness,
offset_x,
offset_y,
bottom_face,
back_face,
shell_t
):
# USB Hub extrusions
if bottom_face:
for element in elements:
model = extrude_shape(
model=model,
face=bottom_face,
w=width,
h=height,
x_offset=263, # offset_x,
y_offset=0, # shell_t + offset_y,
element=element,
height=-(element["height"] + shell_t),
)
# Holes
if back_face:
for hole in holes:
model = punch_hole(
model=model,
face=back_face,
w=width,
h=thickness,
x_offset=width - offset_x,
y_offset=shell_t,
hole=hole,
depth=shell_t,
)
return model

73
notebook_nueva/utils.py Normal file
View File

@ -0,0 +1,73 @@
import cadquery as cq
from math import floor
def extrude_shape(*, model, face, w, h, x_offset, y_offset, element, height):
return (
model.faces(face)
.workplane(centerOption="CenterOfBoundBox")
.center(-w / 2 + x_offset + element["x"], -h / 2 + y_offset + element["y"])
.placeSketch(element["shape"])
.extrude(height)
)
def punch_hole(*, model, face, w, h, x_offset, y_offset, hole, depth):
return (
model.faces(face)
.workplane(centerOption="CenterOfBoundBox")
.center(-w / 2 + x_offset + hole["x"], -h / 2 + y_offset + hole["y"])
.placeSketch(hole["shape"])
.cutBlind(-depth)
)
def extrude_shape2(*, model, face, w, h, x_offset, y_offset, hole, depth):
return (
model.faces(face)
.workplane(centerOption="CenterOfBoundBox")
.center(-w / 2 + x_offset + hole["x"], -h / 2 + y_offset + hole["y"])
.placeSketch(hole["shape"])
.extrude(-depth)
)
def hex_vents(*, size, width, height):
# size is radius of the hexagon
# Information about how this works:
# https://www.redblobgames.com/grids/hexagons/
x_step = size * (3**0.5)
y_step = size * 3 / 2
x_count = floor(width / x_step) - 1
if height > 4 * size:
y_count = floor((height - 2 * size) / (1.5 * size))
else:
y_count = 1
x_size = (x_count + 0.5) * x_step # Assumes at least 2 rows
y_size = 2 * size + 1.5 * size * (y_count - 1)
x_offset = (width - x_size) / 2 + 0.5 * x_step
y_offset = (height - y_size) / 2 + size
vent_positions = []
for x in range(0, x_count):
for y in range(0, y_count):
vent_positions.append(
(
(x + (y % 2) / 2) * x_step + x_offset,
y * y_step + y_offset,
)
)
vents = [
{
"x": 0,
"y": 0,
"shape": cq.Sketch().push(vent_positions).regularPolygon(size * 0.85, 6),
}
]
return vents

View File

@ -0,0 +1,111 @@
import cadquery as cq
from utils import extrude_shape, punch_hole, hex_vents
width = 65
height = 30
pillar_height = 7
stand_positions = [(3.5, 3.5), (3.5, 26.5), (61.5, 26.5), (61.5, 3.5)]
stands = (
cq.Sketch().push(stand_positions).circle(3, mode="a").circle(2.65 / 2, mode="s")
)
elements = [
# CPU holder stands
{
"x": 0,
"y": 0,
"shape": stands,
"height": pillar_height,
},
{
"x": 0,
"y": 0,
"shape": cq.Sketch().push(stand_positions).circle(5),
"height": 0,
},
# Perimeter
{
"x": width / 2,
"y": height / 2,
"shape": (
cq.Sketch()
.trapezoid(width, height, 90, mode="a")
.trapezoid(width - 2, height - 2, 90, mode="s")
.vertices()
.fillet(3)
),
"height": 0.2,
},
]
vents = hex_vents(size=3, width=width, height=height)
holes = [
# One hole for everything TODO: improve
{
"x": -width / 2,
"y": 1 + pillar_height,
"shape": cq.Sketch().trapezoid(50, 6, 90, mode="a").vertices().fillet(1),
}
]
def add(
*,
model,
width,
height,
thickness,
offset_x,
offset_y,
bottom_face,
back_face,
shell_t
):
if bottom_face:
# Vents
for vent in vents:
model = punch_hole(
model=model,
face=bottom_face,
w=width,
h=height,
x_offset=offset_x + vent["x"],
y_offset=shell_t + offset_y + vent["y"],
hole=vent,
depth=shell_t,
)
# CPU holder extrusions
for element in elements:
model = extrude_shape(
model=model,
face=bottom_face,
w=width,
h=height,
x_offset=offset_x,
y_offset=shell_t + offset_y,
element=element,
height=-(element["height"] + shell_t),
)
# Holes
if back_face:
for hole in holes:
model = punch_hole(
model=model,
face=back_face,
w=width,
h=thickness,
x_offset=width - offset_x,
y_offset=shell_t,
hole=hole,
depth=shell_t,
)
return model

View File

@ -1 +1 @@
cadquery
cadquery2