fix to avoid coincident edges that mess up OpenSCAD meshes

This commit is contained in:
2023-12-24 08:36:39 +00:00
parent 9ee8a8cf61
commit 4fa233b799
6 changed files with 300 additions and 79 deletions

View File

@@ -1,67 +1,76 @@
include <lib/defaults.scad> include <lib/defaults.scad>
use <lib/primitives.scad> use <lib/primitives.scad>
use <lib/transformations.scad>
// thickness of case walls // thickness of case walls
Thickness = 3.5; Thickness = 2.5;
// how large of cuts (radial) to make around each component // how large of cuts (radial) to make around each component
MarginCameraCut = 2; MarginCameraCut = 2;
MarginButtonsCut = 2.8; MarginButtonsCut = 2.8;
MarginPortsCut = 5.5; MarginPortsCut = 7.0;
MarginButtonsSeat = 1.5; // radial margin; this value (halved) becomes the margin for thickness
MarginButtonsSeat = 1.4;
// how far into the xy plane to extend the part of the case which covers the front of the phone. // how far into the xy plane to extend the part of the case which covers the front of the phone.
// the top of the phone contains stuff we don't want to cover (camera); the bottom has more margin // the top of the phone contains stuff we don't want to cover (camera); the bottom has more margin
FrontOverhangX = 4; FrontOverhangX = 3;
FrontOverhangTop = 5; FrontOverhangTop = 3.5;
FrontOverhangBot = 7; FrontOverhangBot = 6;
// shift the overhang into the screen, to account that the case has some flex // shift the overhang into the screen, to account that the case has some flex
FrontSquashZ = 1.2; FrontSquashZ = 1.2;
// gives a 1cm margin around the whole body of the phone, but only in the xy plane. // gives a wide margin around the whole body of the phone, but only in the xy plane.
module _PhoneBulkLayer(tol=tol) module _PhoneBulkLayer(zMin=0)
{ {
minkowski() { hull() {
children(/*body*/); translate([-500, -500, zMin]) extractZDomain() children(/*body*/);
cube([20, 20, tol], center=true); translate([ 500, -500, zMin]) extractZDomain() children(/*body*/);
} translate([ 500, 500, zMin]) extractZDomain() children(/*body*/);
translate([-500, 500, zMin]) extractZDomain() children(/*body*/);
};
} }
/// gives as 1cm margin around the whole body of the phone, /// gives a margin around the whole body of the phone,
/// on all axis EXCEPT the -z axis /// on all axes EXCEPT the -z axis
module _PhoneBulkAndBack() module _PhoneBulkAndBack(zMin=0)
{ {
minkowski() { hull() {
children(/*body*/); _PhoneBulkLayer(zMin=zMin) children(/*body*/);
translate([0, 0, 5+tol]) cube([20, 20, 10], center=true); translate([0, 0, 10]) _PhoneBulkLayer(zMin=zMin) children(/*body*/);
} }
} }
module _DilatedBody(ButtonStyle) { module _DilatedBody(ButtonStyle) {
body = 0; body = 0;
buttons = 1; buttons = 1;
union() {
minkowski() { minkowski() {
sphere(r=Thickness); sphere(r=Thickness);
union() {
children(body); children(body);
};
if (ButtonStyle == "extrude") { if (ButtonStyle == "extrude") {
minkowski() {
sphere(r=Thickness);
// cylinderX(r=Thickness, h=2*Thickness, center=true);
children(buttons); children(buttons);
}; };
} }
} };
} }
module _DilatedBodyExceptFront(ButtonStyle) { module _DilatedBodyExceptFront(ButtonStyle, zMin=0) {
body = 0; body = 0;
buttons = 1; buttons = 1;
intersection() { intersection() {
_DilatedBody() { _DilatedBody(ButtonStyle=ButtonStyle) {
children(body); children(body);
children(buttons); children(buttons);
}; };
_PhoneBulkAndBack() children(body); _PhoneBulkAndBack(zMin=zMin) children(body);
} }
} }
/// returns the geometry which should be removed from the case, to make room for ports/buttons
module _PeripheralCutouts(ButtonStyle) { module _PeripheralCutouts(ButtonStyle) {
body = 0; body = 0;
buttons = 1; buttons = 1;
@@ -69,7 +78,8 @@ module _PeripheralCutouts(ButtonStyle) {
camera = 3; camera = 3;
union() { union() {
minkowski() { minkowski() {
sphere(r=MarginCameraCut); // sphere(r=MarginCameraCut);
cylinderZ(r=MarginCameraCut, h=2*Thickness+tol, center=true);
children(camera); children(camera);
}; };
// restrict these cutouts to just the phone bulk -- don't let them impact the back of the case // restrict these cutouts to just the phone bulk -- don't let them impact the back of the case
@@ -84,6 +94,7 @@ module _PeripheralCutouts(ButtonStyle) {
}; };
minkowski() { minkowski() {
sphere(r=MarginPortsCut); sphere(r=MarginPortsCut);
// cylinderY(r=MarginPortsCut, h=2*MarginPortsCut, center=true);
children(ports); children(ports);
}; };
}; };
@@ -91,11 +102,11 @@ module _PeripheralCutouts(ButtonStyle) {
}; };
}; };
module _FrontFace(tol=tol) { module _FrontFace() {
color("thistle") color("thistle")
difference() { difference() {
translate([0, 0, -Thickness]) children(/*body*/); translate([0, 0, -Thickness]) children(/*body*/);
_PhoneBulkLayer(tol=tol) children(/*body*/); _PhoneBulkLayer() children(/*body*/);
}; };
} }
@@ -107,6 +118,7 @@ module _FrontKeep(tol=tol) {
minkowski() { minkowski() {
// take the outline of the front face of the phone // take the outline of the front face of the phone
difference() { difference() {
// TODO: projection, then hull
minkowski() { minkowski() {
cylinder(r=2*tol, h=tol, center=true); cylinder(r=2*tol, h=tol, center=true);
_FrontFace() children(/*body*/); _FrontFace() children(/*body*/);
@@ -123,23 +135,15 @@ module _FrontKeep(tol=tol) {
}; };
} }
/// returns the region which we want to subtract from the case, in order to keep the front screen accessible. /// complete case except for cutouts
/// the returned cutout is strictly z <= 0, module _CaseExceptFeatures(ButtonStyle, tol=tol)
module _FrontCutout() {
difference() {
_FrontFace() children(/*body*/);
_FrontKeep() children(/*body*/);
};
}
module _CaseExceptFeatures(ButtonStyle)
{ {
body = 0; body = 0;
buttons = 1; buttons = 1;
union() { union() {
difference() { difference() {
// start by dilating the phone // start by dilating the phone
_DilatedBodyExceptFront(ButtonStyle=ButtonStyle) { _DilatedBodyExceptFront(ButtonStyle=ButtonStyle, zMin=tol) {
children(body); children(body);
children(buttons); children(buttons);
}; };
@@ -149,7 +153,7 @@ module _CaseExceptFeatures(ButtonStyle)
// add in the front part of the case // add in the front part of the case
intersection() { intersection() {
_FrontKeep() translate([0, 0, FrontSquashZ]) children(body); _FrontKeep() translate([0, 0, FrontSquashZ]) children(body);
_DilatedBody() { _DilatedBody(ButtonStyle=ButtonStyle) {
children(body); children(body);
children(buttons); children(buttons);
}; };
@@ -197,13 +201,13 @@ module _Case(ButtonStyle)
/// ``` /// ```
/// ///
/// replace "Phone" above with the specific model, e.g. `PP` like `PPBody()` /// replace "Phone" above with the specific model, e.g. `PP` like `PPBody()`
module Case(ButtonStyle="extrude", RenderPhone=false) { module Case(ButtonStyle="extrude", RenderPhone=false, OrientForPrint=true) {
body = 0; body = 0;
buttons = 1; buttons = 1;
ports = 2; ports = 2;
camera = 3; camera = 3;
// translate([PPBodyWidth + Thickness, Thickness, PPBodyHeight + Thickness])
rotate(a=[0, 180, 0]) _MaybeOrientForPrint(OrientForPrint)
union() { union() {
color("DarkSlateGray") _Case(ButtonStyle) { color("DarkSlateGray") _Case(ButtonStyle) {
children(body); children(body);
@@ -223,3 +227,16 @@ module Case(ButtonStyle="extrude", RenderPhone=false) {
}; };
}; };
} }
module _MaybeOrientForPrint(OrientForPrint) {
if (OrientForPrint) {
// XXX these translations deform the mesh, slightly.
// it should export OK, but if rendering gives errors about the mesh not being solid,
// then clear cache and re-render (F6)
translateToAxis(x="pos", z="pos")
rotate([0, 180, 0])
children(0);
} else {
children(0);
};
}

View File

@@ -1,7 +1,7 @@
use <../case.scad> use <../case.scad>
use <../pp/exports.scad> use <../pp/exports.scad>
Case(RenderPhone=false) { Case(RenderPhone=false, OrientForPrint=true) {
PPBody(); PPBody();
PPButtons(); PPButtons();
PPPorts(); PPPorts();

View File

@@ -2,12 +2,14 @@
include <defaults.scad> include <defaults.scad>
module empty() {}
/// cylinder() but with the axis on the x axis instead of the z axis. /// cylinder() but with the axis on the x axis instead of the z axis.
/// and where `center=false` behaves like for `cube(center=false)`, /// and where `center=false` behaves like for `cube(center=false)`,
/// i.e. circle center is not at (0, 0) but (r, r) /// i.e. circle center is not at (0, 0) but (r, r)
module cylinderX(d=undef, r=undef, h=undef, center=false) { module cylinderX(d=undef, r=undef, h=undef, center=false) {
_translateIf(!center, [0, _toRad(r=r, d=d), _toRad(r=r, d=d)]) { _translateIf(!center, [0, _toRad(r=r, d=d), _toRad(r=r, d=d)]) {
rotate(a=[0,-90,0]) cylinder(d=d, r=r, h=h, center=center); rotate(a=[0,90,0]) cylinder(d=d, r=r, h=h, center=center);
} }
} }
@@ -28,30 +30,62 @@ module cylinderZ(d=undef, r=undef, h=undef, center=false) {
} }
} }
// a 2d-cylinder extruded along the x-axis // a 2d-cylinder normal to the x axis, and elongated over the y/z axis.
module pillX(dimX, dimY, dimZ, tol=tol, center=false) { module pillX(dimX, dimY, dimZ, center=false) {
diam = min(dimY, dimZ) - tol; diam = min(dimY, dimZ);
minkowski() { hull() {
cube([dimX/2, dimY-diam, dimZ-diam], center=center); cylinderX(d=diam, h=dimX, center=center);
cylinderX(d=diam, h=dimX/2, center=center); translate([0, dimY-diam, dimZ-diam]) cylinderX(d=diam, h=dimX, center=center);
};
}
// a 2d-cylinder normal to the y axis, and elongated over the x/z axis.
module pillY(dimX, dimY, dimZ, center=false) {
diam = min(dimX, dimZ);
hull() {
cylinderY(d=diam, h=dimY, center=center);
translate([dimX-diam, 0, dimZ-diam]) cylinderY(d=diam, h=dimY, center=center);
};
}
// a 2d-cylinder normal to the z axis, and elongated over the x/y axis.
module pillZ(dimX, dimY, dimZ, center=false) {
diam = min(dimX, dimY);
hull() {
cylinderZ(d=diam, h=dimZ, center=center);
translate([dimX-diam, dimY-diam, 0]) cylinderZ(d=diam, h=dimZ, center=center);
};
}
module sphere_(r, align=undef, alignX=undef, alignY=undef, alignZ=undef) {
alignX_ = _default(alignX, align);
alignY_ = _default(alignY, align);
alignZ_ = _default(alignZ, align);
sX = _selectScale(alignX);
sY = _selectScale(alignY);
sZ = _selectScale(alignZ);
translate([r * sX, r * sY, r * sZ]) sphere(r=r);
}
function _default(primary, secondary) = (primary != undef) ? primary : secondary;
function _selectScale(align) = (align == "center") ? 0 : ((align == "bbox") ? 1 : 0.707);
/// a hollowed-out sphere
module sphereShell(r, thickness) {
difference() {
sphere(r=r+thickness/2);
sphere(r=r-thickness/2);
} }
} }
// a 2d-cylinder extruded along the x-axis /// a hollowed-out cube
module pillY(dimX, dimY, dimZ, tol=tol, center=false) { module cubeShell(x, y, z, thickness, center=false) {
diam = min(dimX, dimZ) - tol; difference() {
minkowski() { cube([x+thickness/2, y+thickness/2, z+thickness/2], center=center);
cube([dimX-diam, dimY/2, dimZ-diam], center=center); cube([x-thickness/2, y-thickness/2, z-thickness/2], center=center);
cylinderY(d=diam, h=dimY/2, center=center);
}
}
// a 2d-cylinder extruded along the z-axis
module pillZ(dimX, dimY, dimZ, tol=tol, center=false) {
diam = min(dimX, dimY) - tol;
minkowski() {
cube([dimX-diam, dimY-diam, dimZ/2], center=center);
cylinderZ(d=diam, h=dimZ/2, center=center);
} }
} }

View File

@@ -0,0 +1,160 @@
include <defaults.scad>
/// projects the child onto the x axis, making it effectively 1 dimensional.
/// actually, the result is a 3d "line" of thickness `tol` centered about the x axis.
/// the x bounds are precise (regardless of tol): it's only the y/z thickness which requires tolerance)
module extractXDomain(tol=tol) {
translate([0, -tol/2, -tol/2])
// compress (x,y=[0,tol],z) -> (x,y=[0,tol],z=[0,tol]
linear_extrude(tol) projection(cut=false)
// rotate so it's (x,z), with y in [0, tol]
rotate([-90, 0, 0])
// compress (x,y,z) -> (x,y,z=[0,tol])
linear_extrude(tol) projection(cut=false)
children(0);
}
/// creates a dot of size `tol` at (x=<the minimum x coordinate occupied by the child module>, y=0, z=0>
module extractXMin(tol=tol) {
difference() {
translate([-tol/2, 0, 0]) extractXDomain(tol=tol) children(0);
translate([tol/2, 0, 0]) extractXDomain(tol=tol) children(0);
}
}
/// creates a dot of size `tol` at (x=<the maximum x coordinate occupied by the child module>, y=0, z=0>
module extractXMax(tol=tol) {
difference() {
translate([tol/2, 0, 0]) extractXDomain(tol=tol) children(0);
translate([-tol/2, 0, 0]) extractXDomain(tol=tol) children(0);
}
}
/// translates the child so that its lowest x value is 0.
module translateToX0Pos(tol=tol) {
minkowski() {
// flip about yz plane, so that we're shifting by -(lower_x_bound)
mirror([1, 0, 0]) extractXMin(tol=tol) children(0);
children(0);
}
}
/// translates the child so that its highest x value is 0.
module translateToX0Neg(tol=tol) {
minkowski() {
// flip about yz plane, so that we're shifting by -(upper_x_bound)
mirror([1, 0, 0]) extractXMax(tol=tol) children(0);
children(0);
}
}
/// projects the child onto the y axis, making it effectively 1 dimensional.
/// actually, the result is a 3d "line" of thickness `tol` centered about the y axis.
module extractYDomain(tol=tol) {
rotate([0, 0, 90])
extractXDomain(tol=tol) {
rotate([0, 0, -90]) children(0);
}
}
/// creates a dot of size `tol` at (x=0, y=<the minimum y coordinate occupied by the child module>, z=0).
module extractYMin(tol=tol) {
difference() {
translate([0, -tol/2, 0]) extractYDomain(tol=tol) children(0);
translate([0, tol/2, 0]) extractYDomain(tol=tol) children(0);
}
}
/// creates a dot of size `tol` at (x=0, y=<the maximum y coordinate occupied by the child module>, 0).
module extractYMax(tol=tol) {
difference() {
translate([0, tol/2, 0]) extractYDomain(tol=tol) children(0);
translate([0, -tol/2, 0]) extractYDomain(tol=tol) children(0);
}
}
/// translates the child so that its lowest y value is 0.
module translateToY0Pos(tol=tol) {
minkowski() {
// flip about xz plane, so that we're shifting by -(lower_y_bound)
mirror([0, 1, 0]) extractYMin(tol=tol) children(0);
children(0);
}
}
/// translates the child so that its highest x value is 0.
module translateToY0Neg(tol=tol) {
minkowski() {
// flip about xz plane, so that we're shifting by -(upper_y_bound)
mirror([0, 1, 0]) extractYMax(tol=tol) children(0);
children(0);
}
}
/// projects the child onto the z axis, making it effectively 1 dimensional.
/// actually, the result is a 3d "line" of thickness `tol` centered about the z axis.
module extractZDomain(tol=tol) {
rotate([0, 90, 0])
extractXDomain(tol=tol) {
rotate([0, -90, 0]) children(0);
}
}
/// creates a dot of size `tol` at (x=0, y=0, z=<the minimum z coordinate occupied by the child module>).
module extractZMin(tol=tol) {
difference() {
translate([0, 0, -tol/2]) extractZDomain(tol=tol) children(0);
translate([0, 0, tol/2]) extractZDomain(tol=tol) children(0);
}
}
/// creates a dot of size `tol` at (x=0, y=0, z=<the maximum z coordinate occupied by the child module>).
module extractZMax(tol=tol) {
difference() {
translate([0, 0, tol/2]) extractZDomain(tol=tol) children(0);
translate([0, 0, -tol/2]) extractZDomain(tol=tol) children(0);
}
}
/// translates the child so that its lowest z value is 0.
module translateToZ0Pos(tol=tol) {
minkowski() {
// flip about xy plane, so that we're shifting by -(lower_z_bound)
mirror([0, 0, 1]) extractZMin(tol=tol) children(0);
children(0);
}
}
/// translates the child so that its highest z value is 0.
module translateToZ0Neg(tol=tol) {
minkowski() {
// flip about xy plane, so that we're shifting by -(upper_z_bound)
mirror([0, 0, 1]) extractZMax(tol=tol) children(0);
children(0);
}
}
module translateToAxis(x=undef, y=undef, z=undef, tol=tol) {
minkowski() {
minkowski() {
if (x == "neg") {
mirror([1, 0, 0]) extractXMax(tol=tol) children(0);
} else if (x == "pos") {
mirror([1, 0, 0]) extractXMin(tol=tol) children(0);
}
if (y == "neg") {
mirror([0, 1, 0]) extractYMax(tol=tol) children(0);
} else if (y == "pos") {
mirror([0, 1, 0]) extractYMin(tol=tol) children(0);
}
if (z == "neg") {
mirror([0, 0, 1]) extractZMax(tol=tol) children(0);
} else if (z == "pos") {
mirror([0, 0, 1]) extractZMin(tol=tol) children(0);
}
};
children(0);
}
}

View File

@@ -3,22 +3,22 @@
use <phone.scad> use <phone.scad>
use <bits.scad> use <bits.scad>
module PPButtons(volume=true, power=true) { module PPButtons(volume=true, power=true, box=false) {
union() { union() {
if (volume) Volume(); if (volume) Volume(Box=box);
if (power) Power(); if (power) Power(Box=box);
}; };
} }
module PPPorts(usb=true, aux=true) { module PPPorts(usb=true, aux=true, box=false) {
union() { union() {
if (usb) Usb(); if (usb) Usb(Box=box);
if (aux) Aux(); if (aux) Aux();
}; };
} }
module PPCamera() { module PPCamera(box=false) {
Camera(); Camera(Box=box);
} }
module PPBody(box=false) { module PPBody(box=false) {

View File

@@ -19,16 +19,26 @@ module _BodyConvolve(tol=tol) {
} }
} }
/// represents one corner of the body
module _BodyPill() {
union() {
translate([BodyRadXY, BodyRadXY, BodyRadFrontZ]) rotate_extrude() translate([BodyRadXY - BodyRadFrontZ, 0, 0]) circle(r=BodyRadFrontZ);
translate([BodyRadXY, BodyRadXY, BodyHeight-BodyRadFrontZ]) rotate_extrude() translate([BodyRadXY - BodyRadFrontZ, 0, 0]) circle(r=BodyRadFrontZ);
}
}
module Body(Box=false) module Body(Box=false)
{ {
if (Box) { if (Box) {
cube([BodyWidth, BodyLength, BodyHeight], center=false); cube([BodyWidth, BodyLength, BodyHeight], center=false);
} else { } else {
translate([BodyRadXY, BodyRadXY, BodyRadFrontZ]) hull() {
minkowski() { // body is a `hull` over four pill-shaped corners
cube([BodyWidth-2*BodyRadXY, BodyLength-2*BodyRadXY, BodyHeight-2*BodyRadFrontZ], center=false); _BodyPill();
_BodyConvolve(); translate([BodyWidth-2*BodyRadXY, 0, 0]) _BodyPill();
} translate([BodyWidth-2*BodyRadXY, BodyLength-2*BodyRadXY, 0]) _BodyPill();
translate([0, BodyLength-2*BodyRadXY, 0]) _BodyPill();
};
} }
} }