its a start

This commit is contained in:
Morgan 'ARR\!' Allen 2023-03-21 16:42:22 -07:00
commit 0dbe610d68
10 changed files with 420 additions and 0 deletions

13
README.md Normal file
View file

@ -0,0 +1,13 @@
Quick and dirty demo of using Flask and the Python FreeCAD modules to Spreadsheet based
variables and return the resulting model to a ThreeJS based viewer.
# Quickstart
```
git clone https://git.oit.cloud/morgan/freecad_webviewer_poc.git
cd freecad_webviewer_poc
python -m venv .
pip install flask
```
It is expected you have FreeCAD installed on your system and you might need to update line 2
in app.py

90
app.py Normal file
View file

@ -0,0 +1,90 @@
import sys
sys.path.append('/home/morgan/devel/FreeCAD-build/lib')
from flask import Flask, render_template, request, send_file
from hashlib import blake2b
import FreeCAD
import Part
import Import
import json
import time
from FreeCAD import Mesh
print(FreeCAD, Import, Part)
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
@app.route('/gltf/<name>.gltf')
def gen_gltf(name):
file = './models/{}.FCStd'.format(name)
doc = FreeCAD.openDocument(file)
body = doc.getObject('Body')
sheet = doc.getObject('Spreadsheet')
sheet.set('chole', request.args['count'])
doc.recompute()
out_file = u'static/{}x{}.gltf'.format(name, request.args['count'])
print('writing to {}'.format(out_file))
Import.export([ body ], out_file)
return send_file(out_file)
@app.route('/stl/<name>.stl')
def gen_stl(name):
file = './models/{}.FCStd'.format(name)
print('loading ', file)
doc = FreeCAD.openDocument(file)
print(doc)
#body = doc.getObject('Body')
# TODO support more types
bodies = [body for body in doc.findObjects('PartDesign::Body') if body.Visibility]
sheet = doc.getObject('Spreadsheet')
for k in request.args:
v = request.args[k]
print(k, v)
try:
sheet.set(k, v)
except Exception as e:
print(e)
doc.recompute()
arg_hash = blake2b(bytes(json.dumps(request.args), 'utf-8'), digest_size=10).hexdigest()
out_file = './stls/{}_{}.stl'.format(name, arg_hash)
Mesh.export(bodies, out_file)
return send_file(out_file)
@app.route('/upload', methods=['POST'])
def upload():
f = request.files['upload']
out_file = 'models/{}'.format(f.filename)
f.save(out_file)
doc = FreeCAD.openDocument(out_file)
sheet = doc.getObject('Spreadsheet')
cells = []
for cell in [cell for cell in sheet.getUsedCells() if sheet.getAlias(cell)]:
value = sheet.get(cell)
if hasattr(value, 'Value'): value = value.Value
cells.append({
'cell': cell,
'alias': sheet.getAlias(cell),
'value': value
})
return json.dumps(cells)
@app.route("/")
def hello_world():
return render_template('index.html')

BIN
models/master_rail.FCStd Normal file

Binary file not shown.

66
static/FileLoader.js Normal file
View file

@ -0,0 +1,66 @@
class FileLoaderElement extends HTMLElement {
constructor() {
super();
this.addEventListener('dragover', this.onDragOver.bind(this));
this.addEventListener('dragleave', this.onDragLeave.bind(this));
this.addEventListener('drop', this.onDrop.bind(this));
}
onDrop(evt) {
evt.preventDefault();
this.classList.remove('dragover');
var formData = new FormData();
[...evt.dataTransfer.items].forEach(function(item) {
console.log(item.getAsFile());
formData.append('upload', item.getAsFile());
});
var filename = evt.dataTransfer.files[0].name;
var self = this;
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.send(formData);
xhr.addEventListener('load', function(evt) {
try {
var data = JSON.parse(xhr.responseText);
} catch(e) {
var data = {};
}
self.dispatchEvent(new CustomEvent("upload_complete", {
detail: {
data: data,
filename: filename
}
}));
});
}
onXHRComplete(evt) {
}
onDragOver(evt) {
if(!evt.dataTransfer) return;
if(Array.from(evt.dataTransfer.items).filter(function(el) {
if(el.type == 'application/x-extension-fcstd') return el;
}).length > 0) {
this.classList.add('dragover');
evt.preventDefault();
}
}
onDragLeave(evt) {
this.classList.remove('dragover');
}
connectedCallback() {
this.innerHTML = '<center>Drop File Here<center>';
}
};
customElements.define('file-loader', FileLoaderElement);
export { FileLoaderElement };

39
static/ThreeGuiElement.js Normal file
View file

@ -0,0 +1,39 @@
import { GUI } from 'GUI';
export class ThreeGUIElement extends HTMLElement{
constructor() {
super();
this.GUI = new GUI();
this.GUI.onChange(this.onGUIValueChange.bind(this));
}
onGUIValueChange(evt) {
this.dispatchEvent(new CustomEvent('value-change', {
detail: {
property: evt.property,
value: evt.value
}
}));
}
reset() {
while(this.GUI.children.length > 0) {
this.GUI.children[0].destroy();
}
}
setElements(elements) {
var self = this;
this.elements = {};
var gui = this.GUI;
this.reset();
elements.forEach(function(parameter) {
console.log(parameter)
self.elements[parameter.alias] = parameter.value;
gui.add(self.elements, parameter.alias, parameter.value);
});
}
}
customElements.define('three-gui', ThreeGUIElement);

