Skip to content

Commit

Permalink
Add Limbo to showcase, minor bugfixes, and add to program docs
Browse files Browse the repository at this point in the history
  • Loading branch information
rianadon committed Oct 24, 2024
1 parent ad50dfa commit 5291149
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 27 deletions.
24 changes: 14 additions & 10 deletions docs/docs/qmk-rp2040.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,25 +139,29 @@ Navigate to the qmk home folder that `qmk setup` generated. We'll be creating a
}
```

QMK is a little confusing in that a lot of settings are configurable _both_ in `info.json` and in the C source code. The configuration you copied might be missing some of these options or might even have extra options. Either way, make it look like this.
!!! info "Autogenerating info.json"

1. Unless you plan to sell your keyboard, the `vid` and `pid` settings are arbitrary. Just try to make sure they are unique for your keyboard. I've never seen `device_version` matter in practice: it's simply a way for you to specify the version of your firmware through usb.
QMK has a neat little [converter site](https://qmk.fm/converter/) that will create an `info.json` based off of the KLE data (remember, you can export KLE from Cosmos). It won't have everything we need, but it's a great time-saver.

2. Make sure to set `diode_direction` to whichever way the current flows in your diode wiring. Remember that the bar printed on the diode is the cathode, so it's where the "current flows to". An easy way to remember this is remembering the symbol for a diode, in which the arrow is pointing to the cathode bar. If this picture doesn't make sense, just try both! Only one of these directions will work.
QMK is a little confusing in that a lot of settings are configurable _both_ in `info.json` and in the C source code. The configuration you copied might be missing some of these options or might even have extra options. Either way, make it look like this.

![The diode directions, illustrated](../assets/wiring-row2col.png){ width=400 .center }
1. Unless you plan to sell your keyboard, the `vid` and `pid` settings are arbitrary. Just try to make sure they are unique for your keyboard. I've never seen `device_version` matter in practice: it's simply a way for you to specify the version of your firmware through usb.

3. I recommend keeping the `split` settings as I wrote them here. I use `GP1` as the serial data line (so don't connect it to any switches!) The `[0,0]` in the `matrix` option refers to the top-left key in the left side of your keyboard. Holding it down when plugging in the keyboard will make it enter bootloader mode.
2. Make sure to set `diode_direction` to whichever way the current flows in your diode wiring. Remember that the bar printed on the diode is the cathode, so it's where the "current flows to". An easy way to remember this is remembering the symbol for a diode, in which the arrow is pointing to the cathode bar. If this picture doesn't make sense, just try both! Only one of these directions will work.

4. Underneath `layouts` is where the magic happens. Here you will list all the keys, left to right across both halves then top to bottom. The `x` and `y` values are only used by `qmk info -l` and QMK configurator, neither of which you will use in this guide. However, you should still set them! You can start them at `10` on the right side to create a gap between both sides.
![The diode directions, illustrated](../assets/wiring-row2col.png){ width=400 .center }

First start with the left side and list starting from `[0,0]` to `[0,C-1]`, where `C` is the number of columns in one half. Then, list from `[R,C-1]` to `[R,0]`, where `R` is the number of rows in one half. Then list the next row. Add keys with matrix positions `[1,0]` to `[1,C-1]`, then `[R+1,C-1]` to `[R+1,0]`.
3. I recommend keeping the `split` settings as I wrote them here. I use `GP1` as the serial data line (so don't connect it to any switches!) The `[0,0]` in the `matrix` option refers to the top-left key in the left side of your keyboard. Holding it down when plugging in the keyboard will make it enter bootloader mode.

Things are this way because to QMK, the left and right sides are combined into the same matrix where they share the same rows but the right side has its own rows placed under the left side. Wouldn't it make more sense to share rows but have different columns? Beats me.
4. Underneath `layouts` is where the magic happens. Here you will list all the keys, left to right across both halves then top to bottom. The `x` and `y` values are only used by `qmk info -l` and QMK configurator, neither of which you will use in this guide. However, you should still set them! You can start them at `10` on the right side to create a gap between both sides.

I reverse the columns when listing the right-side keys because I assume you've wired the keyboards as mirror inverses of each other. If the left outermost key is wired to GP2, the right outermost key should also be wired to GP2. Say GP2 is column 0. You should now see why when listing a row we start with `[0,0]` and end with `[R,0]` (both column 0).
First start with the left side and list starting from `[0,0]` to `[0,C-1]`, where `C` is the number of columns in one half. Then, list from `[R,C-1]` to `[R,0]`, where `R` is the number of rows in one half. Then list the next row. Add keys with matrix positions `[1,0]` to `[1,C-1]`, then `[R+1,C-1]` to `[R+1,0]`.

By the end of this, you should have exactly as many items in your layout configuration as you have keys in your keyboard, including encoders. Remember, the encoder switch pins are wired like the keys.
Things are this way because to QMK, the left and right sides are combined into the same matrix where they share the same rows but the right side has its own rows placed under the left side. Wouldn't it make more sense to share rows but have different columns? Beats me.

I reverse the columns when listing the right-side keys because I assume you've wired the keyboards as mirror inverses of each other. If the left outermost key is wired to GP2, the right outermost key should also be wired to GP2. Say GP2 is column 0. You should now see why when listing a row we start with `[0,0]` and end with `[R,0]` (both column 0).

By the end of this, you should have exactly as many items in your layout configuration as you have keys in your keyboard, including encoders. Remember, the encoder switch pins are wired like the keys.

5. Edit `config.h`, creating it if there is none. Edit it to look like this:

Expand Down
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"pako": "^2.1.0",
"qrious": "^4.0.2",
"replicad": "^0.17.0",
"siema": "^1.5.1",
"simplicial-complex-boundary": "^1.0.1",
"svd-js": "^1.1.1",
"svelte-easy-popover": "^1.0.0",
Expand All @@ -41,6 +42,7 @@
"@types/bun": "^1.1.1",
"@types/concaveman": "^1.1.3",
"@types/node": "^20.10.6",
"@types/siema": "^1.4.11",
"@types/three": "^0.164.1",
"@unocss/extractor-svelte": "^0.61.3",
"@unocss/reset": "^0.61.3",
Expand Down
2 changes: 1 addition & 1 deletion src/lib/geometry/socketsParts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const PART_INFO: Record<CuttleKey['type'], PartInfo> = {
stepFile: '/src/assets/key-mx-pcb-twist.step',
partOverride: MX_PART,
socketSize: [18.7, 18.7, 8],
partBottom: [MX_BOTTOM, box(19.4, 19.4, 6.6)],
partBottom: [box(18.7, 18.7, 9.5)],
keycap: true,
extraBomItems: { 'pcb': { item: 'Plum Twist PCBs, 1.6mm Thick', icon: 'pcb', count: 1 } },
},
Expand Down
14 changes: 11 additions & 3 deletions src/routes/beta/lib/editor/toCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,18 @@ function allClustersFlipped(conf: CosmosKeyboard) {
}

export function toCode(proto: CosmosKeyboard) {
// I need two different versions of the config. The flipped one has correct geometry info.
// The non-flipped has appropriate transforms for expert mode.
const cosmosConf = fromCosmosConfig(proto, false)
const geo = newFullGeometry(cosmosConf)
const geo = newFullGeometry(fromCosmosConfig(proto))

const baseConfig: Partial<Cuttleform> = { ...cosmosConf.right! ?? cosmosConf.unibody }
delete baseConfig.keys
baseConfig.connectorIndex = -1

const fingerDefinitions: string[] = []
const fingerReferences: string[] = []
const screwStuff: string[] = []
for (const [name, kbd] of objEntries(cosmosConf)) {
fingerReferences.push(
` ${name}: {`,
Expand All @@ -75,6 +78,10 @@ export function toCode(proto: CosmosKeyboard) {
'const ' + side + capitalize(name) + ': Key[] = ' + jsonToCode(keys) + '\n',
)
}
screwStuff.push(
`// [${name}] screwIndices: ${jsonToCode(geo[name]!.justScrewIndices)}`,
`// [${name}] connectorIndex: ${kbd!.microcontroller ? geo[name]!.autoConnectorIndex : -1}`,
)
}

