Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ def cad():
generation_id, iteration = generate_scad(query)
return {"id": generation_id, "iteration": iteration, "shapes": []}

@cross_origin()
@app.route("/models/generated/<generation_id>", methods=["GET"])
def get_iterations(generation_id):
directory = os.path.join("generated", str(generation_id))
if not os.path.exists(directory):
abort(404, description="Resource not found")
iterations = [name for name in os.listdir(directory) if os.path.isdir(os.path.join(directory, name))]
return jsonify(iterations)

@cross_origin()
@app.route("/models/generated/<generation_id>/<iteration>/output.stl")
def serve_stl(generation_id, iteration):
Expand Down
31 changes: 25 additions & 6 deletions generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,32 @@ def get_last_generated_scad(generation_id: str):
# creates .scad, .png, and .stl files
# places them in /generated/{generation_id}/{iteration}/output.{filetype}
def render_scad(code: str, generation_id: str, iteration: int) -> bool:
os.system(f"mkdir -p generated/{generation_id}/{iteration}")
os.system(f"echo '{code}' > generated/{generation_id}/{iteration}/output.scad")
png_command = f"openscad -o generated/{generation_id}/{iteration}/output.png generated/{generation_id}/{iteration}/output.scad"
stl_command = f"openscad -o generated/{generation_id}/{iteration}/output.stl generated/{generation_id}/{iteration}/output.scad"
png_success = os.system(png_command) == 0
# Ensure the output directory exists
output_dir = f"generated/{generation_id}/{iteration}"
os.makedirs(output_dir, exist_ok=True)

# Write the SCAD code to a file
scad_file = f"{output_dir}/output.scad"
with open(scad_file, 'w') as file:
file.write(code)

# Generate STL file
stl_command = f"openscad -o {output_dir}/output.stl {scad_file}"
stl_success = os.system(stl_command) == 0
return png_success and stl_success

# Call the bash script to generate images
bash_script = "./scad2image.sh"
bash_command = f"{bash_script} {scad_file}"
png_success = os.system(bash_command) == 0

# Check if the final combined image is created
final_image_path = f"{output_dir}/renders/final_image.png"
if png_success and os.path.exists(final_image_path):
# Move the final image to the output directory
os.rename(final_image_path, f"{output_dir}/output.png")
return stl_success
else:
return False


# Generates OpenSCAD code given an initial prompt
Expand Down
138 changes: 58 additions & 80 deletions ui/app/components/cad-viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,93 +1,71 @@
"use client"
"use client";

import { useEffect, useRef } from 'react'
import "../../dist/three-cad-viewer/three-cad-viewer.css"
import { Viewer } from "../../dist/three-cad-viewer/three-cad-viewer.esm.js"
import { useEffect, useRef } from 'react';
import "../../dist/three-cad-viewer/three-cad-viewer.css";
import { Viewer, Display } from "../../dist/three-cad-viewer/three-cad-viewer.esm.js";

function nc(change) {}
function nc(change) {
console.log("NOTIFY:", JSON.stringify(change, null, 2));
}

export interface CadViewerProps {
cadShapes?: any,
stlFileUrl?: string
cadShapes?: any;
stlFileUrl?: string;
}

