1
0
Dieser Commit ist enthalten in:
Chaoscaot 2023-03-06 18:29:03 +01:00
Ursprung d352bfaded
Commit 0df001602e
9 geänderte Dateien mit 380 neuen und 133 gelöschten Zeilen

Datei anzeigen

@ -2,7 +2,15 @@
members = [
"schemsearch-cli",
"schemsearch-lib",
"schemsearch_faster",
"schemsearch-files",
"schemsearch-sql",
"schemsearch-java"
]
[profile.test]
inherits = "release"
lto = true
[profile.release]
lto = true

Datei anzeigen

@ -8,3 +8,4 @@ edition = "2021"
[dependencies]
schemsearch-lib = { path = "../schemsearch-lib" }
schemsearch-files = { path = "../schemsearch-files" }
clap = { version = "4.1.8", features = ["cargo"] }

Datei anzeigen

@ -1,13 +1,203 @@
use std::path::Path;
use std::fmt::format;
use std::fs::File;
use std::io::{BufWriter, Stdout, StdoutLock, Write};
use clap::{command, Arg, ArgAction, ColorChoice, value_parser, Command};
use schemsearch_files::Schematic;
use schemsearch_lib::pattern_mapper::match_palette;
use std::path::Path;
use clap::ArgAction::Help;
use clap::error::ErrorKind;
use schemsearch_lib::{search, SearchBehavior};
fn main() {
let schematic = Schematic::load(Path::new("tests/simple.schem"));
let endstone = Schematic::load(Path::new("tests/endstone.schem"));
let mut cmd = command!("schemsearch")
.arg(
Arg::new("pattern")
.help("The pattern to search for")
.required(true)
.action(ArgAction::Set),
)
.arg(
Arg::new("schematic")
.help("The schematics to search in")
.required(true)
.action(ArgAction::Append),
)
.arg(
Arg::new("ignore-data")
.help("Ignores block data when searching")
.short('d')
.long("ignore-data")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("ignore-block-entities")
.help("Ignores block entities when searching")
.short('b')
.long("ignore-block-entities")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("ignore-entities")
.help("Ignores entities when searching")
.short('e')
.long("ignore-entities")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("ignore-air")
.help("Ignores air when searching")
.short('a')
.long("ignore-air")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("air-as-any")
.help("Treats air as any block when searching")
.short('A')
.long("air-as-any")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("output")
.help("The output format")
.short('o')
.long("output")
.action(ArgAction::Append)
.default_value("std")
.value_parser(["std_csv", "file_csv", "std"]),
)
.arg(
Arg::new("output-file")
.help("The output file")
.short('O')
.long("output-file")
.action(ArgAction::Append)
)
.arg(
Arg::new("threshold")
.help("The threshold for the search")
.short('t')
.long("threshold")
.action(ArgAction::Set)
.default_value("0.9")
.value_parser(|s: &str| s.parse::<f64>().map_err(|e| e.to_string())),
)
.about("Searches for a pattern in a schematic")
.bin_name("schemsearch");
let (matched_schematic, matched_endstone) = match_palette(&schematic, &endstone, true);
let matches = cmd.get_matches_mut();
println!("{:?}", matched_schematic);
println!("{:?}", matched_endstone);
if matches.contains_id("help") {
return;
}
let search_behavior = SearchBehavior {
ignore_block_data: matches.get_flag("ignore-data"),
ignore_block_entities: matches.get_flag("ignore-block-entities"),
ignore_air: matches.get_flag("ignore-air"),
air_as_any: matches.get_flag("air-as-any"),
ignore_entities: matches.get_flag("ignore-entities"),
threshold: *matches.get_one::<f64>("threshold").expect("Couldn't get threshold"),
};
let pattern = match Schematic::load(Path::new(matches.get_one::<String>("pattern").unwrap())) {
Ok(x) => x,
Err(e) => {
cmd.error(ErrorKind::Io, format!("Error while loading Pattern: {}", e.to_string())).exit();
}
};
let schematics = matches.get_many::<String>("schematic").expect("Couldn't get schematics");
let mut output_std = false;
let mut output_std_csv = false;
let mut output_file_csv = false;
let mut output_file = false;
for x in matches.get_many::<String>("output").expect("Couldn't get output") {
match x.as_str() {
"std" => output_std = true,
"std_csv" => output_std_csv = true,
"file_csv" => output_file_csv = true,
"file" => output_file = true,
_ => {}
}
};
let mut stdout = std::io::stdout();
let mut lock = stdout.lock();
let mut file: Option<File> = None;
let mut file_out: Option<BufWriter<File>> = None;
if output_file || output_file_csv {
let output_file_path = match matches.get_one::<String>("output-file") {
None => {
cmd.error(ErrorKind::MissingRequiredArgument, "No output file specified").exit();
}
Some(x) => x
};
file = match std::fs::File::create(output_file_path) {
Ok(x) => Some(x),
Err(e) => {
cmd.error(ErrorKind::Io, format!("Error while creating output file: {}", e.to_string())).exit();
}
};
file_out = Some(BufWriter::new(file.unwrap()));
}
for schem_path in schematics {
let path = Path::new(schem_path);
if path.is_dir() {
match path.read_dir() {
Ok(x) => {
for path in x {
match path {
Ok(x) => {
if x.path().extension().unwrap_or_default() == "schem" {
search_schempath(&mut cmd, search_behavior, &pattern, &mut output_std, &mut output_std_csv, &mut output_file_csv, &mut output_file, &mut lock, &mut file_out, &x.path());
}
}
Err(e) => cmd.error(ErrorKind::Io, format!("Error while reading dir: {}", e.to_string())).exit()
}
}
}
Err(e) => cmd.error(ErrorKind::Io, "Expected to be a dir").exit()
}
} else {
search_schempath(&mut cmd, search_behavior, &pattern, &mut output_std, &mut output_std_csv, &mut output_file_csv, &mut output_file, &mut lock, &mut file_out, path)
}
}
}
fn search_schempath(cmd: &mut Command, search_behavior: SearchBehavior, pattern: &Schematic, output_std: &mut bool, output_std_csv: &mut bool, output_file_csv: &mut bool, output_file: &mut bool, stdout: &mut StdoutLock, file_out: &mut Option<BufWriter<File>>, schem_path: &Path) {
let schematic = match Schematic::load(schem_path) {
Ok(x) => x,
Err(e) => {
cmd.error(ErrorKind::Io, format!("Error while loading Schematic ({}): {}", schem_path.file_name().unwrap().to_str().unwrap(), e.to_string())).exit();
}
};
if *output_std {
writeln!(stdout, "Searching in schematic: {}", schem_path.file_name().unwrap().to_str().unwrap()).unwrap();
}
let matches = search(&schematic, &pattern, search_behavior);
for x in matches {
if *output_std {
writeln!(stdout, "Found match at x: {}, y: {}, z: {}", x.0, x.1, x.2).unwrap();
}
if *output_std_csv {
writeln!(stdout, "{},{},{},{}", schem_path.file_name().unwrap().to_str().unwrap(), x.0, x.1, x.2).unwrap();
}
if *output_file {
writeln!(file_out.as_mut().unwrap(), "Found match at x: {}, y: {}, z: {}", x.0, x.1, x.2).unwrap();
}
if *output_file_csv {
writeln!(file_out.as_mut().unwrap(), "{},{},{},{}", schem_path.file_name().unwrap().to_str().unwrap(), x.0, x.1, x.2).unwrap();
}
}
}

