twarner commited on
Commit
f479e26
·
1 Parent(s): 484c1c4

init interface

Browse files
Files changed (4) hide show
  1. README.md +21 -5
  2. app.js +251 -0
  3. index.html +42 -18
  4. style.css +120 -18
README.md CHANGED
@@ -1,12 +1,28 @@
1
  ---
2
- title: Dcode
3
- emoji: 👀
4
  colorFrom: gray
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
8
  license: mit
9
- short_description: 'gcode Diffusion '
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: dcode
3
+ emoji: ✏️
4
  colorFrom: gray
5
+ colorTo: green
6
  sdk: static
7
  pinned: false
8
  license: mit
9
+ short_description: Text to Polargraph Gcode via Diffusion
10
  ---
11
 
12
+ # dcode
13
+
14
+ Generate polargraph-compatible gcode from text prompts.
15
+
16
+ ## Usage
17
+
18
+ 1. Enter a prompt (e.g., "drawing of a cat")
19
+ 2. Click Generate
20
+ 3. View result in workplane (zoom/pan with mouse)
21
+ 4. Download as Gcode or SVG
22
+
23
+ ## Features
24
+
25
+ - Real-time gcode visualization
26
+ - Zoom/pan controls
27
+ - Machine compatibility validation
28
+ - Export to Gcode or SVG
app.js ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // dcode - Gcode visualization and generation
2
+
3
+ class GcodeViewer {
4
+ constructor(canvas) {
5
+ this.canvas = canvas;
6
+ this.ctx = canvas.getContext('2d');
7
+ this.paths = [];
8
+ this.zoom = 1;
9
+ this.panX = 0;
10
+ this.panY = 0;
11
+ this.isDragging = false;
12
+ this.lastMouse = { x: 0, y: 0 };
13
+
14
+ // Work area bounds (from machine config)
15
+ this.bounds = {
16
+ left: -420.5,
17
+ right: 420.5,
18
+ top: 594.5,
19
+ bottom: -594.5
20
+ };
21
+
22
+ this.resize();
23
+ this.setupEvents();
24
+ this.draw();
25
+ }
26
+
27
+ resize() {
28
+ const rect = this.canvas.getBoundingClientRect();
29
+ this.canvas.width = rect.width * window.devicePixelRatio;
30
+ this.canvas.height = rect.height * window.devicePixelRatio;
31
+ this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
32
+ }
33
+
34
+ setupEvents() {
35
+ this.canvas.addEventListener('wheel', (e) => {
36
+ e.preventDefault();
37
+ const delta = e.deltaY > 0 ? 0.9 : 1.1;
38
+ this.zoom *= delta;
39
+ this.zoom = Math.max(0.1, Math.min(10, this.zoom));
40
+ this.draw();
41
+ });
42
+
43
+ this.canvas.addEventListener('mousedown', (e) => {
44
+ this.isDragging = true;
45
+ this.lastMouse = { x: e.clientX, y: e.clientY };
46
+ });
47
+
48
+ this.canvas.addEventListener('mousemove', (e) => {
49
+ if (this.isDragging) {
50
+ this.panX += e.clientX - this.lastMouse.x;
51
+ this.panY += e.clientY - this.lastMouse.y;
52
+ this.lastMouse = { x: e.clientX, y: e.clientY };
53
+ this.draw();
54
+ }
55
+ });
56
+
57
+ this.canvas.addEventListener('mouseup', () => this.isDragging = false);
58
+ this.canvas.addEventListener('mouseleave', () => this.isDragging = false);
59
+
60
+ window.addEventListener('resize', () => {
61
+ this.resize();
62
+ this.draw();
63
+ });
64
+ }
65
+
66
+ parseGcode(gcode) {
67
+ this.paths = [];
68
+ let currentPath = [];
69
+ let x = 0, y = 0;
70
+ let penDown = false;
71
+
72
+ const lines = gcode.split('\n');
73
+ for (const line of lines) {
74
+ const trimmed = line.trim();
75
+ if (!trimmed || trimmed.startsWith(';')) continue;
76
+
77
+ // Pen up/down
78
+ if (trimmed.includes('M280')) {
79
+ const match = trimmed.match(/S(\d+)/);
80
+ if (match) {
81
+ const angle = parseInt(match[1]);
82
+ const wasDown = penDown;
83
+ penDown = angle < 50; // Down if angle < 50
84
+
85
+ if (wasDown && !penDown && currentPath.length > 1) {
86
+ this.paths.push([...currentPath]);
87
+ currentPath = [];
88
+ }
89
+ }
90
+ }
91
+
92
+ // Movement
93
+ const xMatch = trimmed.match(/X([-\d.]+)/i);
94
+ const yMatch = trimmed.match(/Y([-\d.]+)/i);
95
+
96
+ if (xMatch) x = parseFloat(xMatch[1]);
97
+ if (yMatch) y = parseFloat(yMatch[1]);
98
+
99
+ if ((xMatch || yMatch) && penDown) {
100
+ currentPath.push({ x, y });
101
+ }
102
+ }
103
+
104
+ if (currentPath.length > 1) {
105
+ this.paths.push(currentPath);
106
+ }
107
+
108
+ this.resetView();
109
+ this.draw();
110
+ }
111
+
112
+ resetView() {
113
+ this.zoom = 1;
114
+ this.panX = 0;
115
+ this.panY = 0;
116
+ }
117
+
118
+ draw() {
119
+ const w = this.canvas.width / window.devicePixelRatio;
120
+ const h = this.canvas.height / window.devicePixelRatio;
121
+
122
+ this.ctx.fillStyle = '#111';
123
+ this.ctx.fillRect(0, 0, w, h);
124
+
125
+ this.ctx.save();
126
+ this.ctx.translate(w / 2 + this.panX, h / 2 + this.panY);
127
+
128
+ // Scale to fit work area
129
+ const boundsW = this.bounds.right - this.bounds.left;
130
+ const boundsH = this.bounds.top - this.bounds.bottom;
131
+ const scale = Math.min(w / boundsW, h / boundsH) * 0.9 * this.zoom;
132
+
133
+ this.ctx.scale(scale, -scale); // Flip Y
134
+
135
+ // Draw work area
136
+ this.ctx.strokeStyle = '#333';
137
+ this.ctx.lineWidth = 1 / scale;
138
+ this.ctx.strokeRect(
139
+ this.bounds.left,
140
+ this.bounds.bottom,
141
+ boundsW,
142
+ boundsH
143
+ );
144
+
145
+ // Draw paths
146
+ this.ctx.strokeStyle = '#4ade80';
147
+ this.ctx.lineWidth = 1 / scale;
148
+ this.ctx.lineCap = 'round';
149
+ this.ctx.lineJoin = 'round';
150
+
151
+ for (const path of this.paths) {
152
+ if (path.length < 2) continue;
153
+ this.ctx.beginPath();
154
+ this.ctx.moveTo(path[0].x, path[0].y);
155
+ for (let i = 1; i < path.length; i++) {
156
+ this.ctx.lineTo(path[i].x, path[i].y);
157
+ }
158
+ this.ctx.stroke();
159
+ }
160
+
161
+ this.ctx.restore();
162
+ }
163
+
164
+ toSVG() {
165
+ const boundsW = this.bounds.right - this.bounds.left;
166
+ const boundsH = this.bounds.top - this.bounds.bottom;
167
+
168
+ let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${this.bounds.left} ${-this.bounds.top} ${boundsW} ${boundsH}">`;
169
+ svg += `<rect x="${this.bounds.left}" y="${-this.bounds.top}" width="${boundsW}" height="${boundsH}" fill="none" stroke="#333"/>`;
170
+
171
+ for (const path of this.paths) {
172
+ if (path.length < 2) continue;
173
+ const d = path.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${-p.y}`).join(' ');
174
+ svg += `<path d="${d}" fill="none" stroke="#4ade80" stroke-width="1"/>`;
175
+ }
176
+
177
+ svg += '</svg>';
178
+ return svg;
179
+ }
180
+ }
181
+
182
+ // Main app
183
+ let viewer;
184
+ let currentGcode = '';
185
+
186
+ document.addEventListener('DOMContentLoaded', () => {
187
+ const canvas = document.getElementById('workplane');
188
+ viewer = new GcodeViewer(canvas);
189
+
190
+ document.getElementById('zoom-in').onclick = () => {
191
+ viewer.zoom *= 1.2;
192
+ viewer.draw();
193
+ };
194
+
195
+ document.getElementById('zoom-out').onclick = () => {
196
+ viewer.zoom *= 0.8;
197
+ viewer.draw();
198
+ };
199
+
200
+ document.getElementById('reset-view').onclick = () => {
201
+ viewer.resetView();
202
+ viewer.draw();
203
+ };
204
+
205
+ document.getElementById('generate').onclick = async () => {
206
+ const prompt = document.getElementById('prompt').value;
207
+ if (!prompt) return;
208
+
209
+ document.getElementById('status').textContent = 'Generating...';
210
+ document.getElementById('validation').textContent = '';
211
+
212
+ // TODO: Call actual inference API
213
+ // For now, show placeholder
214
+ document.getElementById('status').textContent = 'API endpoint not configured. Deploy with Gradio backend.';
215
+ };
216
+
217
+ document.getElementById('download-gcode').onclick = () => {
218
+ if (!currentGcode) return;
219
+ const blob = new Blob([currentGcode], { type: 'text/plain' });
220
+ const url = URL.createObjectURL(blob);
221
+ const a = document.createElement('a');
222
+ a.href = url;
223
+ a.download = 'output.gcode';
224
+ a.click();
225
+ URL.revokeObjectURL(url);
226
+ };
227
+
228
+ document.getElementById('download-svg').onclick = () => {
229
+ if (!viewer.paths.length) return;
230
+ const svg = viewer.toSVG();
231
+ const blob = new Blob([svg], { type: 'image/svg+xml' });
232
+ const url = URL.createObjectURL(blob);
233
+ const a = document.createElement('a');
234
+ a.href = url;
235
+ a.download = 'output.svg';
236
+ a.click();
237
+ URL.revokeObjectURL(url);
238
+ };
239
+ });
240
+
241
+ // Expose for testing
242
+ window.loadGcode = (gcode) => {
243
+ currentGcode = gcode;
244
+ viewer.parseGcode(gcode);
245
+ document.getElementById('download-gcode').disabled = false;
246
+ document.getElementById('download-svg').disabled = false;
247
+ document.getElementById('status').textContent = `Loaded ${viewer.paths.length} paths`;
248
+ document.getElementById('validation').textContent = 'Machine compatible';
249
+ document.getElementById('validation').className = 'valid';
250
+ };
251
+
index.html CHANGED
@@ -1,19 +1,43 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>dcode - Text to Gcode</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <header>
12
+ <h1>dcode</h1>
13
+ <p>Text prompt → Polargraph Gcode</p>
14
+ </header>
15
+
16
+ <div class="input-section">
17
+ <input type="text" id="prompt" placeholder="drawing of a cat..." />
18
+ <button id="generate">Generate</button>
19
+ </div>
20
+
21
+ <div class="workplane-container">
22
+ <canvas id="workplane"></canvas>
23
+ <div class="controls">
24
+ <button id="zoom-in">+</button>
25
+ <button id="zoom-out">-</button>
26
+ <button id="reset-view">Reset</button>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="status-section">
31
+ <div id="status"></div>
32
+ <div id="validation"></div>
33
+ </div>
34
+
35
+ <div class="download-section">
36
+ <button id="download-gcode" disabled>Download Gcode</button>
37
+ <button id="download-svg" disabled>Download SVG</button>
38
+ </div>
39
+ </div>
40
+
41
+ <script src="app.js"></script>
42
+ </body>
43
  </html>
style.css CHANGED
@@ -1,28 +1,130 @@
 
 
 
 
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
28
  }
 
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+
7
  body {
8
+ font-family: system-ui, sans-serif;
9
+ background: #1a1a1a;
10
+ color: #e0e0e0;
11
+ min-height: 100vh;
12
+ }
13
+
14
+ .container {
15
+ max-width: 900px;
16
+ margin: 0 auto;
17
+ padding: 2rem;
18
+ }
19
+
20
+ header {
21
+ text-align: center;
22
+ margin-bottom: 2rem;
23
+ }
24
+
25
+ header h1 {
26
+ font-size: 2rem;
27
+ margin-bottom: 0.5rem;
28
+ }
29
+
30
+ header p {
31
+ color: #888;
32
+ }
33
+
34
+ .input-section {
35
+ display: flex;
36
+ gap: 1rem;
37
+ margin-bottom: 2rem;
38
+ }
39
+
40
+ #prompt {
41
+ flex: 1;
42
+ padding: 1rem;
43
+ font-size: 1rem;
44
+ border: 1px solid #333;
45
+ border-radius: 4px;
46
+ background: #222;
47
+ color: #e0e0e0;
48
+ }
49
+
50
+ button {
51
+ padding: 1rem 2rem;
52
+ font-size: 1rem;
53
+ border: none;
54
+ border-radius: 4px;
55
+ background: #444;
56
+ color: #e0e0e0;
57
+ cursor: pointer;
58
+ }
59
+
60
+ button:hover {
61
+ background: #555;
62
+ }
63
+
64
+ button:disabled {
65
+ opacity: 0.5;
66
+ cursor: not-allowed;
67
+ }
68
+
69
+ #generate {
70
+ background: #2563eb;
71
+ }
72
+
73
+ #generate:hover {
74
+ background: #1d4ed8;
75
+ }
76
+
77
+ .workplane-container {
78
+ position: relative;
79
+ background: #111;
80
+ border: 1px solid #333;
81
+ border-radius: 4px;
82
+ margin-bottom: 1rem;
83
+ }
84
+
85
+ #workplane {
86
+ width: 100%;
87
+ height: 500px;
88
+ display: block;
89
+ }
90
+
91
+ .controls {
92
+ position: absolute;
93
+ top: 1rem;
94
+ right: 1rem;
95
+ display: flex;
96
+ gap: 0.5rem;
97
+ }
98
+
99
+ .controls button {
100
+ padding: 0.5rem 1rem;
101
+ font-size: 1.2rem;
102
+ }
103
+
104
+ .status-section {
105
+ margin-bottom: 1rem;
106
+ padding: 1rem;
107
+ background: #222;
108
+ border-radius: 4px;
109
+ }
110
+
111
+ #status {
112
+ margin-bottom: 0.5rem;
113
  }
114
 
115
+ #validation.valid {
116
+ color: #22c55e;
 
117
  }
118
 
119
+ #validation.invalid {
120
+ color: #ef4444;
 
 
 
121
  }
122
 
123
+ .download-section {
124
+ display: flex;
125
+ gap: 1rem;
 
 
 
126
  }
127
 
128
+ .download-section button {
129
+ flex: 1;
130
  }