regex fix, more primitives
This commit is contained in:
@ -17,4 +17,4 @@ async def _(args: Message = CommandArg()):
|
||||
buffer.seek(0)
|
||||
await cmd_marchtoy.send(await UniMessage().image(raw=buffer).export())
|
||||
except Exception as e:
|
||||
await cmd_marchtoy.send(await UniMessage.text(f"发生了错误: {e}").export())
|
||||
await cmd_marchtoy.send(await UniMessage.text(f"cannot render: {e}").export())
|
||||
@ -13,15 +13,9 @@ example:
|
||||
march cube.pos(0, 0, 0).color(red) cam(1.0).pos(1, 1, 1).lookat(0, 0, 0)
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
import numpy as np
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from konabot.plugins.marchtoy.obj import Object, Camera, OBJECT_ENTRIES
|
||||
from konabot.plugins.marchtoy.op import OPERATION_ENTRIES
|
||||
@dataclass
|
||||
class Command:
|
||||
id: str
|
||||
@ -29,7 +23,7 @@ class Command:
|
||||
|
||||
|
||||
class CommandChainParser:
|
||||
CHAIN_PATTERN = r"^[a-zA-Z]+(\([^(]*\))?(\.[a-zA-Z]+(\([^(]*\))?)*"
|
||||
CHAIN_PATTERN = r"^(([a-zA-Z0-9.]+(?:\(([^()]*|(?1)+)(\s*\,\s*(?1))*\))?)(\.(?1))*)"
|
||||
|
||||
def __init__(self, _command_chain: str) -> None:
|
||||
self.command_chain = _command_chain
|
||||
@ -46,7 +40,7 @@ class CommandChainParser:
|
||||
|
||||
|
||||
class CommandParser:
|
||||
CMD_PATTERN = r"^[a-zA-Z]+(\([^(]*\))?"
|
||||
CMD_PATTERN = r"^(([a-zA-Z0-9.]+(?:\(([^()]*|(?1)+)(\s*\,\s*(?1))*\))?))"
|
||||
ID_PATTERN = r"^[a-zA-Z]+(?=\(|\.|$)"
|
||||
|
||||
def __init__(self, _command: str) -> None:
|
||||
@ -61,88 +55,8 @@ class CommandParser:
|
||||
if cmd_id_qry := re.match(CommandParser.ID_PATTERN, cmd):
|
||||
cmd_id = cmd_id_qry[0]
|
||||
self.command = self.command[len(cmd) + 1 :]
|
||||
cmd_args = cmd[len(cmd_id) + 1 : -1].replace(" ", "").split(",")
|
||||
while "" in cmd_args:
|
||||
cmd_args.remove("")
|
||||
cmd_args = cmd[len(cmd_id) + 1 : -1]#.replace(" ", "").split(",")
|
||||
# while "" in cmd_args:
|
||||
# cmd_args.remove("")
|
||||
return Command(cmd_id, cmd_args)
|
||||
raise StopIteration
|
||||
|
||||
class Scene:
|
||||
def __init__(self, _instruction: str) -> None:
|
||||
self.canvas_objs: list[tuple[Object, str]] = []
|
||||
self.camera: Camera = Camera()
|
||||
for raw_cmd in CommandChainParser(_instruction):
|
||||
cmd_queue = CommandParser(raw_cmd)
|
||||
cmd_obj = next(cmd_queue)
|
||||
obj_id, obj_args = cmd_obj.id, cmd_obj.args
|
||||
obj_instance: Optional[Object] = None
|
||||
if obj_id in OBJECT_ENTRIES:
|
||||
obj_cls = OBJECT_ENTRIES[obj_id]
|
||||
if not issubclass(obj_cls, Object):
|
||||
raise Exception(f"{obj_id} is not a subclass of Object.")
|
||||
obj_instance = obj_cls()
|
||||
try:
|
||||
if len(obj_args) != 0:
|
||||
obj_instance.parse_args(obj_args)
|
||||
except Exception as e:
|
||||
raise Exception(
|
||||
f"object {obj_id} failed to parse args passed in: {obj_args}"
|
||||
) from e
|
||||
else:
|
||||
raise Exception(f"{obj_id} is not a valid object type.")
|
||||
|
||||
if obj_instance != None:
|
||||
for cmd in cmd_queue:
|
||||
op_id, op_args = cmd.id, cmd.args
|
||||
if op_id in OPERATION_ENTRIES:
|
||||
op_func = OPERATION_ENTRIES[op_id]
|
||||
if not callable(op_func):
|
||||
raise Exception(f"{op_id} is not a valid operation.")
|
||||
op_func(obj_instance, op_args)
|
||||
else:
|
||||
raise Exception(f"{op_id} is not a valid operation.")
|
||||
|
||||
try:
|
||||
sdf_block = obj_instance.sdf_block_glsl()
|
||||
self.canvas_objs.append((obj_instance, sdf_block))
|
||||
except:
|
||||
if type(obj_instance) == Camera:
|
||||
self.camera = obj_instance
|
||||
|
||||
def __str__(self) -> str:
|
||||
return ", ".join([str(type(obj)) for obj in self.canvas_objs])
|
||||
|
||||
def compile(self, fs_src: str = "frag.glsl") -> str:
|
||||
PATH = pathlib.Path(__file__).parent / "shaders" / fs_src
|
||||
with PATH.open(encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
sdf_block: str = ""
|
||||
color_block: str = ""
|
||||
|
||||
index = 0
|
||||
for canvas_item in self.canvas_objs:
|
||||
obj, sdf_expr = canvas_item
|
||||
sdf_block += f"float sd{index} = {sdf_expr};"
|
||||
sdf_block += f"if(sd{index} < qry.value)"
|
||||
sdf_block += "{" + f"qry.value = sd{index}; qry.obj_id = {index}; " + "}\n"
|
||||
color = obj.texture.color
|
||||
color_block += (
|
||||
f"if(obj_id == {index}) return vec4("
|
||||
f"{color[0]}, {color[1]}, {color[2]}, {color[3]});\n"
|
||||
)
|
||||
index += 1
|
||||
|
||||
content = content.replace("<SDF_BLOCK>", sdf_block)
|
||||
content = content.replace("<COLOR_BLOCK>", color_block)
|
||||
|
||||
cam_pos = self.camera.transform.t[0:3, 3]
|
||||
cam_focus = self.camera.focus
|
||||
cam_dir = self.camera.transform.t @ np.array((0.0, 0.0, -1.0, 0.0))
|
||||
content = content.replace(
|
||||
"<CAM_POS>", f"{cam_pos[0]}, {cam_pos[1]}, {cam_pos[2]}"
|
||||
)
|
||||
content = content.replace("<CAM_FOCUS>", str(cam_focus))
|
||||
content = content.replace(
|
||||
"<CAM_DIR>", f"{cam_dir[0]}, {cam_dir[1]}, {cam_dir[2]}"
|
||||
)
|
||||
return content
|
||||
|
||||
@ -5,7 +5,7 @@ import pathlib
|
||||
import moderngl
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from konabot.plugins.marchtoy.command import Scene
|
||||
from konabot.plugins.marchtoy.scene import Scene
|
||||
|
||||
async def render(command: str, res: tuple[int, int]):
|
||||
fs = Scene(command).compile()
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import numpy as np
|
||||
from konabot.plugins.marchtoy.texture import Texture
|
||||
from konabot.plugins.marchtoy.utilities import ArgParser, Formatter
|
||||
from nonebot import logger
|
||||
from typing import Optional
|
||||
|
||||
OBJECT_ENTRIES = {}
|
||||
|
||||
@ -80,7 +82,7 @@ class Object:
|
||||
self.transform: Transform = Transform()
|
||||
self.texture: Texture = Texture()
|
||||
|
||||
def parse_args(self, args: list[str]):
|
||||
def parse_args(self, args: str):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_transformed(self, p: np.ndarray) -> np.ndarray:
|
||||
@ -94,9 +96,6 @@ class Object:
|
||||
def sdf_block_glsl(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def sdf(self, _p: np.ndarray) -> float:
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
@make_obj("cube", "box")
|
||||
class Cube(Object):
|
||||
@ -104,34 +103,24 @@ class Cube(Object):
|
||||
super().__init__()
|
||||
self.size = _size
|
||||
|
||||
def parse_args(self, args: list[str]):
|
||||
self.size = ArgParser.as_vec3(args)
|
||||
def parse_args(self, args: str):
|
||||
self.size = ArgParser.as_vec3(ArgParser.to_params(args))
|
||||
|
||||
def sdf_block_glsl(self) -> str:
|
||||
return f"sdCube({self.transform.p_expr()}, vec3({self.size[0]}, {self.size[1]}, {self.size[2]}))"
|
||||
|
||||
def sdf(self, _p):
|
||||
p = self.get_transformed(_p)
|
||||
p = np.abs(p) - self.size
|
||||
return np.linalg.norm(np.maximum(p, 0.0)) + np.minimum(np.max(p), 0.0)
|
||||
|
||||
|
||||
@make_obj("sphere", "ball")
|
||||
class Sphere(Object):
|
||||
def __init__(self, _radius: float = 1.0) -> None:
|
||||
super().__init__()
|
||||
self.radius = _radius
|
||||
|
||||
def parse_args(self, args: list[str]):
|
||||
self.radius = ArgParser.as_float(args)
|
||||
def parse_args(self, args: str):
|
||||
self.radius = ArgParser.as_float(ArgParser.to_params(args))
|
||||
|
||||
def sdf_block_glsl(self) -> str:
|
||||
return f"sdSphere({self.transform.p_expr()}, {self.radius})"
|
||||
|
||||
def sdf(self, _p):
|
||||
p = self.get_transformed(_p)
|
||||
return np.linalg.norm(p) - self.radius
|
||||
|
||||
|
||||
@make_obj("cylinder", "cyl")
|
||||
class Cylinder(Object):
|
||||
@ -140,8 +129,8 @@ class Cylinder(Object):
|
||||
self.radius = _radius
|
||||
self.height = _height
|
||||
|
||||
def parse_args(self, args: list[str]):
|
||||
param = ArgParser.as_vec2(args)
|
||||
def parse_args(self, args: str):
|
||||
param = ArgParser.as_vec2(ArgParser.to_params(args))
|
||||
self.radius = param[0]
|
||||
self.height = param[1]
|
||||
|
||||
@ -150,16 +139,6 @@ class Cylinder(Object):
|
||||
f"sdCappedCylinder({self.transform.p_expr()}, {self.radius}, {self.height})"
|
||||
)
|
||||
|
||||
def sdf(self, _p):
|
||||
p = self.get_transformed(_p)
|
||||
d = np.abs(np.array([np.linalg.norm(p[[0, 2]]), p[1]])) - np.array(
|
||||
[self.radius, self.height]
|
||||
)
|
||||
return np.minimum(np.maximum(d[0], d[1]), 0.0) + np.linalg.norm(
|
||||
np.maximum(d, 0.0)
|
||||
)
|
||||
|
||||
|
||||
@make_obj("torus")
|
||||
class Torus(Object):
|
||||
def __init__(self, _r1: float = 1.0, _r2: float = 0.4) -> None:
|
||||
@ -167,19 +146,14 @@ class Torus(Object):
|
||||
self.r1 = _r1
|
||||
self.r2 = _r2
|
||||
|
||||
def parse_args(self, args: list[str]):
|
||||
param = ArgParser.as_vec2(args)
|
||||
def parse_args(self, args: str):
|
||||
param = ArgParser.as_vec2(ArgParser.to_params(args))
|
||||
self.r1 = param[0]
|
||||
self.r2 = param[1]
|
||||
|
||||
def sdf_block_glsl(self) -> str:
|
||||
return f"sdTorus({self.transform.p_expr()}, vec2({self.r1}, {self.r2}))"
|
||||
|
||||
def sdf(self, _p):
|
||||
p = self.get_transformed(_p)
|
||||
q = np.array([np.linalg.norm(p[[0, 2]]) - self.r1, p[1]])
|
||||
return np.linalg.norm(q) - self.r2
|
||||
|
||||
|
||||
@make_obj("capsule", "pill")
|
||||
class Capsule(Object):
|
||||
@ -188,19 +162,32 @@ class Capsule(Object):
|
||||
self.h = _h
|
||||
self.r = _r
|
||||
|
||||
def parse_args(self, args: list[str]):
|
||||
param = ArgParser.as_vec2(args)
|
||||
def parse_args(self, args: str):
|
||||
param = ArgParser.as_vec2(ArgParser.to_params(args))
|
||||
self.h = param[0]
|
||||
self.r = param[1]
|
||||
|
||||
def sdf_block_glsl(self) -> str:
|
||||
return f"sdVerticalCapsule({self.transform.p_expr()}, {self.h}, {self.r})"
|
||||
|
||||
def sdf(self, _p):
|
||||
p = self.get_transformed(_p)
|
||||
p[1] -= np.clip(p[1], 0.0, self.h)
|
||||
return np.linalg.norm(p) - self.r
|
||||
@make_obj("smoothed", "mix")
|
||||
class Smoothed(Object):
|
||||
def __init__(self, _a = None, _b = None, _k: float = 0.25):
|
||||
super().__init__()
|
||||
self.a_index: int = _a
|
||||
self.b: Optional[Object] = _b
|
||||
self.k: float = _k
|
||||
|
||||
def parse_args(self, args: str):
|
||||
# from konabot.plugins.marchtoy.command import CommandChainParser
|
||||
try:
|
||||
raise Exception
|
||||
except Exception as e:
|
||||
raise Exception(f"cannot build smoothed object over {args}: {e}")
|
||||
|
||||
|
||||
def sdf_block_glsl(self):
|
||||
return f"smin({self.a.sdf_block_glsl()}, {self.b.sdf_block_glsl()}, {self.k})"
|
||||
|
||||
@make_obj("camera", "cam")
|
||||
class Camera(Object):
|
||||
@ -209,5 +196,6 @@ class Camera(Object):
|
||||
self.focus = _focus
|
||||
self.transform.translate(5.0, 5.0, 5.0).lookat(0.0, 0.0, 0.0)
|
||||
|
||||
def parse_args(self, args: list[str]):
|
||||
self.focus = ArgParser.as_float(args)
|
||||
def parse_args(self, args: str):
|
||||
self.focus = ArgParser.as_float(ArgParser.to_params(args))
|
||||
|
||||
|
||||
88
konabot/plugins/marchtoy/scene.py
Normal file
88
konabot/plugins/marchtoy/scene.py
Normal file
@ -0,0 +1,88 @@
|
||||
from typing import Optional
|
||||
import pathlib
|
||||
import numpy as np
|
||||
from nonebot import logger
|
||||
|
||||
class Scene:
|
||||
def __init__(self, _instruction: str) -> None:
|
||||
from konabot.plugins.marchtoy.command import CommandChainParser, CommandParser
|
||||
from konabot.plugins.marchtoy.op import OPERATION_ENTRIES
|
||||
from konabot.plugins.marchtoy.obj import Object, Camera, OBJECT_ENTRIES
|
||||
logger.info(f"building scene: {_instruction}")
|
||||
self.canvas_objs: list[tuple[Object, str]] = []
|
||||
self.camera: Camera = Camera()
|
||||
for raw_cmd in CommandChainParser(_instruction):
|
||||
cmd_queue = CommandParser(raw_cmd)
|
||||
cmd_obj = next(cmd_queue)
|
||||
obj_id, obj_args = cmd_obj.id, cmd_obj.args
|
||||
obj_instance: Optional[Object] = None
|
||||
if obj_id in OBJECT_ENTRIES:
|
||||
obj_cls = OBJECT_ENTRIES[obj_id]
|
||||
if not issubclass(obj_cls, Object):
|
||||
raise Exception(f"{obj_id} is not a subclass of Object.")
|
||||
obj_instance = obj_cls()
|
||||
try:
|
||||
if len(obj_args) != 0:
|
||||
obj_instance.parse_args(obj_args)
|
||||
except Exception as e:
|
||||
raise Exception(
|
||||
f"object {obj_id} failed to parse args passed in: {obj_args}.\n{e}"
|
||||
) from e
|
||||
else:
|
||||
raise Exception(f"{obj_id} is not a valid object type.")
|
||||
|
||||
if obj_instance != None:
|
||||
for cmd in cmd_queue:
|
||||
op_id, op_args = cmd.id, cmd.args
|
||||
if op_id in OPERATION_ENTRIES:
|
||||
op_func = OPERATION_ENTRIES[op_id]
|
||||
if not callable(op_func):
|
||||
raise Exception(f"{op_id} is not a valid operation.")
|
||||
op_func(obj_instance, op_args)
|
||||
else:
|
||||
raise Exception(f"{op_id} is not a valid operation.")
|
||||
|
||||
try:
|
||||
sdf_block = obj_instance.sdf_block_glsl()
|
||||
self.canvas_objs.append((obj_instance, sdf_block))
|
||||
except:
|
||||
if type(obj_instance) == Camera:
|
||||
self.camera = obj_instance
|
||||
|
||||
def __str__(self) -> str:
|
||||
return ", ".join([str(type(obj)) for obj in self.canvas_objs])
|
||||
|
||||
def compile(self, fs_src: str = "frag.glsl") -> str:
|
||||
PATH = pathlib.Path(__file__).parent / "shaders" / fs_src
|
||||
with PATH.open(encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
sdf_block: str = ""
|
||||
color_block: str = ""
|
||||
|
||||
index = 0
|
||||
for canvas_item in self.canvas_objs:
|
||||
obj, sdf_expr = canvas_item
|
||||
sdf_block += f"float sd{index} = {sdf_expr};"
|
||||
sdf_block += f"if(sd{index} < qry.value)"
|
||||
sdf_block += "{" + f"qry.value = sd{index}; qry.obj_id = {index}; " + "}\n"
|
||||
color = obj.texture.color
|
||||
color_block += (
|
||||
f"if(obj_id == {index}) return vec4("
|
||||
f"{color[0]}, {color[1]}, {color[2]}, {color[3]});\n"
|
||||
)
|
||||
index += 1
|
||||
|
||||
content = content.replace("<SDF_BLOCK>", sdf_block)
|
||||
content = content.replace("<COLOR_BLOCK>", color_block)
|
||||
|
||||
cam_pos = self.camera.transform.t[0:3, 3]
|
||||
cam_focus = self.camera.focus
|
||||
cam_dir = self.camera.transform.t @ np.array((0.0, 0.0, -1.0, 0.0))
|
||||
content = content.replace(
|
||||
"<CAM_POS>", f"{cam_pos[0]}, {cam_pos[1]}, {cam_pos[2]}"
|
||||
)
|
||||
content = content.replace("<CAM_FOCUS>", str(cam_focus))
|
||||
content = content.replace(
|
||||
"<CAM_DIR>", f"{cam_dir[0]}, {cam_dir[1]}, {cam_dir[2]}"
|
||||
)
|
||||
return content
|
||||
@ -33,7 +33,12 @@ float sdTorus( vec3 p, vec2 t )
|
||||
vec2 q = vec2(length(p.xz)-t.x,p.y);
|
||||
return length(q)-t.y;
|
||||
}
|
||||
|
||||
float smin( float a, float b, float k )
|
||||
{
|
||||
k *= 1.0;
|
||||
float r = exp2(-a/k) + exp2(-b/k);
|
||||
return -k*log2(r);
|
||||
}
|
||||
sdQuery sd(vec3 p) {
|
||||
sdQuery qry;
|
||||
qry.value = 100000000.0;
|
||||
|
||||
@ -26,6 +26,17 @@ class Formatter:
|
||||
TODO: 除零出现 nan 情况的单独处理
|
||||
"""
|
||||
class ArgParser:
|
||||
@staticmethod
|
||||
def to_params(args: str, delim: str = ',') -> list[str]:
|
||||
_params = args.replace(" ", "").split(delim)
|
||||
params: list[str] = []
|
||||
# while 还是太过于令人生畏了
|
||||
for param in _params:
|
||||
if param == "":
|
||||
continue
|
||||
params.append(param)
|
||||
return paramss
|
||||
|
||||
@staticmethod
|
||||
def as_float(args: list[str], default: float = 0.0) -> float:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user