How to

write a keyboard

Why

write a keyboard

Advantages of a programmatic design

  • customization
  • parametrized design
  • it's just a fun thing to do

Attempt 1: Re-using an existing design

My Version

Tools

  • Openscad
  • Deno

Tools: Openscad

							
cube([2,3,4]);
translate([3,0,0]) {
  cube([2,3,4]);
}
							
						

Deno

  • https://deno.land/
  • Typescript runtime
  • uses V8 engine
  • written in Rust
  • easy to start: no bundler configs
  • 							
    Deno.writeTextFile("./sandbox.scad", sandbox().render())
    							
    						

    Constructive Solid Geometry

    Basic operations:

    • Union
    • Hull
    • Intersection
    • Difference

    Union

    Hull

    Intersection

    Difference

    Approach

    • generate Openscad code with Typescript and Deno
    • cube is as a hull of points
    • rotate and transle points in Typescript
    • only post-transformation positions in generated Openscad code
    • corner positions can be accessed after transformation

    Generating the Openscad code

    							
    export const hull = (...elements: OpenscadShape[]): OpenscadShape => {
      return new OpenscadShape(
        openScadFunctionCall({
          name: "hull",
          curlyBracesContent: elements.map((e) => e.build()).join("\n"),
        })
      );
    };
    							
    						
    							
    const curlyBracesContentIndented = options.curlyBracesContent
    	? ` {${indent(options.curlyBracesContent)}}`
    	: ";";
    return `${options.name} (${unnamedParamsString}${separator}${namedParamsString})${curlyBracesContentIndented}`;
    							
    						
    							
    hull(){
    	cube([0, 0, 0]);
    	cube([3, 0, 0]);
    }
    							
    						

    Cube

    							
    this.vertices = [
    	this.position.add(v3(-size.x / 2, -size.y / 2, -size.z / 2)),
    	this.position.add(v3(size.x / 2, -size.y / 2, -size.z / 2)),
    	this.position.add(v3(size.x / 2, size.y / 2, -size.z / 2)),
    	this.position.add(v3(-size.x / 2, size.y / 2, -size.z / 2)),
    	this.position.add(v3(-size.x / 2, -size.y / 2, size.z / 2)),
    	this.position.add(v3(size.x / 2, -size.y / 2, size.z / 2)),
    	this.position.add(v3(size.x / 2, size.y / 2, size.z / 2)),
    	this.position.add(v3(-size.x / 2, size.y / 2, size.z / 2)),
    ];
    							
    						

    Translation

    							
    public translate = (delta: Vector3) => {
    	this.vertices = this.vertices.map((p) => p.add(delta));
    	this.position = this.position.add(delta);
    	return this;
    };
    							
    						

    Rotation

    							
    export const rotatePoint = (options: RotatePointOptions): Vector3 => {
      const initialVector = options.position.sub(options.origin);
      const rotatedVector = { ...initialVector };
      if (options.axis === Axis.Z) {
        rotatedVector.x =
          initialVector.x * Math.cos(-options.angleRadians) -
          initialVector.y * Math.sin(-options.angleRadians);
        rotatedVector.y =
          initialVector.y * Math.cos(-options.angleRadians) +
          initialVector.x * Math.sin(-options.angleRadians);
      } else if (options.axis === Axis.X) {
        rotatedVector.y =
          initialVector.y * Math.cos(-options.angleRadians) -
          initialVector.z * Math.sin(-options.angleRadians);
        rotatedVector.z =
          initialVector.z * Math.cos(-options.angleRadians) +
          initialVector.y * Math.sin(-options.angleRadians);
      } else if (options.axis === Axis.Y) {
        rotatedVector.x =
          initialVector.x * Math.cos(-options.angleRadians) -
          initialVector.z * Math.sin(-options.angleRadians);
        rotatedVector.z =
          initialVector.z * Math.cos(-options.angleRadians) +
          initialVector.x * Math.sin(-options.angleRadians);
      }
      return options.origin.add(rotatedVector);
    };
    							
    						

    Curved rows and columns

    Curved rows and columns

    Attempt 2 Results

    • shape inspired by dactyl manuform

    Printing

    End Result

    Typing Demonstration

    The End