View file

@ -0,0 +1,127 @@
import * as THREE from 'three';
import { STLLoader } from 'STLLoader';
import { OrbitControls } from 'OrbitControls';
export class ThreeRendererElement extends HTMLElement {
constructor() {
super();
var scene = this.scene = new THREE.Scene();
var camera = this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
var light = this.light = new THREE.AmbientLight(0xffffff, 1);
this.scene.add(light);
var point_light = this.point_light = new THREE.DirectionalLight(0xffffff, 0.5);
this.point_light.position.set(0, 0, 10);
this.point_light.target.position.set(-5, 0, 0);
this.scene.add(point_light);
this.scene.add(point_light.target);
var renderer = this.renderer = new THREE.WebGLRenderer();
var bb = this.parentElement.getBoundingClientRect();
this.renderer.setSize(bb.width, bb.height);
this.controls = new OrbitControls(camera, renderer.domElement);
this.geometry = new THREE.BoxGeometry();
this.material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
this.scene.background = new THREE.Color(0xababab);
this.camera.position.z = 5;
var group = this.group = new THREE.Group();
//group.rotation.x = group.rotation.y = Math.PI / 2;
this.scene.add(group);
// ground plane
const planeSize = 40;
const loader = new THREE.TextureLoader();
const texture = loader.load('static/three.js-master/examples/textures/checker.png');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const repeats = planeSize / 2;
texture.repeat.set(repeats, repeats);
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
});
const plane_mesh = new THREE.Mesh(planeGeo, planeMat);
window.plane_mesh = plane_mesh;
//plane_mesh.rotation.x = Math.PI * -.5;
plane_mesh.position.z = -10;
this.scene.add(plane_mesh);
if(this.hasAttribute('src')) {
this.loadObject(this.getAttribute('src'));
}
// XXX applWHY?
this.do_animate = this.do_animate.bind(this);
this.do_animate();
}
loadObject(src) {
const loader = new STLLoader();
const group = this.group;
loader.load(src, function (geometry) {
group.clear();
const material = new THREE.MeshStandardMaterial({
color: 0xababab,
metalness: 0.5,
specular: 0x111111,
shininess: 300
});
const mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
const edges = new THREE.EdgesGeometry(geometry);
const lines = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({
color: 0xff0000
}));
geometry.center();
edges.center();
group.add(mesh);
group.add(lines);
});
}
set src(url) {
return this.setAttribute('src', url);
}
get src() {
return this.getAttribute('src');
}
static get observedAttributes() {
return [ 'src' ];
}
do_animate() {
// TODO handle pausing
requestAnimationFrame(this.do_animate);
this.renderer.render(this.scene, this.camera);
}
attributeChangedCallback(name, old_value, value) {
if(name == 'src') {
this.loadObject(value);
}
}
connectedCallback() {
this.appendChild(this.renderer.domElement);
}
}
customElements.define('three-renderer', ThreeRendererElement);

35
static/index.js Normal file
View file

@ -0,0 +1,35 @@
import './FileLoader.js';
import './ThreeRendererElement.js';
import './ThreeGuiElement.js';
const loadObject = (count) => {
}
window.addEventListener("DOMContentLoaded", function() {
var uploader = document.querySelector('file-loader');
var renderer = document.querySelector('three-renderer');
var gui = document.querySelector('three-gui');
uploader.addEventListener('upload_complete', function(evt) {
console.log(evt, evt.detail);
renderer.setAttribute('src', `/stl/${evt.detail.filename.split('.')[0]}.stl`);
gui.setElements(evt.detail.data);
});
gui.addEventListener('value-change', function(evt) {
if(evt.detail) {
var src = renderer.src;
try {
var url = new URL(src);
} catch(e) {
var url = new URL(window.location)
url.pathname = src;
}
url.searchParams.set(evt.detail.property, evt.detail.value);
console.log(url)
renderer.src = url.href;
}
});
});

19
static/main.css Normal file
View file

@ -0,0 +1,19 @@
html, body, content {
height: 100%;
width: 100%;
}
content {
display: inline-block;
}
file-loader {
background-color: #bab;
height: 40px;
position: absolute;
width: 100%;
}
file-loader.dragover {
background-color: #d042d0;
}

1
static/three.js Submodule

@ -0,0 +1 @@
Subproject commit 9b274fef1d2b9ba0fc47410d06593e1f34f4df0e

30
templates/index.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>FreeCAD Web Viewer</title>
<style>
body { margin: 0; }
</style>
<link href="static/main.css" rel='stylesheet' type='text/css' />
</head>
<body>
<script type='importmap'>
{
"imports": {
"three": "./static/three.js-master/src/Three.js",
"Loader": "./static/three.js-master/src/loaders/Loader.js",
"GUI": "./static/three.js-master/examples/jsm/libs/lil-gui.module.min.js",
"OrbitControls": "./static/three.js-master/examples/jsm/controls/OrbitControls.js",
"STLLoader": "./static/three.js-master/examples/jsm/loaders/STLLoader.js"
}
}
</script>
<content>
<file-loader> </file-loader>
<three-renderer src='static/master_railx10.stl'></three-renderer>
<three-gui></three-gui>
</content>
<script src='./static/index.js' type='module'></script>
</body>
</html>