diff --git a/.gitignore b/.gitignore index a21f53b..9ffdf12 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ meili_data output.mp4 # log files output.log +# images +*.png diff --git a/config.yml b/config.yml index 6356135..a285918 100644 --- a/config.yml +++ b/config.yml @@ -159,7 +159,7 @@ led: size: 24 diameter: 63.5 angle: 0 - pos: [-65.936, 114.3] + pos: [-65.991, 114.3] - type: circle start: 336 size: 24 @@ -420,6 +420,116 @@ led: size: 70 length: 600 angle: 270 # down - pos: [300, 300] + pos: [375, 300] - \ No newline at end of file +global_position_offset: [0,0] # default coordinate spce below as center of arm at 0,0 - adjust if necessary +animation_time: 40 +position_map: + - index: 0 + pos: [-152.4, 263.965] + - index: 1 + pos: [-76.2, 263.965] + - index: 2 + pos: [0, 263.965] + - index: 3 + pos: [76.2, 263.965] + - index: 4 + pos: [152.4, 263.965] + - index: 5 + pos: [-190.5, 197.973] + - index: 6 + pos: [-114.3, 197.973] + - index: 7 + pos: [-38.1, 197.973] + - index: 8 + pos: [38.1, 197.973] + - index: 9 + pos: [114.3, 197.973] + - index: 10 + pos: [190.5, 197.973] + - index: 11 + pos: [-228.6, 131.982] + - index: 12 + pos: [-152.4, 131.982] + - index: 13 + pos: [-76.2, 131.982] + - index: 14 + pos: [0, 131.982] + - index: 15 + pos: [76.2, 131.982] + - index: 16 + pos: [152.4, 131.982] + - index: 17 + pos: [228.6, 131.982] + - index: 18 + pos: [-266.7, 65.991] + - index: 19 + pos: [-190.5, 65.991] + - index: 20 + pos: [-114.3, 65.991] + - index: 21 + pos: [114.3, 65.991] + - index: 22 + pos: [190.5, 65.991] + - index: 23 + pos: [266.7, 65.991] + - index: 24 + pos: [-304.8, 0] + - index: 25 + pos: [-228.6, 0] + - index: 26 + pos: [-152.4, 0] + - index: 27 + pos: [152.4, 0] + - index: 28 + pos: [228.6, 0] + - index: 29 + pos: [304.8, 0] + - index: 30 + pos: [-266.7, -65.991] + - index: 31 + pos: [-190.5, -65.991] + - index: 32 + pos: [-114.3, -65.991] + - index: 33 + pos: [114.3, -65.991] + - index: 34 + pos: [190.5, -65.991] + - index: 35 + pos: [266.7, -65.991] + - index: 36 + pos: [-228.6, -131.982] + - index: 37 + pos: [-152.4, -131.982] + - index: 38 + pos: [-76.2, -131.982] + - index: 39 + pos: [0, -131.982] + - index: 40 + pos: [76.2, -131.982] + - index: 41 + pos: [152.4, -131.982] + - index: 42 + pos: [228.6, -131.982] + - index: 43 + pos: [-190.5, -197.973] + - index: 44 + pos: [-114.3, -197.973] + - index: 45 + pos: [-38.1, -197.973] + - index: 46 + pos: [38.1, -197.973] + - index: 47 + pos: [114.3, -197.973] + - index: 48 + pos: [190.5, -197.973] + - index: 49 + pos: [-152.4, -263.965] + - index: 50 + pos: [-76.2, -263.965] + - index: 51 + pos: [0, -263.965] + - index: 52 + pos: [76.2, -263.965] + - index: 53 + pos: [152.4, -263.965] \ No newline at end of file diff --git a/get_specs.py b/get_specs.py index 7a17f04..17f3801 100755 --- a/get_specs.py +++ b/get_specs.py @@ -5,7 +5,7 @@ import sys import read_datasheet from alive_progress import alive_bar import requests -#import time +import time import json import subprocess from util import fprint @@ -27,29 +27,78 @@ def check_internet(url='https://belden.com', timeout=5): def query_search(partnum, source): - """token_url = "https://www.belden.com/coveo/rest/token?t=" + str(int(time.time())) - with requests.get(token_url) as r: - out = json.loads(r.content) - token = out["token"] - search_url = "https://www.belden.com/coveo/rest/search" - search_data ='{ "q": "' + str(partnum) + '", "sortCriteria": "relevancy", "numberOfResults": "250", "sortCriteria": "@catalogitemwebdisplaypriority ascending", "searchHub": "products-only-search", "pipeline": "Site Search", "maximumAge": "900000", "tab": "products-search", "locale": "en" }' - #"aq": "", "cq": "((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\\"Coveo_web_index - rg-nc-prod-sitecore-prod\\")) OR (@source==(\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\",\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\"))", "firstResult": "0", "categoryFacets": "[{\\"field\\":\\"@catalogitemcategories\\",\\"path\\":[],\\"injectionDepth\\":1000,\\"maximumNumberOfValues\\":6,\\"delimitingCharacter\\":\\"|\\"}]", "facetOptions": "{}", "groupBy": "" }' - #fprint(search_data) - fprint(json.loads(search_data)) - #search_data = '{ "q": "' + str(partnum) + '" }' - fprint(search_data) - headers = headers = { - 'Authorization': f'Bearer {token}', - 'Content-Type': 'application/json' - } - with requests.post(search_url, headers=headers, data=search_data) as r: - fprint(r.text)""" - - # TODO: Reimplement in python - # Bash script uses some crazy json formatting that I could not figure out - # Despite the fact that I wrote it - # So I'll just leave it, becuase it works. if source == "Belden": + token_url = "https://www.belden.com/coveo/rest/token?t=" + str(int(time.time())) + with requests.get(token_url) as r: + out = json.loads(r.content) + token = out["token"] + search_url = "https://www.belden.com/coveo/rest/search" + + # Ridiculous search parameters extracted from website. Do not touch + search_data = r"""{ "q": "{QUERY}", "sortCriteria": "relevancy", "numberOfResults": "250", "sortCriteria": "@catalogitemwebdisplaypriority ascending", "searchHub": "products-only-search", "pipeline": "Site Search", "maximumAge": "900000", "tab": "products-search", "locale": "en", "aq": "(NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B)) ((@syssource==\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\" @catalogitemprimarycategorypublished==true)) ((@catalogitemregionavailable=Global) (@z95xlanguage==en))", "cq": "((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\"Coveo_web_index - rg-nc-prod-sitecore-prod\")) OR (@source==(\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\",\"website_001002_Category_index-rg-nc-prod-sitecore-prod\"))", "firstResult": "0" }, "categoryFacets": "[{\"field\":\"@catalogitemcategories\",\"path\":[],\"injectionDepth\":1000,\"maximumNumberOfValues\":6,\"delimitingCharacter\":\"|\"}]", "facetOptions": "{}", "groupBy": " [{\"field\":\"@contenttype\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[\"Products\"],\"queryOverride\":\"{QUERY}\",\"advancedQueryOverride\":\"(NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B)) ((((((((@z95xpath=3324AF2D58F64C0FB725521052F679D2 @z95xid<>3324AF2D58F64C0FB725521052F679D2) ((@z95xpath=C292F3A37B3A4E6BAB345DF87ADDE516 @z95xid<>C292F3A37B3A4E6BAB345DF87ADDE516) @z95xtemplate==E4EFEB787BDC4B1A908EFC64D56CB2A4)) OR ((@z95xpath=723501A864754FEEB8AE377E4C710271 @z95xid<>723501A864754FEEB8AE377E4C710271) ((@z95xpath=600114EAB0E5407A84AAA9F0985B6575 @z95xid<>600114EAB0E5407A84AAA9F0985B6575) @z95xtemplate==2BE4FD6B3B2C49EBBD9E1F6C92238B05))) OR (@syssource==\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\" @catalogitemprimarycategorypublished==true)) OR ((@z95xpath=3324AF2D58F64C0FB725521052F679D2 @z95xid<>3324AF2D58F64C0FB725521052F679D2) @z95xpath<>C292F3A37B3A4E6BAB345DF87ADDE516)) OR @syssource==\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\") NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B))) ((@catalogitemregionavailable=Global) (@z95xlanguage==en) OR (@contenttype=(Blogs,Resources,Other)) (NOT @ez120xcludefromcoveo==1))\",\"constantQueryOverride\":\"((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\\"Coveo_web_index - rg-nc-prod-sitecore-prod\\")) OR (@source==(\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\",\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\"))\"},{\"field\":\"@catalogitembrand\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@catalogitemenvironment\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@catalogitemregionalavailability\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@prez45xtez120xt\",\"maximumNumberOfValues\":5,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@tags\",\"maximumNumberOfValues\":4,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetassettype\",\"maximumNumberOfValues\":3,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetbrand\",\"maximumNumberOfValues\":3,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetmarket\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetsolution\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetsearchcontentpagetype\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]}]" }""" + search_data = search_data.replace(r"{QUERY}", partnum) + #"aq": "", "cq": "((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\\"Coveo_web_index - rg-nc-prod-sitecore-prod\\")) OR (@source==(\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\",\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\"))", "firstResult": "0", "categoryFacets": "[{\\"field\\":\\"@catalogitemcategories\\",\\"path\\":[],\\"injectionDepth\\":1000,\\"maximumNumberOfValues\\":6,\\"delimitingCharacter\\":\\"|\\"}]", "facetOptions": "{}", "groupBy": "" }' + #fprint(search_data) + #fprint(json.loads(search_data)) + #search_data = '{ "q": "' + str(partnum) + '" }' + #fprint(search_data) + headers = headers = { + 'Authorization': f'Bearer {token}', + 'Content-Type': 'application/json' + } + try: + with requests.post(search_url, headers=headers, data=search_data) as r: + a = r.text + a = json.loads(a) + idx = -1 + name = "" + for partid in range(len(a["results"])): + name = a["results"][partid]["title"] + if name != partnum: + if name.find(partnum) >= 0: + idx = partid + break + elif partnum.find(name) >= 0: + idx = partid + break + + else: + idx = partid + break + + if idx < 0: + fprint("Could not find part in API: " + partnum) + return False + fprint("Search result found: result " + str(idx) + ", for ID " + name) + #urlname = a["results"][0]["raw"]["catalogitemurlname"] + img = a["results"][idx]["raw"]["catalogitemimageurl"] + img = img[0:img.index("?")] + uri = a["results"][idx]["raw"]["clickableuri"] + dsid = a["results"][idx]["raw"]["catalogitemdatasheetid"] + brand = a["results"][idx]["raw"]["catalogitembrand"] + desc = a["results"][idx]["raw"]["catalogitemlongdesc"] + shortdesc = a["results"][idx]["raw"]["catalogitemshortdesc"] + a = json.dumps(a["results"][idx], indent=2) + #print(a, urlname, img, uri, dsurl) + + out = dict() + out["url"] = "https://www.belden.com/products/" + uri + out["datasheet"] = "https://catalog.belden.com/techdata/EN/" + dsid + "_techdata.pdf" + out["brand"] = brand + out["name"] = shortdesc + out["description"] = desc + out["image"] = "https://www.belden.com" + img + out["partnum"] = name + #print(out) + return out + except: + print("falied to search with API. Falling back to datasheet lookup.") + return False + + + # Original bash script + # superceded by above + if source == "Belden_shell": command = ["./query-search.sh", partnum] result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if result.returncode != 0: # error @@ -66,27 +115,28 @@ def query_search(partnum, source): #print(data) try: if data["Count"] > 0: - print(data["Results"][0]["Url"]) - result = data["Results"][0] - if result["Url"].split("/")[-1] == partnum: - #print(partnum) - print(result["Html"]) - try: - imgidx = result["Html"].index(" 1): # Use query - search_result = query_search(partnum.replace(" ", ""), dstype) + search_result = query_search(partnum, dstype) # Try to use belden.com search if search_result is not False: # Download high resolution part image if available and needed + partnum = search_result["partnum"] + output_dir = "cables/" + partnum + path = output_dir + "/datasheet.pdf" + bartext = "Downloading files for part " + partnum + bar.text = bartext + if not os.path.exists(output_dir + "/found_part_hires"): if _download_image(search_result["image"], output_dir): fprint("Downloaded hi-res part image for " + partnum) @@ -245,17 +302,48 @@ def get_multi(partnums): # Failed to download with search or guess :( else: - fprint("Failed to download datasheet for part " + partnum) - bar.text = "Failed to download datasheet for part " + partnum - failed.append(partnum) - bar(skipped=True) - bar(skipped=True) + return False + return True # We already have a hi-res image and the datasheet - perfect! else: fprint("Using cached hi-res part image for " + partnum) __use_cached_datasheet(partnum, path, output_dir, dstype) + return True + for fullpartnum in partnums: + if fullpartnum[0:2] == "BL": # catalog.belden.com entry + partnum = fullpartnum[2:] + dstype = "Belden" + elif fullpartnum[0:2] == "AW": + partnum = fullpartnum[2:] + dstype = "Alphawire" + else: + dstype = "Belden" # guess + partnum = fullpartnum + if not run_search(partnum): + success = False + if len(partnum.split(" ")) > 1: + for name in partnum.split(" "): + fprint("Retrying with alternate name: " + name) + if(run_search(name)): + success = True + break + time.sleep(delay) + if not success: + namestripped = partnum.strip(" ") + fprint("Retrying with alternate name: " + namestripped) + if(run_search(namestripped)): + success = True + time.sleep(delay) + if not success: + fprint("Failed to download datasheet for part " + partnum) + bar.text = "Failed to download datasheet for part " + partnum + failed.append(partnum) + bar(skipped=True) + bar(skipped=True) + time.sleep(delay) + if len(failed) > 0: fprint("Failed to download:") for partnum in failed: @@ -268,22 +356,73 @@ def get_multi(partnums): if __name__ == "__main__": - partnums = ["BL7958A", "BL10GXS12", "BLRST 5L-RKT 5L-949", -"BL10GXS13", -"BL10GXW12", -"BL10GXW13", -"BL2412", -"BL2413", -"BLOSP6AU", -"BLFI4D024P9", -"BLFISD012R9", -"BLFDSD012A9", -"BLFSSL024NG", -"BLFISX006W0", -"BLFISX00103", -"BLC6D1100007" + # partnums = ["BLFISX012W0", "BL7958A", "BL10GXS12", "BLRST 5L-RKT 5L-949", + # "BL10GXS13", + # "BL10GXW12", + # "BL10GXW13", + # "BL2412", + # "BL2413", + # "BLOSP6AU", + # "BLFI4D024P9", + # "BLFISD012R9", + # "BLFDSD012A9", + # "BLFSSL024NG", + # "BLFISX006W0", + # "BLFISX00103", + # "BLC6D1100007" + # ] + partnums = [ + # Actual cables in Jukebox + "AW86104CY", + "AW3050", + "AW6714", + "AW1172C", + "AW2211/4", + + "BLTF-1LF-006-RS5N", + "BLTF-SD9-006-RI5N", + "BLTT-SLG-024-HTNN", + "BLFISX012W0", + "BLFI4X012W0", + "BLSPE101 006Q", + "BLSPE102 006Q", + "BL7922A 010Q", + "BL7958A 008Q", + "BLIOP6U 010Q", + "BL10GXW13 D15Q", + "BL10GXW53 D15Q", + "BL29501F 010Q", + "BL29512 010Q", + "BL3106A 010Q", + "BL9841 060Q", + "BL3105A 010Q", + "BL3092A 010Q", + "BL8760 060Q", + "BL6300UE 008Q", + "BL6300FE 009Q", + "BLRA500P 006Q", + + + # Some ones I picked, including some invalid ones + "BL10GXS12", + "BLRST 5L-RKT 5L-949", + "BL10GXS13", + "BL10GXW12", + "BL10GXW13", + "BL2412", + "BL2413", + "BLOSP6AU", + "BLFI4D024P9", + "BLFISD012R9", + "BLFDSD012A9", + "BLFSSL024NG", + "BLFISX006W0", + "BLFISX00103", + "BLC6D1100007" + ] - get_multi(partnums) - #query_search("3248", "Alphawire") + #query_search("86104CY", "Alphawire") + get_multi(partnums, 0.25) + #query_search("10GXS13", "Belden") diff --git a/install-deps.sh b/install-deps.sh index 65bc6cc..bb8df08 100755 --- a/install-deps.sh +++ b/install-deps.sh @@ -1,4 +1,5 @@ #!/bin/sh +# change this to #!/bin/bash for windows if ! [ -d "venv" ]; then ./venv-setup.sh diff --git a/inv_kin_testing.ipynb b/inv_kin_testing.ipynb new file mode 100644 index 0000000..4bd5917 --- /dev/null +++ b/inv_kin_testing.ipynb @@ -0,0 +1,359 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Inverse Kinematics Simulation" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from scipy.optimize import fsolve\n", + "import matplotlib.pyplot as plt\n", + "import math\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Polar coordinate functions" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def cartesian_to_polar(x, y):\n", + " r = np.sqrt(x**2 + y**2)\n", + " theta = np.arctan2(y, x)\n", + " return r, theta\n", + "\n", + "def polar_to_cartesian(r, theta):\n", + " x = r * np.cos(theta)\n", + " y = r * np.sin(theta)\n", + " return x, y" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate end-joint values from xyz position" + ] + }, + { + "cell_type": "code", + "execution_count": 157, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[45.0, 90.70951422006029, 110.71500355039022, -20.005489330329944]\n", + "[45.0, -90.70951422006029, 110.71500355039022, -110.00548933032994]\n" + ] + } + ], + "source": [ + "def normalize_degree(theta):\n", + " # Normalizes degree theta from -1.5pi to 1.5pi\n", + " multiplier = 1.5\n", + " normalized_theta = theta % (math.pi * multiplier)\n", + " \n", + " # Maintain the negative sign if the original angle is negative\n", + " if theta < 0:\n", + " normalized_theta -= math.pi * multiplier\n", + "\n", + " # Return angle\n", + " return normalized_theta\n", + "\n", + "def get_joints_from_xyz_rel(x, y, z, initial_guess = (math.pi/2, math.pi/2, 0), limbs=(.422864, .359041, .092124)):\n", + " # Get polar coordinates of x,y pair\n", + " r, theta = cartesian_to_polar(x, y)\n", + " \n", + " # Get length of each limb\n", + " l1, l2, l3 = limbs\n", + " \n", + " # Formulas to find out joint positions for (r, z)\n", + " def inv_kin_r_z(p):\n", + " a, b, c = p\n", + "\n", + " return (l1*math.cos(a) + l2*math.cos(a-b) + l3*math.cos(a-b-c) - r, # r\n", + " l1*math.sin(a) + l2*math.sin(a-b) - l3*math.sin(a-b-c) - z, # z\n", + " a-b-c) # wrist angle\n", + "\n", + "\n", + " # Normalize angles\n", + " base, shoulder, elbow, wrist = [normalize_degree(deg) for deg in [theta, *fsolve(inv_kin_r_z, initial_guess)]]\n", + "\n", + " # Return result\n", + " return base, shoulder, elbow, wrist\n", + "\n", + "def get_joints_from_xyz_abs(x, y, z):\n", + " joints = get_joints_from_xyz_rel(x, y, z)\n", + "\n", + " # Joint offsets\n", + " # Base, Shoulder, Elbow, Wrist\n", + " inverse = [1, -1, 1, 1]\n", + " offsets = [0, 0, 0, -math.pi/2]\n", + "\n", + " # Return adjusted joint positions\n", + " return [o+j*i for j, o, i in zip(joints, offsets, inverse)]\n", + "\n", + "print([math.degrees(deg) for deg in get_joints_from_xyz_rel(0.3, 0.3, 0.3)])\n", + "print([math.degrees(deg) for deg in get_joints_from_xyz_abs(0.3, 0.3, 0.3)])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simulate arm and joint angles" + ] + }, + { + "cell_type": "code", + "execution_count": 164, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Target position (x,y,z): 0.3 0.3 0.3\n", + "Angles (base, shoulder, elbow, wrist): [45.0, 90.7095, 110.715, -20.0055]\n", + "Robot Angles: [45.0, -90.7095, 110.715, -110.0055]\n", + "elbow (x,y): -0.005 0.423\n", + "wrist (x,y): 0.332 0.3\n", + "tool (x,y): 0.424 0.3\n" + ] + }, + { + "data": { + "image/png": "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ppqI1CmFORDUBAIaeLsSn/HPf/6TyZMnU1pq/6fcxo0b6dq1q5ershaPrKYRz9DIiIjUWINWCiLnKSwsZMaMGfTr1w+Atm3bMm3aNC9XZT0KIxai1TQiIrVrzpw5HDt2jJtuugmABx54gA8++IBNmzZ5uTJrURixEK2mERGpPWVlZcyYMYPk5GSaN7c/63z48OG0b9+e6dOne7k6a1EYsRDdphERqT02m40JEyaUuy0TEBDAa6+9xoABA7xYmfW4vJpGfJfCiIhI7bHZbDzxxBMXHO/Zsyc9e/b0QkXWpZERC1EYERERM1IYsRCFERERMSOFEQvRahoRETEjhREL0WoaERExI4URC9FtGhERMSOFEQtRGBERETNSGLEQhRERETEjhRELURgREREzUhixEK2mERERM1IYsRCtphERETNSGLEQ3aYREREzUhixEIURERExI4URC1EYERERM1IYsRBNYBURETNSGLEQjYyIiIgZKYxYiFbTiIiIGSmMWIhGRkRExIwURixEYURERMxIYcRCFEZERMSMFEYsRKtpRETEjBRGLEQTWEVExIwURixEt2lERMSMFEYsRGFERETMSGHEQhRGRETEjBRGLERhREREzEhhxEK0mkZERMxIYcRCtJpGRETMSGHEQnSbRkREzEhhxEIURkRExIwURixEYURERMxIYcRCFEZERMSMFEYsRKtpRETEjBRGLESraURExIwURixEt2lERMSMFEYsRGFERETMSGHEQpz9w1QYERERX6IwYjHOjI4ojIiIiC9RGLEYZ8KIVtOIiIgvURixGGdW1GhkREREfInCiMXoNo2IiJiNwojFKIyIiIjZKIxYjMKIiIiYjcKIxSiMiIiI2SiMWIxW04iIiNkojFiMVtOIiIjZKIxYjG7TiIiI2SiMWIzCiIiImI3CiMUojIiIiNkojFiMwoiIiJiNwojFaDWNiIiYjcKIxWg1jYiImI3CiMXoNo2IiJiNwojFKIyIiIjZKIxYjMKIiIiYjcKIxSiMiIiI2SiMWIxW04iIiNkojFiMVtOIiIjZKIxYjDMjI8a5l4iIiC9QGLEYZ8IIaHRERER8h8KIxSiMiIiI2SiMWIyzYUSTWEVExFcojFiMRkZERMRsFEYsxpnVNKAwIiIivqNaYWT27NnExMQQHBxMjx49WL9+faVt586dS69evWjcuDGNGzcmKSnpou2lZjQyIiIiZuNyGFmyZAkpKSmkpqayceNGunTpwsCBAzl06FCF7VevXs2oUaNYtWoVGRkZREdHM2DAAPbv31/j4uVCCiMiImI2LoeRp59+mgkTJpCcnEynTp2YM2cO9evXZ968eRW2f/PNN7nrrruIi4sjNjaWf/3rX5SVlZGenl7j4uVCCiMiImI2LoWR4uJiMjMzSUpK+uUEfn4kJSWRkZHh1DlOnz5NSUkJERERrlUqTtFqGhERMRtn5zsCcOTIEUpLS4mMjCx3PDIykp07dzp1jsmTJ9OiRYtygebXioqKKCoqcvxcUFDgSpl1mkZGRETEbDy6mmb69OksXryY999/n+Dg4ErbpaWlER4e7nhFR0d7sEpz02oaERExG5fCSNOmTfH39ycvL6/c8by8PKKioi762ZkzZzJ9+nQ+++wzOnfufNG2U6ZMIT8/3/Hau3evK2XWaRoZERERs3EpjAQGBhIfH19u8unPk1ETExMr/dyMGTN4/PHHWb58OQkJCVX2ExQURFhYWLmXOEdhREREzMalOSMAKSkpjBs3joSEBLp3786sWbMoLCwkOTkZgLFjx9KyZUvS0tIA+Mc//sHUqVNZtGgRMTEx5ObmAtCwYUMaNmxYi19FQGFERETMx+UwMnLkSA4fPszUqVPJzc0lLi6O5cuXOya15uTk4Of3y4DLSy+9RHFxMSNGjCh3ntTUVB599NGaVS8X0GoaERExG5fDCMCkSZOYNGlShe+tXr263M/Z2dnV6UKqSRNYRUTEbPRsGovRbRoRETEbhRGLURgRERGzURixGIURERExG4URi1EYERERs1EYsRitphEREbNRGLEYraYRERGzURixGN2mERERs1EYsRiFERERMRuFEYtRGBEREbNRGLEYhRERETEbhRGL0WoaERExG4URi9FqGhERMRuFEYvRbRoRETEbhRGLURgRERGzURixGIURERExG4URi1EYERERs1EYsRitphEREbNRGLEYraYRERGzURixGN2mERERs1EYsRiFERERMRuFEYtRGBEREbNRGLEYhRERETEbhRGL0WoaERExG4URi9FqGhERMRuFEYvRbRoREeuYPXs2MTExBAcH06NHD9avX3/R9kuXLiUhIYFGjRrRoEED4uLiWLBggYeqrT6FEYtRGBERsYYlS5aQkpJCamoqGzdu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E4URC1IYERERM1EYsSCFERERMROFEQtSGBERETNRGLEgraYREREzURixIK2mERERM1EYsSDdphERETNRGLEghRERETEThRELUhgREREzURixIIURERExE4URC9JqGhERMROFEQvSahoRETEThREL0m0aERExE4URC1IYERERM1EYsSCFERERMROFEQtSGBERETNRGLEgraYREREzURixIK2mERERM1EYsSDdphERETNRGLEghRERETEThRELUhgREREzURixIIURERExE4URC9JqGhERMROFEQvSahoRETEThREL0m0aERExE4URC1IYERERM1EYsSCFERERMROFEQtSGBERETNRGLEgraYREREzURixIBvO/cFqZERERHyBwohFOTM6ojAiIiK+QGHEohRGRETELBRGLEphREREzEJhxKIuGkZOnoT77iOzdWtCQkLo2bMnGzZscLxtGAZTp07lkksuISQkhKSkJL7//vuL9hcTE4PNZrvgNXHiREebV155hb59+xIWFobNZuPEiRMXnCcjI4O4uDhiYmJ49dVXXfzWIiJiRgojFnXRMHL77bByJa0XLGDLli0MGDCApKQk9u/fD8CMGTN47rnnmDNnDuvWraNBgwYMHDiQM2fOVHrKDRs2cPDgQcdr5cqVAPz+9793tDl9+jSDBg3ioYceqvQ848eP529/+xuLFi0iLS2NvXv3uvS9RUTEfJx9jImYTKV/sD/9BO+9Bx98QP3evWkHPProo3z00Ue89NJLPP7448yaNYtHHnmEoUOHAvDGG28QGRnJsmXLuOWWWyo8bbNmzcr9PH36dNq2bUufPn0cx+677z4AVq9eXWndhYWFXHXVVTRv3pzGjRtz8uRJJ7+xiIiYlUZGLKrSkZGzZ6G0FIKDy80ZCQkJYe3atWRlZZGbm0tSUpLjvfDwcHr06EFGRoZTfRcXF7Nw4UJuu+02bDabS3VPnTqVjh07Eh4eztVXX02nTp1c+ryIiJiPRkYsqtIwEhoKiYnw+OOc6diR0shI3nrrLTIyMmjXrh25ubkAREZGlvtYZGSk472qLFu2jBMnTvDHP/7R5brHjx/PLbfcQnFxMY0bN3b58yIiYj4aGbGoi84ZWbAADIPdLVsSFBTEc889x6hRo/Dzq51fh1dffZXrrruOFi1aVOvzDRo0UBAREalDFEYs6qJhpG1b+PJLYk6dYu/evaxfv56SkhIuu+wyoqKiAMjLyyv3kby8PMd7F/Pjjz/y+eefc/vtt9egehERqUsURizKmX1GjAYNuOSSSzh+/DgrVqxg6NChtGnThqioKNLT0x3tCgoKWLduHYmJiVWec/78+TRv3pzBgwfXoHoREalLFEYs6qKTgVasgOXLKcrKYuXKlfTr14/Y2FiSk5Ox2Wzcd999PPHEE3z44Yds2bKFsWPH0qJFC4YNG+Y4xbXXXssLL7xQ7rRlZWXMnz+fcePGERBwYQW5ubls3ryZ3bt3A7BlyxY2b97MsWPHauEbizd999139OrVi+DgYKKjo5kxY4bTnz169CiXXnrpBXvPLF26lP79+9OsWTPCwsJITExkxYoVlZ5n+vTpjt/f89155520bduWkJAQmjVrxtChQ9m5c6erX1FE3EhhxKIuOjKSnw8TJ5IbG8vYsWO55pprWLFiBfXq1QPgr3/9K3fffTd33HEH3bp149SpUyxfvpzg4GDHKfbs2cORI0fKnfbzzz8nJyeH2267rcJu58yZQ9euXZkwYQIAvXv3pmvXrnz44Yc1+q5Su4qLi11qX1BQwIABA2jdujWZmZk89dRTPProo7zyyitOfX78+PF07tz5guNfffUV/fv359NPPyUzM5N+/fpxww03sGnTpgvabtiwgZdffrnC88THxzN//nx27NjBihUrMAyDAQMGUFqqPYhFfIZhAvn5+QZg5Ofne7sU0+hsGAZVvJp7rTrxJX369DEmTpxo3HvvvUaTJk2Mvn37uvT5F1980WjcuLFRVFTkODZ58mSjQ4cOTn22T58+Rnp6ugEYx48fv2j7Tp06GdOmTSt37OTJk0b79u2NlStXGn369DHuvffei57jv//9rwEYu3fvrrI+kZ+9++67BmAcO3bM26VYkkZGLErPphFXvP766wQGBvL1118zZ84crrvuOho2bFjp6/LLL3d8NiMjg969exMYGOg4NnDgQHbt2sXx48cr7XP79u089thjvPHGG06t5CorK+PkyZNERESUOz5x4kQGDx5cbm+cyhQWFjJ//nzatGlDdHR0le1FxDO0z4hFORNGzrq9CjGL9u3bl5vn8a9//Yuffvqp0vY/39ID+1ygNm3alHv/531qcnNzK1ymXVRUxKhRo3jqqado1aoVP/zwQ5U1zpw5k1OnTnHzzTc7ji1evJiNGzeWe7ZSRV588UX++te/UlhYSIcOHVi5cmW58CQi3qUwYlHO/MFqZER+Fh8fX+7nli1burW/KVOm0LFjR/7whz841X7RokVMmzaNDz74gObNmwOwd+9e7r33XlauXFluPlNFRo8eTf/+/Tl48CAzZ87k5ptv5uuvv67ycyLiGbpNY1G6TSOuaNCgQbmfXblNExUVVeG+ND+/V5EvvviCd955h4CAAAICArj22msBaNq0KampqeXaLl68mNtvv52333673K2YzMxMDh06xFVXXeU4z5dffslzzz1HQEBAuQmq4eHhtG/fnt69e/Puu++yc+dO3n///WpcKalrSkpKLjhmGEaFx6X6NDJiUQojUhOu3KZJTEzk4YcfpqSkxHF85cqVdOjQodKddN97771y59+wYQO33XYba9asoW3bto7jb731FrfddhuLFy++YO+aa6+9li1btpQ7lpycTGxsLJMnT8bfv+L/Coxzc7iLiooq/X4iYJ+n1KxZMxYsWFDu+F//+ld27NjBxx9/7KXKrEdhxKIURqQmXLlNc+uttzJt2jTGjx/P5MmT2bp1K88++yzPPPOMo83777/PlClTHPt7nB84AMcy8Y4dO9KoUSPAfmtm3LhxPPvss/To0cPxbKSQkBDCw8MJDQ3liiuuKHeeBg0a0KRJE8fxH374gSVLljBgwACaNWvGvn37mD59OiEhIVx//fWuXRSpc/z8/OjWrRsPP/wwf/vb3wDYt28fzz33HNOmTfNyddai2zQW5WwYMdxdiFheeHg4n332GVlZWcTHx3P//fczdepU7rjjDkeb/Px8du3a5dJ5X3nlFc6ePcvEiRO55JJLHK97773X6XMEBwezZs0arr/+etq1a8fIkSMJDQ3lm2++ccw9EbmY1NRUtmzZwvr16wF49tlnCQ0NZeLEiV6uzFpshmH4/P8fFRQUEB4eTn5+PmFhYd4uxxQGAZXvVfmLszgXXERE6qr+/fvzv//9j5ycHAIDA5k2bRoPPvigt8uyFI2MWJSz9990q0ZE5OJSU1PJyckBoGHDhhoVcQOFEYtydrRDYURE5OKuueYax6MGJk2aRGhoqJcrsp5qhZHZs2cTExNDcHAwPXr0cNxLq8w777xDbGwswcHBXHnllXz66afVKlacpzAiIlJ7Hn/8cVq0aHHBgxildrgcRpYsWUJK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8PPzY926dV6szPcUFxeTmZlJUlKS45ifnx9JSUlkZGRU+JmMjIxy7QEGDhxYaXsrq871E7vauHanT5+mpKSEiIgId5Xps2p6/QzDID09nV27dtG7d293lupzqnvtHnvsMZo3b8748eM9UaZPqu61O3XqFK1btyY6OpqhQ4eybdu2WqvJZ8NIbm4uzZs3L3csICCAiIgIcnNzvVSVbzpy5AilpaVERkaWOx4ZGVnptcrNzXWpvZVV5/qJXW1cu8mTJ9OiRYsLwnFdUN3rl5+fT8OGDQkMDGTw4ME8//zz9O/f393l+pTqXLu1a9fy6quvMnfuXE+U6LOqc+06dOjAvHnz+OCDD1i4cCFlZWX07NmTffv21UpNHg8jDz744AWTYH792rlzp6fLEhEvmD59OosXL+b999+v3clwFhcaGsrmzZvZsGEDf//730lJSWH16tXeLsunnTx5kjFjxjB37lyaNm3q7XJMJzExkbFjxxIXF0efPn1YunQpzZo14+WXX66V8wfUyllccP/99/PHP/7xom0uu+wyoqKiOHToULnjZ8+e5dixY0RFRbmxQvNp2rQp/v7+5OXllTuel5dX6bWKiopyqb2VVef6iV1Nrt3MmTOZPn06n3/+OZ07d3ZnmT6rutfPz8+Pdu3aARAXF8eOHTtIS0ujb9++7izXp7h67fbs2UN2djY33HCD41hZWRlgH3XftWsXbdu2dW/RPqI2/s6rV68eXbt2Zffu3bVSk8dHRpo1a0ZsbOxFX4GBgSQmJnLixAkyMzMdn/3iiy8oKyujR48eni7bpwUGBhIfH096errjWFlZGenp6SQmJlb4mcTExHLtAVauXFlpeyurzvUTu+peuxkzZvD444+zfPnycvPC6pra+t0rKyujqKjIHSX6LFevXWxsLFu2bGHz5s2O15AhQ+jXrx+bN28mOjrak+V7VW383pWWlrJlyxYuueSS2inKbVNja8GgQYOMrl27GuvWrTPWrl1rtG/fvtzS3n379hkdOnQw1q1b5zh28OBBY9OmTcbcuXMNwPjqq6+MTZs2GUePHvXGV/CYxYsXG0FBQcZrr71mbN++3bjjjjuMRo0aOZZejRkzxnjwwQcd7b/++msjICDAmDlzprFjxw4jNTW1zi/tdeX6FRUVGZs2bTI2bdpkXHLJJcYDDzxgbNq0yfj++++99RW8xtVrN336dCMwMNB49913yy0TPHnypLe+gle5ev2efPJJ47PPPjP27NljbN++3Zg5c6YREBBgzJ0711tfwWtcvXa/VpdX07h67aZNm2asWLHC2LNnj5GZmWnccsstRnBwsLFt27Zaqcenw8jRo0eNUaNGGQ0bNjTCwsKM5OTkcn9hZWVlGYCxatUqx7HU1FQDuOA1f/58z38BD3v++eeNVq1aGYGBgUb37t2N//znP473+vTpY4wbN65c+7ffftv4zW9+YwQGBhqXX3658cknn3i4Yt/iyvX7+Xfv168+ffp4vnAf4Mq1a926dYXXLjU11fOF+whXrt/DDz9stGvXzggODjYaN25sJCYmGosXL/ZC1b7B1b/3zleXw4hhuHbt7rvvPkfbyMhI4/rrrzc2btxYa7XYDMMwameMRURERMR1Pru0V0REROoGhRERERHxKoURERER8SqFEREREfEqhRERERHxKoURERER8SqFEREREfEqhRERERHxKoURERER8SqFEREREfEqhRERERHxKoURERER8ar/DybOZ9mJtTpPAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def draw_arm(x, y, z):\n", + "\n", + " # Get joint angles\n", + " l1, l2, l3 = (.422864, .359041, .092124)\n", + " r, theta = cartesian_to_polar(x, y)\n", + " base, shoulder, elbow, wrist = get_joints_from_xyz_rel(x, y, z)\n", + "\n", + " # Print angles\n", + " print('Target position (x,y,z):', x, y, z)\n", + " print('Angles (base, shoulder, elbow, wrist):', [round(math.degrees(i), 4) for i in [base, shoulder, elbow, wrist]])\n", + " print('Robot Angles:', [round(math.degrees(i), 4) for i in get_joints_from_xyz_abs(x, y, z)])\n", + "\n", + " # Calculate each joint's endpoint position\n", + " x1, y1 = polar_to_cartesian(l1, shoulder)\n", + " x2, y2 = polar_to_cartesian(l2, shoulder-elbow)\n", + " x2 += x1\n", + " y2 += y1\n", + " x3, y3 = polar_to_cartesian(l3, shoulder-elbow-wrist)\n", + " x3 += x2\n", + " y3 += y2\n", + "\n", + " # Print each joint's endpoint position\n", + " print('elbow (x,y):', round(x1,3), round(y1,3))\n", + " print('wrist (x,y):', round(x2,3), round(y2,3))\n", + " print('tool (x,y):', round(x3,3), round(y3,3))\n", + "\n", + " # Draw limbs\n", + " plt.plot([0, x1], [0, y1], color='cyan', linewidth=7)\n", + " plt.plot([x1, x2], [y1, y2], color='orange', linewidth=7)\n", + " plt.plot([x2, x2+l3], [y2, y2], color='red', linewidth=7)\n", + "\n", + " # Display angles\n", + " plt.text(0, 0.02, f'{round(math.degrees(shoulder), 2)}°')\n", + " plt.text(x1, y1+0.02, f'{round(math.degrees(elbow), 2)}°')\n", + " plt.text(x2, y2+0.02, f'{round(math.degrees(wrist), 2)}°')\n", + "\n", + " # Display r arrow\n", + " plt.annotate(f'', xy=(0, 0), xycoords='data', xytext=(x3, 0), textcoords='data', arrowprops={'arrowstyle': '<->'})\n", + " plt.annotate(f'r={round(r,4)}', xy=(x2/2, 0.01), xycoords='data', xytext=(x2/2, 0), textcoords='offset points')\n", + "\n", + " # Display z arrow\n", + " plt.annotate(f'', xy=(x3, 0), xycoords='data', xytext=(x3, y3), textcoords='data', arrowprops={'arrowstyle': '<->'})\n", + " plt.annotate(f'z={round(y2,4)}', xy=(x3+0.01, y2/2), xycoords='data', xytext=(x3/2, 0), textcoords='offset points')\n", + " \n", + " # Display plot\n", + " ax = plt.subplot(111)\n", + " ax.spines[['right', 'top']].set_visible(False)\n", + " plt.axis('equal')\n", + " plt.show()\n", + "\n", + "draw_arm(0.3, 0.3, 0.3)" + ] + }, + { + "cell_type": "code", + "execution_count": 165, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Target position (x,y,z): 0.3 0.3 0.3\n", + "Angles (base, shoulder, elbow, wrist): [45.0, 90.7095, 110.715, -20.0055]\n", + "Robot Angles: [45.0, -90.7095, 110.715, -110.0055]\n", + "elbow (x,y): -0.005 0.423\n", + "wrist (x,y): 0.332 0.3\n", + "tool (x,y): 0.424 0.3\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGnCAYAAABl41fiAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAA9hAAAPYQGoP6dpAABD40lEQVR4nO3deVyVZf7/8dcBZFEBxQXSUEwd0UoxUMNvbhMu5aSWTmaOOmTWTNoy1G/MaiSrCcecssWynLTSTFvM1tGMtLQYNdTJfdIg3MAdFBMQ7t8fx06SIOcAZ7lv3s/H4zwew32uc1+fc0vO2+u+ruu2GYZhICIiIuIlft4uQEREROo2hRERERHxKoURERER8SqFEREREfEqhRERERHxKoURERER8SqFEREREfEqhRERERHxKoURERER8SqFEREREfEqhZE64KuvvuKGG26gRYsW2Gw2li1bVu79pUuXMmDAAJo0aYLNZmPz5s0XnOPMmTNMnDiRJk2a0LBhQ4YPH05eXt5F+7XZbBW+nnrqKQCys7MZP348bdq0ISQkhLZt25KamkpxcXG588ydO5fWrVvTtWtX1q1bV6NrISIivkdhpA4oLCykS5cuzJ49u9L3r7nmGv7xj39Ueo6//OUvfPTRR7zzzjt8+eWXHDhwgJtuuumi/R48eLDca968edhsNoYPHw7Azp07KSsr4+WXX2bbtm0888wzzJkzh4ceeshxjpycHGbMmMHixYt5+OGHSU5OrsYVEBERX2bTg/LqFpvNxvvvv8+wYcMueC87O5s2bdqwadMm4uLiHMfz8/Np1qwZixYtYsSIEYA9SHTs2JGMjAyuvvpqp/oeNmwYJ0+eJD09vdI2Tz31FC+99BI//PADAFu3biU5OZnVq1dz6NAhfvvb35KVleX8FxYREZ8X4O0CxPdlZmZSUlJCUlKS41hsbCytWrVyOozk5eXxySef8Prrr1+0XX5+PhEREY6fr7jiCjp37kx4eDiBgYHMnTu3+l9ERER8km7TSJVyc3MJDAykUaNG5Y5HRkaSm5vr1Dlef/11QkNDL3prZ/fu3Tz//PPceeed5Y6/+uqr5OXlcfToUUaPHu1y/SIi4ts0MiIeMW/ePEaPHk1wcHCF7+/fv59Bgwbx+9//ngkTJlzwfpMmTdxdooiIeIlGRqRKUVFRFBcXc+LEiXLH8/LyiIqKqvLza9asYdeuXdx+++0Vvn/gwAH69etHz549eeWVV2qjZBERMRGFEalSfHw89erVKzfxdNeuXeTk5JCYmFjl51999VXi4+Pp0qXLBe/t37+fvn37Eh8fz/z58/Hz06+kiEhdY4rbNIZhcPLkSUJDQ7HZbN4ux3ROnTrF7t27HT9nZWWxefNmIiIiaNWqFceOHSMnJ4cDBw4A9qAB9hGRqKgowsPDGT9+PCkpKURERBAWFsbdd99NYmJiucmrsbGxpKWlceONNzqOFRQU8M477/DPf/7zgrp+DiKtW7dm5syZHD582PGeMyMuIiJiEYYJ5OfnG4CRn5/v7VJMadWqVQZwwWvcuHGGYRjG/PnzK3w/NTXVcY6ffvrJuOuuu4zGjRsb9evXN2688Ubj4MGD5foBjPnz55c79vLLLxshISHGiRMnLqirsn5N8mspIiK1xBT7jBQUFBAeHk5+fj5hYWHeLkdERERqkW7Qi4iIiFcpjIiIiIhXKYyIiIiIVymMiIiIiFcpjIiIiIhXKYyIiIiIVymMiIiIiFcpjIiIiIhXKYxYSVkJrP8THPwMykq9XY2IiIhTTPFsGqnaciDk4Gf02f0y7H6ZwpAW7Im5lZ0xYzjcuDOlQANgvJfrFBER+TWNjFjEX4CDWQscPzf46QCdd8zk5n934ZpPu5CzYyYvnT7gvQJFREQqoWfTWMTVxfmsej+KkNIzlbYptfnhH5kEbcZA9I0Q0MCDFYqIiFRMt2ksYtDedy8aRAD8jTLI/cz+2tAAoofbg0nzfuDn76FKRUREytNtGov43Xm3aJxythCy3oAv+sMHrWDTX+HEFvcUJyIichG6TWMFhT/CBzG1c67GcRAzBmJuhZCo2jmniIjIRWhkxAqy36y9cx3fDJvuh2UtYdUgyHrTPooiIiLiJhoZMTvDgE86QsEu9/UR0PC8+SV9Nb9ERERqlUZGzK70NDRN5HRAQ/f1cfYUZL0OXyTBB61h02Q4sdV9/YmISJ2ikRGLuP5sIY32fcCYrAUMyP3MvnLG3Rp3tY+WtB6l+SUiIlJtCiMW0R/4/Nz/jvrpIKOy32JM9gK6Ht/s/s5t/hDVH9qMhUuHQkB99/cpIiKWoTBiEYOAFRUcv/zEVsZkLWB09ptc+tN+9xcSEAqthttX5ET2BZvuBIqIyMUpjFjE74BPLvK+X1kp/Q6tYkXWAvz3vueZFTL1oyFmtP1WTngn9/cnIiKmpDBiEUOBD51odxoIOVsIe5fZNz3L+xw8Mr/kqvPml0S6vz8RETENhRGLuAl434l2J4Fy625+OgjZiyBrAZz4r1tqK8fmD1EDzptfEuL+PkVExKcpjFjE74F3nWh3Agiv7M3j30H2Avsmaj8drK3SKhcQCq1GnNu/pI/ml4iI1FEKIxZxC7DEiXZHgYiqGpWVQt4X9tGSfUs9NL+k1XnzSzq6vz8REfEZCiMWMRpY5ES7PKC5KycuOQX7lp2bX5LumfklEfH22zitb4Fgl6oVERETUhixiHHAG060OwBcUt1OTh+AH3+eX/Jddc/iPJs/XDLIPlrScojml4iIWJTCiEXcBsx3ot1e4NLa6PD4f+2h5MdFnplfUi8MokfYR0ya99L8EhERC6nW3+izZ88mJiaG4OBgevTowfr165363OLFi7HZbAwbNqw63cpFOPvoutLa6rBxF7hqJgzdC/1WQMwfwN+NO6+WFMAP8yC9L3zQBv77MOTvdF9/IiLiMS6HkSVLlpCSkkJqaiobN26kS5cuDBw4kEOHDl30c9nZ2TzwwAP06tWr2sVK5TweRn7m5w+XDICeC+CmPLj6dYhKAmy13dMvTufAtiftTyte3g12PQ9nDruvPxERcSuXb9P06NGDbt268cILLwBQVlZGdHQ0d999Nw8++GCFnyktLaV3797cdtttrFmzhhMnTrBs2TKn+9RtmqpNBF50ot3/gPZurgWA0/vP7V/yBuR74Am/toBf5pdcOgT8g93fp4iI1AqXRkaKi4vJzMwkKSnplxP4+ZGUlERGRkaln3vsscdo3rw548ePd6qfoqIiCgoKyr3k4pwdGTnr1irOU78ldPp/cP13cN0miE2BYDc+2dc4Cwc+hq9HwtJIWDcBDn3lmdU/IiJSIy6FkSNHjlBaWkpkZPntvCMjI8nNza3wM2vXruXVV19l7ty5TveTlpZGeHi44xUdHe1KmXVSgJPtav02TVVsNmgcB1f9E4bthb7L7fuJ+LtxZUxJAez5F3zeBz68DP77CBTscl9/IiJSI25dknDy5EnGjBnD3Llzadq0qdOfmzJlCvn5+Y7X3r173VilNXhtzogr/AKgxUDoufDc/JLXIPJa3Dq/pPBH2PZ3+DgWVvSAXS/AmSPu609ERFzm7D+oAWjatCn+/v7k5eWVO56Xl0dU1IVD8Hv27CE7O5sbbrjBcayszD5sHhAQwK5du2jbtu0FnwsKCiIoKMiV0uo8U4SR89ULhcvG2V+n99m3oM9aAPnb3Nfn0fX218a/QIvrzu1fcoPml4iIeJlLIyOBgYHEx8eTnp7uOFZWVkZ6ejqJiYkXtI+NjWXLli1s3rzZ8RoyZAj9+vVj8+bNuv1Si0wXRs5X/1LoNBmu3wKDNkKHv0CwG5/sa5yF/R/B2pthaRSsuwMOrdH8EhERL3FpZAQgJSWFcePGkZCQQPfu3Zk1axaFhYUkJycDMHbsWFq2bElaWhrBwcFcccUV5T7fqFEjgAuOS82YOoz8zGaDiK72V9cZkLvy3PNxlkHpT+7psyQf9sy1vxrE2PdLaTMGwn7jnv5EROQCLoeRkSNHcvjwYaZOnUpubi5xcXEsX77cMak1JycHPz/tjulpPreapqb8Auy3UlpcZ5+QunepPZjkrQLctGlwYTZse8L+atLDHkpajYRg5+c7iYiI67QdvEX8HXjEiXargT7uLcW9Cvfa55dkL4D87e7vzxYALQdDzBho+Tvw11wmEZHapiEMi7DEbRpnNIiGyx+E67fCoEzocJ97n+xrnIV9H8DaEfb5JevvhENrwfczvIiIaSiMWESdCSM/s9kg4iqIfwaG7Yc+n0DrW9y7MqbkBOx+BT7vBR+1g+9S4eRu9/UnIlJHKIxYRJ0LI+fzC4CW18P/vWXfv6THPGje1719nvoBtj4GH7WHFYnwvxeh6Kh7+xQRsSiFEYuw3ATW6qoXBm2TIWkVDP0RujwJYR3d2+fR/8C3E+H9S+CrG+2TbUuL3NuniIiFKIxYRJ0eGalMg1Zw+RQYvA0GboDf3ANBzdzXX1mJfRnymuH2YLL+T3D4G80vERGpglbTWMQc4M9OtHsPuMnNtfi0shI4+Jl9mfD+D6D0jPv7bNj2l/1LQi/ccVhEpK7TyIhFaGTESX717Et1r1kMN+ZCj39Bczcvdj61B7ZOs096/awnfD8Hio65t08RERNRGLEIhZFqCAyHtuMhaTUMzYYuf4ewWPf2eSQDNvz53PySm2Dv+5pfIiJ1nsKIRSiM1FCD1nD5QzB4OwxcD7+5G4LcuPNqWTHsex/W3ATvt4ANd8HhDM0vEZE6SWHEIrSappbYbNCkGyQ8BzcegD4fQavfg58bd14tPgbfvwQre8JHv4Et0+DkHvf1JyLiYxRGLMLZhwxpZMQFfvXsW8Bf8zbclAvd50Lz3u7t89Ru2PKofX7Jymvg+5eh+Lh7+xQR8TKFEYvQbRo3C2wE7W6HpC9hSBZ0fhxC3fxk38Nfw4Y/2behXzMc9i6D0mL39iki4gUKIxahMOJBDWPgikfgdzthwDr4zST3zy/ZuxTW3AjLWsCGiXDkP5pfIiKWoTBiEQojXmCzQdPukPC8fX5J7w8heoR755cUHYXvX4TPEuHjDrDlcTiV5b7+REQ8QGHEIhRGvMyvHlx6A/R659z8klegWS/39nnye9gyFT68DFb2sj/ET/NLRMSEFEYsQqtpfEhgI2g3Afp/BUN+gCsfg9D27u3z8FpYf+e5+SW/h30fan6JiJiGwohFaDWNj2rYBq78G/xuFwz4D7SfCIER7uuvrBj2vgtfDT03v2QSHFmn+SUi4tMURixCt2l8nM0GTXtAtxfgxoPQexlEDwe/QPf1WXQUvp8Nn10NH8fC1ifgVLb7+hMRqSaFEYtQGDER/0C4dCj0etc+v6TbHGj2f+7t8+T/4Lu/wYdtYGVv2D0Xik+4t08REScpjFiEwohJBTaG9ndC/7UwZA9cOQ0atnNvn4fXwPo77PNL1t4M+z6yP81YRMRLbIbh+zeTCwoKCA8PJz8/n7CwMG+X45MygJ5OtJsKTHNzLVJDhgFH10HWG/DjEvt28e4W1BRa3wJtxkJEgv22koiIhyiMWMR6oIcT7R4C/u7mWqQWlRbDgU8hewHs/9g+QdXdwjpAzBho8wf7AwRFRNxMYcQiNgLxTrSbDEx3cy3iJkXHIOdtyFoAR77xTJ/N+0CbMfbN3ALDPdOniNQ5CiMW8V8gzol2DwBPubcU8YSTeyB7oT2YnPLAE379g6HlEHswuWSgfZM3EZFaojBiEVuBK51o9xfgaTfXIh5kGHAkwx5KcpZ4ZgfWoGbQepQ9mETEa36JiNSYwohF7AA6OdHuHuBZN9ciXlJaZJ9fkvUGHPjEMytkwmLtoSTmD9Cglfv7ExFLUhixiP8BHZxoNxF4wc21iA8oOnre/JIMz/TZvK89mLQaAfX036mIOE9hxCL2AM7sTnEnMMfNtYiPObkbshbaV+Sc+sH9/fkHQ8uh9mXClwwAP2cfViAidZXCiEX8CMQ40e52YK57SxFfZRj2VThZC+z7l5SccH+fwc1/mV/S+CrNLxGRCimMWMQ+INqJdsnAPDfXIiZQWmTftyR7gX2eiUfml3S0j5bEjIYGzvy2ikhdoTBiEQeBFk60Gwu87uZaxGSKjtpHSrIWwNH/eKBDG0T2tW+s1mq45peIiMKIVRwCIp1oNxpY6OZaxMQK/ndu/5KFUJjl/v78Q+wPDWwzFqL6a36JSB2lMGIRR4GmTrS7BXjLzbWIBRgGHP7avkw4520oyXd/n8GR580v6ar5JSJ1iMKIRZwAGjvRbgTwjntLEaspPWOfX5J1bn6Jcdb9fYZffm7/ktFQ/1L39yciXqUwYhGngFAn2t0ILHVzLWJhZ47Yd3rNegOOrvdAhzaI7Hfu+TjDoZ4zv+UiYjYKIxbxE1DfiXZDgA/cXIvUEQW7zu1fshAKs93fn38IXHqjPZhEJWl+iYiFKIxYRDEQ5ES7wcDHbq5F6hij7Lz5Je94aH5JFMTcag8mjbpofomIySmMWEQp4My/EwcB/3ZzLVKHlZ6B/R+dm1/ybw/NL7nivPklLd3fn4jUOoURizAAPyfa9Qc+c3MtIgCcOQw/LrYHk2MbPNChDSJ/a18mHH0T1GvogT5FpDYojFiIP1BWRZt+wBceqEWknPyd5/YvWQCnc9zfn399iL7RvrFaVBL4+bu/TxGpNoURCwnCPnfkYnoDX3qgFpEKGWVwaI19G/qcd6CkwP19BkfZb+G0GQONu7i/PxFxmcKIhdTHvqrmYv4PWOuBWkSqdPanX+aXHPw3GKXu77PRlfbRkpjRUN+ZByiIiCcojFhIKPb9Ri7maiDDA7WIuOTMofPml3zr/v5sfhB5rX205NIbNb9ExMsURiykEVDVospugCe2qhKptvwd9lCSvRBO73V/fwENftm/JPJazS8R8QKFEQtpAhyros1VQKYHahGpMaMMDn1lDyY578DZk+7vM6SFff+SmDHQuLP7+xMRQGHEUpoDh6to0xn4rwdqEalVZ0/Dvg/tE18PrvDQ/JLO9tGS1rdqfomImymMWEgL4GAVbS4HtnqgFhG3+SnPPr8kewEc88A4n80PIpPOPR/nRvttHRGpVc7skyUm4cydbg/8e1LEvUIiIfZeGPQtDN4GnR6E+tHu688og9zPIGMMLI2EjHGQ+zmUWf+/ppKSEiZPnsyVV15JgwYNaNGiBWPHjuXAgQPl2h07dozRo0cTFhZGo0aNGD9+PKdOXXw6/ZkzZ5g4cSJNmjShYcOGDB8+nLy8vHJt7rnnHuLj4wkKCiIuLq7C88ydO5fWrVvTtWtX1q1bV6PvK96jkRELiQF+rKJNe+B/7i9FxLOMMjj05XnzS6paV1YLQlr8sn9Joyvd358X5OfnM2LECCZMmECXLl04fvw49957L6WlpXz77S+rnq677joOHjzIyy+/TElJCcnJyXTr1o1FixZVeu4///nPfPLJJ7z22muEh4czadIk/Pz8+Prrrx1t7rnnHjp06MC6dev47rvv2Lx5c7lz5OTkcO211/LGG2+wf/9+pk6dyvbt22v9Ooj7KYxYSFvghyraXAbs8UAtIl5z9jTs+8AeTHI/88z8ksZx5/YvuRVCotzfnxdt2LCB7t278+OPP9KqVSt27NhBp06d2LBhAwkJCQAsX76c66+/nn379tGixYXzbfLz82nWrBmLFi1ixIgRAOzcuZOOHTuSkZHB1VdfXa79o48+yrJlyy4II1u3biU5OZnVq1dz6NAhfvvb35KVleWeLy5upds0FuLMbRoPPLZMxLsC6kPMKOj3KQzbD1c9A427urfP45th0/2wrCWsGgRZb8LZQvf26SX5+fnYbDYaNWoEQEZGBo0aNXIEEYCkpCT8/PwqvW2SmZlJSUkJSUlJjmOxsbG0atWKjAznd0K64oor6Ny5M+Hh4Vx++eU88cQT1ftS4nUKIxaiOSMivxISCbH3wXUb4fqt0Gky1L/Uff0ZZfbVPhl/gKVRkPFHyE23zPySM2fOMHnyZEaNGuUYpc7NzaV58+bl2gUEBBAREUFubm6F58nNzSUwMNARaH4WGRlZ6Wcq8+qrr5KXl8fRo0cZPXq0S58V36EwYiEBTrSxxl+JItXQ6HKImw5DsuG3n0ObcRDgxp1Xz56CrNfhiyT4oDVsmgwnfHst25tvvknDhg0drzVr1jjeKykp4eabb8YwDF566SUvVnmhJk2aEBIS4u0ypAac+f8vMQmNjIg4wc8foq61v7rN/tX8kqqee11NP+2HHTPsr8Zdz+1fMsrn5pcMGTKEHj16OH5u2bIl8EsQ+fHHH/niiy/Kzd2Liori0KFD5c5z9uxZjh07RlRUxd8vKiqK4uJiTpw4UW50JC8vr9LPiLVpZMRCFEZEXBTQwD7ptN+/Ydg+6PpP+2RUdzq+CTamwLJLYdV1kP2WfdKtDwgNDaVdu3aOV0hIiCOIfP/993z++ec0adKk3GcSExM5ceIEmZm/7PnyxRdfUFZWVi7YnC8+Pp569eqRnp7uOLZr1y5ycnJITEx0z5cTn6bVNBbSHdhQRZtw4IT7SxExtxNbzz0f5037qIa7BYRCq+H2FTmRfe0brfmAkpISRowYwcaNG/n444+JjIx0vBcREUFgYCBgX9qbl5fHnDlzHEt7ExISHEt79+/f71iC2717d8C+tPfTTz/ltddeIywsjLvvvhuAb775xtHH7t27OXXqFHPmzGHVqlUsWbIEgE6dOjn6FmtQGLGQROA/VbRpQNVP9hWRc8pK4dAqezDZ+55nVsjUj/5l/5LwTu7v7yKys7Np06ZNhe+tWrWKvn37AvZNzyZNmsRHH32En58fw4cP57nnnqNhw4blznP+Z86cOcP999/PW2+9RVFREQMHDuTFF18sd5umb9++fPnllxf0nZWVRUxMTK1+V/EuhREL6QWsraJNMPCTB2oRsZyzhbB3GWS9AXmfu29+yfkaX3Xe/JLIqtuLmJTCiIX0BS78N0R59YBi95ciYm0/HYTsRfYRkxMeePSkzR+iBkCbsXDpUAjQyhGxFoURC7kW+KKKNn5oEqtIrTr+nf2hfdlv2kOKuwWEQqsR9hGT5n18Zn6JSE0ojFjIAGClE+3KAJubaxGpc8pKIe8L+2jJvqUeml/S6rz5JR3d35+ImyiMWMh1wHIn2p3FuWXAIlJNJadg37Jz80vSPTO/JCLefhun9S0Q3Lzq9iI+RGHEQn4HfOJEuzNAkJtrEZFzTh+AH3+eX/Kd+/uz+cMlg+yjJS2HaH6JmILCiIUMAz5wol0hUN+9pYhIRY7/1x5Kflzkmfkl9cKg1e/t+5c076X5JeKzFEYsZDiw1Il2BUCom2sRkYsoK7XfvslaAHuXQqkHdmBt0No+vyRmDITHur8/ERcojFjIzcA7TrQ7DjRybyki4qySU/ZAkr3A/oRfPPBXckTCefNLmrm/P5EqKIxYyChgsRPtjgBNqmwlIh53et8v+5fke+AJv7aAX+aXXDoE/IPd36dIBRRGLOQPwJtOtMsDNNdexIcZhn0ztawF9nByJrd2z/934OivD9qgXqh9HxOFEue0agVfVLW7kzgjoDofmj17Nk899RS5ubl06dKF559/3vHwo19bunQpTz75JLt376akpIT27dtz//33M2bMmBoVLhdydrnuWbdWISI1ZrPZnx7cOA7i/mG/fZP98/ySWnigw1Hs/yopx8A+o6yg5ucXcZHLU6uXLFlCSkoKqampbNy4kS5dujBw4EAOHTpUYfuIiAgefvhhMjIy+O6770hOTiY5OZkVK1bUuHgpz9lkqR1YRUzELwBaDISeC+GmPLj6NYi8Fm1dKFbi8m2aHj160K1bN1544QUAysrKiI6O5u677+bBBx906hxXXXUVgwcP5vHHH3eqvW7TOOcOYK4T7bKAGPeWIiLudnqffQv6rAWQv821z6ZQwciIuKxtW9i929tVWIJLIyPFxcVkZmaSlJT0ywn8/EhKSiIjI6PKzxuGQXp6Ort27aJ3796VtisqKqKgoKDcS6rm7G0ajYyIWED9S6HTZLh+CwzaCB3+AsF6sq+Yk0th5MiRI5SWlhIZWf4XPjIyktzcyidY5efn07BhQwIDAxk8eDDPP/88/fv3r7R9Wloa4eHhjld0dLQrZdZZCiMidZDNBhFdIf5pGLYP+n4KrUeBv3ZeFfPwyHZ8oaGhbN68mQ0bNvD3v/+dlJQUVq9eXWn7KVOmkJ+f73jt3bvXE2WansKISB3nFwAtroP/WwQ35cLV8yGyH5pfIr7OpdU0TZs2xd/fn7y88jcb8/LyiIqKqvRzfn5+tGvXDoC4uDh27NhBWloaffv2rbB9UFAQQUF6eoqrtJpGRBzqhcFlf7S/Cvfa55dkL4D87d6uTOQCLo2MBAYGEh8fT3p6uuNYWVkZ6enpJCYmOn2esrIyioqKXOlanKDVNCJSoQbRcPmDcP1WGJQJ9cK9XZHplALaUcR9XN5nJCUlhXHjxpGQkED37t2ZNWsWhYWFJCcnAzB27FhatmxJWloaYJ//kZCQQNu2bSkqKuLTTz9lwYIFvPTSS7X7TUS3aUTk4mw2iLgK2l8FQTlw9jScPQlnCym3Db1/CIS08FqZvujPhw4x9+RJ1rZowf+FnJuP06qVd4uyEJfDyMiRIzl8+DBTp04lNzeXuLg4li9f7pjUmpOTg5/fLwMuhYWF3HXXXezbt4+QkBBiY2NZuHAhI0eOrL1vIYDCiIg46de7hpYUQM57kPUGHFoNV8+By8Z6pTRfVFhYyNstWwLwZFwcn3zyiZcrsh5tB28hqcBjTrT7BnD+ppqI1CmFORDUBAIaeLsSn/HPf/6TyZMnU1pq/6fcxo0b6dq1q5ershaPrKYRz9DIiIjUWINWCiLnKSwsZMaMGfTr1w+Atm3bMm3aNC9XZT0KIxai1TQiIrVrzpw5HDt2jJtuugmABx54gA8++IBNmzZ5uTJrURixEK2mERGpPWVlZcyYMYPk5GSaN7c/63z48OG0b9+e6dOne7k6a1EYsRDdphERqT02m40JEyaUuy0TEBDAa6+9xoABA7xYmfW4vJpGfJfCiIhI7bHZbDzxxBMXHO/Zsyc9e/b0QkXWpZERC1EYERERM1IYsRCFERERMSOFEQvRahoRETEjhREL0WoaERExI4URC9FtGhERMSOFEQtRGBERETNSGLEQhRERETEjhRELURgREREzUhixEK2mERERM1IYsRCtphERETNSGLEQ3aYREREzUhixEIURERExI4URC1EYERERM1IYsRBNYBURETNSGLEQjYyIiIgZKYxYiFbTiIiIGSmMWIhGRkRExIwURixEYURERMxIYcRCFEZERMSMFEYsRKtpRETEjBRGLEQTWEVExIwURixEt2lERMSMFEYsRGFERETMSGHEQhRGRETEjBRGLERhREREzEhhxEK0mkZERMxIYcRCtJpGRETMSGHEQnSbRkREzEhhxEIURkRExIwURixEYURERMxIYcRCFEZERMSMFEYsRKtpRETEjBRGLESraURExIwURixEt2lERMSMFEYsRGFERETMSGHEQpz9w1QYERERX6IwYjHOjI4ojIiIiC9RGLEYZ8KIVtOIiIgvURixGGdW1GhkREREfInCiMXoNo2IiJiNwojFKIyIiIjZKIxYjMKIiIiYjcKIxSiMiIiI2SiMWIxW04iIiNkojFiMVtOIiIjZKIxYjG7TiIiI2SiMWIzCiIiImI3CiMUojIiIiNkojFiMwoiIiJiNwojFaDWNiIiYjcKIxWg1jYiImI3CiMXoNo2IiJiNwojFKIyIiIjZKIxYjMKIiIiYjcKIxSiMiIiI2SiMWIxW04iIiNkojFiMVtOIiIjZKIxYjDMjI8a5l4iIiC9QGLEYZ8IIaHRERER8h8KIxSiMiIiI2SiMWIyzYUSTWEVExFcojFiMRkZERMRsFEYsxpnVNKAwIiIivqNaYWT27NnExMQQHBxMjx49WL9+faVt586dS69evWjcuDGNGzcmKSnpou2lZjQyIiIiZuNyGFmyZAkpKSmkpqayceNGunTpwsCBAzl06FCF7VevXs2oUaNYtWoVGRkZREdHM2DAAPbv31/j4uVCCiMiImI2LoeRp59+mgkTJpCcnEynTp2YM2cO9evXZ968eRW2f/PNN7nrrruIi4sjNjaWf/3rX5SVlZGenl7j4uVCCiMiImI2LoWR4uJiMjMzSUpK+uUEfn4kJSWRkZHh1DlOnz5NSUkJERERrlUqTtFqGhERMRtn5zsCcOTIEUpLS4mMjCx3PDIykp07dzp1jsmTJ9OiRYtygebXioqKKCoqcvxcUFDgSpl1mkZGRETEbDy6mmb69OksXryY999/n+Dg4ErbpaWlER4e7nhFR0d7sEpz02oaERExG5fCSNOmTfH39ycvL6/c8by8PKKioi762ZkzZzJ9+nQ+++wzOnfufNG2U6ZMIT8/3/Hau3evK2XWaRoZERERs3EpjAQGBhIfH19u8unPk1ETExMr/dyMGTN4/PHHWb58OQkJCVX2ExQURFhYWLmXOEdhREREzMalOSMAKSkpjBs3joSEBLp3786sWbMoLCwkOTkZgLFjx9KyZUvS0tIA+Mc//sHUqVNZtGgRMTEx5ObmAtCwYUMaNmxYi19FQGFERETMx+UwMnLkSA4fPszUqVPJzc0lLi6O5cuXOya15uTk4Of3y4DLSy+9RHFxMSNGjCh3ntTUVB599NGaVS8X0GoaERExG5fDCMCkSZOYNGlShe+tXr263M/Z2dnV6UKqSRNYRUTEbPRsGovRbRoRETEbhRGLURgRERGzURixGIURERExG4URi1EYERERs1EYsRitphEREbNRGLEYraYRERGzURixGN2mERERs1EYsRiFERERMRuFEYtRGBEREbNRGLEYhRERETEbhRGL0WoaERExG4URi9FqGhERMRuFEYvRbRoRETEbhRGLURgRERGzURixGIURERExG4URi1EYERERs1EYsRitphEREbNRGLEYraYRERGzURixGN2mERERs1EYsRiFERERMRuFEYtRGBEREbNRGLEYhRERETEbhRGL0WoaERExG4URi9FqGhERMRuFEYvRbRoREeuYPXs2MTExBAcH06NHD9avX3/R9kuXLiUhIYFGjRrRoEED4uLiWLBggYeqrT6FEYtRGBERsYYlS5aQkpJCamoqGzdu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E4URC1IYERERM1EYsSCFERERMROFEQtSGBERETNRGLEgraYREREzURixIK2mERERM1EYsSDdphERETNRGLEghRERETEThRELUhgREREzURixIIURERExE4URC9JqGhERMROFEQvSahoRETEThREL0m0aERExE4URC1IYERERM1EYsSCFERERMROFEQtSGBERETNRGLEgraYREREzURixIK2mERERM1EYsSDdphERETNRGLEghRERETEThRELUhgREREzURixIIURERExE4URC9JqGhERMROFEQvSahoRETEThREL0m0aERExE4URC1IYERERM1EYsSCFERERMROFEQtSGBERETNRGLEgraYREREzURixIBvO/cFqZERERHyBwohFOTM6ojAiIiK+QGHEohRGRETELBRGLEphREREzEJhxKIuGkZOnoT77iOzdWtCQkLo2bMnGzZscLxtGAZTp07lkksuISQkhKSkJL7//vuL9hcTE4PNZrvgNXHiREebV155hb59+xIWFobNZuPEiRMXnCcjI4O4uDhiYmJ49dVXXfzWIiJiRgojFnXRMHL77bByJa0XLGDLli0MGDCApKQk9u/fD8CMGTN47rnnmDNnDuvWraNBgwYMHDiQM2fOVHrKDRs2cPDgQcdr5cqVAPz+9793tDl9+jSDBg3ioYceqvQ848eP529/+xuLFi0iLS2NvXv3uvS9RUTEfJx9jImYTKV/sD/9BO+9Bx98QP3evWkHPProo3z00Ue89NJLPP7448yaNYtHHnmEoUOHAvDGG28QGRnJsmXLuOWWWyo8bbNmzcr9PH36dNq2bUufPn0cx+677z4AVq9eXWndhYWFXHXVVTRv3pzGjRtz8uRJJ7+xiIiYlUZGLKrSkZGzZ6G0FIKDy80ZCQkJYe3atWRlZZGbm0tSUpLjvfDwcHr06EFGRoZTfRcXF7Nw4UJuu+02bDabS3VPnTqVjh07Eh4eztVXX02nTp1c+ryIiJiPRkYsqtIwEhoKiYnw+OOc6diR0shI3nrrLTIyMmjXrh25ubkAREZGlvtYZGSk472qLFu2jBMnTvDHP/7R5brHjx/PLbfcQnFxMY0bN3b58yIiYj4aGbGoi84ZWbAADIPdLVsSFBTEc889x6hRo/Dzq51fh1dffZXrrruOFi1aVOvzDRo0UBAREalDFEYs6qJhpG1b+PJLYk6dYu/evaxfv56SkhIuu+wyoqKiAMjLyyv3kby8PMd7F/Pjjz/y+eefc/vtt9egehERqUsURizKmX1GjAYNuOSSSzh+/DgrVqxg6NChtGnThqioKNLT0x3tCgoKWLduHYmJiVWec/78+TRv3pzBgwfXoHoREalLFEYs6qKTgVasgOXLKcrKYuXKlfTr14/Y2FiSk5Ox2Wzcd999PPHEE3z44Yds2bKFsWPH0qJFC4YNG+Y4xbXXXssLL7xQ7rRlZWXMnz+fcePGERBwYQW5ubls3ryZ3bt3A7BlyxY2b97MsWPHauEbizd999139OrVi+DgYKKjo5kxY4bTnz169CiXXnrpBXvPLF26lP79+9OsWTPCwsJITExkxYoVlZ5n+vTpjt/f89155520bduWkJAQmjVrxtChQ9m5c6erX1FE3EhhxKIuOjKSnw8TJ5IbG8vYsWO55pprWLFiBfXq1QPgr3/9K3fffTd33HEH3bp149SpUyxfvpzg4GDHKfbs2cORI0fKnfbzzz8nJyeH2267rcJu58yZQ9euXZkwYQIAvXv3pmvXrnz44Yc1+q5Su4qLi11qX1BQwIABA2jdujWZmZk89dRTPProo7zyyitOfX78+PF07tz5guNfffUV/fv359NPPyUzM5N+/fpxww03sGnTpgvabtiwgZdffrnC88THxzN//nx27NjBihUrMAyDAQMGUFqqPYhFfIZhAvn5+QZg5Ofne7sU0+hsGAZVvJp7rTrxJX369DEmTpxo3HvvvUaTJk2Mvn37uvT5F1980WjcuLFRVFTkODZ58mSjQ4cOTn22T58+Rnp6ugEYx48fv2j7Tp06GdOmTSt37OTJk0b79u2NlStXGn369DHuvffei57jv//9rwEYu3fvrrI+kZ+9++67BmAcO3bM26VYkkZGLErPphFXvP766wQGBvL1118zZ84crrvuOho2bFjp6/LLL3d8NiMjg969exMYGOg4NnDgQHbt2sXx48cr7XP79u089thjvPHGG06t5CorK+PkyZNERESUOz5x4kQGDx5cbm+cyhQWFjJ//nzatGlDdHR0le1FxDO0z4hFORNGzrq9CjGL9u3bl5vn8a9//Yuffvqp0vY/39ID+1ygNm3alHv/531qcnNzK1ymXVRUxKhRo3jqqado1aoVP/zwQ5U1zpw5k1OnTnHzzTc7ji1evJiNGzeWe7ZSRV588UX++te/UlhYSIcOHVi5cmW58CQi3qUwYlHO/MFqZER+Fh8fX+7nli1burW/KVOm0LFjR/7whz841X7RokVMmzaNDz74gObNmwOwd+9e7r33XlauXFluPlNFRo8eTf/+/Tl48CAzZ87k5ptv5uuvv67ycyLiGbpNY1G6TSOuaNCgQbmfXblNExUVVeG+ND+/V5EvvviCd955h4CAAAICArj22msBaNq0KampqeXaLl68mNtvv52333673K2YzMxMDh06xFVXXeU4z5dffslzzz1HQEBAuQmq4eHhtG/fnt69e/Puu++yc+dO3n///WpcKalrSkpKLjhmGEaFx6X6NDJiUQojUhOu3KZJTEzk4YcfpqSkxHF85cqVdOjQodKddN97771y59+wYQO33XYba9asoW3bto7jb731FrfddhuLFy++YO+aa6+9li1btpQ7lpycTGxsLJMnT8bfv+L/Coxzc7iLiooq/X4iYJ+n1KxZMxYsWFDu+F//+ld27NjBxx9/7KXKrEdhxKIURqQmXLlNc+uttzJt2jTGjx/P5MmT2bp1K88++yzPPPOMo83777/PlClTHPt7nB84AMcy8Y4dO9KoUSPAfmtm3LhxPPvss/To0cPxbKSQkBDCw8MJDQ3liiuuKHeeBg0a0KRJE8fxH374gSVLljBgwACaNWvGvn37mD59OiEhIVx//fWuXRSpc/z8/OjWrRsPP/wwf/vb3wDYt28fzz33HNOmTfNyddai2zQW5WwYMdxdiFheeHg4n332GVlZWcTHx3P//fczdepU7rjjDkeb/Px8du3a5dJ5X3nlFc6ePcvEiRO55JJLHK97773X6XMEBwezZs0arr/+etq1a8fIkSMJDQ3lm2++ccw9EbmY1NRUtmzZwvr16wF49tlnCQ0NZeLEiV6uzFpshmH4/P8fFRQUEB4eTn5+PmFhYd4uxxQGAZXvVfmLszgXXERE6qr+/fvzv//9j5ycHAIDA5k2bRoPPvigt8uyFI2MWJSz9990q0ZE5OJSU1PJyckBoGHDhhoVcQOFEYtydrRDYURE5OKuueYax6MGJk2aRGhoqJcrsp5qhZHZs2cTExNDcHAwPXr0cNxLq8w777xDbGwswcHBXHnllXz66afVKlacpzAiIlJ7Hn/8cVq0aHHBgxildrgcRpYsWUJK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8PPzY926dV6szPcUFxeTmZlJUlKS45ifnx9JSUlkZGRU+JmMjIxy7QEGDhxYaXsrq871E7vauHanT5+mpKSEiIgId5Xps2p6/QzDID09nV27dtG7d293lupzqnvtHnvsMZo3b8748eM9UaZPqu61O3XqFK1btyY6OpqhQ4eybdu2WqvJZ8NIbm4uzZs3L3csICCAiIgIcnNzvVSVbzpy5AilpaVERkaWOx4ZGVnptcrNzXWpvZVV5/qJXW1cu8mTJ9OiRYsLwnFdUN3rl5+fT8OGDQkMDGTw4ME8//zz9O/f393l+pTqXLu1a9fy6quvMnfuXE+U6LOqc+06dOjAvHnz+OCDD1i4cCFlZWX07NmTffv21UpNHg8jDz744AWTYH792rlzp6fLEhEvmD59OosXL+b999+v3clwFhcaGsrmzZvZsGEDf//730lJSWH16tXeLsunnTx5kjFjxjB37lyaNm3q7XJMJzExkbFjxxIXF0efPn1YunQpzZo14+WXX66V8wfUyllccP/99/PHP/7xom0uu+wyoqKiOHToULnjZ8+e5dixY0RFRbmxQvNp2rQp/v7+5OXllTuel5dX6bWKiopyqb2VVef6iV1Nrt3MmTOZPn06n3/+OZ07d3ZnmT6rutfPz8+Pdu3aARAXF8eOHTtIS0ujb9++7izXp7h67fbs2UN2djY33HCD41hZWRlgH3XftWsXbdu2dW/RPqI2/s6rV68eXbt2Zffu3bVSk8dHRpo1a0ZsbOxFX4GBgSQmJnLixAkyMzMdn/3iiy8oKyujR48eni7bpwUGBhIfH096errjWFlZGenp6SQmJlb4mcTExHLtAVauXFlpeyurzvUTu+peuxkzZvD444+zfPnycvPC6pra+t0rKyujqKjIHSX6LFevXWxsLFu2bGHz5s2O15AhQ+jXrx+bN28mOjrak+V7VW383pWWlrJlyxYuueSS2inKbVNja8GgQYOMrl27GuvWrTPWrl1rtG/fvtzS3n379hkdOnQw1q1b5zh28OBBY9OmTcbcuXMNwPjqq6+MTZs2GUePHvXGV/CYxYsXG0FBQcZrr71mbN++3bjjjjuMRo0aOZZejRkzxnjwwQcd7b/++msjICDAmDlzprFjxw4jNTW1zi/tdeX6FRUVGZs2bTI2bdpkXHLJJcYDDzxgbNq0yfj++++99RW8xtVrN336dCMwMNB49913yy0TPHnypLe+gle5ev2efPJJ47PPPjP27NljbN++3Zg5c6YREBBgzJ0711tfwWtcvXa/VpdX07h67aZNm2asWLHC2LNnj5GZmWnccsstRnBwsLFt27Zaqcenw8jRo0eNUaNGGQ0bNjTCwsKM5OTkcn9hZWVlGYCxatUqx7HU1FQDuOA1f/58z38BD3v++eeNVq1aGYGBgUb37t2N//znP473+vTpY4wbN65c+7ffftv4zW9+YwQGBhqXX3658cknn3i4Yt/iyvX7+Xfv168+ffp4vnAf4Mq1a926dYXXLjU11fOF+whXrt/DDz9stGvXzggODjYaN25sJCYmGosXL/ZC1b7B1b/3zleXw4hhuHbt7rvvPkfbyMhI4/rrrzc2btxYa7XYDMMwameMRURERMR1Pru0V0REROoGhRERERHxKoURERER8SqFEREREfEqhRERERHxKoURERER8SqFEREREfEqhRERERHxKoURERER8SqFEREREfEqhRERERHxKoURERER8ar/DybOZ9mJtTpPAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Target position (x,y,z): -0.3 -0.3 0.7\n", + "Angles (base, shoulder, elbow, wrist): [-135.0, 71.7346, 15.5104, 56.2242]\n", + "Robot Angles: [-135.0, -71.7346, 15.5104, -33.7758]\n", + "elbow (x,y): 0.133 0.402\n", + "wrist (x,y): 0.332 0.7\n", + "tool (x,y): 0.424 0.7\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Target position (x,y,z): -0.3 0.4 0.2\n", + "Angles (base, shoulder, elbow, wrist): [126.8699, 74.2826, 109.5002, -35.2176]\n", + "Robot Angles: [126.8699, -74.2826, 109.5002, -125.2176]\n", + "elbow (x,y): 0.115 0.407\n", + "wrist (x,y): 0.408 0.2\n", + "tool (x,y): 0.5 0.2\n" + ] + }, + { + "data": { + "image/png": "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roEzZnCurfHlwMeOHaO4uJjHHnuM4uJi3nnnHbZs2cL111/vEvfLX/6SkydP8sknn7BmzRrS0tL45S9/id1uB6Cqqoq77rqLF154gZkzZzJu3Diqq6u9cswiIiJWcruXk5y9veeey+Ef75g6FS64AK688tS+khLCnnmGt1avJqmRruPR0dEsrSuKfjBz5kx69+7Nrl27OO+88zh06BBbt27lpZdeonv37j987VReeOEFNmzYQEJCAlVVVYSHh5Oeng5As2bNqKqqIiIiwgtHLSIiYh1dofEBl7ubqqvhtdfgtttqp50Ajh2D3/yGK2fNIjEhwaPvKCsrw2azERMTA8A555xD586deeWVV6isrOTkyZP87W9/o3379vTs2ROAtm3bMnr0aBITE0lKSmLcuHG0adPG8wMVERFpIrpC4wMuBc2iRXDkCNx666l9f/wj9OnD2KFDPRr/+PHjPPzww9x0003OZp02m42PP/6YYcOG0aZNG8LCwmjfvj35+fnExsY6P5ubm8t9991HWFiYihkREQkYKmiaWBmw4sc7XnoJhgyBpKTa1++/D598AmvXMtCD8U+cOMGvf/1rDMPgxRdfdO43DIO77rqL9u3b89lnn9GiRQvmzp3Lddddx5dffknij6a1oqOjPTk0ERERn9GUUxMrAGrqXnz7LXz8Mfzud6cCPvkEtm+HmBjimzWjWbPamvNXv/oVAwYMOOPYdcXMt99+y9KlS51XZ2qH/YQPPviABQsWcMUVV9CjRw9eeOEFWrRowfz58716jCIiIk1NV2iamMt00z/+Ae3bw7XXnto3fjz87neMA+78YVe3bt147rnnuO666xoct66Y2bp1K59++innnHOOy/vHjh0DICzMtYYNCwvD4XB4fkAiIiJ+QFdompDBjwoah6O2oBk1Cpr9qK5MSICuXRnZtStdf9gAzjvvPDp27OgM69KlC++++y5QW8zccMMNrF69mtdff52amhrsdjt2u91523VmZiaxsbGMGjWKr776im+++YYHH3yQHTt2cO2PCyoRL1m3bh39+vUjKiqK5ORkpk2b1uhnbDbbaduCBQuaIFsRCXS6QtOENgO76158/DHs2lV7d9NPxAKXNzLWli1bKCsrA2Dv3r28//77AM5brut8+umnDBgwgLi4OPLz83nkkUe4+uqrOXHiBJdeeinvvfceaWlp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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "draw_arm(0.3, 0.3, 0.3)\n", + "draw_arm(-0.3, -0.3, 0.7)\n", + "draw_arm(-0.3, 0.4, 0.2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Interactive Arm" + ] + }, + { + "cell_type": "code", + "execution_count": 160, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5617bc9f737949c4bc84c787ff88ce16", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=0.3, description='x', max=1.0, step=0.01), FloatSlider(value=0.3, desc…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 160, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from ipywidgets import interact, FloatSlider\n", + "\n", + "# Interactive slider for z coordinate\n", + "interact(draw_arm, x=FloatSlider(min=0, max=1, step=0.01, value=0.3),\n", + " y=FloatSlider(min=0, max=1, step=0.01, value=0.3),\n", + " z=FloatSlider(min=0, max=1, step=0.01, value=0.3))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/led_control.py b/led_control.py index 75c7823..fe38f11 100755 --- a/led_control.py +++ b/led_control.py @@ -22,6 +22,13 @@ leds_size = None leds_normalized = None controllers = None data = None +exactdata = None +rings = None +ringstatus = None +mode = "Startup" +firstrun = True +changecount = 0 +animation_time = 0 start = uptime() def ping(host): @@ -48,30 +55,50 @@ def map(): global leds_size global leds_normalized global controllers + global rings + global ringstatus + global animation_time + with open('config.yml', 'r') as fileread: #global config config = yaml.safe_load(fileread) + animation_time = config["animation_time"] leds = list() leds_size = list() controllers = list() + rings = list(range(len(config["position_map"]))) + ringstatus = list(range(len(config["position_map"]))) + #print(rings) #fprint(config["led"]["map"]) + generate_map = False + map = list() for shape in config["led"]["map"]: if shape["type"] == "circle": + + if generate_map: + map.append((shape["pos"][1],shape["pos"][0])) #fprint(shape["pos"]) anglediv = 360.0 / shape["size"] angle = 0 radius = shape["diameter"] / 2 lednum = shape["start"] + for item in config['position_map']: + # Check if the current item's position matches the target position + #print(item['pos'],(shape["pos"][1],shape["pos"][0])) + if tuple(item['pos']) == (shape["pos"][1],shape["pos"][0]): + rings[item["index"]] = (shape["pos"][1],shape["pos"][0],lednum,lednum+shape["size"]) # rings[index] = x, y, startpos, endpos + ringstatus[item["index"]] = [None, None] + break if len(leds) < lednum + shape["size"]: for x in range(lednum + shape["size"] - len(leds)): leds.append(None) leds_size.append(None) while angle < 359.999: tmpangle = angle + shape["angle"] - x = math.cos(tmpangle * (math.pi / 180.0)) * radius + shape["pos"][0] - y = math.sin(tmpangle * (math.pi / 180.0)) * radius + shape["pos"][1] + x = math.cos(tmpangle * (math.pi / 180.0)) * radius + shape["pos"][1] # flip by 90 degress when we changed layout + y = math.sin(tmpangle * (math.pi / 180.0)) * radius + shape["pos"][0] leds[lednum] = (x,y) lednum = lednum + 1 angle = angle + anglediv @@ -97,7 +124,23 @@ def map(): dist += distdiv lednum = lednum + 1 + if generate_map: + map = sorted(map, key=lambda x: (-x[1], x[0])) + print(map) + import matplotlib.pyplot as plt + plt.axis('equal') + x, y = zip(*map) + plt.scatter(x, y, s=12) + #plt.plot(x, y, marker='o') + #plt.scatter(*zip(*leds), s=3) + for i, (x_pos, y_pos) in enumerate(map): + plt.text(x_pos, y_pos, str(i), color="red", fontsize=12) + plt.savefig("map2.png", dpi=600, bbox_inches="tight") + data = {"map": [{"index": i, "pos": str(list(pos))} for i, pos in enumerate(map)]} + yaml_str = yaml.dump(data, default_flow_style=False) + print(yaml_str) + print(rings) flag = 0 for x in leds: if x is None: @@ -148,9 +191,10 @@ def init(): global leds_size global controllers global data + global exactdata sender = sacn.sACNsender(fps=config["led"]["fps"], universeDiscovery=False) sender.start() # start the sending thread - for x in range(len(controllers)): + """for x in range(len(controllers)): print("Waiting for the controller at", controllers[x][2], "to be online...", end="") count = 0 while not ping(controllers[x][2]): @@ -159,7 +203,7 @@ def init(): fprint(" ERROR: controller still offline after " + str(count) + " seconds, continuing...") break if count < config["led"]["timeout"]: - fprint(" done") + fprint(" done")""" for x in range(len(controllers)): print("Activating controller", x, "at", controllers[x][2], "with", controllers[x][1]-controllers[x][0], "LEDs.") sender.activate_output(x+1) # start sending out data @@ -168,22 +212,26 @@ def init(): # initialize global pixel data list data = list() + exactdata = list() for x in range(len(leds)): if leds_size[x] == 3: + exactdata.append(None) data.append((20,20,127)) elif leds_size[x] == 4: + exactdata.append(None) data.append((50,50,255,0)) else: + exactdata.append(None) data.append((0,0,0)) sendall(data) #time.sleep(50000) fprint("Running start-up test sequence...") - for y in range(100): + for y in range(1): for x in range(len(leds)): - setpixel(0,0,150,x) + setpixel(0,60,144,x) sendall(data) #time.sleep(2) - #alloffsmooth() + alloffsmooth() def sendall(datain): # send all LED data to all controllers @@ -260,6 +308,209 @@ def setpixelnow(r, g, b, num): setpixel(r,g,b,num) senduniverse(data, num) +def setmode(stmode, r=0,g=0,b=0): + global mode + global firstrun + if stmode is not None: + if mode != stmode: + firstrun = True + + mode = stmode + + +def setring(r,g,b,idx): + + ring = rings[idx] + for pixel in range(ring[2],ring[3]): + setpixel(r,g,b,pixel) + #global data + #senduniverse(data, ring[2]) + +def runmodes(ring = -1, speed = 1): + global mode + global firstrun + global changecount + fprint("Mode: " + str(mode)) + if mode == "Startup": + # loading animation. cable check + if firstrun: + changecount = animation_time * 3 + firstrun = False + for x in range(len(ringstatus)): + ringstatus[x] = [True, animation_time] + + if changecount > 0: + fprint(changecount) + changecount = fadeorder(0,len(leds), changecount, 0,50,100) + else: + setmode("Startup2") + + + elif mode == "Startup2": + if firstrun: + firstrun = False + + else: + for x in range(len(ringstatus)): + if ringstatus[x][0]: + setring(0, 50, 100, x) + else: + ringstatus[x][1] = fadeall(rings[x][2],rings[x][3], ringstatus[x][1], 100,0,0) # not ready + + elif mode == "StartupCheck": + if firstrun: + firstrun = False + for x in range(len(ringstatus)): + ringstatus[x] = [False, animation_time] + else: + for x in range(len(ringstatus)): + if ringstatus[x][0]: + ringstatus[x][1] = fadeall(rings[x][2],rings[x][3], ringstatus[x][1], 0,50,100) # ready + else: + setring(100, 0, 0, x) + + elif mode == "GrabA": + if firstrun: + firstrun = False + changecount = animation_time # 100hz + if changecount > 0: + changecount = fadeall(rings[ring][2],rings[ring][3], changecount, 100,0,0) + else: + setring(100,0,0,ring) + setmode("GrabB") + elif mode == "GrabB": + if firstrun: + firstrun = False + changecount = animation_time # 100hz + if changecount > 0: + changecount = fadeorder(rings[ring][2],rings[ring][3], changecount, 0,100,0) + else: + setring(0,100,0,ring) + setmode("idle") + elif mode == "GrabC": + if firstrun: + firstrun = False + changecount = animation_time # 100hz + if changecount > 0: + changecount = fadeall(rings[ring][2],rings[ring][3], changecount, 0,50,100) + else: + setring(0,50,100,ring) + setmode("idle") + elif mode == "idle": + time.sleep(0) + + sendall(data) + +def fadeall(idxa,idxb,sizerem,r,g,b): + if sizerem < 1: + return 0 + global exactdata + sum = 0 + for x in range(idxa,idxb): + if exactdata[x] is None: + exactdata[x] = data[x] + old = exactdata[x] + dr = (r - old[0])/sizerem + sum += abs(dr) + dr += old[0] + dg = (g - old[1])/sizerem + sum += abs(dg) + dg += old[1] + db = (b - old[2])/sizerem + db += old[2] + sum += abs(db) + exactdata[x] = (dr, dg, db) + #print(new) + setpixel(dr, dg, db, x) + if sizerem == 1: + exactdata[x] = None + if sum == 0 and sizerem > 2: + sizerem = 2 + return sizerem - 1 + +def fadeorder(idxa,idxb,sizerem,r,g,b): + if sizerem < 1: + return 0 + global exactdata + drs = 0 + dgs = 0 + dbs = 0 + sum = 0 + for x in range(idxa,idxb): + if exactdata[x] is None: + exactdata[x] = data[x] + old = exactdata[x] + dr = (r - old[0]) + dg = (g - old[1]) + db = (b - old[2]) + drs += dr + dgs += dg + dbs += db + + drs /= sizerem + dgs /= sizerem + dbs /= sizerem + sum += abs(drs) + abs(dgs) + abs(dbs) + print(drs,dgs,dbs) + for x in range(idxa,idxb): + old = exactdata[x] + new = list(old) + if drs > 0: + if old[0] + drs > r: + new[0] = r + drs -= r - old[0] + else: + new[0] = old[0] + drs + drs = 0 + if dgs > 0: + if old[1] + dgs > g: + new[1] = g + dgs -= g - old[1] + else: + new[1] = old[1] + dgs + dgs = 0 + if dbs > 0: + if old[2] + dbs > b: + new[2] = b + dbs -= b - old[2] + else: + new[2] = old[2] + dbs + dbs = 0 + + if drs < 0: + if old[0] + drs < r: + new[0] = r + drs -= r - old[0] + else: + new[0] = old[0] + drs + drs = 0 + if dgs < 0: + if old[1] + dgs < g: + new[1] = g + dgs -= g - old[1] + else: + new[1] = old[1] + dgs + dgs = 0 + if dbs < 0: + if old[2] + dbs < b: + new[2] = b + dbs -= b - old[2] + else: + new[2] = old[2] + dbs + dbs = 0 + + if drs != 0 or dgs != 0 or dbs != 0: + exactdata[x] = new + setpixel(new[0],new[1],new[2],x) + + if sizerem == 1: + exactdata[x] = None + + if sum == 0 and sizerem > 2: + sizerem = 2 + return sizerem - 1 + + def setpixel(r, g, b, num): global data global leds_size @@ -328,16 +579,105 @@ def mapimage(image, fps=90): global data fastsendall(data) +def mainloop(stmode, ring = -1, fps = 100, preview = False): + global start + while uptime() - start < 1/fps: + time.sleep(0.00001) + fprint(1 / (uptime() - start)) + start = uptime() + if mode is not None: + setmode(stmode) + runmodes(ring) + if preview: + drawdata() + +def drawdata(): + #tmp = list() + #for x in len(leds): + # led = leds[x] + # tmp.append((led[0], led[1], data[x])) + + x = [led[0] for led in leds] + y = [led[1] for led in leds] + colors = data + colors_normalized = [(x[0]/255, x[1]/255, x[2]/255) for x in colors] + # Plot the points + plt.scatter(x, y, c=colors_normalized) + + # Optional: add grid, title, and labels + plt.grid(True) + plt.title('Colored Points') + plt.xlabel('X') + plt.ylabel('Y') + plt.show() + plt.savefig("map3.png", dpi=50, bbox_inches="tight") + plt.clf() + +def startup_animation(show): + + stmode = "Startup" + mainloop(stmode, preview=show) + while mode == "Startup": + mainloop(None, preview=show) + for x in range(54): + ringstatus[x][0] = False + mainloop(None, preview=show) + + for x in range(animation_time): + mainloop(None, preview=show) + clear_animations() + stmode = "StartupCheck" + mainloop(stmode, preview=show) + clear_animations() + +def clear_animations(): + for x in range(len(leds)): + exactdata[x] = None + +def do_animation(stmode, ring=-1): + mainloop(stmode, ring, preview=show) + wait_for_animation(ring) + +def start_animation(stmode, ring=-1): + mainloop(stmode, ring, preview=show) + +def wait_for_animation(ring=-1): + while mode != "idle": + mainloop(None, ring, preview=show) + if __name__ == "__main__": init() - cap = cv2.VideoCapture('output.mp4') + import matplotlib.pyplot as plt + """cap = cv2.VideoCapture('badapple.mp4') while cap.isOpened(): ret, frame = cap.read() if not ret: break - mapimage(frame) + mapimage(frame, fps=30)""" + show = True + ring = 1 + startup_animation(show) + for x in range(54): + ringstatus[x][0] = True + mainloop(None, preview=show) + for x in range(animation_time): + mainloop(None, preview=show) + + do_animation("GrabA", 1) - time.sleep(1) + do_animation("GrabA", 5) + start_animation("GrabC", 1) + + wait_for_animation(1) + do_animation("GrabC", 5) + close() - #sys.exit(0) \ No newline at end of file + #sys.exit(0) + + + # blue : default + # green : target + # yellow : crosshair + # red : missing + # uninitialized : red/purple? \ No newline at end of file diff --git a/map.png b/map.png index 52f3cba..f10b1b1 100644 Binary files a/map.png and b/map.png differ diff --git a/map2.png b/map2.png new file mode 100644 index 0000000..e794e8f Binary files /dev/null and b/map2.png differ diff --git a/map3.png b/map3.png new file mode 100644 index 0000000..de82083 Binary files /dev/null and b/map3.png differ diff --git a/read_datasheet.py b/read_datasheet.py index 0517608..c928f1c 100755 --- a/read_datasheet.py +++ b/read_datasheet.py @@ -2,6 +2,8 @@ # Parse Belden catalog techdata datasheets +import pandas as pd +pd.set_option('future.no_silent_downcasting', True) from PyPDF2 import PdfReader import camelot import numpy as np @@ -11,6 +13,11 @@ import json from util import fprint import uuid from util import run_cmd +import os + +def touch(path): + with open(path, 'a'): + os.utime(path, None) def parse(filename, output_dir, partnum, dstype): @@ -23,6 +30,7 @@ def parse(filename, output_dir, partnum, dstype): reader = PdfReader(filename) page = reader.pages[0] table_list = {} + for table in tables: table.df.infer_objects(copy=False) table.df.replace('', np.nan, inplace=True) @@ -90,6 +98,7 @@ def parse(filename, output_dir, partnum, dstype): # Table parsing and reordring tables = dict() + torename = dict() previous_table = "" for table_name in table_list.keys(): # determine shape: horizontal or vertical @@ -121,7 +130,8 @@ def parse(filename, output_dir, partnum, dstype): for table_name_2 in table_list.keys(): if table_name_2.find(table.iloc[-1, 0]) >= 0: # Name taken from table directly above - this table does not have a name - table_list["Specs " + str(len(tables))] = table_list.pop(table_name_2, None) # rename table to arbitrary altername name + torename[table_name_2] = "Specs " + str(len(tables)) + #table_list["Specs " + str(len(tables))] = table_list[table_name_2] # rename table to arbitrary altername name break if vertical: @@ -142,21 +152,21 @@ def parse(filename, output_dir, partnum, dstype): # multi-page table check if dstype == "Belden": if table_name.isdigit() and len(tables) > 1: - fprint(table_name) - fprint(previous_table) + #fprint(table_name) + #fprint(previous_table) main_key = previous_table cont_key = table_name - fprint(tables) + #fprint(tables) if vertical == False: main_keys = list(tables[main_key].keys()) for i, (cont_key, cont_values) in enumerate(tables[cont_key].items()): if i < len(main_keys): - fprint(tables[main_key][main_keys[i]]) - tables[main_key][main_keys[i]] = (tables[main_key][main_keys[i]] + (cont_key,) + cont_values) + #fprint(tables[main_key][main_keys[i]]) + tables[main_key][main_keys[i]] = (tuple(tables[main_key][main_keys[i]]) + (cont_key,) + cont_values) del tables[table_name] @@ -167,6 +177,10 @@ def parse(filename, output_dir, partnum, dstype): previous_table = table_name + # remove renamed tables + for table_name in torename.keys(): + tables[torename[table_name]] = tables[table_name] + del tables[table_name] # remove multi-line values that occasionally squeak through def replace_newlines_in_dict(d): for key, value in d.items(): @@ -195,12 +209,12 @@ def parse(filename, output_dir, partnum, dstype): - print(output_table) + #print(output_table) - run_cmd("rm " + output_dir + "/*.json") # not reliable! + run_cmd("rm \"" + output_dir + "\"/*.json") # not reliable! with open(output_dir + "/" + output_table["searchspecs"]["id"] + ".json", 'w') as json_file: json.dump(output_table["searchspecs"], json_file) - + touch(output_dir + "/parsed") return output_table @@ -217,7 +231,7 @@ def flatten(tables): # If it fails again, return the original string. return s out = dict() - print("{") + #print("{") for table in tables.keys(): for key in tables[table].keys(): if len(key) < 64: @@ -228,11 +242,16 @@ def flatten(tables): fullkeyname = (table + ": " + keyname).replace(".","") if type(tables[table][key]) is not tuple: out[fullkeyname] = convert_to_number(tables[table][key]) - print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",") + #print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",") elif len(tables[table][key]) == 1: out[fullkeyname] = convert_to_number(tables[table][key][0]) - print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",") + #print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",") + + # if the item has at least two commas in it, split it + if tables[table][key].count(',') > 0: + out[fullkeyname] = list(map(lambda x: x.strip(), tables[table][key].split(","))) + #print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",") # if the item has at least two commas in it, split it if tables[table][key].count(',') > 0: @@ -240,7 +259,7 @@ def flatten(tables): print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",") - print("}") + #print("}") return out diff --git a/requirements.txt b/requirements.txt index 2ab841f..bbe8914 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,9 @@ selenium sacn uptime websockets +numpy +scipy +ipywidgets # Development matplotlib diff --git a/run.py b/run.py index e2bedef..d00a621 100755 --- a/run.py +++ b/run.py @@ -89,6 +89,9 @@ def start_server_socket(): while True: #print("HI") + + # Handeling Server Requests Loop, will run forever + if not from_server_queue.empty(): client_id, message = from_server_queue.get() fprint(f"Message from client {client_id}: {message}") @@ -113,7 +116,7 @@ def start_server_socket(): case "log": fprint("log message") if call == "send": - fprint("webapp: " + data) + fprint("webapp: " + str(data), sendqueue=to_server_queue) elif call == "request": fprint("") @@ -124,6 +127,21 @@ def start_server_socket(): elif call == "request": fprint("") + + case "ping": + fprint("Pong!!!") + + # Lucas' notes + # Add a ping pong :) response/handler + # Add a get cable response/handler + # this will tell the robot arm to move + # Call for turning off everything + + # TODO Helper for converting Python Dictionaries to JSON + # make function: pythonData --> { { "type": "...", "call": "...", "data": pythonData } } + + # to send: to_server_queue.put(("*", "JSON STRING HERE")) # replace * with UUID of client to send to one specific location + case "cable_details": fprint("cable_details message") if call == "send": @@ -235,7 +253,7 @@ def setup_server(pool): if camera_ready is False: fprint("waiting for " + "Camera initilization" + " to complete...", sendqueue=to_server_queue) - camera = process_video.qr_reader(config["cameras"]["banner"]["ip"], config["cameras"]["banner"]["port"]) + # camera = process_video.qr_reader(config["cameras"]["banner"]["ip"], config["cameras"]["banner"]["port"]) fprint("Camera initialized.", sendqueue=to_server_queue) @@ -262,8 +280,8 @@ def mainloop_server(pool): killall() counter = counter + 1 - fprint("Looking for QR code...") - print(camera.read_qr(30)) + # fprint("Looking for QR code...") + # print(camera.read_qr(30)) def run_loading_app(): diff --git a/source.fish b/source.fish new file mode 100644 index 0000000..6b99f46 --- /dev/null +++ b/source.fish @@ -0,0 +1 @@ +source venv/bin/activate.fish diff --git a/source.sh b/source.sh new file mode 100644 index 0000000..619fe07 --- /dev/null +++ b/source.sh @@ -0,0 +1 @@ +source venv/bin/activate diff --git a/test.py b/test.py new file mode 100644 index 0000000..8762c31 --- /dev/null +++ b/test.py @@ -0,0 +1,106 @@ +print("\u001b[37m") + +class Ring: + def __init__(self) -> None: + self.leds = [0] * 24 + self.id = 0 + self.dirty = False + + def __iter__(self) -> iter: + yield from self.leds + + def __repr__(self) -> str: + return f"Ring" + + def __add__(self, other): + self.leds.extend(other) + return self + + def __bool__(self): + return self.dirty + + def __getitem__(self, index): + return self.leds[index] + + def __setitem__(self, index, value): + ivalue = self.leds[index] + if ivalue != value: + self.dirty = True + self.leds[index] = value + + def __getattr__(self, name): + import word2num + name = int(word2num.word2num(name)) + print(name) + if 0 <= name < len(self.leds): + return self.leds[name] + + + +a = Ring() +print(a) +b = Ring() +b.leds[2] = 3 + +print(a + b) + +b.active = True + +if b: + print("Bexist") + +c = [a, b, b, a, a] + +d = list(filter(lambda x: bool(x), c)) + +print(d) + +for i, ring in enumerate(c): + ring[0] = i + print(ring) + +print(a, b) + +print(f"\u001b[32m{a}") +print(f"\u001b[37ma") + +print(getattr(a, "twenty two")) + +# eval(f"getattr(a,\"{input()}\")") + + +# a = r"wow this string is cursed; for example \n" + + +# SEARCHDATA=r"""{ "q": "{QUERY}", "sortCriteria": "relevancy", "numberOfResults": "250", "sortCriteria": "@catalogitemwebdisplaypriority ascending", "searchHub": "products-only-search", "pipeline": "Site Search", "maximumAge": "900000", "tab": "products-search", "locale": "en", "aq": "(NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B)) ((@syssource==\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\" @catalogitemprimarycategorypublished==true)) ((@catalogitemregionavailable=Global) (@z95xlanguage==en))", "cq": "((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\"Coveo_web_index - rg-nc-prod-sitecore-prod\")) OR (@source==(\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\",\"website_001002_Category_index-rg-nc-prod-sitecore-prod\"))", "firstResult": "0" }, "categoryFacets": "[{\"field\":\"@catalogitemcategories\",\"path\":[],\"injectionDepth\":1000,\"maximumNumberOfValues\":6,\"delimitingCharacter\":\"|\"}]", "facetOptions": "{}", "groupBy": " [{\"field\":\"@contenttype\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[\"Products\"],\"queryOverride\":\"{QUERY}\",\"advancedQueryOverride\":\"(NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B)) ((((((((@z95xpath=3324AF2D58F64C0FB725521052F679D2 @z95xid<>3324AF2D58F64C0FB725521052F679D2) ((@z95xpath=C292F3A37B3A4E6BAB345DF87ADDE516 @z95xid<>C292F3A37B3A4E6BAB345DF87ADDE516) @z95xtemplate==E4EFEB787BDC4B1A908EFC64D56CB2A4)) OR ((@z95xpath=723501A864754FEEB8AE377E4C710271 @z95xid<>723501A864754FEEB8AE377E4C710271) ((@z95xpath=600114EAB0E5407A84AAA9F0985B6575 @z95xid<>600114EAB0E5407A84AAA9F0985B6575) @z95xtemplate==2BE4FD6B3B2C49EBBD9E1F6C92238B05))) OR (@syssource==\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\" @catalogitemprimarycategorypublished==true)) OR ((@z95xpath=3324AF2D58F64C0FB725521052F679D2 @z95xid<>3324AF2D58F64C0FB725521052F679D2) @z95xpath<>C292F3A37B3A4E6BAB345DF87ADDE516)) OR @syssource==\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\") NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B))) ((@catalogitemregionavailable=Global) (@z95xlanguage==en) OR (@contenttype=(Blogs,Resources,Other)) (NOT @ez120xcludefromcoveo==1))\",\"constantQueryOverride\":\"((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\\"Coveo_web_index - rg-nc-prod-sitecore-prod\\")) OR (@source==(\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\",\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\"))\"},{\"field\":\"@catalogitembrand\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@catalogitemenvironment\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@catalogitemregionalavailability\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@prez45xtez120xt\",\"maximumNumberOfValues\":5,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@tags\",\"maximumNumberOfValues\":4,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetassettype\",\"maximumNumberOfValues\":3,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetbrand\",\"maximumNumberOfValues\":3,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetmarket\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetsolution\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetsearchcontentpagetype\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]}]" }""" + +# QUERY = "AAAAAAAAAAAA" + +# b = SEARCHDATA.replace(r"{QUERY}", QUERY) + +q = [i * 2 for i in range(10)] + +d = {a : b for a,b in enumerate(q)} + +print(q) + +print(d) + +def stalin_sort(a): + b = sum(a) + b /= len(a) + return [b for _ in range(len(a))] + +def mao_sort(a): + i = 0 + while i < len(a) - 1: + if a[i+1] < a[i]: + del a[i] + else: + i += 1 + return a + +print(stalin_sort(list(range(10)))) +print(mao_sort([1, 3, 2, 4, 5, 8, 7, 6, 9])) + +# i l \ No newline at end of file diff --git a/ur5_control.py b/ur5_control.py index 2030e60..724e500 100755 --- a/ur5_control.py +++ b/ur5_control.py @@ -1,6 +1,8 @@ import urx import math3d as m3d +from scipy.optimize import fsolve import math +import numpy as np import time import os import logging @@ -8,6 +10,9 @@ from urx.robotiq_two_finger_gripper import Robotiq_Two_Finger_Gripper import sys from util import fprint + + + rob = None @@ -45,7 +50,7 @@ def init(ip): time.sleep(0.2) fprint("UR5 ready.") -def set_pos_abs(x, y, z, xb, yb, zb): +def set_pos_abs(x, y, z, xb, yb, zb, threshold=None): global rob new_orientation = m3d.Transform() new_orientation.orient.rotate_xb(xb) # Replace rx with the desired rotation around X-axis @@ -60,7 +65,7 @@ def set_pos_abs(x, y, z, xb, yb, zb): new_trans.pos.y = y new_trans.pos.z = z #rob.speedj(0.2, 0.5, 99999) - rob.set_pose(new_trans, acc=2, vel=2, command="movej") # apply the new pose + rob.set_pose(new_trans, acc=2, vel=2, command="movej", threshold=threshold) # apply the new pose def set_pos_rel_rot_abs(x, y, z, xb, yb, zb): global rob @@ -80,21 +85,241 @@ def set_pos_rel_rot_abs(x, y, z, xb, yb, zb): #rob.speedj(0.2, 0.5, 99999) rob.set_pose(new_trans, acc=0.1, vel=0.4, command="movej") # apply the new pose +def set_pos_abs_rot_rel(x, y, z, xb, yb, zb): + global rob + new_orientation = m3d.Transform() + new_orientation.orient.rotate_xb(xb) # Replace rx with the desired rotation around X-axis + new_orientation.orient.rotate_yb(yb) # Replace ry with the desired rotation around Y-axis + new_orientation.orient.rotate_zb(zb) # Replace rz with the desired rotation around Z-axis + + # Get the current pose + trans = rob.getl() + + # Apply the new orientation while keeping the current position + new_trans = m3d.Transform(new_orientation.orient, m3d.Vector(trans[0:3])) + new_trans.pos.x = x + new_trans.pos.y = y + new_trans.pos.z = z + #rob.speedj(0.2, 0.5, 99999) + rob.set_pose(new_trans, acc=0.1, vel=0.4, command="movej") # apply the new pose +def is_safe_move(start_pose, end_pose, r=0.25): + start_x, start_y = (start_pose[0], start_pose[1]) + end_x, end_y = (end_pose[0], end_pose[1]) + + try: + m = (end_y-start_y)/(end_x-start_x) + b = start_y - m*start_x + # print('m = y/x =', m) + # print('b =', b) + except: + m = (end_x-start_x)/(end_y-start_y) + b = start_x - m*start_y + # print('m = x/y =', m) + # print('b =', b) + + return r**2 - b**2 + m**2 * r**2 < 0 + +def cartesian_to_polar(x, y): + r = np.sqrt(x**2 + y**2) + theta = np.arctan2(y, x) + return r, theta + +def polar_to_cartesian(r, theta): + x = r * np.cos(theta) + y = r * np.sin(theta) + return x, y + + +def move_to_polar(start_pos, end_pos): + global rob + + # Convert to polar coordinates + start_r, start_theta = cartesian_to_polar(start_pos[0], start_pos[1]) + end_r, end_theta = cartesian_to_polar(end_pos[0], end_pos[1]) + + # Interpolate for xy (spiral arc) + n_points = 30 + r_intermediate = np.linspace(start_r, end_r, n_points) + theta_intermediate = np.linspace(start_theta, end_theta, n_points) + + # Interpolate for z (height) + start_z = start_pos[2] + end_z = end_pos[2] + + z_intermediate = np.linspace(start_z, end_z, n_points) + + # Interpolate for rz (keep tool rotation fixed relative to robot) + + + curr_rot = rob.getl() + + theta_delta = theta_intermediate[1]-theta_intermediate[0] + + rx_intermediate = [curr_rot[5] + theta_delta*i for i in range(n_points)] + + # curr_rot = rob.getj() + # start_rz = curr_rot[5] + # rot = end_theta - start_theta + # end_base_joint = curr_rot[0]-start_theta + rot + + # end_rz = curr_rot[0] + rot + # # rob.movel([*polar_to_cartesian(end_r, end_theta), *rob.getl()[2:]], acc=2, vel=2) + + # print('start_theta = ', math.degrees(start_theta)) + # print('end_theta = ', math.degrees(curr_rot[0]-start_theta+rot)) + # print('start_rz =', math.degrees(start_rz)) + # print('rot =', math.degrees(rot)) + # print('end_rz =', math.degrees(end_rz)) + + # rz_intermediate = np.linspace(start_rz, end_rz, n_points) + + # Convert back to cartesian coordinates + curr_pos = rob.getl() + + intermediate_points = [[*polar_to_cartesian(r, theta), z, *curr_pos[3:]] + for r, theta, z, rx in zip(r_intermediate, + theta_intermediate, + z_intermediate, + rx_intermediate)] + + # Move robot + rob.movels(intermediate_points, acc=2, vel=2, radius=0.1) + + + return rx_intermediate + +def move_to_home(): + global rob + + # Home position in degrees + home_pos = [0.10421807948612624, + -2.206111555015423, + 1.710679229503537, + -1.075834511928354, + -1.569301366430687, + 1.675098295930943] + + # Move robot + rob.movej(home_pos, acc=2, vel=2) + +def normalize_degree(theta): + # Normalizes degree theta from -1.5pi to 1.5pi + multiplier = 1 + normalized_theta = theta % (math.pi * multiplier) + + # Maintain the negative sign if the original angle is negative + if theta < 0: + normalized_theta -= math.pi * multiplier + + # Return angle + return normalized_theta + +def get_joints_from_xyz_rel(x, y, z, initial_guess = (math.pi/2, math.pi/2, 0), limbs=(.422864, .359041, .092124)): + # Get polar coordinates of x,y pair + r, theta = cartesian_to_polar(x, y) + + # Get length of each limb + l1, l2, l3 = limbs + + # Formulas to find out joint positions for (r, z) + def inv_kin_r_z(p): + a, b, c = p + + return (l1*math.cos(a) + l2*math.cos(a-b) + l3*math.cos(a-b-c) - r, # r + l1*math.sin(a) + l2*math.sin(a-b) - l3*math.sin(a-b-c) - z, # z + a-b-c) # wrist angle + + + # Normalize angles + base, shoulder, elbow, wrist = [normalize_degree(deg) for deg in [theta, *fsolve(inv_kin_r_z, initial_guess)]] + + # Return result + return base, shoulder, elbow, wrist + +def get_joints_from_xyz_abs(x, y, z): + joints = get_joints_from_xyz_rel(x, y, z) + + # Joint offsets + # Base, Shoulder, Elbow, Wrist + inverse = [1, -1, 1, 1] + offsets = [0, 0, 0, -math.pi/2] + + # Return adjusted joint positions + return [o+j*i for j, o, i in zip(joints, offsets, inverse)] if __name__ == "__main__": #rob.movej((0, 0, 0, 0, 0, 0), 0.1, 0.2) #rob.movel((x, y, z, rx, ry, rz), a, v) init("192.168.1.145") - fprint("Current tool pose is: ", rob.getl()) - #set_pos_rel_rot_abs(0, 0, -0.2, math.pi, 0, -math.pi) - set_pos_abs(0.3, -0.2, 0.5, math.pi, 0, -math.pi) - set_pos_abs(0, 0.2, 0.6, math.pi, 0, -math.pi) - set_pos_abs(-0.5, -0.2, 0.4, math.pi, 0, -math.pi) - #set_pos_rel_rot_abs(0, 0, 0, math.pi, 0, -math.pi) - fprint("Current tool pose is: ", rob.getl()) + print("Current tool pose is: ", rob.getl()) + move_to_home() + + home_pose = [-0.4999999077032916, + -0.2000072960336574, + 0.40002172976662786, + 0, + -3.14152741295329, + 0] + + # time.sleep(.5) + + p1 = [0, + + 0.6, + .4, + 0.2226, + 3.1126, + 0.0510] + + p2 = [0.171, + -0.115, + 0.2, + 0.2226, + 3.1126, + 0.0510] + + curr_pos = rob.getl() + # up/down, + # tool rotation + # tool angle (shouldn't need) + # rob.set_pos(p1[0:3], acc=0.5, vel=0.5) + + # set_pos_abs(*home_pose) + + + angles = get_joints_from_xyz_abs(0.3, 0.3, 0.3) + rob.movej([*angles, *rob.getj()[4:]], acc=1, vel=1) + + angles = get_joints_from_xyz_abs(-0.3, -0.3, 0.7) + rob.movej([*angles, *rob.getj()[4:]], acc=1, vel=1) + + angles = get_joints_from_xyz_abs(-0.3, 0.4, 0.2) + rob.movej([*angles, *rob.getj()[4:]], acc=1, vel=1) + + + # set_pos_abs(*p1) + # move = move_to_polar(p1, p2) + + + # for p in move: + # print(math.degrees(p)) + # print("Safe? :", is_safe_move(p1, p2)) + + + # #set_pos_rel_rot_abs(0, 0, -0.2, math.pi, 0, -math.pi) + # set_pos_abs(0.3, -0.2, 0.5, math.pi, 0, -math.pi) + # set_pos_abs(0, 0.2, 0.6, math.pi, 0, -math.pi) + # set_pos_abs(-0.5, -0.2, 0.4, math.pi, 0, -math.pi) + # #set_pos_rel_rot_abs(0, 0, 0, math.pi, 0, -math.pi) + + # print("Current tool pose is: ", rob.getl()) + # print("getj(): ", rob.getj()) + + # move_to_home() + rob.stop() os.kill(os.getpid(), 9) # dirty kill of self sys.exit(0) diff --git a/venv-setup.sh b/venv-setup.sh index ad297b7..1232298 100755 --- a/venv-setup.sh +++ b/venv-setup.sh @@ -1,6 +1,7 @@ #!/bin/sh +# change this to #!/bin/bash for windows -python -m venv ./venv +python3 -m venv ./venv source ./venv/bin/activate pip install --upgrade pip \ No newline at end of file diff --git a/websocket_test.html b/websocket_test.html index 4cdca7a..c6489bc 100644 --- a/websocket_test.html +++ b/websocket_test.html @@ -1,15 +1,61 @@ + WebSocket Test + + + + +

WebSocket Test

- + +

Example JSON

+

{ "type": "cable_map", "call": "request", "data": { } }

+

{ "type": "log", "call": "send", "data": "123123" }

+ +

Messages/Logs

    + +
    + +
    + +
    + + + - + + \ No newline at end of file