Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
20 changes: 16 additions & 4 deletions src/stepper-motor-element.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,44 @@ export default {
component: 'wokwi-stepper-motor',
argTypes: {
angle: { control: { type: 'range', min: 0, max: 360 } },
size: { control: { type: 'select', options: [8, 11, 14, 17, 23, 34] } },
arrow: { control: { type: 'color' } },
},
args: {
angle: 0,
arrow: '',
units: '',
value: '',
size: 17,
},
};

const Template = ({ angle, arrow, units, value }) =>
const Template = ({ angle, arrow, units, value, size }) =>
html`<wokwi-stepper-motor
.angle=${angle}
.arrow=${arrow}
.units=${units}
.value=${value}
.size=${size}
></wokwi-stepper-motor>`;

export const Default = Template.bind({});
Default.args = {};

export const Rotated90 = Template.bind({});
Rotated90.args = { angle: 90, units: 'degrees', value: '90' };
Rotated90.args = { angle: 90, units: 'degrees', value: '90', size: 14 };

export const Steps = Template.bind({});
Steps.args = { angle: 180, value: '52,500', units: 'steps' };
Steps.args = { angle: 180, value: '52,500', units: 'steps', size: 14 };

export const PurpleArrow = Template.bind({});
PurpleArrow.args = { angle: 70, arrow: '#4a36ba' };
PurpleArrow.args = { angle: 70, arrow: '#4a36ba', size: 14 };

export const Nema17 = Template.bind({});
Nema17.args = { angle: 70, arrow: '#4a36ba', size: 17 };

export const Nema23 = Template.bind({});
Nema23.args = { angle: 70, arrow: '#4a36ba', size: 23 };

export const Nema34 = Template.bind({});
Nema34.args = { angle: 45, arrow: '#4a36ba', size: 34 };
239 changes: 94 additions & 145 deletions src/stepper-motor-element.ts
Original file line number Diff line number Diff line change
@@ -1,177 +1,126 @@
import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { ElementPin } from '.';
import { mmToPix } from './utils/units';


/**
* NemaSpec describes a NEMA Stepper specification (for the purpose of visualisation)
*
*/
export interface NemaSpec {
id: number; // Nema common number representing the size shorthand (Nema11 Nema 17 etc)
frameSize: number; // the frame size in mm. Since Nema Steppers are square, only one side needed
holeRadius: number; // Fastening hole size
shaftRadius: number; // Motor shaft radius
cornerRadius: number; // Frame corner radius
cornerOffset: number; // Offset from corner to center of hole
bodyRadius: number; // the round motor body size
textSize: number; // Text size showing units etc
};

