mindflow.geometry.csg 源代码

# Copyright 2021 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
# pylint: disable=W0223
# pylint: disable=W0221
# pylint: disable=W0212
"""Constructive Solid Geometry"""
from __future__ import absolute_import

import copy
import numpy as np

from mindspore import log as logger

from .geometry_base import Geometry, SamplingConfig, GEOM_TYPES
from .geom_utils import sample
from ..utils.check_func import check_param_type, check_param_type_value

_SPACE = " "


def _check_geom(geoms):
    for i, _ in enumerate(geoms):
        check_param_type(geoms[i], _SPACE.join(("geom", str(i + 1))), data_type=Geometry)
        if geoms[0].dim != geoms[i].dim:
            raise ValueError("Mismatch of dimension, geom1: {}'s dim is: {} while geom{}: {}'s dim is: {}.".format(
                geoms[0].name, geoms[0].dim, i + 1, geoms[i].name, geoms[i].dim))


class CSG(Geometry):
    r"""
    CSG base class.

    Supported Platforms:
        ``Ascend````GPU``
    """

    def __init__(self, name, geom1, geom2, coord_min, coord_max, sampling_config=None):
        _check_geom([geom1, geom2])
        self.geom_type = type(self).__name__
        geom1 = copy.deepcopy(geom1)
        geom2 = copy.deepcopy(geom2)
        self.geom1 = geom1
        self.geom2 = geom2

        super(CSG, self).__init__(name, geom1.dim, coord_min, coord_max, geom1.dtype, sampling_config)

    def _check_sampling_config(self, sampling_config):
        """check sampling_config"""
        check_param_type(sampling_config, _SPACE.join(("sampling_config of", self.name)), data_type=SamplingConfig,
                         exclude_type=type(None))
        if sampling_config.domain and not sampling_config.domain.random_sampling:
            raise ValueError("Only random sampling strategy is supported for CSG instance in domain")
        if sampling_config.bc and not sampling_config.bc.random_sampling:
            raise ValueError("Only random sampling strategy is supported for CSG instance in bc")
        if sampling_config.ic and not sampling_config.ic.random_sampling:
            raise ValueError("Only random sampling strategy is supported for CSG instance in ic")

    def _random_domain_points(self):
        raise NotImplementedError("{}._random_domain_points not implemented".format(self.geom_type))

    def _random_boundary_points(self):
        raise NotImplementedError("{}._random_boundary_points not implemented".format(self.geom_type))

    def set_sampling_config(self, sampling_config: SamplingConfig):
        """
        set sampling info

        Args:
            sampling_config (SamplingConfig): sampling configuration.

        Raises:
            TypeError: If `sampling_config` is not instance of SamplingConfig.
        """
        self._check_sampling_config(sampling_config)
        self.sampling_config = copy.deepcopy(sampling_config)
        self.geom1.set_sampling_config(self.sampling_config)
        self.geom2.set_sampling_config(self.sampling_config)

    def set_name(self, name):
        """
        set geometry instance name
        """
        check_param_type(name, "name", data_type=str)
        self.name = name

    def sampling(self, geom_type="domain"):
        """
        sampling points

        Args:
            geom_type (str): geometry type.

        Returns:
            Numpy.array, numpy array with or without boundary normal vectors.

        Raises:
            ValueError: If `config` is ``None``.
            KeyError: If `geom_type` is `domain` but `config.domain` is ``None``.
            KeyError: If `geom_type` is ``"BC"`` but `config.bc` is ``None``.
            ValueError: If `geom_type` is neither ``"BC"`` nor ``"domain"``.
        """
        self._check_sampling_config(self.sampling_config)
        config = self.sampling_config
        check_param_type_value(geom_type, "geom_type", valid_value=GEOM_TYPES, data_type=str)
        if geom_type.lower() == "domain":
            check_param_type(config.domain, _SPACE.join((self.geom_type, self.name, "'s domain config")),
                             exclude_type=type(None))
            logger.info("Sampling domain points for {}:{}, config info: {}"
                        .format(self.geom_type, self.name, config.domain))
            column_name = self.name + "_domain_points"
            data = self._random_domain_points()
            self.columns_dict["domain"] = [column_name]
            data = data.astype(self.dtype)
            return data
        if geom_type.lower() == "bc":
            check_param_type(config.bc, _SPACE.join((self.geom_type, self.name, "'s bc config")),
                             exclude_type=type(None))
            logger.info("Sampling BC points for {}:{}, config info: {}"
                        .format(self.geom_type, self.name, config.domain))
            if config.bc.with_normal:
                data, data_normal = self._random_boundary_points()
                column_data = self.name + "_BC_points"
                column_normal = self.name + "_BC_normal"
                self.columns_dict["BC"] = [column_data, column_normal]
                data = data.astype(self.dtype)
                data_normal = data_normal.astype(self.dtype)
                return data, data_normal
            data = self._random_boundary_points()
            column_data = self.name + "_BC_points"
            self.columns_dict["BC"] = [column_data]
            data = data.astype(self.dtype)
            return data
        raise ValueError("Unknown geom_type: {}, only \"domain/BC\" are supported for {}:{}".format(
            geom_type, self.geom_type, self.name))


