Pogojig mostly done: KiCAD export works
This commit is contained in:
parent
82b88f920a
commit
b2eb56076d
26 changed files with 502 additions and 611 deletions
47
Makefile
47
Makefile
|
|
@ -1,40 +1,27 @@
|
|||
# Installation-dependent settings. You can overwrite these in a file called config.mk in the same directory as this makefile. See readme.creole.
|
||||
INKSCAPE := inkscape
|
||||
OPENSCAD := openscad
|
||||
PYTHON := python2
|
||||
ASYMPTOTE := asy
|
||||
# Environment variables:
|
||||
# INKSCAPE_DXF_FLATNESS controls inkscape SVG->DXF export curve flatness (default: 0.1)
|
||||
# INKSCAPE, OPENSCAD: Commands to use for inkscape and openscad
|
||||
|
||||
# Settings affecting the compiled results. You can overwrite these in a file called settings.mk in the same directory as this makefile. See readme.creole.
|
||||
DXF_FLATNESS := 0.1
|
||||
|
||||
# Non-file goals.
|
||||
.PHONY: all clean generated dxf stl asy pdf
|
||||
|
||||
# Include the configuration files.
|
||||
-include config.mk settings.mk
|
||||
|
||||
# Command to run the Python scripts.
|
||||
PYTHON_CMD := PYTHONPATH="support" $(PYTHON)
|
||||
INKSCAPE_CMD := INKSCAPE=$(INKSCAPE) DXF_FLATNESS=$(DXF_FLATNESS) $(PYTHON_CMD) -m inkscape
|
||||
OPENSCAD_CMD := OPENSCAD=$(OPENSCAD) $(PYTHON_CMD) -m openscad
|
||||
OPENSCAD ?= openscad
|
||||
|
||||
all: src/jig.stl src/pcb_shape.dxf
|
||||
|
||||
src/input.preprocessed.dxf: src/input.preprocessed.svg
|
||||
support/inkscape_exporter.py $< $@
|
||||
|
||||
src/pcb_shape.dxf: src/pcb_shape.scad src/input.preprocessed.dxf
|
||||
$(OPENSCAD) -o $@ $<
|
||||
|
||||
src/jig.stl: src/jig.scad src/input.preprocessed.dxf
|
||||
$(OPENSCAD) -o $@ $<
|
||||
|
||||
src/input.preprocessed.svg: input.svg
|
||||
support/inkscape_svg_filter_layers.py $< $@ --only --name "Test Points" "Mounting Holes" "Grip Slots" "Outline"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f src/input.preprocessed.dxf
|
||||
rm -f src/input.preprocessed.svg
|
||||
rm -f src/jig.stl
|
||||
rm -f src/pcb_shape.dxf
|
||||
|
||||
src/input.preprocessed.dxf: src/input.preprocessed.svg
|
||||
$(INKSCAPE_CMD) $< $@
|
||||
|
||||
src/pcb_shape.dxf: src/pcb_shape.scad src/input.preprocessed.dxf
|
||||
$(OPENSCAD_CMD) $< $@
|
||||
|
||||
src/jig.stl: src/jig.scad src/input.preprocessed.dxf
|
||||
$(OPENSCAD_CMD) $< $@
|
||||
|
||||
src/input.preprocessed.svg: input.svg
|
||||
support/inkscape_svg_filter_layers.py $< $@ --only --name "Test Points" "Mounting Holes" "Grip Slots" "Outline"
|
||||
|
||||
|
|
|
|||
219
readme.md
219
readme.md
|
|
@ -1,219 +0,0 @@
|
|||
# OpenSCAD Template
|
||||
|
||||
## Repository structure
|
||||
|
||||
This repository, as it is maintained on
|
||||
[GitHub](http://github.com/Feuermurmel/openscad-template), contains two
|
||||
important branches, `master` and `examples`. `master` contains an empty project
|
||||
which is ready to be cloned and used for new project.
|
||||
|
||||
Branch `examples` additionally contains a few example source files which are
|
||||
ready to be compiled. The root directory on that branch also contains a second
|
||||
text document `examples.creole`, describing the example project in more detail.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OpenSCAD snapshot > 2014.11.05
|
||||
- Used to compile OpenSCAD source files to STL.
|
||||
- A recent development snapshot is recommended, e.g. version 2014.11.05 or
|
||||
later.
|
||||
- The current release version (2014.03) generates invalid dependency
|
||||
information if the path to the project contains spaces or other
|
||||
characters that need to be treated specially in a makefile and also
|
||||
has trouble with 2D shapes containing holes. The current development
|
||||
version solves these problems.
|
||||
|
||||
- Inkscape > 0.91
|
||||
- Used to export DXF files to SVG.
|
||||
- Recommended to edit SVG files, especially if importing of separate layers
|
||||
in OpenSCAD is needed.
|
||||
- At least version 0.91 (or maybe some earlier development snapshot) is
|
||||
necessary because the command line verbs used to transform and massage an
|
||||
SVG prior to export have only recently been added.
|
||||
|
||||
- Python 2.7
|
||||
- Used for to run the plugin that exports DXF to SVG and to run scripts
|
||||
that wrap the OpenSCAD command line tool and work around problems with
|
||||
generation of dependency information in OpenSCAD.
|
||||
- Should already be installed as a dependency to Inkscape. The most recent
|
||||
version of Python 2.7 is recommended.
|
||||
|
||||
- Asymptote [0]
|
||||
- Used to compile Asymptote files to PDF.
|
||||
- Recommended when creating Vector cutting projects for Epilog laser
|
||||
cutters.
|
||||
|
||||
[0]: This project was tested with Asymptote Version 2.35. Earlier Versions will
|
||||
probably also work.
|
||||
|
||||
|
||||
### Explicitly specifying paths to binaries
|
||||
|
||||
If any of the required binaries is not available on `$PATH` or a different
|
||||
version should be used, the paths to these binaries can be configured by
|
||||
creating a file called `config.mk` in the same directory as the makefile.
|
||||
There, variables can be set to the absolute or relative paths to these
|
||||
binaries. For example:
|
||||
|
||||
# Path to the OpenSCAD binary
|
||||
OPENSCAD := /Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD
|
||||
|
||||
# Path to the Inkscape binary
|
||||
INKSCAPE := /opt/local/bin/inkscape
|
||||
|
||||
# Path to the Python 2.7 binary
|
||||
PYTHON := /opt/local/bin/python2.7
|
||||
|
||||
# Path to the Asymptote binary
|
||||
ASYMPTOTE := /opt/local/bin/asy
|
||||
|
||||
|
||||
## Supported file types
|
||||
|
||||
### Using SVG files from OpenSCAD
|
||||
|
||||
Any file whose name ends in `.svg` may be used from an OpenSCAD file like this:
|
||||
|
||||
import("file.dxf");
|
||||
|
||||
The makefile will automatically convert the SVG file to a DXF file when
|
||||
building the project. If Inkscape is used to edit the SVG file, multiple layers
|
||||
can be created which can then be imported individually:
|
||||
|
||||
import("file.dxf", "background");
|
||||
|
||||
The DXF export supports all shapes supported by Inkscape (e.g. rectangles,
|
||||
circles, paths, spiro lines, text, …). Before the objects are exported, all
|
||||
objects are converted to paths and combined using the union operation. For
|
||||
objects which have a stroke style, the stroke instead of the filled area is
|
||||
converted to a path. Then, the resulting path is converted to a set of line
|
||||
segments which closely follow the curved parts of the path. The resulting line
|
||||
segments are exported to DXF and combined to the original shapes when imported
|
||||
in OpenSCAD. For these transformations to work, the objects need to be placed
|
||||
in Inkscape layers.
|
||||
|
||||
OpenSCAD itself does not define which unit is used to measure lengths [1].
|
||||
Inkscape OTOH allows the user to define a document wide unit as well as using
|
||||
different units when specifying the size and position of shapes. When exporting
|
||||
the SVG document using Inkscape, all numbers are converted to the unit
|
||||
specified under _General_ in Inkscape's _Document Properties_ dialog. These
|
||||
numbers are the written to the DXF document and used OpenSCAD directly.
|
||||
|
||||
DXF and OpenSCAD both use a right-handed coordinate system (the Y axis runs up
|
||||
while the X-axis runs to the right). While SVG uses a left-handed coordinate
|
||||
system (the Y axis runs down instead). But Inkscape, surprisingly, also uses a
|
||||
right-handed coordinate system. The DXF export script honors this and places
|
||||
the origin of the document in the lower left corner when exporting the
|
||||
document.
|
||||
|
||||
[1]: Although millimeters seems to be the predominant unit.
|
||||
|
||||
|
||||
### Using SVG files from Asymptote
|
||||
|
||||
SVG files may instead be used from Asymptote files. For each SVG file, an
|
||||
Asymptote file of the same name is generated. These files can be imported as
|
||||
modules from other Asymptote files. These modules will contain a member of
|
||||
type `path[]` for each layer in the original SVG file:
|
||||
|
||||
import test;
|
||||
|
||||
draw(test.Layer_1, red + 0.001mm);
|
||||
|
||||
The module also contains a member `all`, which just contains all paths in one
|
||||
array.
|
||||
|
||||
|
||||
### OpenSCAD files
|
||||
|
||||
Files whose names end in `.scad` are compiled to STL files using OpenSCAD.
|
||||
OpenSCAD files whose name start with `_` are treated as "library" files which
|
||||
will not be compiled to STL files. These files can still be used from other
|
||||
OpenSCAD files using one of the following commands:
|
||||
|
||||
include <filename>
|
||||
use <filename>
|
||||
|
||||
Please see the
|
||||
[OpenSCAD User Manual](http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Print_version)
|
||||
for details this and other OpenSCAD functionality.
|
||||
|
||||
|
||||
## Generating Source files
|
||||
|
||||
This template includes support for automatically generated source files. This
|
||||
works by editing the `generate_sources.sh` script.
|
||||
|
||||
The script defines a function `generate_file()`, which should be called in the
|
||||
remainder of the script once for each file to generate. The first argument to
|
||||
the function is be the name of the file. The remaining arguments are treated as
|
||||
a command, which, when run, should output the file's content to standard
|
||||
output. For example:
|
||||
|
||||
generate_file "src/cube.scad" echo "cube(25);"
|
||||
|
||||
How the function `generate_file()` is called is up to the script and may e.g.
|
||||
be done from a `for` loop or while iterating over a set of other source files.
|
||||
|
||||
|
||||
## Compiling
|
||||
|
||||
To compile the whole project, run `make` from the directory in which this
|
||||
readme is. This will generate all sources files, if any, process all SVG files
|
||||
and produce an STL file for each OpenSCAD source file whose name does not start
|
||||
with `_`. Individual files may be created or updated by passing their names to
|
||||
the make command, as usual.
|
||||
|
||||
|
||||
### Makefile targets
|
||||
|
||||
These are the special makefile targets which can be used in addition to the
|
||||
names of individual files to update:
|
||||
|
||||
- `all`: Builds all files that can be built from any source files. This is the
|
||||
default target when running `make` without arguments.
|
||||
- `clean`: Removes all built files [2].
|
||||
- `generated`: Generates all files generated by `generate_sources.sh`.
|
||||
- `dxf`: Exports all SVG files to DXF files.
|
||||
- `stl`: Compiles all OpenSCAD files to STL files.
|
||||
- `asy`: Exports all configured SVG files to Asymptote files.
|
||||
- `pdf`: Compiles all Asymptote files to PDF files.
|
||||
|
||||
[2]: This will not remove files for which the source file was removed. There is
|
||||
no simple way to detect whether a file was previously built from a source file
|
||||
or if it placed in the `src` directory manually.
|
||||
|
||||
|
||||
### Settings used for compilation
|
||||
|
||||
The quality of the DXF export can be specified by creating a file called
|
||||
`settings.mk` in the same directory as the makefile. Setting `DXF_FLATNESS` to
|
||||
a smaller value (which defaults to `0.1`) creates a shape that more closely
|
||||
follows curved parts of the exported shapes. For example:
|
||||
|
||||
# Specify how far the exported approximation may deviate from the actual
|
||||
# shape. The default is 0.1.
|
||||
DXF_FLATNESS := 0.02
|
||||
|
||||
# Specify which SVG files should be exported to Asymptote files instead of
|
||||
# DXF files. By default, this list is empty.
|
||||
ASYMPTOTE_EXPORTED_SVG_FILES := src/example.svg
|
||||
|
||||
|
||||
### Dependency tracking
|
||||
|
||||
OpenSCAD has the ability to write dependency files which record all files used
|
||||
while producing an STL file. These dependency files can be read by `make`. This
|
||||
ability is used to only recompile necessary files when running make.
|
||||
|
||||
This same mechanism is currently not used for converting SVG files referring to
|
||||
other files or for the script used to generate source files. Therefore, if
|
||||
other file used in the process are changed, the corresponding source files
|
||||
tracked by the makefile (the main SVG files or the files `generate_sources.sh`
|
||||
in case of generated sources) needs to be manually marked as changes by calling
|
||||
`touch` on the file before calling `make`.
|
||||
|
||||
For Asymptote files, a safer approach is currently taken. If any of the
|
||||
Asymptote source files in the `src` directory are changed, all Asymptote source
|
||||
files are recompiled.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
include <_settings.scad>
|
||||
include <_lib.scad>
|
||||
|
||||
jig(height, depth, wall, tolerance, chamfer);
|
||||
jig(height, depth, wall, tolerance, chamfer);
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
import sys, os, shutil
|
||||
from lib import util, make
|
||||
|
||||
|
||||
def _asymptote(in_path, out_path, asymptote_dir, working_dir):
|
||||
args = [os.environ['ASYMPTOTE'], '-vv', '-f', 'pdf', '-o', out_path, in_path]
|
||||
|
||||
with util.command_context(args, set_env={'ASYMPTOTE_DIR': asymptote_dir}, working_dir=working_dir, use_stderr=True) as process:
|
||||
def get_loaded_file(line):
|
||||
if any(line.startswith(j) for j in ['Loading ', 'Including ']):
|
||||
parts = line.rstrip('\n').split(' ')
|
||||
|
||||
if len(parts) == 4:
|
||||
_, _, from_, path = parts
|
||||
|
||||
if from_ == 'from':
|
||||
return path
|
||||
|
||||
return None
|
||||
|
||||
def iter_loaded_files():
|
||||
for i in process.stderr:
|
||||
loaded_file = get_loaded_file(i)
|
||||
|
||||
if loaded_file is not None:
|
||||
yield loaded_file
|
||||
elif not any(i.startswith(j) for j in ['cd ', 'Using configuration ']):
|
||||
print >> sys.stderr, i,
|
||||
|
||||
loaded_files = list(iter_loaded_files())
|
||||
|
||||
return loaded_files
|
||||
|
||||
|
||||
@util.main
|
||||
def main(in_path, out_path):
|
||||
try:
|
||||
_, out_suffix = os.path.splitext(out_path)
|
||||
|
||||
with util.TemporaryDirectory() as temp_dir:
|
||||
absolute_in_path = os.path.abspath(in_path)
|
||||
temp_out_path = os.path.join(temp_dir, 'out.pdf')
|
||||
|
||||
# Asymptote creates A LOT of temp files (presumably when invoking
|
||||
# LaTeX) and leaves some of them behind. Thus we run asymptote
|
||||
# in a temporary directory.
|
||||
loaded_files = _asymptote(
|
||||
absolute_in_path,
|
||||
'out',
|
||||
os.path.dirname(absolute_in_path),
|
||||
temp_dir)
|
||||
|
||||
if not os.path.exists(temp_out_path):
|
||||
raise util.UserError('Asymptote did not generate a PDF file.', in_path)
|
||||
|
||||
# All dependencies as paths relative to the project root.
|
||||
dependencies = set(map(os.path.relpath, loaded_files))
|
||||
|
||||
# Write output files.
|
||||
make.write_dependencies(out_path + '.d', out_path, dependencies - {in_path})
|
||||
shutil.copyfile(temp_out_path, out_path)
|
||||
except util.UserError as e:
|
||||
raise util.UserError('While processing {}: {}', in_path, e)
|
||||
322
support/generate_kicad.py
Executable file
322
support/generate_kicad.py
Executable file
|
|
@ -0,0 +1,322 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from os import path
|
||||
from textwrap import dedent
|
||||
import pkgutil
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as xe
|
||||
|
||||
import ezdxf
|
||||
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
PIN_TS_BASE = 0x23420000
|
||||
TEDIT_BASE = 0x23430000
|
||||
PATH_BASE = 0x23440000
|
||||
|
||||
|
||||
def sch_template(name, num_pins, yspace=200):
|
||||
templ = f'''
|
||||
EESchema Schematic File Version 5
|
||||
EELAYER 30 0
|
||||
EELAYER END
|
||||
$Descr A3 16535 11693
|
||||
encoding utf-8
|
||||
Sheet 1 1
|
||||
Title "{name}"
|
||||
Date "{time.strftime("%d %b %Y")}"
|
||||
Rev ""
|
||||
Comp ""
|
||||
Comment1 ""
|
||||
Comment2 ""
|
||||
Comment3 ""
|
||||
Comment4 ""
|
||||
Comment5 ""
|
||||
Comment6 ""
|
||||
Comment7 ""
|
||||
Comment8 ""
|
||||
Comment9 ""
|
||||
$EndDescr
|
||||
{{components}}
|
||||
$EndSCHEMATC
|
||||
'''
|
||||
|
||||
components = []
|
||||
for i in range(num_pins):
|
||||
identifier = f'TP{i}'
|
||||
value = 'pogopin'
|
||||
x, y = 1000, 1000 + i*yspace
|
||||
components.append(dedent(f'''
|
||||
$Comp
|
||||
L Connector:Conn_01x01_Female {identifier}
|
||||
U 1 1 {PIN_TS_BASE + i:08X}
|
||||
P {x} {y}
|
||||
F 0 "{identifier}" H {x-50} {y+50} 50 0000 R CNN
|
||||
F 1 "{value}" H {x+50} {y} 50 0000 L CNN
|
||||
F 2 "Pogopin:AutogeneratedPogopinFootprint" H {x} {y} 50 0001 C CNN
|
||||
F 3 "~" H {x} {y} 50 0001 C CNN
|
||||
1 {x} {y}
|
||||
-1 0 0 1
|
||||
$EndComp
|
||||
''').strip())
|
||||
|
||||
return dedent(templ).lstrip().format(components='\n'.join(components))
|
||||
|
||||
def pcb_template(outline, pins, annular=0.5):
|
||||
pcb_templ = f'''
|
||||
(kicad_pcb (version 20190605) (host pogojig "({__version__})")
|
||||
|
||||
(general
|
||||
(thickness 1.6)
|
||||
(drawings {len(pins)})
|
||||
(tracks 0)
|
||||
(modules {len(pins)})
|
||||
(nets {len(pins)+1})
|
||||
)
|
||||
|
||||
(page "A4")
|
||||
(layers
|
||||
(0 "F.Cu" signal)
|
||||
(31 "B.Cu" signal)
|
||||
(32 "B.Adhes" user)
|
||||
(33 "F.Adhes" user)
|
||||
(34 "B.Paste" user)
|
||||
(35 "F.Paste" user)
|
||||
(36 "B.SilkS" user)
|
||||
(37 "F.SilkS" user)
|
||||
(38 "B.Mask" user)
|
||||
(39 "F.Mask" user)
|
||||
(40 "Dwgs.User" user)
|
||||
(41 "Cmts.User" user)
|
||||
(42 "Eco1.User" user)
|
||||
(43 "Eco2.User" user)
|
||||
(44 "Edge.Cuts" user)
|
||||
(45 "Margin" user)
|
||||
(46 "B.CrtYd" user)
|
||||
(47 "F.CrtYd" user)
|
||||
(48 "B.Fab" user)
|
||||
(49 "F.Fab" user)
|
||||
)
|
||||
|
||||
(setup
|
||||
(last_trace_width 0.25)
|
||||
(trace_clearance 0.2)
|
||||
(zone_clearance 0.508)
|
||||
(zone_45_only no)
|
||||
(trace_min 0.2)
|
||||
(via_size 0.8)
|
||||
(via_drill 0.4)
|
||||
(via_min_size 0.4)
|
||||
(via_min_drill 0.3)
|
||||
(uvia_size 0.3)
|
||||
(uvia_drill 0.1)
|
||||
(uvias_allowed no)
|
||||
(uvia_min_size 0.2)
|
||||
(uvia_min_drill 0.1)
|
||||
(max_error 0.005)
|
||||
(defaults
|
||||
(edge_clearance 0.01)
|
||||
(edge_cuts_line_width 0.05)
|
||||
(courtyard_line_width 0.05)
|
||||
(copper_line_width 0.2)
|
||||
(copper_text_dims (size 1.5 1.5) (thickness 0.3) keep_upright)
|
||||
(silk_line_width 0.12)
|
||||
(silk_text_dims (size 1 1) (thickness 0.15) keep_upright)
|
||||
(other_layers_line_width 0.1)
|
||||
(other_layers_text_dims (size 1 1) (thickness 0.15) keep_upright)
|
||||
)
|
||||
(pad_size 3.14159 3.14159)
|
||||
(pad_drill 1.41421)
|
||||
(pad_to_mask_clearance 0.051)
|
||||
(solder_mask_min_width 0.25)
|
||||
(aux_axis_origin 0 0)
|
||||
(visible_elements FFFFFF7F)
|
||||
(pcbplotparams
|
||||
(layerselection 0x010fc_ffffffff)
|
||||
(usegerberextensions false)
|
||||
(usegerberattributes false)
|
||||
(usegerberadvancedattributes false)
|
||||
(creategerberjobfile false)
|
||||
(excludeedgelayer true)
|
||||
(linewidth 0.100000)
|
||||
(plotframeref false)
|
||||
(viasonmask false)
|
||||
(mode 1)
|
||||
(useauxorigin false)
|
||||
(hpglpennumber 1)
|
||||
(hpglpenspeed 20)
|
||||
(hpglpendiameter 15.000000)
|
||||
(psnegative false)
|
||||
(psa4output false)
|
||||
(plotreference true)
|
||||
(plotvalue true)
|
||||
(plotinvisibletext false)
|
||||
(padsonsilk false)
|
||||
(subtractmaskfromsilk false)
|
||||
(outputformat 1)
|
||||
(mirror false)
|
||||
(drillshape 1)
|
||||
(scaleselection 1)
|
||||
(outputdirectory ""))
|
||||
)
|
||||
|
||||
(net 0 "")
|
||||
{{net_defs}}
|
||||
|
||||
(net_class "Default" "This is the default net class."
|
||||
(clearance 0.2)
|
||||
(trace_width 0.25)
|
||||
(via_dia 0.8)
|
||||
(via_drill 0.4)
|
||||
(uvia_dia 0.3)
|
||||
(uvia_drill 0.1)
|
||||
{{net_class_defs}}
|
||||
)
|
||||
|
||||
{{module_defs}}
|
||||
|
||||
{{edge_cuts}}
|
||||
)'''
|
||||
|
||||
module_defs = []
|
||||
for i, pin in enumerate(pins):
|
||||
(x, y), hole_dia = pin # all dimensions in mm here
|
||||
pad_dia = hole_dia + 2*annular
|
||||
mod = f'''
|
||||
(module "Pogopin:AutogeneratedPogopinFootprint" (layer "F.Cu") (tedit {TEDIT_BASE + i:08X}) (tstamp {PIN_TS_BASE + i:08X})
|
||||
(at {x} {y})
|
||||
(descr "Pogo pin {i}")
|
||||
(tags "test point plated hole")
|
||||
(path "/{PATH_BASE + i:08X}")
|
||||
(attr virtual)
|
||||
(fp_text reference "TP{i}" (at 0 -{pad_dia/2 + 1}) (layer "F.SilkS")
|
||||
(effects (font (size 1 1) (thickness 0.15)))
|
||||
)
|
||||
(fp_text value "pogo pin {i}" (at 0 {pad_dia/2 + 1}) (layer "F.Fab")
|
||||
(effects (font (size 1 1) (thickness 0.15)))
|
||||
)
|
||||
(fp_text user "%R" (at 0 -{pad_dia/2 + 1}) (layer "F.Fab")
|
||||
(effects (font (size 1 1) (thickness 0.15)))
|
||||
)
|
||||
(fp_circle (center 0 0) (end {pad_dia} 0) (layer "F.CrtYd") (width 0.05))
|
||||
(fp_circle (center 0 0) (end 0 -{pad_dia}) (layer "F.SilkS") (width 0.12))
|
||||
(pad "1" thru_hole circle (at 0 0) (size {pad_dia} {pad_dia}) (drill {hole_dia}) (layers *.Cu *.Mask)
|
||||
(net {i+1} "pogo{i}"))
|
||||
)'''
|
||||
module_defs.append(mod)
|
||||
|
||||
edge_cuts = [ f'(gr_line (start {x1} {y1}) (end {x2} {y2}) (layer "Edge.Cuts") (width 0.05))'
|
||||
for (x1, y1), (x2, y2) in outline ]
|
||||
|
||||
net_defs = [ f'(net {i+1} "pogo{i}")' for i, _pin in enumerate(pins) ]
|
||||
net_class_defs = [ f'(add_net "pogo{i}")' for i, _pin in enumerate(pins) ]
|
||||
return pcb_templ.format(
|
||||
net_defs='\n'.join(net_defs),
|
||||
net_class_defs='\n'.join(net_class_defs),
|
||||
module_defs='\n'.join(module_defs),
|
||||
edge_cuts='\n'.join(edge_cuts))
|
||||
|
||||
def inkscape_query_all(filename):
|
||||
proc = subprocess.run([ os.environ.get('INKSCAPE', 'inkscape'), filename, '--query-all'], capture_output=True)
|
||||
proc.check_returncode()
|
||||
data = [ line.split(',') for line in proc.stdout.decode().splitlines() ]
|
||||
return { id: (float(x), float(y), float(w), float(h)) for id, x, y, w, h in data }
|
||||
|
||||
SVG_NS = {
|
||||
'svg': 'http://www.w3.org/2000/svg',
|
||||
'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
|
||||
'sodipodi': 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'
|
||||
}
|
||||
|
||||
def svg_find_elements(doc, tag, layer=None):
|
||||
for i, g in enumerate(doc.findall('svg:g', SVG_NS)):
|
||||
if g.attrib.get(f'{{{SVG_NS["inkscape"]}}}groupmode') != 'layer':
|
||||
continue
|
||||
|
||||
label = g.attrib.get(f'{{{SVG_NS["inkscape"]}}}label', '')
|
||||
if not layer or label == layer:
|
||||
yield from g.iter(tag)
|
||||
|
||||
# def svg_get_scale(doc):
|
||||
# w = doc.attrib['width']
|
||||
# h = doc.attrib['height']
|
||||
#
|
||||
# if not w.endswith('mm') and h.endswith('mm'):
|
||||
# raise ValueError('Document dimensions in SVG must be set to millimeters')
|
||||
#
|
||||
# w, h = float(w[:-2]), float(h[:-2])
|
||||
# _x, _y, vb_w, vb_h = map(float, doc.attrib['viewBox'].split())
|
||||
# scale_x, scale_y = vb_w / w, vb_h / h
|
||||
# assert abs(1 - scale_x/scale_y) < 0.001
|
||||
# return scale_x
|
||||
|
||||
def svg_get_viewbox_mm(doc):
|
||||
w = doc.attrib['width']
|
||||
h = doc.attrib['height']
|
||||
|
||||
if not w.endswith('mm') and h.endswith('mm'):
|
||||
raise ValueError('Document dimensions in SVG must be set to millimeters')
|
||||
|
||||
w, h = float(w[:-2]), float(h[:-2])
|
||||
x, y, vb_w, vb_h = map(float, doc.attrib['viewBox'].split())
|
||||
scale_x, scale_y = vb_w / w, vb_h / h
|
||||
return x/scale_x, y/scale_y, w, h
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('svg', metavar='pogo_map.svg', help='Input inkscape SVG pogo pin map (use provided template!)')
|
||||
parser.add_argument('outline', metavar='outline.dxf', help='Board outline DXF generated by OpenSCAD')
|
||||
parser.add_argument('output', default='kicad', help='Output directory/project name and path')
|
||||
parser.add_argument('-y', '--yspace', type=int, default=200, help='Schematic pin Y spacing in mil (default: 200)')
|
||||
parser.add_argument('-a', '--annular', type=float, default=0.5, help='Pogo pin annular ring width in mm (default: 0.5)')
|
||||
parser.add_argument('-l', '--svg-layer', type=str, default='Test Points', help='Name of SVG layer containing pogo pins')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not path.exists(args.output):
|
||||
os.mkdir(args.output)
|
||||
|
||||
if not path.isdir(args.output):
|
||||
raise SystemError(f'Output path "{args.output}" is not a directory')
|
||||
|
||||
with open(args.svg, 'r') as f:
|
||||
doc = xe.fromstring(f.read())
|
||||
pogo_circle_ids = [ circle.attrib['id'] for circle in svg_find_elements(doc, f'{{{SVG_NS["svg"]}}}circle', args.svg_layer) ]
|
||||
# scale = svg_get_scale(doc)
|
||||
page_x, page_y, page_w, page_h = svg_get_viewbox_mm(doc)
|
||||
MM_PER_IN = 25.4
|
||||
SVG_DEF_DPI = 96
|
||||
px_to_mm = lambda px: px/SVG_DEF_DPI * MM_PER_IN
|
||||
query = inkscape_query_all(args.svg)
|
||||
dims = [ query[id] for id in pogo_circle_ids ]
|
||||
assert all( abs(1 - w/h) < 0.001 for _x, _y, w, h in dims )
|
||||
print('origin:', page_x, page_y)
|
||||
print('dims:', page_w, page_h)
|
||||
pins = [ (
|
||||
(page_x + px_to_mm(x) + px_to_mm(w)/2,
|
||||
page_y - page_h + px_to_mm(y) + px_to_mm(w)/2),
|
||||
px_to_mm(w)) for x, y, w, h in dims ]
|
||||
|
||||
doc = ezdxf.readfile(args.outline)
|
||||
outline = []
|
||||
for line in doc.modelspace().query('LINE'):
|
||||
(x1, y1, _z1), (x2, y2, _z2) = line.dxf.start, line.dxf.end
|
||||
outline.append(((x1, -y1), (x2, -y2)))
|
||||
|
||||
out_name = path.basename(args.output)
|
||||
with open(path.join(args.output, f'{out_name}.sch'), 'w', encoding='utf8') as sch:
|
||||
sch.write(sch_template(f'{out_name} generated schematic (PogoJig v{__version__})', len(pins), yspace=args.yspace))
|
||||
|
||||
with open(path.join(args.output, f'{out_name}.kicad_pcb'), 'w', encoding='utf8') as pcb:
|
||||
pcb.write(pcb_template(outline, pins, annular=args.annular))
|
||||
|
||||
with open(path.join(args.output, f'{out_name}.pro'), 'w', encoding='utf8') as f:
|
||||
f.write(pkgutil.get_data('pogojig.kicad', 'kicad.pro').decode('utf8'))
|
||||
|
||||
with open(path.join(args.output, f'{out_name}-cache.lib'), 'w', encoding='utf8') as f:
|
||||
f.write(pkgutil.get_data('pogojig.kicad', 'kicad-cache.lib').decode('utf8'))
|
||||
|
||||
53
support/inkscape/__main__.py → support/inkscape_exporter.py
Normal file → Executable file
53
support/inkscape/__main__.py → support/inkscape_exporter.py
Normal file → Executable file
|
|
@ -1,6 +1,10 @@
|
|||
import os, shutil
|
||||
from lib import util
|
||||
from . import effect, inkscape
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from pogojig.inkscape import effect, inkscape
|
||||
|
||||
|
||||
def _unfuck_svg_document(temp_svg_path):
|
||||
|
|
@ -32,33 +36,26 @@ def _unfuck_svg_document(temp_svg_path):
|
|||
command_line.delete_layer(copy)
|
||||
|
||||
command_line.apply_to_document('FileSave', 'FileClose', 'FileQuit')
|
||||
|
||||
command_line.run()
|
||||
|
||||
|
||||
@util.main
|
||||
def main(in_path, out_path):
|
||||
try:
|
||||
_, out_suffix = os.path.splitext(out_path)
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('infile', metavar='input.svg', help='Inkscape SVG input file')
|
||||
parser.add_argument('outfile', metavar='output.dxf', help='DXF output file')
|
||||
args = parser.parse_args()
|
||||
|
||||
effect.ExportEffect.check_document_units(args.infile)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
temp_svg_path = os.path.join(tmpdir, os.path.basename(args.infile))
|
||||
shutil.copyfile(args.infile, temp_svg_path)
|
||||
|
||||
effect.ExportEffect.check_document_units(in_path)
|
||||
_unfuck_svg_document(temp_svg_path)
|
||||
|
||||
with util.TemporaryDirectory() as temp_dir:
|
||||
temp_svg_path = os.path.join(temp_dir, os.path.basename(in_path))
|
||||
|
||||
shutil.copyfile(in_path, temp_svg_path)
|
||||
|
||||
_unfuck_svg_document(temp_svg_path)
|
||||
|
||||
export_effect = effect.ExportEffect()
|
||||
export_effect.affect(args=[temp_svg_path], output=False)
|
||||
|
||||
with open(out_path, 'w') as file:
|
||||
if out_suffix == '.dxf':
|
||||
export_effect.write_dxf(file)
|
||||
elif out_suffix == '.asy':
|
||||
export_effect.write_asy(file)
|
||||
else:
|
||||
raise Exception('Unknown file type: {}'.format(out_suffix))
|
||||
except util.UserError as e:
|
||||
raise util.UserError('While processing {}: {}', in_path, e)
|
||||
export_effect = effect.ExportEffect()
|
||||
export_effect.affect(args=[temp_svg_path], output=False)
|
||||
|
||||
with open(args.outfile, 'w') as f:
|
||||
export_effect.write_dxf(f)
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
from . import util
|
||||
|
||||
|
||||
def write_dependencies(path, target, dependencies):
|
||||
util.write_file(path, '{}: {}\n'.format(target, ' '.join(dependencies)).encode())
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
import contextlib
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
class UserError(Exception):
|
||||
def __init__(self, message, *args):
|
||||
super(UserError, self).__init__(message.format(*args))
|
||||
|
||||
|
||||
def main(fn):
|
||||
"""
|
||||
Decorator for "main" functions. Decorates a function that should be
|
||||
called when the containing module is run as a script (e.g. via python -m
|
||||
<module>).
|
||||
"""
|
||||
frame = inspect.currentframe().f_back
|
||||
|
||||
def wrapped_fn(*args, **kwargs):
|
||||
try:
|
||||
fn(*args, **kwargs)
|
||||
except UserError as e:
|
||||
print >> sys.stderr, 'Error:', e
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(2)
|
||||
|
||||
if frame.f_globals['__name__'] == '__main__':
|
||||
wrapped_fn(*sys.argv[1:])
|
||||
|
||||
# Allow the main function also to be called explicitly
|
||||
return wrapped_fn
|
||||
|
||||
|
||||
def rename_atomic(source_path, target_path):
|
||||
"""
|
||||
Move the file at source_path to target_path.
|
||||
|
||||
If both paths reside on the same device, os.rename() is used, otherwise
|
||||
the file is copied to a temporary name next to target_path and moved from
|
||||
there using os.rename().
|
||||
"""
|
||||
source_dir_stat = os.stat(os.path.dirname(source_path))
|
||||
target_dir_stat = os.stat(os.path.dirname(target_path))
|
||||
|
||||
if source_dir_stat.st_dev == target_dir_stat.st_dev:
|
||||
os.rename(source_path, target_path)
|
||||
else:
|
||||
temp_path = target_path + '~'
|
||||
|
||||
shutil.copyfile(source_path, temp_path)
|
||||
os.rename(temp_path, target_path)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def TemporaryDirectory():
|
||||
dir = tempfile.mkdtemp()
|
||||
|
||||
try:
|
||||
yield dir
|
||||
finally:
|
||||
shutil.rmtree(dir)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def command_context(args, remove_env=[], set_env={}, working_dir=None, use_stderr=False):
|
||||
env = dict(os.environ)
|
||||
|
||||
for i in remove_env:
|
||||
del env[i]
|
||||
|
||||
for k, v in set_env.items():
|
||||
env[k] = v
|
||||
|
||||
if use_stderr:
|
||||
stderr = subprocess.PIPE
|
||||
else:
|
||||
stderr = None
|
||||
|
||||
try:
|
||||
process = subprocess.Popen(args, env=env, cwd=working_dir, stderr=stderr)
|
||||
except OSError as e:
|
||||
raise UserError('Error running {}: {}', args[0], e)
|
||||
|
||||
try:
|
||||
yield process
|
||||
except:
|
||||
try:
|
||||
process.kill()
|
||||
except OSError:
|
||||
# Ignore exceptions here so we don't mask the
|
||||
# already-being-thrown exception.
|
||||
pass
|
||||
|
||||
raise
|
||||
finally:
|
||||
# Use communicate so that we won't deadlock if the process generates
|
||||
# some unread output.
|
||||
process.communicate()
|
||||
|
||||
if process.returncode:
|
||||
raise UserError('Command failed: {}', ' '.join(args))
|
||||
|
||||
|
||||
def command(args, remove_env=[], set_env={}, working_dir=None):
|
||||
with command_context(args, remove_env, set_env, working_dir):
|
||||
pass
|
||||
|
||||
|
||||
def bash_escape_string(string):
|
||||
return "'{}'".format(re.sub("'", "'\"'\"'", string))
|
||||
|
||||
|
||||
def write_file(path, data):
|
||||
temp_path = path + '~'
|
||||
|
||||
with open(temp_path, 'wb') as file:
|
||||
file.write(data)
|
||||
|
||||
os.rename(temp_path, path)
|
||||
|
||||
|
||||
def read_file(path):
|
||||
with open(path, 'rb') as file:
|
||||
return file.read()
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import os
|
||||
|
||||
from lib import util, make
|
||||
|
||||
|
||||
def _openscad(in_path, out_path, deps_path):
|
||||
util.command([os.environ['OPENSCAD'], '-o', out_path, '-d', deps_path, in_path])
|
||||
|
||||
|
||||
@util.main
|
||||
def main(in_path, out_path):
|
||||
cwd = os.getcwd()
|
||||
|
||||
def relpath(path):
|
||||
return os.path.relpath(path, cwd)
|
||||
|
||||
with util.TemporaryDirectory() as temp_dir:
|
||||
temp_deps_path = os.path.join(temp_dir, 'deps')
|
||||
temp_mk_path = os.path.join(temp_dir, 'mk')
|
||||
temp_files_path = os.path.join(temp_dir, 'files')
|
||||
|
||||
_, out_ext = os.path.splitext(out_path)
|
||||
|
||||
# OpenSCAD requires the output file name to end in .stl or .dxf.
|
||||
temp_out_path = os.path.join(temp_dir, 'out' + out_ext)
|
||||
|
||||
_openscad(in_path, temp_out_path, temp_deps_path)
|
||||
|
||||
mk_content = '%:; echo "$@" >> {}'.format(util.bash_escape_string(temp_files_path))
|
||||
|
||||
# Use make to parse the dependency makefile written by OpenSCAD.
|
||||
util.write_file(temp_mk_path, mk_content.encode())
|
||||
util.command(
|
||||
['make', '-s', '-B', '-f', temp_mk_path, '-f', temp_deps_path],
|
||||
remove_env=['MAKELEVEL', 'MAKEFLAGS'])
|
||||
|
||||
# All dependencies as paths relative to the project root.
|
||||
deps = set(map(relpath, util.read_file(temp_files_path).decode().splitlines()))
|
||||
|
||||
# Relative paths to all files that should not appear in the
|
||||
# dependency makefile.
|
||||
ignored_files = set(map(relpath, [in_path, temp_deps_path, temp_mk_path, temp_out_path]))
|
||||
|
||||
# Write output files.
|
||||
make.write_dependencies(out_path + '.d', out_path, deps - ignored_files)
|
||||
util.rename_atomic(temp_out_path, out_path)
|
||||
38
support/inkscape/bezmisc.py → support/pogojig/inkscape/bezmisc.py
Executable file → Normal file
38
support/inkscape/bezmisc.py → support/pogojig/inkscape/bezmisc.py
Executable file → Normal file
|
|
@ -55,8 +55,9 @@ def rootWrapper(a,b,c,d):
|
|||
return 1.0*(-d/c),
|
||||
return ()
|
||||
|
||||
def bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))):
|
||||
def bezierparameterize(points):
|
||||
#parametric bezier
|
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = points
|
||||
x0=bx0
|
||||
y0=by0
|
||||
cx=3*(bx1-x0)
|
||||
|
|
@ -69,8 +70,10 @@ def bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))):
|
|||
return ax,ay,bx,by,cx,cy,x0,y0
|
||||
#ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
|
||||
|
||||
def linebezierintersect(((lx1,ly1),(lx2,ly2)),((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))):
|
||||
def linebezierintersect(line, bezier):
|
||||
#parametric line
|
||||
((lx1,ly1),(lx2,ly2)) = line
|
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = bezier
|
||||
dd=lx1
|
||||
cc=lx2-lx1
|
||||
bb=ly1
|
||||
|
|
@ -99,19 +102,23 @@ def linebezierintersect(((lx1,ly1),(lx2,ly2)),((bx0,by0),(bx1,by1),(bx2,by2),(bx
|
|||
retval.append(bezierpointatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),i))
|
||||
return retval
|
||||
|
||||
def bezierpointatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
|
||||
def bezierpointatt(xxx_todo_changeme3,t):
|
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme3
|
||||
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
|
||||
x=ax*(t**3)+bx*(t**2)+cx*t+x0
|
||||
y=ay*(t**3)+by*(t**2)+cy*t+y0
|
||||
return x,y
|
||||
|
||||
def bezierslopeatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
|
||||
def bezierslopeatt(xxx_todo_changeme4,t):
|
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme4
|
||||
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
|
||||
dx=3*ax*(t**2)+2*bx*t+cx
|
||||
dy=3*ay*(t**2)+2*by*t+cy
|
||||
return dx,dy
|
||||
|
||||
def beziertatslope(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),(dy,dx)):
|
||||
def beziertatslope(xxx_todo_changeme5, xxx_todo_changeme6):
|
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme5
|
||||
(dy,dx) = xxx_todo_changeme6
|
||||
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
|
||||
#quadratic coefficents of slope formula
|
||||
if dx:
|
||||
|
|
@ -136,9 +143,12 @@ def beziertatslope(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),(dy,dx)):
|
|||
retval.append(i)
|
||||
return retval
|
||||
|
||||
def tpoint((x1,y1),(x2,y2),t):
|
||||
def tpoint(xxx_todo_changeme7, xxx_todo_changeme8,t):
|
||||
(x1,y1) = xxx_todo_changeme7
|
||||
(x2,y2) = xxx_todo_changeme8
|
||||
return x1+t*(x2-x1),y1+t*(y2-y1)
|
||||
def beziersplitatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
|
||||
def beziersplitatt(xxx_todo_changeme9,t):
|
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme9
|
||||
m1=tpoint((bx0,by0),(bx1,by1),t)
|
||||
m2=tpoint((bx1,by1),(bx2,by2),t)
|
||||
m3=tpoint((bx2,by2),(bx3,by3),t)
|
||||
|
|
@ -167,7 +177,9 @@ Jens Gravesen <gravesen@mat.dth.dk>
|
|||
mat-report no. 1992-10, Mathematical Institute, The Technical
|
||||
University of Denmark.
|
||||
'''
|
||||
def pointdistance((x1,y1),(x2,y2)):
|
||||
def pointdistance(xxx_todo_changeme10, xxx_todo_changeme11):
|
||||
(x1,y1) = xxx_todo_changeme10
|
||||
(x2,y2) = xxx_todo_changeme11
|
||||
return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
|
||||
def Gravesen_addifclose(b, len, error = 0.001):
|
||||
box = 0
|
||||
|
|
@ -208,19 +220,21 @@ def Simpson(f, a, b, n_limit, tolerance):
|
|||
asum += bsum
|
||||
bsum = 0.0
|
||||
est0 = est1
|
||||
for i in xrange(1, n, 2):
|
||||
for i in range(1, n, 2):
|
||||
bsum += f(a + (i * interval))
|
||||
est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum))
|
||||
#print multiplier, endsum, interval, asum, bsum, est1, est0
|
||||
return est1
|
||||
|
||||
def bezierlengthSimpson(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)), tolerance = 0.001):
|
||||
def bezierlengthSimpson(xxx_todo_changeme12, tolerance = 0.001):
|
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme12
|
||||
global balfax,balfbx,balfcx,balfay,balfby,balfcy
|
||||
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
|
||||
balfax,balfbx,balfcx,balfay,balfby,balfcy = 3*ax,2*bx,cx,3*ay,2*by,cy
|
||||
return Simpson(balf, 0.0, 1.0, 4096, tolerance)
|
||||
|
||||
def beziertatlength(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)), l = 0.5, tolerance = 0.001):
|
||||
def beziertatlength(xxx_todo_changeme13, l = 0.5, tolerance = 0.001):
|
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme13
|
||||
global balfax,balfbx,balfcx,balfay,balfby,balfcy
|
||||
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
|
||||
balfax,balfbx,balfcx,balfay,balfby,balfcy = 3*ax,2*bx,cx,3*ay,2*by,cy
|
||||
|
|
@ -267,7 +281,7 @@ if __name__ == '__main__':
|
|||
print s, st
|
||||
'''
|
||||
for curve in curves:
|
||||
print beziertatlength(curve,0.5)
|
||||
print(beziertatlength(curve,0.5))
|
||||
|
||||
|
||||
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
|
||||
7
support/inkscape/cspsubdiv.py → support/pogojig/inkscape/cspsubdiv.py
Executable file → Normal file
7
support/inkscape/cspsubdiv.py → support/pogojig/inkscape/cspsubdiv.py
Executable file → Normal file
|
|
@ -1,8 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
from bezmisc import *
|
||||
from ffgeom import *
|
||||
from .bezmisc import *
|
||||
from .ffgeom import *
|
||||
|
||||
def maxdist(((p0x,p0y),(p1x,p1y),(p2x,p2y),(p3x,p3y))):
|
||||
def maxdist(points):
|
||||
((p0x,p0y),(p1x,p1y),(p2x,p2y),(p3x,p3y)) = points
|
||||
p0 = Point(p0x,p0y)
|
||||
p1 = Point(p1x,p1y)
|
||||
p2 = Point(p2x,p2y)
|
||||
2
support/inkscape/cubicsuperpath.py → support/pogojig/inkscape/cubicsuperpath.py
Executable file → Normal file
2
support/inkscape/cubicsuperpath.py → support/pogojig/inkscape/cubicsuperpath.py
Executable file → Normal file
|
|
@ -19,7 +19,7 @@ along with this program; if not, write to the Free Software
|
|||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
"""
|
||||
import simplepath
|
||||
from . import simplepath
|
||||
from math import *
|
||||
|
||||
def matprod(mlist):
|
||||
|
|
@ -10,7 +10,6 @@ import pkgutil
|
|||
import re
|
||||
from lxml import etree
|
||||
|
||||
from lib import util
|
||||
from . import inkex, simpletransform, cubicsuperpath, cspsubdiv, inkscape
|
||||
|
||||
|
||||
|
|
@ -39,7 +38,7 @@ class ExportEffect(inkex.Effect):
|
|||
def __init__(self):
|
||||
inkex.Effect.__init__(self)
|
||||
|
||||
self._flatness = float(os.environ['DXF_FLATNESS'])
|
||||
self._flatness = float(os.environ.get('INKSCAPE_DXF_FLATNESS', 0.1))
|
||||
|
||||
self._layers = None
|
||||
self._paths = None
|
||||
|
|
@ -139,11 +138,11 @@ class ExportEffect(inkex.Effect):
|
|||
|
||||
layer_indices = {l: i for i, l in enumerate(self._layers)}
|
||||
|
||||
file.write(pkgutil.get_data(__name__, 'dxf_header.txt'))
|
||||
file.write(pkgutil.get_data(__name__, 'dxf_header.txt').decode('ASCII'))
|
||||
|
||||
def write_instruction(code, value):
|
||||
print >> file, code
|
||||
print >> file, value
|
||||
print(code, file=file)
|
||||
print(value, file=file)
|
||||
|
||||
handle_iter = itertools.count(256)
|
||||
|
||||
|
|
@ -165,7 +164,7 @@ class ExportEffect(inkex.Effect):
|
|||
write_instruction(21, repr(y2 / unit_factor))
|
||||
write_instruction(31, 0.0)
|
||||
|
||||
file.write(pkgutil.get_data(__name__, 'dxf_footer.txt'))
|
||||
file.write(pkgutil.get_data(__name__, 'dxf_footer.txt').decode('ASCII'))
|
||||
|
||||
def write_asy(self, file):
|
||||
def write_line(format, *args):
|
||||
|
|
@ -263,18 +262,18 @@ class ExportEffect(inkex.Effect):
|
|||
height_attr = document.getroot().get('height')
|
||||
|
||||
if height_attr is None:
|
||||
raise util.UserError(
|
||||
raise ValueError(
|
||||
'SVG document has no height attribute. See '
|
||||
'https://github.com/Feuermurmel/openscad-template/wiki/Absolute-Measurements')
|
||||
|
||||
_, height_unit = cls._parse_measure(height_attr)
|
||||
|
||||
if height_unit is None or height_unit == 'px':
|
||||
raise util.UserError(
|
||||
raise ValueError(
|
||||
'Height of SVG document is not an absolute measure. See '
|
||||
'https://github.com/Feuermurmel/openscad-template/wiki/Absolute-Measurements')
|
||||
|
||||
if document.getroot().get('viewBox') is None:
|
||||
raise util.UserError(
|
||||
raise ValueError(
|
||||
'SVG document has no viewBox attribute. See '
|
||||
'https://github.com/Feuermurmel/openscad-template/wiki/Absolute-Measurements')
|
||||
18
support/inkscape/ffgeom.py → support/pogojig/inkscape/ffgeom.py
Executable file → Normal file
18
support/inkscape/ffgeom.py → support/pogojig/inkscape/ffgeom.py
Executable file → Normal file
|
|
@ -20,11 +20,6 @@
|
|||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""
|
||||
import math
|
||||
try:
|
||||
NaN = float('NaN')
|
||||
except ValueError:
|
||||
PosInf = 1e300000
|
||||
NaN = PosInf/PosInf
|
||||
|
||||
class Point:
|
||||
precision = 5
|
||||
|
|
@ -72,11 +67,11 @@ class Segment:
|
|||
def slope(self):
|
||||
if self.delta_x() != 0:
|
||||
return self.delta_x() / self.delta_y()
|
||||
return NaN
|
||||
return math.nan
|
||||
def intercept(self):
|
||||
if self.delta_x() != 0:
|
||||
return self[1]['y'] - (self[0]['x'] * self.slope())
|
||||
return NaN
|
||||
return math.nan
|
||||
def distanceToPoint(self, p):
|
||||
s2 = Segment(self[0],p)
|
||||
c1 = dot(s2,self)
|
||||
|
|
@ -88,7 +83,8 @@ class Segment:
|
|||
return self.perpDistanceToPoint(p)
|
||||
def perpDistanceToPoint(self, p):
|
||||
len = self.length()
|
||||
if len == 0: return NaN
|
||||
if len == 0:
|
||||
return math.nan
|
||||
return math.fabs(((self[1]['x'] - self[0]['x']) * (self[0]['y'] - p['y'])) - \
|
||||
((self[0]['x'] - p['x']) * (self[1]['y'] - self[0]['y']))) / len
|
||||
def angle(self):
|
||||
|
|
@ -96,13 +92,13 @@ class Segment:
|
|||
def length(self):
|
||||
return math.sqrt((self.delta_x() ** 2) + (self.delta_y() ** 2))
|
||||
def pointAtLength(self, len):
|
||||
if self.length() == 0: return Point(NaN, NaN)
|
||||
if self.length() == 0: return Point(math.nan, math.nan)
|
||||
ratio = len / self.length()
|
||||
x = self[0]['x'] + (ratio * self.delta_x())
|
||||
y = self[0]['y'] + (ratio * self.delta_y())
|
||||
return Point(x, y)
|
||||
def pointAtRatio(self, ratio):
|
||||
if self.length() == 0: return Point(NaN, NaN)
|
||||
if self.length() == 0: return Point(math.nan, math.nan)
|
||||
x = self[0]['x'] + (ratio * self.delta_x())
|
||||
y = self[0]['y'] + (ratio * self.delta_y())
|
||||
return Point(x, y)
|
||||
|
|
@ -132,7 +128,7 @@ def intersectSegments(s1, s2):
|
|||
x = x1 + ((num / denom) * (x2 - x1))
|
||||
y = y1 + ((num / denom) * (y2 - y1))
|
||||
return Point(x, y)
|
||||
return Point(NaN, NaN)
|
||||
return Point(math.nan, math.nan)
|
||||
|
||||
def dot(s1, s2):
|
||||
return s1.delta_x() * s2.delta_x() + s1.delta_y() * s2.delta_y()
|
||||
13
support/inkscape/inkex.py → support/pogojig/inkscape/inkex.py
Executable file → Normal file
13
support/inkscape/inkex.py → support/pogojig/inkscape/inkex.py
Executable file → Normal file
|
|
@ -35,6 +35,8 @@ import re
|
|||
import sys
|
||||
from math import *
|
||||
|
||||
from lxml import etree
|
||||
|
||||
#a dictionary of all of the xmlns prefixes in a standard inkscape doc
|
||||
NSS = {
|
||||
u'sodipodi' :u'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
|
||||
|
|
@ -107,15 +109,6 @@ def are_near_relative(a, b, eps):
|
|||
else:
|
||||
return False
|
||||
|
||||
|
||||
# third party library
|
||||
try:
|
||||
from lxml import etree
|
||||
except Exception, e:
|
||||
localize()
|
||||
errormsg(_("The fantastic lxml wrapper for libxml2 is required by inkex.py and therefore this extension. Please download and install the latest version from http://cheeseshop.python.org/pypi/lxml/, or install it through your package manager by a command like: sudo apt-get install python-lxml\n\nTechnical details:\n%s" % (e,)))
|
||||
sys.exit()
|
||||
|
||||
def check_inkbool(option, opt, value):
|
||||
if str(value).capitalize() == 'True':
|
||||
return True
|
||||
|
|
@ -126,7 +119,7 @@ def check_inkbool(option, opt, value):
|
|||
|
||||
def addNS(tag, ns=None):
|
||||
val = tag
|
||||
if ns!=None and len(ns)>0 and NSS.has_key(ns) and len(tag)>0 and tag[0]!='{':
|
||||
if ns!=None and len(ns)>0 and ns in NSS and len(tag)>0 and tag[0]!='{':
|
||||
val = "{%s}%s" % (NSS[ns], tag)
|
||||
return val
|
||||
|
||||
|
|
@ -1,46 +1,34 @@
|
|||
import os
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
from lib import util
|
||||
|
||||
|
||||
def get_inkscape_layers(svg_path):
|
||||
document = etree.parse(svg_path)
|
||||
|
||||
def iter_layers():
|
||||
nodes = document.findall(
|
||||
'{http://www.w3.org/2000/svg}g[@{http://www.inkscape.org/namespaces/inkscape}groupmode="layer"]')
|
||||
|
||||
for i in nodes:
|
||||
inkscape_name = i.get('{http://www.inkscape.org/namespaces/inkscape}label').strip()
|
||||
|
||||
if inkscape_name.endswith(']'):
|
||||
export_name, args = inkscape_name[:-1].rsplit('[', 1)
|
||||
|
||||
export_name = export_name.strip()
|
||||
args = args.strip()
|
||||
|
||||
use_paths = 'p' in args
|
||||
else:
|
||||
use_paths = False
|
||||
export_name = inkscape_name
|
||||
|
||||
yield Layer(inkscape_name, export_name, use_paths)
|
||||
layers = []
|
||||
nodes = document.findall(
|
||||
'{http://www.w3.org/2000/svg}g[@{http://www.inkscape.org/namespaces/inkscape}groupmode="layer"]')
|
||||
|
||||
return list(iter_layers())
|
||||
for i in nodes:
|
||||
inkscape_name = i.get('{http://www.inkscape.org/namespaces/inkscape}label').strip()
|
||||
|
||||
if inkscape_name.endswith(']'):
|
||||
export_name, args = inkscape_name[:-1].rsplit('[', 1)
|
||||
|
||||
export_name = export_name.strip()
|
||||
args = args.strip()
|
||||
|
||||
use_paths = 'p' in args
|
||||
else:
|
||||
use_paths = False
|
||||
export_name = inkscape_name
|
||||
|
||||
layers.append(Layer(inkscape_name, export_name, use_paths))
|
||||
return layers
|
||||
|
||||
|
||||
def _inkscape(svg_path, verbs):
|
||||
def iter_args():
|
||||
yield os.environ['INKSCAPE']
|
||||
|
||||
for i in verbs:
|
||||
yield '--verb'
|
||||
yield i
|
||||
|
||||
yield svg_path
|
||||
|
||||
util.command(list(iter_args()))
|
||||
subprocess.run([os.environ.get('INKSCAPE', 'inkscape'), *(x for verb in verbs for x in ('--verb', verb)), svg_path])
|
||||
|
||||
|
||||
class Layer(object):
|
||||
15
support/inkscape/simplepath.py → support/pogojig/inkscape/simplepath.py
Executable file → Normal file
15
support/inkscape/simplepath.py → support/pogojig/inkscape/simplepath.py
Executable file → Normal file
|
|
@ -49,7 +49,7 @@ def lexPath(d):
|
|||
offset = m.end()
|
||||
continue
|
||||
#TODO: create new exception
|
||||
raise Exception, 'Invalid path data!'
|
||||
raise ValueError('Invalid path data!')
|
||||
'''
|
||||
pathdefs = {commandfamily:
|
||||
[
|
||||
|
|
@ -71,6 +71,7 @@ pathdefs = {
|
|||
'A':['A', 7, [float, float, float, int, int, float, float], ['r','r','a',0,'s','x','y']],
|
||||
'Z':['L', 0, [], []]
|
||||
}
|
||||
|
||||
def parsePath(d):
|
||||
"""
|
||||
Parse SVG path and return an array of segments.
|
||||
|
|
@ -87,14 +88,14 @@ def parsePath(d):
|
|||
|
||||
while 1:
|
||||
try:
|
||||
token, isCommand = lexer.next()
|
||||
token, isCommand = next(lexer)
|
||||
except StopIteration:
|
||||
break
|
||||
params = []
|
||||
needParam = True
|
||||
if isCommand:
|
||||
if not lastCommand and token.upper() != 'M':
|
||||
raise Exception, 'Invalid path, must begin with moveto.'
|
||||
raise ValueError('Invalid path, must begin with moveto.')
|
||||
else:
|
||||
command = token
|
||||
else:
|
||||
|
|
@ -107,16 +108,16 @@ def parsePath(d):
|
|||
else:
|
||||
command = pathdefs[lastCommand.upper()][0].lower()
|
||||
else:
|
||||
raise Exception, 'Invalid path, no initial command.'
|
||||
raise ValueError('Invalid path, no initial command.')
|
||||
numParams = pathdefs[command.upper()][1]
|
||||
while numParams > 0:
|
||||
if needParam:
|
||||
try:
|
||||
token, isCommand = lexer.next()
|
||||
token, isCommand = next(lexer)
|
||||
if isCommand:
|
||||
raise Exception, 'Invalid number of parameters'
|
||||
raise ValueError('Invalid number of parameters')
|
||||
except StopIteration:
|
||||
raise Exception, 'Unexpected end of path'
|
||||
raise ValueError('Unexpected end of path')
|
||||
cast = pathdefs[command.upper()][2][-numParams]
|
||||
param = cast(token)
|
||||
if command.islower():
|
||||
7
support/inkscape/simpletransform.py → support/pogojig/inkscape/simpletransform.py
Executable file → Normal file
7
support/inkscape/simpletransform.py → support/pogojig/inkscape/simpletransform.py
Executable file → Normal file
|
|
@ -1,4 +1,3 @@
|
|||
#!/usr/bin/env python
|
||||
'''
|
||||
Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr
|
||||
Copyright (C) 2010 Alvin Penner, penner@vaxxine.com
|
||||
|
|
@ -21,9 +20,11 @@ barraud@math.univ-lille1.fr
|
|||
This code defines several functions to make handling of transform
|
||||
attribute easier.
|
||||
'''
|
||||
import inkex, cubicsuperpath
|
||||
|
||||
import math, re
|
||||
|
||||
from . import inkex, cubicsuperpath
|
||||
|
||||
def parseTransform(transf,mat=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]):
|
||||
if transf=="" or transf==None:
|
||||
return(mat)
|
||||
|
|
@ -117,7 +118,7 @@ def applyTransformToPath(mat,path):
|
|||
def fuseTransform(node):
|
||||
if node.get('d')==None:
|
||||
#FIXME: how do you raise errors?
|
||||
raise AssertionError, 'can not fuse "transform" of elements that have no "d" attribute'
|
||||
raise AssertionError('can not fuse "transform" of elements that have no "d" attribute')
|
||||
t = node.get("transform")
|
||||
if t == None:
|
||||
return
|
||||
21
support/pogojig/kicad/kicad-cache.lib
Normal file
21
support/pogojig/kicad/kicad-cache.lib
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
EESchema-LIBRARY Version 2.4
|
||||
#encoding utf-8
|
||||
#
|
||||
# Connector_Conn_01x01_Female
|
||||
#
|
||||
DEF Connector_Conn_01x01_Female J 0 40 Y N 1 F N
|
||||
F0 "J" 0 100 50 H V C CNN
|
||||
F1 "Connector_Conn_01x01_Female" 0 -100 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
Connector*:*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
A 0 0 20 901 -901 1 1 6 N 0 20 0 -20
|
||||
P 2 1 1 6 -50 0 -20 0 N
|
||||
X Pin_1 1 -200 0 150 R 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
#End Library
|
||||
34
support/pogojig/kicad/kicad.pro
Normal file
34
support/pogojig/kicad/kicad.pro
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
update=05/04/2019 20:44:53
|
||||
version=1
|
||||
last_client=kicad
|
||||
[general]
|
||||
version=1
|
||||
RootSch=
|
||||
BoardNm=
|
||||
[pcbnew]
|
||||
version=1
|
||||
LastNetListRead=
|
||||
UseCmpFile=1
|
||||
PadDrill=0.600000000000
|
||||
PadDrillOvalY=0.600000000000
|
||||
PadSizeH=1.500000000000
|
||||
PadSizeV=1.500000000000
|
||||
PcbTextSizeV=1.500000000000
|
||||
PcbTextSizeH=1.500000000000
|
||||
PcbTextThickness=0.300000000000
|
||||
ModuleTextSizeV=1.000000000000
|
||||
ModuleTextSizeH=1.000000000000
|
||||
ModuleTextSizeThickness=0.150000000000
|
||||
SolderMaskClearance=0.000000000000
|
||||
SolderMaskMinWidth=0.000000000000
|
||||
DrawSegmentWidth=0.200000000000
|
||||
BoardOutlineThickness=0.100000000000
|
||||
ModuleOutlineThickness=0.150000000000
|
||||
CopperEdgeClearance=0.000000000000
|
||||
[cvpcb]
|
||||
version=1
|
||||
NetIExt=net
|
||||
[eeschema]
|
||||
version=1
|
||||
LibDir=
|
||||
[eeschema/libraries]
|
||||
Loading…
Add table
Add a link
Reference in a new issue