bricks_from_excel() to allow for model creation using an Excel template.
@@ -1,41 +0,0 @@
|
||||
#####
|
||||
# Generate LEGO mosaic an image
|
||||
#####
|
||||
|
||||
#Packages Required: dplyr, tidyr, purrrr, ggplot2, readr
|
||||
|
||||
#RUN THIS CODE AFTER DOWNLOADING SOURCE FILES
|
||||
library(brickr)
|
||||
|
||||
#This will take a few seconds to run
|
||||
lego_mosaic <- jpeg::readJPEG("Images/goldengirls.jpg") %>%
|
||||
image_to_bricks(48)
|
||||
|
||||
lego_mosaic %>% display_set()
|
||||
|
||||
#Instruction manual - calculate and graph
|
||||
lego_mosaic %>%
|
||||
generate_instructions(6)
|
||||
|
||||
#Get summary of pieces
|
||||
pieces <- lego_mosaic %>% table_pieces()
|
||||
|
||||
sum(lego_mosaic$pieces$n)
|
||||
|
||||
lego_mosaic %>% display_pieces()
|
||||
|
||||
#Save it
|
||||
ggplot2::ggsave("GG_fewer_pieces.png", device = "png", height = 8, width = 8)
|
||||
|
||||
#3D with rayshader ----
|
||||
library(rayshader)
|
||||
|
||||
#mosaic_height is the elevation of the mosaic in LEGO plates... 3 plates = 1 LEGO brick
|
||||
#Set highest_el = "dark" for dark bricks to be tallest... otherwise light bricks are tallest
|
||||
lego_mosaic %>%
|
||||
collect_3d(mosaic_height = 6) %>%
|
||||
display_3d(fov=0,theta=0,phi=90,windowsize=c(1000,800),zoom=0.75,shadow=FALSE)
|
||||
|
||||
|
||||
#display_3d() takes all inputs of rayshader::plot_3d() EXCEPT hillshade, heightmap, & zscale
|
||||
# If you want to use plot_3d() instead, use the items 'threed_hillshade' and 'threed_elevation' in the collect_3d() output
|
||||
@@ -1,584 +0,0 @@
|
||||
library(dplyr, warn.conflicts = FALSE);
|
||||
library(tidyr, warn.conflicts = FALSE);
|
||||
library(purrr, warn.conflicts = FALSE);
|
||||
library(ggplot2, warn.conflicts = FALSE);
|
||||
library(readr)
|
||||
|
||||
# LEGO colors ----
|
||||
if(file.exists("Colors/Lego_Colors.csv")){lego_color_path <- "Colors/Lego_Colors.csv"} else{
|
||||
lego_color_path <- "https://raw.githubusercontent.com/ryantimpe/LEGOMosaics/master/Colors/Lego_Colors.csv"
|
||||
}
|
||||
lego_colors <- read_csv(lego_color_path,
|
||||
col_types = cols(
|
||||
LEGONo = col_double(),
|
||||
Color = col_character(),
|
||||
Sample = col_logical(),
|
||||
R = col_double(),
|
||||
G = col_double(),
|
||||
B = col_double(),
|
||||
c_Palette2016 = col_logical(),
|
||||
c_Transparent = col_logical(),
|
||||
c_Glow = col_logical(),
|
||||
c_Metallic = col_logical(),
|
||||
t_BW = col_logical(),
|
||||
t_Classic = col_logical(),
|
||||
t_Friends = col_logical(),
|
||||
w_weight = col_double(),
|
||||
w_Classic = col_double()
|
||||
)
|
||||
) %>%
|
||||
filter(c_Palette2016, !c_Transparent, !c_Glow, !c_Metallic) %>%
|
||||
mutate_at(vars(R, G, B), funs(./255)) %>%
|
||||
rename(R_lego = R, G_lego = G, B_lego = B)%>%
|
||||
mutate_at(vars(starts_with("w_")), funs(ifelse(is.na(.), 0, .)))
|
||||
|
||||
# GGplot theme to remove axes, etc ----
|
||||
theme_lego <- theme(panel.background = element_rect(fill = "#7EC0EE"),
|
||||
strip.background = element_rect(fill = "#F7F18D"),
|
||||
strip.text = element_text(color = "#333333", face = "bold"),
|
||||
axis.line = element_blank(),
|
||||
axis.title.x = element_blank(),
|
||||
axis.text.x = element_blank(),
|
||||
axis.title.y = element_blank(),
|
||||
axis.text.y = element_blank())
|
||||
|
||||
#1 SCALE IMAGE ----
|
||||
scale_image <- function(image, img_size, brightness = 1, warhol = 1:3){
|
||||
#Adjust brightness
|
||||
if(brightness < 0 ){stop("brightness should be a positive value. Use 1 for no change, >1 for lighter, <1 for darker.")}
|
||||
image_b <- image*brightness
|
||||
image_b[image_b>1] <- 1
|
||||
|
||||
col_chan <- order(warhol[1:3])
|
||||
|
||||
#Convert image to a data frame with RGB values
|
||||
img <- bind_rows(
|
||||
list(
|
||||
(as.data.frame(image_b[, , col_chan[1]]) %>%
|
||||
mutate(y=row_number(), channel = "R")),
|
||||
(as.data.frame(image_b[, , col_chan[2]]) %>%
|
||||
mutate(y=row_number(), channel = "G")),
|
||||
(as.data.frame(image_b[, , col_chan[3]]) %>%
|
||||
mutate(y=row_number(), channel = "B"))
|
||||
)
|
||||
) %>%
|
||||
gather(x, value, -y, -channel) %>%
|
||||
mutate(x = as.numeric(gsub("V", "", x))) %>%
|
||||
spread(channel, value)
|
||||
|
||||
img_size <- round(img_size, 0)
|
||||
|
||||
#Wide or tall image? Shortest side should be `img_size` pixels
|
||||
if(max(img$x) > max(img$y)){
|
||||
img_scale_x <- max(img$x) / max(img$y)
|
||||
img_scale_y <- 1
|
||||
} else {
|
||||
img_scale_x <- 1
|
||||
img_scale_y <- max(img$y) / max(img$x)
|
||||
}
|
||||
|
||||
#If only 1 img_size value, create a square image
|
||||
if(length(img_size) == 1){
|
||||
img_size2 <- c(img_size, img_size)
|
||||
} else {
|
||||
img_size2 <- img_size[1:2]
|
||||
img_scale_x <- 1
|
||||
img_scale_y <- 1
|
||||
}
|
||||
|
||||
#Rescale the image
|
||||
img2 <- img %>%
|
||||
mutate(y_scaled = (y - min(y))/(max(y)-min(y))*img_size2[2]*img_scale_y + 1,
|
||||
x_scaled = (x - min(x))/(max(x)-min(x))*img_size2[1]*img_scale_x + 1) %>%
|
||||
select(-x, -y) %>%
|
||||
group_by(y = ceiling(y_scaled), x = ceiling(x_scaled)) %>%
|
||||
#Get average R, G, B and convert it to hexcolor
|
||||
summarize_at(vars(R, G, B), funs(mean(.))) %>%
|
||||
rowwise() %>%
|
||||
mutate(color = rgb(R, G, B)) %>%
|
||||
ungroup() %>%
|
||||
#Center the image
|
||||
filter(x <= median(x) + img_size2[1]/2, x > median(x) - img_size2[1]/2,
|
||||
y <= median(y) + img_size2[2]/2, y > median(y) - img_size2[2]/2) %>%
|
||||
#Flip y
|
||||
mutate(y = (max(y) - y) + 1)
|
||||
|
||||
out_list <- list()
|
||||
out_list[["Img_scaled"]] <- img2
|
||||
|
||||
return(out_list)
|
||||
|
||||
}
|
||||
|
||||
#2 Legoize - Convert image Lego colors -----
|
||||
convert_to_lego_colors <- function(R, G, B, dat_color){
|
||||
dat_color %>%
|
||||
mutate(dist = ((R_lego - R)^2 + (G_lego - G)^2 + (B_lego - B)^2)^(1/2)) %>%
|
||||
top_n(-1, dist) %>%
|
||||
mutate(Lego_color = rgb(R_lego, G_lego, B_lego)) %>%
|
||||
select(Lego_name = Color, Lego_color)
|
||||
}
|
||||
|
||||
legoize <- function(image_list, theme = "default", contrast = 1){
|
||||
in_list <- image_list
|
||||
|
||||
color_table <- lego_colors
|
||||
|
||||
if(theme == "default"){
|
||||
#Speed up calc by round pixel to nearest 1/20 & only calculating unique
|
||||
mosaic_colors <- in_list$Img_scaled %>%
|
||||
mutate_at(vars(R, G, B), funs(round(.*20)/20)) %>%
|
||||
select(R, G, B) %>%
|
||||
distinct() %>%
|
||||
mutate(lego = purrr::pmap(list(R, G, B), convert_to_lego_colors, color_table)) %>%
|
||||
unnest(lego)
|
||||
|
||||
img <- in_list$Img_scaled %>%
|
||||
mutate_at(vars(R, G, B), funs(round(.*20)/20)) %>%
|
||||
left_join(mosaic_colors, by = c("R", "G", "B"))
|
||||
|
||||
} else if (theme == "bw"){
|
||||
#Black and white is simpler... cut the colors into 4 groups, then assign lightest = white, darkest = black
|
||||
bw_colors <- color_table %>%
|
||||
filter(t_BW) %>%
|
||||
arrange((R_lego + G_lego + B_lego)) %>%
|
||||
mutate(Lego_color = rgb(R_lego, G_lego, B_lego))
|
||||
|
||||
img <- in_list$Img_scaled %>%
|
||||
mutate(shade = (R+G+B)/3,
|
||||
shade = shade ^ contrast) %>%
|
||||
mutate(shade_bw = as.numeric(as.factor(cut(shade, 4)))) %>%
|
||||
mutate(Lego_name = bw_colors$Color[shade_bw],
|
||||
Lego_color = bw_colors$Lego_color[shade_bw]) %>%
|
||||
select(-starts_with("shade"))
|
||||
|
||||
}
|
||||
in_list[["Img_lego"]] <- img
|
||||
|
||||
return(in_list)
|
||||
|
||||
}
|
||||
|
||||
#3 collect_bricks - Combine bricks into larger ones ----
|
||||
collect_bricks <- function(image_list, mosaic_type = "flat"){
|
||||
in_list <- image_list
|
||||
|
||||
if(mosaic_type == "flat"){
|
||||
img <- in_list$Img_lego %>%
|
||||
select(x, y, Lego_name, Lego_color) %>%
|
||||
#4x2 bricks - horizontal
|
||||
group_by(xg = x %/% 4, yg = y %/% 2) %>%
|
||||
mutate(g_1_x4y2_0 = ifelse(length(unique(Lego_name)) == 1 & n() == 8,
|
||||
paste0("x4y2_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#4x2 bricks - vertical
|
||||
ungroup() %>% group_by(xg = x %/% 2, yg = y %/% 4) %>%
|
||||
mutate(g_2_x2y4_0 = ifelse(length(unique(Lego_name)) == 1 & n() == 8,
|
||||
paste0("x2y4_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#2x2 bricks
|
||||
ungroup() %>% group_by(xg = x %/% 2, yg = y %/% 2) %>%
|
||||
mutate(g_5_x2y2_0 = ifelse(length(unique(Lego_name)) == 1 & n() == 4,
|
||||
paste0("x2y2_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#4x1 bricks - horizontal
|
||||
ungroup() %>% group_by(xg = x %/% 4, yg = y ) %>%
|
||||
mutate(g_7_x4y1_0 = ifelse(length(unique(Lego_name)) == 1 & n() == 4,
|
||||
paste0("x4y1_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#4x1 bricks - vertical
|
||||
ungroup() %>% group_by(xg = x, yg = y %/% 4) %>%
|
||||
mutate(g_8_x1y4_1 = ifelse(length(unique(Lego_name)) == 1 & n() == 4,
|
||||
paste0("x1y4_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#3x1 bricks - horizontal
|
||||
ungroup() %>% group_by(xg = x %/% 3, yg = y ) %>%
|
||||
mutate(g_7_x3y1_0 = ifelse(length(unique(Lego_name)) == 1 & n() == 3,
|
||||
paste0("x3y1_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#3x1 bricks - vertical
|
||||
ungroup() %>% group_by(xg = x, yg = y %/% 3) %>%
|
||||
mutate(g_8_x1y3_1 = ifelse(length(unique(Lego_name)) == 1 & n() == 3,
|
||||
paste0("x1y3_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#2x1 bricks - horizontal
|
||||
ungroup() %>% group_by(xg = x %/% 2, yg = y ) %>%
|
||||
mutate(g_9_x2y1_0 = ifelse(length(unique(Lego_name)) == 1 & n() == 2,
|
||||
paste0("x2y1_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#2x1 bricks - vertical
|
||||
ungroup() %>% group_by(xg = x, yg = y %/% 2) %>%
|
||||
mutate(g_a_x1y2_1 = ifelse(length(unique(Lego_name)) == 1 & n() == 2,
|
||||
paste0("x1y2_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
ungroup() %>%
|
||||
#1x1
|
||||
mutate(g_b_x1y1_0 = paste0("x1y1_", "x", x, "_y", y)) %>%
|
||||
select(-xg, -yg)
|
||||
}
|
||||
else if(mosaic_type == "stacked"){
|
||||
img <- in_list$Img_lego %>%
|
||||
select(x, y, Lego_name, Lego_color) %>%
|
||||
#4x1 bricks - horizontal
|
||||
ungroup() %>% group_by(xg = (x + y %% 4) %/% 4, yg = y ) %>%
|
||||
mutate(g_7_x4y1_0 = ifelse(length(unique(Lego_name)) == 1 & n() == 4,
|
||||
paste0("x4y1_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#3x1 bricks - horizontal
|
||||
ungroup() %>% group_by(xg = (x + y %% 3) %/% 3, yg = y ) %>%
|
||||
mutate(g_7_x3y1_0 = ifelse(length(unique(Lego_name)) == 1 & n() == 3,
|
||||
paste0("x3y1_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
#2x1 bricks - horizontal
|
||||
ungroup() %>% group_by(xg = (x + y %% 2) %/% 2, yg = y ) %>%
|
||||
mutate(g_9_x2y1_0 = ifelse(length(unique(Lego_name)) == 1 & n() == 2,
|
||||
paste0("x2y1_", "x", min(x), "_y", min(y)), NA)) %>%
|
||||
ungroup() %>%
|
||||
#1x1
|
||||
mutate(g_b_x1y1_0 = paste0("x1y1_", "x", x, "_y", y)) %>%
|
||||
select(-xg, -yg)
|
||||
}
|
||||
else(stop("Use mosaic_type = 'flat' or 'stacked'"))
|
||||
|
||||
# New calculation for piece placement, March 1, 2019.
|
||||
# https://github.com/ryantimpe/LEGOMosaics/issues/2
|
||||
img2a <- img %>%
|
||||
gather(Brick, brick_id, dplyr::starts_with("g_"))
|
||||
|
||||
bricks <- unique(img2a$Brick)
|
||||
|
||||
bricks_df <- img2a %>%
|
||||
filter(row_number() <1)
|
||||
|
||||
#Iteratively go through each brick, in order from largest to smallest, removing them and then checking the remaining image for complete bricks.
|
||||
for(bb in bricks){
|
||||
dat <- img2a %>%
|
||||
filter(Brick == bb) %>%
|
||||
drop_na(brick_id) %>%
|
||||
anti_join(bricks_df, by = c("x", "y")) %>%
|
||||
#Necessary Area
|
||||
mutate(area_tar = as.numeric(substr(brick_id, 2,2)) * as.numeric(substr(brick_id, 4,4))) %>%
|
||||
#Actual Area
|
||||
group_by(brick_id) %>%
|
||||
mutate(area_act = n()) %>%
|
||||
ungroup() %>%
|
||||
#Drop rows where the areas don't match
|
||||
filter(area_act == area_tar) %>%
|
||||
select(-starts_with("area"))
|
||||
|
||||
bricks_df <- bricks_df %>%
|
||||
bind_rows(dat)
|
||||
}
|
||||
|
||||
img2 <- bricks_df %>%
|
||||
# min/max coord for geom_rect()
|
||||
group_by(Brick, brick_id, Lego_color, Lego_name) %>%
|
||||
summarise(xmin = min(x)-0.5, xmax = max(x)+0.5,
|
||||
ymin = min(y)-0.5, ymax = max(y)+0.5) %>%
|
||||
ungroup()
|
||||
|
||||
# PREVIOUS Method. Will delete soon
|
||||
# img2 <- img %>%
|
||||
# gather(Brick, brick_id, dplyr::starts_with("g_")) %>%
|
||||
# #Only keep first Brick group has a name
|
||||
# group_by(x, y) %>%
|
||||
# filter(Brick == Brick[min(which(!is.na(brick_id)))]) %>%
|
||||
# ungroup() %>%
|
||||
# # min/max coord for geom_rect()
|
||||
# group_by(Brick, brick_id, Lego_color, Lego_name) %>%
|
||||
# summarise(xmin = min(x)-0.5, xmax = max(x)+0.5,
|
||||
# ymin = min(y)-0.5, ymax = max(y)+0.5) %>%
|
||||
# ungroup()
|
||||
|
||||
# brick_ids <- img %>%
|
||||
# gather(Brick, brick_id, dplyr::starts_with("g_")) %>%
|
||||
# #Only keep first Brick group has a name
|
||||
# group_by(x, y) %>%
|
||||
# filter(Brick == Brick[min(which(!is.na(brick_id)))]) %>%
|
||||
# ungroup()
|
||||
|
||||
# This is very brute-force. Probably a much cleaner way to do this
|
||||
pcs <- img2 %>%
|
||||
select(Brick, brick_id, Lego_name, Lego_color) %>%
|
||||
distinct() %>%
|
||||
separate(Brick, c("g", "gn", "size", "gi")) %>%
|
||||
select(-dplyr::starts_with("g")) %>%
|
||||
mutate(size1 = as.numeric(substr(size, 2, 2)),
|
||||
size2 = as.numeric(substr(size, 4, 4))) %>%
|
||||
mutate(Brick_size = ifelse(size1>size2, paste(size1, "x", size2), paste(size2, "x" , size1))) %>%
|
||||
count(Brick_size, Lego_name, Lego_color)
|
||||
|
||||
#Replace "x 1" bricks with "x 2". More likely to be used for a stacked mosaic
|
||||
if(mosaic_type == "stacked"){
|
||||
pcs <- pcs %>%
|
||||
mutate(Brick_size = gsub("x 1", "x 2", Brick_size, fixed = TRUE))
|
||||
}
|
||||
|
||||
in_list[["Img_bricks"]] <- img2
|
||||
in_list[["ID_bricks"]] <- bricks_df
|
||||
in_list[["mosaic_type"]] <- mosaic_type
|
||||
in_list[["pieces"]] <- pcs
|
||||
|
||||
return(in_list)
|
||||
}
|
||||
|
||||
#3a display_set - plot output of collect_bricks() ----
|
||||
display_set <- function(image_list, title=NULL){
|
||||
in_list <- image_list
|
||||
image <- in_list$Img_bricks
|
||||
type <- in_list$mosaic_type
|
||||
|
||||
coord_x <- c(min(image$xmin)+0.5, max(image$xmax)-0.5)
|
||||
coord_y <- c(min(image$ymin)+0.5, max(image$ymax)-0.5)
|
||||
|
||||
img <- ggplot(image) +
|
||||
geom_rect(aes(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax,
|
||||
fill = Lego_color), color = "#333333")+
|
||||
scale_fill_identity()
|
||||
|
||||
if(type == "flat"){
|
||||
img <- img + geom_point(data = expand.grid(x=coord_x[1]:coord_x[2], y=coord_y[1]:coord_y[2]),
|
||||
aes(x=x, y=y), color = "#333333", alpha = 0.2, shape = 1, size = 2) +
|
||||
coord_fixed(expand = FALSE)
|
||||
} else {
|
||||
img <- img +
|
||||
coord_fixed(ratio = 6/5, expand = FALSE)
|
||||
}
|
||||
|
||||
img <- img+
|
||||
labs(title = title) +
|
||||
theme_minimal() +
|
||||
theme_lego
|
||||
|
||||
return(img)
|
||||
}
|
||||
|
||||
#4 Instructions ----
|
||||
generate_instructions <- function(image_list, num_steps=6) {
|
||||
in_list <- image_list
|
||||
image <- in_list$Img_bricks
|
||||
type <- in_list$mosaic_type
|
||||
|
||||
num_steps <- min(round(num_steps), 40)
|
||||
|
||||
rows_per_step <- ceiling((max(image$ymax)-0.5) / (num_steps+1))
|
||||
|
||||
create_steps <- function(a, n_steps) {
|
||||
if(a < n_steps){
|
||||
image %>%
|
||||
group_by(brick_id) %>%
|
||||
filter(min(ymin) <= a*rows_per_step+(min(image$ymin)+0.5)) %>%
|
||||
ungroup() %>%
|
||||
mutate(Step = paste("Step", (if(a<10){paste0('0', a)}else{a})))
|
||||
} else {
|
||||
image %>%
|
||||
mutate(Step = paste("Step", (if(a<10){paste0('0', a)}else{a})))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Ratio of the "pixels" is different for flat or stacked bricks
|
||||
if(type == "flat"){
|
||||
coord_ratio <- 1
|
||||
} else {
|
||||
coord_ratio <- 6/5
|
||||
}
|
||||
|
||||
1:num_steps %>%
|
||||
map(create_steps, num_steps) %>%
|
||||
bind_rows() %>%
|
||||
ggplot() +
|
||||
geom_rect(aes(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax,
|
||||
fill = Lego_color), color = "#333333")+
|
||||
scale_fill_identity() +
|
||||
coord_fixed(ratio = coord_ratio, expand = FALSE) +
|
||||
facet_wrap(~Step) +
|
||||
theme_minimal()+
|
||||
theme_lego
|
||||
}
|
||||
|
||||
#5 Piece count ----
|
||||
#Print as data frame
|
||||
table_pieces <- function(image_list){
|
||||
pcs <- image_list$pieces
|
||||
|
||||
pcs %>%
|
||||
select(-Lego_color) %>%
|
||||
spread(Brick_size, n, fill = 0) %>%
|
||||
rename(`LEGO Brick Color` = Lego_name)
|
||||
}
|
||||
|
||||
#Print as image
|
||||
display_pieces <- function(image_list){
|
||||
in_list <- image_list
|
||||
pcs <- in_list$pieces
|
||||
|
||||
if(in_list$mosaic_type == "flat"){
|
||||
pcs_coords <- tibble(
|
||||
Brick_size = c("1 x 1", "2 x 1", "3 x 1", "4 x 1", "2 x 2", "4 x 2"),
|
||||
xmin = c(0, 0, 0, 0, 6, 6),
|
||||
xmax = c(1, 2, 3, 4, 8, 8),
|
||||
ymin = c(0, 2, 4, 6, 0, 3),
|
||||
ymax = c(1, 3, 5, 7, 2, 7)
|
||||
)
|
||||
} else {
|
||||
pcs_coords <- tibble(
|
||||
Brick_size = c("1 x 2", "2 x 2", "3 x 2", "4 x 2"),
|
||||
xmin = c(0, 5, 5, 0),
|
||||
xmax = c(2, 7, 7, 2),
|
||||
ymin = c(0, 0, 3, 2),
|
||||
ymax = c(1, 2, 6, 6)
|
||||
)
|
||||
}
|
||||
#This function creates nodes in each brick for stud placement
|
||||
pcs_coords <- pcs_coords %>%
|
||||
mutate(studs = purrr::pmap(list(xmin, xmax, ymin, ymax), function(a, b, c, d){
|
||||
expand.grid(x=seq(a+0.5, b-0.5, by=1),
|
||||
y=seq(c+0.5, d-0.5, by=1))
|
||||
}))
|
||||
|
||||
pcs2 <- pcs %>%
|
||||
arrange(Lego_color) %>%
|
||||
mutate(Lego_name = factor(Lego_name,
|
||||
levels = c("Black",
|
||||
unique(Lego_name)[!(unique(Lego_name) %in% c("Black", "White"))],
|
||||
"White"))) %>%
|
||||
left_join(pcs_coords, by = "Brick_size")
|
||||
|
||||
if(in_list$mosaic_type == "flat"){
|
||||
coord_xlim <- c(-0.5, 10)
|
||||
facet_cols <- 5
|
||||
} else {
|
||||
coord_xlim <- c(-0.5, 9)
|
||||
facet_cols <- 6
|
||||
}
|
||||
|
||||
pcs2 %>%
|
||||
ggplot() +
|
||||
geom_rect(aes(xmin=xmin, xmax=xmax, ymin=-ymin, ymax=-ymax,
|
||||
fill = Lego_color), color = "#333333")+
|
||||
scale_fill_identity() +
|
||||
geom_point(data = pcs2 %>% unnest(studs),
|
||||
aes(x=x, y=-y), color = "#cccccc", alpha = 0.25,
|
||||
shape = 1, size = 2) +
|
||||
geom_text(aes(x = xmax + 0.25, y = -(ymin+ymax)/2, label = paste0("x", n)),
|
||||
hjust = 0, vjust = 0.5, size = 3.5) +
|
||||
coord_fixed(xlim = coord_xlim) +
|
||||
labs(title = (if(in_list$mosaic_type == "stacked"){
|
||||
"Suggested LEGO Bricks"
|
||||
}else{"Suggested LEGO Plates"}),
|
||||
caption = (if(in_list$mosaic_type == "stacked"){
|
||||
"Mosaic is 2-bricks deep. Can substitute 2-stud bricks for 1-stud alternatives for a thinner mosaic."}else{""})) +
|
||||
facet_wrap(~Lego_name, ncol=facet_cols) +
|
||||
theme_minimal()+
|
||||
theme_lego +
|
||||
theme(
|
||||
panel.grid = element_blank()
|
||||
)
|
||||
}
|
||||
|
||||
#3D Functions with rayshader! ----
|
||||
|
||||
collect_3d <- function(image_list, mosaic_height = 6, highest_el = "light"){
|
||||
#Get previous data
|
||||
in_list <- image_list
|
||||
|
||||
if(in_list$mosaic_type != "flat")stop("3D mosaics can only be generated with 'flat' mosaics. Set this input in the 'collect_bricks' function.")
|
||||
|
||||
BrickIDs <- in_list$ID_bricks
|
||||
img_lego <- in_list$Img_lego
|
||||
|
||||
#Number of 'pixels' on a side of a single-stud brick. I think this should be fixed for now
|
||||
ex_size <- 15
|
||||
|
||||
lego_expand <- img_lego %>%
|
||||
select(x, y, Lego_name, Lego_color) %>%
|
||||
mutate(stud_id = row_number())
|
||||
|
||||
lego_expand2 <- expand.grid(x = (min(lego_expand$x)*ex_size):(max(lego_expand$x+1)*ex_size),
|
||||
y = (min(lego_expand$y)*ex_size):(max(lego_expand$y+1)*ex_size)) %>%
|
||||
mutate(x_comp = x %/% ex_size,
|
||||
y_comp = y %/% ex_size) %>%
|
||||
left_join(lego_expand %>% rename(x_comp = x, y_comp = y), by = c("x_comp", "y_comp")) %>%
|
||||
left_join(BrickIDs %>% select(brick_id, x_comp = x, y_comp = y), by = c("x_comp", "y_comp")) %>%
|
||||
select(-x_comp, -y_comp) %>%
|
||||
left_join(lego_colors %>% select(Lego_name = Color, R_lego, G_lego, B_lego), by = "Lego_name") %>%
|
||||
do(
|
||||
if(highest_el == "dark"){
|
||||
mutate(., elevation = (1-((R_lego + G_lego + B_lego )/3)) * 1000)
|
||||
} else {
|
||||
mutate(., elevation = (((R_lego + G_lego + B_lego )/3)) * 1000)
|
||||
}
|
||||
) %>%
|
||||
#Round elevation to nearest 1/height
|
||||
mutate(elevation = as.numeric(as.factor(cut(elevation, mosaic_height)))) %>%
|
||||
mutate(y = max(y)-y) %>%
|
||||
filter(!is.na(elevation)) %>%
|
||||
#Calculate stud placement... radius of 1/3 and height of 0.5 plate
|
||||
group_by(stud_id) %>%
|
||||
mutate(x_mid = median(x), y_mid = median(y),
|
||||
stud = ((x-x_mid)^2 + (y-y_mid)^2)^(1/2) < ex_size/3) %>%
|
||||
ungroup() %>%
|
||||
mutate(elevation = ifelse(stud, elevation+0.5, elevation)) %>%
|
||||
mutate_at(vars(R_lego, G_lego, B_lego), funs(ifelse(stud, .-0.1, .))) %>%
|
||||
mutate_at(vars(R_lego, G_lego, B_lego), funs(ifelse(. < 0, 0, .)))
|
||||
|
||||
edges <- bind_rows(list(
|
||||
lego_expand2 %>% filter(x == min(x)) %>% mutate(x = x-1),
|
||||
lego_expand2 %>% filter(x == max(x)) %>% mutate(x = x+1),
|
||||
lego_expand2 %>% filter(y == min(y)) %>% mutate(y = y-1),
|
||||
lego_expand2 %>% filter(y == max(y)) %>% mutate(y = y+1)
|
||||
)) %>%
|
||||
mutate(R_lego = 1, G_lego = 1, B_lego = 1,
|
||||
elevation = 0,
|
||||
brick_id = NA)
|
||||
|
||||
#Elevation Matrix
|
||||
lego_elmat <- lego_expand2 %>%
|
||||
bind_rows(edges) %>%
|
||||
select(x, y, elevation) %>%
|
||||
spread(y, elevation) %>%
|
||||
select(-x) %>%
|
||||
as.matrix()
|
||||
|
||||
#Hillshade matrix
|
||||
lego_hillshade_m <- array(dim = c(length(unique(lego_expand2$y)), length(unique(lego_expand2$x)), 3))
|
||||
|
||||
lego_expand_color <- lego_expand2 %>%
|
||||
group_by(brick_id) %>%
|
||||
#This darkens the edge of each brick, to look like they are separated
|
||||
mutate_at(vars(R_lego, G_lego, B_lego),
|
||||
funs(ifelse((x == min(x) | y == min(y) | x == max(x) | y == max(y)), .*0.4, .))) %>%
|
||||
ungroup()
|
||||
|
||||
lego_hillshade_m[,,1] <- lego_expand_color %>%
|
||||
select(x, y, R_lego) %>%
|
||||
spread(x, R_lego) %>%
|
||||
select(-y) %>%
|
||||
as.matrix()
|
||||
|
||||
lego_hillshade_m[,,2] <- lego_expand_color %>%
|
||||
select(x, y, G_lego) %>%
|
||||
spread(x, G_lego) %>%
|
||||
select(-y) %>%
|
||||
as.matrix()
|
||||
|
||||
lego_hillshade_m[,,3] <- lego_expand_color %>%
|
||||
select(x, y, B_lego) %>%
|
||||
spread(x, B_lego) %>%
|
||||
select(-y) %>%
|
||||
as.matrix()
|
||||
|
||||
#Return
|
||||
in_list[["threed_elevation"]] <- lego_elmat
|
||||
in_list[["threed_hillshade"]] <- lego_hillshade_m
|
||||
|
||||
return(in_list)
|
||||
|
||||
}
|
||||
|
||||
display_3d <- function(image_list, solidcolor = "#a3a2a4", ...){
|
||||
image_list$`threed_hillshade`%>%
|
||||
rayshader::plot_3d(image_list$`threed_elevation`, zscale=0.125, solidcolor=solidcolor, ...)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Package: brickr
|
||||
Title: Create Simulated LEGO Models from Images or Data Frames
|
||||
Version: 0.0.0.9150
|
||||
Version: 0.0.0.9200
|
||||
Authors@R:
|
||||
person(given = "Ryan",
|
||||
family = "Timpe",
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
LEGOEmmet.jpg
|
||||
LEGOMovie2.jpg
|
||||
RyBob2019.jpg
|
||||
Datasaurs1.jpg
|
||||
Datasaurs2.jpg
|
||||
LEGOEmmetRapt.JPG
|
||||
LEGOEmmetRapt2.JPG
|
||||
beaarthur.jpg
|
||||
torosaurus.JPG
|
||||
UP.PNG
|
||||
brickr.JPG
|
||||
brickr_logo.xlsx
|
||||
GGDepth.tif
|
||||
RyanColor.png
|
||||
RyanColor2.tif
|
||||
RyanDepth.png
|
||||
RyanDepth1.tif
|
||||
goldengirlsDEPTH.JPG
|
||||
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 141 KiB |
@@ -2,6 +2,7 @@
|
||||
|
||||
export("%>%")
|
||||
export(bricks_from_coords)
|
||||
export(bricks_from_excel)
|
||||
export(bricks_from_table)
|
||||
export(collect_3d)
|
||||
export(collect_bricks)
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# brickr 0.0.0.9200
|
||||
|
||||
* Added `bricks_from_excel()` as a way to decrease the starting cost of using brickr.
|
||||
* See [https://github.com/ryantimpe/brickr_toybox](https://github.com/ryantimpe/brickr_toybox)
|
||||
|
||||
# brickr 0.0.0.9150
|
||||
|
||||
* Added `bricks_from_coords()` function to convert a long data frame with x, y, z, and Color columns into input for `display_bricks()`.
|
||||
|
||||
@@ -101,6 +101,56 @@ bricks_from_table <- function(matrix_table, color_guide = lego_colors, .re_level
|
||||
)
|
||||
}
|
||||
|
||||
#' Convert an Excel {brickr} template into a 3D model. https://github.com/ryantimpe/brickr_toybox
|
||||
#' @param excel_table Sheet imported from a brickr Excel template to build model. Contains stud placement and colors.
|
||||
#' @param repeat_levels How many times to repeat a level. Can save time in model planning. Default is 1.
|
||||
#' @inheritParams bricks_from_table
|
||||
#' @return A list with elements \code{Img_lego} to pass to \code{collect_bricks()}.
|
||||
#' @export
|
||||
#'
|
||||
bricks_from_excel <- function(excel_table, repeat_levels = 1,
|
||||
increment_level = 0, max_level = Inf,
|
||||
increment_x = 0, max_x = Inf,
|
||||
increment_y = 0, max_y = Inf,
|
||||
exclude_color = NULL, exclude_level = NULL){
|
||||
|
||||
columns_meta_start <- max(which(grepl("^\\d", names(excel_table))))
|
||||
|
||||
#Set Instructions
|
||||
instructions <- excel_table %>%
|
||||
dplyr::select(1:columns_meta_start) %>%
|
||||
dplyr::rename(Level = 1) %>%
|
||||
dplyr::filter(Level != "Level")
|
||||
|
||||
#Repeat levels.
|
||||
#TODO: DO this for x and y too
|
||||
if(is.numeric(repeat_levels)){
|
||||
rep_levels <- max(round(repeat_levels), 1)
|
||||
|
||||
if(rep_levels == 1){instructions <- instructions}
|
||||
else{
|
||||
instructions <- 1:rep_levels %>%
|
||||
purrr::map_df(
|
||||
~dplyr::mutate(instructions, Level = paste0(Level, .x))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#Color Instructions
|
||||
colors_user <- excel_table %>%
|
||||
dplyr::select(.value = user_color, Color = LEGO_color) %>%
|
||||
tidyr::drop_na()
|
||||
|
||||
#Render as brickr output
|
||||
brickr_out <- instructions %>%
|
||||
bricks_from_table(color_guide = colors_user,
|
||||
.re_level = TRUE,
|
||||
increment_level = increment_level, max_level = max_level,
|
||||
increment_x = increment_x, max_x = max_x,
|
||||
increment_y = increment_y, max_y = max_y,
|
||||
exclude_color = exclude_color, exclude_level = exclude_level)
|
||||
return(brickr_out)
|
||||
}
|
||||
|
||||
#' Convert a data frame with x, y, & z coordinates & Color into bricks for 3D Model
|
||||
#'
|
||||
|
||||
@@ -46,6 +46,8 @@ Currently, 3D models can be built from one of two data input formats: `bricks_fr
|
||||
|
||||
- `bricks_from_table()` converts a matrix-shaped table of integers into LEGO bricks. For simple models, this table can be made manually using `data.frame()` or `tibble::tribble()`. For more advanced models, it's recommended you use MS Excel or a .csv file. The left-most column in the table is associated with the Level or z-axis of the model. The function by default converts this to numeric for you. Each other column is an x-coordinate and each row is a y-coordinate. More flexible inputs will be available in a future release.
|
||||
|
||||
- `bricks_from_excel()` is a wrapper function to more easily build models designed using a Microsoft Excel template. Please see this repo: [brickr toybox](https://github.com/ryantimpe/brickr_toybox).
|
||||
|
||||
- `bricks_from_coords()` takes a data frame with `x`, `y`, & `z` integer values, and `Color` columns, where each combination of x, y, & z is a point in 3-dimensional space. Color must be an official LEGO color name from `display_colors()`. This format is much more flexible than `bricks_from_table()` and allows the programatic development of 3D models.
|
||||
|
||||
Pass the output from any `bricks_from_*()` function to `display_bricks()` to see the 3D model.
|
||||
@@ -222,6 +224,7 @@ More examples using `bricks_from_table()` and `bricks_from_coords()` can be foun
|
||||
- [**Get started**](https://gist.github.com/ryantimpe/a784beaa4f798f57010369329d46ce71) with the framework for building a brick from scratch.
|
||||
- [**Build an owl**](https://gist.github.com/ryantimpe/ceab2ed6b8a4737077280fc9b0d1c886) with `bricks_from_table()` by manually placing each brick.
|
||||
- Generate a punny [**random forest model**](https://gist.github.com/ryantimpe/a7363a5e99dceabada150a43925beec7) using `bricks_from_coords()` and {purrr}.
|
||||
- [**brickr toybox**](https://github.com/ryantimpe/brickr_toybox) repo for tools and resources to get started.
|
||||
|
||||
## Mosaics
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ documentation will be released soon.**
|
||||
y-coordinate. More flexible inputs will be available in a future
|
||||
release.
|
||||
|
||||
- `bricks_from_excel()` is a wrapper function to more easily build
|
||||
models designed using a Microsoft Excel template. Please see this
|
||||
repo: [brickr toybox](https://github.com/ryantimpe/brickr_toybox).
|
||||
|
||||
- `bricks_from_coords()` takes a data frame with `x`, `y`, & `z`
|
||||
integer values, and `Color` columns, where each combination of x, y,
|
||||
& z is a point in 3-dimensional space. Color must be an official
|
||||
@@ -253,6 +257,8 @@ be found at the links below.
|
||||
- Generate a punny [**random forest
|
||||
model**](https://gist.github.com/ryantimpe/a7363a5e99dceabada150a43925beec7)
|
||||
using `bricks_from_coords()` and {purrr}.
|
||||
- [**brickr toybox**](https://github.com/ryantimpe/brickr_toybox) repo
|
||||
for tools and resources to get started.
|
||||
|
||||
## Mosaics
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/bricks_from.R
|
||||
\name{bricks_from_excel}
|
||||
\alias{bricks_from_excel}
|
||||
\title{Convert an Excel {brickr} template into a 3D model}
|
||||
\usage{
|
||||
bricks_from_excel(excel_table, repeat_levels = 1, increment_level = 0,
|
||||
max_level = Inf, increment_x = 0, max_x = Inf, increment_y = 0,
|
||||
max_y = Inf, exclude_color = NULL, exclude_level = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{excel_table}{Sheet imported from a brickr Excel template to build model. Contains stud placement and colors.}
|
||||
|
||||
\item{repeat_levels}{How many times to repeat a level. Can save time in model planning. Default is 1.}
|
||||
|
||||
\item{increment_level}{Default '0'. Use in animations. Shift Level/z dimension by an integer.}
|
||||
|
||||
\item{max_level}{Default 'Inf'. Use in animations. Any Level/z values above this value will be cut off.}
|
||||
|
||||
\item{increment_x}{Default '0'. Use in animations. Shift x dimension by an integer.}
|
||||
|
||||
\item{max_x}{Default 'Inf'. Use in animations. Any x values above this value will be cut off.}
|
||||
|
||||
\item{increment_y}{Default '0'. Use in animations. Shift y dimension by an integer.}
|
||||
|
||||
\item{max_y}{Default 'Inf'. Use in animations. Any y values above this value will be cut off.}
|
||||
|
||||
\item{exclude_color}{Numeric array of color ID numbers to exclude.}
|
||||
|
||||
\item{exclude_level}{Numeric array of Level/z dimensions to exclude.}
|
||||
}
|
||||
\value{
|
||||
A list with elements \code{Img_lego} to pass to \code{collect_bricks()}.
|
||||
}
|
||||
\description{
|
||||
Convert an Excel {brickr} template into a 3D model
|
||||
}
|
||||