Skip to content

feature: VID2805 Dual Stepper Element #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,4 @@ export { DipSwitch8Element } from './dip-switch-8-element';
export { StepperMotorElement } from './stepper-motor-element';
export { HX711Element } from './hx711-element';
export { KS2EMDC5Element } from './ks2e-m-dc5-element';
export { VID2805DualStepperElement } from './vid2805-dual-stepper-element';
2 changes: 2 additions & 0 deletions src/react-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { DipSwitch8Element } from './dip-switch-8-element';
import { StepperMotorElement } from './stepper-motor-element';
import { HX711Element } from './hx711-element';
import { KS2EMDC5Element } from './ks2e-m-dc5-element';
import { VID2805DualStepperElement } from './vid2805-dual-stepper-element';

type WokwiElement<T> = Partial<T> & React.ClassAttributes<T>;

Expand Down Expand Up @@ -103,6 +104,7 @@ declare global {
'wokwi-stepper-motor': WokwiElement<StepperMotorElement>;
'wokwi-hx711': WokwiElement<HX711Element>;
'wokwi-ks2e-m-dc5': WokwiElement<KS2EMDC5Element>;
'wokwi-vid2805-dual-stepper': WokwiElement<VID2805DualStepperElement>;
}
}
}
88 changes: 88 additions & 0 deletions src/vid2805-dual-stepper-element.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { html } from 'lit';
import './vid2805-dual-stepper-element';
import { StepperHand } from './vid2805-dual-stepper-element';

export default {
title: 'VID2805 Dual Stepper',
component: 'wokwi-vid2805-dual-stepper',
argTypes: {
outerLength: { control: { type: 'range', min: 20, max: 70 } },
outerAngle: { control: { type: 'range', min: 0, max: 360 } },
outerColour: { control: { type: 'color' } },
outerShape: { options: ['arrow', 'plain', 'fancy'], control: { type: 'select' } },
innerLength: { control: { type: 'range', min: 20, max: 70 } },
innerAngle: { control: { type: 'range', min: 0, max: 360 } },
innerColour: { control: { type: 'color' } },
innerShape: { options: ['arrow', 'plain', 'fancy'], control: { type: 'select' } },
},
args: {
outerLength: 20,
outerAngle: 0,
outerColour: 'grey',
outerShape: 'plain',
innerLength: 20,
innerAngle: 0,
innerColour: 'darkgrey',
innerShape: 'plain',
},
};

const Template = ({
outerLength,
outerAngle,
outerColour,
outerShape,
innerLength,
innerAngle,
innerColour,
innerShape,
innerHand,
outerHand,
}) => html`<wokwi-vid2805-dual-stepper
.outerLength=${outerLength}
.outerAngle=${outerAngle}
.outerColour=${outerColour}
.outerShape=${outerShape}
.innerLength=${innerLength}
.innerAngle=${innerAngle}
.innerColour=${innerColour}
.innerShape=${innerShape}
.innerHand=${innerHand}
.outerHand=${outerHand}
></wokwi-vid2805-dual-stepper>`;

export const Default = Template.bind({});
Default.args = {
innerHand: new StepperHand(25, 'red', 'arrow', 225),
outerHand: new StepperHand(30, 'green', 'arrow', 270),
};

export const NineOclock = Template.bind({});
NineOclock.args = {
innerHand: new StepperHand(70, 'silver', 'plain', 0),
outerHand: new StepperHand(40, 'gold', 'plain', 270),
};

export const SixOclock = Template.bind({});
SixOclock.args = {
innerHand: new StepperHand(70, 'silver', 'plain', 0),
outerHand: new StepperHand(40, 'gold', 'plain', 180),
};

export const ThreeOclock = Template.bind({});
ThreeOclock.args = {
innerHand: new StepperHand(70, 'silver', 'plain', 270),
outerHand: new StepperHand(50, 'gold', 'plain', 90),
};

export const TenPastTen = Template.bind({});
TenPastTen.args = {
innerHand: new StepperHand(70, 'silver', 'plain', 60),
outerHand: new StepperHand(60, 'gold', 'plain', 300),
};

export const SameLength = Template.bind({});
SameLength.args = {
innerHand: new StepperHand(30, 'blue', 'plain', 0),
outerHand: new StepperHand(30, 'green', 'plain', 180),
};
191 changes: 191 additions & 0 deletions src/vid2805-dual-stepper-element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { html, LitElement, svg } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { ElementPin } from '.';
import { mmToPix } from './utils/units';

export type HandShape = 'arrow' | 'plain'; // | 'fancy';

