Spaces:
Sleeping
Sleeping
| """ | |
| 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 = {} | |
| def rect(self) -> Tuple['Rect', ...]: | |
| # noinspection PyTypeChecker | |
| return tuple(self._rect.values()) | |
| def point(self) -> Tuple['Point', ...]: | |
| # noinspection PyTypeChecker | |
| return tuple(self._point.values()) | |
| def slab(self) -> Tuple['Slab', ...]: | |
| # noinspection PyTypeChecker | |
| return tuple(self._slab.values()) | |
| def room(self) -> Tuple['Room', ...]: | |
| # noinspection PyTypeChecker | |
| return tuple(self._room.values()) | |
| 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 | |
| def mutator_angle(self) -> float: | |
| return float(0 if self._last_mutation is None else self._last_mutation['angle']) | |
| def mutator_scale_x(self) -> float: | |
| return float(1 if self._last_mutation is None else self._last_mutation['sx']) | |
| def mutator_scale_y(self) -> float: | |
| return float(1 if self._last_mutation is None else self._last_mutation['sy']) | |
| 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 | |