"""
Author:     LanHao
Date:       2021/7/8 15:02

"""
import logging
from typing import List
from functools import lru_cache

from OCC.Core.gp import gp_Pnt
from OCC.Core.Bnd import Bnd_Box
from OCC.Core.TopAbs import TopAbs_SHAPE
from OCC.Core.BRepBndLib import brepbndlib_Add
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
from OCC.Core.BRepExtrema import BRepExtrema_DistShapeShape
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeVertex

logger = logging.getLogger(__name__)


def get_boundingbox(shape, tol=1e-6, use_mesh=True):
    bbox: Bnd_Box = Bnd_Box()
    bbox.SetGap(tol)
    if use_mesh:
        mesh = BRepMesh_IncrementalMesh()
        mesh.SetParallelDefault(True)
        mesh.SetShape(shape)
        mesh.Perform()
        if not mesh.IsDone():
            raise AssertionError("Mesh not done.")
    brepbndlib_Add(shape, bbox, use_mesh)

    xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
    return xmin, ymin, zmin, xmax, ymax, zmax, xmax - xmin, ymax - ymin, zmax - zmin


def get_xyz_from_box(box_value):
    points = [
        [box_value[0], box_value[1], box_value[2]],  #
        [box_value[3], box_value[1], box_value[2]],  #
        [box_value[3], box_value[4], box_value[2]],  #
        [box_value[0], box_value[4], box_value[2]],  #

        [box_value[0], box_value[1], box_value[5]],  #
        [box_value[3], box_value[1], box_value[5]],  #
        [box_value[3], box_value[4], box_value[5]],  #
        [box_value[0], box_value[4], box_value[5]],  #
    ]
    return points


def get_distance(shape1, shape2) -> float:
    dss = BRepExtrema_DistShapeShape()
    dss.LoadS1(shape1)
    dss.LoadS2(shape2)
    dss.Perform()

    assert dss.IsDone()
    return dss.Value()


class OCBox(object):
    # 用于生成八叉树的分割的shape
    _shape_in: TopAbs_SHAPE
    # 用于反向判断的
    _shape_reversed: TopAbs_SHAPE
    # 用于终止
    _min_step: float

    def __init__(self, shape_in, min_step: float = 1):
        self._shape_in = shape_in
        self._shape_reversed = shape_in.Reversed()
        self._min_step = min_step
        logger.debug(f"OCBox 初始化结束")

    # 尝试使用缓存,避免每次都运算
    @lru_cache
    def _get_distance(self, x, y, z):
        """
        有缓存的计算一个点到shape 的有向距离
        @param x:
        @param y:
        @param z:
        @return:
        """
        _point = gp_Pnt(x, y, z)

        vtx = BRepBuilderAPI_MakeVertex(_point).Shape()
        value1 = get_distance(vtx, self._shape_in)
        if value1 > 0:
            return value1
        else:
            value2 = get_distance(vtx, self._shape_reversed)
            if value2 > 0:
                return -value2
            return 0

    def _get_check(self, points: List):

        _all_bigger_or_equal_0 = True  # 不满足都在外侧
        _all_small_or_equal_0 = True  # 不满足都在内侧
        _distance_s = []
        for _point in points:
            _distance = self._get_distance(*_point)  # 列表化三参数
            _distance_s.append(_distance)
            if _distance > 0:
                _all_small_or_equal_0 = False
            elif _distance < 0:
                _all_bigger_or_equal_0 = False

            if not _all_bigger_or_equal_0 and not _all_small_or_equal_0:
                break
        return _all_bigger_or_equal_0, _all_small_or_equal_0

    def __iter__(self) -> List:
        """
        通过迭代方式避免使用列表占用内存
        :return:
        """
        # 获取最外层包围框
        _box_out = get_boundingbox(self._shape_in)
        max_step = max(_box_out[-3:])

        _box_out_2 = [
            _box_out[0],  # minx
            _box_out[1],  # miny
            _box_out[2],  # minz
            _box_out[0] + max_step,  # maxx
            _box_out[1] + max_step,  # maxy
            _box_out[2] + max_step,  # maxz
            max_step
        ]  # 得到正方体
        _box_out = _box_out_2
        _stack = [[_box_out]]

        while _stack:
            _box_s_current = _stack.pop()
            for _box_current in _box_s_current:
                split_status = True
                _center_l = [_box_current[0],
                             _box_current[1],
                             _box_current[2]]

                _center = gp_Pnt(*_center_l)
                step_use = _box_current[-1] / 2

                _all_bigger_or_equal_0, _all_small_or_equal_0 = self._get_check(get_xyz_from_box(_box_current))
                if _all_bigger_or_equal_0 ^ _all_small_or_equal_0:
                    box_shape = BRepPrimAPI_MakeBox(_center, _box_current[-1], _box_current[-1],
                                                    _box_current[-1]).Shape()
                    if _all_bigger_or_equal_0:
                        fact_distance = get_distance(box_shape, self._shape_in)
                    else:
                        fact_distance = get_distance(box_shape, self._shape_reversed)  # 取了另外的形状

                    if fact_distance > 1e-10:  # 精度误差,用于判断是否是正常的距离
                        split_status = False
                        if _all_small_or_equal_0:
                            yield _box_current
                if split_status:  # 继续分割三角形

                    if _box_current[-1] < self._min_step:  # 立方体的长宽高很小了
                        yield _box_current
                        continue
                    mid_x = _box_current[0] + step_use
                    mid_y = _box_current[1] + step_use
                    mid_z = _box_current[2] + step_use
                    las_x = _box_current[0] + _box_current[-1]
                    las_y = _box_current[1] + _box_current[-1]
                    las_z = _box_current[2] + _box_current[-1]

                    box_s_split = [
                        [
                            _box_current[0],
                            _box_current[1],
                            _box_current[2],
                            mid_x,
                            mid_y,
                            mid_z,
                            step_use
                        ],
                        [
                            mid_x,
                            _box_current[1],
                            _box_current[2],
                            las_x,
                            mid_y,
                            mid_z, step_use
                        ],
                        [
                            _box_current[0],
                            mid_y,
                            _box_current[2],
                            mid_x,
                            las_y,
                            mid_z,
                            step_use
                        ],
                        [
                            _box_current[0],
                            _box_current[1],
                            mid_z,
                            mid_x,
                            mid_y,
                            las_z,
                            step_use
                        ],
                        [
                            mid_x,
                            mid_y,
                            _box_current[2],
                            las_x,
                            las_y,
                            mid_z, step_use
                        ],
                        [
                            mid_x,
                            _box_current[1],
                            mid_z,
                            las_x,
                            mid_y,
                            las_z,
                            step_use
                        ],
                        [
                            mid_x,
                            mid_y,
                            mid_z,
                            las_x,
                            las_y,
                            las_z,
                            step_use
                        ],
                        [
                            _box_current[0],
                            mid_y,
                            mid_z,
                            mid_x,
                            las_y,
                            las_z,
                            step_use
                        ],

                    ]
                    _stack.append(box_s_split)

