Module kawa_scripts.uv
Useful tools for UV Layers
Expand source code
# Kawashirov's Scripts (c) 2021 by Sergey V. Kawashirov
#
# Kawashirov's Scripts is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <http://creativecommons.org/licenses/by-nc-sa/3.0/>.
#
#
"""
Useful tools for UV Layers
"""
import bpy as _bpy
from bpy import context as _C
from mathutils import Vector as _Vector
from . import commons as _commons
from ._internals import common_str_slots
from ._internals import log as _log
import typing as _typing
if _typing.TYPE_CHECKING:
from typing import *
from bpy.types import *
from mathutils import Vector
def uv_area(poly: 'MeshPolygon', uv_layer_data: 'Union[bpy_prop_collection, List[MeshUVLoop]]'):
""" Returns area of given polygon on given UV Layer in normalized (0..1) space. """
# tuple чуть-чуть быстрее на малых длинах, тестил через timeit
return _commons.poly2_area2(tuple(uv_layer_data[loop].uv for loop in poly.loop_indices))
def repack_active_uv(
obj: 'Object', get_scale: 'Optional[Callable[[Material], float]]' = None,
rotate: 'bool' = None, margin: 'float' = 0.0
):
"""
Repack active UV Layer of a given Object with some adjustments:
- Runs `bpy.ops.uv.average_islands_scale`
- Rescales islands according to `get_scale` per material
- Runs `bpy.ops.uv.pack_islands` with given `rotate` and `margin`
"""
e = _commons.ensure_op_finished
try:
_commons.ensure_deselect_all_objects()
_commons.activate_object(obj)
# Перепаковка...
e(_bpy.ops.object.mode_set_with_submode(mode='EDIT', mesh_select_mode={'FACE'}), name='object.mode_set_with_submode')
e(_bpy.ops.mesh.reveal(select=True), name='mesh.reveal')
e(_bpy.ops.mesh.select_all(action='SELECT'), name='mesh.select_all')
_C.scene.tool_settings.use_uv_select_sync = True
area_type = _C.area.type
try:
_C.area.type = 'IMAGE_EDITOR'
_C.area.ui_type = 'UV'
e(_bpy.ops.uv.reveal(select=True), name='uv.reveal')
e(_bpy.ops.mesh.select_all(action='SELECT'), name='mesh.select_all')
e(_bpy.ops.uv.select_all(action='SELECT'), name='uv.select_all')
e(_bpy.ops.uv.average_islands_scale(), name='uv.average_islands_scale')
for index in range(len(obj.material_slots)):
scale = 1.0
if get_scale is not None:
scale = get_scale(obj.material_slots[index].material)
if scale <= 0 or scale == 1.0:
continue
_C.scene.tool_settings.use_uv_select_sync = True
e(_bpy.ops.mesh.select_all(action='DESELECT'), name='mesh.select_all', index=index)
e(_bpy.ops.uv.select_all(action='DESELECT'), name='uv.select_all', index=index)
obj.active_material_index = index
if 'FINISHED' in _bpy.ops.object.material_slot_select():
# Может быть не FINISHED если есть не использованые материалы
e(_bpy.ops.uv.select_linked(), name='uv.select_linked', index=index)
e(_bpy.ops.transform.resize(value=(scale, scale, scale)), name='transform.resize', value=scale, index=index)
e(_bpy.ops.mesh.select_all(action='SELECT'), name='mesh.select_all')
e(_bpy.ops.uv.select_all(action='SELECT'), name='uv.select_all')
e(_bpy.ops.uv.pack_islands(rotate=rotate, margin=margin), name='uv.pack_islands')
e(_bpy.ops.uv.select_all(action='DESELECT'), name='uv.select_all')
e(_bpy.ops.mesh.select_all(action='DESELECT'), name='mesh.select_all')
finally:
_C.area.type = area_type
finally:
e(_bpy.ops.object.mode_set(mode='OBJECT'), name='object.mode_set')
def remove_all_uv_layers(obj: 'Object'):
"""
Remove all UV Layers from Mesh-Object.
"""
mesh = _commons.get_mesh_safe(obj)
while len(mesh.uv_layers) > 0:
mesh.uv_layers.remove(mesh.uv_layers[0])
def _remove_uv_layer_by_condition(
mesh: 'Mesh',
func_should_delete: 'Callable[str, MeshTexturePolyLayer, bool]',
func_on_delete: 'Callable[str, MeshTexturePolyLayer, None]'
):
# TODO лагаси говно переписать
while True:
# Удаление таким нелепым образом, потому что после вызова remove()
# все MeshTexturePolyLayer взятые из uv_textures становтся сломанными и крешат скрипт
# По этому, после удаления обход начинается заново, до тех пор, пока не кончатся объекты к удалению
# TODO Проверить баг в 2.83
to_delete_name = None
to_delete = None
for uv_layer_name, uv_layer in mesh.uv_layers.items():
if func_should_delete(uv_layer_name, uv_layer):
to_delete_name, to_delete = uv_layer_name, uv_layer
break
if to_delete is None: return
if func_on_delete is not None: func_on_delete(to_delete_name, to_delete)
mesh.uv_layers.remove(to_delete)
class Island:
"""
Internal class of `IslandsBuilder`.
Describes rectangle region of UV Layer.
"""
__slots__ = ('mn', 'mx', 'extends')
def __init__(self, mn: 'Optional[Vector]', mx: 'Optional[Vector]'):
self.mn = mn # type: Optional[Vector]
self.mx = mx # type: Optional[Vector]
self.extends = 0 # Для диагностических целей
def __str__(self) -> str: return common_str_slots(self, self.__slots__)
def __repr__(self) -> str: return common_str_slots(self, self.__slots__)
def is_valid(self):
return self.mn is not None and self.mx is not None
def is_inside_vec2(self, item: 'Vector', epsilon: 'float' = 0):
if type(item) != _Vector:
raise ValueError("type(item) != Vector")
if len(item) != 2:
raise ValueError("len(item) != 2")
if self.mn is None or self.mx is None:
return False
mnx, mny = self.mn.x - epsilon, self.mn.y - epsilon
mxx, mxy = self.mx.x + epsilon, self.mx.y + epsilon
return mnx <= item.x <= mxx and mny <= item.y <= mxy
def is_inside_bbox(self, inner: 'Island', epsilon: 'float' = 0) -> bool:
# Проверяет лежит ли inner внутри self
if self.mn is None or self.mx is None or inner.mn is None or inner.mx is None:
return False
if inner.mx.x + epsilon >= self.mx.x or inner.mx.y + epsilon >= self.mx.y:
return False
if inner.mn.x - epsilon <= self.mn.x or inner.mn.y - epsilon <= self.mn.y:
return False
return True
def get_points(self) -> 'Sequence[Vector]':
return self.mn, self.mx, _Vector((self.mn.x, self.mx.y)), _Vector((self.mx.x, self.mn.y))
def any_inside_vec2(self, items: 'Iterable[Vector]', epsilon: 'float' = 0):
return any(self.is_inside_vec2(x, epsilon=epsilon) for x in items)
def is_intersect(self, other: 'Island', epsilon: 'float' = 0):
return any(self.is_inside_vec2(x, epsilon=epsilon) for x in other.get_points())
def extend_by_vec2(self, vec2: 'Vector'):
if self.mn is None:
self.mn = vec2.xy
else:
self.mn.x = min(self.mn.x, vec2.x)
self.mn.y = min(self.mn.y, vec2.y)
if self.mx is None:
self.mx = vec2.xy
else:
self.mx.x = max(self.mx.x, vec2.x)
self.mx.y = max(self.mx.y, vec2.y)
self.extends += 1
def extend_by_vec2s(self, vec2s: 'Iterable[Vector]'):
for vec2 in vec2s:
self.extend_by_vec2(vec2)
def extend_by_bbox(self, other: 'Island'):
if self is other:
raise ValueError("self is other", self, other)
if not other.is_valid():
raise ValueError("other bbox is not valid", self, other)
self.extend_by_vec2s(other.get_points())
if not self.is_valid():
raise ValueError("Invalid after extend_by_bbox", self, other)
def get_area(self) -> 'float':
if not self.is_valid():
raise ValueError("bbox is not valid", self)
return (self.mx.x - self.mn.x) * (self.mx.y - self.mn.y)
class IslandsBuilder:
"""
Internal class of `kawa_scripts.atlas_baker.BaseAtlasBaker`, but can be used standalone.
Finds non-overlapping bounding boxes on UV Layer of UV polygons.
Just provide UV coords of all your polygons into `add_seq` or `add_bbox`,
`bboxes` will contain all found non-overlapping rectangle regions.
`epsilon` is a search precision in normalized (0..1) space.
If distance between two Islands is less than epsilon these two Islands will be merged into single one.
Be careful with `epsilon = 0`, It can result a lots of small islands touching each others but don't intersect.
Also very small `epsilon` can result poor performance without good output.
`epsilon` about 1..3 of pixel-space recommended (normalize it by you self).
"""
# Занимается разбиением множества точек на прямоугольные непересекающиеся подмноджества
__slots__ = ('bboxes', 'merges')
def __init__(self):
self.bboxes = list() # type: List[Island]
""" All found non-overlapping `Islands`. """
self.merges = 0 # Для диагностических целей
""" For diagnostic and debug purposes. Number of Island merges happened. """
def __str__(self) -> str: return common_str_slots(self, self.__slots__)
def __repr__(self) -> str: return common_str_slots(self, self.__slots__)
def add_bbox(self, bbox: 'Island', epsilon: 'float' = 0):
# Добавляет набор точек
if not bbox.is_valid():
raise ValueError("Invalid bbox!")
bbox_to_add = bbox
while bbox_to_add is not None:
target_idx = -1
# Поиск первго бокса с которым пересекается текущий
for i in range(len(self.bboxes)):
if self.bboxes[i] is bbox_to_add:
raise ValueError("bbox already in bboxes:", (bbox_to_add, self.bboxes[i], self.bboxes))
# TODO
if self.bboxes[i].is_inside_bbox(bbox_to_add, epsilon=epsilon):
return # Если вставляемый bbox внутри существующего, то ничего не надо делать
if self.bboxes[i].is_intersect(bbox_to_add, epsilon=epsilon):
target_idx = i
break
if target_idx == -1:
# Пересечение не найдено, добавляем
self.bboxes.append(bbox_to_add)
bbox_to_add = None
else:
# Пересечение найдено - вытаскиваем, соединяем, пытаемся добавить еще раз
ejected = self.bboxes[target_idx]
del self.bboxes[target_idx]
# print("add_bbox: extending: ", (ejected, bbox_to_add))
# print("add_bbox: merges: ", self.merges)
# print("add_bbox: len(bboxes): ", len(self.bboxes))
ejected.extend_by_bbox(bbox_to_add)
bbox_to_add = ejected
self.merges += 1
def add_seq(self, vec2s: 'Iterable[Vector]', epsilon: 'float' = 0):
vec2s = list(vec2s)
if len(vec2s) != 0:
newbbox = Island(None, None)
newbbox.extend_by_vec2s(vec2s)
# print("add_seq: add_bbox: ", newbbox)
self.add_bbox(newbbox, epsilon=epsilon)
else:
print("Warn: add_seq: empty vec2s!")
def get_extends(self):
return sum(bbox.extends for bbox in self.bboxes)
Functions
def remove_all_uv_layers(obj: Object)
-
Remove all UV Layers from Mesh-Object.
Expand source code
def remove_all_uv_layers(obj: 'Object'): """ Remove all UV Layers from Mesh-Object. """ mesh = _commons.get_mesh_safe(obj) while len(mesh.uv_layers) > 0: mesh.uv_layers.remove(mesh.uv_layers[0])
def repack_active_uv(obj: Object, get_scale: Optional[Callable[[Material], float]] = None, rotate: bool = None, margin: float = 0.0)
-
Repack active UV Layer of a given Object with some adjustments: - Runs
bpy.ops.uv.average_islands_scale
- Rescales islands according toget_scale
per material - Runsbpy.ops.uv.pack_islands
with givenrotate
andmargin
Expand source code
def repack_active_uv( obj: 'Object', get_scale: 'Optional[Callable[[Material], float]]' = None, rotate: 'bool' = None, margin: 'float' = 0.0 ): """ Repack active UV Layer of a given Object with some adjustments: - Runs `bpy.ops.uv.average_islands_scale` - Rescales islands according to `get_scale` per material - Runs `bpy.ops.uv.pack_islands` with given `rotate` and `margin` """ e = _commons.ensure_op_finished try: _commons.ensure_deselect_all_objects() _commons.activate_object(obj) # Перепаковка... e(_bpy.ops.object.mode_set_with_submode(mode='EDIT', mesh_select_mode={'FACE'}), name='object.mode_set_with_submode') e(_bpy.ops.mesh.reveal(select=True), name='mesh.reveal') e(_bpy.ops.mesh.select_all(action='SELECT'), name='mesh.select_all') _C.scene.tool_settings.use_uv_select_sync = True area_type = _C.area.type try: _C.area.type = 'IMAGE_EDITOR' _C.area.ui_type = 'UV' e(_bpy.ops.uv.reveal(select=True), name='uv.reveal') e(_bpy.ops.mesh.select_all(action='SELECT'), name='mesh.select_all') e(_bpy.ops.uv.select_all(action='SELECT'), name='uv.select_all') e(_bpy.ops.uv.average_islands_scale(), name='uv.average_islands_scale') for index in range(len(obj.material_slots)): scale = 1.0 if get_scale is not None: scale = get_scale(obj.material_slots[index].material) if scale <= 0 or scale == 1.0: continue _C.scene.tool_settings.use_uv_select_sync = True e(_bpy.ops.mesh.select_all(action='DESELECT'), name='mesh.select_all', index=index) e(_bpy.ops.uv.select_all(action='DESELECT'), name='uv.select_all', index=index) obj.active_material_index = index if 'FINISHED' in _bpy.ops.object.material_slot_select(): # Может быть не FINISHED если есть не использованые материалы e(_bpy.ops.uv.select_linked(), name='uv.select_linked', index=index) e(_bpy.ops.transform.resize(value=(scale, scale, scale)), name='transform.resize', value=scale, index=index) e(_bpy.ops.mesh.select_all(action='SELECT'), name='mesh.select_all') e(_bpy.ops.uv.select_all(action='SELECT'), name='uv.select_all') e(_bpy.ops.uv.pack_islands(rotate=rotate, margin=margin), name='uv.pack_islands') e(_bpy.ops.uv.select_all(action='DESELECT'), name='uv.select_all') e(_bpy.ops.mesh.select_all(action='DESELECT'), name='mesh.select_all') finally: _C.area.type = area_type finally: e(_bpy.ops.object.mode_set(mode='OBJECT'), name='object.mode_set')
def uv_area(poly: MeshPolygon, uv_layer_data: Union[bpy_prop_collection, List[MeshUVLoop]])
-
Returns area of given polygon on given UV Layer in normalized (0..1) space.
Expand source code
def uv_area(poly: 'MeshPolygon', uv_layer_data: 'Union[bpy_prop_collection, List[MeshUVLoop]]'): """ Returns area of given polygon on given UV Layer in normalized (0..1) space. """ # tuple чуть-чуть быстрее на малых длинах, тестил через timeit return _commons.poly2_area2(tuple(uv_layer_data[loop].uv for loop in poly.loop_indices))
Classes
class Island (mn: Optional[Vector], mx: Optional[Vector])
-
Internal class of
IslandsBuilder
. Describes rectangle region of UV Layer.Expand source code
class Island: """ Internal class of `IslandsBuilder`. Describes rectangle region of UV Layer. """ __slots__ = ('mn', 'mx', 'extends') def __init__(self, mn: 'Optional[Vector]', mx: 'Optional[Vector]'): self.mn = mn # type: Optional[Vector] self.mx = mx # type: Optional[Vector] self.extends = 0 # Для диагностических целей def __str__(self) -> str: return common_str_slots(self, self.__slots__) def __repr__(self) -> str: return common_str_slots(self, self.__slots__) def is_valid(self): return self.mn is not None and self.mx is not None def is_inside_vec2(self, item: 'Vector', epsilon: 'float' = 0): if type(item) != _Vector: raise ValueError("type(item) != Vector") if len(item) != 2: raise ValueError("len(item) != 2") if self.mn is None or self.mx is None: return False mnx, mny = self.mn.x - epsilon, self.mn.y - epsilon mxx, mxy = self.mx.x + epsilon, self.mx.y + epsilon return mnx <= item.x <= mxx and mny <= item.y <= mxy def is_inside_bbox(self, inner: 'Island', epsilon: 'float' = 0) -> bool: # Проверяет лежит ли inner внутри self if self.mn is None or self.mx is None or inner.mn is None or inner.mx is None: return False if inner.mx.x + epsilon >= self.mx.x or inner.mx.y + epsilon >= self.mx.y: return False if inner.mn.x - epsilon <= self.mn.x or inner.mn.y - epsilon <= self.mn.y: return False return True def get_points(self) -> 'Sequence[Vector]': return self.mn, self.mx, _Vector((self.mn.x, self.mx.y)), _Vector((self.mx.x, self.mn.y)) def any_inside_vec2(self, items: 'Iterable[Vector]', epsilon: 'float' = 0): return any(self.is_inside_vec2(x, epsilon=epsilon) for x in items) def is_intersect(self, other: 'Island', epsilon: 'float' = 0): return any(self.is_inside_vec2(x, epsilon=epsilon) for x in other.get_points()) def extend_by_vec2(self, vec2: 'Vector'): if self.mn is None: self.mn = vec2.xy else: self.mn.x = min(self.mn.x, vec2.x) self.mn.y = min(self.mn.y, vec2.y) if self.mx is None: self.mx = vec2.xy else: self.mx.x = max(self.mx.x, vec2.x) self.mx.y = max(self.mx.y, vec2.y) self.extends += 1 def extend_by_vec2s(self, vec2s: 'Iterable[Vector]'): for vec2 in vec2s: self.extend_by_vec2(vec2) def extend_by_bbox(self, other: 'Island'): if self is other: raise ValueError("self is other", self, other) if not other.is_valid(): raise ValueError("other bbox is not valid", self, other) self.extend_by_vec2s(other.get_points()) if not self.is_valid(): raise ValueError("Invalid after extend_by_bbox", self, other) def get_area(self) -> 'float': if not self.is_valid(): raise ValueError("bbox is not valid", self) return (self.mx.x - self.mn.x) * (self.mx.y - self.mn.y)
Instance variables
var extends
-
Return an attribute of instance, which is of type owner.
var mn
-
Return an attribute of instance, which is of type owner.
var mx
-
Return an attribute of instance, which is of type owner.
Methods
def any_inside_vec2(self, items: Iterable[Vector], epsilon: float = 0)
-
Expand source code
def any_inside_vec2(self, items: 'Iterable[Vector]', epsilon: 'float' = 0): return any(self.is_inside_vec2(x, epsilon=epsilon) for x in items)
def extend_by_bbox(self, other: Island)
-
Expand source code
def extend_by_bbox(self, other: 'Island'): if self is other: raise ValueError("self is other", self, other) if not other.is_valid(): raise ValueError("other bbox is not valid", self, other) self.extend_by_vec2s(other.get_points()) if not self.is_valid(): raise ValueError("Invalid after extend_by_bbox", self, other)
def extend_by_vec2(self, vec2: Vector)
-
Expand source code
def extend_by_vec2(self, vec2: 'Vector'): if self.mn is None: self.mn = vec2.xy else: self.mn.x = min(self.mn.x, vec2.x) self.mn.y = min(self.mn.y, vec2.y) if self.mx is None: self.mx = vec2.xy else: self.mx.x = max(self.mx.x, vec2.x) self.mx.y = max(self.mx.y, vec2.y) self.extends += 1
def extend_by_vec2s(self, vec2s: Iterable[Vector])
-
Expand source code
def extend_by_vec2s(self, vec2s: 'Iterable[Vector]'): for vec2 in vec2s: self.extend_by_vec2(vec2)
def get_area(self) ‑> float
-
Expand source code
def get_area(self) -> 'float': if not self.is_valid(): raise ValueError("bbox is not valid", self) return (self.mx.x - self.mn.x) * (self.mx.y - self.mn.y)
def get_points(self) ‑> Sequence[Vector]
-
Expand source code
def get_points(self) -> 'Sequence[Vector]': return self.mn, self.mx, _Vector((self.mn.x, self.mx.y)), _Vector((self.mx.x, self.mn.y))
def is_inside_bbox(self, inner: Island, epsilon: float = 0) ‑> bool
-
Expand source code
def is_inside_bbox(self, inner: 'Island', epsilon: 'float' = 0) -> bool: # Проверяет лежит ли inner внутри self if self.mn is None or self.mx is None or inner.mn is None or inner.mx is None: return False if inner.mx.x + epsilon >= self.mx.x or inner.mx.y + epsilon >= self.mx.y: return False if inner.mn.x - epsilon <= self.mn.x or inner.mn.y - epsilon <= self.mn.y: return False return True
def is_inside_vec2(self, item: Vector, epsilon: float = 0)
-
Expand source code
def is_inside_vec2(self, item: 'Vector', epsilon: 'float' = 0): if type(item) != _Vector: raise ValueError("type(item) != Vector") if len(item) != 2: raise ValueError("len(item) != 2") if self.mn is None or self.mx is None: return False mnx, mny = self.mn.x - epsilon, self.mn.y - epsilon mxx, mxy = self.mx.x + epsilon, self.mx.y + epsilon return mnx <= item.x <= mxx and mny <= item.y <= mxy
def is_intersect(self, other: Island, epsilon: float = 0)
-
Expand source code
def is_intersect(self, other: 'Island', epsilon: 'float' = 0): return any(self.is_inside_vec2(x, epsilon=epsilon) for x in other.get_points())
def is_valid(self)
-
Expand source code
def is_valid(self): return self.mn is not None and self.mx is not None
class IslandsBuilder
-
Internal class of
BaseAtlasBaker
, but can be used standalone. Finds non-overlapping bounding boxes on UV Layer of UV polygons. Just provide UV coords of all your polygons intoadd_seq
oradd_bbox
,bboxes
will contain all found non-overlapping rectangle regions.epsilon
is a search precision in normalized (0..1) space. If distance between two Islands is less than epsilon these two Islands will be merged into single one. Be careful withepsilon = 0
, It can result a lots of small islands touching each others but don't intersect. Also very smallepsilon
can result poor performance without good output.epsilon
about 1..3 of pixel-space recommended (normalize it by you self).Expand source code
class IslandsBuilder: """ Internal class of `kawa_scripts.atlas_baker.BaseAtlasBaker`, but can be used standalone. Finds non-overlapping bounding boxes on UV Layer of UV polygons. Just provide UV coords of all your polygons into `add_seq` or `add_bbox`, `bboxes` will contain all found non-overlapping rectangle regions. `epsilon` is a search precision in normalized (0..1) space. If distance between two Islands is less than epsilon these two Islands will be merged into single one. Be careful with `epsilon = 0`, It can result a lots of small islands touching each others but don't intersect. Also very small `epsilon` can result poor performance without good output. `epsilon` about 1..3 of pixel-space recommended (normalize it by you self). """ # Занимается разбиением множества точек на прямоугольные непересекающиеся подмноджества __slots__ = ('bboxes', 'merges') def __init__(self): self.bboxes = list() # type: List[Island] """ All found non-overlapping `Islands`. """ self.merges = 0 # Для диагностических целей """ For diagnostic and debug purposes. Number of Island merges happened. """ def __str__(self) -> str: return common_str_slots(self, self.__slots__) def __repr__(self) -> str: return common_str_slots(self, self.__slots__) def add_bbox(self, bbox: 'Island', epsilon: 'float' = 0): # Добавляет набор точек if not bbox.is_valid(): raise ValueError("Invalid bbox!") bbox_to_add = bbox while bbox_to_add is not None: target_idx = -1 # Поиск первго бокса с которым пересекается текущий for i in range(len(self.bboxes)): if self.bboxes[i] is bbox_to_add: raise ValueError("bbox already in bboxes:", (bbox_to_add, self.bboxes[i], self.bboxes)) # TODO if self.bboxes[i].is_inside_bbox(bbox_to_add, epsilon=epsilon): return # Если вставляемый bbox внутри существующего, то ничего не надо делать if self.bboxes[i].is_intersect(bbox_to_add, epsilon=epsilon): target_idx = i break if target_idx == -1: # Пересечение не найдено, добавляем self.bboxes.append(bbox_to_add) bbox_to_add = None else: # Пересечение найдено - вытаскиваем, соединяем, пытаемся добавить еще раз ejected = self.bboxes[target_idx] del self.bboxes[target_idx] # print("add_bbox: extending: ", (ejected, bbox_to_add)) # print("add_bbox: merges: ", self.merges) # print("add_bbox: len(bboxes): ", len(self.bboxes)) ejected.extend_by_bbox(bbox_to_add) bbox_to_add = ejected self.merges += 1 def add_seq(self, vec2s: 'Iterable[Vector]', epsilon: 'float' = 0): vec2s = list(vec2s) if len(vec2s) != 0: newbbox = Island(None, None) newbbox.extend_by_vec2s(vec2s) # print("add_seq: add_bbox: ", newbbox) self.add_bbox(newbbox, epsilon=epsilon) else: print("Warn: add_seq: empty vec2s!") def get_extends(self): return sum(bbox.extends for bbox in self.bboxes)
Instance variables
var bboxes
-
All found non-overlapping
Islands
. var merges
-
For diagnostic and debug purposes. Number of Island merges happened.
Methods
def add_bbox(self, bbox: Island, epsilon: float = 0)
-
Expand source code
def add_bbox(self, bbox: 'Island', epsilon: 'float' = 0): # Добавляет набор точек if not bbox.is_valid(): raise ValueError("Invalid bbox!") bbox_to_add = bbox while bbox_to_add is not None: target_idx = -1 # Поиск первго бокса с которым пересекается текущий for i in range(len(self.bboxes)): if self.bboxes[i] is bbox_to_add: raise ValueError("bbox already in bboxes:", (bbox_to_add, self.bboxes[i], self.bboxes)) # TODO if self.bboxes[i].is_inside_bbox(bbox_to_add, epsilon=epsilon): return # Если вставляемый bbox внутри существующего, то ничего не надо делать if self.bboxes[i].is_intersect(bbox_to_add, epsilon=epsilon): target_idx = i break if target_idx == -1: # Пересечение не найдено, добавляем self.bboxes.append(bbox_to_add) bbox_to_add = None else: # Пересечение найдено - вытаскиваем, соединяем, пытаемся добавить еще раз ejected = self.bboxes[target_idx] del self.bboxes[target_idx] # print("add_bbox: extending: ", (ejected, bbox_to_add)) # print("add_bbox: merges: ", self.merges) # print("add_bbox: len(bboxes): ", len(self.bboxes)) ejected.extend_by_bbox(bbox_to_add) bbox_to_add = ejected self.merges += 1
def add_seq(self, vec2s: Iterable[Vector], epsilon: float = 0)
-
Expand source code
def add_seq(self, vec2s: 'Iterable[Vector]', epsilon: 'float' = 0): vec2s = list(vec2s) if len(vec2s) != 0: newbbox = Island(None, None) newbbox.extend_by_vec2s(vec2s) # print("add_seq: add_bbox: ", newbbox) self.add_bbox(newbbox, epsilon=epsilon) else: print("Warn: add_seq: empty vec2s!")
def get_extends(self)
-
Expand source code
def get_extends(self): return sum(bbox.extends for bbox in self.bboxes)