Creating a simple CSG code for steel shapes

Recently, I commented on a post regarding constructive solid geometry (CSG) but I guess it was in the wrong category of topics. I’ve developed a basic library of steel shapes and written a code that can extrude these shapes. It also allows users to adjust parameters such as position, extrusion length, and rotation around the insertion point for each shape they add to the scene.

However, I’m encountering difficulties with generating code that can efficiently create features like holes, slots, and trims for the extruded shapes (e.g., notching or bevel cuts).

My background primarily involves AutoCAD and Inventor, so I’m not as familiar with using Three.js for such tasks. The goal is to make this application web-based, enabling users to design steel frames or structures with customizable details such as holes, slots, grooves, and trims—like notching or coping to fit specific ends.

I’ve attempted to use an existing CSG library, but I was unable to fully implement the necessary functionality. If you’re experienced with CSG and have a potential workaround using an existing library, I’d appreciate any assistance in reviewing the code to make it functional.

Is the green plate a reference to the type of holes you need to cut? If so do you have more image references as to the type of holes and slots you need to cut? Can you provide a minimal working demo of what you have so far?

If you mainly need to create holes in flat plates you’re definitely better of generating holes in the primary shape before extrusion…

Thanks for the reply. I’ve attached the code as requested.

While it is quite comprehensive, this serves as a solid foundation. The plan is to begin with this and eventually integrate shape sizes stored in a database, allowing for easy retrieval. Additionally, we aim to incorporate preformatted ‘clip angles’ similar to those depicted in some of the attached images.

Some of the Channels or W shapes may feature holes on multiple surfaces, such as the flange or web. The primary idea is to enable users to position the first hole, then array the remaining holes with a specified on-center spacing.

For reference, you can explore the actual shape sizes and gauge specifications for the holes using the AISC Steel Detailing Tool. I’ve relied on this tool for years to configure steel shapes in AutoCAD.

Structural Steel Dimensioning Tool | American Institute of Steel Construction (aisc.org)

Here is an example of a connection with notches and holes in the clip angles or beams:

500px-C18-05.png (500×314) (steelconstruction.info)

The user should have the ability to adjust the gap between the notch and the shape it is intended to fit. The existing CSG method I’ve reviewed will not suffice, as it consistently creates a watertight fit, whereas the minimum gap typically required is 1/2 inch.

Miter and bevel cuts are likely the most straightforward to implement initially. This process involves extruding the shape and then making an angled cut after the extrusion, allowing the user to select the portion to retain.

Here is a link to miter and bevel cuts which also includes a few more examples:
3 Different Types of Miter Saws & Their Uses (with Pictures) | House Grail

These standard photos demonstrate the purpose and importance of properly positioning notches and holes, as you can see, some feature slots instead of holes.

Standard Clip Angles:

Simple Connection with clips and a notch:

The final photo shows a baseplate, essentially an extruded rectangle with holes, which is about the extent I can manage before the code breaks. I can successfully create code that places holes in a shape before extrusion, but my challenge lies in adding holes after extrusion using CSG or a comparable method.

The baseplate code is located below this section, and when viewed in wireframe (which I have currently disabled), it becomes clear that the geometry is incorrect. The wireframe jumps erratically between holes, creating an unusual pattern. For now, I’ve removed the wireframe material while I work to identify the cause of this issue.

