diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..fcc2c1d --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,28 @@ +--- +name: Release pypi package +on: + push: + branches: + - "!*" + tags: + - "v*" +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout/@v3 + - run: sed -i -e "s/^\(version = \).*/\1${GITHUB_REF_NAME/v}/" setup.cfg + - uses: actions/setup-python@v3 + with: + python-version: '3.x' + - run: pip install -r test-requirements.txt + - run: pip install -e . + - run: python -m pytest + - run: python -m pylint src tests ldraw2scad + - run: python -m pycodestyle src tests ldraw2scad + - run: python -m build + - run: pip install twine + - run: python -m twine upload dist/* --verbose + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test-python.yaml b/.github/workflows/test-python.yaml index 5ff52e0..5df573d 100644 --- a/.github/workflows/test-python.yaml +++ b/.github/workflows/test-python.yaml @@ -11,6 +11,8 @@ jobs: with: python-version: '3.x' - run: pip install -r test-requirements.txt - - run: pytest . - - run: python -m pylint *.py - - run: python -m pycodestyle *.py + - run: pip install -e . + - run: python -m pytest + - run: python -m pylint src tests ldraw2scad + - run: python -m pycodestyle src tests ldraw2scad + - run: python -m build diff --git a/.gitignore b/.gitignore index 3efc177..ba6f704 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ lib/ +LDraw/ +dist/ __pycache__/ .vscode/ test_anim/ .cache/ pytestdebug.log +*.egg-info diff --git a/README.md b/README.md index 94a3a19..15a98e1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Intent: Converting LDraw projects to Open SCAD to render, animate or use for oth Requires python3, with no external python dependancies: - python ldraw-to-scad.py + ldraw2scad Besides this basic parameters several options are available to generate results either as self-contained OpenSCAD files or relying on an LDraw OpenSCAD library that can be generated with this tool as well. Invoke the tool with the --help option for more information. @@ -17,7 +17,7 @@ It also (naively) expects the ldraw library filenames to be lowercase. ## Testing -Install the test-requirements.txt file, then run `pytest .`. +Install the test-requirements.txt file, then run `pip install -e .` and finally `pytest .`. ## Making animations diff --git a/ldraw2scad b/ldraw2scad new file mode 100755 index 0000000..0f6a1e2 --- /dev/null +++ b/ldraw2scad @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 + +""" Translate LDraw library or file to OpenSCAD library or file. """ + +import os +import argparse +from ldraw_to_scad import LDrawConverter + + +def translate_dir(converter, src, dest, self_contained=False): + """ translate a whole model directory """ + types = ['.mpd', '.ldr', '.dat'] + lst = {} + for fdir, _, files in os.walk(src, followlinks=True): + rel = os.path.relpath(fdir, src) + for file in sorted(files): + base, ext = os.path.splitext(file) + key = os.path.join(rel, base) + if ext in types: + # We will now override the old extension with the new + # one. Therefore let's warn the user that the one with + # the old extension will get skipped. + if key in lst: + print(f'Skipping {os.path.join(src, key+lst[key])}') + lst[key] = ext + for key, value in lst.items(): + print(f'Translating {os.path.join(src,key+value)}' + f' to {os.path.join(dest,key+".scad")}...') + converter.convert_file(os.path.join(src, key+value), + os.path.join(dest, key+".scad"), + self_contained) + + +def main(): + """ Main function """ + parser = argparse.ArgumentParser( + description='Convert an LDraw part to OpenSCAD') + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument( + '-t', '--translib', action='store_true', + help='translate the library') + parser.add_argument( + '-s', '--selfcontained', action='store_true', + help='create self-contained files') + parser.add_argument( + '-u', '--uncommented', action='store_true', + help='create uncommented files') + group.add_argument('ldraw_file', nargs='?', metavar='FILENAME', + help='source file to translate') + parser.add_argument('output_file', nargs='?', metavar='OUTPUT_FILENAME', + help='name of the translated file') + parser.add_argument( + '-l', '--lib', default=os.path.join('lib', 'ldraw'), metavar='LIB_DIR', + help='location of the LDraw parts library') + parser.add_argument( + '-o', '--openscadlibs', default='.', metavar='OPENSCAD_LIB_DIR', + help='location of the OpenSCAD libraries') + parser.add_argument( + '-n', '--libname', default='LDraw', metavar='LIB_NAME', + help='name of the OpenSCAD library') + parser.add_argument( + '--line', default=0.2, type=float, metavar='LINE_WIDTH', + help='width of lines, 0 for no lines') + args = parser.parse_args() + converter = LDrawConverter(libdir=args.lib) + converter.set('scadlibs', args.openscadlibs) + converter.set('scadlibname', args.libname) + converter.set('line', args.line) + converter.set('commented', not args.uncommented) + if args.translib: + print("Translating library...") + converter.convert_lib(args.selfcontained) + else: + if os.path.isdir(args.ldraw_file): + translate_dir( + converter, args.ldraw_file, + args.output_file if args.output_file else args.ldraw_file, + args.selfcontained) + else: + scadfile = args.output_file if args.output_file else \ + os.path.splitext(args.ldraw_file)[0] + '.scad' + print(f"Translating {args.ldraw_file} to {scadfile}...") + converter.convert_file(args.ldraw_file, scadfile, + args.selfcontained) + + +if __name__ == '__main__': + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..314cbf4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires = ["setuptools>=42"] +build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +testpaths = [ + "tests", +] diff --git a/run-tests.sh b/run-tests.sh index 0224acc..edca19f 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -2,5 +2,9 @@ docker run --rm -v ${PWD}:/mnt/src python:3.9 bash -ceu " cd /mnt/src && pip install -r test-requirements.txt && - pytest . + pip install -e . + python -m pytest + python -m pylint src tests ldraw2scad + python -m pycodestyle src tests ldraw2scad + python -m build " diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..357aa77 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,31 @@ +[metadata] +name = ldraw-to-scad +version = 0.0.0 +author = Danny Staple +author_email = danny@orionrobots.co.uk +maintainer = Robert Schiele +maintainer_email = rschiele@gmail.com +description = The LDraw to OpenSCAD converter library +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/orionrobots/ldraw-to-scad +project_urls = + Bug Tracker = https://github.com/orionrobots/ldraw-to-scad/issues +classifiers = + Programming Language :: Python :: 3 + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + +[options] +package_dir = + = src +packages = find: +python_requires = >=3.6 +scripts = ldraw2scad +include_package_data = True + +[options.packages.find] +where = src + +[options.package_data] +ldraw_to_scad = lib.scad diff --git a/src/ldraw_to_scad/__init__.py b/src/ldraw_to_scad/__init__.py new file mode 100644 index 0000000..0cc9d64 --- /dev/null +++ b/src/ldraw_to_scad/__init__.py @@ -0,0 +1,3 @@ +""" the LDraw to OpenSCAD converter library """ + +from .ldrawconverter import * diff --git a/ldraw_to_scad.py b/src/ldraw_to_scad/ldrawconverter.py similarity index 76% rename from ldraw_to_scad.py rename to src/ldraw_to_scad/ldrawconverter.py index 0cf433c..b1ee42f 100755 --- a/ldraw_to_scad.py +++ b/src/ldraw_to_scad/ldrawconverter.py @@ -3,7 +3,10 @@ """ Translate LDraw library or file to OpenSCAD library or file. """ import os -import argparse +import pkg_resources + + +LIB_SCAD = pkg_resources.resource_filename(__name__, 'lib.scad') class LDrawConverter: @@ -237,17 +240,17 @@ def process_queue(self): fdw.write(result) self.queue[1].clear() - def convert_lib(self, selfcontained=False): + def convert_lib(self, self_contained=False): """ Convert the whole library """ for name in self.index: self.enqueue(name) - if selfcontained: + if self_contained: with open(os.path.join(self.settings['scadlibs'], self.settings['scadlibname']+'.scad'), 'w', encoding="utf-8") as fdw: self.settings['selfcontained'] = fdw fdw.write(self.colorfile()) - with open('lib.scad', encoding="utf-8") as filedata: + with open(LIB_SCAD, encoding="utf-8") as filedata: lines = filedata.readlines() fdw.write(''.join(lines)) self.process_queue() @@ -255,7 +258,7 @@ def convert_lib(self, selfcontained=False): os.makedirs(os.path.join(self.settings['scadlibs'], self.settings['scadlibname']), exist_ok=True) - with open('lib.scad', encoding="utf-8") as filedata: + with open(LIB_SCAD, encoding="utf-8") as filedata: lines = filedata.readlines() with open(os.path.join(self.settings['scadlibs'], self.settings['scadlibname'], 'lib.scad'), @@ -269,14 +272,14 @@ def convert_lib(self, selfcontained=False): fdw.write(self.colorfile()) self.process_queue() - def convert_file(self, ldrfile, scadfile, selfcontained=False): + def convert_file(self, ldrfile, scadfile, self_contained=False): """ Convert a single file """ self.enqueue('__main__', '/', ldrfile, scadfile) - if selfcontained: + if self_contained: with open(scadfile, 'w', encoding="utf-8") as fdw: self.settings['selfcontained'] = fdw fdw.write(self.colorfile()) - with open('lib.scad', encoding="utf-8") as filedata: + with open(LIB_SCAD, encoding="utf-8") as filedata: lines = filedata.readlines() fdw.write(''.join(lines)) fdw.write('makepoly(ldraw_lib____main__(), ' @@ -284,81 +287,3 @@ def convert_file(self, ldrfile, scadfile, selfcontained=False): self.process_queue() else: self.process_queue() - - -def translatedir(converter, src, dest, selfcontained=False): - """ translate a whole model directory """ - types = ['.mpd', '.ldr', '.dat'] - lst = {} - for fdir, _, files in os.walk(src, followlinks=True): - rel = os.path.relpath(fdir, src) - for file in sorted(files): - base, ext = os.path.splitext(file) - key = os.path.join(rel, base) - if ext in types: - if key in lst: - print(f'Skipping {os.path.join(src, key+lst[key])}') - lst[key] = ext - for key, value in lst.items(): - print(f'Translating {os.path.join(src,key+value)}' - f' to {os.path.join(dest,key+".scad")}...') - converter.convert_file(os.path.join(src, key+value), - os.path.join(dest, key+".scad"), - selfcontained) - - -def main(): - """ Main function """ - parser = argparse.ArgumentParser( - description='Convert an LDraw part to OpenSCAD') - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument( - '-t', '--translib', action='store_true', - help='translate the library') - parser.add_argument( - '-s', '--selfcontained', action='store_true', - help='create self-contained files') - parser.add_argument( - '-u', '--uncommented', action='store_true', - help='create uncommented files') - group.add_argument('ldraw_file', nargs='?', metavar='FILENAME', - help='source file to translate') - parser.add_argument('output_file', nargs='?', metavar='OUTPUT_FILENAME', - help='name of the translated file') - parser.add_argument( - '-l', '--lib', default=os.path.join('lib', 'ldraw'), metavar='LIB_DIR', - help='location of the LDraw parts library') - parser.add_argument( - '-o', '--openscadlibs', default='.', metavar='OPENSCAD_LIB_DIR', - help='location of the OpenSCAD libraries') - parser.add_argument( - '-n', '--libname', default='LDraw', metavar='LIB_NAME', - help='name of the OpenSCAD library') - parser.add_argument( - '--line', default=0.2, type=float, metavar='LINE_WIDTH', - help='width of lines, 0 for no lines') - args = parser.parse_args() - converter = LDrawConverter(libdir=args.lib) - converter.set('scadlibs', args.openscadlibs) - converter.set('scadlibname', args.libname) - converter.set('line', args.line) - converter.set('commented', not args.uncommented) - if args.translib: - print("Translating library...") - converter.convert_lib(args.selfcontained) - else: - if os.path.isdir(args.ldraw_file): - translatedir( - converter, args.ldraw_file, - args.output_file if args.output_file else args.ldraw_file, - args.selfcontained) - else: - scadfile = args.output_file if args.output_file else \ - os.path.splitext(args.ldraw_file)[0] + '.scad' - print(f"Translating {args.ldraw_file} to {scadfile}...") - converter.convert_file(args.ldraw_file, scadfile, - args.selfcontained) - - -if __name__ == '__main__': - main() diff --git a/lib.scad b/src/ldraw_to_scad/lib.scad similarity index 100% rename from lib.scad rename to src/ldraw_to_scad/lib.scad diff --git a/test-requirements.txt b/test-requirements.txt index 786746f..0c27760 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -2,3 +2,4 @@ pytest mock pylint pycodestyle +build diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mpd_test.dat b/tests/mpd_test.dat similarity index 100% rename from mpd_test.dat rename to tests/mpd_test.dat diff --git a/simple_test.dat b/tests/simple_test.dat similarity index 100% rename from simple_test.dat rename to tests/simple_test.dat diff --git a/test_ldraw_to_scad.py b/tests/test_ldraw_to_scad.py similarity index 98% rename from test_ldraw_to_scad.py rename to tests/test_ldraw_to_scad.py index 2692862..c912ac8 100644 --- a/test_ldraw_to_scad.py +++ b/tests/test_ldraw_to_scad.py @@ -1,4 +1,4 @@ -""" test cases for ldraw_to_scad.py """ +""" test cases for ldraw_to_scad """ from unittest import TestCase import os @@ -7,6 +7,9 @@ from ldraw_to_scad import LDrawConverter +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) + + class TestModule(TestCase): """ tests for generation of function names """ def test_it_should_make_sensible_function_names(self): @@ -201,7 +204,7 @@ def test_multiple_lines(self): def test_reading_file(self): """ test conversion of file content """ # Setup - test_file = "simple_test.dat" + test_file = os.path.join(THIS_DIR, "simple_test.dat") # test converter = LDrawConverter() with open(test_file, encoding="utf-8") as fdr: @@ -311,7 +314,7 @@ def test_try_simplest_mpd(self): def test_loading_an_mpd(self): """ test a complete MPD file """ # Setup - mpd_filename = "mpd_test.dat" + mpd_filename = os.path.join(THIS_DIR, "mpd_test.dat") # Test converter = LDrawConverter() with open(mpd_filename, encoding="utf-8") as fdr: