I am working on a 3D viewer and I am able to struggle my way through a working app.
I am getting the working app to do what I want, so I’m fairly happy. But there are things that I don’t understand and I would be even happier if someone with a good understanding of the inner workings of Anvil or JavaScript or Threejs or whatever I don’t understand helped me understand what happens.
Here are a few questions about the snippet of code below:
- Why do I need that
['default']
when I getOrbitControls
andFontLoader
? - Why the
font_loaded
and thefont_load_error
are not called?
Since they are not called, I decided to just wait for the fonts to be loaded, and… - Why removing the
time.sleep(0.001)
the font is not loaded (well, it’s loaded later)?
I thought theprint
would do whatever thesleep
does and some more, but I was surprised to see that aprint
doesn’t help, while asleep
does the job.
def canvas_3d_show(self, **event_args):
def font_loaded(font):
print(f'font_loaded {font.family}')
def font_load_error(error):
if error:
print(f'error.message {error.message}')
print(f'error.notLoadedFonts {error.notLoadedFonts}')
else:
print('all fonts loaded')
THREE = anvil.js.import_from("https://cdn.skypack.dev/three@0.138.0")
OrbitControls = anvil.js.import_from("https://cdn.skypack.dev/three-orbit-controls")['default'](THREE)
FontLoader = anvil.js.import_from("https://cdn.skypack.dev/FontLoader")['default']
font_loader = FontLoader(['Trebuchet MS:n4', 'Arial:n4', 'Serif:n4'], font_loaded, font_load_error)
font_loader.loadFonts()
t = time.time() + 5
counter = 0
while not font_loader._finished and time.time() < t:
counter += 1
time.sleep(0.001) # removing this line it doesn't work
if counter < 10: # prevent it from printing thousands of times
print('hi')
print(font_loader._finished, counter)
- Threejs either uses the canvas provided as argument if one is provided, or adds one to the specified container element. Why providing a canvas doesn’t work?
# this works
canvas = anvil.js.get_dom_node(self.column_panel_1)
renderer = THREE.WebGLRenderer()
renderer.shadowMap.enabled = True
renderer.shadowMap.type = THREE.PCFSoftShadowMap
renderer.setSize(canvas.clientWidth, 300)
canvas.appendChild(renderer.domElement)
canvas.children[0].style = 'display: block;' # see question # 5 below
# this doesn't
canvas = anvil.js.get_dom_node(self.canvas_1)
renderer = THREE.WebGLRenderer({'canvas': canvas)
- Is the workaround on the line with the comment about question # 5 a reliable one?
The renderer worker is supposed to detect when the canvas is resized and resize the renderer. The following code doesn’t work because the canvas (the one added when creating the renderer) has the width specified both as attribute and in the style attribute, something like this:
<canvas data-engine="three.js r138" width="1221" height="800" style="display: block; width=800px; height=300px;"></canvas>
# this renderer worker only works if width is removed from the style (see above)
def render(renderer, *a):
size = THREE.Vector3()
renderer.getSize(size)
old_width = size.x
canvas = anvil.js.get_dom_node(self.column_panel_1)
new_width = canvas.clientWidth
if old_width != new_width:
renderer.setSize(new_width, canvas.clientHeight, False)
aspect = new_width / canvas.clientHeight
camera.aspect = aspect
camera.updateProjectionMatrix()
renderer.render(scene, camera)
anvil.js.window.requestAnimationFrame(partial(render, renderer))