export default function CadViewer({ cadShapes = [], stlFileUrl }: CadViewerProps) {
const ref = useRef(null)
const { innerWidth: width, innerHeight: height } = window

const viewerOptions = {
theme: "light",
ortho: true,
control: "trackball", // "orbit",
normalLen: 0,
cadWidth: width,
height: height * 0.85,
ticks: 10,
ambientIntensity: 0.9,
directIntensity: 0.12,
transparent: false,
blackEdges: false,
axes: true,
grid: [false, false, false],
timeit: false,
rotateSpeed: 1,
tools: false,
glass: false
}

const renderOptions = {
ambientIntensity: 1.0,
directIntensity: 1.1,
metalness: 0.30,
roughness: 0.65,
edgeColor: 0x707070,
defaultOpacity: 0.5,
normalLen: 0,
up: "Z"
}

useEffect(() => {
const container = ref.current
export default function CadViewer({ cadShapes = {}, stlFileUrl }: CadViewerProps) {
const ref = useRef<HTMLDivElement>(null);
const { innerWidth: width, innerHeight: height } = window;

if (stlFileUrl) {
const viewer = new Viewer(container, viewerOptions, nc)
fetch(stlFileUrl)
.then(response => response.arrayBuffer())
.then(data => {
const blob = new Blob([data], { type: 'application/octet-stream' })
const url = URL.createObjectURL(blob)
viewer.clear()
viewer.loadSTL(url)
viewer.render()
})
.catch(error => console.error(error))
}
}, [stlFileUrl])
const viewerOptions = {
theme: "light",
ortho: true,
control: "trackball",
normalLen: 0,
cadWidth: width,
height: height * 0.85,
treeWidth: 240,
ticks: 10,
ambientIntensity: 0.9,
directIntensity: 0.12,
transparent: false,
blackEdges: false,
axes: true,
grid: [false, false, false],
timeit: false,
rotateSpeed: 1,
};

useEffect(() => {
const container = ref.current
useEffect(() => {
const container = ref.current;

if (cadShapes && cadShapes.length > 0) {
const viewer = new Viewer(container, viewerOptions, nc)
if (container && stlFileUrl) {
const display = new Display(container, viewerOptions);
const viewer = new Viewer(display, true, viewerOptions, nc);
fetch(stlFileUrl)
.then(response => response.arrayBuffer())
.then(data => {
const blob = new Blob([data], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
viewer.clear();
viewer.addModelFromURL(url); // Ensure this is the correct method name
viewer.render();
})
.catch(error => console.error(error));
}
}, [stlFileUrl]);

render("input", ...cadShapes)
function render(name: string, shapes, states) {
viewer?.clear()
const [unselected, selected] = viewer.renderTessellatedShapes(shapes, states, renderOptions)
console.log(unselected)
console.log(selected)
useEffect(() => {
const container = ref.current;

viewer.render(
unselected,
selected,
states,
renderOptions,
)
}
}
}, [cadShapes])
if (container && cadShapes && Object.keys(cadShapes).length > 0) {
const display = new Display(container, viewerOptions);
const viewer = new Viewer(display, true, viewerOptions, nc);
viewer.render(cadShapes, {});
}
}, [cadShapes]);

return (
<div ref={ref} style={{ width: '100%', height: '100%' }}></div>
)
return (
<div ref={ref} style={{ width: '100%', height: '100%' }}></div>
);
}
130 changes: 67 additions & 63 deletions ui/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { Alert, Layout, Select, Space, theme } from 'antd';
import Search from 'antd/es/input/Search';
import { useState } from 'react';
import { getCadDownload as downloadCadFile, getCadShapes as getCadObject, getGeneratedFiles } from './api/cad';
import CadViewer from './components/cad-viewer';
const { Content } = Layout;

const BASE_URL = "http://127.0.0.1:5001"; // Ensure this URL matches your backend server
const test = true; // Set to true for hardcoded values, false for original fetching

export default function Home() {
const {
Expand All @@ -17,46 +16,67 @@ export default function Home() {
const [cadShapes, setCadShapes] = useState([]);
const [isError, setIsError] = useState(false);
const [cadID, setCadID] = useState<string>();
const [iteration, setIteration] = useState<string>();
const [generatedFiles, setGeneratedFiles] = useState<string[]>([]);
const [stlFileUrl, setStlFileUrl] = useState<string>();
const [iterations, setIterations] = useState<string[]>([]);
const [generatedFiles, setGeneratedFiles] = useState<Record<string, string[]>>({});

const onSearch = async (value: string) => {
try {
setIsError(false);
const cadObject = await getCadObject(value);
setCadShapes(cadObject.shapes);
setCadID(cadObject.id);
setIteration(cadObject.iteration);
const files = await getGeneratedFiles(cadObject.id, cadObject.iteration);
setGeneratedFiles(files);
setIsError(false);
const stlFile = files.find(file => file.endsWith('.stl'));
if (stlFile) {
setStlFileUrl(`${BASE_URL}/models/generated/${cadObject.id}/${cadObject.iteration}/${stlFile}`);
if (!test) {
const cadObject = await getCadObject(value);
setCadShapes(cadObject.shapes);
setCadID(cadObject.id);

// Fetch iterations for the generation
const iterationDirs = await fetchIterations(cadObject.id);
setIterations(iterationDirs);

// Fetch generated files for each iteration
const filesByIteration: Record<string, string[]> = {};
for (const iteration of iterationDirs) {
const files = await getGeneratedFiles(cadObject.id, iteration);
filesByIteration[iteration] = files;
}
setGeneratedFiles(filesByIteration);
} else {
// Hardcoded values for testing
setCadID("20240519094656");
setIterations(["0", "1"]);
setGeneratedFiles({
"0": ["output.png", "output.stl", "output.scad"],
"1": ["output.png", "output.stl", "output.scad"],
});
}
} catch {
console.log("error");

setIsError(false);
} catch (error) {
console.log(error);
setIsError(true);
}
};

const onDownload = async (file_type: string) => {
if (cadID && iteration) {
await downloadCadFile(cadID, iteration, file_type);
if (cadID && iterations.length > 0) {
await downloadCadFile(cadID, iterations[0], file_type); // Assuming download from the first iteration
}
};

const fetchIterations = async (generationId: string): Promise<string[]> => {
const response = await fetch(`${BASE_URL}/models/generated/${generationId}`);
const data = await response.json();
return data;
};

return (
<Layout style={{ height: "100vh" }}>
<Space wrap>
<Select
placeholder="Download"
style={{ width: 120 }}
onChange={onDownload}
options={generatedFiles.map(file => ({
value: file.split('.').pop(),
label: file,
options={iterations.map(iteration => ({
value: iteration,
label: `Iteration ${iteration}`,
}))}
/>
</Space>
Expand All @@ -72,50 +92,34 @@ export default function Home() {
alignItems: 'center',
}}
>
<Layout>
<CadViewer cadShapes={cadShapes} stlFileUrl={stlFileUrl} />
</Layout>
{generatedFiles.length > 0 && (
<div style={{ marginTop: 20 }}>
<h3>Generated Files:</h3>
<table>
<thead>
<tr>
<th>File Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{generatedFiles.map(file => (
<tr key={file}>
<td>{file}</td>
<td>
<a
href={`${BASE_URL}/models/generated/${cadID}/${iteration}/${file}`}
download
>
Download
</a>
</td>
</tr>
))}
</tbody>
</table>
{Object.keys(generatedFiles).map(iteration => (
<div key={iteration} style={{ marginBottom: 20 }}>
<h3>Iteration {iteration}</h3>
{generatedFiles[iteration].map(file => (
file.endsWith('output.png') && (
<img
key={file}
src={test ? `/20240519094656/${iteration}/${file}` : `${BASE_URL}/generated/${cadID}/${iteration}/${file}`}
alt={`Generated output ${file}`}
style={{ maxWidth: '100%', display: "block", marginBottom: '10px' }}
/>
)
))}
</div>
)}
</Content>
))}

{isError && (
<Space direction="vertical" style={{ width: '100%' }}>
<Alert
message="Error Generating"
description="Please try again. To debug check logs and 'generated' directory for latest file"
type="error"
/>
</Space>
)}
{isError && (
<Space direction="vertical" style={{ width: '100%' }}>
<Alert
message="Error Generating"
description="Please try again. To debug check logs and 'generated' directory for latest file"
type="error"
/>
</Space>
)}

<Search placeholder="input search text" size="large" onSearch={onSearch} />
<Search placeholder="input search text" size="large" onSearch={onSearch} />
</Content>
</Layout>
</Layout>
</Layout>
Expand Down