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 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.4</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.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.0', APP, init });
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.4</span>
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>
@@ -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
- window._executeParsedPrompt({ type, params: cmd.params || {} });
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);
@@ -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 || 1,
356
- params.height || 1,
357
- params.depth || 1,
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 || 1,
367
- params.radius || 1,
368
- params.height || 2,
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 || 1,
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 || 1,
386
- params.height || 2,
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 || 1,
394
- params.tube || 0.4,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyclecad",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Browser-based parametric 3D CAD modeler with AI-powered tools, native Inventor file parsing, and smart assembly management. No install required.",
5
5
  "main": "index.html",
6
6
  "bin": {