I plan on integrating a better GUI but this is feasible for now until I get all of the features required built and configured into the control panel on the right… maybe even adding tabulation or command input later down the road to keep everything in this basic layout.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Steel Drawing Tool</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">

  <style>
    body {
      margin: 0;
      height: 100vh;
      display: flex;
      background-color: #1a1a1a;
    }
    #container {
      width: 80%; /* Takes up 80% of the width */
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: #282828;
    }
    #controls {
      width: 20%; /* Takes up 20% of the width */
      padding: 10px;
      background-color: #333;
      color: white;
      display: flex;
      flex-direction: column;
      justify-content: flex-start;
      align-items: flex-start;
      box-sizing: border-box;
      overflow-y: auto;
    }
    label {
      margin: 5px 0;
      font-size: 0.9em;
    }
    select, input[type="number"], button {
      margin-bottom: 10px;
      padding: 5px;
    }
    button {
      background-color: #555;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }
    button:hover {
      background-color: #777;
    }
    .shape-list {
      margin-top: 20px;
      width: 100%;
      background-color: #444;
      padding: 2px;
      border-radius: 5px;
    }
    .shape-item {
      display: flex;
      flex-direction: column;
      padding: 2px;
      border-bottom: 1px solid #555;
    }
    .shape-item:last-child {
      border-bottom: none;
    }
    .shape-item input {
      width: calc(100% - 10px);
      margin: 2px 0;
    }
    .position-container {
      display: flex; /* Flexbox for side by side */
      justify-content: space-between; /* Space between inputs */
      margin-bottom: 10px; /* Space below the position container */
    }
    .position-input, .extrusion-input {
      width: 30px; /* Width for each input */
    }
  </style>
</head>
<body>
<div id="container">
  <!-- Main content where the 3D scene will be rendered -->
</div>

<div id="controls">
  <label for="steelShape">Steel Shape:</label>
  <select id="steelShape" onchange="updateSteelSizes()">
    <option value="" disabled selected>Select Shape</option>
    <option value="wideFlange">Wide Flange</option>
    <option value="channel">Channel</option>
<option value="tee">Tee</option>
<option value="angle">Angle</option>
<option value="rectHSS">Rectangular HSS</option>
<option value="squareHSS">Square HSS</option>
<option value="pipe">Pipe</option>
  </select>

  <label for="steelSize">Steel Size:</label>
  <select id="steelSize">
    <option value="" disabled selected>Select Size</option>
  </select>

  <label>Position:</label>
  <div class="position-container">
    <input type="number" id="xPosition" class="position-input" value="0" step="1" placeholder="X">
    <input type="number" id="yPosition" class="position-input" value="0" step="1" placeholder="Y">
    <input type="number" id="zPosition" class="position-input" value="0" step="1" placeholder="Z">
  </div>

  <label for="extrusionLength">Extrusion Length:</label>
  <input type="number" id="extrusionLength" class="extrusion-input" value="1" step="0.1">

  <button id="drawSteelButton" onclick="drawSteel()">Draw Steel</button>

  <div class="shape-list" id="shapeList"></div>
</div>

<!-- Three.js Libraries -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128/examples/js/controls/OrbitControls.js"></script>

<script>
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  const renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.getElementById('container').appendChild(renderer.domElement);

  const controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.dampingFactor = 0.05;
  camera.position.set(0, 100, 200);

  const light = new THREE.DirectionalLight(0xffffff, 1);
  light.position.set(10, 100, 10);
  scene.add(light);

  const ambientLight = new THREE.AmbientLight(0x404040);
  scene.add(ambientLight);

  const steelShapes = {
    wideFlange: ['W8x10', 'W10x15', 'W12x22'],
    channel: ['C6x8.2', 'C8x11.5', 'C10x15.3'],
 tee: ['T6x8.2', 'T8x11.5', 'T10x15.3'],
 angle: ['L3x3x1/4', 'L4x4x1/4', 'L5x5x1/2'],
rectHSS: ['HSS3x2x1/4', 'HSS4x2x1/4', 'HSS5x2x1/2'],
squareHSS: ['HSS3x3x1/4', 'HSS4x4x1/4', 'HSS5x5x1/2'],
pipe: ['Pipe1', 'Pipe1-1/4', 'Pipe1-1/2']



  };

  const wideFlangeSizes = {
    "W8x10": [8, 4, 0.3, 0.4],
    "W10x15": [10, 5, 0.4, 0.5],
    "W12x22": [12, 6, 0.5, 0.6]
  };

  const channelSizes = {
    "C6x8.2": [6, 3, 0.25, 0.35],
    "C8x11.5": [8, 4, 0.35, 0.45],
    "C10x15.3": [10, 5, 0.45, 0.55]
  };
 const teeSizes = {
    "T6x8.2": [6, 3, 0.25, 0.35],
    "T8x11.5": [8, 4, 0.35, 0.45],
    "T10x15.3": [10, 5, 0.45, 0.55]
  }; 
const angleSizes = {
    "L3x3x1/4": [3, 0.25],
    "L4x4x1/4": [4, 0.25],
    "L5x5x1/2": [5, 0.5]
  };
const rectHSSSizes = {
    "HSS3x2x1/4": [3, 2, 0.25],
    "HSS4x2x1/4": [4, 2, 0.25],
    "HSS5x2x1/2": [5, 2, 0.5]
  };
const squareHSSSizes = {
    "HSS3x3x1/4": [3, 3, 0.25],
    "HSS4x4x1/4": [4, 4, 0.25],
    "HSS5x5x1/2": [5, 5, 0.5]
  };
const pipeSizes = {
    "Pipe1": [1, 0.1875],
    "Pipe1-1/4": [1.25, 0.25],
    "Pipe1-1/2": [1.5, 0.3]
  };





  const shapesAdded = [];

  function updateSteelSizes() {
    const shapeSelect = document.getElementById('steelShape').value;
    const sizeSelect = document.getElementById('steelSize');
    sizeSelect.innerHTML = '<option value="" disabled selected>Select Size</option>';

    if (shapeSelect && steelShapes[shapeSelect]) {
      steelShapes[shapeSelect].forEach(size => {
        const option = document.createElement('option');
        option.value = size;
        option.text = size;
        sizeSelect.add(option);
      });
    }
  }

  function drawSteel() {
    const selectedShape = document.getElementById('steelShape').value;
    const selectedSize = document.getElementById('steelSize').value;
    const xPos = parseFloat(document.getElementById('xPosition').value);
    const yPos = parseFloat(document.getElementById('yPosition').value);
    const zPos = parseFloat(document.getElementById('zPosition').value);
    const extrusionLength = parseFloat(document.getElementById('extrusionLength').value);

    if (!selectedShape || !selectedSize) {
        alert('Please select both a shape and size.');
        return;
    }

    if (isNaN(extrusionLength) || extrusionLength <= 0) {
        alert('Please enter a valid extrusion length greater than 0.');
        return;
    }

    let shapeMesh;
    let hasHoles = false; // Flag to indicate if the shape has holes

    switch (selectedShape) {
        case 'wideFlange':
            shapeMesh = createWideFlange(selectedSize, [xPos, yPos, zPos], extrusionLength);
            break;
        case 'channel':
            shapeMesh = createChannel(selectedSize, [xPos, yPos, zPos], extrusionLength);
            break;
        case 'tee':
            shapeMesh = createTee(selectedSize, [xPos, yPos, zPos], extrusionLength);
            break;
        case 'angle':
            shapeMesh = createAngle(selectedSize, [xPos, yPos, zPos], extrusionLength);
            break;
        case 'rectHSS':
            shapeMesh = createRectHSS(selectedSize, [xPos, yPos, zPos], extrusionLength);
            hasHoles = true; // RectHSS has holes
            break;
        case 'squareHSS':
            shapeMesh = createSquareHSS(selectedSize, [xPos, yPos, zPos], extrusionLength);
            hasHoles = true; // SquareHSS has holes
            break;
        case 'pipe':
            shapeMesh = createPipe(selectedSize, [xPos, yPos, zPos], extrusionLength);
            hasHoles = true; // Pipe has holes
            break;
        default:
            alert('Invalid shape selected.');
            return;
    }

    shapesAdded.push({
        shape: selectedShape,
        size: selectedSize,
        position: [xPos, yPos, zPos],
        extrusion: extrusionLength,
        rotation: [0, 0, 0], // Initialize rotation values
        mesh: shapeMesh,
        hasHoles: hasHoles // Add hasHoles property
    });

    updateShapeList();
    animate();
}


function updateShapeList() { 
    const shapeListDiv = document.getElementById('shapeList');
    shapeListDiv.innerHTML = '';

    shapesAdded.forEach((shape, index) => {
        const itemDiv = document.createElement('div');
        itemDiv.className = 'shape-item';

        // Create a textbox for shape text
        const shapeTextBox = document.createElement('input');
        shapeTextBox.type = 'text';
        shapeTextBox.value = `${shape.size}`;
        shapeTextBox.readOnly = true; 
        shapeTextBox.style.fontFamily = 'Arial';
        shapeTextBox.style.fontSize = '12px'; 
        shapeTextBox.style.height = '20px'; 
        shapeTextBox.style.marginRight = '5px'; 

        // Create a container for position inputs and extrusion length
        const positionContainer = document.createElement('div');
        positionContainer.className = 'input-container'; 
        positionContainer.style.display = 'flex'; 

        // X Position Input
        const xInput = document.createElement('input');
        xInput.type = 'number';
        xInput.value = shape.position[0];
        xInput.oninput = (e) => updatePosition(index, 0, parseFloat(e.target.value));
        xInput.style.width = '30px'; 
        xInput.style.height = '12px'; 
        xInput.style.fontSize = '10px'; 
        positionContainer.appendChild(xInput);

        // Y Position Input
        const yInput = document.createElement('input');
        yInput.type = 'number';
        yInput.value = shape.position[1];
        yInput.oninput = (e) => updatePosition(index, 1, parseFloat(e.target.value));
        yInput.style.width = '30px'; 
        yInput.style.height = '12px'; 
        yInput.style.fontSize = '10px'; 
        positionContainer.appendChild(yInput);

        // Z Position Input
        const zInput = document.createElement('input');
        zInput.type = 'number';
        zInput.value = shape.position[2];
        zInput.oninput = (e) => updatePosition(index, 2, parseFloat(e.target.value));
        zInput.style.width = '30px'; 
        zInput.style.height = '12px'; 
        zInput.style.fontSize = '10px'; 
        positionContainer.appendChild(zInput);

        // Extrusion Length Input
        const extrusionInput = document.createElement('input');
        extrusionInput.type = 'number';
        extrusionInput.value = shape.extrusion;
        extrusionInput.oninput = (e) => updateExtrusion(index, parseFloat(e.target.value));
        extrusionInput.style.width = '30px'; 
        extrusionInput.style.height = '12px'; 
        extrusionInput.style.fontSize = '10px'; 
        extrusionInput.style.marginLeft = '5px'; 
        positionContainer.appendChild(extrusionInput);

        // Delete Button
        const deleteButton = document.createElement('button');
       deleteButton.innerHTML = '<i class="fa fa-times"></i>';

        deleteButton.onclick = () => deleteShape(index);
        deleteButton.style.width = '20px'; 
        deleteButton.style.height = '20px'; 
        deleteButton.style.marginTop = '5px'; 
        deleteButton.style.marginLeft = '5px'; 
        deleteButton.style.fontSize = '10px'; 
        deleteButton.style.cursor = 'pointer'; 
        deleteButton.style.backgroundColor = '#ff4d4d'; 
        deleteButton.style.color = '#fff'; 
        deleteButton.style.border = 'none'; 
        deleteButton.style.borderRadius = '1px'; 
        positionContainer.appendChild(deleteButton); 

        // Add position container to itemDiv
        itemDiv.appendChild(shapeTextBox);
        itemDiv.appendChild(positionContainer); 

        // Create a container for rotation inputs
        const rotationContainer = document.createElement('div');
        rotationContainer.className = 'input-container'; 
        rotationContainer.style.display = 'flex'; 

        // Rotation Inputs
        const xRotationInput = createRotationInput('X', shape.rotation[0], index, 0);
        const yRotationInput = createRotationInput('Y', shape.rotation[1], index, 1);
        const zRotationInput = createRotationInput('Z', shape.rotation[2], index, 2);
        
        rotationContainer.appendChild(xRotationInput);
        rotationContainer.appendChild(yRotationInput);
        rotationContainer.appendChild(zRotationInput);

        // Rotation Button
        const rotationButton = document.createElement('button');
       rotationButton.innerHTML = '<i class="fa fa-sync-alt"></i>';

        rotationButton.disabled = true; // Makes the button unclickable
        rotationButton.style.width = '20px'; 
        rotationButton.style.height = '20px'; 
        rotationButton.style.marginLeft = '5px'; 
        rotationButton.style.fontSize = '10px'; 
      
        rotationButton.style.backgroundColor = 'transparent'; // Makes it clear
        rotationButton.style.color = 'black'; // Optional: set text color
        rotationButton.style.border = '1px solid transparent'; // Optional: clear border
        rotationContainer.appendChild(rotationButton);

        // Add rotation container to itemDiv
        itemDiv.appendChild(rotationContainer); 

        // Add itemDiv to the shape list
        shapeListDiv.appendChild(itemDiv);
    });
}

function createRotationInput(axis, value, index, rotationIndex) {
    const rotationInput = document.createElement('input');
    rotationInput.type = 'number';
    rotationInput.value = value;
    rotationInput.placeholder = `${axis} Rotation`;
    rotationInput.oninput = (e) => updateRotation(index, rotationIndex, parseFloat(e.target.value));
    rotationInput.style.width = '40px'; 
    rotationInput.style.height = '12px'; 
    rotationInput.style.fontSize = '10px'; 
    rotationInput.style.marginLeft = '5px'; 
    return rotationInput;
}

function updateRotation(index, rotationIndex, value) {
    if (isNaN(value)) return;
    shapesAdded[index].rotation[rotationIndex] = value;
    rotateShape(index);
}

function rotateShape(index) {
    const shape = shapesAdded[index];
    shape.mesh.rotation.set(
        THREE.MathUtils.degToRad(shape.rotation[0]),
        THREE.MathUtils.degToRad(shape.rotation[1]),
        THREE.MathUtils.degToRad(shape.rotation[2])
    );
    animate(); // Refresh the animation/render
}

function createWideFlange(sizeLabel, position, extrusionLength) { 
    const [beamDepth, flangeWidth, webThickness, flangeDepth] = wideFlangeSizes[sizeLabel];
    const shape = new THREE.Shape();
    const filletRadius = flangeDepth / 2;

    // Start drawing the shape
    shape.moveTo(-flangeWidth / 2, -beamDepth / 2); // Start point
    shape.lineTo(flangeWidth / 2, -beamDepth / 2); // Top horizontal line
    shape.lineTo(flangeWidth / 2, -beamDepth / 2 + flangeDepth - filletRadius); // Top right vertical line
    shape.quadraticCurveTo(flangeWidth / 2, -beamDepth / 2 + flangeDepth, flangeWidth / 2 - filletRadius, -beamDepth / 2 + flangeDepth); // Top right arc
    shape.lineTo(webThickness / 2 + filletRadius, -beamDepth / 2 + flangeDepth); // Right web line
    shape.quadraticCurveTo(webThickness / 2, -beamDepth / 2 + flangeDepth, webThickness / 2, -beamDepth / 2 + flangeDepth + filletRadius); // Right web arc
    shape.lineTo(webThickness / 2, beamDepth / 2 - flangeDepth - filletRadius); // Bottom right vertical line
    shape.quadraticCurveTo(webThickness / 2, beamDepth / 2 - flangeDepth, webThickness / 2 + filletRadius, beamDepth / 2 - flangeDepth); // Bottom right arc
    shape.lineTo(flangeWidth / 2 - filletRadius, beamDepth / 2 - flangeDepth); // Bottom right horizontal line
    shape.quadraticCurveTo(flangeWidth / 2, beamDepth / 2 - flangeDepth, flangeWidth / 2, beamDepth / 2); // Bottom right arc
    shape.lineTo(flangeWidth / 2, beamDepth / 2); // Bottom right vertical line
    shape.lineTo(-flangeWidth / 2, beamDepth / 2); // Bottom left horizontal line
    shape.lineTo(-flangeWidth / 2, beamDepth / 2 - flangeDepth + filletRadius); // Bottom left vertical line
    shape.quadraticCurveTo(-flangeWidth / 2, beamDepth / 2 - flangeDepth, -flangeWidth / 2 + filletRadius, beamDepth / 2 - flangeDepth); // Bottom left arc
    shape.lineTo(-webThickness / 2 - filletRadius, beamDepth / 2 - flangeDepth); // Left web line
    shape.quadraticCurveTo(-webThickness / 2, beamDepth / 2 - flangeDepth, -webThickness / 2, beamDepth / 2 - flangeDepth - filletRadius); // Left web arc
    shape.lineTo(-webThickness / 2, -beamDepth / 2 + flangeDepth + filletRadius); // Top left vertical line
    shape.quadraticCurveTo(-webThickness / 2, -beamDepth / 2 + flangeDepth, -webThickness / 2 - filletRadius, -beamDepth / 2 + flangeDepth); // Top left arc
    shape.lineTo(-flangeWidth / 2 + filletRadius, -beamDepth / 2 + flangeDepth); // Top left horizontal line
    shape.quadraticCurveTo(-flangeWidth / 2, -beamDepth / 2 + flangeDepth, -flangeWidth / 2, -beamDepth / 2); // Top left arc

    const geometry = new THREE.ExtrudeGeometry(shape, {
        depth: extrusionLength,
        bevelEnabled: false,
    });
    const material = new THREE.MeshStandardMaterial({ color: 0x808080 });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(...position);
    scene.add(mesh);
    return mesh;
}


function createChannel(sizeLabel, position, extrusionLength) { 
    const [beamDepth, flangeWidth, webThickness, flangeDepth] = channelSizes[sizeLabel];
    const shape = new THREE.Shape();
    const filletRadius = flangeDepth / 2;

    // Start drawing the shape
    shape.moveTo(-flangeWidth / 2, -beamDepth / 2); // Start point

    // Left side
    shape.lineTo(-flangeWidth / 2, beamDepth / 2); // Vertical line
    shape.lineTo(flangeWidth / 2, beamDepth / 2); // Bottom horizontal line

    // Bottom right arc
    shape.lineTo(flangeWidth / 2, beamDepth / 2 - flangeDepth + filletRadius);
    shape.quadraticCurveTo(
        flangeWidth / 2, 
        beamDepth / 2 - flangeDepth, 
        flangeWidth / 2 - filletRadius, 
        beamDepth / 2 - flangeDepth
    );

    // Bottom web line
    shape.lineTo(-flangeWidth / 2 + webThickness + filletRadius, beamDepth / 2 - flangeDepth);

    // Bottom left arc
    shape.quadraticCurveTo(
        -flangeWidth / 2 + webThickness, 
        beamDepth / 2 - flangeDepth, 
        -flangeWidth / 2 + webThickness, 
        beamDepth / 2 - flangeDepth - filletRadius
    );

    // Left web line
    shape.lineTo(-flangeWidth / 2 + webThickness, -beamDepth / 2 + flangeDepth + filletRadius);

    // Top left arc
    shape.quadraticCurveTo(
        -flangeWidth / 2 + webThickness, 
        -beamDepth / 2 + flangeDepth, 
        -flangeWidth / 2 + webThickness + filletRadius, 
        -beamDepth / 2 + flangeDepth
    );

    // Top web line
    shape.lineTo(flangeWidth / 2 - filletRadius, -beamDepth / 2 + flangeDepth);

    // Top right arc
    shape.quadraticCurveTo(
        flangeWidth / 2, 
        -beamDepth / 2 + flangeDepth, 
        flangeWidth / 2, 
        -beamDepth / 2 + flangeDepth - filletRadius
    );

    // Top right vertical line
    shape.lineTo(flangeWidth / 2, -beamDepth / 2);

    // Close the shape automatically
    shape.autoClose = true;

    const geometry = new THREE.ExtrudeGeometry(shape, {
        depth: extrusionLength,
        bevelEnabled: false,
    });
    const material = new THREE.MeshStandardMaterial({ color: 0x808080 });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(...position);
    scene.add(mesh);
    return mesh;
}



function createTee(sizeLabel, position, extrusionLength) { 
    const [beamDepth, flangeWidth, webThickness, flangeDepth] = teeSizes[sizeLabel]; // Assuming teeSizes is defined
    const shape = new THREE.Shape();
    const filletRadius = flangeDepth / 2;

    // Start drawing the Tee shape
    shape.moveTo(-flangeWidth / 2, -beamDepth / 2); // Top-left corner

    // Top horizontal line
    shape.lineTo(flangeWidth / 2, -beamDepth / 2);

    // Right flange corner (rounded)
    shape.lineTo(flangeWidth / 2, -beamDepth / 2 + webThickness - filletRadius);
    shape.quadraticCurveTo(
        flangeWidth / 2, 
        -beamDepth / 2 + webThickness, 
        flangeWidth / 2 - filletRadius, 
        -beamDepth / 2 + webThickness
    );

    // Right side of flange
    shape.lineTo(webThickness / 2 + filletRadius, -beamDepth / 2 + webThickness);

    // Right-top corner at Web (rounded)
    shape.quadraticCurveTo(
        webThickness / 2, 
        -beamDepth / 2 + webThickness, 
        webThickness / 2, 
        -beamDepth / 2 + webThickness + filletRadius
    );

    // Right vertical line down
    shape.lineTo(webThickness / 2, beamDepth / 2 - filletRadius);

    // Right-bottom corner (rounded)
    shape.quadraticCurveTo(
        webThickness / 2, 
        beamDepth / 2, 
        webThickness / 2 - filletRadius, 
        beamDepth / 2
    );

    // Bottom horizontal line
    shape.lineTo(-webThickness / 2 + filletRadius, beamDepth / 2);

    // Bottom-left corner (rounded)
    shape.quadraticCurveTo(
        -webThickness / 2, 
        beamDepth / 2, 
        -webThickness / 2, 
        beamDepth / 2 - filletRadius
    );

    // Left vertical line up
    shape.lineTo(-webThickness / 2, -beamDepth / 2 + webThickness + filletRadius);

    // Left-top corner at Web (rounded)
    shape.quadraticCurveTo(
        -webThickness / 2, 
        -beamDepth / 2 + webThickness, 
        -webThickness / 2 - filletRadius, 
        -beamDepth / 2 + webThickness
    );

    // Left side of flange
    shape.lineTo(-flangeWidth / 2 + filletRadius, -beamDepth / 2 + webThickness);

    // Left flange corner (rounded)
    shape.quadraticCurveTo(
        -flangeWidth / 2, 
        -beamDepth / 2 + webThickness, 
        -flangeWidth / 2, 
        -beamDepth / 2 + webThickness - filletRadius
    );

    const geometry = new THREE.ExtrudeGeometry(shape, {
        depth: extrusionLength,
        bevelEnabled: false,
    });
    const material = new THREE.MeshStandardMaterial({ color: 0x808080 });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(...position);
    scene.add(mesh);
    return mesh;
}


function createAngle(sizeLabel, position, extrusionLength) {
    const [legLength, thickness] = angleSizes[sizeLabel]; // Assuming angleSizes is defined
    const shape = new THREE.Shape();
    const filletRadius = thickness / 2;

    // Start drawing the angle shape at the insertion point
    shape.moveTo(0, 0); // Insertion point

    // Draw the first leg
    shape.lineTo(legLength, 0);

    // Draw the rounded corner
    shape.lineTo(legLength, -filletRadius);
    shape.quadraticCurveTo(
        legLength, 
        -thickness, 
        legLength - filletRadius, 
        -thickness
    );

    // Draw the horizontal line to the second corner
    shape.lineTo(thickness + filletRadius, -thickness);
    shape.quadraticCurveTo(
        thickness, 
        -thickness, 
        thickness, 
        -thickness - filletRadius
    );

    // Draw the vertical line to the third corner
    shape.lineTo(thickness, -legLength + filletRadius);
    shape.quadraticCurveTo(
        thickness, 
        -legLength, 
        thickness - filletRadius, 
        -legLength
    );

    // Draw the final vertical line down to the last point
    shape.lineTo(0, -legLength);

    // Close the path
    shape.autoClose = true;

    const geometry = new THREE.ExtrudeGeometry(shape, {
        depth: extrusionLength,
        bevelEnabled: false,
    });
    const material = new THREE.MeshStandardMaterial({ color: 0x808080 });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(...position);
    scene.add(mesh);
    return mesh;
}

function createRectHSS(sizeLabel, position, extrusionLength) {
    // Get dimensions from rectHSSSizes
    const [sideLength, sideWidth, wallThickness] = rectHSSSizes[sizeLabel]; 
    const outerWidth = sideLength;
    const outerHeight = sideWidth;
    const innerWidth = outerWidth - 2 * wallThickness;
    const innerHeight = outerHeight - 2 * wallThickness;
    const filletRadius = wallThickness * 2; // Fillet radius
    const innerFilletRadius = filletRadius - wallThickness; // Inner Fillet radius

    // Create outer shape with fillets
    const outerShape = new THREE.Shape();
    outerShape.moveTo(outerWidth / 2, -outerHeight / 2 + filletRadius);
    outerShape.lineTo(outerWidth / 2, outerHeight / 2 - filletRadius);
    outerShape.absarc(outerWidth / 2 - filletRadius, outerHeight / 2 - filletRadius, filletRadius, 0, Math.PI / 2, false);
    outerShape.lineTo(-outerWidth / 2 + filletRadius, outerHeight / 2);
    outerShape.absarc(-outerWidth / 2 + filletRadius, outerHeight / 2 - filletRadius, filletRadius, Math.PI / 2, Math.PI, false);
    outerShape.lineTo(-outerWidth / 2, -outerHeight / 2 + filletRadius);
    outerShape.absarc(-outerWidth / 2 + filletRadius, -outerHeight / 2 + filletRadius, filletRadius, Math.PI, (3 * Math.PI) / 2, false);
    outerShape.lineTo(outerWidth / 2 - filletRadius, -outerHeight / 2);
    outerShape.absarc(outerWidth / 2 - filletRadius, -outerHeight / 2 + filletRadius, filletRadius, (3 * Math.PI) / 2, 0, false);
    outerShape.closePath();

    // Create inner shape with fillets (hollow)
    const innerShape = new THREE.Path(); // Changed from THREE.Shape to THREE.Path
    innerShape.moveTo(innerWidth / 2, -innerHeight / 2 + innerFilletRadius);
    innerShape.lineTo(innerWidth / 2, innerHeight / 2 - innerFilletRadius);
    innerShape.absarc(innerWidth / 2 - innerFilletRadius, innerHeight / 2 - innerFilletRadius, innerFilletRadius, 0, Math.PI / 2, false);
    innerShape.lineTo(-innerWidth / 2 + innerFilletRadius, innerHeight / 2);
    innerShape.absarc(-innerWidth / 2 + innerFilletRadius, innerHeight / 2 - innerFilletRadius, innerFilletRadius, Math.PI / 2, Math.PI, false);
    innerShape.lineTo(-innerWidth / 2, -innerHeight / 2 + innerFilletRadius);
    innerShape.absarc(-innerWidth / 2 + innerFilletRadius, -innerHeight / 2 + innerFilletRadius, innerFilletRadius, Math.PI, (3 * Math.PI) / 2, false);
    innerShape.lineTo(innerWidth / 2 - innerFilletRadius, -innerHeight / 2);
    innerShape.absarc(innerWidth / 2 - innerFilletRadius, -innerHeight / 2 + innerFilletRadius, innerFilletRadius, (3 * Math.PI) / 2, 0, false);
    innerShape.closePath();

    // Add the inner shape as a hole to the outer shape
    outerShape.holes.push(innerShape); // Use the inner shape directly

    const geometry = new THREE.ExtrudeGeometry(outerShape, {
        depth: extrusionLength,
        bevelEnabled: false,
    });
    const material = new THREE.MeshStandardMaterial({ color: 0x808080 });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(...position);
    scene.add(mesh);
    return mesh;
}

function createSquareHSS(sizeLabel, position, extrusionLength) {
    // Get dimensions from squareHSSSizes
    const [sideLength, sideWidth, wallThickness] = squareHSSSizes[sizeLabel]; 
    const outerWidth = sideLength;
    const outerHeight = sideWidth;
    const innerWidth = outerWidth - 2 * wallThickness;
    const innerHeight = outerHeight - 2 * wallThickness;
    const filletRadius = wallThickness * 2; // Fillet radius
    const innerFilletRadius = filletRadius - wallThickness; // Inner Fillet radius

    // Create outer shape with fillets
    const outerShape = new THREE.Shape();
    outerShape.moveTo(outerWidth / 2, -outerHeight / 2 + filletRadius);
    outerShape.lineTo(outerWidth / 2, outerHeight / 2 - filletRadius);
    outerShape.absarc(outerWidth / 2 - filletRadius, outerHeight / 2 - filletRadius, filletRadius, 0, Math.PI / 2, false);
    outerShape.lineTo(-outerWidth / 2 + filletRadius, outerHeight / 2);
    outerShape.absarc(-outerWidth / 2 + filletRadius, outerHeight / 2 - filletRadius, filletRadius, Math.PI / 2, Math.PI, false);
    outerShape.lineTo(-outerWidth / 2, -outerHeight / 2 + filletRadius);
    outerShape.absarc(-outerWidth / 2 + filletRadius, -outerHeight / 2 + filletRadius, filletRadius, Math.PI, (3 * Math.PI) / 2, false);
    outerShape.lineTo(outerWidth / 2 - filletRadius, -outerHeight / 2);
    outerShape.absarc(outerWidth / 2 - filletRadius, -outerHeight / 2 + filletRadius, filletRadius, (3 * Math.PI) / 2, 0, false);
    outerShape.closePath();

    // Create inner shape with fillets (hollow)
    const innerShape = new THREE.Path(); // Changed from THREE.Shape to THREE.Path
    innerShape.moveTo(innerWidth / 2, -innerHeight / 2 + innerFilletRadius);
    innerShape.lineTo(innerWidth / 2, innerHeight / 2 - innerFilletRadius);
    innerShape.absarc(innerWidth / 2 - innerFilletRadius, innerHeight / 2 - innerFilletRadius, innerFilletRadius, 0, Math.PI / 2, false);
    innerShape.lineTo(-innerWidth / 2 + innerFilletRadius, innerHeight / 2);
    innerShape.absarc(-innerWidth / 2 + innerFilletRadius, innerHeight / 2 - innerFilletRadius, innerFilletRadius, Math.PI / 2, Math.PI, false);
    innerShape.lineTo(-innerWidth / 2, -innerHeight / 2 + innerFilletRadius);
    innerShape.absarc(-innerWidth / 2 + innerFilletRadius, -innerHeight / 2 + innerFilletRadius, innerFilletRadius, Math.PI, (3 * Math.PI) / 2, false);
    innerShape.lineTo(innerWidth / 2 - innerFilletRadius, -innerHeight / 2);
    innerShape.absarc(innerWidth / 2 - innerFilletRadius, -innerHeight / 2 + innerFilletRadius, innerFilletRadius, (3 * Math.PI) / 2, 0, false);
    innerShape.closePath();

    // Add the inner shape as a hole to the outer shape
    outerShape.holes.push(innerShape); // Use the inner shape directly

    const geometry = new THREE.ExtrudeGeometry(outerShape, {
        depth: extrusionLength,
        bevelEnabled: false,
    });
    const material = new THREE.MeshStandardMaterial({ color: 0x808080 });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(...position);
    scene.add(mesh);
    return mesh;
}

function createPipe(sizeLabel, position, extrusionLength) {
    // Get dimensions from pipeSizes
    const [outerDiameter, wallThickness] = pipeSizes[sizeLabel]; 
    const innerDiameter = outerDiameter - wallThickness * 2;

    // Create outer shape (circle)
    const outerShape = new THREE.Shape();
    outerShape.absarc(0, 0, outerDiameter / 2, 0, Math.PI * 2, false); // Create outer circle

    // Create inner shape (circle)
    const innerShape = new THREE.Path();
    innerShape.absarc(0, 0, innerDiameter / 2, 0, Math.PI * 2, false); // Create inner circle

    // Add the inner shape as a hole to the outer shape
    outerShape.holes.push(innerShape); // Use the inner shape directly

    const geometry = new THREE.ExtrudeGeometry(outerShape, {
        depth: extrusionLength,
        bevelEnabled: false,
    });
    const material = new THREE.MeshStandardMaterial({ color: 0x808080 });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(...position);
    scene.add(mesh);
    return mesh;
}



function updatePosition(index, axis, value) {
    if (isNaN(value)) return;
    shapesAdded[index].position[axis] = value;
    shapesAdded[index].mesh.position.set(...shapesAdded[index].position);
    animate(); // Refresh the animation/render
}

function updateExtrusion(index, value) {
    if (isNaN(value) || value <= 0) return;
    shapesAdded[index].extrusion = value;

    // Redraw the shape with the updated extrusion length
    let shapeMesh;
    if (shapesAdded[index].shape === 'wideFlange') {
        shapeMesh = createWideFlange(shapesAdded[index].size, shapesAdded[index].position, value);
    } else if (shapesAdded[index].shape === 'channel') {
        shapeMesh = createChannel(shapesAdded[index].size, shapesAdded[index].position, value);
    } else if (shapesAdded[index].shape === 'tee') {
        shapeMesh = createTee(shapesAdded[index].size, shapesAdded[index].position, value);
    } else if (shapesAdded[index].shape === 'angle') {
        shapeMesh = createAngle(shapesAdded[index].size, shapesAdded[index].position, value);
    }  else if (shapesAdded[index].shape === 'rectHSS') {
        shapeMesh = createRectHSS(shapesAdded[index].size, shapesAdded[index].position, value);
    }  else if (shapesAdded[index].shape === 'squareHSS') {
        shapeMesh = createSquareHSS(shapesAdded[index].size, shapesAdded[index].position, value);
    } else if (shapesAdded[index].shape === 'pipe') {
        shapeMesh = createPipe(shapesAdded[index].size, shapesAdded[index].position, value);
    }






    // Clean up the old mesh
    scene.remove(shapesAdded[index].mesh);
    shapesAdded[index].mesh.geometry.dispose(); // Dispose of the old geometry
    shapesAdded[index].mesh = shapeMesh; // Update mesh reference
    scene.add(shapeMesh); // Add new mesh to the scene

    animate(); // Refresh the animation/render
}



  function deleteShape(index) {
    const shape = shapesAdded[index];
    scene.remove(shape.mesh);
    shapesAdded.splice(index, 1);
    updateShapeList();
    animate(); // Refresh the animation/render
  }

  function animate() {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
  }

  animate();
</script>
</body>
</html>

And here is the code for the baseplates:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Polygon with Cylinder in Between</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            display: flex;
            height: 100vh;
            background-color: #1a1a1a;
        }
        #container {
            flex: 1;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        #controls {
            padding: 5px;
            background-color: #333;
            color: white;
            display: flex;
            position: absolute;
            bottom: 10px;
            width: 100%;
            justify-content: center;
        }
        input, button{
   width: 50px;

            padding: 5px;
            margin-right: 10px;
            background-color: #555;
            color: white;
            border: none;
            border-radius: 5px;
        }

 button, select {

            padding: 5px;
            margin-right: 10px;
            background-color: #555;
            color: white;
            border: none;
            border-radius: 5px;
        }

        button:hover, select:hover {
            background-color: #777;
        }
        #materialSelect {
            position: absolute;
            top: 10px;
            right: 10px;
        }
    </style>
