Compare commits
11 Commits
main
...
touhou/shi
Author | SHA1 | Date |
---|---|---|
Leni Aniva | 70fbe7dcb3 | |
Leni Aniva | 21b3c98856 | |
Leni Aniva | dbf374fe20 | |
Leni Aniva | 9109676502 | |
Leni Aniva | 077651e708 | |
Leni Aniva | e02ec4d257 | |
Leni Aniva | 3ac342a65d | |
Leni Aniva | d910326096 | |
Leni Aniva | 95313b76eb | |
Leni Aniva | bfa96e7cef | |
Leni Aniva | fbacd980c0 |
|
@ -1,7 +1,6 @@
|
|||
# Cosplay
|
||||
|
||||
This is the design repository for NorCal Hakkero Factory No. 1, where we use
|
||||
parametric CAD to make cosplay props.
|
||||
This is the design repository for NorCal Hakkero Factory No. 1.
|
||||
|
||||
## Development
|
||||
|
||||
|
@ -16,12 +15,6 @@ and this should succeed
|
|||
python3 -c "import nhf"
|
||||
```
|
||||
|
||||
To visualize an object, create a file `visualize.py`, and run `cq-editor`:
|
||||
|
||||
``` sh
|
||||
python3 -m cq_editor visualize.py
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run all tests with
|
||||
|
|
10
nhf/build.py
10
nhf/build.py
|
@ -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
|
||||
|
|
|
@ -84,6 +84,7 @@ 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)
|
||||
METAL_BRASS = 8.5, _color('gold1', 0.8)
|
||||
|
||||
def __init__(self, density: float, color: Cq.Color):
|
||||
self.density = density
|
||||
|
@ -116,6 +117,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 +127,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
|
||||
|
|
|
@ -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,388 @@
|
|||
import math
|
||||
from dataclasses import dataclass, field
|
||||
import cadquery as Cq
|
||||
from nhf import Material, Role
|
||||
from nhf.build import Model, target, assembly, TargetKind
|
||||
import nhf.utils
|
||||
|
||||
@dataclass
|
||||
class Crown(Model):
|
||||
|
||||
facets: int = 5
|
||||
# Lower circumference
|
||||
base_circ: float = 538.0
|
||||
# Upper circumference
|
||||
tilt_circ: float = 640.0
|
||||
height: float = 120.0
|
||||
|
||||
margin: float = 10.0
|
||||
|
||||
thickness: float = 0.4 # 26 Gauge
|
||||
|
||||
material: Material = Material.METAL_BRASS
|
||||
|
||||
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"
|
||||
|
||||
@property
|
||||
def facet_width_lower(self):
|
||||
return self.base_circ / self.facets
|
||||
@property
|
||||
def facet_width_upper(self):
|
||||
return self.tilt_circ / self.facets
|
||||
|
||||
def profile_base(self) -> Cq.Sketch:
|
||||
# Generate the pentagonal shape
|
||||
|
||||
dx_l = self.facet_width_lower
|
||||
dx_u = self.facet_width_upper
|
||||
dy = self.height
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.polygon([
|
||||
(dx_l/2, 0),
|
||||
(dx_u/2, dy/2),
|
||||
(0, dy),
|
||||
(-dx_u/2, dy/2),
|
||||
(-dx_l/2, 0),
|
||||
])
|
||||
)
|
||||
|
||||
@target(name="side", kind=TargetKind.DXF)
|
||||
def profile_side(self) -> Cq.Sketch:
|
||||
dy = self.facet_width_upper * 0.1
|
||||
x_side = self.facet_width_upper
|
||||
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),
|
||||
(0, y_tip - dy/2),
|
||||
(0, y_tip - dy/2),
|
||||
(-dy, y_tip - dy),
|
||||
])
|
||||
.assemble()
|
||||
)
|
||||
return (
|
||||
self.profile_base()
|
||||
.boolean(eye, mode='s')
|
||||
)
|
||||
|
||||
@target(name="dot", kind=TargetKind.DXF)
|
||||
def profile_dot(self) -> Cq.Sketch:
|
||||
return (
|
||||
Cq.Sketch()
|
||||
.circle(self.margin / 2)
|
||||
)
|
||||
|
||||
@target(name="front", kind=TargetKind.DXF)
|
||||
def profile_front(self) -> Cq.Sketch:
|
||||
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)),
|
||||
)
|
||||
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/64 # "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 (
|
||||
self.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.245 * dx, 0.55 * dy)
|
||||
p_base = Cq.Location.from2d(dx, 0)
|
||||
|
||||
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()
|
||||
.segment(
|
||||
p_base.to2d_pos(),
|
||||
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 assembly(self) -> Cq.Assembly:
|
||||
front = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_front())
|
||||
.extrude(self.thickness)
|
||||
)
|
||||
side = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_side())
|
||||
.extrude(self.thickness)
|
||||
)
|
||||
side_guard = (
|
||||
Cq.Workplane('XY')
|
||||
.placeSketch(self.profile_side_guard())
|
||||
.extrude(self.thickness)
|
||||
)
|
||||
assembly = (
|
||||
Cq.Assembly()
|
||||
.addS(
|
||||
front,
|
||||
name="front",
|
||||
material=self.material,
|
||||
role=Role.DECORATION,
|
||||
)
|
||||
)
|
||||
for i, pos in enumerate([-2, -1, 1, 2]):
|
||||
x = self.facet_width_upper * pos
|
||||
assembly = (
|
||||
assembly
|
||||
.addS(
|
||||
side,
|
||||
name=f"side{i}",
|
||||
material=self.material,
|
||||
role=Role.DECORATION,
|
||||
loc=Cq.Location.from2d(x, 0),
|
||||
)
|
||||
.addS(
|
||||
side_guard,
|
||||
name=f"guard{i}",
|
||||
material=self.material,
|
||||
role=Role.DECORATION,
|
||||
loc=Cq.Location(x, 0, self.thickness),
|
||||
)
|
||||
)
|
||||
return assembly
|
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 |
22
nhf/utils.py
22
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)
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,21 +8,13 @@ readme = "README.md"
|
|||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
cadquery = {git = "https://github.com/CadQuery/cadquery.git"}
|
||||
#build123d = "^0.5.0"
|
||||
numpy = ">=2,<3"
|
||||
build123d = "^0.5.0"
|
||||
numpy = "^1.26.4"
|
||||
colorama = "^0.4.6"
|
||||
|
||||
# cadquery dependency
|
||||
multimethod = "^1.12"
|
||||
scipy = "^1.14.0"
|
||||
typish = "^1.9.3"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
cq-editor = {git = "https://github.com/CadQuery/CQ-editor.git"}
|
||||
pyqt5 = "^5.15.11"
|
||||
logbook = "^1.8.0"
|
||||
spyder = "^5"
|
||||
pyqtgraph = "^0.13.7"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
|
Loading…
Reference in New Issue