Source code for miniworlds.actors.shapes.shapes

from typing import Tuple, Union

import pygame
import pygame.gfxdraw

import miniworlds.actors.actor as actor
import miniworlds.actors.shapes.shape_costume as shape_costume
import miniworlds.positions.vector as world_vector
from miniworlds.base.exceptions import (
    EllipseWrongArgumentsError,
    LineFirstArgumentError,
    LineSecondArgumentError,
)


[docs] class Shape(actor.Actor): """Shape is the parent class for various geometric objects that can be created. Each geometric object has the following properties: * border: The border thickness of the object. * is_filled: True/False if the object should be filled. * fill_color: The fill color of the object * border_color: The border color of the object. .. image:: ../_images/shapes.png :width: 60% :alt: Shapes """ def __init__(self, position: Tuple[float, float] = (0, 0), *args, **kwargs): super().__init__(position, *args, **kwargs)
[docs] def new_costume(self): return shape_costume.ShapeCostume(self)
[docs] class Circle(Shape): """ A circular shape, definied by position and radius .. image:: ../_images/circle.png :width: 120px :alt: Circle Args: position: The position as 2-tuple. The circle is created with its center at the position radius: The radius of the circle Examples: Create a circle at center position (200,100) with radius 20: .. code-block:: python Circle((200, 100), 20) Create a circle at topleft position .. code-block:: python miniworlds.Circle.from_topleft((100,100),50) """ def __init__(self, position=(0, 0), radius: float = 10, *args, **kwargs): self._radius = radius super().__init__(position, *args, **kwargs) self.position_manager.set_size((self._radius * 2, self._radius * 2), scale = False) @property def radius(self): """The radius of the circle. If you change the circle-size (e.g. with self.size = (x, y), the radius value will be changed too. """ return self._radius @radius.setter def radius(self, value): self._radius = value self.position_manager.set_size((self._radius * 2, self._radius * 2), scale = False) self.costume.set_dirty("scale", self.costume.RELOAD_ACTUAL_IMAGE) def _set_physics(self): self.physics.shape_type = "circle" self.physics.can_move = True self.physics.stable = False
[docs] def new_costume(self): return shape_costume.CircleCostume(self)
[docs] @classmethod def from_topleft(cls, position: tuple, radius: int, **kwargs): """Creates a circle with topleft at position""" circle = cls(position, radius, **kwargs) circle.origin = "topleft" return circle
[docs] @classmethod def from_center(cls, position: tuple, radius: float, **kwargs): """Creates a circle with center at position""" circle = cls(position, radius, **kwargs) circle.origin = "center" return circle
[docs] class Point(Circle): """A point is a Circle with Radius 1"""
[docs] def __init__(self, position: tuple): """Init a Point at specified position""" super().__init__(position, 1)
[docs] class Ellipse(Shape): """An elliptic shape. .. image:: ../_images/ellipse.png :width: 120px :alt: Ellipse Args: position: The position as 2-tuple. The ellipse is created at topleft position width: The width of the ellipse height: The height of the ellipse Examples: Create an ellipse at topleft position (200,100) with width 20 and height 30 .. code-block:: python Ellipse((200, 100), 20, 30) Create an ellipse at center-position (200,100) width width 10 and height 10 .. code-block:: python miniworlds.Ellipse.from_center((100,100),10, 10) (Alternative) Create an ellipse at center-position (200,100) with width 10 and height 10 .. code-block:: python e = miniworlds.Ellipse((100,100),10, 10) e.center = e.position """ def __init__( self, position=(0, 0), width: float = 10, height: float = 10, *args, **kwargs ): self.check_arguments(position, width, height) super().__init__(position, *args, **kwargs) self.costume = shape_costume.EllipseCostume(self) self._border = 1 self.size = (width, height)
[docs] def check_arguments(self, position, width, height): if type(position) not in [tuple, None]: raise EllipseWrongArgumentsError()
[docs] @classmethod def from_topleft(cls, position: tuple, width: float, height: float, **kwargs): """Creates an ellipse with topleft at position""" ellipse = cls(position, width, height, **kwargs) ellipse.origin = "topleft" return ellipse
[docs] @classmethod def from_center(cls, position: tuple, width: float, height: float, **kwargs): """Creates an ellipse with center at position""" ellipse = cls(position, width, height, **kwargs) ellipse.origin = "center" return ellipse
class Arc(Ellipse): """ An elliptic Arc. Args: position: The position as 2-tuple. The ellipse is created at topleft position width: The width of the ellipse height: The height of the ellipse start_angle: The start_angle end_angle: end_angle """ def __init__( self, position=(0, 0), width: float = 10, height: float = 10, start_angle: float = 0, end_angle: float = 0, *args, **kwargs, ): self._start_angle = start_angle self._end_angle = end_angle if start_angle == end_angle: self._end_angle = start_angle + 360 super().__init__(position, width, height) self.costume = shape_costume.ArcCostume(self) @property def start_angle(self): return self._start_angle @start_angle.setter def start_angle(self, value): self._start_angle = value self.costume.set_dirty("draw_shapes", self.costume.RELOAD_ACTUAL_IMAGE) @property def end_angle(self): return self._end_angle @end_angle.setter def end_angle(self, value): self._end_angle = value self.costume.set_dirty("draw_shapes", self.costume.RELOAD_ACTUAL_IMAGE)
[docs] class Line(Shape): """A Line-Shape defined by start_position and end_position. .. image:: ../_images/ellipse.png :width: 120px :alt: Line Args: start_position: The start_position as 2-tuple. end_position: The end_position as 2-tuple. Examples: Create a line from (200, 100) to (400, 100) .. code-block:: python Line((200, 100), (400,100)) Create a line from (200, 100) to (400, 100) .. code-block:: python l = Line((200, 100), (400,100)) l.border = 2 """ def __init__( self, start_position: Union[tuple], end_position: Union[tuple], *args, **kwargs ): if not start_position or not end_position: start_position = (0, 0) end_position = (0, 0) if type(start_position) not in [tuple, None]: raise LineFirstArgumentError(start_position) if type(end_position) not in [tuple, None]: raise LineSecondArgumentError(end_position) self._length = 0 self._start_position = start_position self._end_position = end_position super().__init__(start_position) self.costume = shape_costume.LineCostume(self) self._update_size() @property def start_position(self): return self._start_position start = start_position @start_position.setter def start_position(self, value): self._start_position = value self._update_size() @property def end_position(self): return self._end_position end = end_position @end_position.setter def end_position(self, value): self._end_position = value self._update_size() @property def direction(self): return self.position_manager.get_direction() @direction.setter def direction(self, value): self.position_manager.set_direction(value) vec_center = world_vector.Vector.from_position(self.center) direction_vector = world_vector.Vector.from_direction(self.direction) direction_vector = direction_vector.normalize() * self._length * 0.5 self._start_position = (vec_center + direction_vector).to_position() self._end_position = (vec_center - direction_vector).to_position() def _set_physics(self): self.physics.shape_type = "line" self.physics.simulation = "manual"
[docs] def get_bounding_box(self): width = abs(self.start_position[0] - self.end_position[0]) + self.thickness height = abs(self.start_position[1] - self.end_position[1]) + self.thickness box = pygame.Rect( min(self.start_position[0], self.end_position[0]) - int(0.5 * self.thickness), min(self.start_position[1], self.end_position[1]) - int(0.5 * self.thickness), width, height, ) return box
def _update_size(self): self._length = self.world.distance_to(self.start_position, self._end_position) self.position_manager.set_size( (self.thickness, self._length + 2 * self.thickness), scale=False ) self.position_manager.set_direction( self.world.direction_to(self.start_position, self._end_position) ) vec_to_center = ( world_vector.Vector.from_positions(self.start_position, self.end_position) * 0.5 ) self.center = ( self.start_position[0] + vec_to_center.x, self.start_position[1] + vec_to_center.y, ) self.costume.set_dirty("all", 1) @property def length(self): return self._length @property def thickness(self): """-> see border""" return self.costume.border @thickness.setter def thickness(self, value): self.costume.border = value self._update_size() @property def border(self): """-> see border""" return self.costume.border @border.setter def border(self, value): self.costume.border = value self._update_size() line_width = thickness
[docs] class Rectangle(Shape): """ A rectangular shape defined by position, width and height .. image:: ../_images/ellipse.png :width: 120px :alt: Line Args: topleft: Topleft Position of Rect height: The height of the rect width: The width of the rect Examples: Create a rect with the topleft position (200, 100), the width 20 and the height 10 .. code-block:: python Rectangle((200, 100), 20, 10) """ def __init__( self, position=(0, 0), width: float = 10, height: float = 10, *args, **kwargs ): args = (width, height, *args) super().__init__(position, *args, **kwargs) self.costume = shape_costume.RectangleCostume(self) self.size = (width, height) def _validate_arguments(self, position, *args, **kwargs): super()._validate_arguments(position, *args, **kwargs) width = args[0] height = args[1] if type(width) not in [int, float]: raise TypeError( "width of Rectangle should be int or float " + str(type(width)) ) if type(height) not in [int, float]: raise TypeError( "height of Rectangle should be int or float but is " + str(type(height)) ) def _set_physics(self): self.physics.shape_type = "rect" self.physics.stable = False self.physics.correct_angle = 90
[docs] @classmethod def from_topleft(cls, position: tuple, width: float, height: float): """Creates a rectangle with topleft at position""" rectangle = cls(position, width, height).center return rectangle
[docs] @classmethod def from_center(cls, position: tuple, width: float, height: float): """Creates a rectangle with center at position""" rectangle = cls(position, width, height) rectangle.center = rectangle.position return rectangle
[docs] class Polygon(Shape): """ A Polygon-Shape. Args: point-list: A list of points Examples: Example Creation of a polygon >>> Polygon([(200, 100), (400,100), (0, 0)]) Creates a red polygon with the vertices (200, 100) , (400, 100) and (0, 0) Example Creation of a filled polygon >>> Polygon([(200, 100), (400,100), (0, 0)]) Creates a red polygon with the vertices (200, 100) , (400, 100) and (0, 0) """ def __init__(self, pointlist, *args, **kwargs): super().__init__((0, 0)) self._pointlist = pointlist self.costume = shape_costume.PolygonCostume(self, pointlist) @property def pointlist(self): return self._pointlist @pointlist.setter def pointlist(self, value: int): self._pointlist = value
class Triangle(Polygon): def __init__(self, p1: Tuple, p2: Tuple, p3: Tuple, *args, **kwargs): pointlist = [p1, p2, p3] super().__init__(pointlist)