</head>
<body>
    <div id="container"></div>
  <div id="controls">
    <!-- Shape Selection -->
    <select id="shapeSelect" onchange="switchShape()">
        <option value="polygonal">Polygonal</option>
        <option value="rectangular">Rectangular</option>
  <option value="circular">Circular</option>
    </select>
  <!-- Circlular Controls -->
    <div id="circularControls">
        <input type="number" id="numHoles" placeholder="Number of holes" min="1" value="4">
        <input type="number" id="circleDiameter" placeholder="Circle diameter" min="1" value="100">
        <input type="number" id="extrudedCircleDiameter" placeholder="Extruded circle diameter" min="1" value="50">
    </div>

    <!-- Polygonal Controls -->
    <div id="polygonControls">
        <input type="number" id="sides" placeholder="Number of sides" min="3" value="4">
        <input type="number" id="polygonDiameter" placeholder="Polygon diameter" min="1" value="100">
        <input type="number" id="extrudedDiameter" placeholder="Extruded polygon diameter" min="1" value="50">
    </div>

    <!-- Common Controls (both Polygonal and Rectangular) -->
    <div id="commonControls">
        <input type="number" id="holeDiameter" placeholder="Hole diameter" min="1" value="10">
        <input type="number" id="distance" placeholder="Distance from corners" min="1" value="10">
        <input type="number" id="extrusionAmount" placeholder="Extrusion amount" min="1" value="10">
    </div>

  <!-- Rectangular Controls --> 
<div id="rectangularControls" style="display: none;">
    <input type="number" id="rectLength" placeholder="Length" min="1" value="100">
    <input type="number" id="rectWidth" placeholder="Width" min="1" value="50">
    <input type="number" id="holeColumns" placeholder="Columns" min="2" value="2"> 
    <input type="number" id="holeRows" placeholder="Rows" min="2" value="2"> 
</div>


    <!-- Update Button -->
    <button onclick="updateShape()">Update Shape</button>
</div>

    <select id="materialSelect" onchange="updateMaterial()">
        <option value="basic">Basic Material</option>
        <option value="wireframe">Wireframe</option>
        <option value="metallic">Metallic</option>
    </select>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128/examples/js/controls/OrbitControls.js"></script>

   <script>
    let scene, camera, renderer, controls;
    let polygonMesh, holes = [];
    let extrusionMesh, extrudedPolygonMesh;
    let cylinderMesh;
    let rectangularMesh;
let circularMesh;
    let materialType = 'basic';

    function init() {
        scene = new THREE.Scene();
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.z = 150;

        renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.getElementById('container').appendChild(renderer.domElement);

        controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;

        const ambientLight = new THREE.AmbientLight(0x404040);
        scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
        directionalLight.position.set(0, 1, 1).normalize();
        scene.add(directionalLight);

        updatePolygonAndHoles();
        animate();
    }

    function createPolygonVertices(sides, diameter) {
        const vertices = [];
        const radius = diameter / 2;
        for (let i = 0; i < sides; i++) {
            const angle = (i / sides) * Math.PI * 2;
            const x = radius * Math.cos(angle);
            const y = radius * Math.sin(angle);
            vertices.push(new THREE.Vector3(x, y, 0));
        }
        return vertices;
    }

    function createPolygonMesh(vertices) {
        const geometry = new THREE.BufferGeometry().setFromPoints(vertices);
        const material = new THREE.LineBasicMaterial({ color: 0x00ff00 });
        return new THREE.LineLoop(geometry, material);
    }

    function createHole(radius) {
        return new THREE.CircleGeometry(radius, 32);
    }

  function createExtrusionShape(vertices, holes, extrusionAmount) {
    const shape = new THREE.Shape(vertices);
    holes.forEach(hole => {
        const holePath = new THREE.Path(); // Avoid naming conflict
        holePath.absarc(hole.x, hole.y, hole.radius, 0, Math.PI * 2, false);
        shape.holes.push(holePath);
    });

    const geometry = new THREE.ExtrudeGeometry(shape, { depth: extrusionAmount, bevelEnabled: false });
    const material = getMaterial();

    return new THREE.Mesh(geometry, material);
}

    function createCylinder(baseDiameter, topDiameter, height, sides) {
        const geometry = new THREE.CylinderGeometry(baseDiameter / 2, topDiameter / 2, height, sides);
        const material = new THREE.MeshBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.5 });
        return new THREE.Mesh(geometry, material);
    }

    function updatePolygonAndHoles() {
          if (circularMesh) {
            scene.remove(circularMesh);
        }
if (polygonMesh) {
            scene.remove(polygonMesh);
        }
        holes.forEach(hole => scene.remove(hole));
        holes = [];
        if (extrusionMesh) {
            scene.remove(extrusionMesh);
        }
        if (extrudedPolygonMesh) {
            scene.remove(extrudedPolygonMesh);
        }
        if (cylinderMesh) {
            scene.remove(cylinderMesh);
        }
      if (rectangularMesh) {
            scene.remove(rectangularMesh);
        }

        const sides = parseInt(document.getElementById('sides').value);
        const polygonDiameter = parseInt(document.getElementById('polygonDiameter').value);
        const extrudedDiameter = parseInt(document.getElementById('extrudedDiameter').value);
        const holeDiameter = parseInt(document.getElementById('holeDiameter').value);
        const distance = parseInt(document.getElementById('distance').value);
        const extrusionAmount = parseInt(document.getElementById('extrusionAmount').value);

        const vertices = createPolygonVertices(sides, polygonDiameter);
        polygonMesh = createPolygonMesh(vertices);
     

        const extrudedVertices = createPolygonVertices(sides, extrudedDiameter);
        extrudedPolygonMesh = createPolygonMesh(extrudedVertices);
        extrudedPolygonMesh.position.z = extrusionAmount;
     

        const holeRadius = holeDiameter / 2;
        const positions = vertices.map(vertex => {
            const angle = Math.atan2(vertex.y, vertex.x);
            return {
                x: vertex.x - (distance * Math.cos(angle)),
                y: vertex.y - (distance * Math.sin(angle)),
                radius: holeRadius
            };
        });

        positions.forEach(pos => {
            const hole = createHole(pos.radius);
            const holeMesh = new THREE.Mesh(hole, new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide, transparent: true, opacity: 0 }));
            holeMesh.position.set(pos.x, pos.y, 0);
            holes.push(holeMesh);
            scene.add(holeMesh);
        });

        extrusionMesh = createExtrusionShape(vertices, positions, extrusionAmount);
        scene.add(extrusionMesh);

        const cylinderHeight = extrusionAmount;
        cylinderMesh = createCylinder(extrudedDiameter, polygonDiameter, cylinderHeight, sides);
        cylinderMesh.position.z = cylinderHeight / 2;

        const rotationAngle = -(Math.PI / sides) * 2 + Math.PI / 2;
        cylinderMesh.rotation.y = rotationAngle;
        cylinderMesh.rotation.x = Math.PI / 2;

        
    }


    function updateCircularMesh() {
          if (circularMesh) {
            scene.remove(circularMesh);
        }
if (polygonMesh) {
            scene.remove(polygonMesh);
        }
        holes.forEach(hole => scene.remove(hole));
        holes = [];
        if (extrusionMesh) {
            scene.remove(extrusionMesh);
        }
        if (extrudedPolygonMesh) {
            scene.remove(extrudedPolygonMesh);
        }
        if (cylinderMesh) {
            scene.remove(cylinderMesh);
        }
      if (rectangularMesh) {
            scene.remove(rectangularMesh);
        }

      const numHoles = parseInt(document.getElementById('numHoles').value);
const circleDiameter = parseInt(document.getElementById('circleDiameter').value);
const extrudedCircleDiameter = parseInt(document.getElementById('extrudedCircleDiameter').value);
const holeDiameter = parseInt(document.getElementById('holeDiameter').value);
const distance = parseInt(document.getElementById('distance').value);
const extrusionAmount = parseInt(document.getElementById('extrusionAmount').value);

// Create the vertices for the main shape
const vertices = createPolygonVertices(32, circleDiameter);
polygonMesh = createPolygonMesh(vertices);

// Create the vertices for the extruded shape
const extrudedVertices = createPolygonVertices(32, extrudedCircleDiameter);
extrudedCircularMesh = createPolygonMesh(extrudedVertices);
extrudedCircularMesh.position.z = extrusionAmount;

// Calculate hole properties
const holeRadius = holeDiameter / 2;

// Calculate the positions of the holes evenly around the center
const positions = [];
for (let i = 0; i < numHoles; i++) {
    const angle = (i * 2 * Math.PI) / numHoles; // Calculate angle for each hole
    positions.push({
        x: ((circleDiameter/2-distance) * Math.cos(angle)), // Position x
        y: ((circleDiameter/2-distance) * Math.sin(angle)), // Position y
        radius: holeRadius
    });
}

// Create and add holes to the scene
positions.forEach(pos => {
    const hole = createHole(pos.radius);
    const holeMesh = new THREE.Mesh(hole, new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide, transparent: true, opacity: 0 }));
    holeMesh.position.set(pos.x, pos.y, 0);
    holes.push(holeMesh);
    scene.add(holeMesh);
});

// Create and add the extrusion shape to the scene
circularMesh = createExtrusionShape(vertices, positions, extrusionAmount);
scene.add(circularMesh);


       
    }
function createRectangularMesh(length, width, holeDiameter, holeColumns, holeRows, distance, extrusionAmount) {
    const shape = new THREE.Shape();

    // Define the four corners of the rectangle
    const topLeft = { x: -length / 2, y: width / 2 };
    const topRight = { x: length / 2, y: width / 2 };
    const bottomLeft = { x: -length / 2, y: -width / 2 };
    const bottomRight = { x: length / 2, y: -width / 2 };

    // Create the rectangular shape
    shape.moveTo(bottomLeft.x, bottomLeft.y);
    shape.lineTo(bottomRight.x, bottomRight.y);
    shape.lineTo(topRight.x, topRight.y);
    shape.lineTo(topLeft.x, topLeft.y);
    shape.closePath();

    // Calculate hole positions based on the number of columns and rows
    const holeRadius = holeDiameter / 2;

    // Calculate spacing between holes
    const spacingX = (length - distance * 2) / (holeColumns -1);
    const spacingY = (width - distance * 2) / (holeRows - 1);

   // Loop through rows and columns to place holes
for (let row = 0; row < holeRows; row++) {
    for (let col = 0; col < holeColumns; col++) {
        // Calculate the x position for the holes
        const x = holeColumns === 1 ? 0 : -length / 2 + distance + (col * spacingX);
        
        // Calculate the y position for the holes
        const y = holeRows === 1 ? 0 : width / 2 - distance - (row * spacingY);

        shape.holes.push(new THREE.Path().absarc(x, y, holeRadius, 0, Math.PI * 2, false));
    }
}


    // Create the extruded geometry
    const geometry = new THREE.ExtrudeGeometry(shape, { depth: extrusionAmount, bevelEnabled: false });
    const material = getMaterial();

    return new THREE.Mesh(geometry, material);
}

