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>
use <lib/primitives.scad>
use <lib/transformations.scad>
// thickness of case walls
Thickness = 3.5;
Thickness = 2.5;
// how large of cuts (radial) to make around each component
MarginCameraCut = 2;
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.
// the top of the phone contains stuff we don't want to cover (camera); the bottom has more margin
FrontOverhangX = 4;
FrontOverhangTop = 5;
FrontOverhangBot = 7;
FrontOverhangX = 3;
FrontOverhangTop = 3.5;
FrontOverhangBot = 6;
// shift the overhang into the screen, to account that the case has some flex
FrontSquashZ = 1.2;
// gives a 1cm margin around the whole body of the phone, but only in the xy plane.
module _PhoneBulkLayer(tol=tol)
// gives a wide margin around the whole body of the phone, but only in the xy plane.
module _PhoneBulkLayer(zMin=0)
{
minkowski() {
children(/*body*/);
cube([20, 20, tol], center=true);
}
hull() {
translate([-500, -500, zMin]) extractZDomain() children(/*body*/);
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,
/// on all axis EXCEPT the -z axis
module _PhoneBulkAndBack()
/// gives a margin around the whole body of the phone,
/// on all axes EXCEPT the -z axis
module _PhoneBulkAndBack(zMin=0)
{
minkowski() {
children(/*body*/);
translate([0, 0, 5+tol]) cube([20, 20, 10], center=true);
hull() {
_PhoneBulkLayer(zMin=zMin) children(/*body*/);
translate([0, 0, 10]) _PhoneBulkLayer(zMin=zMin) children(/*body*/);
}
}
module _DilatedBody(ButtonStyle) {
body = 0;
buttons = 1;
minkowski() {
sphere(r=Thickness);
union() {
union() {
minkowski() {
sphere(r=Thickness);
children(body);
if (ButtonStyle == "extrude") {
};
if (ButtonStyle == "extrude") {
minkowski() {
sphere(r=Thickness);
// cylinderX(r=Thickness, h=2*Thickness, center=true);
children(buttons);
};
}
}
};
}
module _DilatedBodyExceptFront(ButtonStyle) {
module _DilatedBodyExceptFront(ButtonStyle, zMin=0) {
body = 0;
buttons = 1;
intersection() {
_DilatedBody() {
_DilatedBody(ButtonStyle=ButtonStyle) {
children(body);
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) {
body = 0;
buttons = 1;
@@ -69,7 +78,8 @@ module _PeripheralCutouts(ButtonStyle) {
camera = 3;
union() {
minkowski() {
sphere(r=MarginCameraCut);
// sphere(r=MarginCameraCut);
cylinderZ(r=MarginCameraCut, h=2*Thickness+tol, center=true);
children(camera);
};
// 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() {
sphere(r=MarginPortsCut);
// cylinderY(r=MarginPortsCut, h=2*MarginPortsCut, center=true);
children(ports);
};
};
@@ -91,11 +102,11 @@ module _PeripheralCutouts(ButtonStyle) {
};
};
module _FrontFace(tol=tol) {
module _FrontFace() {
color("thistle")
difference() {
translate([0, 0, -Thickness]) children(/*body*/);
_PhoneBulkLayer(tol=tol) children(/*body*/);
_PhoneBulkLayer() children(/*body*/);
};
}
@@ -107,6 +118,7 @@ module _FrontKeep(tol=tol) {
minkowski() {
// take the outline of the front face of the phone
difference() {
// TODO: projection, then hull
minkowski() {
cylinder(r=2*tol, h=tol, center=true);
_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.
/// the returned cutout is strictly z <= 0,
module _FrontCutout() {
difference() {
_FrontFace() children(/*body*/);
_FrontKeep() children(/*body*/);
};
}
module _CaseExceptFeatures(ButtonStyle)
/// complete case except for cutouts
module _CaseExceptFeatures(ButtonStyle, tol=tol)
{
body = 0;
buttons = 1;
union() {
difference() {
// start by dilating the phone
_DilatedBodyExceptFront(ButtonStyle=ButtonStyle) {
_DilatedBodyExceptFront(ButtonStyle=ButtonStyle, zMin=tol) {
children(body);
children(buttons);
};
@@ -149,7 +153,7 @@ module _CaseExceptFeatures(ButtonStyle)
// add in the front part of the case
intersection() {
_FrontKeep() translate([0, 0, FrontSquashZ]) children(body);
_DilatedBody() {
_DilatedBody(ButtonStyle=ButtonStyle) {
children(body);
children(buttons);
};
@@ -197,13 +201,13 @@ module _Case(ButtonStyle)
/// ```
///
/// 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;
buttons = 1;
ports = 2;
camera = 3;
// translate([PPBodyWidth + Thickness, Thickness, PPBodyHeight + Thickness])
rotate(a=[0, 180, 0])
_MaybeOrientForPrint(OrientForPrint)
union() {
color("DarkSlateGray") _Case(ButtonStyle) {
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 <../pp/exports.scad>
Case(RenderPhone=false) {
Case(RenderPhone=false, OrientForPrint=true) {
PPBody();
PPButtons();
PPPorts();

View File

@@ -2,12 +2,14 @@
include <defaults.scad>
module empty() {}
/// cylinder() but with the axis on the x axis instead of the z axis.
/// and where `center=false` behaves like for `cube(center=false)`,
/// i.e. circle center is not at (0, 0) but (r, r)
module cylinderX(d=undef, r=undef, h=undef, center=false) {
_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
module pillX(dimX, dimY, dimZ, tol=tol, center=false) {
diam = min(dimY, dimZ) - tol;
minkowski() {
cube([dimX/2, dimY-diam, dimZ-diam], center=center);
cylinderX(d=diam, h=dimX/2, center=center);
// a 2d-cylinder normal to the x axis, and elongated over the y/z axis.
module pillX(dimX, dimY, dimZ, center=false) {
diam = min(dimY, dimZ);
hull() {
cylinderX(d=diam, h=dimX, 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
module pillY(dimX, dimY, dimZ, tol=tol, center=false) {
diam = min(dimX, dimZ) - tol;
minkowski() {
cube([dimX-diam, dimY/2, dimZ-diam], 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);
/// a hollowed-out cube
module cubeShell(x, y, z, thickness, center=false) {
difference() {
cube([x+thickness/2, y+thickness/2, z+thickness/2], center=center);
cube([x-thickness/2, y-thickness/2, z-thickness/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 <bits.scad>
module PPButtons(volume=true, power=true) {
module PPButtons(volume=true, power=true, box=false) {
union() {
if (volume) Volume();
if (power) Power();
if (volume) Volume(Box=box);
if (power) Power(Box=box);
};
}
module PPPorts(usb=true, aux=true) {
module PPPorts(usb=true, aux=true, box=false) {
union() {
if (usb) Usb();
if (usb) Usb(Box=box);
if (aux) Aux();
};
}
module PPCamera() {
Camera();
module PPCamera(box=false) {
Camera(Box=box);
}
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)
{
if (Box) {
cube([BodyWidth, BodyLength, BodyHeight], center=false);
} else {
translate([BodyRadXY, BodyRadXY, BodyRadFrontZ])
minkowski() {
cube([BodyWidth-2*BodyRadXY, BodyLength-2*BodyRadXY, BodyHeight-2*BodyRadFrontZ], center=false);
_BodyConvolve();
}
hull() {
// body is a `hull` over four pill-shaped corners
_BodyPill();
translate([BodyWidth-2*BodyRadXY, 0, 0]) _BodyPill();
translate([BodyWidth-2*BodyRadXY, BodyLength-2*BodyRadXY, 0]) _BodyPill();
translate([0, BodyLength-2*BodyRadXY, 0]) _BodyPill();
};
}
}