Source: swcad-js-components/src/moulding/index.js

"use strict"

/**
 * Builds positive mouldings and negative moulds for various 2D profiles.
 * @memberof components
 * @namespace moulding
 */

const mouldBuilder = ({ jscad, swLib }) => {
  const { measureBoundingBox } = jscad.measurements
  const { extrudeLinear, extrudeRotate } = jscad.extrusions
  const { union, intersect } = jscad.booleans
  const { rotate, align, translate, mirror } = jscad.transforms
  const { cuboid, cylinder } = jscad.primitives

  /**
   * Builds a cuboid with given 2D profile placed on one edge.
   * @memberof components.moulding
   * @instance
   * @param {Object} opts
   * @param {number[]} opts.size - size (x, y, z)
   * @param {string} opts.alignment - where to align when profile size differs from
   *     base cuboid ('top' | 'middle' | 'bottom'). Defaults to 'middle'
   * @param {geom2.Geom2} geomProfile - 2D positive cross-section profile
   * @since 0.12.0
   */
  const cuboidMouldingOneEdge = (opts, geomProfile) => {
    const profileBbox = measureBoundingBox(geomProfile);
    const profileSize = [
      profileBbox[1][0] - profileBbox[0][0],
      profileBbox[1][1] - profileBbox[0][1],
    ];

    const baseBlock = cuboid({
      size: [
        opts.size[0] - profileSize[0],
        opts.size[1], opts.size[2]
      ]
    });

    const edgeBlock = rotate(
      [Math.PI / 2, 0, 0],
      extrudeLinear(
        { height: opts.size[1] },
        geomProfile
      )
    );

    const baseBlockBbox = measureBoundingBox(baseBlock);
    const alignedEdgeBlock = align({
      modes: ['min', 'max', 'none'],
      relativeTo: baseBlockBbox[1]
    }, edgeBlock);

    return align(
      { modes: ['center', 'center', 'none'] },
      union(baseBlock, alignedEdgeBlock)
    );
  }

  /**
   * Positive moulding for a cuboid with the given 2D profile placed onto two opposing side edges.
   * @memberof components.moulding
   * @instance
   * @param {Object} opts
   * @param {number[]} opts.size - size (x, y, z)
   * @param {'x' | 'y'} opts.axis - string to specify which axis to place the profile on. Defaults to 'x'
   * @param {geom2.Geom2} geomProfile - 2D positive cross-section profile
   * @since 0.13.3
   */

  const cuboidMouldingTwoEdges = (opts, geomProfile) => {
    let returnBlock = null
    switch (opts.axis) {
      case 'y':
        // Y axis
        const yHalfSize = [opts.size[1] / 2, opts.size[0], opts.size[2]];
        const yHalfBlock = rotate(
          [0, 0, Math.PI / -2],
          cuboidMouldingOneEdge(
            { size: yHalfSize },
            geomProfile
          )
        );
        const yHalfBlockAdj = align({ modes: ['center', 'max', 'none'] }, yHalfBlock);
        const yBlock = union(
          yHalfBlockAdj,
          mirror({ normal: [0, 1, 0] }, yHalfBlockAdj)
        );
        returnBlock = yBlock
        break;
      case 'x':
      default:
        // X axis
        const xHalfSize = [opts.size[0] / 2, opts.size[1], opts.size[2]];
        const xHalfBlock = align(
          { modes: ['min', 'center', 'none'] },
          cuboidMouldingOneEdge({ size: xHalfSize }, geomProfile)
        );
        const xBlock = union(xHalfBlock, mirror({ normal: [1, 0, 0] }, xHalfBlock));
        returnBlock = xBlock
        break;
    }
    return returnBlock
  }

  /**
   * Positive moulding for a cuboid with the given 2D profile placed onto all the side edges.
   * @memberof components.moulding
   * @instance
   * @param {Object} opts
   * @param {number[]} opts.size - size (x, y, z)
   * @param {geom2.Geom2} geomProfile - 2D positive cross-section profile
   * @since 0.12.0
   */
  const cuboidMoulding = (opts, geomProfile) => {
    const xBlock = cuboidMouldingTwoEdges({
      size: opts.size,
      axis: 'x'
    }, geomProfile)

    const yBlock = cuboidMouldingTwoEdges({
      size: opts.size,
      axis: 'y'
    }, geomProfile)

    return intersect(xBlock, yBlock);
  }

  /**
   * Positive moulding for a cylinder with the given 2D profile placed onto the edge.
   * @memberof components.moulding
   * @instance
   * @param {Object} opts
   * @param {number} opts.radius - Cylinder radius.
   * @param {number} opts.height - Cylinder height.
   * @param {number} opts.segments - Cylinder height.
   * @param {geom2.Geom2} geomProfile - 2D positive cross-section profile
   * @since 0.12.0
   */
  const circularMoulding = (opts, geomProfile) => {
    const profileBbox = measureBoundingBox(geomProfile);
    const profileSize = [
      profileBbox[1][0] - profileBbox[0][0],
      profileBbox[1][1] - profileBbox[0][1]
    ];
    const baseCylRad = opts.radius - profileSize[0];
    // cylinder expanded by a tiny amount to ensure no gaps
    const baseCyl = cylinder({
      radius: baseCylRad + 0.05,
      height: opts.height
    });

    const adjProfile = translate([baseCylRad + profileSize[0] / 2], geomProfile);
    const edgeMoulding = extrudeRotate({ segments: opts.segments || 48 }, adjProfile);

    return union(baseCyl, edgeMoulding);
  }

  return {
    cuboidMouldingOneEdge,
    cuboidMouldingTwoEdges,
    cuboidMoulding,
    circularMoulding,
  }
}

module.exports = { init: mouldBuilder }