feat: enhance wire editing features with dynamic component system and improved rendering
parent
a8bb0b6ad9
commit
85cb535804
16
CLAUDE.md
16
CLAUDE.md
|
|
@ -293,18 +293,24 @@ Enable verbose logging:
|
|||
- ✅ Compilation via arduino-cli to .hex files
|
||||
- ✅ Real AVR8 emulation with avr8js
|
||||
- ✅ Pin state tracking and component updates
|
||||
- ✅ Visual components (Arduino Uno, LEDs, resistors, buttons)
|
||||
- ✅ Wire rendering (visual only, not functional)
|
||||
- ✅ Dynamic component system with 48+ wokwi-elements components
|
||||
- ✅ Component picker modal with search and categories
|
||||
- ✅ Component property dialog (single-click interaction)
|
||||
- ✅ Component rotation (90° increments)
|
||||
- ✅ Wire creation and rendering (orthogonal routing)
|
||||
- ✅ Segment-based wire editing (drag segments perpendicular to orientation)
|
||||
- ✅ Real-time wire preview with grid snapping (20px)
|
||||
- ✅ Pin overlay system for wire connections
|
||||
|
||||
**In Progress:**
|
||||
- 🚧 Wire editing and creation UI
|
||||
- 🚧 Functional wire connections (signal routing)
|
||||
- 🚧 Functional wire connections (electrical signal routing)
|
||||
- 🚧 Wire validation and error handling
|
||||
|
||||
**Planned:**
|
||||
- 📋 More components (sensors, displays, motors)
|
||||
- 📋 Serial monitor
|
||||
- 📋 Project persistence (SQLite)
|
||||
- 📋 Multi-board support (Mega, Nano, ESP32)
|
||||
- 📋 Undo/redo functionality
|
||||
|
||||
## Additional Resources
|
||||
|
||||
|
|
|
|||
|
|
@ -1,73 +1,216 @@
|
|||
# React + TypeScript + Vite
|
||||
# Arduino Emulator - Frontend
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
React + TypeScript + Vite frontend for the Arduino emulator with visual simulator and code editor.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
## Features
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
- **Monaco Code Editor** - Full VSCode-like Arduino code editing experience
|
||||
- **Dynamic Component System** - 48+ wokwi-elements components with search and categories
|
||||
- **Visual Simulator Canvas** - Interactive drag-and-drop circuit builder
|
||||
- **Component Property Dialog** - Single-click component interaction (rotate, delete, view pins)
|
||||
- **Segment-Based Wire Editing** - Drag wire segments perpendicular to orientation (like Wokwi)
|
||||
- **Real AVR8 Emulation** - Actual ATmega328p emulation using avr8js
|
||||
- **Pin Management** - Automatic pin mapping and state synchronization
|
||||
- **Grid Snapping** - 20px grid alignment for clean circuit layouts
|
||||
|
||||
## React Compiler
|
||||
## Tech Stack
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
- **React** 18 - UI framework
|
||||
- **TypeScript** - Static typing
|
||||
- **Vite** 5 - Build tool and dev server
|
||||
- **Monaco Editor** - Code editor (VSCode engine)
|
||||
- **Zustand** - State management
|
||||
- **Axios** - HTTP client for backend API
|
||||
- **avr8js** - AVR8 CPU emulator (local clone)
|
||||
- **@wokwi/elements** - Electronic web components (local clone)
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
## Development
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
### Prerequisites
|
||||
- Node.js 18+
|
||||
- Backend running at http://localhost:8001
|
||||
- Wokwi libraries built in `../wokwi-libs/`
|
||||
|
||||
```js
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
// Remove tseslint.configs.recommended and replace with this
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
// Other configs...
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
### Install Dependencies
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs['recommended-typescript'],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
### Run Development Server
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The app will be available at http://localhost:5173
|
||||
|
||||
### Build for Production
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Output will be in the `dist/` directory.
|
||||
|
||||
### Lint
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ ├── components-wokwi/ # React wrappers for wokwi-elements
|
||||
│ │ ├── editor/ # Monaco Editor components
|
||||
│ │ │ ├── CodeEditor.tsx
|
||||
│ │ │ └── EditorToolbar.tsx
|
||||
│ │ └── simulator/ # Simulation canvas components
|
||||
│ │ ├── SimulatorCanvas.tsx
|
||||
│ │ ├── WireLayer.tsx
|
||||
│ │ ├── WireRenderer.tsx
|
||||
│ │ ├── PinOverlay.tsx
|
||||
│ │ ├── ComponentPropertyDialog.tsx
|
||||
│ │ ├── ComponentPickerModal.tsx
|
||||
│ │ └── ComponentPalette.tsx
|
||||
│ ├── simulation/
|
||||
│ │ ├── AVRSimulator.ts # AVR8 CPU wrapper
|
||||
│ │ └── PinManager.ts # Pin mapping and callbacks
|
||||
│ ├── store/
|
||||
│ │ ├── useEditorStore.ts # Code editor state
|
||||
│ │ └── useSimulatorStore.ts # Simulation state
|
||||
│ ├── services/
|
||||
│ │ ├── api.ts # Backend API client
|
||||
│ │ └── ComponentRegistry.ts # Component metadata
|
||||
│ ├── types/ # TypeScript definitions
|
||||
│ ├── utils/
|
||||
│ │ ├── hexParser.ts # Intel HEX parser
|
||||
│ │ ├── wirePathGenerator.ts # Wire SVG path generation
|
||||
│ │ └── wireSegments.ts # Segment-based wire editing
|
||||
│ ├── App.tsx # Main app component
|
||||
│ └── main.tsx # Entry point
|
||||
├── public/ # Static assets
|
||||
├── vite.config.ts # Vite configuration
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## Key Architecture Patterns
|
||||
|
||||
### State Management (Zustand)
|
||||
Two main stores:
|
||||
- **useEditorStore** - Code content, theme, compilation state
|
||||
- **useSimulatorStore** - Simulation running state, components, wires, compiled hex
|
||||
|
||||
### Local Wokwi Libraries
|
||||
Vite aliases point to local clones instead of npm packages:
|
||||
```typescript
|
||||
resolve: {
|
||||
alias: {
|
||||
'avr8js': path.resolve(__dirname, '../wokwi-libs/avr8js/dist/esm'),
|
||||
'@wokwi/elements': path.resolve(__dirname, '../wokwi-libs/wokwi-elements/dist/esm'),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### AVR Simulation Loop
|
||||
- Runs at ~60 FPS using `requestAnimationFrame`
|
||||
- Executes ~267,000 CPU cycles per frame (16MHz / 60fps)
|
||||
- Port listeners fire when GPIO registers change
|
||||
- PinManager routes pin states to component callbacks
|
||||
|
||||
### Component System
|
||||
Components are Web Components from wokwi-elements:
|
||||
1. React wrappers in `components-wokwi/`
|
||||
2. Dynamic loading via ComponentRegistry
|
||||
3. Pin info extracted from component metadata
|
||||
4. State updates via refs and callbacks
|
||||
|
||||
### Wire Editing System
|
||||
Segment-based editing (like Wokwi):
|
||||
- Wires consist of orthogonal segments (horizontal/vertical)
|
||||
- Drag segments perpendicular to orientation:
|
||||
- Horizontal segments: move up/down (ns-resize)
|
||||
- Vertical segments: move left/right (ew-resize)
|
||||
- Local preview state during drag (requestAnimationFrame)
|
||||
- Store update only on mouse up with grid snapping (20px)
|
||||
|
||||
### Performance Optimizations
|
||||
- `requestAnimationFrame` for smooth wire dragging
|
||||
- Local state for real-time previews
|
||||
- Memoized path generation and segment computation
|
||||
- Store updates batched at interaction completion
|
||||
|
||||
## API Integration
|
||||
|
||||
Backend endpoints (http://localhost:8001):
|
||||
- `POST /api/compile` - Compile Arduino code to .hex
|
||||
- `GET /api/compile/status/{task_id}` - Check compilation status
|
||||
- `GET /api/compile/download/{filename}` - Download compiled .hex
|
||||
|
||||
See [backend documentation](../backend/README.md) for API details.
|
||||
|
||||
## Component Development
|
||||
|
||||
### Adding a New Component Type
|
||||
|
||||
1. Check if wokwi-elements has the component:
|
||||
```bash
|
||||
ls ../wokwi-libs/wokwi-elements/src/
|
||||
```
|
||||
|
||||
2. Create React wrapper in `src/components/components-wokwi/`:
|
||||
```typescript
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
export const WokwiMyComponent: React.FC<Props> = ({ ... }) => {
|
||||
const elementRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (elementRef.current) {
|
||||
elementRef.current.setAttribute('prop', value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return <wokwi-my-component ref={elementRef} />;
|
||||
};
|
||||
```
|
||||
|
||||
3. Add to ComponentRegistry metadata
|
||||
|
||||
4. Use in SimulatorCanvas or make available in ComponentPalette
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Monaco Editor Not Loading
|
||||
- Check if `monaco-editor` is installed
|
||||
- Verify Vite worker configuration in vite.config.ts
|
||||
|
||||
### Components Not Rendering
|
||||
- Ensure wokwi-elements is built: `cd ../wokwi-libs/wokwi-elements && npm run build`
|
||||
- Check browser console for Web Component registration errors
|
||||
- Verify Vite alias paths in vite.config.ts
|
||||
|
||||
### Wire Editing Performance Issues
|
||||
- Ensure `requestAnimationFrame` is being used
|
||||
- Check that store updates only happen on mouse up, not during drag
|
||||
- Verify no unnecessary re-renders with React DevTools
|
||||
|
||||
### Pin Alignment Issues
|
||||
- Pin coordinates from wokwi-elements are in CSS pixels
|
||||
- Do NOT multiply by MM_TO_PX conversion factor
|
||||
- Verify component position + pin offset calculation
|
||||
|
||||
### Compilation Fails
|
||||
- Check backend is running at http://localhost:8001
|
||||
- Verify arduino-cli is installed and `arduino:avr` core is available
|
||||
- Check CORS configuration in backend
|
||||
|
||||
## References
|
||||
|
||||
- [Main Project README](../README.md)
|
||||
- [Development Guide (CLAUDE.md)](../CLAUDE.md)
|
||||
- [Architecture Documentation](../ARCHITECTURE.md)
|
||||
- [Wokwi Integration](../WOKWI_LIBS.md)
|
||||
- [Monaco Editor API](https://microsoft.github.io/monaco-editor/api/index.html)
|
||||
- [Vite Documentation](https://vitejs.dev/)
|
||||
- [Zustand Guide](https://docs.pmnd.rs/zustand/getting-started/introduction)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const WireRenderer: React.FC<WireRendererProps> = ({ wire, isSelected })
|
|||
const [previewOrthoPoints, setPreviewOrthoPoints] = useState<Array<{ x: number; y: number }> | null>(null);
|
||||
|
||||
const svgRef = useRef<SVGGElement>(null);
|
||||
const rafRef = useRef<number | null>(null); // For requestAnimationFrame
|
||||
|
||||
// Generate SVG path (memoized for performance)
|
||||
// Use preview points during drag, actual wire points otherwise
|
||||
|
|
@ -56,9 +57,41 @@ export const WireRenderer: React.FC<WireRendererProps> = ({ wire, isSelected })
|
|||
}, [wire, previewOrthoPoints]);
|
||||
|
||||
// Compute segments (memoized)
|
||||
// Use preview points during drag for accurate segment positions
|
||||
const segments = useMemo(() => {
|
||||
if (previewOrthoPoints) {
|
||||
// During drag, compute segments from preview points
|
||||
const previewSegments: WireSegment[] = [];
|
||||
for (let i = 0; i < previewOrthoPoints.length - 1; i++) {
|
||||
const start = previewOrthoPoints[i];
|
||||
const end = previewOrthoPoints[i + 1];
|
||||
|
||||
if (start.x === end.x && start.y === end.y) continue;
|
||||
|
||||
const orientation = start.y === end.y ? 'horizontal' : 'vertical';
|
||||
const length =
|
||||
orientation === 'horizontal'
|
||||
? Math.abs(end.x - start.x)
|
||||
: Math.abs(end.y - start.y);
|
||||
|
||||
previewSegments.push({
|
||||
id: `${wire.id}-seg-${i}`,
|
||||
startPoint: start,
|
||||
endPoint: end,
|
||||
orientation,
|
||||
midPoint: {
|
||||
x: (start.x + end.x) / 2,
|
||||
y: (start.y + end.y) / 2,
|
||||
},
|
||||
length,
|
||||
startIndex: i,
|
||||
endIndex: i + 1,
|
||||
});
|
||||
}
|
||||
return previewSegments;
|
||||
}
|
||||
return computeSegments(wire);
|
||||
}, [wire]);
|
||||
}, [wire, previewOrthoPoints]);
|
||||
|
||||
// Handle wire selection
|
||||
const handleWireClick = useCallback(
|
||||
|
|
@ -73,36 +106,54 @@ export const WireRenderer: React.FC<WireRendererProps> = ({ wire, isSelected })
|
|||
const handleMouseMove = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
if (dragState) {
|
||||
// Handle dragging - use local state for smooth updates
|
||||
const svg = svgRef.current?.ownerSVGElement;
|
||||
if (!svg) return;
|
||||
|
||||
const svgRect = svg.getBoundingClientRect();
|
||||
const mouseX = e.clientX - svgRect.left;
|
||||
const mouseY = e.clientY - svgRect.top;
|
||||
|
||||
const { segment, startMousePos, originalOrthoPoints } = dragState;
|
||||
|
||||
// Calculate offset perpendicular to segment
|
||||
let offset = 0;
|
||||
if (segment.orientation === 'horizontal') {
|
||||
offset = mouseY - startMousePos.y;
|
||||
} else {
|
||||
offset = mouseX - startMousePos.x;
|
||||
// Cancel previous animation frame
|
||||
if (rafRef.current !== null) {
|
||||
cancelAnimationFrame(rafRef.current);
|
||||
}
|
||||
|
||||
// No grid snapping during drag for smooth movement
|
||||
// Grid snapping will be applied on mouse up
|
||||
// Handle dragging - use requestAnimationFrame for smooth updates
|
||||
rafRef.current = requestAnimationFrame(() => {
|
||||
const svg = svgRef.current?.ownerSVGElement;
|
||||
if (!svg) return;
|
||||
|
||||
// Update orthogonal points (local preview)
|
||||
const newOrthoPoints = updateOrthogonalPointsForSegmentDrag(
|
||||
originalOrthoPoints,
|
||||
segment,
|
||||
offset
|
||||
);
|
||||
const svgRect = svg.getBoundingClientRect();
|
||||
const mouseX = e.clientX - svgRect.left;
|
||||
const mouseY = e.clientY - svgRect.top;
|
||||
|
||||
// Update preview state (doesn't touch the store)
|
||||
setPreviewOrthoPoints(newOrthoPoints);
|
||||
const { segment, startMousePos, originalOrthoPoints } = dragState;
|
||||
|
||||
// Calculate offset perpendicular to segment
|
||||
let offset = 0;
|
||||
if (segment.orientation === 'horizontal') {
|
||||
offset = mouseY - startMousePos.y;
|
||||
} else {
|
||||
offset = mouseX - startMousePos.x;
|
||||
}
|
||||
|
||||
console.log('🎯 Drag Update:', {
|
||||
segmentId: segment.id,
|
||||
orientation: segment.orientation,
|
||||
offset,
|
||||
originalPointsCount: originalOrthoPoints.length,
|
||||
mousePos: { x: mouseX, y: mouseY },
|
||||
});
|
||||
|
||||
// No grid snapping during drag for smooth movement
|
||||
// Grid snapping will be applied on mouse up
|
||||
|
||||
// Update orthogonal points (local preview)
|
||||
const newOrthoPoints = updateOrthogonalPointsForSegmentDrag(
|
||||
originalOrthoPoints,
|
||||
segment,
|
||||
offset
|
||||
);
|
||||
|
||||
console.log('📍 New Ortho Points:', newOrthoPoints);
|
||||
|
||||
// Update preview state (doesn't touch the store)
|
||||
setPreviewOrthoPoints(newOrthoPoints);
|
||||
rafRef.current = null;
|
||||
});
|
||||
} else if (isSelected) {
|
||||
// Update hovered segment
|
||||
const svg = svgRef.current?.ownerSVGElement;
|
||||
|
|
@ -134,6 +185,18 @@ export const WireRenderer: React.FC<WireRendererProps> = ({ wire, isSelected })
|
|||
const pathPoints = getPathPoints(wire);
|
||||
const orthoPoints = generateOrthogonalPoints(pathPoints);
|
||||
|
||||
console.log('🚀 Start Dragging Segment:', {
|
||||
segmentId: segment.id,
|
||||
orientation: segment.orientation,
|
||||
segmentStart: segment.startPoint,
|
||||
segmentEnd: segment.endPoint,
|
||||
pathPointsCount: pathPoints.length,
|
||||
orthoPointsCount: orthoPoints.length,
|
||||
wireStart: wire.start,
|
||||
wireEnd: wire.end,
|
||||
wireControlPoints: wire.controlPoints,
|
||||
});
|
||||
|
||||
setDragState({
|
||||
segment,
|
||||
startMousePos: { x: mouseX, y: mouseY },
|
||||
|
|
@ -144,6 +207,14 @@ export const WireRenderer: React.FC<WireRendererProps> = ({ wire, isSelected })
|
|||
);
|
||||
|
||||
const handleMouseUp = useCallback(() => {
|
||||
console.log('🖱️ Mouse Up - Drag State:', {
|
||||
hasDragState: !!dragState,
|
||||
hasPreviewPoints: !!previewOrthoPoints,
|
||||
previewPointsCount: previewOrthoPoints?.length,
|
||||
wireStart: wire.start,
|
||||
wireEnd: wire.end,
|
||||
});
|
||||
|
||||
if (dragState && previewOrthoPoints) {
|
||||
// Apply grid snapping to final position
|
||||
const GRID_SIZE = 20;
|
||||
|
|
@ -152,6 +223,8 @@ export const WireRenderer: React.FC<WireRendererProps> = ({ wire, isSelected })
|
|||
y: Math.round(p.y / GRID_SIZE) * GRID_SIZE,
|
||||
}));
|
||||
|
||||
console.log('📐 Snapped Points:', snappedPoints);
|
||||
|
||||
// Convert back to control points
|
||||
const newControlPoints = orthogonalPointsToControlPoints(
|
||||
snappedPoints,
|
||||
|
|
@ -159,6 +232,12 @@ export const WireRenderer: React.FC<WireRendererProps> = ({ wire, isSelected })
|
|||
wire.end
|
||||
);
|
||||
|
||||
console.log('🔧 New Control Points:', newControlPoints);
|
||||
console.log('🔌 Wire Endpoints:', {
|
||||
start: wire.start,
|
||||
end: wire.end,
|
||||
});
|
||||
|
||||
// Update store only once at the end
|
||||
updateWire(wire.id, { controlPoints: newControlPoints });
|
||||
}
|
||||
|
|
@ -190,6 +269,15 @@ export const WireRenderer: React.FC<WireRendererProps> = ({ wire, isSelected })
|
|||
}
|
||||
}, [hoveredSegment, dragState]);
|
||||
|
||||
// Cleanup animation frame on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (rafRef.current !== null) {
|
||||
cancelAnimationFrame(rafRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<g
|
||||
ref={svgRef}
|
||||
|
|
|
|||
|
|
@ -177,22 +177,38 @@ export function updateOrthogonalPointsForSegmentDrag(
|
|||
/**
|
||||
* Convert orthogonal points back to control points
|
||||
* Removes start/end points and intermediate points that are redundant
|
||||
*
|
||||
* IMPORTANT: The first and last orthoPoints should match the wire endpoints.
|
||||
* We preserve ALL intermediate points that represent corners (direction changes).
|
||||
*/
|
||||
export function orthogonalPointsToControlPoints(
|
||||
orthoPoints: Array<{ x: number; y: number }>,
|
||||
start: { x: number; y: number },
|
||||
end: { x: number; y: number }
|
||||
): WireControlPoint[] {
|
||||
if (orthoPoints.length < 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Remove first and last points (those are start/end endpoints)
|
||||
const innerPoints = orthoPoints.slice(1, -1);
|
||||
|
||||
// Remove redundant points (those that are collinear with neighbors)
|
||||
if (innerPoints.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Use actual orthoPoints for prev/next comparison (not start/end parameters)
|
||||
const actualStart = orthoPoints[0];
|
||||
const actualEnd = orthoPoints[orthoPoints.length - 1];
|
||||
|
||||
// Keep only corner points (where direction changes)
|
||||
const controlPoints: WireControlPoint[] = [];
|
||||
|
||||
for (let i = 0; i < innerPoints.length; i++) {
|
||||
const current = innerPoints[i];
|
||||
const prev = i === 0 ? start : innerPoints[i - 1];
|
||||
const next = i === innerPoints.length - 1 ? end : innerPoints[i + 1];
|
||||
// Get prev from orthoPoints (index i in innerPoints = index i+1 in orthoPoints)
|
||||
const prev = orthoPoints[i]; // Previous point in orthoPoints
|
||||
const next = orthoPoints[i + 2]; // Next point in orthoPoints
|
||||
|
||||
// Check if current point is a corner (changes direction)
|
||||
const isCorner =
|
||||
|
|
|
|||
Loading…
Reference in New Issue