Newer
Older
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
# Copyright (C) 2017 Fabrice Salvaire
#
# 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 this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
# Item
# bounding box, rtree
# distance to point
# nearest item to point
# item in point, radius
# line path
# polygon rect
# item -> user data
# user data -> item
# remove item
####################################################################################################
import logging
from Patro.GeometryEngine.Transformation import AffineTransformation2D
from Patro.GeometryEngine.Vector import Vector2D
from . import GraphicItem
from .GraphicItem import (
# CircleItem,
CoordinateItem,
# CubicBezierItem,
# EllipseItem,
# ImageItem,
## PathItem,
## PolygonItem,
# RectangleItem,
# SegmentItem,
# TextItem,
)
####################################################################################################
_module_logger = logging.getLogger(__name__)
####################################################################################################
class GraphicSceneScope:
__ITEM_CTOR__ = {
'circle': GraphicItem.CircleItem,
'cubic_bezier': GraphicItem.CubicBezierItem,
'ellipse': GraphicItem.EllipseItem,
'image': GraphicItem.ImageItem,
# 'path': GraphicItem.PathItem,
# 'polygon': GraphicItem.PolygonItem,
'rectangle': GraphicItem.RectangleItem,
'segment': GraphicItem.SegmentItem,
'text': GraphicItem.TextItem,
}
##############################################
def __init__(self, transformation=None):
if transformation is None:
transformation = AffineTransformation2D.Identity()
self._transformation = transformation
self._coordinates = {}
self._items = {} # used to retrieve item from item_id, e.g. for rtree query
self._rtree = rtree.index.Index()
self._item_bounding_box_cache = {}
##############################################
@property
def transformation(self):
return self._transformation
##############################################
def __iter__(self):
return iter(self._items.values())
##############################################
def add_coordinate(self, name, position):
item = CoordinateItem(name, position)
self._coordinates[name] = item
return item
##############################################
def remove_coordinate(self, name):
del self._coordinates[name]
##############################################
def coordinate(self, name):
return self._coordinates[name]
##############################################
def cast_position(self, position):
# Fixme: cache ?
if isinstance(position, str):
vector = self._coordinates[position].position
elif isinstance(position, Vector2D):
vector = position
return self._transformation * vector
##############################################
def _add_item(self, cls, *args, **kwargs):
item = cls(self, *args, **kwargs)
# print(item, item.user_data, hash(item))
# if item in self._items:
# print('Warning duplicate', item.user_data)
item_id = id(item) # Fixme: hash ???
self._items[item_id] = item
return item
##############################################
def remove_item(self, item):
self.update_rtree(item, insert=False)
del self._items[item]
##############################################
# Fixme: ???
def item(self, item):
return self._items[item]
##############################################
for item in self._items.values():
if item.dirty:
self.update_rtree_item(item)
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
##############################################
def update_rtree_item(self, item, insert=True):
item_id = id(item)
old_bounding_box = self._item_bounding_box_cache.pop(item_id, None)
if old_bounding_box is not None:
self._rtree.delete(item_id, old_bounding_box)
if insert:
# try:
bounding_box = item.bounding_box.bounding_box # Fixme: name
print(item, bounding_box)
self._rtree.insert(item_id, bounding_box)
self._item_bounding_box_cache[item_id] = bounding_box
# except AttributeError:
# print('bounding_box not implemented for', item)
# pass # Fixme:
##############################################
def item_in_bounding_box(self, bounding_box):
# Fixme: Interval2D ok ?
print('item_in_bounding_box', bounding_box)
item_ids = self._rtree.intersection(bounding_box)
if item_ids:
return [self._items[item_id] for item_id in item_ids]
else:
return None
##############################################
def item_at(self, position, radius):
x, y = list(position)
bounding_box = (
x - radius, y - radius,
x + radius, y + radius,
)
items = []
for item in self.item_in_bounding_box(bounding_box):
try: # Fixme
distance = item.distance_to_point(position)
print('distance_to_point', item, distance)
if distance <= radius:
items.append((distance, item))
except NotImplementedError:
pass
return sorted(items, key=lambda pair: pair[0])
##############################################
# Fixme: !!!
# def add_scope(self, *args, **kwargs):
# return self._add_item(GraphicSceneScope, self, *args, **kwargs)
# def circle(self, *args, **kwargs):
# return self._add_item(CircleItem, *args, **kwargs)
# def cubic_bezier(self, *args, **kwargs):
# return self._add_item(CubicBezierItem, *args, **kwargs)
# def ellipse(self, *args, **kwargs):
# return self._add_item(EllipseItem, *args, **kwargs)
# def image(self, *args, **kwargs):
# return self._add_item(ImageItem, *args, **kwargs)
# def path(self, *args, **kwargs):
# return self._add_item(PathItem, *args, **kwargs)
# def polygon(self, *args, **kwargs):
# return self._add_item(PolygonItem, *args, **kwargs)
# def rectangle(self, *args, **kwargs):
# return self._add_item(RectangleItem, *args, **kwargs)
# def segment(self, *args, **kwargs):
# return self._add_item(SegmentItem, *args, **kwargs)
# def text(self, *args, **kwargs):
# return self._add_item(TextItem, *args, **kwargs)
def make_add_item_wrapper(cls):
def wrapper(self, *args, **kwargs):
return self._add_item(cls, *args, **kwargs)
return wrapper
for name, cls in GraphicSceneScope.__ITEM_CTOR__.items():
setattr(GraphicSceneScope, name, make_add_item_wrapper(cls))
####################################################################################################
class GraphicScene(GraphicSceneScope):
pass