Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/shapepy/bool2d/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
Functions to convert from typical data types into SubSetR2
"""

from ..tools import Is
from ..geometry.base import IGeometricCurve
from ..tools import Is, To
from .base import SubSetR2
from .curve import SingleCurve
from .point import SinglePoint


def from_any(subset: SubSetR2) -> SubSetR2:
"""
Converts an object into a SubSetR2
"""
if not Is.instance(subset, SubSetR2):
raise TypeError
return subset
if Is.instance(subset, SubSetR2):
return subset
if Is.instance(subset, IGeometricCurve):
return SingleCurve(subset)
return SinglePoint(To.point(subset))
79 changes: 79 additions & 0 deletions src/shapepy/bool2d/curve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
Defines a SingleCurve class, that represents a SubSet of the plane
that contains a continous set of points on the plane
"""

from __future__ import annotations

from copy import copy
from typing import Tuple, Union

from ..geometry.base import IGeometricCurve
from ..geometry.point import Point2D
from ..loggers import debug
from ..scalar.angle import Angle
from ..scalar.reals import Real
from ..tools import Is
from .base import SubSetR2
from .density import Density


class SingleCurve(SubSetR2):
"""SingleCurve class

It represents a subset on the plane of continous points
"""

def __init__(self, curve: IGeometricCurve):
if not Is.instance(curve, IGeometricCurve):
raise TypeError(f"Invalid: {type(curve)} != {IGeometricCurve}")
self.__curve = curve

@property
def internal(self) -> IGeometricCurve:
"""Gives the geometric curve that defines the SingleCurve SubSetR2"""
return self.__curve

def __copy__(self) -> SingleCurve:
return SingleCurve(self.internal)

def __deepcopy__(self, memo) -> SingleCurve:
return SingleCurve(copy(self.__curve))

def __str__(self) -> str: # pragma: no cover # For debug
return "{" + str(self.__curve) + "}"

def __eq__(self, other: SubSetR2) -> bool:
"""Compare two subsets

Parameters
----------
other: SubSetR2
The subset to compare

:raises ValueError: If ``other`` is not a SubSetR2 instance
"""
if not Is.instance(other, SubSetR2):
raise ValueError
return (
Is.instance(other, SingleCurve) and self.internal == other.internal
)

@debug("shapepy.bool2d.shape")
def __hash__(self):
return hash(self.internal.length)

def move(self, vector: Point2D) -> SingleCurve:
self.__curve = self.__curve.move(vector)
return self

def scale(self, amount: Union[Real, Tuple[Real, Real]]) -> SingleCurve:
self.__curve = self.__curve.scale(amount)
return self

def rotate(self, angle: Angle) -> SingleCurve:
self.__curve = self.__curve.rotate(angle)
return self

def density(self, center: Point2D) -> Density:
return Density.zero
83 changes: 83 additions & 0 deletions src/shapepy/bool2d/point.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Defines a SinglePoint class, that represents a SubSet of the plane
that contains only one point on the plane
"""

from __future__ import annotations

from copy import copy
from typing import Tuple, Union

from ..geometry.point import Point2D
from ..loggers import debug
from ..scalar.angle import Angle
from ..scalar.reals import Real
from ..tools import Is, To
from .base import SubSetR2
from .density import Density


class SinglePoint(SubSetR2):
"""
SinglePoint class

Is a shape which is defined by only one jordan curve.
It represents the interior/exterior region of the jordan curve
if the jordan curve is counter-clockwise/clockwise

"""

def __init__(self, point: Point2D):
point = To.point(point)
if Is.infinity(point.radius):
raise ValueError("Must be a finite point")
self.__point = point

@property
def internal(self) -> Point2D:
"""Gives the geometric point that defines the SinglePoint SubSetR2"""
return self.__point

def __copy__(self) -> SinglePoint:
return SinglePoint(self.internal)

def __deepcopy__(self, memo) -> SinglePoint:
return SinglePoint(copy(self.__point))

def __str__(self) -> str: # pragma: no cover # For debug
return "{" + str(self.__point) + "}"

def __eq__(self, other: SubSetR2) -> bool:
"""Compare two subsets

Parameters
----------
other: SubSetR2
The shape to compare

