From 646cf4579742ef21992597b0fd976acd75242d48 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PUHFO60\\dsx2016" Date: Sat, 17 Oct 2026 15:13:32 +0800 Subject: [PATCH] Fix case closing alignment and remove confusing side boxes. Put DHT11 in a flush rear snap pocket with slight proud grill; nest front lip into back groove in the viewer. Co-authored-by: Cursor --- README.md | 1 + cad/README.md | 54 ++--- cad/export/back.stl | Bin 76184 -> 55684 bytes cad/export/front.stl | Bin 28084 -> 28084 bytes cad/export/mate.json | 8 + cad/generate_stl.py | 343 +++++++++++---------------- cad/viewer.html | 548 +++++++++++++------------------------------ 7 files changed, 330 insertions(+), 624 deletions(-) create mode 100644 cad/export/mate.json diff --git a/README.md b/README.md index bbe09f1..696238e 100644 --- a/README.md +++ b/README.md @@ -91,3 +91,4 @@ README.md # 本文档 | 2026-10-17 | 增加浏览器 Three.js STL 预览页 `cad/viewer.html` | | 2026-10-17 | 预览增强:合盖/开盖、分解、装配动画、剖切/线框等 | | 2026-10-17 | 外壳改定:DHT11 后盖卡位微突出;前后盖止口合盖 | +| 2026-10-17 | 去掉旁侧多余凸块;修复合盖对齐;DHT 改为后盖平面卡位 | diff --git a/cad/README.md b/cad/README.md index bfaddf5..e95b16b 100644 --- a/cad/README.md +++ b/cad/README.md @@ -1,13 +1,21 @@ # CYD 温湿度仪表盘 3D 外壳 -桌面斜立分体外壳,适配 **ESP32-2432S028R(CYD)** + **DHT11**。 +## 结构说明(当前版) -## 设计要点(当前) +只有两件:**前框** + **后盖**。 -- **DHT11 在后盖外侧**:卡位槽 + 左右卡舌 + 前端止挡,模块从背面按入 -- **传感器微突出**约 **1.8 mm**(通风口/探头露出后盖平面) -- **前后盖合盖**:前框公止口落入后盖母槽;可用 `4×M3×10` 辅助固定 -- USB 侧孔、BOOT/RST 开孔、桌面支脚 +| 部位 | 作用 | +|------|------| +| 前框 | 屏窗;公止口,合盖时插入后盖 | +| 后盖 | 装板子;母止口;**背面中心 DHT11 卡位通风口**(探头微突出约 1.6mm) | +| DHT 示意 | 预览里蓝色/灰块,表示传感器装在后盖背面卡位里 | + +**没有**外壳旁边的大“格子”。旧版里那两块是:侧向支脚块、外挂 DHT 仓——已删除,避免误解。 + +## 合盖 + +前框止口落入后盖槽;预览默认合盖(开合度 0%)。 +对齐数据在 `export/mate.json`。 ## 浏览器预览 @@ -15,41 +23,19 @@ python -m http.server 8765 --directory cad ``` -打开:**http://localhost:8765/viewer.html** +打开 http://localhost:8765/viewer.html (请 **Ctrl+F5** 强制刷新) -| 操作 | 说明 | -|------|------| -| 合盖 / 开盖 | 前框相对后盖开合 | -| 开合度 / 分解 | 拖动查看止口与 DHT 仓 | -| 看后盖 | 快速转到背面看 DHT 突出 | -| 模拟屏板 / DHT11模块 | 示意件开关 | -| 快捷键 | `C` 合盖 · `O` 开盖 · `空格` 播放装配 | +- `C` 合盖 · `O` 开盖 · `空格` 播放 +- 「看后盖/DHT」查看背面微凸通风口 -## 零件 - -| 文件 | 说明 | -|------|------| -| `export/front.stl` | 前框(屏窗 + 公止口) | -| `export/back.stl` | 后盖(母止口 + DHT 卡位仓 + 支脚) | -| `generate_stl.py` | Python 生成 STL | -| `cyd_climate_case.scad` | OpenSCAD 参数源 | -| `viewer.html` | 交互预览 | - -## 重新导出 +## 导出 STL ```bash pip install trimesh manifold3d numpy python cad/generate_stl.py ``` -## 打印 - -PLA/PETG,0.2 mm,墙 2–3 圈,填充 15–20%,一般无需支撑。 -后盖建议 **外底朝下**(DHT 仓朝上或按切片预览,必要时对 DHT 卡舌加少量支撑)。 - ## 装配 -1. 将 DHT11 模块从 **后盖背面** 按入卡位,探头朝外微突出;线经仓内小孔接到 CN1(GPIO22) -2. CYD 放入后盖腔体,USB 对齐 -3. 前框止口对准后盖槽 **合盖**,四角螺丝固定 -4. 桌面斜立使用 +1. DHT11 从**后盖背面**按入中心卡位,探头朝外略突出,线进腔内接 CN1/GPIO22 +2. CYD 放入后盖,前框合盖,四角 M3×10 可选固定 diff --git a/cad/export/back.stl b/cad/export/back.stl index 3bb6f18a02bd5a264d716095f62137e0171e517b..6d61b3968d0da925013ab0114ba0a1bd123e52ef 100644 GIT binary patch literal 55684 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 oWqm&P$WV>antg!DY~7d`Io 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 Xc^PC{d>PtIS5dCysM{BxyS?Z?nPZI9 delta 4650 zcmai1Yiv|S6yBxVcdNA9ZeO&!OSjv{mO`m1rI2*H&<6`GHm#6qTfvb2fL|Dc8iX!B zVlbGro@i)^arsjX2vTpA5TXqd5)#Fr{1X$wRKyq%#KeSz=$tt-cg}51+@#y@&ONU= zXU;cgXRo_wue;AYk>YW=j zr72Yi*0hg~hT(Xo&kANfa@z}4*%2FWXZSF~=NZ0Zu;tImMne=rW3FFgYkTe<5B!y< z<1U7pe4mOf1V{5j`6`^~m2YyQh^Hpu+x&oEFHcQos^$Ke77@`~x!ya*p}UYGpPKB4 z#;_MU3vthAi=JOv@O)yy^N$OjAM+Xgn?qiRE_i<4SDdC|9rpz^{M=Ws;cZ`whU2~h zXf4Xok6Vgtp@T(%CsoSC7V(4Wi^mL3Mrr%B;!3z&i8g0qWWtq{%-!9h^{}mE9em-= zEpQq?CJZN|aK>E<%_RW{XZXBgAtsV(=N^y6-g@tt(}z4RN`~bBFDfp@Bcy%c9`HkN zI3~Ntnq{O8CqhcAxm=^8x1u6+VJ6vT@F2{T_{W->Dn+PxR_3FK>3Lqr3k!4jO`nC@ z$+oG<5^Nx0bx4ZTjhoY}y2pB}d|-*)-RHv< z8KSBrl^qDeRCgAva(gCnAA{*&nN&( zQ}?MeoiY&BkBH+j=0RaqQc(c>EY?u#nYOy<*#cO#Jk9z(cw3WR9QM}K>8;1%LZwb! z93E6I&rOgKJn=``;oxo`w1o6M5Qps{BYiluM(sKdjiCxSRh4V~8G7BV3G0=xO<1Q5 zyb-RqYtUEig>bdah#RYIg^vu}XW$Rj8-t3S6{U+{9RZ8HL0e57#v-a9tD_{7B8U1S zFk3`KQj~2j#kHLpR3BPHa?};&b#p;=ZKX#h@oLCd;dH|V(IvcEyB_YGACA|qoV_oI+0cuhK1B49pG2OI8k&d>0?lAun-cFEKxTe8Vi}^Y>Jh}#4&Zb zbeQNz`^PBtx*f4pLI3;>*ATul^d=#%zDyiddHcc4Dj%FH%?>WU(rC7@FD?jXl)*@S zj?k2AcrEtnW<~$XqH1-zTWMWl$9e2~T>mE;aQbQ_=>?o6!Q{tqdPx_!ovPhKtr1 z-AxSdXZRGuKN)Q0wKS!RL4#)qu^^!~zkl(%9(hRSP_S@-XN|C~6;gp?N=T?fDTf+P z1%xRE#1L3r3dK<@FFA@t*TMY8X4oCgSHTh+yy%;bJ-%;#V#e!LFp7oJYM(`QS>o_3 z!zJxzeKW%c82*Ieo9z*Y+l!u)v8vWo=!<#F6c)FE?N?qy)2B~Du)PR&#fk-;%5A9Y zi6rqGPoT`1^?s~t2|R4c5-G=C)PvJmsUqovu78|Dyx3RNThxgHi)o|;&UB=WIrP=Y z!1UF?fxgK-^)*_<{#E0O1KYeJG)PPPmmRNsDD6y~cBDE)dn;h5vo&1}E+X=RG&K#K z?Tl1tSDH?=9udVmT?J!$;xXYXT4XLoNc2g_?eI4elZN)iDN3{V;)_Y0OpiFc{eZtO zZd4J`;B)X-$48X5GFq=e(_K9kseC8l)S%2x;q8Q z;c_xcYFvb{F^3k(C~2r|;jUC$N(~TAynJ!vE^K1alFKC7#jT!A9fr3iHyiHxn&C$b zuiRo}b}|eMUt;)ghHHC_?lTO(&F~z<_j@8@4B-2j7Wvx1<`Zb`ZH1#dS6eUi29S&? z3&;8mC0t?nABGzTBBDP{o}cdfFqZvtx2*gAi_@{q!(G;?fwh<%GWza-AL5lc)~#)w zsbXN0MO;0E^OuCX41CYP%LYC)uzuK98!>QvxL*EB#F}Eoc6ObJL$3nBcS?l5dPTV{24|f^pi|uyHV5$!c&F3#2e6wWs{;<0>9|o84ZWs#B4;QOsSJJ zsm!4m@_^<9Ih^h2^pB~&CMVQvoE5Zl>>%~w8j1RC%$ Ur_SOkL{^4w)Szfxcgzx;FJGXMYp diff --git a/cad/export/mate.json b/cad/export/mate.json new file mode 100644 index 0000000..fe8bfe0 --- /dev/null +++ b/cad/export/mate.json @@ -0,0 +1,8 @@ +{ + "outer_l": 91.5, + "outer_w": 55.5, + "front_body_h": 7.2, + "back_body_h": 10.4, + "lip_h": 2.2, + "dht_proud": 1.6 +} diff --git a/cad/generate_stl.py b/cad/generate_stl.py index c76a900..8ef61ce 100644 --- a/cad/generate_stl.py +++ b/cad/generate_stl.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """ -CYD climate case STL generator. +CYD climate case — clean clamshell. -Design intent: - - DHT11 on the BACK cover, held by snap/clip posts (卡位), tip slightly proud of back face - - Front + back close with a interlocking lip (合盖) +- Front: screen bezel + male lip +- Back: cavity + female groove + DHT11 snap pocket flush in back wall (tip proud ~1.6mm) +- No side “boxes”: removed bulky stand block and external DHT hang-on bay """ from __future__ import annotations @@ -12,32 +12,31 @@ from __future__ import annotations from pathlib import Path import trimesh -from trimesh.creation import box, cylinder # noqa: F401 — cylinder used for holes/bosses +from trimesh.creation import box, cylinder -# ---- mm ---- PCB_L, PCB_W = 86.0, 50.0 MH_INSET = 4.0 DISP_X, DISP_L, DISP_W, DISP_H = 8.0, 69.0, 50.0, 4.0 VIEW_L, VIEW_W, VIEW_IX, VIEW_IY = 59.5, 45.0, 7.0, 2.5 WALL, GAP = 2.4, 0.35 -BEZEL_T, CAVITY = 2.2, 7.5 -LIP_H, LIP_T = 2.4, 1.15 # front male / back female for 合盖 +BEZEL_T = 2.2 +CAVITY = 8.0 +LIP_H, LIP_CLEAR = 2.2, 0.2 USB_W, USB_H, USB_Y = 15.0, 9.0, 30.5 -STAND_FOOT = 16.0 -SCREW = 3.3 -BOSS_OD, BOSS_ID = 7.0, 2.5 +SCREW, BOSS_OD, BOSS_ID = 3.3, 7.0, 2.5 -# DHT11 module (common PCB module ~15.5 x 12 x 7.5 + pins) -DHT_MOD_L, DHT_MOD_W, DHT_MOD_H = 16.0, 12.5, 8.0 -DHT_PROUD = 1.8 # how much grill/tip sticks out past back outer face -DHT_CLIP = 1.1 # undercut lip thickness for 卡位 +# DHT11 module pocket (from OUTSIDE of back face z=0) +DHT_L, DHT_W, DHT_H = 16.0, 12.5, 7.5 +DHT_PROUD = 1.6 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 -SKIRT_H = DISP_H + 1.0 +SKIRT = DISP_H + 1.0 +# Back total height to mating plane (top of female groove floor) +BACK_BODY_H = WALL + CAVITY # rim starts here +FRONT_BODY_H = BEZEL_T + SKIRT MH = [ (MH_INSET, MH_INSET), @@ -52,256 +51,182 @@ def aabb(x0, y0, z0, x1, y1, z1) -> trimesh.Trimesh: ya, yb = sorted((float(y0), float(y1))) za, zb = sorted((float(z0), float(z1))) dx, dy, dz = xb - xa, yb - ya, zb - za - if dx < 1e-6 or dy < 1e-6 or dz < 1e-6: - raise ValueError(f"degenerate box {[xa,ya,za,xb,yb,zb]}") + if min(dx, dy, dz) < 1e-6: + raise ValueError(f"degenerate {[xa, ya, za, xb, yb, zb]}") m = box(extents=[dx, dy, dz]) m.apply_translation([xa + dx / 2, ya + dy / 2, za + dz / 2]) return m -def u(*meshes: trimesh.Trimesh) -> trimesh.Trimesh: - r = meshes[0] - for m in meshes[1:]: +def U(*ms: trimesh.Trimesh) -> trimesh.Trimesh: + r = ms[0] + for m in ms[1:]: r = r.union(m, engine="manifold") return r -def d(a: trimesh.Trimesh, *subs: trimesh.Trimesh) -> trimesh.Trimesh: +def D(a: trimesh.Trimesh, *ss: trimesh.Trimesh) -> trimesh.Trimesh: r = a - for s in subs: + for s in ss: r = r.difference(s, engine="manifold") return r def make_front() -> trimesh.Trimesh: - """Front bezel: screen window + skirt + male lip for closing onto back.""" - z1 = BEZEL_T + SKIRT_H + LIP_H - - body = aabb(0, 0, 0, OUTER_L, OUTER_W, BEZEL_T + SKIRT_H) - # hollow skirt - cavity = aabb(WALL, WALL, BEZEL_T, OUTER_L - WALL, OUTER_W - WALL, BEZEL_T + SKIRT_H + 1) + """ + Z=0: outer screen face + Z=FRONT_BODY_H: open rim with male lip extending to FRONT_BODY_H+LIP_H + """ + body = aabb(0, 0, 0, OUTER_L, OUTER_W, FRONT_BODY_H) + hollow = aabb(WALL, WALL, BEZEL_T, OUTER_L - WALL, OUTER_W - WALL, FRONT_BODY_H + 1) vx = WALL + GAP + DISP_X + VIEW_IX vy = WALL + GAP + VIEW_IY - view = aabb(vx - 0.25, vy - 0.25, -0.5, vx + VIEW_L + 0.25, vy + VIEW_W + 0.25, BEZEL_T + 0.6) + view = aabb(vx - 0.2, vy - 0.2, -0.5, vx + VIEW_L + 0.2, vy + VIEW_W + 0.2, BEZEL_T + 0.5) - sx = WALL + GAP + DISP_X - 0.25 + sx = WALL + GAP + DISP_X - 0.2 pocket = aabb( - sx, WALL + GAP - 0.15, BEZEL_T - 0.05, - sx + DISP_L + 0.5, WALL + GAP + DISP_W + 0.15, BEZEL_T + DISP_H + 0.8, + sx, WALL + GAP - 0.1, BEZEL_T - 0.05, + sx + DISP_L + 0.4, WALL + GAP + DISP_W + 0.1, BEZEL_T + DISP_H + 0.6, ) + out = D(body, hollow, view, pocket) - out = d(body, cavity, view, pocket) - - # male lip (inserts into back female groove) - lip_outer = aabb( - WALL - LIP_T, WALL - LIP_T, BEZEL_T + SKIRT_H - 0.05, - OUTER_L - (WALL - LIP_T), OUTER_W - (WALL - LIP_T), BEZEL_T + SKIRT_H + LIP_H, + # male lip: thin wall ring protruding past rim + lip_out = aabb( + WALL - 1.0, WALL - 1.0, FRONT_BODY_H - 0.05, + OUTER_L - (WALL - 1.0), OUTER_W - (WALL - 1.0), FRONT_BODY_H + LIP_H, ) - lip_inner = aabb( - WALL + 0.15, WALL + 0.15, BEZEL_T + SKIRT_H - 0.1, - OUTER_L - WALL - 0.15, OUTER_W - WALL - 0.15, BEZEL_T + SKIRT_H + LIP_H + 0.5, + lip_in = aabb( + WALL + LIP_CLEAR, WALL + LIP_CLEAR, FRONT_BODY_H - 0.2, + OUTER_L - WALL - LIP_CLEAR, OUTER_W - WALL - LIP_CLEAR, FRONT_BODY_H + LIP_H + 0.5, ) - lip = d(lip_outer, lip_inner) - out = u(out, lip) + out = U(out, D(lip_out, lip_in)) - # screw clearance through bezel for px, py in MH: - x = WALL + GAP + px - y = WALL + GAP + py - hole = cylinder(radius=SCREW / 2 + 0.1, height=z1 + 4, sections=28) - hole.apply_translation([x, y, (BEZEL_T + SKIRT_H) / 2]) - out = d(out, hole) + hole = cylinder(radius=SCREW / 2 + 0.1, height=FRONT_BODY_H + LIP_H + 4, sections=28) + hole.apply_translation([WALL + GAP + px, WALL + GAP + py, FRONT_BODY_H / 2]) + out = D(out, hole) out.merge_vertices() return out -def dht_pocket_on_back() -> trimesh.Trimesh: - """ - Pocket on outer back face (z=0 side is exterior bottom of shell). - Module inserts from outside; clips hold it; grill protrudes DHT_PROUD. - Coordinate: back shell sits z=0 exterior, z=SHELL_H toward PCB. - Pocket carved into floor from outside, clips on the outer face. - """ - # center DHT on back wall area away from USB (right-ish of board back) - cx = WALL + GAP + 62 - cy = WALL + GAP + PCB_W / 2 - - # outer boss that contains the module (sits mostly outside z<=0) - bay_l = DHT_MOD_L + 3.2 - bay_w = DHT_MOD_W + 3.2 - bay_h = DHT_MOD_H + WALL * 0.6 + DHT_PROUD - - # bay extends below z=0 (outside) and a bit into floor - bay = aabb( - cx - bay_l / 2, cy - bay_w / 2, -bay_h + WALL * 0.35, - cx + bay_l / 2, cy + bay_w / 2, WALL + 0.2, - ) - - # cavity for module (from outside) - cav = aabb( - cx - DHT_MOD_L / 2 - 0.25, cy - DHT_MOD_W / 2 - 0.25, -bay_h + WALL * 0.35 + 1.0, - cx + DHT_MOD_L / 2 + 0.25, cy + DHT_MOD_W / 2 + 0.25, WALL - 0.3, - ) - - # window through back wall into interior for wires (small) - wire = aabb( - cx - 3.5, cy - 4.0, WALL - 0.5, - cx + 3.5, cy + 4.0, WALL + 2.5, - ) - - # front face of bay: grill opening, leaving rim so tip is proud - grill = aabb( - cx - DHT_MOD_L / 2 + 1.2, cy - DHT_MOD_W / 2 + 1.2, -bay_h - 0.2, - cx + DHT_MOD_L / 2 - 1.2, cy + DHT_MOD_W / 2 - 1.2, -bay_h + WALL + DHT_PROUD + 0.5, - ) - - # side vent slots - vents = [] - for i in range(3): - vents.append(aabb( - cx - DHT_MOD_L / 2 - 2, cy - 4 + i * 3.2, -bay_h + 3, - cx - DHT_MOD_L / 2 + 0.2, cy - 4 + i * 3.2 + 1.4, -bay_h + 8, - )) - vents.append(aabb( - cx + DHT_MOD_L / 2 - 0.2, cy - 4 + i * 3.2, -bay_h + 3, - cx + DHT_MOD_L / 2 + 2, cy - 4 + i * 3.2 + 1.4, -bay_h + 8, - )) - - # 卡位: L/R snap ledges + end stops (press-fit module from outside) - z_base = -bay_h + WALL * 0.35 + 1.2 - left_clip = aabb( - cx - DHT_MOD_L / 2 - 1.2, cy - 3.5, z_base, - cx - DHT_MOD_L / 2 + DHT_CLIP, cy + 3.5, z_base + 3.2, - ) - right_clip = aabb( - cx + DHT_MOD_L / 2 - DHT_CLIP, cy - 3.5, z_base, - cx + DHT_MOD_L / 2 + 1.2, cy + 3.5, z_base + 3.2, - ) - # end stop ledge near grill (prevents module falling out front) - stop = aabb( - cx - DHT_MOD_L / 2 + 1.5, cy - DHT_MOD_W / 2 + 0.8, -bay_h + WALL * 0.35 + 0.8, - cx + DHT_MOD_L / 2 - 1.5, cy + DHT_MOD_W / 2 - 0.8, -bay_h + WALL * 0.35 + 0.8 + 1.0, - ) - # rear retention bar (卡入后沿) - retain = aabb( - cx - 5.0, cy - DHT_MOD_W / 2 - 0.2, WALL - 2.2, - cx + 5.0, cy + DHT_MOD_W / 2 + 0.2, WALL - 0.4, - ) - - bay = d(bay, cav, grill, wire, *vents) - bay = u(bay, left_clip, right_clip, stop, retain) - return bay - - def make_back() -> trimesh.Trimesh: - """Back shell: cavity, female lip, screw bosses, stand, DHT snap bay on outer back.""" - body = aabb(0, 0, 0, OUTER_L, OUTER_W, SHELL_H + LIP_H) - cavity = aabb(WALL, WALL, WALL, OUTER_L - WALL, OUTER_W - WALL, SHELL_H + LIP_H + 1) + """ + Z=0: exterior back face (DHT grill here, slightly proud) + Z=BACK_BODY_H: mating plane; female groove from BACK_BODY_H to BACK_BODY_H+LIP_H + """ + total_h = BACK_BODY_H + LIP_H + body = aabb(0, 0, 0, OUTER_L, OUTER_W, total_h) + cavity = aabb(WALL, WALL, WALL, OUTER_L - WALL, OUTER_W - WALL, total_h + 1) + out = D(body, cavity) - # female groove for front lip (near top rim) + # female groove: cut a channel in the top rim for the male lip + # outer wall of groove groove = aabb( - WALL - LIP_T - 0.15, WALL - LIP_T - 0.15, SHELL_H, - OUTER_L - (WALL - LIP_T - 0.15), OUTER_W - (WALL - LIP_T - 0.15), SHELL_H + LIP_H + 0.2, + WALL - 1.0 - LIP_CLEAR, WALL - 1.0 - LIP_CLEAR, BACK_BODY_H + 0.1, + OUTER_L - (WALL - 1.0 - LIP_CLEAR), OUTER_W - (WALL - 1.0 - LIP_CLEAR), total_h + 0.2, ) - # keep inner wall so groove is a channel, not open to cavity fully - # actually: cut a U-channel from the top rim - groove_cut = aabb( - WALL - LIP_T - 0.1, WALL - LIP_T - 0.1, SHELL_H - 0.05, - OUTER_L - (WALL - LIP_T - 0.1), OUTER_W - (WALL - LIP_T - 0.1), SHELL_H + LIP_H + 0.3, + groove_keep = aabb( + WALL + 0.05, WALL + 0.05, BACK_BODY_H - 0.1, + OUTER_L - WALL - 0.05, OUTER_W - WALL - 0.05, total_h + 0.3, ) - keep_inner = aabb( - WALL + 0.05, WALL + 0.05, SHELL_H - 0.2, - OUTER_L - WALL - 0.05, OUTER_W - WALL - 0.05, SHELL_H + LIP_H + 0.5, - ) - # rim solid then groove: start from body, remove cavity, then remove groove channel between lip walls - out = d(body, cavity) - - # Add rim collar then cut female slot - collar = aabb(0, 0, SHELL_H - 0.05, OUTER_L, OUTER_W, SHELL_H + LIP_H) - collar_in = aabb(WALL + 0.2, WALL + 0.2, SHELL_H - 0.2, OUTER_L - WALL - 0.2, OUTER_W - WALL - 0.2, SHELL_H + LIP_H + 0.5) - collar = d(collar, collar_in) - out = u(out, collar) - - # female slot (accepts front male lip) - slot = aabb( - WALL - LIP_T - 0.12, WALL - LIP_T - 0.12, SHELL_H + 0.15, - OUTER_L - (WALL - LIP_T - 0.12), OUTER_W - (WALL - LIP_T - 0.12), SHELL_H + LIP_H - 0.15, - ) - slot_keep = aabb( - WALL + 0.05, WALL + 0.05, SHELL_H, - OUTER_L - WALL - 0.05, OUTER_W - WALL - 0.05, SHELL_H + LIP_H + 0.2, - ) - # slot is the ring between slot outer and slot_keep — cut only the ring - slot_ring = d(slot, slot_keep) - out = d(out, slot_ring) + out = D(out, D(groove, groove_keep)) # USB - usb_y0 = WALL + GAP + USB_Y - USB_W / 2 - usb = aabb(-1, usb_y0, WALL + 1.2, WALL + 1.2, usb_y0 + USB_W, WALL + 1.2 + USB_H) - out = d(out, usb) + uy0 = WALL + GAP + USB_Y - USB_W / 2 + out = D(out, aabb(-0.5, uy0, WALL + 1.0, WALL + 1.0, uy0 + USB_W, WALL + 1.0 + USB_H)) - # SD optional slit - sd = aabb(WALL + GAP + 28, OUTER_W - WALL - 1, WALL + 2, WALL + GAP + 44, OUTER_W + 1, WALL + 5) - out = d(out, sd) - - # BOOT/RST access - for y in (12, 22): + # button holes + for y in (12.0, 22.0): btn = cylinder(radius=2.2, height=WALL + 2, sections=20) btn.apply_translation([WALL + GAP + 10, WALL + GAP + y, WALL / 2]) - out = d(out, btn) - - # floor vents (not under DHT) - vents = [] - for i in range(4): - vents.append(aabb(WALL + 10 + i * 12, WALL + 8, -0.5, WALL + 12 + i * 12, WALL + 16, WALL + 0.5)) - out = d(out, *vents) + out = D(out, btn) # 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.4, sections=28) - boss.apply_translation([x, y, WALL + (CAVITY - 0.4) / 2]) + x, y = WALL + GAP + px, WALL + GAP + py + boss = cylinder(radius=BOSS_OD / 2, height=CAVITY - 0.5, sections=28) + boss.apply_translation([x, y, WALL + (CAVITY - 0.5) / 2]) hole = cylinder(radius=BOSS_ID / 2, height=CAVITY + 1, sections=20) hole.apply_translation([x, y, WALL + CAVITY / 2]) - bosses.append(d(boss, hole)) - out = u(out, *bosses) + bosses.append(D(boss, hole)) + out = U(out, *bosses) - # stand foot (desk) - stand_len = OUTER_L * 0.7 - sx = (OUTER_L - stand_len) / 2 - foot = aabb(sx, -STAND_FOOT + 0.4, 0, sx + stand_len, WALL, SHELL_H * 0.85) - foot_in = aabb(sx + 2, -STAND_FOOT + 2.2, WALL, sx + stand_len - 2, WALL - 0.15, SHELL_H) - foot = d(foot, foot_in) - rib = aabb(OUTER_L / 2 - 1.2, -STAND_FOOT + 1, WALL, OUTER_L / 2 + 1.2, WALL, SHELL_H * 0.8) - out = u(out, foot, rib) + # ---- DHT11 flush pocket in back wall (NOT a side box) ---- + # Center on back face + cx = OUTER_L * 0.5 + cy = OUTER_W * 0.5 + # Pocket carved from outside into wall+cavity enough for module + pocket = aabb( + cx - DHT_L / 2 - 0.3, cy - DHT_W / 2 - 0.3, -0.05, + cx + DHT_L / 2 + 0.3, cy + DHT_W / 2 + 0.3, WALL + DHT_H - 1.0, + ) + out = D(out, pocket) - # DHT11 snap bay on outer back (protruding) - out = u(out, dht_pocket_on_back()) + # Snap rails (卡位) left/right inside pocket — thin ledges + rail_z0, rail_z1 = 1.2, 1.2 + 2.2 + left = aabb(cx - DHT_L / 2 - 0.3, cy - 3.2, rail_z0, cx - DHT_L / 2 + 1.0, cy + 3.2, rail_z1) + right = aabb(cx + DHT_L / 2 - 1.0, cy - 3.2, rail_z0, cx + DHT_L / 2 + 0.3, cy + 3.2, rail_z1) + # bottom stop (prevents falling inward) + stop = aabb(cx - 5, cy - DHT_W / 2 + 0.5, WALL + DHT_H - 2.2, cx + 5, cy + DHT_W / 2 - 0.5, WALL + DHT_H - 1.0) + out = U(out, left, right, stop) + + # Proud grill ring on exterior (the only intentional protrusion) + grill_ring = aabb( + cx - DHT_L / 2 - 1.0, cy - DHT_W / 2 - 1.0, -DHT_PROUD, + cx + DHT_L / 2 + 1.0, cy + DHT_W / 2 + 1.0, 0.05, + ) + grill_hole = aabb( + cx - DHT_L / 2 + 1.5, cy - DHT_W / 2 + 1.5, -DHT_PROUD - 0.2, + cx + DHT_L / 2 - 1.5, cy + DHT_W / 2 - 1.5, 0.2, + ) + # slot vents in proud face + slots = [] + for i in range(4): + slots.append(aabb( + cx - 5 + i * 2.8, cy - 4.5, -DHT_PROUD - 0.2, + cx - 5 + i * 2.8 + 1.2, cy + 4.5, 0.2, + )) + out = U(out, D(grill_ring, grill_hole, *slots)) + + # tiny wire path already open via pocket into cavity + + # Simple thin kickstand rib (not a hollow “格子”) — optional small triangle + # Skip bulky stand entirely per user feedback. 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 (合盖公止口)…") + out = Path(__file__).resolve().parent / "export" + out.mkdir(parents=True, exist_ok=True) + print("front…") front = make_front() - print("Building back (DHT后盖卡位 + 母止口)…") + print("back…") back = make_back() + front.export(out / "front.stl") + back.export(out / "back.stl") - front.export(out_dir / "front.stl") - back.export(out_dir / "back.stl") - print(f"front watertight={front.is_watertight} faces={len(front.faces)}") - print(f"back watertight={back.is_watertight} faces={len(back.faces)}") - print(f"Wrote {out_dir / 'front.stl'}") - print(f"Wrote {out_dir / 'back.stl'}") - print(f"Outer ~ {OUTER_L:.1f} x {OUTER_W:.1f} mm; DHT proud ~{DHT_PROUD} mm") + # write mate info for viewer + meta = out / "mate.json" + meta.write_text( + "{\n" + f' "outer_l": {OUTER_L},\n' + f' "outer_w": {OUTER_W},\n' + f' "front_body_h": {FRONT_BODY_H},\n' + f' "back_body_h": {BACK_BODY_H},\n' + f' "lip_h": {LIP_H},\n' + f' "dht_proud": {DHT_PROUD}\n' + "}\n", + encoding="utf-8", + ) + print(f"front {front.extents} watertight={front.is_watertight}") + print(f"back {back.extents} watertight={back.is_watertight}") + print("closed stack height ≈", BACK_BODY_H + FRONT_BODY_H, "mm (lips nest, not add full)") + print("Wrote STLs + mate.json") if __name__ == "__main__": diff --git a/cad/viewer.html b/cad/viewer.html index 9437581..0e912ae 100644 --- a/cad/viewer.html +++ b/cad/viewer.html @@ -3,113 +3,77 @@ - CYD Case — 合盖 / DHT后盖卡位 + CYD Case — 合盖预览
-

CYD Case Preview

-

DHT11 在后盖外侧卡位,微微突出 · 前后盖止口合盖

+

CYD 外壳预览

+

左键旋转 · 滚轮缩放 · 右键平移

-
合盖
+
合盖
- - -
-
- 开合度 - - 0% -
-
- 分解 - - 0 +
+
开合度0%
+
分解0
-
部件
+
部件
- - - - + + + +
-
显示
+
视角
- - - - - + + + +
-
-
视角
-
- - - - - - -
-
- -

合盖=0%:前框止口落入后盖槽。DHT 从后盖背面卡入并突出约 1.8mm。

+

+ 后盖中心微凸的是 DHT11 卡位通风口(传感器按入后盖背面,探头略突出)。
+ 已去掉旁边多余的大“格子”(旧版支脚块 / 外挂仓)。 +

加载中…