diff --git a/.github/workflows/test_freecad.yml b/.github/workflows/test_freecad.yml new file mode 100644 index 0000000..8cf7911 --- /dev/null +++ b/.github/workflows/test_freecad.yml @@ -0,0 +1,21 @@ +name: Test FreeCAD + +on: [push, pull_request] + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - name: Checkout project + uses: actions/checkout@v4 + + - name: Install and Test + run: | + conda create -y -n freecad -c conda-forge python=3.11 freecad=0.21.2 + conda init bash + source /usr/share/miniconda/bin/activate + conda activate freecad + python -m pip install --upgrade pip + pip install -e . + pip install -e .[dev] + python -m pytest -v tests/test_freecad.py \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 58403d6..afb5706 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,4 +26,4 @@ jobs: pip3 install -e .[dev] - name: Run tests run: | - python3 -m pytest -v + python3 -m pytest -v --ignore=tests/test_freecad.py diff --git a/.gitignore b/.gitignore index 80e953f..ac5fe8d 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# FreeCAD related files +updated_part* diff --git a/pyproject.toml b/pyproject.toml index ee736c7..e8be63e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ "typish", "casadi", "path", + "cadquery-freecad-import-plugin @ git+https://github.com/jmwright/cadquery-freecad-import-plugin.git" ] [project.scripts] diff --git a/src/cq_cli/main.py b/src/cq_cli/main.py index c58a3a6..b7b58a0 100755 --- a/src/cq_cli/main.py +++ b/src/cq_cli/main.py @@ -20,17 +20,55 @@ from cq_cli.cqcodecs import loader +def handle_freecad_file(file_path, params=None): + """ + Wrapper method that takes care of importing a FreeCAD file and applying parameters to it. + """ + from cadquery_freecad_import_plugin.plugin import import_freecad_part + + # Construct a build result so that the rest of the code can handle it + build_result = cq.cqgi.BuildResult() + + # Only apply parameters if there are any + if params != None and len(params) > 0: + # Assemble the FreeCAD params + freecad_params = {} + for key in params: + freecad_params[key] = {"value": params[key], "units": "mm"} + + # Import the FreeCAD file using the parametric method + result = import_freecad_part(file_path, freecad_params) + shape_result = cq.cqgi.ShapeResult() + shape_result.shape = result + build_result.results.append(shape_result) + build_result.success = True + else: + # Import the FreeCAD file without applying parameters + result = import_freecad_part(file_path) + shape_result = cq.cqgi.ShapeResult() + shape_result.shape = result + build_result.results.append(shape_result) + build_result.success = True + + return build_result + + def build_and_parse(script_str, params, errfile, expression): """ Uses CQGI to parse and build a script, substituting in parameters if any were supplied. """ + # We need to do a broad try/catch to let the user know if something higher-level fails try: - # Do the CQGI handling of the script here and, if successful, pass the build result to the codec - if expression != None: - script_str += "\nshow_object({expr})".format(expr=expression) - cqModel = cqgi.parse(script_str) - build_result = cqModel.build(params) + # If we have a freecad file, handle it differently + if script_str.lower().endswith(".fcstd"): + build_result = handle_freecad_file(script_str, params) + else: + # Do the CQGI handling of the script here and, if successful, pass the build result to the codec + if expression != None: + script_str += "\nshow_object({expr})".format(expr=expression) + cqModel = cqgi.parse(script_str) + build_result = cqModel.build(params) # Handle the case of the build not being successful, otherwise pass the codec the build result if not build_result.success: @@ -81,6 +119,8 @@ def get_script_from_infile(infile, outfile, errfile): if infile == None: # Grab the string from stdin script_str = sys.stdin.read() + elif infile.lower().endswith(".fcstd"): + script_str = infile else: with open(infile, "r") as file: script_str = file.read() diff --git a/tests/test_freecad.py b/tests/test_freecad.py new file mode 100644 index 0000000..ff80e20 --- /dev/null +++ b/tests/test_freecad.py @@ -0,0 +1,41 @@ +import tests.test_helpers as helpers + + +def test_static_freecad_file(): + """ + Basic test of the FreeCAD (FCStd) codec plugin. + """ + test_file = helpers.get_test_file_location("shelf.FCStd") + + command = [ + "python", + "src/cq_cli/main.py", + "--codec", + "step", + "--infile", + test_file, + ] + out, err, exitcode = helpers.cli_call(command) + + assert "ISO-10303-21;" in out.decode() + + +def test_parametric_freecad_file(): + """ + Basic test of the FreeCAD (FCStd) codec plugin. + """ + test_file = helpers.get_test_file_location("shelf.FCStd") + + command = [ + "python", + "src/cq_cli/main.py", + "--codec", + "step", + "--params", + "internal_rail_spacing:152.4;", + "--infile", + test_file, + ] + out, err, exitcode = helpers.cli_call(command) + + assert "ISO-10303-21;" in out.decode() diff --git a/tests/testdata/shelf.FCStd b/tests/testdata/shelf.FCStd new file mode 100644 index 0000000..287f8ad Binary files /dev/null and b/tests/testdata/shelf.FCStd differ