diff options
Diffstat (limited to 'archipack')
-rw-r--r-- | archipack/__init__.py | 156 | ||||
-rw-r--r-- | archipack/archipack_polylib.py | 2274 | ||||
-rw-r--r-- | archipack/archipack_wall.py | 137 | ||||
-rw-r--r-- | archipack/bitarray.py | 97 | ||||
-rw-r--r-- | archipack/pyqtree.py | 187 |
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) |