|
|
|
|
|
|
|
|
|
|
|
import numpy as np |
|
import math |
|
import torch |
|
import os |
|
from PIL import Image, ImageDraw |
|
from ..categories import icons |
|
from ..config import color_mapping, COLORS |
|
from pywavefront import Wavefront |
|
|
|
|
|
|
|
def tensor2pil(image): |
|
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)) |
|
|
|
def pil2tensor(image): |
|
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) |
|
|
|
def align_text(align_txt, img_center_x, img_center_y, img_width, img_height, pos_x, pos_y, txt_width, txt_height, txt_padding): |
|
if align_txt == "center": |
|
txt_center_x = img_center_x + pos_x - txt_width / 2 |
|
txt_center_y = img_center_y + pos_y - txt_height / 2 |
|
elif align_txt == "top left": |
|
txt_center_x = pos_x + txt_padding |
|
txt_center_y = pos_y + txt_padding |
|
if align_txt == "top right": |
|
txt_center_x = img_width + pos_x - txt_width - txt_padding |
|
txt_center_y = pos_y + txt_padding |
|
elif align_txt == "top center": |
|
txt_center_x = img_width/2 + pos_x - txt_width/2 - txt_padding |
|
txt_center_y = pos_y + txt_padding |
|
elif align_txt == "bottom left": |
|
txt_center_x = pos_x + txt_padding |
|
txt_center_y = img_height + pos_y - txt_height - txt_padding |
|
elif align_txt == "bottom right": |
|
txt_center_x = img_width + pos_x - txt_width - txt_padding |
|
txt_center_y = img_height + pos_y - txt_height - txt_padding |
|
elif align_txt == "bottom center": |
|
txt_center_x = img_width/2 + pos_x - txt_width/2 - txt_padding |
|
txt_center_y = img_height + pos_y - txt_height - txt_padding |
|
return (txt_center_x, txt_center_y, ) |
|
|
|
|
|
class CR_3DPolygon: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
shapes = ["cube","tetrahedron"] |
|
|
|
return {"required": { |
|
"shape": (shapes,), |
|
"image_width": ("INT", {"default": 512, "min": 64, "max": 2048}), |
|
"image_height": ("INT", {"default": 512, "min": 64, "max": 2048}), |
|
"radius": ("INT", {"default": 100, "min": 2, "max": 2048}), |
|
"distance": ("INT", {"default": 200, "min": 2, "max": 2048}), |
|
"rotation_angle": ("FLOAT", {"default": 0, "min": 0, "max": 3600, "step": 0.5}), |
|
}, |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", ) |
|
FUNCTION = "draw_cube" |
|
CATEGORY = icons.get("Comfyroll/Graphics/3D") |
|
|
|
def draw_cube(self, shape, image_width, image_height, radius, distance, rotation_angle=45): |
|
|
|
size = (image_height, image_width) |
|
image = Image.new("RGB", size) |
|
draw = ImageDraw.Draw(image) |
|
|
|
if shape == "cube": |
|
vertices = [ |
|
(-radius, -radius, -radius), |
|
(radius, -radius, -radius), |
|
(radius, radius, -radius), |
|
(-radius, radius, -radius), |
|
(-radius, -radius, radius), |
|
(radius, -radius, radius), |
|
(radius, radius, radius), |
|
(-radius, radius, radius) |
|
] |
|
edges = [ |
|
(0, 1), (1, 2), (2, 3), (3, 0), |
|
(4, 5), (5, 6), (6, 7), (7, 4), |
|
(0, 4), (1, 5), (2, 6), (3, 7) |
|
] |
|
elif shape == "tetrahedron": |
|
vertices = [ |
|
(0, radius, 0), |
|
(radius, -radius, -radius), |
|
(-radius, -radius, -radius), |
|
(0, -radius, radius) |
|
] |
|
edges = [ |
|
(0, 1), (0, 2), (0, 3), |
|
(1, 2), (2, 3), (3, 1) |
|
] |
|
|
|
|
|
def project_point(point): |
|
x, y, z = point |
|
x_2d = x * distance / (z + distance) + size[0] / 2 |
|
y_2d = y * distance / (z + distance) + size[1] / 2 |
|
return x_2d, y_2d |
|
|
|
|
|
rotated_vertices = [] |
|
angle = math.radians(rotation_angle) |
|
cos_a = math.cos(angle) |
|
sin_a = math.sin(angle) |
|
for vertex in vertices: |
|
x, y, z = vertex |
|
new_x = x * cos_a - z * sin_a |
|
new_z = x * sin_a + z * cos_a |
|
rotated_vertices.append((new_x, y, new_z)) |
|
|
|
|
|
for edge in edges: |
|
start_point = project_point(rotated_vertices[edge[0]]) |
|
end_point = project_point(rotated_vertices[edge[1]]) |
|
draw.line([start_point, end_point], fill=(255, 255, 255)) |
|
|
|
|
|
tensor_image = pil2tensor(image) |
|
|
|
return (tensor_image,) |
|
|
|
|
|
class CR_3DSolids: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
return {"required": { |
|
"image_width": ("INT", {"default": 512, "min": 64, "max": 2048}), |
|
"image_height": ("INT", {"default": 512, "min": 64, "max": 2048}), |
|
"radius": ("INT", {"default": 100, "min": 2, "max": 2048}), |
|
"height": ("INT", {"default": 100, "min": 2, "max": 2048}), |
|
"distance": ("INT", {"default": 200, "min": 2, "max": 2048}), |
|
"rotation_angle": ("FLOAT", {"default": 0, "min": 0, "max": 3600, "step": 0.5}), |
|
}, |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", ) |
|
FUNCTION = "draw" |
|
CATEGORY = icons.get("Comfyroll/Graphics/3D") |
|
|
|
def draw(self, image_width, image_height, radius, height, distance, rotation_angle=45): |
|
|
|
|
|
size = (image_height, image_width) |
|
image = Image.new("RGB", size) |
|
draw = ImageDraw.Draw(image) |
|
|
|
|
|
vertices = [ |
|
(0, height / 2, 0), |
|
(0, -height / 2, 0) |
|
] |
|
|
|
num_points = 20 |
|
base_points = [ |
|
(radius * math.cos(2 * math.pi * i / num_points), -height / 2, radius * math.sin(2 * math.pi * i / num_points)) |
|
for i in range(num_points) |
|
] |
|
vertices = vertices + base_points |
|
|
|
|
|
edges = [] |
|
for i in range(num_points): |
|
edges.append((0, i + 2)) |
|
edges.append((1, i + 2)) |
|
edges.append((i + 2, (i + 3) if i < num_points - 1 else 2)) |
|
|
|
|
|
def project_point(point): |
|
x, y, z = point |
|
x_2d = x * distance / (z + distance) + size[0] / 2 |
|
y_2d = y * distance / (z + distance) + size[1] / 2 |
|
return x_2d, y_2d |
|
|
|
|
|
rotated_vertices = [] |
|
angle = math.radians(rotation_angle) |
|
cos_a = math.cos(angle) |
|
sin_a = math.sin(angle) |
|
for vertex in vertices: |
|
x, y, z = vertex |
|
new_x = x * cos_a - z * sin_a |
|
new_z = x * sin_a + z * cos_a |
|
rotated_vertices.append((new_x, y, new_z)) |
|
|
|
|
|
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] |
|
for i in range(num_points): |
|
vertices_indices = [0, i + 2, (i + 3) if i < num_points - 1 else 2] |
|
face_vertices = [project_point(rotated_vertices[idx]) for idx in vertices_indices] |
|
fill_color = colors[i % 3] |
|
draw.polygon(face_vertices, fill=fill_color) |
|
|
|
|
|
for edge in edges: |
|
start_point = project_point(rotated_vertices[edge[0]]) |
|
end_point = project_point(rotated_vertices[edge[1]]) |
|
draw.line([start_point, end_point], fill=(0, 0, 0)) |
|
|
|
|
|
tensor_image = pil2tensor(image) |
|
|
|
return (tensor_image,) |
|
|
|
|
|
|
|
class CR_DrawOBJ: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
obj_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "obj") |
|
file_list = [f for f in os.listdir(obj_dir) if os.path.isfile(os.path.join(obj_dir, f)) and f.lower().endswith(".obj")] |
|
|
|
|
|
return {"required": { |
|
"image_width": ("INT", {"default": 512, "min": 64, "max": 2048}), |
|
"image_height": ("INT", {"default": 512, "min": 64, "max": 2048}), |
|
"obj_name": (file_list,), |
|
"line_color": (COLORS[1:],), |
|
}, |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", ) |
|
FUNCTION = "draw_wireframe" |
|
CATEGORY = icons.get("Comfyroll/Graphics/3D") |
|
|
|
def draw_wireframe(self, obj_name, image_width=800, image_height=800, line_color="black"): |
|
|
|
|
|
obj_file = "obj\\" + str(obj_name) |
|
resolved_obj_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), obj_file) |
|
scene = Wavefront(resolved_obj_path) |
|
|
|
|
|
img = Image.new("RGB", (image_width, image_height), (0, 0, 0)) |
|
draw = ImageDraw.Draw(img) |
|
|
|
for name, material in scene.materials.items(): |
|
for face in material.mesh.faces: |
|
vertices = [scene.vertices[i] for i in face] |
|
|
|
|
|
for i in range(len(vertices)): |
|
x1, y1, z1 = vertices[i] |
|
x2, y2, z2 = vertices[(i + 1) % len(vertices)] |
|
|
|
|
|
x1 = int((x1 + 1) * image_width / 2) |
|
y1 = int((1 - y1) * image_height / 2) |
|
x2 = int((x2 + 1) * image_width / 2) |
|
y2 = int((1 - y2) * image_height / 2) |
|
|
|
draw.line([(x1, y1), (x2, y2)], fill=line_color) |
|
|
|
|
|
tensor_image = pil2tensor(img) |
|
|
|
return (tensor_image,) |
|
|
|
|
|
|
|
|
|
|
|
''' |
|
NODE_CLASS_MAPPINGS = { |
|
"CR 3D Polygon":CR_3DPolygon, |
|
"CR 3D Solids":CR_3DSolids, |
|
"CR Draw OBJ":CR_DrawOBJ, |
|
} |
|
''' |
|
|
|
|