rawanessam's picture
Update MLStructFP/db/_floor.py
cebb92d verified
"""
MLSTRUCT-FP - DB - CFLOOR
Floor component, container of objects.
"""
__all__ = ['Floor']
from MLStructFP.utils import GeomPoint2D, BoundingBox
from MLStructFP._types import Dict, Tuple, Optional, TYPE_CHECKING, NumberType, NumberInstance
import math
import os
import plotly.graph_objects as go
if TYPE_CHECKING:
from MLStructFP.db._c import BaseComponent
from MLStructFP.db._c_rect import Rect
from MLStructFP.db._c_point import Point
from MLStructFP.db._c_slab import Slab
from MLStructFP.db._c_room import Room
from MLStructFP.db._c_item import Item
class Floor(object):
"""
FP Floor.
"""
_bb: Optional['BoundingBox']
_last_mutation: Optional[Dict[str, float]]
_rect: Dict[int, 'Rect'] # id => rect
_point: Dict[int, 'Point'] # id => point
_slab: Dict[int, 'Slab'] # id => slab
_room: Dict[int, 'Room'] # id => room
_item: Dict[int, 'Item'] # id => item
category: int
category_name: str
elevation: bool
id: int
image_path: str
image_scale: float
project_id: int
project_label: str
def __init__(self, floor_id: int, image_path: str, image_scale: NumberType,
project_id: int, project_label: str = '', category: int = 0, category_name: str = '',
elevation: bool = False) -> None:
"""
Constructor.
:param floor_id: Floor ID
:param image_path: Image path
:param image_scale: Image scale (px to units)
:param project_id: Project ID
:param project_label: Project label (default empty)
:param category: Project category
:param category_name: Project category name
:param elevation: Elevation mode
"""
assert isinstance(floor_id, int) and floor_id > 0
assert os.path.isfile(image_path), f'Image file {image_path} does not exist'
assert isinstance(image_scale, NumberInstance) and image_scale > 0
assert isinstance(elevation, bool)
self.category = category
self.category_name = category_name
self.elevation = elevation
self.id = floor_id
self.image_path = image_path.replace('\\', '/')
self.image_scale = float(image_scale)
self.project_id = project_id
self.project_label = project_label
self._bb = None
self._last_mutation = None
# Object containers
self._rect = {}
self._point = {}
self._slab = {}
self._room = {}
self._item = {}
@property
def rect(self) -> Tuple['Rect', ...]:
# noinspection PyTypeChecker
return tuple(self._rect.values())
@property
def point(self) -> Tuple['Point', ...]:
# noinspection PyTypeChecker
return tuple(self._point.values())
@property
def slab(self) -> Tuple['Slab', ...]:
# noinspection PyTypeChecker
return tuple(self._slab.values())
@property
def room(self) -> Tuple['Room', ...]:
# noinspection PyTypeChecker
return tuple(self._room.values())
@property
def item(self) -> Tuple['Item', ...]:
# noinspection PyTypeChecker
return tuple(self._item.values())
def plot_basic(self) -> 'go.Figure':
"""
Plot basic objects.
:return: Go figure object
"""
return self.plot_complex(fill=False)
def plot_complex(
self,
fill: bool = True,
draw_rect: bool = True,
draw_point: bool = True,
draw_slab: bool = True,
draw_room: bool = True,
draw_item: bool = True,
**kwargs
) -> 'go.Figure':
"""
Complex plot.
:param fill: Fill figure
:param draw_rect: Draw wall rects
:param draw_point: Draw points
:param draw_slab: Draw slabs
:param draw_room: Draw rooms
:param draw_item: Draw items
:param kwargs: Optional keyword arguments
"""
fig = go.Figure()
if draw_slab:
for s in self.slab:
s.plot_plotly(
fig=fig,
fill=fill,
**kwargs
)
if draw_room:
for r in self.room:
r.plot_plotly(
fig=fig,
fill=fill,
**kwargs
)
if draw_item:
for i in self.item:
i.plot_plotly(
fig=fig,
fill=fill,
**kwargs
)
if draw_rect:
for wr in self.rect:
wr.plot_plotly(
fig=fig,
fill=fill,
**kwargs
)
if draw_point:
for wp in self.point:
wp.plot_plotly(
fig=fig,
fill=fill,
**kwargs
)
grid_color = kwargs.get('plot_gridcolor', '#d7d7d7')
fig.update_layout(
plot_bgcolor=kwargs.get('plot_bgcolor', '#ffffff'),
showlegend=kwargs.get('show_legend', True),
title=f'Floor - ID {self.id}',
font=dict(
size=kwargs.get('font_size', 14),
),
yaxis_zeroline=False,
xaxis_zeroline=False,
xaxis=dict(
gridcolor=grid_color
),
yaxis=dict(
gridcolor=grid_color,
scaleanchor='x',
scaleratio=1
)
)
show_grid = kwargs.get('show_grid', True)
fig.update_layout(xaxis_showgrid=show_grid, yaxis_showgrid=show_grid)
fig.update_xaxes(title_text='x (m)', hoverformat='.3f')
fig.update_yaxes(title_text='y (m)', hoverformat='.3f')
return fig
def mutate(self, angle: NumberType = 0, sx: NumberType = 1, sy: NumberType = 1,
scale_first: bool = True) -> 'Floor':
"""
Apply mutator for each object within the floor.
:param angle: Angle
:param sx: Scale on x-axis
:param sy: Scale on y-axis
:param scale_first: Scale first, then rotate
:return: Floor reference
"""
assert isinstance(angle, NumberInstance)
assert isinstance(sx, NumberInstance) and sx != 0
assert isinstance(sy, NumberInstance) and sy != 0
# Undo last mutation
if self._last_mutation is not None:
_angle, _sx, _sy = self.mutator_angle, self.mutator_scale_x, self.mutator_scale_y
self._last_mutation = None # Reset mutation
self.mutate(-_angle, 1 / _sx, 1 / _sy, scale_first=False) # Reverse operation
# Apply mutation
rotation_center = GeomPoint2D()
o: Tuple['BaseComponent']
for o in (self.rect, self.point, self.slab, self.room, self.item):
for c in o:
for p in c.points:
if not scale_first:
p.rotate(rotation_center, angle)
p.x *= sx
p.y *= sy
if scale_first:
p.rotate(rotation_center, angle)
# Update mutation
self._bb = None
self._last_mutation = {
'angle': angle,
'sx': sx,
'sy': sy
}
return self
@property
def mutator_angle(self) -> float:
return float(0 if self._last_mutation is None else self._last_mutation['angle'])
@property
def mutator_scale_x(self) -> float:
return float(1 if self._last_mutation is None else self._last_mutation['sx'])
@property
def mutator_scale_y(self) -> float:
return float(1 if self._last_mutation is None else self._last_mutation['sy'])
@property
def bounding_box(self) -> 'BoundingBox':
"""
:return: Return the bounding box of the floor, calculated using the slab and the points from the rects.
"""
if self._bb is not None:
return self._bb
xmin = math.inf
xmax = -math.inf
ymin = math.inf
ymax = -math.inf
o: Tuple['BaseComponent']
for o in (self.rect, self.slab):
for c in o:
for p in c.points:
xmin = min(xmin, p.x)
xmax = max(xmax, p.x)
ymin = min(ymin, p.y)
ymax = max(ymax, p.y)
self._bb = BoundingBox(xmin, xmax, ymin, ymax)
return self._bb