mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
First draft of style manager panel
This commit is contained in:
@@ -20,24 +20,29 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddStyle,
|
||||
operator.EnableEditingStyle,
|
||||
operator.DisableEditingStyle,
|
||||
operator.EditStyle,
|
||||
operator.UpdateCurrentStyle,
|
||||
operator.EnableEditingExternalStyle,
|
||||
operator.DisableEditingExternalStyle,
|
||||
operator.EditExternalStyle,
|
||||
operator.DisableEditingStyles,
|
||||
operator.BrowseExternalStyle,
|
||||
operator.ActivateExternalStyle,
|
||||
operator.AddPresentationStyle,
|
||||
operator.AddStyle,
|
||||
operator.BrowseExternalStyle,
|
||||
operator.ClearTextureMapPath,
|
||||
operator.DisableAddPresentationStyle,
|
||||
operator.DisableEditingExternalStyle,
|
||||
operator.DisableEditingStyle,
|
||||
operator.DisableEditingStyles,
|
||||
operator.EditExternalStyle,
|
||||
operator.EditStyle,
|
||||
operator.EditSurfaceStyle,
|
||||
operator.EnableAddPresentationStyle,
|
||||
operator.EnableEditingExternalStyle,
|
||||
operator.EnableEditingStyle,
|
||||
operator.EnableEditingSurfaceStyle,
|
||||
operator.LoadStyles,
|
||||
operator.RemoveStyle,
|
||||
operator.SelectByStyle,
|
||||
operator.UnlinkStyle,
|
||||
operator.UpdateCurrentStyle,
|
||||
operator.UpdateStyleColours,
|
||||
operator.UpdateStyleTextures,
|
||||
operator.ClearTextureMapPath,
|
||||
prop.Style,
|
||||
prop.BIMStylesProperties,
|
||||
prop.BIMStyleProperties,
|
||||
|
||||
@@ -16,15 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.style as core
|
||||
import ifcopenshell.util.representation
|
||||
from pathlib import Path
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.style.data import StylesData, StyleAttributesData
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
|
||||
class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -99,9 +100,16 @@ class EnableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_style"
|
||||
bl_label = "Enable Editing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
style: bpy.props.IntProperty(default=0)
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_style(tool.Style, obj=context.active_object.active_material)
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
style = tool.Ifc.get().by_id(self.style)
|
||||
props.is_editing_style = style.id()
|
||||
props.is_editing_class = "IfcSurfaceStyle"
|
||||
attributes = props.attributes
|
||||
attributes.clear()
|
||||
blenderbim.bim.helper.import_attributes2(style, attributes)
|
||||
|
||||
|
||||
class DisableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -110,7 +118,8 @@ class DisableEditingStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Disable Editing Style"
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_style(tool.Style, obj=context.active_object.active_material)
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
props.is_editing_style = 0
|
||||
|
||||
|
||||
class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -119,7 +128,12 @@ class EditStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_style(tool.Ifc, tool.Style, obj=context.active_object.active_material)
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
style = tool.Ifc.get().by_id(props.is_editing_style)
|
||||
attributes = blenderbim.bim.helper.export_attributes(props.attributes)
|
||||
ifcopenshell.api.run("style.edit_presentation_style", tool.Ifc.get(), style=style, attributes=attributes)
|
||||
props.is_editing_style = 0
|
||||
core.load_styles(tool.Style, style_type=props.style_type)
|
||||
|
||||
|
||||
class UpdateCurrentStyle(bpy.types.Operator):
|
||||
@@ -415,3 +429,122 @@ class ClearTextureMapPath(bpy.types.Operator):
|
||||
props = context.material.BIMStyleProperties
|
||||
setattr(props, self.texture_map_prop, "")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableAddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_add_presentation_style"
|
||||
bl_label = "Enable Add Presentation Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
props.is_adding = True
|
||||
|
||||
|
||||
class DisableAddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_add_presentation_style"
|
||||
bl_label = "Disable Add Presentation Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
props.is_adding = False
|
||||
|
||||
|
||||
class AddPresentationStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_presentation_style"
|
||||
bl_label = "Add Presentation Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
if props.style_type == "IfcSurfaceStyle":
|
||||
style = ifcopenshell.api.run("style.add_style", tool.Ifc.get(), name=props.style_name)
|
||||
if props.surface_style_class in ("IfcSurfaceStyleShading", "IfcSurfaceStyleRendering"):
|
||||
surface_style = ifcopenshell.api.run(
|
||||
"style.add_surface_style",
|
||||
tool.Ifc.get(),
|
||||
style=style,
|
||||
ifc_class=props.surface_style_class,
|
||||
attributes={
|
||||
"SurfaceColour": {
|
||||
"Name": None,
|
||||
"Red": props.surface_colour[0],
|
||||
"Green": props.surface_colour[1],
|
||||
"Blue": props.surface_colour[2],
|
||||
}
|
||||
},
|
||||
)
|
||||
if props.surface_style_class == "IfcSurfaceStyleRendering":
|
||||
surface_style.ReflectanceMethod = "NOTDEFINED"
|
||||
props.is_adding = False
|
||||
core.load_styles(tool.Style, style_type=props.style_type)
|
||||
|
||||
|
||||
class EnableEditingSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_surface_style"
|
||||
bl_label = "Enable Editing Surface Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
style: bpy.props.IntProperty(default=0)
|
||||
ifc_class: bpy.props.StringProperty(default="")
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMStylesProperties
|
||||
style = tool.Ifc.get().by_id(self.style)
|
||||
|
||||
surface_style = None
|
||||
for style2 in style.Styles:
|
||||
if style2.is_a() == self.ifc_class:
|
||||
surface_style = style2
|
||||
break
|
||||
|
||||
if surface_style:
|
||||
color_to_tuple = lambda x: (x.Red, x.Green, x.Blue)
|
||||
if self.ifc_class == "IfcSurfaceStyleShading":
|
||||
props.surface_colour = color_to_tuple(surface_style.SurfaceColour)
|
||||
props.transparency = surface_style.Transparency or 0.
|
||||
elif self.ifc_class == "IfcSurfaceStyleRendering":
|
||||
props.surface_colour = color_to_tuple(surface_style.SurfaceColour)
|
||||
props.transparency = surface_style.Transparency or 0.
|
||||
|
||||
props.is_editing_style = self.style
|
||||
props.is_editing_class = self.ifc_class
|
||||
|
||||
|
||||
class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_surface_style"
|
||||
bl_label = "Edit Surface Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = bpy.context.scene.BIMStylesProperties
|
||||
style = tool.Ifc.get().by_id(self.props.is_editing_style)
|
||||
|
||||
self.surface_style = None
|
||||
self.shading_style = None
|
||||
self.rendering_style = None
|
||||
for style2 in style.Styles:
|
||||
if style2.is_a() == self.props.is_editing_class:
|
||||
self.surface_style = style2
|
||||
if style2.is_a() == "IfcSurfaceStyleShading":
|
||||
self.shading_style = style2
|
||||
if style2.is_a() == "IfcSurfaceStyleRendering":
|
||||
self.rendering_style = style2
|
||||
|
||||
if self.surface_style:
|
||||
self.edit_existing_style()
|
||||
else:
|
||||
self.add_new_style()
|
||||
|
||||
self.props.is_editing_style = 0
|
||||
core.load_styles(tool.Style, style_type=self.props.style_type)
|
||||
|
||||
def edit_existing_style(self):
|
||||
if self.surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||
self.surface_style.SurfaceColour.Red = self.props.surface_colour[0]
|
||||
self.surface_style.SurfaceColour.Green = self.props.surface_colour[1]
|
||||
self.surface_style.SurfaceColour.Blue = self.props.surface_colour[2]
|
||||
self.surface_style.Transparency = self.props.transparency or None
|
||||
|
||||
def add_new_style(self):
|
||||
pass
|
||||
|
||||
@@ -44,6 +44,11 @@ class Style(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
total_elements: IntProperty(name="Total Elements")
|
||||
style_classes: CollectionProperty(name="Style Classes", type=StrProperty)
|
||||
has_surface_colour: BoolProperty(name="Has Surface Colour", default=False)
|
||||
surface_colour: bpy.props.FloatVectorProperty(
|
||||
name="Surface Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3
|
||||
)
|
||||
|
||||
|
||||
STYLE_TYPES = [
|
||||
@@ -60,8 +65,32 @@ def update_shading_styles(self, context):
|
||||
|
||||
|
||||
class BIMStylesProperties(PropertyGroup):
|
||||
is_adding: BoolProperty(name="Is Adding")
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
style_type: EnumProperty(items=get_style_types, name="Style Type")
|
||||
is_editing_style: IntProperty(name="Is Editing Style")
|
||||
is_editing_class: StringProperty(name="Is Editing Class")
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
style_type: EnumProperty(items=get_style_types, default=2, name="Style Type")
|
||||
style_name: StringProperty(name="Style Name")
|
||||
surface_style_class: EnumProperty(
|
||||
items=[
|
||||
(x, x, "")
|
||||
for x in (
|
||||
"IfcSurfaceStyleShading",
|
||||
"IfcSurfaceStyleRendering",
|
||||
"IfcSurfaceStyleWithTextures",
|
||||
"IfcSurfaceStyleLighting",
|
||||
"IfcSurfaceStyleRefraction",
|
||||
"IfcExternallyDefinedSurfaceStyle",
|
||||
)
|
||||
],
|
||||
name="Surface Style Class",
|
||||
default="IfcSurfaceStyleShading",
|
||||
)
|
||||
surface_colour: bpy.props.FloatVectorProperty(
|
||||
name="Surface Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3
|
||||
)
|
||||
transparency: bpy.props.FloatProperty(name="Transparency", default=0.0, min=0.0, max=1.0)
|
||||
styles: CollectionProperty(name="Styles", type=Style)
|
||||
active_style_index: IntProperty(name="Active Style Index")
|
||||
active_style_type: EnumProperty(
|
||||
|
||||
@@ -28,11 +28,11 @@ from blenderbim.tool.style import TEXTURE_MAPS_BY_METHODS, STYLE_TEXTURE_PROPS_M
|
||||
class BIM_PT_styles(Panel):
|
||||
bl_label = "Styles"
|
||||
bl_idname = "BIM_PT_styles"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry"
|
||||
bl_parent_id = "BIM_PT_tab_styles"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -44,28 +44,97 @@ class BIM_PT_styles(Panel):
|
||||
|
||||
self.props = context.scene.BIMStylesProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Styles Found".format(StylesData.data["total_styles"]), icon="MATERIAL")
|
||||
row = self.layout.row(align=True)
|
||||
if self.props.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} {}s".format(len(self.props.styles), self.props.style_type), icon="SHADING_RENDERED")
|
||||
if not self.props.is_adding:
|
||||
row.operator("bim.enable_add_presentation_style", text="", icon="ADD")
|
||||
row.operator("bim.disable_editing_styles", text="", icon="CANCEL")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Styles".format(StylesData.data["total_styles"]), icon="SHADING_RENDERED")
|
||||
blenderbim.bim.helper.prop_with_search(row, self.props, "style_type", text="")
|
||||
row.operator("bim.load_styles", text="", icon="IMPORT").style_type = self.props.style_type
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
if self.props.is_adding:
|
||||
box = self.layout.box()
|
||||
row = box.row()
|
||||
row.prop(self.props, "style_name", text="Name")
|
||||
if self.props.style_type == "IfcSurfaceStyle":
|
||||
row = box.row()
|
||||
row.prop(self.props, "surface_style_class", text="Class")
|
||||
if self.props.surface_style_class in ("IfcSurfaceStyleShading", "IfcSurfaceStyleRendering"):
|
||||
row = box.row()
|
||||
row.prop(self.props, "surface_colour", text="Colour")
|
||||
row = box.row(align=True)
|
||||
row.operator("bim.add_presentation_style", text="Save New Style", icon="CHECKMARK")
|
||||
row.operator("bim.disable_add_presentation_style", text="", icon="CANCEL")
|
||||
|
||||
# row.operator("bim.add_presentation_style", text="", icon="ADD")
|
||||
if self.props.styles and self.props.active_style_index < len(self.props.styles):
|
||||
row = self.layout.row(align=True)
|
||||
style = self.props.styles[self.props.active_style_index]
|
||||
op = row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op = row.operator("bim.enable_editing_style", text="Edit Style", icon="GREASEPENCIL")
|
||||
op.style = style.ifc_definition_id
|
||||
row.operator("bim.select_by_style", text="", icon="RESTRICT_SELECT_OFF").style = style.ifc_definition_id
|
||||
row.operator("bim.remove_style", text="", icon="X").style = style.ifc_definition_id
|
||||
|
||||
if self.props.style_type == "IfcSurfaceStyle":
|
||||
col = self.layout.column(align=True)
|
||||
|
||||
row = col.row(align=True)
|
||||
op = row.operator("bim.enable_editing_surface_style", text="Shade", icon="SHADING_SOLID")
|
||||
op.ifc_class = "IfcSurfaceStyleShading"
|
||||
op.style = style.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_surface_style", text="Render", icon="SHADING_RENDERED")
|
||||
op.ifc_class = "IfcSurfaceStyleRendering"
|
||||
op.style = style.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_surface_style", text="Texture", icon="SHADING_TEXTURE")
|
||||
op.ifc_class = "IfcSurfaceStyleWithTextures"
|
||||
op.style = style.ifc_definition_id
|
||||
|
||||
row = col.row(align=True)
|
||||
op = row.operator("bim.enable_editing_surface_style", text="Lighting", icon="LIGHT_SUN")
|
||||
op.ifc_class = "IfcSurfaceStyleLighting"
|
||||
op.style = style.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_surface_style", text="Refract", icon="LIGHT_POINT")
|
||||
op.ifc_class = "IfcSurfaceStyleRefraction"
|
||||
op.style = style.ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_surface_style", text="External", icon="APPEND_BLEND")
|
||||
op.ifc_class = "IfcExternallyDefinedSurfaceStyle"
|
||||
op.style = style.ifc_definition_id
|
||||
|
||||
self.layout.template_list("BIM_UL_styles", "", self.props, "styles", self.props, "active_style_index")
|
||||
|
||||
if self.props.is_editing_style:
|
||||
if self.props.is_editing_class == "IfcSurfaceStyle":
|
||||
blenderbim.bim.helper.draw_attributes(self.props.attributes, self.layout)
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_style", text="Save Attributes", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_style", text="", icon="CANCEL")
|
||||
elif self.props.is_editing_class == "IfcSurfaceStyleShading":
|
||||
self.draw_surface_style_shading()
|
||||
elif self.props.is_editing_class == "IfcSurfaceStyleRendering":
|
||||
self.draw_surface_style_rendering()
|
||||
|
||||
def draw_surface_style_shading(self):
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "surface_colour")
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "transparency")
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_surface_style", text="Save Shading Style", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_style", text="", icon="CANCEL")
|
||||
|
||||
def draw_surface_style_rendering(self):
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "surface_colour")
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "transparency")
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_surface_style", text="Save Rendering Style", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_style", text="", icon="CANCEL")
|
||||
|
||||
|
||||
def draw_style_ui(self, context):
|
||||
mat = context.material
|
||||
@@ -145,9 +214,6 @@ class BIM_PT_style_attributes(Panel):
|
||||
row.operator("bim.disable_editing_style", icon="CANCEL", text="")
|
||||
blenderbim.bim.helper.draw_attributes(props.attributes, self.layout)
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_editing_style", icon="GREASEPENCIL", text="Edit")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="STEP ID")
|
||||
row.label(text=str(mprops.ifc_style_id))
|
||||
@@ -218,9 +284,26 @@ class BIM_UL_styles(UIList):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
if item.has_surface_colour:
|
||||
row = row.row()
|
||||
row.enabled = False
|
||||
row.prop(item, "surface_colour", text="")
|
||||
row2 = row.row()
|
||||
row2.alignment = "RIGHT"
|
||||
row2.label(text=str(item.total_elements))
|
||||
for style in item.style_classes:
|
||||
if style.name == "IfcSurfaceStyleShading":
|
||||
row2.label(text="", icon="SHADING_SOLID")
|
||||
elif style.name == "IfcSurfaceStyleRendering":
|
||||
row2.label(text="", icon="SHADING_RENDERED")
|
||||
elif style.name == "IfcSurfaceStyleWithTextures":
|
||||
row2.label(text="", icon="SHADING_TEXTURE")
|
||||
elif style.name == "IfcSurfaceStyleLighting":
|
||||
row2.label(text="", icon="LIGHT_SUN")
|
||||
elif style.name == "IfcSurfaceStyleRefraction":
|
||||
row2.label(text="", icon="LIGHT_POINT")
|
||||
elif style.name == "IfcExternallyDefinedSurfaceStyle":
|
||||
row2.label(text="", icon="APPEND_BLEND")
|
||||
|
||||
|
||||
class BIM_PT_STYLE_GRAPH(Panel):
|
||||
@@ -273,7 +356,7 @@ class BIM_PT_STYLE_GRAPH(Panel):
|
||||
row.prop(props, path_name)
|
||||
op = row.operator("bim.clear_texture_map_path", text="", icon="X")
|
||||
op.texture_map_prop = path_name
|
||||
|
||||
|
||||
layout.separator()
|
||||
texture_maps = TEXTURE_MAPS_BY_METHODS.get(props.reflectance_method, [])
|
||||
if not texture_maps:
|
||||
|
||||
@@ -26,7 +26,7 @@ import os.path
|
||||
|
||||
# fmt: off
|
||||
TEXTURE_MAPS_BY_METHODS = {
|
||||
"PHYSICAL": ("NORMAL", "EMISSIVE", "METALLICROUGHNESS", "DIFFUSE", "OCCLUSION"),
|
||||
"PHYSICAL": ("NORMAL", "EMISSIVE", "METALLICROUGHNESS", "DIFFUSE", "OCCLUSION"),
|
||||
"FLAT": ("EMISSIVE",)
|
||||
}
|
||||
# fmt: on
|
||||
@@ -430,6 +430,14 @@ class Style(blenderbim.core.tool.Style):
|
||||
new = props.styles.add()
|
||||
new.ifc_definition_id = style.id()
|
||||
new.name = style.Name or "Unnamed"
|
||||
new.ifc_class = style.is_a()
|
||||
for surface_style in getattr(style, "Styles", []):
|
||||
new2 = new.style_classes.add()
|
||||
new2.name = surface_style.is_a()
|
||||
if surface_style.is_a("IfcSurfaceStyleShading"):
|
||||
color_to_tuple = lambda x: (x.Red, x.Green, x.Blue)
|
||||
new.has_surface_colour = True
|
||||
new.surface_colour = color_to_tuple(surface_style.SurfaceColour)
|
||||
new.total_elements = len(ifcopenshell.util.element.get_elements_by_style(tool.Ifc.get(), style))
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in New Issue
Block a user