Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • jllemur813/roco_electrical
1 result
Show changes
Commits on Source (53)
Showing
with 20653 additions and 8002 deletions
**test
\ No newline at end of file
*.erc
*.log
**/libraries
**/research_log
\ No newline at end of file
%% Cell type:code id: tags:
``` python
ses=open('paperbot_ee.ses','r').read().splitlines()
```
%% Cell type:code id: tags:
``` python
pathsec_list=[]
for i in range(len(ses)):
if 'path' in ses[i]:
for j in range(i,len(ses)):
if ')' in ses[j]:
pathsec=[i,j]
break
```
%% Output
25
31
36
43
%% Cell type:code id: tags:
``` python
```
File added
File added
File added
File added
File added
File added
File added
This diff is collapsed.
This diff is collapsed.
File added
(session paperbot_ee.ses
(base_design paperbot_ee.dsn)
(placement
(resolution um 10)
(component J1
(place J1 1150000 910000 front 270)
)
(component U1
(place U1 1040000 461000 front 90)
)
(component T1
(place T1 870000 900000 front 0)
)
(component L1
(place L1 670000 410000 front 90)
)
(component R1
(place R1 720000 410000 front 270)
)
)
(was_is
)
(routes
(resolution um 10)
(parser
(host_cad "KiCad's Pcbnew")
(host_version "5.1.3-ffb9f22~84~ubuntu18.04.1")
)
(library_out
)
(network_out
(net 3v3
(wire
(path F.Cu 20000
1208927 769036
1208927 581620
)
)
(wire
(path F.Cu 20000
1251600 929529
1208927 886856
1208927 769036
)
)
(wire
(path F.Cu 20000
1208927 769036
889067 769036
889067 769037
)
)
(wire
(path F.Cu 15238
870000 798400
870000 788103
889067 769037
)
)
(wire
(path F.Cu 15238
1208927 581620
1205100 577793
1205100 575300
)
)
(wire
(path F.Cu 15238
1251600 973500
1251600 929529
)
)
)
(net GND
(wire
(path F.Cu 20000
897207 826443
824970 826443
670000 671473
670000 435400
)
)
(wire
(path F.Cu 20000
897207 826443
1104527 826443
1226200 948116
)
)
(wire
(path F.Cu 20000
897207 816057
897207 826443
)
)
(wire
(path F.Cu 15238
895400 798400
897207 800207
897207 816057
)
)
(wire
(path F.Cu 15238
1226200 948116
1226200 973500
)
)
(wire
(path F.Cu 20000
670000 435400
668555 435400
642308 409153
642308 385925
671949 356284
808268 356284
1001900 549916
)
)
(wire
(path F.Cu 15238
1001900 549916
1001900 575300
)
)
)
(net NET1
(wire
(path F.Cu 20000
837017 790817
825375 790817
720000 685442
720000 422700
)
)
(wire
(path F.Cu 15238
844600 798400
837017 790817
)
)
)
(net NET2
(wire
(path F.Cu 20000
720000 384600
682700 384600
670000 397300
)
)
)
(net SCL
(wire
(path F.Cu 20000
1200800 988622
1213751 1001573
1262669 1001573
1279703 984539
1279703 610226
1216911 547434
1055182 547434
1052700 549916
)
)
(wire
(path F.Cu 15238
1052700 549916
1052700 575300
)
)
(wire
(path F.Cu 15238
1200800 988622
1200800 973500
)
)
)
(net SDA
(wire
(path F.Cu 20000
1027300 549505
1027300 539917
1045031 522186
1227572 522186
1304811 599425
1304811 995273
1272940 1027144
1203241 1027144
1175400 999303
1175400 990546
)
)
(wire
(path F.Cu 15238
1175400 973500
1175400 990546
)
)
(wire
(path F.Cu 15238
1027300 575300
1027300 549505
)
)
)
)
)
)
\ No newline at end of file
#!/usr/bin/env python3
import numpy as np
import ezdxf
import random
from math import sqrt
import copy
class read_old_paperbot():
def __init__(self,dxf_file):
self.dwg=ezdxf.readfile(dxf_file)
self.msp=self.dwg.modelspace()
self.create_layer('Cut',5)
self.create_layer('Fold',4)
# self.create_layer('Pin_temp',6)
self.create_layer('Circuit_Cut',6)
self.create_layer('Circuit_Etc',8)
self.layer_rearrange() ## reagrrange cut and fold lines to corresponding layers
self.remove_wheels() ##remove wheel drawings for this design, no need to call for other designs
self.center_arr=self.find_pin()
# cross_cut(self.msp,2,self.center_arr)
self.savename='dwg_w_layers.dxf'
self.dwg.saveas(self.savename)
def create_layer(self,layer_name,color):
if not layer_name in self.dwg.layers:
self.dwg.layers.new(name=layer_name,dxfattribs={'color':color})
def layer_rearrange(self):
# put fold lines to the new layer 'Fold'
# put cut lines to the new layer 'Cut
for e in self.msp.query('LINE'):
if e.dxf.color!=5:
e.dxf.layer='Fold'
else:
e.dxf.layer='Cut'
def remove_wheels(self):
#no need to call this function for other design
for e in self.msp.query('Arc LINE[layer=="Cut"]'):
if e.dxf.start[0]>=179:
self.msp.delete_entity(e)
def find_pin(self):
tolerance=0.05 #mm
pincutsize_big=3 #mm
pincutsize_small=1 #mm
pin_edge_arr=np.array([[0,0],[0,0]]).reshape(1,2,2)
center_arr=np.array([[0,0]])
for e in self.msp.query('LINE[layer=="Cut"]'):
length= sqrt((e.dxf.start[0]-e.dxf.end[0])**2+(e.dxf.start[1]-e.dxf.end[1])**2)
if length > pincutsize_small-tolerance and length < pincutsize_big + tolerance:
# e.dxf.layer='Pin_temp'
self.msp.delete_entity(e) #08/13/2019 remove all pins in mechanical desing
if e.dxf.start[1]==e.dxf.end[1]: ##this line is horizontal
pin_edge=np.array([e.dxf.start,e.dxf.end])[:,:2]
pin_edge_arr=np.concatenate((pin_edge_arr,pin_edge.reshape(1,2,2)),axis=0)
pin_edge_arr=pin_edge_arr[1:]
# print(pin_edge_arr)
for i in range(len(pin_edge_arr)):
for e in np.delete(pin_edge_arr,i,axis=0):
if pin_edge_arr[i][0,1]-e[0,1]==1.0:
center_x=pin_edge_arr[i][0,0]-0.5
center_y=pin_edge_arr[i][0,1]-0.5
center=np.array([center_x,center_y]).reshape(1,2)
center_arr=np.append(center_arr,center,axis=0)
center_arr=np.unique(center_arr,axis=0)
center_arr=center_arr[1:]
return center_arr
#!/usr/bin/env python3
# import sys
# sys.path.insert(1,'/home/jingyan/Documents/summer_intern_lemur/roco_electrical/dsn_python')
import dsnwritier
import csv
class brd_design():
def __init__(self,boundary_inx,libpath,netlist_csv,savename='paperbot_ee',dwgfile='dwg_for_autorouter.dxf'):
print('generating board desng file (dsn) ......')
self.dwgfile=dwgfile
self.boundary_inx=boundary_inx
self.libpath=libpath
self.savename=savename
self.module_list=[
['mpu-9250.kicad_mod','J1',[115000,91000],'front',270],
['ESP12F-Devkit-V3.kicad_mod','U1',[104000,46100],'front',90],
['touch_sensor.kicad_mod','T1',[87000,90000],'front',0],
['LED_D3.0mm.kicad_mod','L1',[67000,41000],'front',90],
['Resistor.kicad_mod','R1',[72000,41000],'front',270],
]
self.netlist=[
['3v3',['U1-16','J1-1','T1-2']],
['GND',['U1-24','J1-2','L1-2','T1-3']],
['NET1',['T1-1','R1-1']],
['NET2',['R1-2','L1-1']],
['SCL',['J1-3','U1-22']],
['SDA',['J1-4','U1-23']]
]
# self.netlist=[
# ['3v3',['U1-16','J1-1','T1-2']],
# ['GND',['U1-24','J1-2','L1-2','T1-3']],
# ]
self.netclass_list=[
['default',['3v3','GND','NET1','NET2','SCL','SDA'],'',2000,500]
]
# self.module_list,self.netlist,self.netclass_list=self.read_from_csv(netlist_csv)
self.brd_general()
self.boundary,self.keepout=self.load_drawing()
self.image,self.padstack=self.load_lib()
self.placement=self.place_modules()
self.net,self.netclass=self.create_net()
self.write_dsn()
self.get_pin_loc()
def brd_general(self):
self.brd=dsnwritier.Dsn()
self.layers=[
dsnwritier.Layer('F.Cu')
# dsnwritier.Layer('B.Cu'),
# dsnwritier.Layer('F.Mask'),
# dsnwritier.Layer('B.Mask')
]
self.parsers= dsnwritier.Parser()
self.rule=dsnwritier.Rule()
clearance=[
dsnwritier.Clearance(1000.1),
dsnwritier.Clearance(1000.1,'default_smd'),
dsnwritier.Clearance(1000,'smd_smd')]
self.rule.clearance=clearance
def load_drawing(self):
"""
create class: bourdry and keepout
from drawing file
"""
dwg=(dsnwritier.load_drawing(self.dwgfile)).load_polygon()
bdata=dwg.pop(self.boundary_inx)
kdata=dwg
keepout=[]
for i in range(len(kdata)):
keep_class=dsnwritier.Keepout(kdata[i])
keepout.append(keep_class)
boundary=dsnwritier.Boundary(bdata)
return boundary,keepout
def load_lib(self):
"""
create class: footprint (image) and padstack
from library path and modules
"""
image=[]
for i in range(len(self.module_list)):
mod=self.module_list[i]
img=dsnwritier.Footprint.from_file(self.libpath+mod[0],ref=mod[1])
image.append(img)
if i==0:
padstack=dsnwritier.Padstack.auto_detect(self.libpath+mod[0])
else:
padstack+=dsnwritier.Padstack.auto_detect(self.libpath+mod[0])
return image,padstack
def place_modules(self):
placement=[]
for mod in self.module_list:
place=dsnwritier.Placement(name=mod[0],ref1=mod[1],at=mod[2],flip=mod[3],orientation=mod[4])
placement.append(place)
return placement
def create_net(self):
nets_list=[]
netclass_list=[]
for net in self.netlist:
nets=dsnwritier.Net(net_name=net[0],conn_pins=net[1])
nets_list.append(nets)
for netclass in self.netclass_list:
netclasses=dsnwritier.NetClass(net_class_name=netclass[0],
nets_name=netclass[1],
via_name=netclass[2],
width=netclass[3],
clearance=netclass[4])
netclass_list.append(netclasses)
return nets_list,netclass_list
def write_dsn(self):
self.brd.parser=self.parsers
self.brd.rule=self.rule
self.brd.layers=self.layers
self.brd.boundary=self.boundary
self.brd.keepout=self.keepout
self.brd.image=self.image
self.brd.padstack=self.padstack
self.brd.placement=self.placement
self.brd.net=self.net
self.brd.netclass=self.netclass
self.brd.to_file(self.savename)
print('dsn file generated.....')
def get_pin_loc(self):
from pykicad.module import Module as mod
import numpy as np
from math import sqrt
self.pins_at=[]
for img in self.module_list:
module=mod.from_file(self.libpath+img[0])
pin_at_arr=[]
module_at=np.array([img[2][0],img[2][1]])/1000
flip=1
orientation=img[4]
theta=orientation/180*np.pi
if img[3]=='back':
flip=-1
orientation=img[4]
for i in range(len(module.pads)):
pad=module.pads[i]
pin_at_relative=np.array(pad.at) ##mm
x,y=pin_at_relative[0]*flip,-pin_at_relative[1]
x_=x*np.cos(theta)-y*np.sin(theta)
y_=y*np.cos(theta)+x*np.sin(theta)
pin_at_relative=np.array([x_,y_])
pin_at_abs=list(pin_at_relative+module_at)
pin_at_arr.append(pin_at_abs)
self.pins_at.append(pin_at_arr)
def read_from_csv(self,csv_file):
with open(csv_file, mode='r') as csv_file:
csv_reader = csv.DictReader(csv_file, delimiter = ';')
myDict = list(csv_reader)
numOfRows = len(myDict)
keys = myDict[0].keys()
moduleCols = 4
netCols = 2
netclassCols = 5
moduleRows = 0
netRows = 0
netclassRows = 0
moduleHeaders = ['Module name','Ref','Position','Orientation']
netHeaders = ['Connection name','Pins']
netclassHeaders = ['Netclass','Net connections','Via name','Width','Clearance']
for x in range(numOfRows):
if myDict[x].get('Module name') != '' and myDict[x].get('Module name') != 'None':
moduleRows += 1
if myDict[x].get('Connection name') != '' and myDict[x].get('Connection name') != None:
netRows += 1
if myDict[x].get('Netclass') != '' and myDict[x].get('Netclass') != None:
netclassRows +=1
moduleList = [[0 for i in range(moduleCols)] for j in range(moduleRows)]
netList = [[0 for i in range(netCols)] for j in range(netRows)]
netclassList = [[0 for i in range(netclassCols)] for j in range(netclassRows)]
for x in range(moduleRows):
for y in range(moduleCols):
header = moduleHeaders[y]
moduleList[x][y] = myDict[x].get(header)
for x in range(netRows):
for y in range(netCols):
header = netHeaders[y]
netList[x][y] = myDict[x].get(header)
for x in range(netclassRows):
for y in range(netclassCols):
header = netclassHeaders[y]
netclassList[x][y] = myDict[x].get(header)
return moduleList,netList,netclassList
# libpath='/home/jingyan/Documents/summer_intern_lemur/roco_electrical/libraries/kicad-ESP8266/ESP8266.pretty/'
# dwgfile='/home/jingyan/Documents/summer_intern_lemur/roco_electrical/dsn_line_test.dxf'
# a= brd_design(dwgfile,0,libpath,'paperbot_ee')
#!/usr/bin/env python3
def roco_add_ee(me_drawing,netlist_csv,module_libpath,dsnwritier_dir,
trace_width=2,iso_size=2,cross_size=0.66):
"""
This module call script 'roco_dsn.py' to generate a DSN file for auto_router.
An interface will pop up for the user to confirm wiring design,
if a user does not want to change anything, one can just simply close the window.
Routing information will be stored automatically in the same directory.
The routing information will be read and draw the corresponding line on the dxf file
(Please make sure the ME drawing has everything need to cut (obstacles) in "Cut" layer)
Tested Python Version: 3.6
path information needed:
- mechanical design drawing file
- A csv file describes your netlist
- modules library dir
- dsnwritier(sesreader) dir
"""
##########general lib############
import sys
import subprocess
import os
sys.path.insert(1,dsnwritier_dir)
from sesreader import find_wire
from roco_dsn import brd_design
from ee_dwg_processing import pre_process, post_process
ready_for_autorouter=pre_process(me_drawing) #get dwg_for_autorouter.dxf
ee_design=brd_design(ready_for_autorouter.bry_ind,module_libpath,netlist_csv)#,dwgfile='dwg_for_autorouter_temp.dxf') #get paperbot_ee.dsn
subprocess.call(['java','-jar','freeRouting.jar','-de',ee_design.savename+'.dsn','-white','-s'])
wiring_path=find_wire(ee_design.savename,ee_design.pins_at) #read ses file
post_process(me_drawing,wiring_path,ee_design.pins_at,trace_width,iso_size,cross_size) # draw for fabrication
os.remove(ready_for_autorouter.savename)
os.remove(ee_design.savename+'.dsn')
# os.remove(ee_design.savename+'.ses')
os.remove(ee_design.savename+'.rules')
def test():
from read_old_paperbot import read_old_paperbot
dsnwritier_dir='/home/jingyan/Documents/summer_intern_lemur/roco_electrical/dsn_python'
module_libpath='/home/jingyan/Documents/summer_intern_lemur/roco_electrical/libraries/kicad-ESP8266/ESP8266.pretty/'
dwg_path='/home/jingyan/Documents/summer_intern_lemur/roco_electrical/graph-silhouette.dxf'
net_csv='/home/jingyan/Downloads/moduleList.csv'
old=read_old_paperbot(dwg_path) #get dwg_w_layer.dxf
me_dwg=old.savename
roco_add_ee(me_dwg,net_csv,module_libpath,dsnwritier_dir)
test()
\ No newline at end of file
## ENGINEERING JOURNAL
### Jingyan Ling
### 06/27/2019 (Thur)
- Compile `OldRoCo` [repo](https://git.uclalemur.com/mehtank/oldroco)
- Record of modification:
- ++line 8 of `vstart.sh`
- --> `pip install -r requirements.txt --user`
- Export `paperbot` dxf file : `graph-silhouette.dxf` to `OnShape`
- Record of Tips:
- Onshape does not read dxf file as its own saved unit. Manual settings needed when import.
- ![onshape_dxf_unit](journal_media/onshape_dxf_unit.png)
- Draw circuit diagram as CAD on `Onshape`
- Multiple connection approaches:
- `Cross` shape of `0.039 in`
- `Cross` shape of `0.049 in`
- `Octangular` shape of `0.049 in`
- ![connection_approaches.png](journal_media/connection_approaches.png)
- ![actual_connection_cuts.JPG](journal_media/actual_connection_cut.JPG)
- Note and issue:
- `Octangular` shape is the best connection approach so far, but none of approach has stable conductivity. More research is needed.
- Scratches can be made easily when attaching microcontroller to the sheet. Potential disconnection may occur due to such scratches on metal layer.
- Export 2D CAD drawing with PCB to SVG for silhouette cameo
- Record of Tips:
- `OnShape` unit need to be consisted with unit setting in `Inkscape` (default: mm)
- Plugin `Inkscape-silhouette` cannot take multiple types of trace for cutting.
- Different trace (especially cut and etching) need to be sent separately. Use `coordinate tool` of `Inkscape` to ensure separated cuts have same origin.
- TODO:
- Cut and etching from same `Inkscape` file
# Automatic Electromechanical Design System for Foldable Robots
### 06/28/2019 (Fri)
##### Connection and fabrication approach confirmed !
- Confirm that copper tape has better conductivity than aluminum sheet when attaching microcontroller pins through `H-shape` cut
- Copper tape is harder to be scratched, which provides less risk of disconnection during assembly.
- Etching on copper type isolates connections
- Research on PCB editor
- EAGLE
- EAGLE cannot import `.pcb` file
- `svg` can be exported but not imported
- Custom library needed if doing schematic design
- KiCad
- Can import `.pcb` file
- `svg` can be exported but not imported
- LayoutEditor
- support multiply file type
- `svg` can be exported with different color makers as multiple layers
- include open-sourced auto-router `freerouting`
- Auto-routing package:
- TopoR
- Python-PCB
- FreeRouting
- [Motivation](#motivation)
- [Contribution (this repo)](#contribution)
- [Method](#method)
- [To implement my work](#implement-my-work)
- [Demo Video](#demo)
- Approaches:
- `A`. convert SVG/DXF file to schematic/board design of PCB. Use KiCAD or EAGLE to do auto routing.
- `B`. develop algorithm on auto routing for single layer svg/dxf file. (path finding problem)
##### Approach `A`
- Use regular shape board for PCB in EAGLE/KiCAD
- image processing: divide the shape of `paperbot` to multiple rectangles (ezdxf)
- Pull required connections and place additional header pins around edges of each rectangle
*This repository shares the code and technical details of the research work only, for more information, please check our [paper](https://drive.google.com/file/d/1Indd-3h5moTxg3azODMNYoxxEPuNbpjI/view?usp=sharing) submitted to ICRA2020 or my [engineering journal](developer_journal.md)*.
- Make each connection pin to be two parallel connections so auto-router does parallel trace for each wiring.
- Etching a pair of parallel trace provides isolation, and leave the space between traces to be conductible.
- Build Library for schematic design
- Schematic design helps with router to know desired connection
- Additional header pins at each side of kink mark
This research studies **human computer interaction** in terms of developing a **robot compiler** to create electromechanical design for foldable robots. The work presents an integrated design-to-fabrication method for foldable electromechanical devices that rethinks the merging of mechanical and electrical designs. The mechanical substrate of this device can carry electrical functionality; the electrical circuits are parts of the device body.
### 06/29 & 06/30/2019 (Sat & Sun)
##### Approach `B`
- Potential needed functions of packages
- A package draws on DXF file through script
- A script that pulls out pins' coordinate on paper chassis
- Or a image process package (OpenCV) that reads pins' coordinate on paper chassis
- A package solves multi-node path finding problem*
## Motivation
Printable or foldable robots are becoming a feasible approach for the goal of building robots rapidly with lower cost. While the threshold of the requirement of knowledge for the design and fabricate foldable robots is a barrier to enable the public to create personalized robots, a system doing electrical and mechanical design from a limited amount of user effort is required. In order to be beneficial to the general public, a long-term goal is to develop a compiler that allows people to create a robot with the desired functions.
##### Record of Research
- Python Pathfinding package:
- [pathfinding](https://pypi.org/project/pathfinding/#description)
- Python DXF editor:
- [ezdxf](https://pypi.org/project/ezdxf/)
- [dxfgrabber](https://pypi.org/project/dxfgrabber/)
- `Note`: able to grab simple geometry from file
- [sdxf](http://www.kellbot.com/sdxf-python-library-for-dxf/)
- Going through tutorials of `ezdxf`
- All lines on paper space`layer0`
## Contribution
### 07/01/2019 (Mon)
- Continue the dxf processing with `'graph-silhouette.dxf'`
##### Record of tips:
- Put circuit design on a new layer of dxf drawing
- [check doc about layers here](https://ezdxf.readthedocs.io/en/latest/tutorials/layers.html)
- Use block feature to draw similar pattern
- [check doc about block here](https://ezdxf.readthedocs.io/en/latest/tutorials/blocks.html)
- Blocks are `symbol` in Inkscape. Convert `symbol` to `path`
- Edit > Clone > Unlink Clone (Shift+Alt+D), and you have a group, ungroup (Object > Ungroup, or Shift+Ctrl+G) and edit
- Convert `text` to `path`
- Path combine (Ctrl+K)
- msp.delete_enetity('__') to remove existing entity
This repository presents two software contributions from the technical perspective. For more information of the contribution from the research perspective, please see the paper.
- Test fabrication with different drawing settings
- Layers can be cut separately by toggling visibility
- Set cuts with same `intensity` and `feedrate` on same layer
- Entities' layer can be changed through `Entity.dxf.layer`
- DFM (design for manufacturing) software package for the fabrication process we developed.
- PCB software package generates electrical routing from board outlines converted from an arbitrary mechanical design.
### 07/02/2019 (Tue)
- Continue the dxf processing
- Following tasks can be completed by running the code:
- Able to detect pin center coordinate
- Able to draw arbitrary shape around the pin center
- ![isolation_circuit.png](journal_media/isolation_circuit.gif)
- - Issue & TODO
- Convert dxf image to matrix
- Explore `pathfinding` package
System Pipeline
- `pathfinding` package solve path finding problem by shortest path algorithm, one point to another
- To solve multiple nodes problem, use concept from reinforcement learning
- Build Q function that represents total reward of choosing certain path to be solved first
- Update Q function in a loop with certain amount of episodes
- Choose the order of solving paths randomly, and add possibility of choosing it from Q function
- Exploration-exploitation method
- To solve the problem "convert dxf to matrix" for pathfinding:
- Have tried:
- Export to `png` and read file by `OpenCV`
- Resolution problem: single point does not represented as a pixel in `png`
- TODO:
- create an empty matrix and fill in numbers with the information of each lines' start and end point
### 07/03/2019 (Wed)
- dxf processing completed
![](journal_media/final_pipline.png)
- Convert dxf to matrix with resolution (270X210) (mm)
- Potential issue:
- the `pathfinding` package is based on pixel
- When round up the line's start and end point, the accuracy of pin position is low
## Method
- Apply single point to point path finding
- Able to draw the path on matrix
- Able to draw the path on original dxf file
- ![matrix](journal_media/matrix_to_img.png)
#### DFM software package
- TODO:
- Write out algorithm for multi-node path finding
- break single circuit trace to parallel traces
- solve the accuracy problem
This software package takes the electrical design and the mechanical design of a foldable robot as inputs and outputs such drawing file that makes users ready to proceed the fabrication. We use the 2-D drawing file with extension of `dxf` to represent the electromechanical design of a foldable robot. Such drawing are designed to contain multiple layers meaning different fabrication types.
### 07/05/2019 (Fri)
- Write out algorithm for multi-node path finding
- pseudo code:
~~~~
for (episode in E):
set initial state s (choose first pair of nodes to connect randomly)
find a path
update map
while not every connection is built:
choose next connection
find a path
update map
retrieve cost (length of path)
update Q (as sum of all cost)
~~~~
- Q here represents total cost of certain choice of path finding order.
- Cost defined by length of path
- If path cannot be found, cost 1000
- Input: NX2X2 array specifies desired pin connections
<img src="journal_media/same_pin_intersect.png" width="800">
- Research on KiCAD import needs and python API
The software converts each wiring information into wiring patterns onto the mechanical drawing. The patterns can be tuned through user input. The default wiring pattern turns a single wire in the electrical design to a wider wire with three layers. The reason is to remove extra conductive material during the fabrication when users print such robot on a conductive material.
### 07/08/2019 (Mon)
#### PCB osftware package
This software package takes user input of electrical netlist and mechanical design, outputs an electrical design file with wiring information. We utilize the auto-router [FreeRouting](https://freerouting.org/) to generate wiring information that can be passed to the DFM software package. We use the electrical design file with extension of `dsn` to represent the PCB layout. The package imports the mechanical design from a drawing, treats it as PCB outlines and wiring keepout areas.
- Debug multi-node path finding:
- some obstacles are ignored
- too close to each other
- Able to show the learning process:
- Trying different combination of solving order
- Try to give the best order of solving multiple path
<img src="journal_media/fig12-2.png" width="800">
## Implement My Work
##### Auto-routing learning process:
- ![path_learning.gif](journal_media/path_learning.gif)
#### DFM software package
- Comparison between episodes and choose best combo of paths
- ![learning_episodes.gif](journal_media/learning_episodes.gif)
For testing:
~~~
cd EEME_design
python3 roco_electrical.py
~~~
##### Issues and TODO
- Path export from optimization is messed up
- Obstacles setting are incorrect
Call the function `roco_electrical.roco_add_ee` for arbitrary design. Arguments:
### 07/09/2019
- Finish up path-finding approach (Approach B)
- Only draw the best path on `dxf` when solution is found for every route.
- ![circuit_dxf](journal_media/dxf_circuit.png)
- Throw warning once a route cannot be found
- ![fail_solution](journal_media/path_fail.gif)
- me_drawing: mechanical drawing file (`dxf` file, please make sure everything need to cut is in "Cut" layer)
- CSV file that describes your netlist, one can also change it by modifying the information in `roco_dsn.py`
- Electrical modules footprint library
- The directory of `dsnwriter` package (default under same directory of EEME_design)
- Research on Approach A
- KiCAD 5 does not have built-in auto-router
- Have to use `FreeRouting`
- Optional parameters:
- trace_width: width of the wiring pattern
- iso_size: size of the isolation pattern
- cross_size: size of the placement pattern
- `FreeRouting` does not have python API
- Parent package of `FreeRouting`, `LayoutEditor` has multiple editions
- Part of `LayoutEditor` is open- source
- A large amount of of its website are broken, source and interface are unavailable
For fabrication process, please see the paper and demo video.
- Other auto-routers are not open-source
## Demo
##### Approach A
- 1. Use `LayoutEditor` python API to use `FreeRouting`
- 2. Use `KiCAD 5` python API to sue `FreeRouting`
- 3. Use `KiCAD 4` python API to use builtin auto-routing
###### 1
- `FreeRouting` input format: `dsn`, `lef`, `def`
- `FreeRouting` output format: `ses`, `def`
- `FreeRouting` can take arbitrary board outlines shape
- Edit `library` manually and test routing
- ![dxf_import](journal_media/dxf_import_kicad.png)
- TODO:
- If can run KiCAD 4 from python
- Check `FreeRouting` API, see if a acceptable file can be generated from things on hand
### 07/10/2019
##### Approach A
- Import to PCBnew issue:
- Dimension of board outlines are incorrect
- Pins does not fit in original drawings
- (solved. Microcontroller footprint was incorrect)
- pipeline confirmed:
- 1. Build symbol and footprint library for all required electrical element
- 2. Sketch schematic design
- `.sch` file
- 3. Place elements in required position
- `.dsn` file
- 4. Import `dsn` file into `FreeRouting` and do auto-routing
- `.ses` file
- 5. Import `ses` file back to `KiCAD` and export as `dxf` file
- `.dxf` file
- Manually tested pipeline till step 4
- Step 3
- ![](journal_media/sample_conn_pcbnew.png)
- Step 4
- ![](journal_media/sample_autorouting.gif)
- ISSUE:
- Board outlines are not exported correctly to `FreeRouting`
- Error: `unable to find segment with an endpoint`
- Record of Tips:
- To run `FreeRouting` from command line:
~~~
java -jar freeRouting.jar [proj.dsn]
~~~
- TODO:
- Finish with testing the pipeline manually
- Achieve steps directly from script (Python API)
### 07/11/2019
- Solved Error `unable to find segment with an endpoint`
- `DSN` file can only be exported for continuous closure outlines
- ![](journal_media/fail_dsn.png)
- Drawings like this drawing for paper cut is not acceptable for `dsn` file.
- Change it either to close shape or remove it when import to KiCAD
- Solving Issue: `FreeRouting` is not reading edge cut layer from `dsn`
- Work well on a sample `dsn` file
- Issue with KiCAD output
- Sample KiCAD project can output outlines properly.
- Issue found:
- KiCAD cannot take single line cut. Closed shape needed.
-
One can find demo viedo [here](https://www.youtube.com/watch?v=SaRiikg01lI)
#!/usr/bin/env python
import numpy as np
import ezdxf
import random
from math import sqrt
import cv2
import os
from pathfinding.core.diagonal_movement import DiagonalMovement
from pathfinding.core.grid import Grid
from pathfinding.finder.a_star import AStarFinder
import copy
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import cm
class auto_rounter:
def __init__(self,path,dxf_file):
self.dwg=ezdxf.readfile(path+dxf_file)
self.msp=self.dwg.modelspace()
self.dxf_name='silhouette_ele.dxf'
self.fail_flag=False #return True when path not found
self.create_layer('Cut',5)
self.create_layer('Circuit',1)
self.create_layer('Label',3)
self.create_layer('Fold',4)
self.create_layer('Pin_temp',6)
self.layer_rearrange() ## reagrrange cut and fold lines to corresponding layers
self.remove_wheels() ##remove wheel drawings for this design, no need to call for other designs
self.find_pin()
isolength=2.6 #mm
# self.draw_on_pin(isolength)
self.matrix_shape=(270,210)
self.read_dxf_as_matrix()
# self.connections_list=np.array([[0,0],[0,0]]).reshape(1,2,2)
# connection_amount=4
# for i in range(connection_amount):
# s=random.randint(0,len(self.center_arr)-1)
# e=random.randint(0,len(self.center_arr)-1)
# connections=np.array([[self.center_arr[s],self.center_arr[e]]]).reshape(1,2,2)
# self.connections_list=np.append(self.connections_list,connections,axis=0)
# self.connections_list=np.rint(self.connections_list)[1:]
############for testing and demo purpose #############
self.connections_list=self.test_connections()
# self.matrix_temp=copy.deepcopy(self.matrix)
# self.img=plt.imshow(self.matrix_temp,interpolation='nearest',cmap=cm.Spectral)
# ani=animation.FuncAnimation(self.fig,self.find_multi_path,interval=10)
# plt.show()
self.find_multi_path(1)
self.dwg.saveas(self.dxf_name)
def create_layer(self,layer_name,color):
if not layer_name in self.dwg.layers:
self.dwg.layers.new(name=layer_name,dxfattribs={'color':color})
def layer_rearrange(self):
# put fold lines to the new layer 'Fold'
# put cut lines to the new layer 'Cut
for e in self.msp.query('LINE'):
if e.dxf.color!=5:
e.dxf.layer='Fold'
else:
e.dxf.layer='Cut'
def find_pin(self):
tolerance=0.05 #mm
pincutsize=1 #mm
pin_edge_arr=np.array([[0,0],[0,0]]).reshape(1,2,2)
self.center_arr=np.array([[0,0]])
for e in self.msp.query('LINE[layer=="Cut"]'):
length= sqrt((e.dxf.start[0]-e.dxf.end[0])**2+(e.dxf.start[1]-e.dxf.end[1])**2)
if length > pincutsize-tolerance and length < pincutsize + tolerance:
e.dxf.layer='Pin_temp'
if e.dxf.start[1]==e.dxf.end[1]: ##this line is horizontal
pin_edge=np.array([e.dxf.start,e.dxf.end])[:,:2]
pin_edge_arr=np.concatenate((pin_edge_arr,pin_edge.reshape(1,2,2)),axis=0)
pin_edge_arr=pin_edge_arr[1:]
# print(pin_edge_arr)
for i in range(len(pin_edge_arr)):
for e in np.delete(pin_edge_arr,i,axis=0):
if pin_edge_arr[i][0,1]-e[0,1]==1.0:
center_x=pin_edge_arr[i][0,0]-0.5
center_y=pin_edge_arr[i][0,1]-0.5
center=np.array([center_x,center_y]).reshape(1,2)
self.center_arr=np.append(self.center_arr,center,axis=0)
self.center_arr=np.unique(self.center_arr,axis=0)
self.center_arr=self.center_arr[1:]
# print(self.center_arr)
def remove_wheels(self):
#no need to call this function for other design
for e in self.msp.query('Arc LINE[layer=="Cut"]'):
if e.dxf.start[0]>=179:
self.msp.delete_entity(e)
def draw_on_pin(self,fulllength): #isolation
iso=self.dwg.blocks.new(name='ISO_BLK')
isolength=fulllength/2
trace_w=0.8
iso.add_line((-isolength,isolength),(isolength,isolength),dxfattribs={'linetype':'DASHDOT'})
iso.add_line((-isolength,-isolength),(isolength,-isolength),dxfattribs={'linetype':'DASHDOT'})
iso.add_line((-isolength,-isolength),(-isolength,isolength),dxfattribs={'linetype':'DASHDOT'})
iso.add_line((isolength,-isolength),(isolength,isolength),dxfattribs={'linetype':'DASHDOT'})
iso.add_line((-isolength,-trace_w/2),(-(isolength+5),-trace_w/2),dxfattribs={'linetype':'DASHDOT'})
iso.add_line((-isolength,trace_w/2),(-(isolength+5),trace_w/2),dxfattribs={'linetype':'DASHDOT'})
for center_point in self.center_arr:
self.msp.add_blockref('ISO_BLK',center_point,dxfattribs={'layer':'Circuit'})
def read_dxf_as_matrix(self):
"""unit: mm
accuracy issue expected atm (mm round up)
"""
self.matrix=np.ones(self.matrix_shape)
for line in self.msp.query('LINE[layer!="Fold" & layer!="Label" & layer!="Pin_temp"]'):
start=np.rint(line.dxf.start)
end=np.rint(line.dxf.end)
i=int(start[0])
j=int(start[1])
#can draw horizontal or vertical lines only
if j==end[1]: #this line is a horizontal line
self.matrix[j,i]=0
while i!=end[0]:
if i<end[0]:
i+=1
else:
i-=1
self.matrix[j,i]=0
elif i==end[0]: #this line is a vertical line
self.matrix[j,i]=0
while j!=end[1]:
if j<end[1]:
j+=1
else:
j-=1
self.matrix[j,i]=0
def find_a_path(self,matrixws,start_point,end_point):
"""
find a path between two points on img=matrix
start_point and end_point shape: (2,)
"""
#DEBUG:
# if not np.array_equal(matrixws,self.matrix):
# print('Map updated')
grid = Grid(matrix=matrixws)
start=grid.node(int(start_point[0]),int(start_point[1]))
end=grid.node(int(end_point[0]),int(end_point[1]))
finder = AStarFinder(diagonal_movement=4)
path, runs = finder.find_path(start, end, grid)
return path
def draw_a_path(self,path,matrix,dxf=False):
""" draw a path on img=matrix, or on dxf file too"""
#draw on matrix:
for point in path:
matrix[point[1],point[0]]=0
#draw on dxf:
if dxf:
for i in range(len(path)-1):
self.msp.add_line(path[i],path[i+1],dxfattribs={
'layer':'Circuit',
'linetype':'DASHDOT'})
def get_cost(self,path):
if len(path)==0: #if no path found
print('one path not found')
self.fail_flag=True
cost=1000
else: cost=len(path)
return cost
def find_multi_path(self,i):
"""connection list shape : NX2X2 """
E=20
print("==========Auto rounting start===========")
for episode in range(E):
print 'episode:',episode+1,'(/',E,')==========='
self.matrix_temp=copy.deepcopy(self.matrix)
self.Q=0
self.fail_flag=False
random_ix=random.randint(0,len(self.connections_list)-1)
init_s=self.connections_list[random_ix]
current_solving=np.array([init_s]).reshape(1,2,2)
con_list_temp=np.delete(self.connections_list,random_ix,axis=0)
cur_path=self.find_a_path(self.matrix_temp,init_s[0],init_s[1])
self.draw_a_path(cur_path,self.matrix_temp)
# self.img.set_array(self.matrix_temp)
cost=self.get_cost(cur_path)
self.Q=self.Q+cost
curpath_temp=[cur_path]
while len(con_list_temp)!=0:
random_ix=random.randint(0,len(con_list_temp)-1)
next_conn=con_list_temp[random_ix]
current_solving=np.append(current_solving,next_conn.reshape(1,2,2),axis=0)
con_list_temp=np.delete(con_list_temp,random_ix,axis=0)
curpath=self.find_a_path(self.matrix_temp,next_conn[0],next_conn[1])
self.draw_a_path(curpath,self.matrix_temp)
curpath_temp.append(curpath)
# self.img.set_array(self.matrix_temp)
cost=self.get_cost(curpath)
self.Q=self.Q+cost
if episode==0:
self.Q_buff=copy.deepcopy(self.Q)
if self.Q<=self.Q_buff:
self.Q_buff=copy.deepcopy(self.Q)
self.final_solving=current_solving
self.final_path=curpath_temp
self.final_fail=self.fail_flag
print 'Current cost:',self.Q,'Best cost',self.Q_buff
episode+=1
if not self.final_fail:
for i in range(len(self.final_path)):
self.draw_a_path(self.final_path[i],self.matrix,True)
else:
print 'One or more path cannot be solved'
def test_connections(self):
y=97
x=90
arti_pin_array=np.empty((1,2))
center_pin_conn=np.empty((1,2))
pin_conn=np.empty((1,2,2))
for i in range(10):
arti_pin=(x+i,y)
arti_pin_array=np.append(arti_pin_array,[arti_pin],axis=0)
arti_pin_array=arti_pin_array[1:]
for i in range(len(self.center_arr)):
if self.center_arr[i][1]<60:
center_pin_conn=np.append(center_pin_conn,self.center_arr[i].reshape(1,2),axis=0)
if len(center_pin_conn)>10: break
center_pin_conn=center_pin_conn[1:]
for i in range(10):
pin_conn_temp=np.array([arti_pin_array[i],center_pin_conn[i]]).reshape(1,2,2)
pin_conn=np.append(pin_conn,pin_conn_temp,axis=0)
pin_conn=pin_conn[1:]
return pin_conn
# def main():
path='/home/jingyan/Documents/summer_intern_lemur/roco_electrical/'
dxf_file='graph-silhouette.dxf'
router=auto_rounter(path,dxf_file)
# animation.FuncAnimation(edit.fig,plt.imshow(edit.matrix_temp))
# plt.show()
# if __name__ == '__main__':
# main()