Source code for opengl.canvas_settings

#
##
##  SPDX-FileCopyrightText: © 2007-2024 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.5  (Thu Feb  8 19:11:13 CET 2024)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  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 3 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, see http://www.gnu.org/licenses/.
##
"""This modules defines the CanvasSettings class

"""
import sys
from functools import partial
from dataclasses import dataclass, asdict

import pyformex as pf
from pyformex import utils
from pyformex import arraytools as at
from pyformex import colors
from pyformex.mydict import Dict
from .sanitize import saneColor


############### OpenGL Lighting #################################

class Material():
    def __init__(self, name, ambient=0.2, diffuse=0.2, specular=0.9,
                 emission=0.1, shininess=2.0):
        self.name = str(name)
        self.ambient = float(ambient)
        self.diffuse = float(diffuse)
        self.specular = float(specular)
        self.emission = float(emission)
        self.shininess = float(shininess)


    def setValues(self, **kargs):
        for k in kargs:
            if hasattr(self, k):
                setattr(self, k, float(kargs[k]))


    def dict(self):
        """Return the material light parameters as a dict"""
        return dict([(k, getattr(self, k)) for k in
                     ('ambient', 'diffuse', 'specular', 'emission', 'shininess')])


    def __str__(self):
        return f"""MATERIAL: {self.name}
    ambient: {self.ambient}
    diffuse: {self.diffuse}
    specular: {self.specular}
    emission: {self.emission}
    shininess: {self.shininess}
"""


def getMaterials():
    mats = pf.refcfg['material']
    mats.update(pf.prefcfg['material'])
    mats.update(pf.cfg['material'])
    return mats


def createMaterials():
    mats = getMaterials()
    matdb = {}
    for m in mats:
        matdb[m] = Material(m, **mats[m])
    return matdb


