Browse Source

adding extra verification in sketch parsing

Gilbert Brault 5 years ago
parent
commit
95f559c5a0
2 changed files with 638 additions and 90 deletions
  1. 614 90
      doc/design/pyparsing.ipynb
  2. 24 0
      pysketcher/shapes.py

+ 614 - 90
doc/design/pyparsing.ipynb

@@ -2,7 +2,7 @@
  "cells": [
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -18,7 +18,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -41,7 +41,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -50,7 +50,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -60,7 +60,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -74,7 +74,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -83,7 +83,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -150,7 +150,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -184,7 +184,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -219,7 +219,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -241,63 +241,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "9 = 9 ['9'] => ['9']\n",
-      "-9 = -9 ['-', '9'] => ['9', 'unary -']\n",
-      "--9 = 9 ['-', '-', '9'] => ['9', 'unary -', 'unary -']\n",
-      "-E = -2.718281828459045 ['-', 'E'] => ['E', 'unary -']\n",
-      "9 + 3 + 6 = 18 ['9', '+', '3', '+', '6'] => ['9', '3', '+', '6', '+']\n",
-      "9 + 3 / 11 = 9.272727272727273 ['9', '+', '3', '/', '11'] => ['9', '3', '11', '/', '+']\n",
-      "(9 + 3) = 12 [['9', '+', '3']] => ['9', '3', '+']\n",
-      "(9+3) / 11 = 1.0909090909090908 [['9', '+', '3'], '/', '11'] => ['9', '3', '+', '11', '/']\n",
-      "9 - 12 - 6 = -9 ['9', '-', '12', '-', '6'] => ['9', '12', '-', '6', '-']\n",
-      "9 - (12 - 6) = 3 ['9', '-', ['12', '-', '6']] => ['9', '12', '6', '-', '-']\n",
-      "2*3.14159 = 6.28318 ['2', '*', '3.14159'] => ['2', '3.14159', '*']\n",
-      "3.1415926535*3.1415926535 / 10 = 0.9869604400525172 ['3.1415926535', '*', '3.1415926535', '/', '10'] => ['3.1415926535', '3.1415926535', '*', '10', '/']\n",
-      "PI * PI / 10 = 0.9869604401089358 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']\n",
-      "PI*PI/10 = 0.9869604401089358 ['PI', '*', 'PI', '/', '10'] => ['PI', 'PI', '*', '10', '/']\n",
-      "PI^2 = 9.869604401089358 ['PI', '^', '2'] => ['PI', '2', '^']\n",
-      "round(PI^2) = 10 [('round', 1), [['PI', '^', '2']]] => ['PI', '2', '^', ('round', 1)]\n",
-      "6.02E23 * 8.048 = 4.844896e+24 ['6.02E23', '*', '8.048'] => ['6.02E23', '8.048', '*']\n",
-      "e / 3 = 0.9060939428196817 ['E', '/', '3'] => ['E', '3', '/']\n",
-      "sin(PI/2) = 1.0 [('sin', 1), [['PI', '/', '2']]] => ['PI', '2', '/', ('sin', 1)]\n",
-      "10+sin(PI/4)^2 = 10.5 ['10', '+', ('sin', 1), [['PI', '/', '4']], '^', '2'] => ['10', 'PI', '4', '/', ('sin', 1), '2', '^', '+']\n",
-      "trunc(E) = 2 [('trunc', 1), [['E']]] => ['E', ('trunc', 1)]\n",
-      "trunc(-E) = -2 [('trunc', 1), [['-', 'E']]] => ['E', 'unary -', ('trunc', 1)]\n",
-      "round(E) = 3 [('round', 1), [['E']]] => ['E', ('round', 1)]\n",
-      "round(-E) = -3 [('round', 1), [['-', 'E']]] => ['E', 'unary -', ('round', 1)]\n",
-      "E^PI = 23.140692632779263 ['E', '^', 'PI'] => ['E', 'PI', '^']\n",
-      "exp(0) = 1.0 [('exp', 1), [['0']]] => ['0', ('exp', 1)]\n",
-      "exp(1) = 2.718281828459045 [('exp', 1), [['1']]] => ['1', ('exp', 1)]\n",
-      "2^3^2 = 512 ['2', '^', '3', '^', '2'] => ['2', '3', '2', '^', '^']\n",
-      "(2^3)^2 = 64 [['2', '^', '3'], '^', '2'] => ['2', '3', '^', '2', '^']\n",
-      "2^3+2 = 10 ['2', '^', '3', '+', '2'] => ['2', '3', '^', '2', '+']\n",
-      "2^3+5 = 13 ['2', '^', '3', '+', '5'] => ['2', '3', '^', '5', '+']\n",
-      "2^9 = 512 ['2', '^', '9'] => ['2', '9', '^']\n",
-      "sgn(-2) = -1 [('sgn', 1), [['-', '2']]] => ['2', 'unary -', ('sgn', 1)]\n",
-      "sgn(0) = 0 [('sgn', 1), [['0']]] => ['0', ('sgn', 1)]\n",
-      "sgn(0.1) = 1 [('sgn', 1), [['0.1']]] => ['0.1', ('sgn', 1)]\n",
-      "foo(0.1) failed eval: invalid identifier 'foo' ['0.1', ('foo', 1)]\n",
-      "round(E, 3) = 2.718 [('round', 2), [['E'], ['3']]] => ['E', '3', ('round', 2)]\n",
-      "round(PI^2, 3) = 9.87 [('round', 2), [['PI', '^', '2'], ['3']]] => ['PI', '2', '^', '3', ('round', 2)]\n",
-      "sgn(cos(PI/4)) = 1 [('sgn', 1), [[('cos', 1), [['PI', '/', '4']]]]] => ['PI', '4', '/', ('cos', 1), ('sgn', 1)]\n",
-      "sgn(cos(PI/2)) = 0 [('sgn', 1), [[('cos', 1), [['PI', '/', '2']]]]] => ['PI', '2', '/', ('cos', 1), ('sgn', 1)]\n",
-      "sgn(cos(PI*3/4)) = -1 [('sgn', 1), [[('cos', 1), [['PI', '*', '3', '/', '4']]]]] => ['PI', '3', '*', '4', '/', ('cos', 1), ('sgn', 1)]\n",
-      "+(sgn(cos(PI/4))) = 1 ['+', [('sgn', 1), [[('cos', 1), [['PI', '/', '4']]]]]] => ['PI', '4', '/', ('cos', 1), ('sgn', 1)]\n",
-      "-(sgn(cos(PI/4))) = -1 ['-', [('sgn', 1), [[('cos', 1), [['PI', '/', '4']]]]]] => ['PI', '4', '/', ('cos', 1), ('sgn', 1), 'unary -']\n",
-      "hypot(3, 4) = 5.0 [('hypot', 2), [['3'], ['4']]] => ['3', '4', ('hypot', 2)]\n",
-      "multiply(3, 7) = 21 [('multiply', 2), [['3'], ['7']]] => ['3', '7', ('multiply', 2)]\n",
-      "all(1,1,1) = True [('all', 3), [['1'], ['1'], ['1']]] => ['1', '1', '1', ('all', 3)]\n",
-      "all(1,1,1,1,1,0) = False [('all', 6), [['1'], ['1'], ['1'], ['1'], ['1'], ['0']]] => ['1', '1', '1', '1', '1', '0', ('all', 6)]\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
     "test(\"9\", 9)\n",
     "test(\"-9\", -9)\n",
@@ -350,63 +296,641 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "test(\"Circle(0,0,5)\", \"center=(0,0),radius=5\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "test(\"Circle(0,1,3)\", \"center=(0,0),radius=5\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "A=3\n",
+    "N=4"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "test(\"N*A/5\",12/5)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "body=\"\"\"\\\n",
+    "rectangle: \n",
+    "    formula: Rectangle(contact, rl, rL)\n",
+    "    style:\n",
+    "        linecolor: blue\n",
+    "        filled_curves: blue\n",
+    "    transform: [\"rotate(-theta, contact)\",\n",
+    "                \"translate(-rl/2*tangent_vec)\"]\n",
+    "N: \n",
+    "    formula: Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')\n",
+    "    style:\n",
+    "        linecolor: black\n",
+    "wheel: \n",
+    "    formula: \"Composition({'outer': rectangle})\"   \n",
+    "    style:\n",
+    "        shadow: 1\n",
+    "mc:\n",
+    "    formula: Text(r'$c$', c)\n",
+    "body: \n",
+    "    formula: \"Composition({'wheel': wheel, 'N': N, 'mc': mc})\"\n",
+    "    style:\n",
+    "        linecolor: black\n",
+    "\"\"\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from ruamel.yaml import YAML"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "yaml = YAML()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "gwd = yaml.load(body)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Circle(0,0,5) = center=(0,0),radius=5 [('Circle', 3), [['0'], ['0'], ['5']]] => ['0', '0', '5', ('Circle', 3)]\n"
-     ]
+     "data": {
+      "text/plain": [
+       "8"
+      ]
+     },
+     "execution_count": 23,
+     "metadata": {},
+     "output_type": "execute_result"
     }
    ],
    "source": [
-    "test(\"Circle(0,0,5)\", \"center=(0,0),radius=5\")"
+    "gwd['N'].lc.line"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "BNF().parseString(\"Rectangle(contact, rl, rL)\", parseAll=True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "BNF().parseString(\"Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def sketchInterpreter(sketch, container):\n",
+    "    yaml = YAML()\n",
+    "    gwd = yaml.load(sketch)\n",
+    "    for _k in list(gwd.keys()):\n",
+    "        if _k == \"stop\":\n",
+    "            break\n",
+    "        _c = gwd[_k]\n",
+    "        _t = str(type(_c))\n",
+    "        if _k == \"libraries\":\n",
+    "            for l in _c:\n",
+    "                exec(l,container)\n",
+    "        #print(_k, _c, _t)\n",
+    "        if _t == \"<class 'ruamel.yaml.scalarfloat.ScalarFloat'>\" or \\\n",
+    "        _t == \"<class 'str'>\" or _t == \"<class 'int'>\":\n",
+    "            _formula = f\"{_k} = {_c}\".replace(\"<bslash>\",\"\\\\\") \n",
+    "            #print(_formula)\n",
+    "            exec(_formula,container)\n",
+    "        elif _t == \"<class 'ruamel.yaml.comments.CommentedMap'>\":\n",
+    "            #print(_c)\n",
+    "            _keys = list(_c.keys())\n",
+    "            #print(_keys)\n",
+    "            if 'formula' in _keys:\n",
+    "                _formula = f\"{_k} = {_c['formula']}\".replace(\"<bslash>\",\"\\\\\")\n",
+    "                #print(_formula)\n",
+    "                exec(_formula,container)\n",
+    "            if 'style' in _keys:\n",
+    "                for _style in _c[\"style\"]:\n",
+    "                    #  x_const.set_linestyle('dotted')\n",
+    "                    _param = _c[\"style\"][_style]\n",
+    "                    __t = str(type(_param))\n",
+    "                    #print(__t)\n",
+    "                    if __t == \"<class 'int'>\":\n",
+    "                        _style = f\"{_k}.set_{_style}({_param})\"\n",
+    "                    else:\n",
+    "                        _style = f\"{_k}.set_{_style}('{_param}')\"\n",
+    "                    #print(_style)\n",
+    "                    exec(_style,container)\n",
+    "            if 'transform' in _keys:\n",
+    "                #print(_c['transform'])\n",
+    "                if str(type(_c['transform'])) == \"<class 'str'>\":\n",
+    "                    _t = f\"{_k}.{_c['transform']}\"\n",
+    "                    #print(_t)\n",
+    "                    exec(_t,container)\n",
+    "                else:\n",
+    "                    for _transform in _c[\"transform\"]:\n",
+    "                    #  x_const.rotate(-theta, contact)\n",
+    "                        _t = f\"{_k}.{_transform}\"\n",
+    "                        #print(_t)\n",
+    "                        exec(_t,container)\n",
+    "            if \"action\" in _keys:\n",
+    "                _action = _c[\"action\"]\n",
+    "                #print(_action)\n",
+    "                exec(_action,container)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "parser"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import parser"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exp = parser.expr(\"Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exp.isexpr()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "parser"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pip install showast"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "!pip install wheel"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import showast"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Circle(0,1,3)!!! center=(0,1),radius=3 != center=(0,0),radius=5 [('Circle', 3), [['0'], ['1'], ['3']]] => ['0', '1', '3', ('Circle', 3)]\n"
-     ]
+     "data": {
+      "image/svg+xml": [
+       "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"851pt\" height=\"404pt\" viewBox=\"0.00 0.00 850.50 404.00\">\n",
+       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 400)\">\n",
+       "<title>%3</title>\n",
+       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-400 846.5,-400 846.5,4 -4,4\"/>\n",
+       "<!-- 0 -->\n",
+       "<g id=\"node1\" class=\"node\"><title>0</title>\n",
+       "<text text-anchor=\"start\" x=\"432\" y=\"-375.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">Expr</text>\n",
+       "</g>\n",
+       "<!-- 1 -->\n",
+       "<g id=\"node2\" class=\"node\"><title>1</title>\n",
+       "<text text-anchor=\"start\" x=\"432\" y=\"-303.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">Call</text>\n",
+       "</g>\n",
+       "<!-- 0&#45;&#45;1 -->\n",
+       "<g id=\"edge1\" class=\"edge\"><title>0--1</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M449,-359.697C449,-348.846 449,-334.917 449,-324.104\"/>\n",
+       "</g>\n",
+       "<!-- 2 -->\n",
+       "<g id=\"node3\" class=\"node\"><title>2</title>\n",
+       "<text text-anchor=\"start\" x=\"102\" y=\"-231.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">Name</text>\n",
+       "</g>\n",
+       "<!-- 1&#45;&#45;2 -->\n",
+       "<g id=\"edge2\" class=\"edge\"><title>1--2</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M448,-287C448,-287 225.028,-251.758 146.203,-239.3\"/>\n",
+       "</g>\n",
+       "<!-- 5 -->\n",
+       "<g id=\"node6\" class=\"node\"><title>5</title>\n",
+       "<text text-anchor=\"start\" x=\"279\" y=\"-231.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">BinOp</text>\n",
+       "</g>\n",
+       "<!-- 1&#45;&#45;5 -->\n",
+       "<g id=\"edge5\" class=\"edge\"><title>1--5</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M448,-287C448,-287 372.372,-260.428 329.24,-245.274\"/>\n",
+       "</g>\n",
+       "<!-- 18 -->\n",
+       "<g id=\"node19\" class=\"node\"><title>18</title>\n",
+       "<text text-anchor=\"start\" x=\"432\" y=\"-231.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">Name</text>\n",
+       "</g>\n",
+       "<!-- 1&#45;&#45;18 -->\n",
+       "<g id=\"edge18\" class=\"edge\"><title>1--18</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M448,-287C448,-287 448.381,-267.192 448.673,-252.027\"/>\n",
+       "</g>\n",
+       "<!-- 21 -->\n",
+       "<g id=\"node22\" class=\"node\"><title>21</title>\n",
+       "<text text-anchor=\"start\" x=\"522.5\" y=\"-231.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">Constant</text>\n",
+       "</g>\n",
+       "<!-- 1&#45;&#45;21 -->\n",
+       "<g id=\"edge21\" class=\"edge\"><title>1--21</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M448,-287C448,-287 489.139,-267.192 520.637,-252.027\"/>\n",
+       "</g>\n",
+       "<!-- 23 -->\n",
+       "<g id=\"node24\" class=\"node\"><title>23</title>\n",
+       "<text text-anchor=\"start\" x=\"663\" y=\"-231.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">keyword</text>\n",
+       "</g>\n",
+       "<!-- 1&#45;&#45;23 -->\n",
+       "<g id=\"edge23\" class=\"edge\"><title>1--23</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M448,-287C448,-287 586.683,-257.445 654.942,-242.898\"/>\n",
+       "</g>\n",
+       "<!-- 3 -->\n",
+       "<g id=\"node4\" class=\"node\"><title>3</title>\n",
+       "<text text-anchor=\"middle\" x=\"37\" y=\"-158.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">&quot;Force&quot;</text>\n",
+       "</g>\n",
+       "<!-- 2&#45;&#45;3 -->\n",
+       "<g id=\"edge3\" class=\"edge\"><title>2--3</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M111,-215C111,-215 82.8119,-195.192 61.2304,-180.027\"/>\n",
+       "</g>\n",
+       "<!-- 4 -->\n",
+       "<g id=\"node5\" class=\"node\"><title>4</title>\n",
+       "<text text-anchor=\"middle\" x=\"119\" y=\"-158.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">Load</text>\n",
+       "</g>\n",
+       "<!-- 2&#45;&#45;4 -->\n",
+       "<g id=\"edge4\" class=\"edge\"><title>2--4</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M111,-215C111,-215 114.047,-195.192 116.381,-180.027\"/>\n",
+       "</g>\n",
+       "<!-- 6 -->\n",
+       "<g id=\"node7\" class=\"node\"><title>6</title>\n",
+       "<text text-anchor=\"start\" x=\"174\" y=\"-159.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">Name</text>\n",
+       "</g>\n",
+       "<!-- 5&#45;&#45;6 -->\n",
+       "<g id=\"edge6\" class=\"edge\"><title>5--6</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M294,-215C294,-215 248.579,-192.069 218.062,-176.662\"/>\n",
+       "</g>\n",
+       "<!-- 9 -->\n",
+       "<g id=\"node10\" class=\"node\"><title>9</title>\n",
+       "<text text-anchor=\"middle\" x=\"263\" y=\"-158.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">Sub</text>\n",
+       "</g>\n",
+       "<!-- 5&#45;&#45;9 -->\n",
+       "<g id=\"edge9\" class=\"edge\"><title>5--9</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M294,-215C294,-215 282.191,-195.192 273.151,-180.027\"/>\n",
+       "</g>\n",
+       "<!-- 10 -->\n",
+       "<g id=\"node11\" class=\"node\"><title>10</title>\n",
+       "<text text-anchor=\"start\" x=\"316\" y=\"-159.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">BinOp</text>\n",
+       "</g>\n",
+       "<!-- 5&#45;&#45;10 -->\n",
+       "<g id=\"edge10\" class=\"edge\"><title>5--10</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M294,-215C294,-215 310.38,-195.192 322.92,-180.027\"/>\n",
+       "</g>\n",
+       "<!-- 7 -->\n",
+       "<g id=\"node8\" class=\"node\"><title>7</title>\n",
+       "<text text-anchor=\"middle\" x=\"101\" y=\"-86.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">&quot;contact&quot;</text>\n",
+       "</g>\n",
+       "<!-- 6&#45;&#45;7 -->\n",
+       "<g id=\"edge7\" class=\"edge\"><title>6--7</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M182,-143C182,-143 151.145,-123.192 127.522,-108.027\"/>\n",
+       "</g>\n",
+       "<!-- 8 -->\n",
+       "<g id=\"node9\" class=\"node\"><title>8</title>\n",
+       "<text text-anchor=\"middle\" x=\"192\" y=\"-86.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">Load</text>\n",
+       "</g>\n",
+       "<!-- 6&#45;&#45;8 -->\n",
+       "<g id=\"edge8\" class=\"edge\"><title>6--8</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M182,-143C182,-143 185.809,-123.192 188.726,-108.027\"/>\n",
+       "</g>\n",
+       "<!-- 11 -->\n",
+       "<g id=\"node12\" class=\"node\"><title>11</title>\n",
+       "<text text-anchor=\"start\" x=\"248\" y=\"-87.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">Name</text>\n",
+       "</g>\n",
+       "<!-- 10&#45;&#45;11 -->\n",
+       "<g id=\"edge11\" class=\"edge\"><title>10--11</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M337,-143C337,-143 309.574,-123.192 288.575,-108.027\"/>\n",
+       "</g>\n",
+       "<!-- 14 -->\n",
+       "<g id=\"node15\" class=\"node\"><title>14</title>\n",
+       "<text text-anchor=\"middle\" x=\"337\" y=\"-86.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">Mult</text>\n",
+       "</g>\n",
+       "<!-- 10&#45;&#45;14 -->\n",
+       "<g id=\"edge14\" class=\"edge\"><title>10--14</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M337,-143C337,-143 337,-123.192 337,-108.027\"/>\n",
+       "</g>\n",
+       "<!-- 15 -->\n",
+       "<g id=\"node16\" class=\"node\"><title>15</title>\n",
+       "<text text-anchor=\"start\" x=\"392\" y=\"-87.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">Name</text>\n",
+       "</g>\n",
+       "<!-- 10&#45;&#45;15 -->\n",
+       "<g id=\"edge15\" class=\"edge\"><title>10--15</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M337,-143C337,-143 364.426,-123.192 385.425,-108.027\"/>\n",
+       "</g>\n",
+       "<!-- 12 -->\n",
+       "<g id=\"node13\" class=\"node\"><title>12</title>\n",
+       "<text text-anchor=\"middle\" x=\"203\" y=\"-14.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">&quot;rl&quot;</text>\n",
+       "</g>\n",
+       "<!-- 11&#45;&#45;12 -->\n",
+       "<g id=\"edge12\" class=\"edge\"><title>11--12</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M260,-71C260,-71 238.288,-51.1921 221.664,-36.0267\"/>\n",
+       "</g>\n",
+       "<!-- 13 -->\n",
+       "<g id=\"node14\" class=\"node\"><title>13</title>\n",
+       "<text text-anchor=\"middle\" x=\"275\" y=\"-14.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">Load</text>\n",
+       "</g>\n",
+       "<!-- 11&#45;&#45;13 -->\n",
+       "<g id=\"edge13\" class=\"edge\"><title>11--13</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M260,-71C260,-71 265.714,-51.1921 270.088,-36.0267\"/>\n",
+       "</g>\n",
+       "<!-- 16 -->\n",
+       "<g id=\"node17\" class=\"node\"><title>16</title>\n",
+       "<text text-anchor=\"middle\" x=\"398\" y=\"-14.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">&quot;normal_vec&quot;</text>\n",
+       "</g>\n",
+       "<!-- 15&#45;&#45;16 -->\n",
+       "<g id=\"edge16\" class=\"edge\"><title>15--16</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M416,-71C416,-71 409.143,-51.1921 403.894,-36.0267\"/>\n",
+       "</g>\n",
+       "<!-- 17 -->\n",
+       "<g id=\"node18\" class=\"node\"><title>17</title>\n",
+       "<text text-anchor=\"middle\" x=\"501\" y=\"-14.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">Load</text>\n",
+       "</g>\n",
+       "<!-- 15&#45;&#45;17 -->\n",
+       "<g id=\"edge17\" class=\"edge\"><title>15--17</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M416,-71C416,-71 448.789,-50.9406 473.638,-35.7394\"/>\n",
+       "</g>\n",
+       "<!-- 19 -->\n",
+       "<g id=\"node20\" class=\"node\"><title>19</title>\n",
+       "<text text-anchor=\"middle\" x=\"430\" y=\"-158.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">&quot;contact&quot;</text>\n",
+       "</g>\n",
+       "<!-- 18&#45;&#45;19 -->\n",
+       "<g id=\"edge19\" class=\"edge\"><title>18--19</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M454,-215C454,-215 444.858,-195.192 437.858,-180.027\"/>\n",
+       "</g>\n",
+       "<!-- 20 -->\n",
+       "<g id=\"node21\" class=\"node\"><title>20</title>\n",
+       "<text text-anchor=\"middle\" x=\"521\" y=\"-158.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">Load</text>\n",
+       "</g>\n",
+       "<!-- 18&#45;&#45;20 -->\n",
+       "<g id=\"edge20\" class=\"edge\"><title>18--20</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M454,-215C454,-215 479.522,-195.192 499.062,-180.027\"/>\n",
+       "</g>\n",
+       "<!-- 22 -->\n",
+       "<g id=\"node23\" class=\"node\"><title>22</title>\n",
+       "<text text-anchor=\"middle\" x=\"595\" y=\"-158.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">&quot;$N$&quot;</text>\n",
+       "</g>\n",
+       "<!-- 21&#45;&#45;22 -->\n",
+       "<g id=\"edge22\" class=\"edge\"><title>21--22</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M565.64,-215.697C571.686,-204.846 579.446,-190.917 585.47,-180.104\"/>\n",
+       "</g>\n",
+       "<!-- 24 -->\n",
+       "<g id=\"node25\" class=\"node\"><title>24</title>\n",
+       "<text text-anchor=\"middle\" x=\"692\" y=\"-158.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">&quot;text_pos&quot;</text>\n",
+       "</g>\n",
+       "<!-- 23&#45;&#45;24 -->\n",
+       "<g id=\"edge24\" class=\"edge\"><title>23--24</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M702,-215C702,-215 698.191,-195.192 695.274,-180.027\"/>\n",
+       "</g>\n",
+       "<!-- 25 -->\n",
+       "<g id=\"node26\" class=\"node\"><title>25</title>\n",
+       "<text text-anchor=\"start\" x=\"767.5\" y=\"-159.3\" font-family=\"Courier,monospace\" font-weight=\"bold\" font-size=\"14.00\" fill=\"#004080\">Constant</text>\n",
+       "</g>\n",
+       "<!-- 23&#45;&#45;25 -->\n",
+       "<g id=\"edge25\" class=\"edge\"><title>23--25</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M702,-215C702,-215 739.711,-195.192 768.584,-180.027\"/>\n",
+       "</g>\n",
+       "<!-- 26 -->\n",
+       "<g id=\"node27\" class=\"node\"><title>26</title>\n",
+       "<text text-anchor=\"middle\" x=\"801\" y=\"-86.3\" font-family=\"Courier,monospace\" font-size=\"14.00\" fill=\"#008040\">&quot;start&quot;</text>\n",
+       "</g>\n",
+       "<!-- 25&#45;&#45;26 -->\n",
+       "<g id=\"edge26\" class=\"edge\"><title>25--26</title>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M801,-143.697C801,-132.846 801,-118.917 801,-108.104\"/>\n",
+       "</g>\n",
+       "</g>\n",
+       "</svg>"
+      ],
+      "text/plain": [
+       "<IPython.core.display.SVG object>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
     }
    ],
    "source": [
-    "test(\"Circle(0,1,3)\", \"center=(0,0),radius=5\")"
+    "%%showast\n",
+    "Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 16,
    "metadata": {},
    "outputs": [],
    "source": [
-    "A=3\n",
-    "N=4"
+    "import ast\n",
+    "\n",
+    "root = ast.parse(\"Force(contact - rl*normal_vec, contact, r'$N$', text_pos='start')\")\n",
+    "names = sorted({node.id for node in ast.walk(root) if isinstance(node, ast.Name)})\n",
+    "constants = sorted({node.value for node in ast.walk(root) if isinstance(node, ast.Constant)})"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "N*A/5 failed eval: invalid identifier 'A' ['N', 'A', '*', '5', '/']\n"
-     ]
+     "data": {
+      "text/plain": [
+       "['Force', 'contact', 'normal_vec', 'rl']"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
     }
    ],
    "source": [
-    "test(\"N*A/5\",12/5)"
+    "names"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['$N$', 'start']"
+      ]
+     },
+     "execution_count": 17,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "constants"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "head = \"\"\"\\\n",
+    "libraries: [\"from math import tan, radians, sin, cos\",\"from pysketcher import *\"]\n",
+    "fontsize: 18\n",
+    "g: 9.81              # constant gravity\n",
+    "theta: 30.0          # inclined plane angle\n",
+    "L: 10.0              # sketch sizing parameter\n",
+    "a: 1.0               #\n",
+    "xmin: 0.0            # sketech min Abscissa\n",
+    "ymin: -3.0           # sketech min Ordinate     \n",
+    "rl: 2.0              # rectangle width\n",
+    "rL: 1.0              # rectangle length\n",
+    "setframe:            # sketch setup\n",
+    "    action: \"drawing_tool.set_coordinate_system(xmin=xmin-L/5, xmax=xmin+1.5*L,ymin=ymin, ymax=ymin+1.5*L,instruction_file='tmp_mpl_friction.py')\"\n",
+    "setblackline:        # default frame values and actions\n",
+    "    action: \"drawing_tool.set_linecolor('black')\"\n",
+    "B: point(a+L,0)                      # wall right end\n",
+    "A: point(a,tan(radians(theta))*L)    # wall left end\n",
+    "normal_vec: point(sin(radians(theta)),cos(radians(theta)))     # Vector normal to wall\n",
+    "tangent_vec: point(cos(radians(theta)),-sin(radians(theta)))   # Vector tangent to wall\n",
+    "help_line: Line(A,B)                 # wall line\n",
+    "x: a + 3*L/10.\n",
+    "y: help_line(x=x)    \n",
+    "contact: point(x, y)    \n",
+    "c: contact + rL/2*normal_vec\n",
+    "\"\"\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "myfig={}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pysketcher import *"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "sketchParse(head,myfig)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 31,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "'g' in myfig"
    ]
   }
  ],

