math.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. 'use strict';
  2. goog.provide('Blockly.OpenSCAD.math');
  3. goog.require('Blockly.OpenSCAD');
  4. Blockly.OpenSCAD['math_number'] = function(block) {
  5. // Numeric value.
  6. var code = parseFloat(block.getFieldValue('NUM'));
  7. if (block.getParent())
  8. return [code, Blockly.OpenSCAD.ORDER_ATOMIC];
  9. else return ['//' + code, Blockly.OpenSCAD.ORDER_ATOMIC];
  10. };
  11. Blockly.OpenSCAD['math_angle'] = function(block) {
  12. // Numeric value.
  13. var code = parseFloat(block.getFieldValue('NUM'));
  14. if (block.getParent())
  15. return [code, Blockly.OpenSCAD.ORDER_ATOMIC];
  16. else return ['//' + code, Blockly.OpenSCAD.ORDER_ATOMIC];
  17. };
  18. Blockly.OpenSCAD['math_arithmetic'] = function(block) {
  19. // Basic arithmetic operators and power.
  20. var OPERATORS = {
  21. ADD: [' + ', Blockly.OpenSCAD.ORDER_ADDITION],
  22. MINUS: [' - ', Blockly.OpenSCAD.ORDER_SUBTRACTION],
  23. MULTIPLY: [' * ', Blockly.OpenSCAD.ORDER_MULTIPLICATION],
  24. DIVIDE: [' / ', Blockly.OpenSCAD.ORDER_DIVISION],
  25. POWER: [null, Blockly.OpenSCAD.ORDER_COMMA]
  26. };
  27. var tuple = OPERATORS[block.getFieldValue('OP')];
  28. var operator = tuple[0];
  29. var order = tuple[1];
  30. var argument0 = Blockly.OpenSCAD.valueToCode(block, 'A', order) || '0';
  31. var argument1 = Blockly.OpenSCAD.valueToCode(block, 'B', order) || '0';
  32. var code;
  33. //Power in OpenSCAD uses a special case since it has no operator.
  34. if (!operator) {
  35. code = 'pow(' + argument0 + ', ' + argument1 + ')';
  36. if (block.getParent())
  37. return [code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  38. else return ['//' + code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  39. }
  40. code = argument0 + operator + argument1;
  41. if (block.getParent())
  42. return [code, order];
  43. else return ['//' + code, order];
  44. };
  45. Blockly.OpenSCAD['math_single'] = function(block) {
  46. // Math operators with single operand.
  47. var operator = block.getFieldValue('OP');
  48. var code;
  49. var arg;
  50. if (operator == 'NEG') {
  51. // Negation is a special case given its different operator precedence.
  52. arg = Blockly.OpenSCAD.valueToCode(block, 'NUM',
  53. Blockly.OpenSCAD.ORDER_UNARY_NEGATION) || '0';
  54. if (arg[0] == '-') {
  55. // --3 is not legal in JS.
  56. arg = ' ' + arg;
  57. }
  58. code = '-' + arg;
  59. if (block.getParent())
  60. return [code, Blockly.OpenSCAD.ORDER_UNARY_NEGATION];
  61. else return ['//' + code, Blockly.OpenSCAD.ORDER_UNARY_NEGATION];
  62. }
  63. if (operator == 'SIN' || operator == 'COS' || operator == 'TAN') {
  64. arg = Blockly.OpenSCAD.valueToCode(block, 'NUM',
  65. Blockly.OpenSCAD.ORDER_DIVISION) || '0';
  66. } else {
  67. arg = Blockly.OpenSCAD.valueToCode(block, 'NUM',
  68. Blockly.OpenSCAD.ORDER_NONE) || '0';
  69. }
  70. // First, handle cases which generate values that don't need parentheses
  71. // wrapping the code.
  72. switch (operator) {
  73. case 'ABS':
  74. code = 'abs(' + arg + ')';
  75. break;
  76. case 'ROOT':
  77. code = 'sqrt(' + arg + ')';
  78. break;
  79. case 'LN':
  80. code = 'ln(' + arg + ')';
  81. break;
  82. case 'EXP':
  83. code = 'exp(' + arg + ')';
  84. break;
  85. case 'POW10':
  86. code = 'pow(10,' + arg + ')';
  87. break;
  88. case 'ROUND':
  89. code = 'round(' + arg + ')';
  90. break;
  91. case 'ROUNDUP':
  92. code = 'ceil(' + arg + ')';
  93. break;
  94. case 'ROUNDDOWN':
  95. code = 'floor(' + arg + ')';
  96. break;
  97. case 'SIN':
  98. code = 'sin(' + arg + ')';
  99. break;
  100. case 'COS':
  101. code = 'cos(' + arg + ')';
  102. break;
  103. case 'TAN':
  104. code = 'tan(' + arg + ')';
  105. break;
  106. }
  107. if (code) {
  108. if (block.getParent())
  109. return [code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  110. else return ['//' + code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  111. }
  112. // Second, handle cases which generate values that may need parentheses
  113. // wrapping the code.
  114. switch (operator) {
  115. case 'LOG10':
  116. code = 'log(' + arg + ')';
  117. break;
  118. case 'ASIN':
  119. code = 'asin(' + arg + ')';
  120. break;
  121. case 'ACOS':
  122. code = 'acos(' + arg + ')';
  123. break;
  124. case 'ATAN':
  125. code = 'atan(' + arg + ')';
  126. break;
  127. default:
  128. throw 'Unknown math operator: ' + operator;
  129. }
  130. if (block.getParent())
  131. return [code, Blockly.OpenSCAD.ORDER_DIVISION];
  132. else return ['//' + code, Blockly.OpenSCAD.ORDER_DIVISION];
  133. };
  134. Blockly.OpenSCAD['math_constant_bs'] = function(block) {
  135. // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2).
  136. var CONSTANTS = {
  137. PI: ['3.14159', Blockly.OpenSCAD.ORDER_MEMBER],
  138. E: ['2.71828', Blockly.OpenSCAD.ORDER_MEMBER],
  139. GOLDEN_RATIO: ['(1 + sqrt(5)) / 2', Blockly.OpenSCAD.ORDER_DIVISION],
  140. SQRT2: ['sqrt(2)', Blockly.OpenSCAD.ORDER_MEMBER],
  141. SQRT1_2: ['sqrt(1/2)', Blockly.OpenSCAD.ORDER_MEMBER]
  142. };
  143. if (block.getParent())
  144. return CONSTANTS[block.getFieldValue('CONSTANT')];
  145. else return '//' + CONSTANTS[block.getFieldValue('CONSTANT')];
  146. };
  147. // This is just for backwards compatibility!
  148. Blockly.OpenSCAD['math_constant'] = function(block) {
  149. // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2).
  150. var CONSTANTS = {
  151. PI: ['3.14159', Blockly.OpenSCAD.ORDER_MEMBER],
  152. E: ['2.71828', Blockly.OpenSCAD.ORDER_MEMBER],
  153. GOLDEN_RATIO: ['(1 + sqrt(5)) / 2', Blockly.OpenSCAD.ORDER_DIVISION],
  154. SQRT2: ['sqrt(2)', Blockly.OpenSCAD.ORDER_MEMBER],
  155. SQRT1_2: ['sqrt(1/2)', Blockly.OpenSCAD.ORDER_MEMBER]
  156. };
  157. if (block.getParent())
  158. return CONSTANTS[block.getFieldValue('CONSTANT')];
  159. else return '//' + CONSTANTS[block.getFieldValue('CONSTANT')];
  160. };
  161. Blockly.OpenSCAD['math_number_property'] = function(block) {
  162. // Check if a number is even, odd, prime, whole, positive, or negative
  163. // or if it is divisible by certain number. Returns true or false.
  164. var number_to_check = Blockly.OpenSCAD.valueToCode(block, 'NUMBER_TO_CHECK',
  165. Blockly.OpenSCAD.ORDER_MODULUS) || '0';
  166. var dropdown_property = block.getFieldValue('PROPERTY');
  167. var code;
  168. if (dropdown_property == 'PRIME') {
  169. // Prime is a special case as it is not a one-liner test.
  170. var functionName = Blockly.OpenSCAD.provideFunction_(
  171. 'math_isPrime',
  172. [ 'function ' + Blockly.OpenSCAD.FUNCTION_NAME_PLACEHOLDER_ + '(n) =',
  173. ' // https://en.wikipedia.org/wiki/Primality_test#Naive_methods',
  174. ' if (n == 2 || n == 3) {',
  175. ' 1;',
  176. ' }',
  177. ' // False if n is NaN, negative, is 1, or not whole.',
  178. ' // And false if n is divisible by 2 or 3.',
  179. ' // Cant check for NaN in openscad.',
  180. ' if ( n <= 1 || n % 1 != 0 || n % 2 == 0 ||' +
  181. ' n % 3 == 0) {',
  182. ' 0',
  183. ' }',
  184. ' // Check all the numbers of form 6k +/- 1, up to sqrt(n).',
  185. ' for (x = [6:6:sqrt(n)]) {',
  186. ' if (n % (x - 1) == 0 || n % (x + 1) == 0) {',
  187. ' 0;',
  188. ' }',
  189. ' }',
  190. ' 1;',
  191. '}']);
  192. code = functionName + '(' + number_to_check + ')';
  193. return [code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  194. }
  195. switch (dropdown_property) {
  196. case 'EVEN':
  197. code = number_to_check + ' % 2 == 0';
  198. break;
  199. case 'ODD':
  200. code = number_to_check + ' % 2 == 1';
  201. break;
  202. case 'WHOLE':
  203. code = number_to_check + ' % 1 == 0';
  204. break;
  205. case 'POSITIVE':
  206. code = number_to_check + ' > 0';
  207. break;
  208. case 'NEGATIVE':
  209. code = number_to_check + ' < 0';
  210. break;
  211. case 'DIVISIBLE_BY':
  212. var divisor = Blockly.OpenSCAD.valueToCode(block, 'DIVISOR',
  213. Blockly.OpenSCAD.ORDER_MODULUS) || '0';
  214. code = number_to_check + ' % ' + divisor + ' == 0';
  215. break;
  216. }
  217. if (block.getParent())
  218. return [code, Blockly.OpenSCAD.ORDER_EQUALITY];
  219. else return ['//' + code, Blockly.OpenSCAD.ORDER_EQUALITY];
  220. };
  221. Blockly.OpenSCAD['math_change'] = function(block) {
  222. // Add to a variable in place.
  223. //TODO fix this for OpenSCAD. Possibly using the assign() function.
  224. var argument0 = Blockly.OpenSCAD.valueToCode(block, 'DELTA',
  225. Blockly.OpenSCAD.ORDER_ADDITION) || '0';
  226. var varName = Blockly.OpenSCAD.variableDB_.getName(
  227. block.getFieldValue('VAR'), Blockly.Variables.NAME_TYPE);
  228. return varName + ' = (typeof ' + varName + ' == \'number\' ? ' + varName +
  229. ' : 0) + ' + argument0 + ';\n';
  230. };
  231. // Rounding functions have a single operand.
  232. Blockly.OpenSCAD['math_round'] = Blockly.OpenSCAD['math_single'];
  233. // Trigonometry functions have a single operand.
  234. Blockly.OpenSCAD['math_trig'] = Blockly.OpenSCAD['math_single'];
  235. Blockly.OpenSCAD['math_on_list'] = function(block) {
  236. // Math functions for lists.
  237. // TODO fix this for OpenSCAD. min and max are 2-operand in openscad, there is no avg, median, etc.
  238. var func = block.getFieldValue('OP');
  239. var list, code;
  240. switch (func) {
  241. case 'SUM':
  242. list = Blockly.OpenSCAD.valueToCode(block, 'LIST',
  243. Blockly.OpenSCAD.ORDER_MEMBER) || '[]';
  244. code = list + '.reduce(function(x, y) {return x + y;})';
  245. break;
  246. case 'MIN':
  247. list = Blockly.OpenSCAD.valueToCode(block, 'LIST',
  248. Blockly.OpenSCAD.ORDER_COMMA) || '[]';
  249. code = 'Math.min.apply(null, ' + list + ')';
  250. break;
  251. case 'MAX':
  252. list = Blockly.OpenSCAD.valueToCode(block, 'LIST',
  253. Blockly.OpenSCAD.ORDER_COMMA) || '[]';
  254. code = 'Math.max.apply(null, ' + list + ')';
  255. break;
  256. case 'AVERAGE':
  257. // math_median([null,null,1,3]) == 2.0.
  258. var functionName = Blockly.OpenSCAD.provideFunction_(
  259. 'math_mean',
  260. [ 'function ' + Blockly.OpenSCAD.FUNCTION_NAME_PLACEHOLDER_ +
  261. '(myList) {',
  262. ' return myList.reduce(function(x, y) {return x + y;}) / ' +
  263. 'myList.length;',
  264. '}']);
  265. list = Blockly.OpenSCAD.valueToCode(block, 'LIST',
  266. Blockly.OpenSCAD.ORDER_NONE) || '[]';
  267. code = functionName + '(' + list + ')';
  268. break;
  269. case 'MEDIAN':
  270. // math_median([null,null,1,3]) == 2.0.
  271. var functionName = Blockly.OpenSCAD.provideFunction_(
  272. 'math_median',
  273. [ 'function ' + Blockly.OpenSCAD.FUNCTION_NAME_PLACEHOLDER_ +
  274. '(myList) {',
  275. ' var localList = myList.filter(function (x) ' +
  276. '{return typeof x == \'number\';});',
  277. ' if (!localList.length) return null;',
  278. ' localList.sort(function(a, b) {return b - a;});',
  279. ' if (localList.length % 2 == 0) {',
  280. ' return (localList[localList.length / 2 - 1] + ' +
  281. 'localList[localList.length / 2]) / 2;',
  282. ' } else {',
  283. ' return localList[(localList.length - 1) / 2];',
  284. ' }',
  285. '}']);
  286. list = Blockly.OpenSCAD.valueToCode(block, 'LIST',
  287. Blockly.OpenSCAD.ORDER_NONE) || '[]';
  288. code = functionName + '(' + list + ')';
  289. break;
  290. case 'MODE':
  291. // As a list of numbers can contain more than one mode,
  292. // the returned result is provided as an array.
  293. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1].
  294. var functionName = Blockly.OpenSCAD.provideFunction_(
  295. 'math_modes',
  296. [ 'function ' + Blockly.OpenSCAD.FUNCTION_NAME_PLACEHOLDER_ +
  297. '(values) {',
  298. ' var modes = [];',
  299. ' var counts = [];',
  300. ' var maxCount = 0;',
  301. ' for (var i = 0; i < values.length; i++) {',
  302. ' var value = values[i];',
  303. ' var found = false;',
  304. ' var thisCount;',
  305. ' for (var j = 0; j < counts.length; j++) {',
  306. ' if (counts[j][0] === value) {',
  307. ' thisCount = ++counts[j][1];',
  308. ' found = true;',
  309. ' break;',
  310. ' }',
  311. ' }',
  312. ' if (!found) {',
  313. ' counts.push([value, 1]);',
  314. ' thisCount = 1;',
  315. ' }',
  316. ' maxCount = Math.max(thisCount, maxCount);',
  317. ' }',
  318. ' for (var j = 0; j < counts.length; j++) {',
  319. ' if (counts[j][1] == maxCount) {',
  320. ' modes.push(counts[j][0]);',
  321. ' }',
  322. ' }',
  323. ' return modes;',
  324. '}']);
  325. list = Blockly.OpenSCAD.valueToCode(block, 'LIST',
  326. Blockly.OpenSCAD.ORDER_NONE) || '[]';
  327. code = functionName + '(' + list + ')';
  328. break;
  329. case 'STD_DEV':
  330. var functionName = Blockly.OpenSCAD.provideFunction_(
  331. 'math_standard_deviation',
  332. [ 'function ' + Blockly.OpenSCAD.FUNCTION_NAME_PLACEHOLDER_ +
  333. '(numbers) {',
  334. ' var n = numbers.length;',
  335. ' if (!n) return null;',
  336. ' var mean = numbers.reduce(function(x, y) {return x + y;}) / n;',
  337. ' var variance = 0;',
  338. ' for (var j = 0; j < n; j++) {',
  339. ' variance += Math.pow(numbers[j] - mean, 2);',
  340. ' }',
  341. ' variance = variance / n;',
  342. ' return Math.sqrt(variance);',
  343. '}']);
  344. list = Blockly.OpenSCAD.valueToCode(block, 'LIST',
  345. Blockly.OpenSCAD.ORDER_NONE) || '[]';
  346. code = functionName + '(' + list + ')';
  347. break;
  348. case 'RANDOM':
  349. var functionName = Blockly.OpenSCAD.provideFunction_(
  350. 'math_random_list',
  351. [ 'function ' + Blockly.OpenSCAD.FUNCTION_NAME_PLACEHOLDER_ +
  352. '(list) {',
  353. ' var x = Math.floor(Math.random() * list.length);',
  354. ' return list[x];',
  355. '}']);
  356. list = Blockly.OpenSCAD.valueToCode(block, 'LIST',
  357. Blockly.OpenSCAD.ORDER_NONE) || '[]';
  358. code = functionName + '(' + list + ')';
  359. break;
  360. default:
  361. throw 'Unknown operator: ' + func;
  362. }
  363. if (block.getParent())
  364. return [code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  365. else return ['//' + code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  366. };
  367. Blockly.OpenSCAD['math_modulo'] = function(block) {
  368. // Remainder computation.
  369. var argument0 = Blockly.OpenSCAD.valueToCode(block, 'DIVIDEND',
  370. Blockly.OpenSCAD.ORDER_MODULUS) || '0';
  371. var argument1 = Blockly.OpenSCAD.valueToCode(block, 'DIVISOR',
  372. Blockly.OpenSCAD.ORDER_MODULUS) || '0';
  373. var code = argument0 + ' % ' + argument1;
  374. if (block.getParent())
  375. return [code, Blockly.OpenSCAD.ORDER_MODULUS];
  376. else return ['//' + code, Blockly.OpenSCAD.ORDER_MODULUS];
  377. };
  378. Blockly.OpenSCAD['math_constrain'] = function(block) {
  379. // Constrain a number between two limits.
  380. // TODO fix for OpenSCAD
  381. var argument0 = Blockly.OpenSCAD.valueToCode(block, 'VALUE',
  382. Blockly.OpenSCAD.ORDER_COMMA) || '0';
  383. var argument1 = Blockly.OpenSCAD.valueToCode(block, 'LOW',
  384. Blockly.OpenSCAD.ORDER_COMMA) || '0';
  385. var argument2 = Blockly.OpenSCAD.valueToCode(block, 'HIGH',
  386. Blockly.OpenSCAD.ORDER_COMMA) || 'Infinity';
  387. var code = 'min(max(' + argument0 + ', ' + argument1 + '), ' +
  388. argument2 + ')';
  389. if (block.getParent())
  390. return [code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  391. else return ['//' + code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  392. };
  393. Blockly.OpenSCAD['math_random_int'] = function(block) {
  394. // Random integer between [X] and [Y].
  395. var argument0 = Blockly.OpenSCAD.valueToCode(block, 'FROM',
  396. Blockly.OpenSCAD.ORDER_COMMA) || '0';
  397. var argument1 = Blockly.OpenSCAD.valueToCode(block, 'TO',
  398. Blockly.OpenSCAD.ORDER_COMMA) || '0';
  399. /* var functionName = Blockly.OpenSCAD.provideFunction_(
  400. 'math_random_int',
  401. [ 'function ' + Blockly.OpenSCAD.FUNCTION_NAME_PLACEHOLDER_ +
  402. '(a, b) {',
  403. ' if (a > b) {',
  404. ' // Swap a and b to ensure a is smaller.',
  405. ' var c = a;',
  406. ' a = b;',
  407. ' b = c;',
  408. ' }',
  409. ' return Math.floor(Math.random() * (b - a + 1) + a);',
  410. '}']);
  411. var code = functionName + '(' + argument0 + ', ' + argument1 + ')';
  412. */
  413. var code = 'round(rands(' + argument0 + ',' + argument1 + ',1)[0])';
  414. if (block.getParent())
  415. return [code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  416. else return ['//' + code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  417. };
  418. Blockly.OpenSCAD['math_random_float'] = function(block) {
  419. // Random fraction between 0 and 1.
  420. var code = 'rands(0,1,1)[0]';
  421. if (block.getParent())
  422. return [code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  423. else return ['//' + code, Blockly.OpenSCAD.ORDER_FUNCTION_CALL];
  424. };