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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Leger <stephen@3dservices.ch>2017-07-22 14:25:28 +0300
committerStephen Leger <stephen@3dservices.ch>2017-07-22 14:26:04 +0300
commitc1ab9b4b9c6c0226f8d7789b92efda9b0f33cfd1 (patch)
tree37d5a97c758fa9af48d1dfb5428edd72072d882a /archipack/archipack_gl.py
parent5638a8783502138500912061dde0e8ee476d7fca (diff)
archipack: T52120 release to official
Diffstat (limited to 'archipack/archipack_gl.py')
-rw-r--r--archipack/archipack_gl.py1228
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)