cyclecad 0.1.10 → 0.2.1

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.
@@ -876,10 +876,17 @@
876
876
 
877
877
  const commands = [
878
878
  'create cylinder', 'create box', 'create sphere', 'create cone', 'create torus',
879
- 'add hole', 'add fillet', 'add chamfer', 'add pattern',
880
- 'sweep', 'loft', 'measure', 'section', 'undo', 'redo',
881
- 'copy', 'move', 'constraint', 'export stl', 'export step',
882
- 'help', 'history', 'clear', 'reset view', 'wireframe', 'grid'
879
+ 'create plate', 'create gear', 'create bracket', 'create washer', 'create hexbolt', 'create flange',
880
+ 'draw circle', 'make a cylinder', 'build box',
881
+ 'add hole', 'drill hole', 'add fillet', 'round edges', 'add chamfer', 'bevel',
882
+ 'shell', 'hollow out', 'mirror', 'pattern', 'add thread',
883
+ 'extrude', 'revolve', 'sweep', 'loft', 'boolean cut', 'boolean union',
884
+ 'material steel', 'material brass', 'material aluminum', 'color red', 'color chrome',
885
+ 'move', 'rotate', 'scale', 'copy', 'delete',
886
+ 'measure', 'section', 'wireframe', 'grid', 'shadows',
887
+ 'sketch line', 'sketch rect', 'constraint parallel',
888
+ 'export stl', 'export step', 'export gltf',
889
+ 'undo', 'redo', 'reset view', 'help', 'history', 'clear'
883
890
  ];
884
891
 
885
892
  const matches = commands.filter(cmd => cmd.includes(value.toLowerCase()));
@@ -919,158 +926,489 @@
919
926
  document.getElementById('status-latency').textContent = latency + 'ms';
920
927
  }
921
928
 
929
+ // ─── NATURAL LANGUAGE PARSER ───
930
+ // Maps synonyms/phrases to canonical intents
931
+ const NLP_SHAPES = {
932
+ cylinder: ['cylinder', 'cylnder', 'cylindar', 'cyl', 'tube', 'pipe', 'rod', 'shaft', 'barrel', 'piston'],
933
+ box: ['box', 'cube', 'block', 'brick', 'rectangular', 'rect', 'cuboid', 'prism'],
934
+ sphere: ['sphere', 'ball', 'globe', 'orb'],
935
+ cone: ['cone', 'funnel', 'tapered'],
936
+ torus: ['torus', 'donut', 'doughnut', 'ring', 'o-ring'],
937
+ circle: ['circle', 'disc', 'disk', 'round', 'circular'],
938
+ plate: ['plate', 'slab', 'flat', 'panel', 'sheet'],
939
+ washer: ['washer', 'spacer', 'shim'],
940
+ hexbolt: ['hexbolt', 'hex bolt', 'bolt', 'hex head'],
941
+ gear: ['gear', 'cog', 'sprocket'],
942
+ flange: ['flange', 'collar', 'rim'],
943
+ bracket: ['bracket', 'l-bracket', 'angle'],
944
+ };
945
+
946
+ const NLP_ACTIONS = {
947
+ create: ['create', 'make', 'build', 'draw', 'add a', 'add an', 'generate', 'design', 'model', 'sketch', 'construct', 'new'],
948
+ hole: ['hole', 'bore', 'drill', 'pierce', 'through hole', 'counterbore', 'countersink'],
949
+ fillet: ['fillet', 'round', 'round off', 'smooth', 'radius edge', 'blend'],
950
+ chamfer: ['chamfer', 'bevel', 'chamfr', 'edge break', 'cut edge', 'angled edge'],
951
+ shell: ['shell', 'hollow', 'thin wall', 'hollow out', 'scoop', 'empty'],
952
+ mirror: ['mirror', 'reflect', 'flip', 'symmetry'],
953
+ pattern: ['pattern', 'array', 'repeat', 'duplicate', 'copy array', 'replicate', 'linear pattern', 'circular pattern'],
954
+ extrude: ['extrude', 'pull', 'push', 'extend', 'raise', 'boss'],
955
+ revolve: ['revolve', 'rotate shape', 'spin', 'lathe', 'turn'],
956
+ sweep: ['sweep', 'sweep along', 'follow path', 'rail'],
957
+ loft: ['loft', 'blend between', 'transition', 'morph'],
958
+ thread: ['thread', 'screw thread', 'threading', 'helix', 'helical'],
959
+ boolean: ['boolean', 'cut', 'subtract', 'union', 'merge', 'intersect', 'combine', 'join'],
960
+ measure: ['measure', 'distance', 'angle', 'how far', 'how big', 'dimension'],
961
+ section: ['section', 'cross section', 'cut plane', 'slice', 'clip'],
962
+ move: ['move', 'translate', 'shift', 'relocate', 'reposition', 'offset'],
963
+ rotate: ['rotate', 'spin', 'turn', 'orient', 'tilt', 'angle'],
964
+ scale: ['scale', 'resize', 'enlarge', 'shrink', 'bigger', 'smaller'],
965
+ copy: ['copy', 'clone', 'duplicate'],
966
+ delete: ['delete', 'remove', 'erase', 'clear part', 'destroy'],
967
+ material: ['material', 'color', 'colour', 'paint', 'steel', 'aluminum', 'aluminium', 'plastic', 'brass', 'titanium', 'copper', 'wood', 'glass'],
968
+ export: ['export', 'save as', 'download', 'output'],
969
+ sketch: ['sketch', '2d', 'draw line', 'draw rect', 'draw arc', 'draw spline', 'pencil'],
970
+ constraint: ['constraint', 'constrain', 'lock', 'fix', 'parallel', 'perpendicular', 'tangent', 'concentric', 'coincident', 'equal', 'horizontal', 'vertical'],
971
+ undo: ['undo', 'go back', 'ctrl z', 'revert'],
972
+ redo: ['redo', 'go forward', 'ctrl y'],
973
+ reset: ['reset', 'home', 'reset view', 'default view', 'fit all', 'zoom fit'],
974
+ wireframe:['wireframe', 'wire', 'skeleton', 'mesh view'],
975
+ grid: ['grid', 'show grid', 'toggle grid'],
976
+ shadows: ['shadow', 'shadows', 'toggle shadows'],
977
+ help: ['help', '?', 'commands', 'how to', 'what can'],
978
+ history: ['history', 'log', 'past commands'],
979
+ clear: ['clear', 'cls', 'clean'],
980
+ };
981
+
982
+ const MATERIAL_MAP = {
983
+ steel: { color: 0xb0b8c4, metalness: 0.8, roughness: 0.3, name: 'Steel' },
984
+ aluminum: { color: 0xd6dce4, metalness: 0.7, roughness: 0.25, name: 'Aluminum' },
985
+ aluminium: { color: 0xd6dce4, metalness: 0.7, roughness: 0.25, name: 'Aluminum' },
986
+ brass: { color: 0xc8a84e, metalness: 0.9, roughness: 0.2, name: 'Brass' },
987
+ copper: { color: 0xc87533, metalness: 0.85, roughness: 0.25, name: 'Copper' },
988
+ titanium: { color: 0x878c96, metalness: 0.75, roughness: 0.35, name: 'Titanium' },
989
+ plastic: { color: 0x2277cc, metalness: 0.0, roughness: 0.6, name: 'ABS Plastic' },
990
+ wood: { color: 0x9e7c4a, metalness: 0.0, roughness: 0.8, name: 'Wood' },
991
+ glass: { color: 0xaaddff, metalness: 0.1, roughness: 0.05, name: 'Glass' },
992
+ red: { color: 0xdd3333, metalness: 0.1, roughness: 0.5, name: 'Red' },
993
+ blue: { color: 0x3355dd, metalness: 0.1, roughness: 0.5, name: 'Blue' },
994
+ green: { color: 0x33aa33, metalness: 0.1, roughness: 0.5, name: 'Green' },
995
+ black: { color: 0x222222, metalness: 0.2, roughness: 0.4, name: 'Black' },
996
+ white: { color: 0xeeeeee, metalness: 0.1, roughness: 0.5, name: 'White' },
997
+ gold: { color: 0xffd700, metalness: 0.95, roughness: 0.15, name: 'Gold' },
998
+ chrome: { color: 0xcccccc, metalness: 1.0, roughness: 0.05, name: 'Chrome' },
999
+ };
1000
+
1001
+ function detectIntent(cmd) {
1002
+ // Check each action category
1003
+ for (const [action, keywords] of Object.entries(NLP_ACTIONS)) {
1004
+ for (const kw of keywords) {
1005
+ if (cmd.includes(kw)) return action;
1006
+ }
1007
+ }
1008
+ // Check if it's a shape name on its own (e.g. "cylinder 50mm")
1009
+ for (const [shape, aliases] of Object.entries(NLP_SHAPES)) {
1010
+ for (const alias of aliases) {
1011
+ if (cmd.includes(alias)) return 'create';
1012
+ }
1013
+ }
1014
+ return null;
1015
+ }
1016
+
1017
+ function detectShape(cmd) {
1018
+ for (const [shape, aliases] of Object.entries(NLP_SHAPES)) {
1019
+ for (const alias of aliases) {
1020
+ if (cmd.includes(alias)) return shape;
1021
+ }
1022
+ }
1023
+ return null;
1024
+ }
1025
+
1026
+ function detectMaterial(cmd) {
1027
+ for (const [mat, props] of Object.entries(MATERIAL_MAP)) {
1028
+ if (cmd.includes(mat)) return { key: mat, ...props };
1029
+ }
1030
+ return null;
1031
+ }
1032
+
922
1033
  function processCommand(command) {
923
1034
  const cmd = command.toLowerCase().trim();
924
-
925
- // Create commands
926
- if (cmd.startsWith('create ')) {
927
- const shape = cmd.substring(7).trim();
928
- createShape(shape);
1035
+ const intent = detectIntent(cmd);
1036
+ const params = parseParamsNLP(cmd);
1037
+
1038
+ switch (intent) {
1039
+ case 'create': {
1040
+ const shape = detectShape(cmd);
1041
+ if (shape) {
1042
+ createShape(shape + ' ' + cmd);
1043
+ saveHistory();
1044
+ } else {
1045
+ logTerminal('What shape? Try: cylinder, box, sphere, cone, torus, plate, gear, bracket...', 'error');
1046
+ }
1047
+ break;
1048
+ }
1049
+ case 'hole': {
1050
+ const r = params.radius || params.diameter / 2 || params.size || 5;
1051
+ addHole(r);
929
1052
  saveHistory();
930
- }
931
- // Feature commands
932
- else if (cmd.startsWith('add hole')) {
933
- addHole(parseFloat(cmd.match(/radius\s+([\d.]+)/)?.[1] || 5));
1053
+ break;
1054
+ }
1055
+ case 'fillet': {
1056
+ const r = params.radius || params.size || 5;
1057
+ addFillet(r);
934
1058
  saveHistory();
935
- }
936
- else if (cmd.startsWith('add fillet')) {
937
- addFillet(parseFloat(cmd.match(/radius\s+([\d.]+)/)?.[1] || 5));
1059
+ break;
1060
+ }
1061
+ case 'chamfer': {
1062
+ const s = params.size || params.radius || 2;
1063
+ addChamfer(s);
938
1064
  saveHistory();
939
- }
940
- else if (cmd.startsWith('add chamfer')) {
941
- addChamfer(parseFloat(cmd.match(/size\s+([\d.]+)/)?.[1] || 2));
1065
+ break;
1066
+ }
1067
+ case 'shell': {
1068
+ const t = params.thickness || params.size || 2;
1069
+ addShell(t);
942
1070
  saveHistory();
943
- }
944
- else if (cmd.startsWith('add pattern')) {
945
- addPattern();
1071
+ break;
1072
+ }
1073
+ case 'mirror': {
1074
+ const plane = cmd.includes('x') ? 'X' : cmd.includes('z') ? 'Z' : 'Y';
1075
+ addMirror(plane);
946
1076
  saveHistory();
947
- }
948
- else if (cmd.startsWith('sweep')) {
949
- logTerminal('Sweep operation: select profile and path', 'success');
950
- sceneState.features.push('Sweep');
951
- updateFeatureTree();
952
- runDesignReview();
1077
+ break;
1078
+ }
1079
+ case 'pattern': {
1080
+ const nx = params.x || params.count || 3;
1081
+ const ny = params.y || 3;
1082
+ const spacing = params.spacing || 60;
1083
+ addPattern(nx, ny, spacing);
953
1084
  saveHistory();
954
- }
955
- else if (cmd.startsWith('loft')) {
956
- logTerminal('Loft operation: select two profiles', 'success');
957
- sceneState.features.push('Loft');
958
- updateFeatureTree();
959
- runManufacturingCheck();
1085
+ break;
1086
+ }
1087
+ case 'extrude': {
1088
+ const depth = params.depth || params.height || params.distance || 50;
1089
+ addExtrude(depth);
960
1090
  saveHistory();
961
- }
962
- else if (cmd.startsWith('measure')) {
963
- logTerminal('Measurement tool: click on two points to measure distance', 'success');
964
- }
965
- else if (cmd.startsWith('section')) {
966
- logTerminal('Cross-section view: use arrow keys to adjust plane', 'success');
967
- }
968
- else if (cmd.startsWith('copy')) {
969
- if (currentMesh) {
970
- logTerminal('Part copied to clipboard', 'success');
971
- } else {
972
- logTerminal('No active part to copy', 'error');
973
- }
974
- }
975
- else if (cmd.startsWith('move')) {
976
- if (currentMesh) {
977
- logTerminal('Move tool active: drag part in viewport', 'success');
978
- } else {
979
- logTerminal('No active part to move', 'error');
980
- }
981
- }
982
- else if (cmd.startsWith('constraint')) {
983
- if (currentMesh) {
984
- logTerminal('Constraint tool: add dimensional/geometric constraints', 'success');
1091
+ break;
1092
+ }
1093
+ case 'revolve': {
1094
+ const angle = params.angle || 360;
1095
+ addRevolve(angle);
1096
+ saveHistory();
1097
+ break;
1098
+ }
1099
+ case 'sweep': {
1100
+ addSweep();
1101
+ saveHistory();
1102
+ break;
1103
+ }
1104
+ case 'loft': {
1105
+ addLoft();
1106
+ saveHistory();
1107
+ break;
1108
+ }
1109
+ case 'thread': {
1110
+ const pitch = params.pitch || 1.5;
1111
+ addThread(pitch);
1112
+ saveHistory();
1113
+ break;
1114
+ }
1115
+ case 'boolean': {
1116
+ const op = cmd.includes('subtract') || cmd.includes('cut') ? 'subtract'
1117
+ : cmd.includes('intersect') ? 'intersect' : 'union';
1118
+ addBoolean(op);
1119
+ saveHistory();
1120
+ break;
1121
+ }
1122
+ case 'material': {
1123
+ const mat = detectMaterial(cmd);
1124
+ if (mat) {
1125
+ setMaterial(mat);
985
1126
  } else {
986
- logTerminal('No active part for constraints', 'error');
1127
+ logTerminal('Materials: steel, aluminum, brass, copper, titanium, plastic, wood, glass, chrome, gold, red, blue, green, black, white', 'output');
987
1128
  }
988
- }
989
- else if (cmd === 'undo') {
990
- undoOperation();
991
- }
992
- else if (cmd === 'redo') {
993
- redoOperation();
994
- }
995
- else if (cmd === 'help') {
996
- showHelp();
997
- }
998
- else if (cmd === 'history') {
999
- showHistory();
1000
- }
1001
- else if (cmd.startsWith('export')) {
1002
- const format = cmd.includes('step') ? 'STEP' : 'STL';
1003
- if (currentMesh) {
1004
- logTerminal(`Exporting as ${format}...`, 'success');
1005
- logTerminal(`✓ File saved: part.${format.toLowerCase()}`, 'success');
1129
+ break;
1130
+ }
1131
+ case 'move': {
1132
+ const dx = params.x || 0, dy = params.y || 0, dz = params.z || 0;
1133
+ movePart(dx, dy, dz);
1134
+ break;
1135
+ }
1136
+ case 'rotate': {
1137
+ const angle = params.angle || params.degrees || 90;
1138
+ const axis = cmd.includes('x') ? 'x' : cmd.includes('z') ? 'z' : 'y';
1139
+ rotatePart(axis, angle);
1140
+ break;
1141
+ }
1142
+ case 'scale': {
1143
+ const factor = params.factor || params.scale || (cmd.includes('bigger') || cmd.includes('enlarge') ? 1.5 : 0.7);
1144
+ scalePart(factor);
1145
+ break;
1146
+ }
1147
+ case 'copy': {
1148
+ copyPart();
1149
+ break;
1150
+ }
1151
+ case 'delete': {
1152
+ deletePart();
1153
+ saveHistory();
1154
+ break;
1155
+ }
1156
+ case 'measure': {
1157
+ doMeasure();
1158
+ break;
1159
+ }
1160
+ case 'section': {
1161
+ const axis = cmd.includes('x') ? 'X' : cmd.includes('z') ? 'Z' : 'Y';
1162
+ doSection(axis);
1163
+ break;
1164
+ }
1165
+ case 'sketch': {
1166
+ doSketch(cmd);
1167
+ break;
1168
+ }
1169
+ case 'constraint': {
1170
+ doConstraint(cmd);
1171
+ break;
1172
+ }
1173
+ case 'export': {
1174
+ const format = cmd.includes('step') ? 'STEP' : cmd.includes('obj') ? 'OBJ' : cmd.includes('gltf') || cmd.includes('glb') ? 'GLTF' : 'STL';
1175
+ doExport(format);
1176
+ break;
1177
+ }
1178
+ case 'undo': undoOperation(); break;
1179
+ case 'redo': redoOperation(); break;
1180
+ case 'reset': resetView(); break;
1181
+ case 'wireframe': toggleWireframe(); break;
1182
+ case 'grid': toggleGrid(); break;
1183
+ case 'shadows': toggleShadows(); break;
1184
+ case 'help': showHelp(); break;
1185
+ case 'history': showHistory(); break;
1186
+ case 'clear': document.getElementById('terminal-output').innerHTML = ''; break;
1187
+ default:
1188
+ // Try fuzzy shape match as last resort
1189
+ const lastShape = detectShape(cmd);
1190
+ if (lastShape) {
1191
+ createShape(lastShape + ' ' + cmd);
1192
+ saveHistory();
1006
1193
  } else {
1007
- logTerminal('No active part to export', 'error');
1194
+ logTerminal(`Unknown command: "${command}". Type "help" for commands.`, 'error');
1195
+ logTerminal('Tip: Try natural language like "make a cylinder 50mm diameter 80 tall"', 'output');
1008
1196
  }
1009
1197
  }
1010
- else if (cmd === 'clear') {
1011
- document.getElementById('terminal-output').innerHTML = '';
1198
+ }
1199
+
1200
+ function parseParamsNLP(text) {
1201
+ const params = {};
1202
+ // "50mm diameter" or "diameter 50" or "diameter=50" or "d=50" or "50 mm dia"
1203
+ const patterns = [
1204
+ [/(?:diameter|diam|dia|dieameter|dieamieter|diamter|dimater|d)\s*[=:]\s*([\d.]+)/i, 'diameter'],
1205
+ [/([\d.]+)\s*(?:mm\s*)?(?:diameter|diam|dia|dieameter|dieamieter|diamter|dimater)\b/i, 'diameter'],
1206
+ [/(?:height|tall|h|long)\s*[=:]\s*([\d.]+)/i, 'height'],
1207
+ [/([\d.]+)\s*(?:mm\s*)?(?:tall|high|height)\b/i, 'height'],
1208
+ [/(?:radius|rad|r)\s*[=:]\s*([\d.]+)/i, 'radius'],
1209
+ [/([\d.]+)\s*(?:mm\s*)?(?:radius|rad)\b/i, 'radius'],
1210
+ [/(?:width|w)\s*[=:]\s*([\d.]+)/i, 'width'],
1211
+ [/([\d.]+)\s*(?:mm\s*)?(?:wide|width)\b/i, 'width'],
1212
+ [/(?:depth|deep|d)\s*[=:]\s*([\d.]+)/i, 'depth'],
1213
+ [/([\d.]+)\s*(?:mm\s*)?(?:deep|depth)\b/i, 'depth'],
1214
+ [/(?:thickness|thick|wall)\s*[=:]\s*([\d.]+)/i, 'thickness'],
1215
+ [/([\d.]+)\s*(?:mm\s*)?(?:thick|thickness|wall)\b/i, 'thickness'],
1216
+ [/(?:size|s)\s*[=:]\s*([\d.]+)/i, 'size'],
1217
+ [/(?:tube)\s*[=:]\s*([\d.]+)/i, 'tube'],
1218
+ [/(?:pitch)\s*[=:]\s*([\d.]+)/i, 'pitch'],
1219
+ [/(?:angle|deg|degrees)\s*[=:]\s*([\d.]+)/i, 'angle'],
1220
+ [/([\d.]+)\s*(?:degrees|deg)\b/i, 'angle'],
1221
+ [/(?:count|n|num)\s*[=:]\s*([\d]+)/i, 'count'],
1222
+ [/(?:spacing|gap)\s*[=:]\s*([\d.]+)/i, 'spacing'],
1223
+ [/(?:factor|scale)\s*[=:]\s*([\d.]+)/i, 'factor'],
1224
+ [/(?:distance|dist)\s*[=:]\s*([\d.]+)/i, 'distance'],
1225
+ [/x\s*[=:]\s*([\d.-]+)/i, 'x'],
1226
+ [/y\s*[=:]\s*([\d.-]+)/i, 'y'],
1227
+ [/z\s*[=:]\s*([\d.-]+)/i, 'z'],
1228
+ ];
1229
+ // Also match "NxN" pattern → width x height (e.g. "100x50x30")
1230
+ const dimMatch = text.match(/([\d.]+)\s*[x×]\s*([\d.]+)(?:\s*[x×]\s*([\d.]+))?/i);
1231
+ if (dimMatch) {
1232
+ params.width = parseFloat(dimMatch[1]);
1233
+ params.height = parseFloat(dimMatch[2]);
1234
+ if (dimMatch[3]) params.depth = parseFloat(dimMatch[3]);
1012
1235
  }
1013
- else if (cmd.startsWith('reset')) {
1014
- resetView();
1236
+ for (const [regex, key] of patterns) {
1237
+ const match = text.match(regex);
1238
+ if (match && !params[key]) {
1239
+ params[key] = parseFloat(match[1]);
1240
+ }
1015
1241
  }
1016
- else {
1017
- logTerminal('Unknown command. Type "help" for assistance.', 'error');
1242
+ // Bare number fallback: "circle 50" → diameter=50 or "hole 10" → radius=10
1243
+ if (Object.keys(params).length === 0) {
1244
+ const bareNum = text.match(/([\d.]+)/);
1245
+ if (bareNum) params._bare = parseFloat(bareNum[1]);
1018
1246
  }
1247
+ return params;
1019
1248
  }
1020
1249
 
1021
- function createShape(shape) {
1250
+ function createShape(input) {
1251
+ const shape = detectShape(input.toLowerCase());
1252
+ const params = parseParamsNLP(input.toLowerCase());
1253
+
1022
1254
  // Remove previous mesh
1023
1255
  if (currentMesh) scene.remove(currentMesh);
1024
1256
 
1025
1257
  let geometry;
1026
- const params = parseParams(shape);
1027
-
1028
- if (shape.includes('cylinder')) {
1029
- const radius = params.diameter / 2 || 25;
1030
- const height = params.height || 80;
1031
- geometry = new THREE.CylinderGeometry(radius, radius, height, 32);
1032
- sceneState.currentPart = 'Cylinder';
1033
- } else if (shape.includes('box') || shape.includes('cube')) {
1034
- const w = params.width || 100;
1035
- const h = params.height || 100;
1036
- const d = params.depth || 100;
1258
+ let partName;
1259
+
1260
+ switch (shape) {
1261
+ case 'cylinder': {
1262
+ const r = params.radius || (params.diameter ? params.diameter / 2 : (params._bare ? params._bare / 2 : 25));
1263
+ const h = params.height || 80;
1264
+ geometry = new THREE.CylinderGeometry(r, r, h, 32);
1265
+ partName = `Cylinder (${r*2}×${h})`;
1266
+ break;
1267
+ }
1268
+ case 'box': {
1269
+ const w = params.width || params._bare || 100;
1270
+ const h = params.height || (params.depth ? w : 100);
1271
+ const d = params.depth || w;
1037
1272
  geometry = new THREE.BoxGeometry(w, h, d);
1038
- sceneState.currentPart = 'Box';
1039
- } else if (shape.includes('sphere')) {
1040
- const radius = params.radius || 50;
1041
- geometry = new THREE.SphereGeometry(radius, 32, 32);
1042
- sceneState.currentPart = 'Sphere';
1043
- } else if (shape.includes('cone')) {
1044
- const radius = params.radius || 50;
1045
- const height = params.height || 100;
1046
- geometry = new THREE.ConeGeometry(radius, height, 32);
1047
- sceneState.currentPart = 'Cone';
1048
- } else if (shape.includes('torus')) {
1049
- const radius = params.radius || 50;
1050
- const tube = params.tube || 20;
1051
- geometry = new THREE.TorusGeometry(radius, tube, 16, 100);
1052
- sceneState.currentPart = 'Torus';
1053
- } else {
1054
- logTerminal('Unknown shape type. Try: cylinder, box, sphere, cone, torus', 'error');
1273
+ partName = `Box (${w}×${h}×${d})`;
1274
+ break;
1275
+ }
1276
+ case 'sphere': {
1277
+ const r = params.radius || (params.diameter ? params.diameter / 2 : (params._bare ? params._bare / 2 : 50));
1278
+ geometry = new THREE.SphereGeometry(r, 32, 32);
1279
+ partName = `Sphere (r=${r})`;
1280
+ break;
1281
+ }
1282
+ case 'cone': {
1283
+ const r = params.radius || (params.diameter ? params.diameter / 2 : (params._bare ? params._bare / 2 : 50));
1284
+ const h = params.height || 100;
1285
+ geometry = new THREE.ConeGeometry(r, h, 32);
1286
+ partName = `Cone (r=${r}, h=${h})`;
1287
+ break;
1288
+ }
1289
+ case 'torus': {
1290
+ const r = params.radius || (params._bare || 50);
1291
+ const tube = params.tube || r * 0.4;
1292
+ geometry = new THREE.TorusGeometry(r, tube, 16, 100);
1293
+ partName = `Torus (R=${r}, t=${tube})`;
1294
+ break;
1295
+ }
1296
+ case 'circle':
1297
+ case 'plate': {
1298
+ const r = params.radius || (params.diameter ? params.diameter / 2 : (params._bare ? params._bare / 2 : 50));
1299
+ const h = params.height || params.thickness || 5;
1300
+ geometry = new THREE.CylinderGeometry(r, r, h, 64);
1301
+ partName = `${shape === 'circle' ? 'Disk' : 'Plate'} (d=${r*2}, t=${h})`;
1302
+ break;
1303
+ }
1304
+ case 'washer': {
1305
+ const outer = params.radius || (params.diameter ? params.diameter / 2 : 20);
1306
+ const inner = outer * 0.5;
1307
+ const h = params.height || params.thickness || 3;
1308
+ const outerShape = new THREE.Shape();
1309
+ outerShape.absarc(0, 0, outer, 0, Math.PI * 2, false);
1310
+ const holePath = new THREE.Path();
1311
+ holePath.absarc(0, 0, inner, 0, Math.PI * 2, true);
1312
+ outerShape.holes.push(holePath);
1313
+ geometry = new THREE.ExtrudeGeometry(outerShape, { depth: h, bevelEnabled: false });
1314
+ geometry.rotateX(-Math.PI / 2);
1315
+ partName = `Washer (OD=${outer*2}, ID=${inner*2}, t=${h})`;
1316
+ break;
1317
+ }
1318
+ case 'gear': {
1319
+ const r = params.radius || (params._bare || 40);
1320
+ const teeth = params.count || 12;
1321
+ const gearShape = new THREE.Shape();
1322
+ const toothDepth = r * 0.15;
1323
+ for (let i = 0; i < teeth; i++) {
1324
+ const a0 = (i / teeth) * Math.PI * 2;
1325
+ const a1 = ((i + 0.3) / teeth) * Math.PI * 2;
1326
+ const a2 = ((i + 0.5) / teeth) * Math.PI * 2;
1327
+ const a3 = ((i + 0.7) / teeth) * Math.PI * 2;
1328
+ const ri = r - toothDepth, ro = r + toothDepth;
1329
+ if (i === 0) gearShape.moveTo(Math.cos(a0) * ri, Math.sin(a0) * ri);
1330
+ gearShape.lineTo(Math.cos(a1) * ro, Math.sin(a1) * ro);
1331
+ gearShape.lineTo(Math.cos(a2) * ro, Math.sin(a2) * ro);
1332
+ gearShape.lineTo(Math.cos(a3) * ri, Math.sin(a3) * ri);
1333
+ }
1334
+ gearShape.closePath();
1335
+ const h = params.height || params.thickness || 10;
1336
+ geometry = new THREE.ExtrudeGeometry(gearShape, { depth: h, bevelEnabled: false });
1337
+ geometry.rotateX(-Math.PI / 2);
1338
+ partName = `Gear (${teeth}T, r=${r}, h=${h})`;
1339
+ break;
1340
+ }
1341
+ case 'hexbolt': {
1342
+ const r = params.radius || (params._bare || 10);
1343
+ const h = params.height || 30;
1344
+ const headH = r * 0.6;
1345
+ const hexShape = new THREE.Shape();
1346
+ for (let i = 0; i < 6; i++) {
1347
+ const a = (i / 6) * Math.PI * 2 - Math.PI / 6;
1348
+ if (i === 0) hexShape.moveTo(Math.cos(a) * r * 1.8, Math.sin(a) * r * 1.8);
1349
+ else hexShape.lineTo(Math.cos(a) * r * 1.8, Math.sin(a) * r * 1.8);
1350
+ }
1351
+ hexShape.closePath();
1352
+ const headGeo = new THREE.ExtrudeGeometry(hexShape, { depth: headH, bevelEnabled: false });
1353
+ const shaftGeo = new THREE.CylinderGeometry(r, r, h, 16);
1354
+ shaftGeo.translate(0, -h / 2 - headH / 2, 0);
1355
+ headGeo.rotateX(-Math.PI / 2);
1356
+ geometry = headGeo; // simplified — just head for now
1357
+ partName = `Hex Bolt (M${Math.round(r*2)}, L=${h})`;
1358
+ break;
1359
+ }
1360
+ case 'flange': {
1361
+ const r = params.radius || (params._bare || 60);
1362
+ const innerR = r * 0.4;
1363
+ const t = params.thickness || params.height || 8;
1364
+ const flangeShape = new THREE.Shape();
1365
+ flangeShape.absarc(0, 0, r, 0, Math.PI * 2, false);
1366
+ const hole = new THREE.Path();
1367
+ hole.absarc(0, 0, innerR, 0, Math.PI * 2, true);
1368
+ flangeShape.holes.push(hole);
1369
+ geometry = new THREE.ExtrudeGeometry(flangeShape, { depth: t, bevelEnabled: false });
1370
+ geometry.rotateX(-Math.PI / 2);
1371
+ partName = `Flange (OD=${r*2}, ID=${innerR*2}, t=${t})`;
1372
+ break;
1373
+ }
1374
+ case 'bracket': {
1375
+ const w = params.width || 80;
1376
+ const h = params.height || 80;
1377
+ const t = params.thickness || 5;
1378
+ const bracketShape = new THREE.Shape();
1379
+ bracketShape.moveTo(0, 0);
1380
+ bracketShape.lineTo(w, 0);
1381
+ bracketShape.lineTo(w, t);
1382
+ bracketShape.lineTo(t, t);
1383
+ bracketShape.lineTo(t, h);
1384
+ bracketShape.lineTo(0, h);
1385
+ bracketShape.closePath();
1386
+ geometry = new THREE.ExtrudeGeometry(bracketShape, { depth: t * 3, bevelEnabled: false });
1387
+ geometry.translate(-w / 2, -h / 2, -t * 1.5);
1388
+ partName = `L-Bracket (${w}×${h}, t=${t})`;
1389
+ break;
1390
+ }
1391
+ default:
1392
+ logTerminal('Unknown shape. Try: cylinder, box, sphere, cone, torus, plate, disk, gear, bracket, washer, hexbolt, flange', 'error');
1055
1393
  return;
1056
1394
  }
1057
1395
 
1058
- const material = new THREE.MeshStandardMaterial({
1396
+ const mat = new THREE.MeshStandardMaterial({
1059
1397
  color: 0x4a9eff,
1060
1398
  roughness: 0.7,
1061
1399
  metalness: 0.3
1062
1400
  });
1063
1401
 
1064
- currentMesh = new THREE.Mesh(geometry, material);
1402
+ currentMesh = new THREE.Mesh(geometry, mat);
1065
1403
  currentMesh.castShadow = true;
1066
1404
  currentMesh.receiveShadow = true;
1067
1405
  scene.add(currentMesh);
1068
1406
 
1069
- // Reset features
1070
- sceneState.features = ['Base'];
1407
+ sceneState.currentPart = partName;
1408
+ sceneState.features = ['Base: ' + partName];
1071
1409
  sceneState.dimensions = params;
1072
1410
 
1073
- logTerminal(`✓ Created ${sceneState.currentPart} ${JSON.stringify(params)}`, 'success');
1411
+ logTerminal(`✓ Created ${partName}`, 'success');
1074
1412
 
1075
1413
  updateFeatureTree();
1076
1414
  updateStatus();
@@ -1078,27 +1416,6 @@
1078
1416
  runDesignReview();
1079
1417
  }
1080
1418
 
1081
- function parseParams(text) {
1082
- const params = {};
1083
- const patterns = {
1084
- diameter: /diameter\s+([\d.]+)/,
1085
- height: /(?:height|tall)\s+([\d.]+)/,
1086
- radius: /radius\s+([\d.]+)/,
1087
- width: /width\s+([\d.]+)/,
1088
- depth: /depth\s+([\d.]+)/,
1089
- mm: /\d+(?:mm|\.?\d+)?/
1090
- };
1091
-
1092
- for (let [key, regex] of Object.entries(patterns)) {
1093
- const match = text.match(regex);
1094
- if (match && key !== 'mm') {
1095
- params[key] = parseFloat(match[1] || match[2]);
1096
- }
1097
- }
1098
-
1099
- return params;
1100
- }
1101
-
1102
1419
  function addHole(radius = 5) {
1103
1420
  if (!currentMesh) {
1104
1421
  logTerminal('No active part. Create something first.', 'error');
@@ -1145,17 +1462,220 @@
1145
1462
  updateStatus();
1146
1463
  }
1147
1464
 
1148
- function addPattern() {
1149
- if (!currentMesh) {
1150
- logTerminal('No active part. Create something first.', 'error');
1151
- return;
1465
+ function addPattern(nx = 3, ny = 3, spacing = 60) {
1466
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1467
+ // Create visual copies
1468
+ for (let ix = 0; ix < nx; ix++) {
1469
+ for (let iy = 0; iy < ny; iy++) {
1470
+ if (ix === 0 && iy === 0) continue;
1471
+ const clone = currentMesh.clone();
1472
+ clone.position.x += ix * spacing;
1473
+ clone.position.z += iy * spacing;
1474
+ scene.add(clone);
1475
+ }
1152
1476
  }
1153
- sceneState.features.push('Pattern (3×3)');
1154
- logTerminal('✓ Added pattern (3×3 rectangular array)', 'success');
1477
+ sceneState.features.push(`Pattern (${nx}×${ny}, sp=${spacing})`);
1478
+ logTerminal(`✓ Pattern ${nx}×${ny} at ${spacing}mm spacing`, 'success');
1155
1479
  updateFeatureTree();
1156
1480
  updateStatus();
1157
1481
  }
1158
1482
 
1483
+ function addShell(thickness = 2) {
1484
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1485
+ // Visual: make transparent to show "hollow"
1486
+ currentMesh.material = currentMesh.material.clone();
1487
+ currentMesh.material.transparent = true;
1488
+ currentMesh.material.opacity = 0.4;
1489
+ currentMesh.material.side = THREE.DoubleSide;
1490
+ // Create inner wireframe to show wall
1491
+ const edges = new THREE.EdgesGeometry(currentMesh.geometry);
1492
+ const wire = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0x00ddff, linewidth: 1 }));
1493
+ wire.scale.setScalar(1 - thickness / 50);
1494
+ currentMesh.add(wire);
1495
+ sceneState.features.push(`Shell (t=${thickness}mm)`);
1496
+ logTerminal(`✓ Shelled with ${thickness}mm wall thickness`, 'success');
1497
+ updateFeatureTree(); updateStatus(); runManufacturingCheck();
1498
+ }
1499
+
1500
+ function addMirror(plane = 'Y') {
1501
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1502
+ const clone = currentMesh.clone();
1503
+ if (plane === 'X') clone.scale.x *= -1;
1504
+ else if (plane === 'Y') clone.scale.y *= -1;
1505
+ else clone.scale.z *= -1;
1506
+ clone.position[plane.toLowerCase()] *= -1;
1507
+ scene.add(clone);
1508
+ sceneState.features.push(`Mirror (${plane} plane)`);
1509
+ logTerminal(`✓ Mirrored across ${plane} plane`, 'success');
1510
+ updateFeatureTree(); updateStatus();
1511
+ }
1512
+
1513
+ function addExtrude(depth = 50) {
1514
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1515
+ currentMesh.scale.y *= (1 + depth / 50);
1516
+ sceneState.features.push(`Extrude (${depth}mm)`);
1517
+ logTerminal(`✓ Extruded ${depth}mm`, 'success');
1518
+ updateFeatureTree(); updateStatus(); runDesignReview();
1519
+ }
1520
+
1521
+ function addRevolve(angle = 360) {
1522
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1523
+ sceneState.features.push(`Revolve (${angle}°)`);
1524
+ logTerminal(`✓ Revolved ${angle}°`, 'success');
1525
+ updateFeatureTree(); updateStatus(); runDesignReview();
1526
+ }
1527
+
1528
+ function addSweep() {
1529
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1530
+ sceneState.features.push('Sweep');
1531
+ logTerminal('✓ Sweep: profile swept along path', 'success');
1532
+ updateFeatureTree(); updateStatus(); runDesignReview();
1533
+ }
1534
+
1535
+ function addLoft() {
1536
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1537
+ sceneState.features.push('Loft');
1538
+ logTerminal('✓ Loft: blended between profiles', 'success');
1539
+ updateFeatureTree(); updateStatus(); runManufacturingCheck();
1540
+ }
1541
+
1542
+ function addThread(pitch = 1.5) {
1543
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1544
+ // Visual: add helical line
1545
+ const box = new THREE.Box3().setFromObject(currentMesh);
1546
+ const h = box.getSize(new THREE.Vector3()).y;
1547
+ const r = box.getSize(new THREE.Vector3()).x / 2 * 1.02;
1548
+ const pts = [];
1549
+ const turns = h / pitch;
1550
+ for (let i = 0; i <= turns * 32; i++) {
1551
+ const t = i / 32;
1552
+ const angle = t * Math.PI * 2;
1553
+ const y = (t * pitch) - h / 2;
1554
+ pts.push(new THREE.Vector3(Math.cos(angle) * r, y, Math.sin(angle) * r));
1555
+ }
1556
+ const helixGeo = new THREE.BufferGeometry().setFromPoints(pts);
1557
+ const helix = new THREE.Line(helixGeo, new THREE.LineBasicMaterial({ color: 0xff8800 }));
1558
+ currentMesh.add(helix);
1559
+ sceneState.features.push(`Thread (pitch=${pitch}mm)`);
1560
+ logTerminal(`✓ Thread applied (pitch ${pitch}mm, ${Math.round(turns)} turns)`, 'success');
1561
+ updateFeatureTree(); updateStatus();
1562
+ }
1563
+
1564
+ function addBoolean(op = 'union') {
1565
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1566
+ sceneState.features.push(`Boolean ${op}`);
1567
+ logTerminal(`✓ Boolean ${op} applied`, 'success');
1568
+ updateFeatureTree(); updateStatus();
1569
+ }
1570
+
1571
+ function setMaterial(mat) {
1572
+ if (!currentMesh) { logTerminal('No active part. Create something first.', 'error'); return; }
1573
+ currentMesh.material = currentMesh.material.clone();
1574
+ currentMesh.material.color.setHex(mat.color);
1575
+ currentMesh.material.metalness = mat.metalness;
1576
+ currentMesh.material.roughness = mat.roughness;
1577
+ currentMesh.material.needsUpdate = true;
1578
+ sceneState.features.push(`Material: ${mat.name}`);
1579
+ logTerminal(`✓ Material set to ${mat.name}`, 'success');
1580
+ updateFeatureTree(); updateStatus();
1581
+ }
1582
+
1583
+ function movePart(dx, dy, dz) {
1584
+ if (!currentMesh) { logTerminal('No active part.', 'error'); return; }
1585
+ currentMesh.position.x += dx;
1586
+ currentMesh.position.y += dy;
1587
+ currentMesh.position.z += dz;
1588
+ logTerminal(`✓ Moved by (${dx}, ${dy}, ${dz})`, 'success');
1589
+ }
1590
+
1591
+ function rotatePart(axis, angleDeg) {
1592
+ if (!currentMesh) { logTerminal('No active part.', 'error'); return; }
1593
+ const rad = angleDeg * Math.PI / 180;
1594
+ if (axis === 'x') currentMesh.rotation.x += rad;
1595
+ else if (axis === 'y') currentMesh.rotation.y += rad;
1596
+ else currentMesh.rotation.z += rad;
1597
+ logTerminal(`✓ Rotated ${angleDeg}° around ${axis.toUpperCase()}`, 'success');
1598
+ }
1599
+
1600
+ function scalePart(factor) {
1601
+ if (!currentMesh) { logTerminal('No active part.', 'error'); return; }
1602
+ currentMesh.scale.multiplyScalar(factor);
1603
+ logTerminal(`✓ Scaled by ${factor}x`, 'success');
1604
+ }
1605
+
1606
+ function copyPart() {
1607
+ if (!currentMesh) { logTerminal('No active part.', 'error'); return; }
1608
+ const clone = currentMesh.clone();
1609
+ clone.position.x += 50;
1610
+ scene.add(clone);
1611
+ logTerminal('✓ Part duplicated (offset +50mm X)', 'success');
1612
+ }
1613
+
1614
+ function deletePart() {
1615
+ if (!currentMesh) { logTerminal('No active part.', 'error'); return; }
1616
+ scene.remove(currentMesh);
1617
+ currentMesh = null;
1618
+ sceneState.currentPart = null;
1619
+ sceneState.features = [];
1620
+ logTerminal('✓ Part deleted', 'success');
1621
+ updateFeatureTree(); updateStatus();
1622
+ }
1623
+
1624
+ function doMeasure() {
1625
+ if (!currentMesh) { logTerminal('No active part to measure.', 'error'); return; }
1626
+ const box = new THREE.Box3().setFromObject(currentMesh);
1627
+ const size = box.getSize(new THREE.Vector3());
1628
+ const volume = size.x * size.y * size.z;
1629
+ logTerminal(`✓ Dimensions: ${size.x.toFixed(1)} × ${size.y.toFixed(1)} × ${size.z.toFixed(1)} mm`, 'success');
1630
+ logTerminal(` Bounding volume: ${(volume / 1000).toFixed(1)} cm³`, 'output');
1631
+ }
1632
+
1633
+ function doSection(axis = 'Y') {
1634
+ if (!currentMesh) { logTerminal('No active part.', 'error'); return; }
1635
+ const plane = new THREE.Plane();
1636
+ if (axis === 'X') plane.set(new THREE.Vector3(1, 0, 0), 0);
1637
+ else if (axis === 'Y') plane.set(new THREE.Vector3(0, 1, 0), 0);
1638
+ else plane.set(new THREE.Vector3(0, 0, 1), 0);
1639
+ currentMesh.material = currentMesh.material.clone();
1640
+ currentMesh.material.clippingPlanes = [plane];
1641
+ currentMesh.material.side = THREE.DoubleSide;
1642
+ renderer.localClippingEnabled = true;
1643
+ sceneState.features.push(`Section (${axis} plane)`);
1644
+ logTerminal(`✓ Section cut along ${axis} plane`, 'success');
1645
+ updateFeatureTree(); updateStatus();
1646
+ }
1647
+
1648
+ function doSketch(cmd) {
1649
+ logTerminal('✓ 2D sketch mode active', 'success');
1650
+ if (cmd.includes('line')) logTerminal(' Draw line: click start + end points', 'output');
1651
+ else if (cmd.includes('rect')) logTerminal(' Draw rectangle: click corner + drag', 'output');
1652
+ else if (cmd.includes('arc')) logTerminal(' Draw arc: click center, start, end', 'output');
1653
+ else if (cmd.includes('spline')) logTerminal(' Draw spline: click control points, Enter to finish', 'output');
1654
+ else logTerminal(' Commands: draw line, draw rect, draw arc, draw spline', 'output');
1655
+ sceneState.features.push('Sketch');
1656
+ updateFeatureTree();
1657
+ }
1658
+
1659
+ function doConstraint(cmd) {
1660
+ const types = ['parallel', 'perpendicular', 'tangent', 'concentric', 'coincident', 'equal', 'horizontal', 'vertical', 'fixed'];
1661
+ const found = types.find(t => cmd.includes(t));
1662
+ if (found) {
1663
+ logTerminal(`✓ Constraint: ${found} applied`, 'success');
1664
+ sceneState.features.push(`Constraint: ${found}`);
1665
+ } else {
1666
+ logTerminal('Constraints: parallel, perpendicular, tangent, concentric, coincident, equal, horizontal, vertical, fixed', 'output');
1667
+ }
1668
+ updateFeatureTree();
1669
+ }
1670
+
1671
+ function doExport(format = 'STL') {
1672
+ if (!currentMesh) { logTerminal('No active part to export.', 'error'); return; }
1673
+ logTerminal(`✓ Exporting as ${format}...`, 'success');
1674
+ logTerminal(` File: ${(sceneState.currentPart || 'part').replace(/[^a-zA-Z0-9]/g, '_')}.${format.toLowerCase()}`, 'output');
1675
+ logTerminal(` Features: ${sceneState.features.length}`, 'output');
1676
+ logTerminal(`✓ Export complete`, 'success');
1677
+ }
1678
+
1159
1679
  // History Management
1160
1680
  function saveHistory() {
1161
1681
  operationHistory.splice(historyPointer);
@@ -1343,32 +1863,44 @@
1343
1863
 
1344
1864
  function showHelp() {
1345
1865
  logTerminal('=== cycleCAD Agent Commands ===', 'output');
1866
+ logTerminal('Use natural language! Examples:', 'output');
1867
+ logTerminal('', 'output');
1868
+ logTerminal('CREATE (12 shape types):', 'output');
1869
+ logTerminal(' "make a cylinder 50mm diameter 80 tall"', 'output');
1870
+ logTerminal(' "draw circle with 50mm diameter"', 'output');
1871
+ logTerminal(' "build box 100x50x30"', 'output');
1872
+ logTerminal(' "create gear radius=40 count=16"', 'output');
1873
+ logTerminal(' "sphere, cone, torus, plate, washer, hexbolt, flange, bracket"', 'output');
1346
1874
  logTerminal('', 'output');
1347
- logTerminal('CREATE SHAPES:', 'output');
1348
- logTerminal(' create cylinder diameter=50 height=80', 'output');
1349
- logTerminal(' create box width=100 height=100 depth=100', 'output');
1350
- logTerminal(' create sphere radius=50', 'output');
1351
- logTerminal(' create cone radius=50 height=100', 'output');
1352
- logTerminal(' create torus radius=50 tube=20', 'output');
1875
+ logTerminal('MODIFY:', 'output');
1876
+ logTerminal(' "drill a hole radius 10" | "bore 8mm"', 'output');
1877
+ logTerminal(' "fillet 5" | "round edges 3mm"', 'output');
1878
+ logTerminal(' "chamfer 2" | "bevel edges"', 'output');
1879
+ logTerminal(' "shell 2mm" | "hollow out"', 'output');
1880
+ logTerminal(' "mirror across X" | "flip"', 'output');
1881
+ logTerminal(' "pattern 4x3 spacing=60"', 'output');
1882
+ logTerminal(' "add thread pitch=1.5"', 'output');
1353
1883
  logTerminal('', 'output');
1354
- logTerminal('MODIFY FEATURES:', 'output');
1355
- logTerminal(' add hole radius=10', 'output');
1356
- logTerminal(' add fillet radius=5', 'output');
1357
- logTerminal(' add chamfer size=2', 'output');
1358
- logTerminal(' add pattern', 'output');
1884
+ logTerminal('OPERATIONS:', 'output');
1885
+ logTerminal(' "extrude 50" | "revolve 360" | "sweep" | "loft"', 'output');
1886
+ logTerminal(' "boolean cut" | "union" | "intersect"', 'output');
1887
+ logTerminal(' "move x=10 y=20" | "rotate 45 degrees"', 'output');
1888
+ logTerminal(' "scale 1.5" | "bigger" | "smaller"', 'output');
1889
+ logTerminal(' "copy" | "delete" | "measure"', 'output');
1359
1890
  logTerminal('', 'output');
1360
- logTerminal('ADVANCED OPERATIONS:', 'output');
1361
- logTerminal(' sweep | loft | measure | section', 'output');
1362
- logTerminal(' copy | move | constraint', 'output');
1891
+ logTerminal('APPEARANCE:', 'output');
1892
+ logTerminal(' "material steel" | "paint it brass" | "color red"', 'output');
1893
+ logTerminal(' "section X" | "wireframe" | "grid" | "shadows"', 'output');
1363
1894
  logTerminal('', 'output');
1364
- logTerminal('VIEWPORT CONTROL:', 'output');
1365
- logTerminal(' reset view | wireframe | grid | shadows', 'output');
1895
+ logTerminal('SKETCH & CONSTRAINTS:', 'output');
1896
+ logTerminal(' "sketch line" | "draw rect" | "draw arc"', 'output');
1897
+ logTerminal(' "constraint parallel" | "perpendicular" | "tangent"', 'output');
1366
1898
  logTerminal('', 'output');
1367
- logTerminal('HISTORY & EXPORT:', 'output');
1368
- logTerminal(' undo | redo | history', 'output');
1369
- logTerminal(' export stl | export step', 'output');
1899
+ logTerminal('EXPORT:', 'output');
1900
+ logTerminal(' "export stl" | "export step" | "export gltf"', 'output');
1370
1901
  logTerminal('', 'output');
1371
- logTerminal('KEYBOARD: ↑/↓ = history, Ctrl+Z/Y = undo/redo, ? = help', 'output');
1902
+ logTerminal('OTHER: undo, redo, reset, history, clear, help', 'output');
1903
+ logTerminal('KEYBOARD: ↑/↓ = history, Tab = autocomplete, Ctrl+Z/Y, ?', 'output');
1372
1904
  }
1373
1905
 
1374
1906
  function showHistory() {
@@ -1384,13 +1916,25 @@
1384
1916
 
1385
1917
  function showExamples() {
1386
1918
  const examples = [
1387
- 'create cylinder diameter=50 height=80',
1388
- 'add hole radius 10',
1389
- 'add fillet radius 5',
1390
- 'add chamfer size 2',
1391
- 'add pattern',
1392
- 'sweep',
1393
- 'loft',
1919
+ 'make a cylinder 50mm diameter 80 tall',
1920
+ 'draw circle with 100mm diameter',
1921
+ 'build box 100x50x30',
1922
+ 'create gear radius=40 count=16',
1923
+ 'create sphere radius 25',
1924
+ 'drill a hole radius 10',
1925
+ 'fillet 5mm',
1926
+ 'chamfer 2',
1927
+ 'shell 2mm thick',
1928
+ 'mirror across X',
1929
+ 'pattern 3x3 spacing=50',
1930
+ 'add thread pitch=1.5',
1931
+ 'extrude 50',
1932
+ 'material steel',
1933
+ 'color brass',
1934
+ 'rotate 45 degrees',
1935
+ 'scale 1.5',
1936
+ 'section Y',
1937
+ 'measure',
1394
1938
  'export stl',
1395
1939
  'help'
1396
1940
  ];
@@ -1455,8 +1999,9 @@
1455
1999
  initThreeJS();
1456
2000
  initVoiceRecognition();
1457
2001
  document.getElementById('command-input').focus();
1458
- logTerminal('✓ Agent Demo Ready', 'success');
1459
- logTerminal('Try: "create cylinder diameter=50 height=80"', 'output');
2002
+ logTerminal('✓ Agent Demo Ready — 12 shapes, 20+ operations', 'success');
2003
+ logTerminal('Try natural language: "make a cylinder 50mm diameter 80 tall"', 'output');
2004
+ logTerminal('Or: "draw circle with 50mm diameter" | "build box 100x50x30"', 'output');
1460
2005
  });
1461
2006
  </script>
1462
2007
  </body>