Last active
May 22, 2018 11:45
-
-
Save fragmuffin/8889def56002184ab309e177c6777d3e to your computer and use it in GitHub Desktop.
cqparts part equality and bom discussion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*.pyc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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