Source: dowel-fittings/dowel-joists.js

"use strict"

/**
 * ...
 * @namespace families.dowelFittings.dowelJoists
 */

/**
 * ...
 * @param {*} param0 
 * @returns ...
 */
const dowelJoists = ({ lib, swLib }) => {
    const { cylinder, cuboid } = lib.primitives;
    const { subtract, union } = lib.booleans;
    const { align, rotate, translate } = lib.transforms;
    const { TAU } = lib.maths.constants
    const { measureCenter } = lib.measurements

    const { constants, maths } = swLib.core;
    const { swDefaults } = swLib.core.standards;
    const { transform } = swLib.utils;
    const { mesh3d } = swLib.models.prefab;

    const defaultJoistSpecs = {
        tolerance: maths.inchesToMm(1 / 64),
        thicknessTyp: swDefaults.PANEL_THICKNESS_MD,
        thicknessSm: swDefaults.PANEL_THICKNESS_SM,
        joistFrameLength: maths.inchesToMm(1.5),
        jigSideMargin: maths.inchesToMm(3 / 16),
        jigBaseMargin: maths.inchesToMm(1 / 4),
    }

    const completeJoistSpecs = (initSpecs) => {
        const newSpecs = {
            ...initSpecs,
            diam: initSpecs.radius * 2,
        }

        // default type == 'i'
        let dowelPosHeight = newSpecs.height - newSpecs.diam
        let dowelPosWidth = 0
        let frameHeight = dowelPosHeight
        let frameWidth = newSpecs.thicknessTyp * 2 + newSpecs.diam

        if (newSpecs.type === 'rect') {
            dowelPosWidth = newSpecs.width - newSpecs.diam;
            frameHeight = newSpecs.thicknessTyp * -2 + newSpecs.height
            frameWidth = newSpecs.thicknessTyp * -2 + newSpecs.width
        }

        if (newSpecs.type === 'tri') {
            dowelPosWidth = 1 / constants.EQUI_TRIANGLE_HEIGHT_FACTOR * dowelPosHeight;
            frameWidth = dowelPosWidth
        }
        newSpecs.dowelPosHeight = dowelPosHeight
        newSpecs.dowelPosWidth = dowelPosWidth
        newSpecs.frameHeight = frameHeight
        newSpecs.frameWidth = frameWidth

        return newSpecs
    }

    const keyDowels = (joistSpecs) => {
        const outDowels = []
        const baseDowel = cylinder({
            radius: joistSpecs.radius + (joistSpecs.tolerance / 2),
            height: joistSpecs.joistFrameLength * 1.25
        })

        switch (joistSpecs.type) {
            case 'rect':
                outDowels.push(align(
                    { modes: ['center', 'center', 'center'], relativeTo: [joistSpecs.frameWidth / 2, joistSpecs.frameHeight / 2, 0] },
                    baseDowel
                ))
                outDowels.push(align(
                    { modes: ['center', 'center', 'center'], relativeTo: [joistSpecs.frameWidth / -2, joistSpecs.frameHeight / 2, 0] },
                    baseDowel
                ))
                outDowels.push(align(
                    { modes: ['center', 'center', 'center'], relativeTo: [joistSpecs.frameWidth / 2, joistSpecs.frameHeight / -2, 0] },
                    baseDowel
                ))
                outDowels.push(align(
                    { modes: ['center', 'center', 'center'], relativeTo: [joistSpecs.frameWidth / -2, joistSpecs.frameHeight / -2, 0] },
                    baseDowel
                ))
                break;
            case 'tri':
                const vertOffset = (joistSpecs.frameWidth / 2 - (joistSpecs.frameHeight / 2)) / 2
                outDowels.push(align(
                    { modes: ['center', 'center', 'center'], relativeTo: [0, joistSpecs.frameHeight / 2 + vertOffset, 0] },
                    baseDowel
                ))
                outDowels.push(align(
                    { modes: ['center', 'center', 'center'], relativeTo: [joistSpecs.frameWidth / -2, joistSpecs.frameHeight / -2 + vertOffset, 0] },
                    baseDowel
                ))
                outDowels.push(align(
                    { modes: ['center', 'center', 'center'], relativeTo: [joistSpecs.frameWidth / 2, joistSpecs.frameHeight / -2 + vertOffset, 0] },
                    baseDowel
                ))
                break;
            default:
                // defaults to 'i'
                outDowels.push(align(
                    { modes: ['center', 'center', 'center'], relativeTo: [0, joistSpecs.frameHeight / 2, 0] },
                    baseDowel
                ))
                outDowels.push(align(
                    { modes: ['center', 'center', 'center'], relativeTo: [0, joistSpecs.frameHeight / -2, 0] },
                    baseDowel
                ))
                break;
        }

        return outDowels
    }

    const dowelJoistJigs = (joistSpecs) => {
        const dowelBoundaries = [
            joistSpecs.dowelPosWidth + joistSpecs.diam + joistSpecs.tolerance,
            joistSpecs.dowelPosHeight + joistSpecs.diam + joistSpecs.tolerance
        ]

        const cutoutHeight = dowelBoundaries[1] * constants.PHI_INV
        const holderHeight = joistSpecs.jigBaseMargin + cutoutHeight

        // defaults to 'i'
        const cutoutLowerWidth = dowelBoundaries[0]
        const cutoutUpperWidth = joistSpecs.type === 'tri' ? joistSpecs.diam : dowelBoundaries[0]

        const holderWidth = 2 * joistSpecs.jigSideMargin + dowelBoundaries[0]
        const holderWidthUpper = 2 * joistSpecs.jigSideMargin + cutoutUpperWidth
        const holderDepth = holderWidth * constants.PHI_INV
        const cutoutDepth = holderDepth * 1.5

        const basePlate = mesh3d.meshPanel({
            size: [
                2 * joistSpecs.jigSideMargin + holderWidth,
                2 * joistSpecs.jigSideMargin + holderDepth,
                joistSpecs.thicknessTyp,
            ],
            radius: joistSpecs.thicknessTyp,
            segments: 8,
            edgeMargin: joistSpecs.thicknessTyp,
            patternMode: 'fill'
        });

        const jigHolderBlanks = {
            lower: cuboid({
                size: [
                    holderWidth,
                    holderDepth,
                    holderHeight,
                ]
            }), upper: cuboid({
                size: [
                    holderWidthUpper,
                    holderDepth,
                    holderHeight,
                ]
            })
        };

        const cutouts = {
            upper: cuboid({
                size: [
                    cutoutUpperWidth,
                    cutoutDepth,
                    cutoutHeight,
                ]
            }),
            lower: cuboid({
                size: [
                    cutoutLowerWidth,
                    cutoutDepth,
                    cutoutHeight,
                ]
            }),
        }

        const upperJigHolder = subtract(
            align({ modes: ['center', 'center', 'min'] }, jigHolderBlanks.upper),
            align({ modes: ['center', 'center', 'min'] }, cutouts.upper)
        )
        const lowerJigHolder = subtract(
            align({ modes: ['center', 'center', 'max'] }, jigHolderBlanks.lower),
            align({ modes: ['center', 'center', 'max'] }, cutouts.lower)
        )

        const output = {
            upperJig: union(
                align({ modes: ['center', 'center', 'max'] }, basePlate),
                align({ modes: ['center', 'center', 'max'] }, upperJigHolder)
            ),
            lowerJig: union(
                align({ modes: ['center', 'center', 'min'] }, basePlate),
                align({ modes: ['center', 'center', 'min'] }, lowerJigHolder)
            )
        }

        return output
    }

    /**
     * ...
     * @param {Object} opts 
     * @param {number} opts.dowelRadius 
     * @param {number} opts.height
     * @returns ...
     */
    const iJoist = ({ dowelRadius, height }) => {
        const initSpecs = {
            ...defaultJoistSpecs,
            radius: dowelRadius,
            height,
            type: 'i',
        }
        const joistSpecs = completeJoistSpecs(initSpecs)
        const dowels = keyDowels(joistSpecs)

        const offsetWidth = (joistSpecs.frameWidth - joistSpecs.thicknessTyp) / 2

        const joistCore = rotate([0, TAU / 4, 0], mesh3d.meshPanel({
            size: [
                joistSpecs.joistFrameLength,
                joistSpecs.frameHeight,
                joistSpecs.thicknessTyp,
            ],
            radius: joistSpecs.thicknessTyp,
            segments: 8,
            edgeInsets: [offsetWidth, offsetWidth],
            edgeOffsets: [offsetWidth, offsetWidth],
        }));

        let joistFrame = joistCore

        const joist = subtract(joistFrame, ...dowels)

        const joistJigs = dowelJoistJigs(joistSpecs)

        return {
            joist,
            ...joistJigs,
        };
    }

    /**
     * ...
     * @param {Object} opts 
     * @param {number} opts.dowelRadius 
     * @param {number} opts.height
     * @returns ...
     */
    const triJoist = ({ dowelRadius, height }) => {
        const initSpecs = {
            ...defaultJoistSpecs,
            radius: dowelRadius,
            height,
            type: 'tri',
        }
        const joistSpecs = completeJoistSpecs(initSpecs)
        const dowels = keyDowels(joistSpecs)
        const maxPanelWidth = joistSpecs.thicknessTyp * 2 + joistSpecs.diam

        const dowelSleeves = dowels.map((dowelGeom, dgIdx) => {
            let rot = [0, 0, TAU / 2]
            if (dgIdx === 1) {
                rot = [0, 0, TAU / -6]
            } else if (dgIdx === 2) {
                rot = [0, 0, TAU / 6]
            }

            const baseSleeve = cylinder({ radius: maxPanelWidth / 2, height: joistSpecs.joistFrameLength });
            const cutSleeve = transform.cutCircularSlice({ centralAngle: TAU / Math.PI }, baseSleeve)

            return translate(
                measureCenter(dowelGeom),
                rotate(rot, cutSleeve),
            )
        })

        const offsetWidth = (maxPanelWidth - joistSpecs.thicknessTyp) / 2
        const sizeFactor = (joistSpecs.thicknessTyp * -3 + joistSpecs.frameWidth) / joistSpecs.frameWidth

        const joistCorePanel = mesh3d.meshPanel({
            size: [
                joistSpecs.frameWidth * sizeFactor,
                joistSpecs.joistFrameLength,
                joistSpecs.thicknessTyp,
            ],
            radius: joistSpecs.thicknessTyp,
            segments: 8,
            edgeInsets: [offsetWidth, offsetWidth],
        });

        const triHeight = joistSpecs.frameHeight / 2;
        const triHalfBase = joistSpecs.frameWidth / 4;

        const framePanels = [
            align(
                { modes: ['center', 'center', 'center'], relativeTo: [0, triHeight * -sizeFactor, 0] },
                rotate([TAU / 4, 0, TAU / 2], joistCorePanel)
            ),
            align(
                { modes: ['center', 'center', 'center'], relativeTo: [triHalfBase * sizeFactor, 0, 0] },
                rotate([TAU / 4, 0, TAU / -6], joistCorePanel)
            ),
            align(
                { modes: ['center', 'center', 'center'], relativeTo: [triHalfBase * -sizeFactor, 0, 0] },
                rotate([TAU / 4, 0, TAU / 6], joistCorePanel)
            ),
        ]

        let joistFrame = union(...framePanels, ...dowelSleeves)

        const joist = subtract(joistFrame, ...dowels)

        const joistJigs = dowelJoistJigs(joistSpecs)

        return {
            joist,
            ...joistJigs,
        };
    }

    /**
     * ...
     * @param {Object} opts 
     * @param {number} opts.dowelRadius 
     * @param {number} opts.height
     * @returns ...
     */
    const squareJoist = ({ dowelRadius, height }) => {
        const initSpecs = {
            ...defaultJoistSpecs,
            radius: dowelRadius,
            height,
            width: height,
            type: 'rect',
        }
        const joistSpecs = completeJoistSpecs(initSpecs)
        const dowels = keyDowels(joistSpecs)

        const joistCore = mesh3d.meshCuboid({
            size: [joistSpecs.frameWidth, joistSpecs.frameHeight, joistSpecs.joistFrameLength],
            meshPanelThickness: joistSpecs.thicknessTyp,
            radius: joistSpecs.thicknessTyp,
            segments: 8,
            edgeInsets: [swDefaults.PANEL_THICKNESS_SM, swDefaults.PANEL_THICKNESS_SM],
            openTop: true,
            openBottom: true,
        })

        const crossPieceSize = [
            joistSpecs.frameWidth * Math.sqrt(2) - joistSpecs.diam,
            joistSpecs.joistFrameLength,
            joistSpecs.thicknessSm,
        ]
        const crossPiece = rotate([TAU / 4, 0, TAU / 8], mesh3d.meshPanel({
            size: crossPieceSize,
            radius: joistSpecs.thicknessTyp,
            segments: 8,
            edgeInsets: [swDefaults.PANEL_THICKNESS_XS, swDefaults.PANEL_THICKNESS_XS],
            edgeOffsets: [swDefaults.PANEL_THICKNESS_XS, swDefaults.PANEL_THICKNESS_XS],
        }))

        let joistFrame = union(joistCore, crossPiece)

        const joist = subtract(joistFrame, ...dowels)

        const joistJigs = dowelJoistJigs(joistSpecs)

        return {
            joist,
            ...joistJigs,
        };
    }
    /**
     * ...
     * @param {Object} opts 
     * @param {number} opts.dowelRadius 
     * @param {number[]} opts.size - [height, width]
     * @returns ...
     */
    const rectJoist = ({ dowelRadius, size }) => {
        const initSpecs = {
            ...defaultJoistSpecs,
            radius: dowelRadius,
            height: size[0],
            width: size[1],
            type: 'rect',
        }
        const joistSpecs = completeJoistSpecs(initSpecs)
        const dowels = keyDowels(joistSpecs)

        const joistCore = mesh3d.meshCuboid({
            size: [joistSpecs.frameWidth, joistSpecs.frameHeight, joistSpecs.joistFrameLength],
            meshPanelThickness: joistSpecs.thicknessTyp,
            radius: joistSpecs.thicknessTyp,
            segments: 8,
            edgeMargin: joistSpecs.thicknessTyp,
            edgeInsets: [swDefaults.PANEL_THICKNESS_SM, swDefaults.PANEL_THICKNESS_SM],
            openTop: true,
            openBottom: true,
        })

        const frameHypot = Math.hypot(joistSpecs.frameWidth, joistSpecs.frameHeight)
        const frameAngle = Math.atan2(joistSpecs.frameWidth, joistSpecs.frameHeight) - (TAU / 4)
        const crossPieceSize = [
            frameHypot - joistSpecs.diam,
            joistSpecs.joistFrameLength,
            joistSpecs.thicknessSm,
        ]
        const crossPiece = rotate([TAU / 4, 0, -frameAngle], mesh3d.meshPanel({
            size: crossPieceSize,
            radius: joistSpecs.thicknessTyp,
            segments: 8,
            edgeInsets: [swDefaults.PANEL_THICKNESS_XS, swDefaults.PANEL_THICKNESS_XS],
            edgeOffsets: [swDefaults.PANEL_THICKNESS_XS, swDefaults.PANEL_THICKNESS_XS],
        }))

        let joistFrame = union(joistCore, crossPiece)

        const joist = subtract(joistFrame, ...dowels)

        const joistJigs = dowelJoistJigs(joistSpecs)

        return {
            joist,
            ...joistJigs,
        };
    }

    const output = {
        iJoist,
        triJoist,
        squareJoist,
        rectJoist,
    }

    return output;
}

module.exports = { init: dowelJoists }