├── .gitignore
├── requirements.txt
├── printer
└── README.md
├── archive
├── blender
│ ├── run.sh
│ ├── render.blend
│ └── render_test.py
├── graphql-test
│ └── app.py
└── helix_test.py
├── merged
├── README.md
├── templates
│ ├── show.html
│ └── list.html
├── serve.py
└── dc.py
├── postall.sh
├── ref
├── lm8uu.jpg
├── servo.jpg
├── train.jpg
├── s-l1600.jpg
├── zefEDtW.jpg
├── AA_battery.jpg
├── Raspberry-Pi-Zero.pdf
├── Arduino Uno board size.png
├── F7TJ2I0I697TI4L.LARGE.jpg
├── Raspberry-Pi-1-Model-B.pdf
├── info.txt
├── Raspberry-Pi-B-Plus-V1.2-Mechanical-1.png
├── Back-to-the-Future-Flux-Capacitor-USB-Car-Charger1.jpg?fit=1398%2C1156
├── metal_servo_horn_for_power_hd_giant_servo_hd_1235mg__pololu_2374__04.png
└── flux_capacitor.jpg
├── templ.txt
├── strut.py
├── README.md
├── TODO
├── dc_array.py
├── arduino.py
├── motor.py
├── atx.py
├── partref.py
├── base_box.py
├── ser.py
├── threaded.py
├── idler.py
├── manufacture.py
├── __init__.py
├── calculations.py
├── plank.py
├── gear.py
├── template.py
├── sonar.py
├── involute.py
├── coupling.py
├── shell_test.py
├── boxed_board.py
├── belt.py
├── case_arr.py
├── extraction_test.py
├── control_panel.py
├── extract.py
├── handle.py
├── pulley.py
├── led.py
├── bork.py
├── bucket.py
├── open_box.py
├── shaft.py
├── stepper_cat.py
├── linear_bearing.py
├── beard_boss.py
├── boss.py
├── roller.py
├── multi.py
├── project_case.py
├── flip_box.py
├── pencil_case.py
├── mill.py
├── button.py
├── lcd.py
├── svg_test.py
├── electronics.py
├── robot_base_mount.py
├── circle_pan_tilt.py
├── mounted.py
├── servo_horns.py
├── wheel.py
├── board_test.py
├── battery.py
├── controller.py
├── robot_base.py
├── driver.py
├── mounted_board.py
├── rocket_drone.py
├── simplemount.py
├── get_faces.py
├── turntable.py
├── dc.py
├── peg.py
├── SVGexport.py
├── pan_tilt.py
└── stepper.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.db
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | rectpack
2 | anytree
3 |
--------------------------------------------------------------------------------
/printer/README.md:
--------------------------------------------------------------------------------
1 | prusa i3 conversion to cqparts
2 |
--------------------------------------------------------------------------------
/archive/blender/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | blender -P ./render_test.py -b
3 |
--------------------------------------------------------------------------------
/merged/README.md:
--------------------------------------------------------------------------------
1 | Move components into here when and if they get moved upstream.
2 |
--------------------------------------------------------------------------------
/postall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | for filename in *.py ; do python $filename; done
3 |
--------------------------------------------------------------------------------
/ref/lm8uu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/lm8uu.jpg
--------------------------------------------------------------------------------
/ref/servo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/servo.jpg
--------------------------------------------------------------------------------
/ref/train.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/train.jpg
--------------------------------------------------------------------------------
/ref/s-l1600.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/s-l1600.jpg
--------------------------------------------------------------------------------
/ref/zefEDtW.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/zefEDtW.jpg
--------------------------------------------------------------------------------
/ref/AA_battery.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/AA_battery.jpg
--------------------------------------------------------------------------------
/ref/Raspberry-Pi-Zero.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/Raspberry-Pi-Zero.pdf
--------------------------------------------------------------------------------
/templ.txt:
--------------------------------------------------------------------------------
1 | if __name__ == "__main__":
2 | from cqparts.display import display
3 |
4 | display()
5 |
--------------------------------------------------------------------------------
/archive/blender/render.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/archive/blender/render.blend
--------------------------------------------------------------------------------
/ref/Arduino Uno board size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/Arduino Uno board size.png
--------------------------------------------------------------------------------
/ref/F7TJ2I0I697TI4L.LARGE.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/F7TJ2I0I697TI4L.LARGE.jpg
--------------------------------------------------------------------------------
/ref/Raspberry-Pi-1-Model-B.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/Raspberry-Pi-1-Model-B.pdf
--------------------------------------------------------------------------------
/ref/info.txt:
--------------------------------------------------------------------------------
1 | https://reprap.org/wiki/Prusa_i3_Build_Manual
2 |
3 | https://en.wikipedia.org/wiki/List_of_battery_sizes
4 |
--------------------------------------------------------------------------------
/ref/Raspberry-Pi-B-Plus-V1.2-Mechanical-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/Raspberry-Pi-B-Plus-V1.2-Mechanical-1.png
--------------------------------------------------------------------------------
/strut.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Wed Jun 13 08:33:24 2018
4 |
5 | @author: simonk
6 | """
7 |
8 | # a threaded strut for the
9 |
--------------------------------------------------------------------------------
/ref/Back-to-the-Future-Flux-Capacitor-USB-Car-Charger1.jpg?fit=1398%2C1156:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/Back-to-the-Future-Flux-Capacitor-USB-Car-Charger1.jpg?fit=1398%2C1156
--------------------------------------------------------------------------------
/ref/metal_servo_horn_for_power_hd_giant_servo_hd_1235mg__pololu_2374__04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zignig/cqparts_bucket/HEAD/ref/metal_servo_horn_for_power_hd_giant_servo_hd_1235mg__pololu_2374__04.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Cqparts Bucket
2 | Simon Kirkby 2018 obeygiantrobot@gmail.com
3 |
4 | This is a bucket to put some cqparts musing into.
5 |
6 | Cqparts [https://github.com/fragmuffin/cqparts]
7 | It is all APACHE2 licenced so have at it.
8 |
9 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | go through and clean out all the examples
2 |
3 | rectangle packing
4 | https://www.codeproject.com/Articles/210979/Fast-optimizing-rectangle-packing-algorithm-for-bu
5 |
6 | #SVG EXPORT
7 |
8 | https://github.com/dcowden/cadquery/blob/master/cadquery/freecad_impl/exporters.py
9 |
10 | rewrite to just export hiddens.
11 |
12 |
13 | use modulefinder to extract all the needed modules
14 |
15 |
--------------------------------------------------------------------------------
/dc_array.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cqparts_motors import dc
4 |
5 |
6 | if __name__ == "__main__":
7 | from cqparts.display import display
8 | import json
9 |
10 | d = dc.DCMotor()
11 | display(d)
12 |
13 | def go():
14 | d.build()
15 | display(d)
16 |
17 | def export():
18 | print(json.dumps(d.serialize_parameters(), indent=4))
19 |
--------------------------------------------------------------------------------
/merged/templates/show.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 | {{ item }}
13 |
14 |
--------------------------------------------------------------------------------
/merged/templates/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% for n in items%}
6 | {% if n.leaf %}
7 | -
8 | {{n.name}}
9 | {% if n.built == true %}[x]{% endif %}
10 |
11 | {% else %}
12 | -
13 | {{n.name}}
14 |
15 | {% endif %}
16 | {% endfor %}
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/arduino.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cadquery import Solid
4 | from cqparts.params import *
5 | from cqparts.display import render_props, display
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.constraint import Mate
8 | from cqparts.utils.geometry import CoordSystem
9 |
10 |
11 | if __name__ == "__main__":
12 | from cqparts.display import display
13 |
14 | p = Arduino()
15 | display(p)
16 |
--------------------------------------------------------------------------------
/motor.py:
--------------------------------------------------------------------------------
1 | # base motor class
2 | #
3 |
4 | # 2018 Simon Kirkby obeygiantrobot@gmail.com
5 |
6 | import cqparts
7 |
8 | # base motor class
9 | # TODO lift all motor things up to here
10 |
11 |
12 | class Motor(cqparts.Assembly):
13 | def mount_points(self):
14 | raise NotImplementedError("mount_points function not implemented")
15 |
16 | def get_shaft(self):
17 | raise NotImplementedError("get_shaft function not implemented")
18 |
--------------------------------------------------------------------------------
/atx.py:
--------------------------------------------------------------------------------
1 | """
2 | Generic Coupling
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 |
14 |
15 | class Atx(cqparts.Assembly):
16 | pass
17 |
18 |
19 | if __name__ == "__main__":
20 | from cqparts.display import display
21 |
22 | B = Atx()
23 | display(B)
24 |
--------------------------------------------------------------------------------
/archive/graphql-test/app.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from flask import Flask
3 | from graphene import ObjectType, String, Schema
4 | from flask_graphql import GraphQLView
5 |
6 |
7 | class Query(ObjectType):
8 | hello = String(description="Hello")
9 |
10 | def resolve_hello(self, args, context, info):
11 | return "World"
12 |
13 |
14 | view_func = GraphQLView.as_view("graphql", graphiql=True, schema=Schema(query=Query))
15 |
16 | app = Flask(__name__)
17 | app.add_url_rule("/graphql", view_func=view_func)
18 |
19 |
20 | if __name__ == "__main__":
21 | app.run(debug=True)
22 |
--------------------------------------------------------------------------------
/partref.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | PartRef is used all through this bucket
4 |
5 | factor out and make sure that it can be serialized
6 | """
7 |
8 | from cqparts.params import Parameter
9 |
10 |
11 | class PartRef(Parameter):
12 | def type(self, value):
13 | return value
14 |
15 | @classmethod
16 | def serialize(cls, value):
17 | return cls.__name__
18 |
19 |
20 | # TODO check if it is an instance
21 | class PartInst(Parameter):
22 | def type(self, value):
23 | return value
24 |
25 | @classmethod
26 | def serialize(cls, value):
27 | return cls.__name__
28 |
--------------------------------------------------------------------------------
/base_box.py:
--------------------------------------------------------------------------------
1 | """
2 | Basic box
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 |
14 |
15 | class MyBox(cqparts.Part):
16 | size = PositiveFloat(10)
17 |
18 | def make(self):
19 | b = cq.Workplane("XY").box(self.size, self.size, self.size)
20 | return b
21 |
22 |
23 | if __name__ == "__main__":
24 | b = MyBox()
25 | display(b)
26 |
--------------------------------------------------------------------------------
/ser.py:
--------------------------------------------------------------------------------
1 | """
2 | """
3 |
4 | import cadquery as cq
5 |
6 | import cqparts
7 | from cqparts.params import PositiveFloat
8 |
9 |
10 | class sertest(cqparts.Part):
11 | length = PositiveFloat(24, doc="")
12 | diam = PositiveFloat(5, doc="diameter")
13 |
14 | def make(self):
15 | ob = cq.Workplane("XY").circle(self.diam / 2).extrude(self.length)
16 | return ob
17 |
18 |
19 | print("make an instace")
20 | a = sertest(length=100)
21 | print("serialize")
22 | b = a.serialize()
23 | print("data")
24 | print(b)
25 | c = cqparts.params.ParametricObject.deserialize(b)
26 | print(c)
27 | d = type(c)
28 | print(d, d())
29 |
--------------------------------------------------------------------------------
/threaded.py:
--------------------------------------------------------------------------------
1 | """
2 | Generic Coupling
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 | from cqparts_motors.shaft import Shaft
14 | from cqparts.search import register
15 |
16 |
17 | # this is just a shaft with a different colour
18 | @register(export="shaft")
19 | class Threaded(Shaft):
20 | _render = render_props(color=(75, 5, 50))
21 |
22 |
23 | if __name__ == "__main__":
24 | from cqparts.display import display
25 |
26 | B = Threaded()
27 | display(B)
28 |
--------------------------------------------------------------------------------
/archive/helix_test.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cadquery import Solid
4 | from cqparts.params import *
5 | from cqparts.display import render_props, display
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.constraint import Mate
8 | from cqparts.utils.geometry import CoordSystem
9 |
10 |
11 | class helix(cqparts.Part):
12 | # Parameters
13 | rad = PositiveFloat(10)
14 | spacing = PositiveFloat(40)
15 |
16 | def make(self):
17 | helix = cq.Wire.makeHelix(pitch=1, height=1, radius=1)
18 | sp = cq.Workplane("XY").circle(1).sweep(helix)
19 | return h
20 |
21 |
22 | if __name__ == "__main__":
23 | from cqparts.display import display
24 |
25 | B = helix()
26 | display(B)
27 |
--------------------------------------------------------------------------------
/ref/flux_capacitor.jpg:
--------------------------------------------------------------------------------
1 | --2018-06-19 10:43:11-- https://i1.wp.com/notanygadgets.com/wp-content/uploads/2016/07/Back-to-the-Future-Flux-Capacitor-USB-Car-Charger1.jpg?fit=1398%2C1156
2 | Resolving i1.wp.com (i1.wp.com)... 192.0.77.2
3 | Connecting to i1.wp.com (i1.wp.com)|192.0.77.2|:443... connected.
4 | HTTP request sent, awaiting response... 200 OK
5 | Length: 88313 (86K) [image/jpeg]
6 | Saving to: 'Back-to-the-Future-Flux-Capacitor-USB-Car-Charger1.jpg?fit=1398%2C1156’
7 |
8 | 0K .......... .......... .......... .......... .......... 57% 160K 0s
9 | 50K .......... .......... .......... ...... 100% 794K=0.4s
10 |
11 | 2018-06-19 10:43:12 (241 KB/s) - 'Back-to-the-Future-Flux-Capacitor-USB-Car-Charger1.jpg?fit=1398%2C1156’ saved [88313/88313]
12 |
13 |
--------------------------------------------------------------------------------
/idler.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cadquery import Solid
4 | from cqparts.params import *
5 | from cqparts.display import render_props, display
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.constraint import Mate
8 | from cqparts.utils.geometry import CoordSystem
9 |
10 | from cqparts_bearings.ball import BallBearing
11 | from cqparts_motors.shaft import Shaft
12 |
13 |
14 | class Idler(cqparts.Assembly):
15 | def make_components(self):
16 | comps = {"shaft": Shaft()}
17 | return comps
18 |
19 | def make_constraints(self):
20 | return [Fixed(self.components["shaft"].mate_origin)]
21 |
22 |
23 | if __name__ == "__main__":
24 | from cqparts.display import display
25 |
26 | B = Idler()
27 | display(B)
28 |
--------------------------------------------------------------------------------
/manufacture.py:
--------------------------------------------------------------------------------
1 | """
2 | Super class for printable objects
3 |
4 | extract printable objects via isinstance
5 | """
6 |
7 | import cqparts
8 | from cqparts.params import *
9 |
10 |
11 | class Printable(cqparts.Part):
12 | _printable = True
13 | _material = "default"
14 | clearance = PositiveFloat(0.2)
15 |
16 | # make cutout available to all sub classes
17 | def make_cutout(self, part):
18 | self.local_obj.cut((part.world_coords - self.world_coords) + part.cutout())
19 |
20 | def crossX(self):
21 | print(self.world_coords)
22 | self.local_obj.transformed(rotate=(0, 90, 0), offset=(0, 0, 0)).split(
23 | keepTop=True
24 | ) # ((part.world_coords-self.world_coords)+part.cutout())
25 |
26 |
27 | class Lasercut(cqparts.Part):
28 | pass
29 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # INIT ME
2 |
3 | _version = "0.1a"
4 | _export = True
5 | _namespace = "user/zignig"
6 | __all__ = [
7 | "mill",
8 | "shell_test",
9 | "turntable",
10 | "project_case",
11 | "controller",
12 | "battery",
13 | "wheel",
14 | "case",
15 | "mercanum",
16 | "handle",
17 | "flux_capacitor",
18 | "bucket",
19 | "stepper",
20 | "dc",
21 | "motor_mount",
22 | "electronics",
23 | "pan_tilt",
24 | "sonar",
25 | "cnc",
26 | "beard_boss",
27 | "robot_base",
28 | "linear_bearing",
29 | "box",
30 | "flip_box",
31 | "pencil_case",
32 | "open_box",
33 | "servo",
34 | "servo_horns",
35 | "car",
36 | "train",
37 | "rocket_drone",
38 | "peg",
39 | "threaded",
40 | "partref",
41 | ]
42 |
--------------------------------------------------------------------------------
/calculations.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Wed Jun 13 09:44:59 2018
4 |
5 | @author: simonk
6 | """
7 |
8 | import math
9 |
10 |
11 | # ref for tangents
12 | # https://en.wikipedia.org/wiki/Tangent_lines_to_circles
13 | def CalcTangents(p1, r1, p2, r2):
14 | x1 = p1[0]
15 | y1 = p1[1]
16 | x2 = p2[0]
17 | y2 = p2[1]
18 | half_pi = math.pi / 2.0
19 | gamma = -math.atan((y2 - y1) / (x2 - x1))
20 | beta = math.asin((r2 - r1) / math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2))
21 | alpha = gamma - beta
22 | x3 = x1 + r1 * math.cos(half_pi - alpha)
23 | y3 = y1 + r1 * math.sin(half_pi - alpha)
24 | x4 = x2 + r2 * math.cos(half_pi - alpha)
25 | y4 = y2 + r2 * math.sin(half_pi - alpha)
26 | p1 = (x3, y3)
27 | p2 = (x4, y4)
28 | # returns a quad
29 | pts = [p1, (p1[0], -p1[1]), (p2[0], -p2[1]), p2, p1]
30 | return pts
31 |
--------------------------------------------------------------------------------
/plank.py:
--------------------------------------------------------------------------------
1 | " A plank for mounting stuff on "
2 |
3 | import cadquery as cq
4 | import cqparts
5 | from cqparts.params import *
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts_misc.basic.primatives import Box
8 | from cqparts.display import render_props
9 |
10 | from .manufacture import Lasercut
11 |
12 |
13 | class Plank(Box, Lasercut):
14 | length = PositiveFloat(90)
15 | width = PositiveFloat(90)
16 | thickness = PositiveFloat(3)
17 | _render = render_props(template="wood")
18 | fillet = PositiveFloat(3)
19 |
20 | def make(self):
21 | pl = cq.Workplane("XY").box(self.length, self.width, self.thickness)
22 | pl = pl.translate((0, 0, self.thickness / 2))
23 | if self.fillet > 0:
24 | pl = pl.edges("|Z").fillet(self.fillet)
25 | return pl
26 |
27 |
28 | if __name__ == "__main__":
29 | from cqparts.display import display
30 |
31 | display(Plank())
32 |
--------------------------------------------------------------------------------
/gear.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cadquery import Solid
4 | from cqparts.params import *
5 | from cqparts.display import render_props, display
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.constraint import Mate
8 | from cqparts.utils.geometry import CoordSystem
9 | from cqparts_gears import trapezoidal
10 |
11 |
12 | class TestGear(trapezoidal.TrapezoidalGear):
13 | effective_radius = PositiveFloat(90)
14 | tooth_count = PositiveInt(20)
15 | width = PositiveFloat(20)
16 |
17 |
18 | class GearStack(cqparts.Part):
19 | post = PositiveFloat(20)
20 | offset = PositiveFloat(30)
21 |
22 | def make(self):
23 | wp = cq.Workplane("XY")
24 | post = wp.circle(self.post).extrude(self.offset)
25 | g1 = TestGear().make()
26 | g2 = (
27 | TestGear(tooth_count=14, effective_radius=30)
28 | .make()
29 | .translate((0, 0, self.offset))
30 | )
31 | post = post.union(g1)
32 | post = post.union(g2)
33 | return post
34 |
35 |
36 | if __name__ == "__main__":
37 | from cqparts.display import display
38 |
39 | g = GearStack()
40 | display(g)
41 |
--------------------------------------------------------------------------------
/template.py:
--------------------------------------------------------------------------------
1 | """
2 | template object
3 | # 2018 Simon Kirkby obeygiantrobot@gmail.com
4 | """
5 |
6 |
7 | import cadquery as cq
8 |
9 | import cqparts
10 | from cqparts.params import PositiveFloat
11 | from cqparts.display import render_props
12 | from cqparts.constraint import Mate
13 | from cqparts.utils.geometry import CoordSystem
14 | from cqparts.search import register
15 |
16 |
17 | class Template(cqparts.Part):
18 | length = PositiveFloat(24)
19 | diam = PositiveFloat(5)
20 |
21 | _render = render_props(color=(50, 255, 255))
22 |
23 | def make(self):
24 | shft = cq.Workplane("XY").circle(self.diam / 2).extrude(self.length)
25 | return shft
26 |
27 | def cut_out(self):
28 | cutout = cq.Workplane("XY").circle(self.diam / 2).extrude(self.length)
29 | return cutout
30 |
31 | def get_cutout(self, clearance=0):
32 | " clearance cut out for shaft "
33 | return (
34 | cq.Workplane("XY", origin=(0, 0, 0))
35 | .circle((self.diam / 2) + clearance)
36 | .extrude(self.length * 2)
37 | )
38 |
39 | def mate_tip(self, offset=0):
40 | return Mate(
41 | self,
42 | CoordSystem(origin=(0, 0, self.length), xDir=(1, 0, 0), normal=(0, 0, 1)),
43 | )
44 |
--------------------------------------------------------------------------------
/sonar.py:
--------------------------------------------------------------------------------
1 | """
2 | Sonar sensor model
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 | from cqparts.search import register
14 |
15 | from .controller import PCBBoard
16 |
17 |
18 | class Emmitter(cqparts.Part):
19 | height = PositiveFloat(14.8)
20 | radius = PositiveFloat(8)
21 |
22 | def make(self):
23 | em = cq.Workplane("XY").circle(self.radius).extrude(self.height)
24 | return em
25 |
26 |
27 | @register(export="sensor")
28 | class Sonar(PCBBoard):
29 | length = PositiveFloat(45)
30 | width = PositiveFloat(20)
31 | corner_radius = PositiveFloat(0)
32 | thickness = PositiveFloat(1.2)
33 | hole_size = PositiveFloat(1.0)
34 |
35 | def make(self):
36 | obj = super(Sonar, self).make()
37 | em1 = Emmitter().local_obj.translate((13.5, 0, 0))
38 | em2 = Emmitter().local_obj.translate((-13.5, 0, 0))
39 | obj = obj.union(em1)
40 | obj = obj.union(em2)
41 | return obj
42 |
43 |
44 | if __name__ == "__main__":
45 | from cqparts.display import display
46 |
47 | # em = Emmitter()
48 | em = Sonar()
49 | display(em)
50 |
--------------------------------------------------------------------------------
/involute.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cadquery import Solid
4 | from cqparts.params import *
5 | from cqparts.display import render_props, display
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.constraint import Mate
8 | from cqparts.utils.geometry import CoordSystem
9 |
10 |
11 | class Tooth(cqparts.Part):
12 | thickness = PositiveFloat(3)
13 | size = PositiveFloat(2)
14 |
15 | def make(self):
16 | wp = cq.Workplane("XY")
17 | t = wp.rect(self.size, self.size).extrude(self.thickness)
18 | return t
19 |
20 |
21 | class Involute(cqparts.Part):
22 | module = PositiveFloat(0.4)
23 | teeth = Int(13)
24 | thickness = PositiveFloat(2)
25 |
26 | def initialize_parameters(self):
27 | self.rad = self.module * self.teeth
28 |
29 | def make(self):
30 | wp = cq.Workplane("XY")
31 | post = wp.circle(self.rad).extrude(self.thickness)
32 | inc = 360.0 / self.teeth
33 | for i in range(self.teeth):
34 | print(inc * i)
35 | t = Tooth(thickness=self.thickness).local_obj
36 | t = t.translate((self.rad, 0, 0))
37 | t = t.rotate((0, 0, 0), (0, 0, 1), i * inc)
38 | post = post.union(t)
39 | return post
40 |
41 |
42 | if __name__ == "__main__":
43 | from cqparts.display import display
44 |
45 | g = Involute()
46 | display(g)
47 |
--------------------------------------------------------------------------------
/coupling.py:
--------------------------------------------------------------------------------
1 | """
2 | Generic Coupling
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 |
14 |
15 | class Coupling(cqparts.Part):
16 | length = PositiveFloat(24)
17 | outer_diam = PositiveFloat(9)
18 | inner_diam_A = PositiveFloat(5)
19 | inner_diam_B = PositiveFloat(5)
20 | gap = PositiveFloat(3)
21 |
22 | _render = render_props(color=(75, 75, 50))
23 |
24 | def make(self):
25 | lm = (
26 | cq.Workplane("XY")
27 | .workplane(offset=-self.length / 2)
28 | .circle(self.outer_diam / 2)
29 | .circle(self.inner_diam_A / 2)
30 | .extrude(self.length)
31 | )
32 | return lm
33 |
34 | def mate_input(self, offset=0):
35 | return Mate(
36 | self,
37 | CoordSystem(origin=(0, 0, -self.gap / 2), xDir=(1, 0, 0), normal=(0, 0, 1)),
38 | )
39 |
40 | def mate_output(self, offset=0):
41 | return Mate(
42 | self,
43 | CoordSystem(origin=(0, 0, self.gap / 2), xDir=(1, 0, 0), normal=(0, 0, 1)),
44 | )
45 |
46 |
47 | if __name__ == "__main__":
48 | from cqparts.display import display
49 |
50 | B = Coupling()
51 | display(B)
52 |
--------------------------------------------------------------------------------
/shell_test.py:
--------------------------------------------------------------------------------
1 | """
2 | Shell test
3 | # 2019 Simon Kirkby obeygiantrobot@gmail.com
4 | """
5 |
6 |
7 | import cadquery as cq
8 |
9 | import cqparts
10 | from cqparts.params import PositiveFloat, Int
11 | from cqparts.display import render_props
12 | from cqparts.constraint import Mate
13 | from cqparts.utils.geometry import CoordSystem
14 | from cqparts.search import register
15 |
16 | # base shaft type
17 | @register(export="misc")
18 | class Shell(cqparts.Part):
19 | length = PositiveFloat(124, doc="shaft length")
20 | diam = PositiveFloat(40, doc="shaft diameter")
21 | count = Int(5)
22 |
23 | def make(self):
24 | shft = cq.Workplane("XY").circle(self.diam / 2).extrude(self.length)
25 | inc = 360.0 / float(self.count)
26 | for i in range(self.count):
27 | b = cq.Workplane("XY").circle(self.diam / 4).extrude(self.length / 2)
28 | b = b.translate((self.diam / 2, 0, self.length / 8))
29 | b = b.rotate((0, 0, 0), (0, 0, 1), float(i * inc))
30 | shft = shft.union(b)
31 | c = cq.Workplane("XY").circle(self.diam / 8).extrude(self.length - 6)
32 | c = c.translate((self.diam / 2, 0, 0))
33 | c = c.rotate((0, 0, 0), (0, 0, 1), float(i * inc))
34 | shft = shft.union(c)
35 | shft = shft.faces(">Z").shell(-1)
36 | return shft
37 |
38 |
39 | if __name__ == "__main__":
40 | from cqparts.display import display
41 |
42 | s = Shell()
43 | display(s)
44 |
--------------------------------------------------------------------------------
/boxed_board.py:
--------------------------------------------------------------------------------
1 |
2 | import cqparts
3 | from cqparts.params import *
4 | from cqparts.display import display
5 | from cqparts.constraint import Fixed, Coincident
6 | from cqparts.constraint import Mate
7 | from cqparts.utils import CoordSystem
8 | from cqparts_fasteners.screws import Screw
9 | from .mounted_board import ArduinoBoard,PizeroBoard
10 | from .case import Case
11 |
12 | from .partref import PartRef
13 |
14 | class BoxedBoard(cqparts.Assembly):
15 | clearance = PositiveFloat(12)
16 | case = Case(thickness=3, height=30, screw=Screw)
17 | # board = Pizero()
18 | board = PartRef(PizeroBoard)
19 |
20 | def initialize_parameters(self):
21 | self.coll = []
22 |
23 | def make_components(self):
24 | board = self.board()
25 | bb = board.bounding_box
26 | items = {"case": self.case, "board": board}
27 | # modify the box to fit the board
28 |
29 | self.case.length = board.length + 2 * self.clearance
30 | self.case.width = board.width + 2 * self.clearance
31 | self.case.explode = 0.0
32 | self.case.screw.length = PositiveFloat(15.0)
33 | return items
34 |
35 | def make_constraints(self):
36 | constraints = [
37 | Fixed(self.components["case"].mate_origin),
38 | Fixed(self.components["board"].mate_origin),
39 | ]
40 | return constraints
41 |
42 |
43 | if __name__ == "__main__":
44 | from cqparts.display import display
45 | bb = BoxedBoard()
46 | display(bb)
47 |
--------------------------------------------------------------------------------
/belt.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cadquery import Solid
4 | from cqparts.params import *
5 | from cqparts.display import render_props, display
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.constraint import Mate
8 | from cqparts.utils.geometry import CoordSystem
9 |
10 |
11 | class Belt(cqparts.Part):
12 | # Parameters
13 | rad = PositiveFloat(10)
14 | spacing = PositiveFloat(100)
15 |
16 | belt_width = PositiveFloat(5)
17 | belt_thickness = PositiveFloat(1)
18 |
19 | # default appearance
20 | _render = render_props(template="red")
21 |
22 | def profile(self):
23 | p = cq.Workplane("XZ").rect(self.belt_width, self.belt_thickness)
24 | return p
25 |
26 | def make(self):
27 | outer = self.profile().extrude(self.spacing).translate((0, 0, -self.rad))
28 | p2 = (
29 | self.profile()
30 | .revolve(180, (2, self.rad), (1, self.rad))
31 | .translate((0, 0, -self.rad))
32 | )
33 | outer = outer.union(p2)
34 | p3 = self.profile().extrude(self.spacing).translate((0, 0, self.rad))
35 | outer = outer.union(p3)
36 | p4 = (
37 | self.profile()
38 | .revolve(180, (-2, self.rad), (1, self.rad))
39 | .translate((0, -self.spacing, -self.rad))
40 | )
41 | outer = outer.union(p4)
42 | return outer
43 |
44 |
45 | if __name__ == "__main__":
46 | from cqparts.display import display
47 |
48 | B = Belt()
49 | display(B)
50 |
--------------------------------------------------------------------------------
/case_arr.py:
--------------------------------------------------------------------------------
1 |
2 | import cqparts
3 | from cqparts.display import display
4 | from cqparts.constraint import Fixed, Coincident
5 | from cqparts.constraint import Mate
6 | from cqparts.utils import CoordSystem
7 |
8 | from .case import Case
9 | from cqparts_fasteners.screws import Screw
10 |
11 |
12 | class Boxes(cqparts.Assembly):
13 | def initialize_parameters(self):
14 | self.coll = []
15 | self.offset = 105
16 |
17 | def add(self, i):
18 | self.coll.append(i)
19 |
20 | @classmethod
21 | def item_name(cls, index):
22 | return "item_%03i" % index
23 |
24 | def make_components(self):
25 | items = {}
26 | for i in range(len(self.coll)):
27 | items[self.item_name(i)] = self.coll[i]
28 | return items
29 |
30 | def make_constraints(self):
31 | constraints = []
32 | length = len(self.coll)
33 | total = length * self.offset
34 | for i in range(len(self.coll)):
35 | constraints.append(
36 | Fixed(
37 | self.coll[i].mate_origin,
38 | CoordSystem(
39 | origin=(0, i * self.offset - (total / 2), 0),
40 | xDir=(1, 0, 0),
41 | normal=(0, 0, 1),
42 | ),
43 | )
44 | )
45 |
46 | return constraints
47 |
48 |
49 | ar = Boxes()
50 | for i in range(5, 10):
51 | c = Case(length=i * 10 + 20, height=i * 20, screw=Screw)
52 | ar.add(c)
53 |
54 | display(ar)
55 |
--------------------------------------------------------------------------------
/extraction_test.py:
--------------------------------------------------------------------------------
1 | """
2 | Test for part extraction on assemblies
3 | """
4 |
5 | # TODO printable parts should have a mate_print for the correct orientation
6 |
7 | import cqparts
8 |
9 | from .manufacture import Printable, Lasercut
10 | from .robot_base import Rover
11 |
12 | from .flux_capacitor import CompleteFlux
13 |
14 |
15 | class Extractor(cqparts.Assembly):
16 | parts = []
17 | printable = {}
18 |
19 | def scan(self, obj):
20 | if isinstance(obj, cqparts.Part):
21 | if isinstance(obj, Printable):
22 | if obj._material in self.printable:
23 | self.printable[obj._material].append(obj)
24 | else:
25 | self.printable[obj._material] = [obj]
26 | else:
27 | self.parts.append(obj)
28 | if isinstance(obj, cqparts.Assembly):
29 | for i in obj.components:
30 | self.scan(obj.components[i])
31 |
32 | def show(self):
33 | # print("Parts")
34 | # for i in self.parts:
35 | # print(i.__class__)
36 | print("Printable")
37 | print(self.printable)
38 |
39 | def get_printable(self):
40 | return self.printable
41 |
42 |
43 | m = CompleteFlux()
44 | # m = Rover()
45 | # m = Case()
46 |
47 | # Extract the printables
48 | e = Extractor()
49 | e.scan(m)
50 | e.show()
51 | p = e.get_printable()
52 |
53 | from .multi import Arrange
54 |
55 | if __name__ == "__main__":
56 | from cqparts.display import display
57 |
58 | ar = Arrange(offset=100)
59 | for i in p["red_abs"]:
60 | ar.add(i)
61 | display(ar)
62 |
--------------------------------------------------------------------------------
/control_panel.py:
--------------------------------------------------------------------------------
1 | """
2 | Control Panel Construct
3 | # 2018 Simon Kirkby obeygiantrobot@gmail.com
4 | """
5 |
6 |
7 | import cadquery as cq
8 |
9 | import cqparts
10 | from cqparts.params import PositiveFloat
11 | from cqparts.display import render_props
12 | from cqparts.constraint import Mate
13 | from cqparts.utils.geometry import CoordSystem
14 | from cqparts.search import register
15 |
16 | from .partref import PartRef
17 |
18 | from .button import Button
19 | from .lcd import Lcd
20 | from .led import LED
21 |
22 |
23 | class ControlRow(cqparts.Assembly):
24 | length = PositiveFloat(100)
25 | width = PositiveFloat(100)
26 |
27 | def initialize_parameters(self):
28 | self.controls = []
29 |
30 | def add(self, i):
31 | self.controls.append(i)
32 |
33 | def populate(self):
34 | pass
35 |
36 | @classmethod
37 | def item_name(cls, index):
38 | return "item_%03i" % index
39 |
40 | def make_components(self):
41 | self.populate()
42 | items = {}
43 | for i in range(len(self.controls)):
44 | items[self.item_name(i)] = self.controls[i]
45 | return items
46 |
47 | def make_constraints(self):
48 | for i in self.controls:
49 | print(dir(i))
50 | print(i)
51 | return []
52 |
53 |
54 | class TestCR(ControlRow):
55 | def populate(self):
56 | self.add(Button())
57 | self.add(Button())
58 | # self.add(LED())
59 | # self.add(Lcd())
60 |
61 |
62 | if __name__ == "__main__":
63 | from cqparts.display import display
64 |
65 | # cp = ControlPanel()
66 | cp = TestCR()
67 | display(cp)
68 |
--------------------------------------------------------------------------------
/extract.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | import math
4 | from collections import OrderedDict
5 |
6 |
7 | from manufacture import Lasercut, Printable
8 |
9 | # makes an array of local objects
10 | class Extractor:
11 | def __init__(self, breakout=[Lasercut, Printable]):
12 | # for duplicate names
13 | self.track = {}
14 | self.parts = OrderedDict()
15 | self.breakout = OrderedDict()
16 | for i in breakout:
17 | section = i.__name__
18 | self.breakout[section] = i
19 | self.parts[section] = OrderedDict()
20 | self.parts["default"] = OrderedDict()
21 |
22 | def scan(self, obj, name):
23 | if isinstance(obj, cqparts.Part):
24 | if name in self.track:
25 | actual_name = name + "_%03i" % self.track[name]
26 | self.track[name] += 1
27 | else:
28 | self.track[name] = 1
29 | actual_name = name
30 | for i, j in self.breakout.items():
31 | if isinstance(obj, j):
32 | self.parts[i][actual_name] = obj
33 | return
34 | self.parts["default"][actual_name] = obj
35 |
36 | if isinstance(obj, cqparts.Assembly):
37 | for i in obj.components:
38 | self.scan(obj.components[i], i)
39 |
40 | def show(self):
41 | for j in self.parts:
42 | i = self.parts[j]
43 | area = i.bounding_box.xlen * i.bounding_box.ylen
44 | print(i.__class__, i.bounding_box.xlen, i.bounding_box.ylen, area)
45 |
46 | def get_parts(self):
47 | return self.parts
48 |
--------------------------------------------------------------------------------
/archive/blender/render_test.py:
--------------------------------------------------------------------------------
1 |
2 | # using
3 | # https://github.com/ksons/gltf-blender-importer
4 | import bpy
5 |
6 | # snippit , not working script
7 |
8 | bpy.ops.wm.addon_enable(module="io_scene_gltf")
9 | # maybe script build the entire scene
10 | bpy.ops.scene.new(type="NEW")
11 | bpy.context.scene.name = "cqparts"
12 | # make the world
13 | bpy.ops.world.new()
14 | world = bpy.data.worlds["World.001"]
15 | world.name = "NewWorld"
16 | world.light_settings.use_ambient_occlusion = True
17 |
18 | bpy.context.scene.world = world
19 |
20 | theScene = bpy.data.scenes["cqparts"]
21 | theScene.render.filepath = "/opt/stash/image.png"
22 |
23 | # make and bind the camera
24 | bpy.ops.object.camera_add()
25 | cam = bpy.context.selected_objects[0]
26 | bpy.context.scene.camera = cam
27 | cam.location = (4, -20, 20)
28 | # add the track
29 | bpy.ops.object.constraint_add(type="TRACK_TO")
30 |
31 | # make the target and track the camera
32 | bpy.ops.object.empty_add(type="SPHERE")
33 | tgt = bpy.context.selected_objects[0]
34 | tgt.name = "cam_target"
35 | # select the camers
36 | track = cam.constraints["Track To"]
37 | track.target = bpy.data.objects["cam_target"]
38 | track.up_axis = "UP_Y"
39 | track.track_axis = "TRACK_NEGATIVE_Z"
40 |
41 |
42 | # lamp 1
43 | bpy.ops.object.lamp_add(type="POINT")
44 | lamp = bpy.context.selected_objects[0]
45 | lamp.location = (10, -10, 10)
46 |
47 | # lamp 2
48 | bpy.ops.object.lamp_add(type="POINT")
49 | lamp2 = bpy.context.selected_objects[0]
50 | lamp2.location = (-10, -10, 10)
51 |
52 | bpy.ops.import_scene.gltf(filepath="/opt/stash/CompleteFlux/out.gltf")
53 | outer = theScene.objects["out"]
54 | outer.scale = (100, 100, 100)
55 |
56 | bpy.ops.render.render(write_still=True)
57 |
--------------------------------------------------------------------------------
/handle.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Wed Jun 13 09:44:59 2018
4 |
5 | @author: simonk
6 | """
7 |
8 | # windy handle thing
9 | import cadquery as cq
10 | import cqparts
11 | from cqparts.params import PositiveFloat
12 | from cqparts.search import register
13 |
14 | from .calculations import CalcTangents
15 |
16 |
17 | @register(export="handle")
18 | class Handle(cqparts.Part):
19 | length = PositiveFloat(100)
20 | rad1 = PositiveFloat(25)
21 | rad2 = PositiveFloat(15)
22 | thickness = PositiveFloat(15)
23 | hole = PositiveFloat(12)
24 | handle_height = PositiveFloat(30)
25 |
26 | def make(self):
27 | b = cq.Workplane("XY").circle(self.rad1).extrude(self.thickness)
28 | b2 = (
29 | cq.Workplane("XY")
30 | .circle(self.rad2)
31 | .extrude(self.thickness)
32 | .translate((self.length, 0, 0))
33 | )
34 | b = b.union(b2)
35 | # generate the tangents
36 | pts = CalcTangents((0, 0), self.rad1, (self.length, 0), self.rad2)
37 | base = cq.Workplane("XY").polyline(pts).close().extrude(self.thickness)
38 | b = b.union(base)
39 | handle = (
40 | cq.Workplane("XY")
41 | .circle(self.hole)
42 | .extrude(self.handle_height)
43 | .translate((self.length, 0, self.thickness))
44 | )
45 | b = b.union(handle)
46 | b = b.faces("|Z").chamfer(1)
47 | h = cq.Workplane("XY").circle(self.rad2 * 0.8).extrude(self.thickness)
48 | b = b.cut(h)
49 | return b
50 |
51 |
52 | if __name__ == "__main__":
53 | from cqparts.display import display
54 |
55 | h = Handle()
56 | display(h)
57 |
--------------------------------------------------------------------------------
/pulley.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cadquery import Solid
4 | from cqparts.params import *
5 | from cqparts.display import render_props, display
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.constraint import Mate
8 | from cqparts.utils.geometry import CoordSystem
9 |
10 |
11 | class Pulley(cqparts.Part):
12 | # Parameters
13 | rad = PositiveFloat(9)
14 | thickness = PositiveFloat(5)
15 | rim = PositiveFloat(1)
16 | rim_thickness = PositiveFloat(0.5)
17 |
18 | boss_height = PositiveFloat(2)
19 | boss_rad = PositiveFloat(5)
20 | # default appearance
21 | # _render = render_props(template='tin')
22 | def make(self):
23 | p = (
24 | cq.Workplane("YZ")
25 | .circle(self.rad)
26 | .extrude(self.thickness)
27 | .translate((-self.thickness / 2, 0, 0))
28 | )
29 | top_rim = (
30 | cq.Workplane("YZ")
31 | .circle(self.rad + self.rim)
32 | .extrude(self.rim_thickness)
33 | .translate((-self.thickness / 2 - self.rim_thickness, 0, 0))
34 | )
35 | p = p.union(top_rim)
36 | bottom_rim = (
37 | cq.Workplane("YZ")
38 | .circle(self.rad + self.rim)
39 | .extrude(-self.rim_thickness)
40 | .translate((self.thickness / 2 + self.rim_thickness, 0, 0))
41 | )
42 | p = p.union(bottom_rim)
43 | # boss
44 |
45 | boss = (
46 | cq.Workplane("YZ")
47 | .circle(self.boss_rad)
48 | .extrude(self.boss_height)
49 | .translate((self.thickness / 2 + self.rim_thickness, 0, 0))
50 | )
51 | p = p.union(boss)
52 | return p
53 |
54 |
55 | if __name__ == "__main__":
56 | from cqparts.display import display
57 |
58 | B = Pulley()
59 | display(B)
60 |
--------------------------------------------------------------------------------
/led.py:
--------------------------------------------------------------------------------
1 | """
2 | Plastic LED
3 | # 2018 Simon Kirkby obeygiantrobot@gmail.com
4 | """
5 |
6 |
7 | import cadquery as cq
8 |
9 | import cqparts
10 | from cqparts.params import PositiveFloat
11 | from cqparts.display import render_props
12 | from cqparts.constraint import Mate
13 | from cqparts.utils.geometry import CoordSystem
14 | from cqparts.search import register
15 |
16 |
17 | class LED(cqparts.Part):
18 | length = PositiveFloat(4.0)
19 | diam = PositiveFloat(5.0)
20 | base_thickness = PositiveFloat(1.0)
21 | base_rim = PositiveFloat(1.0)
22 |
23 | _render = render_props(color=(200, 0, 0))
24 |
25 | def make(self):
26 | LED = (
27 | cq.Workplane("XZ")
28 | .lineTo(self.diam / 2, 0)
29 | .lineTo(self.diam / 2, self.length)
30 | .radiusArc((0, self.length + self.diam / 2), -self.diam / 2)
31 | .close()
32 | )
33 | LED = LED.revolve(axisStart=(0, 0, 0), axisEnd=(0, 1, 0))
34 | base = (
35 | cq.Workplane("XY")
36 | .circle((self.base_rim + self.diam) / 2)
37 | .extrude(-self.base_thickness)
38 | )
39 | mark = (
40 | cq.Workplane("XY")
41 | .rect(self.diam + self.base_rim, self.base_thickness)
42 | .extrude(-self.base_thickness)
43 | )
44 | mark = mark.translate((0, (self.base_rim + self.diam) / 2, 0))
45 | base = base.cut(mark)
46 | LED = LED.union(base)
47 | return LED
48 |
49 | def cut_out(self):
50 | cutout = cq.Workplane("XY").circle(self.diam / 2).extrude(self.length)
51 | return cutout
52 |
53 | def get_cutout(self, clearance=0):
54 | return (
55 | cq.Workplane("XY", origin=(0, 0, 0))
56 | .circle((self.diam / 2) + clearance)
57 | .extrude(self.length * 2)
58 | )
59 |
60 | def mate_base(self, offset=0):
61 | return Mate(
62 | self,
63 | CoordSystem(origin=(0, 0, self.length), xDir=(1, 0, 0), normal=(0, 0, 1)),
64 | )
65 |
66 |
67 | if __name__ == "__main__":
68 | from cqparts.display import display
69 |
70 | l = LED()
71 | display(l)
72 |
--------------------------------------------------------------------------------
/bork.py:
--------------------------------------------------------------------------------
1 | """
2 | Demo of weirdness
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 |
14 |
15 | # this is a demo of mate weirdness.
16 |
17 |
18 | class Box(cqparts.Part):
19 | _render = render_props(color=(75, 5, 50))
20 |
21 | def make(self):
22 | b = cq.Workplane("XY").box(10, 10, 10)
23 | return b
24 |
25 | @property
26 | def mate_top(self):
27 | return Mate(
28 | self, CoordSystem(origin=(0, 0, 5), xDir=(1, 0, 0), normal=(0, 0, 1))
29 | )
30 |
31 |
32 | class Cyl(cqparts.Part):
33 | def make(self):
34 | c = cq.Workplane("XY").circle(5).extrude(10)
35 | return c
36 |
37 | @property
38 | def mate_top(self):
39 | return Mate(
40 | self, CoordSystem(origin=(0, 0, 5), xDir=(1, 0, 0), normal=(0, 0, 1))
41 | )
42 |
43 |
44 | class stack(cqparts.Assembly):
45 | def make_components(self):
46 | comps = {"box": Box(), "cyl": Cyl()}
47 | return comps
48 |
49 | def make_constraints(self):
50 | constr = [
51 | Fixed(self.components["box"].mate_origin),
52 | Coincident(
53 | self.components["cyl"].mate_origin, self.components["box"].mate_top
54 | ),
55 | ]
56 | return constr
57 |
58 |
59 | class broken(cqparts.Assembly):
60 | def make_components(self):
61 | a = stack()
62 | b = stack()
63 | comps = {"a": a, "b": b}
64 | t = b.components["box"]
65 | return comps
66 |
67 | def make_constraints(self):
68 | constr = [
69 | Fixed(self.components["a"].mate_origin),
70 | Coincident(
71 | self.components["b"].mate_origin,
72 | Mate(self, CoordSystem(origin=(20, 0, 0))),
73 | ),
74 | ]
75 | return constr
76 |
77 |
78 | if __name__ == "__main__":
79 | from cqparts.display import display
80 |
81 | B = broken()
82 | display(B)
83 |
--------------------------------------------------------------------------------
/bucket.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cadquery import Solid
4 | from cqparts.params import *
5 | from cqparts.display import render_props, display
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.constraint import Mate
8 | from cqparts.utils.geometry import CoordSystem
9 | from cqparts.search import register
10 |
11 |
12 | @register(export="misc")
13 | class Bucket(cqparts.Part):
14 | # Parameters
15 | diambot = PositiveFloat(10)
16 | diamtop = PositiveFloat(12)
17 | height = PositiveFloat(30)
18 | thickness = PositiveFloat(0.4)
19 | rimHeight = PositiveFloat(1)
20 |
21 | # default appearance
22 | _render = render_props(template="tin")
23 |
24 | def make(self):
25 | # outer bucket
26 | outer = (
27 | cq.Workplane("XY")
28 | .circle(self.diambot)
29 | .workplane(offset=self.height)
30 | .circle(self.diamtop)
31 | .loft(filled=True, combine=True)
32 | )
33 | # inner bucket
34 | inner = (
35 | cq.Workplane("XY")
36 | .workplane(offset=self.thickness)
37 | .circle(self.diambot - self.thickness)
38 | .workplane(offset=self.height)
39 | .circle(self.diamtop - self.thickness)
40 | .loft(filled=True, combine=True)
41 | )
42 | upperRim = (
43 | cq.Workplane("XY")
44 | .workplane(offset=self.height - self.rimHeight)
45 | .circle(self.diamtop + self.thickness)
46 | .extrude(self.rimHeight)
47 | )
48 | outer = outer.union(upperRim)
49 | outer = outer.cut(inner)
50 |
51 | lowerRim = cq.Workplane("XY").circle(self.diambot).extrude(-self.rimHeight)
52 | lowerRimCut = (
53 | cq.Workplane("XY")
54 | .circle(self.diambot - self.thickness)
55 | .extrude(-self.rimHeight)
56 | )
57 | lowerRim = lowerRim.cut(lowerRimCut)
58 | outer = outer.union(lowerRim)
59 | outer.chamfer(self.thickness / 3)
60 | return outer
61 |
62 |
63 | if __name__ == "__main__":
64 | from cqparts.display import display
65 |
66 | B = Bucket()
67 | display(B)
68 |
--------------------------------------------------------------------------------
/open_box.py:
--------------------------------------------------------------------------------
1 | """
2 | OPen box
3 | Subclass test for Boxen
4 | """
5 |
6 | import cadquery as cq
7 | import cqparts
8 | from cqparts.params import *
9 | from cqparts.constraint import Fixed, Coincident
10 | from cqparts.utils.geometry import CoordSystem
11 | from cqparts.constraint import Mate
12 | from cqparts.search import register
13 |
14 | from . import box
15 |
16 |
17 | class T2(box._Tab):
18 | count = Int(3)
19 |
20 |
21 | class Front(box.Front):
22 | tabs_on = box.BoolList([True, True, True, False])
23 |
24 | def initialize_parameters(self):
25 | self.length = self.length + self.thickness
26 |
27 |
28 | class Back(box.Back):
29 | tabs_on = box.BoolList([True, True, True, False])
30 |
31 | def initialize_parameters(self):
32 | self.length = self.length + self.thickness
33 |
34 |
35 | @register(export="box")
36 | class OpenBox(box.Boxen):
37 | # Pass down subclassed faces
38 | top = box.PartRef(None)
39 | front = box.PartRef(Front)
40 | back = box.PartRef(Back)
41 | proportion = PositiveFloat(0)
42 | tab = box.PartRef(T2)
43 |
44 | def mate_top(self, lift=0):
45 | return Mate(
46 | self, CoordSystem(origin=(0, 0, lift), xDir=(1, 0, 0), normal=(0, 0, -1))
47 | )
48 |
49 | def make_alterations(self):
50 | super(OpenBox, self).make_alterations()
51 |
52 |
53 | class SmallBox(cqparts.Assembly):
54 | length = PositiveFloat(60)
55 | width = PositiveFloat(60)
56 | height = PositiveFloat(40)
57 |
58 | proportion = PositiveFloat(0.5)
59 |
60 | def make_components(self):
61 | comps = {
62 | "top": OpenBox(length=self.length, width=self.width, height=self.height),
63 | "bottom": OpenBox(length=self.length, width=self.width, height=self.height),
64 | }
65 | return comps
66 |
67 | def make_constraints(self):
68 | constr = [
69 | Fixed(self.components["bottom"].mate_origin),
70 | Fixed(self.components["top"].mate_top(lift=self.height)),
71 | ]
72 | return constr
73 |
74 |
75 | if __name__ == "__main__":
76 | from cqparts.display import display
77 |
78 | FB = OpenBox()
79 | # FB = SmallBox(proportion=0.7)
80 | display(FB)
81 |
--------------------------------------------------------------------------------
/shaft.py:
--------------------------------------------------------------------------------
1 | """
2 | cp parts
3 | base shaft collection
4 | # 2018 Simon Kirkby obeygiantrobot@gmail.com
5 | """
6 |
7 | # TODO
8 | # need tip , base and offset mate points
9 | # maybe shaft needs to go into it's own module
10 | #
11 | # there are lots of types of shafts and extras
12 | # need a clean way to build shafts
13 |
14 | import cadquery as cq
15 |
16 | import cqparts
17 | from cqparts.params import PositiveFloat
18 | from cqparts.display import render_props
19 | from cqparts.constraint import Mate
20 | from cqparts.utils.geometry import CoordSystem
21 | from cqparts.search import register
22 |
23 | # base shaft type
24 | @register(export="shaft")
25 | class Shaft(cqparts.Part):
26 | " base shaft , override ME"
27 | length = PositiveFloat(24, doc="shaft length")
28 | diam = PositiveFloat(5, doc="shaft diameter")
29 |
30 | _render = render_props(color=(50, 255, 255))
31 |
32 | def make(self):
33 | shft = (
34 | cq.Workplane("XY")
35 | .circle(self.diam / 2)
36 | .extrude(self.length)
37 | .faces(">Z")
38 | .chamfer(0.4)
39 | )
40 | return shft
41 |
42 | def cut_out(self):
43 | cutout = cq.Workplane("XY").circle(self.diam / 2).extrude(self.length)
44 | return cutout
45 |
46 | # TODO , mate for shafts
47 |
48 | def get_cutout(self, clearance=0):
49 | " clearance cut out for shaft "
50 | return (
51 | cq.Workplane("XY", origin=(0, 0, 0))
52 | .circle((self.diam / 2) + clearance)
53 | .extrude(self.length * 2)
54 | )
55 |
56 | def make_cutout(self, part, clearance=0):
57 | part = part.local_obj.cut(
58 | (self.world_coords - part.world_coords) + self.cutout(clearance=clearance)
59 | )
60 |
61 | def cutout(self, clearance=0):
62 | so = cq.Workplane("XY").circle(clearance + self.diam / 2).extrude(self.length)
63 | return so
64 |
65 | def mate_tip(self, offset=0):
66 | return Mate(
67 | self,
68 | CoordSystem(origin=(0, 0, self.length), xDir=(1, 0, 0), normal=(0, 0, 1)),
69 | )
70 |
71 |
72 | if __name__ == "__main__":
73 | from cqparts.display import display
74 |
75 | a = Shaft()
76 | display(a)
77 |
--------------------------------------------------------------------------------
/stepper_cat.py:
--------------------------------------------------------------------------------
1 |
2 | import cqparts
3 | from cqparts.display import display
4 | from cqparts.constraint import Fixed, Coincident
5 | from cqparts.constraint import Mate
6 | from cqparts.utils import CoordSystem
7 |
8 | from cqparts.catalogue import JSONCatalogue
9 | import cqparts_motors
10 | import os
11 |
12 | from .motor_mount import MountedStepper
13 | from .stepper import Stepper
14 |
15 | filename = os.path.join(
16 | os.path.dirname(cqparts_motors.__file__), "catalogue", "stepper-nema.json"
17 | )
18 |
19 | catalogue = JSONCatalogue(filename)
20 | item = catalogue.get_query()
21 | steppers = catalogue.iter_items()
22 | stepper_list = []
23 | for i in steppers:
24 | s = catalogue.deserialize_item(i)
25 | cl = type(str(i["id"]), (Stepper,), {})
26 | p = cl.class_params(hidden=True)
27 | for j, k in p.items():
28 | pr = type(k)
29 | setattr(cl, j, pr(i["obj"]["params"][j]))
30 | stepper_list.append(cl)
31 |
32 |
33 | class StepperCat(cqparts.Assembly):
34 | def initialize_parameters(self):
35 | self.coll = []
36 | self.offset = 90
37 |
38 | def add(self, i):
39 | self.coll.append(i)
40 |
41 | @classmethod
42 | def item_name(cls, index):
43 | return "item_%03i" % index
44 |
45 | def make_components(self):
46 | items = {}
47 | for i in range(len(self.coll)):
48 | items[self.item_name(i)] = self.coll[i]
49 | return items
50 |
51 | def make_constraints(self):
52 | constraints = []
53 | length = len(self.coll)
54 | total = length * self.offset
55 | for i in range(len(self.coll)):
56 | constraints.append(
57 | Fixed(
58 | self.coll[i].mate_origin,
59 | CoordSystem(
60 | origin=(0, i * self.offset - (total / 2), 0),
61 | xDir=(0, 1, 0),
62 | normal=(0, 0, 1),
63 | ),
64 | )
65 | )
66 |
67 | return constraints
68 |
69 |
70 | if __name__ == "__main__":
71 | ar = StepperCat()
72 | print(stepper_list)
73 | for i in stepper_list:
74 | ar.add(MountedStepper(clearance=2, thickness=3, stepper=i))
75 | # ar.add(i())
76 | print(ar.tree_str())
77 | display(ar)
78 |
--------------------------------------------------------------------------------
/linear_bearing.py:
--------------------------------------------------------------------------------
1 | """
2 | lm8uu linear bearing
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 |
14 | from cqparts.search import register
15 |
16 |
17 | @register(export="linear_bearing")
18 | class LinearBearing(cqparts.Part):
19 | length = PositiveFloat(24)
20 | outer_diam = PositiveFloat(15)
21 | inner_diam = PositiveFloat(8)
22 |
23 | slot_inset = PositiveFloat(3.25)
24 | slot_width = PositiveFloat(1.1)
25 | slot_depth = PositiveFloat(0.5)
26 |
27 | def _ring(self, offset=4):
28 | return (
29 | cq.Workplane("XY")
30 | .circle(self.outer_diam / 2)
31 | .circle((self.outer_diam / 2) - self.slot_depth)
32 | .extrude(self.slot_width)
33 | .translate((0, 0, offset))
34 | )
35 |
36 | def make(self):
37 | lm = (
38 | cq.Workplane("XY")
39 | .circle(self.outer_diam / 2)
40 | .circle(self.inner_diam / 2)
41 | .extrude(self.length)
42 | )
43 | lm = lm.faces("|Z").chamfer(0.3)
44 | lm = lm.cut(self._ring(offset=self.slot_inset))
45 | lm = lm.cut(self._ring(offset=self.length - self.slot_inset - self.slot_width))
46 | return lm
47 |
48 | def make_cutout(self, part, clearance=0):
49 | part = part.local_obj.cut(
50 | (self.world_coords - part.world_coords) + self.cutout(clearance=clearance)
51 | )
52 |
53 | def cutout(self, clearance=0):
54 | so = (
55 | cq.Workplane("XY")
56 | .circle(clearance + self.outer_diam / 2)
57 | .extrude(self.length)
58 | )
59 | return so
60 |
61 |
62 | @register(export="linear_bearing")
63 | class lm8uu(LinearBearing):
64 | length = PositiveFloat(24)
65 | outer_diam = PositiveFloat(15)
66 | inner_diam = PositiveFloat(8)
67 |
68 | slot_inset = PositiveFloat(3.25)
69 | slot_width = PositiveFloat(1.1)
70 | slot_depth = PositiveFloat(0.5)
71 |
72 |
73 | if __name__ == "__main__":
74 | from cqparts.display import display
75 |
76 | B = lm8uu()
77 | display(B)
78 |
--------------------------------------------------------------------------------
/beard_boss.py:
--------------------------------------------------------------------------------
1 | " A cqparts version of the beard boss"
2 | " from https://groups.google.com/forum/#!topic/cadquery/_jCq0z9Swio"
3 |
4 | import cadquery as cq
5 | import cqparts
6 | from cqparts.params import *
7 | from cqparts.search import register
8 |
9 |
10 | class _boss(cqparts.Part):
11 | boss_height = PositiveFloat(10)
12 | boss_diameter = PositiveFloat(10)
13 |
14 | def make(self):
15 | wp = cq.Workplane("XY").circle(self.boss_diameter / 2).extrude(self.boss_height)
16 | return wp
17 |
18 |
19 | @register(export="misc")
20 | class BeardBoss(cqparts.Part):
21 | # Base Plate
22 | length = PositiveFloat(50)
23 | width = PositiveFloat(50)
24 | height = PositiveFloat(6)
25 | # Boss size
26 | boss_height = PositiveFloat(10)
27 | boss_diameter = PositiveFloat(15)
28 | # Drill Size
29 | hole_diameter = PositiveFloat(8)
30 | # Boss spacing
31 | x_spacing = PositiveFloat(30)
32 | y_spacing = PositiveFloat(30)
33 |
34 | # hand back vertices for the boss positions
35 | def mount_points(self, offset=0):
36 | wp = cq.Workplane("XY", origin=(0, 0, offset))
37 | h = wp.rect(self.x_spacing, self.y_spacing, forConstruction=True).vertices()
38 | return h.objects
39 |
40 | def make(self):
41 | # base plate
42 | pl = cq.Workplane("XY").box(self.length, self.width, self.height)
43 | pl = pl.translate((0, 0, self.height / 2))
44 | # add the bosses
45 | mp = self.mount_points()
46 | for i in mp:
47 | b = _boss(boss_height=self.boss_height, boss_diameter=self.boss_diameter)
48 | b = b.local_obj.translate((i.X, i.Y, self.height))
49 | pl = pl.union(b)
50 | # cur the holes
51 | for i in mp:
52 | h = (
53 | cq.Workplane("XY")
54 | .circle(self.hole_diameter / 2)
55 | .extrude(self.height + self.boss_height)
56 | )
57 | h = h.translate((i.X, i.Y, 0))
58 | pl = pl.cut(h)
59 | pl = pl.faces(">Z[1]").edges("not(X or Y)").fillet(1)
60 | pl = pl.edges("|Z").fillet(3)
61 | return pl
62 |
63 |
64 | if __name__ == "__main__":
65 | from cqparts.display import display
66 |
67 | bb = BeardBoss(height=6, boss_height=20, boss_diameter=10, hole_diameter=5)
68 | display(bb)
69 |
--------------------------------------------------------------------------------
/boss.py:
--------------------------------------------------------------------------------
1 | """
2 | basic boss
3 | # 2019 Simon Kirkby obeygiantrobot@gmail.com
4 | """
5 | import cadquery as cq
6 |
7 | import cqparts
8 | from cqparts.params import PositiveFloat, Int
9 | from cqparts.display import render_props
10 | from cqparts.constraint import Mate
11 | from cqparts.utils.geometry import CoordSystem
12 | from cqparts.search import register
13 |
14 | # base shaft type
15 | @register(export="shaft")
16 | class Boss(cqparts.Part):
17 | " Simple boss "
18 | stem_length = PositiveFloat(5, doc="stem length")
19 | stem_diam = PositiveFloat(8, doc="stem diameter")
20 |
21 | boss_diam = PositiveFloat(20, doc="boss diameter")
22 | boss_length = PositiveFloat(3, doc="boss length")
23 |
24 | shaft_diam = PositiveFloat(5, doc="shaft diameter")
25 |
26 | hole_radius = PositiveFloat(14, doc="distance from center to hole center")
27 | holes = Int(4, doc="number of holes in circle")
28 |
29 | _render = render_props(color=(50, 50, 50))
30 |
31 | def make(self):
32 | boss = cq.Workplane("XY").circle(self.boss_diam / 2).extrude(-self.boss_length)
33 | stem = (
34 | cq.Workplane("XY")
35 | .circle(self.stem_diam / 2)
36 | .extrude(self.stem_length)
37 | .translate((0, 0, -(self.boss_length + self.stem_length)))
38 | )
39 |
40 | boss = boss.union(stem)
41 | shaft = (
42 | cq.Workplane("XY")
43 | .circle(self.shaft_diam / 2)
44 | .extrude(self.boss_length + self.stem_length)
45 | .translate((0, 0, -(self.boss_length + self.stem_length)))
46 | )
47 | boss = boss.cut(shaft)
48 | return boss
49 |
50 | def mount_verts(self):
51 | holes = (
52 | cq.Workplane("XY")
53 | .polygon(self.holes, self.hole_radius, forConstruction=True)
54 | .vertices()
55 | )
56 | return holes.objects
57 |
58 | def cutout(self, part):
59 | return None
60 |
61 | def mate_top(self, offset=0):
62 | return Mate(
63 | self,
64 | CoordSystem(
65 | origin=(0, 0, self.boss_length + offset),
66 | xDir=(1, 0, 0),
67 | normal=(0, 0, 1),
68 | ),
69 | )
70 |
71 |
72 | if __name__ == "__main__":
73 | from cqparts.display import display
74 |
75 | b = Boss()
76 | display(b)
77 |
--------------------------------------------------------------------------------
/roller.py:
--------------------------------------------------------------------------------
1 | """
2 | Roller for the turntable
3 | # 2019 Simon Kirkby obeygiantrobot@gmail.com
4 | """
5 |
6 |
7 | import cadquery as cq
8 |
9 | import cqparts
10 | from cqparts.params import PositiveFloat
11 | from cqparts.display import render_props
12 | from cqparts.constraint import Mate
13 | from cqparts.utils.geometry import CoordSystem
14 | from cqparts.search import register
15 |
16 |
17 | class SimpleBearing(cqparts.Part):
18 | " Fake out simple bearing , 608 "
19 | outer_diam = PositiveFloat(22, doc="outer diameter")
20 | inner_diam = PositiveFloat(8, doc="inner diameter")
21 | thickness = PositiveFloat(7, doc="thickness")
22 |
23 | lip_thickness = PositiveFloat(0.2, doc="lip thickness")
24 | lip_width = PositiveFloat(1.0, doc="lip width")
25 |
26 | _render = render_props(color=(50, 255, 255))
27 |
28 | def make(self):
29 | outer = cq.Workplane("XY").circle(self.outer_diam / 2).extrude(self.thickness)
30 | rim = (
31 | cq.Workplane("XY")
32 | .circle(self.inner_diam / 2 + self.lip_width)
33 | .extrude(self.thickness + 2 * self.lip_thickness)
34 | .translate((0, 0, -self.lip_thickness))
35 | )
36 | # outer = outer.union(rim)
37 | inner = (
38 | cq.Workplane("XY")
39 | .circle(self.inner_diam / 2)
40 | .extrude(self.thickness + 2 * self.lip_thickness)
41 | .translate((0, 0, -self.lip_thickness))
42 | )
43 |
44 | outer = outer.cut(inner)
45 | return outer
46 |
47 | def cut_out(self):
48 | cutout = cq.Workplane("XY").circle(self.diam / 2).extrude(self.length)
49 | return cutout
50 |
51 | # TODO , mate for shafts
52 |
53 | def get_cutout(self, clearance=0):
54 | " clearance cut out for shaft "
55 | return (
56 | cq.Workplane("XY", origin=(0, 0, 0))
57 | .circle((self.diam / 2) + clearance)
58 | .extrude(self.length * 2)
59 | )
60 |
61 | def mate_tip(self, offset=0):
62 | return Mate(
63 | self,
64 | CoordSystem(origin=(0, 0, self.length), xDir=(1, 0, 0), normal=(0, 0, 1)),
65 | )
66 |
67 |
68 | class BearingMount(cqparts.Assembly):
69 | pass
70 |
71 |
72 | if __name__ == "__main__":
73 | from cqparts.display import display
74 |
75 | # sb = SimpleBearing()
76 | t = BearingMount()
77 | display(t)
78 |
--------------------------------------------------------------------------------
/multi.py:
--------------------------------------------------------------------------------
1 |
2 | import cqparts
3 | from cqparts.params import PositiveFloat
4 | from cqparts.display import display
5 | from cqparts.constraint import Fixed, Coincident
6 | from cqparts.constraint import Mate
7 | from cqparts.utils import CoordSystem
8 |
9 | class Gallery(cqparts.Assembly):
10 | offset = PositiveFloat(60)
11 |
12 | def initialize_parameters(self):
13 | self.coll = []
14 |
15 | def add(self, i):
16 | self.coll.append(i)
17 |
18 | @classmethod
19 | def item_name(cls, index):
20 | return "item_%03i" % index
21 |
22 | def make_components(self):
23 | items = {}
24 | for i in range(len(self.coll)):
25 | items[self.item_name(i)] = self.coll[i]
26 | return items
27 |
28 | def make_constraints(self):
29 | constraints = []
30 | # calculate on bounding boxes
31 | for i in range(len(self.coll)):
32 | print(self.coll[i].bounding_box.wrapped)
33 | #constraints.append(
34 | # Fixed(
35 | # self.coll[i].mate_origin,
36 | # CoordSystem(
37 | # origin=(0, i * self.offset - (total / 2) + self.offset / 2, 0),
38 | # xDir=(1, 0, 0),
39 | # normal=(0, 0, 1),
40 | # ),
41 | # )
42 | #)
43 | return constraints
44 |
45 | class Arrange(cqparts.Assembly):
46 | offset = PositiveFloat(60)
47 |
48 | def initialize_parameters(self):
49 | self.coll = []
50 |
51 | def add(self, i):
52 | self.coll.append(i)
53 |
54 | @classmethod
55 | def item_name(cls, index):
56 | return "item_%03i" % index
57 |
58 | def make_components(self):
59 | items = {}
60 | for i in range(len(self.coll)):
61 | items[self.item_name(i)] = self.coll[i]
62 | return items
63 |
64 | def make_constraints(self):
65 | constraints = []
66 | length = len(self.coll)
67 | total = length * self.offset
68 | for i in range(len(self.coll)):
69 | constraints.append(
70 | Fixed(
71 | self.coll[i].mate_origin,
72 | CoordSystem(
73 | origin=(0, i * self.offset - (total / 2) + self.offset / 2, 0),
74 | xDir=(1, 0, 0),
75 | normal=(0, 0, 1),
76 | ),
77 | )
78 | )
79 | return constraints
80 |
--------------------------------------------------------------------------------
/project_case.py:
--------------------------------------------------------------------------------
1 | """
2 | Project case testing
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cqparts.params import *
8 | from cqparts.constraint import Coincident
9 | from cqparts.search import register
10 |
11 | from .partref import PartRef
12 |
13 | from . import box
14 | from .mounted_board import MountedBoard
15 | from .controller import Pizero, BeagleBoneBlack, Arduino
16 | from .lcd import Lcd
17 | from .button import Button
18 |
19 |
20 | class ControlPanel(cqparts.Assembly):
21 | pass
22 |
23 |
24 | @register(export="showcase")
25 | class ProjectBox(cqparts.Assembly):
26 | height = PositiveFloat(70)
27 | width = PositiveFloat(85)
28 | length = PositiveFloat(60)
29 | thickness = PositiveFloat(3)
30 | outset = PositiveFloat(3)
31 |
32 | screen_clearance = PositiveFloat(0.4)
33 |
34 | board = PartRef(Pizero)
35 | screen = PartRef(Lcd)
36 |
37 | def make_components(self):
38 | b = box.Boxen(
39 | height=self.height,
40 | width=self.width,
41 | length=self.length,
42 | thickness=self.thickness,
43 | outset=self.outset,
44 | )
45 | comps = {}
46 | comps["box"] = b
47 | comps["cont"] = MountedBoard(board=self.board, target=b.components["bottom"])
48 | comps["screen"] = self.screen(
49 | clearance=self.screen_clearance, target=b.components["front"]
50 | )
51 | comps["buttonA"] = Button(target=b.components["front"])
52 | comps["buttonB"] = Button(target=b.components["front"])
53 | return comps
54 |
55 | def make_constraints(self):
56 | bot = self.components["box"].components["bottom"]
57 | front = self.components["box"].components["front"]
58 | butA = self.components["buttonA"]
59 | butB = self.components["buttonB"]
60 | c = self.components["cont"]
61 | screen = self.components["screen"]
62 | const = []
63 | const.append(Coincident(c.mate_transverse(), bot.mate_top_pos(x=0, y=0)))
64 | # make a control panel object
65 | const.append(
66 | Coincident(screen.mate_transverse(), front.mate_bottom_pos(x=8, y=0))
67 | )
68 | const.append(Coincident(butA.mate_origin, front.mate_top_pos(x=-19, y=15)))
69 | const.append(Coincident(butB.mate_origin, front.mate_top_pos(x=-19, y=-15)))
70 | return const
71 |
72 | def make_alterations(self):
73 | self.components["screen"].make_alterations()
74 |
75 |
76 | if __name__ == "__main__":
77 | from cqparts.display import display
78 |
79 | FB = ProjectBox()
80 | display(FB)
81 |
--------------------------------------------------------------------------------
/flip_box.py:
--------------------------------------------------------------------------------
1 | """
2 | Flip lid box
3 | Subclass test for Boxen
4 | """
5 |
6 | import cadquery as cq
7 | import cqparts
8 | from cqparts.params import *
9 | from cqparts.search import register
10 |
11 | from . import box
12 |
13 |
14 | class Front(box.Front):
15 | tabs_on = box.BoolList([True, True, True, False])
16 |
17 |
18 | class Back(box.Back):
19 | tabs_on = box.BoolList([True, True, True, False])
20 |
21 | def initialize_parameters(self):
22 | self.length = self.length - self.thickness
23 |
24 | def make(self):
25 | base = super(Back, self).make()
26 |
27 | return base
28 |
29 |
30 | class Lid(box.Top):
31 | tabs_on = box.BoolList()
32 | clearance = PositiveFloat(1.5)
33 |
34 | def initialize_parameters(self):
35 | self.width = self.width - self.clearance
36 |
37 | def make(self):
38 | base = super(Lid, self).make()
39 | detent = (
40 | cq.Workplane("XY")
41 | .rect(
42 | self.thickness * 2.7,
43 | self.width + self.clearance * 3 + self.thickness * 2,
44 | )
45 | .extrude(self.thickness)
46 | )
47 | detent = detent.translate((-self.length / 2, 0, 0))
48 | base = base.union(detent)
49 | base = base.translate((0, -self.clearance / 2, 0))
50 | return base
51 |
52 |
53 | class HingeL(box.Left):
54 | def make(self):
55 | base = super(HingeL, self).make()
56 | offset = (-self.length / 2, -self.width / 2 + self.thickness / 2, 0)
57 | hinge = cq.Workplane("XY").circle(2.7 * self.thickness).extrude(self.thickness)
58 | hinge = hinge.translate(offset)
59 | base = base.union(hinge)
60 | hole = cq.Workplane("XY").circle(1.5 * self.thickness).extrude(self.thickness)
61 | hole = hole.translate(offset)
62 | base = base.cut(hole)
63 | return base
64 |
65 |
66 | class HingeR(box.Right):
67 | def make(self):
68 | base = super(HingeR, self).make()
69 | offset = (self.length / 2, -self.width / 2 + self.thickness / 2, 0)
70 | hinge = cq.Workplane("XY").circle(2.7 * self.thickness).extrude(self.thickness)
71 | hinge = hinge.translate(offset)
72 | base = base.union(hinge)
73 | hole = cq.Workplane("XY").circle(1.5 * self.thickness).extrude(self.thickness)
74 | hole = hole.translate(offset)
75 | base = base.cut(hole)
76 | return base
77 |
78 |
79 | @register(export="box")
80 | @register(export="showcase")
81 | class FlipBox(box.Boxen):
82 | # Pass down subclassed faces
83 | left = box.PartRef(HingeL)
84 | right = box.PartRef(HingeR)
85 | top = box.PartRef(Lid)
86 | front = box.PartRef(Front)
87 | back = box.PartRef(Back)
88 | height = PositiveFloat(50)
89 | outset = PositiveFloat(3)
90 |
91 |
92 | if __name__ == "__main__":
93 | from cqparts.display import display
94 |
95 | FB = FlipBox(height=50, thickness=3, outset=3)
96 | display(FB)
97 |
--------------------------------------------------------------------------------
/pencil_case.py:
--------------------------------------------------------------------------------
1 | """
2 | OPen box
3 | Subclass test for Boxen
4 | """
5 |
6 | import cadquery as cq
7 | import cqparts
8 | from cqparts.params import *
9 | from cqparts.constraint import Fixed, Coincident
10 | from cqparts.utils.geometry import CoordSystem
11 | from cqparts.constraint import Mate
12 |
13 | from cqparts.search import register
14 |
15 | from . import box
16 | from .open_box import OpenBox
17 |
18 |
19 | class T2(box._Tab):
20 | count = Int(7)
21 |
22 |
23 | class Front(box.Front):
24 | tabs_on = box.BoolList([True, True, True, False])
25 |
26 | def initialize_parameters(self):
27 | self.length = self.length + self.thickness
28 |
29 |
30 | class Back(box.Back):
31 | tabs_on = box.BoolList([True, True, True, False])
32 |
33 | def initialize_parameters(self):
34 | self.length = self.length + self.thickness
35 |
36 |
37 | class FingerHole(box.Left):
38 | hole_size = PositiveFloat(15)
39 |
40 | def make(self):
41 | base = super(FingerHole, self).make()
42 | hole = cq.Workplane("XY").circle(self.hole_size / 2).extrude(self.thickness)
43 | hole = hole.translate((0, -self.width / 2, 0))
44 | base = base.cut(hole)
45 | return base
46 |
47 |
48 | class PencilCaseTop(box.Boxen):
49 | # Pass down subclassed faces
50 |
51 | top = box.PartRef(None)
52 | front = box.PartRef(Front)
53 | back = box.PartRef(Back)
54 | left = box.PartRef(FingerHole)
55 | right = box.PartRef(FingerHole)
56 | tab = box.PartRef(T2)
57 |
58 | def mate_top(self):
59 | return Mate(
60 | self,
61 | CoordSystem(origin=(0, 0, self.height), xDir=(1, 0, 0), normal=(0, 0, -1)),
62 | )
63 |
64 | def make_alterations(self):
65 | super(PencilCaseTop, self).make_alterations()
66 |
67 |
68 | @register(export="box", showcase="showcase")
69 | class PencilCase(cqparts.Assembly):
70 | length = PositiveFloat(200)
71 | width = PositiveFloat(65)
72 | height = PositiveFloat(35)
73 | thickness = PositiveFloat(3)
74 |
75 | clearance = PositiveFloat(1)
76 |
77 | def make_components(self):
78 | comps = {
79 | "top": PencilCaseTop(
80 | length=self.length,
81 | width=self.width,
82 | height=self.height,
83 | thickness=self.thickness,
84 | ),
85 | "bottom": OpenBox(
86 | length=self.length - 2 * self.thickness - self.clearance,
87 | width=self.width - 2 * self.thickness - self.clearance,
88 | height=self.height - self.thickness - self.clearance,
89 | thickness=self.thickness,
90 | ),
91 | }
92 | return comps
93 |
94 | def make_constraints(self):
95 | constr = [
96 | Fixed(self.components["bottom"].mate_origin),
97 | Fixed(self.components["top"].mate_top()),
98 | ]
99 | return constr
100 |
101 |
102 | if __name__ == "__main__":
103 | from cqparts.display import display
104 |
105 | FB = PencilCase()
106 | display(FB)
107 |
--------------------------------------------------------------------------------
/mill.py:
--------------------------------------------------------------------------------
1 | import cqparts
2 | import cadquery
3 |
4 |
5 | from cqparts.constraint import Fixed, Coincident
6 | from cqparts.constraint import Mate
7 | from cqparts.utils.geometry import CoordSystem
8 |
9 | from cqparts.params import PositiveFloat, Float
10 | from cqparts.search import register
11 |
12 | from .partref import PartRef
13 | from .cnc import Axis, Rails, Carriage, DriveEnd
14 | from .driver import BeltDrive, ThreadedDrive
15 |
16 | from .linear_bearing import lm8uu
17 | from .plank import Plank
18 |
19 |
20 | class SingleRail(Rails):
21 | def rail_pos(self):
22 | m = Mate(
23 | self,
24 | CoordSystem(origin=(0, 0, self.lift), xDir=(1, 1, 0), normal=(0, 0, 1)),
25 | )
26 | return [m]
27 |
28 |
29 | class XSlide(Carriage):
30 | bearing = PartRef(lm8uu)
31 | pass
32 |
33 |
34 | class XDrive(DriveEnd):
35 | pass
36 |
37 |
38 | class XAxis(Axis):
39 | height = PositiveFloat(65)
40 | width = PositiveFloat(50)
41 | rails = PartRef(SingleRail)
42 | drive_lift = Float(30)
43 | rail_lift = Float(50)
44 | carriage = PartRef(XSlide)
45 | # drive_end = PartRef(XDrive)
46 | pos = PositiveFloat(100)
47 | drive = PartRef(ThreadedDrive)
48 | # drive = PartRef(BeltDrive)
49 |
50 |
51 | class TAxis(Axis):
52 | pass
53 |
54 | @register(export="showcase")
55 | class Mill(cqparts.Assembly):
56 | length = PositiveFloat(250)
57 | width = PositiveFloat(300)
58 | height = PositiveFloat(100)
59 | xaxis = PartRef(XAxis)
60 | yaxis = PartRef(Axis)
61 | zaxis = PartRef(Axis)
62 |
63 | padding = PositiveFloat(20)
64 | base_thickness = PositiveFloat(6)
65 |
66 | def initialize_paramters(self):
67 | pass
68 |
69 | def make_components(self):
70 | comps = {
71 | "base" : Plank(thickness=self.base_thickness,length=self.length+self.padding*4.0,width=self.width+self.padding*3.0),
72 | "XL": self.xaxis(length=self.length),
73 | "XR": self.xaxis(length=self.length),
74 | # "YA": self.yaxis(length=self.width),
75 | }
76 | return comps
77 |
78 | def make_constraints(self):
79 | base = self.components["base"]
80 | constr = [
81 | Fixed(
82 | base.mate_origin
83 | ),
84 | Fixed(
85 | self.components["XL"].mate_origin,
86 | CoordSystem((-self.length/2.0, -self.width / 2.0, base.thickness), (1, 0, 0), (0, 0, 1)),
87 | ),
88 | Fixed(
89 | self.components["XR"].mate_origin,
90 | CoordSystem((-self.length/2.0, self.width / 2.0, base.thickness), (1, 0, 0), (0, 0, 1)),
91 | ),
92 | # Fixed(
93 | # self.components["YA"].mate_origin,
94 | # CoordSystem((0,self.length/2 , 0), (0, -1, 0), (0, 0, 1)),
95 | # ),
96 | ]
97 | return constr
98 |
99 |
100 | if __name__ == "__main__":
101 | from cqparts.display import display
102 |
103 | m = Mill()
104 | display(m)
105 |
--------------------------------------------------------------------------------
/button.py:
--------------------------------------------------------------------------------
1 | """
2 | Clicky Button thing
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 | from cqparts.search import register
14 |
15 | from .partref import PartRef
16 |
17 |
18 | class _Stem(cqparts.Part):
19 | length = PositiveFloat(12)
20 | diameter = PositiveFloat(10)
21 |
22 | def make(self):
23 | obj = cq.Workplane("XY").circle(self.diameter / 2).extrude(-self.length)
24 | return obj
25 |
26 |
27 | class _Push(cqparts.Part):
28 | width = PositiveFloat(12)
29 | length = PositiveFloat(12)
30 | height = PositiveFloat(4)
31 |
32 | _render = render_props(color=(255, 0, 0))
33 |
34 | def make(self):
35 | obj = cq.Workplane("XY").rect(self.width, self.length).extrude(self.height)
36 | return obj
37 |
38 |
39 | class _Shield(cqparts.Part):
40 | width = PositiveFloat(12)
41 | length = PositiveFloat(12)
42 | height = PositiveFloat(4)
43 |
44 |
45 | class Button(cqparts.Assembly):
46 |
47 | stem = PartRef(_Stem)
48 | push = PartRef(_Push)
49 | target = PartRef()
50 | clearance = PositiveFloat(0.2)
51 |
52 | def make_components(self):
53 | comps = {"stem": self.stem(), "push": self.push()}
54 | return comps
55 |
56 | def make_constraints(self):
57 | const = []
58 | const.append(Fixed(self.components["stem"].mate_origin))
59 | const.append(Fixed(self.components["push"].mate_origin))
60 | return const
61 |
62 | def make_alterations(self):
63 | if self.target is not None:
64 | self.make_cutout(self.target, clearance=self.clearance)
65 |
66 | def make_cutout(self, part, clearance=0):
67 | part = part.local_obj.cut(
68 | (self.world_coords - part.world_coords) + self.cutout(clearance=clearance)
69 | )
70 |
71 | def cutout(self, clearance=0):
72 | size = self.stem().diameter
73 | cutter = self.stem(diameter=size + self.clearance * 2).make()
74 | return cutter
75 |
76 |
77 | # Test assembly for mount points and cutouts
78 | from .plank import Plank
79 |
80 |
81 | class _MountedButton(cqparts.Assembly):
82 | def make_components(self):
83 | plank = Plank(height=3)
84 | comps = {"button": Button(target=plank), "plank": plank}
85 | return comps
86 |
87 | def make_constraints(self):
88 | return [
89 | Fixed(self.components["plank"].mate_bottom),
90 | Coincident(
91 | self.components["button"].mate_origin, self.components["plank"].mate_top
92 | ),
93 | ]
94 |
95 |
96 | # def make_alterations(self):
97 | # self.components[""].make_alterations()
98 |
99 | if __name__ == "__main__":
100 | from cqparts.display import display
101 |
102 | # b = _Stem()
103 | # b = Button()
104 | b = _MountedButton()
105 |
106 | # b = _Push()
107 | display(b)
108 |
--------------------------------------------------------------------------------
/lcd.py:
--------------------------------------------------------------------------------
1 | """
2 | Lcd panel
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 | from cqparts.search import register
14 |
15 | from .controller import PCBBoard
16 | from .partref import PartRef
17 |
18 | from .plank import Plank
19 |
20 |
21 | class Screen(cqparts.Part):
22 | length = PositiveFloat(64.5)
23 | width = PositiveFloat(26)
24 | thickness = PositiveFloat(4.6)
25 |
26 | def make(self):
27 | sc = cq.Workplane("XY").rect(self.length, self.width).extrude(self.thickness)
28 | return sc
29 |
30 |
31 | class Lcd(PCBBoard):
32 | length = PositiveFloat(80)
33 | width = PositiveFloat(36)
34 | corner_radius = PositiveFloat(2)
35 | thickness = PositiveFloat(1.6)
36 |
37 | hole_size = PositiveFloat(2.5)
38 | hole_width = PositiveFloat(31)
39 | hole_length = PositiveFloat(75)
40 |
41 | screen_length = PositiveFloat(64.5)
42 | screen_width = PositiveFloat(26)
43 | screen_thickness = PositiveFloat(4.6)
44 |
45 | clearance = PositiveFloat(0.8)
46 |
47 | target = PartRef()
48 |
49 | def make(self):
50 | obj = super(Lcd, self).make()
51 | obj = obj.translate((0, 0, -self.thickness / 2))
52 | scr = Screen(
53 | length=self.screen_length,
54 | width=self.screen_width,
55 | thickness=self.screen_thickness,
56 | ).local_obj
57 | obj = obj.union(scr)
58 | return obj
59 |
60 | def make_alterations(self):
61 | if self.target is not None:
62 | self.make_cutout(self.target, clearance=self.clearance)
63 |
64 | def make_cutout(self, part, clearance=0):
65 | part = part.local_obj.cut(
66 | (self.world_coords - part.world_coords) + self.cutout(clearance=clearance)
67 | )
68 |
69 | def cutout(self, clearance=0):
70 | cutter = (
71 | cq.Workplane("XY")
72 | .rect(self.screen_length + 2 * clearance, self.screen_width + 2 * clearance)
73 | .extrude(self.screen_thickness * 5)
74 | )
75 | return cutter
76 |
77 | def mate_transverse(self, x=0, y=0):
78 | return Mate(
79 | self, CoordSystem(origin=(x, y, 0), xDir=(0, 1, 0), normal=(0, 0, 1))
80 | )
81 |
82 |
83 | # Test assembly for mount points and cutouts
84 | class _MountedLcd(cqparts.Assembly):
85 | def make_components(self):
86 | plank = Plank()
87 | comps = {"lcd": Lcd(target=plank), "plank": plank}
88 | return comps
89 |
90 | def make_constraints(self):
91 | return [
92 | Fixed(self.components["plank"].mate_bottom),
93 | Coincident(
94 | self.components["lcd"].mate_origin, self.components["plank"].mate_bottom
95 | ),
96 | ]
97 |
98 | def make_alterations(self):
99 | self.components["lcd"].make_alterations()
100 |
101 |
102 | if __name__ == "__main__":
103 | from cqparts.display import display
104 |
105 | # em = Lcd()
106 | em = _MountedLcd()
107 | display(em)
108 |
--------------------------------------------------------------------------------
/svg_test.py:
--------------------------------------------------------------------------------
1 | import SVGexport
2 | import cadquery as cq
3 | import cqparts
4 | from collections import OrderedDict
5 |
6 | from . import flip_box
7 | from . import box
8 | from . import pencil_case
9 | from . import plank
10 | from turntable import TurnTable
11 | from rectpack import newPacker, float2dec
12 | from .manufacture import Lasercut
13 |
14 | # fb = plank.Plank()
15 |
16 | # makes an array of local objects
17 | class Extractor(cqparts.Assembly):
18 | def __init__(self, obj):
19 | # for duplicate names
20 | self.track = {}
21 | self.parts = OrderedDict()
22 |
23 | def scan(self, obj, name):
24 | if isinstance(obj, Lasercut):
25 | print(obj)
26 | if name in self.track:
27 | actual_name = name + "_%03i" % self.track[name]
28 | self.track[name] += 1
29 | else:
30 | self.track[name] = 1
31 | actual_name = name
32 | self.parts[actual_name] = obj
33 |
34 | if isinstance(obj, cqparts.Assembly):
35 | for i in obj.components:
36 | self.scan(obj.components[i], i)
37 |
38 | def show(self):
39 | for j in self.parts:
40 | i = self.parts[j]
41 | area = i.bounding_box.xlen * i.bounding_box.ylen
42 | print(i.__class__, i.bounding_box.xlen, i.bounding_box.ylen, area)
43 |
44 | def get_parts(self):
45 | return self.parts
46 |
47 |
48 | def getRects(partDict, gap=6.0):
49 | rects = []
50 | # generate offsets
51 | for i in partDict:
52 |
53 | # bounding boxes are not accurate
54 | # z = partDict[i].local_obj.val().tessellate(0.1)
55 | ##xmin = -1e7
56 | # ymin = -1e7
57 | # xmax = 1e7
58 | # ymax = 1e7
59 | # for v in z[0]:
60 | # if v[0] > xmin:
61 | # xmin = v[0]
62 | # if v[0] < xmax:
63 | # xmax = v[0]
64 | # if v[1] > ymin:
65 | # ymin = v[1]
66 | # if v[1] < ymax:
67 | # ymax = v[1]
68 | # calc_w = abs(xmax-xmin)
69 | # calc_l = abs(ymax-ymin)
70 | w = partDict[i].bounding_box.xlen
71 | l = partDict[i].bounding_box.ylen
72 | # print(calc_w,calc_l,w,l)
73 | rects.append((float2dec(w + 2 * gap, 3), float2dec(l + 2 * gap, 3), i))
74 | return rects
75 |
76 |
77 | def genSVG(binsize, partDict, rectList, filename):
78 | wp = cq.Workplane("XY")
79 | for i in rectList:
80 | print(i)
81 | cx = i[1] + (i[3] / float2dec(2.0, 3))
82 | cy = binsize[1] - (i[2] + (i[4] / float2dec(2.0, 3)))
83 | name = i[5]
84 | print(cx, cy, name)
85 | wp = wp.union(partDict[name].local_obj.translate((cx, cy, 0)))
86 | SVGexport.exportSVG(wp, filename)
87 |
88 |
89 | fb = TurnTable(width=90, length=90)
90 | ex = Extractor(fb)
91 | ex.scan(fb, "")
92 | parts = ex.get_parts()
93 | rects = getRects(parts, gap=3)
94 | p = newPacker(rotation=False)
95 | print("RECTS")
96 | for r in rects:
97 | print(r)
98 | p.add_rect(*r)
99 | bins = [(1024, 1024)]
100 | for b in bins:
101 | p.add_bin(*b, count=10)
102 | p.pack()
103 | rects = p.rect_list()
104 | print(rects)
105 | print("LAYOUT")
106 | genSVG(bins[0], parts, rects, "box.svg")
107 |
--------------------------------------------------------------------------------
/electronics.py:
--------------------------------------------------------------------------------
1 | """
2 | Electronics for rover
3 | """
4 |
5 | import cqparts
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.params import *
8 | from cqparts.utils import CoordSystem
9 | from cqparts.search import register
10 |
11 | from . import battery
12 | from .battery import Battpack
13 | from .controller import Pizero, PCBBoard, BeagleBoneBlack
14 | from .mounted_board import MountedBoard
15 |
16 | from partref import PartRef
17 |
18 |
19 | class RoverBatt(Battpack):
20 | countX = Int(5)
21 | countY = Int(1)
22 | countZ = Int(1)
23 | batt = PartRef(battery.Li18650)
24 | # batt = PartRef(battery.AA)
25 |
26 |
27 | class RoverController(MountedBoard):
28 | board = PartRef(Pizero)
29 | # board = PartRef(BeagleBoneBlack)
30 | standoff = PositiveFloat(20)
31 |
32 |
33 | # temp motor driver
34 |
35 |
36 | class MotorBoard(PCBBoard):
37 | length = PositiveFloat(100)
38 | width = PositiveFloat(40)
39 |
40 |
41 | class MotorController(MountedBoard):
42 | board = PartRef(MotorBoard)
43 | standoff = PositiveFloat(15)
44 |
45 |
46 | @register(export="electronics")
47 | @register(export="showcase")
48 | class Electronics(cqparts.Assembly):
49 | battpack = PartRef(RoverBatt)
50 | controller = PartRef(RoverController)
51 | motorcontroller = PartRef(MotorController)
52 | target = PartRef() # what the electronics are bound to
53 |
54 | gap = PositiveFloat(5)
55 |
56 | def initialize_parameters(self):
57 | pass
58 |
59 | def off(self, offset):
60 | return CoordSystem(origin=(offset, 0, 0))
61 |
62 | def make_components(self):
63 | bp = self.battpack()
64 | c = self.controller(target=self.target)
65 | mc = self.motorcontroller(target=self.target)
66 | print(bp)
67 | comps = {"battpack": bp, "controller": c, "motorcontroller": mc}
68 | return comps
69 |
70 | def make_constraints(self):
71 | bp = self.components["battpack"]
72 | co = self.components["controller"]
73 | mc = self.components["motorcontroller"]
74 | off1 = bp.length / 2
75 | off2 = off1 + bp.length / 2 + co.width / 2 + self.gap
76 | off3 = off2 + co.width / 2 + self.gap + mc.width / 2
77 | constr = [
78 | Fixed(bp.mate_flat(), self.off(off1)),
79 | Fixed(co.mate_transverse(), self.off(off2)),
80 | Fixed(mc.mate_transverse(), self.off(off3)),
81 | ]
82 | return constr
83 |
84 | def make_alterations(self):
85 | # cut all the mount points out of the target
86 | if self.target is not None:
87 | pass
88 | pass
89 |
90 |
91 | class OtherBatt(Battpack):
92 | countX = Int(5)
93 | countY = Int(2)
94 | countZ = Int(1)
95 | batt = PartRef(battery.AA)
96 |
97 |
98 | class OtherController(MountedBoard):
99 | board = PartRef(BeagleBoneBlack)
100 |
101 |
102 | @register(export="electronics")
103 | class type1(Electronics):
104 | battpack = PartRef(OtherBatt)
105 | controller = PartRef(OtherController)
106 | motorcontroller = PartRef(MotorController)
107 | target = PartRef() # what the electronics are bound to
108 |
109 |
110 | if __name__ == "__main__":
111 | from cqparts.display import display
112 |
113 | e = Electronics()
114 | display(e)
115 |
--------------------------------------------------------------------------------
/robot_base_mount.py:
--------------------------------------------------------------------------------
1 | from cqparts.constraint import Mate, Coincident
2 |
3 | from cqparts_fasteners.fasteners.base import Fastener
4 | from cqparts_fasteners.utils import VectorEvaluator, Selector, Applicator
5 | from cqparts_fasteners.nuts import HexNut
6 |
7 | from cqparts_fasteners.male import MaleFastenerPart
8 |
9 | from cqparts_fasteners.fasteners.screw import Screw
10 | from cqparts_fasteners.params import HeadType, DriveType, ThreadType
11 |
12 | from cqparts.params import PositiveFloat, PositiveInt
13 |
14 | from cqparts.utils.geometry import CoordSystem
15 |
16 |
17 | class MountScrew(MaleFastenerPart):
18 | head = HeadType(default=("countersunk", {"diameter": 10.0, "height": 6.4}))
19 | drive = DriveType(default=("phillips", {"diameter": 5.0, "depth": 5, "width": 1}))
20 | thread = ThreadType(default=("iso68", {"diameter": 6.0, "pitch": 0.5}))
21 | neck_length = PositiveFloat(0, doc="length of neck")
22 | length = PositiveFloat(6, doc="screw's length")
23 | tip_length = PositiveFloat(0, doc="length of taper on a pointed tip")
24 |
25 |
26 | class MountNut(HexNut):
27 | edges = PositiveInt(6)
28 | width = PositiveFloat(8)
29 | heigth = PositiveFloat(6)
30 | thread = ThreadType(default=("iso68", {"diameter": 6, "pitch": 0.5}))
31 |
32 |
33 | class FlushFastener(Fastener):
34 | """
35 | Screw and Bolt fastener assembly.
36 | """
37 |
38 | Evaluator = VectorEvaluator
39 |
40 | class Selector(Selector):
41 | def get_components(self):
42 | effect_length = abs(
43 | self.evaluator.eval[-1].end_point - self.evaluator.eval[0].start_point
44 | )
45 |
46 | nut = MountNut()
47 | bolt = MountScrew(length=effect_length + nut.height + 0.3)
48 |
49 | return {"bolt": bolt, "nut": nut}
50 |
51 | def get_constraints(self):
52 | # bind fastener relative to its anchor; the part holding it in.
53 | first_part = self.evaluator.eval[0].part
54 | last_part = self.evaluator.eval[-1].part # last effected part
55 |
56 | return [
57 | Coincident(
58 | self.components["bolt"].mate_origin,
59 | Mate(
60 | first_part,
61 | self.evaluator.eval[0].start_coordsys - first_part.world_coords,
62 | ),
63 | ),
64 | Coincident(
65 | self.components["nut"].mate_origin,
66 | Mate(
67 | last_part,
68 | self.evaluator.eval[-1].end_coordsys - last_part.world_coords,
69 | ),
70 | ),
71 | ]
72 |
73 | class Applicator(Applicator):
74 | def apply_alterations(self):
75 | bolt = self.selector.components["bolt"]
76 | nut = self.selector.components["nut"]
77 | bolt_cutter = bolt.make_cutter() # cutter in local coords
78 | nut_cutter = nut.make_cutter()
79 |
80 | for effect in self.evaluator.eval:
81 | # bolt
82 | bolt_coordsys = bolt.world_coords - effect.part.world_coords
83 | effect.part.local_obj = effect.part.local_obj.cut(
84 | bolt_coordsys + bolt_cutter
85 | )
86 |
87 | # nut
88 | nut_coordsys = nut.world_coords - effect.part.world_coords
89 | effect.part.local_obj = effect.part.local_obj.cut(
90 | nut_coordsys + nut_cutter
91 | )
92 |
93 |
94 | import cqparts
95 | from cqparts_misc.basic.primatives import Box
96 | from .robot_base_mount import FlushFastener
97 | from cqparts.constraint import Fixed, Coincident
98 | from cqparts.constraint import Mate
99 |
100 |
101 | class Thing(cqparts.Assembly):
102 | def make_components(self):
103 | base = Box(length=20, width=30, height=15)
104 | top = Box(length=40, width=20, height=5)
105 | return {"base": base, "top": top, "fastener": FlushFastener(parts=[base, top])}
106 |
107 | def make_constraints(self):
108 | base = self.components["base"]
109 | top = self.components["top"]
110 | fastener = self.components["fastener"]
111 | return [
112 | Fixed(base.mate_bottom),
113 | Coincident(top.mate_bottom, base.mate_top),
114 | Coincident(fastener.mate_origin, top.mate_top),
115 | ]
116 |
117 |
118 | if __name__ == "__main__":
119 | from cqparts.display import display
120 |
121 | thing = Thing()
122 | display(thing)
123 |
--------------------------------------------------------------------------------
/circle_pan_tilt.py:
--------------------------------------------------------------------------------
1 | """
2 | Pan Tilt head mount
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 |
14 | from cqparts_motors.shaft import Shaft
15 |
16 | from .servo import SubMicro
17 |
18 |
19 | class MountTab(cqparts.Part):
20 | diameter = PositiveFloat(10)
21 | height = PositiveFloat(3)
22 | length = PositiveFloat(0)
23 | hole = PositiveFloat(2)
24 | extra = PositiveFloat(2)
25 |
26 | def make(self):
27 | mount = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.height)
28 | tab = (
29 | cq.Workplane("XY")
30 | .rect(
31 | self.diameter / 2 + self.length / 2 + self.extra,
32 | self.diameter,
33 | centered=False,
34 | )
35 | .extrude(self.height)
36 | .translate((0, -self.diameter / 2, 0))
37 | )
38 | mount = mount.union(tab)
39 | hole = cq.Workplane("XY").circle(self.hole / 2).extrude(self.height)
40 | mount = mount.cut(hole)
41 | mount = mount.translate((-(self.diameter / 2 + self.length / 2), 0, 0))
42 | return mount
43 |
44 |
45 | class Base(cqparts.Part):
46 | diameter = PositiveFloat(40)
47 | height = PositiveFloat(10)
48 | mounts = Int(4)
49 |
50 | _render = render_props(color=(100, 150, 100))
51 |
52 | def make(self):
53 | base = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.height)
54 | inc = 360 / float(self.mounts)
55 | for i in range(self.mounts):
56 | t = MountTab().local_obj
57 | t = t.translate((-self.diameter / 2, 0, 0))
58 | t = t.rotate((0, 0, 0), (0, 0, 1), i * inc)
59 | base = base.union(t)
60 | base = base.edges("|Z").fillet(1)
61 | return base
62 |
63 | def mate_top(self):
64 | return Mate(
65 | self,
66 | CoordSystem(origin=(0, 0, self.height), xDir=(1, 0, 0), normal=(0, 0, 1)),
67 | )
68 |
69 |
70 | class Yaw(cqparts.Part):
71 | diameter = PositiveFloat(40)
72 | height = PositiveFloat(10)
73 |
74 | _render = render_props(color=(100, 150, 100))
75 |
76 | def make(self):
77 | yaw = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.height)
78 | return yaw
79 |
80 | def mate_middle(self, offset=0):
81 | return Mate(
82 | self, CoordSystem(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1))
83 | )
84 |
85 |
86 | class Pitch(cqparts.Part):
87 | diameter = PositiveFloat(41)
88 | thickness = PositiveFloat(3)
89 | height = PositiveFloat(10)
90 |
91 | _render = render_props(color=(100, 150, 100))
92 |
93 | def make(self):
94 | pitch = (
95 | cq.Workplane("XY")
96 | .circle(self.diameter / 2 + self.thickness)
97 | .circle(self.diameter / 2)
98 | .extrude(self.height)
99 | )
100 | pitch = pitch.transformed(rotate=(0, 90, 0)).split(keepTop=True)
101 | rot = cq.Workplane("XZ").circle(self.height / 2).extrude(-self.thickness)
102 | rot = rot.translate((0, self.diameter / 2, self.height / 2))
103 | other_side = rot.mirror("XZ")
104 | rot = rot.union(other_side)
105 | pitch = pitch.union(rot)
106 | return pitch
107 |
108 |
109 | class CirclePanTilt(cqparts.Assembly):
110 | diameter = PositiveFloat(10)
111 | height = PositiveFloat(10)
112 | gap = PositiveFloat(2)
113 | feet = Int(4)
114 |
115 | def make_components(self):
116 | comps = {
117 | "base": Base(diameter=self.diameter, height=self.height, mounts=self.feet),
118 | "yaw": Yaw(diameter=self.diameter, height=self.height),
119 | "pitch": Pitch(diameter=self.diameter + self.gap, height=self.height),
120 | }
121 | return comps
122 |
123 | def make_constraints(self):
124 | base = self.components["base"]
125 | yaw = self.components["yaw"]
126 | pitch = self.components["pitch"]
127 | constr = [
128 | Fixed(base.mate_origin),
129 | Coincident(yaw.mate_origin, base.mate_top()),
130 | Coincident(pitch.mate_origin, yaw.mate_middle()),
131 | ]
132 | return constr
133 |
134 |
135 | if __name__ == "__main__":
136 | from cqparts.display import display
137 |
138 | # B = MountTab()
139 | # B = Base()
140 | # B = Yaw()
141 | # B = Pitch()
142 | B = CirclePanTilt()
143 | display(B)
144 |
--------------------------------------------------------------------------------
/mounted.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cqparts.params import *
4 | from cqparts.constraint import Fixed, Coincident
5 | from cqparts.constraint import Mate
6 | from cqparts.utils.geometry import CoordSystem
7 | from cqparts.display import render_props, display
8 |
9 | from cqparts.search import register
10 | from cqparts_fasteners.male import MaleFastenerPart
11 | from cqparts_fasteners.utils import VectorEvaluator
12 |
13 | from cqparts_fasteners.fasteners.screw import Screw
14 | from cqparts_fasteners.params import HeadType, DriveType, ThreadType
15 |
16 | from .partref import PartRef
17 |
18 | from .boss import Boss
19 | from .stepper import Stepper
20 | from .plank import Plank
21 |
22 |
23 | class SmallScrew(Screw):
24 | head = HeadType(
25 | default=("countersunk", {"diameter": 5.0, "height": 2.4}),
26 | doc="head type and parameters",
27 | )
28 | drive = DriveType(
29 | default=("phillips", {"diameter": 3.0, "depth": 2, "width": 0.5}),
30 | doc="screw drive type and parameters",
31 | )
32 | thread = ThreadType(
33 | default=("ball_screw", {"diameter": 3.0, "pitch": 0.5, "ball_radius": 1}),
34 | doc="thread type and parameters",
35 | )
36 | neck_length = PositiveFloat(0, doc="length of neck")
37 | length = PositiveFloat(10, doc="screw's length")
38 | tip_length = PositiveFloat(0, doc="length of taper on a pointed tip")
39 |
40 |
41 | class Mounted(cqparts.Assembly):
42 | base = PartRef(Boss())
43 | target = PartRef()
44 | screw = PartRef(SmallScrew)
45 |
46 | @classmethod
47 | def screw_name(cls, index):
48 | return "screw_%03i" % index
49 |
50 | def initialize_parameters(self):
51 | pass
52 |
53 | def make_components(self):
54 | base = self.base
55 | comps = {"base": base}
56 | for i, j in enumerate(base.mount_verts()):
57 | comps[self.screw_name(i)] = self.screw()
58 | return comps
59 |
60 | @property
61 | def get_base(self):
62 | return self.components["base"]
63 |
64 | def make_constraints(self):
65 | base = self.components["base"]
66 | constr = [Fixed(base.mate_origin)]
67 | constr.append(Coincident(self.target.mate_origin, base.mate_origin)),
68 | for i, j in enumerate(base.mount_verts()):
69 | # TODO covert to a Vector Evealuator
70 | m = Mate(
71 | self,
72 | CoordSystem(
73 | origin=(j.X, j.Y, j.Z + self.target.thickness),
74 | xDir=(1, 0, 0),
75 | normal=(0, 0, 1),
76 | ),
77 | )
78 | constr.append(
79 | Coincident(self.components[self.screw_name(i)].mate_origin, m)
80 | ),
81 | return constr
82 |
83 | def target_cut_out(self, X, Y, Z, part, target):
84 | coord = CoordSystem(origin=(X, Y, Z), xDir=(1, 0, 0), normal=(0, 0, 1))
85 | # cut the screw from the mount
86 | try:
87 | this = self.components["base"].local_obj
88 | this.cut((coord) + part.make_cutter())
89 | except:
90 | pass
91 | # cut the screw from the target
92 | target.local_obj.cut(
93 | (self.world_coords + coord - target.world_coords) + part.make_cutter()
94 | )
95 |
96 | def mate_base(self):
97 | return self.base.mate_origin
98 |
99 | def make_alterations(self):
100 | base = self.components["base"]
101 | # for i, j in enumerate(base.mount_verts()):
102 | # self.components[self.screw_name(i)].cutout(part=self.base)
103 | if self.target is not None:
104 | self.base.cutout(self.target)
105 | for i, j in enumerate(base.mount_verts()):
106 | p = self.components[self.screw_name(i)]
107 | self.target_cut_out(
108 | j.X, j.Y, j.Z + self.target.thickness, p, self.target
109 | )
110 |
111 | # put the board across
112 | def mate_transverse(self):
113 | return Mate(
114 | self, CoordSystem(origin=(0, 0, 0), xDir=(0, 1, 0), normal=(0, 0, 1))
115 | )
116 |
117 |
118 | # positioned mount for target testing
119 | class _DemoMount(cqparts.Assembly):
120 | def make_components(self):
121 | p = Plank(height=2.5)
122 | return {"m": Mounted(base=Boss(), target=p), "p": p}
123 |
124 | def make_constraints(self):
125 | return [Fixed(self.components["m"].mate_origin)]
126 |
127 |
128 | if __name__ == "__main__":
129 | from cqparts.display import display
130 |
131 | # p = MountedBoard(board=Pizero)
132 | # p = MountedBoard(board=BeagleBoneBlack)
133 | # p = Mounted()
134 | p = _DemoMount()
135 | display(p)
136 |
--------------------------------------------------------------------------------
/servo_horns.py:
--------------------------------------------------------------------------------
1 | """
2 | Servo Horns
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cqparts.params import *
8 | from cqparts.display import render_props, display
9 | from cqparts.constraint import Fixed, Coincident
10 | from cqparts.constraint import Mate
11 | from cqparts.utils.geometry import CoordSystem
12 | from cqparts.search import register
13 |
14 | from .multi import Arrange, Gallery
15 | from cqparts_motors.shaft import Shaft
16 |
17 | from .calculations import CalcTangents
18 |
19 |
20 | class ShowHorns(Gallery):
21 | pass
22 |
23 |
24 | # base object for servo horns
25 | class _ServoHorn(cqparts.Part):
26 | arms = Int(1)
27 |
28 | holes = Int(3)
29 | hole_size = PositiveFloat(1.5)
30 | hole_spacing = PositiveFloat(5)
31 |
32 | thickness = PositiveFloat(2.0)
33 |
34 | length = PositiveFloat(15)
35 | rad1 = PositiveFloat(4)
36 | rad2 = PositiveFloat(2)
37 |
38 | hub_size = PositiveFloat(4.6)
39 | hub_height = PositiveFloat(2.3)
40 |
41 | def arm(self):
42 | b = cq.Workplane("XY").circle(self.rad1).extrude(self.thickness)
43 | b2 = (
44 | cq.Workplane("XY")
45 | .circle(self.rad2)
46 | .extrude(self.thickness)
47 | .translate((self.length, 0, 0))
48 | )
49 | b = b.union(b2)
50 | # generate the tangents
51 | pts = CalcTangents((0, 0), self.rad1, (self.length, 0), self.rad2)
52 | base = cq.Workplane("XY").polyline(pts).close().extrude(self.thickness)
53 | b = b.union(base)
54 | # TODO cut holes
55 | for i in range(self.holes):
56 | hole = (
57 | cq.Workplane("XY")
58 | .circle(self.hole_size / 2)
59 | .extrude(self.thickness)
60 | .translate((self.length - i * self.holes, 0, 0))
61 | )
62 | b = b.cut(hole)
63 | return b
64 |
65 | def multiarm(self):
66 | ma = cq.Workplane("XY")
67 | inc = 360 / float(self.arms)
68 | for i in range(self.arms):
69 | print(i)
70 | a = self.arm().rotate((0, 0, 0), (0, 0, 1), inc * i)
71 | ma = ma.union(a)
72 | return ma
73 |
74 | def circle(self):
75 | ci = (
76 | cq.Workplane("XY")
77 | .circle(self.length / 2 + self.rad2)
78 | .extrude(self.thickness)
79 | )
80 | if self.holes >= 3:
81 | holes = (
82 | cq.Workplane("XY")
83 | .polygon(self.holes, self.length, forConstruction=True)
84 | .vertices()
85 | .circle(self.hole_size / 2)
86 | .extrude(self.thickness)
87 | )
88 | ci = ci.cut(holes)
89 | return ci
90 |
91 | def hub(self):
92 | hub = (
93 | cq.Workplane("XY")
94 | .circle((self.hub_size + self.thickness) / 2)
95 | .circle(self.hub_size / 2)
96 | .extrude(-self.hub_height)
97 | )
98 | return hub
99 |
100 | def mount(self):
101 | hub = cq.Workplane("XY").circle(self.hole_size).extrude(self.thickness)
102 | return hub
103 |
104 | def mate_top(self):
105 | return Mate(
106 | self,
107 | CoordSystem(
108 | origin=(0, 0, self.thickness), xDir=(1, 0, 0), normal=(0, 0, 1)
109 | ),
110 | )
111 |
112 |
113 | @register(export="horns")
114 | class SingleArm(_ServoHorn):
115 | def make(self):
116 | b = self.arm()
117 | b = b.union(self.hub())
118 | b = b.cut(self.mount())
119 | return b
120 |
121 |
122 | class _MultiArm(_ServoHorn):
123 | arms = Int(2)
124 |
125 | def make(self):
126 | b = self.multiarm()
127 | if self.arms > 2:
128 | b = b.edges("|Z").fillet(self.thickness / 2)
129 | b = b.union(self.hub())
130 | b = b.cut(self.mount())
131 | return b
132 |
133 |
134 | @register(export="horns")
135 | class TwoArm(_MultiArm):
136 | arms = Int(2)
137 |
138 |
139 | @register(export="horns")
140 | class FourArm(_MultiArm):
141 | arms = Int(4)
142 |
143 |
144 | @register(export="horns")
145 | class ServoArm(_MultiArm):
146 | arms = Int(6)
147 |
148 |
149 | @register(export="horns")
150 | class Circle(_ServoHorn):
151 | length = PositiveFloat(20)
152 | holes = Int(10)
153 |
154 | def make(self):
155 | c = self.circle()
156 | c = c.union(self.hub())
157 | c = c.cut(self.mount())
158 | return c
159 |
160 |
161 | if __name__ == "__main__":
162 | from cqparts.display import display
163 |
164 | sh = ShowHorns(offset=40)
165 | sh.add(SingleArm())
166 | for i in range(2, 7):
167 | sh.add(_MultiArm(arms=i))
168 | sh.add(Circle())
169 | display(sh)
170 |
--------------------------------------------------------------------------------
/merged/serve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import sys
3 | # working inside the lib
4 | sys.path.append('..')
5 | import cqparts_bucket
6 | import cqparts
7 | from cqparts_bucket import *
8 | import cqparts.search as cs
9 | from cqparts.display import display
10 |
11 | from flask import Flask, jsonify, abort , render_template, request
12 |
13 | from anytree import Node , RenderTree , NodeMixin
14 | from anytree.search import findall
15 | from anytree.resolver import Resolver
16 |
17 | from collections import OrderedDict
18 | app = Flask(__name__)
19 |
20 | class thing(NodeMixin):
21 | def __init__(self,name,parent=None,**kwargs):
22 | super(thing,self).__init__()
23 | self.name = name
24 | self.built = False
25 | self.classname = None
26 | self.parent = parent
27 | self.__dict__.update(kwargs)
28 |
29 | def get_path(self):
30 | args = "%s" % self.separator.join([""] + [str(node.name) for node in self.path])
31 | return str(args)
32 |
33 | def info(self):
34 | val = {
35 | 'path':self.get_path(),
36 | 'name':self.name,
37 | 'leaf': self.is_leaf,
38 | 'built': self.built,
39 | 'classname' : self.classname,
40 | }
41 | return val
42 |
43 | def dir(self):
44 | l = []
45 | if self.parent != None:
46 | up = self.parent.info()
47 | up['name'] = '..'
48 | l.append(up)
49 | for i in self.children:
50 | l.append(i.info())
51 | return l
52 |
53 | def __repr__(self):
54 | return ""
55 |
56 | class directory():
57 | def __init__(self,base,name):
58 | d = cs.index[name]
59 | self.d = d
60 | self.res = Resolver('name')
61 | self.base = base
62 | self.class_dict = {}
63 | self.k = {}
64 | self.root = thing(base)
65 | self.build()
66 |
67 | def build(self):
68 | for i in cs.index.keys():
69 | p = thing(i,parent=self.root)
70 | for j in cs.index[i]:
71 | b = thing(j,parent=p)
72 | for k in cs.index[i][j]:
73 | cn = type(k()).__module__+'.'+k.__name__
74 | t = thing(k.__name__,parent=b,c=k,classname=cn)
75 | self.class_dict[cn] = t
76 | self.k[self.base+'/'+i+'/'+j+'/'+k.__name__] = t
77 |
78 | def children(self,path):
79 | r = self.res.get(self.root,path)
80 | print r
81 |
82 | def exists(self,key):
83 | if key in self.k:
84 | return True
85 | return False
86 |
87 | def prefix(self,key):
88 | v = self.res.get(self.root,'/'+key)
89 | return v.dir()
90 |
91 | def build_part(self,params):
92 | key = params.pop('classname',None)
93 | if key in self.class_dict:
94 | fixes = {}
95 | for i in params:
96 | try:
97 | fixes[i] = float(params[i])
98 | except:
99 | pass
100 | item = self.class_dict[key]
101 | o = item.c(**fixes)
102 | display(o)
103 |
104 | def params(self,key):
105 | if self.exists(key) == False:
106 | abort(404)
107 | t = self.k[key]
108 | if t.built == False:
109 | t.inst = t.c()
110 | display(t.inst)
111 | t.built = True
112 | d = {}
113 | pi = t.inst.params().items()
114 | for i in pi:
115 | # only grab the floats for now
116 | if isinstance(i[1],float):
117 | d[i] = i[1]
118 | if isinstance(i[1],int):
119 | d[i] = i[1]
120 | info = t.info()
121 | info['params'] = d
122 | #if isinstance(t.inst,cqparts.Assembly):
123 | # info['tree'] = t.inst.tree_str()
124 | return info
125 |
126 |
127 | #d = directory(cqparts_bucket._namespace,'export')
128 | d = directory('cqparts','export')
129 | print(RenderTree(d.root))
130 |
131 | @app.route('/')
132 | def base():
133 | return render_template('list.html',items=d.root.dir())
134 |
135 | @app.route('/list')
136 | def list():
137 | return jsonify(d.items())
138 |
139 | @app.route('/list/')
140 | def subcat(modelname):
141 | return render_template('list.html',items=d.prefix(modelname))
142 |
143 | @app.route('/show/')
144 | def show_model(modelname):
145 | ob = d.params(modelname)
146 | return render_template('show.html',item=d.params(modelname))
147 | return jsonify(ob)
148 |
149 | @app.route('/rebuild',methods=['POST'])
150 | def rebuild():
151 | v = request.form.copy()
152 | d.build_part(v)
153 | return jsonify(request.form.items())
154 |
155 | if __name__ == '__main__':
156 | app.run(host='0.0.0.0',port=8089)
157 |
--------------------------------------------------------------------------------
/wheel.py:
--------------------------------------------------------------------------------
1 | """
2 | Generic Wheel
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 | from cqparts.search import register
14 |
15 | from .partref import PartRef
16 |
17 | from .manufacture import Printable
18 |
19 | # TODO Break into hub , spokes , rim and tyre
20 | # parametric constructavism for the win
21 |
22 | # base components for wheels
23 | class _Wheel(Printable):
24 | diameter = PositiveFloat(100)
25 | thickness = PositiveFloat(10)
26 | outset = PositiveFloat(10)
27 |
28 | def make_cutout(self, part):
29 | self.local_obj.cut((part.world_coords - self.world_coords) + part.cut_out())
30 |
31 |
32 | class Hub(_Wheel):
33 | thickness = PositiveFloat(15)
34 | diameter = PositiveFloat(15)
35 |
36 | def make(self):
37 | h = (
38 | cq.Workplane("XY")
39 | .circle(self.diameter / 2)
40 | .extrude(self.thickness + self.outset)
41 | )
42 | h = h.translate((0, 0, -self.thickness / 2))
43 | return h
44 |
45 |
46 | class CenterDisc(_Wheel):
47 | thickness = PositiveFloat(2)
48 | count = Int(5)
49 |
50 | def make(self):
51 | cd = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.thickness)
52 | cd = cd.translate((0, 0, -self.thickness / 2))
53 | inc = 360.0 / float(self.count)
54 | for i in range(self.count):
55 | h = cq.Workplane("XY").circle(self.diameter / 8).extrude(2 * self.thickness)
56 | h = h.translate((0, self.diameter / 4, -self.thickness))
57 | h = h.rotate((0, 0, 0), (0, 0, 1), float(i * inc))
58 | cd = cd.cut(h)
59 | return cd
60 |
61 |
62 | class Spokes(_Wheel):
63 | thickness = PositiveFloat(2)
64 | count = Int(5)
65 |
66 | def make(self):
67 | inc = 360.0 / float(self.count)
68 | cd = cq.Workplane("XY")
69 | for i in range(self.count):
70 | h = (
71 | cq.Workplane("XY")
72 | .rect(self.diameter / 2, self.thickness * 2)
73 | .extrude(4 * self.thickness)
74 | )
75 | h = h.translate((self.diameter / 4, 0, -2 * self.thickness))
76 | h = h.rotate((0, 0, 0), (0, 0, 1), float(i * inc))
77 | h = h.chamfer(0.5)
78 | cd = cd.add(h)
79 | return cd
80 |
81 |
82 | class Rim(_Wheel):
83 |
84 | # The rim profile / override for other wheels
85 | def profile(self):
86 | p = cq.Workplane("XZ").rect(self.thickness / 2, self.thickness)
87 | return p
88 |
89 | # over ride in sub classes
90 | def extra(self, rim):
91 | rim = rim.chamfer(self.thickness / 10)
92 |
93 | def make(self):
94 | r = self.profile()
95 | r = r.revolve(360, (self.diameter / 2, 1), (self.diameter / 2, 2))
96 | r = r.translate((-self.diameter / 2, 0, 0))
97 | self.extra(r)
98 | return r
99 |
100 |
101 | class Tyre(_Wheel):
102 | pass
103 |
104 |
105 | @register(export="wheel")
106 | class BuiltWheel(_Wheel):
107 | hub = PartRef(Hub)
108 | center_disc = PartRef(CenterDisc)
109 | rim = PartRef(Rim)
110 | count = Int(5)
111 |
112 | thickness = PositiveFloat(10)
113 |
114 | def make(self):
115 | hub = self.hub(thickness=self.thickness, outset=self.outset)
116 | center_disc = self.center_disc(
117 | thickness=self.thickness / 5, diameter=self.diameter, count=self.count
118 | )
119 | rim = self.rim(thickness=self.thickness, diameter=self.diameter)
120 | w = hub.local_obj
121 | w = w.union(center_disc.local_obj)
122 | w = w.union(rim.local_obj)
123 | return w
124 |
125 | def mate_wheel(self, flip=-1):
126 | return Mate(
127 | self, CoordSystem(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, flip))
128 | )
129 |
130 |
131 | @register(export="wheel")
132 | class SpokeWheel(BuiltWheel):
133 | center_disc = PartRef(Spokes)
134 |
135 |
136 | @register(export="wheel")
137 | class SimpleWheel(_Wheel):
138 | _render = render_props(color=(90, 90, 90))
139 |
140 | def make(self):
141 | sw = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.thickness)
142 | sw = sw.faces("|Z").chamfer(self.thickness / 6)
143 | return sw
144 |
145 | def mate_wheel(self, flip=-1):
146 | return Mate(
147 | self, CoordSystem(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, flip))
148 | )
149 |
150 |
151 | if __name__ == "__main__":
152 | from cqparts.display import display
153 |
154 | # B = SimpleWheel()
155 | # B = Hub(diameter=10,thickness=20)
156 | # B = Rim(diameter=200,thickness=40)
157 | # B = CenterDisc(thickness=3)
158 | # B = BuiltWheel(diameter=50)
159 | B = SpokeWheel(diameter=100, count=12)
160 | display(B)
161 |
--------------------------------------------------------------------------------
/board_test.py:
--------------------------------------------------------------------------------
1 |
2 | import cadquery
3 | import cqparts
4 | from cqparts import part
5 | from cqparts.constraint import Fixed, Coincident
6 | from cqparts.params import *
7 | from cqparts.display import display, render_props
8 | from cqparts.constraint import Mate
9 | from cqparts.utils import CoordSystem
10 | from cqparts_fasteners.fasteners.nutbolt import NutAndBoltFastener
11 | from cqparts_fasteners.fasteners.screw import ScrewFastener
12 |
13 |
14 | class Pin(cqparts.Part):
15 | diameter = PositiveFloat(5, doc="wheel diameter")
16 | length = PositiveFloat(10, doc="pin diameter")
17 | detent = PositiveFloat(1, doc="detent thickness")
18 | detent_outer = PositiveFloat(2, doc="detent outer")
19 |
20 | def make(self):
21 | p = (
22 | cadquery.Workplane("XY")
23 | .circle(self.diameter / 2)
24 | .extrude(-self.length)
25 | .faces("Z")
39 | .chamfer(0.4)
40 | )
41 | p = p.union(top)
42 | return p
43 |
44 |
45 | class Plank(cqparts.Part):
46 | length = PositiveFloat(100, doc="plank length")
47 | width = PositiveFloat(40, doc="plank width")
48 | thickness = PositiveFloat(10, doc="plank thickness")
49 |
50 | _render = render_props(template="wood", alpha=0.5)
51 |
52 | def make(self):
53 | pl = (
54 | cadquery.Workplane("XY")
55 | .box(self.length, self.width, self.thickness)
56 | .chamfer(0.4)
57 | )
58 | return pl
59 |
60 | @property
61 | def mate_bot(self):
62 | return Mate(
63 | self,
64 | CoordSystem(
65 | origin=(0, self.width / 2 + self.thickness / 2, 0),
66 | xDir=(0, 0, 1),
67 | normal=(0, -1, 0),
68 | ),
69 | )
70 |
71 | @property
72 | def mate_left(self):
73 | return Mate(
74 | self,
75 | CoordSystem(
76 | origin=(-self.length / 2 + self.thickness / 2, 0, 0),
77 | xDir=(1, 0, 0),
78 | normal=(0, 0, 1),
79 | ),
80 | )
81 |
82 | @property
83 | def mate_right(self):
84 | return Mate(
85 | self,
86 | CoordSystem(
87 | origin=(self.length / 2 - self.thickness / 2, 0, 0),
88 | xDir=(1, 0, 0),
89 | normal=(0, 0, 1),
90 | ),
91 | )
92 |
93 | @property
94 | def mate_right_up(self):
95 | return Mate(
96 | self,
97 | CoordSystem(
98 | origin=(self.length / 2 - self.thickness / 2, 0, -self.thickness),
99 | xDir=(1, 0, 0),
100 | normal=(0, 0, -1),
101 | ),
102 | )
103 |
104 | @property
105 | def mate_left_up(self):
106 | return Mate(
107 | self,
108 | CoordSystem(
109 | origin=(-self.length / 2 + self.thickness / 2, 0, -self.thickness),
110 | xDir=(1, 0, 0),
111 | normal=(0, 0, -1),
112 | ),
113 | )
114 |
115 |
116 | class Rect(cqparts.Assembly):
117 | length = PositiveFloat(100, doc="rect length")
118 | width = PositiveFloat(50, doc="rect width")
119 | depth = PositiveFloat(50, doc="rect depth")
120 | thickness = PositiveFloat(10, doc="plank thickness")
121 |
122 | def _name(self, int):
123 | return "item_%03i" % int
124 |
125 | def make_components(self):
126 | con = ScrewFastener # NutAndBoltFastener
127 | # con = NutAndBoltFastener
128 | base = Plank(length=self.length, width=self.width, thickness=self.thickness)
129 | left = Plank(length=self.width, width=self.depth, thickness=self.thickness)
130 | right = Plank(length=self.width, width=self.depth, thickness=self.thickness)
131 | fal = con(parts=[left, base])
132 | far = con(parts=[right, base])
133 | for i in range(4):
134 | print(self._name(i))
135 | comp = {"base": base, "left": left, "right": right, "fal": fal, "far": far}
136 |
137 | return comp
138 |
139 | def make_constraints(self):
140 | base = self.components["base"]
141 | left = self.components["left"]
142 | right = self.components["right"]
143 | fal = self.components["fal"]
144 | far = self.components["far"]
145 | cl = [
146 | Fixed(base.mate_origin),
147 | Coincident(left.mate_bot, base.mate_left),
148 | Coincident(right.mate_bot, base.mate_right),
149 | Coincident(fal.mate_origin, base.mate_left_up),
150 | Coincident(far.mate_origin, base.mate_right_up),
151 | ]
152 | return cl
153 |
154 |
155 | if __name__ == "__main__":
156 | from cqparts.display import display
157 |
158 | p = Rect()
159 | display(p)
160 |
--------------------------------------------------------------------------------
/battery.py:
--------------------------------------------------------------------------------
1 | """
2 | Battery
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 | from cqparts.search import register
14 |
15 | from .multi import Arrange
16 |
17 | # A parameter class for passing around objects
18 | # keep on using this , perhaps fold into cqparts.params
19 |
20 |
21 | class PartRef(Parameter):
22 | def type(self, value):
23 | return value
24 |
25 |
26 | # For showing a different name
27 | class _BattView(Arrange):
28 | pass
29 |
30 |
31 | # cylindrical battery
32 |
33 |
34 | @register(export="battery")
35 | class CylBattery(cqparts.Part):
36 | length = PositiveFloat(50.5)
37 | diameter = PositiveFloat(14.5)
38 | pos_height = PositiveFloat(1.0)
39 | pos_diam = PositiveFloat(5.5)
40 | _render = render_props(color=(100, 100, 100))
41 |
42 | def make(self):
43 | bat = (
44 | cq.Workplane("XY")
45 | .circle(self.diameter / 2)
46 | .extrude(self.length - self.pos_height)
47 | )
48 | # bat = bat.fillet(self.pos_height/2)
49 | pos = (
50 | cq.Workplane("XY")
51 | .workplane(offset=self.length - self.pos_height)
52 | .circle(self.pos_diam / 2)
53 | .extrude(self.pos_height)
54 | )
55 | # pos = pos.faces(">Z").fillet(self.pos_height/2)
56 | bat = bat.union(pos)
57 | return bat
58 |
59 |
60 | @register(export="battery")
61 | class AAA(CylBattery):
62 | length = PositiveFloat(44.5)
63 | diameter = PositiveFloat(10.5)
64 |
65 |
66 | @register(export="battery")
67 | class AA(CylBattery):
68 | length = PositiveFloat(50.5)
69 | diameter = PositiveFloat(14.5)
70 |
71 |
72 | @register(export="battery")
73 | class C(CylBattery):
74 | length = PositiveFloat(50.5)
75 | diameter = PositiveFloat(26.2)
76 |
77 |
78 | @register(export="battery")
79 | class D(CylBattery):
80 | length = PositiveFloat(50.5)
81 | diameter = PositiveFloat(34.2)
82 |
83 |
84 | @register(export="battery")
85 | class Li18650(CylBattery):
86 | length = PositiveFloat(65.2)
87 | diameter = PositiveFloat(18.6)
88 |
89 |
90 | @register(export="battery_pack")
91 | class Battpack(cqparts.Assembly):
92 | " a 3 dim array of batteries "
93 | countX = Int(3)
94 | countY = Int(2)
95 | countZ = Int(1)
96 | batt = PartRef(AA)
97 |
98 | def initialize_parameters(self):
99 | self.batts = []
100 | b = self.batt()
101 | self.offset = b.diameter
102 | self.zoffset = b.length
103 | self.total_batts = self.countX * self.countY * self.countZ
104 | self.width = self.countX * self.offset
105 | self.length = self.countZ * self.zoffset
106 |
107 | @classmethod
108 | def item_name(cls, index):
109 | return "battery_%03i" % index
110 |
111 | def make_components(self):
112 | items = {}
113 | for i in range(self.total_batts):
114 | self.batts.append(self.batt())
115 | items[self.item_name(i)] = self.batts[i]
116 | return items
117 |
118 | def make_constraints(self):
119 | constraints = []
120 | length = len(self.batts)
121 | total = length * self.offset
122 | # tripple loop of awesome
123 | count = 0
124 | for i in range(self.countX):
125 | for j in range(self.countY):
126 | for k in range(self.countZ):
127 | pos = ((j * self.offset, i * self.offset, self.zoffset * k),)
128 | constraints.append(
129 | Fixed(
130 | self.batts[count].mate_origin,
131 | CoordSystem(origin=pos, xDir=(1, 0, 0), normal=(0, 0, 1)),
132 | )
133 | )
134 | count = count + 1
135 | return constraints
136 |
137 | def mate_flat(self, flip=-1):
138 |
139 | return Mate(
140 | self,
141 | CoordSystem(
142 | origin=(
143 | -self.offset / 2.0,
144 | self.width / 2.0 - self.offset / 2,
145 | self.length / 2,
146 | ),
147 | xDir=(0, 0, 1),
148 | normal=(1, 0, 0),
149 | ),
150 | )
151 |
152 |
153 | @register(export="battery_pack")
154 | class FlatBatt(cqparts.Assembly):
155 | countX = Int(5)
156 | countY = Int(1)
157 | countZ = Int(1)
158 | batt = PartRef(Li18650)
159 |
160 | def make_components(self):
161 | return {
162 | "m": Battpack(
163 | batt=self.batt,
164 | countX=self.countX,
165 | countY=self.countY,
166 | countZ=self.countZ,
167 | )
168 | }
169 |
170 | def make_constraints(self):
171 | return [Fixed(self.components["m"].mate_flat())]
172 |
173 |
174 | if __name__ == "__main__":
175 | from cqparts.display import display
176 |
177 | # bv = _BattView()
178 | # bv.add(AAA())
179 | # bv.add(AA())
180 | # bv.add(C())
181 | # bv.add(D())
182 | # bv = Battpack(batt=Li18650,countX=5,countY=3,countZ=2)
183 | # bv = Battpack(batt=Li18650,countX=5,countY=1,countZ=1)
184 | bv = _FlatBatt()
185 | display(bv)
186 |
--------------------------------------------------------------------------------
/controller.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cadquery import Solid
4 | from cqparts.params import *
5 | from cqparts.display import render_props, display
6 | from cqparts.constraint import Fixed, Coincident
7 | from cqparts.constraint import Mate
8 | from cqparts.utils.geometry import CoordSystem
9 |
10 | from cqparts.search import register
11 |
12 | from .multi import Arrange, Gallery
13 |
14 |
15 | class _Boards(Gallery):
16 | pass
17 |
18 |
19 | class PCBBoard(cqparts.Part):
20 | # Parameters
21 | length = PositiveFloat(65)
22 | width = PositiveFloat(30)
23 | thickness = PositiveFloat(1)
24 | corner_radius = PositiveFloat(4)
25 |
26 | hole_size = PositiveFloat(2.8)
27 | hole_length = PositiveFloat()
28 | hole_width = PositiveFloat()
29 |
30 | # default appearance
31 | _render = render_props(color=(10, 100, 10))
32 |
33 | def initialize_parameters(self):
34 | if self.hole_length is None:
35 | self.hole_length = self.length - 3 * self.hole_size
36 | if self.hole_width is None:
37 | self.hole_width = self.width - 3 * self.hole_size
38 |
39 | # This returns the verts that the screws get aligned to
40 | def mount_points(self, offset=0):
41 | wp = cq.Workplane("XY", origin=(0, 0, offset))
42 | h = wp.rect(self.hole_length, self.hole_width, forConstruction=True).vertices()
43 | return h
44 |
45 | def mount_verts(self, offset=0):
46 | return self.mount_points().objects
47 |
48 | def make(self):
49 | wp = cq.Workplane("XY")
50 | board = wp.box(length=self.length, width=self.width, height=self.thickness)
51 | if self.corner_radius > 0:
52 | board.edges("|Z").fillet(self.corner_radius)
53 | holes = (
54 | self.mount_points(offset=-self.thickness)
55 | .circle(self.hole_size / 2)
56 | .extrude(self.thickness * 2)
57 | )
58 | board = board.cut(holes)
59 | return board
60 |
61 |
62 | @register(export="controller")
63 | class Arduino(PCBBoard):
64 | # Parameters
65 | length = PositiveFloat(68.6)
66 | width = PositiveFloat(53.3)
67 | thickness = PositiveFloat(1)
68 |
69 | hole_size = PositiveFloat(3)
70 |
71 | # This returns the verts that the screws get aligned to
72 | def mount_points(self, offset=0):
73 | wp = cq.Workplane("XY", origin=(-self.length / 2, -self.width / 2, offset))
74 | pts = [(14, 2.5), (65.5, 7), (65.5, 35), (15.3, 50.5)]
75 | wp = cq.Workplane("XY", origin=(-self.length / 2, -self.width / 2, offset))
76 | h = wp.moveTo(*pts[0]).polyline(pts[1:]).vertices()
77 | return h
78 |
79 | def make(self):
80 | wp = cq.Workplane("XY")
81 | board = wp.box(length=self.length, width=self.width, height=self.thickness)
82 | holes = (
83 | self.mount_points(offset=-self.thickness)
84 | .circle(self.hole_size / 2)
85 | .extrude(self.thickness * 2)
86 | )
87 | board = board.cut(holes)
88 | return board
89 |
90 |
91 | @register(export="controller")
92 | class Pizero(PCBBoard):
93 | length = PositiveFloat(65)
94 | width = PositiveFloat(30)
95 | thickness = PositiveFloat(1)
96 | corner_radius = PositiveFloat(4)
97 |
98 | hole_size = PositiveFloat(2.8)
99 | hole_length = PositiveFloat(58)
100 | hole_width = PositiveFloat(23)
101 |
102 |
103 | @register(export="controller")
104 | class BeagleBoneBlack(PCBBoard):
105 | length = PositiveFloat(86.36)
106 | width = PositiveFloat(54.61)
107 | thickness = PositiveFloat(1)
108 | corner_radius = PositiveFloat(12.7)
109 | corner_radius2 = PositiveFloat(6.35)
110 | hole_size = PositiveFloat(4.45)
111 |
112 | # This returns the verts that the screws get aligned to
113 | def mount_points(self, offset=0):
114 | pts = [
115 | (14.61, 3.18),
116 | (14.61, 3.18 + 48.39),
117 | (14.61 + 66.10, 6.35),
118 | (14.61 + 66.10, 6.35 + 41.91),
119 | ]
120 | wp = cq.Workplane("XY", origin=(-self.length / 2, -self.width / 2, offset))
121 | h = wp.moveTo(*pts[0]).polyline(pts[1:]).vertices()
122 | return h
123 |
124 | def make(self):
125 | wp = cq.Workplane("XY")
126 | board = wp.box(length=self.length, width=self.width, height=self.thickness)
127 | board.edges("|Z and >X").fillet(self.corner_radius)
128 | board.edges("|Z and X").chamfer(self.chamfer)
36 | return base
37 |
38 | # TODO mountpoints for stuff
39 |
40 | def mate_back(self, offset=5):
41 | return Mate(
42 | self,
43 | CoordSystem(
44 | origin=(-self.length / 2 + offset, 0, self.thickness),
45 | xDir=(1, 0, 0),
46 | normal=(0, 0, 1),
47 | ),
48 | )
49 |
50 | def mate_front(self, offset=0):
51 | return Mate(
52 | self,
53 | CoordSystem(
54 | origin=(self.length / 2 - offset, 0, self.thickness),
55 | xDir=(1, 0, 0),
56 | normal=(0, 0, 1),
57 | ),
58 | )
59 |
60 | def mate_RL(self, offset=0):
61 | return Mate(
62 | self,
63 | CoordSystem(
64 | origin=(-self.length / 2 + offset, self.width / 2, 0),
65 | xDir=(1, 0, 0),
66 | normal=(0, 0, -1),
67 | ),
68 | )
69 |
70 | def mate_RR(self, offset=0):
71 | return Mate(
72 | self,
73 | CoordSystem(
74 | origin=(-self.length / 2 + offset, -self.width / 2, 0),
75 | xDir=(-1, 0, 0),
76 | normal=(0, 0, -1),
77 | ),
78 | )
79 |
80 |
81 | class ThisWheel(SpokeWheel):
82 | diameter = PositiveFloat(90)
83 | thickness = PositiveFloat(15)
84 | outset = PositiveFloat(10)
85 |
86 |
87 | class ThisStepper(Stepper):
88 | width = PositiveFloat(30)
89 | height = PositiveFloat(30)
90 | length = PositiveFloat(30)
91 | hole_spacing = PositiveFloat(15)
92 |
93 |
94 | @register(export="showcase", showcase="showcase")
95 | class Rover(cqparts.Assembly):
96 | length = PositiveFloat(280)
97 | width = PositiveFloat(170)
98 | chamfer = PositiveFloat(55)
99 | thickness = PositiveFloat(6)
100 | wheel = PartRef(ThisWheel)
101 | stepper = PartRef(Stepper)
102 | electronics = PartRef(type1)
103 | sensors = PartRef(PanTilt)
104 |
105 | def make_components(self):
106 | base = RobotBase(
107 | length=self.length,
108 | width=self.width,
109 | chamfer=self.chamfer,
110 | thickness=self.thickness,
111 | )
112 | comps = {
113 | "base": base,
114 | "electronics": self.electronics(target=base),
115 | "sensors": self.sensors(target=base),
116 | "Ldrive_b": MountedStepper(
117 | stepper=self.stepper, driven=self.wheel, target=base
118 | ),
119 | "Rdrive_b": MountedStepper(
120 | stepper=self.stepper, driven=self.wheel, target=base
121 | ),
122 | "Ldrive_f": MountedStepper(
123 | stepper=self.stepper, driven=self.wheel, target=base
124 | ),
125 | "Rdrive_f": MountedStepper(
126 | stepper=self.stepper, driven=self.wheel, target=base
127 | ),
128 | }
129 | return comps
130 |
131 | def make_constraints(self):
132 | constr = [
133 | Fixed(self.components["base"].mate_origin, CoordSystem(origin=(0, 0, 60))),
134 | Coincident(
135 | self.components["electronics"].mate_origin,
136 | self.components["base"].mate_back(),
137 | ),
138 | Coincident(
139 | self.components["sensors"].mate_front(),
140 | self.components["base"].mate_front(),
141 | ),
142 | Coincident(
143 | self.components["Ldrive_b"].mate_corner(flip=-1),
144 | self.components["base"].mate_RL(),
145 | ),
146 | Coincident(
147 | self.components["Rdrive_b"].mate_corner(flip=1),
148 | self.components["base"].mate_RR(),
149 | ),
150 | Coincident(
151 | self.components["Ldrive_f"].mate_corner(flip=1),
152 | self.components["base"].mate_RL(offset=self.length - self.chamfer),
153 | ),
154 | Coincident(
155 | self.components["Rdrive_f"].mate_corner(flip=-1),
156 | self.components["base"].mate_RR(offset=self.length - self.chamfer),
157 | ),
158 | ]
159 | return constr
160 |
161 |
162 | if __name__ == "__main__":
163 | from cqparts.display import display
164 |
165 | # B = RobotBase()
166 | B = Rover()
167 | display(B)
168 |
--------------------------------------------------------------------------------
/driver.py:
--------------------------------------------------------------------------------
1 |
2 | import cadquery as cq
3 |
4 | import cqparts
5 | from cqparts.params import Parameter, PositiveFloat
6 | from cqparts.display import render_props
7 | from cqparts.constraint import Fixed, Coincident
8 | from cqparts.constraint import Mate
9 | from cqparts.utils.geometry import CoordSystem
10 |
11 | from .pulley import Pulley
12 | from .belt import Belt
13 | from cqparts_motors.stepper import Stepper
14 | from .idler import Idler
15 | from .coupling import Coupling
16 | from .threaded import Threaded
17 |
18 | from .partref import PartRef
19 |
20 |
21 | class Drive(cqparts.Assembly):
22 | threaded = PartRef(Threaded)
23 | lift = PositiveFloat(10)
24 | length = PositiveFloat(100)
25 |
26 | def make_components(self):
27 | comps = {"drive": self.threaded(length=self.length)}
28 | return comps
29 |
30 | def make_constraints(self):
31 | constr = [
32 | Fixed(
33 | self.components["drive"].mate_origin,
34 | CoordSystem((0, 0, 0), (0, 1, 0), (1, 0, 0)),
35 | )
36 | ]
37 | return constr
38 |
39 | def mate_mount(self, offset=0):
40 | return Mate(
41 | self, CoordSystem(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 0, 1))
42 | )
43 |
44 | def make_alterations(self):
45 | pass
46 |
47 | class BeltAssembly(Drive):
48 | spacing = PositiveFloat(20)
49 | pulley = PartRef(Pulley)
50 |
51 | def make_components(self):
52 | pulley_rad = self.pulley().rad
53 | comp = {
54 | "p": self.pulley(),
55 | "p2": self.pulley(),
56 | "belt": Belt(spacing=self.spacing, rad=pulley_rad),
57 | }
58 | return comp
59 |
60 | def make_constraints(self):
61 | constr = [
62 | Fixed(self.components["p"].mate_origin),
63 | Fixed(
64 | self.components["p2"].mate_origin,
65 | CoordSystem((0, -self.spacing, 0), (1, 0, 0), (0, 0, 1)),
66 | ),
67 | Fixed(self.components["belt"].mate_origin),
68 | ]
69 | return constr
70 |
71 | def pulley_A_mate(self, offset=0):
72 | return Mate(
73 | self, CoordSystem(origin=(-offset, 0, 0), xDir=(0, -1, 0), normal=(1, 0, 0))
74 | )
75 |
76 | def pulley_B_mate(self, offset=0):
77 | return Mate(
78 | self,
79 | CoordSystem(
80 | origin=(-offset, -self.spacing, 0), xDir=(0, 1, 0), normal=(1, 0, 0)
81 | ),
82 | )
83 |
84 | def mate_mount(self, offset=0):
85 | return Mate(
86 | self, CoordSystem(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 0))
87 | )
88 |
89 |
90 | class ThreadedDrive(Drive):
91 | coupling = PartRef(Coupling)
92 | stepper = PartRef(Stepper)
93 | threaded = PartRef(Threaded)
94 | lift = PositiveFloat(0)
95 | length = PositiveFloat(100)
96 |
97 | def make_components(self):
98 | comp = {
99 | "stepper": self.stepper(),
100 | "coupling": self.coupling(),
101 | "thread": self.threaded(length=self.length),
102 | }
103 | return comp
104 |
105 | def make_constraints(self):
106 | constr = [
107 | Fixed(self.components["stepper"].mate_origin),
108 | Coincident(self.components["coupling"].mate_input(), self.mate_tip()),
109 | Coincident(
110 | self.components["thread"].mate_origin,
111 | self.components["coupling"].mate_output(),
112 | ),
113 | ]
114 | return constr
115 |
116 | def mate_tip(self):
117 | return Mate(
118 | self,
119 | CoordSystem(
120 | origin=(0, 0, self.components["stepper"].shaft_length),
121 | xDir=(1, 0, 0),
122 | normal=(0, 0, 1),
123 | ),
124 | )
125 |
126 | def mate_mount(self, offset=0):
127 | return Mate(
128 | self, CoordSystem(origin=(0, 0, 0), xDir=(0, 0, 1), normal=(1, 0, 0))
129 | )
130 |
131 |
132 | class BeltDrive(Drive):
133 | stepper = PartRef(Stepper)
134 | idler = PartRef(Idler)
135 | pulley = PartRef(Pulley)
136 |
137 | height = PositiveFloat(300)
138 | width = PositiveFloat(300)
139 | length = PositiveFloat(100)
140 | lift = PositiveFloat(0)
141 |
142 | def make_components(self):
143 | comp = {
144 | "stepper": self.stepper(),
145 | "drive": BeltAssembly(pulley=self.pulley, spacing=self.length),
146 | "idler": self.idler(),
147 | }
148 | return comp
149 |
150 | def make_constraints(self):
151 | constr = [
152 | Fixed(self.components["stepper"].mate_origin),
153 | Coincident(
154 | self.components["drive"].pulley_A_mate(offset=10),
155 | self.components["stepper"].mate_origin,
156 | ),
157 | Coincident(
158 | self.components["idler"].mate_origin,
159 | self.components["drive"].pulley_B_mate(offset=10),
160 | ),
161 | ]
162 | return constr
163 |
164 | def mate_mount(self, offset=0):
165 | return Mate(
166 | self, CoordSystem(origin=(0, 0, 0), xDir=(1, 0, 0), normal=(0, 1, 0))
167 | )
168 |
169 |
170 | ## test setup
171 | class MyPulley(Pulley):
172 | rad = PositiveFloat(7)
173 |
174 |
175 | if __name__ == "__main__":
176 | from cqparts.display import display
177 |
178 | p = BeltDrive(pulley=MyPulley, length=100)
179 | # p = ThreadedDrive(length=50)
180 | display(p)
181 |
--------------------------------------------------------------------------------
/merged/dc.py:
--------------------------------------------------------------------------------
1 | # DC motor cqparts model
2 | # 2018 Simon Kirkby obeygiantrobot@gmail.com
3 |
4 | import cadquery as cq
5 |
6 | import cqparts
7 | from cqparts.params import *
8 | from cqparts.display import render_props
9 | from cqparts.constraint import Fixed, Coincident
10 | from cqparts.constraint import Mate
11 | from cqparts.utils.geometry import CoordSystem
12 |
13 | import math
14 | from cqparts_motors import shaft
15 |
16 | from cqparts.catalogue import JSONCatalogue
17 |
18 | # defines the profile of the motor , returns a wire
19 | def _profile(shape, diam, thickness):
20 | s = cq.Workplane("XY")
21 | if shape == "circle":
22 | p = s.circle(diam / 2)
23 | return p
24 | if shape == "flat":
25 | r = diam / 2
26 | ht = thickness / 2
27 | ip = math.sqrt(r * r - ht * ht)
28 | p = (
29 | s.moveTo(0, ht)
30 | .lineTo(ip, ht)
31 | .threePointArc((r, 0), (ip, -ht))
32 | .lineTo(0, -ht)
33 | .mirrorY()
34 | )
35 | return p
36 | if shape == "rect":
37 | p = s.rect(thickness / 2, diam / 2)
38 | return p
39 |
40 |
41 | # the motor cup
42 | class _Cup(cqparts.Part):
43 | height = PositiveFloat(25.1, doc="cup length")
44 | diam = PositiveFloat(20.4, doc="cup diameter")
45 | thickness = PositiveFloat(15.4, doc="cup thickness for flat profile")
46 | hole_spacing = PositiveFloat(12.4, doc="distance between the holes")
47 | hole_size = PositiveFloat(2, doc="hole size")
48 | step_diam = PositiveFloat(12, doc="step diameter")
49 | step_height = PositiveFloat(0, doc="height if step, if zero no step")
50 | bush_diam = PositiveFloat(6.15, doc="diameter of the bush")
51 | bush_height = PositiveFloat(1.6, doc="height of the bush")
52 |
53 | profile = String("flat", doc="profile shape (circle|flat|rect)")
54 |
55 | def make(self):
56 | # grab the correct profile
57 | s = cq.Workplane("XY")
58 | cup = _profile(self.profile, self.diam, self.thickness).extrude(-self.height)
59 | if self.step_height > 0:
60 | st = s.circle(self.step_diam / 2).extrude(self.step_height)
61 | cup = cup.union(st)
62 | bush = (
63 | s.workplane(offset=self.step_height)
64 | .circle(self.bush_diam / 2)
65 | .extrude(self.bush_height)
66 | )
67 | cup = cup.union(bush)
68 | return cup
69 |
70 | def get_cutout(self, clearance=0):
71 | return (
72 | cq.Workplane("XY", origin=(0, 0, 0))
73 | .circle((self.diam / 2) + clearance)
74 | .extrude(10)
75 | )
76 |
77 | @property
78 | def mate_bottom(self):
79 | return Mate(
80 | self,
81 | CoordSystem(origin=(0, 0, -self.height), xDir=(1, 0, 0), normal=(0, 0, 1)),
82 | )
83 |
84 |
85 | class BackCover(cqparts.Part):
86 | height = PositiveFloat(6, doc="back length")
87 | diam = PositiveFloat(20.4, doc="back diameter")
88 | thickness = PositiveFloat(15.4, doc="back thickness for flat profile")
89 | profile = String("flat", doc="profile shape (circle|flat|rect)")
90 | bush_diam = PositiveFloat(6.15, doc="diameter of the bush")
91 | bush_height = PositiveFloat(1.6, doc="height of the bush")
92 |
93 | _render = render_props(color=(50, 255, 255))
94 |
95 | def make(self):
96 | # grab the correct profile
97 | s = cq.Workplane("XY")
98 | back = (
99 | s.workplane(offset=-self.height)
100 | .circle(self.bush_diam / 2)
101 | .extrude(-self.bush_height)
102 | )
103 | if self.height > 0:
104 | b = _profile(self.profile, self.diam, self.thickness).extrude(-self.height)
105 | back = back.union(b)
106 | return back
107 |
108 |
109 | class DCMotor(cqparts.Assembly):
110 |
111 | height = PositiveFloat(25.1, doc="back length")
112 | diam = PositiveFloat(20.4, doc="back diameter")
113 | thickness = PositiveFloat(15.4, doc="back thickness for flat profile")
114 | profile = String("flat", doc="profile shape (circle|flat|rect)")
115 |
116 | bush_diam = PositiveFloat(6.15, doc="diameter of the bush")
117 | bush_height = PositiveFloat(1.6, doc="height of the bush")
118 |
119 | shaft_type = shaft.Shaft # replace with other shaft
120 | shaft_length = PositiveFloat(11.55, doc="length of the shaft")
121 | shaft_diam = PositiveFloat(2, doc="diameter of the shaft")
122 |
123 | cover_height = PositiveFloat(0, doc="back cover height")
124 |
125 | def make_components(self):
126 | return {
127 | "body": _Cup(
128 | height=self.height,
129 | thickness=self.thickness,
130 | diam=self.diam,
131 | profile=self.profile,
132 | bush_diam=self.bush_diam,
133 | bush_height=self.bush_height,
134 | ),
135 | "shaft": self.shaft_type(length=self.shaft_length, diam=self.shaft_diam),
136 | "back": BackCover(
137 | height=self.cover_height,
138 | thickness=self.thickness,
139 | diam=self.diam,
140 | profile=self.profile,
141 | bush_diam=self.bush_diam,
142 | bush_height=self.bush_height,
143 | ),
144 | }
145 |
146 | def make_constraints(self):
147 | return [
148 | Fixed(self.components["body"].mate_origin),
149 | Coincident(
150 | self.components["shaft"].mate_origin,
151 | self.components["body"].mate_origin,
152 | ),
153 | Coincident(
154 | self.components["back"].mate_origin, self.components["body"].mate_bottom
155 | ),
156 | ]
157 |
158 |
159 | if __name__ == "__main__":
160 | from cqparts.display import render_props, display
161 |
162 | dc = DCMotor()
163 | display(dc)
164 |
--------------------------------------------------------------------------------
/mounted_board.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | from cqparts.params import *
4 | from cqparts.constraint import Fixed, Coincident
5 | from cqparts.constraint import Mate
6 | from cqparts.utils.geometry import CoordSystem
7 | from cqparts.display import render_props, display
8 |
9 | from cqparts.search import register
10 | from cqparts_fasteners.male import MaleFastenerPart
11 |
12 | from cqparts_fasteners.fasteners.screw import Screw
13 | from cqparts_fasteners.params import HeadType, DriveType, ThreadType
14 |
15 | from .controller import Pizero, BeagleBoneBlack, Arduino
16 | from .plank import Plank
17 |
18 | from partref import PartRef
19 |
20 |
21 | @register(export="board")
22 | class MountedBoard(cqparts.Assembly):
23 | board = PartRef(Pizero)
24 | standoff = Int(10)
25 | target = PartRef()
26 |
27 | @classmethod
28 | def screw_name(cls, index):
29 | return "screw_%03i" % index
30 |
31 | @classmethod
32 | def standoff_name(cls, index):
33 | return "standoff_%03i" % index
34 |
35 | def initialize_parameters(self):
36 | b = self.board()
37 | self.length = b.length
38 | self.width = b.width
39 |
40 | def make_components(self):
41 | board = self.board()
42 | comps = {"board": board}
43 | self.length = PositiveFloat(board.length)
44 | self.width = PositiveFloat(board.width)
45 | for i, j in enumerate(board.mount_verts()):
46 | comps[self.screw_name(i)] = ComputerScrew()
47 | comps[self.standoff_name(i)] = Standoff(length=self.standoff)
48 | return comps
49 |
50 | def make_constraints(self):
51 | board = self.components["board"]
52 | constr = [
53 | Fixed(
54 | board.mate_origin,
55 | CoordSystem(origin=(0, 0, self.standoff + board.thickness / 2)),
56 | )
57 | ]
58 | for i, j in enumerate(board.mount_verts()):
59 | m = Mate(
60 | self,
61 | CoordSystem(
62 | origin=(j.X, j.Y, self.standoff + board.thickness),
63 | xDir=(1, 0, 0),
64 | normal=(0, 0, 1),
65 | ),
66 | )
67 | constr.append(
68 | Coincident(self.components[self.screw_name(i)].mate_origin, m)
69 | ),
70 | constr.append(
71 | Coincident(
72 | self.components[self.standoff_name(i)].mate_top(),
73 | Mate(
74 | self,
75 | CoordSystem(
76 | origin=(j.X, j.Y, self.standoff),
77 | xDir=(1, 0, 0),
78 | normal=(0, 0, 1),
79 | ),
80 | ),
81 | )
82 | )
83 | return constr
84 |
85 | def make_alterations(self):
86 | board = self.components["board"]
87 | print(self)
88 | if self.target is not None:
89 | for i, j in enumerate(board.mount_verts()):
90 | self.components[self.standoff_name(i)].make_cutout(part=self.target)
91 |
92 | # put the board across
93 | def mate_transverse(self):
94 | return Mate(
95 | self, CoordSystem(origin=(0, 0, 0), xDir=(0, 1, 0), normal=(0, 0, 1))
96 | )
97 |
98 |
99 | # standoff widget
100 | class Standoff(cqparts.Part):
101 | size = PositiveFloat(3)
102 | length = PositiveFloat(15)
103 | _render = render_props(template="steel")
104 |
105 | def make(self):
106 | so = cq.Workplane("XY").circle(self.size / 2).extrude(-self.size)
107 | if self.length > 0:
108 | hx = cq.Workplane("XY").polygon(6, self.size * 2).extrude(self.length)
109 | so = so.union(hx)
110 | return so
111 |
112 | def make_cutout(self, part, clearance=0):
113 | part = part.local_obj.cut(
114 | (self.world_coords - part.world_coords) + self.cutout(clearance=clearance)
115 | )
116 |
117 | def cutout(self, clearance=0):
118 | so = cq.Workplane("XY").circle(self.size / 2).extrude(-self.length)
119 | return so
120 |
121 | def mate_top(self):
122 | return Mate(
123 | self,
124 | CoordSystem(origin=(0, 0, self.length), xDir=(1, 0, 0), normal=(0, 0, 1)),
125 | )
126 |
127 |
128 | class ComputerScrew(Screw):
129 | head = HeadType(
130 | default=(
131 | "hex_flange",
132 | {"width": 5.0, "height": 2.4, "washer_diameter": 6.2, "washer_height": 0.2},
133 | ),
134 | doc="head type and parameters",
135 | )
136 | drive = DriveType(
137 | default=("phillips", {"diameter": 3.0, "depth": 2, "width": 0.5}),
138 | doc="screw drive type and parameters",
139 | )
140 | thread = ThreadType(
141 | default=("ball_screw", {"diameter": 3.0, "pitch": 0.5, "ball_radius": 1}),
142 | doc="thread type and parameters",
143 | )
144 | neck_length = PositiveFloat(0, doc="length of neck")
145 | length = PositiveFloat(5, doc="screw's length")
146 | tip_length = PositiveFloat(0, doc="length of taper on a pointed tip")
147 |
148 |
149 | @register(export="board")
150 | @register(export="showcase")
151 | class PizeroBoard(MountedBoard):
152 | board = PartRef(Pizero)
153 |
154 |
155 | @register(export="board")
156 | class ArduinoBoard(MountedBoard):
157 | board = PartRef(Arduino)
158 |
159 |
160 | @register(export="board")
161 | class BeagleBoard(MountedBoard):
162 | board = PartRef(BeagleBoneBlack)
163 |
164 |
165 | # positioned mount for target testing
166 | class _DemoBoard(cqparts.Assembly):
167 | def make_components(self):
168 | p = Plank()
169 | return {"m": MountedBoard(target=p, board=Pizero), "p": p}
170 |
171 | def make_constraints(self):
172 | return [
173 | Fixed(self.components["p"].mate_origin),
174 | Coincident(self.components["m"].mate_origin, self.components["p"].mate_top),
175 | ]
176 |
177 |
178 | if __name__ == "__main__":
179 | from cqparts.display import display
180 |
181 | # p = MountedBoard(board=Pizero)
182 | # p = MountedBoard(board=BeagleBoneBlack)
183 | p = _DemoBoard()
184 | display(p)
185 |
--------------------------------------------------------------------------------
/rocket_drone.py:
--------------------------------------------------------------------------------
1 | " A plank for mounting stuff on "
2 |
3 | import cadquery as cq
4 | import cqparts
5 | from cqparts.params import *
6 | from cqparts.constraint import Fixed, Coincident, Mate
7 | from cqparts.display import render_props
8 | from cqparts.search import register
9 | from cqparts.utils.geometry import CoordSystem
10 |
11 |
12 | from .dc import Cylindrical
13 |
14 |
15 | @register(export="rocket")
16 | class AeroMotor(Cylindrical):
17 | diam = PositiveFloat(15)
18 | length = PositiveFloat(40)
19 | pass
20 |
21 |
22 | @register(export="rocket")
23 | class MotorMount(cqparts.Part):
24 | length = PositiveFloat(20)
25 | diameter = PositiveFloat(15)
26 | thickness = PositiveFloat(0.5)
27 |
28 | def make(self):
29 | mm = (
30 | cq.Workplane("XY")
31 | .circle(self.diameter / 2 + self.thickness)
32 | .circle(self.diameter / 2)
33 | .extrude(self.length)
34 | )
35 | top = (
36 | cq.Workplane("XY")
37 | .circle(self.thickness + self.diameter / 2)
38 | .extrude(self.thickness)
39 | )
40 | top = top.translate((0, 0, self.length))
41 | mm = mm.union(top)
42 | return mm
43 |
44 |
45 | @register(export="rocket")
46 | class Spinner(cqparts.Part):
47 | length = PositiveFloat(20)
48 | diameter = PositiveFloat(15)
49 |
50 | def make(self):
51 | sp = (
52 | cq.Workplane("XZ")
53 | .lineTo(self.diameter / 2, 0)
54 | .lineTo(0, self.length)
55 | .close()
56 | )
57 | sp = sp.revolve(axisStart=(0, 0, 0), axisEnd=(0, 1, 0))
58 | return sp
59 |
60 |
61 | @register(export="rocket")
62 | class Blade(cqparts.Part):
63 | length = PositiveFloat(15)
64 | extra = PositiveFloat(10)
65 | height = PositiveFloat(20)
66 | thickness = PositiveFloat(0.3)
67 |
68 | def make(self):
69 | bl = (
70 | cq.Workplane("XY")
71 | .rect(self.length + self.extra, self.thickness)
72 | .extrude(self.height)
73 | )
74 | bl = bl.translate((self.length / 2, 0, -self.height / 2))
75 | return bl
76 |
77 |
78 | @register(export="rocket")
79 | class Turbine(cqparts.Part):
80 | length = PositiveFloat(20)
81 | diameter = PositiveFloat(15)
82 | outer = PositiveFloat(20)
83 | blades = Int(6)
84 | pitch = PositiveFloat(45)
85 | lift = PositiveFloat(10)
86 |
87 | def make(self):
88 | tb = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.length)
89 | bla = cq.Workplane("XY")
90 | for i in range(self.blades):
91 | bl = Blade(length=self.outer)
92 | bl = bl.local_obj.rotate((0, 0, 0), (1, 0, 0), self.pitch)
93 | bl = bl.translate((self.diameter / 2, 0, self.lift))
94 | bl = bl.rotate((0, 0, 0), (0, 0, 1), i * (360.0 / self.blades))
95 | bla = bla.union(bl)
96 | mm = (
97 | cq.Workplane("XY")
98 | .circle(self.outer * 2)
99 | .circle(self.outer)
100 | .extrude(self.length)
101 | )
102 | bla = bla.cut(mm)
103 | tb = tb.union(bla)
104 | return tb
105 |
106 | def mate_top(self):
107 | return Mate(self, CoordSystem(origin=(0, 0, self.length)))
108 |
109 |
110 | @register(export="rocket")
111 | class Cowl(cqparts.Part):
112 | length = PositiveFloat(60)
113 | diameter = PositiveFloat(50)
114 | thickness = PositiveFloat(0.5)
115 |
116 | vanes = Int(7)
117 | motor_diameter = PositiveFloat(20)
118 | vane_height = PositiveFloat(20)
119 |
120 | def make(self):
121 | rad = self.diameter / 2 - self.motor_diameter / 2
122 | pl = (
123 | cq.Workplane("XY")
124 | .circle(self.diameter / 2)
125 | .circle(self.diameter / 2 - self.thickness)
126 | .extrude(self.length)
127 | )
128 | for i in range(self.vanes):
129 | v = cq.Workplane("XY").rect(rad, self.thickness).extrude(self.vane_height)
130 | v = v.translate((rad / 2 + self.motor_diameter / 2, 0, 0))
131 | v = v.rotate((0, 0, 0), (0, 0, 1), i * (360.0 / self.vanes))
132 |
133 | pl = pl.union(v)
134 | mm = MotorMount(
135 | diameter=self.motor_diameter,
136 | length=self.vane_height,
137 | thickness=self.thickness,
138 | )
139 | mm = mm.local_obj.translate((0, 0, 0))
140 | pl = pl.union(mm)
141 | return pl
142 |
143 | def mate_mount(self):
144 | return Mate(self, CoordSystem(origin=(0, 0, self.vane_height)))
145 |
146 |
147 | @register(export="rocket")
148 | @register(export="showcase")
149 | class TurbineAssembly(cqparts.Assembly):
150 | motor_diameter = PositiveFloat()
151 | motor_clearance = PositiveFloat(1)
152 | blade_clearance = PositiveFloat(1)
153 | diameter = PositiveFloat(50)
154 |
155 | def initialize_parameters(self):
156 | m = AeroMotor()
157 | self.motor_diameter = m.diam + self.motor_clearance
158 | pass
159 |
160 | def make_components(self):
161 | comps = {
162 | "cowl": Cowl(motor_diameter=self.motor_diameter, diameter=self.diameter),
163 | "motor": AeroMotor(),
164 | "turbine": Turbine(outer=self.diameter / 2 - self.blade_clearance),
165 | "spinner": Spinner(),
166 | }
167 | return comps
168 |
169 | def make_constraints(self):
170 | return [
171 | Fixed(self.components["cowl"].mate_origin),
172 | Coincident(
173 | self.components["motor"].mate_origin,
174 | self.components["cowl"].mate_mount(),
175 | ),
176 | Coincident(
177 | self.components["turbine"].mate_origin,
178 | self.components["cowl"].mate_mount(),
179 | ),
180 | Coincident(
181 | self.components["spinner"].mate_origin,
182 | self.components["turbine"].mate_top(),
183 | ),
184 | ]
185 |
186 |
187 | if __name__ == "__main__":
188 | from cqparts.display import display
189 |
190 | # display(Spinner())
191 | # display(Cowl())
192 | # display(Turbine())
193 | # display(Blade())
194 | # display(AeroMotor())
195 | display(TurbineAssembly())
196 |
--------------------------------------------------------------------------------
/simplemount.py:
--------------------------------------------------------------------------------
1 | """
2 | Turntable for scanning
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cqparts.params import *
8 | from cqparts.search import register
9 | from cqparts.constraint import Fixed, Coincident, Mate
10 | from cqparts.utils.geometry import CoordSystem
11 | from cqparts.display import render_props, display
12 |
13 | from . import box
14 | from .partref import PartRef
15 | from .boss import Boss
16 | from .stepper import Stepper
17 | from .mounted import Mounted
18 |
19 | from plank import Plank
20 |
21 |
22 | class T2(box._Tab):
23 | count = Int(5)
24 |
25 |
26 | class Disc(box.Top):
27 | clearance = PositiveFloat(1.5)
28 | diameter = PositiveFloat(20)
29 | thickness = PositiveFloat(4)
30 |
31 | _render = render_props(color=(100, 100, 70))
32 |
33 | def make(self):
34 | cir = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.thickness)
35 | return cir
36 |
37 | def mate_top(self):
38 | return Mate(
39 | self,
40 | CoordSystem(
41 | origin=(0, 0, self.thickness), xDir=(1, 0, 0), normal=(0, 0, 1)
42 | ),
43 | )
44 |
45 |
46 | @register(export="showcase")
47 | class DiscDrive(cqparts.Assembly):
48 | disc = PartRef(Disc)
49 | boss = PartRef(Boss)
50 | motor = PartRef(Stepper)
51 | mount = PartRef(Plank())
52 | diameter = PositiveFloat(40)
53 | thickness = PositiveFloat(3)
54 |
55 | def initialze_parameters(self):
56 | pass
57 |
58 | def make_components(self):
59 | motor = self.motor()
60 | self.sl = motor.shaft_length + self.thickness
61 | disc = self.disc(diameter=self.diameter, thickness=self.thickness)
62 | boss_mount = Mounted(base=self.boss(), target=disc)
63 | motor_mount = Mounted(base=motor, target=self.mount)
64 | comps = {"disc": disc, "boss": boss_mount, "motor": motor_mount}
65 | return comps
66 |
67 | def make_constraints(self):
68 | const = []
69 | disc = self.components["disc"]
70 | boss = self.components["boss"]
71 | motor = self.components["motor"]
72 | const.append(Fixed(disc.mate_top()))
73 | const.append(Coincident(boss.mate_origin, disc.mate_origin))
74 | # const.append(Coincident(motor.mate_origin, boss.mate_origin))
75 | const.append(Fixed(motor.mate_origin, CoordSystem(origin=(0, 0, -self.sl))))
76 | # const.append(Fixed(boss.mate_origin))
77 |
78 | if self.mount is not None:
79 | const.append(Coincident(self.mount.mate_origin, motor.mate_origin))
80 | return const
81 |
82 | def make_alterations(self):
83 | return
84 | if self.mount is not None:
85 | stepper = self.components["motor"]
86 | mount = self.mount
87 |
88 | def motor_offset(self):
89 | a = self.components["disc"].mate_origin.world_coords
90 | return a
91 |
92 | def mate_motor(self):
93 | motor = self.components["motor"]
94 | sl = motor.shaft_length + self.thickness
95 | return Mate(
96 | self, CoordSystem(origin=(0, 0, sl), xDir=(1, 0, 0), normal=(0, 0, 1))
97 | )
98 |
99 |
100 | class Top(box.Top):
101 | clearance = PositiveFloat(1.5)
102 | diameter = PositiveFloat(20)
103 |
104 | def initialize_parameters(self):
105 | pass
106 |
107 | def make(self):
108 | base = super(Top, self).make()
109 | cir = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.thickness)
110 | base = base.cut(cir)
111 | return base
112 |
113 |
114 | class Mid(box.Top):
115 | tabs_on = box.BoolList([True, True, True, True])
116 | tab = PartRef(box._Tab)
117 | width = PositiveFloat(100)
118 | length = PositiveFloat(100)
119 |
120 |
121 | @register(export="showcase")
122 | class TurnTable(box.Boxen):
123 | # Pass down subclassed faces
124 | top = box.PartRef(Top)
125 | width = PositiveFloat(200)
126 | length = PositiveFloat(200)
127 | height = PositiveFloat(90)
128 | outset = PositiveFloat(1.5)
129 | hole = PositiveFloat()
130 | clearance = PositiveFloat(1.5)
131 | coverage = PositiveFloat(0.85)
132 | thickness = PositiveFloat(2.6)
133 | tab = PartRef(T2)
134 |
135 | drive = PartRef(DiscDrive)
136 |
137 | def make_components(self):
138 | comps = super(TurnTable, self).make_components()
139 | mid = Mid(
140 | tab=self.tab,
141 | width=self.width,
142 | thickness=self.thickness,
143 | length=self.length - 2 * self.thickness,
144 | )
145 | comps["mid"] = mid
146 | comps["top"].diameter = self.width * self.coverage
147 | comps["drive"] = self.drive(
148 | mount=mid,
149 | thickness=self.thickness,
150 | diameter=self.width * self.coverage - self.clearance,
151 | )
152 | return comps
153 |
154 | def make_constraints(self):
155 | const = super(TurnTable, self).make_constraints()
156 | top = self.components["top"]
157 | drive = self.components["drive"]
158 | mid = self.components["mid"]
159 | # const.append(Fixed(mid.mate_origin))
160 | # const.append(Coincident(mid.mate_origin, drive.mate_motor()))
161 | const.append(Coincident(drive.mate_origin, top.mate_top()))
162 | return const
163 |
164 | def make_alterations(self):
165 | super(TurnTable, self).make_alterations()
166 | back = self.components["back"]
167 | front = self.components["front"]
168 | left = self.components["left"]
169 | right = self.components["right"]
170 | mid = self.components["mid"]
171 | back.cutter(mid)
172 | front.cutter(mid)
173 | left.cutter(mid)
174 | right.cutter(mid)
175 |
176 |
177 | # positioned mount for target testing
178 | class _DemoDrive(cqparts.Assembly):
179 | def make_components(self):
180 | p = Plank(height=2.5)
181 | return {"m": DiscDrive(mount=p), "p": p}
182 |
183 | def make_constraints(self):
184 | return [Fixed(self.components["m"].mate_origin)]
185 |
186 |
187 | if __name__ == "__main__":
188 | from cqparts.display import display
189 |
190 | # FB = Disc()
191 | # FB = Mid(thickness=3)
192 | # FB = DiscDrive(diameter=60)
193 | # FB = TurnTable()
194 | FB = _DemoDrive()
195 | display(FB)
196 |
--------------------------------------------------------------------------------
/get_faces.py:
--------------------------------------------------------------------------------
1 | import cadquery as cq
2 | import cqparts
3 | import math
4 | from collections import OrderedDict
5 |
6 | from xml.etree import ElementTree as et
7 |
8 | import FreeCAD
9 | from . import flip_box
10 | from . import box
11 | from .plank import Plank
12 | from . import robot_base
13 | from . import servo
14 | from manufacture import Lasercut
15 | from turntable import TurnTable
16 | from flip_box import FlipBox
17 |
18 | # makes an array of local objects
19 | class Extractor(cqparts.Assembly):
20 | def __init__(self):
21 | # for duplicate names
22 | self.track = {}
23 | self.parts = OrderedDict()
24 |
25 | def scan(self, obj, name):
26 | if isinstance(obj, Lasercut):
27 | if name in self.track:
28 | actual_name = name + "_%03i" % self.track[name]
29 | self.track[name] += 1
30 | else:
31 | self.track[name] = 1
32 | actual_name = name
33 | self.parts[actual_name] = obj
34 |
35 | if isinstance(obj, cqparts.Assembly):
36 | for i in obj.components:
37 | self.scan(obj.components[i], i)
38 |
39 | def show(self):
40 | for j in self.parts:
41 | i = self.parts[j]
42 | area = i.bounding_box.xlen * i.bounding_box.ylen
43 | print(i.__class__, i.bounding_box.xlen, i.bounding_box.ylen, area)
44 |
45 | def get_parts(self):
46 | return self.parts
47 |
48 |
49 | fb = Plank(width=200, fillet=20)
50 | # fb = servo._MountedServo()
51 | # fb = TurnTable(outset=12).components['left']
52 | # fb = FlipBox(outset=4).components['left']
53 |
54 |
55 | class SVGexport:
56 | def __init__(self):
57 | self.ex = Extractor()
58 | self.parts = []
59 | self.doc = self.doc()
60 |
61 | def doc(paths):
62 | doc = et.Element(
63 | "svg",
64 | width="480mm",
65 | height="360mm",
66 | version="1.1",
67 | xmlns="http://www.w3.org/2000/svg",
68 | )
69 | return doc
70 |
71 | def save_doc(self):
72 | # ElementTree 1.2 doesn't write the SVG file header errata, so do that manually
73 | g = et.SubElement(doc, "g", id="bob", transform="translate(150 150)")
74 | for i in paths:
75 | a = et.SubElement(
76 | g, "path", d=i, fill="rgb(128,128,128)", stroke="black"
77 | ) # , fill="rgb(20,20,20)")
78 | a.set("stroke-width", "0.2")
79 | f = open("/opt/cqparts.github.io/box.svg", "w")
80 | f.write('\n')
81 | f.write(et.tostring(doc))
82 | f.close()
83 | print(et.tostring(doc))
84 |
85 | def add(self, obj):
86 | self.ex.scan(obj, "")
87 |
88 | def run(self):
89 | parts = self.ex.get_parts()
90 | paths = []
91 | print(parts.keys())
92 | for i in parts:
93 | print(i)
94 | face = parts[i].local_obj.faces(" 0:
62 | step = work_plane.circle(self.step_diam / 2).extrude(self.step_height)
63 | cup = cup.union(step)
64 | bush = (
65 | work_plane.workplane(offset=self.step_height)
66 | .circle(self.bush_diam / 2)
67 | .extrude(self.bush_height)
68 | )
69 | cup = cup.union(bush)
70 | return cup
71 |
72 | def get_cutout(self, clearance=0):
73 | " get the cutout for the shaft"
74 | return (
75 | cq.Workplane("XY", origin=(0, 0, 0))
76 | .circle((self.diam / 2) + clearance)
77 | .extrude(10)
78 | )
79 |
80 | @property
81 | def mate_bottom(self):
82 | " connect to the bottom of the cup"
83 | return Mate(
84 | self,
85 | CoordSystem(origin=(0, 0, -self.height), xDir=(1, 0, 0), normal=(0, 0, 1)),
86 | )
87 |
88 |
89 | class _BackCover(cqparts.Part):
90 | height = PositiveFloat(6, doc="back length")
91 | diam = PositiveFloat(20.4, doc="back diameter")
92 | thickness = PositiveFloat(15.4, doc="back thickness for flat profile")
93 | profile = String("flat", doc="profile shape (circle|flat|rect)")
94 | bush_diam = PositiveFloat(6.15, doc="diameter of the bush")
95 | bush_height = PositiveFloat(1.6, doc="height of the bush")
96 |
97 | _render = render_props(color=(50, 255, 255))
98 |
99 | def make(self):
100 | # grab the correct profile
101 | work_plane = cq.Workplane("XY")
102 | back = (
103 | work_plane.workplane(offset=-self.height)
104 | .circle(self.bush_diam / 2)
105 | .extrude(-self.bush_height)
106 | )
107 | if self.height > 0:
108 | back = _profile(self.profile, self.diam, self.thickness).extrude(
109 | -self.height
110 | )
111 | back = back.union(back)
112 | return back
113 |
114 |
115 | @register(export="motor")
116 | class DCMotor(motor.Motor):
117 | """
118 | DC motors for models
119 |
120 | .. image:: /_static/img/motors/DCMotor.png
121 | """
122 |
123 | height = PositiveFloat(25.1, doc="motor length")
124 | diam = PositiveFloat(20.4, doc="motor diameter")
125 | thickness = PositiveFloat(15.4, doc="back thickness for flat profile")
126 | profile = String("flat", doc="profile shape (circle|flat|rect)")
127 |
128 | bush_diam = PositiveFloat(6.15, doc="diameter of the bush")
129 | bush_height = PositiveFloat(1.6, doc="height of the bush")
130 |
131 | shaft_type = shaft.Shaft # replace with other shaft
132 | shaft_length = PositiveFloat(11.55, doc="length of the shaft")
133 | shaft_diam = PositiveFloat(2, doc="diameter of the shaft")
134 |
135 | cover_height = PositiveFloat(0, doc="back cover height")
136 |
137 | # a step on the top surface
138 | step_height = PositiveFloat(0, doc="height if step, if zero no step")
139 | step_diam = PositiveFloat(12, doc="step diameter")
140 |
141 | def get_shaft(self):
142 | return self.shaft_type
143 |
144 | def mount_points(self):
145 | # TODO handle mount points
146 | pass
147 |
148 | def make_components(self):
149 | return {
150 | "body": _Cup(
151 | height=self.height,
152 | thickness=self.thickness,
153 | diam=self.diam,
154 | profile=self.profile,
155 | bush_diam=self.bush_diam,
156 | bush_height=self.bush_height,
157 | step_height=self.step_height,
158 | ),
159 | "shaft": self.shaft_type(length=self.shaft_length, diam=self.shaft_diam),
160 | "back": _BackCover(
161 | height=self.cover_height,
162 | thickness=self.thickness,
163 | diam=self.diam,
164 | profile=self.profile,
165 | bush_diam=self.bush_diam,
166 | bush_height=self.bush_height,
167 | ),
168 | }
169 |
170 | def make_constraints(self):
171 | return [
172 | Fixed(self.components["body"].mate_origin),
173 | Coincident(
174 | self.components["shaft"].mate_origin,
175 | self.components["body"].mate_origin,
176 | ),
177 | Coincident(
178 | self.components["back"].mate_origin, self.components["body"].mate_bottom
179 | ),
180 | ]
181 |
182 |
183 | @register(export="motor")
184 | class Cylindrical(DCMotor):
185 | profile = String("circle", doc="profile shape (circle|flat|rect)")
186 |
187 |
188 | @register(export="motor")
189 | class Rect(DCMotor):
190 | profile = String("rect", doc="profile shape (circle|flat|rect)")
191 |
--------------------------------------------------------------------------------
/peg.py:
--------------------------------------------------------------------------------
1 |
2 | # TODO: illustrative model only; remove this file
3 | # lifted for testing from cqparts_template
4 | # cqparts/src/cqparts_template/clamp/peg.py
5 |
6 | import cadquery
7 |
8 | import cqparts
9 | from cqparts.params import *
10 | from cqparts.display import render_props
11 | from cqparts import constraint
12 | from cqparts.utils import CoordSystem
13 | from cqparts.search import register
14 |
15 |
16 | class _PegSide(cqparts.Part):
17 | """
18 | One side of a wooden clothes peg.
19 |
20 | Note that this docstring does not get rendered in the sphinx automated
21 | documentation, this is because the class is prefixed with a ``_``.
22 |
23 | Also: idiomatic Python dictates that components with a ``_`` prefix are not
24 | intended for an end-user, which is why they're not documented.
25 | """
26 |
27 | length = PositiveFloat()
28 | width = PositiveFloat()
29 | depth = PositiveFloat()
30 |
31 | tip_chamfer = PositiveFloat()
32 | handle_tip_depth = PositiveFloat()
33 | handle_length = PositiveFloat()
34 |
35 | # spring
36 | spring_diam = PositiveFloat()
37 | spring_arm_length = PositiveFloat()
38 | spring_wire_diam = PositiveFloat()
39 |
40 | # major indent
41 | major_radius = PositiveFloat()
42 | major_depth = PositiveFloat()
43 | major_offset = PositiveFloat()
44 |
45 | # minor indent
46 | minor_radius = PositiveFloat()
47 | minor_depth = PositiveFloat()
48 | minor_offset = PositiveFloat()
49 |
50 | # Default material to render
51 | _render = render_props(template="wood")
52 |
53 | def make(self):
54 | # Main profile shape of peg
55 | points = [
56 | (0, 0),
57 | (self.length, 0),
58 | (self.length, self.handle_tip_depth),
59 | (self.length - self.handle_length, self.depth),
60 | (self.tip_chamfer, self.depth),
61 | (0, self.depth - self.tip_chamfer),
62 | ]
63 |
64 | side = (
65 | cadquery.Workplane("XY")
66 | .moveTo(*points[0])
67 | .polyline(points[1:])
68 | .close()
69 | .extrude(self.width)
70 | )
71 |
72 | # cut spring
73 | side = side.cut(
74 | cadquery.Workplane("XY")
75 | .moveTo(self.length - self.handle_length, self.depth)
76 | .circle(self.spring_diam / 2)
77 | .extrude(self.width)
78 | )
79 |
80 | # cut indents
81 | def cut_indent(obj, radius, depth, offset):
82 | return obj.cut(
83 | cadquery.Workplane("XY")
84 | .moveTo(offset, self.depth + (radius - depth))
85 | .circle(radius)
86 | .extrude(self.width)
87 | )
88 |
89 | side = cut_indent(
90 | obj=side,
91 | radius=self.major_radius,
92 | depth=self.major_depth,
93 | offset=self.major_offset,
94 | )
95 | side = cut_indent(
96 | obj=side,
97 | radius=self.minor_radius,
98 | depth=self.minor_depth,
99 | offset=self.minor_offset,
100 | )
101 |
102 | return side
103 |
104 | @property
105 | def mate_spring_center(self):
106 | # mate in the center of the spring, z-axis along spring center
107 | return constraint.Mate(
108 | self,
109 | CoordSystem(
110 | origin=(self.length - self.handle_length, self.depth, self.width / 2)
111 | ),
112 | )
113 |
114 | @property
115 | def mate_side(self):
116 | # mate in middle of outside edge, z-axis into peg
117 | return constraint.Mate(
118 | self,
119 | CoordSystem(
120 | origin=(self.length / 2, 0, self.width / 2),
121 | xDir=(0, 0, -1),
122 | normal=(0, 1, 0),
123 | ),
124 | )
125 |
126 |
127 | class _Spring(cqparts.Part):
128 | diam = PositiveFloat()
129 | arm_length = PositiveFloat()
130 | wire_diam = PositiveFloat()
131 | width = PositiveFloat()
132 |
133 | def make(self):
134 | spring = (
135 | cadquery.Workplane("XY", origin=(0, 0, -(self.width / 2 + self.wire_diam)))
136 | .circle(self.diam / 2)
137 | .circle((self.diam / 2) - self.wire_diam)
138 | .extrude(self.width + (2 * self.wire_diam))
139 | )
140 |
141 | return spring
142 |
143 |
144 | @register(export="misc")
145 | class ClothesPeg(cqparts.Assembly):
146 | """
147 | A common household clothes-peg
148 |
149 | .. image:: /_static/img/template/peg.png
150 | """
151 |
152 | length = PositiveFloat(75, doc="length of peg side")
153 | width = PositiveFloat(10, doc="peg width")
154 | depth = PositiveFloat(7, doc="depth of peg side, half peg's full depth")
155 |
156 | tip_chamfer = PositiveFloat(5, doc="chamfer at tip")
157 | handle_tip_depth = PositiveFloat(2, doc="depth at handle's tip")
158 | handle_length = PositiveFloat(30, doc="length of tapered handle")
159 |
160 | # spring
161 | spring_diam = PositiveFloat(5, doc="diameter of spring's core")
162 | spring_arm_length = PositiveFloat(
163 | 17.5, doc="length of spring's arm converting torque to closing force"
164 | )
165 | spring_wire_diam = PositiveFloat(1.3, doc="diamter of spring's wire")
166 |
167 | # major indent
168 | major_radius = PositiveFloat(10, doc="large indentation's radius")
169 | major_depth = PositiveFloat(2, doc="large indentation's depth")
170 | major_offset = PositiveFloat(17, doc="large indentation center's distance from tip")
171 |
172 | # minor indent
173 | minor_radius = PositiveFloat(1, doc="small indentation's radius")
174 | minor_depth = PositiveFloat(1, doc="small indentation's depth")
175 | minor_offset = PositiveFloat(31, doc="small indentation center's distance from tip")
176 |
177 | def make_components(self):
178 | params = self.params(hidden=False) # common to _PegSide
179 | return {
180 | "bottom": _PegSide(**params),
181 | "top": _PegSide(**params),
182 | "spring": _Spring(
183 | diam=self.spring_diam,
184 | arm_length=self.spring_arm_length,
185 | wire_diam=self.spring_wire_diam,
186 | width=self.width,
187 | ),
188 | }
189 |
190 | def make_constraints(self):
191 | bottom = self.components["bottom"]
192 | top = self.components["top"]
193 | spring = self.components["spring"]
194 | return [
195 | constraint.Fixed(bottom.mate_side),
196 | constraint.Coincident(
197 | top.mate_spring_center,
198 | bottom.mate_spring_center + CoordSystem(normal=(0, 0, -1)),
199 | ),
200 | constraint.Coincident(spring.mate_origin, bottom.mate_spring_center),
201 | ]
202 |
203 |
204 | if __name__ == "__main__":
205 | from cqparts.display import display
206 |
207 | cp = ClothesPeg()
208 | display(cp)
209 |
--------------------------------------------------------------------------------
/SVGexport.py:
--------------------------------------------------------------------------------
1 | # stolen from
2 | # https://github.com/dcowden/cadquery/blob/master/cadquery/freecad_impl/exporters.py
3 | # svg export modified to export hidden only
4 |
5 | from __future__ import unicode_literals
6 |
7 | import cadquery
8 |
9 | import FreeCAD
10 | import Drawing
11 |
12 | import tempfile, os, io
13 |
14 | # weird syntax i know
15 | from cadquery.freecad_impl import suppress_stdout_stderr
16 |
17 | try:
18 | import xml.etree.cElementTree as ET
19 | except ImportError:
20 | import xml.etree.ElementTree as ET
21 |
22 |
23 | class ExportTypes:
24 | STL = "STL"
25 | STEP = "STEP"
26 | AMF = "AMF"
27 | SVG = "SVG"
28 | TJS = "TJS"
29 |
30 |
31 | class UNITS:
32 | MM = "mm"
33 | IN = "in"
34 |
35 |
36 | def toString(shape, exportType, tolerance=0.1):
37 | s = io.StringIO()
38 | exportShape(shape, exportType, s, tolerance)
39 | return s.getvalue()
40 |
41 |
42 | def guessUnitOfMeasure(shape):
43 | """
44 | Guess the unit of measure of a shape.
45 | """
46 | bb = shape.BoundBox
47 |
48 | dimList = [bb.XLength, bb.YLength, bb.ZLength]
49 | # no real part would likely be bigger than 10 inches on any side
50 | if max(dimList) > 10:
51 | return UNITS.MM
52 |
53 | # no real part would likely be smaller than 0.1 mm on all dimensions
54 | if min(dimList) < 0.1:
55 | return UNITS.IN
56 |
57 | # no real part would have the sum of its dimensions less than about 5mm
58 | if sum(dimList) < 10:
59 | return UNITS.IN
60 |
61 | return UNITS.MM
62 |
63 |
64 | def getPaths(freeCadSVG):
65 | """
66 | freeCad svg is worthless-- except for paths, which are fairly useful
67 | this method accepts svg from fReeCAD and returns a list of strings suitable for inclusion in a path element
68 | returns two lists-- one list of visible lines, and one list of hidden lines
69 |
70 | HACK ALERT!!!!!
71 | FreeCAD does not give a way to determine which lines are hidden and which are not
72 | the only way to tell is that hidden lines are in a with 0.15 stroke and visible are 0.35 stroke.
73 | so we actually look for that as a way to parse.
74 |
75 | to make it worse, elementTree xpath attribute selectors do not work in python 2.6, and we
76 | cannot use python 2.7 due to freecad. So its necessary to look for the pure strings! ick!
77 | """
78 |
79 | hiddenPaths = []
80 | visiblePaths = []
81 | if len(freeCadSVG) > 0:
82 | # yuk, freecad returns svg fragments. stupid stupid
83 | fullDoc = "%s" % freeCadSVG
84 | e = ET.ElementTree(ET.fromstring(fullDoc))
85 | segments = e.findall(".//g")
86 | for s in segments:
87 | paths = s.findall("path")
88 |
89 | if s.get("stroke-width") == "0.15": # hidden line HACK HACK HACK
90 | mylist = hiddenPaths
91 | else:
92 | mylist = visiblePaths
93 |
94 | for p in paths:
95 | mylist.append(p.get("d"))
96 | return (hiddenPaths, visiblePaths)
97 | else:
98 | return ([], [])
99 |
100 |
101 | def getSVG(shape, opts=None, view_vector=(-0, 0, 20.0)):
102 | """
103 | Export a shape to SVG
104 | """
105 |
106 | d = {"width": 800, "height": 800, "marginLeft": 20, "marginTop": 20}
107 |
108 | if opts:
109 | d.update(opts)
110 |
111 | # need to guess the scale and the coordinate center
112 | uom = guessUnitOfMeasure(shape)
113 |
114 | width = float(d["width"])
115 | height = float(d["height"])
116 | marginLeft = float(d["marginLeft"])
117 | marginTop = float(d["marginTop"])
118 |
119 | # TODO: provide option to give 3 views
120 | viewVector = FreeCAD.Base.Vector(view_vector)
121 | (visibleG0, visibleG1, hiddenG0, hiddenG1) = Drawing.project(shape, viewVector)
122 |
123 | (hiddenPaths, visiblePaths) = getPaths(
124 | Drawing.projectToSVG(shape, viewVector, "")
125 | ) # this param is totally undocumented!
126 |
127 | # get bounding box -- these are all in 2-d space
128 | bb = visibleG0.BoundBox
129 | bb.add(visibleG1.BoundBox)
130 | bb.add(hiddenG0.BoundBox)
131 | bb.add(hiddenG1.BoundBox)
132 |
133 | # width pixels for x, height pixesl for y
134 | # massive hack convert pixels to mm
135 | unitScale = (
136 | 3.779527559
137 | ) # min( width / bb.XLength * 0.75 , height / bb.YLength * 0.75 )
138 |
139 | # compute amount to translate-- move the top left into view
140 | (xTranslate, yTranslate) = (
141 | (0 - bb.XMin) + marginLeft / unitScale,
142 | (0 - bb.YMax) - marginTop / unitScale,
143 | )
144 |
145 | # compute paths ( again -- had to strip out freecad crap )
146 | hiddenContent = ""
147 | for p in hiddenPaths:
148 | hiddenContent += PATHTEMPLATE % p
149 | visibleContent = ""
150 | for p in visiblePaths:
151 | visibleContent += PATHTEMPLATE % p
152 |
153 | svg = SVG_TEMPLATE % (
154 | {
155 | "unitScale": str(unitScale),
156 | "strokeWidth": "0.1",
157 | "hiddenContent": visibleContent,
158 | "xTranslate": str(xTranslate),
159 | "yTranslate": str(yTranslate),
160 | "width": str(width),
161 | "height": str(height),
162 | "textboxY": str(height - 30),
163 | "uom": str(uom),
164 | }
165 | )
166 | # svg = SVG_TEMPLATE % (
167 | # {"content": projectedContent}
168 | # )
169 | return svg
170 |
171 |
172 | def exportSVG(shape, fileName, view_vector=(0, 0, 20)):
173 | """
174 | accept a cadquery shape, and export it to the provided file
175 | TODO: should use file-like objects, not a fileName, and/or be able to return a string instead
176 | export a view of a part to svg
177 | """
178 | svg = getSVG(shape.val().wrapped, opts=None, view_vector=view_vector)
179 | f = open(fileName, "w")
180 | f.write(svg)
181 | f.close()
182 |
183 |
184 | SVG_TEMPLATE = """
185 |
199 | """
200 |
201 | RECT_TEMPLATE = """
202 |
216 | """
217 |
218 |
219 | PATHTEMPLATE = '\t\t\t\n'
220 |
--------------------------------------------------------------------------------
/pan_tilt.py:
--------------------------------------------------------------------------------
1 | """
2 | Pan Tilt head mount
3 | """
4 |
5 | import cadquery as cq
6 | import cqparts
7 | from cadquery import Solid
8 | from cqparts.params import *
9 | from cqparts.display import render_props, display
10 | from cqparts.constraint import Fixed, Coincident
11 | from cqparts.constraint import Mate
12 | from cqparts.utils.geometry import CoordSystem
13 | from cqparts.search import register
14 |
15 | from .calculations import CalcTangents
16 | from .manufacture import Printable
17 |
18 | from cqparts_motors.shaft import Shaft
19 |
20 | from .servo import SubMicro, Servo
21 |
22 |
23 | class PartRef(Parameter):
24 | def type(self, value):
25 | return value
26 |
27 |
28 | class MountTab(cqparts.Part):
29 | diameter = PositiveFloat(10)
30 | height = PositiveFloat(5)
31 | length = PositiveFloat(0)
32 | hole = PositiveFloat(3)
33 | extra = PositiveFloat(20)
34 |
35 | def make(self):
36 | mount = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.height)
37 | tab = (
38 | cq.Workplane("XY")
39 | .rect(
40 | self.diameter / 2 + self.length / 2 + self.extra,
41 | self.diameter,
42 | centered=False,
43 | )
44 | .extrude(self.height)
45 | .translate((0, -self.diameter / 2, 0))
46 | )
47 | mount = mount.union(tab)
48 | hole = cq.Workplane("XY").circle(self.hole / 2).extrude(self.height)
49 | mount = mount.cut(hole)
50 | mount = mount.translate((-(self.diameter / 2 + self.length / 2), 0, 0))
51 | return mount
52 |
53 |
54 | class Base(Printable):
55 | diameter = PositiveFloat(10)
56 | width = PositiveFloat(40)
57 | height = PositiveFloat(20)
58 | lower_height = PositiveFloat(10)
59 | thickness = PositiveFloat(2)
60 |
61 | _render = render_props(color=(100, 150, 100))
62 |
63 | def make(self):
64 | # base = cq.Workplane("XY").rect(self.length,self.width).extrude(self.height)
65 | base = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.height)
66 | inc = 360 / float(4)
67 | mt = MountTab()
68 | t1 = mt.local_obj.translate(
69 | (-self.diameter / 2, self.width / 2 - mt.diameter / 2, 0)
70 | )
71 | t2 = t1.mirror("YZ")
72 | t_r = t1.union(t2)
73 | t_l = t_r.mirror("XZ")
74 | base = base.union(t_r)
75 | base = base.union(t_l)
76 | # base = base.edges("|Z").fillet(2)
77 | return base
78 |
79 | def mate_lower(self):
80 | return Mate(self, CoordSystem(origin=(0, 0, self.lower_height)))
81 |
82 |
83 | class Yaw(Printable):
84 | width = PositiveFloat(40)
85 | diameter = PositiveFloat(20)
86 | height = PositiveFloat(5)
87 |
88 | _render = render_props(color=(130, 150, 100))
89 |
90 | def make(self):
91 | # yaw = cq.Workplane("XY").rect(self.length,self.width).extrude(self.height)
92 | yaw = cq.Workplane("XY").circle(self.diameter / 2).extrude(self.height)
93 | # yaw = yaw.edges("|Z").fillet(2)
94 | return yaw
95 |
96 | def mate_middle(self, offset=0):
97 | return Mate(
98 | self,
99 | CoordSystem(origin=(0, 0, self.height), xDir=(1, 0, 0), normal=(0, 0, 1)),
100 | )
101 |
102 | def mate_side(self, offset=0):
103 | return Mate(
104 | self,
105 | CoordSystem(
106 | origin=(offset, 0, self.height / 2), xDir=(1, 0, 0), normal=(0, 1, 0)
107 | ),
108 | )
109 |
110 |
111 | class Pitch(Printable):
112 | diameter = PositiveFloat(41)
113 | width = PositiveFloat(40)
114 | length = PositiveFloat(10)
115 | height = PositiveFloat(10)
116 | thickness = PositiveFloat(5)
117 |
118 | _render = render_props(color=(160, 150, 100))
119 |
120 | def make(self):
121 | pitch = (
122 | cq.Workplane("XY")
123 | .circle(self.diameter / 2 + self.thickness)
124 | .circle(self.diameter / 2)
125 | .extrude(self.height)
126 | )
127 | pitch = pitch.transformed(rotate=(0, 90, 90)).split(keepTop=True)
128 | rot = cq.Workplane("YZ").circle(self.height / 2).extrude(-self.thickness)
129 | rot = rot.translate((self.diameter / 2 + self.thickness, 0, self.height / 2))
130 | other_side = rot.mirror("YZ")
131 | rot = rot.union(other_side)
132 | pitch = pitch.union(rot)
133 | return pitch
134 |
135 | def mate_side(self, offset=0):
136 | return Mate(
137 | self,
138 | CoordSystem(
139 | origin=(offset, 0, self.height / 2), xDir=(0, 1, 0), normal=(1, 0, 0)
140 | ),
141 | )
142 |
143 |
144 | @register(export="sensor")
145 | class PanTilt(cqparts.Assembly):
146 | diameter = PositiveFloat(65)
147 | width = PositiveFloat(30)
148 | lower_height = PositiveFloat(25)
149 | upper_height = PositiveFloat(25)
150 | gap = PositiveFloat(2)
151 | thickness = PositiveFloat(5)
152 | servo = PartRef(SubMicro)
153 | target = PartRef()
154 | # calcs
155 | yaw_offset = PositiveFloat()
156 |
157 | def initialize_parameters(self):
158 | s = self.servo() # collect some dimensions
159 | self.diameter = (
160 | 2 * (s.length - s.boss_offset + s.wing_width)
161 | + 2 * self.thickness
162 | + self.gap
163 | )
164 | self.width = self.diameter * 0.8 # mount pos inset
165 | self.height = s.height + self.thickness + s.boss_height
166 | self.lower_height = s.wing_lift + self.thickness
167 | # calcuated
168 | self.yaw_offset = self.diameter / 2 - (s.height - s.wing_lift) - s.boss_height
169 |
170 | def make_components(self):
171 | base = Base(
172 | diameter=self.diameter,
173 | width=self.width,
174 | height=self.height,
175 | lower_height=self.lower_height,
176 | thickness=self.thickness,
177 | )
178 | yaw = Yaw(diameter=self.diameter, width=self.width, height=self.upper_height)
179 | pitch = Pitch(
180 | diameter=self.diameter + self.gap,
181 | width=self.width,
182 | height=self.upper_height,
183 | )
184 | comps = {
185 | "base": base,
186 | "yaw": yaw,
187 | "yaw_servo": self.servo(target=base),
188 | "pitch": pitch,
189 | "pitch_servo": self.servo(target=yaw, overcut=50),
190 | }
191 | return comps
192 |
193 | def make_constraints(self):
194 | base = self.components["base"]
195 | yaw = self.components["yaw"]
196 | yaw_servo = self.components["yaw_servo"]
197 | pitch = self.components["pitch"]
198 | pitch_servo = self.components["pitch_servo"]
199 |
200 | constr = [
201 | Fixed(base.mate_origin),
202 | Coincident(yaw_servo.mate_wing_bottom(), base.mate_lower()),
203 | Coincident(yaw.mate_origin, yaw_servo.mate_top()),
204 | Coincident(pitch.mate_origin, yaw_servo.mate_top()),
205 | Coincident(
206 | pitch_servo.mate_wing_bottom(),
207 | pitch.mate_side(offset=(self.yaw_offset)),
208 | ),
209 | ]
210 | return constr
211 |
212 | def mate_front(self, offset=0):
213 | return Mate(
214 | self,
215 | CoordSystem(
216 | origin=(0, self.diameter / 2, 0), xDir=(0, 1, 0), normal=(0, 0, 1)
217 | ),
218 | )
219 |
220 |
221 | if __name__ == "__main__":
222 | from cqparts.display import display
223 |
224 | # B = MountTab()
225 | # B = Base()
226 | # B = Yaw()
227 | # B = Pitch()
228 | B = PanTilt(servo=SubMicro)
229 | # B = PanTilt(servo=Servo)
230 | display(B)
231 |
--------------------------------------------------------------------------------
/stepper.py:
--------------------------------------------------------------------------------
1 |
2 | """ cqparts motors
3 | 2018 Simon Kirkby obeygiantrobot@gmail.com
4 | stepper motor generic
5 | """
6 |
7 | # TODO
8 | # even 4 fasteners so it auto mounts to whatever it is parented to.
9 |
10 | import cadquery as cq
11 |
12 | import cqparts
13 | from cqparts.params import PositiveFloat
14 | from cqparts.constraint import Fixed, Coincident
15 | from cqparts.constraint import Mate
16 | from cqparts.display import render_props
17 | from cqparts.utils.geometry import CoordSystem
18 | from cqparts.search import register
19 |
20 | from . import shaft
21 | from . import motor
22 |
23 |
24 | class _EndCap(cqparts.Part):
25 | # Parameters
26 | width = PositiveFloat(42.3, doc="Motor Size")
27 | length = PositiveFloat(10, doc="End length")
28 | cham = PositiveFloat(3, doc="chamfer")
29 |
30 | # _render = render_props(color=(50, 50, 50),alpha=0.4)
31 | def make(self):
32 | base = (
33 | cq.Workplane("XY")
34 | .box(self.width, self.width, self.length)
35 | .edges("|Z")
36 | .chamfer(self.cham)
37 | )
38 | return base
39 |
40 | @property
41 | def mate_top(self):
42 | " connect to the end of the top cap"
43 | return Mate(
44 | self,
45 | CoordSystem(
46 | origin=(0, 0, -self.length / 2), xDir=(0, 1, 0), normal=(0, 0, -1)
47 | ),
48 | )
49 |
50 | @property
51 | def mate_bottom(self):
52 | " bottom of the top cap"
53 | return Mate(
54 | self,
55 | CoordSystem(
56 | origin=(0, 0, -self.length / 2), xDir=(0, 1, 0), normal=(0, 0, 1)
57 | ),
58 | )
59 |
60 |
61 | class _Stator(cqparts.Part):
62 | # Parameters
63 | width = PositiveFloat(40.0, doc="Motor Size")
64 | length = PositiveFloat(20, doc="stator length")
65 | cham = PositiveFloat(3, doc="chamfer")
66 |
67 | _render = render_props(color=(50, 50, 50))
68 |
69 | def make(self):
70 | base = (
71 | cq.Workplane("XY")
72 | .box(self.width, self.width, self.length, centered=(True, True, True))
73 | .edges("|Z")
74 | .chamfer(self.cham)
75 | )
76 | return base
77 |
78 | @property
79 | def mate_top(self):
80 | " top of the stator"
81 | return Mate(
82 | self,
83 | CoordSystem(
84 | origin=(0, 0, self.length / 2), xDir=(0, 1, 0), normal=(0, 0, 1)
85 | ),
86 | )
87 |
88 | @property
89 | def mate_bottom(self):
90 | " bottom of the stator"
91 | return Mate(
92 | self,
93 | CoordSystem(
94 | origin=(0, 0, -self.length / 2), xDir=(1, 0, 0), normal=(0, 0, -1)
95 | ),
96 | )
97 |
98 |
99 | class _StepperMount(_EndCap):
100 | spacing = PositiveFloat(31, doc="hole spacing")
101 | hole_size = PositiveFloat(3, doc="hole size")
102 | boss = PositiveFloat(22, doc="boss size")
103 | boss_length = PositiveFloat(2, doc="boss_length")
104 |
105 | def make(self):
106 | obj = super(_StepperMount, self).make()
107 | obj.faces(">Z").workplane().rect(
108 | self.spacing, self.spacing, forConstruction=True
109 | ).vertices().hole(self.hole_size)
110 | obj.faces(">Z").workplane().circle(self.boss / 2).extrude(self.boss_length)
111 | return obj
112 |
113 | @property
114 | def mate_top(self):
115 | " top of the mount"
116 | return Mate(
117 | self,
118 | CoordSystem(
119 | origin=(0, 0, self.length / 2), xDir=(0, 1, 0), normal=(0, 0, 1)
120 | ),
121 | )
122 |
123 | @property
124 | def mate_bottom(self):
125 | " bottom of the mount"
126 | return Mate(
127 | self,
128 | CoordSystem(
129 | origin=(0, 0, -self.length / 2), xDir=(0, 1, 0), normal=(0, 0, 1)
130 | ),
131 | )
132 |
133 |
134 | class _Back(_EndCap):
135 | spacing = PositiveFloat(31, doc="hole spacing")
136 | hole_size = PositiveFloat(3, doc="hole size")
137 |
138 | def make(self):
139 | obj = super(_Back, self).make()
140 | obj.faces(">Z").workplane().rect(
141 | self.spacing, self.spacing, forConstruction=True
142 | ).vertices().hole(self.hole_size)
143 | return obj
144 |
145 |
146 | @register(export="motor")
147 | class Stepper(motor.Motor):
148 | " Stepper Motor , simple rendering "
149 | shaft_type = shaft.Shaft
150 |
151 | width = PositiveFloat(42.3, doc="width and depth of the stepper")
152 | length = PositiveFloat(50, doc="length of the stepper")
153 | hole_spacing = PositiveFloat(31.0, doc="distance between centers")
154 | hole_size = PositiveFloat(3, doc="hole diameter , select screw with this")
155 | boss_size = PositiveFloat(22, doc="diameter of the raise circle")
156 | boss_length = PositiveFloat(2, doc="length away from the top surface")
157 |
158 | shaft_diam = PositiveFloat(5, doc="diameter of the the shaft ")
159 | shaft_length = PositiveFloat(24, doc="length from top surface")
160 |
161 | def get_shaft(self):
162 | return self.components["shaft"]
163 |
164 | def mount_verts(self):
165 | " return mount points"
166 | wp = cq.Workplane("XY")
167 | h = wp.rect(
168 | self.hole_spacing, self.hole_spacing, forConstruction=True
169 | ).vertices()
170 | return h.objects
171 |
172 | def make_components(self):
173 | sec = self.length / 6
174 | return {
175 | "topcap": _StepperMount(
176 | width=self.width,
177 | length=sec,
178 | spacing=self.hole_spacing,
179 | hole_size=self.hole_size,
180 | boss=self.boss_size,
181 | ),
182 | "stator": _Stator(width=self.width - 3, length=sec * 4),
183 | "botcap": _Back(
184 | width=self.width,
185 | length=sec,
186 | spacing=self.hole_spacing,
187 | hole_size=self.hole_size,
188 | ),
189 | "shaft": self.shaft_type(length=self.shaft_length, diam=self.shaft_diam),
190 | }
191 |
192 | def make_constraints(self):
193 | return [
194 | Fixed(self.components["topcap"].mate_top),
195 | Coincident(
196 | self.components["stator"].mate_top,
197 | self.components["topcap"].mate_bottom,
198 | ),
199 | Coincident(
200 | self.components["botcap"].mate_bottom,
201 | self.components["stator"].mate_bottom,
202 | ),
203 | Coincident(
204 | self.components["shaft"].mate_origin, self.components["topcap"].mate_top
205 | ),
206 | ]
207 |
208 | def apply_cutout(self):
209 | " shaft cutout "
210 | stepper_shaft = self.components["shaft"]
211 | top = self.components["topcap"]
212 | local_obj = top.local_obj
213 | local_obj = local_obj.cut(stepper_shaft.get_cutout(clearance=0.5))
214 |
215 | def make_alterations(self):
216 | self.apply_cutout()
217 |
218 | def boss_cutout(self, clearance=0):
219 | bc = cq.Workplane("XY").circle(self.boss_size / 2).extrude(self.shaft_length)
220 | return bc
221 |
222 | def cutout(self, part):
223 | self.cut_boss(part)
224 |
225 | def cut_boss(self, part, clearance=0):
226 | co = self.boss_cutout(clearance=clearance)
227 | lo = part.local_obj.cut((self.world_coords - part.world_coords) + co)
228 |
229 | def mate_tip(self):
230 | return Mate(
231 | self,
232 | CoordSystem(
233 | origin=(0, 0, self.shaft_length), xDir=(1, 0, 0), normal=(0, 0, 1)
234 | ),
235 | )
236 |
237 |
238 | if __name__ == "__main__":
239 | from cqparts.display import display
240 |
241 | st = Stepper()
242 | display(st)
243 |
--------------------------------------------------------------------------------