3 Commits

Author SHA1 Message Date
906b94fc4f build/doc: fix typos
now it actually does render
2024-02-03 04:20:28 +00:00
ddc87b8ec7 cq_toplevel: add an --export-tjs option 2024-02-03 04:19:54 +00:00
27a4cf5626 add three.js example WebGL renderer 2024-02-03 03:04:24 +00:00
18 changed files with 105 additions and 728 deletions

View File

@@ -67,41 +67,15 @@ install:
install -m644 build/case.stl $(SHAREDIR)
install -m644 build/case.gcode $(SHAREDIR)
%/pinephone_case.vtk: cq_toplevel.py src/*.py
mkdir -p "$(@D)"
./cq_toplevel.py --export-vtk $@
%/pinephone_phone.vtk: cq_toplevel.py src/*.py
mkdir -p "$(@D)"
./cq_toplevel.py --render-phone-only --export-vtk $@
build/web-viewer/vtk.js: doc.in/vtk.js
mkdir -p build/web-viewer
cp $< $@
build/web-viewer/index.html: doc.in/index.html build/web-viewer/vtk.js build/web-viewer/pinephone_case.vtk build/web-viewer/pinephone_phone.vtk
mkdir -p build/web-viewer
cp $< $@
%_case.png: cq_toplevel.py src/*.py
mkdir -p "$(@D)"
./cq_toplevel.py --export-png $@
%_case_with_phone.png: cq_toplevel.py src/*.py
mkdir -p "$(@D)"
./cq_toplevel.py --render-phone --export-png $@
readme: readme_files/pinephone_front_case_with_phone.png readme_files/pinephone_back_case_with_phone.png readme_files/pinephone_side_case_with_phone.png
doc: readme build/web-viewer/index.html
clean:
rm -rf build
build/case.stl: cq_toplevel.py src/*.py
mkdir -p "$(@D)"
build/case.stl: src/*.py
mkdir -p $(shell dirname $@)
./cq_toplevel.py --export-stl $@
%.gcode: %.stl
mkdir -p $(shell dirname $@)
slic3r $(SLIC3R_FLAGS) $< -o $@
.PHONY: all install readme doc clean
.PHONY: all install clean

View File

@@ -1,23 +1,8 @@
this is a 3d-printable case designed for the PinePhone,
but implemented with an eye towards generalizing beyond just that model
and supporting future phones/preferences alongside this first model.
as an example, the default case (pictured below) includes a pouch for carrying an external battery: a quick solution to achieve all-day battery life with any of the stock OSes. the back of this case prints flat, with a mesh that stretches to fit the battery upon installation.
## Images
![front view](readme_files/pinephone_front_case_with_phone.png)
![side view](readme_files/pinephone_side_case_with_phone.png)
![back view](readme_files/pinephone_back_case_with_phone.png)
![back view with battery](readme_files/pinephone_irl_back.jpg)
![side view with battery](readme_files/pinephone_irl_side.jpg)
![phone in hand](readme_files/pinephone_irl_front.jpg)
for an interactive viewer, see [build/web-viewer/index.html](https://git.uninsane.org/colin/phone-case-cq/raw/branch/docs-dev/build/web-viewer/index.html).
you'll need a browser which supports webGL (e.g. chromium).
this is a 3d-printable case designed for the PinePhone, but implemented with an eye towards
generalizing beyond just that model and supporting future phones/preferences alongside
this first model.
status: case is usable, but the print is rough and requires a good deal of manual cleaning due to aggressive fillets/overhangs.
## Prerequisites
- run `nix develop` to enter a dev environment.

47
build/doc/index.html Normal file
View File

@@ -0,0 +1,47 @@
<html>
<head>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three/build/three.module.js"
}
}
</script>
</head>
<body>
<!-- based on three.js example here: <https://jsfiddle.net/2nyxkmco/> -->
<script type="module">
import * as THREE from 'three';
const width = window.innerWidth, height = window.innerHeight;
// init
const camera = new THREE.PerspectiveCamera( 70, width / height, 0.01, 10 );
camera.position.z = 1;
const scene = new THREE.Scene();
const geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
const material = new THREE.MeshNormalMaterial();
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( width, height );
renderer.setAnimationLoop( animation );
document.body.appendChild( renderer.domElement );
// animation
function animation( time ) {
mesh.rotation.x = time / 2000;
mesh.rotation.y = time / 1000;
renderer.render( scene, camera );
}
</script>
</body>
</html>

View File

@@ -1,240 +0,0 @@
<!-- heavily borrows from rendered cadquery docs: <https://cadquery.readthedocs.io/en/latest/examples.html> -->
<!-- vtk.js = Visualization ToolKit; JS version of the same library cadquery uses during runtime -->
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.vtk-viewer {
width: 100%;
height: 70%;
border: 1px solid #bbb;
}
</style>
<script src="vtk.js"></script>
<script>
const RENDERERS = {};
var ID = 0;
var rootContainer = null;
const renderWindow = vtk.Rendering.Core.vtkRenderWindow.newInstance();
const openglRenderWindow = vtk.Rendering.OpenGL.vtkRenderWindow.newInstance();
renderWindow.addView(openglRenderWindow);
const interact_style = vtk.Interaction.Style.vtkInteractorStyleManipulator.newInstance();
const manips = {
rot: vtk.Interaction.Manipulators.vtkMouseCameraTrackballRotateManipulator.newInstance(),
pan: vtk.Interaction.Manipulators.vtkMouseCameraTrackballPanManipulator.newInstance(),
zoom1: vtk.Interaction.Manipulators.vtkMouseCameraTrackballZoomManipulator.newInstance(),
zoom2: vtk.Interaction.Manipulators.vtkMouseCameraTrackballZoomManipulator.newInstance(),
roll: vtk.Interaction.Manipulators.vtkMouseCameraTrackballRollManipulator.newInstance(),
};
manips.zoom1.setControl(true);
manips.zoom2.setButton(3);
manips.roll.setShift(true);
manips.pan.setButton(2);
for (var k in manips){{
interact_style.addMouseManipulator(manips[k]);
}};
const interactor = vtk.Rendering.Core.vtkRenderWindowInteractor.newInstance();
interactor.setView(openglRenderWindow);
interactor.initialize();
interactor.setInteractorStyle(interact_style);
function setVtkRoot(rootContainer_) {
rootContainer = rootContainer_;
rootContainer.style.position = 'fixed';
//rootContainer.style.zIndex = -1;
rootContainer.style.left = 0;
rootContainer.style.top = 0;
rootContainer.style.pointerEvents = 'none';
rootContainer.style.width = '100%';
rootContainer.style.height = '100%';
openglRenderWindow.setContainer(rootContainer);
};
function updateViewPort(element, renderer) {
const { innerHeight, innerWidth } = window;
const { x, y, width, height } = element.getBoundingClientRect();
const viewport = [
x / innerWidth,
1 - (y + height) / innerHeight,
(x + width) / innerWidth,
1 - y / innerHeight,
];
if (renderer) {
renderer.setViewport(...viewport);
}
}
function recomputeViewports() {
const rendererElems = document.querySelectorAll('.renderer');
for (let i = 0; i < rendererElems.length; i++) {
const elem = rendererElems[i];
const { id } = elem;
const renderer = RENDERERS[id];
updateViewPort(elem, renderer);
}
renderWindow.render();
}
function resize() {
rootContainer.style.width = `${window.innerWidth}px`;
openglRenderWindow.setSize(window.innerWidth, window.innerHeight);
recomputeViewports();
}
window.addEventListener('resize', resize);
document.addEventListener('scroll', recomputeViewports);
function enterCurrentRenderer(e) {
interactor.bindEvents(document.body);
interact_style.setEnabled(true);
interactor.setCurrentRenderer(RENDERERS[e.target.id]);
}
function exitCurrentRenderer(e) {
interactor.setCurrentRenderer(null);
interact_style.setEnabled(false);
interactor.unbindEvents();
}
function applyStyle(element) {
element.classList.add('renderer');
element.style.width = '100%';
element.style.height = '100%';
element.style.display = 'inline-block';
element.style.boxSizing = 'border';
return element;
}
window.addEventListener('load', resize);
function render(data, parent_element, ratio){
// Initial setup
const renderer = vtk.Rendering.Core.vtkRenderer.newInstance({ background: [1, 1, 1 ] });
// iterate over all children children
for (var el of data){
var trans = el.position;
var rot = el.orientation;
var rgba = el.color;
var shape = el.shape;
// load the inline data
var reader = vtk.IO.XML.vtkXMLPolyDataReader.newInstance();
const textEncoder = new TextEncoder();
reader.parseAsArrayBuffer(textEncoder.encode(shape));
// setup actor,mapper and add
const mapper = vtk.Rendering.Core.vtkMapper.newInstance();
mapper.setInputConnection(reader.getOutputPort());
mapper.setResolveCoincidentTopologyToPolygonOffset();
mapper.setResolveCoincidentTopologyPolygonOffsetParameters(0.5,100);
const actor = vtk.Rendering.Core.vtkActor.newInstance();
actor.setMapper(mapper);
// set color and position
actor.getProperty().setColor(rgba.slice(0,3));
actor.getProperty().setOpacity(rgba[3]);
actor.rotateZ(rot[2]*180/Math.PI);
actor.rotateY(rot[1]*180/Math.PI);
actor.rotateX(rot[0]*180/Math.PI);
actor.setPosition(trans);
renderer.addActor(actor);
};
//add the container
const container = applyStyle(document.createElement("div"));
parent_element.appendChild(container);
container.addEventListener('mouseenter', enterCurrentRenderer);
container.addEventListener('mouseleave', exitCurrentRenderer);
container.id = ID;
renderWindow.addRenderer(renderer);
updateViewPort(container, renderer);
renderer.getActiveCamera().set({ position: [1, -1, 1], viewUp: [0, 0, 1] });
renderer.resetCamera();
RENDERERS[ID] = renderer;
ID++;
};
</script>
<!-- load model data. populates a global variable whose name matches the filename -->
<script src="pinephone_case.vtk.js"></script>
<script src="pinephone_phone.vtk.js"></script>
<script>
function renderToConsole(modelName) {
// turns out there's just one canvas for the whole renderer,
// so this ignores the modelName and exports the whole canvas
// var viewerCanvas = document.querySelector(`#vtk-viewer-${modelName} > canvas`);
var viewerCanvas = document.querySelector("#vtk-viewer-root > canvas");
console.log("logging viewer image to console to allow the Makefile to capture a static image for docs...");
var image = viewerCanvas.toDataURL("image/png")
console.log("vtk-viewer-canvas: pinephone_case: " + image);
}
</script>
</head>
<body>
<div id="vtk-viewer-root">
<script>
setVtkRoot(document.currentScript.parentNode);
</script>
</div>
<div class="vtk-viewer" id="vtk-viewer-pinephone_case">
<script>
var parent_element = document.currentScript.parentNode;
var pinephone_case_options = [{
"color": [ 1.0, 0.8, 0.0, 1.0 ],
"position": [ 0.0, 0.0, 0.0 ],
"orientation": [ 0.0, 0.0, 0.0 ],
"shape": pinephone_case
}];
render(pinephone_case_options, parent_element);
</script>
</div>
<div class="vtk-viewer">
<script>
var parent_element = document.currentScript.parentNode;
var pinephone_case_with_phone_options = [
{
"color": [ 1.0, 0.8, 0.0, 1.0 ],
"position": [ 0.0, 0.0, 0.0 ],
"orientation": [ 0.0, 0.0, 0.0 ],
"shape": pinephone_case
},
{
"color": [ 0.2, 0.2, 0.2, 1.0 ],
"position": [ 0.0, 0.0, 0.0 ],
"orientation": [ 0.0, 0.0, 0.0 ],
"shape": pinephone_phone
}
];
render(pinephone_case_with_phone_options, parent_element);
</script>
</div>
<script>
resize();
renderToConsole("pinephone_case");
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,84 +20,45 @@ import case
import pinephone
import ldtek_battery
from cadquery.occ_impl.assembly import toVTK
from cadquery.vis import _to_assy
from vtkmodules.vtkRenderingCore import vtkRenderWindow, vtkWindowToImageFilter
from vtkmodules.vtkIOImage import vtkPNGWriter
logger = logging.getLogger(__name__)
def export_png_image(obj, file_: str, orientation: str):
assy = _to_assy(obj)
renderer = toVTK(assy)
win = vtkRenderWindow()
win.AddRenderer(renderer)
win.Render()
camera = renderer.GetActiveCamera()
if orientation == "front":
camera.Roll(-28)
camera.Elevation(-50)
elif orientation == "back":
camera.Roll(0)
camera.Yaw(180)
camera.Elevation(-35)
elif orientation == "side":
camera.Yaw(75)
camera.Elevation(30)
# adjust camera so full object is visible.
# this also resizes the window, potentially changing its aspect ratio.
# it's important that the window has been `Render()`'d at least once by now,
# else it'll adjust the camera based on the wrong aspect ratio.
renderer.ResetCamera()
renderer.SetBackground(0.8, 0.8, 0.8)
def svg_export_options(view: str):
proj = (1, 1, 1)
if view == "front":
proj = (0.00, -0.05, 0.10)
elif view == "back":
proj = (0.00, -0.05, -0.10)
elif view == "right":
proj = (-0.10, 0.00, 0.00)
return dict(
width = 1024,
height = 1024,
marginLeft = 100,
marginTop = 10,
showAxes = False,
# projectionDir controls both the angle and the distance from the camera to the model
projectionDir = proj,
strokeWidth = 0.25,
strokeColor = (255, 0, 0),
hiddenColor = (0, 0, 255),
showHidden = True,
)
win.Render()
# documented here: <https://examples.vtk.org/site/Python/IO/ImageWriter/>
win_to_input = vtkWindowToImageFilter()
win_to_input.SetInput(win)
win_to_input.SetInputBufferTypeToRGB()
win_to_input.ReadFrontBufferOff()
win_to_input.Update()
exporter = vtkPNGWriter()
exporter.SetFileName(file_)
exporter.SetInputConnection(win_to_input.GetOutputPort())
exporter.Write()
def _model(as_assy: bool=False):
logger.info("computing model ...")
def model():
render_phone = os.environ.get("CASE_RENDER_PHONE", "") not in ("", "0")
render_phone_only = os.environ.get("CASE_RENDER_PHONE_ONLY", "") not in ("", "0")
phone = pinephone.PinePhone()
if render_phone_only:
return case.orient_for_printing(phone)
battery = ldtek_battery.LdtekBattery()
return case.case(phone, battery=battery, render_phone=render_phone, as_assy=as_assy)
_computedModels = {}
def model(as_assy: bool=False):
""" memoized wrapper around `_model` """
global _computedModels
if as_assy not in _computedModels:
_computedModels[as_assy] = _model(as_assy=as_assy)
return _computedModels[as_assy]
return case.case(phone, battery=battery, render_phone=render_phone)
def main():
logging.basicConfig()
logging.getLogger().setLevel(logging.INFO)
parser = argparse.ArgumentParser(description="toplevel cadquery interface")
parser.add_argument("--render-phone", action="store_true", help="render the case and also the phone within it; useful to confirm fit visually before printing")
parser.add_argument("--render-phone-only", action="store_true", help="render *only* the phone, not even the case")
parser.add_argument("--render-phone", action="store_true", help="include the phone model itself in the stl; useful to confirm fit visually before printing")
parser.add_argument("--export-stl")
parser.add_argument("--export-png")
parser.add_argument("--export-vtk")
parser.add_argument("--export-svg")
parser.add_argument("--export-tjs")
parser.add_argument("--editor", action="store_true", help="view in cq-editor")
args = parser.parse_args()
@@ -105,40 +66,33 @@ def main():
if args.render_phone:
os.environ["CASE_RENDER_PHONE"] = "1"
if args.render_phone_only:
os.environ["CASE_RENDER_PHONE_ONLY"] = "1"
logger.info("computing model ...")
model_ = model()
if args.export_stl:
model_ = model()
logger.info("exporting stl to %s", args.export_stl)
cq.exporters.export(model_, args.export_stl)
if args.export_png:
orientation = None
if "side" in args.export_png:
orientation = "side"
if "back" in args.export_png:
orientation = "back"
if "front" in args.export_png:
orientation = "front"
model_ = model(as_assy=True)
logger.info("exporting png to %s", args.export_png)
export_png_image(model_, args.export_png, orientation)
if args.export_svg:
view = None
if "front" in args.export_svg:
view = "front"
elif "back" in args.export_svg:
view = "back"
elif "right" in args.export_svg:
view = "right"
logger.info("exporting svg to %s (view: %s)", args.export_svg, str(view))
cq.exporters.export(model_, args.export_svg, opt=svg_export_options(view))
if args.export_vtk:
vtk_file = args.export_vtk
js_var, _ext = os.path.splitext(os.path.basename(vtk_file))
js_file = f'{vtk_file}.js'
model_ = model()
logger.info("exporting VTK (for web rendering) to %s", vtk_file)
cq.exporters.export(model_, vtk_file, cq.exporters.ExportTypes.VTP)
logger.info("wrapping VTK data in a javascript variable (var %s) in %s", js_var, js_file)
vtk_data = open(vtk_file).read()
with open(js_file, 'w') as js:
js.write(f"var {js_var} = `\n")
js.write(vtk_data)
js.write("`;\n")
if args.export_tjs:
logger.info("exporting three.js to %s", args.export_tjs)
cq.exporters.export(
model_,
args.export_tjs,
tolerance=0.01,
angularTolerance=0.1,
exportType=cq.exporters.ExportTypes.TJS,
)
if args.editor:
logger.info("launching cq-editor")
@@ -148,6 +102,4 @@ def main():
if __name__ == "__main__":
main()
else:
# this `result` var should be picked up by cadquery, in case we were imported by it.
# note that we don't actually get here until the user presses the `>` render button.
result = model()

View File

@@ -1,240 +0,0 @@
<!-- heavily borrows from rendered cadquery docs: <https://cadquery.readthedocs.io/en/latest/examples.html> -->
<!-- vtk.js = Visualization ToolKit; JS version of the same library cadquery uses during runtime -->
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.vtk-viewer {
width: 100%;
height: 70%;
border: 1px solid #bbb;
}
</style>
<script src="vtk.js"></script>
<script>
const RENDERERS = {};
var ID = 0;
var rootContainer = null;
const renderWindow = vtk.Rendering.Core.vtkRenderWindow.newInstance();
const openglRenderWindow = vtk.Rendering.OpenGL.vtkRenderWindow.newInstance();
renderWindow.addView(openglRenderWindow);
const interact_style = vtk.Interaction.Style.vtkInteractorStyleManipulator.newInstance();
const manips = {
rot: vtk.Interaction.Manipulators.vtkMouseCameraTrackballRotateManipulator.newInstance(),
pan: vtk.Interaction.Manipulators.vtkMouseCameraTrackballPanManipulator.newInstance(),
zoom1: vtk.Interaction.Manipulators.vtkMouseCameraTrackballZoomManipulator.newInstance(),
zoom2: vtk.Interaction.Manipulators.vtkMouseCameraTrackballZoomManipulator.newInstance(),
roll: vtk.Interaction.Manipulators.vtkMouseCameraTrackballRollManipulator.newInstance(),
};
manips.zoom1.setControl(true);
manips.zoom2.setButton(3);
manips.roll.setShift(true);
manips.pan.setButton(2);
for (var k in manips){{
interact_style.addMouseManipulator(manips[k]);
}};
const interactor = vtk.Rendering.Core.vtkRenderWindowInteractor.newInstance();
interactor.setView(openglRenderWindow);
interactor.initialize();
interactor.setInteractorStyle(interact_style);
function setVtkRoot(rootContainer_) {
rootContainer = rootContainer_;
rootContainer.style.position = 'fixed';
//rootContainer.style.zIndex = -1;
rootContainer.style.left = 0;
rootContainer.style.top = 0;
rootContainer.style.pointerEvents = 'none';
rootContainer.style.width = '100%';
rootContainer.style.height = '100%';
openglRenderWindow.setContainer(rootContainer);
};
function updateViewPort(element, renderer) {
const { innerHeight, innerWidth } = window;
const { x, y, width, height } = element.getBoundingClientRect();
const viewport = [
x / innerWidth,
1 - (y + height) / innerHeight,
(x + width) / innerWidth,
1 - y / innerHeight,
];
if (renderer) {
renderer.setViewport(...viewport);
}
}
function recomputeViewports() {
const rendererElems = document.querySelectorAll('.renderer');
for (let i = 0; i < rendererElems.length; i++) {
const elem = rendererElems[i];
const { id } = elem;
const renderer = RENDERERS[id];
updateViewPort(elem, renderer);
}
renderWindow.render();
}
function resize() {
rootContainer.style.width = `${window.innerWidth}px`;
openglRenderWindow.setSize(window.innerWidth, window.innerHeight);
recomputeViewports();
}
window.addEventListener('resize', resize);
document.addEventListener('scroll', recomputeViewports);
function enterCurrentRenderer(e) {
interactor.bindEvents(document.body);
interact_style.setEnabled(true);
interactor.setCurrentRenderer(RENDERERS[e.target.id]);
}
function exitCurrentRenderer(e) {
interactor.setCurrentRenderer(null);
interact_style.setEnabled(false);
interactor.unbindEvents();
}
function applyStyle(element) {
element.classList.add('renderer');
element.style.width = '100%';
element.style.height = '100%';
element.style.display = 'inline-block';
element.style.boxSizing = 'border';
return element;
}
window.addEventListener('load', resize);
function render(data, parent_element, ratio){
// Initial setup
const renderer = vtk.Rendering.Core.vtkRenderer.newInstance({ background: [1, 1, 1 ] });
// iterate over all children children
for (var el of data){
var trans = el.position;
var rot = el.orientation;
var rgba = el.color;
var shape = el.shape;
// load the inline data
var reader = vtk.IO.XML.vtkXMLPolyDataReader.newInstance();
const textEncoder = new TextEncoder();
reader.parseAsArrayBuffer(textEncoder.encode(shape));
// setup actor,mapper and add
const mapper = vtk.Rendering.Core.vtkMapper.newInstance();
mapper.setInputConnection(reader.getOutputPort());
mapper.setResolveCoincidentTopologyToPolygonOffset();
mapper.setResolveCoincidentTopologyPolygonOffsetParameters(0.5,100);
const actor = vtk.Rendering.Core.vtkActor.newInstance();
actor.setMapper(mapper);
// set color and position
actor.getProperty().setColor(rgba.slice(0,3));
actor.getProperty().setOpacity(rgba[3]);
actor.rotateZ(rot[2]*180/Math.PI);
actor.rotateY(rot[1]*180/Math.PI);
actor.rotateX(rot[0]*180/Math.PI);
actor.setPosition(trans);
renderer.addActor(actor);
};
//add the container
const container = applyStyle(document.createElement("div"));
parent_element.appendChild(container);
container.addEventListener('mouseenter', enterCurrentRenderer);
container.addEventListener('mouseleave', exitCurrentRenderer);
container.id = ID;
renderWindow.addRenderer(renderer);
updateViewPort(container, renderer);
renderer.getActiveCamera().set({ position: [1, -1, 1], viewUp: [0, 0, 1] });
renderer.resetCamera();
RENDERERS[ID] = renderer;
ID++;
};
</script>
<!-- load model data. populates a global variable whose name matches the filename -->
<script src="pinephone_case.vtk.js"></script>
<script src="pinephone_phone.vtk.js"></script>
<script>
function renderToConsole(modelName) {
// turns out there's just one canvas for the whole renderer,
// so this ignores the modelName and exports the whole canvas
// var viewerCanvas = document.querySelector(`#vtk-viewer-${modelName} > canvas`);
var viewerCanvas = document.querySelector("#vtk-viewer-root > canvas");
console.log("logging viewer image to console to allow the Makefile to capture a static image for docs...");
var image = viewerCanvas.toDataURL("image/png")
console.log("vtk-viewer-canvas: pinephone_case: " + image);
}
</script>
</head>
<body>
<div id="vtk-viewer-root">
<script>
setVtkRoot(document.currentScript.parentNode);
</script>
</div>
<div class="vtk-viewer" id="vtk-viewer-pinephone_case">
<script>
var parent_element = document.currentScript.parentNode;
var pinephone_case_options = [{
"color": [ 1.0, 0.8, 0.0, 1.0 ],
"position": [ 0.0, 0.0, 0.0 ],
"orientation": [ 0.0, 0.0, 0.0 ],
"shape": pinephone_case
}];
render(pinephone_case_options, parent_element);
</script>
</div>
<div class="vtk-viewer">
<script>
var parent_element = document.currentScript.parentNode;
var pinephone_case_with_phone_options = [
{
"color": [ 1.0, 0.8, 0.0, 1.0 ],
"position": [ 0.0, 0.0, 0.0 ],
"orientation": [ 0.0, 0.0, 0.0 ],
"shape": pinephone_case
},
{
"color": [ 0.2, 0.2, 0.2, 1.0 ],
"position": [ 0.0, 0.0, 0.0 ],
"orientation": [ 0.0, 0.0, 0.0 ],
"shape": pinephone_phone
}
];
render(pinephone_case_with_phone_options, parent_element);
</script>
</div>
<script>
resize();
renderToConsole("pinephone_case");
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -15,7 +15,6 @@
cqPkgs.cadquery
cqPkgs.cq-editor
pkgs.slic3r
pkgs.chromium
# (pkgs.python37.withPackages (ps: with ps; [ cadquery ]))
];
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -44,17 +44,6 @@ battery_harness_shell_thickness = 0.8
battery_harness_margin_bottom = 21.0
battery_harness_margin_top = 2.0
dark_gray = cq.Color(95/256, 87/256, 79/256)
mid_gray = cq.Color(128/256, 118/256, 108/256)
mid_light_gray = cq.Color(160/256, 150/256, 140/256)
light_gray = cq.Color(192/256, 182/256, 172/256)
yellow = cq.Color(255/256, 236/256, 39/256)
orange = cq.Color(255/256, 192/256, 39/256)
yellow_orange = cq.Color(255/256, 208/256, 49/256)
body_color = mid_light_gray
case_color = yellow_orange
def _thicken(solid, thickness: float=1, combine=False, **kwargs):
"""
dilate the solid in all dimensions by `thickness` and then subtract the original.
@@ -346,7 +335,7 @@ def orient_for_printing(case):
"""
return case.rotate((0, 0, 0), (1000, 0, 0), angleDegrees=180)
def case(phone, battery=None, render_phone: bool=False, as_assy: bool=True):
def case(phone, battery=None, render_phone: bool=False):
body = phone.solids(tag="body")
power = phone.solids(tag="power")
volume = phone.solids(tag="volume")
@@ -379,15 +368,8 @@ def case(phone, battery=None, render_phone: bool=False, as_assy: bool=True):
# TODO: compress the case along the Z axis, to give a snugger fit (0.8mm is a good compression)
if render_phone:
case = case.union(phone)
case = orient_for_printing(case)
phone = orient_for_printing(phone)
if as_assy:
case = cq.Assembly(case, color=case_color)
if render_phone:
case = case.add(phone, color=body_color)
else:
if render_phone:
case = case.union(phone)
return case