+ 24 - 0
pysketcher/shapes.py

@@ -16,6 +16,7 @@ from math import radians
 from io import BytesIO
 from ruamel.yaml import YAML
 from PIL import Image
+import ast
 
 from .MatplotlibDraw import MatplotlibDraw
 drawing_tool = MatplotlibDraw()
@@ -31,6 +32,14 @@ def sketch2PNG():
     img = Image.open(f)
     return img
 
+def sVe(key, expression, container):
+    root = ast.parse(expression)
+    names = {node.id for node in ast.walk(root) if isinstance(node, ast.Name)}
+    for name in names:
+        if name not in container:
+            return f"in key {key}: {name} in {expression} is not defined"
+    return 1
+
 def sketchParse(sketch, container):
     yaml = YAML()
     gwd = yaml.load(sketch)
@@ -47,6 +56,9 @@ def sketchParse(sketch, container):
         _t == "<class 'str'>" or _t == "<class 'int'>":
             _formula = f"{_k} = {_c}".replace("<bslash>","\\") 
             #print(_formula)
+            if type(_r = sVe(_k, _formula, container)) == str:
+                print(_r)
+                break
             exec(_formula,container)
         elif _t == "<class 'ruamel.yaml.comments.CommentedMap'>":
             #print(_c)
@@ -55,6 +67,9 @@ def sketchParse(sketch, container):
             if 'formula' in _keys:
                 _formula = f"{_k} = {_c['formula']}".replace("<bslash>","\\")
                 #print(_formula)
+                if type(_r = sVe(_k, _formula, container)) == str:
+                    print(_r)
+                    break
                 exec(_formula,container)
             if 'style' in _keys:
                 for _style in _c["style"]:
@@ -73,16 +88,25 @@ def sketchParse(sketch, container):
                 if str(type(_c['transform'])) == "<class 'str'>":
                     _t = f"{_k}.{_c['transform']}"
                     #print(_t)
+                    if type(_r = sVe(_k, _t, container)) == str:
+                        print(_r)
+                        break
                     exec(_t,container)
                 else:
                     for _transform in _c["transform"]:
                     #  x_const.rotate(-theta, contact)
                         _t = f"{_k}.{_transform}"
                         #print(_t)
+                        if type(_r = sVe(_k, _t, container)) == str:
+                            print(_r)
+                            break
                         exec(_t,container)
             if "action" in _keys:
                 _action = _c["action"]
                 #print(_action)
+                if type(_r = sVe(_k, _action, container)) == str:
+                    print(_r)
+                    break
                 exec(_action,container)
 
 def point(x, y, check_inside=False):