Skip to content

Instantly share code, notes, and snippets.

@fragmuffin
Last active May 22, 2018 11:45
Show Gist options
  • Save fragmuffin/8889def56002184ab309e177c6777d3e to your computer and use it in GitHub Desktop.
Save fragmuffin/8889def56002184ab309e177c6777d3e to your computer and use it in GitHub Desktop.
cqparts part equality and bom discussion
#!/usr/bin/env python
# The code here should be representative of that in:
# https://fragmuffin.github.io/cqparts/doc/tutorials/assembly.html
# ------------------- Wheel -------------------
import cadquery
import cqparts
from cqparts.params import *
from cqparts.display import render_props, display
class Wheel(cqparts.Part):
# Parameters
width = PositiveFloat(10, doc="width of wheel")
diameter = PositiveFloat(30, doc="wheel diameter")
# default appearance
_render = render_props(template='wood_dark')
def make(self):
wheel = cadquery.Workplane('XY') \
.circle(self.diameter / 2).extrude(self.width)
hole = cadquery.Workplane('XY') \
.circle(2).extrude(self.width/2).faces(">Z") \
.circle(4).extrude(self.width/2)
wheel = wheel.cut(hole)
return wheel
def get_cutout(self, clearance=0):
# A cylinder with a equal clearance on every face
return cadquery.Workplane('XY', origin=(0, 0, -clearance)) \
.circle((self.diameter / 2) + clearance) \
.extrude(self.width + (2 * clearance))
# ------------------- Axle -------------------
from cqparts.constraint import Mate
from cqparts.utils.geometry import CoordSystem
class Axle(cqparts.Part):
# Parameters
length = PositiveFloat(50, doc="axle length")
diameter = PositiveFloat(10, doc="axle diameter")
# default appearance
_render = render_props(color=(50, 50, 50)) # dark grey
def make(self):
axle = cadquery.Workplane('ZX', origin=(0, -self.length/2, 0)) \
.circle(self.diameter / 2).extrude(self.length)
cutout = cadquery.Workplane('ZX', origin=(0, -self.length/2, 0)) \
.circle(1.5).extrude(10)
axle = axle.cut(cutout)
cutout = cadquery.Workplane('XZ', origin=(0, self.length/2, 0)) \
.circle(1.5).extrude(10)
axle = axle.cut(cutout)
return axle
# wheel mates, assuming they rotate around z-axis
@property
def mate_left(self):
return Mate(self, CoordSystem(
origin=(0, -self.length / 2, 0),
xDir=(1, 0, 0), normal=(0, -1, 0),
))
@property
def mate_right(self):
return Mate(self, CoordSystem(
origin=(0, self.length / 2, 0),
xDir=(1, 0, 0), normal=(0, 1, 0),
))
def get_cutout(self, clearance=0):
return cadquery.Workplane('ZX', origin=(0, -self.length/2 - clearance, 0)) \
.circle((self.diameter / 2) + clearance) \
.extrude(self.length + (2 * clearance))
# ------------------- Chassis -------------------
class Chassis(cqparts.Part):
# Parameters
width = PositiveFloat(50, doc="chassis width")
_render = render_props(template='wood_light')
def make(self):
points = [ # chassis outline
(-60,0),(-60,22),(-47,23),(-37,40),
(5,40),(23,25),(60,22),(60,0),
]
return cadquery.Workplane('XZ', origin=(0,self.width/2,0)) \
.moveTo(*points[0]).polyline(points[1:]).close() \
.extrude(self.width)
# ------------------- Wheel Assembly -------------------
from cqparts.constraint import Fixed, Coincident
class WheeledAxle(cqparts.Assembly):
left_width = PositiveFloat(7, doc="left wheel width")
right_width = PositiveFloat(7, doc="right wheel width")
left_diam = PositiveFloat(25, doc="left wheel diameter")
right_diam = PositiveFloat(25, doc="right wheel diameter")
axle_diam = PositiveFloat(8, doc="axle diameter")
axle_track = PositiveFloat(50, doc="distance between wheel tread midlines")
wheel_clearance = PositiveFloat(3, doc="distance between wheel and chassis")
def make_components(self):
axle_length = self.axle_track - (self.left_width + self.right_width) / 2
return {
'axle': Axle(length=axle_length, diameter=self.axle_diam),
'left_wheel': Wheel(
width=self.left_width, diameter=self.left_diam,
),
'right_wheel': Wheel(
width=self.right_width, diameter=self.right_diam,
),
}
def make_constraints(self):
return [
Fixed(self.components['axle'].mate_origin, CoordSystem()),
Coincident(
self.components['left_wheel'].mate_origin,
self.components['axle'].mate_left
),
Coincident(
self.components['right_wheel'].mate_origin,
self.components['axle'].mate_right
),
]
def apply_cutout(self, part):
# Cut wheel & axle from given part
axle = self.components['axle']
left_wheel = self.components['left_wheel']
right_wheel = self.components['right_wheel']
local_obj = part.local_obj
local_obj = local_obj \
.cut((axle.world_coords - part.world_coords) + axle.get_cutout()) \
.cut((left_wheel.world_coords - part.world_coords) + left_wheel.get_cutout(self.wheel_clearance)) \
.cut((right_wheel.world_coords - part.world_coords) + right_wheel.get_cutout(self.wheel_clearance))
part.local_obj = local_obj
# ------------------- Car Assembly -------------------
class Car(cqparts.Assembly):
# Parameters
wheelbase = PositiveFloat(70, "distance between front and rear axles")
axle_track = PositiveFloat(60, "distance between tread midlines")
# wheels
wheel_width = PositiveFloat(10, doc="width of all wheels")
front_wheel_diam = PositiveFloat(30, doc="front wheel diameter")
rear_wheel_diam = PositiveFloat(30, doc="rear wheel diameter")
axle_diam = PositiveFloat(10, doc="axle diameter")
def make_components(self):
return {
'chassis': Chassis(width=self.axle_track),
'front_axle': WheeledAxle(
left_width=self.wheel_width,
right_width=self.wheel_width,
left_diam=self.front_wheel_diam,
right_diam=self.front_wheel_diam,
axle_diam=self.axle_diam,
axle_track=self.axle_track,
),
'rear_axle': WheeledAxle(
left_width=self.wheel_width,
right_width=self.wheel_width,
left_diam=self.rear_wheel_diam,
right_diam=self.rear_wheel_diam,
axle_diam=self.axle_diam,
axle_track=self.axle_track,
),
}
def make_constraints(self):
return [
Fixed(self.components['chassis'].mate_origin),
Coincident(
self.components['front_axle'].mate_origin,
Mate(self.components['chassis'], CoordSystem((self.wheelbase/2,0,0))),
),
Coincident(
self.components['rear_axle'].mate_origin,
Mate(self.components['chassis'], CoordSystem((-self.wheelbase/2,0,0))),
),
]
def make_alterations(self):
# cut out wheel wells
chassis = self.components['chassis']
self.components['front_axle'].apply_cutout(chassis)
self.components['rear_axle'].apply_cutout(chassis)
FreeCAD 0.16, Libs: 0.16R6712 (Git)
Fasteners workbench Loaded
============== Part Equality ==============
instance equality: False
geometry equality: False
--- parameters ---
{'diameter': 40.0, 'width': 10.0}
{'diameter': 40.0, 'width': 10.0}
param comparison: True
--- serialize ---
{'params': {'_render': {'color': (192, 192, 192), 'alpha': 1.0}, 'diameter': 40.0, '_simple': False, 'width': 10.0}, 'class': {'name': 'Wheel', 'module': '__main__'}, 'lib': {'version': '0.2.1', 'name': 'cqparts'}}
serialize comparison: True
============== Unnecessarily Duplicating Geometry ==============
c1 == c2: (False, False)
c1 == c3: (False, False)
============== Potential Solution ==============
c1 == c2: (True, True)
c1 == c3: (False, False)
============== Car Hierarchy ==============
['front_axle', 'chassis', 'rear_axle']
#!/usr/bin/env python
import cadquery
import cqparts
from cqparts.params import *
print("============== Part Equality ==============")
class Wheel(cqparts.Part):
diameter = PositiveFloat(30, doc="wheel's diameter")
width = PositiveFloat(10, doc="wheel's width (or depth)")
def make(self):
return cadquery.Workplane('XY') \
.circle(self.diameter / 2) \
.extrude(self.width)
w1 = Wheel(diameter=40)
w2 = Wheel(diameter=40) # identical
print("instance equality: %r" % (w1 == w2))
print("geometry equality: %r" % (w1.local_obj == w2.local_obj))
print("--- parameters ---")
print(w1.params(hidden=False))
print(w2.params(hidden=False))
print("param comparison: %r" % (w1.params(hidden=False) == w2.params(hidden=False)))
print("--- serialize ---")
print(w1.serialize())
print("serialize comparison: %r" % (w1.serialize() == w2.serialize()))
print("============== Unnecessarily Duplicating Geometry ==============")
class Cube(cqparts.Part):
size = PositiveFloat(1, doc="cube size")
def make(self):
return cadquery.Workplane('XY').box(self.size, self.size, self.size)
c1 = Cube(size=10)
c2 = Cube(size=10)
c3 = Cube(size=20)
eq = lambda a, b: (a == b, a.local_obj == b.local_obj)
print("c1 == c2: %r" % (eq(c1, c2),))
print("c1 == c3: %r" % (eq(c1, c3),))
print("============== Potential Solution ==============")
class AwesomePart(cqparts.Part):
INSTANCES = {}
def __init__(self, *args, **kwargs):
super(AwesomePart, self).__init__(*args, **kwargs)
self._key = tuple((k, v) for (k, v) in sorted(self.params(hidden=False).items()))
if self._key in self.INSTANCES:
self.make = lambda: self.INSTANCES[self._key].local_obj
else:
self.INSTANCES[self._key] = self
def __eq__(self, other):
return (type(self) == type(other)) and (self._key == other._key)
class AwesomeCube(AwesomePart):
size = PositiveFloat(1, doc="cube size")
def make(self):
return cadquery.Workplane('XY').box(self.size, self.size, self.size)
c1 = AwesomeCube(size=10)
c2 = AwesomeCube(size=10)
c3 = AwesomeCube(size=20)
eq = lambda a, b: (a == b, a.local_obj == b.local_obj)
print("c1 == c2: %r" % (eq(c1, c2),))
print("c1 == c3: %r" % (eq(c1, c3),))
print("============== Car Hierarchy ==============")
from car import Car
car = Car()
print(car.components.keys())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment