├── .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 |
5 | 6 | {% for key, value in item.params %} 7 | {{ key }}
8 | {% endfor %} 9 | 10 |
11 |
12 | {{ item }} 13 | 14 | -------------------------------------------------------------------------------- /merged/templates/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 193 | 194 | 195 | %(hiddenContent)s 196 | 197 | 198 | 199 | """ 200 | 201 | RECT_TEMPLATE = """ 202 | 210 | 211 | 212 | %(hiddenContent)s 213 | 214 | 215 | 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 | --------------------------------------------------------------------------------