Created
August 6, 2023 22:27
-
-
Save spydmobile/64caa99f82cc7d4358ac681f2013dd39 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let requestedWidth = Slider("Width", 100, 10, 300, true, 1, 0) | |
let requestedDepth = Slider("Depth", 50, 5, 300, true, 1, 0) | |
let requestedHeight = Slider("Height", 20, 11, 100, true, 1, 0) | |
let rows = Slider("Rows", 2, 1, 20, true, 1, 0) | |
let columns = Slider("Columns", 5, 1, 20, true, 1, 0) | |
let outsideRadius = Slider("Radius", 10, 2, 40, true, 1, 0) | |
let latches = Slider("Latches", 1, 1, 3, true, 1, 0) | |
let hingeStyle = Dropdown("Hinge type", "pin", {Pin: "pin", Detent: "cone"}, true) | |
let latchStyle = Dropdown("Latch type", "pin", {Pin: "pin", Detent: "cone"}, true) | |
let leftHanded = Checkbox("Left handed?", true) | |
let lidAngle = Slider("Lid open angle", 0, 0, 90, true, 1, 0) | |
let showLid = Checkbox("Show lid?", true) | |
let showBase = Checkbox("Show base?", true) | |
let showLabels = Checkbox("Show labels?", false) | |
let insideLabels = TextInput("Inside labels", "") | |
let outsideLabel = TextInput("Outside label", "") | |
let multiMaterial = Checkbox("Multimaterial?", false) | |
let printOrientation = Checkbox("Print orientation?", false) | |
const coneHinge = hingeStyle === 'cone' | |
const coneLatch = latchStyle === 'cone' | |
insideLabels = insideLabels.trim().split(/,/).map(label => label.trim()) | |
const dividerThick = 1.4 | |
const wallThick = 2.5 | |
const floorThick = 2.5 | |
const lidThick = floorThick | |
const lidClearance = 0.2 | |
const lipHeight = 3 | |
const hingeSupportWidth = 7 | |
const hingeRadius = 3 | |
const hingeOffset = 1 | |
const hingeClearance = 0.3 | |
const totalHingeWidth = hingeSupportWidth * 4; | |
const hingeConeDepth = 1.2 | |
const lidHingeWidth = hingeSupportWidth * 2 - hingeClearance * 2 | |
const claspDepth = 6 | |
const claspHeight = 4 | |
const claspWidth = 26 | |
const claspRadius = 2 | |
const claspConeDepth = 0.8 | |
const coneClaspClearance = 0.2 | |
const coneClaspWidth = (claspWidth - coneClaspClearance) / 2 | |
const claspOffset = 0.5 | |
// horizontal hole sizes | |
const holeTight = 1.96 | |
const holeLoose = 2 | |
// vertical hole sizes | |
const holeLooseVertical = 2.05 | |
const minWidth = Math.max(latches * claspWidth, totalHingeWidth, columns * 8) + 0.5 + outsideRadius * 2; | |
const boxWidth = Math.max(requestedWidth, minWidth) | |
const boxDepth = Math.max(requestedDepth, wallThick * 2 + Math.max(outsideRadius * 2, rows * 8)) | |
const boxHeight = Math.max(requestedHeight, coneLatch ? 11 : 14) | |
const hinges = boxWidth < outsideRadius * 2 + totalHingeWidth * 2 + 1 ? 1 : 2 | |
const sectionWidth = (boxWidth - wallThick * 2 - (columns - 1) * dividerThick) / columns | |
const sectionDepth = (boxDepth - wallThick * 2 - (rows - 1) * dividerThick) / rows | |
const sectionHeight = boxHeight - floorThick - lidThick - lidClearance | |
const claspBumpHeight = 0.8 | |
const claspBumpWidth = claspBumpHeight * 2.5 | |
const claspCenter = boxWidth / 2 | |
const claspTop = boxHeight - lipHeight | |
const claspPositions = { | |
center: claspCenter - claspWidth / 2, | |
left: outsideRadius, | |
right: boxWidth - outsideRadius - claspWidth, | |
leftThird: boxWidth * 0.33 - claspWidth / 2, | |
rightThird: boxWidth * 0.66 - claspWidth / 2, | |
} | |
const claspConfigurations = { | |
1: [claspPositions.center], | |
2: [claspPositions.left, claspPositions.right], | |
3: [claspPositions.left, claspPositions.center, claspPositions.right] | |
} | |
const currentClaspConfig = claspConfigurations[latches] | |
const hingeConfigurations = { | |
1: [boxWidth / 2 - totalHingeWidth / 2], | |
2: [outsideRadius, boxWidth - outsideRadius - totalHingeWidth] | |
} | |
const currentHingeConfig = hingeConfigurations[hinges] | |
const lidLipThick = wallThick / 2 - lidClearance | |
const lidZ = boxHeight - lipHeight + lidClearance | |
const lidHeight = lidThick + lipHeight | |
const boxTop = lidZ + lidHeight | |
const lidText = [] | |
const claspsToMirror = [] | |
// reverse last latch | |
if (latches > 1) { | |
claspsToMirror.push(latches - 1) | |
} | |
// reverse center or only latch | |
if (latches != 2 && !leftHanded) { | |
claspsToMirror.push(Math.floor(latches / 2)) | |
} | |
class ChainShape { | |
reference | |
constructor(reference) { | |
this.reference = reference | |
} | |
static dereference(shapes) { | |
return shapes.map(o => { | |
return o instanceof ChainShape ? o.reference : o | |
}) | |
} | |
clone(keepOriginal, callback) { | |
const reference = callback() | |
if (keepOriginal) { | |
return new ChainShape(reference) | |
} | |
this.reference = reference | |
return this | |
} | |
translate(offset, keepOriginal = false) { | |
return this.clone(keepOriginal, () => { | |
return Translate(offset, this.reference, keepOriginal) | |
}) | |
} | |
rotate(axis, degrees, keepOriginal = false) { | |
return this.clone(keepOriginal, () => { | |
return Rotate(axis, degrees, this.reference, keepOriginal) | |
}) | |
} | |
rotateAbout(absPosition, axis, degrees, keepOriginal = false) { | |
return this.clone(keepOriginal, () => { | |
return rotateAround(absPosition, axis, degrees, this.reference, keepOriginal) | |
}) | |
} | |
mirror(absPosition, axes, keepOriginal = false) { | |
return this.clone(keepOriginal, () => { | |
const [x, y, z] = absPosition | |
return Translate(absPosition, Mirror(axes, Translate([-x, -y, -z], this.reference, keepOriginal))) | |
}) | |
} | |
chamfer(distance, edgeList, keepOriginal = false) { | |
return this.clone(keepOriginal, () => { | |
return ChamferEdges(this.reference, distance, edgeList, keepOriginal) | |
}) | |
} | |
fillet(radius, edgeList, keepOriginal = false) { | |
return this.clone(keepOriginal, () => { | |
return FilletEdges(this.reference, radius, edgeList, keepOriginal) | |
}) | |
} | |
difference(objectsToSubtract, keepObjects = false, fuzzValue = null, keepEdges = true) { | |
this.reference = Difference(this.reference, this.constructor.dereference(objectsToSubtract), keepObjects, fuzzValue, keepEdges) | |
return this | |
} | |
union(objectsToJoin, keepObjects = false, fuzzValue = null, keepEdges = false) { | |
return this.clone(keepObjects, () => { | |
return Union([this.reference, ...(this.constructor.dereference(objectsToJoin))], keepObjects, fuzzValue, keepEdges) | |
}) | |
} | |
intersect(objectsToIntersect, keepObjects = false, fuzzValue = null, keepEdges = false) { | |
return this.clone(keepObjects, () => { | |
return Intersection([this.reference, ...(this.constructor.dereference(objectsToIntersect))], keepObjects, fuzzValue, keepEdges) | |
}) | |
} | |
unevenChamfer(dist1, dist2, edgeList, face, keepOriginal = false) { | |
return this.clone(keepOriginal, () => { | |
return UnevenChamferEdges(this.reference, dist1, dist2, edgeList, face, keepOriginal) | |
}) | |
} | |
extrude(direction, keepFace = false) { | |
return this.clone(keepFace, () => { | |
return Extrude(this.reference, direction, keepFace) | |
}) | |
} | |
copy() { | |
return this.translate([0, 0, 0], true) | |
} | |
} | |
function UnevenChamferEdges(shape, dist1, dist2, edgeList, face, keepOriginal) { | |
let curChamfer = CacheOp(arguments, () => { | |
let mkChamfer = new oc.BRepFilletAPI_MakeChamfer(shape) | |
let foundEdges = 0 | |
ForEachEdge(shape, (index, edge) => { | |
if (edgeList.includes(index)) { | |
mkChamfer.Add(dist1, dist2, edge, face) | |
foundEdges++ | |
} | |
}) | |
if (foundEdges == 0) { | |
console.error("Chamfer Edges Not Found! Make sure you are looking at the object _before_ the Chamfer is applied!") | |
return new oc.TopoDS_Solid(shape) | |
} | |
return new oc.TopoDS_Solid(mkChamfer.Shape()) | |
}) | |
sceneShapes.push(curChamfer) | |
if (!keepOriginal) { sceneShapes = Remove(sceneShapes, shape) } | |
return curChamfer | |
} | |
function hinge(r, holeDiameter, angle, vOffset, hOffset, connectionHeight, t) { | |
let a = Math.PI * angle * 0.5 | |
hOffset = hOffset || 0 | |
vOffset = vOffset || 0 | |
let p0 = [0, -r] | |
let p1 = [-r, 0] | |
let p2 = [r * Math.sin(a), r * Math.cos(a)] | |
let oppositeHeight = Math.tan(Math.PI * 0.5 - a) * (p2[1] + r + hOffset) | |
let p3 = [oppositeHeight + p2[0], p0[1] - hOffset] | |
let p4 = [p0[0] + vOffset, p3[1]] | |
let sketch = new Sketch(p0) | |
.ArcTo(p1, p2) | |
.LineTo(p3) | |
.LineTo(p4) | |
.LineTo(p0) | |
.End(true) | |
let extruded = Extrude(sketch.Face(true), [0, 0, t], false) | |
if (connectionHeight) { | |
const cutterHeight = (p3[0] - p0[0]) - connectionHeight | |
let cutter = Box(cutterHeight, r * 2 + hOffset, t) | |
cutter = Translate([vOffset + connectionHeight, -hOffset - r, 0], cutter) | |
extruded = Difference(extruded, [cutter], false) | |
} | |
if (holeDiameter) { | |
const hole = Rotate([0, 1, 0], -90, smallHole(holeDiameter, t)) | |
extruded = Difference(extruded, [hole]) | |
} | |
return extruded | |
} | |
function rotateAround(coords, axes, degrees, object, keepObjects = false) { | |
const [x, y, z] = coords | |
return Translate(coords, Rotate(axes, degrees, Translate([-x, -y, -z], object, keepObjects))) | |
} | |
function smallHole(diameter, length) { | |
const r = diameter / 2 | |
const a = Math.PI * 0.3 | |
let p0 = [r * Math.sin(-a), r * Math.cos(-a)] | |
let p2 = [r * Math.sin(a), r * Math.cos(a)] | |
let s = new Sketch(p0) | |
.ArcTo([0, -r], p2) | |
.LineTo([r * 0.5, r]) | |
.LineTo([-r * 0.5, r]) | |
.End(true) | |
.Face() | |
return Rotate([0, 0, 1], 90, | |
Rotate([1, 0, 0], 90, | |
Extrude(s, [0, 0, length]) | |
) | |
) | |
} | |
function halfSphere(radius) { | |
const quarterCircle = new Sketch([0, 0]) | |
.LineTo([0, radius]) | |
.ArcTo([radius * Math.sin(0.2), radius * Math.cos(0.2)], [radius, 0]) | |
.LineTo([0, 0]) | |
.End() | |
.Face(true) | |
return new ChainShape(Revolve(quarterCircle, 360, [1, 0, 0], false)) | |
.rotate([0, 1, 0], 180) | |
} | |
if (showBase) { | |
const base = new ChainShape(Box(boxWidth, boxDepth, boxHeight - lipHeight)) | |
.fillet(outsideRadius, [0, 2, 4, 6]) | |
.chamfer(1, [3]) | |
const lipRadius = outsideRadius - wallThick / 2 | |
const baseInteriorRadius = outsideRadius - wallThick | |
const baseLip = new ChainShape(Box(boxWidth - wallThick, boxDepth - wallThick, boxHeight)) | |
.fillet(Math.max(0.1, lipRadius), [0, 2, 4, 6]) | |
.translate([wallThick / 2, wallThick / 2, 0]) | |
base.union([baseLip]) | |
const interior = new ChainShape(Box(boxWidth - wallThick * 2, boxDepth - wallThick * 2, boxHeight - floorThick)) | |
.translate([wallThick, wallThick, floorThick]) | |
.fillet(Math.max(baseInteriorRadius, 0.1), [0, 2, 4, 6]) | |
.fillet(1, [3]) | |
base.difference([interior]) | |
.chamfer(0.5, [48]) | |
const hDividers = [...Array(columns - 1)].map((_n, i) => { | |
return Translate([wallThick + (i + 1) * sectionWidth + i * dividerThick, wallThick, floorThick], Box(dividerThick, boxDepth - wallThick * 2, boxHeight - floorThick)) | |
}) | |
const vDividers = [...Array(rows - 1)].map((_n, i) => { | |
return Translate([wallThick, wallThick + (i + 1) * sectionDepth + i * dividerThick, floorThick], Box(boxWidth - wallThick * 2, dividerThick, boxHeight - floorThick)) | |
}) | |
if (columns > 1 || rows > 1) { | |
const dividers = new ChainShape(Union([...hDividers, ...vDividers])) | |
dividers.intersect([interior]) | |
base.union([dividers]) | |
} | |
const holeSize = coneHinge ? 0 : holeTight | |
const hingeY = boxDepth + hingeRadius + hingeOffset | |
const hingeZ = boxHeight - lipHeight | |
const chamferEdge = coneHinge ? 5 : 8 | |
const leftHinge = new ChainShape(hinge(hingeRadius, holeSize, 0.6, 0, hingeOffset, null, hingeSupportWidth)) | |
.rotate([0, 1, 0], 90) | |
.translate([currentHingeConfig[0], hingeY, hingeZ]) | |
.chamfer(0.8, [chamferEdge]) | |
if (coneHinge) { | |
const leftCone = new ChainShape(Cone(hingeRadius - 0.5, 0.8, hingeConeDepth)) | |
.chamfer(0.5, [0]) | |
.rotate([0, 1, 0], -90) | |
.translate([currentHingeConfig[0] + hingeSupportWidth + hingeClearance/2, hingeY, hingeZ]) | |
leftHinge.difference([leftCone]) | |
} | |
const rightHinge = leftHinge.mirror([currentHingeConfig[0] + totalHingeWidth/2, 0, 0], [1, 0, 0], true) | |
const hingeParts = [leftHinge, rightHinge] | |
currentHingeConfig.slice(1).forEach(xPos => { | |
const xTranslation = boxWidth - outsideRadius - totalHingeWidth - currentHingeConfig[0] | |
const part1 = leftHinge.translate([xTranslation, 0, 0], true) | |
const part2 = rightHinge.translate([xTranslation, 0, 0], true) | |
hingeParts.push(part1, part2) | |
}) | |
let baseClasp | |
const baseClaspX = currentClaspConfig[0] | |
if (coneLatch) { | |
baseClasp = new ChainShape(hinge(claspRadius, 0, 0.65, lidClearance, claspOffset, 0, coneClaspWidth)) | |
.rotate([0, 1, 0], 90) | |
.rotate([0, 0, 1], 180) | |
.translate([baseClaspX + coneClaspWidth, -claspRadius - claspOffset, claspTop]) | |
// const outsideBump = new ChainShape(Sphere(hingeRadius)) | |
// .translate([baseClaspX + lidHingeWidth * 2 + hingeClearance, -hingeRadius - claspOffset, claspTop]) | |
const claspDepression = new ChainShape(Cone(claspRadius - 0.5, 0.5, claspConeDepth)) | |
.rotate([0, 1, 0], -90) | |
.fillet(0.5, [0]) | |
.translate([baseClaspX + coneClaspWidth + 0.01, -claspRadius - claspOffset, claspTop]) | |
const sphere = halfSphere(claspRadius) | |
.translate([baseClaspX, -claspRadius - claspOffset, claspTop]) | |
baseClasp.difference([claspDepression]).union([sphere]) | |
} else { | |
baseClasp = new ChainShape(Box(26, claspDepth, claspHeight)) | |
.translate([currentClaspConfig[0], -claspDepth, claspTop - claspHeight]) | |
// const claspBump = new ChainShape(Box(claspWidth, claspBumpWidth, claspBumpHeight)) | |
// .translate([claspCenter - claspWidth/2, -claspDepth + 1, claspTop - claspHeight - claspBumpHeight]) | |
// .unevenChamfer(claspBumpHeight - 0.01, claspBumpWidth * 0.5 - 0.15, [8, 10], 4) | |
// baseClasp.union([claspBump]) | |
const claspFace = new Sketch([0, 0]) | |
.LineTo([claspDepth, 0]) | |
.LineTo([0, 6]) | |
.LineTo([0, 0]) | |
.End(true) | |
.Face(false) | |
const claspSupport = new ChainShape(claspFace) | |
.extrude([0, 0, 5]) | |
.rotate([1, 0, 0], -90) | |
.rotate([0, 0, 1], -90) | |
.translate([currentClaspConfig[0], 0, claspTop - claspHeight]) | |
const claspSupport2 = claspSupport.translate([claspWidth - 5, 0, 0], true) | |
baseClasp.union([claspSupport, claspSupport2]) | |
.chamfer(0.5, [5]) | |
.fillet(1, [1, 6]) | |
.chamfer(1, [3, 7]) | |
} | |
const clasps = [baseClasp] | |
const additionalClasps = currentClaspConfig.slice(1).map(xPos => { | |
return baseClasp.translate([xPos - currentClaspConfig[0], 0, 0], true) | |
}) | |
clasps.push(...additionalClasps) | |
if (coneLatch) { | |
claspsToMirror.forEach(i => { | |
clasps[i].mirror([currentClaspConfig[i] + claspWidth / 2, 0, 0], [1, 0, 0]) | |
}) | |
} | |
base.union([...hingeParts, ...clasps]) | |
if (printOrientation) { | |
base.rotate([1, 0, 0], 90) | |
} | |
} | |
if (showLid) { | |
const interiorRadius = outsideRadius - lidLipThick | |
const lid = new ChainShape(Box(boxWidth, boxDepth, lidHeight)) | |
.translate([0, 0, lidZ]) | |
.fillet(outsideRadius, [0, 2, 4, 6]) | |
const lidInterior = new ChainShape(Box(boxWidth - lidLipThick * 2, boxDepth - lidLipThick * 2, lipHeight)) | |
.translate([lidLipThick, lidLipThick, lidZ]) | |
.fillet(Math.max(0.1, interiorRadius), [0, 2, 4, 6]) | |
lid.difference([lidInterior]) | |
const lidHingeX = currentHingeConfig[0] + totalHingeWidth - hingeSupportWidth - hingeClearance | |
const lidHingeY = boxDepth + hingeOffset + hingeRadius | |
const lidHingeZ = boxHeight - lipHeight | |
const holeSize = coneHinge ? 0 : holeLoose | |
const lidHinge = new ChainShape(hinge(hingeRadius, holeSize, 0.65, lidClearance, hingeOffset + 1, lidHeight, lidHingeWidth)) | |
.rotate([0, 1, 0], -90) | |
.translate([lidHingeX, lidHingeY, lidHingeZ]) | |
if (coneHinge) { | |
const leftCone = new ChainShape(Cone(hingeRadius - 0.5, 1, hingeConeDepth)) | |
.rotate([0, 1, 0], 90) | |
.translate([lidHingeX, lidHingeY, lidHingeZ]) | |
.fillet(0.99, [0]) | |
const rightCone = leftCone.rotateAbout([lidHingeX - lidHingeWidth / 2, 0, lidHingeZ], [0, 1, 0], 180, true) | |
lidHinge.union([leftCone, rightCone]) | |
} | |
const lidHinges = [lidHinge] | |
currentHingeConfig.slice(1).forEach(xPos => { | |
const hinge = lidHinge.translate([xPos - currentHingeConfig[0], 0, 0], true) | |
lidHinges.push(hinge) | |
}) | |
let lidClasp | |
const lidClaspX = currentClaspConfig[0] | |
const lidClaspZ = boxHeight - lipHeight + lidClearance | |
if (coneLatch) { | |
lidClasp = new ChainShape(hinge(claspRadius, 0, 0.65, lidClearance, claspOffset, lidHeight, coneClaspWidth)) | |
.rotate([0, 1, 0], -90) | |
.rotate([0, 0, 1], 180) | |
.translate([lidClaspX + coneClaspWidth + coneClaspClearance, -claspRadius - claspOffset, lidClaspZ - lidClearance]) | |
const sphere = halfSphere(claspRadius) | |
.rotate([0, 0, 1], 180) | |
.translate([lidClaspX + coneClaspWidth * 2 + coneClaspClearance, -claspRadius - claspOffset, lidClaspZ - lidClearance]) | |
const claspCatch = new ChainShape(Cone(claspRadius - 0.5, 0.5, claspConeDepth)) | |
.rotate([0, 1, 0], -90) | |
.fillet(0.5, [0]) | |
.translate([lidClaspX + coneClaspWidth + coneClaspClearance + 0.005, -claspRadius - claspOffset, lidClaspZ - lidClearance]) | |
lidClasp | |
.union([sphere]) | |
.union([claspCatch]) | |
} else { | |
lidClasp = new ChainShape(Box(5, claspDepth + 1 , lidHeight)) | |
.translate([lidClaspX, - claspDepth, boxHeight - lipHeight + lidClearance]) | |
const lidClaspHole = new ChainShape(smallHole(holeTight, claspWidth)) | |
.rotate([1, 0, 0], 180) | |
.translate([lidClaspX, -claspDepth / 2, boxHeight - lipHeight + lidClearance + lidHeight / 2]) | |
lidClasp.difference([lidClaspHole]) | |
const lidClasp2 = lidClasp.translate([claspWidth - 5, 0, 0], true) | |
lidClasp.chamfer(1, [0, 10]) | |
lidClasp2.chamfer(1, [9, 10]) | |
lidClasp.union([lidClasp2]) | |
} | |
const allClasps = [lidClasp] | |
const additionalClasps = currentClaspConfig.slice(1).map(xPos => { | |
return lidClasp.translate([xPos - currentClaspConfig[0], 0, 0], true) | |
}) | |
allClasps.push(...additionalClasps) | |
if (coneLatch) { | |
claspsToMirror.forEach(i => { | |
allClasps[i].mirror([currentClaspConfig[i] + claspWidth / 2, 0, 0], [1, 0, 0]) | |
}) | |
} | |
lid.chamfer(1, [0]) | |
lid.union([lidClasp, ...additionalClasps, ...lidHinges]) | |
if (showLabels) { | |
const fontHeight = 7 | |
const fontSize = fontHeight * 1.2 | |
const fontThickness = 0.6 | |
if (insideLabels.some(Boolean)) { | |
const labelFaces = insideLabels.slice(0, rows * columns).map((label, i) => { | |
const xPos = i % columns | |
const yPos = Math.floor(i / columns) | |
const roughWidth = label.length * fontSize * 0.7 | |
return new ChainShape(Text3D(label, fontSize, 0)) | |
.translate([ | |
wallThick + (dividerThick + sectionWidth) * xPos + sectionWidth / 2 - roughWidth / 2, | |
wallThick + (sectionDepth + dividerThick) * yPos + fontHeight + sectionDepth / 2 - fontHeight / 2, | |
0 | |
]) | |
.reference | |
}) | |
const combinedLabelFaces = new ChainShape(Union(labelFaces)) | |
const embossedLabels = combinedLabelFaces.extrude([0, 0, fontThickness * 2], multiMaterial) | |
.translate([0, 0, boxTop - lidThick - fontThickness]) | |
if (multiMaterial) { | |
lid.difference([embossedLabels]) | |
lidText.push( | |
combinedLabelFaces.extrude([0, 0, fontThickness]) | |
.translate([0, 0, boxTop - lidThick]) | |
) | |
} else { | |
lid.union([embossedLabels]) | |
} | |
} | |
if (outsideLabel) { | |
const labelOffset = 0.293 * (outsideRadius - wallThick) + 4 | |
const topFontSize = Math.min(14, (boxDepth - labelOffset * 2) * 0.8 - 2) | |
const engraveExtrude = 2 / topFontSize | |
const label = new ChainShape(Text3D(outsideLabel, topFontSize, engraveExtrude)) | |
.rotate([1, 0, 0], -90) | |
.translate([ | |
labelOffset, | |
labelOffset + 2, | |
boxTop + 1.4 | |
]) | |
lid.difference([label]) | |
if (multiMaterial) { | |
lidText.push(new ChainShape(Text3D(outsideLabel, topFontSize, 0.6 / topFontSize)) | |
.rotate([1, 0, 0], -90) | |
.translate([ | |
labelOffset, | |
labelOffset + 2, | |
boxTop | |
])) | |
} | |
} | |
} | |
const handleDepth = claspDepth + 2.5 | |
const handleHeight = claspHeight * 2 + 5 | |
const handleClearance = 0.4 | |
const handleLipHeight = 3 | |
let handleWidth = claspWidth - 10 - handleClearance * 2 | |
const handleX = currentClaspConfig[0] + 5 + handleClearance | |
const bumpCatchClearance = 0.3 | |
const handles = [] | |
if (!coneLatch) { | |
const handle = new ChainShape(Box(handleWidth, handleDepth, handleHeight)) | |
.translate([handleX, -handleDepth - 0.4, boxTop - handleHeight]) | |
const handleSubtract = new ChainShape(Box(handleWidth, claspDepth, claspHeight + 0.9)) | |
.translate([handleX, -claspDepth - 0.4, boxHeight - lipHeight - claspHeight - 0.2]) | |
.chamfer(1, [8, 9]) | |
handle.difference([handleSubtract]) | |
.fillet(1.7, [10, 14]) | |
.fillet(3, [13]) | |
const handleLip = new ChainShape(Box(handleWidth, 2.5, handleLipHeight)) | |
.rotateAbout([0, 2.5, handleLipHeight], [1, 0, 0], -12) | |
.translate([handleX, -handleDepth - 0.4, boxTop - handleHeight - handleLipHeight + 0.5]) | |
.fillet(2, [3, 7]) | |
const bumpCatch = new ChainShape(Box(claspWidth, claspBumpWidth + bumpCatchClearance, claspBumpHeight + bumpCatchClearance)) | |
.translate([currentClaspConfig[0], -claspDepth + 1 - bumpCatchClearance * 0.5, claspTop - claspHeight - claspBumpHeight - bumpCatchClearance]) | |
.unevenChamfer(claspBumpHeight - 0.01, claspBumpWidth * 0.5 - 0.15, [8, 10], 4) | |
handle.difference([bumpCatch]) | |
.fillet(1.3, [42, 45]) | |
const handleClaspHole = new ChainShape(Cylinder(holeLooseVertical / 2, claspWidth)) | |
.rotate([0, 1, 0], 90) | |
.translate([handleX, -claspDepth / 2, boxHeight - lipHeight + lidClearance + lidHeight / 2]) | |
handle.difference([handleClaspHole]) | |
.union([handleLip]) | |
const extraHandles = currentClaspConfig.slice(1).map(xPos => { | |
return handle.translate([xPos - currentClaspConfig[0], 0, 0], true) | |
}) | |
handles.push(handle, ...extraHandles) | |
} | |
if (lidAngle && !printOrientation) { | |
[lid, ...handles, ...lidText].forEach(el => { | |
el.rotateAbout([0, boxDepth + hingeOffset + hingeRadius, boxHeight - lipHeight], [1, 0, 0], -lidAngle) | |
}) | |
} | |
if (printOrientation) { | |
[lid, ...lidText].forEach(el => { | |
el.rotateAbout([0, 0, lidZ], [1, 0, 0], -90) | |
.translate([20, -lidHeight, -20]) | |
.rotate([0, 1, 0], 180) | |
}) | |
handles.forEach((h, i) => { | |
h.translate([-currentClaspConfig[i] - 5 - handleClearance, claspDepth, -boxTop]) | |
.rotate([0, 0, 1], -90) | |
.rotate([0, 1, 0], 180) | |
.translate([-claspDepth - 5, 0, 20 * i]) | |
}) | |
} | |
} | |
if (!showLid && !showBase) { | |
Text3D("Nothing here", 10, 0.1) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment