All files / web/src/components/create/abacus KitPlatePreview.stories.tsx

89.61% Statements 138/154
75% Branches 6/8
66.66% Functions 2/3
89.61% Lines 138/154

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 1551x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 1x 1x 1x 1x 1x 1x     6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                             1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
// Abacus Studio — KitPlatePreview stories (Gitea #32).
//
// Every state of the bed picture, staged without a printer, a WASM export or a
// mesh. The GEOMETRY IS REAL: `packKitPlate` needs only footprint numbers and a
// bed, not triangles, so each story runs the actual packer over the actual
// module dimensions (mid 15.5 mm, end 26 mm, 100.5 mm deep at scale 1) and draws
// what it answers. Hand-typed placements would drift from the packer the first
// time a heuristic changed and nobody would notice — the picture would simply
// stop being a picture of anything.
//
// The refusals are staged the same way: the packer is asked for something
// impossible and its own KitPlateFitError copy is what renders, so the words in
// Storybook are the words a user gets.
 
import type { Meta, StoryObj } from '@storybook/react'
import { BAMBU_256_BED } from './abacus-3mf-assembly'
import {
  KitPlateFitError,
  type KitPlateLayout,
  kitPlateInstances,
  type ModuleBasis,
  packKitPlate,
} from './abacus-kit-plate'
import { defaultParams, type FilamentMap, type Params } from './abacus-model'
import type { ModuleKind } from './abacus-module-kit'
import { KitPlatePreview } from './KitPlatePreview'
 
const params = (over: Partial<Params> = {}): Params => ({
  ...defaultParams,
  seam_mode: 'modular',
  feet_mode: 'adhesive',
  ...over,
})
 
const fm: FilamentMap = {
  slots: ['#c9a26e', '#f5f5f5', '#111111', '#2e86ab'],
  frame: 0,
  markerWhite: 1,
  markerBlack: 2,
  beadRoles: [3, 1],
  markerContrast: 21,
}
 
/** Measured module footprints at scale 1 — what `moduleBasis` reports off the
 *  real renders, restated here so a story needs no geometry pipeline. */
const bases: Record<ModuleKind, ModuleBasis> = {
  left: { kind: 'left', minX: 0, minY: 0, wMm: 26, hMm: 100.5 },
  mid: { kind: 'mid', minX: 0, minY: 0, wMm: 15.5, hMm: 100.5 },
  right: { kind: 'right', minX: 0, minY: 0, wMm: 26, hMm: 100.5 },
}
 
function pack(args: {
  cols?: number
  bed?: typeof BAMBU_256_BED
  supportsAtSlice?: boolean
  filaments?: number
}): { layout: KitPlateLayout | null; refusal: KitPlatePreviewRefusal | null } {
  const p = params({ cols: args.cols ?? 13 })
  try {
    return {
      layout: packKitPlate({
        instances: kitPlateInstances(p, fm),
        bases,
        bed: args.bed ?? BAMBU_256_BED,
        supportsAtSlice: args.supportsAtSlice ?? false,
        filaments: args.filaments ?? 3,
      }),
      refusal: null,
    }
  } catch (err) {
    if (err instanceof KitPlateFitError) {
      return {
        layout: null,
        refusal: { headline: err.headline, remediation: err.remediation, modules: err.modules },
      }
    }
    throw err
  }
}
 
type KitPlatePreviewRefusal = {
  headline: string
  remediation: string
  modules: readonly string[]
}
 
const meta: Meta<typeof KitPlatePreview> = {
  title: 'AbacusStudio/KitPlatePreview',
  component: KitPlatePreview,
  tags: ['autodocs'],
  decorators: [
    (Story) => (
      // the studio's dark print panel; the preview's palette assumes it
      <div
        style={{
          width: 340,
          padding: 12,
          borderRadius: 12,
          background: 'rgba(17,24,39,0.95)',
          color: 'rgba(226,232,240,0.95)',
          fontFamily: 'ui-sans-serif, system-ui, sans-serif',
          fontSize: 12,
        }}
      >
        <Story />
      </div>
    ),
  ],
}
export default meta
 
type Story = StoryObj<typeof KitPlatePreview>
 
/** The shipped case: 13 columns, three filaments, one 256 bed. */
export const FullKit: Story = {
  args: { ...pack({ filaments: 3 }), filaments: 3 },
}
 
/** Supports on (printed feet, or the operator's style) widens every gap AND the
 *  tower's clearance ring — the same kit, visibly looser. */
export const SupportsOn: Story = {
  args: { ...pack({ supportsAtSlice: true, filaments: 3 }), filaments: 3 },
}
 
/** A fourth spool (the routed support interface) buys the tower a deeper
 *  envelope row, and on a full bed that is the difference between fitting and
 *  not. Compare the reserve against FullKit. */
export const FourFilaments: Story = {
  args: { ...pack({ filaments: 4 }), filaments: 4 },
}
 
/** Few enough modules that the bed is mostly tower and empty space. */
export const SmallKit: Story = {
  args: { ...pack({ cols: 4, filaments: 3 }), filaments: 3 },
}
 
/** A printer with no declared keep-out — the hatched corner is a property of the
 *  machine, not of the drawing. */
export const NoKeepOut: Story = {
  args: {
    ...pack({ bed: { wMm: 256, dMm: 256 }, filaments: 3 }),
    filaments: 3,
  },
}
 
/** The honest refusal, in the packer's own words. */
export const WontFit: Story = {
  args: pack({ cols: 13, bed: { wMm: 180, dMm: 180 }, filaments: 3 }),
}
 
/** Waiting on the module export. */
export const Packing: Story = {
  args: { layout: null, pending: true },
}