- added a fix in the Gerber parser when adding the geometry in the self.apertures dict for the case that the current aperture is None (Allegro does that)
- finished support for internationalization by adding a set of .po/.mo files for the English language. Unfortunately the final action can be done only when Beta will be out of Beta (no more changes) or when I will decide to stop working on this app. - changed the tooltip for 'feedrate_rapids' parameter to point out that this parameter is useful only for the Marlin postprocessor
This commit is contained in:
115
camlib.py
115
camlib.py
@@ -75,13 +75,6 @@ import FlatCAMTranslation as fcTranslate
|
||||
fcTranslate.apply_language('camlib')
|
||||
|
||||
|
||||
def _tr(text):
|
||||
try:
|
||||
return _(text)
|
||||
except:
|
||||
return text
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
||||
@@ -206,7 +199,7 @@ class Geometry(object):
|
||||
if isinstance(self.solid_geometry, list):
|
||||
return len(self.solid_geometry) == 0
|
||||
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] self.solid_geometry is neither BaseGeometry or list."))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] self.solid_geometry is neither BaseGeometry or list."))
|
||||
return
|
||||
|
||||
def subtract_polygon(self, points):
|
||||
@@ -1393,9 +1386,9 @@ class Geometry(object):
|
||||
self.tools[tool]['solid_geometry'] = mirror_geom(self.tools[tool]['solid_geometry'])
|
||||
else:
|
||||
self.solid_geometry = mirror_geom(self.solid_geometry)
|
||||
self.app.inform.emit(_tr('[success]Object was mirrored ...'))
|
||||
self.app.inform.emit(_('[success]Object was mirrored ...'))
|
||||
except AttributeError:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Failed to mirror. No object selected"))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Failed to mirror. No object selected"))
|
||||
|
||||
def rotate(self, angle, point):
|
||||
"""
|
||||
@@ -1431,9 +1424,9 @@ class Geometry(object):
|
||||
self.tools[tool]['solid_geometry'] = rotate_geom(self.tools[tool]['solid_geometry'])
|
||||
else:
|
||||
self.solid_geometry = rotate_geom(self.solid_geometry)
|
||||
self.app.inform.emit(_tr('[success]Object was rotated ...'))
|
||||
self.app.inform.emit(_('[success]Object was rotated ...'))
|
||||
except AttributeError:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Failed to rotate. No object selected"))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Failed to rotate. No object selected"))
|
||||
|
||||
def skew(self, angle_x, angle_y, point):
|
||||
"""
|
||||
@@ -1467,9 +1460,9 @@ class Geometry(object):
|
||||
self.tools[tool]['solid_geometry'] = skew_geom(self.tools[tool]['solid_geometry'])
|
||||
else:
|
||||
self.solid_geometry = skew_geom(self.solid_geometry)
|
||||
self.app.inform.emit(_tr('[success]Object was skewed ...'))
|
||||
self.app.inform.emit(_('[success]Object was skewed ...'))
|
||||
except AttributeError:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Failed to skew. No object selected"))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Failed to skew. No object selected"))
|
||||
|
||||
# if type(self.solid_geometry) == list:
|
||||
# self.solid_geometry = [affinity.skew(g, angle_x, angle_y, origin=(px, py))
|
||||
@@ -2594,6 +2587,18 @@ class Gerber (Geometry):
|
||||
# NOTE: Letting it continue allows it to react to the
|
||||
# operation code.
|
||||
|
||||
# we do this for the case that a region is done without having defined any aperture
|
||||
# Allegro does that
|
||||
if current_aperture:
|
||||
last_path_aperture = current_aperture
|
||||
|
||||
if last_path_aperture is None:
|
||||
if '0' not in self.apertures:
|
||||
self.apertures['0'] = {}
|
||||
self.apertures['0']['solid_geometry'] = []
|
||||
self.apertures['0']['type'] = 'REG'
|
||||
last_path_aperture = '0'
|
||||
|
||||
# Parse coordinates
|
||||
if match.group(2) is not None:
|
||||
linear_x = parse_gerber_number(match.group(2),
|
||||
@@ -2645,8 +2650,8 @@ class Gerber (Geometry):
|
||||
pass
|
||||
last_path_aperture = current_aperture
|
||||
else:
|
||||
self.app.inform.emit(_tr("[WARNING] Coordinates missing, line ignored: %s") % str(gline))
|
||||
self.app.inform.emit(_tr("[WARNING_NOTCL] GERBER file might be CORRUPT. Check the file !!!"))
|
||||
self.app.inform.emit(_("[WARNING] Coordinates missing, line ignored: %s") % str(gline))
|
||||
self.app.inform.emit(_("[WARNING_NOTCL] GERBER file might be CORRUPT. Check the file !!!"))
|
||||
|
||||
elif current_operation_code == 2:
|
||||
if len(path) > 1:
|
||||
@@ -2675,7 +2680,7 @@ class Gerber (Geometry):
|
||||
geo = Polygon(path)
|
||||
except ValueError:
|
||||
log.warning("Problem %s %s" % (gline, line_num))
|
||||
self.app.inform.emit(_tr("[ERROR] Region does not have enough points. "
|
||||
self.app.inform.emit(_("[ERROR] Region does not have enough points. "
|
||||
"File will be processed but there are parser errors. "
|
||||
"Line number: %s") % str(line_num))
|
||||
else:
|
||||
@@ -2705,8 +2710,8 @@ class Gerber (Geometry):
|
||||
if linear_x is not None and linear_y is not None:
|
||||
path = [[linear_x, linear_y]] # Start new path
|
||||
else:
|
||||
self.app.inform.emit(_tr("[WARNING] Coordinates missing, line ignored: %s") % str(gline))
|
||||
self.app.inform.emit(_tr("[WARNING_NOTCL] GERBER file might be CORRUPT. Check the file !!!"))
|
||||
self.app.inform.emit(_("[WARNING] Coordinates missing, line ignored: %s") % str(gline))
|
||||
self.app.inform.emit(_("[WARNING_NOTCL] GERBER file might be CORRUPT. Check the file !!!"))
|
||||
|
||||
# Flash
|
||||
# Not allowed in region mode.
|
||||
@@ -3029,7 +3034,7 @@ class Gerber (Geometry):
|
||||
|
||||
log.error("Gerber PARSING FAILED. Line %d: %s" % (line_num, gline))
|
||||
loc = 'Gerber Line #%d Gerber Line Content: %s\n' % (line_num, gline) + repr(err)
|
||||
self.app.inform.emit(_tr("[ERROR]Gerber Parser ERROR.\n%s:") % loc)
|
||||
self.app.inform.emit(_("[ERROR]Gerber Parser ERROR.\n%s:") % loc)
|
||||
|
||||
@staticmethod
|
||||
def create_flash_geometry(location, aperture, steps_per_circle=None):
|
||||
@@ -3207,7 +3212,7 @@ class Gerber (Geometry):
|
||||
try:
|
||||
xfactor = float(xfactor)
|
||||
except:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Scale factor has to be a number: integer or float."))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Scale factor has to be a number: integer or float."))
|
||||
return
|
||||
|
||||
if yfactor is None:
|
||||
@@ -3216,7 +3221,7 @@ class Gerber (Geometry):
|
||||
try:
|
||||
yfactor = float(yfactor)
|
||||
except:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Scale factor has to be a number: integer or float."))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Scale factor has to be a number: integer or float."))
|
||||
return
|
||||
|
||||
if point is None:
|
||||
@@ -3245,7 +3250,7 @@ class Gerber (Geometry):
|
||||
except Exception as e:
|
||||
log.debug('FlatCAMGeometry.scale() --> %s' % str(e))
|
||||
|
||||
self.app.inform.emit(_tr("[success]Gerber Scale done."))
|
||||
self.app.inform.emit(_("[success]Gerber Scale done."))
|
||||
|
||||
|
||||
## solid_geometry ???
|
||||
@@ -3278,7 +3283,7 @@ class Gerber (Geometry):
|
||||
try:
|
||||
dx, dy = vect
|
||||
except TypeError:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL]An (x,y) pair of values are needed. "
|
||||
self.app.inform.emit(_("[ERROR_NOTCL]An (x,y) pair of values are needed. "
|
||||
"Probable you entered only one value in the Offset field."))
|
||||
return
|
||||
|
||||
@@ -3302,7 +3307,7 @@ class Gerber (Geometry):
|
||||
except Exception as e:
|
||||
log.debug('FlatCAMGeometry.offset() --> %s' % str(e))
|
||||
|
||||
self.app.inform.emit(_tr("[success]Gerber Offset done."))
|
||||
self.app.inform.emit(_("[success]Gerber Offset done."))
|
||||
|
||||
def mirror(self, axis, point):
|
||||
"""
|
||||
@@ -3678,7 +3683,7 @@ class Excellon(Geometry):
|
||||
# and we need to exit from here
|
||||
if self.detect_gcode_re.search(eline):
|
||||
log.warning("This is GCODE mark: %s" % eline)
|
||||
self.app.inform.emit(_tr('[ERROR_NOTCL] This is GCODE mark: %s') % eline)
|
||||
self.app.inform.emit(_('[ERROR_NOTCL] This is GCODE mark: %s') % eline)
|
||||
return
|
||||
|
||||
# Header Begin (M48) #
|
||||
@@ -4207,8 +4212,8 @@ class Excellon(Geometry):
|
||||
log.info("Zeros: %s, Units %s." % (self.zeros, self.units))
|
||||
except Exception as e:
|
||||
log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline))
|
||||
msg = _tr("[ERROR_NOTCL] An internal error has ocurred. See shell.\n")
|
||||
msg += _tr('[ERROR] Excellon Parser error.\nParsing Failed. Line %d: %s\n') % (line_num, eline)
|
||||
msg = _("[ERROR_NOTCL] An internal error has ocurred. See shell.\n")
|
||||
msg += _('[ERROR] Excellon Parser error.\nParsing Failed. Line %d: %s\n') % (line_num, eline)
|
||||
msg += traceback.format_exc()
|
||||
self.app.inform.emit(msg)
|
||||
|
||||
@@ -4285,7 +4290,7 @@ class Excellon(Geometry):
|
||||
for drill in self.drills:
|
||||
# poly = drill['point'].buffer(self.tools[drill['tool']]["C"]/2.0)
|
||||
if drill['tool'] is '':
|
||||
self.app.inform.emit(_tr("[WARNING] Excellon.create_geometry() -> a drill location was skipped "
|
||||
self.app.inform.emit(_("[WARNING] Excellon.create_geometry() -> a drill location was skipped "
|
||||
"due of not having a tool associated.\n"
|
||||
"Check the resulting GCode."))
|
||||
log.debug("Excellon.create_geometry() -> a drill location was skipped "
|
||||
@@ -4821,7 +4826,7 @@ class CNCjob(Geometry):
|
||||
try:
|
||||
value = getattr(self, command)
|
||||
except AttributeError:
|
||||
self.app.inform.emit(_tr("[ERROR] There is no such parameter: %s") % str(match))
|
||||
self.app.inform.emit(_("[ERROR] There is no such parameter: %s") % str(match))
|
||||
log.debug("CNCJob.parse_custom_toolchange_code() --> AttributeError ")
|
||||
return 'fail'
|
||||
text = text.replace(match, str(value))
|
||||
@@ -4886,14 +4891,14 @@ class CNCjob(Geometry):
|
||||
:rtype: None
|
||||
"""
|
||||
if drillz > 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Cut Z parameter has positive value. "
|
||||
self.app.inform.emit(_("[WARNING] The Cut Z parameter has positive value. "
|
||||
"It is the depth value to drill into material.\n"
|
||||
"The Cut Z parameter needs to have a negative value, assuming it is a typo "
|
||||
"therefore the app will convert the value to negative. "
|
||||
"Check the resulting CNC code (Gcode etc)."))
|
||||
self.z_cut = -drillz
|
||||
elif drillz == 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Cut Z parameter is zero. "
|
||||
self.app.inform.emit(_("[WARNING] The Cut Z parameter is zero. "
|
||||
"There will be no cut, skipping %s file") % exobj.options['name'])
|
||||
return 'fail'
|
||||
else:
|
||||
@@ -4907,7 +4912,7 @@ class CNCjob(Geometry):
|
||||
else:
|
||||
self.xy_toolchange = [float(eval(a)) for a in toolchangexy.split(",")]
|
||||
if len(self.xy_toolchange) < 2:
|
||||
self.app.inform.emit(_tr("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
|
||||
self.app.inform.emit(_("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
|
||||
"in the format (x, y) \nbut now there is only one value, not two. "))
|
||||
return 'fail'
|
||||
except Exception as e:
|
||||
@@ -5109,7 +5114,7 @@ class CNCjob(Geometry):
|
||||
else:
|
||||
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
|
||||
"The loaded Excellon file has no drills ...")
|
||||
self.app.inform.emit(_tr('[ERROR_NOTCL]The loaded Excellon file has no drills ...'))
|
||||
self.app.inform.emit(_('[ERROR_NOTCL]The loaded Excellon file has no drills ...'))
|
||||
return 'fail'
|
||||
|
||||
log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
|
||||
@@ -5199,12 +5204,12 @@ class CNCjob(Geometry):
|
||||
else:
|
||||
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
|
||||
"The loaded Excellon file has no drills ...")
|
||||
self.app.inform.emit(_tr('[ERROR_NOTCL]The loaded Excellon file has no drills ...'))
|
||||
self.app.inform.emit(_('[ERROR_NOTCL]The loaded Excellon file has no drills ...'))
|
||||
return 'fail'
|
||||
|
||||
log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
|
||||
else:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Wrong optimization type selected."))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Wrong optimization type selected."))
|
||||
return 'fail'
|
||||
else:
|
||||
log.debug("Using Travelling Salesman drill path optimization.")
|
||||
@@ -5250,7 +5255,7 @@ class CNCjob(Geometry):
|
||||
else:
|
||||
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
|
||||
"The loaded Excellon file has no drills ...")
|
||||
self.app.inform.emit(_tr('[ERROR_NOTCL]The loaded Excellon file has no drills ...'))
|
||||
self.app.inform.emit(_('[ERROR_NOTCL]The loaded Excellon file has no drills ...'))
|
||||
return 'fail'
|
||||
log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
|
||||
|
||||
@@ -5338,7 +5343,7 @@ class CNCjob(Geometry):
|
||||
else:
|
||||
self.xy_toolchange = [float(eval(a)) for a in toolchangexy.split(",")]
|
||||
if len(self.xy_toolchange) < 2:
|
||||
self.app.inform.emit(_tr("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
|
||||
self.app.inform.emit(_("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
|
||||
"in the format (x, y) \nbut now there is only one value, not two. "))
|
||||
return 'fail'
|
||||
except Exception as e:
|
||||
@@ -5349,35 +5354,35 @@ class CNCjob(Geometry):
|
||||
self.f_plunge = self.app.defaults["geometry_f_plunge"]
|
||||
|
||||
if self.z_cut is None:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Cut_Z parameter is None or zero. Most likely a bad combinations of "
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Cut_Z parameter is None or zero. Most likely a bad combinations of "
|
||||
"other parameters."))
|
||||
return 'fail'
|
||||
|
||||
if self.z_cut > 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Cut Z parameter has positive value. "
|
||||
self.app.inform.emit(_("[WARNING] The Cut Z parameter has positive value. "
|
||||
"It is the depth value to cut into material.\n"
|
||||
"The Cut Z parameter needs to have a negative value, assuming it is a typo "
|
||||
"therefore the app will convert the value to negative."
|
||||
"Check the resulting CNC code (Gcode etc)."))
|
||||
self.z_cut = -self.z_cut
|
||||
elif self.z_cut == 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Cut Z parameter is zero. "
|
||||
self.app.inform.emit(_("[WARNING] The Cut Z parameter is zero. "
|
||||
"There will be no cut, skipping %s file") % self.options['name'])
|
||||
return 'fail'
|
||||
|
||||
if self.z_move is None:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Travel Z parameter is None or zero."))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Travel Z parameter is None or zero."))
|
||||
return 'fail'
|
||||
|
||||
if self.z_move < 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Travel Z parameter has negative value. "
|
||||
self.app.inform.emit(_("[WARNING] The Travel Z parameter has negative value. "
|
||||
"It is the height value to travel between cuts.\n"
|
||||
"The Z Travel parameter needs to have a positive value, assuming it is a typo "
|
||||
"therefore the app will convert the value to positive."
|
||||
"Check the resulting CNC code (Gcode etc)."))
|
||||
self.z_move = -self.z_move
|
||||
elif self.z_move == 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Z Travel parameter is zero. "
|
||||
self.app.inform.emit(_("[WARNING] The Z Travel parameter is zero. "
|
||||
"This is dangerous, skipping %s file") % self.options['name'])
|
||||
return 'fail'
|
||||
|
||||
@@ -5507,13 +5512,13 @@ class CNCjob(Geometry):
|
||||
"""
|
||||
|
||||
if not isinstance(geometry, Geometry):
|
||||
self.app.inform.emit(_tr("[ERROR]Expected a Geometry, got %s") % type(geometry))
|
||||
self.app.inform.emit(_("[ERROR]Expected a Geometry, got %s") % type(geometry))
|
||||
return 'fail'
|
||||
log.debug("Generate_from_geometry_2()")
|
||||
|
||||
# if solid_geometry is empty raise an exception
|
||||
if not geometry.solid_geometry:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL]Trying to generate a CNC Job "
|
||||
self.app.inform.emit(_("[ERROR_NOTCL]Trying to generate a CNC Job "
|
||||
"from a Geometry object without solid_geometry."))
|
||||
|
||||
temp_solid_geometry = []
|
||||
@@ -5552,7 +5557,7 @@ class CNCjob(Geometry):
|
||||
# if the offset is less than half of the total length or less than half of the total width of the
|
||||
# solid geometry it's obvious we can't do the offset
|
||||
if -offset > ((c - a) / 2) or -offset > ((d - b) / 2):
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL]The Tool Offset value is too negative to use "
|
||||
self.app.inform.emit(_("[ERROR_NOTCL]The Tool Offset value is too negative to use "
|
||||
"for the current_geometry.\n"
|
||||
"Raise the value (in module) and try again."))
|
||||
return 'fail'
|
||||
@@ -5609,7 +5614,7 @@ class CNCjob(Geometry):
|
||||
else:
|
||||
self.xy_toolchange = [float(eval(a)) for a in toolchangexy.split(",")]
|
||||
if len(self.xy_toolchange) < 2:
|
||||
self.app.inform.emit(_tr("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
|
||||
self.app.inform.emit(_("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
|
||||
"in the format (x, y) \nbut now there is only one value, not two. "))
|
||||
return 'fail'
|
||||
except Exception as e:
|
||||
@@ -5620,35 +5625,35 @@ class CNCjob(Geometry):
|
||||
self.f_plunge = self.app.defaults["geometry_f_plunge"]
|
||||
|
||||
if self.z_cut is None:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Cut_Z parameter is None or zero. Most likely a bad combinations of "
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Cut_Z parameter is None or zero. Most likely a bad combinations of "
|
||||
"other parameters."))
|
||||
return 'fail'
|
||||
|
||||
if self.z_cut > 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Cut Z parameter has positive value. "
|
||||
self.app.inform.emit(_("[WARNING] The Cut Z parameter has positive value. "
|
||||
"It is the depth value to cut into material.\n"
|
||||
"The Cut Z parameter needs to have a negative value, assuming it is a typo "
|
||||
"therefore the app will convert the value to negative."
|
||||
"Check the resulting CNC code (Gcode etc)."))
|
||||
self.z_cut = -self.z_cut
|
||||
elif self.z_cut == 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Cut Z parameter is zero. "
|
||||
self.app.inform.emit(_("[WARNING] The Cut Z parameter is zero. "
|
||||
"There will be no cut, skipping %s file") % geometry.options['name'])
|
||||
return 'fail'
|
||||
|
||||
if self.z_move is None:
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] Travel Z parameter is None or zero."))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] Travel Z parameter is None or zero."))
|
||||
return 'fail'
|
||||
|
||||
if self.z_move < 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Travel Z parameter has negative value. "
|
||||
self.app.inform.emit(_("[WARNING] The Travel Z parameter has negative value. "
|
||||
"It is the height value to travel between cuts.\n"
|
||||
"The Z Travel parameter needs to have a positive value, assuming it is a typo "
|
||||
"therefore the app will convert the value to positive."
|
||||
"Check the resulting CNC code (Gcode etc)."))
|
||||
self.z_move = -self.z_move
|
||||
elif self.z_move == 0:
|
||||
self.app.inform.emit(_tr("[WARNING] The Z Travel parameter is zero. "
|
||||
self.app.inform.emit(_("[WARNING] The Z Travel parameter is zero. "
|
||||
"This is dangerous, skipping %s file") % self.options['name'])
|
||||
return 'fail'
|
||||
|
||||
@@ -5774,7 +5779,7 @@ class CNCjob(Geometry):
|
||||
|
||||
if not kwargs:
|
||||
log.debug("camlib.generate_from_solderpaste_geo() --> No tool in the solderpaste geometry.")
|
||||
self.app.inform.emit(_tr("[ERROR_NOTCL] There is no tool data in the SolderPaste geometry."))
|
||||
self.app.inform.emit(_("[ERROR_NOTCL] There is no tool data in the SolderPaste geometry."))
|
||||
|
||||
|
||||
# this is the tool diameter, it is used as such to accommodate the postprocessor who need the tool diameter
|
||||
|
||||
Reference in New Issue
Block a user