t view/hide shape
diff --git a/src/Display/WebGl/threejs_renderer.py b/src/Display/WebGl/threejs_renderer.py
index 4fd12a744..167905b82 100644
--- a/src/Display/WebGl/threejs_renderer.py
+++ b/src/Display/WebGl/threejs_renderer.py
@@ -1,4 +1,4 @@
-##Copyright 2011-2019 Thomas Paviot (tpaviot@gmail.com)
+##Copyright 2011-2024 Thomas Paviot (tpaviot@gmail.com)
##
##This file is part of pythonOCC.
##
@@ -15,72 +15,98 @@
##You should have received a copy of the GNU Lesser General Public License
##along with pythonOCC. If not, see
.
+import json
import os
+from string import Template
import sys
import tempfile
import uuid
-import json
+from typing import Any, Dict, Generator, List, Optional, Tuple
from OCC.Core.gp import gp_Vec
from OCC.Core.Tesselator import ShapeTesselator
-from OCC import VERSION as OCC_VERSION
+from OCC import VERSION
from OCC.Extend.TopologyUtils import is_edge, is_wire, discretize_edge, discretize_wire
from OCC.Display.WebGl.simple_server import start_server
-THREEJS_RELEASE = "r113"
-def spinning_cursor():
+def spinning_cursor() -> Generator[str, None, None]:
+ """
+ A spinning cursor generator.
+ """
while True:
- for cursor in '|/-\\':
- yield cursor
+ yield from "|/-\\"
+
+
+def color_to_hex(rgb_color: Tuple[float, float, float]) -> str:
+ """
+ Converts a color from RGB to a hex string.
+
+ Args:
+ rgb_color (tuple): A tuple of 3 floats (R, G, B) between 0 and 1.
-def color_to_hex(rgb_color):
- """ Takes a tuple with 3 floats between 0 and 1.
- Returns a hex. Useful to convert occ colors to web color code
+ Returns:
+ str: The color as a hex string.
"""
r, g, b = rgb_color
- if not (0 <= r <= 1. and 0 <= g <= 1. and 0 <= b <= 1.):
+ if not (0 <= r <= 1.0 and 0 <= g <= 1.0 and 0 <= b <= 1.0):
raise AssertionError("rgb values must be between 0.0 and 1.0")
- rh = int(r * 255.)
- gh = int(g * 255.)
- bh = int(b * 255.)
+ rh = int(r * 255.0)
+ gh = int(g * 255.0)
+ bh = int(b * 255.0)
return "0x%.02x%.02x%.02x" % (rh, gh, bh)
-def export_edgedata_to_json(edge_hash, point_set):
- """ Export a set of points to a LineSegment buffergeometry
+
+def export_edgedata_to_json(edge_hash: str, point_set: List[List[float]]) -> str:
+ """
+ Exports a set of points to a LineSegment buffergeometry.
+
+ Args:
+ edge_hash (str): The hash of the edge.
+ point_set (list): A list of points.
+
+ Returns:
+ str: The JSON string.
"""
# first build the array of point coordinates
# edges are built as follows:
# points_coordinates =[P0x, P0y, P0z, P1x, P1y, P1z, P2x, P2y, etc.]
points_coordinates = []
for point in point_set:
- for coord in point:
- points_coordinates.append(coord)
- # then build the dictionnary exported to json
- edges_data = {"metadata": {"version": 4.4,
- "type": "BufferGeometry",
- "generator": "pythonocc"},
- "uuid": edge_hash,
- "type": "BufferGeometry",
- "data": {"attributes": {"position": {"itemSize": 3,
- "type": "Float32Array",
- "array": points_coordinates}
- }
- }
- }
- return json.dumps(edges_data)
-
-
-HEADER = """
+ points_coordinates.extend(iter(point))
+ # then build the dictionary exported to json
+ edges_data = {
+ "metadata": {
+ "version": 4.4,
+ "type": "BufferGeometry",
+ "generator": "pythonocc",
+ },
+ "uuid": edge_hash,
+ "type": "BufferGeometry",
+ "data": {
+ "attributes": {
+ "position": {
+ "itemSize": 3,
+ "type": "Float32Array",
+ "array": points_coordinates,
+ }
+ }
+ },
+ }
+ return json.dumps(edges_data, indent=4)
+
+
+HEADER_TEMPLATE = Template(
+ """
-
pythonocc @VERSION@ webgl renderer
+
pythonocc $VERSION webgl renderer
"""
-BODY_PART0 = """
-
+)
+
+BODY_TEMPLATE = Template(
+ """
+
t view/hide shape
@@ -140,291 +167,326 @@ def export_edgedata_to_json(edge_hash, point_set):
g view/hide grid
a view/hide axis
-
-
-
-
-""" % (THREEJS_RELEASE, THREEJS_RELEASE, THREEJS_RELEASE, THREEJS_RELEASE)
-
-BODY_PART1 = """
-
- @VertexShaderDefinition@
- @FragmentShaderDefinition@
-
+
+
+
+"""
+)
+
+MAIN_JS_TEMPLATE = Template(
+ """
+import * as THREE from 'three';
+import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
+
+var camera, scene, renderer, object, container, shape_material;
+var controls;
+var directionalLight;
+var axisHelper, gridHelper;
+var light1;
+var mouse;
+var mouseX = 0;
+var mouseXOnMouseDown = 0;
+var mouseY = 0;
+var mouseYOnMouseDown = 0;
+var moveForward = false;
+var moveBackward = false;
+var moveLeft = false;
+var moveRight = false;
+var moveUp = false;
+var moveDown = false;
+var raycaster;
+var windowHalfX = window.innerWidth / 2;
+var windowHalfY = window.innerHeight / 2;
+var selected_target_color_r = 0;
+var selected_target_color_g = 0;
+var selected_target_color_b = 0;
+var selected_target = null;
+init();
+animate();
+
+function init() {
+ container = document.createElement( 'div' );
+ document.body.appendChild( container );
+
+ camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 200);
+ camera.position.z = 100;
+
+ raycaster = new THREE.Raycaster();
+ mouse = new THREE.Vector2();
+
+ scene = new THREE.Scene();
+ scene.background = new THREE.Color(0xf0f0f0);
+ const ambientLight = new THREE.AmbientLight(0x404040, 1.5);
+ scene.add(ambientLight);
+
+ directionalLight = new THREE.DirectionalLight(0xffffff, 1);
+ directionalLight.position.set(1, 1, 1).normalize();
+ scene.add(directionalLight);
+
+ light1 = new THREE.PointLight(0xffffff, 0.8);
+ light1.position.set(50, 50, 50);
+ scene.add(light1);
+
+ $Uniforms
+
+ $ShaderMaterialDefinition
+
+ $ShapeList
+
+ $EdgeList
+
+ renderer = new THREE.WebGLRenderer({antialias:true, alpha: true});
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setPixelRatio( window.devicePixelRatio );
+ container.appendChild(renderer.domElement);
+
+ // shadow rendering
+ renderer.shadowMap.enabled = true;
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+
+ // tone mapping
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
+ renderer.toneMappingExposure = 1.0;
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
+
+ controls = new TrackballControls(camera, renderer.domElement);
+
+ document.addEventListener('keypress', onDocumentKeyPress, false);
+ document.addEventListener('click', onDocumentMouseClick, false);
+ window.addEventListener('resize', onWindowResize, false);
+}
+
+function animate() {
+ requestAnimationFrame(animate);
+ controls.update();
+ render();
+}
+
+function update_lights() {
+ if (directionalLight != undefined) {
+ directionalLight.position.copy(camera.position);
+ }
+}
+
+function onWindowResize() {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onDocumentKeyPress(event) {
+ event.preventDefault();
+ if (event.key=="t") { // t key
+ if (selected_target) {
+ selected_target.material.visible = !selected_target.material.visible;
}
- function onDocumentMouseClick(event) {
- event.preventDefault();
- mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
- mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
- // restore previous selected target color
- if (selected_target) {
- selected_target.material.color.setRGB(selected_target_color_r,
- selected_target_color_g,
- selected_target_color_b);
- }
- // perform selection
- raycaster.setFromCamera(mouse, camera);
- var intersects = raycaster.intersectObjects(scene.children);
- if (intersects.length > 0) {
- var target = intersects[0].object;
- selected_target_color_r = target.material.color.r;
- selected_target_color_g = target.material.color.g;
- selected_target_color_b = target.material.color.b;
- target.material.color.setRGB(1., 0.65, 0.);
- console.log(target);
- selected_target = target;
- }
+ }
+ else if (event.key=="g") { // g key, toggle grid visibility
+ gridHelper.visible = !gridHelper.visible;
+ }
+ else if (event.key=="a") { // g key, toggle axisHelper visibility
+ axisHelper.visible = !axisHelper.visible;
+ }
+ else if (event.key=="w") { // g key, toggle axisHelper visibility
+ if (selected_target) {
+ selected_target.material.wireframe = !selected_target.material.wireframe;
}
- function fit_to_scene() {
- // compute bounding sphere of whole scene
- var center = new THREE.Vector3(0,0,0);
- var radiuses = new Array();
- var positions = new Array();
- // compute center of all objects
- scene.traverse(function(child) {
- if (child instanceof THREE.Mesh) {
- child.geometry.computeBoundingBox();
- var box = child.geometry.boundingBox;
- var curCenter = new THREE.Vector3().copy(box.min).add(box.max).multiplyScalar(0.5);
- var radius = new THREE.Vector3().copy(box.max).distanceTo(box.min)/2.;
- center.add(curCenter);
- positions.push(curCenter);
- radiuses.push(radius);
- }
- });
- if (radiuses.length > 0) {
- center.divideScalar(radiuses.length*0.7);
- }
- var maxRad = 1.;
- // compute bounding radius
- for (var ichild = 0; ichild < radiuses.length; ++ichild) {
- var distToCenter = positions[ichild].distanceTo(center);
- var totalDist = distToCenter + radiuses[ichild];
- if (totalDist > maxRad) {
- maxRad = totalDist;
- }
- }
- maxRad = maxRad * 0.7; // otherwise the scene seems to be too far away
- camera.lookAt(center);
- var direction = new THREE.Vector3().copy(camera.position).sub(controls.target);
- var len = direction.length();
- direction.normalize();
-
- // compute new distance of camera to middle of scene to fit the object to screen
- var lnew = maxRad / Math.sin(camera.fov/180. * Math.PI / 2.);
- direction.multiplyScalar(lnew);
-
- var pnew = new THREE.Vector3().copy(center).add(direction);
- // change near far values to avoid culling of objects
- camera.position.set(pnew.x, pnew.y, pnew.z);
- camera.far = lnew*50;
- camera.near = lnew*50*0.001;
- camera.updateProjectionMatrix();
- controls.target = center;
- controls.update();
- // adds and adjust a grid helper if needed
- gridHelper = new THREE.GridHelper(maxRad*4, 10)
- scene.add(gridHelper);
- // axisHelper
- axisHelper = new THREE.AxesHelper(maxRad);
- scene.add(axisHelper);
+ }
+}
+
+function onDocumentMouseClick(event) {
+ event.preventDefault();
+ mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
+ mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
+ // restore previous selected target color
+ if (selected_target) {
+ selected_target.material.color.setRGB(selected_target_color_r,
+ selected_target_color_g,
+ selected_target_color_b);
+ }
+ // perform selection
+ raycaster.setFromCamera(mouse, camera);
+ var intersects = raycaster.intersectObjects(scene.children);
+ if (intersects.length > 0) {
+ var target = intersects[0].object;
+ selected_target_color_r = target.material.color.r;
+ selected_target_color_g = target.material.color.g;
+ selected_target_color_b = target.material.color.b;
+ target.material.color.setRGB(1., 0.65, 0.);
+ console.log(target);
+ selected_target = target;
+ }
+}
+
+function fit_to_scene() {
+ // compute bounding sphere of whole scene
+ var center = new THREE.Vector3(0,0,0);
+ var radiuses = new Array();
+ var positions = new Array();
+
+ // compute center of all objects
+ scene.traverse(function(child) {
+ if (child instanceof THREE.Mesh) {
+ child.geometry.computeBoundingBox();
+ var box = child.geometry.boundingBox;
+ var curCenter = new THREE.Vector3().copy(box.min).add(box.max).multiplyScalar(0.5);
+ var radius = new THREE.Vector3().copy(box.max).distanceTo(box.min)/2.;
+ center.add(curCenter);
+ positions.push(curCenter);
+ radiuses.push(radius);
}
- function render() {
- //@IncrementTime@ TODO UNCOMMENT
- update_lights();
- renderer.render(scene, camera);
+ });
+
+ if (radiuses.length > 0) {
+ center.divideScalar(radiuses.length*0.7);
+ }
+
+ var maxRad = 1.;
+ // compute bounding radius
+ for (var ichild = 0; ichild < radiuses.length; ++ichild) {
+ var distToCenter = positions[ichild].distanceTo(center);
+ var totalDist = distToCenter + radiuses[ichild];
+ if (totalDist > maxRad) {
+ maxRad = totalDist;
}
-
-
+ }
+
+ maxRad = maxRad * 0.7; // otherwise the scene seems to be too far away
+ camera.lookAt(center);
+ var direction = new THREE.Vector3().copy(camera.position).sub(controls.target);
+ var len = direction.length();
+ direction.normalize();
+
+ // compute new distance of camera to middle of scene to fit the object to screen
+ var lnew = maxRad / Math.sin(camera.fov/180. * Math.PI / 2.);
+ direction.multiplyScalar(lnew);
+
+ var pnew = new THREE.Vector3().copy(center).add(direction);
+ // change near far values to avoid culling of objects
+ camera.position.set(pnew.x, pnew.y, pnew.z);
+ camera.far = lnew * 50;
+ camera.near = lnew * 50 * 0.001;
+ camera.updateProjectionMatrix();
+ controls.target = center;
+ controls.update();
+
+ // adds and adjust a grid helper if needed
+ gridHelper = new THREE.GridHelper(maxRad*4, 10)
+ scene.add(gridHelper);
+
+ // axisHelper
+ axisHelper = new THREE.AxesHelper(maxRad);
+ scene.add(axisHelper);
+}
+
+function render() {
+ //@IncrementTime@ TODO UNCOMMENT
+ update_lights();
+ renderer.render(scene, camera);
+}
"""
+)
class HTMLHeader:
- def __init__(self, bg_gradient_color1="#ced7de", bg_gradient_color2="#808080"):
+ """
+ A class to generate the HTML header.
+ """
+
+ def __init__(
+ self, bg_gradient_color1: str = "#ced7de", bg_gradient_color2: str = "#808080"
+ ) -> None:
+ """
+ Initializes the HTMLHeader.
+
+ Args:
+ bg_gradient_color1 (str, optional): The first color of the background gradient.
+ bg_gradient_color2 (str, optional): The second color of the background gradient.
+ """
self._bg_gradient_color1 = bg_gradient_color1
self._bg_gradient_color2 = bg_gradient_color2
- def get_str(self):
- header_str = HEADER.replace('@bg_gradient_color1@', '%s' % self._bg_gradient_color1)
- header_str = header_str.replace('@bg_gradient_color2@', '%s' % self._bg_gradient_color2)
- header_str = header_str.replace('@VERSION@', OCC_VERSION)
- return header_str
-
-
-class HTMLBody_Part1:
- def __init__(self, vertex_shader=None, fragment_shader=None, uniforms=None):
- self._vertex_shader = vertex_shader
- self._fragment_shader = fragment_shader
- self._uniforms = uniforms
-
- def get_str(self):
- global BODY_PART2
- # get the location where pythonocc is running from
- body_str = BODY_PART1.replace('@VERSION@', OCC_VERSION)
- if (self._fragment_shader is not None) and (self._fragment_shader is not None):
- vertex_shader_string_definition = '' % self._vertex_shader
- fragment_shader_string_definition = '' % self._fragment_shader
- shader_material_definition = """
- var vertexShader = document.getElementById('vertexShader').textContent;
- var fragmentShader = document.getElementById('fragmentShader').textContent;
- var shader_material = new THREE.ShaderMaterial({uniforms: uniforms,
- vertexShader: vertexShader,
- fragmentShader: fragmentShader});
- """
- if self._uniforms is None:
- body_str = body_str.replace('@Uniforms@', 'uniforms ={};\n')
- BODY_PART2 = BODY_PART2.replace('@IncrementTime@', '')
- else:
- body_str = body_str.replace('@Uniforms@', self._uniforms)
- if 'time' in self._uniforms:
- BODY_PART2 = BODY_PART2.replace('@IncrementTime@', 'uniforms.time.value += 0.05;')
- else:
- BODY_PART2 = BODY_PART2.replace('@IncrementTime@', '')
- body_str = body_str.replace('@VertexShaderDefinition@', vertex_shader_string_definition)
- body_str = body_str.replace('@FragmentShaderDefinition@', fragment_shader_string_definition)
- body_str = body_str.replace('@ShaderMaterialDefinition@', shader_material_definition)
- body_str = body_str.replace('@ShapeMaterial@', 'shader_material')
- else:
- body_str = body_str.replace('@Uniforms@', '')
- body_str = body_str.replace('@VertexShaderDefinition@', '')
- body_str = body_str.replace('@FragmentShaderDefinition@', '')
- body_str = body_str.replace('@ShaderMaterialDefinition@', '')
- body_str = body_str.replace('@ShapeMaterial@', 'phong_material')
- body_str = body_str.replace('@IncrementTime@', '')
- return body_str
+ def get_str(self) -> str:
+ """
+ Returns the HTML header as a string.
+ """
+ return HEADER_TEMPLATE.substitute(
+ {
+ "bg_gradient_color1": f"{self._bg_gradient_color1}",
+ "bg_gradient_color2": f"{self._bg_gradient_color2}",
+ "VERSION": VERSION,
+ }
+ )
class ThreejsRenderer:
- def __init__(self, path=None):
- if not path:
- self._path = tempfile.mkdtemp()
- else:
- self._path = path
+ """
+ A renderer that uses three.js to display shapes in a web browser.
+ """
+
+ def __init__(self, path: Optional[str] = None) -> None:
+ """
+ Initializes the ThreejsRenderer.
+
+ Args:
+ path (str, optional): The path to the directory where the HTML
+ and JavaScript files will be created. If not specified, a
+ temporary directory will be created.
+ """
+ self._path = tempfile.mkdtemp() if not path else path
self._html_filename = os.path.join(self._path, "index.html")
- self._3js_shapes = {}
- self._3js_edges = {}
+ self._main_js_filename = os.path.join(self._path, "main.js")
+ self._3js_shapes: Dict[str, Any] = {}
+ self._3js_edges: Dict[str, Any] = {}
self.spinning_cursor = spinning_cursor()
- print("## threejs %s webgl renderer" % THREEJS_RELEASE)
-
- def DisplayShape(self,
- shape,
- export_edges=False,
- color=(0.65, 0.65, 0.7),
- specular_color=(0.2, 0.2, 0.2),
- shininess=0.9,
- transparency=0.,
- line_color=(0, 0., 0.),
- line_width=1.,
- mesh_quality=1.):
+ print("## threejs renderer")
+
+ def DisplayShape(
+ self,
+ shape: Any,
+ export_edges: bool = False,
+ color: Tuple[float, float, float] = (0.65, 0.65, 0.7),
+ specular_color: Tuple[float, float, float] = (0.2, 0.2, 0.2),
+ shininess: float = 0.9,
+ transparency: float = 0.0,
+ line_color: Tuple[float, float, float] = (0, 0.0, 0.0),
+ line_width: float = 1.0,
+ mesh_quality: float = 1.0,
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
+ """
+ Displays a shape.
+
+ Args:
+ shape: The shape to display.
+ export_edges (bool, optional): Whether to export the edges of the shape.
+ color (tuple, optional): The color of the shape.
+ specular_color (tuple, optional): The specular color of the shape.
+ shininess (float, optional): The shininess of the shape.
+ transparency (float, optional): The transparency of the shape.
+ line_color (tuple, optional): The color of the lines.
+ line_width (float, optional): The width of the lines.
+ mesh_quality (float, optional): The quality of the mesh.
+
+ Returns:
+ A tuple containing the shapes and edges.
+ """
# if the shape is an edge or a wire, use the related functions
if is_edge(shape):
print("discretize an edge")
pnts = discretize_edge(shape)
- edge_hash = "edg%s" % uuid.uuid4().hex
+ edge_hash = f"edg{uuid.uuid4().hex}"
str_to_write = export_edgedata_to_json(edge_hash, pnts)
- edge_full_path = os.path.join(self._path, edge_hash + '.json')
+ edge_full_path = os.path.join(self._path, f"{edge_hash}.json")
with open(edge_full_path, "w") as edge_file:
edge_file.write(str_to_write)
# store this edge hash
@@ -433,34 +495,42 @@ def DisplayShape(self,
elif is_wire(shape):
print("discretize a wire")
pnts = discretize_wire(shape)
- wire_hash = "wir%s" % uuid.uuid4().hex
+ wire_hash = f"wir{uuid.uuid4().hex}"
str_to_write = export_edgedata_to_json(wire_hash, pnts)
- wire_full_path = os.path.join(self._path, wire_hash + '.json')
+ wire_full_path = os.path.join(self._path, f"{wire_hash}.json")
with open(wire_full_path, "w") as wire_file:
wire_file.write(str_to_write)
# store this edge hash
self._3js_edges[wire_hash] = [color, line_width]
return self._3js_shapes, self._3js_edges
shape_uuid = uuid.uuid4().hex
- shape_hash = "shp%s" % shape_uuid
- # tesselate
+ shape_hash = f"shp{shape_uuid}"
+ # tesselatte
tess = ShapeTesselator(shape)
- tess.Compute(compute_edges=export_edges,
- mesh_quality=mesh_quality,
- parallel=True)
+ tess.Compute(
+ compute_edges=export_edges, mesh_quality=mesh_quality, parallel=True
+ )
# update spinning cursor
- sys.stdout.write("\r%s mesh shape %s, %i triangles " % (next(self.spinning_cursor),
- shape_hash,
- tess.ObjGetTriangleCount()))
+ sys.stdout.write(
+ "\r%s mesh shape %s, %i triangles "
+ % (next(self.spinning_cursor), shape_hash, tess.ObjGetTriangleCount())
+ )
sys.stdout.flush()
# export to 3JS
- shape_full_path = os.path.join(self._path, shape_hash + '.json')
+ shape_full_path = os.path.join(self._path, f"{shape_hash}.json")
# add this shape to the shape dict, sotres everything related to it
- self._3js_shapes[shape_hash] = [export_edges, color, specular_color, shininess, transparency, line_color, line_width]
+ self._3js_shapes[shape_hash] = [
+ export_edges,
+ color,
+ specular_color,
+ shininess,
+ transparency,
+ line_color,
+ line_width,
+ ]
# generate the mesh
- #tess.ExportShapeToThreejs(shape_hash, shape_full_path)
# and also to JSON
- with open(shape_full_path, 'w') as json_file:
+ with open(shape_full_path, "w") as json_file:
json_file.write(tess.ExportShapeToThreejsJSONString(shape_uuid))
# draw edges if necessary
if export_edges:
@@ -469,108 +539,155 @@ def DisplayShape(self,
nbr_edges = tess.ObjGetEdgeCount()
for i_edge in range(nbr_edges):
# after that, the file can be appended
- str_to_write = ''
- edge_point_set = []
+ str_to_write = ""
nbr_vertices = tess.ObjEdgeGetVertexCount(i_edge)
- for i_vert in range(nbr_vertices):
- edge_point_set.append(tess.GetEdgeVertex(i_edge, i_vert))
+ edge_point_set = [
+ tess.GetEdgeVertex(i_edge, i_vert) for i_vert in range(nbr_vertices)
+ ]
# write to file
- edge_hash = "edg%s" % uuid.uuid4().hex
+ edge_hash = f"edg{uuid.uuid4().hex}"
str_to_write += export_edgedata_to_json(edge_hash, edge_point_set)
# create the file
- edge_full_path = os.path.join(self._path, edge_hash + '.json')
+ edge_full_path = os.path.join(self._path, f"{edge_hash}.json")
with open(edge_full_path, "w") as edge_file:
edge_file.write(str_to_write)
# store this edge hash, with black color
self._3js_edges[edge_hash] = [(0, 0, 0), line_width]
return self._3js_shapes, self._3js_edges
-
- def generate_html_file(self):
- """ Generate the HTML file to be rendered by the web browser
+ def generate_html_file(self) -> None:
+ """
+ Generates the HTML file to be rendered by the web browser.
"""
- global BODY_PART0
+ global BODY_TEMPLATE
# loop over shapes to generate html shapes stuff
# the following line is a list that will help generating the string
# using "".join()
- shape_string_list = []
- shape_string_list.append("loader = new THREE.BufferGeometryLoader();\n")
- shape_idx = 0
- for shape_hash in self._3js_shapes:
+ shape_string_list = ["var loader = new THREE.BufferGeometryLoader();\n"]
+ for shape_idx, shape_hash in enumerate(self._3js_shapes):
# get properties for this shape
- export_edges, color, specular_color, shininess, transparency, line_color, line_width = self._3js_shapes[shape_hash]
- # creates a material for the shape
- shape_string_list.append('\t\t\t%s_phong_material = new THREE.MeshPhongMaterial({' % shape_hash)
- shape_string_list.append('color:%s,' % color_to_hex(color))
- shape_string_list.append('specular:%s,' % color_to_hex(specular_color))
- shape_string_list.append('shininess:%g,' % shininess)
- # force double side rendering, see issue #645
- shape_string_list.append('side: THREE.DoubleSide,')
- if transparency > 0.:
- shape_string_list.append('transparent: true, premultipliedAlpha: true, opacity:%g,' % transparency)
- #var line_material = new THREE.LineBasicMaterial({color: 0x000000, linewidth: 2});
- shape_string_list.append('});\n')
- # load json geometry files
- shape_string_list.append("\t\t\tloader.load('%s.json', function(geometry) {\n" % shape_hash)
- shape_string_list.append("\t\t\t\tmesh = new THREE.Mesh(geometry, %s_phong_material);\n" % shape_hash)
- # enable shadows for object
- shape_string_list.append("\t\t\t\tmesh.castShadow = true;\n")
- shape_string_list.append("\t\t\t\tmesh.receiveShadow = true;\n")
- # add mesh to scene
- shape_string_list.append("\t\t\t\tscene.add(mesh);\n")
+ (
+ export_edges,
+ color,
+ specular_color,
+ shininess,
+ transparency,
+ line_color,
+ line_width,
+ ) = self._3js_shapes[shape_hash]
+ shape_string_list.extend(
+ (
+ "\t\t\tvar %s_phong_material = new THREE.MeshPhongMaterial({"
+ % shape_hash,
+ f"color:{color_to_hex(color)},",
+ f"specular:{color_to_hex(specular_color)},",
+ "shininess:%g," % shininess,
+ "side: THREE.DoubleSide,",
+ "flatShading:false,",
+ )
+ )
+ if transparency > 0.0:
+ shape_string_list.append(
+ "transparent: true, premultipliedAlpha: true, opacity:%g,"
+ % transparency
+ )
+ shape_string_list.extend(
+ (
+ "});\n",
+ "\t\t\tloader.load('%s.json', function(geometry) {\n" % shape_hash,
+ "\t\t\t\tvar mesh = new THREE.Mesh(geometry, %s_phong_material);\n"
+ % shape_hash,
+ "\t\t\t\tmesh.castShadow = true;\n",
+ "\t\t\t\tmesh.receiveShadow = true;\n",
+ "\t\t\t\tscene.add(mesh);\n",
+ )
+ )
# last shape, we request for a fit_to_scene
if shape_idx == len(self._3js_shapes) - 1:
shape_string_list.append("\tfit_to_scene();});\n")
else:
shape_string_list.append("\t\t\t});\n\n")
- shape_idx += 1
# Process edges
edge_string_list = []
for edge_hash in self._3js_edges:
color, line_width = self._3js_edges[edge_hash]
- edge_string_list.append("\tloader.load('%s.json', function(geometry) {\n" % edge_hash)
- edge_string_list.append("\tline_material = new THREE.LineBasicMaterial({color: %s, linewidth: %s});\n" % ((color_to_hex(color), line_width)))
- edge_string_list.append("\tline = new THREE.Line(geometry, line_material);\n")
- # add mesh to scene
- edge_string_list.append("\tscene.add(line);\n")
- edge_string_list.append("\t});\n")
- # write the string for the shape
+ edge_string_list.extend(
+ (
+ "\tloader.load('%s.json', function(geometry) {\n" % edge_hash,
+ "\tvar line_material = new THREE.LineBasicMaterial({color: %s, linewidth: %s});\n"
+ % ((color_to_hex(color), line_width)),
+ "\tvar line = new THREE.Line(geometry, line_material);\n",
+ "\tscene.add(line);\n",
+ "\t});\n",
+ )
+ )
+ # write the main.js file
+ with open(self._main_js_filename, "w") as fp:
+ main_js = MAIN_JS_TEMPLATE.substitute(
+ {
+ "ShapeList": "".join(shape_string_list),
+ "EdgeList": "".join(edge_string_list),
+ "Uniforms": "",
+ "ShaderMaterialDefinition": "",
+ }
+ )
+ fp.write(main_js)
+
+ # write the index.html file
with open(self._html_filename, "w") as fp:
fp.write("\n")
fp.write("")
# header
fp.write(HTMLHeader().get_str())
# body
- BODY_PART0 = BODY_PART0.replace('@VERSION@', OCC_VERSION)
- fp.write(BODY_PART0)
- fp.write(HTMLBody_Part1().get_str())
- fp.write("".join(shape_string_list))
- fp.write("".join(edge_string_list))
- # then write header part 2
- fp.write(BODY_PART2)
+ body = BODY_TEMPLATE.substitute(
+ {
+ "VERSION": VERSION,
+ "VertexShaderDefinition": "",
+ "FragmentShaderDefinition": "",
+ }
+ ) # = BODY_TEMPLATE_PART0.replace("@VERSION@", VERSION)
+ fp.write(body)
fp.write("\n")
- def render(self, addr="localhost", server_port=8080, open_webbrowser=False):
- ''' render the scene into the browser.
- '''
+ def render(
+ self,
+ addr: str = "localhost",
+ server_port: int = 8080,
+ open_webbrowser: bool = False,
+ ) -> None:
+ """
+ Renders the scene in the browser.
+
+ Args:
+ addr (str, optional): The address to bind the server to.
+ server_port (int, optional): The port to use for the server.
+ open_webbrowser (bool, optional): Whether to open a web browser.
+ """
# generate HTML file
self.generate_html_file()
# then create a simple web server
start_server(addr, server_port, self._path, open_webbrowser)
+
if __name__ == "__main__":
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeTorus
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform
- from OCC.Core.gp import gp_Trsf
+ from OCC.Core.gp import gp_Trsf, gp_Vec
+ from OCC.Core.TopoDS import TopoDS_Shape
import time
- def translate_shp(shp, vec, copy=False):
+
+ def translate_shp(
+ shp: TopoDS_Shape, vec: gp_Vec, copy: bool = False
+ ) -> TopoDS_Shape:
trns = gp_Trsf()
trns.SetTranslation(vec)
brep_trns = BRepBuilderAPI_Transform(shp, trns, copy)
brep_trns.Build()
return brep_trns.Shape()
- box = BRepPrimAPI_MakeBox(100., 200., 300.).Shape()
- torus = BRepPrimAPI_MakeTorus(300., 105).Shape()
+
+ box = BRepPrimAPI_MakeBox(100.0, 200.0, 300.0).Shape()
+ torus = BRepPrimAPI_MakeTorus(300.0, 105).Shape()
t_torus = translate_shp(torus, gp_Vec(700, 0, 0))
my_ren = ThreejsRenderer()
init_time = time.time()
diff --git a/src/Display/WebGl/threejs_renderer.pyi b/src/Display/WebGl/threejs_renderer.pyi
new file mode 100644
index 000000000..16b2d4ad4
--- /dev/null
+++ b/src/Display/WebGl/threejs_renderer.pyi
@@ -0,0 +1,33 @@
+from typing import Any, Dict, Generator, List, Optional, Tuple
+
+def spinning_cursor() -> Generator[str, None, None]: ...
+def color_to_hex(rgb_color: Tuple[float, float, float]) -> str: ...
+def export_edgedata_to_json(edge_hash: str, point_set: List[List[float]]) -> str: ...
+
+class HTMLHeader:
+ def __init__(
+ self, bg_gradient_color1: str = "#ced7de", bg_gradient_color2: str = "#808080"
+ ) -> None: ...
+ def get_str(self) -> str: ...
+
+class ThreejsRenderer:
+ def __init__(self, path: Optional[str] = None) -> None: ...
+ def DisplayShape(
+ self,
+ shape: Any,
+ export_edges: bool = False,
+ color: Tuple[float, float, float] = ...,
+ specular_color: Tuple[float, float, float] = ...,
+ shininess: float = 0.9,
+ transparency: float = 0.0,
+ line_color: Tuple[float, float, float] = ...,
+ line_width: float = 1.0,
+ mesh_quality: float = 1.0,
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]: ...
+ def generate_html_file(self) -> None: ...
+ def render(
+ self,
+ addr: str = "localhost",
+ server_port: int = 8080,
+ open_webbrowser: bool = False,
+ ) -> None: ...
diff --git a/src/Display/WebGl/x3dom_renderer.py b/src/Display/WebGl/x3dom_renderer.py
index 2bb74386f..09d0e9199 100644
--- a/src/Display/WebGl/x3dom_renderer.py
+++ b/src/Display/WebGl/x3dom_renderer.py
@@ -17,36 +17,45 @@
import os
import sys
+from string import Template
import tempfile
import uuid
+from typing import Any, Dict, Generator, List, Optional, Tuple
from xml.etree import ElementTree
from OCC.Core.Tesselator import ShapeTesselator
-from OCC import VERSION as OCC_VERSION
+from OCC import VERSION
from OCC.Extend.TopologyUtils import is_edge, is_wire, discretize_edge, discretize_wire
from OCC.Display.WebGl.simple_server import start_server
-def spinning_cursor():
+
+def spinning_cursor() -> Generator[str, None, None]:
+ """
+ A spinning cursor generator.
+ """
while True:
- for cursor in '|/-\\':
- yield cursor
+ yield from "|/-\\"
+
-X3DFILE_HEADER = """
+X3DFILE_HEADER_TEMPLATE = Template(
+ """
-
-
-
-
+
+
+
+
-""" % (OCC_VERSION, OCC_VERSION, OCC_VERSION)
+"""
+)
-HEADER = """
+HEADER_TEMPLATE = Template(
+ """
- pythonOCC @VERSION@ x3dom renderer
+ pythonOCC $VERSION x3dom renderer
@@ -54,7 +63,7 @@ def spinning_cursor():
"""
+)
-BODY = """
+BODY_TEMPLATE = Template(
+ """
- @X3DSCENE@
+ $X3DSCENE
t view/hide shape
@@ -139,7 +148,7 @@ def spinning_cursor():
current_mat = mat;
console.log(mat);
selected_target_color = mat.diffuseColor;
- mat.diffuseColor = "1, 0.65, 0";
+ mat.diffuseColor = "1. 0.65 0.";
//console.log(the_shape.getElementsByTagName("Appearance"));//.getAttribute('diffuseColor'));
}
function onDocumentKeyPress(event) {
@@ -148,9 +157,11 @@ def spinning_cursor():
if (current_selected_shape) {
if (current_selected_shape.render == "true") {
current_selected_shape.render = "false";
+ console.log("hide ", current_selected_shape);
}
else {
current_selected_shape.render = "true";
+ console.log("show ", current_selected_shape)
}
}
}
@@ -161,112 +172,189 @@ def spinning_cursor():
"""
+)
+
+
+def export_edge_to_indexed_lineset(edge_point_set: List[List[float]]) -> str:
+ """
+ Exports an edge to an IndexedLineSet string.
+ Args:
+ edge_point_set (list): A list of points.
-def export_edge_to_indexed_lineset(edge_point_set):
- str_x3d_to_return = "\t
" % len(edge_point_set)
+ Returns:
+ str: The IndexedLineSet string.
+ """
+ str_x3d_to_return = f"\t"
str_x3d_to_return += "\n"
return str_x3d_to_return
-def indexed_lineset_to_x3d_string(str_linesets, header=True, footer=True, ils_id=0):
- """ takes an str_lineset, coming for instance from export_curve_to_ils,
- and export to an X3D string"""
- if header:
- x3dfile_str = X3DFILE_HEADER
- else:
- x3dfile_str = ""
+def indexed_lineset_to_x3d_string(
+ str_linesets: List[str], header: bool = True, footer: bool = True, ils_id: int = 0
+) -> str:
+ """
+ Converts an IndexedLineSet string to an X3D string.
+
+ Args:
+ str_linesets (list): A list of IndexedLineSet strings.
+ header (bool, optional): Whether to include the X3D header.
+ footer (bool, optional): Whether to include the X3D footer.
+ ils_id (int, optional): The ID of the IndexedLineSet.
+
+ Returns:
+ str: The X3D string.
+ """
+ x3dfile_str = (
+ X3DFILE_HEADER_TEMPLATE.substitute({"VERSION": f"{VERSION}"}) if header else ""
+ )
x3dfile_str += "\n"
x3dfile_str += "\t\n"
- ils_id = 0
- for str_lineset in str_linesets:
- x3dfile_str += "\t\t\n" % ils_id
+ for ils_id, str_lineset in enumerate(str_linesets):
+ x3dfile_str += f"\t\t\n"
# empty appearance, but the x3d validator complains if nothing set
- x3dfile_str += "\t\t\t\n\t\t"
+ x3dfile_str += (
+ "\t\t\t\n\t\t"
+ )
x3dfile_str += str_lineset
x3dfile_str += "\t\t\n"
- ils_id += 1
-
x3dfile_str += "\t\n"
x3dfile_str += "\n"
if footer:
- x3dfile_str += '\n\n'
+ x3dfile_str += "\n\n"
return x3dfile_str
class HTMLHeader:
- def __init__(self, bg_gradient_color1="#ced7de", bg_gradient_color2="#808080"):
+ """
+ A class to generate the HTML header.
+ """
+
+ def __init__(
+ self, bg_gradient_color1: str = "#ced7de", bg_gradient_color2: str = "#808080"
+ ) -> None:
+ """
+ Initializes the HTMLHeader.
+
+ Args:
+ bg_gradient_color1 (str, optional): The first color of the background gradient.
+ bg_gradient_color2 (str, optional): The second color of the background gradient.
+ """
self._bg_gradient_color1 = bg_gradient_color1
self._bg_gradient_color2 = bg_gradient_color2
-
- def get_str(self):
- header_str = HEADER.replace('@bg_gradient_color1@', '%s' % self._bg_gradient_color1)
- header_str = header_str.replace('@bg_gradient_color2@', '%s' % self._bg_gradient_color2)
- header_str = header_str.replace('@VERSION@', OCC_VERSION)
- return header_str
+ def get_str(self) -> str:
+ """
+ Returns the HTML header as a string.
+ """
+ return HEADER_TEMPLATE.substitute(
+ {
+ "bg_gradient_color1": f"{self._bg_gradient_color1}",
+ "bg_gradient_color2": f"{self._bg_gradient_color2}",
+ "VERSION": f"{VERSION}",
+ }
+ )
class HTMLBody:
- def __init__(self, x3d_shapes, axes_plane, axes_plane_zoom_factor=1.):
- """ x3d_shapes is a list that contains uid for each shape
+ """
+ A class to generate the HTML body.
+ """
+
+ def __init__(
+ self,
+ x3d_shapes: List[str],
+ axes_plane: bool,
+ axes_plane_zoom_factor: float = 1.0,
+ ) -> None:
+ """
+ Initializes the HTMLBody.
+
+ Args:
+ x3d_shapes (list): A list of shape UIDs.
+ axes_plane (bool): Whether to display the axes plane.
+ axes_plane_zoom_factor (float, optional): The zoom factor for the axes plane.
"""
self._x3d_shapes = x3d_shapes
self.spinning_cursor = spinning_cursor()
self._display_axes_plane = axes_plane
self._axis_plane_zoom_factor = axes_plane_zoom_factor
- def get_str(self):
+ def get_str(self) -> str:
+ """
+ Returns the HTML body as a string.
+ """
# get the location where pythonocc is running from
- body_str = BODY.replace('@VERSION@', OCC_VERSION)
- x3dcontent = '\n\t\n\t\t\n'
+ x3dcontent = "\n\t\n\t\t\n"
nb_shape = len(self._x3d_shapes)
- cur_shp = 1
if self._display_axes_plane:
- x3dcontent += """
-
-
+ x3dcontent += f"""
+
+
- """ % (self._axis_plane_zoom_factor, self._axis_plane_zoom_factor, self._axis_plane_zoom_factor)
- # global rotateso that z is aligne properly
- x3dcontent += ''
- for shp_uid in self._x3d_shapes:
- sys.stdout.write("\r%s meshing shapes... %i%%" % (next(self.spinning_cursor),
- round(cur_shp / nb_shape * 100)))
+ """
+ # global rotate so that z is properly aligned
+ x3dcontent += '\n'
+ for cur_shp, shp_uid in enumerate(self._x3d_shapes, start=1):
+ sys.stdout.write(
+ "\r%s meshing shapes... %i%%"
+ % (next(self.spinning_cursor), round(cur_shp / nb_shape * 100))
+ )
sys.stdout.flush()
+ # only the last downloaded shape raises a fitCamera event
+ x3dcontent += "\t\t\t\n'
+ x3dcontent += "\t\t\t\n\t\t\n\t\n"
- x3dcontent += '\t\t\t\n' % shp_uid
- cur_shp += 1
- x3dcontent += ''
- x3dcontent += "\t\t\n\t\n"
- body_str = body_str.replace('@X3DSCENE@', x3dcontent)
- return body_str
+ return BODY_TEMPLATE.substitute(
+ {"VERSION": f"{VERSION}", "X3DSCENE": f"{x3dcontent}"}
+ )
class X3DExporter:
- """ A class for exporting a TopoDS_Shape to an x3d file """
- def __init__(self,
- shape, # the TopoDS shape to mesh
- vertex_shader, # the vertex_shader, passed as a string
- fragment_shader, # the fragment shader, passed as a string
- export_edges, # if yes, edges are exported to IndexedLineSet (might be SLOWW)
- color, # the default shape color
- specular_color, # shape specular color (white by default)
- shininess, # shape shininess
- transparency, # shape transparency
- line_color, # edge color
- line_width, # edge liewidth,
- mesh_quality # mesh quality default is 1., good is <1, bad is >1
- ):
+ """A class for exporting a TopoDS_Shape to an x3d file"""
+
+ def __init__(
+ self,
+ shape: Any,
+ vertex_shader: Optional[str],
+ fragment_shader: Optional[str],
+ export_edges: bool,
+ color: Tuple[float, float, float],
+ specular_color: Tuple[float, float, float],
+ shininess: float,
+ transparency: float,
+ line_color: Tuple[float, float, float],
+ line_width: float,
+ mesh_quality: float,
+ ) -> None:
+ """
+ Initializes the X3DExporter.
+
+ Args:
+ shape: The shape to export.
+ vertex_shader: The vertex shader to use.
+ fragment_shader: The fragment shader to use.
+ export_edges: Whether to export edges.
+ color: The color of the shape.
+ specular_color: The specular color of the shape.
+ shininess: The shininess of the shape.
+ transparency: The transparency of the shape.
+ line_color: The color of the lines.
+ line_width: The width of the lines.
+ mesh_quality: The quality of the mesh.
+ """
self._shape = shape
self._vs = vertex_shader
self._fs = fragment_shader
@@ -279,54 +367,72 @@ def __init__(self,
# the list of indexed face sets that compose the shape
# if ever the map_faces_to_mesh option is enabled, this list
# maybe composed of dozains of TriangleSet
- self._triangle_sets = []
- self._line_sets = []
+ self._triangle_sets: List[str] = []
+ self._line_sets: List[str] = []
self._x3d_string = "" # the string that contains the x3d description
- def compute(self):
+ def compute(self) -> None:
+ """
+ Computes the tessellation of the shape.
+ """
shape_tesselator = ShapeTesselator(self._shape)
- shape_tesselator.Compute(compute_edges=self._export_edges,
- mesh_quality=self._mesh_quality,
- parallel=True)
+
+ if shape_tesselator.GetDeviation() <= 0:
+ raise ValueError("The deviation is <= 0.")
+
+ shape_tesselator.Compute(
+ compute_edges=self._export_edges,
+ mesh_quality=self._mesh_quality,
+ parallel=True,
+ )
self._triangle_sets.append(shape_tesselator.ExportShapeToX3DTriangleSet())
# then process edges
if self._export_edges:
# get number of edges
nbr_edges = shape_tesselator.ObjGetEdgeCount()
for i_edge in range(nbr_edges):
- edge_point_set = []
nbr_vertices = shape_tesselator.ObjEdgeGetVertexCount(i_edge)
- for i_vert in range(nbr_vertices):
- edge_point_set.append(shape_tesselator.GetEdgeVertex(i_edge, i_vert))
+ edge_point_set = [
+ shape_tesselator.GetEdgeVertex(i_edge, i_vert)
+ for i_vert in range(nbr_vertices)
+ ]
ils = export_edge_to_indexed_lineset(edge_point_set)
self._line_sets.append(ils)
- def to_x3dfile_string(self, shape_id):
- x3dfile_str = X3DFILE_HEADER
+ def to_x3dfile_string(self, shape_id: int) -> str:
+ """
+ Converts the shape to an X3D string.
+
+ Args:
+ shape_id (int): The ID of the shape.
+
+ Returns:
+ str: The X3D string.
+ """
+ x3dfile_str = X3DFILE_HEADER_TEMPLATE.substitute({"VERSION": f"{VERSION}"})
for triangle_set in self._triangle_sets:
- x3dfile_str += "\n"
+ x3dfile_str += ""
+ x3dfile_str += f"\n\n"
+ x3dfile_str += "\n"
#
# set Material or shader
#
if self._vs is None and self._fs is None:
- x3dfile_str += "\n" % self._transparency
- x3dfile_str += "\n"
+ x3dfile_str += f"\n"
else: # set shaders
- x3dfile_str += '\n'
+ x3dfile_str += (
+ '\n'
+ )
x3dfile_str += self._vs
- x3dfile_str += '\n'
+ x3dfile_str += "\n"
x3dfile_str += '\n'
x3dfile_str += self._fs
- x3dfile_str += '\n'
- x3dfile_str += '\n'
+ x3dfile_str += "\n"
+ x3dfile_str += "\n"
# export triangles
x3dfile_str += triangle_set
x3dfile_str += "\n"
@@ -336,59 +442,108 @@ def to_x3dfile_string(self, shape_id):
# -1 means doesn't show line
# the "Switch" node selects the group to be displayed
- x3dfile_str += indexed_lineset_to_x3d_string(self._line_sets, header=False, footer=False)
- x3dfile_str += '\n\n'
+ x3dfile_str += indexed_lineset_to_x3d_string(
+ self._line_sets, header=False, footer=False
+ )
+ x3dfile_str += "\n\n"
#
# use ElementTree to ensure xml file quality
#
xml_et = ElementTree.fromstring(x3dfile_str)
- clean_x3d_str = ElementTree.tostring(xml_et, encoding='utf8').decode('utf8')
+ return ElementTree.tostring(xml_et, encoding="utf8").decode("utf8")
- return clean_x3d_str
+ def write_to_file(self, filename: str, shape_id: int) -> None:
+ """
+ Writes the X3D string to a file.
- def write_to_file(self, filename, shape_id):
+ Args:
+ filename (str): The name of the file to write to.
+ shape_id (int): The ID of the shape.
+ """
with open(filename, "w") as f:
f.write(self.to_x3dfile_string(shape_id))
class X3DomRenderer:
- def __init__(self, path=None, display_axes_plane=True, axes_plane_zoom_factor=1.):
- if not path: # by default, write to a temp directory
- self._path = tempfile.mkdtemp()
- else:
- self._path = path
- self._html_filename = os.path.join(self._path, 'index.html')
- self._x3d_shapes = {}
- self._x3d_edges = {}
- self._axes_plane = display_axes_plane # display the small RVB axes and the plane
+ """
+ A renderer that uses x3dom to display shapes in a web browser.
+ """
+
+ def __init__(
+ self,
+ path: Optional[str] = None,
+ display_axes_plane: bool = True,
+ axes_plane_zoom_factor: float = 1.0,
+ ) -> None:
+ """
+ Initializes the X3DomRenderer.
+
+ Args:
+ path (str, optional): The path to the directory where the HTML
+ and JavaScript files will be created. If not specified, a
+ temporary directory will be created.
+ display_axes_plane (bool, optional): Whether to display the axes plane.
+ axes_plane_zoom_factor (float, optional): The zoom factor for the axes plane.
+ """
+ self._path = tempfile.mkdtemp() if not path else path
+ self._html_filename = os.path.join(self._path, "index.html")
+ self._x3d_shapes: Dict[str, Any] = {}
+ self._x3d_edges: Dict[str, Any] = {}
+ self._axes_plane = (
+ display_axes_plane # display the small RVB axes and the plane
+ )
self._axes_plane_zoom_factor = axes_plane_zoom_factor
- print("## x3dom webgl renderer - render axes/planes : %r - axes/plane zoom factor : %g" % (self._axes_plane,
- self._axes_plane_zoom_factor))
-
- def DisplayShape(self,
- shape,
- vertex_shader=None,
- fragment_shader=None,
- export_edges=False,
- color=(0.65, 0.65, 0.7),
- specular_color=(0.2, 0.2, 0.2),
- shininess=0.9,
- transparency=0.,
- line_color=(0, 0., 0.),
- line_width=2.,
- mesh_quality=1.):
- """ Adds a shape to the rendering buffer. This class computes the x3d file
+ print(
+ f"## x3dom webgl renderer - render axes/planes : {self._axes_plane} - axes/plane zoom factor : {self._axes_plane_zoom_factor}"
+ )
+
+ def DisplayShape(
+ self,
+ shape: Any,
+ vertex_shader: Optional[str] = None,
+ fragment_shader: Optional[str] = None,
+ export_edges: bool = False,
+ color: Tuple[float, float, float] = (0.65, 0.65, 0.7),
+ specular_color: Tuple[float, float, float] = (0.2, 0.2, 0.2),
+ shininess: float = 0.9,
+ transparency: float = 0.0,
+ line_color: Tuple[float, float, float] = (0.0, 0.0, 0.0),
+ line_width: float = 2.0,
+ mesh_quality: float = 1.0,
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
+ """
+ Adds a shape to the rendering buffer.
+
+ This class computes the x3d file.
+
+ Args:
+ shape: The shape to display.
+ vertex_shader (str, optional): The vertex shader to use.
+ fragment_shader (str, optional): The fragment shader to use.
+ export_edges (bool, optional): Whether to export the edges of the shape.
+ color (tuple, optional): The color of the shape.
+ specular_color (tuple, optional): The specular color of the shape.
+ shininess (float, optional): The shininess of the shape.
+ transparency (float, optional): The transparency of the shape.
+ line_color (tuple, optional): The color of the lines.
+ line_width (float, optional): The width of the lines.
+ mesh_quality (float, optional): The quality of the mesh.
+
+ Returns:
+ A tuple containing the shapes and edges.
"""
# if the shape is an edge or a wire, use the related functions
if is_edge(shape):
print("X3D exporter, discretize an edge")
pnts = discretize_edge(shape)
- edge_hash = "edg%s" % uuid.uuid4().hex
+ edge_hash = f"edg{uuid.uuid4().hex}"
line_set = export_edge_to_indexed_lineset(pnts)
- x3dfile_content = indexed_lineset_to_x3d_string([line_set], ils_id=edge_hash)
- edge_full_path = os.path.join(self._path, edge_hash + '.x3d')
+ x3dfile_content = indexed_lineset_to_x3d_string(
+ [line_set], ils_id=edge_hash
+ )
+ edge_full_path = os.path.join(self._path, f"{edge_hash}.x3d")
with open(edge_full_path, "w") as edge_file:
edge_file.write(x3dfile_content)
# store this edge hash
@@ -398,10 +553,12 @@ def DisplayShape(self,
if is_wire(shape):
print("X3D exporter, discretize a wire")
pnts = discretize_wire(shape)
- wire_hash = "wir%s" % uuid.uuid4().hex
+ wire_hash = f"wir{uuid.uuid4().hex}"
line_set = export_edge_to_indexed_lineset(pnts)
- x3dfile_content = indexed_lineset_to_x3d_string([line_set], ils_id=wire_hash)
- wire_full_path = os.path.join(self._path, wire_hash + '.x3d')
+ x3dfile_content = indexed_lineset_to_x3d_string(
+ [line_set], ils_id=wire_hash
+ )
+ wire_full_path = os.path.join(self._path, f"{wire_hash}.x3d")
with open(wire_full_path, "w") as wire_file:
wire_file.write(x3dfile_content)
# store this edge hash
@@ -409,32 +566,60 @@ def DisplayShape(self,
return self._x3d_shapes, self._x3d_edges
shape_uuid = uuid.uuid4().hex
- shape_hash = "shp%s" % shape_uuid
- x3d_exporter = X3DExporter(shape, vertex_shader, fragment_shader,
- export_edges, color,
- specular_color, shininess, transparency,
- line_color, line_width, mesh_quality)
+ shape_hash = f"shp{shape_uuid}"
+ x3d_exporter = X3DExporter(
+ shape,
+ vertex_shader,
+ fragment_shader,
+ export_edges,
+ color,
+ specular_color,
+ shininess,
+ transparency,
+ line_color,
+ line_width,
+ mesh_quality,
+ )
x3d_exporter.compute()
- x3d_filename = os.path.join(self._path, "%s.x3d" % shape_hash)
+ x3d_filename = os.path.join(self._path, f"{shape_hash}.x3d")
# the x3d filename is computed from the shape hash
shape_id = len(self._x3d_shapes)
x3d_exporter.write_to_file(x3d_filename, shape_id)
- self._x3d_shapes[shape_hash] = [export_edges, color, specular_color, shininess,
- transparency, line_color, line_width]
+ self._x3d_shapes[shape_hash] = [
+ export_edges,
+ color,
+ specular_color,
+ shininess,
+ transparency,
+ line_color,
+ line_width,
+ ]
return self._x3d_shapes, self._x3d_edges
- def render(self, addr="localhost", server_port=8080, open_webbrowser=False):
- """ Call the render() method to display the X3D scene.
+ def render(
+ self,
+ addr: str = "localhost",
+ server_port: int = 8080,
+ open_webbrowser: bool = False,
+ ) -> None:
+ """
+ Renders the scene in the browser.
"""
# first generate the HTML root file
self.generate_html_file(self._axes_plane, self._axes_plane_zoom_factor)
# then create a simple web server
start_server(addr, server_port, self._path, open_webbrowser)
- def generate_html_file(self, axes_plane, axes_plane_zoom_factor):
- """ Generate the HTML file to be rendered wy the web browser
- axes_plane: a boolean, telles wether or not display axes
+ def generate_html_file(
+ self, axes_plane: bool, axes_plane_zoom_factor: float
+ ) -> None:
+ """
+ Generates the HTML file to be rendered by the web browser.
+
+ Args:
+ axes_plane (bool): Whether to display the axes plane.
+ axes_plane_zoom_factor (float): The zoom factor for the axes plane.
"""
with open(self._html_filename, "w") as html_file:
html_file.write("\n")
@@ -444,5 +629,7 @@ def generate_html_file(self, axes_plane, axes_plane_zoom_factor):
# body
# merge shapes and edges keys
all_shapes = list(self._x3d_shapes) + list(self._x3d_edges)
- html_file.write(HTMLBody(all_shapes, axes_plane, axes_plane_zoom_factor).get_str())
+ html_file.write(
+ HTMLBody(all_shapes, axes_plane, axes_plane_zoom_factor).get_str()
+ )
html_file.write("