From bb06a745cdc5be35b844a4e1d9940635f811bb17 Mon Sep 17 00:00:00 2001 From: ashawkey Date: Fri, 12 Jan 2024 15:31:40 +0800 Subject: [PATCH] update --- docs/blender/blender_script.md | 419 ++++++++++++++++++ .../image-20230305191231538.png | Bin .../image-20230305191530471.png | Bin .../image-20230305191716034.png | Bin .../image-20230521220540588.png | Bin .../image-20230522151233612.png | Bin .../image-20230522160717435.png | Bin .../image-20230817121854933.png | Bin .../image-20230817121902473.png | Bin .../image-20230817121909364.png | Bin .../image-20230914130930948.png | Bin .../image-20230914131227283.png | Bin .../image-20230914131413554.png | Bin .../image-20230914131605802.png | Bin .../image-20230914132030562.png | Bin docs/{model => blender}/blender_tutorial.md | 0 docs/index.md | 40 +- docs/model/blender_script.md | 52 --- .../image-20240112152907640.png | Bin 0 -> 19569 bytes docs/python/pypi_publish.md | 47 +- 20 files changed, 466 insertions(+), 92 deletions(-) create mode 100644 docs/blender/blender_script.md rename docs/{model => blender}/blender_tutorial.assets/image-20230305191231538.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230305191530471.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230305191716034.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230521220540588.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230522151233612.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230522160717435.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230817121854933.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230817121902473.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230817121909364.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230914130930948.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230914131227283.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230914131413554.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230914131605802.png (100%) rename docs/{model => blender}/blender_tutorial.assets/image-20230914132030562.png (100%) rename docs/{model => blender}/blender_tutorial.md (100%) delete mode 100644 docs/model/blender_script.md create mode 100644 docs/python/pypi_publish.assets/image-20240112152907640.png diff --git a/docs/blender/blender_script.md b/docs/blender/blender_script.md new file mode 100644 index 000000000..4278da70a --- /dev/null +++ b/docs/blender/blender_script.md @@ -0,0 +1,419 @@ +# blender script + +### Install blender on ubuntu + +#### Install blender + +```bash +sudo snap install blender --classic +``` + +This will install the blender binary and all environments. + + +#### Install blender as python modules + +```bash +pip install bpy mathutils +``` + +This allow you to directly run blender scripts with python. + + +To enable GPU, go `Edit --> Preferences --> System --> Cycles Engine --> Choose CUDA or Optix`. + +logs are printed to the terminal console, use `window --> toggle system console` to open it. + + +### auto import many objects + +assume you have a folder with lots of obj models (each in a sub-folder): + +```python +import bpy +import os + +context = bpy.context +dir = r"C:\Users\hawke\Downloads\bear" + +obj_dirs = os.listdir(dir) + +for i, name in enumerate(obj_dirs): + obj_dir = os.path.join(dir, name) + files = os.listdir(obj_dir) + for file in files: + if not file.endswith('.obj'): continue + path = os.path.join(obj_dir, file) + + print(f'[INFO] {i} process {file}') + + bpy.ops.import_scene.obj(filepath=path, filter_glob="*.obj;*.mtl;*.png") # also load png textures + obj = context.selected_objects[0] + + # location (10 in a row) + h, w = i % 10 - 5, i // 10 - 5 + obj.location = (h, w, 0) +``` + +### locate objects in current scene + +```python +import bpy + +# all objects +bpy.data.objects + +# access by index +bpy.data.objects[0] + +# access by name +bpy.data.objects['Light'] + +# modify property +bpy.data.objects['Light'].location = (0, 0, 0) +``` + +### Complete render script + +```python +import os +import sys +import tqdm +import math +import argparse +import numpy as np + +from contextlib import contextmanager + +@contextmanager +def stdout_redirected(to=os.devnull): + ''' + import os + + with stdout_redirected(to=filename): + print("from Python") + os.system("echo non-Python applications are also supported") + ''' + fd = sys.stdout.fileno() + + ##### assert that Python and C stdio write using the same file descriptor + ####assert libc.fileno(ctypes.c_void_p.in_dll(libc, "stdout")) == fd == 1 + + def _redirect_stdout(to): + sys.stdout.close() # + implicit flush() + os.dup2(to.fileno(), fd) # fd writes to 'to' file + sys.stdout = os.fdopen(fd, 'w') # Python writes to fd + + with os.fdopen(os.dup(fd), 'w') as old_stdout: + with open(to, 'w') as file: + _redirect_stdout(to=file) + try: + yield # allow code to be run with the redirected stdout + finally: + _redirect_stdout(to=old_stdout) # restore stdout. + # buffering and flags such as + # CLOEXEC may be different + +parser = argparse.ArgumentParser() +parser.add_argument("--mesh", type=str, required=True) +parser.add_argument("--outdir", type=str, required=True) +parser.add_argument("--camera_type", type=str, default='fixed') +parser.add_argument("--engine", type=str, default='CYCLES', choices=['BLENDER_EEVEE', 'CYCLES']) +parser.add_argument("--gpu", type=int, default=0) +parser.add_argument("--resolution", type=int, default=256) +parser.add_argument("--num_images", type=int, default=16) + +parser.add_argument("--radius", type=float, default=2.0) +parser.add_argument("--fovy", type=float, default=49.1) +parser.add_argument("--bound", type=float, default=0.8) + +parser.add_argument("--elevation", type=float, default=0) # +z to -z: -90 to 90 +parser.add_argument("--elevation_start", type=float, default=-40) +parser.add_argument("--elevation_end", type=float, default=10) + +# argv = sys.argv[sys.argv.index("--") + 1 :] +args = parser.parse_args() + +# start blender env +import bpy +from mathutils import Vector, Matrix + +# render parameters +bpy.context.scene.render.engine = args.engine + +bpy.context.scene.render.resolution_x = args.resolution +bpy.context.scene.render.resolution_y = args.resolution +bpy.context.scene.render.resolution_percentage = 100 + +bpy.context.scene.render.film_transparent = True +bpy.context.scene.render.image_settings.file_format = "PNG" +bpy.context.scene.render.image_settings.color_mode = "RGBA" + +# use nodes system +bpy.context.scene.use_nodes = True +nodes = bpy.context.scene.node_tree.nodes +links = bpy.context.scene.node_tree.links +for n in nodes: + nodes.remove(n) +render_layers = nodes.new("CompositorNodeRLayers") + +# depth +# bpy.context.view_layer.use_pass_z = True +# depth_file_output = nodes.new(type="CompositorNodeOutputFile") +# depth_file_output.label = "Depth Output" +# depth_file_output.base_path = "" +# depth_file_output.file_slots[0].use_node_format = True +# depth_file_output.format.file_format = "OPEN_EXR" +# depth_file_output.format.color_depth = "16" +# links.new(render_layers.outputs["Depth"], depth_file_output.inputs[0]) + +# normal +bpy.context.view_layer.use_pass_normal = True +scale_node = nodes.new(type="CompositorNodeMixRGB") +scale_node.blend_type = "MULTIPLY" +scale_node.inputs[2].default_value = (0.5, 0.5, 0.5, 1) +links.new(render_layers.outputs["Normal"], scale_node.inputs[1]) +bias_node = nodes.new(type="CompositorNodeMixRGB") +bias_node.blend_type = "ADD" +bias_node.inputs[2].default_value = (0.5, 0.5, 0.5, 0) +links.new(scale_node.outputs[0], bias_node.inputs[1]) + +normal_file_output = nodes.new(type="CompositorNodeOutputFile") +normal_file_output.label = "Normal Output" +normal_file_output.base_path = "" +normal_file_output.file_slots[0].use_node_format = True +normal_file_output.format.file_format = "PNG" +normal_file_output.format.color_mode = "RGBA" +links.new(bias_node.outputs[0], normal_file_output.inputs[0]) + +# albedo +bpy.context.view_layer.use_pass_diffuse_color = True +alpha_albedo = nodes.new(type="CompositorNodeSetAlpha") +links.new(render_layers.outputs["DiffCol"], alpha_albedo.inputs["Image"]) +links.new(render_layers.outputs["Alpha"], alpha_albedo.inputs["Alpha"]) + +albedo_file_output = nodes.new(type="CompositorNodeOutputFile") +albedo_file_output.label = "Albedo Output" +albedo_file_output.base_path = "" +albedo_file_output.file_slots[0].use_node_format = True +albedo_file_output.format.file_format = "PNG" +albedo_file_output.format.color_mode = "RGBA" +links.new(alpha_albedo.outputs["Image"], albedo_file_output.inputs[0]) + +# NOTE: shamely, blender cannot render metallic and roughness as image... + +# EEVEE will use OpenGL, CYCLES will use GPU + CUDA +if bpy.context.scene.render.engine == 'CYCLES': + bpy.context.scene.cycles.device = "GPU" + bpy.context.scene.cycles.samples = 64 # 128 + bpy.context.scene.cycles.diffuse_bounces = 1 + bpy.context.scene.cycles.glossy_bounces = 1 + bpy.context.scene.cycles.transparent_max_bounces = 3 + bpy.context.scene.cycles.transmission_bounces = 3 + bpy.context.scene.cycles.filter_width = 0.01 + bpy.context.scene.cycles.use_denoising = True + bpy.context.scene.cycles.tile_size = 8192 + + bpy.context.preferences.addons["cycles"].preferences.get_devices() + + # set which GPU to use + for i, device in enumerate(bpy.context.preferences.addons["cycles"].preferences.devices): + if i == args.gpu: + device.use = True + print(f'[INFO] using device {i}: {device}') + else: + device.use = False + + + bpy.context.preferences.addons["cycles"].preferences.compute_device_type = "CUDA" # or "OPENCL" + +# set camera +cam = bpy.context.scene.objects["Camera"] +cam.data.angle = np.deg2rad(args.fovy) + +# make orbit camera +cam_constraint = cam.constraints.new(type="TRACK_TO") +cam_constraint.track_axis = "TRACK_NEGATIVE_Z" +cam_constraint.up_axis = "UP_Y" + + +def get_calibration_matrix_K_from_blender(camera): + f_in_mm = camera.data.lens + resolution_x_in_px = bpy.context.scene.render.resolution_x + resolution_y_in_px = bpy.context.scene.render.resolution_y + scale = bpy.context.scene.render.resolution_percentage / 100 + sensor_width_in_mm = camera.data.sensor_width + sensor_height_in_mm = camera.data.sensor_height + pixel_aspect_ratio = bpy.context.scene.render.pixel_aspect_x / bpy.context.scene.render.pixel_aspect_y + + if camera.data.sensor_fit == 'VERTICAL': + # the sensor height is fixed (sensor fit is horizontal), + # the sensor width is effectively changed with the pixel aspect ratio + s_u = resolution_x_in_px * scale / sensor_width_in_mm / pixel_aspect_ratio + s_v = resolution_y_in_px * scale / sensor_height_in_mm + else: # 'HORIZONTAL' and 'AUTO' + # the sensor width is fixed (sensor fit is horizontal), + # the sensor height is effectively changed with the pixel aspect ratio + s_u = resolution_x_in_px * scale / sensor_width_in_mm + s_v = resolution_y_in_px * scale * pixel_aspect_ratio / sensor_height_in_mm + + # Parameters of intrinsic calibration matrix K + alpha_u = f_in_mm * s_u + alpha_v = f_in_mm * s_u + u_0 = resolution_x_in_px * scale / 2 + v_0 = resolution_y_in_px * scale / 2 + skew = 0 # only use rectangular pixels + + K = np.asarray(((alpha_u, skew, u_0), + (0, alpha_v, v_0), + (0, 0, 1)),np.float32) + return K + + +def reset_scene(): + """Resets the scene to a clean state.""" + # delete everything that isn't part of a camera or a light + for obj in bpy.data.objects: + if obj.type not in {"CAMERA", "LIGHT"}: + bpy.data.objects.remove(obj, do_unlink=True) + # delete all the materials + for material in bpy.data.materials: + bpy.data.materials.remove(material, do_unlink=True) + # delete all the textures + for texture in bpy.data.textures: + bpy.data.textures.remove(texture, do_unlink=True) + # delete all the images + for image in bpy.data.images: + bpy.data.images.remove(image, do_unlink=True) + + +# load the glb model +def load_object(mesh): + """Loads a glb model into the scene.""" + if mesh.endswith(".glb"): + bpy.ops.import_scene.gltf(filepath=mesh, merge_vertices=True) + elif mesh.endswith(".fbx"): + bpy.ops.import_scene.fbx(filepath=mesh) + else: + raise ValueError(f"Unsupported file type: {mesh}") + + +def get_scene_meshes(): + for obj in bpy.context.scene.objects.values(): + if isinstance(obj.data, (bpy.types.Mesh)): + yield obj + +def get_scene_bbox(single_obj=None, ignore_matrix=False): + bbox_min = (math.inf,) * 3 + bbox_max = (-math.inf,) * 3 + found = False + for obj in get_scene_meshes() if single_obj is None else [single_obj]: + found = True + for coord in obj.bound_box: + coord = Vector(coord) + if not ignore_matrix: + coord = obj.matrix_world @ coord + bbox_min = tuple(min(x, y) for x, y in zip(bbox_min, coord)) + bbox_max = tuple(max(x, y) for x, y in zip(bbox_max, coord)) + if not found: + raise RuntimeError("no objects in scene to compute bounding box for") + return Vector(bbox_min), Vector(bbox_max) + +def get_scene_root_objects(): + for obj in bpy.context.scene.objects.values(): + if not obj.parent: + yield obj + +def normalize_scene(bound=0.9): + # bound: normalize to [-bound, bound] + + bbox_min, bbox_max = get_scene_bbox() + scale = 2 * bound / max(bbox_max - bbox_min) + for obj in get_scene_root_objects(): + obj.scale = obj.scale * scale + # Apply scale to matrix_world. + bpy.context.view_layer.update() + bbox_min, bbox_max = get_scene_bbox() + offset = - (bbox_min + bbox_max) / 2 + for obj in get_scene_root_objects(): + obj.matrix_world.translation += offset + bpy.ops.object.select_all(action="DESELECT") + + +def save_images(object_file: str) -> None: + + object_uid = os.path.basename(object_file).split(".")[0] + os.makedirs(os.path.join(args.outdir, object_uid), exist_ok=True) + + # clean scene + reset_scene() + # load the object + load_object(object_file) + # normalize objects to [-b, b]^3 + normalize_scene(bound=args.bound) + + # create orbit camera target + empty = bpy.data.objects.new("Empty", None) + bpy.context.scene.collection.objects.link(empty) + cam_constraint.target = empty + + # place cameras + if args.camera_type == 'fixed': + azimuths = (np.arange(args.num_images)/args.num_images*np.pi*2).astype(np.float32) + elevations = np.deg2rad(np.asarray([args.elevation] * args.num_images).astype(np.float32)) + elif args.camera_type == 'random': + azimuths = (np.arange(args.num_images) / args.num_images * np.pi * 2).astype(np.float32) + elevations = np.random.uniform(args.elevation_start, args.elevation_end, args.num_images) + elevations = np.deg2rad(elevations) + else: + raise NotImplementedError + + # get camera positions in blender coordinates + # NOTE: assume +x axis is the object front view (azimuth = 0) + x = args.radius * np.cos(azimuths) * np.cos(elevations) + y = args.radius * np.sin(azimuths) * np.cos(elevations) + z = - args.radius * np.sin(elevations) + cam_pos = np.stack([x,y,z], axis=-1) + + cam_poses = [] + + for i in tqdm.trange(args.num_images): + # set camera + cam.location = cam_pos[i] + bpy.context.view_layer.update() + + # pose matrix (c2w) + c2w = np.eye(4) + t, R = cam.matrix_world.decompose()[0:2] + c2w[:3, :3] = np.asarray(R.to_matrix()) # [3, 3] + c2w[:3, 3] = np.asarray(t) + + # blender to opengl + c2w_opengl = c2w.copy() + c2w_opengl[1] *= -1 + c2w_opengl[[1, 2]] = c2w_opengl[[2, 1]] + + cam_poses.append(c2w_opengl) + + # render image + render_file_path = os.path.join(args.outdir, object_uid, f"{i:03d}") + bpy.context.scene.render.filepath = render_file_path + # depth_file_output.file_slots[0].path = render_file_path + "_depth" + normal_file_output.file_slots[0].path = render_file_path + "_normal" + albedo_file_output.file_slots[0].path = render_file_path + "_albedo" + + # if os.path.exists(render_file_path): + # continue + + with stdout_redirected(): # suppress rendering logs + bpy.ops.render.render(write_still=True) + + # write camera + K = get_calibration_matrix_K_from_blender(cam) + cam_poses = np.stack(cam_poses, 0) + np.savez(os.path.join(args.outdir, object_uid, 'cameras.npz'), K=K, poses=cam_poses) + +if __name__ == "__main__": + save_images(args.mesh) +``` + diff --git a/docs/model/blender_tutorial.assets/image-20230305191231538.png b/docs/blender/blender_tutorial.assets/image-20230305191231538.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230305191231538.png rename to docs/blender/blender_tutorial.assets/image-20230305191231538.png diff --git a/docs/model/blender_tutorial.assets/image-20230305191530471.png b/docs/blender/blender_tutorial.assets/image-20230305191530471.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230305191530471.png rename to docs/blender/blender_tutorial.assets/image-20230305191530471.png diff --git a/docs/model/blender_tutorial.assets/image-20230305191716034.png b/docs/blender/blender_tutorial.assets/image-20230305191716034.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230305191716034.png rename to docs/blender/blender_tutorial.assets/image-20230305191716034.png diff --git a/docs/model/blender_tutorial.assets/image-20230521220540588.png b/docs/blender/blender_tutorial.assets/image-20230521220540588.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230521220540588.png rename to docs/blender/blender_tutorial.assets/image-20230521220540588.png diff --git a/docs/model/blender_tutorial.assets/image-20230522151233612.png b/docs/blender/blender_tutorial.assets/image-20230522151233612.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230522151233612.png rename to docs/blender/blender_tutorial.assets/image-20230522151233612.png diff --git a/docs/model/blender_tutorial.assets/image-20230522160717435.png b/docs/blender/blender_tutorial.assets/image-20230522160717435.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230522160717435.png rename to docs/blender/blender_tutorial.assets/image-20230522160717435.png diff --git a/docs/model/blender_tutorial.assets/image-20230817121854933.png b/docs/blender/blender_tutorial.assets/image-20230817121854933.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230817121854933.png rename to docs/blender/blender_tutorial.assets/image-20230817121854933.png diff --git a/docs/model/blender_tutorial.assets/image-20230817121902473.png b/docs/blender/blender_tutorial.assets/image-20230817121902473.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230817121902473.png rename to docs/blender/blender_tutorial.assets/image-20230817121902473.png diff --git a/docs/model/blender_tutorial.assets/image-20230817121909364.png b/docs/blender/blender_tutorial.assets/image-20230817121909364.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230817121909364.png rename to docs/blender/blender_tutorial.assets/image-20230817121909364.png diff --git a/docs/model/blender_tutorial.assets/image-20230914130930948.png b/docs/blender/blender_tutorial.assets/image-20230914130930948.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230914130930948.png rename to docs/blender/blender_tutorial.assets/image-20230914130930948.png diff --git a/docs/model/blender_tutorial.assets/image-20230914131227283.png b/docs/blender/blender_tutorial.assets/image-20230914131227283.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230914131227283.png rename to docs/blender/blender_tutorial.assets/image-20230914131227283.png diff --git a/docs/model/blender_tutorial.assets/image-20230914131413554.png b/docs/blender/blender_tutorial.assets/image-20230914131413554.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230914131413554.png rename to docs/blender/blender_tutorial.assets/image-20230914131413554.png diff --git a/docs/model/blender_tutorial.assets/image-20230914131605802.png b/docs/blender/blender_tutorial.assets/image-20230914131605802.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230914131605802.png rename to docs/blender/blender_tutorial.assets/image-20230914131605802.png diff --git a/docs/model/blender_tutorial.assets/image-20230914132030562.png b/docs/blender/blender_tutorial.assets/image-20230914132030562.png similarity index 100% rename from docs/model/blender_tutorial.assets/image-20230914132030562.png rename to docs/blender/blender_tutorial.assets/image-20230914132030562.png diff --git a/docs/model/blender_tutorial.md b/docs/blender/blender_tutorial.md similarity index 100% rename from docs/model/blender_tutorial.md rename to docs/blender/blender_tutorial.md diff --git a/docs/index.md b/docs/index.md index 44cd612fb..7f516d8b8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,23 +4,23 @@ [kiui](https://kiui.moe/)'s notebook. ## Recent Updates -- [sphinx_doc.md](python\sphinx_doc/)
2024-01-11 23:59:04.735043
-- [basics.md](godot\basics/)
2024-01-07 13:19:24.855045
-- [rebuttals.md](writings\rebuttals/)
2024-01-07 13:19:24.838534
-- [review.md](writings\review/)
2024-01-07 13:19:24.838534
-- [societal_impact.md](writings\societal_impact/)
2024-01-07 13:19:24.838534
-- [web_overview.md](web\web_overview/)
2024-01-07 13:19:24.837537
-- [xpath.md](web\scrape\xpath/)
2024-01-07 13:19:24.837537
-- [cuda.md](windows\cuda/)
2024-01-07 13:19:24.837537
-- [examples.md](writings\examples/)
2024-01-07 13:19:24.837537
-- [pitfalls.md](writings\pitfalls/)
2024-01-07 13:19:24.837537
-- [trojan-go.md](web\proxy\trojan-go/)
2024-01-07 13:19:24.836251
-- [trojan.md](web\proxy\trojan/)
2024-01-07 13:19:24.836251
-- [rsshub.md](web\rss\rsshub/)
2024-01-07 13:19:24.836251
-- [ttrss.md](web\rss\ttrss/)
2024-01-07 13:19:24.836251
-- [grab.md](web\scrape\grab/)
2024-01-07 13:19:24.836251
-- [concepts.md](web\proxy\concepts/)
2024-01-07 13:19:24.835251
-- [frp_reverse_proxy.md](web\proxy\frp_reverse_proxy/)
2024-01-07 13:19:24.835251
-- [set_usual_apps_proxy.md](web\proxy\set_usual_apps_proxy/)
2024-01-07 13:19:24.835251
-- [ss.md](web\proxy\ss/)
2024-01-07 13:19:24.835251
-- [ip.md](web\ip/)
2024-01-07 13:19:24.834251
+- [pypi_publish.md](python\pypi_publish/)
2024-01-12 15:31:40.094346
+- [blender_script.md](blender\blender_script/)
2024-01-12 15:31:38.915473
+- [mkdocs.md](python\mkdocs/)
2023-12-29 14:32:16.436796
+- [trojan.md](web\proxy\trojan/)
2023-12-29 14:29:49.998239
+- [frp_reverse_proxy.md](web\proxy\frp_reverse_proxy/)
2023-12-29 14:29:49.994726
+- [http.md](web\frontend\html\http/)
2023-12-29 14:29:49.993438
+- [css.md](web\frontend\html\css/)
2023-12-29 14:29:49.990813
+- [html.md](web\frontend\html\html/)
2023-12-29 14:29:49.990813
+- [react-basics.md](web\frontend\react-basics/)
2023-12-29 14:29:49.986814
+- [nodejs_basics.md](web\frontend\nodejs_basics/)
2023-12-29 14:29:49.985230
+- [javascript_basics.md](web\frontend\javascript_basics/)
2023-12-29 14:29:49.984229
+- [javascript_advanced.md](web\frontend\javascript_advanced/)
2023-12-29 14:29:49.982230
+- [nginx.md](web\deploy\nginx/)
2023-12-29 14:29:49.978239
+- [sql_sqlite.md](web\backend\sql_sqlite/)
2023-12-29 14:29:49.977733
+- [python_flask.md](web\backend\python_flask/)
2023-12-29 14:29:49.973222
+- [sql_mysql.md](web\backend\sql_mysql/)
2023-12-29 14:29:49.973222
+- [transformation.md](vision\transformation/)
2023-12-29 14:29:49.969222
+- [golang_basics.md](web\backend\golang_basics/)
2023-12-29 14:29:49.969222
+- [golang_gin.md](web\backend\golang_gin/)
2023-12-29 14:29:49.969222
+- [image_processing.md](vision\image_processing/)
2023-12-29 14:29:49.968036
diff --git a/docs/model/blender_script.md b/docs/model/blender_script.md deleted file mode 100644 index 5168c106c..000000000 --- a/docs/model/blender_script.md +++ /dev/null @@ -1,52 +0,0 @@ -# blender script - -logs are printed to the terminal console, use `window --> toggle system console` to open it. - -### auto import many objects - -assume you have a folder with lots of obj models (each in a sub-folder): - -```python -import bpy -import os - -context = bpy.context -dir = r"C:\Users\hawke\Downloads\bear" - -obj_dirs = os.listdir(dir) - -for i, name in enumerate(obj_dirs): - obj_dir = os.path.join(dir, name) - files = os.listdir(obj_dir) - for file in files: - if not file.endswith('.obj'): continue - path = os.path.join(obj_dir, file) - - print(f'[INFO] {i} process {file}') - - bpy.ops.import_scene.obj(filepath=path, filter_glob="*.obj;*.mtl;*.png") # also load png textures - obj = context.selected_objects[0] - - # location (10 in a row) - h, w = i % 10 - 5, i // 10 - 5 - obj.location = (h, w, 0) -``` - -### locate objects in current scene - -```python -import bpy - -# all objects -bpy.data.objects - -# access by index -bpy.data.objects[0] - -# access by name -bpy.data.objects['Light'] - -# modify property -bpy.data.objects['Light'].location = (0, 0, 0) -``` - diff --git a/docs/python/pypi_publish.assets/image-20240112152907640.png b/docs/python/pypi_publish.assets/image-20240112152907640.png new file mode 100644 index 0000000000000000000000000000000000000000..83b0308580d29a43542bd995a3060afdfa794bba GIT binary patch literal 19569 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 Publishing`, and add Github as a new publisher: + +![image-20240112152907640](pypi_publish.assets/image-20240112152907640.png) +Then create workflows at `.github/workflows/pypi-publish.yml`: + +```yaml name: Upload Python Package on: - release: + release: # publish when releasing a new tag on github. types: [created] + workflow_dispatch: # allow you to manually trigger this workflow from github. jobs: deploy: runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/kiui/ + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: - python-version: '3.x' - - name: Install dependencies + python-version: '3.10' + # prepare distributions in dist/ + - name: Install dependencies and Build run: | python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | + pip install setuptools wheel python setup.py sdist bdist_wheel - twine upload dist/* + # publish by trusted publishers (need to first setup in pypi.org projects-manage-publishing!) + # ref: https://github.com/marketplace/actions/pypi-publish + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 ``` -Create secrets at repository. - When you want to publish a new version, navigate to **release** and release a version tag. Then github actions will build and publish current repository state automatically!