This repository has been archived on 2023-10-03. You can view files and clone it, but cannot push or open issues or pull requests.

210 lines
6.4 KiB
Python
Raw Normal View History

import logging
import os.path
import bpy
import bgl
import pillarsdk
from . import nodes
if bpy.app.version < (2, 80):
from . import draw_27 as draw
else:
from . import draw
library_icons_path = os.path.join(os.path.dirname(__file__), "icons")
ICON_WIDTH = 128
ICON_HEIGHT = 128
class MenuItem:
"""GUI menu item for the 3D View GUI."""
icon_margin_x = 4
icon_margin_y = 4
text_margin_x = 6
text_size = 12
text_size_small = 10
DEFAULT_ICONS = {
"FOLDER": os.path.join(library_icons_path, "folder.png"),
"SPINNER": os.path.join(library_icons_path, "spinner.png"),
"ERROR": os.path.join(library_icons_path, "error.png"),
}
FOLDER_NODE_TYPES = {
"group_texture",
"group_hdri",
nodes.UpNode.NODE_TYPE,
nodes.ProjectNode.NODE_TYPE,
}
SUPPORTED_NODE_TYPES = {"texture", "hdri"}.union(FOLDER_NODE_TYPES)
def __init__(self, node, file_desc, thumb_path: str, label_text):
self.log = logging.getLogger("%s.MenuItem" % __name__)
if node["node_type"] not in self.SUPPORTED_NODE_TYPES:
self.log.info("Invalid node type in node: %s", node)
raise TypeError(
"Node of type %r not supported; supported are %r."
% (node["node_type"], self.SUPPORTED_NODE_TYPES)
)
assert isinstance(node, pillarsdk.Node), "wrong type for node: %r" % type(node)
assert isinstance(node["_id"], str), 'wrong type for node["_id"]: %r' % type(
node["_id"]
)
self.node = node # pillarsdk.Node, contains 'node_type' key to indicate type
self.file_desc = file_desc # pillarsdk.File object, or None if a 'folder' node.
self.label_text = label_text
self.small_text = self._small_text_from_node()
self._thumb_path = ""
self.icon = None
self._is_folder = node["node_type"] in self.FOLDER_NODE_TYPES
self._is_spinning = False
# Determine sorting order.
# by default, sort all the way at the end and folders first.
self._order = 0 if self._is_folder else 10000
if node and node.properties and node.properties.order is not None:
self._order = node.properties.order
self.thumb_path = thumb_path
# Updated when drawing the image
self.x = 0
self.y = 0
self.width = 0
self.height = 0
def _small_text_from_node(self) -> str:
"""Return the components of the texture (i.e. which map types are available)."""
if not self.node:
return ""
try:
node_files = self.node.properties.files
except AttributeError:
# Happens for nodes that don't have .properties.files.
return ""
if not node_files:
return ""
map_types = {f.map_type for f in node_files if f.map_type}
map_types.discard("color") # all textures have colour
if not map_types:
return ""
return ", ".join(sorted(map_types))
def sort_key(self):
"""Key for sorting lists of MenuItems."""
return self._order, self.label_text
@property
def thumb_path(self) -> str:
return self._thumb_path
@thumb_path.setter
def thumb_path(self, new_thumb_path: str):
self._is_spinning = new_thumb_path == "SPINNER"
self._thumb_path = self.DEFAULT_ICONS.get(new_thumb_path, new_thumb_path)
if self._thumb_path:
self.icon = bpy.data.images.load(filepath=self._thumb_path)
else:
self.icon = None
@property
def node_uuid(self) -> str:
return self.node["_id"]
def represents(self, node) -> bool:
"""Returns True iff this MenuItem represents the given node."""
node_uuid = node["_id"]
return self.node_uuid == node_uuid
def update(self, node, file_desc, thumb_path: str, label_text=None):
# We can get updated information about our Node, but a MenuItem should
# always represent one node, and it shouldn't be shared between nodes.
if self.node_uuid != node["_id"]:
raise ValueError(
"Don't change the node ID this MenuItem reflects, "
"just create a new one."
)
self.node = node
self.file_desc = file_desc # pillarsdk.File object, or None if a 'folder' node.
self.thumb_path = thumb_path
if label_text is not None:
self.label_text = label_text
if thumb_path == "ERROR":
self.small_text = "This open is broken"
else:
self.small_text = self._small_text_from_node()
@property
def is_folder(self) -> bool:
return self._is_folder
@property
def is_spinning(self) -> bool:
return self._is_spinning
def update_placement(self, x, y, width, height):
"""Use OpenGL to draw this one menu item."""
self.x = x
self.y = y
self.width = width
self.height = height
def draw(self, highlighted: bool):
bgl.glEnable(bgl.GL_BLEND)
if highlighted:
color = (0.555, 0.555, 0.555, 0.8)
else:
color = (0.447, 0.447, 0.447, 0.8)
draw.aabox((self.x, self.y), (self.x + self.width, self.y + self.height), color)
texture = self.icon
if texture:
err = draw.load_texture(texture)
assert not err, "OpenGL error: %i" % err
# ------ TEXTURE ---------#
if texture:
draw.bind_texture(texture)
bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA)
draw.aabox_with_texture(
(self.x + self.icon_margin_x, self.y),
(self.x + self.icon_margin_x + ICON_WIDTH, self.y + ICON_HEIGHT),
)
bgl.glDisable(bgl.GL_BLEND)
if texture:
texture.gl_free()
# draw some text
text_x = self.x + self.icon_margin_x + ICON_WIDTH + self.text_margin_x
text_y = self.y + ICON_HEIGHT * 0.5 - 0.25 * self.text_size
draw.text((text_x, text_y), self.label_text, fsize=self.text_size)
draw.text(
(text_x, self.y + 0.5 * self.text_size_small),
self.small_text,
fsize=self.text_size_small,
rgba=(1.0, 1.0, 1.0, 0.5),
)
def hits(self, mouse_x: int, mouse_y: int) -> bool:
return (
self.x < mouse_x < self.x + self.width
and self.y < mouse_y < self.y + self.height
)