[文档]class CSGDifference(CSG): r""" CSG class for difference of geometry. Args: geom1 (Geometry): a geometry object. geom2 (Geometry): a geometry object to be subtracted from geom1. sampling_config (SamplingConfig): sampling configuration. Default: ``None``. Supported Platforms: ``Ascend`` ``GPU`` Examples: >>> from mindflow.geometry import generate_sampling_config, Disk, Rectangle, CSGDifference >>> sampling_config_csg = dict({ ... 'domain': dict({ ... 'random_sampling': True, ... 'size': 1000, ... 'sampler': 'uniform' ... }), ... 'BC': dict({ ... 'random_sampling': True, ... 'size': 200, ... 'sampler': 'uniform', ... 'with_normal': True, ... }), ... }) >>> disk = Disk("disk", (1.2, 0.5), 0.8) >>> rect = Rectangle("rect", (-1.0, 0), (1, 1)) >>> diff = CSGDifference(rect, disk) >>> diff.set_sampling_config(generate_sampling_config(sampling_config_csg)) >>> domain = diff.sampling(geom_type="domain") >>> bc, bc_normal = diff.sampling(geom_type="BC") >>> print(domain.shape) (1000, 2) """ def __init__(self, geom1, geom2, sampling_config=None): r"""This class returns geom1\\geom2""" _check_geom([geom1, geom2]) name = geom1.name + "_sub_" + geom2.name self.columns_dict = {} super(CSGDifference, self).__init__(name, geom1, geom2, geom1.coord_min, geom1.coord_max, sampling_config) if sampling_config is None: sampling_config = geom1.sampling_config else: self.set_sampling_config(sampling_config) def _inside(self, points): """Check whether points in geom1\\geom2 or not""" inside1 = self.geom1._inside(points) inside2 = self.geom2._inside(points) return np.logical_and(inside1, ~inside2) def _random_domain_points(self): """Sample points in geom1\\geom2""" diff_size = self.sampling_config.domain.size diff_domain_points = np.empty(shape=(diff_size, self.dim)) index = 0 while index < diff_size: domain_points_from_geom1 = self.geom1.sampling(geom_type="domain") domain_points_from_geom1_sub_geom2 = domain_points_from_geom1[~self.geom2._inside(domain_points_from_geom1)] added_size = len(domain_points_from_geom1_sub_geom2) diff_domain_points[index: min(diff_size, added_size + index)] = \ domain_points_from_geom1_sub_geom2[:min(added_size, diff_size - index)] index += added_size return diff_domain_points def _random_boundary_points(self): """Sample boundary points in geom1\\geom2""" diff_size = self.sampling_config.bc.size need_normal = self.sampling_config.bc.with_normal diff_points = np.empty(shape=(diff_size, self.dim)) if need_normal: diff_normal = np.empty(shape=(diff_size, self.dim)) index = 0 while index < diff_size: if need_normal: points_from_geom1, normal_from_geom1 = self.geom1.sampling(geom_type="BC") else: points_from_geom1 = self.geom1.sampling(geom_type="BC") points_from_geom1_out_geom2 = points_from_geom1[~self.geom2._inside(points_from_geom1)] if need_normal: normal_from_geom1_out_geom2 = normal_from_geom1[~self.geom2._inside(points_from_geom1)] if need_normal: points_from_geom2, normal_from_geom2 = self.geom2.sampling(geom_type="BC") else: points_from_geom2 = self.geom2.sampling(geom_type="BC") points_from_geom2_out_geom1 = points_from_geom2[self.geom1._inside(points_from_geom2)] if need_normal: normal_from_geom2_out_geom1 = -1 * normal_from_geom2[self.geom1._inside(points_from_geom2)] points_from_geom1_sub_geom2 = np.concatenate([points_from_geom1_out_geom2, points_from_geom2_out_geom1], axis=0) added_size = len(points_from_geom1_sub_geom2) if need_normal: rand_index = np.random.permutation(added_size) points_from_geom1_sub_geom2 = points_from_geom1_sub_geom2[rand_index] normal_from_geom1_sub_geom2 = np.concatenate([normal_from_geom1_out_geom2, normal_from_geom2_out_geom1], axis=0) normal_from_geom1_sub_geom2 = normal_from_geom1_sub_geom2[rand_index] else: points_from_geom1_sub_geom2 = np.random.permutation(points_from_geom1_sub_geom2) diff_points[index: min(diff_size, added_size + index)] = \ points_from_geom1_sub_geom2[:min(added_size, diff_size - index)] if need_normal: diff_normal[index: min(diff_size, added_size + index)] = \ normal_from_geom1_sub_geom2[:min(added_size, diff_size - index)] index += added_size if need_normal: return diff_points, diff_normal return diff_points
[文档]class CSGUnion(CSG): r""" CSG class for union of geometries. Args: geom1 (Geometry): a geometry object. geom2 (Geometry): a geometry object to be subtracted from geom1. sampling_config (SamplingConfig): sampling configuration. Default: ``None``. Supported Platforms: ``Ascend`` ``GPU`` Examples: >>> from mindflow.geometry import generate_sampling_config, Disk, Rectangle, CSGUnion >>> sampling_config_csg = dict({ ... 'domain': dict({ ... 'random_sampling': True, ... 'size': 1000, ... 'sampler': 'uniform' ... }), ... 'BC': dict({ ... 'random_sampling': True, ... 'size': 200, ... 'sampler': 'uniform', ... 'with_normal': True, ... }), ... }) >>> disk = Disk("disk", (1.2, 0.5), 0.8) >>> rect = Rectangle("rect", (-1.0, 0), (1, 1)) >>> union = CSGUnion(rect, disk) >>> union.set_sampling_config(generate_sampling_config(sampling_config_csg)) >>> domain = union.sampling(geom_type="domain") >>> bc, bc_normal = union.sampling(geom_type="BC") >>> print(domain.shape) (1000, 2) """ def __init__(self, geom1, geom2, sampling_config=None): """This class returns geom1 or geom2""" _check_geom([geom1, geom2]) name = geom1.name + "_add_" + geom2.name self.columns_dict = {} min_coord_min = np.minimum(geom1.coord_min, geom2.coord_min) max_coord_max = np.maximum(geom1.coord_max, geom2.coord_max) super(CSGUnion, self).__init__(name, geom1, geom2, min_coord_min, max_coord_max, sampling_config) if sampling_config is None: self.sampling_config = None else: self.set_sampling_config(sampling_config) def _inside(self, points): """Check whether points in geom1 or geom2 or not""" inside1 = self.geom1._inside(points) inside2 = self.geom2._inside(points) return np.logical_or(inside1, inside2) def _random_domain_points(self): """Sample points in geom1 or geom2""" union_size = self.sampling_config.domain.size sampler = self.sampling_config.domain.sampler union_domain_points = np.empty(shape=(union_size, self.dim)) index = 0 while index < union_size: domain_points = sample(union_size, self.dim, sampler) * (self.coord_max - self.coord_min) + self.coord_min domain_points = np.reshape(domain_points, (-1, self.dim)) union_points = domain_points[self._inside(domain_points)] added_size = len(union_points) union_domain_points[index: min(union_size, added_size + index)] = \ union_points[:min(added_size, union_size - index)] index += added_size return union_domain_points def _random_boundary_points(self): """Sample boundary points in geom1 or geom2""" union_size = self.sampling_config.bc.size need_normal = self.sampling_config.bc.with_normal union_points = np.empty(shape=(union_size, self.dim)) if need_normal: union_normal = np.empty(shape=(union_size, self.dim)) index = 0 if need_normal: while index < union_size: boundary_from_geom1, normal_from_geom1 = self.geom1.sampling(geom_type="BC") boundary_from_geom2, normal_from_geom2 = self.geom2.sampling(geom_type="BC") bound_geom1_sub_geom2 = boundary_from_geom1[~self.geom2._inside(boundary_from_geom1)] normal_geom1_sub_geom2 = normal_from_geom1[~self.geom2._inside(boundary_from_geom1)] bound_geom2_sub_geom1 = boundary_from_geom2[~self.geom1._inside(boundary_from_geom2)] normal_geom2_sub_geom1 = normal_from_geom2[~self.geom1._inside(boundary_from_geom2)] boundary_from_csg = np.concatenate((bound_geom1_sub_geom2, bound_geom2_sub_geom1), axis=0) normal_from_csg = np.concatenate((normal_geom1_sub_geom2, normal_geom2_sub_geom1), axis=0) added_size = len(boundary_from_csg) rand_index = np.random.permutation(added_size) boundary_from_csg = boundary_from_csg[rand_index] normal_from_csg = normal_from_csg[rand_index] union_points[index: min(union_size, added_size + index)] = \ boundary_from_csg[:min(added_size, union_size - index)] union_normal[index: min(union_size, added_size + index)] = \ normal_from_csg[:min(added_size, union_size - index)] index += added_size return union_points, union_normal while index < union_size: boundary_from_geom1 = self.geom1.sampling(geom_type="BC") boundary_from_geom2 = self.geom2.sampling(geom_type="BC") bound_geom1_sub_geom2 = boundary_from_geom1[~self.geom2._inside(boundary_from_geom1)] bound_geom2_sub_geom1 = boundary_from_geom2[~self.geom1._inside(boundary_from_geom2)] boundary_from_csg = np.concatenate((bound_geom1_sub_geom2, bound_geom2_sub_geom1), axis=0) added_size = len(boundary_from_csg) boundary_from_csg = np.random.permutation(boundary_from_csg) union_points[index: min(union_size, added_size + index)] = \ boundary_from_csg[:min(added_size, union_size - index)] index += added_size return union_points
[文档]class CSGIntersection(CSG): r""" CSG class for intersection of geometries. Args: geom1 (Geometry): a geometry object. geom2 (Geometry): a geometry object to be subtracted from geom1. sampling_config (SamplingConfig): sampling configuration. Default: ``None``. Supported Platforms: ``Ascend`` ``GPU`` Examples: >>> from mindflow.geometry import generate_sampling_config, Disk, Rectangle, CSGIntersection >>> sampling_config_csg = dict({ ... 'domain': dict({ ... 'random_sampling': True, ... 'size': 1000, ... 'sampler': 'uniform' ... }), ... 'BC': dict({ ... 'random_sampling': True, ... 'size': 200, ... 'sampler': 'uniform', ... 'with_normal': True, ... }), ... }) >>> disk = Disk("disk", (1.2, 0.5), 0.8) >>> rect = Rectangle("rect", (-1.0, 0), (1, 1)) >>> inter = CSGIntersection(rect, disk) >>> inter.set_sampling_config(generate_sampling_config(sampling_config_csg)) >>> domain = inter.sampling(geom_type="domain") >>> bc, bc_normal = inter.sampling(geom_type="BC") >>> print(domain.shape) (1000, 2) """ def __init__(self, geom1, geom2, sampling_config=None): """This class returns geom1 and geom2""" _check_geom([geom1, geom2]) if geom1.dim != geom2.dim: raise ValueError("Unable to union: {} and {} do not match in dimension.".format(geom1.name, geom2.name)) name = geom1.name + "_add_" + geom2.name self.columns_dict = {} max_coord_min = np.maximum(geom1.coord_min, geom2.coord_min) min_coord_max = np.minimum(geom1.coord_max, geom2.coord_max) super(CSGIntersection, self).__init__(name, geom1, geom2, max_coord_min, min_coord_max, sampling_config) if sampling_config is None: sampling_config = geom1.sampling_config else: self.set_sampling_config(sampling_config) def _inside(self, points): """Check whether points in geom1 and geom2 or not""" inside_geom1 = self.geom1._inside(points) inside_geom2 = self.geom2._inside(points) return np.logical_and(inside_geom1, inside_geom2) def _random_domain_points(self): """Sample points in geom1 and geom2""" inter_size = self.sampling_config.domain.size sampler = self.sampling_config.domain.sampler inter_domain_points = np.empty(shape=(inter_size, self.dim)) index = 0 while index < inter_size: domain_points = sample(inter_size, self.dim, sampler) * (self.coord_max - self.coord_min) + self.coord_min domain_points = np.reshape(domain_points, (-1, self.dim)) inter_points = domain_points[self._inside(domain_points)] added_size = len(inter_points) inter_domain_points[index: min(inter_size, added_size + index)] = \ inter_points[:min(added_size, inter_size - index)] index += added_size return inter_domain_points def _random_boundary_points(self): """Sample points in geom1 and geom2""" inter_size = self.sampling_config.bc.size need_normal = self.sampling_config.bc.with_normal inter_points = np.empty(shape=(inter_size, self.dim)) if need_normal: inter_normal = np.empty(shape=(inter_size, self.dim)) index = 0 if need_normal: while index < inter_size: boundary_from_geom1, normal_from_geom1 = self.geom1.sampling(geom_type="BC") boundary_from_geom2, normal_from_geom2 = self.geom2.sampling(geom_type="BC") boundary_from_geom1_exclude = boundary_from_geom1[self.geom2._inside(boundary_from_geom1)] normal_from_geom1_exclude = normal_from_geom1[self.geom2._inside(boundary_from_geom1)] boundary_from_geom2_exclude = boundary_from_geom2[self.geom1._inside(boundary_from_geom2)] normal_from_geom2_exclude = normal_from_geom2[self.geom1._inside(boundary_from_geom2)] boundary_from_csg = np.concatenate((boundary_from_geom1_exclude, boundary_from_geom2_exclude), axis=0) normal_from_csg = np.concatenate((normal_from_geom1_exclude, normal_from_geom2_exclude), axis=0) added_size = len(boundary_from_csg) rand_index = np.random.permutation(added_size) boundary_from_csg = boundary_from_csg[rand_index] normal_from_csg = normal_from_csg[rand_index] inter_points[index: min(inter_size, added_size + index)] = \ boundary_from_csg[:min(added_size, inter_size - index)] inter_normal[index: min(inter_size, added_size + index)] = \ normal_from_csg[:min(added_size, inter_size - index)] index += added_size return inter_points, inter_normal while index < inter_size: boundary_from_geom1 = self.geom1.sampling(geom_type="BC") boundary_from_geom2 = self.geom2.sampling(geom_type="BC") boundary_from_geom1_exclude = boundary_from_geom1[self.geom2._inside(boundary_from_geom1)] boundary_from_geom2_exclude = boundary_from_geom2[self.geom1._inside(boundary_from_geom2)] boundary_from_csg = np.concatenate((boundary_from_geom1_exclude, boundary_from_geom2_exclude), axis=0) added_size = len(boundary_from_csg) boundary_from_csg = np.random.permutation(boundary_from_csg) inter_points[index: min(inter_size, added_size + index)] = \ boundary_from_csg[:min(added_size, inter_size - index)] index += added_size return inter_points
[文档]class CSGXOR(CSG): r""" CSG class for xor of geometries. Args: geom1 (Geometry): a geometry object. geom2 (Geometry): a geometry object to be subtracted from geom1. sampling_config (SamplingConfig): sampling configuration. Default: ``None``. Supported Platforms: ``Ascend`` ``GPU`` Examples: >>> from mindflow.geometry import generate_sampling_config, Disk, Rectangle, CSGXOR >>> sampling_config_csg = dict({ ... 'domain': dict({ ... 'random_sampling': True, ... 'size': 1000, ... 'sampler': 'uniform' ... }), ... 'BC': dict({ ... 'random_sampling': True, ... 'size': 200, ... 'sampler': 'uniform', ... 'with_normal': True, ... }), ... }) >>> disk = Disk("disk", (1.2, 0.5), 0.8) >>> rect = Rectangle("rect", (-1.0, 0), (1, 1)) >>> xor = CSGXOR(rect, disk) >>> xor.set_sampling_config(generate_sampling_config(sampling_config_csg)) >>> domain = xor.sampling(geom_type="domain") >>> bc, bc_normal = xor.sampling(geom_type="BC") >>> print(domain.shape) (1000, 2) """ def __init__(self, geom1, geom2, sampling_config=None): """This class returns geom1 xor geom2""" _check_geom([geom1, geom2]) name = geom1.name + "_xor_" + geom2.name self.columns_dict = {} max_coord_min = np.minimum(geom1.coord_min, geom2.coord_min) min_coord_max = np.maximum(geom1.coord_max, geom2.coord_max) super(CSGXOR, self).__init__(name, geom1, geom2, max_coord_min, min_coord_max, sampling_config) if sampling_config is None: sampling_config = geom1.sampling_config else: self.set_sampling_config(sampling_config) def _inside(self, points): """Check whether points in geom1 xor geom2 or not""" inside1 = self.geom1._inside(points) inside2 = self.geom2._inside(points) inside1_not_inside2 = np.logical_and(inside1, ~inside2) inside2_not_inside1 = np.logical_and(~inside1, inside2) return np.logical_or(inside1_not_inside2, inside2_not_inside1) def _random_domain_points(self): """Sample points in geom1 xor geom2""" xor_size = self.sampling_config.domain.size sampler = self.sampling_config.domain.sampler xor_domain_points = np.empty(shape=(xor_size, self.dim)) index = 0 while index < xor_size: domain_points = sample(xor_size, self.dim, sampler) * (self.coord_max - self.coord_min) + self.coord_min domain_points = np.reshape(domain_points, (-1, self.dim)) xor_points = domain_points[self._inside(domain_points)] added_size = len(xor_points) xor_domain_points[index: min(xor_size, added_size + index)] = \ xor_points[:min(added_size, xor_size - index)] index += added_size return xor_domain_points def _random_boundary_points(self): """Sample points in geom1 xor geom2""" xor_size = self.sampling_config.bc.size need_normal = self.sampling_config.bc.with_normal xor_points = np.empty(shape=(xor_size, self.dim)) if need_normal: xor_normal = np.empty(shape=(xor_size, self.dim)) index = 0 if need_normal: while index < xor_size: boundary_from_geom1, normal_from_geom1 = self.geom1.sampling(geom_type="BC") boundary_from_geom2, normal_from_geom2 = self.geom2.sampling(geom_type="BC") index_in_geom1_out_geom2 = ~self.geom2._inside(boundary_from_geom1) index_in_geom1_in_geom2 = self.geom2._inside(boundary_from_geom1) index_in_geom2_out_geom1 = ~self.geom1._inside(boundary_from_geom2) index_in_geom2_in_geom1 = self.geom1._inside(boundary_from_geom2) boundary_from_geom1_out_geom2 = boundary_from_geom1[index_in_geom1_out_geom2] boundary_from_geom1_in_geom2 = boundary_from_geom1[index_in_geom1_in_geom2] boundary_from_geom2_out_geom1 = boundary_from_geom2[index_in_geom2_out_geom1] boundary_from_geom2_in_geom1 = boundary_from_geom2[index_in_geom2_in_geom1] boundary_from_csg = np.concatenate((boundary_from_geom1_out_geom2, boundary_from_geom1_in_geom2, boundary_from_geom2_out_geom1, boundary_from_geom2_in_geom1), axis=0) normal_from_geom1_out_geom2 = normal_from_geom1[index_in_geom1_out_geom2] normal_from_geom1_in_geom2 = -1.0 * normal_from_geom1[index_in_geom1_in_geom2] normal_from_geom2_out_geom1 = normal_from_geom2[index_in_geom2_out_geom1] normal_from_geom2_in_geom1 = -1.0 * normal_from_geom2[index_in_geom2_in_geom1] normal_from_csg = np.concatenate((normal_from_geom1_out_geom2, normal_from_geom1_in_geom2, normal_from_geom2_out_geom1, normal_from_geom2_in_geom1), axis=0) added_size = len(normal_from_csg) rand_index = np.random.permutation(added_size) boundary_from_csg = boundary_from_csg[rand_index] normal_from_csg = normal_from_csg[rand_index] xor_points[index: min(xor_size, added_size + index)] = \ boundary_from_csg[:min(added_size, xor_size - index)] xor_normal[index: min(xor_size, added_size + index)] = \ normal_from_csg[:min(added_size, xor_size - index)] index += added_size return xor_points, xor_normal while index < xor_size: boundary_from_geom1 = self.geom1.sampling(geom_type="BC") boundary_from_geom2 = self.geom2.sampling(geom_type="BC") boundary_from_csg = np.concatenate((boundary_from_geom1, boundary_from_geom2), axis=0) boundary_from_csg = np.random.permutation(boundary_from_csg) added_size = len(boundary_from_csg) xor_points[index: min(xor_size, added_size + index)] = \ boundary_from_csg[:min(added_size, xor_size - index)] index += added_size return xor_points