24 Commits

Author SHA1 Message Date
ee6d874b3c README: add photos 2024-02-08 01:01:20 +00:00
faba843dce web-viewer: check index.html and vtk.js into repo, to make viewable w/o building 2024-02-07 23:34:48 +00:00
549a48580b readme: add case renderings 2024-02-07 23:32:22 +00:00
a82781e6b1 cq_toplevel: don't assume as_assy=True; some consumers need a single object 2024-02-07 23:31:13 +00:00
5e8550f5f5 colorize the case and the phone separately when exporting images 2024-02-07 22:49:08 +00:00
1ef190d932 cq_toplevel: fix "back" image export to actually show the back of the phone 2024-02-07 22:47:48 +00:00
fd9e768fc1 cq_toplevel: allow exporting case in front/back/side orientation 2024-02-07 22:08:46 +00:00
425bd07d38 cq_toplevel: remove dead code from export_png_image 2024-02-07 21:51:03 +00:00
c19431ba1d replace cq_toplevel.py --export-svg with --export-png, which uses the same render pipeline as the interactive editor 2024-02-07 04:27:41 +00:00
3afdf11e8f cq_toplevel: memoize the model, to speed up the case where invoked in a way that we dont need to compute it 2024-02-07 01:41:46 +00:00
67deb480f0 doc: partial work to exporting viewer images as part of build
this approach may be flawed in that headless chromium can't actually load this vtk page, and hence i wouldn't be able to build in a sandbox (i.e. as a nix package)

a better approach may be to export the images via vtk python, though it's unclear if this is possible without a UI either
2024-02-06 23:52:17 +00:00
aac4584e30 flake: include chromium in the dev env to aid browser automation 2024-02-06 23:50:31 +00:00
b5ae83ddcd Makefile: 'cp' instead of 'ln' the html artifacts to avoid funky ways that browsers follow symlinks 2024-02-06 23:50:08 +00:00
5ed54c4aad Makefile: add rules to generate .svg files as part of make doc 2024-02-05 04:20:03 +00:00
e839bb489c Makefile: create build/ directories before generating output 2024-02-05 04:18:16 +00:00
ccc04dbd5a rename: doc-vtk -> web-viewer 2024-02-05 03:58:32 +00:00
9a35d265ea doc-vtk: move sources out of build/ 2024-02-05 03:57:04 +00:00
2b414b51ae doc-vtk: fix scrolling 2024-02-05 03:49:22 +00:00
afb41b4483 Makefile: add a make doc rule 2024-02-05 03:42:50 +00:00
2a8b5382ef doc-vtk: also render the case with a phone 2024-02-05 03:42:35 +00:00
e9f5e4aec3 cq_toplevel: implement --render-phone-only CLI option to render just the phone without any case 2024-02-05 03:41:20 +00:00
5da7a1a8d8 cq_toplevel: implement --export-vtk CLI option 2024-02-05 03:18:39 +00:00
3027847301 doc-vtk: render the actual pinephone case 2024-02-05 03:18:17 +00:00
c89764b73a build/doc-vtk: proof-of-concept model web renderer
VTK is what cadquery _actually_ uses for its docs, despite claims that it uses TJS
2024-02-03 04:22:30 +00:00
17 changed files with 731 additions and 48 deletions

View File

