Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
20 changes: 18 additions & 2 deletions .github/workflows/quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,23 @@ jobs:
install-args: --only main,lint,test

- name: Run quality hooks
run: poetry run pre-commit run --all-files
run: poetry run -- pre-commit run --all-files

- name: Run tests
run: xvfb-run -a -s "-screen 0 1920x1080x24 -nolisten tcp" poetry run python -m pytest tests
run: xvfb-run -a -s "-screen 0 1920x1080x24 -dpi 96 -nolisten tcp" poetry run -- python3 -m pytest tests

- name: Run visual regression tests
run: |
visual_jobs=$(( $(nproc) - 1 ))
visual_jobs=$(( visual_jobs > 0 ? visual_jobs : 1 ))
poetry run -- python3 scripts/visual_tests.py reference-compare --jobs "$visual_jobs" --xvfb

- name: Upload visual regression output
if: failure()
uses: actions/upload-artifact@v4
with:
name: visual-regression-output
path: |
tests/integration/output/**/*.png
tests/integration/reference/**/*.png
if-no-files-found: ignore
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ coverage.xml
.pytest_cache/
cover/
tests/integration/output/
tests/integration/reference/
tests/integration/local-reference/

# Translations
*.mo
Expand Down Expand Up @@ -94,4 +94,4 @@ assets/packaging/ios/carveracontroller-ios/carveracontrollerpkg/*

# Libraries for hidapi support that would normally be packaged
carveracontroller/hidapi.dll
carveracontroller/libhidapi.dylib
carveracontroller/libhidapi.dylib
11 changes: 8 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@ Thank you for your interest in contributing to our project! We welcome contribut
Run the configured hooks before opening a pull request:

```bash
poetry run pre-commit run --all-files
poetry run -- pre-commit run --all-files
```

Run the test suite separately:

```bash
poetry run python -m pytest tests -q
poetry run -- python3 -m pytest tests -q

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate python's idiosyncrasies across different operating systems. I would argue this change (all of the python3 vs python command changes) should be done/discussed in a different commit - changing styling/formatting for all commands in the project is out of the scope this commit aims to achieve.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can undo it if you insist, I just thought python3 is more portable than python - do Windows python installations not provide it or something?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a mess, depends on how/when you installed and if you need a specific/multiple run time. Generally speaking python3 is a posix specific thing. If you need multiple runtimes, windows instead uses the py.exe launcher.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://xkcd.com/1987/ is evergreen. I'll see what I can do about this.

```

For targeted checks, run the individual Poetry commands listed in the [Quality Checks section in the README](README.md#quality-checks).
For screenshot visual tests, use local ignored references while iterating and committed references for Linux checks.
See [README visual tests](README.md#visual-regression-tests) for commands, including the CI-like container helper for
macOS and Windows.

For targeted checks, run the individual Poetry commands listed in the
[Quality Checks section in the README](README.md#quality-checks).
80 changes: 67 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ Once you have Poetry installed, setting up the development environment is straig
poetry env activate
```

This step is usually not necessary since `poetry run <command>` automatically uses the virtual environment, but it can be helpful if you want to run multiple commands without prefixing `poetry run`.
This step is usually not necessary since `poetry run -- <command>` automatically uses the virtual environment, but it can be helpful if you want to run multiple commands without prefixing `poetry run --`.

### Running the Project

You can run the Controller software using Poetry's run command without installation. This is handy for iterative development.

```bash
poetry run python -m carveracontroller
poetry run -- python3 -m carveracontroller
```

To run the iOS app, you first need to build its dependencies using the Local Packaging instructions below. The build script will open Xcode for you, or you can open the project manually by finding it in `assets/packaging/ios/carveracontroller-ios`.
Expand All @@ -109,38 +109,92 @@ The project uses [Ruff](https://docs.astral.sh/ruff/) (linting), [Mypy](https://
Run all configured hooks with pre-commit:

```bash
poetry run pre-commit run --all-files
poetry run -- pre-commit run --all-files
```

To run the same checks automatically on every `git commit`, install the hooks once:

```bash
poetry run pre-commit install
poetry run -- pre-commit install
```

Run individual checks with Poetry:

```bash
poetry run ruff check carveracontroller tests scripts
poetry run ruff format --check carveracontroller tests scripts
poetry run mypy carveracontroller --config-file pyproject.toml
poetry run mypy carveracontroller/machine --config-file pyproject.toml --strict
poetry run lint-imports --config pyproject.toml
poetry run python -m pytest tests -q
poetry run -- ruff check carveracontroller tests scripts
poetry run -- ruff format --check carveracontroller tests scripts
poetry run -- mypy carveracontroller --config-file pyproject.toml
poetry run -- mypy carveracontroller/machine --config-file pyproject.toml --strict
poetry run -- lint-imports --config pyproject.toml
poetry run -- python3 -m pytest tests -q
```

To format checked Python files, run:

```bash
poetry run ruff format carveracontroller tests scripts
poetry run -- ruff format carveracontroller tests scripts
```

### Visual Regression Tests

The screenshot tests support two reference modes:

* Local mode uses ignored host-local references in `tests/integration/local-reference/`, grouped by test file.
* Committed mode uses tracked Linux references in `tests/integration/reference/`, grouped by test file.

For fast local UI iteration, create host-local references before changing the UI:

```bash
poetry run -- python3 scripts/visual_tests.py local-update
```

Then compare against those local references:

```bash
poetry run -- python3 scripts/visual_tests.py local-compare
```

Local comparisons skip any screenshot that is missing from `tests/integration/local-reference/`; they do not fall back
to committed Linux references. Extra pytest arguments can be passed after the script command, for example:

```bash
poetry run -- python3 scripts/visual_tests.py local-compare \
tests/integration/test_visual_regression.py::TestDisconnectedState::test_control_page
```

On Linux, update or check the committed references directly:

```bash
poetry run -- python3 scripts/visual_tests.py reference-update --xvfb
poetry run -- python3 scripts/visual_tests.py reference-compare --xvfb
```

Reference compare/update can run isolated screenshot tests in parallel:

```bash
visual_jobs=$(( $(nproc) - 1 ))
visual_jobs=$(( visual_jobs > 0 ? visual_jobs : 1 ))
poetry run -- python3 scripts/visual_tests.py reference-compare --jobs "$visual_jobs" --xvfb
```

On macOS or Windows, run the same command inside the CI-like dependency container:

```bash
poetry run -- python3 scripts/run_in_container.py -- \
sh -c 'visual_jobs=$(( $(nproc) - 1 )); visual_jobs=$(( visual_jobs > 0 ? visual_jobs : 1 )); poetry run -- python3 scripts/visual_tests.py reference-compare --jobs "$visual_jobs" --xvfb'
```

The container helper uses Podman if available, then Docker. Override that with `CARVERA_CI_ENGINE=docker` or
`--engine docker`. The image tag can be overridden with `CARVERA_CI_IMAGE` or `--image`. The helper passes a native
container platform by default, such as `linux/arm64` on Apple Silicon and `linux/amd64` on typical CI runners;
override it with `CARVERA_CI_PLATFORM` or `--platform` if your container engine uses a remote architecture.

### Local Packaging

The application is packaged using PyInstaller (except for iOS). This tool converts Python applications into a standalone executable, so it can be run on systems without requiring management of a installed Python interpreter or dependent libraries. An build helper script is configured with Poetry and can be run with:

```bash
poetry run python scripts/build.py --os os --version version [--no-appimage]
poetry run -- python3 scripts/build.py --os os --version version [--no-appimage]
```

The options for `os` are windows, macos, linux, pypi, ios or android. If selecting `linux`, an appimage is built by default unless --no-appimage is specified.
Expand All @@ -157,7 +211,7 @@ If you add or modify any UI text strings you need to update the messages.pot fil
Updating the .pot and .po strings, as well as compiling to .mo can be performed by running the following command:

``` bash
poetry run python scripts/update_translations.py
poetry run -- python3 scripts/update_translations.py
```

This utility scans the python and kivvy code for new strings and updates the mapping files. It does not clear/overwrite previous translations.
Expand Down
87 changes: 44 additions & 43 deletions carveracontroller/Objloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,49 +103,50 @@ def __init__(self, filename, swapyz=False):
self._current_object = None

material = None
for line in open(filename):
if line.startswith("#"):
continue
if line.startswith("s"):
continue
values = line.split()
if not values:
continue
if values[0] == "o":
self.finish_object()
self._current_object = values[1]
# elif values[0] == 'mtllib':
# self.mtl = MTL(values[1])
# elif values[0] in ('usemtl', 'usemat'):
# material = values[1]
if values[0] == "v":
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.vertices.append(v)
elif values[0] == "vn":
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.normals.append(v)
elif values[0] == "vt":
self.texcoords.append(list(map(float, values[1:3])))
elif values[0] == "f":
face = []
texcoords = []
norms = []
for v in values[1:]:
w = v.split("/")
face.append(int(w[0]))
if len(w) >= 2 and len(w[1]) > 0:
texcoords.append(int(w[1]))
else:
texcoords.append(-1)
if len(w) >= 3 and len(w[2]) > 0:
norms.append(int(w[2]))
else:
norms.append(-1)
self.faces.append((face, norms, texcoords, material))
with open(filename, encoding="utf-8") as obj_file:
for line in obj_file:
if line.startswith("#"):
continue
if line.startswith("s"):
continue
values = line.split()
if not values:
continue
if values[0] == "o":
self.finish_object()
self._current_object = values[1]
# elif values[0] == 'mtllib':
# self.mtl = MTL(values[1])
# elif values[0] in ('usemtl', 'usemat'):
# material = values[1]
if values[0] == "v":
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.vertices.append(v)
elif values[0] == "vn":
v = list(map(float, values[1:4]))
if swapyz:
v = v[0], v[2], v[1]
self.normals.append(v)
elif values[0] == "vt":
self.texcoords.append(list(map(float, values[1:3])))
elif values[0] == "f":
face = []
texcoords = []
norms = []
for v in values[1:]:
w = v.split("/")
face.append(int(w[0]))
if len(w) >= 2 and len(w[1]) > 0:
texcoords.append(int(w[1]))
else:
texcoords.append(-1)
if len(w) >= 3 and len(w[2]) > 0:
norms.append(int(w[2]))
else:
norms.append(-1)
self.faces.append((face, norms, texcoords, material))
self.finish_object()


Expand Down
6 changes: 3 additions & 3 deletions carveracontroller/translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ def __getattr__(self, name):
def init(langname: str | None = None):
if langname is None or langname not in LANGS:
try:
default_locale = locale.getdefaultlocale()
if default_locale is not None and default_locale[0] is not None:
locale_name = locale.getlocale()[0]
if locale_name is not None:
for lang_key in LANGS:
if default_locale[0][0:2] in lang_key:
if locale_name[0:2] in lang_key:
langname = lang_key
break
except:
Expand Down
2 changes: 1 addition & 1 deletion scripts/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ cd "$(dirname "$0")"
cd ..

poetry sync
poetry run python -m carveracontroller
poetry run -- python3 -m carveracontroller
Loading
Loading