# -*- coding: utf-8 -*-
# Copyright (C) 2014 ederag <edera@gmx.fr>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GeOptics; see the file LICENSE.txt. If not, see
# <http://www.gnu.org/licenses/>.
"""Define regions.
Specific region classes should inherit from the generic :class:`.Region`.
Currently an all-purpose :class:`.Polycurve` is available.
"""
from geoptics.elements.line import Line
from geoptics.elements.vector import Point, Vector
from .arc import Arc
from .segment import Segment
[docs]class Region(object):
"""Region of space."""
def __init__(self, n=None, scene=None):
#: :term:`optical index`
self.n = n
#: physical scene where the region will be placed
self.scene = scene
if scene:
self.scene.add(self)
[docs] def contains(self, point=None, u=None, line=None): # noqa: D400
"""Is a given point contained in this region ?
Alternatively, `line` can be given,
in which case `point` is taken from `line.p`,
and `u` from `line.u`.
Args:
point (Point): The point of interest
u (Vector): Searching direction
line (Line):
Contains both point and searching direction (see above)
"""
if line is None:
line = Line(p=point, u=u)
intersections = self.intersection(line, sign_of_s=1)
if len(intersections) % 2:
# odd number of intersections with the region
# point is inside it
return True
else:
return False
[docs] def intersection(self, other, sign_of_s=0):
"""Return the intersections between the region boundaries and other.
By default, return an empty list.
This method should be overloaded by specific region classes.
Args: same as :meth:`.elements.segment.Segment.intersection`
"""
return []
[docs]class Polycurve(Region):
"""Define a region inside curves (line segments, arcs, ...).
Args:
n (float): :term:`optical index` of the region
M1 (Point): first point
tag (str): tag for this region
Returns:
Region: a region
"""
def __init__(self, n=None, scene=None, tag=None):
Region.__init__(self, n=n, scene=scene)
self.tag = tag
[docs] def start(self, M_start):
"""Initialize the region boundary.
Set the starting point to `M_start`
"""
self.M = [M_start.copy()]
self.curves = []
[docs] def add_line(self, M_next):
"""Add a straight section to the region boundary.
The added section is actually a :class:`~.elements.segment.Segment`,
between the last point and the given `M_next` point.
"""
self.curves.append(Segment(self.M[-1], M_next))
self.M.append(M_next)
[docs] def add_arc(self, M_next, tangent):
"""Add an :class:`~.elements.arc.Arc` curve to the boundary.
The arc starts from the last point with the given tangent,
and ends on M_next.
"""
self.curves.append(Arc(self.M[-1], M_next, tangent))
self.M.append(M_next)
[docs] def close(self):
"""Join the last point to the first point with a segment."""
self.curves.append(Segment(self.M[-1], self.M[0]))
@property
def config(self): # noqa: D401
"""Configuration dictionary."""
return {'Class': 'Polycurve',
'tag': self.tag,
'n': self.n,
'curves': [curve.config for curve in self.curves],
}
[docs] @classmethod
def from_config(cls, config, scene=None, tag=None):
"""Alternate constructor.
Args:
config (dict): Configuration dictionary
Returns:
:class:`.Polycurve`: new Polycurve instance
"""
if tag is None:
tag = config.get('tag')
region = cls(scene=scene, tag=tag)
curves_config = config['curves']
M_start = Point.from_config(curves_config[0]['M1'])
region.start(M_start)
region.tag = config.get('tag')
region.n = config['n']
for curve_config in curves_config:
cls = curve_config['Class']
if cls == 'Segment':
M_next = Point.from_config(curve_config['M2'])
region.add_line(M_next)
elif cls == 'Arc':
M_next = Point.from_config(curve_config['M2'])
tangent = Vector.from_config(curve_config['tangent'])
region.add_arc(M_next, tangent)
else:
raise NotImplementedError
# normally, the stored polycurves are already closed
# (the last point is equal to the first one)
# no need for a final close()
return region
[docs] def intersection(self, other, sign_of_s=0):
"""Return the intersections between the region boundaries and other.
Args: same as :meth:`.elements.segment.Segment.intersection`
"""
result = []
for curve in self.curves:
result.extend(curve.intersection(other, sign_of_s))
return result
[docs] def translate(self, **kwargs):
"""Translate the region as a whole.
Same syntax and same side effects as
:py:func:`geoptics.elements.vector.Point.translate`
Likewise, it is possible to
insert a ``.copy()`` to avoid side-effects.
FIXME: :meth:`copy()` not implemented yet...
Return `self` for convenience
"""
for curve in self.curves:
curve.translate(**kwargs)
return self