"""
Author:     LanHao
Date:       2021/7/8 15:02

"""
import logging
from typing import List
from functools import lru_cache

from OCC.Core.gp import gp_Pnt
from OCC.Core.Bnd import Bnd_Box
from OCC.Core.TopAbs import TopAbs_SHAPE
from OCC.Core.BRepBndLib import brepbndlib_Add
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
from OCC.Core.BRepExtrema import BRepExtrema_DistShapeShape
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeVertex

logger = logging.getLogger(__name__)


def get_boundingbox(shape, tol=1e-6, use_mesh=True):
    bbox: Bnd_Box = Bnd_Box()
    bbox.SetGap(tol)
    if use_mesh:
        mesh = BRepMesh_IncrementalMesh()
        mesh.SetParallelDefault(True)
        mesh.SetShape(shape)
        mesh.Perform()
        if not mesh.IsDone():
            raise AssertionError("Mesh not done.")
    brepbndlib_Add(shape, bbox, use_mesh)

    xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
    return xmin, ymin, zmin, xmax, ymax, zmax, xmax - xmin, ymax - ymin, zmax - zmin


def get_xyz_from_box(box_value):
    points = [
        [box_value[0], box_value[1], box_value[2]],  #
        [box_value[3], box_value[1], box_value[2]],  #
        [box_value[3], box_value[4], box_value[2]],  #
        [box_value[0], box_value[4], box_value[2]],  #

        [box_value[0], box_value[1], box_value[5]],  #
        [box_value[3], box_value[1], box_value[5]],  #
        [box_value[3], box_value[4], box_value[5]],  #
        [box_value[0], box_value[4], box_value[5]],  #
    ]
    return points


def get_distance(shape1, shape2) -> float:
    dss = BRepExtrema_DistShapeShape()
    dss.LoadS1(shape1)
    dss.LoadS2(shape2)
    dss.Perform()

    assert dss.IsDone()
    return dss.Value()