// const keysPlane = upperKeysPlane(proto)
Expand All @@ -94,11 +101,12 @@ export function toCode(proto: CosmosKeyboard) {
'// NOTE: Screws / the connector with',
'// negative indices are placed automatically.',
'// In the basic/advanced tab, these values were:',
...screwStuff,
// `// screwIndices: ${jsonToCode(screwIdx)}`,
// `// connectorIndex: ${connectorIdx}`,
'',
// '',
// `const curvature = ${stringifyObj(curvature(false, proto).merged, 0)}`,
'',
// '',
// '/**',
// ' * Useful for setting a different curvature',
// ' * for the pinky keys.',
Expand Down
4 changes: 3 additions & 1 deletion src/routes/beta/lib/viewers/Viewer3D.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -1476,7 +1476,9 @@
/>
{#if flags.intersection}
{#each componentBoxes(geo.c, geo) as box}
<T.Mesh geometry={componentGeometry(box)} material={new KeyMaterial(1, 1, 'red')} />
<T.Mesh geometry={componentGeometry(box)}>
<KeyboardMaterial status="error" kind="key" />
</T.Mesh>
{/each}
{/if}
{#if $showKeyInts}
Expand Down
33 changes: 25 additions & 8 deletions src/routes/showcase/[slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
mdiWrenchOutline,
} from '@mdi/js'
import type { PageData } from './$types'
import Carousel from './Carousel.svelte'
export let data: PageData
$: kbd = data.keyboard
Expand All @@ -27,13 +28,29 @@
<Header />

<div class="mx-auto container max-w-[calc(190vh-22rem)]">
<div class="w-full aspect-[1.9/1]">
<img
src={kbd.largeImage}
alt="Image of {kbd.author}'s keyboard"
class="w-full h-full object-cover"
/>
</div>
{#if kbd.images.length == 1}
<div class="w-full aspect-[1.9/1]">
<img
src={kbd.largeImage}
alt="Image of {kbd.author}'s keyboard"
class="w-full h-full object-cover"
/>
</div>
{:else}
<div class="w-full aspect-[1.9/1] overflow-hidden">
<Carousel>
{#each kbd.images as image}
<div class="w-full aspect-[1.9/1]">
<img
src={image.largeImage}
alt="Image of {kbd.author}'s keyboard"
class="w-full h-full object-cover"
/>
</div>
{/each}
</Carousel>
</div>
{/if}
<div class="sm:flex mt-2 mx-2 gap-4 md:gap-12 justify-center mx-8">
<div class="w-full mb-2 mt-3 max-w-prose">
<h1 class="uppercase text-2xl sm:text-3xl text-[#68e4a9] font-medium">
Expand All @@ -50,7 +67,7 @@
</div>
{/if}
{#if kbd.details}
<p class="mt-2 mb-4 showcasedetails">{@html kbd.details}</p>
<div class="mt-2 mb-4 showcasedetails">{@html kbd.details}</div>
{/if}
<div
class="opacity-80 grid grid-cols-[4.8rem_1fr] line-height-tight gap-row-1 mt-4 mb-4 showcasedetails"
Expand Down
104 changes: 104 additions & 0 deletions src/routes/showcase/[slug]/Carousel.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<script lang="ts">
import Icon from '$lib/presentation/Icon.svelte'
import { mdiChevronLeftCircle, mdiChevronRightCircle } from '@mdi/js'
import Siema from 'siema'
import { onMount } from 'svelte'
export let perPage = 1
export let loop = true
export let duration = 200
export let easing = 'ease-out'
export let startIndex = 0
export let draggable = true
export let multipleDrag = true
export let dots = true
export let controls = true
export let threshold = 20
export let rtl = false
let currentIndex = startIndex
let siema: HTMLElement | undefined
let controller: Siema
$: pips = controller ? (controller as any).innerElements : []
$: currentPerPage = controller ? controller.perPage : perPage
$: totalDots = controller ? Math.ceil((controller as any).innerElements.length / currentPerPage) : []
onMount(() => {
controller = new Siema({
selector: siema,
perPage: typeof perPage === 'object' ? perPage : Number(perPage),
loop,
duration,
easing,
startIndex,
draggable,
multipleDrag,
threshold,
rtl,
onChange: () => (currentIndex = controller.currentSlide),
})
return () => {
controller.destroy()
}
})
export function isDotActive(currentIndex: number, dotIndex: number) {
if (currentIndex < 0) currentIndex = pips.length + currentIndex
return (
currentIndex >= dotIndex * currentPerPage &&
currentIndex < dotIndex * currentPerPage + currentPerPage
)
}
</script>

<div class="color-black relative w-full justify-center items-center">
<div class="slides" bind:this={siema}>
<slot />
</div>
{#if controls}
<button class="control left-[2vw]" on:click={() => controller.prev()} aria-label="left">
<Icon path={mdiChevronLeftCircle} size={36} />
</button>
<button class="control right-[2vw]" on:click={() => controller.next()} aria-label="right">
<Icon path={mdiChevronRightCircle} size={36} />
</button>
{/if}
{#if dots}
<div class="dots">
{#each new Array(totalDots) as _, i}
<button
on:click={() => controller.goTo(i * currentPerPage)}
class="p-1 m-0.5 rounded-full"
class:active={isDotActive(currentIndex, i)}
>
<div class="dot" />
</button>
{/each}
</div>
{/if}
</div>

<style>
.control {
--at-apply: 'absolute opacity-70 rounded-full mt-[-18px] top-[50%] z-1 transition-opacity';
}
.control:hover {
opacity: 1;
}
.dots {
--at-apply: 'absolute flex justify-center w-full mt--30px';
}
.dot {
--at-apply: 'w-3 h-3 bg-black rounded-full transition-opacity opacity-50';
}
.dots > button:hover > .dot {
opacity: 0.85;
}
.dots > button.active > .dot {
opacity: 1;
}
</style>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 37 additions & 4 deletions src/routes/showcase/showcase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,50 @@ const _keyboards: Record<string, Keyboard> = {
switches: 'Glorious Lynx',
keycaps: 'Custom via KeyV2. <a href="../../jamgam%20keycap.stl">[STL]</a> or <a href="../../jamgam%20keycap%20with%20base%20for%20printing.stl">[STL with Base for Printing]</a>',
},
'd85e682f': {
author: 'rianadon',
name: 'Limbo (Low Profile)',
type: 'split',
modifiedInCAD: true,
details:
"<p>I wanted to try to make as thin of Dactyl-style keyboard as possible while preserving the standard curvature and functionality. The result is the Limbo. It's portable enough to throw in a backpack, but it still packs enough keys to get your work done.</p><p>If you're going to try this yourself, beware of the thumb cluster. The vertical cluster requires you to press with the pad of your thumb, which places more strain on your fingers than pressing with the side. I recommend doing a test print of just the thumb cluster before going all-in.</p><p><i>Note: The top case was printed as-is from Cosmos, but I added the logo and stars to the bottom plate in CAD.</i></p>",
config:
'#cm:CrYBCg0SBRCQQSATEgASADgxCh8SEQgIEIBLKKoBMB5AgIBMULUCEgIgExIAEgA4HUAACiISBRCQWSATEgASAxCwLxIMCAgQsF8gJCigATB4OAlAgJg9CiISBRCQZSATEgASAxCwOxIMCAgQsGsgJCigATBkOApAgKoEChMSBRCQcSATEgASADgeQICmi/ACChgSBBAQIBMSBBCggAoSAhAwODJAgM6L8AEYAEDohaCu8FVI2vCisAEKRQosEhQQwIACIABA/pyGsARIwpmglZC8ARISEEAgAECdhaCNwAdIrJ20vKA9OAAYAiIEGAAgAECFjdydwAdIl9HUvILTCxADGIYgIgYIuQEQuQE4A4IBAgQCSAVQAlhHYAFyBCAKMBR4yIOUlAE=',
filament: 'OVV3D PLA Tri-Color Red-Yellow-Blue',
switches: 'Choc Brown',
keycaps: 'YMDK Choc Keycaps',
},
}

function findImages(kbd: Keyboard) {
const authorSplit = kbd.author.split(' ')[0].toLowerCase().replace(/\W/g, '')
const kbdName = kbd.name?.replace(/\(.*\)/g, '').toLocaleLowerCase().replace(/\W/g, '')
const matcher = new RegExp(`/src/routes/showcase/assets/kbd(-${kbdName})?-${authorSplit}(-\\d+)?.jpg`)
const matchedPaths = Object.keys(images).filter(p => matcher.test(p)).sort((a, b) => {
const nA = Number((a.match(/-(\d+).jpg/) || [])[1] || 0)
const nB = Number((b.match(/-(\d+).jpg/) || [])[1] || 0)
return nA - nB
})
return matchedPaths.map(fullPath => {
const basename = fullPath.replace('/src/routes/showcase/assets/', '').replace('.jpg', '')
const fallback = images[fullPath].default
return {
image: images[`/target/media/${basename}.thumb.jpg`]?.default || fallback,
largeImage: images[`/target/media/${basename}.jpg`]?.default || fallback,
}
})
}

export const keyboards = Object.entries(_keyboards).map(([key, kbd]) => {
const authorSplit = kbd.author.split(' ')[0].toLowerCase()
const fallback = images[`/src/routes/showcase/assets/kbd-${authorSplit}.jpg`]?.default
const authorSplit = kbd.author.split(' ')[0].toLowerCase().replace(/\W/g, '')
const foundImages = findImages(kbd)
return {
...kbd,
key,
name: kbd.name || `${kbd.author}'s Keyboard`,
image: images[`/target/media/kbd-${authorSplit}.thumb.jpg`]?.default || fallback,
largeImage: images[`/target/media/kbd-${authorSplit}.jpg`]?.default || fallback,
images: foundImages,
image: foundImages[0].image,
largeImage: foundImages[0].largeImage,
authorImage: images[`/src/routes/showcase/assets/author-${authorSplit}.jpg`]?.default,
}
})
Expand Down

0 comments on commit 5291149

Please sign in to comment.