Datei anzeigen

@ -1,6 +1,7 @@
use std::path::Path;
use nbt::{Map, Value};
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
use serde::de::Error;
#[derive(Serialize, Deserialize, Debug)]
pub struct Schematic {
@ -22,14 +23,22 @@ pub struct Schematic {
pub palette_max: i32,
#[serde(rename = "Palette")]
pub palette: Map<String, i32>,
#[serde(rename = "BlockData")]
pub block_data: Vec<u8>,
#[serde(rename = "BlockData", deserialize_with = "read_blockdata")]
pub block_data: Vec<i32>,
#[serde(rename = "BlockEntities")]
pub block_entities: Vec<BlockEntity>,
#[serde(rename = "Entities")]
pub entities: Option<Vec<Entity>>,
}
fn read_blockdata<'de, D>(deserializer: D) -> Result<Vec<i32>, D::Error>
where
D: Deserializer<'de>,
{
let s: Vec<u8> = Deserialize::deserialize(deserializer)?;
Ok(read_varint_array(&s))
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BlockEntity {
#[serde(rename = "Id")]
@ -47,17 +56,16 @@ pub struct Entity {
}
impl Schematic {
pub fn load(path: &Path) -> Schematic {
let file = std::fs::File::open(path).expect("Failed to open file");
pub fn load(path: &Path) -> Result<Schematic, String> {
let file = match std::fs::File::open(path) {
Ok(x) => x,
Err(_) => return Err(format!("Failed to open file: {}", path.display()))
};
let schematic: Schematic = match nbt::from_gzip_reader(file) {
Ok(schem) => schem,
Err(e) => panic!("Failed to parse schematic: {}", e),
Err(e) => return Err(format!("Failed to parse schematic: {}", e))
};
schematic
}
pub fn read_blockdata(&self) -> Vec<i32> {
read_varint_array(&self.block_data)
Ok(schematic)
}
}
@ -89,20 +97,3 @@ pub fn read_varint_array(read: &Vec<u8>) -> Vec<i32> {
}
data
}
pub fn to_varint_array(data: &Vec<i32>) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::new();
for value in data {
let mut value = *value as u32;
'inner: loop {
if (value & 0x80) == 0 {
bytes.push(value as u8);
break 'inner;
}
bytes.push((value & 0x7F) as u8 | 0x80);
value >>= 7;
}
}
bytes
}

Datei anzeigen

@ -1,27 +1,29 @@
#![no_mangle]
use std::path::Path;
use jni::JNIEnv;
use jni::objects::{JClass, JString};
use jni::sys::jstring;
use schemsearch_files::Schematic;
use schemsearch_lib::{search, SearchBehavior};
#[no_mangle]
pub extern "system" fn Java_SchemSearch_search<'local>(mut env: JNIEnv<'local>,
class: JClass<'local>,
schematic_path: JString<'local>,
pattern_path: JString<'local>) -> jstring {
let schematic_path: String = env.get_string(&schematic_path).expect("Couldn't get java string!").into();
let pattern_path: String = env.get_string(&pattern_path).expect("Couldn't get java string!").into();
let file = std::fs::File::open(schematic_path).expect("Failed to open file");
let schematic = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect();
let file = std::fs::File::open(pattern_path).expect("Failed to open file");
let pattern = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect();
let schematic = Schematic::load(Path::new(&schematic_path));
let pattern = Schematic::load(Path::new(&pattern_path));
let matches = search(schematic, pattern, SearchBehavior {
let matches = search(&schematic, &pattern, SearchBehavior {
ignore_block_data: true,
ignore_block_entities: true,
ignore_entities: true,
ignore_air: false,
air_as_any: false,
threshold: 0.0,
});
let mut result = String::new();

Datei anzeigen

@ -1,23 +1,23 @@
pub mod pattern_mapper;
use pattern_mapper::match_palette;
use schemsearch_files::Schematic;
pub mod pattern_mapper;
#[derive(Debug, Clone, Copy)]
pub struct SearchBehavior {
pub ignore_block_data: bool,
pub ignore_block_entities: bool,
pub ignore_air: bool,
pub air_as_any: bool,
pub ignore_entities: bool,
pub threshold: f64,
}
pub fn search(
data: &Vec<u8>,
pattern: &Vec<u8>,
schem: &Schematic,
pattern_schem: &Schematic,
search_behavior: SearchBehavior,
) -> Vec<(u16, u16, u16)> {
let schem: Schematic = parse_schematic(data);
let pattern_schem: Schematic = parse_schematic(pattern);
if schem.width < pattern_schem.width || schem.height < pattern_schem.height || schem.length < pattern_schem.length {
return vec![];
}
@ -26,52 +26,44 @@ pub fn search(
return vec![];
}
let (schem, pattern_schem) = match_palette(&schem, &pattern_schem, search_behavior.ignore_block_data);
let pattern_schem = match match_palette(&schem, &pattern_schem, search_behavior.ignore_block_data) {
Some(x) => x,
None => return vec![],
};
let mut matches: Vec<(u16, u16, u16)> = Vec::new();
println!("{:?}", schem);
println!("{:?}", pattern_schem);
let pattern_data = pattern_schem.block_data;
let schem_data = &schem.block_data;
let air_id = if search_behavior.ignore_air || search_behavior.air_as_any { pattern_schem.palette.get("minecraft:air").unwrap_or(&-1) } else { &-1};
let pattern_data = pattern_schem.read_blockdata();
let schem_data = schem.read_blockdata();
let pattern_blocks = (pattern_schem.width * pattern_schem.height * pattern_schem.length) as f64;
for x in 0..=schem.width - pattern_schem.width {
for y in 0..=schem.height - pattern_schem.height {
for z in 0..=schem.length - pattern_schem.length {
let mut match_found = true;
'outer: for i in 0..pattern_schem.width {
for j in 0..pattern_schem.height {
for k in 0..pattern_schem.length {
let index = (x + i) + (y + j) * schem.width + (z + k) * schem.width * schem.height;
let pattern_index = i + j * pattern_schem.width + k * pattern_schem.width * pattern_schem.height;
if schem_data.get(index as usize) != pattern_data.get(pattern_index as usize) {
match_found = false;
break 'outer;
for x in 0..=schem.width as usize - pattern_schem.width as usize {
for y in 0..=schem.height as usize - pattern_schem.height as usize {
for z in 0..=schem.length as usize - pattern_schem.length as usize {
let mut matching = 0;
for i in 0..pattern_schem.width as usize {
for j in 0..pattern_schem.height as usize {
for k in 0..pattern_schem.length as usize {
let index = (x + i) + (y + j) * (schem.width as usize) + (z + k) * (schem.width as usize) * (schem.height as usize);
let pattern_index = i + j * pattern_schem.width as usize + k * pattern_schem.width as usize * pattern_schem.height as usize;
let data = schem_data.get(index as usize).expect("Index out of bounds");
let pattern_data = pattern_data.get(pattern_index as usize).expect("Index out of bounds");
if data == pattern_data || (search_behavior.ignore_air && *data == *air_id) || (search_behavior.air_as_any && *pattern_data == *air_id) {
matching += 1;
}
}
}
}
if match_found {
matches.push((x, y, z));
if matching as f64 / pattern_blocks > search_behavior.threshold {
matches.push((x as u16, y as u16, z as u16));
}
}
}
}
/*
[
0, -1, 1, 1, 2,
0, -1, 2, 1, 0,
2, -1, -1, 2, -1,
2, 0, 0, 2, -1,
2, 1, 2, 2, 1
]
*/
return matches;
}
pub fn normalize_data(data: &String, ignore_data: bool) -> String {
@ -104,7 +96,6 @@ mod tests {
let schematic = Schematic::load(Path::new("../tests/simple.schem"));
assert_eq!(schematic.width as usize * schematic.height as usize * schematic.length as usize, schematic.block_data.len());
assert_eq!(schematic.palette_max, schematic.palette.len() as i32);
println!("{:?}", schematic);
}
#[test]
@ -113,7 +104,6 @@ mod tests {
let schematic: Schematic = parse_schematic(&std::io::Read::bytes(file).map(|b| b.unwrap()).collect());
assert_eq!(schematic.width as usize * schematic.height as usize * schematic.length as usize, schematic.block_data.len());
assert_eq!(schematic.palette_max, schematic.palette.len() as i32);
println!("{:?}", schematic);
}
#[test]
@ -122,7 +112,6 @@ mod tests {
let stripped = strip_data(&schematic);
assert_eq!(stripped.palette.keys().any(|k| k.contains('[')), false);
println!("{:?}", stripped);
}
#[test]
@ -130,17 +119,38 @@ mod tests {
let schematic = Schematic::load(Path::new("../tests/simple.schem"));
let endstone = Schematic::load(Path::new("../tests/endstone.schem"));
let (matched_schematic, matched_endstone) = match_palette(&schematic, &endstone, true);
let matched_schematic = match_palette(&schematic, &endstone, true);
}
println!("{:?}", matched_schematic);
println!("{:?}", matched_endstone);
#[test]
fn test_match_palette_ignore_data() {
let schematic = Schematic::load(Path::new("../tests/simple.schem"));
let endstone = Schematic::load(Path::new("../tests/endstone.schem"));
let matched_schematic = match_palette(&schematic, &endstone, false);
}
#[test]
pub fn test_big_search() {
let file = std::fs::File::open("../tests/simple.schem").expect("Failed to open file");
let schematic = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect();
let file = std::fs::File::open("../tests/endstone.schem").expect("Failed to open file");
let pattern = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect();
let matches = search(schematic, pattern, SearchBehavior {
ignore_block_data: true,
ignore_block_entities: true,
ignore_entities: true,
ignore_air: false,
air_as_any: false,
threshold: 0.9
});
}
#[test]
pub fn test_search() {
let file = std::fs::File::open("../tests/Random.schem").expect("Failed to open file");
let schematic = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect();
println!("{:?}", schematic);
let file = std::fs::File::open("../tests/Pattern.schem").expect("Failed to open file");
let pattern = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect();
@ -148,6 +158,9 @@ mod tests {
ignore_block_data: true,
ignore_block_entities: true,
ignore_entities: true,
ignore_air: false,
air_as_any: false,
threshold: 0.9
});
println!("{:?}", matches);

Datei anzeigen

@ -1,5 +1,5 @@
use nbt::Map;
use schemsearch_files::{Schematic, to_varint_array};
use schemsearch_files::Schematic;
use crate::normalize_data;
fn create_reverse_palette(schem: &Schematic) -> Vec<String> {
@ -17,7 +17,7 @@ pub fn strip_data(schem: &Schematic) -> Schematic {
let mut palette: Map<String, i32> = Map::new();
let mut palette_max: i32 = 0;
let reverse_palette = create_reverse_palette(schem);
let dat = schem.read_blockdata();
let dat = &schem.block_data;
for block in dat.iter() {
let block_name = reverse_palette[*block as usize].clone();
@ -36,7 +36,7 @@ pub fn strip_data(schem: &Schematic) -> Schematic {
data_version: schem.data_version,
palette,
palette_max,
block_data: to_varint_array(&data),
block_data: data,
block_entities: schem.block_entities.clone(),
height: schem.height,
length: schem.length,
@ -47,24 +47,27 @@ pub fn strip_data(schem: &Schematic) -> Schematic {
}
}
fn match_palette_adapt(schem: &Schematic, matching_palette: Map<String, i32>, ignore_data: bool) -> Vec<i32> {
fn match_palette_adapt(schem: &Schematic, matching_palette: &Map<String, i32>, ignore_data: bool) -> Option<Vec<i32>> {
let mut data: Vec<i32> = Vec::new();
for x in schem.read_blockdata().iter() {
for x in schem.block_data.iter() {
let blockname = schem.palette.iter().find(|(_, &v)| v == *x).expect("Invalid Schematic").0;
let blockname = if ignore_data { normalize_data(&blockname, ignore_data) } else { blockname.clone() };
let block_id = matching_palette.get(&blockname).unwrap_or(&-1);
let block_id = match matching_palette.get(&blockname) {
None => return None,
Some(x) => x
};
data.push(*block_id);
}
data
Some(data)
}
pub fn match_palette(
schem: &Schematic,
pattern: &Schematic,
ignore_data: bool,
) -> (Schematic, Schematic) {
) -> Option<Schematic> {
if ignore_data {
match_palette_internal(&strip_data(schem), &strip_data(pattern), ignore_data)
} else {
@ -76,46 +79,21 @@ fn match_palette_internal(
schem: &Schematic,
pattern: &Schematic,
ignore_data: bool,
) -> (Schematic, Schematic) {
if schem.palette.len() < pattern.palette.len() {
panic!("Schematic palette is larger than pattern palette");
) -> Option<Schematic> {
if pattern.palette.iter().any(|(k, _)| schem.palette.get(k).is_none()) {
return None;
}
let mut matching_palette: Map<String, i32> = Map::new();
let mut matching_palette_max: i32 = 0;
for (block_name, _) in pattern.palette.iter() {
let block_name = normalize_data(block_name, true);
let schem_block_id = pattern.palette.get(&block_name).expect("Pattern block not found in schematic palette");
matching_palette.insert(block_name, *schem_block_id);
matching_palette_max += 1;
}
let data_schem: Vec<i32> = match_palette_adapt(&schem, matching_palette.clone(), true);
let data_pattern: Vec<i32> = match_palette_adapt(&pattern, matching_palette.clone(), true);
let schem = Schematic {
version: schem.version.clone(),
data_version: schem.data_version.clone(),
palette: matching_palette.clone(),
palette_max: matching_palette_max.clone(),
block_data: to_varint_array(&data_schem),
block_entities: schem.block_entities.clone(),
height: schem.height.clone(),
length: schem.length.clone(),
width: schem.width.clone(),
metadata: schem.metadata.clone(),
offset: schem.offset.clone(),
entities: None,
let data_pattern: Vec<i32> = match match_palette_adapt(&pattern, &schem.palette, ignore_data) {
None => return None,
Some(x) => x
};
let pattern = Schematic {
Some(Schematic {
version: pattern.version.clone(),
data_version: pattern.data_version.clone(),
palette: matching_palette.clone(),
palette_max: matching_palette_max.clone(),
block_data: to_varint_array(&data_pattern),
palette: schem.palette.clone(),
palette_max: schem.palette_max,
block_data: data_pattern,
block_entities: pattern.block_entities.clone(),
height: pattern.height.clone(),
length: pattern.length.clone(),
@ -123,7 +101,5 @@ fn match_palette_internal(
metadata: pattern.metadata.clone(),
offset: pattern.offset.clone(),
entities: None,
};
(schem, pattern)
})
}

Datei anzeigen

@ -0,0 +1,11 @@
[package]
name = "schemsearch_faster"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
schemsearch-lib = { path = "../schemsearch-lib" }
schemsearch-files = { path = "../schemsearch-files" }
hematite-nbt = "0.5.2"

Datei anzeigen

@ -0,0 +1,55 @@
use nbt::Map;
use schemsearch_files::Schematic;
pub fn convert_to_search_space(schem: &Schematic, palette: &Vec<String>) -> Vec<Vec<u8>> {
let mut data: Vec<Vec<u8>> = Vec::with_capacity(palette.len());
let block_data = &schem.block_data;
for name in palette {
let mut output: Vec<u8> = Vec::with_capacity(block_data.len());
for block in block_data.iter() {
if schem.palette.get(name).unwrap_or(&-1) == block {
output.push(1);
} else {
output.push(0);
}
}
data.push(output);
}
data
}
pub fn unwrap_palette(palette: &Map<String, i32>) -> Vec<String> {
let mut output: Vec<String> = Vec::with_capacity(palette.len());
(0..palette.len()).for_each(|_| output.push(String::new()));
for (key, id) in palette.iter() {
output[*id as usize] = key.clone();
}
output
}
#[allow(unused_imports)]
mod tests {
use std::path::Path;
use schemsearch_files::Schematic;
use crate::{convert_to_search_space, unwrap_palette};
#[test]
pub fn test() {
let schematic = Schematic::load(Path::new("../tests/Pattern.schem"));
dbg!(convert_to_search_space(&schematic, &unwrap_palette(&schematic.palette)));
}
#[test]
pub fn test_2() {
let schematic = Schematic::load(Path::new("../tests/Pattern.schem"));
let schematic2 = Schematic::load(Path::new("../tests/Random.schem"));
println!("{:?}", convert_to_search_space(&schematic2, &unwrap_palette(&schematic.palette)));
}
#[test]
pub fn test_big() {
let schematic = Schematic::load(Path::new("../tests/endstone.schem"));
let schematic2 = Schematic::load(Path::new("../tests/simple.schem"));
let _ = convert_to_search_space(&schematic2, &unwrap_palette(&schematic.palette));
}
}