refactor: Restructure data files into component-specific and common directories, add new UI components, and update project documentation.

This commit is contained in:
MunchDev-oss
2026-01-07 02:06:43 -05:00
parent 97d2b66f02
commit 5366865b4b
28 changed files with 1894 additions and 2051 deletions

View File

@@ -69,7 +69,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -1427,7 +1426,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"dev": true,
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
@@ -1467,7 +1465,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1932,7 +1929,6 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -2696,7 +2692,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3908,7 +3903,6 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -4673,7 +4667,6 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -4889,7 +4882,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -5699,7 +5691,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"peer": true,
"engines": {
"node": ">=12"
},
@@ -5845,13 +5836,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"optional": true
},
"node_modules/unzipper": {
"version": "0.10.14",
"resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz",
@@ -5958,7 +5942,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
"dev": true,
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -6050,7 +6033,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"peer": true,
"engines": {
"node": ">=12"
},
@@ -6233,7 +6215,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz",
"integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==",
"dev": true,
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
export default function Footer() {
return (
<footer className="w-full py-8 mt-12 border-t border-gray-200 dark:border-gray-700">
<div className="max-w-4xl mx-auto px-4 text-center">
<p className="text-sm text-gray-500 dark:text-gray-400">
Disclaimer: The OSSM Configurator is an independent open-source project.
We are not directly associated with the vendors listed, but their contributions to open source are deeply appreciated and reciprocated.
</p>
</div>
</footer>
);
}

View File

@@ -1,4 +1,4 @@
import partsData from '../data/index.js';
import Footer from './Footer';
export default function MainPage({ onSelectBuildType }) {
const handleSelect = (buildType) => {
@@ -6,8 +6,8 @@ export default function MainPage({ onSelectBuildType }) {
};
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-4xl mx-auto px-4">
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8 flex flex-col">
<div className="max-w-4xl mx-auto px-4 flex-grow">
{/* Header */}
<div className="mb-12 text-center">
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
@@ -23,7 +23,7 @@ export default function MainPage({ onSelectBuildType }) {
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6 text-center">
Select Your Build Type
</h2>
<div className="grid md:grid-cols-3 gap-6">
{/* New Build - RAD Kit */}
<button
@@ -120,6 +120,7 @@ export default function MainPage({ onSelectBuildType }) {
</div>
</div>
</div>
<Footer />
</div>
);
}

View File

@@ -6,6 +6,7 @@ import OptionsStep from './steps/OptionsStep';
import RemoteStep from './steps/RemoteStep';
import ToyMountStep from './steps/ToyMountStep';
import BOMSummary from './BOMSummary';
import Footer from './Footer';
const steps = [
{ id: 'motor', name: 'Motor', component: MotorStep },
@@ -87,7 +88,7 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
// Power Supply step - require power supply selection
return;
}
if (currentStep < filteredSteps.length - 1) {
setCurrentStep(currentStep + 1);
}
@@ -104,17 +105,17 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
if (stepIndex <= currentStep) {
return true;
}
// In upgrade mode, no validation needed
if (buildType === 'upgrade') {
return true;
}
// In RAD Kit mode, all steps are pre-selected, so navigation is always allowed
if (buildType === 'rad-kit') {
return true;
}
// Check if required steps are completed before jumping ahead
if (stepIndex > 0 && !config.motor) {
return false; // Can't skip motor step
@@ -122,7 +123,7 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
if (stepIndex > 1 && !config.powerSupply) {
return false; // Can't skip power supply step
}
return true;
};
@@ -137,7 +138,7 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
if (buildType === 'upgrade') {
return true;
}
if (currentStep === 0 && !config.motor) {
return false; // Motor step - require motor selection
}
@@ -158,8 +159,8 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
}, [buildType, currentStep]);
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-4xl mx-auto px-4">
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8 flex flex-col">
<div className="max-w-4xl mx-auto px-4 flex-grow w-full">
{/* Back Button */}
{onBackToMain && (
<div className="mb-4">
@@ -191,7 +192,7 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
OSSM Configurator
</h1>
<p className="text-gray-600 dark:text-gray-300">
{buildType === 'upgrade'
{buildType === 'upgrade'
? 'Select upgrade components and modifications'
: 'Configure your Open Source Sex Machine'}
</p>
@@ -212,17 +213,15 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
{/* Circle */}
<button
onClick={() => goToStep(index)}
className={`w-10 h-10 rounded-full flex items-center justify-center font-semibold flex-shrink-0 z-10 ${
index === currentStep
? 'bg-blue-600 dark:bg-blue-500 text-white'
: index < currentStep
className={`w-10 h-10 rounded-full flex items-center justify-center font-semibold flex-shrink-0 z-10 ${index === currentStep
? 'bg-blue-600 dark:bg-blue-500 text-white'
: index < currentStep
? 'bg-green-500 dark:bg-green-600 text-white'
: 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400'
} ${
index <= currentStep
} ${index <= currentStep
? 'cursor-pointer hover:opacity-80'
: 'cursor-not-allowed'
}`}
}`}
disabled={!canNavigateToStep(index)}
>
{index < currentStep ? '✓' : index + 1}
@@ -230,10 +229,9 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
{/* Connecting line to the right */}
{index < filteredSteps.length - 1 && (
<div
className={`absolute top-5 left-1/2 h-1 ${
index < currentStep ? 'bg-green-500 dark:bg-green-600' : 'bg-gray-200 dark:bg-gray-700'
}`}
style={{
className={`absolute top-5 left-1/2 h-1 ${index < currentStep ? 'bg-green-500 dark:bg-green-600' : 'bg-gray-200 dark:bg-gray-700'
}`}
style={{
width: 'calc(100% - 40px)',
marginLeft: '20px'
}}
@@ -242,11 +240,10 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
{/* Text label */}
<button
onClick={() => goToStep(index)}
className={`mt-2 text-sm font-medium text-center ${
index <= currentStep
? 'text-blue-600 dark:text-blue-400 cursor-pointer hover:text-blue-800 dark:hover:text-blue-300'
: 'text-gray-400 dark:text-gray-500 cursor-not-allowed'
}`}
className={`mt-2 text-sm font-medium text-center ${index <= currentStep
? 'text-blue-600 dark:text-blue-400 cursor-pointer hover:text-blue-800 dark:hover:text-blue-300'
: 'text-gray-400 dark:text-gray-500 cursor-not-allowed'
}`}
disabled={!canNavigateToStep(index)}
>
{step.name}
@@ -273,11 +270,10 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
<button
onClick={prevStep}
disabled={currentStep === 0}
className={`px-6 py-2 rounded-lg font-medium ${
currentStep === 0
? 'bg-gray-200 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed'
: 'bg-gray-600 dark:bg-gray-700 text-white hover:bg-gray-700 dark:hover:bg-gray-600'
}`}
className={`px-6 py-2 rounded-lg font-medium ${currentStep === 0
? 'bg-gray-200 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed'
: 'bg-gray-600 dark:bg-gray-700 text-white hover:bg-gray-700 dark:hover:bg-gray-600'
}`}
>
Previous
</button>
@@ -285,11 +281,10 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
<button
onClick={nextStep}
disabled={!canProceedToNextStep()}
className={`px-6 py-2 rounded-lg font-medium ${
canProceedToNextStep()
? 'bg-blue-600 dark:bg-blue-500 text-white hover:bg-blue-700 dark:hover:bg-blue-600'
: 'bg-gray-200 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed'
}`}
className={`px-6 py-2 rounded-lg font-medium ${canProceedToNextStep()
? 'bg-blue-600 dark:bg-blue-500 text-white hover:bg-blue-700 dark:hover:bg-blue-600'
: 'bg-gray-200 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed'
}`}
>
Next
</button>
@@ -297,6 +292,7 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
</div>
)}
</div>
<Footer />
</div>
);
}

View File

@@ -33,7 +33,7 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
const updates = {
remoteType: remoteId,
};
// Reset PCB selection when switching remotes
if (remoteId === 'ossm-remote-radr') {
// RADR only available from RAD
@@ -41,10 +41,10 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
} else {
updates.remotePCB = null;
}
// Clear knob selection when switching remotes
updates.remoteKnob = null;
updateConfig(updates);
};
@@ -64,7 +64,7 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
const getAvailableKnobs = () => {
const remoteSystem = getSelectedRemoteSystem();
if (!remoteSystem || !remoteSystem.knobs) return [];
return remoteSystem.knobs.map((knob) => ({
id: knob.id,
name: knob.name,
@@ -88,11 +88,10 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
<button
key={remote.id}
onClick={() => handleRemoteSelect(remote.id)}
className={`p-4 border-2 rounded-lg text-left transition-all w-full ${
isSelected
className={`p-4 border-2 rounded-lg text-left transition-all w-full ${isSelected
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
}`}
>
{imagePath && (
<div className="mb-3 flex justify-center">
@@ -147,21 +146,19 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
<div className="flex gap-4">
<button
onClick={() => handlePCBSelect('rad')}
className={`px-4 py-2 border-2 rounded-lg transition-all ${
selectedRemotePCB === 'rad'
className={`px-4 py-2 border-2 rounded-lg transition-all ${selectedRemotePCB === 'rad'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30 text-blue-900 dark:text-blue-300 font-medium'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}`}
}`}
>
Purchase from RAD
</button>
<button
onClick={() => handlePCBSelect('pcbway')}
className={`px-4 py-2 border-2 rounded-lg transition-all ${
selectedRemotePCB === 'pcbway'
className={`px-4 py-2 border-2 rounded-lg transition-all ${selectedRemotePCB === 'pcbway'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30 text-blue-900 dark:text-blue-300 font-medium'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}`}
}`}
>
Self-source with PCBWay
</button>
@@ -177,11 +174,10 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
<button
key={knob.id}
onClick={() => handleKnobSelect(knob)}
className={`p-4 border-2 rounded-lg text-left transition-all w-full ${
isSelected
className={`p-4 border-2 rounded-lg text-left transition-all w-full ${isSelected
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
}`}
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
@@ -243,7 +239,7 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
{/* Remote Selection */}
<div className="mb-6">
<h3 className="text-lg font-semibold mb-3">Remote System</h3>
<h3 className="text-lg font-semibold mb-3 text-gray-900 dark:text-white">Remote System</h3>
<div className="grid grid-cols-2 gap-4">
{availableRemotesFiltered.map((remote) => renderRemoteCard(remote))}
</div>
@@ -285,9 +281,8 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
)}
</div>
<svg
className={`w-4 h-4 text-gray-500 dark:text-gray-400 transition-transform ${
expandedKnobs ? 'transform rotate-180' : ''
}`}
className={`w-4 h-4 text-gray-500 dark:text-gray-400 transition-transform ${expandedKnobs ? 'transform rotate-180' : ''
}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"

View File

@@ -19,7 +19,7 @@
"pinned_sha": "52537c0896eaef83fd9771dcc633903c7aa6a8ab",
"pinned_raw_url": "https://raw.githubusercontent.com/KinkyMakers/OSSM-hardware/52537c0896eaef83fd9771dcc633903c7aa6a8ab/Printed Parts/Actuator/OSSM - Actuator - Body - Bottom.stl",
"checksum_sha256": "e7abdb99a7e9b9e7408a7b04a7dd50e42cc74510ea2969016a45a2a1387dcde3",
"last_checked": "2026-01-07T01:21:02.027595+00:00",
"last_checked": "2026-01-07T06:30:14.604915+00:00",
"status": "up-to-date"
}
},
@@ -39,7 +39,7 @@
"pinned_sha": "52537c0896eaef83fd9771dcc633903c7aa6a8ab",
"pinned_raw_url": "https://raw.githubusercontent.com/KinkyMakers/OSSM-hardware/52537c0896eaef83fd9771dcc633903c7aa6a8ab/Printed Parts/Actuator/OSSM - Actuator - Body - Middle.stl",
"checksum_sha256": "ce6fb769378636c287af788ce42bdab1f2185dcffba929a0c72598742793b48a",
"last_checked": "2026-01-07T01:21:03.531342+00:00",
"last_checked": "2026-01-07T06:30:22.906540+00:00",
"status": "up-to-date"
}
},
@@ -51,6 +51,9 @@
"timeEstimate": "1h3m",
"colour": "primary",
"required": true,
"Condition": {
"cover.id": "standard-cover"
},
"filePath": "OSSM - Actuator Body Cover.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Actuator/OSSM%20-%20Actuator%20-%20Body%20-%20Cover.stl?raw=true",
"vendor": {
@@ -59,7 +62,7 @@
"pinned_sha": "52537c0896eaef83fd9771dcc633903c7aa6a8ab",
"pinned_raw_url": "https://raw.githubusercontent.com/KinkyMakers/OSSM-hardware/52537c0896eaef83fd9771dcc633903c7aa6a8ab/Printed Parts/Actuator/OSSM - Actuator - Body - Cover.stl",
"checksum_sha256": "bbabc742d2f1753d1b4e21e42c197aec31a4a083b5c634e6e825cec69d4e3258",
"last_checked": "2026-01-07T01:21:02.767604+00:00",
"last_checked": "2026-01-07T06:30:18.689516+00:00",
"status": "up-to-date"
}
},
@@ -99,7 +102,7 @@
"pinned_sha": "52537c0896eaef83fd9771dcc633903c7aa6a8ab",
"pinned_raw_url": "https://raw.githubusercontent.com/KinkyMakers/OSSM-hardware/52537c0896eaef83fd9771dcc633903c7aa6a8ab/Printed Parts/Actuator/OSSM - 24mm Clamping Thread - Belt Clamp.stl",
"checksum_sha256": "457a71bc09cb53f12026fd829bec8fa5b04fdead0788822935780f42c90b9a7a",
"last_checked": "2026-01-07T01:20:58.945151+00:00",
"last_checked": "2026-01-07T06:30:08.525159+00:00",
"status": "up-to-date"
}
},
@@ -119,7 +122,7 @@
"pinned_sha": "52537c0896eaef83fd9771dcc633903c7aa6a8ab",
"pinned_raw_url": "https://raw.githubusercontent.com/KinkyMakers/OSSM-hardware/52537c0896eaef83fd9771dcc633903c7aa6a8ab/Printed Parts/Actuator/OSSM - 24mm Clamping Thread - End Effector.stl",
"checksum_sha256": "4860947b201e2e773b295d33bba09423ae40b4adeef3605d62687f2d40277de1",
"last_checked": "2026-01-07T01:20:59.854476+00:00",
"last_checked": "2026-01-07T06:30:09.547007+00:00",
"status": "up-to-date"
}
},
@@ -139,7 +142,7 @@
"pinned_sha": "52537c0896eaef83fd9771dcc633903c7aa6a8ab",
"pinned_raw_url": "https://raw.githubusercontent.com/KinkyMakers/OSSM-hardware/52537c0896eaef83fd9771dcc633903c7aa6a8ab/Printed Parts/Actuator/OSSM - 24mm Nut - 5 Sided.stl",
"checksum_sha256": "38630c70b2fb929bba9a705dabf5bbd7b49ec882963e042b7108dc74284dd6ff",
"last_checked": "2026-01-07T01:21:00.555525+00:00",
"last_checked": "2026-01-07T06:30:10.564924+00:00",
"status": "up-to-date"
}
}

View File

@@ -51,6 +51,9 @@
"timeEstimate": "2h10m",
"colour": "primary",
"required": true,
"Condition": {
"motor.id": "57AIM30"
},
"filePath": "OSSM - Base - PitClamp Mini - 57AIM30 V1.1.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Mounting/OSSM%20-%20Mounting%20Ring%20-%20PitClamp%20Mini%20-%2057AIM%20V1.1.stl?raw=true",
"vendor": {
@@ -71,6 +74,9 @@
"timeEstimate": "2h10m",
"colour": "primary",
"required": true,
"Condition": {
"motor.id": "42AIM30"
},
"filePath": "OSSM - Base - PitClamp Mini - 42AIM30 V1.1.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Mounting/Non-standard/OSSM%20-%20Mounting%20Ring%20-%20PitClamp%20Mini%20-%2042AIM%20V1.1.stl?raw=true",
"vendor": {
@@ -91,6 +97,9 @@
"timeEstimate": "2h10m",
"colour": "primary",
"required": true,
"Condition": {
"motor.id": "iHSV57"
},
"filePath": "OSSM - Base - PitClamp Mini - iHSV57 V1.1.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Mounting/Non-standard/OSSM%20-%20Mounting%20Ring%20-%20PitClamp%20Mini%20-%20iHSV57.stl?raw=true",
"vendor": {
@@ -185,6 +194,9 @@
"timeEstimate": "5h8m",
"colour": "primary",
"required": true,
"replaces": [
"ossm-actuator-body-middle"
],
"filePath": "OSSM - Actuator Body Middle Pivot.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Actuator/Non-standard/OSSM%20-%20Actuator%20-%20Body%20-%20Middle%20Pivot.stl?raw=true",
"vendor": {

View File

@@ -66,7 +66,7 @@
"pinned_sha": "52537c0896eaef83fd9771dcc633903c7aa6a8ab",
"pinned_raw_url": "https://raw.githubusercontent.com/KinkyMakers/OSSM-hardware/52537c0896eaef83fd9771dcc633903c7aa6a8ab/Printed Parts/Stand/OSSM - Stand - 3030 Extrusion Base - Handle Spacer.stl",
"checksum_sha256": "55ede7dff60a31d68159b352b5f2c63792b7a0dbe9d543a43681c3e52d229115",
"last_checked": "2026-01-07T01:20:58.324330+00:00",
"last_checked": "2026-01-07T06:30:07.525364+00:00",
"status": "up-to-date"
}
}
@@ -187,7 +187,7 @@
"pinned_sha": "52537c0896eaef83fd9771dcc633903c7aa6a8ab",
"pinned_raw_url": "https://raw.githubusercontent.com/KinkyMakers/OSSM-hardware/52537c0896eaef83fd9771dcc633903c7aa6a8ab/Printed Parts/Stand/OSSM - Stand - 3030 Extrusion Base - Extrusion Cap.stl",
"checksum_sha256": "56fa9bb318cdeadc6d1698a1e6cef9371e58b0bc9c7729985bf639d8da2f25da",
"last_checked": "2026-01-07T01:21:01.205246+00:00",
"last_checked": "2026-01-07T06:30:11.578686+00:00",
"status": "up-to-date"
}
}

View File

@@ -1,115 +0,0 @@
{
"toyMounts": {
"category": "Toy Mounts",
"type": "mod",
"printedParts": [
{
"id": "ossm-toy-mount-flange-base-24mm-threaded",
"name": "Toy Mount Flange Base 24mm Threaded",
"description": "Toy mount system",
"filamentEstimate": 46.26,
"timeEstimate": "1h48m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-flange-base-24mm-threaded.stl"
},
{
"id": "ossm-toy-mount-flange-base-dildo-ring-2.5in ",
"name": "Toy Mount Flange Base Dildo Ring 2.5in",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-flange-base-dildo-ring-2_5in.stl"
},
{
"id": "ossm-toy-mount-flange-base-dildo-ring-2in",
"name": "Toy Mount Flange Base Dildo Ring 2in",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-flange-base-dildo-ring-2in.stl"
},
{
"id": "ossm-toy-mount-double-double-24mm-threaded",
"name": "Toy Mount Double Double 24mm Threaded",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-double-double-24mm-threaded.stl"
},
{
"id": "ossm-toy-mount-double-double-rail-mounted",
"name": "Toy Mount Double Double Rail Mounted",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "primary",
"required": true,
"filePath": "ossm-toy-mount-double-double-rail-mounted.stl"
},
{
"id": "ossm-toy-mount-sucson-mount-base-plate-24mm-threaded",
"name": "Toy Mount Sucson Mount Base Plate 24mm Threaded",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-sucson-mount-base-plate-24mm-threaded.stl"
},
{
"id": "ossm-toy-mount-sucson-mount-ring-insert-55mm",
"name": "Toy Mount Sucson Mount Ring Insert 55mm",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "primary",
"required": true,
"filePath": "ossm-toy-mount-sucson-mount-ring-insert-55mm.stl"
},
{
"id": "ossm-toy-mount-sucson-mount-threaded-ring",
"name": "Toy Mount Sucson Mount Threaded Ring",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-sucson-mount-threaded-ring.stl"
},
{
"id": "ossm-toy-mount-tie-down-and-suction-plate-110mm",
"name": "Toy Mount Tie Down and Suction Plate 110mm",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-tie-down-and-suction-plate-110mm.stl"
},
{
"id": "ossm-toy-mount-tie-down-and-suction-plate-135mm",
"name": "Toy Mount Tie Down and Suction Plate 135mm",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-tie-down-and-suction-plate-135mm.stl"
}
],
"hardwareParts": [
{
"id": "toy-mount-hardware",
"required": true,
"relatedParts": []
}
]
}
}

View File

@@ -0,0 +1,14 @@
import ossm from './ossm.json';
const rawParts = [ossm];
const mergedToyMounts = {
category: "Toy Mounts",
type: "mod",
printedParts: rawParts.flatMap(v => v.printedParts || []),
hardwareParts: rawParts.flatMap(v => v.hardwareParts || [])
};
export default {
toyMounts: mergedToyMounts
};

View File

@@ -0,0 +1,211 @@
{
"printedParts": [
{
"id": "ossm-toy-mount-flange-base-24mm-threaded",
"name": "Toy Mount Flange Base 24mm Threaded",
"description": "Toy mount system",
"filamentEstimate": 46.26,
"timeEstimate": "1h48m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-flange-base-24mm-threaded.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Flange%20Base%2024mm%20Threaded.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-flange-base-24mm-threaded",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Flange Base 24mm Threaded.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
},
{
"id": "ossm-toy-mount-flange-base-dildo-ring-2.5in ",
"name": "Toy Mount Flange Base Dildo Ring 2.5in",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-flange-base-dildo-ring-2_5in.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Flange%20Base%20Dildo%20Ring%202.5in.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-flange-base-dildo-ring-2.5in ",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Flange Base Dildo Ring 2.5in.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
},
{
"id": "ossm-toy-mount-flange-base-dildo-ring-2in",
"name": "Toy Mount Flange Base Dildo Ring 2in",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-flange-base-dildo-ring-2in.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Flange%20Base%20Dildo%20Ring%202in.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-flange-base-dildo-ring-2in",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Flange Base Dildo Ring 2in.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
},
{
"id": "ossm-toy-mount-double-double-24mm-threaded",
"name": "Toy Mount Double Double 24mm Threaded",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-double-double-24mm-threaded.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Double%20Double%2024mm%20Threaded.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-double-double-24mm-threaded",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Double Double 24mm Threaded.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
},
{
"id": "ossm-toy-mount-double-double-rail-mounted",
"name": "Toy Mount Double Double Rail Mounted",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "primary",
"required": true,
"filePath": "ossm-toy-mount-double-double-rail-mounted.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Double%20Double%20Rail%20Mounted.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-double-double-rail-mounted",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Double Double Rail Mounted.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
},
{
"id": "ossm-toy-mount-sucson-mount-base-plate-24mm-threaded",
"name": "Toy Mount Sucson Mount Base Plate 24mm Threaded",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-sucson-mount-base-plate-24mm-threaded.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Sucson%20Mount%20Base%20Plate%2024mm%20Threaded.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-sucson-mount-base-plate-24mm-threaded",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Sucson Mount Base Plate 24mm Threaded.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
},
{
"id": "ossm-toy-mount-sucson-mount-ring-insert-55mm",
"name": "Toy Mount Sucson Mount Ring Insert 55mm",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "primary",
"required": true,
"filePath": "ossm-toy-mount-sucson-mount-ring-insert-55mm.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Sucson%20Mount%20Ring%20Insert%2055mm.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-sucson-mount-ring-insert-55mm",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Sucson Mount Ring Insert 55mm.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
},
{
"id": "ossm-toy-mount-sucson-mount-threaded-ring",
"name": "Toy Mount Sucson Mount Threaded Ring",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-sucson-mount-threaded-ring.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Sucson%20Mount%20Threaded%20Ring.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-sucson-mount-threaded-ring",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Sucson Mount Threaded Ring.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
},
{
"id": "ossm-toy-mount-tie-down-and-suction-plate-110mm",
"name": "Toy Mount Tie Down and Suction Plate 110mm",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-tie-down-and-suction-plate-110mm.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Tie%20Down%20and%20Suction%20Plate%20110mm.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-tie-down-and-suction-plate-110mm",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Tie Down and Suction Plate 110mm.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
},
{
"id": "ossm-toy-mount-tie-down-and-suction-plate-135mm",
"name": "Toy Mount Tie Down and Suction Plate 135mm",
"description": "Toy mount system",
"filamentEstimate": 15.24,
"timeEstimate": "55m",
"colour": "secondary",
"required": true,
"filePath": "ossm-toy-mount-tie-down-and-suction-plate-135mm.stl",
"url": "https://action.github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Toy%20Mounts/OSSM%20-%20Toy%20Mount%20Tie%20Down%20and%20Suction%20Plate%20135mm.stl?raw=true",
"vendor": {
"manifest_id": "ossm-toy-mount-tie-down-and-suction-plate-135mm",
"local_path": "vendor/KinkyMakers-OSSM-hardware/Printed Parts/Toy Mounts/OSSM - Toy Mount Tie Down and Suction Plate 135mm.stl",
"pinned_sha": null,
"pinned_raw_url": null,
"checksum_sha256": null,
"last_checked": null,
"status": "pending"
}
}
],
"hardwareParts": [
{
"id": "toy-mount-hardware",
"required": true,
"relatedParts": []
}
]
}

View File

@@ -1,12 +1,12 @@
import motors from './motors.json';
import powerSupplies from './powerSupplies.json';
import optionsData from './options.json';
import colors from './colors.json';
import hardwareData from './hardware.json';
import motors from './components/motors.json';
import powerSupplies from './components/powerSupplies.json';
import optionsData from './config/options.json';
import colors from './common/colors.json';
import hardwareData from './common/hardware.json';
import actuatorComponents from './components/actuator.json';
import standComponents from './components/stand.json';
import mountingComponents from './components/mounting.json';
import toyMountsComponents from './components/toyMounts.json';
import toyMountsComponents from './components/toyMounts/index.js';
import remoteComponents from './components/remote.json';
import pcbComponents from './components/pcb.json';
@@ -21,10 +21,10 @@ Object.values(hardwareData).forEach((category) => {
// Function to resolve hardware references (IDs) to full hardware definitions
const resolveHardwareReferences = (components) => {
const resolvedComponents = {};
Object.entries(components).forEach(([componentKey, component]) => {
resolvedComponents[componentKey] = { ...component };
// Resolve hardwareParts if they exist
if (component.hardwareParts) {
resolvedComponents[componentKey].hardwareParts = component.hardwareParts.map((hw) => {
@@ -51,7 +51,7 @@ const resolveHardwareReferences = (components) => {
};
});
}
// Also resolve hardwareParts in systems
if (component.systems) {
resolvedComponents[componentKey].systems = {};
@@ -82,7 +82,7 @@ const resolveHardwareReferences = (components) => {
});
}
});
return resolvedComponents;
};
@@ -133,7 +133,7 @@ const convertComponentPartsToOptions = (componentIds, componentData) => {
});
return allKnobs;
}
// New structure: systems with printedParts and hardwareParts (for hinges, etc.)
return componentIds
.map((systemId) => {
@@ -142,12 +142,12 @@ const convertComponentPartsToOptions = (componentIds, componentData) => {
console.warn(`Component system not found: ${systemId}`);
return null;
}
// Calculate total filament estimate from printed parts
const totalFilament = (system.printedParts || system.bodyParts)?.reduce((sum, part) => {
return sum + (part.filamentEstimate || 0);
}, 0) || 0;
return {
id: systemId,
name: system.name,
@@ -176,7 +176,7 @@ const convertComponentPartsToOptions = (componentIds, componentData) => {
console.warn(`Component part not found: ${componentId}`);
return null;
}
return {
id: part.id,
name: part.name,
@@ -196,24 +196,24 @@ const convertComponentPartsToOptions = (componentIds, componentData) => {
// Merge component options into options
const processOptions = (options, componentsData) => {
const processedOptions = { ...options };
// Process each option category
Object.keys(processedOptions).forEach((optionKey) => {
const optionCategory = processedOptions[optionKey];
if (!optionCategory || !optionCategory.sections) return;
const sections = { ...optionCategory.sections };
const categoryUseComponents = optionCategory.useComponents;
// Convert component parts to options format for each section
Object.keys(sections).forEach((sectionKey) => {
const section = sections[sectionKey];
// Check if section has componentIds to process
if (section.componentIds !== undefined) {
// Determine which component category to use
const componentKey = section.useComponents || categoryUseComponents;
if (componentKey) {
const componentData = componentsData[componentKey];
const options = convertComponentPartsToOptions(section.componentIds, componentData);
@@ -224,19 +224,19 @@ const processOptions = (options, componentsData) => {
console.warn(`No useComponents specified for ${optionKey}.${sectionKey}`);
section.options = [];
}
// Clean up temporary properties
delete section.componentIds;
delete section.useComponents;
}
});
processedOptions[optionKey].sections = sections;
if (categoryUseComponents) {
delete processedOptions[optionKey].useComponents;
}
});
return processedOptions;
};

View File

@@ -3,10 +3,10 @@ import ExcelJS from 'exceljs';
// Generate markdown overview
export const generateMarkdownOverview = (config, printedParts, hardwareParts, filamentTotals, totalTime, total) => {
const md = [];
md.push('# OSSM Build Configuration');
md.push(`\n**Generated:** ${new Date().toLocaleString()}\n`);
// Motor
if (config.motor) {
md.push(`## Motor: ${config.motor.name}`);
@@ -15,38 +15,38 @@ export const generateMarkdownOverview = (config, printedParts, hardwareParts, fi
md.push(`- **Wattage:** ${config.motor.wattage}`);
md.push('');
}
// Power Supply
if (config.powerSupply) {
md.push(`## Power Supply: ${config.powerSupply.name}`);
md.push(`- **Price:** ${config.powerSupply.price}`);
md.push('');
}
// Colors
md.push(`## Colors`);
md.push(`- **Primary:** ${config.primaryColor || 'Not selected'}`);
md.push(`- **Accent:** ${config.accentColor || 'Not selected'}`);
md.push('');
// Mount
if (config.mount) {
md.push(`## Mount: ${config.mount.name}`);
md.push('');
}
// Cover
if (config.cover) {
md.push(`## Cover: ${config.cover.name}`);
md.push('');
}
// PCB Mount
if (config.pcbMount) {
md.push(`## PCB Mount: ${config.pcbMount.name}`);
md.push('');
}
// Stand Options
if (config.standHinge || config.standFeet || config.standCrossbarSupports?.length > 0) {
md.push(`## Stand Options`);
@@ -57,7 +57,7 @@ export const generateMarkdownOverview = (config, printedParts, hardwareParts, fi
}
md.push('');
}
// Remote
if (config.remote || config.remoteKnob) {
md.push(`## Remote`);
@@ -65,7 +65,7 @@ export const generateMarkdownOverview = (config, printedParts, hardwareParts, fi
if (config.remoteKnob) md.push(`- **Knob:** ${config.remoteKnob.name}`);
md.push('');
}
// Filament Summary
if (filamentTotals.total > 0) {
md.push(`## Filament Summary`);
@@ -75,12 +75,12 @@ export const generateMarkdownOverview = (config, printedParts, hardwareParts, fi
md.push(`- **Estimated Print Time:** ${totalTime}`);
md.push('');
}
// Print Parts Summary
if (printedParts.length > 0) {
md.push(`## Printed Parts Summary`);
md.push(`- **Total Parts:** ${printedParts.length}`);
// Group by category
const partsByCategory = {};
printedParts.forEach(part => {
@@ -90,7 +90,7 @@ export const generateMarkdownOverview = (config, printedParts, hardwareParts, fi
}
partsByCategory[category].push(part);
});
Object.entries(partsByCategory).forEach(([category, parts]) => {
md.push(`### ${category} (${parts.length} parts)`);
parts.forEach(part => {
@@ -99,31 +99,31 @@ export const generateMarkdownOverview = (config, printedParts, hardwareParts, fi
});
md.push('');
}
// Hardware Summary
if (hardwareParts.length > 0) {
md.push(`## Hardware Summary`);
md.push(`- **Total Items:** ${hardwareParts.length}`);
md.push('');
}
// Cost Summary
if (total > 0) {
md.push(`## Estimated Cost`);
md.push(`- **Total:** $${total.toFixed(2)}`);
md.push('');
}
return md.join('\n');
};
// Generate Excel BOM with purchase links
export const generateExcelBOM = (hardwareParts, printedParts, config) => {
const rows = [];
// Header
rows.push(['Item', 'Name', 'Quantity', 'Price', 'Link', 'Category', 'Type']);
// Add motor
if (config.motor) {
const motorLinks = config.motor.links || [];
@@ -138,7 +138,7 @@ export const generateExcelBOM = (hardwareParts, printedParts, config) => {
'Hardware'
]);
}
// Add power supply
if (config.powerSupply) {
const psuLinks = config.powerSupply.links || [];
@@ -153,7 +153,7 @@ export const generateExcelBOM = (hardwareParts, printedParts, config) => {
'Hardware'
]);
}
// Add hardware parts
hardwareParts.forEach(hw => {
const links = hw.links || [];
@@ -168,7 +168,7 @@ export const generateExcelBOM = (hardwareParts, printedParts, config) => {
'Hardware'
]);
});
// Add printed parts (for reference, not purchase)
printedParts.forEach(part => {
rows.push([
@@ -181,14 +181,14 @@ export const generateExcelBOM = (hardwareParts, printedParts, config) => {
'Printed Part'
]);
});
// Create workbook and worksheet
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('BOM');
// Add rows
worksheet.addRows(rows);
// Set column widths
worksheet.columns = [
{ width: 30 }, // Item
@@ -199,42 +199,42 @@ export const generateExcelBOM = (hardwareParts, printedParts, config) => {
{ width: 20 }, // Category
{ width: 15 } // Type
];
return workbook;
};
// Generate Excel Print List with completion tracker
export const generateExcelPrintList = (printedParts, filamentTotals) => {
const rows = [];
// Header
rows.push(['Part Name', 'Category', 'Color', 'Quantity', 'Filament (g)', 'Print Time', 'Status', 'Completed']);
// Group parts by category and color
const partsByCategoryColor = {};
printedParts.forEach(part => {
const category = part.category || 'Other';
const color = part.colour === 'primary' ? 'Primary' : part.colour === 'secondary' ? 'Accent' : 'Other';
const key = `${category}_${color}`;
if (!partsByCategoryColor[key]) {
partsByCategoryColor[key] = [];
}
partsByCategoryColor[key].push(part);
});
// Sort by category, then color
const sortedKeys = Object.keys(partsByCategoryColor).sort();
sortedKeys.forEach(key => {
const parts = partsByCategoryColor[key];
const [category, color] = key.split('_');
parts.forEach(part => {
const filament = typeof part.filamentEstimate === 'number'
? part.filamentEstimate
const filament = typeof part.filamentEstimate === 'number'
? part.filamentEstimate
: parseFloat(part.filamentEstimate?.replace('~', '').replace('g', '')) || 0;
rows.push([
part.name || part.id || '',
category,
@@ -247,18 +247,18 @@ export const generateExcelPrintList = (printedParts, filamentTotals) => {
]);
});
});
// Add summary row
rows.push([]);
rows.push(['TOTAL', '', '', printedParts.length, filamentTotals.total.toFixed(2), '', '', '']);
// Create workbook and worksheet
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Print List');
// Add rows
worksheet.addRows(rows);
// Set column widths
worksheet.columns = [
{ width: 40 }, // Part Name
@@ -270,18 +270,18 @@ export const generateExcelPrintList = (printedParts, filamentTotals) => {
{ width: 15 }, // Status
{ width: 12 } // Completed
];
// Create a summary sheet with progress calculation
const summaryWorksheet = workbook.addWorksheet('Summary');
// Add summary rows
summaryWorksheet.getCell('A1').value = 'Print Progress Summary';
summaryWorksheet.getCell('A3').value = 'Total Parts';
summaryWorksheet.getCell('B3').value = printedParts.length;
summaryWorksheet.getCell('A4').value = 'Completed Parts';
summaryWorksheet.getCell('B4').formula = `COUNTIF('Print List'.H:H,"✓")`;
summaryWorksheet.getCell('B4').value = { formula: `COUNTIF('Print List'.H:H,"✓")` };
summaryWorksheet.getCell('A5').value = 'Progress %';
summaryWorksheet.getCell('B5').formula = `IF(B3>0, (B4/B3)*100, 0)`;
summaryWorksheet.getCell('B5').value = { formula: `IF(B3>0, (B4/B3)*100, 0)` };
summaryWorksheet.getCell('A7').value = 'Filament Summary';
summaryWorksheet.getCell('A8').value = 'Total Filament (g)';
summaryWorksheet.getCell('B8').value = filamentTotals.total.toFixed(2);
@@ -289,12 +289,12 @@ export const generateExcelPrintList = (printedParts, filamentTotals) => {
summaryWorksheet.getCell('B9').value = filamentTotals.primary.toFixed(2);
summaryWorksheet.getCell('A10').value = 'Accent Color (g)';
summaryWorksheet.getCell('B10').value = (filamentTotals.secondary || 0).toFixed(2);
// Set column widths for summary sheet
summaryWorksheet.columns = [
{ width: 25 },
{ width: 15 }
];
return workbook;
};