Refactored the ComponentPreset.create method into the ComponentPresetFactory object to make code maintenance easier. Added support for Bulk heads.
This commit is contained in:
parent
f69ed1ea2f
commit
d1c83cb9b4
31
core/resources/datafiles/presets/semroc/bulkheadpresets.csv
Normal file
31
core/resources/datafiles/presets/semroc/bulkheadpresets.csv
Normal file
@ -0,0 +1,31 @@
|
||||
Manufacturer,PartNo,Description,Type,OuterDiameter,Length,Material,Mass
|
||||
Semroc,BTC-5,Solid Balsa Tube Adapter - Series 5,BULK_HEAD,0.013081,0.01905,Balsa,0.000283495
|
||||
Semroc,BTC-7,Solid Balsa Tube Adapter - Series 7,BULK_HEAD,0.018161,0.0254,Balsa,0.000737087
|
||||
Semroc,BTC-8,Solid Balsa Tube Adapter - Series 8,BULK_HEAD,0.021971,0.0254,Balsa,0.001077281
|
||||
Semroc,BTC-8F,Solid Balsa Tube Adapter - Series 8F,BULK_HEAD,0.022479,0.0254,Balsa,0.001077281
|
||||
Semroc,BTC-9,Solid Balsa Tube Adapter - Series 9,BULK_HEAD,0.02413,0.0381,Balsa,0.001275727
|
||||
Semroc,BTC-10,Solid Balsa Tube Adapter - Series 10,BULK_HEAD,0.0254,0.0381,Balsa,0.001814368
|
||||
Semroc,BTC-11,Solid Balsa Tube Adapter - Series 11,BULK_HEAD,0.028702,0.0381,Balsa,0.001956115
|
||||
Semroc,BTC-13,Solid Balsa Tube Adapter - Series 13,BULK_HEAD,0.03302,0.04445,Balsa,0.003543687
|
||||
Semroc,BTC-16,Solid Balsa Tube Adapter - Series 16,BULK_HEAD,0.04064,0.04445,Balsa,0.004961161
|
||||
Semroc,BTC-18,Solid Balsa Tube Adapter - Series 18,BULK_HEAD,0.04572,0.04445,Balsa,0.005669899
|
||||
Semroc,BTC-20,Solid Balsa Tube Adapter - Series 20,BULK_HEAD,0.0508,0.05334,Balsa,0.007512616
|
||||
Semroc,BTC-085,Solid Balsa Tube Adapter - Series 85,BULK_HEAD,0.021971,0.0381,Balsa,0.001615921
|
||||
Semroc,BTC-115,Solid Balsa Tube Adapter - Series 115,BULK_HEAD,0.02921,0.04445,Balsa,0.003175143
|
||||
Semroc,BTC-125,Solid Balsa Tube Adapter - Series 125,BULK_HEAD,0.03175,0.0508,Balsa,0.004535919
|
||||
Semroc,BTC-150,Solid Balsa Tube Adapter - Series 150,BULK_HEAD,0.0381,0.0508,Balsa,0.006662131
|
||||
Semroc,BTC-175,Solid Balsa Tube Adapter - Series 175,BULK_HEAD,0.04445,0.05715,Balsa,0.00949708
|
||||
Semroc,BTC-225,Solid Balsa Tube Adapter - Series 225,BULK_HEAD,0.05715,0.0635,Balsa,0.018001928
|
||||
Semroc,BTC-275,Solid Balsa Tube Adapter - Series 275,BULK_HEAD,0.06985,0.0762,Balsa,0.032176674
|
||||
Semroc,NB-3,Solid Balsa Tube Adapter - Series BT-3,BULK_HEAD,0.0088646,0.01905,Balsa,0.000198446
|
||||
Semroc,NB-5,Solid Balsa Tube Adapter - Series BT-5,BULK_HEAD,0.013081,0.01905,Balsa,0.000283495
|
||||
Semroc,NB-20,Solid Balsa Tube Adapter - Series BT-20,BULK_HEAD,0.018034,0.01905,Balsa,0.00056699
|
||||
Semroc,NB-30,Solid Balsa Tube Adapter - Series BT-30,BULK_HEAD,0.018415,0.01905,Balsa,0.00056699
|
||||
Semroc,NB-40,Solid Balsa Tube Adapter - Series BT-40,BULK_HEAD,0.019431,0.0254,Balsa,0.000793786
|
||||
Semroc,NB-50,Solid Balsa Tube Adapter - Series BT-50,BULK_HEAD,0.02413,0.0254,Balsa,0.00113398
|
||||
Semroc,NB-50L,Solid Balsa Tube Adapter - Series BT-50,BULK_HEAD,0.02413,0.030226,Balsa,0.002182911
|
||||
Semroc,NB-55,Solid Balsa Tube Adapter - Series BT-55,BULK_HEAD,0.0325882,0.03175,Balsa,0.003260192
|
||||
Semroc,NB-60,Solid Balsa Tube Adapter - Series BT-60,BULK_HEAD,0.040513,0.0381,Balsa,0.004819414
|
||||
Semroc,NB-65,Solid Balsa Tube Adapter - Series BT-65,BULK_HEAD,0.04445,0.04445,Balsa,0.005953393
|
||||
Semroc,NB-70,Solid Balsa Tube Adapter - Series BT-70,BULK_HEAD,0.055245,0.0508,Balsa,0.006236888
|
||||
Semroc,NB-80,Solid Balsa Tube Adapter - Series BT-80,BULK_HEAD,0.0649732,0.0762,Balsa,0.010489312
|
|
@ -12,6 +12,7 @@ import net.sf.openrocket.file.Loader;
|
||||
import net.sf.openrocket.file.preset.PresetCSVReader;
|
||||
import net.sf.openrocket.logging.LogHelper;
|
||||
import net.sf.openrocket.preset.ComponentPreset;
|
||||
import net.sf.openrocket.preset.ComponentPresetFactory;
|
||||
import net.sf.openrocket.preset.InvalidComponentPresetException;
|
||||
import net.sf.openrocket.preset.TypedPropertyMap;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
@ -37,7 +38,7 @@ public class ComponentPresetDatabase extends Database<ComponentPreset> implement
|
||||
List<TypedPropertyMap> list = parser.parse();
|
||||
for( TypedPropertyMap o : list ) {
|
||||
try {
|
||||
ComponentPreset preset = ComponentPreset.create(o);
|
||||
ComponentPreset preset = ComponentPresetFactory.create(o);
|
||||
if ( favorites.contains(preset.preferenceKey())) {
|
||||
preset.setFavorite(true);
|
||||
}
|
||||
|
@ -61,8 +61,14 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
|
||||
ComponentPreset.SHAPE,
|
||||
ComponentPreset.FORE_OUTER_DIAMETER,
|
||||
ComponentPreset.OUTER_DIAMETER,
|
||||
ComponentPreset.LENGTH
|
||||
} ) ;
|
||||
ComponentPreset.LENGTH} ),
|
||||
|
||||
BULK_HEAD( new TypedKey<?>[] {
|
||||
ComponentPreset.MANUFACTURER,
|
||||
ComponentPreset.PARTNO,
|
||||
ComponentPreset.DESCRIPTION,
|
||||
ComponentPreset.OUTER_DIAMETER,
|
||||
ComponentPreset.LENGTH} );
|
||||
|
||||
Type[] compatibleTypes;
|
||||
TypedKey<?>[] displayedColumns;
|
||||
@ -135,150 +141,8 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
|
||||
keyMap.put(MASS.getName(), MASS);
|
||||
}
|
||||
|
||||
public static ComponentPreset create( TypedPropertyMap props ) throws InvalidComponentPresetException {
|
||||
|
||||
ComponentPreset preset = new ComponentPreset();
|
||||
// First do validation.
|
||||
if ( !props.containsKey(TYPE)) {
|
||||
throw new InvalidComponentPresetException("No Type specified " + props.toString() );
|
||||
}
|
||||
|
||||
if (!props.containsKey(MANUFACTURER)) {
|
||||
throw new InvalidComponentPresetException("No Manufacturer specified " + props.toString() );
|
||||
}
|
||||
|
||||
if (!props.containsKey(PARTNO)) {
|
||||
throw new InvalidComponentPresetException("No PartNo specified " + props.toString() );
|
||||
}
|
||||
|
||||
preset.properties.putAll(props);
|
||||
|
||||
// Should check for various bits of each of the types.
|
||||
Type t = props.get(TYPE);
|
||||
switch ( t ) {
|
||||
case BODY_TUBE: {
|
||||
|
||||
if ( !props.containsKey(LENGTH) ) {
|
||||
throw new InvalidComponentPresetException( "No Length specified for body tube preset " + props.toString());
|
||||
}
|
||||
|
||||
BodyTube bt = new BodyTube();
|
||||
|
||||
bt.setLength(props.get(LENGTH));
|
||||
|
||||
// Need to verify contains 2 of OD, thickness, ID. Compute the third.
|
||||
boolean hasOd = props.containsKey(OUTER_DIAMETER);
|
||||
boolean hasId = props.containsKey(INNER_DIAMETER);
|
||||
boolean hasThickness = props.containsKey(THICKNESS);
|
||||
|
||||
if ( hasOd ) {
|
||||
double outerRadius = props.get(OUTER_DIAMETER)/2.0;
|
||||
double thickness = 0;
|
||||
bt.setOuterRadius( outerRadius );
|
||||
if ( hasId ) {
|
||||
thickness = outerRadius - props.get(INNER_DIAMETER)/2.0;
|
||||
} else if ( hasThickness ) {
|
||||
thickness = props.get(THICKNESS);
|
||||
} else {
|
||||
throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
|
||||
}
|
||||
bt.setThickness( thickness );
|
||||
} else {
|
||||
if ( ! hasId && ! hasThickness ) {
|
||||
throw new InvalidComponentPresetException("Body tube preset underspecified " + props.toString());
|
||||
}
|
||||
double innerRadius = props.get(INNER_DIAMETER)/2.0;
|
||||
double thickness = props.get(THICKNESS);
|
||||
bt.setOuterRadius(innerRadius + thickness);
|
||||
bt.setThickness(thickness);
|
||||
}
|
||||
|
||||
preset.properties.put(OUTER_DIAMETER, bt.getOuterRadius() *2.0);
|
||||
preset.properties.put(INNER_DIAMETER, bt.getInnerRadius() *2.0);
|
||||
preset.properties.put(THICKNESS, bt.getThickness());
|
||||
|
||||
// Need to translate Mass to Density.
|
||||
if ( props.containsKey(MASS) ) {
|
||||
String materialName = "TubeCustom";
|
||||
if ( props.containsKey(MATERIAL) ) {
|
||||
materialName = props.get(MATERIAL).getName();
|
||||
}
|
||||
Material m = Material.newMaterial(Material.Type.BULK, materialName, props.get(MASS)/bt.getComponentVolume(), false);
|
||||
preset.properties.put(MATERIAL, m);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case NOSE_CONE: {
|
||||
|
||||
if ( !props.containsKey(LENGTH) ) {
|
||||
throw new InvalidComponentPresetException( "No Length specified for nose cone preset " + props.toString());
|
||||
}
|
||||
if ( !props.containsKey(SHAPE) ) {
|
||||
throw new InvalidComponentPresetException( "No Shape specified for nose cone preset " + props.toString());
|
||||
}
|
||||
if ( !props.containsKey(OUTER_DIAMETER) ) {
|
||||
throw new InvalidComponentPresetException( "No Outer Diameter specified for nose cone preset " + props.toString());
|
||||
}
|
||||
|
||||
if ( props.containsKey(MASS) ) {
|
||||
// compute a density for this component
|
||||
double mass = props.get(MASS);
|
||||
NoseCone nc = new NoseCone();
|
||||
nc.loadPreset(preset);
|
||||
double density = mass / nc.getComponentVolume();
|
||||
|
||||
String materialName = "NoseConeCustom";
|
||||
if ( props.containsKey(MATERIAL) ) {
|
||||
materialName = props.get(MATERIAL).getName();
|
||||
}
|
||||
|
||||
Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
|
||||
preset.properties.put(MATERIAL, m);
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TRANSITION: {
|
||||
|
||||
if ( !props.containsKey(LENGTH) ) {
|
||||
throw new InvalidComponentPresetException( "No Length specified for transition preset " + props.toString());
|
||||
}
|
||||
if ( !props.containsKey(OUTER_DIAMETER) ) {
|
||||
throw new InvalidComponentPresetException( "No Outer Diameter specified for transition preset " + props.toString());
|
||||
}
|
||||
if ( !props.containsKey(FORE_OUTER_DIAMETER) ) {
|
||||
throw new InvalidComponentPresetException( "No Fore Outer Diameter specified for transition preset " + props.toString());
|
||||
}
|
||||
|
||||
if ( props.containsKey(MASS) ) {
|
||||
// compute a density for this component
|
||||
double mass = props.get(MASS);
|
||||
Transition tr = new Transition();
|
||||
tr.loadPreset(preset);
|
||||
double density = mass / tr.getComponentVolume();
|
||||
|
||||
String materialName = "TransitionCustom";
|
||||
if ( props.containsKey(MATERIAL) ) {
|
||||
materialName = props.get(MATERIAL).getName();
|
||||
}
|
||||
|
||||
Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
|
||||
preset.properties.put(MATERIAL, m);
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
preset.computeDigest();
|
||||
|
||||
return preset;
|
||||
|
||||
}
|
||||
|
||||
// Private constructor to encourage use of factory.
|
||||
private ComponentPreset() {
|
||||
// package scope constructor to encourage use of factory.
|
||||
ComponentPreset() {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,6 +178,26 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
|
||||
return properties.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Package scope so the ComponentPresetFactory can call it.
|
||||
* @param other
|
||||
*/
|
||||
void putAll(TypedPropertyMap other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
properties.putAll(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Package scope so the ComponentPresetFactory can call it.
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
<T> void put( TypedKey<T> key, T value ) {
|
||||
properties.put(key, value);
|
||||
}
|
||||
|
||||
public <T> T get(TypedKey<T> key) {
|
||||
T value = properties.get(key);
|
||||
if (value == null) {
|
||||
@ -349,7 +233,10 @@ public class ComponentPreset implements Comparable<ComponentPreset> {
|
||||
return get(MANUFACTURER).toString() + "|" + get(PARTNO);
|
||||
}
|
||||
|
||||
private void computeDigest() {
|
||||
/**
|
||||
* Package scope so the factory can call it.
|
||||
*/
|
||||
void computeDigest() {
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
199
core/src/net/sf/openrocket/preset/ComponentPresetFactory.java
Normal file
199
core/src/net/sf/openrocket/preset/ComponentPresetFactory.java
Normal file
@ -0,0 +1,199 @@
|
||||
package net.sf.openrocket.preset;
|
||||
|
||||
import static net.sf.openrocket.preset.ComponentPreset.FORE_OUTER_DIAMETER;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.INNER_DIAMETER;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.LENGTH;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.MANUFACTURER;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.MASS;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.MATERIAL;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.OUTER_DIAMETER;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.PARTNO;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.SHAPE;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.THICKNESS;
|
||||
import static net.sf.openrocket.preset.ComponentPreset.TYPE;
|
||||
import net.sf.openrocket.material.Material;
|
||||
import net.sf.openrocket.preset.ComponentPreset.Type;
|
||||
import net.sf.openrocket.rocketcomponent.BodyTube;
|
||||
import net.sf.openrocket.rocketcomponent.Bulkhead;
|
||||
import net.sf.openrocket.rocketcomponent.NoseCone;
|
||||
import net.sf.openrocket.rocketcomponent.Transition;
|
||||
|
||||
public abstract class ComponentPresetFactory {
|
||||
|
||||
public static ComponentPreset create( TypedPropertyMap props ) throws InvalidComponentPresetException {
|
||||
|
||||
ComponentPreset preset = new ComponentPreset();
|
||||
// First do validation.
|
||||
if ( !props.containsKey(TYPE)) {
|
||||
throw new InvalidComponentPresetException("No Type specified " + props.toString() );
|
||||
}
|
||||
|
||||
if (!props.containsKey(MANUFACTURER)) {
|
||||
throw new InvalidComponentPresetException("No Manufacturer specified " + props.toString() );
|
||||
}
|
||||
|
||||
if (!props.containsKey(PARTNO)) {
|
||||
throw new InvalidComponentPresetException("No PartNo specified " + props.toString() );
|
||||
}
|
||||
|
||||
preset.putAll(props);
|
||||
|
||||
// Should check for various bits of each of the types.
|
||||
Type t = props.get(TYPE);
|
||||
switch ( t ) {
|
||||
case BODY_TUBE: {
|
||||
makeBodyTube(preset);
|
||||
break;
|
||||
}
|
||||
case NOSE_CONE: {
|
||||
makeNoseCone(preset);
|
||||
break;
|
||||
}
|
||||
case TRANSITION: {
|
||||
makeTransition(preset);
|
||||
break;
|
||||
}
|
||||
case BULK_HEAD: {
|
||||
makeBulkHead(preset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
preset.computeDigest();
|
||||
|
||||
return preset;
|
||||
|
||||
}
|
||||
|
||||
private static void makeBodyTube( ComponentPreset preset ) throws InvalidComponentPresetException {
|
||||
|
||||
checkRequiredFields( preset, LENGTH );
|
||||
|
||||
BodyTube bt = new BodyTube();
|
||||
|
||||
bt.setLength(preset.get(LENGTH));
|
||||
|
||||
// Need to verify contains 2 of OD, thickness, ID. Compute the third.
|
||||
boolean hasOd = preset.has(OUTER_DIAMETER);
|
||||
boolean hasId = preset.has(INNER_DIAMETER);
|
||||
boolean hasThickness = preset.has(THICKNESS);
|
||||
|
||||
if ( hasOd ) {
|
||||
double outerRadius = preset.get(OUTER_DIAMETER)/2.0;
|
||||
double thickness = 0;
|
||||
bt.setOuterRadius( outerRadius );
|
||||
if ( hasId ) {
|
||||
thickness = outerRadius - preset.get(INNER_DIAMETER)/2.0;
|
||||
} else if ( hasThickness ) {
|
||||
thickness = preset.get(THICKNESS);
|
||||
} else {
|
||||
throw new InvalidComponentPresetException("Body tube preset underspecified " + preset.toString());
|
||||
}
|
||||
bt.setThickness( thickness );
|
||||
} else {
|
||||
if ( ! hasId && ! hasThickness ) {
|
||||
throw new InvalidComponentPresetException("Body tube preset underspecified " + preset.toString());
|
||||
}
|
||||
double innerRadius = preset.get(INNER_DIAMETER)/2.0;
|
||||
double thickness = preset.get(THICKNESS);
|
||||
bt.setOuterRadius(innerRadius + thickness);
|
||||
bt.setThickness(thickness);
|
||||
}
|
||||
|
||||
preset.put(OUTER_DIAMETER, bt.getOuterRadius() *2.0);
|
||||
preset.put(INNER_DIAMETER, bt.getInnerRadius() *2.0);
|
||||
preset.put(THICKNESS, bt.getThickness());
|
||||
|
||||
// Need to translate Mass to Density.
|
||||
if ( preset.has(MASS) ) {
|
||||
String materialName = "TubeCustom";
|
||||
if ( preset.has(MATERIAL) ) {
|
||||
materialName = preset.get(MATERIAL).getName();
|
||||
}
|
||||
Material m = Material.newMaterial(Material.Type.BULK, materialName, preset.get(MASS)/bt.getComponentVolume(), false);
|
||||
preset.put(MATERIAL, m);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static void makeNoseCone( ComponentPreset preset ) throws InvalidComponentPresetException {
|
||||
|
||||
checkRequiredFields( preset, LENGTH, SHAPE, OUTER_DIAMETER );
|
||||
|
||||
if ( preset.has(MASS) ) {
|
||||
// compute a density for this component
|
||||
double mass = preset.get(MASS);
|
||||
NoseCone nc = new NoseCone();
|
||||
nc.loadPreset(preset);
|
||||
double density = mass / nc.getComponentVolume();
|
||||
|
||||
String materialName = "NoseConeCustom";
|
||||
if ( preset.has(MATERIAL) ) {
|
||||
materialName = preset.get(MATERIAL).getName();
|
||||
}
|
||||
|
||||
Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
|
||||
preset.put(MATERIAL, m);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void makeTransition( ComponentPreset preset ) throws InvalidComponentPresetException {
|
||||
checkRequiredFields(preset, LENGTH, OUTER_DIAMETER, FORE_OUTER_DIAMETER);
|
||||
|
||||
if ( preset.has(MASS) ) {
|
||||
// compute a density for this component
|
||||
double mass = preset.get(MASS);
|
||||
Transition tr = new Transition();
|
||||
tr.loadPreset(preset);
|
||||
double density = mass / tr.getComponentVolume();
|
||||
|
||||
String materialName = "TransitionCustom";
|
||||
if ( preset.has(MATERIAL) ) {
|
||||
materialName = preset.get(MATERIAL).getName();
|
||||
}
|
||||
|
||||
Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
|
||||
preset.put(MATERIAL, m);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void makeBulkHead( ComponentPreset preset ) throws InvalidComponentPresetException {
|
||||
checkRequiredFields(preset, LENGTH, OUTER_DIAMETER );
|
||||
|
||||
if ( preset.has(MASS) ) {
|
||||
// compute a density for this component
|
||||
double mass = preset.get(MASS);
|
||||
Bulkhead tr = new Bulkhead();
|
||||
tr.loadPreset(preset);
|
||||
// FIXME - Bulkhead.getComponentVolume does not exist!
|
||||
// double density = mass / tr.getComponentVolume();
|
||||
|
||||
double volume = Math.pow(preset.get(OUTER_DIAMETER),2) * Math.PI / 4.0;
|
||||
double density = mass / volume;
|
||||
|
||||
String materialName = "TransitionCustom";
|
||||
if ( preset.has(MATERIAL) ) {
|
||||
materialName = preset.get(MATERIAL).getName();
|
||||
}
|
||||
|
||||
Material m = Material.newMaterial(Material.Type.BULK, materialName,density, false);
|
||||
preset.put(MATERIAL, m);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void checkRequiredFields( ComponentPreset preset, TypedKey<?> ... keys ) throws InvalidComponentPresetException {
|
||||
for( TypedKey<?> key: keys ) {
|
||||
if (! preset.has(key) ) {
|
||||
throw new InvalidComponentPresetException( "No " + key.getName() + " specified for " + preset.getType().name() + " preset " + preset.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user