diff --git a/docs/conftest.py b/docs/conftest.py index 206c49e362aaf..71b4ec0c98e6e 100644 --- a/docs/conftest.py +++ b/docs/conftest.py @@ -1,20 +1,21 @@ # -*- coding: utf-8 -*- # -- stdlib -- +from dataclasses import dataclass +from functools import wraps +from itertools import count +from re import M +from typing import List, Optional, Dict import linecache import sys import uuid import warnings -from dataclasses import dataclass -from functools import wraps -from itertools import count -from typing import List, Optional # -- third party -- +from pytest import ExceptionInfo import marko +import matplotlib.pyplot as plt import pytest -from pytest import ExceptionInfo - import taichi as ti # -- own -- @@ -58,12 +59,6 @@ M = 8 """ -PRELUDES[ - "gui" -] = """ -gui = ti.GUI('Title', res=(400, 400)) -""" - def hook(module, name=None): def inner(hooker): @@ -81,6 +76,9 @@ def real_hooker(*args, **kwargs): return inner +GUI_WINDOW = None + + @hook(ti.GUI) def show(orig, self, *args, **kwargs): if not self.running: @@ -92,10 +90,29 @@ def show(orig, self, *args, **kwargs): return orig(self, *args, *kwargs) +@hook(ti.GUI) +def __init__(orig, self, *args, **kwargs): + global GUI_WINDOW + assert not GUI_WINDOW + orig(self, *args, **kwargs) + GUI_WINDOW = self + + +@hook(ti.GUI) +def close(orig, self): + global GUI_WINDOW + assert not GUI_WINDOW or self is GUI_WINDOW + GUI_WINDOW = None + return orig(self) + + +GGUI_WINDOW = None + + @hook(ti.ui.Window) def show(orig, self, *args, **kwargs): if not self.running: - self.close() + self.destroy() return self._frames_remaining -= 1 @@ -103,10 +120,47 @@ def show(orig, self, *args, **kwargs): return orig(self, *args, *kwargs) +@hook(ti.ui.Window) +def __init__(orig, self, *args, **kwargs): + global GGUI_WINDOW + assert not GGUI_WINDOW + orig(self, *args, **kwargs) + GGUI_WINDOW = self + + +@hook(ti.ui.Window) +def destroy(orig, self): + global GGUI_WINDOW + assert not GGUI_WINDOW or self is GGUI_WINDOW + GGUI_WINDOW = None + return orig(self) + + +@hook(plt) +def show(orig): + return + + +@hook(plt) +def imshow(orig, img): + return + + +_prop_running = property( + (lambda self: self._frames_remaining > 0), + (lambda self, v: None), +) + ti.GUI._frames_remaining = 10 -ti.GUI.running = property(lambda self: self._frames_remaining <= 0) +ti.GUI.running = _prop_running ti.ui.Window._frames_remaining = 10 -ti.ui.Window.running = property(lambda self: self._frames_remaining <= 0) +ti.ui.Window.running = _prop_running + + +def pytest_runtest_teardown(item, nextitem): + global GUI_WINDOW, GGUI_WINDOW + GUI_WINDOW and GUI_WINDOW.close() + GGUI_WINDOW and GGUI_WINDOW.destroy() @dataclass @@ -114,6 +168,7 @@ class PythonSnippet: name: str code: str skip: Optional[str] + per_file_preludes: Dict[str, str] known_error: bool = False preludes: Optional[List[str]] = None @@ -131,13 +186,21 @@ def collect(self): if bad_tags: raise ValueError(f"Invalid language tag {bad_tags} in markdown file") + per_file_preludes = {} + spec = None for name, c in codes: if not c.lang == "python": continue extra = dict((v.split(":", 1) + [None])[:2] for v in c.extra.split()) code = c.children[0].children - if "cont" in extra: + if "as-prelude" in extra: + assert "cont" not in extra + assert "preludes" not in extra + prelude_name = extra["as-prelude"] + assert prelude_name not in per_file_preludes, f"Duplicate prelude {prelude_name}" + per_file_preludes[prelude_name] = code + elif "cont" in extra: assert spec is not None spec.code += code else: @@ -153,6 +216,7 @@ def collect(self): code=code, skip=extra.get("skip-ci"), known_error="known-error" in extra, + per_file_preludes=per_file_preludes, preludes=preludes, ) @@ -215,8 +279,14 @@ def runtest(self): else: preludes.insert(0, "init") - source = [PRELUDES[p] for p in preludes] + [spec.code] - source = "".join(source) + snippets = [] + for p in preludes: + c = spec.per_file_preludes.get(p) + c = c or PRELUDES.get(p) + assert c is not None, f"Unknown prelude {p}" + snippets.append(c) + snippets.append(spec.code) + source = "".join(snippets) fn = f"" code = compile(source, fn, "exec") linecache.cache[fn] = ( diff --git a/docs/cover-in-ci.lst b/docs/cover-in-ci.lst index bb633530f520a..420ae0ee214c6 100644 --- a/docs/cover-in-ci.lst +++ b/docs/cover-in-ci.lst @@ -26,6 +26,8 @@ docs/lang/articles/kernels/kernel_sync.md docs/lang/articles/performance_tuning/performance.md docs/lang/articles/performance_tuning/profiler.md docs/lang/articles/reference/global_settings.md +docs/lang/articles/faqs/faq.md +docs/lang/articles/visualization/ggui.md docs/lang/articles/visualization/gui_system.md docs/rfcs/20220410-rfc-process.md docs/rfcs/yyyymmdd-rfc-template.md diff --git a/docs/lang/articles/faqs/faq.md b/docs/lang/articles/faqs/faq.md index c1b6d2570cc00..210a5466cf22f 100755 --- a/docs/lang/articles/faqs/faq.md +++ b/docs/lang/articles/faqs/faq.md @@ -14,7 +14,7 @@ You may have a Python interpreter with an unsupported version. Currently, Taichi ### Outer-most loops in Taichi kernels are by default parallel. How can I **serialize** one of them? -A solution is to add an additional *ghost* loop with only one iteration outside the loop you want to serialize. +A solution is to add an additional _ghost_ loop with only one iteration outside the loop you want to serialize. ```python {1} for _ in range(1): # This "ghost" loop will be "parallelized", but with only one thread. Therefore, the containing loop below is serialized. @@ -28,7 +28,6 @@ You can call `ti.sync()`, which is similar to CUDA's `cudaStreamSynchronize()`, `__syncthreads()` is a block-level synchronization barrier, and Taichi provides a synonymous API `ti.simt.block.sync()`, which for now supports CUDA and Vulkan backends only. However, all block-level APIs are still experimental, and you should use this API only when it relates to SIMT operation synchronization and `SharedArray` reads and writes. - ## Data structures ### How do I declare a field with a **dynamic length**? @@ -49,6 +48,9 @@ Direct value assignments lead to semantic ambiguity. For example, `a = b` can me You can swap two fields in the Taichi scope using a struct for: ```python +a = ti.field(ti.i32, 32) +b = ti.field(ti.i32, 32) + @ti.func def field_copy(src: ti.template(), dst: ti.template()): for I in ti.grouped(src): @@ -65,13 +67,26 @@ test() ### How do I compute the minimum/maximum of a field? -Use `ti.automic.min/max` instead of `ti.min/max`. For example: +Use `ti.atomic_min/atomic_max` instead of `ti.min/max`. For example: ```python -for i in x: - ret = ti.atomic_min(ret, x[i]) +x = ti.field(ti.f32, 32) + +@ti.kernel +def x_min() -> ti.f32: + ret: ti.f32 = x[0] + + for i in x: + ret = ti.atomic_min(ret, x[i]) + + return ret + +x_min() ``` +### Does Taichi support `bool` type? + +Currently, Taichi does not support the `bool` type. ### How do I program on less structured data structures (such as graphs and tetrahedral meshes) in Taichi? @@ -92,6 +107,7 @@ Yes, Taichi's Python user-facing APIs should work natively with any language ser Take VSCode as an example, you can install `Python` or `Pylance` extensions to get language support like signature help with type information, code completion etc. If it doesn't work out of box after installing the extension, please make sure the right Python interpreter is selected by: + - invoke command palette (`Shift + Command + P (Mac) / Ctrl + Shift + P (Windows/Linux)`) - find `Python: Select Interpreter` - make sure you select the path to the Python interpreter you're using with a `taichi` package installed @@ -106,15 +122,14 @@ Follow these steps to install Taichi on a server without Internet access. pip download taichi ``` -*This command downloads the wheel package of Taichi and all its dependencies.* +_This command downloads the wheel package of Taichi and all its dependencies._ -2. Copy the downloaded *.whl packages to your local server and install each with the following command. Note that you *must* complete all dependency installation before installing Taichi. +2. Copy the downloaded *.whl packages to your local server and install each with the following command. Note that you *must\* complete all dependency installation before installing Taichi. ``` python -m pip install xxxx.whl ``` - ## Integration with other libs/softwares ### What is the most convenient way to load images into Taichi fields? @@ -127,17 +142,23 @@ Yes, Taichi supports many popular Python packages. Taichi provides helper functi ```python import taichi as ti -pixels = ti.field(ti.f32, (1024, 512)) import numpy as np -arr = np.random.rand(1024, 512) -pixels.from_numpy(arr) # load numpy data into taichi fields import matplotlib.pyplot as plt + +pixels = ti.field(ti.f32, (512, 512)) + +def render_pixels(): + arr = np.random.rand(512, 512) + pixels.from_numpy(arr) # load numpy data into taichi fields + +render_pixels() arr = pixels.to_numpy() # store taichi data into numpy arrays plt.imshow(arr) plt.show() import matplotlib.cm as cm cmap = cm.get_cmap('magma') -gui = ti.GUI('Color map') +gui = ti.GUI('Color map', (512, 512)) + while gui.running: render_pixels() arr = pixels.to_numpy() @@ -147,14 +168,12 @@ while gui.running: Besides, you can also pass numpy arrays or torch tensors into a Taichi kernel as arguments. See [Interacting with external arrays](../basic/external.md) for more details. - ### Can I integrate Taichi and Houdini? The answer is an unequivocal Yes! Our contributors managed to embed [taichi_elements](https://github.com/taichi-dev/taichi_elements), a multi-material continuum physics engine, into Houdini as an extension, combining Houdini's flexibility in preprocessing with Taichi's strength in high-performance computation. You can follow the instructions provided [here](https://github.com/taichi-dev/taichi_houdini). - ## Precision related ### How do I accurately initialize a vector or matrix with `f64` precision when my default floating-point precision (`default_fp`) is `f32`? @@ -196,7 +215,7 @@ A Taichi kernel **cannot** take a Python list directly. You need to use NumPy ar For example, the following code snippet does not work: -```python +```python skip-ci:Error import taichi as ti import numpy as np ti.init() @@ -233,18 +252,16 @@ Taichi's GUI system can display colors when the field it accepts is a 3D vector To enable color mapping, convert `ti.field` into a NumPy array and call Matplotlib's colormap (`cm`), as shown in the following example: -```python -gui = ti.GUI(f'Re = {un * lx / mu:4.0f} V_mag', (nx+1, ny+1)) +```python skip-ci:Trivial +pixels = ti.Vector.field(3, shape=(w, h)) +gui = ti.GUI(f'Window title', (w, h)) step = 0 while gui.running: # Main loop - print(f'>>> step : {step:<6d}, time : {step*dt:<6.3f} sec') - substep() - if step % 10 = 1: - V_np = V_mag.to_numpy() - V_img = cm.jet(V_np) - gui.set_image(V_img) # Plot the velocity magnitude contour - gui.show() - step += 1 + simulate_one_substep(pixels) + img = pixels.to_numpy() + img = cm.jet(img) + gui.set_image(img) + gui.show() ``` ## Objective-oriented programming @@ -328,7 +345,7 @@ And data in fields `x` and `y` can be found in files **x.txt** and **y.txt**, re Taichi fields adopt a different coordinate system from NumPy's arrays for storing images. In a Taichi field, [0,0] denotes the pixel at the lower left corner of the image; the first axis extends to the right of the image; the second axis extends to the top. -This is different from the usual convention taken by popular third-party libs like `matplotlib` or `opencv`, where [0, 0] denotes the pixel at the top left corner, the first axis extends down to the bottom of the image, and the second axis extends to the right. +This is different from the usual convention taken by popular third-party libs like `matplotlib` or `opencv`, where [0, 0] denotes the pixel at the top left corner, the first axis extends down to the bottom of the image, and the second axis extends to the right. Therefore, to display a NumPy array using `matplotlb`'s `imshow()`, you must rotate it 90 degrees clockwise. diff --git a/docs/lang/articles/visualization/ggui.md b/docs/lang/articles/visualization/ggui.md index edf166162c7a9..ba5e833b8d2aa 100644 --- a/docs/lang/articles/visualization/ggui.md +++ b/docs/lang/articles/visualization/ggui.md @@ -4,10 +4,10 @@ sidebar_position: 2 # A New UI system: GGUI -| **Category** | **Prerequisites** | -| ------------ | ------------------------------------------------------------ | -| OS | Windows / Linux / Mac OS X | -| Backend | x64 / CUDA / Vulkan | +| **Category** | **Prerequisites** | +| ------------ | -------------------------- | +| OS | Windows / Linux / Mac OS X | +| Backend | x64 / CUDA / Vulkan | Starting from v0.8.0, Taichi adds a new UI system GGUI. The new system uses GPU for rendering, making it much faster to render 3D scenes. That is why this new system gets its name as GGUI. This document describes the APIs that it provides. @@ -15,25 +15,41 @@ Starting from v0.8.0, Taichi adds a new UI system GGUI. The new system uses GPU If you choose Vulkan as backend, ensure that you [install the Vulkan environment](https://vulkan.lunarg.com/sdk/home). ::: - :::note It is recommended that you familiarize yourself with GGUI through the examples in `examples/ggui_examples`. ::: +:::note +The variables referenced in code snippets below are define like this: + +```python as-prelude:vars +vertices = ti.Vector.field(2, ti.f32, shape=200) +vertices_3d = ti.Vector.field(3, ti.f32, shape=200) +indices = ti.field(ti.i32, shape=200 * 3) +normals = ti.Vector.field(3, ti.f32, shape=200) +per_vertex_color = ti.Vector.field(3, ti.f32, shape=200) + +color = (0.5, 0.5, 0.5) +``` +::: + ## Create a window `ti.ui.Window(name, res)` creates a window. -```python -window = ti.ui.Window('Window Title', res = (640, 360), pos = (150, 150)) +```python preludes:vars +window = ti.ui.Window(name='Window Title', res = (640, 360), fps_limit=200, pos = (150, 150)) ``` -The argument `res` means resulotion(width and height) of the window, `pos` means the position of the window which origins from the left-top of your main screen. -The following three types of objects can be displayed on a `ti.ui.Window`: +- The `name` parameter sets the title of the window. +- The `res` parameter specifies the resolution (width and height) of the window. +- The `fps_limit` parameter sets the maximum frames per second (FPS) for the window. +- The `pos` parameter specifies the position of the window with respect to the top-left corner of the main screen. + +A `ti.ui.Window` can display three types of objects: -- 2D Canvas, which can be used to draw simple 2D geometries such as circles and triangles. -- 3D Scene, which can be used to render 3D meshes and particles, with a configurable camera and light sources. -- Immediate mode GUI components, for example buttons and textboxes. +- 2D Canvas, which is used to draw simple 2D geometries like circles and triangles.+ 3D Scene, which is used to render 3D meshes and particles, and provides configurable camera and light sources. +- Immediate mode GUI components, such as buttons and textboxes. ## 2D Canvas @@ -41,18 +57,22 @@ The following three types of objects can be displayed on a `ti.ui.Window`: The following code retrieves a `Canvas` object that covers the entire window. -```python +```python cont canvas = window.get_canvas() ``` ### Draw on the canvas -```python +```python cont canvas.set_background_color(color) canvas.triangles(vertices, color, indices, per_vertex_color) + +radius = 5 canvas.circles(vertices, radius, color, per_vertex_color) + +width = 2 canvas.lines(vertices, width, indices, color, per_vertex_color) -canvas.set_image(image) +canvas.set_image(window.get_image_buffer_as_numpy()) ``` The arguments `vertices`, `indices`, `per_vertex_color`, and `image` must be Taichi fields. If `per_vertex_color` is provided, `color` is ignored. @@ -65,18 +85,18 @@ The canvas is cleared after every frame. Always call these methods within the re ### Create a scene -```python +```python cont scene = ti.ui.Scene() ``` ### Configure camera -```python +```python cont camera = ti.ui.Camera() -camera.position(pos) -camera.lookat(pos) -camera.up(dir) -camera.projection_mode(mode) +camera.position(1, 2, 3) # x, y, z +camera.lookat(4, 5, 6) +camera.up(0, 1, 0) +camera.projection_mode(ti.ui.ProjectionMode.Perspective) scene.set_camera(camera) ``` @@ -86,17 +106,17 @@ scene.set_camera(camera) Call `point_light()` to add a point light to the scene. -```python -scene.point_light(pos, color) +```python cont +scene.point_light(pos=(1, 2, 3), color=(0.5, 0.5, 0.5)) ``` Note that you need to call `point_light()` for every frame. Similar to the `canvas()` methods, call this method within your render loop. ### 3D Geometries -```python +```python cont scene.lines(vertices, width, indices, color, per_vertex_color) -scene.mesh(vertices, indices, normals, color, per_vertex_color) +scene.mesh(vertices_3d, indices, normals, color, per_vertex_color) scene.particles(vertices, radius, color, per_vertex_color) ``` @@ -106,7 +126,7 @@ The positions/centers of geometries should be in the world-space coordinates. :::note -If a mesh has `num` triangles, the `indices` should be a 1D scalar field with a shape `(num * 3)`, *not* a vector field. +If a mesh has `num` triangles, the `indices` should be a 1D scalar field with a shape `(num * 3)`, _not_ a vector field. `normals` is an optional parameter for `scene.mesh()`. @@ -153,14 +173,18 @@ while window.running: ### Advanced 3d Geometries -```python -scene.lines(vertices, width, indices, color, per_vertex_color, vertex_offset, vertex_count, index_offset, index_count) +```python preludes:vars +scene = ti.ui.Scene() +width = 2 +radius = 5 + +scene.lines(vertices, width, indices, color, per_vertex_color, vertex_offset=0, vertex_count=10, index_offset=0, index_count=10) -scene.mesh(vertices, indices, normals, color, per_vertex_color, vertex_offset, vertex_count, index_offset, index_count, show_wireframe) +scene.mesh(vertices_3d, indices, normals, color, per_vertex_color, vertex_offset=0, vertex_count=10, index_offset=0, index_count=10, show_wireframe=True) -scene.particles(vertices, radius, color, per_vertex_color, index_offset, index_count) +scene.particles(vertices, radius, color, per_vertex_color, index_offset=0, index_count=10) -scene.mesh_instance(vertices, indices, normals, color, per_vertex_color, vertex_offset, vertex_count, index_offset, index_count, show_wireframe) +scene.mesh_instance(vertices_3d, indices, normals, color, per_vertex_color, vertex_offset=0, vertex_count=10, index_offset=0, index_count=10, show_wireframe=True) ``` The additional arguments `vertex_offset`, `vertex_count`, `index_offset` and `index_count` control the visible part of the particles and mesh. For the `mesh()` and `mesh_instance()` methods, set whether to show wireframe mode through setting `show_wireframe`. @@ -169,34 +193,38 @@ The additional arguments `vertex_offset`, `vertex_count`, `index_offset` and `in 1. Example of drawing a part of the mesh/particles -```python +```python preludes:vars +scene = ti.ui.Scene() + +center = ti.Vector.field(3, ti.f32, shape=10) + # For particles # draw the 2-th to 7-th particles -scene.particles(center, radius, -index_offset = 1, -index_count = 6) +scene.particles(center, radius=1, index_offset = 1, index_count = 6) # For mesh # 1. with indices -scene.mesh(vertices, indices, -index_offset = user_defined_first_indices_index, -index_count = user_defined_index_count, -# vertex_offset is set to 0 by default, and it is not necessary -# to assign vertex_offset a value that otherwise you must. -vertex_offset = user_defined_vertex_offset) +scene.mesh( + vertices_3d, indices, index_offset=1, index_count=3, + # vertex_offset is set to 0 by default, and it is not necessary + # to assign vertex_offset a value that otherwise you must. + vertex_offset = 1 + ) # usually used as below: # draw the 11-th to 111-th mesh vertexes -scene.mesh(vertices, indices, -index_offset = 10, -index_count = 100) +scene.mesh(vertices_3d, indices, index_offset=10, index_count=100) # 2. without indices (similar to the particles' example above) -scene.mesh(vertices, -vertex_offset = user_defined_first_vertex_index, -vertex_count = user_defined_vertex_count) +scene.mesh( + vertices_3d, + vertex_offset=2, # user defined first vertex index + vertex_count=3, # user defined vertex count + ) ``` + 2. An example of drawing part of lines + ```python import taichi as ti @@ -249,8 +277,11 @@ while window.running: ``` 3. Details of mesh instancing -```python -num_instance = 100 + +```python preludes:vars +scene = ti.ui.Scene() + +num_instance = 100 m_transforms = ti.Matrix.field(4, 4, dtype = ti.f32, shape = num_instance) @@ -279,11 +310,12 @@ m_transforms = ti.Matrix.field(4, 4, dtype = ti.f32, shape = num_instance) ... # Draw mesh instances (from the 1st instance) -scene.mesh_instance(vertices, indices, transforms = m_transforms, instance_offset = 1) +scene.mesh_instance(vertices_3d, indices, transforms = m_transforms, instance_offset = 1) ``` + 4. Example of setting wireframe mode -```python +```python preludes:vars window = ti.ui.Window("Display Mesh", (1024, 1024), vsync=True) canvas = window.get_canvas() scene = ti.ui.Scene() @@ -302,27 +334,27 @@ while window.running: ... # if to show wireframe - scene.mesh_instance(vertices, indices, instance_count = 100 , show_wireframe = True) + scene.mesh_instance(vertices_3d, indices, instance_count = 100 , show_wireframe = True) canvas.scene(scene) show_options() window.show() ``` - - :::note If `indices` is not provided, consider using like this: -```python -scene.mesh(vertices, normals, color, per_vertex_color, vertex_offset, vertex_count, wireframe) -``` -If `indices` is provided, consider using like this: -```python -scene.mesh(vertices, indices, normals, color, per_vertex_color, vertex_offset, index_offset, index_count, wireframe) + +```python preludes:vars skip-ci:Taichi-Bug +scene = ti.ui.Scene() +scene.mesh(vertices_3d, normals, color, per_vertex_color, vertex_offset=0, vertex_count=50, show_wireframe=True) ``` +If `indices` is provided, consider using like this: +```python cont +scene.mesh(vertices_3d, indices, normals, color, per_vertex_color, vertex_offset=0, index_offset=0, index_count=50, show_wireframe=True) +``` ::: @@ -330,13 +362,15 @@ scene.mesh(vertices, indices, normals, color, per_vertex_color, vertex_offset, i You can render a scene on a canvas. -```python +```python cont +window = ti.ui.Window(name='Title', res=(640, 360)) +canvas = window.get_canvas() canvas.scene(scene) ``` ### Fetching Color/Depth information -```python +```python cont img = window.get_image_buffer_as_numpy() window.get_depth_buffer(scene_depth) depth = window.get_depth_buffer_as_numpy() @@ -348,12 +382,13 @@ After rendering the current scene, you can fetch the color and depth information :::example 1. Example of fetching color information -```python + +```python skip-ci:Trivial window = ti.ui.Window("Test for getting image buffer from ggui", (768, 768), vsync=True) video_manager = ti.tools.VideoManager("OutputDir") while window.running: - render_scene() + # render_scene() img = window.get_image_buffer_as_numpy() video_manager.write_frame(img) window.show() @@ -362,6 +397,7 @@ video_manager.make_video(gif=True, mp4=True) ``` 2. An example of fetching the depth data + ```python window_shape = (720, 1080) window = ti.ui.Window("Test for copy depth data", window_shape) @@ -377,7 +413,7 @@ scene_depth = ti.ndarray(ti.f32, shape = (w, h)) # scene_depth = ti.field(ti.f32, shape = (w, h)) while window.running: - render() + # render() canvas.scene(scene) window.get_depth_buffer(scene_depth) window.show() @@ -388,30 +424,32 @@ while window.running: The design of GGUI's GUI components follows the [Dear ImGui](https://github.com/ocornut/imgui) APIs. ```python +window = ti.ui.Window("Test for GUI", res=(512, 512)) gui = window.get_gui() -with gui.sub_window(name, x, y, width, height): - gui.text(text) - is_clicked = gui.button(name) - new_value = gui.slider_float(name, old_value, min_value, max_value) - new_color = gui.color_edit_3(name, old_color) +value = 0 +color = (1.0, 1.0, 1.0) +with gui.sub_window("Sub Window", x=10, y=10, width=300, height=100): + gui.text("text") + is_clicked = gui.button("name") + value = gui.slider_float("name1", value, minimum=0, maximum=100) + color = gui.color_edit_3("name2", color) ``` ## Show a window Call `show()` to show a window. -```python -... +```python cont window.show() ``` -Call this method *only* at the end of the render loop for each frame. +Call this method _only_ at the end of the render loop for each frame. ## User input processing To retrieve the events that have occurred since the last method call: -```python +```python cont events = window.get_events() ``` @@ -430,7 +468,10 @@ To check if a key is pressed: The following is a user input processing example from [**mpm128**](https://github.com/taichi-dev/taichi/blob/master/python/taichi/examples/ggui_examples/mpm128_ggui.py): -```python +```python cont +gravity = ti.Vector.field(2, ti.f32, shape=()) +attractor_strength = ti.field(ti.f32, shape=()) + while window.running: # keyboard event processing if window.get_event(ti.ui.PRESS): @@ -449,17 +490,19 @@ while window.running: attractor_strength[None] = 1 if window.is_pressed(ti.ui.RMB): attractor_strength[None] = -1 + + window.show() ``` ## Image I/O To write the current frame in the window to an image file: -```python -window.save_image(filename) +```python cont +window.save_image('frame.png') ``` -Note that you must call `window.save_image()` before calling `window.show()`. +Note that you _must_ call `window.save_image()` before calling `window.show()`. ## Off-screen rendering diff --git a/docs/lang/articles/visualization/gui_system.md b/docs/lang/articles/visualization/gui_system.md index a67edf11ab0d3..34cea85a4ac4c 100644 --- a/docs/lang/articles/visualization/gui_system.md +++ b/docs/lang/articles/visualization/gui_system.md @@ -8,10 +8,15 @@ Taichi has a built-in GUI system for visualizing simulation data in data contain ## Create and display a window -The following code creates a `640x360` window with a "Hello World!" title, and displays it by calling `gui.show()`: +The following code creates a `640x360` window with a "Hello World!" title: -```python +```python as-prelude:gui gui = ti.GUI('Hello World!', (640, 360)) +``` + +Displays it by calling `gui.show()`: + +```python preludes:gui while gui.running: gui.show() ``` @@ -28,8 +33,10 @@ You can set `gui.running=False` in the `while` loop to close the GUI: ```python gui = ti.GUI('Window Title', (640, 360)) +some_events_happend = lambda: random.random() < 0.8 + while gui.running: - if some_events_happend: + if some_events_happend(): gui.running = False gui.show() ``` @@ -331,6 +338,7 @@ while gui.running: print('Go left!') elif gui.is_pressed('d', ti.GUI.RIGHT): print('Go right!') + gui.show() ``` :::caution