-
Notifications
You must be signed in to change notification settings - Fork 6
/
main.py
268 lines (227 loc) · 7.57 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
"""
MC Structure Cleaner
By: Nyveon and DemonInTheCloset
Thanks: lleheny0
Special thanks: panchito, tomimi, puntito,
and everyone who has reported bugs and given feedback.
Modded structure cleaner for minecraft. Removes all references to non-existent
structures to allow for clean error logs and chunk saving.
Project structure:
main.py - Command Line Interface
remove_tags.py - Main logic
tests - Unit tests
"""
from argparse import ArgumentParser, Namespace
from pathlib import Path
from multiprocessing import cpu_count
from structurecleaner.constants import SEP
from structurecleaner.remove_tags import remove_tags
from typing import Tuple
try:
from gooey import Gooey, GooeyParser # type: ignore
except ImportError:
Gooey = None
# Information
NAME = "MC Structure Cleaner"
VERSION = "1.7"
DESCRIPTION = f"By: Nyveon\nVersion: {VERSION}"
HELP_JOBS = (
"The number of processes to run. "
"Going over your CPU count may "
"slow things down. The default is recommendable"
)
HELP_TAG = (
"You can leave this blank to remove all non-vanilla structures. \n"
"Or you can write a list of: \n"
"- The exact structure tag you want removed. \n"
"- A (*) as a wildcard for a prefix (see github) \n"
"Separate tags by spaces. Use \"\" if the names have space characters. "
)
HELP_PATH = "The path of the world you wish to process"
HELP_OUTPUT = "The path of the folder you wish to save the new region files to"
HELP_REGION = (
"The name of the region folder (dimension) "
" | Overworld: (blank) | Nether: DIM-1 | End: DIM1"
)
# Configuration
DEFAULT_PATH = "world"
DEFAULT_OUTPUT = "./"
# Environment
def setup_environment(new_region: Path) -> bool:
"""Try to create new_region folder
This is the folder where the new region files will be saved
Args:
new_region (Path): Path to new region folder
Returns:
bool: True if successful, False otherwise
"""
if new_region.exists():
if Gooey:
raise FileExistsError(
f"{new_region} already exists, please delete"
" it or choose a different folder."
)
else:
print(f"{new_region.resolve()} exists, this may cause problems")
proceed = input("Do you want to proceed regardless? [y/N] ")
print(SEP)
return proceed.startswith("y")
new_region.mkdir()
print(f"Saving newly generated region files to {new_region.resolve()}")
return True
def get_default_jobs() -> int:
"""Get default number of jobs
Returns:
int: The number of CPU cores in the device
"""
return cpu_count() // 2
def get_cli_args() -> Namespace:
"""Get CLI Arguments
Returns:
Namespace: The parsed arguments
"""
jobs = get_default_jobs()
jobs_help = f"{HELP_JOBS} (default: {jobs})"
path_help = f"{HELP_PATH} (default: '{DEFAULT_PATH}')"
output_help = f"{HELP_OUTPUT} (default: '{DEFAULT_OUTPUT}')"
parser = ArgumentParser(prog=f"{NAME}\n{DESCRIPTION}")
parser.add_argument(
"-t", "--tag", type=str, help=HELP_TAG, default="", nargs="*"
)
parser.add_argument("-j", "--jobs", type=int, help=jobs_help, default=jobs)
parser.add_argument(
"-p", "--path", type=str, help=path_help, default="world"
)
parser.add_argument(
"-o", "--output", type=str, help=output_help, default="./"
)
parser.add_argument(
"-r", "--region", type=str, help=HELP_REGION, default=""
)
return parser.parse_args()
# GUI (Only if Gooey is installed)
if Gooey:
@Gooey(
program_name=NAME,
program_description=DESCRIPTION,
header_bg_color="#6dd684",
default_size=(610, 610),
image_dir="./images",
menu=[
{
"name": "About",
"items": [
{
"type": "AboutDialog",
"menuTitle": "About",
"name": NAME,
"description": DESCRIPTION,
"version": VERSION,
"website": "https://github.com/Nyveon/MCStructureCleaner",
}
],
},
{
"name": "Help",
"items": [
{
"type": "Link",
"menuTitle": "Information",
"url": "https://github.com/Nyveon/MCStructureCleaner",
},
{
"type": "Link",
"menuTitle": "Report an issue",
"url": "https://github.com/Nyveon/MCStructureCleaner/issues",
},
],
},
],
)
def get_gui_args() -> Namespace:
"""Get GUI Arguments
Returns:
Namespace: The parsed arguments
"""
jobs = get_default_jobs()
parser = GooeyParser()
parser.add_argument(
"-t", "--tag", type=str, help=HELP_TAG, default="", nargs="*"
)
parser.add_argument(
"-j",
"--jobs",
type=int,
help=HELP_JOBS,
default=jobs,
widget="IntegerField",
gooey_options={"min": 1, "max": jobs * 2},
)
parser.add_argument(
"-p",
"--path",
type=str,
help=HELP_PATH,
default="./world",
widget="DirChooser",
)
parser.add_argument(
"-o",
"--output",
type=str,
help=HELP_OUTPUT,
default="./",
widget="DirChooser",
)
parser.add_argument(
"-r", "--region", type=str, help=HELP_REGION, default=""
)
return parser.parse_args()
def process_args(args: Namespace) -> Tuple[set, Path, Path, int]:
"""Process CLI or GUI Arguments
Args:
args (Namespace): Parsed CLI or GUI arguments
Returns:
Tuple[set, Path, Path, int]: Processed arguments:
1. A set of tags (strings)
2. The output path
3. The input path
4. The job
"""
return (
set(args.tag),
Path(f"{args.output}/new_region{args.region}"),
Path(f"{args.path}/{args.region}/region"),
args.jobs,
)
def main() -> None:
"""The main program"""
# CLI or GUI arguments
args = get_gui_args() if Gooey else get_cli_args()
to_replace, new_region, world_region, num_processes = process_args(args)
# Force purge mode if no tag is given, otherwise normal.
mode = "purge" if not to_replace else "normal"
if mode == "purge":
print("No tag given, will run in purge mode.")
print(
f"Replacing all non-vanilla structures in \
all region files in {world_region}."
)
else:
print("Tag(s) given, will run in normal mode.")
print(f"Replacing {to_replace} in all region files in {world_region}.")
print(SEP)
# Check if world exists
if not world_region.exists():
raise FileNotFoundError(f"Couldn't find {world_region.resolve()}")
# Check if output already exists
if not setup_environment(new_region):
raise SystemExit("Aborted, nothing was done")
n_to_process = len(list(world_region.iterdir()))
remove_tags(to_replace, world_region, new_region, num_processes, mode)
# End output
print(f"{SEP}\nProcessed {n_to_process} files")
print(f"You can now replace {world_region} with {new_region}")
return None
if __name__ == "__main__":
main()