diff --git a/exploded-diagrams/css/main.css b/exploded-diagrams/css/main.css new file mode 100644 index 000000000..970072f26 --- /dev/null +++ b/exploded-diagrams/css/main.css @@ -0,0 +1,288 @@ +* { margin: 0; padding: 0; box-sizing: border-box; } + +:root { + --bg-dark: #0f0f1a; + --bg-panel: rgba(255,255,255,0.03); + --border: rgba(255,255,255,0.06); + --accent: #00d4ff; + --accent-alt: #00ff88; + --text: #fff; + --text-muted: #888; + --text-dim: #666; +} + +body { + font-family: 'Segoe UI', system-ui, sans-serif; + background: linear-gradient(135deg, var(--bg-dark) 0%, #1a1a2e 50%, #0f1a2e 100%); + min-height: 100vh; + color: var(--text); +} + +#app { + max-width: 1400px; + margin: 0 auto; + padding: 20px; +} + +header { + text-align: center; + padding: 15px 0 20px; +} + +h1 { + font-size: 2rem; + font-weight: 300; + letter-spacing: 3px; + background: linear-gradient(90deg, var(--accent), var(--accent-alt), #7b2cbf); + background-size: 200% auto; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: gradientShift 3s ease infinite; +} + +@keyframes gradientShift { + 0%, 100% { background-position: 0% center; } + 50% { background-position: 100% center; } +} + +.subtitle { color: var(--text-dim); font-size: 0.85rem; letter-spacing: 1px; } + +/* Breadcrumb */ +#breadcrumb { + display: flex; + gap: 8px; + align-items: center; + padding: 10px 15px; + background: var(--bg-panel); + border-radius: 8px; + margin-bottom: 15px; + font-size: 0.85rem; + flex-wrap: wrap; +} + +#breadcrumb a { + color: var(--accent); + text-decoration: none; + transition: opacity 0.2s; +} + +#breadcrumb a:hover { opacity: 0.7; } +#breadcrumb span { color: var(--text-dim); } +#breadcrumb .current { color: var(--text); font-weight: 500; } + +/* Controls */ +.controls { + display: flex; + gap: 15px; + justify-content: center; + flex-wrap: wrap; + margin-bottom: 20px; + padding: 18px 25px; + background: var(--bg-panel); + border-radius: 16px; + border: 1px solid var(--border); +} + +.control-group { + display: flex; + flex-direction: column; + gap: 5px; +} + +.control-group label { + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 1.5px; + color: var(--text-muted); +} + +input[type="range"] { + width: 140px; + height: 4px; + -webkit-appearance: none; + background: linear-gradient(90deg, #333, #444); + border-radius: 2px; + outline: none; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + background: linear-gradient(135deg, var(--accent), var(--accent-alt)); + border-radius: 50%; + cursor: pointer; + box-shadow: 0 0 10px rgba(0, 212, 255, 0.5); +} + +select, button { + padding: 10px 18px; + border: none; + border-radius: 8px; + font-size: 0.85rem; + cursor: pointer; + transition: all 0.3s; +} + +select { + background: rgba(40, 40, 60, 0.8); + color: var(--text); + min-width: 170px; + border: 1px solid rgba(255,255,255,0.1); +} + +select:hover { border-color: rgba(0, 212, 255, 0.3); } + +button { + background: linear-gradient(135deg, rgba(0,212,255,0.2), rgba(123,44,191,0.2)); + color: var(--text); + font-weight: 500; + border: 1px solid rgba(0,212,255,0.3); +} + +button:hover { + background: linear-gradient(135deg, rgba(0,212,255,0.4), rgba(123,44,191,0.4)); + transform: translateY(-1px); + box-shadow: 0 5px 20px rgba(0, 212, 255, 0.2); +} + +/* Main layout */ +main { + display: grid; + grid-template-columns: 1fr 280px; + gap: 20px; +} + +@media (max-width: 1000px) { + main { grid-template-columns: 1fr; } + #sidebar { order: -1; } +} + +.diagram-container { + background: #fafafa; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 20px 60px rgba(0,0,0,0.4); +} + +#diagram { width: 100%; height: auto; display: block; } + +/* Sidebar */ +#sidebar { + display: flex; + flex-direction: column; + gap: 15px; +} + +.panel { + background: var(--bg-panel); + border-radius: 12px; + padding: 15px; + border: 1px solid var(--border); +} + +.panel h3 { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 1.5px; + color: var(--accent); + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid rgba(255,255,255,0.1); +} + +.stats-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +.stat { + text-align: center; + padding: 10px; + background: rgba(0,0,0,0.2); + border-radius: 8px; +} + +.stat-value { font-size: 1.4rem; font-weight: 600; color: var(--accent); } +.stat-label { font-size: 0.65rem; text-transform: uppercase; color: var(--text-dim); letter-spacing: 1px; } + +.parts-list { max-height: 300px; overflow-y: auto; } + +.part-item { + padding: 8px 10px; + margin-bottom: 4px; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + display: flex; + justify-content: space-between; + align-items: center; +} + +.part-item:hover { background: rgba(0, 212, 255, 0.1); } +.part-item.highlighted { background: rgba(0, 212, 255, 0.2); border: 1px solid rgba(0, 212, 255, 0.3); } +.part-item .name { font-size: 0.8rem; color: #ddd; } +.part-item .pn { font-size: 0.7rem; color: var(--text-dim); font-family: monospace; } + +.legend-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; } +.legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.7rem; color: var(--text-muted); } +.legend-color { width: 12px; height: 12px; border-radius: 2px; flex-shrink: 0; } + +/* Tooltip */ +.tooltip { + position: fixed; + background: rgba(10, 10, 20, 0.95); + padding: 12px 18px; + border-radius: 8px; + pointer-events: none; + z-index: 1000; + display: none; + border: 1px solid rgba(0, 212, 255, 0.3); + backdrop-filter: blur(10px); + max-width: 280px; +} + +.tooltip.visible { display: block; } +.tooltip h4 { font-size: 0.95rem; color: var(--accent); margin-bottom: 4px; } +.tooltip .details { font-size: 0.75rem; color: var(--text-muted); line-height: 1.5; } +.tooltip .material { + display: inline-block; + padding: 2px 8px; + background: rgba(255,255,255,0.1); + border-radius: 4px; + margin-top: 6px; + font-size: 0.7rem; +} + +/* Scrollbar */ +.parts-list::-webkit-scrollbar { width: 4px; } +.parts-list::-webkit-scrollbar-track { background: rgba(255,255,255,0.05); border-radius: 2px; } +.parts-list::-webkit-scrollbar-thumb { background: rgba(0, 212, 255, 0.3); border-radius: 2px; } + +/* Package browser */ +.package-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 15px; +} + +.package-card { + background: var(--bg-panel); + border: 1px solid var(--border); + border-radius: 12px; + padding: 20px; + cursor: pointer; + transition: all 0.3s; +} + +.package-card:hover { + border-color: var(--accent); + transform: translateY(-2px); + box-shadow: 0 10px 30px rgba(0, 212, 255, 0.1); +} + +.package-card h4 { color: var(--text); margin-bottom: 5px; } +.package-card p { font-size: 0.8rem; color: var(--text-muted); } +.package-card .meta { font-size: 0.7rem; color: var(--text-dim); margin-top: 10px; } diff --git a/exploded-diagrams/index.html b/exploded-diagrams/index.html new file mode 100644 index 000000000..a1035fbc8 --- /dev/null +++ b/exploded-diagrams/index.html @@ -0,0 +1,32 @@ + + + + + + Exploded Diagram Viewer + + + +
+
+

EXPLODED DIAGRAMS

+

Modular Technical Illustrations

+
+ + + +
+ +
+
+ +
+ +
+ +
+
+ + + + diff --git a/exploded-diagrams/js/DiagramRenderer.js b/exploded-diagrams/js/DiagramRenderer.js new file mode 100644 index 000000000..67e9ba40b --- /dev/null +++ b/exploded-diagrams/js/DiagramRenderer.js @@ -0,0 +1,334 @@ +export class DiagramRenderer { + constructor(svgElement, assemblyData, materials) { + this.svg = svgElement; + this.assembly = assemblyData; + this.materials = materials; + this.explosion = 50; + this.rotation = 0; + this.highlightedPart = null; + this.animating = false; + this.gradientIds = {}; + } + + createDefs() { + let defs = ''; + + // Create gradients from materials + Object.entries(this.materials).forEach(([id, mat]) => { + const gradId = `grad-${id}`; + this.gradientIds[id] = gradId; + const g = mat.gradient; + const angle = g.angle || 0; + const rad = angle * Math.PI / 180; + const x2 = Math.round(50 + Math.cos(rad) * 50); + const y2 = Math.round(50 + Math.sin(rad) * 50); + + defs += ``; + g.stops.forEach(s => { + defs += ``; + }); + defs += ''; + }); + + // Filters + defs += ` + + + + + + + + + + `; + + defs += ''; + return defs; + } + + getFill(geo, partMaterial) { + if (geo.fill) return geo.fill; + const mat = geo.material || partMaterial; + return mat ? `url(#grad-${mat})` : '#888'; + } + + renderGeometry(geo, cx, cy, partMaterial) { + const ox = geo.offsetX || 0; + const oy = geo.offsetY || 0; + const x = cx + ox; + const y = cy + oy; + const fill = this.getFill(geo, partMaterial); + const opacity = geo.opacity !== undefined ? `opacity="${geo.opacity}"` : ''; + + switch (geo.type) { + case 'circle': + return ``; + + case 'ellipse': + return ``; + + case 'rect': { + const rx = geo.rx || 0; + return ``; + } + + case 'line': + return ``; + + case 'polygon': { + const pts = []; + for (let i = 0; i < geo.points.length; i += 2) { + pts.push(`${x + geo.points[i]},${y + geo.points[i+1]}`); + } + const strokeAttr = geo.stroke ? `stroke="${geo.stroke}" stroke-width="${geo.strokeWidth || 1}"` : ''; + const fillAttr = geo.fill === 'none' ? 'fill="none"' : `fill="${fill}"`; + return ``; + } + + case 'cylinder': { + const { rx, ry, height } = geo; + const topY = y - height/2; + const botY = y + height/2; + return ` + + + + `; + } + + case 'cone': { + const { topRx, topRy, bottomRx, bottomRy, height } = geo; + const topY = y - height/2; + const botY = y + height/2; + return ` + + + + `; + } + + case 'coilSpring': { + const { coils, rx, ry, pitch, strokeWidth } = geo; + let svg = ''; + for (let i = 0; i < coils; i++) { + const cy2 = y + i * pitch; + svg += ``; + } + return svg; + } + + case 'gearRing': { + const { teeth, outerRadius, toothHeight } = geo; + let svg = ''; + for (let i = 0; i < teeth; i++) { + const angle = (i / teeth) * Math.PI * 2; + const tx = x + Math.cos(angle) * outerRadius; + const ty = y + Math.sin(angle) * (outerRadius * 0.35); + const rot = (i / teeth) * 360; + svg += ``; + } + return svg; + } + + case 'radialRects': { + const { count, radius, width, height, offsetY: oy2 = 0 } = geo; + const rectFill = geo.material ? `url(#grad-${geo.material})` : (geo.fill || fill); + let svg = ''; + for (let i = 0; i < count; i++) { + const angle = (i / count) * Math.PI * 2; + const rx2 = x + Math.cos(angle) * radius; + svg += ``; + } + return svg; + } + + case 'radialBlades': { + const { count, radius, width, height, curve = 0, offsetY: oy2 = 0 } = geo; + let svg = ''; + for (let i = 0; i < count; i++) { + const angle = (i / count) * Math.PI * 2; + const bx = x + Math.cos(angle) * radius; + const rot = (i / count) * 360 + curve; + svg += ``; + } + return svg; + } + + case 'text': + return `${geo.content}`; + + default: + console.warn(`Unknown geometry type: ${geo.type}`); + return ''; + } + } + + render() { + const { assembly } = this; + const centerX = 350; + const baseOffset = 90; + const maxExplosion = 70; + const explosionFactor = this.explosion / 100; + + let svg = this.createDefs(); + + // Background + svg += ``; + + // Title + svg += ` + + ${assembly.name.toUpperCase()} + + + ${assembly.description || ''} + + + `; + + // Axis line + svg += ``; + + // Render parts + assembly.parts.forEach((part, index) => { + const explosionOffset = index * maxExplosion * explosionFactor; + const baseY = baseOffset + part.baseY * 0.7 + explosionOffset; + + const isHighlighted = this.highlightedPart === part.id; + const filter = isHighlighted ? 'url(#glow)' : 'url(#dropShadow)'; + const transform = this.rotation !== 0 ? `transform="rotate(${this.rotation}, ${centerX}, ${baseY})"` : ''; + + svg += ``; + + part.geometry.forEach(geo => { + svg += this.renderGeometry(geo, centerX, baseY, part.material); + }); + + svg += ''; + + // Leader line and label + const side = index % 2 === 0 ? 1 : -1; + const labelX = centerX + side * 160; + const lineStartX = centerX + side * 50; + + svg += ` + + + ${part.name} + ${part.partNumber} + `; + }); + + // Footer + const totalWeight = assembly.parts.reduce((sum, p) => sum + (p.weight * p.quantity), 0); + svg += ` + + ${assembly.parts.length} unique parts • ${totalWeight.toFixed(1)}g total weight + + `; + + this.svg.innerHTML = svg; + this.attachListeners(); + } + + attachListeners() { + const tooltip = document.getElementById('tooltip'); + + this.svg.querySelectorAll('.part').forEach(el => { + const partId = el.dataset.part; + const part = this.assembly.parts.find(p => p.id === partId); + + el.addEventListener('mouseenter', (e) => { + this.highlightPart(partId); + + if (part) { + const mat = this.materials[part.material]; + tooltip.innerHTML = ` +

${part.name}

+
+
${part.partNumber} × ${part.quantity}
+
${part.weight}g each
+ ${mat?.name || part.material} +
+ `; + tooltip.classList.add('visible'); + } + }); + + el.addEventListener('mousemove', (e) => { + tooltip.style.left = (e.clientX + 15) + 'px'; + tooltip.style.top = (e.clientY - 10) + 'px'; + }); + + el.addEventListener('mouseleave', () => { + this.highlightPart(null); + tooltip.classList.remove('visible'); + }); + }); + } + + highlightPart(partId) { + this.highlightedPart = partId; + this.render(); + + // Also highlight in parts list + document.querySelectorAll('.part-item').forEach(el => { + el.classList.toggle('highlighted', el.dataset.part === partId); + }); + } + + setExplosion(val) { + this.explosion = val; + this.render(); + } + + setRotation(val) { + this.rotation = val; + this.render(); + } + + animate() { + if (this.animating) return; + this.animating = true; + + const start = this.explosion; + const target = start < 50 ? 100 : 0; + const duration = 1200; + const startTime = performance.now(); + + const tick = (now) => { + const elapsed = now - startTime; + const progress = Math.min(elapsed / duration, 1); + const eased = 1 - Math.pow(1 - progress, 3); + + this.explosion = start + (target - start) * eased; + + // Update slider + const slider = document.getElementById('explosionSlider'); + const label = document.getElementById('explosionValue'); + if (slider) slider.value = this.explosion; + if (label) label.textContent = Math.round(this.explosion); + + this.render(); + + if (progress < 1) { + requestAnimationFrame(tick); + } else { + this.animating = false; + } + }; + + requestAnimationFrame(tick); + } + + exportSVG(filename = 'diagram') { + const blob = new Blob([this.svg.outerHTML], { type: 'image/svg+xml' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${filename}-exploded.svg`; + a.click(); + URL.revokeObjectURL(url); + } +} diff --git a/exploded-diagrams/js/PackageLoader.js b/exploded-diagrams/js/PackageLoader.js new file mode 100644 index 000000000..f0fe8e6be --- /dev/null +++ b/exploded-diagrams/js/PackageLoader.js @@ -0,0 +1,72 @@ +export class PackageLoader { + constructor(basePath = 'packages') { + this.basePath = basePath; + this.cache = new Map(); + this.materials = null; + } + + async fetchJSON(path) { + if (this.cache.has(path)) { + return this.cache.get(path); + } + + const response = await fetch(path); + if (!response.ok) { + throw new Error(`Failed to load ${path}: ${response.status}`); + } + + const data = await response.json(); + this.cache.set(path, data); + return data; + } + + async getCategories() { + const index = await this.fetchJSON(`${this.basePath}/index.json`); + return index.categories; + } + + async getManufacturers(category) { + const index = await this.fetchJSON(`${this.basePath}/${category}/index.json`); + return index.manufacturers; + } + + async getProducts(category, manufacturer) { + const index = await this.fetchJSON(`${this.basePath}/${category}/${manufacturer}/index.json`); + return index.products; + } + + async loadProductPackage(category, manufacturer, product) { + const pkgPath = `${this.basePath}/${category}/${manufacturer}/${product}`; + return await this.fetchJSON(`${pkgPath}/package.json`); + } + + async loadAssembly(category, manufacturer, product, assembly) { + const basePath = `${this.basePath}/${category}/${manufacturer}/${product}/${assembly}`; + + // Load assembly manifest + const manifest = await this.fetchJSON(`${basePath}/assembly.json`); + + // Load shared materials if not already loaded + if (!this.materials) { + this.materials = await this.fetchJSON(`${this.basePath}/materials.json`); + } + + // Load each part + const parts = await Promise.all( + manifest.parts.map(async (partRef) => { + const partData = await this.fetchJSON(`${basePath}/parts/${partRef}.json`); + return partData; + }) + ); + + return { + ...manifest, + parts + }; + } + + async loadPart(category, manufacturer, product, assembly, partId) { + const path = `${this.basePath}/${category}/${manufacturer}/${product}/${assembly}/parts/${partId}.json`; + return await this.fetchJSON(path); + } +} diff --git a/exploded-diagrams/js/UIController.js b/exploded-diagrams/js/UIController.js new file mode 100644 index 000000000..82f560dd0 --- /dev/null +++ b/exploded-diagrams/js/UIController.js @@ -0,0 +1,244 @@ +export class UIController { + constructor(app) { + this.app = app; + this.controlsEl = document.getElementById('controls'); + this.sidebarEl = document.getElementById('sidebar'); + this.breadcrumbEl = document.getElementById('breadcrumb'); + this.diagramContainer = document.querySelector('.diagram-container'); + + // Callbacks + this.onExplosionChange = null; + this.onRotationChange = null; + this.onAnimate = null; + this.onExport = null; + this.onPartHover = null; + } + + updateBreadcrumb(path) { + const labels = { + 'rc': 'RC Vehicles', + 'traxxas': 'Traxxas', + 'slash-4x4': 'Slash 4x4', + 'front-shock': 'Front Shock', + 'rear-differential': 'Rear Differential', + 'steering-servo': 'Steering Servo' + }; + + let html = `Home`; + + path.forEach((segment, i) => { + const subPath = path.slice(0, i + 1).join('/'); + const label = labels[segment] || segment.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); + + html += ``; + + if (i === path.length - 1) { + html += `${label}`; + } else { + html += `${label}`; + } + }); + + this.breadcrumbEl.innerHTML = html; + + // Attach click handlers + this.breadcrumbEl.querySelectorAll('a').forEach(a => { + a.addEventListener('click', (e) => { + e.preventDefault(); + const newPath = a.dataset.path ? a.dataset.path.split('/') : []; + this.app.navigate(newPath); + }); + }); + } + + showControls() { + this.controlsEl.innerHTML = ` +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ `; + + // Wire up events + document.getElementById('explosionSlider').addEventListener('input', (e) => { + document.getElementById('explosionValue').textContent = e.target.value; + if (this.onExplosionChange) this.onExplosionChange(parseInt(e.target.value)); + }); + + document.getElementById('rotationSlider').addEventListener('input', (e) => { + document.getElementById('rotationValue').textContent = e.target.value; + if (this.onRotationChange) this.onRotationChange(parseInt(e.target.value)); + }); + + document.getElementById('animateBtn').addEventListener('click', () => { + if (this.onAnimate) this.onAnimate(); + }); + + document.getElementById('exportBtn').addEventListener('click', () => { + if (this.onExport) this.onExport(); + }); + + this.controlsEl.style.display = 'flex'; + this.diagramContainer.style.display = 'block'; + } + + hideControls() { + this.controlsEl.style.display = 'none'; + this.diagramContainer.style.display = 'none'; + } + + showSidebar(assembly) { + const totalParts = assembly.parts.reduce((sum, p) => sum + p.quantity, 0); + const totalWeight = assembly.parts.reduce((sum, p) => sum + (p.weight * p.quantity), 0); + + // Get unique materials + const materials = [...new Set(assembly.parts.map(p => p.material))]; + + this.sidebarEl.innerHTML = ` +
+

Assembly Stats

+
+
+
${assembly.parts.length}
+
Unique Parts
+
+
+
${totalWeight < 1000 ? totalWeight.toFixed(0) + 'g' : (totalWeight/1000).toFixed(2) + 'kg'}
+
Weight
+
+
+
${assembly.category || '-'}
+
Category
+
+
+
${totalParts}
+
Total Pieces
+
+
+
+ +
+

Parts List

+
+ ${assembly.parts.map(p => ` +
+ ${p.name} + ${p.partNumber} +
+ `).join('')} +
+
+ +
+

Materials

+
+ ${materials.map(m => ` +
+
+ ${this.getMaterialName(m)} +
+ `).join('')} +
+
+ `; + + // Attach hover events to parts list + this.sidebarEl.querySelectorAll('.part-item').forEach(el => { + el.addEventListener('mouseenter', () => { + if (this.onPartHover) this.onPartHover(el.dataset.part); + }); + el.addEventListener('mouseleave', () => { + if (this.onPartHover) this.onPartHover(null); + }); + }); + + this.sidebarEl.style.display = 'flex'; + } + + getMaterialColor(materialId) { + const colors = { + aluminum: '#d0d0d0', + steel: '#6a6a6a', + blueAnodized: '#4a90d9', + redAnodized: '#d94a4a', + blackPlastic: '#2a2a2a', + copper: '#b87333', + titanium: '#8a8a8a', + brass: '#d4a84a', + spring: '#d94a4a', + greenSpring: '#4ad94a' + }; + return colors[materialId] || '#888'; + } + + getMaterialName(materialId) { + const names = { + aluminum: 'Aluminum', + steel: 'Steel', + blueAnodized: 'Anodized (Blue)', + redAnodized: 'Anodized (Red)', + blackPlastic: 'Composite', + copper: 'Copper', + titanium: 'Titanium', + brass: 'Brass', + spring: 'Spring Steel', + greenSpring: 'Spring (Soft)' + }; + return names[materialId] || materialId; + } + + showPackageGrid(items, title, onClick) { + this.sidebarEl.innerHTML = ''; + this.sidebarEl.style.display = 'none'; + + // Replace diagram with package browser + this.diagramContainer.innerHTML = ` +
+

${title}

+
+ ${items.map(item => ` +
+

${item.name}

+

${item.description || ''}

+ ${item.meta ? `
${item.meta}
` : ''} +
+ `).join('')} +
+
+ `; + + this.diagramContainer.style.display = 'block'; + this.diagramContainer.style.background = '#fafafa'; + + // Attach click handlers + this.diagramContainer.querySelectorAll('.package-card').forEach(card => { + card.addEventListener('click', () => { + const item = items.find(i => i.id === card.dataset.id); + if (item && onClick) onClick(item); + }); + }); + } + + showError(message) { + this.diagramContainer.innerHTML = ` +
+

Error

+

${message}

+ +
+ `; + this.diagramContainer.style.display = 'block'; + } +} diff --git a/exploded-diagrams/js/main.js b/exploded-diagrams/js/main.js new file mode 100644 index 000000000..f62be9c29 --- /dev/null +++ b/exploded-diagrams/js/main.js @@ -0,0 +1,135 @@ +import { PackageLoader } from './PackageLoader.js'; +import { DiagramRenderer } from './DiagramRenderer.js'; +import { UIController } from './UIController.js'; + +class App { + constructor() { + this.loader = new PackageLoader(); + this.renderer = null; + this.ui = null; + this.currentPath = []; + } + + async init() { + this.ui = new UIController(this); + + // Parse URL hash for deep linking + const hash = window.location.hash.slice(1); + if (hash) { + this.currentPath = hash.split('/').filter(Boolean); + } + + await this.navigate(this.currentPath); + + // Handle browser back/forward + window.addEventListener('hashchange', () => { + const newPath = window.location.hash.slice(1).split('/').filter(Boolean); + this.navigate(newPath); + }); + } + + async navigate(path) { + this.currentPath = path; + window.location.hash = path.join('/'); + + if (path.length === 0) { + // Show root - list categories + await this.showCategories(); + } else if (path.length === 1) { + // Show manufacturers in category + await this.showManufacturers(path[0]); + } else if (path.length === 2) { + // Show products from manufacturer + await this.showProducts(path[0], path[1]); + } else if (path.length === 3) { + // Show assemblies in product + await this.showAssemblies(path[0], path[1], path[2]); + } else if (path.length >= 4) { + // Load and render assembly + await this.loadAssembly(path[0], path[1], path[2], path[3]); + } + + this.ui.updateBreadcrumb(path); + } + + async showCategories() { + const categories = await this.loader.getCategories(); + this.ui.showPackageGrid(categories, 'Categories', (cat) => { + this.navigate([cat.id]); + }); + this.ui.hideControls(); + } + + async showManufacturers(category) { + const manufacturers = await this.loader.getManufacturers(category); + this.ui.showPackageGrid(manufacturers, 'Manufacturers', (mfr) => { + this.navigate([category, mfr.id]); + }); + this.ui.hideControls(); + } + + async showProducts(category, manufacturer) { + const products = await this.loader.getProducts(category, manufacturer); + this.ui.showPackageGrid(products, 'Products', (prod) => { + this.navigate([category, manufacturer, prod.id]); + }); + this.ui.hideControls(); + } + + async showAssemblies(category, manufacturer, product) { + const pkg = await this.loader.loadProductPackage(category, manufacturer, product); + this.ui.showPackageGrid(pkg.assemblies.map(a => ({ + id: a, + name: a.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()), + description: 'Assembly' + })), 'Assemblies', (asm) => { + this.navigate([category, manufacturer, product, asm.id]); + }); + this.ui.hideControls(); + } + + async loadAssembly(category, manufacturer, product, assembly) { + try { + const data = await this.loader.loadAssembly(category, manufacturer, product, assembly); + + this.renderer = new DiagramRenderer( + document.getElementById('diagram'), + data, + this.loader.materials + ); + + this.ui.showControls(); + this.ui.showSidebar(data); + this.renderer.render(); + + // Wire up UI events + this.ui.onExplosionChange = (val) => { + this.renderer.setExplosion(val); + }; + + this.ui.onRotationChange = (val) => { + this.renderer.setRotation(val); + }; + + this.ui.onAnimate = () => { + this.renderer.animate(); + }; + + this.ui.onExport = () => { + this.renderer.exportSVG(assembly); + }; + + this.ui.onPartHover = (partId) => { + this.renderer.highlightPart(partId); + }; + + } catch (err) { + console.error('Failed to load assembly:', err); + this.ui.showError(`Failed to load assembly: ${err.message}`); + } + } +} + +// Boot +const app = new App(); +app.init().catch(console.error); diff --git a/exploded-diagrams/packages/index.json b/exploded-diagrams/packages/index.json new file mode 100644 index 000000000..739e95ab1 --- /dev/null +++ b/exploded-diagrams/packages/index.json @@ -0,0 +1,11 @@ +{ + "version": "1.0.0", + "categories": [ + { + "id": "rc", + "name": "RC Vehicles", + "description": "Radio controlled cars, trucks, and buggies", + "meta": "3 manufacturers" + } + ] +} diff --git a/exploded-diagrams/packages/materials.json b/exploded-diagrams/packages/materials.json new file mode 100644 index 000000000..155b00bbd --- /dev/null +++ b/exploded-diagrams/packages/materials.json @@ -0,0 +1,97 @@ +{ + "aluminum": { + "name": "Aluminum", + "gradient": { + "angle": 135, + "stops": [ + { "offset": 0, "color": "#e8e8e8" }, + { "offset": 25, "color": "#d0d0d0" }, + { "offset": 50, "color": "#f5f5f5" }, + { "offset": 75, "color": "#c8c8c8" }, + { "offset": 100, "color": "#a8a8a8" } + ] + } + }, + "steel": { + "name": "Hardened Steel", + "gradient": { + "angle": 135, + "stops": [ + { "offset": 0, "color": "#6a6a6a" }, + { "offset": 30, "color": "#9a9a9a" }, + { "offset": 50, "color": "#5a5a5a" }, + { "offset": 100, "color": "#4a4a4a" } + ] + } + }, + "blueAnodized": { + "name": "Anodized Aluminum (Blue)", + "gradient": { + "angle": 135, + "stops": [ + { "offset": 0, "color": "#4a90d9" }, + { "offset": 30, "color": "#6ab0f9" }, + { "offset": 60, "color": "#3a70b9" }, + { "offset": 100, "color": "#2a5090" } + ] + } + }, + "redAnodized": { + "name": "Anodized Aluminum (Red)", + "gradient": { + "angle": 135, + "stops": [ + { "offset": 0, "color": "#d94a4a" }, + { "offset": 30, "color": "#f96a6a" }, + { "offset": 60, "color": "#b93a3a" }, + { "offset": 100, "color": "#902a2a" } + ] + } + }, + "blackPlastic": { + "name": "Composite/Nylon", + "gradient": { + "angle": 135, + "stops": [ + { "offset": 0, "color": "#3a3a3a" }, + { "offset": 40, "color": "#2a2a2a" }, + { "offset": 100, "color": "#1a1a1a" } + ] + } + }, + "copper": { + "name": "Copper", + "gradient": { + "angle": 135, + "stops": [ + { "offset": 0, "color": "#b87333" }, + { "offset": 30, "color": "#da9a62" }, + { "offset": 60, "color": "#a05a23" }, + { "offset": 100, "color": "#804a13" } + ] + } + }, + "brass": { + "name": "Brass", + "gradient": { + "angle": 135, + "stops": [ + { "offset": 0, "color": "#d4a84a" }, + { "offset": 30, "color": "#e8c878" }, + { "offset": 60, "color": "#b8923a" }, + { "offset": 100, "color": "#987020" } + ] + } + }, + "spring": { + "name": "Spring Steel (Red)", + "gradient": { + "angle": 0, + "stops": [ + { "offset": 0, "color": "#d94a4a" }, + { "offset": 50, "color": "#ff6a6a" }, + { "offset": 100, "color": "#b93a3a" } + ] + } + } +} diff --git a/exploded-diagrams/packages/rc/index.json b/exploded-diagrams/packages/rc/index.json new file mode 100644 index 000000000..59593eaec --- /dev/null +++ b/exploded-diagrams/packages/rc/index.json @@ -0,0 +1,11 @@ +{ + "category": "rc", + "manufacturers": [ + { + "id": "traxxas", + "name": "Traxxas", + "description": "The Fastest Name in Radio Control", + "meta": "USA • Founded 1986" + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/index.json b/exploded-diagrams/packages/rc/traxxas/index.json new file mode 100644 index 000000000..d1fc2f44d --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/index.json @@ -0,0 +1,11 @@ +{ + "manufacturer": "traxxas", + "products": [ + { + "id": "slash-4x4", + "name": "Slash 4x4", + "description": "1/10 Scale 4WD Short Course Truck", + "meta": "Model #68086-4" + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/assembly.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/assembly.json new file mode 100644 index 000000000..102b934ed --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/assembly.json @@ -0,0 +1,17 @@ +{ + "id": "front-shock", + "name": "Front Shock Assembly", + "description": "GTR Long Shock - Oil-filled coilover with adjustable preload", + "category": "Suspension", + "version": "1.0.0", + "parts": [ + "shock-cap", + "shaft", + "piston", + "spring", + "retainer", + "body", + "bushing", + "mount" + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/body.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/body.json new file mode 100644 index 000000000..6250b1214 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/body.json @@ -0,0 +1,61 @@ +{ + "id": "body", + "name": "Shock Body", + "partNumber": "TRA-5461", + "material": "aluminum", + "quantity": 1, + "weight": 45.0, + "weightUnit": "g", + "baseY": 465, + "description": "6061-T6 aluminum shock body with reservoir", + "geometry": [ + { + "type": "cylinder", + "rx": 30, + "ry": 10, + "height": 100 + }, + { + "type": "cylinder", + "rx": 8, + "ry": 3, + "height": 50, + "offsetX": 36, + "offsetY": -35 + }, + { + "type": "line", + "x1": -28, + "y1": -25, + "x2": 26, + "y2": -25, + "stroke": "#bbb", + "strokeWidth": 0.5 + }, + { + "type": "line", + "x1": -28, + "y1": 0, + "x2": 26, + "y2": 0, + "stroke": "#bbb", + "strokeWidth": 0.5 + }, + { + "type": "line", + "x1": -28, + "y1": 25, + "x2": 26, + "y2": 25, + "stroke": "#bbb", + "strokeWidth": 0.5 + }, + { + "type": "circle", + "r": 4, + "offsetX": 36, + "offsetY": -15, + "fill": "#888" + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/bushing.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/bushing.json new file mode 100644 index 000000000..1885e9f98 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/bushing.json @@ -0,0 +1,25 @@ +{ + "id": "bushing", + "name": "Lower Bushing", + "partNumber": "TRA-5468", + "material": "blackPlastic", + "quantity": 2, + "weight": 1.8, + "weightUnit": "g", + "baseY": 600, + "description": "Delrin shock bushing", + "geometry": [ + { + "type": "cylinder", + "rx": 20, + "ry": 7, + "height": 20 + }, + { + "type": "ellipse", + "rx": 8, + "ry": 3, + "fill": "#0a0a0a" + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/mount.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/mount.json new file mode 100644 index 000000000..493374ac6 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/mount.json @@ -0,0 +1,43 @@ +{ + "id": "mount", + "name": "Shock Mount", + "partNumber": "TRA-5460", + "material": "aluminum", + "quantity": 1, + "weight": 15.2, + "weightUnit": "g", + "baseY": 660, + "description": "Aluminum lower shock mount bracket", + "geometry": [ + { + "type": "polygon", + "points": [-35, 20, -20, -10, 20, -10, 35, 20] + }, + { + "type": "circle", + "r": 8, + "offsetY": 2, + "fill": "#888" + }, + { + "type": "circle", + "r": 5, + "offsetY": 2, + "fill": "#555" + }, + { + "type": "circle", + "r": 4, + "offsetX": -22, + "offsetY": 15, + "fill": "#666" + }, + { + "type": "circle", + "r": 4, + "offsetX": 22, + "offsetY": 15, + "fill": "#666" + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/piston.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/piston.json new file mode 100644 index 000000000..b38abf202 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/piston.json @@ -0,0 +1,43 @@ +{ + "id": "piston", + "name": "Piston", + "partNumber": "TRA-5467", + "material": "blackPlastic", + "quantity": 1, + "weight": 3.1, + "weightUnit": "g", + "baseY": 195, + "description": "PTFE composite piston with 4-hole pattern", + "geometry": [ + { + "type": "cylinder", + "rx": 22, + "ry": 7, + "height": 14 + }, + { + "type": "circle", + "r": 3, + "offsetX": -10, + "fill": "#0a0a0a" + }, + { + "type": "circle", + "r": 3, + "offsetX": 10, + "fill": "#0a0a0a" + }, + { + "type": "circle", + "r": 3, + "offsetY": -4, + "fill": "#0a0a0a" + }, + { + "type": "circle", + "r": 3, + "offsetY": 4, + "fill": "#0a0a0a" + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/retainer.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/retainer.json new file mode 100644 index 000000000..f3305ae38 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/retainer.json @@ -0,0 +1,25 @@ +{ + "id": "retainer", + "name": "Spring Retainer", + "partNumber": "TRA-5465", + "material": "blueAnodized", + "quantity": 1, + "weight": 5.4, + "weightUnit": "g", + "baseY": 410, + "description": "Aluminum spring retainer / preload adjuster", + "geometry": [ + { + "type": "cylinder", + "rx": 32, + "ry": 10, + "height": 14 + }, + { + "type": "ellipse", + "rx": 12, + "ry": 4, + "fill": "#1a3a5a" + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/shaft.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/shaft.json new file mode 100644 index 000000000..54f1ee5d2 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/shaft.json @@ -0,0 +1,27 @@ +{ + "id": "shaft", + "name": "Shock Shaft", + "partNumber": "TRA-5464", + "material": "steel", + "quantity": 1, + "weight": 12.5, + "weightUnit": "g", + "baseY": 55, + "description": "Hardened steel shock shaft - TiN coated", + "geometry": [ + { + "type": "rect", + "width": 10, + "height": 120, + "rx": 2 + }, + { + "type": "rect", + "width": 3, + "height": 120, + "offsetX": -3, + "fill": "#aaa", + "opacity": 0.3 + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/shock-cap.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/shock-cap.json new file mode 100644 index 000000000..6c8505628 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/shock-cap.json @@ -0,0 +1,41 @@ +{ + "id": "shock-cap", + "name": "Shock Cap", + "partNumber": "TRA-5466", + "material": "blueAnodized", + "quantity": 1, + "weight": 8.2, + "weightUnit": "g", + "baseY": 0, + "description": "Aluminum shock cap with hex adjustment and bleed hole", + "geometry": [ + { + "type": "cylinder", + "rx": 28, + "ry": 10, + "height": 30 + }, + { + "type": "polygon", + "points": [0, -12, 12, -6, 12, 6, 0, 12, -12, 6, -12, -6], + "offsetY": 8, + "stroke": "#2a5090", + "strokeWidth": 1.5, + "fill": "none" + }, + { + "type": "circle", + "r": 5, + "fill": "#1a3a5a" + }, + { + "type": "ellipse", + "rx": 6, + "ry": 3, + "offsetX": 8, + "offsetY": -10, + "fill": "#7ac0f9", + "opacity": 0.4 + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/spring.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/spring.json new file mode 100644 index 000000000..0b4f34df3 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/front-shock/parts/spring.json @@ -0,0 +1,21 @@ +{ + "id": "spring", + "name": "Shock Spring", + "partNumber": "TRA-5442R", + "material": "spring", + "quantity": 1, + "weight": 22.0, + "weightUnit": "g", + "baseY": 250, + "description": "Progressive rate spring - Red (Firm)", + "geometry": [ + { + "type": "coilSpring", + "coils": 8, + "rx": 26, + "ry": 8, + "pitch": 18, + "strokeWidth": 7 + } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/package.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/package.json new file mode 100644 index 000000000..60c933152 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/package.json @@ -0,0 +1,14 @@ +{ + "product": "slash-4x4", + "name": "Traxxas Slash 4x4", + "description": "1/10 Scale 4WD Electric Short Course Truck", + "model": "68086-4", + "manufacturer": "Traxxas", + "category": "Short Course Truck", + "scale": "1:10", + "assemblies": [ + "front-shock", + "rear-differential", + "steering-servo" + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/assembly.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/assembly.json new file mode 100644 index 000000000..a64422d92 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/assembly.json @@ -0,0 +1,15 @@ +{ + "id": "rear-differential", + "name": "Rear Differential Assembly", + "description": "Sealed gear differential with metal internals", + "category": "Drivetrain", + "version": "1.0.0", + "parts": [ + "case-top", + "ring-gear", + "spider-gears", + "outdrives", + "bearings", + "case-bottom" + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/bearings.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/bearings.json new file mode 100644 index 000000000..2a77b3b24 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/bearings.json @@ -0,0 +1,17 @@ +{ + "id": "bearings", + "name": "Diff Bearings", + "partNumber": "TRA-5118", + "material": "steel", + "quantity": 2, + "weight": 4.5, + "weightUnit": "g", + "baseY": 350, + "description": "10x15x4mm rubber sealed ball bearings", + "geometry": [ + { "type": "cylinder", "rx": 15, "ry": 5, "height": 10, "offsetX": -45 }, + { "type": "ellipse", "rx": 8, "ry": 3, "offsetX": -45, "fill": "#4a4a4a" }, + { "type": "cylinder", "rx": 15, "ry": 5, "height": 10, "offsetX": 45 }, + { "type": "ellipse", "rx": 8, "ry": 3, "offsetX": 45, "fill": "#4a4a4a" } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/case-bottom.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/case-bottom.json new file mode 100644 index 000000000..008da36ee --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/case-bottom.json @@ -0,0 +1,18 @@ +{ + "id": "case-bottom", + "name": "Diff Case (Bottom)", + "partNumber": "TRA-5381B", + "material": "blackPlastic", + "quantity": 1, + "weight": 32.0, + "weightUnit": "g", + "baseY": 430, + "description": "Glass-filled nylon differential case half with output shaft bore", + "geometry": [ + { "type": "cylinder", "rx": 55, "ry": 20, "height": 25 }, + { "type": "ellipse", "rx": 25, "ry": 9, "fill": "#1a1a1a" }, + { "type": "circle", "r": 5, "offsetX": -35, "fill": "#2a2a2a" }, + { "type": "circle", "r": 5, "offsetX": 35, "fill": "#2a2a2a" }, + { "type": "circle", "r": 8, "offsetY": 22, "fill": "#2a2a2a" } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/case-top.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/case-top.json new file mode 100644 index 000000000..86c908f66 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/case-top.json @@ -0,0 +1,17 @@ +{ + "id": "case-top", + "name": "Diff Case (Top)", + "partNumber": "TRA-5381", + "material": "blackPlastic", + "quantity": 1, + "weight": 28.5, + "weightUnit": "g", + "baseY": 0, + "description": "Glass-filled nylon differential case half", + "geometry": [ + { "type": "cylinder", "rx": 55, "ry": 20, "height": 15 }, + { "type": "ellipse", "rx": 25, "ry": 9, "fill": "#1a1a1a" }, + { "type": "circle", "r": 5, "offsetX": -35, "fill": "#2a2a2a" }, + { "type": "circle", "r": 5, "offsetX": 35, "fill": "#2a2a2a" } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/outdrives.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/outdrives.json new file mode 100644 index 000000000..845cfd778 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/outdrives.json @@ -0,0 +1,19 @@ +{ + "id": "outdrives", + "name": "Outdrives", + "partNumber": "TRA-5380", + "material": "steel", + "quantity": 2, + "weight": 18.0, + "weightUnit": "g", + "baseY": 270, + "description": "Hardened steel differential outdrives", + "geometry": [ + { "type": "rect", "width": 35, "height": 12, "offsetX": -32 }, + { "type": "ellipse", "rx": 6, "ry": 11, "offsetX": -15 }, + { "type": "rect", "width": 15, "height": 8, "offsetX": -57 }, + { "type": "rect", "width": 35, "height": 12, "offsetX": 32 }, + { "type": "ellipse", "rx": 6, "ry": 11, "offsetX": 15 }, + { "type": "rect", "width": 15, "height": 8, "offsetX": 57 } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/ring-gear.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/ring-gear.json new file mode 100644 index 000000000..96198b84b --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/ring-gear.json @@ -0,0 +1,16 @@ +{ + "id": "ring-gear", + "name": "Ring Gear", + "partNumber": "TRA-5379", + "material": "steel", + "quantity": 1, + "weight": 52.0, + "weightUnit": "g", + "baseY": 80, + "description": "Hardened steel ring gear - 43T", + "geometry": [ + { "type": "gearRing", "teeth": 24, "outerRadius": 48, "toothHeight": 8 }, + { "type": "cylinder", "rx": 45, "ry": 16, "height": 30 }, + { "type": "ellipse", "rx": 25, "ry": 9, "fill": "#4a4a4a" } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/spider-gears.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/spider-gears.json new file mode 100644 index 000000000..f9675b3c3 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/rear-differential/parts/spider-gears.json @@ -0,0 +1,18 @@ +{ + "id": "spider-gears", + "name": "Spider Gears", + "partNumber": "TRA-5382", + "material": "steel", + "quantity": 4, + "weight": 8.0, + "weightUnit": "g", + "baseY": 175, + "description": "Hardened steel spider gear set with cross pin", + "geometry": [ + { "type": "cylinder", "rx": 18, "ry": 6, "height": 15, "offsetX": -30 }, + { "type": "circle", "r": 5, "offsetX": -30, "fill": "#3a3a3a" }, + { "type": "cylinder", "rx": 18, "ry": 6, "height": 15, "offsetX": 30 }, + { "type": "circle", "r": 5, "offsetX": 30, "fill": "#3a3a3a" }, + { "type": "rect", "width": 16, "height": 25, "rx": 2, "offsetY": -5 } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/assembly.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/assembly.json new file mode 100644 index 000000000..e63068a41 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/assembly.json @@ -0,0 +1,14 @@ +{ + "id": "steering-servo", + "name": "Steering Servo Assembly", + "description": "High-torque digital servo with metal gear train", + "category": "Electronics", + "version": "1.0.0", + "parts": [ + "servo-horn", + "horn-screw", + "output-gear", + "servo-body", + "servo-mount" + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/horn-screw.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/horn-screw.json new file mode 100644 index 000000000..2a9b8807b --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/horn-screw.json @@ -0,0 +1,16 @@ +{ + "id": "horn-screw", + "name": "Horn Screw", + "partNumber": "TRA-2548", + "material": "steel", + "quantity": 1, + "weight": 0.8, + "weightUnit": "g", + "baseY": 50, + "description": "M3 servo horn retaining screw", + "geometry": [ + { "type": "circle", "r": 8, "offsetY": -8 }, + { "type": "rect", "width": 6, "height": 25 }, + { "type": "line", "x1": -4, "y1": -8, "x2": 4, "y2": -8, "stroke": "#444", "strokeWidth": 1.5 } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/output-gear.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/output-gear.json new file mode 100644 index 000000000..8f6d1a0ec --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/output-gear.json @@ -0,0 +1,16 @@ +{ + "id": "output-gear", + "name": "Output Gear", + "partNumber": "TRA-2074", + "material": "brass", + "quantity": 1, + "weight": 6.2, + "weightUnit": "g", + "baseY": 105, + "description": "Brass output gear with 25T spline", + "geometry": [ + { "type": "gearRing", "teeth": 20, "outerRadius": 18, "toothHeight": 4 }, + { "type": "cylinder", "rx": 16, "ry": 5, "height": 15 }, + { "type": "ellipse", "rx": 6, "ry": 2, "fill": "#806020" } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/servo-body.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/servo-body.json new file mode 100644 index 000000000..a05947857 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/servo-body.json @@ -0,0 +1,23 @@ +{ + "id": "servo-body", + "name": "Servo Body", + "partNumber": "TRA-2075X", + "material": "blackPlastic", + "quantity": 1, + "weight": 55.0, + "weightUnit": "g", + "baseY": 165, + "description": "Digital high-torque servo - 255oz-in", + "geometry": [ + { "type": "rect", "width": 60, "height": 50, "rx": 3 }, + { "type": "rect", "width": 80, "height": 8, "offsetY": -10 }, + { "type": "circle", "r": 3, "offsetX": -35, "offsetY": -10, "fill": "#1a1a1a" }, + { "type": "circle", "r": 3, "offsetX": 35, "offsetY": -10, "fill": "#1a1a1a" }, + { "type": "rect", "width": 40, "height": 12, "offsetY": -18, "fill": "#2a2a2a" }, + { "type": "text", "content": "2075X", "offsetY": -15, "fill": "#00d4ff", "fontSize": 7 }, + { "type": "circle", "r": 8, "offsetY": -30, "material": "steel" }, + { "type": "rect", "width": 12, "height": 20, "offsetX": 21, "offsetY": 10, "fill": "#333" }, + { "type": "rect", "width": 3, "height": 14, "offsetX": 17, "offsetY": 10, "fill": "#d94a4a" }, + { "type": "rect", "width": 3, "height": 14, "offsetX": 25, "offsetY": 10, "fill": "#d4a84a" } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/servo-horn.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/servo-horn.json new file mode 100644 index 000000000..4d6911a53 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/servo-horn.json @@ -0,0 +1,18 @@ +{ + "id": "servo-horn", + "name": "Servo Horn", + "partNumber": "TRA-2072", + "material": "blueAnodized", + "quantity": 1, + "weight": 4.5, + "weightUnit": "g", + "baseY": 0, + "description": "25T spline aluminum servo horn", + "geometry": [ + { "type": "ellipse", "rx": 12, "ry": 8 }, + { "type": "rect", "width": 100, "height": 10, "rx": 5, "offsetY": -4 }, + { "type": "circle", "r": 4, "offsetX": -40, "fill": "#1a3a5a" }, + { "type": "circle", "r": 4, "offsetX": 40, "fill": "#1a3a5a" }, + { "type": "circle", "r": 6, "fill": "#1a3a5a" } + ] +} diff --git a/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/servo-mount.json b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/servo-mount.json new file mode 100644 index 000000000..8a8eb6945 --- /dev/null +++ b/exploded-diagrams/packages/rc/traxxas/slash-4x4/steering-servo/parts/servo-mount.json @@ -0,0 +1,20 @@ +{ + "id": "servo-mount", + "name": "Servo Mount", + "partNumber": "TRA-2073", + "material": "aluminum", + "quantity": 1, + "weight": 18.0, + "weightUnit": "g", + "baseY": 280, + "description": "Aluminum servo mounting bracket", + "geometry": [ + { "type": "rect", "width": 100, "height": 25, "rx": 3 }, + { "type": "rect", "width": 70, "height": 55, "rx": 2, "offsetY": 15 }, + { "type": "rect", "width": 56, "height": 35, "offsetY": 20, "fill": "#aaa" }, + { "type": "circle", "r": 4, "offsetX": -42, "fill": "#888" }, + { "type": "circle", "r": 4, "offsetX": 42, "fill": "#888" }, + { "type": "circle", "r": 4, "offsetX": -42, "offsetY": 38, "fill": "#888" }, + { "type": "circle", "r": 4, "offsetX": 42, "offsetY": 38, "fill": "#888" } + ] +}