class OCBox(object):
    # 用于生成八叉树的分割的shape
    _shape_in: TopAbs_SHAPE
    # 用于反向判断的
    _shape_reversed: TopAbs_SHAPE
    # 用于终止
    _min_step: float

    def __init__(self, shape_in, min_step: float = 1):
        self._shape_in = shape_in
        self._shape_reversed = shape_in.Reversed()
        self._min_step = min_step
        logger.debug(f"OCBox 初始化结束")

    # 尝试使用缓存,避免每次都运算
    @lru_cache
    def _get_distance(self, x, y, z):
        """
        有缓存的计算一个点到shape 的有向距离
        @param x:
        @param y:
        @param z:
        @return:
        """
        _point = gp_Pnt(x, y, z)

        vtx = BRepBuilderAPI_MakeVertex(_point).Shape()
        value1 = get_distance(vtx, self._shape_in)
        if value1 > 0:
            return value1
        else:
            value2 = get_distance(vtx, self._shape_reversed)
            if value2 > 0:
                return -value2
            return 0

    def _get_check(self, points: List):

        _all_bigger_or_equal_0 = True  # 不满足都在外侧
        _all_small_or_equal_0 = True  # 不满足都在内侧
        _distance_s = []
        for _point in points:
            _distance = self._get_distance(*_point)  # 列表化三参数
            _distance_s.append(_distance)
            if _distance > 0:
                _all_small_or_equal_0 = False
            elif _distance < 0:
                _all_bigger_or_equal_0 = False

            if not _all_bigger_or_equal_0 and not _all_small_or_equal_0:
                break
        return _all_bigger_or_equal_0, _all_small_or_equal_0

    def __iter__(self) -> List:
        """
        通过迭代方式避免使用列表占用内存
        :return:
        """
        # 获取最外层包围框
        _box_out = get_boundingbox(self._shape_in)
        max_step = max(_box_out[-3:])

        _box_out_2 = [
            _box_out[0],  # minx
            _box_out[1],  # miny
            _box_out[2],  # minz
            _box_out[0] + max_step,  # maxx
            _box_out[1] + max_step,  # maxy
            _box_out[2] + max_step,  # maxz
            max_step
        ]  # 得到正方体
        _box_out = _box_out_2
        _stack = [[_box_out]]

        while _stack:
            _box_s_current = _stack.pop()
            for _box_current in _box_s_current:
                split_status = True
                _center_l = [_box_current[0],
                             _box_current[1],
                             _box_current[2]]

                _center = gp_Pnt(*_center_l)
                step_use = _box_current[-1] / 2

                _all_bigger_or_equal_0, _all_small_or_equal_0 = self._get_check(get_xyz_from_box(_box_current))
                if _all_bigger_or_equal_0 ^ _all_small_or_equal_0:
                    box_shape = BRepPrimAPI_MakeBox(_center, _box_current[-1], _box_current[-1],
                                                    _box_current[-1]).Shape()
                    if _all_bigger_or_equal_0:
                        fact_distance = get_distance(box_shape, self._shape_in)
                    else:
                        fact_distance = get_distance(box_shape, self._shape_reversed)  # 取了另外的形状

                    if fact_distance > 1e-10:  # 精度误差,用于判断是否是正常的距离
                        split_status = False
                        if _all_small_or_equal_0:
                            yield _box_current
                if split_status:  # 继续分割三角形

                    if _box_current[-1] < self._min_step:  # 立方体的长宽高很小了
                        yield _box_current
                        continue
                    mid_x = _box_current[0] + step_use
                    mid_y = _box_current[1] + step_use
                    mid_z = _box_current[2] + step_use
                    las_x = _box_current[0] + _box_current[-1]
                    las_y = _box_current[1] + _box_current[-1]
                    las_z = _box_current[2] + _box_current[-1]

                    box_s_split = [
                        [
                            _box_current[0],
                            _box_current[1],
                            _box_current[2],
                            mid_x,
                            mid_y,
                            mid_z,
                            step_use
                        ],
                        [
                            mid_x,
                            _box_current[1],
                            _box_current[2],
                            las_x,
                            mid_y,
                            mid_z, step_use
                        ],
                        [
                            _box_current[0],
                            mid_y,
                            _box_current[2],
                            mid_x,
                            las_y,
                            mid_z,
                            step_use
                        ],
                        [
                            _box_current[0],
                            _box_current[1],
                            mid_z,
                            mid_x,
                            mid_y,
                            las_z,
                            step_use
                        ],
                        [
                            mid_x,
                            mid_y,
                            _box_current[2],
                            las_x,
                            las_y,
                            mid_z, step_use
                        ],
                        [
                            mid_x,
                            _box_current[1],
                            mid_z,
                            las_x,
                            mid_y,
                            las_z,
                            step_use
                        ],
                        [
                            mid_x,
                            mid_y,
                            mid_z,
                            las_x,
                            las_y,
                            las_z,
                            step_use
                        ],
                        [
                            _box_current[0],
                            mid_y,
                            mid_z,
                            mid_x,
                            las_y,
                            las_z,
                            step_use
                        ],

                    ]
                    _stack.append(box_s_split)

Author:     LanHao Date:       2021/7/8 15:02 # # # # # # # # # 用于生成八叉树的分割的shape # 用于反向判断的 # 用于终止 # 尝试使用缓存,避免每次都运算 有缓存的计算一个点到shape 的有向距离         @param x:         @param y:         @param z:         @return: # 不满足都在外侧 # 不满足都在内侧 # 列表化三参数 通过迭代方式避免使用列表占用内存         :return: # 获取最外层包围框 # minx # miny # minz # maxx # maxy # maxz # 得到正方体 # 取了另外的形状 # 精度误差,用于判断是否是正常的距离 # 继续分割三角形 # 立方体的长宽高很小了