@customElement('wokwi-stepper-motor')
export class StepperMotorElement extends LitElement {
@property() angle = 0;
@property() arrow = '';
@property() value = '';
@property() units = '';
@property() size : 8 | 11 | 14 | 17 | 23 | 34=17; // valid sizes are 8, 11, 14, 17, 21, 24. 8 is the default at 0.8"

get pinInfo(): ElementPin[] {
const spec = this.nemaSpecMap[this.size];

// these offsets match the transform in renderFace
const xOff = (spec.frameSize / 2 - 3.75) * mmToPix;
const yOff = (spec.frameSize + 5) * mmToPix;

const pi = [
{ name: 'A+', y: yOff, x: xOff, number: 1, signals: [] },
{ name: 'A-', y: yOff, x: xOff + 2.54 * mmToPix, number: 2, signals: [] },
{ name: 'B+', y: yOff, x: xOff + 5.08 * mmToPix, number: 3, signals: [] },
{ name: 'B-', y: yOff, x: xOff + 7.62 * mmToPix, number: 4, signals: [] },
];

return pi;
}

readonly nemaSpecMap : {[key:string] : NemaSpec } = {
"8": { "id": 8, "frameSize": 20.4, "holeRadius": 0.5, "shaftRadius": 3.5, "cornerRadius": 2.5, "cornerOffset": 2.5, "bodyRadius": 7.7, "textSize":8 },
"11": { "id": 11, "frameSize": 28.2, "holeRadius": 1.25, "shaftRadius": 5, "cornerRadius": 2.5, "cornerOffset": 2.5, "bodyRadius": 11, "textSize":8 },
"14": { "id": 14, "frameSize": 35.2, "holeRadius": 1.5, "shaftRadius": 5, "cornerRadius": 4.5, "cornerOffset": 4.5, "bodyRadius": 11, "textSize":10 },
"17": { "id": 17, "frameSize": 42.3, "holeRadius": 1.5, "shaftRadius": 5, "cornerRadius": 5, "cornerOffset": 5.5, "bodyRadius": 14, "textSize":10 },
"23": { "id": 23, "frameSize": 57.3, "holeRadius": 2.5, "shaftRadius": 6.35, "cornerRadius": 5, "cornerOffset": 5.5, "bodyRadius": 19.5, "textSize":16 },
"34": { "id": 34, "frameSize": 86, "holeRadius": 3.25, "shaftRadius": 14, "cornerRadius": 3.25, "cornerOffset": 8.4, "bodyRadius": 36.5, "textSize":16 },
}

readonly pinInfo: ElementPin[] = [
{ name: 'A+', y: 237, x: 108, number: 1, signals: [] },
{ name: 'A-', y: 237, x: 98, number: 2, signals: [] },
{ name: 'B+', y: 237, x: 117, number: 3, signals: [] },
{ name: 'B-', y: 237, x: 127, number: 4, signals: [] },
];
render() {
const spec = this.nemaSpecMap[this.size];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add fallback behavior when size is not valid (e.g. use the values of size 17, or the closest size)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this what happens when specifying the property as one of a set of values?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storybook does validate the size, but in Wokwi users can change the size on the diagram to an invalid value and then they get an error.


render() {
const { arrow } = this;
// local vars const ratio = 1; //100 / spec.frameSize;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in latest commit

const cornerRadius = spec.cornerRadius;
const holeRadius = spec.holeRadius;
const shaftRadius = spec.shaftRadius;
const frameSize = spec.frameSize;
const cornerOffset = spec.cornerOffset;
const bodyRadius = spec.bodyRadius;

return html`
<svg
width="57mm"
height="63.349mm"
version="1.1"
viewBox="0 0 57 63.349"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
return html`<svg
width="100mm"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SVG size should match the actual size of the graphics. Otherwise, Wokwi will draw the selection rectangle too big when you click on the element.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you suggesting that I should vary the generated svg to match the size of the component?
I can do that

height="100mm"
version="1.1"
viewBox="0 0 ${100 * mmToPix} ${100 * mmToPix}"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<linearGradient
id="motor-body"
x1="17.567"
x2="197.27"
y1="171.8"
y2="171.8"
gradientTransform="matrix(.31539 0 0 .31539 9.0021 56.33)"
gradientUnits="userSpaceOnUse"
>
<linearGradient id="motor-body" x1="0%" x2="120%" y1="0" y2="0" gradientUnits="userSpaceOnUse" >
<stop stop-color="#666" offset="0" />
<stop stop-color="#fff" offset="1" />
</linearGradient>
<linearGradient
id="linearGradient5454"
x1="339.07"
x2="478.3"
y1="660.79"
y2="660.79"
gradientTransform="matrix(.083446 0 0 .083446 9.845 56.471)"
gradientUnits="userSpaceOnUse"
>
<linearGradient id="shaft" x1="0%" x2="100%" y1="0" y2="0" gradientUnits="userSpaceOnUse" >
<stop stop-color="#9d9d9d" offset="0" />
<stop stop-color="#9d9d9d" stop-opacity="0" offset="1" />
<stop stop-color="#fdfafa" stop-opacity="0" offset="1" />
</linearGradient>
<linearGradient
id="linearGradient29501"
x1="33.418"
x2="53.905"
y1="114.39"
y2="113.37"
gradientUnits="userSpaceOnUse"
>
<linearGradient id="linearGradient29501" x1="0%" x2="200%" y1="0%" y2="150%" gradientUnits="userSpaceOnUse" >
<stop stop-color="#9d9d9d" offset="0" />
<stop stop-color="#fdfafa" offset=".29501" />
<stop offset="1" />
</linearGradient>
</defs>
<!-- Body -->
<g transform="translate(-14.38 -82.486)">
<g stroke-linecap="round" stroke-linejoin="round">
<rect
x="14.543"
y="82.648"
width="56.675"
height="55.731"
rx="4.7308"
ry="4.6584"
fill="url(#motor-body)"
stroke="#000"
stroke-width=".3245"
/>
<circle cx="20.407" cy="88.675" r="1.7314" fill="#666" stroke-width=".47984" />
<circle
cx="65.732"
cy="88.585"
r="1.7314"
fill="#666"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width=".47984"
/>
<ellipse cx="65.704" cy="88.613" rx="1.0232" ry=".99314" fill="#e6e6e6" />
<circle
cx="66.278"
cy="133.65"
r="1.7314"
fill="#666"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width=".47984"
/>
<ellipse cx="66.25" cy="133.54" rx="1.0232" ry=".99314" fill="#e6e6e6" />
<circle
cx="19.6"
cy="133.13"
r="1.7314"
fill="#666"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width=".47984"
/>
<ellipse cx="19.572" cy="133.16" rx="1.0232" ry=".99314" fill="#ccc" />
<ellipse cx="20.378" cy="88.702" rx="1.0232" ry=".99314" fill="#ccc" />
</defs>
<!-- Body -->
<g transform="scale(${mmToPix})">
<g stroke-linecap="round" stroke-linejoin="round">
<rect width="${frameSize}" height="${frameSize}" rx="${cornerRadius}" ry="${cornerRadius}" fill="url(#motor-body)" stroke="#000" stroke-width=".3245" />
<circle cx="${cornerOffset}" cy="${cornerOffset}" r="${holeRadius}" fill="darkgrey" />
<circle cx="${cornerOffset}" cy="${cornerOffset}" r="${holeRadius*0.9}" fill="lightgrey" />
<circle cx="${frameSize - cornerOffset}" cy="${cornerOffset}" r="${holeRadius}" fill="darkgrey" />
<circle cx="${frameSize - cornerOffset}" cy="${cornerOffset}" r="${holeRadius*0.9}" fill="lightgrey" />
<circle cx="${cornerOffset}" cy="${frameSize - cornerOffset}" r="${holeRadius}" fill="darkgrey" />
<circle cx="${cornerOffset}" cy="${frameSize - cornerOffset}" r="${holeRadius*0.9}" fill="lightgrey" />
<circle cx="${frameSize - cornerOffset}" cy="${frameSize - cornerOffset}" r="${holeRadius}" fill="darkgrey" />
<circle cx="${frameSize - cornerOffset}" cy="${frameSize - cornerOffset}" r="${holeRadius*0.9}" fill="lightgrey" />
</g>
<!-- Rotator -->
<circle
cx="43.816"
cy="111.05"
r="10.25"
fill="#868686"
fill-opacity=".89602"
opacity=".73"
stroke="url(#linearGradient29501)"
stroke-width=".41429"
/>
<path
transform="rotate(${this.angle}, 43.82, 111.2)"
fill="${arrow || 'transparent'}"
d="M48.706051,111.66821H38.189949L43.448,83.34119Z"
/>
<path
transform="rotate(${this.angle}, 43.82, 111.2)"
d="m40.056 106.86a5.3616 5.3616 0 0 0-1.9696 4.1497 5.3616 5.3616 0 0 0 5.3616 5.3616 5.3616 5.3616 0 0 0 5.3616-5.3616 5.3616 5.3616 0 0 0-1.9672-4.1497z"
fill="#4d4d4d"
stroke="url(#linearGradient5454)"
stroke-width=".57968"
/>
<!-- motor body -->
<circle cx="${frameSize / 2}" cy="${frameSize / 2}" r="${bodyRadius}" fill="#868686" fill-opacity=".89602" opacity=".73" stroke="url(#shaft)" stroke-width=".41429" />
<!-- Rotator -->
<g >
<path transform="rotate(${this.angle}, ${frameSize/2},${frameSize/2}) translate(${frameSize/2} ${frameSize/2})" fill="${this.arrow || 'transparent'}" d="m 0 0 l -${shaftRadius} 0 l ${shaftRadius} -${frameSize/2 - 3} l ${shaftRadius} ${frameSize/2 - 3} z" />
<path transform="rotate(${this.angle}, ${frameSize/2} ${frameSize/2}) translate(${(frameSize-shaftRadius)/2} ${(frameSize-2*shaftRadius)/2})" d="m 0 0 a ${shaftRadius} ${shaftRadius} 0 1 0 ${shaftRadius} 0 z" fill="#4d4d4d" stroke="url(#shaft)" stroke-width=".57968" />
</g>
<g
transform="matrix(-.0031398 -.26456 .26456 -.0031398 32.829 149.58)"
clip-rule="evenodd"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
stroke-miterlimit="1.5"
>
<!-- Pins -->
<path
id="pin"
fill="#9f9f9f"
d="m15.259 56.3c-0.382-2.2e-5 -0.74801 0.15196-1.019 0.42194-0.27002 0.26998-0.42104 0.63698-0.42106 1.019-2.2e-5 0.382 0.15096 0.74801 0.42094 1.019 0.27098 0.27002 0.63698 0.42204 1.019 0.42206 5.242 2.96e-4 23.147 0.0013 26.132 0.0015 0.233 1.4e-5 0.42301-0.18998 0.42302-0.42398 3.1e-5 -0.545 8.4e-5 -1.489 1.15e-4 -2.035 1.3e-5 -0.233-0.18998-0.42301-0.42298-0.42302-2.985-1.68e-4 -20.89-0.0012-26.132-0.0015z"
/>
<!-- Pins -->
<path id="pin" transform="translate(${frameSize/2 - 3.75} ${frameSize})" fill="#9f9f9f" d="m 0 0 c .5 0 .5 0 .5 .5 v 5 c -.5 .5 -.5 .5 -1 0 v -5 c 0 -.5 0 -.5 .5 -.5" />
<use xlink:href="#pin" x="2.54" />
<use xlink:href="#pin" x="5.08" />
<use xlink:href="#pin" x="7.62" />
<use xlink:href="#pin" y="-9.6" />
<use xlink:href="#pin" y="-19.2" />
<use xlink:href="#pin" y="-28.8" />
<!-- Text -->
<text font-family="arial" font-size="14.667px" text-align="center" text-anchor="middle" >
<tspan x="${frameSize/ 2}" y="${frameSize - spec.textSize/2}" font-size="${spec.textSize/mmToPix}px">${this.value} ${this.units}</tspan>
</text>
<!-- Text -->
<text
transform="rotate(90)"
font-family="arial"
font-size="14.667px"
text-align="center"
text-anchor="middle"
>
<tspan x="45" y="-58.62619" font-size="14px">${this.units}</tspan>
<tspan x="45" y="-75.066772" font-size="24px">${this.value}</tspan>
</text>
</g>
</g>
</svg>
`;
</svg>`;
}
}