imrishabh18/corne-keyboard

The code defines and renders two surface-mount chip components with SMT pads, silkscreen outlines, and 3D CAD models (OBJ and STEP) for PCB assembly.

Version
2.0.21
License
unset
Stars
1

scripts/copper-pour-obstacles.test.ts

import { beforeAll, expect, test } from 'bun:test'
import { pathToFileURL } from 'node:url'
import type { AnyCircuitElement, BRepShape, PcbPlatedHole } from 'circuit-json'
import type { InputPad, InputProblem } from '@tscircuit/copper-pour-solver'

const solver = process.env.COPPER_POUR_TEST_BUNDLE
  ? await import(pathToFileURL(process.env.COPPER_POUR_TEST_BUNDLE).href)
  : await import('@tscircuit/copper-pour-solver')
beforeAll(async () => { await solver.initializeManifoldGeometry() })

const square = [{ x: -1, y: -1 }, { x: 1, y: -1 }, { x: 1, y: 1 }, { x: -1, y: 1 }]
const polygon: InputPad = {
  shape: 'polygon', padId: 'polygon', points: square,
  layer: 'top', connectivityKey: 'signal', isSmtPad: true,
}
const rect: InputPad = {
  shape: 'rect', padId: 'rect',
  bounds: { minX: -1, maxX: 1, minY: -1, maxY: 1 },
  layer: 'top', connectivityKey: 'signal', isSmtPad: true,
}
const inputFor = (pad: InputPad, overrides = {}): InputProblem => ({
  pads: [pad],
  regionsForPour: [{
    shape: 'rect', layer: 'top', connectivityKey: 'ground',
    bounds: { minX: -5, maxX: 5, minY: -5, maxY: 5 },
    padMargin: 0.2, traceMargin: 0.1, ...overrides,
  }],
})
function solve(input: InputProblem): BRepShape[] {
  return new solver.CopperPourPipelineSolver(input).getOutput().brep_shapes
}
function holeBounds(shapes: BRepShape[]) {
  expect(shapes).toHaveLength(1)
  expect(shapes[0]!.inner_rings).toHaveLength(1)
  const vertices = shapes[0]!.inner_rings[0]!.vertices
  return {
    minX: Math.min(...vertices.map(p => p.x)), maxX: Math.max(...vertices.map(p => p.x)),
    minY: Math.min(...vertices.map(p => p.y)), maxY: Math.max(...vertices.map(p => p.y)),
  }
}
function expectBounds(shapes: BRepShape[], x: number, y = x) {
  const bounds = holeBounds(shapes)
  expect(bounds.minX).toBeCloseTo(-x, 5)
  expect(bounds.maxX).toBeCloseTo(x, 5)
  expect(bounds.minY).toBeCloseTo(-y, 5)
  expect(bounds.maxY).toBeCloseTo(y, 5)
}

test('different-net square polygon gets the same 0.2 mm clearance as a rectangular pad', () => {
  expectBounds(solve(inputFor(rect)), 1.2)
  expectBounds(solve(inputFor(polygon)), 1.2)
})
test('polygon clearance follows the configured pad margin', () => {
  expectBounds(solve(inputFor(polygon, { padMargin: 0.4 })), 1.4)
  expectBounds(solve(inputFor(polygon, { padMargin: 0 })), 1)
})
test('same-net polygon remains joined to a solid pour', () => {
  const shapes = solve(inputFor({ ...polygon, connectivityKey: 'ground' }))
  expect(shapes).toHaveLength(1)
  expect(shapes[0]!.inner_rings).toHaveLength(0)
})
test('polygon keepout uses its exact outline, and cutout uses cutout_margin', () => {
  expectBounds(solve(inputFor({ ...polygon, connectivityKey: 'keepout:1' })), 1)
  expectBounds(solve(inputFor({ ...polygon, connectivityKey: 'cutout:1' }, { cutout_margin: 0.35 })), 1.35)
})

const circuitWith = (hole: PcbPlatedHole): AnyCircuitElement[] => [{
  type: 'pcb_board', pcb_board_id: 'pcb_board_0', center: { x: 0, y: 0 },
  width: 10, height: 10, num_layers: 2, thickness: 1.6, material: 'fr4',
}, hole]
function fromCircuit(hole: PcbPlatedHole) {
  return solver.convertCircuitJsonToInputProblem(circuitWith(hole), {
    layer: 'top', subcircuit_connectivity_map_key: 'ground',
    pad_margin: 0.2, trace_margin: 0.1,
  }) as InputProblem
}
const baseHole = {
  type: 'pcb_plated_hole' as const, pcb_plated_hole_id: 'pcb_plated_hole_0',
  x: 0, y: 0, layers: ['top', 'bottom'] as ['top', 'bottom'],
  pad_shape: 'rect' as const, rect_pad_width: 4, rect_pad_height: 2,
  hole_offset_x: 0, hole_offset_y: 0,
}
test('unassigned circular-hole rectangular pad remains a clearance obstacle', () => {
  const problem = fromCircuit({ ...baseHole,
    shape: 'circular_hole_with_rect_pad', hole_shape: 'circle', hole_diameter: 0.8,
  })
  expect(problem.pads).toHaveLength(1)
  expectBounds(solve(problem), 2.2, 1.2)
})
test('unassigned pill-hole rectangular pad is included as a clearance obstacle', () => {
  const problem = fromCircuit({ ...baseHole,
    shape: 'pill_hole_with_rect_pad', hole_shape: 'pill', hole_width: 1.2, hole_height: 0.8,
  })
  expect(problem.pads).toHaveLength(1)
  expect(problem.pads[0]!.connectivityKey).toBe('unconnected-plated-hole:pcb_plated_hole_0')
  expectBounds(solve(problem), 2.2, 1.2)
})
for (const [padRotation, holeRotation] of [[0, 90], [90, 0], [270, 270]]) {
  test(`rotated pill-hole pad uses pad rotation ${padRotation}, independently of drill rotation ${holeRotation}`, () => {
    const problem = fromCircuit({ ...baseHole,
      shape: 'rotated_pill_hole_with_rect_pad', hole_shape: 'rotated_pill',
      hole_width: 1.2, hole_height: 0.8,
      rect_ccw_rotation: padRotation!, hole_ccw_rotation: holeRotation!,
    })
    expect(problem.pads).toHaveLength(1)
    if (padRotation === 0) expectBounds(solve(problem), 2.2, 1.2)
    else expectBounds(solve(problem), 1.2, 2.2)
  })
}