export class StepperHand {
/**
* length used to control hand length
*/
length = 25;

/**
* colour used to paint the hand
*/
colour = '';

/**
* hand shape selects from predefined shape paths
*/
shape: HandShape = 'plain';

/**
* the current hand angle
*/
angle = 0;

constructor(length = 25, colour = '', shape: HandShape = 'plain', angle = 0) {
this.angle = angle;
this.colour = colour;
this.shape = shape;
this.length = length;
}
}

@customElement('wokwi-vid2805-dual-stepper')
export class VID2805DualStepperElement extends LitElement {
@property() outerHand = new StepperHand();
@property() innerHand = new StepperHand();

private coords() {
const _x = 112.3; // x location of shaft point
const _y = 50; // y location of shaft point
const _innerOff = 4.7; // offset to center of inner hand's ring
const _outerOff = 9; // offset to center of outer hand's ring
let _trY = 0;

const ml = Math.max(this.innerHand.length, this.outerHand.length);
if (ml > 30) {
_trY = ml - 30;
}
return {
x: _x,
y: _y,
innerOff: _innerOff,
outerOff: _outerOff,
trY: _trY,
};
}

get pinInfo(): ElementPin[] {
const { x, y, innerOff, outerOff, trY } = this.coords();
const pinXY = (x: number) => {
return { x: 15 + trY, y: (48.5 - x * 2.54) * mmToPix };
};

const pi = [
{ name: 'A1+', ...pinXY(0), number: 1, signals: [] },
{ name: 'A1-', ...pinXY(1), number: 2, signals: [] },
{ name: 'B1+', ...pinXY(2), number: 3, signals: [] },
{ name: 'B1-', ...pinXY(3), number: 4, signals: [] },
{ name: 'A2+', ...pinXY(4), number: 5, signals: [] },
{ name: 'A2-', ...pinXY(5), number: 6, signals: [] },
{ name: 'B2+', ...pinXY(6), number: 7, signals: [] },
{ name: 'B2-', ...pinXY(7), number: 8, signals: [] },
];

console.debug(pi);
return pi;
}

readonly handMap: { [key: string]: string } = {
outer_plain_hand:
'm0,0 c0,5,4,9,9,9,3.28,0,6.13-1.73,7.7-4.33v.03c.5-.8,1.2-1.6,2.1-2.1,.8-.5,1.8-.8,2.8-.8h${len}c1,0,1.8-.8,1.8-1.8s-.8-1.8-1.8-1.8h-${len}c-1,0-1.9-.3-2.8-.8-.8-.5-1.6-1.2-2.1-2h0c-1.56-2.64-4.43-4.4-7.74-4.4-5,0-9,4.1-9,9Zm3.5,0c0-3,2.4-5.4,5.5-5.4s5.5,2.4,5.5,5.4-2.5,5.4-5.5,5.4-5.5-2.4-5.5-5.4Z',
outer_arrow_hand:
'm 0 0 c 0 5 4 9 9 9 c 3.89 0 7.16 -2.42 8.43 -5.85 c 0.3 -0.58 0.78 -1.16 1.27 -1.45 c 0.6 -0.3 1.29 -0.4 1.99 -0.2 c 0 0 0 0 0 0 h 0.01 s 0.9 0.2 0.9 0.2 l ${len} -1.7 l -${len} -1.9 l -0.9 0.3 c -0.7 0.2 -1.4 0.1 -2 -0.2 c -0.6 -0.3 -1.1 -0.8 -1.3 -1.5 c 0 0 -0.01 0.02 -0.02 0.02 c -1.38 -3.72 -4.38 -5.72 -8.38 -5.72 c -5 0 -9 4 -9 9 Z m 3.5 0 c 0 -3.1 2.5 -5.5 5.5 -5.5 s 5.5 2.5 5.5 5.5 s -2.5 5.5 -5.5 5.5 s -5.5 -2.5 -5.5 -5.5 Z',
inner_plain_hand:
'm0,0 c 0 2.6 2.1 4.7 4.7 4.7 c 1.27 0 2.41 -0.5 3.25 -1.31 h 0 c 0.5 -0.49 1.1 -0.89 1.8 -1.19 c 0.7 -0.3 1.4 -0.4 2.1 -0.4 h ${len} c 1 0 1.8 -0.8 1.8 -1.8 s -0.8 -1.8 -1.8 -1.8 h -${len} s -0.06 0 -0.06 0 c -0.7 0 -1.5 -0.1 -2.1 -0.4 c -0.7 -0.3 -1.3 -0.7 -1.8 -1.2 h 0 c -0.84 -0.8 -1.98 -1.3 -3.24 -1.3 c -2.6 0 -4.7 2.1 -4.7 4.7 Z m 2.7 0 c 0 -1.1 0.9 -2 2 -2 s 2 0.9 2 2 s -0.9 2 -2 2 s -2 -0.9 -2 -2 Z',
inner_arrow_hand:
'm 0 0 c 0 2.6 2.1 4.7 4.7 4.7 c 1.37 0 2.59 -0.59 3.44 -1.52 c 0.7 -0.7 1.5 -1.2 2.5 -1.5 s 2 -0.3 2.9 -0.1 l 0.9 0.2 l ${len} -1.8 l -${len} -1.8 l -0.9 0.2 c -1 0.2 -2 0.2 -2.9 -0.1 c -1 -0.3 -1.8 -0.8 -2.5 -1.5 c -0.86 -0.93 -2.08 -1.52 -3.44 -1.52 c -2.6 0 -4.7 2.1 -4.7 4.7 z m 2.7 0 c 0 -1.1 0.9 -2 2 -2 s 2 0.9 2 2 s -0.9 2 -2 2 s -2 -0.9 -2 -2 z',
};

render() {
let inner_svg = this.handMap['inner_' + this.innerHand.shape + '_hand'];
let outer_svg = this.handMap['outer_' + this.outerHand.shape + '_hand'];

// todo: validate hands

// replace length from args
if (this.innerHand.length > 70) {
this.innerHand.length = 70;
}
if (this.outerHand.length > 70) {
this.outerHand.length = 70;
}

inner_svg = inner_svg.split('${len}').join(this.innerHand.length.toString());
outer_svg = outer_svg.split('${len}').join(this.outerHand.length.toString());

const { x, y, innerOff, outerOff, trY } = this.coords();
console.debug(this.coords());
console.debug(trY);
console.debug(x);
console.debug(y);

return html`
<svg id="VID2805" xmlns="http://www.w3.org/2000/svg" width=70mm height=70mm viewBox="0 0 265 265">
<defs>
<style>
.cls-1{fill:#939598;}
.cls-2{fill:#d2d2d2;}
.cls-3{fill:#808285;}
.cls-4{fill:#ed1f24;}
.cls-5{fill:#70bf44;}
.cls-6{fill:#414042;}
.cls-h{fill:"blue";stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:.1px;transform-box:fill-box;}
</style>
<linearGradient
id="hole-gradient"
>
<stop stop-color="#4f4f4f" offset="-25%" />
<stop stop-color="grey" offset="75%" />
</linearGradient>
</defs>
<g id="vid2805-translated" transform="translate(15,265) rotate(270 0 0) translate(0,${trY})">

<!-- pins - translate to top of base, between holes -->
<g id="pins" transform="scale(${mmToPix}) translate(21 3) ">
<path id="pin-1" class="cls-3" d="m 0 0 v -3 c 0 -1 1 -1 1 0 v 3 z" />
<use href="#pin-1" x=2.54 />
<use href="#pin-1" x=5.08 />
<use href="#pin-1" x=7.62 />
<use href="#pin-1" x=10.16 />
<use href="#pin-1" x=12.70 />
<use href="#pin-1" x=15.24 />
<use href="#pin-1" x=17.78 />
</g>

<g id="scaled_body" transform="scale(1.333)">
<path id="base" class="cls-6" d="M170.08,32.12c-4.98-15.86-18.92-27.75-35.9-29.71-1.2-1.4-2.96-2.31-4.96-2.31s-3.76,.91-4.96,2.31c-2.13,.25-4.2,.67-6.23,1.22H51.81c-1.93-.52-3.9-.92-5.92-1.17-1.2-1.48-3.01-2.45-5.07-2.45s-3.82,.94-5.02,2.39C19,4.27,5.19,15.86,0,31.41v27.09c5.15,15.43,18.82,26.97,35.45,28.97,1.2,1.56,3.06,2.58,5.17,2.58s3.98-1.02,5.17-2.58c2.18-.26,4.3-.7,6.37-1.28H117.67c2.07,.58,4.19,1.01,6.37,1.28,1.2,1.56,3.06,2.58,5.17,2.58s3.98-1.02,5.17-2.58c16.89-2.04,30.72-13.89,35.69-29.68v-25.67Z" />
<g id="ribs">
<path class="cls-3" d="M51.36,17.58l7.77-13.95h-5.11l-17.04,30.61c-.85-.27-1.74-.44-2.66-.5L30.9,3.26c-1.48,.34-2.93,.77-4.35,1.27l3.34,29.78c-1.43,.48-2.71,1.23-3.83,2.2l-13.79-10.54-.03,.03c-.38-.26-.82-.44-1.31-.44-1.33,0-2.4,1.08-2.4,2.4,0,1.2,.9,2.16,2.06,2.33l12.75,9.75c-.42,.84-.77,1.72-.97,2.67H.22v4.47H22.3c.18,1.01,.51,1.97,.95,2.87l-12.69,9.94c-1.14,.18-2.03,1.13-2.03,2.33,0,1.33,1.08,2.4,2.4,2.4,.79,0,1.46-.4,1.89-.99l.08,.1,13.03-10.2c1.1,.98,2.37,1.75,3.78,2.25l-3.18,29.5c1.42,.5,2.87,.93,4.35,1.27l3.25-30.13c1.05-.05,2.05-.25,3.01-.57l16.78,30.25h5.11l-7.82-14.1H118.91l-7.85,14.1h5.11l16.81-30.2c.81,.25,1.66,.42,2.54,.49l3.38,30.17c1.48-.34,2.93-.78,4.35-1.27l-3.3-29.44c1.59-.52,3.04-1.36,4.24-2.48l12.76,9.75c.36,.87,1.22,1.49,2.22,1.49,1.33,0,2.4-1.08,2.4-2.4,0-.81-.43-1.49-1.04-1.93l.02-.02-13.75-10.51c.38-.84,.67-1.74,.84-2.67h22.43v-4.47h-22.49c-.19-.88-.5-1.7-.88-2.49l12.62-9.88c1.25-.09,2.24-1.1,2.24-2.37s-1.08-2.4-2.4-2.4c-.3,0-.58,.07-.85,.17l-.13-.17-14.15,11.08c-1.13-1.02-2.45-1.8-3.91-2.3l3.21-29.78c-1.42-.5-2.87-.93-4.35-1.27l-3.28,30.45c-.89,.05-1.75,.21-2.57,.46L116.18,3.62h-5.11l7.74,13.95h-31.55V3.62h-4.47v13.95h-31.43Zm31.43,50.05H48.73l-7.71-13.9c1.93-1.67,3.3-3.95,3.77-6.56h28.93c.84,4.63,4.45,8.29,9.07,9.18v11.28Zm0-33.75c-4.5,.87-8.06,4.36-9.01,8.83h-29.04c-.54-2.53-1.94-4.71-3.85-6.32l7.79-13.99h34.12v11.48Zm4.47-11.48h34.22l7.7,13.88c-1.98,1.61-3.43,3.84-3.98,6.43h-1.92c-1.03-5.27-5.66-9.25-11.23-9.25s-10.21,3.98-11.23,9.25h-4.65c-.95-4.43-4.45-7.92-8.91-8.82v-11.49Zm0,33.94c4.57-.93,8.13-4.57,8.96-9.17h4.6c1.05,5.24,5.68,9.2,11.23,9.2s10.18-3.95,11.23-9.2h1.87c.49,2.68,1.92,5,3.92,6.68l-7.67,13.77h-34.14v-11.29Z" />
</g>
<g id="holes">
<g>
<circle cx="40.75" cy="6.55" r="6.5" class="cls-1" />
<circle cx="40.75" cy="6.55" r="3.25" fill="url(#hole-gradient)" />
</g>
<g>
<circle cx="40.75" cy="83.5" r="6.5" class="cls-1" />
<circle cx="40.75" cy="83.5" r="3.25" fill="url(#hole-gradient)"/>
</g>
<g>
<circle cx="129.25" cy="6.55" r="6.5" class="cls-1" />
<circle cx="129.25" cy="6.55" r="3.25" fill="url(#hole-gradient)" />
</g>
<g>
<circle cx="129.25" cy="83.5" r="6.5" class="cls-1" />
<circle cx="129.25" cy="83.5" r="3.25" fill="url(#hole-gradient)" />
</g>
</g>
<g id="hours">
<path class="cls-h" transform="
translate(${x} ${y + outerOff / 2})
rotate(${this.outerHand.angle})
translate(-${outerOff}, -${outerOff})"
fill="${this.outerHand.colour}" d=${outer_svg} />
</g>
<g id="minutes">
<path class="cls-h"
transform="
translate(${x} ${y})
rotate(${this.innerHand.angle})
translate(-${innerOff}, -${innerOff})"
fill="${this.innerHand.colour}" d=${inner_svg} />
</g>
</g>
</svg>`;
}
}