Files
flockmodels/flock_camera_prop.scad
T
2026-07-20 23:33:02 +00:00

299 lines
8.5 KiB
OpenSCAD

/*
Flock-style Camera Prop — non-functional film/political-commentary prop
Approximate exterior geometry derived from publicly visible product photos.
Not dimensionally exact and not suitable for surveillance, mounting in traffic,
impersonating an operational device, or outdoor deployment.
Open in OpenSCAD. Set PART below, press F6, then Export STL.
PART options:
"assembly"
"front_shell"
"rear_shell"
"lens_insert"
"lower_badge"
"pole_bracket"
"solar_panel"
"solar_frame"
*/
PART = "assembly";
// ---------- Main dimensions, mm ----------
body_w = 112;
body_h = 245;
body_d = 70;
corner_r = 28;
shell_wall = 2.6;
split_gap = 0.35;
hood_depth = 28;
hood_overhang = 10;
lens_outer_d = 51;
lens_recess_d = 4;
lens_center_z = 72;
badge_d = 28;
badge_center_z = -74;
pole_d = 60.3; // nominal 2-inch schedule-40 PVC outside diameter
bracket_wall = 5;
bracket_width = 72;
bracket_depth = 46;
panel_w = 310;
panel_h = 205;
panel_t = 10;
panel_frame = 9;
$fn = 72;
// ---------- Helpers ----------
module rounded_prism(w,h,d,r){
linear_extrude(height=d, center=true)
hull()
for (x=[-w/2+r,w/2-r], y=[-h/2+r,h/2-r])
translate([x,y]) circle(r=r);
}
module soft_body(d=body_d){
// Subtle taper: narrower near bottom.
hull(){
translate([0, 20, 0]) rounded_prism(body_w, body_h-40, d, corner_r);
translate([0,-body_h/2+29,0])
rounded_prism(body_w-16, 58, d, 24);
}
}
module face_features(depth=8){
// Lens recess
translate([0, body_h/2-lens_center_z, body_d/2-depth/2+0.1])
cylinder(d=lens_outer_d+2, h=depth, center=true);
// Lower circular badge recess
translate([0, body_h/2-(body_h/2-badge_center_z), body_d/2-depth/2+0.1])
cylinder(d=badge_d+2, h=depth, center=true);
// Small status aperture under lens
translate([0, body_h/2-lens_center_z-39, body_d/2-depth/2+0.1])
cylinder(d=9, h=depth, center=true);
}
module camera_outer(){
difference(){
soft_body();
// Flatten rear slightly
translate([0,0,-body_d/2-10])
cube([body_w+30, body_h+30, 20], center=true);
}
// Flock-like asymmetric hood around upper face
translate([0, body_h/2-lens_center_z+5, body_d/2+hood_depth/2-2])
difference(){
rounded_prism(body_w+2*hood_overhang, 104, hood_depth, 25);
translate([0,-5,-hood_depth/2+5])
rounded_prism(body_w-5, 92, hood_depth+4, 20);
// Open the lower hood edge
translate([0,-56,0])
cube([body_w+40,38,hood_depth+10],center=true);
}
}
module interior_void(){
scale([(body_w-2*shell_wall)/body_w,
(body_h-2*shell_wall)/body_h,
(body_d-2*shell_wall)/body_d])
soft_body(body_d);
}
module alignment_pins(front=true){
z = front ? -body_d/2+9 : body_d/2-9;
for (x=[-36,36], y=[-75,75])
translate([x,y,z])
cylinder(d=5.2, h=8, center=true);
}
module alignment_holes(front=true){
z = front ? -body_d/2+9 : body_d/2-9;
for (x=[-36,36], y=[-75,75])
translate([x,y,z])
cylinder(d=5.8, h=10, center=true);
}
module front_shell(){
difference(){
intersection(){
camera_outer();
translate([0,0,body_d/4])
cube([body_w+80, body_h+80, body_d/2-split_gap], center=true);
}
interior_void();
face_features(12);
alignment_holes(false);
// Optional 3 mm screw clearance through side tabs
for (y=[-75,75])
translate([0,y,body_d/2-8])
rotate([0,90,0]) cylinder(d=3.4,h=body_w+30,center=true);
}
// Rearward registration pins
alignment_pins(false);
}
module rear_shell(){
difference(){
intersection(){
camera_outer();
translate([0,0,-body_d/4])
cube([body_w+80, body_h+80, body_d/2-split_gap], center=true);
}
interior_void();
alignment_holes(true);
// Flat mounting slots
for (y=[-60,25])
translate([0,y,-body_d/2+3])
cube([42,7,10],center=true);
for (y=[-75,75])
translate([0,y,-body_d/2+8])
rotate([0,90,0]) cylinder(d=3.4,h=body_w+30,center=true);
}
alignment_pins(true);
}
module lens_insert(){
difference(){
union(){
cylinder(d=lens_outer_d, h=5, center=true);
translate([0,0,2.8])
cylinder(d1=lens_outer_d-7,d2=lens_outer_d-12,h=4,center=true);
}
// Central camera opening
cylinder(d=18,h=12,center=true);
// Ring of fake IR emitters
for(a=[0:45:315])
rotate([0,0,a]) translate([15.5,0,0])
cylinder(d=6.2,h=12,center=true);
}
}
module lower_badge(){
union(){
cylinder(d=badge_d,h=3.2,center=true);
translate([0,0,1.8])
linear_extrude(height=1.2,center=true)
text("F",size=15,halign="center",valign="center",
font="Liberation Sans:style=Bold");
}
}
module wordmark(){
// Separate raised lettering for gluing to the side or panel.
linear_extrude(height=1.6)
text("FLOCK",size=17,halign="center",valign="center",
font="Liberation Sans:style=Bold",spacing=0.95);
}
module pole_bracket(){
difference(){
union(){
// Camera-side mounting plate
translate([0,0,0])
rounded_prism(bracket_width, bracket_depth, bracket_wall, 7);
// Curved pole saddle
translate([0,0,-18])
rotate([90,0,0])
difference(){
cylinder(d=pole_d+2*bracket_wall,h=bracket_depth,center=true);
cylinder(d=pole_d+0.8,h=bracket_depth+3,center=true);
translate([0,-(pole_d+20)/2,0])
cube([pole_d+30,pole_d+30,bracket_depth+10],center=true);
}
// Two gussets
for(x=[-25,25])
hull(){
translate([x,0,-2]) cube([7,bracket_depth-8,4],center=true);
translate([x,0,-20]) cube([7,bracket_depth-8,4],center=true);
}
}
// Camera attachment holes
for (x=[-22,22], y=[-12,12])
translate([x,y,0])
cylinder(d=4.2,h=bracket_wall+8,center=true);
// Slots for stainless hose clamps
for (z=[-12,-28])
translate([0,0,z])
rotate([90,0,0])
cube([58,5,bracket_depth+20],center=true);
}
}
module solar_panel(){
difference(){
rounded_prism(panel_w,panel_h,panel_t,7);
translate([0,0,panel_t/2-2])
rounded_prism(panel_w-2*panel_frame,
panel_h-2*panel_frame,5,3);
}
// Simple fake cell grid
translate([0,0,panel_t/2+0.5])
for(x=[-panel_w/2+panel_frame+18:32:panel_w/2-panel_frame-18])
cube([1.2,panel_h-2*panel_frame-8,1],center=true);
translate([0,0,panel_t/2+0.5])
for(y=[-panel_h/2+panel_frame+18:30:panel_h/2-panel_frame-18])
cube([panel_w-2*panel_frame-8,1.2,1],center=true);
}
module solar_frame(){
// Compact printable tilt bracket, intended as a visual prop only.
difference(){
union(){
cube([95,48,7],center=true);
for(x=[-34,34])
translate([x,0,-26])
cube([9,42,52],center=true);
}
for(x=[-34,34])
translate([x,0,-26])
rotate([90,0,0]) cylinder(d=6.4,h=60,center=true);
for(x=[-34,34], y=[-15,15])
translate([x,y,0]) cylinder(d=4.5,h=15,center=true);
}
}
module assembly(){
// Pole is a preview only; do not export it.
color("dimgray")
translate([0,0,-160]) cylinder(d=pole_d,h=650,center=true);
color("black")
translate([0,-40,45]) rotate([90,0,0]) camera_outer();
color("gray")
translate([0,-10,35]) rotate([90,0,0]) pole_bracket();
color("midnightblue")
translate([0,5,250]) rotate([65,0,0]) solar_panel();
color("gray")
translate([0,0,190]) rotate([0,0,0]) solar_frame();
}
// ---------- Part selector ----------
if (PART == "assembly") assembly();
else if (PART == "front_shell") rotate([0,180,0]) front_shell();
else if (PART == "rear_shell") rear_shell();
else if (PART == "lens_insert") lens_insert();
else if (PART == "lower_badge") lower_badge();
else if (PART == "pole_bracket") pole_bracket();
else if (PART == "solar_panel") solar_panel();
else if (PART == "solar_frame") solar_frame();
else if (PART == "wordmark") wordmark();