From aa57d262e29406d0fa53b51e5a797dc93cd8529e Mon Sep 17 00:00:00 2001 From: Colin Date: Tue, 26 Dec 2023 11:53:01 +0000 Subject: [PATCH] create a trivial case from the phone model missing cutouts for ports and extrusions for buttons --- cq_toplevel.py | 11 ++++++++--- src/case.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/pinephone.py | 25 ++++++++++++++++--------- 3 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 src/case.py diff --git a/cq_toplevel.py b/cq_toplevel.py index 0495edc..35da7b9 100755 --- a/cq_toplevel.py +++ b/cq_toplevel.py @@ -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() diff --git a/src/case.py b/src/case.py new file mode 100644 index 0000000..4e64ec1 --- /dev/null +++ b/src/case.py @@ -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") + # 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 diff --git a/src/pinephone.py b/src/pinephone.py index 74af394..af106ee 100644 --- a/src/pinephone.py +++ b/src/pinephone.py @@ -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_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():