create a trivial case from the phone model
missing cutouts for ports and extrusions for buttons
This commit is contained in:
@@ -15,10 +15,15 @@ import sys
|
||||
|
||||
sys.path.append(os.path.join(os.getcwd(), "src"))
|
||||
|
||||
import case
|
||||
import pinephone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def model():
|
||||
phone = pinephone.PinePhone()
|
||||
return case.case(phone)
|
||||
|
||||
def main():
|
||||
logging.basicConfig()
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
@@ -28,14 +33,14 @@ def main():
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
model = pinephone.PinePhone()
|
||||
model_ = model()
|
||||
|
||||
if args.export_stl:
|
||||
logger.info("exporting stl to %s", args.export_stl)
|
||||
cq.exporters.export(model, args.export_stl)
|
||||
cq.exporters.export(model_, args.export_stl)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
else:
|
||||
result = pinephone.PinePhone()
|
||||
result = model()
|
||||
|
48
src/case.py
Normal file
48
src/case.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# thickness of case walls
|
||||
thickness = 1.5
|
||||
|
||||
# how far into the xy plane to extend the part of the case which covers the front of the phone.
|
||||
# the top of the phone contains stuff we don't want to cover (camera); the bottom has more margin
|
||||
overhang_leftright = 2
|
||||
overhang_top = 2
|
||||
overhang_bot = 3.5
|
||||
# how much to smooth the overhangs, else the main opening in front appears rectangular
|
||||
overhang_radius = 2
|
||||
|
||||
def _makeShell(solid, thickness: float=1, combine=False, **kwargs):
|
||||
"""
|
||||
dilate the solid in all dimensions by `thickness` and then subtract the original.
|
||||
this implementation only behaves as expected if the solid has no cusps (i.e. is smoothed).
|
||||
so for boxes, one should fillet the edges first (even if fillet'd with a trivial radius).
|
||||
"""
|
||||
# alternatively, to perform an inset, set thickness negative and use combine="cut"
|
||||
return solid.faces().each(lambda f: f.thicken(thickness), combine=combine)
|
||||
|
||||
def front_cutaway(case):
|
||||
# split the case into the front part, which covers the screen, and the rear
|
||||
split_case = case.faces("<<Z").workplane(offset=-thickness).split(keepTop=True, keepBottom=True)
|
||||
case_front, case_back = split_case.all()
|
||||
# front_face is a closed face capturing the full width/height of the case at z=0
|
||||
front_face = case_front.faces(">Z")
|
||||
# we only want to cutaway this smaller part of the front face
|
||||
front_face_cutaway = (front_face
|
||||
.intersect(front_face.translate((thickness + overhang_leftright, 0, 0)))
|
||||
.intersect(front_face.translate((-(thickness + overhang_leftright), 0, 0)))
|
||||
.intersect(front_face.translate((0, thickness + overhang_top, 0)))
|
||||
.intersect(front_face.translate((0, -thickness - overhang_bot, 0)))
|
||||
)
|
||||
# "extrude" the front_face_cutaway into something thick enough to actually cut out
|
||||
# N.B.: i think this logic isn't 100% correct
|
||||
return front_face_cutaway.faces("|Z").each(lambda f: f.thicken(-thickness), combine=False) \
|
||||
.edges("|Z").fillet(overhang_radius)
|
||||
# front_cutaway = front_face_cutaway.faces("|Z").each(lambda f: f.thicken(thickness).translate((0, 0, -thickness)), combine=False)
|
||||
|
||||
|
||||
def case(phone):
|
||||
# the entire case, before cutting anything away:
|
||||
case = _makeShell(phone.solids(tag="body"), thickness)
|
||||
case = case.cut(front_cutaway(case))
|
||||
# TODO: compress the case along the Z axis, to give a snugger fit (0.8mm is a good compression)
|
||||
# TODO: cut out the camera, aux, USB.
|
||||
# TODO: add in volume/power buttons
|
||||
return case
|
@@ -52,19 +52,20 @@ def body():
|
||||
return (
|
||||
cq.Workplane("front")
|
||||
.box(body_width, body_length, body_height, centered=False)
|
||||
.tag("body")
|
||||
.tag("body_box")
|
||||
.faces(">>X").tag("body_right")
|
||||
.faces(tag="body")
|
||||
.faces(tag="body_box")
|
||||
.faces("<<Y").tag("body_top")
|
||||
.faces(tag="body")
|
||||
.faces(tag="body_box")
|
||||
.faces(">>Y").tag("body_bottom")
|
||||
.faces(tag="body")
|
||||
.faces(tag="body_box")
|
||||
.faces(">>Z").tag("body_back")
|
||||
.faces(tag="body")
|
||||
.faces(tag="body_box")
|
||||
.edges("|Z")
|
||||
.fillet(body_rad_xy)
|
||||
.edges("(|X or |Y)")
|
||||
.fillet(body_rad_front_z)
|
||||
.tag("body")
|
||||
)
|
||||
|
||||
def volume(body):
|
||||
@@ -74,9 +75,12 @@ def volume(body):
|
||||
.workplane()
|
||||
.move(volume_min_y, volume_min_z)
|
||||
.box(volume_length, volume_height, volume_width, centered=False, combine=False)
|
||||
.tag("volume")
|
||||
.tag("volume_box")
|
||||
.edges("|X")
|
||||
.fillet(volume_height/3) #< XXX: cadquery doesn't allow a full 1/2 fillet
|
||||
# .edges("|Y")
|
||||
# .fillet(volume_width/3)
|
||||
.tag("volume")
|
||||
)
|
||||
|
||||
def power(body):
|
||||
@@ -86,9 +90,10 @@ def power(body):
|
||||
.workplane()
|
||||
.move(power_min_y, power_min_z)
|
||||
.box(power_length, power_height, power_width, centered=False, combine=False)
|
||||
.tag("volume")
|
||||
.tag("power_box")
|
||||
.edges("|X")
|
||||
.fillet(power_height/3) #< XXX: cadquery doesn't allow a full 1/2 fillet
|
||||
.tag("power")
|
||||
)
|
||||
|
||||
def camera(body):
|
||||
@@ -98,9 +103,10 @@ def camera(body):
|
||||
.workplane()
|
||||
.move(camera_min_x, camera_min_y)
|
||||
.box(camera_width, camera_length, 2, centered=False, combine=False)
|
||||
.tag("camera")
|
||||
.tag("camera_box")
|
||||
.edges("|Z")
|
||||
.fillet(camera_length/3)
|
||||
.tag("camera")
|
||||
)
|
||||
|
||||
def aux(body):
|
||||
@@ -120,9 +126,10 @@ def usb(body):
|
||||
.workplane(offset=-10)
|
||||
.move(-body_width/2, usb_min_z)
|
||||
.box(usb_width, usb_height, 10, centered=(True, False, False), combine=False)
|
||||
.tag("usb")
|
||||
.tag("usb_box")
|
||||
.edges("|Y")
|
||||
.fillet(usb_height/3)
|
||||
.tag("usb")
|
||||
)
|
||||
|
||||
def PinePhone():
|
||||
|
Reference in New Issue
Block a user