Add parametric CYD desk-stand case with DHT11 vent bay.
Provide OpenSCAD source, watertight front/back STLs, and print/assembly docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
16
README.md
16
README.md
@@ -39,6 +39,20 @@ pio device monitor -b 115200
|
||||
| `cyd_28r_inv` | 颜色反相 / 发白 |
|
||||
| `cyd_28_st7789` | USB-C 双口 / ST7789 变体 |
|
||||
|
||||
## 3D 外壳
|
||||
|
||||
桌面斜立分体壳(前框 + 后盖支架 + DHT11 通风仓),可直接打印:
|
||||
|
||||
- STL:[`cad/export/front.stl`](cad/export/front.stl)、[`cad/export/back.stl`](cad/export/back.stl)
|
||||
- 参数源 / 说明:[`cad/README.md`](cad/README.md)
|
||||
|
||||
```bash
|
||||
pip install trimesh manifold3d numpy
|
||||
python cad/generate_stl.py
|
||||
```
|
||||
|
||||
固定用 `4 × M3×10`;DHT11 装后盖通风仓,线接 CN1。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
@@ -46,6 +60,7 @@ platformio.ini # 板级与库依赖
|
||||
include/lv_conf.h # LVGL 配置
|
||||
src/main.cpp # 仪表盘主程序
|
||||
scripts/exclude_lvgl_asm.py # 排除 ESP32 不兼容的 LVGL ASM
|
||||
cad/ # 3D 外壳(OpenSCAD + STL)
|
||||
.cursor/rules/ # Cursor 协作规则
|
||||
README.md # 本文档
|
||||
```
|
||||
@@ -66,3 +81,4 @@ README.md # 本文档
|
||||
| 2026-10-17 | 初版:LVGL 仪表盘 + DHT11(GPIO22),平滑/舒适度/趋势/背光 |
|
||||
| 2026-10-17 | 曾加入 ECO 熄屏休眠,后改回常亮正常版 |
|
||||
| 2026-10-17 | 初始化 Git,补充 README 与提交约定 |
|
||||
| 2026-10-17 | 新增 CYD+DHT11 桌面斜立 3D 外壳(OpenSCAD / STL) |
|
||||
|
||||
59
cad/README.md
Normal file
59
cad/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# CYD 温湿度仪表盘 3D 外壳
|
||||
|
||||
桌面斜立分体外壳,适配 **ESP32-2432S028R(CYD)** + **DHT11(CN1 / GPIO22)**。
|
||||
|
||||
## 零件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `export/front.stl` | 前框(屏窗 + 压边) |
|
||||
| `export/back.stl` | 后盖 + 斜立支脚 + DHT11 通风仓 |
|
||||
| `cyd_climate_case.scad` | 参数化源文件(OpenSCAD,可改尺寸) |
|
||||
| `generate_stl.py` | 无 OpenSCAD 时用 Python 导出 STL |
|
||||
|
||||
## 设计要点
|
||||
|
||||
- PCB 基准:`86 × 50 × 1.6` mm,四角安装孔
|
||||
- 前框可视窗约 `59.5 × 45` mm
|
||||
- 后盖背面净空约 7 mm;Micro-USB / USB-C 合并开孔
|
||||
- BOOT / RST 背面开孔;底部散热缝
|
||||
- **DHT11 独立通风仓**(后盖长边外侧),减少板温干扰;线槽通向 CN1
|
||||
- 固定:`4 × M3×10` 自攻进后盖塑料柱(孔径约 2.5 mm)
|
||||
|
||||
## 打印建议
|
||||
|
||||
| 参数 | 建议 |
|
||||
|------|------|
|
||||
| 材料 | PLA 或 PETG |
|
||||
| 层高 | 0.2 mm |
|
||||
| 喷嘴 | 0.4 mm |
|
||||
| 墙厚 | 2–3 圈 |
|
||||
| 填充 | 15–20% |
|
||||
| 支撑 | **不需要**(按 STL 默认朝向) |
|
||||
| 前框 | 屏窗朝上打印 |
|
||||
| 后盖 | 底板朝下,支脚伸出床外方向按切片预览摆放 |
|
||||
|
||||
## 重新导出 STL
|
||||
|
||||
```bash
|
||||
pip install trimesh manifold3d numpy
|
||||
python cad/generate_stl.py
|
||||
```
|
||||
|
||||
或用 OpenSCAD:
|
||||
|
||||
```bash
|
||||
openscad -o cad/export/front.stl -D part=\"front\" cad/cyd_climate_case.scad
|
||||
openscad -o cad/export/back.stl -D part=\"back\" cad/cyd_climate_case.scad
|
||||
```
|
||||
|
||||
改尺寸:编辑 `cyd_climate_case.scad` / `generate_stl.py` 顶部参数后重新导出。
|
||||
|
||||
## 装配
|
||||
|
||||
1. DHT11 放入后盖通风仓,杜邦线经线槽接到 **CN1**(`3V3 / IO22 / GND`,按你现有接线)
|
||||
2. CYD 屏朝前框放入,USB 对齐侧孔
|
||||
3. 合上后盖,四角 M3×10 拧紧(勿过紧防裂)
|
||||
4. 桌面斜立放置;点屏可调背光
|
||||
|
||||
若孔位与你的板子有 ±1 mm 偏差,优先改 `generate_stl.py` 里 `USB_Y` / `MH_INSET` 后重导。
|
||||
300
cad/cyd_climate_case.scad
Normal file
300
cad/cyd_climate_case.scad
Normal file
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* CYD ESP32-2432S028R + DHT11 桌面斜立外壳
|
||||
* Climate Dashboard Case — parametric OpenSCAD
|
||||
*
|
||||
* 零件:
|
||||
* part = "front" 前框(屏窗)
|
||||
* part = "back" 后盖 + 斜立支架 + DHT 通风仓
|
||||
* part = "assembly" 装配预览(半透明板子)
|
||||
*
|
||||
* 导出 STL(本机安装 OpenSCAD 后):
|
||||
* openscad -o export/front.stl -D part=\"front\" cyd_climate_case.scad
|
||||
* openscad -o export/back.stl -D part=\"back\" cyd_climate_case.scad
|
||||
*/
|
||||
|
||||
/* [Which part] */
|
||||
part = "assembly"; // ["front", "back", "assembly"]
|
||||
|
||||
/* [PCB / CYD] */
|
||||
pcb_l = 86.0;
|
||||
pcb_w = 50.0;
|
||||
pcb_t = 1.6;
|
||||
pcb_corner_r = 4.0;
|
||||
mh_inset = 4.0; // 安装孔距角边中心
|
||||
mh_dia = 3.2; // PCB 孔
|
||||
|
||||
/* [Display] */
|
||||
disp_l = 69.0;
|
||||
disp_w = 50.0;
|
||||
disp_h = 4.0;
|
||||
disp_x = 8.0; // 屏相对 PCB 左边偏移
|
||||
view_l = 59.5;
|
||||
view_w = 45.0;
|
||||
view_inset_x = 7.0; // 相对屏模组
|
||||
view_inset_y = 2.5;
|
||||
|
||||
/* [Case shell] */
|
||||
wall = 2.4;
|
||||
gap = 0.4; // 板与壳间隙
|
||||
front_lip = 1.2; // 屏边压边宽度
|
||||
bezel_front_t = 2.0; // 前框面板厚度
|
||||
cavity_back = 7.0; // 背面元件净空
|
||||
stand_angle = 60; // 桌面倾角(度,相对水平)
|
||||
stand_foot = 18; // 支脚接触深度
|
||||
|
||||
/* [USB dual cutout] */
|
||||
usb_dual = true;
|
||||
usb_w = 14.0; // 双口合并开孔宽
|
||||
usb_h = 8.5;
|
||||
usb_y = 30.5; // 相对 PCB 原点(短边为 X=0 侧)
|
||||
|
||||
/* [Buttons] */
|
||||
btn_enable = true;
|
||||
btn_dia = 4.5;
|
||||
|
||||
/* [DHT11 bay] */
|
||||
dht_enable = true;
|
||||
dht_bay_l = 22;
|
||||
dht_bay_w = 18;
|
||||
dht_bay_h = 14;
|
||||
dht_vent_slot = 1.6;
|
||||
dht_vent_gap = 2.8;
|
||||
|
||||
/* [Screws] */
|
||||
screw_m3 = 3.2;
|
||||
boss_od = 7.2;
|
||||
boss_id = 2.6; // 自攻进塑料
|
||||
$fn = 48;
|
||||
|
||||
// ---- derived ----
|
||||
inner_l = pcb_l + 2 * gap;
|
||||
inner_w = pcb_w + 2 * gap;
|
||||
outer_l = inner_l + 2 * wall;
|
||||
outer_w = inner_w + 2 * wall;
|
||||
case_h = bezel_front_t + disp_h + pcb_t + cavity_back + wall;
|
||||
|
||||
mh_pts = [
|
||||
[mh_inset, mh_inset],
|
||||
[pcb_l - mh_inset, mh_inset],
|
||||
[pcb_l - mh_inset, pcb_w - mh_inset],
|
||||
[mh_inset, pcb_w - mh_inset]
|
||||
];
|
||||
|
||||
module rounded_rect(l, w, h, r) {
|
||||
hull() {
|
||||
for (x = [r, l - r], y = [r, w - r])
|
||||
translate([x, y, 0]) cylinder(h = h, r = r);
|
||||
}
|
||||
}
|
||||
|
||||
module cyd_mock() {
|
||||
color("gold")
|
||||
difference() {
|
||||
rounded_rect(pcb_l, pcb_w, pcb_t, pcb_corner_r);
|
||||
for (p = mh_pts)
|
||||
translate([p[0], p[1], -0.1])
|
||||
cylinder(d = mh_dia, h = pcb_t + 0.2);
|
||||
}
|
||||
color("black")
|
||||
translate([disp_x, 0, pcb_t])
|
||||
cube([disp_l, disp_w, disp_h]);
|
||||
color("navy")
|
||||
translate([disp_x + view_inset_x, view_inset_y, pcb_t + disp_h])
|
||||
cube([view_l, view_w, 0.2]);
|
||||
}
|
||||
|
||||
// 前框:从屏面方向看,Z=0 为最前表面
|
||||
module front_bezel() {
|
||||
view_x = wall + gap + disp_x + view_inset_x;
|
||||
view_y = wall + gap + view_inset_y;
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
// 前表面
|
||||
rounded_rect(outer_l, outer_w, bezel_front_t, 3);
|
||||
// 侧壁向下包住屏+PCB 前半
|
||||
translate([0, 0, bezel_front_t])
|
||||
difference() {
|
||||
rounded_rect(outer_l, outer_w, disp_h + pcb_t * 0.5, 3);
|
||||
translate([wall, wall, -0.1])
|
||||
rounded_rect(inner_l, inner_w, disp_h + pcb_t + 1, 2);
|
||||
}
|
||||
// 止口(与后盖搭接)
|
||||
translate([wall * 0.45, wall * 0.45, bezel_front_t + disp_h + pcb_t * 0.5 - 0.01])
|
||||
difference() {
|
||||
rounded_rect(outer_l - wall * 0.9, outer_w - wall * 0.9, 2.2, 2.4);
|
||||
translate([wall * 0.55, wall * 0.55, -0.1])
|
||||
rounded_rect(outer_l - wall * 2.0, outer_w - wall * 2.0, 3, 1.6);
|
||||
}
|
||||
}
|
||||
|
||||
// 可视窗
|
||||
translate([view_x - 0.3, view_y - 0.3, -0.1])
|
||||
cube([view_l + 0.6, view_w + 0.6, bezel_front_t + 1]);
|
||||
|
||||
// 屏模组沉台(背面)
|
||||
translate([wall + gap + disp_x - 0.3, wall + gap - 0.2, bezel_front_t - 0.05])
|
||||
cube([disp_l + 0.6, disp_w + 0.4, disp_h + 0.5]);
|
||||
|
||||
// 螺丝通孔
|
||||
for (p = mh_pts)
|
||||
translate([wall + gap + p[0], wall + gap + p[1], -0.1])
|
||||
cylinder(d = screw_m3 + 0.2, h = case_h);
|
||||
}
|
||||
|
||||
// 压屏边框内侧(窄边)
|
||||
difference() {
|
||||
translate([wall + gap + disp_x, wall + gap, bezel_front_t])
|
||||
cube([disp_l, disp_w, 0.8]);
|
||||
translate([view_x - 0.2, view_y - 0.2, bezel_front_t - 0.1])
|
||||
cube([view_l + 0.4, view_w + 0.4, 2]);
|
||||
}
|
||||
}
|
||||
|
||||
// 后盖主体(不含支架倾转);本地坐标:PCB 底面朝上放置时的壳
|
||||
module back_shell_body() {
|
||||
shell_h = cavity_back + wall;
|
||||
pcb_z = wall + cavity_back; // PCB 底面高度(元件朝下进腔)
|
||||
|
||||
difference() {
|
||||
union() {
|
||||
rounded_rect(outer_l, outer_w, shell_h, 3);
|
||||
|
||||
// 止口凹槽配合前框(外壳顶部一圈台阶)
|
||||
translate([0, 0, shell_h - 0.01])
|
||||
difference() {
|
||||
rounded_rect(outer_l, outer_w, 2.0, 3);
|
||||
translate([wall * 0.5, wall * 0.5, -0.05])
|
||||
rounded_rect(outer_l - wall, outer_w - wall, 2.5, 2.4);
|
||||
}
|
||||
|
||||
// 螺丝柱
|
||||
for (p = mh_pts) {
|
||||
translate([wall + gap + p[0], wall + gap + p[1], wall])
|
||||
difference() {
|
||||
cylinder(d = boss_od, h = cavity_back - 0.2);
|
||||
translate([0, 0, 1])
|
||||
cylinder(d = boss_id, h = cavity_back);
|
||||
}
|
||||
}
|
||||
|
||||
// DHT 通风仓(PCB 长边外侧,靠近 CN1:x≈51)
|
||||
if (dht_enable) {
|
||||
dht_x = wall + gap + 48;
|
||||
dht_y = outer_w - wall * 0.2;
|
||||
translate([dht_x, dht_y, wall])
|
||||
dht_bay();
|
||||
}
|
||||
}
|
||||
|
||||
// 内腔
|
||||
translate([wall, wall, wall])
|
||||
rounded_rect(inner_l, inner_w, shell_h, 1.8);
|
||||
|
||||
// USB 开孔(短边 x=0 侧)
|
||||
usb_cut_y = wall + gap + usb_y - usb_w / 2;
|
||||
translate([-0.1, usb_cut_y, wall + 1.5])
|
||||
cube([wall + 1.2, usb_w, usb_h]);
|
||||
|
||||
// SD 卡槽(可选宽缝)
|
||||
translate([wall + gap + 28, outer_w - wall - 0.1, wall + 2])
|
||||
cube([16, wall + 1, 3.2]);
|
||||
|
||||
// BOOT / RST 软按开孔(背面)
|
||||
if (btn_enable) {
|
||||
translate([wall + gap + 10, wall + gap + 12, -0.1])
|
||||
cylinder(d = btn_dia, h = wall + 0.3);
|
||||
translate([wall + gap + 10, wall + gap + 22, -0.1])
|
||||
cylinder(d = btn_dia, h = wall + 0.3);
|
||||
}
|
||||
|
||||
// 散热缝
|
||||
for (i = [0:4])
|
||||
translate([wall + 12 + i * 12, wall + 8, -0.1])
|
||||
hull() {
|
||||
cylinder(d = 2.2, h = wall + 0.3);
|
||||
translate([0, 10, 0]) cylinder(d = 2.2, h = wall + 0.3);
|
||||
}
|
||||
|
||||
// DHT 线槽通向 CN1
|
||||
if (dht_enable) {
|
||||
translate([wall + gap + 48, outer_w - wall - 2, wall + 2])
|
||||
cube([8, wall + 4, 4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module dht_bay() {
|
||||
// 局部:仓体贴在 outer 长边外侧
|
||||
difference() {
|
||||
translate([0, 0, 0])
|
||||
rounded_rect(dht_bay_l, dht_bay_w + wall, dht_bay_h, 2);
|
||||
translate([wall * 0.6, wall * 0.6, wall * 0.6])
|
||||
cube([dht_bay_l - wall * 1.2, dht_bay_w, dht_bay_h]);
|
||||
|
||||
// 顶面百叶通风
|
||||
for (i = [0:4])
|
||||
translate([3 + i * dht_vent_gap, dht_bay_w * 0.3, dht_bay_h - wall - 0.1])
|
||||
cube([dht_vent_slot, dht_bay_w * 0.55, wall + 0.3]);
|
||||
|
||||
// 侧面通风
|
||||
for (i = [0:2])
|
||||
translate([4 + i * 5.5, dht_bay_w + wall - 0.2, 3 + i * 0.2])
|
||||
cube([dht_vent_slot, wall + 0.4, 6]);
|
||||
}
|
||||
}
|
||||
|
||||
// 带斜立支架的后盖(打印时支脚朝下)
|
||||
module back_with_stand() {
|
||||
shell_h = cavity_back + wall;
|
||||
|
||||
// 后盖:PCB 元件朝下;支架使屏幕斜立
|
||||
// 组装时:屏幕朝向 -Z 转 stand_angle
|
||||
union() {
|
||||
back_shell_body();
|
||||
|
||||
// 三角形支脚(沿 PCB 长边下方)
|
||||
stand_len = outer_l * 0.72;
|
||||
stand_x = (outer_l - stand_len) / 2;
|
||||
// 支脚从后盖底面向后延伸
|
||||
translate([stand_x, -stand_foot + 0.5, 0])
|
||||
difference() {
|
||||
cube([stand_len, stand_foot, shell_h]);
|
||||
// 挖空减料
|
||||
translate([2, 2, wall])
|
||||
cube([stand_len - 4, stand_foot - 2, shell_h]);
|
||||
// 斜切贴合桌面视觉(打印可平放)
|
||||
translate([-0.1, 0, shell_h])
|
||||
rotate([-stand_angle + 90, 0, 0])
|
||||
cube([stand_len + 0.2, 40, 40]);
|
||||
}
|
||||
|
||||
// 后支撑肋
|
||||
translate([outer_l * 0.5 - 1.2, -stand_foot + 1, wall])
|
||||
cube([2.4, stand_foot - 1, shell_h - wall - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
module assembly() {
|
||||
// 后盖在下
|
||||
color("SteelBlue", 0.85) back_with_stand();
|
||||
|
||||
// PCB:元件朝下坐在腔顶
|
||||
translate([wall + gap, wall + gap, wall + cavity_back])
|
||||
rotate([180, 0, 0])
|
||||
translate([0, -pcb_w, -pcb_t])
|
||||
%cyd_mock();
|
||||
|
||||
// 前框盖上
|
||||
translate([0, 0, wall + cavity_back + pcb_t + disp_h + bezel_front_t])
|
||||
rotate([180, 0, 0])
|
||||
translate([0, -outer_w, 0])
|
||||
color("SlateGray", 0.7) front_bezel();
|
||||
}
|
||||
|
||||
if (part == "front")
|
||||
front_bezel();
|
||||
else if (part == "back")
|
||||
back_with_stand();
|
||||
else
|
||||
assembly();
|
||||
BIN
cad/export/back.stl
Normal file
BIN
cad/export/back.stl
Normal file
Binary file not shown.
BIN
cad/export/front.stl
Normal file
BIN
cad/export/front.stl
Normal file
Binary file not shown.
200
cad/generate_stl.py
Normal file
200
cad/generate_stl.py
Normal file
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate printable STL for CYD climate case using trimesh + manifold boolean."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import trimesh
|
||||
from trimesh.creation import box, cylinder
|
||||
|
||||
# ---- mm, aligned with cyd_climate_case.scad ----
|
||||
PCB_L, PCB_W = 86.0, 50.0
|
||||
MH_INSET = 4.0
|
||||
DISP_L, DISP_W, DISP_H, DISP_X = 69.0, 50.0, 4.0, 8.0
|
||||
VIEW_L, VIEW_W, VIEW_IX, VIEW_IY = 59.5, 45.0, 7.0, 2.5
|
||||
WALL, GAP = 2.4, 0.4
|
||||
BEZEL_T, CAVITY = 2.0, 7.0
|
||||
PCB_T_HALF = 0.8
|
||||
USB_W, USB_H, USB_Y = 14.0, 8.5, 30.5
|
||||
STAND_FOOT = 18.0
|
||||
DHT_L, DHT_W, DHT_H = 22.0, 18.0, 14.0
|
||||
SCREW = 3.3
|
||||
BOSS_OD, BOSS_ID = 7.2, 2.5
|
||||
|
||||
INNER_L = PCB_L + 2 * GAP
|
||||
INNER_W = PCB_W + 2 * GAP
|
||||
OUTER_L = INNER_L + 2 * WALL
|
||||
OUTER_W = INNER_W + 2 * WALL
|
||||
SHELL_H = CAVITY + WALL
|
||||
|
||||
MH = [
|
||||
(MH_INSET, MH_INSET),
|
||||
(PCB_L - MH_INSET, MH_INSET),
|
||||
(PCB_L - MH_INSET, PCB_W - MH_INSET),
|
||||
(MH_INSET, PCB_W - MH_INSET),
|
||||
]
|
||||
|
||||
|
||||
def place(mesh: trimesh.Trimesh, center: tuple[float, float, float]) -> trimesh.Trimesh:
|
||||
m = mesh.copy()
|
||||
m.apply_translation(np.array(center) - m.bounding_box.centroid)
|
||||
return m
|
||||
|
||||
|
||||
def aabb(x0, y0, z0, x1, y1, z1) -> trimesh.Trimesh:
|
||||
dx, dy, dz = x1 - x0, y1 - y0, z1 - z0
|
||||
m = box(extents=[dx, dy, dz])
|
||||
m.apply_translation([x0 + dx / 2, y0 + dy / 2, z0 + dz / 2])
|
||||
return m
|
||||
|
||||
|
||||
def u(*meshes: trimesh.Trimesh) -> trimesh.Trimesh:
|
||||
result = meshes[0]
|
||||
for m in meshes[1:]:
|
||||
result = result.union(m, engine="manifold")
|
||||
return result
|
||||
|
||||
|
||||
def d(a: trimesh.Trimesh, *subs: trimesh.Trimesh) -> trimesh.Trimesh:
|
||||
result = a
|
||||
for s in subs:
|
||||
result = result.difference(s, engine="manifold")
|
||||
return result
|
||||
|
||||
|
||||
def make_front() -> trimesh.Trimesh:
|
||||
skirt_h = DISP_H + PCB_T_HALF
|
||||
z1 = BEZEL_T + skirt_h
|
||||
|
||||
body = aabb(0, 0, 0, OUTER_L, OUTER_W, z1)
|
||||
cavity = aabb(WALL, WALL, BEZEL_T, OUTER_L - WALL, OUTER_W - WALL, z1 + 1)
|
||||
|
||||
vx = WALL + GAP + DISP_X + VIEW_IX
|
||||
vy = WALL + GAP + VIEW_IY
|
||||
view = aabb(vx - 0.3, vy - 0.3, -0.5, vx + VIEW_L + 0.3, vy + VIEW_W + 0.3, BEZEL_T + 0.5)
|
||||
|
||||
# screen pocket
|
||||
sx = WALL + GAP + DISP_X - 0.3
|
||||
pocket = aabb(sx, WALL + GAP - 0.2, BEZEL_T - 0.05, sx + DISP_L + 0.6, WALL + GAP + DISP_W + 0.2, z1 + 0.5)
|
||||
|
||||
out = d(body, cavity, view, pocket)
|
||||
|
||||
for px, py in MH:
|
||||
x = WALL + GAP + px
|
||||
y = WALL + GAP + py
|
||||
hole = cylinder(radius=SCREW / 2, height=z1 + 2, sections=32)
|
||||
hole.apply_translation([x, y, z1 / 2])
|
||||
out = d(out, hole)
|
||||
|
||||
out.merge_vertices()
|
||||
return out
|
||||
|
||||
|
||||
def make_back() -> trimesh.Trimesh:
|
||||
body = aabb(0, 0, 0, OUTER_L, OUTER_W, SHELL_H)
|
||||
cavity = aabb(WALL, WALL, WALL, OUTER_L - WALL, OUTER_W - WALL, SHELL_H + 1)
|
||||
|
||||
# USB on -X
|
||||
usb_y0 = WALL + GAP + USB_Y - USB_W / 2
|
||||
usb = aabb(-1, usb_y0, WALL + 1.5, WALL + 1, usb_y0 + USB_W, WALL + 1.5 + USB_H)
|
||||
|
||||
# SD slot on +Y
|
||||
sd = aabb(WALL + GAP + 28, OUTER_W - WALL - 1, WALL + 2, WALL + GAP + 44, OUTER_W + 1, WALL + 5.2)
|
||||
|
||||
# button holes on floor
|
||||
btn1 = cylinder(radius=2.3, height=WALL + 2, sections=24)
|
||||
btn1.apply_translation([WALL + GAP + 10, WALL + GAP + 12, WALL / 2])
|
||||
btn2 = cylinder(radius=2.3, height=WALL + 2, sections=24)
|
||||
btn2.apply_translation([WALL + GAP + 10, WALL + GAP + 22, WALL / 2])
|
||||
|
||||
# vent slots on floor
|
||||
vents = []
|
||||
for i in range(5):
|
||||
v = aabb(WALL + 12 + i * 12, WALL + 8, -0.5, WALL + 14 + i * 12, WALL + 18, WALL + 0.5)
|
||||
vents.append(v)
|
||||
|
||||
out = d(body, cavity, usb, sd, btn1, btn2, *vents)
|
||||
|
||||
# screw bosses
|
||||
bosses = []
|
||||
for px, py in MH:
|
||||
x = WALL + GAP + px
|
||||
y = WALL + GAP + py
|
||||
boss = cylinder(radius=BOSS_OD / 2, height=CAVITY - 0.3, sections=32)
|
||||
boss.apply_translation([x, y, WALL + (CAVITY - 0.3) / 2])
|
||||
hole = cylinder(radius=BOSS_ID / 2, height=CAVITY + 1, sections=24)
|
||||
hole.apply_translation([x, y, WALL + CAVITY / 2])
|
||||
bosses.append(d(boss, hole))
|
||||
out = u(out, *bosses)
|
||||
|
||||
# stand foot
|
||||
stand_len = OUTER_L * 0.72
|
||||
sx = (OUTER_L - stand_len) / 2
|
||||
foot = aabb(sx, -STAND_FOOT + 0.5, 0, sx + stand_len, WALL, SHELL_H)
|
||||
# hollow foot
|
||||
foot_in = aabb(sx + 2, -STAND_FOOT + 2.5, WALL, sx + stand_len - 2, WALL - 0.2, SHELL_H + 1)
|
||||
foot = d(foot, foot_in)
|
||||
rib = aabb(OUTER_L * 0.5 - 1.2, -STAND_FOOT + 1, WALL, OUTER_L * 0.5 + 1.2, WALL, SHELL_H - 1)
|
||||
out = u(out, foot, rib)
|
||||
|
||||
# DHT bay
|
||||
dx = WALL + GAP + 48
|
||||
dy = OUTER_W - 0.2
|
||||
bay = aabb(dx, dy, WALL, dx + DHT_L, dy + DHT_W + WALL, WALL + DHT_H)
|
||||
bay_in = aabb(dx + 1.2, dy + 1.2, WALL + 1.0, dx + DHT_L - 1.2, dy + DHT_W, WALL + DHT_H + 1)
|
||||
# top vents
|
||||
top_vents = []
|
||||
for i in range(5):
|
||||
tv = aabb(
|
||||
dx + 2.5 + i * 3.6,
|
||||
dy + 3,
|
||||
WALL + DHT_H - WALL - 0.2,
|
||||
dx + 2.5 + i * 3.6 + 1.5,
|
||||
dy + DHT_W - 2,
|
||||
WALL + DHT_H + 0.5,
|
||||
)
|
||||
top_vents.append(tv)
|
||||
# side vents
|
||||
for i in range(3):
|
||||
sv = aabb(
|
||||
dx + 4 + i * 5.5,
|
||||
dy + DHT_W + WALL - 0.5,
|
||||
WALL + 3,
|
||||
dx + 4 + i * 5.5 + 1.5,
|
||||
dy + DHT_W + WALL + 1,
|
||||
WALL + 9,
|
||||
)
|
||||
top_vents.append(sv)
|
||||
bay = d(bay, bay_in, *top_vents)
|
||||
|
||||
# cable channel from bay to CN1
|
||||
channel = aabb(dx + 2, OUTER_W - WALL - 2, WALL + 2, dx + 10, OUTER_W + DHT_W, WALL + 6)
|
||||
out = d(u(out, bay), channel)
|
||||
|
||||
out.merge_vertices()
|
||||
return out
|
||||
|
||||
|
||||
def main() -> None:
|
||||
out_dir = Path(__file__).resolve().parent / "export"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print("Building front…")
|
||||
front = make_front()
|
||||
print("Building back…")
|
||||
back = make_back()
|
||||
|
||||
front.export(out_dir / "front.stl")
|
||||
back.export(out_dir / "back.stl")
|
||||
|
||||
print(f"front: {front.faces.shape[0]} faces, watertight={front.is_watertight}")
|
||||
print(f"back: {back.faces.shape[0]} faces, watertight={back.is_watertight}")
|
||||
print(f"Outer: {OUTER_L:.1f} x {OUTER_W:.1f} x front/back shells")
|
||||
print(f"Wrote {out_dir / 'front.stl'}")
|
||||
print(f"Wrote {out_dir / 'back.stl'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user