:raises ValueError: If ``other`` is not a SubSetR2 instance
"""
if not Is.instance(other, SubSetR2):
raise ValueError
return (
Is.instance(other, SinglePoint) and self.internal == other.internal
)

@debug("shapepy.bool2d.shape")
def __hash__(self):
return hash((self.internal.xcoord, self.internal.ycoord))

def move(self, vector: Point2D) -> SinglePoint:
self.__point = self.__point.move(vector)
return self

def scale(self, amount: Union[Real, Tuple[Real, Real]]) -> SinglePoint:
self.__point = self.__point.scale(amount)
return self

def rotate(self, angle: Angle) -> SinglePoint:
self.__point = self.__point.rotate(angle)
return self

def density(self, center: Point2D) -> Density:
return Density.zero
39 changes: 22 additions & 17 deletions src/shapepy/bool2d/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
from ..scalar.angle import Angle
from ..scalar.reals import Real
from ..tools import Is, To
from .base import EmptyShape, SubSetR2
from .base import EmptyShape, Future, SubSetR2
from .curve import SingleCurve
from .density import (
Density,
intersect_densities,
lebesgue_density_jordan,
unite_densities,
)
from .point import SinglePoint


class SimpleShape(SubSetR2):
Expand Down Expand Up @@ -132,24 +134,27 @@ def invert(self) -> SimpleShape:
return self

def __contains__(self, other: SubSetR2) -> bool:
if Is.instance(other, SubSetR2):
if Is.instance(other, SimpleShape):
return self.__contains_simple(other)
if Is.instance(other, ConnectedShape):
return ~self in ~other
if Is.instance(other, DisjointShape):
return all(o in self for o in other.subshapes)
return Is.instance(other, EmptyShape)
if Is.instance(other, JordanCurve):
return self.__contains_jordan(other)
return self.__contains_point(other)

def __contains_point(self, point: Point2D) -> bool:
density = float(self.density(point))
if not Is.instance(other, SubSetR2):
other = Future.convert(other)
if Is.instance(other, SinglePoint):
return self.__contains_point(other)
if Is.instance(other, SingleCurve):
return self.__contains_curve(other)
if Is.instance(other, SimpleShape):
return self.__contains_simple(other)
if Is.instance(other, ConnectedShape):
return ~self in ~other
if Is.instance(other, DisjointShape):
return all(o in self for o in other.subshapes)
return Is.instance(other, EmptyShape)

def __contains_point(self, point: SinglePoint) -> bool:
point = Future.convert(point)
density = float(self.density(point.internal))
return density > 0 if self.boundary else density == 1

def __contains_jordan(self, jordan: JordanCurve) -> bool:
piecewise = jordan.parametrize()
def __contains_curve(self, curve: SingleCurve) -> bool:
piecewise = curve.internal.parametrize()
vertices = map(piecewise, piecewise.knots[:-1])
if not all(map(self.__contains_point, vertices)):
return False
Expand Down
126 changes: 126 additions & 0 deletions tests/bool2d/test_curve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""File to test the functions `move`, `scale` and `rotate`"""

from copy import copy, deepcopy

import pytest

from shapepy.bool2d.curve import SingleCurve
from shapepy.geometry.factory import FactorySegment
from shapepy.scalar.angle import degrees


@pytest.mark.order(23)
@pytest.mark.dependency(
depends=[
"tests/geometry/test_point.py::test_all",
"tests/geometry/test_jordan_polygon.py::test_all",
"tests/bool2d/test_empty_whole.py::test_end",
"tests/bool2d/test_point.py::test_end",
],
scope="session",
)
def test_begin():
pass


@pytest.mark.order(23)
@pytest.mark.dependency(depends=["test_begin"])
def test_build():
segment = FactorySegment.bezier([(1, 2), (-4, 1)])
SingleCurve(segment)


@pytest.mark.order(23)
@pytest.mark.dependency(depends=["test_begin", "test_build"])
def test_move():
segment = FactorySegment.bezier([(1, 2), (-4, 1)])
curve = SingleCurve(segment)

test = curve.move((-3, 2))
segment = FactorySegment.bezier([(-2, 4), (-7, 3)])
good = SingleCurve(segment)

assert test == good


@pytest.mark.order(23)
@pytest.mark.dependency(depends=["test_begin", "test_build"])
def test_scale():
segment = FactorySegment.bezier([(1, 2), (-4, 1)])
curve = SingleCurve(segment)

test = curve.scale(4)
segment = FactorySegment.bezier([(4, 8), (-16, 4)])
good = SingleCurve(segment)

assert test == good


@pytest.mark.order(23)
@pytest.mark.dependency(depends=["test_begin", "test_build"])
def test_rotate():
segment = FactorySegment.bezier([(1, 2), (-4, 1)])
curve = SingleCurve(segment)

test = curve.rotate(degrees(90))
segment = FactorySegment.bezier([(-2, 1), (-1, -4)])
good = SingleCurve(segment)

assert test == good


@pytest.mark.order(23)
@pytest.mark.dependency(depends=["test_begin"])
def test_density():
segment = FactorySegment.bezier([(1, 2), (-4, 1)])
subset = SingleCurve(segment)

assert subset.density((0, 0)) == 0
assert subset.density((-1, 2)) == 0
assert subset.density((1, 2)) == 0
assert subset.density((-4, 1)) == 0


@pytest.mark.order(23)
@pytest.mark.dependency(depends=["test_begin"])
def test_copy():
segment = FactorySegment.bezier([(1, 2), (-4, 1)])
subset = SingleCurve(segment)

other = copy(subset)
assert other == subset
assert id(other) != id(subset)
assert id(subset.internal) == id(other.internal)

other = deepcopy(subset)
assert other == subset
assert id(other) != id(subset)
assert id(subset.internal) != id(other.internal)


@pytest.mark.order(22)
@pytest.mark.dependency(depends=["test_begin"])
def test_hash():
segment = FactorySegment.bezier([(1, 2), (-4, 1)])
subseta = SingleCurve(segment)

segment = FactorySegment.bezier([(1, 2), (-4, 1)])
subsetb = SingleCurve(segment)
assert hash(subseta) == hash(subsetb)


@pytest.mark.order(23)
@pytest.mark.dependency(
depends=[
"test_begin",
"test_build",
"test_move",
"test_scale",
"test_rotate",
"test_density",
"test_copy",
"test_hash",
]
)
def test_end():
pass
Loading
Loading