[docs]class Light(): """A class representing an OpenGL light. The light can emit 3 types of light: ambient, diffuse and specular, which can have different color and are all off by default. """ def __init__(self, ambient=0.0, diffuse=0.0, specular=0.0, position=[0., 0., 1.], enabled=True): self.setValues(ambient, diffuse, specular, position) self.enable(enabled) def setValues(self, ambient=None, diffuse=None, specular=None, position=None): if ambient is not None: self.ambient = colors.Color(ambient) if diffuse is not None: self.diffuse = colors.Color(diffuse) if specular is not None: self.specular = colors.Color(specular) if position is not None: self.position = at.checkArray(position, (3,), 'f') def enable(self, onoff=True): self.enabled = bool(onoff) def disable(self): self.enable(False) def __str__(self, name=''): return f"""LIGHT{name} (enabled: {self.enabled}): ambient color: {self.ambient} diffuse color: {self.diffuse} specular color: {self.specular} position: {self.position} """
[docs]class LightProfile(): """A lightprofile contains all the lighting parameters. Currently this consists off: - `ambient`: the global ambient lighting (currently a float) - `lights`: a list of 1 to 4 Lights """ def __init__(self, ambient, lights): self.ambient = ambient self.lights = lights def __str__(self): s = f"""LIGHTPROFILE: global ambient: {self.amnient} """ for i, l in enumerate(self.lights): s += ' ' + l.__str__(i) return s
##################################### ## Rendermode ## WIREMODE = {'wire': 1, 'bord': 2, 'feat': 3} if sys.hexversion >= 0x030A0000: my_dataclass = partial(dataclass, slots=True) else: my_dataclass = dataclass
[docs]@my_dataclass class Rendermode: """Store the rendermode parameters in a single object Currently this holds the following parameters: - fill: boolean - smooth: boolean (smooth/flat shading) - lighting: boolean (lights on/off) - wiremode: int 0..3 | None - avgnormals: boolean | None The parameters can be set by a string value:: fill smooth wiremode avgnormals lighting wireframe F F None None flat T F 0 None flatwire T F 1 None flatbord T F 2 None flatfeat T F 3 None smooth T T 0 F smoothwire T T 1 F smoothbord T T 2 F smoothfeat T T 3 F smoothnorm T T 0 T """ if sys.hexversion < 0x030A0000: __slots__ = ('fill', 'smooth', 'lighting', 'wiremode', 'avgnormals') fill: bool smooth: bool lighting: bool wiremode: int avgnormals: bool def __post_init__(self): self.fill = bool(self.fill) self.smooth = bool(self.smooth) self.lighting = bool(self.lighting) if self.wiremode is not None: self.wiremode = int(self.wiremode) if self.avgnormals is not None: self.avgnormals = bool(self.avgnormals) @classmethod def from_str(cls, s): fill = not s.startswith('wire') smooth = lighting = s.startswith('smooth') if fill: wiremode = WIREMODE.get(s[-4:], 0) # if not smooth: # wiremode = -wiremode else: wiremode = 0 # should become None if smooth: avgnormals = s[-4:] == 'norm' else: avgnormals = None return Rendermode(fill, smooth, lighting, wiremode, avgnormals) def to_str(self): if not self.fill: return 'wireframe' s = 'smooth' if self.smooth else 'flat' s += ['', 'wire', 'bord', 'feat'][max(self.wiremode, 0)] if s == 'smooth' and self.avgnormals: s += 'norm' return s def asdict(self): return asdict(self)
# __str__ = to_str RenderProfiles = dict((k, Rendermode.from_str(k)) for k in ( 'wireframe', 'flat', 'flatwire', 'flatbord', 'flatfeat', 'smooth', 'smoothwire', 'smoothbord', 'smoothfeat', 'smoothnorm')) def list_renderprofiles(): for k, v in RenderProfiles.items(): print(f"{k} = {v!r} = {v!s}") ################################################################## # # The Canvas Settings #
[docs]class CanvasSettings(Dict): """A collection of settings for an OpenGL Canvas. The canvas settings are a collection of settings and default values affecting the rendering in an individual viewport. There are two type of settings: - mode settings are set during the initialization of the canvas and can/should not be changed during the drawing of actors and decorations; - default settings can be used as default values but may be changed during the drawing of actors/decorations: they are reset before each individual draw instruction. Currently the following mode settings are defined: - bgmode: the viewport background color mode - bgcolor: the viewport background color: a single color or a list of colors (max. 4 are used). - bgimage: background image filename - alphablend: boolean (transparency on/off) The list of default settings includes: - fgcolor: the default drawing color - bkcolor: the default backface color - slcolor: the highlight color - colormap: the default color map to be used if color is an index - bklormap: the default color map to be used if bkcolor is an index - textcolor: the default color for text drawing - culling: boolean - transparency: float (0.0..1.0) - pointsize: the default size for drawing points - marksize: the default size for drawing markers - linewidth: the default width for drawing lines Any of these values can be set in the constructor using a keyword argument. All items that are not set, will get their value from the configuration file(s). """ bgcolor_modes = ['solid', 'vertical', 'horizontal', 'full'] edge_modes = ['none', 'feature', 'all'] def __init__(self, **kargs): """Create a new set of CanvasSettings.""" Dict.__init__(self) self.reset(kargs)
[docs] def reset(self, d={}): """Reset the CanvasSettings to its defaults. The default values are taken from the configuration files. An optional dictionary may be specified to override (some of) these defaults. """ self.update(pf.refcfg['canvas']) self.update(pf.prefcfg['canvas']) self.update(pf.cfg['canvas']) if d: self.update(d)
[docs] def update(self, d, strict=True): """Update current values with the specified settings Returns the sanitized update values. """ ok = self.checkDict(d, strict) Dict.update(self, ok)
[docs] @classmethod def checkDict(clas, dict, strict=True): """Transform a dict to acceptable settings.""" ok = {} for k, v in dict.items(): try: if k in ('bgcolor', 'fgcolor', 'bkcolor', 'slcolor', 'colormap', 'bkcolormap', 'textcolor'): if v is not None: v = saneColor(v) elif k in ('bgimage',): v = str(v) elif k in ('culling', 'alphablend'): v = bool(v) elif k in ['linewidth', 'pointsize', 'marksize']: v = float(v) elif k in ['wiremode']: v = int(v) elif k == 'linestipple': v = [int(vi) for vi in v] elif k == 'transparency': v = max(min(float(v), 1.0), 0.0) elif k == 'bgmode': v = str(v).lower() if v not in clas.bgcolor_modes: raise ValueError(f"Invalid background color mode {v}") elif k == 'marktype': pass elif k == 'shading': if v in ['wire', 'flat', 'smooth']: pass else: raise ValueError('Invalide shading value') else: raise ValueError("Invalid key") ok[k] = v except Exception: if strict: raise ValueError( f"Invalid key/value for CanvasSettings: {k} = {v}") return ok
[docs] def activate(self): """Activate the default canvas settings in the GL machine.""" from . import gl for k in self: if k in ['smooth', 'fill', 'linewidth', 'pointsize']: func = getattr(gl, 'gl_'+k) try: func(self[k]) except Exception: print(f"Error in setting {k} with func {func}") raise
__str__ = utils.formatDict
[docs] @staticmethod def extractCanvasSettings(d): """Split a dict in canvas settings and other items. Returns a tuple of two dicts: the first one contains the items that are canvas settings, the second one the rest. """ return (utils.select(d, pf.refcfg['canvas']), utils.remove(d, pf.refcfg['canvas']))
### End