Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Leger <stephen@3dservices.ch>2017-07-24 18:26:43 +0300
committerStephen Leger <stephen@3dservices.ch>2017-07-24 18:28:29 +0300
commitd1b57c76ad5cfd475ad270a93fed2c530fc3e7d0 (patch)
tree0e554161581b08ed70dce45f05ce2e9d2a4e7b8e
parent5bf39c3cfb72f7b1b79c847ac9f6f0804f813868 (diff)
archipack: remove 2d to 3d module (shapely dep issue)
-rw-r--r--archipack/__init__.py156
-rw-r--r--archipack/archipack_polylib.py2274
-rw-r--r--archipack/archipack_wall.py137
-rw-r--r--archipack/bitarray.py97
-rw-r--r--archipack/pyqtree.py187
5 files changed, 3 insertions, 2848 deletions
diff --git a/archipack/__init__.py b/archipack/__init__.py
index 6c966614..5bdf9b10 100644
--- a/archipack/__init__.py
+++ b/archipack/__init__.py
@@ -27,14 +27,14 @@
bl_info = {
'name': 'Archipack',
- 'description': 'Architectural objects and 2d polygons detection from unordered splines',
+ 'description': 'Architectural objects',
'author': 's-leger',
'license': 'GPL',
- 'deps': 'shapely',
+ 'deps': '',
'version': (1, 2, 6),
'blender': (2, 7, 8),
'location': 'View3D > Tools > Create > Archipack',
- 'warning': '2d to 3d require shapely python module (see setup in documentation)',
+ 'warning': '',
'wiki_url': 'https://github.com/s-leger/archipack/wiki',
'tracker_url': 'https://github.com/s-leger/archipack/issues',
'link': 'https://github.com/s-leger/archipack',
@@ -53,19 +53,12 @@ if "bpy" in locals():
imp.reload(archipack_door)
imp.reload(archipack_window)
imp.reload(archipack_stair)
- imp.reload(archipack_wall)
imp.reload(archipack_wall2)
imp.reload(archipack_slab)
imp.reload(archipack_fence)
imp.reload(archipack_truss)
imp.reload(archipack_floor)
imp.reload(archipack_rendering)
- try:
- imp.reload(archipack_polylib)
- HAS_POLYLIB = True
- except:
- HAS_POLYLIB = False
- pass
print("archipack: reload ready")
else:
@@ -76,24 +69,12 @@ else:
from . import archipack_door
from . import archipack_window
from . import archipack_stair
- from . import archipack_wall
from . import archipack_wall2
from . import archipack_slab
from . import archipack_fence
from . import archipack_truss
from . import archipack_floor
from . import archipack_rendering
- try:
- """
- polylib depends on shapely
- raise ImportError when not meet
- """
- from . import archipack_polylib
- HAS_POLYLIB = True
- except:
- print("archipack: shapely not found, using built in modules only")
- HAS_POLYLIB = False
- pass
print("archipack: ready")
@@ -121,14 +102,11 @@ icons_collection = {}
def update_panel(self, context):
try:
- bpy.utils.unregister_class(TOOLS_PT_Archipack_PolyLib)
bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools)
bpy.utils.unregister_class(TOOLS_PT_Archipack_Create)
except:
pass
prefs = context.user_preferences.addons[__name__].preferences
- TOOLS_PT_Archipack_PolyLib.bl_category = prefs.tools_category
- bpy.utils.register_class(TOOLS_PT_Archipack_PolyLib)
TOOLS_PT_Archipack_Tools.bl_category = prefs.tools_category
bpy.utils.register_class(TOOLS_PT_Archipack_Tools)
TOOLS_PT_Archipack_Create.bl_category = prefs.create_category
@@ -155,11 +133,6 @@ class Archipack_Pref(AddonPreferences):
description="Put Achipack's object into a sub menu (shift+a)",
default=True
)
- enable_2d_to_3d = BoolProperty(
- name="Enable 2d to 3d",
- description="Enable 2d to 3d module",
- default=False
- )
max_style_draw_tool = BoolProperty(
name="Draw a wall use 3dsmax style",
description="Reverse clic / release cycle for Draw a wall",
@@ -251,12 +224,6 @@ class Archipack_Pref(AddonPreferences):
box.label("Features")
box.prop(self, "max_style_draw_tool")
box = layout.box()
- box.label("2d to 3d")
- if not HAS_POLYLIB:
- box.label(text="WARNING Shapely python module not found", icon="ERROR")
- box.label(text="2d to 3d tools are disabled, see setup in documentation")
- box.prop(self, "enable_2d_to_3d")
- box = layout.box()
row = box.row()
split = row.split(percentage=0.5)
col = split.column()
@@ -286,114 +253,6 @@ class Archipack_Pref(AddonPreferences):
# ----------------------------------------------------
-class TOOLS_PT_Archipack_PolyLib(Panel):
- bl_label = "Archipack 2d to 3d"
- bl_idname = "TOOLS_PT_Archipack_PolyLib"
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_category = "Tools"
- bl_context = "objectmode"
-
- @classmethod
- def poll(self, context):
-
- global archipack_polylib
- return (HAS_POLYLIB and
- context.user_preferences.addons[__name__].preferences.enable_2d_to_3d and
- ((archipack_polylib.vars_dict['select_polygons'] is not None) or
- (context.object is not None and context.object.type == 'CURVE')))
-
- def draw(self, context):
- global icons_collection
- icons = icons_collection["main"]
- layout = self.layout
- row = layout.row(align=True)
- box = row.box()
- row = box.row(align=True)
- row.operator(
- "archipack.polylib_detect",
- icon_value=icons["detect"].icon_id,
- text='Detect'
- ).extend = context.window_manager.archipack_polylib.extend
- row.prop(context.window_manager.archipack_polylib, "extend")
- row = box.row(align=True)
- row.prop(context.window_manager.archipack_polylib, "resolution")
- row = box.row(align=True)
- row.label(text="Polygons")
- row = box.row(align=True)
- row.operator(
- "archipack.polylib_pick_2d_polygons",
- icon_value=icons["selection"].icon_id,
- text='Select'
- ).action = 'select'
- row.operator(
- "archipack.polylib_pick_2d_polygons",
- icon_value=icons["union"].icon_id,
- text='Union'
- ).action = 'union'
- row.operator(
- "archipack.polylib_output_polygons",
- icon_value=icons["polygons"].icon_id,
- text='All')
- row = box.row(align=True)
- row.operator(
- "archipack.polylib_pick_2d_polygons",
- text='Wall',
- icon_value=icons["wall"].icon_id).action = 'wall'
- row.prop(context.window_manager.archipack_polylib, "solidify_thickness")
- row = box.row(align=True)
- row.operator("archipack.polylib_pick_2d_polygons",
- text='Window',
- icon_value=icons["window"].icon_id).action = 'window'
- row.operator("archipack.polylib_pick_2d_polygons",
- text='Door',
- icon_value=icons["door"].icon_id).action = 'door'
- row.operator("archipack.polylib_pick_2d_polygons", text='Rectangle').action = 'rectangle'
- row = box.row(align=True)
- row.label(text="Lines")
- row = box.row(align=True)
- row.operator(
- "archipack.polylib_pick_2d_lines",
- icon_value=icons["selection"].icon_id,
- text='Lines').action = 'select'
- row.operator(
- "archipack.polylib_pick_2d_lines",
- icon_value=icons["union"].icon_id,
- text='Union').action = 'union'
- row.operator(
- "archipack.polylib_output_lines",
- icon_value=icons["polygons"].icon_id,
- text='All')
- row = box.row(align=True)
- row.label(text="Points")
- row = box.row(align=True)
- row.operator(
- "archipack.polylib_pick_2d_points",
- icon_value=icons["selection"].icon_id,
- text='Points').action = 'select'
- row = layout.row(align=True)
- box = row.box()
- row = box.row(align=True)
- row.operator("archipack.polylib_simplify")
- row.prop(context.window_manager.archipack_polylib, "simplify_tolerance")
- row = box.row(align=True)
- row.prop(context.window_manager.archipack_polylib, "simplify_preserve_topology")
- row = layout.row(align=True)
- box = row.box()
- row = box.row(align=True)
- row.operator("archipack.polylib_offset")
- row = box.row(align=True)
- row.prop(context.window_manager.archipack_polylib, "offset_distance")
- row = box.row(align=True)
- row.prop(context.window_manager.archipack_polylib, "offset_side")
- row = box.row(align=True)
- row.prop(context.window_manager.archipack_polylib, "offset_resolution")
- row = box.row(align=True)
- row.prop(context.window_manager.archipack_polylib, "offset_join_style")
- row = box.row(align=True)
- row.prop(context.window_manager.archipack_polylib, "offset_mitre_limit")
-
-
class TOOLS_PT_Archipack_Tools(Panel):
bl_label = "Archipack Tools"
bl_idname = "TOOLS_PT_Archipack_Tools"
@@ -602,7 +461,6 @@ def register():
archipack_door.register()
archipack_window.register()
archipack_stair.register()
- archipack_wall.register()
archipack_wall2.register()
archipack_slab.register()
archipack_fence.register()
@@ -610,9 +468,6 @@ def register():
archipack_floor.register()
archipack_rendering.register()
- if HAS_POLYLIB:
- archipack_polylib.register()
-
bpy.utils.register_class(archipack_data)
WindowManager.archipack = PointerProperty(type=archipack_data)
bpy.utils.register_class(Archipack_Pref)
@@ -626,7 +481,6 @@ def unregister():
bpy.types.INFO_MT_mesh_add.remove(menu_func)
bpy.utils.unregister_class(ARCHIPACK_create_menu)
- bpy.utils.unregister_class(TOOLS_PT_Archipack_PolyLib)
bpy.utils.unregister_class(TOOLS_PT_Archipack_Tools)
bpy.utils.unregister_class(TOOLS_PT_Archipack_Create)
bpy.utils.unregister_class(Archipack_Pref)
@@ -639,7 +493,6 @@ def unregister():
archipack_door.unregister()
archipack_window.unregister()
archipack_stair.unregister()
- archipack_wall.unregister()
archipack_wall2.unregister()
archipack_slab.unregister()
archipack_fence.unregister()
@@ -647,9 +500,6 @@ def unregister():
archipack_floor.unregister()
archipack_rendering.unregister()
- if HAS_POLYLIB:
- archipack_polylib.unregister()
-
bpy.utils.unregister_class(archipack_data)
del WindowManager.archipack
diff --git a/archipack/archipack_polylib.py b/archipack/archipack_polylib.py
deleted file mode 100644
index 886029ba..00000000
--- a/archipack/archipack_polylib.py
+++ /dev/null
@@ -1,2274 +0,0 @@
-# -*- coding:utf-8 -*-
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# 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 2
-# 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, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-# ----------------------------------------------------------
-# Author: Stephen Leger (s-leger)
-#
-# ----------------------------------------------------------
-
-bl_info = {
- 'name': 'PolyLib',
- 'description': 'Polygons detection from unordered splines',
- 'author': 's-leger',
- 'license': 'GPL',
- 'deps': 'shapely',
- 'version': (1, 1),
- 'blender': (2, 7, 8),
- 'location': 'View3D > Tools > Polygons',
- 'warning': '',
- 'wiki_url': 'https://github.com/s-leger/blenderPolygons/wiki',
- 'tracker_url': 'https://github.com/s-leger/blenderPolygons/issues',
- 'link': 'https://github.com/s-leger/blenderPolygons',
- 'support': 'COMMUNITY',
- 'category': '3D View'
- }
-
-import sys
-import time
-import bpy
-import bgl
-import numpy as np
-from math import cos, sin, pi, atan2
-import bmesh
-
-# let shapely import raise ImportError when missing
-import shapely.ops
-import shapely.prepared
-from shapely.geometry import Point as ShapelyPoint
-from shapely.geometry import Polygon as ShapelyPolygon
-
-try:
- import shapely.speedups
- if shapely.speedups.available:
- shapely.speedups.enable()
-except:
- pass
-
-from .bitarray import BitArray
-from .pyqtree import _QuadTree
-from mathutils import Vector, Matrix
-from mathutils.geometry import intersect_line_plane, interpolate_bezier
-from bpy_extras import view3d_utils
-from bpy.types import Operator, PropertyGroup
-from bpy.props import StringProperty, FloatProperty, PointerProperty, EnumProperty, IntProperty, BoolProperty
-from bpy.app.handlers import persistent
-from .materialutils import MaterialUtils
-from .archipack_gl import (
- FeedbackPanel,
- GlCursorFence,
- GlCursorArea,
- GlLine,
- GlPolyline
-)
-
-# module globals vars dict
-vars_dict = {
- # spacial tree for segments and points
- 'seg_tree': None,
- 'point_tree': None,
- # keep track of shapely geometry selection sets
- 'select_polygons': None,
- 'select_lines': None,
- 'select_points': None
- }
-
-
-# module constants
-# precision 1e-4 = 0.1mm
-EPSILON = 1.0e-4
-# Qtree params
-MAX_ITEMS = 10
-MAX_DEPTH = 20
-
-
-class CoordSys(object):
- """
- reference coordsys
- world : matrix from local to world
- invert: matrix from world to local
- width, height: bonding region size
- """
- def __init__(self, objs):
- x = []
- y = []
- if len(objs) > 0:
- if hasattr(objs[0], 'bound_box'):
- for obj in objs:
- pos = obj.location
- x.append(obj.bound_box[0][0] + pos.x)
- x.append(obj.bound_box[6][0] + pos.x)
- y.append(obj.bound_box[0][1] + pos.y)
- y.append(obj.bound_box[6][1] + pos.y)
- elif hasattr(objs[0], 'bounds'):
- for geom in objs:
- x0, y0, x1, y1 = geom.bounds
- x.append(x0)
- x.append(x1)
- y.append(y0)
- y.append(y1)
- else:
- raise Exception("CoordSys require at least one object with bounds or bound_box property to initialize")
- else:
- raise Exception("CoordSys require at least one object to initialize bounds")
- x0 = min(x)
- y0 = min(y)
- x1 = max(x)
- y1 = max(y)
- width, height = x1 - x0, y1 - y0
- midx, midy = x0 + width / 2.0, y0 + height / 2.0
- # reference coordsys bounding box center
- self.world = Matrix([
- [1, 0, 0, midx],
- [0, 1, 0, midy],
- [0, 0, 1, 0],
- [0, 0, 0, 1],
- ])
- self.invert = self.world.inverted()
- self.width = width
- self.height = height
-
-
-class Prolongement():
- """ intersection of two segments outside segment (projection)
- c0 = extremite sur le segment courant
- c1 = intersection point on oposite segment
- id = oposite segment id
- t = param t on oposite segment
- d = distance from ends to segment
- insert = do we need to insert the point on other segment
- use id, c1 and t to insert segment slices
- """
- def __init__(self, c0, c1, id, t, d):
- self.length = c0.distance(c1)
- self.c0 = c0
- self.c1 = c1
- self.id = id
- self.t = t
- self.d = d
-
-
-class Point():
-
- def __init__(self, co, precision=EPSILON):
- self.users = 0
- self.co = tuple(co)
- x, y, z = co
- self.shapeIds = []
- self.bounds = (x - precision, y - precision, x + precision, y + precision)
-
- @property
- def geom(self):
- return ShapelyPoint(self.co)
-
- def vect(self, point):
- """ vector from this point to another """
- return np.subtract(point.co, self.co)
-
- def distance(self, point):
- """ euclidian distance between points """
- return np.linalg.norm(self.vect(point))
-
- def add_user(self):
- self.users += 1
-
-
-class Segment():
-
- def __init__(self, c0, c1, extend=EPSILON):
-
- self.c0 = c0
- self.c1 = c1
- self._splits = []
-
- self.available = True
- # ensure uniqueness when merge
-
- self.opposite = False
- # this seg has an opposite
-
- self.original = False
- # source of opposite
-
- x0, y0, z0 = c0.co
- x1, y1, z1 = c1.co
- self.bounds = (min(x0, x1) - extend, min(y0, y1) - extend, max(x0, x1) + extend, max(y0, y1) + extend)
-
- @property
- def splits(self):
- return sorted(self._splits)
-
- @property
- def vect(self):
- """ vector c0-c1"""
- return np.subtract(self.c1.co, self.c0.co)
-
- @property
- def vect_2d(self):
- v = self.vect
- v[2] = 0
- return v
-
- def lerp(self, t):
- return np.add(self.c0.co, np.multiply(t, self.vect))
-
- def _point_sur_segment(self, point):
- """ _point_sur_segment
- point: Point
- t: param t de l'intersection sur le segment courant
- d: distance laterale perpendiculaire
- """
- vect = self.vect
- dp = point.vect(self.c0)
- dl = np.linalg.norm(vect)
- d = np.linalg.norm(np.cross(vect, dp)) / dl
- t = -np.divide(np.dot(dp, vect), np.multiply(dl, dl))
- if d < EPSILON:
- if t > 0 and t < 1:
- self._append_splits((t, point))
-
- def is_end(self, point):
- return point == self.c0 or point == self.c1
-
- def min_intersect_dist(self, t, point):
- """ distance intersection extremite la plus proche
- t: param t de l'intersection sur le segment courant
- point: Point d'intersection
- return d: distance
- """
- if t > 0.5:
- return self.c1.distance(point)
- else:
- return self.c0.distance(point)
-
- def intersect(self, segment):
- """ point_sur_segment return
- p: point d'intersection
- u: param t de l'intersection sur le segment courant
- v: param t de l'intersection sur le segment segment
- """
- v2d = self.vect_2d
- c2 = np.cross(segment.vect_2d, (0, 0, 1))
- d = np.dot(v2d, c2)
- if d == 0:
- # segments paralleles
- segment._point_sur_segment(self.c0)
- segment._point_sur_segment(self.c1)
- self._point_sur_segment(segment.c0)
- self._point_sur_segment(segment.c1)
- return False, 0, 0, 0
- c1 = np.cross(v2d, (0, 0, 1))
- v3 = self.c0.vect(segment.c0)
- v3[2] = 0.0
- u = np.dot(c2, v3) / d
- v = np.dot(c1, v3) / d
- co = self.lerp(u)
- return True, co, u, v
-
- def _append_splits(self, split):
- """
- append a unique split point
- """
- if split not in self._splits:
- self._splits.append(split)
-
- def slice(self, d, t, point):
- if d > EPSILON:
- if t > 0.5:
- if point != self.c1:
- self._append_splits((t, point))
- else:
- if point != self.c0:
- self._append_splits((t, point))
-
- def add_user(self):
- self.c0.add_user()
- self.c1.add_user()
-
- def consume(self):
- self.available = False
-
-
-class Shape():
- """
- Ensure uniqueness and fix precision issues by design
- implicit closed with last point
- require point_tree and seg_tree
- """
-
- def __init__(self, points=[]):
- """
- @vertex: list of coords
- """
- self.available = True
- # Ensure uniqueness of shape when merging
-
- self._segs = []
- # Shape segments
-
- self.shapeId = []
- # Id of shape in shapes to keep a track of shape parts when merging
-
- self._create_segments(points)
-
- def _create_segments(self, points):
- global vars_dict
- if vars_dict['seg_tree'] is None:
- raise RuntimeError('Shape._create_segments() require spacial index ')
- # skip null segments with unique points test
- self._segs = list(vars_dict['seg_tree'].newSegment(points[v], points[v + 1])
- for v in range(len(points) - 1) if points[v] != points[v + 1])
-
- @property
- def coords(self):
- coords = list(seg.c0.co for seg in self._segs)
- coords.append(self.c1.co)
- return coords
-
- @property
- def points(self):
- points = list(seg.c0 for seg in self._segs)
- points.append(self.c1)
- return points
-
- @property
- def c0(self):
- if not self.valid:
- raise RuntimeError('Shape does not contains any segments')
- return self._segs[0].c0
-
- @property
- def c1(self):
- if not self.valid:
- raise RuntimeError('Shape does not contains any segments')
- return self._segs[-1].c1
-
- @property
- def nbsegs(self):
- return len(self._segs)
-
- @property
- def valid(self):
- return self.nbsegs > 0
-
- @property
- def closed(self):
- return self.valid and bool(self.c0 == self.c1)
-
- def merge(self, shape):
- """ merge this shape with specified shape
- shapes must share at least one vertex
- """
- if not self.valid or not shape.valid:
- raise RuntimeError('Trying to merge invalid shape')
- if self.c1 == shape.c1 or self.c0 == shape.c0:
- shape._reverse()
- if self.c1 == shape.c0:
- self._segs += shape._segs
- elif shape.c1 == self.c0:
- self._segs = shape._segs + self._segs
- else:
- # should never happen
- raise RuntimeError("Shape merge failed {} {} {} {}".format(
- id(self), id(shape), self.shapeId, shape.shapeId))
-
- def _reverse(self):
- """
- reverse vertex order
- """
- points = self.points[::-1]
- self._create_segments(points)
-
- def slice(self, shapes):
- """
- slice shape into smaller parts at intersections
- """
- if not self.valid:
- raise RuntimeError('Cant slice invalid shape')
- points = []
- for seg in self._segs:
- if seg.available and not seg.original:
- seg.consume()
- points.append(seg.c0)
- if seg.c1.users > 2:
- points.append(seg.c1)
- shape = Shape(points)
- shapes.append(shape)
- points = []
- if len(points) > 0:
- points.append(self.c1)
- shape = Shape(points)
- shapes.append(shape)
-
- def add_points(self):
- """
- add points from intersection data
- """
- points = []
- if self.nbsegs > 0:
- for seg in self._segs:
- points.append(seg.c0)
- for split in seg.splits:
- points.append(split[1])
- points.append(self.c1)
- self._create_segments(points)
-
- def set_users(self):
- """
- add users on segments and points
- """
- for seg in self._segs:
- seg.add_user()
-
- def consume(self):
- self.available = False
-
-
-class Qtree(_QuadTree):
- """
- The top spatial index to be created by the user. Once created it can be
- populated with geographically placed members that can later be tested for
- intersection with a user inputted geographic bounding box.
- """
- def __init__(self, coordsys, extend=EPSILON, max_items=MAX_ITEMS, max_depth=MAX_DEPTH):
- """
- objs may be blender objects or shapely geoms
- extend: how much seek arround
- """
- self._extend = extend
- self._geoms = []
-
- # store input coordsys
- self.coordsys = coordsys
-
- super(Qtree, self).__init__(0, 0, coordsys.width, coordsys.height, max_items, max_depth)
-
- @property
- def ngeoms(self):
- return len(self._geoms)
-
- def build(self, geoms):
- """
- Build a spacial index from shapely geoms
- """
- t = time.time()
- self._geoms = geoms
- for i, geom in enumerate(geoms):
- self._insert(i, geom.bounds)
- print("Qtree.build() :%.2f seconds" % (time.time() - t))
-
- def insert(self, id, geom):
- self._geoms.append(geom)
- self._insert(id, geom.bounds)
-
- def newPoint(self, co):
- point = Point(co, self._extend)
- count, found = self.intersects(point)
- for id in found:
- return self._geoms[id]
- self.insert(self.ngeoms, point)
- return point
-
- def newSegment(self, c0, c1):
- """
- allow "opposite" segments,
- those segments are not found by intersects
- and not stored in self.geoms
- """
- new_seg = Segment(c0, c1, self._extend)
- count, found = self.intersects(new_seg)
- for id in found:
- old_seg = self._geoms[id]
- if (old_seg.c0 == c0 and old_seg.c1 == c1):
- return old_seg
- if (old_seg.c0 == c1 and old_seg.c1 == c0):
- if not old_seg.opposite:
- old_seg.opposite = new_seg
- new_seg.original = old_seg
- return old_seg.opposite
- self.insert(self.ngeoms, new_seg)
- return new_seg
-
- def intersects(self, geom):
- selection = list(self._intersect(geom.bounds))
- count = len(selection)
- return count, sorted(selection)
-
-
-class Io():
-
- @staticmethod
- def ensure_iterable(obj):
- try:
- iter(obj)
- except TypeError:
- obj = [obj]
- return obj
-
- # Conversion methods
- @staticmethod
- def _to_geom(shape):
- if not shape.valid:
- raise RuntimeError('Cant convert invalid shape to Shapely LineString')
- return shapely.geometry.LineString(shape.coords)
-
- @staticmethod
- def shapes_to_geoms(shapes):
- return [Io._to_geom(shape) for shape in shapes]
-
- @staticmethod
- def _to_shape(geometry, shapes):
- global vars_dict
- if vars_dict['point_tree'] is None:
- raise RuntimeError("geoms to shapes require a global point_tree spacial index")
- if hasattr(geometry, 'exterior'):
- Io._to_shape(geometry.exterior, shapes)
- for geom in geometry.interiors:
- Io._to_shape(geom, shapes)
- elif hasattr(geometry, 'geoms'):
- # Multi and Collections
- for geom in geometry.geoms:
- Io._to_shape(geom, shapes)
- else:
- points = list(vars_dict['point_tree'].newPoint(p) for p in list(geometry.coords))
- shape = Shape(points)
- shapes.append(shape)
-
- @staticmethod
- def geoms_to_shapes(geoms, shapes=[]):
- for geom in geoms:
- Io._to_shape(geom, shapes)
- return shapes
-
- # Input methods
- @staticmethod
- def _interpolate_bezier(pts, wM, p0, p1, resolution):
- # straight segment, worth testing here
- # since this can lower points count by a resolution factor
- # use normalized to handle non linear t
- if resolution == 0:
- pts.append(wM * p0.co.to_3d())
- else:
- v = (p1.co - p0.co).normalized()
- d1 = (p0.handle_right - p0.co).normalized()
- d2 = (p1.co - p1.handle_left).normalized()
- if d1 == v and d2 == v:
- pts.append(wM * p0.co.to_3d())
- else:
- seg = interpolate_bezier(wM * p0.co,
- wM * p0.handle_right,
- wM * p1.handle_left,
- wM * p1.co,
- resolution)
- for i in range(resolution - 1):
- pts.append(seg[i].to_3d())
-
- @staticmethod
- def _coords_from_spline(wM, resolution, spline):
- pts = []
- if spline.type == 'POLY':
- pts = [wM * p.co.to_3d() for p in spline.points]
- if spline.use_cyclic_u:
- pts.append(pts[0])
- elif spline.type == 'BEZIER':
- points = spline.bezier_points
- for i in range(1, len(points)):
- p0 = points[i - 1]
- p1 = points[i]
- Io._interpolate_bezier(pts, wM, p0, p1, resolution)
- pts.append(wM * points[-1].co)
- if spline.use_cyclic_u:
- p0 = points[-1]
- p1 = points[0]
- Io._interpolate_bezier(pts, wM, p0, p1, resolution)
- pts.append(pts[0])
- return pts
-
- @staticmethod
- def _add_geom_from_curve(curve, invert_world, resolution, geoms):
- wM = invert_world * curve.matrix_world
- for spline in curve.data.splines:
- pts = Io._coords_from_spline(wM, resolution, spline)
- geom = shapely.geometry.LineString(pts)
- geoms.append(geom)
-
- @staticmethod
- def curves_to_geoms(curves, resolution, geoms=[]):
- """
- @curves : blender curves collection
- Return coordsys for outputs
- """
- curves = Io.ensure_iterable(curves)
- coordsys = CoordSys(curves)
- t = time.time()
- for curve in curves:
- Io._add_geom_from_curve(curve, coordsys.invert, resolution, geoms)
- print("Io.curves_as_line() :%.2f seconds" % (time.time() - t))
- return coordsys
-
- @staticmethod
- def _add_shape_from_curve(curve, invert_world, resolution, shapes):
- global vars_dict
- wM = invert_world * curve.matrix_world
- for spline in curve.data.splines:
- pts = Io._coords_from_spline(wM, resolution, spline)
- pts = [vars_dict['point_tree'].newPoint(pt) for pt in pts]
- shape = Shape(points=pts)
- shapes.append(shape)
-
- @staticmethod
- def curves_to_shapes(curves, coordsys, resolution, shapes=[]):
- """
- @curves : blender curves collection
- Return simple shapes
- """
- curves = Io.ensure_iterable(curves)
- t = time.time()
- for curve in curves:
- Io._add_shape_from_curve(curve, coordsys.invert, resolution, shapes)
- print("Io.curves_to_shapes() :%.2f seconds" % (time.time() - t))
-
- # Output methods
- @staticmethod
- def _poly_to_wall(scene, matrix_world, poly, height, name):
- global vars_dict
- curve = bpy.data.curves.new(name, type='CURVE')
- curve.dimensions = "2D"
- curve.fill_mode = 'BOTH'
- curve.extrude = height
- n_ext = len(poly.exterior.coords)
- n_int = len(poly.interiors)
- Io._add_spline(curve, poly.exterior)
- for geom in poly.interiors:
- Io._add_spline(curve, geom)
- curve_obj = bpy.data.objects.new(name, curve)
- curve_obj.matrix_world = matrix_world
- scene.objects.link(curve_obj)
- curve_obj.select = True
- scene.objects.active = curve_obj
- return n_ext, n_int, curve_obj
-
- @staticmethod
- def wall_uv(me, bm):
-
- for face in bm.faces:
- face.select = face.material_index > 0
-
- bmesh.update_edit_mesh(me, True)
- bpy.ops.uv.cube_project(scale_to_bounds=False, correct_aspect=True)
-
- for face in bm.faces:
- face.select = face.material_index < 1
-
- bmesh.update_edit_mesh(me, True)
- bpy.ops.uv.smart_project(use_aspect=True, stretch_to_bounds=False)
-
- @staticmethod
- def to_wall(scene, coordsys, geoms, height, name, walls=[]):
- """
- use curve extrude as it does respect vertices number and is not removing doubles
- so it is easy to set material index
- cap faces are tri, sides faces are quads
- """
- bpy.ops.object.select_all(action='DESELECT')
- geoms = Io.ensure_iterable(geoms)
- for poly in geoms:
- if hasattr(poly, 'exterior'):
- half_height = height / 2.0
- n_ext, n_int, obj = Io._poly_to_wall(scene, coordsys.world, poly, half_height, name)
- bpy.ops.object.convert(target="MESH")
- bpy.ops.object.mode_set(mode='EDIT')
- me = obj.data
- bm = bmesh.from_edit_mesh(me)
- bm.verts.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
- for v in bm.verts:
- v.co.z += half_height
- nfaces = 0
- for i, f in enumerate(bm.faces):
- bm.faces[i].material_index = 2
- if len(f.verts) > 3:
- nfaces = i
- break
- # walls without holes are inside
- mat_index = 0 if n_int > 0 else 1
- for i in range(nfaces, nfaces + n_ext - 1):
- bm.faces[i].material_index = mat_index
- for i in range(nfaces + n_ext - 1, len(bm.faces)):
- bm.faces[i].material_index = 1
- bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.003)
- bmesh.update_edit_mesh(me, True)
- Io.wall_uv(me, bm)
- bpy.ops.mesh.dissolve_limited(angle_limit=0.00349066, delimit={'NORMAL'})
- bpy.ops.mesh.dissolve_degenerate()
- bpy.ops.object.mode_set(mode='OBJECT')
- bpy.ops.object.shade_flat()
- MaterialUtils.add_wall_materials(obj)
- walls.append(obj)
- return walls
-
- @staticmethod
- def _add_spline(curve, geometry):
- coords = list(geometry.coords)
- spline = curve.splines.new('POLY')
- spline.use_endpoint_u = False
- spline.use_cyclic_u = coords[0] == coords[-1]
- spline.points.add(len(coords) - 1)
- for i, coord in enumerate(coords):
- x, y, z = Vector(coord).to_3d()
- spline.points[i].co = (x, y, z, 1)
-
- @staticmethod
- def _as_spline(curve, geometry):
- """
- add a spline into a blender curve
- @curve : blender curve
- """
- if hasattr(geometry, 'exterior'):
- # Polygon
- Io._add_spline(curve, geometry.exterior)
- for geom in geometry.interiors:
- Io._add_spline(curve, geom)
- elif hasattr(geometry, 'geoms'):
- # Multi and Collections
- for geom in geometry.geoms:
- Io._as_spline(curve, geom)
- else:
- # LinearRing, LineString and Shape
- Io._add_spline(curve, geometry)
-
- @staticmethod
- def to_curve(scene, coordsys, geoms, name, dimensions='3D'):
- global vars_dict
- t = time.time()
- geoms = Io.ensure_iterable(geoms)
- curve = bpy.data.curves.new(name, type='CURVE')
- curve.dimensions = dimensions
- for geom in geoms:
- Io._as_spline(curve, geom)
- curve_obj = bpy.data.objects.new(name, curve)
- curve_obj.matrix_world = coordsys.world
- scene.objects.link(curve_obj)
- curve_obj.select = True
- print("Io.to_curves() :%.2f seconds" % (time.time() - t))
- return curve_obj
-
- @staticmethod
- def to_curves(scene, coordsys, geoms, name, dimensions='3D'):
- geoms = Io.ensure_iterable(geoms)
- return [Io.to_curve(scene, coordsys, geom, name, dimensions) for geom in geoms]
-
-
-class ShapelyOps():
-
- @staticmethod
- def min_bounding_rect(geom):
- """ min_bounding_rect
- minimum area oriented bounding rect
- """
- # Compute edges (x2-x1,y2-y1)
- if geom.convex_hull.geom_type == 'Polygon':
- hull_points_2d = [list(coord[0:2]) for coord in list(geom.convex_hull.exterior.coords)]
- else:
- hull_points_2d = [list(coord[0:2]) for coord in list(geom.convex_hull.coords)]
- edges = np.zeros((len(hull_points_2d) - 1, 2))
- # empty 2 column array
- for i in range(len(edges)):
- edge_x = hull_points_2d[i + 1][0] - hull_points_2d[i][0]
- edge_y = hull_points_2d[i + 1][1] - hull_points_2d[i][1]
- edges[i] = [edge_x, edge_y]
- # Calculate edge angles atan2(y/x)
- edge_angles = np.zeros((len(edges))) # empty 1 column array
- for i in range(len(edge_angles)):
- edge_angles[i] = atan2(edges[i, 1], edges[i, 0])
- # Check for angles in 1st quadrant
- for i in range(len(edge_angles)):
- edge_angles[i] = abs(edge_angles[i] % (pi / 2)) # want strictly positive answers
- # Remove duplicate angles
- edge_angles = np.unique(edge_angles)
- # Test each angle to find bounding box with smallest area
- min_bbox = (0, sys.maxsize, 0, 0, 0, 0, 0, 0) # rot_angle, area, width, height, min_x, max_x, min_y, max_y
- # print "Testing", len(edge_angles), "possible rotations for bounding box... \n"
- for i in range(len(edge_angles)):
- # Create rotation matrix to shift points to baseline
- # R = [ cos(theta) , cos(theta-PI/2)
- # cos(theta+PI/2) , cos(theta) ]
- R = np.array([[cos(edge_angles[i]), cos(edge_angles[i] - (pi / 2))],
- [cos(edge_angles[i] + (pi / 2)), cos(edge_angles[i])]])
- # Apply this rotation to convex hull points
- rot_points = np.dot(R, np.transpose(hull_points_2d)) # 2x2 * 2xn
- # Find min/max x,y points
- min_x = np.nanmin(rot_points[0], axis=0)
- max_x = np.nanmax(rot_points[0], axis=0)
- min_y = np.nanmin(rot_points[1], axis=0)
- max_y = np.nanmax(rot_points[1], axis=0)
- # Calculate height/width/area of this bounding rectangle
- width = max_x - min_x
- height = max_y - min_y
- area = width * height
- # Store the smallest rect found first
- if (area < min_bbox[1]):
- min_bbox = (edge_angles[i], area, width, height, min_x, max_x, min_y, max_y)
- # Re-create rotation matrix for smallest rect
- angle = min_bbox[0]
- R = np.array([[cos(angle), cos(angle - (pi / 2))], [cos(angle + (pi / 2)), cos(angle)]])
- # min/max x,y points are against baseline
- min_x = min_bbox[4]
- max_x = min_bbox[5]
- min_y = min_bbox[6]
- max_y = min_bbox[7]
- # Calculate center point and project onto rotated frame
- center_x = (min_x + max_x) / 2
- center_y = (min_y + max_y) / 2
- center_point = np.dot([center_x, center_y], R)
- if min_bbox[2] > min_bbox[3]:
- a = -cos(angle)
- b = sin(angle)
- w = min_bbox[2] / 2
- h = min_bbox[3] / 2
- else:
- a = -cos(angle + (pi / 2))
- b = sin(angle + (pi / 2))
- w = min_bbox[3] / 2
- h = min_bbox[2] / 2
- tM = Matrix([[a, b, 0, center_point[0]], [-b, a, 0, center_point[1]], [0, 0, 1, 0], [0, 0, 0, 1]])
- l_pts = [Vector((-w, -h, 0)), Vector((-w, h, 0)), Vector((w, h, 0)), Vector((w, -h, 0))]
- w_pts = [tM * pt for pt in l_pts]
- return tM, 2 * w, 2 * h, l_pts, w_pts
-
- @staticmethod
- def detect_polygons(geoms):
- """ detect_polygons
- """
- print("Ops.detect_polygons()")
- t = time.time()
- result, dangles, cuts, invalids = shapely.ops.polygonize_full(geoms)
- print("Ops.detect_polygons() :%.2f seconds" % (time.time() - t))
- return result, dangles, cuts, invalids
-
- @staticmethod
- def optimize(geoms, tolerance=0.001, preserve_topology=True):
- """ optimize
- """
- t = time.time()
- geoms = Io.ensure_iterable(geoms)
- optimized = [geom.simplify(tolerance, preserve_topology) for geom in geoms]
- print("Ops.optimize() :%.2f seconds" % (time.time() - t))
- return optimized
-
- @staticmethod
- def union(geoms):
- """ union (shapely based)
- cascaded union - may require snap before use to fix precision issues
- use union2 for best performances
- """
- t = time.time()
- geoms = Io.ensure_iterable(geoms)
- collection = shapely.geometry.GeometryCollection(geoms)
- union = shapely.ops.cascaded_union(collection)
- print("Ops.union() :%.2f seconds" % (time.time() - t))
- return union
-
-
-class ShapeOps():
-
- @staticmethod
- def union(shapes, extend=0.001):
- """ union2 (Shape based)
- cascaded union
- require point_tree and seg_tree
- """
- split = ShapeOps.split(shapes, extend=extend)
- union = ShapeOps.merge(split)
- return union
-
- @staticmethod
- def _intersection_point(d, t, point, seg):
- if d > EPSILON:
- return point
- elif t > 0.5:
- return seg.c1
- else:
- return seg.c0
-
- @staticmethod
- def split(shapes, extend=0.01):
- """ _split
- detect intersections between segments and slice shapes according
- is able to project segment ends on closest segment
- require point_tree and seg_tree
- """
- global vars_dict
- t = time.time()
- new_shapes = []
- segs = vars_dict['seg_tree']._geoms
- nbsegs = len(segs)
- it_start = [None for x in range(nbsegs)]
- it_end = [None for x in range(nbsegs)]
- for s, seg in enumerate(segs):
- count, idx = vars_dict['seg_tree'].intersects(seg)
- for id in idx:
- if id > s:
- intersect, co, u, v = seg.intersect(segs[id])
- if intersect:
- point = vars_dict['point_tree'].newPoint(co)
- du = seg.min_intersect_dist(u, point)
- dv = segs[id].min_intersect_dist(v, point)
- # point intersection sur segment id
- pt = ShapeOps._intersection_point(dv, v, point, segs[id])
- # print("s:%s id:%s u:%7f v:%7f du:%7f dv:%7f" % (s, id, u, v, du, dv))
- if u <= 0:
- # prolonge segment s c0
- if du < extend and not seg.is_end(pt):
- it = Prolongement(seg.c0, pt, id, v, du)
- last = it_start[s]
- if last is None or last.length > it.length:
- it_start[s] = it
- elif u < 1:
- # intersection sur segment s
- seg.slice(du, u, pt)
- else:
- # prolonge segment s c1
- if du < extend and not seg.is_end(pt):
- it = Prolongement(seg.c1, pt, id, v, du)
- last = it_end[s]
- if last is None or last.length > it.length:
- it_end[s] = it
- pt = ShapeOps._intersection_point(du, u, point, seg)
- if v <= 0:
- # prolonge segment id c0
- if dv < extend and not segs[id].is_end(pt):
- it = Prolongement(segs[id].c0, pt, s, u, dv)
- last = it_start[id]
- if last is None or last.length > it.length:
- it_start[id] = it
- elif v < 1:
- # intersection sur segment s
- segs[id].slice(dv, v, pt)
- else:
- # prolonge segment s c1
- if dv < extend and not segs[id].is_end(pt):
- it = Prolongement(segs[id].c1, pt, s, u, dv)
- last = it_end[id]
- if last is None or last.length > it.length:
- it_end[id] = it
- for it in it_start:
- if it is not None:
- # print("it_start[%s] id:%s t:%4f d:%4f" % (s, it.id, it.t, it.d) )
- if it.t > 0 and it.t < 1:
- segs[it.id]._append_splits((it.t, it.c1))
- if it.d > EPSILON:
- shape = Shape([it.c0, it.c1])
- shapes.append(shape)
- for it in it_end:
- if it is not None:
- # print("it_end[%s] id:%s t:%4f d:%4f" % (s, it.id, it.t, it.d) )
- if it.t > 0 and it.t < 1:
- segs[it.id]._append_splits((it.t, it.c1))
- if it.d > EPSILON:
- shape = Shape([it.c0, it.c1])
- shapes.append(shape)
- print("Ops.split() intersect :%.2f seconds" % (time.time() - t))
- t = time.time()
- for shape in shapes:
- shape.add_points()
- for shape in shapes:
- shape.set_users()
- for shape in shapes:
- if shape.valid:
- shape.slice(new_shapes)
- print("Ops.split() slice :%.2f seconds" % (time.time() - t))
- return new_shapes
-
- @staticmethod
- def merge(shapes):
- """ merge
- merge shapes ends
- reverse use seg_tree
- does not need tree as all:
- - set shape ids to end vertices
- - traverse shapes looking for points with 2 shape ids
- - merge different shapes according
- """
- t = time.time()
- merged = []
- for i, shape in enumerate(shapes):
- shape.available = True
- shape.shapeId = [i]
- shape.c0.shapeIds = []
- shape.c1.shapeIds = []
- for i, shape in enumerate(shapes):
- shape.c0.shapeIds.append(i)
- shape.c1.shapeIds.append(i)
- for i, shape in enumerate(shapes):
- shapeIds = shape.c1.shapeIds
- if len(shapeIds) == 2:
- if shapeIds[0] in shape.shapeId:
- s = shapeIds[1]
- else:
- s = shapeIds[0]
- if shape != shapes[s]:
- shape.merge(shapes[s])
- shape.shapeId += shapes[s].shapeId
- for j in shape.shapeId:
- shapes[j] = shape
- shapeIds = shape.c0.shapeIds
- if len(shapeIds) == 2:
- if shapeIds[0] in shape.shapeId:
- s = shapeIds[1]
- else:
- s = shapeIds[0]
- if shape != shapes[s]:
- shape.merge(shapes[s])
- shape.shapeId += shapes[s].shapeId
- for j in shape.shapeId:
- shapes[j] = shape
- for shape in shapes:
- if shape.available:
- shape.consume()
- merged.append(shape)
- print("Ops.merge() :%.2f seconds" % (time.time() - t))
- return merged
-
-
-class Selectable(object):
-
- """ selectable shapely geoms """
- def __init__(self, geoms, coordsys):
- # selection sets (bitArray)
- self.selections = []
- # selected objects on screen representation
- self.curves = []
- # Rtree to speedup region selections
- self.tree = Qtree(coordsys)
- self.tree.build(geoms)
- # BitArray ids of selected geoms
- self.ba = BitArray(self.ngeoms)
- # Material to represent selection on screen
- self.mat = self.build_display_mat("Selected",
- color=bpy.context.user_preferences.themes[0].view_3d.object_selected)
- self.cursor_fence = GlCursorFence()
- self.cursor_fence.enable()
- self.cursor_area = GlCursorArea()
- self.feedback = FeedbackPanel()
- self.action = None
- self.store_index = 0
-
- @property
- def coordsys(self):
- return self.tree.coordsys
-
- @property
- def geoms(self):
- return self.tree._geoms
-
- @property
- def ngeoms(self):
- return self.tree.ngeoms
-
- @property
- def nsets(self):
- return len(self.selections)
-
- def build_display_mat(self, name, color=(0.2, 0.2, 0)):
- mat = MaterialUtils.build_default_mat(name, color)
- mat.use_object_color = True
- mat.emit = 0.2
- mat.alpha = 0.2
- mat.game_settings.alpha_blend = 'ADD'
- return mat
-
- def _unselect(self, selection):
- t = time.time()
- for i in selection:
- self.ba.clear(i)
- print("Selectable._unselect() :%.2f seconds" % (time.time() - t))
-
- def _select(self, selection):
- t = time.time()
- for i in selection:
- self.ba.set(i)
- print("Selectable._select() :%.2f seconds" % (time.time() - t))
-
- def _position_3d_from_coord(self, context, coord):
- """return point in local input coordsys
- """
- region = context.region
- rv3d = context.region_data
- view_vector_mouse = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
- ray_origin_mouse = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
- loc = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse,
- Vector((0, 0, 0)), Vector((0, 0, 1)), False)
- x, y, z = self.coordsys.invert * loc
- return Vector((x, y, z))
-
- def _position_2d_from_coord(self, context, coord):
- """ coord given in local input coordsys
- """
- region = context.region
- rv3d = context.region_data
- loc = view3d_utils.location_3d_to_region_2d(region, rv3d, self.coordsys.world * coord)
- x, y = loc
- return Vector((x, y))
-
- def _contains(self, context, coord, event):
- t = time.time()
- point = self._position_3d_from_coord(context, coord)
- selection = []
- pt = ShapelyPoint(point)
- prepared_pt = shapely.prepared.prep(pt)
- count, gids = self.tree.intersects(pt)
- selection = [i for i in gids if prepared_pt.intersects(self.geoms[i])]
- print("Selectable._contains() :%.2f seconds" % (time.time() - t))
- if event.shift:
- self._unselect(selection)
- else:
- self._select(selection)
- self._draw(context)
-
- def _intersects(self, context, coord, event):
- t = time.time()
- c0 = self._position_3d_from_coord(context, coord)
- c1 = self._position_3d_from_coord(context, (coord[0], event.mouse_region_y))
- c2 = self._position_3d_from_coord(context, (event.mouse_region_x, event.mouse_region_y))
- c3 = self._position_3d_from_coord(context, (event.mouse_region_x, coord[1]))
- poly = ShapelyPolygon([c0, c1, c2, c3])
- prepared_poly = shapely.prepared.prep(poly)
- count, gids = self.tree.intersects(poly)
- if event.ctrl:
- selection = [i for i in gids if prepared_poly.contains(self.geoms[i])]
- else:
- selection = [i for i in gids if prepared_poly.intersects(self.geoms[i])]
- print("Selectable._intersects() :%.2f seconds" % (time.time() - t))
- if event.shift:
- self._unselect(selection)
- else:
- self._select(selection)
- self._draw(context)
-
- def _hide(self, context):
- t = time.time()
- if len(self.curves) > 0:
- try:
- for curve in self.curves:
- data = curve.data
- context.scene.objects.unlink(curve)
- bpy.data.objects.remove(curve, do_unlink=True)
- if data is None:
- return
- name = data.name
- if bpy.data.curves.find(name) > - 1:
- bpy.data.curves.remove(data, do_unlink=True)
- except:
- pass
- self.curves = []
- print("Selectable._hide() :%.2f seconds" % (time.time() - t))
-
- def _draw(self, context):
- print("Selectable._draw() %s" % (self.coordsys.world))
- t = time.time()
- self._hide(context)
- selection = [self.geoms[i] for i in self.ba.list]
- if len(selection) > 1000:
- self.curves = [Io.to_curve(context.scene, self.coordsys, selection, 'selection', '3D')]
- else:
- self.curves = Io.to_curves(context.scene, self.coordsys, selection, 'selection', '2D')
- for curve in self.curves:
- curve.color = (1, 1, 0, 1)
- if len(curve.data.materials) < 1:
- curve.data.materials.append(self.mat)
- curve.active_material = self.mat
- curve.select = True
- print("Selectable._draw() :%.2f seconds" % (time.time() - t))
-
- def store(self):
- self.selections.append(self.ba.copy)
- self.store_index = self.nsets
-
- def recall(self):
- if self.nsets > 0:
- if self.store_index < 1:
- self.store_index = self.nsets
- self.store_index -= 1
- self.ba = self.selections[self.store_index].copy
-
- def select(self, context, coord, event):
- if abs(event.mouse_region_x - coord[0]) > 2 and abs(event.mouse_region_y - coord[1]) > 2:
- self._intersects(context, coord, event)
- else:
- self._contains(context, (event.mouse_region_x, event.mouse_region_y), event)
-
- def init(self, pick_tool, context, action):
- raise NotImplementedError("Selectable must implement init(self, pick_tool, context, action)")
-
- def keyboard(self, context, event):
- """ keyboard events modal handler """
- raise NotImplementedError("Selectable must implement keyboard(self, context, event)")
-
- def complete(self, context):
- raise NotImplementedError("Selectable must implement complete(self, context)")
-
- def modal(self, context, event):
- """ modal handler """
- raise NotImplementedError("Selectable must implement modal(self, context, event)")
-
- def draw_callback(self, _self, context):
- """ a gl draw callback """
- raise NotImplementedError("Selectable must implement draw_callback(self, _self, context)")
-
-
-class SelectPoints(Selectable):
-
- def __init__(self, shapes, coordsys):
- geoms = []
- for shape in shapes:
- if shape.valid:
- for point in shape.points:
- point.users = 1
- for shape in shapes:
- if shape.valid:
- for point in shape.points:
- if point.users > 0:
- point.users = 0
- geoms.append(point.geom)
- super(SelectPoints, self).__init__(geoms, coordsys)
-
- def _draw(self, context):
- """ override draw method """
- print("SelectPoints._draw()")
- t = time.time()
- self._hide(context)
- selection = list(self.geoms[i] for i in self.ba.list)
- geom = ShapelyOps.union(selection)
- self.curves = [Io.to_curve(context.scene, self.coordsys, geom.convex_hull, 'selection', '3D')]
- for curve in self.curves:
- curve.color = (1, 1, 0, 1)
- curve.select = True
- print("SelectPoints._draw() :%.2f seconds" % (time.time() - t))
-
- def init(self, pick_tool, context, action):
- # Post selection actions
- self.selectMode = True
- self.object_location = None
- self.startPoint = (0, 0)
- self.endPoint = (0, 0)
- self.drag = False
- self.feedback.instructions(context, "Select Points", "Click & Drag to select points in area", [
- ('SHIFT', 'deselect'),
- ('CTRL', 'contains'),
- ('A', 'All'),
- ('I', 'Inverse'),
- ('F', 'Create line around selection'),
- # ('W', 'Create window using selection'),
- # ('D', 'Create door using selection'),
- ('ALT+F', 'Create best fit rectangle'),
- ('R', 'Retrieve selection'),
- ('S', 'Store selection'),
- ('ESC or RIGHTMOUSE', 'exit when done')
- ])
- self.feedback.enable()
- args = (self, context)
- self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback, args, 'WINDOW', 'POST_PIXEL')
- self.action = action
- self._draw(context)
- print("SelectPoints.init()")
-
- def complete(self, context):
- self.feedback.disable()
- self._hide(context)
-
- def keyboard(self, context, event):
- if event.type in {'A'}:
- if len(self.ba.list) > 0:
- self.ba.none()
- else:
- self.ba.all()
- elif event.type in {'I'}:
- self.ba.reverse()
- elif event.type in {'S'}:
- self.store()
- elif event.type in {'R'}:
- self.recall()
- elif event.type in {'F'}:
- sel = [self.geoms[i] for i in self.ba.list]
- if len(sel) > 0:
- scene = context.scene
- geom = ShapelyOps.union(sel)
- if event.alt:
- tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom)
- x0 = -w / 2.0
- y0 = -h / 2.0
- x1 = w / 2.0
- y1 = h / 2.0
- poly = shapely.geometry.LineString([(x0, y0, 0), (x1, y0, 0), (x1, y1, 0),
- (x0, y1, 0), (x0, y0, 0)])
- result = Io.to_curve(scene, self.coordsys, poly, 'points')
- result.matrix_world = self.coordsys.world * tM
- scene.objects.active = result
- else:
- result = Io.to_curve(scene, self.coordsys, geom.convex_hull, 'points')
- scene.objects.active = result
- self.ba.none()
- self.complete(context)
- elif event.type in {'W'}:
- sel = [self.geoms[i] for i in self.ba.list]
- if len(sel) > 0:
- scene = context.scene
- geom = ShapelyOps.union(sel)
- if event.alt:
- tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom)
- x0 = -w / 2.0
- y0 = -h / 2.0
- x1 = w / 2.0
- y1 = h / 2.0
- poly = shapely.geometry.LineString([(x0, y0, 0), (x1, y0, 0), (x1, y1, 0),
- (x0, y1, 0), (x0, y0, 0)])
- result = Io.to_curve(scene, self.coordsys, poly, 'points')
- result.matrix_world = self.coordsys.world * tM
- scene.objects.active = result
- else:
- result = Io.to_curve(scene, self.coordsys, geom.convex_hull, 'points')
- scene.objects.active = result
- self.ba.none()
- self.complete(context)
- elif event.type in {'D'}:
- sel = [self.geoms[i] for i in self.ba.list]
- if len(sel) > 0:
- scene = context.scene
- geom = ShapelyOps.union(sel)
- if event.alt:
- tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom)
- x0 = -w / 2.0
- y0 = -h / 2.0
- x1 = w / 2.0
- y1 = h / 2.0
- poly = shapely.geometry.LineString([(x0, y0, 0), (x1, y0, 0), (x1, y1, 0),
- (x0, y1, 0), (x0, y0, 0)])
- result = Io.to_curve(scene, self.coordsys, poly, 'points')
- result.matrix_world = self.coordsys.world * tM
- scene.objects.active = result
- else:
- result = Io.to_curve(scene, self.coordsys, geom.convex_hull, 'points')
- scene.objects.active = result
- self.ba.none()
- self.complete(context)
- self._draw(context)
-
- def modal(self, context, event):
- if event.type in {'I', 'A', 'S', 'R', 'F'} and event.value == 'PRESS':
- self.keyboard(context, event)
- elif event.type in {'RIGHTMOUSE', 'ESC'}:
- self.complete(context)
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- return {'FINISHED'}
- elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
- self.drag = True
- self.cursor_area.enable()
- self.cursor_fence.disable()
- self.startPoint = (event.mouse_region_x, event.mouse_region_y)
- self.endPoint = (event.mouse_region_x, event.mouse_region_y)
- elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
- self.drag = False
- self.cursor_area.disable()
- self.cursor_fence.enable()
- self.endPoint = (event.mouse_region_x, event.mouse_region_y)
- self.select(context, self.startPoint, event)
- elif event.type == 'MOUSEMOVE':
- self.endPoint = (event.mouse_region_x, event.mouse_region_y)
- return {'RUNNING_MODAL'}
-
- def draw_callback(self, _self, context):
- self.feedback.draw(context)
- self.cursor_area.set_location(context, self.startPoint, self.endPoint)
- self.cursor_fence.set_location(context, self.endPoint)
- self.cursor_area.draw(context)
- self.cursor_fence.draw(context)
-
-
-class SelectLines(Selectable):
-
- def __init__(self, geoms, coordsys):
- super(SelectLines, self).__init__(geoms, coordsys)
-
- def _draw(self, context):
- """ override draw method """
- print("SelectLines._draw()")
- t = time.time()
- self._hide(context)
- selection = list(self.geoms[i] for i in self.ba.list)
- self.curves = [Io.to_curve(context.scene, self.coordsys, selection, 'selection', '3D')]
- for curve in self.curves:
- curve.color = (1, 1, 0, 1)
- curve.select = True
- print("SelectLines._draw() :%.2f seconds" % (time.time() - t))
-
- def init(self, pick_tool, context, action):
- # Post selection actions
- self.selectMode = True
- self.object_location = None
- self.startPoint = (0, 0)
- self.endPoint = (0, 0)
- self.drag = False
- self.feedback.instructions(context, "Select Lines", "Click & Drag to select lines in area", [
- ('SHIFT', 'deselect'),
- ('CTRL', 'contains'),
- ('A', 'All'),
- ('I', 'Inverse'),
- # ('F', 'Create lines from selection'),
- ('R', 'Retrieve selection'),
- ('S', 'Store selection'),
- ('ESC or RIGHTMOUSE', 'exit when done')
- ])
- self.feedback.enable()
- args = (self, context)
- self._handle = bpy.types.SpaceView3D.draw_handler_add(
- self.draw_callback, args, 'WINDOW', 'POST_PIXEL')
- self.action = action
- self._draw(context)
- print("SelectLines.init()")
-
- def complete(self, context):
- print("SelectLines.complete()")
- t = time.time()
- self._hide(context)
- scene = context.scene
- selection = list(self.geoms[i] for i in self.ba.list)
- if len(selection) > 0:
- if self.action == 'select':
- result = Io.to_curve(scene, self.coordsys, selection, 'selection')
- scene.objects.active = result
- elif self.action == 'union':
- shapes = Io.geoms_to_shapes(selection)
- merged = ShapeOps.merge(shapes)
- union = Io.shapes_to_geoms(merged)
- # union = self.ops.union(selection)
- resopt = ShapelyOps.optimize(union)
- result = Io.to_curve(scene, self.coordsys, resopt, 'union')
- scene.objects.active = result
- self.feedback.disable()
- print("SelectLines.complete() :%.2f seconds" % (time.time() - t))
-
- def keyboard(self, context, event):
- if event.type in {'A'}:
- if len(self.ba.list) > 0:
- self.ba.none()
- else:
- self.ba.all()
- elif event.type in {'I'}:
- self.ba.reverse()
- elif event.type in {'S'}:
- self.store()
- elif event.type in {'R'}:
- self.recall()
- self._draw(context)
-
- def modal(self, context, event):
- if event.type in {'I', 'A', 'S', 'R'} and event.value == 'PRESS':
- self.keyboard(context, event)
- elif event.type in {'RIGHTMOUSE', 'ESC'}:
- self.complete(context)
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- return {'FINISHED'}
- elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
- self.drag = True
- self.cursor_area.enable()
- self.cursor_fence.disable()
- self.startPoint = (event.mouse_region_x, event.mouse_region_y)
- self.endPoint = (event.mouse_region_x, event.mouse_region_y)
- elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
- self.drag = False
- self.cursor_area.disable()
- self.cursor_fence.enable()
- self.endPoint = (event.mouse_region_x, event.mouse_region_y)
- self.select(context, self.startPoint, event)
- elif event.type == 'MOUSEMOVE':
- self.endPoint = (event.mouse_region_x, event.mouse_region_y)
- return {'RUNNING_MODAL'}
-
- def draw_callback(self, _self, context):
- self.feedback.draw(context)
- self.cursor_area.set_location(context, self.startPoint, self.endPoint)
- self.cursor_fence.set_location(context, self.endPoint)
- self.cursor_area.draw(context)
- self.cursor_fence.draw(context)
-
-
-class SelectPolygons(Selectable):
-
- def __init__(self, geoms, coordsys):
- super(SelectPolygons, self).__init__(geoms, coordsys)
-
- """
- pick_tools actions
- """
- def init(self, pick_tool, context, action):
- # Post selection actions
- self.need_rotation = False
- self.direction = 0
- self.object_location = None
- self.selectMode = True
- self.startPoint = (0, 0)
- self.endPoint = (0, 0)
- if action in ['select', 'union', 'rectangle']:
- self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [
- ('SHIFT', 'deselect'),
- ('CTRL', 'contains'),
- ('A', 'All'),
- ('I', 'Inverse'),
- ('B', 'Bigger than current'),
- # ('F', 'Create from selection'),
- ('R', 'Retrieve selection'),
- ('S', 'Store selection'),
- ('ESC or RIGHTMOUSE', 'exit when done')
- ])
- elif action == 'wall':
- self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [
- ('SHIFT', 'deselect'),
- ('CTRL', 'contains'),
- ('A', 'All'),
- ('I', 'Inverse'),
- ('B', 'Bigger than current'),
- ('R', 'Retrieve selection'),
- ('S', 'Store selection'),
- ('ESC or RIGHTMOUSE', 'exit and build wall when done')
- ])
- elif action == 'window':
- self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [
- ('SHIFT', 'deselect'),
- ('CTRL', 'contains'),
- ('A', 'All'),
- ('I', 'Inverse'),
- ('B', 'Bigger than current'),
- ('F', 'Create a window from selection'),
- ('ESC or RIGHTMOUSE', 'exit tool when done')
- ])
- elif action == 'door':
- self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [
- ('SHIFT', 'deselect'),
- ('CTRL', 'contains'),
- ('A', 'All'),
- ('I', 'Inverse'),
- ('B', 'Bigger than current'),
- ('F', 'Create a door from selection'),
- ('ESC or RIGHTMOUSE', 'exit tool when done')
- ])
- self.gl_arc = GlPolyline((1.0, 1.0, 1.0, 0.5), d=3)
- self.gl_arc.width = 1
- self.gl_arc.style = bgl.GL_LINE_STIPPLE
- self.gl_line = GlLine(d=3)
- self.gl_line.colour_inactive = (1.0, 1.0, 1.0, 0.5)
- self.gl_line.width = 2
- self.gl_line.style = bgl.GL_LINE_STIPPLE
- self.gl_side = GlLine(d=2)
- self.gl_side.colour_inactive = (1.0, 1.0, 1.0, 0.5)
- self.gl_side.width = 2
- self.gl_side.style = bgl.GL_LINE_STIPPLE
- self.feedback.enable()
- self.drag = False
- args = (self, context)
- self._handle = bpy.types.SpaceView3D.draw_handler_add(
- self.draw_callback, args, 'WINDOW', 'POST_PIXEL')
- self.action = action
- self._draw(context)
- print("SelectPolygons.init()")
-
- def complete(self, context):
- print("SelectPolygons.complete()")
- t = time.time()
- scene = context.scene
- self._hide(context)
- selection = list(self.geoms[i] for i in self.ba.list)
- if len(selection) > 0:
- if self.action == 'select':
- result = Io.to_curve(scene, self.coordsys, selection, 'selection')
- scene.objects.active = result
- elif self.action == 'union':
- union = ShapelyOps.union(selection)
- resopt = ShapelyOps.optimize(union)
- result = Io.to_curve(scene, self.coordsys, resopt, 'union')
- scene.objects.active = result
- elif self.action == 'wall':
- union = ShapelyOps.union(selection)
- union = ShapelyOps.optimize(union)
- res = []
- z = context.window_manager.archipack_polylib.solidify_thickness
- Io.to_wall(scene, self.coordsys, union, z, 'wall', res)
- if len(res) > 0:
- scene.objects.active = res[0]
- if len(res) > 1:
- bpy.ops.object.join()
- bpy.ops.archipack.wall(z=z)
- elif self.action == 'rectangle':
- # currently only output a best fitted rectangle
- # over selection
- if self.object_location is not None:
- tM, w, h, l_pts, w_pts = self.object_location
- poly = shapely.geometry.LineString(l_pts)
- result = Io.to_curve(scene, self.coordsys, poly, 'rectangle')
- result.matrix_world = self.coordsys.world * tM
- scene.objects.active = result
- self.ba.none()
- elif self.action == 'window':
- if self.object_location is not None:
-
- tM, w, h, l_pts, w_pts = self.object_location
-
- if self.need_rotation:
- rM = Matrix([
- [-1, 0, 0, 0],
- [0, -1, 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1],
- ])
- else:
- rM = Matrix()
-
- if w > 1.8:
- z = 2.2
- altitude = 0.0
- else:
- z = 1.2
- altitude = 1.0
-
- bpy.ops.archipack.window(x=w, y=h, z=z, altitude=altitude, auto_manipulate=False)
- result = context.object
- result.matrix_world = self.coordsys.world * tM * rM
- result.data.archipack_window[0].hole_margin = 0.02
- self.ba.none()
- elif self.action == 'door':
- if self.object_location is not None:
-
- tM, w, h, l_pts, w_pts = self.object_location
-
- if self.need_rotation:
- rM = Matrix([
- [-1, 0, 0, 0],
- [0, -1, 0, 0],
- [0, 0, 1, 0],
- [0, 0, 0, 1],
- ])
- else:
- rM = Matrix()
-
- if w < 1.5:
- n_panels = 1
- else:
- n_panels = 2
-
- bpy.ops.archipack.door(x=w, y=h, z=2.0, n_panels=n_panels,
- direction=self.direction, auto_manipulate=False)
- result = context.object
- result.matrix_world = self.coordsys.world * tM * rM
- result.data.archipack_door[0].hole_margin = 0.02
- self.ba.none()
-
- if self.action not in ['window', 'door']:
- self.feedback.disable()
-
- print("SelectPolygons.complete() :%.2f seconds" % (time.time() - t))
-
- def keyboard(self, context, event):
- if event.type in {'A'}:
- if len(self.ba.list) > 0:
- self.ba.none()
- else:
- self.ba.all()
- elif event.type in {'I'}:
- self.ba.reverse()
- elif event.type in {'S'}:
- self.store()
- elif event.type in {'R'}:
- self.recall()
- elif event.type in {'B'}:
- areas = [self.geoms[i].area for i in self.ba.list]
- area = max(areas)
- self.ba.none()
- for i, geom in enumerate(self.geoms):
- if geom.area > area:
- self.ba.set(i)
- elif event.type in {'F'}:
- if self.action == 'rectangle':
- self.complete(context)
- else:
- sel = [self.geoms[i] for i in self.ba.list]
- if len(sel) > 0:
- if self.action == 'window':
- self.feedback.instructions(context,
- "Select Polygons", "Click & Drag to select polygons in area", [
- ('CLICK & DRAG', 'Set window orientation'),
- ('RELEASE', 'Create window'),
- ('F', 'Return to select mode'),
- ('ESC or RIGHTMOUSE', 'exit tool when done')
- ])
- elif self.action == 'door':
- self.feedback.instructions(context,
- "Select Polygons", "Click & Drag to select polygons in area", [
- ('CLICK & DRAG', 'Set door orientation'),
- ('RELEASE', 'Create door'),
- ('F', 'Return to select mode'),
- ('ESC or RIGHTMOUSE', 'exit tool when done')
- ])
- self.selectMode = not self.selectMode
- geom = ShapelyOps.union(sel)
- tM, w, h, l_pts, w_pts = ShapelyOps.min_bounding_rect(geom)
- self.object_location = (tM, w, h, l_pts, w_pts)
- self.startPoint = self._position_2d_from_coord(context, tM.translation)
- self._draw(context)
-
- def modal(self, context, event):
- if event.type in {'I', 'A', 'S', 'R', 'F', 'B'} and event.value == 'PRESS':
-
- self.keyboard(context, event)
- elif event.type in {'RIGHTMOUSE', 'ESC'}:
- if self.action == 'object':
- self._hide(context)
- else:
- self.complete(context)
- bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
- return {'FINISHED'}
- elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
- self.drag = True
- self.cursor_area.enable()
- self.cursor_fence.disable()
- if self.selectMode:
- self.startPoint = (event.mouse_region_x, event.mouse_region_y)
- self.endPoint = (event.mouse_region_x, event.mouse_region_y)
- elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
- self.drag = False
- self.cursor_area.disable()
- self.cursor_fence.enable()
- self.endPoint = (event.mouse_region_x, event.mouse_region_y)
- if self.selectMode:
- self.select(context, self.startPoint, event)
- else:
- self.complete(context)
- if self.action == 'window':
- self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [
- ('SHIFT', 'deselect'),
- ('CTRL', 'contains'),
- ('A', 'All'),
- ('I', 'Inverse'),
- ('B', 'Bigger than current'),
- ('F', 'Create a window from selection'),
- ('ESC or RIGHTMOUSE', 'exit tool when done')
- ])
- elif self.action == 'door':
- self.feedback.instructions(context, "Select Polygons", "Click & Drag to select polygons in area", [
- ('SHIFT', 'deselect'),
- ('CTRL', 'contains'),
- ('A', 'All'),
- ('I', 'Inverse'),
- ('B', 'Bigger than current'),
- ('F', 'Create a door from selection'),
- ('ESC or RIGHTMOUSE', 'exit tool when done')
- ])
- self.selectMode = True
- if event.type == 'MOUSEMOVE':
- self.endPoint = (event.mouse_region_x, event.mouse_region_y)
- return {'RUNNING_MODAL'}
-
- def _draw_2d_arc(self, context, c, p0, p1):
- """
- draw projection of 3d arc in 2d space
- """
- d0 = np.subtract(c, p0)
- d1 = np.subtract(p1, c)
- a0 = atan2(d0[1], d0[0])
- a1 = atan2(d1[1], d1[0])
- da = a1 - a0
- if da < pi:
- da += 2 * pi
- if da > pi:
- da -= 2 * pi
- da = da / 12
- r = np.linalg.norm(d1)
- pts = []
- for i in range(13):
- a = a0 + da * i
- p3d = c + Vector((cos(a) * r, sin(a) * r, 0))
- pts.append(self.coordsys.world * p3d)
-
- self.gl_arc.set_pos(pts)
- self.gl_arc.draw(context)
- self.gl_line.p = self.coordsys.world * c
- self.gl_line.v = pts[0] - self.gl_line.p
- self.gl_line.draw(context)
-
- def draw_callback(self, _self, context):
- """
- draw on screen feedback using gl.
- """
- self.feedback.draw(context)
-
- if self.selectMode:
- self.cursor_area.set_location(context, self.startPoint, self.endPoint)
- self.cursor_fence.set_location(context, self.endPoint)
- self.cursor_area.draw(context)
- self.cursor_fence.draw(context)
- else:
- if self.drag:
- x0, y0 = self.startPoint
- x1, y1 = self.endPoint
- # draw 2d line marker
- # self.gl.Line(x0, y0, x1, y1, self.gl.line_colour)
-
- # 2d line
- self.gl_side.p = Vector(self.startPoint)
- self.gl_side.v = Vector(self.endPoint) - Vector(self.startPoint)
- self.gl_side.draw(context)
-
- tM, w, h, l_pts, w_pts = self.object_location
- pt = self._position_3d_from_coord(context, self.endPoint)
- pt = tM.inverted() * Vector(pt)
- self.need_rotation = pt.y < 0
- if self.action == 'door':
- # symbole porte
- if pt.x > 0:
- if pt.y > 0:
- self.direction = 1
- i_s, i_c, i_e = 3, 2, 1
- else:
- self.direction = 0
- i_s, i_c, i_e = 2, 3, 0
- else:
- if pt.y > 0:
- self.direction = 0
- i_s, i_c, i_e = 0, 1, 2
- else:
- self.direction = 1
- i_s, i_c, i_e = 1, 0, 3
- self._draw_2d_arc(context, w_pts[i_c], w_pts[i_s], w_pts[i_e])
- elif self.action == 'window':
- # symbole fenetre
- if pt.y > 0:
- i_s0, i_c0 = 0, 1
- i_s1, i_c1 = 3, 2
- else:
- i_s0, i_c0 = 1, 0
- i_s1, i_c1 = 2, 3
- pc = w_pts[i_c0] + 0.5 * (w_pts[i_c1] - w_pts[i_c0])
- self._draw_2d_arc(context, w_pts[i_c0], w_pts[i_s0], pc)
- self._draw_2d_arc(context, w_pts[i_c1], w_pts[i_s1], pc)
-
-
-class ARCHIPACK_OP_PolyLib_Pick2DPoints(Operator):
- bl_idname = "archipack.polylib_pick_2d_points"
- bl_label = "Pick lines"
- bl_description = "Pick lines"
- bl_options = {'REGISTER', 'UNDO'}
- pass_keys = ['NUMPAD_0', 'NUMPAD_1', 'NUMPAD_3', 'NUMPAD_4',
- 'NUMPAD_5', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8',
- 'NUMPAD_9', 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE']
- action = StringProperty(name="action", default="select")
-
- @classmethod
- def poll(self, context):
- global vars_dict
- return vars_dict['select_points'] is not None
-
- def modal(self, context, event):
- global vars_dict
- context.area.tag_redraw()
- if event.type in self.pass_keys:
- return {'PASS_THROUGH'}
- return vars_dict['select_points'].modal(context, event)
-
- def invoke(self, context, event):
- global vars_dict
- if vars_dict['select_points'] is None:
- self.report({'WARNING'}, "Use detect before")
- return {'CANCELLED'}
- elif context.space_data.type == 'VIEW_3D':
- vars_dict['select_points'].init(self, context, self.action)
- context.window_manager.modal_handler_add(self)
- return {'RUNNING_MODAL'}
- else:
- self.report({'WARNING'}, "Active space must be a View3d")
- return {'CANCELLED'}
-
-
-class ARCHIPACK_OP_PolyLib_Pick2DLines(Operator):
- bl_idname = "archipack.polylib_pick_2d_lines"
- bl_label = "Pick lines"
- bl_description = "Pick lines"
- bl_options = {'REGISTER', 'UNDO'}
- pass_keys = ['NUMPAD_0', 'NUMPAD_1', 'NUMPAD_3', 'NUMPAD_4',
- 'NUMPAD_5', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8',
- 'NUMPAD_9', 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE']
- action = StringProperty(name="action", default="select")
-
- @classmethod
- def poll(self, context):
- global vars_dict
- return vars_dict['select_lines'] is not None
-
- def modal(self, context, event):
- global vars_dict
- context.area.tag_redraw()
- if event.type in self.pass_keys:
- return {'PASS_THROUGH'}
- return vars_dict['select_lines'].modal(context, event)
-
- def invoke(self, context, event):
- global vars_dict
- if vars_dict['select_lines'] is None:
- self.report({'WARNING'}, "Use detect before")
- return {'CANCELLED'}
- elif context.space_data.type == 'VIEW_3D':
- vars_dict['select_lines'].init(self, context, self.action)
- context.window_manager.modal_handler_add(self)
- return {'RUNNING_MODAL'}
- else:
- self.report({'WARNING'}, "Active space must be a View3d")
- return {'CANCELLED'}
-
-
-class ARCHIPACK_OP_PolyLib_Pick2DPolygons(Operator):
- bl_idname = "archipack.polylib_pick_2d_polygons"
- bl_label = "Pick 2d"
- bl_description = "Pick polygons"
- bl_options = {'REGISTER', 'UNDO'}
- pass_keys = ['NUMPAD_0', 'NUMPAD_1', 'NUMPAD_3', 'NUMPAD_4',
- 'NUMPAD_5', 'NUMPAD_6', 'NUMPAD_7', 'NUMPAD_8',
- 'NUMPAD_9', 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE']
- action = StringProperty(name="action", default="select")
-
- @classmethod
- def poll(self, context):
- global vars_dict
- return vars_dict['select_polygons'] is not None
-
- def modal(self, context, event):
- global vars_dict
- context.area.tag_redraw()
- if event.type in self.pass_keys:
- return {'PASS_THROUGH'}
- return vars_dict['select_polygons'].modal(context, event)
-
- def invoke(self, context, event):
- global vars_dict
- if vars_dict['select_polygons'] is None:
- self.report({'WARNING'}, "Use detect before")
- return {'CANCELLED'}
- elif context.space_data.type == 'VIEW_3D':
- vars_dict['select_polygons'].init(self, context, self.action)
- context.window_manager.modal_handler_add(self)
- return {'RUNNING_MODAL'}
- else:
- self.report({'WARNING'}, "Active space must be a View3d")
- return {'CANCELLED'}
-
-
-class ARCHIPACK_OP_PolyLib_Detect(Operator):
- bl_idname = "archipack.polylib_detect"
- bl_label = "Detect Polygons"
- bl_description = "Detect polygons from unordered splines"
- bl_options = {'REGISTER', 'UNDO'}
- extend = FloatProperty(name="extend", default=0.01, subtype='DISTANCE', unit='LENGTH', min=0)
-
- @classmethod
- def poll(self, context):
- return len(context.selected_objects) > 0 and context.object is not None and context.object.type == 'CURVE'
-
- def execute(self, context):
- global vars_dict
- print("Detect")
- t = time.time()
- objs = [obj for obj in context.selected_objects if obj.type == 'CURVE']
-
- if len(objs) < 1:
- self.report({'WARNING'}, "Select a curve object before")
- return {'CANCELLED'}
-
- for obj in objs:
- obj.select = False
-
- coordsys = CoordSys(objs)
-
- vars_dict['point_tree'] = Qtree(coordsys, extend=0.5 * EPSILON)
- vars_dict['seg_tree'] = Qtree(coordsys, extend=self.extend)
-
- # Shape based union
- shapes = []
- Io.curves_to_shapes(objs, coordsys, context.window_manager.archipack_polylib.resolution, shapes)
- union = ShapeOps.union(shapes, self.extend)
-
- # output select points
- vars_dict['select_points'] = SelectPoints(shapes, coordsys)
-
- geoms = Io.shapes_to_geoms(union)
-
- # output select_lines
- vars_dict['select_lines'] = SelectLines(geoms, coordsys)
-
- # Shapely based union
- # vars_dict['select_polygons'].io.curves_as_shapely(objs, lines)
- # geoms = vars_dict['select_polygons'].ops.union(lines, self.extend)
-
- result, dangles, cuts, invalids = ShapelyOps.detect_polygons(geoms)
- vars_dict['select_polygons'] = SelectPolygons(result, coordsys)
-
- if len(invalids) > 0:
- errs = Io.to_curve(context.scene, coordsys, invalids, "invalid_polygons")
- err_mat = vars_dict['select_polygons'].build_display_mat("Invalid_polygon", (1, 0, 0))
- # curve.data.bevel_depth = 0.02
- errs.color = (1, 0, 0, 1)
- if len(errs.data.materials) < 1:
- errs.data.materials.append(err_mat)
- errs.active_material = err_mat
- errs.select = True
- self.report({'WARNING'}, str(len(invalids)) + " invalid polygons detected")
- print("Detect :%.2f seconds polygons:%s invalids:%s" % (time.time() - t, len(result), len(invalids)))
- return {'FINISHED'}
-
-
-class ARCHIPACK_OP_PolyLib_Offset(Operator):
- bl_idname = "archipack.polylib_offset"
- bl_label = "Offset"
- bl_description = "Offset lines"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- return len(context.selected_objects) > 0 and context.object is not None and context.object.type == 'CURVE'
-
- def execute(self, context):
- wm = context.window_manager.archipack_polylib
- objs = list(obj for obj in context.selected_objects if obj.type == 'CURVE')
- if len(objs) < 1:
- self.report({'WARNING'}, "Select a curve object before")
- return {'CANCELLED'}
- for obj in objs:
- obj.select = False
- lines = []
- coordsys = Io.curves_to_geoms(objs, wm.resolution, lines)
- offset = []
- for line in lines:
- res = line.parallel_offset(wm.offset_distance, wm.offset_side, resolution=wm.offset_resolution,
- join_style=int(wm.offset_join_style), mitre_limit=wm.offset_mitre_limit)
- offset.append(res)
- Io.to_curve(context.scene, coordsys, offset, 'offset')
- return {'FINISHED'}
-
-
-class ARCHIPACK_OP_PolyLib_Simplify(Operator):
- bl_idname = "archipack.polylib_simplify"
- bl_label = "Simplify"
- bl_description = "Simplify lines"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- return (len(context.selected_objects) > 0 and
- context.object is not None and
- context.object.type == 'CURVE')
-
- def execute(self, context):
- global vars_dict
- wm = context.window_manager.archipack_polylib
- objs = [obj for obj in context.selected_objects if obj.type == 'CURVE']
- if len(objs) < 1:
- self.report({'WARNING'}, "Select a curve object before")
- return {'CANCELLED'}
- for obj in objs:
- obj.select = False
- simple = []
- lines = []
- coordsys = Io.curves_to_geoms(objs, wm.resolution, lines)
- for line in lines:
- res = line.simplify(wm.simplify_tolerance, preserve_topology=wm.simplify_preserve_topology)
- simple.append(res)
- Io.to_curve(context.scene, coordsys, simple, 'simplify')
- return {'FINISHED'}
-
-
-class ARCHIPACK_OP_PolyLib_OutputPolygons(Operator):
- bl_idname = "archipack.polylib_output_polygons"
- bl_label = "Output Polygons"
- bl_description = "Output all polygons"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- global vars_dict
- return vars_dict['select_polygons'] is not None
-
- def execute(self, context):
- global vars_dict
- result = Io.to_curve(context.scene, vars_dict['select_polygons'].coordsys,
- vars_dict['select_polygons'].geoms, 'polygons')
- context.scene.objects.active = result
- return {'FINISHED'}
-
-
-class ARCHIPACK_OP_PolyLib_OutputLines(Operator):
- bl_idname = "archipack.polylib_output_lines"
- bl_label = "Output lines"
- bl_description = "Output all lines"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- global vars_dict
- return vars_dict['select_lines'] is not None
-
- def execute(self, context):
- global vars_dict
- result = Io.to_curve(context.scene, vars_dict['select_lines'].coordsys,
- vars_dict['select_lines'].geoms, 'lines')
- context.scene.objects.active = result
- return {'FINISHED'}
-
-
-class ARCHIPACK_OP_PolyLib_Solidify(Operator):
- bl_idname = "archipack.polylib_solidify"
- bl_label = "Extrude"
- bl_description = "Extrude all polygons"
- bl_options = {'REGISTER', 'UNDO'}
-
- @classmethod
- def poll(self, context):
- return (len(context.selected_objects) > 0 and
- context.object is not None and
- context.object.type == 'CURVE')
-
- def execute(self, context):
- wm = context.window_manager.archipack_polylib
- objs = [obj for obj in context.selected_objects if obj.type == 'CURVE']
- if len(objs) < 1:
- self.report({'WARNING'}, "Select a curve object before")
- return {'CANCELLED'}
- for obj in objs:
- obj.data.dimensions = '2D'
- mod = obj.modifiers.new("Solidify", 'SOLIDIFY')
- mod.thickness = wm.solidify_thickness
- mod.offset = 1.00
- mod.use_even_offset = True
- mod.use_quality_normals = True
- return {'FINISHED'}
-
-
-class archipack_polylib(PropertyGroup):
- bl_idname = 'archipack.polylib_parameters'
- extend = FloatProperty(
- name="Extend",
- description="Extend to closest intersecting segment",
- default=0.01,
- subtype='DISTANCE', unit='LENGTH', min=0
- )
- offset_distance = FloatProperty(
- name="Distance",
- default=0.05,
- subtype='DISTANCE', unit='LENGTH', min=0
- )
- offset_side = EnumProperty(
- name="Side", default='left',
- items=[('left', 'Left', 'Left'),
- ('right', 'Right', 'Right')]
- )
- offset_resolution = IntProperty(
- name="Resolution", default=16
- )
- offset_join_style = EnumProperty(
- name="Style", default='2',
- items=[('1', 'Round', 'Round'),
- ('2', 'Mitre', 'Mitre'),
- ('3', 'Bevel', 'Bevel')]
- )
- offset_mitre_limit = FloatProperty(
- name="Mitre limit",
- default=10.0,
- subtype='DISTANCE',
- unit='LENGTH', min=0
- )
- simplify_tolerance = FloatProperty(
- name="Tolerance",
- default=0.01,
- subtype='DISTANCE', unit='LENGTH', min=0
- )
- simplify_preserve_topology = BoolProperty(
- name="Preserve topology",
- description="Preserve topology (fast without, but may introduce self crossing)",
- default=True
- )
- solidify_thickness = FloatProperty(
- name="Thickness",
- default=2.7,
- subtype='DISTANCE', unit='LENGTH', min=0
- )
- resolution = IntProperty(
- name="Bezier resolution", min=0, default=12
- )
-
-
-@persistent
-def load_handler(dummy):
- global vars_dict
- vars_dict['select_polygons'] = None
- vars_dict['select_lines'] = None
- vars_dict['seg_tree'] = None
- vars_dict['point_tree'] = None
-
-
-def register():
- global vars_dict
- vars_dict = {
- # spacial tree for segments and points
- 'seg_tree': None,
- 'point_tree': None,
- # keep track of shapely geometry selection sets
- 'select_polygons': None,
- 'select_lines': None,
- 'select_points': None
- }
- bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Pick2DPolygons)
- bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Pick2DLines)
- bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Pick2DPoints)
- bpy.utils.register_class(ARCHIPACK_OP_PolyLib_OutputPolygons)
- bpy.utils.register_class(ARCHIPACK_OP_PolyLib_OutputLines)
- bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Offset)
- bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Simplify)
- bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Detect)
- bpy.utils.register_class(ARCHIPACK_OP_PolyLib_Solidify)
- bpy.utils.register_class(archipack_polylib)
- bpy.types.WindowManager.archipack_polylib = PointerProperty(type=archipack_polylib)
- bpy.app.handlers.load_post.append(load_handler)
-
-
-def unregister():
- global vars_dict
- del vars_dict
- bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Pick2DPolygons)
- bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Pick2DLines)
- bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Pick2DPoints)
- bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Detect)
- bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_OutputPolygons)
- bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_OutputLines)
- bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Offset)
- bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Simplify)
- bpy.utils.unregister_class(ARCHIPACK_OP_PolyLib_Solidify)
- bpy.utils.unregister_class(archipack_polylib)
- bpy.app.handlers.load_post.remove(load_handler)
- del bpy.types.WindowManager.archipack_polylib
-
-
-if __name__ == "__main__":
- register()
diff --git a/archipack/archipack_wall.py b/archipack/archipack_wall.py
deleted file mode 100644
index 5adf92c2..00000000
--- a/archipack/archipack_wall.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# -*- coding:utf-8 -*-
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# 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 2
-# 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, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-# ----------------------------------------------------------
-# Author: Stephen Leger (s-leger)
-#
-# ----------------------------------------------------------
-import bpy
-import bmesh
-from bpy.types import Operator, PropertyGroup, Mesh, Panel
-from bpy.props import FloatProperty, CollectionProperty
-from .archipack_object import ArchipackObject
-
-
-def update_wall(self, context):
- self.update(context)
-
-
-class archipack_wall(ArchipackObject, PropertyGroup):
- z = FloatProperty(
- name='height',
- min=0.1, max=10000,
- default=2.7, precision=2,
- description='height', update=update_wall,
- )
-
- def update(self, context):
- # update height via bmesh to avoid loosing material ids
- # this should be the rule for other simple objects
- # as long as there is no topologic changes
- o = context.active_object
- if archipack_wall.datablock(o) != self:
- return
- bpy.ops.object.mode_set(mode='EDIT')
- me = o.data
- bm = bmesh.from_edit_mesh(me)
- bm.verts.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
- new_z = self.z
- last_z = list(v.co.z for v in bm.verts)
- max_z = max(last_z)
- for v in bm.verts:
- if v.co.z == max_z:
- v.co.z = new_z
- bmesh.update_edit_mesh(me, True)
- bpy.ops.object.mode_set(mode='OBJECT')
-
-
-class ARCHIPACK_PT_wall(Panel):
- bl_idname = "ARCHIPACK_PT_wall"
- bl_label = "Wall"
- bl_space_type = 'VIEW_3D'
- bl_region_type = 'UI'
- bl_category = 'ArchiPack'
-
- @classmethod
- def poll(cls, context):
- return archipack_wall.filter(context.active_object)
-
- def draw(self, context):
-
- prop = archipack_wall.datablock(context.active_object)
- if prop is None:
- return
- layout = self.layout
- layout.prop(prop, 'z')
-
-
-# ------------------------------------------------------------------
-# Define operator class to create object
-# ------------------------------------------------------------------
-
-
-class ARCHIPACK_OT_wall(Operator):
- bl_idname = "archipack.wall"
- bl_label = "Wall"
- bl_description = "Add wall parameters to active object"
- bl_category = 'Archipack'
- bl_options = {'REGISTER', 'UNDO'}
- z = FloatProperty(
- name="z",
- default=2.7
- )
-
- @classmethod
- def poll(cls, context):
- return context.active_object is not None
-
- def draw(self, context):
- layout = self.layout
- row = layout.row()
- row.label("Use Properties panel (N) to define parms", icon='INFO')
-
- def execute(self, context):
- if context.mode == "OBJECT":
- o = context.active_object
- if archipack_wall.filter(o):
- return {'CANCELLED'}
- params = o.data.archipack_wall.add()
- params.z = self.z
- return {'FINISHED'}
- else:
- self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
- return {'CANCELLED'}
-
-
-def register():
- bpy.utils.register_class(archipack_wall)
- Mesh.archipack_wall = CollectionProperty(type=archipack_wall)
- bpy.utils.register_class(ARCHIPACK_PT_wall)
- bpy.utils.register_class(ARCHIPACK_OT_wall)
-
-
-def unregister():
- bpy.utils.unregister_class(archipack_wall)
- del Mesh.archipack_wall
- bpy.utils.unregister_class(ARCHIPACK_PT_wall)
- bpy.utils.unregister_class(ARCHIPACK_OT_wall)
diff --git a/archipack/bitarray.py b/archipack/bitarray.py
deleted file mode 100644
index cf712610..00000000
--- a/archipack/bitarray.py
+++ /dev/null
@@ -1,97 +0,0 @@
-
-import array
-
-# -*- coding:utf-8 -*-
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# 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 2
-# 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, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-# ----------------------------------------------------------
-# Author: Stephen Leger (s-leger)
-#
-# ----------------------------------------------------------
-
-
-class BitArray():
-
- def __init__(self, bitSize, fill=0):
- self.size = bitSize
- intSize = bitSize >> 5
- if (bitSize & 31):
- intSize += 1
- if fill == 1:
- fill = 4294967295
- else:
- fill = 0
- self.bitArray = array.array('I')
- self.bitArray.extend((fill,) * intSize)
-
- def __str__(self):
- return str(self.list)
-
- def bit_location(self, bit_num):
- return bit_num >> 5, bit_num & 31
-
- def test(self, bit_num):
- record, offset = self.bit_location(bit_num)
- mask = 1 << offset
- return(self.bitArray[record] & mask)
-
- def set(self, bit_num):
- record, offset = self.bit_location(bit_num)
- mask = 1 << offset
- self.bitArray[record] |= mask
-
- def clear(self, bit_num):
- record, offset = self.bit_location(bit_num)
- mask = ~(1 << offset)
- self.bitArray[record] &= mask
-
- def toggle(self, bit_num):
- record, offset = self.bit_location(bit_num)
- mask = 1 << offset
- self.bitArray[record] ^= mask
-
- @property
- def len(self):
- return len(self.bitArray)
-
- @property
- def copy(self):
- copy = BitArray(self.size)
- for i in range(self.len):
- copy.bitArray[i] = self.bitArray[i]
- return copy
-
- @property
- def list(self):
- return [x for x in range(self.size) if self.test(x) > 0]
-
- def none(self):
- for i in range(self.len):
- self.bitArray[i] = 0
-
- def reverse(self):
- for i in range(self.len):
- self.bitArray[i] = 4294967295 ^ self.bitArray[i]
-
- def all(self):
- for i in range(self.len):
- self.bitArray[i] = 4294967295
diff --git a/archipack/pyqtree.py b/archipack/pyqtree.py
deleted file mode 100644
index 80b75727..00000000
--- a/archipack/pyqtree.py
+++ /dev/null
@@ -1,187 +0,0 @@
-# -*- coding:utf-8 -*-
-
-# <pep8 compliant>
-
-"""
-# Pyqtree
-
-Pyqtree is a pure Python spatial index for GIS or rendering usage.
-It stores and quickly retrieves items from a 2x2 rectangular grid area,
-and grows in depth and detail as more items are added.
-The actual quad tree implementation is adapted from
-[Matt Rasmussen's compbio library](https://github.com/mdrasmus/compbio/blob/master/rasmus/quadtree.py)
-and extended for geospatial use.
-
-
-## Platforms
-
-Python 2 and 3.
-
-
-## Dependencies
-
-Pyqtree is written in pure Python and has no dependencies.
-
-
-## Installing It
-
-Installing Pyqtree can be done by opening your terminal or commandline and typing:
-
- pip install pyqtree
-
-Alternatively, you can simply download the "pyqtree.py" file and place
-it anywhere Python can import it, such as the Python site-packages folder.
-
-
-## Example Usage
-
-Start your script by importing the quad tree.
-
- from pyqtree import Index
-
-Setup the spatial index, giving it a bounding box area to keep track of.
-The bounding box being in a four-tuple: (xmin, ymin, xmax, ymax).
-
- spindex = Index(bbox=(0, 0, 100, 100))
-
-Populate the index with items that you want to be retrieved at a later point,
-along with each item's geographic bbox.
-
- # this example assumes you have a list of items with bbox attribute
- for item in items:
- spindex.insert(item, item.bbox)
-
-Then when you have a region of interest and you wish to retrieve items from that region,
-just use the index's intersect method. This quickly gives you a list of the stored items
-whose bboxes intersects your region of interests.
-
- overlapbbox = (51, 51, 86, 86)
- matches = spindex.intersect(overlapbbox)
-
-There are other things that can be done as well, but that's it for the main usage!
-
-
-## More Information:
-
-- [Home Page](http://github.com/karimbahgat/Pyqtree)
-- [API Documentation](http://pythonhosted.org/Pyqtree)
-
-
-## License:
-
-This code is free to share, use, reuse, and modify according to the MIT license, see LICENSE.txt.
-
-
-## Credits:
-
-- Karim Bahgat (2015)
-- Joschua Gandert (2016)
-
-"""
-
-
-__version__ = "0.25.0"
-
-# PYTHON VERSION CHECK
-import sys
-
-
-PYTHON3 = int(sys.version[0]) == 3
-if PYTHON3:
- xrange = range
-
-
-class _QuadNode(object):
- def __init__(self, item, rect):
- self.item = item
- self.rect = rect
-
-
-class _QuadTree(object):
- """
- Internal backend version of the index.
- The index being used behind the scenes. Has all the same methods as the user
- index, but requires more technical arguments when initiating it than the
- user-friendly version.
- """
- def __init__(self, x, y, width, height, max_items, max_depth, _depth=0):
- self.nodes = []
- self.children = []
- self.center = (x, y)
- self.width, self.height = width, height
- self.max_items = max_items
- self.max_depth = max_depth
- self._depth = _depth
-
- def _insert(self, item, bbox):
- if len(self.children) == 0:
- node = _QuadNode(item, bbox)
- self.nodes.append(node)
- if len(self.nodes) > self.max_items and self._depth < self.max_depth:
- self._split()
- else:
- self._insert_into_children(item, bbox)
-
- def _intersect(self, rect, results=None):
- if results is None:
- results = set()
- # search children
- if self.children:
- if rect[0] <= self.center[0]:
- if rect[1] <= self.center[1]:
- self.children[0]._intersect(rect, results)
- if rect[3] >= self.center[1]:
- self.children[1]._intersect(rect, results)
- if rect[2] >= self.center[0]:
- if rect[1] <= self.center[1]:
- self.children[2]._intersect(rect, results)
- if rect[3] >= self.center[1]:
- self.children[3]._intersect(rect, results)
- # search node at this level
- for node in self.nodes:
- if (node.rect[2] >= rect[0] and node.rect[0] <= rect[2] and
- node.rect[3] >= rect[1] and node.rect[1] <= rect[3]):
- results.add(node.item)
- return results
-
- def _insert_into_children(self, item, rect):
- # if rect spans center then insert here
- if (rect[0] <= self.center[0] and rect[2] >= self.center[0] and
- rect[1] <= self.center[1] and rect[3] >= self.center[1]):
- node = _QuadNode(item, rect)
- self.nodes.append(node)
- else:
- # try to insert into children
- if rect[0] <= self.center[0]:
- if rect[1] <= self.center[1]:
- self.children[0]._insert(item, rect)
- if rect[3] >= self.center[1]:
- self.children[1]._insert(item, rect)
- if rect[2] > self.center[0]:
- if rect[1] <= self.center[1]:
- self.children[2]._insert(item, rect)
- if rect[3] >= self.center[1]:
- self.children[3]._insert(item, rect)
-
- def _split(self):
- quartwidth = self.width / 4.0
- quartheight = self.height / 4.0
- halfwidth = self.width / 2.0
- halfheight = self.height / 2.0
- x1 = self.center[0] - quartwidth
- x2 = self.center[0] + quartwidth
- y1 = self.center[1] - quartheight
- y2 = self.center[1] + quartheight
- new_depth = self._depth + 1
- self.children = [_QuadTree(x1, y1, halfwidth, halfheight,
- self.max_items, self.max_depth, new_depth),
- _QuadTree(x1, y2, halfwidth, halfheight,
- self.max_items, self.max_depth, new_depth),
- _QuadTree(x2, y1, halfwidth, halfheight,
- self.max_items, self.max_depth, new_depth),
- _QuadTree(x2, y2, halfwidth, halfheight,
- self.max_items, self.max_depth, new_depth)]
- nodes = self.nodes
- self.nodes = []
- for node in nodes:
- self._insert_into_children(node.item, node.rect)