generated from CubeCraft-Creations/Tracehound
Dev #26
@@ -1,6 +1,6 @@
|
|||||||
module github.com/cubecraft/remoterig
|
module github.com/cubecraft/remoterig
|
||||||
|
|
||||||
go 1.25.0
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/eclipse/paho.mqtt.golang v1.5.0
|
github.com/eclipse/paho.mqtt.golang v1.5.0
|
||||||
@@ -12,9 +12,12 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/net v0.27.0 // indirect
|
||||||
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
modernc.org/libc v1.72.3 // indirect
|
modernc.org/libc v1.72.3 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/eclipse/paho.mqtt.golang v1.5.0 h1:EH+bUVJNgttidWFkLLVKaQPGmkTUfQQqjOsyvMGvD6o=
|
||||||
|
github.com/eclipse/paho.mqtt.golang v1.5.0/go.mod h1:du/2qNQVqJf/Sqs4MEL77kR8QTqANF7XU7Fk0aOTAgk=
|
||||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
@@ -15,30 +17,24 @@ github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm
|
|||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
||||||
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
|
||||||
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
|
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
|
||||||
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
|
|
||||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
|
||||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
|
||||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
|
||||||
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
|
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
|
||||||
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
|
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
@@ -46,12 +42,8 @@ modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJ
|
|||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
|
||||||
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
|
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
|
||||||
modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
include <tripod-case-v3.scad>;
|
|
||||||
render(convexity=10) case_body();
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
include <tripod-case-v3.scad>;
|
|
||||||
render(convexity=10) case_lid();
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
include <tripod-case-v3.scad>;
|
|
||||||
render(convexity=10) full_case();
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
include <tripod-case-v3.scad>;
|
|
||||||
render(convexity=10) tripod_clamp();
|
|
||||||
Binary file not shown.
@@ -1,217 +0,0 @@
|
|||||||
// RemoteRig — Dual-ESP Tripod Case v3
|
|
||||||
// v3 changes: screw-tightened tripod clamp + dovetail slide interface.
|
|
||||||
// Coordinate system: all case/lid geometry uses bottom-origin Z.
|
|
||||||
|
|
||||||
$fn = 36;
|
|
||||||
|
|
||||||
// Board dimensions
|
|
||||||
esp8266_w = 34.2; esp8266_d = 25.6; esp8266_h = 5;
|
|
||||||
esp32_w = 52; esp32_d = 28; esp32_h = 5;
|
|
||||||
board_gap = 3;
|
|
||||||
stack_h = esp8266_h + esp32_h + board_gap;
|
|
||||||
inner_w = max(esp8266_w, esp32_w);
|
|
||||||
inner_d = max(esp8266_d, esp32_d);
|
|
||||||
inner_h = stack_h + 2;
|
|
||||||
|
|
||||||
// Case parameters
|
|
||||||
wall = 2.0;
|
|
||||||
tol = 0.4;
|
|
||||||
outer_w = inner_w + wall*2 + tol*2; // 56.8mm
|
|
||||||
outer_d = inner_d + wall*2 + tol*2; // 32.8mm
|
|
||||||
outer_h = inner_h + wall*2; // 19mm
|
|
||||||
corner_r = 2.5;
|
|
||||||
|
|
||||||
// Tripod clamp parameters
|
|
||||||
pole_dia = 35; // nominal stand/pole diameter
|
|
||||||
clamp_thick = 4.0; // ring wall thickness
|
|
||||||
clamp_width = 16.0; // extrusion width along Z
|
|
||||||
mouth_width = 13.0; // clamp opening
|
|
||||||
m3_clearance = 3.4; // M3 screw clearance
|
|
||||||
nut_flat = 6.4; // M3 nut trap flat-to-flat
|
|
||||||
|
|
||||||
// Dovetail slide interface
|
|
||||||
// Male rail is on the case; matching female socket is on the tripod clamp.
|
|
||||||
// This is easier to inspect and avoids the previous mismatched "two lips + tab" geometry.
|
|
||||||
rail_z = outer_h * 0.78;
|
|
||||||
rail_depth = 5.0;
|
|
||||||
rail_neck_w = 12.0; // narrow width at case wall / slot opening
|
|
||||||
rail_outer_w = 18.0; // wider retained edge
|
|
||||||
rail_clearance = 0.45; // FDM sliding clearance per side-ish
|
|
||||||
socket_wall = 2.2;
|
|
||||||
|
|
||||||
// Cable ports
|
|
||||||
usb_port_w = 12; usb_port_h = 6;
|
|
||||||
uart_port_w = 6; uart_port_h = 4;
|
|
||||||
|
|
||||||
// Uncomment one for manual OpenSCAD use
|
|
||||||
// full_case();
|
|
||||||
// case_body();
|
|
||||||
// case_lid();
|
|
||||||
// tripod_clamp();
|
|
||||||
|
|
||||||
module rounded_cube_centered(w, d, h, r) {
|
|
||||||
hull() {
|
|
||||||
for (x = [-1, 1], y = [-1, 1], z = [-1, 1]) {
|
|
||||||
translate([x*(w/2 - r), y*(d/2 - r), z*(h/2 - r)])
|
|
||||||
sphere(r=r, $fn=24);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module rounded_cube0(w, d, h, r) {
|
|
||||||
translate([0, 0, h/2]) rounded_cube_centered(w, d, h, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
module hex_prism(d, h) {
|
|
||||||
cylinder(d=d, h=h, center=true, $fn=6);
|
|
||||||
}
|
|
||||||
|
|
||||||
module dovetail_prism(length_z, front_w, back_w, depth) {
|
|
||||||
// 2D profile is X/Y, extruded along Z.
|
|
||||||
rotate([0, 0, 0])
|
|
||||||
linear_extrude(height=length_z, center=true, convexity=10)
|
|
||||||
polygon(points=[
|
|
||||||
[-front_w/2, 0], [front_w/2, 0],
|
|
||||||
[back_w/2, depth], [-back_w/2, depth]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
module case_shell() {
|
|
||||||
difference() {
|
|
||||||
rounded_cube0(outer_w, outer_d, outer_h, corner_r);
|
|
||||||
|
|
||||||
// Open internal cavity: starts above bottom wall, extends past top.
|
|
||||||
translate([0, 0, wall])
|
|
||||||
rounded_cube0(inner_w + tol, inner_d + tol, outer_h + 2, 1.6);
|
|
||||||
|
|
||||||
// USB power IN / OUT ports through front/back walls.
|
|
||||||
translate([0, outer_d/2 + 0.1, wall + 4])
|
|
||||||
cube([usb_port_w, wall*3, usb_port_h], center=true);
|
|
||||||
translate([0, -outer_d/2 - 0.1, wall + 4])
|
|
||||||
cube([usb_port_w, wall*3, usb_port_h], center=true);
|
|
||||||
|
|
||||||
// UART side channel.
|
|
||||||
translate([outer_w/2 + 0.1, 0, wall + 6])
|
|
||||||
cube([wall*3, uart_port_w, uart_port_h], center=true);
|
|
||||||
|
|
||||||
// LED viewing window on front lower wall.
|
|
||||||
translate([-outer_w/4, -outer_d/2 - 0.1, wall + 2])
|
|
||||||
cube([6, wall*2, 3], center=true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module screw_post(x, y) {
|
|
||||||
difference() {
|
|
||||||
translate([x, y, wall]) cylinder(d=5.0, h=outer_h-wall-0.5, center=false, $fn=24);
|
|
||||||
translate([x, y, wall-0.5]) cylinder(d=2.1, h=outer_h+1, center=false, $fn=20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module case_male_dovetail_rail() {
|
|
||||||
// Positive tapered rail on the case back. Cross-section is narrow at the
|
|
||||||
// wall and wider at the outside, so the clamp socket captures it.
|
|
||||||
translate([0, outer_d/2 - 0.15, outer_h/2])
|
|
||||||
dovetail_prism(rail_z, rail_neck_w, rail_outer_w, rail_depth);
|
|
||||||
|
|
||||||
// Bottom stop so the clamp socket cannot slide past the case.
|
|
||||||
translate([0, outer_d/2 + rail_depth/2, outer_h*0.12])
|
|
||||||
rounded_cube_centered(rail_outer_w + 3.0, rail_depth + 0.8, 2.4, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
module case_body() {
|
|
||||||
union() {
|
|
||||||
case_shell();
|
|
||||||
for (x = [-1, 1], y = [-1, 1])
|
|
||||||
screw_post(x*(outer_w/2 - 5), y*(outer_d/2 - 5));
|
|
||||||
case_male_dovetail_rail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module case_lid() {
|
|
||||||
difference() {
|
|
||||||
rounded_cube0(outer_w, outer_d, wall*2, 1.8);
|
|
||||||
|
|
||||||
for (x = [-1, 1], y = [-1, 1]) {
|
|
||||||
translate([x*(outer_w/2 - 5), y*(outer_d/2 - 5), -0.5])
|
|
||||||
cylinder(d=2.4, h=wall*2 + 1, center=false, $fn=20);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (x = [-outer_w/4, 0, outer_w/4]) {
|
|
||||||
translate([x, 0, wall*2/2])
|
|
||||||
cube([8, outer_d*0.6, wall*3], center=true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module clamp_ring_with_mouth() {
|
|
||||||
outer_r = pole_dia/2 + clamp_thick;
|
|
||||||
difference() {
|
|
||||||
cylinder(r=outer_r, h=clamp_width, center=true, $fn=72);
|
|
||||||
cylinder(r=pole_dia/2 + rail_clearance, h=clamp_width + 1, center=true, $fn=72);
|
|
||||||
// Mouth opens toward +Y. Width is intentionally generous for snap-on placement before tightening.
|
|
||||||
translate([0, outer_r, 0])
|
|
||||||
cube([mouth_width, outer_r*2, clamp_width + 2], center=true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module clamp_ears() {
|
|
||||||
outer_r = pole_dia/2 + clamp_thick;
|
|
||||||
ear_y = outer_r + 2.2;
|
|
||||||
ear_z = 0;
|
|
||||||
difference() {
|
|
||||||
union() {
|
|
||||||
translate([-mouth_width/2 - 3.2, ear_y, ear_z])
|
|
||||||
rounded_cube_centered(7.0, 9.0, clamp_width, 1.4);
|
|
||||||
translate([ mouth_width/2 + 3.2, ear_y, ear_z])
|
|
||||||
rounded_cube_centered(7.0, 9.0, clamp_width, 1.4);
|
|
||||||
}
|
|
||||||
// M3 screw passes across the mouth along X.
|
|
||||||
translate([0, ear_y, ear_z])
|
|
||||||
rotate([0, 90, 0]) cylinder(d=m3_clearance, h=mouth_width + 24, center=true, $fn=24);
|
|
||||||
// Nut trap on the right ear.
|
|
||||||
translate([mouth_width/2 + 3.2, ear_y, ear_z])
|
|
||||||
rotate([0, 90, 0]) hex_prism(nut_flat, 4.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module clamp_dovetail_socket() {
|
|
||||||
outer_r = pole_dia/2 + clamp_thick;
|
|
||||||
socket_outer_w = rail_outer_w + socket_wall*2;
|
|
||||||
socket_depth = rail_depth + socket_wall*2;
|
|
||||||
|
|
||||||
// Solid boss on the rear of the clamp, opposite the tightening mouth.
|
|
||||||
// A matching dovetail void is cut through it along Z so the case rail
|
|
||||||
// slides in from the top/bottom with practical FDM clearance.
|
|
||||||
difference() {
|
|
||||||
translate([0, -outer_r - socket_depth/2 + socket_wall, 0])
|
|
||||||
rounded_cube_centered(socket_outer_w, socket_depth, clamp_width, 1.2);
|
|
||||||
|
|
||||||
translate([0, -outer_r - 0.15, 0])
|
|
||||||
dovetail_prism(
|
|
||||||
clamp_width + 1.0,
|
|
||||||
rail_neck_w + rail_clearance,
|
|
||||||
rail_outer_w + rail_clearance,
|
|
||||||
rail_depth + 0.6
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module tripod_clamp() {
|
|
||||||
union() {
|
|
||||||
clamp_ring_with_mouth();
|
|
||||||
clamp_ears();
|
|
||||||
clamp_dovetail_socket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backward-compatible alias for earlier export scripts.
|
|
||||||
module tripod_clip() {
|
|
||||||
tripod_clamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
module full_case() {
|
|
||||||
case_body();
|
|
||||||
translate([0, 0, outer_h + 2]) case_lid();
|
|
||||||
translate([0, outer_d/2 + pole_dia/2 + clamp_thick + 8, outer_h/2])
|
|
||||||
rotate([90, 0, 0]) tripod_clamp();
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
// RemoteRig — Dual-ESP Tripod Case
|
|
||||||
// =================================
|
|
||||||
// Small box that clips onto a tripod leg or light stand pole.
|
|
||||||
// Holds ESP8266 D1 Mini + ESP32 Dev Board (stacked).
|
|
||||||
// Powered by standard USB battery pack. No camera sleeve needed.
|
|
||||||
//
|
|
||||||
// Print settings:
|
|
||||||
// Material: PETG | Layer: 0.2mm | Infill: 20% gyroid
|
|
||||||
// Supports: yes (for clip overhang) | Brim: 5mm
|
|
||||||
|
|
||||||
// ── Board dimensions ──
|
|
||||||
esp8266_w = 34.2; esp8266_d = 25.6; esp8266_h = 5;
|
|
||||||
esp32_w = 52; esp32_d = 28; esp32_h = 5;
|
|
||||||
board_gap = 3; // air gap between stacked boards
|
|
||||||
stack_h = esp8266_h + esp32_h + board_gap;
|
|
||||||
inner_w = max(esp8266_w, esp32_w);
|
|
||||||
inner_d = max(esp8266_d, esp32_d);
|
|
||||||
inner_h = stack_h + 2;
|
|
||||||
|
|
||||||
// ── Case parameters ──
|
|
||||||
wall = 2.0;
|
|
||||||
tol = 0.4;
|
|
||||||
outer_w = inner_w + wall*2 + tol*2;
|
|
||||||
outer_d = inner_d + wall*2 + tol*2;
|
|
||||||
outer_h = inner_h + wall*2;
|
|
||||||
|
|
||||||
// ── Tripod clip parameters ──
|
|
||||||
pole_min_dia = 20; // smallest pole
|
|
||||||
pole_max_dia = 35; // largest pole
|
|
||||||
clip_width = 12; // clip width
|
|
||||||
clip_thick = 3; // clip arm thickness
|
|
||||||
clip_grip = 2; // grip ridges
|
|
||||||
|
|
||||||
// ── Cable ports ──
|
|
||||||
usb_port_w = 12; usb_port_h = 6;
|
|
||||||
uart_port_w = 6; uart_port_h = 4;
|
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
// MAIN — render the full case
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
// Uncomment to render individual parts:
|
|
||||||
full_case();
|
|
||||||
// case_body();
|
|
||||||
// case_lid();
|
|
||||||
// tripod_clip();
|
|
||||||
|
|
||||||
module full_case() {
|
|
||||||
case_body();
|
|
||||||
// Lid positioned above (for visualization)
|
|
||||||
translate([0, 0, outer_h + 2])
|
|
||||||
case_lid();
|
|
||||||
// Clip on the back
|
|
||||||
translate([0, outer_d/2 + pole_max_dia/2 + clip_thick, outer_h/2])
|
|
||||||
tripod_clip();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
// Case Body — holds both boards, cable ports
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
module case_body() {
|
|
||||||
difference() {
|
|
||||||
// Outer shell
|
|
||||||
rounded_cube(outer_w, outer_d, outer_h, 3);
|
|
||||||
|
|
||||||
// Inner cavity
|
|
||||||
translate([0, 0, wall])
|
|
||||||
rounded_cube(inner_w + tol, inner_d + tol, inner_h + tol, 2);
|
|
||||||
|
|
||||||
// ── Board recesses ──
|
|
||||||
|
|
||||||
// Bottom: ESP32 (larger board)
|
|
||||||
translate([0, 0, wall + 1])
|
|
||||||
cube([esp32_w + tol, esp32_d + tol, esp32_h + 1], center=true);
|
|
||||||
|
|
||||||
// Top: ESP8266 (smaller board)
|
|
||||||
translate([0, 0, wall + esp32_h + board_gap + 1])
|
|
||||||
cube([esp8266_w + tol, esp8266_d + tol, esp8266_h + 1], center=true);
|
|
||||||
|
|
||||||
// ── Cable ports ──
|
|
||||||
|
|
||||||
// USB power IN (from battery pack → ESP32)
|
|
||||||
translate([0, outer_d/2, outer_h/3])
|
|
||||||
cube([usb_port_w, wall*3, usb_port_h], center=true);
|
|
||||||
|
|
||||||
// USB power OUT (from battery pack → GoPro)
|
|
||||||
translate([0, -outer_d/2, outer_h/3])
|
|
||||||
cube([usb_port_w, wall*3, usb_port_h], center=true);
|
|
||||||
|
|
||||||
// UART wire channel (ESP8266 → ESP32 internal)
|
|
||||||
translate([outer_w/2, 0, outer_h/2])
|
|
||||||
cube([wall*3, uart_port_w, uart_port_h], center=true);
|
|
||||||
|
|
||||||
// ── Ventilation slots (top edge) ──
|
|
||||||
for (x = [-outer_w/4, 0, outer_w/4]) {
|
|
||||||
translate([x, 0, outer_h - wall])
|
|
||||||
cube([8, outer_d*0.6, 2], center=true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Screw posts for lid ──
|
|
||||||
for (x = [-1, 1], y = [-1, 1]) {
|
|
||||||
translate([x*(outer_w/2 - 5), y*(outer_d/2 - 5), outer_h/2])
|
|
||||||
cylinder(d=3.2, h=outer_h, center=true, $fn=16);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── LED window (thin spot to see board LEDs) ──
|
|
||||||
translate([-outer_w/4, -outer_d/2, wall])
|
|
||||||
cube([6, 1, 3], center=true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Tripod clip mount (rail on back) ──
|
|
||||||
translate([0, outer_d/2, outer_h/2])
|
|
||||||
rotate([90, 0, 0])
|
|
||||||
difference() {
|
|
||||||
cube([clip_width + 4, outer_h*0.7, 6], center=true);
|
|
||||||
// T-slot for clip to slide in
|
|
||||||
cube([clip_width + 1, outer_h*0.7 + 1, 4], center=true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
// Case Lid — snap-fit or screw-on cover
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
module case_lid() {
|
|
||||||
difference() {
|
|
||||||
rounded_cube(outer_w, outer_d, wall*2, 2);
|
|
||||||
|
|
||||||
// Screw holes (match body posts)
|
|
||||||
for (x = [-1, 1], y = [-1, 1]) {
|
|
||||||
translate([x*(outer_w/2 - 5), y*(outer_d/2 - 5), 0])
|
|
||||||
cylinder(d=3.2, h=wall*3, center=true, $fn=16);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ventilation slots (match body)
|
|
||||||
for (x = [-outer_w/4, 0, outer_w/4]) {
|
|
||||||
translate([x, 0, 0])
|
|
||||||
cube([8, outer_d*0.6, 3], center=true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
// Tripod Clip — C-clamp for pole mounting
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
module tripod_clip() {
|
|
||||||
difference() {
|
|
||||||
union() {
|
|
||||||
// Main body
|
|
||||||
hull() {
|
|
||||||
translate([0, -pole_max_dia/2 - clip_thick, 0])
|
|
||||||
cube([clip_width, clip_thick*2, outer_h*0.7], center=true);
|
|
||||||
|
|
||||||
translate([0, pole_max_dia/2 + clip_thick, 0])
|
|
||||||
cube([clip_width, clip_thick*2, outer_h*0.7], center=true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top arm (flexible)
|
|
||||||
translate([0, -pole_max_dia/2 - clip_thick, outer_h*0.35])
|
|
||||||
cube([clip_width, pole_max_dia + clip_thick*4, clip_thick], center=true);
|
|
||||||
|
|
||||||
// Bottom arm
|
|
||||||
translate([0, -pole_max_dia/2 - clip_thick, -outer_h*0.35])
|
|
||||||
cube([clip_width, pole_max_dia + clip_thick*4, clip_thick], center=true);
|
|
||||||
|
|
||||||
// Mounting tab (slides into case rail)
|
|
||||||
translate([0, -pole_max_dia/2 - clip_thick*3, 0])
|
|
||||||
cube([clip_width + 1, clip_thick*2, outer_h*0.7], center=true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pole hole
|
|
||||||
cylinder(d=pole_max_dia + 2, h=outer_h*1.5, center=true, $fn=32);
|
|
||||||
|
|
||||||
// Grip ridges on inner surface
|
|
||||||
for (z = [-outer_h*0.25, 0, outer_h*0.25]) {
|
|
||||||
translate([0, 0, z])
|
|
||||||
rotate_extrude(angle=180, $fn=32)
|
|
||||||
translate([pole_max_dia/2 + 0.5, 0])
|
|
||||||
circle(d=1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry slot (pole slides in from front)
|
|
||||||
translate([0, pole_max_dia/2 + clip_thick, 0])
|
|
||||||
cube([clip_width + 2, pole_max_dia + 10, outer_h*0.7], center=true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
// Utility: rounded cube
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
module rounded_cube(w, d, h, r) {
|
|
||||||
hull() {
|
|
||||||
for (x = [-1, 1], y = [-1, 1], z = [-1, 1]) {
|
|
||||||
translate([x*(w/2 - r), y*(d/2 - r), z*(h/2 - r)])
|
|
||||||
sphere(r=r, $fn=20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,274 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>RemoteRig Case — 3D Viewer</title>
|
|
||||||
<style>
|
|
||||||
body { margin: 0; overflow: hidden; background: #1a1a2e; font-family: system-ui; }
|
|
||||||
canvas { display: block; }
|
|
||||||
#info {
|
|
||||||
position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%);
|
|
||||||
color: #888; font-size: 13px; pointer-events: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
||||||
<script>
|
|
||||||
// ── Scene setup ──
|
|
||||||
const scene = new THREE.Scene();
|
|
||||||
scene.background = new THREE.Color(0x1a1a2e);
|
|
||||||
scene.fog = new THREE.Fog(0x1a1a2e, 8, 25);
|
|
||||||
|
|
||||||
const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.5, 50);
|
|
||||||
camera.position.set(5, 3.5, 7);
|
|
||||||
camera.lookAt(0, 0, 0);
|
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
renderer.shadowMap.enabled = true;
|
|
||||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
||||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
||||||
renderer.toneMappingExposure = 1.2;
|
|
||||||
document.body.appendChild(renderer.domElement);
|
|
||||||
|
|
||||||
// ── Lighting ──
|
|
||||||
const ambient = new THREE.AmbientLight(0x404060, 0.6);
|
|
||||||
scene.add(ambient);
|
|
||||||
const key = new THREE.DirectionalLight(0xffffff, 1.2);
|
|
||||||
key.position.set(8, 10, 5);
|
|
||||||
key.castShadow = true;
|
|
||||||
key.shadow.mapSize.set(2048, 2048);
|
|
||||||
key.shadow.camera.near = 0.5; key.shadow.camera.far = 50;
|
|
||||||
key.shadow.camera.left = -10; key.shadow.camera.right = 10;
|
|
||||||
key.shadow.camera.top = 10; key.shadow.camera.bottom = -10;
|
|
||||||
scene.add(key);
|
|
||||||
const fill = new THREE.DirectionalLight(0x8899cc, 0.4);
|
|
||||||
fill.position.set(-3, 2, -2);
|
|
||||||
scene.add(fill);
|
|
||||||
const rim = new THREE.DirectionalLight(0xaaccff, 0.5);
|
|
||||||
rim.position.set(0, 1, -5);
|
|
||||||
scene.add(rim);
|
|
||||||
|
|
||||||
// ── Ground ──
|
|
||||||
const ground = new THREE.Mesh(
|
|
||||||
new THREE.PlaneGeometry(20, 20),
|
|
||||||
new THREE.MeshStandardMaterial({ color: 0x2a2a3e, roughness: 0.8 })
|
|
||||||
);
|
|
||||||
ground.rotation.x = -Math.PI/2;
|
|
||||||
ground.position.y = -3;
|
|
||||||
ground.receiveShadow = true;
|
|
||||||
scene.add(ground);
|
|
||||||
|
|
||||||
// ── Materials ──
|
|
||||||
const petgMat = new THREE.MeshStandardMaterial({
|
|
||||||
color: 0x3d3d4a, roughness: 0.35, metalness: 0.1,
|
|
||||||
});
|
|
||||||
const accentMat = new THREE.MeshStandardMaterial({
|
|
||||||
color: 0xf59e0b, roughness: 0.3, metalness: 0.2, emissive: 0x331100, emissiveIntensity: 0.3
|
|
||||||
});
|
|
||||||
const boardMat = new THREE.MeshStandardMaterial({
|
|
||||||
color: 0x1a6630, roughness: 0.6
|
|
||||||
});
|
|
||||||
const metalMat = new THREE.MeshStandardMaterial({
|
|
||||||
color: 0x888899, roughness: 0.3, metalness: 0.8
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Create rounded box with bevel ──
|
|
||||||
function createRoundedBox(w, h, d, r, segments = 3) {
|
|
||||||
const shape = new THREE.Shape();
|
|
||||||
const hw = w/2 - r, hh = h/2 - r;
|
|
||||||
shape.moveTo(-hw, -hh + r);
|
|
||||||
shape.quadraticCurveTo(-hw, -hh, -hw + r, -hh);
|
|
||||||
shape.lineTo(hw - r, -hh);
|
|
||||||
shape.quadraticCurveTo(hw, -hh, hw, -hh + r);
|
|
||||||
shape.lineTo(hw, hh - r);
|
|
||||||
shape.quadraticCurveTo(hw, hh, hw - r, hh);
|
|
||||||
shape.lineTo(-hw + r, hh);
|
|
||||||
shape.quadraticCurveTo(-hw, hh, -hw, hh - r);
|
|
||||||
shape.closePath();
|
|
||||||
|
|
||||||
const extrudeSettings = { depth: d - r*2, bevelEnabled: true, bevelThickness: r, bevelSize: r, bevelSegments: segments };
|
|
||||||
const geom = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
|
||||||
geom.translate(0, 0, -d/2 + r);
|
|
||||||
return geom;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Case Body ──
|
|
||||||
const caseW = 2.5, caseH = 1.5, caseD = 1.1;
|
|
||||||
const bodyGeom = createRoundedBox(caseW, caseD, caseH, 0.12);
|
|
||||||
const body = new THREE.Mesh(bodyGeom, petgMat);
|
|
||||||
body.castShadow = true; body.receiveShadow = true;
|
|
||||||
scene.add(body);
|
|
||||||
|
|
||||||
// ── Lid (slightly offset) ──
|
|
||||||
const lidGeom = createRoundedBox(caseW, caseD, 0.15, 0.08);
|
|
||||||
const lid = new THREE.Mesh(lidGeom, petgMat);
|
|
||||||
lid.position.y = caseH/2 + 0.07;
|
|
||||||
lid.castShadow = true;
|
|
||||||
scene.add(lid);
|
|
||||||
|
|
||||||
// ── Ventilation slots ──
|
|
||||||
for (let i = -0.6; i <= 0.6; i += 0.6) {
|
|
||||||
const slot = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(0.4, 0.04, caseD * 0.7),
|
|
||||||
new THREE.MeshStandardMaterial({ color: 0x1a1a2e })
|
|
||||||
);
|
|
||||||
slot.position.set(i, caseH/2 + 0.15, 0);
|
|
||||||
scene.add(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Screws ──
|
|
||||||
for (let x = -1; x <= 1; x += 2) {
|
|
||||||
for (let z = -0.35; z <= 0.35; z += 0.7) {
|
|
||||||
const screw = new THREE.Mesh(
|
|
||||||
new THREE.CylinderGeometry(0.05, 0.05, 0.04, 8),
|
|
||||||
metalMat
|
|
||||||
);
|
|
||||||
screw.position.set(x * (caseW/2 - 0.2), caseH/2 + 0.15, z);
|
|
||||||
scene.add(screw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Boards inside (semi-visible) ──
|
|
||||||
const esp32Board = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(caseW - 0.3, 0.04, caseD - 0.2),
|
|
||||||
boardMat
|
|
||||||
);
|
|
||||||
esp32Board.position.set(0, caseH/2 - 0.15, 0);
|
|
||||||
esp32Board.castShadow = true;
|
|
||||||
scene.add(esp32Board);
|
|
||||||
|
|
||||||
const esp8266Board = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(caseW - 0.5, 0.04, caseD - 0.3),
|
|
||||||
boardMat
|
|
||||||
);
|
|
||||||
esp8266Board.position.set(0, caseH/2 - 0.08, 0);
|
|
||||||
esp8266Board.castShadow = true;
|
|
||||||
scene.add(esp8266Board);
|
|
||||||
|
|
||||||
// Chip on ESP32
|
|
||||||
const chip = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(0.3, 0.03, 0.3),
|
|
||||||
new THREE.MeshStandardMaterial({ color: 0x111122, roughness: 0.2 })
|
|
||||||
);
|
|
||||||
chip.position.set(0, caseH/2 - 0.12, 0);
|
|
||||||
scene.add(chip);
|
|
||||||
|
|
||||||
// LED
|
|
||||||
const led = new THREE.Mesh(
|
|
||||||
new THREE.SphereGeometry(0.03, 8, 8),
|
|
||||||
new THREE.MeshStandardMaterial({ color: 0x00ff44, roughness: 0.2, emissive: 0x00ff44, emissiveIntensity: 1.5 })
|
|
||||||
);
|
|
||||||
led.position.set(-0.8, caseH/2 - 0.12, -0.3);
|
|
||||||
scene.add(led);
|
|
||||||
|
|
||||||
// ── USB Port (front face) ──
|
|
||||||
const usbPort = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(0.35, 0.02, 0.15),
|
|
||||||
new THREE.MeshStandardMaterial({ color: 0x111122, roughness: 0.2 })
|
|
||||||
);
|
|
||||||
usbPort.position.set(0, 0.2, caseD/2);
|
|
||||||
scene.add(usbPort);
|
|
||||||
|
|
||||||
// ── Tripod Clip ──
|
|
||||||
const clipGroup = new THREE.Group();
|
|
||||||
clipGroup.position.set(0, 0, -caseD/2 - 0.7);
|
|
||||||
|
|
||||||
// Clip arms
|
|
||||||
for (let y = -0.4; y <= 0.4; y += 0.8) {
|
|
||||||
const arm = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(0.4, 0.08, 0.8),
|
|
||||||
petgMat
|
|
||||||
);
|
|
||||||
arm.position.set(0, y, 0.3);
|
|
||||||
arm.castShadow = true;
|
|
||||||
clipGroup.add(arm);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip body
|
|
||||||
const clipBody = new THREE.Mesh(
|
|
||||||
new THREE.BoxGeometry(0.4, 1.0, 0.15),
|
|
||||||
petgMat
|
|
||||||
);
|
|
||||||
clipBody.position.set(0, 0, -0.1);
|
|
||||||
clipBody.castShadow = true;
|
|
||||||
clipGroup.add(clipBody);
|
|
||||||
|
|
||||||
scene.add(clipGroup);
|
|
||||||
|
|
||||||
// ── Tripod Pole ──
|
|
||||||
const poleGeom = new THREE.CylinderGeometry(0.35, 0.35, 6, 24);
|
|
||||||
const poleMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.4, metalness: 0.3 });
|
|
||||||
const pole = new THREE.Mesh(poleGeom, poleMat);
|
|
||||||
pole.position.set(0, 0, -caseD/2 - 1.2);
|
|
||||||
pole.castShadow = true; pole.receiveShadow = true;
|
|
||||||
scene.add(pole);
|
|
||||||
|
|
||||||
// ── USB Cables ──
|
|
||||||
function createCable(start, end, color = 0x222233) {
|
|
||||||
const curve = new THREE.CubicBezierCurve3(
|
|
||||||
start,
|
|
||||||
new THREE.Vector3(start.x + 0.5, start.y - 0.5, start.z + 0.2),
|
|
||||||
new THREE.Vector3(end.x - 0.3, end.y - 0.3, end.z + 0.1),
|
|
||||||
end
|
|
||||||
);
|
|
||||||
const geom = new THREE.TubeGeometry(curve, 20, 0.03, 8, false);
|
|
||||||
const mat = new THREE.MeshStandardMaterial({ color, roughness: 0.6 });
|
|
||||||
return new THREE.Mesh(geom, mat);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cable1 = createCable(
|
|
||||||
new THREE.Vector3(0, 0.2, caseD/2),
|
|
||||||
new THREE.Vector3(-2, -1, 1)
|
|
||||||
);
|
|
||||||
cable1.castShadow = true;
|
|
||||||
scene.add(cable1);
|
|
||||||
|
|
||||||
const cable2 = createCable(
|
|
||||||
new THREE.Vector3(0.1, 0.2, caseD/2),
|
|
||||||
new THREE.Vector3(2, -1.5, 1.2),
|
|
||||||
0x332222
|
|
||||||
);
|
|
||||||
cable2.castShadow = true;
|
|
||||||
scene.add(cable2);
|
|
||||||
|
|
||||||
// ── Interaction ──
|
|
||||||
let isDragging = false, prevMouse = { x: 0, y: 0 };
|
|
||||||
let rotY = 0.4, rotX = 0.3, zoom = 7;
|
|
||||||
|
|
||||||
document.addEventListener('mousedown', e => { isDragging = true; prevMouse = { x: e.clientX, y: e.clientY }; });
|
|
||||||
document.addEventListener('mouseup', () => isDragging = false);
|
|
||||||
document.addEventListener('mousemove', e => {
|
|
||||||
if (!isDragging) return;
|
|
||||||
rotY += (e.clientX - prevMouse.x) * 0.005;
|
|
||||||
rotX += (e.clientY - prevMouse.y) * 0.005;
|
|
||||||
rotX = Math.max(-0.8, Math.min(1.2, rotX));
|
|
||||||
prevMouse = { x: e.clientX, y: e.clientY };
|
|
||||||
});
|
|
||||||
document.addEventListener('wheel', e => {
|
|
||||||
zoom += e.deltaY * 0.005;
|
|
||||||
zoom = Math.max(3, Math.min(15, zoom));
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Render loop ──
|
|
||||||
function animate() {
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
camera.position.x = zoom * Math.sin(rotY) * Math.cos(rotX);
|
|
||||||
camera.position.y = zoom * Math.sin(rotX);
|
|
||||||
camera.position.z = zoom * Math.cos(rotY) * Math.cos(rotX);
|
|
||||||
camera.lookAt(0, -0.1, 0);
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
animate();
|
|
||||||
|
|
||||||
// Resize
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
camera.aspect = window.innerWidth / window.innerHeight;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -209,6 +209,21 @@ func (s *Subscriber) handleStatus(cameraID string, payload []byte) {
|
|||||||
prevRecording = -1 // no previous status
|
prevRecording = -1 // no previous status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CUB-230: Deduplication check - skip if same (camera_id, recorded_at) exists
|
||||||
|
// This handles replayed entries from offline buffering
|
||||||
|
var dupCount int
|
||||||
|
err = s.db.QueryRow(`
|
||||||
|
SELECT COUNT(*) FROM status_logs
|
||||||
|
WHERE camera_id = ? AND recorded_at = ?
|
||||||
|
`, cameraID, ts).Scan(&dupCount)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("MQTT status dedup check error for %s: %v", cameraID, err)
|
||||||
|
// Continue anyway if check fails
|
||||||
|
} else if dupCount > 0 {
|
||||||
|
log.Printf("MQTT status deduplicated (camera_id=%s, recorded_at=%s) - replay from offline buffer", cameraID, ts.Format("2006-01-02 15:04:05"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Insert status_log
|
// Insert status_log
|
||||||
_, err = s.db.Exec(`
|
_, err = s.db.Exec(`
|
||||||
INSERT INTO status_logs (camera_id, recorded_at, battery_pct,
|
INSERT INTO status_logs (camera_id, recorded_at, battery_pct,
|
||||||
|
|||||||
Reference in New Issue
Block a user