import math
from itertools import cycle, islice
from typing import List, Union, Tuple, Dict, Callable
import numpy as np
from trescope import DisplayOutputManager, DisplayOutputDesc, FileOutputManager, FileOutputDesc, Trescope
from trescope.config import (Mesh3DConfig, Wireframe3DConfig)
def __displayOutput(rows: int, columns: int, scale: float, row: int, column: int, output_ids: Union[None, List[str]]) -> DisplayOutputDesc:
id_ = int(row * columns + column)
id_ = id_ if output_ids is None else output_ids[id_]
startRow: int = round(row / rows / (scale / (rows / columns)))
startColumn: int = round(column / columns / scale)
span: int = math.ceil(1 / (columns * scale))
return DisplayOutputDesc().id(id_).startAt(startRow, startColumn).withSpan(span, span)
[docs]def simpleDisplayOutputs(rows: int, columns: int, output_ids: Union[None, List[str]] = None) -> DisplayOutputManager:
"""
Convenient method to specify uniform , square display output .
:param rows: row number
:param columns: column number
:param output_ids: ID of output , default `None`
:return: :py:mod:`trescope.DisplayOutputManager`
"""
scale = 1 / columns
manager = DisplayOutputManager(int(math.ceil(rows / columns / scale)), int(math.ceil(1 / scale)))
for row in range(rows):
for column in range(columns): manager.add(__displayOutput(rows, columns, scale, row, column, output_ids))
return manager
[docs]def simpleFileOutputs(directory: str, fileNames: List[Union[str, int]], widthPixel: int = 640, heightPixel: int = 480) -> FileOutputManager:
"""
Convenient method to specify file output.
:param directory: directory to generate files
:param fileNames: names of file
:param widthPixel: width of image file in pixel
:param heightPixel: height of image file in pixel
:return: :py:mod:`trescope.FileOutputManager`
"""
manager = FileOutputManager(directory, widthPixel, heightPixel)
for fileName in fileNames: manager.add(FileOutputDesc().id(fileName))
return manager
def random_color(alpha: int = 0xff) -> int:
import numpy as np
return alpha << 24 | np.random.randint(low=0, high=0xffffff)
__colors = [
"#000000", "#FFFF00", "#1CE6FF", "#FF34FF", "#FF4A46", "#008941", "#006FA6", "#A30059",
"#FFDBE5", "#7A4900", "#0000A6", "#63FFAC", "#B79762", "#004D43", "#8FB0FF", "#997D87",
"#5A0007", "#809693", "#FEFFE6", "#1B4400", "#4FC601", "#3B5DFF", "#4A3B53", "#FF2F80",
"#61615A", "#BA0900", "#6B7900", "#00C2A0", "#FFAA92", "#FF90C9", "#B903AA", "#D16100",
"#DDEFFF", "#000035", "#7B4F4B", "#A1C299", "#300018", "#0AA6D8", "#013349", "#00846F",
"#372101", "#FFB500", "#C2FFED", "#A079BF", "#CC0744", "#C0B9B2", "#C2FF99", "#001E09",
"#00489C", "#6F0062", "#0CBD66", "#EEC3FF", "#456D75", "#B77B68", "#7A87A1", "#788D66",
"#885578", "#FAD09F", "#FF8A9A", "#D157A0", "#BEC459", "#456648", "#0086ED", "#886F4C",
"#34362D", "#B4A8BD", "#00A6AA", "#452C2C", "#636375", "#A3C8C9", "#FF913F", "#938A81",
"#575329", "#00FECF", "#B05B6F", "#8CD0FF", "#3B9700", "#04F757", "#C8A1A1", "#1E6E00",
"#7900D7", "#A77500", "#6367A9", "#A05837", "#6B002C", "#772600", "#D790FF", "#9B9700",
"#549E79", "#FFF69F", "#201625", "#72418F", "#BC23FF", "#99ADC0", "#3A2465", "#922329",
"#5B4534", "#FDE8DC", "#404E55", "#0089A3", "#CB7E98", "#A4E804", "#324E72", "#6A3A4C",
"#83AB58", "#001C1E", "#D1F7CE", "#004B28", "#C8D0F6", "#A3A489", "#806C66", "#222800",
"#BF5650", "#E83000", "#66796D", "#DA007C", "#FF1A59", "#8ADBB4", "#1E0200", "#5B4E51",
"#C895C5", "#320033", "#FF6832", "#66E1D3", "#CFCDAC", "#D0AC94", "#7ED379", "#012C58"
][:32]
__colors = list(map(lambda color: int(f'0xFF{color[1:]}', 16), __colors))
[docs]def color_from_label(label: int, max_label: int = 100) -> int:
"""
Generate color by integer .
:param label: int label
:param max_label: max label number
:return: color
"""
return list(islice(cycle(__colors), int(max_label + 1)))[label]
def __extrude_along_y(contour_xz: np.ndarray, y1: float, y2: float) -> Tuple[np.ndarray, np.ndarray]:
_, num = contour_xz.shape
contour_xyz_bottom = np.array([
contour_xz[0],
[y1] * num,
contour_xz[1],
])
contour_xyz_top = np.array([
contour_xz[0],
[y2] * num,
contour_xz[1],
])
vertices = np.hstack([contour_xyz_bottom, contour_xyz_top])
faces = np.array([0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7, 3, 3, 7, 4, 3, 4, 0,
0, 1, 2, 0, 2, 3,
4, 5, 6, 4, 6, 7], np.int).reshape((-1, 3)).T
return vertices, faces
def mesh_from_aabb3d(min_xyz: Union[np.ndarray, List[float],], max_xyz: Union[np.ndarray, List[float]]) -> Tuple[
np.ndarray, np.ndarray]:
minx, miny, minz = min_xyz
maxx, maxy, maxz = max_xyz
return __extrude_along_y(np.array([
[minx, minz],
[maxx, minz],
[maxx, maxz],
[minx, maxz]
]).T, miny, maxz)
[docs]def visualize_front3d_mesh(output_id: Union[str, int], scene: Dict,
focus_meshes_fetcher: Union[Callable, None] = None, ignore_meshes_fetcher: Union[Callable, None] = None,
material_when_unavailable: Union[Callable, int] = 0xff000000):
"""
visualize_front3d_mesh(0, scene_dict,
focus_meshes_fetcher=lambda mesh_table: mesh_table[mesh_table['type'].isin(['Floor'])],
ignore_meshes_fetcher=lambda mesh_table: mesh_table[mesh_table['type'].isin(['WallOuter', 'WallInner'])])
:param output_id: output id
:param scene: scene
:param focus_meshes_fetcher: focus_meshes_fetcher
:param ignore_meshes_fetcher: ignore_meshes_fetcher
:param material_when_unavailable: material_when_unavailable
"""
import pandas as pd
def __create_mesh_table():
_ = {'uid': [], 'type': [], 'material': [], 'xyz': [], 'uv': [], 'normal': [], 'face': []}
for _mesh in scene['mesh']:
_['uid'].append(_mesh['uid'])
_['type'].append(_mesh['type'])
_['material'].append(_mesh['material'])
_['xyz'].append(np.array(_mesh['xyz']).reshape((-1, 3)).T)
_['uv'].append(np.array(_mesh['uv']).reshape((-1, 2)).T)
_['normal'].append(np.array(_mesh['normal']).reshape((-1, 3)).T)
_['face'].append(np.array(_mesh['faces'], np.int).reshape((-1, 3)).T)
return pd.DataFrame(_)
def __create_material_table():
def compatible_texture(_texture):
# return {'type': 'url', 'value': _texture} if isinstance(_texture, str) else _texture
return _texture['value'] if isinstance(_texture, dict) else _texture
def compatible_color(rgba_or_rgb_array):
r, g, b, a = rgba_or_rgb_array if 4 == len(rgba_or_rgb_array) else [*rgba_or_rgb_array, 255]
return a << 24 | r << 16 | g << 8 | b
def compatible_color_mode(_material):
return _material['colorMode'] if 'colorMode' in _material else ('color' if _material['useColor'] else 'texture')
def compatible_uv_transform(_material):
return np.array(_material['UVTransform']).reshape(3, 3) if 'UVTransform' in _material else np.eye(3)
_ = {'uid': [], 'texture': [], 'color': [], 'colorMode': [], 'UVTransform': [], }
for _material in scene['material']:
_['uid'].append(_material['uid'])
try:
_['texture'].append(compatible_texture(_material['texture']))
_['color'].append(compatible_color(_material['color']))
_['colorMode'].append(compatible_color_mode(_material))
_['UVTransform'].append(compatible_uv_transform(_material))
except:
if isinstance(material_when_unavailable, int):
_['texture'].append('')
_['UVTransform'].append(np.eye(3))
_['color'].append(material_when_unavailable)
_['colorMode'].append('color')
else:
_m = material_when_unavailable(_material)
_['texture'].append(compatible_texture(_m['texture']))
_['UVTransform'].append(compatible_uv_transform(_m))
_['color'].append(compatible_color(_m['color']))
_['colorMode'].append(compatible_color_mode(_m))
return pd.DataFrame(_)
def __reduce_meshes(_meshes: pd.DataFrame):
t = _meshes.loc[:, ('uid', 'type', 'material', 'xyz', 'uv', 'normal', 'face', 'texture', 'color', 'colorMode', 'UVTransform')].values
_uid_all, _type_all, _, _xyz_all, _uv_all, _normal_all, _face_all, _, _, _, _UVTransform = t[0]
_uid_all, _type_all = [_uid_all], [_type_all]
_uv_all = __transform_uv(_uv_all, _UVTransform)
for _uid, _type, _, _xyz, _uv, _normal, _face, _, _, _, _UVTransform in t[1:]:
_face_all = np.hstack([_face_all, _face + _xyz_all.shape[1]])
_xyz_all = np.hstack([_xyz_all, _xyz])
_uv_all = np.hstack([_uv_all, __transform_uv(_uv, _UVTransform)])
_normal_all = np.hstack([_normal_all, _normal])
_uid_all += [_uid]
_type_all += [_type]
return _xyz_all, _uv_all, _normal_all, _face_all, set(_uid_all), set(_type_all)
def __transform_uv(_uv, matrix):
new_uv = np.matmul(matrix.T, np.vstack([_uv, [1] * _uv.shape[1]]))
return new_uv[(0, 1), :] / new_uv[2, :]
def __aggregate_mesh_by_material_texture(_meshes: pd.DataFrame):
enum_material_texture = set(_meshes[_meshes['colorMode'] == 'texture'].loc[:, 'texture'].values)
return [(_texture, __reduce_meshes(_meshes[_meshes['texture'] == _texture])) for _texture in enum_material_texture]
def __aggregate_mesh_by_material_color(_meshes: pd.DataFrame):
enum_material_color = set(_meshes[_meshes['colorMode'] == 'color'].loc[:, 'color'].values.tolist())
return [(_color, __reduce_meshes(_meshes[_meshes['color'] == _color])) for _color in enum_material_color]
def plot_mesh(_xyz, _uv, _texture, _color, _face, _color_mode, _name):
config = Mesh3DConfig().indices(*_face).name(_name).flatShading(True)
config = config.color(_color) if _color_mode == 'color' else config.texture(_texture, wrap=('REPEAT', 'REPEAT')).textureCoordinate(*_uv)
Trescope().selectOutput(output_id).plotMesh3D(*_xyz).withConfig(config)
mesh_table = __create_mesh_table()
material_table = __create_material_table()
mesh_material = mesh_table.join(material_table.set_index('uid'), on='material')
focus_meshes: pd.DataFrame = mesh_material[mesh_table['uid'].isin([])] if focus_meshes_fetcher is None else focus_meshes_fetcher(mesh_material)
ignore_meshes: pd.DataFrame = mesh_material[mesh_table['uid'].isin([])] if ignore_meshes_fetcher is None else ignore_meshes_fetcher(mesh_material)
vanilla_meshes = mesh_material[
~(mesh_material['uid'].isin(focus_meshes.loc[:, 'uid'].values) | mesh_material['uid'].isin(ignore_meshes.loc[:, 'uid'].values))]
xyz_all, uv_all, normal_all, face_all, _, _ = __reduce_meshes(mesh_material)
Trescope().selectOutput(output_id).plotWireframe3D(*xyz_all).withConfig(Wireframe3DConfig().indices(*face_all).color(0xffff0000).name('wireframe'))
# focus_meshes
for uid, _type, material, xyz, uv, normal, face, texture, color, colorMode, UVTransform in \
focus_meshes.loc[:, ('uid', 'type', 'material', 'xyz', 'uv', 'normal', 'face', 'texture', 'color', 'colorMode', 'UVTransform')].values:
plot_mesh(xyz, __transform_uv(uv, UVTransform), texture, color, face, colorMode, f'focus: {uid} | {_type} | {material}')
# vanilla_meshes: same color
for color, (xyz, uv, normal, face, uid_list, type_list) in __aggregate_mesh_by_material_color(vanilla_meshes):
plot_mesh(xyz, uv, None, color, face, 'color', f'vanilla: same_color({hex(color)})<br>uids: {uid_list}<br>types: {type_list}')
# vanilla_meshes: same texture
for texture, (xyz, uv, normal, face, uid_list, type_list) in __aggregate_mesh_by_material_texture(vanilla_meshes):
plot_mesh(xyz, uv, texture, None, face, 'texture', f'vanilla same_texture({texture})<br>uids: {uid_list}<br>types: {type_list}')