cyclecad 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/index.html +33 -4
- package/app/js/ai-copilot.js +13 -1
- package/app/js/operations.js +48 -30
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -1419,7 +1419,7 @@
|
|
|
1419
1419
|
<span class="splash-logo-cycle">cycle</span><span class="splash-logo-cad">CAD</span>
|
|
1420
1420
|
</div>
|
|
1421
1421
|
<p class="splash-subtitle">Parametric 3D CAD Modeler for the Mechanical Designer</p>
|
|
1422
|
-
<p style="display:inline-block; color:#0066cc; font-size:1rem; margin:12px 0 0 0; letter-spacing:2px; font-family:monospace; font-weight:700; background:rgba(0,102,204,0.08); border:1.5px solid rgba(0,102,204,0.25); border-radius:8px; padding:5px 20px;">v1.0.
|
|
1422
|
+
<p style="display:inline-block; color:#0066cc; font-size:1rem; margin:12px 0 0 0; letter-spacing:2px; font-family:monospace; font-weight:700; background:rgba(0,102,204,0.08); border:1.5px solid rgba(0,102,204,0.25); border-radius:8px; padding:5px 20px;">v1.0.5</p>
|
|
1423
1423
|
</div>
|
|
1424
1424
|
<div class="splash-options">
|
|
1425
1425
|
<button class="splash-button splash-button-primary" id="btn-empty-project" style="grid-column: 1 / -1;">
|
|
@@ -2904,6 +2904,11 @@
|
|
|
2904
2904
|
const pParams = prompt.params || prompt;
|
|
2905
2905
|
const result = createPrimitive(pType, pParams);
|
|
2906
2906
|
const mesh = result.mesh || result;
|
|
2907
|
+
// Apply position offset for multi-item placement (e.g., 4 holes around a part)
|
|
2908
|
+
if (pParams._offsetX || pParams._offsetZ) {
|
|
2909
|
+
mesh.position.x += (pParams._offsetX || 0) * 0.1;
|
|
2910
|
+
mesh.position.z += (pParams._offsetZ || 0) * 0.1;
|
|
2911
|
+
}
|
|
2907
2912
|
addToScene(mesh);
|
|
2908
2913
|
if (result.wireframe) addToScene(result.wireframe);
|
|
2909
2914
|
|
|
@@ -3554,7 +3559,7 @@
|
|
|
3554
3559
|
init();
|
|
3555
3560
|
}
|
|
3556
3561
|
|
|
3557
|
-
window.cycleCAD = Object.assign(window.cycleCAD || {}, { version: '1.0.
|
|
3562
|
+
window.cycleCAD = Object.assign(window.cycleCAD || {}, { version: '1.0.6', APP, init });
|
|
3558
3563
|
|
|
3559
3564
|
// Expose dialog functions globally so inline onclick="" handlers work
|
|
3560
3565
|
window.openDialog = openDialog;
|
|
@@ -4029,7 +4034,31 @@
|
|
|
4029
4034
|
panel.style.cssText = 'position:fixed;top:60px;right:320px;width:420px;max-height:80vh;overflow-y:auto;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;z-index:500;display:flex;flex-direction:column;box-shadow:var(--shadow-lg);';
|
|
4030
4035
|
|
|
4031
4036
|
const header = document.createElement('div');
|
|
4032
|
-
header.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid var(--border-color);background:var(--bg-tertiary);border-radius:8px 8px 0 0;cursor:move;';
|
|
4037
|
+
header.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid var(--border-color);background:var(--bg-tertiary);border-radius:8px 8px 0 0;cursor:move;user-select:none;';
|
|
4038
|
+
|
|
4039
|
+
// Make panel draggable by header
|
|
4040
|
+
let isDragging = false;
|
|
4041
|
+
let dragOffsetX = 0;
|
|
4042
|
+
let dragOffsetY = 0;
|
|
4043
|
+
|
|
4044
|
+
header.addEventListener('mousedown', (e) => {
|
|
4045
|
+
isDragging = true;
|
|
4046
|
+
const rect = panel.getBoundingClientRect();
|
|
4047
|
+
dragOffsetX = e.clientX - rect.left;
|
|
4048
|
+
dragOffsetY = e.clientY - rect.top;
|
|
4049
|
+
panel.style.zIndex = '9999'; // Bring to front
|
|
4050
|
+
});
|
|
4051
|
+
|
|
4052
|
+
document.addEventListener('mousemove', (e) => {
|
|
4053
|
+
if (!isDragging || !panel.parentElement) return;
|
|
4054
|
+
panel.style.left = (e.clientX - dragOffsetX) + 'px';
|
|
4055
|
+
panel.style.top = (e.clientY - dragOffsetY) + 'px';
|
|
4056
|
+
panel.style.right = 'auto'; // Remove right constraint when dragging
|
|
4057
|
+
});
|
|
4058
|
+
|
|
4059
|
+
document.addEventListener('mouseup', () => {
|
|
4060
|
+
isDragging = false;
|
|
4061
|
+
});
|
|
4033
4062
|
|
|
4034
4063
|
const titles = {
|
|
4035
4064
|
'copilot-panel': '✨ AI Copilot',
|
|
@@ -5243,6 +5272,6 @@
|
|
|
5243
5272
|
</div>
|
|
5244
5273
|
</div>
|
|
5245
5274
|
|
|
5246
|
-
<span id="version-badge" style="position:fixed;bottom:42px;left:50%;transform:translateX(-50%);z-index:999;font-size:0.9rem;color:rgba(255,255,255,0.9);letter-spacing:0.1em;white-space:nowrap;padding:6px 16px;user-select:all;pointer-events:auto;font-family:monospace;font-weight:700;background:rgba(0,0,0,0.7);border:1px solid rgba(88,166,255,0.4);border-radius:6px;text-shadow:0 1px 3px rgba(0,0,0,0.5);" title="cycleCAD version">cycleCAD v1.0.
|
|
5275
|
+
<span id="version-badge" style="position:fixed;bottom:42px;left:50%;transform:translateX(-50%);z-index:999;font-size:0.9rem;color:rgba(255,255,255,0.9);letter-spacing:0.1em;white-space:nowrap;padding:6px 16px;user-select:all;pointer-events:auto;font-family:monospace;font-weight:700;background:rgba(0,0,0,0.7);border:1px solid rgba(88,166,255,0.4);border-radius:6px;text-shadow:0 1px 3px rgba(0,0,0,0.5);" title="cycleCAD version">cycleCAD v1.0.6</span>
|
|
5247
5276
|
</body>
|
|
5248
5277
|
</html>
|
package/app/js/ai-copilot.js
CHANGED
|
@@ -948,7 +948,19 @@ export async function executeTextCommand(prompt) {
|
|
|
948
948
|
if (window._executeParsedPrompt) {
|
|
949
949
|
const method = cmd.method || '';
|
|
950
950
|
const type = method.replace('shape.', '').replace('feature.', '');
|
|
951
|
-
|
|
951
|
+
// Handle count param (e.g., 4 mounting holes)
|
|
952
|
+
const count = cmd.params?.count || 1;
|
|
953
|
+
for (let ci = 0; ci < count; ci++) {
|
|
954
|
+
const p = Object.assign({}, cmd.params);
|
|
955
|
+
// Offset multiple items so they don't stack
|
|
956
|
+
if (count > 1) {
|
|
957
|
+
const angle = (ci / count) * Math.PI * 2;
|
|
958
|
+
const spread = (p.radius || 5) * 4;
|
|
959
|
+
p._offsetX = Math.cos(angle) * spread;
|
|
960
|
+
p._offsetZ = Math.sin(angle) * spread;
|
|
961
|
+
}
|
|
962
|
+
window._executeParsedPrompt({ type, params: p });
|
|
963
|
+
}
|
|
952
964
|
results.push({ ok: true, method });
|
|
953
965
|
} else if (window.cycleCAD && window.cycleCAD.execute) {
|
|
954
966
|
const result = await window.cycleCAD.execute(cmd);
|
package/app/js/operations.js
CHANGED
|
@@ -349,12 +349,16 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
349
349
|
const { material = 'steel' } = options;
|
|
350
350
|
let geometry;
|
|
351
351
|
|
|
352
|
+
// Scale factor: dimensions in mm, viewport units are ~10x smaller for good camera framing
|
|
353
|
+
// Camera starts at (150,100,150) so a 100mm cube should be ~10 units to look right
|
|
354
|
+
const SCALE = 0.1;
|
|
355
|
+
|
|
352
356
|
switch (type) {
|
|
353
357
|
case 'box':
|
|
354
358
|
geometry = new THREE.BoxGeometry(
|
|
355
|
-
params.width ||
|
|
356
|
-
params.height ||
|
|
357
|
-
params.depth ||
|
|
359
|
+
(params.width || 10) * SCALE,
|
|
360
|
+
(params.height || 10) * SCALE,
|
|
361
|
+
(params.depth || 10) * SCALE,
|
|
358
362
|
params.widthSegments || 1,
|
|
359
363
|
params.heightSegments || 1,
|
|
360
364
|
params.depthSegments || 1
|
|
@@ -363,9 +367,9 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
363
367
|
|
|
364
368
|
case 'cylinder':
|
|
365
369
|
geometry = new THREE.CylinderGeometry(
|
|
366
|
-
params.radius ||
|
|
367
|
-
params.radius ||
|
|
368
|
-
params.height ||
|
|
370
|
+
(params.radius || 10) * SCALE,
|
|
371
|
+
(params.radius || 10) * SCALE,
|
|
372
|
+
(params.height || 20) * SCALE,
|
|
369
373
|
params.segments || 32,
|
|
370
374
|
1,
|
|
371
375
|
params.openEnded || false
|
|
@@ -374,7 +378,7 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
374
378
|
|
|
375
379
|
case 'sphere':
|
|
376
380
|
geometry = new THREE.SphereGeometry(
|
|
377
|
-
params.radius ||
|
|
381
|
+
(params.radius || 10) * SCALE,
|
|
378
382
|
params.segments || 32,
|
|
379
383
|
params.segments || 32
|
|
380
384
|
);
|
|
@@ -382,26 +386,40 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
382
386
|
|
|
383
387
|
case 'cone':
|
|
384
388
|
geometry = new THREE.ConeGeometry(
|
|
385
|
-
params.bottomRadius ||
|
|
386
|
-
params.height ||
|
|
389
|
+
(params.bottomRadius || 10) * SCALE,
|
|
390
|
+
(params.height || 20) * SCALE,
|
|
387
391
|
params.segments || 32
|
|
388
392
|
);
|
|
389
393
|
break;
|
|
390
394
|
|
|
391
395
|
case 'torus':
|
|
392
396
|
geometry = new THREE.TorusGeometry(
|
|
393
|
-
params.radius ||
|
|
394
|
-
params.tube ||
|
|
397
|
+
(params.radius || 10) * SCALE,
|
|
398
|
+
(params.tube || 4) * SCALE,
|
|
395
399
|
params.radialSegments || 16,
|
|
396
400
|
params.tubeSegments || 100
|
|
397
401
|
);
|
|
398
402
|
break;
|
|
399
403
|
|
|
404
|
+
case 'hole': {
|
|
405
|
+
// Create a dark cylinder for hole preview (visual subtract indicator)
|
|
406
|
+
const holeRadius = (params.radius || 5) * SCALE;
|
|
407
|
+
const holeDepth = (params.depth || params.height || 20) * SCALE;
|
|
408
|
+
geometry = new THREE.CylinderGeometry(holeRadius, holeRadius, holeDepth, 16, 1, false);
|
|
409
|
+
// Override material to dark red so holes are clearly visible
|
|
410
|
+
const holeMat = new THREE.MeshStandardMaterial({ color: 0x882222, metalness: 0.3, roughness: 0.7, transparent: true, opacity: 0.85 });
|
|
411
|
+
const holeMesh = new THREE.Mesh(geometry, holeMat);
|
|
412
|
+
holeMesh.castShadow = true;
|
|
413
|
+
holeMesh.receiveShadow = true;
|
|
414
|
+
const holeWire = createWireframeEdges(holeMesh);
|
|
415
|
+
return { mesh: holeMesh, wireframe: holeWire, params: { type, params, options } };
|
|
416
|
+
}
|
|
417
|
+
|
|
400
418
|
case 'bracket': {
|
|
401
419
|
// L-shaped bracket from width, height, thickness
|
|
402
|
-
const bw = params.width || 80;
|
|
403
|
-
const bh = params.height || 40;
|
|
404
|
-
const bt = params.thickness || 5;
|
|
420
|
+
const bw = (params.width || 80) * SCALE;
|
|
421
|
+
const bh = (params.height || 40) * SCALE;
|
|
422
|
+
const bt = (params.thickness || 5) * SCALE;
|
|
405
423
|
const shape = new THREE.Shape();
|
|
406
424
|
shape.moveTo(0, 0);
|
|
407
425
|
shape.lineTo(bw, 0);
|
|
@@ -417,9 +435,9 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
417
435
|
|
|
418
436
|
case 'flange': {
|
|
419
437
|
// Annular flange: outer ring with center hole
|
|
420
|
-
const fo = (params.outerDiameter || params.outerRadius * 2 || 60) / 2;
|
|
421
|
-
const fi = (params.innerDiameter || params.innerRadius * 2 || 20) / 2;
|
|
422
|
-
const fh = params.height || params.thickness || 10;
|
|
438
|
+
const fo = ((params.outerDiameter || params.outerRadius * 2 || 60) / 2) * SCALE;
|
|
439
|
+
const fi = ((params.innerDiameter || params.innerRadius * 2 || 20) / 2) * SCALE;
|
|
440
|
+
const fh = (params.height || params.thickness || 10) * SCALE;
|
|
423
441
|
const outerShape = new THREE.Shape();
|
|
424
442
|
outerShape.absarc(0, 0, fo, 0, Math.PI * 2, false);
|
|
425
443
|
const holePath = new THREE.Path();
|
|
@@ -431,9 +449,9 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
431
449
|
}
|
|
432
450
|
|
|
433
451
|
case 'washer': {
|
|
434
|
-
const wo = (params.outerDiameter || 20) / 2;
|
|
435
|
-
const wi = (params.innerDiameter || 10) / 2;
|
|
436
|
-
const wt = params.thickness || 2;
|
|
452
|
+
const wo = ((params.outerDiameter || 20) / 2) * SCALE;
|
|
453
|
+
const wi = ((params.innerDiameter || 10) / 2) * SCALE;
|
|
454
|
+
const wt = (params.thickness || 2) * SCALE;
|
|
437
455
|
const wShape = new THREE.Shape();
|
|
438
456
|
wShape.absarc(0, 0, wo, 0, Math.PI * 2, false);
|
|
439
457
|
const wHole = new THREE.Path();
|
|
@@ -445,9 +463,9 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
445
463
|
}
|
|
446
464
|
|
|
447
465
|
case 'spacer': {
|
|
448
|
-
const so = (params.outerDiameter || params.diameter || 15) / 2;
|
|
449
|
-
const si = (params.innerDiameter || params.holeDiameter || 6) / 2;
|
|
450
|
-
const sh = params.height || params.length || 20;
|
|
466
|
+
const so = (((params.outerDiameter || params.diameter || 15) / 2) * SCALE);
|
|
467
|
+
const si = (((params.innerDiameter || params.holeDiameter || 6) / 2) * SCALE);
|
|
468
|
+
const sh = (params.height || params.length || 20) * SCALE;
|
|
451
469
|
const sShape = new THREE.Shape();
|
|
452
470
|
sShape.absarc(0, 0, so, 0, Math.PI * 2, false);
|
|
453
471
|
const sHole = new THREE.Path();
|
|
@@ -460,9 +478,9 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
460
478
|
|
|
461
479
|
case 'gear': {
|
|
462
480
|
// Simple spur gear approximation
|
|
463
|
-
const gr = params.radius || params.diameter / 2 || 30;
|
|
481
|
+
const gr = ((params.radius || params.diameter / 2 || 30) * SCALE);
|
|
464
482
|
const gt = params.teeth || 20;
|
|
465
|
-
const gd = params.depth || params.thickness || 10;
|
|
483
|
+
const gd = (params.depth || params.thickness || 10) * SCALE;
|
|
466
484
|
const toothH = gr * 0.15;
|
|
467
485
|
const gShape = new THREE.Shape();
|
|
468
486
|
for (let i = 0; i < gt; i++) {
|
|
@@ -482,9 +500,9 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
482
500
|
}
|
|
483
501
|
|
|
484
502
|
case 'plate': {
|
|
485
|
-
const pw = params.width || 100;
|
|
486
|
-
const ph = params.height || params.depth || 50;
|
|
487
|
-
const pt = params.thickness || 5;
|
|
503
|
+
const pw = (params.width || 100) * SCALE;
|
|
504
|
+
const ph = (params.height || params.depth || 50) * SCALE;
|
|
505
|
+
const pt = (params.thickness || 5) * SCALE;
|
|
488
506
|
geometry = new THREE.BoxGeometry(pw, pt, ph);
|
|
489
507
|
break;
|
|
490
508
|
}
|
|
@@ -493,8 +511,8 @@ export function createPrimitive(type, params = {}, options = {}) {
|
|
|
493
511
|
// Fallback: treat unknown as a box with available dimensions
|
|
494
512
|
console.warn(`Unknown primitive type "${type}" — creating box fallback`);
|
|
495
513
|
geometry = new THREE.BoxGeometry(
|
|
496
|
-
params.width || params.diameter || 50,
|
|
497
|
-
params.height || params.thickness || 50,
|
|
514
|
+
(params.width || params.diameter || 50) * SCALE,
|
|
515
|
+
(params.height || params.thickness || 50) * SCALE,
|
|
498
516
|
params.depth || params.thickness || 50
|
|
499
517
|
);
|
|
500
518
|
}
|
package/package.json
CHANGED