- updated the ToolPanelize tool so the Gerber panel of type FlatCAMGerber can be isolated like any other FlatCAMGerber object
- updated the ToolPanelize tool so it can be edited
This commit is contained in:
@@ -17,6 +17,8 @@ CAD program, and create G-Code for Isolation routing.
|
|||||||
- fixed some bugs related to moving an Gerber object with the aperture table in view
|
- fixed some bugs related to moving an Gerber object with the aperture table in view
|
||||||
- added a new parameter in the Edit -> Preferences -> App Preferences named Geo Tolerance. This parameter control the level of geometric detail throughout FlatCAM. It directly influence the effect of Circle Steps parameter.
|
- added a new parameter in the Edit -> Preferences -> App Preferences named Geo Tolerance. This parameter control the level of geometric detail throughout FlatCAM. It directly influence the effect of Circle Steps parameter.
|
||||||
- solved a bug in Excellon Editor that caused app crash when trying to edit a tool in Tool Table due of missing a tool offset
|
- solved a bug in Excellon Editor that caused app crash when trying to edit a tool in Tool Table due of missing a tool offset
|
||||||
|
- updated the ToolPanelize tool so the Gerber panel of type FlatCAMGerber can be isolated like any other FlatCAMGerber object
|
||||||
|
- updated the ToolPanelize tool so it can be edited
|
||||||
|
|
||||||
5.05.2019
|
5.05.2019
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import time
|
|||||||
|
|
||||||
import gettext
|
import gettext
|
||||||
import FlatCAMTranslation as fcTranslate
|
import FlatCAMTranslation as fcTranslate
|
||||||
|
import builtins
|
||||||
|
|
||||||
fcTranslate.apply_language('strings')
|
fcTranslate.apply_language('strings')
|
||||||
import builtins
|
|
||||||
if '_' not in builtins.__dict__:
|
if '_' not in builtins.__dict__:
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
@@ -39,11 +39,11 @@ class Panelize(FlatCAMTool):
|
|||||||
""")
|
""")
|
||||||
self.layout.addWidget(title_label)
|
self.layout.addWidget(title_label)
|
||||||
|
|
||||||
## Form Layout
|
# Form Layout
|
||||||
form_layout = QtWidgets.QFormLayout()
|
form_layout = QtWidgets.QFormLayout()
|
||||||
self.layout.addLayout(form_layout)
|
self.layout.addLayout(form_layout)
|
||||||
|
|
||||||
## Type of object to be panelized
|
# Type of object to be panelized
|
||||||
self.type_obj_combo = QtWidgets.QComboBox()
|
self.type_obj_combo = QtWidgets.QComboBox()
|
||||||
self.type_obj_combo.addItem("Gerber")
|
self.type_obj_combo.addItem("Gerber")
|
||||||
self.type_obj_combo.addItem("Excellon")
|
self.type_obj_combo.addItem("Excellon")
|
||||||
@@ -56,13 +56,13 @@ class Panelize(FlatCAMTool):
|
|||||||
self.type_obj_combo_label = QtWidgets.QLabel(_("Object Type:"))
|
self.type_obj_combo_label = QtWidgets.QLabel(_("Object Type:"))
|
||||||
self.type_obj_combo_label.setToolTip(
|
self.type_obj_combo_label.setToolTip(
|
||||||
_("Specify the type of object to be panelized\n"
|
_("Specify the type of object to be panelized\n"
|
||||||
"It can be of type: Gerber, Excellon or Geometry.\n"
|
"It can be of type: Gerber, Excellon or Geometry.\n"
|
||||||
"The selection here decide the type of objects that will be\n"
|
"The selection here decide the type of objects that will be\n"
|
||||||
"in the Object combobox.")
|
"in the Object combobox.")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
|
form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
|
||||||
|
|
||||||
## Object to be panelized
|
# Object to be panelized
|
||||||
self.object_combo = QtWidgets.QComboBox()
|
self.object_combo = QtWidgets.QComboBox()
|
||||||
self.object_combo.setModel(self.app.collection)
|
self.object_combo.setModel(self.app.collection)
|
||||||
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||||
@@ -71,11 +71,11 @@ class Panelize(FlatCAMTool):
|
|||||||
self.object_label = QtWidgets.QLabel(_("Object:"))
|
self.object_label = QtWidgets.QLabel(_("Object:"))
|
||||||
self.object_label.setToolTip(
|
self.object_label.setToolTip(
|
||||||
_("Object to be panelized. This means that it will\n"
|
_("Object to be panelized. This means that it will\n"
|
||||||
"be duplicated in an array of rows and columns.")
|
"be duplicated in an array of rows and columns.")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.object_label, self.object_combo)
|
form_layout.addRow(self.object_label, self.object_combo)
|
||||||
|
|
||||||
## Type of Box Object to be used as an envelope for panelization
|
# Type of Box Object to be used as an envelope for panelization
|
||||||
self.type_box_combo = QtWidgets.QComboBox()
|
self.type_box_combo = QtWidgets.QComboBox()
|
||||||
self.type_box_combo.addItem("Gerber")
|
self.type_box_combo.addItem("Gerber")
|
||||||
self.type_box_combo.addItem("Excellon")
|
self.type_box_combo.addItem("Excellon")
|
||||||
@@ -89,13 +89,13 @@ class Panelize(FlatCAMTool):
|
|||||||
self.type_box_combo_label = QtWidgets.QLabel(_("Box Type:"))
|
self.type_box_combo_label = QtWidgets.QLabel(_("Box Type:"))
|
||||||
self.type_box_combo_label.setToolTip(
|
self.type_box_combo_label.setToolTip(
|
||||||
_("Specify the type of object to be used as an container for\n"
|
_("Specify the type of object to be used as an container for\n"
|
||||||
"panelization. It can be: Gerber or Geometry type.\n"
|
"panelization. It can be: Gerber or Geometry type.\n"
|
||||||
"The selection here decide the type of objects that will be\n"
|
"The selection here decide the type of objects that will be\n"
|
||||||
"in the Box Object combobox.")
|
"in the Box Object combobox.")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.type_box_combo_label, self.type_box_combo)
|
form_layout.addRow(self.type_box_combo_label, self.type_box_combo)
|
||||||
|
|
||||||
## Box
|
# Box
|
||||||
self.box_combo = QtWidgets.QComboBox()
|
self.box_combo = QtWidgets.QComboBox()
|
||||||
self.box_combo.setModel(self.app.collection)
|
self.box_combo.setModel(self.app.collection)
|
||||||
self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||||
@@ -104,29 +104,29 @@ class Panelize(FlatCAMTool):
|
|||||||
self.box_combo_label = QtWidgets.QLabel(_("Box Object:"))
|
self.box_combo_label = QtWidgets.QLabel(_("Box Object:"))
|
||||||
self.box_combo_label.setToolTip(
|
self.box_combo_label.setToolTip(
|
||||||
_("The actual object that is used a container for the\n "
|
_("The actual object that is used a container for the\n "
|
||||||
"selected object that is to be panelized.")
|
"selected object that is to be panelized.")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.box_combo_label, self.box_combo)
|
form_layout.addRow(self.box_combo_label, self.box_combo)
|
||||||
|
|
||||||
## Spacing Columns
|
# Spacing Columns
|
||||||
self.spacing_columns = FCEntry()
|
self.spacing_columns = FCEntry()
|
||||||
self.spacing_columns_label = QtWidgets.QLabel(_("Spacing cols:"))
|
self.spacing_columns_label = QtWidgets.QLabel(_("Spacing cols:"))
|
||||||
self.spacing_columns_label.setToolTip(
|
self.spacing_columns_label.setToolTip(
|
||||||
_("Spacing between columns of the desired panel.\n"
|
_("Spacing between columns of the desired panel.\n"
|
||||||
"In current units.")
|
"In current units.")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.spacing_columns_label, self.spacing_columns)
|
form_layout.addRow(self.spacing_columns_label, self.spacing_columns)
|
||||||
|
|
||||||
## Spacing Rows
|
# Spacing Rows
|
||||||
self.spacing_rows = FCEntry()
|
self.spacing_rows = FCEntry()
|
||||||
self.spacing_rows_label = QtWidgets.QLabel(_("Spacing rows:"))
|
self.spacing_rows_label = QtWidgets.QLabel(_("Spacing rows:"))
|
||||||
self.spacing_rows_label.setToolTip(
|
self.spacing_rows_label.setToolTip(
|
||||||
_("Spacing between rows of the desired panel.\n"
|
_("Spacing between rows of the desired panel.\n"
|
||||||
"In current units.")
|
"In current units.")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.spacing_rows_label, self.spacing_rows)
|
form_layout.addRow(self.spacing_rows_label, self.spacing_rows)
|
||||||
|
|
||||||
## Columns
|
# Columns
|
||||||
self.columns = FCEntry()
|
self.columns = FCEntry()
|
||||||
self.columns_label = QtWidgets.QLabel(_("Columns:"))
|
self.columns_label = QtWidgets.QLabel(_("Columns:"))
|
||||||
self.columns_label.setToolTip(
|
self.columns_label.setToolTip(
|
||||||
@@ -134,7 +134,7 @@ class Panelize(FlatCAMTool):
|
|||||||
)
|
)
|
||||||
form_layout.addRow(self.columns_label, self.columns)
|
form_layout.addRow(self.columns_label, self.columns)
|
||||||
|
|
||||||
## Rows
|
# Rows
|
||||||
self.rows = FCEntry()
|
self.rows = FCEntry()
|
||||||
self.rows_label = QtWidgets.QLabel(_("Rows:"))
|
self.rows_label = QtWidgets.QLabel(_("Rows:"))
|
||||||
self.rows_label.setToolTip(
|
self.rows_label.setToolTip(
|
||||||
@@ -142,26 +142,26 @@ class Panelize(FlatCAMTool):
|
|||||||
)
|
)
|
||||||
form_layout.addRow(self.rows_label, self.rows)
|
form_layout.addRow(self.rows_label, self.rows)
|
||||||
|
|
||||||
## Type of resulting Panel object
|
# Type of resulting Panel object
|
||||||
self.panel_type_radio = RadioSet([{'label': 'Gerber', 'value': 'gerber'},
|
self.panel_type_radio = RadioSet([{'label': 'Gerber', 'value': 'gerber'},
|
||||||
{'label': 'Geometry', 'value': 'geometry'}])
|
{'label': 'Geometry', 'value': 'geometry'}])
|
||||||
self.panel_type_label = QtWidgets.QLabel(_("Panel Type:"))
|
self.panel_type_label = QtWidgets.QLabel(_("Panel Type:"))
|
||||||
self.panel_type_label.setToolTip(
|
self.panel_type_label.setToolTip(
|
||||||
_("Choose the type of object for the panel object:\n"
|
_("Choose the type of object for the panel object:\n"
|
||||||
"- Geometry\n"
|
"- Geometry\n"
|
||||||
"- Gerber")
|
"- Gerber")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.panel_type_label)
|
form_layout.addRow(self.panel_type_label)
|
||||||
form_layout.addRow(self.panel_type_radio)
|
form_layout.addRow(self.panel_type_radio)
|
||||||
|
|
||||||
## Constrains
|
# Constrains
|
||||||
self.constrain_cb = FCCheckBox(_("Constrain panel within:"))
|
self.constrain_cb = FCCheckBox(_("Constrain panel within:"))
|
||||||
self.constrain_cb.setToolTip(
|
self.constrain_cb.setToolTip(
|
||||||
_("Area define by DX and DY within to constrain the panel.\n"
|
_("Area define by DX and DY within to constrain the panel.\n"
|
||||||
"DX and DY values are in current units.\n"
|
"DX and DY values are in current units.\n"
|
||||||
"Regardless of how many columns and rows are desired,\n"
|
"Regardless of how many columns and rows are desired,\n"
|
||||||
"the final panel will have as many columns and rows as\n"
|
"the final panel will have as many columns and rows as\n"
|
||||||
"they fit completely within selected area.")
|
"they fit completely within selected area.")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.constrain_cb)
|
form_layout.addRow(self.constrain_cb)
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ class Panelize(FlatCAMTool):
|
|||||||
self.x_width_lbl = QtWidgets.QLabel(_("Width (DX):"))
|
self.x_width_lbl = QtWidgets.QLabel(_("Width (DX):"))
|
||||||
self.x_width_lbl.setToolTip(
|
self.x_width_lbl.setToolTip(
|
||||||
_("The width (DX) within which the panel must fit.\n"
|
_("The width (DX) within which the panel must fit.\n"
|
||||||
"In current units.")
|
"In current units.")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.x_width_lbl, self.x_width_entry)
|
form_layout.addRow(self.x_width_lbl, self.x_width_entry)
|
||||||
|
|
||||||
@@ -177,14 +177,14 @@ class Panelize(FlatCAMTool):
|
|||||||
self.y_height_lbl = QtWidgets.QLabel(_("Height (DY):"))
|
self.y_height_lbl = QtWidgets.QLabel(_("Height (DY):"))
|
||||||
self.y_height_lbl.setToolTip(
|
self.y_height_lbl.setToolTip(
|
||||||
_("The height (DY)within which the panel must fit.\n"
|
_("The height (DY)within which the panel must fit.\n"
|
||||||
"In current units.")
|
"In current units.")
|
||||||
)
|
)
|
||||||
form_layout.addRow(self.y_height_lbl, self.y_height_entry)
|
form_layout.addRow(self.y_height_lbl, self.y_height_entry)
|
||||||
|
|
||||||
self.constrain_sel = OptionalInputSection(
|
self.constrain_sel = OptionalInputSection(
|
||||||
self.constrain_cb, [self.x_width_lbl, self.x_width_entry, self.y_height_lbl, self.y_height_entry])
|
self.constrain_cb, [self.x_width_lbl, self.x_width_entry, self.y_height_lbl, self.y_height_entry])
|
||||||
|
|
||||||
## Buttons
|
# Buttons
|
||||||
hlay_2 = QtWidgets.QHBoxLayout()
|
hlay_2 = QtWidgets.QHBoxLayout()
|
||||||
self.layout.addLayout(hlay_2)
|
self.layout.addLayout(hlay_2)
|
||||||
|
|
||||||
@@ -192,14 +192,14 @@ class Panelize(FlatCAMTool):
|
|||||||
self.panelize_object_button = QtWidgets.QPushButton(_("Panelize Object"))
|
self.panelize_object_button = QtWidgets.QPushButton(_("Panelize Object"))
|
||||||
self.panelize_object_button.setToolTip(
|
self.panelize_object_button.setToolTip(
|
||||||
_("Panelize the specified object around the specified box.\n"
|
_("Panelize the specified object around the specified box.\n"
|
||||||
"In other words it creates multiple copies of the source object,\n"
|
"In other words it creates multiple copies of the source object,\n"
|
||||||
"arranged in a 2D array of rows and columns.")
|
"arranged in a 2D array of rows and columns.")
|
||||||
)
|
)
|
||||||
hlay_2.addWidget(self.panelize_object_button)
|
hlay_2.addWidget(self.panelize_object_button)
|
||||||
|
|
||||||
self.layout.addStretch()
|
self.layout.addStretch()
|
||||||
|
|
||||||
## Signals
|
# Signals
|
||||||
self.panelize_object_button.clicked.connect(self.on_panelize)
|
self.panelize_object_button.clicked.connect(self.on_panelize)
|
||||||
self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
||||||
self.type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
|
self.type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
|
||||||
@@ -387,7 +387,6 @@ class Panelize(FlatCAMTool):
|
|||||||
|
|
||||||
panel_type = str(self.panel_type_radio.get_value())
|
panel_type = str(self.panel_type_radio.get_value())
|
||||||
|
|
||||||
|
|
||||||
if 0 in {columns, rows}:
|
if 0 in {columns, rows}:
|
||||||
self.app.inform.emit(_("[ERROR_NOTCL] Columns or Rows are zero value. Change them to a positive integer."))
|
self.app.inform.emit(_("[ERROR_NOTCL] Columns or Rows are zero value. Change them to a positive integer."))
|
||||||
return "Columns or Rows are zero value. Change them to a positive integer."
|
return "Columns or Rows are zero value. Change them to a positive integer."
|
||||||
@@ -471,7 +470,11 @@ class Panelize(FlatCAMTool):
|
|||||||
if type(geom) == list:
|
if type(geom) == list:
|
||||||
geoms = list()
|
geoms = list()
|
||||||
for local_geom in geom:
|
for local_geom in geom:
|
||||||
geoms.append(translate_recursion(local_geom))
|
res_geo = translate_recursion(local_geom)
|
||||||
|
try:
|
||||||
|
geoms += (res_geo)
|
||||||
|
except TypeError:
|
||||||
|
geoms.append(res_geo)
|
||||||
return geoms
|
return geoms
|
||||||
else:
|
else:
|
||||||
return affinity.translate(geom, xoff=currentx, yoff=currenty)
|
return affinity.translate(geom, xoff=currentx, yoff=currenty)
|
||||||
@@ -485,6 +488,16 @@ class Panelize(FlatCAMTool):
|
|||||||
for tool in panel_obj.tools:
|
for tool in panel_obj.tools:
|
||||||
obj_fin.tools[tool]['solid_geometry'][:] = []
|
obj_fin.tools[tool]['solid_geometry'][:] = []
|
||||||
|
|
||||||
|
if isinstance(panel_obj, FlatCAMGerber):
|
||||||
|
obj_fin.apertures = deepcopy(panel_obj.apertures)
|
||||||
|
for ap in obj_fin.apertures:
|
||||||
|
if 'solid_geometry' in obj_fin.apertures[ap]:
|
||||||
|
obj_fin.apertures[ap]['solid_geometry'] = []
|
||||||
|
if 'clear_geometry' in obj_fin.apertures[ap]:
|
||||||
|
obj_fin.apertures[ap]['clear_geometry'] = []
|
||||||
|
if 'follow_geometry' in obj_fin.apertures[ap]:
|
||||||
|
obj_fin.apertures[ap]['follow_geometry'] = []
|
||||||
|
|
||||||
self.app.progress.emit(0)
|
self.app.progress.emit(0)
|
||||||
for row in range(rows):
|
for row in range(rows):
|
||||||
currentx = 0.0
|
currentx = 0.0
|
||||||
@@ -493,21 +506,54 @@ class Panelize(FlatCAMTool):
|
|||||||
if isinstance(panel_obj, FlatCAMGeometry):
|
if isinstance(panel_obj, FlatCAMGeometry):
|
||||||
if panel_obj.multigeo is True:
|
if panel_obj.multigeo is True:
|
||||||
for tool in panel_obj.tools:
|
for tool in panel_obj.tools:
|
||||||
obj_fin.tools[tool]['solid_geometry'].append(translate_recursion(
|
geo = translate_recursion(panel_obj.tools[tool]['solid_geometry'])
|
||||||
panel_obj.tools[tool]['solid_geometry'])
|
if isinstance(geo, list):
|
||||||
)
|
obj_fin.tools[tool]['solid_geometry'] += geo
|
||||||
|
else:
|
||||||
|
obj_fin.tools[tool]['solid_geometry'].append(geo)
|
||||||
else:
|
else:
|
||||||
obj_fin.solid_geometry.append(
|
geo = translate_recursion(panel_obj.solid_geometry)
|
||||||
translate_recursion(panel_obj.solid_geometry)
|
if isinstance(geo, list):
|
||||||
)
|
obj_fin.solid_geometry += geo
|
||||||
|
else:
|
||||||
|
obj_fin.solid_geometry.append(geo)
|
||||||
else:
|
else:
|
||||||
obj_fin.solid_geometry.append(
|
geo = translate_recursion(panel_obj.solid_geometry)
|
||||||
translate_recursion(panel_obj.solid_geometry)
|
if isinstance(geo, list):
|
||||||
)
|
obj_fin.solid_geometry += geo
|
||||||
|
else:
|
||||||
|
obj_fin.solid_geometry.append(geo)
|
||||||
|
|
||||||
|
for apid in panel_obj.apertures:
|
||||||
|
if 'solid_geometry' in panel_obj.apertures[apid]:
|
||||||
|
geo_aper = translate_recursion(panel_obj.apertures[apid]['solid_geometry'])
|
||||||
|
if isinstance(geo_aper, list):
|
||||||
|
obj_fin.apertures[apid]['solid_geometry'] += geo_aper
|
||||||
|
else:
|
||||||
|
obj_fin.apertures[apid]['solid_geometry'].append(geo_aper)
|
||||||
|
|
||||||
|
if 'clear_geometry' in panel_obj.apertures[apid]:
|
||||||
|
geo_aper = translate_recursion(panel_obj.apertures[apid]['clear_geometry'])
|
||||||
|
if isinstance(geo_aper, list):
|
||||||
|
obj_fin.apertures[apid]['clear_geometry'] += geo_aper
|
||||||
|
else:
|
||||||
|
obj_fin.apertures[apid]['clear_geometry'].append(geo_aper)
|
||||||
|
|
||||||
|
if 'follow_geometry' in panel_obj.apertures[apid]:
|
||||||
|
geo_aper = translate_recursion(panel_obj.apertures[apid]['follow_geometry'])
|
||||||
|
if isinstance(geo_aper, list):
|
||||||
|
obj_fin.apertures[apid]['follow_geometry'] += geo_aper
|
||||||
|
else:
|
||||||
|
obj_fin.apertures[apid]['follow_geometry'].append(geo_aper)
|
||||||
|
|
||||||
currentx += lenghtx
|
currentx += lenghtx
|
||||||
currenty += lenghty
|
currenty += lenghty
|
||||||
|
|
||||||
|
app_obj.log.debug("Found %s geometries. Creating a panel geometry cascaded union ..." %
|
||||||
|
len(obj_fin.solid_geometry))
|
||||||
|
obj_fin.solid_geometry = cascaded_union(obj_fin.solid_geometry)
|
||||||
|
app_obj.log.debug("Finished creating a cascaded union for the panel.")
|
||||||
|
|
||||||
if isinstance(panel_obj, FlatCAMExcellon):
|
if isinstance(panel_obj, FlatCAMExcellon):
|
||||||
self.app.progress.emit(50)
|
self.app.progress.emit(50)
|
||||||
self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
|
self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
|
||||||
@@ -520,7 +566,8 @@ class Panelize(FlatCAMTool):
|
|||||||
self.app.inform.emit(_("[success] Panel done..."))
|
self.app.inform.emit(_("[success] Panel done..."))
|
||||||
else:
|
else:
|
||||||
self.constrain_flag = False
|
self.constrain_flag = False
|
||||||
self.app.inform.emit(_("[WARNING] Too big for the constrain area. Final panel has {col} columns and {row} rows").format(
|
self.app.inform.emit(_("[WARNING] Too big for the constrain area. "
|
||||||
|
"Final panel has {col} columns and {row} rows").format(
|
||||||
col=columns, row=rows))
|
col=columns, row=rows))
|
||||||
|
|
||||||
proc = self.app.proc_container.new(_("Generating panel ... Please wait."))
|
proc = self.app.proc_container.new(_("Generating panel ... Please wait."))
|
||||||
|
|||||||
Reference in New Issue
Block a user