Initial commit
This commit is contained in:
commit
e9f8cadc98
30 changed files with 6236 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
_site/
|
||||
43
LICENSE
Normal file
43
LICENSE
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
## creative commons
|
||||
|
||||
# CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.
|
||||
|
||||
### Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. __Copyright and Related Rights.__ A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. __Waiver.__ To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. __Public License Fallback.__ Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
|
||||
|
||||
4. __Limitations and Disclaimers.__
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
|
||||
27
README.md
Normal file
27
README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
## cookiecutter-mystl
|
||||
|
||||
A Jekyll (github pages) boilerplate for hosting your own STL files.
|
||||
For use with [cookiecutter](https://github.com/audreyr/cookiecutter).
|
||||
|
||||
## License
|
||||
|
||||
The license for the "original parts" of this cookiecutter is CC0, the Creative
|
||||
Commons Public Dedication. However, included parts are under other licenses
|
||||
such as MIT (the html, js, css) and CC-BY-SA
|
||||
([the monolith photograph](https://commons.wikimedia.org/wiki/File:HAL_2001_monolith_(color_correction).jpg)).
|
||||
|
||||
The intent is not that the works you exhibit with your site be covered under
|
||||
any specific license. You get a choice of several popular licenses when you
|
||||
run this cookie-cutter.
|
||||
|
||||
The intent is to give you as much freedom as possible with the site that
|
||||
cookiecutter-mystl produces.
|
||||
|
||||
## Requests for Contributions
|
||||
|
||||
Contributions welcome! Here are some of the directions I'd like to see
|
||||
cookiecutter-mystl project grow:
|
||||
|
||||
- integrations with other hosting and CI services (gitlab, travis, etc)
|
||||
- improve lightbox & 3d viewer
|
||||
- work with other file types (.stp? FreeCAD?)
|
||||
10
cookiecutter.json
Normal file
10
cookiecutter.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"project_name": "MySTL",
|
||||
"repo_name": "mystl",
|
||||
"main_branch_name": ["master", "main"],
|
||||
"author": "Jeff Epler",
|
||||
"copyrightyears" : "{% now 'local', '%Y' %}",
|
||||
"project_short_description": "Demonstration site for MySTL",
|
||||
"license": ["CC-BY", "CC-BY-SA", "CC-BY-NC", "CC-BY-NC-SA", "CC-BY-ND", "CC-BY-NC-ND", "CC0", "GPL-3.0-or-later", "GPL-3.0-only"],
|
||||
"_extensions": ["jinja2_time.TimeExtension"]
|
||||
}
|
||||
41
{{cookiecutter.repo_name}}/.github/workflows/publish.yml
vendored
Normal file
41
{{cookiecutter.repo_name}}/.github/workflows/publish.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
name: Build and Publish Website
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
release:
|
||||
types: [published]
|
||||
check_suite:
|
||||
types: [rerequested]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-18.04
|
||||
container: debian:buster
|
||||
|
||||
steps:
|
||||
- name: install dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get --no-install-recommends -y install admesh git imagemagick jekyll make openscad python3 python3-yaml xauth xvfb
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: build
|
||||
run: |
|
||||
xvfb-run make V=2 -O -j$(nproc) jekyll
|
||||
- if: github.event_name == 'push' && github.ref == 'refs/heads/{{ cookiecutter.main_branch_name }}'
|
||||
name: publish
|
||||
run: |
|
||||
{% raw %}
|
||||
git config user.name "${GITHUB_ACTOR}"
|
||||
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
|
||||
git remote set-url --push origin https://${GITHUB_ACTOR}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
|
||||
make -O V=1 publish
|
||||
{% endraw %}
|
||||
- if: github.event_name != 'push' || github.ref != 'refs/heads/{{ cookiecutter.main_branch_name }}'
|
||||
name: artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: website
|
||||
path: _site
|
||||
|
||||
1
{{cookiecutter.repo_name}}/.gitignore
vendored
Normal file
1
{{cookiecutter.repo_name}}/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
_site/
|
||||
2259
{{cookiecutter.repo_name}}/LICENSE.md
Normal file
2259
{{cookiecutter.repo_name}}/LICENSE.md
Normal file
File diff suppressed because it is too large
Load diff
40
{{cookiecutter.repo_name}}/Makefile
Normal file
40
{{cookiecutter.repo_name}}/Makefile
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
ifeq ("$(origin V)", "command line")
|
||||
BUILD_VERBOSE = $(V)
|
||||
endif
|
||||
ifndef BUILD_VERBOSE
|
||||
BUILD_VERBOSE = 0
|
||||
endif
|
||||
|
||||
ifeq "$(findstring s,$(MAKEFLAGS))" ""
|
||||
ECHO=@echo
|
||||
VECHO=echo
|
||||
else
|
||||
ECHO=@true
|
||||
VECHO=true
|
||||
endif
|
||||
|
||||
default:
|
||||
|
||||
clean:
|
||||
$(ECHO) CLEAN
|
||||
$(Q)rm -rf resources/gen
|
||||
$(Q)jekyll clean
|
||||
|
||||
resources/gen/rules.mk: _data/assets.yml _lib/rules.py
|
||||
$(ECHO) RULES
|
||||
$(Q)mkdir -p resources/gen
|
||||
$(Q)_lib/rules.py $< > $@
|
||||
-include resources/gen/rules.mk
|
||||
|
||||
|
||||
.PHONY: publish jekyll
|
||||
jekyll: default
|
||||
$(ECHO) "JEKYLL"
|
||||
$(Q)jekyll build
|
||||
$(Q)touch _site/.nojekyll
|
||||
|
||||
publish: jekyll default
|
||||
$(ECHO) "PUBLISH"
|
||||
$(Q)git branch -D gh-pages || true
|
||||
$(Q)./_lib/docimport.py | git fast-import --date-format=now
|
||||
$(Q)git push -f origin gh-pages
|
||||
49
{{cookiecutter.repo_name}}/README.md
Normal file
49
{{cookiecutter.repo_name}}/README.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# cookiecutter.repo_name
|
||||
|
||||
{{ cookiecutter.project_short_description }}
|
||||
|
||||
This is a project created with [cookiecutter-mystl](https://github.com/jepler/cookiecutter-mystl).
|
||||
Use cookiecutter-mystl to host your own OpenSCAD and STL models!
|
||||
Have improvements to your site? Contribute them back to cookiecutter-mystl!
|
||||
|
||||
My STL
|
||||
|
||||
## Requirements
|
||||
|
||||
See .github/workflows/publish.yml for the canonical requirements.
|
||||
Intended to work on debian systems with a minimum of fuss.
|
||||
|
||||
* admesh
|
||||
* git
|
||||
* Imagemagick
|
||||
* jekyll
|
||||
* make
|
||||
* OpenSCAD
|
||||
* python3
|
||||
* python3-yaml
|
||||
|
||||
For headless building,
|
||||
* xauth
|
||||
* xvfb
|
||||
|
||||
## Models and STL generation from .scad files
|
||||
|
||||
`_data/assets.yml` describes the files that make up your project.
|
||||
It should be a series of blocks.
|
||||
At a minimum the block must have a name and an stl file.
|
||||
It can optionally have notes, an scad file, scad generation flags, images, and other downloads.
|
||||
You can refer to a source scad file multiple times with different flags,
|
||||
in order to generate related or parametric models.
|
||||
|
||||
## Build assets and render Locally
|
||||
|
||||
$ make
|
||||
$ jekyll serve --watch
|
||||
|
||||
## Build locally and push site to github pages
|
||||
|
||||
$ make publish
|
||||
|
||||
## Push to {{ cookiecutter.main_branch_name }} branch and get a gh-pages build:
|
||||
|
||||
$ git push origin {{ cookiecutter.main_branch_name }}
|
||||
2
{{cookiecutter.repo_name}}/_config.yml
Normal file
2
{{cookiecutter.repo_name}}/_config.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
exclude: [README.md,Makefile,resources/gen/rules.mk,.*,resources/gen/*.d]
|
||||
highligter: rouge
|
||||
12
{{cookiecutter.repo_name}}/_data/assets.yml
Normal file
12
{{cookiecutter.repo_name}}/_data/assets.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
- name: Featureless Cube
|
||||
scad: main.scad
|
||||
stl: gen/main.stl
|
||||
images: ['gen/main.png']
|
||||
|
||||
- name: Monolith from 2001
|
||||
notes: ""
|
||||
scad: main.scad
|
||||
flags: -Dsize=[1,4,9]
|
||||
stl: gen/monolith.stl
|
||||
images: ['gen/monolith.png', 'HAL_2001_monolith.jpg']
|
||||
downloads: ['monolith.txt']
|
||||
55
{{cookiecutter.repo_name}}/_layouts/base.html
Normal file
55
{{cookiecutter.repo_name}}/_layouts/base.html
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="{{ cookiecutter.project_short_description }}">
|
||||
<meta name="author" content="{{ cookiecutter.author }}">
|
||||
{% raw %}
|
||||
{% capture lvl %}{{ page.url | append:'index.html' | split:'/' | size }}{% endcapture %}
|
||||
{% capture relative %}{% for i in (3..lvl) %}../{% endfor %}{% endcapture %}
|
||||
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700|PT+Sans:400,700,400italic' rel='stylesheet' type='text/css'>
|
||||
<link href="{{ relative }}resources/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{{ relative }}resources/css/style.css" rel="stylesheet">
|
||||
<link href="{{ relative }}resources/css/lightbox.css" rel="stylesheet">
|
||||
<script src="{{ relative }}resources/js/three.min.js"></script>
|
||||
<script src="{{ relative }}resources/js/STLLoader.js"></script>
|
||||
<script src="{{ relative }}resources/js/OrbitControls.js"></script>
|
||||
<script src="{{ relative }}resources/js/stlviewer.js"></script>
|
||||
<script src="{{ relative }}resources/js/lightbox.js"></script>
|
||||
<title>{{ page.title }}</title>
|
||||
{% endraw %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div id="header">
|
||||
<div class="container">
|
||||
{% raw %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
{% endraw %}
|
||||
</div>
|
||||
</div>
|
||||
{% raw %}
|
||||
<div id="content">
|
||||
<div class="container">
|
||||
{{ content }}
|
||||
</div>
|
||||
</div>
|
||||
{% endraw %}
|
||||
</div>
|
||||
<hr>
|
||||
<div id="footer">
|
||||
<div class="container">
|
||||
<p>
|
||||
{% raw %}
|
||||
This 3D printable project is offered under <a href="{{ relative }}LICENSE.html">an open source license</a>.
|
||||
The site is built with software under <a href="{{ relative }}other-licenses.html">various open source licenses</a>.
|
||||
{% endraw %}
|
||||
Create your own MySTL site with <a href="https://github.com/jepler/cookiecutter-mystl">cookiecutter-mystl</a>.
|
||||
|
||||
</p>
|
||||
<a class="pull-right" href="#">Back to top</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
33
{{cookiecutter.repo_name}}/_lib/docimport.py
Executable file
33
{{cookiecutter.repo_name}}/_lib/docimport.py
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/python3
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("branch", default="gh-pages", nargs="?")
|
||||
args = parser.parse_args()
|
||||
|
||||
version = subprocess.getoutput("git describe --always")
|
||||
|
||||
fd = os.fdopen(sys.stdout.fileno(), 'wb')
|
||||
|
||||
fd.write(b"commit refs/heads/" + args.branch.encode('utf-8') + b"\n")
|
||||
fd.write(b"committer Doc Man <noreply@example.com> now" + b"\n")
|
||||
fd.write(b"data <<EOF" + b"\n")
|
||||
fd.write(b"Docs built at " + version.encode('utf-8') + b"\n")
|
||||
fd.write(b"EOF" + b"\n")
|
||||
|
||||
for root, dirs, files in os.walk("_site"):
|
||||
for fn in files:
|
||||
fn = os.path.join(root, fn)
|
||||
gfn = fn.split("/", 1)[1]
|
||||
print(fn, "->", gfn, file=sys.stderr)
|
||||
with open(fn, 'rb') as f: contents = f.read()
|
||||
fd.write(b"M 644 inline " + gfn.encode('utf-8') + b"\n")
|
||||
fd.write(b"data " + str(len(contents)).encode("utf-8") + b"\n")
|
||||
fd.write(contents)
|
||||
fd.write(b"done\n")
|
||||
|
||||
2
{{cookiecutter.repo_name}}/_lib/readfile.scad
Normal file
2
{{cookiecutter.repo_name}}/_lib/readfile.scad
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
CONVEXITY=4;
|
||||
import(input, convexity=CONVEXITY);
|
||||
80
{{cookiecutter.repo_name}}/_lib/rules.py
Executable file
80
{{cookiecutter.repo_name}}/_lib/rules.py
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import shlex
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
resources = pathlib.Path("resources")
|
||||
|
||||
MAKE_SCAD_STL = """
|
||||
-include {target}.d
|
||||
{target}: {src}
|
||||
\t$(ECHO) $target
|
||||
\t$(Q)mkdir -p $(dir {target})
|
||||
\t$(Q)openscad -o {target}.tmp.stl -d {target}.d $(SCAD_FLAGS) {flags} {src}
|
||||
\t$(Q)admesh -b {target} {target}.tmp.stl
|
||||
\t$(Q)rm -f {target}.tmp.stl
|
||||
default: {target}
|
||||
"""
|
||||
|
||||
MAKE_SCAD_SVG = """
|
||||
-include {target}.d
|
||||
{target}: {src}
|
||||
\t$(ECHO) $target
|
||||
\t$(Q)mkdir -p $(dir {target})
|
||||
\t$(Q)openscad -o {target} -d {target}.d $(SCAD_FLAGS) {flags} {src}
|
||||
default: {target}
|
||||
"""
|
||||
|
||||
MAKE_SCAD_PNG = """
|
||||
-include {target}.d
|
||||
{target}: {src}
|
||||
\t$(ECHO) $target
|
||||
\t$(Q)mkdir -p $(dir {target})
|
||||
\t$(Q)openscad --imgsize=1800,1800 -o {target}.tmp.png -d {target}.d $(SCAD_FLAGS) {flags} {src}
|
||||
\t$(Q)convert -geometry 25% {target}.tmp.png {target}
|
||||
\t$(Q)rm -f {target}.tmp.png
|
||||
default: {target}
|
||||
"""
|
||||
|
||||
MAKE_STL_PNG = """
|
||||
-include {target}.d
|
||||
{target}: {src}
|
||||
\t$(ECHO) $target
|
||||
\t$(Q)mkdir -p $(dir {target})
|
||||
\t$(Q)openscad --imgsize=1800,1800 -o {target}.tmp.png -d {target}.d $(SCAD_FLAGS) {flags} -Dinput=\\\"{src}\\\" _lib/readfile.scad
|
||||
\t$(Q)convert -geometry 25% {target}.tmp.png {target}
|
||||
\t$(Q)rm -f {target}.tmp.png
|
||||
default: {target}
|
||||
"""
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
doc = yaml.safe_load(f)
|
||||
|
||||
for row in doc:
|
||||
flags = row.get('flags', '')
|
||||
if isinstance(flags, str):
|
||||
flags = flags.split()
|
||||
flags = " ".join(shlex.quote(f) for f in flags)
|
||||
scad = row.get('scad', '')
|
||||
stl = row.get('stl', '')
|
||||
svg = row.get('svg', '')
|
||||
|
||||
if stl.startswith('gen/'):
|
||||
print(MAKE_SCAD_STL.format(
|
||||
**{'target': resources / stl, 'src': resources / scad, 'flags': flags}))
|
||||
|
||||
if svg.startswith('gen/'):
|
||||
print(MAKE_SCAD_SVG.format(
|
||||
**{'target': resources / svg, 'src': resources / scad, 'flags': flags}))
|
||||
|
||||
for png in row.get('images', []):
|
||||
if png.startswith('gen/'):
|
||||
if scad:
|
||||
print(MAKE_SCAD_PNG.format(
|
||||
**{'target': resources / png, 'src': resources / scad, 'flags': flags}))
|
||||
else:
|
||||
print(MAKE_STL_PNG.format(
|
||||
**{'target': resources / png, 'src': resources / stl, 'flags': flags}))
|
||||
63
{{cookiecutter.repo_name}}/index.md
Normal file
63
{{cookiecutter.repo_name}}/index.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
layout: base
|
||||
title: {{ cookiecutter.project_name }}
|
||||
---
|
||||
|
||||
{% raw %}
|
||||
|
||||
|
||||
{% for asset in site.data.assets %}
|
||||
{% assign stlbasename = asset.stl | split: "/" | last | split: "." | first %}
|
||||
### {{ asset.name }}
|
||||
|
||||
<html>
|
||||
{% for image in asset.images %}
|
||||
{% assign basename = image | split: "/" | last | split: "." | first %}
|
||||
{% if basename == stlbasename %}
|
||||
<img src="{{ relative }}resources/{{ image }}" title="{{ asset.name }}" data-stl="{{ relative }}resources/{{ asset.stl }}">
|
||||
{% else %}
|
||||
<img src="{{ relative }}resources/{{ image }}" title="{{ asset.name }}">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</html>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% comment %}
|
||||
Sections to consider adding:
|
||||
## How I made this
|
||||
## Print Settings
|
||||
## Remixed from
|
||||
{% endcomment %}
|
||||
|
||||
## Downloads
|
||||
|
||||
{% for asset in site.data.assets %}
|
||||
### {{ asset.name }}:
|
||||
|
||||
{{ asset.notes }}
|
||||
|
||||
{% if asset.flags %}
|
||||
STL generated with `{{ asset.flags }}`
|
||||
{% endif %}
|
||||
|
||||
{% if asset.scad %}
|
||||
{% assign basename = asset.scad | split: "/" | last %}
|
||||
- [{{ basename }}](resources/{{ asset.scad }})
|
||||
{% endif %}
|
||||
{% if asset.stl %}
|
||||
{% assign basename = asset.stl | split: "/" | last %}
|
||||
- [{{ basename }}](resources/{{ asset.stl }})
|
||||
{% endif %}
|
||||
{% if asset.svg %}
|
||||
{% assign basename = asset.svg | split: "/" | last %}
|
||||
- [{{ basename }}](resources/{{ asset.svg }})
|
||||
{% endif %}
|
||||
{% for download in asset.downloads %}
|
||||
{% assign basename = download | split: "/" | last %}
|
||||
- [{{ basename }}](resources/{{ download }})
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endraw %}
|
||||
11
{{cookiecutter.repo_name}}/other-licenses.md
Normal file
11
{{cookiecutter.repo_name}}/other-licenses.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
layout: base
|
||||
title: Web Site Licenses
|
||||
---
|
||||
|
||||
This website is built on various open source technologies:
|
||||
|
||||
- [Jekyll](https://github.com/jekyll/jekyll/blob/master/LICENSE) [MIT]
|
||||
- [Jekyll Codex](https://github.com/jhvanderschee/jekyllcodex/#mit-license) [MIT]
|
||||
- [three.js](https://github.com/mrdoob/three.js/blob/dev/LICENSE) [MIT]
|
||||
- [simple-stl-viewer](https://tonybox.net/posts/simple-stl-viewer/) [MIT]
|
||||
BIN
{{cookiecutter.repo_name}}/resources/HAL_2001_monolith.jpg
Normal file
BIN
{{cookiecutter.repo_name}}/resources/HAL_2001_monolith.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
497
{{cookiecutter.repo_name}}/resources/css/bootstrap.min.css
vendored
Normal file
497
{{cookiecutter.repo_name}}/resources/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
95
{{cookiecutter.repo_name}}/resources/css/lightbox.css
Normal file
95
{{cookiecutter.repo_name}}/resources/css/lightbox.css
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
#lightbox {width: 100%; height: 100%; position: fixed; top: 0; left: 0; background: rgba(0,0,0,0.85); z-index: 9999999; line-height: 0; cursor: pointer; display: none;}
|
||||
#lightbox .img {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-ms-transform: translateX(-50%) translateY(-50%);
|
||||
-webkit-transform: translate(-50%,-50%);
|
||||
transform: translate(-50%,-50%);
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
#lightbox .img img {opacity: 0; pointer-events: none; width: auto;}
|
||||
@media screen and (min-width: 1200px) {
|
||||
#lightbox .img {
|
||||
max-width: 1200px;
|
||||
}
|
||||
}
|
||||
@media screen and (min-height: 1200px) {
|
||||
#lightbox .img {
|
||||
max-height: 1200px;
|
||||
}
|
||||
}
|
||||
#lightbox span {display: block; position: fixed; bottom: 13px; height: 1.5em; line-height: 1.4em; width: 100%; text-align: center; color: white; text-shadow:
|
||||
-1px -1px 0 #000,
|
||||
1px -1px 0 #000,
|
||||
-1px 1px 0 #000,
|
||||
1px 1px 0 #000;
|
||||
}
|
||||
|
||||
#lightbox span {display: none;}
|
||||
|
||||
#lightbox .videoWrapperContainer {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-ms-transform: translateX(-50%) translateY(-50%);
|
||||
-webkit-transform: translate(-50%,-50%);
|
||||
transform: translate(-50%,-50%);
|
||||
max-width: 900px;
|
||||
max-height: 100%;
|
||||
}
|
||||
#lightbox .videoWrapperContainer .videoWrapper {
|
||||
height: 0;
|
||||
line-height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
padding-bottom: 56.333%; /* custom */
|
||||
background: black;
|
||||
}
|
||||
#lightbox .videoWrapper iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
display: block;
|
||||
}
|
||||
#lightbox #prev, #lightbox #next {height: 50px; line-height: 36px; display: none; margin-top: -25px; position: fixed; top: 50%; padding: 0 15px; cursor: pointer; text-decoration: none; z-index: 99; color: white; font-size: 60px;}
|
||||
#lightbox.gallery #prev, #lightbox.gallery #next {display: block;}
|
||||
#lightbox #prev {left: 0;}
|
||||
#lightbox #next {right: 0;}
|
||||
#lightbox #close {height: 50px; width: 50px; position: fixed; cursor: pointer; text-decoration: none; z-index: 99; right: 0; top: 0;}
|
||||
#lightbox #close:after, #lightbox #close:before {position: absolute; margin-top: 22px; margin-left: 14px; content: ""; height: 3px; background: white; width: 23px;
|
||||
-webkit-transform-origin: 50% 50%;
|
||||
-moz-transform-origin: 50% 50%;
|
||||
-o-transform-origin: 50% 50%;
|
||||
transform-origin: 50% 50%;
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(-45deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(-45deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(-45deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(-45deg);
|
||||
}
|
||||
#lightbox #close:after {
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(45deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(45deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(45deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(45deg);
|
||||
}
|
||||
#lightbox, #lightbox * {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.lightbox-image-stl { cursor: pointer; }
|
||||
0
{{cookiecutter.repo_name}}/resources/css/style.css
Normal file
0
{{cookiecutter.repo_name}}/resources/css/style.css
Normal file
1183
{{cookiecutter.repo_name}}/resources/js/OrbitControls.js
Normal file
1183
{{cookiecutter.repo_name}}/resources/js/OrbitControls.js
Normal file
File diff suppressed because it is too large
Load diff
393
{{cookiecutter.repo_name}}/resources/js/STLLoader.js
Normal file
393
{{cookiecutter.repo_name}}/resources/js/STLLoader.js
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/**
|
||||
* @author aleeper / http://adamleeper.com/
|
||||
* @author mrdoob / http://mrdoob.com/
|
||||
* @author gero3 / https://github.com/gero3
|
||||
* @author Mugen87 / https://github.com/Mugen87
|
||||
* @author neverhood311 / https://github.com/neverhood311
|
||||
*
|
||||
* Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.
|
||||
*
|
||||
* Supports both binary and ASCII encoded files, with automatic detection of type.
|
||||
*
|
||||
* The loader returns a non-indexed buffer geometry.
|
||||
*
|
||||
* Limitations:
|
||||
* Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL).
|
||||
* There is perhaps some question as to how valid it is to always assume little-endian-ness.
|
||||
* ASCII decoding assumes file is UTF-8.
|
||||
*
|
||||
* Usage:
|
||||
* var loader = new THREE.STLLoader();
|
||||
* loader.load( './models/stl/slotted_disk.stl', function ( geometry ) {
|
||||
* scene.add( new THREE.Mesh( geometry ) );
|
||||
* });
|
||||
*
|
||||
* For binary STLs geometry might contain colors for vertices. To use it:
|
||||
* // use the same code to load STL as above
|
||||
* if (geometry.hasColors) {
|
||||
* material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: true });
|
||||
* } else { .... }
|
||||
* var mesh = new THREE.Mesh( geometry, material );
|
||||
*
|
||||
* For ASCII STLs containing multiple solids, each solid is assigned to a different group.
|
||||
* Groups can be used to assign a different color by defining an array of materials with the same length of
|
||||
* geometry.groups and passing it to the Mesh constructor:
|
||||
*
|
||||
* var mesh = new THREE.Mesh( geometry, material );
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* var materials = [];
|
||||
* var nGeometryGroups = geometry.groups.length;
|
||||
*
|
||||
* var colorMap = ...; // Some logic to index colors.
|
||||
*
|
||||
* for (var i = 0; i < nGeometryGroups; i++) {
|
||||
*
|
||||
* var material = new THREE.MeshPhongMaterial({
|
||||
* color: colorMap[i],
|
||||
* wireframe: false
|
||||
* });
|
||||
*
|
||||
* }
|
||||
*
|
||||
* materials.push(material);
|
||||
* var mesh = new THREE.Mesh(geometry, materials);
|
||||
*/
|
||||
|
||||
|
||||
THREE.STLLoader = function ( manager ) {
|
||||
|
||||
THREE.Loader.call( this, manager );
|
||||
|
||||
};
|
||||
|
||||
THREE.STLLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
|
||||
|
||||
constructor: THREE.STLLoader,
|
||||
|
||||
load: function ( url, onLoad, onProgress, onError ) {
|
||||
|
||||
var scope = this;
|
||||
|
||||
var loader = new THREE.FileLoader( scope.manager );
|
||||
loader.setPath( scope.path );
|
||||
loader.setResponseType( 'arraybuffer' );
|
||||
loader.load( url, function ( text ) {
|
||||
|
||||
try {
|
||||
|
||||
onLoad( scope.parse( text ) );
|
||||
|
||||
} catch ( e ) {
|
||||
|
||||
if ( onError ) {
|
||||
|
||||
onError( e );
|
||||
|
||||
} else {
|
||||
|
||||
console.error( e );
|
||||
|
||||
}
|
||||
|
||||
scope.manager.itemError( url );
|
||||
|
||||
}
|
||||
|
||||
}, onProgress, onError );
|
||||
|
||||
},
|
||||
|
||||
parse: function ( data ) {
|
||||
|
||||
function isBinary( data ) {
|
||||
|
||||
var expect, face_size, n_faces, reader;
|
||||
reader = new DataView( data );
|
||||
face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 );
|
||||
n_faces = reader.getUint32( 80, true );
|
||||
expect = 80 + ( 32 / 8 ) + ( n_faces * face_size );
|
||||
|
||||
if ( expect === reader.byteLength ) {
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// An ASCII STL data must begin with 'solid ' as the first six bytes.
|
||||
// However, ASCII STLs lacking the SPACE after the 'd' are known to be
|
||||
// plentiful. So, check the first 5 bytes for 'solid'.
|
||||
|
||||
// Several encodings, such as UTF-8, precede the text with up to 5 bytes:
|
||||
// https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
|
||||
// Search for "solid" to start anywhere after those prefixes.
|
||||
|
||||
// US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd'
|
||||
|
||||
var solid = [ 115, 111, 108, 105, 100 ];
|
||||
|
||||
for ( var off = 0; off < 5; off ++ ) {
|
||||
|
||||
// If "solid" text is matched to the current offset, declare it to be an ASCII STL.
|
||||
|
||||
if ( matchDataViewAt( solid, reader, off ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
// Couldn't find "solid" text at the beginning; it is binary STL.
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
function matchDataViewAt( query, reader, offset ) {
|
||||
|
||||
// Check if each byte in query matches the corresponding byte from the current offset
|
||||
|
||||
for ( var i = 0, il = query.length; i < il; i ++ ) {
|
||||
|
||||
if ( query[ i ] !== reader.getUint8( offset + i, false ) ) return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
function parseBinary( data ) {
|
||||
|
||||
var reader = new DataView( data );
|
||||
var faces = reader.getUint32( 80, true );
|
||||
|
||||
var r, g, b, hasColors = false, colors;
|
||||
var defaultR, defaultG, defaultB, alpha;
|
||||
|
||||
// process STL header
|
||||
// check for default color in header ("COLOR=rgba" sequence).
|
||||
|
||||
for ( var index = 0; index < 80 - 10; index ++ ) {
|
||||
|
||||
if ( ( reader.getUint32( index, false ) == 0x434F4C4F /*COLO*/ ) &&
|
||||
( reader.getUint8( index + 4 ) == 0x52 /*'R'*/ ) &&
|
||||
( reader.getUint8( index + 5 ) == 0x3D /*'='*/ ) ) {
|
||||
|
||||
hasColors = true;
|
||||
colors = new Float32Array( faces * 3 * 3 );
|
||||
|
||||
defaultR = reader.getUint8( index + 6 ) / 255;
|
||||
defaultG = reader.getUint8( index + 7 ) / 255;
|
||||
defaultB = reader.getUint8( index + 8 ) / 255;
|
||||
alpha = reader.getUint8( index + 9 ) / 255;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var dataOffset = 84;
|
||||
var faceLength = 12 * 4 + 2;
|
||||
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
|
||||
var vertices = new Float32Array( faces * 3 * 3 );
|
||||
var normals = new Float32Array( faces * 3 * 3 );
|
||||
|
||||
for ( var face = 0; face < faces; face ++ ) {
|
||||
|
||||
var start = dataOffset + face * faceLength;
|
||||
var normalX = reader.getFloat32( start, true );
|
||||
var normalY = reader.getFloat32( start + 4, true );
|
||||
var normalZ = reader.getFloat32( start + 8, true );
|
||||
|
||||
if ( hasColors ) {
|
||||
|
||||
var packedColor = reader.getUint16( start + 48, true );
|
||||
|
||||
if ( ( packedColor & 0x8000 ) === 0 ) {
|
||||
|
||||
// facet has its own unique color
|
||||
|
||||
r = ( packedColor & 0x1F ) / 31;
|
||||
g = ( ( packedColor >> 5 ) & 0x1F ) / 31;
|
||||
b = ( ( packedColor >> 10 ) & 0x1F ) / 31;
|
||||
|
||||
} else {
|
||||
|
||||
r = defaultR;
|
||||
g = defaultG;
|
||||
b = defaultB;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( var i = 1; i <= 3; i ++ ) {
|
||||
|
||||
var vertexstart = start + i * 12;
|
||||
var componentIdx = ( face * 3 * 3 ) + ( ( i - 1 ) * 3 );
|
||||
|
||||
vertices[ componentIdx ] = reader.getFloat32( vertexstart, true );
|
||||
vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true );
|
||||
vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true );
|
||||
|
||||
normals[ componentIdx ] = normalX;
|
||||
normals[ componentIdx + 1 ] = normalY;
|
||||
normals[ componentIdx + 2 ] = normalZ;
|
||||
|
||||
if ( hasColors ) {
|
||||
|
||||
colors[ componentIdx ] = r;
|
||||
colors[ componentIdx + 1 ] = g;
|
||||
colors[ componentIdx + 2 ] = b;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
|
||||
geometry.setAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
|
||||
|
||||
if ( hasColors ) {
|
||||
|
||||
geometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
|
||||
geometry.hasColors = true;
|
||||
geometry.alpha = alpha;
|
||||
|
||||
}
|
||||
|
||||
return geometry;
|
||||
|
||||
}
|
||||
|
||||
function parseASCII( data ) {
|
||||
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
var patternSolid = /solid([\s\S]*?)endsolid/g;
|
||||
var patternFace = /facet([\s\S]*?)endfacet/g;
|
||||
var faceCounter = 0;
|
||||
|
||||
var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source;
|
||||
var patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' );
|
||||
var patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' );
|
||||
|
||||
var vertices = [];
|
||||
var normals = [];
|
||||
|
||||
var normal = new THREE.Vector3();
|
||||
|
||||
var result;
|
||||
|
||||
var groupCount = 0;
|
||||
var startVertex = 0;
|
||||
var endVertex = 0;
|
||||
|
||||
while ( ( result = patternSolid.exec( data ) ) !== null ) {
|
||||
|
||||
startVertex = endVertex;
|
||||
|
||||
var solid = result[ 0 ];
|
||||
|
||||
while ( ( result = patternFace.exec( solid ) ) !== null ) {
|
||||
|
||||
var vertexCountPerFace = 0;
|
||||
var normalCountPerFace = 0;
|
||||
|
||||
var text = result[ 0 ];
|
||||
|
||||
while ( ( result = patternNormal.exec( text ) ) !== null ) {
|
||||
|
||||
normal.x = parseFloat( result[ 1 ] );
|
||||
normal.y = parseFloat( result[ 2 ] );
|
||||
normal.z = parseFloat( result[ 3 ] );
|
||||
normalCountPerFace ++;
|
||||
|
||||
}
|
||||
|
||||
while ( ( result = patternVertex.exec( text ) ) !== null ) {
|
||||
|
||||
vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );
|
||||
normals.push( normal.x, normal.y, normal.z );
|
||||
vertexCountPerFace ++;
|
||||
endVertex ++;
|
||||
|
||||
}
|
||||
|
||||
// every face have to own ONE valid normal
|
||||
|
||||
if ( normalCountPerFace !== 1 ) {
|
||||
|
||||
console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter );
|
||||
|
||||
}
|
||||
|
||||
// each face have to own THREE valid vertices
|
||||
|
||||
if ( vertexCountPerFace !== 3 ) {
|
||||
|
||||
console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter );
|
||||
|
||||
}
|
||||
|
||||
faceCounter ++;
|
||||
|
||||
}
|
||||
|
||||
var start = startVertex;
|
||||
var count = endVertex - startVertex;
|
||||
|
||||
geometry.addGroup( start, count, groupCount );
|
||||
groupCount ++;
|
||||
|
||||
}
|
||||
|
||||
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
|
||||
geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
|
||||
|
||||
return geometry;
|
||||
|
||||
}
|
||||
|
||||
function ensureString( buffer ) {
|
||||
|
||||
if ( typeof buffer !== 'string' ) {
|
||||
|
||||
return THREE.LoaderUtils.decodeText( new Uint8Array( buffer ) );
|
||||
|
||||
}
|
||||
|
||||
return buffer;
|
||||
|
||||
}
|
||||
|
||||
function ensureBinary( buffer ) {
|
||||
|
||||
if ( typeof buffer === 'string' ) {
|
||||
|
||||
var array_buffer = new Uint8Array( buffer.length );
|
||||
for ( var i = 0; i < buffer.length; i ++ ) {
|
||||
|
||||
array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian
|
||||
|
||||
}
|
||||
|
||||
return array_buffer.buffer || array_buffer;
|
||||
|
||||
} else {
|
||||
|
||||
return buffer;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// start
|
||||
|
||||
var binData = ensureBinary( data );
|
||||
|
||||
return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
87
{{cookiecutter.repo_name}}/resources/js/lightbox.js
Normal file
87
{{cookiecutter.repo_name}}/resources/js/lightbox.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
function setGallery(el) {
|
||||
var elements = document.body.querySelectorAll(".gallery");
|
||||
elements.forEach(element => {
|
||||
element.classList.remove('gallery');
|
||||
});
|
||||
if(el.closest('ul, p')) {
|
||||
var link_elements = el.closest('ul, p').querySelectorAll("a[class*='lightbox-']");
|
||||
link_elements.forEach(link_element => {
|
||||
link_element.classList.remove('current');
|
||||
});
|
||||
link_elements.forEach(link_element => {
|
||||
if(el.getAttribute('href') == link_element.getAttribute('href')) {
|
||||
link_element.classList.add('current');
|
||||
}
|
||||
});
|
||||
if(link_elements.length>1) {
|
||||
document.getElementById('lightbox').classList.add('gallery');
|
||||
link_elements.forEach(link_element => {
|
||||
link_element.classList.add('gallery');
|
||||
});
|
||||
}
|
||||
var currentkey;
|
||||
var gallery_elements = document.querySelectorAll('a.gallery');
|
||||
Object.keys(gallery_elements).forEach(function (k) {
|
||||
if(gallery_elements[k].classList.contains('current')) currentkey = k;
|
||||
});
|
||||
if(currentkey==(gallery_elements.length-1)) var nextkey = 0;
|
||||
else var nextkey = parseInt(currentkey)+1;
|
||||
if(currentkey==0) var prevkey = parseInt(gallery_elements.length-1);
|
||||
else var prevkey = parseInt(currentkey)-1;
|
||||
document.getElementById('next').addEventListener("click", function() {
|
||||
gallery_elements[nextkey].click();
|
||||
});
|
||||
document.getElementById('prev').addEventListener("click", function() {
|
||||
gallery_elements[prevkey].click();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
|
||||
//create lightbox div in the footer
|
||||
var newdiv = document.createElement("div");
|
||||
newdiv.tabIndex = 0;
|
||||
newdiv.setAttribute('id',"lightbox");
|
||||
document.body.appendChild(newdiv);
|
||||
|
||||
//add classes to links to be able to initiate lightboxes
|
||||
var elements = document.querySelectorAll('img');
|
||||
elements.forEach(element => {
|
||||
var url = element.getAttribute('data-stl');
|
||||
if(url) {
|
||||
element.classList.add('lightbox-image-stl');
|
||||
}
|
||||
});
|
||||
|
||||
//remove the clicked lightbox
|
||||
document.getElementById('lightbox').addEventListener("click", function(event) {
|
||||
if(event.target.id == 'close'){
|
||||
this.innerHTML = '';
|
||||
document.getElementById('lightbox').style.display = 'none';
|
||||
}
|
||||
});
|
||||
// hit esc to dismiss lightbox
|
||||
document.getElementById('lightbox').addEventListener("keydown", function(event) {
|
||||
if(event.key == 'Escape'){
|
||||
this.innerHTML = '';
|
||||
document.getElementById('lightbox').style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
//add the stl lightbox on click
|
||||
var elements = document.querySelectorAll('img.lightbox-image-stl');
|
||||
elements.forEach(element => {
|
||||
element.addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
document.getElementById('lightbox').innerHTML = '<a id="close"></a><a id="next">›</a><a id="prev">‹</a><div id="model" class="img" style="width: 100%; height:100%;" title="'+this.getAttribute('title')+'" ><</div><span>'+this.getAttribute('title')+'</span>';
|
||||
document.getElementById('lightbox').style.display = 'block';
|
||||
document.getElementById('model').setAttribute('data-rotate', 'x');
|
||||
document.getElementById('model').setAttribute('data-zdistance', '4');
|
||||
STLViewer(document.getElementById("model"), this.getAttribute('data-stl'));
|
||||
setGallery(this);
|
||||
document.getElementById('lightbox').focus();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
86
{{cookiecutter.repo_name}}/resources/js/stlviewer.js
Normal file
86
{{cookiecutter.repo_name}}/resources/js/stlviewer.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
|
||||
function STLViewerEnable(classname) {
|
||||
var models = document.getElementsByClassName(classname);
|
||||
for (var i = 0; i < models.length; i++) {
|
||||
STLViewer(models[i], models[i].getAttribute("data-src"));
|
||||
}
|
||||
}
|
||||
|
||||
function STLViewer(elem, model) {
|
||||
|
||||
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
var camera = new THREE.PerspectiveCamera(50, elem.clientWidth / elem.clientHeight, 1, 1000);
|
||||
|
||||
renderer.setSize(elem.clientWidth, elem.clientHeight);
|
||||
elem.appendChild(renderer.domElement);
|
||||
|
||||
var controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.rotateSpeed = 0.7;
|
||||
controls.dampingFactor = 0.1;
|
||||
controls.enableZoom = true;
|
||||
controls.enablePan = true;
|
||||
controls.autoRotate = true;
|
||||
controls.autoRotateSpeed = 0.75;
|
||||
|
||||
var scene = new THREE.Scene();
|
||||
|
||||
scene.add(new THREE.HemisphereLight(0xffffff, 0x080820, 1.5));
|
||||
|
||||
(new THREE.STLLoader()).load(model, function (geometry) {
|
||||
|
||||
// Determine the color
|
||||
var colorString = elem.getAttribute("data-color")
|
||||
if(colorString != null) { var color = new THREE.Color(colorString); }
|
||||
else { var color = 0xffff33 }
|
||||
|
||||
// Set up the material
|
||||
var material = new THREE.MeshPhongMaterial({ color: color, specular: 100, shininess: 100 });
|
||||
var mesh = new THREE.Mesh(geometry, material);
|
||||
scene.add(mesh);
|
||||
|
||||
// Compute the middle
|
||||
var middle = new THREE.Vector3();
|
||||
geometry.computeBoundingBox();
|
||||
geometry.boundingBox.getCenter(middle);
|
||||
|
||||
// Center it
|
||||
mesh.geometry.applyMatrix(new THREE.Matrix4().makeTranslation( -middle.x, -middle.y, -middle.z ) );
|
||||
|
||||
// Rotate, if desired
|
||||
if(elem.getAttribute("data-rotate") == "x")
|
||||
mesh.rotation.x = -Math.PI/3
|
||||
|
||||
// Pull the camera away as needed
|
||||
var largestDimension = Math.max(geometry.boundingBox.max.x - geometry.boundingBox.min.x,
|
||||
geometry.boundingBox.max.y - geometry.boundingBox.min.y,
|
||||
geometry.boundingBox.max.z - geometry.boundingBox.min.z)
|
||||
camera.position.z = largestDimension * elem.getAttribute("data-zdistance");
|
||||
|
||||
|
||||
var animate = function () {
|
||||
requestAnimationFrame(animate);
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
}; animate();
|
||||
|
||||
|
||||
const observer = new MutationObserver(function(mutationList, observer) {
|
||||
mutationList.forEach(element => {
|
||||
if(element.removedNodes.length > 0) {
|
||||
renderer.dispose();
|
||||
geometry.dispose();
|
||||
material.dispose();
|
||||
observer.disconnect();
|
||||
}
|
||||
})
|
||||
});
|
||||
observer.observe(elem.parentElement, {childList: true});
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
renderer.setSize(elem.clientWidth, elem.clientHeight);
|
||||
camera.aspect = elem.clientWidth / elem.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
1068
{{cookiecutter.repo_name}}/resources/js/three.min.js
vendored
Normal file
1068
{{cookiecutter.repo_name}}/resources/js/three.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3
{{cookiecutter.repo_name}}/resources/main.scad
Normal file
3
{{cookiecutter.repo_name}}/resources/main.scad
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
size = 10;
|
||||
|
||||
cube(size);
|
||||
9
{{cookiecutter.repo_name}}/resources/monolith.txt
Normal file
9
{{cookiecutter.repo_name}}/resources/monolith.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
The next time you see the full moon high in the south, look carefully at its
|
||||
right-hand edge and let your eye travel upward along the curve of the disk.
|
||||
Round about two o'clock you will notice a small, dark oval: anyone with normal
|
||||
eyesight can find it quite easily. It is the great walled plain, one of the
|
||||
finest on the Moon, known as the Mare Crisium---the Sea of Crises. Three hundred
|
||||
miles in diameter, and almost completely surrounded by a ring of magnificent
|
||||
mountains, it had never been explored until we entered it in the late summer of
|
||||
1996.
|
||||
[extract from SENTINEL OF ETERNITY (1951) by Arthur C. Clarke]
|
||||
17
{{cookiecutter.repo_name}}/rules.mk
Normal file
17
{{cookiecutter.repo_name}}/rules.mk
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# resources/gen/main has no recognized extension
|
||||
|
||||
-include resources/gen/main.stl.d
|
||||
resources/gen/main.stl: resources/main.scad
|
||||
mkdir -p $(dir resources/gen/main.stl)
|
||||
openscad -o resources/gen/main.stl.tmp.stl -d resources/gen/main.stl.d $(SCAD_FLAGS) resources/main.scad
|
||||
admesh -b resources/gen/main.stl resources/gen/main.stl.tmp.stl
|
||||
rm -f resources/gen/main.stl.tmp.stl
|
||||
default:: resources/gen/main.stl
|
||||
|
||||
|
||||
-include resources/gen/main.png.d
|
||||
resources/gen/main.png: resources/main.scad
|
||||
mkdir -p $(dir resources/gen/main.png)
|
||||
openscad -o resources/gen/main.png -d resources/gen/main.png.d $(SCAD_FLAGS) resources/main.scad
|
||||
default:: resources/gen/main.png
|
||||
|
||||
69
{{cookiecutter.repo_name}}/rules.py
Executable file
69
{{cookiecutter.repo_name}}/rules.py
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
MAKE_SCAD_STL = """
|
||||
-include {target}.d
|
||||
{target}: {src}
|
||||
\tmkdir -p $(dir {target})
|
||||
\topenscad -o {target}.tmp.stl -d {target}.d $(SCAD_FLAGS) {flags} {src}
|
||||
\tadmesh -b {target} {target}.tmp.stl
|
||||
\trm -f {target}.tmp.stl
|
||||
default:: {target}
|
||||
"""
|
||||
|
||||
MAKE_SCAD_PNG = """
|
||||
-include {target}.d
|
||||
{target}: {src}
|
||||
\tmkdir -p $(dir {target})
|
||||
\topenscad -o {target} -d {target}.d $(SCAD_FLAGS) {flags} {src}
|
||||
default:: {target}
|
||||
"""
|
||||
|
||||
MAKE_STL_PNG = """
|
||||
-include {target}.d
|
||||
{target}: {src}
|
||||
\tmkdir -p $(dir {target})
|
||||
\topenscad -o {target} -d {target}.d $(SCAD_FLAGS) {flags} -Dinput=\"{src}\" readfile.scad
|
||||
default:: {target}
|
||||
"""
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
for row in f:
|
||||
parts = row.split(":", 2)
|
||||
if len(parts) == 1:
|
||||
target = parts
|
||||
src = f"{target}.scad"
|
||||
if not os.exists(src):
|
||||
src = f"{target}.stl"
|
||||
flags = ""
|
||||
if len(parts) == 2:
|
||||
target, src = parts
|
||||
flags = ""
|
||||
else:
|
||||
target, src, flags = parts
|
||||
|
||||
target = target.strip()
|
||||
src = src.strip()
|
||||
flags = flags.strip()
|
||||
|
||||
src = f"resources/{src}"
|
||||
target = f"resources/gen/{target}"
|
||||
|
||||
if src.lower().endswith(".scad"):
|
||||
if target.lower().endswith(".stl"):
|
||||
print(MAKE_SCAD_STL.format(**{'target': target, 'src': src, 'flags': flags}))
|
||||
elif target.lower().endswith(".png"):
|
||||
print(MAKE_SCAD_PNG.format(**{'target': target, 'src': src, 'flags': flags}))
|
||||
else:
|
||||
print(f"# {target} has no recognized extension")
|
||||
print(MAKE_SCAD_STL.format(**{'target': f"{target}.stl", 'src': src, 'flags': flags}))
|
||||
print(MAKE_SCAD_PNG.format(**{'target': f"{target}.png", 'src': src, 'flags': flags}))
|
||||
elif src.lower().endswith(".stl"):
|
||||
if target.lower().endswith(".png"):
|
||||
print(MAKE_STL_PNG.format(**{'target': target, 'src': src, 'flags': flags}))
|
||||
else:
|
||||
print(MAKE_STL_PNG.format(**{'target': f"{target}.png", 'src': src, 'flags': flags}))
|
||||
else:
|
||||
raise RuntimeError(f"Don't know how to handle {src} -> {target}")
|
||||
Loading…
Reference in a new issue