"use strict"
/**
* Builds cross-section profiles in gothic style.
* Output profiles are centred at (0, 0, 0).
* Edge profiles have a 1mm margin between all details and the flat (host) side.
* @namespace details.profiles
*/
const EDGE_PROFILE_MARGIN = 1;
const profileBuilder = ({ lib }) => {
const { square, circle, rectangle } = lib.primitives
const { intersect, union, subtract } = lib.booleans
const { rotate, align, translate } = lib.transforms
return {
/**
* Square with circular notches at corners.
* @memberof details.profiles
* @instance
* @param {Object} opts
* @param {number} opts.sqLength - side length for bounding square
* @param {number} opts.notchRadius - radius of circular notch
*/
sqCornerCircNotch: (opts) => {
// TODO - fix implementation. Everything assumes that cornerRad === sqLen / 4.
// So the bounding square probably would be off if it's changed.
const sqLen = opts.sqLength;
const halfUnit = sqLen / 2
const cornerRad = opts.notchRadius || sqLen / 4;
const centrePoints = [
[halfUnit, halfUnit],
[-halfUnit, halfUnit],
[halfUnit, -halfUnit],
[-halfUnit, -halfUnit],
];
const baseSquare = square({ size: sqLen });
const cornerCircles = union(centrePoints.map(cPt => {
return circle({ radius: cornerRad, center: cPt });
}));
return subtract(baseSquare, cornerCircles);
},
/**
* Square with circles at corners.
* @memberof details.profiles
* @instance
* @param {Object} opts
* @param {number} opts.sqLength - side length for bounding square
* @param {number} opts.cornerRadius - radius of circular corner
*/
sqCornerCircles: (opts) => {
// TODO - fix implementation. Everything assumes that cornerRad === baseSqLen / 4.
// So the bounding square probably would be off if it's changed.
const sqLen = opts.sqLength;
const baseSqLen = sqLen * 2 / 3;
const halfUnit = baseSqLen / 2;
const cornerRad = opts.cornerRadius || baseSqLen / 4;
const centrePoints = [
[halfUnit, halfUnit],
[-halfUnit, halfUnit],
[halfUnit, -halfUnit],
[-halfUnit, -halfUnit],
];
const baseSquare = square({ size: baseSqLen });
const cornerCircles = union(centrePoints.map(cPt => {
return circle({ radius: cornerRad, center: cPt });
}));
return union(baseSquare, cornerCircles);
},
/**
* Octagonal
* @memberof details.profiles
* @instance
* @param {Object} opts
* @param {number} opts.sqLength - side length for bounding square
*/
octagonal: (opts) => {
const sqLen = opts.sqLength;
// const octagonSideLen = Math.tan(Math.PI / 8) * (sqLen / 2) * 2;
const baseSquare = square({ size: sqLen });
const angledSquare = rotate([0, 0, Math.PI / 4], baseSquare);
return intersect(baseSquare, angledSquare);
},
/**
* Edge profiles
* @memberof details.profiles
* @namespace edge
*/
edge: {
/**
* Edge profile: Circular notch in bottom half
* @memberof details.profiles.edge
* @instance
* @param {Object} opts
* @param {number} opts.totalThickness - total thickness of edge
* @param {number} opts.topThickness - thickness of top (left intact by ornaments)
* @param {number} opts.smallOffset - small offset between notch and main edge
*/
circNotch: (opts) => {
const ornamentThickness = opts.totalThickness - opts.topThickness;
const smallOffset = opts.smallOffset || ornamentThickness / 6;
const notchRadius = ornamentThickness - (smallOffset * 2);
const profileWidth = smallOffset * 2 + notchRadius;
const baseRect = rectangle({ size: [profileWidth, opts.totalThickness] });
const margin = rectangle({ size: [EDGE_PROFILE_MARGIN, opts.totalThickness] });
const alignedMargin = align({ modes: ['max', 'center', 'none'], relativeTo: [profileWidth / -2, 0, 0] }, margin)
const baseShape = union(baseRect, alignedMargin);
const cutawayCircle = circle({ radius: notchRadius, center: [profileWidth / 2 - smallOffset, opts.totalThickness / -2 + smallOffset] });
const cutawayCorner1 = square({
size: smallOffset * 2, center: [
profileWidth / -2 + smallOffset,
opts.totalThickness / -2,
]
});
const cutawayCorner2 = square({
size: smallOffset * 2, center: [
profileWidth / 2,
opts.totalThickness / 2 - opts.topThickness - smallOffset,
]
});
const cutaway = union(cutawayCircle, cutawayCorner1, cutawayCorner2);
return align({ modes: ['center', 'center', 'none'] }, subtract(baseShape, cutaway));
},
/**
* Edge profile: Circular portrusion in bottom half
* @memberof details.profiles.edge
* @instance
* @param {Object} opts
* @param {number} opts.totalThickness - total thickness of edge
* @param {number} opts.topThickness - thickness of top (left intact by ornaments)
* @param {number} opts.smallOffset - small offset between portrusion and main edge
*/
circPortrusion: (opts) => {
const ornamentThickness = opts.totalThickness - opts.topThickness;
const smallOffset = opts.smallOffset || ornamentThickness / 8;
const circRadius = ornamentThickness - (smallOffset * 3);
const profileWidth = smallOffset * 3 + circRadius;
const baseRect = rectangle({ size: [profileWidth, opts.totalThickness] });
const margin = rectangle({ size: [EDGE_PROFILE_MARGIN, opts.totalThickness] });
const alignedMargin = align({ modes: ['max', 'center', 'none'], relativeTo: [profileWidth / -2, 0, 0] }, margin)
const cutaway = translate([0, opts.topThickness / -2], rectangle({ size: [profileWidth, ornamentThickness] }));
const cutShape = subtract(baseRect, cutaway);
const baseShape = union(cutShape, alignedMargin);
const portCircle = circle({ radius: circRadius, center: [profileWidth / -2 + smallOffset, opts.totalThickness / 2 - opts.topThickness - smallOffset] });
const portArc = intersect(baseRect, portCircle);
const smallCorner1 = rectangle({
size: [smallOffset, smallOffset * 2], center: [
profileWidth / -2 + (smallOffset / 2),
opts.totalThickness / -2 + (smallOffset * 2),
]
});
const smallCorner2 = square({
size: smallOffset * 2, center: [
profileWidth / 2 - (smallOffset * 2),
opts.totalThickness / 2 - opts.topThickness,
]
});
const ornament = union(portArc, smallCorner1, smallCorner2)
return align({ modes: ['center', 'center', 'none'] }, union(baseShape, ornament));
},
}
}
}
module.exports = { init: profileBuilder }