mirror of
https://github.com/rsheldiii/KeyV2.git
synced 2026-01-15 06:47:54 -05:00
Compare commits
15 Commits
v2.1.0
...
v2/autoleg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e955331e8d | ||
|
|
1a10d1e5bc | ||
|
|
41381ed376 | ||
|
|
08f17a4e1f | ||
|
|
4766e3eca6 | ||
|
|
19cdb2d9ae | ||
|
|
75cfa2a856 | ||
|
|
4ba88df064 | ||
|
|
315bc83039 | ||
|
|
077de5ac87 | ||
|
|
38bfbfa61c | ||
|
|
7ee9e61412 | ||
|
|
c81889b298 | ||
|
|
0a246489ec | ||
|
|
654874fb5f |
14
README.md
14
README.md
@@ -25,7 +25,19 @@ If you are technically inclined at all, this is definitely the best way to run t
|
||||
|
||||
First, you'll need OpenSCAD: http://www.openscad.org/downloads.html. I highly recommend installing the development snapshot, as they generally support more features and are relatively stable. Development snapshots are listed in their own section on the downloads page.
|
||||
|
||||
After you have openSCAD installed, you need to download the code and run it. running `git clone https://github.com/rsheldiii/KeyV2.git` if you have git, or downloading [this zip](https://github.com/rsheldiii/KeyV2/archive/master.zip) and extracting the directory should do it. Then all you need to do is open `keys.scad` with openSCAD and you are set! It is possible to edit this project with an external editor by checking off Design => 'Automatic Reload and Preview' in OpenSCAD.
|
||||
After you have openSCAD installed, you need to download the code and run it. running `git clone https://github.com/rsheldiii/openSCAD-projects.git` if you have git, or downloading [this zip](https://github.com/rsheldiii/openSCAD-projects/archive/master.zip) and extracting the directory should do it.
|
||||
|
||||
To make your own key, all you need to do is open `keys.scad` with openSCAD and modify this line:
|
||||
|
||||
```
|
||||
dcs_row(5) legend("⇪", size=9) key();
|
||||
```
|
||||
|
||||
To be whatever you want. For example, this is for a ctrl key on an OEM keyboard:
|
||||
|
||||
```u(1.25) oem_row(3) legend("ctrl", size=4.5) key();```
|
||||
|
||||
It is possible to edit this project with an external editor by checking off Design => 'Automatic Reload and Preview' in OpenSCAD.
|
||||
|
||||
All examples below assume you are running the library on your computer with OpenSCAD.
|
||||
|
||||
|
||||
@@ -162,12 +162,14 @@ $double_sculpted = false;
|
||||
//valign = "top" or "center" or "bottom"
|
||||
// Currently does not work with thingiverse customizer, and actually breaks it
|
||||
$legends = [];
|
||||
$autolegends = [];
|
||||
|
||||
//list of front legends to place on a key format: [text, halign, valign, size]
|
||||
//halign = "left" or "center" or "right"
|
||||
//valign = "top" or "center" or "bottom"
|
||||
// Currently does not work with thingiverse customizer, and actually breaks it
|
||||
$front_legends = [];
|
||||
$front_autolegends = [];
|
||||
|
||||
// print legends on the front of the key instead of the top
|
||||
$front_print_legends = false;
|
||||
@@ -175,6 +177,9 @@ $front_print_legends = false;
|
||||
// how recessed inset legends / artisans are from the top of the key
|
||||
$inset_legend_depth = 0.2;
|
||||
|
||||
// legends are not allowed to print within this many mm of the edge of the key
|
||||
$legend_margin = 0.8;
|
||||
|
||||
// Dimensions of alps stem
|
||||
$alps_stem = [4.45, 2.25];
|
||||
|
||||
@@ -1105,6 +1110,18 @@ module front_legend(text, position=[0,0], size=undef) {
|
||||
children();
|
||||
}
|
||||
|
||||
module autolegend(texts) {
|
||||
// $autolegends = [for(L=[$legends, [[text, position, font_size]]], a=L) a];
|
||||
$autolegends = texts;
|
||||
children();
|
||||
}
|
||||
|
||||
module front_autolegend(texts) {
|
||||
font_size = size == undef ? $font_size : size;
|
||||
$front_autolegends = [for(L=[$front_legends, [[text, position, font_size]]], a=L) a];
|
||||
children();
|
||||
}
|
||||
|
||||
module bump(depth=undef) {
|
||||
$key_bump = true;
|
||||
$key_bump_depth = depth == undef ? $key_bump_depth : depth;
|
||||
@@ -3534,7 +3551,7 @@ module rounded_cherry_stem(depth, slop, throw) {
|
||||
|
||||
// inside cross
|
||||
// translation purely for aesthetic purposes, to get rid of that awful lattice
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
}
|
||||
// a safe theoretical distance between two vertices such that they don't collapse. hard to use
|
||||
@@ -3734,7 +3751,7 @@ module box_cherry_stem(depth, slop, throw) {
|
||||
}
|
||||
|
||||
// inside cross
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
}
|
||||
module alps_stem(depth, slop, throw){
|
||||
@@ -4098,12 +4115,12 @@ module brim_support(stem_type, stem_support_height, slop) {
|
||||
}
|
||||
}
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "rounded_cherry") {
|
||||
difference() {
|
||||
cylinder(d=$rounded_cherry_stem_d * 2, h=stem_support_height);
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "box_cherry") {
|
||||
difference() {
|
||||
@@ -4113,7 +4130,7 @@ module brim_support(stem_type, stem_support_height, slop) {
|
||||
}
|
||||
}
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "cherry_stabilizer") {
|
||||
difference() {
|
||||
@@ -4123,7 +4140,7 @@ module brim_support(stem_type, stem_support_height, slop) {
|
||||
}
|
||||
}
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if(stem_type == "choc") {
|
||||
translate([-5.7/2,0,0]) linear_extrude(height=stem_support_height) {
|
||||
@@ -4372,7 +4389,7 @@ module tines_support(stem_type, stem_support_height, slop) {
|
||||
}
|
||||
}
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "cherry_stabilizer") {
|
||||
difference () {
|
||||
@@ -4392,13 +4409,13 @@ module tines_support(stem_type, stem_support_height, slop) {
|
||||
difference () {
|
||||
centered_tines(stem_support_height);
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "rounded_cherry") {
|
||||
difference () {
|
||||
centered_tines(stem_support_height);
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "alps"){
|
||||
centered_tines(stem_support_height);
|
||||
@@ -5034,6 +5051,45 @@ module legends(depth=0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
module autolegends(depth=0) {
|
||||
if (len($front_autolegends) > 0) {
|
||||
front_of_key() {
|
||||
for (i=[0:len($front_legends)-1]) {
|
||||
rotate([90,0,0]) keytext($front_legends[i][0], $front_legends[i][1], $front_legends[i][2], depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (len($autolegends) > 0) {
|
||||
// legends are printed in a square grid - 1, 4, 9 legends, etc
|
||||
grid_size = len($autolegends)^0.5;
|
||||
echo("grid_size", grid_size);
|
||||
max_width = (top_total_key_width() - $legend_margin * (grid_size + 1)) / grid_size;
|
||||
max_height = (top_total_key_height() - $legend_margin * (grid_size + 1)) / grid_size;
|
||||
|
||||
top_of_key() {
|
||||
for (column=[0:grid_size-1]) {
|
||||
for (row=[0:grid_size-1]) {
|
||||
top_left_corner = [-top_total_key_width()/2, top_total_key_height()/2];
|
||||
centering_offset = [max_width / 2, -max_height / 2];
|
||||
position_offset = [(max_width + $legend_margin) * column, (-max_height-$legend_margin) * row];
|
||||
margin_offset = [$legend_margin, -$legend_margin];
|
||||
|
||||
translate(top_left_corner + centering_offset + position_offset + margin_offset) {
|
||||
translate([0,0,-depth]) {
|
||||
color($tertiary_color) linear_extrude(height=$dish_depth + depth){
|
||||
// resize([0, max_height, 0]) {
|
||||
resize([max_width, 0], auto=true) {
|
||||
text(text=$autolegends[row * grid_size + column], font=$font, halign="center", valign="center");
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// use skin() instead of successive hulls. much more correct, and looks faster
|
||||
// too, in most cases. successive hull relies on overlapping faces which are
|
||||
// not good. But, skin works on vertex sets instead of shapes, which makes it
|
||||
@@ -6242,7 +6298,10 @@ module additive_features(inset) {
|
||||
if($key_bump) keybump($key_bump_depth, $key_bump_edge);
|
||||
if(!inset && $children > 0) color($secondary_color) children();
|
||||
}
|
||||
if($outset_legends) legends(0);
|
||||
if($outset_legends) {
|
||||
legends(0);
|
||||
autolegends(0);
|
||||
}
|
||||
// render the clearance check if it's enabled, but don't have it intersect with anything
|
||||
if ($clearance_check) %clearance_check();
|
||||
}
|
||||
@@ -6252,7 +6311,10 @@ module subtractive_features(inset) {
|
||||
top_of_key() {
|
||||
if (inset && $children > 0) color($secondary_color) children();
|
||||
}
|
||||
if(!$outset_legends) legends($inset_legend_depth);
|
||||
if(!$outset_legends) {
|
||||
legends($inset_legend_depth);
|
||||
autolegends($inset_legend_depth);
|
||||
}
|
||||
// subtract the clearance check if it's enabled, letting the user see the
|
||||
// parts of the keycap that will hit the cherry switch
|
||||
// this is a little confusing as it eats the stem too
|
||||
@@ -6468,12 +6530,14 @@ $double_sculpted = false;
|
||||
//valign = "top" or "center" or "bottom"
|
||||
// Currently does not work with thingiverse customizer, and actually breaks it
|
||||
$legends = [];
|
||||
$autolegends = [];
|
||||
|
||||
//list of front legends to place on a key format: [text, halign, valign, size]
|
||||
//halign = "left" or "center" or "right"
|
||||
//valign = "top" or "center" or "bottom"
|
||||
// Currently does not work with thingiverse customizer, and actually breaks it
|
||||
$front_legends = [];
|
||||
$front_autolegends = [];
|
||||
|
||||
// print legends on the front of the key instead of the top
|
||||
$front_print_legends = false;
|
||||
@@ -6481,6 +6545,9 @@ $front_print_legends = false;
|
||||
// how recessed inset legends / artisans are from the top of the key
|
||||
$inset_legend_depth = 0.2;
|
||||
|
||||
// legends are not allowed to print within this many mm of the edge of the key
|
||||
$legend_margin = 0.8;
|
||||
|
||||
// Dimensions of alps stem
|
||||
$alps_stem = [4.45, 2.25];
|
||||
|
||||
|
||||
@@ -9,12 +9,15 @@ include <./includes.scad>
|
||||
|
||||
|
||||
// example key
|
||||
dcs_row(5) legend("⇪", size=9) key();
|
||||
|
||||
$stem_inner_slop = 0;
|
||||
dcs_row(5) autolegend(["q", "w", "a", "z", "e", "r", "t", "", "hoobastank"]) {
|
||||
$stem_positions = [[2,2]];
|
||||
key();
|
||||
}
|
||||
// example row
|
||||
/* for (x = [0:1:4]) {
|
||||
translate_u(0,-x) dcs_row(x) key();
|
||||
} */
|
||||
|
||||
// example layout
|
||||
/* preonic_default("dcs"); */
|
||||
/* preonic_default("dcs") key(); */
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
include <features/key_bump.scad>
|
||||
include <features/clearance_check.scad>
|
||||
include <features/legends.scad>
|
||||
include <features/autolegends.scad>
|
||||
|
||||
39
src/features/autolegends.scad
Normal file
39
src/features/autolegends.scad
Normal file
@@ -0,0 +1,39 @@
|
||||
module autolegends(depth=0) {
|
||||
if (len($front_autolegends) > 0) {
|
||||
front_of_key() {
|
||||
for (i=[0:len($front_legends)-1]) {
|
||||
rotate([90,0,0]) keytext($front_legends[i][0], $front_legends[i][1], $front_legends[i][2], depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (len($autolegends) > 0) {
|
||||
// legends are printed in a square grid - 1, 4, 9 legends, etc
|
||||
grid_size = len($autolegends)^0.5;
|
||||
echo("grid_size", grid_size);
|
||||
max_width = (top_total_key_width() - $legend_margin * (grid_size + 1)) / grid_size;
|
||||
max_height = (top_total_key_height() - $legend_margin * (grid_size + 1)) / grid_size;
|
||||
|
||||
top_of_key() {
|
||||
for (column=[0:grid_size-1]) {
|
||||
for (row=[0:grid_size-1]) {
|
||||
top_left_corner = [-top_total_key_width()/2, top_total_key_height()/2];
|
||||
centering_offset = [max_width / 2, -max_height / 2];
|
||||
position_offset = [(max_width + $legend_margin) * column, (-max_height-$legend_margin) * row];
|
||||
margin_offset = [$legend_margin, -$legend_margin];
|
||||
|
||||
translate(top_left_corner + centering_offset + position_offset + margin_offset) {
|
||||
translate([0,0,-depth]) {
|
||||
color($tertiary_color) linear_extrude(height=$dish_depth + depth){
|
||||
// resize([0, max_height, 0]) {
|
||||
resize([max_width, 0], auto=true) {
|
||||
text(text=$autolegends[row * grid_size + column], font=$font, halign="center", valign="center");
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/key.scad
10
src/key.scad
@@ -180,7 +180,10 @@ module additive_features(inset) {
|
||||
if($key_bump) keybump($key_bump_depth, $key_bump_edge);
|
||||
if(!inset && $children > 0) color($secondary_color) children();
|
||||
}
|
||||
if($outset_legends) legends(0);
|
||||
if($outset_legends) {
|
||||
legends(0);
|
||||
autolegends(0);
|
||||
}
|
||||
// render the clearance check if it's enabled, but don't have it intersect with anything
|
||||
if ($clearance_check) %clearance_check();
|
||||
}
|
||||
@@ -190,7 +193,10 @@ module subtractive_features(inset) {
|
||||
top_of_key() {
|
||||
if (inset && $children > 0) color($secondary_color) children();
|
||||
}
|
||||
if(!$outset_legends) legends($inset_legend_depth);
|
||||
if(!$outset_legends) {
|
||||
legends($inset_legend_depth);
|
||||
autolegends($inset_legend_depth);
|
||||
}
|
||||
// subtract the clearance check if it's enabled, letting the user see the
|
||||
// parts of the keycap that will hit the cherry switch
|
||||
// this is a little confusing as it eats the stem too
|
||||
|
||||
@@ -170,6 +170,18 @@ module front_legend(text, position=[0,0], size=undef) {
|
||||
children();
|
||||
}
|
||||
|
||||
module autolegend(texts) {
|
||||
// $autolegends = [for(L=[$legends, [[text, position, font_size]]], a=L) a];
|
||||
$autolegends = texts;
|
||||
children();
|
||||
}
|
||||
|
||||
module front_autolegend(texts) {
|
||||
font_size = size == undef ? $font_size : size;
|
||||
$front_autolegends = [for(L=[$front_legends, [[text, position, font_size]]], a=L) a];
|
||||
children();
|
||||
}
|
||||
|
||||
module bump(depth=undef) {
|
||||
$key_bump = true;
|
||||
$key_bump_depth = depth == undef ? $key_bump_depth : depth;
|
||||
|
||||
@@ -147,12 +147,14 @@ $double_sculpted = false;
|
||||
//valign = "top" or "center" or "bottom"
|
||||
// Currently does not work with thingiverse customizer, and actually breaks it
|
||||
$legends = [];
|
||||
$autolegends = [];
|
||||
|
||||
//list of front legends to place on a key format: [text, halign, valign, size]
|
||||
//halign = "left" or "center" or "right"
|
||||
//valign = "top" or "center" or "bottom"
|
||||
// Currently does not work with thingiverse customizer, and actually breaks it
|
||||
$front_legends = [];
|
||||
$front_autolegends = [];
|
||||
|
||||
// print legends on the front of the key instead of the top
|
||||
$front_print_legends = false;
|
||||
@@ -160,6 +162,9 @@ $front_print_legends = false;
|
||||
// how recessed inset legends / artisans are from the top of the key
|
||||
$inset_legend_depth = 0.2;
|
||||
|
||||
// legends are not allowed to print within this many mm of the edge of the key
|
||||
$legend_margin = 0.8;
|
||||
|
||||
// Dimensions of alps stem
|
||||
$alps_stem = [4.45, 2.25];
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ module brim_support(stem_type, stem_support_height, slop) {
|
||||
}
|
||||
}
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "rounded_cherry") {
|
||||
difference() {
|
||||
cylinder(d=$rounded_cherry_stem_d * 2, h=stem_support_height);
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "box_cherry") {
|
||||
difference() {
|
||||
@@ -31,7 +31,7 @@ module brim_support(stem_type, stem_support_height, slop) {
|
||||
}
|
||||
}
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "cherry_stabilizer") {
|
||||
difference() {
|
||||
@@ -41,7 +41,7 @@ module brim_support(stem_type, stem_support_height, slop) {
|
||||
}
|
||||
}
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if(stem_type == "choc") {
|
||||
translate([-5.7/2,0,0]) linear_extrude(height=stem_support_height) {
|
||||
|
||||
@@ -47,7 +47,7 @@ module tines_support(stem_type, stem_support_height, slop) {
|
||||
}
|
||||
}
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "cherry_stabilizer") {
|
||||
difference () {
|
||||
@@ -67,13 +67,13 @@ module tines_support(stem_type, stem_support_height, slop) {
|
||||
difference () {
|
||||
centered_tines(stem_support_height);
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "rounded_cherry") {
|
||||
difference () {
|
||||
centered_tines(stem_support_height);
|
||||
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
} else if (stem_type == "alps"){
|
||||
centered_tines(stem_support_height);
|
||||
|
||||
@@ -11,6 +11,6 @@ module box_cherry_stem(depth, slop, throw) {
|
||||
}
|
||||
|
||||
// inside cross
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ module rounded_cherry_stem(depth, slop, throw) {
|
||||
|
||||
// inside cross
|
||||
// translation purely for aesthetic purposes, to get rid of that awful lattice
|
||||
inside_cherry_cross(slop);
|
||||
inside_cherry_cross($stem_inner_slop);
|
||||
}
|
||||
}
|
||||
|
||||
17
yarn.lock
17
yarn.lock
@@ -445,12 +445,12 @@ copy-descriptor@^0.1.0:
|
||||
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
||||
|
||||
copy-props@^2.0.1:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe"
|
||||
integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.5.tgz#03cf9ae328d4ebb36f8f1d804448a6af9ee3f2d2"
|
||||
integrity sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==
|
||||
dependencies:
|
||||
each-props "^1.3.0"
|
||||
is-plain-object "^2.0.1"
|
||||
each-props "^1.3.2"
|
||||
is-plain-object "^5.0.0"
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
@@ -538,7 +538,7 @@ duplexify@^3.6.0:
|
||||
readable-stream "^2.0.0"
|
||||
stream-shift "^1.0.0"
|
||||
|
||||
each-props@^1.3.0:
|
||||
each-props@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333"
|
||||
integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==
|
||||
@@ -1178,6 +1178,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
is-plain-object@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
|
||||
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
|
||||
|
||||
is-relative@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
|
||||
|
||||
Reference in New Issue
Block a user