$ git clone https://thingshare.ion.nu/thingshare.git
commit f434929efcecfdb5944d62e742ad93ace2ea4663
Author: Alicia <...>
Date: Mon Mar 23 21:03:13 2020 +0100
Added a 3D view for 3D model files.
diff --git a/3dview.js b/3dview.js
new file mode 100644
index 0000000..9dc0608
--- /dev/null
+++ b/3dview.js
@@ -0,0 +1,32 @@
+/*
+ This file is part of Thingshare, a federated system for sharing data for home manufacturing (e.g. 3D models to 3D print)
+ https://thingshare.ion.nu/
+ Copyright (C) 2020 Alicia <...>
+
+ See /COPYING for license text (AGPLv3+)
+*/
+function threedview(link,w,h)
+{
+ div=link.parentElement.parentElement;
+ // Toggle view
+ var x3d=div.getElementsByTagName('x3d');
+ if(x3d.length>0)
+ {
+ link.textContent='3D view';
+ // Remove 3D view
+ for(var i=0; i<x3d.length; ++i){x3d[i].remove();}
+ // Restore 2D view
+ var img=div.getElementsByTagName('img');
+ for(var i=0; i<img.length; ++i){img[i].style.display='';}
+ return false;
+ }else{
+ link.textContent='2D view';
+ }
+ // Hide 2D view
+ var img=div.getElementsByTagName('img');
+ for(var i=0; i<img.length; ++i){img[i].style.display='none';}
+ // Add 3D preview (tried to do this with the DOM first, but x3dom wouldn't pick it up)
+ div.innerHTML+='<x3d width="'+w+'px" height="'+h+'px"><scene><inline url="'+link.dataset.file+'"></inline></scene></x3d>';
+ x3dom.reload(); // Load the new addition
+ return false;
+}
diff --git a/Dependencies b/Dependencies
index 8e5d611..589e08e 100644
--- a/Dependencies
+++ b/Dependencies
@@ -1,6 +1,7 @@
-Site dependencies (clone directly to web directory)
+Site dependencies (clone/download directly to web directory)
Parsedown (git clone https://github.com/erusev/parsedown)
Mdjs (git clone https://github.com/hangxingliu/mdjs)
+x3dom (wget https://x3dom.org/download/1.8.1/x3dom.{debug.js,css})
Web dependencies:
PHP
diff --git a/files.php b/files.php
index c377f9b..cb4091b 100644
--- a/files.php
+++ b/files.php
@@ -43,4 +43,10 @@ function getpreview($name, $hash)
if(file_exists('icons/'.$filetype.'.png')){return BASEURL.'/icons/'.$filetype.'.png';}
return BASEURL.'/icons/default.svg';
}
+function get3dpreview($name, $hash)
+{
+ $file=getfilepath($hash, true);
+ if(file_exists($file.'.x3d')){return BASEURL.'/'.$file.'.x3d';}
+ return false;
+}
?>
diff --git a/genpreviews.php b/genpreviews.php
index 5c8c713..1fa452f 100755
--- a/genpreviews.php
+++ b/genpreviews.php
@@ -27,13 +27,14 @@ if(!flock($lock, LOCK_EX|LOCK_NB)){die("Lock failed");}
include_once('db.php');
include_once('config.php');
include_once('files.php');
-mkdir(TMPDIR, 0755);
+$regen=($argv[1]=='all'); // Regenerate all previews or only those missing 2D previews
+@mkdir(TMPDIR, 0755);
$res=mysqli_query($db, 'select distinct lower(substring_index(name,".",-1)) as type, hash from files');
while($row=mysqli_fetch_assoc($res))
{
$file=getfilepath($row['hash'], true);
// Preview image
- if(file_exists($file.'.png')){continue;}
+ if(file_exists($file.'.png') && !$regen){continue;}
$img=false;
switch($row['type'])
{
@@ -67,7 +68,11 @@ while($row=mysqli_fetch_assoc($res))
// Convert to STL for the next step (even for stl for bin->ascii)
copy($file, TMPDIR.'/thingsharepreview_orig.'.$row['type']);
system('assimp export '.escapeshellarg(TMPDIR.'/thingsharepreview_orig.'.$row['type']).' '.escapeshellarg(TMPDIR.'/thingsharepreview.stl'));
+ // Convert to X3D for 3D view while we're at it
+ system('assimp export '.escapeshellarg(TMPDIR.'/thingsharepreview_orig.'.$row['type']).' '.escapeshellarg(TMPDIR.'/thingsharepreview.x3d'));
unlink(TMPDIR.'/thingsharepreview_orig.'.$row['type']);
+ $min=false;
+ $max=false;
switch(PREVIEW_RENDERMETHOD)
{
case 'povray':
@@ -107,7 +112,7 @@ while($row=mysqli_fetch_assoc($res))
fwrite($out, "mesh {\n");
}
}
- fclose($f);
+ fclose($in);
$dist=0;
$maxdist=0;
$avg=Array();
@@ -129,28 +134,91 @@ while($row=mysqli_fetch_assoc($res))
$dist/=1.65;
fwrite($out, ' location <'.($max[0]+$dist).','.($max[1]+$dist).','.($max[2]+$dist).">\n");
fwrite($out, ' look_at <'.$avg[0].','.$avg[1].','.$avg[2].">\n");
- fwrite($out, ' right x*'.(PREVIEW_SIZE[0]/PREVIEW_SIZE[1])."\n");
+ fwrite($out, ' right -x*'.(PREVIEW_SIZE[0]/PREVIEW_SIZE[1])."\n");
fwrite($out, " sky <0,0,1>\n}\n"); // Set Z vertical
fwrite($out, 'light_source { <'.($max[0]+$dist+10).','.($max[1]+$dist).','.($max[2]+$dist+20).'> color rgb<1, 1, 1> }'); // Light slightly offset from camera to distinguish corners better
+ fclose($out);
// Render using Povray
$tmpfile=escapeshellarg(TMPDIR.'/thingsharepreview.pov');
system('povray +I'.$tmpfile.' +O'.escapeshellarg($file.'.png').' -D +P +W'.(int)PREVIEW_SIZE[0].' +H'.(int)PREVIEW_SIZE[1].' +A0.5');
unlink(TMPDIR.'/thingsharepreview.pov');
break;
- case 'openscad': // TODO: Test this code (completely untested so far), looks like we need to rename the file for the import to identify it correctly
+ case 'openscad': // TODO: Test this code (not well tested so far)
$c=sprintf('#%02x%02x%02x', PREVIEW_COLOR[0], PREVIEW_COLOR[1], PREVIEW_COLOR[2]);
- copy($file, TMPDIR.'/thingsharepreview.'.$row['type']);
- file_put_contents(TMPDIR.'/thingsharepreview.scad', 'color("'.$c.'")import("thingsharepreview.'.str_replace('"', '', $row['type']).'");');
+ file_put_contents(TMPDIR.'/thingsharepreview.scad', 'color("'.$c.'")import("thingsharepreview.stl");');
if(getenv('DISPLAY')==''){putenv('DISPLAY=:0');} // Default X server
system('openscad --imgsize '.(int)PREVIEW_SIZE[0].','.(int)PREVIEW_SIZE[1].' -o '.escapeshellarg($file.'.png').' '.escapeshellarg(TMPDIR.'/thingsharepreview.scad'));
- unlink(TMPDIR.'/thingsharepreview.'.$row['type']);
unlink(TMPDIR.'/thingsharepreview.scad');
break;
}
+ if(!$min || !$max) // If we haven't found them as part of 2D rendering, find minmax coordinates
+ {
+ $in=fopen(TMPDIR.'/thingsharepreview.stl', 'r');
+ while($line=fgets($in))
+ {
+ if(substr(trim($line), 0, 7)=='vertex ')
+ {
+ $vertex=array_slice(explode(' ', trim($line)), 1);
+ for($i=0; $i<3; ++$i)
+ {
+ if($min[$i]===false || $vertex[$i]<$min[$i]){$min[$i]=$vertex[$i];}
+ if($max[$i]===false || $vertex[$i]>$max[$i]){$max[$i]=$vertex[$i];}
+ }
+ }
+ }
+ fclose($in);
+ }
+ $dist=0;
+ $maxdist=0;
+ $avg=Array();
+ for($i=0; $i<3; ++$i)
+ {
+ $avg[$i]=($max[$i]+$min[$i])/2;
+ if($max[$i]-$min[$i]>$maxdist){$maxdist=$max[$i]-$min[$i];}
+ $dist+=($max[$i]-$min[$i])/3;
+ }
+ $dist=($dist+$maxdist)/3.3;
+ // Edit X3D to match
+ $in=fopen(TMPDIR.'/thingsharepreview.x3d', 'r');
+ $out=fopen($file.'.x3d', 'w');
+ $c=sprintf('#%02x%02x%02x', PREVIEW_COLOR[0]*0.75, PREVIEW_COLOR[1]*0.75, PREVIEW_COLOR[2]*0.75);
+ $cs=sprintf('#%02x%02x%02x', PREVIEW_COLOR[0], PREVIEW_COLOR[1], PREVIEW_COLOR[2]);
+ while($line=fgets($in, 2048))
+ {
+ // Replace appearance with our own
+ if(substr_count(strtolower($line), '<appearance ')>0)
+ {
+ while(substr_count(strtolower($line), '</appearance>')<1 && ($line=fgets($in)));
+ $line='<appearance><material diffusecolor="'.$c.'" specularcolor="'.$cs.'"></material></appearance>';
+ }
+ if(substr_count(strtolower($line), '<scene>')>0 && substr_count(strtolower($line), '<!--')==0)
+ {
+ // Calculate orientation
+ $x=$avg[0]-($max[0]+$dist);
+ $y=$avg[1]-($max[1]+$dist);
+ $z=$avg[2]-($max[2]+$dist);
+ $rz=-atan2($x, $y);
+ $xy=hypot($x*cos($rz), $y*sin($rz));
+ if($x<0 && $y<0){$xy=-$xy;} // Restore sign of values turned absolute in the process
+ $xy*=1.5; // Not sure why, but without this view ends up too low
+ $rx=atan2($xy, $z)+pi();
+ $line.='<transform translation="'.($max[0]+$dist).','.($max[1]+$dist).','.($max[2]+$dist).'">
+<transform rotation="0,0,1,'.$rz.'">
+<transform rotation="1,0,0,'.$rx.'">
+<viewpoint></viewpoint>
+</transform>
+</transform>
+</transform>'."\n";
+ }
+ fwrite($out, $line);
+ }
+ fclose($in);
+ fclose($out);
+ // Clean up
+ unlink(TMPDIR.'/thingsharepreview.x3d');
unlink(TMPDIR.'/thingsharepreview.stl');
break;
}
- // TODO: Preview 3D model (x3d)
// print($file.'.'.$row['type']."\n");
}
?>
diff --git a/head.php b/head.php
index e1b02d9..a1ac9b3 100644
--- a/head.php
+++ b/head.php
@@ -50,6 +50,9 @@ $search=htmlentities(isset($_GET['q'])?$_GET['q']:'');
<head>
<title><?=NODENAME?></title>
<link rel="stylesheet" href="<?=BASEURL?>/style.css" type="text/css" />
+ <link rel="stylesheet" href="<?=BASEURL?>/x3dom.css" type="text/css" />
+ <script src="<?=BASEURL?>/x3dom.debug.js"></script>
+ <script src="<?=BASEURL?>/3dview.js"></script>
<script src="<?=BASEURL?>/time.js"></script>
</head>
<body>
diff --git a/rpc_thing.php b/rpc_thing.php
index 9ee70c0..3c4a854 100644
--- a/rpc_thing.php
+++ b/rpc_thing.php
@@ -38,6 +38,7 @@ while($row=mysqli_fetch_assoc($res))
$file=Array('name'=>$row['name'],
'path'=>getfilepath($row['hash']),
'preview'=>getpreview($row['name'], $row['hash']));
+ if($preview3d=get3dpreview($row['name'], $row['hash'])){$file['preview3d']=$preview3d;}
// Grab mimetype, if set
$mime=db_getmimetype($row['name']);
if($mime!==false){$file['type']=$mime;}
diff --git a/setup.php b/setup.php
index d2b3870..e538e4c 100644
--- a/setup.php
+++ b/setup.php
@@ -87,6 +87,7 @@ if(!is_dir('parsedown'))
$mandatory=true;
}
if(!is_dir('mdjs')){$deps.='<li>The javascript library mdjs appears to be missing. It can optionally be used to generate live previews of markdown formatted text. See <a href="Dependencies">Dependencies</a> for how to obtain it</li>';}
+if(!file_exists('x3dom.debug.js') || !file_exists('x3dom.css')){$deps.='<li>The javascript library x3dom appears to be missing. It can optionally be used to preview models in 3D. See <a href="Dependencies">Dependencies</a> for how to obtain it</li>';}
if($deps!='')
{
print('<h1>Dependencies</h1><ul>'.$deps.'</ul>');
diff --git a/style.css b/style.css
index 563b6f7..1c8b8de 100644
--- a/style.css
+++ b/style.css
@@ -108,11 +108,13 @@ div.boxbottom {
text-align:center;
width:100%;
background-color:#ffffffa0;
+ z-index:1;
}
div.boxtop {
position:absolute;
top:0px;
background-color:#ffffffa0;
+ z-index:1;
}
div.thing>a>img {
width:100%;
diff --git a/thing.php b/thing.php
index a82629a..cc5722d 100644
--- a/thing.php
+++ b/thing.php
@@ -53,7 +53,9 @@ $files='';
foreach($thingobj['files'] as $file)
{
$type=(isset($file['type'])?$file['type']:'unknown');
- $files.='<div class="thing"><a href="https://'.$thing[1].$file['path'].'" download="'.htmlentities($file['name']).'" type="'.$type.'"><div class="boxbottom">'.htmlentities($file['name']).'</div><img src="https://'.$thing[1].$file['preview'].'" /></a></div>';
+ $files.='<div class="thing">';
+ if(isset($file['preview3d'])){$files.='<div class="boxtop" style="right:0px;"><a href="#" onclick="return threedview(this,'.PREVIEW_SIZE[0].','.PREVIEW_SIZE[1].');" data-file="https://'.$thing[1].$file['preview3d'].'">3D view</a></div>';}
+ $files.='<a href="https://'.$thing[1].$file['path'].'" download="'.htmlentities($file['name']).'" type="'.$type.'"><div class="boxbottom">'.htmlentities($file['name']).'</div><img src="https://'.$thing[1].$file['preview'].'" /></a></div>';
}
?>
<h1><?=$name?> <small class="subheader">by <a href="<?=BASEURL?>/user/<?=$thingobj['by']['name']?>@<?=$thing[1]?>" title="<?=$thingobj['by']['name']?>@<?=$thing[1]?>"><?=htmlentities($thingobj['by']['displayname'])?></a></small></h1>