@@ -67,15 +67,41 @@ install:
install -m644 build/case.stl $(SHAREDIR) install -m644 build/case.stl $(SHAREDIR)
install -m644 build/case.gcode $(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: clean:
rm -rf build rm -rf build
build/case.stl: src/*.py build/case.stl: cq_toplevel.py src/*.py
mkdir -p $(shell dirname $@) mkdir -p "$(@D)"
./cq_toplevel.py --export-stl $@ ./cq_toplevel.py --export-stl $@
%.gcode: %.stl %.gcode: %.stl
mkdir -p $(shell dirname $@)
slic3r $(SLIC3R_FLAGS) $< -o $@ slic3r $(SLIC3R_FLAGS) $< -o $@
.PHONY: all install clean .PHONY: all install readme doc clean

View File

@@ -1,8 +1,23 @@
this is a 3d-printable case designed for the PinePhone, but implemented with an eye towards this is a 3d-printable case designed for the PinePhone,
generalizing beyond just that model and supporting future phones/preferences alongside but implemented with an eye towards generalizing beyond just that model
this first 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).
status: case is usable, but the print is rough and requires a good deal of manual cleaning due to aggressive fillets/overhangs.
## Prerequisites ## Prerequisites
- run `nix develop` to enter a dev environment. - run `nix develop` to enter a dev environment.

240
build/web-viewer/index.html Normal file
View File

@@ -0,0 +1,240 @@
<!-- 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

3
build/web-viewer/vtk.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -20,44 +20,84 @@ import case
import pinephone import pinephone
import ldtek_battery 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__) logger = logging.getLogger(__name__)
def svg_export_options(view: str): def export_png_image(obj, file_: str, orientation: str):
proj = (1, 1, 1) assy = _to_assy(obj)
if view == "front": renderer = toVTK(assy)
proj = (0.00, -0.05, 0.10) win = vtkRenderWindow()
elif view == "back": win.AddRenderer(renderer)
proj = (0.00, -0.05, -0.10) win.Render()
elif view == "right": camera = renderer.GetActiveCamera()
proj = (-0.10, 0.00, 0.00) if orientation == "front":
return dict( camera.Roll(-28)
width = 1024, camera.Elevation(-50)
height = 1024, elif orientation == "back":
marginLeft = 100, camera.Roll(0)
marginTop = 10, camera.Yaw(180)
showAxes = False, camera.Elevation(-35)
# projectionDir controls both the angle and the distance from the camera to the model elif orientation == "side":
projectionDir = proj, camera.Yaw(75)
strokeWidth = 0.25, camera.Elevation(30)
strokeColor = (255, 0, 0), # adjust camera so full object is visible.
hiddenColor = (0, 0, 255), # this also resizes the window, potentially changing its aspect ratio.
showHidden = True, # 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 model(): 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 ...")
render_phone = os.environ.get("CASE_RENDER_PHONE", "") not in ("", "0") 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() phone = pinephone.PinePhone()
if render_phone_only:
return case.orient_for_printing(phone)
battery = ldtek_battery.LdtekBattery() battery = ldtek_battery.LdtekBattery()
return case.case(phone, battery=battery, render_phone=render_phone) 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]
def main(): def main():
logging.basicConfig() logging.basicConfig()
logging.getLogger().setLevel(logging.INFO) logging.getLogger().setLevel(logging.INFO)
parser = argparse.ArgumentParser(description="toplevel cadquery interface") parser = argparse.ArgumentParser(description="toplevel cadquery interface")
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("--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("--export-stl") parser.add_argument("--export-stl")
parser.add_argument("--export-svg") parser.add_argument("--export-png")
parser.add_argument("--export-vtk")
parser.add_argument("--editor", action="store_true", help="view in cq-editor") parser.add_argument("--editor", action="store_true", help="view in cq-editor")
args = parser.parse_args() args = parser.parse_args()
@@ -65,22 +105,41 @@ def main():
if args.render_phone: if args.render_phone:
os.environ["CASE_RENDER_PHONE"] = "1" os.environ["CASE_RENDER_PHONE"] = "1"
logger.info("computing model ...") if args.render_phone_only:
model_ = model() os.environ["CASE_RENDER_PHONE_ONLY"] = "1"
if args.export_stl: if args.export_stl:
model_ = model()
logger.info("exporting stl to %s", 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 args.export_svg:
view = None if args.export_png:
if "front" in args.export_svg: orientation = None
view = "front" if "side" in args.export_png:
elif "back" in args.export_svg: orientation = "side"
view = "back" if "back" in args.export_png:
elif "right" in args.export_svg: orientation = "back"
view = "right" if "front" in args.export_png:
logger.info("exporting svg to %s (view: %s)", args.export_svg, str(view)) orientation = "front"
cq.exporters.export(model_, args.export_svg, opt=svg_export_options(view)) 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_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.editor: if args.editor:
logger.info("launching cq-editor") logger.info("launching cq-editor")
subprocess.check_call(["cq-editor", __file__]) subprocess.check_call(["cq-editor", __file__])
@@ -89,4 +148,6 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()
else: 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() result = model()

240
doc.in/index.html Normal file
View File

@@ -0,0 +1,240 @@
<!-- 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>

3
doc.in/vtk.js Normal file

File diff suppressed because one or more lines are too long

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -44,6 +44,17 @@ battery_harness_shell_thickness = 0.8
battery_harness_margin_bottom = 21.0 battery_harness_margin_bottom = 21.0
battery_harness_margin_top = 2.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): def _thicken(solid, thickness: float=1, combine=False, **kwargs):
""" """
dilate the solid in all dimensions by `thickness` and then subtract the original. dilate the solid in all dimensions by `thickness` and then subtract the original.
@@ -335,7 +346,7 @@ def orient_for_printing(case):
""" """
return case.rotate((0, 0, 0), (1000, 0, 0), angleDegrees=180) return case.rotate((0, 0, 0), (1000, 0, 0), angleDegrees=180)
def case(phone, battery=None, render_phone: bool=False): def case(phone, battery=None, render_phone: bool=False, as_assy: bool=True):
body = phone.solids(tag="body") body = phone.solids(tag="body")
power = phone.solids(tag="power") power = phone.solids(tag="power")
volume = phone.solids(tag="volume") volume = phone.solids(tag="volume")
@@ -368,8 +379,15 @@ def case(phone, battery=None, render_phone: bool=False):
# TODO: compress the case along the Z axis, to give a snugger fit (0.8mm is a good compression) # 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) 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 return case