diff --git a/src/lib/store.ts b/src/lib/store.ts index 13448c0..da07990 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -19,6 +19,7 @@ export const codeError = writable(null) export const hoveredKey = writable(null) export const clickedKey = writable(null) +export const showGrid = writable(false) export const noWall = writable(false) export const noBase = writable(false) export const noBlanks = writable(false) diff --git a/src/lib/worker/api.ts b/src/lib/worker/api.ts index 238db90..5e0b34a 100644 --- a/src/lib/worker/api.ts +++ b/src/lib/worker/api.ts @@ -246,10 +246,11 @@ export async function generateBoardHolder(config: Cuttleform) { return result } -export async function generateWristRest(config: Cuttleform) { +export async function generateWristRest(config: Cuttleform, flip = false) { await ensureOC() - if (!config.wristRest) return NULL - const rest = wristRest(config, newGeometry(config)) as Solid + const name = flip ? 'wristRestLeft' : 'wristRestRight' + if (!config[name]) return NULL + const rest = wristRest(config, newGeometry(config), name) as Solid const result = meshWithVolume(rest) rest.delete() return result @@ -257,9 +258,9 @@ export async function generateWristRest(config: Cuttleform) { export async function generateMirroredWristRest(config: Cuttleform) { await ensureOC() - if (!config.wristRest) return NULL + if (!config.wristRestLeft) return NULL config.keys.forEach(k => k.position = new ETrsf(k.position.history).mirror([1, 0, 0])) - const rest = wristRest(config, newGeometry(config)).mirror('YZ', [0, 0, 0]) + const rest = wristRest(config, newGeometry(config), 'wristRestLeft').mirror('YZ', [0, 0, 0]) const result = meshWithVolume(rest) rest.delete() return result @@ -297,7 +298,7 @@ async function getModel(conf: Cuttleform, name: string, stitchWalls: boolean, fl } else if (name == 'holder') { return boardHolder(conf, geometry).translateZ(-geometry.floorZ) } else if (name == 'wristrest') { - return wristRest(conf, geometry).translateZ(-geometry.floorZ) + return wristRest(conf, geometry, flip ? 'wristRestLeft' : 'wristRestRight').translateZ(-geometry.floorZ) } else { throw new Error("I don't know what model you want") } @@ -306,9 +307,9 @@ async function getModel(conf: Cuttleform, name: string, stitchWalls: boolean, fl export async function getSTL(conf: Cuttleform, name: string, side: 'left' | 'right' | 'unibody') { const flip = side == 'left' let model = await getModel(conf, name, true, flip) - if (name == 'wristrest' && side == 'unibody' && conf.wristRest && model) { + if (name == 'wristrest' && side == 'unibody' && conf.wristRestRight && model) { conf.keys.forEach(k => k.position = new ETrsf(k.position.history).mirror([1, 0, 0])) - model = (model as Solid).fuse(wristRest(conf, newGeometry(conf)).mirror('YZ', [0, 0, 0])) + model = (model as Solid).fuse(wristRest(conf, newGeometry(conf), 'wristRestLeft').mirror('YZ', [0, 0, 0])) } if (!model) throw new Error(`Model ${name} is empty`) if (flip) model = model.mirror('YZ', [0, 0, 0]) @@ -324,8 +325,8 @@ export async function getSTEP(conf: Cuttleform, flip: boolean, stitchWalls: bool assembly.add('Microcontroller Holder', boardHolder(conf, geometry)) } - if (conf.wristRest && (await getUser()).sponsor) { - assembly.add('Wrist Rest', wristRest(conf, geometry)) + if (conf.wristRestRight && (await getUser()).sponsor) { + assembly.add('Wrist Rest', wristRest(conf, geometry, flip ? 'wristRestLeft' : 'wristRestRight')) } assembly = assembly.transform(new Trsf().translate(0, 0, -geometry.floorZ)) diff --git a/src/lib/worker/config.cosmos.ts b/src/lib/worker/config.cosmos.ts index 4c98191..44161ca 100644 --- a/src/lib/worker/config.cosmos.ts +++ b/src/lib/worker/config.cosmos.ts @@ -76,6 +76,20 @@ export type CosmosKey = { sizeA?: number sizeB?: number } + +interface CosmosWristRestProps { + angle: number + taper: number + tenting: number + slope: number + stilts?: boolean + + maxWidthLeft: number + maxWidthRight: number + extensionLeft: number + extensionRight: number +} + export type CosmosKeyboard = & { curvature: Required @@ -95,7 +109,7 @@ export type CosmosKeyboard = shell: AnyShell wristRestEnable: boolean unibody: boolean - wristRestProps: Exclude + wristRestProps: CosmosWristRestProps wristRestPosition: bigint connectorIndex: number } @@ -314,17 +328,30 @@ export function toCosmosConfig(conf: Cuttleform, side: 'left' | 'right' | 'unibo verticalClearance: conf.verticalClearance, rounded: conf.rounded, shell: conf.shell, - wristRestEnable: !!conf.wristRest, + wristRestEnable: !!conf.wristRestRight, connectorIndex: conf.connectorIndex, unibody: side == 'unibody', - wristRestProps: conf.wristRest || { - angle: 0, - taper: 10, - slope: 5, - maxWidth: 100, - tenting: 6, - extension: 8, - }, + wristRestProps: conf.wristRestRight + ? { + angle: conf.wristRestRight.angle, + taper: conf.wristRestRight.taper, + slope: conf.wristRestRight.slope, + maxWidthRight: conf.wristRestRight.maxWidth, + maxWidthLeft: conf.wristRestLeft?.maxWidth ?? 100, + tenting: conf.wristRestRight.tenting, + extensionRight: conf.wristRestRight.extension, + extensionLeft: conf.wristRestLeft?.extension ?? 6, + } + : { + angle: 0, + taper: 10, + slope: 5, + maxWidthLeft: 100, + maxWidthRight: 100, + tenting: 6, + extensionLeft: 8, + extensionRight: 8, + }, wristRestPosition: overrideWristRest ? KEYBOARD_DEFAULTS.wristRestPosition! : encodeTuple(wrOrigin.xyz().map(t => Math.round(t * 10))), clusters: side == 'unibody' ? [ @@ -408,7 +435,28 @@ export function sideFromCosmosConfig(c: CosmosKeyboard, side: 'left' | 'right' | microcontroller: c.microcontroller, microcontrollerAngle: c.microcontrollerAngle, fastenMicrocontroller: c.fastenMicrocontroller, - wristRest: c.wristRestEnable ? { ...c.wristRestProps } : undefined, + wristRestLeft: c.wristRestEnable + ? { + angle: c.wristRestProps.angle, + taper: c.wristRestProps.taper, + tenting: c.wristRestProps.tenting, + slope: c.wristRestProps.slope, + + maxWidth: c.wristRestProps.maxWidthLeft, + extension: c.wristRestProps.extensionLeft, + } + : undefined, + wristRestRight: c.wristRestEnable + ? { + angle: c.wristRestProps.angle, + taper: c.wristRestProps.taper, + tenting: c.wristRestProps.tenting, + slope: c.wristRestProps.slope, + + maxWidth: c.wristRestProps.maxWidthRight, + extension: c.wristRestProps.extensionRight, + } + : undefined, wristRestOrigin: new ETrsf().translate(wrPos[0] / 10, wrPos[1] / 10, wrPos[2] / 10), shell: c.shell, } diff --git a/src/lib/worker/config.serialize.ts b/src/lib/worker/config.serialize.ts index e3ca59a..d17bf61 100644 --- a/src/lib/worker/config.serialize.ts +++ b/src/lib/worker/config.serialize.ts @@ -153,10 +153,12 @@ const KEYBOARD_EXTRA_DEFAULTS: KeyboardExtra = { roundedTopVertical: 67, wristRestAngle: 0, wristRestTaper: 450, - wristRestMaxWidth: 1000, + wristRestLeftMaxWidth: 1000, + wristRestRightMaxWidth: 1000, wristRestTenting: 270, wristRestSlope: 225, - wristRestExtension: 80, + wristRestLeftExtension: 80, + wristRestRightExtension: 80, connectorIndex: -10, screwIndices: [], microcontrollerAngle: 0, @@ -408,10 +410,12 @@ export function decodeConfigIdk(b64: string): CosmosKeyboard { wristRestProps: { angle: keebExtra.wristRestAngle / 45, taper: keebExtra.wristRestTaper / 45, - maxWidth: keebExtra.wristRestMaxWidth / 10, + maxWidthLeft: keebExtra.wristRestLeftMaxWidth / 10, + maxWidthRight: keebExtra.wristRestRightMaxWidth / 10, tenting: keebExtra.wristRestTenting / 45, slope: keebExtra.wristRestSlope / 45, - extension: keebExtra.wristRestExtension / 10, + extensionLeft: keebExtra.wristRestLeftExtension / 10, + extensionRight: keebExtra.wristRestRightExtension / 10, }, wristRestPosition: keeb.wristRestPosition, connectorIndex: keebExtra.connectorIndex / 10, @@ -593,9 +597,11 @@ export function encodeCosmosConfig(conf: CosmosKeyboard): Keyboard { wristRestAngle: Math.round(conf.wristRestProps.angle * 45), wristRestTaper: Math.round(conf.wristRestProps.taper * 45), wristRestTenting: Math.round(conf.wristRestProps.tenting * 45), - wristRestMaxWidth: Math.round(conf.wristRestProps.maxWidth * 10), + wristRestLeftMaxWidth: Math.round(conf.wristRestProps.maxWidthLeft * 10), + wristRestRightMaxWidth: Math.round(conf.wristRestProps.maxWidthRight * 10), wristRestSlope: Math.round(conf.wristRestProps.slope * 45), - wristRestExtension: Math.round(conf.wristRestProps.extension * 10), + wristRestLeftExtension: Math.round(conf.wristRestProps.extensionLeft * 10), + wristRestRightExtension: Math.round(conf.wristRestProps.extensionRight * 10), connectorIndex: Math.round(conf.connectorIndex * 10), screwIndices: conf.screwIndices.some(c => c >= 0) ? conf.screwIndices.map(i => Math.round(i * 10) + 10) : [], roundedSideConcavity: conf.rounded.side ? Math.round(conf.rounded.side.concavity * 10) : undefined, diff --git a/src/lib/worker/config.ts b/src/lib/worker/config.ts index a616f81..b028afd 100644 --- a/src/lib/worker/config.ts +++ b/src/lib/worker/config.ts @@ -52,6 +52,22 @@ const X: Point = [1, 0, 0] const Y: Point = [0, 1, 0] const Z: Point = [0, 0, 1] +interface WristRest { + /** Angle at which the wrist rest is attached to the keyboard */ + angle: number + /** Angle at which the wrist rest sides are tapered inwards */ + taper: number + /** Maximum width of the wrist rest */ + maxWidth: number + /** Angle at which the wrist rest is tented */ + tenting: number + /** Angle at which the wrist rests slopes down towards the wrist */ + slope: number + /** Amount by which the wrist rests sticks out past the wrist */ + extension: number + stilts?: boolean +} + export interface SpecificCuttleform { wallThickness: number wallShrouding: number @@ -74,21 +90,8 @@ export interface SpecificCuttleform { screwType: 'screw insert' | 'tapered screw insert' | 'expanding screw insert' | 'tapped hole' screwSize: 'M3' | 'M4' | '#4-40' | '#6-32' screwCountersink: boolean - wristRest?: { - /** Angle at which the wrist rest is attached to the keyboard */ - angle: number - /** Angle at which the wrist rest sides are tapered inwards */ - taper: number - /** Maximum width of the wrist rest */ - maxWidth: number - /** Angle at which the wrist rest is tented */ - tenting: number - /** Angle at which the wrist rests slopes down towards the wrist */ - slope: number - /** Amount by which the wrist rests sticks out past the wrist */ - extension: number - stilts?: boolean - } + wristRestLeft?: WristRest + wristRestRight?: WristRest wristRestOrigin: ETrsf microcontroller: MicrocontrollerName /* Angle at which microcontroller should be placed */ @@ -312,7 +315,20 @@ export function cuttleConf(c: DeepRequired): Cuttleform { connector: MAP_CONNECTOR[c.wall.connector], connectorSizeUSB: MAP_CONNECTOR_SIZE[c.wall.connectorSizeUsb], connectorIndex: -1, - wristRest: c.wall.wristRest + wristRestRight: c.wall.wristRest + ? { + // length: c.wall.wristRestLength / 10, + maxWidth: c.wall.wristRestMaxWidth / 10, + // xOffset: c.wall.wristRestXOffset / 10, + // zOffset: c.wall.wristRestZOffset / 10, + angle: 0, + taper: c.wall.wristRestAngle / 45, + tenting: c.curvature.tenting / 45 / 2 + c.wall.wristRestTenting / 45, + slope: 5, + extension: 8, + } + : undefined, + wristRestLeft: c.wall.wristRest ? { // length: c.wall.wristRestLength / 10, maxWidth: c.wall.wristRestMaxWidth / 10, @@ -1502,12 +1518,12 @@ export function fullEstimatedCenter(geo: FullGeometry | undefined, withWristRest const defaultCenter = { left: [0, 0, 0] as Point, unibody: [0, 0, 0] as Point, right: [0, 0, 0] as Point } if (!geo) return { left: defaultCenter, both: defaultCenter, right: defaultCenter } if (geo.unibody) { - const center = estimatedCenter(geo.unibody, withWristRest && !!geo.unibody!.c.wristRest) + const center = estimatedCenter(geo.unibody, withWristRest && !!geo.unibody!.c.wristRestRight) const modelCenters = { unibody: center } return { left: modelCenters, both: modelCenters, right: modelCenters } } else { - const leftBB = estimatedBB(geo.left!, withWristRest && !!geo.left!.c.wristRest) - const rightBB = estimatedBB(geo.right!, withWristRest && !!geo.right!.c.wristRest) + const leftBB = estimatedBB(geo.left!, withWristRest && !!geo.left!.c.wristRestRight) + const rightBB = estimatedBB(geo.right!, withWristRest && !!geo.right!.c.wristRestRight) const sepDiff = (VIEW_SEPARATION - (rightBB[0] + leftBB[0])) / 2 return { left: { diff --git a/src/lib/worker/geometry.ts b/src/lib/worker/geometry.ts index 2f56aae..89093ed 100644 --- a/src/lib/worker/geometry.ts +++ b/src/lib/worker/geometry.ts @@ -1444,13 +1444,13 @@ function transformCriticalPoint(pt: WallCriticalPoints, trsf: Trsf): WallCritica } } -export function wristRestGeometry(c: Cuttleform, geo: Geometry) { - if (!c.wristRest) throw new Error('Wrist rest is not enabled') +export function wristRestGeometry(c: Cuttleform, geo: Geometry, wrSide: 'wristRestRight' | 'wristRestLeft') { + if (!c[wrSide]) throw new Error('Wrist rest is not enabled') // Form the wrist rest by considering all walls roated by the wrist rest angle // At the end of the function, everything is rotated back. const origin = new ETrsf(c.wristRestOrigin.history).evaluate({ flat: false }).xyz() - const walls = geo.allWallCriticalPoints().map(p => transformCriticalPoint(p, new Trsf().rotate(-c.wristRest!.angle, origin, [0, 0, 1]))) + const walls = geo.allWallCriticalPoints().map(p => transformCriticalPoint(p, new Trsf().rotate(-c[wrSide]!.angle, origin, [0, 0, 1]))) const xMin = new Set() const xMax = new Set() @@ -1467,8 +1467,8 @@ export function wristRestGeometry(c: Cuttleform, geo: Geometry) { }) const midFrontWall = frontWalls[Math.floor(frontWalls.length / 2)] - const left = Math.max(origin[0] - c.wristRest.maxWidth / 2, Math.min(...xMin)) - const right = Math.min(origin[0] + c.wristRest.maxWidth / 2, Math.max(...xMax)) + const left = Math.max(origin[0] - c[wrSide].maxWidth / 2, Math.min(...xMin)) + const right = Math.min(origin[0] + c[wrSide].maxWidth / 2, Math.max(...xMax)) if (left > right) throw new Error('Wrist rest is not wide enough') const leftWallY = wallXToY(walls, left, walls.indexOf(midFrontWall), 1, -1, 'to') @@ -1478,9 +1478,9 @@ export function wristRestGeometry(c: Cuttleform, geo: Geometry) { if (!leftWallY || !rightWallY || !leftWallY2 || !rightWallY2) throw new Error('Could not locate walls for wrist rest') - const sinAngle = Math.sin(c.wristRest.taper * Math.PI / 180) - const cosAngle = Math.cos(c.wristRest.taper * Math.PI / 180) - const tanAngle = Math.tan(c.wristRest.taper * Math.PI / 180) + const sinAngle = Math.sin(c[wrSide].taper * Math.PI / 180) + const cosAngle = Math.cos(c[wrSide].taper * Math.PI / 180) + const tanAngle = Math.tan(c[wrSide].taper * Math.PI / 180) const leftStart = new Vector(left, Math.max(leftWallY.y, leftWallY2.y), 0) const rightStart = new Vector(right, Math.max(rightWallY.y, rightWallY2.y), 0) @@ -1498,7 +1498,7 @@ export function wristRestGeometry(c: Cuttleform, geo: Geometry) { } const minY = Math.min(leftStart.y, rightStart.y) - const length = minY - origin[1] + c.wristRest.extension + const length = minY - origin[1] + c[wrSide].extension const leftLength = (length + leftStart.y - minY) / cosAngle const rightLength = (length + rightStart.y - minY) / cosAngle @@ -1511,7 +1511,7 @@ export function wristRestGeometry(c: Cuttleform, geo: Geometry) { rightEnd.x = middle + 10 // throw new Error('Wrist rest width is not big enough to support taper angle') } - const rotateBack = new Trsf().rotate(c.wristRest!.angle, origin, [0, 0, 1]) + const rotateBack = new Trsf().rotate(c[wrSide]!.angle, origin, [0, 0, 1]) return { leftStart: rotateBack.apply(leftStart), leftEnd: rotateBack.apply(leftEnd), diff --git a/src/proto/cosmos.proto b/src/proto/cosmos.proto index 1bd970b..0aea9cc 100644 --- a/src/proto/cosmos.proto +++ b/src/proto/cosmos.proto @@ -55,9 +55,11 @@ message KeyboardExtra { optional int32 rounded_top_horizontal = 7; optional int32 rounded_top_vertical = 8; - optional int32 wrist_rest_extension = 9; + optional int32 wrist_rest_left_extension = 9; + optional int32 wrist_rest_right_extension = 16; optional sint32 wrist_rest_taper = 10; - optional int32 wrist_rest_max_width= 11; + optional int32 wrist_rest_left_max_width = 11; + optional int32 wrist_rest_right_max_width = 17; optional sint32 wrist_rest_angle = 12; optional sint32 wrist_rest_slope = 13; optional sint32 wrist_rest_tenting = 14; diff --git a/src/routes/beta/App.svelte b/src/routes/beta/App.svelte index b19870c..7fb135b 100644 --- a/src/routes/beta/App.svelte +++ b/src/routes/beta/App.svelte @@ -52,6 +52,7 @@ view, noBlanks, noLabels, + showGrid, } from '$lib/store' import { onDestroy } from 'svelte' import { browser } from '$app/environment' @@ -255,7 +256,8 @@ cutPromise: pool.execute((w) => w.cutWall(conf), 'Cut wall'), holderPromise: pool.execute((w) => w.generateBoardHolder(conf), 'Holder'), screwPromise: pool.execute((w) => w.generateScrewInserts(conf), 'Inserts'), - wristRestPromise: hasPro && pool.execute((w) => w.generateWristRest(conf), 'Wrist Rest'), + wristRestPromise: + hasPro && pool.execute((w) => w.generateWristRest(conf, side == 'left'), 'Wrist Rest'), secondWristRestPromise: hasPro && side == 'unibody' && @@ -285,8 +287,7 @@ oldTempConfig = cloneConfig(conf) if ( - differences.length == 1 && - (differences[0] == 'wristRest' || differences[0] == 'wristRestOrigin') + differences.every((d) => d == 'wristRestLeft' || d == 'wristRestRight' || d == 'wristRestOrigin') ) { const renderNumber = ++lastRenderNumber console.log('PROCESSING WRIST REST', renderNumber) @@ -307,7 +308,9 @@ } else { pool.reset(kbdNames.length) const wristMeshes = await Promise.all( - kbdNames.map((k) => pool.execute((w) => w.generateWristRest(conf[k]!), 'Wrist Rest')) + kbdNames.map((k) => + pool.execute((w) => w.generateWristRest(conf[k]!, k == 'left'), 'Wrist Rest') + ) ) if (renderNumber >= lastRenderNumber) { wristMeshes.forEach((wristMesh, i) => { @@ -646,6 +649,9 @@ class:selected={$view == 'right'}> + diff --git a/src/routes/beta/lib/dialogs/DownloadDialog.svelte b/src/routes/beta/lib/dialogs/DownloadDialog.svelte index f091632..17aee99 100644 --- a/src/routes/beta/lib/dialogs/DownloadDialog.svelte +++ b/src/routes/beta/lib/dialogs/DownloadDialog.svelte @@ -298,7 +298,7 @@

Wrist Rest

{#if $user.success && $user.sponsor} - {#if Object.values(config).some((c) => c.wristRest)} + {#if Object.values(config).some((c) => c.wristRestRight)}
{#each configKeys as kbd}