Skip to content

Commit

Permalink
Filament chart and other nice things
Browse files Browse the repository at this point in the history
  • Loading branch information
rianadon committed Jun 4, 2023
1 parent b460399 commit 12ed3db
Show file tree
Hide file tree
Showing 15 changed files with 349 additions and 64 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ The new developments of this work are:
- Fast CSG previews are generated using ~~the [OpenJSCAD project](https://github.com/jscad/OpenJSCAD.org). I've added a new back end to the [scad-clj](https://github.com/farrellm/scad-clj) library (which has been copied into this project due to how heavily I've edited it) to generate the OpenJSCAD objects.~~ Since Manifold is such a fast kernel, it is also used for previews. I've written an interop layer to give Manifold the same API as OpenJSCAD.
- CSGs are previewed with Three.JS, using the [Svelte-cubed](https://svelte-cubed.vercel.app) bindings.
- The ClojureScript source files are compiled into a web worker, which is run from [Svelte](https://svelte.dev)/TypeScript frontend.
- Configurations are compressed with [protobuf](https://protobuf.dev) and saved to the URL. This makes it easy to share configurations with others. I've also switched to using camelcase for the json configuration, which makes integrating with protobuf easier.
- Configurations are compressed with [protobuf](https://protobuf.dev) and saved to the URL. This makes it easy to share configurations with others. I've also switched to using camelcase for the JSON configuration, which makes integrating with protobuf easier.
- Confusing options have help information explaining what they do.
- I've made a few edits to the presets and Dactyl code to create better default models. These include making TRRS jacks the default instead of RJ9, removing the Pro Micro holder since it rarely correctly attaches to the side of the case, and fixing the TRRS holder so that it's attached to the case.

## Building and running

Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="The Dactyl Web Configurator generates Dactyl Manuform and Dactyl LightCycle keyboard bodies from your browser.">
<meta name="description" content="Generate Dactyl Manuform and Dactyl LightCycle keyboard cases from your browser.">
<title>Dactyl Generator</title>
</head>
<body>
Expand Down
Binary file added public/stars.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/starsdark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 49 additions & 28 deletions src/App.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
<script lang="ts">
import Viewer from './lib/Viewer.svelte'
import Github from 'svelte-material-icons/Github.svelte'
import Book from 'svelte-material-icons/BookOpenVariant.svelte'
import Info from 'svelte-material-icons/Information.svelte'
import Shimmer from 'svelte-material-icons/Shimmer.svelte'
import Popover from 'svelte-easy-popover'
import { estimateFilament, fromMesh } from './lib/mesh'
import { SUPPORTS_DENSITY, estimateFilament, fromMesh } from './lib/mesh'
import { exampleGeometry } from './lib/example'
import DactylWorker from './worker?worker'
Expand All @@ -19,6 +17,7 @@
import Footer from './lib/Footer.svelte';
import ShapingSection from './lib/ShapingSection.svelte';
import Instructions from './lib/Instructions.svelte';
import FilamentChart from './lib/FilamentChart.svelte';
import { serialize, deserialize } from './lib/serialize';
import presetLight from './assets/presets/lightcycle.default.json'
Expand All @@ -27,7 +26,9 @@
import presetCorne from './assets/presets/manuform.corne.json'
import presetSmallest from './assets/presets/manuform.smallest.json'
let geometries = [];
let keyboardGeometry = null;
let keyboardVolume = 0;
let supportGeometry = null;
let filament;
let referenceElement;
Expand All @@ -44,12 +45,13 @@
let stlDialogOpen = false;
let sponsorOpen = false;
let instructionsOpen = !(localStorage['instructions'] === 'false')
let showSupports = false
$: localStorage['instructions'] = JSON.stringify(instructionsOpen)
exampleGeometry().then(g => {
// Load the example gemoetry if nothing has been rendered yet
if (!geometries.length) geometries = [g]
if (!keyboardGeometry) keyboardGeometry = g
})
$: try {
Expand Down Expand Up @@ -102,6 +104,8 @@
// Reset the state
generatingCSG = true;
csgError = undefined;
filament = undefined;
supportGeometry = null;
logs = [];
if (myWorker) myWorker.terminate();
myWorker = new DactylWorker()
Expand All @@ -127,8 +131,11 @@
} else if (e.data.type == 'csg') {
// Preview finished. Show it!
generatingCSG = false;
geometries = fromMesh(e.data.data);
filament = estimateFilament(e.data.data[0]?.volume);
keyboardGeometry = fromMesh(e.data.data);
keyboardVolume = e.data.data.volume
} else if (e.data.type == 'supports') {
supportGeometry = fromMesh(e.data.data);
filament = estimateFilament(keyboardVolume, e.data.data.volume);
} else if (e.data.type == 'log') {
// Logging from OpenSACD
logs = [...logs, e.data.data]
Expand All @@ -142,21 +149,15 @@
<button class="flex items-center gap-2 bg-yellow-400/10 border-2 border-yellow-400 px-3 py-1.5 rounded hover:bg-yellow-400/60 hover:shadow-md hover:shadow-yellow-400/30 transition-shadow mt-6 sm:mt-0 sm:ml-2" on:click={() => sponsorOpen = true}>
<Shimmer size="24" class="text-yellow-500 dark:text-yellow-300" />Support My Work
</button>
<!--<a class="text-gray-800 dark:text-gray-100 mx-2 md:mx-4" href="/docs">
<Book size="2em" />
</a>
<a class="text-gray-800 dark:text-gray-100 mx-2 md:mx-4" href="/">
<Github size="2em" />
</a>-->
</header>
{#if instructionsOpen}
<Instructions on:close={() => instructionsOpen = false} />
{/if}
<main class="mt-4 px-8 dark:text-slate-100 flex flex-col-reverse xs:flex-row">
<main class="mt-6 mb-16 px-8 dark:text-slate-100 flex flex-col-reverse xs:flex-row">
<div class="xs:w-80 md:w-auto">
<div class="mb-8">
<button class="help-button" on:click={() => instructionsOpen = !instructionsOpen}>{#if instructionsOpen}Hide Instructions{:else}Show Instructions{/if}</button>
<button class="button" on:click={downloadSTL}>Download STL</button>
<button class="button" on:click={downloadSTL}>Download Model</button>
</div>

<h2 class="text-2xl text-teal-500 dark:text-teal-300 font-semibold mb-2">Presets</h2>
Expand Down Expand Up @@ -196,17 +197,21 @@
</div>
{/if}
<div class="viewer relative xs:sticky h-[100vh] top-0">
<Viewer geometries={geometries} style="opacity: {generatingCSG ? 0.2 : 1}"></Viewer>
<Viewer geometries={[keyboardGeometry, supportGeometry]} showSupports={showSupports} style="opacity: {generatingCSG ? 0.2 : 1}"></Viewer>
{#if filament}
<div class="absolute bottom-0 right-0 text-right mb-2">
<div class="absolute bottom-0 right-0 mb-2">
{filament.length.toFixed(1)}m <span class="text-gray-600 dark:text-gray-100">of filament</span>
<div class="align-[-18%] inline-block text-gray-600 dark:text-gray-100" bind:this={referenceElement}>
<button class="align-[-18%] inline-block text-gray-600 dark:text-gray-100" bind:this={referenceElement}>
<Info size="20px" />
</div>
<Popover triggerEvents={["hover", "focus"]} {referenceElement} placement="top" spaceAway={4}>
<div class="rounded bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 px-2 py-1 mx-4 w-80">
<p>Estimate using 100% infill, no supports.</p>
<p>This will set you back at least ${filament.cost.toFixed(2)}.</p>
</button>
<Popover triggerEvents={["hover", "focus"]} {referenceElement} placement="top" spaceAway={4} on:change={({ detail: { isOpen }}) => showSupports = isOpen}>
<div class="flex gap-4 items-end rounded bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 px-2 py-1 mx-4 text-gray-600 dark:text-gray-100">
<FilamentChart fractionKeyboard={filament.fractionKeyboard} />
<div>
<p class="whitespace-nowrap mb-2">Estimated using <span class="font-semibold text-teal-500 dark:text-teal-400">100% infill</span>,<br><span class="font-semibold text-purple-500 dark:text-purple-400">{SUPPORTS_DENSITY*100}% supports density</span>.</p>
<p class="whitespace-nowrap mb-1">This will cost about <span class="font-semibold text-black dark:text-white">${filament.cost.toFixed(2)}</span>.</p>
<p class="whitespace-nowrap text-sm">The keyboard itself uses {filament.keyboard.length.toFixed(1)}m.</p>
</div>
</div>
</Popover>
</div>
Expand All @@ -223,7 +228,11 @@
</div>
</div>
</main>
<footer class="px-8 pb-8 pt-16">
<p class="cosmos mx-2 mb-8 px-2 py-2 relative mb-2 from-purple-100 to-teal-100 dark:from-purple-900 dark:to-teal-800 rounded flex items-center justify-between">
<span class="bg-[#eaecfc]/80 dark:bg-[#353c70]/50 px-4 py-2 rounded mix-blend-luminosity"><span class="font-semibold hidden sm:block md:inline">Enjoying the configurator?</span> I'm building Cosmos, a new configurable keyboard.</span>
<a class="rounded bg-gradient-to-br from-[#3c0d61] to-gray-700 text-white dark:text-black dark:from-purple-200 dark:to-purple-100 py-2 px-4 mx-4 font-semibold flex-none hover:from-purple-700 hover:to-teal-800 hover:dark:from-white hover:dark:to-white hover:shadow-md hover:shadow-teal-900/30 hover:dark:shadow-teal-200/30 transition-shadow" href="https://ryanis.cool/cosmos?utm_source=dactyl">Check it out</a>
</p>
<footer class="px-8 pb-8">
<Footer></Footer>
</footer>
{#if sponsorOpen}
Expand All @@ -235,8 +244,8 @@
</RenderDialog>
{:else if stlDialogOpen}
<RenderDialog logs={logs} closeable={!generatingSCADSTL} on:close={() => stlDialogOpen= false} generating={generatingSTL}>
<p class="mb-1">Your model should download in a few seconds.</p>
<p class="mb-1">Want to edit it in OpenSCAD? You can download the source file <button class="underline text-teal-500" on:click={downloadSCAD}>here</button>.</p>
<p class="mb-1">An STL file for 3D printing should download in a few seconds.</p>
<p class="mb-1">Want to edit the model in OpenSCAD? Download the source file <button class="underline text-teal-500" on:click={downloadSCAD}>here</button>.</p>

<div class="bg-gray-100 dark:bg-gray-900 px-4 py-2 my-4 rounded text-left">
<p class="font-bold mb-1">A few seconds? Why so fast?</p>
Expand Down Expand Up @@ -269,13 +278,25 @@
}
.button {
@apply bg-purple-300 dark:bg-gray-900 hover:bg-purple-400 dark:hover:bg-teal-700 dark:text-white font-semibold py-2 px-4 rounded focus:outline-none border border-transparent focus:border-teal-500 mb-2;
@apply bg-purple-300 dark:bg-gray-900 hover:bg-purple-400 dark:hover:bg-teal-700 dark:text-white font-semibold px-4 h-[42px] rounded focus:outline-none border border-transparent focus:border-teal-500 mb-2;
}
.help-button {
@apply border-2 border-purple-300 dark:border-gray-900 hover:bg-purple-100 dark:hover:bg-gray-800 dark:text-white py-2 px-4 rounded focus:outline-none focus:border-teal-500;
@apply border-2 border-purple-300 dark:border-gray-700 hover:bg-purple-100 dark:hover:bg-gray-800 dark:hover:border-teal-500 dark:text-white h-[42px] px-4 rounded focus:outline-none focus:border-teal-500;
}
.preset {
@apply bg-[#99F0DC] dark:bg-gray-900 hover:bg-teal-500 dark:hover:bg-teal-700 dark:text-white py-1 px-4 rounded focus:outline-none border border-transparent focus:border-teal-500 mb-2;
}
.cosmos {
background-image: url('/stars.png'), linear-gradient(to right, var(--tw-gradient-stops));
background-repeat: repeat, no-repeat;
background-size: auto 100%, auto auto;
background-position: center, 0% 0%;
}
@media (prefers-color-scheme: dark) {
.cosmos {
background-image: url('/starsdark.png'), linear-gradient(to right, var(--tw-gradient-stops));
}
}
</style>
35 changes: 35 additions & 0 deletions src/lib/FilamentChart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script lang="ts">
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
export let fractionKeyboard: number;
export let size = '4em'
const angle = tweened(0, { easing: cubicOut, duration: 400 })
$: angle.update(() => 360 * fractionKeyboard)
</script>

<div class="text-left flex flex-col items-center">
<div class="chart rounded-full mb-2" style:--angle={$angle + 'deg'} style:width={size} style:height={size}></div>
<div class="text-sm text-gray-600 dark:text-gray-100">
<p class="whitespace-nowrap flex items-center gap-2 mb-[-0.1em]"><span class="inline-block w-2 h-2 bg-teal-400" /> Keyboard</p>
<p class="whitespace-nowrap flex items-center gap-2"><span class="inline-block w-2 h-2 bg-purple-400" /> Supports</p>
</div>
</div>

<style lang="postcss">
.chart {
--hole: 45%;
background: radial-gradient(theme('colors.gray.100') var(--hole), transparent calc(var(--hole) + 1%)),
conic-gradient(transparent 0deg var(--angle), theme('colors.purple.400') var(--angle) 360deg),
theme('colors.teal.400');
}
@media (prefers-color-scheme: dark) {
.chart {
background: radial-gradient(theme('colors.gray.700') var(--hole), transparent calc(var(--hole) + 1%)),
conic-gradient(transparent 0deg var(--angle), theme('colors.purple.400') var(--angle) 360deg),
theme('colors.teal.500');
}
}
</style>
4 changes: 2 additions & 2 deletions src/lib/Footer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
Special thanks to everyone who has made the Dactyl keyboard possible, including <a class="darka" href="https://github.com/adereth">@adereth</a> (original Dactyl Design), <a class="darka" href="https://github.com/tshort">@tshort</a> (Dactyl Manuform) and <a class="darka" href="https://github.com/ibnuda">@ibnuda</a> (Dactyl Generator).
</p>
<p class="mb-2 text-gray-500 dark:text-gray-400">
For trackballs and more advanced settings, I recommend <a href="https://github.com/joshreve/dactyl-keyboard">@joshreve's</a> and <a href="https://github.com/bullwinkle3000/dactyl-keyboard">@bullwinkle3000's</a> CadQuery generators, which can also output STEP files.
For trackballs and more advanced settings, I recommend <a href="https://github.com/joshreve/dactyl-keyboard">@joshreve's</a> and <a href="https://github.com/bullwinkle3000/dactyl-keyboard">@bullwinkle3000's</a> CadQuery generators, which can also output STEP files.
</p>
<p class="mb-2 text-gray-500 dark:text-gray-400">
You can find this website's source on <a href="https://github.com/rianadon/dactyl-configurator">GitHub</a>. It's licensed under the <a href="https://github.com/rianadon/dactyl-configurator/blob/main/LICENSE.md">AGPL</a>.
You can find this website's <a href="https://github.com/rianadon/dactyl-configurator">source</a> and <a href="https://github.com/rianadon/dactyl-configurator/releases">release notes</a> on GitHub. The code is licensed under the <a href="https://github.com/rianadon/dactyl-configurator/blob/main/LICENSE.md">AGPL</a>.
</p>

<style lang="postcss">
Expand Down
19 changes: 12 additions & 7 deletions src/lib/Instructions.svelte
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
<article class="mx-4 mb-4 pl-4 pr-12 py-2 rounded dark:bg-gray-900/50 relative">
<p>The Dactyl keyboard is a configurable ergonomic keyboard. You can not only change the switch types you are using, but the key layout and shape of the keyboard as well, so that it fits to your typing style and needs.</p>
<script lang="ts">
import Heart from 'svelte-material-icons/HeartOutline.svelte'
</script>

<article class="mx-2 mb-4 px-6 py-3 rounded dark:bg-gray-900/50 relative">
<p>The Dactyl keyboard is a customizable ergonomic keyboard. Unlike typical keyboards, it contours to the shape of your hand. This tool allows you to configure almost every aspect of the keyboard for maximum comfort and flexibility.</p>
<p>I recommend you begin with the default preset (<span>Ergodox</span>). It has better defaults for the key holes and connector.</p>
<p>Before 3D printing your first Dactyl Keyboard case, I recommend you:</p>
<p>Before 3D printing your first Dactyl keyboard case, I recommend you:</p>
<ul class="list-disc pl-4">
<li>Copy down the current site URL so you can revisit your design. The URL is unique to the keyboard you've configured.</li>
<li>Copy down the current site URL so you can revisit or share your design. The URL is unique to the keyboard you've configured.</li>
<li>Take a look at a few build logs online and decide whether you want to use PCBs or solder the wires directly to the switches.</li>
<li>Find where to buy the parts necessary for the keyboard (switches, keycaps, diodes, wires, microcontrollers, cables, and soldering equipment).</li>
<li>Find a suitable keyboard layout using the <a href="https://config.qmk.fm/#/handwired/dactyl_manuform/5x7/LAYOUT_5x7">QMK Configurator</a> and become familiar with <a href="https://docs.qmk.fm/#/configurator_step_by_step">compiling and flashing</a>.
</ul>
<p>There's a lot to making your own keyboard, but it's a fun process. I promise!</p>
<p>There's a lot to making your own keyboard, but it's a fun process. I promise! <Heart class="inline relative top-[-1px] text-teal-500" size="20px" /> </p>
</article>
<style lang="postcss">
p, ul { @apply mb-1; }
/* span { @apply rounded px-1 py-0.5 dark:bg-gray-900 border border-gray-700; } */
span { @apply rounded px-1 bg-teal-100; }
a { @apply underline text-teal-500; }
span { @apply rounded px-1 bg-[#99F0DC]/50 dark:bg-teal-500/30; }
</style>
32 changes: 21 additions & 11 deletions src/lib/Viewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@
import WebGL from 'three/examples/jsm/capabilities/WebGL'
export let geometries: THREE.Geometry[];
export let showSupports: boolean;
export let style: string;
let canvas;
let camera: THREE.PerspectiveCamera;
let root: any;
const middle = new THREE.Vector3()
const cameraFOV = 45;
const size = new THREE.Vector3();
const size = new THREE.Vector3(1, 1, 1);
$: loadGeometries(geometries)
$: validGeometries = geometries.filter(g => !!g)
$: loadGeometries(validGeometries)
$: {
// Give the camera an initial position
Expand All @@ -28,18 +31,20 @@
function loadGeometries(gs: THREE.BufferGeometry[]) {
// Compute bounding boxes of the gemoetries
const boundingBox = new THREE.Box3(new THREE.Vector3(-0.1, -0.1, -0.1), new THREE.Vector3(0.1, 0.1, 0.1));
for (const g of gs) {
g.computeBoundingBox();
boundingBox.union(g.boundingBox);
if (gs.length == 1) {
gs[0].computeBoundingBox();
boundingBox.union(gs[0].boundingBox);
boundingBox.getCenter(middle);
boundingBox.getSize(size);
}
const middle = new THREE.Vector3();
boundingBox.getCenter(middle);
for (const g of gs) {
if (g.wastransformed) continue
g.applyMatrix4(new THREE.Matrix4().makeTranslation(-middle.x, -middle.y, -middle.z));
g.wastransformed = true
}
boundingBox.getSize(size);
resize();
}
Expand All @@ -65,11 +70,16 @@
{#if WebGL.isWebGLAvailable()}
<div class="container" bind:this={canvas} style={style}>
<SC.Canvas antialias alpha={true}>
{#each geometries as geometry}
<SC.Mesh geometry={geometry} />
{/each}
{#if validGeometries[0]}
<SC.Mesh geometry={validGeometries[0]} />
{/if}
{#if validGeometries[1] && showSupports}
<SC.Mesh geometry={validGeometries[1]} material={new THREE.MeshStandardMaterial({ color: 0xcc80f2, transparent: true, opacity: 0.85 })} />
{/if}
<PerspectiveCamera fov={cameraFOV} bind:self={camera} bind:root={root} />
<SC.OrbitControls enableZoom={false} />
<SC.AmbientLight intensity={0.8} />
<SC.DirectionalLight intensity={0.3} position={[0, 0, -20]} />
</SC.Canvas>
</div>
{:else}
Expand Down
Loading

0 comments on commit 12ed3db

Please sign in to comment.