diff options
author | Stephen Leger <stephen@3dservices.ch> | 2017-07-22 14:25:28 +0300 |
---|---|---|
committer | Stephen Leger <stephen@3dservices.ch> | 2017-07-22 14:26:04 +0300 |
commit | c1ab9b4b9c6c0226f8d7789b92efda9b0f33cfd1 (patch) | |
tree | 37d5a97c758fa9af48d1dfb5428edd72072d882a /archipack/archipack_gl.py | |
parent | 5638a8783502138500912061dde0e8ee476d7fca (diff) |
archipack: T52120 release to official
Diffstat (limited to 'archipack/archipack_gl.py')
-rw-r--r-- | archipack/archipack_gl.py | 1228 |
1 files changed, 1228 insertions, 0 deletions
diff --git a/archipack/archipack_gl.py b/archipack/archipack_gl.py new file mode 100644 index 00000000..fc1f8c03 --- /dev/null +++ b/archipack/archipack_gl.py @@ -0,0 +1,1228 @@ +# -*- 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 bgl +import blf +import bpy +from math import sin, cos, atan2, pi +from mathutils import Vector, Matrix +from bpy_extras import view3d_utils, object_utils + + +# ------------------------------------------------------------------ +# Define Gl Handle types +# ------------------------------------------------------------------ + + +class DefaultColorScheme: + """ + Font sizes and basic colour scheme + default to this when not found in addon prefs + Colors are FloatVectorProperty of size 4 and type COLOR_GAMMA + """ + feedback_size_main = 16 + feedback_size_title = 14 + feedback_size_shortcut = 11 + feedback_colour_main = (0.95, 0.95, 0.95, 1.0) + feedback_colour_key = (0.67, 0.67, 0.67, 1.0) + feedback_colour_shortcut = (0.51, 0.51, 0.51, 1.0) + feedback_shortcut_area = (0, 0.4, 0.6, 0.2) + feedback_title_area = (0, 0.4, 0.6, 0.5) + + +""" + # Addon prefs template + + feedback_size_main = IntProperty( + name="Main", + description="Main title font size (pixels)", + min=2, + default=16 + ) + feedback_size_title = IntProperty( + name="Title", + description="Tool name font size (pixels)", + min=2, + default=14 + ) + feedback_size_shortcut = IntProperty( + name="Shortcut", + description="Shortcuts font size (pixels)", + min=2, + default=11 + ) + feedback_shortcut_area = FloatVectorProperty( + name="Background Shortcut", + description="Shortcut area background color", + subtype='COLOR_GAMMA', + default=(0, 0.4, 0.6, 0.2), + size=4, + min=0, max=1 + ) + feedback_title_area = FloatVectorProperty( + name="Background Main", + description="Title area background color", + subtype='COLOR_GAMMA', + default=(0, 0.4, 0.6, 0.5), + size=4, + min=0, max=1 + ) + feedback_colour_main = FloatVectorProperty( + name="Font Main", + description="Title color", + subtype='COLOR_GAMMA', + default=(0.95, 0.95, 0.95, 1.0), + size=4, + min=0, max=1 + ) + feedback_colour_key = FloatVectorProperty( + name="Font Shortcut key", + description="KEY label color", + subtype='COLOR_GAMMA', + default=(0.67, 0.67, 0.67, 1.0), + size=4, + min=0, max=1 + ) + feedback_colour_shortcut = FloatVectorProperty( + name="Font Shortcut hint", + description="Shortcuts text color", + subtype='COLOR_GAMMA', + default=(0.51, 0.51, 0.51, 1.0), + size=4, + min=0, max=1 + ) + + def draw(self, context): + layout = self.layout + box = layout.box() + row = box.row() + split = row.split(percentage=0.5) + col = split.column() + col.label(text="Colors:") + row = col.row(align=True) + row.prop(self, "feedback_title_area") + row = col.row(align=True) + row.prop(self, "feedback_shortcut_area") + row = col.row(align=True) + row.prop(self, "feedback_colour_main") + row = col.row(align=True) + row.prop(self, "feedback_colour_key") + row = col.row(align=True) + row.prop(self, "feedback_colour_shortcut") + col = split.column() + col.label(text="Font size:") + col.prop(self, "feedback_size_main") + col.prop(self, "feedback_size_title") + col.prop(self, "feedback_size_shortcut") +""" + + +# @TODO: +# 1 Make a clear separation of 2d (pixel position) and 3d (world position) +# modes way to set gl coords +# 2 Unify methods to set points - currently set_pts, set_pos ... +# 3 Put all Gl part in a sub module as it may be used by other devs +# as gl toolkit abstraction for screen feedback +# 4 Implement cursor badges (np_station sample) +# 5 Define a clear color scheme so it is easy to customize +# 6 Allow different arguments for each classes like +# eg: for line p0 p1, p0 and vector (p1-p0) +# raising exceptions when incomplete +# 7 Use correct words, normal is not realy a normal +# but a perpendicular +# May be hard code more shapes ? +# Fine tuned text styles with shadows and surronding boxes / backgrounds +# Extending tests to hdr screens, ultra wide ones and so on +# Circular handle, handle styling (only border, filling ...) + +# Keep point 3 in mind while doing this, to keep it simple and easy to use +# Take inspiration from other's feed back systems, talk to other devs +# and find who actually work on bgl future for 2.8 release + + +class Gl(): + """ + handle 3d -> 2d gl drawing + d : dimensions + 3 to convert pos from 3d + 2 to keep pos as 2d absolute screen position + """ + def __init__(self, + d=3, + colour=(0.0, 0.0, 0.0, 1.0)): + # nth dimensions of input coords 3=word coords 2=pixel screen coords + self.d = d + self.pos_2d = Vector((0, 0)) + self.colour_inactive = colour + + @property + def colour(self): + return self.colour_inactive + + def position_2d_from_coord(self, context, coord, render=False): + """ coord given in local input coordsys + """ + if self.d == 2: + return coord + if render: + return self.get_render_location(context, coord) + region = context.region + rv3d = context.region_data + loc = view3d_utils.location_3d_to_region_2d(region, rv3d, coord, self.pos_2d) + return loc + + def get_render_location(self, context, coord): + scene = context.scene + co_2d = object_utils.world_to_camera_view(scene, scene.camera, coord) + # Get pixel coords + render_scale = scene.render.resolution_percentage / 100 + render_size = (int(scene.render.resolution_x * render_scale), + int(scene.render.resolution_y * render_scale)) + return [round(co_2d.x * render_size[0]), round(co_2d.y * render_size[1])] + + def _end(self): + bgl.glEnd() + bgl.glPopAttrib() + bgl.glLineWidth(1) + bgl.glDisable(bgl.GL_BLEND) + bgl.glColor4f(0.0, 0.0, 0.0, 1.0) + + +class GlText(Gl): + + def __init__(self, + d=3, + label="", + value=None, + precision=2, + unit_mode='AUTO', + unit_type='SIZE', + dimension=1, + angle=0, + font_size=12, + colour=(1, 1, 1, 1), + z_axis=Vector((0, 0, 1))): + """ + d: [2|3] coords type: 2 for coords in screen pixels, 3 for 3d world location + label : string label + value : float value (will add unit according following settings) + precision : integer rounding for values + dimension : [1 - 3] nth dimension of unit (single, square, cubic) + unit_mode : ['AUTO','METER','CENTIMETER','MILIMETER','FEET','INCH','RADIANS','DEGREE'] + unit type to use to postfix values + auto use scene units setup + unit_type : ['SIZE','ANGLE'] + unit type to add to value + angle : angle to rotate text + + """ + self.z_axis = z_axis + # text, add as prefix to value + self.label = label + # value with unit related + self.value = value + self.precision = precision + self.dimension = dimension + self.unit_type = unit_type + self.unit_mode = unit_mode + + self.font_size = font_size + self.angle = angle + Gl.__init__(self, d) + self.colour_inactive = colour + # store text with units + self._text = "" + + def text_size(self, context): + """ + overall on-screen size in pixels + """ + dpi, font_id = context.user_preferences.system.dpi, 0 + if self.angle != 0: + blf.enable(font_id, blf.ROTATION) + blf.rotation(font_id, self.angle) + blf.aspect(font_id, 1.0) + blf.size(font_id, self.font_size, dpi) + x, y = blf.dimensions(font_id, self.text) + if self.angle != 0: + blf.disable(font_id, blf.ROTATION) + return Vector((x, y)) + + @property + def pts(self): + return [self.pos_3d] + + @property + def text(self): + s = self.label + self._text + return s.strip() + + def add_units(self, context): + if self.value is None: + return "" + if self.unit_type == 'ANGLE': + scale = 1 + else: + scale = context.scene.unit_settings.scale_length + + val = self.value * scale + mode = self.unit_mode + if mode == 'AUTO': + if self.unit_type == 'ANGLE': + mode = context.scene.unit_settings.system_rotation + else: + if context.scene.unit_settings.system == "IMPERIAL": + if round(val * (3.2808399 ** self.dimension), 2) >= 1.0: + mode = 'FEET' + else: + mode = 'INCH' + elif context.scene.unit_settings.system == "METRIC": + if round(val, 2) >= 1.0: + mode = 'METER' + else: + if round(val, 2) >= 0.01: + mode = 'CENTIMETER' + else: + mode = 'MILIMETER' + # convert values + if mode == 'METER': + unit = "m" + elif mode == 'CENTIMETER': + val *= (100 ** self.dimension) + unit = "cm" + elif mode == 'MILIMETER': + val *= (1000 ** self.dimension) + unit = 'mm' + elif mode == 'INCH': + val *= (39.3700787 ** self.dimension) + unit = "in" + elif mode == 'FEET': + val *= (3.2808399 ** self.dimension) + unit = "ft" + elif mode == 'RADIANS': + unit = "" + elif mode == 'DEGREES': + val = self.value / pi * 180 + unit = "°" + else: + unit = "" + if self.dimension == 2: + unit += "\u00b2" # Superscript two + elif self.dimension == 3: + unit += "\u00b3" # Superscript three + + fmt = "%1." + str(self.precision) + "f " + unit + return fmt % val + + def set_pos(self, context, value, pos_3d, direction, angle=0, normal=Vector((0, 0, 1))): + self.up_axis = direction.normalized() + self.c_axis = self.up_axis.cross(normal) + self.pos_3d = pos_3d + self.value = value + self.angle = angle + self._text = self.add_units(context) + + def draw(self, context, render=False): + self.render = render + x, y = self.position_2d_from_coord(context, self.pts[0], render) + # dirty fast assignment + dpi, font_id = context.user_preferences.system.dpi, 0 + bgl.glColor4f(*self.colour) + if self.angle != 0: + blf.enable(font_id, blf.ROTATION) + blf.rotation(font_id, self.angle) + blf.size(font_id, self.font_size, dpi) + blf.position(font_id, x, y, 0) + blf.draw(font_id, self.text) + if self.angle != 0: + blf.disable(font_id, blf.ROTATION) + + +class GlBaseLine(Gl): + + def __init__(self, + d=3, + width=1, + style=bgl.GL_LINE, + closed=False): + Gl.__init__(self, d) + # default line width + self.width = width + # default line style + self.style = style + # allow closed lines + self.closed = False + + def draw(self, context, render=False): + """ + render flag when rendering + """ + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + if self.style == bgl.GL_LINE_STIPPLE: + bgl.glLineStipple(1, 0x9999) + bgl.glEnable(self.style) + bgl.glEnable(bgl.GL_BLEND) + if render: + # enable anti-alias on lines + bgl.glEnable(bgl.GL_LINE_SMOOTH) + bgl.glColor4f(*self.colour) + bgl.glLineWidth(self.width) + if self.closed: + bgl.glBegin(bgl.GL_LINE_LOOP) + else: + bgl.glBegin(bgl.GL_LINE_STRIP) + + for pt in self.pts: + x, y = self.position_2d_from_coord(context, pt, render) + bgl.glVertex2f(x, y) + self._end() + + +class GlLine(GlBaseLine): + """ + 2d/3d Line + """ + def __init__(self, d=3, p=None, v=None, p0=None, p1=None, z_axis=None): + """ + d=3 use 3d coords, d=2 use 2d pixels coords + Init by either + p: Vector or tuple origin + v: Vector or tuple size and direction + or + p0: Vector or tuple 1 point location + p1: Vector or tuple 2 point location + Will convert any into Vector 3d + both optionnals + """ + if p is not None and v is not None: + self.p = Vector(p) + self.v = Vector(v) + elif p0 is not None and p1 is not None: + self.p = Vector(p0) + self.v = Vector(p1) - self.p + else: + self.p = Vector((0, 0, 0)) + self.v = Vector((0, 0, 0)) + if z_axis is not None: + self.z_axis = z_axis + else: + self.z_axis = Vector((0, 0, 1)) + GlBaseLine.__init__(self, d) + + @property + def p0(self): + return self.p + + @property + def p1(self): + return self.p + self.v + + @p0.setter + def p0(self, p0): + """ + Note: setting p0 + move p0 only + """ + p1 = self.p1 + self.p = Vector(p0) + self.v = p1 - p0 + + @p1.setter + def p1(self, p1): + """ + Note: setting p1 + move p1 only + """ + self.v = Vector(p1) - self.p + + @property + def length(self): + return self.v.length + + @property + def angle(self): + return atan2(self.v.y, self.v.x) + + @property + def cross(self): + """ + Vector perpendicular on plane defined by z_axis + lie on the right side + p1 + |--x + p0 + """ + return self.v.cross(self.z_axis) + + def normal(self, t=0): + """ + Line perpendicular on plane defined by z_axis + lie on the right side + p1 + |--x + p0 + """ + n = GlLine() + n.p = self.lerp(t) + n.v = self.cross + return n + + def sized_normal(self, t, size): + """ + GlLine perpendicular on plane defined by z_axis and of given size + positionned at t in current line + lie on the right side + p1 + |--x + p0 + """ + n = GlLine() + n.p = self.lerp(t) + n.v = size * self.cross.normalized() + return n + + def lerp(self, t): + """ + Interpolate along segment + t parameter [0, 1] where 0 is start of arc and 1 is end + """ + return self.p + self.v * t + + def offset(self, offset): + """ + offset > 0 on the right part + """ + self.p += offset * self.cross.normalized() + + def point_sur_segment(self, pt): + """ point_sur_segment (2d) + point: Vector 3d + t: param t de l'intersection sur le segment courant + d: distance laterale perpendiculaire positif a droite + """ + dp = (pt - self.p).to_2d() + v2d = self.v.to_2d() + dl = v2d.length + d = (self.v.x * dp.y - self.v.y * dp.x) / dl + t = (v2d * dp) / (dl * dl) + return t > 0 and t < 1, d, t + + @property + def pts(self): + return [self.p0, self.p1] + + +class GlCircle(GlBaseLine): + + def __init__(self, + d=3, + radius=0, + center=Vector((0, 0, 0)), + z_axis=Vector((0, 0, 1))): + + self.r = radius + self.c = center + z = z_axis + + if z.z < 1: + x = z.cross(Vector((0, 0, 1))) + y = x.cross(z) + else: + x = Vector((1, 0, 0)) + y = Vector((0, 1, 0)) + + self.rM = Matrix([ + Vector((x.x, y.x, z.x)), + Vector((x.y, y.y, z.y)), + Vector((x.z, y.z, z.z)) + ]) + self.z_axis = z + self.a0 = 0 + self.da = 2 * pi + GlBaseLine.__init__(self, d) + + def lerp(self, t): + """ + Linear interpolation + """ + a = self.a0 + t * self.da + return self.c + self.rM * Vector((self.r * cos(a), self.r * sin(a), 0)) + + @property + def pts(self): + n_pts = max(1, int(round(abs(self.da) / pi * 30, 0))) + t_step = 1 / n_pts + return [self.lerp(i * t_step) for i in range(n_pts + 1)] + + +class GlArc(GlCircle): + + def __init__(self, + d=3, + radius=0, + center=Vector((0, 0, 0)), + z_axis=Vector((0, 0, 1)), + a0=0, + da=0): + """ + a0 and da arguments are in radians + a0 = 0 on the x+ axis side + a0 = pi on the x- axis side + da > 0 CCW contrary-clockwise + da < 0 CW clockwise + """ + GlCircle.__init__(self, d, radius, center, z_axis) + self.da = da + self.a0 = a0 + + @property + def length(self): + return self.r * abs(self.da) + + def normal(self, t=0): + """ + perpendicular line always on the right side + """ + n = GlLine(d=self.d, z_axis=self.z_axis) + n.p = self.lerp(t) + if self.da < 0: + n.v = self.c - n.p + else: + n.v = n.p - self.c + return n + + def sized_normal(self, t, size): + n = GlLine(d=self.d, z_axis=self.z_axis) + n.p = self.lerp(t) + if self.da < 0: + n.v = size * (self.c - n.p).normalized() + else: + n.v = size * (n.p - self.c).normalized() + return n + + def tangeant(self, t, length): + a = self.a0 + t * self.da + ca = cos(a) + sa = sin(a) + n = GlLine(d=self.d, z_axis=self.z_axis) + n.p = self.c + self.rM * Vector((self.r * ca, self.r * sa, 0)) + n.v = self.rM * Vector((length * sa, -length * ca, 0)) + if self.da > 0: + n.v = -n.v + return n + + def offset(self, offset): + """ + offset > 0 on the right part + """ + if self.da > 0: + radius = self.r + offset + else: + radius = self.r - offset + return GlArc(d=self.d, + radius=radius, + center=self.c, + a0=self.a0, + da=self.da, + z_axis=self.z_axis) + + +class GlPolygon(Gl): + + def __init__(self, + colour=(0.0, 0.0, 0.0, 1.0), + d=3): + + self.pts_3d = [] + Gl.__init__(self, d, colour) + + def set_pos(self, pts_3d): + self.pts_3d = pts_3d + + @property + def pts(self): + return self.pts_3d + + def draw(self, context, render=False): + """ + render flag when rendering + """ + self.render = render + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + bgl.glEnable(bgl.GL_BLEND) + if render: + # enable anti-alias on polygons + bgl.glEnable(bgl.GL_POLYGON_SMOOTH) + bgl.glColor4f(*self.colour) + bgl.glBegin(bgl.GL_POLYGON) + + for pt in self.pts: + x, y = self.position_2d_from_coord(context, pt, render) + bgl.glVertex2f(x, y) + self._end() + + +class GlRect(GlPolygon): + def __init__(self, + colour=(0.0, 0.0, 0.0, 1.0), + d=2): + GlPolygon.__init__(self, colour, d) + + def draw(self, context, render=False): + self.render = render + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + bgl.glEnable(bgl.GL_BLEND) + if render: + # enable anti-alias on polygons + bgl.glEnable(bgl.GL_POLYGON_SMOOTH) + bgl.glColor4f(*self.colour) + p0 = self.pts[0] + p1 = self.pts[1] + bgl.glRectf(p0.x, p0.y, p1.x, p1.y) + self._end() + + +class GlImage(Gl): + def __init__(self, + d=2, + image=None): + self.image = image + self.colour_inactive = (1, 1, 1, 1) + Gl.__init__(self, d) + self.pts_2d = [Vector((0, 0)), Vector((10, 10))] + + def set_pos(self, pts): + self.pts_2d = pts + + @property + def pts(self): + return self.pts_2d + + def draw(self, context, render=False): + if self.image is None: + return + bgl.glPushAttrib(bgl.GL_ENABLE_BIT) + p0 = self.pts[0] + p1 = self.pts[1] + bgl.glEnable(bgl.GL_BLEND) + bgl.glColor4f(*self.colour) + bgl.glRectf(p0.x, p0.y, p1.x, p1.y) + self.image.gl_load() + bgl.glEnable(bgl.GL_BLEND) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.image.bindcode[0]) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST) + bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST) + bgl.glEnable(bgl.GL_TEXTURE_2D) + bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA) + # bgl.glColor4f(1, 1, 1, 1) + bgl.glBegin(bgl.GL_QUADS) + bgl.glTexCoord2d(0, 0) + bgl.glVertex2d(p0.x, p0.y) + bgl.glTexCoord2d(0, 1) + bgl.glVertex2d(p0.x, p1.y) + bgl.glTexCoord2d(1, 1) + bgl.glVertex2d(p1.x, p1.y) + bgl.glTexCoord2d(1, 0) + bgl.glVertex2d(p1.x, p0.y) + bgl.glEnd() + self.image.gl_free() + bgl.glDisable(bgl.GL_TEXTURE_2D) + + +class GlPolyline(GlBaseLine): + def __init__(self, colour, d=3): + self.pts_3d = [] + GlBaseLine.__init__(self, d) + self.colour_inactive = colour + + def set_pos(self, pts_3d): + self.pts_3d = pts_3d + # self.pts_3d.append(pts_3d[0]) + + @property + def pts(self): + return self.pts_3d + + +class GlHandle(GlPolygon): + + def __init__(self, sensor_size, size, draggable=False, selectable=False, d=3): + """ + sensor_size : 2d size in pixels of sensor area + size : 3d size of handle + """ + GlPolygon.__init__(self, d=d) + self.colour_active = (1.0, 0.0, 0.0, 1.0) + self.colour_hover = (1.0, 1.0, 0.0, 1.0) + self.colour_normal = (1.0, 1.0, 1.0, 1.0) + self.colour_selected = (0.0, 0.0, 0.7, 1.0) + self.size = size + self.sensor_width = sensor_size + self.sensor_height = sensor_size + self.pos_3d = Vector((0, 0, 0)) + self.up_axis = Vector((0, 0, 0)) + self.c_axis = Vector((0, 0, 0)) + self.hover = False + self.active = False + self.draggable = draggable + self.selectable = selectable + self.selected = False + + def set_pos(self, context, pos_3d, direction, normal=Vector((0, 0, 1))): + self.up_axis = direction.normalized() + self.c_axis = self.up_axis.cross(normal) + self.pos_3d = pos_3d + self.pos_2d = self.position_2d_from_coord(context, self.sensor_center) + + def check_hover(self, pos_2d): + if self.draggable: + dp = pos_2d - self.pos_2d + self.hover = abs(dp.x) < self.sensor_width and abs(dp.y) < self.sensor_height + + @property + def sensor_center(self): + pts = self.pts + n = len(pts) + x, y, z = 0, 0, 0 + for pt in pts: + x += pt.x + y += pt.y + z += pt.z + return Vector((x / n, y / n, z / n)) + + @property + def pts(self): + raise NotImplementedError + + @property + def colour(self): + if self.render: + return self.colour_inactive + elif self.draggable: + if self.active: + return self.colour_active + elif self.hover: + return self.colour_hover + elif self.selected: + return self.colour_selected + return self.colour_normal + else: + return self.colour_inactive + + +class SquareHandle(GlHandle): + + def __init__(self, sensor_size, size, draggable=False, selectable=False): + GlHandle.__init__(self, sensor_size, size, draggable, selectable) + + @property + def pts(self): + n = self.up_axis + c = self.c_axis + if self.selected or self.hover or self.active: + scale = 1 + else: + scale = 0.5 + x = n * self.size * scale + y = c * self.size * scale + return [self.pos_3d - x - y, self.pos_3d + x - y, self.pos_3d + x + y, self.pos_3d - x + y] + + +class TriHandle(GlHandle): + + def __init__(self, sensor_size, size, draggable=False, selectable=False): + GlHandle.__init__(self, sensor_size, size, draggable, selectable) + + @property + def pts(self): + n = self.up_axis + c = self.c_axis + # does move sensitive area so disable for tri handle + # may implement sensor_center property to fix this + # if self.selected or self.hover or self.active: + scale = 1 + # else: + # scale = 0.5 + x = n * self.size * 4 * scale + y = c * self.size * scale + return [self.pos_3d - x + y, self.pos_3d - x - y, self.pos_3d] + + +class EditableText(GlText, GlHandle): + def __init__(self, sensor_size, size, draggable=False, selectable=False): + GlHandle.__init__(self, sensor_size, size, draggable, selectable) + GlText.__init__(self, colour=(0, 0, 0, 1)) + + def set_pos(self, context, value, pos_3d, direction, normal=Vector((0, 0, 1))): + self.up_axis = direction.normalized() + self.c_axis = self.up_axis.cross(normal) + self.pos_3d = pos_3d + self.value = value + self._text = self.add_units(context) + x, y = self.text_size(context) + self.pos_2d = self.position_2d_from_coord(context, pos_3d) + self.pos_2d.x += 0.5 * x + self.sensor_width, self.sensor_height = 0.5 * x, y + + @property + def sensor_center(self): + return self.pos_3d + + +class ThumbHandle(GlHandle): + + def __init__(self, size_2d, label, image=None, draggable=False, selectable=False, d=2): + GlHandle.__init__(self, size_2d, size_2d, draggable, selectable, d) + self.image = GlImage(image=image) + self.label = GlText(d=2, label=label.replace("_", " ").capitalize()) + self.frame = GlPolyline((1, 1, 1, 1), d=2) + self.frame.closed = True + self.size_2d = size_2d + self.sensor_width = 0.5 * size_2d.x + self.sensor_height = 0.5 * size_2d.y + self.colour_normal = (0.715, 0.905, 1, 0.9) + self.colour_hover = (1, 1, 1, 1) + + def set_pos(self, context, pos_2d): + """ + pos 2d is center !! + """ + self.pos_2d = pos_2d + ts = self.label.text_size(context) + self.label.pos_3d = pos_2d + Vector((-0.5 * ts.x, ts.y - 0.5 * self.size_2d.y)) + p0, p1 = self.pts + self.image.set_pos(self.pts) + self.frame.set_pos([p0, Vector((p1.x, p0.y)), p1, Vector((p0.x, p1.y))]) + + @property + def pts(self): + s = 0.5 * self.size_2d + return [self.pos_2d - s, self.pos_2d + s] + + @property + def sensor_center(self): + return self.pos_2d + 0.5 * self.size_2d + + def draw(self, context, render=False): + self.render = render + self.image.colour_inactive = self.colour + GlHandle.draw(self, context, render=False) + self.image.draw(context, render=False) + self.label.draw(context, render=False) + self.frame.draw(context, render=False) + + +class Screen(): + def __init__(self, margin): + self.margin = margin + + def size(self, context): + + system = context.user_preferences.system + w = context.region.width + h = context.region.height + y_min = self.margin + y_max = h - self.margin + x_min = self.margin + x_max = w - self.margin + if (system.use_region_overlap and + system.window_draw_method in {'TRIPLE_BUFFER', 'AUTOMATIC'}): + area = context.area + + for r in area.regions: + if r.type == 'TOOLS': + x_min += r.width + elif r.type == 'UI': + x_max -= r.width + return x_min, x_max, y_min, y_max + + +class FeedbackPanel(): + """ + Feed-back panel + inspired by np_station + """ + def __init__(self, title='Archipack'): + + prefs = self.get_prefs(bpy.context) + + self.main_title = GlText(d=2, + label=title + " : ", + font_size=prefs.feedback_size_main, + colour=prefs.feedback_colour_main + ) + self.title = GlText(d=2, + font_size=prefs.feedback_size_title, + colour=prefs.feedback_colour_main + ) + self.spacing = Vector(( + 0.5 * prefs.feedback_size_shortcut, + 0.5 * prefs.feedback_size_shortcut)) + self.margin = 50 + self.explanation = GlText(d=2, + font_size=prefs.feedback_size_shortcut, + colour=prefs.feedback_colour_main + ) + self.shortcut_area = GlPolygon(colour=prefs.feedback_shortcut_area, d=2) + self.title_area = GlPolygon(colour=prefs.feedback_title_area, d=2) + self.shortcuts = [] + self.on = False + self.show_title = True + self.show_main_title = True + # read only, when enabled, after draw() the top left coord of info box + self.top = Vector((0, 0)) + self.screen = Screen(self.margin) + + def disable(self): + self.on = False + + def enable(self): + self.on = True + + def get_prefs(self, context): + global __name__ + try: + # retrieve addon name from imports + addon_name = __name__.split('.')[0] + prefs = context.user_preferences.addons[addon_name].preferences + except: + prefs = DefaultColorScheme + pass + return prefs + + def instructions(self, context, title, explanation, shortcuts): + """ + position from bottom to top + """ + prefs = self.get_prefs(context) + + self.explanation.label = explanation + self.title.label = title + + self.shortcuts = [] + + for key, label in shortcuts: + key = GlText(d=2, label=key, + font_size=prefs.feedback_size_shortcut, + colour=prefs.feedback_colour_key) + label = GlText(d=2, label=' : ' + label, + font_size=prefs.feedback_size_shortcut, + colour=prefs.feedback_colour_shortcut) + ks = key.text_size(context) + ls = label.text_size(context) + self.shortcuts.append([key, ks, label, ls]) + + def draw(self, context, render=False): + if self.on: + """ + draw from bottom to top + so we are able to always fit needs + """ + x_min, x_max, y_min, y_max = self.screen.size(context) + available_w = x_max - x_min - 2 * self.spacing.x + main_title_size = self.main_title.text_size(context) + Vector((5, 0)) + + # h = context.region.height + # 0,0 = bottom left + pos = Vector((x_min + self.spacing.x, y_min)) + shortcuts = [] + + # sort by lines + lines = [] + line = [] + space = 0 + sum_txt = 0 + + for key, ks, label, ls in self.shortcuts: + space += ks.x + ls.x + self.spacing.x + if pos.x + space > available_w: + txt_spacing = (available_w - sum_txt) / (max(1, len(line) - 1)) + sum_txt = 0 + space = ks.x + ls.x + self.spacing.x + lines.append((txt_spacing, line)) + line = [] + sum_txt += ks.x + ls.x + line.append([key, ks, label, ls]) + + if len(line) > 0: + txt_spacing = (available_w - sum_txt) / (max(1, len(line) - 1)) + lines.append((txt_spacing, line)) + + # reverse lines to draw from bottom to top + lines = list(reversed(lines)) + for spacing, line in lines: + pos.y += self.spacing.y + pos.x = x_min + self.spacing.x + for key, ks, label, ls in line: + key.pos_3d = pos.copy() + pos.x += ks.x + label.pos_3d = pos.copy() + pos.x += ls.x + spacing + shortcuts.extend([key, label]) + pos.y += ks.y + self.spacing.y + + n_shortcuts = len(shortcuts) + # shortcut area + self.shortcut_area.pts_3d = [ + (x_min, self.margin), + (x_max, self.margin), + (x_max, pos.y), + (x_min, pos.y) + ] + + # small space between shortcut area and main title bar + if n_shortcuts > 0: + pos.y += 0.5 * self.spacing.y + + self.title_area.pts_3d = [ + (x_min, pos.y), + (x_max, pos.y), + (x_max, pos.y + main_title_size.y + 2 * self.spacing.y), + (x_min, pos.y + main_title_size.y + 2 * self.spacing.y) + ] + pos.y += self.spacing.y + + title_size = self.title.text_size(context) + # check for space available: + # if explanation + title + main_title are too big + # 1 remove main title + # 2 remove title + explanation_size = self.explanation.text_size(context) + + self.show_title = True + self.show_main_title = True + + if title_size.x + explanation_size.x > available_w: + # keep only explanation + self.show_title = False + self.show_main_title = False + elif main_title_size.x + title_size.x + explanation_size.x > available_w: + # keep title + explanation + self.show_main_title = False + self.title.pos_3d = (x_min + self.spacing.x, pos.y) + else: + self.title.pos_3d = (x_min + self.spacing.x + main_title_size.x, pos.y) + + self.explanation.pos_3d = (x_max - self.spacing.x - explanation_size.x, pos.y) + self.main_title.pos_3d = (x_min + self.spacing.x, pos.y) + + self.shortcut_area.draw(context) + self.title_area.draw(context) + if self.show_title: + self.title.draw(context) + if self.show_main_title: + self.main_title.draw(context) + self.explanation.draw(context) + for s in shortcuts: + s.draw(context) + + self.top = Vector((x_min, pos.y + main_title_size.y + self.spacing.y)) + + +class GlCursorFence(): + """ + Cursor crossing Fence + """ + def __init__(self, width=1, colour=(1.0, 1.0, 1.0, 0.5), style=2852): + self.line_x = GlLine(d=2) + self.line_x.style = style + self.line_x.width = width + self.line_x.colour_inactive = colour + self.line_y = GlLine(d=2) + self.line_y.style = style + self.line_y.width = width + self.line_y.colour_inactive = colour + self.on = True + + def set_location(self, context, location): + w = context.region.width + h = context.region.height + x, y = location + self.line_x.p = Vector((0, y)) + self.line_x.v = Vector((w, 0)) + self.line_y.p = Vector((x, 0)) + self.line_y.v = Vector((0, h)) + + def enable(self): + self.on = True + + def disable(self): + self.on = False + + def draw(self, context, render=False): + if self.on: + self.line_x.draw(context) + self.line_y.draw(context) + + +class GlCursorArea(): + def __init__(self, + width=1, + bordercolour=(1.0, 1.0, 1.0, 0.5), + areacolour=(0.5, 0.5, 0.5, 0.08), + style=2852): + + self.border = GlPolyline(bordercolour, d=2) + self.border.style = style + self.border.width = width + self.border.closed = True + self.area = GlPolygon(areacolour, d=2) + self.min = Vector((0, 0)) + self.max = Vector((0, 0)) + self.on = False + + def in_area(self, pt): + return (self.min.x <= pt.x and self.max.x >= pt.x and + self.min.y <= pt.y and self.max.y >= pt.y) + + def set_location(self, context, p0, p1): + x0, y0 = p0 + x1, y1 = p1 + if x0 > x1: + x1, x0 = x0, x1 + if y0 > y1: + y1, y0 = y0, y1 + self.min = Vector((x0, y0)) + self.max = Vector((x1, y1)) + pos = [ + Vector((x0, y0)), + Vector((x0, y1)), + Vector((x1, y1)), + Vector((x1, y0))] + self.area.set_pos(pos) + self.border.set_pos(pos) + + def enable(self): + self.on = True + + def disable(self): + self.on = False + + def draw(self, context, render=False): + if self.on: + self.area.draw(context) + self.border.draw(context) |