Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10"]
python-version: ["3.11"]
steps:
- uses: actions/checkout@v2
- name: Install CadQuery and pytest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10"]
python-version: ["3.11", "3.12"]
os: [ubuntu-22.04] # windows-latest , macos-latest
runs-on: ${{ matrix.os }}
steps:
Expand Down
200 changes: 106 additions & 94 deletions README.md

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions TECH_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# CadQuery CLI (cq-cli) - Project Overview

`cq-cli` is a Command Line Interface for executing CadQuery scripts and converting their output to various formats (STEP, STL, DXF, SVG, GLB, GLTF, ThreeJS). It is designed to be used in automation pipelines and supports stdin/stdout streams.

## Architecture

The project is built around the **CadQuery Gateway Interface (CQGI)**.
- **Entry Point:** `src/cq_cli/main.py` handles argument parsing, script loading, and coordination between CQGI and codecs.
- **Plugin System:** Codecs are dynamically loaded from `src/cq_cli/cqcodecs/` by `loader.py`. Any file matching `cq_codec_*.py` is treated as a codec.
- **FreeCAD Support:** Integrates `cadquery_freecad_import_plugin` to handle `.fcstd` files.

## Tech Stack
- **Language:** Python 3.9+
Comment thread
ianscrivener marked this conversation as resolved.
Outdated
- **Core Library:** [CadQuery](https://github.com/CadQuery/cadquery)
- **Environment Management:** `uv` (preferred)
- **Build Tool:** PyInstaller (for standalone binaries)
- **Testing:** `pytest`

## Key Commands

### Development
- **Install Dependencies:** `uv sync`
- **Run CLI (Development):** `python -m cq_cli.main --help`
- **Run Tests:** `pytest`
- **Linting:** `black` (v19.10b0 specified in `pyproject.toml`)
Comment thread
jmwright marked this conversation as resolved.
Outdated

### Usage Examples
- **Convert to STEP:** `cq-cli --codec step --infile model.py --outfile model.step`
- **Auto-detect Codec:** `cq-cli --infile model.py --outfile model.stl`
- **Extract Parameters:** `cq-cli --getparams true --infile model.py`
- **Pass Parameters:** `cq-cli --params "width:10;height:20" --infile model.py`
- **Evaluate Expression:** `cq-cli --expression "my_part(10)" --infile models.py`

### Building
- **PyInstaller (One-file):** `pyinstaller cq-cli_pyinstaller.spec onefile`
- **PyInstaller (Directory):** `pyinstaller cq-cli_pyinstaller.spec dir`

## Development Conventions

### Adding a New Codec
1. Create `src/cq_cli/cqcodecs/cq_codec_[name].py`.
2. Implement a `convert` function:
```python
def convert(build_result, outfile, errfile, output_opts):
# build_result is a cqgi.BuildResult
# Return string/bytes for writing to outfile (or stdout)
# Return None if the codec writes directly to outfile
```
3. Add the new codec to `hiddenimports` in `cq-cli_pyinstaller.spec` for standalone builds.
4. Add a test in `tests/test_[name]_codec.py`.

### Exit Codes
- **0:** Success
- **1:** Input file read error
- **2:** Usage/Argument error
- **3:** Missing/Invalid codec
- **100:** CadQuery build error (script execution failure)
- **200:** Codec conversion error

## Testing
Tests are located in the `tests/` directory and use `pytest`. Many tests rely on `tests/test_helpers.py` for CLI invocation and `tests/testdata/` for sample scripts.
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ authors = [
]
description = "Command Line Interface for executing CadQuery scripts and converting their output to another format."
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.11"
classifiers = [
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
Expand All @@ -27,8 +27,8 @@ cq-cli = "cq_cli.main:main"
[project.optional-dependencies]
dev = [
"pytest",
"black==19.10b0",
"click==8.0.4"
"black==26.3.1",
"click==8.3.1"
]

[project.urls]
Expand Down
6 changes: 3 additions & 3 deletions src/cq_cli/cqcodecs/cq_codec_stl.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def convert(build_result, output_file=None, error_file=None, output_opts=None):
angularDeflection = 0.1

# If the user has provided the deflection settings, use them
if "linearDeflection" in output_opts:
if output_opts and "linearDeflection" in output_opts:
linearDeflection = output_opts["linearDeflection"]
if "angularDeflection" in output_opts:
if output_opts and "angularDeflection" in output_opts:
angularDeflection = output_opts["angularDeflection"]

# The exporters will add extra output that we do not want, so suppress it
Expand All @@ -33,7 +33,7 @@ def convert(build_result, output_file=None, error_file=None, output_opts=None):
result.exportStl(temp_file, linearDeflection, angularDeflection, True)

# Read the STL output back in
with open(temp_file, "r") as file:
with open(temp_file, "rb") as file:
stl_str = file.read()

return stl_str
18 changes: 14 additions & 4 deletions src/cq_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,13 @@ def main():
if codec in key:
codec_module = loaded_codecs[key]

# If codec_module is still None, the user specified an invalid codec name
if codec_module is None:
print("Please specify a valid codec. You have the following to choose from:")
for key in loaded_codecs:
print(key.replace("cq_codec_", ""))
sys.exit(3)

# Handle there being multiple codecs
if codecs != None:
for cur_codec in codecs:
Expand Down Expand Up @@ -460,7 +467,7 @@ def main():
or args.params.startswith(".")
or args.params.startswith("..")
or args.params.startswith("~")
or args.params[1] == ":"
or (len(args.params) >= 2 and args.params[1] == ":")
):
# Load the parameters dictionary from the file
file_params = get_params_from_file(args.params, errfile)
Expand Down Expand Up @@ -506,7 +513,7 @@ def main():
elif "." in op1:
op = float(opt_parts[1])
elif '"' in op1 or "'" in op1:
op = str(opt_parts[1])
op = str(opt_parts[1]).strip("\"'")
else:
op = int(opt_parts[1])

Expand All @@ -528,7 +535,7 @@ def main():
print("build_and_parse error: " + str(err), file=sys.stderr)
else:
with open(errfile, "w") as file:
file.write(err)
file.write(str(err))
sys.exit(100)

#
Expand All @@ -555,7 +562,10 @@ def main():
if converted != None:
# Write the converted output to the appropriate place based on the command line arguments
if outfile == None:
print(converted)
if isinstance(converted, (bytes, bytearray)):
sys.stdout.buffer.write(converted)
else:
print(converted)
else:
if isinstance(converted, str):
with open(outfile, "w") as file:
Expand Down
Loading
Loading