function updateRectangularMesh() {
 if (circularMesh) {
            scene.remove(circularMesh);
        }

        if (rectangularMesh) {
            scene.remove(rectangularMesh);
        }
    if (extrusionMesh) {
            scene.remove(extrusionMesh);
        }
   if (cylinderMesh) {
            scene.remove(cylinderMesh);
        }
     if (polygonMesh) {
            scene.remove(polygonMesh);
        }
     if (extrudedPolygonMesh) {
            scene.remove(extrudedPolygonMesh);
        }

    const length = parseInt(document.getElementById('rectLength').value);
    const width = parseInt(document.getElementById('rectWidth').value);
    const holeDiameter = parseInt(document.getElementById('holeDiameter').value);
    const holeColumns = parseInt(document.getElementById('holeColumns').value);
    const holeRows = parseInt(document.getElementById('holeRows').value);
    const distance = parseInt(document.getElementById('distance').value);
    const extrusionAmount = parseInt(document.getElementById('extrusionAmount').value);

    rectangularMesh = createRectangularMesh(length, width, holeDiameter, holeColumns, holeRows, distance, extrusionAmount);
    scene.add(rectangularMesh);
}

    function getMaterial() {
        if (materialType === 'wireframe') {
            return new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });
        } else if (materialType === 'metallic') {
            return new THREE.MeshStandardMaterial({ color: 0x888888, metalness: 1.0, roughness: 0.4 });
        } else {
            return new THREE.MeshBasicMaterial({ color: 0x00ff00, transparent: true, opacity: 0.5 });
        }
    }

    function switchShape() {
        const shape = document.getElementById('shapeSelect').value;
        const polygonControls = document.getElementById('polygonControls');
        const rectangularControls = document.getElementById('rectangularControls');
  const circularControls = document.getElementById('circularControls');

        if (shape === 'rectangular') {
 circularControls.style.display = 'none';

            polygonControls.style.display = 'none';
            rectangularControls.style.display = 'block';
        }
else if (shape === 'circular') {
 rectangularControls.style.display = 'none';
            polygonControls.style.display = 'none';
            circularControls.style.display = 'block';
        } 
 else {
            polygonControls.style.display = 'block';
            rectangularControls.style.display = 'none';
 circularControls.style.display = 'none';

        }
    }

    function updateShape() {
        const shape = document.getElementById('shapeSelect').value;
        if (shape === 'rectangular') {
            updateRectangularMesh();
        }
 else if (shape === 'circular') {
            updateCircularMesh();
        }
 else {
            updatePolygonAndHoles();
        }
    }

    function updateMaterial() {
        materialType = document.getElementById('materialSelect').value;
        updatePolygonAndHoles();
    }

    function animate() {
        requestAnimationFrame(animate);
        controls.update();
        renderer.render(scene, camera);
    }

    window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    });

    init();
</script>

</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Extruded Shape with Cylinder Holes - Three.js</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
        #csgButton {
            position: absolute;
            top: 20px;
            left: 20px;
            z-index: 10;
            padding: 10px;
            background-color: #0077ff;
            color: white;
            border: none;
            cursor: pointer;
            font-size: 16px;
        }
        #container {
            width: 100vw;
            height: 100vh;
            position: relative;
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <button id="csgButton">Apply CSG</button>
    <script type="importmap">
        {
            "imports": {
                "three": "https://cdn.jsdelivr.net/npm/three@0.128/build/three.module.js",
                "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.128/examples/jsm/"
            }}
    </script>
    <!-- Three.js Libraries -->
    
    <script type = 'module'>
import *as THREE from "three"
import {OrbitControls} from "three/addons/controls/OrbitControls.js"
        import CSG from "https://cdn.jsdelivr.net/gh/manthrax/THREE-CSGMesh/three-csg.js"
        import {BufferGeometryUtils} from "three/addons/utils/BufferGeometryUtils.js"
        let scene, camera, renderer, controls, beamMesh, cylinders = [];

        init();
        animate();

        function init() {
            // Scene and camera setup
            scene = new THREE.Scene();
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, 50, 100);

            // Renderer setup
            renderer = new THREE.WebGLRenderer();
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.getElementById('container').appendChild(renderer.domElement);

            // OrbitControls
            controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;

            // Lighting
            const ambientLight = new THREE.AmbientLight(0x404040);
            scene.add(ambientLight);

            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
            directionalLight.position.set(10, 20, 20);
            scene.add(directionalLight);

            // Create the wide flange beam
            beamMesh = createWideFlange('W12x50', [0, 0, 0], 20);

          //  beamMesh.geometry = new THREE.BoxGeometry(10,10,30);
            scene.add(beamMesh);

            // Create a row of cylinders (bolt holes)
            const cylinderRadius = 1;
            const cylinderHeight = 22; // Longer than beam depth for full subtraction
            const numberOfCylinders = 5;
            const cylinderSpacing = 4;

            for (let i = 0; i < numberOfCylinders; i++) {
                const zPos = i * cylinderSpacing - (numberOfCylinders - 1) * cylinderSpacing / 2;
                const cylinderMesh = createCylinder(2, 0, zPos, cylinderRadius, cylinderHeight);


            //    cylinderMesh.geometry = new THREE.BoxGeometry(1,20,1)
                scene.add(cylinderMesh); // Add cylinders to the scene
                cylinders.push(cylinderMesh); // Store the cylinders for later CSG subtraction
            }


            // Add event listener for CSG subtraction
            document.getElementById('csgButton').addEventListener('click', applyCSGSubtraction);
        }

        // Function to apply CSG subtraction
        function applyCSGSubtraction() {
            let m = CSG;

            let g = beamMesh.geometry;

            let beamCSG = CSG.fromMesh(beamMesh,0);

            cylinders.forEach(cylinderMesh => {

                let cylinderCSG = CSG.fromMesh(cylinderMesh,1);
                beamCSG = beamCSG.subtract(cylinderCSG);
                cylinderMesh.visible = false;
            });

            const resultMesh = CSG.toMesh(beamCSG,beamMesh.matrix,[beamMesh.material,cylinders[0].material]);//toMesh(new THREE.MeshStandardMaterial({ color: 0x0077ff }));
            resultMesh.geometry.computeVertexNormals();
            resultMesh.updateMatrix();
            resultMesh.updateMatrixWorld();

            // Remove original beam and add the CSG result
            scene.remove(beamMesh);

            scene.add(resultMesh);
        }

        // Function to create and extrude the wide flange shape
        function createWideFlange(sizeLabel, position, extrusionLength) {
            const wideFlangeSizes = {
                'W12x50': [12, 8, 0.5, 1] // [beamDepth, flangeWidth, webThickness, flangeDepth]
            };

            const [beamDepth, flangeWidth, webThickness, flangeDepth] = wideFlangeSizes[sizeLabel];
            const shape = new THREE.Shape();
            const filletRadius = flangeDepth / 2;

            // Draw the shape
            shape.moveTo(-flangeWidth / 2, -beamDepth / 2);
            shape.lineTo(flangeWidth / 2, -beamDepth / 2);
            shape.lineTo(flangeWidth / 2, -beamDepth / 2 + flangeDepth - filletRadius);
            shape.quadraticCurveTo(flangeWidth / 2, -beamDepth / 2 + flangeDepth, flangeWidth / 2 - filletRadius, -beamDepth / 2 + flangeDepth);
            shape.lineTo(webThickness / 2 + filletRadius, -beamDepth / 2 + flangeDepth);
            shape.quadraticCurveTo(webThickness / 2, -beamDepth / 2 + flangeDepth, webThickness / 2, -beamDepth / 2 + flangeDepth + filletRadius);
            shape.lineTo(webThickness / 2, beamDepth / 2 - flangeDepth - filletRadius);
            shape.quadraticCurveTo(webThickness / 2, beamDepth / 2 - flangeDepth, webThickness / 2 + filletRadius, beamDepth / 2 - flangeDepth);
            shape.lineTo(flangeWidth / 2 - filletRadius, beamDepth / 2 - flangeDepth);
            shape.quadraticCurveTo(flangeWidth / 2, beamDepth / 2 - flangeDepth, flangeWidth / 2, beamDepth / 2);
            shape.lineTo(flangeWidth / 2, beamDepth / 2);
            shape.lineTo(-flangeWidth / 2, beamDepth / 2);
            shape.lineTo(-flangeWidth / 2, beamDepth / 2 - flangeDepth + filletRadius);
            shape.quadraticCurveTo(-flangeWidth / 2, beamDepth / 2 - flangeDepth, -flangeWidth / 2 + filletRadius, beamDepth / 2 - flangeDepth);
            shape.lineTo(-webThickness / 2 - filletRadius, beamDepth / 2 - flangeDepth);
            shape.quadraticCurveTo(-webThickness / 2, beamDepth / 2 - flangeDepth, -webThickness / 2, beamDepth / 2 - flangeDepth - filletRadius);
            shape.lineTo(-webThickness / 2, -beamDepth / 2 + flangeDepth + filletRadius);
            shape.quadraticCurveTo(-webThickness / 2, -beamDepth / 2 + flangeDepth, -webThickness / 2 - filletRadius, -beamDepth / 2 + flangeDepth);
            shape.lineTo(-flangeWidth / 2 + filletRadius, -beamDepth / 2 + flangeDepth);
            shape.quadraticCurveTo(-flangeWidth / 2, -beamDepth / 2 + flangeDepth, -flangeWidth / 2, -beamDepth / 2);

            const geometry = new THREE.ExtrudeGeometry(shape, {
                depth: extrusionLength,
                bevelEnabled: false,
            });

            const material = new THREE.MeshStandardMaterial({ color: 0x0077ff });
            const mesh = new THREE.Mesh(geometry, material);
            mesh.position.set(...position);
            mesh.updateMatrix();
            mesh.updateMatrixWorld();
            return mesh;
        }

       // Function to create a cylinder
        function createCylinder(x, y, z, radius, height) {
            const geometry = new THREE.CylinderGeometry(radius, radius, height, 32);
            const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
            const cylinder = new THREE.Mesh(geometry, material);
            cylinder.position.set(x, y , z+10);
            cylinder.updateMatrix();
            cylinder.updateMatrixWorld();
            return cylinder;
        }

        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
    </script>
</body>
</html>

https://manthrax.github.io/glider/csgtest.html

4 Likes