Compare commits
111 Commits
touhou/hou
...
main
Author | SHA1 | Date |
---|---|---|
|
4f48955c81 | |
|
d7e3e1a8de | |
|
f669b61647 | |
|
2a405f916a | |
|
4c0a54dc4a | |
|
db4232f94d | |
|
675f4f995b | |
|
52b4e0b329 | |
|
7ec2728a6c | |
|
5d7137c037 | |
|
49d3fa44bf | |
|
89c0d88de7 | |
|
0a4ca64dad | |
|
d6ccc3496b | |
|
c8bbc0de91 | |
|
34ecf59124 | |
|
a0100f8fb7 | |
|
e4cfc71f1a | |
|
80730a9c5a | |
|
6777033383 | |
|
758b51c9db | |
|
ef0b0dad91 | |
|
6ad74047bc | |
|
7d3845f3c1 | |
|
b55dc8caa3 | |
|
e94546b017 | |
|
2d6d65235d | |
|
0f151bd279 | |
|
6709e4f32e | |
|
d937fc9513 | |
|
a3288ce98f | |
|
bec15c5136 | |
|
b565ab05a0 | |
|
af82a86652 | |
|
40c32213e1 | |
|
a8c80a307f | |
|
b1fe538747 | |
|
bd15f28403 | |
|
0574a767a3 | |
|
bd7e8677c7 | |
|
c5f9e570a6 | |
|
63c2c74e02 | |
|
0fb88a97d3 | |
|
4edad88299 | |
|
83d4232ad7 | |
|
4d4e4c7eab | |
|
b83bf5a57d | |
|
670d4a8c21 | |
|
22a4f4ceec | |
|
a684996475 | |
|
5b5ccee94e | |
|
b88d52f4be | |
|
ca606c6bc1 | |
|
916ccee260 | |
|
44cd6ee960 | |
|
4dcd97613b | |
|
97675a2fc8 | |
|
74145f88d2 | |
|
878d532890 | |
|
7511efa9ee | |
|
3e0eab0cec | |
|
70a157a0ab | |
|
026c2c933c | |
|
eae736fdfb | |
|
b15db172a0 | |
|
1e692b89ed | |
|
f7b915a7e4 | |
|
ccdcf018f8 | |
|
bfb4ad6973 | |
|
9d9d59ffeb | |
|
704baebd1e | |
|
aef269e2eb | |
|
46a27ef543 | |
|
eb6343fa32 | |
|
0991b39d8a | |
|
e1893d139f | |
|
a74f919a5b | |
|
f4704b9ad6 | |
|
590033e492 | |
|
396dd997e0 | |
|
89283efd59 | |
|
524ab73ea4 | |
|
6842b0c4c8 | |
|
6b0b604ae1 | |
|
317b187d43 | |
|
9ff3e72474 | |
|
9d78bf604a | |
|
596311f326 | |
|
6384d326c1 | |
|
6470010da6 | |
|
5ab611666e | |
|
a6685e6779 | |
|
418e6517a0 | |
|
9563501327 | |
|
d1fd830766 | |
|
3884d75f1c | |
|
ca437c3855 | |
|
8e4553311b | |
|
016b717b41 | |
|
eace8745f3 | |
|
70fbe7dcb3 | |
|
21b3c98856 | |
|
dbf374fe20 | |
|
9109676502 | |
|
077651e708 | |
|
e02ec4d257 | |
|
3ac342a65d | |
|
d910326096 | |
|
95313b76eb | |
|
bfa96e7cef | |
|
fbacd980c0 |
74
README.md
74
README.md
|
@ -1,24 +1,74 @@
|
|||
# Cosplay
|
||||
|
||||
This is the design repository for NorCal Hakkero Factory No. 1.
|
||||
This is the design repository for NorCal Hakkero Factory No. 1, where we use
|
||||
parametric CAD to make cosplay props. We design cosplay props based on an
|
||||
engineering point of view.
|
||||
|
||||
> NorCal Hakkero Factory № 1
|
||||
>
|
||||
> 北加国営八卦炉第一工場
|
||||
|
||||
Most cosplay schematics are created with Blender, CadQuery, and Inkscape.
|
||||
|
||||
## Development
|
||||
|
||||
Most cosplay schematics are created with Blender, CadQuery, and Inkscape. To
|
||||
enter into a CadQuery environment, install `poetry` and use
|
||||
```sh
|
||||
poetry install
|
||||
poetry shell
|
||||
```
|
||||
and this should succeed
|
||||
```sh
|
||||
python3 -c "import nhf"
|
||||
Install `uv`, and then execute
|
||||
|
||||
``` sh
|
||||
uv sync
|
||||
```
|
||||
|
||||
## Testing
|
||||
To get a development environment, run
|
||||
``` sh
|
||||
uv venv
|
||||
```
|
||||
|
||||
Then, either follow the instruction to activate this venv, or install `direnv`
|
||||
and create the file
|
||||
|
||||
``` sh
|
||||
# .envrc
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Test the environment with `python3 -c "import nhf"`
|
||||
|
||||
To visualize an object, create a file `visualize.py`, and run `cq-editor`:
|
||||
|
||||
``` sh
|
||||
python3 -m cq_editor visualize.py
|
||||
```
|
||||
|
||||
### Folder Structure
|
||||
|
||||
- `nhf/parts/`: Ready-made parts
|
||||
- `nhf/$WORK/$CHARACTER`: Design for an individual character
|
||||
|
||||
For each individual character, the `__init__.py` script stores the overall build
|
||||
entry point and the entry point for all unit tests.
|
||||
|
||||
### Testing
|
||||
|
||||
Run all tests with
|
||||
``` sh
|
||||
python3 -m unittest
|
||||
unittest-parallel
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Wayland
|
||||
|
||||
If there is the error
|
||||
```
|
||||
X Error of failed request: BadWindow (invalid Window parameter)
|
||||
Major opcode of failed request: 3 (X_GetWindowAttributes)
|
||||
Resource id in failed request: 0x3
|
||||
Serial number of failed request: 28
|
||||
Current serial number in output stream: 29
|
||||
```
|
||||
Export the environment variable
|
||||
|
||||
``` sh
|
||||
export QT_QPA_PLATFORM=xcb
|
||||
```
|
||||
|
||||
|
|
12
nhf/build.py
12
nhf/build.py
|
@ -90,7 +90,7 @@ class Target:
|
|||
x = (
|
||||
Cq.Workplane()
|
||||
.add(x._faces)
|
||||
.add(x._wires)
|
||||
.add(x.wires)
|
||||
.add(x._edges)
|
||||
)
|
||||
assert isinstance(x, Cq.Workplane)
|
||||
|
@ -214,7 +214,7 @@ class Submodel:
|
|||
def write_to(self, obj, path: str):
|
||||
x = self._method(obj)
|
||||
assert isinstance(x, Model), f"Unexpected type: {type(x)}"
|
||||
x.build_all(path)
|
||||
x.build_all(path, prefix=False)
|
||||
|
||||
@classmethod
|
||||
def methods(cls, subject):
|
||||
|
@ -271,11 +271,17 @@ class Model:
|
|||
total += 1
|
||||
return total
|
||||
|
||||
def build_all(self, output_dir: Union[Path, str] = "build", verbose=1):
|
||||
def build_all(
|
||||
self,
|
||||
output_dir: Union[Path, str] = "build",
|
||||
prefix: bool = True,
|
||||
verbose=1):
|
||||
"""
|
||||
Build all targets in this model and write the results to file
|
||||
"""
|
||||
output_dir = Path(output_dir)
|
||||
if prefix:
|
||||
output_dir = output_dir / self.name
|
||||
targets = Target.methods(self)
|
||||
for t in targets.values():
|
||||
file_name = t.file_name
|
||||
|
|
|
@ -25,13 +25,16 @@ class Role(Flag):
|
|||
PARENT = auto()
|
||||
CHILD = auto()
|
||||
CASING = auto()
|
||||
STATOR = auto()
|
||||
ROTOR = auto()
|
||||
BEARING = auto()
|
||||
# Springs, cushions
|
||||
DAMPING = auto()
|
||||
# Main structural support
|
||||
STRUCTURE = auto()
|
||||
DECORATION = auto()
|
||||
ELECTRONIC = auto()
|
||||
MOTION = auto()
|
||||
MOTOR = auto()
|
||||
|
||||
# Fasteners, etc.
|
||||
CONNECTION = auto()
|
||||
|
@ -59,11 +62,14 @@ ROLE_COLOR_MAP = {
|
|||
Role.PARENT: _color('blue4', 0.6),
|
||||
Role.CASING: _color('dodgerblue3', 0.6),
|
||||
Role.CHILD: _color('darkorange2', 0.6),
|
||||
Role.STATOR: _color('gray', 0.5),
|
||||
Role.ROTOR: _color('blue3', 0.5),
|
||||
Role.BEARING: _color('green3', 0.8),
|
||||
Role.DAMPING: _color('springgreen', 1.0),
|
||||
Role.STRUCTURE: _color('gray', 0.4),
|
||||
Role.DECORATION: _color('lightseagreen', 0.4),
|
||||
Role.ELECTRONIC: _color('mediumorchid', 0.7),
|
||||
Role.MOTION: _color('thistle3', 0.7),
|
||||
Role.MOTOR: _color('thistle3', 0.7),
|
||||
Role.CONNECTION: _color('steelblue3', 0.8),
|
||||
Role.HANDLE: _color('tomato4', 0.8),
|
||||
}
|
||||
|
@ -84,6 +90,9 @@ class Material(Enum):
|
|||
ACRYLIC_TRANSLUSCENT = 1.18, _color('ivory2', 0.8)
|
||||
ACRYLIC_TRANSPARENT = 1.18, _color('ghostwhite', 0.5)
|
||||
STEEL_SPRING = 7.8, _color('gray', 0.8)
|
||||
STEEL_STAINLESS = 7.8, _color('gray', 0.9)
|
||||
METAL_AL = 2.7, _color('gray', 0.6)
|
||||
METAL_BRASS = 8.5, _color('gold1', 0.8)
|
||||
|
||||
def __init__(self, density: float, color: Cq.Color):
|
||||
self.density = density
|
||||
|
@ -116,6 +125,9 @@ def add_with_material_role(
|
|||
Cq.Assembly.addS = add_with_material_role
|
||||
|
||||
def color_by_material(self: Cq.Assembly) -> Cq.Assembly:
|
||||
"""
|
||||
Set colours in an assembly by material
|
||||
"""
|
||||
for _, a in self.traverse():
|
||||
if KEY_MATERIAL not in a.metadata:
|
||||
continue
|
||||
|
@ -123,6 +135,9 @@ def color_by_material(self: Cq.Assembly) -> Cq.Assembly:
|
|||
return self
|
||||
Cq.Assembly.color_by_material = color_by_material
|
||||
def color_by_role(self: Cq.Assembly, avg: bool = True) -> Cq.Assembly:
|
||||
"""
|
||||
Set colours in an assembly by role
|
||||
"""
|
||||
for _, a in self.traverse():
|
||||
if KEY_ROLE not in a.metadata:
|
||||
continue
|
||||
|
|
|
@ -83,11 +83,16 @@ class BatteryBox18650(Item):
|
|||
battery_dist: float = 20.18
|
||||
height: float = 19.66
|
||||
# space from bottom to battery begin
|
||||
thickness: float = 1.66
|
||||
thickness: float = 2.28
|
||||
battery_diam: float = 18.48
|
||||
battery_height: float = 68.80
|
||||
n_batteries: int = 3
|
||||
|
||||
battery_gap: float = 2.0
|
||||
|
||||
diam_thread: float = 3.0
|
||||
hole_dy: float = 39.50 / 2
|
||||
|
||||
def __post_init__(self):
|
||||
assert 2 * self.thickness < min(self.length, self.height)
|
||||
|
||||
|
@ -95,13 +100,20 @@ class BatteryBox18650(Item):
|
|||
def name(self) -> str:
|
||||
return f"BatteryBox 18650*{self.n_batteries}"
|
||||
|
||||
@property
|
||||
def holes(self) -> list[Cq.Location]:
|
||||
return [
|
||||
Cq.Location.from2d(0, self.hole_dy),
|
||||
Cq.Location.from2d(0, -self.hole_dy),
|
||||
]
|
||||
|
||||
@property
|
||||
def role(self) -> Role:
|
||||
return Role.ELECTRONIC
|
||||
|
||||
def generate(self) -> Cq.Workplane:
|
||||
width = self.width_base + self.battery_dist * (self.n_batteries - 1) + self.battery_diam
|
||||
return (
|
||||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.box(
|
||||
length=self.length,
|
||||
|
@ -117,7 +129,7 @@ class BatteryBox18650(Item):
|
|||
centered=(True, True, False),
|
||||
combine='cut',
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XY', origin=(-self.battery_height/2, 0, self.thickness + self.battery_diam/2)))
|
||||
.copyWorkplane(Cq.Workplane('XY', origin=(-self.battery_height/2, 0, self.thickness + self.battery_diam/2 + self.battery_gap)))
|
||||
.rarray(
|
||||
xSpacing=1,
|
||||
ySpacing=self.battery_dist,
|
||||
|
@ -132,4 +144,16 @@ class BatteryBox18650(Item):
|
|||
centered=(True, True, False),
|
||||
combine=True,
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XY'))
|
||||
)
|
||||
hole = Cq.Solid.makeCylinder(
|
||||
radius=self.diam_thread/2,
|
||||
height=self.thickness,
|
||||
)
|
||||
result -= hole.moved(0, self.hole_dy)
|
||||
result -= hole.moved(0, -self.hole_dy)
|
||||
result.tagAbsolute("holeT0", (0, self.hole_dy, self.thickness), direction="+Z")
|
||||
result.tagAbsolute("holeT1", (0, -self.hole_dy, self.thickness), direction="+Z")
|
||||
result.tagAbsolute("holeB0", (0, self.hole_dy, 0), direction="-Z")
|
||||
result.tagAbsolute("holeB1", (0, -self.hole_dy, 0), direction="-Z")
|
||||
return result
|
||||
|
|
|
@ -11,6 +11,7 @@ class FlatHeadBolt(Item):
|
|||
height_head: float
|
||||
diam_thread: float
|
||||
height_thread: float
|
||||
pitch: float = 1.0
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
@ -34,7 +35,7 @@ class FlatHeadBolt(Item):
|
|||
centered=(True, True, False))
|
||||
)
|
||||
rod.faces("<Z").tag("tip")
|
||||
rod.faces(">Z").tag("root")
|
||||
rod.tagAbsolute("root", (0, 0, self.height_thread), direction="-Z")
|
||||
rod = rod.union(head.located(Cq.Location((0, 0, self.height_thread))))
|
||||
return rod
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import cadquery as Cq
|
||||
|
||||
def mystery():
|
||||
return (
|
||||
Cq.Workplane("XY")
|
||||
.box(10, 5, 5)
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.hole(1)
|
||||
.edges("|Z")
|
||||
.fillet(2)
|
||||
)
|
|
@ -0,0 +1,146 @@
|
|||
from dataclasses import dataclass
|
||||
import cadquery as Cq
|
||||
from nhf import Material, Role
|
||||
from nhf.build import Model, target, assembly, TargetKind, submodel
|
||||
from nhf.parts.box import MountingBox, Hole
|
||||
from nhf.parts.electronics import ArduinoUnoR3
|
||||
import nhf.utils
|
||||
|
||||
@dataclass
|
||||
class LightPanel(Model):
|
||||
|
||||
# Dimensions of the base panel
|
||||
length: float = 300.0
|
||||
width: float = 200.0
|
||||
|
||||
attach_height: float = 20.0
|
||||
attach_diam: float = 8.0
|
||||
attach_depth: float = 12.7
|
||||
|
||||
grid_height: float = 20.0
|
||||
grid_top_height: float = 5.0
|
||||
# Distance from grid to edge
|
||||
grid_margin: float = 20.0
|
||||
# Number of holes in each row of the grid
|
||||
grid_holes: int = 9
|
||||
grid_layers: int = 6
|
||||
grid_hole_width: float = 15.0
|
||||
|
||||
base_thickness: float = 25.4/16
|
||||
grid_thickness: float = 25.4/4
|
||||
base_material: Material = Material.WOOD_BIRCH
|
||||
grid_material: Material = Material.ACRYLIC_TRANSPARENT
|
||||
|
||||
controller: ArduinoUnoR3 = ArduinoUnoR3()
|
||||
|
||||
def __post_init__(self):
|
||||
assert self.grid_holes >= 2
|
||||
super().__init__(name="light-panel")
|
||||
|
||||
@property
|
||||
def grid_spacing_y(self) -> float:
|
||||
return (self.width - 2 * self.grid_margin - self.grid_thickness) / (self.grid_layers - 1)
|
||||
|
||||
@target(name="grid", kind=TargetKind.DXF)
|
||||
def grid_profile(self):
|
||||
w = self.length - self.grid_margin * 2
|
||||
h = self.grid_height + self.grid_top_height
|
||||
|
||||
# The width of one hole (w0) satisfies
|
||||
# n * w0 + (n+1) t = w
|
||||
# where t is the thickness of the edge
|
||||
n = self.grid_holes
|
||||
w0 = self.grid_hole_width
|
||||
t = (w - n * w0) / (n + 1)
|
||||
# The spacing is such that the first and last holes are a distance `margin`
|
||||
# away from the edges, so it satisfies
|
||||
# t + w0/2 + (n-1) * s + w0/2 + t = w
|
||||
step = (w - t*2 - w0) / (n - 1)
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.push([(0, h/2)])
|
||||
.rect(w, h)
|
||||
.push([
|
||||
(i * step + t + w0/2 - w/2, self.grid_height/2)
|
||||
for i in range(0, n)
|
||||
])
|
||||
.rect(w0, self.grid_height, mode='s')
|
||||
)
|
||||
|
||||
def grid(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.grid_profile())
|
||||
.extrude(self.grid_thickness)
|
||||
)
|
||||
|
||||
@submodel(name="base")
|
||||
def base(self) -> MountingBox:
|
||||
xshift = self.length / 2 - self.controller.length - self.grid_margin / 2
|
||||
yshift = self.grid_margin / 2
|
||||
holes = [
|
||||
Hole(
|
||||
x=x + xshift, y=y + yshift,
|
||||
diam=self.controller.hole_diam,
|
||||
tag=f"controller_conn{i}",
|
||||
)
|
||||
for i, (x, y) in enumerate(self.controller.holes)
|
||||
]
|
||||
return MountingBox(
|
||||
holes=holes,
|
||||
hole_diam=self.controller.hole_diam,
|
||||
length=self.length,
|
||||
width=self.width,
|
||||
centred=(True, False),
|
||||
thickness=self.base_thickness,
|
||||
)
|
||||
|
||||
@target(name="attachment")
|
||||
def attachment(self) -> Cq.Workplane:
|
||||
l = self.length / 2
|
||||
w = self.width / 2
|
||||
return (
|
||||
Cq.Workplane('XY')
|
||||
.box(
|
||||
l, w, self.attach_height,
|
||||
centered=(True, True, False),
|
||||
)
|
||||
.faces(">Z")
|
||||
.hole(self.attach_diam, self.attach_depth)
|
||||
)
|
||||
|
||||
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
assembly = (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
self.base().generate(),
|
||||
name="base",
|
||||
role=Role.STRUCTURE,
|
||||
material=self.base_material,
|
||||
)
|
||||
)
|
||||
# Grid thickness t is fixed, so the spacing of the grid satisfies
|
||||
# margin + t + (n-1) * spacing + margin = width
|
||||
spacing = self.grid_spacing_y
|
||||
shift = self.grid_margin + self.grid_thickness / 2
|
||||
for i in range(self.grid_layers):
|
||||
assembly = assembly.addS(
|
||||
self.grid(),
|
||||
name=f"grid_{i}",
|
||||
role=Role.STRUCTURE,
|
||||
material=self.grid_material,
|
||||
loc=Cq.Location(0, spacing * i + shift, self.base_thickness, 90, 0, 0),
|
||||
)
|
||||
return assembly
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
p = LightPanel()
|
||||
print(p.grid_spacing_y)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
p.build_all()
|
||||
sys.exit(0)
|
|
@ -1,14 +0,0 @@
|
|||
#+title: Cosplay: Houjuu Nue
|
||||
|
||||
* Controller
|
||||
|
||||
This part describes the electrical connections and the microcontroller code.
|
||||
|
||||
* Structure
|
||||
|
||||
This part describes the 3d printed and laser cut structures. ~structure.blend~
|
||||
is an overall sketch of the shapes and looks of the wing.
|
||||
|
||||
* Pattern
|
||||
|
||||
This part describes the sewing patterns.
|
|
@ -1,204 +0,0 @@
|
|||
"""
|
||||
To build, execute
|
||||
```
|
||||
python3 nhf/touhou/houjuu_nue/__init__.py
|
||||
```
|
||||
|
||||
This cosplay consists of 3 components:
|
||||
|
||||
## Trident
|
||||
|
||||
The trident is composed of individual segments, made of acrylic, and a 3D
|
||||
printed head (convention rule prohibits metal) with a metallic paint. To ease
|
||||
transportation, the trident handle has individual segments with threads and can
|
||||
be assembled on site.
|
||||
|
||||
## Snake
|
||||
|
||||
A 3D printed snake with a soft material so it can wrap around and bend
|
||||
|
||||
## Wings
|
||||
|
||||
This is the crux of the cosplay and the most complex component. The wings mount
|
||||
on a wearable harness. Each wing consists of 4 segments with 3 joints. Parts of
|
||||
the wing which demands transluscency are created from 1/16" acrylic panels.
|
||||
These panels serve double duty as the exoskeleton.
|
||||
|
||||
The wings are labeled r1,r2,r3,l1,l2,l3. The segments of the wings are labeled
|
||||
from root to tip s0 (root),
|
||||
s1, s2, s3. The joints are named (from root to tip)
|
||||
shoulder, elbow, wrist in analogy with human anatomy.
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
import cadquery as Cq
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
import nhf.touhou.houjuu_nue.wing as MW
|
||||
import nhf.touhou.houjuu_nue.trident as MT
|
||||
import nhf.touhou.houjuu_nue.joints as MJ
|
||||
import nhf.touhou.houjuu_nue.harness as MH
|
||||
import nhf.touhou.houjuu_nue.electronics as ME
|
||||
from nhf.parts.item import Item
|
||||
import nhf.utils
|
||||
|
||||
WING_DEFLECT_ODD = 0.0
|
||||
WING_DEFLECT_EVEN = 25.0
|
||||
@dataclass
|
||||
class Parameters(Model):
|
||||
"""
|
||||
Defines dimensions for the Houjuu Nue cosplay
|
||||
"""
|
||||
|
||||
harness: MH.Harness = field(default_factory=lambda: MH.Harness())
|
||||
|
||||
wing_r1: MW.WingR = field(default_factory=lambda: MW.WingR(
|
||||
name="r1",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(0,1,1,1),
|
||||
parent_substrate_cull_edges=(0,0,1,0),
|
||||
),
|
||||
shoulder_angle_bias=WING_DEFLECT_ODD,
|
||||
s0_top_hole=False,
|
||||
s0_bot_hole=True,
|
||||
arrow_height=350.0
|
||||
))
|
||||
wing_r2: MW.WingR = field(default_factory=lambda: MW.WingR(
|
||||
name="r2",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,1,1,1),
|
||||
parent_substrate_cull_edges=(0,0,1,0),
|
||||
),
|
||||
electronic_board=ME.ElectronicBoardControl(),
|
||||
shoulder_angle_bias=WING_DEFLECT_EVEN,
|
||||
s0_top_hole=True,
|
||||
s0_bot_hole=True,
|
||||
))
|
||||
wing_r3: MW.WingR = field(default_factory=lambda: MW.WingR(
|
||||
name="r3",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,1,1,0),
|
||||
parent_substrate_cull_edges=(0,0,1,0),
|
||||
),
|
||||
shoulder_angle_bias=WING_DEFLECT_ODD,
|
||||
s0_top_hole=True,
|
||||
s0_bot_hole=False,
|
||||
))
|
||||
wing_l1: MW.WingL = field(default_factory=lambda: MW.WingL(
|
||||
name="l1",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,0,1,1),
|
||||
parent_substrate_cull_edges=(1,0,0,0),
|
||||
),
|
||||
shoulder_angle_bias=WING_DEFLECT_EVEN,
|
||||
wrist_angle=-60.0,
|
||||
s0_top_hole=False,
|
||||
s0_bot_hole=True,
|
||||
))
|
||||
wing_l2: MW.WingL = field(default_factory=lambda: MW.WingL(
|
||||
name="l2",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,1,1,1),
|
||||
parent_substrate_cull_edges=(1,0,0,0),
|
||||
),
|
||||
wrist_angle=-30.0,
|
||||
shoulder_angle_bias=WING_DEFLECT_ODD,
|
||||
s0_top_hole=True,
|
||||
s0_bot_hole=True,
|
||||
))
|
||||
wing_l3: MW.WingL = field(default_factory=lambda: MW.WingL(
|
||||
name="l3",
|
||||
root_joint=MJ.RootJoint(
|
||||
parent_substrate_cull_corners=(1,1,0,1),
|
||||
parent_substrate_cull_edges=(1,0,0,0),
|
||||
),
|
||||
shoulder_angle_bias=WING_DEFLECT_EVEN,
|
||||
wrist_angle=-0.0,
|
||||
s0_top_hole=True,
|
||||
s0_bot_hole=False,
|
||||
))
|
||||
|
||||
trident: MT.Trident = field(default_factory=lambda: MT.Trident())
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name="houjuu-nue")
|
||||
|
||||
@submodel(name="harness")
|
||||
def submodel_harness(self) -> Model:
|
||||
return self.harness
|
||||
|
||||
@submodel(name="wing-r1")
|
||||
def submodel_wing_r1(self) -> Model:
|
||||
return self.wing_r1
|
||||
@submodel(name="wing-r2")
|
||||
def submodel_wing_r2(self) -> Model:
|
||||
return self.wing_r2
|
||||
@submodel(name="wing-r3")
|
||||
def submodel_wing_r3(self) -> Model:
|
||||
return self.wing_r3
|
||||
@submodel(name="wing-l1")
|
||||
def submodel_wing_l1(self) -> Model:
|
||||
return self.wing_l1
|
||||
@submodel(name="wing-l2")
|
||||
def submodel_wing_l2(self) -> Model:
|
||||
return self.wing_l2
|
||||
@submodel(name="wing-l3")
|
||||
def submodel_wing_l3(self) -> Model:
|
||||
return self.wing_l3
|
||||
|
||||
@assembly()
|
||||
def wings_harness_assembly(self,
|
||||
parts: Optional[list[str]] = None,
|
||||
**kwargs) -> Cq.Assembly:
|
||||
"""
|
||||
Assembly of harness with all the wings
|
||||
"""
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(self.harness.assembly(), name="harness", loc=Cq.Location((0, 0, 0)))
|
||||
.add(self.wing_r1.assembly(parts, root_offset=9, **kwargs), name="wing_r1")
|
||||
.add(self.wing_r2.assembly(parts, root_offset=7, **kwargs), name="wing_r2")
|
||||
.add(self.wing_r3.assembly(parts, root_offset=6, **kwargs), name="wing_r3")
|
||||
.add(self.wing_l1.assembly(parts, root_offset=19, **kwargs), name="wing_l1")
|
||||
.add(self.wing_l2.assembly(parts, root_offset=20, **kwargs), name="wing_l2")
|
||||
.add(self.wing_l3.assembly(parts, root_offset=21, **kwargs), name="wing_l3")
|
||||
)
|
||||
for tag in ["r1", "r2", "r3", "l1", "l2", "l3"]:
|
||||
self.harness.add_root_joint_constraint(
|
||||
result,
|
||||
"harness/base",
|
||||
f"wing_{tag}/root",
|
||||
tag
|
||||
)
|
||||
return result.solve()
|
||||
|
||||
@submodel(name="trident")
|
||||
def submodel_trident(self) -> Model:
|
||||
return self.trident
|
||||
|
||||
def stat(self) -> dict[str, float]:
|
||||
a = self.wings_harness_assembly()
|
||||
bbox = a.toCompound().BoundingBox()
|
||||
return {
|
||||
"wing-span": bbox.xlen,
|
||||
"wing-depth": bbox.ylen,
|
||||
"wing-height": bbox.zlen,
|
||||
"wing-mass": a.total_mass(),
|
||||
"wing-centre-of-mass": a.centre_of_mass().toTuple(),
|
||||
"items": Item.count(a),
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
p = Parameters()
|
||||
if len(sys.argv) == 1:
|
||||
p.build_all()
|
||||
sys.exit(0)
|
||||
|
||||
if sys.argv[1] == 'stat':
|
||||
print(p.stat())
|
||||
elif sys.argv[1] == 'model':
|
||||
file_name = sys.argv[2]
|
||||
a = p.wings_harness_assembly()
|
||||
a.save(file_name, exportType='STEP')
|
|
@ -1,18 +0,0 @@
|
|||
from nhf.parts.fasteners import FlatHeadBolt, HexNut, ThreaddedKnob
|
||||
|
||||
NUT_COMMON = HexNut(
|
||||
# FIXME: measure
|
||||
mass=0.0,
|
||||
diam_thread=4.0,
|
||||
pitch=0.7,
|
||||
thickness=3.2,
|
||||
width=7.0,
|
||||
)
|
||||
BOLT_COMMON = FlatHeadBolt(
|
||||
# FIXME: measure
|
||||
mass=0.0,
|
||||
diam_head=8.0,
|
||||
height_head=2.0,
|
||||
diam_thread=4.0,
|
||||
height_thread=20.0,
|
||||
)
|
|
@ -1,68 +0,0 @@
|
|||
#include <FastLED.h>
|
||||
|
||||
// Main LED strip setup
|
||||
#define LED_PIN 5
|
||||
#define NUM_LEDS 100
|
||||
#define LED_PART 50
|
||||
#define BRIGHTNESS 250
|
||||
#define LED_TYPE WS2811
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
CRGB color_red;
|
||||
CRGB color_blue;
|
||||
CRGB color_green;
|
||||
|
||||
#define DIAG_PIN 6
|
||||
|
||||
|
||||
void setup() {
|
||||
// Calculate colors
|
||||
hsv2rgb_spectrum(CHSV(4, 255, 100), color_red);
|
||||
hsv2rgb_spectrum(CHSV(170, 255, 100), color_blue);
|
||||
hsv2rgb_spectrum(CHSV(90, 255, 100), color_green);
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
pinMode(DIAG_PIN, OUTPUT);
|
||||
|
||||
// Main LED strip
|
||||
FastLED.addLeds<LED_TYPE, LED_PIN, RGB>(leds, NUM_LEDS);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
fill_segmented(CRGB::Green, CRGB::Orange);
|
||||
delay(500);
|
||||
|
||||
flash(leds, NUM_LEDS, color_red, 10, 20);
|
||||
delay(500);
|
||||
flash(leds, NUM_LEDS, color_blue, 10, 20);
|
||||
delay(500);
|
||||
}
|
||||
|
||||
void fill_segmented(CRGB c1, CRGB c2)
|
||||
{
|
||||
//fill_solid(leds, LED_PART, c1);
|
||||
fill_gradient_RGB(leds, LED_PART, CRGB::Black ,c1);
|
||||
fill_gradient_RGB(leds + LED_PART, NUM_LEDS - LED_PART, CRGB::Black, c2);
|
||||
FastLED.show();
|
||||
}
|
||||
void flash(CRGB *ptr, uint16_t num, CRGB const& lead, int steps, int step_time)
|
||||
{
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
|
||||
//fill_solid(leds, NUM_LEDS, CRGB::Black);
|
||||
for (int i = 0; i < steps; ++i)
|
||||
{
|
||||
uint8_t factor = 255 * i / steps;
|
||||
analogWrite(DIAG_PIN, factor);
|
||||
CRGB tail = blend(lead, CRGB::Black, factor);
|
||||
uint16_t front = factor * (int) num / 255;
|
||||
fill_solid(ptr, front, tail);
|
||||
//fill_gradient_RGB(ptr, front, tail, lead);
|
||||
//fill_solid(leds + front, NUM_LEDS - front, CRGB::Black);
|
||||
FastLED.show();
|
||||
delay(step_time);
|
||||
}
|
||||
fill_gradient_RGB(ptr, num, CRGB::Black, lead);
|
||||
FastLED.show();
|
||||
analogWrite(DIAG_PIN, LOW);
|
||||
}
|
|
@ -1,540 +0,0 @@
|
|||
"""
|
||||
Electronic components
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Tuple
|
||||
import math
|
||||
import cadquery as Cq
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
from nhf.materials import Role, Material
|
||||
from nhf.parts.box import MountingBox, Hole
|
||||
from nhf.parts.fibre import tension_fibre
|
||||
from nhf.parts.item import Item
|
||||
from nhf.parts.fasteners import FlatHeadBolt, HexNut
|
||||
from nhf.parts.electronics import ArduinoUnoR3, BatteryBox18650
|
||||
from nhf.touhou.houjuu_nue.common import NUT_COMMON, BOLT_COMMON
|
||||
import nhf.utils
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LinearActuator(Item):
|
||||
stroke_length: float
|
||||
shaft_diam: float = 9.04
|
||||
|
||||
front_hole_ext: float = 4.41
|
||||
front_hole_diam: float = 4.41
|
||||
front_length: float = 9.55
|
||||
front_width: float = 9.24
|
||||
front_height: float = 5.98
|
||||
|
||||
segment1_length: float = 37.54
|
||||
segment1_width: float = 15.95
|
||||
segment1_height: float = 11.94
|
||||
|
||||
segment2_length: float = 37.37
|
||||
segment2_width: float = 20.03
|
||||
segment2_height: float = 15.03
|
||||
|
||||
back_hole_ext: float = 4.58
|
||||
back_hole_diam: float = 4.18
|
||||
back_length: float = 9.27
|
||||
back_width: float = 10.16
|
||||
back_height: float = 8.12
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return f"LinearActuator {self.stroke_length}mm"
|
||||
|
||||
@property
|
||||
def role(self) -> Role:
|
||||
return Role.MOTION
|
||||
|
||||
@property
|
||||
def conn_length(self):
|
||||
return self.segment1_length + self.segment2_length + self.front_hole_ext + self.back_hole_ext
|
||||
|
||||
def generate(self, pos: float=0) -> Cq.Assembly:
|
||||
assert -1e-6 <= pos <= 1 + 1e-6, f"Illegal position: {pos}"
|
||||
stroke_x = pos * self.stroke_length
|
||||
front = (
|
||||
Cq.Workplane('XZ')
|
||||
.cylinder(
|
||||
radius=self.front_width / 2,
|
||||
height=self.front_height,
|
||||
centered=True,
|
||||
)
|
||||
.box(
|
||||
length=self.front_hole_ext,
|
||||
width=self.front_width,
|
||||
height=self.front_height,
|
||||
combine=True,
|
||||
centered=(False, True, True)
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XZ'))
|
||||
.cylinder(
|
||||
radius=self.front_hole_diam / 2,
|
||||
height=self.front_height,
|
||||
centered=True,
|
||||
combine='cut',
|
||||
)
|
||||
)
|
||||
front.copyWorkplane(Cq.Workplane('XZ')).tagPlane('conn')
|
||||
if stroke_x > 0:
|
||||
shaft = (
|
||||
Cq.Workplane('YZ')
|
||||
.cylinder(
|
||||
radius=self.shaft_diam / 2,
|
||||
height=stroke_x,
|
||||
centered=(True, True, False)
|
||||
)
|
||||
)
|
||||
else:
|
||||
shaft = None
|
||||
segment1 = (
|
||||
Cq.Workplane()
|
||||
.box(
|
||||
length=self.segment1_length,
|
||||
height=self.segment1_width,
|
||||
width=self.segment1_height,
|
||||
centered=(False, True, True),
|
||||
)
|
||||
)
|
||||
segment2 = (
|
||||
Cq.Workplane()
|
||||
.box(
|
||||
length=self.segment2_length,
|
||||
height=self.segment2_width,
|
||||
width=self.segment2_height,
|
||||
centered=(False, True, True),
|
||||
)
|
||||
)
|
||||
back = (
|
||||
Cq.Workplane('XZ')
|
||||
.cylinder(
|
||||
radius=self.back_width / 2,
|
||||
height=self.back_height,
|
||||
centered=True,
|
||||
)
|
||||
.box(
|
||||
length=self.back_hole_ext,
|
||||
width=self.back_width,
|
||||
height=self.back_height,
|
||||
combine=True,
|
||||
centered=(False, True, True)
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XZ'))
|
||||
.cylinder(
|
||||
radius=self.back_hole_diam / 2,
|
||||
height=self.back_height,
|
||||
centered=True,
|
||||
combine='cut',
|
||||
)
|
||||
)
|
||||
back.faces(">X").tag("dir")
|
||||
back.copyWorkplane(Cq.Workplane('XZ')).tagPlane('conn')
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.add(front, name="front",
|
||||
loc=Cq.Location((-self.front_hole_ext, 0, 0)))
|
||||
.add(segment1, name="segment1",
|
||||
loc=Cq.Location((stroke_x, 0, 0)))
|
||||
.add(segment2, name="segment2",
|
||||
loc=Cq.Location((stroke_x + self.segment1_length, 0, 0)))
|
||||
.add(back, name="back",
|
||||
loc=Cq.Location((stroke_x + self.segment1_length + self.segment2_length + self.back_hole_ext, 0, 0), (0, 1, 0), 180))
|
||||
)
|
||||
if shaft:
|
||||
result.add(shaft, name="shaft")
|
||||
return result
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MountingBracket(Item):
|
||||
"""
|
||||
Mounting bracket for a linear actuator
|
||||
"""
|
||||
mass: float = 1.6
|
||||
hole_diam: float = 4.0
|
||||
width: float = 8.0
|
||||
height: float = 12.20
|
||||
thickness: float = 0.98
|
||||
length: float = 13.00
|
||||
hole_to_side_ext: float = 8.25
|
||||
|
||||
def __post_init__(self):
|
||||
assert self.hole_to_side_ext - self.hole_diam / 2 > 0
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return f"MountingBracket M{int(self.hole_diam)}"
|
||||
|
||||
@property
|
||||
def role(self) -> Role:
|
||||
return Role.MOTION
|
||||
|
||||
def generate(self) -> Cq.Workplane:
|
||||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.box(
|
||||
length=self.hole_to_side_ext,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
centered=(False, True, True)
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XY'))
|
||||
.cylinder(
|
||||
height=self.height,
|
||||
radius=self.width / 2,
|
||||
combine=True,
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XY'))
|
||||
.box(
|
||||
length=2 * (self.hole_to_side_ext - self.thickness),
|
||||
width=self.width,
|
||||
height=self.height - self.thickness * 2,
|
||||
combine='cut',
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('XY'))
|
||||
.cylinder(
|
||||
height=self.height,
|
||||
radius=self.hole_diam / 2,
|
||||
combine='cut'
|
||||
)
|
||||
.copyWorkplane(Cq.Workplane('YZ'))
|
||||
.cylinder(
|
||||
height=self.hole_to_side_ext * 2,
|
||||
radius=self.hole_diam / 2,
|
||||
combine='cut'
|
||||
)
|
||||
)
|
||||
result.copyWorkplane(Cq.Workplane('YZ', origin=(self.hole_to_side_ext, 0, 0))).tagPlane("conn_side")
|
||||
result.copyWorkplane(Cq.Workplane('XY', origin=(0, 0, self.height/2))).tagPlane("conn_top")
|
||||
result.copyWorkplane(Cq.Workplane('YX', origin=(0, 0, -self.height/2))).tagPlane("conn_bot")
|
||||
result.copyWorkplane(Cq.Workplane('XY')).tagPlane("conn_mid")
|
||||
return result
|
||||
|
||||
|
||||
LINEAR_ACTUATOR_50 = LinearActuator(
|
||||
mass=40.8,
|
||||
stroke_length=50,
|
||||
shaft_diam=9.05,
|
||||
front_hole_ext=4.32,
|
||||
back_hole_ext=4.54,
|
||||
segment1_length=57.35,
|
||||
segment1_width=15.97,
|
||||
segment1_height=11.95,
|
||||
segment2_length=37.69,
|
||||
segment2_width=19.97,
|
||||
segment2_height=14.96,
|
||||
|
||||
front_length=9.40,
|
||||
front_width=9.17,
|
||||
front_height=6.12,
|
||||
back_length=9.18,
|
||||
back_width=10.07,
|
||||
back_height=8.06,
|
||||
)
|
||||
LINEAR_ACTUATOR_30 = LinearActuator(
|
||||
mass=34.0,
|
||||
stroke_length=30,
|
||||
)
|
||||
LINEAR_ACTUATOR_21 = LinearActuator(
|
||||
# FIXME: Measure
|
||||
mass=0.0,
|
||||
stroke_length=21,
|
||||
front_hole_ext=4,
|
||||
back_hole_ext=4,
|
||||
segment1_length=34,
|
||||
segment2_length=34,
|
||||
)
|
||||
LINEAR_ACTUATOR_10 = LinearActuator(
|
||||
mass=41.3,
|
||||
stroke_length=10,
|
||||
front_hole_ext=4.02,
|
||||
back_hole_ext=4.67,
|
||||
segment1_length=13.29,
|
||||
segment1_width=15.88,
|
||||
segment1_height=12.07,
|
||||
segment2_length=42.52,
|
||||
segment2_width=20.98,
|
||||
segment2_height=14.84,
|
||||
)
|
||||
LINEAR_ACTUATOR_HEX_NUT = HexNut(
|
||||
mass=0.8,
|
||||
diam_thread=4,
|
||||
pitch=0.7,
|
||||
thickness=4.16,
|
||||
width=6.79,
|
||||
)
|
||||
LINEAR_ACTUATOR_BOLT = FlatHeadBolt(
|
||||
mass=1.7,
|
||||
diam_head=6.68,
|
||||
height_head=2.98,
|
||||
diam_thread=4.0,
|
||||
height_thread=15.83,
|
||||
)
|
||||
LINEAR_ACTUATOR_BRACKET = MountingBracket()
|
||||
|
||||
BATTERY_BOX = BatteryBox18650()
|
||||
|
||||
# Acrylic hex nut
|
||||
ELECTRONIC_MOUNT_HEXNUT = HexNut(
|
||||
mass=0.8,
|
||||
diam_thread=4,
|
||||
pitch=0.7,
|
||||
thickness=3.57,
|
||||
width=6.81,
|
||||
)
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Winch:
|
||||
linear_motion_span: float
|
||||
|
||||
actuator: LinearActuator = LINEAR_ACTUATOR_21
|
||||
nut: HexNut = LINEAR_ACTUATOR_HEX_NUT
|
||||
bolt: FlatHeadBolt = LINEAR_ACTUATOR_BOLT
|
||||
bracket: MountingBracket = LINEAR_ACTUATOR_BRACKET
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Flexor:
|
||||
"""
|
||||
Actuator assembly which flexes, similar to biceps
|
||||
"""
|
||||
motion_span: float
|
||||
arm_radius: Optional[float] = None
|
||||
pos_smaller: bool = True
|
||||
|
||||
actuator: LinearActuator = LINEAR_ACTUATOR_50
|
||||
nut: HexNut = LINEAR_ACTUATOR_HEX_NUT
|
||||
bolt: FlatHeadBolt = LINEAR_ACTUATOR_BOLT
|
||||
bracket: MountingBracket = LINEAR_ACTUATOR_BRACKET
|
||||
# Length of line attached to the flexor
|
||||
line_length: float = 0.0
|
||||
line_thickness: float = 0.5
|
||||
# By how much is the line permitted to slack. This reduces the effective stroke length
|
||||
line_slack: float = 0.0
|
||||
|
||||
def __post_init__(self):
|
||||
assert self.line_slack <= self.line_length, f"Insufficient length: {self.line_slack} >= {self.line_length}"
|
||||
assert self.line_slack < self.actuator.stroke_length
|
||||
|
||||
@property
|
||||
def mount_height(self):
|
||||
return self.bracket.hole_to_side_ext
|
||||
|
||||
@property
|
||||
def d_open(self):
|
||||
return self.actuator.conn_length + self.actuator.stroke_length + self.line_length - self.line_slack
|
||||
@property
|
||||
def d_closed(self):
|
||||
return self.actuator.conn_length + self.line_length
|
||||
|
||||
def open_pos(self) -> Tuple[float, float, float]:
|
||||
r, phi, r_ = nhf.geometry.contraction_span_pos_from_radius(
|
||||
d_open=self.d_open,
|
||||
d_closed=self.d_closed,
|
||||
theta=math.radians(self.motion_span),
|
||||
r=self.arm_radius,
|
||||
smaller=self.pos_smaller,
|
||||
)
|
||||
return r, math.degrees(phi), r_
|
||||
|
||||
def target_length_at_angle(
|
||||
self,
|
||||
angle: float = 0.0
|
||||
) -> float:
|
||||
"""
|
||||
Length of the actuator at some angle
|
||||
"""
|
||||
assert 0 <= angle <= self.motion_span
|
||||
r, phi, rp = self.open_pos()
|
||||
th = math.radians(phi - angle)
|
||||
|
||||
result = math.sqrt(r * r + rp * rp - 2 * r * rp * math.cos(th))
|
||||
#result = math.sqrt((r * math.cos(th) - rp) ** 2 + (r * math.sin(th)) ** 2)
|
||||
assert self.d_closed -1e-6 <= result <= self.d_open + 1e-6,\
|
||||
f"Illegal length: {result} not in [{self.d_closed}, {self.d_open}]"
|
||||
return result
|
||||
|
||||
|
||||
def add_to(
|
||||
self,
|
||||
a: Cq.Assembly,
|
||||
target_length: float,
|
||||
tag_prefix: Optional[str] = None,
|
||||
tag_hole_front: Optional[str] = None,
|
||||
tag_hole_back: Optional[str] = None,
|
||||
tag_dir: Optional[str] = None):
|
||||
"""
|
||||
Adds the necessary mechanical components to this assembly. Does not
|
||||
invoke `a.solve()`.
|
||||
"""
|
||||
draft = max(0, target_length - self.d_closed - self.line_length)
|
||||
pos = draft / self.actuator.stroke_length
|
||||
line_l = target_length - draft - self.actuator.conn_length
|
||||
if tag_prefix:
|
||||
tag_prefix = tag_prefix + "_"
|
||||
else:
|
||||
tag_prefix = ""
|
||||
name_actuator = f"{tag_prefix}actuator"
|
||||
name_bracket_front = f"{tag_prefix}bracket_front"
|
||||
name_bracket_back = f"{tag_prefix}bracket_back"
|
||||
name_bolt_front = f"{tag_prefix}front_bolt"
|
||||
name_bolt_back = f"{tag_prefix}back_bolt"
|
||||
name_nut_front = f"{tag_prefix}front_nut"
|
||||
name_nut_back = f"{tag_prefix}back_nut"
|
||||
(
|
||||
a
|
||||
.add(self.actuator.assembly(pos=pos), name=name_actuator)
|
||||
.add(self.bracket.assembly(), name=name_bracket_front)
|
||||
.add(self.bolt.assembly(), name=name_bolt_front)
|
||||
.add(self.nut.assembly(), name=name_nut_front)
|
||||
.constrain(f"{name_bolt_front}?root", f"{name_bracket_front}?conn_top",
|
||||
"Plane", param=0)
|
||||
.constrain(f"{name_nut_front}?bot", f"{name_bracket_front}?conn_bot",
|
||||
"Plane")
|
||||
.add(self.bracket.assembly(), name=name_bracket_back)
|
||||
.add(self.bolt.assembly(), name=name_bolt_back)
|
||||
.add(self.nut.assembly(), name=name_nut_back)
|
||||
.constrain(f"{name_actuator}/back?conn", f"{name_bracket_back}?conn_mid",
|
||||
"Plane", param=0)
|
||||
.constrain(f"{name_bolt_back}?root", f"{name_bracket_back}?conn_top",
|
||||
"Plane", param=0)
|
||||
.constrain(f"{name_nut_back}?bot", f"{name_bracket_back}?conn_bot",
|
||||
"Plane")
|
||||
)
|
||||
if self.line_length == 0.0:
|
||||
a.constrain(
|
||||
f"{name_actuator}/front?conn",
|
||||
f"{name_bracket_front}?conn_mid",
|
||||
"Plane", param=0)
|
||||
else:
|
||||
(
|
||||
a
|
||||
.addS(tension_fibre(
|
||||
length=line_l,
|
||||
hole_diam=self.nut.diam_thread,
|
||||
thickness=self.line_thickness,
|
||||
), name="fibre", role=Role.CONNECTION)
|
||||
.constrain(
|
||||
f"{name_actuator}/front?conn",
|
||||
"fibre?male",
|
||||
"Plane"
|
||||
)
|
||||
.constrain(
|
||||
f"{name_bracket_front}?conn_mid",
|
||||
"fibre?female",
|
||||
"Plane"
|
||||
)
|
||||
)
|
||||
if tag_hole_front:
|
||||
a.constrain(tag_hole_front, f"{name_bracket_front}?conn_side", "Plane")
|
||||
if tag_hole_back:
|
||||
a.constrain(tag_hole_back, f"{name_bracket_back}?conn_side", "Plane")
|
||||
if tag_dir:
|
||||
a.constrain(tag_dir, f"{name_bracket_front}?conn_mid", "Axis", param=0)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ElectronicBoard(Model):
|
||||
|
||||
name: str = "electronic-board"
|
||||
nut: HexNut = NUT_COMMON
|
||||
bolt: FlatHeadBolt = BOLT_COMMON
|
||||
length: float = 70.0
|
||||
width: float = 170.0
|
||||
mount_holes: list[Hole] = field(default_factory=lambda: [
|
||||
Hole(x=25, y=75),
|
||||
Hole(x=25, y=-75),
|
||||
Hole(x=-25, y=75),
|
||||
Hole(x=-25, y=-75),
|
||||
])
|
||||
panel_thickness: float = 25.4 / 16
|
||||
mount_panel_thickness: float = 25.4 / 4
|
||||
material: Material = Material.WOOD_BIRCH
|
||||
|
||||
@property
|
||||
def mount_hole_diam(self) -> float:
|
||||
return self.bolt.diam_thread
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name=self.name)
|
||||
|
||||
def panel(self) -> MountingBox:
|
||||
return MountingBox(
|
||||
holes=self.mount_holes,
|
||||
hole_diam=self.mount_hole_diam,
|
||||
length=self.length,
|
||||
width=self.width,
|
||||
centred=(True, True),
|
||||
thickness=self.panel_thickness,
|
||||
generate_reverse_tags=True,
|
||||
)
|
||||
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
panel = self.panel()
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.addS(panel.generate(), name="panel",
|
||||
role=Role.ELECTRONIC | Role.STRUCTURE, material=self.material)
|
||||
)
|
||||
for hole in self.mount_holes:
|
||||
bolt_name = f"{hole.tag}_bolt"
|
||||
(
|
||||
result
|
||||
.add(self.bolt.assembly(), name=bolt_name)
|
||||
.constrain(
|
||||
f"{bolt_name}?root",
|
||||
f"panel?{hole.tag}",
|
||||
"Plane", param=0
|
||||
)
|
||||
)
|
||||
return result.solve()
|
||||
|
||||
@dataclass
|
||||
class ElectronicBoardBattery(ElectronicBoard):
|
||||
name: str = "electronic-board-battery"
|
||||
battery_box: BatteryBox18650 = BATTERY_BOX
|
||||
|
||||
@submodel(name="panel")
|
||||
def panel_out(self) -> MountingBox:
|
||||
return self.panel()
|
||||
|
||||
@dataclass
|
||||
class ElectronicBoardControl(ElectronicBoard):
|
||||
name: str = "electronic-board-control"
|
||||
|
||||
controller_datum: Cq.Location = Cq.Location.from2d(-25, 23, -90)
|
||||
|
||||
controller: ArduinoUnoR3 = ArduinoUnoR3()
|
||||
|
||||
def panel(self) -> MountingBox:
|
||||
box = super().panel()
|
||||
def transform(i, x, y):
|
||||
pos = self.controller_datum * Cq.Location.from2d(x, self.controller.width - y)
|
||||
x, y = pos.to2d_pos()
|
||||
return Hole(
|
||||
x=x, y=y,
|
||||
diam=self.controller.hole_diam,
|
||||
tag=f"controller_conn{i}",
|
||||
)
|
||||
box.holes = box.holes.copy() + [
|
||||
transform(i, x, y)
|
||||
for i, (x, y) in enumerate(self.controller.holes)
|
||||
]
|
||||
return box
|
||||
|
||||
@submodel(name="panel")
|
||||
def panel_out(self) -> MountingBox:
|
||||
return self.panel()
|
||||
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
result = super().assembly()
|
||||
result.add(self.controller.assembly(), name="controller")
|
||||
for i in range(len(self.controller.holes)):
|
||||
result.constrain(f"controller?conn{i}", f"panel?controller_conn{i}", "Plane")
|
||||
return result.solve()
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LightStrip:
|
||||
|
||||
width: float = 10.0
|
||||
height: float = 4.5
|
|
@ -1,204 +0,0 @@
|
|||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
from nhf.parts.joints import HirthJoint
|
||||
from nhf import Material, Role
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
from nhf.touhou.houjuu_nue.joints import RootJoint
|
||||
from nhf.parts.box import MountingBox
|
||||
import nhf.utils
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class Mannequin:
|
||||
"""
|
||||
A mannequin for calibration
|
||||
"""
|
||||
|
||||
shoulder_width: float = 400
|
||||
shoulder_to_waist: float = 440
|
||||
waist_width: float = 250
|
||||
head_height: float = 220.0
|
||||
neck_height: float = 105.0
|
||||
neck_diam: float = 140
|
||||
head_diam: float = 210
|
||||
torso_thickness: float = 150
|
||||
|
||||
def generate(self) -> Cq.Workplane:
|
||||
head_neck = (
|
||||
Cq.Workplane("XY")
|
||||
.cylinder(
|
||||
radius=self.neck_diam/2,
|
||||
height=self.neck_height,
|
||||
centered=(True, True, False))
|
||||
.faces(">Z")
|
||||
.workplane()
|
||||
.cylinder(
|
||||
radius=self.head_diam/2,
|
||||
height=self.head_height,
|
||||
combine=True, centered=(True, True, False))
|
||||
)
|
||||
result = (
|
||||
Cq.Workplane("XY")
|
||||
.rect(self.waist_width, self.torso_thickness)
|
||||
.workplane(offset=self.shoulder_to_waist)
|
||||
.rect(self.shoulder_width, self.torso_thickness)
|
||||
.loft(combine=True)
|
||||
.union(head_neck.translate((0, 0, self.shoulder_to_waist)))
|
||||
)
|
||||
return result.translate((0, self.torso_thickness / 2, 0))
|
||||
|
||||
|
||||
BASE_POS_X = 70.0
|
||||
BASE_POS_Y = 100.0
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Harness(Model):
|
||||
thickness: float = 25.4 / 8
|
||||
width: float = 200.0
|
||||
height: float = 304.8
|
||||
fillet: float = 10.0
|
||||
|
||||
wing_base_pos: list[tuple[str, float, float]] = field(default_factory=lambda: [
|
||||
("r1", BASE_POS_X, BASE_POS_Y),
|
||||
("l1", -BASE_POS_X, BASE_POS_Y),
|
||||
("r2", BASE_POS_X, 0),
|
||||
("l2", -BASE_POS_X, 0),
|
||||
("r3", BASE_POS_X, -BASE_POS_Y),
|
||||
("l3", -BASE_POS_X, -BASE_POS_Y),
|
||||
])
|
||||
|
||||
root_joint: RootJoint = field(default_factory=lambda: RootJoint())
|
||||
|
||||
mannequin: Mannequin = Mannequin()
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name="harness")
|
||||
|
||||
@submodel(name="bridge-pair-horizontal")
|
||||
def bridge_pair_horizontal(self) -> MountingBox:
|
||||
return self.root_joint.bridge_pair_horizontal(centre_dx=BASE_POS_X * 2)
|
||||
@submodel(name="bridge-pair-vertical")
|
||||
def bridge_pair_vertical(self) -> MountingBox:
|
||||
return self.root_joint.bridge_pair_vertical(centre_dy=BASE_POS_Y)
|
||||
|
||||
@target(name="profile", kind=TargetKind.DXF)
|
||||
def profile(self) -> Cq.Sketch:
|
||||
"""
|
||||
Creates the harness shape
|
||||
"""
|
||||
w, h = self.width / 2, self.height / 2
|
||||
sketch = (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(w, h),
|
||||
(w, -h),
|
||||
(-w, -h),
|
||||
(-w, h),
|
||||
#(0.7 * w, h),
|
||||
#(w, 0),
|
||||
#(0.7 * w, -h),
|
||||
#(0.7 * -w, -h),
|
||||
#(-w, 0),
|
||||
#(0.7 * -w, h),
|
||||
])
|
||||
#.rect(self.harness_width, self.harness_height)
|
||||
.vertices()
|
||||
.fillet(self.fillet)
|
||||
)
|
||||
for tag, x, y in self.wing_base_pos:
|
||||
conn = [(px + x, py + y) for px, py in self.root_joint.corner_pos()]
|
||||
sketch = (
|
||||
sketch
|
||||
.push(conn)
|
||||
.tag(tag)
|
||||
.circle(self.root_joint.corner_hole_diam / 2, mode='s')
|
||||
.reset()
|
||||
)
|
||||
return sketch
|
||||
|
||||
def surface(self) -> Cq.Workplane:
|
||||
"""
|
||||
Creates the harness shape
|
||||
"""
|
||||
result = (
|
||||
Cq.Workplane('XZ')
|
||||
.placeSketch(self.profile())
|
||||
.extrude(self.thickness)
|
||||
)
|
||||
result.faces(">Y").tag("mount")
|
||||
plane = result.faces(">Y").workplane()
|
||||
for tag, x, y in self.wing_base_pos:
|
||||
conn = [(px + x, py + y) for px, py
|
||||
in self.root_joint.corner_pos()]
|
||||
for i, (px, py) in enumerate(conn):
|
||||
plane.moveTo(px, py).tagPlane(f"{tag}_{i}")
|
||||
return result
|
||||
|
||||
def add_root_joint_constraint(
|
||||
self,
|
||||
a: Cq.Assembly,
|
||||
harness_tag: str,
|
||||
joint_tag: str,
|
||||
mount_tag: str):
|
||||
for i in range(4):
|
||||
a.constrain(f"{harness_tag}?{mount_tag}_{i}", f"{joint_tag}/parent?h{i}", "Point")
|
||||
|
||||
|
||||
@assembly()
|
||||
def assembly(self, with_root_joint: bool = False) -> Cq.Assembly:
|
||||
harness = self.surface()
|
||||
mannequin_z = self.mannequin.shoulder_to_waist * 0.6
|
||||
|
||||
result = (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
harness, name="base",
|
||||
material=Material.WOOD_BIRCH,
|
||||
role=Role.STRUCTURE)
|
||||
.constrain("base", "Fixed")
|
||||
.addS(
|
||||
self.mannequin.generate(),
|
||||
name="mannequin",
|
||||
role=Role.FIXTURE,
|
||||
loc=Cq.Location((0, -self.thickness, -mannequin_z), (0, 0, 1), 180))
|
||||
.constrain("mannequin", "Fixed")
|
||||
)
|
||||
bridge_h = self.bridge_pair_horizontal().generate()
|
||||
for i in [1,2,3]:
|
||||
name = f"r{i}l{i}_bridge"
|
||||
(
|
||||
result
|
||||
.addS(
|
||||
bridge_h, name=name,
|
||||
role=Role.FIXTURE,
|
||||
material=Material.WOOD_BIRCH,
|
||||
)
|
||||
.constrain(f"{name}?conn0_rev", f"base?r{i}_1", "Point")
|
||||
.constrain(f"{name}?conn1_rev", f"base?l{i}_0", "Point")
|
||||
.constrain(f"{name}?conn2_rev", f"base?l{i}_3", "Point")
|
||||
.constrain(f"{name}?conn3_rev", f"base?r{i}_2", "Point")
|
||||
)
|
||||
bridge_v = self.bridge_pair_vertical().generate()
|
||||
(
|
||||
result
|
||||
.addS(bridge_v, name="r1_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH)
|
||||
.constrain("r1_bridge?conn0_rev", "base?r1_3", 'Plane')
|
||||
.constrain("r1_bridge?conn1_rev", "base?r2_0", 'Plane')
|
||||
.addS(bridge_v, name="r2_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH)
|
||||
.constrain("r2_bridge?conn0_rev", "base?r2_3", 'Plane')
|
||||
.constrain("r2_bridge?conn1_rev", "base?r3_0", 'Plane')
|
||||
.addS(bridge_v, name="l1_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH)
|
||||
.constrain("l1_bridge?conn0_rev", "base?l1_2", 'Plane')
|
||||
.constrain("l1_bridge?conn1_rev", "base?l2_1", 'Plane')
|
||||
.addS(bridge_v, name="l2_bridge", role=Role.FIXTURE, material=Material.WOOD_BIRCH)
|
||||
.constrain("l2_bridge?conn0_rev", "base?l2_2", 'Plane')
|
||||
.constrain("l2_bridge?conn1_rev", "base?l3_1", 'Plane')
|
||||
)
|
||||
if with_root_joint:
|
||||
for name in ["l1", "l2", "l3", "r1", "r2", "r3"]:
|
||||
result.addS(
|
||||
self.root_joint.assembly(), name=name,
|
||||
role=Role.PARENT,
|
||||
material=Material.PLASTIC_PLA)
|
||||
self.add_root_joint_constraint(result, "base", name, name)
|
||||
result.solve()
|
||||
return result
|
File diff suppressed because it is too large
Load Diff
|
@ -1,130 +0,0 @@
|
|||
import unittest
|
||||
import cadquery as Cq
|
||||
import nhf.touhou.houjuu_nue as M
|
||||
import nhf.touhou.houjuu_nue.joints as MJ
|
||||
import nhf.touhou.houjuu_nue.electronics as ME
|
||||
from nhf.checks import pairwise_intersection
|
||||
|
||||
class TestElectronics(unittest.TestCase):
|
||||
|
||||
def test_actuator_length(self):
|
||||
self.assertAlmostEqual(
|
||||
ME.LINEAR_ACTUATOR_50.conn_length, 103.9
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
ME.LINEAR_ACTUATOR_30.conn_length, 83.9
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
ME.LINEAR_ACTUATOR_10.conn_length, 64.5
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
ME.LINEAR_ACTUATOR_21.conn_length, 76.0
|
||||
)
|
||||
|
||||
def test_flexor(self):
|
||||
flexor = ME.Flexor(
|
||||
motion_span=60,
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
flexor.target_length_at_angle(0),
|
||||
flexor.actuator.stroke_length + flexor.actuator.conn_length)
|
||||
self.assertAlmostEqual(
|
||||
flexor.target_length_at_angle(flexor.motion_span),
|
||||
flexor.actuator.conn_length)
|
||||
|
||||
|
||||
class TestJoints(unittest.TestCase):
|
||||
|
||||
def test_shoulder_collision_of_torsion_joint(self):
|
||||
j = MJ.ShoulderJoint()
|
||||
assembly = j.torsion_joint.rider_track_assembly()
|
||||
self.assertEqual(pairwise_intersection(assembly), [])
|
||||
|
||||
def test_shoulder_collision_0(self):
|
||||
j = MJ.ShoulderJoint()
|
||||
assembly = j.assembly()
|
||||
self.assertEqual(pairwise_intersection(assembly), [])
|
||||
|
||||
def test_shoulder_align(self):
|
||||
j = MJ.ShoulderJoint()
|
||||
a = j.assembly()
|
||||
l_t_c0 = a.get_abs_location("parent_top/lip?conn0")
|
||||
l_b_c0 = a.get_abs_location("parent_bot/lip?conn0")
|
||||
v = l_t_c0 - l_b_c0
|
||||
self.assertAlmostEqual(v.x, 0)
|
||||
self.assertAlmostEqual(v.y, 0)
|
||||
|
||||
def test_shoulder_joint_dist(self):
|
||||
"""
|
||||
Tests the arm radius
|
||||
"""
|
||||
j = MJ.ShoulderJoint()
|
||||
for deflection in [0, 40, j.angle_max_deflection]:
|
||||
with self.subTest(deflection=deflection):
|
||||
a = j.assembly(deflection=deflection)
|
||||
# Axle
|
||||
o = a.get_abs_location("parent_top/track?spring")
|
||||
l_c1 = a.get_abs_location("parent_top/lip?conn0")
|
||||
l_c2= a.get_abs_location("parent_top/lip?conn1")
|
||||
v_c = 0.5 * ((l_c1 - o) + (l_c2 - o))
|
||||
v_c.z = 0
|
||||
self.assertAlmostEqual(v_c.Length, j.parent_lip_ext)
|
||||
|
||||
def test_disk_collision_0(self):
|
||||
j = MJ.DiskJoint()
|
||||
assembly = j.assembly(angle=0)
|
||||
self.assertEqual(pairwise_intersection(assembly), [])
|
||||
def test_disk_collision_mid(self):
|
||||
j = MJ.DiskJoint()
|
||||
assembly = j.assembly(angle=j.movement_angle / 2)
|
||||
self.assertEqual(pairwise_intersection(assembly), [])
|
||||
def test_disk_collision_max(self):
|
||||
j = MJ.DiskJoint()
|
||||
assembly = j.assembly(angle=j.movement_angle)
|
||||
self.assertEqual(pairwise_intersection(assembly), [])
|
||||
|
||||
def test_elbow_joint_dist(self):
|
||||
"""
|
||||
Tests the arm radius
|
||||
"""
|
||||
j = MJ.ElbowJoint()
|
||||
for angle in [0, 10, 20, j.disk_joint.movement_angle]:
|
||||
with self.subTest(angle=angle):
|
||||
a = j.assembly(angle=angle)
|
||||
o = a.get_abs_location("child/disk?mate_bot")
|
||||
l_c1 = a.get_abs_location("child/lip?conn_top0")
|
||||
l_c2 = a.get_abs_location("child/lip?conn_bot0")
|
||||
v_c = 0.5 * ((l_c1 - o) + (l_c2 - o))
|
||||
v_c.z = 0
|
||||
self.assertAlmostEqual(v_c.Length, j.child_arm_radius)
|
||||
|
||||
l_p1 = a.get_abs_location("parent_upper/lip?conn_top0")
|
||||
l_p2 = a.get_abs_location("parent_upper/lip?conn_bot0")
|
||||
v_p = 0.5 * ((l_p1 - o) + (l_p2 - o))
|
||||
v_p.z = 0
|
||||
self.assertAlmostEqual(v_p.Length, j.parent_arm_radius)
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def test_hs_joint_parent(self):
|
||||
p = M.Parameters()
|
||||
obj = p.harness.hs_joint_parent()
|
||||
self.assertIsInstance(obj.val().solids(), Cq.Solid, msg="H-S joint must be in one piece")
|
||||
|
||||
def test_wings_assembly(self):
|
||||
p = M.Parameters()
|
||||
p.wings_harness_assembly()
|
||||
def test_trident_assembly(self):
|
||||
p = M.Parameters()
|
||||
assembly = p.trident.assembly()
|
||||
bbox = assembly.toCompound().BoundingBox()
|
||||
length = bbox.zlen
|
||||
self.assertGreater(length, 1300)
|
||||
self.assertLess(length, 1700)
|
||||
#def test_assemblies(self):
|
||||
# p = M.Parameters()
|
||||
# p.check_all()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -1,88 +0,0 @@
|
|||
import math
|
||||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
from nhf import Material, Role
|
||||
from nhf.parts.handle import Handle, BayonetMount
|
||||
from nhf.build import Model, target, assembly
|
||||
import nhf.utils
|
||||
|
||||
@dataclass
|
||||
class Trident(Model):
|
||||
handle: Handle = field(default_factory=lambda: Handle(
|
||||
diam=38,
|
||||
diam_inner=38-2 * 25.4/8,
|
||||
diam_connector_internal=18,
|
||||
simplify_geometry=False,
|
||||
mount=BayonetMount(n_pin=3),
|
||||
))
|
||||
terminal_height: float = 80
|
||||
terminal_hole_diam: float = 24
|
||||
terminal_bottom_thickness: float = 10
|
||||
segment_length: float = 24 * 25.4
|
||||
|
||||
@target(name="handle-connector")
|
||||
def handle_connector(self):
|
||||
return self.handle.connector()
|
||||
@target(name="handle-insertion")
|
||||
def handle_insertion(self):
|
||||
return self.handle.insertion()
|
||||
@target(name="proto-handle-terminal-connector", prototype=True)
|
||||
def proto_handle_connector(self):
|
||||
return self.handle.one_side_connector(height=15)
|
||||
|
||||
@target(name="handle-terminal-connector")
|
||||
def handle_terminal_connector(self):
|
||||
result = self.handle.one_side_connector(height=self.terminal_height)
|
||||
#result.faces("<Z").circle(radius=25/2).cutThruAll()
|
||||
h = self.terminal_height + self.handle.insertion_length - self.terminal_bottom_thickness
|
||||
result = result.faces(">Z").hole(self.terminal_hole_diam, depth=h)
|
||||
return result
|
||||
|
||||
@assembly()
|
||||
def assembly(self):
|
||||
def segment():
|
||||
return self.handle.segment(self.segment_length)
|
||||
|
||||
terminal = (
|
||||
self.handle
|
||||
.one_side_connector(height=self.terminal_height)
|
||||
.faces(">Z")
|
||||
.hole(15, self.terminal_height + self.handle.insertion_length - 10)
|
||||
)
|
||||
mat_c = Material.PLASTIC_PLA
|
||||
mat_i = Material.RESIN_TOUGH_1500
|
||||
mat_s = Material.ACRYLIC_BLACK
|
||||
role_i = Role.CONNECTION
|
||||
role_c = Role.CONNECTION
|
||||
role_s = Role.STRUCTURE
|
||||
a = (
|
||||
Cq.Assembly()
|
||||
.addS(self.handle.insertion(), name="i0",
|
||||
material=mat_i, role=role_i)
|
||||
.constrain("i0", "Fixed")
|
||||
.addS(segment(), name="s1",
|
||||
material=mat_s, role=role_s)
|
||||
.constrain("i0?rim", "s1?mate1", "Plane", param=0)
|
||||
.addS(self.handle.insertion(), name="i1",
|
||||
material=mat_i, role=role_i)
|
||||
.addS(self.handle.connector(), name="c1",
|
||||
material=mat_c, role=role_c)
|
||||
.addS(self.handle.insertion(), name="i2",
|
||||
material=mat_i, role=role_i)
|
||||
.constrain("s1?mate2", "i1?rim", "Plane", param=0)
|
||||
.constrain("i1?mate", "c1?mate1", "Plane")
|
||||
.constrain("i2?mate", "c1?mate2", "Plane")
|
||||
.addS(segment(), name="s2",
|
||||
material=mat_s, role=role_s)
|
||||
.constrain("i2?rim", "s2?mate1", "Plane", param=0)
|
||||
.addS(self.handle.insertion(), name="i3",
|
||||
material=mat_i, role=role_i)
|
||||
.constrain("s2?mate2", "i3?rim", "Plane", param=0)
|
||||
.addS(self.handle.one_side_connector(), name="head",
|
||||
material=mat_c, role=role_c)
|
||||
.constrain("i3?mate", "head?mate", "Plane")
|
||||
.addS(terminal, name="terminal",
|
||||
material=mat_c, role=role_c)
|
||||
.constrain("i0?mate", "terminal?mate", "Plane")
|
||||
)
|
||||
return a.solve()
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,40 @@
|
|||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
import nhf.touhou.shiki_eiki.rod as MR
|
||||
import nhf.touhou.shiki_eiki.crown as MC
|
||||
import nhf.touhou.shiki_eiki.epaulette as ME
|
||||
import nhf.utils
|
||||
|
||||
@dataclass
|
||||
class Parameters(Model):
|
||||
|
||||
rod: MR.Rod = field(default_factory=lambda: MR.Rod())
|
||||
crown: MC.Crown = field(default_factory=lambda: MC.Crown())
|
||||
epaulette_ze: ME.Epaulette = field(default_factory=lambda: ME.Epaulette(side="ze"))
|
||||
epaulette_hi: ME.Epaulette = field(default_factory=lambda: ME.Epaulette(side="hi"))
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name="shiki-eiki")
|
||||
|
||||
@submodel(name="rod")
|
||||
def submodel_rod(self) -> Model:
|
||||
return self.rod
|
||||
@submodel(name="crown")
|
||||
def submodel_crown(self) -> Model:
|
||||
return self.crown
|
||||
@submodel(name="epaulette_ze")
|
||||
def submodel_epaulette_ze(self) -> Model:
|
||||
return self.epaulette_ze
|
||||
@submodel(name="epaulette_hi")
|
||||
def submodel_epaulette_hi(self) -> Model:
|
||||
return self.epaulette_hi
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
p = Parameters()
|
||||
if len(sys.argv) == 1:
|
||||
p.build_all()
|
||||
sys.exit(0)
|
|
@ -0,0 +1,758 @@
|
|||
from nhf import Material, Role
|
||||
from nhf.build import Model, target, assembly, TargetKind
|
||||
import nhf.utils
|
||||
|
||||
import math
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
import cadquery as Cq
|
||||
|
||||
class AttachPoint(Enum):
|
||||
DOVETAIL_IN = 1
|
||||
DOVETAIL_OUT = 2
|
||||
NONE = 3
|
||||
# Inset slot for front surface attachment j
|
||||
SLOT = 4
|
||||
|
||||
@dataclass
|
||||
class Crown(Model):
|
||||
|
||||
facets: int = 5
|
||||
# Lower circumference
|
||||
base_circ: float = 538.0
|
||||
# Upper circumference, at the middle
|
||||
tilt_circ: float = 640.0
|
||||
front_base_circ: float = (640.0 + 538.0) / 2
|
||||
# Total height
|
||||
height: float = 120.0
|
||||
|
||||
# Front guard has a wing that inserts into the side guards.
|
||||
front_wing_angle: float = 9.0
|
||||
front_wing_dh: float = 40.0
|
||||
front_wing_height: float = 20.0
|
||||
|
||||
margin: float = 10.0
|
||||
|
||||
thickness: float = 0.4 # 26 Gauge
|
||||
side_guard_thickness: float = 15.0
|
||||
side_guard_channel_radius: float = 90
|
||||
side_guard_channel_height: float = 10
|
||||
side_guard_hole_height: float = 15.0
|
||||
side_guard_hole_diam: float = 1.5
|
||||
side_guard_dovetail_height: float = 30.0
|
||||
|
||||
side_guard_slot_width: float = 22.0
|
||||
side_guard_slot_angle: float = 18.0
|
||||
# brass insert thickness
|
||||
slot_thickness: float = 2.0
|
||||
slot_width: float = 20.0
|
||||
slot_tilt: float = 60
|
||||
|
||||
material: Material = Material.METAL_BRASS
|
||||
material_side: Material = Material.PLASTIC_PLA
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name="crown")
|
||||
|
||||
assert self.tilt_circ > self.base_circ
|
||||
assert self.facet_width_upper / 2 > self.height / 2, "Top angle must be > 90 degrees"
|
||||
assert self.side_guard_channel_radius > self.radius_lower
|
||||
|
||||
assert self.front_wing_angle < 180 / self.facets
|
||||
assert self.front_wing_dh + self.front_wing_height < self.height
|
||||
assert self.slot_phi < 2 * math.pi / self.facets
|
||||
|
||||
@property
|
||||
def facet_width_lower(self):
|
||||
return self.base_circ / self.facets
|
||||
@property
|
||||
def facet_width_upper(self):
|
||||
return self.tilt_circ / self.facets
|
||||
@property
|
||||
def radius_lower(self):
|
||||
return self.base_circ / (2 * math.pi)
|
||||
@property
|
||||
def radius_middle(self):
|
||||
return self.tilt_circ / (2 * math.pi)
|
||||
@property
|
||||
def radius_upper(self):
|
||||
return (self.tilt_circ + (self.tilt_circ - self.base_circ)) / (2 * math.pi)
|
||||
|
||||
@property
|
||||
def radius_lower_front(self):
|
||||
return self.front_base_circ / (2 * math.pi)
|
||||
@property
|
||||
def radius_middle_front(self):
|
||||
return self.radius_lower_front + (self.radius_middle - self.radius_lower)
|
||||
@property
|
||||
def radius_upper_front(self):
|
||||
return self.radius_lower_front + (self.radius_upper - self.radius_lower)
|
||||
|
||||
@property
|
||||
def slot_r0(self):
|
||||
return self.radius_lower + self.thickness / 2
|
||||
@property
|
||||
def slot_r1(self):
|
||||
return self.radius_upper + self.thickness / 2
|
||||
|
||||
@property
|
||||
def slot_h0(self) -> float:
|
||||
"""
|
||||
Phantom height formed by similar triangle, i.e. h0 in
|
||||
|
||||
(h0 + h) / r2 = h0 / r1
|
||||
"""
|
||||
rat = self.slot_r0 / (self.slot_r1 - self.slot_r0)
|
||||
return self.height * rat
|
||||
@property
|
||||
def slot_outer_h0(self):
|
||||
rat = (self.slot_r0 + self.side_guard_thickness) / (self.slot_r1 - self.slot_r0)
|
||||
return self.height * rat
|
||||
@property
|
||||
def slot_theta(self) -> float:
|
||||
"""
|
||||
Cone tilt, related to other quantities by
|
||||
h0 = r1 * cot theta
|
||||
"""
|
||||
h = self.height
|
||||
return math.atan(self.slot_r0 / (self.height + self.slot_h0))
|
||||
@property
|
||||
def slot_phi(self) -> float:
|
||||
"""
|
||||
When a slice of the crown is expanded (via Gauss's Theorema Egregium),
|
||||
it does not form a full circle. phi is the angle of one of the slices.
|
||||
|
||||
Note that on the cone itself, the angular slice is `2 pi / n` which `n`
|
||||
is the number of sides.
|
||||
"""
|
||||
arc = self.slot_r0 * math.pi * 2 / self.facets
|
||||
rho = self.slot_h0 / math.cos(self.slot_theta)
|
||||
return arc / rho
|
||||
|
||||
|
||||
def profile_base(self) -> Cq.Sketch:
|
||||
# Generate a conical pentagonal shape
|
||||
|
||||
y0 = self.slot_h0 / math.cos(self.slot_theta)
|
||||
yh = (self.height/2 + self.slot_h0) / math.cos(self.slot_theta)
|
||||
yq = (self.height*3/4 + self.slot_h0) / math.cos(self.slot_theta)
|
||||
y1 = (self.height + self.slot_h0) / math.cos(self.slot_theta)
|
||||
phi2 = self.slot_phi / 2
|
||||
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
(y0 * math.sin(phi2), y0 * (-1 + math.cos(phi2))),
|
||||
(yh * math.sin(phi2), -y0 + yh * math.cos(phi2)),
|
||||
)
|
||||
.arc(
|
||||
(yh * math.sin(phi2), -y0 + yh * math.cos(phi2)),
|
||||
(yq * math.sin(phi2/2), -y0 + yq * math.cos(phi2/2)),
|
||||
(0, y1 - y0),
|
||||
)
|
||||
.arc(
|
||||
(-yh * math.sin(phi2), -y0 + yh * math.cos(phi2)),
|
||||
(-yq * math.sin(phi2/2), -y0 + yq * math.cos(phi2/2)),
|
||||
(0, y1 - y0),
|
||||
)
|
||||
.segment(
|
||||
(-y0 * math.sin(phi2), y0 * (-1 + math.cos(phi2))),
|
||||
(-yh * math.sin(phi2), -y0 + yh * math.cos(phi2)),
|
||||
)
|
||||
.arc(
|
||||
(y0 * math.sin(phi2), -y0 + y0 * math.cos(phi2)),
|
||||
(0, 0),
|
||||
(-y0 * math.sin(phi2), y0 * (-1 + math.cos(phi2))),
|
||||
)
|
||||
.assemble()
|
||||
)
|
||||
|
||||
@target(name="eye", kind=TargetKind.DXF)
|
||||
def profile_eye(self) -> Cq.Sketch:
|
||||
"""
|
||||
deprecated
|
||||
"""
|
||||
dy = self.facet_width_upper * 0.1
|
||||
y_tip = self.height - self.margin
|
||||
|
||||
eye = (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
(0, y_tip),
|
||||
(dy, y_tip - dy),
|
||||
)
|
||||
.segment(
|
||||
(0, y_tip),
|
||||
(-dy, y_tip - dy),
|
||||
)
|
||||
.bezier([
|
||||
(dy, y_tip - dy),
|
||||
(dy/2, y_tip - dy*.6),
|
||||
(dy/4, y_tip - dy/2),
|
||||
(0, y_tip - dy/2),
|
||||
])
|
||||
.bezier([
|
||||
(0, y_tip - dy/2),
|
||||
(-dy/4, y_tip - dy/2),
|
||||
(-dy/2, y_tip - dy*.6),
|
||||
(-dy, y_tip - dy),
|
||||
])
|
||||
.assemble()
|
||||
)
|
||||
return eye
|
||||
|
||||
@target(name="dot", kind=TargetKind.DXF)
|
||||
def profile_dot(self) -> Cq.Sketch:
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.circle(self.margin / 2)
|
||||
)
|
||||
|
||||
def profile_front_wing(self, mirror: bool) -> Cq.Sketch:
|
||||
"""
|
||||
These two wings help the front profile attach
|
||||
"""
|
||||
hw = self.front_wing_height / math.cos(self.slot_theta)
|
||||
hw0 = (self.front_wing_dh + self.slot_h0) / math.cos(self.slot_theta)
|
||||
hw1 = hw0 + hw
|
||||
y0 = self.slot_h0 / math.cos(self.slot_theta)
|
||||
# Calculate angle of wing analogously to `this.slot_phi`. This arc's
|
||||
# radius is hw0.
|
||||
wing_arc = self.slot_r0 * math.radians(self.front_wing_angle)
|
||||
phi_w = wing_arc / hw0
|
||||
sign = -1 if mirror else 1
|
||||
phi2 = self.slot_phi / 2
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
(sign * hw0 * math.sin(phi2), -y0 + hw0 * math.cos(phi2)),
|
||||
(sign * hw1 * math.sin(phi2), -y0 + hw1 * math.cos(phi2)),
|
||||
)
|
||||
.segment(
|
||||
(sign * hw0 * math.sin(phi2+phi_w), -y0 + hw0 * math.cos(phi2+phi_w)),
|
||||
(sign * hw1 * math.sin(phi2+phi_w), -y0 + hw1 * math.cos(phi2+phi_w)),
|
||||
)
|
||||
.arc(
|
||||
(sign * hw0 * math.sin(phi2), -y0 + hw0 * math.cos(phi2)),
|
||||
(sign * hw0 * math.sin(phi2+phi_w/2), -y0 + hw0 * math.cos(phi2+phi_w/2)),
|
||||
(sign * hw0 * math.sin(phi2+phi_w), -y0 + hw0 * math.cos(phi2+phi_w)),
|
||||
)
|
||||
.arc(
|
||||
(sign * hw1 * math.sin(phi2), -y0 + hw1 * math.cos(phi2)),
|
||||
(sign * hw1 * math.sin(phi2+phi_w/2), -y0 + hw1 * math.cos(phi2+phi_w/2)),
|
||||
(sign * hw1 * math.sin(phi2+phi_w), -y0 + hw1 * math.cos(phi2+phi_w)),
|
||||
)
|
||||
.assemble()
|
||||
)
|
||||
|
||||
|
||||
@target(name="front", kind=TargetKind.DXF)
|
||||
def profile_front(self) -> Cq.Sketch:
|
||||
"""
|
||||
Front profile slots into holes on the side guards
|
||||
"""
|
||||
profile_base = (
|
||||
self.profile_base()
|
||||
.boolean(self.profile_front_wing(False), mode='a')
|
||||
.boolean(self.profile_front_wing(True), mode='a')
|
||||
)
|
||||
|
||||
|
||||
dx_l = self.facet_width_lower
|
||||
dx_u = self.facet_width_upper
|
||||
dy = self.height
|
||||
|
||||
window_length = dy / 5
|
||||
window_height = self.margin / 2
|
||||
window = (
|
||||
Cq.Sketch()
|
||||
.rect(window_length, window_height)
|
||||
)
|
||||
window_p1 = Cq.Location.from2d(
|
||||
dx_u/2 - self.margin - window_length * 0.4,
|
||||
dy/2 + self.margin/2,
|
||||
math.degrees(math.atan2(dy/2, -dx_u/2) * 0.95),
|
||||
)
|
||||
window_p2 = Cq.Location.from2d(
|
||||
dx_l/2 - self.margin + window_length * 0.15,
|
||||
window_length/2 + self.margin,
|
||||
math.degrees(math.atan2(dy/2, (dx_u-dx_l)/2)),
|
||||
)
|
||||
|
||||
# Carve the scale
|
||||
z = dy * 1/32 # "Pen" Thickness
|
||||
scale_pan_x = dx_l / 2 * 0.6
|
||||
scale_pan_y = dy / 2 * 0.7
|
||||
pan_dx = dx_l * 1/4
|
||||
pan_dy = dy * 1/16
|
||||
|
||||
scale_pan = (
|
||||
Cq.Sketch()
|
||||
.arc(
|
||||
(- pan_dx/2, pan_dy),
|
||||
(0, 0),
|
||||
(+ pan_dx/2, pan_dy),
|
||||
)
|
||||
.segment(
|
||||
(+pan_dx/2, pan_dy),
|
||||
(+pan_dx/2 - z, pan_dy),
|
||||
)
|
||||
.arc(
|
||||
(-pan_dx/2 + z, pan_dy),
|
||||
(0, z),
|
||||
(+pan_dx/2 - z, pan_dy),
|
||||
)
|
||||
.segment(
|
||||
(-pan_dx/2, pan_dy),
|
||||
(-pan_dx/2 + z, pan_dy),
|
||||
)
|
||||
.assemble()
|
||||
)
|
||||
loc_scale_pan = Cq.Location.from2d(scale_pan_x, scale_pan_y)
|
||||
loc_scale_pan2 = Cq.Location.from2d(-scale_pan_x, scale_pan_y)
|
||||
|
||||
scale_base_y = dy / 2 * 0.36
|
||||
scale_base_x = dx_l / 10
|
||||
assert scale_base_y < scale_pan_y
|
||||
assert scale_base_x < scale_pan_x
|
||||
|
||||
scale_body = (
|
||||
Cq.Sketch()
|
||||
.arc(
|
||||
(scale_pan_x, scale_pan_y),
|
||||
(0, scale_base_y),
|
||||
(-scale_pan_x, scale_pan_y),
|
||||
)
|
||||
.segment(
|
||||
(-scale_pan_x, scale_pan_y),
|
||||
(-scale_pan_x+z, scale_pan_y+z),
|
||||
)
|
||||
.arc(
|
||||
(scale_pan_x - z, scale_pan_y+z),
|
||||
(0, scale_base_y + z),
|
||||
(-scale_pan_x + z, scale_pan_y+z),
|
||||
)
|
||||
.segment(
|
||||
(scale_pan_x, scale_pan_y),
|
||||
(scale_pan_x-z, scale_pan_y+z),
|
||||
)
|
||||
.assemble()
|
||||
.polygon([
|
||||
(scale_base_x, scale_base_y + z/2),
|
||||
(scale_base_x, self.margin),
|
||||
(scale_base_x-z, self.margin),
|
||||
(scale_base_x-z, scale_base_y-z),
|
||||
|
||||
(-scale_base_x+z, scale_base_y-z),
|
||||
(-scale_base_x+z, self.margin),
|
||||
(-scale_base_x, self.margin),
|
||||
(-scale_base_x, scale_base_y + z/2),
|
||||
], mode='a')
|
||||
)
|
||||
|
||||
# Needle
|
||||
needle_y_top = dy - self.margin
|
||||
needle_y_mid = dy * 0.7
|
||||
needle_dx = scale_base_x * 2
|
||||
y_shoulder = needle_y_mid - z * 2
|
||||
needle = (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
(0, needle_y_mid),
|
||||
(z, y_shoulder),
|
||||
)
|
||||
.segment(
|
||||
(z, y_shoulder),
|
||||
(z, scale_base_y),
|
||||
)
|
||||
.segment(
|
||||
(z, scale_base_y),
|
||||
(-z, scale_base_y),
|
||||
)
|
||||
.segment(
|
||||
(-z, y_shoulder),
|
||||
(-z, scale_base_y),
|
||||
)
|
||||
.segment(
|
||||
(-z, y_shoulder),
|
||||
(0, needle_y_mid),
|
||||
)
|
||||
.assemble()
|
||||
)
|
||||
z2 = z * 2
|
||||
y1 = needle_y_mid + z2
|
||||
needle_head = (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
(z, needle_y_mid),
|
||||
(z, y1),
|
||||
)
|
||||
.segment(
|
||||
(-z, needle_y_mid),
|
||||
(-z, y1),
|
||||
)
|
||||
# Outer edge
|
||||
.bezier([
|
||||
(0, needle_y_top),
|
||||
(0, (needle_y_top + needle_y_mid)/2),
|
||||
(needle_dx, (needle_y_top + needle_y_mid)/2),
|
||||
(z, needle_y_mid),
|
||||
])
|
||||
.bezier([
|
||||
(0, needle_y_top),
|
||||
(0, (needle_y_top + needle_y_mid)/2),
|
||||
(-needle_dx, (needle_y_top + needle_y_mid)/2),
|
||||
(-z, needle_y_mid),
|
||||
])
|
||||
# Inner edge
|
||||
.bezier([
|
||||
(0, needle_y_top - z2),
|
||||
(0, (needle_y_top + needle_y_mid)/2),
|
||||
(needle_dx-z2*2, (needle_y_top + needle_y_mid)/2),
|
||||
(z, y1),
|
||||
])
|
||||
.bezier([
|
||||
(0, needle_y_top - z2),
|
||||
(0, (needle_y_top + needle_y_mid)/2),
|
||||
(-needle_dx+z2*2, (needle_y_top + needle_y_mid)/2),
|
||||
(-z, y1),
|
||||
])
|
||||
.assemble()
|
||||
)
|
||||
|
||||
return (
|
||||
profile_base
|
||||
.boolean(window.moved(window_p1), mode='s')
|
||||
.boolean(window.moved(window_p1.flip_x()), mode='s')
|
||||
.boolean(window.moved(window_p2), mode='s')
|
||||
.boolean(window.moved(window_p2.flip_x()), mode='s')
|
||||
.boolean(scale_pan.moved(loc_scale_pan), mode='s')
|
||||
.boolean(scale_pan.moved(loc_scale_pan2), mode='s')
|
||||
.boolean(scale_body, mode='s')
|
||||
.boolean(needle, mode='s')
|
||||
.boolean(needle_head, mode='s')
|
||||
.clean()
|
||||
)
|
||||
|
||||
@target(name="side-guard", kind=TargetKind.DXF)
|
||||
def profile_side_guard(self) -> Cq.Sketch:
|
||||
dx = self.facet_width_lower / 2
|
||||
dy = self.height
|
||||
|
||||
# Main control points
|
||||
p_mid = Cq.Location.from2d(0, 0.5 * dy)
|
||||
p_mid_v = Cq.Location.from2d(10/57 * dx, 0)
|
||||
p_top1 = Cq.Location.from2d(0.408 * dx, 5/24 * dy)
|
||||
p_top1_v = Cq.Location.from2d(0.13 * dx, 0)
|
||||
p_top2 = Cq.Location.from2d(0.737 * dx, 0.255 * dy)
|
||||
p_top2_c1 = p_top2 * Cq.Location.from2d(-0.105 * dx, 0.033 * dy)
|
||||
p_top2_c2 = p_top2 * Cq.Location.from2d(-0.053 * dx, -0.09 * dy)
|
||||
p_top3 = Cq.Location.from2d(0.929 * dx, 0.145 * dy)
|
||||
p_top3_v = Cq.Location.from2d(0.066 * dx, 0.033 * dy)
|
||||
p_top4 = Cq.Location.from2d(0.85 * dx, 0.374 * dy)
|
||||
p_top4_v = Cq.Location.from2d(-0.053 * dx, 0.008 * dy)
|
||||
p_top5 = Cq.Location.from2d(0.54 * dx, 0.349 * dy)
|
||||
p_top5_c1 = p_top5 * Cq.Location.from2d(0.103 * dx, 0.017 * dy)
|
||||
p_top5_c2 = p_top5 * Cq.Location.from2d(0.158 * dx, 0.034 * dy)
|
||||
p_base_c = Cq.Location.from2d(1.5 * dx, 0.55 * dy)
|
||||
|
||||
y0 = self.slot_outer_h0 / math.cos(self.slot_theta)
|
||||
phi2 = self.slot_phi / 2
|
||||
p_base = Cq.Location.from2d(y0 * math.sin(phi2), -y0 + y0 * math.cos(phi2))
|
||||
|
||||
bezier_groups = [
|
||||
[
|
||||
p_base,
|
||||
p_base_c,
|
||||
p_top5_c2,
|
||||
p_top5,
|
||||
],
|
||||
[
|
||||
p_top5,
|
||||
p_top5_c1,
|
||||
p_top4 * p_top4_v,
|
||||
p_top4,
|
||||
],
|
||||
[
|
||||
p_top4,
|
||||
p_top4 * p_top4_v.inverse.scale(4),
|
||||
p_top3 * p_top3_v,
|
||||
p_top3,
|
||||
],
|
||||
[
|
||||
p_top3,
|
||||
p_top3 * p_top3_v.inverse,
|
||||
p_top2_c2,
|
||||
p_top2,
|
||||
],
|
||||
[
|
||||
p_top2,
|
||||
p_top2_c1,
|
||||
p_top1 * p_top1_v,
|
||||
p_top1,
|
||||
],
|
||||
[
|
||||
p_top1,
|
||||
p_top1 * p_top1_v.inverse,
|
||||
p_mid * p_mid_v,
|
||||
p_mid,
|
||||
],
|
||||
]
|
||||
sketch = (
|
||||
Cq.Sketch()
|
||||
.arc(
|
||||
p_base.to2d_pos(),
|
||||
(0, 0),
|
||||
p_base.flip_x().to2d_pos(),
|
||||
)
|
||||
)
|
||||
for bezier_group in bezier_groups:
|
||||
sketch = (
|
||||
sketch
|
||||
.bezier([p.to2d_pos() for p in bezier_group])
|
||||
.bezier([p.flip_x().to2d_pos() for p in bezier_group])
|
||||
)
|
||||
return sketch.assemble()
|
||||
|
||||
def side_guard_dovetail(self) -> Cq.Solid:
|
||||
"""
|
||||
Generates a dovetail coupling for the side guard
|
||||
"""
|
||||
dx = self.side_guard_thickness / 2
|
||||
wire = Cq.Wire.makePolygon([
|
||||
(dx * 0.5, 0),
|
||||
(dx * 0.7, dx),
|
||||
(-dx * 0.7, dx),
|
||||
(-dx * 0.5, 0),
|
||||
], close=True)
|
||||
return Cq.Solid.extrudeLinear(
|
||||
wire,
|
||||
[],
|
||||
(0,0,dx + self.side_guard_dovetail_height),
|
||||
).moved((0, 0, -dx))
|
||||
|
||||
def side_guard_frontal_slot(self) -> Cq.Workplane:
|
||||
angle = 360 / self.facets
|
||||
inner_d = self.thickness / 2 - self.slot_thickness / 2
|
||||
outer_d = self.thickness / 2 + self.slot_thickness / 2
|
||||
outer = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower_front + outer_d,
|
||||
radius2=self.radius_upper_front + outer_d,
|
||||
height=self.height,
|
||||
angleDegrees=angle,
|
||||
)
|
||||
inner = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower_front + inner_d,
|
||||
radius2=self.radius_upper_front + inner_d,
|
||||
height=self.height,
|
||||
angleDegrees=angle,
|
||||
)
|
||||
shell = (
|
||||
outer.cut(inner)
|
||||
.rotate((0,0,0), (0,0,1), -angle/2)
|
||||
)
|
||||
# Generate the sector intersector
|
||||
intersector = Cq.Solid.makeCylinder(
|
||||
radius=self.radius_upper + self.side_guard_thickness,
|
||||
height=self.front_wing_height,
|
||||
angleDegrees=self.front_wing_angle,
|
||||
).moved(Cq.Location(0,0,self.front_wing_dh,0,0,-self.front_wing_angle/2))
|
||||
return shell * intersector
|
||||
|
||||
def side_guard(
|
||||
self,
|
||||
attach_left: AttachPoint,
|
||||
attach_right: AttachPoint,
|
||||
) -> Cq.Workplane:
|
||||
"""
|
||||
Constructs the side guard using a cone. Via Gauss's Theorema Egregium,
|
||||
the surface of the cone can be deformed into a plane.
|
||||
"""
|
||||
angle_span = 360 / self.facets
|
||||
outer = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower + self.side_guard_thickness,
|
||||
radius2=self.radius_upper + self.side_guard_thickness,
|
||||
height=self.height,
|
||||
angleDegrees=angle_span,
|
||||
)
|
||||
inner = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower,
|
||||
radius2=self.radius_upper,
|
||||
height=self.height,
|
||||
angleDegrees=angle_span,
|
||||
)
|
||||
shell = (outer - inner).rotate((0,0,0), (0,0,1), -angle_span/2)
|
||||
dx = math.sin(math.radians(angle_span / 2)) * (self.radius_middle + self.side_guard_thickness)
|
||||
profile = (
|
||||
Cq.Workplane('YZ')
|
||||
.polyline([
|
||||
(0, self.height),
|
||||
(-dx, self.height / 2),
|
||||
(-dx, 0),
|
||||
(dx, 0),
|
||||
(dx, self.height / 2),
|
||||
])
|
||||
.close()
|
||||
.extrude(self.radius_upper + self.side_guard_thickness)
|
||||
.val()
|
||||
)
|
||||
#channel = (
|
||||
# Cq.Solid.makeCylinder(
|
||||
# radius=self.side_guard_channel_radius + 1.0,
|
||||
# height=self.side_guard_channel_height,
|
||||
# ) - Cq.Solid.makeCylinder(
|
||||
# radius=self.side_guard_channel_radius,
|
||||
# height=self.side_guard_channel_height,
|
||||
# )
|
||||
#)
|
||||
result = shell * profile# - channel
|
||||
|
||||
# Create the downward slots
|
||||
for sign in [-1, 1]:
|
||||
slot_box = Cq.Solid.makeBox(
|
||||
length=self.height,
|
||||
width=self.slot_width,
|
||||
height=self.slot_thickness,
|
||||
).moved(
|
||||
Cq.Location(-self.slot_thickness,-self.slot_width/2, -self.slot_thickness/2)
|
||||
)
|
||||
# keyhole for threads to stay in place
|
||||
slot_cyl = Cq.Solid.makeCylinder(
|
||||
radius=self.slot_thickness/2,
|
||||
height=self.height,
|
||||
pnt=(0,0,self.slot_thickness/2),
|
||||
dir=(1,0,0),
|
||||
)
|
||||
slot = slot_box + slot_cyl
|
||||
slot = slot.moved(
|
||||
Cq.Location.rot2d(sign * self.side_guard_slot_angle) *
|
||||
Cq.Location(self.radius_lower + self.side_guard_thickness/2, 0, 0) *
|
||||
Cq.Location(0,0,0,0,-180 + self.slot_tilt,0)
|
||||
)
|
||||
result = result - slot
|
||||
|
||||
radius_attach = self.radius_lower + self.side_guard_thickness / 2
|
||||
# tilt the dovetail by radius differential
|
||||
angle_tilt = math.degrees(math.atan2(self.radius_middle - self.radius_lower, self.height / 2))
|
||||
dovetail = self.side_guard_dovetail()
|
||||
loc_dovetail_left = Cq.Location.rot2d(angle_span / 2) * Cq.Location(radius_attach, 0, 0, 0, angle_tilt, 0)
|
||||
loc_dovetail_right = Cq.Location.rot2d(-angle_span / 2) * Cq.Location(radius_attach, 0, 0, 0, angle_tilt, 0)
|
||||
|
||||
angle_slot = 180 / self.facets - self.front_wing_angle / 2
|
||||
match attach_left:
|
||||
case AttachPoint.DOVETAIL_IN:
|
||||
loc_dovetail_left *= Cq.Location.rot2d(180)
|
||||
result = result - dovetail.moved(loc_dovetail_left)
|
||||
case AttachPoint.DOVETAIL_OUT:
|
||||
result = result + dovetail.moved(loc_dovetail_left)
|
||||
case AttachPoint.SLOT:
|
||||
result = result - self.side_guard_frontal_slot().moved(Cq.Location.rot2d(angle_slot))
|
||||
case AttachPoint.NONE:
|
||||
pass
|
||||
match attach_right:
|
||||
case AttachPoint.DOVETAIL_IN:
|
||||
result = result - dovetail.moved(loc_dovetail_right)
|
||||
case AttachPoint.DOVETAIL_OUT:
|
||||
loc_dovetail_right *= Cq.Location.rot2d(180)
|
||||
result = result + dovetail.moved(loc_dovetail_right)
|
||||
case AttachPoint.SLOT:
|
||||
result = result - self.side_guard_frontal_slot().moved(Cq.Location.rot2d(-angle_slot))
|
||||
case AttachPoint.NONE:
|
||||
pass
|
||||
# Remove parts below the horizontal
|
||||
cut_h = self.radius_lower
|
||||
result -= Cq.Solid.makeCylinder(
|
||||
radius=self.radius_lower + self.side_guard_thickness,
|
||||
height=cut_h).moved((0,0,-cut_h))
|
||||
return result
|
||||
|
||||
@target(name="side_guard_1", angularTolerance=0.01)
|
||||
def side_guard_1(self) -> Cq.Workplane:
|
||||
return self.side_guard(
|
||||
attach_left=AttachPoint.SLOT,
|
||||
attach_right=AttachPoint.DOVETAIL_IN,
|
||||
)
|
||||
@target(name="side_guard_2", angularTolerance=0.01)
|
||||
def side_guard_2(self) -> Cq.Workplane:
|
||||
return self.side_guard(
|
||||
attach_left=AttachPoint.DOVETAIL_OUT,
|
||||
attach_right=AttachPoint.DOVETAIL_IN,
|
||||
)
|
||||
@target(name="side_guard_3", angularTolerance=0.01)
|
||||
def side_guard_3(self) -> Cq.Workplane:
|
||||
return self.side_guard(
|
||||
attach_left=AttachPoint.DOVETAIL_OUT,
|
||||
attach_right=AttachPoint.DOVETAIL_IN,
|
||||
)
|
||||
@target(name="side_guard_4", angularTolerance=0.01)
|
||||
def side_guard_4(self) -> Cq.Workplane:
|
||||
return self.side_guard(
|
||||
attach_left=AttachPoint.DOVETAIL_OUT,
|
||||
attach_right=AttachPoint.SLOT,
|
||||
)
|
||||
|
||||
def front_surrogate(self) -> Cq.Workplane:
|
||||
"""
|
||||
Create a surrogate cylindrical section structure for the front since we
|
||||
cannot bend extrusions
|
||||
"""
|
||||
angle = 360 / 5
|
||||
outer = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower_front + self.thickness,
|
||||
radius2=self.radius_upper_front + self.thickness,
|
||||
height=self.height,
|
||||
angleDegrees=angle,
|
||||
)
|
||||
inner = Cq.Solid.makeCone(
|
||||
radius1=self.radius_lower_front,
|
||||
radius2=self.radius_upper_front,
|
||||
height=self.height,
|
||||
angleDegrees=angle,
|
||||
)
|
||||
shell = (
|
||||
outer.cut(inner)
|
||||
.rotate((0,0,0), (0,0,1), -angle/2)
|
||||
)
|
||||
dx = math.sin(math.radians(angle / 2)) * self.radius_middle_front
|
||||
profile = (
|
||||
Cq.Workplane('YZ')
|
||||
.polyline([
|
||||
(0, self.height),
|
||||
(-dx, self.height / 2),
|
||||
(-dx, 0),
|
||||
(dx, 0),
|
||||
(dx, self.height / 2),
|
||||
])
|
||||
.close()
|
||||
.extrude(self.radius_upper_front + self.side_guard_thickness)
|
||||
.val()
|
||||
)
|
||||
return shell * profile
|
||||
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
"""
|
||||
New assembly using conformal mapping on the cone.
|
||||
"""
|
||||
side_guards = [
|
||||
self.side_guard_1(),
|
||||
self.side_guard_2(),
|
||||
self.side_guard_3(),
|
||||
self.side_guard_4(),
|
||||
]
|
||||
a = Cq.Assembly()
|
||||
for i,side_guard in enumerate(side_guards):
|
||||
angle = -(i+1) * 360 / self.facets
|
||||
a = a.addS(
|
||||
side_guard,
|
||||
name=f"side-{i}",
|
||||
material=self.material_side,
|
||||
loc=Cq.Location(rz=angle)
|
||||
)
|
||||
a.addS(
|
||||
self.front_surrogate(),
|
||||
name="front",
|
||||
material=self.material,
|
||||
)
|
||||
return a
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
|||
import math
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
import cadquery as Cq
|
||||
from nhf import Material, Role
|
||||
from nhf.build import Model, target, assembly
|
||||
import nhf.utils
|
||||
|
||||
@dataclass
|
||||
class Epaulette(Model):
|
||||
|
||||
side: str
|
||||
diam: float = 100.0
|
||||
thickness_brass: float = 0.4 # 26 Gauge
|
||||
thickness_fabric: float = 0.3
|
||||
material: Material = Material.METAL_BRASS
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name=f"epaulette-{self.side}")
|
||||
|
||||
def surface(self) -> Cq.Solid:
|
||||
path = Path(__file__).resolve().parent / f"epaulette-{self.side}.dxf"
|
||||
return (
|
||||
Cq.importers.importDXF(path).wires().toPending().extrude(self.thickness_brass)
|
||||
)
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
assembly = (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
self.surface(),
|
||||
name="surface",
|
||||
material=self.material,
|
||||
role=Role.DECORATION,
|
||||
)
|
||||
)
|
||||
return assembly
|
|
@ -0,0 +1,587 @@
|
|||
import math
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Tuple
|
||||
import cadquery as Cq
|
||||
from nhf import Material, Role
|
||||
from nhf.build import Model, target, assembly, TargetKind
|
||||
import nhf.utils
|
||||
|
||||
@dataclass
|
||||
class Rod(Model):
|
||||
|
||||
width: float = 120.0
|
||||
length: float = 550.0
|
||||
length_tip: float = 100.0
|
||||
width_tail: float = 60.0
|
||||
margin: float = 10.0
|
||||
|
||||
thickness_top: float = 25.4 / 8
|
||||
# The side which has mounted hinges must be thicker
|
||||
thickness_side: float = 25.4 / 4
|
||||
|
||||
height_internal: float = 30.0
|
||||
|
||||
material_shell: Material = Material.WOOD_BIRCH
|
||||
|
||||
# Considering the glyph on the top ...
|
||||
|
||||
# counted from middle to the bottom
|
||||
fac_bar_top: float = 0.1
|
||||
# counted from bottom to top
|
||||
fac_window_tsumi_bot: float = 0.63
|
||||
fac_window_tsumi_top: float = 0.88
|
||||
|
||||
fac_window_footer_bot: float = 0.36
|
||||
fac_window_footer_top: float = 0.6
|
||||
|
||||
# Considering the side ...
|
||||
hinge_plate_pos: list[float] = field(default_factory=lambda: [0.1, 0.9])
|
||||
hinge_plate_length: float = 30.0
|
||||
hinge_hole_diam: float = 2.5
|
||||
# Hole distance to axis
|
||||
hinge_hole_axis_dist: float = 12.5 / 2
|
||||
# Distance between holes
|
||||
hinge_hole_sep: float = 15.89
|
||||
|
||||
# Consider the reference objects
|
||||
ref_object_width: float = 50.0
|
||||
ref_object_length: float = 50.0
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name="rod")
|
||||
self.loc_core = Cq.Location.from2d(self.length - self.length_tip, 0)
|
||||
assert self.length_tip * 2 < self.length
|
||||
#assert self.fac_bar_top + self.fac_window_tsumi_top < 1
|
||||
assert self.fac_window_tsumi_bot < self.fac_window_tsumi_top
|
||||
|
||||
@property
|
||||
def length_tail(self):
|
||||
return self.length - self.length_tip
|
||||
|
||||
@property
|
||||
def _reduced_tip_x(self):
|
||||
return self.length_tip - self.margin
|
||||
@property
|
||||
def _reduced_y(self):
|
||||
return self.width / 2 - self.margin
|
||||
@property
|
||||
def _reduced_tail_y(self):
|
||||
return self.width_tail / 2 - self.margin
|
||||
|
||||
def profile_points(self) -> list[Tuple[str, Tuple[float, float]]]:
|
||||
"""
|
||||
Points in polygon line order, labaled
|
||||
"""
|
||||
return [
|
||||
("tip", (self.length, 0)),
|
||||
("mid_r", (self.length - self.length_tip, self.width/2)),
|
||||
("bot_r", (0, self.width_tail / 2)),
|
||||
("bot_l", (0, -self.width_tail / 2)),
|
||||
("mid_l", (self.length - self.length_tip, -self.width/2)),
|
||||
]
|
||||
|
||||
def _window_tip(self) -> Cq.Sketch:
|
||||
dxh = self._reduced_tip_x
|
||||
dy = self._reduced_y
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
(dxh, 0),
|
||||
(dxh / 2, dy / 2),
|
||||
)
|
||||
.bezier([
|
||||
(dxh / 2, dy / 2),
|
||||
(dxh * 0.6, dy * 0.4),
|
||||
(dxh * 0.6, -dy * 0.4),
|
||||
(dxh / 2, -dy / 2),
|
||||
])
|
||||
.segment(
|
||||
(dxh, 0),
|
||||
)
|
||||
.assemble()
|
||||
.moved(self.loc_core.to2d_pos())
|
||||
)
|
||||
def _window_eye(self, refl: bool = False) -> Cq.Sketch:
|
||||
|
||||
sign = -1 if refl else 1
|
||||
dxh = self._reduced_tip_x
|
||||
xm = dxh * 0.45
|
||||
dy = sign * self._reduced_y
|
||||
fac = 0.05
|
||||
|
||||
p1 = Cq.Location.from2d(xm, sign * self.margin / 2)
|
||||
p2 = Cq.Location.from2d(dxh * 0.1, sign * self.margin / 2)
|
||||
p3 = Cq.Location.from2d(dxh * 0.15, dy * 0.55)
|
||||
p4 = Cq.Location.from2d(dxh * 0.4, dy * 0.45)
|
||||
d4 = Cq.Location.from2d(dxh * fac, -dy * fac)
|
||||
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
p1.to2d_pos(),
|
||||
p2.to2d_pos(),
|
||||
)
|
||||
.bezier([
|
||||
p2.to2d_pos(),
|
||||
(p2 * Cq.Location.from2d(0, dy * fac)).to2d_pos(),
|
||||
(p3 * Cq.Location.from2d(-dxh * fac, -dy * fac)).to2d_pos(),
|
||||
p3.to2d_pos(),
|
||||
])
|
||||
.bezier([
|
||||
p3.to2d_pos(),
|
||||
(p3 * Cq.Location.from2d(0, dy * fac)).to2d_pos(),
|
||||
(p4 * d4.inverse).to2d_pos(),
|
||||
p4.to2d_pos(),
|
||||
])
|
||||
.bezier([
|
||||
p4.to2d_pos(),
|
||||
(p4 * d4).to2d_pos(),
|
||||
(p1 * Cq.Location.from2d(0, dy * fac)).to2d_pos(),
|
||||
p1.to2d_pos(),
|
||||
])
|
||||
.assemble()
|
||||
.moved(self.loc_core.to2d_pos())
|
||||
)
|
||||
|
||||
def _window_bar(self) -> Cq.Sketch():
|
||||
dxh = self._reduced_tip_x
|
||||
dy = self._reduced_y
|
||||
dyt = self._reduced_tail_y
|
||||
dxt = self.length_tail
|
||||
|
||||
ext_fac = self.fac_bar_top
|
||||
|
||||
p_corner = Cq.Location.from2d(0, dy)
|
||||
p_top = Cq.Location.from2d(0.3 * dxh, 0.7 * dy)
|
||||
p_bot = Cq.Location.from2d(-ext_fac * dxt, dy + ext_fac * (dyt - dy))
|
||||
p_top_int = p_corner * Cq.Location.from2d(.05 * dxh, -.2 * dy)
|
||||
p_top_ctrl = Cq.Location.from2d(0, .3 * dy)
|
||||
p_bot_int = p_corner * Cq.Location.from2d(-.15 * dxh, -.2 * dy)
|
||||
p_bot_ctrl = Cq.Location.from2d(-.25 * dxh, .3 * dy)
|
||||
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.segment(
|
||||
p_corner.to2d_pos(),
|
||||
p_top.to2d_pos(),
|
||||
)
|
||||
.segment(p_top_int.to2d_pos())
|
||||
.bezier([
|
||||
p_top_int.to2d_pos(),
|
||||
p_top_ctrl.to2d_pos(),
|
||||
p_top_ctrl.flip_y().to2d_pos(),
|
||||
p_top_int.flip_y().to2d_pos(),
|
||||
])
|
||||
.segment(p_top.flip_y().to2d_pos())
|
||||
.segment(p_corner.flip_y().to2d_pos())
|
||||
.segment(p_bot.flip_y().to2d_pos())
|
||||
.segment(p_bot_int.flip_y().to2d_pos())
|
||||
.bezier([
|
||||
p_bot_int.flip_y().to2d_pos(),
|
||||
p_bot_ctrl.flip_y().to2d_pos(),
|
||||
p_bot_ctrl.to2d_pos(),
|
||||
p_bot_int.to2d_pos(),
|
||||
])
|
||||
.segment(p_bot.to2d_pos())
|
||||
.segment(p_corner.to2d_pos())
|
||||
.assemble()
|
||||
.moved(self.loc_core.to2d_pos())
|
||||
)
|
||||
|
||||
def _window_tsumi(self) -> Cq.Sketch:
|
||||
dx = (self.fac_window_tsumi_top - self.fac_window_tsumi_bot) * self.length_tail
|
||||
dy = 2 * self._reduced_y * 0.8
|
||||
loc = Cq.Location(self.fac_window_tsumi_bot * self.length_tail, 0)
|
||||
|
||||
# Construction of the top part of the kanji
|
||||
|
||||
dx_top = dx * 0.3
|
||||
x_top = dx - dx_top / 2
|
||||
dy_top = dy
|
||||
dy_eye = dy * 0.2
|
||||
dy_border = (dy_top - 3 * dy_eye) / 4
|
||||
# The skip must follow 3 * eye + 4 * border = dy_top
|
||||
y_skip = dy_eye + dy_border
|
||||
|
||||
# Construction of the bottom part
|
||||
x_bot = dx * 0.65
|
||||
y3 = dy * 0.4
|
||||
y2 = dy * 0.2
|
||||
y1 = dy * 0.1
|
||||
# x/y-centers of the legs
|
||||
x_leg0 = x_bot / 14
|
||||
dx_leg = x_bot / 7
|
||||
y_leg = (y3 + y1) / 2
|
||||
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.push([(x_top, 0)])
|
||||
.rect(dx_top, dy_top)
|
||||
.push([
|
||||
(x_top, -y_skip),
|
||||
(x_top, 0),
|
||||
(x_top, y_skip),
|
||||
])
|
||||
.rect(dx_top / 3, dy_eye, mode='s')
|
||||
|
||||
# Construct the two sides
|
||||
.push([
|
||||
(x_bot / 2, (y2 + y1) / 2),
|
||||
(x_bot / 2, -(y2 + y1) / 2),
|
||||
])
|
||||
.rect(x_bot, y2 - y1, mode='a')
|
||||
.push([
|
||||
(x_leg0 + dx_leg, y_leg),
|
||||
(x_leg0 + 3 * dx_leg, y_leg),
|
||||
(x_leg0 + 5 * dx_leg, y_leg),
|
||||
(x_leg0 + dx_leg, -y_leg),
|
||||
(x_leg0 + 3 * dx_leg, -y_leg),
|
||||
(x_leg0 + 5 * dx_leg, -y_leg),
|
||||
])
|
||||
.rect(dx_leg, y3 - y1, mode='a')
|
||||
|
||||
.moved(loc)
|
||||
)
|
||||
|
||||
def _window_footer(self) -> Cq.Sketch:
|
||||
x_bot = self.fac_window_footer_bot * self.length_tail
|
||||
dx = (self.fac_window_footer_top - self.fac_window_footer_bot) * self.length_tail
|
||||
loc = Cq.Location(x_bot, 0)
|
||||
|
||||
dy = self._reduced_y * 0.8
|
||||
|
||||
# eyes
|
||||
eye_y2 = dy * .5
|
||||
eye_y1 = dy * .2
|
||||
eye_width = eye_y2 - eye_y1
|
||||
eye_x = dx - eye_width / 2
|
||||
|
||||
# bar polygon
|
||||
bar_x0 = dx * 0.65
|
||||
bar_dx = dx * 0.1
|
||||
bar_x1 = bar_x0 + bar_dx
|
||||
bar_x2 = bar_x0 + bar_dx * 2
|
||||
bar_x3 = bar_x0 + bar_dx * 3
|
||||
bar_y1 = dy * .75
|
||||
assert bar_y1 > eye_y2
|
||||
bar_y2 = dy * .9
|
||||
assert bar_y1 < bar_y2
|
||||
|
||||
# Construction of the cross
|
||||
cross_dx = dx * 0.7 / math.sqrt(2)
|
||||
cross_dy = dy * 0.2
|
||||
|
||||
cross = (
|
||||
Cq.Sketch()
|
||||
.rect(cross_dx, cross_dy)
|
||||
.rect(cross_dy, cross_dx, mode='a')
|
||||
.moved(Cq.Location.from2d(dx * 0.5, 0, 45))
|
||||
)
|
||||
return (
|
||||
Cq.Sketch()
|
||||
# eyes
|
||||
.push([
|
||||
(eye_x, (eye_y1 + eye_y2)/2),
|
||||
(eye_x, -(eye_y1 + eye_y2)/2),
|
||||
])
|
||||
.rect(eye_width, eye_width, mode='a')
|
||||
# middle bar
|
||||
.push([(0,0)])
|
||||
.polygon([
|
||||
(bar_x1, bar_y1),
|
||||
(bar_x0, bar_y1),
|
||||
(bar_x0, bar_y2),
|
||||
(bar_x3, bar_y2),
|
||||
(bar_x3, bar_y1),
|
||||
(bar_x2, bar_y1),
|
||||
|
||||
(bar_x2, -bar_y1),
|
||||
(bar_x3, -bar_y1),
|
||||
(bar_x3, -bar_y2),
|
||||
(bar_x0, -bar_y2),
|
||||
(bar_x0, -bar_y1),
|
||||
(bar_x1, -bar_y1),
|
||||
], mode='a')
|
||||
# cross
|
||||
.boolean(cross, mode='a')
|
||||
|
||||
#.push([(0,0)])
|
||||
#.rect(10, 10)
|
||||
|
||||
.moved(loc)
|
||||
)
|
||||
|
||||
@target(name="bottom", kind=TargetKind.DXF)
|
||||
def profile_bottom(self) -> Cq.Sketch:
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.polygon([p for _, p in self.profile_points()])
|
||||
)
|
||||
|
||||
@target(name="top", kind=TargetKind.DXF)
|
||||
def profile_top(self) -> Cq.Sketch:
|
||||
return (
|
||||
self.profile_bottom()
|
||||
.boolean(self._window_tip(), mode='s')
|
||||
.boolean(self._window_eye(True), mode='s')
|
||||
.boolean(self._window_eye(False), mode='s')
|
||||
.boolean(self._window_bar(), mode='s')
|
||||
.boolean(self._window_tsumi(), mode='s')
|
||||
.boolean(self._window_footer(), mode='s')
|
||||
)
|
||||
|
||||
def surface_top(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_top())
|
||||
.extrude(self.thickness_top)
|
||||
)
|
||||
|
||||
def surface_bottom(self) -> Cq.Workplane:
|
||||
surface = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_bottom())
|
||||
.extrude(self.thickness_top)
|
||||
)
|
||||
plane = surface.faces(">Z").workplane()
|
||||
|
||||
for (name, p) in self.profile_points():
|
||||
plane.moveTo(*p).tagPlane(name)
|
||||
|
||||
return surface
|
||||
|
||||
# Properties of the side surfaces
|
||||
|
||||
@property
|
||||
def length_edge_tip(self):
|
||||
return math.sqrt(self.length_tip ** 2 + (self.width / 2) ** 2)
|
||||
@property
|
||||
def length_edge_tail(self):
|
||||
dw = (self.width - self.width_tail) / 2
|
||||
return math.sqrt(self.length_tail ** 2 + dw ** 2)
|
||||
@property
|
||||
def tip_incident_angle(self):
|
||||
"""
|
||||
Angle (measuring from vertical) at which the tip edge pieces must be
|
||||
sanded in order to make them not collide into each other.
|
||||
"""
|
||||
return math.atan2(self.length_tip, self.width / 2)
|
||||
@property
|
||||
def shoulder_incident_angle(self) -> float:
|
||||
angle_tip = math.atan2(self.width / 2, self.length_tip)
|
||||
angle_tail = math.atan2((self.width - self.width_tail) / 2, self.length_tail)
|
||||
return (angle_tip + angle_tail) / 2
|
||||
|
||||
@target(name="ref-tip")
|
||||
def ref_tip(self) -> Cq.Workplane:
|
||||
angle = self.tip_incident_angle
|
||||
w = self.ref_object_width
|
||||
drop = math.sin(angle) * w
|
||||
profile = (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(0, 0),
|
||||
(0, w),
|
||||
(w, w),
|
||||
(w - drop, 0),
|
||||
])
|
||||
)
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(profile)
|
||||
.extrude(self.ref_object_length)
|
||||
)
|
||||
@target(name="ref-shoulder")
|
||||
def ref_shoulder(self) -> Cq.Workplane:
|
||||
angle = self.shoulder_incident_angle
|
||||
w = self.ref_object_width
|
||||
drop = math.sin(angle) * w
|
||||
profile = (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(0, 0),
|
||||
(0, w),
|
||||
(w, w),
|
||||
(w - drop, 0),
|
||||
])
|
||||
)
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(profile)
|
||||
.extrude(self.ref_object_length)
|
||||
)
|
||||
|
||||
@target(name="side-tip-2x", kind=TargetKind.DXF)
|
||||
def profile_side_tip(self):
|
||||
l = self.length_edge_tip
|
||||
w = self.height_internal
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.push([(l/2, w/2)])
|
||||
.rect(l, w)
|
||||
)
|
||||
@target(name="side-tail", kind=TargetKind.DXF)
|
||||
def profile_side_tail(self):
|
||||
"""
|
||||
Plain side 2 with no hinge
|
||||
"""
|
||||
l = self.length_edge_tail
|
||||
w = self.height_internal
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.push([(l/2, w/2)])
|
||||
.rect(l, w)
|
||||
)
|
||||
@target(name="side-hinge-plate", kind=TargetKind.DXF)
|
||||
def profile_side_hinge_plate(self):
|
||||
l = self.hinge_plate_length
|
||||
w = self.height_internal / 2
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.push([(0, w/2)])
|
||||
.rect(l, w)
|
||||
.push([
|
||||
(self.hinge_hole_sep / 2, self.hinge_hole_axis_dist),
|
||||
(-self.hinge_hole_sep / 2, self.hinge_hole_axis_dist),
|
||||
])
|
||||
.circle(self.hinge_hole_diam / 2, mode='s')
|
||||
)
|
||||
@target(name="side-tail-hinged", kind=TargetKind.DXF)
|
||||
def profile_side_tail_hinged(self):
|
||||
"""
|
||||
Plain side 2 with no hinge
|
||||
"""
|
||||
l = self.length_edge_tail
|
||||
w = self.height_internal
|
||||
|
||||
# Holes for hinge
|
||||
plate_pos = [
|
||||
(t * l, w * 3/4) for t in self.hinge_plate_pos
|
||||
]
|
||||
hole_pos = [
|
||||
(self.hinge_hole_sep / 2, self.hinge_hole_axis_dist),
|
||||
(-self.hinge_hole_sep / 2, self.hinge_hole_axis_dist),
|
||||
]
|
||||
return (
|
||||
self.profile_side_tail()
|
||||
.push(plate_pos)
|
||||
.rect(self.hinge_plate_length, w/2, mode='s')
|
||||
.push([
|
||||
(hx + px, w/2 - hy)
|
||||
for hx, hy in hole_pos
|
||||
for px, _ in plate_pos
|
||||
])
|
||||
.circle(self.hinge_hole_diam / 2, mode='s')
|
||||
)
|
||||
@target(name="side-bot", kind=TargetKind.DXF)
|
||||
def profile_side_bot(self):
|
||||
l = self.width_tail - self.thickness_side * 2
|
||||
w = self.height_internal
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.rect(l, w)
|
||||
)
|
||||
|
||||
def surface_side_tip(self):
|
||||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_side_tip())
|
||||
.extrude(self.thickness_side)
|
||||
)
|
||||
plane = result.faces(">Y").workplane()
|
||||
plane.moveTo(0, 0).tagPlane("bot")
|
||||
plane.moveTo(-self.length_edge_tip, 0).tagPlane("top")
|
||||
return result
|
||||
def surface_side_tail(self):
|
||||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_side_tail())
|
||||
.extrude(self.thickness_side)
|
||||
)
|
||||
plane = result.faces(">Y").workplane()
|
||||
plane.moveTo(0, 0).tagPlane("bot")
|
||||
plane.moveTo(-self.length_edge_tail, 0).tagPlane("top")
|
||||
return result
|
||||
def surface_side_tail_hinged(self):
|
||||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_side_tail_hinged())
|
||||
.extrude(self.thickness_side)
|
||||
)
|
||||
plane = result.faces(">Y").workplane()
|
||||
plane.moveTo(0, 0).tagPlane("bot")
|
||||
plane.moveTo(-self.length_edge_tail, 0).tagPlane("top")
|
||||
return result
|
||||
def surface_side_bot(self):
|
||||
result = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_side_bot())
|
||||
.extrude(self.thickness_side)
|
||||
)
|
||||
plane = result.faces(">Y").workplane()
|
||||
plane.moveTo(self.width_tail / 2, 0).tagPlane("bot")
|
||||
plane.moveTo(-self.width_tail / 2, 0).tagPlane("top")
|
||||
return result
|
||||
|
||||
@assembly()
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
a = (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
self.surface_top(),
|
||||
name="top",
|
||||
material=self.material_shell,
|
||||
role=Role.STRUCTURE | Role.DECORATION
|
||||
)
|
||||
.constrain("top", "Fixed")
|
||||
.addS(
|
||||
self.surface_bottom(),
|
||||
name="bottom",
|
||||
material=self.material_shell,
|
||||
role=Role.STRUCTURE,
|
||||
loc=Cq.Location(0, 0, -self.thickness_top - self.height_internal)
|
||||
)
|
||||
.constrain("bottom", "Fixed")
|
||||
.addS(
|
||||
self.surface_side_tip(),
|
||||
name="side_tip_l",
|
||||
material=self.material_shell,
|
||||
role=Role.STRUCTURE,
|
||||
)
|
||||
.constrain("bottom?tip", "side_tip_l?top", "Plane")
|
||||
.constrain("bottom?mid_l", "side_tip_l?bot", "Plane")
|
||||
.addS(
|
||||
self.surface_side_tip(),
|
||||
name="side_tip_r",
|
||||
material=self.material_shell,
|
||||
role=Role.STRUCTURE,
|
||||
)
|
||||
.constrain("bottom?tip", "side_tip_r?bot", "Plane")
|
||||
.constrain("bottom?mid_r", "side_tip_r?top", "Plane")
|
||||
.addS(
|
||||
self.surface_side_tail(),
|
||||
name="side_tail_l",
|
||||
material=self.material_shell,
|
||||
role=Role.STRUCTURE,
|
||||
)
|
||||
.constrain("bottom?mid_l", "side_tail_l?top", "Plane")
|
||||
.constrain("bottom?bot_l", "side_tail_l?bot", "Plane")
|
||||
.addS(
|
||||
self.surface_side_tail_hinged(),
|
||||
name="side_tail_r",
|
||||
material=self.material_shell,
|
||||
role=Role.STRUCTURE,
|
||||
)
|
||||
.constrain("bottom?mid_r", "side_tail_r?bot", "Plane")
|
||||
.constrain("bottom?bot_r", "side_tail_r?top", "Plane")
|
||||
.addS(
|
||||
self.surface_side_bot(),
|
||||
name="side_bot",
|
||||
material=self.material_shell,
|
||||
role=Role.STRUCTURE,
|
||||
)
|
||||
.constrain("bottom?bot_l", "side_bot?top", "Plane")
|
||||
.constrain("bottom?bot_r", "side_bot?bot", "Plane")
|
||||
.solve()
|
||||
)
|
||||
return a
|
|
@ -0,0 +1,139 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="100mm"
|
||||
height="100mm"
|
||||
viewBox="0 0 100 100"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
||||
sodipodi:docname="zehi.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.8706489"
|
||||
inkscape:cx="171.86549"
|
||||
inkscape:cy="207.68194"
|
||||
inkscape:window-width="1640"
|
||||
inkscape:window-height="962"
|
||||
inkscape:window-x="20"
|
||||
inkscape:window-y="40"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer3"
|
||||
showguides="true">
|
||||
<sodipodi:guide
|
||||
position="50,90.756846"
|
||||
orientation="-1,0"
|
||||
id="guide1"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,134,229)" />
|
||||
<sodipodi:guide
|
||||
position="32.08421,55"
|
||||
orientation="0,1"
|
||||
id="guide2"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,134,229)" />
|
||||
<sodipodi:guide
|
||||
position="60,78.19709"
|
||||
orientation="-1,0"
|
||||
id="guide3"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,134,229)" />
|
||||
<sodipodi:guide
|
||||
position="40,74.842796"
|
||||
orientation="-1,0"
|
||||
id="guide4"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,134,229)" />
|
||||
<sodipodi:guide
|
||||
position="4.9999993,79.999999"
|
||||
orientation="0,1"
|
||||
id="guide5"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,134,229)" />
|
||||
<sodipodi:guide
|
||||
position="-9.7833569,45"
|
||||
orientation="0,1"
|
||||
id="guide6"
|
||||
inkscape:locked="false"
|
||||
inkscape:label=""
|
||||
inkscape:color="rgb(0,134,229)" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Outline"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
id="path1"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264453;stroke-opacity:1;-inkscape-stroke:none"
|
||||
inkscape:label="outer"
|
||||
d="M 50.000049 0.13229167 A 49.867775 49.867775 0 0 0 0.13229167 50.000049 A 49.867775 49.867775 0 0 0 50.000049 99.867806 A 49.867775 49.867775 0 0 0 99.867806 50.000049 A 49.867775 49.867775 0 0 0 50.000049 0.13229167 z M 50.000049 5.1190674 A 44.880997 44.880997 0 0 1 94.88103 50.000049 A 44.880997 44.880997 0 0 1 50.000049 94.88103 A 44.880997 44.880997 0 0 1 5.1190674 50.000049 A 44.880997 44.880997 0 0 1 50.000049 5.1190674 z " />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="Cut"
|
||||
style="display:none">
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264453;stroke-opacity:1;-inkscape-stroke:none"
|
||||
id="circle14"
|
||||
cx="50"
|
||||
cy="50"
|
||||
inkscape:label="outer"
|
||||
r="49.867775" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Ze"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="path10"
|
||||
style="fill:none;stroke:#144e16;stroke-width:0.290227;stroke-opacity:1;-inkscape-stroke:none"
|
||||
d="M 50.000049,12.645223 A 37.354885,37.354885 0 0 0 13.002824,44.999837 h 2.546614 2.469617 63.965088 1.438155 3.574976 A 37.354885,37.354885 0 0 0 50.000049,12.645223 Z m 0,4.980575 a 32.374233,32.374233 0 0 1 22.160404,8.817549 H 27.842745 A 32.374233,32.374233 0 0 1 50.000049,17.625798 Z M 23.725167,31.201713 h 52.552864 a 32.374233,32.374233 0 0 1 4.547526,9.039758 H 19.177641 a 32.374233,32.374233 0 0 1 4.547526,-9.039758 z"
|
||||
inkscape:label="top" />
|
||||
<path
|
||||
style="fill:none;stroke:#144e16;stroke-width:0.261252;stroke-opacity:1;-inkscape-stroke:none"
|
||||
d="m 30.468424,65.042542 -8.764322,8.764322 a 37.141727,37.141731 0 0 0 28.295947,13.12168 37.141727,37.141731 0 0 0 23.236308,-8.258411 L 70.147139,75.252771 C 64.43218,79.841996 57.3295,82.357002 50.000049,82.386702 42.031806,82.356509 34.403936,79.387593 28.529008,74.129842 l 5.513359,-5.513358 z"
|
||||
id="path14" />
|
||||
<path
|
||||
style="fill:none;stroke:#144e16;stroke-width:0.261252;stroke-opacity:1;-inkscape-stroke:none"
|
||||
d="m 53.049475,54.972872 v 0.02687 H 13.231234 a 37.141727,37.141731 0 0 0 1.076937,5.000211 H 53.049475 V 75.366976 H 58.10343 V 67.69716 H 73.684908 V 62.643205 H 58.10343 v -2.64325 h 27.588497 a 37.141727,37.141731 0 0 0 1.076937,-5.000211 H 58.10343 v -0.02687 z"
|
||||
id="path11" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="Hi"
|
||||
style="display:inline">
|
||||
<path
|
||||
id="rect6"
|
||||
style="fill:none;stroke:#053efb;stroke-width:0.264583;stroke-opacity:1;-inkscape-stroke:none"
|
||||
d="m 54.786837,19.999813 v 59.999955 h 5.213118 v -8.94364 H 79.70883 V 62.775496 H 59.999955 V 54.140365 H 79.70883 V 45.859733 H 59.999955 V 37.224601 H 79.70883 V 28.94397 H 59.999955 v -8.944157 z"
|
||||
inkscape:label="right" />
|
||||
<path
|
||||
id="path9"
|
||||
style="fill:none;stroke:#053efb;stroke-width:0.264583;stroke-opacity:1;-inkscape-stroke:none"
|
||||
d="M 45.245359,79.999977 V 20.000022 h -5.213118 v 8.94364 H 20.323366 v 8.280632 h 19.708875 v 8.635131 H 20.323366 v 8.280632 h 19.708875 v 8.635132 H 20.323366 v 8.280631 h 19.708875 v 8.944157 z"
|
||||
inkscape:label="left" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.6 KiB |
|
@ -0,0 +1,3 @@
|
|||
# Yasaka Kanako
|
||||
|
||||
This cosplay won a Judge's favourite award at TouhouFest 2025.
|
|
@ -0,0 +1,37 @@
|
|||
import nhf.touhou.yasaka_kanako.mirror as MM
|
||||
import nhf.touhou.yasaka_kanako.onbashira as MO
|
||||
import nhf.touhou.yasaka_kanako.shimenawa as MS
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
import nhf.utils
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
|
||||
@dataclass
|
||||
class Parameters(Model):
|
||||
|
||||
mirror: MM.Mirror = field(default_factory=lambda: MM.Mirror())
|
||||
onbashira: MO.Onbashira = field(default_factory=lambda: MO.Onbashira())
|
||||
shimenawa: MS.Shimenawa = field(default_factory=lambda: MS.Shimenawa())
|
||||
|
||||
def __post_init__(self):
|
||||
super().__init__(name="yasaka-kanako")
|
||||
|
||||
@submodel(name="mirror")
|
||||
def submodel_mirror(self) -> Model:
|
||||
return self.mirror
|
||||
@submodel(name="onbashira")
|
||||
def submodel_onbashira(self) -> Model:
|
||||
return self.onbashira
|
||||
@submodel(name="shimenawa")
|
||||
def submodel_shimenawa(self) -> Model:
|
||||
return self.shimenawa
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
p = Parameters()
|
||||
if len(sys.argv) == 1:
|
||||
p.build_all()
|
||||
sys.exit(0)
|
|
@ -0,0 +1,211 @@
|
|||
#define USE_MOTOR 1
|
||||
#define USE_LED 1
|
||||
#define USE_DISPLAY 0
|
||||
|
||||
// The mode switch button should be wired to the ground with an internal pullup resistor.
|
||||
#define pinButtonMode 9
|
||||
#define pinDiag 6
|
||||
|
||||
// Main LED strip setup
|
||||
#define pinLED 3
|
||||
#define NUM_LEDS 20
|
||||
#define LED_PART 10
|
||||
#define BRIGHTNESS 250
|
||||
#define LED_TYPE WS2811
|
||||
|
||||
// Relay controlled motor
|
||||
#define pinMotor 7
|
||||
|
||||
#if USE_LED
|
||||
#include <FastLED.h>
|
||||
int cycles = 100;
|
||||
int cycle_duration = 100;
|
||||
CRGB leds[NUM_LEDS];
|
||||
|
||||
CRGB color_red;
|
||||
CRGB color_blue;
|
||||
CRGB color_green;
|
||||
#endif
|
||||
|
||||
#if USE_DISPLAY
|
||||
#include <Wire.h>
|
||||
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 32
|
||||
|
||||
#define OLED_RESET -1
|
||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
||||
|
||||
#endif
|
||||
|
||||
// Program state
|
||||
|
||||
bool stateButtonMode = false;
|
||||
int programId = 0;
|
||||
bool programChanged = true;
|
||||
#define MAX_PROGRAMS 3
|
||||
bool flag_motor = false;
|
||||
bool flag_lighting = false;
|
||||
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
pinMode(pinButtonMode, INPUT_PULLUP);
|
||||
#if USE_LED
|
||||
// Calculate colours
|
||||
hsv2rgb_spectrum(CHSV(4, 255, 100), color_red);
|
||||
hsv2rgb_spectrum(CHSV(170, 255, 100), color_blue);
|
||||
hsv2rgb_spectrum(CHSV(90, 255, 100), color_green);
|
||||
pinMode(pinLED, OUTPUT);
|
||||
#endif
|
||||
pinMode(pinDiag, OUTPUT);
|
||||
#if USE_MOTOR
|
||||
pinMode(pinMotor, OUTPUT);
|
||||
#endif
|
||||
|
||||
#if USE_LED
|
||||
// Main LED strip
|
||||
FastLED.addLeds<LED_TYPE, pinLED, RGB>(leds, NUM_LEDS);
|
||||
fill_solid(leds, NUM_LEDS, CRGB::White);
|
||||
delay(500);
|
||||
FastLED.show();
|
||||
fill_solid(leds, NUM_LEDS, CRGB::Black);
|
||||
delay(500);
|
||||
#endif
|
||||
#if USE_DISPLAY
|
||||
Serial.begin(9600);
|
||||
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
|
||||
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
|
||||
Serial.println(F("SSD1306 allocation failed"));
|
||||
for(;;); // Don't proceed, loop forever
|
||||
pinMode(pinLED, OUTPUT);
|
||||
digitalWrite(pinLED, HIGH);
|
||||
}
|
||||
#endif
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Detect a rising edge
|
||||
int buttonState = !digitalRead(pinButtonMode);
|
||||
if (buttonState && !stateButtonMode) {
|
||||
programId = (programId + 1) % MAX_PROGRAMS;
|
||||
programChanged = true;
|
||||
stateButtonMode = true;
|
||||
}
|
||||
if (!buttonState) {
|
||||
stateButtonMode = false;
|
||||
}
|
||||
switch (programId)
|
||||
{
|
||||
case 0:
|
||||
program_off();
|
||||
break;
|
||||
case 1:
|
||||
program_still();
|
||||
break;
|
||||
case 2:
|
||||
program_rotate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (programChanged) {
|
||||
update_screen();
|
||||
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
programChanged = false;
|
||||
}
|
||||
|
||||
// Utility for updating LEDs
|
||||
void fill_segmented(CRGB c1, CRGB c2)
|
||||
{
|
||||
#if USE_LED
|
||||
//fill_solid(leds, LED_PART, c1);
|
||||
fill_gradient_RGB(leds, LED_PART, CRGB::Black ,c1);
|
||||
fill_gradient_RGB(leds + LED_PART, NUM_LEDS - LED_PART, CRGB::Black, c2);
|
||||
FastLED.show();
|
||||
#endif
|
||||
}
|
||||
void set_motor(bool flag)
|
||||
{
|
||||
#if USE_MOTOR
|
||||
if (flag) {
|
||||
digitalWrite(pinMotor, HIGH);
|
||||
flag_motor = true;
|
||||
}
|
||||
else {
|
||||
digitalWrite(pinMotor, LOW);
|
||||
flag_motor = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Update current display status
|
||||
void update_screen()
|
||||
{
|
||||
#if USE_DISPLAY
|
||||
display.clearDisplay();
|
||||
display.setTextSize(2);
|
||||
display.setTextColor(SSD1306_WHITE);
|
||||
display.setCursor(0,0);
|
||||
display.println("Yasaka K.");
|
||||
display.print("P");
|
||||
display.print(programId);
|
||||
display.print(" ");
|
||||
if (flag_motor) {
|
||||
display.print("M");
|
||||
}
|
||||
if (flag_lighting) {
|
||||
display.print("L");
|
||||
}
|
||||
display.display();
|
||||
#endif
|
||||
}
|
||||
|
||||
void program_off()
|
||||
{
|
||||
if (programChanged)
|
||||
{
|
||||
set_motor(false);
|
||||
#if USE_LED
|
||||
flag_lighting = false;
|
||||
fill_solid(leds, NUM_LEDS, CRGB::Black);
|
||||
FastLED.show();
|
||||
#endif
|
||||
}
|
||||
delay(cycle_duration);
|
||||
}
|
||||
void program_still()
|
||||
{
|
||||
if (programChanged)
|
||||
{
|
||||
set_motor(false);
|
||||
#if USE_LED
|
||||
flag_lighting = true;
|
||||
fill_segmented(CRGB::Green, CRGB::Orange);
|
||||
FastLED.show();
|
||||
#endif
|
||||
}
|
||||
delay(cycle_duration);
|
||||
}
|
||||
void program_rotate()
|
||||
{
|
||||
if (programChanged)
|
||||
{
|
||||
set_motor(true);
|
||||
}
|
||||
#if USE_LED
|
||||
flag_lighting = true;
|
||||
fill_segmented(CRGB::Green, CRGB::Orange);
|
||||
delay(cycle_duration/2);
|
||||
fill_solid(leds, NUM_LEDS, CRGB::Black);
|
||||
FastLED.show();
|
||||
delay(cycle_duration/2);
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
from nhf.materials import Role, Material
|
||||
import nhf.touhou.yasaka_kanako.onbashira as MO
|
||||
import nhf.utils
|
||||
|
||||
@dataclass
|
||||
class Mirror(Model):
|
||||
"""
|
||||
Kanako's mirror, made of three levels.
|
||||
|
||||
The mirror suface is sandwiched between two layers of wood. As such, its
|
||||
dimensions have to sit in between that of the aperature on the surface, and
|
||||
the outer walls. The width/height here refers to the outer edge's width and height
|
||||
"""
|
||||
width: float = 100.0
|
||||
height: float = 120.0
|
||||
|
||||
inner_gap: float = 3.0
|
||||
outer_gap: float = 3.0
|
||||
|
||||
core_thickness: float = 25.4 / 8
|
||||
casing_thickness: float = 25.4 / 8
|
||||
|
||||
flange_r0: float = 8.0
|
||||
flange_r1: float = 20.0
|
||||
flange_y1: float = 12.0
|
||||
flange_y2: float = 25.0
|
||||
flange_hole_r: float = 8.0
|
||||
wing_x1: float = 15.0
|
||||
wing_x2: float = 24.0
|
||||
wing_r1: float = 10.0
|
||||
wing_r2: float = 16.0
|
||||
tail_r0: float = 8.0
|
||||
tail_r1: float = 13.0
|
||||
tail_y1: float = 16.0
|
||||
tail_y2: float = 29.0
|
||||
|
||||
# Necklace hole
|
||||
hole_diam: float = 5.0
|
||||
|
||||
material_mirror: Material = Material.ACRYLIC_TRANSPARENT
|
||||
material_casing: Material = Material.WOOD_BIRCH
|
||||
|
||||
@target(name="core", kind=TargetKind.DXF)
|
||||
def profile_core(self) -> Cq.Sketch:
|
||||
rx = self.width/2 - self.outer_gap
|
||||
ry = self.height/2 - self.outer_gap
|
||||
return Cq.Sketch().ellipse(rx, ry)
|
||||
|
||||
def core(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(self.profile_core())
|
||||
.extrude(self.core_thickness)
|
||||
)
|
||||
|
||||
@target(name="casing-bot", kind=TargetKind.DXF)
|
||||
def profile_casing_bot(self) -> Cq.Sketch:
|
||||
"""
|
||||
Base of the casing with no holes carved out
|
||||
"""
|
||||
yt = self.height / 2 - self.outer_gap
|
||||
yh = (self.flange_y1 + self.flange_y2) / 2
|
||||
flange = (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(self.flange_r0, yt),
|
||||
(self.flange_r0, yt + self.flange_y1),
|
||||
(self.flange_r1, yt + self.flange_y1),
|
||||
(self.flange_r1, yt + self.flange_y2),
|
||||
(-self.flange_r1, yt + self.flange_y2),
|
||||
(-self.flange_r1, yt + self.flange_y1),
|
||||
(-self.flange_r0, yt + self.flange_y1),
|
||||
(-self.flange_r0, yt),
|
||||
])
|
||||
.push([
|
||||
(self.flange_hole_r, yt+yh),
|
||||
(-self.flange_hole_r, yt+yh),
|
||||
])
|
||||
.circle(self.hole_diam/2, mode="s")
|
||||
)
|
||||
tail = (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(+self.tail_r0, -yt),
|
||||
(+self.tail_r0, -yt - self.tail_y1),
|
||||
(+self.tail_r1, -yt - self.tail_y1),
|
||||
(+self.tail_r1, -yt - self.tail_y2),
|
||||
(-self.tail_r1, -yt - self.tail_y2),
|
||||
(-self.tail_r1, -yt - self.tail_y1),
|
||||
(-self.tail_r0, -yt - self.tail_y1),
|
||||
(-self.tail_r0, -yt),
|
||||
])
|
||||
)
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.ellipse(self.width/2, self.height/2)
|
||||
.boolean(flange, mode="a")
|
||||
.boolean(tail, mode="a")
|
||||
.boolean(self.profile_wing(-1), mode="a")
|
||||
.boolean(self.profile_wing(1), mode="a")
|
||||
)
|
||||
def casing_bot(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(self.profile_casing_bot())
|
||||
.extrude(self.casing_thickness)
|
||||
)
|
||||
def profile_wing(self, sign: float=1) -> Cq.Sketch:
|
||||
xt = self.width / 2 - self.outer_gap
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(sign*xt, self.wing_r1),
|
||||
(sign*(xt+self.wing_x1), self.wing_r1),
|
||||
(sign*(xt+self.wing_x1), self.wing_r2),
|
||||
(sign*(xt+self.wing_x2), self.wing_r2),
|
||||
(sign*(xt+self.wing_x2), -self.wing_r2),
|
||||
(sign*(xt+self.wing_x1), -self.wing_r2),
|
||||
(sign*(xt+self.wing_x1), -self.wing_r1),
|
||||
(sign*xt, -self.wing_r1),
|
||||
])
|
||||
)
|
||||
@target(name="casing-mid", kind=TargetKind.DXF)
|
||||
def profile_casing_mid(self) -> Cq.Sketch:
|
||||
rx = self.width/2 - self.outer_gap
|
||||
ry = self.height/2 - self.outer_gap
|
||||
return (
|
||||
self.profile_casing_bot()
|
||||
.ellipse(rx, ry, mode="s")
|
||||
)
|
||||
def casing_mid(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(self.profile_casing_mid())
|
||||
.extrude(self.core_thickness)
|
||||
)
|
||||
@target(name="casing-top", kind=TargetKind.DXF)
|
||||
def profile_casing_top(self) -> Cq.Sketch:
|
||||
rx = self.width/2 - self.outer_gap - self.inner_gap
|
||||
ry = self.height/2 - self.outer_gap - self.inner_gap
|
||||
return (
|
||||
self.profile_casing_bot()
|
||||
.ellipse(rx, ry, mode="s")
|
||||
)
|
||||
def casing_top(self) -> Cq.Workplane:
|
||||
return (
|
||||
Cq.Workplane()
|
||||
.placeSketch(self.profile_casing_top())
|
||||
.extrude(self.casing_thickness)
|
||||
)
|
||||
|
||||
@assembly()
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
return (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
self.core(),
|
||||
name="core",
|
||||
material=self.material_mirror,
|
||||
role=Role.DECORATION,
|
||||
loc=Cq.Location(0, 0, self.casing_thickness)
|
||||
)
|
||||
.addS(
|
||||
self.casing_bot(),
|
||||
name="casing_bot",
|
||||
material=self.material_casing,
|
||||
role=Role.CASING,
|
||||
)
|
||||
.addS(
|
||||
self.casing_mid(),
|
||||
name="casing_mid",
|
||||
material=self.material_casing,
|
||||
role=Role.CASING,
|
||||
loc=Cq.Location(0, 0, self.casing_thickness)
|
||||
)
|
||||
.addS(
|
||||
self.casing_top(),
|
||||
name="casing_top",
|
||||
material=self.material_casing,
|
||||
role=Role.CASING,
|
||||
loc=Cq.Location(0, 0, self.core_thickness + self.casing_thickness)
|
||||
)
|
||||
)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,264 @@
|
|||
from nhf.build import Model, TargetKind, target, assembly, submodel
|
||||
from nhf.materials import Role, Material
|
||||
import nhf.utils
|
||||
from nhf.parts.fasteners import FlatHeadBolt, HexNut, Washer
|
||||
from nhf.parts.electronics import ArduinoUnoR3, BatteryBox18650
|
||||
|
||||
from typing import Optional, Union
|
||||
import math
|
||||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
|
||||
NUT_COMMON = HexNut(
|
||||
# FIXME: weigh
|
||||
mass=0.0,
|
||||
diam_thread=6.0,
|
||||
pitch=1.0,
|
||||
thickness=5.0,
|
||||
width=9.89,
|
||||
)
|
||||
BOLT_COMMON = FlatHeadBolt(
|
||||
# FIXME: weigh
|
||||
mass=0.0,
|
||||
diam_head=12.8,
|
||||
height_head=2.8,
|
||||
diam_thread=6.0,
|
||||
height_thread=30.0,
|
||||
pitch=1.0,
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class Shimenawa(Model):
|
||||
"""
|
||||
The ring
|
||||
"""
|
||||
|
||||
diam_inner: float = 43.0
|
||||
diam_outer: float = 43.0 + 9 * 2
|
||||
|
||||
diam_hole_outer: float = 8.0
|
||||
hole_ext: float = 2.0
|
||||
hole_z: float = 15.0
|
||||
|
||||
pipe_fitting_angle_span: float = 6.0
|
||||
|
||||
pipe_joint_length: float = 120.0
|
||||
pipe_joint_outer_thickness: float = 5.0
|
||||
pipe_joint_inner_thickness: float = 4.0
|
||||
|
||||
pipe_joint_inner_angle_span: float = 120.0
|
||||
pipe_joint_taper: float = 5.0
|
||||
pipe_joint_taper_length: float = 10.0
|
||||
|
||||
ear_dr: float = 6.0
|
||||
ear_hole_diam: float = 10.0
|
||||
ear_radius: float = 15.0
|
||||
ear_thickness: float = 10.0
|
||||
|
||||
main_circumference: float = 3600.0
|
||||
|
||||
material_fitting: Material = Material.PLASTIC_PLA
|
||||
|
||||
def __post_init__(self):
|
||||
assert self.diam_inner < self.diam_outer
|
||||
|
||||
@property
|
||||
def main_radius(self) -> float:
|
||||
return self.main_circumference / (2 * math.pi)
|
||||
|
||||
@target(name="pipe-fitting-curved")
|
||||
def pipe_fitting_curved(self) -> Cq.Workplane:
|
||||
r_minor = self.diam_outer/2 + self.pipe_joint_outer_thickness
|
||||
a1 = self.pipe_fitting_angle_span
|
||||
outer = Cq.Solid.makeTorus(
|
||||
radius1=self.main_radius,
|
||||
radius2=r_minor,
|
||||
)
|
||||
inner = Cq.Solid.makeTorus(
|
||||
radius1=self.main_radius,
|
||||
radius2=self.diam_outer/2,
|
||||
)
|
||||
angle_intersector = Cq.Solid.makeCylinder(
|
||||
radius=self.main_radius + r_minor,
|
||||
height=r_minor*2,
|
||||
angleDegrees=a1,
|
||||
pnt=(0,0,-r_minor)
|
||||
).rotate((0,0,0),(0,0,1),-a1/2)
|
||||
result = (outer - inner) * angle_intersector
|
||||
|
||||
ear_outer = Cq.Solid.makeCylinder(
|
||||
radius=self.ear_radius,
|
||||
height=self.ear_thickness,
|
||||
pnt=(0,-self.ear_thickness/2,0),
|
||||
dir=(0,1,0),
|
||||
)
|
||||
ear_hole = Cq.Solid.makeCylinder(
|
||||
radius=self.ear_hole_diam/2,
|
||||
height=self.ear_thickness,
|
||||
pnt=(-self.ear_dr,-self.ear_thickness/2,0),
|
||||
dir=(0,1,0),
|
||||
)
|
||||
ear = (ear_outer - ear_hole).moved(self.main_radius - r_minor, 0, 0)
|
||||
result += ear - inner
|
||||
return result
|
||||
@target(name="pipe-joint-outer")
|
||||
def pipe_joint_outer(self) -> Cq.Workplane:
|
||||
"""
|
||||
Used to joint two pipes together (outside)
|
||||
"""
|
||||
r1 = self.diam_outer / 2 + self.pipe_joint_outer_thickness
|
||||
h = self.pipe_joint_length
|
||||
result = (
|
||||
Cq.Workplane()
|
||||
.cylinder(
|
||||
radius=r1,
|
||||
height=self.pipe_joint_length,
|
||||
)
|
||||
)
|
||||
cut_interior = Cq.Solid.makeCylinder(
|
||||
radius=self.diam_outer/2,
|
||||
height=h,
|
||||
pnt=(0, 0, -h/2)
|
||||
)
|
||||
rh = r1 + self.hole_ext
|
||||
add_hole = Cq.Solid.makeCylinder(
|
||||
radius=self.diam_hole_outer/2,
|
||||
height=rh*2,
|
||||
pnt=(-rh, 0, 0),
|
||||
dir=(1, 0, 0),
|
||||
)
|
||||
cut_hole = Cq.Solid.makeCylinder(
|
||||
radius=BOLT_COMMON.diam_thread/2,
|
||||
height=rh*2,
|
||||
pnt=(-rh, 0, 0),
|
||||
dir=(1, 0, 0),
|
||||
)
|
||||
z = self.hole_z
|
||||
result = (
|
||||
result
|
||||
+ add_hole.moved(0, 0, -z)
|
||||
+ add_hole.moved(0, 0, z)
|
||||
- cut_hole.moved(0, 0, -z)
|
||||
- cut_hole.moved(0, 0, z)
|
||||
- cut_interior
|
||||
)
|
||||
ear_outer = Cq.Solid.makeCylinder(
|
||||
radius=self.ear_radius,
|
||||
height=self.ear_thickness,
|
||||
pnt=(0, r1, -self.ear_thickness/2),
|
||||
)
|
||||
ear_hole = Cq.Solid.makeCylinder(
|
||||
radius=self.ear_hole_diam/2,
|
||||
height=self.ear_thickness,
|
||||
pnt=(0,r1+self.ear_dr,-self.ear_thickness/2),
|
||||
)
|
||||
ear = ear_outer - ear_hole - cut_interior
|
||||
return result + ear
|
||||
|
||||
@target(name="pipe-joint-inner")
|
||||
def pipe_joint_inner(self) -> Cq.Workplane:
|
||||
"""
|
||||
Used to joint two pipes together (inside)
|
||||
"""
|
||||
r1 = self.diam_inner / 2
|
||||
r2 = r1 - self.pipe_joint_taper
|
||||
r3 = r2 - self.pipe_joint_inner_thickness
|
||||
h = self.pipe_joint_length
|
||||
h0 = h - self.pipe_joint_taper_length*2
|
||||
core = Cq.Solid.makeCylinder(
|
||||
radius=r2,
|
||||
height=h0/2,
|
||||
)
|
||||
centre_cut = Cq.Solid.makeCylinder(
|
||||
radius=r3,
|
||||
height=h0/2,
|
||||
)
|
||||
taper = Cq.Solid.makeCone(
|
||||
radius1=r2,
|
||||
radius2=r1,
|
||||
height=(h - h0) / 2,
|
||||
pnt=(0, 0, h0/2),
|
||||
)
|
||||
centre_cut_taper = Cq.Solid.makeCone(
|
||||
radius1=r3,
|
||||
radius2=r3 + self.pipe_joint_taper,
|
||||
height=(h - h0) / 2,
|
||||
pnt=(0, 0, h0/2),
|
||||
)
|
||||
angle_intersector = Cq.Solid.makeCylinder(
|
||||
radius=r1,
|
||||
height=h,
|
||||
angleDegrees=self.pipe_joint_inner_angle_span
|
||||
).rotate((0,0,0), (0,0,1), -self.pipe_joint_inner_angle_span/2)
|
||||
result = (taper + core - centre_cut - centre_cut_taper) * angle_intersector
|
||||
|
||||
result += result.mirror("XY")
|
||||
|
||||
add_hole = Cq.Solid.makeCylinder(
|
||||
radius=self.diam_hole_outer/2,
|
||||
height=self.hole_ext,
|
||||
pnt=(r3, 0, 0),
|
||||
dir=(-1, 0, 0),
|
||||
)
|
||||
cut_hole = Cq.Solid.makeCylinder(
|
||||
radius=BOLT_COMMON.diam_thread/2,
|
||||
height=r1,
|
||||
pnt=(0, 0, 0),
|
||||
dir=(r1, 0, 0),
|
||||
)
|
||||
z = self.hole_z
|
||||
# avoid collisions
|
||||
nut_x = r3 - self.hole_ext - NUT_COMMON.thickness
|
||||
nut = NUT_COMMON.generate().val().rotate((0,0,0),(0,1,0),90)
|
||||
result = (
|
||||
result
|
||||
+ add_hole.moved(0, 0, z)
|
||||
+ add_hole.moved(0, 0, -z)
|
||||
- cut_hole.moved(0, 0, z)
|
||||
- cut_hole.moved(0, 0, -z)
|
||||
- nut.moved(nut_x, 0, z)
|
||||
- nut.moved(nut_x, 0, -z)
|
||||
)
|
||||
return result
|
||||
@assembly()
|
||||
def assembly_pipe_joint(self) -> Cq.Assembly:
|
||||
a = (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
self.pipe_joint_outer(),
|
||||
name="joint_outer",
|
||||
material=self.material_fitting,
|
||||
role=Role.STRUCTURE,
|
||||
)
|
||||
.addS(
|
||||
self.pipe_joint_inner(),
|
||||
name="joint_inner1",
|
||||
material=self.material_fitting,
|
||||
role=Role.STRUCTURE,
|
||||
)
|
||||
.addS(
|
||||
self.pipe_joint_inner(),
|
||||
name="joint_inner2",
|
||||
material=self.material_fitting,
|
||||
role=Role.STRUCTURE,
|
||||
loc=Cq.Location.rot2d(180),
|
||||
)
|
||||
)
|
||||
return a
|
||||
|
||||
@assembly()
|
||||
def assembly(self) -> Cq.Assembly:
|
||||
a = (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
self.pipe_fitting_curved(),
|
||||
name="fitting1",
|
||||
material=self.material_fitting,
|
||||
role=Role.STRUCTURE,
|
||||
)
|
||||
.add(
|
||||
self.assembly_pipe_joint(),
|
||||
name="pipe_joint",
|
||||
)
|
||||
)
|
||||
return a
|
31
nhf/utils.py
31
nhf/utils.py
|
@ -1,13 +1,11 @@
|
|||
"""
|
||||
Utility functions for cadquery objects
|
||||
"""
|
||||
import functools
|
||||
import math
|
||||
from typing import Optional
|
||||
import functools, math
|
||||
from typing import Optional, Union, Tuple, cast
|
||||
import cadquery as Cq
|
||||
from cadquery.occ_impl.solver import ConstraintSpec
|
||||
from nhf import Role
|
||||
from typing import Union, Tuple, cast
|
||||
from nhf.materials import KEY_ITEM, KEY_MATERIAL
|
||||
|
||||
# Bug fixes
|
||||
|
@ -55,6 +53,11 @@ def is2d(self: Cq.Location) -> bool:
|
|||
return z == 0 and rx == 0 and ry == 0
|
||||
Cq.Location.is2d = is2d
|
||||
|
||||
def scale(self: Cq.Location, fac: float) -> bool:
|
||||
(x, y, z), (rx, ry, rz) = self.toTuple()
|
||||
return Cq.Location(x*fac, y*fac, z*fac, rx, ry, rz)
|
||||
Cq.Location.scale = scale
|
||||
|
||||
def to2d(self: Cq.Location) -> Tuple[Tuple[float, float], float]:
|
||||
"""
|
||||
Returns position and angle
|
||||
|
@ -93,17 +96,24 @@ Cq.Location.with_angle_2d = with_angle_2d
|
|||
|
||||
def flip_x(self: Cq.Location) -> Cq.Location:
|
||||
(x, y), a = self.to2d()
|
||||
return Cq.Location.from2d(-x, y, 90 - a)
|
||||
return Cq.Location.from2d(-x, y, 180 - a)
|
||||
Cq.Location.flip_x = flip_x
|
||||
def flip_y(self: Cq.Location) -> Cq.Location:
|
||||
(x, y), a = self.to2d()
|
||||
return Cq.Location.from2d(x, -y, -a)
|
||||
Cq.Location.flip_y = flip_y
|
||||
|
||||
def boolean(self: Cq.Sketch, obj, **kwargs) -> Cq.Sketch:
|
||||
def boolean(
|
||||
self: Cq.Sketch,
|
||||
obj: Union[Cq.Face, Cq.Sketch, Cq.Compound],
|
||||
**kwargs) -> Cq.Sketch:
|
||||
"""
|
||||
Performs Boolean operation between a sketch and a sketch-like object
|
||||
"""
|
||||
return (
|
||||
self
|
||||
.reset()
|
||||
# Has to be 0, 0. Translation doesn't work.
|
||||
.push([(0, 0)])
|
||||
.each(lambda _: obj, **kwargs)
|
||||
)
|
||||
|
@ -152,6 +162,15 @@ def tagPlane(self, tag: str,
|
|||
|
||||
Cq.Workplane.tagPlane = tagPlane
|
||||
|
||||
def tag_absolute(
|
||||
self,
|
||||
tag: str,
|
||||
loc: Union[Cq.Location, Tuple[float, float, float]],
|
||||
direction: Union[str, Cq.Vector, Tuple[float, float, float]] = '+Z'):
|
||||
return self.pushPoints([loc]).tagPlane(tag, direction=direction)
|
||||
|
||||
Cq.Workplane.tagAbsolute = tag_absolute
|
||||
|
||||
def make_sphere(r: float = 2) -> Cq.Solid:
|
||||
"""
|
||||
Makes a full sphere. The default function makes a hemisphere
|
||||
|
|
|
@ -1,845 +0,0 @@
|
|||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "anytree"
|
||||
version = "2.12.1"
|
||||
description = "Powerful and Lightweight Python Tree Data Structure with various plugins"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2,<4"
|
||||
files = [
|
||||
{file = "anytree-2.12.1-py3-none-any.whl", hash = "sha256:5ea9e61caf96db1e5b3d0a914378d2cd83c269dfce1fb8242ce96589fa3382f0"},
|
||||
{file = "anytree-2.12.1.tar.gz", hash = "sha256:244def434ccf31b668ed282954e5d315b4e066c4940b94aff4a7962d85947830"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = "*"
|
||||
|
||||
[[package]]
|
||||
name = "asttokens"
|
||||
version = "2.4.1"
|
||||
description = "Annotate AST trees with source code positions"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
|
||||
{file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.12.0"
|
||||
|
||||
[package.extras]
|
||||
astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"]
|
||||
test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "build123d"
|
||||
version = "0.5.0"
|
||||
description = "A python CAD programming library"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "build123d-0.5.0-py3-none-any.whl", hash = "sha256:d0a4e82cdb0e53ef21fca8d2c84124351d7c7070077b5efa173d789002c8194c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anytree = ">=2.8.0,<3"
|
||||
cadquery-ocp = ">=7.7.0"
|
||||
ezdxf = ">=1.0.0,<2"
|
||||
ipython = ">=8.0.0,<9"
|
||||
numpy = ">=1.24.1,<2"
|
||||
numpy-stl = ">=3.0.0,<4"
|
||||
ocpsvg = "*"
|
||||
py-lib3mf = ">=2.3.1"
|
||||
svgpathtools = ">=1.5.1,<2"
|
||||
trianglesolver = "*"
|
||||
typing-extensions = ">=4.6.0,<5"
|
||||
|
||||
[[package]]
|
||||
name = "cadquery"
|
||||
version = "2.5.0.dev0"
|
||||
description = "CadQuery is a parametric scripting language for creating and traversing CAD models"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = []
|
||||
develop = false
|
||||
|
||||
[package.dependencies]
|
||||
cadquery-ocp = ">=7.7.0a0,<7.8"
|
||||
casadi = "*"
|
||||
ezdxf = "*"
|
||||
multimethod = ">=1.11,<2.0"
|
||||
nlopt = "*"
|
||||
path = "*"
|
||||
typish = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["black @ git+https://github.com/cadquery/black.git@cq", "docutils", "ipython", "pytest"]
|
||||
ipython = ["ipython"]
|
||||
|
||||
[package.source]
|
||||
type = "git"
|
||||
url = "https://github.com/CadQuery/cadquery.git"
|
||||
reference = "HEAD"
|
||||
resolved_reference = "8ea37a71d40d383b55b8009c68987526f47a7613"
|
||||
|
||||
[[package]]
|
||||
name = "cadquery-ocp"
|
||||
version = "7.7.2"
|
||||
description = "OCP+VTK wheel with shared library dependencies bundled."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "cadquery_ocp-7.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f2faf716855f9a8372493e001e6e318da75b046472e7ee69881d2a4e6b65a04"},
|
||||
{file = "cadquery_ocp-7.7.2-cp310-cp310-manylinux_2_35_x86_64.whl", hash = "sha256:35e2c8ca923e4ba77b833357eb14c7e8a939f17285ef6acde7c26961989a8dfe"},
|
||||
{file = "cadquery_ocp-7.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:4fc230711d52a9e7d71ab4c79c957287c7f2f85e5f09e036b573e6851df4d1ba"},
|
||||
{file = "cadquery_ocp-7.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:488401fa4d070e3eb17729fb8b6494bd3577f0913c4e901e674c4d8fcd9ac1fe"},
|
||||
{file = "cadquery_ocp-7.7.2-cp311-cp311-manylinux_2_35_x86_64.whl", hash = "sha256:2be9408551c03e1c715e3b158b41ce458178ecf530ba644e3e884a5989feb611"},
|
||||
{file = "cadquery_ocp-7.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:8e1f90250924ac50344bf0867e3d9a927f077583351b270b4e0e171033dd2046"},
|
||||
{file = "cadquery_ocp-7.7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:78a7903267cd9986181b634a315ca66fac1bb9e6dbfaf60724d812b3a2cc77bf"},
|
||||
{file = "cadquery_ocp-7.7.2-cp312-cp312-manylinux_2_35_x86_64.whl", hash = "sha256:7ac2d83b4f4b3d7c35421a1d9f8fec2adc73b6ed6cac50c1ffcede5552e38e9b"},
|
||||
{file = "cadquery_ocp-7.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9e5c8ef5aeb1d3fb8f0b207ed1a2b781d22dc3e9b9c0551f52342f5a6ee7fc5"},
|
||||
{file = "cadquery_ocp-7.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7134e629559ba110a4d0cda53cb6a30c0af52524f7a6151e772d4b720ef508be"},
|
||||
{file = "cadquery_ocp-7.7.2-cp38-cp38-manylinux_2_35_x86_64.whl", hash = "sha256:73183f141514e507c45a1e4ba1d25f1fdc604b20dd42bed7c8168f468632686b"},
|
||||
{file = "cadquery_ocp-7.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:9e40840e1f4da5e0a8f1eb2168efc33ae65ac8e046c27a49936a2e9931fc1711"},
|
||||
{file = "cadquery_ocp-7.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f6750c84f8af930f366ad22397141f3c332c39fb826edf3fe26c4e6938690f7"},
|
||||
{file = "cadquery_ocp-7.7.2-cp39-cp39-manylinux_2_35_x86_64.whl", hash = "sha256:9f6c601d1db66353f0ef7c63fde70411573e917c16fb4c7cb5d92ed85b72f154"},
|
||||
{file = "cadquery_ocp-7.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:feea223eaa2dfa33684f568b5ba2b02c35e96b5d894014f98927b5c08041a6be"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "casadi"
|
||||
version = "3.6.5"
|
||||
description = "CasADi -- framework for algorithmic differentiation and numeric optimization"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "casadi-3.6.5-cp27-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:6039081fdd1daf4ef7fa2b52814a954d75bfc03eb0dc62414e02af5d25746e8f"},
|
||||
{file = "casadi-3.6.5-cp27-none-manylinux1_i686.whl", hash = "sha256:b5192dfabf6f5266b168b984d124dd3086c1c5a408c0743ff3a82290a8ccf3b5"},
|
||||
{file = "casadi-3.6.5-cp27-none-manylinux2010_x86_64.whl", hash = "sha256:35b2ff6098e386a4d5e8bc681744e52bcd2f2f15cfa44c09814a8979b51a6794"},
|
||||
{file = "casadi-3.6.5-cp27-none-win_amd64.whl", hash = "sha256:caf395d1e36bfb215b154e8df61583d534a07ddabb18cbe50f259b7692a41ac8"},
|
||||
{file = "casadi-3.6.5-cp310-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:314886ef44bd01f1a98579e7784a3bed6e0584e88f9465cf9596af2523efb0dd"},
|
||||
{file = "casadi-3.6.5-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:c6789c8060a99b329bb584d97c1eab6a5e4f3e2d2db391e6c2001c6323774990"},
|
||||
{file = "casadi-3.6.5-cp310-none-manylinux2014_aarch64.whl", hash = "sha256:e40afb3c062817dd6ce2497cd001f00f107ee1ea41ec4d6ee9f9a5056d219e83"},
|
||||
{file = "casadi-3.6.5-cp310-none-manylinux2014_i686.whl", hash = "sha256:ee5a4ed50d2becd0bd6d203c7a60ffad27c14a3e0ae357480de11c846a8dd928"},
|
||||
{file = "casadi-3.6.5-cp310-none-manylinux2014_x86_64.whl", hash = "sha256:1ddb6e4afdd1da95d7d9d652ed973c1b7f50ef1454965a9170b657e223a2c73e"},
|
||||
{file = "casadi-3.6.5-cp310-none-win_amd64.whl", hash = "sha256:e96ca81b00b9621007d45db1254fcf232d518ebcc802f42853f57b4df977c567"},
|
||||
{file = "casadi-3.6.5-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:bebd3909db24ba711e094aacc0a2329b9903d422d73f61be851873731244b7d1"},
|
||||
{file = "casadi-3.6.5-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:ccb962ea02b7d6d245d5cd40fb52c29e812040a45273c6eed32cb8fcff673dda"},
|
||||
{file = "casadi-3.6.5-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:1ce199a4ea1d376edbe5399cd622a4564040c83f50c50114fe50a69a8ea81d92"},
|
||||
{file = "casadi-3.6.5-cp311-none-manylinux2014_i686.whl", hash = "sha256:d12b67d467a5b2b0a909378ef7231fbc9af0da923baa13b1d5464d8471601ac3"},
|
||||
{file = "casadi-3.6.5-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:3a3fb8af868f83d4a4f26d878c49f4acc4ed7ee92e731c73e650e5893418a634"},
|
||||
{file = "casadi-3.6.5-cp311-none-win_amd64.whl", hash = "sha256:3bdd645151beda013af5fd019fb554756e7dac37541b9f120cdfba90405b2671"},
|
||||
{file = "casadi-3.6.5-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:33afd1a4da0c86b4316953fe541635a8a7dc51703282e24a870ada13a46adb53"},
|
||||
{file = "casadi-3.6.5-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:0d6ee0558b4ecdd8aa4aa70fd31528b135801f1086c28a9cb78d8e8242b7aedd"},
|
||||
{file = "casadi-3.6.5-cp312-none-manylinux2014_i686.whl", hash = "sha256:be40e9897d80fb72a97e750b2143c32f63f8800cfb78f9b396d8ce7a913fca39"},
|
||||
{file = "casadi-3.6.5-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:0118637823e292a9270133e02c9c6d3f3c7f75e8c91a6f6dc5275ade82dd1d9d"},
|
||||
{file = "casadi-3.6.5-cp312-none-win_amd64.whl", hash = "sha256:fe2b64d777e36cc3f101220dd1e219a0e11c3e4ee2b5e708b30fea9a27107e41"},
|
||||
{file = "casadi-3.6.5-cp35-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:a1ae36449adec534125d4af5be912b6fb9dafe74d1fee39f6c82263695e21ca5"},
|
||||
{file = "casadi-3.6.5-cp35-none-manylinux1_i686.whl", hash = "sha256:32644c47fbfb643d5cf9769c7bbc94c6bdb9a40ea9c12c54af5e2754599c3186"},
|
||||
{file = "casadi-3.6.5-cp35-none-manylinux2010_x86_64.whl", hash = "sha256:601b76b7afcb27b11563999f6ad1d9d2a2510ab3d00a6f4ce86a0bee97c9d17a"},
|
||||
{file = "casadi-3.6.5-cp35-none-win_amd64.whl", hash = "sha256:febc645bcc0aed6d7a2bdb6e58b9a89cb8f74b19bc028c41cc807d75a5d54058"},
|
||||
{file = "casadi-3.6.5-cp36-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:c98e68023c9e5905d9d6b99ae1fbbfe4b85ba9846b3685408bb498b20509f99a"},
|
||||
{file = "casadi-3.6.5-cp36-none-manylinux2014_aarch64.whl", hash = "sha256:eb311088dca5359acc05aa4d8895bf99afaa16c7c04b27bf640ce4c2361b8cde"},
|
||||
{file = "casadi-3.6.5-cp36-none-manylinux2014_i686.whl", hash = "sha256:bceb69bf9f04fded8a564eb64e298d19e945eaf4734f7145a5ee61cf9ac693e7"},
|
||||
{file = "casadi-3.6.5-cp36-none-manylinux2014_x86_64.whl", hash = "sha256:c951031e26d987986dbc334492b2e6ef108077f11c00e178ff4007e4a9bf91d8"},
|
||||
{file = "casadi-3.6.5-cp36-none-win_amd64.whl", hash = "sha256:e44af450ce944649932f9ef63ff00d2d21f642b506444418b4b20e69dba3adaf"},
|
||||
{file = "casadi-3.6.5-cp37-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:c661fe88a93b7cc7ea42802aac76a674135cd65e3e564a6f08570dd3bea05201"},
|
||||
{file = "casadi-3.6.5-cp37-none-manylinux2014_aarch64.whl", hash = "sha256:5266fc82e39352e26cb1a4e0a5c3deb32d09e6333be637bd78c273fa50f9012b"},
|
||||
{file = "casadi-3.6.5-cp37-none-manylinux2014_i686.whl", hash = "sha256:02d6fb63c460abd99a450e861034d97568a8aec621fc0a4fed22f7494989c682"},
|
||||
{file = "casadi-3.6.5-cp37-none-manylinux2014_x86_64.whl", hash = "sha256:5e8adffe2015cde370fc545b2d0fe731e96e583e4ea4c5f3044e818fea975cfc"},
|
||||
{file = "casadi-3.6.5-cp37-none-win_amd64.whl", hash = "sha256:7ea8545579872b6f5412985dafec26b906b67bd4639a6c718b7e07f802af4e42"},
|
||||
{file = "casadi-3.6.5-cp38-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:0a38bf808bf51368607c64307dd77a7363fbe8e5c910cd5c605546be60edfaff"},
|
||||
{file = "casadi-3.6.5-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:f62f779481b30e5ea88392bdb8225e9545a21c4460dc3e96c2b782405b938d04"},
|
||||
{file = "casadi-3.6.5-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:deb2cb2bee8aba0c2cad03c832965b51ca305d0f8eb15de8b857ba86a76f0db0"},
|
||||
{file = "casadi-3.6.5-cp38-none-manylinux2014_i686.whl", hash = "sha256:f6e10b66d6ae8216dab01532f7ad75cc9d66a95125d421b33d078a51ea0fc2a0"},
|
||||
{file = "casadi-3.6.5-cp38-none-manylinux2014_x86_64.whl", hash = "sha256:f9e82658c910e3317535d769334260e0a24d97bbce68cadb72f592e9fcbafd61"},
|
||||
{file = "casadi-3.6.5-cp38-none-win_amd64.whl", hash = "sha256:092e448e05feaed8958d684e896d909e756d199b84d3b9d0182da38cd3deebf6"},
|
||||
{file = "casadi-3.6.5-cp39-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:f9c1de9a798767c00f89c27677b74059df4c9601d69270967b06d7fcff204b4d"},
|
||||
{file = "casadi-3.6.5-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:83e3404de4449cb7382e49d811eec79cd370e64b97b5c94b155c604d7c523a40"},
|
||||
{file = "casadi-3.6.5-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:af95de5aa5942d627d43312834791623384c2ad6ba87928bf0e3cacc8a6698e8"},
|
||||
{file = "casadi-3.6.5-cp39-none-manylinux2014_i686.whl", hash = "sha256:dbeb50726603454a1f85323cba7caf72524cd43ca0aeb1f286d07005a967ece9"},
|
||||
{file = "casadi-3.6.5-cp39-none-manylinux2014_x86_64.whl", hash = "sha256:8bbfb2eb8cb6b9e2384814d6427e48bcf6df049bf7ed05b0a58bb311a1fbf18c"},
|
||||
{file = "casadi-3.6.5-cp39-none-win_amd64.whl", hash = "sha256:0e4a4ec2e26ebeb22b0c129f2db3cf90f730cf9fbe98adb9a12720ff6ca1834a"},
|
||||
{file = "casadi-3.6.5.tar.gz", hash = "sha256:409a5f6725eadea40fddfb8ba2321139b5252edac8bc115a72f68e648631d56a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = "*"
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decorator"
|
||||
version = "5.1.1"
|
||||
description = "Decorators for Humans"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
|
||||
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.2"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "executing"
|
||||
version = "2.0.1"
|
||||
description = "Get the currently executing AST node of a frame, and other information"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"},
|
||||
{file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
|
||||
|
||||
[[package]]
|
||||
name = "ezdxf"
|
||||
version = "1.3.2"
|
||||
description = "A Python package to create/manipulate DXF drawings."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "ezdxf-1.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6f4eacaa8d55ddcbd64795409ff4f5e452c4b066f4e33b210bc4c6189c71ec6f"},
|
||||
{file = "ezdxf-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35d1fa27f175d2b648f3aa5f31448b81ae8fe3627b0e908862a15983bdeb191b"},
|
||||
{file = "ezdxf-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:240f7e894fe0364585d28e8f697c12e93db6fbb426c37d6a3f43a027c57d6dbf"},
|
||||
{file = "ezdxf-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c20adceb7c78e1370f117615c245a293bc7fe65525457eeb287d24fa4cd96c8"},
|
||||
{file = "ezdxf-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a311c455a84e7c2f03cefa0922fa4919d6950e9207e8e7175893507889852012"},
|
||||
{file = "ezdxf-1.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8c2955db7f41596b7245441090d02b083cae060110fd595abc2f3347bfd3cb09"},
|
||||
{file = "ezdxf-1.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:120273751ca4818d87a216cfd0f74d0fc73518b5ec052aa8c17bad9711463e48"},
|
||||
{file = "ezdxf-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:90274032eb4b047af2b38f71bca749dc6bff2110bb2f4c818f5f48e6864e6a97"},
|
||||
{file = "ezdxf-1.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:464689421c55e1c9d193da46ea461bfc82a1c0ab0007a37cbaefb44189385b04"},
|
||||
{file = "ezdxf-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7a39234e9ccb072e2362b086f511706ce76ac5774ddb618fe7ca6710b5418f72"},
|
||||
{file = "ezdxf-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:193f5146e6c8b93e6293248467d8b0c38fa12fc41b85507300f15e85b73ce219"},
|
||||
{file = "ezdxf-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e37534530c9734c927f6afafe1f3f5a6fdbde2bbf438a661173ff0ba86de8937"},
|
||||
{file = "ezdxf-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6118e375852f6db04b66c0111ded47c0e0acd42869a43aaa302815b947c5e8de"},
|
||||
{file = "ezdxf-1.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:420b6d7f80fa1bff374c7fb611ba8aef071d5523dbab9ad3a64465f7b2ac82cc"},
|
||||
{file = "ezdxf-1.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f60ada8f7b0d232a6d45cbfec4b205dc7a1beb94bb90a2518893e7a9b43681c6"},
|
||||
{file = "ezdxf-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:06550cf39bf60f62a1db3ee43426a8c66485fc946a44324d921a901f7d35bfe7"},
|
||||
{file = "ezdxf-1.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1a1bcda7d2d97f3aa3fb0db14006c91000ad51cd5aa16d51b73d42b3e88a794e"},
|
||||
{file = "ezdxf-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cd36e1430b6150e071466f1bd712aad8552c986a165fcabd1c74b47cf72684d6"},
|
||||
{file = "ezdxf-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e6a645036c3874c1693e6e2411647645ab67882e5c0c762f700e55ac9a0dc56"},
|
||||
{file = "ezdxf-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c12e9602abc8444dc5606e0c39cb6826df17e7c1a01d576d586f0a39696d539d"},
|
||||
{file = "ezdxf-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77aed29c3d14067c2e7986057b6fe6842167b89d6a35df5d1636b6627e1ea117"},
|
||||
{file = "ezdxf-1.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e0881f8fb4fa6386ef963a657bc7291f5ec3029844ba6e7a905c9f9b713ccae"},
|
||||
{file = "ezdxf-1.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fddf6cfd0bf7fe78273918986f917b4f515d9a6371ee1b8cf310d4cd879d33e9"},
|
||||
{file = "ezdxf-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:d504f843c20e9b7c2d331352ac91710bd6ebd14cf56c576a3432dacdfdde7106"},
|
||||
{file = "ezdxf-1.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3bcd10a9ac39728d949d0edfd7eb460707d4b4620d37db41b4790c3c871dbab"},
|
||||
{file = "ezdxf-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4795843993061f9a3127e41328c5c02483ba619fda53b91bbe1e764b4294ad31"},
|
||||
{file = "ezdxf-1.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cfcb2bee332917b1f7353f30d8cfe1e24774034e86d1f1360eaa0675b2c402bf"},
|
||||
{file = "ezdxf-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13acf2a25854d735b23ba569500aa9222ae34862a5dc39a3bb867089b884274"},
|
||||
{file = "ezdxf-1.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f3fd73b9f654491864e37153d86ceb14cfae6cc78d0693259cea49bdcd935882"},
|
||||
{file = "ezdxf-1.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5629cb3a21ccc3895b57a507f046951a76836b9aaafff7dd5c1cda67ef258271"},
|
||||
{file = "ezdxf-1.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6615464a6b2a6af282716f0ab3f218e0a8abf27604e2cc638ee27285b29c8034"},
|
||||
{file = "ezdxf-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:e4f3dd9c93623c25488f7cddbd2914a9a18b29fc32c7ae5a95a3915b149836dc"},
|
||||
{file = "ezdxf-1.3.2-py3-none-any.whl", hash = "sha256:4451a04765323e93df943a0584db50f3851be0ca4aa8b8a4ee809faf492b3a5d"},
|
||||
{file = "ezdxf-1.3.2.zip", hash = "sha256:ecaa9e69f20fb66245164f235e616dd0789a11ac8a72a0302780b77621e1c354"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
fonttools = "*"
|
||||
numpy = "*"
|
||||
pyparsing = ">=2.0.1"
|
||||
typing_extensions = ">=4.6.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["Cython", "Pillow", "PyMuPDF (>=1.20.0)", "PySide6", "matplotlib", "pytest", "setuptools", "wheel"]
|
||||
dev5 = ["Cython", "Pillow", "PyMuPDF (>=1.20.0)", "PyQt5", "matplotlib", "pytest", "setuptools", "wheel"]
|
||||
draw = ["Pillow", "PyMuPDF (>=1.20.0)", "PySide6", "matplotlib"]
|
||||
draw5 = ["Pillow", "PyMuPDF (>=1.20.0)", "PyQt5", "matplotlib"]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.53.1"
|
||||
description = "Tools to manipulate font files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"},
|
||||
{file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"},
|
||||
{file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
|
||||
graphite = ["lz4 (>=1.7.4.2)"]
|
||||
interpolatable = ["munkres", "pycairo", "scipy"]
|
||||
lxml = ["lxml (>=4.0)"]
|
||||
pathops = ["skia-pathops (>=0.5.0)"]
|
||||
plot = ["matplotlib"]
|
||||
repacker = ["uharfbuzz (>=0.23.0)"]
|
||||
symfont = ["sympy"]
|
||||
type1 = ["xattr"]
|
||||
ufo = ["fs (>=2.2.0,<3)"]
|
||||
unicode = ["unicodedata2 (>=15.1.0)"]
|
||||
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "8.26.0"
|
||||
description = "IPython: Productive Interactive Computing"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "ipython-8.26.0-py3-none-any.whl", hash = "sha256:e6b347c27bdf9c32ee9d31ae85defc525755a1869f14057e900675b9e8d6e6ff"},
|
||||
{file = "ipython-8.26.0.tar.gz", hash = "sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
decorator = "*"
|
||||
exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
|
||||
jedi = ">=0.16"
|
||||
matplotlib-inline = "*"
|
||||
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""}
|
||||
prompt-toolkit = ">=3.0.41,<3.1.0"
|
||||
pygments = ">=2.4.0"
|
||||
stack-data = "*"
|
||||
traitlets = ">=5.13.0"
|
||||
typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""}
|
||||
|
||||
[package.extras]
|
||||
all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"]
|
||||
black = ["black"]
|
||||
doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"]
|
||||
kernel = ["ipykernel"]
|
||||
matplotlib = ["matplotlib"]
|
||||
nbconvert = ["nbconvert"]
|
||||
nbformat = ["nbformat"]
|
||||
notebook = ["ipywidgets", "notebook"]
|
||||
parallel = ["ipyparallel"]
|
||||
qtconsole = ["qtconsole"]
|
||||
test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"]
|
||||
test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"]
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.19.1"
|
||||
description = "An autocompletion tool for Python that can be used for text editors."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
|
||||
{file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
parso = ">=0.8.3,<0.9.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
|
||||
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
|
||||
testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib-inline"
|
||||
version = "0.1.7"
|
||||
description = "Inline Matplotlib backend for Jupyter"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"},
|
||||
{file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
traitlets = "*"
|
||||
|
||||
[[package]]
|
||||
name = "multimethod"
|
||||
version = "1.12"
|
||||
description = "Multiple argument dispatching."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "multimethod-1.12-py3-none-any.whl", hash = "sha256:fd0c473c43558908d97cc06e4d68e8f69202f167db46f7b4e4058893e7dbdf60"},
|
||||
{file = "multimethod-1.12.tar.gz", hash = "sha256:8db8ef2a8d2a247e3570cc23317680892fdf903d84c8c1053667c8e8f7671a67"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nlopt"
|
||||
version = "2.7.1"
|
||||
description = "Library for nonlinear optimization, wrapping many algorithms for global and local, constrained or unconstrained, optimization"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "nlopt-2.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:42b7883704e1285ff40d930699eb7fc7e1341229da33666b4163459cfdf89fb1"},
|
||||
{file = "nlopt-2.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ba0862162248442fbf1f04b20a321c11ff40ff4442a12aaaafcdaff9abb0ab7"},
|
||||
{file = "nlopt-2.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:426c18548d733640449d707c82eb57c09a5f01d4b064f87312808d194d227f24"},
|
||||
{file = "nlopt-2.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7a12fe3cbfb36a6a18f84a1ac23ed3dda323860235381b3d2d182d8b771783ef"},
|
||||
{file = "nlopt-2.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1653de0060a42d6709423e6160888893bb688f4ff79aa0f1def4701ea25dd8"},
|
||||
{file = "nlopt-2.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:88ec7cf491da150d497ecc61889bc7adb0af0ad05a67e925a4f5ac88e20f1b9c"},
|
||||
{file = "nlopt-2.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:da0ac81b10f838afe7c1b99a2f895c31e05ca68328571fe430f382ce08cbfb07"},
|
||||
{file = "nlopt-2.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:592ded3b34bb888cd99c5da3fb1c3c9269ddd996dade578a8ec325cd8b6be752"},
|
||||
{file = "nlopt-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:1647131d53302e72f5c4851ab04a92401a342c3e0fcfaac0eda316f5e8f3b283"},
|
||||
{file = "nlopt-2.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:79791a2179d1cf708622eaeea76c88acbadc6af0d2f198df21a74473838686c3"},
|
||||
{file = "nlopt-2.7.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad38bab99348f6c3bbf0d5f339b3fd77465b27ef44c330f4ba512a40b87b373"},
|
||||
{file = "nlopt-2.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:479a415f522051f6d728a3279c013aab96a6eaf3c323a89582dcb07eb636f15f"},
|
||||
{file = "nlopt-2.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d99f1d6217bc3ead6fa6fe84a923577003f9a5f760cd354a3f8dcd1e11d626ce"},
|
||||
{file = "nlopt-2.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f9370bd37788b4ac792cf161835f1e4e9bbad8bfb5a76f75a295ae38dcd8d0"},
|
||||
{file = "nlopt-2.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8e7b65cf3a751e822b02f28b65d0c548052523fa6333619af3f24fec60a6b6bd"},
|
||||
{file = "nlopt-2.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:087ff54de5ec0375fd18f843b36e9a8590c0f1e194bb45d3119ba844aeb836dd"},
|
||||
{file = "nlopt-2.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4a05448f0ffebbab7a6a822297430e018c848652280e6efa13484e210291d5c"},
|
||||
{file = "nlopt-2.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:757c41210f3ab6173e5c508c79c7833e33cf90a068d098b1e13d277432120b81"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.14"
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "1.26.4"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"},
|
||||
{file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"},
|
||||
{file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"},
|
||||
{file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"},
|
||||
{file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"},
|
||||
{file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"},
|
||||
{file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"},
|
||||
{file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"},
|
||||
{file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"},
|
||||
{file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"},
|
||||
{file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"},
|
||||
{file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"},
|
||||
{file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"},
|
||||
{file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"},
|
||||
{file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"},
|
||||
{file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"},
|
||||
{file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"},
|
||||
{file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"},
|
||||
{file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"},
|
||||
{file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"},
|
||||
{file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"},
|
||||
{file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"},
|
||||
{file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"},
|
||||
{file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"},
|
||||
{file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"},
|
||||
{file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"},
|
||||
{file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"},
|
||||
{file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"},
|
||||
{file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"},
|
||||
{file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"},
|
||||
{file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"},
|
||||
{file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"},
|
||||
{file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"},
|
||||
{file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"},
|
||||
{file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"},
|
||||
{file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy-stl"
|
||||
version = "3.1.1"
|
||||
description = "Library to make reading, writing and modifying both binary and ascii STL files easy."
|
||||
optional = false
|
||||
python-versions = ">3.6.0"
|
||||
files = [
|
||||
{file = "numpy-stl-3.1.1.tar.gz", hash = "sha256:f78eea62c80938bf53ea914fa5b6c92f448f0eab5609e0e5a737dde039404334"},
|
||||
{file = "numpy_stl-3.1.1-py3-none-any.whl", hash = "sha256:b0b7f4455c29d26d3dc0eed894f5b17c64e4019b056d0060be48f93680f6e6d3"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = "*"
|
||||
python-utils = ">=3.4.5"
|
||||
|
||||
[[package]]
|
||||
name = "ocpsvg"
|
||||
version = "0.2.1"
|
||||
description = ""
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "ocpsvg-0.2.1-py3-none-any.whl", hash = "sha256:e04c6fc6578a9a5c51ddfd9af9c10b9294c9cd3781e90b4ff693b58777fe6106"},
|
||||
{file = "ocpsvg-0.2.1.tar.gz", hash = "sha256:8a089249b52b0bff99cca9698af3d74ef678ced2359316f8c2a04aee9baafb46"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cadquery-ocp = ">=7.7.0"
|
||||
svgelements = ">=1.9.1,<2"
|
||||
svgpathtools = ">=1.5.1,<2"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.4"
|
||||
description = "A Python Parser"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"},
|
||||
{file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
|
||||
testing = ["docopt", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "path"
|
||||
version = "16.14.0"
|
||||
description = "A module wrapper for os.path"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "path-16.14.0-py3-none-any.whl", hash = "sha256:8ee37703cbdc7cc83835ed4ecc6b638226fb2b43b7b45f26b620589981a109a5"},
|
||||
{file = "path-16.14.0.tar.gz", hash = "sha256:dbaaa7efd4602fd6ba8d82890dc7823d69e5de740a6e842d9919b0faaf2b6a8e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["appdirs", "more-itertools", "packaging", "pygments", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "pywin32"]
|
||||
|
||||
[[package]]
|
||||
name = "pexpect"
|
||||
version = "4.9.0"
|
||||
description = "Pexpect allows easy control of interactive console applications."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
|
||||
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
ptyprocess = ">=0.5"
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.47"
|
||||
description = "Library for building powerful interactive command lines in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"},
|
||||
{file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
wcwidth = "*"
|
||||
|
||||
[[package]]
|
||||
name = "ptyprocess"
|
||||
version = "0.7.0"
|
||||
description = "Run a subprocess in a pseudo terminal"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
|
||||
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pure-eval"
|
||||
version = "0.2.2"
|
||||
description = "Safely evaluate AST nodes without side effects"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
|
||||
{file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "py-lib3mf"
|
||||
version = "2.3.1"
|
||||
description = "A python package for Lib3MF tools"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "py_lib3mf-2.3.1-py3-none-any.whl", hash = "sha256:86a870ef386debba9b74683d3a08125a34c153aaa65e967f61677cc5a0a65e24"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.18.0"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
|
||||
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.1.2"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
optional = false
|
||||
python-versions = ">=3.6.8"
|
||||
files = [
|
||||
{file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"},
|
||||
{file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "python-utils"
|
||||
version = "3.8.2"
|
||||
description = "Python Utils is a module with some convenient utilities not included with the standard Python install"
|
||||
optional = false
|
||||
python-versions = ">3.8.0"
|
||||
files = [
|
||||
{file = "python-utils-3.8.2.tar.gz", hash = "sha256:c5d161e4ca58ce3f8c540f035e018850b261a41e7cb98f6ccf8e1deb7174a1f1"},
|
||||
{file = "python_utils-3.8.2-py2.py3-none-any.whl", hash = "sha256:ad0ccdbd6f856d015cace07f74828b9840b5c4072d9e868a7f6a14fd195555a8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">3.10.0.2"
|
||||
|
||||
[package.extras]
|
||||
docs = ["mock", "python-utils", "sphinx"]
|
||||
loguru = ["loguru"]
|
||||
tests = ["flake8", "loguru", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mypy", "sphinx", "types-setuptools"]
|
||||
|
||||
[[package]]
|
||||
name = "scipy"
|
||||
version = "1.14.0"
|
||||
description = "Fundamental algorithms for scientific computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "scipy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484"},
|
||||
{file = "scipy-1.14.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6"},
|
||||
{file = "scipy-1.14.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7"},
|
||||
{file = "scipy-1.14.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1"},
|
||||
{file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0"},
|
||||
{file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0"},
|
||||
{file = "scipy-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d"},
|
||||
{file = "scipy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359"},
|
||||
{file = "scipy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e"},
|
||||
{file = "scipy-1.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb"},
|
||||
{file = "scipy-1.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf"},
|
||||
{file = "scipy-1.14.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86"},
|
||||
{file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8"},
|
||||
{file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74"},
|
||||
{file = "scipy-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb"},
|
||||
{file = "scipy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc"},
|
||||
{file = "scipy-1.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9"},
|
||||
{file = "scipy-1.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4"},
|
||||
{file = "scipy-1.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0"},
|
||||
{file = "scipy-1.14.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f"},
|
||||
{file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209"},
|
||||
{file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14"},
|
||||
{file = "scipy-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159"},
|
||||
{file = "scipy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20"},
|
||||
{file = "scipy-1.14.0.tar.gz", hash = "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.23.5,<2.3"
|
||||
|
||||
[package.extras]
|
||||
dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"]
|
||||
doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"]
|
||||
test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stack-data"
|
||||
version = "0.6.3"
|
||||
description = "Extract data from python stack frames and tracebacks for informative displays"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
|
||||
{file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asttokens = ">=2.1.0"
|
||||
executing = ">=1.2.0"
|
||||
pure-eval = "*"
|
||||
|
||||
[package.extras]
|
||||
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
|
||||
|
||||
[[package]]
|
||||
name = "svgelements"
|
||||
version = "1.9.6"
|
||||
description = "Svg Elements Parsing"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "svgelements-1.9.6-py2.py3-none-any.whl", hash = "sha256:8a5cf2cc066d98e713d5b875b1d6e5eeb9b92e855e835ebd7caab2713ae1dcad"},
|
||||
{file = "svgelements-1.9.6.tar.gz", hash = "sha256:7c02ad6404cd3d1771fd50e40fbfc0550b0893933466f86a6eb815f3ba3f37f7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svgpathtools"
|
||||
version = "1.6.1"
|
||||
description = "A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "svgpathtools-1.6.1-py2.py3-none-any.whl", hash = "sha256:39967f9a817b8a12cc6dd1646fc162d522fca6c3fd5f8c94913c15ee4cb3a906"},
|
||||
{file = "svgpathtools-1.6.1.tar.gz", hash = "sha256:7054e6de1953e295bf565d535d585695453b09f8db4a2f7c4853348732097a3e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = "*"
|
||||
scipy = "*"
|
||||
svgwrite = "*"
|
||||
|
||||
[[package]]
|
||||
name = "svgwrite"
|
||||
version = "1.4.3"
|
||||
description = "A Python library to create SVG drawings."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "svgwrite-1.4.3-py3-none-any.whl", hash = "sha256:bb6b2b5450f1edbfa597d924f9ac2dd099e625562e492021d7dd614f65f8a22d"},
|
||||
{file = "svgwrite-1.4.3.zip", hash = "sha256:a8fbdfd4443302a6619a7f76bc937fc683daf2628d9b737c891ec08b8ce524c3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "traitlets"
|
||||
version = "5.14.3"
|
||||
description = "Traitlets Python configuration system"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
|
||||
{file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
||||
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
|
||||
|
||||
[[package]]
|
||||
name = "trianglesolver"
|
||||
version = "1.2"
|
||||
description = "Find all the sides and angles of a triangle, if you know some of the sides and/or angles. (Uses the Law of Sines and Law of Cosines.)"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "trianglesolver-1.2-py3-none-any.whl", hash = "sha256:aa0903c3708b4e2b496f06d490cae72c6ff6274b00d1edce420fcfa3b2b76682"},
|
||||
{file = "trianglesolver-1.2.tar.gz", hash = "sha256:4af18aade579d5c0d64389b3e65aeaf06cff26319762ccd859e3268559a76aea"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typish"
|
||||
version = "1.9.3"
|
||||
description = "Functionality for types"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "typish-1.9.3-py3-none-any.whl", hash = "sha256:03cfee5e6eb856dbf90244e18f4e4c41044c8790d5779f4e775f63f982e2f896"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["codecov", "coverage", "mypy", "nptyping (>=1.3.0)", "numpy", "pycodestyle", "pylint", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.13"
|
||||
description = "Measures the displayed width of unicode strings in a terminal"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
|
||||
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "6fc2644e7778ba22f8f5f2bcb2ca54f03b325f62c8a3fcd1c265a17561d874b8"
|
|
@ -1,21 +1,34 @@
|
|||
[tool.poetry]
|
||||
[project]
|
||||
name = "nhf"
|
||||
version = "0.1.0"
|
||||
description = "NorCal Hakkero Factory No. 1 Cosplay Designs"
|
||||
authors = ["Leni Aniva <v@leni.sh>"]
|
||||
authors = [{ name = "Leni Aniva", email = "aniva@stanford.edu" }]
|
||||
requires-python = ">=3.10,<3.13"
|
||||
readme = "README.md"
|
||||
dependencies = [
|
||||
"cadquery==2.5.2",
|
||||
"numpy>=2,<3",
|
||||
"colorama>=0.4.6,<0.5",
|
||||
"multimethod~=1.12",
|
||||
"scipy>=1.14.0,<2",
|
||||
"typish>=1.9.3,<2",
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
cadquery = {git = "https://github.com/CadQuery/cadquery.git"}
|
||||
build123d = "^0.5.0"
|
||||
numpy = "^1.26.4"
|
||||
colorama = "^0.4.6"
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"cq-editor",
|
||||
"pyqt5>=5.15.11,<6",
|
||||
"logbook>=1.8.0,<2",
|
||||
"spyder>=5,<6",
|
||||
"pyqtgraph>=0.13.7,<0.14",
|
||||
"unittest-parallel>=1.7.4",
|
||||
]
|
||||
|
||||
# cadquery dependency
|
||||
multimethod = "^1.12"
|
||||
scipy = "^1.14.0"
|
||||
[tool.uv]
|
||||
|
||||
[tool.uv.sources]
|
||||
cq-editor = { git = "https://github.com/CadQuery/CQ-editor.git" }
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
|
Loading…
Reference in New Issue