partomatic 0.0.1__py3-none-any.whl
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.
- partomatic/__init__.py +3 -0
- partomatic/buildable_part.py +39 -0
- partomatic/partomatic.py +124 -0
- partomatic/partomatic_config.py +131 -0
- partomatic-0.0.1.dist-info/METADATA +32 -0
- partomatic-0.0.1.dist-info/RECORD +8 -0
- partomatic-0.0.1.dist-info/WHEEL +4 -0
- partomatic-0.0.1.dist-info/licenses/LICENSE +7 -0
partomatic/__init__.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""BuildablePart is a dataclass that contains a Part object and additional inormation for saving and
|
|
2
|
+
displaying the part"""
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field, fields, is_dataclass, MISSING
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from os import getcwd
|
|
7
|
+
|
|
8
|
+
from build123d import Part, Location
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class BuildablePart(Part):
|
|
13
|
+
part: Part = field(default_factory=Part)
|
|
14
|
+
display_location: Location = field(default_factory=Location)
|
|
15
|
+
stl_folder: str = getcwd()
|
|
16
|
+
_file_name: str = "partomatic"
|
|
17
|
+
|
|
18
|
+
def __init__(self, part, file_name, **kwargs):
|
|
19
|
+
self.display_location = Location()
|
|
20
|
+
self.file_name = file_name
|
|
21
|
+
self.part = part
|
|
22
|
+
if "display_location" in kwargs:
|
|
23
|
+
display_location = kwargs["display_location"]
|
|
24
|
+
if isinstance(display_location, Location):
|
|
25
|
+
self.display_location = display_location
|
|
26
|
+
if "stl_folder" in kwargs:
|
|
27
|
+
self.stl_folder = kwargs["stl_folder"]
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def file_name(self) -> str:
|
|
31
|
+
return self._file_name
|
|
32
|
+
|
|
33
|
+
@file_name.setter
|
|
34
|
+
def file_name(self, value: str):
|
|
35
|
+
"""
|
|
36
|
+
Assigns the file name to the BuildablePart, ensuring that no
|
|
37
|
+
file extension is included.
|
|
38
|
+
"""
|
|
39
|
+
self._file_name = Path(value).stem
|
partomatic/partomatic.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Part extended for CI/CD automation"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field, fields, is_dataclass, MISSING
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from build123d import Part, Location, export_stl
|
|
8
|
+
|
|
9
|
+
import ocp_vscode
|
|
10
|
+
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
from .partomatic_config import PartomaticConfig
|
|
14
|
+
from .buildable_part import BuildablePart
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Partomatic(ABC):
|
|
18
|
+
"""
|
|
19
|
+
Partomatic is an extension of the Compound class from build123d
|
|
20
|
+
that allows for automation within a continuous integration
|
|
21
|
+
environment. Descendant classes must implement:
|
|
22
|
+
- compile: generating the geometry of components in the parts list
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
_config: PartomaticConfig
|
|
26
|
+
parts: list[BuildablePart] = field(default_factory=list)
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def compile(self):
|
|
30
|
+
"""
|
|
31
|
+
Builds the relevant parts for the partomatic part
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def display(self):
|
|
35
|
+
"""
|
|
36
|
+
Shows the relevant parts in OCP CAD Viewer
|
|
37
|
+
"""
|
|
38
|
+
ocp_vscode.show(
|
|
39
|
+
(
|
|
40
|
+
[
|
|
41
|
+
part.part.move(Location(part.display_location))
|
|
42
|
+
for part in self.parts
|
|
43
|
+
]
|
|
44
|
+
),
|
|
45
|
+
reset_camera=ocp_vscode.Camera.KEEP,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def complete_stl_file_path(self, part: BuildablePart) -> str:
|
|
49
|
+
return str(
|
|
50
|
+
Path(
|
|
51
|
+
Path(part.stl_folder)
|
|
52
|
+
/ f"{self._config.file_prefix}{part.file_name}{self._config.file_suffix}"
|
|
53
|
+
).with_suffix(".stl")
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def export_stls(self):
|
|
57
|
+
"""
|
|
58
|
+
Generates the relevant STLs in the configured
|
|
59
|
+
folder
|
|
60
|
+
"""
|
|
61
|
+
if self._config.stl_folder == "NONE":
|
|
62
|
+
return
|
|
63
|
+
for part in self.parts:
|
|
64
|
+
Path(self.complete_stl_file_path(part)).parent.mkdir(
|
|
65
|
+
parents=True, exist_ok=self._config.create_folders_if_missing
|
|
66
|
+
)
|
|
67
|
+
if (
|
|
68
|
+
not Path(self.complete_stl_file_path(part)).parent.exists()
|
|
69
|
+
or not Path(self.complete_stl_file_path(part)).parent.is_dir()
|
|
70
|
+
):
|
|
71
|
+
raise FileNotFoundError(
|
|
72
|
+
f"Directory {Path(self.complete_stl_file_path(part)).parent} does not exist"
|
|
73
|
+
)
|
|
74
|
+
export_stl(part.part, self.complete_stl_file_path(part))
|
|
75
|
+
|
|
76
|
+
def load_config(self, configuration: any, **kwargs):
|
|
77
|
+
"""
|
|
78
|
+
loads a partomatic configuration from a file or valid yaml
|
|
79
|
+
-------
|
|
80
|
+
arguments:
|
|
81
|
+
- configuration: the path to the configuration file
|
|
82
|
+
OR
|
|
83
|
+
a valid yaml configuration string
|
|
84
|
+
-------
|
|
85
|
+
notes:
|
|
86
|
+
if yaml_tree is set in the PartomaticConfig descendent,
|
|
87
|
+
PartomaticConfig will use that tree to find a node deep
|
|
88
|
+
within the yaml tree, following the node names separated by slashes
|
|
89
|
+
(example: "BigObject/Partomatic")
|
|
90
|
+
"""
|
|
91
|
+
self._config.load_config(configuration, **kwargs)
|
|
92
|
+
|
|
93
|
+
def __init__(self, configuration: any = None, **kwargs):
|
|
94
|
+
"""
|
|
95
|
+
loads a partomatic configuration from a file or valid yaml
|
|
96
|
+
-------
|
|
97
|
+
arguments:
|
|
98
|
+
- configuration: the path to the configuration file
|
|
99
|
+
OR
|
|
100
|
+
a valid yaml configuration string
|
|
101
|
+
OR
|
|
102
|
+
None (default) for an empty object
|
|
103
|
+
- **kwargs: specific fields to set in the configuration
|
|
104
|
+
-------
|
|
105
|
+
notes:
|
|
106
|
+
you can assign yaml_tree as a kwarg here to load a
|
|
107
|
+
configuration from a node node deep within the yaml tree,
|
|
108
|
+
following the node names separated by slashes
|
|
109
|
+
(example: "BigObject/Partomatic")
|
|
110
|
+
"""
|
|
111
|
+
self.parts = []
|
|
112
|
+
self._config = self.__class__._config
|
|
113
|
+
self.load_config(configuration, **kwargs)
|
|
114
|
+
|
|
115
|
+
def partomate(self):
|
|
116
|
+
"""automates the part generation and exports stl and step models
|
|
117
|
+
-------
|
|
118
|
+
notes:
|
|
119
|
+
- if you want to avoid exporting one of those file formats,
|
|
120
|
+
you can override the export_stls or export_steps methods
|
|
121
|
+
with a no-op method using the pass keyword
|
|
122
|
+
"""
|
|
123
|
+
self.compile()
|
|
124
|
+
self.export_stls()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Part extended for CI/CD automation"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field, fields, is_dataclass, MISSING
|
|
4
|
+
from enum import Enum, Flag
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AutoDataclassMeta(type):
|
|
11
|
+
def __new__(cls, name, bases, dct):
|
|
12
|
+
new_cls = super().__new__(cls, name, bases, dct)
|
|
13
|
+
return dataclass(init=False)(new_cls)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class PartomaticConfig(metaclass=AutoDataclassMeta):
|
|
18
|
+
yaml_tree: str = "Part"
|
|
19
|
+
stl_folder: str = "NONE"
|
|
20
|
+
file_prefix: str = ""
|
|
21
|
+
file_suffix: str = ""
|
|
22
|
+
create_folders_if_missing: bool = True
|
|
23
|
+
|
|
24
|
+
def _default_config(self):
|
|
25
|
+
"""
|
|
26
|
+
Resets all values to their default values.
|
|
27
|
+
"""
|
|
28
|
+
for field in fields(self):
|
|
29
|
+
if field.default is not MISSING:
|
|
30
|
+
setattr(self, field.name, field.default)
|
|
31
|
+
elif field.default_factory is not MISSING:
|
|
32
|
+
setattr(self, field.name, field.default_factory())
|
|
33
|
+
else:
|
|
34
|
+
raise ValueError(f"Field {field.name} has no default value")
|
|
35
|
+
|
|
36
|
+
def load_config(self, configuration: any, **kwargs):
|
|
37
|
+
"""
|
|
38
|
+
loads a partomatic configuration from a file or valid yaml
|
|
39
|
+
-------
|
|
40
|
+
arguments:
|
|
41
|
+
- configuration: the path to the configuration file
|
|
42
|
+
OR
|
|
43
|
+
a valid yaml configuration string
|
|
44
|
+
-------
|
|
45
|
+
notes:
|
|
46
|
+
if yaml_tree is set in the PartomaticConfig descendent,
|
|
47
|
+
PartomaticConfig will use that tree to find a node deep
|
|
48
|
+
within the yaml tree, following the node names separated by slashes
|
|
49
|
+
(example: "BigObject/Partomatic")
|
|
50
|
+
"""
|
|
51
|
+
if "yaml_tree" in kwargs:
|
|
52
|
+
self.yaml_tree = kwargs["yaml_tree"]
|
|
53
|
+
if isinstance(configuration, self.__class__):
|
|
54
|
+
for field in fields(self):
|
|
55
|
+
setattr(self, field.name, getattr(configuration, field.name))
|
|
56
|
+
return
|
|
57
|
+
if configuration is not None:
|
|
58
|
+
configuration = str(configuration)
|
|
59
|
+
if "\n" not in configuration:
|
|
60
|
+
path = Path(configuration)
|
|
61
|
+
if path.exists() and path.is_file():
|
|
62
|
+
configuration = path.read_text()
|
|
63
|
+
bracket_dict = yaml.safe_load(configuration)
|
|
64
|
+
for node in self.yaml_tree.split("/"):
|
|
65
|
+
if node not in bracket_dict:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"Node {node} not found in configuration file"
|
|
68
|
+
)
|
|
69
|
+
bracket_dict = bracket_dict[node]
|
|
70
|
+
|
|
71
|
+
for classfield in fields(self.__class__):
|
|
72
|
+
if classfield.name in bracket_dict:
|
|
73
|
+
value = bracket_dict[classfield.name]
|
|
74
|
+
if isinstance(classfield.type, type) and issubclass(
|
|
75
|
+
classfield.type, (Enum, Flag)
|
|
76
|
+
):
|
|
77
|
+
setattr(
|
|
78
|
+
self,
|
|
79
|
+
classfield.name,
|
|
80
|
+
classfield.type[value.upper()],
|
|
81
|
+
)
|
|
82
|
+
elif is_dataclass(classfield.type) and isinstance(
|
|
83
|
+
value, dict
|
|
84
|
+
):
|
|
85
|
+
setattr(
|
|
86
|
+
self,
|
|
87
|
+
classfield.name,
|
|
88
|
+
classfield.type(**value),
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
setattr(self, classfield.name, value)
|
|
92
|
+
|
|
93
|
+
def __init__(self, configuration: any = None, **kwargs):
|
|
94
|
+
"""
|
|
95
|
+
loads a partomatic configuration from a file or valid yaml
|
|
96
|
+
-------
|
|
97
|
+
arguments:
|
|
98
|
+
- configuration: the path to the configuration file
|
|
99
|
+
OR
|
|
100
|
+
a valid yaml configuration string
|
|
101
|
+
OR
|
|
102
|
+
None (default) for an empty object
|
|
103
|
+
- **kwargs: specific fields to set in the configuration
|
|
104
|
+
-------
|
|
105
|
+
notes:
|
|
106
|
+
you can assign yaml_tree as a kwarg here to load a
|
|
107
|
+
configuration from a node node deep within the yaml tree,
|
|
108
|
+
following the node names separated by slashes
|
|
109
|
+
(example: "BigObject/Partomatic")
|
|
110
|
+
"""
|
|
111
|
+
if "yaml_tree" in kwargs:
|
|
112
|
+
self.yaml_tree = kwargs["yaml_tree"]
|
|
113
|
+
if configuration is not None:
|
|
114
|
+
self.load_config(configuration, yaml_tree=self.yaml_tree)
|
|
115
|
+
elif kwargs:
|
|
116
|
+
self._default_config()
|
|
117
|
+
for key, value in kwargs.items():
|
|
118
|
+
classfield = next(
|
|
119
|
+
(f for f in fields(self.__class__) if f.name == key),
|
|
120
|
+
None,
|
|
121
|
+
)
|
|
122
|
+
if classfield:
|
|
123
|
+
if is_dataclass(classfield.type):
|
|
124
|
+
if isinstance(value, dict):
|
|
125
|
+
setattr(self, key, classfield.type(**value))
|
|
126
|
+
else:
|
|
127
|
+
setattr(self, key, value)
|
|
128
|
+
else:
|
|
129
|
+
setattr(self, key, value)
|
|
130
|
+
else:
|
|
131
|
+
self._default_config()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: partomatic
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: build123d Part extended for CI/CD automation
|
|
5
|
+
Project-URL: Homepage, https://github.com/x0pher/partomatic
|
|
6
|
+
Project-URL: Issues, https://github.com/x0pher/partomatic/issues
|
|
7
|
+
Project-URL: docs, https://partomatic.readthedocs.org
|
|
8
|
+
Project-URL: documentation, https://partomatic.readthedocs.org
|
|
9
|
+
Author: x0pherl
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Partomatic
|
|
18
|
+
|
|
19
|
+
Partomatic is an attempt to build an automatable ecosystem for generating parametric models through automation -- making CI/CD automation possible for your 3d models.
|
|
20
|
+
|
|
21
|
+
# The Partomatic philosophy
|
|
22
|
+
|
|
23
|
+
Build123d is a powerful library, but it leaves the creation of final parts up to the developer. For a large project with many related and interlocking parts, this can make releasing a new version a project in and of itself.
|
|
24
|
+
|
|
25
|
+
[Partomatic](https://github.com/x0pherl/partomatic) enables _parametric modeling_ and standardizes some _build automation_ for a part.
|
|
26
|
+
|
|
27
|
+
## Parametric Modeling
|
|
28
|
+
Parametric 3D modeling is a method of creating 3D models where the geometry is defined by parameters, allowing for easy adjustment by simply changing the values of these parameters. This approach enables the creation of flexible and reusable designs that can be quickly adapted to different requirements.
|
|
29
|
+
|
|
30
|
+
## Build Automation
|
|
31
|
+
|
|
32
|
+
Build automation is a common practice in the software delivery world. Continuous Integration uses build automation to deliver software into testing and production environments whenever changes are checked in by a developer. Partomatic wraps additional information about how to name files and where to store them, so that an automated build script generate and save those parts.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
partomatic/__init__.py,sha256=KyBzMae6m-DNdr5PDTlEImmPSrX2i9-arys6s2ob77o,128
|
|
2
|
+
partomatic/buildable_part.py,sha256=m1S-JuEjIzBOK1M75FdpMFWekwOVmBmT44wv0LN0cFU,1299
|
|
3
|
+
partomatic/partomatic.py,sha256=3mfyGah8XFpw_ElrFQNao_XhiOkRZHhLloqwM6ZbOqA,4311
|
|
4
|
+
partomatic/partomatic_config.py,sha256=aGlLesFIRyvS_BNqpSW2ZYvrAE1oArUEUGY4NngkzIU,5168
|
|
5
|
+
partomatic-0.0.1.dist-info/METADATA,sha256=_q93pjtfVbuDZOc4Qj3-ycF4vB73Vj8VfgQyh1j6EAs,1898
|
|
6
|
+
partomatic-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
partomatic-0.0.1.dist-info/licenses/LICENSE,sha256=9PaWMWFAAGGaGsLa_BpN-Vd89Xca8eNlg3dbOExalCk,1074
|
|
8
|
+
partomatic-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2024 Christopher Litsinger
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|