b3dkit 0.1.5__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- b3dkit-0.3.2/.github/workflows/release.yml +72 -0
- b3dkit-0.3.2/.github/workflows/test.yml +81 -0
- b3dkit-0.3.2/.pre-commit-config.yaml +22 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/PKG-INFO +4 -4
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/basic_shapes.md +11 -11
- b3dkit-0.3.2/docs/dovetail.md +121 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/high_top_slide_box.md +11 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/slide_box.md +0 -28
- {b3dkit-0.1.5 → b3dkit-0.3.2}/pyproject.toml +30 -4
- b3dkit-0.3.2/src/b3dkit/__init__.py +92 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/antichamfer.py +8 -3
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/ball_socket.py +12 -9
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/basic_shapes.py +28 -26
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/bolt_fittings.py +18 -11
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/click_fit.py +6 -5
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/dovetail.py +209 -150
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/hexwall.py +9 -5
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/high_top_slide_box.py +46 -5
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/point.py +15 -5
- b3dkit-0.3.2/src/b3dkit/py.typed +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/slide_box.py +33 -30
- {b3dkit-0.1.5 → b3dkit-0.3.2}/src/b3dkit/twist_snap.py +8 -10
- b3dkit-0.3.2/tests/conftest.py +12 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_antichamfer.py +6 -6
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_ball_socket.py +4 -2
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_basic_shapes.py +6 -17
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_bolt_fittings.py +7 -7
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_click_fit.py +2 -7
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_dovetail.py +159 -26
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_hexwall.py +5 -4
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_high_top_slide_box.py +136 -47
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_point.py +1 -3
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_slide_box.py +7 -9
- {b3dkit-0.1.5 → b3dkit-0.3.2}/tests/test_twist_snap.py +3 -4
- b3dkit-0.1.5/.coverage +0 -0
- b3dkit-0.1.5/build.bat +0 -44
- b3dkit-0.1.5/build.sh +0 -44
- b3dkit-0.1.5/docs/dovetail.md +0 -81
- b3dkit-0.1.5/src/b3dkit/__init__.py +0 -11
- b3dkit-0.1.5/tests/conftest.py +0 -14
- {b3dkit-0.1.5 → b3dkit-0.3.2}/.coveragerc +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/.gitignore +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/.vscode/settings.json +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/LICENSE +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/README.md +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/Makefile +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/antichamfer.md +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/ball_socket.md +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/bolt_fittings.md +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/click_fit.md +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/conf.py +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/dovetail.png +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/hexwall.md +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/index.md +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/point.md +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/twist_snap.md +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/docs/twist_snap.png +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/mkdocs.yml +0 -0
- {b3dkit-0.1.5 → b3dkit-0.3.2}/readthedocs.yaml +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v7
|
|
13
|
+
|
|
14
|
+
- uses: actions/setup-python@v7
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
cache: pip
|
|
18
|
+
|
|
19
|
+
- name: Install and test
|
|
20
|
+
run: |
|
|
21
|
+
python -m pip install --upgrade pip build
|
|
22
|
+
python -m pip install -e ".[dev]"
|
|
23
|
+
pytest -q
|
|
24
|
+
|
|
25
|
+
- name: Check the tag matches the packaged version
|
|
26
|
+
run: |
|
|
27
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
28
|
+
version=$(python -c "import re,pathlib;print(re.search(r'^version = \"([^\"]+)\"',pathlib.Path('pyproject.toml').read_text(),re.M).group(1))")
|
|
29
|
+
if [ "$tag" != "$version" ]; then
|
|
30
|
+
echo "::error::tag $GITHUB_REF_NAME does not match pyproject version $version"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
echo "tag and version agree: $version"
|
|
34
|
+
|
|
35
|
+
- name: Build
|
|
36
|
+
run: python -m build
|
|
37
|
+
|
|
38
|
+
- uses: actions/upload-artifact@v7
|
|
39
|
+
with:
|
|
40
|
+
name: dist
|
|
41
|
+
path: dist/
|
|
42
|
+
|
|
43
|
+
publish:
|
|
44
|
+
needs: build
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
environment: pypi
|
|
47
|
+
permissions:
|
|
48
|
+
id-token: write # required for PyPI Trusted Publishing
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/download-artifact@v7
|
|
51
|
+
with:
|
|
52
|
+
name: dist
|
|
53
|
+
path: dist/
|
|
54
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
55
|
+
|
|
56
|
+
smoke-test:
|
|
57
|
+
# Install the published artifact from PyPI and build something, mirroring the
|
|
58
|
+
# post-upload check the retired build.sh performed by hand.
|
|
59
|
+
needs: publish
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/setup-python@v7
|
|
63
|
+
with:
|
|
64
|
+
python-version: "3.12"
|
|
65
|
+
- name: Wait for PyPI to serve the new version
|
|
66
|
+
run: sleep 60
|
|
67
|
+
- name: Install from PyPI and build a part
|
|
68
|
+
run: |
|
|
69
|
+
version="${GITHUB_REF_NAME#v}"
|
|
70
|
+
python -m pip install --upgrade pip
|
|
71
|
+
python -m pip install "b3dkit==$version"
|
|
72
|
+
python -c "from b3dkit import Divot; assert Divot().is_valid; print('smoke test OK')"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: test-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
20
|
+
build123d: [latest]
|
|
21
|
+
include:
|
|
22
|
+
# One job pinned at the declared dependency floor. b3dkit 0.1.5 shipped
|
|
23
|
+
# code that could not run against its own advertised minimum; this job
|
|
24
|
+
# is what stops that from happening again.
|
|
25
|
+
- python-version: "3.12"
|
|
26
|
+
build123d: floor
|
|
27
|
+
|
|
28
|
+
name: py${{ matrix.python-version }} / build123d ${{ matrix.build123d }}
|
|
29
|
+
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v7
|
|
32
|
+
|
|
33
|
+
- uses: actions/setup-python@v7
|
|
34
|
+
with:
|
|
35
|
+
python-version: ${{ matrix.python-version }}
|
|
36
|
+
cache: pip
|
|
37
|
+
|
|
38
|
+
- name: Install
|
|
39
|
+
run: |
|
|
40
|
+
python -m pip install --upgrade pip
|
|
41
|
+
python -m pip install -e ".[dev]"
|
|
42
|
+
|
|
43
|
+
- name: Pin build123d to the declared floor
|
|
44
|
+
if: matrix.build123d == 'floor'
|
|
45
|
+
run: |
|
|
46
|
+
floor=$(python -c "import re,pathlib;print(re.search(r'build123d>=([0-9.]+)',pathlib.Path('pyproject.toml').read_text()).group(1))")
|
|
47
|
+
echo "Installing build123d==$floor"
|
|
48
|
+
python -m pip install "build123d==$floor"
|
|
49
|
+
|
|
50
|
+
- name: Show resolved versions
|
|
51
|
+
run: python -c "import sys,build123d;print(sys.version);print('build123d',build123d.__version__)"
|
|
52
|
+
|
|
53
|
+
- name: Run tests
|
|
54
|
+
run: pytest --cov=b3dkit --cov-report=term-missing --junitxml=junit.xml
|
|
55
|
+
|
|
56
|
+
- name: Fail if any test was skipped
|
|
57
|
+
# A green suite that silently skips is how the broken 0.10.0 floor reached
|
|
58
|
+
# PyPI: 27 tests were skipped and nothing complained.
|
|
59
|
+
if: always()
|
|
60
|
+
run: |
|
|
61
|
+
python - <<'PY'
|
|
62
|
+
import sys, xml.etree.ElementTree as ET
|
|
63
|
+
suite = ET.parse("junit.xml").getroot()
|
|
64
|
+
skipped = sum(int(s.get("skipped", 0)) for s in suite.iter("testsuite"))
|
|
65
|
+
if skipped:
|
|
66
|
+
print(f"::error::{skipped} test(s) skipped; the skip budget is zero")
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
print("No skipped tests.")
|
|
69
|
+
PY
|
|
70
|
+
|
|
71
|
+
lint:
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v7
|
|
75
|
+
- uses: actions/setup-python@v7
|
|
76
|
+
with:
|
|
77
|
+
python-version: "3.12"
|
|
78
|
+
cache: pip
|
|
79
|
+
- run: python -m pip install --upgrade pip ruff black
|
|
80
|
+
- run: ruff check .
|
|
81
|
+
- run: black --check .
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.14.5
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
stages: [manual]
|
|
9
|
+
|
|
10
|
+
- repo: https://github.com/psf/black
|
|
11
|
+
rev: 25.9.0
|
|
12
|
+
hooks:
|
|
13
|
+
- id: black
|
|
14
|
+
|
|
15
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
16
|
+
rev: v6.0.0
|
|
17
|
+
hooks:
|
|
18
|
+
- id: end-of-file-fixer
|
|
19
|
+
- id: trailing-whitespace
|
|
20
|
+
- id: check-toml
|
|
21
|
+
- id: check-yaml
|
|
22
|
+
- id: check-merge-conflict
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: b3dkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: build123d libraries and utilities
|
|
5
5
|
Project-URL: Homepage, https://github.com/x0pherl/b3dkit
|
|
6
6
|
Project-URL: Issues, https://github.com/x0pherl/b3dkit/issues
|
|
@@ -10,8 +10,8 @@ License-File: LICENSE
|
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Requires-Python: >=3.
|
|
14
|
-
Requires-Dist: build123d>=0.
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Requires-Dist: build123d>=0.11.0
|
|
15
15
|
Requires-Dist: ocp-vscode>=3.1.1
|
|
16
16
|
Provides-Extra: dev
|
|
17
17
|
Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
|
|
@@ -28,7 +28,7 @@ def apothem_to_radius(apothem: float, side_count: int = 6) -> float
|
|
|
28
28
|
|
|
29
29
|
**Arguments**
|
|
30
30
|
- `apothem` (float): The apothem of the polygon
|
|
31
|
-
- `side_count` (
|
|
31
|
+
- `side_count` (int): The number of sides of the poygon.
|
|
32
32
|
|
|
33
33
|
**Returns:**
|
|
34
34
|
- `float`: The radius of the polygon
|
|
@@ -56,7 +56,7 @@ Finds the intersection point along one axis given a coordinate on the other axis
|
|
|
56
56
|
### distance_to_circle_edge
|
|
57
57
|
|
|
58
58
|
```python
|
|
59
|
-
distance_to_circle_edge(radius: float, point: tuple, angle: float) -> float
|
|
59
|
+
distance_to_circle_edge(radius: float, point: tuple[float, float], angle: float) -> float
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
Calculates the distance from a given point to the edge of a circle in a specified direction.
|
|
@@ -87,15 +87,15 @@ Calculates the opposite side length of a right triangle given the angle and adja
|
|
|
87
87
|
**Returns:**
|
|
88
88
|
- `float`: The length of the opposite side
|
|
89
89
|
|
|
90
|
-
###
|
|
90
|
+
### radius_to_apothem
|
|
91
91
|
|
|
92
92
|
```python
|
|
93
|
-
def
|
|
93
|
+
def radius_to_apothem(radius: float, side_count: int = 6) -> float
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
**Arguments:**
|
|
97
97
|
- `radius` (float): the radius of the polygon
|
|
98
|
-
- `side_count` (
|
|
98
|
+
- `side_count` (int): the number of sides of the polygon
|
|
99
99
|
|
|
100
100
|
**Returns:**
|
|
101
101
|
- `float`: The apothem of the polygon
|
|
@@ -112,7 +112,7 @@ DiamondCylinder(
|
|
|
112
112
|
radius: float,
|
|
113
113
|
height: float,
|
|
114
114
|
arc_size: float = 360,
|
|
115
|
-
stretch: tuple = (1, 1, 1),
|
|
115
|
+
stretch: tuple[float, float, float] = (1, 1, 1),
|
|
116
116
|
rotation: RotationLike = (0, 0, 0),
|
|
117
117
|
align: Align | tuple[Align, Align, Align] | None = None,
|
|
118
118
|
mode: Mode = Mode.ADD,
|
|
@@ -123,7 +123,7 @@ DiamondCylinder(
|
|
|
123
123
|
- `radius` (float): The radius of the circumscribed circle
|
|
124
124
|
- `height` (float): The height of the extrusion
|
|
125
125
|
- `arc_size` (float, default=360): Angular sweep in degrees for the circular clipping sector used to intersect the base profile
|
|
126
|
-
- `stretch` (tuple, default=(1, 1, 1)): Scaling factors (X, Y, Z)
|
|
126
|
+
- `stretch` (tuple[float, float, float], default=(1, 1, 1)): Scaling factors (X, Y, Z)
|
|
127
127
|
- `rotation` (RotationLike, default=(0, 0, 0)): Rotation angles (X, Y, Z) in degrees
|
|
128
128
|
- `align` (Align | tuple[Align, Align, Align] | None, default=None): Alignment along X, Y, Z axes
|
|
129
129
|
- `mode` (Mode, default=Mode.ADD): Boolean combination mode
|
|
@@ -145,7 +145,7 @@ Creates a torus by sweeping a diamond (square rotated 45°) along a circular pat
|
|
|
145
145
|
DiamondTorus(
|
|
146
146
|
major_radius: float,
|
|
147
147
|
minor_radius: float,
|
|
148
|
-
stretch: tuple = (1, 1)
|
|
148
|
+
stretch: tuple[float, float] = (1, 1)
|
|
149
149
|
)
|
|
150
150
|
```
|
|
151
151
|
|
|
@@ -154,7 +154,7 @@ Creates a torus by sweeping a diamond (square rotated 45°) along a circular pat
|
|
|
154
154
|
**Arguments:**
|
|
155
155
|
- `major_radius` (float): The radius of the circular sweep path
|
|
156
156
|
- `minor_radius` (float): The radius of the diamond cross-section
|
|
157
|
-
- `stretch` (tuple, default=(1, 1)): Scaling factors for the diamond shape
|
|
157
|
+
- `stretch` (tuple[float, float], default=(1, 1)): Scaling factors for the diamond shape
|
|
158
158
|
- `rotation` (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0)
|
|
159
159
|
- `align` (Align | tuple[Align, Align, Align] | None, optional): align MIN, CENTER,
|
|
160
160
|
or MAX of object. Defaults to (Align.CENTER, Align.CENTER, Align.CENTER)
|
|
@@ -175,7 +175,7 @@ PolygonalCylinder(
|
|
|
175
175
|
height: float,
|
|
176
176
|
side_count: int = 6,
|
|
177
177
|
arc_size: float = 360,
|
|
178
|
-
stretch: tuple = (1, 1, 1),
|
|
178
|
+
stretch: tuple[float, float, float] = (1, 1, 1),
|
|
179
179
|
rotation: RotationLike = (0, 0, 0),
|
|
180
180
|
align: Align | tuple[Align, Align, Align] | None = None,
|
|
181
181
|
mode: Mode = Mode.ADD,
|
|
@@ -188,7 +188,7 @@ PolygonalCylinder(
|
|
|
188
188
|
- `height` (float): The height of the extrusion
|
|
189
189
|
- `side_count` (int, default=6): Number of sides of the polygon
|
|
190
190
|
- `arc_size` (float, default=360): Angular sweep in degrees for the circular clipping sector used to intersect the base profile
|
|
191
|
-
- `stretch` (tuple, default=(1, 1, 1)): Scaling factors (X, Y, Z)
|
|
191
|
+
- `stretch` (tuple[float, float, float], default=(1, 1, 1)): Scaling factors (X, Y, Z)
|
|
192
192
|
- `rotation` (RotationLike, default=(0, 0, 0)): Angles to rotate about axes
|
|
193
193
|
- `align` (Align | tuple[Align, Align, Align] | None, default=None): Align MIN, CENTER, or MAX on each axis
|
|
194
194
|
- `mode` (Mode, default=Mode.ADD): Boolean combination mode
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Dovetail
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Dovetail is intended for breaking large parts into a dovetail and socketed part that can be easily fitted together with very tight and precise tolerances. This might be useful, for example, when designing parts that cannot fit onto common 3d-printer beds, and must be broken into multiple parts.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
The `dovetail_subpart` function takes a build123d part and the necessary parameters to break it into either the dovetail or the socket component of the split. Call it twice with the same arguments, changing only `subpart`, to produce a mating pair.
|
|
10
|
+
|
|
11
|
+
All linear dimensions are in millimeters and all angles are in degrees.
|
|
12
|
+
|
|
13
|
+
## Terminology
|
|
14
|
+
|
|
15
|
+
- **subpart** — one of the two pieces produced by splitting a part. This is what `dovetail_subpart` returns, and the `subpart=` argument selects which one you get.
|
|
16
|
+
- **tail** — the subpart carrying the protruding tongue (`DovetailSubpart.TAIL`).
|
|
17
|
+
- **socket** — the subpart carrying the matching recess (`DovetailSubpart.SOCKET`).
|
|
18
|
+
|
|
19
|
+
The two subparts are not equal halves: the tongue belongs to the tail, and for `SNUGTAIL` the joint wraps around three sides, so the socket is typically several times the volume of the tail.
|
|
20
|
+
|
|
21
|
+
## Styles
|
|
22
|
+
|
|
23
|
+
`style` selects the joint geometry, and **it determines which of the other arguments have any effect**:
|
|
24
|
+
|
|
25
|
+
- `DovetailStyle.SNUGTAIL` *(default)* — an updated design for 3d printing that wraps around three sides of the object for a tighter fit and a large glue/friction surface.
|
|
26
|
+
- `DovetailStyle.TRADITIONAL` — a traditional woodworking dovetail.
|
|
27
|
+
- `DovetailStyle.T_SLOT` — a T-shaped tail, sized by slot count and depth rather than by tongue ratios.
|
|
28
|
+
|
|
29
|
+
## Arguments
|
|
30
|
+
|
|
31
|
+
### Always applicable
|
|
32
|
+
|
|
33
|
+
- `part` (Part): The part to split into a dovetail or socket part. The part should be oriented along the XY plane.
|
|
34
|
+
- `start` (Point): The start point along the XY Plane for the dovetail line.
|
|
35
|
+
- `end` (Point): The end point along the XY Plane for the dovetail line.
|
|
36
|
+
- `subpart` (DovetailSubpart, default=`DovetailSubpart.TAIL`): Which subpart to create — `DovetailSubpart.TAIL` or `DovetailSubpart.SOCKET`.
|
|
37
|
+
- `style` (DovetailStyle, default=`DovetailStyle.SNUGTAIL`): The dovetail style. See [Styles](#styles).
|
|
38
|
+
- `tolerance` (float, default=0.025): The clearance between tail and socket, in mm.
|
|
39
|
+
- `vertical_tolerance` (float, default=0.2): Additional tolerance for vertical offset, given that in printing, supports or bridging introduce additional volume.
|
|
40
|
+
- `scarf_angle` (float, default=0): Places the entire cut and dovetail at an angle along the Z-axis. Likely to improve stability in some parts.
|
|
41
|
+
- `taper_angle` (float, default=0): Tapers the dovetail by the given angle. Even a small taper angle can allow for easier assembly.
|
|
42
|
+
- `vertical_offset` (float, default=0): Offsets the dovetail along the Z axis, producing a straight cut on one side that acts as a hard stop when fitting. A positive value gives a straight cut on the bottom of the part, a negative value on the top.
|
|
43
|
+
- `click_fit_radius` (float, default=0): The radius of the click-fit divots. `0` disables them.
|
|
44
|
+
|
|
45
|
+
### Style-conditional
|
|
46
|
+
|
|
47
|
+
The table below reflects what each parameter actually changes. Passing a parameter to a style that does not use it is currently accepted and silently ignored.
|
|
48
|
+
|
|
49
|
+
| Argument | Default | TRADITIONAL | SNUGTAIL | T_SLOT |
|
|
50
|
+
|---|---|---|---|---|
|
|
51
|
+
| `length_ratio` | 1/3 | ✅ | ✅ | — |
|
|
52
|
+
| `depth_ratio` | 1/6 | ✅ | — (see below) | — |
|
|
53
|
+
| `tail_angle_offset` | 15 | ✅ | ✅ | — |
|
|
54
|
+
| `linear_offset` | 0 | ✅ | — | — |
|
|
55
|
+
| `slot_count` | 1 | — | — | ✅ |
|
|
56
|
+
| `depth` | 2 | — | — | ✅ |
|
|
57
|
+
|
|
58
|
+
- `length_ratio` (float, default=1/3): The ratio of the length of the tongue to the total length of the cut.
|
|
59
|
+
- `depth_ratio` (float, default=1/6): The ratio of the depth of the tongue to the total length of the cut.
|
|
60
|
+
- `tail_angle_offset` (float, default=15): The adjustment pitch of the angle of the dovetail. `0` results in a square dovetail.
|
|
61
|
+
- `linear_offset` (float, default=0): Offsets the center of the tail or socket along the line by the amount specified. This slides the joint along the cut without changing its volume.
|
|
62
|
+
- `slot_count` (int, default=1): The number of slots to be added.
|
|
63
|
+
- `depth` (float, default=2): The depth of the T-slot into the socket.
|
|
64
|
+
|
|
65
|
+
!!! note "`depth_ratio` and SNUGTAIL"
|
|
66
|
+
|
|
67
|
+
SNUGTAIL does not accept `depth_ratio` from `dovetail_subpart`; it uses its own
|
|
68
|
+
prototyped value of `0.15`. This is deliberate, not an oversight. The snugtail depth
|
|
69
|
+
model was rewritten after physical prototyping so that `depth_ratio` no longer means
|
|
70
|
+
what it means for TRADITIONAL, and forwarding the shared value would change the
|
|
71
|
+
geometry of every snugtail joint. `length_ratio` and `tail_angle_offset` *are*
|
|
72
|
+
honored for SNUGTAIL.
|
|
73
|
+
|
|
74
|
+
## Returns
|
|
75
|
+
|
|
76
|
+
- `Part`: The requested subpart — the tail or the socket, per `subpart`.
|
|
77
|
+
|
|
78
|
+
## Example
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from build123d import Align, Box, BuildPart, Mode
|
|
82
|
+
from b3dkit import Point, DovetailSubpart, DovetailStyle, dovetail_subpart
|
|
83
|
+
|
|
84
|
+
with BuildPart(mode=Mode.PRIVATE) as longbox:
|
|
85
|
+
Box(50, 40, 50, align=(Align.CENTER, Align.CENTER, Align.MIN))
|
|
86
|
+
|
|
87
|
+
start = Point(0, -20)
|
|
88
|
+
end = Point(0, 20)
|
|
89
|
+
|
|
90
|
+
# A mating pair with default parameters (SNUGTAIL).
|
|
91
|
+
tail = dovetail_subpart(longbox.part, start, end, subpart=DovetailSubpart.TAIL)
|
|
92
|
+
socket = dovetail_subpart(longbox.part, start, end, subpart=DovetailSubpart.SOCKET)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Both subparts of a joint must be built from the same arguments — only `subpart` may
|
|
96
|
+
differ. Any other divergence produces two subparts that are individually valid and do
|
|
97
|
+
not fit together.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# A traditional dovetail with custom proportions.
|
|
101
|
+
joint = dict(
|
|
102
|
+
style=DovetailStyle.TRADITIONAL,
|
|
103
|
+
tolerance=0.1,
|
|
104
|
+
scarf_angle=5,
|
|
105
|
+
taper_angle=2.0,
|
|
106
|
+
length_ratio=0.7,
|
|
107
|
+
depth_ratio=1 / 4,
|
|
108
|
+
click_fit_radius=0.2,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
tail = dovetail_subpart(longbox.part, start, end, subpart=DovetailSubpart.TAIL, **joint)
|
|
112
|
+
socket = dovetail_subpart(longbox.part, start, end, subpart=DovetailSubpart.SOCKET, **joint)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Raises
|
|
116
|
+
|
|
117
|
+
- `ValueError`: if `start` and `end` are the same point.
|
|
118
|
+
- `ValueError`: if `abs(vertical_offset)` exceeds the part's height.
|
|
119
|
+
- `ValueError`: if `vertical_offset` is negative and `taper_angle` is negative.
|
|
120
|
+
- `ValueError`: if `vertical_offset` is positive and `taper_angle` is positive.
|
|
121
|
+
- `ValueError`: for SNUGTAIL, if `length_ratio + depth_ratio` exceeds 1.
|
|
@@ -84,6 +84,17 @@ Creates only the base component of the box. This includes the hollowed-out inter
|
|
|
84
84
|
**Returns:**
|
|
85
85
|
- `Part`: The base part with hollowed interior and rail channels
|
|
86
86
|
|
|
87
|
+
## Dimension requirements
|
|
88
|
+
|
|
89
|
+
All three functions validate their inputs and raise `ValueError` if the part cannot be
|
|
90
|
+
built as a single solid:
|
|
91
|
+
|
|
92
|
+
- `top_height`, `rail_height` and `wall_thickness` must all be greater than 0.
|
|
93
|
+
- `wall_thickness * 2` must be less than the smaller of the part's width and depth.
|
|
94
|
+
- At least `wall_thickness` of the part's height must remain below the rails, i.e.
|
|
95
|
+
`part_height - top_height - rail_height >= wall_thickness`. With less than that, the
|
|
96
|
+
divots have too little material to fuse into and break away as separate solids.
|
|
97
|
+
|
|
87
98
|
## Design Considerations
|
|
88
99
|
|
|
89
100
|
### Rail System
|
|
@@ -64,34 +64,6 @@ Creates only the sliding lid component of the box. This is useful when you need
|
|
|
64
64
|
**Returns:**
|
|
65
65
|
- `Part`: The sliding lid part with taper and optional thumb grip
|
|
66
66
|
|
|
67
|
-
### slider_template
|
|
68
|
-
|
|
69
|
-
```python
|
|
70
|
-
def slider_template(
|
|
71
|
-
sketch: Sketch,
|
|
72
|
-
wall_thickness: float = 2,
|
|
73
|
-
tolerance: float = 0.2,
|
|
74
|
-
top_offset: float = 0,
|
|
75
|
-
x_straighten_distance: float = 0,
|
|
76
|
-
divot_radius: float = 0,
|
|
77
|
-
cut_template: bool = True,
|
|
78
|
-
) -> Part
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Creates a slider template part based on a 2D sketch. This is an internal function used to generate the sliding mechanism geometry.
|
|
82
|
-
|
|
83
|
-
**Arguments:**
|
|
84
|
-
- `sketch` (Sketch): 2D sketch defining the slider cross-section
|
|
85
|
-
- `wall_thickness` (float, default=2): Thickness of the walls
|
|
86
|
-
- `tolerance` (float, default=0.2): Clearance for the sliding fit
|
|
87
|
-
- `top_offset` (float, default=0): Vertical offset from the top
|
|
88
|
-
- `x_straighten_distance` (float, default=0): Distance for straight sections at edges
|
|
89
|
-
- `divot_radius` (float, default=0): Radius of divots
|
|
90
|
-
- `cut_template` (bool, default=True): Whether this is for cutting (True) or building (False)
|
|
91
|
-
|
|
92
|
-
**Returns:**
|
|
93
|
-
- `Part`: The slider template part
|
|
94
|
-
|
|
95
67
|
## Design Principles
|
|
96
68
|
|
|
97
69
|
### Tapered Sliding Mechanism
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "b3dkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="x0pherl"},
|
|
6
6
|
]
|
|
7
7
|
description = "build123d libraries and utilities"
|
|
8
8
|
readme = "README.md"
|
|
9
|
-
requires-python = ">=3.
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
10
|
classifiers = [
|
|
11
11
|
"Programming Language :: Python :: 3",
|
|
12
12
|
"License :: OSI Approved :: MIT License",
|
|
13
13
|
"Operating System :: OS Independent",
|
|
14
14
|
]
|
|
15
15
|
dependencies = [
|
|
16
|
-
"build123d>=0.
|
|
16
|
+
"build123d>=0.11.0",
|
|
17
17
|
"ocp_vscode>=3.1.1",
|
|
18
18
|
]
|
|
19
19
|
|
|
@@ -48,6 +48,32 @@ build-backend = "hatchling.build"
|
|
|
48
48
|
exclude = [".venv-1"]
|
|
49
49
|
|
|
50
50
|
[tool.pytest.ini_options]
|
|
51
|
+
pythonpath = ["src"]
|
|
52
|
+
addopts = "--strict-markers"
|
|
51
53
|
markers = [
|
|
52
54
|
"manual: marks tests that can only be executed manually",
|
|
53
|
-
]
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.black]
|
|
58
|
+
line-length = 88
|
|
59
|
+
target-version = ["py311"]
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
line-length = 88
|
|
63
|
+
target-version = "py311"
|
|
64
|
+
src = ["src", "tests"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
68
|
+
ignore = [
|
|
69
|
+
"E501", # line length is black's job
|
|
70
|
+
"B008", # build123d builder-context idioms call in argument defaults
|
|
71
|
+
# build123d's builder pattern is `with BuildPart() as p:` where the binding
|
|
72
|
+
# is often unused -- the context manager does the work. 10 of the 12 F841
|
|
73
|
+
# hits in this package are that idiom, so the rule costs more than it earns.
|
|
74
|
+
"F841",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint.per-file-ignores]
|
|
78
|
+
# demo blocks under __main__ import the viewer after the module body starts
|
|
79
|
+
"src/b3dkit/*.py" = ["E402"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""build123d libraries and utilities.
|
|
2
|
+
|
|
3
|
+
The names listed in ``__all__`` are b3dkit's public API; anything else is an
|
|
4
|
+
implementation detail.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from b3dkit.antichamfer import anti_chamfer
|
|
8
|
+
from b3dkit.ball_socket import BallMount, BallSocket
|
|
9
|
+
from b3dkit.basic_shapes import (
|
|
10
|
+
DiamondCylinder,
|
|
11
|
+
DiamondTorus,
|
|
12
|
+
PolygonalCylinder,
|
|
13
|
+
RoundedCylinder,
|
|
14
|
+
Teardrop,
|
|
15
|
+
TeardropCylinder,
|
|
16
|
+
adjacent_length,
|
|
17
|
+
apothem_to_radius,
|
|
18
|
+
circular_intersection,
|
|
19
|
+
distance_to_circle_edge,
|
|
20
|
+
opposite_length,
|
|
21
|
+
radius_to_apothem,
|
|
22
|
+
)
|
|
23
|
+
from b3dkit.bolt_fittings import (
|
|
24
|
+
BoltCutSinkhole,
|
|
25
|
+
HeatsinkCut,
|
|
26
|
+
NutCut,
|
|
27
|
+
ScrewCut,
|
|
28
|
+
SquareNutSinkhole,
|
|
29
|
+
TeardropBoltCutSinkhole,
|
|
30
|
+
)
|
|
31
|
+
from b3dkit.click_fit import Divot
|
|
32
|
+
from b3dkit.dovetail import DovetailStyle, DovetailSubpart, dovetail_subpart
|
|
33
|
+
from b3dkit.hexwall import HexCylindrical, HexWall
|
|
34
|
+
from b3dkit.high_top_slide_box import (
|
|
35
|
+
high_top_slide_box,
|
|
36
|
+
high_top_slide_box_base,
|
|
37
|
+
high_top_slide_box_lid,
|
|
38
|
+
)
|
|
39
|
+
from b3dkit.point import Point, midpoint, shifted_midpoint
|
|
40
|
+
from b3dkit.slide_box import slide_box, slide_lid
|
|
41
|
+
from b3dkit.twist_snap import TwistSnapConnector, TwistSnapSocket
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
# antichamfer
|
|
45
|
+
"anti_chamfer",
|
|
46
|
+
# ball_socket
|
|
47
|
+
"BallMount",
|
|
48
|
+
"BallSocket",
|
|
49
|
+
# basic_shapes
|
|
50
|
+
"DiamondCylinder",
|
|
51
|
+
"DiamondTorus",
|
|
52
|
+
"PolygonalCylinder",
|
|
53
|
+
"RoundedCylinder",
|
|
54
|
+
"Teardrop",
|
|
55
|
+
"TeardropCylinder",
|
|
56
|
+
"adjacent_length",
|
|
57
|
+
"apothem_to_radius",
|
|
58
|
+
"circular_intersection",
|
|
59
|
+
"distance_to_circle_edge",
|
|
60
|
+
"opposite_length",
|
|
61
|
+
"radius_to_apothem",
|
|
62
|
+
# bolt_fittings
|
|
63
|
+
"BoltCutSinkhole",
|
|
64
|
+
"HeatsinkCut",
|
|
65
|
+
"NutCut",
|
|
66
|
+
"ScrewCut",
|
|
67
|
+
"SquareNutSinkhole",
|
|
68
|
+
"TeardropBoltCutSinkhole",
|
|
69
|
+
# click_fit
|
|
70
|
+
"Divot",
|
|
71
|
+
# dovetail
|
|
72
|
+
"DovetailSubpart",
|
|
73
|
+
"DovetailStyle",
|
|
74
|
+
"dovetail_subpart",
|
|
75
|
+
# hexwall
|
|
76
|
+
"HexCylindrical",
|
|
77
|
+
"HexWall",
|
|
78
|
+
# high_top_slide_box
|
|
79
|
+
"high_top_slide_box",
|
|
80
|
+
"high_top_slide_box_base",
|
|
81
|
+
"high_top_slide_box_lid",
|
|
82
|
+
# point
|
|
83
|
+
"Point",
|
|
84
|
+
"midpoint",
|
|
85
|
+
"shifted_midpoint",
|
|
86
|
+
# slide_box
|
|
87
|
+
"slide_box",
|
|
88
|
+
"slide_lid",
|
|
89
|
+
# twist_snap
|
|
90
|
+
"TwistSnapConnector",
|
|
91
|
+
"TwistSnapSocket",
|
|
92
|
+
]
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
from math import atan, degrees
|
|
2
|
+
|
|
1
3
|
from build123d import (
|
|
2
4
|
Align,
|
|
3
5
|
Axis,
|
|
4
6
|
BasePartObject,
|
|
5
7
|
Box,
|
|
6
|
-
BuildPart,
|
|
7
8
|
Builder,
|
|
9
|
+
BuildPart,
|
|
8
10
|
Compound,
|
|
9
11
|
Face,
|
|
10
12
|
Iterable,
|
|
@@ -17,8 +19,11 @@ from build123d import (
|
|
|
17
19
|
flatten_sequence,
|
|
18
20
|
validate_inputs,
|
|
19
21
|
)
|
|
20
|
-
from
|
|
21
|
-
|
|
22
|
+
from ocp_vscode import Camera, show
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"anti_chamfer",
|
|
26
|
+
]
|
|
22
27
|
|
|
23
28
|
|
|
24
29
|
def anti_chamfer(
|