Source: models/profiles/connections.js

const connectionsInit = ({ lib, swLib }) => {
    const { circle, rectangle, triangle } = lib.primitives
    const { union, subtract } = lib.booleans
    const { align, translate, mirror } = lib.transforms
    const { hull } = lib.hulls
    const { degToRad } = lib.utils

    const { position, maths } = swLib.core

    //---------------
    //  CONNECTIONS
    //---------------

    const connectionDefaults = {
        tolerance: maths.inchesToMm(1 / 128),
        fit: maths.inchesToMm(1 / 128),
        cornerRadius: maths.inchesToMm(1 / 4),
        dovetailAngle: degToRad(8),
        dovetailMargin: 1,
        tabAngle: degToRad(8),
        tabMargin: 1,
    }

    const createConnTrapezoid = ({ depth, width, angle, reverse = false }) => {
        const baseRect = rectangle({ size: [depth, width] })
        const baseRectCoords = position.getGeomCoords(baseRect)
        const triOpts = position.triangle.rightTriangleOpts({ long: depth, longAngle: angle })
        const triangleEnds = [
            align(
                { modes: ['center', 'max', 'center'], relativeTo: [0, baseRectCoords.back, 0] },
                mirror({ normal: [0, 1, 0] }, triangle(triOpts))
            ),
            align(
                { modes: ['center', 'min', 'center'], relativeTo: [0, baseRectCoords.front, 0] },
                triangle(triOpts)
            ),
        ]

        const outShape = subtract(baseRect, triangleEnds)

        if (reverse) {
            return mirror({ normal: [1, 0, 0] }, outShape)
        }
        return outShape
    }

    /**
     * Connection profiles
     * @memberof models.profiles
     * @namespace connections
     */
    const connections = {
        /**
         * ...
         * @memberof models.profiles.connections
         * @param {object} opts 
         * @returns ...
         */
        pegboard: ({
            spacing,
            radius,
            margin,
            cornerRadius = connectionDefaults.cornerRadius,
            fit = connectionDefaults.fit,
            tolerance = connectionDefaults.tolerance
        }) => {
            const diametre = radius * 2
            const totalGap = fit / 4 + (tolerance / 4)
            const specs = {
                ...connectionDefaults,
                diametre,
                fitDowelRadius: -totalGap + radius,
                fitHoleRadius: totalGap + radius,
                margin: margin || radius,
            }
            specs.totalWidth = specs.margin * 2 + (radius * 2 + spacing)
            const halfWidth = specs.totalWidth / 2
            specs.cornerPoints = [
                [halfWidth - cornerRadius, halfWidth - cornerRadius, 0],
                [-halfWidth + cornerRadius, halfWidth - cornerRadius, 0],
                [-halfWidth + cornerRadius, -halfWidth + cornerRadius, 0],
                [halfWidth - cornerRadius, -halfWidth + cornerRadius, 0],
            ]
            const halfUnit = spacing / 2
            specs.dowelPoints = [
                [halfUnit, halfUnit, 0],
                [-halfUnit, halfUnit, 0],
                [-halfUnit, -halfUnit, 0],
                [halfUnit, -halfUnit, 0],
            ]

            const corners = specs.cornerPoints.map(cPt => {
                return translate(cPt, circle({ radius: cornerRadius }))
            })
            const dowels = specs.dowelPoints.map(dPt => {
                return translate(dPt, circle({ radius: specs.fitDowelRadius }))
            })
            const dowelDies = specs.dowelPoints.map(dPt => {
                return translate(dPt, circle({ radius: specs.fitHoleRadius }))
            })

            const basePlate = hull(corners)
            const dowelAssembly = union(dowels)

            const male = dowelAssembly
            const female = subtract(basePlate, dowelDies)

            return {
                male,
                female,
                size: [specs.totalWidth, specs.totalWidth],
            }
        },
        /**
         * ...
         * @memberof models.profiles.connections
         * @param {object} opts
         * @returns ...
         */
        polygon: ({
            radius,
            segments,
            margin,
            cornerRadius = connectionDefaults.cornerRadius,
            fit = connectionDefaults.fit,
            tolerance = connectionDefaults.tolerance
        }) => {
            const diametre = radius * 2
            const totalGap = fit / 4 + (tolerance / 4)
            const specs = {
                ...connectionDefaults,
                diametre,
                fitDowelRadius: -totalGap + radius,
                fitHoleRadius: totalGap + radius,
                margin: margin || radius,
            }
            specs.totalWidth = specs.margin * 2 + specs.diametre
            const halfWidth = specs.totalWidth / 2
            specs.cornerPoints = [
                [halfWidth - cornerRadius, halfWidth - cornerRadius, 0],
                [-halfWidth + cornerRadius, halfWidth - cornerRadius, 0],
                [-halfWidth + cornerRadius, -halfWidth + cornerRadius, 0],
                [halfWidth - cornerRadius, -halfWidth + cornerRadius, 0],
            ]

            const dowel = circle({ radius: specs.fitDowelRadius, segments })
            const dowelDie = circle({ radius: specs.fitHoleRadius, segments })

            const corners = specs.cornerPoints.map(cPt => {
                return translate(cPt, circle({ radius: cornerRadius }))
            })
            const basePlate = hull(corners)

            const male = dowel
            const female = subtract(basePlate, dowelDie)

            return {
                male,
                female,
                size: [specs.totalWidth, specs.totalWidth],
            }
        },
        /**
         * Tab and Dovetail profiles are designed to fit right against the male edge.
         * With a margin (1mm default) extending into the female edge to ensure one shape that holds both dovetail ends.
         * @memberof models.profiles.connections
         * @param {*} opts 
         * @returns ...
         */
        tab: ({
            width,
            depth,
            margin = connectionDefaults.tabMargin,
            angle = connectionDefaults.tabAngle,
            fit = connectionDefaults.fit,
            tolerance = connectionDefaults.tolerance
        }) => {
            const specs = {
                ...connectionDefaults,
                totalWidth: margin * 2 + width,
                totalTabDepth: margin + depth,
            }

            const baseRect = rectangle({ size: [specs.totalTabDepth, specs.totalWidth] })
            const baseRectCoords = position.getGeomCoords(baseRect)

            const male = align(
                { modes: ['max', 'center', 'center'], relativeTo: [baseRectCoords.right, 0, 0] },
                createConnTrapezoid({ depth, width, angle })
            )
            const female = align(
                { modes: ['min', 'center', 'center'], relativeTo: [baseRectCoords.left, 0, 0] },
                subtract(baseRect, male)
            )

            return {
                male,
                female,
                size: [specs.totalTabDepth, specs.totalWidth],
            }
        },
        /**
         * Tab and Dovetail profiles are designed to fit right against the male edge.
         * With a margin (1mm default) extending into the female edge to ensure one shape that holds both dovetail ends.
         * @memberof models.profiles.connections
         * @param {*} opts 
         * @returns ...
         */
        dovetail: ({
            width,
            depth,
            margin = connectionDefaults.dovetailMargin,
            angle = connectionDefaults.dovetailAngle,
            fit = connectionDefaults.fit,
            tolerance = connectionDefaults.tolerance
        }) => {
            const specs = {
                ...connectionDefaults,
                totalWidth: margin * 2 + width,
                totalTabDepth: margin + depth,
            }

            const baseRect = rectangle({ size: [specs.totalTabDepth, specs.totalWidth] })
            const baseRectCoords = position.getGeomCoords(baseRect)

            const male = align(
                { modes: ['max', 'center', 'center'], relativeTo: [baseRectCoords.right, 0, 0] },
                createConnTrapezoid({ depth, width, angle, reverse: true })
            )
            const female = align(
                { modes: ['min', 'center', 'center'], relativeTo: [baseRectCoords.left, 0, 0] },
                subtract(baseRect, male)
            )

            return {
                male,
                female,
                size: [specs.totalTabDepth, specs.totalWidth],
            }
        },
    }

    return connections;
}

module.exports = { init: connectionsInit }