From b7c71c01cf68ee266760e3459c3db7bf58c88502 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Tue, 20 Aug 2024 08:08:55 +1000 Subject: [PATCH 01/41] xstate start --- src/lib/toolbar.ts | 17 +++++--- src/machines/modelingMachine.ts | 74 +++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/src/lib/toolbar.ts b/src/lib/toolbar.ts index ca1b6c2022..597f9fee22 100644 --- a/src/lib/toolbar.ts +++ b/src/lib/toolbar.ts @@ -2,7 +2,7 @@ import { CustomIconName } from 'components/CustomIcon' import { DEV } from 'env' import { commandBarMachine } from 'machines/commandBarMachine' import { - canRectangleTool, + canRectangleOrCircleTool, isEditingExistingSketch, modelingMachine, } from 'machines/modelingMachine' @@ -355,9 +355,17 @@ export const toolbarConfig: Record = { [ { id: 'circle-center', - onClick: () => console.error('Center circle not yet implemented'), + onClick: ({ modelingStateMatches, modelingSend }) => + modelingSend({ + type: 'change tool', + data: { + tool: !modelingStateMatches('Sketch.Rectangle tool') + ? 'circle' + : 'none', + }, + }), icon: 'circle', - status: 'unavailable', + status: 'available', title: 'Center circle', showTitle: false, description: 'Start drawing a circle from its center', @@ -374,7 +382,6 @@ export const toolbarConfig: Record = { console.error('Three-point circle not yet implemented'), icon: 'circle', status: 'unavailable', - disabled: () => true, title: 'Three-point circle', showTitle: false, description: 'Draw a circle defined by three points', @@ -396,7 +403,7 @@ export const toolbarConfig: Record = { icon: 'rectangle', status: 'available', disabled: (state) => - !canRectangleTool(state.context) && + !canRectangleOrCircleTool(state.context) && !state.matches('Sketch.Rectangle tool'), title: 'Corner rectangle', hotkey: (state) => diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 4e3026dfc3..82e070763c 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -142,7 +142,12 @@ export interface Store { openPanes: SidebarType[] } -export type SketchTool = 'line' | 'tangentialArc' | 'rectangle' | 'none' +export type SketchTool = + | 'line' + | 'tangentialArc' + | 'rectangle' + | 'circle' + | 'none' export type ModelingMachineEvent = | { @@ -211,6 +216,10 @@ export type ModelingMachineEvent = type: 'Add rectangle origin' data: [x: number, y: number] } + | { + type: 'Add circle origin' + data: [x: number, y: number] + } | { type: 'done.invoke.animate-to-face' | 'done.invoke.animate-to-sketch' data: SketchDetails @@ -292,7 +301,7 @@ export const modelingMachineDefaultContext = { export const modelingMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANhoBWAHQAOAMwB2KQEY5AFgCcGqWqkAaEAE9Ew0RLEqa64TIBMKmTUXCAvk71oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBEEpYSkJOUUaOWsxeylrWzk9QwQClQkrVOsZNWExFItnVxB3LDxCXwCAW1QAVyDA9hJ2MAjeGI4ueNBE5KkaCWspZfMM+XE1YsQZMQWxNQtxao0ZeRc3dDavTv9ybhG+djGoibjeRJothBpzlsvPDp+fy+KBdMC4AIAeQAbmAAE6YEj6WDPZisSbcd6IT4GbG-VoAiTYCCYMAEACiEPhgQA1n5yAALVHRdFvBJCazCOQSRSctQyKS5RRqFTWL6nNTSfnSsS5FRiZT4-7tIkksmUkZw2n0pmKSJo2JTLFJWwLFSOBy7WQ0eZFXEIczWCRqaxyOq81KKAWOJUeFXE0kUx5w3oYZmvI3spJiOQyZ1mZQyeUyRSC4RfXmndKOKRyWUpmqKX1XKCqwMAMWwmFJT3o41ZkZmQnzEjsgoVXPE2TtJS9KmEPNjuZsKbkDmLhID6r4LDhtf1LMNmKjgjEdXSrtENuEihUcjUigz-cUEhqeaUwmsqavE-9aoIyBIdPDDeXTYdTtSIusalj5qy+xfFyaSxvUKh7rKdgqFIt74GWZIACLBCMgTBKEUwvku0z8EIO7lNoCZjqm5qKGIGYxuUNSXj+iiOLR+6waWU4EAAKmAjyCOwqCCEQACCCGYRi2GzAqTpmCkspqLKf7pvayj9hU8gFPs+TCOaciMfBEgMsSYAAAqIrgcAELxc7YAAZiQoT+FAcIkEwDL+CwTC9IiIwQIJbLvoI-aSuIia-loQUyLJJS-nGlrQbmXq0VIKiaVOEiwAyqAAO4GWQxmmZwlnWbZ9mOWAXRMJwkCeY2OFJHu3IKDGYicsB4F5hmvJOqmB5ejQNBWAe1iad4dLsIyxBkJQmADTq5VvpVmZxrYSacmuHq2C1BwSN1-Y7DG+40DI-WDcNSE1mAqGguC871lhxpyHmzo-vVcW-jashfPK3IaOY+a7Gp3X7TqBB3Bg-iQBw-gQL0cLtNqQ1MnWLyvsJiBei6ph7oFWhqWYPaINK61rsstRZLRIp-TDEgTTDACS96PnSp1ghC-iwmZ5AkJgU2IwgXoCqeqaqY40G1EeBSLPKj3zMsSx7c0BIqhTjLkwdDLU4GtMnUEZ2MylkMAF73GzHPGl6V7pOB9Q7PylQqBmNhmge9X7ETXVFjLypwfLDKKzqKtkkQ3CwOwdl4P42vYHrEJs2D2AB6NoxwwaQlG-YJ7KPkywpjGyxfKakjOzYe47KRbqkwrHs+8Q-uByQwfM5wrOYFHMcUHHC4RtNiQxQOz3WrywpytnSxOnuXU0AcuQpiXntl-efu4AHQe4P4vEAELeP4AAahtRtzYl-vy5pXjuZH2j+VjOjQnIWPY1gODBrt+u7Ste1TM+VwvS+r-4ACaW-vjvp4KF3KPWoDtrb2nsL+CoaxzxxQUCkSez9GTl1nvPaui8yBQFJL-GaNQ9jCltBsCw+Rs4dkWBYR0u0ljgXivfEsiCp5K2QW-NB-hST4HYLDVuCMk5xVMJaXMO4UxxRkNnS8NVSK2AKDfZYpEEHT0DCgquwcmDwhUbgCA2ByCuRIFqDRTdKDYI7q6SQVh6oHEltkbQ2d8hxmMDGAoI91B9VoYSD29CmFzyUYvUO4chjVn0P4Nm2AoC4EMUjCBzoRSbVUsY4+vYHai0cNkLqIUbTSwuA-Usbj5G+2YTXeEdc2aYACUEkJYSuY2AHPjQU+5fycjsBmKw5RchrgKFJA4I45GMNfp49+sBcD2X8FxDe5SvQmBqIFVQ+xMjEJPv5Qcu5zFKAPBpFxcsn45Irr0lh-TBnDJ-vHRcidt6pMWK1WoV59yxhESfUiA51Dyl2MKHYo8unex6ag4OYAACOvRI5sKgBw0ZSYFh1AAkoFI5yMyxklPIHc81Km7jeS-BReTF5MB0UU6ghy26cy9KoHkl59hckyLuL0GYpJVNlHFBwak+zIqQR8rx-g4RFVQLCW4aL2Aohxdw7e91TbCL3GC4xGZZCSguaoYeB4UwuwyXQ7J3TAwACUwCCHYiEXoIxRk7jSNoc0dQNCiF-BmcQZpZDgUpcIE4zj5WuI2UqskgMTogwCODSG+BoaMmBSjHcHUFBjjsMKU1u11pulMWufkso5V-EyfQ9xPTa5DNQEzHR2ASAACMsG8quicjQ0gsiqRFMKKwYpwGH1PGuPCN99g0LtesnUCbAyMgwSdLiqB2Y5uOe+HIF9pA-lHjkA44Uy29gkWGrcbTZBWDvvWx+jbeKpWrgEHx+sG56KGM3fweBzKoAIBAbgYAiS4GhKgOkEgYDsEEKuiOmBBA7tQOU6oCpTA2MltI-scTECSJAtoNSUrcgODrbGhVT9F3LpDqgXWa7G6bsoNu3Au6CDwjhFBiQTA3K7rhF0C9fhr1QbDmu+9iHH1dq8pVZ9Tpe0Cm0JyUimwT64OdCkUU-YljzBjAg8DoNa6aMjhu2OCGkMHqMse0957L2CF4-XYju6n0W0WLGOBcV5iXgHijfImQ3TmBdIqNZ86ybcYCNJ-j0c4MnQfchuEqG4Tocw1BnDkmTN3offJ-Yzp1JwrXGOEUA8X20V3A1NSpx0kgftQupdoMV5r3XkJvdImj14HE0eyTmbYCCD4LJ0jXDc09pCnGLQ+5bRJktdnX8J5tymjHCkbIXHIsBGixvOLVmbN2eGFhxzeG0sZay-JtSphrUpBvhkdSZWcinnITdAUWR+SrLnVksD9WP5ry-s1hLYmz0pa6xm9L+hetkYqokaoZ91CJnApkea2MED2DdK2JqqhBTDtzHViDjXVuWZQ2hjD7WHO4avd1vbrmDvt2-SFcojgQpFz3FeG04pEzrVItBOoroDVzbCw2wzS2MGkjW4ejbEmuv4FJPtnL3aKNg9bPIWi8hzCzJKDkdcAibBmBTL1YDssDMKyM4EonFmSMta+-Z7Df3BDY7VUD0n5GjshUkDSzIxgHBukPCfeqXd9g1HlItEbL3QYAo4bj0TSXNsi71wyEnl0yfS8vAN2Q2Q6i7GyKIkU61R5kqsDdTj+mFsRYg6b5rn3bPffYB1k34JAVm4lxbqXoOz7HdOMYdQSZdyiJfaIWQ+KFT5F2jrgIKi4RqI0VoxEuizOCcs+to3BOr154L5o7RcJBACebub+GuXyeZHWiKewKZdrRqu1LSiygB25mkjnpyqjwSF-r7Bsv-OA9teD79yTNfJ91+L430vzfI+t8t6Djvo87C7UyFtZQ2cFADlHMPvMnv5vxoADJ4DbagDtAMGStuTR2p9ogqlixuqmUtXwLoaQquuwtQogro44Xu9+j+H+mAEglMuAHABAT6UszoUqICeYvIL09o1qCwxEbopwXotutq6OnOnsD+RksB8BiB7AyBeoUeh236NEaBWB-Y4ESkoUiA+4A4v41qEOV4AoCgCCFBT+HaEgAAcimnpKgHgNyiZBABAIMDornjIRCF-mtNfDUCONoFJEBNkK2EfNtNkPsAxFAW4iIVQZIf4NIbIbACNM3J2pLowVzBjIStOl2PYroPaBkGaKKIrvUFoDaAgggUgaMt1LYofFng0OfhmJIq2H2LsPHhAsETQXQU4SDlzMAutJyOnLREsGOJwRUl1OkLKoGhfLkIKAgixK2hCOmg3DouQMmq-u-u2o4QwRkesBfvKOINasNqIFdueBUMPAQTNjaGIAgr0OoimjHHOB7JCLgPunjpXkerxN4CxIIJMQeoIDMewHMaEsDnipDq+jdFeGLPsL5t4f5pjNBJnGjOzm7N7mTKqqEGLrAc0fgKIW0TvtHtdpkHgeJCjryOfldhdpKLqvkHmGMWuAgs8Zupgp8RIEZlDEEHcOopynCEZHCAQJWIgclCyiEHCdmukZzAtJ+EKI0P5AqGAiULmE6PnN+DuJkHuPcXGm4rCa8a0YifVlDARlAHgPIYoayi8bzv4LyXgOUiCieHmD+I8tajdl+ggD4WGkcMsicJeDCQSRyc-nATidHAyJAP4OybzsgQcUbGOAsPdJbCpI4JyIAWfAeBjKPBtC6AguQKSGQIEG5GSKMseKbKROsBfDYDkC1CbFJM8uYFuJDmPqiUmsMtCGmpmoGBXiesbpJjGQUpxNxPGZDImeLiRvJiLLYP+gUH6vYIUUGWkIFnYF5o4C6WYYthBumXOMmqmjmVmmSPPkHiHmmdwLXJmVJgme2S3gnD8dUPIBUGMkRA4F1OWSFCBJYqKIycsiyaBj7qDKiZ8ovKyj0ByhuV4nIcmclhIFsXuQvIINueymqqeWghdN8c4dUCbGYv+D+LICmIUamFmBkECbRPUDaBkNGWipBtBresUosYbimeeieWivhsBX4sUvJtBCUT+PICsIPNSeEh3mpBoDNh2LuOMfWWuQENefkizEUvoGBYlhBUelBdsngFJgUnxv4m5vcoPK6FoCjuhVzLTqeKkGsLsLfKcABbRd4gRr4mRYEjgCEhRfjtRdxMRbgDBYRiBXtqUvscScaGOWCQqLsLTvKJxaRMoFKI0NeDcUIQRZjo2YBc5sUhJcEgsYecbjRZufRaRf4qLpJWpe0ZzA+SeI7FWUoAqI0l6BNg6VJM+kmEJZuYEAMkwC2evNJcsceXJdBbskwP2XwAhTVBkHwWqYFtnOQutFUNavbC8jGhzo8VzktvJdFXsiml-AlVRUlYIPJdsTFf2foG5rYoWk4sIt3tnF6JIDkbRFyL+bRJFcyj8n8g3Kbg1UeU5V4uqr8mzIIKbvJjUIVVkD+JMrKNCuNqPJjK6B0p0uZZVZZcJU5JitWFgLNY5clcJYIBinZFdV8SOfeTsF3KKErrtAnrGBSnuDyLIFeLWXchFSdZ7EQG-h8f4CxNqSaepVGICQjqcA8jGBdjcr2K7iUaoHUujLUAghDe-jDS-lQPQXeRkTROUOgTfKxsKMGXJJjUpuoJYnAnjWDRIATVDUTZgMgdYPDe+CmOoDyNtF1PNMaujUjAzTTjjSzSueFmTBzTANDbDVQDIHzZVCFDdBUFJIKP+FRDsKageFjUzT+CzU0Lfh7P4LgCmrlGSL4KSNZBgJZK5LnoZC3F5caIIDfEPMhSWu2MsoUVyHGOGnUl1O+moJpGQNgF0MMFDMMt9kZDdeepHdHSMP2TbeUjGF1RcsKvtVnDgTuIsA7ssJCrGCQeVRIMnTHZ6sMuxGZsiUrInUepXanVxNsUrBnYZZeJSscMoFMuKOIDxTKh2NkDaU0M0FbRgPAFEOVe7SuKkGkJtPUAoOoBYAxr2JyPEaPCpG6LmEAQlGqLPd5D9AYTsLmI9KvV8OKjyNBPlqkKjmXQ8VpDpBgBlEZFPa9RkauCjIvWfSveoKauNksKRBfE9AGg-XGolMlGlK-XAIfZVIIDsE6AoB9LtNaLUgA4NQFSA2OGA5PHAx3JaBUJEdIv0SkP1XGNaVJMAmpOqWzTkvg0jN0ZTkmNtHUKVnJDfBFLmOICvb+DfWPjen4jPlug+gw1zPbKYG6FJBkJbBrtnOYCeDRLyCjW+WPs5sI-BqI2TXii6JKJaS6IrhaKOkwfUGgSPBREOjfqQRVZ7Nzo1rFlox-To+YGcgeN1SbWVi446DuCAzDqFuXW4nY5-O9iRmI88mCWpM+YKN1C6P3QSsoEQamA9qBGPq8Y40cj8cKKKOkArleLvTUIKCQoZVI1yNoNOlUGPn7uk7ikbLNqYDUGYIfoGfUKIvKFjUoH3LKJAebQ2aDCvuomvjoho3zrumEx0s6PnCkMsObHTfTjtBUHUKkEmEIp9MITAa0WI1eLsGQrI7+OFFdvUINaoAIVvd6GbdY9AZQZySEewJs-gjs-lns0pl8ERDyL3F+MAysms1c9qRIFbdhmzJs40xOacABIyQeC8x3uBDfMoAEeYt8wiVYTYRCO-Rk-efxfEb1HUHFJkDtd4RkIsHyDTpULoykRwGE-LukEoI6AqC6N-qtAsBtBGjdB0lUTUZwJHA0cmmI0pOUBYDpVNuaWQzgetcYJeIyTGJSuHWzZsdMUMLMUrPMWE7GAOCSmwcNWOMYwgLKJIKsA9oDdaLLRjgrEafCbATy91CeNFGYDkF7d1F8DGAOJw6JESlBPhT042qazjpyUiZ6iidwIoXcBifCDy5kJKNa6KPVBfPa-aB09ICkLKqIGuInhqcKWaz69yZ6mKdhDU1GALSeKYueDE-KZC4y2OA9MKGqeA6uU8ZqSKZybqclAaV62AKG0mKYPVD+bKJyNIoAd1NILgTYNkBCXUK6e6YvLAF6WE+BKCsg3mNkAasrhjfoQcE0vvFNu6xc4E1Vb2QUi2dmemu2WM6GpbHYJ6JIhfOKG0osBoDdNkIfJjONe-BebuVyqi7m3-FoBViOBfJuLkAUGKqGShYHZ9AIU+ywoI2RWE1++tD+wUG6P+-pS+dIAQRoAeNahU2zdztVdZSUE40bDB5QtfH+-nEeP226LtC6NkGWc9lhzu+dZB-4rZSEtB4bURyhXUsBORP28NVQ-+922jgE700RVZQxfXDZapax9+2OXOzkY0g4PU8m7GDOYHeB8HKlXFce3GJbDkPivMAzvlTYDexBENvYPKGpxOzFS2V-FpxM0Snp+UYUQIdyK6LUtBLWYJXR2dVFZNf8mHhwlJ7BzJ4VpyAoNCv23CsoITO5xYBZxdU9aSJgMe1RvMIQWfSy1q88rq6PCpj0YBuc0J42grSdFzZs3bqjEvRkAm5lwZUbdLQTAV4-RbVbf4DbWI57TzLKL0cXccw4K9OuNMrULUCFpE0a3BM3bHSmvHa29o0bPafkNBAQZnX+kBPVMxmCss9tNW4SBN9XSmrXQHPXTqGI8YIpjkKcK6EAzdF4SUG+ULVfGInRKsi4EAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANhoBWAHQAOAMwB2KQEY5AFgCcGqWqkAaEAE9Ew0RLEqa64TIBMKmTUXCAvk71oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBEEpYSkJOUUaOWsxeylrWzk9QwQClQkrVOsZNWExFItnVxB3LDxCXwCAW1QAVyDA9hJ2MAjeGI4ueNBE5KkaCWspZfMM+XE1YsQZMQWxNQtxao0ZeRc3dDavTv9ybhG+djGoibjeRJothBpzlsvPDp+fy+KBdMC4AIAeQAbmAAE6YEj6WDPZisSbcd6IT4GbG-VoAiTYCCYMAEACiEPhgQA1n5yAALVHRdFvBJCazCOQSRSctQyKS5RRqFTWL6nNTSfnSsS5FRiZT4-7tIkksmUkZw2n0pmKSJo2JTLFJWwLFSOBy7WQ0eZFXEIczWCRqaxyOq81KKAWOJUeFXE0kUx5w3oYZmvI3spJiOQyZ1mZQyeUyRSC4RfXmndKOKRyWUpmqKX1XKCqwMAMWwmFJT3o41ZkZmQnzEjsgoVXPE2TtJS9KmEPNjuZsKbkDmLhID6r4LDhtf1LMNmKjgjEdXSrtENuEihUcjUigz-cUEhqeaUwmsqavE-9aoIyBIdPDDeXTYdTtSIusalj5qy+xfFyaSxvUKh7rKdgqFIt74GWZIACLBCMgTBKEUwvku0z8EIO7lNoCZjqm5qKGIGYxuUNSXj+iiOLR+6waWU4EAAKmAjyCOwqCCEQACCCGYRi2GzAqTpmCkspqLKf7pvayj9hU8gFPs+TCOaciMfBEgMsSYAAAqIrgcAELxc7YAAZiQoT+FAcIkEwDL+CwTC9IiIwQIJbLvoI-aSuIia-loQUyLJJS-nGlrQbmXq0VIKiaVOEiwAyqAAO4GWQxmmZwlnWbZ9mOWAXRMJwkCeY2OFJHu3IKDGYicsB4F5hmvJOqmB5ejQNBWAe1iad4dLsIyxBkJQmADTq5VvpVXq7KY6i-sBbpujILUiqe6jZJy2QHvk-WDcNSE1mAqGguC871lhxpyHmzo-vVcW-jashfPK3IaOY+a7Gp3X7TqBB3Bg-iQBw-gQL0cLtNqQ1MnWLyvsJiCkTQkqCvKNEFCkKRfCKcZJsKy00FeSZNBcfpwRNMMSJTjIAJL3o+dKnWCEL+LCZnkCQmBTYjCC7rKFQ5PIPUxUenKmPY-LCAcAp2BpzQEiqNMMtTB0MvTgaMydQRnazKWQwAXvcXM88au41BI-legK1hdbsKgZjY5Q3cYnVE7Rsp-VTysa2SRDcLA7B2Xg-j69gRsQlzYPYIHo2jHDBpCWb5ppNB0ECmYBRE18pqSksamxfYyiul7jKqzqvvEAHQckCH7OcJzmDR7HFDxwuEbTYk5t41JAo0Emshcqt9qYwOea7LUu6yELpcqz797+7ggfB7g-i8QAQt4-gABqm1Gu7qNI8wWCFGd5g7I+1GkqZugeZ79zIs-lzDleL8vterxvW8AJp7++B+SlSL5VqP4agXxKCmBw8Y1KXl-Nofu8syYlmfmXeegY341xDmQKApI-4zV2NyVSOxsho0vDneqZotDQV6rVKST80F+2rivfwpJ8DsFhu3BGZtdgnnmEoeQsgpLaDFCPKwTour91AaRLQxh6Fq1fkwj+Tl4RMHBBAbA5BXIkC1OolulA8FdxqE6EUxFaIo3NroEe+R3pWhRijWUWRH4K2VBTNWKD1YL0USHMOEchjVn0P4Lm2AoC4AMUjUU3Ii4OE+pyKh5EfymG2vsFGKRqikz+OTUsyt3EKKXpg1e9cNFc0wAEoJISwl83AnGXMZgbQFBCgoYevYrBpHyFkTI4hzB7nSYrVxOocmeLycw2AuB7L+C4jvCpu4brpG6qcN07YCgiJKLbAW8o6g3UWvyaociK6DPfiHEZYyJm-wTouJO+9qqmFTHFNSVTczLMQFeEwmyrAkMFLYXZL99n5OBgAR16FHVhUB2FTLevGWMNpzRC1SBmVQcZXZXl3JuUBXy6Y-OYUwbRxTqBnI7rzXcB5WzS13NLEKspcgZlqAsOKKNXQ5APOoJxSDCTZIYVXIZSi4RFVQLCW4XiIQojxVwy5XpLZmG0AtA+u4MxxXKIUWQqYLCkrEGijxgYABKYBBDsRCL0EYUzYwDh-O06iORaIZk6ekVIuRjCXi6oKNVldAYnRBgEcGkN8DQ0ZFMied01ypBRkA+olqsinh3FtYwct+5OsGfXcZqA2baOwCQAARrg4VV194Km5LROWGRUjyAyOKLOFR1IOHsPY1VzjMnuIGYGRk2CTpcVQNzTNFz3ycltjyHYakliJlzC1Nc4bMhaBqPMl0T9eKpVrgEHxxsm66KGK3fweBzKoAIBAbgYAiS4GhKgOkEgYDsEEPOyOmBBBrtQBU2wqQj5jrldkUQ4Cnl2AirIP8tSUx1CnTO0GZ6-HN2XZQVduB10EHhHCVAcIJBMDcuuuEXQj1+FPdB8OC7L1geve2rylVb2SllNLcwRwYxNKeTUSQrpSL0WqPkIsNbkHZOnbOtm8IG5RyXXHUD4Gt1GV3fuw9x7BCFMbph9dN7ummHmBKLITVQpPJ-Lm7qjgdgWGqH1BjrK3HMdBiJjjMdgMnSvRBuEUGYNweGAhpDQm9MXqvRJmZth8i5mWCmaoZHSg5EkFRBU8h9gD1-Sxr+O9uMbt4zuvAAmd1CbTbAQQfAxPYc4Vmzt0ETy22TEG7cHnFPebWK6aTqREEZMY9pv9ARgvb1CyZszsH4PQesyh2L8XEsSaUNIN00LFOKpzi6AjWg1xNXqCjQLoNgvf2q+F-jB7otNdTXF-QrWcMVUSE5hY+5nP9hChs+TCA8jrZ-CkYUCZdyjYq5vfwE3jOQeg3VyzDXkMnua4t+zy3O6vva3uBwdRwqXj3OKZQkS7ZGrKDYM7gT8Ckkm9u6bgmmuQ+1a95LHa8NpfSHYYwVFshJnIWOHkeFeT1Wlq6atLKlZlZY9gqH13TO3Ys+wKzj3BBU8R1htrcZSV1CqEoZqI9cgLCTOBC8V56g-s0+T-pOmAjAvYdDvjkWZtM5lwyJbyPcOrb3KjVQLolhKGgrtvkaQfyiiWLGV02RwfK+qzd8z9XENK-BCClXSPLoo41-Meauuit1TMDnOokgx3EdubIMw4PVFwlUbgdRmjEQ6IM1x4zU2Fdw5PeHyP0etFwkEJx1uqvXfq9fa6VsDrcg2A0BsnOsg0iiGqJssc9RScla05L8ryiI9qI0ZnoDCesM1bp3bxrqeVEd5j9o7P8fc8u-hil1HReg+l5-LUGMOd9wLBr0Wp6Den4ABk8DNtQK2gGDIm0JtbTe7H0h6oROhX+HGNhpAe1qOPJYuQd979P5gCQtNcAcAIBJqB-ajQ8wpEooHmRGxKBEhwywV+b+RkH+X+P+7Af+eo+eK2r6KMiwN0sg0EY4CgxgXwwBiw9q-c5oP0KYsB++raEgAAcomnpKgHgOwLACZBABAIMNogECwIwTeraBUDaLjP2FYCjEBG6OjlGvrm6F6BpmTn0lTLvnAS2p-rQf4PQYwcwaQK3G2mrmgaUBWq2NrqOu7KKF8BkM7FRkAUYmDuLrIWXN-r-lMkqndPkCOLyOoJyBmLehUARPIHYFIeBE-HYUgVQCgdPm7kjM8qeKcG+hKEsPKI7OIJbI4DkNruPDUE-CxE2hCCmk3NouQAmkfifooRUjbAih2O0nuPJD2IgG6MYjYCpo6C6AKE-L0FHomrHHOMrJCLgJujDsnjurxN4CxIIC0VuoIO0ewJ0aEm9gSiFCePUDdFePKIKB9CYQqPjvKNBDGHFPuPFNYVkm4lqqECzh-gUfgJQVoage9ntleNyMeEWj1LbFkJalmDLIyqIMQqdnsXWoccujgucRIFLlDEEHcFHvynCEZHCAQJWD-slP4NykcQjsUbkJKEInUOnKRHUB5rmAODuF1Ipt1N1CXF8dkj8ccYoQCeVlDGhlAHgCwWwfCb8VDtSXgEicKOKiKFJPuDGIsSYbuKeIRqRMblJD6MSQcSEIyf8dCTHAyJAP4KSQjn-tMWbDznwU5lUG9HUDjHehlhYMfMjLGE-OQKSGQIEG5GSL6vuJbNBDaFtMOGpC1OLLXu7KRLsAqI3r0vsS3ixiCfGhMtCMmmmoGEnnuorkJj6WxpxNxP6ZDIGazuJkqVGLYBbPmETE5tgf2CWvfhEhQg0MRLsTIZ6VTFLvynumxgmkmjGemmSDbndgzg9mGdwPXJGcJgGVWXnqEQXqUBsXdKmZIqoI8ntvIBFMNviYWvKODiCQcqvNyj0HypOfkkwT0fLiGYeqMfOSvIIDObytquuR-BdB2ToUma0jsNSlfqoGuLKvIM6LUDsPkEKETO6S4oWWXMWbud4mhr4sUvoEuRFiuTumuQKqhobAuiUhJsYJbGAssGYtLFYI7HyfyAtPbMYIvhOQKqxhzF+T+bDv+dxG+bgMJmxkUv4g5nGH+FgbRAeCGnJNChULkMfPsAqNoD0k+XWq+WhQBl+YEjgCElhX0RIABZyngEBehueiUsztxVMdoVcbYMOlJPMGYJAlJC+nzPVG1OPNQsKMOKhYJQUoRY3CUlxcEt0cGVFvxbhYBbZmJWUpJZcbzEeVKMYC6LgS6MpXRNyPsBEq7GjCKNpVOYEKMkwOWdvLxX+WZYIHhWMQFc2XwGBZIFkPkCbo8UsDnLqfyacDGA4P3P7r5b8kcoFSciFaZQJVOZFfZM2foA5gsIyjUMKbJnYDnF6ARrkLbBnPYJyDlcwmAACkCo7uwoVYrsVfkjqt1ResrhJsOg0ksETP5KkJYr2DtKeEoMidAUmG6B1UolinZNWFgP1aueZTpYIJtTihcQedJXuHjEyrRJyPYDuB5sdieP2PkNoHgVsTBKKf0kQMfmcf4CxAfpgIqVJXZYKGkCmNoOXiHipparUJbMpHFEYgcI+bWtkp9Sfr9YfsEYDcaPYPfv5F9nUASZqXJEGjDTkHDX1uIE-Cjd9Wjf9VQNYJjVGDYOBf2KKPbGOETHdcTamWBPDRYMVh6XWlTTAD9X9X-jIAze+PIFJJbETF0qcAgqoFDQONzWTSaojaVh9V9cLTTX-ioBLZVOYHFDyFkL+PUlYOYLtpkNDamZidoEFNIU3hLlTEQNgHCEaecaccLUUQme+OaESusikNLMsMBHfuUL5muA-FeJyPmY7TYSrC7W7VDuSSQJSV6syd0bxKwbcK7e7f4OnRUuBNBM6CSnAkFMYfaKYfyWuIKRyR2LPP4LgImrlGSL4KSNZBgJZK5JwYZG3LZcaIILbE6KKPyMKDavUKPSIaRY5dkA6vkD+JpGQNgF0MMFDBMhZkZLtTuovcvSMM2c3RUqRnwbUIUKIFfHNUYDuIsAQssEdmbgvT-jvavYmuxAZkCWrJvRINvcMNqlxGMWrAfcoILFJJeIWDdHEfaPYN5iSjsI4NkEkU0M0I3RgPAFEB6X3SuLNcSreQoOoBYJsHJAksTsAb+AtHFA7QLVOOg95D9Fg-MY9HgwQQKNcvmPuFtFyQlGqNpLpBlEZCg4nJ2auC6LQy5rg+oJajkIsEtUTE9AoBbl8YlMlGlDw3AFQ5VIIDsE6AoB9P3NaPuPg72A+ZI8jPibI+QyxcrKo13FIU4epm5m4QboA8KFNTYN1IRMyrHc+XPPImqJY0jGuDVMRibbtLBQQ2GkpMil1N0iFODhxYuhPiBler47offq5gQqKHbKcDnJ9KYDUbyIcKcPRgWaxa3rZt3iuok6dXZY0KqZnEmRRIeCPNdVfRPEsMLLRODpVqFkk6su5TkHUPMGSnFL1uBBUNieFNFKmB0xdldlht0+IAOE9KeXmi9BA-Fa2INl+EHcHeDscRU-w4eQSZbNkNyZkOPTkOQgUOKt0tLHgbIO4wLUxq3lbns+cp2asuULcijOjFGr+H7nNKmVoAjTNWHsPlHp3rHmUwk7M5U8aIPeljUAgldbemRCPDsfGFkPXrEoRhQR-t09tMw4yrbDuAqF8PUHnIIZkEiskoUx43WvIf8YEd0ymJIG6YS8piSxXQkWeH05lZkPzeY24vS-AY3YhlzN067M6JpTGLI6ICiyUJXd2JkI9ZYWY0jYK+-uScoaoYKuK4A8c1yJ6P5CKCYbdKJLLbKLmHUgEYgUk61OUDaVJK6OsqoJbcKJRJtAUFyDRHtO9VTBkWcVkVHLkQmkk7LCeDA-sPuObZ60BHYPGH2HeYpjeL62XCMW0UMB0WrF0ba4KHGIFNEkmCmI4KS-sF4a0-sAVusk-PKX8bizC4zcoJKMoPME5RS9oKS5ae2LUk-sqqqxrVTDW0nX9RSbOkCSENwGwXcOCfCKGwULmgoHSi6zuO2-aBeIOI1Za91GpPy2q-0oO-8YCWnZDDSdhPiljaTc6KAXUpyTyRXXyTsNXfVLXSKUUySeKWScO1KclLKfu7O2scDcYNKzLP9vaB1DyCdsImXoqCmyrO7SabAGaba9JM6IsgxV2HK0jBizyNVXciTjGjByO6DOGXOOWdGSmlWXM1eIsMPZawKNgQ0xAu5lJvrr4cYFG+tSHFuXOQKkwXMzaJEdATkNIxmXJKpIsAUEpXYPmLIgR2xTpaHB+SBSUPs9JQ6gJx8rsMbiE72NjfjlPERJpdE7J63nhehexv4nxyDXR-VJpyKNp+ErRFaWYBtkqnFBx6vLEwZdZZZ+p1BWkjzuRJkMXi1TUXmKkO52Z0RV5xJT56cDfUoP5wORaAHrbH+BRlUMxbu0WSZ2hXlUFZR+lqKMAc+nYLUClSFDk5KjGEqs+hF3lycgV9R1NRaFUmVyPLuHMUIUoB1CKLIBF11YCk3MrrF3R35yFAF3JBkGvjuA1NLMIvUBF0ddtZgHM2yaLssFeEaymFShbERvyG6SAZl-22XELSdDTd03DdIKIImDcw4KmEOgOCFCknAh1DGJTTnUO62kk37QitBLYLyLyGOkBETNIHCxW2SzaO94nf8SnaO0e8EngN954QM6OLXqfUBAcKeLbMKDmEmN1G9a+24gnbneSV+zKWwcT6SEj+aGWglQD0sPyCa4QgeP3ApUtaoPXY3f4M3UkwPUw4Ri6Dff2Q4K9OuPsKPTeacHcjHQLV-ZwF6mvT3ba1YCh05itKRkxUBPVCh-jYW9K324SHL0-cDHwK-V6rAGrEk+BfSgviQoDufXto4OByfJeAWtBy4EAA */ id: 'Modeling', tsTypes: {} as import('./modelingMachine.typegen').Typegen0, @@ -804,8 +813,34 @@ export const modelingMachine = createMachine( target: 'Tangential arc to', cond: 'next is tangential arc', }, + { + target: 'Circle tool', + cond: 'next is circle', + }, ], }, + + 'Circle tool': { + on: { + 'change tool': 'Change Tool', + }, + + states: { + 'awaiting origin': { + on: { + 'Add circle origin': { + target: 'Finished Circle', + actions: 'set up draft circle', + }, + }, + }, + + 'Finished Circle': {}, + }, + + initial: 'awaiting origin', + entry: 'listen for circle origin', + }, }, initial: 'Init', @@ -1047,7 +1082,11 @@ export const modelingMachine = createMachine( isEditingExistingSketch({ sketchDetails }), 'next is rectangle': ({ sketchDetails }, _, { state }) => { if ((state?.event as any).data.tool !== 'rectangle') return false - return canRectangleTool({ sketchDetails }) + return canRectangleOrCircleTool({ sketchDetails }) + }, + 'next is circle': ({ sketchDetails }, _, { state }) => { + if ((state?.event as any).data.tool !== 'circle') return false + return canRectangleOrCircleTool({ sketchDetails }) }, 'next is line': (_, __, { state }) => (state?.event as any).data.tool === 'line', @@ -1301,6 +1340,23 @@ export const modelingMachine = createMachine( }, }) }, + 'listen for circle origin': ({ sketchDetails }) => { + if (!sketchDetails) return + sceneEntitiesManager.setupNoPointsListener({ + sketchDetails, + afterClick: (args) => { + const twoD = args.intersectionPoint?.twoD + if (twoD) { + sceneInfra.modelingSend({ + type: 'Add circle origin', + data: [twoD.x, twoD.y], + }) + } else { + console.error('No intersection point found') + } + }, + }) + }, 'set up draft rectangle': ({ sketchDetails }, { data }) => { if (!sketchDetails || !data) return sceneEntitiesManager.setupDraftRectangle( @@ -1311,6 +1367,16 @@ export const modelingMachine = createMachine( data ) }, + 'set up draft circle': ({ sketchDetails }, { data }) => { + if (!sketchDetails || !data) return + sceneEntitiesManager.setupDraftCircle( + sketchDetails.sketchPathToNode, + sketchDetails.zAxis, + sketchDetails.yAxis, + sketchDetails.origin, + data + ) + }, 'set up draft line without teardown': ({ sketchDetails }) => { if (!sketchDetails) return sceneEntitiesManager.setUpDraftSegment( @@ -1683,7 +1749,7 @@ export function isEditingExistingSketch({ return hasStartProfileAt && pipeExpression.body.length > 2 } -export function canRectangleTool({ +export function canRectangleOrCircleTool({ sketchDetails, }: { sketchDetails: SketchDetails | null From acfa95f728a804e3d5e85dbca257faaeacac7e77 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Sat, 24 Aug 2024 21:08:33 +1000 Subject: [PATCH 02/41] basic circle edit working --- src/clientSideScene/sceneEntities.ts | 435 +++++++++++++++++++++++++-- src/clientSideScene/segments.ts | 125 +++++++- src/lang/std/sketch.ts | 31 ++ src/wasm-lib/kcl/src/executor.rs | 17 ++ src/wasm-lib/kcl/src/std/extrude.rs | 2 +- src/wasm-lib/kcl/src/std/shapes.rs | 80 ++++- 6 files changed, 649 insertions(+), 41 deletions(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index d677d50ef6..17ee8f4f68 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -62,9 +62,10 @@ import { import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { executeAst } from 'lang/langHelpers' import { + circleSegment, createArcGeometry, dashedStraight, - profileStart, + createProfileStartHandle, straightSegment, tangentialArcToSegment, } from './segments' @@ -72,6 +73,7 @@ import { addCallExpressionsToPipe, addCloseToPipe, addNewSketchLn, + changeCircleArguments, changeSketchArguments, updateStartProfileAtArgs, } from 'lang/std/sketch' @@ -119,6 +121,10 @@ export const TANGENTIAL_ARC_TO__SEGMENT_DASH = 'tangential-arc-to-segment-body-dashed' export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment' export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body' +export const CIRCLE_SEGMENT = 'circle-segment' +export const CIRCLE_SEGMENT_BODY = 'circle-segment-body' +export const CIRCLE_SEGMENT_DASH = 'circle-segment-body-dashed' +export const CIRCLE_CENTER_HANDLE = 'circle-center-handle' export const SEGMENT_WIDTH_PX = 1.6 export const HIDE_SEGMENT_LENGTH = 75 // in pixels export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels @@ -186,6 +192,27 @@ export class SceneEntities { }) ) } + if ( + segment.userData.from && + segment.userData.to && + segment.userData.center && + segment.userData.radius && + segment.userData.prevSegment && + segment.userData.type === CIRCLE_SEGMENT + ) { + callbacks.push( + this.updateCircleSegment({ + prevSegment: segment.userData.prevSegment, + from: segment.userData.from, + to: segment.userData.to, + center: segment.userData.center, + radius: segment.userData.radius, + group: segment, + scale: factor, + }) + ) + } + if (segment.name === PROFILE_START) { segment.scale.set(factor, factor, factor) } @@ -421,19 +448,21 @@ export class SceneEntities { maybeModdedAst, sketchGroup.start.__geoMeta.sourceRange ) - const _profileStart = profileStart({ - from: sketchGroup.start.from, - id: sketchGroup.start.__geoMeta.id, - pathToNode: segPathToNode, - scale: factor, - theme: sceneInfra._theme, - }) - _profileStart.layers.set(SKETCH_LAYER) - _profileStart.traverse((child) => { - child.layers.set(SKETCH_LAYER) - }) - group.add(_profileStart) - this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart + if (sketchGroup?.value?.[0].type !== 'Circle') { + const _profileStart = createProfileStartHandle({ + from: sketchGroup.start.from, + id: sketchGroup.start.__geoMeta.id, + pathToNode: segPathToNode, + scale: factor, + theme: sceneInfra._theme, + }) + _profileStart.layers.set(SKETCH_LAYER) + _profileStart.traverse((child) => { + child.layers.set(SKETCH_LAYER) + }) + group.add(_profileStart) + this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart + } const callbacks: (() => SegmentOverlayPayload | null)[] = [] sketchGroup.value.forEach((segment, index) => { let segPathToNode = getNodePathFromSourceRange( @@ -498,6 +527,32 @@ export class SceneEntities { scale: factor, }) ) + } else if (segment.type === 'Circle') { + seg = circleSegment({ + prevSegment: sketchGroup.value[index - 1], + from: segment.from, + to: segment.to, + center: segment.center, + radius: segment.radius, + id: segment.__geoMeta.id, + pathToNode: segPathToNode, + isDraftSegment, + scale: factor, + texture: sceneInfra.extraSegmentTexture, + theme: sceneInfra._theme, + isSelected, + }) + callbacks.push( + this.updateCircleSegment({ + prevSegment: sketchGroup.value[index - 1], + from: segment.from, + to: segment.to, + center: segment.center, + radius: segment.radius, + group: seg, + scale: factor, + }) + ) } else { seg = straightSegment({ from: segment.from, @@ -587,6 +642,7 @@ export class SceneEntities { segmentName: 'line' | 'tangentialArcTo' = 'line', shouldTearDown = true ) => { + // try { const _ast = structuredClone(kclManager.ast) const _node1 = getNodeFromPath( @@ -602,11 +658,10 @@ export class SceneEntities { kclManager.programMemory.get(variableDeclarationName), variableDeclarationName ) - if (err(sg)) return sg - const lastSeg = sg.value?.slice(-1)[0] || sg.start + if (err(sg)) return Promise.reject(sg) + const lastSeg = sg?.value?.slice(-1)[0] || sg.start const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1` - const mod = addNewSketchLn({ node: _ast, programMemory: kclManager.programMemory, @@ -621,7 +676,7 @@ export class SceneEntities { const draftExpressionsIndices = { start: index, end: index } - if (shouldTearDown) await this.tearDownSketch({ removeAxis: false }) + // if (shouldTearDown) await this.tearDownSketch({ removeAxis: false }) sceneInfra.resetMouseListeners() const { truncatedAst, programMemoryOverride, sketchGroup } = @@ -725,6 +780,9 @@ export class SceneEntities { }, ...this.mouseEnterLeaveCallbacks(), }) + // } catch (e) { + // console.log('yo wtf', e) + // } } setupDraftRectangle = async ( sketchPathToNode: PathToNode, @@ -746,6 +804,164 @@ export class SceneEntities { const startSketchOn = _node1.node?.declarations const startSketchOnInit = startSketchOn?.[0]?.init + const sg = sketchGroupFromKclValue( + kclManager.programMemory.get(variableDeclarationName), + variableDeclarationName + ) + if (err(sg)) return sg + const tags: [string, string, string] = [ + findUniqueName(_ast, 'rectangleSegmentA'), + findUniqueName(_ast, 'rectangleSegmentB'), + findUniqueName(_ast, 'rectangleSegmentC'), + ] + + startSketchOn[0].init = createPipeExpression([ + startSketchOnInit, + ...getRectangleCallExpressions(rectangleOrigin, tags), + ]) + + let _recastAst = parse(recast(_ast)) + if (trap(_recastAst)) return Promise.reject(_recastAst) + _ast = _recastAst + + const { programMemoryOverride, truncatedAst } = await this.setupSketch({ + sketchPathToNode, + forward, + up, + position: sketchOrigin, + maybeModdedAst: _ast, + draftExpressionsIndices: { start: 0, end: 3 }, + }) + + sceneInfra.setCallbacks({ + onMove: async (args) => { + // Update the width and height of the draft rectangle + const pathToNodeTwo = structuredClone(sketchPathToNode) + pathToNodeTwo[1][0] = 0 + + const _node = getNodeFromPath( + truncatedAst, + pathToNodeTwo || [], + 'VariableDeclaration' + ) + if (trap(_node)) return Promise.reject(_node) + const sketchInit = _node.node?.declarations?.[0]?.init + + const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0] + const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] + + if (sketchInit.type === 'PipeExpression') { + updateRectangleSketch(sketchInit, x, y, tags[0]) + } + + const { programMemory } = await executeAst({ + ast: truncatedAst, + useFakeExecutor: true, + engineCommandManager: this.engineCommandManager, + programMemoryOverride, + }) + this.sceneProgramMemory = programMemory + const sketchGroup = sketchGroupFromKclValue( + programMemory.get(variableDeclarationName), + variableDeclarationName + ) + if (err(sketchGroup)) return Promise.reject(sketchGroup) + const sgPaths = sketchGroup.value + const orthoFactor = orthoScale(sceneInfra.camControls.camera) + + this.updateSegment( + sketchGroup.start, + 0, + 0, + _ast, + orthoFactor, + sketchGroup + ) + sgPaths.forEach((seg, index) => + this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup) + ) + }, + onClick: async (args) => { + // Commit the rectangle to the full AST/code and return to sketch.idle + const cornerPoint = args.intersectionPoint?.twoD + if (!cornerPoint || args.mouseEvent.button !== 0) return + + const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0]) + const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1]) + + const _node = getNodeFromPath( + _ast, + sketchPathToNode || [], + 'VariableDeclaration' + ) + if (trap(_node)) return Promise.reject(_node) + const sketchInit = _node.node?.declarations?.[0]?.init + + if (sketchInit.type === 'PipeExpression') { + updateRectangleSketch(sketchInit, x, y, tags[0]) + + let _recastAst = parse(recast(_ast)) + if (trap(_recastAst)) return Promise.reject(_recastAst) + _ast = _recastAst + + // Update the primary AST and unequip the rectangle tool + await kclManager.executeAstMock(_ast) + sceneInfra.modelingSend({ type: 'Finish rectangle' }) + + const { programMemory } = await executeAst({ + ast: _ast, + useFakeExecutor: true, + engineCommandManager: this.engineCommandManager, + programMemoryOverride, + }) + + // Prepare to update the THREEjs scene + this.sceneProgramMemory = programMemory + const sketchGroup = sketchGroupFromKclValue( + programMemory.get( + variableDeclarationName + ), variableDeclarationName) + if (err(sketchGroup)) return Promise.reject(sketchGroup) + const sgPaths = sketchGroup.value + const orthoFactor = orthoScale(sceneInfra.camControls.camera) + + // Update the starting segment of the THREEjs scene + this.updateSegment( + sketchGroup.start, + 0, + 0, + _ast, + orthoFactor, + sketchGroup + ) + // Update the rest of the segments of the THREEjs scene + sgPaths.forEach((seg, index) => + this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup) + ) + } + }, + }) + } + setupDraftCircle = async ( + sketchPathToNode: PathToNode, + forward: [number, number, number], + up: [number, number, number], + sketchOrigin: [number, number, number], + rectangleOrigin: [x: number, y: number] + ) => { + let _ast = structuredClone(kclManager.ast) + + const _node1 = getNodeFromPath( + _ast, + sketchPathToNode || [], + 'VariableDeclaration' + ) + if (trap(_node1)) return Promise.reject(_node1) + const variableDeclarationName = + _node1.node?.declarations?.[0]?.id?.name || '' + const startSketchOn = _node1.node?.declarations + const startSketchOnInit = startSketchOn?.[0]?.init + const tags: [string, string, string] = [ findUniqueName(_ast, 'rectangleSegmentA'), findUniqueName(_ast, 'rectangleSegmentB'), @@ -1047,7 +1263,9 @@ export class SceneEntities { STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, PROFILE_START, + CIRCLE_SEGMENT, ]) + const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE]) if (!group) return const pathToNode: PathToNode = structuredClone(group.userData.pathToNode) const varDecIndex = pathToNode[1][0] @@ -1065,7 +1283,7 @@ export class SceneEntities { group.userData.from[0], group.userData.from[1], ] - const to: [number, number] = [intersection2d.x, intersection2d.y] + const dragTo: [number, number] = [intersection2d.x, intersection2d.y] let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast } const _node = getNodeFromPath( @@ -1088,16 +1306,42 @@ export class SceneEntities { modded = updateStartProfileAtArgs({ node: modifiedAst, pathToNode, - to, + to: dragTo, from, previousProgramMemory: kclManager.programMemory, }) + } else if (group.name === CIRCLE_SEGMENT && subGroup?.name === ARROWHEAD) { + // is dragging the radius handle + modded = changeCircleArguments( + modifiedAst, + kclManager.programMemory, + [node.start, node.end], + group.userData.center, + Math.sqrt( + (group.userData.center[0] - dragTo[0]) ** 2 + + (group.userData.center[0] - dragTo[0]) ** 2 + ) + ) + console.log('modded', modded) + } else if ( + group.name === CIRCLE_SEGMENT && + subGroup?.name === CIRCLE_CENTER_HANDLE + ) { + // is dragging the center handle + modded = changeCircleArguments( + modifiedAst, + kclManager.programMemory, + [node.start, node.end], + dragTo, + group.userData.radius + ) + console.log('modded', modded) } else { modded = changeSketchArguments( modifiedAst, kclManager.programMemory, [node.start, node.end], - to, + dragTo, from ) } @@ -1216,6 +1460,20 @@ export class SceneEntities { group, scale: factor, }) + } else if ( + type === CIRCLE_SEGMENT && + 'center' in segment && + 'radius' in segment + ) { + return this.updateCircleSegment({ + prevSegment: sgPaths[index - 1], + from: segment.from, + to: segment.to, + center: segment.center, + radius: segment.radius, + group, + scale: factor, + }) } else if (type === PROFILE_START) { group.position.set(segment.from[0], segment.from[1], 0) group.scale.set(factor, factor, factor) @@ -1241,6 +1499,9 @@ export class SceneEntities { group.userData.prevSegment = prevSegment const arrowGroup = group.getObjectByName(ARROWHEAD) as Group const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE) + if (!prevSegment) { + console.trace('prevSegment is undefined') + } const previousPoint = prevSegment?.type === 'TangentialArcTo' @@ -1336,6 +1597,111 @@ export class SceneEntities { angle, }) } + updateCircleSegment({ + prevSegment, + from, + to, + center, + radius, + group, + scale = 1, + }: { + prevSegment: SketchGroup['value'][number] + from: [number, number] + to: [number, number] + center: [number, number] + radius: number + group: Group + scale?: number + }): () => SegmentOverlayPayload | null { + group.userData.from = from + group.userData.to = to + group.userData.center = center + group.userData.radius = radius + group.userData.prevSegment = prevSegment + const arrowGroup = group.getObjectByName(ARROWHEAD) as Group + const circleCenterHandle = group.getObjectByName( + CIRCLE_CENTER_HANDLE + ) as Group + + const pxLength = (2 * radius * Math.PI) / scale + const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH + const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH + + const hoveredParent = + sceneInfra.hoveredObject && + getParentGroup(sceneInfra.hoveredObject, [TANGENTIAL_ARC_TO_SEGMENT]) + let isHandlesVisible = !shouldHideIdle + if (hoveredParent && hoveredParent?.uuid === group?.uuid) { + isHandlesVisible = !shouldHideHover + } + + if (arrowGroup) { + arrowGroup.position.set( + center[0] + Math.cos(Math.PI / 4) * radius, + center[1] + Math.sin(Math.PI / 4) * radius, + 0 + ) + + const arrowheadAngle = Math.PI / 4 + arrowGroup.quaternion.setFromUnitVectors( + new Vector3(0, 1, 0), + new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0) + ) + arrowGroup.scale.set(scale, scale, scale) + arrowGroup.visible = isHandlesVisible + } + + if (circleCenterHandle) { + circleCenterHandle.position.set(center[0], center[1], 0) + circleCenterHandle.scale.set(scale, scale, scale) + circleCenterHandle.visible = isHandlesVisible + } + + const circleSegmentBody = group.children.find( + (child) => child.userData.type === CIRCLE_SEGMENT_BODY + ) as Mesh + + if (circleSegmentBody) { + const newGeo = createArcGeometry({ + radius, + center, + startAngle: 0, + endAngle: Math.PI * 2, + ccw: true, + scale, + }) + circleSegmentBody.geometry = newGeo + } + const circleSegmentBodyDashed = group.children.find( + (child) => child.userData.type === CIRCLE_SEGMENT_DASH + ) as Mesh + if (circleSegmentBodyDashed) { + // consider throttling the whole updateTangentialArcToSegment + // if there are more perf considerations going forward + this.throttledUpdateDashedArcGeo({ + // ...arcInfo, + center, + radius, + ccw: true, + startAngle: 0, + endAngle: 360, + mesh: circleSegmentBodyDashed, + isDashed: true, + scale, + }) + } + const angle = 0 + return () => + sceneInfra.updateOverlayDetails({ + arrowGroup, + group, + isHandlesVisible, + from, + to, + angle, + }) + } throttledUpdateDashedArcGeo = throttle( ( args: Parameters[0] & { @@ -1470,7 +1836,7 @@ export class SceneEntities { } private _tearDownSketch( callDepth = 0, - resolve: (val: unknown) => void, + resolve: any, reject: () => void, { removeAxis = true }: { removeAxis?: boolean } ) { @@ -1500,7 +1866,7 @@ export class SceneEntities { this._tearDownSketch(callDepth + 1, resolve, reject, { removeAxis }) }, delay) } else { - reject() + resolve(true) } } sceneInfra.camControls.enableRotate = true @@ -1512,7 +1878,7 @@ export class SceneEntities { removeAxis = true, }: { removeAxis?: boolean - } = {}) { + } = {}): Promise { // I think promisifying this is mostly a side effect of not having // "setupSketch" correctly capture a promise when it's done // so we're effectively waiting for to be finished setting up the scene just to tear it down @@ -1578,6 +1944,16 @@ export class SceneEntities { group: parent, scale: factor, }) + } else if (parent.name === CIRCLE_SEGMENT) { + this.updateCircleSegment({ + prevSegment: parent.userData.prevSegment, + from: parent.userData.from, + to: parent.userData.to, + center: parent.userData.center, + radius: parent.userData.radius, + group: parent, + scale: factor, + }) } return } @@ -1613,6 +1989,16 @@ export class SceneEntities { group: parent, scale: factor, }) + } else if (parent.name === CIRCLE_SEGMENT) { + this.updateCircleSegment({ + prevSegment: parent.userData.prevSegment, + from: parent.userData.from, + to: parent.userData.to, + center: parent.userData.center, + radius: parent.userData.radius, + group: parent, + scale: factor, + }) } } const isSelected = parent?.userData?.isSelected @@ -1825,6 +2211,7 @@ function colorSegment(object: any, color: number) { const straightSegmentBody = getParentGroup(object, [ STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, + CIRCLE_SEGMENT, ]) if (straightSegmentBody) { straightSegmentBody.traverse((child) => { diff --git a/src/clientSideScene/segments.ts b/src/clientSideScene/segments.ts index e1d451fe0b..369187727f 100644 --- a/src/clientSideScene/segments.ts +++ b/src/clientSideScene/segments.ts @@ -24,6 +24,10 @@ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm' import { + CIRCLE_CENTER_HANDLE, + CIRCLE_SEGMENT, + CIRCLE_SEGMENT_BODY, + CIRCLE_SEGMENT_DASH, EXTRA_SEGMENT_HANDLE, EXTRA_SEGMENT_OFFSET_PX, HIDE_SEGMENT_LENGTH, @@ -46,7 +50,7 @@ import { import { Themes, getThemeColorForThreeJs } from 'lib/theme' import { roundOff } from 'lib/utils' -export function profileStart({ +export function createProfileStartHandle({ from, id, pathToNode, @@ -225,6 +229,28 @@ function createArrowhead(scale = 1, theme: Themes, color?: number): Group { arrowGroup.scale.set(scale, scale, scale) return arrowGroup } +function createCircleCenterHandle( + scale = 1, + theme: Themes, + color?: number +): Group { + const circleCenterGroup = new Group() + + const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later + const baseColor = getThemeColorForThreeJs(theme) + const body = new MeshBasicMaterial({ color }) + const mesh = new Mesh(geometry, body) + + circleCenterGroup.add(mesh) + + circleCenterGroup.userData = { + type: CIRCLE_CENTER_HANDLE, + baseColor, + } + circleCenterGroup.name = CIRCLE_CENTER_HANDLE + circleCenterGroup.scale.set(scale, scale, scale) + return circleCenterGroup +} function createExtraSegmentHandle( scale: number, @@ -300,6 +326,103 @@ function createLengthIndicator({ return lengthIndicatorGroup } +export function circleSegment({ + prevSegment, + from, + to, + center, + radius, + id, + pathToNode, + isDraftSegment, + scale = 1, + texture, + theme, + isSelected, +}: { + prevSegment: SketchGroup['value'][number] + from: Coords2d + center: Coords2d + radius: number + to: Coords2d + id: string + pathToNode: PathToNode + isDraftSegment?: boolean + scale?: number + texture: Texture + theme: Themes + isSelected?: boolean +}): Group { + const group = new Group() + + const geometry = createArcGeometry({ + center, + radius, + startAngle: 0, + endAngle: Math.PI * 2, + ccw: true, + isDashed: isDraftSegment, + scale, + }) + + const baseColor = getThemeColorForThreeJs(theme) + const color = isSelected ? 0x0000ff : baseColor + const body = new MeshBasicMaterial({ color }) + const mesh = new Mesh(geometry, body) + mesh.userData.type = isDraftSegment + ? CIRCLE_SEGMENT_DASH + : CIRCLE_SEGMENT_BODY + + group.userData = { + type: CIRCLE_SEGMENT, + id, + from, + to, + radius, + center, + ccw: true, + prevSegment, + pathToNode, + isSelected, + baseColor, + } + group.name = CIRCLE_SEGMENT + + const arrowGroup = createArrowhead(scale, theme, color) + arrowGroup.position.set( + center[0] + Math.cos(Math.PI / 4) * radius, + center[1] + Math.sin(Math.PI / 4) * radius, + 0 + ) + + const circleCenterGroup = createCircleCenterHandle(scale, theme, color) + circleCenterGroup.position.set(center[0], center[1], 0) + const arrowheadAngle = Math.PI / 4 + arrowGroup.quaternion.setFromUnitVectors( + new Vector3(0, 1, 0), + new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0) + ) + const pxLength = (radius * 2 * Math.PI) / scale + const shouldHide = pxLength < HIDE_SEGMENT_LENGTH + + const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme) + const extraSegmentAngle = 0 + const extraSegmentOffset = new Vector2( + Math.cos(extraSegmentAngle) * radius, + Math.sin(extraSegmentAngle) * radius + ) + extraSegmentGroup.position.set( + center[0] + extraSegmentOffset.x, + center[1] + extraSegmentOffset.y, + 0 + ) + + extraSegmentGroup.visible = !shouldHide + + group.add(mesh, arrowGroup, circleCenterGroup, extraSegmentGroup) + + return group +} export function tangentialArcToSegment({ prevSegment, from, diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 068c2fd63a..44d334cdca 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -1611,6 +1611,37 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = { tangentialArcTo, } as const +export function changeCircleArguments( + node: Program, + programMemory: ProgramMemory, + sourceRange: SourceRange, + center: [number, number], + radius: number +): { modifiedAst: Program; pathToNode: PathToNode } | Error { + const _node = { ...node } + const thePath = getNodePathFromSourceRange(_node, sourceRange) + const nodeMeta = getNodeFromPath(_node, thePath) + if (err(nodeMeta)) return nodeMeta + + const { node: callExpression, shallowPath } = nodeMeta + + if (callExpression?.callee?.name === 'circle') { + const newCenter = createArrayExpression([ + createLiteral(roundOff(center[0])), + createLiteral(roundOff(center[1])), + ]) + const newRadius = createLiteral(roundOff(radius)) + callExpression.arguments[0] = newCenter + callExpression.arguments[1] = newRadius + return { + modifiedAst: _node, + pathToNode: shallowPath, + } + } + + return new Error(`not a sketch line helper: ${callExpression?.callee?.name}`) +} + export function changeSketchArguments( node: Program, programMemory: ProgramMemory, diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index 87323c9ad7..32887e1433 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -1415,6 +1415,19 @@ pub enum Path { /// arc's direction ccw: bool, }, + /// a complete arc + Circle { + #[serde(flatten)] + base: BasePath, + /// the arc's center + #[ts(type = "[number, number]")] + center: [f64; 2], + /// the arc's radius + radius: f64, + /// arc's direction + // Maybe this one's not needed since it's a full revolution? + ccw: bool, + }, /// A path that is horizontal. Horizontal { #[serde(flatten)] @@ -1447,6 +1460,7 @@ impl Path { Path::Base { base } => base.geo_meta.id, Path::TangentialArcTo { base, .. } => base.geo_meta.id, Path::TangentialArc { base, .. } => base.geo_meta.id, + Path::Circle { base, .. } => base.geo_meta.id, } } @@ -1458,6 +1472,7 @@ impl Path { Path::Base { base } => base.tag.clone(), Path::TangentialArcTo { base, .. } => base.tag.clone(), Path::TangentialArc { base, .. } => base.tag.clone(), + Path::Circle { base, .. } => base.tag.clone(), } } @@ -1469,6 +1484,7 @@ impl Path { Path::Base { base } => base, Path::TangentialArcTo { base, .. } => base, Path::TangentialArc { base, .. } => base, + Path::Circle { base, .. } => base, } } @@ -1480,6 +1496,7 @@ impl Path { Path::Base { base } => Some(base), Path::TangentialArcTo { base, .. } => Some(base), Path::TangentialArc { base, .. } => Some(base), + Path::Circle { base, .. } => Some(base), } } } diff --git a/src/wasm-lib/kcl/src/std/extrude.rs b/src/wasm-lib/kcl/src/std/extrude.rs index 0a0081c4a9..bfd9b3e3ac 100644 --- a/src/wasm-lib/kcl/src/std/extrude.rs +++ b/src/wasm-lib/kcl/src/std/extrude.rs @@ -204,7 +204,7 @@ pub(crate) async fn do_post_extrude( for path in sketch_group.value.iter() { if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) { match path { - Path::TangentialArc { .. } | Path::TangentialArcTo { .. } => { + Path::TangentialArc { .. } | Path::TangentialArcTo { .. } | Path::Circle { .. } => { let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::ExtrudeArc { face_id: *actual_face_id, tag: path.get_base().tag.clone(), diff --git a/src/wasm-lib/kcl/src/std/shapes.rs b/src/wasm-lib/kcl/src/std/shapes.rs index 1c80c2d9d4..7e066adb1b 100644 --- a/src/wasm-lib/kcl/src/std/shapes.rs +++ b/src/wasm-lib/kcl/src/std/shapes.rs @@ -2,16 +2,19 @@ use anyhow::Result; use derive_docs::stdlib; +use kittycad::types::{Angle, ModelingCmd}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ ast::types::TagDeclarator, - errors::KclError, - executor::KclValue, - std::{Args, SketchGroup, SketchSurface}, + errors::{KclError, KclErrorDetails}, + executor::{BasePath, GeoMeta, KclValue, Path, Point2d, SketchGroup, SketchSurface}, + std::Args, }; +use super::utils::arc_center_and_end; + /// A sketch surface or a sketch group. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[ts(export)] @@ -65,23 +68,70 @@ async fn inner_circle( SketchSurfaceOrGroup::SketchSurface(surface) => surface, SketchSurfaceOrGroup::SketchGroup(group) => group.on, }; - let mut sketch_group = + let sketch_group = crate::std::sketch::inner_start_profile_at([center[0] + radius, center[1]], sketch_surface, None, args.clone()) .await?; - // Call arc. - sketch_group = crate::std::sketch::inner_arc( - crate::std::sketch::ArcData::AnglesAndRadius { - angle_start: 0.0, - angle_end: 360.0, - radius, + let from: Point2d = sketch_group.current_pen_position()?; + + let angle_start = Angle::from_degrees(0.0); + let angle_end = Angle::from_degrees(360.0); + let (center, end) = arc_center_and_end(from, angle_start, angle_end, radius); + + if angle_start == angle_end { + return Err(KclError::Type(KclErrorDetails { + message: "Arc start and end angles must be different".to_string(), + source_ranges: vec![args.source_range], + })); + } + + let id = uuid::Uuid::new_v4(); + + args.batch_modeling_cmd( + id, + ModelingCmd::ExtendPath { + path: sketch_group.id, + segment: kittycad::types::PathSegment::Arc { + start: angle_start, + end: angle_end, + center: center.into(), + radius, + relative: false, + }, + }, + ) + .await?; + + let current_path = Path::Circle { + base: BasePath { + from: center.into(), + // to: end.into(), + to: center.into(), + tag: tag.clone(), + geo_meta: GeoMeta { + id, + metadata: args.source_range.into(), + }, + }, + radius, + center: center.into(), + ccw: angle_start.degrees() < angle_end.degrees(), + }; + + let mut new_sketch_group = sketch_group.clone(); + if let Some(tag) = &tag { + new_sketch_group.add_tag(tag, ¤t_path); + } + + new_sketch_group.value.push(current_path); + + args.batch_modeling_cmd( + id, + ModelingCmd::ClosePath { + path_id: new_sketch_group.id, }, - sketch_group, - tag, - args.clone(), ) .await?; - // Call close. - crate::std::sketch::inner_close(sketch_group, None, args).await + Ok(new_sketch_group) } From 19888886993b24d18ad00e8409132d48af234c33 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Sat, 24 Aug 2024 21:43:39 +1000 Subject: [PATCH 03/41] update is editing existing sketch --- src/machines/modelingMachine.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 82e070763c..9e41c50105 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -301,7 +301,7 @@ export const modelingMachineDefaultContext = { export const modelingMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANhoBWAHQAOAMwB2KQEY5AFgCcGqWqkAaEAE9Ew0RLEqa64TIBMKmTUXCAvk71oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBEEpYSkJOUUaOWsxeylrWzk9QwQClQkrVOsZNWExFItnVxB3LDxCXwCAW1QAVyDA9hJ2MAjeGI4ueNBE5KkaCWspZfMM+XE1YsQZMQWxNQtxao0ZeRc3dDavTv9ybhG+djGoibjeRJothBpzlsvPDp+fy+KBdMC4AIAeQAbmAAE6YEj6WDPZisSbcd6IT4GbG-VoAiTYCCYMAEACiEPhgQA1n5yAALVHRdFvBJCazCOQSRSctQyKS5RRqFTWL6nNTSfnSsS5FRiZT4-7tIkksmUkZw2n0pmKSJo2JTLFJWwLFSOBy7WQ0eZFXEIczWCRqaxyOq81KKAWOJUeFXE0kUx5w3oYZmvI3spJiOQyZ1mZQyeUyRSC4RfXmndKOKRyWUpmqKX1XKCqwMAMWwmFJT3o41ZkZmQnzEjsgoVXPE2TtJS9KmEPNjuZsKbkDmLhID6r4LDhtf1LMNmKjgjEdXSrtENuEihUcjUigz-cUEhqeaUwmsqavE-9aoIyBIdPDDeXTYdTtSIusalj5qy+xfFyaSxvUKh7rKdgqFIt74GWZIACLBCMgTBKEUwvku0z8EIO7lNoCZjqm5qKGIGYxuUNSXj+iiOLR+6waWU4EAAKmAjyCOwqCCEQACCCGYRi2GzAqTpmCkspqLKf7pvayj9hU8gFPs+TCOaciMfBEgMsSYAAAqIrgcAELxc7YAAZiQoT+FAcIkEwDL+CwTC9IiIwQIJbLvoI-aSuIia-loQUyLJJS-nGlrQbmXq0VIKiaVOEiwAyqAAO4GWQxmmZwlnWbZ9mOWAXRMJwkCeY2OFJHu3IKDGYicsB4F5hmvJOqmB5ejQNBWAe1iad4dLsIyxBkJQmADTq5VvpVXq7KY6i-sBbpujILUiqe6jZJy2QHvk-WDcNSE1mAqGguC871lhxpyHmzo-vVcW-jashfPK3IaOY+a7Gp3X7TqBB3Bg-iQBw-gQL0cLtNqQ1MnWLyvsJiCkTQkqCvKNEFCkKRfCKcZJsKy00FeSZNBcfpwRNMMSJTjIAJL3o+dKnWCEL+LCZnkCQmBTYjCC7rKFQ5PIPUxUenKmPY-LCAcAp2BpzQEiqNMMtTB0MvTgaMydQRnazKWQwAXvcXM88au41BI-legK1hdbsKgZjY5Q3cYnVE7Rsp-VTysa2SRDcLA7B2Xg-j69gRsQlzYPYIHo2jHDBpCWb5ppNB0ECmYBRE18pqSksamxfYyiul7jKqzqvvEAHQckCH7OcJzmDR7HFDxwuEbTYk5t41JAo0Emshcqt9qYwOea7LUu6yELpcqz797+7ggfB7g-i8QAQt4-gABqm1Gu7qNI8wWCFGd5g7I+1GkqZugeZ79zIs-lzDleL8vterxvW8AJp7++B+SlSL5VqP4agXxKCmBw8Y1KXl-Nofu8syYlmfmXeegY341xDmQKApI-4zV2NyVSOxsho0vDneqZotDQV6rVKST80F+2rivfwpJ8DsFhu3BGZtdgnnmEoeQsgpLaDFCPKwTour91AaRLQxh6Fq1fkwj+Tl4RMHBBAbA5BXIkC1OolulA8FdxqE6EUxFaIo3NroEe+R3pWhRijWUWRH4K2VBTNWKD1YL0USHMOEchjVn0P4Lm2AoC4AMUjUU3Ii4OE+pyKh5EfymG2vsFGKRqikz+OTUsyt3EKKXpg1e9cNFc0wAEoJISwl83AnGXMZgbQFBCgoYevYrBpHyFkTI4hzB7nSYrVxOocmeLycw2AuB7L+C4jvCpu4brpG6qcN07YCgiJKLbAW8o6g3UWvyaociK6DPfiHEZYyJm-wTouJO+9qqmFTHFNSVTczLMQFeEwmyrAkMFLYXZL99n5OBgAR16FHVhUB2FTLevGWMNpzRC1SBmVQcZXZXl3JuUBXy6Y-OYUwbRxTqBnI7rzXcB5WzS13NLEKspcgZlqAsOKKNXQ5APOoJxSDCTZIYVXIZSi4RFVQLCW4XiIQojxVwy5XpLZmG0AtA+u4MxxXKIUWQqYLCkrEGijxgYABKYBBDsRCL0EYUzYwDh-O06iORaIZk6ekVIuRjCXi6oKNVldAYnRBgEcGkN8DQ0ZFMied01ypBRkA+olqsinh3FtYwct+5OsGfXcZqA2baOwCQAARrg4VV194Km5LROWGRUjyAyOKLOFR1IOHsPY1VzjMnuIGYGRk2CTpcVQNzTNFz3ycltjyHYakliJlzC1Nc4bMhaBqPMl0T9eKpVrgEHxxsm66KGK3fweBzKoAIBAbgYAiS4GhKgOkEgYDsEEPOyOmBBBrtQBU2wqQj5jrldkUQ4Cnl2AirIP8tSUx1CnTO0GZ6-HN2XZQVduB10EHhHCVAcIJBMDcuuuEXQj1+FPdB8OC7L1geve2rylVb2SllNLcwRwYxNKeTUSQrpSL0WqPkIsNbkHZOnbOtm8IG5RyXXHUD4Gt1GV3fuw9x7BCFMbph9dN7ummHmBKLITVQpPJ-Lm7qjgdgWGqH1BjrK3HMdBiJjjMdgMnSvRBuEUGYNweGAhpDQm9MXqvRJmZth8i5mWCmaoZHSg5EkFRBU8h9gD1-Sxr+O9uMbt4zuvAAmd1CbTbAQQfAxPYc4Vmzt0ETy22TEG7cHnFPebWK6aTqREEZMY9pv9ARgvb1CyZszsH4PQesyh2L8XEsSaUNIN00LFOKpzi6AjWg1xNXqCjQLoNgvf2q+F-jB7otNdTXF-QrWcMVUSE5hY+5nP9hChs+TCA8jrZ-CkYUCZdyjYq5vfwE3jOQeg3VyzDXkMnua4t+zy3O6vva3uBwdRwqXj3OKZQkS7ZGrKDYM7gT8Ckkm9u6bgmmuQ+1a95LHa8NpfSHYYwVFshJnIWOHkeFeT1Wlq6atLKlZlZY9gqH13TO3Ys+wKzj3BBU8R1htrcZSV1CqEoZqI9cgLCTOBC8V56g-s0+T-pOmAjAvYdDvjkWZtM5lwyJbyPcOrb3KjVQLolhKGgrtvkaQfyiiWLGV02RwfK+qzd8z9XENK-BCClXSPLoo41-Meauuit1TMDnOokgx3EdubIMw4PVFwlUbgdRmjEQ6IM1x4zU2Fdw5PeHyP0etFwkEJx1uqvXfq9fa6VsDrcg2A0BsnOsg0iiGqJssc9RScla05L8ryiI9qI0ZnoDCesM1bp3bxrqeVEd5j9o7P8fc8u-hil1HReg+l5-LUGMOd9wLBr0Wp6Den4ABk8DNtQK2gGDIm0JtbTe7H0h6oROhX+HGNhpAe1qOPJYuQd979P5gCQtNcAcAIBJqB-ajQ8wpEooHmRGxKBEhwywV+b+RkH+X+P+7Af+eo+eK2r6KMiwN0sg0EY4CgxgXwwBiw9q-c5oP0KYsB++raEgAAcomnpKgHgOwLACZBABAIMNogECwIwTeraBUDaLjP2FYCjEBG6OjlGvrm6F6BpmTn0lTLvnAS2p-rQf4PQYwcwaQK3G2mrmgaUBWq2NrqOu7KKF8BkM7FRkAUYmDuLrIWXN-r-lMkqndPkCOLyOoJyBmLehUARPIHYFIeBE-HYUgVQCgdPm7kjM8qeKcG+hKEsPKI7OIJbI4DkNruPDUE-CxE2hCCmk3NouQAmkfifooRUjbAih2O0nuPJD2IgG6MYjYCpo6C6AKE-L0FHomrHHOMrJCLgJujDsnjurxN4CxIIC0VuoIO0ewJ0aEm9gSiFCePUDdFePKIKB9CYQqPjvKNBDGHFPuPFNYVkm4lqqECzh-gUfgJQVoage9ntleNyMeEWj1LbFkJalmDLIyqIMQqdnsXWoccujgucRIFLlDEEHcFHvynCEZHCAQJWD-slP4NykcQjsUbkJKEInUOnKRHUB5rmAODuF1Ipt1N1CXF8dkj8ccYoQCeVlDGhlAHgCwWwfCb8VDtSXgEicKOKiKFJPuDGIsSYbuKeIRqRMblJD6MSQcSEIyf8dCTHAyJAP4KSQjn-tMWbDznwU5lUG9HUDjHehlhYMfMjLGE-OQKSGQIEG5GSL6vuJbNBDaFtMOGpC1OLLXu7KRLsAqI3r0vsS3ixiCfGhMtCMmmmoGEnnuorkJj6WxpxNxP6ZDIGazuJkqVGLYBbPmETE5tgf2CWvfhEhQg0MRLsTIZ6VTFLvynumxgmkmjGemmSDbndgzg9mGdwPXJGcJgGVWXnqEQXqUBsXdKmZIqoI8ntvIBFMNviYWvKODiCQcqvNyj0HypOfkkwT0fLiGYeqMfOSvIIDObytquuR-BdB2ToUma0jsNSlfqoGuLKvIM6LUDsPkEKETO6S4oWWXMWbud4mhr4sUvoEuRFiuTumuQKqhobAuiUhJsYJbGAssGYtLFYI7HyfyAtPbMYIvhOQKqxhzF+T+bDv+dxG+bgMJmxkUv4g5nGH+FgbRAeCGnJNChULkMfPsAqNoD0k+XWq+WhQBl+YEjgCElhX0RIABZyngEBehueiUsztxVMdoVcbYMOlJPMGYJAlJC+nzPVG1OPNQsKMOKhYJQUoRY3CUlxcEt0cGVFvxbhYBbZmJWUpJZcbzEeVKMYC6LgS6MpXRNyPsBEq7GjCKNpVOYEKMkwOWdvLxX+WZYIHhWMQFc2XwGBZIFkPkCbo8UsDnLqfyacDGA4P3P7r5b8kcoFSciFaZQJVOZFfZM2foA5gsIyjUMKbJnYDnF6ARrkLbBnPYJyDlcwmAACkCo7uwoVYrsVfkjqt1ResrhJsOg0ksETP5KkJYr2DtKeEoMidAUmG6B1UolinZNWFgP1aueZTpYIJtTihcQedJXuHjEyrRJyPYDuB5sdieP2PkNoHgVsTBKKf0kQMfmcf4CxAfpgIqVJXZYKGkCmNoOXiHipparUJbMpHFEYgcI+bWtkp9Sfr9YfsEYDcaPYPfv5F9nUASZqXJEGjDTkHDX1uIE-Cjd9Wjf9VQNYJjVGDYOBf2KKPbGOETHdcTamWBPDRYMVh6XWlTTAD9X9X-jIAze+PIFJJbETF0qcAgqoFDQONzWTSaojaVh9V9cLTTX-ioBLZVOYHFDyFkL+PUlYOYLtpkNDamZidoEFNIU3hLlTEQNgHCEaecaccLUUQme+OaESusikNLMsMBHfuUL5muA-FeJyPmY7TYSrC7W7VDuSSQJSV6syd0bxKwbcK7e7f4OnRUuBNBM6CSnAkFMYfaKYfyWuIKRyR2LPP4LgImrlGSL4KSNZBgJZK5JwYZG3LZcaIILbE6KKPyMKDavUKPSIaRY5dkA6vkD+JpGQNgF0MMFDBMhZkZLtTuovcvSMM2c3RUqRnwbUIUKIFfHNUYDuIsAQssEdmbgvT-jvavYmuxAZkCWrJvRINvcMNqlxGMWrAfcoILFJJeIWDdHEfaPYN5iSjsI4NkEkU0M0I3RgPAFEB6X3SuLNcSreQoOoBYJsHJAksTsAb+AtHFA7QLVOOg95D9Fg-MY9HgwQQKNcvmPuFtFyQlGqNpLpBlEZCg4nJ2auC6LQy5rg+oJajkIsEtUTE9AoBbl8YlMlGlDw3AFQ5VIIDsE6AoB9P3NaPuPg72A+ZI8jPibI+QyxcrKo13FIU4epm5m4QboA8KFNTYN1IRMyrHc+XPPImqJY0jGuDVMRibbtLBQQ2GkpMil1N0iFODhxYuhPiBler47offq5gQqKHbKcDnJ9KYDUbyIcKcPRgWaxa3rZt3iuok6dXZY0KqZnEmRRIeCPNdVfRPEsMLLRODpVqFkk6su5TkHUPMGSnFL1uBBUNieFNFKmB0xdldlht0+IAOE9KeXmi9BA-Fa2INl+EHcHeDscRU-w4eQSZbNkNyZkOPTkOQgUOKt0tLHgbIO4wLUxq3lbns+cp2asuULcijOjFGr+H7nNKmVoAjTNWHsPlHp3rHmUwk7M5U8aIPeljUAgldbemRCPDsfGFkPXrEoRhQR-t09tMw4yrbDuAqF8PUHnIIZkEiskoUx43WvIf8YEd0ymJIG6YS8piSxXQkWeH05lZkPzeY24vS-AY3YhlzN067M6JpTGLI6ICiyUJXd2JkI9ZYWY0jYK+-uScoaoYKuK4A8c1yJ6P5CKCYbdKJLLbKLmHUgEYgUk61OUDaVJK6OsqoJbcKJRJtAUFyDRHtO9VTBkWcVkVHLkQmkk7LCeDA-sPuObZ60BHYPGH2HeYpjeL62XCMW0UMB0WrF0ba4KHGIFNEkmCmI4KS-sF4a0-sAVusk-PKX8bizC4zcoJKMoPME5RS9oKS5ae2LUk-sqqqxrVTDW0nX9RSbOkCSENwGwXcOCfCKGwULmgoHSi6zuO2-aBeIOI1Za91GpPy2q-0oO-8YCWnZDDSdhPiljaTc6KAXUpyTyRXXyTsNXfVLXSKUUySeKWScO1KclLKfu7O2scDcYNKzLP9vaB1DyCdsImXoqCmyrO7SabAGaba9JM6IsgxV2HK0jBizyNVXciTjGjByO6DOGXOOWdGSmlWXM1eIsMPZawKNgQ0xAu5lJvrr4cYFG+tSHFuXOQKkwXMzaJEdATkNIxmXJKpIsAUEpXYPmLIgR2xTpaHB+SBSUPs9JQ6gJx8rsMbiE72NjfjlPERJpdE7J63nhehexv4nxyDXR-VJpyKNp+ErRFaWYBtkqnFBx6vLEwZdZZZ+p1BWkjzuRJkMXi1TUXmKkO52Z0RV5xJT56cDfUoP5wORaAHrbH+BRlUMxbu0WSZ2hXlUFZR+lqKMAc+nYLUClSFDk5KjGEqs+hF3lycgV9R1NRaFUmVyPLuHMUIUoB1CKLIBF11YCk3MrrF3R35yFAF3JBkGvjuA1NLMIvUBF0ddtZgHM2yaLssFeEaymFShbERvyG6SAZl-22XELSdDTd03DdIKIImDcw4KmEOgOCFCknAh1DGJTTnUO62kk37QitBLYLyLyGOkBETNIHCxW2SzaO94nf8SnaO0e8EngN954QM6OLXqfUBAcKeLbMKDmEmN1G9a+24gnbneSV+zKWwcT6SEj+aGWglQD0sPyCa4QgeP3ApUtaoPXY3f4M3UkwPUw4Ri6Dff2Q4K9OuPsKPTeacHcjHQLV-ZwF6mvT3ba1YCh05itKRkxUBPVCh-jYW9K324SHL0-cDHwK-V6rAGrEk+BfSgviQoDufXto4OByfJeAWtBy4EAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANhoBWAHQAOAMwB2KQEY5AFgCcGqWqkAaEAE9Ew0RLEqa64TIBMKmTUXCAvk71oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBEEpYSkJOUUaOWsxeylrWzk9QwQClQkrVOsZNWExFItnVxB3LDxCXwCAW1QAVyDA9hJ2MAjeGI4ueNBE5KkaCWspZfMM+XE1YsQZMQWxNQtxao0ZeRc3dDavTv9ybhG+djGoibjeRJothBpzlsvPDp+fy+KBdMC4AIAeQAbmAAE6YEj6WDPZisSbcd6IT4GbG-VoAiTYCCYMAEACiEPhgQA1n5yAALVHRdFvBJCazCOQSRSctQyKS5RRqFTWL6nNTSfnSsS5FRiZT4-7tIkksmUkZw2n0pmKSJo2JTLFJWwLFSOBy7WQ0eZFXEIczWCRqaxyOq81KKAWOJUeFXE0kUx5w3oYZmvI3spJiOQyZ1mZQyeUyRSC4RfXmndKOKRyWUpmqKX1XKCqwMAMWwmFJT3o41ZkZmQnzEjsgoVXPE2TtJS9KmEPNjuZsKbkDmLhID6r4LDhtf1LMNmKjgjEdXSrtENuEihUcjUigz-cUEhqeaUwmsqavE-9aoIyBIdPDDeXTYdTtSIusalj5qy+xfFyaSxvUKh7rKdgqFIt74GWZIACLBCMgTBKEUwvku0z8EIO7lNoCZjqm5qKGIGYxuUNSXj+iiOLR+6waWU4EAAKmAjyCOwqCCEQACCCGYRi2GzAqTpmCkspqLKf7pvayj9hU8gFPs+TCOaciMfBEgMsSYAAAqIrgcAELxc7YAAZiQoT+FAcIkEwDL+CwTC9IiIwQIJbLvoI-aSuIia-loQUyLJJS-nGlrQbmXq0VIKiaVOEiwAyqAAO4GWQxmmZwlnWbZ9mOWAXRMJwkCeY2OFJHu3IKDGYicsB4F5hmvJOqmB5ejQNBWAe1iad4dLsIyxBkJQmADTq5VvpVXq7KY6i-sBbpujILUiqe6jZJy2QHvk-WDcNSE1mAqGguC871lhxpyHmzo-vVcW-jashfPK3IaOY+a7Gp3X7TqBB3Bg-iQBw-gQL0cLtNqQ1MnWLyvsJiCkTQkqCvKNEFCkKRfCKcZJsKy00FeSZNBcfpwRNMMSJTjIAJL3o+dKnWCEL+LCZnkCQmBTYjCC7rKFQ5PIPUxUenKmPY-LCAcAp2BpzQEiqNMMtTB0MvTgaMydQRnazKWQwAXvcXM88au41BI-legK1hdbsKgZjY5Q3cYnVE7Rsp-VTysa2SRDcLA7B2Xg-j69gRsQlzYPYIHo2jHDBpCWb5ppNB0ECmYBRE18pqSksamxfYyiul7jKqzqvvEAHQckCH7OcJzmDR7HFDxwuEbTYk5t41JAo0Emshcqt9qYwOea7LUu6yELpcqz797+7ggfB7g-i8QAQt4-gABqm1Gu7qNI8wWCFGd5g7I+1GkqZugeZ79zIs-lzDleL8vterxvW8AJp7++B+SlSL5VqP4agXxKCmBw8Y1KXl-Nofu8syYlmfmXeegY341xDmQKApI-4zV2NyVSOxsho0vDneqZotDQV6rVKST80F+2rivfwpJ8DsFhu3BGZtdgnnmEoeQsgpLaDFCPKwTour91AaRLQxh6Fq1fkwj+Tl4RMHBBAbA5BXIkC1OolulA8FdxqE6EUxFaIo3NroEe+R3pWhRijWUWRH4K2VBTNWKD1YL0USHMOEchjVn0P4Lm2AoC4AMUjUU3Ii4OE+pyKh5EfymG2vsFGKRqikz+OTUsyt3EKKXpg1e9cNFc0wAEoJISwl83AnGXMZgbQFBCgoYevYrBpHyFkTI4hzB7nSYrVxOocmeLycw2AuB7L+C4jvCpu4brpG6qcN07YCgiJKLbAW8o6g3UWvyaociK6DPfiHEZYyJm-wTouJO+9qqmFTHFNSVTczLMQFeEwmyrAkMFLYXZL99n5OBgAR16FHVhUB2FTLevGWMNpzRC1SBmVQcZXZXl3JuUBXy6Y-OYUwbRxTqBnI7rzXcB5WzS13NLEKspcgZlqAsOKKNXQ5APOoJxSDCTZIYVXIZSi4RFVQLCW4XiIQojxVwy5XpLZmG0AtA+u4MxxXKIUWQqYLCkrEGijxgYABKYBBDsRCL0EYUzYwDh-O06iORaIZk6ekVIuRjCXi6oKNVldAYnRBgEcGkN8DQ0ZFMied01ypBRkA+olqsinh3FtYwct+5OsGfXcZqA2baOwCQAARrg4VV194Km5LROWGRUjyAyOKLOFR1IOHsPY1VzjMnuIGYGRk2CTpcVQNzTNFz3ycltjyHYakliJlzC1Nc4bMhaBqPMl0T9eKpVrgEHxxsm66KGK3fweBzKoAIBAbgYAiS4GhKgOkEgYDsEEPOyOmBBBrtQBU2wqQj5jrldkUQ4Cnl2AirIP8tSUx1CnTO0GZ6-HN2XZQVduB10EHhHCVAcIJBMDcuuuEXQj1+FPdB8OC7L1geve2rylVb2SllNLcwRwYxNKeTUSQrpSL0WqPkIsNbkHZOnbOtm8IG5RyXXHUD4Gt1GV3fuw9x7BCFMbph9dN7ummHmBKLITVQpPJ-Lm7qjgdgWGqH1BjrK3HMdBiJjjMdgMnSvRBuEUGYNweGAhpDQm9MXqvRJmZth8i5mWCmaoZHSg5EkFRBU8h9gD1-Sxr+O9uMbt4zuvAAmd1CbTbAQQfAxPYc4Vmzt0ETy22TEG7cHnFPebWK6aTqREEZMY9pv9ARgvb1CyZszsH4PQesyh2L8XEsSaUNIN00LFOKpzi6AjWg1xNXqCjQLoNgvf2q+F-jB7otNdTXF-QrWcMVUSE5hY+5nP9hChs+TCA8jrZ-CkYUCZdyjYq5vfwE3jOQeg3VyzDXkMnua4t+zy3O6vva3uBwdRwqXj3OKZQkS7ZGrKDYM7gT8Ckkm9u6bgmmuQ+1a95LHa8NpfSHYYwVFshJnIWOHkeFeT1Wlq6atLKlZlZY9gqH13TO3Ys+wKzj3BBU8R1htrcZSV1CqEoZqI9cgLCTOBC8V56g-s0+T-pOmAjAvYdDvjkWZtM5lwyJbyPcOrb3KjVQLolhKGgrtvkaQfyiiWLGV02RwfK+qzd8z9XENK-BCClXSPLoo41-Meauuit1TMDnOokgx3EdubIMw4PVFwlUbgdRmjEQ6IM1x4zU2Fdw5PeHyP0etFwkEJx1uqvXfq9fa6VsDrcg2A0BsnOsg0iiGqJssc9RScla05L8ryiI9qI0ZnoDCesM1bp3bxrqeVEd5j9o7P8fc8u-hil1HReg+l5-LUGMOd9wLBr0Wp6Den4ABk8DNtQK2gGDIm0JtbTe7H0h6oROhX+HGNhpAe1qOPJYuQd979P5gCQtNcAcAIBJqB-ajQ8wpEooHmRGxKBEhwywV+b+RkH+X+P+7Af+eo+eK2r6KMiwN0sg0EY4CgxgXwwBiw9q-c5oP0KYsB++raEgAAcomnpKgHgOwLACZBABAIMNogECwIwTeraBUDaLjP2FYCjEBG6OjlGvrm6F6BpmTn0lTLvnAS2p-rQf4PQYwcwaQK3G2mrmgaUBWq2NrqOu7KKF8BkM7FRkAUYmDuLrIWXN-r-lMkqndPkCOLyOoJyBmLehUARPIHYFIeBE-HYUgVQCgdPm7kjPUoOM5goCkEmFkB4R7jGJkGnHfCpk-CxE2hCCmk3NouQAmkfifooRUjbAih2O0nuPJD2IgG6MYjYCpo6C6AKE-L0FHomrHHOMrJCLgJujDsnjurxN4CxIIM0VuoIG0ewB0aEm9gSiFCePUDdFePKIKB9CYQqPjvKNBDGHFPuPFNYVkm4lqqECzh-vkfgJQVoage9ntleNyMeEWj1LbHEXJA-M6P3IyqIMQqdrsXWgccujgmcRIFLlDEEHcFHvynCEZHCAQJWD-slP4NyocQjkUbkJKEInUOnKRHUB5rmAODuF1Ipt1N1CXF8dkj8UcYoQCeVlDGhlAHgCwWwfCb8VDtSXgEicKOKiKFJPuDGAsSYbuKeIRqRMblJD6MSfsSEIyf8dCTHAyJAP4KSQjn-lMWbDznwU5lUG9HUDjHehlhYMfMjLGE-OQKSGQIEG5GSL6vuJbNBDaFtMOGpC1OLLXu7KRLsAqI3r0nsS3ixiCfGhMtCMmmmoGEnnuorkJj6WxpxNxP6ZDIGazuJkqVGLYBbPmETE5tgf2CWvfhEhQg0MRDsTIZ6VTFLvynumxgmkmjGemmSDbndgzg9mGdwPXJGcJgGVWXnqEQXqUOsXdKmZIqoI8ntvIBFMNviYWvKODiCQcqvNyj0HypOfkkwd0fLiGYeiMfOSvIIDObytquuR-BdB2ToUma0jsNSlfqoGuLKvIM6LUDsPkEKETO6S4oWWXMWbud4mhr4sUvoEuRFiuTumuQKqhobAuiUhJsYJbGAssGYtLFYI7HyfyAtPbMYIvhOQKqxhzF+T+bDv+dxG+bgMJmxkUv4g5nGH+FgbRAeCGnJNChULkMfPsAqNoD0k+XWq+WhQBl+YEjgCElhb0RIABZyngEBehueiUsztxZMdoZcbYMOlJPMGYJAlJC+nzPVG1OPNQsKMOKhYJQUoRY3CUlxcEl0cGVFvxbhYBbZmJWUpJRcbzEeVKMYC6LgS6MpXRNyPsBEq7GjCKNpVOYEKMkwOWdvLxX+WZYIHhaMQFc2XwGBZIFkPkCbg8UsDnLqfyacDGA4P3P7r5b8kcoFSciFaZQJVOZFfZM2foA5gsIyjUMKbJnYDnF6ARrkLbBnPYJyDlcwmAACkCo7uwoVYrsVfkjqt1ResrhJsOg0ksETP5KkJYr2DtKeEoMidAUmG6B1UolinZNWFgP1aueZTpYIJtTiucQedJXuHjEyrRJyPYDuB5sdieP2PkNoHgZsTBKKf0kQMfqcf4CxAfpgIqVJXZYKGkCmNoOXiHipparUJbMpHFEYgcI+bWtkp9Sfr9YfsEYDcaPYPfv5F9nUASZqU8dDamWBPDeIE-Cjd9Wjf9VQNYJjVGDYOBf2KKPbGOETHdUGjDTkHDX1hYMVh6XWpTTAD9X9X-jIPTe+PIFJJbETF0qcAgqoFDQOCTTzSaojaVh9V9cLdTX-ioBLZVOYHFDyFkL+PUlYOYLtpkMTQUJidoEFNIU3hLlTEQNgHCEaWcSccLYUQme+OaESusikNLMsMBHfuUL5muA-FeJyPmY7TYSrC7W7VDuSSQJSV6syV0bxKwbcK7e7f4OnRUuBNBM6CSnAkFMYfaKYfyWuIKRyR2LPP4LgImrlGSL4KSNZBgJZK5JwYZG3LZcaIILbE6KKPyMKDavUKPSIaRY5dkA6vkD+JpGQNgF0MMFDBMhZkZLtTuovcvSMM2c3RUqRnwbUIUKIFfHNUYDuIsAQssEdmbgvT-jvavYmuxAZkCWrJvRINvcMNqlxKMWrAfcoILFJJeIWDdPKOKOIOGnfB2NkI4O1b8I3RgPAFEB6X3SuLNcSreQoOoBYJsHJFeJbMYFtG6CjNSg7QLVOGg95D9Jg3MY9LgwQQKNcpnATIKAKG9QWVpDpBgBlEZMg4nJ2auC6LQy5jg+oJajkIsEtUTE9AoBbl8YlMlGlLw3AFQ5VIIDsE6AoB9P3NaPuHg72A+VI8jPiXI+QyxcrGo13FIU4epm5m4QbjaKYEQ-yLkAqOBDGu9d7PImqFY0jGuDVMRibbtLBfg2GkpMil1N0iFODhxYuhPiBlen47offq5gQqKHbKcDnJ9KYNUbyIcKcPRpw0xq3rZt3iukk6dXZY0KqZnEmRRIeCPNdVfRPEsMLLRODpVqFsk6su5TkHUPMGSnFL1uBBUNieFNFKmJ0xdldlhj0+IAOE9KeXmi9PaPYAoK2INl+EHcHeDkcZUwI4eQSZbNkNyZkOPTkOQgUOKt0tLHgbIMyrHc+SrMWVbgc+cp2asuULcijOjFGr+H7nNKmVoAjTNWHsPlHp3rHuU4k3M1U8aIPeljUAgldbemRCPNsfGFkPXrEoRhQR-j09tMw4yrbDuAqF8PUHnP2BRCjCTK-l42XPIf8YET0ymJIG6SS8puSxXZA2eP05lZkPzRY24ky-AY3YhlzD067M6JpTGHI6IOiyUJXd2EkYWg0eY0jSK+-uScoaoYKlK4A6c1yJ6P5CKCYbdKJLLbKLmHUgEYgck61OUDaVJK6OsqoJbcKJRJtDbTtNeGkRkZwFHDkQmsk7LCeDsKRHAubTbUBHYPGH2HeYpjeAyyrMMa0UMO0WrJ0Q62w86AoNEkmCmI4BS-sF4W0-sAVusk-PKX8QS-CwzcoJKMoPME5YIZpRS5ae2J2LmG+jEymxIDW0nX9RSbOkCSENwGwXcOCfCKGwULmvm6267NoAQY4IOI1Ta91GpEK5q-0oO-8YCWnZDDSdhPiljdzc6KAXUpyTyRXXyTsNXfVLXSKcU2KQibW+SVKclLKXu7O6scDcYHKzLP9vaB1DyCdsImXoqP2+7SabAGaQ69JM6IsgxV2Iq0jNizyNVXciTp4y+16aDOGXOOWdGSmlWfMwQ7YIdmOOw-jJmXGGBCkL4S48xTu0Wa3nhXCTynOQKkwfM046cNATkDIxmXJKpIsAUEpXYPmLIv22xTpaHB+SBSUIc9JQ6qeOw-VLsMbqE72NjfjlPERJpX23h2x96WhZZcpx80c-xxp0J9p3dSRFaWYBtkqnFOte+cBaJaUhJXxyDew1BWkjzuRJkMXi1dUXmKkO57pRhf4oZSEr5+pzfUoIFwORaAHrbH+BRlUCxxraZwR2hXlUFeR+lqKMAc+nYLUClSFLk5KokYMxYFF-5ccomt-MV4sKV99hYBVwbruLMUIUoB1CKLII111YCk3MrglwJzfZmJ6JUXzBkGvjuA1NLMIvUI10ddtZgPM2yaLssFeKaymFShbERq49RmYDl83s7VrSdNTT03DdIKIImHcw4KmEOgOCFCknAh1DGBTTnUO62sk37QitBLYLyLyGOiW+l7LU+gVssL94nf8SnaO4e8EngID54YM6OLXqfUBAcKeLbMKDmLEZF-2wnbnR+3gNKbKWT6SOj+aGWglWD0sPyOa4QgeP3ApUtaoPXY3f4M3ckwPUw4Ri6Dff2Q4K9OuPsKPTeacHcjHQLV-ZwF6mvT3Q61YEh05itKRkxUBPVEh-jYW3Kxq8gor0-cDHwK-V6rAGrMk+BfSgviQoDufXtqu8jBjlRhGi4C4EAA */ id: 'Modeling', tsTypes: {} as import('./modelingMachine.typegen').Typegen0, @@ -1746,7 +1746,10 @@ export function isEditingExistingSketch({ (item) => item.type === 'CallExpression' && item.callee.name === 'startProfileAt' ) - return hasStartProfileAt && pipeExpression.body.length > 2 + const hasCircle = pipeExpression.body.some( + (item) => item.type === 'CallExpression' && item.callee.name === 'circle' + ) + return (hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle } export function canRectangleOrCircleTool({ From c56c446e15b7c0e0645fda381fb6d0b5ee343cdd Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Sat, 24 Aug 2024 21:59:35 +1000 Subject: [PATCH 04/41] add circle segment to searches --- src/clientSideScene/sceneEntities.ts | 16 ++++++++++------ src/components/ModelingMachineProvider.tsx | 3 +++ src/lib/selections.ts | 11 ++++++++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 17ee8f4f68..7f31df4f45 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -918,9 +918,9 @@ export class SceneEntities { // Prepare to update the THREEjs scene this.sceneProgramMemory = programMemory const sketchGroup = sketchGroupFromKclValue( - programMemory.get( + programMemory.get(variableDeclarationName), variableDeclarationName - ), variableDeclarationName) + ) if (err(sketchGroup)) return Promise.reject(sketchGroup) const sgPaths = sketchGroup.value const orthoFactor = orthoScale(sceneInfra.camControls.camera) @@ -1322,7 +1322,6 @@ export class SceneEntities { (group.userData.center[0] - dragTo[0]) ** 2 ) ) - console.log('modded', modded) } else if ( group.name === CIRCLE_SEGMENT && subGroup?.name === CIRCLE_CENTER_HANDLE @@ -1335,7 +1334,6 @@ export class SceneEntities { dragTo, group.userData.radius ) - console.log('modded', modded) } else { modded = changeSketchArguments( modifiedAst, @@ -1630,7 +1628,7 @@ export class SceneEntities { const hoveredParent = sceneInfra.hoveredObject && - getParentGroup(sceneInfra.hoveredObject, [TANGENTIAL_ARC_TO_SEGMENT]) + getParentGroup(sceneInfra.hoveredObject, [CIRCLE_SEGMENT]) let isHandlesVisible = !shouldHideIdle if (hoveredParent && hoveredParent?.uuid === group?.uuid) { isHandlesVisible = !shouldHideHover @@ -1899,6 +1897,7 @@ export class SceneEntities { const parent = getParentGroup(selected, [ STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, + CIRCLE_SEGMENT, PROFILE_START, ]) if (parent?.userData?.pathToNode) { @@ -1964,6 +1963,7 @@ export class SceneEntities { const parent = getParentGroup(selected, [ STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, + CIRCLE_SEGMENT, PROFILE_START, ]) if (parent) { @@ -2161,7 +2161,11 @@ function prepareTruncatedMemoryAndAst( export function getParentGroup( object: any, - stopAt: string[] = [STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT] + stopAt: string[] = [ + STRAIGHT_SEGMENT, + TANGENTIAL_ARC_TO_SEGMENT, + CIRCLE_SEGMENT, + ] ): Group | null { if (stopAt.includes(object?.userData?.type)) { return object diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 801a3cef7f..c1a4e532fd 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -50,6 +50,7 @@ import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance' import useStateMachineCommands from 'hooks/useStateMachineCommands' import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig' import { + CIRCLE_SEGMENT, STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, getParentGroup, @@ -179,6 +180,7 @@ export const ModelingMachineProvider = ({ const parent = getParentGroup(event.data.on, [ STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, + CIRCLE_SEGMENT, ]) const pathToNode = parent?.userData?.pathToNode const pathToNodeString = JSON.stringify(pathToNode) @@ -196,6 +198,7 @@ export const ModelingMachineProvider = ({ const mouseOnParent = getParentGroup(mouseState.on, [ STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, + CIRCLE_SEGMENT, ]) if (!mouseOnParent || !mouseOnParent?.userData?.pathToNode) return segmentHoverMap diff --git a/src/lib/selections.ts b/src/lib/selections.ts index e1faf47349..0d96e74e2c 100644 --- a/src/lib/selections.ts +++ b/src/lib/selections.ts @@ -24,6 +24,7 @@ import { TANGENTIAL_ARC_TO_SEGMENT, getParentGroup, PROFILE_START, + CIRCLE_SEGMENT, } from 'clientSideScene/sceneEntities' import { Mesh, Object3D, Object3DEventMap } from 'three' import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra' @@ -150,6 +151,7 @@ export function getEventForSegmentSelection( const group = getParentGroup(obj, [ STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, + CIRCLE_SEGMENT, PROFILE_START, ]) const axisGroup = getParentGroup(obj, [AXIS_GROUP]) @@ -290,9 +292,12 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) { Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => { if ( - ![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, PROFILE_START].includes( - segmentGroup?.name - ) + ![ + STRAIGHT_SEGMENT, + TANGENTIAL_ARC_TO_SEGMENT, + PROFILE_START, + CIRCLE_SEGMENT, + ].includes(segmentGroup?.name) ) return const nodeMeta = getNodeFromPath( From 22c854815a346d53adff5b388bf6df8c859c245a Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Wed, 28 Aug 2024 19:07:26 +1000 Subject: [PATCH 05/41] can use circle tool tip --- src/clientSideScene/sceneEntities.ts | 63 ++++++++++++++++++---------- src/lang/std/sketch.ts | 7 ++-- src/lib/toolbar.ts | 6 ++- src/machines/modelingMachine.ts | 46 ++++++++++++++++---- 4 files changed, 88 insertions(+), 34 deletions(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 7f31df4f45..37a96a4450 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -947,7 +947,7 @@ export class SceneEntities { forward: [number, number, number], up: [number, number, number], sketchOrigin: [number, number, number], - rectangleOrigin: [x: number, y: number] + circleCenter: [x: number, y: number] ) => { let _ast = structuredClone(kclManager.ast) @@ -962,33 +962,36 @@ export class SceneEntities { const startSketchOn = _node1.node?.declarations const startSketchOnInit = startSketchOn?.[0]?.init - const tags: [string, string, string] = [ - findUniqueName(_ast, 'rectangleSegmentA'), - findUniqueName(_ast, 'rectangleSegmentB'), - findUniqueName(_ast, 'rectangleSegmentC'), - ] - startSketchOn[0].init = createPipeExpression([ startSketchOnInit, - ...getRectangleCallExpressions(rectangleOrigin, tags), + createCallExpressionStdLib('circle', [ + createArrayExpression([ + createLiteral(roundOff(circleCenter[0])), + createLiteral(roundOff(circleCenter[1])), + ]), + createLiteral(1), + createPipeSubstitution(), + ]), ]) let _recastAst = parse(recast(_ast)) if (trap(_recastAst)) return Promise.reject(_recastAst) _ast = _recastAst + // do a quick mock execution to get the program memory up-to-date + await kclManager.executeAstMock(_ast) + const { programMemoryOverride, truncatedAst } = await this.setupSketch({ sketchPathToNode, forward, up, position: sketchOrigin, maybeModdedAst: _ast, - draftExpressionsIndices: { start: 0, end: 3 }, + draftExpressionsIndices: { start: 0, end: 0 }, }) sceneInfra.setCallbacks({ onMove: async (args) => { - // Update the width and height of the draft rectangle const pathToNodeTwo = structuredClone(sketchPathToNode) pathToNodeTwo[1][0] = 0 @@ -997,18 +1000,27 @@ export class SceneEntities { pathToNodeTwo || [], 'VariableDeclaration' ) + let modded = structuredClone(truncatedAst) if (trap(_node)) return Promise.reject(_node) const sketchInit = _node.node?.declarations?.[0]?.init - const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0] - const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] + const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0] + const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1] if (sketchInit.type === 'PipeExpression') { - updateRectangleSketch(sketchInit, x, y, tags[0]) + const moddedResult = changeCircleArguments( + modded, + kclManager.programMemory, + [..._node.deepPath, ['body', 'PipeExpression'], [1, 'index']], + circleCenter, + Math.sqrt(x ** 2 + y ** 2) + ) + if (err(moddedResult)) return Promise.reject(moddedResult) + modded = moddedResult.modifiedAst } const { programMemory } = await executeAst({ - ast: truncatedAst, + ast: modded, useFakeExecutor: true, engineCommandManager: this.engineCommandManager, programMemoryOverride, @@ -1039,8 +1051,8 @@ export class SceneEntities { const cornerPoint = args.intersectionPoint?.twoD if (!cornerPoint || args.mouseEvent.button !== 0) return - const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0]) - const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1]) + const x = roundOff((cornerPoint.x || 0) - circleCenter[0]) + const y = roundOff((cornerPoint.y || 0) - circleCenter[1]) const _node = getNodeFromPath( _ast, @@ -1050,16 +1062,25 @@ export class SceneEntities { if (trap(_node)) return Promise.reject(_node) const sketchInit = _node.node?.declarations?.[0]?.init + let modded = structuredClone(_ast) if (sketchInit.type === 'PipeExpression') { - updateRectangleSketch(sketchInit, x, y, tags[0]) + const moddedResult = changeCircleArguments( + modded, + kclManager.programMemory, + [..._node.deepPath, ['body', 'PipeExpression'], [1, 'index']], + circleCenter, + Math.sqrt(x ** 2 + y ** 2) + ) + if (err(moddedResult)) return Promise.reject(moddedResult) + modded = moddedResult.modifiedAst - let _recastAst = parse(recast(_ast)) + let _recastAst = parse(recast(modded)) if (trap(_recastAst)) return Promise.reject(_recastAst) _ast = _recastAst // Update the primary AST and unequip the rectangle tool await kclManager.executeAstMock(_ast) - sceneInfra.modelingSend({ type: 'Finish rectangle' }) + sceneInfra.modelingSend({ type: 'Finish circle' }) const { programMemory } = await executeAst({ ast: _ast, @@ -1315,7 +1336,7 @@ export class SceneEntities { modded = changeCircleArguments( modifiedAst, kclManager.programMemory, - [node.start, node.end], + getNodePathFromSourceRange(modifiedAst, [node.start, node.end]), group.userData.center, Math.sqrt( (group.userData.center[0] - dragTo[0]) ** 2 + @@ -1330,7 +1351,7 @@ export class SceneEntities { modded = changeCircleArguments( modifiedAst, kclManager.programMemory, - [node.start, node.end], + getNodePathFromSourceRange(modifiedAst, [node.start, node.end]), dragTo, group.userData.radius ) diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 44d334cdca..093fb8d222 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -1614,13 +1614,12 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = { export function changeCircleArguments( node: Program, programMemory: ProgramMemory, - sourceRange: SourceRange, + pathToNode: PathToNode, center: [number, number], radius: number ): { modifiedAst: Program; pathToNode: PathToNode } | Error { const _node = { ...node } - const thePath = getNodePathFromSourceRange(_node, sourceRange) - const nodeMeta = getNodeFromPath(_node, thePath) + const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta const { node: callExpression, shallowPath } = nodeMeta @@ -1639,7 +1638,7 @@ export function changeCircleArguments( } } - return new Error(`not a sketch line helper: ${callExpression?.callee?.name}`) + return new Error(`There was a problem: ${callExpression?.callee?.name}`) } export function changeSketchArguments( diff --git a/src/lib/toolbar.ts b/src/lib/toolbar.ts index 597f9fee22..cf178e0617 100644 --- a/src/lib/toolbar.ts +++ b/src/lib/toolbar.ts @@ -359,7 +359,7 @@ export const toolbarConfig: Record = { modelingSend({ type: 'change tool', data: { - tool: !modelingStateMatches('Sketch.Rectangle tool') + tool: !modelingStateMatches('Sketch.Circle tool') ? 'circle' : 'none', }, @@ -367,6 +367,10 @@ export const toolbarConfig: Record = { icon: 'circle', status: 'available', title: 'Center circle', + disabled: (state) => + !canRectangleOrCircleTool(state.context) && + !state.matches('Sketch.Circle tool'), + isActive: (state) => state.matches('Sketch.Circle tool'), showTitle: false, description: 'Start drawing a circle from its center', links: [ diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 7e3678dd41..34b0636da8 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -58,6 +58,8 @@ import { Coords2d } from 'lang/std/sketch' import { deleteSegment } from 'clientSideScene/ClientSideSceneComp' import { executeAst } from 'lang/langHelpers' import toast from 'react-hot-toast' +import { quaternionFromUpNForward } from 'clientSideScene/helpers' +import { Vector3 } from 'three' export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY' @@ -249,6 +251,7 @@ export type ModelingMachineEvent = } } | { type: 'Finish rectangle' } + | { type: 'Finish circle' } | { type: 'Artifact graph populated' } | { type: 'Artifact graph emptied' } @@ -299,7 +302,7 @@ export const modelingMachineDefaultContext = { export const modelingMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANhoBWAHQAOAMwB2KQEY5AFgCcGqWqkAaEAE9Ew0RLEqa64TIBMKmTUXCAvk71oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBEEpYSkJOUUaOWsxeylrWzk9QwQClQkrVOsZNWExFItnVxB3LDxCXwCAW1QAVyDA9hJ2MAjeGI4ueNBE5KkaCWspZfMM+XE1YsQZMQWxNQtxao0ZeRc3dDavTv9ybhG+djGoibjeRJothBpzlsvPDp+fy+KBdMC4AIAeQAbmAAE6YEj6WDPZisSbcd6IT4GbG-VoAiTYCCYMAEACiEPhgQA1n5yAALVHRdFvBJCazCOQSRSctQyKS5RRqFTWL6nNTSfnSsS5FRiZT4-7tIkksmUkZw2n0pmKSJo2JTLFJWwLFSOBy7WQ0eZFXEIczWCRqaxyOq81KKAWOJUeFXE0kUx5w3oYZmvI3spJiOQyZ1mZQyeUyRSC4RfXmndKOKRyWUpmqKX1XKCqwMAMWwmFJT3o41ZkZmQnzEjsgoVXPE2TtJS9KmEPNjuZsKbkDmLhID6r4LDhtf1LMNmKjgjEdXSrtENuEihUcjUigz-cUEhqeaUwmsqavE-9aoIyBIdPDDeXTYdTtSIusalj5qy+xfFyaSxvUKh7rKdgqFIt74GWZIACLBCMgTBKEUwvku0z8EIO7lNoCZjqm5qKGIGYxuUNSXj+iiOLR+6waWU4EAAKmAjyCOwqCCEQACCCGYRi2GzAqTpmCkspqLKf7pvayj9hU8gFPs+TCOaciMfBEgMsSYAAAqIrgcAELxc7YAAZiQoT+FAcIkEwDL+CwTC9IiIwQIJbLvoI-aSuIia-loQUyLJJS-nGlrQbmXq0VIKiaVOEiwAyqAAO4GWQxmmZwlnWbZ9mOWAXRMJwkCeY2OFJHu3IKDGYicsB4F5hmvJOqmB5ejQNBWAe1iad4dLsIyxBkJQmADTq5VvpVXq7KY6i-sBbpujILUiqe6jZJy2QHvk-WDcNSE1mAqGguC871lhxpyHmzo-vVcW-jashfPK3IaOY+a7Gp3X7TqBB3Bg-iQBw-gQL0cLtNqQ1MnWLyvsJiCkTQkqCvKNEFCkKRfCKcZJsKy00FeSZNBcfpwRNMMSJTjIAJL3o+dKnWCEL+LCZnkCQmBTYjCC7rKFQ5PIPUxUenKmPY-LCAcAp2BpzQEiqNMMtTB0MvTgaMydQRnazKWQwAXvcXM88au41BI-legK1hdbsKgZjY5Q3cYnVE7Rsp-VTysa2SRDcLA7B2Xg-j69gRsQlzYPYIHo2jHDBpCWb5ppNB0ECmYBRE18pqSksamxfYyiul7jKqzqvvEAHQckCH7OcJzmDR7HFDxwuEbTYk5t41JAo0Emshcqt9qYwOea7LUu6yELpcqz797+7ggfB7g-i8QAQt4-gABqm1Gu7qNI8wWCFGd5g7I+1GkqZugeZ79zIs-lzDleL8vterxvW8AJp7++B+SlSL5VqP4agXxKCmBw8Y1KXl-Nofu8syYlmfmXeegY341xDmQKApI-4zV2NyVSOxsho0vDneqZotDQV6rVKST80F+2rivfwpJ8DsFhu3BGZtdgnnmEoeQsgpLaDFCPKwTour91AaRLQxh6Fq1fkwj+Tl4RMHBBAbA5BXIkC1OolulA8FdxqE6EUxFaIo3NroEe+R3pWhRijWUWRH4K2VBTNWKD1YL0USHMOEchjVn0P4Lm2AoC4AMUjUU3Ii4OE+pyKh5EfymG2vsFGKRqikz+OTUsyt3EKKXpg1e9cNFc0wAEoJISwl83AnGXMZgbQFBCgoYevYrBpHyFkTI4hzB7nSYrVxOocmeLycw2AuB7L+C4jvCpu4brpG6qcN07YCgiJKLbAW8o6g3UWvyaociK6DPfiHEZYyJm-wTouJO+9qqmFTHFNSVTczLMQFeEwmyrAkMFLYXZL99n5OBgAR16FHVhUB2FTLevGWMNpzRC1SBmVQcZXZXl3JuUBXy6Y-OYUwbRxTqBnI7rzXcB5WzS13NLEKspcgZlqAsOKKNXQ5APOoJxSDCTZIYVXIZSi4RFVQLCW4XiIQojxVwy5XpLZmG0AtA+u4MxxXKIUWQqYLCkrEGijxgYABKYBBDsRCL0EYUzYwDh-O06iORaIZk6ekVIuRjCXi6oKNVldAYnRBgEcGkN8DQ0ZFMied01ypBRkA+olqsinh3FtYwct+5OsGfXcZqA2baOwCQAARrg4VV194Km5LROWGRUjyAyOKLOFR1IOHsPY1VzjMnuIGYGRk2CTpcVQNzTNFz3ycltjyHYakliJlzC1Nc4bMhaBqPMl0T9eKpVrgEHxxsm66KGK3fweBzKoAIBAbgYAiS4GhKgOkEgYDsEEPOyOmBBBrtQBU2wqQj5jrldkUQ4Cnl2AirIP8tSUx1CnTO0GZ6-HN2XZQVduB10EHhHCVAcIJBMDcuuuEXQj1+FPdB8OC7L1geve2rylVb2SllNLcwRwYxNKeTUSQrpSL0WqPkIsNbkHZOnbOtm8IG5RyXXHUD4Gt1GV3fuw9x7BCFMbph9dN7ummHmBKLITVQpPJ-Lm7qjgdgWGqH1BjrK3HMdBiJjjMdgMnSvRBuEUGYNweGAhpDQm9MXqvRJmZth8i5mWCmaoZHSg5EkFRBU8h9gD1-Sxr+O9uMbt4zuvAAmd1CbTbAQQfAxPYc4Vmzt0ETy22TEG7cHnFPebWK6aTqREEZMY9pv9ARgvb1CyZszsH4PQesyh2L8XEsSaUNIN00LFOKpzi6AjWg1xNXqCjQLoNgvf2q+F-jB7otNdTXF-QrWcMVUSE5hY+5nP9hChs+TCA8jrZ-CkYUCZdyjYq5vfwE3jOQeg3VyzDXkMnua4t+zy3O6vva3uBwdRwqXj3OKZQkS7ZGrKDYM7gT8Ckkm9u6bgmmuQ+1a95LHa8NpfSHYYwVFshJnIWOHkeFeT1Wlq6atLKlZlZY9gqH13TO3Ys+wKzj3BBU8R1htrcZSV1CqEoZqI9cgLCTOBC8V56g-s0+T-pOmAjAvYdDvjkWZtM5lwyJbyPcOrb3KjVQLolhKGgrtvkaQfyiiWLGV02RwfK+qzd8z9XENK-BCClXSPLoo41-Meauuit1TMDnOokgx3EdubIMw4PVFwlUbgdRmjEQ6IM1x4zU2Fdw5PeHyP0etFwkEJx1uqvXfq9fa6VsDrcg2A0BsnOsg0iiGqJssc9RScla05L8ryiI9qI0ZnoDCesM1bp3bxrqeVEd5j9o7P8fc8u-hil1HReg+l5-LUGMOd9wLBr0Wp6Den4ABk8DNtQK2gGDIm0JtbTe7H0h6oROhX+HGNhpAe1qOPJYuQd979P5gCQtNcAcAIBJqB-ajQ8wpEooHmRGxKBEhwywV+b+RkH+X+P+7Af+eo+eK2r6KMiwN0sg0EY4CgxgXwwBiw9q-c5oP0KYsB++raEgAAcomnpKgHgOwLACZBABAIMNogECwIwTeraBUDaLjP2FYCjEBG6OjlGvrm6F6BpmTn0lTLvnAS2p-rQf4PQYwcwaQK3G2mrmgaUBWq2NrqOu7KKF8BkM7FRkAUYmDuLrIWXN-r-lMkqndPkCOLyOoJyBmLehUARPIHYFIeBE-HYUgVQCgdPm7kjPUoOM5goCkEmFkB4R7jGJkGnHfCpk-CxE2hCCmk3NouQAmkfifooRUjbAih2O0nuPJD2IgG6MYjYCpo6C6AKE-L0FHomrHHOMrJCLgJujDsnjurxN4CxIIM0VuoIG0ewB0aEm9gSiFCePUDdFePKIKB9CYQqPjvKNBDGHFPuPFNYVkm4lqqECzh-vkfgJQVoage9ntleNyMeEWj1LbHEXJA-M6P3IyqIMQqdrsXWgccujgmcRIFLlDEEHcFHvynCEZHCAQJWD-slP4NyocQjkUbkJKEInUOnKRHUB5rmAODuF1Ipt1N1CXF8dkj8UcYoQCeVlDGhlAHgCwWwfCb8VDtSXgEicKOKiKFJPuDGAsSYbuKeIRqRMblJD6MSfsSEIyf8dCTHAyJAP4KSQjn-lMWbDznwU5lUG9HUDjHehlhYMfMjLGE-OQKSGQIEG5GSL6vuJbNBDaFtMOGpC1OLLXu7KRLsAqI3r0nsS3ixiCfGhMtCMmmmoGEnnuorkJj6WxpxNxP6ZDIGazuJkqVGLYBbPmETE5tgf2CWvfhEhQg0MRDsTIZ6VTFLvynumxgmkmjGemmSDbndgzg9mGdwPXJGcJgGVWXnqEQXqUOsXdKmZIqoI8ntvIBFMNviYWvKODiCQcqvNyj0HypOfkkwd0fLiGYeiMfOSvIIDObytquuR-BdB2ToUma0jsNSlfqoGuLKvIM6LUDsPkEKETO6S4oWWXMWbud4mhr4sUvoEuRFiuTumuQKqhobAuiUhJsYJbGAssGYtLFYI7HyfyAtPbMYIvhOQKqxhzF+T+bDv+dxG+bgMJmxkUv4g5nGH+FgbRAeCGnJNChULkMfPsAqNoD0k+XWq+WhQBl+YEjgCElhb0RIABZyngEBehueiUsztxZMdoZcbYMOlJPMGYJAlJC+nzPVG1OPNQsKMOKhYJQUoRY3CUlxcEl0cGVFvxbhYBbZmJWUpJRcbzEeVKMYC6LgS6MpXRNyPsBEq7GjCKNpVOYEKMkwOWdvLxX+WZYIHhaMQFc2XwGBZIFkPkCbg8UsDnLqfyacDGA4P3P7r5b8kcoFSciFaZQJVOZFfZM2foA5gsIyjUMKbJnYDnF6ARrkLbBnPYJyDlcwmAACkCo7uwoVYrsVfkjqt1ResrhJsOg0ksETP5KkJYr2DtKeEoMidAUmG6B1UolinZNWFgP1aueZTpYIJtTiucQedJXuHjEyrRJyPYDuB5sdieP2PkNoHgZsTBKKf0kQMfqcf4CxAfpgIqVJXZYKGkCmNoOXiHipparUJbMpHFEYgcI+bWtkp9Sfr9YfsEYDcaPYPfv5F9nUASZqU8dDamWBPDeIE-Cjd9Wjf9VQNYJjVGDYOBf2KKPbGOETHdUGjDTkHDX1hYMVh6XWpTTAD9X9X-jIPTe+PIFJJbETF0qcAgqoFDQOCTTzSaojaVh9V9cLdTX-ioBLZVOYHFDyFkL+PUlYOYLtpkMTQUJidoEFNIU3hLlTEQNgHCEaWcSccLYUQme+OaESusikNLMsMBHfuUL5muA-FeJyPmY7TYSrC7W7VDuSSQJSV6syV0bxKwbcK7e7f4OnRUuBNBM6CSnAkFMYfaKYfyWuIKRyR2LPP4LgImrlGSL4KSNZBgJZK5JwYZG3LZcaIILbE6KKPyMKDavUKPSIaRY5dkA6vkD+JpGQNgF0MMFDBMhZkZLtTuovcvSMM2c3RUqRnwbUIUKIFfHNUYDuIsAQssEdmbgvT-jvavYmuxAZkCWrJvRINvcMNqlxKMWrAfcoILFJJeIWDdPKOKOIOGnfB2NkI4O1b8I3RgPAFEB6X3SuLNcSreQoOoBYJsHJFeJbMYFtG6CjNSg7QLVOGg95D9Jg3MY9LgwQQKNcpnATIKAKG9QWVpDpBgBlEZMg4nJ2auC6LQy5jg+oJajkIsEtUTE9AoBbl8YlMlGlLw3AFQ5VIIDsE6AoB9P3NaPuHg72A+VI8jPiXI+QyxcrGo13FIU4epm5m4QbjaKYEQ-yLkAqOBDGu9d7PImqFY0jGuDVMRibbtLBfg2GkpMil1N0iFODhxYuhPiBlen47offq5gQqKHbKcDnJ9KYNUbyIcKcPRpw0xq3rZt3iukk6dXZY0KqZnEmRRIeCPNdVfRPEsMLLRODpVqFsk6su5TkHUPMGSnFL1uBBUNieFNFKmJ0xdldlhj0+IAOE9KeXmi9PaPYAoK2INl+EHcHeDkcZUwI4eQSZbNkNyZkOPTkOQgUOKt0tLHgbIMyrHc+SrMWVbgc+cp2asuULcijOjFGr+H7nNKmVoAjTNWHsPlHp3rHuU4k3M1U8aIPeljUAgldbemRCPNsfGFkPXrEoRhQR-j09tMw4yrbDuAqF8PUHnP2BRCjCTK-l42XPIf8YET0ymJIG6SS8puSxXZA2eP05lZkPzRY24ky-AY3YhlzD067M6JpTGHI6IOiyUJXd2EkYWg0eY0jSK+-uScoaoYKlK4A6c1yJ6P5CKCYbdKJLLbKLmHUgEYgck61OUDaVJK6OsqoJbcKJRJtDbTtNeGkRkZwFHDkQmsk7LCeDsKRHAubTbUBHYPGH2HeYpjeAyyrMMa0UMO0WrJ0Q62w86AoNEkmCmI4BS-sF4W0-sAVusk-PKX8QS-CwzcoJKMoPME5YIZpRS5ae2J2LmG+jEymxIDW0nX9RSbOkCSENwGwXcOCfCKGwULmvm6267NoAQY4IOI1Ta91GpEK5q-0oO-8YCWnZDDSdhPiljdzc6KAXUpyTyRXXyTsNXfVLXSKcU2KQibW+SVKclLKXu7O6scDcYHKzLP9vaB1DyCdsImXoqP2+7SabAGaQ69JM6IsgxV2Iq0jNizyNVXciTp4y+16aDOGXOOWdGSmlWfMwQ7YIdmOOw-jJmXGGBCkL4S48xTu0Wa3nhXCTynOQKkwfM046cNATkDIxmXJKpIsAUEpXYPmLIv22xTpaHB+SBSUIc9JQ6qeOw-VLsMbqE72NjfjlPERJpX23h2x96WhZZcpx80c-xxp0J9p3dSRFaWYBtkqnFOte+cBaJaUhJXxyDew1BWkjzuRJkMXi1dUXmKkO57pRhf4oZSEr5+pzfUoIFwORaAHrbH+BRlUCxxraZwR2hXlUFeR+lqKMAc+nYLUClSFLk5KokYMxYFF-5ccomt-MV4sKV99hYBVwbruLMUIUoB1CKLII111YCk3MrglwJzfZmJ6JUXzBkGvjuA1NLMIvUI10ddtZgPM2yaLssFeKaymFShbERq49RmYDl83s7VrSdNTT03DdIKIImHcw4KmEOgOCFCknAh1DGBTTnUO62sk37QitBLYLyLyGOiW+l7LU+gVssL94nf8SnaO4e8EngID54YM6OLXqfUBAcKeLbMKDmLEZF-2wnbnR+3gNKbKWT6SOj+aGWglWD0sPyOa4QgeP3ApUtaoPXY3f4M3ckwPUw4Ri6Dff2Q4K9OuPsKPTeacHcjHQLV-ZwF6mvT3Q61YEh05itKRkxUBPVEh-jYW3Kxq8gor0-cDHwK-V6rAGrMk+BfSgviQoDufXtqu8jBjlRhGi4C4EAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0ANhoBWAHQAOAMwB2KQEY5AFgCcGqWqkAaEAE9Ew0RLEqa64TIBMKmTUXCAvk71oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBEEpYSkJOUUaOWsxeylrWzk9QwQClQkrVOsZNWExFItnVxB3LDxCXwCAW1QAVyDA9hJ2MAjeGI4ueNBE5KkaCWspZfMM+XE1YsQZMQWxNQtxao0ZeRc3dDavTv9ybhG+djGoibjeRJothBpzlsvPDp+fy+KBdMC4AIAeQAbmAAE6YEj6WDPZisSbcd6IT4GbG-VoAiTYCCYMAEACiEPhgQA1n5yAALVHRdFvBJCazCOQSRSctQyKS5RRqFTWL6nNTSfnSsS5FRiZT4-7tIkksmUkZw2n0pmKSJo2JTLFJWwLFSOBy7WQ0eZFXEIczWCRqaxyOq81KKAWOJUeFXE0kUx5w3oYZmvI3spJiOQyZ1mZQyeUyRSC4RfXmndKOKRyWUpmqKX1XKCqwMAMWwmFJT3o41ZkZmQnzEjsgoVXPE2TtJS9KmEPNjuZsKbkDmLhID6r4LDhtf1LMNmKjgjEdXSrtENuEihUcjUigz-cUEhqeaUwmsqavE-9aoIyBIdPDDeXTYdTtSIusalj5qy+xfFyaSxvUKh7rKdgqFIt74GWZIACLBCMgTBKEUwvku0z8EIO7lNoCZjqm5qKGIGYxuUNSXj+iiOLR+6waWU4EAAKmAjyCOwqCCEQACCCGYRi2GzAqTpmCkspqLKf7pvayj9hU8gFPs+TCOaciMfBEgMsSYAAAqIrgcAELxc7YAAZiQoT+FAcIkEwDL+CwTC9IiIwQIJbLvoI-aSuIia-loQUyLJJS-nGlrQbmXq0VIKiaVOEiwAyqAAO4GWQxmmZwlnWbZ9mOWAXRMJwkCeY2OFJHu3IKDGYicsB4F5hmvJOqmB5ejQNBWAe1iad4dLsIyxBkJQmADTq5VvpVrXci6Np2AqmTGLocm2CB3petYNCurV-WDcNSE1mAqGguC871lhxpyHmzo-vVcW-jashfPKc0iuIuS7Gp3X7TqBB3Bg-iQBw-gQL0cLtNqQ1MnWLyvsJiBermiw5DUYGXoB9oaDV9ghdkKYaGIf0wxIE0wwAkvej50qdYIQv4sJmeQJCYFNiMIF6NjOrUtgpruY7iEe8zOsKNiqKcY7xc0BIquTjJkwdDJU4GNMnUEZ0MylkMAF73Kz7PGjFA6ittXVmEm5irb28iSo4uw0DshPniTCvy8r95ENwsDsHZeD+Nr2B6xCrNg9gPujaMcMGkJRv3dI0lHHU+y2F8thWBUqjzNUW6nK7DKKzqKtkl7uA+37uCM-CnAs5gYcRxQUcLhG02JDFCxupee5ZMoMbW4g1TmjyUEpMBqZrvnheU573u+yQ-u8QAQt4-gABqG1GW3cjkNAHo7O61KF2w7WJbo2sKnJaDsk-u8XxCzxX-hLyvACaG-vlvEifdJIpWD+-cIBTGuU84FBQ7F3sYaWFw-RwXdlPRkd9S7l3npXMgUBSTvxmgKSQwpsimmevVI+pQKLSBurmWoMZsg3yVogh+KD-CknwOwWGzcEZG1toOA4Fg1z-1zGnVIA4BTRW0G9GM1Ci4zzLnPf2TB4SyNwBAbA5BXIkC1IohulBMFt1dAsFMOiFA2DlGKe0Sx8inkLBoTG+MNIy2VLApW8CPaBiQdIyugdg5DGrPofwrNsBQFwFopG9hyhclTLaM+ADSKyFFgqSwSxoJQL+DA0scDb6SOQf7JmNdWaYG8b4-xgTOY2BPOnV06hu6pB7EjYQ-JFi70yOIWQBwYK2OSY4xxtCpGP1gLgey-guJr0KV6WUX8brbXAjaXqRCry72kOBWogpur7k5OI6ezi6H+x6X0gZb9o6LljpvHY5Rli-mNjdc5acrw4NSDtBQqYBTgVWQg9JrjgYAEdeih0YVAZhQzTiSBsEoPMwyXSxgzPuNIFhagOCklnbQTynElw2ZXJgqicnUD2S3DmXpUhzK6ji-YIoYxp0tKjfkygFBcN-AizpGTK5wiKqgWEtxkXsBRJithm8fxmhyIKZQu5ZDCgzEFRS1FqhJi6jY6BJZ2lpMDAAJTAIIdiIRegjCGTuOMxgok-nlAcUiLVZmkRqa6YUqZUgtOlYSVJND7yAxOiDAI4NIb4GhoyP5KMfx1CTPkHaIUZAZm6iePMDgYrfjyDSyRWT+moEZqo7AJAABGGCOVXUOSKVsgo+ZaGUFyIh4tJCOy6gea0ShciRsDIyNBJ0uKoDZqmg575bC-lMN1XYUlVg3SIbyTI6Ryn7hdCOKSk9eKpXngEdx+s67qKGI3fweBzKoAIBAbgYAiS4GhKgOkEgYDsEEJOkOmBBALtQIUweA4cjaH8mOd0adoLcj3FeHeREbojrHaDA9nj66zsoPO3Ai6CDwjhKgOEEgmBuUXXCLoO6-D7pA0HKdx7-2nobV5Sq567oKCWXmWopw73qAqISk+uRPRvvHVXZmocZ2Rz-QBldRl12bu3buwQWSlGsyQ4us9Tt4y8i7IFSBacdgnnKZBR25hB1kdBmx2u36aMnsA3CYDoHwPDEg9BljMmOMnu42YCoZqzCiHdCoITsyFDqAsEseagopMBGfmvWjS76NrrwExtdLGk2wEEHwTjKHWFpqbTsNIdUzypFFHKcUO1JQ4YyLIf5e5Emy3sTqCQo7yP2dXo5xTymwMQZAxp2DnnvO+d05KQoroDhPqvCZkx2g4y-n3LKc2xg1C2afsvfwL8svOcY1u9zhXE1ef0CV1DFVEjVHqDyGppEcVdWguKYZPJrz1WgmeRLdiUkOLS6DezXWFNAZA7ltT+WYN7qK8NnTo3W4DyC3MzINgalETIvaEcJ4LW1HNPVH8fVWkyrgdtgIaDSTddXb15jhX8CkhG-5xt6Gdh213LkbqBwu7TK7qYdQ8x7BrgUJapJf2tvvsB5Dk6+2lOHdU+wdTp3BBA6VZdmHaHxshT2L3CbCo1y5DTmBdIgLsgzaam175zCQcMdc31mnwuGTQ8urD5nMZFhvS0KIe9Vg055k-KpC+l4-5C-BD8xyZOcuU+pyxqXMv4YBbhwrg88gvoaF3NMjICwljKFt53KSeOkubZSwDpycjwSKOUYiNR4cf2k+Q8u0H4vwd7tkXCeRQeVFwkENRxuFuY5M5uxYdIGRsjm3+dMrQToNBZFSA7cCso2vx8T0o5Pcm51G4p3lqDNOa+B7ryH1PYfI4Z-2Vn0owTc-KDNrkHYRfwI8zL4siw8pia-etQ4gAMngGtqA60AwZNWmNdaz2iEkAqewhRxPmgDXJfFpg4pdrHmOVrC+5bL9XzvzAEgKa4A4AQM9ro0jultyt-kSwXwWcCc+QEmAoGQSwk8K+Rkz+r+7+7An+eosuA+BQKQFQ96loIoFKXw7YhGjs0o-YnI189+yWpM0Ba+daEgAAcrGnpKgHgGyiZBABAIMKogECwAwXvnFM6BCpkO1DUlJEASmDyOAk7L+HuCFFAU-rWi-jQf4HQQwbACNI3PWozmNkjFoAsHFJQhrvmKnPaG6AjmYFJFePILIFKvjovilm-h-kMo0osKXqXkFi9HJPYCeNuKCrULFNBJPDYQgVQEgZbnLkjA0hjgeIZrvLGLyBmKcPhPsHYJVvRDUpPCxNWhCAmnXKouQDGpvtvjIYUusO4WUEOGOJzjgcoKeNtPmAAeZjIJPL0AorGhHHOO7JCLgFHmLhuhLrxN4CxIIA0SuoIM0ewK0QEldtiiFCePUGMojoKBoDViUMClNvKNBH3HuCKJPIqqEHTs-rkfgBQaocgeoYArvMcjYLYHYJjAeIGssA4dUP5LETYJsSELOuggcalkTlDEEHcAoiynCEZHCAQJWO-slP4AytsSTgUepOYkcKRMYWuF8PyAsJoKKH6inE8SQT7qTFsa8cDjIR8eOlDPBlAHgEwSweCbiSdMSXgAUVRK2HjJjGatEQYbiqoOYI0utLyM8RCW8bAcCeHAyJAP4DiXTp-uMcaDUMIR2LVLFPVHhufnYIsKROtHFIKnYJPOQKSGQIEG5GSEMvKCbO1LkCYbyFUpzKmAsJjCRGFh1F7htu0n7j8dGgMtCPGkmoGD1jHv1nuk6dXJxNxK6ZDO6fTshtxo4HdEIi6PRDsOKEsAOGWh1DUHNnuG1r6XODGnGkGcmmSAdipi3gVj6dwFkv6axm6dmX3lisaOKk6DRDaKUefI7OKKKA+v2KiTnDUvPlag-r7kTiyl0vQgyj0Myj8XSowZ6V0duoMSOa4oIIOUykqtORXBdEESgSmBet1PkNkNBDisKi6KeFfvevkLhqmcigHPBh4jkvoB0S5hOWulOcinBrrFOrktxoqf+EcrIFbGaTipKCONgjGGpJVief2ZktXOxl4teWDnedxIuSgqxmBbXC+eKVGBNieJkGuKAuFgkkeFyF-KKEyTvCnMBXSmeU+Yerkj4jgP4pBV6RIPeSBbgI+QhuRcNvkmMWoddoPkmJUcXvdlBP2ORG6KYD1DUPMrGEmMRa8lpl4pRX4u0eOW5nRTBQ+dJbkrTlRexUcZxdUOGZyHmPEQWI7hmKEs6KpKEgkpEZJd0r0kwBmavDRbeUpYILBXgEMTZSWXwK+U6PMpkFoFBKmGaTMk6KoPJHmI6CspiQ6b2S5ZXFsrZTsg5YpfRXSm5fZCWfoK+WkEsIQpeEmH2IFWLM6J9iKAKKYlYFZfQmAB8l8vrswolRLslTOVVZ8kelLtxlmEOI7i6O2JsHJAYukAIcEsqTaBVTImitWFgPVZOcpQxYIKinZBNYcSuccRNh3NtBStoGpAqIFebAnCtOSmpJKZPEQFvvsf4CxOvpgGKRxRzK6DsBUAcJ3OYDUksi1LsK2JkOIS6JuFXpFXAiddvhdRvgETdVWQcJKKbJLPIDUL+G9WaJ9aoN9W6L9V2aQQrADWdUDVdVQNYKDVGCmIqbIEtBkPyKft+aRPDZEeoAUMjcdadTAOdZdZ-jIHje+PjE6NDaauIPIKkHDR9VTUjcnHTYDUzVQCoKzZVFRCJrjgKFJOFIeHJBTR9ZkJXmEqKE0KjViejdgHCJqQcXsQzfkche+M9WkJueJRARuYibcfqlJNuMKCkHaW0v9TrXrbAQDkSZDCSe0bxMwbcK7cDtSZpctZxaKLUrRMKPyDdAcAqEAeIK2DkKoHUAeCnRYd7u0kQAHe8R7a6vKiQIov0ECXgAKf7brSmhLYkHYKRM6BKvsJeAeGrvaFoJIPuFYPsDaGePVMdVnXycXclEKZnWXXqcbZLeSqYI4CtNHRkGpIGoqVcjkPmI9o8n9UrP4LgLGrlGSL4KSNZBgJZK5OwYZE3FpRzIINtN5T+ITOXvUMKEQlyHGB7vns9AUHfprRIGQNgF0MMFDAMqpkZFNWuh-V-SMCWZvYUjGFqhAoUKIAsgAtqosLsLjikImD9m-UA9-a6gMuxGHl8UrAA+-e-sA0qlxEMUrOAw4K2F1HyqqZuQrSUNjjyOFLtPIJkDeL8OvRgPAFEN7ifcaCILcf2EFuZgcOoEeMiYnbRG9FFF3ZFVOLwyuD9K2G3bmI9FCjgQKOPcaVkDdLuLUAlGqNpLpBlEZFw5nscauHuYI9Mao6I+fjkIsGWlFmOAoFQrIwY8lGlMY3API95DsMFVoB9AKPYAOoGvY67rsD+M47mKg5Yd2TDD4zNA4C7o4QcM4WfiUKgQnTGOPEoHuIqCvSlnKmAAk23JXktnWXk-VBFmtDuANdzWjIQets7YTuRp+lRj3o3shiU0jHvAg3YCFfxQKHevsM6GpKIInTCl6G1tJQ3r+iet05zIOjwRKsrjUmM0JhUYdatvjHk21hlo5gs2LMchQoUKCqoM9hkxoH5BEvuI4IFE0wTj2elh1ntl0yHdiqTSAteATPeiFAttzHFKsSKDjOoG1jsfM+80bFHa2FBEIt9o2SYgBTC0AiGoKP03rkwobm82Y5xWLDWXczuDvIcMYhk1LMrVZvUI7G6A81YaTH7u3gop3qorMxHouocyKjA6k0TVYIFY9OYg3ehaEhrbE2jQXOQc-gs9VscikFhi6B2o4EBIqW3ZZjUs2eqQU2QdIZdXARwJKxfNIDK-MHKwmEQjhukGmP2MabUKoFITAfievVBqzJK8aQ4VkLYAqLvFJBc4gIYUtvCaYQYmnfaXAuK-iXIQoRCKY-3iteoIWhQotBKHQ4gEec6HNhTcsGuCkL4fAYc3wa2PUEoL-Cjt65zOtLzkoFtLcntBqwrKkfsekaHFkTGgsywyBGUGM47V6EBNoPyyUfkLIBFW-XAgMU0UMC0UrG0Yc1ESAssEoN1HUG2kBLuF-EoAUFtTdEGtyZSRK5C-jeIAOOJHLd3EkyS0YNxRcQtEmGYBkJ2SK1rQXCKSTu7Z8a6t8dwCwXcP8fCC2yrRjs3SKILKwzgWOKYPsJSvcVU0WDWw+y8TsfiTnVAP4EHT+yFKeDFCGgRUAayRMhyduTE+nXAo+7yfifyf3SwUR6SD+z+GSrkDvLYDkIieuGYDkPdkKPVFB0Ow4nrdqbALqYc4tKeGuDUHpVRLKC1FKRCnyNkDuL9NBwSaDGmQEC6WWZR7ux-By8jLkG6AUOnLGSjEw+JJazS3EwrI6aeXOcOaylG5WZvBm+kJERfEQSFN2ssJabRJbBQmePh8Gy0wp6eW014uy4KPZ6ct9VYCFJEjsNyMYbmGFcROaKNZXKpSUDi9inZ7fiTWu6cLzXJJa-SeAeJvuOBHUXJ2ZwxaRcxZ4hRWxUFy3ZEYZ84wUORGpOkCI+E6oEsCV5x0835+V8l7Jf4rVyF2FVfm6wsdUiYNtL1NTZBBid13S9FaeXFXZeyz25qitOLC4Rk7vMiVUCFKc9tEG80z1wEDFYEDZRmS-Ktz-jiqkJt+kwPMKJIFJALBcRaVm6V4t+V81TVZi0Nxlw1266e5zMjXhRQnggssKwR756d6efNeipgKtwOLClnP2-UH83JPMSu6cDkJ6M9d58d6TBjQzVjZK-zosCmIcKiY6BmDMn2vyFjlZm6F13exnT3TIQs+YIqZI9owUME1t4gDGOI9k6JKqwqN3UPc+4Sa6sh2p5VBJnEQYk94jSW+JfGPKCrXFKRPyBL27fBy+4h3nQXdZ5yibfUktp2nz1kALwgNaEVRIZHWuRoLr3idq6R4KSwYPXrZz7vACiTXmJAjFMDyWsJS-XUGuy4-nGvRvVZMU3L7MNUMFmuN1csB1w4K9ExynWs6cCFOBMZ3BOg5wJg7Gn-XH6l0bBnNoOtMzxA5tUBPVKZQu0mFF642g4Qxg4h1g3wDg6+0rAs8YIsNkGPq6K7mQgtgOBTZcaajuDYi4EAA */ id: 'Modeling', tsTypes: {} as import('./modelingMachine.typegen').Typegen0, @@ -824,19 +827,27 @@ export const modelingMachine = createMachine( }, states: { - 'awaiting origin': { + 'Awaiting origin': { on: { 'Add circle origin': { - target: 'Finished Circle', + target: 'Awaiting Radius', actions: 'set up draft circle', }, }, }, - 'Finished Circle': {}, + 'Awaiting Radius': { + on: { + 'Finish circle': 'Finished Circle', + }, + }, + + 'Finished Circle': { + always: '#Modeling.Sketch.SketchIdle', + }, }, - initial: 'awaiting origin', + initial: 'Awaiting origin', entry: 'listen for circle origin', }, }, @@ -1291,9 +1302,28 @@ export const modelingMachine = createMachine( }, 'listen for circle origin': ({ sketchDetails }) => { if (!sketchDetails) return - sceneEntitiesManager.setupNoPointsListener({ - sketchDetails, - afterClick: (args) => { + sceneEntitiesManager.createIntersectionPlane() + const quaternion = quaternionFromUpNForward( + new Vector3(...sketchDetails.yAxis), + new Vector3(...sketchDetails.zAxis) + ) + + // Position the click raycast plane + if (sceneEntitiesManager.intersectionPlane) { + sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion( + quaternion + ) + sceneEntitiesManager.intersectionPlane.position.copy( + new Vector3(...(sketchDetails?.origin || [0, 0, 0])) + ) + } + sceneInfra.setCallbacks({ + onClick: async (args) => { + if (!args) return + if (args.mouseEvent.which !== 1) return + const { intersectionPoint } = args + if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) + return const twoD = args.intersectionPoint?.twoD if (twoD) { sceneInfra.modelingSend({ From 44e07ca6e5d6d126e07e86193eca38535658ab43 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Wed, 28 Aug 2024 19:23:05 +1000 Subject: [PATCH 06/41] fix draft arc not being complete --- src/clientSideScene/sceneEntities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 37a96a4450..e7845ee23b 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -1704,7 +1704,7 @@ export class SceneEntities { radius, ccw: true, startAngle: 0, - endAngle: 360, + endAngle: Math.PI * 2, mesh: circleSegmentBodyDashed, isDashed: true, scale, From bfd92a626b263a42762d86526fad3fa6a5033a36 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Wed, 28 Aug 2024 19:40:59 +1000 Subject: [PATCH 07/41] remove extra segment handle --- src/clientSideScene/sceneEntities.ts | 2 +- src/clientSideScene/segments.ts | 19 +------------------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index e7845ee23b..f7b565439d 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -1717,7 +1717,7 @@ export class SceneEntities { group, isHandlesVisible, from, - to, + to: [center[0], center[1]], angle, }) } diff --git a/src/clientSideScene/segments.ts b/src/clientSideScene/segments.ts index 369187727f..47e50826f1 100644 --- a/src/clientSideScene/segments.ts +++ b/src/clientSideScene/segments.ts @@ -402,25 +402,8 @@ export function circleSegment({ new Vector3(0, 1, 0), new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0) ) - const pxLength = (radius * 2 * Math.PI) / scale - const shouldHide = pxLength < HIDE_SEGMENT_LENGTH - - const extraSegmentGroup = createExtraSegmentHandle(scale, texture, theme) - const extraSegmentAngle = 0 - const extraSegmentOffset = new Vector2( - Math.cos(extraSegmentAngle) * radius, - Math.sin(extraSegmentAngle) * radius - ) - extraSegmentGroup.position.set( - center[0] + extraSegmentOffset.x, - center[1] + extraSegmentOffset.y, - 0 - ) - - extraSegmentGroup.visible = !shouldHide - - group.add(mesh, arrowGroup, circleCenterGroup, extraSegmentGroup) + group.add(mesh, arrowGroup, circleCenterGroup) return group } export function tangentialArcToSegment({ From 8d912fa62a721e0e70a0ec2b995dbd615b0d6bf8 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Wed, 28 Aug 2024 19:41:16 +1000 Subject: [PATCH 08/41] make sure cursor is consistent with other tool tips --- src/clientSideScene/ClientSideSceneComp.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx index 6aa50db47a..0662ad5804 100644 --- a/src/clientSideScene/ClientSideSceneComp.tsx +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -126,7 +126,8 @@ export const ClientSideScene = ({ } else if ( state.matches('Sketch.Line tool') || state.matches('Sketch.Tangential arc to') || - state.matches('Sketch.Rectangle tool') + state.matches('Sketch.Rectangle tool') || + state.matches('Sketch.Circle tool') ) { cursor = 'crosshair' } else { From 379960561ddd1695a8d4ec2acf2f756c9d8e3a8d Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Wed, 28 Aug 2024 19:57:26 +1000 Subject: [PATCH 09/41] fix weird tear down issue --- src/clientSideScene/sceneEntities.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index f7b565439d..81a79b8c11 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -676,7 +676,7 @@ export class SceneEntities { const draftExpressionsIndices = { start: index, end: index } - // if (shouldTearDown) await this.tearDownSketch({ removeAxis: false }) + if (shouldTearDown) await this.tearDownSketch({ removeAxis: false }) sceneInfra.resetMouseListeners() const { truncatedAst, programMemoryOverride, sketchGroup } = @@ -780,9 +780,6 @@ export class SceneEntities { }, ...this.mouseEnterLeaveCallbacks(), }) - // } catch (e) { - // console.log('yo wtf', e) - // } } setupDraftRectangle = async ( sketchPathToNode: PathToNode, From c6577a5ae60144bab4824d5af88879c3309d6171 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Wed, 28 Aug 2024 20:03:36 +1000 Subject: [PATCH 10/41] remove error when circle has second click --- src/clientSideScene/sceneEntities.ts | 31 ---------------------------- 1 file changed, 31 deletions(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 81a79b8c11..9c74e07aad 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -1078,37 +1078,6 @@ export class SceneEntities { // Update the primary AST and unequip the rectangle tool await kclManager.executeAstMock(_ast) sceneInfra.modelingSend({ type: 'Finish circle' }) - - const { programMemory } = await executeAst({ - ast: _ast, - useFakeExecutor: true, - engineCommandManager: this.engineCommandManager, - programMemoryOverride, - }) - - // Prepare to update the THREEjs scene - this.sceneProgramMemory = programMemory - const sketchGroup = sketchGroupFromKclValue( - programMemory.get(variableDeclarationName), - variableDeclarationName - ) - if (err(sketchGroup)) return sketchGroup - const sgPaths = sketchGroup.value - const orthoFactor = orthoScale(sceneInfra.camControls.camera) - - // Update the starting segment of the THREEjs scene - this.updateSegment( - sketchGroup.start, - 0, - 0, - _ast, - orthoFactor, - sketchGroup - ) - // Update the rest of the segments of the THREEjs scene - sgPaths.forEach((seg, index) => - this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup) - ) } }, }) From 0d5a8aea936d986445934d4f6af885e46e1da39f Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Thu, 29 Aug 2024 15:05:22 +1000 Subject: [PATCH 11/41] change start end angle for draft circle so that it animates from the oposite side to the handl --- src/clientSideScene/sceneEntities.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 9c74e07aad..86e1ef8cdf 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -1669,8 +1669,9 @@ export class SceneEntities { center, radius, ccw: true, - startAngle: 0, - endAngle: Math.PI * 2, + // make the start end where the handle is + startAngle: Math.PI * 0.25, + endAngle: Math.PI * 2.25, mesh: circleSegmentBodyDashed, isDashed: true, scale, From 72e522dd5151bb72c5d487c80b20c0257a683af8 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Wed, 4 Sep 2024 18:55:05 +1000 Subject: [PATCH 12/41] change circle args --- src/wasm-lib/kcl/src/std/args.rs | 6 +++-- src/wasm-lib/kcl/src/std/shapes.rs | 43 +++++++++++++++++++----------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/wasm-lib/kcl/src/std/args.rs b/src/wasm-lib/kcl/src/std/args.rs index 714da650de..42080dff19 100644 --- a/src/wasm-lib/kcl/src/std/args.rs +++ b/src/wasm-lib/kcl/src/std/args.rs @@ -14,6 +14,8 @@ use crate::{ std::{shapes::SketchSurfaceOrGroup, sketch::FaceTag, FnAsArg}, }; +use super::shapes::Circle; + #[derive(Debug, Clone)] pub struct Args { pub args: Vec, @@ -261,8 +263,7 @@ impl Args { &self, ) -> Result< ( - [f64; 2], - f64, + crate::std::shapes::CircleData, crate::std::shapes::SketchSurfaceOrGroup, Option, ), @@ -605,6 +606,7 @@ fn from_user_val(arg: &KclValue) -> Option { impl_from_arg_via_json!(super::sketch::AngledLineData); impl_from_arg_via_json!(super::sketch::AngledLineToData); impl_from_arg_via_json!(super::sketch::AngledLineThatIntersectsData); +impl_from_arg_via_json!(super::shapes::CircleData); impl_from_arg_via_json!(super::sketch::ArcData); impl_from_arg_via_json!(super::sketch::TangentialArcData); impl_from_arg_via_json!(super::sketch::BezierData); diff --git a/src/wasm-lib/kcl/src/std/shapes.rs b/src/wasm-lib/kcl/src/std/shapes.rs index 7e066adb1b..094a95651b 100644 --- a/src/wasm-lib/kcl/src/std/shapes.rs +++ b/src/wasm-lib/kcl/src/std/shapes.rs @@ -24,12 +24,24 @@ pub enum SketchSurfaceOrGroup { SketchGroup(Box), } +/// Data for drawing an angled line that intersects with a given line. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +// TODO: make sure the docs on the args below are correct. +pub struct CircleData { + /// The center of the circle. + pub center: [f64; 2], + /// The circle radius + pub radius: f64, +} + /// Sketch a circle. pub async fn circle(args: Args) -> Result { - let (center, radius, sketch_surface_or_group, tag): ([f64; 2], f64, SketchSurfaceOrGroup, Option) = + let (data, sketch_surface_or_group, tag): (CircleData, SketchSurfaceOrGroup, Option) = args.get_circle_args()?; - let sketch_group = inner_circle(center, radius, sketch_surface_or_group, tag, args).await?; + let sketch_group = inner_circle(data, sketch_surface_or_group, tag, args).await?; Ok(KclValue::new_user_val(sketch_group.meta.clone(), sketch_group)) } @@ -58,8 +70,7 @@ pub async fn circle(args: Args) -> Result { name = "circle", }] async fn inner_circle( - center: [f64; 2], - radius: f64, + data: CircleData, sketch_surface_or_group: SketchSurfaceOrGroup, tag: Option, args: Args, @@ -68,15 +79,18 @@ async fn inner_circle( SketchSurfaceOrGroup::SketchSurface(surface) => surface, SketchSurfaceOrGroup::SketchGroup(group) => group.on, }; - let sketch_group = - crate::std::sketch::inner_start_profile_at([center[0] + radius, center[1]], sketch_surface, None, args.clone()) - .await?; + let sketch_group = crate::std::sketch::inner_start_profile_at( + [data.center[0] + data.radius, data.center[1]], + sketch_surface, + None, + args.clone(), + ) + .await?; let from: Point2d = sketch_group.current_pen_position()?; let angle_start = Angle::from_degrees(0.0); let angle_end = Angle::from_degrees(360.0); - let (center, end) = arc_center_and_end(from, angle_start, angle_end, radius); if angle_start == angle_end { return Err(KclError::Type(KclErrorDetails { @@ -94,8 +108,8 @@ async fn inner_circle( segment: kittycad::types::PathSegment::Arc { start: angle_start, end: angle_end, - center: center.into(), - radius, + center: data.center.into(), + radius: data.radius, relative: false, }, }, @@ -104,17 +118,16 @@ async fn inner_circle( let current_path = Path::Circle { base: BasePath { - from: center.into(), - // to: end.into(), - to: center.into(), + from: data.center.into(), + to: data.center.into(), tag: tag.clone(), geo_meta: GeoMeta { id, metadata: args.source_range.into(), }, }, - radius, - center: center.into(), + radius: data.radius, + center: data.center.into(), ccw: angle_start.degrees() < angle_end.degrees(), }; From 94b05105210e3434cc335bc6e942c864fff8b5b1 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Sat, 7 Sep 2024 09:04:07 +1000 Subject: [PATCH 13/41] big refactor of var values for astMods --- src/clientSideScene/ClientSideSceneComp.tsx | 15 +- src/clientSideScene/sceneEntities.ts | 83 ++- src/lang/langHelpers.ts | 8 +- src/lang/modifyAst.test.ts | 37 +- src/lang/modifyAst.ts | 48 +- src/lang/queryAst.ts | 10 + src/lang/std/sketch.test.ts | 14 +- src/lang/std/sketch.ts | 742 +++++++++++++++----- src/lang/std/sketchConstraints.ts | 22 +- src/lang/std/sketchcombos.ts | 617 ++++++++++------ src/lang/std/stdTypes.ts | 89 ++- 11 files changed, 1195 insertions(+), 490 deletions(-) diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx index 0662ad5804..ded3b92871 100644 --- a/src/clientSideScene/ClientSideSceneComp.tsx +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -40,7 +40,6 @@ import { InstanceProps, create } from 'react-modal-promise' import { executeAst } from 'lang/langHelpers' import { deleteSegmentFromPipeExpression, - makeRemoveSingleConstraintInput, removeSingleConstraintInfo, } from 'lang/modifyAst' import { ActionButton } from 'components/ActionButton' @@ -515,6 +514,11 @@ const ConstraintSymbol = ({ displayName: 'Intersection Offset', iconName: 'intersection-offset', }, + radius: { + varName: 'radius', + displayName: 'Radius', + iconName: 'dimension', + }, // implicit constraints vertical: { @@ -605,13 +609,10 @@ const ConstraintSymbol = ({ if (trap(_node1)) return Promise.reject(_node1) const shallowPath = _node1.shallowPath - const input = makeRemoveSingleConstraintInput( - argPosition, - shallowPath - ) - if (!input || !context.sketchDetails) return + if (!context.sketchDetails || !argPosition) return const transform = removeSingleConstraintInfo( - input, + shallowPath, + argPosition, kclManager.ast, kclManager.programMemory ) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 86e1ef8cdf..f4720309fb 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -89,6 +89,7 @@ import { createArrayExpression, createCallExpressionStdLib, createLiteral, + createObjectExpression, createPipeExpression, createPipeSubstitution, findUniqueName, @@ -108,6 +109,7 @@ import { getThemeColorForThreeJs } from 'lib/theme' import { err, trap } from 'lib/trap' import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import { Point3d } from 'wasm-lib/kcl/bindings/Point3d' +import { c } from 'vite/dist/node/types.d-aGj9QkWt' type DraftSegment = 'line' | 'tangentialArcTo' @@ -665,8 +667,11 @@ export class SceneEntities { const mod = addNewSketchLn({ node: _ast, programMemory: kclManager.programMemory, - to: [lastSeg.to[0], lastSeg.to[1]], - from: [lastSeg.to[0], lastSeg.to[1]], + input: { + type: 'straight-segment', + to: [lastSeg.to[0], lastSeg.to[1]], + from: [lastSeg.to[0], lastSeg.to[1]], + }, fnName: segmentName, pathToNode: sketchPathToNode, }) @@ -736,8 +741,11 @@ export class SceneEntities { const tmp = addNewSketchLn({ node: kclManager.ast, programMemory: kclManager.programMemory, - to: [intersection2d.x, intersection2d.y], - from: [lastSegment.to[0], lastSegment.to[1]], + input: { + type: 'straight-segment', + to: [intersection2d.x, intersection2d.y], + from: [lastSegment.to[0], lastSegment.to[1]], + }, fnName: lastSegment.type === 'TangentialArcTo' ? 'tangentialArcTo' @@ -962,11 +970,13 @@ export class SceneEntities { startSketchOn[0].init = createPipeExpression([ startSketchOnInit, createCallExpressionStdLib('circle', [ - createArrayExpression([ - createLiteral(roundOff(circleCenter[0])), - createLiteral(roundOff(circleCenter[1])), - ]), - createLiteral(1), + createObjectExpression({ + center: createArrayExpression([ + createLiteral(roundOff(circleCenter[0])), + createLiteral(roundOff(circleCenter[1])), + ]), + radius: createLiteral(1), + }), createPipeSubstitution(), ]), ]) @@ -1147,8 +1157,11 @@ export class SceneEntities { const mod = addNewSketchLn({ node: kclManager.ast, programMemory: kclManager.programMemory, - to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y], - from: [prevSegment.from[0], prevSegment.from[1]], + input: { + type: 'straight-segment', + to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y], + from: [prevSegment.from[0], prevSegment.from[1]], + }, // TODO assuming it's always a straight segments being added // as this is easiest, and we'll need to add "tabbing" behavior // to support other segment types @@ -1293,41 +1306,55 @@ export class SceneEntities { modded = updateStartProfileAtArgs({ node: modifiedAst, pathToNode, - to: dragTo, - from, + input: { + type: 'straight-segment', + to: dragTo, + from, + }, previousProgramMemory: kclManager.programMemory, }) } else if (group.name === CIRCLE_SEGMENT && subGroup?.name === ARROWHEAD) { // is dragging the radius handle - modded = changeCircleArguments( + + modded = changeSketchArguments( modifiedAst, kclManager.programMemory, - getNodePathFromSourceRange(modifiedAst, [node.start, node.end]), - group.userData.center, - Math.sqrt( - (group.userData.center[0] - dragTo[0]) ** 2 + - (group.userData.center[0] - dragTo[0]) ** 2 - ) + [node.start, node.end], + { + type: 'arc-segment', + from, + center: group.userData.center, + radius: Math.sqrt( + (group.userData.center[0] - dragTo[0]) ** 2 + + (group.userData.center[0] - dragTo[0]) ** 2 + ), + } ) } else if ( group.name === CIRCLE_SEGMENT && subGroup?.name === CIRCLE_CENTER_HANDLE ) { - // is dragging the center handle - modded = changeCircleArguments( + modded = changeSketchArguments( modifiedAst, kclManager.programMemory, - getNodePathFromSourceRange(modifiedAst, [node.start, node.end]), - dragTo, - group.userData.radius + [node.start, node.end], + { + type: 'arc-segment', + from, + center: dragTo, + radius: group.userData.radius, + } ) } else { modded = changeSketchArguments( modifiedAst, kclManager.programMemory, [node.start, node.end], - dragTo, - from + { + type: 'straight-segment', + from, + to: dragTo, + } ) } if (trap(modded)) return @@ -1683,7 +1710,7 @@ export class SceneEntities { arrowGroup, group, isHandlesVisible, - from, + from: to, to: [center[0], center[1]], angle, }) diff --git a/src/lang/langHelpers.ts b/src/lang/langHelpers.ts index f2b69822ff..028eab25ed 100644 --- a/src/lang/langHelpers.ts +++ b/src/lang/langHelpers.ts @@ -24,11 +24,9 @@ export type ToolTip = | 'yLineTo' | 'angledLineThatIntersects' | 'tangentialArcTo' + | 'circle' -export const toolTips = [ - 'sketch_line', - 'move', - // original tooltips +export const toolTips: Array = [ 'line', 'lineTo', 'angledLine', @@ -42,7 +40,7 @@ export const toolTips = [ 'yLineTo', 'angledLineThatIntersects', 'tangentialArcTo', -] as any as ToolTip[] +] export async function executeAst({ ast, diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts index b196dfb9db..cf775effcd 100644 --- a/src/lang/modifyAst.test.ts +++ b/src/lang/modifyAst.test.ts @@ -20,6 +20,7 @@ import { import { enginelessExecutor } from '../lib/testHelpers' import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst' import { err } from 'lib/trap' +import { SimplifiedVarValue, VarValueKeys } from './std/stdTypes' beforeAll(async () => { await initPromise @@ -638,11 +639,21 @@ describe('Testing removeSingleConstraintInfo', () => { code.indexOf(lineOfInterest) + lineOfInterest.length, ] const pathToNode = getNodePathFromSourceRange(ast, range) + let argPosition: SimplifiedVarValue + if (key === 'arrayIndex' && typeof value === 'number') { + argPosition = { type: 'arrayItem', argIndex: 0, index: value ? 0 : 1 } + } else if (key === 'objectProperty' && typeof value === 'string') { + argPosition = { + type: 'objectProperty', + key: value as VarValueKeys, + argIndex: 0, + } + } else { + throw new Error('argPosition is undefined') + } const mod = removeSingleConstraintInfo( - { - pathToCallExp: pathToNode, - [key]: value, - }, + pathToNode, + argPosition, ast, programMemory ) @@ -675,12 +686,22 @@ describe('Testing removeSingleConstraintInfo', () => { code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + lineOfInterest.length, ] + let argPosition: SimplifiedVarValue + if (key === 'arrayIndex' && typeof value === 'number') { + argPosition = { type: 'arrayItem', argIndex: 0, index: value ? 0 : 1 } + } else if (key === 'objectProperty' && typeof value === 'string') { + argPosition = { + type: 'objectProperty', + key: value as VarValueKeys, + argIndex: 0, + } + } else { + throw new Error('argPosition is undefined') + } const pathToNode = getNodePathFromSourceRange(ast, range) const mod = removeSingleConstraintInfo( - { - pathToCallExp: pathToNode, - [key]: value, - }, + pathToNode, + argPosition, ast, programMemory ) diff --git a/src/lang/modifyAst.ts b/src/lang/modifyAst.ts index 8f985df58c..bcd3cb5956 100644 --- a/src/lang/modifyAst.ts +++ b/src/lang/modifyAst.ts @@ -38,7 +38,7 @@ import { import { DefaultPlaneStr } from 'clientSideScene/sceneEntities' import { isOverlap, roundOff } from 'lib/utils' import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants' -import { ConstrainInfo } from './std/stdTypes' +import { SimplifiedVarValue } from './std/stdTypes' import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator' import { Models } from '@kittycad/lib' @@ -799,15 +799,10 @@ export function deleteSegmentFromPipeExpression( ) if (!constraintInfo) return - const input = makeRemoveSingleConstraintInput( - constraintInfo.argPosition, - callExp.shallowPath - ) - if (!input) return + if (!constraintInfo.argPosition) return const transform = removeSingleConstraintInfo( - { - ...input, - }, + callExp.shallowPath, + constraintInfo.argPosition, _modifiedAst, programMemory ) @@ -834,37 +829,9 @@ export function deleteSegmentFromPipeExpression( return _modifiedAst } -export function makeRemoveSingleConstraintInput( - argPosition: ConstrainInfo['argPosition'], - pathToNode: PathToNode -): Parameters[0] | false { - return argPosition?.type === 'singleValue' - ? { - pathToCallExp: pathToNode, - } - : argPosition?.type === 'arrayItem' - ? { - pathToCallExp: pathToNode, - arrayIndex: argPosition.index, - } - : argPosition?.type === 'objectProperty' - ? { - pathToCallExp: pathToNode, - objectProperty: argPosition.key, - } - : false -} - export function removeSingleConstraintInfo( - { - pathToCallExp, - arrayIndex, - objectProperty, - }: { - pathToCallExp: PathToNode - arrayIndex?: number - objectProperty?: string - }, + pathToCallExp: PathToNode, + varValue: SimplifiedVarValue, ast: Program, programMemory: ProgramMemory ): @@ -875,8 +842,7 @@ export function removeSingleConstraintInfo( | false { const transform = removeSingleConstraint({ pathToCallExp, - arrayIndex, - objectProperty, + inputDetails: varValue, ast, }) if (!transform) return false diff --git a/src/lang/queryAst.ts b/src/lang/queryAst.ts index 236b21a501..21fd36ba94 100644 --- a/src/lang/queryAst.ts +++ b/src/lang/queryAst.ts @@ -16,6 +16,7 @@ import { VariableDeclaration, VariableDeclarator, sketchGroupFromKclValue, + ObjectExpression, } from './wasm' import { createIdentifier, splitPathAtLastIndex } from './modifyAst' import { getSketchSegmentFromSourceRange } from './std/sketchConstraints' @@ -934,3 +935,12 @@ export function hasExtrudableGeometry(ast: Program) { }) return Object.keys(theMap).length > 0 } + +export function getObjExpProperty( + node: ObjectExpression, + propName: string +): { exp: Expr; index: number } | null { + const index = node.properties.findIndex(({ key }) => key.name === propName) + if (index === -1) return null + return { exp: node.properties[index].value, index } +} diff --git a/src/lang/std/sketch.test.ts b/src/lang/std/sketch.test.ts index e5735f4c8b..2496390552 100644 --- a/src/lang/std/sketch.test.ts +++ b/src/lang/std/sketch.test.ts @@ -123,8 +123,11 @@ describe('testing changeSketchArguments', () => { ast, programMemory, [sourceStart, sourceStart + lineToChange.length], - [2, 3], - [0, 0] + { + type: 'straight-segment', + from: [0, 0], + to: [2, 3], + } ) if (err(changeSketchArgsRetVal)) return changeSketchArgsRetVal expect(recast(changeSketchArgsRetVal.modifiedAst)).toBe(expectedCode) @@ -150,8 +153,11 @@ const mySketch001 = startSketchOn('XY') const newSketchLnRetVal = addNewSketchLn({ node: ast, programMemory, - to: [2, 3], - from: [0, 0], + input: { + type: 'straight-segment', + from: [0, 0], + to: [2, 3], + }, fnName: 'lineTo', pathToNode: [ ['body', ''], diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 093fb8d222..74add0bdab 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -9,7 +9,6 @@ import { CallExpression, VariableDeclarator, Expr, - Literal, VariableDeclaration, Identifier, sketchGroupFromKclValue, @@ -18,9 +17,9 @@ import { getNodeFromPath, getNodeFromPathCurry, getNodePathFromSourceRange, + getObjExpProperty, } from 'lang/queryAst' import { - LineInputsType, isLiteralArrayOrStatic, isNotLiteralArrayOrStatic, } from 'lang/std/sketchcombos' @@ -31,13 +30,12 @@ import { SketchLineHelper, TransformCallback, ConstrainInfo, - RawValues, ArrayItemInput, ObjectPropertyInput, SingleValueInput, - VarValueKeys, - ArrayOrObjItemInput, AddTagInfo, + SegmentInputs, + SimplifiedVarValue, } from 'lang/std/stdTypes' import { @@ -56,6 +54,11 @@ import { err } from 'lib/trap' import { perpendicularDistance } from 'sketch-helpers' import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator' +const STRAIGHT_SEGMENT_ERR = new Error( + 'Invalid input, expected "straight-segment"' +) +const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"') + export type Coords2d = [number, number] export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d { @@ -109,6 +112,7 @@ type AbbreviatedInput = | ArrayItemInput['index'] | ObjectPropertyInput['key'] | SingleValueInput['type'] + | SimplifiedVarValue | undefined const constrainInfo = ( @@ -126,11 +130,13 @@ const constrainInfo = ( sourceRange: d, argPosition: g === 'singleValue' - ? { type: 'singleValue' } - : typeof g === 'number' - ? { type: 'arrayItem', index: g } + ? { type: 'singleValue', argIndex: 0 } + : g === 1 || g === 0 + ? { type: 'arrayItem', index: g, argIndex: 0 } : typeof g === 'string' - ? { type: 'objectProperty', key: g } + ? { type: 'objectProperty', key: g, argIndex: 0 } + : g && 'type' in g + ? g : undefined, pathToNode: e, stdLibFnName: f, @@ -270,45 +276,6 @@ const horzVertConstraintInfoHelper = ( ] } -function arrayRawValuesHelper(a: Array<[Literal, LineInputsType]>): RawValues { - return a.map( - ([literal, argType], index): ArrayItemInput => ({ - type: 'arrayItem', - index: index === 0 ? 0 : 1, - argType, - value: literal, - }) - ) -} - -function arrOrObjectRawValuesHelper( - a: Array<[Literal, LineInputsType, VarValueKeys]> -): RawValues { - return a.map( - ([literal, argType, key], index): ArrayOrObjItemInput => ({ - type: 'arrayOrObjItem', - // key: argType,w - index: index === 0 ? 0 : 1, - key, - argType, - value: literal, - }) - ) -} - -function singleRawValueHelper( - literal: Literal, - argType: LineInputsType -): RawValues { - return [ - { - type: 'singleValue', - argType, - value: literal, - }, - ] -} - function getTag(index = 2): SketchLineHelper['getTag'] { return (callExp: CallExpression) => { if (callExp.type !== 'CallExpression') @@ -325,11 +292,13 @@ export const lineTo: SketchLineHelper = { add: ({ node, pathToNode, - to, + input, createCallback, replaceExisting, referencedSegment, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const to = input.to const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -351,11 +320,28 @@ export const lineTo: SketchLineHelper = { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) if (replaceExisting && createCallback) { const { callExp, valueUsedInTransform } = createCallback( - newVals, - arrayRawValuesHelper([ - [createLiteral(roundOff(to[0], 2)), 'xAbsolute'], - [createLiteral(roundOff(to[1], 2)), 'yAbsolute'], - ]), + [ + { + varExpression: createLiteral(roundOff(to[0], 2)), + varDetails: { + type: 'arrayItem', + index: 0, + argType: 'xAbsolute', + value: createLiteral(roundOff(to[0], 2)), + argIndex: 0, + }, + }, + { + varExpression: createLiteral(roundOff(to[1], 2)), + varDetails: { + type: 'arrayItem', + index: 1, + argType: 'yAbsolute', + value: createLiteral(roundOff(to[1], 2)), + argIndex: 0, + }, + }, + ], referencedSegment ) pipe.body[callIndex] = callExp @@ -372,7 +358,9 @@ export const lineTo: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -407,13 +395,14 @@ export const line: SketchLineHelper = { node, previousProgramMemory, pathToNode, - to, - from, + input, replaceExisting, referencedSegment, createCallback, spliceBetween, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = input const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -459,11 +448,28 @@ export const line: SketchLineHelper = { if (replaceExisting && createCallback && pipe.type !== 'CallExpression') { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback( - [newXVal, newYVal], - arrayRawValuesHelper([ - [createLiteral(roundOff(to[0] - from[0], 2)), 'xRelative'], - [createLiteral(roundOff(to[1] - from[1], 2)), 'yRelative'], - ]), + [ + { + varExpression: createLiteral(roundOff(to[0] - from[0], 2)), + varDetails: { + type: 'arrayItem', + index: 0, + argType: 'xRelative', + value: createLiteral(roundOff(to[0] - from[0], 2)), + argIndex: 0, + }, + }, + { + varExpression: createLiteral(roundOff(to[1] - from[1], 2)), + varDetails: { + type: 'arrayItem', + index: 0, + argType: 'yRelative', + value: createLiteral(roundOff(to[1] - from[1], 2)), + argIndex: 0, + }, + }, + ], referencedSegment ) pipe.body[callIndex] = callExp @@ -496,7 +502,9 @@ export const line: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -530,7 +538,9 @@ export const line: SketchLineHelper = { } export const xLineTo: SketchLineHelper = { - add: ({ node, pathToNode, to, replaceExisting, createCallback }) => { + add: ({ node, pathToNode, input, replaceExisting, createCallback }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = input const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -541,10 +551,17 @@ export const xLineTo: SketchLineHelper = { if (replaceExisting && createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [newVal, newVal], - singleRawValueHelper(newVal, 'xAbsolute') - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + varExpression: createLiteral(roundOff(to[0], 2)), + varDetails: { + type: 'singleValue', + argType: 'xAbsolute', + value: createLiteral(roundOff(to[0], 2)), + argIndex: 0, + }, + }, + ]) pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -562,7 +579,9 @@ export const xLineTo: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -591,7 +610,9 @@ export const xLineTo: SketchLineHelper = { } export const yLineTo: SketchLineHelper = { - add: ({ node, pathToNode, to, replaceExisting, createCallback }) => { + add: ({ node, pathToNode, input, replaceExisting, createCallback }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = input const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -602,10 +623,17 @@ export const yLineTo: SketchLineHelper = { if (replaceExisting && createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [newVal, newVal], - singleRawValueHelper(newVal, 'yAbsolute') - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + varExpression: newVal, + varDetails: { + type: 'singleValue', + argType: 'yAbsolute', + value: newVal, + argIndex: 0, + }, + }, + ]) pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -623,7 +651,9 @@ export const yLineTo: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -652,7 +682,9 @@ export const yLineTo: SketchLineHelper = { } export const xLine: SketchLineHelper = { - add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => { + add: ({ node, pathToNode, input, replaceExisting, createCallback }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = input const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -660,14 +692,20 @@ export const xLine: SketchLineHelper = { const { node: pipe } = _node1 const newVal = createLiteral(roundOff(to[0] - from[0], 2)) - const firstArg = newVal if (replaceExisting && createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [firstArg, firstArg], - singleRawValueHelper(firstArg, 'xRelative') - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + varExpression: newVal, + varDetails: { + type: 'singleValue', + argType: 'xRelative', + value: newVal, + argIndex: 0, + }, + }, + ]) pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -677,13 +715,15 @@ export const xLine: SketchLineHelper = { } const newLine = createCallExpression('xLine', [ - firstArg, + newVal, createPipeSubstitution(), ]) pipe.body = [...pipe.body, newLine] return { modifiedAst: _node, pathToNode } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -712,7 +752,9 @@ export const xLine: SketchLineHelper = { } export const yLine: SketchLineHelper = { - add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => { + add: ({ node, pathToNode, input, replaceExisting, createCallback }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = input const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -721,10 +763,17 @@ export const yLine: SketchLineHelper = { const newVal = createLiteral(roundOff(to[1] - from[1], 2)) if (replaceExisting && createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [newVal, newVal], - singleRawValueHelper(newVal, 'yRelative') - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + varExpression: newVal, + varDetails: { + type: 'singleValue', + argType: 'yRelative', + value: newVal, + argIndex: 0, + }, + }, + ]) pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -740,7 +789,9 @@ export const yLine: SketchLineHelper = { pipe.body = [...pipe.body, newLine] return { modifiedAst: _node, pathToNode } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -772,11 +823,13 @@ export const tangentialArcTo: SketchLineHelper = { add: ({ node, pathToNode, - to, + input, createCallback, replaceExisting, referencedSegment, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = input const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -796,11 +849,28 @@ export const tangentialArcTo: SketchLineHelper = { if (replaceExisting && createCallback && pipe.type !== 'CallExpression') { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback( - [toX, toY], - arrayRawValuesHelper([ - [createLiteral(roundOff(to[0], 2)), 'xAbsolute'], - [createLiteral(roundOff(to[1], 2)), 'yAbsolute'], - ]), + [ + { + varExpression: toX, + varDetails: { + type: 'arrayItem', + index: 0, + argType: 'xRelative', + value: toX, + argIndex: 0, + }, + }, + { + varExpression: toY, + varDetails: { + type: 'arrayItem', + index: 1, + argType: 'yAbsolute', + value: toY, + argIndex: 0, + }, + }, + ], referencedSegment ) pipe.body[callIndex] = callExp @@ -832,7 +902,9 @@ export const tangentialArcTo: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -899,16 +971,198 @@ export const tangentialArcTo: SketchLineHelper = { ] }, } +export const circle: SketchLineHelper = { + add: ({ node, pathToNode, input, createCallback, replaceExisting }) => { + if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR + + const { center, radius } = input + const _node = { ...node } + const nodeMeta = getNodeFromPath( + _node, + pathToNode, + 'PipeExpression' + ) + if (err(nodeMeta)) return nodeMeta + + const { node: pipe } = nodeMeta + + const x = createLiteral(roundOff(center[0], 2)) + const y = createLiteral(roundOff(center[1], 2)) + + const radiusExp = createLiteral(roundOff(radius, 2)) + + if (replaceExisting && createCallback) { + const { callExp, valueUsedInTransform } = createCallback([ + { + varExpression: x, + varDetails: { + type: 'objectPropertyArray', + index: 0, + key: 'center', + argType: 'xAbsolute', + value: x, + argIndex: 0, + }, + }, + { + varExpression: y, + varDetails: { + type: 'objectPropertyArray', + index: 1, + key: 'center', + argType: 'yAbsolute', + value: y, + argIndex: 0, + }, + }, + { + varExpression: radiusExp, + varDetails: { + type: 'objectProperty', + key: 'radius', + argType: 'radius', + value: radiusExp, + argIndex: 0, + }, + }, + ]) + + const { index: callIndex } = splitPathAtPipeExpression(pathToNode) + pipe.body[callIndex] = callExp + + return { + modifiedAst: _node, + pathToNode, + valueUsedInTransform, + } + } + return new Error('not implemented') + }, + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR + const { center, radius } = input + const _node = { ...node } + const nodeMeta = getNodeFromPath(_node, pathToNode) + if (err(nodeMeta)) return nodeMeta + + const { node: callExpression, shallowPath } = nodeMeta + + const firstArg = callExpression.arguments?.[0] + const newCenter = createArrayExpression([ + createLiteral(roundOff(center[0])), + createLiteral(roundOff(center[1])), + ]) + mutateObjExpProp(firstArg, newCenter, 'center') + const newRadius = createLiteral(roundOff(radius)) + mutateObjExpProp(firstArg, newRadius, 'radius') + return { + modifiedAst: _node, + pathToNode: shallowPath, + } + }, + getTag: getTag(3), + addTag: addTag(3), + getConstraintInfo: (callExp: CallExpression, code, pathToNode) => { + if (callExp.type !== 'CallExpression') return [] + const firstArg = callExp.arguments?.[0] + if (firstArg.type !== 'ObjectExpression') return [] + const centerDetails = getObjExpProperty(firstArg, 'center') + const radiusDetails = getObjExpProperty(firstArg, 'radius') + if (!centerDetails || !radiusDetails) return [] + if (centerDetails.exp.type !== 'ArrayExpression') return [] + + const pathToCenterArrayExpression: PathToNode = [ + ...pathToNode, + ['arguments', 'CallExpression'], + [0, 'index'], + ['properties', 'ObjectExpression'], + [centerDetails.index, 'index'], + ['value', 'Property'], + ['elements', 'ArrayExpression'], + ] + const pathToRadiusLiteral: PathToNode = [ + ...pathToNode, + ['arguments', 'CallExpression'], + [0, 'index'], + ['properties', 'ObjectExpression'], + [radiusDetails.index, 'index'], + ['value', 'Property'], + ] + const pathToXArg: PathToNode = [ + ...pathToCenterArrayExpression, + [0, 'index'], + ] + const pathToYArg: PathToNode = [ + ...pathToCenterArrayExpression, + [1, 'index'], + ] + + return [ + constrainInfo( + 'radius', + isNotLiteralArrayOrStatic(radiusDetails.exp), + code.slice(radiusDetails.exp.start, radiusDetails.exp.end), + 'circle', + { + type: 'objectProperty', + key: 'radius', + argIndex: 0, + }, + [radiusDetails.exp.start, radiusDetails.exp.end], + pathToRadiusLiteral + ), + { + stdLibFnName: 'circle', + type: 'xAbsolute', + isConstrained: isNotLiteralArrayOrStatic(centerDetails.exp.elements[0]), + sourceRange: [ + centerDetails.exp.elements[0].start, + centerDetails.exp.elements[0].end, + ], + pathToNode: pathToXArg, + value: code.slice( + centerDetails.exp.elements[0].start, + centerDetails.exp.elements[0].end + ), + argPosition: { + type: 'arrayInObject', + index: 0, + key: 'center', + }, + }, + { + stdLibFnName: 'circle', + type: 'yAbsolute', + isConstrained: isNotLiteralArrayOrStatic(centerDetails.exp.elements[1]), + sourceRange: [ + centerDetails.exp.elements[1].start, + centerDetails.exp.elements[1].end, + ], + pathToNode: pathToYArg, + value: code.slice( + centerDetails.exp.elements[1].start, + centerDetails.exp.elements[1].end + ), + argPosition: { + type: 'arrayInObject', + index: 1, + key: 'center', + }, + }, + ] + }, +} export const angledLine: SketchLineHelper = { add: ({ node, pathToNode, - to, - from, + input, createCallback, replaceExisting, referencedSegment, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = input const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -925,11 +1179,30 @@ export const angledLine: SketchLineHelper = { if (replaceExisting && createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback( - [newAngleVal, newLengthVal], - arrOrObjectRawValuesHelper([ - [newAngleVal, 'angle', 'angle'], - [newLengthVal, 'length', 'length'], - ]), + [ + { + varExpression: newAngleVal, + varDetails: { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + value: newAngleVal, + argIndex: 0, + }, + }, + { + varExpression: newLengthVal, + varDetails: { + type: 'arrayOrObjItem', + index: 1, + key: 'length', + argType: 'length', + value: newLengthVal, + argIndex: 0, + }, + }, + ], referencedSegment ) pipe.body[callIndex] = callExp @@ -946,7 +1219,9 @@ export const angledLine: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -988,11 +1263,12 @@ export const angledLineOfXLength: SketchLineHelper = { node, previousProgramMemory, pathToNode, - to, - from, + input, createCallback, replaceExisting, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = input const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1020,13 +1296,30 @@ export const angledLineOfXLength: SketchLineHelper = { const angle = createLiteral(roundOff(getAngle(from, to), 0)) const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1) const newLine = createCallback - ? createCallback( - [angle, xLength], - arrOrObjectRawValuesHelper([ - [angle, 'angle', 'angle'], - [xLength, 'xRelative', 'length'], - ]) - ).callExp + ? createCallback([ + { + varExpression: angle, + varDetails: { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + value: angle, + argIndex: 0, + }, + }, + { + varExpression: xLength, + varDetails: { + type: 'arrayOrObjItem', + index: 1, + key: 'length', + argType: 'xRelative', + value: xLength, + argIndex: 0, + }, + }, + ]).callExp : createCallExpression('angledLineOfXLength', [ createArrayExpression([angle, xLength]), createPipeSubstitution(), @@ -1042,7 +1335,9 @@ export const angledLineOfXLength: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1088,11 +1383,12 @@ export const angledLineOfYLength: SketchLineHelper = { node, previousProgramMemory, pathToNode, - to, - from, + input, createCallback, replaceExisting, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = input const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1118,13 +1414,30 @@ export const angledLineOfYLength: SketchLineHelper = { const angle = createLiteral(roundOff(getAngle(from, to), 0)) const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1) const newLine = createCallback - ? createCallback( - [angle, yLength], - arrOrObjectRawValuesHelper([ - [angle, 'angle', 'angle'], - [yLength, 'yRelative', 'length'], - ]) - ).callExp + ? createCallback([ + { + varExpression: angle, + varDetails: { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + value: angle, + argIndex: 0, + }, + }, + { + varExpression: yLength, + varDetails: { + type: 'arrayOrObjItem', + index: 1, + key: 'length', + argType: 'yRelative', + value: yLength, + argIndex: 0, + }, + }, + ]).callExp : createCallExpression('angledLineOfYLength', [ createArrayExpression([angle, yLength]), createPipeSubstitution(), @@ -1140,7 +1453,9 @@ export const angledLineOfYLength: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1185,12 +1500,13 @@ export const angledLineToX: SketchLineHelper = { add: ({ node, pathToNode, - to, - from, + input, createCallback, replaceExisting, referencedSegment, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = input const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1204,11 +1520,30 @@ export const angledLineToX: SketchLineHelper = { const xArg = createLiteral(roundOff(to[0], 2)) if (replaceExisting && createCallback) { const { callExp, valueUsedInTransform } = createCallback( - [angle, xArg], - arrOrObjectRawValuesHelper([ - [angle, 'angle', 'angle'], - [xArg, 'xAbsolute', 'to'], - ]), + [ + { + varExpression: angle, + varDetails: { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + value: angle, + argIndex: 0, + }, + }, + { + varExpression: xArg, + varDetails: { + type: 'arrayOrObjItem', + index: 1, + key: 'to', + argType: 'xAbsolute', + value: xArg, + argIndex: 0, + }, + }, + ], referencedSegment ) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) @@ -1230,7 +1565,9 @@ export const angledLineToX: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1273,12 +1610,13 @@ export const angledLineToY: SketchLineHelper = { add: ({ node, pathToNode, - to, - from, + input, createCallback, replaceExisting, referencedSegment, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = input const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1294,11 +1632,30 @@ export const angledLineToY: SketchLineHelper = { if (replaceExisting && createCallback) { const { callExp, valueUsedInTransform } = createCallback( - [angle, yArg], - arrOrObjectRawValuesHelper([ - [angle, 'angle', 'angle'], - [yArg, 'yAbsolute', 'to'], - ]), + [ + { + varExpression: angle, + varDetails: { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + value: angle, + argIndex: 0, + }, + }, + { + varExpression: yArg, + varDetails: { + type: 'arrayOrObjItem', + index: 1, + key: 'to', + argType: 'yAbsolute', + value: yArg, + argIndex: 0, + }, + }, + ], referencedSegment ) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) @@ -1320,7 +1677,9 @@ export const angledLineToY: SketchLineHelper = { pathToNode, } }, - updateArgs: ({ node, pathToNode, to, from }) => { + updateArgs: ({ node, pathToNode, input }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1363,12 +1722,13 @@ export const angledLineThatIntersects: SketchLineHelper = { add: ({ node, pathToNode, - to, - from, + input, createCallback, replaceExisting, referencedSegment, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = input const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1396,23 +1756,28 @@ export const angledLineThatIntersects: SketchLineHelper = { ) if (replaceExisting && createCallback) { - const { callExp, valueUsedInTransform } = createCallback( - [angle, offset], - [ - { + const { callExp, valueUsedInTransform } = createCallback([ + { + varExpression: angle, + varDetails: { type: 'objectProperty', key: 'angle', - value: angle, argType: 'angle', + value: angle, + argIndex: 0, }, - { + }, + { + varExpression: offset, + varDetails: { type: 'objectProperty', key: 'offset', - value: offset, argType: 'intersectionOffset', + value: offset, + argIndex: 0, }, - ] - ) + }, + ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) pipe.body[callIndex] = callExp return { @@ -1423,7 +1788,9 @@ export const angledLineThatIntersects: SketchLineHelper = { } return new Error('not implemented') }, - updateArgs: ({ node, pathToNode, to, from, previousProgramMemory }) => { + updateArgs: ({ node, pathToNode, input, previousProgramMemory }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to, from } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) return nodeMeta @@ -1559,8 +1926,10 @@ export const angledLineThatIntersects: SketchLineHelper = { export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({ node, pathToNode, - to, + input, }) => { + if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = input const _node = { ...node } const nodeMeta = getNodeFromPath(_node, pathToNode) if (err(nodeMeta)) { @@ -1609,8 +1978,12 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = { angledLineToY, angledLineThatIntersects, tangentialArcTo, + circle, } as const +/** + * @deprecated Use {@link changeSketchArguments} instead + */ export function changeCircleArguments( node: Program, programMemory: ProgramMemory, @@ -1630,8 +2003,10 @@ export function changeCircleArguments( createLiteral(roundOff(center[1])), ]) const newRadius = createLiteral(roundOff(radius)) - callExpression.arguments[0] = newCenter - callExpression.arguments[1] = newRadius + callExpression.arguments[0] = createObjectExpression({ + center: newCenter, + radius: newRadius, + }) return { modifiedAst: _node, pathToNode: shallowPath, @@ -1645,8 +2020,7 @@ export function changeSketchArguments( node: Program, programMemory: ProgramMemory, sourceRange: SourceRange, - args: [number, number], - from: [number, number] + input: SegmentInputs ): { modifiedAst: Program; pathToNode: PathToNode } | Error { const _node = { ...node } const thePath = getNodePathFromSourceRange(_node, sourceRange) @@ -1665,8 +2039,7 @@ export function changeSketchArguments( node: _node, previousProgramMemory: programMemory, pathToNode: shallowPath, - to: args, - from, + input, }) } @@ -1714,8 +2087,7 @@ export function compareVec2Epsilon2( interface CreateLineFnCallArgs { node: Program programMemory: ProgramMemory - to: [number, number] - from: [number, number] + input: SegmentInputs fnName: ToolTip pathToNode: PathToNode spliceBetween?: boolean @@ -1724,10 +2096,9 @@ interface CreateLineFnCallArgs { export function addNewSketchLn({ node: _node, programMemory: previousProgramMemory, - to, fnName, pathToNode, - from, + input, spliceBetween = false, }: CreateLineFnCallArgs): | { @@ -1751,8 +2122,7 @@ export function addNewSketchLn({ node, previousProgramMemory, pathToNode, - to, - from, + input, replaceExisting: false, spliceBetween, }) @@ -1814,8 +2184,7 @@ export function replaceSketchLine({ programMemory, pathToNode: _pathToNode, fnName, - to, - from, + input, createCallback, referencedSegment, }: { @@ -1823,8 +2192,7 @@ export function replaceSketchLine({ programMemory: ProgramMemory pathToNode: PathToNode fnName: ToolTip - to: [number, number] - from: [number, number] + input: SegmentInputs createCallback: TransformCallback referencedSegment?: Path }): @@ -1834,8 +2202,8 @@ export function replaceSketchLine({ pathToNode: PathToNode } | Error { - if (![...toolTips, 'intersect'].includes(fnName)) { - return new Error('not a tooltip') + if (![...toolTips, 'intersect', 'circle'].includes(fnName)) { + return new Error(`The following function name is not tooltip: ${fnName}`) } const _node = { ...node } @@ -1845,8 +2213,7 @@ export function replaceSketchLine({ previousProgramMemory: programMemory, pathToNode: _pathToNode, referencedSegment, - to, - from, + input, replaceExisting: true, createCallback, }) @@ -2023,6 +2390,32 @@ function getFirstArgValuesForXYLineFns(callExpression: CallExpression): { } } +const getCircle = ( + callExp: CallExpression +): + | { + val: [Expr, Expr, Expr] + tag?: Expr + } + | Error => { + const firstArg = callExp.arguments[0] + if (firstArg.type === 'ObjectExpression') { + const centerDetails = getObjExpProperty(firstArg, 'center') + const radiusDetails = getObjExpProperty(firstArg, 'radius') + const tag = callExp.arguments[2] + if (centerDetails?.exp?.type === 'ArrayExpression' && radiusDetails) { + return { + val: [ + centerDetails?.exp.elements[0], + centerDetails?.exp.elements[1], + radiusDetails.exp, + ], + tag, + } + } + } + return new Error('expected ArrayExpression or ObjectExpression') +} const getAngledLineThatIntersects = ( callExp: CallExpression ): @@ -2082,5 +2475,8 @@ export function getFirstArg(callExp: CallExpression): // TODO probably needs it's own implementation return getFirstArgValuesForXYFns(callExp) } + if (name === 'circle') { + return getCircle(callExp) + } return new Error('unexpected call expression: ' + name) } diff --git a/src/lang/std/sketchConstraints.ts b/src/lang/std/sketchConstraints.ts index 62fc2e2f06..bac1b57a6b 100644 --- a/src/lang/std/sketchConstraints.ts +++ b/src/lang/std/sketchConstraints.ts @@ -43,6 +43,16 @@ export function getSketchSegmentFromSourceRange( index: number } | Error { + const lineIndex = sketchGroup.value.findIndex( + ({ __geoMeta: { sourceRange } }: Path) => + sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd + ) + const line = sketchGroup.value[lineIndex] + if (line) + return { + segment: line, + index: lineIndex, + } const startSourceRange = sketchGroup.start?.__geoMeta.sourceRange if ( startSourceRange && @@ -51,17 +61,7 @@ export function getSketchSegmentFromSourceRange( sketchGroup.start ) return { segment: { ...sketchGroup.start, type: 'Base' }, index: -1 } - - const lineIndex = sketchGroup.value.findIndex( - ({ __geoMeta: { sourceRange } }: Path) => - sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd - ) - const line = sketchGroup.value[lineIndex] - if (!line) return new Error('could not find matching line') - return { - segment: line, - index: lineIndex, - } + return new Error('could not find matching segment') } export function isSketchVariablesLinked( diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index bfe12a6ff2..b9383bc391 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1,4 +1,4 @@ -import { TransformCallback, VarValues } from './stdTypes' +import { SimplifiedVarValue, TransformCallback, VarValue } from './stdTypes' import { ToolTip, toolTips } from 'lang/langHelpers' import { Selections, Selection } from 'lib/selections' import { cleanErrs, err } from 'lib/trap' @@ -68,12 +68,15 @@ export type ConstraintType = | 'setAngleBetween' function createCallWrapper( - a: ToolTip, + tooltip: ToolTip, val: [Expr, Expr] | Expr, tag?: Expr, valueUsedInTransform?: number ): ReturnType { - const args = [createFirstArg(a, val), createPipeSubstitution()] + const args = + tooltip === 'circle' + ? [] + : [createFirstArg(tooltip, val), createPipeSubstitution()] if (tag) { args.push(tag) } @@ -88,7 +91,7 @@ function createCallWrapper( } return { - callExp: createCallExpression(a, argsWOutErr), + callExp: createCallExpression(tooltip, argsWOutErr), valueUsedInTransform, } } @@ -154,9 +157,11 @@ function intersectCallWrapper({ export type TransformInfo = { tooltip: ToolTip createNode: (a: { - varValues: VarValues - varValA: Expr // x / angle - varValB: Expr // y / length or x y for angledLineOfXlength etc + // TODO: this type is used in a few places, should be extracted + inputs: { + varExpression: Expr + varDetails: VarValue + }[] referenceSegName: string tag?: Expr forceValueUsedInTransform?: Expr @@ -183,8 +188,13 @@ const xyLineSetLength = ? forceValueUsedInTransform : referenceSeg ? segRef - : args[0] - return createCallWrapper(xOrY, lineVal, tag, getArgLiteralVal(args[0])) + : args[0].varExpression + return createCallWrapper( + xOrY, + lineVal, + tag, + getArgLiteralVal(args[0].varExpression) + ) } const basicAngledLineCreateNode = @@ -193,25 +203,27 @@ const basicAngledLineCreateNode = valToForce: 'ang' | 'len' | 'none' = 'none', varValToUse: 'ang' | 'len' | 'none' = 'none' ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform, varValA, varValB }) => - (args, _, path) => { + ( + { referenceSegName, tag, forceValueUsedInTransform, inputs } //, varValA, varValB }) => + ) => + (args, path) => { const refAng = path ? getAngle(path?.from, path?.to) : 0 const nonForcedAng = varValToUse === 'ang' - ? varValA + ? inputs[0].varExpression : referenceSeg === 'ang' ? getClosesAngleDirection( - args[0], + args[0].varExpression, refAng, createSegAngle(referenceSegName) as BinaryPart ) - : args[0] + : args[0].varExpression const nonForcedLen = varValToUse === 'len' - ? varValB + ? inputs[1].varExpression : referenceSeg === 'len' ? createSegLen(referenceSegName) - : args[1] + : args[1].varExpression const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform return createCallWrapper( @@ -221,15 +233,17 @@ const basicAngledLineCreateNode = shouldForceLen ? forceValueUsedInTransform : nonForcedLen, ], tag, - getArgLiteralVal(valToForce === 'ang' ? args[0] : args[1]) + getArgLiteralVal( + valToForce === 'ang' ? args[0].varExpression : args[1].varExpression + ) ) } const angledLineAngleCreateNode: TransformInfo['createNode'] = - ({ referenceSegName, varValA, tag }) => + ({ referenceSegName, inputs, tag }) => () => createCallWrapper( 'angledLine', - [varValA, createSegLen(referenceSegName)], + [inputs[0].varExpression, createSegLen(referenceSegName)], tag ) @@ -301,9 +315,10 @@ const setHorzVertDistanceCreateNode = index = xOrY === 'x' ? 0 : 1 ): TransformInfo['createNode'] => ({ referenceSegName, tag, forceValueUsedInTransform }) => { - return (args, _, referencedSegment) => { + return (args, referencedSegment) => { const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0), + getArgLiteralVal(args?.[index].varExpression) - + (referencedSegment?.to?.[index] || 0), 2 ) let finalValue: Expr = createBinaryExpressionWithUnary([ @@ -316,7 +331,9 @@ const setHorzVertDistanceCreateNode = } return createCallWrapper( 'lineTo', - !index ? [finalValue, args[1]] : [args[0], finalValue], + !index + ? [finalValue, args[1].varExpression] + : [args[0].varExpression, finalValue], tag, valueUsedInTransform ) @@ -327,10 +344,11 @@ const setHorzVertDistanceForAngleLineCreateNode = xOrY: 'x' | 'y', index = xOrY === 'x' ? 0 : 1 ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform, varValA }) => { - return (args, _, referencedSegment) => { + ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => { + return (args, referencedSegment) => { const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[1]) - (referencedSegment?.to?.[index] || 0), + getArgLiteralVal(args?.[1].varExpression) - + (referencedSegment?.to?.[index] || 0), 2 ) const binExp = createBinaryExpressionWithUnary([ @@ -340,7 +358,7 @@ const setHorzVertDistanceForAngleLineCreateNode = ]) return createCallWrapper( xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', - [varValA, binExp], + [inputs[0].varExpression, binExp], tag, valueUsedInTransform ) @@ -355,7 +373,10 @@ const setAbsDistanceCreateNode = ): TransformInfo['createNode'] => ({ tag, forceValueUsedInTransform }) => (args) => { - const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[index]), 2) + const valueUsedInTransform = roundOff( + getArgLiteralVal(args?.[index].varExpression), + 2 + ) const val = (forceValueUsedInTransform as BinaryPart) || createLiteral(valueUsedInTransform) @@ -369,22 +390,25 @@ const setAbsDistanceCreateNode = } return createCallWrapper( 'lineTo', - !index ? [val, args[1]] : [args[0], val], + !index ? [val, args[1].varExpression] : [args[0].varExpression, val], tag, valueUsedInTransform ) } const setAbsDistanceForAngleLineCreateNode = (xOrY: 'x' | 'y'): TransformInfo['createNode'] => - ({ tag, forceValueUsedInTransform, varValA }) => { + ({ tag, forceValueUsedInTransform, inputs }) => { return (args) => { - const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[1]), 2) + const valueUsedInTransform = roundOff( + getArgLiteralVal(args?.[1].varExpression), + 2 + ) const val = (forceValueUsedInTransform as BinaryPart) || createLiteral(valueUsedInTransform) return createCallWrapper( xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', - [varValA, val], + [inputs[0].varExpression, val], tag, valueUsedInTransform ) @@ -394,10 +418,11 @@ const setAbsDistanceForAngleLineCreateNode = const setHorVertDistanceForXYLines = (xOrY: 'x' | 'y'): TransformInfo['createNode'] => ({ referenceSegName, tag, forceValueUsedInTransform }) => { - return (args, _, referencedSegment) => { + return (args, referencedSegment) => { const index = xOrY === 'x' ? 0 : 1 const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0), + getArgLiteralVal(args?.[index].varExpression) - + (referencedSegment?.to?.[index] || 0), 2 ) const makeBinExp = createBinaryExpressionWithUnary([ @@ -416,16 +441,18 @@ const setHorVertDistanceForXYLines = const setHorzVertDistanceConstraintLineCreateNode = (isX: boolean): TransformInfo['createNode'] => - ({ referenceSegName, tag, varValA, varValB }) => { - const varVal = (isX ? varValB : varValA) as BinaryPart + ({ referenceSegName, tag, inputs }) => { + const varVal = ( + isX ? inputs[0].varExpression : inputs[1].varExpression + ) as BinaryPart const varValBinExp = createBinaryExpressionWithUnary([ createLastSeg(!isX), varVal, ]) - return (args, _, referencedSegment) => { + return (args, referencedSegment) => { const makeBinExp = (index: 0 | 1) => { - const arg = getArgLiteralVal(args?.[index]) + const arg = getArgLiteralVal(args?.[index].varExpression) return createBinaryExpressionWithUnary([ createSegEnd(referenceSegName, isX), createLiteral( @@ -445,10 +472,15 @@ const setAngledIntersectLineForLines: TransformInfo['createNode'] = ({ referenceSegName, tag, forceValueUsedInTransform }) => (args) => { const valueUsedInTransform = roundOff( - args[1].type === 'Literal' ? Number(args[1].value) : 0, + args[1].varExpression.type === 'Literal' + ? Number(args[1].varExpression.value) + : 0, 2 ) - const angle = args[0].type === 'Literal' ? Number(args[0].value) : 0 + const angle = + args[0].varExpression.type === 'Literal' + ? Number(args[0].varExpression.value) + : 0 const varNamMap: { [key: number]: string } = { 0: 'ZERO', 90: 'QUARTER_TURN', @@ -470,16 +502,17 @@ const setAngledIntersectLineForLines: TransformInfo['createNode'] = } const setAngledIntersectForAngledLines: TransformInfo['createNode'] = - ({ referenceSegName, tag, forceValueUsedInTransform, varValA }) => + ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => (args) => { const valueUsedInTransform = roundOff( - args[1].type === 'Literal' ? Number(args[1].value) : 0, + args[1].varExpression.type === 'Literal' + ? Number(args[1].varExpression.value) + : 0, 2 ) - // const angle = args[0].type === 'Literal' ? Number(args[0].value) : 0 return intersectCallWrapper({ fnName: 'angledLineThatIntersects', - angleVal: varValA, + angleVal: inputs[0].varExpression, offsetVal: forceValueUsedInTransform || createLiteral(valueUsedInTransform), intersectTag: createIdentifier(referenceSegName), @@ -490,14 +523,16 @@ const setAngledIntersectForAngledLines: TransformInfo['createNode'] = const setAngleBetweenCreateNode = (tranformToType: 'none' | 'xAbs' | 'yAbs'): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform, varValA, varValB }) => { - return (args, _, referencedSegment) => { + ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => { + return (args, referencedSegment) => { const refAngle = referencedSegment ? getAngle(referencedSegment?.from, referencedSegment?.to) : 0 let valueUsedInTransform = roundOff( normaliseAngle( - (args[0].type === 'Literal' ? Number(args[0].value) : 0) - refAngle + (args[0].varExpression.type === 'Literal' + ? Number(args[0].varExpression.value) + : 0) - refAngle ) ) let firstHalfValue = createSegAngle(referenceSegName) as BinaryPart @@ -521,10 +556,10 @@ const setAngleBetweenCreateNode = ? 'angledLineToX' : 'angledLineToY', tranformToType === 'none' - ? [binExp, args[1]] + ? [binExp, args[1].varExpression] : tranformToType === 'xAbs' - ? [binExp, varValA] - : [binExp, varValB], + ? [binExp, inputs[0].varExpression] + : [binExp, inputs[1].varExpression], tag, valueUsedInTransform ) @@ -536,15 +571,15 @@ const transformMap: TransformMap = { xRelative: { equalLength: { tooltip: 'line', - createNode: ({ referenceSegName, varValA, tag }) => { + createNode: ({ referenceSegName, inputs, tag }) => { const [minVal, legLenVal] = getMinAndSegLenVals( referenceSegName, - varValA + inputs[0].varExpression ) return (args) => createCallWrapper( 'line', - [minVal, getSignedLeg(args[1], legLenVal)], + [minVal, getSignedLeg(args[1].varExpression, legLenVal)], tag ) }, @@ -552,9 +587,9 @@ const transformMap: TransformMap = { horizontal: { tooltip: 'xLine', createNode: - ({ varValA, tag }) => + ({ inputs, tag }) => () => - createCallWrapper('xLine', varValA, tag), + createCallWrapper('xLine', inputs[0].varExpression, tag), }, setVertDistance: { tooltip: 'lineTo', @@ -564,15 +599,15 @@ const transformMap: TransformMap = { yRelative: { equalLength: { tooltip: 'line', - createNode: ({ referenceSegName, varValB, tag }) => { + createNode: ({ referenceSegName, inputs, tag }) => { const [minVal, legLenVal] = getMinAndSegLenVals( referenceSegName, - varValB + inputs[1].varExpression ) return (args) => createCallWrapper( 'line', - [getSignedLeg(args[0], legLenVal), minVal], + [getSignedLeg(args[0].varExpression, legLenVal), minVal], tag ) }, @@ -580,9 +615,9 @@ const transformMap: TransformMap = { vertical: { tooltip: 'yLine', createNode: - ({ varValB, tag }) => + ({ inputs, tag }) => () => - createCallWrapper('yLine', varValB, tag), + createCallWrapper('yLine', inputs[1].varExpression, tag), }, setHorzDistance: { tooltip: 'lineTo', @@ -599,14 +634,14 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('xLine', args[0], tag), + createCallWrapper('xLine', args[0].varExpression, tag), }, vertical: { tooltip: 'yLine', createNode: ({ tag }) => (args) => - createCallWrapper('yLine', args[1], tag), + createCallWrapper('yLine', args[1].varExpression, tag), }, setHorzDistance: { tooltip: 'lineTo', @@ -657,33 +692,39 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('xLineTo', args[0], tag), + createCallWrapper('xLineTo', args[0].varExpression, tag), }, vertical: { tooltip: 'yLineTo', createNode: ({ tag }) => (args) => - createCallWrapper('yLineTo', args[1], tag), + createCallWrapper('yLineTo', args[1].varExpression, tag), }, }, xAbsolute: { equalLength: { tooltip: 'angledLineToX', createNode: - ({ referenceSegName, varValA, tag }) => + ({ referenceSegName, inputs, tag }) => (args) => { const angleToMatchLengthXCall = createCallExpression( 'angleToMatchLengthX', [ createIdentifier(referenceSegName), - varValA, + inputs[0].varExpression, createPipeSubstitution(), ] ) return createCallWrapper( 'angledLineToX', - [getAngleLengthSign(args[0], angleToMatchLengthXCall), varValA], + [ + getAngleLengthSign( + args[0].varExpression, + angleToMatchLengthXCall + ), + inputs[0].varExpression, + ], tag ) }, @@ -691,9 +732,9 @@ const transformMap: TransformMap = { horizontal: { tooltip: 'xLineTo', createNode: - ({ varValA, tag }) => + ({ inputs, tag }) => () => - createCallWrapper('xLineTo', varValA, tag), + createCallWrapper('xLineTo', inputs[0].varExpression, tag), }, setAngleBetween: { tooltip: 'angledLineToX', @@ -704,19 +745,25 @@ const transformMap: TransformMap = { equalLength: { tooltip: 'angledLineToY', createNode: - ({ referenceSegName, varValB, tag }) => + ({ referenceSegName, inputs, tag }) => (args) => { const angleToMatchLengthYCall = createCallExpression( 'angleToMatchLengthY', [ createIdentifier(referenceSegName), - varValB, + inputs[1].varExpression, createPipeSubstitution(), ] ) return createCallWrapper( 'angledLineToY', - [getAngleLengthSign(args[0], angleToMatchLengthYCall), varValB], + [ + getAngleLengthSign( + args[0].varExpression, + angleToMatchLengthYCall + ), + inputs[1].varExpression, + ], tag ) }, @@ -724,20 +771,23 @@ const transformMap: TransformMap = { vertical: { tooltip: 'yLineTo', createNode: - ({ varValB, tag }) => + ({ inputs, tag }) => () => - createCallWrapper('yLineTo', varValB, tag), + createCallWrapper('yLineTo', inputs[1].varExpression, tag), }, setAngle: { tooltip: 'angledLineToY', createNode: - ({ varValB, tag, forceValueUsedInTransform }) => + ({ inputs, tag, forceValueUsedInTransform }) => (args) => { return createCallWrapper( 'angledLineToY', - [forceValueUsedInTransform || args[0], varValB], + [ + forceValueUsedInTransform || args[0].varExpression, + inputs[1].varExpression, + ], tag, - getArgLiteralVal(args[0]) + getArgLiteralVal(args[0].varExpression) ) }, }, @@ -752,11 +802,11 @@ const transformMap: TransformMap = { equalLength: { tooltip: 'angledLine', createNode: - ({ referenceSegName, varValA, tag }) => + ({ referenceSegName, inputs, tag }) => () => createCallWrapper( 'angledLine', - [varValA, createSegLen(referenceSegName)], + [inputs[0].varExpression, createSegLen(referenceSegName)], tag ), }, @@ -799,39 +849,61 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLine', args[1], tag), + createCallWrapper('yLine', args[1].varExpression, tag), }, horizontal: { tooltip: 'xLine', createNode: ({ tag }) => (args) => - createCallWrapper('xLine', args[0], tag), + createCallWrapper('xLine', args[0].varExpression, tag), }, }, length: { vertical: { tooltip: 'yLine', createNode: - ({ varValB, tag }) => + ({ inputs, tag }) => ([arg0]) => { - const val = - arg0.type === 'Literal' && Number(arg0.value) < 0 - ? createUnaryExpression(varValB as BinaryPart) - : varValB - return createCallWrapper('yLine', val, tag) + const expr = inputs[1].varExpression + if ( + !( + arg0.varExpression.type === 'Literal' && + Number(arg0.varExpression.value) < 0 + ) + ) + return createCallWrapper('yLine', expr, tag) + if (isExprBinaryPart(expr)) + return createCallWrapper( + 'yLine', + createUnaryExpression(expr), + tag + ) + // TODO maybe should return error here instead + return createCallWrapper('yLine', expr, tag) }, }, horizontal: { tooltip: 'xLine', createNode: - ({ varValB, tag }) => + ({ inputs, tag }) => ([arg0]) => { - const val = - arg0.type === 'Literal' && Number(arg0.value) < 0 - ? createUnaryExpression(varValB as BinaryPart) - : varValB - return createCallWrapper('xLine', val, tag) + const expr = inputs[1].varExpression + if ( + !( + arg0.varExpression.type === 'Literal' && + Number(arg0.varExpression.value) < 0 + ) + ) + return createCallWrapper('xLine', expr, tag) + if (isExprBinaryPart(expr)) + return createCallWrapper( + 'xLine', + createUnaryExpression(expr), + tag + ) + // TODO maybe should return error here instead + return createCallWrapper('xLine', expr, tag) }, }, setAngle: { @@ -855,7 +927,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('xLine', args[0], tag), + createCallWrapper('xLine', args[0].varExpression, tag), }, }, angle: { @@ -867,15 +939,15 @@ const transformMap: TransformMap = { xRelative: { equalLength: { tooltip: 'angledLineOfXLength', - createNode: ({ referenceSegName, varValB, tag }) => { + createNode: ({ referenceSegName, inputs, tag }) => { const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, - varValB + inputs[0].varExpression ) return (args) => createCallWrapper( 'angledLineOfXLength', - [getLegAng(args[0], legAngle), minVal], + [getLegAng(args[0].varExpression, legAngle), minVal], tag ) }, @@ -883,13 +955,24 @@ const transformMap: TransformMap = { horizontal: { tooltip: 'xLine', createNode: - ({ varValB, tag }) => + ({ inputs, tag }) => ([arg0]) => { - const val = - arg0.type === 'Literal' && Number(arg0.value) < 0 - ? createUnaryExpression(varValB as BinaryPart) - : varValB - return createCallWrapper('xLine', val, tag) + const expr = inputs[1].varExpression + if ( + !( + arg0.varExpression.type === 'Literal' && + Number(arg0.varExpression.value) < 0 + ) + ) + return createCallWrapper('xLine', expr, tag) + if (isExprBinaryPart(expr)) + return createCallWrapper( + 'xLine', + createUnaryExpression(expr), + tag + ) + // TODO maybe should return error here instead + return createCallWrapper('xLine', expr, tag) }, }, }, @@ -905,7 +988,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLine', args[1], tag), + createCallWrapper('yLine', args[1].varExpression, tag), }, }, angle: { @@ -917,16 +1000,16 @@ const transformMap: TransformMap = { yRelative: { equalLength: { tooltip: 'angledLineOfYLength', - createNode: ({ referenceSegName, varValB, tag }) => { + createNode: ({ referenceSegName, inputs, tag }) => { const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, - varValB, + inputs[1].varExpression, 'legAngY' ) return (args) => createCallWrapper( 'angledLineOfXLength', - [getLegAng(args[0], legAngle), minVal], + [getLegAng(args[0].varExpression, legAngle), minVal], tag ) }, @@ -934,13 +1017,24 @@ const transformMap: TransformMap = { vertical: { tooltip: 'yLine', createNode: - ({ varValB, tag }) => + ({ inputs, tag }) => ([arg0]) => { - const val = - arg0.type === 'Literal' && Number(arg0.value) < 0 - ? createUnaryExpression(varValB as BinaryPart) - : varValB - return createCallWrapper('yLine', val, tag) + const expr = inputs[1].varExpression + if ( + !( + arg0.varExpression.type === 'Literal' && + Number(arg0.varExpression.value) < 0 + ) + ) + return createCallWrapper('yLine', expr, tag) + if (isExprBinaryPart(expr)) + return createCallWrapper( + 'yLine', + createUnaryExpression(expr), + tag + ) + // TODO maybe should return error here instead + return createCallWrapper('yLine', expr, tag) }, }, }, @@ -956,7 +1050,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('xLineTo', args[0], tag), + createCallWrapper('xLineTo', args[0].varExpression, tag), }, }, angle: { @@ -969,19 +1063,25 @@ const transformMap: TransformMap = { equalLength: { tooltip: 'angledLineToX', createNode: - ({ referenceSegName, varValB, tag }) => + ({ referenceSegName, inputs, tag }) => (args) => { const angleToMatchLengthXCall = createCallExpression( 'angleToMatchLengthX', [ createIdentifier(referenceSegName), - varValB, + inputs[1].varExpression, createPipeSubstitution(), ] ) return createCallWrapper( 'angledLineToX', - [getAngleLengthSign(args[0], angleToMatchLengthXCall), varValB], + [ + getAngleLengthSign( + args[0].varExpression, + angleToMatchLengthXCall + ), + inputs[1].varExpression, + ], tag ) }, @@ -989,9 +1089,9 @@ const transformMap: TransformMap = { horizontal: { tooltip: 'xLineTo', createNode: - ({ varValB, tag }) => + ({ inputs, tag }) => ([arg0]) => - createCallWrapper('xLineTo', varValB, tag), + createCallWrapper('xLineTo', inputs[1].varExpression, tag), }, }, }, @@ -1006,7 +1106,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLineTo', args[1], tag), + createCallWrapper('yLineTo', args[1].varExpression, tag), }, }, angle: { @@ -1019,19 +1119,25 @@ const transformMap: TransformMap = { equalLength: { tooltip: 'angledLineToY', createNode: - ({ referenceSegName, varValB, tag }) => + ({ referenceSegName, inputs, tag }) => (args) => { const angleToMatchLengthXCall = createCallExpression( 'angleToMatchLengthY', [ createIdentifier(referenceSegName), - varValB, + inputs[1].varExpression, createPipeSubstitution(), ] ) return createCallWrapper( 'angledLineToY', - [getAngleLengthSign(args[0], angleToMatchLengthXCall), varValB], + [ + getAngleLengthSign( + args[0].varExpression, + angleToMatchLengthXCall + ), + inputs[1].varExpression, + ], tag ) }, @@ -1039,9 +1145,9 @@ const transformMap: TransformMap = { vertical: { tooltip: 'yLineTo', createNode: - ({ varValB, tag }) => + ({ inputs, tag }) => () => - createCallWrapper('yLineTo', varValB, tag), + createCallWrapper('yLineTo', inputs[1].varExpression, tag), }, }, }, @@ -1052,10 +1158,19 @@ const transformMap: TransformMap = { createNode: ({ referenceSegName, tag }) => (arg) => { - const argVal = getArgLiteralVal(arg[0]) - const segLen = createSegLen(referenceSegName) as BinaryPart - const val = argVal > 0 ? segLen : createUnaryExpression(segLen) - return createCallWrapper('xLine', val, tag, argVal) + const argVal = getArgLiteralVal(arg[0].varExpression) + const segLen = createSegLen(referenceSegName) // as BinaryPart + if (argVal > 0) + return createCallWrapper('xLine', segLen, tag, argVal) + if (isExprBinaryPart(segLen)) + return createCallWrapper( + 'xLine', + createUnaryExpression(segLen), + tag, + argVal + ) + // should probably return error here instead + return createCallWrapper('xLine', segLen, tag, argVal) }, }, setHorzDistance: { @@ -1083,7 +1198,7 @@ const transformMap: TransformMap = { createNode: ({ referenceSegName, tag }) => (arg) => { - const argVal = getArgLiteralVal(arg[0]) + const argVal = getArgLiteralVal(arg[0].varExpression) let segLen = createSegLen(referenceSegName) as BinaryPart if (argVal < 0) segLen = createUnaryExpression(segLen) return createCallWrapper('yLine', segLen, tag, argVal) @@ -1167,14 +1282,18 @@ export function getRemoveConstraintsTransform( createNode: ({ tag, referenceSegName }) => (args) => { - return createCallWrapper('line', args, tag) + return createCallWrapper( + 'line', + [args[0].varExpression, args[1].varExpression], + tag + ) // The following commented changes values to hardcode, but keeps the line type the same, maybe that's useful? // if (name === 'angledLineThatIntersects') { // return intersectCallWrapper({ // fnName: name, - // angleVal: args[0], - // offsetVal: args[1], + // angleVal: args[0].varExpression, + // offsetVal: args[1].varExpression, // intersectTag: createIdentifier(referenceSegName), // tag, // }) @@ -1217,13 +1336,11 @@ export function getRemoveConstraintsTransform( export function removeSingleConstraint({ pathToCallExp, - arrayIndex, - objectProperty, + inputDetails, ast, }: { pathToCallExp: PathToNode - arrayIndex?: number - objectProperty?: string + inputDetails: SimplifiedVarValue ast: Program }): TransformInfo | false { const callExp = getNodeFromPath( @@ -1243,48 +1360,30 @@ export function removeSingleConstraint({ const transform: TransformInfo = { tooltip: callExp.node.callee.name as any, createNode: - ({ tag, referenceSegName, varValues }) => - (_, rawValues) => { - if (objectProperty) { - const expression: Parameters[0] = {} - varValues.forEach((varValue) => { - if ( - varValue.type !== 'objectProperty' && - varValue.type !== 'arrayOrObjItem' - ) - return - const literal = rawValues.find( - (rawValue) => - (rawValue.type === 'objectProperty' || - rawValue.type === 'arrayOrObjItem') && - rawValue.key === objectProperty - )?.value - const value = - (varValue.key === objectProperty && literal) || varValue.value - expression[varValue.key] = value - }) - const objExp = createObjectExpression(expression) - return createStdlibCallExpression( - callExp.node.callee.name as any, - objExp, - tag - ) - } - if (typeof arrayIndex === 'number') { - const values = varValues.map((varValue) => { + ({ tag, inputs }) => + (rawValues) => { + // inputs is the current values for each of the inputs + // rawValues is the rav 'literal' values equivalent to the inputs + // inputDetails is the one variable we're removing the constraint from + // So we should update the call expression to use the inputs, except for + // the inputDetails, input where we should use the rawValue(s) + + if (inputDetails.type === 'arrayItem') { + const values = inputs.map(({ varDetails: varValue }) => { if ( (varValue.type === 'arrayItem' || varValue.type === 'arrayOrObjItem') && - varValue.index === arrayIndex + varValue.index === inputDetails.index ) { const literal = rawValues.find( (rawValue) => - (rawValue.type === 'arrayItem' || - rawValue.type === 'arrayOrObjItem') && - rawValue.index === arrayIndex - )?.value + (rawValue.varDetails.type === 'arrayItem' || + rawValue.varDetails.type === 'arrayOrObjItem') && + rawValue.varDetails.index === inputDetails.index + )?.varDetails?.value return ( - (varValue.index === arrayIndex && literal) || varValue.value + (varValue.index === inputDetails.index && literal) || + varValue.value ) } return varValue.value @@ -1295,12 +1394,87 @@ export function removeSingleConstraint({ tag ) } - - // if (typeof arrayIndex !== 'number' || !objectProperty) must be single value input xLine, yLineTo etc + if ( + inputDetails.type === 'arrayInObject' || + inputDetails.type === 'objectProperty' + ) { + const arrayDetailsNameBetterLater: { + [key: string]: Parameters[0] + } = {} + const otherThing: Parameters[0] = {} + inputs.forEach(({ varDetails: varValue, varExpression }) => { + if ( + varValue.type !== 'objectProperty' && + varValue.type !== 'arrayOrObjItem' && + varValue.type !== 'objectPropertyArray' + ) + return + const rawLiteralArrayInObject = rawValues.find( + (rawValue) => + rawValue.varDetails.type === 'objectPropertyArray' && + rawValue.varDetails.key === inputDetails.key && + rawValue.varDetails.index === + (varValue.type === 'objectPropertyArray' + ? varValue.index + : -1) + ) + const rawLiteralObjProp = rawValues.find( + (rawValue) => + (rawValue.varDetails.type === 'objectProperty' || + rawValue.varDetails.type === 'arrayOrObjItem' || + rawValue.varDetails.type === 'objectPropertyArray') && + rawValue.varDetails.key === inputDetails.key + ) + if ( + inputDetails.type === 'arrayInObject' && + rawLiteralArrayInObject?.varDetails.type === + 'objectPropertyArray' && + rawLiteralArrayInObject?.varDetails.index === + inputDetails.index && + rawLiteralArrayInObject?.varDetails.key === inputDetails.key + ) { + if (!arrayDetailsNameBetterLater[varValue.key]) + arrayDetailsNameBetterLater[varValue.key] = [] + arrayDetailsNameBetterLater[inputDetails.key][ + inputDetails.index + ] = rawLiteralArrayInObject.varDetails.value + } else if ( + inputDetails.type === 'objectProperty' && + rawLiteralObjProp?.varDetails.type === 'objectProperty' && + rawLiteralObjProp?.varDetails.key === inputDetails.key && + varValue.key === inputDetails.key + ) { + otherThing[inputDetails.key] = rawLiteralObjProp.varDetails.value + } else if (varValue.type === 'objectPropertyArray') { + if (!arrayDetailsNameBetterLater[varValue.key]) + arrayDetailsNameBetterLater[varValue.key] = [] + arrayDetailsNameBetterLater[varValue.key][varValue.index] = + varExpression + } else if (varValue.type === 'objectProperty') { + otherThing[varValue.key] = varExpression + } + }) + const createObjParam: Parameters[0] = + {} + Object.entries(arrayDetailsNameBetterLater).forEach( + ([key, value]) => { + createObjParam[key] = createArrayExpression(value) + } + ) + const objExp = createObjectExpression({ + ...createObjParam, + ...otherThing, + }) + return createStdlibCallExpression( + callExp.node.callee.name as any, + objExp, + tag + ) + } return createCallWrapper( callExp.node.callee.name as any, - rawValues[0].value, + rawValues[0].varDetails.value, tag ) }, @@ -1583,8 +1757,6 @@ export function transformAstSketchLines({ const varDec = getNode('VariableDeclarator') if (err(varDec)) return varDec - const firstArg = getFirstArg(callExp.node) - if (err(firstArg)) return firstArg const callBackTag = callExp.node.arguments[2] const _referencedSegmentNameVal = callExp.node.arguments[0]?.type === 'ObjectExpression' && @@ -1597,10 +1769,11 @@ export function transformAstSketchLines({ _referencedSegmentNameVal.type === 'Identifier' && String(_referencedSegmentNameVal.name)) || '' - const { val } = firstArg - const [varValA, varValB] = Array.isArray(val) ? val : [val, val] - - const varValues: VarValues = [] + // TODO: this type is used in a few places, should be extracted + const inputs: { + varExpression: Expr + varDetails: VarValue + }[] = [] getConstraintInfo(callExp.node, '', _pathToNode).forEach((a) => { if ( @@ -1614,24 +1787,48 @@ export function transformAstSketchLines({ if (err(nodeMeta)) return if (a?.argPosition?.type === 'arrayItem') { - varValues.push({ - type: 'arrayItem', - index: a.argPosition.index, - value: nodeMeta.node, - argType: a.type, + inputs.push({ + varDetails: { + type: 'arrayItem', + index: a.argPosition.index, + value: nodeMeta.node, + argType: a.type, + argIndex: a.argPosition.argIndex, + }, + varExpression: nodeMeta.node, }) } else if (a?.argPosition?.type === 'objectProperty') { - varValues.push({ - type: 'objectProperty', - key: a.argPosition.key, - value: nodeMeta.node, - argType: a.type, + inputs.push({ + varDetails: { + type: 'objectProperty', + key: a.argPosition.key, + value: nodeMeta.node, + argType: a.type, + argIndex: a.argPosition.argIndex, + }, + varExpression: nodeMeta.node, }) } else if (a?.argPosition?.type === 'singleValue') { - varValues.push({ - type: 'singleValue', - argType: a.type, - value: nodeMeta.node, + inputs.push({ + varDetails: { + type: 'singleValue', + argType: a.type, + value: nodeMeta.node, + argIndex: a.argPosition.argIndex, + }, + varExpression: nodeMeta.node, + }) + } else if (a?.argPosition?.type === 'arrayInObject') { + inputs.push({ + varDetails: { + type: 'objectPropertyArray', + key: a.argPosition.key, + index: a.argPosition.index, + value: nodeMeta.node, + argType: a.type, + argIndex: 0, + }, + varExpression: nodeMeta.node, }) } }) @@ -1675,13 +1872,22 @@ export function transformAstSketchLines({ pathToNode: _pathToNode, referencedSegment, fnName: transformTo || (callExp.node.callee.name as ToolTip), - to, - from, + input: + seg.type === 'Circle' + ? { + type: 'arc-segment', + center: seg.center, + radius: seg.radius, + from, + } + : { + type: 'straight-segment', + to, + from, + }, createCallback: callBack({ referenceSegName: _referencedSegmentName, - varValues, - varValA, - varValB, + inputs, tag: callBackTag, forceValueUsedInTransform, }), @@ -1809,3 +2015,16 @@ export function isNotLiteralArrayOrStatic( (val.type === 'UnaryExpression' && val.argument.type !== 'Literal') ) } + +function isExprBinaryPart(expr: Expr): expr is BinaryPart { + if ( + expr.type === 'Literal' || + expr.type === 'Identifier' || + expr.type === 'BinaryExpression' || + expr.type === 'CallExpression' || + expr.type === 'UnaryExpression' || + expr.type === 'MemberExpression' + ) + return true + return false +} diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index 166d4cbeba..d640c08226 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -37,9 +37,36 @@ export interface AddTagInfo { pathToNode: PathToNode } -interface addCall extends ModifyAstBase { +/** Inputs for all straight segments, to and from are absolute values, as this gives a + * consistent base that can be converted to all of the line, angledLine, etc segment types + * One notable exception to "straight segment" is that tangentialArcTo is included in this + * Input type since it too only takes x-y values and is able to get extra info it needs + * to be tangential from the previous segment */ +interface StraightSegmentInput { + type: 'straight-segment' + from: [number, number] to: [number, number] +} + +/** Inputs for arcs, excluding tangentialArcTo for reasons explain in + * the @straightSegmentInput comment */ +interface ArcSegmentInput { + type: 'arc-segment' from: [number, number] + center: [number, number] + radius: number +} + +/** + * SegmentInputs is a union type that can be either a StraightSegmentInput or an ArcSegmentInput. + * + * - StraightSegmentInput: Represents a straight segment with a starting point (from) and an ending point (to). + * - ArcSegmentInput: Represents an arc segment with a starting point (from), a center point, and a radius. + */ +export type SegmentInputs = StraightSegmentInput | ArcSegmentInput + +interface addCall extends ModifyAstBase { + input: SegmentInputs referencedSegment?: Path replaceExisting?: boolean createCallback?: TransformCallback // TODO: #29 probably should not be optional @@ -48,35 +75,54 @@ interface addCall extends ModifyAstBase { } interface updateArgs extends ModifyAstBase { - from: [number, number] - to: [number, number] + input: SegmentInputs } -export type VarValueKeys = 'angle' | 'offset' | 'length' | 'to' | 'intersectTag' +export type VarValueKeys = + | 'angle' + | 'offset' + | 'length' + | 'to' + | 'intersectTag' + | 'radius' + | 'center' export interface SingleValueInput { type: 'singleValue' - argType: LineInputsType + argType: LineInputsType | 'radius' value: T + argIndex: number } export interface ArrayItemInput { type: 'arrayItem' index: 0 | 1 - argType: LineInputsType + argType: LineInputsType | 'radius' value: T + argIndex: number } export interface ObjectPropertyInput { type: 'objectProperty' key: VarValueKeys - argType: LineInputsType + argType: LineInputsType | 'radius' value: T + argIndex: number } export interface ArrayOrObjItemInput { type: 'arrayOrObjItem' key: VarValueKeys index: 0 | 1 - argType: LineInputsType + argType: LineInputsType | 'radius' + value: T + argIndex: number +} + +export interface ObjectPropertyArrayInput { + type: 'objectPropertyArray' + key: VarValueKeys + argType: LineInputsType | 'radius' + index: 0 | 1 value: T + argIndex: number } export type _VarValue = @@ -84,6 +130,7 @@ export type _VarValue = | ArrayItemInput | ObjectPropertyInput | ArrayOrObjItemInput + | ObjectPropertyArrayInput export type VarValue = _VarValue export type RawValue = _VarValue @@ -91,16 +138,25 @@ export type RawValue = _VarValue export type VarValues = Array export type RawValues = Array -type SimplifiedVarValue = +export type SimplifiedVarValue = | { type: 'singleValue' + argIndex: number + } + | { type: 'arrayItem'; index: 0 | 1; argIndex: number } + | { type: 'objectProperty'; key: VarValueKeys; argIndex: number } + | { + type: 'arrayInObject' + key: VarValueKeys + index: 0 | 1 } - | { type: 'arrayItem'; index: 0 | 1 } - | { type: 'objectProperty'; key: VarValueKeys } export type TransformCallback = ( - args: [Expr, Expr], - literalValues: RawValues, + // args: Array, + inputs: { + varExpression: Expr + varDetails: VarValue + }[], referencedSegment?: Path ) => { callExp: Expr @@ -109,7 +165,12 @@ export type TransformCallback = ( export interface ConstrainInfo { stdLibFnName: ToolTip - type: LineInputsType | 'vertical' | 'horizontal' | 'tangentialWithPrevious' + type: + | LineInputsType + | 'vertical' + | 'horizontal' + | 'tangentialWithPrevious' + | 'radius' isConstrained: boolean sourceRange: SourceRange pathToNode: PathToNode From 6753a9e9f85f083b3a2e5518b2aa5a3410c7c865 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Sat, 7 Sep 2024 15:40:59 +1000 Subject: [PATCH 14/41] make lint happy --- src/clientSideScene/sceneEntities.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index f4720309fb..b56df4832b 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -109,7 +109,6 @@ import { getThemeColorForThreeJs } from 'lib/theme' import { err, trap } from 'lib/trap' import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import { Point3d } from 'wasm-lib/kcl/bindings/Point3d' -import { c } from 'vite/dist/node/types.d-aGj9QkWt' type DraftSegment = 'line' | 'tangentialArcTo' From 998b194712f2337ff176fa30135be4f33bf74462 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 11:52:23 +1000 Subject: [PATCH 15/41] fix unit tests --- src/lang/modifyAst.test.ts | 17 ++++++++++-- src/lang/std/sketch.ts | 12 ++++---- src/lang/std/sketchcombos.ts | 54 +++++++++++++++++++----------------- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts index cf775effcd..724564fea2 100644 --- a/src/lang/modifyAst.test.ts +++ b/src/lang/modifyAst.test.ts @@ -641,13 +641,22 @@ describe('Testing removeSingleConstraintInfo', () => { const pathToNode = getNodePathFromSourceRange(ast, range) let argPosition: SimplifiedVarValue if (key === 'arrayIndex' && typeof value === 'number') { - argPosition = { type: 'arrayItem', argIndex: 0, index: value ? 0 : 1 } + argPosition = { + type: 'arrayItem', + index: value === 0 ? 0 : 1, + argIndex: 0, + } } else if (key === 'objectProperty' && typeof value === 'string') { argPosition = { type: 'objectProperty', key: value as VarValueKeys, argIndex: 0, } + } else if (key === '') { + argPosition = { + type: 'singleValue', + argIndex: 0, + } } else { throw new Error('argPosition is undefined') } @@ -688,7 +697,11 @@ describe('Testing removeSingleConstraintInfo', () => { ] let argPosition: SimplifiedVarValue if (key === 'arrayIndex' && typeof value === 'number') { - argPosition = { type: 'arrayItem', argIndex: 0, index: value ? 0 : 1 } + argPosition = { + type: 'arrayItem', + argIndex: 0, + index: value === 0 ? 0 : 1, + } } else if (key === 'objectProperty' && typeof value === 'string') { argPosition = { type: 'objectProperty', diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 74add0bdab..e03a691466 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -131,12 +131,10 @@ const constrainInfo = ( argPosition: g === 'singleValue' ? { type: 'singleValue', argIndex: 0 } - : g === 1 || g === 0 + : typeof g === 'number' ? { type: 'arrayItem', index: g, argIndex: 0 } : typeof g === 'string' ? { type: 'objectProperty', key: g, argIndex: 0 } - : g && 'type' in g - ? g : undefined, pathToNode: e, stdLibFnName: f, @@ -163,8 +161,12 @@ const commonConstraintInfoHelper = ( const firstArg = callExp.arguments?.[0] const isArr = firstArg.type === 'ArrayExpression' if (!isArr && firstArg.type !== 'ObjectExpression') return [] + const pipeExpressionIndex = pathToNode.findIndex( + (x) => x[1] === 'PipeExpression' + ) + const pathToBase = pathToNode.slice(0, pipeExpressionIndex + 2) const pathToArrayExpression: PathToNode = [ - ...pathToNode, + ...pathToBase, ['arguments', 'CallExpression'], [0, 'index'], isArr @@ -463,7 +465,7 @@ export const line: SketchLineHelper = { varExpression: createLiteral(roundOff(to[1] - from[1], 2)), varDetails: { type: 'arrayItem', - index: 0, + index: 1, argType: 'yRelative', value: createLiteral(roundOff(to[1] - from[1], 2)), argIndex: 0, diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index b9383bc391..48abdd5acc 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -442,9 +442,8 @@ const setHorVertDistanceForXYLines = const setHorzVertDistanceConstraintLineCreateNode = (isX: boolean): TransformInfo['createNode'] => ({ referenceSegName, tag, inputs }) => { - const varVal = ( - isX ? inputs[0].varExpression : inputs[1].varExpression - ) as BinaryPart + let varVal = isX ? inputs[1].varExpression : inputs[0].varExpression + varVal = isExprBinaryPart(varVal) ? varVal : createLiteral(0) const varValBinExp = createBinaryExpressionWithUnary([ createLastSeg(!isX), varVal, @@ -641,7 +640,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLine', args[1].varExpression, tag), + createCallWrapper('yLine', args[0].varExpression, tag), }, setHorzDistance: { tooltip: 'lineTo', @@ -699,7 +698,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLineTo', args[1].varExpression, tag), + createCallWrapper('yLineTo', args[0].varExpression, tag), }, }, xAbsolute: { @@ -849,7 +848,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLine', args[1].varExpression, tag), + createCallWrapper('yLine', args[0].varExpression, tag), }, horizontal: { tooltip: 'xLine', @@ -940,9 +939,12 @@ const transformMap: TransformMap = { equalLength: { tooltip: 'angledLineOfXLength', createNode: ({ referenceSegName, inputs, tag }) => { + const input = + inputs.find((a) => a.varDetails.argType === 'xRelative') || + inputs[0] const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, - inputs[0].varExpression + input.varExpression ) return (args) => createCallWrapper( @@ -988,7 +990,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLine', args[1].varExpression, tag), + createCallWrapper('yLine', args[0].varExpression, tag), }, }, angle: { @@ -1106,7 +1108,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLineTo', args[1].varExpression, tag), + createCallWrapper('yLineTo', args[0].varExpression, tag), }, }, angle: { @@ -1371,22 +1373,23 @@ export function removeSingleConstraint({ if (inputDetails.type === 'arrayItem') { const values = inputs.map(({ varDetails: varValue }) => { if ( - (varValue.type === 'arrayItem' || - varValue.type === 'arrayOrObjItem') && - varValue.index === inputDetails.index - ) { - const literal = rawValues.find( - (rawValue) => - (rawValue.varDetails.type === 'arrayItem' || - rawValue.varDetails.type === 'arrayOrObjItem') && - rawValue.varDetails.index === inputDetails.index - )?.varDetails?.value - return ( - (varValue.index === inputDetails.index && literal) || - varValue.value + !( + (varValue.type === 'arrayItem' || + varValue.type === 'arrayOrObjItem') && + varValue.index === inputDetails.index ) - } - return varValue.value + ) + return varValue.value + const literal = rawValues.find( + (rawValue) => + (rawValue.varDetails.type === 'arrayItem' || + rawValue.varDetails.type === 'arrayOrObjItem') && + rawValue.varDetails.index === inputDetails.index + )?.varDetails?.value + return ( + (varValue.index === inputDetails.index && literal) || + varValue.value + ) }) return createStdlibCallExpression( callExp.node.callee.name as any, @@ -1440,7 +1443,8 @@ export function removeSingleConstraint({ ] = rawLiteralArrayInObject.varDetails.value } else if ( inputDetails.type === 'objectProperty' && - rawLiteralObjProp?.varDetails.type === 'objectProperty' && + (rawLiteralObjProp?.varDetails.type === 'objectProperty' || + rawLiteralObjProp?.varDetails.type === 'arrayOrObjItem') && rawLiteralObjProp?.varDetails.key === inputDetails.key && varValue.key === inputDetails.key ) { From 59284ffa50ecdd4e481bbc9a0887a37cfd6a6d79 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 12:45:59 +1000 Subject: [PATCH 16/41] rely a little less on arbitary indexs --- src/lang/std/sketchcombos.ts | 56 +++++++++++++++++++++++------------- src/lang/std/stdTypes.ts | 10 ++++--- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index 48abdd5acc..fed713ea1e 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1,4 +1,4 @@ -import { SimplifiedVarValue, TransformCallback, VarValue } from './stdTypes' +import { SegmentInput, SimplifiedVarValue, TransformCallback } from './stdTypes' import { ToolTip, toolTips } from 'lang/langHelpers' import { Selections, Selection } from 'lib/selections' import { cleanErrs, err } from 'lib/trap' @@ -157,11 +157,7 @@ function intersectCallWrapper({ export type TransformInfo = { tooltip: ToolTip createNode: (a: { - // TODO: this type is used in a few places, should be extracted - inputs: { - varExpression: Expr - varDetails: VarValue - }[] + inputs: SegmentInput[] referenceSegName: string tag?: Expr forceValueUsedInTransform?: Expr @@ -640,7 +636,11 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLine', args[0].varExpression, tag), + createCallWrapper( + 'yLine', + getInputOfType(args, 'yRelative').varExpression, + tag + ), }, setHorzDistance: { tooltip: 'lineTo', @@ -698,7 +698,11 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLineTo', args[0].varExpression, tag), + createCallWrapper( + 'yLineTo', + getInputOfType(args, 'yAbsolute').varExpression, + tag + ), }, }, xAbsolute: { @@ -848,7 +852,11 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLine', args[0].varExpression, tag), + createCallWrapper( + 'yLine', + getInputOfType(args, 'yRelative').varExpression, + tag + ), }, horizontal: { tooltip: 'xLine', @@ -939,12 +947,9 @@ const transformMap: TransformMap = { equalLength: { tooltip: 'angledLineOfXLength', createNode: ({ referenceSegName, inputs, tag }) => { - const input = - inputs.find((a) => a.varDetails.argType === 'xRelative') || - inputs[0] const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, - input.varExpression + getInputOfType(inputs, 'xRelative').varExpression ) return (args) => createCallWrapper( @@ -990,7 +995,11 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLine', args[0].varExpression, tag), + createCallWrapper( + 'yLine', + getInputOfType(args, 'yRelative').varExpression, + tag + ), }, }, angle: { @@ -1108,7 +1117,11 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('yLineTo', args[0].varExpression, tag), + createCallWrapper( + 'yLineTo', + getInputOfType(args, 'yAbsolute').varExpression, + tag + ), }, }, angle: { @@ -1773,11 +1786,7 @@ export function transformAstSketchLines({ _referencedSegmentNameVal.type === 'Identifier' && String(_referencedSegmentNameVal.name)) || '' - // TODO: this type is used in a few places, should be extracted - const inputs: { - varExpression: Expr - varDetails: VarValue - }[] = [] + const inputs: SegmentInput[] = [] getConstraintInfo(callExp.node, '', _pathToNode).forEach((a) => { if ( @@ -2032,3 +2041,10 @@ function isExprBinaryPart(expr: Expr): expr is BinaryPart { return true return false } + +function getInputOfType( + a: SegmentInput[], + b: LineInputsType | 'radius' +): SegmentInput { + return a.find(({ varDetails }) => varDetails.argType === b) || a[0] +} diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index d640c08226..0868891e20 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -151,12 +151,14 @@ export type SimplifiedVarValue = index: 0 | 1 } +export interface SegmentInput { + varExpression: Expr + varDetails: VarValue +} + export type TransformCallback = ( // args: Array, - inputs: { - varExpression: Expr - varDetails: VarValue - }[], + inputs: SegmentInput[], referencedSegment?: Path ) => { callExp: Expr From 57a460f57a287bffb9c31897e6a53311ad965d91 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 13:23:34 +1000 Subject: [PATCH 17/41] update samples --- .../tests/executor/inputs/cylinder.kcl | 2 +- .../executor/inputs/fillet-and-shell.kcl | 4 ++-- .../focusrite_scarlett_mounting_braket.kcl | 23 ++++++++++++------- .../tests/executor/inputs/global-tags.kcl | 22 +++++++++++------- .../tests/executor/inputs/helix_ccw.kcl | 2 +- .../tests/executor/inputs/helix_defaults.kcl | 2 +- .../helix_defaults_negative_extrude.kcl | 2 +- .../executor/inputs/helix_with_length.kcl | 2 +- src/wasm-lib/tests/executor/inputs/lego.kcl | 4 ++-- .../tests/executor/inputs/pattern_vase.kcl | 2 +- .../executor/inputs/server-rack-heavy.kcl | 18 +++++++-------- .../executor/inputs/server-rack-lite.kcl | 18 +++++++-------- .../inputs/sketch_on_face_circle_tagged.kcl | 2 +- .../tests/executor/inputs/slow_lego.kcl.tmpl | 4 ++-- 14 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/wasm-lib/tests/executor/inputs/cylinder.kcl b/src/wasm-lib/tests/executor/inputs/cylinder.kcl index 6304c62133..f3800be1c7 100644 --- a/src/wasm-lib/tests/executor/inputs/cylinder.kcl +++ b/src/wasm-lib/tests/executor/inputs/cylinder.kcl @@ -1,3 +1,3 @@ const cylinder = startSketchOn('XY') - |> circle([0,0], 22, %) + |> circle({ center: [0, 0], radius: 22 }, %) |> extrude(14, %) diff --git a/src/wasm-lib/tests/executor/inputs/fillet-and-shell.kcl b/src/wasm-lib/tests/executor/inputs/fillet-and-shell.kcl index cd7dc2c5db..295f959b09 100644 --- a/src/wasm-lib/tests/executor/inputs/fillet-and-shell.kcl +++ b/src/wasm-lib/tests/executor/inputs/fillet-and-shell.kcl @@ -61,8 +61,8 @@ const case = startSketchOn('XY') fn m25Screw = (x, y, height) => { const screw = startSketchOn("XY") |> startProfileAt([0, 0], %) - |> circle([x, y], 2.5, %) - |> hole(circle([x, y], 1.25, %), %) + |> circle({ center: [x, y], radius: 2.5 }, %) + |> hole(circle({ center: [x, y], radius: 1.25 }, %), %) |> extrude(height, %) return screw } diff --git a/src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl b/src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl index c6d2b9a0c3..047127d433 100644 --- a/src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl +++ b/src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl @@ -1,6 +1,7 @@ // A mounting bracket for the Focusrite Scarlett Solo audio interface // This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material + // define constants in mm const radius = 6.0 const width = 144.0 @@ -79,10 +80,13 @@ const tabsR = startSketchOn(tabPlane) |> line([0, -tabLength / 3 * 2], %, $edge12) |> line([-tabWidth, -tabLength / 3], %, $edge13) |> close(%, $edge14) - |> hole(circle([ - width / 2 + thk + tabWidth / 2, - length / 2 + thk - (tabLength / (3 / 2)) - ], holeDiam / 2, %), %) + |> hole(circle({ + center: [ + width / 2 + thk + tabWidth / 2, + length / 2 + thk - (tabLength / (3 / 2)) + ], + radius: holeDiam / 2 + }, %), %) |> extrude(-tabThk, %) |> fillet({ radius: holeDiam / 2, @@ -104,10 +108,13 @@ const tabsL = startSketchOn(tabPlane) |> line([0, -tabLength / 3 * 2], %, $edge22) |> line([tabWidth, -tabLength / 3], %, $edge23) |> close(%, $edge24) - |> hole(circle([ - -width / 2 - thk - (tabWidth / 2), - length / 2 + thk - (tabLength / (3 / 2)) - ], holeDiam / 2, %), %) + |> hole(circle({ + center: [ + -width / 2 - thk - (tabWidth / 2), + length / 2 + thk - (tabLength / (3 / 2)) + ], + radius: holeDiam / 2 + }, %), %) |> extrude(-tabThk, %) |> fillet({ radius: holeDiam / 2, diff --git a/src/wasm-lib/tests/executor/inputs/global-tags.kcl b/src/wasm-lib/tests/executor/inputs/global-tags.kcl index 9781831705..31dcd9105a 100644 --- a/src/wasm-lib/tests/executor/inputs/global-tags.kcl +++ b/src/wasm-lib/tests/executor/inputs/global-tags.kcl @@ -80,10 +80,13 @@ const tabsR = startSketchOn(tabPlane) |> line([0, -tabLength / 3 * 2], %, $edge12) |> line([-tabWidth, -tabLength / 3], %, $edge13) |> close(%, $edge14) - |> hole(circle([ - width / 2 + thk + tabWidth / 2, - length / 2 + thk - (tabLength / (3 / 2)) - ], holeDiam / 2, %), %) + |> hole(circle({ + center: [ + width / 2 + thk + tabWidth / 2, + length / 2 + thk - (tabLength / (3 / 2)) + ], + radius: holeDiam / 2 + }, %), %) |> extrude(-tabThk, %) |> fillet({ radius: holeDiam / 2, @@ -105,10 +108,13 @@ const tabsL = startSketchOn(tabPlane) |> line([0, -tabLength / 3 * 2], %, $edge22) |> line([tabWidth, -tabLength / 3], %, $edge23) |> close(%, $edge24) - |> hole(circle([ - -width / 2 - thk - (tabWidth / 2), - length / 2 + thk - (tabLength / (3 / 2)) - ], holeDiam / 2, %), %) + |> hole(circle({ + center: [ + -width / 2 - thk - (tabWidth / 2), + length / 2 + thk - (tabLength / (3 / 2)) + ], + radius: holeDiam / 2 + }, %), %) |> extrude(-tabThk, %) |> fillet({ radius: holeDiam / 2, diff --git a/src/wasm-lib/tests/executor/inputs/helix_ccw.kcl b/src/wasm-lib/tests/executor/inputs/helix_ccw.kcl index 6975ab7b93..76f31c6df3 100644 --- a/src/wasm-lib/tests/executor/inputs/helix_ccw.kcl +++ b/src/wasm-lib/tests/executor/inputs/helix_ccw.kcl @@ -1,4 +1,4 @@ const part001 = startSketchOn('XY') - |> circle([5, 5], 10, %) + |> circle({ center: [5, 5], radius: 10 }, %) |> extrude(10, %) |> helix({revolutions: 16, angle_start: 0, ccw: true}, %) diff --git a/src/wasm-lib/tests/executor/inputs/helix_defaults.kcl b/src/wasm-lib/tests/executor/inputs/helix_defaults.kcl index e85eab8de5..0c66510377 100644 --- a/src/wasm-lib/tests/executor/inputs/helix_defaults.kcl +++ b/src/wasm-lib/tests/executor/inputs/helix_defaults.kcl @@ -1,4 +1,4 @@ const part001 = startSketchOn('XY') - |> circle([5, 5], 10, %) + |> circle({ center: [5, 5], radius: 10 }, %) |> extrude(10, %) |> helix({revolutions: 16, angle_start: 0}, %) diff --git a/src/wasm-lib/tests/executor/inputs/helix_defaults_negative_extrude.kcl b/src/wasm-lib/tests/executor/inputs/helix_defaults_negative_extrude.kcl index 9a2f3efc1b..ab26043c8e 100644 --- a/src/wasm-lib/tests/executor/inputs/helix_defaults_negative_extrude.kcl +++ b/src/wasm-lib/tests/executor/inputs/helix_defaults_negative_extrude.kcl @@ -1,4 +1,4 @@ const part001 = startSketchOn('XY') - |> circle([5, 5], 10, %) + |> circle({ center: [5, 5], radius: 10 }, %) |> extrude(-10, %) |> helix({revolutions: 16, angle_start: 0}, %) diff --git a/src/wasm-lib/tests/executor/inputs/helix_with_length.kcl b/src/wasm-lib/tests/executor/inputs/helix_with_length.kcl index 1847d4ad05..7818e3c5d8 100644 --- a/src/wasm-lib/tests/executor/inputs/helix_with_length.kcl +++ b/src/wasm-lib/tests/executor/inputs/helix_with_length.kcl @@ -1,4 +1,4 @@ const part001 = startSketchOn('XY') - |> circle([5, 5], 10, %) + |> circle({ center: [5, 5], radius: 10 }, %) |> extrude(10, %) |> helix({revolutions: 16, angle_start: 0, length: 3}, %) diff --git a/src/wasm-lib/tests/executor/inputs/lego.kcl b/src/wasm-lib/tests/executor/inputs/lego.kcl index 26099987e9..88a5b8d799 100644 --- a/src/wasm-lib/tests/executor/inputs/lego.kcl +++ b/src/wasm-lib/tests/executor/inputs/lego.kcl @@ -39,10 +39,10 @@ const shellExtrude = startSketchOn(s, "start") |> extrude(-(height - t), %) const peg = startSketchOn(s, "end") - |> circle([ + |> circle({ center: [ -(total_width / 2 - wSegments), -(total_length / 2 - lSegments) - ], bumpDiam / 2, %) + ], radius: bumpDiam / 2 }, %) |> patternLinear2d({ axis: [1, 0], repetitions: 5, diff --git a/src/wasm-lib/tests/executor/inputs/pattern_vase.kcl b/src/wasm-lib/tests/executor/inputs/pattern_vase.kcl index 24f52bffba..c6edfd9862 100644 --- a/src/wasm-lib/tests/executor/inputs/pattern_vase.kcl +++ b/src/wasm-lib/tests/executor/inputs/pattern_vase.kcl @@ -14,7 +14,7 @@ fn transform = (replicaId) => { // Each layer is just a pretty thin cylinder with a fillet. fn layer = () => { return startSketchOn("XY") // or some other plane idk - |> circle([0, 0], 1, %, $tag1) + |> circle({ center: [0, 0], radius: 1 }, %, $tag1) |> extrude(h, %) // |> fillet({ // radius: h / 2.01, diff --git a/src/wasm-lib/tests/executor/inputs/server-rack-heavy.kcl b/src/wasm-lib/tests/executor/inputs/server-rack-heavy.kcl index b16c9c075d..1170bc2940 100644 --- a/src/wasm-lib/tests/executor/inputs/server-rack-heavy.kcl +++ b/src/wasm-lib/tests/executor/inputs/server-rack-heavy.kcl @@ -30,22 +30,22 @@ fn caster = (originStart) => { |> xLine(-3.543, %) |> lineTo([profileStartX(%), profileStartY(%)], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ (3.543 - 2.756) / 2, (3.543 - 2.756) / 2 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4}, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2, (3.543 - 2.756) / 2 + 2.756 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 + 2.756 - ], 8.8 / 2 / 25.4, %), %) + ], radius: 8.8 / 2 / 25.4 }, %), %) |> extrude(-.25, %) const sketch002c = startSketchOn(sketch001c, 'START') @@ -71,7 +71,7 @@ fn caster = (originStart) => { } } const sketch003c = startSketchOn(plane002c) - |> circle([0, 1.2], 2.48 / 2, %) + |> circle({ center: [0, 1.2], radius 2.48 / 2 }, %) const examplec = extrude(-1 - (3 / 16), sketch003c) return examplec } diff --git a/src/wasm-lib/tests/executor/inputs/server-rack-lite.kcl b/src/wasm-lib/tests/executor/inputs/server-rack-lite.kcl index 46f0ca6509..4a0b4c8640 100644 --- a/src/wasm-lib/tests/executor/inputs/server-rack-lite.kcl +++ b/src/wasm-lib/tests/executor/inputs/server-rack-lite.kcl @@ -28,22 +28,22 @@ fn caster = (originStart) => { |> xLine(-3.543, %) |> lineTo([profileStartX(%), profileStartY(%)], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ (3.543 - 2.756) / 2, (3.543 - 2.756) / 2 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2, (3.543 - 2.756) / 2 + 2.756 - ], 8.8 / 2 / 25.4, %), %) - |> hole(circle([ + ], radius: 8.8 / 2 / 25.4 }, %), %) + |> hole(circle({ center: [ (3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 + 2.756 - ], 8.8 / 2 / 25.4, %), %) + ], radius: 8.8 / 2 / 25.4 }, %), %) |> extrude(-.25, %) const sketch002c = startSketchOn(sketch001c, 'START') @@ -69,7 +69,7 @@ fn caster = (originStart) => { } } const sketch003c = startSketchOn(plane002c) - |> circle([0, 1.2], 2.48 / 2, %) + |> circle({ center: [0, 1.2], radisu: 2.48 / 2 }, %) const examplec = extrude(-1 - (3 / 16), sketch003c) return examplec } diff --git a/src/wasm-lib/tests/executor/inputs/sketch_on_face_circle_tagged.kcl b/src/wasm-lib/tests/executor/inputs/sketch_on_face_circle_tagged.kcl index c4df4848b6..9b37a5d1ef 100644 --- a/src/wasm-lib/tests/executor/inputs/sketch_on_face_circle_tagged.kcl +++ b/src/wasm-lib/tests/executor/inputs/sketch_on_face_circle_tagged.kcl @@ -12,5 +12,5 @@ const part001 = cube([0,0], 20) |> extrude(20, %) const part002 = startSketchOn(part001, "end") - |> circle([0, 0], 5, %, $myCircle) + |> circle({ center: [0, 0], radisu: 5 }, %, $myCircle) |> extrude(5, %) diff --git a/src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl b/src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl index a8aae57f25..8c1b291562 100644 --- a/src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl +++ b/src/wasm-lib/tests/executor/inputs/slow_lego.kcl.tmpl @@ -62,10 +62,10 @@ fn tr = (i) => { // Create the pegs on the top of the base const totalBumps = (wbumps * lbumps)-1 const peg = startSketchOn(s, 'end') - |> circle([ + |> circle({ center: [ -(pitch*(wbumps-1)/2), -(pitch*(lbumps-1)/2) - ], bumpDiam / 2, %) + ], radius: bumpDiam / 2 }, %) |> patternLinear2d({ axis: [1, 0], repetitions: wbumps-1, From a59de4efa374e7d20f691d82f2f6c667adaecec1 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 13:43:52 +1000 Subject: [PATCH 18/41] hopefully fix e2e tests --- e2e/playwright/editor-tests.spec.ts | 2 +- e2e/playwright/sketch-tests.spec.ts | 8 +++++--- e2e/playwright/storageStates.ts | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/e2e/playwright/editor-tests.spec.ts b/e2e/playwright/editor-tests.spec.ts index 82c5e84886..f33e5d190f 100644 --- a/e2e/playwright/editor-tests.spec.ts +++ b/e2e/playwright/editor-tests.spec.ts @@ -558,7 +558,7 @@ test.describe('Editor tests', () => { await page.keyboard.press('ArrowDown') await page.keyboard.press('Enter') await page.keyboard.type(`const extrusion = startSketchOn('XY') - |> circle([0, 0], dia/2, %) + |> circle({ center: [0, 0], radius: dia/2 }, %) |> hole(squareHole(length, width, height), %) |> extrude(height, %)`) diff --git a/e2e/playwright/sketch-tests.spec.ts b/e2e/playwright/sketch-tests.spec.ts index 0d15c7dd14..d412d03d0d 100644 --- a/e2e/playwright/sketch-tests.spec.ts +++ b/e2e/playwright/sketch-tests.spec.ts @@ -149,14 +149,16 @@ test.describe('Sketch tests', () => { await page.getByRole('button', { name: 'line Line', exact: true }).click() await page.waitForTimeout(100) - await page.mouse.click(700, 200) + await expect(async () => { + await page.mouse.click(700, 200) - await expect.poll(u.normalisedEditorCode) - .toBe(`const sketch001 = startSketchOn('XZ') + await expect.poll(u.normalisedEditorCode, { timeout: 1000 }) + .toBe(`const sketch001 = startSketchOn('XZ') |> startProfileAt([12.34, -12.34], %) |> line([-12.34, 12.34], %) `) + }).toPass({ timeout: 40_000, intervals: [1_000] }) }) test('Can exit selection of face', async ({ page }) => { // Load the app with the code panes diff --git a/e2e/playwright/storageStates.ts b/e2e/playwright/storageStates.ts index 6cc85bfdce..61259fbcbb 100644 --- a/e2e/playwright/storageStates.ts +++ b/e2e/playwright/storageStates.ts @@ -365,10 +365,10 @@ const box = startSketchOn('XY') svg(startSketchOn(keychain, 'end'), [-33, 32], -thickness) startSketchOn(keychain, 'end') - |> circle([ + |> circle({ center: [ width / 2, height - (keychainHoleSize + 1.5) - ], keychainHoleSize, %) + ], radius: keychainHoleSize }, %) |> extrude(-thickness, %)` export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `const thing = 1` From b3c4aec8db0294575514a48c75f8eefdf75161c2 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 14:36:00 +1000 Subject: [PATCH 19/41] try increas playwright timeout --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 21cfac5ade..879269cb00 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -262,7 +262,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-14] - timeout-minutes: 40 + timeout-minutes: 60 runs-on: ${{ matrix.os }} needs: check-rust-changes steps: From 48d347b4de2a1eb6d99c9e1922ea1c032ae27b8d Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 16:18:39 +1000 Subject: [PATCH 20/41] add e2e tests --- e2e/playwright/sketch-tests.spec.ts | 86 +++++++++++++++++++ e2e/playwright/snapshot-tests.spec.ts | 58 +++++++++++++ .../testing-segment-overlays.spec.ts | 76 ++++++++++++++++ src/components/ModelingMachineProvider.tsx | 3 +- src/lang/std/sketch.ts | 6 +- 5 files changed, 223 insertions(+), 6 deletions(-) diff --git a/e2e/playwright/sketch-tests.spec.ts b/e2e/playwright/sketch-tests.spec.ts index d412d03d0d..1b90cbad0e 100644 --- a/e2e/playwright/sketch-tests.spec.ts +++ b/e2e/playwright/sketch-tests.spec.ts @@ -346,6 +346,92 @@ test.describe('Sketch tests', () => { }) }) + test('Can edit a circle center and radius by dragging its handles', async ({ + page, + }) => { + const u = await getUtils(page) + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const sketch001 = startSketchOn('XZ') + |> circle({ center: [4.61, -5.01], radius: 8 }, %)` + ) + }) + + await page.setViewportSize({ width: 1200, height: 500 }) + + await u.waitForAuthSkipAppStart() + await expect( + page.getByRole('button', { name: 'Start Sketch' }) + ).not.toBeDisabled() + + await page.waitForTimeout(100) + await u.openAndClearDebugPanel() + await u.sendCustomCmd({ + type: 'modeling_cmd_req', + cmd_id: uuidv4(), + cmd: { + type: 'default_camera_look_at', + vantage: { x: 0, y: -1250, z: 580 }, + center: { x: 0, y: 0, z: 0 }, + up: { x: 0, y: 0, z: 1 }, + }, + }) + await page.waitForTimeout(100) + await u.sendCustomCmd({ + type: 'modeling_cmd_req', + cmd_id: uuidv4(), + cmd: { + type: 'default_camera_get_settings', + }, + }) + await page.waitForTimeout(100) + + const startPX = [667, 325] + + const dragPX = 40 + + await page + .getByText('circle({ center: [4.61, -5.01], radius: 8 }, %)') + .click() + await expect( + page.getByRole('button', { name: 'Edit Sketch' }) + ).toBeVisible() + await page.getByRole('button', { name: 'Edit Sketch' }).click() + await page.waitForTimeout(400) + let prevContent = await page.locator('.cm-content').innerText() + + await expect(page.getByTestId('segment-overlay')).toHaveCount(1) + + await test.step('drag circle center handle', async () => { + await page.dragAndDrop('#stream', '#stream', { + sourcePosition: { x: startPX[0], y: startPX[1] }, + targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX }, + }) + await page.waitForTimeout(100) + await expect(page.locator('.cm-content')).not.toHaveText(prevContent) + prevContent = await page.locator('.cm-content').innerText() + }) + + await test.step('drag circle radius handle', async () => { + await page.waitForTimeout(100) + + const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]') + await page.waitForTimeout(100) + await page.dragAndDrop('#stream', '#stream', { + sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y }, + targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX }, + }) + await expect(page.locator('.cm-content')).not.toHaveText(prevContent) + prevContent = await page.locator('.cm-content').innerText() + }) + + // expect the code to have changed + await expect(page.locator('.cm-content')) + .toHaveText(`const sketch001 = startSketchOn('XZ') + |> circle({ center: [7.26, -2.37], radius: 11.79 }, %) +`) + }) test('Can edit a sketch that has been extruded in the same pipe', async ({ page, }) => { diff --git a/e2e/playwright/snapshot-tests.spec.ts b/e2e/playwright/snapshot-tests.spec.ts index 9aaeda388b..c153678941 100644 --- a/e2e/playwright/snapshot-tests.spec.ts +++ b/e2e/playwright/snapshot-tests.spec.ts @@ -532,6 +532,64 @@ test( }) } ) +test( + 'Draft circle should look right', + { tag: '@snapshot' }, + async ({ page, context }) => { + // FIXME: Skip on macos its being weird. + // test.skip(process.platform === 'darwin', 'Skip on macos') + + const u = await getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + const PUR = 400 / 37.5 //pixeltoUnitRatio + + await u.waitForAuthSkipAppStart() + await u.openDebugPanel() + + await expect( + page.getByRole('button', { name: 'Start Sketch' }) + ).not.toBeDisabled() + await expect( + page.getByRole('button', { name: 'Start Sketch' }) + ).toBeVisible() + + // click on "Start Sketch" button + await u.clearCommandLogs() + await u.doAndWaitForImageDiff( + () => page.getByRole('button', { name: 'Start Sketch' }).click(), + 200 + ) + + // select a plane + await page.mouse.click(700, 200) + + await expect(page.locator('.cm-content')).toHaveText( + `const sketch001 = startSketchOn('XZ')` + ) + + await page.waitForTimeout(500) // TODO detect animation ending, or disable animation + await u.closeDebugPanel() + + const startXPx = 600 + + // Equip the rectangle tool + // await page.getByRole('button', { name: 'line Line', exact: true }).click() + await page.getByTestId('circle-center').click() + + // Draw the rectangle + await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) + await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 }) + + // Ensure the draft rectangle looks the same as it usually does + await expect(page).toHaveScreenshot({ + maxDiffPixels: 100, + }) + await expect(page.locator('.cm-content')).toHaveText( + `const sketch001 = startSketchOn('XZ') + |> circle({ center: [14.44, -2.44], radius: 1 }, %)` + ) + } +) test.describe( 'Client side scene scale should match engine scale', diff --git a/e2e/playwright/testing-segment-overlays.spec.ts b/e2e/playwright/testing-segment-overlays.spec.ts index 2a08b2a9cf..a3faa5b35d 100644 --- a/e2e/playwright/testing-segment-overlays.spec.ts +++ b/e2e/playwright/testing-segment-overlays.spec.ts @@ -40,6 +40,7 @@ test.describe('Testing segment overlays', () => { | 'horizontal' | 'vertical' | 'tangentialWithPrevious' + | 'radius' | LineInputsType expectBeforeUnconstrained: string expectAfterUnconstrained: string @@ -121,6 +122,7 @@ test.describe('Testing segment overlays', () => { | 'horizontal' | 'vertical' | 'tangentialWithPrevious' + | 'radius' | LineInputsType expectBeforeUnconstrained: string expectAfterUnconstrained: string @@ -774,6 +776,80 @@ const part001 = startSketchOn('XZ') locator: '[data-overlay-toolbar-index="12"]', }) }) + test('for segment [circle]', async ({ page }) => { + await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `const part001 = startSketchOn('XZ') + |> circle({ center: [1 + 0, 0], radius: 8 }, %) + ` + ) + localStorage.setItem('disableAxis', 'true') + }) + const u = await getUtils(page) + await page.setViewportSize({ width: 1200, height: 500 }) + + await u.waitForAuthSkipAppStart() + + // wait for execution done + await u.openDebugPanel() + await u.expectCmdLog('[data-message-type="execution-done"]') + await u.closeDebugPanel() + + await page + .getByText('circle({ center: [1 + 0, 0], radius: 8 }, %)') + .click() + await page.waitForTimeout(100) + await page.getByRole('button', { name: 'Edit Sketch' }).click() + await page.waitForTimeout(500) + + await expect(page.getByTestId('segment-overlay')).toHaveCount(1) + + const clickUnconstrained = _clickUnconstrained(page) + const clickConstrained = _clickConstrained(page) + + const hoverPos = { x: 789, y: 114 } as const + let ang = await u.getAngle('[data-overlay-index="0"]') + console.log('angl', ang) + console.log('circle center x') + await clickConstrained({ + hoverPos, + constraintType: 'xAbsolute', + expectBeforeUnconstrained: + 'circle({ center: [1 + 0, 0], radius: 8 }, %)', + expectAfterUnconstrained: 'circle({ center: [1, 0], radius: 8 }, %)', + expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + ang: ang + 105, + steps: 6, + locator: '[data-overlay-toolbar-index="0"]', + }) + console.log('circle center y') + await clickUnconstrained({ + hoverPos, + constraintType: 'yAbsolute', + expectBeforeUnconstrained: + 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + expectAfterUnconstrained: + 'circle({ center: [xAbs001, yAbs001], radius: 8 }, %)', + expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + ang: ang + 105, + steps: 10, + locator: '[data-overlay-toolbar-index="0"]', + }) + console.log('circle radius') + await clickUnconstrained({ + hoverPos, + constraintType: 'radius', + expectBeforeUnconstrained: + 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + expectAfterUnconstrained: + 'circle({ center: [xAbs001, 0], radius: radius001 }, %)', + expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)', + ang: ang + 105, + steps: 10, + locator: '[data-overlay-toolbar-index="0"]', + }) + }) }) test.describe('Testing deleting a segment', () => { const _deleteSegmentSequence = diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 71b5941d4b..6807b6f896 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -210,7 +210,8 @@ export const ModelingMachineProvider = ({ pathToNodeString, }, }) - }, 800) as unknown as number + // overlay timeout is 1s + }, 1000) as unknown as number return { ...segmentHoverMap, [pathToNodeString]: timeoutId, diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index e03a691466..febf701449 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -1105,11 +1105,7 @@ export const circle: SketchLineHelper = { isNotLiteralArrayOrStatic(radiusDetails.exp), code.slice(radiusDetails.exp.start, radiusDetails.exp.end), 'circle', - { - type: 'objectProperty', - key: 'radius', - argIndex: 0, - }, + 'radius', [radiusDetails.exp.start, radiusDetails.exp.end], pathToRadiusLiteral ), From 85abcde08630fb4c1d2458ed7244a3735f158e8a Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 16:28:22 +1000 Subject: [PATCH 21/41] make sure behaviour is sane betwen center and radius clicks --- src/lib/toolbar.ts | 12 +++++++++--- src/machines/modelingMachine.ts | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/lib/toolbar.ts b/src/lib/toolbar.ts index cb3a24463a..6a4cd7123e 100644 --- a/src/lib/toolbar.ts +++ b/src/lib/toolbar.ts @@ -3,6 +3,7 @@ import { DEV } from 'env' import { commandBarMachine } from 'machines/commandBarMachine' import { canRectangleOrCircleTool, + isClosedSketch, isEditingExistingSketch, modelingMachine, } from 'machines/modelingMachine' @@ -297,7 +298,9 @@ export const toolbarConfig: Record = { status: 'available', disabled: (state) => state.matches('Sketch no face') || - state.matches('Sketch.Rectangle tool.Awaiting second corner'), + state.matches('Sketch.Rectangle tool.Awaiting second corner') || + state.matches('Sketch.Circle tool.Awaiting Radius') || + isClosedSketch(state.context), title: 'Line', hotkey: (state) => state.matches('Sketch.Line tool') ? ['Esc', 'L'] : 'L', @@ -320,8 +323,11 @@ export const toolbarConfig: Record = { icon: 'arc', status: 'available', disabled: (state) => - !isEditingExistingSketch(state.context) && - !state.matches('Sketch.Tangential arc to'), + (!isEditingExistingSketch(state.context) && + !state.matches('Sketch.Tangential arc to')) || + state.matches('Sketch.Rectangle tool.Awaiting second corner') || + state.matches('Sketch.Circle tool.Awaiting Radius') || + isClosedSketch(state.context), title: 'Tangential Arc', hotkey: (state) => state.matches('Sketch.Tangential arc to') ? ['Esc', 'A'] : 'A', diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 08b779ca57..f86e168f05 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -1744,3 +1744,25 @@ export function canRectangleOrCircleTool({ if (err(node)) return false return node.node?.declarations?.[0]?.init.type !== 'PipeExpression' } + +/** If the sketch contains `close` or `circle` stdlib functions it must be closed */ +export function isClosedSketch({ + sketchDetails, +}: { + sketchDetails: SketchDetails | null +}): boolean { + const node = getNodeFromPath( + kclManager.ast, + sketchDetails?.sketchPathToNode || [], + 'VariableDeclaration' + ) + // This should not be returning false, and it should be caught + // but we need to simulate old behavior to move on. + if (err(node)) return false + if (node.node?.declarations?.[0]?.init.type !== 'PipeExpression') return false + return node.node.declarations[0].init.body.some( + (yo) => + yo.type === 'CallExpression' && + (yo.callee.name === 'close' || yo.callee.name === 'circle') + ) +} From 466511ac46def22104da4f27d9cfe43e6dbdf2c7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Sep 2024 06:26:33 +0000 Subject: [PATCH 22/41] =?UTF-8?q?A=20snapshot=20a=20day=20keeps=20the=20bu?= =?UTF-8?q?gs=20away!=20=F0=9F=93=B7=F0=9F=90=9B=20(OS:=20ubuntu-latest)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-should-look-right-1-Google-Chrome-linux.png | Bin 0 -> 42339 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..41c6a71bd24d4e719294a1e2a0660eaedc818221 GIT binary patch literal 42339 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 literal 0 HcmV?d00001 From 840e75e5a1d746af1dc03f3fc577ebd3a3318844 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 16:37:41 +1000 Subject: [PATCH 23/41] fix dragging circle circumference error --- src/clientSideScene/sceneEntities.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index b56df4832b..c9b398276f 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -1312,7 +1312,12 @@ export class SceneEntities { }, previousProgramMemory: kclManager.programMemory, }) - } else if (group.name === CIRCLE_SEGMENT && subGroup?.name === ARROWHEAD) { + } else if ( + group.name === CIRCLE_SEGMENT && + // !subGroup treats grabbing the outer circumference of the circle + // as a drag of the center handle + (!subGroup || subGroup?.name === ARROWHEAD) + ) { // is dragging the radius handle modded = changeSketchArguments( From 9d6a09766cc342c9b5c1514eeaa22c835993794d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Sep 2024 06:55:27 +0000 Subject: [PATCH 24/41] =?UTF-8?q?A=20snapshot=20a=20day=20keeps=20the=20bu?= =?UTF-8?q?gs=20away!=20=F0=9F=93=B7=F0=9F=90=9B=20(OS:=20windows-latest)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-should-look-right-1-Google-Chrome-win32.png | Bin 0 -> 37820 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-win32.png diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-win32.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-win32.png new file mode 100644 index 0000000000000000000000000000000000000000..fc059e049777210a560a1142365af6bd6e54be6f GIT binary patch literal 37820 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

ZSyqy1aGO+9)-Xa4v^?^X1 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 Date: Mon, 9 Sep 2024 17:10:40 +1000 Subject: [PATCH 25/41] fix circle line thickness and handles scale correctly on zoom --- src/clientSideScene/sceneEntities.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index c9b398276f..53b6fa6654 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -198,7 +198,6 @@ export class SceneEntities { segment.userData.to && segment.userData.center && segment.userData.radius && - segment.userData.prevSegment && segment.userData.type === CIRCLE_SEGMENT ) { callbacks.push( From 994f71bce5863ca187a21df077c3c49f7d41873d Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 19:30:51 +1000 Subject: [PATCH 26/41] make clippy happy --- src/wasm-lib/kcl/src/std/args.rs | 2 -- src/wasm-lib/kcl/src/std/mod.rs | 2 +- src/wasm-lib/kcl/src/std/shapes.rs | 12 ++++-------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/wasm-lib/kcl/src/std/args.rs b/src/wasm-lib/kcl/src/std/args.rs index 94899417be..7013c09090 100644 --- a/src/wasm-lib/kcl/src/std/args.rs +++ b/src/wasm-lib/kcl/src/std/args.rs @@ -14,8 +14,6 @@ use crate::{ std::{shapes::SketchSurfaceOrGroup, sketch::FaceTag, FnAsArg}, }; -use super::shapes::Circle; - #[derive(Debug, Clone)] pub struct Args { pub args: Vec, diff --git a/src/wasm-lib/kcl/src/std/mod.rs b/src/wasm-lib/kcl/src/std/mod.rs index 97e6f3c28e..534039c277 100644 --- a/src/wasm-lib/kcl/src/std/mod.rs +++ b/src/wasm-lib/kcl/src/std/mod.rs @@ -37,7 +37,7 @@ use crate::{ ast::types::FunctionExpression, docs::StdLibFn, errors::KclError, - executor::{KclValue, ProgramMemory, SketchGroup, SketchSurface}, + executor::{KclValue, ProgramMemory}, std::kcl_stdlib::KclStdLibFn, }; diff --git a/src/wasm-lib/kcl/src/std/shapes.rs b/src/wasm-lib/kcl/src/std/shapes.rs index 094a95651b..d07e9cae89 100644 --- a/src/wasm-lib/kcl/src/std/shapes.rs +++ b/src/wasm-lib/kcl/src/std/shapes.rs @@ -9,12 +9,10 @@ use serde::{Deserialize, Serialize}; use crate::{ ast::types::TagDeclarator, errors::{KclError, KclErrorDetails}, - executor::{BasePath, GeoMeta, KclValue, Path, Point2d, SketchGroup, SketchSurface}, + executor::{BasePath, GeoMeta, KclValue, Path, SketchGroup, SketchSurface}, std::Args, }; -use super::utils::arc_center_and_end; - /// A sketch surface or a sketch group. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[ts(export)] @@ -87,8 +85,6 @@ async fn inner_circle( ) .await?; - let from: Point2d = sketch_group.current_pen_position()?; - let angle_start = Angle::from_degrees(0.0); let angle_end = Angle::from_degrees(360.0); @@ -118,8 +114,8 @@ async fn inner_circle( let current_path = Path::Circle { base: BasePath { - from: data.center.into(), - to: data.center.into(), + from: data.center, + to: data.center, tag: tag.clone(), geo_meta: GeoMeta { id, @@ -127,7 +123,7 @@ async fn inner_circle( }, }, radius: data.radius, - center: data.center.into(), + center: data.center, ccw: angle_start.degrees() < angle_end.degrees(), }; From a1dd8840135c21578fd2ea217a3c098aac6c34d7 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 19:34:12 +1000 Subject: [PATCH 27/41] stdlib docs update --- docs/kcl/angleToMatchLengthX.md | 40 ++++++++++++++ docs/kcl/angleToMatchLengthY.md | 40 ++++++++++++++ docs/kcl/angledLine.md | 80 ++++++++++++++++++++++++++++ docs/kcl/angledLineOfXLength.md | 80 ++++++++++++++++++++++++++++ docs/kcl/angledLineOfYLength.md | 80 ++++++++++++++++++++++++++++ docs/kcl/angledLineThatIntersects.md | 80 ++++++++++++++++++++++++++++ docs/kcl/angledLineToX.md | 80 ++++++++++++++++++++++++++++ docs/kcl/angledLineToY.md | 80 ++++++++++++++++++++++++++++ docs/kcl/arc.md | 80 ++++++++++++++++++++++++++++ docs/kcl/bezierCurve.md | 80 ++++++++++++++++++++++++++++ docs/kcl/chamfer.md | 40 ++++++++++++++ 11 files changed, 760 insertions(+) diff --git a/docs/kcl/angleToMatchLengthX.md b/docs/kcl/angleToMatchLengthX.md index e666283df5..f66c1d9dbb 100644 --- a/docs/kcl/angleToMatchLengthX.md +++ b/docs/kcl/angleToMatchLengthX.md @@ -270,6 +270,26 @@ const extrusion = extrude(5, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -479,6 +499,26 @@ const extrusion = extrude(5, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angleToMatchLengthY.md b/docs/kcl/angleToMatchLengthY.md index 20243dab08..3ebb5278e3 100644 --- a/docs/kcl/angleToMatchLengthY.md +++ b/docs/kcl/angleToMatchLengthY.md @@ -274,6 +274,26 @@ const extrusion = extrude(5, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -483,6 +503,26 @@ const extrusion = extrude(5, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLine.md b/docs/kcl/angledLine.md index 432fcb642e..f5db4221da 100644 --- a/docs/kcl/angledLine.md +++ b/docs/kcl/angledLine.md @@ -189,6 +189,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -398,6 +418,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -609,6 +649,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -818,6 +878,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineOfXLength.md b/docs/kcl/angledLineOfXLength.md index 2ff7573e98..d34cddd640 100644 --- a/docs/kcl/angledLineOfXLength.md +++ b/docs/kcl/angledLineOfXLength.md @@ -188,6 +188,26 @@ const extrusion = extrude(10, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -397,6 +417,26 @@ const extrusion = extrude(10, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -608,6 +648,26 @@ const extrusion = extrude(10, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -817,6 +877,26 @@ const extrusion = extrude(10, sketch001) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineOfYLength.md b/docs/kcl/angledLineOfYLength.md index dd87e73447..5e15b2d4b4 100644 --- a/docs/kcl/angledLineOfYLength.md +++ b/docs/kcl/angledLineOfYLength.md @@ -190,6 +190,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -399,6 +419,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -610,6 +650,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -819,6 +879,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineThatIntersects.md b/docs/kcl/angledLineThatIntersects.md index 9e039ef234..92a527010d 100644 --- a/docs/kcl/angledLineThatIntersects.md +++ b/docs/kcl/angledLineThatIntersects.md @@ -282,6 +282,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -491,6 +511,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -702,6 +742,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -911,6 +971,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineToX.md b/docs/kcl/angledLineToX.md index 27c299817b..082017d084 100644 --- a/docs/kcl/angledLineToX.md +++ b/docs/kcl/angledLineToX.md @@ -187,6 +187,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -396,6 +416,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -607,6 +647,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -816,6 +876,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/angledLineToY.md b/docs/kcl/angledLineToY.md index ffc1f0fae0..171822fde5 100644 --- a/docs/kcl/angledLineToY.md +++ b/docs/kcl/angledLineToY.md @@ -187,6 +187,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -396,6 +416,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -607,6 +647,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -816,6 +876,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/arc.md b/docs/kcl/arc.md index ac9fca5612..faf8f55753 100644 --- a/docs/kcl/arc.md +++ b/docs/kcl/arc.md @@ -200,6 +200,26 @@ const exampleSketch = startSketchOn('XZ') to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -409,6 +429,26 @@ const exampleSketch = startSketchOn('XZ') to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -620,6 +660,26 @@ const exampleSketch = startSketchOn('XZ') to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -829,6 +889,26 @@ const exampleSketch = startSketchOn('XZ') to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/bezierCurve.md b/docs/kcl/bezierCurve.md index 9db9871a97..49ac9d1496 100644 --- a/docs/kcl/bezierCurve.md +++ b/docs/kcl/bezierCurve.md @@ -193,6 +193,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -402,6 +422,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -613,6 +653,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -822,6 +882,26 @@ const example = extrude(10, exampleSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], diff --git a/docs/kcl/chamfer.md b/docs/kcl/chamfer.md index 29551c4f76..bce8ae9bad 100644 --- a/docs/kcl/chamfer.md +++ b/docs/kcl/chamfer.md @@ -415,6 +415,26 @@ const mountingPlate = extrude(thickness, mountingPlateSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], @@ -819,6 +839,26 @@ const mountingPlate = extrude(thickness, mountingPlateSketch) to: [number, number], type: "TangentialArc", } | +{ + // arc's direction + ccw: bool, + // the arc's center + center: [number, number], + // The from point. + from: [number, number], + // the arc's radius + radius: number, + // The tag of the path. + tag: { + digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number], + end: number, + start: number, + value: string, +}, + // The to point. + to: [number, number], + type: "Circle", +} | { // The from point. from: [number, number], From e794c5b0e915d22a1c6422476bcac9a2f25c1af0 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 20:28:24 +1000 Subject: [PATCH 28/41] Update some circle function calls and twenty twenty stuff --- src/wasm-lib/kcl/src/docs.rs | 2 +- src/wasm-lib/kcl/src/executor.rs | 4 +- src/wasm-lib/kcl/src/std/convert.rs | 2 +- src/wasm-lib/kcl/src/std/helix.rs | 2 +- src/wasm-lib/kcl/src/std/loft.rs | 8 +-- src/wasm-lib/kcl/src/std/math.rs | 2 +- src/wasm-lib/kcl/src/std/patterns.rs | 6 +-- src/wasm-lib/kcl/src/std/planes.rs | 8 +-- src/wasm-lib/kcl/src/std/revolve.rs | 8 +-- src/wasm-lib/kcl/src/std/shapes.rs | 4 +- src/wasm-lib/kcl/src/std/sketch.rs | 6 +-- src/wasm-lib/kcl/src/unparser.rs | 16 +++--- ...l_test_example_get_next_adjacent_edge0.png | Bin 65258 -> 64161 bytes ...st_example_get_previous_adjacent_edge0.png | Bin 64161 -> 65258 bytes .../outputs/serial_test_example_loft1.png | Bin 138174 -> 138890 bytes .../outputs/serial_test_example_loft2.png | Bin 138174 -> 138890 bytes .../serial_test_example_offset_plane0.png | Bin 103371 -> 104052 bytes src/wasm-lib/tests/executor/main.rs | 46 +++++++++--------- .../executor/outputs/mike_stress_test.png | Bin 47813 -> 0 bytes 19 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/wasm-lib/kcl/src/docs.rs b/src/wasm-lib/kcl/src/docs.rs index 760c11f526..7de168382a 100644 --- a/src/wasm-lib/kcl/src/docs.rs +++ b/src/wasm-lib/kcl/src/docs.rs @@ -937,6 +937,6 @@ mod tests { fn get_autocomplete_snippet_circle() { let circle_fn: Box = Box::new(crate::std::shapes::Circle); let snippet = circle_fn.to_autocomplete_snippet().unwrap(); - assert_eq!(snippet, r#"circle([${0:3.14}, ${1:3.14}], ${2:3.14}, ${3:%})${}"#); + assert_eq!(snippet, r#"circle({ center: [${0:3.14}, ${1:3.14}], radius: ${2:3.14} }, ${3:%})${}"#); } } diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index 187703888a..ddae77ccd7 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -2589,7 +2589,7 @@ fn transform = (replicaId) => { fn layer = () => { return startSketchOn("XY") - |> circle([0, 0], 1, %, $tag1) + |> circle({ center: [0, 0], radius: 1 }, %, $tag1) |> extrude(10, %) } @@ -2717,7 +2717,7 @@ fn transform = (replicaId) => { fn layer = () => { return startSketchOn("XY") - |> circle([0, 0], 1, %, $tag1) + |> circle({ center: [0, 0], radius: 1 }, %, $tag1) |> extrude(10, %) } diff --git a/src/wasm-lib/kcl/src/std/convert.rs b/src/wasm-lib/kcl/src/std/convert.rs index 8ce353b4fe..5c2e5bfb1d 100644 --- a/src/wasm-lib/kcl/src/std/convert.rs +++ b/src/wasm-lib/kcl/src/std/convert.rs @@ -50,7 +50,7 @@ pub async fn int(args: Args) -> Result { /// /// ```no_run /// const sketch001 = startSketchOn('XZ') -/// |> circle([0, 0], 2, %) +/// |> circle({ center: [0, 0], radius: 2 }, %) /// const extrude001 = extrude(5, sketch001) /// /// const pattern01 = patternTransform(int(ceil(5 / 2)), (id) => { diff --git a/src/wasm-lib/kcl/src/std/helix.rs b/src/wasm-lib/kcl/src/std/helix.rs index 220bf650b2..b0a9074070 100644 --- a/src/wasm-lib/kcl/src/std/helix.rs +++ b/src/wasm-lib/kcl/src/std/helix.rs @@ -42,7 +42,7 @@ pub async fn helix(args: Args) -> Result { /// /// ```no_run /// const part001 = startSketchOn('XY') -/// |> circle([5, 5], 10, %) +/// |> circle({ center: [5, 5], radius: 10 }, %) /// |> extrude(10, %) /// |> helix({ /// angleStart: 0, diff --git a/src/wasm-lib/kcl/src/std/loft.rs b/src/wasm-lib/kcl/src/std/loft.rs index 57cf8f16db..885e7797f8 100644 --- a/src/wasm-lib/kcl/src/std/loft.rs +++ b/src/wasm-lib/kcl/src/std/loft.rs @@ -91,10 +91,10 @@ pub async fn loft(args: Args) -> Result { /// |> close(%) /// /// const circleSketch0 = startSketchOn(offsetPlane('XY', 75)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// const circleSketch1 = startSketchOn(offsetPlane('XY', 150)) -/// |> circle([0, 100], 20, %) +/// |> circle({ center: [0, 100], radius: 20 }, %) /// /// loft([squareSketch, circleSketch0, circleSketch1]) /// ``` @@ -110,10 +110,10 @@ pub async fn loft(args: Args) -> Result { /// |> close(%) /// /// const circleSketch0 = startSketchOn(offsetPlane('XY', 75)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// const circleSketch1 = startSketchOn(offsetPlane('XY', 150)) -/// |> circle([0, 100], 20, %) +/// |> circle({ center: [0, 100], radius: 20 }, %) /// /// loft([squareSketch, circleSketch0, circleSketch1], { /// // This can be set to override the automatically determined diff --git a/src/wasm-lib/kcl/src/std/math.rs b/src/wasm-lib/kcl/src/std/math.rs index d53307c171..e833c5b09d 100644 --- a/src/wasm-lib/kcl/src/std/math.rs +++ b/src/wasm-lib/kcl/src/std/math.rs @@ -113,7 +113,7 @@ pub async fn pi(args: Args) -> Result { /// const circumference = 70 /// /// const exampleSketch = startSketchOn("XZ") -/// |> circle([0, 0], circumference/ (2 * pi()), %) +/// |> circle({ center: [0, 0], radius: circumference/ (2 * pi()) }, %) /// /// const example = extrude(5, exampleSketch) /// ``` diff --git a/src/wasm-lib/kcl/src/std/patterns.rs b/src/wasm-lib/kcl/src/std/patterns.rs index 5929e1568f..f5b1c3475e 100644 --- a/src/wasm-lib/kcl/src/std/patterns.rs +++ b/src/wasm-lib/kcl/src/std/patterns.rs @@ -117,7 +117,7 @@ pub async fn pattern_transform(args: Args) -> Result { /// // Each layer is just a pretty thin cylinder. /// fn layer = () => { /// return startSketchOn("XY") // or some other plane idk -/// |> circle([0, 0], 1, %, $tag1) +/// |> circle({ center: [0, 0], radius: 1 }, %, $tag1) /// |> extrude(h, %) /// } /// // The vase is 100 layers tall. @@ -318,7 +318,7 @@ pub async fn pattern_linear_2d(args: Args) -> Result { /// /// ```no_run /// const exampleSketch = startSketchOn('XZ') -/// |> circle([0, 0], 1, %) +/// |> circle({ center: [0, 0], radius: 1 }, %) /// |> patternLinear2d({ /// axis: [1, 0], /// repetitions: 6, @@ -651,7 +651,7 @@ pub async fn pattern_circular_3d(args: Args) -> Result { /// /// ```no_run /// const exampleSketch = startSketchOn('XZ') -/// |> circle([0, 0], 1, %) +/// |> circle({ center: [0, 0], radius: 1 }, %) /// /// const example = extrude(-5, exampleSketch) /// |> patternCircular3d({ diff --git a/src/wasm-lib/kcl/src/std/planes.rs b/src/wasm-lib/kcl/src/std/planes.rs index 47ad5c3795..cb41f7c26e 100644 --- a/src/wasm-lib/kcl/src/std/planes.rs +++ b/src/wasm-lib/kcl/src/std/planes.rs @@ -77,7 +77,7 @@ pub async fn offset_plane(args: Args) -> Result { /// |> close(%) /// /// const circleSketch = startSketchOn(offsetPlane('XY', 150)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// loft([squareSketch, circleSketch]) /// ``` @@ -93,7 +93,7 @@ pub async fn offset_plane(args: Args) -> Result { /// |> close(%) /// /// const circleSketch = startSketchOn(offsetPlane('XZ', 150)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// loft([squareSketch, circleSketch]) /// ``` @@ -109,7 +109,7 @@ pub async fn offset_plane(args: Args) -> Result { /// |> close(%) /// /// const circleSketch = startSketchOn(offsetPlane('YZ', 150)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// loft([squareSketch, circleSketch]) /// ``` @@ -125,7 +125,7 @@ pub async fn offset_plane(args: Args) -> Result { /// |> close(%) /// /// const circleSketch = startSketchOn(offsetPlane('-XZ', -150)) -/// |> circle([0, 100], 50, %) +/// |> circle({ center: [0, 100], radius: 50 }, %) /// /// loft([squareSketch, circleSketch]) /// ``` diff --git a/src/wasm-lib/kcl/src/std/revolve.rs b/src/wasm-lib/kcl/src/std/revolve.rs index ffdffc7a4c..8727838db7 100644 --- a/src/wasm-lib/kcl/src/std/revolve.rs +++ b/src/wasm-lib/kcl/src/std/revolve.rs @@ -133,7 +133,7 @@ pub async fn revolve(args: Args) -> Result { /// ```no_run /// // A donut shape. /// const sketch001 = startSketchOn('XY') -/// |> circle([15, 0], 5, %) +/// |> circle({ center: [15, 0], radius: 5 }, %) /// |> revolve({ /// angle: 360, /// axis: 'y' @@ -185,7 +185,7 @@ pub async fn revolve(args: Args) -> Result { /// |> extrude(20, %) /// /// const sketch001 = startSketchOn(box, "END") -/// |> circle([10,10], 4, %) +/// |> circle({ center: [10,10], radius: 4 }, %) /// |> revolve({ /// angle: -90, /// axis: 'y' @@ -202,7 +202,7 @@ pub async fn revolve(args: Args) -> Result { /// |> extrude(20, %) /// /// const sketch001 = startSketchOn(box, "END") -/// |> circle([10,10], 4, %) +/// |> circle({ center: [10,10], radius: 4 }, %) /// |> revolve({ /// angle: 90, /// axis: getOppositeEdge(revolveAxis) @@ -219,7 +219,7 @@ pub async fn revolve(args: Args) -> Result { /// |> extrude(20, %) /// /// const sketch001 = startSketchOn(box, "END") -/// |> circle([10,10], 4, %) +/// |> circle({ center: [10,10], radius: 4 }, %) /// |> revolve({ /// angle: 90, /// axis: getOppositeEdge(revolveAxis), diff --git a/src/wasm-lib/kcl/src/std/shapes.rs b/src/wasm-lib/kcl/src/std/shapes.rs index d07e9cae89..7807ff378f 100644 --- a/src/wasm-lib/kcl/src/std/shapes.rs +++ b/src/wasm-lib/kcl/src/std/shapes.rs @@ -48,7 +48,7 @@ pub async fn circle(args: Args) -> Result { /// /// ```no_run /// const exampleSketch = startSketchOn("-XZ") -/// |> circle([0, 0], 10, %) +/// |> circle({ center: [0, 0], radius: 10 }, %) /// /// const example = extrude(5, exampleSketch) /// ``` @@ -60,7 +60,7 @@ pub async fn circle(args: Args) -> Result { /// |> line([0, 30], %) /// |> line([-30, 0], %) /// |> close(%) -/// |> hole(circle([0, 15], 5, %), %) +/// |> hole(circle({ center: [0, 15], radius: 5 }, %), %) /// /// const example = extrude(5, exampleSketch) /// ``` diff --git a/src/wasm-lib/kcl/src/std/sketch.rs b/src/wasm-lib/kcl/src/std/sketch.rs index e62cc719e2..d65ead8743 100644 --- a/src/wasm-lib/kcl/src/std/sketch.rs +++ b/src/wasm-lib/kcl/src/std/sketch.rs @@ -2059,8 +2059,8 @@ pub async fn hole(args: Args) -> Result { /// |> line([5, 0], %) /// |> line([0, -5], %) /// |> close(%) -/// |> hole(circle([1, 1], .25, %), %) -/// |> hole(circle([1, 4], .25, %), %) +/// |> hole(circle({ center: [1, 1], radius: .25 }, %), %) +/// |> hole(circle({ center: [1, 4], radius: .25 }, %), %) /// /// const example = extrude(1, exampleSketch) /// ``` @@ -2077,7 +2077,7 @@ pub async fn hole(args: Args) -> Result { /// } /// /// const exampleSketch = startSketchOn('-XZ') -/// |> circle([0, 0], 3, %) +/// |> circle({ center: [0, 0], radius: 3 }, %) /// |> hole(squareHoleSketch(), %) /// const example = extrude(1, exampleSketch) /// ``` diff --git a/src/wasm-lib/kcl/src/unparser.rs b/src/wasm-lib/kcl/src/unparser.rs index a401d273ff..c13c9c1d25 100644 --- a/src/wasm-lib/kcl/src/unparser.rs +++ b/src/wasm-lib/kcl/src/unparser.rs @@ -1028,10 +1028,10 @@ const tabs_r = startSketchOn({ |> line([0, -10], %) |> line([-10, -5], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ width / 2 + thk + hole_diam, length / 2 - hole_diam - ], hole_diam / 2, %), %) + ], radius: hole_diam / 2 }, %), %) |> extrude(-thk, %) |> patternLinear3d({ axis: [0, -1, 0], @@ -1052,10 +1052,10 @@ const tabs_l = startSketchOn({ |> line([0, -10], %) |> line([10, -5], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ -width / 2 - thk - hole_diam, length / 2 - hole_diam - ], hole_diam / 2, %), %) + ], radius: hole_diam / 2 }, %), %) |> extrude(-thk, %) |> patternLinear3d({ axis: [0, -1, 0], @@ -1148,10 +1148,10 @@ const tabs_r = startSketchOn({ |> line([0, -10], %) |> line([-10, -5], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ width / 2 + thk + hole_diam, length / 2 - hole_diam - ], hole_diam / 2, %), %) + ], radius: hole_diam / 2 }, %), %) |> extrude(-thk, %) |> patternLinear3d({ axis: [0, -1, 0], @@ -1172,10 +1172,10 @@ const tabs_l = startSketchOn({ |> line([0, -10], %) |> line([10, -5], %) |> close(%) - |> hole(circle([ + |> hole(circle({ center: [ -width / 2 - thk - hole_diam, length / 2 - hole_diam - ], hole_diam / 2, %), %) + ], radius: hole_diam / 2 }, %), %) |> extrude(-thk, %) |> patternLinear3d({ axis: [0, -1, 0], diff --git a/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_next_adjacent_edge0.png b/src/wasm-lib/kcl/tests/outputs/serial_test_example_get_next_adjacent_edge0.png index a61f6b8af8fcd56c6df94dfb0d0afa6dce1eda8c..debdd74470fee53617cffd581d451ac1903d160a 100644 GIT binary patch literal 64161 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 IlP8}4zXpW=0{{R3 literal 65258 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 IlP8}4zXpW=0{{R3 diff --git a/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft1.png b/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft1.png index a44145b368315df03404e97edb16a6595942dab2..25dc6517cce9eced4ed2241dec54bdbf6e8c58d1 100644 GIT binary patch delta 2050 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 diff --git a/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft2.png b/src/wasm-lib/kcl/tests/outputs/serial_test_example_loft2.png index a44145b368315df03404e97edb16a6595942dab2..25dc6517cce9eced4ed2241dec54bdbf6e8c58d1 100644 GIT binary patch delta 2050 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 diff --git a/src/wasm-lib/kcl/tests/outputs/serial_test_example_offset_plane0.png b/src/wasm-lib/kcl/tests/outputs/serial_test_example_offset_plane0.png index 35e17abd94a4473a1da75f123c54981555dbbb6c..37ec6fb46f7011a017b29e7061debefcf442bdd1 100644 GIT binary patch delta 1344 zcmY+EeM}Q~9L8IXRI-guogy$>O^YsDSWVPA?VGv*HU!P$I-Emc>H>~-bs}rAr9XU` zsE8d9Wv0^3HIYS}6_FMv?Lleg|f0OztT6W z-mO=)qbO~|e0W}(D-s9OuG?peUC0p`WAl8tP&&4u%KN3NDL=&@aqvgw1SW@AX=5G#af+=!Mfg0myFZl?nW+oCbV^2HdzpW5DP?}`-Z+Xl zn05WFLN-Y;XC0s4|DvPz!n)k!EkdluG~ol#huEh{C26?x?HTObJejSJr{OkkGXy>x z&5Cmd)2@}sq+@6kH?(@Zv%Q&Xo;2-_BRvr5WYRsPBC95(FoYGWLkdo7PD67}umdGV zP~poZsCNmx%Qfe>DAX;YfD^%;$+l)vVeqC`JS?Iv21pT`s1>^smz{LGpMEKy)03Cs z%R`G^J81cse(1r``jDHXrJy)kRTHfmao}AJJfRSo)FHiKzBmZtPr_lg7pY_u)eUVb zYc$22zf??7JA7(7B%g039ksK+dxErbVdXThLzbIcP80bfn<(nREY09>u%h<_brpc= zCcM*B@PQ|j(NmW(RVT+s$W3TSd$DzKC|sBhJ#^@CW;yiMnV2mb{|8wehMb-WA#7MG z3>Za$T6u#vi4qE%Qa^T|H#uY?h8%b1yuuM+@4pJg)b5RezNF0ZLg(Zj?9 z(6Fgsj$@bc=xD@HLbGQ5<8nroSI!t2jsNx+e)bk=SL&_4wj06cf|q4Y6y6=L;6Mf0 zyo?u2qL-<0Pfy*9n->p+J1T)u;XT4L)B(t{x>dmDW+AwrEn4E4Rucu2mB%fGbk&KmchQ?DyXdJBvH`Y%P5z~4lF+TE?EYJ3Xd`@FDCZEsVryf)E8 z6KxOj3SO*fh=PYTU)(dO=MI!|Mv1SJ_`)+@6L>EQ8S%zKEP6Z9p7-He;ZK1RfTi|- zR08mOJBWVHzRAAmySWi#lp>|!Rf3^1p`;DtOT#Bj_?Uscl3fE5#w<@V7*F2n2EWQ~ zjJ>b7-e+}L+haLQvM$7q+lDKim+uU;<|wh_Yj5(E+AdsG0w#R1aQ6=tug?HUBx!W; rXPV&I#`3S;h@q~irOs}QTmjr*1Bwke4`d%O_ViWght#5hDld delta 540 zcmeyehVArnwh78ilP5K*ZdGNJYp?&Gv2EkodXw8Xp4m^z&G{^U)@<9)^J&tzW4`X6 zuJUiS&11>)3=fXRy*V29`)K^1qw%v>uiN!%)#`P>wkz)d(Lhdp`}NAX*DLiKs^9Hw z|NFN#=kt7^DL_+AZ{IiuG;V#8G>Gv(G$$FzcxFF4Hz#@af1hm|dvpIy-_Xh^HhpUc zqhS3i@n;`COxT=$p6SDl<96S+!i~3Xg4tPKalE|d_<6R9SHbg7{RNrA1vbSRVowT^ zJ%7?S_k!)w0jn?utLO)M4Cc|3K$rCcb^LJ$D*U(kX8kd<@W0(%`ikT98*y{^*Wz_n{)`d@?ekNy{#^y9ym z(Esb6kLs;G|Kuxm{(q_RasNz}b-$+He71jb?my!j&vxeKy#z(qbV*fe*3BQ@D+?H9% O00f?{elF{r5}E+rAVClS diff --git a/src/wasm-lib/tests/executor/main.rs b/src/wasm-lib/tests/executor/main.rs index 66f3c18e41..8fa6025839 100644 --- a/src/wasm-lib/tests/executor/main.rs +++ b/src/wasm-lib/tests/executor/main.rs @@ -301,8 +301,8 @@ async fn kcl_test_holes() { |> line([10, 0], %) |> line([0, -10], %) |> close(%) - |> hole(circle([2, 2], .5, %), %) - |> hole(circle([2, 8], .5, %), %) + |> hole(circle({ center: [2, 2], radius: .5 }, %), %) + |> hole(circle({ center: [2, 8], radius: .5 }, %), %) |> extrude(2, %) "#; @@ -354,10 +354,10 @@ const holeRadius = 1 const holeIndex = 6 const part = roundedRectangle([0, 0], 20, 20, 4) - |> hole(circle([-holeIndex, holeIndex], holeRadius, %), %) - |> hole(circle([holeIndex, holeIndex], holeRadius, %), %) - |> hole(circle([-holeIndex, -holeIndex], holeRadius, %), %) - |> hole(circle([holeIndex, -holeIndex], holeRadius, %), %) + |> hole(circle({ center: [-holeIndex, holeIndex], radius: holeRadius }, %), %) + |> hole(circle({ center: [holeIndex, holeIndex], radius: holeRadius }, %), %) + |> hole(circle({ center: [-holeIndex, -holeIndex], radius: holeRadius }, %), %) + |> hole(circle({ center: [holeIndex, -holeIndex], radius: holeRadius }, %), %) |> extrude(2, %) "#; @@ -367,7 +367,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4) #[tokio::test(flavor = "multi_thread")] async fn kcl_test_top_level_expression() { - let code = r#"startSketchOn('XY') |> circle([0,0], 22, %) |> extrude(14, %)"#; + let code = r#"startSketchOn('XY') |> circle({ center: [0,0], radius: 22 }, %) |> extrude(14, %)"#; let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); assert_out("top_level_expression", &result); @@ -378,7 +378,7 @@ async fn kcl_test_patterns_linear_basic_with_math() { let code = r#"const num = 12 const distance = 5 const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternLinear2d({axis: [0,1], repetitions: num -1, distance: distance - 1}, %) |> extrude(1, %) "#; @@ -390,7 +390,7 @@ const part = startSketchOn('XY') #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_linear_basic() { let code = r#"const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternLinear2d({axis: [0,1], repetitions: 12, distance: 4}, %) |> extrude(1, %) "#; @@ -418,7 +418,7 @@ async fn kcl_test_patterns_linear_basic_3d() { #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_linear_basic_negative_distance() { let code = r#"const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternLinear2d({axis: [0,1], repetitions: 12, distance: -2}, %) |> extrude(1, %) "#; @@ -430,7 +430,7 @@ async fn kcl_test_patterns_linear_basic_negative_distance() { #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_linear_basic_negative_axis() { let code = r#"const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternLinear2d({axis: [0,-1], repetitions: 12, distance: 2}, %) |> extrude(1, %) "#; @@ -442,7 +442,7 @@ async fn kcl_test_patterns_linear_basic_negative_axis() { #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_linear_basic_holes() { let code = r#"const circles = startSketchOn('XY') - |> circle([5, 5], 1, %) + |> circle({ center: [5, 5], radius: 1 }, %) |> patternLinear2d({axis: [1,1], repetitions: 12, distance: 3}, %) const rectangle = startSketchOn('XY') @@ -463,7 +463,7 @@ const rectangle = startSketchOn('XY') #[tokio::test(flavor = "multi_thread")] async fn kcl_test_patterns_circular_basic_2d() { let code = r#"const part = startSketchOn('XY') - |> circle([0,0], 2, %) + |> circle({ center: [0,0], radius: 2 }, %) |> patternCircular2d({center: [20, 20], repetitions: 12, arcDegrees: 210, rotateDuplicates: true}, %) |> extrude(1, %) "#; @@ -787,8 +787,8 @@ async fn kcl_test_stdlib_kcl_error_right_code_path() { |> line([10, 0], %) |> line([0, -10], %) |> close(%) - |> hole(circle([2, 2], .5), %) - |> hole(circle([2, 8], .5, %), %) + |> hole(circle({ center: [2, 2], radius: .5 }), %) + |> hole(circle({ center: [2, 8], radius: .5 }, %), %) |> extrude(2, %) "#; @@ -816,7 +816,7 @@ const part001 = cube([0,0], 20) |> extrude(20, %) const part002 = startSketchOn(part001, "end") - |> circle([0, 0], 5, %) + |> circle({ center: [0, 0], radius: 5 }, %) |> extrude(5, %) "#; @@ -1085,7 +1085,7 @@ async fn kcl_test_revolve_on_face_circle_edge() { |> extrude(20, %) const sketch001 = startSketchOn(box, "END") - |> circle([10,10], 4, %) + |> circle({ center: [10,10], radius: 4 }, %) |> revolve({ angle: 90, axis: getOppositeEdge(revolveAxis) @@ -1107,7 +1107,7 @@ async fn kcl_test_revolve_on_face_circle() { |> extrude(20, %) const sketch001 = startSketchOn(box, "END") - |> circle([10,10], 4, %) + |> circle({ center: [10,10], radius: 4 }, %) |> revolve({ angle: -90, axis: 'y' @@ -1147,7 +1147,7 @@ const sketch001 = startSketchOn(box, "end") #[tokio::test(flavor = "multi_thread")] async fn kcl_test_basic_revolve_circle() { let code = r#"const sketch001 = startSketchOn('XY') - |> circle([15, 0], 5, %) + |> circle({ center: [15, 0], radius: 5 }, %) |> revolve({ angle: 360, axis: 'y' @@ -1271,10 +1271,10 @@ async fn kcl_test_member_expression_in_params() { z_axis: { x: 0, y: 1, z: 0 } } }) - |> circle([0, 0], capDia / 2, %) + |> circle({ center: [0, 0], radius: capDia / 2 }, %) |> extrude(capHeadLength, %) const screw = startSketchOn(screwHead, "start") - |> circle([0, 0], dia / 2, %) + |> circle({ center: [0, 0], radius: dia / 2 }, %) |> extrude(length, %) return screw } @@ -1343,7 +1343,7 @@ async fn kcl_test_error_empty_start_sketch_on_string() { |> extrude(100, %) const secondSketch = startSketchOn(part001, '') - |> circle([-20, 50], 40, %) + |> circle({ center: [-20, 50], radius: 40 }, %) |> extrude(20, %) "#; @@ -1373,7 +1373,7 @@ fn squareHole = (l, w) => { } const extrusion = startSketchOn('XY') - |> circle([0, 0], dia/2, %) + |> circle({ center: [0, 0], radius: dia/2 }, %) |> hole(squareHole(length, width, height), %) |> extrude(height, %) "#; diff --git a/src/wasm-lib/tests/executor/outputs/mike_stress_test.png b/src/wasm-lib/tests/executor/outputs/mike_stress_test.png index e30fa4bd8ba106f713f789d9ea1463695fb761c4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 47813 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 Date: Mon, 9 Sep 2024 20:32:57 +1000 Subject: [PATCH 29/41] cargo fmt --- src/wasm-lib/kcl/src/docs.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wasm-lib/kcl/src/docs.rs b/src/wasm-lib/kcl/src/docs.rs index 7de168382a..d2bb9f7678 100644 --- a/src/wasm-lib/kcl/src/docs.rs +++ b/src/wasm-lib/kcl/src/docs.rs @@ -937,6 +937,9 @@ mod tests { fn get_autocomplete_snippet_circle() { let circle_fn: Box = Box::new(crate::std::shapes::Circle); let snippet = circle_fn.to_autocomplete_snippet().unwrap(); - assert_eq!(snippet, r#"circle({ center: [${0:3.14}, ${1:3.14}], radius: ${2:3.14} }, ${3:%})${}"#); + assert_eq!( + snippet, + r#"circle({ center: [${0:3.14}, ${1:3.14}], radius: ${2:3.14} }, ${3:%})${}"# + ); } } From 63dea715bcd21a9ceddff14fa209ddf4dd49b8ae Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 22:04:43 +1000 Subject: [PATCH 30/41] some renames --- src/lang/modifyAst.test.ts | 5 -- src/lang/std/sketch.ts | 155 ++++++++++++++++++----------------- src/lang/std/sketchcombos.ts | 6 +- src/lang/std/stdTypes.ts | 13 +-- 4 files changed, 84 insertions(+), 95 deletions(-) diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts index 724564fea2..7eaf784612 100644 --- a/src/lang/modifyAst.test.ts +++ b/src/lang/modifyAst.test.ts @@ -644,18 +644,15 @@ describe('Testing removeSingleConstraintInfo', () => { argPosition = { type: 'arrayItem', index: value === 0 ? 0 : 1, - argIndex: 0, } } else if (key === 'objectProperty' && typeof value === 'string') { argPosition = { type: 'objectProperty', key: value as VarValueKeys, - argIndex: 0, } } else if (key === '') { argPosition = { type: 'singleValue', - argIndex: 0, } } else { throw new Error('argPosition is undefined') @@ -699,14 +696,12 @@ describe('Testing removeSingleConstraintInfo', () => { if (key === 'arrayIndex' && typeof value === 'number') { argPosition = { type: 'arrayItem', - argIndex: 0, index: value === 0 ? 0 : 1, } } else if (key === 'objectProperty' && typeof value === 'string') { argPosition = { type: 'objectProperty', key: value as VarValueKeys, - argIndex: 0, } } else { throw new Error('argPosition is undefined') diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index febf701449..34c652aecc 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -130,11 +130,11 @@ const constrainInfo = ( sourceRange: d, argPosition: g === 'singleValue' - ? { type: 'singleValue', argIndex: 0 } + ? { type: 'singleValue' } : typeof g === 'number' - ? { type: 'arrayItem', index: g, argIndex: 0 } + ? { type: 'arrayItem', index: g } : typeof g === 'string' - ? { type: 'objectProperty', key: g, argIndex: 0 } + ? { type: 'objectProperty', key: g } : undefined, pathToNode: e, stdLibFnName: f, @@ -294,13 +294,13 @@ export const lineTo: SketchLineHelper = { add: ({ node, pathToNode, - input, + segmentInput, createCallback, replaceExisting, referencedSegment, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const to = input.to + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const to = segmentInput.to const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -330,7 +330,6 @@ export const lineTo: SketchLineHelper = { index: 0, argType: 'xAbsolute', value: createLiteral(roundOff(to[0], 2)), - argIndex: 0, }, }, { @@ -340,7 +339,6 @@ export const lineTo: SketchLineHelper = { index: 1, argType: 'yAbsolute', value: createLiteral(roundOff(to[1], 2)), - argIndex: 0, }, }, ], @@ -397,14 +395,14 @@ export const line: SketchLineHelper = { node, previousProgramMemory, pathToNode, - input, + segmentInput, replaceExisting, referencedSegment, createCallback, spliceBetween, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { from, to } = input + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -458,7 +456,6 @@ export const line: SketchLineHelper = { index: 0, argType: 'xRelative', value: createLiteral(roundOff(to[0] - from[0], 2)), - argIndex: 0, }, }, { @@ -468,7 +465,6 @@ export const line: SketchLineHelper = { index: 1, argType: 'yRelative', value: createLiteral(roundOff(to[1] - from[1], 2)), - argIndex: 0, }, }, ], @@ -540,9 +536,15 @@ export const line: SketchLineHelper = { } export const xLineTo: SketchLineHelper = { - add: ({ node, pathToNode, input, replaceExisting, createCallback }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { to } = input + add: ({ + node, + pathToNode, + segmentInput, + replaceExisting, + createCallback, + }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -560,7 +562,6 @@ export const xLineTo: SketchLineHelper = { type: 'singleValue', argType: 'xAbsolute', value: createLiteral(roundOff(to[0], 2)), - argIndex: 0, }, }, ]) @@ -612,9 +613,15 @@ export const xLineTo: SketchLineHelper = { } export const yLineTo: SketchLineHelper = { - add: ({ node, pathToNode, input, replaceExisting, createCallback }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { to } = input + add: ({ + node, + pathToNode, + segmentInput, + replaceExisting, + createCallback, + }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -632,7 +639,6 @@ export const yLineTo: SketchLineHelper = { type: 'singleValue', argType: 'yAbsolute', value: newVal, - argIndex: 0, }, }, ]) @@ -684,9 +690,15 @@ export const yLineTo: SketchLineHelper = { } export const xLine: SketchLineHelper = { - add: ({ node, pathToNode, input, replaceExisting, createCallback }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { from, to } = input + add: ({ + node, + pathToNode, + segmentInput, + replaceExisting, + createCallback, + }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -704,7 +716,6 @@ export const xLine: SketchLineHelper = { type: 'singleValue', argType: 'xRelative', value: newVal, - argIndex: 0, }, }, ]) @@ -754,9 +765,15 @@ export const xLine: SketchLineHelper = { } export const yLine: SketchLineHelper = { - add: ({ node, pathToNode, input, replaceExisting, createCallback }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { from, to } = input + add: ({ + node, + pathToNode, + segmentInput, + replaceExisting, + createCallback, + }) => { + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -772,7 +789,6 @@ export const yLine: SketchLineHelper = { type: 'singleValue', argType: 'yRelative', value: newVal, - argIndex: 0, }, }, ]) @@ -825,13 +841,13 @@ export const tangentialArcTo: SketchLineHelper = { add: ({ node, pathToNode, - input, + segmentInput, createCallback, replaceExisting, referencedSegment, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { to } = input + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -859,7 +875,6 @@ export const tangentialArcTo: SketchLineHelper = { index: 0, argType: 'xRelative', value: toX, - argIndex: 0, }, }, { @@ -869,7 +884,6 @@ export const tangentialArcTo: SketchLineHelper = { index: 1, argType: 'yAbsolute', value: toY, - argIndex: 0, }, }, ], @@ -974,10 +988,16 @@ export const tangentialArcTo: SketchLineHelper = { }, } export const circle: SketchLineHelper = { - add: ({ node, pathToNode, input, createCallback, replaceExisting }) => { - if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR + add: ({ + node, + pathToNode, + segmentInput, + createCallback, + replaceExisting, + }) => { + if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR - const { center, radius } = input + const { center, radius } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1003,7 +1023,6 @@ export const circle: SketchLineHelper = { key: 'center', argType: 'xAbsolute', value: x, - argIndex: 0, }, }, { @@ -1014,7 +1033,6 @@ export const circle: SketchLineHelper = { key: 'center', argType: 'yAbsolute', value: y, - argIndex: 0, }, }, { @@ -1024,7 +1042,6 @@ export const circle: SketchLineHelper = { key: 'radius', argType: 'radius', value: radiusExp, - argIndex: 0, }, }, ]) @@ -1154,13 +1171,13 @@ export const angledLine: SketchLineHelper = { add: ({ node, pathToNode, - input, + segmentInput, createCallback, replaceExisting, referencedSegment, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { from, to } = input + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const getNode = getNodeFromPathCurry(_node, pathToNode) const _node1 = getNode('PipeExpression') @@ -1186,7 +1203,6 @@ export const angledLine: SketchLineHelper = { key: 'angle', argType: 'angle', value: newAngleVal, - argIndex: 0, }, }, { @@ -1197,7 +1213,6 @@ export const angledLine: SketchLineHelper = { key: 'length', argType: 'length', value: newLengthVal, - argIndex: 0, }, }, ], @@ -1261,12 +1276,12 @@ export const angledLineOfXLength: SketchLineHelper = { node, previousProgramMemory, pathToNode, - input, + segmentInput, createCallback, replaceExisting, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { from, to } = input + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1303,7 +1318,6 @@ export const angledLineOfXLength: SketchLineHelper = { key: 'angle', argType: 'angle', value: angle, - argIndex: 0, }, }, { @@ -1314,7 +1328,6 @@ export const angledLineOfXLength: SketchLineHelper = { key: 'length', argType: 'xRelative', value: xLength, - argIndex: 0, }, }, ]).callExp @@ -1381,12 +1394,12 @@ export const angledLineOfYLength: SketchLineHelper = { node, previousProgramMemory, pathToNode, - input, + segmentInput, createCallback, replaceExisting, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { from, to } = input + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1421,7 +1434,6 @@ export const angledLineOfYLength: SketchLineHelper = { key: 'angle', argType: 'angle', value: angle, - argIndex: 0, }, }, { @@ -1432,7 +1444,6 @@ export const angledLineOfYLength: SketchLineHelper = { key: 'length', argType: 'yRelative', value: yLength, - argIndex: 0, }, }, ]).callExp @@ -1498,13 +1509,13 @@ export const angledLineToX: SketchLineHelper = { add: ({ node, pathToNode, - input, + segmentInput, createCallback, replaceExisting, referencedSegment, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { from, to } = input + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1527,7 +1538,6 @@ export const angledLineToX: SketchLineHelper = { key: 'angle', argType: 'angle', value: angle, - argIndex: 0, }, }, { @@ -1538,7 +1548,6 @@ export const angledLineToX: SketchLineHelper = { key: 'to', argType: 'xAbsolute', value: xArg, - argIndex: 0, }, }, ], @@ -1608,13 +1617,13 @@ export const angledLineToY: SketchLineHelper = { add: ({ node, pathToNode, - input, + segmentInput, createCallback, replaceExisting, referencedSegment, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { from, to } = input + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1639,7 +1648,6 @@ export const angledLineToY: SketchLineHelper = { key: 'angle', argType: 'angle', value: angle, - argIndex: 0, }, }, { @@ -1650,7 +1658,6 @@ export const angledLineToY: SketchLineHelper = { key: 'to', argType: 'yAbsolute', value: yArg, - argIndex: 0, }, }, ], @@ -1720,13 +1727,13 @@ export const angledLineThatIntersects: SketchLineHelper = { add: ({ node, pathToNode, - input, + segmentInput, createCallback, replaceExisting, referencedSegment, }) => { - if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR - const { from, to } = input + if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR + const { from, to } = segmentInput const _node = { ...node } const nodeMeta = getNodeFromPath( _node, @@ -1762,7 +1769,6 @@ export const angledLineThatIntersects: SketchLineHelper = { key: 'angle', argType: 'angle', value: angle, - argIndex: 0, }, }, { @@ -1772,7 +1778,6 @@ export const angledLineThatIntersects: SketchLineHelper = { key: 'offset', argType: 'intersectionOffset', value: offset, - argIndex: 0, }, }, ]) @@ -2096,7 +2101,7 @@ export function addNewSketchLn({ programMemory: previousProgramMemory, fnName, pathToNode, - input, + input: segmentInput, spliceBetween = false, }: CreateLineFnCallArgs): | { @@ -2120,7 +2125,7 @@ export function addNewSketchLn({ node, previousProgramMemory, pathToNode, - input, + segmentInput, replaceExisting: false, spliceBetween, }) @@ -2182,7 +2187,7 @@ export function replaceSketchLine({ programMemory, pathToNode: _pathToNode, fnName, - input, + segmentInput, createCallback, referencedSegment, }: { @@ -2190,7 +2195,7 @@ export function replaceSketchLine({ programMemory: ProgramMemory pathToNode: PathToNode fnName: ToolTip - input: SegmentInputs + segmentInput: SegmentInputs createCallback: TransformCallback referencedSegment?: Path }): @@ -2211,7 +2216,7 @@ export function replaceSketchLine({ previousProgramMemory: programMemory, pathToNode: _pathToNode, referencedSegment, - input, + segmentInput, replaceExisting: true, createCallback, }) diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index fed713ea1e..46e2542e13 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1806,7 +1806,6 @@ export function transformAstSketchLines({ index: a.argPosition.index, value: nodeMeta.node, argType: a.type, - argIndex: a.argPosition.argIndex, }, varExpression: nodeMeta.node, }) @@ -1817,7 +1816,6 @@ export function transformAstSketchLines({ key: a.argPosition.key, value: nodeMeta.node, argType: a.type, - argIndex: a.argPosition.argIndex, }, varExpression: nodeMeta.node, }) @@ -1827,7 +1825,6 @@ export function transformAstSketchLines({ type: 'singleValue', argType: a.type, value: nodeMeta.node, - argIndex: a.argPosition.argIndex, }, varExpression: nodeMeta.node, }) @@ -1839,7 +1836,6 @@ export function transformAstSketchLines({ index: a.argPosition.index, value: nodeMeta.node, argType: a.type, - argIndex: 0, }, varExpression: nodeMeta.node, }) @@ -1885,7 +1881,7 @@ export function transformAstSketchLines({ pathToNode: _pathToNode, referencedSegment, fnName: transformTo || (callExp.node.callee.name as ToolTip), - input: + segmentInput: seg.type === 'Circle' ? { type: 'arc-segment', diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index 0868891e20..6da7935016 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -66,7 +66,7 @@ interface ArcSegmentInput { export type SegmentInputs = StraightSegmentInput | ArcSegmentInput interface addCall extends ModifyAstBase { - input: SegmentInputs + segmentInput: SegmentInputs referencedSegment?: Path replaceExisting?: boolean createCallback?: TransformCallback // TODO: #29 probably should not be optional @@ -90,21 +90,18 @@ export interface SingleValueInput { type: 'singleValue' argType: LineInputsType | 'radius' value: T - argIndex: number } export interface ArrayItemInput { type: 'arrayItem' index: 0 | 1 argType: LineInputsType | 'radius' value: T - argIndex: number } export interface ObjectPropertyInput { type: 'objectProperty' key: VarValueKeys argType: LineInputsType | 'radius' value: T - argIndex: number } export interface ArrayOrObjItemInput { @@ -113,7 +110,6 @@ export interface ArrayOrObjItemInput { index: 0 | 1 argType: LineInputsType | 'radius' value: T - argIndex: number } export interface ObjectPropertyArrayInput { @@ -122,7 +118,6 @@ export interface ObjectPropertyArrayInput { argType: LineInputsType | 'radius' index: 0 | 1 value: T - argIndex: number } export type _VarValue = @@ -141,10 +136,9 @@ export type RawValues = Array export type SimplifiedVarValue = | { type: 'singleValue' - argIndex: number } - | { type: 'arrayItem'; index: 0 | 1; argIndex: number } - | { type: 'objectProperty'; key: VarValueKeys; argIndex: number } + | { type: 'arrayItem'; index: 0 | 1 } + | { type: 'objectProperty'; key: VarValueKeys } | { type: 'arrayInObject' key: VarValueKeys @@ -157,7 +151,6 @@ export interface SegmentInput { } export type TransformCallback = ( - // args: Array, inputs: SegmentInput[], referencedSegment?: Path ) => { From bb265ca8335ad40856150afef11eb8b833891772 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 22:10:02 +1000 Subject: [PATCH 31/41] few more minor renames --- src/lang/std/sketch.ts | 4 ++-- src/lang/std/sketchcombos.ts | 14 +++++++------- src/lang/std/stdTypes.ts | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 34c652aecc..e39244db07 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -1018,7 +1018,7 @@ export const circle: SketchLineHelper = { { varExpression: x, varDetails: { - type: 'objectPropertyArray', + type: 'arrayInObject', index: 0, key: 'center', argType: 'xAbsolute', @@ -1028,7 +1028,7 @@ export const circle: SketchLineHelper = { { varExpression: y, varDetails: { - type: 'objectPropertyArray', + type: 'arrayInObject', index: 1, key: 'center', argType: 'yAbsolute', diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index 46e2542e13..ad47880775 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1422,15 +1422,15 @@ export function removeSingleConstraint({ if ( varValue.type !== 'objectProperty' && varValue.type !== 'arrayOrObjItem' && - varValue.type !== 'objectPropertyArray' + varValue.type !== 'arrayInObject' ) return const rawLiteralArrayInObject = rawValues.find( (rawValue) => - rawValue.varDetails.type === 'objectPropertyArray' && + rawValue.varDetails.type === 'arrayInObject' && rawValue.varDetails.key === inputDetails.key && rawValue.varDetails.index === - (varValue.type === 'objectPropertyArray' + (varValue.type === 'arrayInObject' ? varValue.index : -1) ) @@ -1438,13 +1438,13 @@ export function removeSingleConstraint({ (rawValue) => (rawValue.varDetails.type === 'objectProperty' || rawValue.varDetails.type === 'arrayOrObjItem' || - rawValue.varDetails.type === 'objectPropertyArray') && + rawValue.varDetails.type === 'arrayInObject') && rawValue.varDetails.key === inputDetails.key ) if ( inputDetails.type === 'arrayInObject' && rawLiteralArrayInObject?.varDetails.type === - 'objectPropertyArray' && + 'arrayInObject' && rawLiteralArrayInObject?.varDetails.index === inputDetails.index && rawLiteralArrayInObject?.varDetails.key === inputDetails.key @@ -1462,7 +1462,7 @@ export function removeSingleConstraint({ varValue.key === inputDetails.key ) { otherThing[inputDetails.key] = rawLiteralObjProp.varDetails.value - } else if (varValue.type === 'objectPropertyArray') { + } else if (varValue.type === 'arrayInObject') { if (!arrayDetailsNameBetterLater[varValue.key]) arrayDetailsNameBetterLater[varValue.key] = [] arrayDetailsNameBetterLater[varValue.key][varValue.index] = @@ -1831,7 +1831,7 @@ export function transformAstSketchLines({ } else if (a?.argPosition?.type === 'arrayInObject') { inputs.push({ varDetails: { - type: 'objectPropertyArray', + type: 'arrayInObject', key: a.argPosition.key, index: a.argPosition.index, value: nodeMeta.node, diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index 6da7935016..c8f8040063 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -112,8 +112,8 @@ export interface ArrayOrObjItemInput { value: T } -export interface ObjectPropertyArrayInput { - type: 'objectPropertyArray' +export interface ArrayInObject { + type: 'arrayInObject' key: VarValueKeys argType: LineInputsType | 'radius' index: 0 | 1 @@ -125,7 +125,7 @@ export type _VarValue = | ArrayItemInput | ObjectPropertyInput | ArrayOrObjItemInput - | ObjectPropertyArrayInput + | ArrayInObject export type VarValue = _VarValue export type RawValue = _VarValue From 4219a2c31d3eefed0c155f0ec763ac2fdb9ea7fb Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 22:38:02 +1000 Subject: [PATCH 32/41] more minor clean up renaming --- src/lang/modifyAst.test.ts | 6 ++-- src/lang/modifyAst.ts | 4 +-- src/lang/std/sketch.ts | 54 ++++++++++++++++----------------- src/lang/std/sketchcombos.ts | 35 ++++++++++----------- src/lang/std/stdTypes.ts | 59 +++++++++++++++++++++++++++--------- 5 files changed, 94 insertions(+), 64 deletions(-) diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts index 7eaf784612..4fed48ba4f 100644 --- a/src/lang/modifyAst.test.ts +++ b/src/lang/modifyAst.test.ts @@ -20,7 +20,7 @@ import { import { enginelessExecutor } from '../lib/testHelpers' import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst' import { err } from 'lib/trap' -import { SimplifiedVarValue, VarValueKeys } from './std/stdTypes' +import { SimplifiedArgDetails, VarValueKeys } from './std/stdTypes' beforeAll(async () => { await initPromise @@ -639,7 +639,7 @@ describe('Testing removeSingleConstraintInfo', () => { code.indexOf(lineOfInterest) + lineOfInterest.length, ] const pathToNode = getNodePathFromSourceRange(ast, range) - let argPosition: SimplifiedVarValue + let argPosition: SimplifiedArgDetails if (key === 'arrayIndex' && typeof value === 'number') { argPosition = { type: 'arrayItem', @@ -692,7 +692,7 @@ describe('Testing removeSingleConstraintInfo', () => { code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + lineOfInterest.length, ] - let argPosition: SimplifiedVarValue + let argPosition: SimplifiedArgDetails if (key === 'arrayIndex' && typeof value === 'number') { argPosition = { type: 'arrayItem', diff --git a/src/lang/modifyAst.ts b/src/lang/modifyAst.ts index bcd3cb5956..738d755dfa 100644 --- a/src/lang/modifyAst.ts +++ b/src/lang/modifyAst.ts @@ -38,7 +38,7 @@ import { import { DefaultPlaneStr } from 'clientSideScene/sceneEntities' import { isOverlap, roundOff } from 'lib/utils' import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants' -import { SimplifiedVarValue } from './std/stdTypes' +import { SimplifiedArgDetails } from './std/stdTypes' import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator' import { Models } from '@kittycad/lib' @@ -831,7 +831,7 @@ export function deleteSegmentFromPipeExpression( export function removeSingleConstraintInfo( pathToCallExp: PathToNode, - varValue: SimplifiedVarValue, + varValue: SimplifiedArgDetails, ast: Program, programMemory: ProgramMemory ): diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index e39244db07..6dc8edeffb 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -35,7 +35,7 @@ import { SingleValueInput, AddTagInfo, SegmentInputs, - SimplifiedVarValue, + SimplifiedArgDetails, } from 'lang/std/stdTypes' import { @@ -112,7 +112,7 @@ type AbbreviatedInput = | ArrayItemInput['index'] | ObjectPropertyInput['key'] | SingleValueInput['type'] - | SimplifiedVarValue + | SimplifiedArgDetails | undefined const constrainInfo = ( @@ -329,7 +329,7 @@ export const lineTo: SketchLineHelper = { type: 'arrayItem', index: 0, argType: 'xAbsolute', - value: createLiteral(roundOff(to[0], 2)), + expr: createLiteral(roundOff(to[0], 2)), }, }, { @@ -338,7 +338,7 @@ export const lineTo: SketchLineHelper = { type: 'arrayItem', index: 1, argType: 'yAbsolute', - value: createLiteral(roundOff(to[1], 2)), + expr: createLiteral(roundOff(to[1], 2)), }, }, ], @@ -455,7 +455,7 @@ export const line: SketchLineHelper = { type: 'arrayItem', index: 0, argType: 'xRelative', - value: createLiteral(roundOff(to[0] - from[0], 2)), + expr: createLiteral(roundOff(to[0] - from[0], 2)), }, }, { @@ -464,7 +464,7 @@ export const line: SketchLineHelper = { type: 'arrayItem', index: 1, argType: 'yRelative', - value: createLiteral(roundOff(to[1] - from[1], 2)), + expr: createLiteral(roundOff(to[1] - from[1], 2)), }, }, ], @@ -561,7 +561,7 @@ export const xLineTo: SketchLineHelper = { varDetails: { type: 'singleValue', argType: 'xAbsolute', - value: createLiteral(roundOff(to[0], 2)), + expr: createLiteral(roundOff(to[0], 2)), }, }, ]) @@ -638,7 +638,7 @@ export const yLineTo: SketchLineHelper = { varDetails: { type: 'singleValue', argType: 'yAbsolute', - value: newVal, + expr: newVal, }, }, ]) @@ -715,7 +715,7 @@ export const xLine: SketchLineHelper = { varDetails: { type: 'singleValue', argType: 'xRelative', - value: newVal, + expr: newVal, }, }, ]) @@ -788,7 +788,7 @@ export const yLine: SketchLineHelper = { varDetails: { type: 'singleValue', argType: 'yRelative', - value: newVal, + expr: newVal, }, }, ]) @@ -874,7 +874,7 @@ export const tangentialArcTo: SketchLineHelper = { type: 'arrayItem', index: 0, argType: 'xRelative', - value: toX, + expr: toX, }, }, { @@ -883,7 +883,7 @@ export const tangentialArcTo: SketchLineHelper = { type: 'arrayItem', index: 1, argType: 'yAbsolute', - value: toY, + expr: toY, }, }, ], @@ -1022,7 +1022,7 @@ export const circle: SketchLineHelper = { index: 0, key: 'center', argType: 'xAbsolute', - value: x, + expr: x, }, }, { @@ -1032,7 +1032,7 @@ export const circle: SketchLineHelper = { index: 1, key: 'center', argType: 'yAbsolute', - value: y, + expr: y, }, }, { @@ -1041,7 +1041,7 @@ export const circle: SketchLineHelper = { type: 'objectProperty', key: 'radius', argType: 'radius', - value: radiusExp, + expr: radiusExp, }, }, ]) @@ -1202,7 +1202,7 @@ export const angledLine: SketchLineHelper = { index: 0, key: 'angle', argType: 'angle', - value: newAngleVal, + expr: newAngleVal, }, }, { @@ -1212,7 +1212,7 @@ export const angledLine: SketchLineHelper = { index: 1, key: 'length', argType: 'length', - value: newLengthVal, + expr: newLengthVal, }, }, ], @@ -1317,7 +1317,7 @@ export const angledLineOfXLength: SketchLineHelper = { index: 0, key: 'angle', argType: 'angle', - value: angle, + expr: angle, }, }, { @@ -1327,7 +1327,7 @@ export const angledLineOfXLength: SketchLineHelper = { index: 1, key: 'length', argType: 'xRelative', - value: xLength, + expr: xLength, }, }, ]).callExp @@ -1433,7 +1433,7 @@ export const angledLineOfYLength: SketchLineHelper = { index: 0, key: 'angle', argType: 'angle', - value: angle, + expr: angle, }, }, { @@ -1443,7 +1443,7 @@ export const angledLineOfYLength: SketchLineHelper = { index: 1, key: 'length', argType: 'yRelative', - value: yLength, + expr: yLength, }, }, ]).callExp @@ -1537,7 +1537,7 @@ export const angledLineToX: SketchLineHelper = { index: 0, key: 'angle', argType: 'angle', - value: angle, + expr: angle, }, }, { @@ -1547,7 +1547,7 @@ export const angledLineToX: SketchLineHelper = { index: 1, key: 'to', argType: 'xAbsolute', - value: xArg, + expr: xArg, }, }, ], @@ -1647,7 +1647,7 @@ export const angledLineToY: SketchLineHelper = { index: 0, key: 'angle', argType: 'angle', - value: angle, + expr: angle, }, }, { @@ -1657,7 +1657,7 @@ export const angledLineToY: SketchLineHelper = { index: 1, key: 'to', argType: 'yAbsolute', - value: yArg, + expr: yArg, }, }, ], @@ -1768,7 +1768,7 @@ export const angledLineThatIntersects: SketchLineHelper = { type: 'objectProperty', key: 'angle', argType: 'angle', - value: angle, + expr: angle, }, }, { @@ -1777,7 +1777,7 @@ export const angledLineThatIntersects: SketchLineHelper = { type: 'objectProperty', key: 'offset', argType: 'intersectionOffset', - value: offset, + expr: offset, }, }, ]) diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index ad47880775..95d84c92e1 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1,4 +1,8 @@ -import { SegmentInput, SimplifiedVarValue, TransformCallback } from './stdTypes' +import { + SegmentInput, + SimplifiedArgDetails, + TransformCallback, +} from './stdTypes' import { ToolTip, toolTips } from 'lang/langHelpers' import { Selections, Selection } from 'lib/selections' import { cleanErrs, err } from 'lib/trap' @@ -1355,7 +1359,7 @@ export function removeSingleConstraint({ ast, }: { pathToCallExp: PathToNode - inputDetails: SimplifiedVarValue + inputDetails: SimplifiedArgDetails ast: Program }): TransformInfo | false { const callExp = getNodeFromPath( @@ -1392,16 +1396,16 @@ export function removeSingleConstraint({ varValue.index === inputDetails.index ) ) - return varValue.value + return varValue.expr const literal = rawValues.find( (rawValue) => (rawValue.varDetails.type === 'arrayItem' || rawValue.varDetails.type === 'arrayOrObjItem') && rawValue.varDetails.index === inputDetails.index - )?.varDetails?.value + )?.varDetails?.expr return ( (varValue.index === inputDetails.index && literal) || - varValue.value + varValue.expr ) }) return createStdlibCallExpression( @@ -1430,9 +1434,7 @@ export function removeSingleConstraint({ rawValue.varDetails.type === 'arrayInObject' && rawValue.varDetails.key === inputDetails.key && rawValue.varDetails.index === - (varValue.type === 'arrayInObject' - ? varValue.index - : -1) + (varValue.type === 'arrayInObject' ? varValue.index : -1) ) const rawLiteralObjProp = rawValues.find( (rawValue) => @@ -1443,8 +1445,7 @@ export function removeSingleConstraint({ ) if ( inputDetails.type === 'arrayInObject' && - rawLiteralArrayInObject?.varDetails.type === - 'arrayInObject' && + rawLiteralArrayInObject?.varDetails.type === 'arrayInObject' && rawLiteralArrayInObject?.varDetails.index === inputDetails.index && rawLiteralArrayInObject?.varDetails.key === inputDetails.key @@ -1453,7 +1454,7 @@ export function removeSingleConstraint({ arrayDetailsNameBetterLater[varValue.key] = [] arrayDetailsNameBetterLater[inputDetails.key][ inputDetails.index - ] = rawLiteralArrayInObject.varDetails.value + ] = rawLiteralArrayInObject.varDetails.expr } else if ( inputDetails.type === 'objectProperty' && (rawLiteralObjProp?.varDetails.type === 'objectProperty' || @@ -1461,7 +1462,7 @@ export function removeSingleConstraint({ rawLiteralObjProp?.varDetails.key === inputDetails.key && varValue.key === inputDetails.key ) { - otherThing[inputDetails.key] = rawLiteralObjProp.varDetails.value + otherThing[inputDetails.key] = rawLiteralObjProp.varDetails.expr } else if (varValue.type === 'arrayInObject') { if (!arrayDetailsNameBetterLater[varValue.key]) arrayDetailsNameBetterLater[varValue.key] = [] @@ -1491,7 +1492,7 @@ export function removeSingleConstraint({ return createCallWrapper( callExp.node.callee.name as any, - rawValues[0].varDetails.value, + rawValues[0].varDetails.expr, tag ) }, @@ -1804,7 +1805,7 @@ export function transformAstSketchLines({ varDetails: { type: 'arrayItem', index: a.argPosition.index, - value: nodeMeta.node, + expr: nodeMeta.node, argType: a.type, }, varExpression: nodeMeta.node, @@ -1814,7 +1815,7 @@ export function transformAstSketchLines({ varDetails: { type: 'objectProperty', key: a.argPosition.key, - value: nodeMeta.node, + expr: nodeMeta.node, argType: a.type, }, varExpression: nodeMeta.node, @@ -1824,7 +1825,7 @@ export function transformAstSketchLines({ varDetails: { type: 'singleValue', argType: a.type, - value: nodeMeta.node, + expr: nodeMeta.node, }, varExpression: nodeMeta.node, }) @@ -1834,7 +1835,7 @@ export function transformAstSketchLines({ type: 'arrayInObject', key: a.argPosition.key, index: a.argPosition.index, - value: nodeMeta.node, + expr: nodeMeta.node, argType: a.type, }, varExpression: nodeMeta.node, diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index c8f8040063..44496d7476 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -89,51 +89,80 @@ export type VarValueKeys = export interface SingleValueInput { type: 'singleValue' argType: LineInputsType | 'radius' - value: T + expr: T } export interface ArrayItemInput { type: 'arrayItem' index: 0 | 1 argType: LineInputsType | 'radius' - value: T + expr: T } export interface ObjectPropertyInput { type: 'objectProperty' key: VarValueKeys argType: LineInputsType | 'radius' - value: T + expr: T } -export interface ArrayOrObjItemInput { +interface ArrayOrObjItemInput { type: 'arrayOrObjItem' key: VarValueKeys index: 0 | 1 argType: LineInputsType | 'radius' - value: T + expr: T } -export interface ArrayInObject { +interface ArrayInObject { type: 'arrayInObject' key: VarValueKeys argType: LineInputsType | 'radius' index: 0 | 1 - value: T + expr: T } -export type _VarValue = +type _VarValue = | SingleValueInput | ArrayItemInput | ObjectPropertyInput | ArrayOrObjItemInput | ArrayInObject -export type VarValue = _VarValue -export type RawValue = _VarValue +/** + * {@link RawArg.expr} is the current expression for each of the args for a segment + * i.e. if the expression is 5 + 6, {@link RawArg.expr} will be that binary expression + * + * Other properties on this type describe how the args are defined for this particular segment + * i.e. line uses [x, y] style inputs, while angledLine uses either [angle, length] or {angle, length} + * and circle uses {center: [x, y], radius: number} + * Which is why a union type is used that can be type narrowed using the {@link RawArg.type} property + * {@link RawArg.expr} is common to all of these types + */ +type InputArg = _VarValue -export type VarValues = Array -export type RawValues = Array +/** + * {@link RawArg.expr} is the literal equivalent of whatever current expression is + * i.e. if the expression is 5 + 6, the literal would be 11 + * but of course works for expressions like myVar + someFn() etc too + * This is useful in cases where we want to "un-constrain" inputs to segments + */ +type RawArg = _VarValue -export type SimplifiedVarValue = +type InputArgs = Array + +// /** +// * The literal equivalent of whatever current expression is +// * i.e. if the expression is 5 + 6, the literal would be 11 +// * but of course works for expressions like myVar + someFn() etc too +// * This is useful in cases where we want to "un-constrain" inputs to segments +// */ +type RawArgs = Array + +/** + * Serves the same role as {@link InputArg} on {@link RawArg} + * but without the {@link RawArg.expr} property, since it is not needed + * when we only need to know where there arg is. + */ +export type SimplifiedArgDetails = | { type: 'singleValue' } @@ -147,7 +176,7 @@ export type SimplifiedVarValue = export interface SegmentInput { varExpression: Expr - varDetails: VarValue + varDetails: InputArg } export type TransformCallback = ( @@ -171,7 +200,7 @@ export interface ConstrainInfo { pathToNode: PathToNode value: string calculatedValue?: any - argPosition?: SimplifiedVarValue + argPosition?: SimplifiedArgDetails } export interface SketchLineHelper { From 99ffc82ffa27f31d5a97a7a274bc10b0301f561f Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Mon, 9 Sep 2024 22:58:36 +1000 Subject: [PATCH 33/41] change types that that effected a lot of places --- src/lang/std/sketch.ts | 291 +++++++++++----------------- src/lang/std/sketchcombos.ts | 366 ++++++++++++++--------------------- src/lang/std/stdTypes.ts | 11 +- 3 files changed, 254 insertions(+), 414 deletions(-) diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 6dc8edeffb..f8b645cd2c 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -324,22 +324,16 @@ export const lineTo: SketchLineHelper = { const { callExp, valueUsedInTransform } = createCallback( [ { - varExpression: createLiteral(roundOff(to[0], 2)), - varDetails: { - type: 'arrayItem', - index: 0, - argType: 'xAbsolute', - expr: createLiteral(roundOff(to[0], 2)), - }, + type: 'arrayItem', + index: 0, + argType: 'xAbsolute', + expr: createLiteral(roundOff(to[0], 2)), }, { - varExpression: createLiteral(roundOff(to[1], 2)), - varDetails: { - type: 'arrayItem', - index: 1, - argType: 'yAbsolute', - expr: createLiteral(roundOff(to[1], 2)), - }, + type: 'arrayItem', + index: 1, + argType: 'yAbsolute', + expr: createLiteral(roundOff(to[1], 2)), }, ], referencedSegment @@ -450,22 +444,16 @@ export const line: SketchLineHelper = { const { callExp, valueUsedInTransform } = createCallback( [ { - varExpression: createLiteral(roundOff(to[0] - from[0], 2)), - varDetails: { - type: 'arrayItem', - index: 0, - argType: 'xRelative', - expr: createLiteral(roundOff(to[0] - from[0], 2)), - }, + type: 'arrayItem', + index: 0, + argType: 'xRelative', + expr: createLiteral(roundOff(to[0] - from[0], 2)), }, { - varExpression: createLiteral(roundOff(to[1] - from[1], 2)), - varDetails: { - type: 'arrayItem', - index: 1, - argType: 'yRelative', - expr: createLiteral(roundOff(to[1] - from[1], 2)), - }, + type: 'arrayItem', + index: 1, + argType: 'yRelative', + expr: createLiteral(roundOff(to[1] - from[1], 2)), }, ], referencedSegment @@ -557,12 +545,9 @@ export const xLineTo: SketchLineHelper = { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { - varExpression: createLiteral(roundOff(to[0], 2)), - varDetails: { - type: 'singleValue', - argType: 'xAbsolute', - expr: createLiteral(roundOff(to[0], 2)), - }, + type: 'singleValue', + argType: 'xAbsolute', + expr: createLiteral(roundOff(to[0], 2)), }, ]) pipe.body[callIndex] = callExp @@ -634,12 +619,9 @@ export const yLineTo: SketchLineHelper = { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { - varExpression: newVal, - varDetails: { - type: 'singleValue', - argType: 'yAbsolute', - expr: newVal, - }, + type: 'singleValue', + argType: 'yAbsolute', + expr: newVal, }, ]) pipe.body[callIndex] = callExp @@ -711,12 +693,9 @@ export const xLine: SketchLineHelper = { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { - varExpression: newVal, - varDetails: { - type: 'singleValue', - argType: 'xRelative', - expr: newVal, - }, + type: 'singleValue', + argType: 'xRelative', + expr: newVal, }, ]) pipe.body[callIndex] = callExp @@ -784,12 +763,9 @@ export const yLine: SketchLineHelper = { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { - varExpression: newVal, - varDetails: { - type: 'singleValue', - argType: 'yRelative', - expr: newVal, - }, + type: 'singleValue', + argType: 'yRelative', + expr: newVal, }, ]) pipe.body[callIndex] = callExp @@ -869,22 +845,16 @@ export const tangentialArcTo: SketchLineHelper = { const { callExp, valueUsedInTransform } = createCallback( [ { - varExpression: toX, - varDetails: { - type: 'arrayItem', - index: 0, - argType: 'xRelative', - expr: toX, - }, + type: 'arrayItem', + index: 0, + argType: 'xRelative', + expr: toX, }, { - varExpression: toY, - varDetails: { - type: 'arrayItem', - index: 1, - argType: 'yAbsolute', - expr: toY, - }, + type: 'arrayItem', + index: 1, + argType: 'yAbsolute', + expr: toY, }, ], referencedSegment @@ -1016,33 +986,24 @@ export const circle: SketchLineHelper = { if (replaceExisting && createCallback) { const { callExp, valueUsedInTransform } = createCallback([ { - varExpression: x, - varDetails: { - type: 'arrayInObject', - index: 0, - key: 'center', - argType: 'xAbsolute', - expr: x, - }, + type: 'arrayInObject', + index: 0, + key: 'center', + argType: 'xAbsolute', + expr: x, }, { - varExpression: y, - varDetails: { - type: 'arrayInObject', - index: 1, - key: 'center', - argType: 'yAbsolute', - expr: y, - }, + type: 'arrayInObject', + index: 1, + key: 'center', + argType: 'yAbsolute', + expr: y, }, { - varExpression: radiusExp, - varDetails: { - type: 'objectProperty', - key: 'radius', - argType: 'radius', - expr: radiusExp, - }, + type: 'objectProperty', + key: 'radius', + argType: 'radius', + expr: radiusExp, }, ]) @@ -1196,24 +1157,18 @@ export const angledLine: SketchLineHelper = { const { callExp, valueUsedInTransform } = createCallback( [ { - varExpression: newAngleVal, - varDetails: { - type: 'arrayOrObjItem', - index: 0, - key: 'angle', - argType: 'angle', - expr: newAngleVal, - }, + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: newAngleVal, }, { - varExpression: newLengthVal, - varDetails: { - type: 'arrayOrObjItem', - index: 1, - key: 'length', - argType: 'length', - expr: newLengthVal, - }, + type: 'arrayOrObjItem', + index: 1, + key: 'length', + argType: 'length', + expr: newLengthVal, }, ], referencedSegment @@ -1311,24 +1266,18 @@ export const angledLineOfXLength: SketchLineHelper = { const newLine = createCallback ? createCallback([ { - varExpression: angle, - varDetails: { - type: 'arrayOrObjItem', - index: 0, - key: 'angle', - argType: 'angle', - expr: angle, - }, + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: angle, }, { - varExpression: xLength, - varDetails: { - type: 'arrayOrObjItem', - index: 1, - key: 'length', - argType: 'xRelative', - expr: xLength, - }, + type: 'arrayOrObjItem', + index: 1, + key: 'length', + argType: 'xRelative', + expr: xLength, }, ]).callExp : createCallExpression('angledLineOfXLength', [ @@ -1427,24 +1376,18 @@ export const angledLineOfYLength: SketchLineHelper = { const newLine = createCallback ? createCallback([ { - varExpression: angle, - varDetails: { - type: 'arrayOrObjItem', - index: 0, - key: 'angle', - argType: 'angle', - expr: angle, - }, + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: angle, }, { - varExpression: yLength, - varDetails: { - type: 'arrayOrObjItem', - index: 1, - key: 'length', - argType: 'yRelative', - expr: yLength, - }, + type: 'arrayOrObjItem', + index: 1, + key: 'length', + argType: 'yRelative', + expr: yLength, }, ]).callExp : createCallExpression('angledLineOfYLength', [ @@ -1531,24 +1474,18 @@ export const angledLineToX: SketchLineHelper = { const { callExp, valueUsedInTransform } = createCallback( [ { - varExpression: angle, - varDetails: { - type: 'arrayOrObjItem', - index: 0, - key: 'angle', - argType: 'angle', - expr: angle, - }, + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: angle, }, { - varExpression: xArg, - varDetails: { - type: 'arrayOrObjItem', - index: 1, - key: 'to', - argType: 'xAbsolute', - expr: xArg, - }, + type: 'arrayOrObjItem', + index: 1, + key: 'to', + argType: 'xAbsolute', + expr: xArg, }, ], referencedSegment @@ -1641,24 +1578,18 @@ export const angledLineToY: SketchLineHelper = { const { callExp, valueUsedInTransform } = createCallback( [ { - varExpression: angle, - varDetails: { - type: 'arrayOrObjItem', - index: 0, - key: 'angle', - argType: 'angle', - expr: angle, - }, + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: angle, }, { - varExpression: yArg, - varDetails: { - type: 'arrayOrObjItem', - index: 1, - key: 'to', - argType: 'yAbsolute', - expr: yArg, - }, + type: 'arrayOrObjItem', + index: 1, + key: 'to', + argType: 'yAbsolute', + expr: yArg, }, ], referencedSegment @@ -1763,22 +1694,16 @@ export const angledLineThatIntersects: SketchLineHelper = { if (replaceExisting && createCallback) { const { callExp, valueUsedInTransform } = createCallback([ { - varExpression: angle, - varDetails: { - type: 'objectProperty', - key: 'angle', - argType: 'angle', - expr: angle, - }, + type: 'objectProperty', + key: 'angle', + argType: 'angle', + expr: angle, }, { - varExpression: offset, - varDetails: { - type: 'objectProperty', - key: 'offset', - argType: 'intersectionOffset', - expr: offset, - }, + type: 'objectProperty', + key: 'offset', + argType: 'intersectionOffset', + expr: offset, }, ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index 95d84c92e1..cfce31458f 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1,5 +1,6 @@ import { - SegmentInput, + InputArg, + InputArgs, SimplifiedArgDetails, TransformCallback, } from './stdTypes' @@ -161,7 +162,7 @@ function intersectCallWrapper({ export type TransformInfo = { tooltip: ToolTip createNode: (a: { - inputs: SegmentInput[] + inputs: InputArgs referenceSegName: string tag?: Expr forceValueUsedInTransform?: Expr @@ -188,13 +189,8 @@ const xyLineSetLength = ? forceValueUsedInTransform : referenceSeg ? segRef - : args[0].varExpression - return createCallWrapper( - xOrY, - lineVal, - tag, - getArgLiteralVal(args[0].varExpression) - ) + : args[0].expr + return createCallWrapper(xOrY, lineVal, tag, getArgLiteralVal(args[0].expr)) } const basicAngledLineCreateNode = @@ -203,27 +199,25 @@ const basicAngledLineCreateNode = valToForce: 'ang' | 'len' | 'none' = 'none', varValToUse: 'ang' | 'len' | 'none' = 'none' ): TransformInfo['createNode'] => - ( - { referenceSegName, tag, forceValueUsedInTransform, inputs } //, varValA, varValB }) => - ) => + ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => (args, path) => { const refAng = path ? getAngle(path?.from, path?.to) : 0 const nonForcedAng = varValToUse === 'ang' - ? inputs[0].varExpression + ? inputs[0].expr : referenceSeg === 'ang' ? getClosesAngleDirection( - args[0].varExpression, + args[0].expr, refAng, createSegAngle(referenceSegName) as BinaryPart ) - : args[0].varExpression + : args[0].expr const nonForcedLen = varValToUse === 'len' - ? inputs[1].varExpression + ? inputs[1].expr : referenceSeg === 'len' ? createSegLen(referenceSegName) - : args[1].varExpression + : args[1].expr const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform return createCallWrapper( @@ -233,9 +227,7 @@ const basicAngledLineCreateNode = shouldForceLen ? forceValueUsedInTransform : nonForcedLen, ], tag, - getArgLiteralVal( - valToForce === 'ang' ? args[0].varExpression : args[1].varExpression - ) + getArgLiteralVal(valToForce === 'ang' ? args[0].expr : args[1].expr) ) } const angledLineAngleCreateNode: TransformInfo['createNode'] = @@ -243,7 +235,7 @@ const angledLineAngleCreateNode: TransformInfo['createNode'] = () => createCallWrapper( 'angledLine', - [inputs[0].varExpression, createSegLen(referenceSegName)], + [inputs[0].expr, createSegLen(referenceSegName)], tag ) @@ -317,7 +309,7 @@ const setHorzVertDistanceCreateNode = ({ referenceSegName, tag, forceValueUsedInTransform }) => { return (args, referencedSegment) => { const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[index].varExpression) - + getArgLiteralVal(args?.[index].expr) - (referencedSegment?.to?.[index] || 0), 2 ) @@ -331,9 +323,7 @@ const setHorzVertDistanceCreateNode = } return createCallWrapper( 'lineTo', - !index - ? [finalValue, args[1].varExpression] - : [args[0].varExpression, finalValue], + !index ? [finalValue, args[1].expr] : [args[0].expr, finalValue], tag, valueUsedInTransform ) @@ -347,7 +337,7 @@ const setHorzVertDistanceForAngleLineCreateNode = ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => { return (args, referencedSegment) => { const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[1].varExpression) - + getArgLiteralVal(args?.[1].expr) - (referencedSegment?.to?.[index] || 0), 2 ) @@ -358,7 +348,7 @@ const setHorzVertDistanceForAngleLineCreateNode = ]) return createCallWrapper( xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', - [inputs[0].varExpression, binExp], + [inputs[0].expr, binExp], tag, valueUsedInTransform ) @@ -374,7 +364,7 @@ const setAbsDistanceCreateNode = ({ tag, forceValueUsedInTransform }) => (args) => { const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[index].varExpression), + getArgLiteralVal(args?.[index].expr), 2 ) const val = @@ -390,7 +380,7 @@ const setAbsDistanceCreateNode = } return createCallWrapper( 'lineTo', - !index ? [val, args[1].varExpression] : [args[0].varExpression, val], + !index ? [val, args[1].expr] : [args[0].expr, val], tag, valueUsedInTransform ) @@ -399,16 +389,13 @@ const setAbsDistanceForAngleLineCreateNode = (xOrY: 'x' | 'y'): TransformInfo['createNode'] => ({ tag, forceValueUsedInTransform, inputs }) => { return (args) => { - const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[1].varExpression), - 2 - ) + const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[1].expr), 2) const val = (forceValueUsedInTransform as BinaryPart) || createLiteral(valueUsedInTransform) return createCallWrapper( xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', - [inputs[0].varExpression, val], + [inputs[0].expr, val], tag, valueUsedInTransform ) @@ -421,7 +408,7 @@ const setHorVertDistanceForXYLines = return (args, referencedSegment) => { const index = xOrY === 'x' ? 0 : 1 const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[index].varExpression) - + getArgLiteralVal(args?.[index].expr) - (referencedSegment?.to?.[index] || 0), 2 ) @@ -442,7 +429,7 @@ const setHorVertDistanceForXYLines = const setHorzVertDistanceConstraintLineCreateNode = (isX: boolean): TransformInfo['createNode'] => ({ referenceSegName, tag, inputs }) => { - let varVal = isX ? inputs[1].varExpression : inputs[0].varExpression + let varVal = isX ? inputs[1].expr : inputs[0].expr varVal = isExprBinaryPart(varVal) ? varVal : createLiteral(0) const varValBinExp = createBinaryExpressionWithUnary([ createLastSeg(!isX), @@ -451,7 +438,7 @@ const setHorzVertDistanceConstraintLineCreateNode = return (args, referencedSegment) => { const makeBinExp = (index: 0 | 1) => { - const arg = getArgLiteralVal(args?.[index].varExpression) + const arg = getArgLiteralVal(args?.[index].expr) return createBinaryExpressionWithUnary([ createSegEnd(referenceSegName, isX), createLiteral( @@ -471,15 +458,11 @@ const setAngledIntersectLineForLines: TransformInfo['createNode'] = ({ referenceSegName, tag, forceValueUsedInTransform }) => (args) => { const valueUsedInTransform = roundOff( - args[1].varExpression.type === 'Literal' - ? Number(args[1].varExpression.value) - : 0, + args[1].expr.type === 'Literal' ? Number(args[1].expr.value) : 0, 2 ) const angle = - args[0].varExpression.type === 'Literal' - ? Number(args[0].varExpression.value) - : 0 + args[0].expr.type === 'Literal' ? Number(args[0].expr.value) : 0 const varNamMap: { [key: number]: string } = { 0: 'ZERO', 90: 'QUARTER_TURN', @@ -504,14 +487,12 @@ const setAngledIntersectForAngledLines: TransformInfo['createNode'] = ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => (args) => { const valueUsedInTransform = roundOff( - args[1].varExpression.type === 'Literal' - ? Number(args[1].varExpression.value) - : 0, + args[1].expr.type === 'Literal' ? Number(args[1].expr.value) : 0, 2 ) return intersectCallWrapper({ fnName: 'angledLineThatIntersects', - angleVal: inputs[0].varExpression, + angleVal: inputs[0].expr, offsetVal: forceValueUsedInTransform || createLiteral(valueUsedInTransform), intersectTag: createIdentifier(referenceSegName), @@ -529,9 +510,8 @@ const setAngleBetweenCreateNode = : 0 let valueUsedInTransform = roundOff( normaliseAngle( - (args[0].varExpression.type === 'Literal' - ? Number(args[0].varExpression.value) - : 0) - refAngle + (args[0].expr.type === 'Literal' ? Number(args[0].expr.value) : 0) - + refAngle ) ) let firstHalfValue = createSegAngle(referenceSegName) as BinaryPart @@ -555,10 +535,10 @@ const setAngleBetweenCreateNode = ? 'angledLineToX' : 'angledLineToY', tranformToType === 'none' - ? [binExp, args[1].varExpression] + ? [binExp, args[1].expr] : tranformToType === 'xAbs' - ? [binExp, inputs[0].varExpression] - : [binExp, inputs[1].varExpression], + ? [binExp, inputs[0].expr] + : [binExp, inputs[1].expr], tag, valueUsedInTransform ) @@ -573,12 +553,12 @@ const transformMap: TransformMap = { createNode: ({ referenceSegName, inputs, tag }) => { const [minVal, legLenVal] = getMinAndSegLenVals( referenceSegName, - inputs[0].varExpression + inputs[0].expr ) return (args) => createCallWrapper( 'line', - [minVal, getSignedLeg(args[1].varExpression, legLenVal)], + [minVal, getSignedLeg(args[1].expr, legLenVal)], tag ) }, @@ -588,7 +568,7 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => () => - createCallWrapper('xLine', inputs[0].varExpression, tag), + createCallWrapper('xLine', inputs[0].expr, tag), }, setVertDistance: { tooltip: 'lineTo', @@ -601,12 +581,12 @@ const transformMap: TransformMap = { createNode: ({ referenceSegName, inputs, tag }) => { const [minVal, legLenVal] = getMinAndSegLenVals( referenceSegName, - inputs[1].varExpression + inputs[1].expr ) return (args) => createCallWrapper( 'line', - [getSignedLeg(args[0].varExpression, legLenVal), minVal], + [getSignedLeg(args[0].expr, legLenVal), minVal], tag ) }, @@ -616,7 +596,7 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => () => - createCallWrapper('yLine', inputs[1].varExpression, tag), + createCallWrapper('yLine', inputs[1].expr, tag), }, setHorzDistance: { tooltip: 'lineTo', @@ -633,7 +613,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('xLine', args[0].varExpression, tag), + createCallWrapper('xLine', args[0].expr, tag), }, vertical: { tooltip: 'yLine', @@ -642,7 +622,7 @@ const transformMap: TransformMap = { (args) => createCallWrapper( 'yLine', - getInputOfType(args, 'yRelative').varExpression, + getInputOfType(args, 'yRelative').expr, tag ), }, @@ -695,7 +675,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('xLineTo', args[0].varExpression, tag), + createCallWrapper('xLineTo', args[0].expr, tag), }, vertical: { tooltip: 'yLineTo', @@ -704,7 +684,7 @@ const transformMap: TransformMap = { (args) => createCallWrapper( 'yLineTo', - getInputOfType(args, 'yAbsolute').varExpression, + getInputOfType(args, 'yAbsolute').expr, tag ), }, @@ -719,18 +699,15 @@ const transformMap: TransformMap = { 'angleToMatchLengthX', [ createIdentifier(referenceSegName), - inputs[0].varExpression, + inputs[0].expr, createPipeSubstitution(), ] ) return createCallWrapper( 'angledLineToX', [ - getAngleLengthSign( - args[0].varExpression, - angleToMatchLengthXCall - ), - inputs[0].varExpression, + getAngleLengthSign(args[0].expr, angleToMatchLengthXCall), + inputs[0].expr, ], tag ) @@ -741,7 +718,7 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => () => - createCallWrapper('xLineTo', inputs[0].varExpression, tag), + createCallWrapper('xLineTo', inputs[0].expr, tag), }, setAngleBetween: { tooltip: 'angledLineToX', @@ -758,18 +735,15 @@ const transformMap: TransformMap = { 'angleToMatchLengthY', [ createIdentifier(referenceSegName), - inputs[1].varExpression, + inputs[1].expr, createPipeSubstitution(), ] ) return createCallWrapper( 'angledLineToY', [ - getAngleLengthSign( - args[0].varExpression, - angleToMatchLengthYCall - ), - inputs[1].varExpression, + getAngleLengthSign(args[0].expr, angleToMatchLengthYCall), + inputs[1].expr, ], tag ) @@ -780,7 +754,7 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => () => - createCallWrapper('yLineTo', inputs[1].varExpression, tag), + createCallWrapper('yLineTo', inputs[1].expr, tag), }, setAngle: { tooltip: 'angledLineToY', @@ -789,12 +763,9 @@ const transformMap: TransformMap = { (args) => { return createCallWrapper( 'angledLineToY', - [ - forceValueUsedInTransform || args[0].varExpression, - inputs[1].varExpression, - ], + [forceValueUsedInTransform || args[0].expr, inputs[1].expr], tag, - getArgLiteralVal(args[0].varExpression) + getArgLiteralVal(args[0].expr) ) }, }, @@ -813,7 +784,7 @@ const transformMap: TransformMap = { () => createCallWrapper( 'angledLine', - [inputs[0].varExpression, createSegLen(referenceSegName)], + [inputs[0].expr, createSegLen(referenceSegName)], tag ), }, @@ -858,7 +829,7 @@ const transformMap: TransformMap = { (args) => createCallWrapper( 'yLine', - getInputOfType(args, 'yRelative').varExpression, + getInputOfType(args, 'yRelative').expr, tag ), }, @@ -867,7 +838,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('xLine', args[0].varExpression, tag), + createCallWrapper('xLine', args[0].expr, tag), }, }, length: { @@ -876,13 +847,8 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => ([arg0]) => { - const expr = inputs[1].varExpression - if ( - !( - arg0.varExpression.type === 'Literal' && - Number(arg0.varExpression.value) < 0 - ) - ) + const expr = inputs[1].expr + if (!(arg0.expr.type === 'Literal' && Number(arg0.expr.value) < 0)) return createCallWrapper('yLine', expr, tag) if (isExprBinaryPart(expr)) return createCallWrapper( @@ -899,13 +865,8 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => ([arg0]) => { - const expr = inputs[1].varExpression - if ( - !( - arg0.varExpression.type === 'Literal' && - Number(arg0.varExpression.value) < 0 - ) - ) + const expr = inputs[1].expr + if (!(arg0.expr.type === 'Literal' && Number(arg0.expr.value) < 0)) return createCallWrapper('xLine', expr, tag) if (isExprBinaryPart(expr)) return createCallWrapper( @@ -938,7 +899,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('xLine', args[0].varExpression, tag), + createCallWrapper('xLine', args[0].expr, tag), }, }, angle: { @@ -953,12 +914,12 @@ const transformMap: TransformMap = { createNode: ({ referenceSegName, inputs, tag }) => { const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, - getInputOfType(inputs, 'xRelative').varExpression + getInputOfType(inputs, 'xRelative').expr ) return (args) => createCallWrapper( 'angledLineOfXLength', - [getLegAng(args[0].varExpression, legAngle), minVal], + [getLegAng(args[0].expr, legAngle), minVal], tag ) }, @@ -968,13 +929,8 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => ([arg0]) => { - const expr = inputs[1].varExpression - if ( - !( - arg0.varExpression.type === 'Literal' && - Number(arg0.varExpression.value) < 0 - ) - ) + const expr = inputs[1].expr + if (!(arg0.expr.type === 'Literal' && Number(arg0.expr.value) < 0)) return createCallWrapper('xLine', expr, tag) if (isExprBinaryPart(expr)) return createCallWrapper( @@ -1001,7 +957,7 @@ const transformMap: TransformMap = { (args) => createCallWrapper( 'yLine', - getInputOfType(args, 'yRelative').varExpression, + getInputOfType(args, 'yRelative').expr, tag ), }, @@ -1018,13 +974,13 @@ const transformMap: TransformMap = { createNode: ({ referenceSegName, inputs, tag }) => { const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, - inputs[1].varExpression, + inputs[1].expr, 'legAngY' ) return (args) => createCallWrapper( 'angledLineOfXLength', - [getLegAng(args[0].varExpression, legAngle), minVal], + [getLegAng(args[0].expr, legAngle), minVal], tag ) }, @@ -1034,13 +990,8 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => ([arg0]) => { - const expr = inputs[1].varExpression - if ( - !( - arg0.varExpression.type === 'Literal' && - Number(arg0.varExpression.value) < 0 - ) - ) + const expr = inputs[1].expr + if (!(arg0.expr.type === 'Literal' && Number(arg0.expr.value) < 0)) return createCallWrapper('yLine', expr, tag) if (isExprBinaryPart(expr)) return createCallWrapper( @@ -1065,7 +1016,7 @@ const transformMap: TransformMap = { createNode: ({ tag }) => (args) => - createCallWrapper('xLineTo', args[0].varExpression, tag), + createCallWrapper('xLineTo', args[0].expr, tag), }, }, angle: { @@ -1084,18 +1035,15 @@ const transformMap: TransformMap = { 'angleToMatchLengthX', [ createIdentifier(referenceSegName), - inputs[1].varExpression, + inputs[1].expr, createPipeSubstitution(), ] ) return createCallWrapper( 'angledLineToX', [ - getAngleLengthSign( - args[0].varExpression, - angleToMatchLengthXCall - ), - inputs[1].varExpression, + getAngleLengthSign(args[0].expr, angleToMatchLengthXCall), + inputs[1].expr, ], tag ) @@ -1106,7 +1054,7 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => ([arg0]) => - createCallWrapper('xLineTo', inputs[1].varExpression, tag), + createCallWrapper('xLineTo', inputs[1].expr, tag), }, }, }, @@ -1123,7 +1071,7 @@ const transformMap: TransformMap = { (args) => createCallWrapper( 'yLineTo', - getInputOfType(args, 'yAbsolute').varExpression, + getInputOfType(args, 'yAbsolute').expr, tag ), }, @@ -1144,18 +1092,15 @@ const transformMap: TransformMap = { 'angleToMatchLengthY', [ createIdentifier(referenceSegName), - inputs[1].varExpression, + inputs[1].expr, createPipeSubstitution(), ] ) return createCallWrapper( 'angledLineToY', [ - getAngleLengthSign( - args[0].varExpression, - angleToMatchLengthXCall - ), - inputs[1].varExpression, + getAngleLengthSign(args[0].expr, angleToMatchLengthXCall), + inputs[1].expr, ], tag ) @@ -1166,7 +1111,7 @@ const transformMap: TransformMap = { createNode: ({ inputs, tag }) => () => - createCallWrapper('yLineTo', inputs[1].varExpression, tag), + createCallWrapper('yLineTo', inputs[1].expr, tag), }, }, }, @@ -1177,7 +1122,7 @@ const transformMap: TransformMap = { createNode: ({ referenceSegName, tag }) => (arg) => { - const argVal = getArgLiteralVal(arg[0].varExpression) + const argVal = getArgLiteralVal(arg[0].expr) const segLen = createSegLen(referenceSegName) // as BinaryPart if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal) @@ -1217,7 +1162,7 @@ const transformMap: TransformMap = { createNode: ({ referenceSegName, tag }) => (arg) => { - const argVal = getArgLiteralVal(arg[0].varExpression) + const argVal = getArgLiteralVal(arg[0].expr) let segLen = createSegLen(referenceSegName) as BinaryPart if (argVal < 0) segLen = createUnaryExpression(segLen) return createCallWrapper('yLine', segLen, tag, argVal) @@ -1301,18 +1246,14 @@ export function getRemoveConstraintsTransform( createNode: ({ tag, referenceSegName }) => (args) => { - return createCallWrapper( - 'line', - [args[0].varExpression, args[1].varExpression], - tag - ) + return createCallWrapper('line', [args[0].expr, args[1].expr], tag) // The following commented changes values to hardcode, but keeps the line type the same, maybe that's useful? // if (name === 'angledLineThatIntersects') { // return intersectCallWrapper({ // fnName: name, - // angleVal: args[0].varExpression, - // offsetVal: args[1].varExpression, + // angleVal: args[0].expr, + // offsetVal: args[1].expr, // intersectTag: createIdentifier(referenceSegName), // tag, // }) @@ -1388,25 +1329,21 @@ export function removeSingleConstraint({ // the inputDetails, input where we should use the rawValue(s) if (inputDetails.type === 'arrayItem') { - const values = inputs.map(({ varDetails: varValue }) => { + const values = inputs.map((arg) => { if ( !( - (varValue.type === 'arrayItem' || - varValue.type === 'arrayOrObjItem') && - varValue.index === inputDetails.index + (arg.type === 'arrayItem' || arg.type === 'arrayOrObjItem') && + arg.index === inputDetails.index ) ) - return varValue.expr + return arg.expr const literal = rawValues.find( (rawValue) => - (rawValue.varDetails.type === 'arrayItem' || - rawValue.varDetails.type === 'arrayOrObjItem') && - rawValue.varDetails.index === inputDetails.index - )?.varDetails?.expr - return ( - (varValue.index === inputDetails.index && literal) || - varValue.expr - ) + (rawValue.type === 'arrayItem' || + rawValue.type === 'arrayOrObjItem') && + rawValue.index === inputDetails.index + )?.expr + return (arg.index === inputDetails.index && literal) || arg.expr }) return createStdlibCallExpression( callExp.node.callee.name as any, @@ -1422,54 +1359,52 @@ export function removeSingleConstraint({ [key: string]: Parameters[0] } = {} const otherThing: Parameters[0] = {} - inputs.forEach(({ varDetails: varValue, varExpression }) => { + inputs.forEach((arg) => { if ( - varValue.type !== 'objectProperty' && - varValue.type !== 'arrayOrObjItem' && - varValue.type !== 'arrayInObject' + arg.type !== 'objectProperty' && + arg.type !== 'arrayOrObjItem' && + arg.type !== 'arrayInObject' ) return const rawLiteralArrayInObject = rawValues.find( (rawValue) => - rawValue.varDetails.type === 'arrayInObject' && - rawValue.varDetails.key === inputDetails.key && - rawValue.varDetails.index === - (varValue.type === 'arrayInObject' ? varValue.index : -1) + rawValue.type === 'arrayInObject' && + rawValue.key === inputDetails.key && + rawValue.index === + (arg.type === 'arrayInObject' ? arg.index : -1) ) const rawLiteralObjProp = rawValues.find( (rawValue) => - (rawValue.varDetails.type === 'objectProperty' || - rawValue.varDetails.type === 'arrayOrObjItem' || - rawValue.varDetails.type === 'arrayInObject') && - rawValue.varDetails.key === inputDetails.key + (rawValue.type === 'objectProperty' || + rawValue.type === 'arrayOrObjItem' || + rawValue.type === 'arrayInObject') && + rawValue.key === inputDetails.key ) if ( inputDetails.type === 'arrayInObject' && - rawLiteralArrayInObject?.varDetails.type === 'arrayInObject' && - rawLiteralArrayInObject?.varDetails.index === - inputDetails.index && - rawLiteralArrayInObject?.varDetails.key === inputDetails.key + rawLiteralArrayInObject?.type === 'arrayInObject' && + rawLiteralArrayInObject?.index === inputDetails.index && + rawLiteralArrayInObject?.key === inputDetails.key ) { - if (!arrayDetailsNameBetterLater[varValue.key]) - arrayDetailsNameBetterLater[varValue.key] = [] + if (!arrayDetailsNameBetterLater[arg.key]) + arrayDetailsNameBetterLater[arg.key] = [] arrayDetailsNameBetterLater[inputDetails.key][ inputDetails.index - ] = rawLiteralArrayInObject.varDetails.expr + ] = rawLiteralArrayInObject.expr } else if ( inputDetails.type === 'objectProperty' && - (rawLiteralObjProp?.varDetails.type === 'objectProperty' || - rawLiteralObjProp?.varDetails.type === 'arrayOrObjItem') && - rawLiteralObjProp?.varDetails.key === inputDetails.key && - varValue.key === inputDetails.key + (rawLiteralObjProp?.type === 'objectProperty' || + rawLiteralObjProp?.type === 'arrayOrObjItem') && + rawLiteralObjProp?.key === inputDetails.key && + arg.key === inputDetails.key ) { - otherThing[inputDetails.key] = rawLiteralObjProp.varDetails.expr - } else if (varValue.type === 'arrayInObject') { - if (!arrayDetailsNameBetterLater[varValue.key]) - arrayDetailsNameBetterLater[varValue.key] = [] - arrayDetailsNameBetterLater[varValue.key][varValue.index] = - varExpression - } else if (varValue.type === 'objectProperty') { - otherThing[varValue.key] = varExpression + otherThing[inputDetails.key] = rawLiteralObjProp.expr + } else if (arg.type === 'arrayInObject') { + if (!arrayDetailsNameBetterLater[arg.key]) + arrayDetailsNameBetterLater[arg.key] = [] + arrayDetailsNameBetterLater[arg.key][arg.index] = arg.expr + } else if (arg.type === 'objectProperty') { + otherThing[arg.key] = arg.expr } }) const createObjParam: Parameters[0] = @@ -1492,7 +1427,7 @@ export function removeSingleConstraint({ return createCallWrapper( callExp.node.callee.name as any, - rawValues[0].varDetails.expr, + rawValues[0].expr, tag ) }, @@ -1787,7 +1722,7 @@ export function transformAstSketchLines({ _referencedSegmentNameVal.type === 'Identifier' && String(_referencedSegmentNameVal.name)) || '' - const inputs: SegmentInput[] = [] + const inputs: InputArgs = [] getConstraintInfo(callExp.node, '', _pathToNode).forEach((a) => { if ( @@ -1802,43 +1737,31 @@ export function transformAstSketchLines({ if (a?.argPosition?.type === 'arrayItem') { inputs.push({ - varDetails: { - type: 'arrayItem', - index: a.argPosition.index, - expr: nodeMeta.node, - argType: a.type, - }, - varExpression: nodeMeta.node, + type: 'arrayItem', + index: a.argPosition.index, + expr: nodeMeta.node, + argType: a.type, }) } else if (a?.argPosition?.type === 'objectProperty') { inputs.push({ - varDetails: { - type: 'objectProperty', - key: a.argPosition.key, - expr: nodeMeta.node, - argType: a.type, - }, - varExpression: nodeMeta.node, + type: 'objectProperty', + key: a.argPosition.key, + expr: nodeMeta.node, + argType: a.type, }) } else if (a?.argPosition?.type === 'singleValue') { inputs.push({ - varDetails: { - type: 'singleValue', - argType: a.type, - expr: nodeMeta.node, - }, - varExpression: nodeMeta.node, + type: 'singleValue', + argType: a.type, + expr: nodeMeta.node, }) } else if (a?.argPosition?.type === 'arrayInObject') { inputs.push({ - varDetails: { - type: 'arrayInObject', - key: a.argPosition.key, - index: a.argPosition.index, - expr: nodeMeta.node, - argType: a.type, - }, - varExpression: nodeMeta.node, + type: 'arrayInObject', + key: a.argPosition.key, + index: a.argPosition.index, + expr: nodeMeta.node, + argType: a.type, }) } }) @@ -2039,9 +1962,6 @@ function isExprBinaryPart(expr: Expr): expr is BinaryPart { return false } -function getInputOfType( - a: SegmentInput[], - b: LineInputsType | 'radius' -): SegmentInput { - return a.find(({ varDetails }) => varDetails.argType === b) || a[0] +function getInputOfType(a: InputArgs, b: LineInputsType | 'radius'): InputArg { + return a.find(({ argType }) => argType === b) || a[0] } diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index 44496d7476..ef39c72df3 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -137,7 +137,7 @@ type _VarValue = * Which is why a union type is used that can be type narrowed using the {@link RawArg.type} property * {@link RawArg.expr} is common to all of these types */ -type InputArg = _VarValue +export type InputArg = _VarValue /** * {@link RawArg.expr} is the literal equivalent of whatever current expression is @@ -147,7 +147,7 @@ type InputArg = _VarValue */ type RawArg = _VarValue -type InputArgs = Array +export type InputArgs = Array // /** // * The literal equivalent of whatever current expression is @@ -174,13 +174,8 @@ export type SimplifiedArgDetails = index: 0 | 1 } -export interface SegmentInput { - varExpression: Expr - varDetails: InputArg -} - export type TransformCallback = ( - inputs: SegmentInput[], + inputs: InputArgs, referencedSegment?: Path ) => { callExp: Expr From 25080e9895b744f4335220b624cadacf7b534eb5 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Tue, 10 Sep 2024 01:08:57 +1000 Subject: [PATCH 34/41] clean up create call back double function stuff --- src/components/Toolbar/EqualAngle.tsx | 2 +- src/components/Toolbar/EqualLength.tsx | 2 +- src/components/Toolbar/HorzVert.tsx | 2 +- src/components/Toolbar/Intersect.tsx | 2 +- .../Toolbar/RemoveConstrainingValues.tsx | 2 +- src/components/Toolbar/SetAbsDistance.tsx | 2 +- src/components/Toolbar/SetAngleBetween.tsx | 2 +- .../Toolbar/SetHorzVertDistance.tsx | 2 +- src/components/Toolbar/setAngleLength.tsx | 2 +- src/lang/std/sketch.ts | 212 ++- src/lang/std/sketchcombos.ts | 1214 ++++++++--------- src/lang/std/stdTypes.ts | 31 +- 12 files changed, 700 insertions(+), 775 deletions(-) diff --git a/src/components/Toolbar/EqualAngle.tsx b/src/components/Toolbar/EqualAngle.tsx index ec4f719ac9..892088bc41 100644 --- a/src/components/Toolbar/EqualAngle.tsx +++ b/src/components/Toolbar/EqualAngle.tsx @@ -10,10 +10,10 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, } from '../../lang/std/sketchcombos' import { kclManager } from 'lib/singletons' import { err } from 'lib/trap' +import { TransformInfo } from 'lang/std/stdTypes' export function equalAngleInfo({ selectionRanges, diff --git a/src/components/Toolbar/EqualLength.tsx b/src/components/Toolbar/EqualLength.tsx index d99ff588b2..23b27bc79e 100644 --- a/src/components/Toolbar/EqualLength.tsx +++ b/src/components/Toolbar/EqualLength.tsx @@ -10,8 +10,8 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { kclManager } from 'lib/singletons' import { err } from 'lib/trap' diff --git a/src/components/Toolbar/HorzVert.tsx b/src/components/Toolbar/HorzVert.tsx index c06ad82f1d..797db49a74 100644 --- a/src/components/Toolbar/HorzVert.tsx +++ b/src/components/Toolbar/HorzVert.tsx @@ -9,8 +9,8 @@ import { PathToNodeMap, getTransformInfos, transformAstSketchLines, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { kclManager } from 'lib/singletons' import { err } from 'lib/trap' diff --git a/src/components/Toolbar/Intersect.tsx b/src/components/Toolbar/Intersect.tsx index 2398b9c8fe..bcd56ec048 100644 --- a/src/components/Toolbar/Intersect.tsx +++ b/src/components/Toolbar/Intersect.tsx @@ -11,8 +11,8 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { createVariableDeclaration } from '../../lang/modifyAst' import { removeDoubleNegatives } from '../AvailableVarsHelpers' diff --git a/src/components/Toolbar/RemoveConstrainingValues.tsx b/src/components/Toolbar/RemoveConstrainingValues.tsx index 44c4a4f2c1..dd88fad010 100644 --- a/src/components/Toolbar/RemoveConstrainingValues.tsx +++ b/src/components/Toolbar/RemoveConstrainingValues.tsx @@ -9,8 +9,8 @@ import { PathToNodeMap, getRemoveConstraintsTransforms, transformAstSketchLines, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { kclManager } from 'lib/singletons' import { err } from 'lib/trap' diff --git a/src/components/Toolbar/SetAbsDistance.tsx b/src/components/Toolbar/SetAbsDistance.tsx index 42be910ea1..ac6b4173e8 100644 --- a/src/components/Toolbar/SetAbsDistance.tsx +++ b/src/components/Toolbar/SetAbsDistance.tsx @@ -9,8 +9,8 @@ import { getTransformInfos, transformAstSketchLines, PathToNodeMap, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { SetAngleLengthModal, createSetAngleLengthModal, diff --git a/src/components/Toolbar/SetAngleBetween.tsx b/src/components/Toolbar/SetAngleBetween.tsx index 77a2129bba..04cb8baace 100644 --- a/src/components/Toolbar/SetAngleBetween.tsx +++ b/src/components/Toolbar/SetAngleBetween.tsx @@ -10,8 +10,8 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { createVariableDeclaration } from '../../lang/modifyAst' import { removeDoubleNegatives } from '../AvailableVarsHelpers' diff --git a/src/components/Toolbar/SetHorzVertDistance.tsx b/src/components/Toolbar/SetHorzVertDistance.tsx index 36240bb714..7a7a457d6c 100644 --- a/src/components/Toolbar/SetHorzVertDistance.tsx +++ b/src/components/Toolbar/SetHorzVertDistance.tsx @@ -9,8 +9,8 @@ import { transformSecondarySketchLinesTagFirst, getTransformInfos, PathToNodeMap, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst' import { removeDoubleNegatives } from '../AvailableVarsHelpers' diff --git a/src/components/Toolbar/setAngleLength.tsx b/src/components/Toolbar/setAngleLength.tsx index a36cc4346d..00c18149c7 100644 --- a/src/components/Toolbar/setAngleLength.tsx +++ b/src/components/Toolbar/setAngleLength.tsx @@ -9,8 +9,8 @@ import { PathToNodeMap, getTransformInfos, transformAstSketchLines, - TransformInfo, } from '../../lang/std/sketchcombos' +import { TransformInfo } from 'lang/std/stdTypes' import { SetAngleLengthModal, createSetAngleLengthModal, diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index f8b645cd2c..a1ff5bbf12 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -28,7 +28,6 @@ import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst' import { SketchLineHelper, - TransformCallback, ConstrainInfo, ArrayItemInput, ObjectPropertyInput, @@ -36,6 +35,8 @@ import { AddTagInfo, SegmentInputs, SimplifiedArgDetails, + RawArgs, + CreateStdLibSketchCallExpr, } from 'lang/std/stdTypes' import { @@ -321,23 +322,20 @@ export const lineTo: SketchLineHelper = { ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) if (replaceExisting && createCallback) { - const { callExp, valueUsedInTransform } = createCallback( - [ - { - type: 'arrayItem', - index: 0, - argType: 'xAbsolute', - expr: createLiteral(roundOff(to[0], 2)), - }, - { - type: 'arrayItem', - index: 1, - argType: 'yAbsolute', - expr: createLiteral(roundOff(to[1], 2)), - }, - ], - referencedSegment - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + type: 'arrayItem', + index: 0, + argType: 'xAbsolute', + expr: createLiteral(roundOff(to[0], 2)), + }, + { + type: 'arrayItem', + index: 1, + argType: 'yAbsolute', + expr: createLiteral(roundOff(to[1], 2)), + }, + ]) pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -441,23 +439,20 @@ export const line: SketchLineHelper = { if (replaceExisting && createCallback && pipe.type !== 'CallExpression') { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [ - { - type: 'arrayItem', - index: 0, - argType: 'xRelative', - expr: createLiteral(roundOff(to[0] - from[0], 2)), - }, - { - type: 'arrayItem', - index: 1, - argType: 'yRelative', - expr: createLiteral(roundOff(to[1] - from[1], 2)), - }, - ], - referencedSegment - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + type: 'arrayItem', + index: 0, + argType: 'xRelative', + expr: createLiteral(roundOff(to[0] - from[0], 2)), + }, + { + type: 'arrayItem', + index: 1, + argType: 'yRelative', + expr: createLiteral(roundOff(to[1] - from[1], 2)), + }, + ]) pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -842,23 +837,20 @@ export const tangentialArcTo: SketchLineHelper = { if (replaceExisting && createCallback && pipe.type !== 'CallExpression') { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [ - { - type: 'arrayItem', - index: 0, - argType: 'xRelative', - expr: toX, - }, - { - type: 'arrayItem', - index: 1, - argType: 'yAbsolute', - expr: toY, - }, - ], - referencedSegment - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + type: 'arrayItem', + index: 0, + argType: 'xRelative', + expr: toX, + }, + { + type: 'arrayItem', + index: 1, + argType: 'yAbsolute', + expr: toY, + }, + ]) pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -1154,25 +1146,22 @@ export const angledLine: SketchLineHelper = { if (replaceExisting && createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback( - [ - { - type: 'arrayOrObjItem', - index: 0, - key: 'angle', - argType: 'angle', - expr: newAngleVal, - }, - { - type: 'arrayOrObjItem', - index: 1, - key: 'length', - argType: 'length', - expr: newLengthVal, - }, - ], - referencedSegment - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: newAngleVal, + }, + { + type: 'arrayOrObjItem', + index: 1, + key: 'length', + argType: 'length', + expr: newLengthVal, + }, + ]) pipe.body[callIndex] = callExp return { modifiedAst: _node, @@ -1471,25 +1460,22 @@ export const angledLineToX: SketchLineHelper = { const angle = createLiteral(roundOff(getAngle(from, to), 0)) const xArg = createLiteral(roundOff(to[0], 2)) if (replaceExisting && createCallback) { - const { callExp, valueUsedInTransform } = createCallback( - [ - { - type: 'arrayOrObjItem', - index: 0, - key: 'angle', - argType: 'angle', - expr: angle, - }, - { - type: 'arrayOrObjItem', - index: 1, - key: 'to', - argType: 'xAbsolute', - expr: xArg, - }, - ], - referencedSegment - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: angle, + }, + { + type: 'arrayOrObjItem', + index: 1, + key: 'to', + argType: 'xAbsolute', + expr: xArg, + }, + ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) pipe.body[callIndex] = callExp return { @@ -1575,25 +1561,22 @@ export const angledLineToY: SketchLineHelper = { const yArg = createLiteral(roundOff(to[1], 2)) if (replaceExisting && createCallback) { - const { callExp, valueUsedInTransform } = createCallback( - [ - { - type: 'arrayOrObjItem', - index: 0, - key: 'angle', - argType: 'angle', - expr: angle, - }, - { - type: 'arrayOrObjItem', - index: 1, - key: 'to', - argType: 'yAbsolute', - expr: yArg, - }, - ], - referencedSegment - ) + const { callExp, valueUsedInTransform } = createCallback([ + { + type: 'arrayOrObjItem', + index: 0, + key: 'angle', + argType: 'angle', + expr: angle, + }, + { + type: 'arrayOrObjItem', + index: 1, + key: 'to', + argType: 'yAbsolute', + expr: yArg, + }, + ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) pipe.body[callIndex] = callExp return { @@ -2121,7 +2104,7 @@ export function replaceSketchLine({ pathToNode: PathToNode fnName: ToolTip segmentInput: SegmentInputs - createCallback: TransformCallback + createCallback: (rawArgs: RawArgs) => ReturnType referencedSegment?: Path }): | { @@ -2151,13 +2134,16 @@ export function replaceSketchLine({ return { modifiedAst, valueUsedInTransform, pathToNode } } -export function addTagForSketchOnFace(a: AddTagInfo, expressionName: string) { +export function addTagForSketchOnFace( + tagInfo: AddTagInfo, + expressionName: string +) { if (expressionName === 'close') { - return addTag(1)(a) + return addTag(1)(tagInfo) } if (expressionName in sketchLineHelperMap) { const { addTag } = sketchLineHelperMap[expressionName] - return addTag(a) + return addTag(tagInfo) } return new Error(`"${expressionName}" is not a sketch line helper`) } diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index cfce31458f..ee685d54b0 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1,8 +1,9 @@ import { + CreateStdLibSketchCallExpr, InputArg, InputArgs, SimplifiedArgDetails, - TransformCallback, + TransformInfo, } from './stdTypes' import { ToolTip, toolTips } from 'lang/langHelpers' import { Selections, Selection } from 'lib/selections' @@ -77,7 +78,7 @@ function createCallWrapper( val: [Expr, Expr] | Expr, tag?: Expr, valueUsedInTransform?: number -): ReturnType { +): ReturnType { const args = tooltip === 'circle' ? [] @@ -115,7 +116,7 @@ function createStdlibCallExpression( val: Expr, tag?: Expr, valueUsedInTransform?: number -): ReturnType { +): ReturnType { const args = [val, createPipeSubstitution()] if (tag) { args.push(tag) @@ -140,7 +141,7 @@ function intersectCallWrapper({ intersectTag: Expr tag?: Expr valueUsedInTransform?: number -}): ReturnType { +}): ReturnType { const firstArg: any = { angle: angleVal, offset: offsetVal, @@ -159,16 +160,6 @@ function intersectCallWrapper({ } } -export type TransformInfo = { - tooltip: ToolTip - createNode: (a: { - inputs: InputArgs - referenceSegName: string - tag?: Expr - forceValueUsedInTransform?: Expr - }) => TransformCallback -} - type TransformMap = { [key in ToolTip]?: { [key in LineInputsType | 'free']?: { @@ -178,12 +169,8 @@ type TransformMap = { } const xyLineSetLength = - ( - xOrY: 'xLine' | 'yLine', - referenceSeg = false - ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform }) => - (args) => { + (xOrY: 'xLine' | 'yLine', referenceSeg = false): CreateStdLibSketchCallExpr => + ({ referenceSegName, tag, forceValueUsedInTransform, rawArgs: args }) => { const segRef = createSegLen(referenceSegName) const lineVal = forceValueUsedInTransform ? forceValueUsedInTransform @@ -198,9 +185,15 @@ const basicAngledLineCreateNode = referenceSeg: 'ang' | 'len' | 'none' = 'none', valToForce: 'ang' | 'len' | 'none' = 'none', varValToUse: 'ang' | 'len' | 'none' = 'none' - ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => - (args, path) => { + ): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + inputs, + rawArgs: args, + referencedSegment: path, + }) => { const refAng = path ? getAngle(path?.from, path?.to) : 0 const nonForcedAng = varValToUse === 'ang' @@ -230,14 +223,16 @@ const basicAngledLineCreateNode = getArgLiteralVal(valToForce === 'ang' ? args[0].expr : args[1].expr) ) } -const angledLineAngleCreateNode: TransformInfo['createNode'] = - ({ referenceSegName, inputs, tag }) => - () => - createCallWrapper( - 'angledLine', - [inputs[0].expr, createSegLen(referenceSegName)], - tag - ) +const angledLineAngleCreateNode: CreateStdLibSketchCallExpr = ({ + referenceSegName, + inputs, + tag, +}) => + createCallWrapper( + 'angledLine', + [inputs[0].expr, createSegLen(referenceSegName)], + tag + ) const getMinAndSegLenVals = ( referenceSegName: string, @@ -302,57 +297,59 @@ function getClosesAngleDirection( } const setHorzVertDistanceCreateNode = - ( - xOrY: 'x' | 'y', - index = xOrY === 'x' ? 0 : 1 - ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform }) => { - return (args, referencedSegment) => { - const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[index].expr) - - (referencedSegment?.to?.[index] || 0), - 2 - ) - let finalValue: Expr = createBinaryExpressionWithUnary([ - createSegEnd(referenceSegName, !index), - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform), - ]) - if (isValueZero(forceValueUsedInTransform)) { - finalValue = createSegEnd(referenceSegName, !index) - } - return createCallWrapper( - 'lineTo', - !index ? [finalValue, args[1].expr] : [args[0].expr, finalValue], - tag, - valueUsedInTransform - ) + (xOrY: 'x' | 'y', index = xOrY === 'x' ? 0 : 1): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + rawArgs: args, + referencedSegment, + }) => { + const valueUsedInTransform = roundOff( + getArgLiteralVal(args?.[index].expr) - + (referencedSegment?.to?.[index] || 0), + 2 + ) + let finalValue: Expr = createBinaryExpressionWithUnary([ + createSegEnd(referenceSegName, !index), + (forceValueUsedInTransform as BinaryPart) || + createLiteral(valueUsedInTransform), + ]) + if (isValueZero(forceValueUsedInTransform)) { + finalValue = createSegEnd(referenceSegName, !index) } + return createCallWrapper( + 'lineTo', + !index ? [finalValue, args[1].expr] : [args[0].expr, finalValue], + tag, + valueUsedInTransform + ) } const setHorzVertDistanceForAngleLineCreateNode = - ( - xOrY: 'x' | 'y', - index = xOrY === 'x' ? 0 : 1 - ): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => { - return (args, referencedSegment) => { - const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[1].expr) - - (referencedSegment?.to?.[index] || 0), - 2 - ) - const binExp = createBinaryExpressionWithUnary([ - createSegEnd(referenceSegName, !index), - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform), - ]) - return createCallWrapper( - xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', - [inputs[0].expr, binExp], - tag, - valueUsedInTransform - ) - } + (xOrY: 'x' | 'y', index = xOrY === 'x' ? 0 : 1): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + inputs, + rawArgs: args, + referencedSegment, + }) => { + const valueUsedInTransform = roundOff( + getArgLiteralVal(args?.[1].expr) - (referencedSegment?.to?.[index] || 0), + 2 + ) + const binExp = createBinaryExpressionWithUnary([ + createSegEnd(referenceSegName, !index), + (forceValueUsedInTransform as BinaryPart) || + createLiteral(valueUsedInTransform), + ]) + return createCallWrapper( + xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', + [inputs[0].expr, binExp], + tag, + valueUsedInTransform + ) } const setAbsDistanceCreateNode = @@ -360,9 +357,8 @@ const setAbsDistanceCreateNode = xOrY: 'x' | 'y', isXOrYLine = false, index = xOrY === 'x' ? 0 : 1 - ): TransformInfo['createNode'] => - ({ tag, forceValueUsedInTransform }) => - (args) => { + ): CreateStdLibSketchCallExpr => + ({ tag, forceValueUsedInTransform, rawArgs: args }) => { const valueUsedInTransform = roundOff( getArgLiteralVal(args?.[index].expr), 2 @@ -386,49 +382,51 @@ const setAbsDistanceCreateNode = ) } const setAbsDistanceForAngleLineCreateNode = - (xOrY: 'x' | 'y'): TransformInfo['createNode'] => - ({ tag, forceValueUsedInTransform, inputs }) => { - return (args) => { - const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[1].expr), 2) - const val = - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform) - return createCallWrapper( - xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', - [inputs[0].expr, val], - tag, - valueUsedInTransform - ) - } + (xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr => + ({ tag, forceValueUsedInTransform, inputs, rawArgs: args }) => { + const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[1].expr), 2) + const val = + (forceValueUsedInTransform as BinaryPart) || + createLiteral(valueUsedInTransform) + return createCallWrapper( + xOrY === 'x' ? 'angledLineToX' : 'angledLineToY', + [inputs[0].expr, val], + tag, + valueUsedInTransform + ) } const setHorVertDistanceForXYLines = - (xOrY: 'x' | 'y'): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform }) => { - return (args, referencedSegment) => { - const index = xOrY === 'x' ? 0 : 1 - const valueUsedInTransform = roundOff( - getArgLiteralVal(args?.[index].expr) - - (referencedSegment?.to?.[index] || 0), - 2 - ) - const makeBinExp = createBinaryExpressionWithUnary([ - createSegEnd(referenceSegName, xOrY === 'x'), - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform), - ]) - return createCallWrapper( - xOrY === 'x' ? 'xLineTo' : 'yLineTo', - makeBinExp, - tag, - valueUsedInTransform - ) - } + (xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + rawArgs: args, + referencedSegment, + }) => { + const index = xOrY === 'x' ? 0 : 1 + const valueUsedInTransform = roundOff( + getArgLiteralVal(args?.[index].expr) - + (referencedSegment?.to?.[index] || 0), + 2 + ) + const makeBinExp = createBinaryExpressionWithUnary([ + createSegEnd(referenceSegName, xOrY === 'x'), + (forceValueUsedInTransform as BinaryPart) || + createLiteral(valueUsedInTransform), + ]) + return createCallWrapper( + xOrY === 'x' ? 'xLineTo' : 'yLineTo', + makeBinExp, + tag, + valueUsedInTransform + ) } const setHorzVertDistanceConstraintLineCreateNode = - (isX: boolean): TransformInfo['createNode'] => - ({ referenceSegName, tag, inputs }) => { + (isX: boolean): CreateStdLibSketchCallExpr => + ({ referenceSegName, tag, inputs, rawArgs: args, referencedSegment }) => { let varVal = isX ? inputs[1].expr : inputs[0].expr varVal = isExprBinaryPart(varVal) ? varVal : createLiteral(0) const varValBinExp = createBinaryExpressionWithUnary([ @@ -436,113 +434,118 @@ const setHorzVertDistanceConstraintLineCreateNode = varVal, ]) - return (args, referencedSegment) => { - const makeBinExp = (index: 0 | 1) => { - const arg = getArgLiteralVal(args?.[index].expr) - return createBinaryExpressionWithUnary([ - createSegEnd(referenceSegName, isX), - createLiteral( - roundOff(arg - (referencedSegment?.to?.[index] || 0), 2) - ), - ]) - } - return createCallWrapper( - 'lineTo', - isX ? [makeBinExp(0), varValBinExp] : [varValBinExp, makeBinExp(1)], - tag - ) + const makeBinExp = (index: 0 | 1) => { + const arg = getArgLiteralVal(args?.[index].expr) + return createBinaryExpressionWithUnary([ + createSegEnd(referenceSegName, isX), + createLiteral(roundOff(arg - (referencedSegment?.to?.[index] || 0), 2)), + ]) } - } - -const setAngledIntersectLineForLines: TransformInfo['createNode'] = - ({ referenceSegName, tag, forceValueUsedInTransform }) => - (args) => { - const valueUsedInTransform = roundOff( - args[1].expr.type === 'Literal' ? Number(args[1].expr.value) : 0, - 2 + return createCallWrapper( + 'lineTo', + isX ? [makeBinExp(0), varValBinExp] : [varValBinExp, makeBinExp(1)], + tag ) - const angle = - args[0].expr.type === 'Literal' ? Number(args[0].expr.value) : 0 - const varNamMap: { [key: number]: string } = { - 0: 'ZERO', - 90: 'QUARTER_TURN', - 180: 'HALF_TURN', - 270: 'THREE_QUARTER_TURN', - } - const angleVal = [0, 90, 180, 270].includes(angle) - ? createIdentifier(varNamMap[angle]) - : createLiteral(angle) - return intersectCallWrapper({ - fnName: 'angledLineThatIntersects', - angleVal, - offsetVal: - forceValueUsedInTransform || createLiteral(valueUsedInTransform), - intersectTag: createIdentifier(referenceSegName), - tag, - valueUsedInTransform, - }) } -const setAngledIntersectForAngledLines: TransformInfo['createNode'] = - ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => - (args) => { - const valueUsedInTransform = roundOff( - args[1].expr.type === 'Literal' ? Number(args[1].expr.value) : 0, - 2 - ) - return intersectCallWrapper({ - fnName: 'angledLineThatIntersects', - angleVal: inputs[0].expr, - offsetVal: - forceValueUsedInTransform || createLiteral(valueUsedInTransform), - intersectTag: createIdentifier(referenceSegName), - tag, - valueUsedInTransform, - }) +const setAngledIntersectLineForLines: CreateStdLibSketchCallExpr = ({ + referenceSegName, + tag, + forceValueUsedInTransform, + rawArgs: args, +}) => { + const valueUsedInTransform = roundOff( + args[1].expr.type === 'Literal' ? Number(args[1].expr.value) : 0, + 2 + ) + const angle = args[0].expr.type === 'Literal' ? Number(args[0].expr.value) : 0 + const varNamMap: { [key: number]: string } = { + 0: 'ZERO', + 90: 'QUARTER_TURN', + 180: 'HALF_TURN', + 270: 'THREE_QUARTER_TURN', } + const angleVal = [0, 90, 180, 270].includes(angle) + ? createIdentifier(varNamMap[angle]) + : createLiteral(angle) + return intersectCallWrapper({ + fnName: 'angledLineThatIntersects', + angleVal, + offsetVal: forceValueUsedInTransform || createLiteral(valueUsedInTransform), + intersectTag: createIdentifier(referenceSegName), + tag, + valueUsedInTransform, + }) +} + +const setAngledIntersectForAngledLines: CreateStdLibSketchCallExpr = ({ + referenceSegName, + tag, + forceValueUsedInTransform, + inputs, + rawArgs: args, +}) => { + const valueUsedInTransform = roundOff( + args[1].expr.type === 'Literal' ? Number(args[1].expr.value) : 0, + 2 + ) + return intersectCallWrapper({ + fnName: 'angledLineThatIntersects', + angleVal: inputs[0].expr, + offsetVal: forceValueUsedInTransform || createLiteral(valueUsedInTransform), + intersectTag: createIdentifier(referenceSegName), + tag, + valueUsedInTransform, + }) +} const setAngleBetweenCreateNode = - (tranformToType: 'none' | 'xAbs' | 'yAbs'): TransformInfo['createNode'] => - ({ referenceSegName, tag, forceValueUsedInTransform, inputs }) => { - return (args, referencedSegment) => { - const refAngle = referencedSegment - ? getAngle(referencedSegment?.from, referencedSegment?.to) - : 0 - let valueUsedInTransform = roundOff( - normaliseAngle( - (args[0].expr.type === 'Literal' ? Number(args[0].expr.value) : 0) - - refAngle - ) + (tranformToType: 'none' | 'xAbs' | 'yAbs'): CreateStdLibSketchCallExpr => + ({ + referenceSegName, + tag, + forceValueUsedInTransform, + inputs, + rawArgs: args, + referencedSegment, + }) => { + const refAngle = referencedSegment + ? getAngle(referencedSegment?.from, referencedSegment?.to) + : 0 + let valueUsedInTransform = roundOff( + normaliseAngle( + (args[0].expr.type === 'Literal' ? Number(args[0].expr.value) : 0) - + refAngle ) - let firstHalfValue = createSegAngle(referenceSegName) as BinaryPart - if (Math.abs(valueUsedInTransform) > 90) { - firstHalfValue = createBinaryExpression([ - firstHalfValue, - '+', - createIdentifier('HALF_TURN'), - ]) - valueUsedInTransform = normaliseAngle(valueUsedInTransform - 180) - } - const binExp = createBinaryExpressionWithUnary([ + ) + let firstHalfValue = createSegAngle(referenceSegName) as BinaryPart + if (Math.abs(valueUsedInTransform) > 90) { + firstHalfValue = createBinaryExpression([ firstHalfValue, - (forceValueUsedInTransform as BinaryPart) || - createLiteral(valueUsedInTransform), + '+', + createIdentifier('HALF_TURN'), ]) - return createCallWrapper( - tranformToType === 'none' - ? 'angledLine' - : tranformToType === 'xAbs' - ? 'angledLineToX' - : 'angledLineToY', - tranformToType === 'none' - ? [binExp, args[1].expr] - : tranformToType === 'xAbs' - ? [binExp, inputs[0].expr] - : [binExp, inputs[1].expr], - tag, - valueUsedInTransform - ) + valueUsedInTransform = normaliseAngle(valueUsedInTransform - 180) } + const binExp = createBinaryExpressionWithUnary([ + firstHalfValue, + (forceValueUsedInTransform as BinaryPart) || + createLiteral(valueUsedInTransform), + ]) + return createCallWrapper( + tranformToType === 'none' + ? 'angledLine' + : tranformToType === 'xAbs' + ? 'angledLineToX' + : 'angledLineToY', + tranformToType === 'none' + ? [binExp, args[1].expr] + : tranformToType === 'xAbs' + ? [binExp, inputs[0].expr] + : [binExp, inputs[1].expr], + tag, + valueUsedInTransform + ) } const transformMap: TransformMap = { @@ -550,25 +553,22 @@ const transformMap: TransformMap = { xRelative: { equalLength: { tooltip: 'line', - createNode: ({ referenceSegName, inputs, tag }) => { + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { const [minVal, legLenVal] = getMinAndSegLenVals( referenceSegName, inputs[0].expr ) - return (args) => - createCallWrapper( - 'line', - [minVal, getSignedLeg(args[1].expr, legLenVal)], - tag - ) + return createCallWrapper( + 'line', + [minVal, getSignedLeg(args[1].expr, legLenVal)], + tag + ) }, }, horizontal: { tooltip: 'xLine', - createNode: - ({ inputs, tag }) => - () => - createCallWrapper('xLine', inputs[0].expr, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('xLine', inputs[0].expr, tag), }, setVertDistance: { tooltip: 'lineTo', @@ -578,25 +578,22 @@ const transformMap: TransformMap = { yRelative: { equalLength: { tooltip: 'line', - createNode: ({ referenceSegName, inputs, tag }) => { + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { const [minVal, legLenVal] = getMinAndSegLenVals( referenceSegName, inputs[1].expr ) - return (args) => - createCallWrapper( - 'line', - [getSignedLeg(args[0].expr, legLenVal), minVal], - tag - ) + return createCallWrapper( + 'line', + [getSignedLeg(args[0].expr, legLenVal), minVal], + tag + ) }, }, vertical: { tooltip: 'yLine', - createNode: - ({ inputs, tag }) => - () => - createCallWrapper('yLine', inputs[1].expr, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('yLine', inputs[1].expr, tag), }, setHorzDistance: { tooltip: 'lineTo', @@ -610,21 +607,17 @@ const transformMap: TransformMap = { }, horizontal: { tooltip: 'xLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLine', args[0].expr, tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLine', args[0].expr, tag), }, vertical: { tooltip: 'yLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper( - 'yLine', - getInputOfType(args, 'yRelative').expr, - tag - ), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLine', + getInputOfType(args, 'yRelative').expr, + tag + ), }, setHorzDistance: { tooltip: 'lineTo', @@ -672,53 +665,45 @@ const transformMap: TransformMap = { }, horizontal: { tooltip: 'xLineTo', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLineTo', args[0].expr, tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLineTo', args[0].expr, tag), }, vertical: { tooltip: 'yLineTo', - createNode: - ({ tag }) => - (args) => - createCallWrapper( - 'yLineTo', - getInputOfType(args, 'yAbsolute').expr, - tag - ), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLineTo', + getInputOfType(args, 'yAbsolute').expr, + tag + ), }, }, xAbsolute: { equalLength: { tooltip: 'angledLineToX', - createNode: - ({ referenceSegName, inputs, tag }) => - (args) => { - const angleToMatchLengthXCall = createCallExpression( - 'angleToMatchLengthX', - [ - createIdentifier(referenceSegName), - inputs[0].expr, - createPipeSubstitution(), - ] - ) - return createCallWrapper( - 'angledLineToX', - [ - getAngleLengthSign(args[0].expr, angleToMatchLengthXCall), - inputs[0].expr, - ], - tag - ) - }, + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { + const angleToMatchLengthXCall = createCallExpression( + 'angleToMatchLengthX', + [ + createIdentifier(referenceSegName), + inputs[0].expr, + createPipeSubstitution(), + ] + ) + return createCallWrapper( + 'angledLineToX', + [ + getAngleLengthSign(args[0].expr, angleToMatchLengthXCall), + inputs[0].expr, + ], + tag + ) + }, }, horizontal: { tooltip: 'xLineTo', - createNode: - ({ inputs, tag }) => - () => - createCallWrapper('xLineTo', inputs[0].expr, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('xLineTo', inputs[0].expr, tag), }, setAngleBetween: { tooltip: 'angledLineToX', @@ -728,46 +713,45 @@ const transformMap: TransformMap = { yAbsolute: { equalLength: { tooltip: 'angledLineToY', - createNode: - ({ referenceSegName, inputs, tag }) => - (args) => { - const angleToMatchLengthYCall = createCallExpression( - 'angleToMatchLengthY', - [ - createIdentifier(referenceSegName), - inputs[1].expr, - createPipeSubstitution(), - ] - ) - return createCallWrapper( - 'angledLineToY', - [ - getAngleLengthSign(args[0].expr, angleToMatchLengthYCall), - inputs[1].expr, - ], - tag - ) - }, + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { + const angleToMatchLengthYCall = createCallExpression( + 'angleToMatchLengthY', + [ + createIdentifier(referenceSegName), + inputs[1].expr, + createPipeSubstitution(), + ] + ) + return createCallWrapper( + 'angledLineToY', + [ + getAngleLengthSign(args[0].expr, angleToMatchLengthYCall), + inputs[1].expr, + ], + tag + ) + }, }, vertical: { tooltip: 'yLineTo', - createNode: - ({ inputs, tag }) => - () => - createCallWrapper('yLineTo', inputs[1].expr, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('yLineTo', inputs[1].expr, tag), }, setAngle: { tooltip: 'angledLineToY', - createNode: - ({ inputs, tag, forceValueUsedInTransform }) => - (args) => { - return createCallWrapper( - 'angledLineToY', - [forceValueUsedInTransform || args[0].expr, inputs[1].expr], - tag, - getArgLiteralVal(args[0].expr) - ) - }, + createNode: ({ + inputs, + tag, + forceValueUsedInTransform, + rawArgs: args, + }) => { + return createCallWrapper( + 'angledLineToY', + [forceValueUsedInTransform || args[0].expr, inputs[1].expr], + tag, + getArgLiteralVal(args[0].expr) + ) + }, }, setAngleBetween: { tooltip: 'angledLineToY', @@ -779,14 +763,12 @@ const transformMap: TransformMap = { angle: { equalLength: { tooltip: 'angledLine', - createNode: - ({ referenceSegName, inputs, tag }) => - () => - createCallWrapper( - 'angledLine', - [inputs[0].expr, createSegLen(referenceSegName)], - tag - ), + createNode: ({ referenceSegName, inputs, tag }) => + createCallWrapper( + 'angledLine', + [inputs[0].expr, createSegLen(referenceSegName)], + tag + ), }, setLength: { tooltip: 'angledLine', @@ -824,59 +806,47 @@ const transformMap: TransformMap = { }, vertical: { tooltip: 'yLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper( - 'yLine', - getInputOfType(args, 'yRelative').expr, - tag - ), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLine', + getInputOfType(args, 'yRelative').expr, + tag + ), }, horizontal: { tooltip: 'xLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLine', args[0].expr, tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLine', args[0].expr, tag), }, }, length: { vertical: { tooltip: 'yLine', - createNode: - ({ inputs, tag }) => - ([arg0]) => { - const expr = inputs[1].expr - if (!(arg0.expr.type === 'Literal' && Number(arg0.expr.value) < 0)) - return createCallWrapper('yLine', expr, tag) - if (isExprBinaryPart(expr)) - return createCallWrapper( - 'yLine', - createUnaryExpression(expr), - tag - ) - // TODO maybe should return error here instead + createNode: ({ inputs, tag, rawArgs: args }) => { + const expr = inputs[1].expr + if ( + !(args[0].expr.type === 'Literal' && Number(args[0].expr.value) < 0) + ) return createCallWrapper('yLine', expr, tag) - }, + if (isExprBinaryPart(expr)) + return createCallWrapper('yLine', createUnaryExpression(expr), tag) + // TODO maybe should return error here instead + return createCallWrapper('yLine', expr, tag) + }, }, horizontal: { tooltip: 'xLine', - createNode: - ({ inputs, tag }) => - ([arg0]) => { - const expr = inputs[1].expr - if (!(arg0.expr.type === 'Literal' && Number(arg0.expr.value) < 0)) - return createCallWrapper('xLine', expr, tag) - if (isExprBinaryPart(expr)) - return createCallWrapper( - 'xLine', - createUnaryExpression(expr), - tag - ) - // TODO maybe should return error here instead + createNode: ({ inputs, tag, rawArgs: args }) => { + const expr = inputs[1].expr + if ( + !(args[0].expr.type === 'Literal' && Number(args[0].expr.value) < 0) + ) return createCallWrapper('xLine', expr, tag) - }, + if (isExprBinaryPart(expr)) + return createCallWrapper('xLine', createUnaryExpression(expr), tag) + // TODO maybe should return error here instead + return createCallWrapper('xLine', expr, tag) + }, }, setAngle: { tooltip: 'angledLine', @@ -896,10 +866,8 @@ const transformMap: TransformMap = { }, horizontal: { tooltip: 'xLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLine', args[0].expr, tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLine', args[0].expr, tag), }, }, angle: { @@ -911,36 +879,31 @@ const transformMap: TransformMap = { xRelative: { equalLength: { tooltip: 'angledLineOfXLength', - createNode: ({ referenceSegName, inputs, tag }) => { + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, getInputOfType(inputs, 'xRelative').expr ) - return (args) => - createCallWrapper( - 'angledLineOfXLength', - [getLegAng(args[0].expr, legAngle), minVal], - tag - ) + return createCallWrapper( + 'angledLineOfXLength', + [getLegAng(args[0].expr, legAngle), minVal], + tag + ) }, }, horizontal: { tooltip: 'xLine', - createNode: - ({ inputs, tag }) => - ([arg0]) => { - const expr = inputs[1].expr - if (!(arg0.expr.type === 'Literal' && Number(arg0.expr.value) < 0)) - return createCallWrapper('xLine', expr, tag) - if (isExprBinaryPart(expr)) - return createCallWrapper( - 'xLine', - createUnaryExpression(expr), - tag - ) - // TODO maybe should return error here instead + createNode: ({ inputs, tag, rawArgs: args }) => { + const expr = inputs[1].expr + if ( + !(args[0].expr.type === 'Literal' && Number(args[0].expr.value) < 0) + ) return createCallWrapper('xLine', expr, tag) - }, + if (isExprBinaryPart(expr)) + return createCallWrapper('xLine', createUnaryExpression(expr), tag) + // TODO maybe should return error here instead + return createCallWrapper('xLine', expr, tag) + }, }, }, }, @@ -952,14 +915,12 @@ const transformMap: TransformMap = { }, vertical: { tooltip: 'yLine', - createNode: - ({ tag }) => - (args) => - createCallWrapper( - 'yLine', - getInputOfType(args, 'yRelative').expr, - tag - ), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLine', + getInputOfType(args, 'yRelative').expr, + tag + ), }, }, angle: { @@ -971,37 +932,32 @@ const transformMap: TransformMap = { yRelative: { equalLength: { tooltip: 'angledLineOfYLength', - createNode: ({ referenceSegName, inputs, tag }) => { + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { const [minVal, legAngle] = getMinAndSegAngVals( referenceSegName, inputs[1].expr, 'legAngY' ) - return (args) => - createCallWrapper( - 'angledLineOfXLength', - [getLegAng(args[0].expr, legAngle), minVal], - tag - ) + return createCallWrapper( + 'angledLineOfXLength', + [getLegAng(args[0].expr, legAngle), minVal], + tag + ) }, }, vertical: { tooltip: 'yLine', - createNode: - ({ inputs, tag }) => - ([arg0]) => { - const expr = inputs[1].expr - if (!(arg0.expr.type === 'Literal' && Number(arg0.expr.value) < 0)) - return createCallWrapper('yLine', expr, tag) - if (isExprBinaryPart(expr)) - return createCallWrapper( - 'yLine', - createUnaryExpression(expr), - tag - ) - // TODO maybe should return error here instead + createNode: ({ inputs, tag, rawArgs: args }) => { + const expr = inputs[1].expr + if ( + !(args[0].expr.type === 'Literal' && Number(args[0].expr.value) < 0) + ) return createCallWrapper('yLine', expr, tag) - }, + if (isExprBinaryPart(expr)) + return createCallWrapper('yLine', createUnaryExpression(expr), tag) + // TODO maybe should return error here instead + return createCallWrapper('yLine', expr, tag) + }, }, }, }, @@ -1013,10 +969,8 @@ const transformMap: TransformMap = { }, horizontal: { tooltip: 'xLineTo', - createNode: - ({ tag }) => - (args) => - createCallWrapper('xLineTo', args[0].expr, tag), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper('xLineTo', args[0].expr, tag), }, }, angle: { @@ -1028,33 +982,29 @@ const transformMap: TransformMap = { xAbsolute: { equalLength: { tooltip: 'angledLineToX', - createNode: - ({ referenceSegName, inputs, tag }) => - (args) => { - const angleToMatchLengthXCall = createCallExpression( - 'angleToMatchLengthX', - [ - createIdentifier(referenceSegName), - inputs[1].expr, - createPipeSubstitution(), - ] - ) - return createCallWrapper( - 'angledLineToX', - [ - getAngleLengthSign(args[0].expr, angleToMatchLengthXCall), - inputs[1].expr, - ], - tag - ) - }, + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { + const angleToMatchLengthXCall = createCallExpression( + 'angleToMatchLengthX', + [ + createIdentifier(referenceSegName), + inputs[1].expr, + createPipeSubstitution(), + ] + ) + return createCallWrapper( + 'angledLineToX', + [ + getAngleLengthSign(args[0].expr, angleToMatchLengthXCall), + inputs[1].expr, + ], + tag + ) + }, }, horizontal: { tooltip: 'xLineTo', - createNode: - ({ inputs, tag }) => - ([arg0]) => - createCallWrapper('xLineTo', inputs[1].expr, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('xLineTo', inputs[1].expr, tag), }, }, }, @@ -1066,14 +1016,12 @@ const transformMap: TransformMap = { }, vertical: { tooltip: 'yLineTo', - createNode: - ({ tag }) => - (args) => - createCallWrapper( - 'yLineTo', - getInputOfType(args, 'yAbsolute').expr, - tag - ), + createNode: ({ tag, rawArgs: args }) => + createCallWrapper( + 'yLineTo', + getInputOfType(args, 'yAbsolute').expr, + tag + ), }, }, angle: { @@ -1085,33 +1033,29 @@ const transformMap: TransformMap = { yAbsolute: { equalLength: { tooltip: 'angledLineToY', - createNode: - ({ referenceSegName, inputs, tag }) => - (args) => { - const angleToMatchLengthXCall = createCallExpression( - 'angleToMatchLengthY', - [ - createIdentifier(referenceSegName), - inputs[1].expr, - createPipeSubstitution(), - ] - ) - return createCallWrapper( - 'angledLineToY', - [ - getAngleLengthSign(args[0].expr, angleToMatchLengthXCall), - inputs[1].expr, - ], - tag - ) - }, + createNode: ({ referenceSegName, inputs, tag, rawArgs: args }) => { + const angleToMatchLengthXCall = createCallExpression( + 'angleToMatchLengthY', + [ + createIdentifier(referenceSegName), + inputs[1].expr, + createPipeSubstitution(), + ] + ) + return createCallWrapper( + 'angledLineToY', + [ + getAngleLengthSign(args[0].expr, angleToMatchLengthXCall), + inputs[1].expr, + ], + tag + ) + }, }, vertical: { tooltip: 'yLineTo', - createNode: - ({ inputs, tag }) => - () => - createCallWrapper('yLineTo', inputs[1].expr, tag), + createNode: ({ inputs, tag }) => + createCallWrapper('yLineTo', inputs[1].expr, tag), }, }, }, @@ -1119,23 +1063,20 @@ const transformMap: TransformMap = { free: { equalLength: { tooltip: 'xLine', - createNode: - ({ referenceSegName, tag }) => - (arg) => { - const argVal = getArgLiteralVal(arg[0].expr) - const segLen = createSegLen(referenceSegName) // as BinaryPart - if (argVal > 0) - return createCallWrapper('xLine', segLen, tag, argVal) - if (isExprBinaryPart(segLen)) - return createCallWrapper( - 'xLine', - createUnaryExpression(segLen), - tag, - argVal - ) - // should probably return error here instead - return createCallWrapper('xLine', segLen, tag, argVal) - }, + createNode: ({ referenceSegName, tag, rawArgs: args }) => { + const argVal = getArgLiteralVal(args[0].expr) + const segLen = createSegLen(referenceSegName) // as BinaryPart + if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal) + if (isExprBinaryPart(segLen)) + return createCallWrapper( + 'xLine', + createUnaryExpression(segLen), + tag, + argVal + ) + // should probably return error here instead + return createCallWrapper('xLine', segLen, tag, argVal) + }, }, setHorzDistance: { tooltip: 'xLineTo', @@ -1159,14 +1100,12 @@ const transformMap: TransformMap = { free: { equalLength: { tooltip: 'yLine', - createNode: - ({ referenceSegName, tag }) => - (arg) => { - const argVal = getArgLiteralVal(arg[0].expr) - let segLen = createSegLen(referenceSegName) as BinaryPart - if (argVal < 0) segLen = createUnaryExpression(segLen) - return createCallWrapper('yLine', segLen, tag, argVal) - }, + createNode: ({ referenceSegName, tag, rawArgs: args }) => { + const argVal = getArgLiteralVal(args[0].expr) + let segLen = createSegLen(referenceSegName) as BinaryPart + if (argVal < 0) segLen = createUnaryExpression(segLen) + return createCallWrapper('yLine', segLen, tag, argVal) + }, }, setLength: { tooltip: 'yLine', @@ -1190,10 +1129,8 @@ const transformMap: TransformMap = { free: { equalLength: { tooltip: 'xLine', - createNode: - ({ referenceSegName, tag }) => - () => - createCallWrapper('xLine', createSegLen(referenceSegName), tag), + createNode: ({ referenceSegName, tag }) => + createCallWrapper('xLine', createSegLen(referenceSegName), tag), }, setLength: { tooltip: 'xLine', @@ -1205,10 +1142,8 @@ const transformMap: TransformMap = { free: { equalLength: { tooltip: 'yLine', - createNode: - ({ referenceSegName, tag }) => - () => - createCallWrapper('yLine', createSegLen(referenceSegName), tag), + createNode: ({ referenceSegName, tag }) => + createCallWrapper('yLine', createSegLen(referenceSegName), tag), }, setLength: { tooltip: 'yLine', @@ -1243,23 +1178,21 @@ export function getRemoveConstraintsTransform( const transformInfo: TransformInfo = { tooltip: 'line', // tooltip: name, - createNode: - ({ tag, referenceSegName }) => - (args) => { - return createCallWrapper('line', [args[0].expr, args[1].expr], tag) - // The following commented changes values to hardcode, but keeps the line type the same, maybe that's useful? - - // if (name === 'angledLineThatIntersects') { - // return intersectCallWrapper({ - // fnName: name, - // angleVal: args[0].expr, - // offsetVal: args[1].expr, - // intersectTag: createIdentifier(referenceSegName), - // tag, - // }) - // } - // return createCallWrapper(name, args, tag) - }, + createNode: ({ tag, referenceSegName, rawArgs: args }) => { + return createCallWrapper('line', [args[0].expr, args[1].expr], tag) + // The following commented changes values to hardcode, but keeps the line type the same, maybe that's useful? + + // if (name === 'angledLineThatIntersects') { + // return intersectCallWrapper({ + // fnName: name, + // angleVal: args[0].expr, + // offsetVal: args[1].expr, + // intersectTag: createIdentifier(referenceSegName), + // tag, + // }) + // } + // return createCallWrapper(name, args, tag) + }, } // check if the function is locked down and so can't be transformed @@ -1319,118 +1252,111 @@ export function removeSingleConstraint({ const transform: TransformInfo = { tooltip: callExp.node.callee.name as any, - createNode: - ({ tag, inputs }) => - (rawValues) => { - // inputs is the current values for each of the inputs - // rawValues is the rav 'literal' values equivalent to the inputs - // inputDetails is the one variable we're removing the constraint from - // So we should update the call expression to use the inputs, except for - // the inputDetails, input where we should use the rawValue(s) - - if (inputDetails.type === 'arrayItem') { - const values = inputs.map((arg) => { - if ( - !( - (arg.type === 'arrayItem' || arg.type === 'arrayOrObjItem') && - arg.index === inputDetails.index - ) + createNode: ({ tag, inputs, rawArgs }) => { + // inputs is the current values for each of the inputs + // rawValues is the rav 'literal' values equivalent to the inputs + // inputDetails is the one variable we're removing the constraint from + // So we should update the call expression to use the inputs, except for + // the inputDetails, input where we should use the rawValue(s) + + if (inputDetails.type === 'arrayItem') { + const values = inputs.map((arg) => { + if ( + !( + (arg.type === 'arrayItem' || arg.type === 'arrayOrObjItem') && + arg.index === inputDetails.index ) - return arg.expr - const literal = rawValues.find( - (rawValue) => - (rawValue.type === 'arrayItem' || - rawValue.type === 'arrayOrObjItem') && - rawValue.index === inputDetails.index - )?.expr - return (arg.index === inputDetails.index && literal) || arg.expr - }) - return createStdlibCallExpression( - callExp.node.callee.name as any, - createArrayExpression(values), - tag ) - } - if ( - inputDetails.type === 'arrayInObject' || - inputDetails.type === 'objectProperty' - ) { - const arrayDetailsNameBetterLater: { - [key: string]: Parameters[0] - } = {} - const otherThing: Parameters[0] = {} - inputs.forEach((arg) => { - if ( - arg.type !== 'objectProperty' && - arg.type !== 'arrayOrObjItem' && - arg.type !== 'arrayInObject' - ) - return - const rawLiteralArrayInObject = rawValues.find( - (rawValue) => - rawValue.type === 'arrayInObject' && - rawValue.key === inputDetails.key && - rawValue.index === - (arg.type === 'arrayInObject' ? arg.index : -1) - ) - const rawLiteralObjProp = rawValues.find( - (rawValue) => - (rawValue.type === 'objectProperty' || - rawValue.type === 'arrayOrObjItem' || - rawValue.type === 'arrayInObject') && - rawValue.key === inputDetails.key - ) - if ( - inputDetails.type === 'arrayInObject' && - rawLiteralArrayInObject?.type === 'arrayInObject' && - rawLiteralArrayInObject?.index === inputDetails.index && - rawLiteralArrayInObject?.key === inputDetails.key - ) { - if (!arrayDetailsNameBetterLater[arg.key]) - arrayDetailsNameBetterLater[arg.key] = [] - arrayDetailsNameBetterLater[inputDetails.key][ - inputDetails.index - ] = rawLiteralArrayInObject.expr - } else if ( - inputDetails.type === 'objectProperty' && - (rawLiteralObjProp?.type === 'objectProperty' || - rawLiteralObjProp?.type === 'arrayOrObjItem') && - rawLiteralObjProp?.key === inputDetails.key && - arg.key === inputDetails.key - ) { - otherThing[inputDetails.key] = rawLiteralObjProp.expr - } else if (arg.type === 'arrayInObject') { - if (!arrayDetailsNameBetterLater[arg.key]) - arrayDetailsNameBetterLater[arg.key] = [] - arrayDetailsNameBetterLater[arg.key][arg.index] = arg.expr - } else if (arg.type === 'objectProperty') { - otherThing[arg.key] = arg.expr - } - }) - const createObjParam: Parameters[0] = - {} - Object.entries(arrayDetailsNameBetterLater).forEach( - ([key, value]) => { - createObjParam[key] = createArrayExpression(value) - } + return arg.expr + const literal = rawArgs.find( + (rawValue) => + (rawValue.type === 'arrayItem' || + rawValue.type === 'arrayOrObjItem') && + rawValue.index === inputDetails.index + )?.expr + return (arg.index === inputDetails.index && literal) || arg.expr + }) + return createStdlibCallExpression( + callExp.node.callee.name as any, + createArrayExpression(values), + tag + ) + } + if ( + inputDetails.type === 'arrayInObject' || + inputDetails.type === 'objectProperty' + ) { + const arrayDetailsNameBetterLater: { + [key: string]: Parameters[0] + } = {} + const otherThing: Parameters[0] = {} + inputs.forEach((arg) => { + if ( + arg.type !== 'objectProperty' && + arg.type !== 'arrayOrObjItem' && + arg.type !== 'arrayInObject' ) - const objExp = createObjectExpression({ - ...createObjParam, - ...otherThing, - }) - return createStdlibCallExpression( - callExp.node.callee.name as any, - objExp, - tag + return + const rawLiteralArrayInObject = rawArgs.find( + (rawValue) => + rawValue.type === 'arrayInObject' && + rawValue.key === inputDetails.key && + rawValue.index === (arg.type === 'arrayInObject' ? arg.index : -1) ) - } - - return createCallWrapper( + const rawLiteralObjProp = rawArgs.find( + (rawValue) => + (rawValue.type === 'objectProperty' || + rawValue.type === 'arrayOrObjItem' || + rawValue.type === 'arrayInObject') && + rawValue.key === inputDetails.key + ) + if ( + inputDetails.type === 'arrayInObject' && + rawLiteralArrayInObject?.type === 'arrayInObject' && + rawLiteralArrayInObject?.index === inputDetails.index && + rawLiteralArrayInObject?.key === inputDetails.key + ) { + if (!arrayDetailsNameBetterLater[arg.key]) + arrayDetailsNameBetterLater[arg.key] = [] + arrayDetailsNameBetterLater[inputDetails.key][inputDetails.index] = + rawLiteralArrayInObject.expr + } else if ( + inputDetails.type === 'objectProperty' && + (rawLiteralObjProp?.type === 'objectProperty' || + rawLiteralObjProp?.type === 'arrayOrObjItem') && + rawLiteralObjProp?.key === inputDetails.key && + arg.key === inputDetails.key + ) { + otherThing[inputDetails.key] = rawLiteralObjProp.expr + } else if (arg.type === 'arrayInObject') { + if (!arrayDetailsNameBetterLater[arg.key]) + arrayDetailsNameBetterLater[arg.key] = [] + arrayDetailsNameBetterLater[arg.key][arg.index] = arg.expr + } else if (arg.type === 'objectProperty') { + otherThing[arg.key] = arg.expr + } + }) + const createObjParam: Parameters[0] = {} + Object.entries(arrayDetailsNameBetterLater).forEach(([key, value]) => { + createObjParam[key] = createArrayExpression(value) + }) + const objExp = createObjectExpression({ + ...createObjParam, + ...otherThing, + }) + return createStdlibCallExpression( callExp.node.callee.name as any, - rawValues[0].expr, + objExp, tag ) - }, + } + + return createCallWrapper( + callExp.node.callee.name as any, + rawArgs[0].expr, + tag + ) + }, } return transform } @@ -1818,12 +1744,16 @@ export function transformAstSketchLines({ to, from, }, - createCallback: callBack({ - referenceSegName: _referencedSegmentName, - inputs, - tag: callBackTag, - forceValueUsedInTransform, - }), + + createCallback: (rawArgs) => + callBack({ + referenceSegName: _referencedSegmentName, + inputs, + tag: callBackTag, + rawArgs, + forceValueUsedInTransform, + referencedSegment, + }), }) if (err(replacedSketchLine)) return replacedSketchLine diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index ef39c72df3..fc028fa2e6 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -69,7 +69,7 @@ interface addCall extends ModifyAstBase { segmentInput: SegmentInputs referencedSegment?: Path replaceExisting?: boolean - createCallback?: TransformCallback // TODO: #29 probably should not be optional + createCallback?: (rawArgs: RawArgs) => ReturnType /// defaults to false, normal behavior is to add a new callExpression to the end of the pipeExpression spliceBetween?: boolean } @@ -149,13 +149,13 @@ type RawArg = _VarValue export type InputArgs = Array -// /** -// * The literal equivalent of whatever current expression is -// * i.e. if the expression is 5 + 6, the literal would be 11 -// * but of course works for expressions like myVar + someFn() etc too -// * This is useful in cases where we want to "un-constrain" inputs to segments -// */ -type RawArgs = Array +/** + * The literal equivalent of whatever current expression is + * i.e. if the expression is 5 + 6, the literal would be 11 + * but of course works for expressions like myVar + someFn() etc too + * This is useful in cases where we want to "un-constrain" inputs to segments + */ +export type RawArgs = Array /** * Serves the same role as {@link InputArg} on {@link RawArg} @@ -174,14 +174,23 @@ export type SimplifiedArgDetails = index: 0 | 1 } -export type TransformCallback = ( - inputs: InputArgs, +export type CreateStdLibSketchCallExpr = (args: { + inputs: InputArgs + referenceSegName: string + tag?: Expr + forceValueUsedInTransform?: Expr + rawArgs: InputArgs referencedSegment?: Path -) => { +}) => { callExp: Expr valueUsedInTransform?: number } +export type TransformInfo = { + tooltip: ToolTip + createNode: CreateStdLibSketchCallExpr +} + export interface ConstrainInfo { stdLibFnName: ToolTip type: From a6aff874bb092e1685e152efcb71b72b777eec4a Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Tue, 10 Sep 2024 01:43:34 +1000 Subject: [PATCH 35/41] more types clean up --- src/lang/std/sketch.ts | 105 +++++++++++---------------------------- src/lang/std/stdTypes.ts | 29 +++++------ 2 files changed, 41 insertions(+), 93 deletions(-) diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index a1ff5bbf12..122da6e24c 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -296,9 +296,7 @@ export const lineTo: SketchLineHelper = { node, pathToNode, segmentInput, - createCallback, - replaceExisting, - referencedSegment, + replaceExistingCallback: createCallback, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const to = segmentInput.to @@ -321,7 +319,7 @@ export const lineTo: SketchLineHelper = { createPipeSubstitution(), ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - if (replaceExisting && createCallback) { + if (createCallback) { const { callExp, valueUsedInTransform } = createCallback([ { type: 'arrayItem', @@ -388,9 +386,7 @@ export const line: SketchLineHelper = { previousProgramMemory, pathToNode, segmentInput, - replaceExisting, - referencedSegment, - createCallback, + replaceExistingCallback: createCallback, spliceBetween, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR @@ -437,7 +433,7 @@ export const line: SketchLineHelper = { } } - if (replaceExisting && createCallback && pipe.type !== 'CallExpression') { + if (createCallback && pipe.type !== 'CallExpression') { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { @@ -519,13 +515,7 @@ export const line: SketchLineHelper = { } export const xLineTo: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - replaceExisting, - createCallback, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { to } = segmentInput const _node = { ...node } @@ -536,7 +526,7 @@ export const xLineTo: SketchLineHelper = { const newVal = createLiteral(roundOff(to[0], 2)) - if (replaceExisting && createCallback) { + if (createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { @@ -593,13 +583,7 @@ export const xLineTo: SketchLineHelper = { } export const yLineTo: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - replaceExisting, - createCallback, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { to } = segmentInput const _node = { ...node } @@ -610,7 +594,7 @@ export const yLineTo: SketchLineHelper = { const newVal = createLiteral(roundOff(to[1], 2)) - if (replaceExisting && createCallback) { + if (createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { @@ -667,13 +651,7 @@ export const yLineTo: SketchLineHelper = { } export const xLine: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - replaceExisting, - createCallback, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput const _node = { ...node } @@ -684,7 +662,7 @@ export const xLine: SketchLineHelper = { const newVal = createLiteral(roundOff(to[0] - from[0], 2)) - if (replaceExisting && createCallback) { + if (createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { @@ -739,13 +717,7 @@ export const xLine: SketchLineHelper = { } export const yLine: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - replaceExisting, - createCallback, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput const _node = { ...node } @@ -754,7 +726,7 @@ export const yLine: SketchLineHelper = { if (err(_node1)) return _node1 const { node: pipe } = _node1 const newVal = createLiteral(roundOff(to[1] - from[1], 2)) - if (replaceExisting && createCallback) { + if (createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { @@ -813,9 +785,7 @@ export const tangentialArcTo: SketchLineHelper = { node, pathToNode, segmentInput, - createCallback, - replaceExisting, - referencedSegment, + replaceExistingCallback: createCallback, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { to } = segmentInput @@ -835,7 +805,7 @@ export const tangentialArcTo: SketchLineHelper = { const toX = createLiteral(roundOff(to[0], 2)) const toY = createLiteral(roundOff(to[1], 2)) - if (replaceExisting && createCallback && pipe.type !== 'CallExpression') { + if (createCallback && pipe.type !== 'CallExpression') { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { @@ -950,13 +920,7 @@ export const tangentialArcTo: SketchLineHelper = { }, } export const circle: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - createCallback, - replaceExisting, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR const { center, radius } = segmentInput @@ -975,7 +939,7 @@ export const circle: SketchLineHelper = { const radiusExp = createLiteral(roundOff(radius, 2)) - if (replaceExisting && createCallback) { + if (createCallback) { const { callExp, valueUsedInTransform } = createCallback([ { type: 'arrayInObject', @@ -1125,9 +1089,7 @@ export const angledLine: SketchLineHelper = { node, pathToNode, segmentInput, - createCallback, - replaceExisting, - referencedSegment, + replaceExistingCallback: createCallback, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput @@ -1144,7 +1106,7 @@ export const angledLine: SketchLineHelper = { createPipeSubstitution(), ]) - if (replaceExisting && createCallback) { + if (createCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { callExp, valueUsedInTransform } = createCallback([ { @@ -1221,8 +1183,7 @@ export const angledLineOfXLength: SketchLineHelper = { previousProgramMemory, pathToNode, segmentInput, - createCallback, - replaceExisting, + replaceExistingCallback: createCallback, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput @@ -1274,7 +1235,7 @@ export const angledLineOfXLength: SketchLineHelper = { createPipeSubstitution(), ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - if (replaceExisting) { + if (createCallback) { pipe.body[callIndex] = newLine } else { pipe.body = [...pipe.body, newLine] @@ -1333,8 +1294,7 @@ export const angledLineOfYLength: SketchLineHelper = { previousProgramMemory, pathToNode, segmentInput, - createCallback, - replaceExisting, + replaceExistingCallback: createCallback, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput @@ -1384,7 +1344,7 @@ export const angledLineOfYLength: SketchLineHelper = { createPipeSubstitution(), ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - if (replaceExisting) { + if (createCallback) { pipe.body[callIndex] = newLine } else { pipe.body = [...pipe.body, newLine] @@ -1442,9 +1402,7 @@ export const angledLineToX: SketchLineHelper = { node, pathToNode, segmentInput, - createCallback, - replaceExisting, - referencedSegment, + replaceExistingCallback: createCallback, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput @@ -1459,7 +1417,7 @@ export const angledLineToX: SketchLineHelper = { const { node: pipe } = nodeMeta const angle = createLiteral(roundOff(getAngle(from, to), 0)) const xArg = createLiteral(roundOff(to[0], 2)) - if (replaceExisting && createCallback) { + if (createCallback) { const { callExp, valueUsedInTransform } = createCallback([ { type: 'arrayOrObjItem', @@ -1541,9 +1499,7 @@ export const angledLineToY: SketchLineHelper = { node, pathToNode, segmentInput, - createCallback, - replaceExisting, - referencedSegment, + replaceExistingCallback: createCallback, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput @@ -1560,7 +1516,7 @@ export const angledLineToY: SketchLineHelper = { const angle = createLiteral(roundOff(getAngle(from, to), 0)) const yArg = createLiteral(roundOff(to[1], 2)) - if (replaceExisting && createCallback) { + if (createCallback) { const { callExp, valueUsedInTransform } = createCallback([ { type: 'arrayOrObjItem', @@ -1642,8 +1598,7 @@ export const angledLineThatIntersects: SketchLineHelper = { node, pathToNode, segmentInput, - createCallback, - replaceExisting, + replaceExistingCallback: createCallback, referencedSegment, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR @@ -1674,7 +1629,7 @@ export const angledLineThatIntersects: SketchLineHelper = { ) ) - if (replaceExisting && createCallback) { + if (createCallback) { const { callExp, valueUsedInTransform } = createCallback([ { type: 'objectProperty', @@ -2034,7 +1989,6 @@ export function addNewSketchLn({ previousProgramMemory, pathToNode, segmentInput, - replaceExisting: false, spliceBetween, }) } @@ -2125,8 +2079,7 @@ export function replaceSketchLine({ pathToNode: _pathToNode, referencedSegment, segmentInput, - replaceExisting: true, - createCallback, + replaceExistingCallback: createCallback, }) if (err(addRetVal)) return addRetVal diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index fc028fa2e6..3aeebc081d 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -9,22 +9,8 @@ import { CallExpression, Literal, } from '../wasm' -import { EngineCommandManager } from './engineConnection' import { LineInputsType } from './sketchcombos' -export interface InternalFirstArg { - programMemory: ProgramMemory - name?: string - sourceRange: SourceRange - engineCommandManager: EngineCommandManager - code: string -} - -export interface PathReturn { - programMemory: ProgramMemory - currentPath: Path -} - export interface ModifyAstBase { node: Program // TODO #896: Remove ProgramMemory from this interface @@ -65,12 +51,21 @@ interface ArcSegmentInput { */ export type SegmentInputs = StraightSegmentInput | ArcSegmentInput + +/** + * Interface for adding or replacing a sketch stblib call expression to a sketch. + * Replacing normally means adding or removing a constraint + * + * @property segmentInput - The input segment data, which can be either a straight segment or an arc segment. + * @property replaceExistingCallback - An optional callback function to replace an existing call expression, + * if not provided, a new call expression will be added using segMentInput values. + * @property referencedSegment - An optional path to a referenced segment. + * @property spliceBetween=false - Defaults to false. Normal behavior is to add a new callExpression to the end of the pipeExpression. + */ interface addCall extends ModifyAstBase { segmentInput: SegmentInputs + replaceExistingCallback?: (rawArgs: RawArgs) => ReturnType referencedSegment?: Path - replaceExisting?: boolean - createCallback?: (rawArgs: RawArgs) => ReturnType - /// defaults to false, normal behavior is to add a new callExpression to the end of the pipeExpression spliceBetween?: boolean } From 28a63194c739dfea62737143406118aeb46c94bf Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Tue, 10 Sep 2024 01:49:07 +1000 Subject: [PATCH 36/41] more clean up --- src/lang/std/sketch.ts | 127 +++++++++++++++-------------------- src/lang/std/sketchcombos.ts | 2 +- src/lang/std/stdTypes.ts | 7 +- 3 files changed, 59 insertions(+), 77 deletions(-) diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index 122da6e24c..e50f5541be 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -292,12 +292,7 @@ function getTag(index = 2): SketchLineHelper['getTag'] { } export const lineTo: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - replaceExistingCallback: createCallback, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const to = segmentInput.to const _node = { ...node } @@ -319,8 +314,8 @@ export const lineTo: SketchLineHelper = { createPipeSubstitution(), ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - if (createCallback) { - const { callExp, valueUsedInTransform } = createCallback([ + if (replaceExistingCallback) { + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'arrayItem', index: 0, @@ -386,7 +381,7 @@ export const line: SketchLineHelper = { previousProgramMemory, pathToNode, segmentInput, - replaceExistingCallback: createCallback, + replaceExistingCallback, spliceBetween, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR @@ -410,7 +405,11 @@ export const line: SketchLineHelper = { const newXVal = createLiteral(roundOff(to[0] - from[0], 2)) const newYVal = createLiteral(roundOff(to[1] - from[1], 2)) - if (spliceBetween && !createCallback && pipe.type === 'PipeExpression') { + if ( + spliceBetween && + !replaceExistingCallback && + pipe.type === 'PipeExpression' + ) { const callExp = createCallExpression('line', [ createArrayExpression([newXVal, newYVal]), createPipeSubstitution(), @@ -433,9 +432,9 @@ export const line: SketchLineHelper = { } } - if (createCallback && pipe.type !== 'CallExpression') { + if (replaceExistingCallback && pipe.type !== 'CallExpression') { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback([ + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'arrayItem', index: 0, @@ -515,7 +514,7 @@ export const line: SketchLineHelper = { } export const xLineTo: SketchLineHelper = { - add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { to } = segmentInput const _node = { ...node } @@ -526,9 +525,9 @@ export const xLineTo: SketchLineHelper = { const newVal = createLiteral(roundOff(to[0], 2)) - if (createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback([ + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'singleValue', argType: 'xAbsolute', @@ -583,7 +582,7 @@ export const xLineTo: SketchLineHelper = { } export const yLineTo: SketchLineHelper = { - add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { to } = segmentInput const _node = { ...node } @@ -594,9 +593,9 @@ export const yLineTo: SketchLineHelper = { const newVal = createLiteral(roundOff(to[1], 2)) - if (createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback([ + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'singleValue', argType: 'yAbsolute', @@ -651,7 +650,7 @@ export const yLineTo: SketchLineHelper = { } export const xLine: SketchLineHelper = { - add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput const _node = { ...node } @@ -662,9 +661,9 @@ export const xLine: SketchLineHelper = { const newVal = createLiteral(roundOff(to[0] - from[0], 2)) - if (createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback([ + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'singleValue', argType: 'xRelative', @@ -717,7 +716,7 @@ export const xLine: SketchLineHelper = { } export const yLine: SketchLineHelper = { - add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput const _node = { ...node } @@ -726,9 +725,9 @@ export const yLine: SketchLineHelper = { if (err(_node1)) return _node1 const { node: pipe } = _node1 const newVal = createLiteral(roundOff(to[1] - from[1], 2)) - if (createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback([ + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'singleValue', argType: 'yRelative', @@ -781,12 +780,7 @@ export const yLine: SketchLineHelper = { } export const tangentialArcTo: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - replaceExistingCallback: createCallback, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { to } = segmentInput const _node = { ...node } @@ -805,9 +799,9 @@ export const tangentialArcTo: SketchLineHelper = { const toX = createLiteral(roundOff(to[0], 2)) const toY = createLiteral(roundOff(to[1], 2)) - if (createCallback && pipe.type !== 'CallExpression') { + if (replaceExistingCallback && pipe.type !== 'CallExpression') { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback([ + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'arrayItem', index: 0, @@ -920,7 +914,7 @@ export const tangentialArcTo: SketchLineHelper = { }, } export const circle: SketchLineHelper = { - add: ({ node, pathToNode, segmentInput, replaceExistingCallback: createCallback }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR const { center, radius } = segmentInput @@ -939,8 +933,8 @@ export const circle: SketchLineHelper = { const radiusExp = createLiteral(roundOff(radius, 2)) - if (createCallback) { - const { callExp, valueUsedInTransform } = createCallback([ + if (replaceExistingCallback) { + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'arrayInObject', index: 0, @@ -1085,12 +1079,7 @@ export const circle: SketchLineHelper = { }, } export const angledLine: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - replaceExistingCallback: createCallback, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput const _node = { ...node } @@ -1106,9 +1095,9 @@ export const angledLine: SketchLineHelper = { createPipeSubstitution(), ]) - if (createCallback) { + if (replaceExistingCallback) { const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - const { callExp, valueUsedInTransform } = createCallback([ + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'arrayOrObjItem', index: 0, @@ -1183,7 +1172,7 @@ export const angledLineOfXLength: SketchLineHelper = { previousProgramMemory, pathToNode, segmentInput, - replaceExistingCallback: createCallback, + replaceExistingCallback, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput @@ -1213,8 +1202,8 @@ export const angledLineOfXLength: SketchLineHelper = { } const angle = createLiteral(roundOff(getAngle(from, to), 0)) const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1) - const newLine = createCallback - ? createCallback([ + const newLine = replaceExistingCallback + ? replaceExistingCallback([ { type: 'arrayOrObjItem', index: 0, @@ -1235,7 +1224,7 @@ export const angledLineOfXLength: SketchLineHelper = { createPipeSubstitution(), ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - if (createCallback) { + if (replaceExistingCallback) { pipe.body[callIndex] = newLine } else { pipe.body = [...pipe.body, newLine] @@ -1294,7 +1283,7 @@ export const angledLineOfYLength: SketchLineHelper = { previousProgramMemory, pathToNode, segmentInput, - replaceExistingCallback: createCallback, + replaceExistingCallback, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput @@ -1322,8 +1311,8 @@ export const angledLineOfYLength: SketchLineHelper = { const angle = createLiteral(roundOff(getAngle(from, to), 0)) const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1) - const newLine = createCallback - ? createCallback([ + const newLine = replaceExistingCallback + ? replaceExistingCallback([ { type: 'arrayOrObjItem', index: 0, @@ -1344,7 +1333,7 @@ export const angledLineOfYLength: SketchLineHelper = { createPipeSubstitution(), ]) const { index: callIndex } = splitPathAtPipeExpression(pathToNode) - if (createCallback) { + if (replaceExistingCallback) { pipe.body[callIndex] = newLine } else { pipe.body = [...pipe.body, newLine] @@ -1398,12 +1387,7 @@ export const angledLineOfYLength: SketchLineHelper = { } export const angledLineToX: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - replaceExistingCallback: createCallback, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput const _node = { ...node } @@ -1417,8 +1401,8 @@ export const angledLineToX: SketchLineHelper = { const { node: pipe } = nodeMeta const angle = createLiteral(roundOff(getAngle(from, to), 0)) const xArg = createLiteral(roundOff(to[0], 2)) - if (createCallback) { - const { callExp, valueUsedInTransform } = createCallback([ + if (replaceExistingCallback) { + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'arrayOrObjItem', index: 0, @@ -1495,12 +1479,7 @@ export const angledLineToX: SketchLineHelper = { } export const angledLineToY: SketchLineHelper = { - add: ({ - node, - pathToNode, - segmentInput, - replaceExistingCallback: createCallback, - }) => { + add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR const { from, to } = segmentInput const _node = { ...node } @@ -1516,8 +1495,8 @@ export const angledLineToY: SketchLineHelper = { const angle = createLiteral(roundOff(getAngle(from, to), 0)) const yArg = createLiteral(roundOff(to[1], 2)) - if (createCallback) { - const { callExp, valueUsedInTransform } = createCallback([ + if (replaceExistingCallback) { + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'arrayOrObjItem', index: 0, @@ -1598,7 +1577,7 @@ export const angledLineThatIntersects: SketchLineHelper = { node, pathToNode, segmentInput, - replaceExistingCallback: createCallback, + replaceExistingCallback, referencedSegment, }) => { if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR @@ -1629,8 +1608,8 @@ export const angledLineThatIntersects: SketchLineHelper = { ) ) - if (createCallback) { - const { callExp, valueUsedInTransform } = createCallback([ + if (replaceExistingCallback) { + const { callExp, valueUsedInTransform } = replaceExistingCallback([ { type: 'objectProperty', key: 'angle', @@ -2050,7 +2029,7 @@ export function replaceSketchLine({ pathToNode: _pathToNode, fnName, segmentInput, - createCallback, + replaceExistingCallback, referencedSegment, }: { node: Program @@ -2058,7 +2037,9 @@ export function replaceSketchLine({ pathToNode: PathToNode fnName: ToolTip segmentInput: SegmentInputs - createCallback: (rawArgs: RawArgs) => ReturnType + replaceExistingCallback: ( + rawArgs: RawArgs + ) => ReturnType referencedSegment?: Path }): | { @@ -2079,7 +2060,7 @@ export function replaceSketchLine({ pathToNode: _pathToNode, referencedSegment, segmentInput, - replaceExistingCallback: createCallback, + replaceExistingCallback, }) if (err(addRetVal)) return addRetVal diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index ee685d54b0..cc5be71899 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1745,7 +1745,7 @@ export function transformAstSketchLines({ from, }, - createCallback: (rawArgs) => + replaceExistingCallback: (rawArgs) => callBack({ referenceSegName: _referencedSegmentName, inputs, diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index 3aeebc081d..e2952befb2 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -51,11 +51,10 @@ interface ArcSegmentInput { */ export type SegmentInputs = StraightSegmentInput | ArcSegmentInput - /** * Interface for adding or replacing a sketch stblib call expression to a sketch. * Replacing normally means adding or removing a constraint - * + * * @property segmentInput - The input segment data, which can be either a straight segment or an arc segment. * @property replaceExistingCallback - An optional callback function to replace an existing call expression, * if not provided, a new call expression will be added using segMentInput values. @@ -64,7 +63,9 @@ export type SegmentInputs = StraightSegmentInput | ArcSegmentInput */ interface addCall extends ModifyAstBase { segmentInput: SegmentInputs - replaceExistingCallback?: (rawArgs: RawArgs) => ReturnType + replaceExistingCallback?: ( + rawArgs: RawArgs + ) => ReturnType referencedSegment?: Path spliceBetween?: boolean } From 0abe4c4082855476d50234160cc3b53586904e5e Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Tue, 10 Sep 2024 11:51:03 +1000 Subject: [PATCH 37/41] some xstate v5 stuff that got missed from merge with main --- src/lib/toolbar.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/toolbar.ts b/src/lib/toolbar.ts index 21ab057867..5e153fd73e 100644 --- a/src/lib/toolbar.ts +++ b/src/lib/toolbar.ts @@ -375,11 +375,11 @@ export const toolbarConfig: Record = { [ { id: 'circle-center', - onClick: ({ modelingStateMatches, modelingSend }) => + onClick: ({ modelingState, modelingSend }) => modelingSend({ type: 'change tool', data: { - tool: !modelingStateMatches('Sketch.Circle tool') + tool: !modelingState.matches({ Sketch: 'Circle tool' }) ? 'circle' : 'none', }, @@ -389,8 +389,8 @@ export const toolbarConfig: Record = { title: 'Center circle', disabled: (state) => !canRectangleOrCircleTool(state.context) && - !state.matches('Sketch.Circle tool'), - isActive: (state) => state.matches('Sketch.Circle tool'), + !state.matches({ Sketch: 'Circle tool' }), + isActive: (state) => state.matches({ Sketch: 'Circle tool' }), showTitle: false, description: 'Start drawing a circle from its center', links: [ From 04e82bf7283200901766f92991cdc48c65e3e03f Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Tue, 10 Sep 2024 13:15:11 +1000 Subject: [PATCH 38/41] comments and naming tweaks --- src/lang/std/sketch.ts | 6 ++-- src/lang/std/sketchcombos.ts | 63 +++++++++++++----------------------- src/lang/std/stdTypes.ts | 28 +++++++++++----- 3 files changed, 45 insertions(+), 52 deletions(-) diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index e50f5541be..7d3827b9e6 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -36,7 +36,7 @@ import { SegmentInputs, SimplifiedArgDetails, RawArgs, - CreateStdLibSketchCallExpr, + CreatedSketchExprResult, } from 'lang/std/stdTypes' import { @@ -2037,9 +2037,7 @@ export function replaceSketchLine({ pathToNode: PathToNode fnName: ToolTip segmentInput: SegmentInputs - replaceExistingCallback: ( - rawArgs: RawArgs - ) => ReturnType + replaceExistingCallback: (rawArgs: RawArgs) => CreatedSketchExprResult referencedSegment?: Path }): | { diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index cc5be71899..cbac3bf03c 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -1,4 +1,5 @@ import { + CreatedSketchExprResult, CreateStdLibSketchCallExpr, InputArg, InputArgs, @@ -17,6 +18,7 @@ import { PathToNode, ProgramMemory, sketchGroupFromKclValue, + Literal, } from '../wasm' import { getNodeFromPath, @@ -78,7 +80,7 @@ function createCallWrapper( val: [Expr, Expr] | Expr, tag?: Expr, valueUsedInTransform?: number -): ReturnType { +): CreatedSketchExprResult { const args = tooltip === 'circle' ? [] @@ -116,7 +118,7 @@ function createStdlibCallExpression( val: Expr, tag?: Expr, valueUsedInTransform?: number -): ReturnType { +): CreatedSketchExprResult { const args = [val, createPipeSubstitution()] if (tag) { args.push(tag) @@ -141,7 +143,7 @@ function intersectCallWrapper({ intersectTag: Expr tag?: Expr valueUsedInTransform?: number -}): ReturnType { +}): CreatedSketchExprResult { const firstArg: any = { angle: angleVal, offset: offsetVal, @@ -261,13 +263,11 @@ const getMinAndSegAngVals = ( return [minVal, legAngle] } -const getSignedLeg = (arg: Expr, legLenVal: BinaryPart) => - arg.type === 'Literal' && Number(arg.value) < 0 - ? createUnaryExpression(legLenVal) - : legLenVal +const getSignedLeg = (arg: Literal, legLenVal: BinaryPart) => + Number(arg.value) < 0 ? createUnaryExpression(legLenVal) : legLenVal -const getLegAng = (arg: Expr, legAngleVal: BinaryPart) => { - const ang = (arg.type === 'Literal' && Number(arg.value)) || 0 +const getLegAng = (arg: Literal, legAngleVal: BinaryPart) => { + const ang = Number(arg.value) || 0 const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360 const truncatedTo90 = Math.floor(normalisedAngle / 90) * 90 const binExp = createBinaryExpressionWithUnary([ @@ -277,18 +277,18 @@ const getLegAng = (arg: Expr, legAngleVal: BinaryPart) => { return truncatedTo90 === 0 ? legAngleVal : binExp } -const getAngleLengthSign = (arg: Expr, legAngleVal: BinaryPart) => { - const ang = (arg.type === 'Literal' && Number(arg.value)) || 0 +const getAngleLengthSign = (arg: Literal, legAngleVal: BinaryPart) => { + const ang = Number(arg.value) || 0 const normalisedAngle = ((ang % 180) + 180) % 180 // between 0 and 180 return normalisedAngle > 90 ? createUnaryExpression(legAngleVal) : legAngleVal } function getClosesAngleDirection( - arg: Expr, + arg: Literal, refAngle: number, angleVal: BinaryPart ) { - const currentAng = (arg.type === 'Literal' && Number(arg.value)) || 0 + const currentAng = Number(arg.value) || 0 const angDiff = Math.abs(currentAng - refAngle) const normalisedAngle = ((angDiff % 360) + 360) % 360 // between 0 and 180 return normalisedAngle > 90 @@ -454,11 +454,8 @@ const setAngledIntersectLineForLines: CreateStdLibSketchCallExpr = ({ forceValueUsedInTransform, rawArgs: args, }) => { - const valueUsedInTransform = roundOff( - args[1].expr.type === 'Literal' ? Number(args[1].expr.value) : 0, - 2 - ) - const angle = args[0].expr.type === 'Literal' ? Number(args[0].expr.value) : 0 + const valueUsedInTransform = roundOff(Number(args[1].expr.value) || 0, 2) + const angle = Number(args[0].expr.value) || 0 const varNamMap: { [key: number]: string } = { 0: 'ZERO', 90: 'QUARTER_TURN', @@ -485,10 +482,7 @@ const setAngledIntersectForAngledLines: CreateStdLibSketchCallExpr = ({ inputs, rawArgs: args, }) => { - const valueUsedInTransform = roundOff( - args[1].expr.type === 'Literal' ? Number(args[1].expr.value) : 0, - 2 - ) + const valueUsedInTransform = roundOff(Number(args[1].expr.value) || 0, 2) return intersectCallWrapper({ fnName: 'angledLineThatIntersects', angleVal: inputs[0].expr, @@ -513,10 +507,7 @@ const setAngleBetweenCreateNode = ? getAngle(referencedSegment?.from, referencedSegment?.to) : 0 let valueUsedInTransform = roundOff( - normaliseAngle( - (args[0].expr.type === 'Literal' ? Number(args[0].expr.value) : 0) - - refAngle - ) + normaliseAngle((Number(args[0].expr.value) || 0) - refAngle) ) let firstHalfValue = createSegAngle(referenceSegName) as BinaryPart if (Math.abs(valueUsedInTransform) > 90) { @@ -824,9 +815,7 @@ const transformMap: TransformMap = { tooltip: 'yLine', createNode: ({ inputs, tag, rawArgs: args }) => { const expr = inputs[1].expr - if ( - !(args[0].expr.type === 'Literal' && Number(args[0].expr.value) < 0) - ) + if (Number(args[0].expr.value) >= 0) return createCallWrapper('yLine', expr, tag) if (isExprBinaryPart(expr)) return createCallWrapper('yLine', createUnaryExpression(expr), tag) @@ -838,9 +827,7 @@ const transformMap: TransformMap = { tooltip: 'xLine', createNode: ({ inputs, tag, rawArgs: args }) => { const expr = inputs[1].expr - if ( - !(args[0].expr.type === 'Literal' && Number(args[0].expr.value) < 0) - ) + if (Number(args[0].expr.value) >= 0) return createCallWrapper('xLine', expr, tag) if (isExprBinaryPart(expr)) return createCallWrapper('xLine', createUnaryExpression(expr), tag) @@ -895,9 +882,7 @@ const transformMap: TransformMap = { tooltip: 'xLine', createNode: ({ inputs, tag, rawArgs: args }) => { const expr = inputs[1].expr - if ( - !(args[0].expr.type === 'Literal' && Number(args[0].expr.value) < 0) - ) + if (Number(args[0].expr.value) >= 0) return createCallWrapper('xLine', expr, tag) if (isExprBinaryPart(expr)) return createCallWrapper('xLine', createUnaryExpression(expr), tag) @@ -949,9 +934,7 @@ const transformMap: TransformMap = { tooltip: 'yLine', createNode: ({ inputs, tag, rawArgs: args }) => { const expr = inputs[1].expr - if ( - !(args[0].expr.type === 'Literal' && Number(args[0].expr.value) < 0) - ) + if (Number(args[0].expr.value) >= 0) return createCallWrapper('yLine', expr, tag) if (isExprBinaryPart(expr)) return createCallWrapper('yLine', createUnaryExpression(expr), tag) @@ -1806,8 +1789,8 @@ function createLastSeg(isX: boolean): CallExpression { ]) } -function getArgLiteralVal(arg: Expr): number { - return arg?.type === 'Literal' ? Number(arg.value) : 0 +function getArgLiteralVal(arg: Literal): number { + return Number(arg.value) || 0 } export type ConstraintLevel = 'free' | 'partial' | 'full' diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index e2952befb2..81751f41c0 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -63,9 +63,7 @@ export type SegmentInputs = StraightSegmentInput | ArcSegmentInput */ interface addCall extends ModifyAstBase { segmentInput: SegmentInputs - replaceExistingCallback?: ( - rawArgs: RawArgs - ) => ReturnType + replaceExistingCallback?: (rawArgs: RawArgs) => CreatedSketchExprResult referencedSegment?: Path spliceBetween?: boolean } @@ -170,17 +168,31 @@ export type SimplifiedArgDetails = index: 0 | 1 } +/** + * Represents the result of creating a sketch expression (line, tangentialArcTo, angledLine, circle, etc.). + * + * @property {Expr} callExp - This is the main result; recasting the expression should give the user the new function call. + * @property {number} [valueUsedInTransform] - Aside from `callExp`, we also return the number used in the transform, which is useful for constraints. + * For example, when adding a "horizontal distance" constraint, we don't want the segments to move, just constrain them in place. + * So the second segment will probably be something like `lineTo([segEndX($firstSegTag) + someLiteral, 123], %)` where `someLiteral` is + * the value of the current horizontal distance, that we calculate to constrain the second segment without it moving. + * We can run the ast-mod to get this constraint `valueUsedInTransform` without applying the mod so that we can surface this to the user in a modal. + * We show them the modal where they can specify the distance they want to constrain to. + * We pre-fill this with the current value `valueUsedInTransform`, which they can accept or change, and we'll use that to apply the final ast-mod. + */ +export interface CreatedSketchExprResult { + callExp: Expr + valueUsedInTransform?: number +} + export type CreateStdLibSketchCallExpr = (args: { inputs: InputArgs + rawArgs: RawArgs referenceSegName: string tag?: Expr forceValueUsedInTransform?: Expr - rawArgs: InputArgs referencedSegment?: Path -}) => { - callExp: Expr - valueUsedInTransform?: number -} +}) => CreatedSketchExprResult export type TransformInfo = { tooltip: ToolTip From 9fafc90c573fe6a303fed204e2e0775c9d9c0fde Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Tue, 10 Sep 2024 13:23:21 +1000 Subject: [PATCH 39/41] remove surplus | 'radius's --- e2e/playwright/testing-segment-overlays.spec.ts | 2 -- src/clientSideScene/ClientSideSceneComp.tsx | 11 ++++------- src/lang/std/sketchcombos.ts | 1 + src/lang/std/stdTypes.ts | 10 +++++----- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/e2e/playwright/testing-segment-overlays.spec.ts b/e2e/playwright/testing-segment-overlays.spec.ts index a3faa5b35d..f7cc286eea 100644 --- a/e2e/playwright/testing-segment-overlays.spec.ts +++ b/e2e/playwright/testing-segment-overlays.spec.ts @@ -40,7 +40,6 @@ test.describe('Testing segment overlays', () => { | 'horizontal' | 'vertical' | 'tangentialWithPrevious' - | 'radius' | LineInputsType expectBeforeUnconstrained: string expectAfterUnconstrained: string @@ -122,7 +121,6 @@ test.describe('Testing segment overlays', () => { | 'horizontal' | 'vertical' | 'tangentialWithPrevious' - | 'radius' | LineInputsType expectBeforeUnconstrained: string expectAfterUnconstrained: string diff --git a/src/clientSideScene/ClientSideSceneComp.tsx b/src/clientSideScene/ClientSideSceneComp.tsx index f0311313d3..c1ddec1e3e 100644 --- a/src/clientSideScene/ClientSideSceneComp.tsx +++ b/src/clientSideScene/ClientSideSceneComp.tsx @@ -34,7 +34,6 @@ import { CustomIcon, CustomIconName } from 'components/CustomIcon' import { ConstrainInfo } from 'lang/std/stdTypes' import { getConstraintInfo } from 'lang/std/sketch' import { Dialog, Popover, Transition } from '@headlessui/react' -import { LineInputsType } from 'lang/std/sketchcombos' import toast from 'react-hot-toast' import { InstanceProps, create } from 'react-modal-promise' import { executeAst } from 'lang/langHelpers' @@ -547,12 +546,10 @@ const ConstraintSymbol = ({ iconName: 'dimension', }, } - const varName = - _type in varNameMap ? varNameMap[_type as LineInputsType].varName : 'var' - const name: CustomIconName = varNameMap[_type as LineInputsType].iconName - const displayName = varNameMap[_type as LineInputsType]?.displayName - const implicitDesc = - varNameMap[_type as LineInputsType]?.implicitConstraintDesc + const varName = _type in varNameMap ? varNameMap[_type].varName : 'var' + const name: CustomIconName = varNameMap[_type].iconName + const displayName = varNameMap[_type]?.displayName + const implicitDesc = varNameMap[_type]?.implicitConstraintDesc const _node = useMemo( () => getNodeFromPath(kclManager.ast, pathToNode), diff --git a/src/lang/std/sketchcombos.ts b/src/lang/std/sketchcombos.ts index cbac3bf03c..932256be5b 100644 --- a/src/lang/std/sketchcombos.ts +++ b/src/lang/std/sketchcombos.ts @@ -59,6 +59,7 @@ export type LineInputsType = | 'length' | 'intersectionOffset' | 'intersectionTag' + | 'radius' export type ConstraintType = | 'equalLength' diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index 81751f41c0..8f57ac18d5 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -82,19 +82,19 @@ export type VarValueKeys = | 'center' export interface SingleValueInput { type: 'singleValue' - argType: LineInputsType | 'radius' + argType: LineInputsType expr: T } export interface ArrayItemInput { type: 'arrayItem' index: 0 | 1 - argType: LineInputsType | 'radius' + argType: LineInputsType expr: T } export interface ObjectPropertyInput { type: 'objectProperty' key: VarValueKeys - argType: LineInputsType | 'radius' + argType: LineInputsType expr: T } @@ -102,14 +102,14 @@ interface ArrayOrObjItemInput { type: 'arrayOrObjItem' key: VarValueKeys index: 0 | 1 - argType: LineInputsType | 'radius' + argType: LineInputsType expr: T } interface ArrayInObject { type: 'arrayInObject' key: VarValueKeys - argType: LineInputsType | 'radius' + argType: LineInputsType index: 0 | 1 expr: T } From 93d3df4877f4d6c6a7d872242e1c8390657225d5 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Tue, 10 Sep 2024 17:23:11 +1000 Subject: [PATCH 40/41] more renaming --- src/clientSideScene/sceneEntities.ts | 13 +++++-------- src/lang/modifyAst.test.ts | 6 +++--- src/lang/modifyAst.ts | 4 ++-- src/lang/std/stdTypes.ts | 18 +++++++++--------- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 12e8c0c0ba..378ca1e679 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -643,7 +643,6 @@ export class SceneEntities { segmentName: 'line' | 'tangentialArcTo' = 'line', shouldTearDown = true ) => { - // try { const _ast = structuredClone(kclManager.ast) const _node1 = getNodeFromPath( @@ -668,8 +667,8 @@ export class SceneEntities { programMemory: kclManager.programMemory, input: { type: 'straight-segment', - to: [lastSeg.to[0], lastSeg.to[1]], - from: [lastSeg.to[0], lastSeg.to[1]], + to: lastSeg.to, + from: lastSeg.to, }, fnName: segmentName, pathToNode: sketchPathToNode, @@ -744,7 +743,7 @@ export class SceneEntities { input: { type: 'straight-segment', to: [intersection2d.x, intersection2d.y], - from: [lastSegment.to[0], lastSegment.to[1]], + from: lastSegment.to, }, fnName: lastSegment.type === 'TangentialArcTo' @@ -1165,7 +1164,7 @@ export class SceneEntities { input: { type: 'straight-segment', to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y], - from: [prevSegment.from[0], prevSegment.from[1]], + from: prevSegment.from, }, // TODO assuming it's always a straight segments being added // as this is easiest, and we'll need to add "tabbing" behavior @@ -1326,7 +1325,6 @@ export class SceneEntities { (!subGroup || subGroup?.name === ARROWHEAD) ) { // is dragging the radius handle - modded = changeSketchArguments( modifiedAst, kclManager.programMemory, @@ -1715,7 +1713,6 @@ export class SceneEntities { scale, }) } - const angle = 0 return () => sceneInfra.updateOverlayDetails({ arrowGroup, @@ -1723,7 +1720,7 @@ export class SceneEntities { isHandlesVisible, from: to, to: [center[0], center[1]], - angle, + angle: Math.PI / 4, }) } throttledUpdateDashedArcGeo = throttle( diff --git a/src/lang/modifyAst.test.ts b/src/lang/modifyAst.test.ts index 4fed48ba4f..1e35255b72 100644 --- a/src/lang/modifyAst.test.ts +++ b/src/lang/modifyAst.test.ts @@ -20,7 +20,7 @@ import { import { enginelessExecutor } from '../lib/testHelpers' import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst' import { err } from 'lib/trap' -import { SimplifiedArgDetails, VarValueKeys } from './std/stdTypes' +import { SimplifiedArgDetails, InputArgKeys } from './std/stdTypes' beforeAll(async () => { await initPromise @@ -648,7 +648,7 @@ describe('Testing removeSingleConstraintInfo', () => { } else if (key === 'objectProperty' && typeof value === 'string') { argPosition = { type: 'objectProperty', - key: value as VarValueKeys, + key: value as InputArgKeys, } } else if (key === '') { argPosition = { @@ -701,7 +701,7 @@ describe('Testing removeSingleConstraintInfo', () => { } else if (key === 'objectProperty' && typeof value === 'string') { argPosition = { type: 'objectProperty', - key: value as VarValueKeys, + key: value as InputArgKeys, } } else { throw new Error('argPosition is undefined') diff --git a/src/lang/modifyAst.ts b/src/lang/modifyAst.ts index bc32a228b7..0dda319d34 100644 --- a/src/lang/modifyAst.ts +++ b/src/lang/modifyAst.ts @@ -831,7 +831,7 @@ export function deleteSegmentFromPipeExpression( export function removeSingleConstraintInfo( pathToCallExp: PathToNode, - varValue: SimplifiedArgDetails, + argDetails: SimplifiedArgDetails, ast: Program, programMemory: ProgramMemory ): @@ -842,7 +842,7 @@ export function removeSingleConstraintInfo( | false { const transform = removeSingleConstraint({ pathToCallExp, - inputDetails: varValue, + inputDetails: argDetails, ast, }) if (!transform) return false diff --git a/src/lang/std/stdTypes.ts b/src/lang/std/stdTypes.ts index 8f57ac18d5..1333728014 100644 --- a/src/lang/std/stdTypes.ts +++ b/src/lang/std/stdTypes.ts @@ -72,7 +72,7 @@ interface updateArgs extends ModifyAstBase { input: SegmentInputs } -export type VarValueKeys = +export type InputArgKeys = | 'angle' | 'offset' | 'length' @@ -93,14 +93,14 @@ export interface ArrayItemInput { } export interface ObjectPropertyInput { type: 'objectProperty' - key: VarValueKeys + key: InputArgKeys argType: LineInputsType expr: T } interface ArrayOrObjItemInput { type: 'arrayOrObjItem' - key: VarValueKeys + key: InputArgKeys index: 0 | 1 argType: LineInputsType expr: T @@ -108,13 +108,13 @@ interface ArrayOrObjItemInput { interface ArrayInObject { type: 'arrayInObject' - key: VarValueKeys + key: InputArgKeys argType: LineInputsType index: 0 | 1 expr: T } -type _VarValue = +type _InputArg = | SingleValueInput | ArrayItemInput | ObjectPropertyInput @@ -131,7 +131,7 @@ type _VarValue = * Which is why a union type is used that can be type narrowed using the {@link RawArg.type} property * {@link RawArg.expr} is common to all of these types */ -export type InputArg = _VarValue +export type InputArg = _InputArg /** * {@link RawArg.expr} is the literal equivalent of whatever current expression is @@ -139,7 +139,7 @@ export type InputArg = _VarValue * but of course works for expressions like myVar + someFn() etc too * This is useful in cases where we want to "un-constrain" inputs to segments */ -type RawArg = _VarValue +type RawArg = _InputArg export type InputArgs = Array @@ -161,10 +161,10 @@ export type SimplifiedArgDetails = type: 'singleValue' } | { type: 'arrayItem'; index: 0 | 1 } - | { type: 'objectProperty'; key: VarValueKeys } + | { type: 'objectProperty'; key: InputArgKeys } | { type: 'arrayInObject' - key: VarValueKeys + key: InputArgKeys index: 0 | 1 } From fa580d4035f90dff951fa6bc7da58c704debab6a Mon Sep 17 00:00:00 2001 From: Kurt Hutten Irev-Dev Date: Tue, 10 Sep 2024 17:30:21 +1000 Subject: [PATCH 41/41] clean up by grouping segment labels --- src/clientSideScene/sceneEntities.ts | 48 +++++++++------------- src/components/ModelingMachineProvider.tsx | 19 +++------ src/lib/selections.ts | 22 ++-------- 3 files changed, 29 insertions(+), 60 deletions(-) diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index 378ca1e679..e73fe8c5be 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -129,6 +129,15 @@ export const CIRCLE_CENTER_HANDLE = 'circle-center-handle' export const SEGMENT_WIDTH_PX = 1.6 export const HIDE_SEGMENT_LENGTH = 75 // in pixels export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels +export const SEGMENT_BODIES = [ + STRAIGHT_SEGMENT, + TANGENTIAL_ARC_TO_SEGMENT, + CIRCLE_SEGMENT, +] +export const SEGMENT_BODIES_PLUS_PROFILE_START = [ + ...SEGMENT_BODIES, + PROFILE_START, +] type Vec3Array = [number, number, number] @@ -1264,12 +1273,7 @@ export class SceneEntities { ? new Vector2(profileStart.position.x, profileStart.position.y) : _intersection2d - const group = getParentGroup(object, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - PROFILE_START, - CIRCLE_SEGMENT, - ]) + const group = getParentGroup(object, SEGMENT_BODIES_PLUS_PROFILE_START) const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE]) if (!group) return const pathToNode: PathToNode = structuredClone(group.userData.pathToNode) @@ -1917,12 +1921,10 @@ export class SceneEntities { mat.color.set(obj.userData.baseColor) mat.color.offsetHSL(0, 0, 0.5) } - const parent = getParentGroup(selected, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - CIRCLE_SEGMENT, - PROFILE_START, - ]) + const parent = getParentGroup( + selected, + SEGMENT_BODIES_PLUS_PROFILE_START + ) if (parent?.userData?.pathToNode) { const updatedAst = parse(recast(kclManager.ast)) if (trap(updatedAst)) return @@ -1983,12 +1985,10 @@ export class SceneEntities { }, onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => { editorManager.setHighlightRange([[0, 0]]) - const parent = getParentGroup(selected, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - CIRCLE_SEGMENT, - PROFILE_START, - ]) + const parent = getParentGroup( + selected, + SEGMENT_BODIES_PLUS_PROFILE_START + ) if (parent) { const orthoFactor = orthoScale(sceneInfra.camControls.camera) @@ -2184,11 +2184,7 @@ function prepareTruncatedMemoryAndAst( export function getParentGroup( object: any, - stopAt: string[] = [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - CIRCLE_SEGMENT, - ] + stopAt: string[] = SEGMENT_BODIES ): Group | null { if (stopAt.includes(object?.userData?.type)) { return object @@ -2235,11 +2231,7 @@ function colorSegment(object: any, color: number) { }) return } - const straightSegmentBody = getParentGroup(object, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - CIRCLE_SEGMENT, - ]) + const straightSegmentBody = getParentGroup(object, SEGMENT_BODIES) if (straightSegmentBody) { straightSegmentBody.traverse((child) => { if (child instanceof Mesh && !child.userData.ignoreColorChange) { diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 71c3dbd434..d2ebadfa49 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -50,9 +50,7 @@ import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance' import useStateMachineCommands from 'hooks/useStateMachineCommands' import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig' import { - CIRCLE_SEGMENT, - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, + SEGMENT_BODIES, getParentGroup, getSketchOrientationDetails, } from 'clientSideScene/sceneEntities' @@ -169,11 +167,7 @@ export const ModelingMachineProvider = ({ if (event.type !== 'Set mouse state') return {} const nextSegmentHoverMap = () => { if (event.data.type === 'isHovering') { - const parent = getParentGroup(event.data.on, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - CIRCLE_SEGMENT, - ]) + const parent = getParentGroup(event.data.on, SEGMENT_BODIES) const pathToNode = parent?.userData?.pathToNode const pathToNodeString = JSON.stringify(pathToNode) if (!parent || !pathToNode) return context.segmentHoverMap @@ -189,11 +183,10 @@ export const ModelingMachineProvider = ({ event.data.type === 'idle' && context.mouseState.type === 'isHovering' ) { - const mouseOnParent = getParentGroup(context.mouseState.on, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - CIRCLE_SEGMENT, - ]) + const mouseOnParent = getParentGroup( + context.mouseState.on, + SEGMENT_BODIES + ) if (!mouseOnParent || !mouseOnParent?.userData?.pathToNode) return context.segmentHoverMap const pathToNodeString = JSON.stringify( diff --git a/src/lib/selections.ts b/src/lib/selections.ts index a984d36253..d15a8a3ecc 100644 --- a/src/lib/selections.ts +++ b/src/lib/selections.ts @@ -20,11 +20,8 @@ import { } from 'lang/queryAst' import { CommandArgument } from './commandTypes' import { - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, getParentGroup, - PROFILE_START, - CIRCLE_SEGMENT, + SEGMENT_BODIES_PLUS_PROFILE_START, } from 'clientSideScene/sceneEntities' import { Mesh, Object3D, Object3DEventMap } from 'three' import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra' @@ -163,12 +160,7 @@ export async function getEventForSelectWithPoint({ export function getEventForSegmentSelection( obj: Object3D ): ModelingMachineEvent | null { - const group = getParentGroup(obj, [ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - CIRCLE_SEGMENT, - PROFILE_START, - ]) + const group = getParentGroup(obj, SEGMENT_BODIES_PLUS_PROFILE_START) const axisGroup = getParentGroup(obj, [AXIS_GROUP]) if (!group && !axisGroup) return null if (axisGroup?.userData.type === AXIS_GROUP) { @@ -305,15 +297,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) { const updated = kclManager.ast Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => { - if ( - ![ - STRAIGHT_SEGMENT, - TANGENTIAL_ARC_TO_SEGMENT, - PROFILE_START, - CIRCLE_SEGMENT, - ].includes(segmentGroup?.name) - ) - return + if (!SEGMENT_BODIES_PLUS_PROFILE_START.includes(segmentGroup?.name)) return const nodeMeta = getNodeFromPath( updated, segmentGroup.userData.pathToNode,